Repository: sublimelsp/LSP

Issue:
Could you add following command: "Show all occurrences and rename symbol"
Hi!

I would like to see all occurrences of symbol before renaming.
It would be nice to have this command.

Thanks!


Discussion:
```js
{ "keys": ["???"], "command": "lsp_symbol_references", "context": [{ "key": "setting.lsp_active" }] },
{ "keys": ["???"], "command": "lsp_symbol_rename", "context": [{ "key": "setting.lsp_active" }] },
```

Commands can be chained by a macro or [Chain of Command](https://packagecontrol.io/packages/Chain%20of%20Command).

---

For `Chain of Command`,

```js
    {
        "keys": ["???"],
        "command": "chain",
        "context": [{ "key": "setting.lsp_active" }],
        "args": {
            "commands": [
                [ "lsp_symbol_references" ],
                [ "lsp_symbol_rename" ],
            ],
        },
    },
```
@jfcherng 
I believe this plugin exists! 
@jfcherng 
But this is what I expect when I use rename command.
I don't want to blindly rename symbol without knowledge about its usage.
Incremental renaming?
@jfcherng 
Not familiar with this term.
If it is a functionality similar to current Xcode implementation ( you have a view of all occurrences which are updated on-the-fly when you rename symbol ) - yes, this is ideal functionality for renaming.
But with current implementation I am fine: chain of two commands.

This LSP package is API-friendly, not human-friendly. I don't want a command "rename symbol blindly" in Text Editor GUI.

Resolving patch:
diff --git a/plugin/core/panels.py b/plugin/core/panels.py
--- a/plugin/core/panels.py
+++ b/plugin/core/panels.py
@@ -35,6 +35,7 @@
 class PanelName:
     Diagnostics = "diagnostics"
     References = "references"
+    Rename = "rename"
     LanguageServers = "language servers"
 
 
diff --git a/plugin/rename.py b/plugin/rename.py
--- a/plugin/rename.py
+++ b/plugin/rename.py
@@ -1,14 +1,19 @@
-import sublime
-import sublime_plugin
 from .core.edit import apply_workspace_edit
 from .core.edit import parse_workspace_edit
+from .core.edit import TextEdit
+from .core.panels import ensure_panel
+from .core.panels import PanelName
 from .core.protocol import Range
 from .core.protocol import Request
 from .core.registry import get_position
 from .core.registry import LspTextCommand
-from .core.typing import Any, Optional
+from .core.registry import windows
+from .core.typing import Any, Optional, Dict, List
 from .core.views import range_to_region
 from .core.views import text_document_position_params
+import os
+import sublime
+import sublime_plugin
 
 
 class RenameSymbolInputHandler(sublime_plugin.TextInputHandler):
@@ -105,7 +110,18 @@ def on_rename_result(self, response: Any) -> None:
         window = self.view.window()
         if window:
             if response:
-                apply_workspace_edit(window, parse_workspace_edit(response))
+                changes = parse_workspace_edit(response)
+                file_count = len(changes.keys())
+                if file_count > 1:
+                    total_changes = sum(map(len, changes.values()))
+                    message = "Replace {} occurrences across {} files?".format(total_changes, file_count)
+                    choice = sublime.yes_no_cancel_dialog(message, "Replace", "Dry Run")
+                    if choice == sublime.DIALOG_YES:
+                        apply_workspace_edit(window, changes)
+                    elif choice == sublime.DIALOG_NO:
+                        self._render_rename_panel(changes, total_changes, file_count)
+                else:
+                    apply_workspace_edit(window, changes)
             else:
                 window.status_message('Nothing to rename')
 
@@ -126,3 +142,49 @@ def on_prepare_result(self, response: Any, pos: int) -> None:
 
     def on_prepare_error(self, error: Any) -> None:
         sublime.error_message("Rename error: {}".format(error["message"]))
+
+    def _get_relative_path(self, file_path: str) -> str:
+        window = self.view.window()
+        if not window:
+            return file_path
+        base_dir = windows.lookup(window).get_project_path(file_path)
+        if base_dir:
+            return os.path.relpath(file_path, base_dir)
+        else:
+            return file_path
+
+    def _render_rename_panel(self, changes: Dict[str, List[TextEdit]], total_changes: int, file_count: int) -> None:
+        window = self.view.window()
+        if not window:
+            return
+        panel = ensure_rename_panel(window)
+        if not panel:
+            return
+        text = ''
+        for file, file_changes in changes.items():
+            text += '◌ {}:\n'.format(self._get_relative_path(file))
+            for edit in file_changes:
+                start = edit[0]
+                text += '\t{:>8}:{}\n'.format(start[0] + 1, start[1] + 1)
+            # append a new line after each file name
+            text += '\n'
+        base_dir = windows.lookup(window).get_project_path(self.view.file_name() or "")
+        panel.settings().set("result_base_dir", base_dir)
+        panel.run_command("lsp_clear_panel")
+        window.run_command("show_panel", {"panel": "output.rename"})
+        fmt = "{} changes across {} files. Double-click on a row:col number to jump to that location.\n\n{}"
+        panel.run_command('append', {
+            'characters': fmt.format(total_changes, file_count, text),
+            'force': True,
+            'scroll_to_end': False
+        })
+
+
+def ensure_rename_panel(window: sublime.Window) -> Optional[sublime.View]:
+    return ensure_panel(
+        window=window,
+        name=PanelName.Rename,
+        result_file_regex=r"^\s*\S\s+(\S.*):$",
+        result_line_regex=r"^\s*([0-9]+):([0-9]+)\s*$",
+        syntax="Packages/LSP/Syntaxes/Rename.sublime-syntax"
+    )


Test patch: