diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/DocumentHighlightHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/DocumentHighlightHandler.java index d98a3f0f66..27c3655372 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/DocumentHighlightHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/DocumentHighlightHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016-2017 Red Hat Inc. and others. + * Copyright (c) 2016-2022 Red Hat Inc. 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 @@ -18,75 +18,140 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jdt.core.ITypeRoot; -import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.manipulation.CoreASTProvider; +import org.eclipse.jdt.internal.core.manipulation.search.BreakContinueTargetFinder; +import org.eclipse.jdt.internal.core.manipulation.search.ExceptionOccurrencesFinder; import org.eclipse.jdt.internal.core.manipulation.search.IOccurrencesFinder; +import org.eclipse.jdt.internal.core.manipulation.search.ImplementOccurrencesFinder; +import org.eclipse.jdt.internal.core.manipulation.search.MethodExitsFinder; import org.eclipse.jdt.internal.core.manipulation.search.IOccurrencesFinder.OccurrenceLocation; import org.eclipse.jdt.internal.core.manipulation.search.OccurrencesFinder; import org.eclipse.jdt.ls.core.internal.JDTUtils; -import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; import org.eclipse.lsp4j.DocumentHighlight; import org.eclipse.lsp4j.DocumentHighlightKind; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.TextDocumentPositionParams; -@SuppressWarnings("restriction") -public class DocumentHighlightHandler{ - - private List computeOccurrences(ITypeRoot unit, int line, int column, IProgressMonitor monitor) { - if (unit != null) { - try { - int offset = JsonRpcHelpers.toOffset(unit.getBuffer(), line, column); - OccurrencesFinder finder = new OccurrencesFinder(); - CompilationUnit ast = CoreASTProvider.getInstance().getAST(unit, CoreASTProvider.WAIT_YES, monitor); - if (ast != null) { - String error = finder.initialize(ast, offset, 0); - if (error == null){ - List result = new ArrayList<>(); - OccurrenceLocation[] occurrences = finder.getOccurrences(); - if (occurrences != null) { - for (OccurrenceLocation loc : occurrences) { - if (monitor.isCanceled()) { - return Collections.emptyList(); - } - result.add(convertToHighlight(unit, loc)); - } - } - return result; - } - } - } catch (JavaModelException e) { - JavaLanguageServerPlugin.logException("Problem with compute occurrences for" + unit.getElementName(), e); - } +/** + * Handler for {@code textDocument/documentHighlight} requests. + */ +public class DocumentHighlightHandler { + + /** + * Handles a {@code textDocument/documentHighlight} request. + * + * @param params the position at which to find highlights + * @param monitor the progress monitor + * @return the document highlights for the given position + */ + public static List documentHighlight(TextDocumentPositionParams params, IProgressMonitor monitor) { + ITypeRoot typeRoot = JDTUtils.resolveTypeRoot(params.getTextDocument().getUri()); + if (typeRoot == null || monitor.isCanceled()) { + return Collections.emptyList(); + } + CompilationUnit ast = CoreASTProvider.getInstance().getAST(typeRoot, CoreASTProvider.WAIT_YES, monitor); + if (ast == null || monitor.isCanceled()) { + return Collections.emptyList(); + } + + int offset = JsonRpcHelpers.toOffset(typeRoot, + params.getPosition().getLine(), params.getPosition().getCharacter()); + ASTNode node = NodeFinder.perform(ast, offset, 0); + if (monitor.isCanceled()) { + return Collections.emptyList(); + } + + return findHighlights(ast, node, monitor); + } + + /** + * Finds {@link DocumentHighlight}s in a {@link CompilationUnit}. + * The highlights are searched using the following {@link IOccurrencesFinder}s: + *
    + *
  1. {@link ExceptionOccurrencesFinder}
  2. + *
  3. {@link MethodExitsFinder}
  4. + *
  5. {@link BreakContinueTargetFinder}
  6. + *
  7. {@link ImplementOccurrencesFinder}
  8. + *
  9. {@link OccurrencesFinder}
  10. + *
+ * + * @param ast the {@link CompilationUnit} + * @param node the selected {@link ASTNode} to find highlights for + * @param monitor the progress monitor + * @return the highlights, or an empty list if none were found + */ + private static List findHighlights(CompilationUnit ast, ASTNode node, IProgressMonitor monitor) { + IOccurrencesFinder finder; + + finder = new ExceptionOccurrencesFinder(); + if (finder.initialize(ast, node) == null) { + return convertToHighlights(ast, finder.getOccurrences()); + } + if (monitor.isCanceled()) { + return Collections.emptyList(); + } + + finder = new MethodExitsFinder(); + if (finder.initialize(ast, node) == null) { + return convertToHighlights(ast, finder.getOccurrences()); } + if (monitor.isCanceled()) { + return Collections.emptyList(); + } + + finder = new BreakContinueTargetFinder(); + if (finder.initialize(ast, node) == null) { + return convertToHighlights(ast, finder.getOccurrences()); + } + if (monitor.isCanceled()) { + return Collections.emptyList(); + } + + finder = new ImplementOccurrencesFinder(); + if (finder.initialize(ast, node) == null) { + return convertToHighlights(ast, finder.getOccurrences()); + } + if (monitor.isCanceled()) { + return Collections.emptyList(); + } + + finder = new OccurrencesFinder(); + if (finder.initialize(ast, node) == null) { + return convertToHighlights(ast, finder.getOccurrences()); + } + return Collections.emptyList(); } - private DocumentHighlight convertToHighlight(ITypeRoot unit, OccurrenceLocation occurrence) - throws JavaModelException { - DocumentHighlight h = new DocumentHighlight(); - if ((occurrence.getFlags() | IOccurrencesFinder.F_WRITE_OCCURRENCE) == IOccurrencesFinder.F_WRITE_OCCURRENCE) { - h.setKind(DocumentHighlightKind.Write); - } else if ((occurrence.getFlags() - | IOccurrencesFinder.F_READ_OCCURRENCE) == IOccurrencesFinder.F_READ_OCCURRENCE) { - h.setKind(DocumentHighlightKind.Read); + private static List convertToHighlights(CompilationUnit ast, OccurrenceLocation[] locations) { + List highlights = new ArrayList<>(locations.length); + for (OccurrenceLocation loc : locations) { + highlights.add(convertToHighlight(ast, loc)); } - int[] loc = JsonRpcHelpers.toLine(unit.getBuffer(), occurrence.getOffset()); - int[] endLoc = JsonRpcHelpers.toLine(unit.getBuffer(), occurrence.getOffset() + occurrence.getLength()); - - h.setRange(new Range( - new Position(loc[0], loc[1]), - new Position(endLoc[0],endLoc[1]) - )); - return h; + return highlights; } - public List documentHighlight(TextDocumentPositionParams position, IProgressMonitor monitor) { - ITypeRoot type = JDTUtils.resolveTypeRoot(position.getTextDocument().getUri()); - return computeOccurrences(type, position.getPosition().getLine(), - position.getPosition().getCharacter(), monitor); + private static DocumentHighlight convertToHighlight(CompilationUnit ast, OccurrenceLocation occurrence) { + DocumentHighlight highlight = new DocumentHighlight(); + if ((occurrence.getFlags() & IOccurrencesFinder.F_WRITE_OCCURRENCE) != 0) { + highlight.setKind(DocumentHighlightKind.Write); + } else { + // highlight kind for symbols should be either Read or Write (not Text), see + // https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#textDocument_documentHighlight + highlight.setKind(DocumentHighlightKind.Read); + } + + int[] startPos = JsonRpcHelpers.toLine(ast.getTypeRoot(), occurrence.getOffset()); + int[] endPos = JsonRpcHelpers.toLine(ast.getTypeRoot(), occurrence.getOffset() + occurrence.getLength()); + highlight.setRange(new Range( + new Position(startPos[0], startPos[1]), + new Position(endPos[0], endPos[1]) + )); + return highlight; } } 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 7df669acd0..6670c0f781 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016-2020 Red Hat Inc. and others. + * Copyright (c) 2016-2022 Red Hat Inc. 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 @@ -620,8 +620,7 @@ public CompletableFuture> findLinks(FindLinksParams par @Override public CompletableFuture> documentHighlight(DocumentHighlightParams position) { logInfo(">> document/documentHighlight"); - DocumentHighlightHandler handler = new DocumentHighlightHandler(); - return computeAsync((monitor) -> handler.documentHighlight(position, monitor)); + return computeAsync((monitor) -> DocumentHighlightHandler.documentHighlight(position, monitor)); } /* (non-Javadoc) 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 6fd583aa81..ec0f097e3b 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 @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2020 Microsoft Corporation and others. +* Copyright (c) 2020-2022 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 @@ -75,6 +75,9 @@ public void registerCapabilities(InitializeResult initializeResult) { if (!preferenceManager.getClientPreferences().isCompletionDynamicRegistered()) { capabilities.setCompletionProvider(CompletionHandler.DEFAULT_COMPLETION_OPTIONS); } + if (!preferenceManager.getClientPreferences().isDocumentHighlightDynamicRegistered()) { + capabilities.setDocumentHighlightProvider(Boolean.TRUE); + } TextDocumentSyncOptions textDocumentSyncOptions = new TextDocumentSyncOptions(); textDocumentSyncOptions.setOpenClose(Boolean.TRUE); textDocumentSyncOptions.setSave(new SaveOptions(Boolean.TRUE)); 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 8451c98c35..81a53da304 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 @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2020 Microsoft Corporation and others. +* Copyright (c) 2020-2022 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 @@ -43,6 +43,7 @@ import org.eclipse.jdt.ls.core.internal.handlers.BaseDocumentLifeCycleHandler; import org.eclipse.jdt.ls.core.internal.handlers.CompletionHandler; import org.eclipse.jdt.ls.core.internal.handlers.CompletionResolveHandler; +import org.eclipse.jdt.ls.core.internal.handlers.DocumentHighlightHandler; import org.eclipse.jdt.ls.core.internal.handlers.DocumentSymbolHandler; import org.eclipse.jdt.ls.core.internal.handlers.FoldingRangeHandler; import org.eclipse.jdt.ls.core.internal.handlers.HoverHandler; @@ -67,6 +68,8 @@ import org.eclipse.lsp4j.DidCloseTextDocumentParams; import org.eclipse.lsp4j.DidOpenTextDocumentParams; import org.eclipse.lsp4j.DidSaveTextDocumentParams; +import org.eclipse.lsp4j.DocumentHighlight; +import org.eclipse.lsp4j.DocumentHighlightParams; import org.eclipse.lsp4j.DocumentSymbol; import org.eclipse.lsp4j.DocumentSymbolParams; import org.eclipse.lsp4j.FoldingRange; @@ -227,6 +230,10 @@ public void initialized() { registerCapability(Preferences.HOVER_ID, Preferences.HOVER, null); } + if (preferenceManager.getClientPreferences().isDocumentHighlightDynamicRegistered()) { + registerCapability(Preferences.DOCUMENT_HIGHLIGHT_ID, Preferences.DOCUMENT_HIGHLIGHT); + } + if (preferenceManager.getClientPreferences().isWorkspaceChangeWatchedFilesDynamicRegistered()) { projectsManager.registerWatchers(); } @@ -410,6 +417,12 @@ public CompletableFuture semanticTokensFull(SemanticTokensParams documentLifeCycleHandler.new DocumentMonitor(params.getTextDocument().getUri()))); } + @Override + public CompletableFuture> documentHighlight(DocumentHighlightParams position) { + logInfo(">> document/documentHighlight"); + return computeAsync((monitor) -> DocumentHighlightHandler.documentHighlight(position, monitor)); + } + private void waitForLifecycleJobs(IProgressMonitor monitor) { JobHelpers.waitForJobs(BaseDocumentLifeCycleHandler.DOCUMENT_LIFE_CYCLE_JOBS, monitor); } diff --git a/org.eclipse.jdt.ls.tests/projects/eclipse/hello/src/org/sample/Highlight.java b/org.eclipse.jdt.ls.tests/projects/eclipse/hello/src/org/sample/Highlight.java index d7c59ab8aa..cf9b7f4d15 100644 --- a/org.eclipse.jdt.ls.tests/projects/eclipse/hello/src/org/sample/Highlight.java +++ b/org.eclipse.jdt.ls.tests/projects/eclipse/hello/src/org/sample/Highlight.java @@ -1,12 +1,49 @@ package org.sample; -public class Highlight { - - public void test() { - String string = ""; - string.toString(); - string = ""; - string.toString(); - } - +import java.io.IOException; + +public class Highlight implements FooInterface { + + private String str = "string"; + + public String getFoo() throws IOException { + if (str.contains("!")) { + throw new IOException(); + } + str = "bar"; + if (str.length() == 0) { + throw new RuntimeException(); + } + loop: while (!str.isEmpty()) { + for (;;) { + if (str.contains("foo")) { + break loop; + } + continue; + } + } + str = String.format(str); + return str + "foo"; + } + + public int getBar() { + String str = "bar"; + return str.length(); + } + + @Override + public void foo() { + // TODO Auto-generated method stub + } + + @Override + public void bar() { + // TODO Auto-generated method stub + } + +} + +interface FooInterface { + public void foo(); + public void bar(); } diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/DocumentHighlightHandlerTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/DocumentHighlightHandlerTest.java index 93f1e1589c..d97694b23c 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/DocumentHighlightHandlerTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/DocumentHighlightHandlerTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Remy Suen and others. + * Copyright (c) 2017-2022 Remy Suen 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 @@ -14,9 +14,12 @@ import static org.junit.Assert.assertEquals; +import java.util.Comparator; +import java.util.Iterator; import java.util.List; import org.eclipse.core.resources.IProject; +import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.ls.core.internal.ClassFileUtil; import org.eclipse.jdt.ls.core.internal.Lsp4jAssertions; import org.eclipse.jdt.ls.core.internal.WorkspaceHelper; @@ -32,32 +35,94 @@ public class DocumentHighlightHandlerTest extends AbstractProjectsManagerBasedTest { private IProject project; - private DocumentHighlightHandler handler; - - private void assertHighlight(DocumentHighlight highlight, int expectedLine, int expectedStart, int expectedEnd, DocumentHighlightKind expectedKind) { - Lsp4jAssertions.assertRange(expectedLine, expectedStart, expectedEnd, highlight.getRange()); - assertEquals(expectedKind, highlight.getKind()); - } @Before public void setup() throws Exception { importProjects("eclipse/hello"); project = WorkspaceHelper.getProject("hello"); - handler = new DocumentHighlightHandler(); } @Test - public void testDocumentHighlightHandler() throws Exception { - String uri = ClassFileUtil.getURI(project, "org.sample.Highlight"); + public void testDocumentHighlight_ExceptionOccurences() throws JavaModelException { + List result = requestHighlights("org.sample.Highlight", 8, 34); + Iterator it = result.iterator(); + + assertEquals(2, result.size()); + assertHighlight(it.next(), 8, 31, 42, DocumentHighlightKind.Read); + assertHighlight(it.next(), 10, 3, 8, DocumentHighlightKind.Read); + } + + @Test + public void testDocumentHighlight_MethodExits() throws JavaModelException { + List result = requestHighlights("org.sample.Highlight", 8, 11); + Iterator it = result.iterator(); + + assertEquals(4, result.size()); + assertHighlight(it.next(), 8, 8, 14, DocumentHighlightKind.Read); + assertHighlight(it.next(), 10, 3, 8, DocumentHighlightKind.Read); + assertHighlight(it.next(), 14, 3, 8, DocumentHighlightKind.Read); + assertHighlight(it.next(), 25, 2, 21, DocumentHighlightKind.Read); + } + + @Test + public void testDocumentHighlight_BreakContinueTarget() throws JavaModelException { + List result = requestHighlights("org.sample.Highlight", 19, 7); + Iterator it = result.iterator(); + + assertEquals(2, result.size()); + assertHighlight(it.next(), 16, 2, 6, DocumentHighlightKind.Read); + assertHighlight(it.next(), 23, 2, 3, DocumentHighlightKind.Read); + } + + @Test + public void testDocumentHighlight_ImplementOccurrences() throws JavaModelException { + List result = requestHighlights("org.sample.Highlight", 4, 38); + Iterator it = result.iterator(); + + assertEquals(3, result.size()); + assertHighlight(it.next(), 4, 34, 46, DocumentHighlightKind.Read); + assertHighlight(it.next(), 34, 13, 16, DocumentHighlightKind.Read); + assertHighlight(it.next(), 39, 13, 16, DocumentHighlightKind.Read); + } + + @Test + public void testDocumentHighlight_Occurrences() throws JavaModelException { + List result = requestHighlights("org.sample.Highlight", 6, 18); + Iterator it = result.iterator(); + + assertEquals(9, result.size()); + assertHighlight(it.next(), 6, 16, 19, DocumentHighlightKind.Write); + assertHighlight(it.next(), 9, 6, 9, DocumentHighlightKind.Read); + assertHighlight(it.next(), 12, 2, 5, DocumentHighlightKind.Write); + assertHighlight(it.next(), 13, 6, 9, DocumentHighlightKind.Read); + assertHighlight(it.next(), 16, 16, 19, DocumentHighlightKind.Read); + assertHighlight(it.next(), 18, 8, 11, DocumentHighlightKind.Read); + assertHighlight(it.next(), 24, 2, 5, DocumentHighlightKind.Write); + assertHighlight(it.next(), 24, 22, 25, DocumentHighlightKind.Read); + assertHighlight(it.next(), 25, 9, 12, DocumentHighlightKind.Read); + } + + private List requestHighlights(String compilationUnit, int line, int character) throws JavaModelException { + String uri = ClassFileUtil.getURI(project, compilationUnit); TextDocumentIdentifier identifier = new TextDocumentIdentifier(uri); - TextDocumentPositionParams params = new TextDocumentPositionParams(identifier, new Position(5, 10)); - - List highlights = handler.documentHighlight(params, monitor); - assertEquals(4, highlights.size()); - assertHighlight(highlights.get(0), 5, 9, 15, DocumentHighlightKind.Write); - assertHighlight(highlights.get(1), 6, 2, 8, DocumentHighlightKind.Read); - assertHighlight(highlights.get(2), 7, 2, 8, DocumentHighlightKind.Write); - assertHighlight(highlights.get(3), 8, 2, 8, DocumentHighlightKind.Read); + TextDocumentPositionParams params = new TextDocumentPositionParams(identifier, new Position(line, character)); + List highlights = DocumentHighlightHandler.documentHighlight(params, monitor); + // Sorting the highlights to make testing easier + highlights.sort(Comparator.comparingInt(this::getStartLine).thenComparingInt(this::getStartCharacter)); + return highlights; + } + + private int getStartLine(DocumentHighlight highlight) { + return highlight.getRange().getStart().getLine(); + } + + private int getStartCharacter(DocumentHighlight highlight) { + return highlight.getRange().getStart().getCharacter(); + } + + private void assertHighlight(DocumentHighlight highlight, int expectedLine, int expectedStart, int expectedEnd, DocumentHighlightKind expectedKind) { + Lsp4jAssertions.assertRange(expectedLine, expectedStart, expectedEnd, highlight.getRange()); + assertEquals(expectedKind, highlight.getKind()); } }