Skip to content

Commit 91eba02

Browse files
travkin79rubenporras
authored andcommitted
Introduce symbol icon provider extension point for customizing overlay
icons
1 parent d3c9371 commit 91eba02

8 files changed

Lines changed: 314 additions & 32 deletions

File tree

org.eclipse.lsp4e/plugin.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<?eclipse version="3.4"?>
33
<plugin>
44
<extension-point id="languageServer" name="Language Server" schema="schema/languageServer.exsd" />
5+
<extension-point id="symbolIconsProvider" name="Symbol Icons Provider" schema="schema/symbolIconsProvider.exsd"/>
56

67
<!-- ===================================== -->
78
<!-- Setup Text Doc To LS Connection -->
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<!-- Schema file written by PDE -->
3+
<schema targetNamespace="org.eclipse.lsp4e" xmlns="http://www.w3.org/2001/XMLSchema">
4+
<annotation>
5+
<appinfo>
6+
<meta.schema plugin="org.eclipse.lsp4e" id="symbolIconsProvider" name="Symbol Icons Provider"/>
7+
</appinfo>
8+
<documentation>
9+
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.
10+
</documentation>
11+
</annotation>
12+
13+
<element name="extension">
14+
<annotation>
15+
<appinfo>
16+
<meta.element />
17+
</appinfo>
18+
</annotation>
19+
<complexType>
20+
<sequence minOccurs="1" maxOccurs="unbounded">
21+
<element ref="iconProvider"/>
22+
</sequence>
23+
<attribute name="point" type="string" use="required">
24+
<annotation>
25+
<documentation>
26+
27+
</documentation>
28+
</annotation>
29+
</attribute>
30+
<attribute name="id" type="string">
31+
<annotation>
32+
<documentation>
33+
34+
</documentation>
35+
</annotation>
36+
</attribute>
37+
<attribute name="name" type="string">
38+
<annotation>
39+
<documentation>
40+
41+
</documentation>
42+
<appinfo>
43+
<meta.attribute translatable="true"/>
44+
</appinfo>
45+
</annotation>
46+
</attribute>
47+
</complexType>
48+
</element>
49+
50+
<element name="iconProvider">
51+
<complexType>
52+
<sequence minOccurs="1" maxOccurs="unbounded">
53+
<element ref="contentType"/>
54+
</sequence>
55+
<attribute name="id" type="string" use="required">
56+
<annotation>
57+
<documentation>
58+
A string uniquely identifying this symbol icon provider extension.
59+
</documentation>
60+
</annotation>
61+
</attribute>
62+
<attribute name="class" type="string" use="required">
63+
<annotation>
64+
<documentation>
65+
66+
</documentation>
67+
<appinfo>
68+
<meta.attribute kind="java" basedOn="org.eclipse.lsp4e.ui.SymbolIconProvider:"/>
69+
</appinfo>
70+
</annotation>
71+
</attribute>
72+
</complexType>
73+
</element>
74+
75+
<element name="contentType">
76+
<complexType>
77+
<attribute name="contentTypeId" type="string" use="required">
78+
<annotation>
79+
<documentation>
80+
81+
</documentation>
82+
<appinfo>
83+
<meta.attribute kind="identifier" basedOn="org.eclipse.core.contenttype.contentTypes/content-type/@id"/>
84+
</appinfo>
85+
</annotation>
86+
</attribute>
87+
</complexType>
88+
</element>
89+
90+
<annotation>
91+
<appinfo>
92+
<meta.section type="since"/>
93+
</appinfo>
94+
<documentation>
95+
[Enter the first release in which this extension point appears.]
96+
</documentation>
97+
</annotation>
98+
99+
<annotation>
100+
<appinfo>
101+
<meta.section type="examples"/>
102+
</appinfo>
103+
<documentation>
104+
[Enter extension point usage example here.]
105+
</documentation>
106+
</annotation>
107+
108+
<annotation>
109+
<appinfo>
110+
<meta.section type="apiinfo"/>
111+
</appinfo>
112+
<documentation>
113+
[Enter API information here.]
114+
</documentation>
115+
</annotation>
116+
117+
<annotation>
118+
<appinfo>
119+
<meta.section type="implementation"/>
120+
</appinfo>
121+
<documentation>
122+
[Enter information about supplied implementation of this extension point.]
123+
</documentation>
124+
</annotation>
125+
126+
<annotation>
127+
<appinfo>
128+
<meta.section type="copyright"/>
129+
</appinfo>
130+
<documentation>
131+
Copyright (c) 2024 Advantest GmbH and others.
132+
This program and the accompanying materials are made
133+
available under the terms of the Eclipse Public License 2.0
134+
which is available at https://www.eclipse.org/legal/epl-2.0/
135+
136+
SPDX-License-Identifier: EPL-2.0
137+
</documentation>
138+
</annotation>
139+
140+
</schema>

org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyLabelProvider.java

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
1717
import org.eclipse.jface.viewers.LabelProvider;
1818
import org.eclipse.jface.viewers.StyledString;
19+
import org.eclipse.lsp4e.operations.symbols.internal.SymbolIconProviderRegistry;
1920
import org.eclipse.lsp4e.ui.SymbolIconProvider;
2021
import org.eclipse.lsp4j.CallHierarchyItem;
2122
import org.eclipse.swt.graphics.Image;
@@ -25,21 +26,12 @@
2526
*/
2627
public class CallHierarchyLabelProvider extends LabelProvider implements IStyledLabelProvider {
2728

28-
private final SymbolIconProvider symbolIconProvider;
29-
30-
public CallHierarchyLabelProvider() {
31-
this(new SymbolIconProvider());
32-
}
33-
34-
public CallHierarchyLabelProvider(SymbolIconProvider symbolIconProvider) {
35-
this.symbolIconProvider = symbolIconProvider;
36-
}
37-
3829
@Override
3930
public @Nullable Image getImage(final @Nullable Object element) {
4031
if (element instanceof CallHierarchyViewTreeNode treeNode) {
4132
CallHierarchyItem callContainer = treeNode.getCallContainer();
42-
Image res = symbolIconProvider.getImageFor(callContainer.getKind(), callContainer.getTags(), element);
33+
SymbolIconProvider symbolIconProvider = SymbolIconProviderRegistry.getSymbolIconProviderFor(callContainer);
34+
Image res = symbolIconProvider.getImageFor(callContainer.getKind(), callContainer.getTags(), callContainer);
4335
if (res != null) {
4436
return res;
4537
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Advantest GmbH and others.
3+
* This program and the accompanying materials are made
4+
* available under the terms of the Eclipse Public License 2.0
5+
* which is available at https://www.eclipse.org/legal/epl-2.0/
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Dietrich Travkin (Solunar GmbH) - initial implementation
11+
*******************************************************************************/
12+
package org.eclipse.lsp4e.operations.symbols.internal;
13+
14+
import java.net.URI;
15+
import java.nio.file.Path;
16+
import java.util.HashMap;
17+
import java.util.Map;
18+
19+
import org.eclipse.core.runtime.CoreException;
20+
import org.eclipse.core.runtime.IConfigurationElement;
21+
import org.eclipse.core.runtime.IExtensionPoint;
22+
import org.eclipse.core.runtime.Platform;
23+
import org.eclipse.core.runtime.content.IContentType;
24+
import org.eclipse.jdt.annotation.Nullable;
25+
import org.eclipse.lsp4e.LanguageServerPlugin;
26+
import org.eclipse.lsp4e.outline.SymbolsModel.DocumentSymbolWithURI;
27+
import org.eclipse.lsp4e.ui.SymbolIconProvider;
28+
import org.eclipse.lsp4j.CallHierarchyItem;
29+
import org.eclipse.lsp4j.Location;
30+
import org.eclipse.lsp4j.SymbolInformation;
31+
import org.eclipse.lsp4j.TypeHierarchyItem;
32+
import org.eclipse.lsp4j.WorkspaceSymbol;
33+
import org.eclipse.lsp4j.WorkspaceSymbolLocation;
34+
35+
public class SymbolIconProviderRegistry {
36+
37+
private static final String EXTENSION_POINT_ID = LanguageServerPlugin.PLUGIN_ID + ".symbolIconsProvider"; //$NON-NLS-1$
38+
39+
private static SymbolIconProviderRegistry INSTANCE = null;
40+
41+
// default icon provider from LSP4E
42+
private final SymbolIconProvider defaultIconProvider = new SymbolIconProvider();
43+
44+
// symbol icon providers from extensions, cached per content type ID
45+
private final Map<String, SymbolIconProvider> cachedIconProviders = new HashMap<>();
46+
47+
private SymbolIconProviderRegistry() {
48+
loadExtensions();
49+
}
50+
51+
private static SymbolIconProviderRegistry get() {
52+
if (INSTANCE == null) {
53+
INSTANCE = new SymbolIconProviderRegistry();
54+
}
55+
return INSTANCE;
56+
}
57+
58+
private void loadExtensions() {
59+
IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(EXTENSION_POINT_ID);
60+
61+
if (extensionPoint == null) {
62+
Platform.getLog(getClass()).error("No extension point found for ID " + EXTENSION_POINT_ID); //$NON-NLS-1$
63+
return;
64+
}
65+
66+
for (IConfigurationElement configurationElement : extensionPoint.getConfigurationElements()) {
67+
if ("iconProvider".equals(configurationElement.getName())) { //$NON-NLS-1$
68+
String className = configurationElement.getAttribute("class"); //$NON-NLS-1$
69+
70+
if (className == null || className.isBlank()) {
71+
continue;
72+
}
73+
74+
SymbolIconProvider iconProvider;
75+
try {
76+
iconProvider = (SymbolIconProvider) configurationElement.createExecutableExtension("class"); //$NON-NLS-1$
77+
} catch (CoreException | ClassCastException e) {
78+
Platform.getLog(getClass()).error("Failed instantiating class " + className, e); //$NON-NLS-1$
79+
continue;
80+
}
81+
82+
for ( IConfigurationElement contentTypeConfigElement : configurationElement.getChildren("contentType")) { //$NON-NLS-1$
83+
String contentTypeId = contentTypeConfigElement.getAttribute("contentTypeId"); //$NON-NLS-1$
84+
85+
if (contentTypeId == null || contentTypeId.isBlank()) {
86+
continue;
87+
}
88+
89+
cachedIconProviders.put(contentTypeId, iconProvider);
90+
}
91+
}
92+
}
93+
}
94+
95+
public static SymbolIconProvider getSymbolIconProviderFor(Object symbol) {
96+
return get().getIconProvider(symbol);
97+
}
98+
99+
private SymbolIconProvider getIconProvider(Object symbol) {
100+
URI uri = getUri(symbol);
101+
if (uri == null) {
102+
return null;
103+
}
104+
105+
String fileName = null;
106+
try {
107+
fileName = Path.of(uri.getPath()).getFileName().toString();
108+
} catch (Exception e) {
109+
Platform.getLog(getClass()).warn("Failed to parse file name from URI " + uri, e); //$NON-NLS-1$
110+
return null;
111+
}
112+
113+
IContentType[] contentTypes = Platform.getContentTypeManager().findContentTypesFor(fileName);
114+
for (IContentType contentType : contentTypes) {
115+
IContentType candidate = contentType;
116+
while (candidate != null) {
117+
SymbolIconProvider iconProvider = cachedIconProviders.get(candidate.getId());
118+
if (iconProvider != null) {
119+
return iconProvider;
120+
}
121+
candidate = candidate.getBaseType();
122+
}
123+
}
124+
125+
return defaultIconProvider;
126+
}
127+
128+
private @Nullable URI getUri(Object symbol) {
129+
if (symbol instanceof SymbolInformation info)
130+
return toUri(info.getLocation().getUri());
131+
if (symbol instanceof WorkspaceSymbol ws)
132+
return toUri(ws.getLocation().map(Location::getUri, WorkspaceSymbolLocation::getUri));
133+
if (symbol instanceof DocumentSymbolWithURI s)
134+
return s.uri;
135+
if (symbol instanceof TypeHierarchyItem item) // for subclasses handling TH
136+
return toUri(item.getUri());
137+
if (symbol instanceof CallHierarchyItem item) // for subclasses handling CH
138+
return toUri(item.getUri());
139+
return null; // plain DocumentSymbol — no URI
140+
}
141+
142+
private @Nullable URI toUri(String uri) {
143+
if (uri == null) {
144+
return null;
145+
}
146+
147+
try {
148+
return URI.create(uri);
149+
} catch (IllegalArgumentException e) {
150+
Platform.getLog(getClass()).warn("Failed to parse URI " + uri, e); //$NON-NLS-1$
151+
return null;
152+
}
153+
}
154+
}

org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/typeHierarchy/TypeHierarchyItemLabelProvider.java

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,13 @@
1212
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
1313
import org.eclipse.jface.viewers.LabelProvider;
1414
import org.eclipse.jface.viewers.StyledString;
15+
import org.eclipse.lsp4e.operations.symbols.internal.SymbolIconProviderRegistry;
1516
import org.eclipse.lsp4e.ui.SymbolIconProvider;
1617
import org.eclipse.lsp4j.TypeHierarchyItem;
1718
import org.eclipse.swt.graphics.Image;
1819

1920
public class TypeHierarchyItemLabelProvider extends LabelProvider implements IStyledLabelProvider {
2021

21-
private final SymbolIconProvider symbolIconProvider;
22-
23-
public TypeHierarchyItemLabelProvider() {
24-
this(new SymbolIconProvider());
25-
}
26-
27-
public TypeHierarchyItemLabelProvider(SymbolIconProvider symbolIconProvider) {
28-
this.symbolIconProvider = symbolIconProvider;
29-
}
30-
3122
@Override
3223
public String getText(Object element) {
3324
if (element instanceof TypeHierarchyItem item) {
@@ -39,7 +30,8 @@ public String getText(Object element) {
3930
@Override
4031
public @Nullable Image getImage(@Nullable Object element) {
4132
if (element instanceof TypeHierarchyItem item) {
42-
return symbolIconProvider.getImageFor(item.getKind(), item.getTags(), element);
33+
SymbolIconProvider symbolIconProvider = SymbolIconProviderRegistry.getSymbolIconProviderFor(item);
34+
return symbolIconProvider.getImageFor(item.getKind(), item.getTags(), item);
4335
}
4436
return element == null ? null : super.getImage(element);
4537
}

0 commit comments

Comments
 (0)