From ca21621f143d2002d6abe50dc38309d797e41d2c Mon Sep 17 00:00:00 2001 From: 0dinD Date: Thu, 24 Jun 2021 22:43:51 +0200 Subject: [PATCH] Move semantic tokens to LSP implementation Signed-off-by: 0dinD --- org.eclipse.jdt.ls.core/plugin.xml | 6 -- .../internal/JDTDelegateCommandHandler.java | 6 -- .../core/internal/handlers/InitHandler.java | 14 +++ .../internal/handlers/JDTLanguageServer.java | 17 ++- .../SemanticTokensHandler.java} | 41 ++++--- .../semantictokens/SemanticTokens.java | 100 ------------------ .../semantictokens/SemanticTokensLegend.java | 38 ------- .../semantictokens/SemanticTokensVisitor.java | 16 +-- .../semantictokens/TokenModifier.java | 13 +-- .../internal/semantictokens/TokenType.java | 27 ++--- .../syntaxserver/SyntaxInitHandler.java | 15 +++ .../syntaxserver/SyntaxLanguageServer.java | 12 +++ .../SemanticTokensHandlerTest.java} | 58 +++++----- 13 files changed, 140 insertions(+), 223 deletions(-) rename org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/{commands/SemanticTokensCommand.java => handlers/SemanticTokensHandler.java} (50%) delete mode 100644 org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/SemanticTokens.java delete mode 100644 org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/SemanticTokensLegend.java rename org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/{commands/SemanticTokensCommandTest.java => handlers/SemanticTokensHandlerTest.java} (92%) diff --git a/org.eclipse.jdt.ls.core/plugin.xml b/org.eclipse.jdt.ls.core/plugin.xml index f1d261c98e..f317c65570 100644 --- a/org.eclipse.jdt.ls.core/plugin.xml +++ b/org.eclipse.jdt.ls.core/plugin.xml @@ -88,12 +88,6 @@ - - - - diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java index fb1ec614f5..51be11c4cb 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java @@ -25,10 +25,8 @@ import org.eclipse.jdt.ls.core.internal.commands.ProjectCommand.ClasspathOptions; import org.eclipse.jdt.ls.core.internal.handlers.FormatterHandler; import org.eclipse.jdt.ls.core.internal.handlers.ResolveSourceMappingHandler; -import org.eclipse.jdt.ls.core.internal.commands.SemanticTokensCommand; import org.eclipse.jdt.ls.core.internal.commands.SourceAttachmentCommand; import org.eclipse.jdt.ls.core.internal.commands.TypeHierarchyCommand; -import org.eclipse.jdt.ls.core.internal.semantictokens.SemanticTokensLegend; import org.eclipse.lsp4j.ResolveTypeHierarchyItemParams; import org.eclipse.lsp4j.TextDocumentPositionParams; import org.eclipse.lsp4j.TypeHierarchyDirection; @@ -84,10 +82,6 @@ public Object executeCommand(String commandId, List arguments, IProgress return ProjectCommand.getAllJavaProjects(); case "java.project.refreshDiagnostics": return DiagnosticsCommand.refreshDiagnostics((String) arguments.get(0), (String) arguments.get(1), (boolean) arguments.get(2)); - case "java.project.provideSemanticTokens": - return SemanticTokensCommand.provide((String) arguments.get(0)); - case "java.project.getSemanticTokensLegend": - return new SemanticTokensLegend(); case "java.project.import": ProjectCommand.importProject(monitor); return null; diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/InitHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/InitHandler.java index dde42ac7c3..05b60981a7 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/InitHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/InitHandler.java @@ -17,6 +17,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -38,11 +39,14 @@ import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager; import org.eclipse.lsp4j.CodeActionOptions; import org.eclipse.lsp4j.CodeLensOptions; +import org.eclipse.lsp4j.DocumentFilter; import org.eclipse.lsp4j.DocumentOnTypeFormattingOptions; import org.eclipse.lsp4j.ExecuteCommandOptions; import org.eclipse.lsp4j.InitializeParams; import org.eclipse.lsp4j.InitializeResult; import org.eclipse.lsp4j.SaveOptions; +import org.eclipse.lsp4j.SemanticTokensServerFull; +import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions; import org.eclipse.lsp4j.ServerCapabilities; import org.eclipse.lsp4j.TextDocumentSyncKind; import org.eclipse.lsp4j.TextDocumentSyncOptions; @@ -178,6 +182,16 @@ public void registerCapabilities(InitializeResult initializeResult) { wsCapabilities.setWorkspaceFolders(wsFoldersOptions); capabilities.setWorkspace(wsCapabilities); + SemanticTokensWithRegistrationOptions semanticTokensOptions = new SemanticTokensWithRegistrationOptions(); + semanticTokensOptions.setFull(new SemanticTokensServerFull(false)); + semanticTokensOptions.setRange(false); + semanticTokensOptions.setDocumentSelector(List.of( + new DocumentFilter("java", "file", null), + new DocumentFilter("java", "jdt", null) + )); + semanticTokensOptions.setLegend(SemanticTokensHandler.getLegend()); + capabilities.setSemanticTokensProvider(semanticTokensOptions); + initializeResult.setCapabilities(capabilities); } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java index e4ac882db9..f90d3db9d2 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java @@ -125,6 +125,8 @@ import org.eclipse.lsp4j.RenameParams; import org.eclipse.lsp4j.SelectionRange; import org.eclipse.lsp4j.SelectionRangeParams; +import org.eclipse.lsp4j.SemanticTokens; +import org.eclipse.lsp4j.SemanticTokensParams; import org.eclipse.lsp4j.SignatureHelp; import org.eclipse.lsp4j.SignatureHelpParams; import org.eclipse.lsp4j.SymbolInformation; @@ -455,12 +457,12 @@ public void didChangeConfiguration(DidChangeConfigurationParams params) { syncCapabilitiesToSettings(); boolean jvmChanged = false; try { - jvmChanged = jvmConfigurator.configureJVMs(preferenceManager.getPreferences(), this.client); + jvmChanged = JVMConfigurator.configureJVMs(preferenceManager.getPreferences(), this.client); } catch (Exception e) { JavaLanguageServerPlugin.logException(e.getMessage(), e); } try { - boolean autoBuildChanged = pm.setAutoBuilding(preferenceManager.getPreferences().isAutobuildEnabled()); + boolean autoBuildChanged = ProjectsManager.setAutoBuilding(preferenceManager.getPreferences().isAutobuildEnabled()); if (jvmChanged) { buildWorkspace(Either.forLeft(true)); } else if (autoBuildChanged) { @@ -983,7 +985,7 @@ public CompletableFuture> searchSymbols(SearchSymbolPara @Override public CompletableFuture> prepareCallHierarchy(CallHierarchyPrepareParams params) { - logInfo(">> textDocumentt/prepareCallHierarchy"); + logInfo(">> textDocument/prepareCallHierarchy"); return computeAsyncWithClientProgress((monitor) -> new CallHierarchyHandler().prepareCallHierarchy(params, monitor)); } @@ -999,6 +1001,15 @@ public CompletableFuture> callHierarchyOutgoingC return computeAsyncWithClientProgress((monitor) -> new CallHierarchyHandler().callHierarchyOutgoingCalls(params, monitor)); } + @Override + public CompletableFuture semanticTokensFull(SemanticTokensParams params) { + logInfo(">> textDocument/semanticTokens/full"); + return computeAsync(monitor -> { + waitForLifecycleJobs(monitor); + return SemanticTokensHandler.provide(monitor, params); + }); + } + private CompletableFuture computeAsyncWithClientProgress(Function code) { return CompletableFutures.computeAsync((cc) -> { IProgressMonitor monitor = progressReporterManager.getProgressReporter(cc); diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/commands/SemanticTokensCommand.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/SemanticTokensHandler.java similarity index 50% rename from org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/commands/SemanticTokensCommand.java rename to org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/SemanticTokensHandler.java index 0b344cf838..219641392d 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/commands/SemanticTokensCommand.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/SemanticTokensHandler.java @@ -11,37 +11,48 @@ * Microsoft Corporation - initial API and implementation *******************************************************************************/ -package org.eclipse.jdt.ls.core.internal.commands; +package org.eclipse.jdt.ls.core.internal.handlers; +import java.util.Arrays; +import java.util.Collections; +import java.util.stream.Collectors; + +import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jdt.core.ITypeRoot; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.manipulation.CoreASTProvider; import org.eclipse.jdt.ls.core.internal.JDTUtils; -import org.eclipse.jdt.ls.core.internal.JobHelpers; -import org.eclipse.jdt.ls.core.internal.handlers.DocumentLifeCycleHandler; -import org.eclipse.jdt.ls.core.internal.semantictokens.SemanticTokens; import org.eclipse.jdt.ls.core.internal.semantictokens.SemanticTokensVisitor; +import org.eclipse.jdt.ls.core.internal.semantictokens.TokenModifier; +import org.eclipse.jdt.ls.core.internal.semantictokens.TokenType; +import org.eclipse.lsp4j.SemanticTokens; +import org.eclipse.lsp4j.SemanticTokensLegend; +import org.eclipse.lsp4j.SemanticTokensParams; -public class SemanticTokensCommand { - public static SemanticTokens provide(String uri) { - JobHelpers.waitForJobs(DocumentLifeCycleHandler.DOCUMENT_LIFE_CYCLE_JOBS, null); - return doProvide(uri); - } +public class SemanticTokensHandler { - private static SemanticTokens doProvide(String uri) { - ITypeRoot typeRoot = JDTUtils.resolveTypeRoot(uri); - if (typeRoot == null) { - return new SemanticTokens(new int[0]); + public static SemanticTokens provide(IProgressMonitor monitor, SemanticTokensParams params) { + ITypeRoot typeRoot = JDTUtils.resolveTypeRoot(params.getTextDocument().getUri()); + if (typeRoot == null || monitor.isCanceled()) { + return new SemanticTokens(Collections.emptyList()); } CompilationUnit root = CoreASTProvider.getInstance().getAST(typeRoot, CoreASTProvider.WAIT_YES, new NullProgressMonitor()); - if (root == null) { - return new SemanticTokens(new int[0]); + if (root == null || monitor.isCanceled()) { + return new SemanticTokens(Collections.emptyList()); } SemanticTokensVisitor collector = new SemanticTokensVisitor(root); root.accept(collector); return collector.getSemanticTokens(); } + + public static SemanticTokensLegend getLegend() { + return new SemanticTokensLegend( + Arrays.stream(TokenType.values()).map(TokenType::toString).collect(Collectors.toList()), + Arrays.stream(TokenModifier.values()).map(TokenModifier::toString).collect(Collectors.toList()) + ); + } + } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/SemanticTokens.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/SemanticTokens.java deleted file mode 100644 index 98b234ed2c..0000000000 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/SemanticTokens.java +++ /dev/null @@ -1,100 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2020 Microsoft Corporation and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Microsoft Corporation - initial API and implementation - * 0dinD - Semantic highlighting improvements - https://github.com/eclipse/eclipse.jdt.ls/pull/1501 - *******************************************************************************/ -package org.eclipse.jdt.ls.core.internal.semantictokens; - -import org.eclipse.lsp4j.util.Preconditions; - -public class SemanticTokens { - - /** - * Tokens in a file are represented as an array of integers. The position of each token is expressed relative to - * the token before it, because most tokens remain stable relative to each other when edits are made in a file. - * - * --- - * In short, each token takes 5 integers to represent, so a specific token `i` in the file consists of the following array indices: - * - at index `5*i` - `deltaLine`: token line number, relative to the previous token - * - at index `5*i+1` - `deltaStart`: token start character, relative to the previous token (relative to 0 or the previous token's start if they are on the same line) - * - at index `5*i+2` - `length`: the length of the token. A token cannot be multiline. - * - at index `5*i+3` - `tokenType`: will be looked up in `SemanticTokensLegend.tokenTypes`. We currently ask that `tokenType` < 65536. - * - at index `5*i+4` - `tokenModifiers`: each set bit will be looked up in `SemanticTokensLegend.tokenModifiers` - * - * --- - * ### How to encode tokens - * - * Here is an example for encoding a file with 3 tokens in a uint32 array: - * ``` - * { line: 2, startChar: 5, length: 3, tokenType: "property", tokenModifiers: ["private", "static"] }, - * { line: 2, startChar: 10, length: 4, tokenType: "type", tokenModifiers: [] }, - * { line: 5, startChar: 2, length: 7, tokenType: "class", tokenModifiers: [] } - * ``` - * - * 1. First of all, a legend must be devised. This legend must be provided up-front and capture all possible token types. - * For this example, we will choose the following legend which must be passed in when registering the provider: - * ``` - * tokenTypes: ['property', 'type', 'class'], - * tokenModifiers: ['private', 'static'] - * ``` - * - * 2. The first transformation step is to encode `tokenType` and `tokenModifiers` as integers using the legend. Token types are looked - * up by index, so a `tokenType` value of `1` means `tokenTypes[1]`. Multiple token modifiers can be set by using bit flags, - * so a `tokenModifier` value of `3` is first viewed as binary `0b00000011`, which means `[tokenModifiers[0], tokenModifiers[1]]` because - * bits 0 and 1 are set. Using this legend, the tokens now are: - * ``` - * { line: 2, startChar: 5, length: 3, tokenType: 0, tokenModifiers: 3 }, - * { line: 2, startChar: 10, length: 4, tokenType: 1, tokenModifiers: 0 }, - * { line: 5, startChar: 2, length: 7, tokenType: 2, tokenModifiers: 0 } - * ``` - * - * 3. The next step is to represent each token relative to the previous token in the file. In this case, the second token - * is on the same line as the first token, so the `startChar` of the second token is made relative to the `startChar` - * of the first token, so it will be `10 - 5`. The third token is on a different line than the second token, so the - * `startChar` of the third token will not be altered: - * ``` - * { deltaLine: 2, deltaStartChar: 5, length: 3, tokenType: 0, tokenModifiers: 3 }, - * { deltaLine: 0, deltaStartChar: 5, length: 4, tokenType: 1, tokenModifiers: 0 }, - * { deltaLine: 3, deltaStartChar: 2, length: 7, tokenType: 2, tokenModifiers: 0 } - * ``` - * - * 4. Finally, the last step is to inline each of the 5 fields for a token in a single array, which is a memory friendly representation: - * ``` - * // 1st token, 2nd token, 3rd token - * [ 2,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] - * ``` - */ - private final int[] data; - - /** - * The result id of the tokens (optional). - * - * This is the id that will be passed to incrementally calculate semantic tokens. - */ - private final String resultId; - - public SemanticTokens(int[] data) { - this(data, null); - } - - public SemanticTokens(int[] data, String resultId) { - this.data = Preconditions.checkNotNull(data, "data"); - this.resultId = resultId; - } - - public String getResultId() { - return resultId; - } - - public int[] getData() { - return data; - } -} diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/SemanticTokensLegend.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/SemanticTokensLegend.java deleted file mode 100644 index d1eb0ead48..0000000000 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/SemanticTokensLegend.java +++ /dev/null @@ -1,38 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2020 Microsoft Corporation and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Microsoft Corporation - initial API and implementation - * 0dinD - Semantic highlighting improvements - https://github.com/eclipse/eclipse.jdt.ls/pull/1501 - *******************************************************************************/ -package org.eclipse.jdt.ls.core.internal.semantictokens; - -import java.util.Arrays; - -public final class SemanticTokensLegend { - private final String[] tokenTypes; - private final String[] tokenModifiers; - - public SemanticTokensLegend() { - tokenTypes = Arrays.stream(TokenType.values()) - .map(TokenType::toString) - .toArray(String[]::new); - tokenModifiers = Arrays.stream(TokenModifier.values()) - .map(TokenModifier::toString) - .toArray(String[]::new); - } - - public String[] getTokenTypes() { - return tokenTypes; - } - - public String[] getTokenModifiers() { - return tokenModifiers; - } -} diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/SemanticTokensVisitor.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/SemanticTokensVisitor.java index d7ed3c68bc..3f9a6a6c0e 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/SemanticTokensVisitor.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/SemanticTokensVisitor.java @@ -45,6 +45,7 @@ import org.eclipse.jdt.core.dom.TagElement; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeLiteral; +import org.eclipse.lsp4j.SemanticTokens; public class SemanticTokensVisitor extends ASTVisitor { private CompilationUnit cu; @@ -90,9 +91,9 @@ public SemanticTokens getSemanticTokens() { return new SemanticTokens(encodedTokens()); } - private int[] encodedTokens() { + private List encodedTokens() { int numTokens = tokens.size(); - int[] data = new int[numTokens * 5]; + List data = new ArrayList<>(numTokens * 5); int currentLine = 0; int currentColumn = 0; for (int i = 0; i < numTokens; i++) { @@ -111,12 +112,11 @@ private int[] encodedTokens() { int tokenTypeIndex = token.getTokenType().ordinal(); int tokenModifiers = token.getTokenModifiers(); - int offset = i * 5; - data[offset] = deltaLine; - data[offset + 1] = deltaColumn; - data[offset + 2] = token.getLength(); - data[offset + 3] = tokenTypeIndex; - data[offset + 4] = tokenModifiers; + data.add(deltaLine); + data.add(deltaColumn); + data.add(token.getLength()); + data.add(tokenTypeIndex); + data.add(tokenModifiers); } } return data; diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/TokenModifier.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/TokenModifier.java index d56121056d..60948ba0df 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/TokenModifier.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/TokenModifier.java @@ -29,15 +29,16 @@ 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("abstract"), - STATIC("static"), - FINAL("readonly"), - DEPRECATED("deprecated"), - DECLARATION("declaration"), - DOCUMENTATION("documentation"), + ABSTRACT(SemanticTokenModifiers.Abstract), + STATIC(SemanticTokenModifiers.Static), + FINAL(SemanticTokenModifiers.Readonly), + DEPRECATED(SemanticTokenModifiers.Deprecated), + DECLARATION(SemanticTokenModifiers.Declaration), + DOCUMENTATION(SemanticTokenModifiers.Documentation), // Custom token modifiers PUBLIC("public"), diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/TokenType.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/TokenType.java index b41120b43c..8022df0e52 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/TokenType.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/semantictokens/TokenType.java @@ -17,22 +17,23 @@ 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("namespace"), - CLASS("class"), - INTERFACE("interface"), - ENUM("enum"), - ENUM_MEMBER("enumMember"), - TYPE("type"), - TYPE_PARAMETER("typeParameter"), - METHOD("method"), - PROPERTY("property"), - VARIABLE("variable"), - PARAMETER("parameter"), - MODIFIER("modifier"), - KEYWORD("keyword"), + 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"), diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/syntaxserver/SyntaxInitHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/syntaxserver/SyntaxInitHandler.java index 51822cc195..420a95e651 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/syntaxserver/SyntaxInitHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/syntaxserver/SyntaxInitHandler.java @@ -14,6 +14,7 @@ package org.eclipse.jdt.ls.core.internal.syntaxserver; import java.util.Collection; +import java.util.List; import java.util.stream.Collectors; import org.eclipse.core.resources.ResourcesPlugin; @@ -26,10 +27,14 @@ import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; import org.eclipse.jdt.ls.core.internal.handlers.BaseInitHandler; import org.eclipse.jdt.ls.core.internal.handlers.CompletionHandler; +import org.eclipse.jdt.ls.core.internal.handlers.SemanticTokensHandler; import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager; import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager; +import org.eclipse.lsp4j.DocumentFilter; import org.eclipse.lsp4j.InitializeResult; import org.eclipse.lsp4j.SaveOptions; +import org.eclipse.lsp4j.SemanticTokensServerFull; +import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions; import org.eclipse.lsp4j.ServerCapabilities; import org.eclipse.lsp4j.TextDocumentSyncKind; import org.eclipse.lsp4j.TextDocumentSyncOptions; @@ -83,6 +88,16 @@ public void registerCapabilities(InitializeResult initializeResult) { wsCapabilities.setWorkspaceFolders(wsFoldersOptions); capabilities.setWorkspace(wsCapabilities); + SemanticTokensWithRegistrationOptions semanticTokensOptions = new SemanticTokensWithRegistrationOptions(); + semanticTokensOptions.setFull(new SemanticTokensServerFull(false)); + semanticTokensOptions.setRange(false); + semanticTokensOptions.setDocumentSelector(List.of( + new DocumentFilter("java", "file", null), + new DocumentFilter("java", "jdt", null) + )); + semanticTokensOptions.setLegend(SemanticTokensHandler.getLegend()); + capabilities.setSemanticTokensProvider(semanticTokensOptions); + initializeResult.setCapabilities(capabilities); } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/syntaxserver/SyntaxLanguageServer.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/syntaxserver/SyntaxLanguageServer.java index aabcc68e37..ea0a626424 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/syntaxserver/SyntaxLanguageServer.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/syntaxserver/SyntaxLanguageServer.java @@ -49,6 +49,7 @@ import org.eclipse.jdt.ls.core.internal.handlers.NavigateToDefinitionHandler; import org.eclipse.jdt.ls.core.internal.handlers.NavigateToTypeDefinitionHandler; import org.eclipse.jdt.ls.core.internal.handlers.SelectionRangeHandler; +import org.eclipse.jdt.ls.core.internal.handlers.SemanticTokensHandler; import org.eclipse.jdt.ls.core.internal.handlers.WorkspaceEventsHandler; import org.eclipse.jdt.ls.core.internal.handlers.WorkspaceFolderChangeHandler; import org.eclipse.jdt.ls.core.internal.managers.ContentProviderManager; @@ -78,6 +79,8 @@ import org.eclipse.lsp4j.LocationLink; import org.eclipse.lsp4j.SelectionRange; import org.eclipse.lsp4j.SelectionRangeParams; +import org.eclipse.lsp4j.SemanticTokens; +import org.eclipse.lsp4j.SemanticTokensParams; import org.eclipse.lsp4j.SymbolInformation; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.TypeDefinitionParams; @@ -400,6 +403,15 @@ public CompletableFuture resolveCompletionItem(CompletionItem un return result; } + @Override + public CompletableFuture semanticTokensFull(SemanticTokensParams params) { + logInfo(">> textDocument/semanticTokens/full"); + return computeAsync(monitor -> { + waitForLifecycleJobs(monitor); + return SemanticTokensHandler.provide(monitor, params); + }); + } + private void waitForLifecycleJobs(IProgressMonitor monitor) { JobHelpers.waitForJobs(BaseDocumentLifeCycleHandler.DOCUMENT_LIFE_CYCLE_JOBS, monitor); } diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/commands/SemanticTokensCommandTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/SemanticTokensHandlerTest.java similarity index 92% rename from org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/commands/SemanticTokensCommandTest.java rename to org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/SemanticTokensHandlerTest.java index 0931dccdcc..b668fa924b 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/commands/SemanticTokensCommandTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/SemanticTokensHandlerTest.java @@ -10,7 +10,7 @@ * Contributors: * Microsoft Corporation - initial API and implementation *******************************************************************************/ -package org.eclipse.jdt.ls.core.internal.commands; +package org.eclipse.jdt.ls.core.internal.handlers; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -21,6 +21,7 @@ import java.util.Hashtable; import java.util.List; +import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jdt.core.IBuffer; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; @@ -31,17 +32,18 @@ import org.eclipse.jdt.ls.core.internal.JDTUtils; import org.eclipse.jdt.ls.core.internal.WorkspaceHelper; import org.eclipse.jdt.ls.core.internal.correction.TestOptions; -import org.eclipse.jdt.ls.core.internal.handlers.JsonRpcHelpers; import org.eclipse.jdt.ls.core.internal.managers.AbstractProjectsManagerBasedTest; -import org.eclipse.jdt.ls.core.internal.semantictokens.SemanticTokens; -import org.eclipse.jdt.ls.core.internal.semantictokens.SemanticTokensLegend; +import org.eclipse.lsp4j.SemanticTokens; +import org.eclipse.lsp4j.SemanticTokensLegend; +import org.eclipse.lsp4j.SemanticTokensParams; +import org.eclipse.lsp4j.TextDocumentIdentifier; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.runners.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) -public class SemanticTokensCommandTest extends AbstractProjectsManagerBasedTest { +public class SemanticTokensHandlerTest extends AbstractProjectsManagerBasedTest { private IJavaProject semanticTokensProject; private IPackageFragment fooPackage; private String classFileUri = "jdt://contents/foo.jar/foo/bar.class?%3Dsemantic-tokens%2Ffoo.jar%3Cfoo%28bar.class"; @@ -377,32 +379,32 @@ private String getURI(String compilationUnitName) { } /** - * Helper class for asserting semantic tokens provided by the {@link SemanticTokensCommand}, + * Helper class for asserting semantic tokens provided by the {@link SemanticTokensHandler}, * using the builder pattern. Call {@link #beginAssertion(String, String...)} to get an instance * of the helper, then chain calls to {@link #assertNextToken(String, String, String...)} until no * more tokens are expected, at which point {@link #endAssertion()} should finally be called. */ private static class TokenAssertionHelper { - private static final SemanticTokensLegend LEGEND = new SemanticTokensLegend(); + private static final SemanticTokensLegend LEGEND = SemanticTokensHandler.getLegend(); private IBuffer buffer; private int currentLine = 0; private int currentColumn = 0; - private int[] semanticTokensData; + private List semanticTokensData; private int currentDataIndex = 0; private List tokenTypeFilter; - private TokenAssertionHelper(IBuffer buffer, int[] semanticTokensData, List tokenTypeFilter) { + private TokenAssertionHelper(IBuffer buffer, List semanticTokensData, List tokenTypeFilter) { this.buffer = buffer; this.semanticTokensData = semanticTokensData; this.tokenTypeFilter = tokenTypeFilter; } /** - * Begins an assertion for semantic tokens (calling {@link SemanticTokensCommand#provide(String)}), + * Begins an assertion for semantic tokens (calling {@link SemanticTokensHandler#provide(String)}), * optionally providing a filter describing which token types to assert. * * @param uri The URI to assert provided semantic tokens for. @@ -412,15 +414,15 @@ private TokenAssertionHelper(IBuffer buffer, int[] semanticTokensData, List= 0); assertTrue("Token deltaColumn should not be negative", deltaColumn >= 0); @@ -451,7 +453,7 @@ public TokenAssertionHelper assertNextToken(String expectedText, String expected currentDataIndex += 5; - if (tokenTypeFilter.isEmpty() || tokenTypeFilter.contains(LEGEND.getTokenTypes()[typeIndex])) { + if (tokenTypeFilter.isEmpty() || tokenTypeFilter.contains(LEGEND.getTokenTypes().get(typeIndex))) { assertTextMatchInBuffer(length, expectedText); assertTokenType(typeIndex, expectedType); assertTokenModifiers(encodedModifiers, Arrays.asList(expectedModifiers)); @@ -465,16 +467,16 @@ public TokenAssertionHelper assertNextToken(String expectedText, String expected /** * Asserts that there are no more unexpected semantic tokens present in the data - * provided by {@link SemanticTokensCommand}. + * provided by {@link SemanticTokensHandler}. */ public void endAssertion() { if (tokenTypeFilter.isEmpty()) { - assertTrue("There should be no more tokens", currentDataIndex == semanticTokensData.length); + assertTrue("There should be no more tokens", currentDataIndex == semanticTokensData.size()); } else { - while (currentDataIndex < semanticTokensData.length) { - int currentTypeIndex = semanticTokensData[currentDataIndex + 3]; - String currentType = LEGEND.getTokenTypes()[currentTypeIndex]; + while (currentDataIndex < semanticTokensData.size()) { + int currentTypeIndex = semanticTokensData.get(currentDataIndex + 3); + String currentType = LEGEND.getTokenTypes().get(currentTypeIndex); assertFalse( "There should be no more tokens matching the filter, but found '" + currentType + "' token", tokenTypeFilter.contains(currentType) @@ -490,12 +492,12 @@ private void assertTextMatchInBuffer(int length, String expectedText) { } private void assertTokenType(int typeIndex, String expectedType) { - assertEquals("Token type should be correct.", expectedType, LEGEND.getTokenTypes()[typeIndex]); + assertEquals("Token type should be correct.", expectedType, LEGEND.getTokenTypes().get(typeIndex)); } private void assertTokenModifiers(int encodedModifiers, List expectedModifiers) { - for (int i = 0; i < LEGEND.getTokenModifiers().length; i++) { - String modifier = LEGEND.getTokenModifiers()[i]; + for (int i = 0; i < LEGEND.getTokenModifiers().size(); i++) { + String modifier = LEGEND.getTokenModifiers().get(i); boolean modifierIsEncoded = ((encodedModifiers >>> i) & 1) == 1; boolean modifierIsExpected = expectedModifiers.contains(modifier);