Temporarily bring JDT LS semantic tokens into Boot LS to avoid conflicts
This commit is contained in:
@@ -32,21 +32,16 @@ import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.HoverParams;
|
||||
import org.eclipse.lsp4j.InlayHint;
|
||||
import org.eclipse.lsp4j.InlayHintParams;
|
||||
import org.eclipse.lsp4j.SemanticTokens;
|
||||
import org.eclipse.lsp4j.SemanticTokensDelta;
|
||||
import org.eclipse.lsp4j.SemanticTokensDeltaParams;
|
||||
import org.eclipse.lsp4j.SemanticTokensParams;
|
||||
import org.eclipse.lsp4j.SemanticTokensRangeParams;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.SemanticTokensLegend;
|
||||
import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.WorkspaceSymbol;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokensHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.CodeActionHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.CodeLensHandler;
|
||||
@@ -65,9 +60,7 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
public class CompositeLanguageServerComponents implements LanguageServerComponents {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CompositeLanguageServerComponents.class);
|
||||
|
||||
|
||||
public static class Builder {
|
||||
private Map<LanguageId, List<LanguageServerComponents>> componentsByLanguageId = new HashMap<>();
|
||||
|
||||
@@ -219,15 +212,12 @@ public class CompositeLanguageServerComponents implements LanguageServerComponen
|
||||
|
||||
List<SemanticTokensHandler> semanticTokenHandlers = componentsByLanguageId.values().stream().flatMap(l -> l.stream()).map(c -> c.getSemanticTokensHandler()).filter(o -> o.isPresent()).map(o -> o.get()).collect(Collectors.toList());
|
||||
List<SemanticTokensWithRegistrationOptions> listCapabilities = semanticTokenHandlers.stream().map(sth -> sth.getCapability()).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
for (int i = 1; i < listCapabilities.size(); i++) {
|
||||
SemanticTokensWithRegistrationOptions first = listCapabilities.get(0);
|
||||
SemanticTokensWithRegistrationOptions current = listCapabilities.get(i);
|
||||
if (!Objects.equals(first.getFull(), current.getFull())
|
||||
|| !Objects.equals(first.getLegend(), current.getLegend())
|
||||
|| !Objects.equals(first.getRange(), current.getRange())) {
|
||||
throw new IllegalStateException("Incompatible Semantic Token registrations for composite language server components");
|
||||
}
|
||||
}
|
||||
|
||||
SemanticTokensLegend legend = new SemanticTokensLegend(
|
||||
listCapabilities.stream().flatMap(cap -> cap.getLegend().getTokenTypes().stream()).distinct().collect(Collectors.toList()),
|
||||
listCapabilities.stream().flatMap(cap -> cap.getLegend().getTokenModifiers().stream()).distinct().collect(Collectors.toList())
|
||||
);
|
||||
|
||||
this.semanticTokensHanlder = semanticTokenHandlers.isEmpty() ? null : new SemanticTokensHandler() {
|
||||
|
||||
@Override
|
||||
@@ -236,32 +226,24 @@ public class CompositeLanguageServerComponents implements LanguageServerComponen
|
||||
SemanticTokensWithRegistrationOptions capabilities = new SemanticTokensWithRegistrationOptions();
|
||||
capabilities.setDocumentSelector(listCapabilities.stream().map(c -> c.getDocumentSelector()).flatMap(l -> l.stream()).collect(Collectors.toList()));
|
||||
if (!listCapabilities.isEmpty()) {
|
||||
SemanticTokensWithRegistrationOptions first = listCapabilities.get(0);
|
||||
capabilities.setFull(first.getFull());
|
||||
capabilities.setLegend(first.getLegend());
|
||||
capabilities.setRange(first.getRange());
|
||||
capabilities.setFull(true);
|
||||
capabilities.setLegend(legend);
|
||||
capabilities.setRange(false);
|
||||
}
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticTokens semanticTokensFull(SemanticTokensParams params, CancelChecker cancelChecker) {
|
||||
return findHandler(params.getTextDocument()).map(sth -> sth.semanticTokensFull(params, cancelChecker)).orElse(SemanticTokensHandler.super.semanticTokensFull(params, cancelChecker));
|
||||
public List<SemanticTokenData> semanticTokensFull(TextDocument doc, CancelChecker cancelChecker) {
|
||||
return findHandler(doc).map(sth -> sth.semanticTokensFull(doc, cancelChecker)).orElse(SemanticTokensHandler.super.semanticTokensFull(doc, cancelChecker));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Either<SemanticTokens, SemanticTokensDelta> semanticTokensFullDelta(
|
||||
SemanticTokensDeltaParams params, CancelChecker cancelChecker) {
|
||||
return findHandler(params.getTextDocument()).map(sth -> sth.semanticTokensFullDelta(params, cancelChecker)).orElse(SemanticTokensHandler.super.semanticTokensFullDelta(params, cancelChecker));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticTokens semanticTokensRange(SemanticTokensRangeParams params, CancelChecker cancelChecker) {
|
||||
return findHandler(params.getTextDocument()).map(sth -> sth.semanticTokensRange(params, cancelChecker)).orElse(SemanticTokensHandler.super.semanticTokensRange(params, cancelChecker));
|
||||
public List<SemanticTokenData> semanticTokensRange(TextDocument doc, Range range, CancelChecker cancelChecker) {
|
||||
return findHandler(doc).map(sth -> sth.semanticTokensRange(doc, range, cancelChecker)).orElse(SemanticTokensHandler.super.semanticTokensRange(doc, range, cancelChecker));
|
||||
}
|
||||
|
||||
private Optional<SemanticTokensHandler> findHandler(TextDocumentIdentifier docId) {
|
||||
TextDocument doc = server.getTextDocumentService().getLatestSnapshot(docId.getUri());
|
||||
private Optional<SemanticTokensHandler> findHandler(TextDocument doc) {
|
||||
// Only opened docs ideally should get requests for semantic token to highlight
|
||||
if (doc != null) {
|
||||
LanguageId language = doc.getLanguageId();
|
||||
@@ -269,8 +251,6 @@ public class CompositeLanguageServerComponents implements LanguageServerComponen
|
||||
if (subComponents != null) {
|
||||
return subComponents.stream().filter(sc -> sc.getSemanticTokensHandler().isPresent()).map(sc -> sc.getSemanticTokensHandler().get()).findFirst();
|
||||
}
|
||||
} else {
|
||||
log.error("Received Semantic Tokens request for a non opened document: %s".formatted(docId.getUri()));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@@ -10,28 +10,22 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.languageserver.semantic.tokens;
|
||||
|
||||
import org.eclipse.lsp4j.SemanticTokens;
|
||||
import org.eclipse.lsp4j.SemanticTokensDelta;
|
||||
import org.eclipse.lsp4j.SemanticTokensDeltaParams;
|
||||
import org.eclipse.lsp4j.SemanticTokensParams;
|
||||
import org.eclipse.lsp4j.SemanticTokensRangeParams;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public interface SemanticTokensHandler {
|
||||
|
||||
SemanticTokensWithRegistrationOptions getCapability();
|
||||
|
||||
default SemanticTokens semanticTokensFull(SemanticTokensParams params, CancelChecker cancelChecker) {
|
||||
default List<SemanticTokenData> semanticTokensFull(TextDocument doc, CancelChecker cancelChecker) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default Either<SemanticTokens, SemanticTokensDelta> semanticTokensFullDelta(SemanticTokensDeltaParams params, CancelChecker cancelChecker) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default SemanticTokens semanticTokensRange(SemanticTokensRangeParams params, CancelChecker cancelChecker) {
|
||||
default List<SemanticTokenData> semanticTokensRange(TextDocument doc, Range range, CancelChecker cancelChecker) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,15 @@ import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.lsp4j.SemanticTokensLegend;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class SemanticTokensUtils {
|
||||
|
||||
private static Logger log = LoggerFactory.getLogger(SemanticTokensUtils.class);
|
||||
|
||||
private static int getSemanticTokenTypeIndex(SemanticTokensLegend legend, String tokenType) {
|
||||
return legend.getTokenTypes().indexOf(tokenType);
|
||||
}
|
||||
@@ -59,5 +65,35 @@ public class SemanticTokensUtils {
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public static List<Integer> mapTokensDataToLsp(TextDocument doc, SemanticTokensLegend legend,
|
||||
List<SemanticTokenData> tokensData) {
|
||||
|
||||
// Sort tokens by start offset
|
||||
Collections.sort(tokensData);
|
||||
|
||||
List<Integer> data = new ArrayList<>(tokensData.size() * 5);
|
||||
|
||||
// Encode relative positions for tokens
|
||||
int previousLine = 0;
|
||||
int previousColumn = 0;
|
||||
for (SemanticTokenData tokenData : tokensData) {
|
||||
try {
|
||||
int currentLine = doc.getLineOfOffset(tokenData.start());
|
||||
int currentColumn = tokenData.start() - doc.getLineOffset(currentLine);
|
||||
data.add(currentLine - previousLine);
|
||||
data.add(currentLine == previousLine ? currentColumn - previousColumn : currentColumn);
|
||||
data.add(tokenData.end() - tokenData.start());
|
||||
data.add(SemanticTokensUtils.getSemanticTokenTypeIndex(legend, tokenData.type()));
|
||||
data.add(SemanticTokensUtils.getSemanticTokenModifiersFlags(legend, tokenData.modifiers()));
|
||||
previousLine = currentLine;
|
||||
previousColumn = currentColumn;
|
||||
} catch (BadLocationException e) {
|
||||
log.error("", e);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -60,8 +60,6 @@ import org.eclipse.lsp4j.PublishDiagnosticsParams;
|
||||
import org.eclipse.lsp4j.ReferenceParams;
|
||||
import org.eclipse.lsp4j.RenameParams;
|
||||
import org.eclipse.lsp4j.SemanticTokens;
|
||||
import org.eclipse.lsp4j.SemanticTokensDelta;
|
||||
import org.eclipse.lsp4j.SemanticTokensDeltaParams;
|
||||
import org.eclipse.lsp4j.SemanticTokensParams;
|
||||
import org.eclipse.lsp4j.SemanticTokensRangeParams;
|
||||
import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions;
|
||||
@@ -88,6 +86,7 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.ide.vscode.commons.languageserver.config.LanguageServerProperties;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokensHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokensUtils;
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
@@ -458,30 +457,34 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE
|
||||
|
||||
@Override
|
||||
public CompletableFuture<SemanticTokens> semanticTokensFull(SemanticTokensParams params) {
|
||||
if (semanticTokensHandler == null) {
|
||||
return CompletableFuture.completedFuture(new SemanticTokens());
|
||||
} else {
|
||||
return CompletableFutures.computeAsync(messageWorkerThreadPool, cancelChecker -> semanticTokensHandler.semanticTokensFull(params, cancelChecker));
|
||||
if (semanticTokensHandler != null) {
|
||||
TextDocument doc = getLatestSnapshot(params.getTextDocument().getUri());
|
||||
if (doc != null) {
|
||||
return CompletableFutures.computeAsync(/*messageWorkerThreadPool,*/ cancelChecker -> semanticTokensHandler.semanticTokensFull(doc, cancelChecker)).thenApply(std -> {
|
||||
if (std != null && !std.isEmpty()) {
|
||||
return new SemanticTokens(SemanticTokensUtils.mapTokensDataToLsp(doc, semanticTokensHandler.getCapability().getLegend(), std));
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Either<SemanticTokens, SemanticTokensDelta>> semanticTokensFullDelta(
|
||||
SemanticTokensDeltaParams params) {
|
||||
if (semanticTokensHandler == null) {
|
||||
return CompletableFuture.completedFuture(Either.forLeft(new SemanticTokens()));
|
||||
} else {
|
||||
return CompletableFutures.computeAsync(messageWorkerThreadPool, cancelChecker -> semanticTokensHandler.semanticTokensFullDelta(params, cancelChecker));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public CompletableFuture<SemanticTokens> semanticTokensRange(SemanticTokensRangeParams params) {
|
||||
if (semanticTokensHandler == null) {
|
||||
return CompletableFuture.completedFuture(new SemanticTokens());
|
||||
} else {
|
||||
return CompletableFutures.computeAsync(messageWorkerThreadPool, cancelChecker -> semanticTokensHandler.semanticTokensRange(params, cancelChecker));
|
||||
if (semanticTokensHandler != null) {
|
||||
TextDocument doc = getLatestSnapshot(params.getTextDocument().getUri());
|
||||
if (doc != null) {
|
||||
return CompletableFutures.computeAsync(/*messageWorkerThreadPool,*/ cancelChecker -> semanticTokensHandler.semanticTokensRange(doc, params.getRange(), cancelChecker)).thenApply(std -> {
|
||||
if (std != null && !std.isEmpty()) {
|
||||
return new SemanticTokens(SemanticTokensUtils.mapTokensDataToLsp(doc, semanticTokensHandler.getCapability().getLegend(), std));
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
private List<Either<Command, CodeAction>> computeCodeActions(CancelChecker cancelToken, CodeActionCapabilities capabilities, TextDocument doc, CodeActionParams params) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 Pivotal, Inc.
|
||||
* Copyright (c) 2016, 2024 Pivotal, Inc.
|
||||
* 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
|
||||
@@ -37,4 +37,8 @@ public class Collector<T> implements IRequestor<T> {
|
||||
public List<T> get() {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return nodes.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@ package org.springframework.ide.vscode.boot.app;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.HqlSemanticTokens;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.QueryJdtAstReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtDataQuerySemanticTokensProvider;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JpqlSemanticTokens;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JpqlSupportState;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.QueryJdtAstReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.AddConfigurationIfBeansPresentReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.AnnotationNodeReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.AuthorizeHttpRequestsReconciler;
|
||||
@@ -35,6 +35,7 @@ import org.springframework.ide.vscode.boot.java.reconcilers.PreciseBeanTypeRecon
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.ServerHttpSecurityLambdaDslReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.UnnecessarySpringExtensionReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.WebSecurityConfigurerAdapterReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.semantictokens.JavaSemanticTokensProvider;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@@ -112,6 +113,10 @@ public class JdtConfig {
|
||||
return new EntityIdForRepoReconciler();
|
||||
}
|
||||
|
||||
@Bean JavaSemanticTokensProvider javaSemanticTokens() {
|
||||
return new JavaSemanticTokensProvider();
|
||||
}
|
||||
|
||||
@Bean JdtDataQuerySemanticTokensProvider jpqlJdtSemanticTokensProvider(JpqlSemanticTokens jpqlProvider, HqlSemanticTokens hqlProvider, JpqlSupportState supportState) {
|
||||
return new JdtDataQuerySemanticTokensProvider(jpqlProvider, hqlProvider, supportState);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
package org.springframework.ide.vscode.boot.java;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -18,18 +19,20 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.lsp4j.DocumentFilter;
|
||||
import org.eclipse.lsp4j.SemanticTokens;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.SemanticTokensLegend;
|
||||
import org.eclipse.lsp4j.SemanticTokensParams;
|
||||
import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.CompositeASTVisitor;
|
||||
import org.springframework.ide.vscode.boot.java.semantictokens.JavaSemanticTokensProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokensHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokensUtils;
|
||||
import org.springframework.ide.vscode.commons.util.Collector;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class JdtSemanticTokensHandler implements SemanticTokensHandler {
|
||||
|
||||
@@ -38,9 +41,19 @@ public class JdtSemanticTokensHandler implements SemanticTokensHandler {
|
||||
private final Collection<JdtSemanticTokensProvider> tokenProviders;
|
||||
private final SemanticTokensLegend legend;
|
||||
|
||||
private JavaSemanticTokensProvider jdtLsProvider;
|
||||
|
||||
public JdtSemanticTokensHandler(CompilationUnitCache cuCache, JavaProjectFinder projectFinder, Collection<JdtSemanticTokensProvider> tokenProviders) {
|
||||
this.cuCache = cuCache;
|
||||
this.projectFinder = projectFinder;
|
||||
ArrayList<JdtSemanticTokensProvider> tokenProvidersList = new ArrayList<>(tokenProviders.size());
|
||||
for (JdtSemanticTokensProvider tp : tokenProviders) {
|
||||
if (tp instanceof JavaSemanticTokensProvider jdtLsProvider) {
|
||||
this.jdtLsProvider = jdtLsProvider;
|
||||
} else {
|
||||
tokenProvidersList.add(tp);
|
||||
}
|
||||
}
|
||||
this.tokenProviders = tokenProviders;
|
||||
this.legend = new SemanticTokensLegend(
|
||||
tokenProviders.stream().flatMap(tp -> tp.getTokenTypes().stream()).distinct().collect(Collectors.toList()),
|
||||
@@ -54,31 +67,57 @@ public class JdtSemanticTokensHandler implements SemanticTokensHandler {
|
||||
DocumentFilter documentFilter = new DocumentFilter();
|
||||
documentFilter.setLanguage(LanguageId.JAVA.getId());
|
||||
capabilities.setDocumentSelector(List.of(documentFilter));
|
||||
capabilities.setFull(true);
|
||||
capabilities.setFull(false);
|
||||
capabilities.setRange(true);
|
||||
capabilities.setLegend(legend);
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public List<SemanticTokenData> semanticTokensFull(TextDocument doc, CancelChecker cancelChecker) {
|
||||
return semanticTokens(doc, cancelChecker, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticTokens semanticTokensFull(SemanticTokensParams params, CancelChecker cancelChecker) {
|
||||
Optional<IJavaProject> optProject = projectFinder.find(params.getTextDocument());
|
||||
public List<SemanticTokenData> semanticTokensRange(TextDocument doc, Range range, CancelChecker cancelChecker) {
|
||||
return semanticTokens(doc, cancelChecker, range);
|
||||
}
|
||||
|
||||
private List<SemanticTokenData> semanticTokens(TextDocument doc, CancelChecker cancelChecker, Range r) {
|
||||
Optional<IJavaProject> optProject = projectFinder.find(doc.getId());
|
||||
if (optProject.isPresent()) {
|
||||
IJavaProject jp = optProject.get();
|
||||
List<JdtSemanticTokensProvider> applicableTokenProviders = tokenProviders.stream().filter(tp -> tp.isApplicable(jp)).collect(Collectors.toList());
|
||||
if (!applicableTokenProviders.isEmpty()) {
|
||||
return cuCache.withCompilationUnit(jp, URI.create(params.getTextDocument().getUri()), cu -> computeTokens(applicableTokenProviders, jp, cu));
|
||||
return cuCache.withCompilationUnit(jp, URI.create(doc.getUri()), cu -> computeTokens(applicableTokenProviders, jp, cu, r));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private SemanticTokens computeTokens(List<JdtSemanticTokensProvider> applicableTokenProviders, IJavaProject jp, CompilationUnit cu) {
|
||||
private List<SemanticTokenData> computeTokens(List<JdtSemanticTokensProvider> applicableTokenProviders, IJavaProject jp, CompilationUnit cu, Range r) {
|
||||
if (cu == null) {
|
||||
return null;
|
||||
}
|
||||
List<SemanticTokenData> tokensData = applicableTokenProviders.stream().map(tp -> tp.computeTokens(jp, cu)).flatMap(t -> t.stream()).collect(Collectors.toList());
|
||||
List<Integer> toLsp = SemanticTokensUtils.mapTokensDataToLsp(tokensData, legend, offset -> cu.getLineNumber(offset) - 1, cu::getColumnNumber);
|
||||
return toLsp.isEmpty() ? null : new SemanticTokens(toLsp);
|
||||
Collector<SemanticTokenData> collector = new Collector<>();
|
||||
CompositeASTVisitor visitor = new CompositeASTVisitor();
|
||||
applicableTokenProviders.forEach(tp -> visitor.add(tp.getTokensComputer(jp, cu, collector)));
|
||||
if (r != null) {
|
||||
if (r.getStart() != null) {
|
||||
visitor.setStartOffset(cu.getPosition(r.getStart().getLine(), r.getStart().getCharacter()));
|
||||
}
|
||||
if (r.getEnd() != null) {
|
||||
visitor.setEndOffset(cu.getPosition(r.getEnd().getLine(), r.getEnd().getCharacter()));
|
||||
}
|
||||
}
|
||||
cu.accept(visitor);
|
||||
if (!collector.isEmpty() && jdtLsProvider != null) {
|
||||
// If there are tokens computed then also run JDT LS tokens provider not to lose JDT LS semantic highlights
|
||||
cu.accept(jdtLsProvider.getTokensComputer(jp, cu, collector));
|
||||
}
|
||||
return collector.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,15 +13,17 @@ package org.springframework.ide.vscode.boot.java;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
|
||||
import org.springframework.ide.vscode.commons.util.Collector;
|
||||
|
||||
public interface JdtSemanticTokensProvider {
|
||||
|
||||
List<String> getTokenTypes();
|
||||
default List<String> getTokenModifiers() { return Collections.emptyList(); }
|
||||
List<SemanticTokenData> computeTokens(IJavaProject project, CompilationUnit cu);
|
||||
boolean isApplicable(IJavaProject project);
|
||||
ASTVisitor getTokensComputer(IJavaProject project, CompilationUnit cu, Collector<SemanticTokenData> collector);
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.data.jpa.queries;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -32,6 +31,7 @@ import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokensDataProvider;
|
||||
import org.springframework.ide.vscode.commons.util.Collector;
|
||||
|
||||
public class JdtDataQuerySemanticTokensProvider implements JdtSemanticTokensProvider {
|
||||
|
||||
@@ -59,13 +59,9 @@ public class JdtDataQuerySemanticTokensProvider implements JdtSemanticTokensProv
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SemanticTokenData> computeTokens(IJavaProject jp, CompilationUnit cu) {
|
||||
List<SemanticTokenData> tokensData = new ArrayList<>();
|
||||
|
||||
public ASTVisitor getTokensComputer(IJavaProject jp, CompilationUnit cu, Collector<SemanticTokenData> tokensData) {
|
||||
SemanticTokensDataProvider provider = SpringProjectUtil.hasDependencyStartingWith(jp, "hibernate-core", null) ? hqlProvider : jpqlProvider;
|
||||
|
||||
cu.accept(new ASTVisitor() {
|
||||
|
||||
return new ASTVisitor() {
|
||||
@Override
|
||||
public boolean visit(NormalAnnotation a) {
|
||||
if (isQueryAnnotation(a)) {
|
||||
@@ -100,7 +96,7 @@ public class JdtDataQuerySemanticTokensProvider implements JdtSemanticTokensProv
|
||||
if (isNative) {
|
||||
//TODO: SQL semantic tokens
|
||||
} else {
|
||||
tokensData.addAll(computeTokensForQueryExpression(provider, queryExpression));
|
||||
computeTokensForQueryExpression(provider, queryExpression).forEach(tokensData::accept);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +108,7 @@ public class JdtDataQuerySemanticTokensProvider implements JdtSemanticTokensProv
|
||||
@Override
|
||||
public boolean visit(SingleMemberAnnotation a) {
|
||||
if (isQueryAnnotation(a)) {
|
||||
tokensData.addAll(computeTokensForQueryExpression(provider, a.getValue()));
|
||||
computeTokensForQueryExpression(provider, a.getValue()).forEach(tokensData::accept);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -123,16 +119,13 @@ public class JdtDataQuerySemanticTokensProvider implements JdtSemanticTokensProv
|
||||
IMethodBinding methodBinding = node.resolveMethodBinding();
|
||||
if ("jakarta.persistence.EntityManager".equals(methodBinding.getDeclaringClass().getQualifiedName())) {
|
||||
if (methodBinding.getParameterTypes().length <= 2 && "java.lang.String".equals(methodBinding.getParameterTypes()[0].getQualifiedName())) {
|
||||
tokensData.addAll(computeTokensForQueryExpression(provider, queryExpr));
|
||||
computeTokensForQueryExpression(provider, queryExpr).forEach(tokensData::accept);
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return tokensData;
|
||||
};
|
||||
}
|
||||
|
||||
private static List<SemanticTokenData> computeTokensForQueryExpression(SemanticTokensDataProvider provider, Expression valueExp) {
|
||||
|
||||
@@ -27,7 +27,7 @@ public class JpaQueryPropertiesLanguageServerComponents implements LanguageServe
|
||||
|
||||
public JpaQueryPropertiesLanguageServerComponents(SimpleTextDocumentService documents, JavaProjectFinder projectsFinder,
|
||||
JpqlSemanticTokens jpqlSemanticTokensProvider, HqlSemanticTokens hqlSematicTokensProvider, JpqlSupportState supportState) {
|
||||
this.semanticTokensHandler = new QueryPropertiesSemanticTokensHandler(documents, projectsFinder, jpqlSemanticTokensProvider, hqlSematicTokensProvider, supportState);
|
||||
this.semanticTokensHandler = new QueryPropertiesSemanticTokensHandler(projectsFinder, jpqlSemanticTokensProvider, hqlSematicTokensProvider, supportState);
|
||||
this.reconcileEngine = new NamedQueryPropertiesReconcileEngine(projectsFinder);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ public final class JpqlSupportState {
|
||||
this.enabled = enabled;
|
||||
config.addListener(v -> setEnabled(config.isJpqlEnabled()));
|
||||
projectObserver.addListener(ProjectObserver.onAny(jp -> {
|
||||
if (enabled && server.getWorkspaceService().supportsSemanticTokensRefresh()) {
|
||||
if (this.enabled && server.getWorkspaceService().supportsSemanticTokensRefresh()) {
|
||||
server.getAsync().execute(() -> server.getClient().refreshSemanticTokens());
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -11,29 +11,21 @@
|
||||
package org.springframework.ide.vscode.boot.java.data.jpa.queries;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.lsp4j.DocumentFilter;
|
||||
import org.eclipse.lsp4j.SemanticTokens;
|
||||
import org.eclipse.lsp4j.SemanticTokensLegend;
|
||||
import org.eclipse.lsp4j.SemanticTokensParams;
|
||||
import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokensDataProvider;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokensHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokensUtils;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
import org.springframework.ide.vscode.java.properties.antlr.parser.AntlrParser;
|
||||
@@ -43,17 +35,13 @@ import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Value
|
||||
|
||||
public class QueryPropertiesSemanticTokensHandler implements SemanticTokensHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(QueryPropertiesSemanticTokensHandler.class);
|
||||
|
||||
private final JavaProjectFinder projectFinder;
|
||||
private final SimpleTextDocumentService documents;
|
||||
private final JpqlSemanticTokens jpqlTokensProvider;
|
||||
private final HqlSemanticTokens hqlTokensProvider;
|
||||
private final JpqlSupportState supportState;
|
||||
|
||||
|
||||
public QueryPropertiesSemanticTokensHandler(SimpleTextDocumentService documents, JavaProjectFinder projectFinder, JpqlSemanticTokens jpqlTokensProvider, HqlSemanticTokens hqlTokensProvider, JpqlSupportState supportState) {
|
||||
this.documents = documents;
|
||||
public QueryPropertiesSemanticTokensHandler(JavaProjectFinder projectFinder, JpqlSemanticTokens jpqlTokensProvider, HqlSemanticTokens hqlTokensProvider, JpqlSupportState supportState) {
|
||||
this.projectFinder = projectFinder;
|
||||
this.jpqlTokensProvider = jpqlTokensProvider;
|
||||
this.hqlTokensProvider = hqlTokensProvider;
|
||||
@@ -74,13 +62,12 @@ public class QueryPropertiesSemanticTokensHandler implements SemanticTokensHandl
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticTokens semanticTokensFull(SemanticTokensParams params, CancelChecker cancelChecker) {
|
||||
public List<SemanticTokenData> semanticTokensFull(TextDocument doc, CancelChecker cancelChecker) {
|
||||
if (!supportState.isEnabled()) {
|
||||
return new SemanticTokens();
|
||||
return null;
|
||||
}
|
||||
Optional<IJavaProject> optProject = projectFinder.find(params.getTextDocument());
|
||||
Optional<IJavaProject> optProject = projectFinder.find(doc.getId());
|
||||
if (optProject.isPresent() && SpringProjectUtil.hasDependencyStartingWith(optProject.get(), "spring-data-jpa", null)) {
|
||||
TextDocument doc = documents.getLatestSnapshot(params.getTextDocument().getUri());
|
||||
if (doc != null) {
|
||||
SemanticTokensDataProvider tokensProvider = SpringProjectUtil.hasDependencyStartingWith(optProject.get(), "hibernate-core", null) ? hqlTokensProvider : jpqlTokensProvider;
|
||||
AntlrParser propertiesParser = new AntlrParser();
|
||||
@@ -92,26 +79,10 @@ public class QueryPropertiesSemanticTokensHandler implements SemanticTokensHandl
|
||||
data.addAll(tokensProvider.computeTokens(value.decode(), value.getOffset()));
|
||||
}
|
||||
}
|
||||
Collections.sort(data);
|
||||
SemanticTokensLegend legend = new SemanticTokensLegend(tokensProvider.getTokenTypes(), tokensProvider.getTypeModifiers());
|
||||
return new SemanticTokens(SemanticTokensUtils.mapTokensDataToLsp(data, legend, t -> {
|
||||
try {
|
||||
return doc.getLineOfOffset(t);
|
||||
} catch (BadLocationException e) {
|
||||
log.error("", e);
|
||||
}
|
||||
return -1;
|
||||
}, o -> {
|
||||
try {
|
||||
return o - doc.getLineOffset(doc.getLineOfOffset(o));
|
||||
} catch (BadLocationException e) {
|
||||
log.error("", e);
|
||||
}
|
||||
return -1;
|
||||
}));
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return SemanticTokensHandler.super.semanticTokensFull(params, cancelChecker);
|
||||
return SemanticTokensHandler.super.semanticTokensFull(doc, cancelChecker);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.java.reconcilers;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.FieldDeclaration;
|
||||
import org.eclipse.jdt.core.dom.ImportDeclaration;
|
||||
@@ -29,6 +30,8 @@ import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
public class CompositeASTVisitor extends ASTVisitor {
|
||||
|
||||
List<ASTVisitor> visitors = new ArrayList<>();
|
||||
private int startOffset = -1;
|
||||
private int endOffset = -1;
|
||||
|
||||
public void add(ASTVisitor visitor) {
|
||||
visitors.add(visitor);
|
||||
@@ -37,8 +40,11 @@ public class CompositeASTVisitor extends ASTVisitor {
|
||||
@Override
|
||||
public boolean visit(TypeDeclaration node) {
|
||||
boolean result = true;
|
||||
if (!checkOffset(node)) {
|
||||
return false;
|
||||
}
|
||||
for (ASTVisitor astVisitor : visitors) {
|
||||
result &= astVisitor.visit(node);
|
||||
result |= astVisitor.visit(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -46,8 +52,11 @@ public class CompositeASTVisitor extends ASTVisitor {
|
||||
@Override
|
||||
public boolean visit(MethodInvocation node) {
|
||||
boolean result = true;
|
||||
if (!checkOffset(node)) {
|
||||
return false;
|
||||
}
|
||||
for (ASTVisitor astVisitor : visitors) {
|
||||
result &= astVisitor.visit(node);
|
||||
result |= astVisitor.visit(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -55,8 +64,11 @@ public class CompositeASTVisitor extends ASTVisitor {
|
||||
@Override
|
||||
public boolean visit(MethodDeclaration node) {
|
||||
boolean result = true;
|
||||
if (!checkOffset(node)) {
|
||||
return false;
|
||||
}
|
||||
for (ASTVisitor astVisitor : visitors) {
|
||||
result &= astVisitor.visit(node);
|
||||
result |= astVisitor.visit(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -71,8 +83,11 @@ public class CompositeASTVisitor extends ASTVisitor {
|
||||
@Override
|
||||
public boolean visit(FieldDeclaration node) {
|
||||
boolean result = true;
|
||||
if (!checkOffset(node)) {
|
||||
return false;
|
||||
}
|
||||
for (ASTVisitor astVisitor : visitors) {
|
||||
result &= astVisitor.visit(node);
|
||||
result |= astVisitor.visit(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -80,8 +95,11 @@ public class CompositeASTVisitor extends ASTVisitor {
|
||||
@Override
|
||||
public boolean visit(SingleMemberAnnotation node) {
|
||||
boolean result = true;
|
||||
if (!checkOffset(node)) {
|
||||
return false;
|
||||
}
|
||||
for (ASTVisitor astVisitor : visitors) {
|
||||
result &= astVisitor.visit(node);
|
||||
result |= astVisitor.visit(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -89,8 +107,11 @@ public class CompositeASTVisitor extends ASTVisitor {
|
||||
@Override
|
||||
public boolean visit(NormalAnnotation node) {
|
||||
boolean result = true;
|
||||
if (!checkOffset(node)) {
|
||||
return false;
|
||||
}
|
||||
for (ASTVisitor astVisitor : visitors) {
|
||||
result &= astVisitor.visit(node);
|
||||
result |= astVisitor.visit(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -98,8 +119,11 @@ public class CompositeASTVisitor extends ASTVisitor {
|
||||
@Override
|
||||
public boolean visit(MarkerAnnotation node) {
|
||||
boolean result = true;
|
||||
if (!checkOffset(node)) {
|
||||
return false;
|
||||
}
|
||||
for (ASTVisitor astVisitor : visitors) {
|
||||
result &= astVisitor.visit(node);
|
||||
result |= astVisitor.visit(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -107,8 +131,11 @@ public class CompositeASTVisitor extends ASTVisitor {
|
||||
@Override
|
||||
public boolean visit(ImportDeclaration node) {
|
||||
boolean result = true;
|
||||
if (!checkOffset(node)) {
|
||||
return false;
|
||||
}
|
||||
for (ASTVisitor astVisitor : visitors) {
|
||||
result &= astVisitor.visit(node);
|
||||
result |= astVisitor.visit(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -116,8 +143,11 @@ public class CompositeASTVisitor extends ASTVisitor {
|
||||
@Override
|
||||
public boolean visit(SimpleType node) {
|
||||
boolean result = true;
|
||||
if (!checkOffset(node)) {
|
||||
return false;
|
||||
}
|
||||
for (ASTVisitor astVisitor : visitors) {
|
||||
result &= astVisitor.visit(node);
|
||||
result |= astVisitor.visit(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -125,8 +155,11 @@ public class CompositeASTVisitor extends ASTVisitor {
|
||||
@Override
|
||||
public boolean visit(QualifiedName node) {
|
||||
boolean result = true;
|
||||
if (!checkOffset(node)) {
|
||||
return false;
|
||||
}
|
||||
for (ASTVisitor astVisitor : visitors) {
|
||||
result &= astVisitor.visit(node);
|
||||
result |= astVisitor.visit(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -134,10 +167,34 @@ public class CompositeASTVisitor extends ASTVisitor {
|
||||
@Override
|
||||
public boolean visit(ReturnStatement node) {
|
||||
boolean result = true;
|
||||
if (!checkOffset(node)) {
|
||||
return false;
|
||||
}
|
||||
for (ASTVisitor astVisitor : visitors) {
|
||||
result &= astVisitor.visit(node);
|
||||
result |= astVisitor.visit(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean checkOffset(ASTNode n) {
|
||||
return (startOffset < 0 || n.getStartPosition() >= startOffset)
|
||||
|| (endOffset <0 || n.getStartPosition() + n.getLength() < endOffset);
|
||||
}
|
||||
|
||||
public int getStartOffset() {
|
||||
return startOffset;
|
||||
}
|
||||
|
||||
public void setStartOffset(int startOffset) {
|
||||
this.startOffset = startOffset;
|
||||
}
|
||||
|
||||
public int getEndOffset() {
|
||||
return endOffset;
|
||||
}
|
||||
|
||||
public void setEndOffset(int endOffset) {
|
||||
this.endOffset = endOffset;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom, Inc.
|
||||
* 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
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.semantictokens;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.springframework.ide.vscode.boot.java.JdtSemanticTokensProvider;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
|
||||
import org.springframework.ide.vscode.commons.util.Collector;
|
||||
|
||||
public class JavaSemanticTokensProvider implements JdtSemanticTokensProvider {
|
||||
|
||||
@Override
|
||||
public List<String> getTokenTypes() {
|
||||
return Arrays.stream(TokenType.values()).map(TokenType::toString).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTokenModifiers() {
|
||||
return Arrays.stream(TokenModifier.values()).map(TokenModifier::toString).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(IJavaProject project) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ASTVisitor getTokensComputer(IJavaProject project, CompilationUnit cu,
|
||||
Collector<SemanticTokenData> collector) {
|
||||
return new SemanticTokensVisitor(cu, collector);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,733 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom, Inc.
|
||||
* 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
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
// Copied from JDT LS to workaround https://github.com/spring-projects/sts4/issues/1249
|
||||
package org.springframework.ide.vscode.boot.java.semantictokens;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jdt.core.ITypeRoot;
|
||||
import org.eclipse.jdt.core.JavaCore;
|
||||
import org.eclipse.jdt.core.JavaModelException;
|
||||
import org.eclipse.jdt.core.ToolFactory;
|
||||
import org.eclipse.jdt.core.compiler.IScanner;
|
||||
import org.eclipse.jdt.core.compiler.ITerminalSymbols;
|
||||
import org.eclipse.jdt.core.compiler.InvalidInputException;
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
|
||||
import org.eclipse.jdt.core.dom.Comment;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.ExportsDirective;
|
||||
import org.eclipse.jdt.core.dom.IBinding;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.IPackageBinding;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.ImportDeclaration;
|
||||
import org.eclipse.jdt.core.dom.Javadoc;
|
||||
import org.eclipse.jdt.core.dom.MethodInvocation;
|
||||
import org.eclipse.jdt.core.dom.Modifier;
|
||||
import org.eclipse.jdt.core.dom.ModuleDeclaration;
|
||||
import org.eclipse.jdt.core.dom.ModulePackageAccess;
|
||||
import org.eclipse.jdt.core.dom.Name;
|
||||
import org.eclipse.jdt.core.dom.NameQualifiedType;
|
||||
import org.eclipse.jdt.core.dom.OpensDirective;
|
||||
import org.eclipse.jdt.core.dom.PackageDeclaration;
|
||||
import org.eclipse.jdt.core.dom.ParameterizedType;
|
||||
import org.eclipse.jdt.core.dom.QualifiedName;
|
||||
import org.eclipse.jdt.core.dom.QualifiedType;
|
||||
import org.eclipse.jdt.core.dom.RecordDeclaration;
|
||||
import org.eclipse.jdt.core.dom.RequiresDirective;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.eclipse.jdt.core.dom.SimpleType;
|
||||
import org.eclipse.jdt.core.dom.TagElement;
|
||||
import org.eclipse.jdt.core.dom.Type;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.jdt.core.dom.TypeLiteral;
|
||||
import org.eclipse.jdt.internal.core.dom.util.DOMASTUtil;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
|
||||
import org.springframework.ide.vscode.commons.util.Collector;
|
||||
|
||||
public class SemanticTokensVisitor extends ASTVisitor {
|
||||
private CompilationUnit cu;
|
||||
private final IScanner scanner;
|
||||
private Collector<SemanticTokenData> collector;
|
||||
|
||||
public SemanticTokensVisitor(CompilationUnit cu, Collector<SemanticTokenData> collector) {
|
||||
super(true);
|
||||
this.cu = cu;
|
||||
this.scanner = createScanner(cu);
|
||||
this.collector = collector;
|
||||
}
|
||||
|
||||
private String[] unpackModifiers(int modifiers) {
|
||||
List<String> modifierStrings = new ArrayList<>();
|
||||
for (TokenModifier m : TokenModifier.values()) {
|
||||
if ((modifiers & m.bitmask) != 0) {
|
||||
modifierStrings.add(m.toString());
|
||||
}
|
||||
}
|
||||
return modifierStrings.toArray(new String[modifierStrings.size()]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* "Static" modifiers which are always added by {@link #addToken(int, int, TokenType, int)}.
|
||||
* Modifiers can be set or removed at any time during the visitation process, and as such
|
||||
* will be applied for all nodes visited during the time a static modifier is set.
|
||||
*
|
||||
* Setting and removing the modifiers is done via bitwise OR/AND, see
|
||||
* {@link TokenModifier#bitmask} and {@link TokenModifier#inverseBitmask}
|
||||
*/
|
||||
private int staticModifiers = 0;
|
||||
|
||||
/**
|
||||
* Adds a semantic token to the list of tokens being collected by this
|
||||
* semantic token visitor.
|
||||
*
|
||||
* @param offset The document offset of the semantic token.
|
||||
* @param length The length of the semantic token.
|
||||
* @param tokenType The type of the semantic token.
|
||||
* @param modifiers The bitwise OR of the semantic token modifiers, see {@link TokenModifier#bitmask}.
|
||||
*
|
||||
* @apiNote This method is order-dependent because of {@link #encodedTokens()}.
|
||||
* If semantic tokens are not added in the order they appear in the document,
|
||||
* the encoding algorithm might discard them.
|
||||
*/
|
||||
private void addToken(int offset, int length, TokenType tokenType, int modifiers) {
|
||||
collector.accept(new SemanticTokenData(offset, offset + length, tokenType.toString(), unpackModifiers(modifiers | staticModifiers)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a semantic token to the list of tokens being collected by this
|
||||
* semantic token visitor.
|
||||
*
|
||||
* @param node The AST node representing the location of the semantic token.
|
||||
* @param tokenType The type of the semantic token.
|
||||
* @param modifiers The bitwise OR of the semantic token modifiers, see {@link TokenModifier#bitmask}.
|
||||
*
|
||||
* @apiNote This method is order-dependent because of {@link #encodedTokens()}.
|
||||
* If semantic tokens are not added in the order they appear in the document,
|
||||
* the encoding algorithm might discard them.
|
||||
*/
|
||||
private void addToken(ASTNode node, TokenType tokenType, int modifiers) {
|
||||
addToken(node.getStartPosition(), node.getLength(), tokenType, modifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a semantic token to the list of tokens being collected by this
|
||||
* semantic token visitor.
|
||||
*
|
||||
* @param node The AST node representing the location of the semantic token.
|
||||
* @param tokenType The type of the semantic token.
|
||||
*
|
||||
* @apiNote This method is order-dependent because of {@link #encodedTokens()}.
|
||||
* If semantic tokens are not added in the order they appear in the document,
|
||||
* the encoding algorithm might discard them.
|
||||
*/
|
||||
private void addToken(ASTNode node, TokenType tokenType) {
|
||||
addToken(node, tokenType, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(TypeLiteral node) {
|
||||
acceptNode(node.getType());
|
||||
// Don't add "class" keyword token for recovered type literals
|
||||
// The AST doesn't always contain the correct information,
|
||||
// so we need to verfify that the literal is 6 characters longer
|
||||
// than the type itself (corresponding to the ".class" characters).
|
||||
// https://github.com/eclipse/eclipse.jdt.ls/issues/1922
|
||||
if (node.getLength() == node.getType().getLength() + 6) {
|
||||
// The last 5 characters of a TypeLiteral are the "class" keyword
|
||||
int offset = node.getStartPosition() + node.getLength() - 5;
|
||||
addToken(offset, 5, TokenType.KEYWORD, 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(Javadoc node) {
|
||||
staticModifiers |= TokenModifier.DOCUMENTATION.bitmask;
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endVisit(Javadoc node) {
|
||||
staticModifiers &= TokenModifier.DOCUMENTATION.inverseBitmask;
|
||||
super.endVisit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(TagElement node) {
|
||||
if (node.getTagName() != null) {
|
||||
// If the token is nested, we need to skip the { character
|
||||
int offset = node.isNested() ? node.getStartPosition() + 1 : node.getStartPosition();
|
||||
addToken(offset, node.getTagName().length(), TokenType.KEYWORD, 0);
|
||||
}
|
||||
acceptNodeList(node.fragments());
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(PackageDeclaration node) {
|
||||
acceptNode(node.getJavadoc());
|
||||
acceptNodeList(node.annotations());
|
||||
addTokenToSimpleNamesOfName(node.getName(), TokenType.NAMESPACE);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(ImportDeclaration node) {
|
||||
staticModifiers |= TokenModifier.IMPORT_DECLARATION.bitmask;
|
||||
|
||||
IBinding binding = node.resolveBinding();
|
||||
if (binding == null || binding instanceof IPackageBinding) {
|
||||
addTokenToSimpleNamesOfName(node.getName(), TokenType.NAMESPACE);
|
||||
}
|
||||
else {
|
||||
nonPackageNameOfImportDeclarationVisitor(node.getName());
|
||||
}
|
||||
|
||||
staticModifiers &= TokenModifier.IMPORT_DECLARATION.inverseBitmask;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link NodeVisitor} for {@link Name} nodes inside an import declaration that are not package names.
|
||||
*
|
||||
* @param nonPackageName
|
||||
*/
|
||||
private void nonPackageNameOfImportDeclarationVisitor(Name nonPackageName) {
|
||||
if (nonPackageName instanceof SimpleName) {
|
||||
nonPackageName.accept(this);
|
||||
}
|
||||
else {
|
||||
QualifiedName qualifiedName = (QualifiedName) nonPackageName;
|
||||
Name qualifier = qualifiedName.getQualifier();
|
||||
|
||||
if (hasPackageQualifier(qualifiedName)) {
|
||||
addTokenToSimpleNamesOfName(qualifier, TokenType.NAMESPACE);
|
||||
}
|
||||
else {
|
||||
nonPackageNameOfImportDeclarationVisitor(qualifier);
|
||||
}
|
||||
|
||||
qualifiedName.getName().accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a qualified name has a package name as its qualifier,
|
||||
* even if the qualifier's binding cannot be resolved.
|
||||
* This is in order to work around an issue where package names in import statements
|
||||
* cannot have their bindings resolved when their corresponding package is not explicitely
|
||||
* exported and is part of a module.
|
||||
*
|
||||
* @param qualifiedName A qualified name.
|
||||
* @return {@code true} if the qualified name has a package name as its qualifier,
|
||||
* {@code false} otherwise.
|
||||
*/
|
||||
private boolean hasPackageQualifier(QualifiedName qualifiedName) {
|
||||
IBinding qualifierBinding = qualifiedName.getQualifier().resolveBinding();
|
||||
if (qualifierBinding != null) {
|
||||
return qualifierBinding instanceof IPackageBinding;
|
||||
}
|
||||
else {
|
||||
IBinding binding = qualifiedName.resolveBinding();
|
||||
return binding instanceof IPackageBinding || binding instanceof ITypeBinding;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(Modifier node) {
|
||||
addToken(node, TokenType.MODIFIER);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(SimpleName node) {
|
||||
IBinding binding = node.resolveBinding();
|
||||
TokenType tokenType = TokenType.getApplicableType(binding);
|
||||
if (tokenType != null) {
|
||||
int modifiers
|
||||
= TokenModifier.checkJavaModifiers(binding)
|
||||
| TokenModifier.checkConstructor(binding)
|
||||
| TokenModifier.checkGeneric(binding)
|
||||
| TokenModifier.checkDeclaration(node);
|
||||
addToken(node, tokenType, modifiers);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(ModuleDeclaration node) {
|
||||
acceptJavdocOfModuleDeclaration(node);
|
||||
acceptNodeList(node.annotations());
|
||||
addTokenToSimpleNamesOfName(node.getName(), TokenType.NAMESPACE);
|
||||
acceptNodeList(node.moduleStatements());
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts into the Javadoc of a module declaration.
|
||||
* This method attempts to work around an issue where {@link ModuleDeclaration#getJavadoc()}
|
||||
* always returns {@code null}, even if there is an associated Javadoc comment. This is done by
|
||||
* retrieving the Javadoc from {@link CompilationUnit#getCommentList()} instead.
|
||||
* This method should be removed once the compiler supports resolving the Javadoc
|
||||
* for module declarations.
|
||||
*
|
||||
* @param moduleDeclaration The module declaration.
|
||||
*/
|
||||
private void acceptJavdocOfModuleDeclaration(ModuleDeclaration moduleDeclaration) {
|
||||
for (Comment comment : this.<Comment>castNodeList(cu.getCommentList())) {
|
||||
// The start position of the module declaration still includes the Javadoc,
|
||||
// so we just need to look for a comment with the same start position.
|
||||
if (comment.getStartPosition() == moduleDeclaration.getStartPosition()) {
|
||||
comment.accept(this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(RequiresDirective node) {
|
||||
addTokenToSimpleNamesOfName(node.getName(), TokenType.NAMESPACE);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(ExportsDirective node) {
|
||||
modulePackageAccessVisitor(node);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(OpensDirective node) {
|
||||
modulePackageAccessVisitor(node);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link NodeVisitor} for {@link ModulePackageAccess} nodes.
|
||||
*
|
||||
* @param modulePackageAccess
|
||||
*/
|
||||
private void modulePackageAccessVisitor(ModulePackageAccess modulePackageAccess) {
|
||||
acceptNode(modulePackageAccess.getName());
|
||||
this.<Name>visitNodeList(modulePackageAccess.modules(), module -> {
|
||||
addTokenToSimpleNamesOfName(module, TokenType.NAMESPACE);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(ParameterizedType node) {
|
||||
acceptNode(node.getType());
|
||||
visitNodeList(node.typeArguments(), this::typeArgumentVisitor);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(MethodInvocation node) {
|
||||
acceptNode(node.getExpression());
|
||||
visitNodeList(node.typeArguments(), this::typeArgumentVisitor);
|
||||
acceptNode(node.getName());
|
||||
acceptNodeList(node.arguments());
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link NodeVisitor} for {@link Type} nodes that are type arguments.
|
||||
*
|
||||
* @param typeArgument
|
||||
*/
|
||||
private void typeArgumentVisitor(Type typeArgument) {
|
||||
visitSimpleNameOfType(typeArgument, simpleName -> {
|
||||
IBinding binding = simpleName.resolveBinding();
|
||||
TokenType tokenType = TokenType.getApplicableType(binding);
|
||||
if (tokenType != null) {
|
||||
int modifiers
|
||||
= TokenModifier.checkJavaModifiers(binding)
|
||||
| TokenModifier.checkGeneric(binding)
|
||||
| TokenModifier.TYPE_ARGUMENT.bitmask;
|
||||
addToken(simpleName, tokenType, modifiers);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassInstanceCreation node) {
|
||||
acceptNode(node.getExpression());
|
||||
visitNodeList(node.typeArguments(), this::typeArgumentVisitor);
|
||||
|
||||
visitSimpleNameOfType(node.getType(), simpleName -> {
|
||||
// Figure out the token type based on the constructed type.
|
||||
TokenType tokenType = TokenType.getApplicableType(node.resolveTypeBinding());
|
||||
if (tokenType != null) {
|
||||
// Figure out the token modifiers based on the constructor method.
|
||||
// For example, the type could be public, whereas a specific
|
||||
// constructor may be private.
|
||||
IMethodBinding constructorBinding = node.resolveConstructorBinding();
|
||||
int modifiers
|
||||
= TokenModifier.checkJavaModifiers(constructorBinding)
|
||||
| TokenModifier.checkGeneric(constructorBinding)
|
||||
| TokenModifier.CONSTRUCTOR.bitmask;
|
||||
addToken(simpleName, tokenType, modifiers);
|
||||
}
|
||||
});
|
||||
|
||||
acceptNodeList(node.arguments());
|
||||
acceptNode(node.getAnonymousClassDeclaration());
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(RecordDeclaration node) {
|
||||
acceptNode(node.getJavadoc());
|
||||
acceptNodeList(node.modifiers());
|
||||
// Adds token for 'record' keyword. Token type 'MODIFIER' is used to provide the correct highlight colour.
|
||||
addToken(node.getRestrictedIdentifierStartPosition(), 6, TokenType.MODIFIER, 0);
|
||||
acceptNode(node.getName());
|
||||
acceptNodeList(node.typeParameters());
|
||||
acceptNodeList(node.recordComponents());
|
||||
acceptNodeList(node.superInterfaceTypes());
|
||||
acceptNodeList(node.bodyDeclarations());
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(TypeDeclaration node) {
|
||||
acceptNode(node.getJavadoc());
|
||||
acceptNodeList(node.modifiers());
|
||||
tokenizeGapBeforeAndAfterTypeDeclarationName(node, (scannerToken, tokenOffset, tokenLength) -> {
|
||||
switch (scannerToken) {
|
||||
case ITerminalSymbols.TokenNameclass:
|
||||
case ITerminalSymbols.TokenNameinterface:
|
||||
addToken(tokenOffset, tokenLength, TokenType.MODIFIER, 0);
|
||||
acceptNode(node.getName());
|
||||
acceptNodeList(node.typeParameters());
|
||||
break; // "class" or "interface" keyword tokens
|
||||
case ITerminalSymbols.TokenNameextends:
|
||||
addToken(tokenOffset, tokenLength, TokenType.MODIFIER, 0);
|
||||
acceptNode(node.getSuperclassType());
|
||||
break; // "extends" keyword token
|
||||
case ITerminalSymbols.TokenNameimplements:
|
||||
addToken(tokenOffset, tokenLength, TokenType.MODIFIER, 0);
|
||||
acceptNodeList(node.superInterfaceTypes());
|
||||
break; // "implements" keyword token
|
||||
default:
|
||||
break; // ignore other tokens
|
||||
}
|
||||
});
|
||||
if (DOMASTUtil.isFeatureSupportedinAST(cu.getAST(), Modifier.SEALED)) {
|
||||
if (node.getRestrictedIdentifierStartPosition() != -1) {
|
||||
addToken(node.getRestrictedIdentifierStartPosition(), 7, TokenType.MODIFIER, 0);
|
||||
}
|
||||
acceptNodeList(node.permittedTypes());
|
||||
}
|
||||
acceptNodeList(node.bodyDeclarations());
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to create an {@link IScanner} for the source of the given compilation unit.
|
||||
*
|
||||
* @param cu the compilation unit
|
||||
* @return the scanner, or {@code null} if not available
|
||||
*/
|
||||
private IScanner createScanner(CompilationUnit cu) {
|
||||
final ITypeRoot typeRoot = cu.getTypeRoot();
|
||||
if (typeRoot == null) {
|
||||
return null;
|
||||
}
|
||||
final IJavaProject javaProject = typeRoot.getJavaProject();
|
||||
if (javaProject == null) {
|
||||
return null;
|
||||
}
|
||||
final String source;
|
||||
try {
|
||||
source = typeRoot.getSource();
|
||||
} catch (JavaModelException e) {
|
||||
return null;
|
||||
}
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String sourceLevel = javaProject.getOption(JavaCore.COMPILER_SOURCE, true);
|
||||
final String complianceLevel = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true);
|
||||
final boolean enablePreview = JavaCore.ENABLED.equals(javaProject.getOption(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, true));
|
||||
|
||||
final IScanner scanner = ToolFactory.createScanner(false, false, false, sourceLevel, complianceLevel, enablePreview);
|
||||
scanner.setSource(source.toCharArray());
|
||||
return scanner;
|
||||
}
|
||||
|
||||
private int getNextValidToken(IScanner scanner) {
|
||||
while (true) {
|
||||
try {
|
||||
return scanner.getNextToken();
|
||||
} catch (InvalidInputException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visitor for {@link IScanner} tokens.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
private interface ScannerTokenVisitor {
|
||||
/**
|
||||
* Visits the given scanner token.
|
||||
*
|
||||
* @param scannerToken the scanner token ID, see {@link ITerminalSymbols}
|
||||
* @param tokenOffset the document offset of the scanner token
|
||||
* @param tokenLength the length of the scanner token
|
||||
*/
|
||||
public void visit(int scannerToken, int tokenOffset, int tokenLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses an {@link IScanner} (if available) to tokenize a source range in the document,
|
||||
* and visits the scanner tokens in order of occurrence in the source range.
|
||||
*
|
||||
* <p>
|
||||
* <strong>NOTE:</strong> If semantic tokens are added by the visitor, the scan range MUST NOT intersect
|
||||
* with any other range where semantic tokens can appear, in order to avoid overlapping semantic tokens.
|
||||
* </p>
|
||||
*
|
||||
* @param startPosition the (inclusive) start position of the scan range
|
||||
* @param endPosition the (exclusive) end position of the scan range
|
||||
* @param tokenVisitor the visitor to use for scanner tokens
|
||||
*/
|
||||
private void tokenizeWithScanner(int startPosition, int endPosition, ScannerTokenVisitor tokenVisitor) {
|
||||
if (scanner == null) {
|
||||
return;
|
||||
}
|
||||
scanner.resetTo(startPosition, endPosition - 1); // -1 because resetTo wants inclusive endPosition
|
||||
for (int token = getNextValidToken(scanner); token != ITerminalSymbols.TokenNameEOF; token = getNextValidToken(scanner)) {
|
||||
int tokenOffset = scanner.getCurrentTokenStartPosition();
|
||||
int tokenLength = scanner.getCurrentTokenEndPosition() - tokenOffset + 1; // +1 because getCurrentTokenEndPosition is inclusive
|
||||
tokenVisitor.visit(token, tokenOffset, tokenLength);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses an {@link IScanner} (if available) to tokenize the gap in the AST just before and after {@link TypeDeclaration#getName()},
|
||||
* and visits the scanner tokens in order of occurrence in the source range. For example, the following would be tokenized as seen between the square brackets:
|
||||
* <br><br>
|
||||
* <code>public[ class FooBar extends Foo implements ]Bar</code>
|
||||
*
|
||||
* <p>
|
||||
* <strong>NOTE:</strong> If semantic tokens are added by the visitor, the scan range MUST NOT intersect
|
||||
* with any other range where semantic tokens can appear, in order to avoid overlapping semantic tokens.
|
||||
* </p>
|
||||
*
|
||||
* @param typeDeclaration the type declaration node
|
||||
* @param tokenVisitor the visitor to use for scanner tokens
|
||||
*/
|
||||
private void tokenizeGapBeforeAndAfterTypeDeclarationName(TypeDeclaration typeDeclaration, ScannerTokenVisitor tokenVisitor) {
|
||||
// Try potentially nonexistent start positions, closest first
|
||||
int gapBeforeNameStart = getEndPosition(typeDeclaration.modifiers());
|
||||
if (gapBeforeNameStart == -1) {
|
||||
gapBeforeNameStart = getEndPosition(typeDeclaration.getJavadoc());
|
||||
}
|
||||
// Fallback to closest known start position
|
||||
if (gapBeforeNameStart == -1) {
|
||||
gapBeforeNameStart = typeDeclaration.getStartPosition();
|
||||
}
|
||||
// Try potentially nonexistent end positions, farthest first
|
||||
int gapBeforeNameEnd = !typeDeclaration.superInterfaceTypes().isEmpty() ? ((ASTNode) (typeDeclaration.superInterfaceTypes().get(0))).getStartPosition() : -1;
|
||||
if (gapBeforeNameEnd == -1) {
|
||||
gapBeforeNameEnd = typeDeclaration.getSuperclassType() != null ? typeDeclaration.getSuperclassType().getStartPosition() : -1;
|
||||
}
|
||||
if (gapBeforeNameEnd == -1) {
|
||||
gapBeforeNameEnd = typeDeclaration.getName().getStartPosition();
|
||||
}
|
||||
tokenizeWithScanner(gapBeforeNameStart, gapBeforeNameEnd, tokenVisitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* A node visitor may be used by helpers like {@link #visitSimpleNameOfType(Name, NodeVisitor)},
|
||||
* and is responsible for the visitation logic of a special kind of node.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
private interface NodeVisitor<T extends ASTNode> {
|
||||
public void visit(T node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to make an unchecked cast from a raw list of AST nodes
|
||||
* to a generic list of a certain type. The caller is responsible for
|
||||
* making sure that the cast is safe. An example use case is for
|
||||
* {@link CompilationUnit#getCommentList()}, which returns a raw list,
|
||||
* but whose Javadoc states that the element type is {@link Comment}.
|
||||
*
|
||||
* @param <T> The type of {@link ASTNode} expected in the node list.
|
||||
* @param nodeList The node list to be cast.
|
||||
* @return The cast node list.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends ASTNode> List<T> castNodeList(List<?> nodeList) {
|
||||
return (List<T>) nodeList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method which visits the nodes in a raw list of AST nodes
|
||||
* using a {@link NodeVisitor}. An unchecked cast is made via
|
||||
* {@link #castNodeList(List)}, so the caller is responsible for
|
||||
* making sure that the cast is safe.
|
||||
*
|
||||
* @param <T> The type of {@link ASTNode} expected in the node list.
|
||||
* @param nodeList The node list to visit (may be {@code null}).
|
||||
* @param visitor A {@link NodeVisitor}.
|
||||
*/
|
||||
private <T extends ASTNode> void visitNodeList(List<?> nodeList, NodeVisitor<T> visitor) {
|
||||
if (nodeList != null) {
|
||||
for (T node : this.<T>castNodeList(nodeList)) {
|
||||
visitor.visit(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method which accepts into the nodes in a raw list of AST nodes.
|
||||
* An unchecked cast is made via {@link #castNodeList(List)},
|
||||
* so the caller is responsible for making sure that the cast is safe.
|
||||
*
|
||||
* @param nodeList The node list to accept into (may be {@code null}).
|
||||
*/
|
||||
private void acceptNodeList(List<?> nodeList) {
|
||||
if (nodeList != null) {
|
||||
for (ASTNode node : this.<ASTNode>castNodeList(nodeList)) {
|
||||
node.accept(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method which performs a null-check on and then accepts into an AST node.
|
||||
*
|
||||
* @param node The AST node to accept into (may be {@code null}).
|
||||
*/
|
||||
private void acceptNode(ASTNode node) {
|
||||
if (node != null) {
|
||||
node.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (exclusive) document end position of the given list of AST nodes.
|
||||
*
|
||||
* <p>
|
||||
* The caller is responsible for making sure that the element type
|
||||
* of the given list is {@link ASTNode}.
|
||||
* </p>
|
||||
*
|
||||
* @param nodeList the list of AST nodes (may be {@code null})
|
||||
* @return the end position, or {@code -1} if unknown
|
||||
*/
|
||||
private int getEndPosition(List<?> nodeList) {
|
||||
if (nodeList != null && !nodeList.isEmpty()) {
|
||||
ASTNode lastNode = (ASTNode) nodeList.get(nodeList.size() - 1);
|
||||
return lastNode.getStartPosition() + lastNode.getLength();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (exclusive) document end position of the given AST node.
|
||||
*
|
||||
* @param node the AST node (may be {@code null})
|
||||
* @return the end position, or {@code -1} if unknown
|
||||
*/
|
||||
private int getEndPosition(ASTNode node) {
|
||||
if (node != null) {
|
||||
return node.getStartPosition() + node.getLength();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to recursively visit all the simple names of a {@link Name} node
|
||||
* using the specified {@link NodeVisitor}.
|
||||
*
|
||||
* @param name A {@link Name} node (may be {@code null}).
|
||||
* @param visitor The {@link NodeVisitor} to use.
|
||||
*/
|
||||
private void visitSimpleNamesOfName(Name name, NodeVisitor<SimpleName> visitor) {
|
||||
if (name == null) {
|
||||
return;
|
||||
}
|
||||
if (name instanceof SimpleName simpleName) {
|
||||
visitor.visit(simpleName);
|
||||
} else if (name instanceof QualifiedName qualifiedName) {
|
||||
visitSimpleNamesOfName(qualifiedName.getQualifier(), visitor);
|
||||
visitor.visit(qualifiedName.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to recursively add a token of the specified type to
|
||||
* all the simple names of a name.
|
||||
*
|
||||
* @param name A {@link Name} node (may be {@code null}).
|
||||
* @param tokenType The {@link TokenType} to add for all the simple names.
|
||||
*/
|
||||
private void addTokenToSimpleNamesOfName(Name name, TokenType tokenType) {
|
||||
visitSimpleNamesOfName(name, simpleName -> addToken(simpleName, tokenType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to find and visit the simple name of a {@code Type} node
|
||||
* using the specified {@link NodeVisitor}.
|
||||
*
|
||||
* @param type A type node (may be {@code null}).
|
||||
* @param visitor The {@link NodeVisitor} to use.
|
||||
*/
|
||||
private void visitSimpleNameOfType(Type type, NodeVisitor<SimpleName> visitor) {
|
||||
if (type == null) {
|
||||
return;
|
||||
}
|
||||
else if (type instanceof SimpleType simpleType) {
|
||||
acceptNodeList(simpleType.annotations());
|
||||
|
||||
Name simpleTypeName = simpleType.getName();
|
||||
if (simpleTypeName instanceof SimpleName simpleName) {
|
||||
visitor.visit(simpleName);
|
||||
} else if (simpleTypeName instanceof QualifiedName qualifiedName) {
|
||||
qualifiedName.getQualifier().accept(this);
|
||||
visitor.visit(qualifiedName.getName());
|
||||
}
|
||||
}
|
||||
else if (type instanceof QualifiedType qualifiedType) {
|
||||
qualifiedType.getQualifier().accept(this);
|
||||
acceptNodeList(qualifiedType.annotations());
|
||||
visitor.visit(qualifiedType.getName());
|
||||
}
|
||||
else if (type instanceof NameQualifiedType nameQualifiedType) {
|
||||
nameQualifiedType.getQualifier().accept(this);
|
||||
acceptNodeList(nameQualifiedType.annotations());
|
||||
visitor.visit(nameQualifiedType.getName());
|
||||
}
|
||||
else if (type instanceof ParameterizedType parameterizedType) {
|
||||
visitSimpleNameOfType(parameterizedType.getType(), visitor);
|
||||
visitNodeList(parameterizedType.typeArguments(), this::typeArgumentVisitor);
|
||||
}
|
||||
else {
|
||||
type.accept(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom, Inc.
|
||||
* 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
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
// Copied from JDT LS to workaround https://github.com/spring-projects/sts4/issues/1249
|
||||
package org.springframework.ide.vscode.boot.java.semantictokens;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
|
||||
import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration;
|
||||
import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
|
||||
import org.eclipse.jdt.core.dom.EnumDeclaration;
|
||||
import org.eclipse.jdt.core.dom.IBinding;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.Modifier;
|
||||
import org.eclipse.jdt.core.dom.RecordDeclaration;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
|
||||
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.jdt.core.dom.TypeParameter;
|
||||
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
|
||||
import org.eclipse.lsp4j.SemanticTokenModifiers;
|
||||
|
||||
public enum TokenModifier {
|
||||
// Standard LSP token modifiers, see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens
|
||||
ABSTRACT(SemanticTokenModifiers.Abstract),
|
||||
STATIC(SemanticTokenModifiers.Static),
|
||||
FINAL(SemanticTokenModifiers.Readonly),
|
||||
DEPRECATED(SemanticTokenModifiers.Deprecated),
|
||||
DECLARATION(SemanticTokenModifiers.Declaration),
|
||||
DOCUMENTATION(SemanticTokenModifiers.Documentation),
|
||||
|
||||
// Custom token modifiers
|
||||
PUBLIC("public"),
|
||||
PRIVATE("private"),
|
||||
PROTECTED("protected"),
|
||||
NATIVE("native"),
|
||||
GENERIC("generic"),
|
||||
TYPE_ARGUMENT("typeArgument"),
|
||||
IMPORT_DECLARATION("importDeclaration"),
|
||||
CONSTRUCTOR("constructor");
|
||||
|
||||
/**
|
||||
* This is the name of the token modifier given to the client, so it
|
||||
* should be as generic as possible and follow the standard LSP (see below)
|
||||
* token modifier names where applicable. For example, the generic name of the
|
||||
* {@link #FINAL} modifier is "readonly", since it has similar meaning.
|
||||
* Using standardized names makes life easier for theme authors, since
|
||||
* they don't need to know about language-specific terminology.
|
||||
*
|
||||
* @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens
|
||||
*/
|
||||
private final String genericName;
|
||||
|
||||
/**
|
||||
* The bitmask for this semantic token modifier.
|
||||
* Use bitwise OR to combine with other token modifiers.
|
||||
*/
|
||||
public final int bitmask = 1 << ordinal();
|
||||
|
||||
/**
|
||||
* The inverse bitmask for this semantic token modifier.
|
||||
* Use bitwise AND to remove from other token modifiers.
|
||||
*/
|
||||
public final int inverseBitmask = ~bitmask;
|
||||
|
||||
TokenModifier(String genericName) {
|
||||
this.genericName = genericName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return genericName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bitwise OR of all the semantic token modifiers that apply
|
||||
* based on the binding's {@link Modifier}s and wheter or not it is deprecated.
|
||||
*
|
||||
* @param binding A binding.
|
||||
* @return The bitwise OR of the applicable modifiers for the binding.
|
||||
*/
|
||||
public static int checkJavaModifiers(IBinding binding) {
|
||||
if (binding == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int modifiers = 0;
|
||||
int bindingModifiers = binding.getModifiers();
|
||||
if (Modifier.isPublic(bindingModifiers)) {
|
||||
modifiers |= PUBLIC.bitmask;
|
||||
}
|
||||
if (Modifier.isPrivate(bindingModifiers)) {
|
||||
modifiers |= PRIVATE.bitmask;
|
||||
}
|
||||
if (Modifier.isProtected(bindingModifiers)) {
|
||||
modifiers |= PROTECTED.bitmask;
|
||||
}
|
||||
if (Modifier.isAbstract(bindingModifiers)) {
|
||||
modifiers |= ABSTRACT.bitmask;
|
||||
}
|
||||
if (Modifier.isStatic(bindingModifiers)) {
|
||||
modifiers |= STATIC.bitmask;
|
||||
}
|
||||
if (Modifier.isFinal(bindingModifiers)) {
|
||||
modifiers |= FINAL.bitmask;
|
||||
}
|
||||
if (Modifier.isNative(bindingModifiers)) {
|
||||
modifiers |= NATIVE.bitmask;
|
||||
}
|
||||
if (binding.isDeprecated()) {
|
||||
modifiers |= DEPRECATED.bitmask;
|
||||
}
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a binding represents a constructor.
|
||||
*
|
||||
* @param binding A binding.
|
||||
* @return A bitmask with the {@link #CONSTRUCTOR} bit set accordingly.
|
||||
*/
|
||||
public static int checkConstructor(IBinding binding) {
|
||||
if (binding instanceof IMethodBinding methodBinding && methodBinding.isConstructor()) {
|
||||
return CONSTRUCTOR.bitmask;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a binding represents a generic type or method.
|
||||
*
|
||||
* @param binding A binding.
|
||||
* @return A bitmask with the {@link #GENERIC} bit set accordingly.
|
||||
*/
|
||||
public static int checkGeneric(IBinding binding) {
|
||||
if (binding instanceof ITypeBinding typeBinding) {
|
||||
if (typeBinding.isGenericType() || typeBinding.isParameterizedType()) {
|
||||
return GENERIC.bitmask;
|
||||
}
|
||||
}
|
||||
if (binding instanceof IMethodBinding methodBinding) {
|
||||
if (methodBinding.isGenericMethod() || methodBinding.isParameterizedMethod()) {
|
||||
return GENERIC.bitmask;
|
||||
}
|
||||
return checkGeneric(methodBinding.getDeclaringClass());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a binding represents a declaration.
|
||||
*
|
||||
* @param binding A binding.
|
||||
* @return A bitmask with the {@link #DECLARATION} bit set accordingly.
|
||||
*/
|
||||
public static int checkDeclaration(SimpleName simpleName) {
|
||||
if (isDeclaration(simpleName)) {
|
||||
return DECLARATION.bitmask;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a simple name represents a name that is being defined,
|
||||
* as opposed to one being referenced.
|
||||
*
|
||||
* @param simpleName A simple name.
|
||||
* @return {@code true} if this node declares a name, and {@code false} otherwise.
|
||||
*/
|
||||
private static boolean isDeclaration(SimpleName simpleName) {
|
||||
StructuralPropertyDescriptor d = simpleName.getLocationInParent();
|
||||
if (d == null) {
|
||||
return false;
|
||||
}
|
||||
ASTNode parent = simpleName.getParent();
|
||||
if (parent instanceof TypeDeclaration) {
|
||||
return (d == TypeDeclaration.NAME_PROPERTY);
|
||||
}
|
||||
if (parent instanceof MethodDeclaration) {
|
||||
return (d == MethodDeclaration.NAME_PROPERTY);
|
||||
}
|
||||
if (parent instanceof SingleVariableDeclaration) {
|
||||
return (d == SingleVariableDeclaration.NAME_PROPERTY);
|
||||
}
|
||||
if (parent instanceof VariableDeclarationFragment) {
|
||||
return (d == VariableDeclarationFragment.NAME_PROPERTY);
|
||||
}
|
||||
if (parent instanceof EnumDeclaration) {
|
||||
return (d == EnumDeclaration.NAME_PROPERTY);
|
||||
}
|
||||
if (parent instanceof EnumConstantDeclaration) {
|
||||
return (d == EnumConstantDeclaration.NAME_PROPERTY);
|
||||
}
|
||||
if (parent instanceof TypeParameter) {
|
||||
return (d == TypeParameter.NAME_PROPERTY);
|
||||
}
|
||||
if (parent instanceof AnnotationTypeDeclaration) {
|
||||
return (d == AnnotationTypeDeclaration.NAME_PROPERTY);
|
||||
}
|
||||
if (parent instanceof AnnotationTypeMemberDeclaration) {
|
||||
return (d == AnnotationTypeMemberDeclaration.NAME_PROPERTY);
|
||||
}
|
||||
if (parent instanceof RecordDeclaration) {
|
||||
return (d == RecordDeclaration.NAME_PROPERTY);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom, Inc.
|
||||
* 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
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
// Copied from JDT LS to workaround https://github.com/spring-projects/sts4/issues/1249
|
||||
package org.springframework.ide.vscode.boot.java.semantictokens;
|
||||
|
||||
import org.eclipse.jdt.core.dom.IBinding;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.IVariableBinding;
|
||||
import org.eclipse.lsp4j.SemanticTokenTypes;
|
||||
|
||||
public enum TokenType {
|
||||
// Standard LSP token types, see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens
|
||||
NAMESPACE(SemanticTokenTypes.Namespace),
|
||||
CLASS(SemanticTokenTypes.Class),
|
||||
INTERFACE(SemanticTokenTypes.Interface),
|
||||
ENUM(SemanticTokenTypes.Enum),
|
||||
ENUM_MEMBER(SemanticTokenTypes.EnumMember),
|
||||
TYPE(SemanticTokenTypes.Type),
|
||||
TYPE_PARAMETER(SemanticTokenTypes.TypeParameter),
|
||||
METHOD(SemanticTokenTypes.Method),
|
||||
PROPERTY(SemanticTokenTypes.Property),
|
||||
VARIABLE(SemanticTokenTypes.Variable),
|
||||
PARAMETER(SemanticTokenTypes.Parameter),
|
||||
MODIFIER(SemanticTokenTypes.Modifier),
|
||||
KEYWORD(SemanticTokenTypes.Keyword),
|
||||
|
||||
// Custom token types
|
||||
ANNOTATION("annotation"),
|
||||
ANNOTATION_MEMBER("annotationMember"),
|
||||
RECORD("record"),
|
||||
RECORD_COMPONENT("recordComponent");
|
||||
|
||||
|
||||
/**
|
||||
* This is the name of the token type given to the client, so it
|
||||
* should be as generic as possible and follow the standard LSP (see below)
|
||||
* token type names where applicable. For example, the generic name of the
|
||||
* {@link #PACKAGE} type is "namespace", since it has similar meaning.
|
||||
* Using standardized names makes life easier for theme authors, since
|
||||
* they don't need to know about language-specific terminology.
|
||||
*
|
||||
* @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens
|
||||
*/
|
||||
private String genericName;
|
||||
|
||||
TokenType(String genericName) {
|
||||
this.genericName = genericName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return genericName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the semantic token type that applies to a binding, or
|
||||
* {@code null} if there is no token type that applies to the binding.
|
||||
*
|
||||
* @param binding A binding.
|
||||
* @return The semantic token type that applies to the binding, or
|
||||
* {@code null} if there is no token type that applies to the binding.
|
||||
*/
|
||||
public static TokenType getApplicableType(IBinding binding) {
|
||||
if (binding == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (binding.getKind()) {
|
||||
case IBinding.VARIABLE: {
|
||||
IVariableBinding variableBinding = (IVariableBinding) binding;
|
||||
if (variableBinding.isEnumConstant()) {
|
||||
return TokenType.ENUM_MEMBER;
|
||||
}
|
||||
if (variableBinding.isRecordComponent()) {
|
||||
return TokenType.RECORD_COMPONENT;
|
||||
}
|
||||
if (variableBinding.isField()) {
|
||||
return TokenType.PROPERTY;
|
||||
}
|
||||
if (variableBinding.isParameter()) {
|
||||
return TokenType.PARAMETER;
|
||||
}
|
||||
return TokenType.VARIABLE;
|
||||
}
|
||||
case IBinding.METHOD: {
|
||||
IMethodBinding methodBinding = (IMethodBinding) binding;
|
||||
if (methodBinding.isConstructor()) {
|
||||
return getApplicableType(methodBinding.getDeclaringClass());
|
||||
}
|
||||
if (methodBinding.isAnnotationMember()) {
|
||||
return TokenType.ANNOTATION_MEMBER;
|
||||
}
|
||||
return TokenType.METHOD;
|
||||
}
|
||||
case IBinding.TYPE: {
|
||||
ITypeBinding typeBinding = (ITypeBinding) binding;
|
||||
if (typeBinding.isTypeVariable()) {
|
||||
return TokenType.TYPE_PARAMETER;
|
||||
}
|
||||
if (typeBinding.isAnnotation()) {
|
||||
return TokenType.ANNOTATION;
|
||||
}
|
||||
if (typeBinding.isRecord()) {
|
||||
return TokenType.RECORD;
|
||||
}
|
||||
if (typeBinding.isInterface()) {
|
||||
return TokenType.INTERFACE;
|
||||
}
|
||||
if (typeBinding.isEnum()) {
|
||||
return TokenType.ENUM;
|
||||
}
|
||||
if (typeBinding.isClass()) {
|
||||
return TokenType.CLASS;
|
||||
}
|
||||
return TokenType.TYPE;
|
||||
}
|
||||
case IBinding.PACKAGE:
|
||||
case IBinding.MODULE: {
|
||||
return TokenType.NAMESPACE;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,9 +23,11 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
|
||||
import org.springframework.ide.vscode.boot.bootiful.HoverTestConf;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.CompositeASTVisitor;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
|
||||
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
|
||||
import org.springframework.ide.vscode.commons.util.Collector;
|
||||
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
@@ -44,6 +46,14 @@ public class JdtDataQuerySemanticTokensProviderTest {
|
||||
public void setup() throws Exception {
|
||||
jp = projects.mavenProject("spring-modulith-example-full");
|
||||
}
|
||||
|
||||
private List<SemanticTokenData> computeTokens(CompilationUnit cu) {
|
||||
Collector<SemanticTokenData> collector = new Collector<>();
|
||||
CompositeASTVisitor visitor = new CompositeASTVisitor();
|
||||
visitor.add(provider.getTokensComputer(jp, cu, collector));
|
||||
cu.accept(visitor);
|
||||
return collector.get();
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleMemberAnnotation() throws Exception {
|
||||
@@ -64,7 +74,7 @@ public class JdtDataQuerySemanticTokensProviderTest {
|
||||
|
||||
assertThat(cu).isNotNull();
|
||||
|
||||
List<SemanticTokenData> tokens = provider.computeTokens(jp, cu);
|
||||
List<SemanticTokenData> tokens = computeTokens(cu);
|
||||
|
||||
SemanticTokenData token = tokens.get(0);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(120, 126, "keyword", new String[0]));
|
||||
@@ -112,7 +122,7 @@ public class JdtDataQuerySemanticTokensProviderTest {
|
||||
|
||||
assertThat(cu).isNotNull();
|
||||
|
||||
List<SemanticTokenData> tokens = provider.computeTokens(jp, cu);
|
||||
List<SemanticTokenData> tokens = computeTokens(cu);
|
||||
|
||||
SemanticTokenData token = tokens.get(0);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(125, 131, "keyword", new String[0]));
|
||||
@@ -158,7 +168,7 @@ public class JdtDataQuerySemanticTokensProviderTest {
|
||||
|
||||
assertThat(cu).isNotNull();
|
||||
|
||||
List<SemanticTokenData> tokens = provider.computeTokens(jp, cu);
|
||||
List<SemanticTokenData> tokens = computeTokens(cu);
|
||||
|
||||
SemanticTokenData token = tokens.get(0);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(128, 134, "keyword", new String[0]));
|
||||
@@ -205,7 +215,7 @@ public class JdtDataQuerySemanticTokensProviderTest {
|
||||
|
||||
assertThat(cu).isNotNull();
|
||||
|
||||
List<SemanticTokenData> tokens = provider.computeTokens(jp, cu);
|
||||
List<SemanticTokenData> tokens = computeTokens(cu);
|
||||
|
||||
SemanticTokenData token = tokens.get(0);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(176, 182, "keyword", new String[0]));
|
||||
@@ -254,7 +264,7 @@ public class JdtDataQuerySemanticTokensProviderTest {
|
||||
|
||||
assertThat(cu).isNotNull();
|
||||
|
||||
List<SemanticTokenData> tokens = provider.computeTokens(jp, cu);
|
||||
List<SemanticTokenData> tokens = computeTokens(cu);
|
||||
|
||||
SemanticTokenData token = tokens.get(0);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(181, 187, "keyword", new String[0]));
|
||||
@@ -300,7 +310,7 @@ public class JdtDataQuerySemanticTokensProviderTest {
|
||||
|
||||
assertThat(cu).isNotNull();
|
||||
|
||||
List<SemanticTokenData> tokens = provider.computeTokens(jp, cu);
|
||||
List<SemanticTokenData> tokens = computeTokens(cu);
|
||||
|
||||
assertThat(tokens.size()).isZero();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user