-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support for textDocument/selectionRange
Fixes #561
- Loading branch information
1 parent
12f6b41
commit 1224def
Showing
7 changed files
with
366 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
218 changes: 218 additions & 0 deletions
218
...p4e/src/org/eclipse/lsp4e/operations/selectionRange/LSPSelectionRangeAbstractHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} |
Oops, something went wrong.