Repository: sublimelsp/LSP

Issue:
ValueError: path is on mount 'D:', start on mount 'C:'
* OS: Windows 10
* How you installed LSP (Package Control or from git?): git (latest master be60102fdf8b72de85fe1088eafbd248516ef6f9)
* Minimal reproduction steps
1. open a project with two folders one in C drive and the other in D:
'''json
{
	"folders":
	[
		{
			"path": "C:\\Users\\Lehdhili\\AppData\\Roaming\\Sublime Text 3\\Packages\\LSP-rust"
		},
		{
			"path": "D:\\Amjad\\rls-vscode"
		}
	]
}```
2. open a file in the D folder
3. invoke references on a name in that file
* Log
```
LSP:  --> textDocument/references
LSP:      [{'uri': 'file:///D:/Amjad/rls-vscode/src/extension.ts', 'range': {'end': {'line': 25, 'character': 20}, 'start': {'line': 25, 'character': 9}}}, {'uri': 'file:///D:/Amjad/rls-vscode/src/extension.ts', 'range': {'end': {'line': 434, 'character': 25}, 'start': {'line': 434, 'character': 14}}}, {'uri': 'file:///D:/Amjad/rls-vscode/src/rustup.ts', 'range': {'end': {'line': 67, 'character': 33}, 'start': {'line': 67, 'character': 22}}}]
Error handling server payload
Traceback (most recent call last):
  File "C:\Users\Lehdhili\AppData\Roaming\Sublime Text 3\Packages\LSP\plugin\core\rpc.py", line 150, in receive_payload
    self.response_handler(payload)
  File "C:\Users\Lehdhili\AppData\Roaming\Sublime Text 3\Packages\LSP\plugin\core\rpc.py", line 169, in response_handler
    handler(response["result"])
  File "C:\Users\Lehdhili\AppData\Roaming\Sublime Text 3\Packages\LSP\plugin\references.py", line 45, in <lambda>
    request, lambda response: self.handle_response(response, pos))
  File "C:\Users\Lehdhili\AppData\Roaming\Sublime Text 3\Packages\LSP\plugin\references.py", line 64, in handle_response
    formatted_references = self._get_formatted_references(response, base_dir)
  File "C:\Users\Lehdhili\AppData\Roaming\Sublime Text 3\Packages\LSP\plugin\references.py", line 142, in _get_formatted_references
    grouped_references = self._group_references_by_file(references, base_dir)
  File "C:\Users\Lehdhili\AppData\Roaming\Sublime Text 3\Packages\LSP\plugin\references.py", line 150, in _group_references_by_file
    relative_file_path = os.path.relpath(file_path, base_dir)
  File "./python3.3/ntpath.py", line 564, in relpath
ValueError: path is on mount 'D:', start on mount 'C:'
```
possible duplicate #247


Discussion:
Looks like a check for `os.path.commonprefix` went missing, but adding it in now will make the references behaviour really inconsistent. I'd prefer to leave this bug in place until we build better support for multiple workspace folders.

Resolving patch:
diff --git a/plugin/references.py b/plugin/references.py
--- a/plugin/references.py
+++ b/plugin/references.py
@@ -27,6 +27,9 @@ class LspSymbolReferencesCommand(LspTextCommand):
     def __init__(self, view: sublime.View) -> None:
         super().__init__(view)
         self.reflist = []  # type: List[List[str]]
+        self.word_region = None  # type: Optional[sublime.Region]
+        self.word = ""
+        self.base_dir = None  # type: Optional[str]
 
     def is_enabled(self, event: 'Optional[dict]' = None) -> bool:
         if self.has_client_with_capability('referencesProvider'):
@@ -37,6 +40,16 @@ def run(self, edit: sublime.Edit, event: 'Optional[dict]' = None) -> None:
         client = self.client_with_capability('referencesProvider')
         if client:
             pos = get_position(self.view, event)
+            window = self.view.window()
+            self.word_region = self.view.word(pos)
+            self.word = self.view.substr(self.word_region)
+
+            # use relative paths if file on the same root.
+            base_dir = windows.lookup(window).get_project_path()
+            if base_dir:
+                if os.path.commonprefix([base_dir, self.view.file_name()]):
+                    self.base_dir = base_dir
+
             document_position = get_document_position(self.view, pos)
             if document_position:
                 document_position['context'] = {
@@ -59,99 +72,102 @@ def handle_response(self, response: 'Optional[List[ReferenceDict]]', pos: int) -
             window.status_message("No references found")
             return
 
-        word_region = self.view.word(pos)
-        word = self.view.substr(word_region)
-
-        base_dir = windows.lookup(window).get_project_path()
-        formatted_references = self._get_formatted_references(response, base_dir)
+        references_by_file = self._group_references_by_file(response)
 
         if settings.show_references_in_quick_panel:
-            flags = sublime.KEEP_OPEN_ON_FOCUS_LOST
-            if settings.quick_panel_monospace_font:
-                flags |= sublime.MONOSPACE_FONT
-            window.show_quick_panel(
-                self.reflist,
-                lambda index: self.on_ref_choice(base_dir, index),
-                flags,
-                self.get_current_ref(base_dir, word_region.begin()),
-                lambda index: self.on_ref_highlight(base_dir, index)
-            )
+            self.show_quick_panel(references_by_file)
         else:
-            panel = ensure_references_panel(window)
-            if not panel:
-                return
-            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 get_current_ref(self, base_dir: 'Optional[str]', pos: int) -> 'Optional[int]':
-        row, col = self.view.rowcol(pos)
-        row, col = row + 1, col + 1
-
-        def find_matching_ref(condition: 'Callable[[int, int], bool]') -> 'Optional[int]':
-            for i, ref in enumerate(self.reflist):
-                file = ref[0]
-                filename, filerow, filecol = file.rsplit(':', 2)
-
-                row, col = int(filerow), int(filecol)
-                filepath = filename
-                if base_dir:
-                    filepath = os.path.join(base_dir, filename)
-
-                if not os.path.exists(filepath):
-                    continue
-
-                if os.path.samefile(filepath, self.view.file_name()) and condition(row, col):
-                    return i
-            return None
-
-        ref = find_matching_ref(lambda r, c: row == r and col == c)
-        if ref is not None:
-            return ref
-
-        ref = find_matching_ref(lambda r, c: row == r)
-        if ref is not None:
-            return ref
-
-        return 0
-
-    def on_ref_choice(self, base_dir: 'Optional[str]', index: int) -> None:
+            self.show_references_panel(references_by_file)
+
+    def show_quick_panel(self, references_by_file: 'Dict[str, List[Tuple[Point, str]]]') -> None:
+        selected_index = -1
+        current_file_path = self.view.file_name()
+        for file_path, references in references_by_file.items():
+            for reference in references:
+                point, line = reference
+                item = ['{}:{}:{}'.format(self.get_relative_path(file_path), point.row + 1, point.col + 1), line]
+                self.reflist.append(item)
+
+                # pre-select a reference in the current file.
+                if current_file_path == file_path and selected_index == -1:
+                    selected_index = len(self.reflist) - 1
+
+        flags = sublime.KEEP_OPEN_ON_FOCUS_LOST
+        if settings.quick_panel_monospace_font:
+            flags |= sublime.MONOSPACE_FONT
         window = self.view.window()
+        window.show_quick_panel(
+            self.reflist,
+            self.on_ref_choice,
+            flags,
+            selected_index,
+            self.on_ref_highlight
+        )
+
+    def on_ref_choice(self, index: int) -> None:
+        self.open_ref_index(index)
+
+    def on_ref_highlight(self, index: int) -> None:
+        self.open_ref_index(index, transient=True)
+
+    def open_ref_index(self, index: int, transient: bool=False) -> None:
         if index != -1:
-            window.open_file(self.get_selected_file_path(base_dir, index), sublime.ENCODED_POSITION)
+            flags = sublime.ENCODED_POSITION | sublime.TRANSIENT if transient else sublime.ENCODED_POSITION
+            window = self.view.window()
+            window.open_file(self.get_selected_file_path(index), flags)
 
-    def on_ref_highlight(self, base_dir: 'Optional[str]', index: int) -> None:
+    def show_references_panel(self, references_by_file: 'Dict[str, List[Tuple[Point, str]]]') -> None:
         window = self.view.window()
-        if index != -1:
-            window.open_file(self.get_selected_file_path(base_dir, index), sublime.ENCODED_POSITION | sublime.TRANSIENT)
+        panel = ensure_references_panel(window)
+        if not panel:
+            return
 
-    def get_selected_file_path(self, base_dir: 'Optional[str]', index: int) -> str:
-        file_path = self.reflist[index][0]
-        if base_dir:
-            file_path = os.path.join(base_dir, file_path)
+        text = ''
+        references_count = 0
+        for file, references in references_by_file.items():
+            text += '◌ {}:\n'.format(self.get_relative_path(file))
+            for reference in references:
+                references_count += 1
+                point, line = reference
+                text += '\t{:>8}:{:<4} {}\n'.format(point.row + 1, point.col + 1, line)
+            # append a new line after each file name
+            text += '\n'
+
+        base_dir = windows.lookup(window).get_project_path()
+        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, self.word, text),
+            'force': True,
+            'scroll_to_end': False
+        })
+
+        # highlight all word occurrences
+        regions = panel.find_all(r"\b{}\b".format(self.word))
+        panel.add_regions('ReferenceHighlight', regions, 'comment', flags=sublime.DRAW_OUTLINED)
+        panel.set_read_only(True)
+
+    def get_selected_file_path(self, index: int) -> str:
+        return self.get_full_path(self.reflist[index][0])
+
+    def get_relative_path(self, file_path: str) -> str:
+        if self.base_dir:
+            return os.path.relpath(file_path, self.base_dir)
+        else:
+            return file_path
+
+    def get_full_path(self, file_path: str) -> str:
+        if self.base_dir:
+            return os.path.join(self.base_dir, file_path)
         return file_path
 
     def want_event(self) -> bool:
         return True
 
-    def _get_formatted_references(self, references: 'List[ReferenceDict]', base_dir: 'Optional[str]') -> str:
-        grouped_references = self._group_references_by_file(references, base_dir)
-        return self._format_references(grouped_references)
-
-    def _group_references_by_file(self, references: 'List[ReferenceDict]',
-                                  base_dir: 'Optional[str]'
+    def _group_references_by_file(self, references: 'List[ReferenceDict]'
                                   ) -> 'Dict[str, List[Tuple[Point, str]]]':
         """ Return a dictionary that groups references by the file it belongs. """
         grouped_references = {}  # type: Dict[str, List[Tuple[Point, str]]]
@@ -162,9 +178,6 @@ def _group_references_by_file(self, references: 'List[ReferenceDict]',
             # get line of the reference, to showcase its use
             reference_line = linecache.getline(file_path, point.row + 1).strip()
 
-            if base_dir:
-                file_path = os.path.relpath(file_path, base_dir)
-
             if grouped_references.get(file_path) is None:
                 grouped_references[file_path] = []
             grouped_references[file_path].append((point, reference_line))
@@ -173,17 +186,3 @@ def _group_references_by_file(self, references: 'List[ReferenceDict]',
         linecache.clearcache()
 
         return grouped_references
-
-    def _format_references(self, grouped_references: 'Dict[str, List[Tuple[Point, str]]]') -> str:
-        text = ''
-        refs = []  # type: List[List[str]]
-        for file, references in grouped_references.items():
-            text += '◌ {}:\n'.format(file)
-            for reference in references:
-                point, line = reference
-                text += '\t{:>8}:{:<4} {}\n'.format(point.row + 1, point.col + 1, line)
-                refs.append(['{}:{}:{}'.format(file, point.row + 1, point.col + 1), line])
-            # append a new line after each file name
-            text += '\n'
-        self.reflist = refs
-        return text


Test patch: