Repository: sublimelsp/LSP

Issue:
When a server returns SymbolInformation[] in textDocument/documentSymbol, show the containerName
Document symbols can and should support the containerName in the UI.

https://microsoft.github.io/language-server-protocol/specifications/specification-3-15/#textDocument_documentSymbol

`/**
	The name of the symbol containing this symbol. This information is for
	 user interface purposes (e.g. to render a qualifier in the user interface
	 if necessary). It can't be used to re-infer a hierarchy for the document
	 symbols.
	 */
	containerName?: string;`

For example something like this would be helpful to give context to the symbol without affecting the filtering behavior. 

![Screen_Shot_2020-06-30_at_12 16 15_PM](https://user-images.githubusercontent.com/10937186/86125641-35ccc980-bb07-11ea-9fbf-e62eb5eb6dc8.png)



Discussion:
We actually have the entire parent hierarchy available to us in symbols.py. So we could display the hierarchy flattened, separated by a token.

My problem here is that different languages use different separator tokens.

For C++ (maybe Rust too?) the separator token is `::`
For JSON the separator token is `/` (to make it look like a [JSON pointer](https://tools.ietf.org/html/rfc6901)).
For JS the separator is `.` I'm assuming.

A neutral separator could be
* `/` to make the item look like a "file hierarchy"
* ` ` (a space) to make the items visually separated by a space

I'm currently leaning towards `/` as it feels familiar with CTRL+P.
We really need minihtml to make this world properly.

can we use tabs to separate them? what about | ? -> comes to mind also. I have not string opinion I guess but we should lobby for minihtml as a better solution.
If we join parent symbol with current symbol then that will affect filtering also.
Would that be something we want or should we not join?

Unless we want to show the joined path in subfield in which case that will probably not affect filtering
I'm not sure what minihtml can bring to the table here.

I'm talking about the following problem:

Given that I have a list of "names":
```
["MyNamespace", "CMyClass", "EMyNestedEnum"]
```
I'm assuming for Pascal it'd be best to render it in the quick panel as
```
MyNamespace.CMyClass.EMyNestedEnum
```
If this were JSON, it'd be most natural to render it as
```
MyNamespace/CMyClass/EMyNestedEnum
```
And for C++ it'd be best to render it as
```
MyNamespace::CMyClass::EMyNestedEnum
```

If we want to join the we should find some generic symbol (some utf8 character for example) that would represent descendants well. Nothing that matches any particular language as that would get confusing for other languages.
You're talking about showing the entire hierarchy. Honestly that may present its own sets of problems given how long the string could be become (maybe expose this as an option?) I don't think this is really syntax question but rather a visual design question.

What about emojis? :) 
> If we want to join the we should find some generic symbol (some utf8 character for example) that would represent descendants well. Nothing that matches any particular language as that would get confusing for other languages.

I agree. Something like • would be good enough for me. Or emojis like ▶️ perhaps, but I'm not sure if those are allowed. I mentioned minihtml because I would prefer some smaller but different style text, kind of like a sub caption with a background color maybe even.
We could be absolute nerds and use `⊃` ("is superset of") (or possibly a unicode arrow `→`)
After playing around with this a bit I think it’s better if the flattened hierarchy is in the first row (so it influences the filtering), and if the separator is always `/`.

Resolving patch:
diff --git a/plugin/symbols.py b/plugin/symbols.py
--- a/plugin/symbols.py
+++ b/plugin/symbols.py
@@ -1,10 +1,11 @@
 from .core.protocol import Request, Range
 from .core.registry import LspTextCommand
-from .core.typing import Any, List, Optional, Tuple, Dict
+from .core.typing import Any, List, Optional, Tuple, Dict, Generator
 from .core.views import location_to_encoded_filename
 from .core.views import range_to_region
 from .core.views import SYMBOL_KINDS
 from .core.views import text_document_identifier
+from contextlib import contextmanager
 import os
 import sublime
 import sublime_plugin
@@ -16,12 +17,27 @@ def format_symbol_kind(kind: int) -> str:
     return str(kind)
 
 
+def _kind_and_detail(item: Dict[str, Any]) -> str:
+    kind = format_symbol_kind(item["kind"])
+    detail = item.get("detail")
+    if isinstance(detail, str) and detail:
+        return "{} | {}".format(kind, detail)
+    return kind
+
+
 def format_symbol_scope(kind: int) -> str:
     if 1 <= kind <= len(SYMBOL_KINDS):
         return SYMBOL_KINDS[kind - 1][1]
     return 'comment'
 
 
+@contextmanager
+def _additional_name(names: List[str], name: str) -> Generator[None, None, None]:
+    names.append(name)
+    yield
+    names.pop(-1)
+
+
 class LspSelectionClearCommand(sublime_plugin.TextCommand):
     """
     Selections may not be modified outside the run method of a text command. Thus, to allow modification in an async
@@ -58,7 +74,6 @@ def __init__(self, view: sublime.View) -> None:
         super().__init__(view)
         self.old_regions = []  # type: List[sublime.Region]
         self.regions = []  # type: List[Tuple[sublime.Region, Optional[sublime.Region], str]]
-        self.found_at_least_one_nonempty_detail = False
         self.is_first_selection = False
 
     def run(self, edit: sublime.Edit) -> None:
@@ -119,28 +134,23 @@ def process_symbols(self, items: List[Dict[str, Any]]) -> List[List[str]]:
 
     def process_document_symbols(self, items: List[Dict[str, Any]]) -> List[List[str]]:
         quick_panel_items = []  # type: List[List[str]]
-        self.found_at_least_one_nonempty_detail = False
+        names = []  # type: List[str]
         for item in items:
-            self.process_document_symbol_recursive(quick_panel_items, item, 0)
-        if self.found_at_least_one_nonempty_detail:
-            return quick_panel_items
-        else:
-            return [[item[0], item[1]] for item in quick_panel_items]
+            self.process_document_symbol_recursive(quick_panel_items, item, names)
+        return quick_panel_items
 
     def process_document_symbol_recursive(self, quick_panel_items: List[List[str]], item: Dict[str, Any],
-                                          depth: int) -> None:
+                                          names: List[str]) -> None:
         kind = item['kind']
         self.regions.append((range_to_region(Range.from_lsp(item['range']), self.view),
                              range_to_region(Range.from_lsp(item['selectionRange']), self.view),
                              format_symbol_scope(kind)))
-        name = ' ' * (4 * depth) + item['name']
-        quick_panel_item = [name, format_symbol_kind(kind), item.get('detail') or '']
-        if quick_panel_item[2]:
-            self.found_at_least_one_nonempty_detail = True
-        quick_panel_items.append(quick_panel_item)
-        children = item.get('children') or []
-        for child in children:
-            self.process_document_symbol_recursive(quick_panel_items, child, depth + 1)
+        name = item['name']
+        with _additional_name(names, name):
+            quick_panel_items.append([name, "{} | {}".format(_kind_and_detail(item), " > ".join(names))])
+            children = item.get('children') or []
+            for child in children:
+                self.process_document_symbol_recursive(quick_panel_items, child, names)
 
     def process_symbol_informations(self, items: List[Dict[str, Any]]) -> List[List[str]]:
         quick_panel_items = []  # type: List[List[str]]
@@ -148,7 +158,11 @@ def process_symbol_informations(self, items: List[Dict[str, Any]]) -> List[List[
             kind = item['kind']
             self.regions.append((range_to_region(Range.from_lsp(item['location']['range']), self.view),
                                  None, format_symbol_scope(kind)))
-            quick_panel_items.append([item['name'], format_symbol_kind(kind)])
+            container = item.get("containerName")
+            second_row = _kind_and_detail(item)
+            if container:
+                second_row += " | {}".format(container)
+            quick_panel_items.append([item['name'], second_row])
         return quick_panel_items
 
 


Test patch: