diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..6bb66b660 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,45 @@ +# based on https://github.com/alexkaratarakis/gitattributes/ + +# Handle line endings automatically for files detected as text +# and leave all files detected as binary untouched. +* text=auto + +# +# The above will handle all files NOT found below +# +# These files are text and should be normalized (Convert crlf => lf) +*.gitattributes text +.gitignore text +*.bash text eol=lf +*.bat text eol=crlf +*.cmd text eol=crlf +*.css text diff=css +*.exsd text +*.htm text diff=html +*.html text diff=html +*.ini text +*.md text diff=markdown +*.java text diff=java +*.js text +*.json text +*.properties text +*.sh text +*.tmLanguage text +*.ts text +*.txt text +*.xsd text +*.xml text +*.yaml text +*.yml text +MANIFEST.MF text +Dockerfile text eol=lf +Jenkinsfile text +LICENSE text + +# These files are binary and should be left untouched +# (binary is a macro for -text -diff) +*.gif binary +*.ico binary +*.jpeg binary +*.jpg binary +*.png binary diff --git a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/semanticTokens/SemanticTokensDataStreamProcessorTest.java b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/semanticTokens/SemanticTokensDataStreamProcessorTest.java index 85b8996f5..46ba1e6a3 100644 --- a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/semanticTokens/SemanticTokensDataStreamProcessorTest.java +++ b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/semanticTokens/SemanticTokensDataStreamProcessorTest.java @@ -1,50 +1,50 @@ -/******************************************************************************* - * Copyright (c) 2022 Avaloq Group AG. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -package org.eclipse.lsp4e.test.semanticTokens; - -import static org.junit.Assert.assertEquals; - -import java.util.Arrays; -import java.util.List; - -import org.eclipse.jface.text.Document; -import org.eclipse.lsp4e.operations.semanticTokens.SemanticTokensDataStreamProcessor; -import org.eclipse.lsp4e.test.utils.AbstractTest; -import org.eclipse.lsp4j.SemanticTokensLegend; -import org.eclipse.swt.custom.StyleRange; -import org.junit.Test; - -public class SemanticTokensDataStreamProcessorTest extends AbstractTest { - - @Test - public void testKeyword() { - Document document = new Document(SemanticTokensTestUtil.keywordText); - - SemanticTokensDataStreamProcessor processor = new SemanticTokensDataStreamProcessor(SemanticTokensTestUtil - .keywordTokenTypeMapper(SemanticTokensTestUtil.RED_TOKEN), SemanticTokensTestUtil.offsetMapper(document)); - - List expectedStream = SemanticTokensTestUtil.keywordSemanticTokens(); - List expectedStyleRanges = Arrays.asList(// - new StyleRange(0, 4, SemanticTokensTestUtil.RED, null), // - new StyleRange(15, 4, SemanticTokensTestUtil.RED, null), // - new StyleRange(24, 7, SemanticTokensTestUtil.RED, null)// - ); - - List styleRanges = processor.getStyleRanges(expectedStream, getSemanticTokensLegend()); - - assertEquals(expectedStyleRanges, styleRanges); - } - - private SemanticTokensLegend getSemanticTokensLegend() { - SemanticTokensLegend semanticTokensLegend = new SemanticTokensLegend(); - semanticTokensLegend.setTokenTypes(Arrays.asList("keyword","other")); - semanticTokensLegend.setTokenModifiers(Arrays.asList("obsolete")); - return semanticTokensLegend; - } -} +/******************************************************************************* + * Copyright (c) 2022 Avaloq Group AG. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.lsp4e.test.semanticTokens; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jface.text.Document; +import org.eclipse.lsp4e.operations.semanticTokens.SemanticTokensDataStreamProcessor; +import org.eclipse.lsp4e.test.utils.AbstractTest; +import org.eclipse.lsp4j.SemanticTokensLegend; +import org.eclipse.swt.custom.StyleRange; +import org.junit.Test; + +public class SemanticTokensDataStreamProcessorTest extends AbstractTest { + + @Test + public void testKeyword() { + Document document = new Document(SemanticTokensTestUtil.keywordText); + + SemanticTokensDataStreamProcessor processor = new SemanticTokensDataStreamProcessor(SemanticTokensTestUtil + .keywordTokenTypeMapper(SemanticTokensTestUtil.RED_TOKEN), SemanticTokensTestUtil.offsetMapper(document)); + + List expectedStream = SemanticTokensTestUtil.keywordSemanticTokens(); + List expectedStyleRanges = Arrays.asList(// + new StyleRange(0, 4, SemanticTokensTestUtil.RED, null), // + new StyleRange(15, 4, SemanticTokensTestUtil.RED, null), // + new StyleRange(24, 7, SemanticTokensTestUtil.RED, null)// + ); + + List styleRanges = processor.getStyleRanges(expectedStream, getSemanticTokensLegend()); + + assertEquals(expectedStyleRanges, styleRanges); + } + + private SemanticTokensLegend getSemanticTokensLegend() { + SemanticTokensLegend semanticTokensLegend = new SemanticTokensLegend(); + semanticTokensLegend.setTokenTypes(Arrays.asList("keyword","other")); + semanticTokensLegend.setTokenModifiers(Arrays.asList("obsolete")); + return semanticTokensLegend; + } +} diff --git a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/semanticTokens/SemanticTokensLegendProviderTest.java b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/semanticTokens/SemanticTokensLegendProviderTest.java index 97268eeee..26f5c1754 100644 --- a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/semanticTokens/SemanticTokensLegendProviderTest.java +++ b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/semanticTokens/SemanticTokensLegendProviderTest.java @@ -1,48 +1,48 @@ -/******************************************************************************* - * Copyright (c) 2022 Avaloq Group AG. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -package org.eclipse.lsp4e.test.semanticTokens; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.lsp4e.LanguageServerWrapper; -import org.eclipse.lsp4e.LanguageServiceAccessor; -import org.eclipse.lsp4e.operations.semanticTokens.SemanticHighlightReconcilerStrategy; -import org.eclipse.lsp4e.test.utils.AbstractTestWithProject; -import org.eclipse.lsp4e.test.utils.TestUtils; -import org.eclipse.lsp4j.SemanticTokensLegend; -import org.junit.Test; - -public class SemanticTokensLegendProviderTest extends AbstractTestWithProject { - - @Test - public void testSemanticTokensLegendProvider() throws CoreException, IOException { - // Setup Server Capabilities - List tokenTypes = Arrays.asList("keyword","other"); - List tokenModifiers = Arrays.asList("obsolete"); - SemanticTokensTestUtil.setSemanticTokensLegend(tokenTypes, tokenModifiers); - - // Setup test data - IFile file = TestUtils.createUniqueTestFile(project, "lspt", "test content"); - // start the LS - LanguageServerWrapper wrapper = LanguageServiceAccessor.getLSWrappers(file, c -> Boolean.TRUE).iterator() - .next(); - - SemanticTokensLegend semanticTokensLegend = new SemanticHighlightReconcilerStrategy().getSemanticTokensLegend(wrapper); - assertNotNull(semanticTokensLegend); - assertEquals(tokenTypes, semanticTokensLegend.getTokenTypes()); - assertEquals(tokenModifiers, semanticTokensLegend.getTokenModifiers()); - } -} +/******************************************************************************* + * Copyright (c) 2022 Avaloq Group AG. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.lsp4e.test.semanticTokens; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.lsp4e.LanguageServerWrapper; +import org.eclipse.lsp4e.LanguageServiceAccessor; +import org.eclipse.lsp4e.operations.semanticTokens.SemanticHighlightReconcilerStrategy; +import org.eclipse.lsp4e.test.utils.AbstractTestWithProject; +import org.eclipse.lsp4e.test.utils.TestUtils; +import org.eclipse.lsp4j.SemanticTokensLegend; +import org.junit.Test; + +public class SemanticTokensLegendProviderTest extends AbstractTestWithProject { + + @Test + public void testSemanticTokensLegendProvider() throws CoreException, IOException { + // Setup Server Capabilities + List tokenTypes = Arrays.asList("keyword","other"); + List tokenModifiers = Arrays.asList("obsolete"); + SemanticTokensTestUtil.setSemanticTokensLegend(tokenTypes, tokenModifiers); + + // Setup test data + IFile file = TestUtils.createUniqueTestFile(project, "lspt", "test content"); + // start the LS + LanguageServerWrapper wrapper = LanguageServiceAccessor.getLSWrappers(file, c -> Boolean.TRUE).iterator() + .next(); + + SemanticTokensLegend semanticTokensLegend = new SemanticHighlightReconcilerStrategy().getSemanticTokensLegend(wrapper); + assertNotNull(semanticTokensLegend); + assertEquals(tokenTypes, semanticTokensLegend.getTokenTypes()); + assertEquals(tokenModifiers, semanticTokensLegend.getTokenModifiers()); + } +} diff --git a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/semanticTokens/StyleRangeHolderTest.java b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/semanticTokens/StyleRangeHolderTest.java index 421304442..9218aaaf1 100644 --- a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/semanticTokens/StyleRangeHolderTest.java +++ b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/semanticTokens/StyleRangeHolderTest.java @@ -1,74 +1,74 @@ -/******************************************************************************* - * Copyright (c) 2022 Avaloq Group AG. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -package org.eclipse.lsp4e.test.semanticTokens; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; - -import java.util.Arrays; -import java.util.List; - -import org.eclipse.jface.text.DocumentEvent; -import org.eclipse.jface.text.Region; -import org.eclipse.jface.text.TextEvent; -import org.eclipse.lsp4e.operations.semanticTokens.StyleRangeHolder; -import org.eclipse.lsp4e.test.utils.AbstractTest; -import org.eclipse.swt.custom.StyleRange; -import org.eclipse.swt.graphics.Color; -import org.junit.Test; - -public class StyleRangeHolderTest extends AbstractTest { - - private static final Color RED = new Color(255, 0, 0); - private List originalStyleRanges = Arrays.asList(new StyleRange(0, 4, RED, null), new StyleRange(15, 4, RED, null), new StyleRange(24, 7, RED, null)); - - @Test - public void testAllDocumentRanges() { - StyleRangeHolder holder = new StyleRangeHolder(); - holder.saveStyles(originalStyleRanges); - - StyleRange[] allDocumentRanges = holder.overlappingRanges(new Region(0, 50)); - - assertNotEquals(originalStyleRanges, allDocumentRanges); // styles must be copied - assertEquals(originalStyleRanges.size(), allDocumentRanges.length); - } - - @Test - public void testPartialDocumentRanges() { - StyleRangeHolder holder = new StyleRangeHolder(); - holder.saveStyles(originalStyleRanges); - - StyleRange[] allDocumentRanges = holder.overlappingRanges(new Region(0, 20)); // only two ranges overlap this region - - assertEquals(2, allDocumentRanges.length); - } - - @Test - public void testDocumentChange() { - StyleRangeHolder holder = new StyleRangeHolder(); - holder.saveStyles(originalStyleRanges); - - TextEvent textEvent = new TextEvent(0, 1, " ", null, new DocumentEvent(), false) {}; - - // this will remove the first style and shift the last two - holder.textChanged(textEvent); - - StyleRange[] noOverlappingRanges = holder.overlappingRanges(new Region(0, 10)); // only one range overlap this region - - assertEquals(0, noOverlappingRanges.length); - - StyleRange[] twoShiftedOverlappingRanges = holder.overlappingRanges(new Region(10, 50)); // only one range overlap this region - - assertEquals(2, twoShiftedOverlappingRanges.length); - assertEquals(16, twoShiftedOverlappingRanges[0].start); - assertEquals(4, twoShiftedOverlappingRanges[0].length); - assertEquals(25, twoShiftedOverlappingRanges[1].start); - assertEquals(7, twoShiftedOverlappingRanges[1].length); - } -} +/******************************************************************************* + * Copyright (c) 2022 Avaloq Group AG. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.lsp4e.test.semanticTokens; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextEvent; +import org.eclipse.lsp4e.operations.semanticTokens.StyleRangeHolder; +import org.eclipse.lsp4e.test.utils.AbstractTest; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.graphics.Color; +import org.junit.Test; + +public class StyleRangeHolderTest extends AbstractTest { + + private static final Color RED = new Color(255, 0, 0); + private List originalStyleRanges = Arrays.asList(new StyleRange(0, 4, RED, null), new StyleRange(15, 4, RED, null), new StyleRange(24, 7, RED, null)); + + @Test + public void testAllDocumentRanges() { + StyleRangeHolder holder = new StyleRangeHolder(); + holder.saveStyles(originalStyleRanges); + + StyleRange[] allDocumentRanges = holder.overlappingRanges(new Region(0, 50)); + + assertNotEquals(originalStyleRanges, allDocumentRanges); // styles must be copied + assertEquals(originalStyleRanges.size(), allDocumentRanges.length); + } + + @Test + public void testPartialDocumentRanges() { + StyleRangeHolder holder = new StyleRangeHolder(); + holder.saveStyles(originalStyleRanges); + + StyleRange[] allDocumentRanges = holder.overlappingRanges(new Region(0, 20)); // only two ranges overlap this region + + assertEquals(2, allDocumentRanges.length); + } + + @Test + public void testDocumentChange() { + StyleRangeHolder holder = new StyleRangeHolder(); + holder.saveStyles(originalStyleRanges); + + TextEvent textEvent = new TextEvent(0, 1, " ", null, new DocumentEvent(), false) {}; + + // this will remove the first style and shift the last two + holder.textChanged(textEvent); + + StyleRange[] noOverlappingRanges = holder.overlappingRanges(new Region(0, 10)); // only one range overlap this region + + assertEquals(0, noOverlappingRanges.length); + + StyleRange[] twoShiftedOverlappingRanges = holder.overlappingRanges(new Region(10, 50)); // only one range overlap this region + + assertEquals(2, twoShiftedOverlappingRanges.length); + assertEquals(16, twoShiftedOverlappingRanges[0].start); + assertEquals(4, twoShiftedOverlappingRanges[0].length); + assertEquals(25, twoShiftedOverlappingRanges[1].start); + assertEquals(7, twoShiftedOverlappingRanges[1].length); + } +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/IMarkerAttributeComputer.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/IMarkerAttributeComputer.java index feb3fe7f3..a99b9bb72 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/IMarkerAttributeComputer.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/IMarkerAttributeComputer.java @@ -1,45 +1,45 @@ -/******************************************************************************* - * Copyright (c) 2016, 2017 Red Hat Inc. and others. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Rubén Porras Campo (Avaloq) - extracted to separate file - *******************************************************************************/ -package org.eclipse.lsp4e; - -import java.util.Map; - -import org.eclipse.core.resources.IResource; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jface.text.IDocument; -import org.eclipse.lsp4j.Diagnostic; - -/** - * An interface that allows adding custom attributes to a - * {@link org.eclipse.core.resources.IMarker}. - * - */ -public interface IMarkerAttributeComputer { - - /** - * Adds new attributes to a marker for the given document, diagnostic and - * resource. - * - * @param diagnostic - * the {@link Diagnostic} to me mapped to a marker - * @param document - * the {@link IDocument} attached to the given resource - * @param resource - * the {@link IResource} that contains the document - * @param attributes - * the map with the attributes for the marker, where the - * implementation can add attributes - */ - public void addMarkerAttributesForDiagnostic(@NonNull Diagnostic diagnostic, @Nullable IDocument document, - @NonNull IResource resource, @NonNull Map attributes); -} +/******************************************************************************* + * Copyright (c) 2016, 2017 Red Hat Inc. and others. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Rubén Porras Campo (Avaloq) - extracted to separate file + *******************************************************************************/ +package org.eclipse.lsp4e; + +import java.util.Map; + +import org.eclipse.core.resources.IResource; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jface.text.IDocument; +import org.eclipse.lsp4j.Diagnostic; + +/** + * An interface that allows adding custom attributes to a + * {@link org.eclipse.core.resources.IMarker}. + * + */ +public interface IMarkerAttributeComputer { + + /** + * Adds new attributes to a marker for the given document, diagnostic and + * resource. + * + * @param diagnostic + * the {@link Diagnostic} to me mapped to a marker + * @param document + * the {@link IDocument} attached to the given resource + * @param resource + * the {@link IResource} that contains the document + * @param attributes + * the map with the attributes for the marker, where the + * implementation can add attributes + */ + public void addMarkerAttributesForDiagnostic(@NonNull Diagnostic diagnostic, @Nullable IDocument document, + @NonNull IResource resource, @NonNull Map attributes); +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyCommandHandler.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyCommandHandler.java index 6ab1c6834..184ee7c04 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyCommandHandler.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyCommandHandler.java @@ -1,52 +1,52 @@ -/******************************************************************************* - * Copyright (c) 2022 Avaloq Group AG (http://www.avaloq.com). - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Andrew Lamb (Avaloq Group AG) - Initial implementation - *******************************************************************************/ -package org.eclipse.lsp4e.callhierarchy; - -import org.eclipse.core.commands.ExecutionEvent; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.ITextSelection; -import org.eclipse.lsp4e.LSPEclipseUtils; -import org.eclipse.lsp4e.LanguageServerPlugin; -import org.eclipse.lsp4e.internal.LSPDocumentAbstractHandler; -import org.eclipse.lsp4e.ui.UI; -import org.eclipse.lsp4j.ServerCapabilities; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.texteditor.ITextEditor; - -/** - * Command handler for the LSP call hierarchy view. - */ -public class CallHierarchyCommandHandler extends LSPDocumentAbstractHandler { - - @Override - protected void execute(ExecutionEvent event, ITextEditor editor) { - IDocument document = LSPEclipseUtils.getDocument(editor); - if (document == null) { - return; - } - if (editor.getSelectionProvider().getSelection() instanceof ITextSelection sel) { - int offset = sel.getOffset(); - - try { - CallHierarchyView theView = UI.showView(CallHierarchyView.ID); - theView.initialize(document, offset); - } catch (PartInitException e) { - LanguageServerPlugin.logError("Error while opening the Call Hierarchy view", e); //$NON-NLS-1$ - } - } - } - - @Override - public void setEnabled(Object evaluationContext) { - setEnabled(ServerCapabilities::getCallHierarchyProvider, this::hasSelection); - } -} +/******************************************************************************* + * Copyright (c) 2022 Avaloq Group AG (http://www.avaloq.com). + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Andrew Lamb (Avaloq Group AG) - Initial implementation + *******************************************************************************/ +package org.eclipse.lsp4e.callhierarchy; + +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.lsp4e.LSPEclipseUtils; +import org.eclipse.lsp4e.LanguageServerPlugin; +import org.eclipse.lsp4e.internal.LSPDocumentAbstractHandler; +import org.eclipse.lsp4e.ui.UI; +import org.eclipse.lsp4j.ServerCapabilities; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.texteditor.ITextEditor; + +/** + * Command handler for the LSP call hierarchy view. + */ +public class CallHierarchyCommandHandler extends LSPDocumentAbstractHandler { + + @Override + protected void execute(ExecutionEvent event, ITextEditor editor) { + IDocument document = LSPEclipseUtils.getDocument(editor); + if (document == null) { + return; + } + if (editor.getSelectionProvider().getSelection() instanceof ITextSelection sel) { + int offset = sel.getOffset(); + + try { + CallHierarchyView theView = UI.showView(CallHierarchyView.ID); + theView.initialize(document, offset); + } catch (PartInitException e) { + LanguageServerPlugin.logError("Error while opening the Call Hierarchy view", e); //$NON-NLS-1$ + } + } + } + + @Override + public void setEnabled(Object evaluationContext) { + setEnabled(ServerCapabilities::getCallHierarchyProvider, this::hasSelection); + } +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyContentProvider.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyContentProvider.java index a5a8dd7dd..fa89e666d 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyContentProvider.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyContentProvider.java @@ -1,205 +1,205 @@ -/******************************************************************************* - * Copyright (c) 2022 Avaloq Group AG (http://www.avaloq.com). - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Andrew Lamb (Avaloq Group AG) - Initial implementation - *******************************************************************************/ - -package org.eclipse.lsp4e.callhierarchy; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.lsp4e.LSPEclipseUtils; -import org.eclipse.lsp4e.LanguageServerWrapper; -import org.eclipse.lsp4e.LanguageServers; -import org.eclipse.lsp4e.LanguageServers.LanguageServerDocumentExecutor; -import org.eclipse.lsp4e.internal.Pair; -import org.eclipse.lsp4e.ui.Messages; -import org.eclipse.lsp4e.ui.views.HierarchyViewInput; -import org.eclipse.lsp4j.CallHierarchyIncomingCall; -import org.eclipse.lsp4j.CallHierarchyIncomingCallsParams; -import org.eclipse.lsp4j.CallHierarchyItem; -import org.eclipse.lsp4j.CallHierarchyPrepareParams; -import org.eclipse.lsp4j.Range; -import org.eclipse.lsp4j.ServerCapabilities; -import org.eclipse.ui.PlatformUI; - -/** - * Content provider for the call hierarchy tree view. - */ -public class CallHierarchyContentProvider implements ITreeContentProvider { - private TreeViewer treeViewer; - private LanguageServerWrapper languageServerWrapper; - private List rootItems; - private String rootMessage = Messages.CH_finding_callers; - - @Override - public Object[] getElements(final Object inputElement) { - if (rootItems != null) { - return rootItems.toArray(); - } - return new Object[] { rootMessage }; - } - - @Override - public Object[] getChildren(final Object parentElement) { - if (parentElement instanceof CallHierarchyViewTreeNode treeNode) { - return findCallers(treeNode); - } else { - return new Object[0]; - } - } - - @Override - public Object getParent(final Object element) { - if (element instanceof CallHierarchyViewTreeNode treeNode) { - return treeNode.getParent(); - } - return null; - } - - @Override - public boolean hasChildren(final Object element) { - return element instanceof CallHierarchyViewTreeNode; - } - - @Override - public void inputChanged(final Viewer viewer, final Object oldInput, final Object newInput) { - ITreeContentProvider.super.inputChanged(viewer, oldInput, newInput); - - treeViewer = (TreeViewer) viewer; - if (newInput instanceof HierarchyViewInput viewInput) { - rootMessage = Messages.CH_finding_callers; - rootItems = null; - - IDocument document = viewInput.getDocument(); - if (document != null) { - try { - initialise(document, viewInput.getOffset()); - } catch (BadLocationException e) { - handleRootError(); - } - } else { - handleRootError(); - } - } else { - handleRootError(); - } - - } - - private void initialise(final @NonNull IDocument document, final int offset) throws BadLocationException { - LanguageServerDocumentExecutor executor = LanguageServers.forDocument(document) - .withCapability(ServerCapabilities::getCallHierarchyProvider); - if (!executor.anyMatching()) { - handleRootError(); - return; - } - CallHierarchyPrepareParams prepareParams = LSPEclipseUtils.toCallHierarchyPrepareParams(offset, document); - executor.computeFirst((w, ls) -> ls.getTextDocumentService().prepareCallHierarchy(prepareParams) - .thenApply(result -> new Pair<>(w, result))).thenAccept(o -> o.ifPresentOrElse(p -> { - languageServerWrapper = p.getFirst(); - List hierarchyItems = p.getSecond(); - if (!hierarchyItems.isEmpty()) { - rootItems = new ArrayList<>(hierarchyItems.size()); - for (CallHierarchyItem item : hierarchyItems) { - rootItems.add(new CallHierarchyViewTreeNode(item)); - } - } else { - rootMessage = Messages.CH_no_call_hierarchy; - } - PlatformUI.getWorkbench().getDisplay().asyncExec(() -> { - if (treeViewer != null) { - treeViewer.refresh(); - treeViewer.expandToLevel(2); - } - }); - }, this::handleRootError)).handle((result, error) -> { - if (error != null) { - handleRootError(); - } - return result; - }); - - } - - private void handleRootError() { - rootItems = Collections.emptyList(); - PlatformUI.getWorkbench().getDisplay().asyncExec(() -> { - if (treeViewer != null) { - treeViewer.refresh(); - } - }); - } - - private Object[] findCallers(final CallHierarchyViewTreeNode callee) { - if (callee.getChildren() == null) { - treeViewer.getControl().setEnabled(false); - updateCallers(callee); - return new Object[] { Messages.CH_finding_callers }; - } - return callee.getChildren(); - } - - private void updateCallers(final CallHierarchyViewTreeNode callee) { - CallHierarchyIncomingCallsParams incomingCallParams = new CallHierarchyIncomingCallsParams( - callee.getCallContainer()); - languageServerWrapper.execute(languageServer -> languageServer.getTextDocumentService() - .callHierarchyIncomingCalls(incomingCallParams)).thenApply(incomingCalls -> { - List children = new ArrayList<>(incomingCalls.size()); - for (CallHierarchyIncomingCall call : incomingCalls) { - CallHierarchyItem callContainer = call.getFrom(); - List callSites = call.getFromRanges(); - for (Range callSite : callSites) { - CallHierarchyViewTreeNode child = new CallHierarchyViewTreeNode(callContainer, callSite); - child.setParent(callee); - children.add(child); - } - if (callSites.isEmpty()) { - CallHierarchyViewTreeNode child = new CallHierarchyViewTreeNode(callContainer); - child.setParent(callee); - children.add(child); - } - } - return children; - }).handle((result, error) -> updateChildrenInView(callee, result, error)); - } - - private List updateChildrenInView(final CallHierarchyViewTreeNode callee, - final List children, final Throwable error) { - if (error != null || children == null) { - callee.setChildren(Collections.emptyList()); - } else { - callee.setChildren(children); - } - PlatformUI.getWorkbench().getDisplay().asyncExec(() -> { - if (treeViewer != null) { - treeViewer.refresh(); - treeViewer.getControl().setEnabled(true); - } - }); - return children; - } - - @Override - public void dispose() { - if (treeViewer != null) { - treeViewer.getControl().dispose(); - treeViewer = null; - } - } - -} +/******************************************************************************* + * Copyright (c) 2022 Avaloq Group AG (http://www.avaloq.com). + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Andrew Lamb (Avaloq Group AG) - Initial implementation + *******************************************************************************/ + +package org.eclipse.lsp4e.callhierarchy; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.lsp4e.LSPEclipseUtils; +import org.eclipse.lsp4e.LanguageServerWrapper; +import org.eclipse.lsp4e.LanguageServers; +import org.eclipse.lsp4e.LanguageServers.LanguageServerDocumentExecutor; +import org.eclipse.lsp4e.internal.Pair; +import org.eclipse.lsp4e.ui.Messages; +import org.eclipse.lsp4e.ui.views.HierarchyViewInput; +import org.eclipse.lsp4j.CallHierarchyIncomingCall; +import org.eclipse.lsp4j.CallHierarchyIncomingCallsParams; +import org.eclipse.lsp4j.CallHierarchyItem; +import org.eclipse.lsp4j.CallHierarchyPrepareParams; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.ServerCapabilities; +import org.eclipse.ui.PlatformUI; + +/** + * Content provider for the call hierarchy tree view. + */ +public class CallHierarchyContentProvider implements ITreeContentProvider { + private TreeViewer treeViewer; + private LanguageServerWrapper languageServerWrapper; + private List rootItems; + private String rootMessage = Messages.CH_finding_callers; + + @Override + public Object[] getElements(final Object inputElement) { + if (rootItems != null) { + return rootItems.toArray(); + } + return new Object[] { rootMessage }; + } + + @Override + public Object[] getChildren(final Object parentElement) { + if (parentElement instanceof CallHierarchyViewTreeNode treeNode) { + return findCallers(treeNode); + } else { + return new Object[0]; + } + } + + @Override + public Object getParent(final Object element) { + if (element instanceof CallHierarchyViewTreeNode treeNode) { + return treeNode.getParent(); + } + return null; + } + + @Override + public boolean hasChildren(final Object element) { + return element instanceof CallHierarchyViewTreeNode; + } + + @Override + public void inputChanged(final Viewer viewer, final Object oldInput, final Object newInput) { + ITreeContentProvider.super.inputChanged(viewer, oldInput, newInput); + + treeViewer = (TreeViewer) viewer; + if (newInput instanceof HierarchyViewInput viewInput) { + rootMessage = Messages.CH_finding_callers; + rootItems = null; + + IDocument document = viewInput.getDocument(); + if (document != null) { + try { + initialise(document, viewInput.getOffset()); + } catch (BadLocationException e) { + handleRootError(); + } + } else { + handleRootError(); + } + } else { + handleRootError(); + } + + } + + private void initialise(final @NonNull IDocument document, final int offset) throws BadLocationException { + LanguageServerDocumentExecutor executor = LanguageServers.forDocument(document) + .withCapability(ServerCapabilities::getCallHierarchyProvider); + if (!executor.anyMatching()) { + handleRootError(); + return; + } + CallHierarchyPrepareParams prepareParams = LSPEclipseUtils.toCallHierarchyPrepareParams(offset, document); + executor.computeFirst((w, ls) -> ls.getTextDocumentService().prepareCallHierarchy(prepareParams) + .thenApply(result -> new Pair<>(w, result))).thenAccept(o -> o.ifPresentOrElse(p -> { + languageServerWrapper = p.getFirst(); + List hierarchyItems = p.getSecond(); + if (!hierarchyItems.isEmpty()) { + rootItems = new ArrayList<>(hierarchyItems.size()); + for (CallHierarchyItem item : hierarchyItems) { + rootItems.add(new CallHierarchyViewTreeNode(item)); + } + } else { + rootMessage = Messages.CH_no_call_hierarchy; + } + PlatformUI.getWorkbench().getDisplay().asyncExec(() -> { + if (treeViewer != null) { + treeViewer.refresh(); + treeViewer.expandToLevel(2); + } + }); + }, this::handleRootError)).handle((result, error) -> { + if (error != null) { + handleRootError(); + } + return result; + }); + + } + + private void handleRootError() { + rootItems = Collections.emptyList(); + PlatformUI.getWorkbench().getDisplay().asyncExec(() -> { + if (treeViewer != null) { + treeViewer.refresh(); + } + }); + } + + private Object[] findCallers(final CallHierarchyViewTreeNode callee) { + if (callee.getChildren() == null) { + treeViewer.getControl().setEnabled(false); + updateCallers(callee); + return new Object[] { Messages.CH_finding_callers }; + } + return callee.getChildren(); + } + + private void updateCallers(final CallHierarchyViewTreeNode callee) { + CallHierarchyIncomingCallsParams incomingCallParams = new CallHierarchyIncomingCallsParams( + callee.getCallContainer()); + languageServerWrapper.execute(languageServer -> languageServer.getTextDocumentService() + .callHierarchyIncomingCalls(incomingCallParams)).thenApply(incomingCalls -> { + List children = new ArrayList<>(incomingCalls.size()); + for (CallHierarchyIncomingCall call : incomingCalls) { + CallHierarchyItem callContainer = call.getFrom(); + List callSites = call.getFromRanges(); + for (Range callSite : callSites) { + CallHierarchyViewTreeNode child = new CallHierarchyViewTreeNode(callContainer, callSite); + child.setParent(callee); + children.add(child); + } + if (callSites.isEmpty()) { + CallHierarchyViewTreeNode child = new CallHierarchyViewTreeNode(callContainer); + child.setParent(callee); + children.add(child); + } + } + return children; + }).handle((result, error) -> updateChildrenInView(callee, result, error)); + } + + private List updateChildrenInView(final CallHierarchyViewTreeNode callee, + final List children, final Throwable error) { + if (error != null || children == null) { + callee.setChildren(Collections.emptyList()); + } else { + callee.setChildren(children); + } + PlatformUI.getWorkbench().getDisplay().asyncExec(() -> { + if (treeViewer != null) { + treeViewer.refresh(); + treeViewer.getControl().setEnabled(true); + } + }); + return children; + } + + @Override + public void dispose() { + if (treeViewer != null) { + treeViewer.getControl().dispose(); + treeViewer = null; + } + } + +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyLabelProvider.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyLabelProvider.java index 385e62a24..37f62088f 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyLabelProvider.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyLabelProvider.java @@ -1,86 +1,86 @@ -/******************************************************************************* - * Copyright (c) 2022 Avaloq Group AG (http://www.avaloq.com). - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Andrew Lamb (Avaloq Group AG) - Initial implementation - *******************************************************************************/ - -package org.eclipse.lsp4e.callhierarchy; - -import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider; -import org.eclipse.jface.viewers.LabelProvider; -import org.eclipse.jface.viewers.StyledString; -import org.eclipse.lsp4e.ui.LSPImages; -import org.eclipse.lsp4j.CallHierarchyItem; -import org.eclipse.swt.graphics.Image; - -/** - * Label provider for the call hierarchy tree view. - */ -public class CallHierarchyLabelProvider extends LabelProvider implements IStyledLabelProvider { - - @Override - public Image getImage(final Object element) { - if (element instanceof CallHierarchyViewTreeNode treeNode) { - CallHierarchyItem callContainer = treeNode.getCallContainer(); - Image res = LSPImages.imageFromSymbolKind(callContainer.getKind()); - if (res != null) { - return res; - } - } - return super.getImage(element); - } - - @Override - public StyledString getStyledText(final Object element) { - if (element instanceof CallHierarchyViewTreeNode treeNode) { - CallHierarchyItem callContainer = treeNode.getCallContainer(); - StyledString styledString = new StyledString(); - appendName(styledString, callContainer.getName()); - if (callContainer.getDetail() != null) { - appendDetail(styledString, callContainer.getDetail()); - } - return styledString; - } else if (element instanceof String s) { - return new StyledString(s); - } - return null; - } - - /** - * Append the given call container name to the given styled string. - * - * @param styledString - * the styled string to append to. - * @param name - * the call container name to append. - */ - protected void appendName(final StyledString styledString, final String name) { - // This implementation is specific to the way the symbol detail is encoded in - // the call container name (following VSCode as defacto standard). - int colon = name.lastIndexOf(':'); - if (colon >= 0) { - styledString.append(name.substring(0, colon)); - styledString.append(name.substring(colon), StyledString.DECORATIONS_STYLER); - } else { - styledString.append(name); - } - } - - /** - * Append the given call container detail to the given styled string. - * - * @param styledString - * the styled string to append to. - * @param detail - * the call container detail to append. - */ - protected void appendDetail(final StyledString styledString, final String detail) { - styledString.append(detail, StyledString.QUALIFIER_STYLER); - } -} +/******************************************************************************* + * Copyright (c) 2022 Avaloq Group AG (http://www.avaloq.com). + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Andrew Lamb (Avaloq Group AG) - Initial implementation + *******************************************************************************/ + +package org.eclipse.lsp4e.callhierarchy; + +import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.lsp4e.ui.LSPImages; +import org.eclipse.lsp4j.CallHierarchyItem; +import org.eclipse.swt.graphics.Image; + +/** + * Label provider for the call hierarchy tree view. + */ +public class CallHierarchyLabelProvider extends LabelProvider implements IStyledLabelProvider { + + @Override + public Image getImage(final Object element) { + if (element instanceof CallHierarchyViewTreeNode treeNode) { + CallHierarchyItem callContainer = treeNode.getCallContainer(); + Image res = LSPImages.imageFromSymbolKind(callContainer.getKind()); + if (res != null) { + return res; + } + } + return super.getImage(element); + } + + @Override + public StyledString getStyledText(final Object element) { + if (element instanceof CallHierarchyViewTreeNode treeNode) { + CallHierarchyItem callContainer = treeNode.getCallContainer(); + StyledString styledString = new StyledString(); + appendName(styledString, callContainer.getName()); + if (callContainer.getDetail() != null) { + appendDetail(styledString, callContainer.getDetail()); + } + return styledString; + } else if (element instanceof String s) { + return new StyledString(s); + } + return null; + } + + /** + * Append the given call container name to the given styled string. + * + * @param styledString + * the styled string to append to. + * @param name + * the call container name to append. + */ + protected void appendName(final StyledString styledString, final String name) { + // This implementation is specific to the way the symbol detail is encoded in + // the call container name (following VSCode as defacto standard). + int colon = name.lastIndexOf(':'); + if (colon >= 0) { + styledString.append(name.substring(0, colon)); + styledString.append(name.substring(colon), StyledString.DECORATIONS_STYLER); + } else { + styledString.append(name); + } + } + + /** + * Append the given call container detail to the given styled string. + * + * @param styledString + * the styled string to append to. + * @param detail + * the call container detail to append. + */ + protected void appendDetail(final StyledString styledString, final String detail) { + styledString.append(detail, StyledString.QUALIFIER_STYLER); + } +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyView.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyView.java index 8d333ca20..6bbe5e750 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyView.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyView.java @@ -1,81 +1,81 @@ -/******************************************************************************* - * Copyright (c) 2022 Avaloq Group AG (http://www.avaloq.com). - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Andrew Lamb (Avaloq Group AG) - Initial implementation - *******************************************************************************/ - -package org.eclipse.lsp4e.callhierarchy; - -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider; -import org.eclipse.jface.viewers.DoubleClickEvent; -import org.eclipse.jface.viewers.IDoubleClickListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.lsp4e.LSPEclipseUtils; -import org.eclipse.lsp4e.ui.views.HierarchyViewInput; -import org.eclipse.lsp4j.CallHierarchyItem; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.ui.part.ViewPart; - -/** - * An LSP based CallHierarchyView. - */ -public class CallHierarchyView extends ViewPart { - public static final String ID = "org.eclipse.lsp4e.callHierarchy.callHierarchyView"; //$NON-NLS-1$ - - protected TreeViewer treeViewer; - - private final CallHierarchyContentProvider contentProvider = new CallHierarchyContentProvider(); - - @Override - public void createPartControl(final Composite parent) { - // Create the tree viewer as a child of the composite parent - treeViewer = new TreeViewer(parent); - treeViewer.setContentProvider(contentProvider); - - treeViewer.setLabelProvider(new DelegatingStyledCellLabelProvider(new CallHierarchyLabelProvider())); - - treeViewer.setUseHashlookup(true); - treeViewer.getControl().setEnabled(false); - treeViewer.addDoubleClickListener(new IDoubleClickListener() { - - @SuppressWarnings("unchecked") - @Override - public void doubleClick(final DoubleClickEvent event) { - if (event.getSelection() instanceof IStructuredSelection structuredSelection) { - structuredSelection.iterator().forEachRemaining(selectedObject -> { - if (selectedObject instanceof CallHierarchyViewTreeNode selectedNode) { - CallHierarchyItem callContainer = selectedNode.getCallContainer(); - LSPEclipseUtils.open(callContainer.getUri(), selectedNode.getSelectionRange()); - } - }); - } - } - }); - } - - @Override - public void setFocus() { - treeViewer.getControl().setFocus(); - } - - /** - * Initialise this view with the call hierarchy for the specified selection. - * - * @param document - * the document containing the current selection. - * @param offset - * the offset into the document of the current selection. - */ - public void initialize(final IDocument document, final int offset) { - HierarchyViewInput viewInput = new HierarchyViewInput(document, offset); - treeViewer.setInput(viewInput); - } -} +/******************************************************************************* + * Copyright (c) 2022 Avaloq Group AG (http://www.avaloq.com). + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Andrew Lamb (Avaloq Group AG) - Initial implementation + *******************************************************************************/ + +package org.eclipse.lsp4e.callhierarchy; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.lsp4e.LSPEclipseUtils; +import org.eclipse.lsp4e.ui.views.HierarchyViewInput; +import org.eclipse.lsp4j.CallHierarchyItem; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.part.ViewPart; + +/** + * An LSP based CallHierarchyView. + */ +public class CallHierarchyView extends ViewPart { + public static final String ID = "org.eclipse.lsp4e.callHierarchy.callHierarchyView"; //$NON-NLS-1$ + + protected TreeViewer treeViewer; + + private final CallHierarchyContentProvider contentProvider = new CallHierarchyContentProvider(); + + @Override + public void createPartControl(final Composite parent) { + // Create the tree viewer as a child of the composite parent + treeViewer = new TreeViewer(parent); + treeViewer.setContentProvider(contentProvider); + + treeViewer.setLabelProvider(new DelegatingStyledCellLabelProvider(new CallHierarchyLabelProvider())); + + treeViewer.setUseHashlookup(true); + treeViewer.getControl().setEnabled(false); + treeViewer.addDoubleClickListener(new IDoubleClickListener() { + + @SuppressWarnings("unchecked") + @Override + public void doubleClick(final DoubleClickEvent event) { + if (event.getSelection() instanceof IStructuredSelection structuredSelection) { + structuredSelection.iterator().forEachRemaining(selectedObject -> { + if (selectedObject instanceof CallHierarchyViewTreeNode selectedNode) { + CallHierarchyItem callContainer = selectedNode.getCallContainer(); + LSPEclipseUtils.open(callContainer.getUri(), selectedNode.getSelectionRange()); + } + }); + } + } + }); + } + + @Override + public void setFocus() { + treeViewer.getControl().setFocus(); + } + + /** + * Initialise this view with the call hierarchy for the specified selection. + * + * @param document + * the document containing the current selection. + * @param offset + * the offset into the document of the current selection. + */ + public void initialize(final IDocument document, final int offset) { + HierarchyViewInput viewInput = new HierarchyViewInput(document, offset); + treeViewer.setInput(viewInput); + } +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyViewTreeNode.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyViewTreeNode.java index de243cbc2..34ba1da0d 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyViewTreeNode.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyViewTreeNode.java @@ -1,149 +1,149 @@ -/******************************************************************************* - * Copyright (c) 2022 Avaloq Group AG (http://www.avaloq.com). - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Andrew Lamb (Avaloq Group AG) - Initial implementation - *******************************************************************************/ - -package org.eclipse.lsp4e.callhierarchy; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.lsp4j.CallHierarchyItem; -import org.eclipse.lsp4j.Range; - -/** - * Class representing a node in the call hierarchy tree. - */ -public class CallHierarchyViewTreeNode { - - /** - * the {@link CallHierarchyItem} of the callable containing the call site that - * this node represents. - */ - private final @NonNull CallHierarchyItem callContainer; - - /** the location of the call site that this node represents. */ - private final @Nullable Range callSite; - - private @Nullable CallHierarchyViewTreeNode parent; - private @Nullable CallHierarchyViewTreeNode[] children; - - /** - * Creates a new instance of {@link CallHierarchyViewTreeNode}. - * - * @param callContainer - * the {@link CallHierarchyItem} of the callable containing the call. - * @param callSite - * the range in the callable of the call site. - */ - public CallHierarchyViewTreeNode(final @NonNull CallHierarchyItem callContainer, final @Nullable Range callSite) { - this.callContainer = callContainer; - this.callSite = callSite; - } - - /** - * Creates a new instance of {@link CallHierarchyViewTreeNode}. - * - * @param callContainer - * the {@link CallHierarchyItem} of the callable containing the call. - */ - public CallHierarchyViewTreeNode(final @NonNull CallHierarchyItem callContainer) { - this.callContainer = callContainer; - this.callSite = null; - } - - /** - * Get the parent of this node. - * - * @return the parent node. - */ - public @Nullable CallHierarchyViewTreeNode getParent() { - return parent; - } - - /** - * Set the parent for this node. - * - * @param parent - * The new parent. - */ - public void setParent(final CallHierarchyViewTreeNode parent) { - this.parent = parent; - } - - /** - * Get all the children of this node. - * - * @return this node's children. - */ - public @Nullable CallHierarchyViewTreeNode[] getChildren() { - return children; - } - - /** - * Set the children for this node. - * - * @param children - * the new children. - */ - public void setChildren(final @NonNull List children) { - this.children = children.toArray(new CallHierarchyViewTreeNode[children.size()]); - } - - /** - * Get the {@link CallHierarchyItem} describing the object containing the call - * site that this node represents. - * - * @return the containing {@link CallHierarchyItem}. - */ - public @NonNull CallHierarchyItem getCallContainer() { - return callContainer; - } - - /** - * Get the {@link Range} describing the call site within the call container. - * - * @return the call site {@link Range} - */ - public @Nullable Range getCallSite() { - return callSite; - } - - /** - * Get the {@link Range} to select when this node is double clicked. - * - * @return the selection range of this node. - */ - public @NonNull Range getSelectionRange() { - Range theCallSite = callSite; - if (theCallSite != null) { - return theCallSite; - } - return callContainer.getSelectionRange(); - } - - /** - * Determines if the container represents a recursion call (i.e. whether the - * call is already in the tree.) - * - * @return True if the call is part of a recursion - */ - public boolean isRecursive() { - String uri = callContainer.getUri(); - for (CallHierarchyViewTreeNode ancestor = parent; ancestor != null; ancestor = ancestor.getParent()) { - if (uri.equals(ancestor.getCallContainer().getUri())) { - return true; - } - } - return false; - } - -} +/******************************************************************************* + * Copyright (c) 2022 Avaloq Group AG (http://www.avaloq.com). + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Andrew Lamb (Avaloq Group AG) - Initial implementation + *******************************************************************************/ + +package org.eclipse.lsp4e.callhierarchy; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.lsp4j.CallHierarchyItem; +import org.eclipse.lsp4j.Range; + +/** + * Class representing a node in the call hierarchy tree. + */ +public class CallHierarchyViewTreeNode { + + /** + * the {@link CallHierarchyItem} of the callable containing the call site that + * this node represents. + */ + private final @NonNull CallHierarchyItem callContainer; + + /** the location of the call site that this node represents. */ + private final @Nullable Range callSite; + + private @Nullable CallHierarchyViewTreeNode parent; + private @Nullable CallHierarchyViewTreeNode[] children; + + /** + * Creates a new instance of {@link CallHierarchyViewTreeNode}. + * + * @param callContainer + * the {@link CallHierarchyItem} of the callable containing the call. + * @param callSite + * the range in the callable of the call site. + */ + public CallHierarchyViewTreeNode(final @NonNull CallHierarchyItem callContainer, final @Nullable Range callSite) { + this.callContainer = callContainer; + this.callSite = callSite; + } + + /** + * Creates a new instance of {@link CallHierarchyViewTreeNode}. + * + * @param callContainer + * the {@link CallHierarchyItem} of the callable containing the call. + */ + public CallHierarchyViewTreeNode(final @NonNull CallHierarchyItem callContainer) { + this.callContainer = callContainer; + this.callSite = null; + } + + /** + * Get the parent of this node. + * + * @return the parent node. + */ + public @Nullable CallHierarchyViewTreeNode getParent() { + return parent; + } + + /** + * Set the parent for this node. + * + * @param parent + * The new parent. + */ + public void setParent(final CallHierarchyViewTreeNode parent) { + this.parent = parent; + } + + /** + * Get all the children of this node. + * + * @return this node's children. + */ + public @Nullable CallHierarchyViewTreeNode[] getChildren() { + return children; + } + + /** + * Set the children for this node. + * + * @param children + * the new children. + */ + public void setChildren(final @NonNull List children) { + this.children = children.toArray(new CallHierarchyViewTreeNode[children.size()]); + } + + /** + * Get the {@link CallHierarchyItem} describing the object containing the call + * site that this node represents. + * + * @return the containing {@link CallHierarchyItem}. + */ + public @NonNull CallHierarchyItem getCallContainer() { + return callContainer; + } + + /** + * Get the {@link Range} describing the call site within the call container. + * + * @return the call site {@link Range} + */ + public @Nullable Range getCallSite() { + return callSite; + } + + /** + * Get the {@link Range} to select when this node is double clicked. + * + * @return the selection range of this node. + */ + public @NonNull Range getSelectionRange() { + Range theCallSite = callSite; + if (theCallSite != null) { + return theCallSite; + } + return callContainer.getSelectionRange(); + } + + /** + * Determines if the container represents a recursion call (i.e. whether the + * call is already in the tree.) + * + * @return True if the call is part of a recursion + */ + public boolean isRecursive() { + String uri = callContainer.getUri(); + for (CallHierarchyViewTreeNode ancestor = parent; ancestor != null; ancestor = ancestor.getParent()) { + if (uri.equals(ancestor.getCallContainer().getUri())) { + return true; + } + } + return false; + } + +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/CancellationSupport.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/CancellationSupport.java index 6c00cd7e1..8b9ccfb55 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/CancellationSupport.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/CancellationSupport.java @@ -1,71 +1,71 @@ -/******************************************************************************* - * Copyright (c) 2303 Red Hat Inc. and others. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Angelo ZERR (Red Hat Inc.) - initial implementation - *******************************************************************************/ -package org.eclipse.lsp4e.internal; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; - -import org.eclipse.lsp4j.jsonrpc.CancelChecker; - -/** - * LSP cancellation support hosts the list of LSP requests to cancel when a - * process is canceled (ex: when completion is re-triggered, when hover is give - * up, etc) - * - * @see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#cancelRequest - */ -public class CancellationSupport implements CancelChecker { - - private final List> futuresToCancel; - - private boolean cancelled; - - public CancellationSupport() { - this.futuresToCancel = new ArrayList>(); - this.cancelled = false; - } - - public CompletableFuture execute(CompletableFuture future) { - this.futuresToCancel.add(future); - return future; - } - - /** - * Cancel all LSP requests. - */ - public void cancel() { - this.cancelled = true; - for (CompletableFuture futureToCancel : futuresToCancel) { - if (!futureToCancel.isDone()) { - futureToCancel.cancel(true); - } - } - futuresToCancel.clear(); - } - - @Override - public void checkCanceled() { - // When LSP requests are called (ex : 'textDocument/completion') the LSP - // response - // items are used to compose some UI item (ex : LSP CompletionItem are translate - // to Eclipse ICompletionProposal). - // If the cancel occurs after the call of those LSP requests, the component - // which uses the LSP responses - // can call checkCanceled to stop the UI creation. - if (cancelled) { - throw new CancellationException(); - } - } -} +/******************************************************************************* + * Copyright (c) 2303 Red Hat Inc. and others. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Angelo ZERR (Red Hat Inc.) - initial implementation + *******************************************************************************/ +package org.eclipse.lsp4e.internal; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.lsp4j.jsonrpc.CancelChecker; + +/** + * LSP cancellation support hosts the list of LSP requests to cancel when a + * process is canceled (ex: when completion is re-triggered, when hover is give + * up, etc) + * + * @see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#cancelRequest + */ +public class CancellationSupport implements CancelChecker { + + private final List> futuresToCancel; + + private boolean cancelled; + + public CancellationSupport() { + this.futuresToCancel = new ArrayList>(); + this.cancelled = false; + } + + public CompletableFuture execute(CompletableFuture future) { + this.futuresToCancel.add(future); + return future; + } + + /** + * Cancel all LSP requests. + */ + public void cancel() { + this.cancelled = true; + for (CompletableFuture futureToCancel : futuresToCancel) { + if (!futureToCancel.isDone()) { + futureToCancel.cancel(true); + } + } + futuresToCancel.clear(); + } + + @Override + public void checkCanceled() { + // When LSP requests are called (ex : 'textDocument/completion') the LSP + // response + // items are used to compose some UI item (ex : LSP CompletionItem are translate + // to Eclipse ICompletionProposal). + // If the cancel occurs after the call of those LSP requests, the component + // which uses the LSP responses + // can call checkCanceled to stop the UI creation. + if (cancelled) { + throw new CancellationException(); + } + } +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/CancellationUtil.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/CancellationUtil.java index b9141f154..a644cbb89 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/CancellationUtil.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/CancellationUtil.java @@ -1,44 +1,44 @@ -/******************************************************************************* - * Copyright (c) 2023 Avaloq Group AG. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Rubén Porras Campo (Avaloq Group AG) - Initial Implementation - *******************************************************************************/ -package org.eclipse.lsp4e.internal; - -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletionException; -import java.util.concurrent.ExecutionException; - -import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; -import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; -import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode; - -public final class CancellationUtil { - - private CancellationUtil() { - // this class shouldn't be instantiated - } - - public static boolean isRequestCancelledException(Throwable throwable) { - if (throwable instanceof CompletionException || throwable instanceof ExecutionException) { - throwable = throwable.getCause(); - } - if (throwable instanceof ResponseErrorException responseErrorException) { - return isRequestCancelled(responseErrorException); - } - return throwable instanceof CancellationException; - } - - private static boolean isRequestCancelled(ResponseErrorException responseErrorException) { - ResponseError responseError = responseErrorException.getResponseError(); - return responseError != null - && responseError.getCode() == ResponseErrorCode.RequestCancelled.getValue(); - } - -} +/******************************************************************************* + * Copyright (c) 2023 Avaloq Group AG. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Rubén Porras Campo (Avaloq Group AG) - Initial Implementation + *******************************************************************************/ +package org.eclipse.lsp4e.internal; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; + +import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; +import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; +import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode; + +public final class CancellationUtil { + + private CancellationUtil() { + // this class shouldn't be instantiated + } + + public static boolean isRequestCancelledException(Throwable throwable) { + if (throwable instanceof CompletionException || throwable instanceof ExecutionException) { + throwable = throwable.getCause(); + } + if (throwable instanceof ResponseErrorException responseErrorException) { + return isRequestCancelled(responseErrorException); + } + return throwable instanceof CancellationException; + } + + private static boolean isRequestCancelled(ResponseErrorException responseErrorException) { + ResponseError responseError = responseErrorException.getResponseError(); + return responseError != null + && responseError.getCode() == ResponseErrorCode.RequestCancelled.getValue(); + } + +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/DocumentUtil.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/DocumentUtil.java index a52168335..bc7a34b24 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/DocumentUtil.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/DocumentUtil.java @@ -1,48 +1,48 @@ -/******************************************************************************* - * Copyright (c) 2023 Avaloq Group AG. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Rubén Porras Campo (Avaloq Group AG) - Initial Implementation - *******************************************************************************/ -package org.eclipse.lsp4e.internal; - -import org.eclipse.core.resources.IFile; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.IDocumentExtension4; -import org.eclipse.lsp4e.LSPEclipseUtils; - -public final class DocumentUtil { - - private DocumentUtil() { - // this class shouldn't be instantiated - } - - /** - * Gets the modification stamp for the supplied document, or returns - * {@code IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP} if not available. - * - * In practice just a sanity-checked downcast of a legacy API: should expect the platform to be instantiating - * Documents that implement the later interfaces. - * - * @param document Document to check - * @return Opaque version stamp, or {@code IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP} if not available - */ - public static long getDocumentModificationStamp(@Nullable IDocument document) { - if (document instanceof IDocumentExtension4 ext) { - return ext.getModificationStamp(); - } else if (document != null){ - IFile file = LSPEclipseUtils.getFile(document); - if (file != null) { - return file.getModificationStamp(); - } - } - return IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; - } - -} +/******************************************************************************* + * Copyright (c) 2023 Avaloq Group AG. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Rubén Porras Campo (Avaloq Group AG) - Initial Implementation + *******************************************************************************/ +package org.eclipse.lsp4e.internal; + +import org.eclipse.core.resources.IFile; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IDocumentExtension4; +import org.eclipse.lsp4e.LSPEclipseUtils; + +public final class DocumentUtil { + + private DocumentUtil() { + // this class shouldn't be instantiated + } + + /** + * Gets the modification stamp for the supplied document, or returns + * {@code IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP} if not available. + * + * In practice just a sanity-checked downcast of a legacy API: should expect the platform to be instantiating + * Documents that implement the later interfaces. + * + * @param document Document to check + * @return Opaque version stamp, or {@code IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP} if not available + */ + public static long getDocumentModificationStamp(@Nullable IDocument document) { + if (document instanceof IDocumentExtension4 ext) { + return ext.getModificationStamp(); + } else if (document != null){ + IFile file = LSPEclipseUtils.getFile(document); + if (file != null) { + return file.getModificationStamp(); + } + } + return IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; + } + +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/LSPDocumentAbstractHandler.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/LSPDocumentAbstractHandler.java index 25b38a5ba..71a146a5a 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/LSPDocumentAbstractHandler.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/LSPDocumentAbstractHandler.java @@ -1,130 +1,130 @@ -/******************************************************************************* - * Copyright (c) 2023 Avaloq Group AG. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Rubén Porras Campo (Avaloq Group AG) - initial implementation - * - *******************************************************************************/ -package org.eclipse.lsp4e.internal; - -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.function.Function; -import java.util.function.Predicate; - -import org.eclipse.core.commands.AbstractHandler; -import org.eclipse.core.commands.ExecutionEvent; -import org.eclipse.core.commands.ExecutionException; -import org.eclipse.core.commands.HandlerEvent; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.ITextSelection; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.ISelectionProvider; -import org.eclipse.lsp4e.LSPEclipseUtils; -import org.eclipse.lsp4e.LanguageServerPlugin; -import org.eclipse.lsp4e.LanguageServerWrapper; -import org.eclipse.lsp4e.LanguageServers.LanguageServerDocumentExecutor; -import org.eclipse.lsp4e.ui.UI; -import org.eclipse.lsp4j.ServerCapabilities; -import org.eclipse.lsp4j.jsonrpc.messages.Either; -import org.eclipse.ui.handlers.HandlerUtil; -import org.eclipse.ui.texteditor.ITextEditor; - -public abstract class LSPDocumentAbstractHandler extends AbstractHandler { - - private static class LanguageServerDocumentHandlerExecutor extends LanguageServerDocumentExecutor { - LanguageServerDocumentHandlerExecutor(@NonNull IDocument document) { - super(document); - } - - /** - * Waits if necessary for at most 50 milliseconds for getting a server, if that - * is not enough: - *
  • It is assumed that the server would not be matching. - *
  • The server is get asynchronously and then a runner will be called if - * the call completes with true as final result. - * - * @return True if there is a language server for this document & server - * capabilities. - */ - boolean anyMatching(Runnable runner) { - return getServers().stream().filter(wF -> matches(wF, runner)).findFirst().isPresent(); - } - - @Override - public @NonNull LanguageServerDocumentHandlerExecutor withFilter(final @NonNull Predicate filter) { - return (LanguageServerDocumentHandlerExecutor) super.withFilter(filter); - } - - Boolean matches(@NonNull CompletableFuture<@Nullable LanguageServerWrapper> wrapperFuture, Runnable runner) { - try { - return wrapperFuture.thenApply(Objects::nonNull).get(50, TimeUnit.MILLISECONDS); - } catch (java.util.concurrent.ExecutionException e) { - LanguageServerPlugin.logError(e); - } catch (InterruptedException e) { - LanguageServerPlugin.logError(e); - Thread.currentThread().interrupt(); - } catch (TimeoutException e) { - wrapperFuture.thenAcceptAsync(w -> { - if (w != null) { - runner.run(); - } - }); - return Boolean.FALSE; - } - return Boolean.FALSE; - } - } - - @Override - public Object execute(ExecutionEvent event) throws ExecutionException { - Optional.ofNullable(UI.asTextEditor(HandlerUtil.getActiveEditor(event))) - .ifPresent(textEditor -> execute(event, textEditor)); - return null; - } - - /** - * Intended to be implemented by sub-classes which work on the text editor. This - * method is only called if an {@link ITextEditor} can be obtained. Sub-classes - * may still override {@link #execute(ExecutionEvent)} for custom behavior. - * - * @param event - * @param textEditor - */ - protected abstract void execute(ExecutionEvent event, ITextEditor textEditor); - - protected boolean hasSelection(ITextEditor textEditor) { - ISelectionProvider provider = textEditor.getSelectionProvider(); - ISelection selection = provider == null ? null : provider.getSelection(); - return selection instanceof ITextSelection && !selection.isEmpty(); - } - - protected void setEnabled(final @NonNull Function> serverCapabilities, Predicate condition) { - Predicate filter = f -> LSPEclipseUtils.hasCapability(serverCapabilities.apply(f)); - setEnabled(filter, condition); - } - - protected void setEnabled(final @NonNull Predicate filter, Predicate condition) { - ITextEditor textEditor = UI.getActiveTextEditor(); - if (textEditor != null && condition.test(textEditor)) { - IDocument document = LSPEclipseUtils.getDocument(textEditor); - if (document != null) { - setBaseEnabled(new LanguageServerDocumentHandlerExecutor(document) - .withFilter(filter) - .anyMatching(() -> fireHandlerChanged(new HandlerEvent(this, true, false)))); - return; - } - } - setBaseEnabled(false); - } -} +/******************************************************************************* + * Copyright (c) 2023 Avaloq Group AG. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Rubén Porras Campo (Avaloq Group AG) - initial implementation + * + *******************************************************************************/ +package org.eclipse.lsp4e.internal; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.HandlerEvent; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.lsp4e.LSPEclipseUtils; +import org.eclipse.lsp4e.LanguageServerPlugin; +import org.eclipse.lsp4e.LanguageServerWrapper; +import org.eclipse.lsp4e.LanguageServers.LanguageServerDocumentExecutor; +import org.eclipse.lsp4e.ui.UI; +import org.eclipse.lsp4j.ServerCapabilities; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eclipse.ui.texteditor.ITextEditor; + +public abstract class LSPDocumentAbstractHandler extends AbstractHandler { + + private static class LanguageServerDocumentHandlerExecutor extends LanguageServerDocumentExecutor { + LanguageServerDocumentHandlerExecutor(@NonNull IDocument document) { + super(document); + } + + /** + * Waits if necessary for at most 50 milliseconds for getting a server, if that + * is not enough: + *
  • It is assumed that the server would not be matching. + *
  • The server is get asynchronously and then a runner will be called if + * the call completes with true as final result. + * + * @return True if there is a language server for this document & server + * capabilities. + */ + boolean anyMatching(Runnable runner) { + return getServers().stream().filter(wF -> matches(wF, runner)).findFirst().isPresent(); + } + + @Override + public @NonNull LanguageServerDocumentHandlerExecutor withFilter(final @NonNull Predicate filter) { + return (LanguageServerDocumentHandlerExecutor) super.withFilter(filter); + } + + Boolean matches(@NonNull CompletableFuture<@Nullable LanguageServerWrapper> wrapperFuture, Runnable runner) { + try { + return wrapperFuture.thenApply(Objects::nonNull).get(50, TimeUnit.MILLISECONDS); + } catch (java.util.concurrent.ExecutionException e) { + LanguageServerPlugin.logError(e); + } catch (InterruptedException e) { + LanguageServerPlugin.logError(e); + Thread.currentThread().interrupt(); + } catch (TimeoutException e) { + wrapperFuture.thenAcceptAsync(w -> { + if (w != null) { + runner.run(); + } + }); + return Boolean.FALSE; + } + return Boolean.FALSE; + } + } + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + Optional.ofNullable(UI.asTextEditor(HandlerUtil.getActiveEditor(event))) + .ifPresent(textEditor -> execute(event, textEditor)); + return null; + } + + /** + * Intended to be implemented by sub-classes which work on the text editor. This + * method is only called if an {@link ITextEditor} can be obtained. Sub-classes + * may still override {@link #execute(ExecutionEvent)} for custom behavior. + * + * @param event + * @param textEditor + */ + protected abstract void execute(ExecutionEvent event, ITextEditor textEditor); + + protected boolean hasSelection(ITextEditor textEditor) { + ISelectionProvider provider = textEditor.getSelectionProvider(); + ISelection selection = provider == null ? null : provider.getSelection(); + return selection instanceof ITextSelection && !selection.isEmpty(); + } + + protected void setEnabled(final @NonNull Function> serverCapabilities, Predicate condition) { + Predicate filter = f -> LSPEclipseUtils.hasCapability(serverCapabilities.apply(f)); + setEnabled(filter, condition); + } + + protected void setEnabled(final @NonNull Predicate filter, Predicate condition) { + ITextEditor textEditor = UI.getActiveTextEditor(); + if (textEditor != null && condition.test(textEditor)) { + IDocument document = LSPEclipseUtils.getDocument(textEditor); + if (document != null) { + setBaseEnabled(new LanguageServerDocumentHandlerExecutor(document) + .withFilter(filter) + .anyMatching(() -> fireHandlerChanged(new HandlerEvent(this, true, false)))); + return; + } + } + setBaseEnabled(false); + } +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/Pair.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/Pair.java index d8f1be0a4..ccb420373 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/Pair.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/Pair.java @@ -1,53 +1,53 @@ -/******************************************************************************* - * Copyright (c) 2023 Avaloq Group AG. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Rubén Porras Campo (Avaloq Group AG) - Initial Implementation - *******************************************************************************/ -package org.eclipse.lsp4e.internal; - -import java.util.Objects; - -public final class Pair { - private final F first; - private final S second; - - public Pair(F first, S second) { - this.first = first; - this.second = second; - } - - public S getSecond() { - return second; - } - - public F getFirst() { - return first; - } - - @Override - public int hashCode() { - return Objects.hash(first, second); - } - - public static Pair of(F first, S second) { - return new Pair(first, second); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Pair other = (Pair) obj; - return Objects.equals(first, other.first) && Objects.equals(second, other.second); - } -} +/******************************************************************************* + * Copyright (c) 2023 Avaloq Group AG. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Rubén Porras Campo (Avaloq Group AG) - Initial Implementation + *******************************************************************************/ +package org.eclipse.lsp4e.internal; + +import java.util.Objects; + +public final class Pair { + private final F first; + private final S second; + + public Pair(F first, S second) { + this.first = first; + this.second = second; + } + + public S getSecond() { + return second; + } + + public F getFirst() { + return first; + } + + @Override + public int hashCode() { + return Objects.hash(first, second); + } + + public static Pair of(F first, S second) { + return new Pair(first, second); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Pair other = (Pair) obj; + return Objects.equals(first, other.first) && Objects.equals(second, other.second); + } +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/StyleUtil.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/StyleUtil.java index d7fc0a4ed..88f7f0935 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/StyleUtil.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/StyleUtil.java @@ -1,29 +1,29 @@ -/******************************************************************************* - * Copyright (c) 2023 Avaloq Group AG. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Rubén Porras Campo (Avaloq Group AG) - Initial Implementation - *******************************************************************************/ -package org.eclipse.lsp4e.internal; - -import org.eclipse.jface.viewers.StyledString.Styler; -import org.eclipse.swt.graphics.TextStyle; - -public class StyleUtil { - - private StyleUtil() { - // this class shouldn't be instantiated - } - - public static final Styler DEPRECATE = new Styler() { - @Override - public void applyStyles(TextStyle textStyle) { - textStyle.strikeout = true; - }; - }; -} +/******************************************************************************* + * Copyright (c) 2023 Avaloq Group AG. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Rubén Porras Campo (Avaloq Group AG) - Initial Implementation + *******************************************************************************/ +package org.eclipse.lsp4e.internal; + +import org.eclipse.jface.viewers.StyledString.Styler; +import org.eclipse.swt.graphics.TextStyle; + +public class StyleUtil { + + private StyleUtil() { + // this class shouldn't be instantiated + } + + public static final Styler DEPRECATE = new Styler() { + @Override + public void applyStyles(TextStyle textStyle) { + textStyle.strikeout = true; + }; + }; +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/documentLink/LSPDocumentLinkPresentationReconcilingStrategy.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/documentLink/LSPDocumentLinkPresentationReconcilingStrategy.java index 6d45e4e91..b9c449830 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/documentLink/LSPDocumentLinkPresentationReconcilingStrategy.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/documentLink/LSPDocumentLinkPresentationReconcilingStrategy.java @@ -1,171 +1,171 @@ -/******************************************************************************* - * Copyright (c) 2022 Red Hat Inc. and others. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -package org.eclipse.lsp4e.operations.documentLink; - -import java.net.URI; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.IRegion; -import org.eclipse.jface.text.ITextViewer; -import org.eclipse.jface.text.ITextViewerLifecycle; -import org.eclipse.jface.text.Region; -import org.eclipse.jface.text.TextPresentation; -import org.eclipse.jface.text.reconciler.DirtyRegion; -import org.eclipse.jface.text.reconciler.IReconcilingStrategy; -import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension; -import org.eclipse.lsp4e.LSPEclipseUtils; -import org.eclipse.lsp4e.LanguageServerPlugin; -import org.eclipse.lsp4e.LanguageServers; -import org.eclipse.lsp4j.DocumentLink; -import org.eclipse.lsp4j.DocumentLinkParams; -import org.eclipse.swt.custom.StyleRange; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; - -/** - * Reconciling strategy used to display links coming from LSP - * 'textDocument/documentLink' with underline style. - * - * @author Angelo ZERR - * - */ -public class LSPDocumentLinkPresentationReconcilingStrategy - implements IReconcilingStrategy, IReconcilingStrategyExtension, ITextViewerLifecycle { - - /** The target viewer. */ - private ITextViewer viewer; - - private CompletableFuture request; - - private IDocument document; - - @Override - public void install(@Nullable ITextViewer viewer) { - this.viewer = viewer; - } - - @Override - public void uninstall() { - this.viewer = null; - cancel(); - } - - private void underline() { - ITextViewer theViewer = viewer; - if (theViewer == null) - return; - - final IDocument document = theViewer.getDocument(); - if (document == null) { - return; - } - - URI uri = LSPEclipseUtils.toUri(document); - if (uri == null) { - return; - } - cancel(); - final var params = new DocumentLinkParams(LSPEclipseUtils.toTextDocumentIdentifier(uri)); - final Control control = theViewer.getTextWidget(); - if (control != null && !control.isDisposed()) { - Display display = control.getDisplay(); - request = LanguageServers.forDocument(document) - .withFilter(capabilities -> capabilities.getDocumentLinkProvider() != null) - .collectAll(languageServer -> languageServer.getTextDocumentService().documentLink(params)) - .thenAcceptAsync(links -> links.forEach(this::underline), display); - } - } - - private void underline(List links) { - if (document == null || links == null) { - return; - } - for (DocumentLink link : links) { - try { - // Compute link region - int start = LSPEclipseUtils.toOffset(link.getRange().getStart(), document); - int end = LSPEclipseUtils.toOffset(link.getRange().getEnd(), document); - int length = end - start; - final var linkRegion = new Region(start, length); - - // Update existing style range with underline or create a new style range with - // underline - StyleRange styleRange = null; - StyleRange[] styleRanges = viewer.getTextWidget().getStyleRanges(start, length); - if (styleRanges != null && styleRanges.length > 0) { - // It exists some styles for the range of document link, update just the - // underline style. - for (StyleRange s : styleRanges) { - s.underline = true; - } - final var presentation = new TextPresentation(linkRegion, 100); - presentation.replaceStyleRanges(styleRanges); - viewer.changeTextPresentation(presentation, false); - - } else { - // No styles for the range of document link, create a style range with underline - styleRange = new StyleRange(); - styleRange.underline = true; - styleRange.start = start; - styleRange.length = length; - - final var presentation = new TextPresentation(linkRegion, 100); - presentation.replaceStyleRange(styleRange); - viewer.changeTextPresentation(presentation, false); - } - - } catch (BadLocationException e) { - LanguageServerPlugin.logError(e); - } - } - } - - @Override - public void initialReconcile() { - underline(); - } - - /** - * Cancel the last call of 'documenLink'. - */ - private void cancel() { - if (request != null) { - request.cancel(true); - request = null; - } - } - - @Override - public void setDocument(IDocument document) { - this.document = document; - } - - @Override - public void setProgressMonitor(IProgressMonitor monitor) { - // Do nothing - } - - @Override - public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) { - // Do nothing - } - - @Override - public void reconcile(IRegion partition) { - // Underline document by using textDocument/documentLink with some delay as - // reconcile method is called in a Thread background. - underline(); - } - +/******************************************************************************* + * Copyright (c) 2022 Red Hat Inc. and others. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.lsp4e.operations.documentLink; + +import java.net.URI; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.ITextViewerLifecycle; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextPresentation; +import org.eclipse.jface.text.reconciler.DirtyRegion; +import org.eclipse.jface.text.reconciler.IReconcilingStrategy; +import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension; +import org.eclipse.lsp4e.LSPEclipseUtils; +import org.eclipse.lsp4e.LanguageServerPlugin; +import org.eclipse.lsp4e.LanguageServers; +import org.eclipse.lsp4j.DocumentLink; +import org.eclipse.lsp4j.DocumentLinkParams; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; + +/** + * Reconciling strategy used to display links coming from LSP + * 'textDocument/documentLink' with underline style. + * + * @author Angelo ZERR + * + */ +public class LSPDocumentLinkPresentationReconcilingStrategy + implements IReconcilingStrategy, IReconcilingStrategyExtension, ITextViewerLifecycle { + + /** The target viewer. */ + private ITextViewer viewer; + + private CompletableFuture request; + + private IDocument document; + + @Override + public void install(@Nullable ITextViewer viewer) { + this.viewer = viewer; + } + + @Override + public void uninstall() { + this.viewer = null; + cancel(); + } + + private void underline() { + ITextViewer theViewer = viewer; + if (theViewer == null) + return; + + final IDocument document = theViewer.getDocument(); + if (document == null) { + return; + } + + URI uri = LSPEclipseUtils.toUri(document); + if (uri == null) { + return; + } + cancel(); + final var params = new DocumentLinkParams(LSPEclipseUtils.toTextDocumentIdentifier(uri)); + final Control control = theViewer.getTextWidget(); + if (control != null && !control.isDisposed()) { + Display display = control.getDisplay(); + request = LanguageServers.forDocument(document) + .withFilter(capabilities -> capabilities.getDocumentLinkProvider() != null) + .collectAll(languageServer -> languageServer.getTextDocumentService().documentLink(params)) + .thenAcceptAsync(links -> links.forEach(this::underline), display); + } + } + + private void underline(List links) { + if (document == null || links == null) { + return; + } + for (DocumentLink link : links) { + try { + // Compute link region + int start = LSPEclipseUtils.toOffset(link.getRange().getStart(), document); + int end = LSPEclipseUtils.toOffset(link.getRange().getEnd(), document); + int length = end - start; + final var linkRegion = new Region(start, length); + + // Update existing style range with underline or create a new style range with + // underline + StyleRange styleRange = null; + StyleRange[] styleRanges = viewer.getTextWidget().getStyleRanges(start, length); + if (styleRanges != null && styleRanges.length > 0) { + // It exists some styles for the range of document link, update just the + // underline style. + for (StyleRange s : styleRanges) { + s.underline = true; + } + final var presentation = new TextPresentation(linkRegion, 100); + presentation.replaceStyleRanges(styleRanges); + viewer.changeTextPresentation(presentation, false); + + } else { + // No styles for the range of document link, create a style range with underline + styleRange = new StyleRange(); + styleRange.underline = true; + styleRange.start = start; + styleRange.length = length; + + final var presentation = new TextPresentation(linkRegion, 100); + presentation.replaceStyleRange(styleRange); + viewer.changeTextPresentation(presentation, false); + } + + } catch (BadLocationException e) { + LanguageServerPlugin.logError(e); + } + } + } + + @Override + public void initialReconcile() { + underline(); + } + + /** + * Cancel the last call of 'documenLink'. + */ + private void cancel() { + if (request != null) { + request.cancel(true); + request = null; + } + } + + @Override + public void setDocument(IDocument document) { + this.document = document; + } + + @Override + public void setProgressMonitor(IProgressMonitor monitor) { + // Do nothing + } + + @Override + public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) { + // Do nothing + } + + @Override + public void reconcile(IRegion partition) { + // Underline document by using textDocument/documentLink with some delay as + // reconcile method is called in a Thread background. + underline(); + } + } \ No newline at end of file diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/highlight/HighlightReconcilingStrategy.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/highlight/HighlightReconcilingStrategy.java index f4f4cfe81..42d037d97 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/highlight/HighlightReconcilingStrategy.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/highlight/HighlightReconcilingStrategy.java @@ -1,326 +1,326 @@ -/******************************************************************************* - * Copyright (c) 2017 Rogue Wave Software Inc. and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Michal Niewrzal (Rogue Wave Software Inc.) - initial implementation - * Angelo Zerr - fix Bug 521020 - * Lucas Bullen (Red Hat Inc.) - fix Bug 522737, 517428, 527426 - * Joao Dinis Ferreira (Avaloq Group AG) - Remove all annotations when uninstalling - *******************************************************************************/ -package org.eclipse.lsp4e.operations.highlight; - -import java.net.URI; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map.Entry; -import java.util.concurrent.CompletableFuture; - -import org.eclipse.core.runtime.ICoreRunnable; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.jobs.Job; -import org.eclipse.core.runtime.preferences.IEclipsePreferences; -import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; -import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; -import org.eclipse.core.runtime.preferences.InstanceScope; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.IRegion; -import org.eclipse.jface.text.ISynchronizable; -import org.eclipse.jface.text.ITextSelection; -import org.eclipse.jface.text.ITextViewer; -import org.eclipse.jface.text.ITextViewerLifecycle; -import org.eclipse.jface.text.reconciler.DirtyRegion; -import org.eclipse.jface.text.reconciler.IReconcilingStrategy; -import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension; -import org.eclipse.jface.text.source.Annotation; -import org.eclipse.jface.text.source.IAnnotationModel; -import org.eclipse.jface.text.source.IAnnotationModelExtension; -import org.eclipse.jface.text.source.ISourceViewer; -import org.eclipse.jface.viewers.IPostSelectionProvider; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.ISelectionProvider; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.lsp4e.LSPEclipseUtils; -import org.eclipse.lsp4e.LanguageServerPlugin; -import org.eclipse.lsp4e.LanguageServers; -import org.eclipse.lsp4j.DocumentHighlight; -import org.eclipse.lsp4j.DocumentHighlightKind; -import org.eclipse.lsp4j.DocumentHighlightParams; -import org.eclipse.lsp4j.Position; -import org.eclipse.lsp4j.ServerCapabilities; -import org.eclipse.swt.custom.StyledText; - -/** - * {@link IReconcilingStrategy} implementation to Highlight Symbol (mark - * occurrences like). - * - */ -public class HighlightReconcilingStrategy - implements IReconcilingStrategy, IReconcilingStrategyExtension, IPreferenceChangeListener, ITextViewerLifecycle { - - public static final String TOGGLE_HIGHLIGHT_PREFERENCE = "org.eclipse.ui.genericeditor.togglehighlight"; //$NON-NLS-1$ - - public static final String READ_ANNOTATION_TYPE = "org.eclipse.lsp4e.read"; //$NON-NLS-1$ - public static final String WRITE_ANNOTATION_TYPE = "org.eclipse.lsp4e.write"; //$NON-NLS-1$ - public static final String TEXT_ANNOTATION_TYPE = "org.eclipse.lsp4e.text"; //$NON-NLS-1$ - - private boolean enabled; - private ISourceViewer sourceViewer; - private IDocument document; - - private Job highlightJob; - - /** - * Holds the current occurrence annotations. - */ - private Annotation[] fOccurrenceAnnotations = null; - - class EditorSelectionChangedListener implements ISelectionChangedListener { - - public void install(ISelectionProvider selectionProvider) { - if (selectionProvider == null) - return; - - if (selectionProvider instanceof IPostSelectionProvider provider) { - provider.addPostSelectionChangedListener(this); - } else { - selectionProvider.addSelectionChangedListener(this); - } - } - - public void uninstall(ISelectionProvider selectionProvider) { - if (selectionProvider == null) - return; - - if (selectionProvider instanceof IPostSelectionProvider provider) { - provider.removePostSelectionChangedListener(this); - } else { - selectionProvider.removeSelectionChangedListener(this); - } - } - - @Override - public void selectionChanged(SelectionChangedEvent event) { - updateHighlights(event.getSelection()); - } - } - - private void updateHighlights(ISelection selection) { - if (selection instanceof ITextSelection textSelection) { - if (highlightJob != null) { - highlightJob.cancel(); - } - highlightJob = Job.createSystem("LSP4E Highlight", //$NON-NLS-1$ - (ICoreRunnable)(monitor -> collectHighlights(textSelection.getOffset(), monitor))); - highlightJob.schedule(); - } - } - - private EditorSelectionChangedListener editorSelectionChangedListener; - - private @NonNull List<@NonNull CompletableFuture<@Nullable List>> requests = List.of(); - - @Override - public void install(ITextViewer viewer) { - if (viewer instanceof ISourceViewer thisSourceViewer) { - IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(LanguageServerPlugin.PLUGIN_ID); - preferences.addPreferenceChangeListener(this); - this.enabled = preferences.getBoolean(TOGGLE_HIGHLIGHT_PREFERENCE, true); - this.sourceViewer = thisSourceViewer; - editorSelectionChangedListener = new EditorSelectionChangedListener(); - editorSelectionChangedListener.install(sourceViewer.getSelectionProvider()); - } - } - - @Override - public void uninstall() { - removeOccurrenceAnnotations(); - if (sourceViewer != null) { - editorSelectionChangedListener.uninstall(sourceViewer.getSelectionProvider()); - } - IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(LanguageServerPlugin.PLUGIN_ID); - preferences.removePreferenceChangeListener(this); - cancel(); - } - - @Override - public void setProgressMonitor(IProgressMonitor monitor) { - - } - - @Override - public void initialReconcile() { - if (sourceViewer != null) { - ISelectionProvider selectionProvider = sourceViewer.getSelectionProvider(); - final StyledText textWidget = sourceViewer.getTextWidget(); - if (textWidget != null && selectionProvider != null) { - textWidget.getDisplay().asyncExec(() -> { - if (!textWidget.isDisposed()) { - updateHighlights(selectionProvider.getSelection()); - } - }); - } - } - } - - @Override - public void setDocument(IDocument document) { - this.document = document; - } - - /** - * Collect list of highlight for the given caret offset by consuming language - * server 'documentHighligh't. - * - * @param caretOffset - * @param monitor - */ - private void collectHighlights(int caretOffset, IProgressMonitor monitor) { - if (sourceViewer == null || !enabled || monitor.isCanceled()) { - return; - } - cancel(); - Position position; - try { - position = LSPEclipseUtils.toPosition(caretOffset, document); - } catch (BadLocationException e) { - LanguageServerPlugin.logError(e); - return; - } - URI uri = LSPEclipseUtils.toUri(document); - if(uri == null) { - return; - } - final var identifier = LSPEclipseUtils.toTextDocumentIdentifier(uri); - final var params = new DocumentHighlightParams(identifier, position); - requests = LanguageServers.forDocument(document).withCapability(ServerCapabilities::getDocumentHighlightProvider) - .computeAll(languageServer -> languageServer.getTextDocumentService().documentHighlight(params)); - requests.forEach(request -> request.thenAcceptAsync(highlights -> { - if (!monitor.isCanceled()) { - updateAnnotations(highlights, sourceViewer.getAnnotationModel()); - } - })); - } - - /** - * Cancel the last call of 'documentHighlight'. - */ - private void cancel() { - requests.forEach(request -> request.cancel(true)); - } - - /** - * Update the UI annotations with the given list of DocumentHighlight. - * - * @param highlights - * list of DocumentHighlight - * @param annotationModel - * annotation model to update. - */ - private void updateAnnotations(@Nullable List highlights, IAnnotationModel annotationModel) { - if (highlights == null) - return; - - final var annotationMap = new HashMap(highlights.size()); - for (DocumentHighlight h : highlights) { - if (h != null) { - try { - int start = LSPEclipseUtils.toOffset(h.getRange().getStart(), document); - int end = LSPEclipseUtils.toOffset(h.getRange().getEnd(), document); - annotationMap.put(new Annotation(kindToAnnotationType(h.getKind()), false, null), - new org.eclipse.jface.text.Position(start, end - start)); - } catch (BadLocationException e) { - LanguageServerPlugin.logError(e); - } - } - } - - synchronized (getLockObject(annotationModel)) { - if (annotationModel instanceof IAnnotationModelExtension modelExtension) { - modelExtension.replaceAnnotations(fOccurrenceAnnotations, annotationMap); - } else { - removeOccurrenceAnnotations(); - Iterator> iter = annotationMap.entrySet().iterator(); - while (iter.hasNext()) { - Entry mapEntry = iter.next(); - annotationModel.addAnnotation(mapEntry.getKey(), mapEntry.getValue()); - } - } - fOccurrenceAnnotations = annotationMap.keySet().toArray(new Annotation[annotationMap.size()]); - } - } - - /** - * Returns the lock object for the given annotation model. - * - * @param annotationModel - * the annotation model - * @return the annotation model's lock object - */ - private Object getLockObject(IAnnotationModel annotationModel) { - if (annotationModel instanceof ISynchronizable sync) { - Object lock = sync.getLockObject(); - if (lock != null) - return lock; - } - return annotationModel; - } - - void removeOccurrenceAnnotations() { - IAnnotationModel annotationModel = sourceViewer.getAnnotationModel(); - if (annotationModel == null || fOccurrenceAnnotations == null) - return; - - synchronized (getLockObject(annotationModel)) { - if (annotationModel instanceof IAnnotationModelExtension modelExtension) { - modelExtension.replaceAnnotations(fOccurrenceAnnotations, null); - } else { - for (Annotation fOccurrenceAnnotation : fOccurrenceAnnotations) - annotationModel.removeAnnotation(fOccurrenceAnnotation); - } - fOccurrenceAnnotations = null; - } - } - - private String kindToAnnotationType(@Nullable DocumentHighlightKind kind) { - if (kind == null) - return TEXT_ANNOTATION_TYPE; - - return switch (kind) { - case Read -> READ_ANNOTATION_TYPE; - case Write -> WRITE_ANNOTATION_TYPE; - default -> TEXT_ANNOTATION_TYPE; - }; - } - - @Override - public void preferenceChange(PreferenceChangeEvent event) { - if (event.getKey().equals(TOGGLE_HIGHLIGHT_PREFERENCE)) { - this.enabled = Boolean.valueOf(event.getNewValue().toString()); - if (enabled) { - initialReconcile(); - } else { - removeOccurrenceAnnotations(); - } - } - } - - @Override - public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) { - // Do nothing - } - - @Override - public void reconcile(IRegion partition) { - // Do nothing - } - -} +/******************************************************************************* + * Copyright (c) 2017 Rogue Wave Software Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Michal Niewrzal (Rogue Wave Software Inc.) - initial implementation + * Angelo Zerr - fix Bug 521020 + * Lucas Bullen (Red Hat Inc.) - fix Bug 522737, 517428, 527426 + * Joao Dinis Ferreira (Avaloq Group AG) - Remove all annotations when uninstalling + *******************************************************************************/ +package org.eclipse.lsp4e.operations.highlight; + +import java.net.URI; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.core.runtime.ICoreRunnable; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; +import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ISynchronizable; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.ITextViewerLifecycle; +import org.eclipse.jface.text.reconciler.DirtyRegion; +import org.eclipse.jface.text.reconciler.IReconcilingStrategy; +import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension; +import org.eclipse.jface.text.source.Annotation; +import org.eclipse.jface.text.source.IAnnotationModel; +import org.eclipse.jface.text.source.IAnnotationModelExtension; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.jface.viewers.IPostSelectionProvider; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.lsp4e.LSPEclipseUtils; +import org.eclipse.lsp4e.LanguageServerPlugin; +import org.eclipse.lsp4e.LanguageServers; +import org.eclipse.lsp4j.DocumentHighlight; +import org.eclipse.lsp4j.DocumentHighlightKind; +import org.eclipse.lsp4j.DocumentHighlightParams; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.ServerCapabilities; +import org.eclipse.swt.custom.StyledText; + +/** + * {@link IReconcilingStrategy} implementation to Highlight Symbol (mark + * occurrences like). + * + */ +public class HighlightReconcilingStrategy + implements IReconcilingStrategy, IReconcilingStrategyExtension, IPreferenceChangeListener, ITextViewerLifecycle { + + public static final String TOGGLE_HIGHLIGHT_PREFERENCE = "org.eclipse.ui.genericeditor.togglehighlight"; //$NON-NLS-1$ + + public static final String READ_ANNOTATION_TYPE = "org.eclipse.lsp4e.read"; //$NON-NLS-1$ + public static final String WRITE_ANNOTATION_TYPE = "org.eclipse.lsp4e.write"; //$NON-NLS-1$ + public static final String TEXT_ANNOTATION_TYPE = "org.eclipse.lsp4e.text"; //$NON-NLS-1$ + + private boolean enabled; + private ISourceViewer sourceViewer; + private IDocument document; + + private Job highlightJob; + + /** + * Holds the current occurrence annotations. + */ + private Annotation[] fOccurrenceAnnotations = null; + + class EditorSelectionChangedListener implements ISelectionChangedListener { + + public void install(ISelectionProvider selectionProvider) { + if (selectionProvider == null) + return; + + if (selectionProvider instanceof IPostSelectionProvider provider) { + provider.addPostSelectionChangedListener(this); + } else { + selectionProvider.addSelectionChangedListener(this); + } + } + + public void uninstall(ISelectionProvider selectionProvider) { + if (selectionProvider == null) + return; + + if (selectionProvider instanceof IPostSelectionProvider provider) { + provider.removePostSelectionChangedListener(this); + } else { + selectionProvider.removeSelectionChangedListener(this); + } + } + + @Override + public void selectionChanged(SelectionChangedEvent event) { + updateHighlights(event.getSelection()); + } + } + + private void updateHighlights(ISelection selection) { + if (selection instanceof ITextSelection textSelection) { + if (highlightJob != null) { + highlightJob.cancel(); + } + highlightJob = Job.createSystem("LSP4E Highlight", //$NON-NLS-1$ + (ICoreRunnable)(monitor -> collectHighlights(textSelection.getOffset(), monitor))); + highlightJob.schedule(); + } + } + + private EditorSelectionChangedListener editorSelectionChangedListener; + + private @NonNull List<@NonNull CompletableFuture<@Nullable List>> requests = List.of(); + + @Override + public void install(ITextViewer viewer) { + if (viewer instanceof ISourceViewer thisSourceViewer) { + IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(LanguageServerPlugin.PLUGIN_ID); + preferences.addPreferenceChangeListener(this); + this.enabled = preferences.getBoolean(TOGGLE_HIGHLIGHT_PREFERENCE, true); + this.sourceViewer = thisSourceViewer; + editorSelectionChangedListener = new EditorSelectionChangedListener(); + editorSelectionChangedListener.install(sourceViewer.getSelectionProvider()); + } + } + + @Override + public void uninstall() { + removeOccurrenceAnnotations(); + if (sourceViewer != null) { + editorSelectionChangedListener.uninstall(sourceViewer.getSelectionProvider()); + } + IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(LanguageServerPlugin.PLUGIN_ID); + preferences.removePreferenceChangeListener(this); + cancel(); + } + + @Override + public void setProgressMonitor(IProgressMonitor monitor) { + + } + + @Override + public void initialReconcile() { + if (sourceViewer != null) { + ISelectionProvider selectionProvider = sourceViewer.getSelectionProvider(); + final StyledText textWidget = sourceViewer.getTextWidget(); + if (textWidget != null && selectionProvider != null) { + textWidget.getDisplay().asyncExec(() -> { + if (!textWidget.isDisposed()) { + updateHighlights(selectionProvider.getSelection()); + } + }); + } + } + } + + @Override + public void setDocument(IDocument document) { + this.document = document; + } + + /** + * Collect list of highlight for the given caret offset by consuming language + * server 'documentHighligh't. + * + * @param caretOffset + * @param monitor + */ + private void collectHighlights(int caretOffset, IProgressMonitor monitor) { + if (sourceViewer == null || !enabled || monitor.isCanceled()) { + return; + } + cancel(); + Position position; + try { + position = LSPEclipseUtils.toPosition(caretOffset, document); + } catch (BadLocationException e) { + LanguageServerPlugin.logError(e); + return; + } + URI uri = LSPEclipseUtils.toUri(document); + if(uri == null) { + return; + } + final var identifier = LSPEclipseUtils.toTextDocumentIdentifier(uri); + final var params = new DocumentHighlightParams(identifier, position); + requests = LanguageServers.forDocument(document).withCapability(ServerCapabilities::getDocumentHighlightProvider) + .computeAll(languageServer -> languageServer.getTextDocumentService().documentHighlight(params)); + requests.forEach(request -> request.thenAcceptAsync(highlights -> { + if (!monitor.isCanceled()) { + updateAnnotations(highlights, sourceViewer.getAnnotationModel()); + } + })); + } + + /** + * Cancel the last call of 'documentHighlight'. + */ + private void cancel() { + requests.forEach(request -> request.cancel(true)); + } + + /** + * Update the UI annotations with the given list of DocumentHighlight. + * + * @param highlights + * list of DocumentHighlight + * @param annotationModel + * annotation model to update. + */ + private void updateAnnotations(@Nullable List highlights, IAnnotationModel annotationModel) { + if (highlights == null) + return; + + final var annotationMap = new HashMap(highlights.size()); + for (DocumentHighlight h : highlights) { + if (h != null) { + try { + int start = LSPEclipseUtils.toOffset(h.getRange().getStart(), document); + int end = LSPEclipseUtils.toOffset(h.getRange().getEnd(), document); + annotationMap.put(new Annotation(kindToAnnotationType(h.getKind()), false, null), + new org.eclipse.jface.text.Position(start, end - start)); + } catch (BadLocationException e) { + LanguageServerPlugin.logError(e); + } + } + } + + synchronized (getLockObject(annotationModel)) { + if (annotationModel instanceof IAnnotationModelExtension modelExtension) { + modelExtension.replaceAnnotations(fOccurrenceAnnotations, annotationMap); + } else { + removeOccurrenceAnnotations(); + Iterator> iter = annotationMap.entrySet().iterator(); + while (iter.hasNext()) { + Entry mapEntry = iter.next(); + annotationModel.addAnnotation(mapEntry.getKey(), mapEntry.getValue()); + } + } + fOccurrenceAnnotations = annotationMap.keySet().toArray(new Annotation[annotationMap.size()]); + } + } + + /** + * Returns the lock object for the given annotation model. + * + * @param annotationModel + * the annotation model + * @return the annotation model's lock object + */ + private Object getLockObject(IAnnotationModel annotationModel) { + if (annotationModel instanceof ISynchronizable sync) { + Object lock = sync.getLockObject(); + if (lock != null) + return lock; + } + return annotationModel; + } + + void removeOccurrenceAnnotations() { + IAnnotationModel annotationModel = sourceViewer.getAnnotationModel(); + if (annotationModel == null || fOccurrenceAnnotations == null) + return; + + synchronized (getLockObject(annotationModel)) { + if (annotationModel instanceof IAnnotationModelExtension modelExtension) { + modelExtension.replaceAnnotations(fOccurrenceAnnotations, null); + } else { + for (Annotation fOccurrenceAnnotation : fOccurrenceAnnotations) + annotationModel.removeAnnotation(fOccurrenceAnnotation); + } + fOccurrenceAnnotations = null; + } + } + + private String kindToAnnotationType(@Nullable DocumentHighlightKind kind) { + if (kind == null) + return TEXT_ANNOTATION_TYPE; + + return switch (kind) { + case Read -> READ_ANNOTATION_TYPE; + case Write -> WRITE_ANNOTATION_TYPE; + default -> TEXT_ANNOTATION_TYPE; + }; + } + + @Override + public void preferenceChange(PreferenceChangeEvent event) { + if (event.getKey().equals(TOGGLE_HIGHLIGHT_PREFERENCE)) { + this.enabled = Boolean.valueOf(event.getNewValue().toString()); + if (enabled) { + initialReconcile(); + } else { + removeOccurrenceAnnotations(); + } + } + } + + @Override + public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) { + // Do nothing + } + + @Override + public void reconcile(IRegion partition) { + // Do nothing + } + +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/references/LSSearchQuery.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/references/LSSearchQuery.java index a7944b6a9..79e5e906b 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/references/LSSearchQuery.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/references/LSSearchQuery.java @@ -1,185 +1,185 @@ -/******************************************************************************* - * Copyright (c) 2016-23 Red Hat Inc. and others. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Mickael Istria (Red Hat Inc.) - initial implementation - * Michał Niewrzał (Rogue Wave Software Inc.) - * Angelo Zerr - fix Bug 526255 - * Lucas Bullen (Red Hat Inc.) - [Bug 517428] Requests sent before initialization - *******************************************************************************/ -package org.eclipse.lsp4e.operations.references; - -import java.util.List; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; - -import org.eclipse.core.filebuffers.FileBuffers; -import org.eclipse.core.filebuffers.LocationKind; -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.NullProgressMonitor; -import org.eclipse.core.runtime.OperationCanceledException; -import org.eclipse.core.runtime.Status; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.IRegion; -import org.eclipse.lsp4e.LSPEclipseUtils; -import org.eclipse.lsp4e.LanguageServerPlugin; -import org.eclipse.lsp4e.LanguageServers; -import org.eclipse.lsp4e.LanguageServers.LanguageServerDocumentExecutor; -import org.eclipse.lsp4e.ui.Messages; -import org.eclipse.lsp4j.Location; -import org.eclipse.lsp4j.Position; -import org.eclipse.lsp4j.ReferenceContext; -import org.eclipse.lsp4j.ReferenceParams; -import org.eclipse.lsp4j.ServerCapabilities; -import org.eclipse.search.internal.ui.text.FileMatch; -import org.eclipse.search.internal.ui.text.FileSearchQuery; -import org.eclipse.search.internal.ui.text.LineElement; -import org.eclipse.search.ui.ISearchQuery; -import org.eclipse.search.ui.text.Match; - -/** - * {@link ISearchQuery} implementation for LSP. - */ -public class LSSearchQuery extends FileSearchQuery { - - private final @NonNull IDocument document; - private final int offset; - - private LSSearchResult result; - - /** - * LSP search query to "Find references" from the given offset of the given - * {@link LanguageServerDocumentExecutor}. - * - * @param offset - * @param document - */ - public LSSearchQuery(int offset, @NonNull IDocument document) - throws BadLocationException { - super("", false, false, true, true, null); //$NON-NLS-1$ - this.document = document; - this.offset = offset; - } - - @Override - public IStatus run(IProgressMonitor monitor) throws OperationCanceledException { - getSearchResult().removeAll(); - - try { - // Execute LSP "references" service - final var params = new ReferenceParams(); - params.setContext(new ReferenceContext(false)); - params.setTextDocument(LSPEclipseUtils.toTextDocumentIdentifier(document)); - params.setPosition(LSPEclipseUtils.toPosition(offset, document)); - - @NonNull List<@NonNull CompletableFuture>> requests = LanguageServers.forDocument(document).withCapability(ServerCapabilities::getReferencesProvider) - .computeAll(languageServer -> languageServer.getTextDocumentService().references(params)); - CompletableFuture[] populateUIFutures = requests.stream().map(request -> - request.thenAcceptAsync(locations -> { - if (locations != null) { - // Convert each LSP Location to a Match search. - locations.stream() // - .filter(Objects::nonNull) // - .map(LSSearchQuery::toMatch) // - .filter(Objects::nonNull) // - .forEach(result::addMatch); - } - })).toArray(CompletableFuture[]::new); - CompletableFuture.allOf(populateUIFutures).join(); - return Status.OK_STATUS; - } catch (Exception ex) { - return new Status(IStatus.ERROR, LanguageServerPlugin.getDefault().getBundle().getSymbolicName(), - ex.getMessage(), ex); - } - } - - /** - * Convert the given LSP {@link Location} to Eclipse search {@link Match}. - * - * @param location - * the LSP location to convert. - * @return the converted Eclipse search {@link Match}. - */ - private static Match toMatch(@NonNull Location location) { - IResource resource = LSPEclipseUtils.findResourceFor(location.getUri()); - if (resource != null) { - IDocument document = LSPEclipseUtils.getExistingDocument(resource); - boolean temporaryLoadDocument = document == null; - if (temporaryLoadDocument) { - document = LSPEclipseUtils.getDocument(resource); - } - if (document != null) { - try { - int startOffset = LSPEclipseUtils.toOffset(location.getRange().getStart(), document); - int endOffset = LSPEclipseUtils.toOffset(location.getRange().getEnd(), document); - - IRegion lineInformation = document.getLineInformationOfOffset(startOffset); - final var lineEntry = new LineElement(resource, document.getLineOfOffset(startOffset) + 1, - lineInformation.getOffset(), - document.get(lineInformation.getOffset(), lineInformation.getLength())); - return new FileMatch((IFile) resource, startOffset, endOffset - startOffset, lineEntry); - } catch (BadLocationException ex) { - LanguageServerPlugin.logError(ex); - } finally { - if (temporaryLoadDocument) { - try { - FileBuffers.getTextFileBufferManager().disconnect(resource.getFullPath(), LocationKind.IFILE, new NullProgressMonitor()); - } catch (CoreException e) { - LanguageServerPlugin.logError(e); - } - } - } - } - - Position startPosition = location.getRange().getStart(); - final var lineEntry = new LineElement(resource, startPosition.getLine() + 1, 0, - String.format("%s:%s", startPosition.getLine(), startPosition.getCharacter())); //$NON-NLS-1$ - return new FileMatch((IFile) resource, 0, 0, lineEntry); - } - try { - return new URIMatch(location); - } catch (BadLocationException ex) { - LanguageServerPlugin.logError(ex); - return null; - } - } - - @Override - public LSSearchResult getSearchResult() { - if (result == null) { - result = new LSSearchResult(this); - } - return result; - } - - @Override - public String getLabel() { - return Messages.LSSearchQuery_label; - } - - @Override - public boolean canRerun() { - return true; - } - - @Override - public boolean canRunInBackground() { - return true; - } - - @Override - public boolean isFileNameSearch() { - return false; - } -} +/******************************************************************************* + * Copyright (c) 2016-23 Red Hat Inc. and others. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Mickael Istria (Red Hat Inc.) - initial implementation + * Michał Niewrzał (Rogue Wave Software Inc.) + * Angelo Zerr - fix Bug 526255 + * Lucas Bullen (Red Hat Inc.) - [Bug 517428] Requests sent before initialization + *******************************************************************************/ +package org.eclipse.lsp4e.operations.references; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.core.filebuffers.FileBuffers; +import org.eclipse.core.filebuffers.LocationKind; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.lsp4e.LSPEclipseUtils; +import org.eclipse.lsp4e.LanguageServerPlugin; +import org.eclipse.lsp4e.LanguageServers; +import org.eclipse.lsp4e.LanguageServers.LanguageServerDocumentExecutor; +import org.eclipse.lsp4e.ui.Messages; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.ReferenceContext; +import org.eclipse.lsp4j.ReferenceParams; +import org.eclipse.lsp4j.ServerCapabilities; +import org.eclipse.search.internal.ui.text.FileMatch; +import org.eclipse.search.internal.ui.text.FileSearchQuery; +import org.eclipse.search.internal.ui.text.LineElement; +import org.eclipse.search.ui.ISearchQuery; +import org.eclipse.search.ui.text.Match; + +/** + * {@link ISearchQuery} implementation for LSP. + */ +public class LSSearchQuery extends FileSearchQuery { + + private final @NonNull IDocument document; + private final int offset; + + private LSSearchResult result; + + /** + * LSP search query to "Find references" from the given offset of the given + * {@link LanguageServerDocumentExecutor}. + * + * @param offset + * @param document + */ + public LSSearchQuery(int offset, @NonNull IDocument document) + throws BadLocationException { + super("", false, false, true, true, null); //$NON-NLS-1$ + this.document = document; + this.offset = offset; + } + + @Override + public IStatus run(IProgressMonitor monitor) throws OperationCanceledException { + getSearchResult().removeAll(); + + try { + // Execute LSP "references" service + final var params = new ReferenceParams(); + params.setContext(new ReferenceContext(false)); + params.setTextDocument(LSPEclipseUtils.toTextDocumentIdentifier(document)); + params.setPosition(LSPEclipseUtils.toPosition(offset, document)); + + @NonNull List<@NonNull CompletableFuture>> requests = LanguageServers.forDocument(document).withCapability(ServerCapabilities::getReferencesProvider) + .computeAll(languageServer -> languageServer.getTextDocumentService().references(params)); + CompletableFuture[] populateUIFutures = requests.stream().map(request -> + request.thenAcceptAsync(locations -> { + if (locations != null) { + // Convert each LSP Location to a Match search. + locations.stream() // + .filter(Objects::nonNull) // + .map(LSSearchQuery::toMatch) // + .filter(Objects::nonNull) // + .forEach(result::addMatch); + } + })).toArray(CompletableFuture[]::new); + CompletableFuture.allOf(populateUIFutures).join(); + return Status.OK_STATUS; + } catch (Exception ex) { + return new Status(IStatus.ERROR, LanguageServerPlugin.getDefault().getBundle().getSymbolicName(), + ex.getMessage(), ex); + } + } + + /** + * Convert the given LSP {@link Location} to Eclipse search {@link Match}. + * + * @param location + * the LSP location to convert. + * @return the converted Eclipse search {@link Match}. + */ + private static Match toMatch(@NonNull Location location) { + IResource resource = LSPEclipseUtils.findResourceFor(location.getUri()); + if (resource != null) { + IDocument document = LSPEclipseUtils.getExistingDocument(resource); + boolean temporaryLoadDocument = document == null; + if (temporaryLoadDocument) { + document = LSPEclipseUtils.getDocument(resource); + } + if (document != null) { + try { + int startOffset = LSPEclipseUtils.toOffset(location.getRange().getStart(), document); + int endOffset = LSPEclipseUtils.toOffset(location.getRange().getEnd(), document); + + IRegion lineInformation = document.getLineInformationOfOffset(startOffset); + final var lineEntry = new LineElement(resource, document.getLineOfOffset(startOffset) + 1, + lineInformation.getOffset(), + document.get(lineInformation.getOffset(), lineInformation.getLength())); + return new FileMatch((IFile) resource, startOffset, endOffset - startOffset, lineEntry); + } catch (BadLocationException ex) { + LanguageServerPlugin.logError(ex); + } finally { + if (temporaryLoadDocument) { + try { + FileBuffers.getTextFileBufferManager().disconnect(resource.getFullPath(), LocationKind.IFILE, new NullProgressMonitor()); + } catch (CoreException e) { + LanguageServerPlugin.logError(e); + } + } + } + } + + Position startPosition = location.getRange().getStart(); + final var lineEntry = new LineElement(resource, startPosition.getLine() + 1, 0, + String.format("%s:%s", startPosition.getLine(), startPosition.getCharacter())); //$NON-NLS-1$ + return new FileMatch((IFile) resource, 0, 0, lineEntry); + } + try { + return new URIMatch(location); + } catch (BadLocationException ex) { + LanguageServerPlugin.logError(ex); + return null; + } + } + + @Override + public LSSearchResult getSearchResult() { + if (result == null) { + result = new LSSearchResult(this); + } + return result; + } + + @Override + public String getLabel() { + return Messages.LSSearchQuery_label; + } + + @Override + public boolean canRerun() { + return true; + } + + @Override + public boolean canRunInBackground() { + return true; + } + + @Override + public boolean isFileNameSearch() { + return false; + } +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/references/LSSearchResult.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/references/LSSearchResult.java index 65cc77386..6165b33a1 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/references/LSSearchResult.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/references/LSSearchResult.java @@ -1,93 +1,93 @@ -/******************************************************************************* - * Copyright (c) 2016 Red Hat Inc. and others. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Mickael Istria (Red Hat Inc.) - initial implementation - * Michał Niewrzał (Rogue Wave Software Inc.) - * Angelo Zerr - fix Bug 526255 - *******************************************************************************/ -package org.eclipse.lsp4e.operations.references; - -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import org.eclipse.core.resources.IFile; -import org.eclipse.search.internal.ui.text.FileSearchResult; -import org.eclipse.search.ui.ISearchResult; -import org.eclipse.search.ui.text.AbstractTextSearchResult; -import org.eclipse.search.ui.text.IEditorMatchAdapter; -import org.eclipse.search.ui.text.IFileMatchAdapter; -import org.eclipse.search.ui.text.Match; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IFileEditorInput; -import org.eclipse.ui.IURIEditorInput; - -/** - * {@link ISearchResult} implementation for LSP; add support for URI only match (no resource) - */ -public class LSSearchResult extends FileSearchResult implements IEditorMatchAdapter, IFileMatchAdapter { - - public LSSearchResult(LSSearchQuery query) { - super(query); - } - - private static final Match[] EMPTY_ARR= new Match[0]; - private final Set nonFileElements = ConcurrentHashMap.newKeySet(); - - @Override - public IFile getFile(Object element) { - return element instanceof IFile file ? file : null; - } - - @Override - public void addMatch(Match match) { - super.addMatch(match); - this.nonFileElements.add(match.getElement()); - } - - @Override - public boolean isShownInEditor(Match match, IEditorPart editor) { - IEditorInput ei= editor.getEditorInput(); - return (ei instanceof IFileEditorInput fi && Objects.equals(match.getElement(), fi.getFile())) || - (ei instanceof IURIEditorInput uriInput && Objects.equals(match.getElement(), uriInput.getURI())); - } - - @Override - public Match[] computeContainedMatches(AbstractTextSearchResult result, IEditorPart editor) { - IEditorInput ei= editor.getEditorInput(); - if (ei instanceof IFileEditorInput fi) { - return getMatches(fi.getFile()); - } else if (ei instanceof IURIEditorInput uriInput) { - return getMatches(uriInput.getURI()); - } - return EMPTY_ARR; - } - - @Override - public LSSearchQuery getQuery() { - return (LSSearchQuery)super.getQuery(); - } - - @Override - public IFileMatchAdapter getFileMatchAdapter() { - return this; - } - - @Override - public IEditorMatchAdapter getEditorMatchAdapter() { - return this; - } - - @Override - public String getLabel() { - return "References"; //$NON-NLS-1$ - } - -} +/******************************************************************************* + * Copyright (c) 2016 Red Hat Inc. and others. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Mickael Istria (Red Hat Inc.) - initial implementation + * Michał Niewrzał (Rogue Wave Software Inc.) + * Angelo Zerr - fix Bug 526255 + *******************************************************************************/ +package org.eclipse.lsp4e.operations.references; + +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.core.resources.IFile; +import org.eclipse.search.internal.ui.text.FileSearchResult; +import org.eclipse.search.ui.ISearchResult; +import org.eclipse.search.ui.text.AbstractTextSearchResult; +import org.eclipse.search.ui.text.IEditorMatchAdapter; +import org.eclipse.search.ui.text.IFileMatchAdapter; +import org.eclipse.search.ui.text.Match; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.IURIEditorInput; + +/** + * {@link ISearchResult} implementation for LSP; add support for URI only match (no resource) + */ +public class LSSearchResult extends FileSearchResult implements IEditorMatchAdapter, IFileMatchAdapter { + + public LSSearchResult(LSSearchQuery query) { + super(query); + } + + private static final Match[] EMPTY_ARR= new Match[0]; + private final Set nonFileElements = ConcurrentHashMap.newKeySet(); + + @Override + public IFile getFile(Object element) { + return element instanceof IFile file ? file : null; + } + + @Override + public void addMatch(Match match) { + super.addMatch(match); + this.nonFileElements.add(match.getElement()); + } + + @Override + public boolean isShownInEditor(Match match, IEditorPart editor) { + IEditorInput ei= editor.getEditorInput(); + return (ei instanceof IFileEditorInput fi && Objects.equals(match.getElement(), fi.getFile())) || + (ei instanceof IURIEditorInput uriInput && Objects.equals(match.getElement(), uriInput.getURI())); + } + + @Override + public Match[] computeContainedMatches(AbstractTextSearchResult result, IEditorPart editor) { + IEditorInput ei= editor.getEditorInput(); + if (ei instanceof IFileEditorInput fi) { + return getMatches(fi.getFile()); + } else if (ei instanceof IURIEditorInput uriInput) { + return getMatches(uriInput.getURI()); + } + return EMPTY_ARR; + } + + @Override + public LSSearchQuery getQuery() { + return (LSSearchQuery)super.getQuery(); + } + + @Override + public IFileMatchAdapter getFileMatchAdapter() { + return this; + } + + @Override + public IEditorMatchAdapter getEditorMatchAdapter() { + return this; + } + + @Override + public String getLabel() { + return "References"; //$NON-NLS-1$ + } + +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/rename/LSPRenameProcessor.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/rename/LSPRenameProcessor.java index ac054581c..038133b82 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/rename/LSPRenameProcessor.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/rename/LSPRenameProcessor.java @@ -1,236 +1,236 @@ -/** - * Copyright (c) 2017-2023 Angelo ZERR. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Angelo Zerr - initial API and implementation - * Lucas Bullen (Red Hat Inc.) - [Bug 517428] Requests sent before initialization - * Jan Koehnlein (TypeFox) - give user feedback on failures and no-ops - * Pierre-Yves B. - Bug 525411 - [rename] input field should be filled with symbol to rename - */ -package org.eclipse.lsp4e.operations.rename; - -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.eclipse.core.runtime.Assert; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.OperationCanceledException; -import org.eclipse.core.runtime.Status; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.IDocument; -import org.eclipse.lsp4e.LSPEclipseUtils; -import org.eclipse.lsp4e.LanguageServerPlugin; -import org.eclipse.lsp4e.LanguageServerWrapper; -import org.eclipse.lsp4e.LanguageServers; -import org.eclipse.lsp4e.internal.Pair; -import org.eclipse.lsp4e.ui.Messages; -import org.eclipse.lsp4j.PrepareRenameDefaultBehavior; -import org.eclipse.lsp4j.PrepareRenameParams; -import org.eclipse.lsp4j.PrepareRenameResult; -import org.eclipse.lsp4j.Range; -import org.eclipse.lsp4j.RenameOptions; -import org.eclipse.lsp4j.RenameParams; -import org.eclipse.lsp4j.ServerCapabilities; -import org.eclipse.lsp4j.WorkspaceEdit; -import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; -import org.eclipse.lsp4j.jsonrpc.messages.Either; -import org.eclipse.lsp4j.jsonrpc.messages.Either3; -import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; -import org.eclipse.ltk.core.refactoring.Change; -import org.eclipse.ltk.core.refactoring.RefactoringStatus; -import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; -import org.eclipse.ltk.core.refactoring.participants.RefactoringParticipant; -import org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor; -import org.eclipse.ltk.core.refactoring.participants.SharableParticipants; - -/** - * LTK {@link RefactoringProcessor} implementation to refactoring LSP symbols. - * - */ -public class LSPRenameProcessor extends RefactoringProcessor { - - private static final String ID = "org.eclipse.lsp4e.operations.rename"; //$NON-NLS-1$ - - private final @NonNull IDocument document; - private final int offset; - - private LanguageServerWrapper refactoringServer; - - private String newName; - - private WorkspaceEdit rename; - private Either3 prepareRenameResult; - - public LSPRenameProcessor(@NonNull IDocument document, int offset) { - this.document = document; - this.offset = offset; - } - - @Override - public Object[] getElements() { - return null; - } - - @Override - public String getIdentifier() { - return ID; - } - - @Override - public String getProcessorName() { - return Messages.rename_processor_name; - } - - @Override - public boolean isApplicable() throws CoreException { - return true; - } - - @Override - public RefactoringStatus checkInitialConditions(IProgressMonitor pm) - throws CoreException, OperationCanceledException { - final var status = new RefactoringStatus(); - - try { - final var identifier = LSPEclipseUtils.toTextDocumentIdentifier(document); - final var params = new PrepareRenameParams(); - params.setTextDocument(identifier); - params.setPosition(LSPEclipseUtils.toPosition(offset, document)); - - @SuppressWarnings("null") - List>> list = LanguageServers - .forDocument(document).withFilter(LSPRenameProcessor::isPrepareRenameProvider) - .collectAll((w, ls) -> ls.getTextDocumentService().prepareRename(params) - .thenApply(result -> new Pair<>(w, result))) - .get(1000, TimeUnit.MILLISECONDS); - - Optional>> tmp = list - .stream().filter(Objects::nonNull).filter(t -> t.getSecond() != null).findFirst(); - - if (tmp.isEmpty()) { - status.addFatalError(Messages.rename_invalidated); - } else { - tmp.ifPresent(p -> { - refactoringServer = p.getFirst(); - prepareRenameResult = p.getSecond(); - }); - } - } catch (TimeoutException e) { - LanguageServerPlugin.logWarning("Could not prepare rename due to timeout after 1 seconds in `textDocument/prepareRename`. 'newName' will be used", e); //$NON-NLS-1$ - } catch (Exception e) { - status.addFatalError(getErrorMessage(e)); - } - return status; - } - - public String getPlaceholder() { - @Nullable String placeholder = null; - if (prepareRenameResult != null) { - placeholder = prepareRenameResult.map(range -> { - try { - int startOffset = LSPEclipseUtils.toOffset(range.getStart(), document); - int endOffset = LSPEclipseUtils.toOffset(range.getEnd(), document); - return document.get(startOffset, endOffset - startOffset); - } catch (BadLocationException e) { - LanguageServerPlugin.logError(e); - return null; - } - }, PrepareRenameResult::getPlaceholder, - options -> null); - } - return placeholder != null && !placeholder.isBlank() ? placeholder :"newName"; //$NON-NLS-1$ - } - - public static boolean isPrepareRenameProvider(ServerCapabilities serverCapabilities) { - if (serverCapabilities == null) { - return false; - } - Either renameProvider = serverCapabilities.getRenameProvider(); - if (renameProvider == null) { - return false; - } - - if (renameProvider.isRight()) { - return renameProvider.getRight() != null && renameProvider.getRight().getPrepareProvider(); - } - return false; - } - - @Override - public RefactoringStatus checkFinalConditions(IProgressMonitor pm, CheckConditionsContext context) - throws CoreException, OperationCanceledException { - final var status = new RefactoringStatus(); - try { - final var params = new RenameParams(); +/** + * Copyright (c) 2017-2023 Angelo ZERR. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Angelo Zerr - initial API and implementation + * Lucas Bullen (Red Hat Inc.) - [Bug 517428] Requests sent before initialization + * Jan Koehnlein (TypeFox) - give user feedback on failures and no-ops + * Pierre-Yves B. - Bug 525411 - [rename] input field should be filled with symbol to rename + */ +package org.eclipse.lsp4e.operations.rename; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.lsp4e.LSPEclipseUtils; +import org.eclipse.lsp4e.LanguageServerPlugin; +import org.eclipse.lsp4e.LanguageServerWrapper; +import org.eclipse.lsp4e.LanguageServers; +import org.eclipse.lsp4e.internal.Pair; +import org.eclipse.lsp4e.ui.Messages; +import org.eclipse.lsp4j.PrepareRenameDefaultBehavior; +import org.eclipse.lsp4j.PrepareRenameParams; +import org.eclipse.lsp4j.PrepareRenameResult; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.RenameOptions; +import org.eclipse.lsp4j.RenameParams; +import org.eclipse.lsp4j.ServerCapabilities; +import org.eclipse.lsp4j.WorkspaceEdit; +import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.lsp4j.jsonrpc.messages.Either3; +import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; +import org.eclipse.ltk.core.refactoring.participants.RefactoringParticipant; +import org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor; +import org.eclipse.ltk.core.refactoring.participants.SharableParticipants; + +/** + * LTK {@link RefactoringProcessor} implementation to refactoring LSP symbols. + * + */ +public class LSPRenameProcessor extends RefactoringProcessor { + + private static final String ID = "org.eclipse.lsp4e.operations.rename"; //$NON-NLS-1$ + + private final @NonNull IDocument document; + private final int offset; + + private LanguageServerWrapper refactoringServer; + + private String newName; + + private WorkspaceEdit rename; + private Either3 prepareRenameResult; + + public LSPRenameProcessor(@NonNull IDocument document, int offset) { + this.document = document; + this.offset = offset; + } + + @Override + public Object[] getElements() { + return null; + } + + @Override + public String getIdentifier() { + return ID; + } + + @Override + public String getProcessorName() { + return Messages.rename_processor_name; + } + + @Override + public boolean isApplicable() throws CoreException { + return true; + } + + @Override + public RefactoringStatus checkInitialConditions(IProgressMonitor pm) + throws CoreException, OperationCanceledException { + final var status = new RefactoringStatus(); + + try { + final var identifier = LSPEclipseUtils.toTextDocumentIdentifier(document); + final var params = new PrepareRenameParams(); + params.setTextDocument(identifier); params.setPosition(LSPEclipseUtils.toPosition(offset, document)); - final var identifier = LSPEclipseUtils.toTextDocumentIdentifier(document); + + @SuppressWarnings("null") + List>> list = LanguageServers + .forDocument(document).withFilter(LSPRenameProcessor::isPrepareRenameProvider) + .collectAll((w, ls) -> ls.getTextDocumentService().prepareRename(params) + .thenApply(result -> new Pair<>(w, result))) + .get(1000, TimeUnit.MILLISECONDS); + + Optional>> tmp = list + .stream().filter(Objects::nonNull).filter(t -> t.getSecond() != null).findFirst(); + + if (tmp.isEmpty()) { + status.addFatalError(Messages.rename_invalidated); + } else { + tmp.ifPresent(p -> { + refactoringServer = p.getFirst(); + prepareRenameResult = p.getSecond(); + }); + } + } catch (TimeoutException e) { + LanguageServerPlugin.logWarning("Could not prepare rename due to timeout after 1 seconds in `textDocument/prepareRename`. 'newName' will be used", e); //$NON-NLS-1$ + } catch (Exception e) { + status.addFatalError(getErrorMessage(e)); + } + return status; + } + + public String getPlaceholder() { + @Nullable String placeholder = null; + if (prepareRenameResult != null) { + placeholder = prepareRenameResult.map(range -> { + try { + int startOffset = LSPEclipseUtils.toOffset(range.getStart(), document); + int endOffset = LSPEclipseUtils.toOffset(range.getEnd(), document); + return document.get(startOffset, endOffset - startOffset); + } catch (BadLocationException e) { + LanguageServerPlugin.logError(e); + return null; + } + }, PrepareRenameResult::getPlaceholder, + options -> null); + } + return placeholder != null && !placeholder.isBlank() ? placeholder :"newName"; //$NON-NLS-1$ + } + + public static boolean isPrepareRenameProvider(ServerCapabilities serverCapabilities) { + if (serverCapabilities == null) { + return false; + } + Either renameProvider = serverCapabilities.getRenameProvider(); + if (renameProvider == null) { + return false; + } + + if (renameProvider.isRight()) { + return renameProvider.getRight() != null && renameProvider.getRight().getPrepareProvider(); + } + return false; + } + + @Override + public RefactoringStatus checkFinalConditions(IProgressMonitor pm, CheckConditionsContext context) + throws CoreException, OperationCanceledException { + final var status = new RefactoringStatus(); + try { + final var params = new RenameParams(); + params.setPosition(LSPEclipseUtils.toPosition(offset, document)); + final var identifier = LSPEclipseUtils.toTextDocumentIdentifier(document); identifier.setUri(LSPEclipseUtils.toUri(document).toString()); - params.setTextDocument(identifier); - params.setNewName(newName); - if (params.getNewName() != null && refactoringServer != null) { - // TODO: how to manage ltk with CompletableFuture? Is 1000 ms is enough? - if (refactoringServer != null) { - rename = refactoringServer.execute(ls -> ls.getTextDocumentService().rename(params)).get(1000, TimeUnit.MILLISECONDS); - } else { - // Prepare timed out so we don't have a preferred server, so just try all the servers again - rename = LanguageServers.forDocument(document).withCapability(ServerCapabilities::getRenameProvider) - .computeFirst(ls -> ls.getTextDocumentService().rename(params)).get(1000, TimeUnit.MILLISECONDS).orElse(null); - } - if (!status.hasError() && (rename == null - || (rename.getChanges().isEmpty() && rename.getDocumentChanges().isEmpty()))) { - status.addWarning(Messages.rename_empty_message); - } - } - } catch (Exception e) { - status.addFatalError(getErrorMessage(e)); - } - return status; - } - - private String getErrorMessage(Throwable e) { - if (e.getCause() instanceof ResponseErrorException responseErrorException) { - ResponseError responseError = responseErrorException.getResponseError(); - return responseError.getMessage() - + ((responseError.getData() instanceof String data) ? (": " + data) : ""); //$NON-NLS-1$ //$NON-NLS-2$ - } else { - return e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName(); - } - } - - @Override - public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException { - if (rename == null) { - throw new CoreException( - new Status(IStatus.ERROR, LanguageServerPlugin.PLUGIN_ID, Messages.rename_processor_required)); - } - return LSPEclipseUtils.toCompositeChange(rename, Messages.rename_title); - } - - @Override - public RefactoringParticipant[] loadParticipants(RefactoringStatus status, SharableParticipants sharedParticipants) - throws CoreException { - return null; - } - - /** - * Set new name. - * - * @param newName - * the new name. - */ - public void setNewName(String newName) { - Assert.isNotNull(newName); - this.newName = newName; - } -} + params.setTextDocument(identifier); + params.setNewName(newName); + if (params.getNewName() != null && refactoringServer != null) { + // TODO: how to manage ltk with CompletableFuture? Is 1000 ms is enough? + if (refactoringServer != null) { + rename = refactoringServer.execute(ls -> ls.getTextDocumentService().rename(params)).get(1000, TimeUnit.MILLISECONDS); + } else { + // Prepare timed out so we don't have a preferred server, so just try all the servers again + rename = LanguageServers.forDocument(document).withCapability(ServerCapabilities::getRenameProvider) + .computeFirst(ls -> ls.getTextDocumentService().rename(params)).get(1000, TimeUnit.MILLISECONDS).orElse(null); + } + if (!status.hasError() && (rename == null + || (rename.getChanges().isEmpty() && rename.getDocumentChanges().isEmpty()))) { + status.addWarning(Messages.rename_empty_message); + } + } + } catch (Exception e) { + status.addFatalError(getErrorMessage(e)); + } + return status; + } + + private String getErrorMessage(Throwable e) { + if (e.getCause() instanceof ResponseErrorException responseErrorException) { + ResponseError responseError = responseErrorException.getResponseError(); + return responseError.getMessage() + + ((responseError.getData() instanceof String data) ? (": " + data) : ""); //$NON-NLS-1$ //$NON-NLS-2$ + } else { + return e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName(); + } + } + + @Override + public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException { + if (rename == null) { + throw new CoreException( + new Status(IStatus.ERROR, LanguageServerPlugin.PLUGIN_ID, Messages.rename_processor_required)); + } + return LSPEclipseUtils.toCompositeChange(rename, Messages.rename_title); + } + + @Override + public RefactoringParticipant[] loadParticipants(RefactoringStatus status, SharableParticipants sharedParticipants) + throws CoreException { + return null; + } + + /** + * Set new name. + * + * @param newName + * the new name. + */ + public void setNewName(String newName) { + Assert.isNotNull(newName); + this.newName = newName; + } +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/selectionRange/LSPSelectionRangeDownHandler.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/selectionRange/LSPSelectionRangeDownHandler.java index 13179bf34..da55d4ff2 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/selectionRange/LSPSelectionRangeDownHandler.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/selectionRange/LSPSelectionRangeDownHandler.java @@ -1,27 +1,27 @@ -package org.eclipse.lsp4e.operations.selectionRange; - -/******************************************************************************* - * Copyright (c) 2023 Red Hat Inc. and others. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Angelo ZERR (Red Hat Inc.) - initial implementation - *******************************************************************************/ -import org.eclipse.lsp4e.operations.selectionRange.LSPSelectionRangeAbstractHandler.SelectionRangeHandler.Direction; -/** - * Selection range DOWN handler. - * - * @author Angelo ZERR - * - */ -public class LSPSelectionRangeDownHandler extends LSPSelectionRangeAbstractHandler { - - @Override - protected Direction getDirection() { - return Direction.DOWN; - } -} +package org.eclipse.lsp4e.operations.selectionRange; + +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Angelo ZERR (Red Hat Inc.) - initial implementation + *******************************************************************************/ +import org.eclipse.lsp4e.operations.selectionRange.LSPSelectionRangeAbstractHandler.SelectionRangeHandler.Direction; +/** + * Selection range DOWN handler. + * + * @author Angelo ZERR + * + */ +public class LSPSelectionRangeDownHandler extends LSPSelectionRangeAbstractHandler { + + @Override + protected Direction getDirection() { + return Direction.DOWN; + } +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/selectionRange/LSPSelectionRangeUpHandler.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/selectionRange/LSPSelectionRangeUpHandler.java index b37985694..7df8f1609 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/selectionRange/LSPSelectionRangeUpHandler.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/selectionRange/LSPSelectionRangeUpHandler.java @@ -1,28 +1,28 @@ -/******************************************************************************* - * Copyright (c) 2023 Red Hat Inc. and others. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Angelo ZERR (Red Hat Inc.) - initial implementation - *******************************************************************************/ -package org.eclipse.lsp4e.operations.selectionRange; - -import org.eclipse.lsp4e.operations.selectionRange.LSPSelectionRangeAbstractHandler.SelectionRangeHandler.Direction; - -/** - * Selection range UP handler. - * - * @author Angelo ZERR - * - */ -public class LSPSelectionRangeUpHandler extends LSPSelectionRangeAbstractHandler { - - @Override - protected Direction getDirection() { - return Direction.UP; - } -} +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Angelo ZERR (Red Hat Inc.) - initial implementation + *******************************************************************************/ +package org.eclipse.lsp4e.operations.selectionRange; + +import org.eclipse.lsp4e.operations.selectionRange.LSPSelectionRangeAbstractHandler.SelectionRangeHandler.Direction; + +/** + * Selection range UP handler. + * + * @author Angelo ZERR + * + */ +public class LSPSelectionRangeUpHandler extends LSPSelectionRangeAbstractHandler { + + @Override + protected Direction getDirection() { + return Direction.UP; + } +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/semanticTokens/SemanticTokensDataStreamProcessor.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/semanticTokens/SemanticTokensDataStreamProcessor.java index 336a4cd76..0647a1f77 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/semanticTokens/SemanticTokensDataStreamProcessor.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/semanticTokens/SemanticTokensDataStreamProcessor.java @@ -1,167 +1,167 @@ -/******************************************************************************* - * Copyright (c) 2022, 2024 Avaloq Group AG. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - *******************************************************************************/ -package org.eclipse.lsp4e.operations.semanticTokens; - -import java.util.ArrayList; -import java.util.BitSet; -import java.util.Collections; -import java.util.List; -import java.util.function.Function; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jface.text.TextAttribute; -import org.eclipse.jface.text.rules.IToken; -import org.eclipse.lsp4e.internal.StyleUtil; -import org.eclipse.lsp4j.Position; -import org.eclipse.lsp4j.SemanticTokenModifiers; -import org.eclipse.lsp4j.SemanticTokensLegend; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.StyleRange; - -/** - * The Class SemanticTokensDataStreamProcessor translates a stream of integers - * as defined by the LSP SemanticTokenRequests into a list of StyleRanges. - */ -public class SemanticTokensDataStreamProcessor { - - private final Function offsetMapper; - private final Function tokenTypeMapper; - - /** - * Creates a new instance of {@link SemanticTokensDataStreamProcessor}. - * - * @param tokenTypeMapper - * @param offsetMapper - */ - public SemanticTokensDataStreamProcessor(@NonNull final Function tokenTypeMapper, - @NonNull final Function offsetMapper) { - this.tokenTypeMapper = tokenTypeMapper; - this.offsetMapper = offsetMapper; - } - - /** - * Get the StyleRanges for the given data stream and tokens legend. - * - * @param dataStream - * @param semanticTokensLegend - * @return - */ - public @NonNull List getStyleRanges(@NonNull final List dataStream, - @NonNull final SemanticTokensLegend semanticTokensLegend) { - final var styleRanges = new ArrayList(dataStream.size() / 5); - - int idx = 0; - int prevLine = 0; - int line = 0; - int offset = 0; - int length = 0; - String tokenType = null; - for (Integer data : dataStream) { - switch (idx % 5) { - case 0: // line - line += data; - break; - case 1: // offset - if (line == prevLine) { - offset += data; - } else { - offset = offsetMapper.apply(new Position(line, data)); - } - break; - case 2: // length - length = data; - break; - case 3: // token type - tokenType = tokenType(data, semanticTokensLegend.getTokenTypes()); - break; - case 4: // token modifier - prevLine = line; - List tokenModifiers = tokenModifiers(data, semanticTokensLegend.getTokenModifiers()); - StyleRange styleRange = getStyleRange(offset, length, textAttribute(tokenType)); - if (tokenModifiers.stream().anyMatch(x -> x.equals(SemanticTokenModifiers.Deprecated))) { - if (styleRange == null) { - styleRange = new StyleRange(); - styleRange.start = offset; - styleRange.length = length; - } - StyleUtil.DEPRECATE.applyStyles(styleRange); - } - if (styleRange != null) { - styleRanges.add(styleRange); - } - break; - } - idx++; - } - return styleRanges; - } - - private String tokenType(final Integer data, final List legend) { - try { - return legend.get(data); - } catch (IndexOutOfBoundsException e) { - return null; // no match - } - } - - private List tokenModifiers(final Integer data, final List legend) { - if (data.intValue() == 0) { - return Collections.emptyList(); - } - final var bitSet = BitSet.valueOf(new long[] { data }); - final var tokenModifiers = new ArrayList(); - for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) { - try { - tokenModifiers.add(legend.get(i)); - } catch (IndexOutOfBoundsException e) { - // no match - } - } - - return tokenModifiers; - } - - private TextAttribute textAttribute(final String tokenType) { - if (tokenType != null) { - IToken token = tokenTypeMapper.apply(tokenType); - if (token != null) { - Object data = token.getData(); - if (data instanceof final TextAttribute textAttribute) { - return textAttribute; - } - } - } - return null; - } - - /** - * Gets a style range for the given inputs. - * - * @param offset - * the offset of the range to be styled - * @param length - * the length of the range to be styled - * @param attr - * the attribute describing the style of the range to be styled - */ - private @Nullable StyleRange getStyleRange(final int offset, final int length, final TextAttribute attr) { - if (attr != null) { - final int style = attr.getStyle(); - final int fontStyle = style & (SWT.ITALIC | SWT.BOLD | SWT.NORMAL); - final StyleRange styleRange = new StyleRange(offset, length, attr.getForeground(), attr.getBackground(), fontStyle); - styleRange.strikeout = (style & TextAttribute.STRIKETHROUGH) != 0; - styleRange.underline = (style & TextAttribute.UNDERLINE) != 0; - styleRange.font = attr.getFont(); - return styleRange; - } - return null; - } +/******************************************************************************* + * Copyright (c) 2022, 2024 Avaloq Group AG. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + *******************************************************************************/ +package org.eclipse.lsp4e.operations.semanticTokens; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jface.text.TextAttribute; +import org.eclipse.jface.text.rules.IToken; +import org.eclipse.lsp4e.internal.StyleUtil; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.SemanticTokenModifiers; +import org.eclipse.lsp4j.SemanticTokensLegend; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StyleRange; + +/** + * The Class SemanticTokensDataStreamProcessor translates a stream of integers + * as defined by the LSP SemanticTokenRequests into a list of StyleRanges. + */ +public class SemanticTokensDataStreamProcessor { + + private final Function offsetMapper; + private final Function tokenTypeMapper; + + /** + * Creates a new instance of {@link SemanticTokensDataStreamProcessor}. + * + * @param tokenTypeMapper + * @param offsetMapper + */ + public SemanticTokensDataStreamProcessor(@NonNull final Function tokenTypeMapper, + @NonNull final Function offsetMapper) { + this.tokenTypeMapper = tokenTypeMapper; + this.offsetMapper = offsetMapper; + } + + /** + * Get the StyleRanges for the given data stream and tokens legend. + * + * @param dataStream + * @param semanticTokensLegend + * @return + */ + public @NonNull List getStyleRanges(@NonNull final List dataStream, + @NonNull final SemanticTokensLegend semanticTokensLegend) { + final var styleRanges = new ArrayList(dataStream.size() / 5); + + int idx = 0; + int prevLine = 0; + int line = 0; + int offset = 0; + int length = 0; + String tokenType = null; + for (Integer data : dataStream) { + switch (idx % 5) { + case 0: // line + line += data; + break; + case 1: // offset + if (line == prevLine) { + offset += data; + } else { + offset = offsetMapper.apply(new Position(line, data)); + } + break; + case 2: // length + length = data; + break; + case 3: // token type + tokenType = tokenType(data, semanticTokensLegend.getTokenTypes()); + break; + case 4: // token modifier + prevLine = line; + List tokenModifiers = tokenModifiers(data, semanticTokensLegend.getTokenModifiers()); + StyleRange styleRange = getStyleRange(offset, length, textAttribute(tokenType)); + if (tokenModifiers.stream().anyMatch(x -> x.equals(SemanticTokenModifiers.Deprecated))) { + if (styleRange == null) { + styleRange = new StyleRange(); + styleRange.start = offset; + styleRange.length = length; + } + StyleUtil.DEPRECATE.applyStyles(styleRange); + } + if (styleRange != null) { + styleRanges.add(styleRange); + } + break; + } + idx++; + } + return styleRanges; + } + + private String tokenType(final Integer data, final List legend) { + try { + return legend.get(data); + } catch (IndexOutOfBoundsException e) { + return null; // no match + } + } + + private List tokenModifiers(final Integer data, final List legend) { + if (data.intValue() == 0) { + return Collections.emptyList(); + } + final var bitSet = BitSet.valueOf(new long[] { data }); + final var tokenModifiers = new ArrayList(); + for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) { + try { + tokenModifiers.add(legend.get(i)); + } catch (IndexOutOfBoundsException e) { + // no match + } + } + + return tokenModifiers; + } + + private TextAttribute textAttribute(final String tokenType) { + if (tokenType != null) { + IToken token = tokenTypeMapper.apply(tokenType); + if (token != null) { + Object data = token.getData(); + if (data instanceof final TextAttribute textAttribute) { + return textAttribute; + } + } + } + return null; + } + + /** + * Gets a style range for the given inputs. + * + * @param offset + * the offset of the range to be styled + * @param length + * the length of the range to be styled + * @param attr + * the attribute describing the style of the range to be styled + */ + private @Nullable StyleRange getStyleRange(final int offset, final int length, final TextAttribute attr) { + if (attr != null) { + final int style = attr.getStyle(); + final int fontStyle = style & (SWT.ITALIC | SWT.BOLD | SWT.NORMAL); + final StyleRange styleRange = new StyleRange(offset, length, attr.getForeground(), attr.getBackground(), fontStyle); + styleRange.strikeout = (style & TextAttribute.STRIKETHROUGH) != 0; + styleRange.underline = (style & TextAttribute.UNDERLINE) != 0; + styleRange.font = attr.getFont(); + return styleRange; + } + return null; + } } \ No newline at end of file diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/semanticTokens/StyleRangeHolder.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/semanticTokens/StyleRangeHolder.java index 92b5e70b2..ee8fe745f 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/semanticTokens/StyleRangeHolder.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/semanticTokens/StyleRangeHolder.java @@ -1,95 +1,95 @@ -/******************************************************************************* - * Copyright (c) 2022 Avaloq Group AG. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - *******************************************************************************/ -package org.eclipse.lsp4e.operations.semanticTokens; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jface.text.IRegion; -import org.eclipse.jface.text.ITextListener; -import org.eclipse.jface.text.Region; -import org.eclipse.jface.text.TextEvent; -import org.eclipse.jface.text.TextUtilities; -import org.eclipse.swt.custom.StyleRange; - -/** - * The Class SemanticTokensDataStreamProcessor holds a list of StyleRanges. - *

    - * To avoid flickering, we also implement {@link ITextListener} to adapt (the - * only adaptation currently supported shifting ranges) recorded semantic - * highlights When the user writes a single or multi-line comments shifting is - * not enough. That could be improved if we can access - * org.eclipse.tm4e.languageconfiguration.ILanguageConfiguration.getComments() - * (still unclear on how to do that). - */ -public class StyleRangeHolder implements ITextListener { - private final List previousRanges; - - public StyleRangeHolder() { - previousRanges = new ArrayList<>(); - } - - /** - * save the styles. - * - * @param styleRanges - */ - public void saveStyles(@NonNull final List styleRanges) { - synchronized (previousRanges) { - previousRanges.clear(); - previousRanges.addAll(styleRanges); - previousRanges.sort(Comparator.comparing(s -> s.start)); - } - } - - /** - * return a copy of the saved styles that overlap the given region. - * - * @param region - * @return - */ - public StyleRange[] overlappingRanges(@NonNull final IRegion region) { - synchronized (previousRanges) { - // we need to create new styles because the text presentation might change a - // style when applied to the presentation - // and we want the ones saved from the reconciling as immutable - return previousRanges.stream()// - .filter(r -> TextUtilities.overlaps(region, new Region(r.start, r.length)))// - .map(this::clone).toArray(StyleRange[]::new); - } - } - - private StyleRange clone(final StyleRange styleRange) { - final var clonedStyleRange = new StyleRange(styleRange.start, styleRange.length, styleRange.foreground, - styleRange.background, styleRange.fontStyle); - clonedStyleRange.strikeout = styleRange.strikeout; - return clonedStyleRange; - } - - private boolean isContained(final int offset, final IRegion region) { - return offset >= region.getOffset() && offset < (region.getOffset() + region.getLength()); - } - - @Override - public void textChanged(final TextEvent event) { - if (event.getDocumentEvent() != null) { // if null, it is an internal event, not a changed text - String replacedText = event.getReplacedText(); - String text = event.getText(); - int delta = (text != null ? text.length() : 0) - (replacedText != null ? replacedText.length() : 0); - synchronized (previousRanges) { - previousRanges.removeIf(r -> isContained(event.getOffset(), new Region(r.start, r.length))); - previousRanges.stream().filter(r -> r.start >= event.getOffset()).forEach(r -> r.start += delta); - } - } - } - -} +/******************************************************************************* + * Copyright (c) 2022 Avaloq Group AG. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + *******************************************************************************/ +package org.eclipse.lsp4e.operations.semanticTokens; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextListener; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextEvent; +import org.eclipse.jface.text.TextUtilities; +import org.eclipse.swt.custom.StyleRange; + +/** + * The Class SemanticTokensDataStreamProcessor holds a list of StyleRanges. + *

    + * To avoid flickering, we also implement {@link ITextListener} to adapt (the + * only adaptation currently supported shifting ranges) recorded semantic + * highlights When the user writes a single or multi-line comments shifting is + * not enough. That could be improved if we can access + * org.eclipse.tm4e.languageconfiguration.ILanguageConfiguration.getComments() + * (still unclear on how to do that). + */ +public class StyleRangeHolder implements ITextListener { + private final List previousRanges; + + public StyleRangeHolder() { + previousRanges = new ArrayList<>(); + } + + /** + * save the styles. + * + * @param styleRanges + */ + public void saveStyles(@NonNull final List styleRanges) { + synchronized (previousRanges) { + previousRanges.clear(); + previousRanges.addAll(styleRanges); + previousRanges.sort(Comparator.comparing(s -> s.start)); + } + } + + /** + * return a copy of the saved styles that overlap the given region. + * + * @param region + * @return + */ + public StyleRange[] overlappingRanges(@NonNull final IRegion region) { + synchronized (previousRanges) { + // we need to create new styles because the text presentation might change a + // style when applied to the presentation + // and we want the ones saved from the reconciling as immutable + return previousRanges.stream()// + .filter(r -> TextUtilities.overlaps(region, new Region(r.start, r.length)))// + .map(this::clone).toArray(StyleRange[]::new); + } + } + + private StyleRange clone(final StyleRange styleRange) { + final var clonedStyleRange = new StyleRange(styleRange.start, styleRange.length, styleRange.foreground, + styleRange.background, styleRange.fontStyle); + clonedStyleRange.strikeout = styleRange.strikeout; + return clonedStyleRange; + } + + private boolean isContained(final int offset, final IRegion region) { + return offset >= region.getOffset() && offset < (region.getOffset() + region.getLength()); + } + + @Override + public void textChanged(final TextEvent event) { + if (event.getDocumentEvent() != null) { // if null, it is an internal event, not a changed text + String replacedText = event.getReplacedText(); + String text = event.getText(); + int delta = (text != null ? text.length() : 0) - (replacedText != null ? replacedText.length() : 0); + synchronized (previousRanges) { + previousRanges.removeIf(r -> isContained(event.getOffset(), new Region(r.start, r.length))); + previousRanges.stream().filter(r -> r.start >= event.getOffset()).forEach(r -> r.start += delta); + } + } + } + +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/semanticTokens/TokenTypeMapper.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/semanticTokens/TokenTypeMapper.java index 327e0569e..2a6ee0d60 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/semanticTokens/TokenTypeMapper.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/semanticTokens/TokenTypeMapper.java @@ -1,47 +1,47 @@ -/******************************************************************************* - * Copyright (c) 2022 Avaloq Group AG. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - *******************************************************************************/ -package org.eclipse.lsp4e.operations.semanticTokens; - -import java.util.function.Function; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jface.text.ITextViewer; -import org.eclipse.jface.text.rules.IToken; -import org.eclipse.tm4e.ui.TMUIPlugin; -import org.eclipse.tm4e.ui.text.TMPresentationReconciler; -import org.eclipse.tm4e.ui.themes.ITokenProvider; - -/** - * A Class that maps TokenTypes to {@link IToken}. - */ -public class TokenTypeMapper implements Function { - private @NonNull final ITextViewer viewer; - - public TokenTypeMapper(@NonNull final ITextViewer viewer) { - this.viewer = viewer; - } - - @Override - public IToken apply(final String tokenType) { - if (tokenType == null) { - return null; - } - TMPresentationReconciler tmPresentationReconciler = TMPresentationReconciler - .getTMPresentationReconciler(viewer); - - if (tmPresentationReconciler != null) { - ITokenProvider tokenProvider = tmPresentationReconciler.getTokenProvider(); - if (tokenProvider != null) { - return tokenProvider.getToken(tokenType); - } - } - return TMUIPlugin.getThemeManager().getDefaultTheme().getToken(tokenType); - } -} +/******************************************************************************* + * Copyright (c) 2022 Avaloq Group AG. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + *******************************************************************************/ +package org.eclipse.lsp4e.operations.semanticTokens; + +import java.util.function.Function; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.rules.IToken; +import org.eclipse.tm4e.ui.TMUIPlugin; +import org.eclipse.tm4e.ui.text.TMPresentationReconciler; +import org.eclipse.tm4e.ui.themes.ITokenProvider; + +/** + * A Class that maps TokenTypes to {@link IToken}. + */ +public class TokenTypeMapper implements Function { + private @NonNull final ITextViewer viewer; + + public TokenTypeMapper(@NonNull final ITextViewer viewer) { + this.viewer = viewer; + } + + @Override + public IToken apply(final String tokenType) { + if (tokenType == null) { + return null; + } + TMPresentationReconciler tmPresentationReconciler = TMPresentationReconciler + .getTMPresentationReconciler(viewer); + + if (tmPresentationReconciler != null) { + ITokenProvider tokenProvider = tmPresentationReconciler.getTokenProvider(); + if (tokenProvider != null) { + return tokenProvider.getToken(tokenType); + } + } + return TMUIPlugin.getThemeManager().getDefaultTheme().getToken(tokenType); + } +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/progress/LSPProgressManager.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/progress/LSPProgressManager.java index f7976ce04..d32e977b3 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/progress/LSPProgressManager.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/progress/LSPProgressManager.java @@ -1,207 +1,207 @@ -/******************************************************************************* - * Copyright (c) 2022, 2023 Avaloq Evolution AG. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Rubén Porras Campo (Avaloq Evolution AG) - initial implementation - *******************************************************************************/ -package org.eclipse.lsp4e.progress; - -import java.util.Map; -import java.util.Set; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentSkipListSet; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; - -import org.eclipse.core.runtime.ICoreRunnable; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.OperationCanceledException; -import org.eclipse.core.runtime.jobs.IJobChangeEvent; -import org.eclipse.core.runtime.jobs.Job; -import org.eclipse.core.runtime.jobs.JobChangeAdapter; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.lsp4e.LanguageServerPlugin; -import org.eclipse.lsp4e.LanguageServersRegistry.LanguageServerDefinition; -import org.eclipse.lsp4e.ui.Messages; -import org.eclipse.lsp4j.ProgressParams; -import org.eclipse.lsp4j.WorkDoneProgressBegin; -import org.eclipse.lsp4j.WorkDoneProgressCancelParams; -import org.eclipse.lsp4j.WorkDoneProgressCreateParams; -import org.eclipse.lsp4j.WorkDoneProgressEnd; -import org.eclipse.lsp4j.WorkDoneProgressKind; -import org.eclipse.lsp4j.WorkDoneProgressNotification; -import org.eclipse.lsp4j.WorkDoneProgressReport; -import org.eclipse.lsp4j.services.LanguageServer; - -public class LSPProgressManager { - private final Map> progressMap; - private final Map currentPercentageMap; - private LanguageServer languageServer; - private LanguageServerDefinition languageServerDefinition; - private final Set done; - private final Set jobs; - - public LSPProgressManager() { - this.progressMap = new ConcurrentHashMap<>(); - this.currentPercentageMap = new ConcurrentHashMap<>(); - this.done = new ConcurrentSkipListSet<>(); - this.jobs = new ConcurrentSkipListSet<>(); - } - - public void connect(final LanguageServer languageServer, LanguageServerDefinition languageServerDefinition) { - this.languageServer = languageServer; - this.languageServerDefinition = languageServerDefinition; - } - /** - * Creates the progress. - * - * @param params - * the {@link WorkDoneProgressCreateParams} to be used to create the progress - * @return the completable future - */ - public @NonNull CompletableFuture createProgress(final @NonNull WorkDoneProgressCreateParams params) { - final var queue = new LinkedBlockingDeque(); - - String jobIdentifier = params.getToken().map(Function.identity(), Object::toString); - BlockingQueue oldQueue = progressMap.put(jobIdentifier, queue); - if (oldQueue != null) { - LanguageServerPlugin.logInfo( - "Old progress with identifier " + jobIdentifier + " discarded due to new create progress request"); //$NON-NLS-1$//$NON-NLS-2$ - } - createJob(queue, jobIdentifier); - return CompletableFuture.completedFuture(null); - } - - private void createJob(final LinkedBlockingDeque queue, final String jobIdentifier) { - final var languageServer = this.languageServer; - final var languageServerDefinition = this.languageServerDefinition; - - final var jobName = languageServerDefinition == null // - || languageServerDefinition.label == null || languageServerDefinition.label.isBlank() // - ? Messages.LSPProgressManager_BackgroundJobName - : languageServerDefinition.label; - Job job = Job.create(jobName, (ICoreRunnable) monitor -> { - try { - while (true) { - if (monitor.isCanceled()) { - progressMap.remove(jobIdentifier); - currentPercentageMap.remove(monitor); - if (languageServer != null) { - final var workDoneProgressCancelParams = new WorkDoneProgressCancelParams(); - workDoneProgressCancelParams.setToken(jobIdentifier); - languageServer.cancelProgress(workDoneProgressCancelParams); - } - throw new OperationCanceledException(); - } - ProgressParams nextProgressNotification = queue.pollFirst(1, TimeUnit.SECONDS); - if (nextProgressNotification != null ) { - WorkDoneProgressNotification progressNotification = nextProgressNotification.getValue().getLeft(); - if (progressNotification != null) { - WorkDoneProgressKind kind = progressNotification.getKind(); - if (kind == WorkDoneProgressKind.begin) { - begin((WorkDoneProgressBegin) progressNotification, monitor); - } else if (kind == WorkDoneProgressKind.report) { - report((WorkDoneProgressReport) progressNotification, monitor); - } else if (kind == WorkDoneProgressKind.end) { - end((WorkDoneProgressEnd) progressNotification, monitor); - progressMap.remove(jobIdentifier); - currentPercentageMap.remove(monitor); - return; - } - } - } else if (done.remove(jobIdentifier)) { - monitor.done(); - } - } - } catch (InterruptedException e) { - LanguageServerPlugin.logError(e); - Thread.currentThread().interrupt(); - } - }); - jobs.add(job); - Job.getJobManager().addJobChangeListener(new JobChangeAdapter() { - @Override - public void done(IJobChangeEvent event) { - jobs.remove(event.getJob()); - } - }); - job.schedule(); - } - - private void begin(final WorkDoneProgressBegin begin, final IProgressMonitor monitor) { - Integer percentage = begin.getPercentage(); - if (percentage != null) { - if (percentage == 0) { - monitor.beginTask(begin.getTitle(), 100); - } else { - monitor.beginTask(begin.getTitle(), percentage); - } - currentPercentageMap.put(monitor, 0); - } else { - monitor.beginTask(begin.getTitle(), IProgressMonitor.UNKNOWN); - } - - String message = begin.getMessage(); - if (message != null && !message.isBlank()) { - monitor.subTask(message); - } - } - - private void end(final WorkDoneProgressEnd end, final IProgressMonitor monitor) { - monitor.subTask(end.getMessage()); - monitor.done(); - } - - private void report(final WorkDoneProgressReport report, final IProgressMonitor monitor) { - if (report.getMessage() != null && !report.getMessage().isBlank()) { - monitor.subTask(report.getMessage()); - } - - if (report.getPercentage() != null) { - if (currentPercentageMap.containsKey(monitor)) { - Integer percentage = currentPercentageMap.get(monitor); - int worked = percentage != null ? Math.min(percentage, report.getPercentage()) : 0; - monitor.worked(report.getPercentage().intValue() - worked); - } - - currentPercentageMap.put(monitor, report.getPercentage()); - } - } - - /** - * Notify progress. - * - * @param params - * the {@link ProgressParams} used for the progress notification - */ - public void notifyProgress(final @NonNull ProgressParams params) { - String jobIdentifier = params.getToken().map(Function.identity(), Object::toString); - BlockingQueue progress = progressMap.get(jobIdentifier); - if (progress != null) { // may happen if the server does not wait on the return value of the future of createProgress - progress.add(params); - } else { - WorkDoneProgressNotification progressNotification = params.getValue().getLeft(); - if (progressNotification != null && progressNotification.getKind() == WorkDoneProgressKind.end) { - done.add(jobIdentifier); - } - } - } - - /** - * Dispose the progress manager. - */ - public void dispose() { - jobs.forEach(Job::cancel); - currentPercentageMap.clear(); - progressMap.clear(); - done.clear(); - } -} +/******************************************************************************* + * Copyright (c) 2022, 2023 Avaloq Evolution AG. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Rubén Porras Campo (Avaloq Evolution AG) - initial implementation + *******************************************************************************/ +package org.eclipse.lsp4e.progress; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import org.eclipse.core.runtime.ICoreRunnable; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.lsp4e.LanguageServerPlugin; +import org.eclipse.lsp4e.LanguageServersRegistry.LanguageServerDefinition; +import org.eclipse.lsp4e.ui.Messages; +import org.eclipse.lsp4j.ProgressParams; +import org.eclipse.lsp4j.WorkDoneProgressBegin; +import org.eclipse.lsp4j.WorkDoneProgressCancelParams; +import org.eclipse.lsp4j.WorkDoneProgressCreateParams; +import org.eclipse.lsp4j.WorkDoneProgressEnd; +import org.eclipse.lsp4j.WorkDoneProgressKind; +import org.eclipse.lsp4j.WorkDoneProgressNotification; +import org.eclipse.lsp4j.WorkDoneProgressReport; +import org.eclipse.lsp4j.services.LanguageServer; + +public class LSPProgressManager { + private final Map> progressMap; + private final Map currentPercentageMap; + private LanguageServer languageServer; + private LanguageServerDefinition languageServerDefinition; + private final Set done; + private final Set jobs; + + public LSPProgressManager() { + this.progressMap = new ConcurrentHashMap<>(); + this.currentPercentageMap = new ConcurrentHashMap<>(); + this.done = new ConcurrentSkipListSet<>(); + this.jobs = new ConcurrentSkipListSet<>(); + } + + public void connect(final LanguageServer languageServer, LanguageServerDefinition languageServerDefinition) { + this.languageServer = languageServer; + this.languageServerDefinition = languageServerDefinition; + } + /** + * Creates the progress. + * + * @param params + * the {@link WorkDoneProgressCreateParams} to be used to create the progress + * @return the completable future + */ + public @NonNull CompletableFuture createProgress(final @NonNull WorkDoneProgressCreateParams params) { + final var queue = new LinkedBlockingDeque(); + + String jobIdentifier = params.getToken().map(Function.identity(), Object::toString); + BlockingQueue oldQueue = progressMap.put(jobIdentifier, queue); + if (oldQueue != null) { + LanguageServerPlugin.logInfo( + "Old progress with identifier " + jobIdentifier + " discarded due to new create progress request"); //$NON-NLS-1$//$NON-NLS-2$ + } + createJob(queue, jobIdentifier); + return CompletableFuture.completedFuture(null); + } + + private void createJob(final LinkedBlockingDeque queue, final String jobIdentifier) { + final var languageServer = this.languageServer; + final var languageServerDefinition = this.languageServerDefinition; + + final var jobName = languageServerDefinition == null // + || languageServerDefinition.label == null || languageServerDefinition.label.isBlank() // + ? Messages.LSPProgressManager_BackgroundJobName + : languageServerDefinition.label; + Job job = Job.create(jobName, (ICoreRunnable) monitor -> { + try { + while (true) { + if (monitor.isCanceled()) { + progressMap.remove(jobIdentifier); + currentPercentageMap.remove(monitor); + if (languageServer != null) { + final var workDoneProgressCancelParams = new WorkDoneProgressCancelParams(); + workDoneProgressCancelParams.setToken(jobIdentifier); + languageServer.cancelProgress(workDoneProgressCancelParams); + } + throw new OperationCanceledException(); + } + ProgressParams nextProgressNotification = queue.pollFirst(1, TimeUnit.SECONDS); + if (nextProgressNotification != null ) { + WorkDoneProgressNotification progressNotification = nextProgressNotification.getValue().getLeft(); + if (progressNotification != null) { + WorkDoneProgressKind kind = progressNotification.getKind(); + if (kind == WorkDoneProgressKind.begin) { + begin((WorkDoneProgressBegin) progressNotification, monitor); + } else if (kind == WorkDoneProgressKind.report) { + report((WorkDoneProgressReport) progressNotification, monitor); + } else if (kind == WorkDoneProgressKind.end) { + end((WorkDoneProgressEnd) progressNotification, monitor); + progressMap.remove(jobIdentifier); + currentPercentageMap.remove(monitor); + return; + } + } + } else if (done.remove(jobIdentifier)) { + monitor.done(); + } + } + } catch (InterruptedException e) { + LanguageServerPlugin.logError(e); + Thread.currentThread().interrupt(); + } + }); + jobs.add(job); + Job.getJobManager().addJobChangeListener(new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + jobs.remove(event.getJob()); + } + }); + job.schedule(); + } + + private void begin(final WorkDoneProgressBegin begin, final IProgressMonitor monitor) { + Integer percentage = begin.getPercentage(); + if (percentage != null) { + if (percentage == 0) { + monitor.beginTask(begin.getTitle(), 100); + } else { + monitor.beginTask(begin.getTitle(), percentage); + } + currentPercentageMap.put(monitor, 0); + } else { + monitor.beginTask(begin.getTitle(), IProgressMonitor.UNKNOWN); + } + + String message = begin.getMessage(); + if (message != null && !message.isBlank()) { + monitor.subTask(message); + } + } + + private void end(final WorkDoneProgressEnd end, final IProgressMonitor monitor) { + monitor.subTask(end.getMessage()); + monitor.done(); + } + + private void report(final WorkDoneProgressReport report, final IProgressMonitor monitor) { + if (report.getMessage() != null && !report.getMessage().isBlank()) { + monitor.subTask(report.getMessage()); + } + + if (report.getPercentage() != null) { + if (currentPercentageMap.containsKey(monitor)) { + Integer percentage = currentPercentageMap.get(monitor); + int worked = percentage != null ? Math.min(percentage, report.getPercentage()) : 0; + monitor.worked(report.getPercentage().intValue() - worked); + } + + currentPercentageMap.put(monitor, report.getPercentage()); + } + } + + /** + * Notify progress. + * + * @param params + * the {@link ProgressParams} used for the progress notification + */ + public void notifyProgress(final @NonNull ProgressParams params) { + String jobIdentifier = params.getToken().map(Function.identity(), Object::toString); + BlockingQueue progress = progressMap.get(jobIdentifier); + if (progress != null) { // may happen if the server does not wait on the return value of the future of createProgress + progress.add(params); + } else { + WorkDoneProgressNotification progressNotification = params.getValue().getLeft(); + if (progressNotification != null && progressNotification.getKind() == WorkDoneProgressKind.end) { + done.add(jobIdentifier); + } + } + } + + /** + * Dispose the progress manager. + */ + public void dispose() { + jobs.forEach(Job::cancel); + currentPercentageMap.clear(); + progressMap.clear(); + done.clear(); + } +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/views/HierarchyViewInput.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/views/HierarchyViewInput.java index 1d4b3d6e0..ddcd2aad2 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/views/HierarchyViewInput.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/views/HierarchyViewInput.java @@ -1,57 +1,57 @@ -/******************************************************************************* - * Copyright (c) 2022 Avaloq Group AG (http://www.avaloq.com). - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Andrew Lamb (Avaloq Group AG) - Initial implementation - * Gesa Hentschke - made the class generic - *******************************************************************************/ - -package org.eclipse.lsp4e.ui.views; - -import org.eclipse.jface.text.IDocument; - -/** - * Simple type representing the input to a hierarchy view. - */ -public class HierarchyViewInput { - private final IDocument document; - private final int offset; - - /** - * Creates a new instance of {@link HierarchyViewInput}. - * - * @param document - * the document containing the selection to start a hierarchy - * from. - * @param offset - * the offset into the document to select as the root of the - * hierarchy. - */ - public HierarchyViewInput(final IDocument document, final int offset) { - this.document = document; - this.offset = offset; - } - - /** - * Get the document containing the selection to start a hierarchy from. - * - * @return the document containing the selection to start a call hierarchy from. - */ - public IDocument getDocument() { - return document; - } - - /** - * Get the offset into the document to select as the root of the hierarchy. - * - * @return the offset into the document to select as the root of the hierarchy. - */ - public int getOffset() { - return offset; - } -} +/******************************************************************************* + * Copyright (c) 2022 Avaloq Group AG (http://www.avaloq.com). + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Andrew Lamb (Avaloq Group AG) - Initial implementation + * Gesa Hentschke - made the class generic + *******************************************************************************/ + +package org.eclipse.lsp4e.ui.views; + +import org.eclipse.jface.text.IDocument; + +/** + * Simple type representing the input to a hierarchy view. + */ +public class HierarchyViewInput { + private final IDocument document; + private final int offset; + + /** + * Creates a new instance of {@link HierarchyViewInput}. + * + * @param document + * the document containing the selection to start a hierarchy + * from. + * @param offset + * the offset into the document to select as the root of the + * hierarchy. + */ + public HierarchyViewInput(final IDocument document, final int offset) { + this.document = document; + this.offset = offset; + } + + /** + * Get the document containing the selection to start a hierarchy from. + * + * @return the document containing the selection to start a call hierarchy from. + */ + public IDocument getDocument() { + return document; + } + + /** + * Get the offset into the document to select as the root of the hierarchy. + * + * @return the offset into the document to select as the root of the hierarchy. + */ + public int getOffset() { + return offset; + } +}