Skip to content

Commit

Permalink
impl semantic highlighting
Browse files Browse the repository at this point in the history
  • Loading branch information
CsCherrYY committed Aug 31, 2021
1 parent cba7a88 commit 25ea215
Show file tree
Hide file tree
Showing 8 changed files with 320 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,21 @@

import java.io.IOException;
import java.net.Socket;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import com.microsoft.gradle.semantictokens.TokenModifier;
import com.microsoft.gradle.semantictokens.TokenType;

import org.eclipse.lsp4j.DocumentFilter;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.SaveOptions;
import org.eclipse.lsp4j.SemanticTokensLegend;
import org.eclipse.lsp4j.SemanticTokensServerFull;
import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.eclipse.lsp4j.TextDocumentSyncOptions;
Expand Down Expand Up @@ -61,6 +71,14 @@ public GradleLanguageServer() {
@Override
public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
ServerCapabilities serverCapabilities = new ServerCapabilities();
SemanticTokensWithRegistrationOptions semanticOptions = new SemanticTokensWithRegistrationOptions();
semanticOptions.setFull(new SemanticTokensServerFull(false));
semanticOptions.setRange(false);
semanticOptions.setDocumentSelector(List.of(new DocumentFilter("gradle", "file", null)));
semanticOptions.setLegend(new SemanticTokensLegend(
Arrays.stream(TokenType.values()).map(TokenType::toString).collect(Collectors.toList()),
Arrays.stream(TokenModifier.values()).map(TokenModifier::toString).collect(Collectors.toList())));
serverCapabilities.setSemanticTokensProvider(semanticOptions);
TextDocumentSyncOptions textDocumentSyncOptions = new TextDocumentSyncOptions();
textDocumentSyncOptions.setOpenClose(Boolean.TRUE);
textDocumentSyncOptions.setSave(new SaveOptions(Boolean.TRUE));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;

import com.microsoft.gradle.compile.GradleCompilationUnit;
import com.microsoft.gradle.manager.GradleFilesManager;
import com.microsoft.gradle.semantictokens.SemanticToken;
import com.microsoft.gradle.utils.LSPUtils;

import org.codehaus.groovy.control.CompilationFailedException;
Expand All @@ -39,6 +41,8 @@
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.SemanticTokens;
import org.eclipse.lsp4j.SemanticTokensParams;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageClientAware;
import org.eclipse.lsp4j.services.TextDocumentService;
Expand Down Expand Up @@ -102,6 +106,7 @@ private void compile(URI uri, GradleCompilationUnit unit) {
Set<PublishDiagnosticsParams> diagnostics = new HashSet<>();
try {
unit.compile(Phases.CANONICALIZATION);
unit.getVisitor().visitCompilationUnit();
// Send empty diagnostic if there is no error
diagnostics.add(new PublishDiagnosticsParams(uri.toString(), new ArrayList<>()));
} catch (CompilationFailedException e) {
Expand All @@ -117,7 +122,7 @@ private Set<PublishDiagnosticsParams> generateDiagnostics(ErrorCollector collect
Map<String, List<Diagnostic>> diagnosticsStorage = new HashMap<>();
for (Message error : collector.getErrors()) {
if (error instanceof SyntaxErrorMessage) {
SyntaxException exp = ((SyntaxErrorMessage)error).getCause();
SyntaxException exp = ((SyntaxErrorMessage) error).getCause();
Range range = LSPUtils.toRange(exp);
Diagnostic diagnostic = new Diagnostic();
diagnostic.setRange(range);
Expand All @@ -139,4 +144,15 @@ private Set<PublishDiagnosticsParams> generateDiagnostics(ErrorCollector collect
}
return diagnosticsParams;
}

@Override
public CompletableFuture<SemanticTokens> semanticTokensFull(SemanticTokensParams params) {
URI uri = URI.create(params.getTextDocument().getUri());
GradleCompilationUnit unit = this.gradleFilesManager.getCompilationUnit(uri);
if (unit == null) {
return CompletableFuture.completedFuture(new SemanticTokens(new ArrayList<>()));
}
return CompletableFuture
.completedFuture(new SemanticTokens(SemanticToken.encodedTokens(unit.getVisitor().getSemanticTokens())));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle.compile;

import java.util.ArrayList;
import java.util.List;

import com.microsoft.gradle.semantictokens.SemanticToken;
import com.microsoft.gradle.semantictokens.TokenModifier;
import com.microsoft.gradle.semantictokens.TokenType;

import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.expr.MapEntryExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.control.SourceUnit;

public class GradleASTVisitor extends ClassCodeVisitorSupport {

public GradleASTVisitor(GradleCompilationUnit unit) {
this.compilationUnit = unit;
}

private GradleCompilationUnit compilationUnit;
private List<SemanticToken> tokens = new ArrayList<>();

public List<SemanticToken> getSemanticTokens() {
return this.tokens;
}

private void addToken(int line, int column, int length, TokenType tokenType, int modifiers) {
if (length > 0) {
tokens.add(new SemanticToken(line, column, length, tokenType, modifiers));
}
}

private void addToken(ASTNode node, TokenType tokenType, int modifiers) {
addToken(node.getLineNumber(), node.getColumnNumber(), node.getLength(), tokenType, modifiers);
}

private void addToken(ASTNode node, TokenType tokenType) {
addToken(node.getLineNumber(), node.getColumnNumber(), node.getLength(), tokenType, 0);
}

public void visitCompilationUnit() {
this.compilationUnit.iterator().forEachRemaining(unit -> visitSourceUnit(unit));
}

public void visitSourceUnit(SourceUnit unit) {
ModuleNode moduleNode = unit.getAST();
if (moduleNode != null) {
visitModule(moduleNode);
}
}

public void visitModule(ModuleNode node) {
node.getClasses().forEach(classNode -> {
super.visitClass(classNode);
});
}

@Override
public void visitMethodCallExpression(MethodCallExpression node) {
if (TokenModifier.isDefaultLibrary(node.getMethod().getText())) {
addToken(node.getMethod(), TokenType.FUNCTION, TokenModifier.DEFAULT_LIBRARY.bitmask);
} else {
addToken(node.getMethod(), TokenType.FUNCTION);
}
super.visitMethodCallExpression(node);
}

@Override
public void visitMapEntryExpression(MapEntryExpression node) {
addToken(node.getKeyExpression(), TokenType.PARAMETER);
super.visitMapEntryExpression(node);
}

@Override
public void visitVariableExpression(VariableExpression node) {
addToken(node, TokenType.VARIABLE);
super.visitVariableExpression(node);
}

@Override
public void visitPropertyExpression(PropertyExpression node) {
addToken(node.getProperty(), TokenType.PROPERTY);
super.visitPropertyExpression(node);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,19 @@

public class GradleCompilationUnit extends CompilationUnit {
private Integer version;
private GradleASTVisitor visitor;

public GradleCompilationUnit(CompilerConfiguration configuration, CodeSource codeSource, GroovyClassLoader loader, Integer version) {
super(configuration, codeSource, loader);
this.version = version;
this.visitor = new GradleASTVisitor(this);
}

public Integer getVersion() {
return this.version;
}

public GradleASTVisitor getVisitor() {
return this.visitor;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,12 @@ public GradleCompilationUnit getCompilationUnit(URI uri, Integer version) {
return unit;
}

public GradleCompilationUnit getCompilationUnit(URI uri) {
// if there is no version info provided, we return the newest version
// when the previous cu exists, otherwise return null
if (this.unitStorage.containsKey(uri)) {
return this.unitStorage.get(uri);
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle.semantictokens;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class SemanticToken {
private final TokenType tokenType;
private final int tokenModifiers;
private final int line;
private final int column;
private final int length;

public SemanticToken(int line, int column, int length, TokenType tokenType, int tokenModifiers) {
this.line = line;
this.column = column;
this.length = length;
this.tokenType = tokenType;
this.tokenModifiers = tokenModifiers;
}

public TokenType getTokenType() {
return tokenType;
}

public int getTokenModifiers() {
return tokenModifiers;
}

public int getLine() {
return line;
}

public int getColumn() {
return column;
}

public int getLength() {
return length;
}

public static List<Integer> encodedTokens(List<SemanticToken> tokens) {
tokens.sort(new Comparator<SemanticToken>() {
@Override
public int compare(final SemanticToken a, final SemanticToken b) {
int lineResult = Integer.valueOf(a.getLine()).compareTo(Integer.valueOf(b.getLine()));
if (lineResult == 0) {
return Integer.valueOf(a.getColumn()).compareTo(Integer.valueOf(b.getColumn()));
}
return lineResult;
}
});
int numTokens = tokens.size();
List<Integer> data = new ArrayList<>(numTokens * 5);
int currentLine = 0;
int currentColumn = 0;
for (int i = 0; i < numTokens; i++) {
SemanticToken token = tokens.get(i);
int line = token.getLine() - 1;
int column = token.getColumn() - 1;
if (line < 0 || column < 0) {
continue;
}
int deltaLine = line - currentLine;
if (deltaLine != 0) {
currentLine = line;
currentColumn = 0;
}
int deltaColumn = column - currentColumn;
currentColumn = column;
// Disallow duplicate/conflict token (if exists)
if (deltaLine != 0 || deltaColumn != 0 || i == 0) {
int tokenTypeIndex = token.getTokenType().ordinal();
int tokenModifiers = token.getTokenModifiers();
data.add(deltaLine);
data.add(deltaColumn);
data.add(token.getLength());
data.add(tokenTypeIndex);
data.add(tokenModifiers);
}
}
return data;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle.semantictokens;

import java.util.List;

import org.eclipse.lsp4j.SemanticTokenModifiers;

public enum TokenModifier {

DEFAULT_LIBRARY(SemanticTokenModifiers.DefaultLibrary);

private String genericName;
// See https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html
private static List<String> defaultLibrary = List.of("afterEvaluate", "allprojects", "ant", "apply", "artifacts",
"beforeEvaluate", "buildscript", "configurations", "configure", "copy", "copySpec", "dependencies", "javaexec",
"repositories", "subprojects", "task");

public final int bitmask = 1 << ordinal();

TokenModifier(String genericName) {
this.genericName = genericName;
}

@Override
public String toString() {
return genericName;
}

public static boolean isDefaultLibrary(String method) {
if (TokenModifier.defaultLibrary.contains(method)) {
return true;
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.gradle.semantictokens;

import org.eclipse.lsp4j.SemanticTokenTypes;

public enum TokenType {
FUNCTION(SemanticTokenTypes.Function), PROPERTY(SemanticTokenTypes.Property), VARIABLE(SemanticTokenTypes.Variable),
PARAMETER(SemanticTokenTypes.Parameter);

private String genericName;

TokenType(String genericName) {
this.genericName = genericName;
}

@Override
public String toString() {
return genericName;
}
}

0 comments on commit 25ea215

Please sign in to comment.