From b6f053567541c218173fd6e692be95edcd896bf7 Mon Sep 17 00:00:00 2001 From: Dietrich Travkin Date: Tue, 10 Feb 2026 15:12:50 +0100 Subject: [PATCH 01/11] feat: Use symbol tags in views to display visibility and other details Affected views - Outline view - Call hierarchy view - Type hierachy view (and quick type hierarchy dialog) This change is an implementation of the proposal from https://github.com/microsoft/language-server-protocol/pull/2003 See also https://github.com/eclipse-lsp4e/lsp4e/discussions/977 and https://github.com/llvm/llvm-project/pull/167536 --- .../lsp4e/test/symbols/SymbolsUtilTest.java | 88 + .../lsp4e/test/utils/LSPImagesTest.java | 58 + .../icons/full/obj16/constructor.png | Bin 627 -> 5801 bytes .../icons/full/obj16/constructor.svg | 101 +- .../icons/full/obj16/constructor@2x.png | Bin 1309 -> 7036 bytes .../icons/full/ovr16/abstract_co.svg | 99 + .../icons/full/ovr16/constr_ovr.svg | 185 + .../icons/full/ovr16/declaration_co.svg | 1052 ++++ .../icons/full/ovr16/definition_co.svg | 1052 ++++ .../icons/full/ovr16/deprecated.svg | 123 + .../icons/full/ovr16/file_visable_co.svg | 168 + .../icons/full/ovr16/final_co.svg | 159 + .../icons/full/ovr16/implement_co.svg | 828 +++ .../icons/full/ovr16/internal_co.svg | 130 + .../icons/full/ovr16/non_null_co.svg | 159 + .../icons/full/ovr16/nullable_co.svg | 145 + .../icons/full/ovr16/override_co.svg | 828 +++ .../icons/full/ovr16/package_co.svg | 129 + .../icons/full/ovr16/private_co.svg | 4448 +++++++++++++++++ .../icons/full/ovr16/protected_co.svg | 168 + .../icons/full/ovr16/public_co.svg | 131 + .../icons/full/ovr16/read_only_co.svg | 263 + .../icons/full/ovr16/sealed_co.svg | 78 + .../icons/full/ovr16/static_co.svg | 78 + .../icons/full/ovr16/synch_co.svg | 149 + .../icons/full/ovr16/transient_co.svg | 111 + .../icons/full/ovr16/virtual_co.svg | 86 + .../icons/full/ovr16/volatile_co.svg | 76 + .../eclipse/lsp4e/LanguageServerPlugin.java | 9 +- .../CallHierarchyLabelProvider.java | 2 +- .../lsp4e/internal/SupportedFeatures.java | 7 +- .../lsp4e/operations/symbols/SymbolsUtil.java | 188 + .../TypeHierarchyItemLabelProvider.java | 2 +- .../lsp4e/outline/SymbolsLabelProvider.java | 132 +- .../src/org/eclipse/lsp4e/ui/LSPImages.java | 297 ++ 35 files changed, 11423 insertions(+), 106 deletions(-) create mode 100644 org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/symbols/SymbolsUtilTest.java create mode 100644 org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/utils/LSPImagesTest.java create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/abstract_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/constr_ovr.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/declaration_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/definition_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/deprecated.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/file_visable_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/final_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/implement_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/internal_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/non_null_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/nullable_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/override_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/package_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/private_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/protected_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/public_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/read_only_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/sealed_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/static_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/synch_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/transient_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/virtual_co.svg create mode 100644 org.eclipse.lsp4e/icons/full/ovr16/volatile_co.svg create mode 100644 org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/symbols/SymbolsUtil.java diff --git a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/symbols/SymbolsUtilTest.java b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/symbols/SymbolsUtilTest.java new file mode 100644 index 000000000..e954c4f38 --- /dev/null +++ b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/symbols/SymbolsUtilTest.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * 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.test.symbols; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.lsp4e.operations.symbols.SymbolsUtil; +import org.eclipse.lsp4j.DocumentSymbol; +import org.eclipse.lsp4j.SymbolInformation; +import org.eclipse.lsp4j.SymbolTag; +import org.eclipse.lsp4j.WorkspaceSymbol; +import org.junit.jupiter.api.Test; + +public class SymbolsUtilTest { + + private static final List symbolTagsWithDeprecated = Arrays.asList( + SymbolTag.Package, SymbolTag.Deprecated, SymbolTag.ReadOnly); + private static final List symbolTagsWithoutDeprecated = Arrays.asList( + SymbolTag.Public, SymbolTag.Declaration, SymbolTag.Static); + + @Test + public void testDeprecatedCheckForSymbolInformation() { + var symbolInformation = new SymbolInformation(); + + assertFalse(SymbolsUtil.isDeprecated(symbolInformation)); + + symbolInformation.setDeprecated(true); + + assertTrue(SymbolsUtil.isDeprecated(symbolInformation)); + + symbolInformation = new SymbolInformation(); + symbolInformation.setTags(symbolTagsWithDeprecated); + + assertTrue(SymbolsUtil.isDeprecated(symbolInformation)); + + symbolInformation.setTags(symbolTagsWithoutDeprecated); + + assertFalse(SymbolsUtil.isDeprecated(symbolInformation)); + } + + @Test + public void testDeprecatedCheckForWorkspaceSymbol() { + var workspaceSymbol = new WorkspaceSymbol(); + + assertFalse(SymbolsUtil.isDeprecated(workspaceSymbol)); + + workspaceSymbol.setTags(symbolTagsWithDeprecated); + + assertTrue(SymbolsUtil.isDeprecated(workspaceSymbol)); + + workspaceSymbol.setTags(symbolTagsWithoutDeprecated); + + assertFalse(SymbolsUtil.isDeprecated(workspaceSymbol)); + } + + @Test + public void testDeprecatedCheckForDocumentSymbol() { + var documentSymbol = new DocumentSymbol(); + + assertFalse(SymbolsUtil.isDeprecated(documentSymbol)); + + documentSymbol.setDeprecated(true); + + assertTrue(SymbolsUtil.isDeprecated(documentSymbol)); + + documentSymbol = new DocumentSymbol(); + documentSymbol.setTags(symbolTagsWithDeprecated); + + assertTrue(SymbolsUtil.isDeprecated(documentSymbol)); + + documentSymbol.setTags(symbolTagsWithoutDeprecated); + + assertFalse(SymbolsUtil.isDeprecated(documentSymbol)); + } + +} 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 new file mode 100644 index 000000000..23d806786 --- /dev/null +++ b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/utils/LSPImagesTest.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * 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.test.utils; + +import static org.junit.jupiter.api.Assertions.*; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.lsp4e.ui.LSPImages; +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.CompletionItemKind; +import org.eclipse.lsp4j.SymbolKind; +import org.eclipse.lsp4j.SymbolTag; +import org.eclipse.swt.graphics.Image; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +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); + } + + @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/icons/full/obj16/constructor.png b/org.eclipse.lsp4e/icons/full/obj16/constructor.png index ab97bb71ed8fcad7d301b15f75a42dec98e5d9a3..71e5585552a574cbe7ceeda047f212ed83ab5eba 100644 GIT binary patch literal 5801 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 DV7?bB delta 603 zcmV-h0;K(^E%OABBYyw{b3#c}2nYxWdtL%LXu9=p?`ZV6`_A1qNF_4$+ldp z@X(y4vhCHicTN-CyZ86&5L;`TbnZEQe-A&N@AEwHh5sI+k{Aq}=&P?|459V{2qnxN zDeq2BhF|uTEzf-Ta41& z;w6(w?Dpkv2TG7~9O)~*!=YC!#O649>@?Z+4D0Kg6n~_WR|CeaKkLDu z*4XH2oq0V$)-uUjCZLGN<~eZC!0$IYfQJAZn-=fhKL1>bFg`Uqy<^+KWYWw;KFpMs zEPRcyx@K6Jt(y^>tDLbffZ0AjHy z?`Pl8)l}6}-9fn%V7i;{FR|I&4Y5NMi*@!%LrN_W*Q p)WTq(_nODE=RrB=6l@K_;x7nt#oubdL-_yz002ovPDHLkV1n~UCguPD diff --git a/org.eclipse.lsp4e/icons/full/obj16/constructor.svg b/org.eclipse.lsp4e/icons/full/obj16/constructor.svg index c1fdb7378..4a229c816 100644 --- a/org.eclipse.lsp4e/icons/full/obj16/constructor.svg +++ b/org.eclipse.lsp4e/icons/full/obj16/constructor.svg @@ -2,23 +2,23 @@ + inkscape:export-ydpi="96" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + inkscape:window-y="58" + inkscape:window-maximized="0" + inkscape:showpageshadow="0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#505050"> + snapvisiblegridlinesonly="true" + originx="0" + originy="0" + spacingy="1" + spacingx="1" + units="px" /> @@ -467,19 +475,46 @@ id="tspan4209" x="4.8992257" y="938.64496" - style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:6.14256096px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#ffffff;fill-opacity:1">M - + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:6.14256px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#ffffff;fill-opacity:1">M + + + id="path4163-4-0" + d="m 15.347633,1041.4132 v 0.5002 c 0,0 -0.532599,1.0498 -1.506129,1.0498 h -1.188536 c -0.515959,0 -1.124963,-0.7698 -1.124963,-0.9276 l -0.148438,-0.6884 v -0.8854" + style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.01003;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + + + + + diff --git a/org.eclipse.lsp4e/icons/full/obj16/constructor@2x.png b/org.eclipse.lsp4e/icons/full/obj16/constructor@2x.png index 92aab92e748fd5a4ab8b520e1034c6f4a8138974..bcae997b36d8823389360ae606dfe2a272400a2e 100644 GIT binary patch literal 7036 zcmeHLc|4Ts+aHmANrcKWjS?}tWlYAtmnCFtEVDeuSZ0_RW=JWKHCqy)2;mp9XALK% zBuYw2Nm@v@6grA`sB=!|^Zx!hpU?aLcV<4%GtYBf*ZsY&>w90%{ap9N+uK@*3GWaF zfk0wbmZlEeU-0@N$j?19!^1y=KmuLiPOdBm0vjB}p!-k*05B^o2mk{dst*Xn8J^B^ zV|>Jlo31q_ct&AP++=j9U1rZ^jtXcr1$dpOuiTJ*=;gtP&rnU{+nR0otfGCj?M3@C zpW59$YFgKG;reC8G8wjQib^<2WHD3!QjkRO_sJVa#rbGv!^@53UyrX&--xuG^Ub&z zkyV;}G(S?&ck*KUD(YbyqfWtX?f7VYi_}e0$EegqpeL<9LbG7BKPAKtujEB3aWbH< z+zaVE>gc-nbQLl2472`L;W5X-2?d3>>0)M)v=~?2?&SuN-1#1he!G^+wg8LCwCs2qC@*~K z+zQmj-$m^F;?pxZkEg_QUiP?zx)eyldUSVK?71vJK*Xo@AtM$#yZu+*l(@8i$4<`$ zs1;Qi3z>!rDt9q!Z*O&%n?I=(J6Zr*( z_K-${Fb-YBfA-q%pSP_Dn`gXgZ9)!bcZ;qa+kZIu)M&Do!D)X!qt2T#@i{lEza!0L z*D5#7gA~H-eU-i~057FpK{92u$CU-~&n))puUw6`Z|zRob|Ry~B>#a?M`H9{6_W%c zudNuyC9HUP!8diT@de&KH8qRGlOwaO%3m>l?xN8!`CgwT4B`tB+#WY86z zE<*%ViM+BmR1|wk9KK2#!QZtT`OUznZ#ygU#E8p?XMy&YH)WhH#-hoN*92GOybhZj zKEQ$>S$ffNeR^j9QgyAkkpK`^T3OatJ5+T{1LqQoS^Ug#y6t!T9FM8Zx1_6qBVL^Z z|IXJURW0R`fQ5un(Bfz6SZl@cnP|1boAYN*_3=BW>^8@>G#!85ytdd8t6hoBzI0q^ z^QED~QUBo_nt1oESFZWMdux>93!y=ko&kg#qKsa4C^PBe>z?H?&6t)M+~$7om|N>a6bHH_Tq-lKf3`IsXv%WCpFjL_F((}% z+oeDDcJPig-|1w&r7Tlt*%_lPf&Loh6Y3V5x~irsX0XZ`moA%3>*c)fm-Lt5tcbqK zxxvRO5x4&>EU#}?b%3H@eTa7fo)~oKuIxzc_?zW4*?7Mzfu81z$?DgZGLvp)73LIR zX27afI+Py8cU*_H>1S0-o=LWhC-Q|4U~fDtBQoS|JeD-}Gk2vDz1;>&#^WII`7thW zj>c%8kHkCYnwAPl6Th>QM)tcpLf(O#Da~4@jtHNj2h16l%6V&LI0h?r~GpJ6Pj6;n_QJuwvDf%iB}(!M|Bt|8VAMR)#3rW4!Df-*+R~ zy7N;N9+}}n&3Y`4?Mh!Vu206e0cAcKp@*Mon!Uo`&UYZz>;`&HuFa)94^D%?(jM(< zGpT$%zWcs+&%S5G&CPY$vO`*L5{%DgbdW7CrqZnpQ!ZQSFb-(BD|PO;`Rv>5_TWIs zsV3MX6Ns0Gqwci@lXo5=mVFi>%{p-u6@sRB1Jv@u{;h&dN%foQ_XPQ4&*B~C;>`U5 zQw7l?rXo5f@@;taED`-MuoUlA>*nn@d}H@Jq{>F*|Grz49^&|N^<3U)-EE;m7K0*Y*S^roM8BA#4_&T<9w~| zL&?V|MS?jRVA1?i9sDVc4E!5g;_xE4_hb@yVp*&Vym>4`%P#g(PnM+>8&`UR-dFN5 z?#iud-K#Q6brzW!nZ-{ zj6w3w#i5u41Oo)(iKd#E*jt&H{P8~LzQuEorQs}Z8p_}Da!iCk#Y49Q-?6{d8C4or z=v*MU&A8&he@@*k_IgmX%PKfn8ZRP#T*FxbpBoYqw2iM*VhcrJubHgce56%@S&~5a zcMf|tcu9QAj%`=mJjPF4dG3SF-cs9ixO!z9flty%un<+7q-$l}$&PrT+%eRG>HlP~ z^ggaMERlyNjgz_kicPDLUK@31?oxbV`R=P-@4HAU9;wS|QwiPl+(ej&9@@`mV4>>} zvHEGJwLoopnWUX$x06jt#IC98p0LDP=d$W2xUV8N!;S&jQ<|}Eug3yv6DLfIxsn0CNHZXiO{z2id^IaM5PwPR0|+c1IwKGVIY_02aQ;#_Q3C--7Ga$< zqz)R5LZh^h+9-^cj<(KUM$Q0($t^e>1O?O8h5c|uCSlFFfCO&cPy-1R03Jl6Y>Zf+ z6)ZOwF0X|3GUWnq$hoOtO&9=yMQ1qC=>a&%dZ-(P4c7ZH9hOukR~fcm>p!dA5eWWq z_oMg&s2eIUc%xvk1kw+DCLt6cZ}{Mj`=KKF5@-~F`(*ztr9b4<|1wQJWG$o?KtMss zBoqRwi6j!CL;{y!T`htZ5lIAe2$&yS{E5z_`>@yq24FO{XH7C zFR)G?0;LH>qM!)06H*h4!eG(b>Iftjfq=k&rVL+yqW{QQ5B|Sc(c3WiWeMPp`yt~l zE!>p~{^wHlleKjs{~td;XXF3T0~h*VC;y1wf9d*{u7AY9KQjKey8fl>A2INcjQ_2! z|1-LT|Jv*TG;SZn=5A%uETtN_+aP}80Si;m+WI%Yt>i3MA{1ol#sq;xq}C4}(1lz% zu26twWn(5VA<8QzzZ<>pNF-OZon_|AGNA{q_spP;UJf9G*;JM8 zOpTmeZ%$_(32-_reY(BAe}s~sS0?tzy6Y1;UzJB}SJ5UvOVKZ#hDnm;dzQWVoIZE@ zw(?88mO8ZQc2}&*vJXTnPDcamZD}u5Tx`l$6yshrG+D=d$LM>h|M@Zo-R64+Ufx~1 z#(ctFtsfdbk~J1oM_SAs?JnpW$G5T0IoWO1dB4e9Ezv@zf?zRyFZw%yXRcSX<3{3{ zkMy7!TFydE4#LV%{xOP|3aBF?r;WBq_O5GM}jzp=HEDe*pPMkNnH71b465EQ{m3iM{$~j-h%u*>d4C7 zrUEKrgkRJ~xT=h1Rgv{(g}B8k zV0wg3)#Dls9hp3{j@jBzD?XXl6vpSJJ^Pr+E$vUQH{?c~YB-rFdU4tZeD2l2^AZ() zxdV#go8!zVM=mF@13G5+=$9P)@eE_js;16eWX&Hw_M56ZTG@b<1Rss?&q-=?qSHjL#1|U)kBh#^EX>YgQE? zX{y;t_xlv4o_e-zAM(9XCGm*elyh)EacEzP{q>JXPqzj6YYW7&t#P)4QN8eN1#{s> zPiJSVR-*n;3yev541uIg*i7kQuYLM}>;Kg2VVC;2DGhC?(ZgHa-lMM&+cRaCRu7p7 zLZqI@`=6P@YLr**-q4T$Gb@htpWIP518&VBhT7={Um=P zO!4Y+Cfy%rt0_Yq}<@c&Qm96435oK>bKD=BnU|BpM7nouAs7?X#pEpcXn8fbWFXHj4 z;k|w)5M+@NHY)T=Fskh!B0(bBn5XA*ZT5ux@==j5i+kj3i;?b%fy>J!Ry^g#68)NN z57*X4)XbaQqs(M~W1jZ7J4KN%hYT?3cwgVA?B8FMA&%UV>CM@5w9two7Nj5_T3VnLMC(WRi~$o#ObmshF;NLdVt;%wk)n|hBdE~_(F9uL z#ncB(3^7O{5{O{ZLQ82Np#%;6#9i7#x82>DIle$E-EC<}d6JXNWODES-us(#=Kk-% z{kR|h_mGi@%1cT-mnV0x5Md2Skq9{es6>=2s;bHvbAS!&=C4Hb3qkWl za8vw#!!!*5Y=6fg8jHK?s)5LP6@9OwqW0IZ0+5ZR1zQz9@_0OMdN9?rEPuifRfzQV z(baXsv2Djt*jBNx_G3Ut0$>i+v7xkJD^MN?q)7I}bi?QKCgl|YpVvcLTEMU!SK@KI zWZn~#+%xs9zuz@L*}8&dB7ARIDUy{H#Ow7DOrJ;~FnV3s8M1-6Nr}56_Zz+CiHR%~SG@d&S zK*snS-rcf?;zeu7&dH_z%n$+MvJ@4t=jg5`Nmckg^kQTVO6lGR=Fx3=E zO!=(K;eU8it;aMG!Bf(+J74rfTAt7J+z}t3pK+-`+tM_xXdOrY}43xR8 zJ%5bjIH(J%7y_@Bl@`1_)Z&WnOaOQ!x90HQmkR5{CqCo3x`BRsWfZn;WY zS|+=`8va16uRFn)yEY@REg=H{m6fNf%YW7vei-SEZtu9-sacunVi?0is4A{A@ZEHF zUZth^(rp^cO5@SoS+}(%1pwGvS+iqfX`%M^#1&Th<2A18ZV_2FWom5!!oa~WC{vN1>q+MyvIPH)@^W0LIRqRUPFGes@GK%2hQaa` zrBomOk)Nu*CSZ-@x#wRQJ;2?BVSgBGe50I2i&ybv{t|Y5zKQ+&cHr@N2?nzUboUCN zsCYSx7Ow(edR{)8Ht)pa@lsjwDNRlF_ZGmm|#?EnA(07*qoL + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/constr_ovr.svg b/org.eclipse.lsp4e/icons/full/ovr16/constr_ovr.svg new file mode 100644 index 000000000..8c4fab343 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/constr_ovr.svg @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/declaration_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/declaration_co.svg new file mode 100644 index 000000000..95c493013 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/declaration_co.svg @@ -0,0 +1,1052 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/definition_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/definition_co.svg new file mode 100644 index 000000000..b106c342e --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/definition_co.svg @@ -0,0 +1,1052 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/deprecated.svg b/org.eclipse.lsp4e/icons/full/ovr16/deprecated.svg new file mode 100644 index 000000000..76431fe24 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/deprecated.svg @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/file_visable_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/file_visable_co.svg new file mode 100644 index 000000000..1f56b59cc --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/file_visable_co.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/final_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/final_co.svg new file mode 100644 index 000000000..61c5103a6 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/final_co.svg @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/implement_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/implement_co.svg new file mode 100644 index 000000000..d1325bddf --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/implement_co.svg @@ -0,0 +1,828 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/internal_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/internal_co.svg new file mode 100644 index 000000000..c6812a413 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/internal_co.svg @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/non_null_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/non_null_co.svg new file mode 100644 index 000000000..fec5a6692 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/non_null_co.svg @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/nullable_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/nullable_co.svg new file mode 100644 index 000000000..92bdf7366 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/nullable_co.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/override_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/override_co.svg new file mode 100644 index 000000000..ca6269a86 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/override_co.svg @@ -0,0 +1,828 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/package_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/package_co.svg new file mode 100644 index 000000000..dee689a3b --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/package_co.svg @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/private_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/private_co.svg new file mode 100644 index 000000000..12ff1c672 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/private_co.svg @@ -0,0 +1,4448 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/protected_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/protected_co.svg new file mode 100644 index 000000000..61e694bf6 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/protected_co.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/public_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/public_co.svg new file mode 100644 index 000000000..c72adc590 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/public_co.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/read_only_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/read_only_co.svg new file mode 100644 index 000000000..2aaecc516 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/read_only_co.svg @@ -0,0 +1,263 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/sealed_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/sealed_co.svg new file mode 100644 index 000000000..b235e7d44 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/sealed_co.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/static_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/static_co.svg new file mode 100644 index 000000000..a94d9c822 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/static_co.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/synch_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/synch_co.svg new file mode 100644 index 000000000..abea286be --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/synch_co.svg @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/transient_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/transient_co.svg new file mode 100644 index 000000000..5f98881e1 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/transient_co.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/virtual_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/virtual_co.svg new file mode 100644 index 000000000..eb3e4d528 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/virtual_co.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/volatile_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/volatile_co.svg new file mode 100644 index 000000000..979eda22c --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/ovr16/volatile_co.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerPlugin.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerPlugin.java index d2cd575c6..39d0e808c 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerPlugin.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerPlugin.java @@ -67,8 +67,13 @@ public void start(BundleContext context) throws Exception { @Override public void stop(BundleContext context) throws Exception { plugin = null; - LanguageServiceAccessor.shutdownAllDispatchers(); - super.stop(context); + try { + LanguageServiceAccessor.shutdownAllDispatchers(); + LSPImages.dispose(); + } + finally { + super.stop(context); + } } /** 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 cc874037c..ba671714c 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyLabelProvider.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyLabelProvider.java @@ -29,7 +29,7 @@ public class CallHierarchyLabelProvider extends LabelProvider implements IStyled public @Nullable Image getImage(final @Nullable Object element) { if (element instanceof CallHierarchyViewTreeNode treeNode) { CallHierarchyItem callContainer = treeNode.getCallContainer(); - Image res = LSPImages.imageFromSymbolKind(callContainer.getKind()); + Image res = LSPImages.getImageFor(callContainer.getKind(), callContainer.getTags()); if (res != null) { return res; } diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/SupportedFeatures.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/SupportedFeatures.java index f674b4eed..f8c3615c8 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/SupportedFeatures.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/SupportedFeatures.java @@ -57,6 +57,8 @@ import org.eclipse.lsp4j.SymbolCapabilities; import org.eclipse.lsp4j.SymbolKind; import org.eclipse.lsp4j.SymbolKindCapabilities; +import org.eclipse.lsp4j.SymbolTag; +import org.eclipse.lsp4j.SymbolTagSupportCapabilities; import org.eclipse.lsp4j.SynchronizationCapabilities; import org.eclipse.lsp4j.TextDocumentClientCapabilities; import org.eclipse.lsp4j.TypeDefinitionCapabilities; @@ -120,6 +122,7 @@ public static TextDocumentClientCapabilities getTextDocumentClientCapabilities() final var documentSymbol = new DocumentSymbolCapabilities(); documentSymbol.setHierarchicalDocumentSymbolSupport(true); documentSymbol.setSymbolKind(new SymbolKindCapabilities(List.of(SymbolKind.values()))); + documentSymbol.setTagSupport(new SymbolTagSupportCapabilities(List.of(SymbolTag.values()))); textDocumentClientCapabilities.setDocumentSymbol(documentSymbol); final var foldingRangeCapabilities = new FoldingRangeCapabilities(); foldingRangeCapabilities.setLineFoldingOnly(true); @@ -153,7 +156,9 @@ public static WorkspaceClientCapabilities getWorkspaceClientCapabilities() { workspaceClientCapabilities.setApplyEdit(true); workspaceClientCapabilities.setConfiguration(true); workspaceClientCapabilities.setExecuteCommand(new ExecuteCommandCapabilities(true)); - workspaceClientCapabilities.setSymbol(new SymbolCapabilities(true)); + SymbolCapabilities symbolCapabilities = new SymbolCapabilities(true); + symbolCapabilities.setTagSupport(new SymbolTagSupportCapabilities(List.of(SymbolTag.values()))); + workspaceClientCapabilities.setSymbol(symbolCapabilities); workspaceClientCapabilities.setWorkspaceFolders(true); final var editCapabilities = new WorkspaceEditCapabilities(); diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/symbols/SymbolsUtil.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/symbols/SymbolsUtil.java new file mode 100644 index 000000000..2c2b6c467 --- /dev/null +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/symbols/SymbolsUtil.java @@ -0,0 +1,188 @@ +/******************************************************************************* + * 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; + +import java.util.Collections; +import java.util.List; + +import org.eclipse.lsp4e.outline.SymbolsModel.DocumentSymbolWithURI; +import org.eclipse.lsp4j.DocumentSymbol; +import org.eclipse.lsp4j.SymbolInformation; +import org.eclipse.lsp4j.SymbolKind; +import org.eclipse.lsp4j.SymbolTag; +import org.eclipse.lsp4j.WorkspaceSymbol; + +public class SymbolsUtil { + + public static SymbolKind getKind(SymbolInformation symbolInformation) { + return symbolInformation.getKind(); + } + + public static SymbolKind getKind(WorkspaceSymbol workspaceSymbol) { + return workspaceSymbol.getKind(); + } + + public static SymbolKind getKind(DocumentSymbol documentSymbol) { + return documentSymbol.getKind(); + } + + public static SymbolKind getKind(DocumentSymbolWithURI documentSymbolWithUri) { + return getKind(documentSymbolWithUri.symbol); + } + + public static List getSymbolTags(SymbolInformation symbolInformation) { + if (symbolInformation.getTags() != null) { + return symbolInformation.getTags(); + } + + return Collections.emptyList(); + } + + public static List getSymbolTags(WorkspaceSymbol workspaceSymbol) { + if (workspaceSymbol.getTags() != null) { + return workspaceSymbol.getTags(); + } + + return Collections.emptyList(); + } + + public static List getSymbolTags(DocumentSymbol documentSymbol) { + if (documentSymbol.getTags() != null) { + return documentSymbol.getTags(); + } + + return Collections.emptyList(); + } + + public static List getSymbolTags(DocumentSymbolWithURI documentSymbolWithUri) { + return getSymbolTags(documentSymbolWithUri.symbol); + } + + public static boolean hasSymbolTag(List tagList, SymbolTag tag) { + return tagList.contains(tag); + } + + public static boolean hasSymbolTag(SymbolInformation symbolInformation, SymbolTag tag) { + return getSymbolTags(symbolInformation).contains(tag); + } + + public static boolean hasSymbolTag(WorkspaceSymbol workspaceSymbol, SymbolTag tag) { + return getSymbolTags(workspaceSymbol).contains(tag); + } + + public static boolean hasSymbolTag(DocumentSymbol documentSymbol, SymbolTag tag) { + return getSymbolTags(documentSymbol).contains(tag); + } + + public static boolean hasSymbolTag(DocumentSymbolWithURI documentSymbolWithUri, SymbolTag tag) { + return getSymbolTags(documentSymbolWithUri).contains(tag); + } + + public static boolean isDeprecated(SymbolInformation symbolInformation) { + boolean deprecated = isDeprecated(getSymbolTags(symbolInformation)); + return deprecated || Boolean.TRUE.equals(symbolInformation.getDeprecated()); + } + + public static boolean isDeprecated(WorkspaceSymbol workspaceSymbol) { + return isDeprecated(getSymbolTags(workspaceSymbol)); + } + + public static boolean isDeprecated(DocumentSymbol documentSymbol) { + boolean deprecated = isDeprecated(getSymbolTags(documentSymbol)); + return deprecated || Boolean.TRUE.equals(documentSymbol.getDeprecated()); + } + + public static boolean isDeprecated(DocumentSymbolWithURI documentSymbolWithUri) { + return isDeprecated(documentSymbolWithUri.symbol); + } + + public static boolean isDeprecated(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.Deprecated); + } + + public static boolean isPrivate(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.Private); + } + + public static boolean isPackage(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.Package); + } + + public static boolean isProtected(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.Protected); + } + + public static boolean isPublic(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.Public); + } + + public static boolean isInternal(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.Internal); + } + + public static boolean isFileVisible(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.File); + } + + public static boolean isStatic(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.Static); + } + + public static boolean isAbstract(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.Abstract); + } + + public static boolean isFinal(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.Final); + } + + public static boolean isSealed(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.Sealed); + } + + public static boolean isTransient(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.Transient); + } + + public static boolean isVolatile(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.Volatile); + } + + public static boolean isSynchronized(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.Synchronized); + } + + public static boolean isVirtual(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.Virtual); + } + + public static boolean isNullable(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.Nullable); + } + + public static boolean isNonNull(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.NonNull); + } + + public static boolean isDeclaration(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.Declaration); + } + + public static boolean isDefinition(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.Definition); + } + + public static boolean isReadOnly(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.ReadOnly); + } + +} 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 1cea86359..c4f4841cd 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 @@ -29,7 +29,7 @@ public String getText(Object element) { @Override public @Nullable Image getImage(@Nullable Object element) { if (element instanceof TypeHierarchyItem item) { - return LSPImages.imageFromSymbolKind(item.getKind()); + return LSPImages.getImageFor(item.getKind(), item.getTags()); } 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 69720ffbc..b45b4174d 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/SymbolsLabelProvider.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/SymbolsLabelProvider.java @@ -8,19 +8,19 @@ * * Contributors: * Mickael Istria (Red Hat Inc.) - initial implementation + * Dietrich Travkin (SOLUNAR GmbH) - Add overlay icons for new symbol tags *******************************************************************************/ package org.eclipse.lsp4e.outline; -import static org.eclipse.lsp4e.LSPEclipseUtils.findResourceFor; -import static org.eclipse.lsp4e.internal.NullSafetyHelper.castNullable; +import static org.eclipse.lsp4e.LSPEclipseUtils.*; import java.net.URI; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; @@ -35,9 +35,7 @@ import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.viewers.DecorationOverlayIcon; import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider; -import org.eclipse.jface.viewers.IDecoration; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.LabelProviderChangedEvent; @@ -45,6 +43,7 @@ import org.eclipse.lsp4e.LSPEclipseUtils; import org.eclipse.lsp4e.LanguageServerPlugin; import org.eclipse.lsp4e.internal.StyleUtil; +import org.eclipse.lsp4e.operations.symbols.SymbolsUtil; import org.eclipse.lsp4e.outline.SymbolsModel.DocumentSymbolWithURI; import org.eclipse.lsp4e.ui.LSPImages; import org.eclipse.lsp4e.ui.Messages; @@ -87,11 +86,7 @@ public class SymbolsLabelProvider extends LabelProvider LanguageServerPlugin.logError(ex); } }; - /* - * key: initial object image - * value: array of images decorated with marker for severity (index + 1) - */ - private final Map overlays = new HashMap<>(); + private final Map resourceCache = new HashMap<>(); private final boolean showLocation; @@ -103,7 +98,7 @@ public SymbolsLabelProvider() { .getBoolean(CNFOutlinePage.SHOW_KIND_PREFERENCE, false)); } - public SymbolsLabelProvider(boolean showLocation, boolean showKind) { + public SymbolsLabelProvider(final boolean showLocation, final boolean showKind) { this.showLocation = showLocation; this.showKind = showKind; InstanceScope.INSTANCE.getNode(LanguageServerPlugin.PLUGIN_ID).addPreferenceChangeListener(this); @@ -114,14 +109,16 @@ public SymbolsLabelProvider(boolean showLocation, boolean showKind) { public void dispose() { ResourcesPlugin.getWorkspace().removeResourceChangeListener(listener); InstanceScope.INSTANCE.getNode(LanguageServerPlugin.PLUGIN_ID).removePreferenceChangeListener(this); - overlays.values().stream().flatMap(Arrays::stream).filter(Objects::nonNull).forEach(Image::dispose); - overlays.clear(); super.dispose(); } @Override - public @Nullable Image getImage(@Nullable Object element) { - if (element == null) { + public @Nullable Image getImage(final @Nullable Object element) { + // If needed, we could use more overlays like in org.eclipse.jdt.ui.JavaElementImageDescriptor, + // but this would demand more space in various views. + // See guidelines, Section "Icon Overlays": https://www.eclipse.org/articles/Article-UI-Guidelines/Contents.html + + if (element == null){ return null; } if (element instanceof PendingUpdateAdapter) { @@ -130,29 +127,53 @@ public void dispose() { if (element instanceof Throwable) { return LSPImages.getSharedImage(ISharedImages.IMG_OBJS_ERROR_TSK); } + + var actualElement = element; if (element instanceof Either either) { - element = either.get(); + actualElement = either.get(); + } + SymbolKind symbolKind = null; + List symbolTags = Collections.emptyList(); + boolean deprecated = false; + if (actualElement instanceof SymbolInformation info) { + symbolKind = SymbolsUtil.getKind(info); + symbolTags = SymbolsUtil.getSymbolTags(info); + deprecated = SymbolsUtil.isDeprecated(info); + } else if (actualElement instanceof WorkspaceSymbol symbol) { + symbolKind = SymbolsUtil.getKind(symbol); + symbolTags = SymbolsUtil.getSymbolTags(symbol); + deprecated = SymbolsUtil.isDeprecated(symbol); + } else if (actualElement instanceof DocumentSymbol symbol) { + symbolKind = SymbolsUtil.getKind(symbol); + symbolTags = SymbolsUtil.getSymbolTags(symbol); + deprecated = SymbolsUtil.isDeprecated(symbol); + } else if (actualElement instanceof DocumentSymbolWithURI symbolWithURI) { + symbolKind = SymbolsUtil.getKind(symbolWithURI); + symbolTags = SymbolsUtil.getSymbolTags(symbolWithURI); + deprecated = SymbolsUtil.isDeprecated(symbolWithURI); } - Image image = null; + if (actualElement != null && symbolKind != null) { + return LSPImages.getImageFor(symbolKind, symbolTags, deprecated, getMaxSeverity(actualElement)); + } + + return null; + } + + private int getMaxSeverity(final Object element) { IResource file = null; if (element instanceof SymbolInformation info) { - image = LSPImages.imageFromSymbolKind(info.getKind()); file = resourceCache.computeIfAbsent(info.getLocation().getUri(), uri -> findResourceFor((String) uri)); } else if (element instanceof WorkspaceSymbol symbol) { - image = LSPImages.imageFromSymbolKind(symbol.getKind()); file = resourceCache.computeIfAbsent(getUri(symbol), uri -> findResourceFor((String) uri)); - } else if (element instanceof DocumentSymbol symbol) { - image = LSPImages.imageFromSymbolKind(symbol.getKind()); } else if (element instanceof DocumentSymbolWithURI symbolWithURI) { - image = LSPImages.imageFromSymbolKind(symbolWithURI.symbol.getKind()); file = resourceCache.computeIfAbsent(symbolWithURI.uri, uri -> findResourceFor((URI) uri)); } /* * Implementation node: for problem decoration, maybe consider using a ILabelDecorator/IDelayedLabelDecorator? */ - if (file != null && image != null) { + if (file != null) { Range range = null; if (element instanceof SymbolInformation symbol) { range = symbol.getLocation().getRange(); @@ -163,6 +184,7 @@ public void dispose() { } else if (element instanceof DocumentSymbolWithURI symbolWithURI) { range = symbolWithURI.symbol.getRange(); } + if (range != null) { try { // use existing documents only to calculate the severity @@ -171,20 +193,17 @@ public void dispose() { IDocument doc = LSPEclipseUtils.getExistingDocument(file); if (doc != null) { - int maxSeverity = getMaxSeverity(file, doc, range); - if (maxSeverity > IMarker.SEVERITY_INFO) { - return getOverlay(image, maxSeverity); - } + return getMaxSeverity(file, doc, range); } } catch (CoreException | BadLocationException e) { LanguageServerPlugin.logError(e); } } } - return image; + return -1; } - protected int getMaxSeverity(IResource resource, IDocument doc, Range range) + protected int getMaxSeverity(final IResource resource, final IDocument doc, final Range range) throws CoreException, BadLocationException { if (!severities.containsKey(resource)) { refreshMarkersByLine(resource); @@ -207,7 +226,7 @@ protected int getMaxSeverity(IResource resource, IDocument doc, Range range) .orElse(-1); } - private void refreshMarkersByLine(IResource resource) throws CoreException { + private void refreshMarkersByLine(final IResource resource) throws CoreException { RangeMap rangeMap = TreeRangeMap.create(); Arrays.stream(resource.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_ZERO)) .filter(marker -> marker.getAttribute(IMarker.SEVERITY, -1) > IMarker.SEVERITY_INFO) @@ -228,31 +247,13 @@ private void refreshMarkersByLine(IResource resource) throws CoreException { severities.put(resource, rangeMap); } - private Image getOverlay(Image res, int maxSeverity) { - if (maxSeverity != 1 && maxSeverity != 2) { - throw new IllegalArgumentException("Severity " + maxSeverity + " not supported."); //$NON-NLS-1$ //$NON-NLS-2$ - } - Image[] currentOverlays = this.overlays.computeIfAbsent(res, key -> new Image [2]); - if (castNullable(currentOverlays[maxSeverity - 1]) == null) { - String overlayId = null; - if (maxSeverity == IMarker.SEVERITY_ERROR) { - overlayId = ISharedImages.IMG_DEC_FIELD_ERROR; - } else if (maxSeverity == IMarker.SEVERITY_WARNING) { - overlayId = ISharedImages.IMG_DEC_FIELD_WARNING; - } - currentOverlays[maxSeverity - 1] = new DecorationOverlayIcon(res, - LSPImages.getSharedImageDescriptor(overlayId), IDecoration.BOTTOM_LEFT).createImage(); - } - return currentOverlays[maxSeverity - 1]; - } - @Override - public String getText(Object element) { + public String getText(final Object element) { return getStyledText(element).getString(); } @Override - public StyledString getStyledText(@Nullable Object element) { + public StyledString getStyledText(final @Nullable Object element) { if (element instanceof PendingUpdateAdapter) { return new StyledString(Messages.outline_computingSymbols); @@ -268,44 +269,46 @@ public StyledString getStyledText(@Nullable Object element) { if (element == null){ return res; } + + var actualElement = element; if (element instanceof Either either) { - element = either.get(); + actualElement = either.get(); } String name = null; SymbolKind kind = null; String detail = null; URI location = null; boolean deprecated = false; - if (element instanceof SymbolInformation symbolInformation) { + if (actualElement instanceof SymbolInformation symbolInformation) { name = symbolInformation.getName(); kind = symbolInformation.getKind(); - deprecated = isDeprecated(symbolInformation.getTags()) || symbolInformation.getDeprecated() != null && symbolInformation.getDeprecated(); + deprecated = SymbolsUtil.isDeprecated(symbolInformation); try { location = URI.create(symbolInformation.getLocation().getUri()); } catch (IllegalArgumentException e) { LanguageServerPlugin.logError("Invalid URI: " + symbolInformation.getLocation().getUri(), e); //$NON-NLS-1$ } - } else if (element instanceof WorkspaceSymbol workspaceSymbol) { + } else if (actualElement instanceof WorkspaceSymbol workspaceSymbol) { name = workspaceSymbol.getName(); kind = workspaceSymbol.getKind(); String rawUri = getUri(workspaceSymbol); - deprecated = isDeprecated(workspaceSymbol.getTags()); + deprecated = SymbolsUtil.isDeprecated(workspaceSymbol); try { location = URI.create(rawUri); } catch (IllegalArgumentException e) { LanguageServerPlugin.logError("Invalid URI: " + rawUri, e); //$NON-NLS-1$ } - } else if (element instanceof DocumentSymbol documentSymbol) { + } else if (actualElement instanceof DocumentSymbol documentSymbol) { name = documentSymbol.getName(); kind = documentSymbol.getKind(); detail = documentSymbol.getDetail(); - deprecated = isDeprecated(documentSymbol.getTags()) || documentSymbol.getDeprecated() != null && documentSymbol.getDeprecated(); - } else if (element instanceof DocumentSymbolWithURI symbolWithURI) { + deprecated = SymbolsUtil.isDeprecated(documentSymbol); + } else if (actualElement instanceof DocumentSymbolWithURI symbolWithURI) { name = symbolWithURI.symbol.getName(); kind = symbolWithURI.symbol.getKind(); detail = symbolWithURI.symbol.getDetail(); location = symbolWithURI.uri; - deprecated = isDeprecated(symbolWithURI.symbol.getTags()) || symbolWithURI.symbol.getDeprecated() != null && symbolWithURI.symbol.getDeprecated(); + deprecated = SymbolsUtil.isDeprecated(symbolWithURI); } if (name != null) { if (deprecated) { @@ -332,13 +335,6 @@ public StyledString getStyledText(@Nullable Object element) { return res; } - private boolean isDeprecated(@Nullable List tags) { - if(tags != null){ - return tags.contains(SymbolTag.Deprecated); - } - return false; - } - @Override public void restoreState(final IMemento aMemento) { } @@ -357,7 +353,7 @@ public void init(final ICommonContentExtensionSite aConfig) { } @Override - public void preferenceChange(PreferenceChangeEvent event) { + public void preferenceChange(final PreferenceChangeEvent event) { if (event.getKey().equals(CNFOutlinePage.SHOW_KIND_PREFERENCE)) { this.showKind = Boolean.parseBoolean(String.valueOf(event.getNewValue())); for (Object listener : this.getListeners()) { @@ -368,7 +364,7 @@ public void preferenceChange(PreferenceChangeEvent event) { } } - private static String getUri(WorkspaceSymbol symbol) { + private static String getUri(final WorkspaceSymbol symbol) { return symbol.getLocation().map(Location::getUri, WorkspaceSymbolLocation::getUri); } 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 d371adfb6..67b873553 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/LSPImages.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/LSPImages.java @@ -8,23 +8,36 @@ * * Contributors: * Michał Niewrzał (Rogue Wave Software Inc.) - initial implementation + * Dietrich Travkin (Solunar GmbH) - add overlay images computation, dispose cached images *******************************************************************************/ package org.eclipse.lsp4e.ui; import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.eclipse.core.resources.IMarker; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.ImageRegistry; +import org.eclipse.jface.viewers.DecorationOverlayIcon; import org.eclipse.lsp4e.LSPEclipseUtils; import org.eclipse.lsp4e.LanguageServerPlugin; +import org.eclipse.lsp4e.operations.symbols.SymbolsUtil; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.SymbolKind; +import org.eclipse.lsp4j.SymbolTag; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; @@ -40,10 +53,26 @@ private LSPImages() { } private static @Nullable ImageRegistry imageRegistry; + private static final Map 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(SymbolKind, 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$ @@ -81,6 +110,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; @@ -118,6 +172,30 @@ 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 + "private_co.svg"); //$NON-NLS-1$ + declareRegistryImage(IMG_OVR_PACKAGE, OVERLAY + "package_co.svg"); //$NON-NLS-1$ + declareRegistryImage(IMG_OVR_PROTECTED, OVERLAY + "protected_co.svg"); //$NON-NLS-1$ + declareRegistryImage(IMG_OVR_PUBLIC, OVERLAY + "public_co.svg"); //$NON-NLS-1$ + declareRegistryImage(IMG_OVR_INTERNAL, OVERLAY + "internal_co.svg"); //$NON-NLS-1$ + declareRegistryImage(IMG_OVR_FILE_VIS, OVERLAY + "file_visable_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 +211,11 @@ private static void declareRegistryImage(String key, String path) { getImageRegistry().put(key, desc); } + private record ImageWithOverlaysKey(SymbolKind symbolKind, + @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. */ @@ -240,6 +323,60 @@ public static ImageRegistry getImageRegistry() { }; } + public static @Nullable Image imageOverlayFromSymbolTag(SymbolTag symbolTag) { + return switch (symbolTag) { + case Deprecated -> getImage(IMG_OVR_DEPRECATED); + case Private -> getImage(IMG_OVR_PRIVATE); + case Package -> getImage(IMG_OVR_PACKAGE); + case Protected -> getImage(IMG_OVR_PROTECTED); + case Public -> getImage(IMG_OVR_PUBLIC); + case Internal -> getImage(IMG_OVR_INTERNAL); + case File -> getImage(IMG_OVR_FILE_VIS); + case Static -> getImage(IMG_OVR_STATIC); + case Abstract -> getImage(IMG_OVR_ABSTRACT); + case Final -> getImage(IMG_OVR_FINAL); + case Sealed -> getImage(IMG_OVR_SEALED); + case Transient -> getImage(IMG_OVR_TRANSIENT); + case Volatile -> getImage(IMG_OVR_VOLATILE); + case Synchronized -> getImage(IMG_OVR_SYNC); + case Virtual -> getImage(IMG_OVR_VIRTUAL); + case Nullable -> getImage(IMG_OVR_NULLABLE); + case NonNull -> getImage(IMG_OVR_NON_NULL); + case Declaration -> getImage(IMG_OVR_DECLARATION); + case Definition -> getImage(IMG_OVR_DEFINITION); + case ReadOnly -> getImage(IMG_OVR_READ_ONLY); + case Overrides -> getImage(IMG_OVR_OVERRIDE); + case Implements -> getImage(IMG_OVR_IMPLEMENT); + }; + } + + public static @Nullable ImageDescriptor imageDescriptorOverlayFromSymbolTag(SymbolTag symbolTag) { + return switch (symbolTag) { + case Deprecated -> getImageDescriptor(IMG_OVR_DEPRECATED); + case Private -> getImageDescriptor(IMG_OVR_PRIVATE); + case Package -> getImageDescriptor(IMG_OVR_PACKAGE); + case Protected -> getImageDescriptor(IMG_OVR_PROTECTED); + case Public -> getImageDescriptor(IMG_OVR_PUBLIC); + case Internal -> getImageDescriptor(IMG_OVR_INTERNAL); + case File -> getImageDescriptor(IMG_OVR_FILE_VIS); + case Static -> getImageDescriptor(IMG_OVR_STATIC); + case Abstract -> getImageDescriptor(IMG_OVR_ABSTRACT); + case Final -> getImageDescriptor(IMG_OVR_FINAL); + case Sealed -> getImageDescriptor(IMG_OVR_SEALED); + case Transient -> getImageDescriptor(IMG_OVR_TRANSIENT); + case Volatile -> getImageDescriptor(IMG_OVR_VOLATILE); + case Synchronized -> getImageDescriptor(IMG_OVR_SYNC); + case Virtual -> getImageDescriptor(IMG_OVR_VIRTUAL); + case Nullable -> getImageDescriptor(IMG_OVR_NULLABLE); + case NonNull -> getImageDescriptor(IMG_OVR_NON_NULL); + case Declaration -> getImageDescriptor(IMG_OVR_DECLARATION); + case Definition -> getImageDescriptor(IMG_OVR_DEFINITION); + case ReadOnly -> getImageDescriptor(IMG_OVR_READ_ONLY); + case Overrides -> getImageDescriptor(IMG_OVR_OVERRIDE); + case Implements -> getImageDescriptor(IMG_OVR_IMPLEMENT); + }; + } + private static @Nullable Image getImageForColor(CompletionItem completionItem) { String hexValue = null; @@ -274,4 +411,164 @@ public static ImageRegistry getImageRegistry() { return image; }); } + + private static final List VISIBILITY_PRECEDENCE = List.of( + SymbolTag.Public, SymbolTag.Protected, SymbolTag.Package, SymbolTag.Private, + SymbolTag.Internal, SymbolTag.File); + + // Precedence for remaining symbol tags (without visibility tags and deprecation tag) + // 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); + + private static Optional getHighestPrecedenceVisibilitySymbolTag(List symbolTags) { + // TODO Log a warning if we find more than one visibility tag? + return symbolTags.stream() + .filter(tag -> VISIBILITY_PRECEDENCE.contains(tag)) + .min(Comparator.comparing(VISIBILITY_PRECEDENCE::indexOf)); + } + + private static List getAdditionalSymbolTagsSorted(List symbolTags) { + return symbolTags.stream() + .filter(tag -> ADDITIONAL_TAGS_PRECEDENCE.contains(tag)) + .sorted(Comparator.comparing(ADDITIONAL_TAGS_PRECEDENCE::indexOf)) + .collect(Collectors.toList()); + } + + private static @Nullable ImageDescriptor getOverlayForVisibility(List symbolTags) { + Optional visibilityTag = getHighestPrecedenceVisibilitySymbolTag(symbolTags); + + if (visibilityTag.isEmpty()) { + return null; + } + + return LSPImages.imageDescriptorOverlayFromSymbolTag(visibilityTag.get()); + } + + private static @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; + }; + } + + private static @Nullable ImageDescriptor getUnderlayForDeprecation(boolean deprecated) { + if (!deprecated) { + return null; + } + return LSPImages.imageDescriptorOverlayFromSymbolTag(SymbolTag.Deprecated); + } + + private static @Nullable Image getImageWithOverlays(SymbolKind symbolKind, + @Nullable ImageDescriptor topLeftOverlayDescriptor, @Nullable ImageDescriptor topRightOverlayDescriptor, + @Nullable ImageDescriptor bottomLeftOverlayDescriptor, @Nullable ImageDescriptor bottomRightOverlayDescriptor, + @Nullable ImageDescriptor underlayImageDescriptor) { + Image baseImage = LSPImages.imageFromSymbolKind(symbolKind); + + if (baseImage == null) { + return null; + } + + // 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 baseImage; + } + + ImageWithOverlaysKey key = new ImageWithOverlaysKey(symbolKind, + topLeftOverlayDescriptor, topRightOverlayDescriptor, + bottomLeftOverlayDescriptor, bottomRightOverlayDescriptor, underlayImageDescriptor); + + return overlayImagesCache.computeIfAbsent(key, + k -> new DecorationOverlayIcon(baseImage, overlays).createImage()); + } + + /** + * 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, boolean, int) + */ + public static @Nullable Image getImageFor(@Nullable SymbolKind symbolKind, @Nullable List symbolTags) { + return getImageFor(symbolKind, symbolTags, false, -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 deprecated whether to add a deprecation overlay icon even if there is no {@link SymbolTag#Deprecated} in the 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. + */ + public static @Nullable Image getImageFor(@Nullable SymbolKind symbolKind, @Nullable List symbolTags, + boolean deprecated, int severity) { + + if (symbolKind == null) { + return EMPTY_IMAGE; + } + + if (symbolTags == null) { + symbolTags = Collections.emptyList(); + } + + ImageDescriptor severityImageDescriptor = getOverlayForMarkerSeverity(severity); + ImageDescriptor visibilityImageDescriptor = getOverlayForVisibility(symbolTags); + ImageDescriptor deprecatedImageDescriptor = getUnderlayForDeprecation(deprecated || SymbolsUtil.isDeprecated(symbolTags)); + + List additionalTags = getAdditionalSymbolTagsSorted(symbolTags); + + // 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) + ImageDescriptor topLeftOverlayDescriptor = null; + ImageDescriptor topRightOverlayDescriptor = null; + ImageDescriptor bottomLeftOverlayDescriptor = severityImageDescriptor; + ImageDescriptor bottomRightOverlayDescriptor = visibilityImageDescriptor; + ImageDescriptor underlayDescriptor = deprecatedImageDescriptor; + + // TODO Use visibility-representing document symbol icons for fields and methods (similar to JDT) so that we can visualize one more symbol tag + + if (!additionalTags.isEmpty()) { + topLeftOverlayDescriptor = LSPImages.imageDescriptorOverlayFromSymbolTag(additionalTags.get(0)); + + if (additionalTags.size() > 1 && !SymbolKind.Constructor.equals(symbolKind)) { + // constructor base image has a built-in overlay in the top right corner, + // in this case we omit the second symbol tag's overlay icon + topRightOverlayDescriptor = LSPImages.imageDescriptorOverlayFromSymbolTag(additionalTags.get(1)); + } + } + + return getImageWithOverlays(symbolKind, topLeftOverlayDescriptor, topRightOverlayDescriptor, + bottomLeftOverlayDescriptor, bottomRightOverlayDescriptor, underlayDescriptor); + } + + public static final void dispose() { + Stream.concat( + colorToImageCache.values().stream(), + overlayImagesCache.values().stream()) + .filter(Objects::nonNull) + .forEach(Image::dispose); + overlayImagesCache.clear(); + colorToImageCache.clear(); + } } From 5e5bb190d513818ac50318b90f5b26ff247864df Mon Sep 17 00:00:00 2001 From: Dietrich Travkin Date: Tue, 10 Feb 2026 15:46:47 +0100 Subject: [PATCH 02/11] Add field and method icons from JDT, add additional visibility-dependent icons Update and improve some overlay icons --- .../icons/full/obj16/field_file_vis_obj.svg | 522 ++++++++++++++++++ .../icons/full/obj16/field_internal_obj.svg | 444 +++++++++++++++ .../icons/full/obj16/field_package_obj.svg | 422 ++++++++++++++ .../icons/full/obj16/field_private_obj.svg | 470 ++++++++++++++++ .../icons/full/obj16/field_protected_obj.svg | 511 +++++++++++++++++ .../icons/full/obj16/field_public_obj.svg | 460 +++++++++++++++ .../icons/full/obj16/method_file_vis_obj.svg | 515 +++++++++++++++++ .../icons/full/obj16/method_internal_obj.svg | 438 +++++++++++++++ .../icons/full/obj16/method_package_obj.svg | 118 ++++ .../icons/full/obj16/method_private_obj.svg | 139 +++++ .../icons/full/obj16/method_protected_obj.svg | 190 +++++++ .../icons/full/obj16/method_public_obj.svg | 122 ++++ .../icons/full/ovr16/file_visable_co.svg | 117 +++- .../icons/full/ovr16/internal_co.svg | 16 +- .../icons/full/ovr16/package_co.svg | 12 +- .../icons/full/ovr16/protected_co.svg | 15 +- .../icons/full/ovr16/public_co.svg | 12 +- 17 files changed, 4474 insertions(+), 49 deletions(-) create mode 100644 org.eclipse.lsp4e/icons/full/obj16/field_file_vis_obj.svg create mode 100644 org.eclipse.lsp4e/icons/full/obj16/field_internal_obj.svg create mode 100644 org.eclipse.lsp4e/icons/full/obj16/field_package_obj.svg create mode 100644 org.eclipse.lsp4e/icons/full/obj16/field_private_obj.svg create mode 100644 org.eclipse.lsp4e/icons/full/obj16/field_protected_obj.svg create mode 100644 org.eclipse.lsp4e/icons/full/obj16/field_public_obj.svg create mode 100644 org.eclipse.lsp4e/icons/full/obj16/method_file_vis_obj.svg create mode 100644 org.eclipse.lsp4e/icons/full/obj16/method_internal_obj.svg create mode 100644 org.eclipse.lsp4e/icons/full/obj16/method_package_obj.svg create mode 100644 org.eclipse.lsp4e/icons/full/obj16/method_private_obj.svg create mode 100644 org.eclipse.lsp4e/icons/full/obj16/method_protected_obj.svg create mode 100644 org.eclipse.lsp4e/icons/full/obj16/method_public_obj.svg diff --git a/org.eclipse.lsp4e/icons/full/obj16/field_file_vis_obj.svg b/org.eclipse.lsp4e/icons/full/obj16/field_file_vis_obj.svg new file mode 100644 index 000000000..cac739db4 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/obj16/field_file_vis_obj.svg @@ -0,0 +1,522 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/obj16/field_internal_obj.svg b/org.eclipse.lsp4e/icons/full/obj16/field_internal_obj.svg new file mode 100644 index 000000000..0c27a94c1 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/obj16/field_internal_obj.svg @@ -0,0 +1,444 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/obj16/field_package_obj.svg b/org.eclipse.lsp4e/icons/full/obj16/field_package_obj.svg new file mode 100644 index 000000000..03cd8110e --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/obj16/field_package_obj.svg @@ -0,0 +1,422 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/obj16/field_private_obj.svg b/org.eclipse.lsp4e/icons/full/obj16/field_private_obj.svg new file mode 100644 index 000000000..c5d4ab012 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/obj16/field_private_obj.svg @@ -0,0 +1,470 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/obj16/field_protected_obj.svg b/org.eclipse.lsp4e/icons/full/obj16/field_protected_obj.svg new file mode 100644 index 000000000..f939b486b --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/obj16/field_protected_obj.svg @@ -0,0 +1,511 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/obj16/field_public_obj.svg b/org.eclipse.lsp4e/icons/full/obj16/field_public_obj.svg new file mode 100644 index 000000000..71b9836ad --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/obj16/field_public_obj.svg @@ -0,0 +1,460 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/obj16/method_file_vis_obj.svg b/org.eclipse.lsp4e/icons/full/obj16/method_file_vis_obj.svg new file mode 100644 index 000000000..0db1dbbaa --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/obj16/method_file_vis_obj.svg @@ -0,0 +1,515 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/obj16/method_internal_obj.svg b/org.eclipse.lsp4e/icons/full/obj16/method_internal_obj.svg new file mode 100644 index 000000000..63390382d --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/obj16/method_internal_obj.svg @@ -0,0 +1,438 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/obj16/method_package_obj.svg b/org.eclipse.lsp4e/icons/full/obj16/method_package_obj.svg new file mode 100644 index 000000000..2040c9630 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/obj16/method_package_obj.svg @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/obj16/method_private_obj.svg b/org.eclipse.lsp4e/icons/full/obj16/method_private_obj.svg new file mode 100644 index 000000000..f83fdcf3a --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/obj16/method_private_obj.svg @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/obj16/method_protected_obj.svg b/org.eclipse.lsp4e/icons/full/obj16/method_protected_obj.svg new file mode 100644 index 000000000..0a8783f76 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/obj16/method_protected_obj.svg @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/obj16/method_public_obj.svg b/org.eclipse.lsp4e/icons/full/obj16/method_public_obj.svg new file mode 100644 index 000000000..e69941d40 --- /dev/null +++ b/org.eclipse.lsp4e/icons/full/obj16/method_public_obj.svg @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/file_visable_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/file_visable_co.svg index 1f56b59cc..b4724dd51 100644 --- a/org.eclipse.lsp4e/icons/full/ovr16/file_visable_co.svg +++ b/org.eclipse.lsp4e/icons/full/ovr16/file_visable_co.svg @@ -52,16 +52,6 @@ offset="1" id="stop4907-3-6" /> - + + + + + + + + + - + + + + + + + + + + + + diff --git a/org.eclipse.lsp4e/icons/full/ovr16/internal_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/internal_co.svg index c6812a413..3fdc638c0 100644 --- a/org.eclipse.lsp4e/icons/full/ovr16/internal_co.svg +++ b/org.eclipse.lsp4e/icons/full/ovr16/internal_co.svg @@ -55,15 +55,15 @@ inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="39.36" - inkscape:cx="3.8744919" - inkscape:cy="5.3607724" + inkscape:cx="3.4679878" + inkscape:cy="5.3734756" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="true" inkscape:window-width="2560" - inkscape:window-height="1353" + inkscape:window-height="1382" inkscape:window-x="0" - inkscape:window-y="31" + inkscape:window-y="58" inkscape:window-maximized="0" inkscape:showpageshadow="0" inkscape:pagecheckerboard="false" @@ -110,7 +110,7 @@ + transform="matrix(0.57938821,-0.99526724,0.99326492,0.56874019,7.5244052,1047.939)" + inkscape:transform-center-y="0.7675311" + inkscape:transform-center-x="-0.0070277347" /> diff --git a/org.eclipse.lsp4e/icons/full/ovr16/package_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/package_co.svg index dee689a3b..1af42b609 100644 --- a/org.eclipse.lsp4e/icons/full/ovr16/package_co.svg +++ b/org.eclipse.lsp4e/icons/full/ovr16/package_co.svg @@ -56,14 +56,14 @@ inkscape:pageshadow="2" inkscape:zoom="39.36" inkscape:cx="3.8490854" - inkscape:cy="5.4115854" + inkscape:cy="5.4242886" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="true" inkscape:window-width="2560" - inkscape:window-height="1353" + inkscape:window-height="1382" inkscape:window-x="0" - inkscape:window-y="31" + inkscape:window-y="58" inkscape:window-maximized="0" inkscape:showpageshadow="0" inkscape:pagecheckerboard="false" @@ -110,7 +110,7 @@ + transform="matrix(1.1574475,0,0,1.1456082,-1.8755368,1049.2893)" + inkscape:transform-center-y="-0.77287901" /> diff --git a/org.eclipse.lsp4e/icons/full/ovr16/protected_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/protected_co.svg index 61e694bf6..cd68055ed 100644 --- a/org.eclipse.lsp4e/icons/full/ovr16/protected_co.svg +++ b/org.eclipse.lsp4e/icons/full/ovr16/protected_co.svg @@ -95,16 +95,16 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="22.627417" - inkscape:cx="-4.4857086" - inkscape:cy="3.0935922" + inkscape:zoom="36.76" + inkscape:cx="8.0930359" + inkscape:cy="3.2508161" inkscape:document-units="px" inkscape:current-layer="g4953" showgrid="true" inkscape:window-width="2560" - inkscape:window-height="1353" + inkscape:window-height="1382" inkscape:window-x="0" - inkscape:window-y="31" + inkscape:window-y="58" inkscape:window-maximized="0" showguides="true" inkscape:guide-bbox="true" @@ -152,9 +152,10 @@ transform="translate(-13.081475,-10.429825)"> + transform="matrix(0.74287572,0,0,0.74286112,0.82269545,267.82604)" + style="stroke-width:1.34613341;stroke-dasharray:none"> Date: Wed, 11 Feb 2026 14:48:21 +0100 Subject: [PATCH 03/11] Use special symbol icons for fields and methods that show visibility Also Move LabelProvider-related code to new adaptable class and this way make most of the LabelProvider behavior customizable so that LSP-based plug-ins can adapt handling SymbolTags and overlay icons to their concrete language. Simplify API and LSPImage cache key. --- .../lsp4e/test/utils/LSPImagesTest.java | 27 ++ .../{file_visable_co.svg => vis_file_co.svg} | 2 +- .../{internal_co.svg => vis_internal_co.svg} | 0 .../{package_co.svg => vis_package_co.svg} | 0 .../{private_co.svg => vis_private_co.svg} | 0 ...{protected_co.svg => vis_protected_co.svg} | 0 .../{public_co.svg => vis_public_co.svg} | 0 .../CallHierarchyLabelProvider.java | 7 +- .../TypeHierarchyItemLabelProvider.java | 7 +- .../lsp4e/outline/SymbolsLabelProvider.java | 14 +- .../lsp4e/ui/AbstractLsp4eLabelProvider.java | 240 +++++++++++++ .../src/org/eclipse/lsp4e/ui/LSPImages.java | 338 +++++++----------- 12 files changed, 412 insertions(+), 223 deletions(-) rename org.eclipse.lsp4e/icons/full/ovr16/{file_visable_co.svg => vis_file_co.svg} (99%) rename org.eclipse.lsp4e/icons/full/ovr16/{internal_co.svg => vis_internal_co.svg} (100%) rename org.eclipse.lsp4e/icons/full/ovr16/{package_co.svg => vis_package_co.svg} (100%) rename org.eclipse.lsp4e/icons/full/ovr16/{private_co.svg => vis_private_co.svg} (100%) rename org.eclipse.lsp4e/icons/full/ovr16/{protected_co.svg => vis_protected_co.svg} (100%) rename org.eclipse.lsp4e/icons/full/ovr16/{public_co.svg => vis_public_co.svg} (100%) create mode 100644 org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/AbstractLsp4eLabelProvider.java 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 23d806786..577b6740a 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 @@ -13,7 +13,11 @@ import static org.junit.jupiter.api.Assertions.*; +import java.util.List; + +import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.lsp4e.ui.AbstractLsp4eLabelProvider; import org.eclipse.lsp4e.ui.LSPImages; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionItemKind; @@ -43,6 +47,29 @@ public void testAllOverlayImagesForSymbolTagAvailable(SymbolTag tag) { assertNotNull(descriptor); assertNotNull(img); } + + private static class TestLabelProvider extends AbstractLsp4eLabelProvider { + // increase method visibility for the following test + @Override + public @Nullable Image getImageFor(@Nullable SymbolKind symbolKind, @Nullable List symbolTags) { + return super.getImageFor(symbolKind, symbolTags); + } + } + + // Deprecated is used to test the case where no visibility tag is available, that should default to the standard symbol icon + @ParameterizedTest + @EnumSource(value=SymbolTag.class, mode=Mode.INCLUDE, names= { "Private", "Package", "Protected", "Public", + "Internal", "File", "Deprecated"}) + public void testVisibilityOverlayImagesForFieldsAndMethodsAvailable(SymbolTag tag) { + var symbolTags = List.of(tag); + TestLabelProvider labelProvider = new TestLabelProvider(); + + Image fieldImage = labelProvider.getImageFor(SymbolKind.Field, symbolTags); + Image methodImage = labelProvider.getImageFor(SymbolKind.Method, symbolTags); + + assertNotNull(fieldImage); + assertNotNull(methodImage); + } @ParameterizedTest @EnumSource(value=CompletionItemKind.class, mode=Mode.EXCLUDE, names= { "Color", "Event", "Operator" }) diff --git a/org.eclipse.lsp4e/icons/full/ovr16/file_visable_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/vis_file_co.svg similarity index 99% rename from org.eclipse.lsp4e/icons/full/ovr16/file_visable_co.svg rename to org.eclipse.lsp4e/icons/full/ovr16/vis_file_co.svg index b4724dd51..f98b7d046 100644 --- a/org.eclipse.lsp4e/icons/full/ovr16/file_visable_co.svg +++ b/org.eclipse.lsp4e/icons/full/ovr16/vis_file_co.svg @@ -7,7 +7,7 @@ id="svg2" version="1.1" inkscape:version="1.4.2 (ebf0e940, 2025-05-08)" - sodipodi:docname="file_visable_co.svg" + sodipodi:docname="vis_file_co.svg" viewBox="0 0 7 8" inkscape:export-filename="protected_co.png" inkscape:export-xdpi="96" diff --git a/org.eclipse.lsp4e/icons/full/ovr16/internal_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/vis_internal_co.svg similarity index 100% rename from org.eclipse.lsp4e/icons/full/ovr16/internal_co.svg rename to org.eclipse.lsp4e/icons/full/ovr16/vis_internal_co.svg diff --git a/org.eclipse.lsp4e/icons/full/ovr16/package_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/vis_package_co.svg similarity index 100% rename from org.eclipse.lsp4e/icons/full/ovr16/package_co.svg rename to org.eclipse.lsp4e/icons/full/ovr16/vis_package_co.svg diff --git a/org.eclipse.lsp4e/icons/full/ovr16/private_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/vis_private_co.svg similarity index 100% rename from org.eclipse.lsp4e/icons/full/ovr16/private_co.svg rename to org.eclipse.lsp4e/icons/full/ovr16/vis_private_co.svg diff --git a/org.eclipse.lsp4e/icons/full/ovr16/protected_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/vis_protected_co.svg similarity index 100% rename from org.eclipse.lsp4e/icons/full/ovr16/protected_co.svg rename to org.eclipse.lsp4e/icons/full/ovr16/vis_protected_co.svg diff --git a/org.eclipse.lsp4e/icons/full/ovr16/public_co.svg b/org.eclipse.lsp4e/icons/full/ovr16/vis_public_co.svg similarity index 100% rename from org.eclipse.lsp4e/icons/full/ovr16/public_co.svg rename to org.eclipse.lsp4e/icons/full/ovr16/vis_public_co.svg 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 ba671714c..601fd7fe0 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyLabelProvider.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyLabelProvider.java @@ -14,22 +14,21 @@ import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider; -import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.StyledString; -import org.eclipse.lsp4e.ui.LSPImages; +import org.eclipse.lsp4e.ui.AbstractLsp4eLabelProvider; import org.eclipse.lsp4j.CallHierarchyItem; import org.eclipse.swt.graphics.Image; /** * Label provider for the call hierarchy tree view. */ -public class CallHierarchyLabelProvider extends LabelProvider implements IStyledLabelProvider { +public class CallHierarchyLabelProvider extends AbstractLsp4eLabelProvider implements IStyledLabelProvider { @Override public @Nullable Image getImage(final @Nullable Object element) { if (element instanceof CallHierarchyViewTreeNode treeNode) { CallHierarchyItem callContainer = treeNode.getCallContainer(); - Image res = LSPImages.getImageFor(callContainer.getKind(), callContainer.getTags()); + Image res = getImageFor(callContainer.getKind(), callContainer.getTags()); if (res != null) { return res; } 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 c4f4841cd..071ec66e0 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 @@ -10,13 +10,12 @@ import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider; -import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.StyledString; -import org.eclipse.lsp4e.ui.LSPImages; +import org.eclipse.lsp4e.ui.AbstractLsp4eLabelProvider; import org.eclipse.lsp4j.TypeHierarchyItem; import org.eclipse.swt.graphics.Image; -public class TypeHierarchyItemLabelProvider extends LabelProvider implements IStyledLabelProvider { +public class TypeHierarchyItemLabelProvider extends AbstractLsp4eLabelProvider implements IStyledLabelProvider { @Override public String getText(Object element) { @@ -29,7 +28,7 @@ public String getText(Object element) { @Override public @Nullable Image getImage(@Nullable Object element) { if (element instanceof TypeHierarchyItem item) { - return LSPImages.getImageFor(item.getKind(), item.getTags()); + return getImageFor(item.getKind(), item.getTags()); } 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 b45b4174d..058344925 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/SymbolsLabelProvider.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/SymbolsLabelProvider.java @@ -15,6 +15,7 @@ import static org.eclipse.lsp4e.LSPEclipseUtils.*; import java.net.URI; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; @@ -37,7 +38,6 @@ import org.eclipse.jface.text.IDocument; import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider; import org.eclipse.jface.viewers.ILabelProviderListener; -import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.LabelProviderChangedEvent; import org.eclipse.jface.viewers.StyledString; import org.eclipse.lsp4e.LSPEclipseUtils; @@ -45,6 +45,7 @@ import org.eclipse.lsp4e.internal.StyleUtil; import org.eclipse.lsp4e.operations.symbols.SymbolsUtil; import org.eclipse.lsp4e.outline.SymbolsModel.DocumentSymbolWithURI; +import org.eclipse.lsp4e.ui.AbstractLsp4eLabelProvider; import org.eclipse.lsp4e.ui.LSPImages; import org.eclipse.lsp4e.ui.Messages; import org.eclipse.lsp4j.DocumentSymbol; @@ -67,7 +68,7 @@ import com.google.common.collect.RangeMap; import com.google.common.collect.TreeRangeMap; -public class SymbolsLabelProvider extends LabelProvider +public class SymbolsLabelProvider extends AbstractLsp4eLabelProvider implements ICommonLabelProvider, IStyledLabelProvider, IPreferenceChangeListener { private final Map> severities = new HashMap<>(); @@ -153,8 +154,15 @@ public void dispose() { deprecated = SymbolsUtil.isDeprecated(symbolWithURI); } + if (deprecated && !symbolTags.contains(SymbolTag.Deprecated)) { + var adaptedTags = new ArrayList(symbolTags.size() + 1); + adaptedTags.addAll(symbolTags); + adaptedTags.add(SymbolTag.Deprecated); + symbolTags = adaptedTags; + } + if (actualElement != null && symbolKind != null) { - return LSPImages.getImageFor(symbolKind, symbolTags, deprecated, getMaxSeverity(actualElement)); + return getImageFor(symbolKind, symbolTags, getMaxSeverity(actualElement)); } return null; diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/AbstractLsp4eLabelProvider.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/AbstractLsp4eLabelProvider.java new file mode 100644 index 000000000..ecd2358dc --- /dev/null +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/AbstractLsp4eLabelProvider.java @@ -0,0 +1,240 @@ +package org.eclipse.lsp4e.ui; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +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; + +public abstract class AbstractLsp4eLabelProvider extends LabelProvider { + + /** + * Returns an overlay icon {@link ImageDescriptor} for the given severity. + * + * @param severity one of {@link IMarker.SEVERITY_ERROR} or {@link 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); + } + + /** + * 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 List.of( + SymbolTag.Public, SymbolTag.Protected, SymbolTag.Package, + SymbolTag.Internal, SymbolTag.File, SymbolTag.Private); + } + + /** + * 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: + * {@link SymbolTag.Nullable}, {@link SymbolTag.NonNull}, {@link SymbolTag.Declaration}, {@link SymbolTag.Definition} + * + * @return a list of {@link SymbolTag}s without visibility and deprecation tags + */ + protected List getAdditionalTagsPrecedence() { + // 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 + return 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 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(tag -> precedenceList.contains(tag)) + .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(tag -> precedenceList.contains(tag)) + .sorted(Comparator.comparing(precedenceList::indexOf)) + .collect(Collectors.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 get different symbol icons depending on their visibility. + * + * @param kind a document symbol's kind, e.g. field, method, 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) { + 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) + */ + protected @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) + */ + protected @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; + + if (!additionalTags.isEmpty()) { + topLeftOverlayDescriptor = LSPImages.imageDescriptorOverlayFromSymbolTag(additionalTags.get(0)); + + if (additionalTags.size() > 1 && !SymbolKind.Constructor.equals(symbolKind)) { + // constructor base image has a built-in overlay in the top right corner, + // in this case we omit the second symbol tag's overlay icon + topRightOverlayDescriptor = LSPImages.imageDescriptorOverlayFromSymbolTag(additionalTags.get(1)); + } + } + + if (SymbolKind.Field == symbolKind || SymbolKind.Method == symbolKind) { + // In this case 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 { + // 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) + bottomRightOverlayDescriptor = getOverlayForVisibility(finalSymbolTags);; + } + + return LSPImages.getImageWithOverlays(baseImageKey, topLeftOverlayDescriptor, topRightOverlayDescriptor, + bottomLeftOverlayDescriptor, bottomRightOverlayDescriptor, underlayDescriptor); + } + +} 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 67b873553..a8d66cbd7 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/LSPImages.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/LSPImages.java @@ -14,17 +14,11 @@ import java.net.URL; import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; import java.util.stream.Stream; -import org.eclipse.core.resources.IMarker; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; @@ -34,7 +28,6 @@ import org.eclipse.jface.viewers.DecorationOverlayIcon; import org.eclipse.lsp4e.LSPEclipseUtils; import org.eclipse.lsp4e.LanguageServerPlugin; -import org.eclipse.lsp4e.operations.symbols.SymbolsUtil; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.SymbolKind; import org.eclipse.lsp4j.SymbolTag; @@ -64,7 +57,7 @@ private LSPImages() { *
  • Value: the base image with optional overlays and an optional underlay combined in one image.
  • * * - * See {@link #getImageWithOverlays(SymbolKind, ImageDescriptor, ImageDescriptor, ImageDescriptor, ImageDescriptor, ImageDescriptor)}; + * See {@link #getImageWithOverlays(String, ImageDescriptor, ImageDescriptor, ImageDescriptor, ImageDescriptor, ImageDescriptor)} */ private static final Map overlayImagesCache = new HashMap<>(); @@ -81,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$ @@ -144,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$ @@ -175,12 +196,14 @@ public static void initalize(ImageRegistry registry) { 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 + "private_co.svg"); //$NON-NLS-1$ - declareRegistryImage(IMG_OVR_PACKAGE, OVERLAY + "package_co.svg"); //$NON-NLS-1$ - declareRegistryImage(IMG_OVR_PROTECTED, OVERLAY + "protected_co.svg"); //$NON-NLS-1$ - declareRegistryImage(IMG_OVR_PUBLIC, OVERLAY + "public_co.svg"); //$NON-NLS-1$ - declareRegistryImage(IMG_OVR_INTERNAL, OVERLAY + "internal_co.svg"); //$NON-NLS-1$ - declareRegistryImage(IMG_OVR_FILE_VIS, OVERLAY + "file_visable_co.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$ @@ -211,7 +234,7 @@ private static void declareRegistryImage(String key, String path) { getImageRegistry().put(key, desc); } - private record ImageWithOverlaysKey(SymbolKind symbolKind, + private record ImageWithOverlaysKey(String baseImageKey, @Nullable ImageDescriptor overlayTopLeftDescriptor, @Nullable ImageDescriptor overlayTopRightDescriptor, @Nullable ImageDescriptor overlayBottomLeftDescriptor, @Nullable ImageDescriptor overlayBottomRightDescriptor, @Nullable ImageDescriptor underlayDescriptor) {} @@ -261,34 +284,55 @@ public static ImageRegistry getImageRegistry() { } 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); + } + + 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; }; } @@ -324,56 +368,39 @@ public static ImageRegistry getImageRegistry() { } public static @Nullable Image imageOverlayFromSymbolTag(SymbolTag symbolTag) { - return switch (symbolTag) { - case Deprecated -> getImage(IMG_OVR_DEPRECATED); - case Private -> getImage(IMG_OVR_PRIVATE); - case Package -> getImage(IMG_OVR_PACKAGE); - case Protected -> getImage(IMG_OVR_PROTECTED); - case Public -> getImage(IMG_OVR_PUBLIC); - case Internal -> getImage(IMG_OVR_INTERNAL); - case File -> getImage(IMG_OVR_FILE_VIS); - case Static -> getImage(IMG_OVR_STATIC); - case Abstract -> getImage(IMG_OVR_ABSTRACT); - case Final -> getImage(IMG_OVR_FINAL); - case Sealed -> getImage(IMG_OVR_SEALED); - case Transient -> getImage(IMG_OVR_TRANSIENT); - case Volatile -> getImage(IMG_OVR_VOLATILE); - case Synchronized -> getImage(IMG_OVR_SYNC); - case Virtual -> getImage(IMG_OVR_VIRTUAL); - case Nullable -> getImage(IMG_OVR_NULLABLE); - case NonNull -> getImage(IMG_OVR_NON_NULL); - case Declaration -> getImage(IMG_OVR_DECLARATION); - case Definition -> getImage(IMG_OVR_DEFINITION); - case ReadOnly -> getImage(IMG_OVR_READ_ONLY); - case Overrides -> getImage(IMG_OVR_OVERRIDE); - case Implements -> getImage(IMG_OVR_IMPLEMENT); - }; + 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 -> getImageDescriptor(IMG_OVR_DEPRECATED); - case Private -> getImageDescriptor(IMG_OVR_PRIVATE); - case Package -> getImageDescriptor(IMG_OVR_PACKAGE); - case Protected -> getImageDescriptor(IMG_OVR_PROTECTED); - case Public -> getImageDescriptor(IMG_OVR_PUBLIC); - case Internal -> getImageDescriptor(IMG_OVR_INTERNAL); - case File -> getImageDescriptor(IMG_OVR_FILE_VIS); - case Static -> getImageDescriptor(IMG_OVR_STATIC); - case Abstract -> getImageDescriptor(IMG_OVR_ABSTRACT); - case Final -> getImageDescriptor(IMG_OVR_FINAL); - case Sealed -> getImageDescriptor(IMG_OVR_SEALED); - case Transient -> getImageDescriptor(IMG_OVR_TRANSIENT); - case Volatile -> getImageDescriptor(IMG_OVR_VOLATILE); - case Synchronized -> getImageDescriptor(IMG_OVR_SYNC); - case Virtual -> getImageDescriptor(IMG_OVR_VIRTUAL); - case Nullable -> getImageDescriptor(IMG_OVR_NULLABLE); - case NonNull -> getImageDescriptor(IMG_OVR_NON_NULL); - case Declaration -> getImageDescriptor(IMG_OVR_DECLARATION); - case Definition -> getImageDescriptor(IMG_OVR_DEFINITION); - case ReadOnly -> getImageDescriptor(IMG_OVR_READ_ONLY); - case Overrides -> getImageDescriptor(IMG_OVR_OVERRIDE); - case Implements -> getImageDescriptor(IMG_OVR_IMPLEMENT); + 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; }; } @@ -412,67 +439,10 @@ public static ImageRegistry getImageRegistry() { }); } - private static final List VISIBILITY_PRECEDENCE = List.of( - SymbolTag.Public, SymbolTag.Protected, SymbolTag.Package, SymbolTag.Private, - SymbolTag.Internal, SymbolTag.File); - - // Precedence for remaining symbol tags (without visibility tags and deprecation tag) - // 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); - - private static Optional getHighestPrecedenceVisibilitySymbolTag(List symbolTags) { - // TODO Log a warning if we find more than one visibility tag? - return symbolTags.stream() - .filter(tag -> VISIBILITY_PRECEDENCE.contains(tag)) - .min(Comparator.comparing(VISIBILITY_PRECEDENCE::indexOf)); - } - - private static List getAdditionalSymbolTagsSorted(List symbolTags) { - return symbolTags.stream() - .filter(tag -> ADDITIONAL_TAGS_PRECEDENCE.contains(tag)) - .sorted(Comparator.comparing(ADDITIONAL_TAGS_PRECEDENCE::indexOf)) - .collect(Collectors.toList()); - } - - private static @Nullable ImageDescriptor getOverlayForVisibility(List symbolTags) { - Optional visibilityTag = getHighestPrecedenceVisibilitySymbolTag(symbolTags); - - if (visibilityTag.isEmpty()) { - return null; - } - - return LSPImages.imageDescriptorOverlayFromSymbolTag(visibilityTag.get()); - } - - private static @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; - }; - } - - private static @Nullable ImageDescriptor getUnderlayForDeprecation(boolean deprecated) { - if (!deprecated) { - return null; - } - return LSPImages.imageDescriptorOverlayFromSymbolTag(SymbolTag.Deprecated); - } - - private static @Nullable Image getImageWithOverlays(SymbolKind symbolKind, + public static @Nullable Image getImageWithOverlays(String baseImageKey, @Nullable ImageDescriptor topLeftOverlayDescriptor, @Nullable ImageDescriptor topRightOverlayDescriptor, @Nullable ImageDescriptor bottomLeftOverlayDescriptor, @Nullable ImageDescriptor bottomRightOverlayDescriptor, @Nullable ImageDescriptor underlayImageDescriptor) { - Image baseImage = LSPImages.imageFromSymbolKind(symbolKind); - - if (baseImage == null) { - return null; - } // 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 @@ -485,81 +455,27 @@ private static List getAdditionalSymbolTagsSorted(List sym .filter(Objects::nonNull) .count(); if (numOverlays == 0L) { - return baseImage; + return getImage(baseImageKey); } - ImageWithOverlaysKey key = new ImageWithOverlaysKey(symbolKind, + ImageWithOverlaysKey key = new ImageWithOverlaysKey(baseImageKey, topLeftOverlayDescriptor, topRightOverlayDescriptor, bottomLeftOverlayDescriptor, bottomRightOverlayDescriptor, underlayImageDescriptor); - return overlayImagesCache.computeIfAbsent(key, - k -> new DecorationOverlayIcon(baseImage, overlays).createImage()); - } - - /** - * 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, boolean, int) - */ - public static @Nullable Image getImageFor(@Nullable SymbolKind symbolKind, @Nullable List symbolTags) { - return getImageFor(symbolKind, symbolTags, false, -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 deprecated whether to add a deprecation overlay icon even if there is no {@link SymbolTag#Deprecated} in the 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. - */ - public static @Nullable Image getImageFor(@Nullable SymbolKind symbolKind, @Nullable List symbolTags, - boolean deprecated, int severity) { - - if (symbolKind == null) { - return EMPTY_IMAGE; + if (overlayImagesCache.containsKey(key)) { + return overlayImagesCache.get(key); } - if (symbolTags == null) { - symbolTags = Collections.emptyList(); + final Image baseImage = getImage(baseImageKey); + if (baseImage == null) { + // Do not create cache entries for non existing base images + return null; } - ImageDescriptor severityImageDescriptor = getOverlayForMarkerSeverity(severity); - ImageDescriptor visibilityImageDescriptor = getOverlayForVisibility(symbolTags); - ImageDescriptor deprecatedImageDescriptor = getUnderlayForDeprecation(deprecated || SymbolsUtil.isDeprecated(symbolTags)); - - List additionalTags = getAdditionalSymbolTagsSorted(symbolTags); - - // 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) - ImageDescriptor topLeftOverlayDescriptor = null; - ImageDescriptor topRightOverlayDescriptor = null; - ImageDescriptor bottomLeftOverlayDescriptor = severityImageDescriptor; - ImageDescriptor bottomRightOverlayDescriptor = visibilityImageDescriptor; - ImageDescriptor underlayDescriptor = deprecatedImageDescriptor; - - // TODO Use visibility-representing document symbol icons for fields and methods (similar to JDT) so that we can visualize one more symbol tag - - if (!additionalTags.isEmpty()) { - topLeftOverlayDescriptor = LSPImages.imageDescriptorOverlayFromSymbolTag(additionalTags.get(0)); - - if (additionalTags.size() > 1 && !SymbolKind.Constructor.equals(symbolKind)) { - // constructor base image has a built-in overlay in the top right corner, - // in this case we omit the second symbol tag's overlay icon - topRightOverlayDescriptor = LSPImages.imageDescriptorOverlayFromSymbolTag(additionalTags.get(1)); - } - } + Image imageWithOverlays = new DecorationOverlayIcon(baseImage, overlays).createImage(); + overlayImagesCache.put(key, imageWithOverlays); - return getImageWithOverlays(symbolKind, topLeftOverlayDescriptor, topRightOverlayDescriptor, - bottomLeftOverlayDescriptor, bottomRightOverlayDescriptor, underlayDescriptor); + return imageWithOverlays; } public static final void dispose() { From 75ecaf7ee3201f270032791822abb60b53e8ec56 Mon Sep 17 00:00:00 2001 From: Dietrich Travkin Date: Thu, 12 Feb 2026 11:21:45 +0100 Subject: [PATCH 04/11] Bump versions --- org.eclipse.lsp4e.test/META-INF/MANIFEST.MF | 2 +- org.eclipse.lsp4e.test/pom.xml | 2 +- org.eclipse.lsp4e/META-INF/MANIFEST.MF | 2 +- org.eclipse.lsp4e/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/org.eclipse.lsp4e.test/META-INF/MANIFEST.MF b/org.eclipse.lsp4e.test/META-INF/MANIFEST.MF index 8962a6a6a..7367814e1 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.7.qualifier +Bundle-Version: 0.16.8.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 d95907fcd..1dbcfaff1 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.7-SNAPSHOT + 0.16.8-SNAPSHOT diff --git a/org.eclipse.lsp4e/META-INF/MANIFEST.MF b/org.eclipse.lsp4e/META-INF/MANIFEST.MF index ffe2dbf57..494912d00 100644 --- a/org.eclipse.lsp4e/META-INF/MANIFEST.MF +++ b/org.eclipse.lsp4e/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Language Server Protocol client for Eclipse IDE (Incubation) Bundle-SymbolicName: org.eclipse.lsp4e;singleton:=true -Bundle-Version: 0.19.7.qualifier +Bundle-Version: 0.19.8.qualifier Bundle-RequiredExecutionEnvironment: JavaSE-21 Require-Bundle: org.eclipse.core.runtime;bundle-version="3.12.0", org.eclipse.equinox.common;bundle-version="3.8.0", diff --git a/org.eclipse.lsp4e/pom.xml b/org.eclipse.lsp4e/pom.xml index e379bb9ca..8868cfff3 100644 --- a/org.eclipse.lsp4e/pom.xml +++ b/org.eclipse.lsp4e/pom.xml @@ -10,7 +10,7 @@ org.eclipse.lsp4e eclipse-plugin - 0.19.7-SNAPSHOT + 0.19.8-SNAPSHOT From 0bf949db565fa281d143ee8d52e6068aad71f665 Mon Sep 17 00:00:00 2001 From: Dietrich Travkin Date: Thu, 12 Feb 2026 12:31:28 +0100 Subject: [PATCH 05/11] Add missing convenience methods --- .../org/eclipse/lsp4e/operations/symbols/SymbolsUtil.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/symbols/SymbolsUtil.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/symbols/SymbolsUtil.java index 2c2b6c467..2dc99295a 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/symbols/SymbolsUtil.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/symbols/SymbolsUtil.java @@ -185,4 +185,12 @@ public static boolean isReadOnly(List tags) { return SymbolsUtil.hasSymbolTag(tags, SymbolTag.ReadOnly); } + public static boolean isOverrides(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.Overrides); + } + + public static boolean isImplements(List tags) { + return SymbolsUtil.hasSymbolTag(tags, SymbolTag.Implements); + } + } From f6c97c476bc567ec514826b7569e2ec12fc7b969 Mon Sep 17 00:00:00 2001 From: Dietrich Travkin Date: Thu, 12 Feb 2026 12:32:04 +0100 Subject: [PATCH 06/11] Add copyrights header and fix some javadoc comments --- .../lsp4e/ui/AbstractLsp4eLabelProvider.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/AbstractLsp4eLabelProvider.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/AbstractLsp4eLabelProvider.java index ecd2358dc..3c3fa5d0e 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/AbstractLsp4eLabelProvider.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/AbstractLsp4eLabelProvider.java @@ -1,3 +1,14 @@ +/******************************************************************************* + * 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; @@ -21,7 +32,7 @@ public abstract class AbstractLsp4eLabelProvider extends LabelProvider { /** * Returns an overlay icon {@link ImageDescriptor} for the given severity. * - * @param severity one of {@link IMarker.SEVERITY_ERROR} or {@link IMarker.SEVERITY_WARNING} + * @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) { @@ -61,7 +72,8 @@ protected List getVisibilityPrecedence() { * 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: - * {@link SymbolTag.Nullable}, {@link SymbolTag.NonNull}, {@link SymbolTag.Declaration}, {@link SymbolTag.Definition} + * SymbolTag.Nullable, SymbolTag.NonNull, + * SymbolTag.Declaration, SymbolTag.Definition * * @return a list of {@link SymbolTag}s without visibility and deprecation tags */ From 84c079225e7f2eaede11d7b2e8f6a32097007f57 Mon Sep 17 00:00:00 2001 From: Dietrich Travkin Date: Thu, 12 Feb 2026 13:03:31 +0100 Subject: [PATCH 07/11] Add some javadoc comments --- .../src/org/eclipse/lsp4e/ui/LSPImages.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) 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 a8d66cbd7..89bd80efc 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/LSPImages.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/LSPImages.java @@ -283,6 +283,18 @@ 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 #getImageDescriptor(String) + * @see AbstractLsp4eLabelProvider#getImage(Object) + * @see AbstractLsp4eLabelProvider#getImageFor(SymbolKind, java.util.List) + * @see AbstractLsp4eLabelProvider#getImageFor(SymbolKind, java.util.List, int) + */ public static @Nullable Image imageFromSymbolKind(@Nullable SymbolKind kind) { if (kind == null) { return EMPTY_IMAGE; @@ -305,6 +317,18 @@ public static ImageRegistry getImageRegistry() { 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) + */ public static String imageKeyFromSymbolKind(SymbolKind kind) { return switch (kind) { case Array -> IMG_ARRAY; @@ -367,6 +391,15 @@ public static String imageKeyFromSymbolKind(SymbolKind kind) { }; } + /** + * 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); @@ -439,6 +472,20 @@ private static String imageOverlayKeyFromSymbolTag(SymbolTag symbolTag) { }); } + /** + * 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) + */ public static @Nullable Image getImageWithOverlays(String baseImageKey, @Nullable ImageDescriptor topLeftOverlayDescriptor, @Nullable ImageDescriptor topRightOverlayDescriptor, @Nullable ImageDescriptor bottomLeftOverlayDescriptor, @Nullable ImageDescriptor bottomRightOverlayDescriptor, From 878d3958e632a8bb51d37df915a9f5c928980234 Mon Sep 17 00:00:00 2001 From: Dietrich Travkin Date: Thu, 12 Feb 2026 15:04:59 +0100 Subject: [PATCH 08/11] Show constructors' visibility similar to methods --- .../lsp4e/ui/AbstractLsp4eLabelProvider.java | 32 +++++++++++++------ .../src/org/eclipse/lsp4e/ui/LSPImages.java | 2 ++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/AbstractLsp4eLabelProvider.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/AbstractLsp4eLabelProvider.java index 3c3fa5d0e..74bd5b9c6 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/AbstractLsp4eLabelProvider.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/AbstractLsp4eLabelProvider.java @@ -132,9 +132,9 @@ protected final List getAdditionalSymbolTagsSorted(List sy /** * 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 get different symbol icons depending on their visibility. + * 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, class, property + * @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 * @@ -161,7 +161,7 @@ protected String getImageKeyFromSymbolKindWithVisibility(SymbolKind kind, List LSPImages.IMG_FIELD_VIS_FILE; default -> LSPImages.IMG_FIELD; }; - } else if (kind == SymbolKind.Method) { + } 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; @@ -224,25 +224,37 @@ 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))) { + topRightOverlayDescriptor = LSPImages.getImageDescriptor(LSPImages.IMG_OVR_CONSTRUCTOR); + } + if (!additionalTags.isEmpty()) { topLeftOverlayDescriptor = LSPImages.imageDescriptorOverlayFromSymbolTag(additionalTags.get(0)); - if (additionalTags.size() > 1 && !SymbolKind.Constructor.equals(symbolKind)) { - // constructor base image has a built-in overlay in the top right corner, - // in this case we omit the second symbol tag's overlay icon - topRightOverlayDescriptor = LSPImages.imageDescriptorOverlayFromSymbolTag(additionalTags.get(1)); + 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 this case the visibility is already expressed by the symbol icon, so we can display one more symbol tag + // 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 { + } 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) - bottomRightOverlayDescriptor = getOverlayForVisibility(finalSymbolTags);; + // 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, 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 89bd80efc..6927438ff 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/LSPImages.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/LSPImages.java @@ -328,6 +328,7 @@ public static ImageRegistry getImageRegistry() { * @see #getImage(String) * @see #getImageDescriptor(String) * @see #getImageWithOverlays(String, ImageDescriptor, ImageDescriptor, ImageDescriptor, ImageDescriptor, ImageDescriptor) + * @see AbstractLsp4eLabelProvider#getImageKeyFromSymbolKindWithVisibility(SymbolKind, java.util.List) */ public static String imageKeyFromSymbolKind(SymbolKind kind) { return switch (kind) { @@ -485,6 +486,7 @@ private static String imageOverlayKeyFromSymbolTag(SymbolTag symbolTag) { * @return returns a new or cached image built from the given arguments. * * @see #imageKeyFromSymbolKind(SymbolKind) + * @see AbstractLsp4eLabelProvider#getImageKeyFromSymbolKindWithVisibility(SymbolKind, java.util.List) */ public static @Nullable Image getImageWithOverlays(String baseImageKey, @Nullable ImageDescriptor topLeftOverlayDescriptor, @Nullable ImageDescriptor topRightOverlayDescriptor, From 4a9779c25163a531c48763fd436ae995b986f60f Mon Sep 17 00:00:00 2001 From: Dietrich Travkin Date: Tue, 17 Feb 2026 10:12:41 +0100 Subject: [PATCH 09/11] Extract precedence lists into static variables --- .../lsp4e/ui/AbstractLsp4eLabelProvider.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/AbstractLsp4eLabelProvider.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/AbstractLsp4eLabelProvider.java index 74bd5b9c6..fcefdecc8 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/AbstractLsp4eLabelProvider.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/AbstractLsp4eLabelProvider.java @@ -56,6 +56,10 @@ public abstract class AbstractLsp4eLabelProvider extends LabelProvider { 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. @@ -63,11 +67,17 @@ public abstract class AbstractLsp4eLabelProvider extends LabelProvider { * @return a list of visibility {@link SymbolTag}s */ protected List getVisibilityPrecedence() { - return List.of( - SymbolTag.Public, SymbolTag.Protected, SymbolTag.Package, - SymbolTag.Internal, SymbolTag.File, SymbolTag.Private); + 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. @@ -78,13 +88,7 @@ protected List getVisibilityPrecedence() { * @return a list of {@link SymbolTag}s without visibility and deprecation tags */ protected List getAdditionalTagsPrecedence() { - // 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 - return List.of( - SymbolTag.Static, SymbolTag.Final, SymbolTag.Abstract, - SymbolTag.Overrides, SymbolTag.Implements, SymbolTag.Virtual, SymbolTag.Sealed, - SymbolTag.Synchronized, SymbolTag.Transient, SymbolTag.Volatile, - SymbolTag.ReadOnly); + return ADDITIONAL_TAGS_PRECEDENCE; } /** From a9e81f00938ad9ca634be42162907884aa3b4403 Mon Sep 17 00:00:00 2001 From: Dietrich Travkin Date: Tue, 17 Feb 2026 17:12:07 +0100 Subject: [PATCH 10/11] Replace inheritance with composition Move common label provider methods for determining document symbol images to a customizable, but non-abstract, non-LabelProvider class that is used by LabelProviders through composition instead of inheritance. --- .../eclipse/lsp4e/test/utils/LSPImagesTest.java | 15 +++------------ .../CallHierarchyLabelProvider.java | 17 ++++++++++++++--- .../TypeHierarchyItemLabelProvider.java | 17 ++++++++++++++--- .../lsp4e/outline/SymbolsLabelProvider.java | 16 ++++++++++++---- .../src/org/eclipse/lsp4e/ui/LSPImages.java | 10 +++++----- ...belProvider.java => SymbolIconProvider.java} | 11 ++++++++--- 6 files changed, 56 insertions(+), 30 deletions(-) rename org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/{AbstractLsp4eLabelProvider.java => SymbolIconProvider.java} (96%) 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 577b6740a..369aeaafe 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 @@ -11,14 +11,13 @@ *******************************************************************************/ package org.eclipse.lsp4e.test.utils; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.util.List; -import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.lsp4e.ui.AbstractLsp4eLabelProvider; import org.eclipse.lsp4e.ui.LSPImages; +import org.eclipse.lsp4e.ui.SymbolIconProvider; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionItemKind; import org.eclipse.lsp4j.SymbolKind; @@ -48,21 +47,13 @@ public void testAllOverlayImagesForSymbolTagAvailable(SymbolTag tag) { assertNotNull(img); } - private static class TestLabelProvider extends AbstractLsp4eLabelProvider { - // increase method visibility for the following test - @Override - public @Nullable Image getImageFor(@Nullable SymbolKind symbolKind, @Nullable List symbolTags) { - return super.getImageFor(symbolKind, symbolTags); - } - } - // Deprecated is used to test the case where no visibility tag is available, that should default to the standard symbol icon @ParameterizedTest @EnumSource(value=SymbolTag.class, mode=Mode.INCLUDE, names= { "Private", "Package", "Protected", "Public", "Internal", "File", "Deprecated"}) public void testVisibilityOverlayImagesForFieldsAndMethodsAvailable(SymbolTag tag) { var symbolTags = List.of(tag); - TestLabelProvider labelProvider = new TestLabelProvider(); + SymbolIconProvider labelProvider = new SymbolIconProvider(); Image fieldImage = labelProvider.getImageFor(SymbolKind.Field, symbolTags); Image methodImage = labelProvider.getImageFor(SymbolKind.Method, symbolTags); 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 601fd7fe0..809a2ed88 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyLabelProvider.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/callhierarchy/CallHierarchyLabelProvider.java @@ -14,21 +14,32 @@ import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider; +import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.StyledString; -import org.eclipse.lsp4e.ui.AbstractLsp4eLabelProvider; +import org.eclipse.lsp4e.ui.SymbolIconProvider; import org.eclipse.lsp4j.CallHierarchyItem; import org.eclipse.swt.graphics.Image; /** * Label provider for the call hierarchy tree view. */ -public class CallHierarchyLabelProvider extends AbstractLsp4eLabelProvider implements IStyledLabelProvider { +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 = getImageFor(callContainer.getKind(), callContainer.getTags()); + Image res = symbolIconProvider.getImageFor(callContainer.getKind(), callContainer.getTags()); if (res != null) { return res; } 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 071ec66e0..4d426704e 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 @@ -10,12 +10,23 @@ import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider; +import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.StyledString; -import org.eclipse.lsp4e.ui.AbstractLsp4eLabelProvider; +import org.eclipse.lsp4e.ui.SymbolIconProvider; import org.eclipse.lsp4j.TypeHierarchyItem; import org.eclipse.swt.graphics.Image; -public class TypeHierarchyItemLabelProvider extends AbstractLsp4eLabelProvider implements IStyledLabelProvider { +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) { @@ -28,7 +39,7 @@ public String getText(Object element) { @Override public @Nullable Image getImage(@Nullable Object element) { if (element instanceof TypeHierarchyItem item) { - return getImageFor(item.getKind(), item.getTags()); + return symbolIconProvider.getImageFor(item.getKind(), item.getTags()); } 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 058344925..e981fc337 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/SymbolsLabelProvider.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/outline/SymbolsLabelProvider.java @@ -12,7 +12,7 @@ *******************************************************************************/ package org.eclipse.lsp4e.outline; -import static org.eclipse.lsp4e.LSPEclipseUtils.*; +import static org.eclipse.lsp4e.LSPEclipseUtils.findResourceFor; import java.net.URI; import java.util.ArrayList; @@ -38,6 +38,7 @@ import org.eclipse.jface.text.IDocument; import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider; import org.eclipse.jface.viewers.ILabelProviderListener; +import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.LabelProviderChangedEvent; import org.eclipse.jface.viewers.StyledString; import org.eclipse.lsp4e.LSPEclipseUtils; @@ -45,9 +46,9 @@ import org.eclipse.lsp4e.internal.StyleUtil; import org.eclipse.lsp4e.operations.symbols.SymbolsUtil; import org.eclipse.lsp4e.outline.SymbolsModel.DocumentSymbolWithURI; -import org.eclipse.lsp4e.ui.AbstractLsp4eLabelProvider; import org.eclipse.lsp4e.ui.LSPImages; import org.eclipse.lsp4e.ui.Messages; +import org.eclipse.lsp4e.ui.SymbolIconProvider; import org.eclipse.lsp4j.DocumentSymbol; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.Range; @@ -68,9 +69,11 @@ import com.google.common.collect.RangeMap; import com.google.common.collect.TreeRangeMap; -public class SymbolsLabelProvider extends AbstractLsp4eLabelProvider +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 { @@ -100,8 +103,13 @@ 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); } @@ -162,7 +170,7 @@ public void dispose() { } if (actualElement != null && symbolKind != null) { - return getImageFor(symbolKind, symbolTags, getMaxSeverity(actualElement)); + return symbolIconProvider.getImageFor(symbolKind, symbolTags, getMaxSeverity(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 6927438ff..df3106af0 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/LSPImages.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/LSPImages.java @@ -290,10 +290,10 @@ 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 #getImageDescriptor(String) - * @see AbstractLsp4eLabelProvider#getImage(Object) - * @see AbstractLsp4eLabelProvider#getImageFor(SymbolKind, java.util.List) - * @see AbstractLsp4eLabelProvider#getImageFor(SymbolKind, java.util.List, int) + * @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) { @@ -328,7 +328,7 @@ public static ImageRegistry getImageRegistry() { * @see #getImage(String) * @see #getImageDescriptor(String) * @see #getImageWithOverlays(String, ImageDescriptor, ImageDescriptor, ImageDescriptor, ImageDescriptor, ImageDescriptor) - * @see AbstractLsp4eLabelProvider#getImageKeyFromSymbolKindWithVisibility(SymbolKind, java.util.List) + * @see SymbolIconProvider#getImageKeyFromSymbolKindWithVisibility(SymbolKind, java.util.List) */ public static String imageKeyFromSymbolKind(SymbolKind kind) { return switch (kind) { @@ -486,7 +486,7 @@ private static String imageOverlayKeyFromSymbolTag(SymbolTag symbolTag) { * @return returns a new or cached image built from the given arguments. * * @see #imageKeyFromSymbolKind(SymbolKind) - * @see AbstractLsp4eLabelProvider#getImageKeyFromSymbolKindWithVisibility(SymbolKind, java.util.List) + * @see SymbolIconProvider#getImageKeyFromSymbolKindWithVisibility(SymbolKind, java.util.List) */ public static @Nullable Image getImageWithOverlays(String baseImageKey, @Nullable ImageDescriptor topLeftOverlayDescriptor, @Nullable ImageDescriptor topRightOverlayDescriptor, diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/AbstractLsp4eLabelProvider.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/SymbolIconProvider.java similarity index 96% rename from org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/AbstractLsp4eLabelProvider.java rename to org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/SymbolIconProvider.java index fcefdecc8..5293d4c1a 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/AbstractLsp4eLabelProvider.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/SymbolIconProvider.java @@ -27,7 +27,12 @@ import org.eclipse.swt.graphics.Image; import org.eclipse.ui.ISharedImages; -public abstract class AbstractLsp4eLabelProvider extends LabelProvider { + +/** + * 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. @@ -189,7 +194,7 @@ protected String getImageKeyFromSymbolKindWithVisibility(SymbolKind kind, List symbolTags) { + public @Nullable Image getImageFor(@Nullable SymbolKind symbolKind, @Nullable List symbolTags) { return getImageFor(symbolKind, symbolTags, -1); } @@ -206,7 +211,7 @@ protected String getImageKeyFromSymbolKindWithVisibility(SymbolKind kind, List symbolTags, int severity) { if (symbolKind == null) { From 2c634b369eb3eb733d095b4ec9c628777872efc3 Mon Sep 17 00:00:00 2001 From: Dietrich Travkin Date: Tue, 17 Feb 2026 17:18:58 +0100 Subject: [PATCH 11/11] Minor code improvements --- .../src/org/eclipse/lsp4e/ui/SymbolIconProvider.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) 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 5293d4c1a..c675253ce 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/SymbolIconProvider.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/SymbolIconProvider.java @@ -15,7 +15,6 @@ import java.util.Comparator; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import org.eclipse.core.resources.IMarker; import org.eclipse.jdt.annotation.Nullable; @@ -63,7 +62,7 @@ public class SymbolIconProvider { private static final List VISIBILITY_PRECEDENCE = List.of( SymbolTag.Public, SymbolTag.Protected, SymbolTag.Package, - SymbolTag.Internal, SymbolTag.File, SymbolTag.Private);; + SymbolTag.Internal, SymbolTag.File, SymbolTag.Private); /** * Returns a list of visibility {@link SymbolTag}s with decreasing precedence. @@ -107,7 +106,7 @@ protected List getAdditionalTagsPrecedence() { protected final Optional getHighestPrecedenceVisibilitySymbolTag(List symbolTags) { final var precedenceList = getVisibilityPrecedence(); return symbolTags.stream() - .filter(tag -> precedenceList.contains(tag)) + .filter(precedenceList::contains) .min(Comparator.comparing(precedenceList::indexOf)); } @@ -123,9 +122,9 @@ protected final Optional getHighestPrecedenceVisibilitySymbolTag(List protected final List getAdditionalSymbolTagsSorted(List symbolTags) { final var precedenceList = getAdditionalTagsPrecedence(); return symbolTags.stream() - .filter(tag -> precedenceList.contains(tag)) + .filter(precedenceList::contains) .sorted(Comparator.comparing(precedenceList::indexOf)) - .collect(Collectors.toList()); + .toList(); } private @Nullable ImageDescriptor getOverlayForVisibility(List symbolTags) {