Repository: sublimelsp/LSP

Issue:
Look into detailedLabelSupport
**Is your feature request related to a problem? Please describe.**
The upcoming 3.17 spec allows servers to provide a more detailed completion label:

```typescript
export interface CompletionItemLabel {
	/**
	 * The name of a function or variable.
	 */
	name: string;

	/**
	 * The parameters without the return type.
	 */
	parameters?: string;

	/**
	 * The fully qualified name, like package name or file path.
	 */
	qualifier?: string;

	/**
	 * The return-type of a function or type of a property/variable.
	 */
	type?: string;
}
```

**Describe the solution you'd like**
Because ST4 allows us to use a limited form of minihtml in the `details` slot, it should be possible to render this additional information with some coloring.

**Describe alternatives you've considered**
N/A

**Additional context**
See the upcoming 3.17 spec: https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/



Discussion:
related https://github.com/microsoft/language-server-protocol/issues/1236#issue-848567478

Resolving patch:
diff --git a/plugin/core/protocol.py b/plugin/core/protocol.py
--- a/plugin/core/protocol.py
+++ b/plugin/core/protocol.py
@@ -181,6 +181,11 @@ class InsertTextMode:
     'range': RangeLsp
 }, total=True)
 
+CompletionItemLabelDetails = TypedDict('CompletionItemLabelDetails', {
+    'detail': str,
+    'description': str
+}, total=False)
+
 CompletionItem = TypedDict('CompletionItem', {
     'additionalTextEdits': List[TextEdit],
     'command': Command,
@@ -195,6 +200,7 @@ class InsertTextMode:
     'insertTextMode': InsertTextMode,
     'kind': int,
     'label': str,
+    'labelDetails': CompletionItemLabelDetails,
     'preselect': bool,
     'sortText': str,
     'tags': List[int],
diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py
--- a/plugin/core/sessions.py
+++ b/plugin/core/sessions.py
@@ -178,7 +178,8 @@ def get_initialize_params(variables: Dict[str, str], workspace_folders: List[Wor
                 },
                 "resolveSupport": {
                     "properties": ["detail", "documentation", "additionalTextEdits"]
-                }
+                },
+                "labelDetailsSupport": True
             },
             "completionItemKind": {
                 "valueSet": completion_kinds
diff --git a/plugin/core/views.py b/plugin/core/views.py
--- a/plugin/core/views.py
+++ b/plugin/core/views.py
@@ -777,6 +777,10 @@ def _is_completion_item_deprecated(item: CompletionItem) -> bool:
     return False
 
 
+def _wrap_in_tags(tag: str, item: str) -> str:
+    return "<{0}>{1}</{0}>".format(tag, html.escape(item))
+
+
 def format_completion(
     item: CompletionItem, index: int, can_resolve_completion_items: bool, session_name: str
 ) -> sublime.CompletionItem:
@@ -791,23 +795,37 @@ def format_completion(
         kind = (kind[0], '⚠', "⚠ {} - Deprecated".format(kind[2]))
 
     lsp_label = item["label"]
+    lsp_label_details = item.get("labelDetails")
     lsp_filter_text = item.get("filterText")
     st_annotation = (item.get("detail") or "").replace('\n', ' ')
 
     st_details = ""
     if can_resolve_completion_items or item.get("documentation"):
         st_details += make_command_link("lsp_resolve_docs", "More", {"index": index, "session_name": session_name})
-
-    if lsp_filter_text and lsp_filter_text != lsp_label:
-        st_trigger = lsp_filter_text
+    if lsp_label_details:
         if st_details:
             st_details += " | "
-        st_details += "<p>{}</p>".format(html.escape(lsp_label))
-    else:
-        st_trigger = lsp_label
+        lsp_label_detail = lsp_label_details.get("detail")
+        lsp_label_description = lsp_label_details.get("description")
+        st_details += "<p>"
+        # `label` should be rendered most prominent.
+        st_details += _wrap_in_tags("b", lsp_label)
+        if isinstance(lsp_label_detail, str):
+            # `detail` should be rendered less prominent than `label`.
+            # The string is appended directly after `label`, with no additional white space applied.
+            st_details += html.escape(lsp_label_detail)
+        if isinstance(lsp_label_description, str):
+            # `description` should be rendered less prominent than `detail`.
+            # Additional separation is added.
+            st_details += " - {}".format(_wrap_in_tags("i", lsp_label_description))
+        st_details += "</p>"
+    elif lsp_filter_text and lsp_filter_text != lsp_label:
+        if st_details:
+            st_details += " | "
+        st_details += _wrap_in_tags("p", lsp_label)
 
     completion = sublime.CompletionItem.command_completion(
-        trigger=st_trigger,
+        trigger=lsp_filter_text or lsp_label,
         command="lsp_select_completion_item",
         args={"item": item, "session_name": session_name},
         annotation=st_annotation,


Test patch:
diff --git a/tests/test_completion.py b/tests/test_completion.py
--- a/tests/test_completion.py
+++ b/tests/test_completion.py
@@ -1,7 +1,8 @@
 from copy import deepcopy
 from LSP.plugin.core.protocol import CompletionItem
+from LSP.plugin.core.protocol import CompletionItemLabelDetails
 from LSP.plugin.core.protocol import CompletionItemTag
-from LSP.plugin.core.typing import Any, Generator, List, Dict, Callable
+from LSP.plugin.core.typing import Any, Generator, List, Dict, Callable, Optional
 from LSP.plugin.core.views import format_completion
 from setup import TextDocumentTestCase
 import sublime
@@ -628,6 +629,108 @@ def test_strips_carriage_return_in_text_edit(self) -> 'Generator':
             insert_text='',
             expected_text='hello\nworld')
 
+    def test_label_details_with_filter_text(self) -> None:
+
+        def check(
+            resolve_support: bool,
+            expected_regex: str,
+            label: str,
+            label_details: Optional[CompletionItemLabelDetails]
+        ) -> None:
+            lsp = {"label": label, "filterText": "force_label_to_go_into_st_detail_field"}  # type: CompletionItem
+            if label_details is not None:
+                lsp["labelDetails"] = label_details
+            native = format_completion(lsp, 0, resolve_support, "")
+            self.assertRegex(native.details, expected_regex)
+
+        check(
+            resolve_support=False,
+            expected_regex=r"^<p>f</p>$",
+            label="f",
+            label_details=None
+        )
+        check(
+            resolve_support=False,
+            expected_regex=r"^<p><b>f</b>\(X&amp; x\)</p>$",
+            label="f",
+            label_details={"detail": "(X& x)"}
+        )
+        check(
+            resolve_support=False,
+            expected_regex=r"^<p><b>f</b>\(X&amp; x\) - <i>does things</i></p>$",
+            label="f",
+            label_details={"detail": "(X& x)", "description": "does things"}
+        )
+        check(
+            resolve_support=True,
+            expected_regex=r"^<a href='subl:lsp_resolve_docs {\S+}'>More</a> \| <p>f</p>$",
+            label="f",
+            label_details=None
+        )
+        check(
+            resolve_support=True,
+            expected_regex=r"^<a href='subl:lsp_resolve_docs {\S+}'>More</a> \| <p><b>f</b>\(X&amp; x\)</p>$",
+            label="f",
+            label_details={"detail": "(X& x)"}
+        )
+        check(
+            resolve_support=True,
+            expected_regex=r"^<a href='subl:lsp_resolve_docs {\S+}'>More</a> \| <p><b>f</b>\(X&amp; x\) - <i>does things</i></p>$",  # noqa: E501
+            label="f",
+            label_details={"detail": "(X& x)", "description": "does things"}
+        )
+
+    def test_label_details_without_filter_text(self) -> None:
+
+        def check(
+            resolve_support: bool,
+            expected_regex: str,
+            label: str,
+            label_details: Optional[CompletionItemLabelDetails]
+        ) -> None:
+            lsp = {"label": label}  # type: CompletionItem
+            if label_details is not None:
+                lsp["labelDetails"] = label_details
+            native = format_completion(lsp, 0, resolve_support, "")
+            self.assertRegex(native.details, expected_regex)
+
+        check(
+            resolve_support=False,
+            expected_regex=r"^$",
+            label="f",
+            label_details=None
+        )
+        check(
+            resolve_support=False,
+            expected_regex=r"^<p><b>f</b>\(X&amp; x\)</p>$",
+            label="f",
+            label_details={"detail": "(X& x)"}
+        )
+        check(
+            resolve_support=False,
+            expected_regex=r"^<p><b>f</b>\(X&amp; x\) - <i>does things</i></p>$",
+            label="f",
+            label_details={"detail": "(X& x)", "description": "does things"}
+        )
+        check(
+            resolve_support=True,
+            expected_regex=r"^<a href='subl:lsp_resolve_docs {\S+}'>More</a>$",
+            label="f",
+            label_details=None
+        )
+        check(
+            resolve_support=True,
+            expected_regex=r"^<a href='subl:lsp_resolve_docs {\S+}'>More</a> \| <p><b>f</b>\(X&amp; x\)</p>$",
+            label="f",
+            label_details={"detail": "(X& x)"}
+        )
+        check(
+            resolve_support=True,
+            expected_regex=r"^<a href='subl:lsp_resolve_docs {\S+}'>More</a> \| <p><b>f</b>\(X&amp; x\) - <i>does things</i></p>$",  # noqa: E501
+            label="f",
+            label_details={"detail": "(X& x)", "description": "does things"}
+        )
+
 
 class QueryCompletionsNoResolverTests(CompletionsTestsBase):
     '''