Goto Symbol popup fully functional
This commit is contained in:
@@ -12,7 +12,6 @@ Require-Bundle: org.eclipse.ui,
|
||||
org.eclipse.core.filebuffers,
|
||||
org.eclipse.ui.navigator,
|
||||
org.eclipse.equinox.registry,
|
||||
io.projectreactor.reactor-core,
|
||||
org.springsource.ide.eclipse.commons.livexp,
|
||||
com.google.guava,
|
||||
org.eclipse.core.jobs,
|
||||
@@ -20,3 +19,5 @@ Require-Bundle: org.eclipse.ui,
|
||||
org.springframework.ide.eclipse.boot,
|
||||
org.springsource.ide.eclipse.commons.core
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
||||
Bundle-Activator: org.springframework.tooling.ls.eclipse.gotosymbol.GotoSymbolPlugin
|
||||
Bundle-ActivationPolicy: lazy
|
||||
|
||||
@@ -14,18 +14,18 @@
|
||||
id="org.springframework.tooling.ls.eclipse.gotosymbol.command">
|
||||
</command>
|
||||
</extension>
|
||||
<extension
|
||||
<!-- <extension
|
||||
point="org.eclipse.ui.handlers">
|
||||
<handler
|
||||
commandId="org.springframework.tooling.ls.eclipse.gotosymbol.command"
|
||||
class="org.springframework.tooling.ls.eclipse.gotosymbol.handlers.GotoSymbolHandler">
|
||||
</handler>
|
||||
</extension>
|
||||
</extension> -->
|
||||
<extension
|
||||
point="org.eclipse.ui.bindings">
|
||||
<key
|
||||
commandId="org.springframework.tooling.ls.eclipse.gotosymbol.command"
|
||||
contextId="org.eclipse.ui.contexts.window"
|
||||
contextId="org.eclipse.ui.contexts.dialogAndWindow"
|
||||
sequence="M1+6"
|
||||
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
|
||||
</key>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* 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:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.tooling.ls.eclipse.gotosymbol;
|
||||
import org.eclipse.ui.plugin.AbstractUIPlugin;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
public class GotoSymbolPlugin extends AbstractUIPlugin {
|
||||
|
||||
private static GotoSymbolPlugin instance;
|
||||
|
||||
public GotoSymbolPlugin() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(BundleContext context) throws Exception {
|
||||
super.start(context);
|
||||
this.instance = this;
|
||||
}
|
||||
|
||||
public static GotoSymbolPlugin getInstance() {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,15 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.eclipse.jface.dialogs.IDialogSettings;
|
||||
import org.eclipse.jface.dialogs.PopupDialog;
|
||||
import org.eclipse.jface.layout.GridDataFactory;
|
||||
import org.eclipse.jface.text.IRegion;
|
||||
import org.eclipse.jface.text.ITextSelection;
|
||||
import org.eclipse.jface.text.source.SourceViewer;
|
||||
import org.eclipse.jface.util.Geometry;
|
||||
import org.eclipse.jface.viewers.IDoubleClickListener;
|
||||
import org.eclipse.jface.viewers.ISelection;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.jface.viewers.ITreeContentProvider;
|
||||
import org.eclipse.jface.viewers.StructuredSelection;
|
||||
@@ -24,8 +31,11 @@ import org.eclipse.jface.viewers.TreeViewer;
|
||||
import org.eclipse.lsp4e.outline.SymbolsLabelProvider;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.custom.StyledText;
|
||||
import org.eclipse.swt.events.KeyAdapter;
|
||||
import org.eclipse.swt.events.KeyEvent;
|
||||
import org.eclipse.swt.graphics.Point;
|
||||
import org.eclipse.swt.graphics.Rectangle;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
@@ -36,12 +46,15 @@ import org.eclipse.swt.widgets.Shell;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
import org.eclipse.swt.widgets.TreeItem;
|
||||
import org.eclipse.ui.texteditor.ITextEditor;
|
||||
import org.springframework.ide.eclipse.boot.util.Log;
|
||||
import org.springframework.tooling.ls.eclipse.gotosymbol.GotoSymbolPlugin;
|
||||
import org.springframework.tooling.ls.eclipse.gotosymbol.util.SwtConnect;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.UIValueListener;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.Disposable;
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public class GotoSymbolDialog extends PopupDialog {
|
||||
|
||||
|
||||
private static final boolean DEBUG = (""+Platform.getLocation()).contains("kdvolder");
|
||||
|
||||
private static void debug(String string) {
|
||||
@@ -80,15 +93,53 @@ public class GotoSymbolDialog extends PopupDialog {
|
||||
private GotoSymbolDialogModel model;
|
||||
private List<Disposable> disposables = new ArrayList<>();
|
||||
private ITextEditor fTextEditor;
|
||||
|
||||
/**
|
||||
* If true, align the dialog so it looks like its attached right side of the editor. Otherwise, dialog is centered on the editor instead.
|
||||
*/
|
||||
private boolean alignRight;
|
||||
private IDialogSettings dlgSettings;
|
||||
|
||||
public GotoSymbolDialog(Shell parentShell, ITextEditor textEditor, GotoSymbolDialogModel model) {
|
||||
super(parentShell, PopupDialog.INFOPOPUP_SHELLSTYLE, true, true, true, false, false, null, null);
|
||||
public GotoSymbolDialog(Shell parentShell, ITextEditor textEditor, GotoSymbolDialogModel model, boolean alignRight) {
|
||||
super(parentShell, PopupDialog.INFOPOPUPRESIZE_SHELLSTYLE,
|
||||
/*focus*/true, /*persistSize*/true, /*persistLoc*/true,
|
||||
/*menu*/false, /*showPersistActions*/false, null, null
|
||||
);
|
||||
//Note: because of the way shell is initialized, the options to persist size / location do not work.
|
||||
// If we want this to work, it will have to be debugged.
|
||||
//For the time being I've simply disabled the menu that makes it appear like this should work.
|
||||
this.fTextEditor = textEditor;
|
||||
this.model = model;
|
||||
this.alignRight = alignRight;
|
||||
create();
|
||||
}
|
||||
|
||||
private void installKeyListeners(Text pattern, TreeViewer list) {
|
||||
@Override
|
||||
protected IDialogSettings getDialogSettings() {
|
||||
if (dlgSettings==null) {
|
||||
this.dlgSettings = GotoSymbolPlugin.getInstance().getDialogSettings();
|
||||
}
|
||||
return dlgSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the 'target' for the dialog's action.
|
||||
*/
|
||||
private SymbolInformation getTarget(TreeViewer list) {
|
||||
ISelection sel = list.getSelection();
|
||||
if (sel instanceof IStructuredSelection) {
|
||||
IStructuredSelection ss = (IStructuredSelection) sel;
|
||||
Object selected = ss.getFirstElement();
|
||||
if (selected!=null) {
|
||||
return (SymbolInformation) selected;
|
||||
}
|
||||
}
|
||||
//No element selected, target the first element in the list instead.
|
||||
//This allows user to execute the action without explicitly selecting an element.
|
||||
return getFirstElement(list);
|
||||
}
|
||||
|
||||
private void installWidgetListeners(Text pattern, TreeViewer list) {
|
||||
pattern.addKeyListener(new KeyAdapter() {
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.keyCode == SWT.ARROW_DOWN) {
|
||||
@@ -102,6 +153,8 @@ public class GotoSymbolDialog extends PopupDialog {
|
||||
new Event());
|
||||
}
|
||||
}
|
||||
} else if (e.character == '\r') {
|
||||
performOk(list);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -124,6 +177,8 @@ public class GotoSymbolDialog extends PopupDialog {
|
||||
}
|
||||
|
||||
}
|
||||
} else if (e.character == '\r') {
|
||||
performOk(list);
|
||||
}
|
||||
|
||||
// if (e.keyCode == SWT.ARROW_DOWN
|
||||
@@ -135,13 +190,21 @@ public class GotoSymbolDialog extends PopupDialog {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
list.addDoubleClickListener(e -> performOk(list));
|
||||
}
|
||||
|
||||
private Object getFirstElement(TreeViewer list) {
|
||||
private void performOk(TreeViewer list) {
|
||||
if (model.performOk(getTarget(list))) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
private SymbolInformation getFirstElement(TreeViewer list) {
|
||||
TreeItem[] items = list.getTree().getItems();
|
||||
if (items!=null && items.length>0) {
|
||||
TreeItem item = items[0];
|
||||
return item.getData();
|
||||
return (SymbolInformation) item.getData();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -173,25 +236,14 @@ public class GotoSymbolDialog extends PopupDialog {
|
||||
pattern.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
SwtConnect.connect(pattern, model.getSearchBox());
|
||||
|
||||
TreeViewer viewer = new TreeViewer(dialogArea);
|
||||
TreeViewer viewer = new TreeViewer(dialogArea, SWT.SINGLE);
|
||||
GridDataFactory.fillDefaults().grab(true, true).applyTo(viewer.getControl());
|
||||
viewer.setContentProvider(new SymbolsContentProvider());
|
||||
viewer.setLabelProvider(new SymbolsLabelProvider());
|
||||
viewer.setUseHashlookup(true);
|
||||
disposables.add(model.getSymbols().onChange((e, v) -> {
|
||||
disposables.add(model.getSymbols().onChange(UIValueListener.from((e, v) -> {
|
||||
viewer.refresh();
|
||||
}));
|
||||
if (DEBUG) {
|
||||
viewer.addSelectionChangedListener(event -> {
|
||||
IStructuredSelection selection = (IStructuredSelection) event.getSelection();
|
||||
if (selection.isEmpty()) {
|
||||
debug("selected NONE");
|
||||
} else {
|
||||
SymbolInformation symbolInformation = (SymbolInformation) selection.getFirstElement();
|
||||
debug("selected = "+symbolInformation.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
})));
|
||||
//TODO: somehow show selection in local file, (but not in other file ?)
|
||||
// viewer.addSelectionChangedListener(event -> {
|
||||
// IStructuredSelection selection = (IStructuredSelection) event.getSelection();
|
||||
@@ -217,7 +269,7 @@ public class GotoSymbolDialog extends PopupDialog {
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
installKeyListeners(pattern, viewer);
|
||||
installWidgetListeners(pattern, viewer);
|
||||
|
||||
Label statusLabel = new Label(dialogArea, SWT.NONE);
|
||||
statusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
@@ -230,13 +282,54 @@ public class GotoSymbolDialog extends PopupDialog {
|
||||
@Override
|
||||
protected void configureShell(Shell shell) {
|
||||
super.configureShell(shell);
|
||||
|
||||
shell.setSize(280, 300);
|
||||
|
||||
Control control = fTextEditor.getAdapter(Control.class);
|
||||
if (control != null) {
|
||||
shell.setLocation(
|
||||
control.toDisplay(control.getBounds().width - shell.getSize().x, control.getLocation().y));
|
||||
//Set size relative to editor's size.
|
||||
Point size = new Point(control.getBounds().width/3, control.getBounds().height/2);
|
||||
shell.setSize(size);
|
||||
if (alignRight) {
|
||||
shell.setLocation(control.toDisplay(control.getBounds().width - shell.getSize().x, control.getLocation().y));
|
||||
} else {
|
||||
//centered on the editor
|
||||
shell.setLocation(control.toDisplay(control.getBounds().width/3, control.getBounds().height/4));
|
||||
}
|
||||
} else {
|
||||
// fallback
|
||||
shell.setSize(280, 300);
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Determines the graphical area covered by the given text region.
|
||||
// *
|
||||
// * @param region the region whose graphical extend must be computed
|
||||
// * @return the graphical extend of the given region
|
||||
// */
|
||||
// private Rectangle computeArea(IRegion region) {
|
||||
//
|
||||
// int start= 0;
|
||||
// int end= 0;
|
||||
//
|
||||
// IRegion widgetRegion= modelRange2WidgetRange(region);
|
||||
// if (widgetRegion != null) {
|
||||
// start= widgetRegion.getOffset();
|
||||
// end= widgetRegion.getOffset() + widgetRegion.getLength();
|
||||
// }
|
||||
//
|
||||
// StyledText styledText= fTextEditor.getTextWidget();
|
||||
// Rectangle bounds;
|
||||
// if (end > 0 && start < end)
|
||||
// bounds= styledText.getTextBounds(start, end - 1);
|
||||
// else {
|
||||
// Point loc= styledText.getLocationAtOffset(start);
|
||||
// bounds= new Rectangle(loc.x, loc.y, 0, styledText.getLineHeight(start));
|
||||
// }
|
||||
//
|
||||
// Rectangle clientArea= styledText.getClientArea();
|
||||
// Geometry.moveInside(bounds, clientArea);
|
||||
// return bounds;
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -12,68 +12,72 @@ package org.springframework.tooling.ls.eclipse.gotosymbol.dialogs;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.core.runtime.Assert;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.eclipse.jface.viewers.ISelection;
|
||||
import org.eclipse.jface.viewers.TreeViewer;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.springframework.ide.eclipse.boot.util.Log;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.AsyncLiveExpression.AsyncMode;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ObservableSet;
|
||||
import org.springsource.ide.eclipse.commons.core.util.StringUtil;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import org.springframework.ide.eclipse.boot.util.Log;
|
||||
|
||||
public class GotoSymbolDialogModel {
|
||||
|
||||
private static final boolean DEBUG = (""+Platform.getLocation()).contains("kdvolder");
|
||||
|
||||
private static void debug(String string) {
|
||||
if (DEBUG) {
|
||||
System.out.println(string);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//TODO: support for switching between local/workspace symbols.
|
||||
@FunctionalInterface
|
||||
public interface OKHandler {
|
||||
/**
|
||||
* Called by the ui to perform the dialog's action. The dialog will be
|
||||
* closed by the ui this returns true, otherwise it remains open.
|
||||
*/
|
||||
boolean performOk(SymbolInformation selection);
|
||||
}
|
||||
|
||||
private static final OKHandler DEFAULT_OK_HANDLER = (selection) -> true;
|
||||
|
||||
private SymbolsProvider[] symbolsProviders;
|
||||
private final LiveVariable<String> status = new LiveVariable<>();
|
||||
private final LiveVariable<SymbolsProvider> symbolsProvider = new LiveVariable<>(null);
|
||||
private int currentSymbolsProviderIndex;
|
||||
private final LiveVariable<SymbolsProvider> currentSymbolsProvider = new LiveVariable<>(null);
|
||||
private final LiveVariable<String> searchBox = new LiveVariable<>("");
|
||||
private final ObservableSet<SymbolInformation> unfilteredSymbols = new ObservableSet<SymbolInformation>(ImmutableSet.of(), AsyncMode.ASYNC, AsyncMode.SYNC) {
|
||||
//Note: fetching is 'slow' so is done asynchronously
|
||||
{
|
||||
setRefreshDelay(100);
|
||||
dependsOn(searchBox);
|
||||
dependsOn(symbolsProvider);
|
||||
dependsOn(currentSymbolsProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ImmutableSet<SymbolInformation> compute() {
|
||||
status.setValue("Fetching symbols...");
|
||||
try {
|
||||
debug("Fetching symbols ");
|
||||
SymbolsProvider sp = symbolsProvider.getValue();
|
||||
SymbolsProvider sp = currentSymbolsProvider.getValue();
|
||||
if (sp!=null) {
|
||||
debug("Fetching "+sp.getName());
|
||||
String query = searchBox.getValue();
|
||||
debug("Fetching symbols... from symbol provider, for '"+query+"'");
|
||||
Collection<SymbolInformation> fetched = sp.fetchFor(query);
|
||||
if (DEBUG) {
|
||||
fetched.stream().forEach(sym -> debug("symbol: "+sym.getName()));
|
||||
if (keyBindings==null) {
|
||||
status.setValue(sp.getName());
|
||||
} else {
|
||||
status.setValue("Press ["+keyBindings+"] for "+nextSymbolsProvider().getName());
|
||||
}
|
||||
StringBuilder msg = new StringBuilder();
|
||||
msg.append("Fetched ");
|
||||
msg.append(fetched.size());
|
||||
msg.append(" symbols");
|
||||
if (StringUtil.hasText(query)) {
|
||||
msg.append(" matching '");
|
||||
msg.append(query);
|
||||
msg.append("'");
|
||||
}
|
||||
debug(msg.toString());
|
||||
status.setValue(msg.toString());
|
||||
return ImmutableSet.copyOf(fetched);
|
||||
} else {
|
||||
status.setValue("No symbol provider");
|
||||
@@ -84,7 +88,13 @@ public class GotoSymbolDialogModel {
|
||||
}
|
||||
return ImmutableSet.of();
|
||||
}
|
||||
|
||||
private SymbolsProvider nextSymbolsProvider() {
|
||||
int nextIndex = (currentSymbolsProviderIndex + 1)%symbolsProviders.length;
|
||||
return symbolsProviders[nextIndex];
|
||||
}
|
||||
};
|
||||
|
||||
private ObservableSet<SymbolInformation> filteredSymbols = new ObservableSet<SymbolInformation>() {
|
||||
//Note: filtering is 'fast' so is done synchronously
|
||||
{
|
||||
@@ -114,9 +124,16 @@ public class GotoSymbolDialogModel {
|
||||
return builder.build();
|
||||
}
|
||||
};
|
||||
|
||||
private String keyBindings;
|
||||
private OKHandler okHandler = DEFAULT_OK_HANDLER;
|
||||
|
||||
public GotoSymbolDialogModel(SymbolsProvider symbolsProvider) {
|
||||
this.symbolsProvider.setValue(symbolsProvider);
|
||||
public GotoSymbolDialogModel(String keyBindings, SymbolsProvider... symbolsProviders) {
|
||||
this.keyBindings = keyBindings;
|
||||
Assert.isLegal(symbolsProviders.length>0);
|
||||
this.symbolsProviders = symbolsProviders;
|
||||
this.currentSymbolsProviderIndex = 0;
|
||||
this.currentSymbolsProvider.setValue(symbolsProviders[0]);
|
||||
if (DEBUG) {
|
||||
searchBox.addListener((e, v) -> {
|
||||
debug("searchBox = "+v);
|
||||
@@ -142,4 +159,26 @@ public class GotoSymbolDialogModel {
|
||||
return status;
|
||||
}
|
||||
|
||||
public synchronized void toggleSymbolsProvider() {
|
||||
currentSymbolsProviderIndex = (currentSymbolsProviderIndex+1)%symbolsProviders.length;
|
||||
currentSymbolsProvider.setValue(symbolsProviders[currentSymbolsProviderIndex]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an ok handler. The handler is meant to be called by the UI when user request to
|
||||
* execute the dialogs action on its current selection. For example, by pressing 'ENTER'
|
||||
* key, or by double-clicking an element.
|
||||
* <p>
|
||||
* If 'okHandler' returns 'true' then the dialog is closed. Otherwise it remains open.
|
||||
*/
|
||||
public GotoSymbolDialogModel setOkHandler(OKHandler okHandler) {
|
||||
this.okHandler = okHandler == null ? DEFAULT_OK_HANDLER : okHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean performOk(SymbolInformation selection) {
|
||||
return this.okHandler.performOk(selection);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -11,23 +11,66 @@
|
||||
package org.springframework.tooling.ls.eclipse.gotosymbol.dialogs;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.core.commands.ExecutionEvent;
|
||||
import org.eclipse.lsp4e.LSPEclipseUtils;
|
||||
import org.eclipse.lsp4e.LanguageServiceAccessor;
|
||||
import org.eclipse.lsp4e.LanguageServiceAccessor.LSPDocumentInfo;
|
||||
import org.eclipse.lsp4j.DocumentSymbolParams;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.swt.widgets.Shell;
|
||||
import org.eclipse.ui.IEditorPart;
|
||||
import org.eclipse.ui.handlers.HandlerUtil;
|
||||
import org.eclipse.ui.texteditor.ITextEditor;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* SymbolsProvider that only looks for symbols in current file using the LSP 'document/symbols' api.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
@SuppressWarnings("restriction")
|
||||
public class InFileSymbolsProvider implements SymbolsProvider {
|
||||
|
||||
private ITextEditor target;
|
||||
private LSPDocumentInfo info;
|
||||
|
||||
public InFileSymbolsProvider(ITextEditor target) {
|
||||
public InFileSymbolsProvider(LSPDocumentInfo target) {
|
||||
super();
|
||||
this.target = target;
|
||||
this.info = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> fetchFor(String query) {
|
||||
// TODO Auto-generated method stub
|
||||
public Collection<SymbolInformation> fetchFor(String query) throws Exception {
|
||||
DocumentSymbolParams params = new DocumentSymbolParams(
|
||||
new TextDocumentIdentifier(info.getFileUri().toString()));
|
||||
CompletableFuture<List<? extends SymbolInformation>> symbolsFuture = info.getLanguageClient()
|
||||
.getTextDocumentService().documentSymbol(params);
|
||||
List<? extends SymbolInformation> symbols = symbolsFuture.get();
|
||||
Thread.sleep(2000);
|
||||
return symbols == null ? ImmutableList.of() : ImmutableList.copyOf(symbols);
|
||||
}
|
||||
|
||||
public static SymbolsProvider createFor(ITextEditor textEditor) {
|
||||
Collection<LSPDocumentInfo> infos = LanguageServiceAccessor.getLSPDocumentInfosFor(
|
||||
LSPEclipseUtils.getDocument(textEditor),
|
||||
capabilities -> Boolean.TRUE.equals(capabilities.getDocumentSymbolProvider()));
|
||||
if (infos.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// TODO maybe consider better strategy such as iterating on all LS until we have a good result
|
||||
LSPDocumentInfo info = infos.iterator().next();
|
||||
if (info!=null) {
|
||||
return new InFileSymbolsProvider(info);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Symbols in File";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* 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:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.tooling.ls.eclipse.gotosymbol.dialogs;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.core.commands.ExecutionEvent;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.runtime.IAdaptable;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.lsp4e.LanguageServiceAccessor;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.eclipse.lsp4j.WorkspaceSymbolParams;
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
import org.eclipse.ui.IEditorPart;
|
||||
import org.eclipse.ui.handlers.HandlerUtil;
|
||||
import org.springframework.ide.eclipse.boot.util.Log;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public class InWorkspaceSymbolsProvider implements SymbolsProvider {
|
||||
|
||||
private static final Duration TIMEOUT = Duration.ofSeconds(2);
|
||||
|
||||
private List<LanguageServer> languageServers;
|
||||
|
||||
public InWorkspaceSymbolsProvider(List<LanguageServer> languageServers) {
|
||||
this.languageServers = languageServers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Symbols in Workspace";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> fetchFor(String query) throws Exception {
|
||||
//TODO: if we want decent support for multiple language servers...
|
||||
// consider changing SymbolsProvider api and turning the stuff in here into something producing a
|
||||
// Flux<Collection<SymbolInformation>>
|
||||
// This will help in
|
||||
// - supporting cancelation
|
||||
// - executing multiple requests to different servers in parallel.
|
||||
// - producing results per server so don't have to wait for one slow server to see the rest.
|
||||
//However it will also add complexity to the code that consumes this and at this time we only
|
||||
// really use this with a single language server anyways.
|
||||
ImmutableList.Builder<SymbolInformation> allSymbols = ImmutableList.builder();
|
||||
for (LanguageServer server : this.languageServers) {
|
||||
try {
|
||||
WorkspaceSymbolParams params = new WorkspaceSymbolParams(query);
|
||||
List<? extends SymbolInformation> symbolsFuture =
|
||||
server.getWorkspaceService().symbol(params)
|
||||
.get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
|
||||
allSymbols.addAll(symbolsFuture);
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
}
|
||||
return allSymbols.build();
|
||||
}
|
||||
|
||||
public static InWorkspaceSymbolsProvider createFor(ExecutionEvent event) {
|
||||
IEditorPart part = HandlerUtil.getActiveEditor(event);
|
||||
IResource resource = null;
|
||||
if (part != null && part.getEditorInput() != null) {
|
||||
resource = part.getEditorInput().getAdapter(IResource.class);
|
||||
} else {
|
||||
IStructuredSelection selection = HandlerUtil.getCurrentStructuredSelection(event);
|
||||
if (selection.isEmpty() || !(selection.getFirstElement() instanceof IAdaptable)) {
|
||||
return null;
|
||||
}
|
||||
IAdaptable adaptable = (IAdaptable) selection.getFirstElement();
|
||||
resource = adaptable.getAdapter(IResource.class);
|
||||
}
|
||||
if (resource!=null) {
|
||||
return createFor(resource.getProject());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static InWorkspaceSymbolsProvider createFor(IProject project) {
|
||||
List<LanguageServer> languageServers = LanguageServiceAccessor.getLanguageServers(project,
|
||||
capabilities -> Boolean.TRUE.equals(capabilities.getWorkspaceSymbolProvider()));
|
||||
if (!languageServers.isEmpty()) {
|
||||
return new InWorkspaceSymbolsProvider(languageServers);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,5 +20,6 @@ import org.eclipse.lsp4j.SymbolInformation;
|
||||
* called only from a background job.
|
||||
*/
|
||||
public interface SymbolsProvider {
|
||||
Collection<SymbolInformation> fetchFor(String query);
|
||||
String getName();
|
||||
Collection<SymbolInformation> fetchFor(String query) throws Exception;
|
||||
}
|
||||
@@ -11,66 +11,88 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.tooling.ls.eclipse.gotosymbol.handlers;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.core.commands.AbstractHandler;
|
||||
import org.eclipse.core.commands.Command;
|
||||
import org.eclipse.core.commands.ExecutionEvent;
|
||||
import org.eclipse.core.commands.ExecutionException;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.eclipse.jface.bindings.TriggerSequence;
|
||||
import org.eclipse.lsp4e.LSPEclipseUtils;
|
||||
import org.eclipse.lsp4e.LanguageServiceAccessor;
|
||||
import org.eclipse.lsp4e.LanguageServiceAccessor.LSPDocumentInfo;
|
||||
import org.eclipse.lsp4j.DocumentSymbolParams;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.swt.widgets.Shell;
|
||||
import org.eclipse.ui.IEditorPart;
|
||||
import org.eclipse.ui.IWorkbenchPage;
|
||||
import org.eclipse.ui.IWorkbenchPart;
|
||||
import org.eclipse.ui.PlatformUI;
|
||||
import org.eclipse.ui.handlers.HandlerUtil;
|
||||
import org.eclipse.ui.keys.IBindingService;
|
||||
import org.eclipse.ui.texteditor.ITextEditor;
|
||||
import org.springframework.tooling.ls.eclipse.gotosymbol.dialogs.GotoSymbolDialog;
|
||||
import org.springframework.tooling.ls.eclipse.gotosymbol.dialogs.GotoSymbolDialogModel;
|
||||
import org.springframework.tooling.ls.eclipse.gotosymbol.dialogs.InFileSymbolsProvider;
|
||||
import org.springframework.tooling.ls.eclipse.gotosymbol.dialogs.InWorkspaceSymbolsProvider;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* Our sample handler extends AbstractHandler, an IHandler base class.
|
||||
* @see org.eclipse.core.commands.IHandler
|
||||
* @see org.eclipse.core.commands.AbstractHandler
|
||||
*/
|
||||
@SuppressWarnings("restriction")
|
||||
public class GotoSymbolHandler extends AbstractHandler {
|
||||
|
||||
private static final boolean DEBUG = (""+Platform.getLocation()).contains("kdvolder");
|
||||
|
||||
private static void debug(String string) {
|
||||
if (DEBUG) {
|
||||
System.out.println(string);
|
||||
}
|
||||
}
|
||||
private static GotoSymbolDialogModel currentDialog = null;
|
||||
|
||||
public GotoSymbolHandler() {
|
||||
debug("Creating GotoSymbolHandler");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(ExecutionEvent event) throws ExecutionException {
|
||||
System.out.println("GotoSymbolHandler.execute");
|
||||
IEditorPart part = HandlerUtil.getActiveEditor(event);
|
||||
if (part instanceof ITextEditor) {
|
||||
final ITextEditor textEditor = (ITextEditor) part;
|
||||
Collection<LSPDocumentInfo> infos = LanguageServiceAccessor.getLSPDocumentInfosFor(
|
||||
LSPEclipseUtils.getDocument(textEditor),
|
||||
capabilities -> Boolean.TRUE.equals(capabilities.getDocumentSymbolProvider()));
|
||||
if (infos.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// TODO maybe consider better strategy such as iterating on all LS until we have a good result
|
||||
LSPDocumentInfo info = infos.iterator().next();
|
||||
final Shell shell = HandlerUtil.getActiveShell(event);
|
||||
DocumentSymbolParams params = new DocumentSymbolParams(
|
||||
new TextDocumentIdentifier(info.getFileUri().toString()));
|
||||
CompletableFuture<List<? extends SymbolInformation>> symbols = info.getLanguageClient()
|
||||
.getTextDocumentService().documentSymbol(params);
|
||||
|
||||
symbols.thenAccept((List<? extends SymbolInformation> t) -> {
|
||||
shell.getDisplay().asyncExec(() -> {
|
||||
List<SymbolInformation> allSymbols = ImmutableList.copyOf(t);
|
||||
GotoSymbolDialogModel model = new GotoSymbolDialogModel(query -> allSymbols);
|
||||
GotoSymbolDialog dialog = new GotoSymbolDialog(shell, textEditor, model);
|
||||
dialog.open();
|
||||
debug(">>>GotoSymbolHandler.execute");
|
||||
if (currentDialog!=null) {
|
||||
debug("GotoSymbolDialog already open: send 'toggle' signal");
|
||||
currentDialog.toggleSymbolsProvider();
|
||||
} else {
|
||||
IEditorPart part = HandlerUtil.getActiveEditor(event);
|
||||
if (part instanceof ITextEditor) {
|
||||
final Shell shell = HandlerUtil.getActiveShell(event);
|
||||
final ITextEditor textEditor = (ITextEditor) part;
|
||||
|
||||
GotoSymbolDialogModel model = new GotoSymbolDialogModel(getKeybindings(event), InFileSymbolsProvider.createFor(textEditor), InWorkspaceSymbolsProvider.createFor(event))
|
||||
.setOkHandler(symbolInformation -> {
|
||||
if (symbolInformation!=null) {
|
||||
Location location = symbolInformation.getLocation();
|
||||
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
|
||||
LSPEclipseUtils.openInEditor(location, page);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
GotoSymbolDialog dialog = new GotoSymbolDialog(shell, textEditor, model, /*alignRight*/ false);
|
||||
currentDialog = model;
|
||||
dialog.open();
|
||||
debug("GotoSymbolDialog opened");
|
||||
dialog.getShell().addDisposeListener(de -> {
|
||||
debug("GotoSymbolDialog closed!");
|
||||
currentDialog = null;
|
||||
});
|
||||
}
|
||||
debug("<<<GotoSymbolHandler.execute");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getKeybindings(ExecutionEvent event) {
|
||||
Command invokingCommand = event.getCommand();
|
||||
IBindingService service = PlatformUI.getWorkbench().getService(IBindingService.class);
|
||||
TriggerSequence[] bindings = service.getActiveBindingsFor(invokingCommand.getId());
|
||||
if (bindings!=null && bindings.length>0) {
|
||||
return bindings[0].toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -78,18 +100,18 @@ public class GotoSymbolHandler extends AbstractHandler {
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
boolean r = _isEnabled();
|
||||
System.out.println("isEnabled = "+r);
|
||||
return r;
|
||||
}
|
||||
public boolean _isEnabled() {
|
||||
|
||||
IWorkbenchPart part = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActivePart();
|
||||
if (part instanceof ITextEditor) {
|
||||
debug("activePart instanceof ITextEditor");
|
||||
List<LSPDocumentInfo> infos = LanguageServiceAccessor.getLSPDocumentInfosFor(
|
||||
LSPEclipseUtils.getDocument((ITextEditor) part),
|
||||
(capabilities) -> Boolean.TRUE.equals(capabilities.getDocumentSymbolProvider()));
|
||||
return !infos.isEmpty();
|
||||
}
|
||||
debug("activePart not ITextEditor: "+part);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user