Initial implementation 'Goto Symbols' View

This commit is contained in:
Kris De Volder
2020-05-08 10:22:59 -07:00
parent edf29d2d93
commit 9cc632a7f0
11 changed files with 586 additions and 373 deletions

View File

@@ -19,7 +19,8 @@ Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime;bundle-version="3.13.0",
org.springsource.ide.eclipse.commons.core;bundle-version="3.9.2",
io.projectreactor.reactor-core;bundle-version="3.0.7",
org.reactivestreams.reactive-streams;bundle-version="1.0.0"
org.reactivestreams.reactive-streams;bundle-version="1.0.0",
org.springframework.ide.eclipse.boot.dash
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-Activator: org.springframework.tooling.ls.eclipse.gotosymbol.GotoSymbolPlugin
Bundle-ActivationPolicy: lazy

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 526 B

View File

@@ -1,6 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
<extension
point="org.eclipse.ui.views">
<view
name="Spring Symbols"
icon="$nl$/icons/boot-icon.png"
category="org.springframework.ide.eclipse.ui.views"
class="org.springframework.tooling.ls.eclipse.gotosymbol.view.SpringSymbolsView"
id="org.springframework.ide.eclipse.boot.dash.views.BootDashView">
</view>
</extension>
<extension
point="org.eclipse.ui.commands">
<category

View File

@@ -11,196 +11,27 @@
*******************************************************************************/
package org.springframework.tooling.ls.eclipse.gotosymbol.dialogs;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.PopupDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.resource.JFaceColors;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StyledCellLabelProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.lsp4e.LSPEclipseUtils;
import org.eclipse.lsp4e.outline.SymbolsLabelProvider;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.jface.operation.IRunnableContext;
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.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.texteditor.ITextEditor;
import org.springframework.tooling.ls.eclipse.gotosymbol.GotoSymbolPlugin;
import org.springframework.tooling.ls.eclipse.gotosymbol.dialogs.GotoSymbolDialogModel.Match;
import org.springsource.ide.eclipse.commons.core.util.FuzzyMatcher;
import org.springsource.ide.eclipse.commons.livexp.core.UIValueListener;
import org.springsource.ide.eclipse.commons.livexp.ui.Disposable;
import org.springsource.ide.eclipse.commons.livexp.ui.Stylers;
import org.springsource.ide.eclipse.commons.livexp.ui.util.SwtConnect;
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
@SuppressWarnings("restriction")
public class GotoSymbolDialog extends PopupDialog {
public class GotoSymbolDialog extends PopupDialog implements IPageWithSections {
private static class SymbolsContentProvider implements ITreeContentProvider {
@Override
public Object[] getChildren(Object parentElement) {
return null;
}
@Override
public Object getParent(Object element) {
return null;
}
@Override
public boolean hasChildren(Object element) {
return false;
}
@Override
public Object[] getElements(Object inputElement) {
if (inputElement instanceof GotoSymbolDialogModel) {
GotoSymbolDialogModel model = (GotoSymbolDialogModel) inputElement;
return model.getSymbols().getValue().toArray();
}
return null;
}
}
private class GotoSymbolsLabelProvider extends StyledCellLabelProvider {
private Stylers stylers;
private SymbolsLabelProvider symbolsLabelProvider;
public GotoSymbolsLabelProvider(Font base) {
stylers = new Stylers(base);
boolean showSymbolsLabelProviderLocation = false; /* dont show full location. we show relative location in our own implementation below */
boolean showKindInformation = false;
symbolsLabelProvider = new SymbolsLabelProvider(showSymbolsLabelProviderLocation , showKindInformation) {
@Override
protected int getMaxSeverity(IResource resource, IDocument doc, Range range)
throws CoreException, BadLocationException {
int maxSeverity = -1;
for (IMarker marker : resource.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_ZERO)) {
int offset = marker.getAttribute(IMarker.CHAR_START, -1);
if (offset != -1) {
maxSeverity = Math.max(maxSeverity, marker.getAttribute(IMarker.SEVERITY, -1));
}
}
return maxSeverity;
}
};
}
@Override
public Color getToolTipBackgroundColor(Object object) {
return JFaceColors.getInformationViewerBackgroundColor(Display.getDefault());
}
@Override
public Color getToolTipForegroundColor(Object object) {
return JFaceColors.getInformationViewerForegroundColor(Display.getDefault());
}
@Override
public String getToolTipText(Object element) {
if (element instanceof Match) {
SymbolInformation si = getSymbolInformation((Match<?>)element);
if (si!=null) {
return si.getName();
}
}
return null;
}
@Override
public void update(ViewerCell cell) {
super.update(cell);
Object obj = cell.getElement();
if (obj instanceof Match) {
Match<?> match = (Match<?>) obj;
cell.setImage(symbolsLabelProvider.getImage(match.value));
StyledString styledString = getStyledText(match);
cell.setText(styledString.getString());
cell.setStyleRanges(styledString.getStyleRanges());
cell.getControl().redraw();
//^^^ Sigh... Yes, this is needed. It seems SWT/Jface isn't smart enough to itself figure out that if
//the styleranges change a redraw is needed to make the change visible.
} else {
super.update(cell);
}
}
private StyledString getStyledText(Match<?> element) {
SymbolInformation symbolInformation = getSymbolInformation(element);
if (symbolInformation != null) {
String name = symbolInformation.getName();
StyledString s = new StyledString(name);
Collection<IRegion> highlights = FuzzyMatcher.highlights(element.query, name.toLowerCase());
for (IRegion hl : highlights) {
s.setStyle(hl.getOffset(), hl.getLength(), stylers.bold());
}
String locationText = getSymbolLocationText(symbolInformation);
if (locationText != null) {
s = s.append(locationText, stylers.italicColoured(SWT.COLOR_DARK_GRAY));
}
return s;
} else {
return symbolsLabelProvider.getStyledText(element.value);
}
}
@Override
public void dispose() {
stylers.dispose();
symbolsLabelProvider.dispose();
super.dispose();
}
protected String getSymbolLocationText(SymbolInformation symbol) {
Optional<String> location = GotoSymbolDialog.this
.getSymbolLocation(symbol);
if (location.isPresent()) {
return " -- [" + location.get() + "]";
}
return null;
}
}
private static final Point DEFAULT_SIZE = new Point(280, 300);
private GotoSymbolDialogModel model;
private List<Disposable> disposables = new ArrayList<>();
private final GotoSymbolSection content;
private ITextEditor fTextEditor;
/**
@@ -219,26 +50,11 @@ public class GotoSymbolDialog extends PopupDialog {
//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.content = new GotoSymbolSection(this, model);
this.alignRight = alignRight;
create();
}
private SymbolInformation getSymbolInformation(Match<?> element) {
if (element.value instanceof SymbolInformation) {
return (SymbolInformation) element.value;
}
if (element.value instanceof Either) {
Either<?,?> either = (Either<?, ?>)element.value;
if (either.isLeft()) {
Object left = either.getLeft();
if (left instanceof SymbolInformation) {
return (SymbolInformation) left;
}
}
}
return null;
}
@Override
protected IDialogSettings getDialogSettings() {
if (dlgSettings==null) {
@@ -247,125 +63,9 @@ public class GotoSymbolDialog extends PopupDialog {
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 instanceof Match) {
SymbolInformation si = getSymbolInformation((Match<?>) selected);
if (si!=null) {
return si;
}
}
}
//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) {
if (list.getTree().getItemCount() > 0) {
list.getTree().setFocus();
TreeItem[] items = list.getTree().getItems();
if (items!=null && items.length>0) {
list.getTree().setSelection(items[0]);
//programatic selection may not fire selection events so...
list.getTree().notifyListeners(SWT.Selection,
new Event());
}
}
} else if (e.character == '\r') {
performOk(list);
}
}
});
list.getTree().addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if (e.keyCode == SWT.ARROW_UP && (e.stateMask & SWT.SHIFT) == 0
&& (e.stateMask & SWT.CTRL) == 0) {
StructuredSelection selection = (StructuredSelection) list
.getSelection();
if (selection.size() == 1) {
Object element = selection.getFirstElement();
if (element.equals(getFirstElement(list))) {
pattern.setFocus();
list.setSelection(new StructuredSelection());
list.getTree().notifyListeners(SWT.Selection,
new Event());
}
}
} else if (e.character == '\r') {
performOk(list);
}
// if (e.keyCode == SWT.ARROW_DOWN
// && (e.stateMask & SWT.SHIFT) != 0
// && (e.stateMask & SWT.CTRL) != 0) {
//
// list.getTree().notifyListeners(SWT.Selection, new Event());
// }
}
});
list.addDoubleClickListener(e -> performOk(list));
}
private Optional<String> getSymbolLocation(SymbolInformation symbolInformation) {
String val = null;
if (!model.fromFileProvider(symbolInformation)) {
Location location = symbolInformation.getLocation();
IResource targetResource = LSPEclipseUtils.findResourceFor(location.getUri());
if (targetResource != null && targetResource.getFullPath() != null) {
val = targetResource.getFullPath().toString();
}
}
return val != null ? Optional.of(val) : Optional.empty();
}
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];
Object data = item.getData();
if (data instanceof Match) {
SymbolInformation si = getSymbolInformation((Match<?>) data);
if (si!=null) {
return si;
}
}
}
return null;
}
@Override
protected Control createDialogArea(Composite parent) {
Composite dialogArea = new Composite(parent, SWT.NONE);
dialogArea.addDisposeListener(de -> {
for (Disposable d : disposables) {
d.dispose();
}
});
if (parent.getLayout() instanceof GridLayout) {
dialogArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
}
@@ -375,66 +75,7 @@ public class GotoSymbolDialog extends PopupDialog {
layout.marginWidth = 0;
dialogArea.setLayout(layout);
Text pattern = new Text(dialogArea, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL);
// pattern.getAccessible().addAccessibleListener(new AccessibleAdapter() {
// public void getName(AccessibleEvent e) {
// e.result = LegacyActionTools.removeMnemonics(headerLabel)
// .getText());
// }
// });
pattern.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
pattern.setMessage(model.getSearchBoxHintMessage());
SwtConnect.connect(pattern, model.getSearchBox());
TreeViewer viewer = new TreeViewer(dialogArea, SWT.SINGLE);
ColumnViewerToolTipSupport.enableFor(viewer);
GridDataFactory.fillDefaults().grab(true, true).applyTo(viewer.getControl());
viewer.setContentProvider(new SymbolsContentProvider());
viewer.setLabelProvider(new GotoSymbolsLabelProvider(viewer.getTree().getFont()));
viewer.setUseHashlookup(true);
disposables.add(model.getSymbols().onChange(UIValueListener.from((e, v) -> {
if (!viewer.getControl().isDisposed()) viewer.refresh();
})));
//TODO: somehow show selection in local file, (but not in other file ?)
// viewer.addSelectionChangedListener(event -> {
// IStructuredSelection selection = (IStructuredSelection) event.getSelection();
// if (selection.isEmpty()) {
// return;
// }
// SymbolInformation symbolInformation = (SymbolInformation) selection.getFirstElement();
// Location location = symbolInformation.getLocation();
//
// IResource targetResource = LSPEclipseUtils.findResourceFor(location.getUri());
// if (targetResource == null) {
// return;
// }
// IDocument targetDocument = FileBuffers.getTextFileBufferManager()
// .getTextFileBuffer(targetResource.getFullPath(), LocationKind.IFILE).getDocument();
// if (targetDocument != null) {
// try {
// int offset = LSPEclipseUtils.toOffset(location.getRange().getStart(), targetDocument);
// int endOffset = LSPEclipseUtils.toOffset(location.getRange().getEnd(), targetDocument);
// fTextEditor.selectAndReveal(offset, endOffset - offset);
// } catch (BadLocationException e) {
// LanguageServerPlugin.logError(e);
// }
// }
// });
installWidgetListeners(pattern, viewer);
StyledText statusLabel = new StyledText(dialogArea, SWT.NONE);
// Allow for some extra space for highlight fonts
statusLabel.setLeftMargin(3);
statusLabel.setBottomMargin(2);
statusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
Stylers stylers = new Stylers(dialogArea.getFont());
disposables.add(stylers);
SwtConnect.connectHighlighted(stylers.bold(), statusLabel, model.getStatus(), Duration.ofMillis(500));
viewer.setInput(model);
content.createContents(dialogArea);
return dialogArea;
}
@@ -461,6 +102,11 @@ public class GotoSymbolDialog extends PopupDialog {
return new Point(control.getBounds().width, control.getBounds().height/2);
}
@Override
public IRunnableContext getRunnableContext() {
return PlatformUI.getWorkbench().getProgressService();
}
// /**
// * Determines the graphical area covered by the given text region.
// *

View File

@@ -0,0 +1,401 @@
/*******************************************************************************
* Copyright (c) 2016, 2019, 2020 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
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Michał Niewrzał (Rogue Wave Software Inc.) - initial implementation
* Kris De Volder (Pivotal Inc) - Copied and adapted
*******************************************************************************/
package org.springframework.tooling.ls.eclipse.gotosymbol.dialogs;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.resource.JFaceColors;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StyledCellLabelProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.lsp4e.LSPEclipseUtils;
import org.eclipse.lsp4e.outline.SymbolsLabelProvider;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
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.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.TreeItem;
import org.springframework.tooling.ls.eclipse.gotosymbol.dialogs.GotoSymbolDialogModel.Match;
import org.springsource.ide.eclipse.commons.core.util.FuzzyMatcher;
import org.springsource.ide.eclipse.commons.livexp.core.UIValueListener;
import org.springsource.ide.eclipse.commons.livexp.ui.Disposable;
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
import org.springsource.ide.eclipse.commons.livexp.ui.Stylers;
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
import org.springsource.ide.eclipse.commons.livexp.ui.util.SwtConnect;
@SuppressWarnings("restriction")
public class GotoSymbolSection extends WizardPageSection {
private static class SymbolsContentProvider implements ITreeContentProvider {
@Override
public Object[] getChildren(Object parentElement) {
return null;
}
@Override
public Object getParent(Object element) {
return null;
}
@Override
public boolean hasChildren(Object element) {
return false;
}
@Override
public Object[] getElements(Object inputElement) {
if (inputElement instanceof GotoSymbolDialogModel) {
GotoSymbolDialogModel model = (GotoSymbolDialogModel) inputElement;
return model.getSymbols().getValue().toArray();
}
return null;
}
}
private class GotoSymbolsLabelProvider extends StyledCellLabelProvider {
private Stylers stylers;
private SymbolsLabelProvider symbolsLabelProvider;
public GotoSymbolsLabelProvider(Font base) {
stylers = new Stylers(base);
boolean showSymbolsLabelProviderLocation = false; /* dont show full location. we show relative location in our own implementation below */
boolean showKindInformation = false;
symbolsLabelProvider = new SymbolsLabelProvider(showSymbolsLabelProviderLocation , showKindInformation) {
@Override
protected int getMaxSeverity(IResource resource, IDocument doc, Range range)
throws CoreException, BadLocationException {
int maxSeverity = -1;
for (IMarker marker : resource.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_ZERO)) {
int offset = marker.getAttribute(IMarker.CHAR_START, -1);
if (offset != -1) {
maxSeverity = Math.max(maxSeverity, marker.getAttribute(IMarker.SEVERITY, -1));
}
}
return maxSeverity;
}
};
}
@Override
public Color getToolTipBackgroundColor(Object object) {
return JFaceColors.getInformationViewerBackgroundColor(Display.getDefault());
}
@Override
public Color getToolTipForegroundColor(Object object) {
return JFaceColors.getInformationViewerForegroundColor(Display.getDefault());
}
@Override
public String getToolTipText(Object element) {
if (element instanceof Match) {
SymbolInformation si = getSymbolInformation((Match<?>)element);
if (si!=null) {
return si.getName();
}
}
return null;
}
@Override
public void update(ViewerCell cell) {
super.update(cell);
Object obj = cell.getElement();
if (obj instanceof Match) {
Match<?> match = (Match<?>) obj;
cell.setImage(symbolsLabelProvider.getImage(match.value));
StyledString styledString = getStyledText(match);
cell.setText(styledString.getString());
cell.setStyleRanges(styledString.getStyleRanges());
cell.getControl().redraw();
//^^^ Sigh... Yes, this is needed. It seems SWT/Jface isn't smart enough to itself figure out that if
//the styleranges change a redraw is needed to make the change visible.
} else {
super.update(cell);
}
}
private StyledString getStyledText(Match<?> element) {
SymbolInformation symbolInformation = getSymbolInformation(element);
if (symbolInformation != null) {
String name = symbolInformation.getName();
StyledString s = new StyledString(name);
Collection<IRegion> highlights = FuzzyMatcher.highlights(element.query, name.toLowerCase());
for (IRegion hl : highlights) {
s.setStyle(hl.getOffset(), hl.getLength(), stylers.bold());
}
String locationText = getSymbolLocationText(symbolInformation);
if (locationText != null) {
s = s.append(locationText, stylers.italicColoured(SWT.COLOR_DARK_GRAY));
}
return s;
} else {
return symbolsLabelProvider.getStyledText(element.value);
}
}
@Override
public void dispose() {
stylers.dispose();
symbolsLabelProvider.dispose();
super.dispose();
}
protected String getSymbolLocationText(SymbolInformation symbol) {
Optional<String> location = GotoSymbolSection.this
.getSymbolLocation(symbol);
if (location.isPresent()) {
return " -- [" + location.get() + "]";
}
return null;
}
}
private final GotoSymbolDialogModel model;
public GotoSymbolSection(IPageWithSections owner, GotoSymbolDialogModel model) {
super(owner);
this.model = model;
}
@Override
public void createContents(Composite dialogArea) {
List<Disposable> disposables = new ArrayList<>();
dialogArea.addDisposeListener(de -> {
for (Disposable d : disposables) {
d.dispose();
}
});
//Search box:
Text pattern = new Text(dialogArea, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL);
// pattern.getAccessible().addAccessibleListener(new AccessibleAdapter() {
// public void getName(AccessibleEvent e) {
// e.result = LegacyActionTools.removeMnemonics(headerLabel)
// .getText());
// }
// });
pattern.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
pattern.setMessage(model.getSearchBoxHintMessage());
SwtConnect.connect(pattern, model.getSearchBox());
//Tree viewer with results
TreeViewer viewer = new TreeViewer(dialogArea, SWT.SINGLE);
ColumnViewerToolTipSupport.enableFor(viewer);
GridDataFactory.fillDefaults().grab(true, true).applyTo(viewer.getControl());
viewer.setContentProvider(new SymbolsContentProvider());
viewer.setLabelProvider(new GotoSymbolsLabelProvider(viewer.getTree().getFont()));
viewer.setUseHashlookup(true);
disposables.add(model.getSymbols().onChange(UIValueListener.from((e, v) -> {
if (!viewer.getControl().isDisposed()) viewer.refresh();
})));
//TODO: somehow show selection in local file, (but not in other file ?)
// viewer.addSelectionChangedListener(event -> {
// IStructuredSelection selection = (IStructuredSelection) event.getSelection();
// if (selection.isEmpty()) {
// return;
// }
// SymbolInformation symbolInformation = (SymbolInformation) selection.getFirstElement();
// Location location = symbolInformation.getLocation();
//
// IResource targetResource = LSPEclipseUtils.findResourceFor(location.getUri());
// if (targetResource == null) {
// return;
// }
// IDocument targetDocument = FileBuffers.getTextFileBufferManager()
// .getTextFileBuffer(targetResource.getFullPath(), LocationKind.IFILE).getDocument();
// if (targetDocument != null) {
// try {
// int offset = LSPEclipseUtils.toOffset(location.getRange().getStart(), targetDocument);
// int endOffset = LSPEclipseUtils.toOffset(location.getRange().getEnd(), targetDocument);
// fTextEditor.selectAndReveal(offset, endOffset - offset);
// } catch (BadLocationException e) {
// LanguageServerPlugin.logError(e);
// }
// }
// });
installWidgetListeners(pattern, viewer);
//Status label
StyledText statusLabel = new StyledText(dialogArea, SWT.NONE);
// Allow for some extra space for highlight fonts
statusLabel.setLeftMargin(3);
statusLabel.setBottomMargin(2);
statusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
Stylers stylers = new Stylers(dialogArea.getFont());
disposables.add(stylers);
SwtConnect.connectHighlighted(stylers.bold(), statusLabel, model.getStatus(), Duration.ofMillis(500));
viewer.setInput(model);
}
private void installWidgetListeners(Text pattern, TreeViewer list) {
pattern.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if (e.keyCode == SWT.ARROW_DOWN) {
if (list.getTree().getItemCount() > 0) {
list.getTree().setFocus();
TreeItem[] items = list.getTree().getItems();
if (items!=null && items.length>0) {
list.getTree().setSelection(items[0]);
//programatic selection may not fire selection events so...
list.getTree().notifyListeners(SWT.Selection,
new Event());
}
}
} else if (e.character == '\r') {
performOk(list);
}
}
});
list.getTree().addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if (e.keyCode == SWT.ARROW_UP && (e.stateMask & SWT.SHIFT) == 0
&& (e.stateMask & SWT.CTRL) == 0) {
StructuredSelection selection = (StructuredSelection) list
.getSelection();
if (selection.size() == 1) {
Object element = selection.getFirstElement();
if (element.equals(getFirstElement(list))) {
pattern.setFocus();
list.setSelection(new StructuredSelection());
list.getTree().notifyListeners(SWT.Selection,
new Event());
}
}
} else if (e.character == '\r') {
performOk(list);
}
// if (e.keyCode == SWT.ARROW_DOWN
// && (e.stateMask & SWT.SHIFT) != 0
// && (e.stateMask & SWT.CTRL) != 0) {
//
// list.getTree().notifyListeners(SWT.Selection, new Event());
// }
}
});
list.addDoubleClickListener(e -> performOk(list));
}
private void performOk(TreeViewer list) {
if (model.performOk(getTarget(list))) {
close();
}
}
/**
* 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 instanceof Match) {
SymbolInformation si = getSymbolInformation((Match<?>) selected);
if (si!=null) {
return si;
}
}
}
//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 SymbolInformation getFirstElement(TreeViewer list) {
TreeItem[] items = list.getTree().getItems();
if (items!=null && items.length>0) {
TreeItem item = items[0];
Object data = item.getData();
if (data instanceof Match) {
SymbolInformation si = getSymbolInformation((Match<?>) data);
if (si!=null) {
return si;
}
}
}
return null;
}
private SymbolInformation getSymbolInformation(Match<?> element) {
if (element.value instanceof SymbolInformation) {
return (SymbolInformation) element.value;
}
if (element.value instanceof Either) {
Either<?,?> either = (Either<?, ?>)element.value;
if (either.isLeft()) {
Object left = either.getLeft();
if (left instanceof SymbolInformation) {
return (SymbolInformation) left;
}
}
}
return null;
}
private Optional<String> getSymbolLocation(SymbolInformation symbolInformation) {
String val = null;
if (!model.fromFileProvider(symbolInformation)) {
Location location = symbolInformation.getLocation();
IResource targetResource = LSPEclipseUtils.findResourceFor(location.getUri());
if (targetResource != null && targetResource.getFullPath() != null) {
val = targetResource.getFullPath().toString();
}
}
return val != null ? Optional.of(val) : Optional.empty();
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017, 2019 Pivotal, Inc.
* Copyright (c) 2017, 2019, 2020 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
@@ -12,6 +12,7 @@ package org.springframework.tooling.ls.eclipse.gotosymbol.dialogs;
import java.time.Duration;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.eclipse.core.commands.ExecutionEvent;
@@ -38,11 +39,22 @@ import reactor.core.publisher.Mono;
@SuppressWarnings("restriction")
public class InWorkspaceSymbolsProvider implements SymbolsProvider {
public static InWorkspaceSymbolsProvider createFor(Supplier<IProject> _project) {
return new InWorkspaceSymbolsProvider(() -> {
IProject project = _project.get();
if (project!=null) {
return LanguageServiceAccessor.getLanguageServers(project,
capabilities -> Boolean.TRUE.equals(capabilities.getWorkspaceSymbolProvider()), true);
}
return ImmutableList.of();
});
}
public static InWorkspaceSymbolsProvider createFor(IProject project) {
List<LanguageServer> languageServers = LanguageServiceAccessor.getLanguageServers(project,
capabilities -> Boolean.TRUE.equals(capabilities.getWorkspaceSymbolProvider()), true);
if (!languageServers.isEmpty()) {
return new InWorkspaceSymbolsProvider(languageServers);
return new InWorkspaceSymbolsProvider(() -> languageServers);
}
return null;
}
@@ -50,10 +62,9 @@ public class InWorkspaceSymbolsProvider implements SymbolsProvider {
private static final Duration TIMEOUT = Duration.ofSeconds(2);
private static final int MAX_RESULTS = 200;
private Supplier<List<LanguageServer>> languageServers;
private List<LanguageServer> languageServers;
public InWorkspaceSymbolsProvider(List<LanguageServer> languageServers) {
public InWorkspaceSymbolsProvider(Supplier<List<LanguageServer>> languageServers) {
this.languageServers = languageServers;
}
@@ -75,7 +86,7 @@ public class InWorkspaceSymbolsProvider implements SymbolsProvider {
// really use this with a single language server anyways.
WorkspaceSymbolParams params = new WorkspaceSymbolParams(query);
Flux<Either<SymbolInformation, DocumentSymbol>> symbols = Flux.fromIterable(this.languageServers)
Flux<Either<SymbolInformation, DocumentSymbol>> symbols = Flux.fromIterable(this.languageServers.get())
.flatMap(server -> Mono.fromFuture(server.getWorkspaceService().symbol(params))
.timeout(TIMEOUT)
.doOnError(e -> log(e))

View File

@@ -0,0 +1,129 @@
package org.springframework.tooling.ls.eclipse.gotosymbol.view;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.ISelectionService;
import org.eclipse.ui.IWorkbenchPart;
import org.springframework.ide.eclipse.boot.dash.views.sections.ViewPartWithSections;
import org.springframework.tooling.ls.eclipse.gotosymbol.dialogs.GotoSymbolSection;
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.ui.DescriptionSection;
import org.springsource.ide.eclipse.commons.livexp.ui.IPageSection;
import org.springsource.ide.eclipse.commons.livexp.ui.InfoFieldSection;
import org.springsource.ide.eclipse.commons.livexp.ui.StringFieldSection;
import org.springsource.ide.eclipse.commons.livexp.util.Log;
public class SpringSymbolsView extends ViewPartWithSections {
/**
* Adds scroll support to the whole view. You probably want to disable this
* if view is broken into pieces that have their own scrollbars
*/
private static final boolean ENABLE_SCROLLING = false;
private final SpringSymbolsViewModel model = new SpringSymbolsViewModel();
private final LiveExpression<String> projectAsString = model.currentProject.apply(p -> {
System.out.println("project = "+p);
return p==null ? "null" : p.getName();
});
private ISelectionListener selectionListener = new ISelectionListener() {
@Override
public void selectionChanged(IWorkbenchPart arg0, ISelection selection) {
if (selection instanceof IStructuredSelection) {
IStructuredSelection ss = (IStructuredSelection) selection;
Object element = ss.getFirstElement();
IProject project = getProject(element);
if (project!=null) {
model.currentProject.setValue(project);
}
} else if (selection instanceof ITextSelection) {
//Let's assume the selection is in the active editor
try {
IEditorPart editor = getSite().getWorkbenchWindow().getActivePage().getActiveEditor();
if (editor!=null) {
IEditorInput input = editor.getEditorInput();
IResource resource = input.getAdapter(IResource.class);
if (resource != null) {
IProject project = resource.getProject();
if (project!=null) {
model.currentProject.setValue(project);
}
}
}
} catch (Exception e) {
Log.log(e);
}
}
}
private IProject getProject(Object element) {
if (element instanceof IResource) {
return ((IResource) element).getProject();
} else if (element instanceof IAdaptable) {
IResource resource = ((IAdaptable) element).getAdapter(IResource.class);
if (resource!=null) {
return resource.getProject();
}
}
return null;
}
};
public SpringSymbolsView() {
super(ENABLE_SCROLLING);
}
@Override
protected List<IPageSection> createSections() throws CoreException {
List<IPageSection> sections = new ArrayList<>();
sections.add(new InfoFieldSection(this, "Project", projectAsString));
sections.add(new GotoSymbolSection(this, model.gotoSymbols));
ISelectionService selectitonService = getSite().getWorkbenchWindow().getSelectionService();
selectitonService.addSelectionListener(selectionListener);
return sections;
}
/**
* This is a callback that will allow us to create the viewer and initialize
* it.
*/
public void createPartControl(Composite parent) {
super.createPartControl(parent);
contributeToActionBars();
}
private void contributeToActionBars() {
IActionBars bars = getViewSite().getActionBars();
fillLocalPullDown(bars.getMenuManager());
fillLocalToolBar(bars.getToolBarManager());
}
/**
* Fills the pull-down menu for this view (accessible from the toolbar)
*/
private void fillLocalPullDown(IMenuManager manager) {
}
private void fillLocalToolBar(IToolBarManager manager) {
}
}

View File

@@ -0,0 +1,13 @@
package org.springframework.tooling.ls.eclipse.gotosymbol.view;
import org.eclipse.core.resources.IProject;
import org.springframework.tooling.ls.eclipse.gotosymbol.dialogs.GotoSymbolDialogModel;
import org.springframework.tooling.ls.eclipse.gotosymbol.dialogs.InWorkspaceSymbolsProvider;
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
public class SpringSymbolsViewModel {
public final LiveVariable<IProject> currentProject = new LiveVariable<>();
public final GotoSymbolDialogModel gotoSymbols = new GotoSymbolDialogModel(null, InWorkspaceSymbolsProvider.createFor(currentProject::getValue));
}