Repository: sublimelsp/LSP

Issue:
Show line of reference in panel
### Suggestion:

When displaying references, it would be nice if the line was also copied into the panel at the bottom. Maybe with a configurable number of surrounding lines for better context and the actual usage highlighted with an outline box.

Would also be helpful in remembering whose references you are actually seeing.

I'm using pyls, FWIW.


Discussion:
Something like this?

![2018-09-14-144330_683x617_scrot](https://user-images.githubusercontent.com/22029477/45550909-9b659e00-b82c-11e8-9c30-f49dcc0b43a1.png)

This is just an example. It is not finished.

Yes. 

Resolving patch:
diff --git a/plugin/references.py b/plugin/references.py
--- a/plugin/references.py
+++ b/plugin/references.py
@@ -1,5 +1,6 @@
 import os
 import sublime
+import linecache
 
 from .core.panels import create_output_panel
 from .core.settings import PLUGIN_NAME
@@ -22,8 +23,8 @@ def ensure_references_panel(window: sublime.Window):
 
 def create_references_panel(window: sublime.Window):
     panel = create_output_panel(window, "references")
-    panel.settings().set("result_file_regex",
-                         r"^\s+\S\s+(\S.+)\s+(\d+):?(\d+)$")
+    panel.settings().set("result_file_regex", r"^\s*\S\s+(\S.*):$")
+    panel.settings().set("result_line_regex", r"^\s+([0-9]+):?([0-9]+).*$")
     panel.assign_syntax("Packages/" + PLUGIN_NAME +
                         "/Syntaxes/References.sublime-syntax")
     # Call create_output_panel a second time after assigning the above
@@ -57,44 +58,75 @@ def run(self, edit, event=None):
 
     def handle_response(self, response: 'Optional[List[Dict]]', pos) -> None:
         window = self.view.window()
-        word = self.view.substr(self.view.word(pos))
-        base_dir = get_project_path(window)
-        file_path = self.view.file_name()
+
         if response is None:
             response = []
 
-        references = list(format_reference(item, base_dir) for item in response)
-
-        if (len(references)) > 0:
-            display_path = file_path
-            if base_dir and os.path.commonprefix([file_path, base_dir]):
-                display_path = os.path.relpath(file_path, base_dir)
-            panel = ensure_references_panel(window)
-            panel.settings().set("result_base_dir", base_dir)
-            panel.set_read_only(False)
-            panel.run_command("lsp_clear_panel")
-            panel.run_command('append', {
-                'characters': 'References to "' + word + '" at ' + display_path + ':\n'
-            })
-            window.run_command("show_panel", {"panel": "output.references"})
-            for reference in references:
-                panel.run_command('append', {
-                    'characters': reference + "\n",
-                    'force': True,
-                    'scroll_to_end': True
-                })
-            panel.set_read_only(True)
-
-        else:
+        references_count = len(response)
+        # return if there are no references
+        if references_count < 1:
             window.run_command("hide_panel", {"panel": "output.references"})
             window.status_message("No references found")
+            return
+
+        word = self.view.substr(self.view.word(pos))
+
+        base_dir = get_project_path(window)
+        formatted_references = self._get_formatted_references(response, base_dir)
+
+        panel = ensure_references_panel(window)
+        panel.settings().set("result_base_dir", base_dir)
+
+        panel.set_read_only(False)
+        panel.run_command("lsp_clear_panel")
+        window.run_command("show_panel", {"panel": "output.references"})
+        panel.run_command('append', {
+            'characters': "{} references for '{}'\n\n{}".format(references_count, word, formatted_references),
+            'force': True,
+            'scroll_to_end': False
+        })
+
+        # highlight all word occurrences
+        regions = panel.find_all(r"\b{}\b".format(word))
+        panel.add_regions('ReferenceHighlight', regions, 'comment', flags=sublime.DRAW_OUTLINED)
+        panel.set_read_only(True)
 
     def want_event(self):
         return True
 
+    def _get_formatted_references(self, references: 'List[Dict]', base_dir) -> str:
+        grouped_references = self._group_references_by_file(references, base_dir)
+        formatted_references = self._format_references(grouped_references)
+        return formatted_references
+
+    def _group_references_by_file(self, references, base_dir):
+        """ Return a dictionary that groups references by the file it belongs. """
+        grouped_references = {}  # type: Dict[str, List[Dict]]
+        for reference in references:
+            file_path = uri_to_filename(reference.get("uri"))
+            relative_file_path = os.path.relpath(file_path, base_dir)
+
+            point = Point.from_lsp(reference.get('range').get('start'))
+            # get line of the reference, to showcase its use
+            reference_line = linecache.getline(file_path, point.row + 1).strip()
 
-def format_reference(reference, base_dir):
-    start = Point.from_lsp(reference.get('range').get('start'))
-    file_path = uri_to_filename(reference.get("uri"))
-    relative_file_path = os.path.relpath(file_path, base_dir)
-    return " ◌ {} {}:{}".format(relative_file_path, start.row + 1, start.col + 1)
+            if grouped_references.get(relative_file_path) is None:
+                grouped_references[relative_file_path] = []
+            grouped_references[relative_file_path].append({'point': point, 'text': reference_line})
+
+        # we don't want to cache the line, we always want to get fresh data
+        linecache.clearcache()
+
+        return grouped_references
+
+    def _format_references(self, grouped_references) -> str:
+        text = ''
+        for file in grouped_references:
+            text += '◌ {}:\n'.format(file)
+            references = grouped_references.get(file)
+            for reference in references:
+                point = reference.get('point')
+                text += '\t{:>8}:{:<4} {}\n'.format(point.row + 1, point.col + 1, reference.get('text'))
+            # append a new line after each file name
+            text += '\n'
+        return text


Test patch: