Skip to content

Commit

Permalink
Support for textDocument/selectionRange
Browse files Browse the repository at this point in the history
Fixes #561
  • Loading branch information
angelozerr committed Mar 20, 2023
1 parent 12f6b41 commit 1224def
Show file tree
Hide file tree
Showing 7 changed files with 366 additions and 9 deletions.
62 changes: 62 additions & 0 deletions org.eclipse.lsp4e/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,18 @@
id="org.eclipse.lsp4e.openCallHierarchy"
name="%command.open.call.hierarchy.name">
</command>
<command
categoryId="org.eclipse.lsp4e.category"
description="%command.selection.range.up.decription"
id="org.eclipse.lsp4e.selectionRange.up"
name="%command.selection.range.up.name">
</command>
<command
categoryId="org.eclipse.lsp4e.category"
description="%command.selection.range.down.decription"
id="org.eclipse.lsp4e.selectionRange.down"
name="%command.selection.range.down.name">
</command>
</extension>
<extension
point="org.eclipse.ui.handlers">
Expand Down Expand Up @@ -156,6 +168,20 @@
class="org.eclipse.lsp4e.callhierarchy.CallHierarchyCommandHandler"
commandId="org.eclipse.lsp4e.openCallHierarchy">
</handler>
<handler
class="org.eclipse.lsp4e.operations.selectionRange.LSPSelectionRangeUpHandler"
commandId="org.eclipse.lsp4e.selectionRange.up">
<activeWhen>
<reference definitionId="org.eclipse.lsp4e.textSelectionHasLanguageServer" />
</activeWhen>
</handler>
<handler
class="org.eclipse.lsp4e.operations.selectionRange.LSPSelectionRangeDownHandler"
commandId="org.eclipse.lsp4e.selectionRange.down">
<activeWhen>
<reference definitionId="org.eclipse.lsp4e.textSelectionHasLanguageServer" />
</activeWhen>
</handler>
</extension>
<extension
point="org.eclipse.search.searchResultViewPages">
Expand Down Expand Up @@ -350,6 +376,42 @@
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
sequence="Ctrl+Alt+H">
</key>

<!-- edit -->
<key
sequence="M2+M3+ARROW_UP"
contextId="org.eclipse.ui.genericeditor.genericEditorContext"
commandId="org.eclipse.lsp4e.selectionRange.up"
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"/>
<key
platform="carbon"
sequence="M2+M3+ARROW_UP"
contextId="org.eclipse.ui.genericeditor.genericEditorContext"
commandId=""
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"/>
<key
platform="carbon"
sequence="CTRL+SHIFT+PAGE_UP"
contextId="org.eclipse.ui.genericeditor.genericEditorContext"
commandId="org.eclipse.lsp4e.selectionRange.up"
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"/>
<key
sequence="M2+M3+ARROW_DOWN"
contextId="org.eclipse.ui.genericeditor.genericEditorContext"
commandId="org.eclipse.lsp4e.selectionRange.down"
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"/>
<key
platform="carbon"
sequence="M2+M3+ARROW_DOWN"
contextId="org.eclipse.ui.genericeditor.genericEditorContext"
commandId=""
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"/>
<key
platform="carbon"
sequence="CTRL+SHIFT+PAGE_DOWN"
contextId="org.eclipse.ui.genericeditor.genericEditorContext"
commandId="org.eclipse.lsp4e.selectionRange.down"
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"/>
</extension>
<extension
point="org.eclipse.core.runtime.adapters">
Expand Down
26 changes: 17 additions & 9 deletions org.eclipse.lsp4e/src/org/eclipse/lsp4e/LSPEclipseUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.RewriteSessionEditProcessor;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.lsp4e.refactoring.CreateFileChange;
import org.eclipse.lsp4e.refactoring.DeleteExternalFile;
Expand Down Expand Up @@ -195,6 +196,17 @@ public static CompletionParams toCompletionParams(URI fileUri, int offset, IDocu
return param;
}

public static ISelection toSelection(Range range, IDocument document) {
try {
int offset = toOffset(range.getStart(), document);
int endOffset = toOffset(range.getEnd(), document);
return new TextSelection(offset, endOffset > offset ? endOffset - offset : 0);
} catch (BadLocationException e) {
LanguageServerPlugin.logError(e);
return null;
}
}

/**
* @param fileUri
* @param offset
Expand Down Expand Up @@ -704,16 +716,12 @@ protected static void openFileLocationInEditor(String uri, IWorkbenchPage page,
targetDocument = textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput());
}

try {
if (targetDocument != null) {
ISelectionProvider selectionProvider = part.getEditorSite().getSelectionProvider();
int offset = toOffset(optionalRange.getStart(), targetDocument);
int endOffset = toOffset(optionalRange.getEnd(), targetDocument);
selectionProvider
.setSelection(new TextSelection(offset, endOffset > offset ? endOffset - offset : 0));
if (targetDocument != null) {
ISelectionProvider selectionProvider = part.getEditorSite().getSelectionProvider();
ISelection selection = toSelection(optionalRange, targetDocument);
if (selection != null) {
selectionProvider.setSelection(selection);
}
} catch (BadLocationException e) {
LanguageServerPlugin.logError(e);
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
import org.eclipse.lsp4j.InitializedParams;
import org.eclipse.lsp4j.Registration;
import org.eclipse.lsp4j.RegistrationParams;
import org.eclipse.lsp4j.SelectionRangeRegistrationOptions;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.eclipse.lsp4j.TextDocumentSyncOptions;
Expand Down Expand Up @@ -902,6 +903,16 @@ void registerCapability(RegistrationParams params) {
final Either<Boolean, WorkspaceSymbolOptions> beforeRegistration = serverCapabilities.getWorkspaceSymbolProvider();
serverCapabilities.setWorkspaceSymbolProvider(Boolean.TRUE);
addRegistration(reg, () -> serverCapabilities.setWorkspaceSymbolProvider(beforeRegistration));
} else if ("textDocument/selectionRange".equals(reg.getMethod())) { //$NON-NLS-1$
Either<Boolean, SelectionRangeRegistrationOptions> selectionRangeProvider = serverCapabilities
.getSelectionRangeProvider();
if (selectionRangeProvider == null || selectionRangeProvider.isLeft()) {
serverCapabilities.setSelectionRangeProvider(Boolean.TRUE);
addRegistration(reg, () -> serverCapabilities.setSelectionRangeProvider(selectionRangeProvider));
} else {
serverCapabilities.setSelectionRangeProvider(selectionRangeProvider.getRight());
addRegistration(reg, () -> serverCapabilities.setSelectionRangeProvider(selectionRangeProvider));
}
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.eclipse.lsp4j.ReferencesCapabilities;
import org.eclipse.lsp4j.RenameCapabilities;
import org.eclipse.lsp4j.ResourceOperationKind;
import org.eclipse.lsp4j.SelectionRangeCapabilities;
import org.eclipse.lsp4j.ShowDocumentCapabilities;
import org.eclipse.lsp4j.SignatureHelpCapabilities;
import org.eclipse.lsp4j.SymbolCapabilities;
Expand Down Expand Up @@ -113,6 +114,8 @@ public class SupportedFeatures {
textDocumentClientCapabilities.setSignatureHelp(new SignatureHelpCapabilities());
textDocumentClientCapabilities
.setSynchronization(new SynchronizationCapabilities(Boolean.TRUE, Boolean.TRUE, Boolean.TRUE));
SelectionRangeCapabilities selectionRange = new SelectionRangeCapabilities();
textDocumentClientCapabilities.setSelectionRange(selectionRange);
return textDocumentClientCapabilities;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/*******************************************************************************
* 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 java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewer;
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.LanguageServers;
import org.eclipse.lsp4e.LanguageServers.LanguageServerDocumentExecutor;
import org.eclipse.lsp4e.internal.LSPDocumentAbstractHandler;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.SelectionRange;
import org.eclipse.lsp4j.SelectionRangeParams;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.swt.custom.CaretEvent;
import org.eclipse.swt.custom.CaretListener;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.handlers.HandlerUtil;
import org.eclipse.ui.texteditor.ITextEditor;

/**
* Abstract class which takes care of 'textDocument/selectionRange' LSP
* operation.
*
* @author Angelo ZERR
*
*/
public abstract class LSPSelectionRangeAbstractHandler extends LSPDocumentAbstractHandler {

/**
* Selection range handler mapped with a {@link StyledText} of a text editor.
*
*/
protected static class SelectionRangeHandler {

public static enum Direction {
UP, DOWN;
}

private static final String KEY = SelectionRangeHandler.class.getName();

private SelectionRange root;

private SelectionRange previous;

private final StyledText styledText;

private boolean updating;

public void setRoot(SelectionRange root) {
this.root = root;
this.previous = root;
}

public static SelectionRangeHandler getSelectionRangeHandler(StyledText styledText) {
SelectionRangeHandler handler = (SelectionRangeHandler) styledText.getData(KEY);
if (handler == null) {
handler = new SelectionRangeHandler(styledText);
}
return handler;
}

public SelectionRangeHandler(StyledText styledText) {
this.styledText = styledText;
styledText.setData(KEY, this);
styledText.addCaretListener(new CaretListener() {

@Override
public void caretMoved(CaretEvent arg0) {
if (!updating) {
root = null;
}
}
});
}

public SelectionRange getSelectionRange(Direction direction) {
if (direction == Direction.UP) {
if (previous != null) {
previous = previous.getParent();
return previous;
}
} else {
if (previous != null) {
SelectionRange selectionRange = root;
while (selectionRange != null) {
SelectionRange parent = selectionRange.getParent();
if (previous.equals(parent)) {
previous = selectionRange;
return previous;
}
selectionRange = parent;
}
}
}
return null;
}

public boolean isDirty() {
return root == null;
}

public void updateSelection(ISelectionProvider provider, IDocument document, Direction direction) {
if (styledText.isDisposed()) {
return;
}
SelectionRange selectionRange = getSelectionRange(direction);
if (selectionRange != null) {
ISelection selection = LSPEclipseUtils.toSelection(selectionRange.getRange(), document);
styledText.getDisplay().execute(() -> {
try {
updating = true;
provider.setSelection(selection);
} finally {
updating = false;
}
});
}
}

}

@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
IEditorPart part = HandlerUtil.getActiveEditor(event);
if (part instanceof ITextEditor textEditor) {
final ISelectionProvider provider = textEditor.getSelectionProvider();
if (provider == null) {
return null;
}
ISelection sel = provider.getSelection();
ITextViewer viewer = textEditor.getAdapter(ITextViewer.class);
if (viewer == null) {
return null;
}
StyledText styledText = viewer.getTextWidget();
if (styledText == null) {
return null;
}

if (sel instanceof ITextSelection textSelection && !textSelection.isEmpty()) {
IDocument document = LSPEclipseUtils.getDocument(textEditor);
if (document != null) {
LanguageServerDocumentExecutor executor = LanguageServers.forDocument(document)
.withCapability(ServerCapabilities::getSelectionRangeProvider);
if (executor.anyMatching()) {
SelectionRangeHandler.Direction direction = getDirection();
SelectionRangeHandler handler = SelectionRangeHandler.getSelectionRangeHandler(styledText);
if (handler.isDirty()) {
collectSelectionRanges(document, textSelection).thenApply(result -> {
if (result.isPresent()) {
List<SelectionRange> ranges = result.get();
SelectionRange root = ranges.get(0);
handler.setRoot(root);
handler.updateSelection(provider, document, direction);
}
return null;
});
} else {
handler.updateSelection(provider, document, direction);
}
}
}
}
}
return null;
}

private CompletableFuture<Optional<List<SelectionRange>>> collectSelectionRanges(IDocument document,
ITextSelection textSelection) {
if (document == null) {
return CompletableFuture.completedFuture(null);
}
try {
Position position = LSPEclipseUtils.toPosition(textSelection.getOffset(), document);
TextDocumentIdentifier identifier = LSPEclipseUtils.toTextDocumentIdentifier(document);
List<Position> positions = Arrays.asList(position);
SelectionRangeParams params = new SelectionRangeParams(identifier, positions);
return LanguageServers.forDocument(document).withCapability(ServerCapabilities::getSelectionRangeProvider)
.collectAll(languageServer -> languageServer.getTextDocumentService().selectionRange(params))
.thenApply(ranges -> ranges.stream().filter(Objects::nonNull).findFirst());
} catch (BadLocationException e) {
LanguageServerPlugin.logError(e);
return CompletableFuture.completedFuture(null);
}
}

@Override
public void setEnabled(Object evaluationContext) {
setEnabled(ServerCapabilities::getSelectionRangeProvider, x -> true);
}

protected abstract SelectionRangeHandler.Direction getDirection();
}
Loading

0 comments on commit 1224def

Please sign in to comment.