colorToImageCache = new HashMap<>();
+
+ /**
+ * Cache for symbol images with various overlays and / or an underlay.
+ *
+ * - Key: a combined key based on the element's kind (e.g. class or method)
+ * and a set of overlay image descriptors for the icon corners and the underlay,
+ * - Value: the base image with optional overlays and an optional underlay combined in one image.
+ *
+ *
+ * See {@link #getImageWithOverlays(String, ImageDescriptor, ImageDescriptor, ImageDescriptor, ImageDescriptor, ImageDescriptor)}
+ */
+ private static final Map overlayImagesCache = new HashMap<>();
+
private static final String ICONS_PATH = "$nl$/icons/full/"; //$NON-NLS-1$
private static final String OBJECT = ICONS_PATH + "obj16/"; // basic colors - size 16x16 //$NON-NLS-1$
private static final String ACTION = ICONS_PATH + "elcl16/"; // basic colors - size 16x16 //$NON-NLS-1$
+ private static final String OVERLAY = ICONS_PATH + "ovr16/"; // basic colors - size 7x8 and 14x16 //$NON-NLS-1$
+
private static final Image EMPTY_IMAGE = new Image(UI.getDisplay(), 16, 16);
public static final String IMG_MODULE = "IMG_MODULE"; //$NON-NLS-1$
@@ -52,8 +74,20 @@ private LSPImages() {
public static final String IMG_CLASS = "IMG_CLASS"; //$NON-NLS-1$
public static final String IMG_TYPE_PARAMETER = "IMG_TYPE_PARAMETER"; //$NON-NLS-1$
public static final String IMG_METHOD = "IMG_METOHD"; //$NON-NLS-1$
+ public static final String IMG_METHOD_VIS_FILE = "IMG_METH_FILE"; //$NON-NLS-1$
+ public static final String IMG_METHOD_VIS_INTERNAL = "IMG_METH_INTERNAL"; //$NON-NLS-1$
+ public static final String IMG_METHOD_VIS_PRIVATE = "IMG_METH_PRIVATE"; //$NON-NLS-1$
+ public static final String IMG_METHOD_VIS_PACKAGE = "IMG_METH_PACKAGE"; //$NON-NLS-1$
+ public static final String IMG_METHOD_VIS_PROTECTED = "IMG_METH_PROTECTED"; //$NON-NLS-1$
+ public static final String IMG_METHOD_VIS_PUBLIC = "IMG_METH_PUBLIC"; //$NON-NLS-1$
public static final String IMG_PROPERTY = "IMG_PROPERTY"; //$NON-NLS-1$
public static final String IMG_FIELD = "IMG_FIELD"; //$NON-NLS-1$
+ public static final String IMG_FIELD_VIS_FILE = "IMG_FIELD_FILE"; //$NON-NLS-1$
+ public static final String IMG_FIELD_VIS_INTERNAL = "IMG_FIELD_INTERNAL"; //$NON-NLS-1$
+ public static final String IMG_FIELD_VIS_PRIVATE = "IMG_FIELD_PRIVATE"; //$NON-NLS-1$
+ public static final String IMG_FIELD_VIS_PACKAGE = "IMG_FIELD_PACKAGE"; //$NON-NLS-1$
+ public static final String IMG_FIELD_VIS_PROTECTED = "IMG_FIELD_PROTECTED"; //$NON-NLS-1$
+ public static final String IMG_FIELD_VIS_PUBLIC = "IMG_FIELD_PUBLIC"; //$NON-NLS-1$
public static final String IMG_CONSTRUCTOR = "IMG_CONSTRUCTOR"; //$NON-NLS-1$
public static final String IMG_ENUM = "IMG_ENUM"; //$NON-NLS-1$
public static final String IMG_ENUM_MEMBER = "IMG_ENUM_MEMBER"; //$NON-NLS-1$
@@ -81,6 +115,31 @@ private LSPImages() {
public static final String IMG_SUPERTYPE = "IMG_SUPERTYPE"; //$NON-NLS-1$
public static final String IMG_SUBTYPE = "IMG_SUBTYPE"; //$NON-NLS-1$
+ public static final String IMG_OVR_CONSTRUCTOR = "IMG_OVR_CONSTRUCTOR"; //$NON-NLS-1$
+ public static final String IMG_OVR_DEPRECATED = "IMG_OVR_DEPRECATED"; //$NON-NLS-1$
+ public static final String IMG_OVR_PRIVATE = "IMG_OVR_PRIVATE"; //$NON-NLS-1$
+ public static final String IMG_OVR_PACKAGE = "IMG_OVR_PACKAGE"; //$NON-NLS-1$
+ public static final String IMG_OVR_PROTECTED = "IMG_OVR_PROTECTED"; //$NON-NLS-1$
+ public static final String IMG_OVR_PUBLIC = "IMG_OVR_PUBLIC"; //$NON-NLS-1$
+ public static final String IMG_OVR_INTERNAL = "IMG_OVR_INTERNAL"; //$NON-NLS-1$
+ public static final String IMG_OVR_FILE_VIS = "IMG_OVR_FILE_VIS"; //$NON-NLS-1$
+ public static final String IMG_OVR_ABSTRACT = "IMG_OVR_ABSTRACT"; //$NON-NLS-1$
+ public static final String IMG_OVR_VIRTUAL = "IMG_OVR_VIRTUAL"; //$NON-NLS-1$
+ public static final String IMG_OVR_FINAL = "IMG_OVR_FINAL"; //$NON-NLS-1$
+ public static final String IMG_OVR_SEALED = "IMG_OVR_SEALED"; //$NON-NLS-1$
+ public static final String IMG_OVR_STATIC = "IMG_OVR_STATIC"; //$NON-NLS-1$
+ public static final String IMG_OVR_SYNC = "IMG_OVR_SYNC"; //$NON-NLS-1$
+ public static final String IMG_OVR_TRANSIENT = "IMG_OVR_TRANSIENT"; //$NON-NLS-1$
+ public static final String IMG_OVR_VOLATILE = "IMG_OVR_VOLATILE"; //$NON-NLS-1$
+ public static final String IMG_OVR_NULLABLE = "IMG_OVR_NULLABLE"; //$NON-NLS-1$
+ public static final String IMG_OVR_NON_NULL = "IMG_OVR_NON_NULL"; //$NON-NLS-1$
+ public static final String IMG_OVR_DECLARATION = "IMG_OVR_DECLARATION"; //$NON-NLS-1$
+ public static final String IMG_OVR_DEFINITION = "IMG_OVR_DEFINITION"; //$NON-NLS-1$
+ public static final String IMG_OVR_READ_ONLY = "IMG_OVR_READ_ONLY"; //$NON-NLS-1$
+ public static final String IMG_OVR_IMPLEMENT = "IMG_OVR_IMPLEMENT"; //$NON-NLS-1$
+ public static final String IMG_OVR_OVERRIDE = "IMG_OVR_OVERRIDE"; //$NON-NLS-1$
+
+
public static void initalize(ImageRegistry registry) {
imageRegistry = registry;
@@ -90,8 +149,24 @@ public static void initalize(ImageRegistry registry) {
declareRegistryImage(IMG_CLASS, OBJECT + "class.svg"); //$NON-NLS-1$
declareRegistryImage(IMG_TYPE_PARAMETER, OBJECT + "type_parameter.svg"); //$NON-NLS-1$
declareRegistryImage(IMG_METHOD, OBJECT + "method.svg"); //$NON-NLS-1$
+
+ declareRegistryImage(IMG_METHOD_VIS_FILE, OBJECT + "method_file_vis_obj.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_METHOD_VIS_INTERNAL, OBJECT + "method_internal_obj.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_METHOD_VIS_PRIVATE, OBJECT + "method_private_obj.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_METHOD_VIS_PACKAGE, OBJECT + "method_package_obj.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_METHOD_VIS_PROTECTED, OBJECT + "method_protected_obj.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_METHOD_VIS_PUBLIC, OBJECT + "method_public_obj.svg"); //$NON-NLS-1$
+
declareRegistryImage(IMG_PROPERTY, OBJECT + "property.svg"); //$NON-NLS-1$
declareRegistryImage(IMG_FIELD, OBJECT + "field.svg"); //$NON-NLS-1$
+
+ declareRegistryImage(IMG_FIELD_VIS_FILE, OBJECT + "field_file_vis_obj.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_FIELD_VIS_INTERNAL, OBJECT + "field_internal_obj.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_FIELD_VIS_PRIVATE, OBJECT + "field_private_obj.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_FIELD_VIS_PACKAGE, OBJECT + "field_package_obj.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_FIELD_VIS_PROTECTED, OBJECT + "field_protected_obj.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_FIELD_VIS_PUBLIC, OBJECT + "field_public_obj.svg"); //$NON-NLS-1$
+
declareRegistryImage(IMG_CONSTRUCTOR, OBJECT + "constructor.svg"); //$NON-NLS-1$
declareRegistryImage(IMG_ENUM, OBJECT + "enum.svg"); //$NON-NLS-1$
declareRegistryImage(IMG_ENUM_MEMBER, OBJECT + "enum_member.svg"); //$NON-NLS-1$
@@ -118,6 +193,32 @@ public static void initalize(ImageRegistry registry) {
declareRegistryImage(IMG_SUPERTYPE, ACTION + "super_co.svg"); //$NON-NLS-1$
declareRegistryImage(IMG_SUBTYPE, ACTION + "sub_co.svg"); //$NON-NLS-1$
declareRegistryImage(IMG_TERMINATE_CO, OBJECT + "terminate_co.svg"); //$NON-NLS-1$
+
+ declareRegistryImage(IMG_OVR_CONSTRUCTOR, OVERLAY + "constr_ovr.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_DEPRECATED, OVERLAY + "deprecated.svg"); //$NON-NLS-1$
+
+ declareRegistryImage(IMG_OVR_PRIVATE, OVERLAY + "vis_private_co.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_PACKAGE, OVERLAY + "vis_package_co.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_PROTECTED, OVERLAY + "vis_protected_co.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_PUBLIC, OVERLAY + "vis_public_co.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_INTERNAL, OVERLAY + "vis_internal_co.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_FILE_VIS, OVERLAY + "vis_file_co.svg"); //$NON-NLS-1$
+
+ declareRegistryImage(IMG_OVR_ABSTRACT, OVERLAY + "abstract_co.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_VIRTUAL, OVERLAY + "virtual_co.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_FINAL, OVERLAY + "final_co.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_SEALED, OVERLAY + "sealed_co.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_STATIC, OVERLAY + "static_co.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_SYNC, OVERLAY + "synch_co.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_TRANSIENT, OVERLAY + "transient_co.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_VOLATILE, OVERLAY + "volatile_co.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_NULLABLE, OVERLAY + "nullable_co.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_NON_NULL, OVERLAY + "non_null_co.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_DECLARATION, OVERLAY + "declaration_co.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_DEFINITION, OVERLAY + "definition_co.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_READ_ONLY, OVERLAY + "read_only_co.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_IMPLEMENT, OVERLAY + "implement_co.svg"); //$NON-NLS-1$
+ declareRegistryImage(IMG_OVR_OVERRIDE, OVERLAY + "override_co.svg"); //$NON-NLS-1$
}
private static void declareRegistryImage(String key, String path) {
@@ -133,6 +234,11 @@ private static void declareRegistryImage(String key, String path) {
getImageRegistry().put(key, desc);
}
+ private record ImageWithOverlaysKey(String baseImageKey,
+ @Nullable ImageDescriptor overlayTopLeftDescriptor, @Nullable ImageDescriptor overlayTopRightDescriptor,
+ @Nullable ImageDescriptor overlayBottomLeftDescriptor, @Nullable ImageDescriptor overlayBottomRightDescriptor,
+ @Nullable ImageDescriptor underlayDescriptor) {}
+
/**
* Returns the Image identified by the given key, or null if it does not exist.
*/
@@ -177,35 +283,81 @@ public static ImageRegistry getImageRegistry() {
return PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(imageId);
}
+ /**
+ * Returns an image representing the given symbol kind. Does not consider the document symbol's visibility
+ * (given by {@link SymbolTag}s).
+ *
+ * @param kind a document symbol's kind
+ * @return an image representing the given symbol kind or null
+ *
+ * @see #getImage(String))
+ * @see #getImageDescriptor(String)
+ * @see SymbolIconProvider#getImageFor(SymbolKind, java.util.List)
+ * @see SymbolIconProvider#getImageFor(SymbolKind, java.util.List, int)
+ */
public static @Nullable Image imageFromSymbolKind(@Nullable SymbolKind kind) {
+ if (kind == null) {
+ return EMPTY_IMAGE;
+ }
+
+ String imgKey = imageKeyFromSymbolKind(kind);
+ if (ISharedImages.IMG_OBJ_FILE.equals(imgKey)) {
+ return getSharedImage(imgKey);
+ }
+
+ return getImage(imgKey);
+ }
+
+ public static @Nullable ImageDescriptor imageDescriptorFromSymbolKind(SymbolKind kind) {
+ String imgKey = imageKeyFromSymbolKind(kind);
+ if (ISharedImages.IMG_OBJ_FILE.equals(imgKey)) {
+ return getSharedImageDescriptor(imgKey);
+ }
+
+ return getImageDescriptor(imgKey);
+ }
+
+ /**
+ * Returns an image identifier (key) that can be used with the image registry or in {@link #getImage(String)} and
+ * {@link #getImageDescriptor(String)} to retrieve the image representing the given document symbol kind.
+ * Does not consider any visibility details given in a document symbol's {@link SymbolTag}s.
+ *
+ * @param kind a document symbol's kind
+ * @return an image identifier (key) representing the given symbol kind's corresponding image
+ *
+ * @see #getImage(String)
+ * @see #getImageDescriptor(String)
+ * @see #getImageWithOverlays(String, ImageDescriptor, ImageDescriptor, ImageDescriptor, ImageDescriptor, ImageDescriptor)
+ * @see SymbolIconProvider#getImageKeyFromSymbolKindWithVisibility(SymbolKind, java.util.List)
+ */
+ public static String imageKeyFromSymbolKind(SymbolKind kind) {
return switch (kind) {
- case Array -> getImage(IMG_ARRAY);
- case Boolean -> getImage(IMG_BOOLEAN);
- case Class -> getImage(IMG_CLASS);
- case Constant -> getImage(IMG_CONSTANT);
- case Constructor -> getImage(IMG_CONSTRUCTOR);
- case Enum -> getImage(IMG_ENUM);
- case EnumMember -> getImage(IMG_ENUM_MEMBER);
- case Struct -> getImage(IMG_STRUCT);
- case Field -> getImage(IMG_FIELD);
- case File -> getSharedImage(ISharedImages.IMG_OBJ_FILE);
- case Function -> getImage(IMG_FUNCTION);
- case Interface -> getImage(IMG_INTERACE);
- case Method -> getImage(IMG_METHOD);
- case Module -> getImage(IMG_MODULE);
- case Namespace -> getImage(IMG_NAMESPACE);
- case Number -> getImage(IMG_NUMBER);
- case Object -> getImage(IMG_OBJECT);
- case Package -> getImage(IMG_PACKAGE);
- case Property -> getImage(IMG_PROPERTY);
- case String -> getImage(IMG_TEXT);
- case TypeParameter -> getImage(IMG_TYPE_PARAMETER);
- case Variable -> getImage(IMG_VARIABLE);
- case Null -> getImage(IMG_NULL);
- case Event -> getImage(IMG_EVENT);
- case Key -> getImage(IMG_KEY);
- case Operator -> getImage(IMG_OPERATOR);
- case null -> EMPTY_IMAGE;
+ case Array -> IMG_ARRAY;
+ case Boolean -> IMG_BOOLEAN;
+ case Class -> IMG_CLASS;
+ case Constant -> IMG_CONSTANT;
+ case Constructor -> IMG_CONSTRUCTOR;
+ case Enum -> IMG_ENUM;
+ case EnumMember -> IMG_ENUM_MEMBER;
+ case Struct -> IMG_STRUCT;
+ case Field -> IMG_FIELD;
+ case File -> ISharedImages.IMG_OBJ_FILE;
+ case Function -> IMG_FUNCTION;
+ case Interface -> IMG_INTERACE;
+ case Method -> IMG_METHOD;
+ case Module -> IMG_MODULE;
+ case Namespace -> IMG_NAMESPACE;
+ case Number -> IMG_NUMBER;
+ case Object -> IMG_OBJECT;
+ case Package -> IMG_PACKAGE;
+ case Property -> IMG_PROPERTY;
+ case String -> IMG_TEXT;
+ case TypeParameter -> IMG_TYPE_PARAMETER;
+ case Variable -> IMG_VARIABLE;
+ case Null -> IMG_NULL;
+ case Event -> IMG_EVENT;
+ case Key -> IMG_KEY;
+ case Operator -> IMG_OPERATOR;
};
}
@@ -240,6 +392,52 @@ public static ImageRegistry getImageRegistry() {
};
}
+ /**
+ * Returns an overlay icon ({@link Image}) representing the given symbol tag,
+ * e.g. a document symbol's visibility "private" or the symbol being "static" or "final".
+ *
+ * @param symbolTag a document symbol's tag to be represented as an overlay icon
+ * @return the overlay icon corresponding to the given tag or null if the image cannot be found
+ *
+ * @see #imageDescriptorOverlayFromSymbolTag(SymbolTag)
+ */
+ public static @Nullable Image imageOverlayFromSymbolTag(SymbolTag symbolTag) {
+ String imgKey = imageOverlayKeyFromSymbolTag(symbolTag);
+ return getImage(imgKey);
+ }
+
+ public static @Nullable ImageDescriptor imageDescriptorOverlayFromSymbolTag(SymbolTag symbolTag) {
+ String imgKey = imageOverlayKeyFromSymbolTag(symbolTag);
+ return getImageDescriptor(imgKey);
+ }
+
+ private static String imageOverlayKeyFromSymbolTag(SymbolTag symbolTag) {
+ return switch (symbolTag) {
+ case Deprecated -> IMG_OVR_DEPRECATED;
+ case Private -> IMG_OVR_PRIVATE;
+ case Package -> IMG_OVR_PACKAGE;
+ case Protected -> IMG_OVR_PROTECTED;
+ case Public -> IMG_OVR_PUBLIC;
+ case Internal -> IMG_OVR_INTERNAL;
+ case File -> IMG_OVR_FILE_VIS;
+ case Static -> IMG_OVR_STATIC;
+ case Abstract -> IMG_OVR_ABSTRACT;
+ case Final -> IMG_OVR_FINAL;
+ case Sealed -> IMG_OVR_SEALED;
+ case Transient -> IMG_OVR_TRANSIENT;
+ case Volatile -> IMG_OVR_VOLATILE;
+ case Synchronized -> IMG_OVR_SYNC;
+ case Virtual -> IMG_OVR_VIRTUAL;
+ case Nullable -> IMG_OVR_NULLABLE;
+ case NonNull -> IMG_OVR_NON_NULL;
+ case Declaration -> IMG_OVR_DECLARATION;
+ case Definition -> IMG_OVR_DEFINITION;
+ case ReadOnly -> IMG_OVR_READ_ONLY;
+ case Overrides -> IMG_OVR_OVERRIDE;
+ case Implements -> IMG_OVR_IMPLEMENT;
+ };
+ }
+
private static @Nullable Image getImageForColor(CompletionItem completionItem) {
String hexValue = null;
@@ -274,4 +472,68 @@ public static ImageRegistry getImageRegistry() {
return image;
});
}
+
+ /**
+ * Returns a new or cached image built from the given arguments.
+ * The image is a combination of a base image with optional overlay and underlay icons.
+ *
+ * @param baseImageKey the image identifier given by e.g. {@link #imageKeyFromSymbolKind(SymbolKind)}
+ * @param topLeftOverlayDescriptor the overlay icon descriptor for the upper left corner of the base icon
+ * @param topRightOverlayDescriptor the overlay icon descriptor for the upper right corner of the base icon
+ * @param bottomLeftOverlayDescriptor the overlay icon descriptor for the lower left corner of the base icon
+ * @param bottomRightOverlayDescriptor the overlay icon descriptor for the lower right corner of the base icon
+ * @param underlayImageDescriptor the underlay icon descriptor for being drawn behind the the base icon
+ * @return returns a new or cached image built from the given arguments.
+ *
+ * @see #imageKeyFromSymbolKind(SymbolKind)
+ * @see SymbolIconProvider#getImageKeyFromSymbolKindWithVisibility(SymbolKind, java.util.List)
+ */
+ public static @Nullable Image getImageWithOverlays(String baseImageKey,
+ @Nullable ImageDescriptor topLeftOverlayDescriptor, @Nullable ImageDescriptor topRightOverlayDescriptor,
+ @Nullable ImageDescriptor bottomLeftOverlayDescriptor, @Nullable ImageDescriptor bottomRightOverlayDescriptor,
+ @Nullable ImageDescriptor underlayImageDescriptor) {
+
+ // array index: 0 = top left, 1 = top right, 2 = bottom left, 3 = bottom right, 4 = underlay
+ // see IDecoration.TOP_LEFT ... IDecoration.BOTTOM_RIGHT, IDecoration.UNDERLAY
+ @Nullable ImageDescriptor[] overlays = {
+ topLeftOverlayDescriptor, topRightOverlayDescriptor,
+ bottomLeftOverlayDescriptor, bottomRightOverlayDescriptor,
+ underlayImageDescriptor};
+
+ long numOverlays = Arrays.stream(overlays)
+ .filter(Objects::nonNull)
+ .count();
+ if (numOverlays == 0L) {
+ return getImage(baseImageKey);
+ }
+
+ ImageWithOverlaysKey key = new ImageWithOverlaysKey(baseImageKey,
+ topLeftOverlayDescriptor, topRightOverlayDescriptor,
+ bottomLeftOverlayDescriptor, bottomRightOverlayDescriptor, underlayImageDescriptor);
+
+ if (overlayImagesCache.containsKey(key)) {
+ return overlayImagesCache.get(key);
+ }
+
+ final Image baseImage = getImage(baseImageKey);
+ if (baseImage == null) {
+ // Do not create cache entries for non existing base images
+ return null;
+ }
+
+ Image imageWithOverlays = new DecorationOverlayIcon(baseImage, overlays).createImage();
+ overlayImagesCache.put(key, imageWithOverlays);
+
+ return imageWithOverlays;
+ }
+
+ public static final void dispose() {
+ Stream.concat(
+ colorToImageCache.values().stream(),
+ overlayImagesCache.values().stream())
+ .filter(Objects::nonNull)
+ .forEach(Image::dispose);
+ overlayImagesCache.clear();
+ colorToImageCache.clear();
+ }
}
diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/SymbolIconProvider.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/SymbolIconProvider.java
new file mode 100644
index 000000000..c675253ce
--- /dev/null
+++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/SymbolIconProvider.java
@@ -0,0 +1,272 @@
+/*******************************************************************************
+ * 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.ui;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.lsp4e.operations.symbols.SymbolsUtil;
+import org.eclipse.lsp4j.SymbolKind;
+import org.eclipse.lsp4j.SymbolTag;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.ui.ISharedImages;
+
+
+/**
+ * Customizable class for creating document symbol icons with overlays depending on the symbol's
+ * {@link SymbolKind} and {@link SymbolTag}s. This class is meant to be used with {@link LabelProvider}s.
+ */
+public class SymbolIconProvider {
+
+ /**
+ * Returns an overlay icon {@link ImageDescriptor} for the given severity.
+ *
+ * @param severity one of IMarker.SEVERITY_ERROR or IMarker.SEVERITY_WARNING
+ * @return image descriptor for a warning or an error, or null in all other cases
+ */
+ protected @Nullable ImageDescriptor getOverlayForMarkerSeverity(int severity) {
+ return switch(severity) {
+ case IMarker.SEVERITY_ERROR -> LSPImages.getSharedImageDescriptor(ISharedImages.IMG_DEC_FIELD_ERROR);
+ case IMarker.SEVERITY_WARNING -> LSPImages.getSharedImageDescriptor(ISharedImages.IMG_DEC_FIELD_WARNING);
+ default -> null;
+ };
+ }
+
+ /**
+ * Returns an underlay icon {@link ImageDescriptor} if the given argument is true, null otherwise.
+ *
+ * @param deprecated if a symbol is deprecated
+ * @return a deprecation underlay icon or null
+ */
+ protected @Nullable ImageDescriptor getUnderlayForDeprecation(boolean deprecated) {
+ if (!deprecated) {
+ return null;
+ }
+ return LSPImages.imageDescriptorOverlayFromSymbolTag(SymbolTag.Deprecated);
+ }
+
+ private static final List VISIBILITY_PRECEDENCE = List.of(
+ SymbolTag.Public, SymbolTag.Protected, SymbolTag.Package,
+ SymbolTag.Internal, SymbolTag.File, SymbolTag.Private);
+
+ /**
+ * Returns a list of visibility {@link SymbolTag}s with decreasing precedence.
+ * May be overridden by subclasses to change the visibility overlay icons shown.
+ *
+ * @return a list of visibility {@link SymbolTag}s
+ */
+ protected List getVisibilityPrecedence() {
+ return VISIBILITY_PRECEDENCE;
+ }
+
+ // 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(
+ SymbolTag.Static, SymbolTag.Final, SymbolTag.Abstract,
+ SymbolTag.Overrides, SymbolTag.Implements, SymbolTag.Virtual, SymbolTag.Sealed,
+ SymbolTag.Synchronized, SymbolTag.Transient, SymbolTag.Volatile,
+ SymbolTag.ReadOnly);
+
+ /**
+ * Returns a list of {@link SymbolTag}s excluding visibility and deprecation tags with decreasing precedence.
+ * May be overridden by subclasses to change the overlay icons shown in addition to visibility and deprecation.
+ * The default implementation also excludes the following tags:
+ * SymbolTag.Nullable, SymbolTag.NonNull,
+ * SymbolTag.Declaration, SymbolTag.Definition
+ *
+ * @return a list of {@link SymbolTag}s without visibility and deprecation tags
+ */
+ protected List getAdditionalTagsPrecedence() {
+ return ADDITIONAL_TAGS_PRECEDENCE;
+ }
+
+ /**
+ * Returns the visibility {@link SymbolTag} to be shown in the UI. All other {@link SymbolTag}s will be ignored.
+ *
+ * @param symbolTags a document symbol's {@link SymbolTag}s
+ * @return the highest precedence visibility {@link SymbolTag} if available
+ *
+ * @see #getVisibilityPrecedence()
+ */
+ protected final Optional getHighestPrecedenceVisibilitySymbolTag(List symbolTags) {
+ final var precedenceList = getVisibilityPrecedence();
+ return symbolTags.stream()
+ .filter(precedenceList::contains)
+ .min(Comparator.comparing(precedenceList::indexOf));
+ }
+
+ /**
+ * Returns a list of a document symbol's {@link SymbolTag}s excluding visibility and deprecation tags
+ * sorted according to their precedence. Symbol tags with higher precedence are more likely to be shown in the UI.
+ *
+ * @param symbolTags a document symbol's {@link SymbolTag}s
+ * @return a sorted list of {@link SymbolTag}s excluding visibility and deprecation tags
+ *
+ * @see #getAdditionalTagsPrecedence()
+ */
+ protected final List getAdditionalSymbolTagsSorted(List symbolTags) {
+ final var precedenceList = getAdditionalTagsPrecedence();
+ return symbolTags.stream()
+ .filter(precedenceList::contains)
+ .sorted(Comparator.comparing(precedenceList::indexOf))
+ .toList();
+ }
+
+ private @Nullable ImageDescriptor getOverlayForVisibility(List symbolTags) {
+ Optional visibilityTag = getHighestPrecedenceVisibilitySymbolTag(symbolTags);
+
+ if (visibilityTag.isEmpty()) {
+ return null;
+ }
+
+ return LSPImages.imageDescriptorOverlayFromSymbolTag(visibilityTag.get());
+ }
+
+ /**
+ * Determines an image key (identifier) for the given arguments that can be used with the image registry.
+ * Instead of just determining the icon for a document symbol, the given optional visibility {@link SymbolTag}s
+ * are considered, i.e. fields and methods (incl. constructors) get different symbol icons depending on their visibility.
+ *
+ * @param kind a document symbol's kind, e.g. field, method, constructor, class, property
+ * @param symbolTags a document symbol's {@link SymbolTag}s, only visibility tags are considered
+ * @return an image's key (identifier) for the use with the image registry
+ *
+ * @see LSPImages#getImage(String)
+ * @see LSPImages#getImageDescriptor(String)
+ */
+ protected String getImageKeyFromSymbolKindWithVisibility(SymbolKind kind, List symbolTags) {
+
+ Optional visibilityTag = getHighestPrecedenceVisibilitySymbolTag(symbolTags);
+
+ if (visibilityTag.isEmpty()) {
+ return LSPImages.imageKeyFromSymbolKind(kind);
+ }
+
+ SymbolTag visibility = visibilityTag.get();
+
+ if (kind == SymbolKind.Field) {
+ return switch (visibility) {
+ case Private -> LSPImages.IMG_FIELD_VIS_PRIVATE;
+ case Package -> LSPImages.IMG_FIELD_VIS_PACKAGE;
+ case Protected -> LSPImages.IMG_FIELD_VIS_PROTECTED;
+ case Public -> LSPImages.IMG_FIELD_VIS_PUBLIC;
+ case Internal -> LSPImages.IMG_FIELD_VIS_INTERNAL;
+ case File -> LSPImages.IMG_FIELD_VIS_FILE;
+ default -> LSPImages.IMG_FIELD;
+ };
+ } else if (kind == SymbolKind.Method || kind == SymbolKind.Constructor) {
+ return switch (visibility) {
+ case Private -> LSPImages.IMG_METHOD_VIS_PRIVATE;
+ case Package -> LSPImages.IMG_METHOD_VIS_PACKAGE;
+ case Protected -> LSPImages.IMG_METHOD_VIS_PROTECTED;
+ case Public -> LSPImages.IMG_METHOD_VIS_PUBLIC;
+ case Internal -> LSPImages.IMG_METHOD_VIS_INTERNAL;
+ case File -> LSPImages.IMG_METHOD_VIS_FILE;
+ default -> LSPImages.IMG_METHOD;
+ };
+ }
+
+ return LSPImages.imageKeyFromSymbolKind(kind);
+ }
+
+ /**
+ * Returns an image for the given arguments.
+ *
+ * @param symbolKind the kind of symbol
+ * @param symbolTags the symbol tags
+ * @return a new or cached image for the given symbol kind with overlay icons computed for the given arguments.
+ *
+ * @see #getImageFor(SymbolKind, List, int)
+ */
+ public @Nullable Image getImageFor(@Nullable SymbolKind symbolKind, @Nullable List symbolTags) {
+ return getImageFor(symbolKind, symbolTags, -1);
+ }
+
+ /**
+ * Returns an image for the given arguments.
+ * Uses caching for all combinations of a symbol kind and a set of overlays.
+ * Deprecation is shown if the deprecated parameter is true
+ * or {@link SymbolTag#Deprecated} is in the set of symbol tags.
+ *
+ * @param symbolKind the kind of symbol
+ * @param symbolTags the symbol tags
+ * @param severity one of -1, {@link IMarker#SEVERITY_WARNING}, and {@link IMarker#SEVERITY_ERROR}. -1 indicates no overlay icon.
+ * @return a new or cached image for the given symbol kind with overlay icons computed for the given arguments.
+ *
+ * @see #getImageFor(SymbolKind, List)
+ */
+ public @Nullable Image getImageFor(final @Nullable SymbolKind symbolKind,
+ final @Nullable List symbolTags, int severity) {
+
+ if (symbolKind == null) {
+ return LSPImages.imageFromSymbolKind(symbolKind);
+ }
+
+ final List finalSymbolTags = symbolTags != null ? symbolTags : Collections.emptyList();
+
+ String baseImageKey = getImageKeyFromSymbolKindWithVisibility(symbolKind, finalSymbolTags);
+
+ ImageDescriptor severityImageDescriptor = getOverlayForMarkerSeverity(severity);
+ ImageDescriptor deprecatedImageDescriptor = getUnderlayForDeprecation(SymbolsUtil.isDeprecated(finalSymbolTags));
+
+ List additionalTags = getAdditionalSymbolTagsSorted(finalSymbolTags);
+
+ ImageDescriptor topLeftOverlayDescriptor = null;
+ ImageDescriptor topRightOverlayDescriptor = null;
+ ImageDescriptor bottomLeftOverlayDescriptor = severityImageDescriptor;
+ ImageDescriptor bottomRightOverlayDescriptor = null;
+ ImageDescriptor underlayDescriptor = deprecatedImageDescriptor;
+
+ // special case for a constructor with visibility tag => we need to add a "C" overlay to show it's a constructor
+ if (SymbolKind.Constructor == symbolKind
+ && !baseImageKey.equals(LSPImages.imageKeyFromSymbolKind(symbolKind))) {
+ topRightOverlayDescriptor = LSPImages.getImageDescriptor(LSPImages.IMG_OVR_CONSTRUCTOR);
+ }
+
+ if (!additionalTags.isEmpty()) {
+ topLeftOverlayDescriptor = LSPImages.imageDescriptorOverlayFromSymbolTag(additionalTags.get(0));
+
+ if (additionalTags.size() > 1) {
+ if (SymbolKind.Constructor == symbolKind) {
+ // a constructor has an overlay in the top right corner,
+ // in this case we place the second symbol tag's overlay icon at the lower right corner
+ bottomRightOverlayDescriptor = LSPImages.imageDescriptorOverlayFromSymbolTag(additionalTags.get(1));
+ } else {
+ topRightOverlayDescriptor = LSPImages.imageDescriptorOverlayFromSymbolTag(additionalTags.get(1));
+ }
+ }
+ }
+
+ if (SymbolKind.Field == symbolKind || SymbolKind.Method == symbolKind) {
+ // In these cases the visibility is already expressed by the symbol icon, so we can display one more symbol tag
+ if (additionalTags.size() > 2) {
+ bottomRightOverlayDescriptor = LSPImages.imageDescriptorOverlayFromSymbolTag(additionalTags.get(2));
+ }
+ } else if (SymbolKind.Constructor != symbolKind) {
+ // We place the visibility overlay icon on the lower right corner, similar to JDT.
+ // The top left and top right corners remain for additional symbol tags (besides visibility, severity, deprecation)
+ // In case of constructors we already have a "C" for "constructor" in the upper right corner
+ // and have use the lower right corner for another additional symbol tag.
+ bottomRightOverlayDescriptor = getOverlayForVisibility(finalSymbolTags);
+ }
+
+ return LSPImages.getImageWithOverlays(baseImageKey, topLeftOverlayDescriptor, topRightOverlayDescriptor,
+ bottomLeftOverlayDescriptor, bottomRightOverlayDescriptor, underlayDescriptor);
+ }
+
+}