Temporarily bring JDT LS semantic tokens into Boot LS to avoid conflicts

This commit is contained in:
aboyko
2024-05-13 19:40:09 -04:00
parent 92eeec8e65
commit 6243feefff
18 changed files with 1380 additions and 153 deletions

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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) {

View File

@@ -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();
}
}