diff --git a/org.eclipse.lsp4e.test/META-INF/MANIFEST.MF b/org.eclipse.lsp4e.test/META-INF/MANIFEST.MF
index 09733f8d6..d9ad4b604 100644
--- a/org.eclipse.lsp4e.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.lsp4e.test/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Tests for language server bundle (Incubation)
Bundle-SymbolicName: org.eclipse.lsp4e.test;singleton:=true
-Bundle-Version: 0.16.9.qualifier
+Bundle-Version: 0.16.10.qualifier
Fragment-Host: org.eclipse.lsp4e
Bundle-Vendor: Eclipse LSP4E
Bundle-RequiredExecutionEnvironment: JavaSE-21
diff --git a/org.eclipse.lsp4e.test/pom.xml b/org.eclipse.lsp4e.test/pom.xml
index a098539ea..9347b66c7 100644
--- a/org.eclipse.lsp4e.test/pom.xml
+++ b/org.eclipse.lsp4e.test/pom.xml
@@ -8,7 +8,7 @@
org.eclipse.lsp4e.test
eclipse-test-plugin
- 0.16.9-SNAPSHOT
+ 0.16.10-SNAPSHOT
diff --git a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/utils/LSPImagesTest.java b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/utils/LSPImagesTest.java
index 369aeaafe..539fa17d1 100644
--- a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/utils/LSPImagesTest.java
+++ b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/utils/LSPImagesTest.java
@@ -28,21 +28,21 @@
import org.junit.jupiter.params.provider.EnumSource.Mode;
public class LSPImagesTest {
-
+
@ParameterizedTest
@EnumSource(SymbolKind.class)
public void testAllImagesForSymbolKindAvailable(SymbolKind kind) {
Image img = LSPImages.imageFromSymbolKind(kind);
-
+
assertNotNull(img);
}
-
+
@ParameterizedTest
@EnumSource(SymbolTag.class)
public void testAllOverlayImagesForSymbolTagAvailable(SymbolTag tag) {
ImageDescriptor descriptor = LSPImages.imageDescriptorOverlayFromSymbolTag(tag);
Image img = LSPImages.imageOverlayFromSymbolTag(tag);
-
+
assertNotNull(descriptor);
assertNotNull(img);
}
@@ -54,22 +54,23 @@ public void testAllOverlayImagesForSymbolTagAvailable(SymbolTag tag) {
public void testVisibilityOverlayImagesForFieldsAndMethodsAvailable(SymbolTag tag) {
var symbolTags = List.of(tag);
SymbolIconProvider labelProvider = new SymbolIconProvider();
-
- Image fieldImage = labelProvider.getImageFor(SymbolKind.Field, symbolTags);
- Image methodImage = labelProvider.getImageFor(SymbolKind.Method, symbolTags);
-
+
+ // we do not need a symbol for this test, so the last argument is null
+ Image fieldImage = labelProvider.getImageFor(SymbolKind.Field, symbolTags, null);
+ Image methodImage = labelProvider.getImageFor(SymbolKind.Method, symbolTags, null);
+
assertNotNull(fieldImage);
assertNotNull(methodImage);
}
-
+
@ParameterizedTest
@EnumSource(value=CompletionItemKind.class, mode=Mode.EXCLUDE, names= { "Color", "Event", "Operator" })
public void testAllImagesForCompletionItemKindAvailable(CompletionItemKind kind) {
CompletionItem item = new CompletionItem();
item.setKind(kind);
-
+
Image img = LSPImages.imageFromCompletionItem(item);
-
+
assertNotNull(img);
}
diff --git a/org.eclipse.lsp4e/plugin.xml b/org.eclipse.lsp4e/plugin.xml
index fc7d6aeee..42c5b03b7 100644
--- a/org.eclipse.lsp4e/plugin.xml
+++ b/org.eclipse.lsp4e/plugin.xml
@@ -2,6 +2,7 @@
+
diff --git a/org.eclipse.lsp4e/schema/symbolIconsProvider.exsd b/org.eclipse.lsp4e/schema/symbolIconsProvider.exsd
new file mode 100644
index 000000000..cbfbbb433
--- /dev/null
+++ b/org.eclipse.lsp4e/schema/symbolIconsProvider.exsd
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+ This extension point allows for customizing the (document) symbol icons with their overlays, their placing and filtering for certain language servers. This way, clients can use other images, adapt overlay icon placing and decide wich symbol details to show or hide in the outline view, call hierarchy, or type hierarchy.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The fully qualified identifier of the extension point.
+
+This extension point offers client plug-ins options to adapt placement and selection of overlay icons for document symbols and other related elements.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A string uniquely identifying this symbol icon provider extension.
+
+
+
+
+
+
+ The fully qualified name of the class implementing this symbol icon provider. The class must extend <code>org.eclipse.lsp4e.ui.SymbolIconProvider</code>. Clients can override individual methods to customize overlay icons and their placement (top-left, top-right, bottom-left, bottom-right, underlay).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The unique identifier of a content type (as registered with the <code>org.eclipse.core.contenttype.contentTypes</code> extension point) for which this icon provider should be used. The provider is also used for all derived content types that do not have a more specific provider registered.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.19.10
+
+
+
+
+
+
+
+
+
+
+ LSP4E provides a default implementation via <code>org.eclipse.lsp4e.ui.SymbolIconProvider</code>. It is used automatically for all content types that have no specific provider registered through this extension point. It renders symbol icons based on the LSP <code>SymbolKind</code> and up to four overlay icons derived from <code>SymbolTag</code>s (visibility, static/final/abstract/etc., marker severity) plus a deprecation underlay.
+
+
+
+
+
+
+
+
+ Copyright (c) 2024 Advantest GmbH 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
+
+
+
+
diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerPlugin.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerPlugin.java
index 39d0e808c..6c109bf6a 100644
--- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerPlugin.java
+++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerPlugin.java
@@ -11,7 +11,7 @@
*******************************************************************************/
package org.eclipse.lsp4e;
-import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.nio.charset.StandardCharsets.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -91,6 +91,16 @@ protected void initializeImageRegistry(ImageRegistry registry) {
LSPImages.initalize(registry);
}
+ /**
+ * Utility method to log errors.
+ *
+ * @param message
+ * User comprehensible message
+ */
+ public static void logError(final String message) {
+ logError(message, null);
+ }
+
/**
* Utility method to log errors.
*
diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyLabelProvider.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyLabelProvider.java
index 809a2ed88..313dc93bc 100644
--- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyLabelProvider.java
+++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyLabelProvider.java
@@ -16,6 +16,7 @@
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StyledString;
+import org.eclipse.lsp4e.operations.symbols.internal.SymbolIconProviderRegistry;
import org.eclipse.lsp4e.ui.SymbolIconProvider;
import org.eclipse.lsp4j.CallHierarchyItem;
import org.eclipse.swt.graphics.Image;
@@ -25,21 +26,12 @@
*/
public class CallHierarchyLabelProvider extends LabelProvider implements IStyledLabelProvider {
- private final SymbolIconProvider symbolIconProvider;
-
- public CallHierarchyLabelProvider() {
- this(new SymbolIconProvider());
- }
-
- public CallHierarchyLabelProvider(SymbolIconProvider symbolIconProvider) {
- this.symbolIconProvider = symbolIconProvider;
- }
-
@Override
public @Nullable Image getImage(final @Nullable Object element) {
if (element instanceof CallHierarchyViewTreeNode treeNode) {
CallHierarchyItem callContainer = treeNode.getCallContainer();
- Image res = symbolIconProvider.getImageFor(callContainer.getKind(), callContainer.getTags());
+ SymbolIconProvider symbolIconProvider = SymbolIconProviderRegistry.getSymbolIconProviderFor(callContainer);
+ Image res = symbolIconProvider.getImageFor(callContainer.getKind(), callContainer.getTags(), callContainer);
if (res != null) {
return res;
}
diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/symbols/internal/SymbolIconProviderRegistry.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/symbols/internal/SymbolIconProviderRegistry.java
new file mode 100644
index 000000000..05349e4c4
--- /dev/null
+++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/symbols/internal/SymbolIconProviderRegistry.java
@@ -0,0 +1,155 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Advantest GmbH 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:
+ * Dietrich Travkin (Solunar GmbH) - initial implementation
+ *******************************************************************************/
+package org.eclipse.lsp4e.operations.symbols.internal;
+
+import java.net.URI;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtensionPoint;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.content.IContentType;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.lsp4e.LanguageServerPlugin;
+import org.eclipse.lsp4e.outline.SymbolsModel.DocumentSymbolWithURI;
+import org.eclipse.lsp4e.ui.SymbolIconProvider;
+import org.eclipse.lsp4j.CallHierarchyItem;
+import org.eclipse.lsp4j.Location;
+import org.eclipse.lsp4j.SymbolInformation;
+import org.eclipse.lsp4j.TypeHierarchyItem;
+import org.eclipse.lsp4j.WorkspaceSymbol;
+import org.eclipse.lsp4j.WorkspaceSymbolLocation;
+
+public class SymbolIconProviderRegistry {
+
+ private static final String EXTENSION_POINT_ID = LanguageServerPlugin.PLUGIN_ID + ".symbolIconsProvider"; //$NON-NLS-1$
+
+ // default icon provider from LSP4E
+ private final SymbolIconProvider defaultIconProvider = new SymbolIconProvider();
+
+ // symbol icon providers from extensions, cached per content type ID
+ private final Map cachedIconProviders = new HashMap<>();
+
+ private SymbolIconProviderRegistry() {
+ loadExtensions();
+ }
+
+ /**
+ * Initialization-on-demand holder: the JVM guarantees that this nested class
+ * is loaded and initialized exactly once, in a thread-safe manner, the first
+ * time {@link #get()} is called.
+ */
+ private static final class Holder {
+ static final SymbolIconProviderRegistry INSTANCE = new SymbolIconProviderRegistry();
+ }
+
+ private static SymbolIconProviderRegistry get() {
+ return Holder.INSTANCE;
+ }
+
+ private void loadExtensions() {
+ IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(EXTENSION_POINT_ID);
+
+ if (extensionPoint == null) {
+ LanguageServerPlugin.logError("No extension point found for ID " + EXTENSION_POINT_ID); //$NON-NLS-1$
+ return;
+ }
+
+ for (IConfigurationElement configurationElement : extensionPoint.getConfigurationElements()) {
+ if ("iconProvider".equals(configurationElement.getName())) { //$NON-NLS-1$
+ String className = configurationElement.getAttribute("class"); //$NON-NLS-1$
+
+ if (className == null || className.isBlank()) {
+ continue;
+ }
+
+ SymbolIconProvider iconProvider;
+ try {
+ iconProvider = (SymbolIconProvider) configurationElement.createExecutableExtension("class"); //$NON-NLS-1$
+ } catch (CoreException | ClassCastException e) {
+ LanguageServerPlugin.logError("Failed instantiating class " + className, e); //$NON-NLS-1$
+ continue;
+ }
+
+ for ( IConfigurationElement contentTypeConfigElement : configurationElement.getChildren("contentType")) { //$NON-NLS-1$
+ String contentTypeId = contentTypeConfigElement.getAttribute("contentTypeId"); //$NON-NLS-1$
+
+ if (contentTypeId == null || contentTypeId.isBlank()) {
+ continue;
+ }
+
+ cachedIconProviders.put(contentTypeId, iconProvider);
+ }
+ }
+ }
+ }
+
+ public static SymbolIconProvider getSymbolIconProviderFor(Object symbol) {
+ return get().getIconProvider(symbol);
+ }
+
+ private SymbolIconProvider getIconProvider(Object symbol) {
+ URI uri = getUri(symbol);
+ if (uri == null) {
+ return defaultIconProvider;
+ }
+
+ String fileName = null;
+ try {
+ fileName = Path.of(uri.getPath()).getFileName().toString();
+ } catch (Exception e) {
+ LanguageServerPlugin.logWarning("Failed to parse file name from URI " + uri, e); //$NON-NLS-1$
+ return defaultIconProvider;
+ }
+
+ IContentType[] contentTypes = Platform.getContentTypeManager().findContentTypesFor(fileName);
+ for (IContentType contentType : contentTypes) {
+ IContentType candidate = contentType;
+ while (candidate != null) {
+ SymbolIconProvider iconProvider = cachedIconProviders.get(candidate.getId());
+ if (iconProvider != null) {
+ return iconProvider;
+ }
+ candidate = candidate.getBaseType();
+ }
+ }
+
+ return defaultIconProvider;
+ }
+
+ private @Nullable URI getUri(Object symbol) {
+ return switch (symbol) {
+ case SymbolInformation info -> toUri(info.getLocation().getUri());
+ case WorkspaceSymbol ws -> toUri(ws.getLocation().map(Location::getUri, WorkspaceSymbolLocation::getUri));
+ case DocumentSymbolWithURI s -> s.uri;
+ case TypeHierarchyItem item -> toUri(item.getUri());
+ case CallHierarchyItem item -> toUri(item.getUri());
+ default -> null; // plain DocumentSymbol — no URI
+ };
+ }
+
+ private @Nullable URI toUri(String uri) {
+ if (uri == null) {
+ return null;
+ }
+
+ try {
+ return URI.create(uri);
+ } catch (IllegalArgumentException e) {
+ LanguageServerPlugin.logWarning("Failed to parse URI " + uri, e); //$NON-NLS-1$
+ return null;
+ }
+ }
+}
diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/typeHierarchy/TypeHierarchyItemLabelProvider.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/typeHierarchy/TypeHierarchyItemLabelProvider.java
index 4d426704e..06ff3a17f 100644
--- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/typeHierarchy/TypeHierarchyItemLabelProvider.java
+++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/typeHierarchy/TypeHierarchyItemLabelProvider.java
@@ -12,22 +12,13 @@
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StyledString;
+import org.eclipse.lsp4e.operations.symbols.internal.SymbolIconProviderRegistry;
import org.eclipse.lsp4e.ui.SymbolIconProvider;
import org.eclipse.lsp4j.TypeHierarchyItem;
import org.eclipse.swt.graphics.Image;
public class TypeHierarchyItemLabelProvider extends LabelProvider implements IStyledLabelProvider {
- private final SymbolIconProvider symbolIconProvider;
-
- public TypeHierarchyItemLabelProvider() {
- this(new SymbolIconProvider());
- }
-
- public TypeHierarchyItemLabelProvider(SymbolIconProvider symbolIconProvider) {
- this.symbolIconProvider = symbolIconProvider;
- }
-
@Override
public String getText(Object element) {
if (element instanceof TypeHierarchyItem item) {
@@ -39,7 +30,8 @@ public String getText(Object element) {
@Override
public @Nullable Image getImage(@Nullable Object element) {
if (element instanceof TypeHierarchyItem item) {
- return symbolIconProvider.getImageFor(item.getKind(), item.getTags());
+ SymbolIconProvider symbolIconProvider = SymbolIconProviderRegistry.getSymbolIconProviderFor(item);
+ return symbolIconProvider.getImageFor(item.getKind(), item.getTags(), item);
}
return element == null ? null : super.getImage(element);
}
diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/SymbolsLabelProvider.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/SymbolsLabelProvider.java
index e981fc337..ca3f204de 100644
--- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/SymbolsLabelProvider.java
+++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/SymbolsLabelProvider.java
@@ -45,6 +45,7 @@
import org.eclipse.lsp4e.LanguageServerPlugin;
import org.eclipse.lsp4e.internal.StyleUtil;
import org.eclipse.lsp4e.operations.symbols.SymbolsUtil;
+import org.eclipse.lsp4e.operations.symbols.internal.SymbolIconProviderRegistry;
import org.eclipse.lsp4e.outline.SymbolsModel.DocumentSymbolWithURI;
import org.eclipse.lsp4e.ui.LSPImages;
import org.eclipse.lsp4e.ui.Messages;
@@ -72,8 +73,6 @@
public class SymbolsLabelProvider extends LabelProvider
implements ICommonLabelProvider, IStyledLabelProvider, IPreferenceChangeListener {
- private final SymbolIconProvider symbolIconProvider;
-
private final Map> severities = new HashMap<>();
private final IResourceChangeListener listener = e -> {
try {
@@ -103,13 +102,8 @@ public SymbolsLabelProvider() {
}
public SymbolsLabelProvider(final boolean showLocation, final boolean showKind) {
- this(showLocation, showKind, new SymbolIconProvider());
- }
-
- public SymbolsLabelProvider(final boolean showLocation, final boolean showKind, SymbolIconProvider symbolIconProvider) {
this.showLocation = showLocation;
this.showKind = showKind;
- this.symbolIconProvider = symbolIconProvider;
InstanceScope.INSTANCE.getNode(LanguageServerPlugin.PLUGIN_ID).addPreferenceChangeListener(this);
ResourcesPlugin.getWorkspace().addResourceChangeListener(listener);
}
@@ -170,7 +164,8 @@ public void dispose() {
}
if (actualElement != null && symbolKind != null) {
- return symbolIconProvider.getImageFor(symbolKind, symbolTags, getMaxSeverity(actualElement));
+ SymbolIconProvider symbolIconProvider = SymbolIconProviderRegistry.getSymbolIconProviderFor(actualElement);
+ return symbolIconProvider.getImageFor(symbolKind, symbolTags, getMaxSeverity(actualElement), actualElement);
}
return null;
diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/LSPImages.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/LSPImages.java
index df3106af0..f278f2b38 100644
--- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/LSPImages.java
+++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/LSPImages.java
@@ -239,10 +239,22 @@ private record ImageWithOverlaysKey(String baseImageKey,
@Nullable ImageDescriptor overlayBottomLeftDescriptor, @Nullable ImageDescriptor overlayBottomRightDescriptor,
@Nullable ImageDescriptor underlayDescriptor) {}
+ /**
+ * Returns an empty fallback image f(16x16 pixels).
+ *
+ * @return an empty 16x16 icon
+ */
+ public static Image getEmptyImage() {
+ return EMPTY_IMAGE;
+ }
+
/**
* Returns the Image identified by the given key, or null if it does not exist.
*/
public static @Nullable Image getImage(String key) {
+ if (ISharedImages.IMG_OBJ_FILE.equals(key)) {
+ return getSharedImage(key);
+ }
return getImageRegistry().get(key);
}
@@ -250,6 +262,9 @@ private record ImageWithOverlaysKey(String baseImageKey,
* Returns the ImageDescriptor identified by the given key, or null if it does not exist.
*/
public static @Nullable ImageDescriptor getImageDescriptor(String key) {
+ if (ISharedImages.IMG_OBJ_FILE.equals(key)) {
+ return getSharedImageDescriptor(key);
+ }
return getImageRegistry().getDescriptor(key);
}
@@ -266,7 +281,7 @@ public static ImageRegistry getImageRegistry() {
* @return the workbench's shared image for the , or null if not found
*/
public static @Nullable Image getSharedImage(@Nullable String imageId) {
- if(imageId == null) {
+ if (imageId == null) {
return null;
}
return PlatformUI.getWorkbench().getSharedImages().getImage(imageId);
@@ -277,7 +292,7 @@ public static ImageRegistry getImageRegistry() {
* @return the workbench's shared image descriptor for the workbench, or null if not found
*/
public static @Nullable ImageDescriptor getSharedImageDescriptor(@Nullable String imageId) {
- if(imageId == null) {
+ if (imageId == null) {
return null;
}
return PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(imageId);
@@ -290,14 +305,14 @@ public static ImageRegistry getImageRegistry() {
* @param kind a document symbol's kind
* @return an image representing the given symbol kind or null
*
- * @see #getImage(String))
+ * @see #getImage(String)
* @see #getImageDescriptor(String)
- * @see SymbolIconProvider#getImageFor(SymbolKind, java.util.List)
- * @see SymbolIconProvider#getImageFor(SymbolKind, java.util.List, int)
+ * @see SymbolIconProvider#getImageFor(SymbolKind, java.util.List, Object)
+ * @see SymbolIconProvider#getImageFor(SymbolKind, java.util.List, int, Object)
*/
public static @Nullable Image imageFromSymbolKind(@Nullable SymbolKind kind) {
if (kind == null) {
- return EMPTY_IMAGE;
+ return getEmptyImage();
}
String imgKey = imageKeyFromSymbolKind(kind);
diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/SymbolIconProvider.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/SymbolIconProvider.java
index c675253ce..ce5253e22 100644
--- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/SymbolIconProvider.java
+++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/SymbolIconProvider.java
@@ -21,8 +21,14 @@
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.lsp4e.operations.symbols.SymbolsUtil;
+import org.eclipse.lsp4e.outline.SymbolsModel.DocumentSymbolWithURI;
+import org.eclipse.lsp4j.CallHierarchyItem;
+import org.eclipse.lsp4j.DocumentSymbol;
+import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.SymbolKind;
import org.eclipse.lsp4j.SymbolTag;
+import org.eclipse.lsp4j.TypeHierarchyItem;
+import org.eclipse.lsp4j.WorkspaceSymbol;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.ISharedImages;
@@ -33,6 +39,28 @@
*/
public class SymbolIconProvider {
+ /**
+ * Returns a symbol's name if the given symbol is an instance of
+ * {@link DocumentSymbol}, or {@link DocumentSymbolWithURI},
+ * {@link SymbolInformation}, or {@link WorkspaceSymbol},
+ * or {@link CallHierarchyItem}, or {@link TypeHierarchyItem},
+ * returns null otherwise.
+ *
+ * @param symbol the symbol to get the name for
+ * @return the symbol's name or null.
+ */
+ protected @Nullable String getName(Object symbol) {
+ return switch (symbol) {
+ case SymbolInformation info -> info.getName();
+ case WorkspaceSymbol wpSymbol -> wpSymbol.getName();
+ case DocumentSymbol docSymbol -> docSymbol.getName();
+ case DocumentSymbolWithURI symbolWithURI -> symbolWithURI.symbol.getName();
+ case CallHierarchyItem callHierItem -> callHierItem.getName();
+ case TypeHierarchyItem typeHierItem -> typeHierItem.getName();
+ default -> null;
+ };
+ }
+
/**
* Returns an overlay icon {@link ImageDescriptor} for the given severity.
*
@@ -60,7 +88,7 @@ public class SymbolIconProvider {
return LSPImages.imageDescriptorOverlayFromSymbolTag(SymbolTag.Deprecated);
}
- private static final List VISIBILITY_PRECEDENCE = List.of(
+ protected static final List VISIBILITY_PRECEDENCE = List.of(
SymbolTag.Public, SymbolTag.Protected, SymbolTag.Package,
SymbolTag.Internal, SymbolTag.File, SymbolTag.Private);
@@ -76,7 +104,7 @@ protected List getVisibilityPrecedence() {
// In order to keep the number of overlay icons rather small in the UI, we do not show the following symbol tags:
// SymbolTag.Nullable, SymbolTag.NonNull, SymbolTag.Declaration, SymbolTag.Definition
- private static final List ADDITIONAL_TAGS_PRECEDENCE = List.of(
+ protected static final List ADDITIONAL_TAGS_PRECEDENCE = List.of(
SymbolTag.Static, SymbolTag.Final, SymbolTag.Abstract,
SymbolTag.Overrides, SymbolTag.Implements, SymbolTag.Virtual, SymbolTag.Sealed,
SymbolTag.Synchronized, SymbolTag.Transient, SymbolTag.Volatile,
@@ -154,6 +182,10 @@ protected String getImageKeyFromSymbolKindWithVisibility(SymbolKind kind, List visibilityTag = getHighestPrecedenceVisibilitySymbolTag(symbolTags);
if (visibilityTag.isEmpty()) {
+ if (kind == SymbolKind.Constructor) {
+ // we'll add a constructor overlay icon instead in #getOverlaysFor(...), this way, the overlays will be customizable
+ return LSPImages.IMG_METHOD;
+ }
return LSPImages.imageKeyFromSymbolKind(kind);
}
@@ -189,12 +221,14 @@ protected String getImageKeyFromSymbolKindWithVisibility(SymbolKind kind, List symbolTags) {
- return getImageFor(symbolKind, symbolTags, -1);
+ public @Nullable Image getImageFor(@Nullable SymbolKind symbolKind, @Nullable List symbolTags,
+ Object symbol) {
+ return getImageFor(symbolKind, symbolTags, -1, symbol);
}
/**
@@ -206,25 +240,47 @@ protected String getImageKeyFromSymbolKindWithVisibility(SymbolKind kind, List symbolTags, int severity) {
+ final @Nullable List symbolTags, int severity, Object symbol) {
if (symbolKind == null) {
- return LSPImages.imageFromSymbolKind(symbolKind);
+ return LSPImages.getEmptyImage();
}
final List finalSymbolTags = symbolTags != null ? symbolTags : Collections.emptyList();
String baseImageKey = getImageKeyFromSymbolKindWithVisibility(symbolKind, finalSymbolTags);
+ Overlays overlays = getOverlaysFor(symbolKind, finalSymbolTags, severity, symbol);
+
+ return LSPImages.getImageWithOverlays(baseImageKey, overlays.topLeft, overlays.topRight,
+ overlays.bottomLeft, overlays.bottomRight, overlays.underlay);
+ }
+
+ /**
+ * Determines the overlay icons to be shown for a symbol with the given arguments.
+ * Sub-classes may override this method to customize the overlay icons.
+ *
+ * @param symbolKind the symbol kind, e.g. field, method, constructor, class, property
+ * @param symbolTags the symbol tags, e.g. visibility tags, deprecation tag, static tag, final tag
+ * @param severity the severity of the most severe marker associated with the symbol, or -1 if no marker is associated with the symbol
+ * @param symbol the original symbol, i.e. an instance of {@link DocumentSymbol}, {@link DocumentSymbolWithURI}, {@link SymbolInformation}, or {@link WorkspaceSymbol}
+ * @return The overlay icons to display for the given symbol
+ */
+ protected Overlays getOverlaysFor(final SymbolKind symbolKind,
+ final List symbolTags, int severity, Object symbol) {
+ // The symbol parameter is intentionally added to the method signature in order to give subclasses
+ // a way to adapt the overlay icons depending on a symbol's properties.
+
ImageDescriptor severityImageDescriptor = getOverlayForMarkerSeverity(severity);
- ImageDescriptor deprecatedImageDescriptor = getUnderlayForDeprecation(SymbolsUtil.isDeprecated(finalSymbolTags));
+ ImageDescriptor deprecatedImageDescriptor = getUnderlayForDeprecation(SymbolsUtil.isDeprecated(symbolTags));
- List additionalTags = getAdditionalSymbolTagsSorted(finalSymbolTags);
+ List additionalTags = getAdditionalSymbolTagsSorted(symbolTags);
ImageDescriptor topLeftOverlayDescriptor = null;
ImageDescriptor topRightOverlayDescriptor = null;
@@ -233,8 +289,7 @@ protected String getImageKeyFromSymbolKindWithVisibility(SymbolKind kind, List we need to add a "C" overlay to show it's a constructor
- if (SymbolKind.Constructor == symbolKind
- && !baseImageKey.equals(LSPImages.imageKeyFromSymbolKind(symbolKind))) {
+ if (SymbolKind.Constructor == symbolKind) {
topRightOverlayDescriptor = LSPImages.getImageDescriptor(LSPImages.IMG_OVR_CONSTRUCTOR);
}
@@ -262,11 +317,29 @@ protected String getImageKeyFromSymbolKindWithVisibility(SymbolKind kind, List