From a240416dc731ee479da473909fbbd376bb872575 Mon Sep 17 00:00:00 2001 From: Dietrich Travkin <10887297+travkin79@users.noreply.github.com> Date: Tue, 13 Aug 2024 19:53:00 +0200 Subject: [PATCH] Add content filtering to outline view based on SymbolKind (fix issue #254) (#1049) --- org.eclipse.lsp4e/plugin.xml | 21 +++++ .../eclipse/lsp4e/outline/CNFOutlinePage.java | 1 + .../outline/LSSymbolsContentProvider.java | 81 ++++++++++++++--- ...lineViewHideSymbolKindMenuContributor.java | 87 +++++++++++++++++++ 4 files changed, 180 insertions(+), 10 deletions(-) create mode 100644 org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/OutlineViewHideSymbolKindMenuContributor.java diff --git a/org.eclipse.lsp4e/plugin.xml b/org.eclipse.lsp4e/plugin.xml index 82d103b45..129b22dbe 100644 --- a/org.eclipse.lsp4e/plugin.xml +++ b/org.eclipse.lsp4e/plugin.xml @@ -282,6 +282,27 @@ + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/CNFOutlinePage.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/CNFOutlinePage.java index 067aa5cd4..38bfd33ac 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/CNFOutlinePage.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/CNFOutlinePage.java @@ -63,6 +63,7 @@ public class CNFOutlinePage implements IContentOutlinePage, ILabelProviderListen public static final String LINK_WITH_EDITOR_PREFERENCE = ID + ".linkWithEditor"; //$NON-NLS-1$ public static final String SHOW_KIND_PREFERENCE = ID + ".showKind"; //$NON-NLS-1$ public static final String SORT_OUTLINE_PREFERENCE = ID + ".sortOutline"; //$NON-NLS-1$ + public static final String HIDE_DOCUMENT_SYMBOL_KIND_PREFERENCE_PREFIX = ID + ".hide"; //$NON-NLS-1$ private CommonViewer outlineViewer = lateNonNull(); diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/LSSymbolsContentProvider.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/LSSymbolsContentProvider.java index 4648166c6..cab53be9f 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/LSSymbolsContentProvider.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/LSSymbolsContentProvider.java @@ -7,9 +7,10 @@ * 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 + * 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 + * Dietrich Travkin (Solunar GmbH) - Issue 254 - Add outline view contents filtering *******************************************************************************/ package org.eclipse.lsp4e.outline; @@ -31,6 +32,9 @@ import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; +import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -53,10 +57,12 @@ import org.eclipse.lsp4e.LanguageServerPlugin; import org.eclipse.lsp4e.LanguageServerWrapper; import org.eclipse.lsp4e.internal.CancellationUtil; +import org.eclipse.lsp4e.outline.SymbolsModel.DocumentSymbolWithURI; import org.eclipse.lsp4e.ui.UI; import org.eclipse.lsp4j.DocumentSymbol; import org.eclipse.lsp4j.DocumentSymbolParams; import org.eclipse.lsp4j.SymbolInformation; +import org.eclipse.lsp4j.SymbolKind; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.ui.IMemento; @@ -139,6 +145,34 @@ public void documentAboutToBeChanged(DocumentEvent event) { public void documentChanged(DocumentEvent event) { refreshTreeContentFromLS(); } + + } + + private final class PreferencesChangedOutlineUpdater implements IPreferenceChangeListener, IOutlineUpdater { + + @Override + public void install() { + IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(LanguageServerPlugin.PLUGIN_ID); + preferences.addPreferenceChangeListener(this); + } + + @Override + public void uninstall() { + IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(LanguageServerPlugin.PLUGIN_ID); + preferences.removePreferenceChangeListener(this); + } + + @Override + public void preferenceChange(PreferenceChangeEvent event) { + if (viewer != null + && event.getKey().startsWith(CNFOutlinePage.HIDE_DOCUMENT_SYMBOL_KIND_PREFERENCE_PREFIX)) { + viewer.getControl().getDisplay().asyncExec(() -> { + if(!viewer.getTree().isDisposed()) { + viewer.refresh(); + } + }); + } + } } private final class ReconcilerOutlineUpdater extends AbstractReconciler implements IOutlineUpdater { @@ -225,6 +259,7 @@ public void resourceChanged(IResourceChangeEvent event) { private final boolean refreshOnResourceChanged; private boolean isQuickOutline; private @Nullable IOutlineUpdater outlineUpdater; + private IOutlineUpdater preferencesDependantOutlineUpdater; public LSSymbolsContentProvider() { this(false); @@ -232,10 +267,12 @@ public LSSymbolsContentProvider() { public LSSymbolsContentProvider(boolean refreshOnResourceChanged) { this.refreshOnResourceChanged = refreshOnResourceChanged; + preferencesDependantOutlineUpdater = new PreferencesChangedOutlineUpdater(); } @Override public void init(@NonNullByDefault({}) ICommonContentExtensionSite aConfig) { + preferencesDependantOutlineUpdater.install(); } @Override @@ -281,18 +318,39 @@ private IOutlineUpdater createOutlineUpdater() { @Override public Object[] getElements(@Nullable Object inputElement) { - if (this.symbols != null && !this.symbols.isDone()) { + if (symbols != null && !symbols.isDone()) { return new Object[] { new PendingUpdateAdapter() }; } - if (this.lastError != null && symbolsModel.getElements().length == 0) { + if (lastError != null && symbolsModel.getElements().length == 0) { return new Object[] { "An error occured, see log for details" }; //$NON-NLS-1$ } - return symbolsModel.getElements(); + return Arrays.stream(symbolsModel.getElements()) + .filter(element -> !hideElement(element)) + .toArray(Object[]::new); } @Override public Object[] getChildren(Object parentElement) { - return symbolsModel.getChildren(parentElement); + return Arrays.stream(symbolsModel.getChildren(parentElement)) + .filter(element -> !hideElement(element)) + .toArray(Object[]::new); + } + + private boolean hideElement(Object element) { + SymbolKind kind = null; + + if (element instanceof DocumentSymbol documentSymbol) { + kind = documentSymbol.getKind(); + } else if (element instanceof DocumentSymbolWithURI documentSymbolWithURI) { + kind = documentSymbolWithURI.symbol.getKind(); + } else if (element instanceof SymbolInformation symbolInformation) { + kind = symbolInformation.getKind(); + } + + if (kind != null) { + return OutlineViewHideSymbolKindMenuContributor.isHideSymbolKind(kind); + } + return false; } @Override @@ -342,10 +400,12 @@ protected void refreshTreeContentFromLS() { TreePath[] initialSelection = ((ITreeSelection) viewer.getSelection()).getPaths(); viewer.refresh(); if (expandedElements.length > 0) { - viewer.setExpandedTreePaths(Arrays.stream(expandedElements).map(symbolsModel::toUpdatedSymbol) - .filter(Objects::nonNull).toArray(TreePath[]::new)); + 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))); + .map(symbolsModel::toUpdatedSymbol) + .filter(Objects::nonNull).toArray(TreePath[]::new))); } else { viewer.expandToLevel(EXPAND_ROOT_LEVEL); } @@ -376,6 +436,7 @@ public void dispose() { if (outlineUpdater != null) { outlineUpdater.uninstall(); } + preferencesDependantOutlineUpdater.uninstall(); } @Override diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/OutlineViewHideSymbolKindMenuContributor.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/OutlineViewHideSymbolKindMenuContributor.java new file mode 100644 index 000000000..d7e543b36 --- /dev/null +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/OutlineViewHideSymbolKindMenuContributor.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2024 Advantest Europe GmbH. All rights reserved. + * 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: + * Dietrich Travkin (Solunar GmbH) - initial implementation of outline contents filtering (issue #254) + *******************************************************************************/ +package org.eclipse.lsp4e.outline; + +import java.util.Arrays; +import java.util.Comparator; + +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ActionContributionItem; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IContributionItem; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.lsp4e.LanguageServerPlugin; +import org.eclipse.lsp4e.ui.LSPImages; +import org.eclipse.lsp4j.SymbolKind; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.actions.CompoundContributionItem; + +public class OutlineViewHideSymbolKindMenuContributor extends CompoundContributionItem { + + @Override + protected IContributionItem[] getContributionItems() { + return Arrays.stream(SymbolKind.values()) + .sorted(new Comparator() { + + @Override + public int compare(SymbolKind sk1, SymbolKind sk2) { + return sk1.name().compareTo(sk2.name()); + } + + }) + .map(kind -> createHideSymbolKindContributionItem(kind)) + .toArray(IContributionItem[]::new); + } + + private IContributionItem createHideSymbolKindContributionItem(SymbolKind kind) { + return new ActionContributionItem(new HideSymbolKindAction(kind)); + } + + static boolean isHideSymbolKind(SymbolKind kind) { + IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(LanguageServerPlugin.PLUGIN_ID); + return preferences.getBoolean(CNFOutlinePage.HIDE_DOCUMENT_SYMBOL_KIND_PREFERENCE_PREFIX + kind.name(), false); + } + + static boolean toggleHideSymbolKind(SymbolKind kind) { + IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(LanguageServerPlugin.PLUGIN_ID); + boolean oldValue = isHideSymbolKind(kind); + + preferences.putBoolean(CNFOutlinePage.HIDE_DOCUMENT_SYMBOL_KIND_PREFERENCE_PREFIX + kind.name(), !oldValue); + + return !oldValue; + } + + private static class HideSymbolKindAction extends Action { + private final SymbolKind kind; + + HideSymbolKindAction(SymbolKind kind) { + super(kind.name(), IAction.AS_CHECK_BOX); + this.kind = kind; + setChecked(isHideSymbolKind(kind)); + + Image img = LSPImages.imageFromSymbolKind(kind); + if (img != null) { + setImageDescriptor(ImageDescriptor.createFromImage(img)); + } + } + + @Override + public void run() { + boolean checkedState = toggleHideSymbolKind(kind); + setChecked(checkedState); + } + + } + +}