| /******************************************************************************* |
| * Copyright (c) 2017 Red Hat Inc. and others. |
| * This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Mickael Istria (Red Hat Inc.) - initial implementation |
| * Lucas Bullen (Red Hat Inc.) - Bug 508472 - Outline to provide "Link with Editor" |
| * - Bug 517428 - Requests sent before initialization |
| *******************************************************************************/ |
| package org.eclipse.lsp4e.outline; |
| |
| import java.net.URI; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.concurrent.CompletableFuture; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceChangeEvent; |
| import org.eclipse.core.resources.IResourceChangeListener; |
| import org.eclipse.core.resources.IResourceDelta; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.preferences.InstanceScope; |
| import org.eclipse.jface.text.DocumentEvent; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IDocumentListener; |
| import org.eclipse.jface.text.ITextOperationTarget; |
| import org.eclipse.jface.text.ITextSelection; |
| import org.eclipse.jface.text.ITextViewer; |
| import org.eclipse.jface.text.reconciler.AbstractReconciler; |
| import org.eclipse.jface.text.reconciler.DirtyRegion; |
| import org.eclipse.jface.text.reconciler.IReconcilingStrategy; |
| import org.eclipse.jface.viewers.ITreeContentProvider; |
| import org.eclipse.jface.viewers.ITreeSelection; |
| import org.eclipse.jface.viewers.StructuredViewer; |
| import org.eclipse.jface.viewers.TreePath; |
| import org.eclipse.jface.viewers.TreeSelection; |
| import org.eclipse.jface.viewers.TreeViewer; |
| import org.eclipse.jface.viewers.Viewer; |
| import org.eclipse.lsp4e.LSPEclipseUtils; |
| import org.eclipse.lsp4e.LanguageServerPlugin; |
| import org.eclipse.lsp4e.outline.CNFOutlinePage.OutlineInfo; |
| import org.eclipse.lsp4j.DocumentSymbol; |
| import org.eclipse.lsp4j.DocumentSymbolParams; |
| import org.eclipse.lsp4j.SymbolInformation; |
| import org.eclipse.lsp4j.TextDocumentIdentifier; |
| import org.eclipse.lsp4j.jsonrpc.messages.Either; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.ui.IEditorPart; |
| import org.eclipse.ui.IMemento; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.navigator.ICommonContentExtensionSite; |
| import org.eclipse.ui.navigator.ICommonContentProvider; |
| import org.eclipse.ui.texteditor.AbstractTextEditor; |
| |
| public class LSSymbolsContentProvider implements ICommonContentProvider, ITreeContentProvider { |
| |
| public static final Object COMPUTING = new Object(); |
| |
| interface IOutlineUpdater { |
| void install(); |
| |
| void uninstall(); |
| } |
| |
| class DocumentChangedOutlineUpdater implements IDocumentListener, IOutlineUpdater { |
| |
| private final IDocument document; |
| |
| @Override |
| public void install() { |
| document.addDocumentListener(this); |
| refreshTreeContentFromLS(); |
| } |
| |
| @Override |
| public void uninstall() { |
| document.removeDocumentListener(this); |
| } |
| |
| DocumentChangedOutlineUpdater(IDocument document) { |
| this.document = document; |
| } |
| |
| @Override |
| public void documentAboutToBeChanged(DocumentEvent event) { |
| // Do nothing |
| } |
| |
| @Override |
| public void documentChanged(DocumentEvent event) { |
| refreshTreeContentFromLS(); |
| } |
| } |
| |
| class ReconcilerOutlineUpdater extends AbstractReconciler implements IOutlineUpdater { |
| |
| private final ITextViewer textViewer; |
| |
| ReconcilerOutlineUpdater(ITextViewer textViewer) { |
| this.textViewer = textViewer; |
| super.setIsIncrementalReconciler(false); |
| super.setIsAllowedToModifyDocument(false); |
| } |
| |
| @Override |
| public void install() { |
| super.install(textViewer); |
| } |
| |
| @Override |
| protected void initialProcess() { |
| refreshTreeContentFromLS(); |
| } |
| |
| @Override |
| protected void process(DirtyRegion dirtyRegion) { |
| refreshTreeContentFromLS(); |
| } |
| |
| @Override |
| protected void reconcilerDocumentChanged(IDocument newDocument) { |
| // Do nothing |
| } |
| |
| @Override |
| public IReconcilingStrategy getReconcilingStrategy(String contentType) { |
| return null; |
| } |
| } |
| |
| class ResourceChangeOutlineUpdater implements IResourceChangeListener, IOutlineUpdater { |
| |
| private final IResource resource; |
| |
| public ResourceChangeOutlineUpdater(IResource resource) { |
| this.resource = resource; |
| } |
| |
| @Override |
| public void install() { |
| resource.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE); |
| } |
| |
| @Override |
| public void uninstall() { |
| resource.getWorkspace().removeResourceChangeListener(this); |
| } |
| |
| @Override |
| public void resourceChanged(IResourceChangeEvent event) { |
| if ((event.getDelta().getFlags() ^ IResourceDelta.MARKERS) != 0) { |
| try { |
| event.getDelta().accept(delta -> { |
| if (delta.getResource().equals(this.resource)) { |
| viewer.getControl().getDisplay().asyncExec(() -> { |
| if (!viewer.getControl().isDisposed() && viewer instanceof StructuredViewer) { |
| viewer.refresh(true); |
| } |
| }); |
| } |
| return delta.getResource().getFullPath().isPrefixOf(this.resource.getFullPath()); |
| }); |
| } catch (CoreException e) { |
| LanguageServerPlugin.logError(e); |
| } |
| } |
| } |
| } |
| |
| private TreeViewer viewer; |
| private Throwable lastError; |
| private OutlineInfo outlineInfo; |
| |
| private SymbolsModel symbolsModel = new SymbolsModel(); |
| private CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> symbols; |
| private final boolean refreshOnResourceChanged; |
| private IOutlineUpdater outlineUpdater; |
| |
| public LSSymbolsContentProvider() { |
| this(false); |
| } |
| |
| public LSSymbolsContentProvider(boolean refreshOnResourceChanged) { |
| this.refreshOnResourceChanged = refreshOnResourceChanged; |
| } |
| |
| @Override |
| public void init(ICommonContentExtensionSite aConfig) { |
| } |
| |
| @Override |
| public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { |
| this.viewer = (TreeViewer) viewer; |
| this.outlineInfo = (OutlineInfo) newInput; |
| IFile file = LSPEclipseUtils.getFile(this.outlineInfo.document); |
| this.symbolsModel.setFile(file); |
| if (outlineUpdater != null) { |
| outlineUpdater.uninstall(); |
| } |
| outlineUpdater = createOutlineUpdater(file); |
| outlineUpdater.install(); |
| } |
| |
| private IOutlineUpdater createOutlineUpdater(IFile file) { |
| if (refreshOnResourceChanged) { |
| return new ResourceChangeOutlineUpdater(file); |
| } |
| ITextViewer textViewer = outlineInfo.textEditor == null ? null |
| : ((ITextViewer) outlineInfo.textEditor.getAdapter(ITextOperationTarget.class)); |
| return textViewer == null |
| ? new DocumentChangedOutlineUpdater(outlineInfo.document) |
| : new ReconcilerOutlineUpdater(textViewer); |
| } |
| |
| @Override |
| public Object[] getElements(Object inputElement) { |
| if (this.symbols != null && !this.symbols.isDone()) { |
| return new Object[] { COMPUTING }; |
| } |
| if (this.lastError != null) { |
| return new Object[] { this.lastError }; |
| } |
| return symbolsModel.getElements(); |
| } |
| |
| @Override |
| public Object[] getChildren(Object parentElement) { |
| return symbolsModel.getChildren(parentElement); |
| } |
| |
| @Override |
| public Object getParent(Object element) { |
| return symbolsModel.getParent(element); |
| } |
| |
| @Override |
| public boolean hasChildren(Object parentElement) { |
| return symbolsModel.hasChildren(parentElement); |
| } |
| |
| private void refreshTreeContentFromLS() { |
| if (symbols != null && !symbols.isDone()) { |
| symbols.cancel(true); |
| } |
| lastError = null; |
| URI documentUri = LSPEclipseUtils.toUri(outlineInfo.document); |
| if(documentUri == null) { |
| return; |
| } |
| DocumentSymbolParams params = new DocumentSymbolParams( |
| new TextDocumentIdentifier(documentUri.toString())); |
| symbols = outlineInfo.languageServer.getTextDocumentService().documentSymbol(params); |
| symbols.thenAcceptAsync(t -> { |
| symbolsModel.update(t); |
| |
| viewer.getControl().getDisplay().asyncExec(() -> { |
| TreePath[] expandedElements = viewer.getExpandedTreePaths(); |
| TreePath[] initialSelection = ((ITreeSelection)viewer.getSelection()).getPaths(); |
| viewer.refresh(); |
| viewer.setExpandedTreePaths(Arrays.stream(expandedElements).map(symbolsModel::toUpdatedSymbol).filter(Objects::nonNull).toArray(TreePath[]::new)); |
| viewer.setSelection(new TreeSelection(Arrays.stream(initialSelection).map(symbolsModel::toUpdatedSymbol).filter(Objects::nonNull).toArray(TreePath[]::new))); |
| }); |
| if (!InstanceScope.INSTANCE.getNode(LanguageServerPlugin.PLUGIN_ID) |
| .getBoolean(CNFOutlinePage.LINK_WITH_EDITOR_PREFERENCE, true)) { |
| return; |
| } |
| Display.getDefault().asyncExec(() -> { |
| IEditorPart editorPart = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage() |
| .getActiveEditor(); |
| if (editorPart instanceof AbstractTextEditor) { |
| ITextSelection selection = (ITextSelection) ((AbstractTextEditor) editorPart).getSelectionProvider() |
| .getSelection(); |
| CNFOutlinePage.refreshTreeSelection(viewer, selection.getOffset(), outlineInfo.document); |
| } |
| }); |
| }); |
| |
| symbols.exceptionally(ex -> { |
| lastError = ex; |
| viewer.getControl().getDisplay().asyncExec(viewer::refresh); |
| return Collections.emptyList(); |
| }); |
| } |
| |
| @Override |
| public void dispose() { |
| if(outlineUpdater != null) { |
| outlineUpdater.uninstall(); |
| } |
| ICommonContentProvider.super.dispose(); |
| } |
| |
| @Override |
| public void restoreState(IMemento aMemento) { |
| } |
| |
| @Override |
| public void saveState(IMemento aMemento) { |
| } |
| } |