From f5ec6c59f179c1dff93c6b2a25f9286201600e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 25 Jun 2024 15:50:17 +0200 Subject: [PATCH 001/129] add willRename and didRename fileOperations --- Default.sublime-commands | 4 ++ boot.py | 2 + plugin/core/protocol.py | 8 ++++ plugin/core/sessions.py | 5 +++ plugin/rename_file.py | 89 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 108 insertions(+) create mode 100644 plugin/rename_file.py diff --git a/Default.sublime-commands b/Default.sublime-commands index 75bb04d95..2f64684d2 100644 --- a/Default.sublime-commands +++ b/Default.sublime-commands @@ -151,6 +151,10 @@ "caption": "LSP: Rename", "command": "lsp_symbol_rename" }, + { + "caption": "LSP: Rename File", + "command": "lsp_rename_file" + }, { "caption": "LSP: Code Action", "command": "lsp_code_actions" diff --git a/boot.py b/boot.py index 56ae078db..6ff25cec8 100644 --- a/boot.py +++ b/boot.py @@ -69,6 +69,7 @@ from .plugin.references import LspSymbolReferencesCommand from .plugin.rename import LspHideRenameButtonsCommand from .plugin.rename import LspSymbolRenameCommand +from .plugin.rename_file import LspRenameFileCommand from .plugin.save_command import LspSaveAllCommand from .plugin.save_command import LspSaveCommand from .plugin.selection_range import LspExpandSelectionCommand @@ -146,6 +147,7 @@ "LspSymbolImplementationCommand", "LspSymbolReferencesCommand", "LspSymbolRenameCommand", + "LspRenameFileCommand", "LspSymbolTypeDefinitionCommand", "LspToggleCodeLensesCommand", "LspToggleHoverPopupsCommand", diff --git a/plugin/core/protocol.py b/plugin/core/protocol.py index 9cf429aec..759fe99ba 100644 --- a/plugin/core/protocol.py +++ b/plugin/core/protocol.py @@ -6064,6 +6064,10 @@ def colorPresentation(cls, params: ColorPresentationParams, view: sublime.View) def willSaveWaitUntil(cls, params: WillSaveTextDocumentParams, view: sublime.View) -> Request: return Request("textDocument/willSaveWaitUntil", params, view) + @classmethod + def willRenameFiles(cls, params: RenameFilesParams) -> Request: + return Request("workspace/willRenameFiles", params) + @classmethod def documentSymbols(cls, params: DocumentSymbolParams, view: sublime.View) -> Request: return Request("textDocument/documentSymbol", params, view, progress=True) @@ -6256,6 +6260,10 @@ def didSave(cls, params: DidSaveTextDocumentParams) -> Notification: def didClose(cls, params: DidCloseTextDocumentParams) -> Notification: return Notification("textDocument/didClose", params) + @classmethod + def didRenameFiles(cls, params: RenameFilesParams) -> Notification: + return Notification("workspace/didRenameFiles", params) + @classmethod def didChangeConfiguration(cls, params: DidChangeConfigurationParams) -> Notification: return Notification("workspace/didChangeConfiguration", params) diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py index ccb30b429..04e7fa41f 100644 --- a/plugin/core/sessions.py +++ b/plugin/core/sessions.py @@ -493,6 +493,11 @@ def get_initialize_params(variables: dict[str, str], workspace_folders: list[Wor "codeLens": { "refreshSupport": True }, + "fileOperations": { + "dynamicRegistration": True, + "willRename": True, + "didRename": True + }, "inlayHint": { "refreshSupport": True }, diff --git a/plugin/rename_file.py b/plugin/rename_file.py new file mode 100644 index 000000000..06a948df1 --- /dev/null +++ b/plugin/rename_file.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +from .core.open import open_file_uri +from .core.protocol import Notification, RenameFilesParams, Request, WorkspaceEdit +from .core.registry import LspTextCommand +from .core.url import parse_uri +from .core.views import did_open_text_document_params, uri_from_view +from pathlib import Path +from urllib.parse import urljoin +import os +import sublime +import sublime_plugin + + +class RenameFileInputHandler(sublime_plugin.TextInputHandler): + def want_event(self) -> bool: + return False + + def __init__(self, file_name: str) -> None: + self.file_name = file_name + + def name(self) -> str: + return "new_name" + + def placeholder(self) -> str: + return self.file_name + + def initial_text(self) -> str: + return self.placeholder() + + def validate(self, name: str) -> bool: + return len(name) > 0 + +class LspRenameFileCommand(LspTextCommand): + capability = 'workspace.fileOperations.willRename' + + def want_event(self) -> bool: + return False + + def input(self, args: dict) -> sublime_plugin.TextInputHandler | None: + if "new_name" in args: + return None + return RenameFileInputHandler(Path(self.view.file_name() or "").name) + + def run( + self, + _edit: sublime.Edit, + new_name: str = "", # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy + ) -> None: + session = self.best_session("workspace.fileOperations.willRename") + if not session: + return + current_file_path = self.view.file_name() or '' + new_file_path = os.path.normpath(Path(current_file_path).parent / new_name) + window = self.view.window() + if os.path.exists(new_file_path) and window: + window.status_message(f'Unable to Rename. File already exists') + return + rename_file_params: RenameFilesParams = { + "files": [{ + "newUri": urljoin("file:", new_file_path), + "oldUri": uri_from_view(self.view), + }] + } + request = Request.willRenameFiles(rename_file_params) + session.send_request(request, lambda res: self.handle(res, session.config.name, new_name, rename_file_params)) + + def handle(self, res: WorkspaceEdit | None, session_name: str, new_name: str, rename_file_params: RenameFilesParams) -> None: + window = self.view.window() + session = self.session_by_name(session_name) + if session and window: + # LSP spec - Apply WorkspaceEdit before the files are renamed + if res: + session.apply_workspace_edit_async(res, is_refactoring=True) + renamed_file = rename_file_params['files'][0] + old_regions = [region for region in self.view.sel()] + self.view.close() # LSP spec - send didClose for old file + # actally rename the file, this will create a new file + os.rename( + parse_uri(renamed_file['oldUri'])[1], + parse_uri(renamed_file['newUri'])[1] + ) + # LSP spec - send didOpen for the new file + open_file_uri(window, renamed_file['newUri']) \ + .then(lambda v: v and v.sel().add_all(old_regions)) + for session in self.sessions('workspace.fileOperations.didRename'): + session.send_notification(Notification.didRenameFiles(rename_file_params)) + + From e26a48e2608d6b60fa6f84802b72d6fd002d0e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 25 Jun 2024 16:10:13 +0200 Subject: [PATCH 002/129] always enable --- plugin/rename_file.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 06a948df1..15c8c4bde 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -34,6 +34,9 @@ def validate(self, name: str) -> bool: class LspRenameFileCommand(LspTextCommand): capability = 'workspace.fileOperations.willRename' + def is_enabled(self): + return True + def want_event(self) -> bool: return False @@ -46,7 +49,10 @@ def run( self, _edit: sublime.Edit, new_name: str = "", # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy + paths: str | None = None ) -> None: + print('paths', paths) + print('new_name', new_name) session = self.best_session("workspace.fileOperations.willRename") if not session: return From e6304c23c260084ee47debbefa8cec2688475309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 25 Jun 2024 17:58:21 +0200 Subject: [PATCH 003/129] add renaming from the sidebar --- Side Bar.sublime-menu | 8 ++++ plugin/rename_file.py | 87 ++++++++++++++++++++++++------------------- 2 files changed, 57 insertions(+), 38 deletions(-) create mode 100644 Side Bar.sublime-menu diff --git a/Side Bar.sublime-menu b/Side Bar.sublime-menu new file mode 100644 index 000000000..d9bbc76ab --- /dev/null +++ b/Side Bar.sublime-menu @@ -0,0 +1,8 @@ +[ + { + "caption": "Lsp: Rename", + "mnemonic": "l", + "command": "lsp_rename_file", + "args": {"paths": []} + } +] \ No newline at end of file diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 15c8c4bde..387780e10 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -1,10 +1,7 @@ from __future__ import annotations - from .core.open import open_file_uri from .core.protocol import Notification, RenameFilesParams, Request, WorkspaceEdit -from .core.registry import LspTextCommand -from .core.url import parse_uri -from .core.views import did_open_text_document_params, uri_from_view +from .core.registry import LspWindowCommand from pathlib import Path from urllib.parse import urljoin import os @@ -31,7 +28,8 @@ def initial_text(self) -> str: def validate(self, name: str) -> bool: return len(name) > 0 -class LspRenameFileCommand(LspTextCommand): + +class LspRenameFileCommand(LspWindowCommand): capability = 'workspace.fileOperations.willRename' def is_enabled(self): @@ -43,53 +41,66 @@ def want_event(self) -> bool: def input(self, args: dict) -> sublime_plugin.TextInputHandler | None: if "new_name" in args: return None - return RenameFileInputHandler(Path(self.view.file_name() or "").name) + old_path = self.get_old_path(args.get('paths'), self.window.active_view()) + return RenameFileInputHandler(Path(old_path).name) def run( self, - _edit: sublime.Edit, - new_name: str = "", # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy - paths: str | None = None + new_name: str, # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy + paths: list[str] | None = None, # exist when invoked from the sidebar with "LSP: Rename" ) -> None: - print('paths', paths) - print('new_name', new_name) - session = self.best_session("workspace.fileOperations.willRename") - if not session: - return - current_file_path = self.view.file_name() or '' - new_file_path = os.path.normpath(Path(current_file_path).parent / new_name) - window = self.view.window() - if os.path.exists(new_file_path) and window: - window.status_message(f'Unable to Rename. File already exists') + session = self.session() + old_path = self.get_old_path(paths, self.window.active_view()) + new_path = os.path.normpath(Path(old_path).parent / new_name) + if os.path.exists(new_path): + self.window.status_message(f'Unable to Rename. Already exists') return rename_file_params: RenameFilesParams = { "files": [{ - "newUri": urljoin("file:", new_file_path), - "oldUri": uri_from_view(self.view), + "newUri": urljoin("file:", new_path), + "oldUri": urljoin("file:", old_path), }] } + if not session: + self.rename_path(old_path, new_path) + self.notify_did_rename(rename_file_params) + return request = Request.willRenameFiles(rename_file_params) - session.send_request(request, lambda res: self.handle(res, session.config.name, new_name, rename_file_params)) + session.send_request(request, lambda res: self.handle(res, session.config.name, old_path, new_path, rename_file_params)) + + def get_old_path(self, paths: list[str] | None, view: sublime.View | None) -> str: + if paths: + return paths[0] + if view: + return view.file_name() or "" + return "" - def handle(self, res: WorkspaceEdit | None, session_name: str, new_name: str, rename_file_params: RenameFilesParams) -> None: - window = self.view.window() + def handle(self, res: WorkspaceEdit | None, session_name: str, old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None: session = self.session_by_name(session_name) - if session and window: + if session: # LSP spec - Apply WorkspaceEdit before the files are renamed if res: session.apply_workspace_edit_async(res, is_refactoring=True) - renamed_file = rename_file_params['files'][0] - old_regions = [region for region in self.view.sel()] - self.view.close() # LSP spec - send didClose for old file - # actally rename the file, this will create a new file - os.rename( - parse_uri(renamed_file['oldUri'])[1], - parse_uri(renamed_file['newUri'])[1] - ) + self.rename_path(old_path, new_path) + self.notify_did_rename(rename_file_params) + + def rename_path(self, old_path: str, new_path: str) -> None: + old_regions: list[sublime.Region] = [] + view = self.window.find_open_file(old_path) + if view: + old_regions = [region for region in view.sel()] + view.close() # LSP spec - send didClose for old file + # actally rename the file, this will create a new file + os.rename( + old_path, + new_path + ) + if os.path.isfile(new_path): # LSP spec - send didOpen for the new file - open_file_uri(window, renamed_file['newUri']) \ - .then(lambda v: v and v.sel().add_all(old_regions)) - for session in self.sessions('workspace.fileOperations.didRename'): - session.send_notification(Notification.didRenameFiles(rename_file_params)) - + open_file_uri(self.window, new_path) \ + .then(lambda v: v and old_regions and v.sel().add_all(old_regions)) + def notify_did_rename(self, rename_file_params: RenameFilesParams): + sessions = [s for s in self.sessions() if s.has_capability('workspace.fileOperations.didRename')] + for session in sessions: + session.send_notification(Notification.didRenameFiles(rename_file_params)) From e5e07221bfbd2a1def57d8f63c6a6c0eebf0161c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 25 Jun 2024 18:09:54 +0200 Subject: [PATCH 004/129] changes --- plugin/rename_file.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 387780e10..4856993b5 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -66,7 +66,10 @@ def run( self.notify_did_rename(rename_file_params) return request = Request.willRenameFiles(rename_file_params) - session.send_request(request, lambda res: self.handle(res, session.config.name, old_path, new_path, rename_file_params)) + session.send_request( + request, + lambda res: self.handle(res, session.config.name, old_path, new_path, rename_file_params + )) def get_old_path(self, paths: list[str] | None, view: sublime.View | None) -> str: if paths: @@ -75,7 +78,8 @@ def get_old_path(self, paths: list[str] | None, view: sublime.View | None) -> st return view.file_name() or "" return "" - def handle(self, res: WorkspaceEdit | None, session_name: str, old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None: + def handle(self, res: WorkspaceEdit | None, session_name: str, + old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None: session = self.session_by_name(session_name) if session: # LSP spec - Apply WorkspaceEdit before the files are renamed @@ -89,18 +93,20 @@ def rename_path(self, old_path: str, new_path: str) -> None: view = self.window.find_open_file(old_path) if view: old_regions = [region for region in view.sel()] - view.close() # LSP spec - send didClose for old file + view.close() # LSP spec - send didClose for the old file # actally rename the file, this will create a new file - os.rename( - old_path, - new_path - ) + os.rename(old_path, new_path) if os.path.isfile(new_path): + def restore_regions(v: sublime.View | None) -> None: + if not v: + return + v.sel().clear() + v.sel().add_all(old_regions) + # LSP spec - send didOpen for the new file - open_file_uri(self.window, new_path) \ - .then(lambda v: v and old_regions and v.sel().add_all(old_regions)) + open_file_uri(self.window, new_path).then(restore_regions) def notify_did_rename(self, rename_file_params: RenameFilesParams): sessions = [s for s in self.sessions() if s.has_capability('workspace.fileOperations.didRename')] - for session in sessions: - session.send_notification(Notification.didRenameFiles(rename_file_params)) + for s in sessions: + s.send_notification(Notification.didRenameFiles(rename_file_params)) From 5c91301a1f73f1b6e3995f815c3f241c270a4ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 25 Jun 2024 18:22:24 +0200 Subject: [PATCH 005/129] fix styles --- plugin/rename_file.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 4856993b5..09abb2363 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -46,14 +46,14 @@ def input(self, args: dict) -> sublime_plugin.TextInputHandler | None: def run( self, - new_name: str, # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy - paths: list[str] | None = None, # exist when invoked from the sidebar with "LSP: Rename" + new_name: str, # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy + paths: list[str] | None = None, # exist when invoked from the sidebar with "LSP: Rename" ) -> None: session = self.session() old_path = self.get_old_path(paths, self.window.active_view()) new_path = os.path.normpath(Path(old_path).parent / new_name) if os.path.exists(new_path): - self.window.status_message(f'Unable to Rename. Already exists') + self.window.status_message('Unable to Rename. Already exists') return rename_file_params: RenameFilesParams = { "files": [{ @@ -68,8 +68,8 @@ def run( request = Request.willRenameFiles(rename_file_params) session.send_request( request, - lambda res: self.handle(res, session.config.name, old_path, new_path, rename_file_params - )) + lambda res: self.handle(res, session.config.name, old_path, new_path, rename_file_params) + ) def get_old_path(self, paths: list[str] | None, view: sublime.View | None) -> str: if paths: @@ -93,7 +93,7 @@ def rename_path(self, old_path: str, new_path: str) -> None: view = self.window.find_open_file(old_path) if view: old_regions = [region for region in view.sel()] - view.close() # LSP spec - send didClose for the old file + view.close() # LSP spec - send didClose for the old file # actally rename the file, this will create a new file os.rename(old_path, new_path) if os.path.isfile(new_path): From 075e50e535ccdb2aeca9ebfb811c77e8bf8bbe43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 25 Jun 2024 18:29:38 +0200 Subject: [PATCH 006/129] add new line --- Side Bar.sublime-menu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Side Bar.sublime-menu b/Side Bar.sublime-menu index d9bbc76ab..62795b026 100644 --- a/Side Bar.sublime-menu +++ b/Side Bar.sublime-menu @@ -5,4 +5,4 @@ "command": "lsp_rename_file", "args": {"paths": []} } -] \ No newline at end of file +] From 814b0bbc9d466107118e216d18e46f38b928dab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 25 Jun 2024 18:47:12 +0200 Subject: [PATCH 007/129] hande if new path directory doesn't exist --- plugin/rename_file.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 09abb2363..9d3cbe252 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -95,6 +95,9 @@ def rename_path(self, old_path: str, new_path: str) -> None: old_regions = [region for region in view.sel()] view.close() # LSP spec - send didClose for the old file # actally rename the file, this will create a new file + new_dir = Path(new_path).parent + if not os.path.exists(new_dir): + os.makedirs(new_dir) os.rename(old_path, new_path) if os.path.isfile(new_path): def restore_regions(v: sublime.View | None) -> None: From d488b0609291a1a03257db02f7b8a1d9fe53d1a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Wed, 26 Jun 2024 11:56:40 +0200 Subject: [PATCH 008/129] handle renaming buffers --- plugin/rename_file.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 9d3cbe252..f2de9575b 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -50,11 +50,15 @@ def run( paths: list[str] | None = None, # exist when invoked from the sidebar with "LSP: Rename" ) -> None: session = self.session() - old_path = self.get_old_path(paths, self.window.active_view()) + view = self.window.active_view() + old_path = self.get_old_path(paths, view) new_path = os.path.normpath(Path(old_path).parent / new_name) if os.path.exists(new_path): self.window.status_message('Unable to Rename. Already exists') return + if old_path == '' and view: # handle renaming buffers + view.set_name(Path(new_path).name) + return rename_file_params: RenameFilesParams = { "files": [{ "newUri": urljoin("file:", new_path), From d1a10cfdc5ce2c8b402bc46c4f781570e63f8248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Wed, 26 Jun 2024 12:03:03 +0200 Subject: [PATCH 009/129] ahh... --- plugin/rename_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index f2de9575b..623a675f8 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -56,7 +56,7 @@ def run( if os.path.exists(new_path): self.window.status_message('Unable to Rename. Already exists') return - if old_path == '' and view: # handle renaming buffers + if old_path == '' and view: # handle renaming buffers view.set_name(Path(new_path).name) return rename_file_params: RenameFilesParams = { From 08e3af11a9cdeda1fbc302606f375dff42a971fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Wed, 26 Jun 2024 12:10:56 +0200 Subject: [PATCH 010/129] add initial_selection --- plugin/rename_file.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 623a675f8..b109bf6a3 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -25,6 +25,10 @@ def placeholder(self) -> str: def initial_text(self) -> str: return self.placeholder() + def initial_selection(self) -> list[tuple[int, int]]: + end_point = self.file_name.rfind('.') + return [(0, end_point)] + def validate(self, name: str) -> bool: return len(name) > 0 From 6f654623cbedfd7f4dada7269783861d68c5b344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Wed, 26 Jun 2024 12:13:56 +0200 Subject: [PATCH 011/129] improve initial_selection and rename file_name to path --- plugin/rename_file.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index b109bf6a3..fe2e59ac6 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -13,24 +13,26 @@ class RenameFileInputHandler(sublime_plugin.TextInputHandler): def want_event(self) -> bool: return False - def __init__(self, file_name: str) -> None: - self.file_name = file_name + def __init__(self, path: str) -> None: + self.path = path def name(self) -> str: return "new_name" def placeholder(self) -> str: - return self.file_name + return self.path def initial_text(self) -> str: return self.placeholder() def initial_selection(self) -> list[tuple[int, int]]: - end_point = self.file_name.rfind('.') + end_point = self.path.rfind('.') + if end_point == -1: + end_point = len(self.path) return [(0, end_point)] - def validate(self, name: str) -> bool: - return len(name) > 0 + def validate(self, path: str) -> bool: + return len(path) > 0 class LspRenameFileCommand(LspWindowCommand): From c9e877283b3cd26cb2299d301717fe2a469e1d05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Thu, 27 Jun 2024 12:12:00 +0200 Subject: [PATCH 012/129] Split "Lsp: Rename" to "Lsp: Rename Folder" and "Lsp: Rename File" --- Side Bar.sublime-menu | 14 ++++++++++---- plugin/rename_file.py | 25 ++++++++++++++++++------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/Side Bar.sublime-menu b/Side Bar.sublime-menu index 62795b026..45a7f6bf9 100644 --- a/Side Bar.sublime-menu +++ b/Side Bar.sublime-menu @@ -1,8 +1,14 @@ [ { - "caption": "Lsp: Rename", + "caption": "Lsp: Rename File", "mnemonic": "l", "command": "lsp_rename_file", - "args": {"paths": []} - } -] + "args": {"files": []} + }, + { + "caption": "Lsp: Rename Folder", + "mnemonic": "l", + "command": "lsp_rename_file", + "args": {"dirs": []} + }, +] \ No newline at end of file diff --git a/plugin/rename_file.py b/plugin/rename_file.py index fe2e59ac6..9f673d3d4 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -41,23 +41,33 @@ class LspRenameFileCommand(LspWindowCommand): def is_enabled(self): return True + def is_visible(self, dirs=None, files=None): + if dirs is None and files is None: + return True # show 'LSP: Rename File' in command palette + if dirs is not None: + return len(dirs) == 1 # show 'LSP: Rename Folder' in sidebar + if files is not None: + return len(files) == 1 # show 'LSP: Rename File' in sidebar + return False + def want_event(self) -> bool: return False def input(self, args: dict) -> sublime_plugin.TextInputHandler | None: if "new_name" in args: return None - old_path = self.get_old_path(args.get('paths'), self.window.active_view()) + old_path = self.get_old_path(args.get('dirs'), args.get('files'), self.window.active_view()) return RenameFileInputHandler(Path(old_path).name) def run( self, new_name: str, # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy - paths: list[str] | None = None, # exist when invoked from the sidebar with "LSP: Rename" + dirs: list[str] | None = None, # exist when invoked from the sidebar with "LSP: Rename Folder" + files: list[str] | None = None, # exist when invoked from the sidebar with "LSP: Rename File" ) -> None: session = self.session() view = self.window.active_view() - old_path = self.get_old_path(paths, view) + old_path = self.get_old_path(dirs, files, view) new_path = os.path.normpath(Path(old_path).parent / new_name) if os.path.exists(new_path): self.window.status_message('Unable to Rename. Already exists') @@ -81,9 +91,11 @@ def run( lambda res: self.handle(res, session.config.name, old_path, new_path, rename_file_params) ) - def get_old_path(self, paths: list[str] | None, view: sublime.View | None) -> str: - if paths: - return paths[0] + def get_old_path(self, dirs: list[str] | None, files: list[str] | None, view: sublime.View | None) -> str: + if dirs: + return dirs[0] + if files: + return files[0] if view: return view.file_name() or "" return "" @@ -104,7 +116,6 @@ def rename_path(self, old_path: str, new_path: str) -> None: if view: old_regions = [region for region in view.sel()] view.close() # LSP spec - send didClose for the old file - # actally rename the file, this will create a new file new_dir = Path(new_path).parent if not os.path.exists(new_dir): os.makedirs(new_dir) From e0c9c8264a249988e91f69462ba06145d4029d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Thu, 27 Jun 2024 12:14:55 +0200 Subject: [PATCH 013/129] new line --- Side Bar.sublime-menu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Side Bar.sublime-menu b/Side Bar.sublime-menu index 45a7f6bf9..67b0316cf 100644 --- a/Side Bar.sublime-menu +++ b/Side Bar.sublime-menu @@ -11,4 +11,4 @@ "command": "lsp_rename_file", "args": {"dirs": []} }, -] \ No newline at end of file +] From a52b5c4151597238aab457621323810fbd13eeef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Thu, 27 Jun 2024 19:07:15 +0200 Subject: [PATCH 014/129] when renaming a directory, it would be good to retarget all open views from that folder to new location so we do not lose changes The ST default rename_path command doesn't do this VS Code does this LSP will do this --- plugin/rename_file.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 9f673d3d4..671b9ccc0 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -119,7 +119,15 @@ def rename_path(self, old_path: str, new_path: str) -> None: new_dir = Path(new_path).parent if not os.path.exists(new_dir): os.makedirs(new_dir) + isdir = os.path.isdir(old_path) os.rename(old_path, new_path) + if isdir: + for v in self.window.views(): + file_name = v.file_name() + if not file_name: + continue + if file_name.startswith(old_path): + v.retarget(file_name.replace(old_path, new_path)) if os.path.isfile(new_path): def restore_regions(v: sublime.View | None) -> None: if not v: From 2acceb6ca7df114b71428d3859615d99d420c53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Thu, 27 Jun 2024 21:59:30 +0200 Subject: [PATCH 015/129] save some lines --- plugin/rename_file.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 671b9ccc0..b829040ad 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -124,9 +124,7 @@ def rename_path(self, old_path: str, new_path: str) -> None: if isdir: for v in self.window.views(): file_name = v.file_name() - if not file_name: - continue - if file_name.startswith(old_path): + if file_name and file_name.startswith(old_path): v.retarget(file_name.replace(old_path, new_path)) if os.path.isfile(new_path): def restore_regions(v: sublime.View | None) -> None: From 9ab9dc5e9e90db6edc453467e942330164a7be6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Thu, 27 Jun 2024 22:08:36 +0200 Subject: [PATCH 016/129] remove more lines --- plugin/rename_file.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index b829040ad..ba5c27bdf 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -10,9 +10,6 @@ class RenameFileInputHandler(sublime_plugin.TextInputHandler): - def want_event(self) -> bool: - return False - def __init__(self, path: str) -> None: self.path = path @@ -26,10 +23,8 @@ def initial_text(self) -> str: return self.placeholder() def initial_selection(self) -> list[tuple[int, int]]: - end_point = self.path.rfind('.') - if end_point == -1: - end_point = len(self.path) - return [(0, end_point)] + name, _ext = os.path.splitext(self.path) + return [(0, len(name))] def validate(self, path: str) -> bool: return len(path) > 0 From f5c26a663436c2f9bf1d8e1a30db0268d49c81aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Thu, 27 Jun 2024 23:35:39 +0200 Subject: [PATCH 017/129] few less lines --- plugin/rename_file.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index ba5c27bdf..d40ee50d5 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -52,7 +52,7 @@ def input(self, args: dict) -> sublime_plugin.TextInputHandler | None: if "new_name" in args: return None old_path = self.get_old_path(args.get('dirs'), args.get('files'), self.window.active_view()) - return RenameFileInputHandler(Path(old_path).name) + return RenameFileInputHandler(Path(old_path or "").name) def run( self, @@ -63,13 +63,13 @@ def run( session = self.session() view = self.window.active_view() old_path = self.get_old_path(dirs, files, view) + if old_path is None: # handle renaming buffers + if view: view.set_name(new_name) + return new_path = os.path.normpath(Path(old_path).parent / new_name) if os.path.exists(new_path): self.window.status_message('Unable to Rename. Already exists') return - if old_path == '' and view: # handle renaming buffers - view.set_name(Path(new_path).name) - return rename_file_params: RenameFilesParams = { "files": [{ "newUri": urljoin("file:", new_path), @@ -86,19 +86,14 @@ def run( lambda res: self.handle(res, session.config.name, old_path, new_path, rename_file_params) ) - def get_old_path(self, dirs: list[str] | None, files: list[str] | None, view: sublime.View | None) -> str: - if dirs: - return dirs[0] - if files: - return files[0] - if view: - return view.file_name() or "" - return "" + def get_old_path(self, dirs: list[str] | None, files: list[str] | None, view: sublime.View | None) -> str | None: + if dirs: return dirs[0] + if files: return files[0] + if view: return view.file_name() def handle(self, res: WorkspaceEdit | None, session_name: str, old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None: - session = self.session_by_name(session_name) - if session: + if session := self.session_by_name(session_name): # LSP spec - Apply WorkspaceEdit before the files are renamed if res: session.apply_workspace_edit_async(res, is_refactoring=True) @@ -107,8 +102,7 @@ def handle(self, res: WorkspaceEdit | None, session_name: str, def rename_path(self, old_path: str, new_path: str) -> None: old_regions: list[sublime.Region] = [] - view = self.window.find_open_file(old_path) - if view: + if view := self.window.find_open_file(old_path): old_regions = [region for region in view.sel()] view.close() # LSP spec - send didClose for the old file new_dir = Path(new_path).parent From 437659fd4e865f7e5c4c3e22f72d93ea788f8fa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Thu, 27 Jun 2024 23:39:40 +0200 Subject: [PATCH 018/129] simpler conditions --- plugin/rename_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index d40ee50d5..d183b81d4 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -39,9 +39,9 @@ def is_enabled(self): def is_visible(self, dirs=None, files=None): if dirs is None and files is None: return True # show 'LSP: Rename File' in command palette - if dirs is not None: + if dirs: return len(dirs) == 1 # show 'LSP: Rename Folder' in sidebar - if files is not None: + if files: return len(files) == 1 # show 'LSP: Rename File' in sidebar return False From 3647f92a3c0b1b53e9f11d01606eeab0e27ac2f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Thu, 27 Jun 2024 23:41:02 +0200 Subject: [PATCH 019/129] avoid multiple statements on one line --- plugin/rename_file.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index d183b81d4..b886109e0 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -64,7 +64,8 @@ def run( view = self.window.active_view() old_path = self.get_old_path(dirs, files, view) if old_path is None: # handle renaming buffers - if view: view.set_name(new_name) + if view: + view.set_name(new_name) return new_path = os.path.normpath(Path(old_path).parent / new_name) if os.path.exists(new_path): @@ -87,9 +88,12 @@ def run( ) def get_old_path(self, dirs: list[str] | None, files: list[str] | None, view: sublime.View | None) -> str | None: - if dirs: return dirs[0] - if files: return files[0] - if view: return view.file_name() + if dirs: + return dirs[0] + if files: + return files[0] + if view: + return view.file_name() def handle(self, res: WorkspaceEdit | None, session_name: str, old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None: From 3389f66824dfca1439ce233daf5c194ce2981993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 2 Jul 2024 14:52:50 +0200 Subject: [PATCH 020/129] implement FileOperationFilter --- plugin/core/types.py | 67 ++++++++++++++++++++++++++++++++++++++++++- plugin/rename_file.py | 36 +++++++++++++++-------- 2 files changed, 90 insertions(+), 13 deletions(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index 46d7efa7b..5a44ba19c 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -2,7 +2,7 @@ from .collections import DottedDict from .file_watcher import FileWatcherEventType from .logging import debug, set_debug_logging -from .protocol import TextDocumentSyncKind +from .protocol import FileOperationFilter, FileOperationPattern, FileOperationPatternKind, TextDocumentSyncKind from .url import filename_to_uri from .url import parse_uri from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, TypedDict, TypeVar, Union @@ -10,6 +10,7 @@ from wcmatch.glob import BRACE from wcmatch.glob import globmatch from wcmatch.glob import GLOBSTAR +from wcmatch.glob import IGNORECASE import contextlib import fnmatch import os @@ -440,6 +441,70 @@ def matches(self, view: sublime.View) -> bool: return any(f(view) for f in self.filters) if self.filters else True +class FileOperationFilterChecker: + """ + A file operation filter denotes a view or path through properties like scheme or pattern. An example is a filter + that applies to TypeScript files on disk. Another example is a filter that applies to JSON files with name + package.json: + { + "scheme": "file", + "pattern": { + "glob": "**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}", + "matches": "file" + } + } + """ + + __slots__ = ("scheme", "pattern") + + def __init__( + self, + scheme: str | None = None, + pattern: FileOperationPattern | None = None + ) -> None: + self.scheme = scheme + self.pattern = pattern + + def __call__(self, path: str, view: sublime.View | None) -> bool: + if self.scheme and view: + uri = view.settings().get("lsp_uri") + if isinstance(uri, str) and parse_uri(uri)[0] != self.scheme: + return False + if self.pattern: + matches = self.pattern.get('matches') + if matches: + if matches == FileOperationPatternKind.File and os.path.isdir(path): + return False + if matches == FileOperationPatternKind.Folder and os.path.isfile(path): + return False + options = self.pattern.get('options', {}) + flags = GLOBSTAR | BRACE + if options.get('ignoreCase', False): + flags |= IGNORECASE + if not globmatch(path, self.pattern['glob'], flags=flags): + return False + return True + + +class FileOperationFilterMatcher: + """ + A FileOperationFilterMatcher is a list of FileOperationFilterChecker. + Provides logic to see if a path/view matches the specified FileOperationFilter's. + """ + + __slots__ = ("filters",) + + def __init__(self, file_filters: list[FileOperationFilter]) -> None: + self.filters = [FileOperationFilterChecker(**file_filter) for file_filter in file_filters] + + def __bool__(self) -> bool: + return bool(self.filters) + + def matches(self, new_path: str, view: sublime.View | None) -> bool: + """Does this selector match the view? A selector with no filters matches all views.""" + return any(f(new_path, view) for f in self.filters) if self.filters else True + + # method -> (capability dotted path, optional registration dotted path) # these are the EXCEPTIONS. The general rule is: method foo/bar --> (barProvider, barProvider.id) _METHOD_TO_CAPABILITY_EXCEPTIONS: dict[str, tuple[str, str | None]] = { diff --git a/plugin/rename_file.py b/plugin/rename_file.py index b886109e0..aa82a054f 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -1,4 +1,6 @@ from __future__ import annotations + +from .core.types import FileOperationFilterMatcher from .core.open import open_file_uri from .core.protocol import Notification, RenameFilesParams, Request, WorkspaceEdit from .core.registry import LspWindowCommand @@ -79,13 +81,18 @@ def run( } if not session: self.rename_path(old_path, new_path) - self.notify_did_rename(rename_file_params) + self.notify_did_rename(rename_file_params, new_path, view) + return + capability = session.get_capability('workspace.fileOperations.willRename') + if not capability: return - request = Request.willRenameFiles(rename_file_params) - session.send_request( - request, - lambda res: self.handle(res, session.config.name, old_path, new_path, rename_file_params) - ) + filters = FileOperationFilterMatcher(capability.get('filters')) + if filters.matches(old_path, view): + request = Request.willRenameFiles(rename_file_params) + session.send_request( + request, + lambda res: self.handle(res, session.config.name, old_path, new_path, rename_file_params, view) + ) def get_old_path(self, dirs: list[str] | None, files: list[str] | None, view: sublime.View | None) -> str | None: if dirs: @@ -96,13 +103,13 @@ def get_old_path(self, dirs: list[str] | None, files: list[str] | None, view: su return view.file_name() def handle(self, res: WorkspaceEdit | None, session_name: str, - old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None: + old_path: str, new_path: str, rename_file_params: RenameFilesParams, view: sublime.View | None) -> None: if session := self.session_by_name(session_name): # LSP spec - Apply WorkspaceEdit before the files are renamed if res: session.apply_workspace_edit_async(res, is_refactoring=True) self.rename_path(old_path, new_path) - self.notify_did_rename(rename_file_params) + self.notify_did_rename(rename_file_params, new_path, view) def rename_path(self, old_path: str, new_path: str) -> None: old_regions: list[sublime.Region] = [] @@ -129,7 +136,12 @@ def restore_regions(v: sublime.View | None) -> None: # LSP spec - send didOpen for the new file open_file_uri(self.window, new_path).then(restore_regions) - def notify_did_rename(self, rename_file_params: RenameFilesParams): - sessions = [s for s in self.sessions() if s.has_capability('workspace.fileOperations.didRename')] - for s in sessions: - s.send_notification(Notification.didRenameFiles(rename_file_params)) + def notify_did_rename(self, rename_file_params: RenameFilesParams, path: str, view: sublime.View | None): + for s in self.sessions(): + capability = s.get_capability('workspace.fileOperations.didRename') + if not capability: + continue + filters = FileOperationFilterMatcher(capability.get('filters')) + if filters.matches(path, view): + s.send_notification(Notification.didRenameFiles(rename_file_params)) + From 4785d4938c2b4abc715a4e6f3291b0ca323f534e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 2 Jul 2024 15:03:52 +0200 Subject: [PATCH 021/129] remomve few lines --- plugin/rename_file.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index aa82a054f..8ef5b770a 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -83,16 +83,16 @@ def run( self.rename_path(old_path, new_path) self.notify_did_rename(rename_file_params, new_path, view) return - capability = session.get_capability('workspace.fileOperations.willRename') - if not capability: - return - filters = FileOperationFilterMatcher(capability.get('filters')) - if filters.matches(old_path, view): + filters = (session.get_capability('workspace.fileOperations.willRename') or {}).get('filters') + if filters and FileOperationFilterMatcher(filters).matches(old_path, view): request = Request.willRenameFiles(rename_file_params) session.send_request( request, lambda res: self.handle(res, session.config.name, old_path, new_path, rename_file_params, view) ) + else: + self.rename_path(old_path, new_path) + self.notify_did_rename(rename_file_params, new_path, view) def get_old_path(self, dirs: list[str] | None, files: list[str] | None, view: sublime.View | None) -> str | None: if dirs: @@ -138,10 +138,9 @@ def restore_regions(v: sublime.View | None) -> None: def notify_did_rename(self, rename_file_params: RenameFilesParams, path: str, view: sublime.View | None): for s in self.sessions(): - capability = s.get_capability('workspace.fileOperations.didRename') - if not capability: + filters = (s.get_capability('workspace.fileOperations.didRename') or {}).get('filters') + if not filters: continue - filters = FileOperationFilterMatcher(capability.get('filters')) - if filters.matches(path, view): + if FileOperationFilterMatcher(filters).matches(path, view): s.send_notification(Notification.didRenameFiles(rename_file_params)) From 2f5af3173253cb49946315c7031f52f12c667aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 2 Jul 2024 15:04:24 +0200 Subject: [PATCH 022/129] fix flake --- plugin/rename_file.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 8ef5b770a..9d0015391 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -143,4 +143,3 @@ def notify_did_rename(self, rename_file_params: RenameFilesParams, path: str, vi continue if FileOperationFilterMatcher(filters).matches(path, view): s.send_notification(Notification.didRenameFiles(rename_file_params)) - From 890667caf4a152c5f11560673957050f9dad42e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 2 Jul 2024 15:12:18 +0200 Subject: [PATCH 023/129] fix pyright --- stubs/wcmatch/glob.pyi | 1 + 1 file changed, 1 insertion(+) diff --git a/stubs/wcmatch/glob.pyi b/stubs/wcmatch/glob.pyi index 9d5a7a6f6..0cc199577 100644 --- a/stubs/wcmatch/glob.pyi +++ b/stubs/wcmatch/glob.pyi @@ -2,6 +2,7 @@ from typing import Any, Optional BRACE: int = ... GLOBSTAR: int = ... +IGNORECASE: int = ... def globmatch( From af04c56692d61c1c2e8755dd535833d8fef545db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 2 Jul 2024 15:17:14 +0200 Subject: [PATCH 024/129] remove is_visible code It is not necessary --- plugin/rename_file.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 9d0015391..ad12cdd7c 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -38,14 +38,6 @@ class LspRenameFileCommand(LspWindowCommand): def is_enabled(self): return True - def is_visible(self, dirs=None, files=None): - if dirs is None and files is None: - return True # show 'LSP: Rename File' in command palette - if dirs: - return len(dirs) == 1 # show 'LSP: Rename Folder' in sidebar - if files: - return len(files) == 1 # show 'LSP: Rename File' in sidebar - return False def want_event(self) -> bool: return False From 178adae6e086f9edc3600bdbbd1f64d98c4c2337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 2 Jul 2024 15:18:06 +0200 Subject: [PATCH 025/129] remove LSP: Rename File and Rename folder in favor of LSP: Rename... in the sidebar context menu --- Side Bar.sublime-menu | 12 +++--------- plugin/rename_file.py | 16 ++++++---------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/Side Bar.sublime-menu b/Side Bar.sublime-menu index 67b0316cf..5538942d2 100644 --- a/Side Bar.sublime-menu +++ b/Side Bar.sublime-menu @@ -1,14 +1,8 @@ [ { - "caption": "Lsp: Rename File", + "caption": "LSP: Rename...", "mnemonic": "l", "command": "lsp_rename_file", - "args": {"files": []} - }, - { - "caption": "Lsp: Rename Folder", - "mnemonic": "l", - "command": "lsp_rename_file", - "args": {"dirs": []} - }, + "args": {"paths": []} + } ] diff --git a/plugin/rename_file.py b/plugin/rename_file.py index ad12cdd7c..9c785422e 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -38,25 +38,23 @@ class LspRenameFileCommand(LspWindowCommand): def is_enabled(self): return True - def want_event(self) -> bool: return False def input(self, args: dict) -> sublime_plugin.TextInputHandler | None: if "new_name" in args: return None - old_path = self.get_old_path(args.get('dirs'), args.get('files'), self.window.active_view()) + old_path = self.get_old_path(args.get('paths'), self.window.active_view()) return RenameFileInputHandler(Path(old_path or "").name) def run( self, new_name: str, # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy - dirs: list[str] | None = None, # exist when invoked from the sidebar with "LSP: Rename Folder" - files: list[str] | None = None, # exist when invoked from the sidebar with "LSP: Rename File" + paths: list[str] | None = None, # exist when invoked from the sidebar with "LSP: Rename..." ) -> None: session = self.session() view = self.window.active_view() - old_path = self.get_old_path(dirs, files, view) + old_path = self.get_old_path(paths, view) if old_path is None: # handle renaming buffers if view: view.set_name(new_name) @@ -86,11 +84,9 @@ def run( self.rename_path(old_path, new_path) self.notify_did_rename(rename_file_params, new_path, view) - def get_old_path(self, dirs: list[str] | None, files: list[str] | None, view: sublime.View | None) -> str | None: - if dirs: - return dirs[0] - if files: - return files[0] + def get_old_path(self, paths: list[str] | None, view: sublime.View | None) -> str | None: + if paths: + return paths[0] if view: return view.file_name() From 8ef443c2f1ebb27f52dc145b795c04b10a97d110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 2 Jul 2024 15:19:19 +0200 Subject: [PATCH 026/129] rename LspRenameFileCommand to LspRenamePathCommand --- Default.sublime-commands | 2 +- Side Bar.sublime-menu | 2 +- boot.py | 4 ++-- plugin/rename_file.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Default.sublime-commands b/Default.sublime-commands index 2f64684d2..631920f88 100644 --- a/Default.sublime-commands +++ b/Default.sublime-commands @@ -153,7 +153,7 @@ }, { "caption": "LSP: Rename File", - "command": "lsp_rename_file" + "command": "lsp_rename_path" }, { "caption": "LSP: Code Action", diff --git a/Side Bar.sublime-menu b/Side Bar.sublime-menu index 5538942d2..a82bbd17c 100644 --- a/Side Bar.sublime-menu +++ b/Side Bar.sublime-menu @@ -2,7 +2,7 @@ { "caption": "LSP: Rename...", "mnemonic": "l", - "command": "lsp_rename_file", + "command": "lsp_rename_path", "args": {"paths": []} } ] diff --git a/boot.py b/boot.py index 6ff25cec8..32de129c4 100644 --- a/boot.py +++ b/boot.py @@ -69,7 +69,7 @@ from .plugin.references import LspSymbolReferencesCommand from .plugin.rename import LspHideRenameButtonsCommand from .plugin.rename import LspSymbolRenameCommand -from .plugin.rename_file import LspRenameFileCommand +from .plugin.rename_file import LspRenamePathCommand from .plugin.save_command import LspSaveAllCommand from .plugin.save_command import LspSaveCommand from .plugin.selection_range import LspExpandSelectionCommand @@ -147,7 +147,7 @@ "LspSymbolImplementationCommand", "LspSymbolReferencesCommand", "LspSymbolRenameCommand", - "LspRenameFileCommand", + "LspRenamePathCommand", "LspSymbolTypeDefinitionCommand", "LspToggleCodeLensesCommand", "LspToggleHoverPopupsCommand", diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 9c785422e..fd8bec49c 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -32,7 +32,7 @@ def validate(self, path: str) -> bool: return len(path) > 0 -class LspRenameFileCommand(LspWindowCommand): +class LspRenamePathCommand(LspWindowCommand): capability = 'workspace.fileOperations.willRename' def is_enabled(self): From d1342b96deff642522b9ff0541277ff0d0e4c396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 2 Jul 2024 16:16:22 +0200 Subject: [PATCH 027/129] remove FileOperationFilterChecker, FileOperationFilterMatcher in favor of match_file_operation_filters --- plugin/core/types.py | 64 +++++++++---------------------------------- plugin/rename_file.py | 12 ++++---- 2 files changed, 18 insertions(+), 58 deletions(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index 5a44ba19c..8fd01f11f 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -2,7 +2,7 @@ from .collections import DottedDict from .file_watcher import FileWatcherEventType from .logging import debug, set_debug_logging -from .protocol import FileOperationFilter, FileOperationPattern, FileOperationPatternKind, TextDocumentSyncKind +from .protocol import FileOperationFilter, FileOperationPattern, FileOperationPatternKind, FileOperationRegistrationOptions, TextDocumentSyncKind from .url import filename_to_uri from .url import parse_uri from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, TypedDict, TypeVar, Union @@ -440,69 +440,31 @@ def matches(self, view: sublime.View) -> bool: """Does this selector match the view? A selector with no filters matches all views.""" return any(f(view) for f in self.filters) if self.filters else True - -class FileOperationFilterChecker: - """ - A file operation filter denotes a view or path through properties like scheme or pattern. An example is a filter - that applies to TypeScript files on disk. Another example is a filter that applies to JSON files with name - package.json: - { - "scheme": "file", - "pattern": { - "glob": "**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}", - "matches": "file" - } - } - """ - - __slots__ = ("scheme", "pattern") - - def __init__( - self, - scheme: str | None = None, - pattern: FileOperationPattern | None = None - ) -> None: - self.scheme = scheme - self.pattern = pattern - - def __call__(self, path: str, view: sublime.View | None) -> bool: - if self.scheme and view: +def match_file_operation_filters(file_operation_options: FileOperationRegistrationOptions, path: str, view: sublime.View | None) -> bool: + def matches(file_operation_filter: FileOperationFilter) -> bool: + pattern = file_operation_filter.get('pattern') + scheme = file_operation_filter.get('scheme') + if scheme and view: uri = view.settings().get("lsp_uri") - if isinstance(uri, str) and parse_uri(uri)[0] != self.scheme: + if isinstance(uri, str) and parse_uri(uri)[0] != scheme: return False - if self.pattern: - matches = self.pattern.get('matches') + if pattern: + matches = pattern.get('matches') if matches: if matches == FileOperationPatternKind.File and os.path.isdir(path): return False if matches == FileOperationPatternKind.Folder and os.path.isfile(path): return False - options = self.pattern.get('options', {}) + options = pattern.get('options', {}) flags = GLOBSTAR | BRACE if options.get('ignoreCase', False): flags |= IGNORECASE - if not globmatch(path, self.pattern['glob'], flags=flags): + if not globmatch(path, pattern['glob'], flags=flags): return False return True - -class FileOperationFilterMatcher: - """ - A FileOperationFilterMatcher is a list of FileOperationFilterChecker. - Provides logic to see if a path/view matches the specified FileOperationFilter's. - """ - - __slots__ = ("filters",) - - def __init__(self, file_filters: list[FileOperationFilter]) -> None: - self.filters = [FileOperationFilterChecker(**file_filter) for file_filter in file_filters] - - def __bool__(self) -> bool: - return bool(self.filters) - - def matches(self, new_path: str, view: sublime.View | None) -> bool: - """Does this selector match the view? A selector with no filters matches all views.""" - return any(f(new_path, view) for f in self.filters) if self.filters else True + filters = [matches(file_operation_filter) for file_operation_filter in file_operation_options.get('filters')] + return any(filters) if filters else True # method -> (capability dotted path, optional registration dotted path) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index fd8bec49c..9098f9774 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -1,6 +1,6 @@ from __future__ import annotations -from .core.types import FileOperationFilterMatcher +from .core.types import match_file_operation_filters from .core.open import open_file_uri from .core.protocol import Notification, RenameFilesParams, Request, WorkspaceEdit from .core.registry import LspWindowCommand @@ -73,8 +73,8 @@ def run( self.rename_path(old_path, new_path) self.notify_did_rename(rename_file_params, new_path, view) return - filters = (session.get_capability('workspace.fileOperations.willRename') or {}).get('filters') - if filters and FileOperationFilterMatcher(filters).matches(old_path, view): + file_operation_options = session.get_capability('workspace.fileOperations.willRename') + if file_operation_options and match_file_operation_filters(file_operation_options, old_path, view): request = Request.willRenameFiles(rename_file_params) session.send_request( request, @@ -126,8 +126,6 @@ def restore_regions(v: sublime.View | None) -> None: def notify_did_rename(self, rename_file_params: RenameFilesParams, path: str, view: sublime.View | None): for s in self.sessions(): - filters = (s.get_capability('workspace.fileOperations.didRename') or {}).get('filters') - if not filters: - continue - if FileOperationFilterMatcher(filters).matches(path, view): + file_operation_options = s.get_capability('workspace.fileOperations.didRename') + if file_operation_options and match_file_operation_filters(file_operation_options, path, view): s.send_notification(Notification.didRenameFiles(rename_file_params)) From 057eaff2cab6cd0e270ee8a44ab3d0330aed2e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 2 Jul 2024 16:23:11 +0200 Subject: [PATCH 028/129] flake8 fixes --- plugin/core/types.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index 8fd01f11f..76a775d20 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -2,7 +2,10 @@ from .collections import DottedDict from .file_watcher import FileWatcherEventType from .logging import debug, set_debug_logging -from .protocol import FileOperationFilter, FileOperationPattern, FileOperationPatternKind, FileOperationRegistrationOptions, TextDocumentSyncKind +from .protocol import FileOperationFilter +from .protocol import FileOperationPatternKind +from .protocol import FileOperationRegistrationOptions +from .protocol import TextDocumentSyncKind from .url import filename_to_uri from .url import parse_uri from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, TypedDict, TypeVar, Union @@ -440,7 +443,10 @@ def matches(self, view: sublime.View) -> bool: """Does this selector match the view? A selector with no filters matches all views.""" return any(f(view) for f in self.filters) if self.filters else True -def match_file_operation_filters(file_operation_options: FileOperationRegistrationOptions, path: str, view: sublime.View | None) -> bool: + +def match_file_operation_filters( + file_operation_options: FileOperationRegistrationOptions, path: str, view: sublime.View | None +) -> bool: def matches(file_operation_filter: FileOperationFilter) -> bool: pattern = file_operation_filter.get('pattern') scheme = file_operation_filter.get('scheme') From cb82086537649f152a298e2dbf1ac4eba054ab48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 2 Jul 2024 20:32:47 +0200 Subject: [PATCH 029/129] cannot use an input handler to rename folder because it always displays LSP: Rename File I cannot tweak the text inside the TextInputHandler use show_input_panel instead... --- Side Bar.sublime-menu | 2 +- boot.py | 2 ++ plugin/rename_file.py | 52 +++++++++++++++++++++++++++---------------- 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/Side Bar.sublime-menu b/Side Bar.sublime-menu index a82bbd17c..ae55009a7 100644 --- a/Side Bar.sublime-menu +++ b/Side Bar.sublime-menu @@ -2,7 +2,7 @@ { "caption": "LSP: Rename...", "mnemonic": "l", - "command": "lsp_rename_path", + "command": "lsp_rename_path_sidebar", "args": {"paths": []} } ] diff --git a/boot.py b/boot.py index 32de129c4..020add216 100644 --- a/boot.py +++ b/boot.py @@ -70,6 +70,7 @@ from .plugin.rename import LspHideRenameButtonsCommand from .plugin.rename import LspSymbolRenameCommand from .plugin.rename_file import LspRenamePathCommand +from .plugin.rename_file import LspRenamePathSidebarCommand from .plugin.save_command import LspSaveAllCommand from .plugin.save_command import LspSaveCommand from .plugin.selection_range import LspExpandSelectionCommand @@ -148,6 +149,7 @@ "LspSymbolReferencesCommand", "LspSymbolRenameCommand", "LspRenamePathCommand", + "LspRenamePathSidebarCommand", "LspSymbolTypeDefinitionCommand", "LspToggleCodeLensesCommand", "LspToggleHoverPopupsCommand", diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 9098f9774..ff791089e 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -9,9 +9,32 @@ import os import sublime import sublime_plugin - - -class RenameFileInputHandler(sublime_plugin.TextInputHandler): +import functools + + +class LspRenamePathSidebarCommand(LspWindowCommand): + def run(self, paths: list[str] | None = None) -> None: + old_path = paths[0] if paths else None + path_name = Path(old_path or "").name + v = self.window.show_input_panel( + "(LSP) New Name:", + path_name, + functools.partial(self.on_done, old_path), + None, + None) + v.sel().clear() + name, _ext = os.path.splitext(path_name) + v.sel().add(sublime.Region(0, len(name))) + + def on_done(self, old_path: str | None, new_name: str) -> None: + if new_name: + self.window.run_command('lsp_rename_path', { + "new_name": new_name, + "old_path": old_path + }) + + +class RenamePathInputHandler(sublime_plugin.TextInputHandler): def __init__(self, path: str) -> None: self.path = path @@ -44,17 +67,18 @@ def want_event(self) -> bool: def input(self, args: dict) -> sublime_plugin.TextInputHandler | None: if "new_name" in args: return None - old_path = self.get_old_path(args.get('paths'), self.window.active_view()) - return RenameFileInputHandler(Path(old_path or "").name) + view = self.window.active_view() + old_path = view.file_name() if view else None + return RenamePathInputHandler(Path(old_path or "").name) def run( self, new_name: str, # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy - paths: list[str] | None = None, # exist when invoked from the sidebar with "LSP: Rename..." + old_path: str | None = None, ) -> None: session = self.session() view = self.window.active_view() - old_path = self.get_old_path(paths, view) + old_path = old_path or view.file_name() if view else None if old_path is None: # handle renaming buffers if view: view.set_name(new_name) @@ -69,12 +93,8 @@ def run( "oldUri": urljoin("file:", old_path), }] } - if not session: - self.rename_path(old_path, new_path) - self.notify_did_rename(rename_file_params, new_path, view) - return - file_operation_options = session.get_capability('workspace.fileOperations.willRename') - if file_operation_options and match_file_operation_filters(file_operation_options, old_path, view): + file_operation_options = session.get_capability('workspace.fileOperations.willRename') if session else None + if session and file_operation_options and match_file_operation_filters(file_operation_options, old_path, view): request = Request.willRenameFiles(rename_file_params) session.send_request( request, @@ -84,12 +104,6 @@ def run( self.rename_path(old_path, new_path) self.notify_did_rename(rename_file_params, new_path, view) - def get_old_path(self, paths: list[str] | None, view: sublime.View | None) -> str | None: - if paths: - return paths[0] - if view: - return view.file_name() - def handle(self, res: WorkspaceEdit | None, session_name: str, old_path: str, new_path: str, rename_file_params: RenameFilesParams, view: sublime.View | None) -> None: if session := self.session_by_name(session_name): From 728c6eac9a8dff9d073ab4ef71f3185461e6462a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Thu, 4 Jul 2024 00:19:23 +0200 Subject: [PATCH 030/129] Update plugin/core/types.py Co-authored-by: jwortmann --- plugin/core/types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index 76a775d20..3b49d070c 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -469,8 +469,8 @@ def matches(file_operation_filter: FileOperationFilter) -> bool: return False return True - filters = [matches(file_operation_filter) for file_operation_filter in file_operation_options.get('filters')] - return any(filters) if filters else True + filters = file_operation_options.get('filters') + return any(matches(_filter) for _filter in filters) if filters else True # method -> (capability dotted path, optional registration dotted path) From 93b024ab80717824bf2ff4ae23e0e2bae5a64858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Thu, 4 Jul 2024 00:18:18 +0200 Subject: [PATCH 031/129] remove unnecessary if --- plugin/core/types.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index 3b49d070c..e35bb76f5 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -456,11 +456,10 @@ def matches(file_operation_filter: FileOperationFilter) -> bool: return False if pattern: matches = pattern.get('matches') - if matches: - if matches == FileOperationPatternKind.File and os.path.isdir(path): - return False - if matches == FileOperationPatternKind.Folder and os.path.isfile(path): - return False + if matches == FileOperationPatternKind.File and os.path.isdir(path): + return False + if matches == FileOperationPatternKind.Folder and os.path.isfile(path): + return False options = pattern.get('options', {}) flags = GLOBSTAR | BRACE if options.get('ignoreCase', False): From 1ff4648380a11baee7cb4168c9f4e88562054799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Thu, 4 Jul 2024 00:26:53 +0200 Subject: [PATCH 032/129] remove LSP: Rename... from sidebar in favor of overriding the existing ST rename_path command --- Default.sublime-commands | 2 +- Side Bar.sublime-menu | 8 -------- boot.py | 8 ++++++-- plugin/rename_file.py | 6 +++--- 4 files changed, 10 insertions(+), 14 deletions(-) delete mode 100644 Side Bar.sublime-menu diff --git a/Default.sublime-commands b/Default.sublime-commands index 631920f88..2f64684d2 100644 --- a/Default.sublime-commands +++ b/Default.sublime-commands @@ -153,7 +153,7 @@ }, { "caption": "LSP: Rename File", - "command": "lsp_rename_path" + "command": "lsp_rename_file" }, { "caption": "LSP: Code Action", diff --git a/Side Bar.sublime-menu b/Side Bar.sublime-menu deleted file mode 100644 index ae55009a7..000000000 --- a/Side Bar.sublime-menu +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "caption": "LSP: Rename...", - "mnemonic": "l", - "command": "lsp_rename_path_sidebar", - "args": {"paths": []} - } -] diff --git a/boot.py b/boot.py index 020add216..16ce854b0 100644 --- a/boot.py +++ b/boot.py @@ -69,8 +69,8 @@ from .plugin.references import LspSymbolReferencesCommand from .plugin.rename import LspHideRenameButtonsCommand from .plugin.rename import LspSymbolRenameCommand +from .plugin.rename_file import LspRenameFileCommand from .plugin.rename_file import LspRenamePathCommand -from .plugin.rename_file import LspRenamePathSidebarCommand from .plugin.save_command import LspSaveAllCommand from .plugin.save_command import LspSaveCommand from .plugin.selection_range import LspExpandSelectionCommand @@ -148,8 +148,8 @@ "LspSymbolImplementationCommand", "LspSymbolReferencesCommand", "LspSymbolRenameCommand", + "LspRenameFileCommand", "LspRenamePathCommand", - "LspRenamePathSidebarCommand", "LspSymbolTypeDefinitionCommand", "LspToggleCodeLensesCommand", "LspToggleHoverPopupsCommand", @@ -265,6 +265,10 @@ def on_pre_close(self, view: sublime.View) -> None: tup[1](None) break + def on_window_command(self, window: sublime.Window, command_name: str, args: dict) -> tuple[str, dict] | None: + if command_name == "rename_path": + return ("lsp_rename_path", args) + def on_post_window_command(self, window: sublime.Window, command_name: str, args: dict[str, Any] | None) -> None: if command_name == "show_panel": wm = windows.lookup(window) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index ff791089e..799bb7a69 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -12,7 +12,7 @@ import functools -class LspRenamePathSidebarCommand(LspWindowCommand): +class LspRenamePathCommand(LspWindowCommand): def run(self, paths: list[str] | None = None) -> None: old_path = paths[0] if paths else None path_name = Path(old_path or "").name @@ -28,7 +28,7 @@ def run(self, paths: list[str] | None = None) -> None: def on_done(self, old_path: str | None, new_name: str) -> None: if new_name: - self.window.run_command('lsp_rename_path', { + self.window.run_command('lsp_rename_file', { "new_name": new_name, "old_path": old_path }) @@ -55,7 +55,7 @@ def validate(self, path: str) -> bool: return len(path) > 0 -class LspRenamePathCommand(LspWindowCommand): +class LspRenameFileCommand(LspWindowCommand): capability = 'workspace.fileOperations.willRename' def is_enabled(self): From 1ae7ec45a7dc1e5241e1366f3d350ea90aa86546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Thu, 4 Jul 2024 00:46:02 +0200 Subject: [PATCH 033/129] always enable LspRenamePathCommand --- plugin/rename_file.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 799bb7a69..4c6ec1b52 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -13,6 +13,9 @@ class LspRenamePathCommand(LspWindowCommand): + def is_enabled(self): + return True + def run(self, paths: list[str] | None = None) -> None: old_path = paths[0] if paths else None path_name = Path(old_path or "").name From 61816445e486bcec50a1546767f61cb56612011e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Thu, 4 Jul 2024 00:46:33 +0200 Subject: [PATCH 034/129] handle OS errors --- plugin/rename_file.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 4c6ec1b52..b47b37c45 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -125,7 +125,10 @@ def rename_path(self, old_path: str, new_path: str) -> None: if not os.path.exists(new_dir): os.makedirs(new_dir) isdir = os.path.isdir(old_path) - os.rename(old_path, new_path) + try: + os.rename(old_path, new_path) + except: + sublime.status_message("Unable to rename") if isdir: for v in self.window.views(): file_name = v.file_name() From 181f17c25e92f9c43d0845eec6b9df7919a71383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Thu, 4 Jul 2024 12:23:59 +0200 Subject: [PATCH 035/129] except Exception --- plugin/rename_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index b47b37c45..399828671 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -127,7 +127,7 @@ def rename_path(self, old_path: str, new_path: str) -> None: isdir = os.path.isdir(old_path) try: os.rename(old_path, new_path) - except: + except Exception: sublime.status_message("Unable to rename") if isdir: for v in self.window.views(): From 18e854902c8f92e3731707e2d9340ab79b528069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sun, 18 Aug 2024 12:27:12 +0200 Subject: [PATCH 036/129] Remove "LSP: Rename File" commands, instead override Default ST commands --- Default.sublime-commands | 4 ---- boot.py | 6 +++--- plugin/rename_file.py | 37 ++++++++----------------------------- 3 files changed, 11 insertions(+), 36 deletions(-) diff --git a/Default.sublime-commands b/Default.sublime-commands index 2f64684d2..75bb04d95 100644 --- a/Default.sublime-commands +++ b/Default.sublime-commands @@ -151,10 +151,6 @@ "caption": "LSP: Rename", "command": "lsp_symbol_rename" }, - { - "caption": "LSP: Rename File", - "command": "lsp_rename_file" - }, { "caption": "LSP: Code Action", "command": "lsp_code_actions" diff --git a/boot.py b/boot.py index 16ce854b0..c4a791485 100644 --- a/boot.py +++ b/boot.py @@ -70,7 +70,7 @@ from .plugin.rename import LspHideRenameButtonsCommand from .plugin.rename import LspSymbolRenameCommand from .plugin.rename_file import LspRenameFileCommand -from .plugin.rename_file import LspRenamePathCommand +from .plugin.rename_file import RenameFileCommand from .plugin.save_command import LspSaveAllCommand from .plugin.save_command import LspSaveCommand from .plugin.selection_range import LspExpandSelectionCommand @@ -149,7 +149,7 @@ "LspSymbolReferencesCommand", "LspSymbolRenameCommand", "LspRenameFileCommand", - "LspRenamePathCommand", + "RenameFileCommand", "LspSymbolTypeDefinitionCommand", "LspToggleCodeLensesCommand", "LspToggleHoverPopupsCommand", @@ -267,7 +267,7 @@ def on_pre_close(self, view: sublime.View) -> None: def on_window_command(self, window: sublime.Window, command_name: str, args: dict) -> tuple[str, dict] | None: if command_name == "rename_path": - return ("lsp_rename_path", args) + return ("rename_file", args) def on_post_window_command(self, window: sublime.Window, command_name: str, args: dict[str, Any] | None) -> None: if command_name == "show_panel": diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 399828671..e24824010 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -12,13 +12,20 @@ import functools -class LspRenamePathCommand(LspWindowCommand): +# It is bad that this command is named RenameFileCommand, same as the command in Default/rename.py +# ST has a bug that prevents the RenameFileCommand to be override in on_window_command: +# https://github.com/sublimehq/sublime_text/issues/2234 +# So naming this command "RenameFileCommand" is one BAD way to override the rename behavior. +class RenameFileCommand(LspWindowCommand): def is_enabled(self): return True def run(self, paths: list[str] | None = None) -> None: old_path = paths[0] if paths else None path_name = Path(old_path or "").name + view = self.window.active_view() + if path_name == "" and view: + path_name = Path(view.file_name() or "").name v = self.window.show_input_panel( "(LSP) New Name:", path_name, @@ -37,27 +44,6 @@ def on_done(self, old_path: str | None, new_name: str) -> None: }) -class RenamePathInputHandler(sublime_plugin.TextInputHandler): - def __init__(self, path: str) -> None: - self.path = path - - def name(self) -> str: - return "new_name" - - def placeholder(self) -> str: - return self.path - - def initial_text(self) -> str: - return self.placeholder() - - def initial_selection(self) -> list[tuple[int, int]]: - name, _ext = os.path.splitext(self.path) - return [(0, len(name))] - - def validate(self, path: str) -> bool: - return len(path) > 0 - - class LspRenameFileCommand(LspWindowCommand): capability = 'workspace.fileOperations.willRename' @@ -67,13 +53,6 @@ def is_enabled(self): def want_event(self) -> bool: return False - def input(self, args: dict) -> sublime_plugin.TextInputHandler | None: - if "new_name" in args: - return None - view = self.window.active_view() - old_path = view.file_name() if view else None - return RenamePathInputHandler(Path(old_path or "").name) - def run( self, new_name: str, # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy From 51962f1fdcd138ae0647fe688a5065a169029164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sun, 18 Aug 2024 12:28:34 +0200 Subject: [PATCH 037/129] remove unused import --- plugin/rename_file.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index e24824010..a73447207 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -8,7 +8,6 @@ from urllib.parse import urljoin import os import sublime -import sublime_plugin import functools From 0e0daa7f627e6e522eb60840bb0aa2439b7e2653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 17 Feb 2025 17:21:13 +0100 Subject: [PATCH 038/129] prevent save prompt to show up --- plugin/rename_file.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index a73447207..5bf63563b 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -97,6 +97,7 @@ def handle(self, res: WorkspaceEdit | None, session_name: str, def rename_path(self, old_path: str, new_path: str) -> None: old_regions: list[sublime.Region] = [] if view := self.window.find_open_file(old_path): + view.run_command('save', {'async': False}) old_regions = [region for region in view.sel()] view.close() # LSP spec - send didClose for the old file new_dir = Path(new_path).parent From 51670f9708bc6ca458924af08195ab982ddd9bac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 17 Feb 2025 17:22:18 +0100 Subject: [PATCH 039/129] Introduce "LSP: Rename Path" and rename "LSP: Rename" to "LSP: Rename Symbol" --- Default.sublime-commands | 6 ++++- boot.py | 11 ++++---- plugin/rename_file.py | 57 +++++++++++++++++++++++----------------- 3 files changed, 44 insertions(+), 30 deletions(-) diff --git a/Default.sublime-commands b/Default.sublime-commands index 75bb04d95..c1e35f5e0 100644 --- a/Default.sublime-commands +++ b/Default.sublime-commands @@ -148,7 +148,11 @@ "command": "lsp_show_diagnostics_panel" }, { - "caption": "LSP: Rename", + "caption": "LSP: Rename Path", + "command": "lsp_rename_path" + }, + { + "caption": "LSP: Rename Symbol", "command": "lsp_symbol_rename" }, { diff --git a/boot.py b/boot.py index 8ea2f9626..e3d6b20c7 100644 --- a/boot.py +++ b/boot.py @@ -70,8 +70,8 @@ from .plugin.references import LspSymbolReferencesCommand from .plugin.rename import LspHideRenameButtonsCommand from .plugin.rename import LspSymbolRenameCommand -from .plugin.rename_file import LspRenameFileCommand -from .plugin.rename_file import RenameFileCommand +from .plugin.rename_file import LspRenameFromSidebarOverride +from .plugin.rename_file import LspRenamePathCommand from .plugin.save_command import LspSaveAllCommand from .plugin.save_command import LspSaveCommand from .plugin.selection_range import LspExpandSelectionCommand @@ -149,8 +149,8 @@ "LspSymbolImplementationCommand", "LspSymbolReferencesCommand", "LspSymbolRenameCommand", - "LspRenameFileCommand", - "RenameFileCommand", + "LspRenameFromSidebarOverride", + "LspRenamePathCommand", "LspSymbolTypeDefinitionCommand", "LspToggleCodeLensesCommand", "LspToggleHoverPopupsCommand", @@ -268,7 +268,8 @@ def on_pre_close(self, view: sublime.View) -> None: def on_window_command(self, window: sublime.Window, command_name: str, args: dict) -> tuple[str, dict] | None: if command_name == "rename_path": - return ("rename_file", args) + return ('lsp_rename_from_sidebar_override', args) + def on_post_window_command(self, window: sublime.Window, command_name: str, args: dict[str, Any] | None) -> None: if command_name == "show_panel": diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 5bf63563b..636b8c355 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -8,42 +8,44 @@ from urllib.parse import urljoin import os import sublime +import sublime_plugin import functools -# It is bad that this command is named RenameFileCommand, same as the command in Default/rename.py -# ST has a bug that prevents the RenameFileCommand to be override in on_window_command: -# https://github.com/sublimehq/sublime_text/issues/2234 -# So naming this command "RenameFileCommand" is one BAD way to override the rename behavior. -class RenameFileCommand(LspWindowCommand): +class LspRenameFromSidebarOverride(LspWindowCommand): def is_enabled(self): return True def run(self, paths: list[str] | None = None) -> None: old_path = paths[0] if paths else None - path_name = Path(old_path or "").name - view = self.window.active_view() - if path_name == "" and view: - path_name = Path(view.file_name() or "").name - v = self.window.show_input_panel( - "(LSP) New Name:", - path_name, - functools.partial(self.on_done, old_path), - None, - None) - v.sel().clear() - name, _ext = os.path.splitext(path_name) - v.sel().add(sublime.Region(0, len(name))) - - def on_done(self, old_path: str | None, new_name: str) -> None: - if new_name: - self.window.run_command('lsp_rename_file', { - "new_name": new_name, + if old_path: + self.window.run_command('lsp_rename_path', { "old_path": old_path }) -class LspRenameFileCommand(LspWindowCommand): +class RenamePathInputHandler(sublime_plugin.TextInputHandler): + def __init__(self, path: str) -> None: + self.path = path + + def name(self) -> str: + return "new_name" + + def placeholder(self) -> str: + return self.path + + def initial_text(self) -> str: + return self.placeholder() + + def initial_selection(self) -> list[tuple[int, int]]: + name, _ext = os.path.splitext(self.path) + return [(0, len(name))] + + def validate(self, path: str) -> bool: + return len(path) > 0 + + +class LspRenamePathCommand(LspWindowCommand): capability = 'workspace.fileOperations.willRename' def is_enabled(self): @@ -52,6 +54,13 @@ def is_enabled(self): def want_event(self) -> bool: return False + def input(self, args: dict) -> sublime_plugin.TextInputHandler | None: + if "new_name" in args: + return None + view = self.window.active_view() + old_path = view.file_name() if view else None + return RenamePathInputHandler(Path(old_path or "").name) + def run( self, new_name: str, # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy From f5307566106d156897f13cdcd7cbf7a892d9d3d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 17 Feb 2025 17:24:28 +0100 Subject: [PATCH 040/129] fix flake --- plugin/core/types.py | 1 - plugin/rename_file.py | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index 3094bb01f..1193431bb 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -7,7 +7,6 @@ from .protocol import FileOperationRegistrationOptions from .protocol import TextDocumentSyncKind from .protocol import ServerCapabilities -from .protocol import TextDocumentSyncKind from .protocol import TextDocumentSyncOptions from .url import filename_to_uri from .url import parse_uri diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 636b8c355..2424028f0 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -1,15 +1,13 @@ from __future__ import annotations - -from .core.types import match_file_operation_filters from .core.open import open_file_uri from .core.protocol import Notification, RenameFilesParams, Request, WorkspaceEdit from .core.registry import LspWindowCommand +from .core.types import match_file_operation_filters from pathlib import Path from urllib.parse import urljoin import os import sublime import sublime_plugin -import functools class LspRenameFromSidebarOverride(LspWindowCommand): From defedc7a1df2b5cd360c44b0775a16967e52283b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Fri, 21 Nov 2025 20:37:57 +0100 Subject: [PATCH 041/129] Update plugin/rename_file.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Chłodnicki --- plugin/rename_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 2424028f0..e069bbe2b 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -108,7 +108,7 @@ def rename_path(self, old_path: str, new_path: str) -> None: old_regions = [region for region in view.sel()] view.close() # LSP spec - send didClose for the old file new_dir = Path(new_path).parent - if not os.path.exists(new_dir): + if not new_dir.exists(): os.makedirs(new_dir) isdir = os.path.isdir(old_path) try: From 848e6cd265159b21c4d4bd376c2deed23f392006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Fri, 21 Nov 2025 20:38:07 +0100 Subject: [PATCH 042/129] Update plugin/rename_file.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Chłodnicki --- plugin/rename_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index e069bbe2b..5fed9a2cb 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -105,7 +105,7 @@ def rename_path(self, old_path: str, new_path: str) -> None: old_regions: list[sublime.Region] = [] if view := self.window.find_open_file(old_path): view.run_command('save', {'async': False}) - old_regions = [region for region in view.sel()] + old_regions = list(view.sel()) view.close() # LSP spec - send didClose for the old file new_dir = Path(new_path).parent if not new_dir.exists(): From 7cea6633b8ab00a17183c2aa1193f28379af44f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Fri, 21 Nov 2025 20:37:40 +0100 Subject: [PATCH 043/129] return early in case of exception --- plugin/rename_file.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 5fed9a2cb..3961e0fbc 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -115,6 +115,7 @@ def rename_path(self, old_path: str, new_path: str) -> None: os.rename(old_path, new_path) except Exception: sublime.status_message("Unable to rename") + return if isdir: for v in self.window.views(): file_name = v.file_name() From d198d3f27bc3d6ca7de15c971e10431581e34ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Fri, 21 Nov 2025 20:44:57 +0100 Subject: [PATCH 044/129] rename handle to _handle_response_async --- plugin/rename_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 3961e0fbc..f8768beb1 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -86,13 +86,13 @@ def run( request = Request.willRenameFiles(rename_file_params) session.send_request( request, - lambda res: self.handle(res, session.config.name, old_path, new_path, rename_file_params, view) + lambda res: self._handle_response_async(res, session.config.name, old_path, new_path, rename_file_params, view) ) else: self.rename_path(old_path, new_path) self.notify_did_rename(rename_file_params, new_path, view) - def handle(self, res: WorkspaceEdit | None, session_name: str, + def _handle_response_async(self, res: WorkspaceEdit | None, session_name: str, old_path: str, new_path: str, rename_file_params: RenameFilesParams, view: sublime.View | None) -> None: if session := self.session_by_name(session_name): # LSP spec - Apply WorkspaceEdit before the files are renamed From d1c2ba80143dac153566b4d604e040ea1f29eccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Fri, 21 Nov 2025 20:45:56 +0100 Subject: [PATCH 045/129] move session closer to where it is used --- plugin/rename_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index f8768beb1..4dea43a25 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -64,7 +64,6 @@ def run( new_name: str, # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy old_path: str | None = None, ) -> None: - session = self.session() view = self.window.active_view() old_path = old_path or view.file_name() if view else None if old_path is None: # handle renaming buffers @@ -81,6 +80,7 @@ def run( "oldUri": urljoin("file:", old_path), }] } + session = self.session() file_operation_options = session.get_capability('workspace.fileOperations.willRename') if session else None if session and file_operation_options and match_file_operation_filters(file_operation_options, old_path, view): request = Request.willRenameFiles(rename_file_params) From 26f53d3a3e62180abc7595699de76be5214cf1f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Fri, 21 Nov 2025 20:46:33 +0100 Subject: [PATCH 046/129] add return types --- plugin/rename_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 4dea43a25..1de380d11 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -11,7 +11,7 @@ class LspRenameFromSidebarOverride(LspWindowCommand): - def is_enabled(self): + def is_enabled(self) -> bool: return True def run(self, paths: list[str] | None = None) -> None: @@ -46,7 +46,7 @@ def validate(self, path: str) -> bool: class LspRenamePathCommand(LspWindowCommand): capability = 'workspace.fileOperations.willRename' - def is_enabled(self): + def is_enabled(self) -> bool: return True def want_event(self) -> bool: From f292c0c36dee7031e86e373ea5312b9d81400a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Fri, 21 Nov 2025 20:47:29 +0100 Subject: [PATCH 047/129] use filename_to_uri --- plugin/rename_file.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 1de380d11..2a0aacde7 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -3,8 +3,8 @@ from .core.protocol import Notification, RenameFilesParams, Request, WorkspaceEdit from .core.registry import LspWindowCommand from .core.types import match_file_operation_filters +from .core.url import filename_to_uri from pathlib import Path -from urllib.parse import urljoin import os import sublime import sublime_plugin @@ -76,8 +76,8 @@ def run( return rename_file_params: RenameFilesParams = { "files": [{ - "newUri": urljoin("file:", new_path), - "oldUri": urljoin("file:", old_path), + "newUri": filename_to_uri(new_path), + "oldUri": filename_to_uri(old_path), }] } session = self.session() From 2ffe9485a77cdfdcdedbd401fb7076fda18c9e10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Fri, 21 Nov 2025 20:50:00 +0100 Subject: [PATCH 048/129] dont use abbreviations --- plugin/rename_file.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 2a0aacde7..07d3962ab 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -86,18 +86,18 @@ def run( request = Request.willRenameFiles(rename_file_params) session.send_request( request, - lambda res: self._handle_response_async(res, session.config.name, old_path, new_path, rename_file_params, view) + lambda response: self._handle_response_async(response, session.config.name, old_path, new_path, rename_file_params, view) ) else: self.rename_path(old_path, new_path) self.notify_did_rename(rename_file_params, new_path, view) - def _handle_response_async(self, res: WorkspaceEdit | None, session_name: str, + def _handle_response_async(self, response: WorkspaceEdit | None, session_name: str, old_path: str, new_path: str, rename_file_params: RenameFilesParams, view: sublime.View | None) -> None: if session := self.session_by_name(session_name): # LSP spec - Apply WorkspaceEdit before the files are renamed - if res: - session.apply_workspace_edit_async(res, is_refactoring=True) + if response: + session.apply_workspace_edit_async(response, is_refactoring=True) self.rename_path(old_path, new_path) self.notify_did_rename(rename_file_params, new_path, view) @@ -110,29 +110,29 @@ def rename_path(self, old_path: str, new_path: str) -> None: new_dir = Path(new_path).parent if not new_dir.exists(): os.makedirs(new_dir) - isdir = os.path.isdir(old_path) + is_directory = os.path.isdir(old_path) try: os.rename(old_path, new_path) except Exception: sublime.status_message("Unable to rename") return - if isdir: - for v in self.window.views(): - file_name = v.file_name() + if is_directory: + for view in self.window.views(): + file_name = view.file_name() if file_name and file_name.startswith(old_path): - v.retarget(file_name.replace(old_path, new_path)) + view.retarget(file_name.replace(old_path, new_path)) if os.path.isfile(new_path): - def restore_regions(v: sublime.View | None) -> None: - if not v: + def restore_regions(view: sublime.View | None) -> None: + if not view: return - v.sel().clear() - v.sel().add_all(old_regions) + view.sel().clear() + view.sel().add_all(old_regions) # LSP spec - send didOpen for the new file open_file_uri(self.window, new_path).then(restore_regions) def notify_did_rename(self, rename_file_params: RenameFilesParams, path: str, view: sublime.View | None): - for s in self.sessions(): - file_operation_options = s.get_capability('workspace.fileOperations.didRename') + for session in self.sessions(): + file_operation_options = session.get_capability('workspace.fileOperations.didRename') if file_operation_options and match_file_operation_filters(file_operation_options, path, view): - s.send_notification(Notification.didRenameFiles(rename_file_params)) + session.send_notification(Notification.didRenameFiles(rename_file_params)) From 50a80aa0162a6e1450d5d2a6015164f04781353f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 22 Nov 2025 20:36:34 +0100 Subject: [PATCH 049/129] pattern and filters are required fields in the LSP spec --- plugin/core/types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index b5254f6b4..55edffd85 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -455,7 +455,7 @@ def match_file_operation_filters( file_operation_options: FileOperationRegistrationOptions, path: str, view: sublime.View | None ) -> bool: def matches(file_operation_filter: FileOperationFilter) -> bool: - pattern = file_operation_filter.get('pattern') + pattern = file_operation_filter['pattern'] scheme = file_operation_filter.get('scheme') if scheme and view: uri = view.settings().get("lsp_uri") @@ -475,7 +475,7 @@ def matches(file_operation_filter: FileOperationFilter) -> bool: return False return True - filters = file_operation_options.get('filters') + filters = file_operation_options['filters'] return any(matches(_filter) for _filter in filters) if filters else True From 54626f4e85b93bec93ca2c2f6bd00f623dcae48a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 22 Nov 2025 20:40:19 +0100 Subject: [PATCH 050/129] import from ..protocol --- plugin/rename_file.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 07d3962ab..94db512e1 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -1,6 +1,8 @@ from __future__ import annotations +from ..protocol import WorkspaceEdit +from ..protocol import RenameFilesParams from .core.open import open_file_uri -from .core.protocol import Notification, RenameFilesParams, Request, WorkspaceEdit +from .core.protocol import Notification, Request from .core.registry import LspWindowCommand from .core.types import match_file_operation_filters from .core.url import filename_to_uri From 9c608feabcf4a35b13c9083a21291f298068c10c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 22 Nov 2025 20:56:47 +0100 Subject: [PATCH 051/129] remove view arg in match_file_operation_filters and update code --- plugin/core/types.py | 16 ++++++++-------- plugin/rename_file.py | 21 ++++++++++++--------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index 55edffd85..f03fb2624 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -5,6 +5,7 @@ from ...protocol import ServerCapabilities from ...protocol import TextDocumentSyncKind from ...protocol import TextDocumentSyncOptions +from ...protocol import URI from .collections import DottedDict from .constants import LANGUAGE_IDENTIFIERS from .file_watcher import FileWatcherEventType @@ -452,26 +453,25 @@ def matches(self, view: sublime.View) -> bool: def match_file_operation_filters( - file_operation_options: FileOperationRegistrationOptions, path: str, view: sublime.View | None + file_operation_options: FileOperationRegistrationOptions, uri: URI ) -> bool: def matches(file_operation_filter: FileOperationFilter) -> bool: + uri_scheme, uri_path = parse_uri(uri) pattern = file_operation_filter['pattern'] scheme = file_operation_filter.get('scheme') - if scheme and view: - uri = view.settings().get("lsp_uri") - if isinstance(uri, str) and parse_uri(uri)[0] != scheme: - return False + if scheme and uri_scheme != scheme: + return False if pattern: matches = pattern.get('matches') - if matches == FileOperationPatternKind.File and os.path.isdir(path): + if matches == FileOperationPatternKind.File and os.path.isdir(uri_path): return False - if matches == FileOperationPatternKind.Folder and os.path.isfile(path): + if matches == FileOperationPatternKind.Folder and os.path.isfile(uri_path): return False options = pattern.get('options', {}) flags = GLOBSTAR | BRACE if options.get('ignoreCase', False): flags |= IGNORECASE - if not globmatch(path, pattern['glob'], flags=flags): + if not globmatch(uri_path, pattern['glob'], flags=flags): return False return True diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 94db512e1..9345c5808 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -76,32 +76,34 @@ def run( if os.path.exists(new_path): self.window.status_message('Unable to Rename. Already exists') return + new_path_uri = filename_to_uri(new_path) + old_path_uri = filename_to_uri(old_path) rename_file_params: RenameFilesParams = { "files": [{ - "newUri": filename_to_uri(new_path), - "oldUri": filename_to_uri(old_path), + "newUri": new_path_uri, + "oldUri": old_path_uri }] } session = self.session() file_operation_options = session.get_capability('workspace.fileOperations.willRename') if session else None - if session and file_operation_options and match_file_operation_filters(file_operation_options, old_path, view): + if session and file_operation_options and match_file_operation_filters(file_operation_options, old_path_uri): request = Request.willRenameFiles(rename_file_params) session.send_request( request, - lambda response: self._handle_response_async(response, session.config.name, old_path, new_path, rename_file_params, view) + lambda response: self._handle_response_async(response, session.config.name, old_path, new_path, rename_file_params) ) else: self.rename_path(old_path, new_path) - self.notify_did_rename(rename_file_params, new_path, view) + self.notify_did_rename(rename_file_params) def _handle_response_async(self, response: WorkspaceEdit | None, session_name: str, - old_path: str, new_path: str, rename_file_params: RenameFilesParams, view: sublime.View | None) -> None: + old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None: if session := self.session_by_name(session_name): # LSP spec - Apply WorkspaceEdit before the files are renamed if response: session.apply_workspace_edit_async(response, is_refactoring=True) self.rename_path(old_path, new_path) - self.notify_did_rename(rename_file_params, new_path, view) + self.notify_did_rename(rename_file_params) def rename_path(self, old_path: str, new_path: str) -> None: old_regions: list[sublime.Region] = [] @@ -133,8 +135,9 @@ def restore_regions(view: sublime.View | None) -> None: # LSP spec - send didOpen for the new file open_file_uri(self.window, new_path).then(restore_regions) - def notify_did_rename(self, rename_file_params: RenameFilesParams, path: str, view: sublime.View | None): + def notify_did_rename(self, rename_file_params: RenameFilesParams): for session in self.sessions(): file_operation_options = session.get_capability('workspace.fileOperations.didRename') - if file_operation_options and match_file_operation_filters(file_operation_options, path, view): + old_uri = rename_file_params['files'][0]['oldUri'] + if file_operation_options and match_file_operation_filters(file_operation_options, old_uri): session.send_notification(Notification.didRenameFiles(rename_file_params)) From 6b8fd30d8eab67d634855115bfc5a6fd72227f5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 22 Nov 2025 21:10:52 +0100 Subject: [PATCH 052/129] trigger rename_path and notify_did_rename after apply_workspace_edit_async promise resolved --- plugin/rename_file.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 9345c5808..3e3aa88b6 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -98,13 +98,14 @@ def run( def _handle_response_async(self, response: WorkspaceEdit | None, session_name: str, old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None: - if session := self.session_by_name(session_name): - # LSP spec - Apply WorkspaceEdit before the files are renamed - if response: - session.apply_workspace_edit_async(response, is_refactoring=True) + + def on_workspace_edits_applied(): self.rename_path(old_path, new_path) self.notify_did_rename(rename_file_params) + if (session := self.session_by_name(session_name)) and response: + session.apply_workspace_edit_async(response, is_refactoring=True).then(lambda _: on_workspace_edits_applied()) + def rename_path(self, old_path: str, new_path: str) -> None: old_regions: list[sublime.Region] = [] if view := self.window.find_open_file(old_path): From 672cdf206b11c7e03537ee3feb527e8f5339834e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 22 Nov 2025 21:12:23 +0100 Subject: [PATCH 053/129] Update boot.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Chłodnicki --- boot.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/boot.py b/boot.py index bf9750f75..facf4f07d 100644 --- a/boot.py +++ b/boot.py @@ -277,8 +277,7 @@ def on_pre_close(self, view: sublime.View) -> None: def on_window_command(self, window: sublime.Window, command_name: str, args: dict) -> tuple[str, dict] | None: if command_name == "rename_path": return ('lsp_rename_from_sidebar_override', args) - - + def on_post_window_command(self, window: sublime.Window, command_name: str, args: dict[str, Any] | None) -> None: if command_name == "show_panel": wm = windows.lookup(window) From 4fcac0e1679d0ae03e29647d0f617d7b90068b36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 22 Nov 2025 21:22:05 +0100 Subject: [PATCH 054/129] use path resolve and inline run method signature --- plugin/rename_file.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 3e3aa88b6..fb5a91ffc 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -61,21 +61,18 @@ def input(self, args: dict) -> sublime_plugin.TextInputHandler | None: old_path = view.file_name() if view else None return RenamePathInputHandler(Path(old_path or "").name) - def run( - self, - new_name: str, # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy - old_path: str | None = None, - ) -> None: + def run(self, new_name: str, old_path: str | None = None) -> None: view = self.window.active_view() old_path = old_path or view.file_name() if view else None if old_path is None: # handle renaming buffers if view: view.set_name(new_name) return - new_path = os.path.normpath(Path(old_path).parent / new_name) - if os.path.exists(new_path): - self.window.status_message('Unable to Rename. Already exists') - return + # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy + resolved_new_path = (Path(old_path).parent / new_name).resolve() + if resolved_new_path.exists(): + return self.window.status_message('Unable to Rename. Already exists') + new_path = str(resolved_new_path) new_path_uri = filename_to_uri(new_path) old_path_uri = filename_to_uri(old_path) rename_file_params: RenameFilesParams = { From ca1247750ec6e6083156fe440187c9a8f0432196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 22 Nov 2025 21:31:58 +0100 Subject: [PATCH 055/129] make it more obvious that this is the file_name --- plugin/core/types.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index f03fb2624..87718512e 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -456,22 +456,22 @@ def match_file_operation_filters( file_operation_options: FileOperationRegistrationOptions, uri: URI ) -> bool: def matches(file_operation_filter: FileOperationFilter) -> bool: - uri_scheme, uri_path = parse_uri(uri) + uri_scheme, file_name = parse_uri(uri) pattern = file_operation_filter['pattern'] scheme = file_operation_filter.get('scheme') if scheme and uri_scheme != scheme: return False if pattern: matches = pattern.get('matches') - if matches == FileOperationPatternKind.File and os.path.isdir(uri_path): + if matches == FileOperationPatternKind.File and os.path.isdir(file_name): return False - if matches == FileOperationPatternKind.Folder and os.path.isfile(uri_path): + if matches == FileOperationPatternKind.Folder and os.path.isfile(file_name): return False options = pattern.get('options', {}) flags = GLOBSTAR | BRACE if options.get('ignoreCase', False): flags |= IGNORECASE - if not globmatch(uri_path, pattern['glob'], flags=flags): + if not globmatch(file_name, pattern['glob'], flags=flags): return False return True From c7f26ff5d350e3bccea99a1fdb5364b6683ec36b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 22 Nov 2025 22:39:56 +0100 Subject: [PATCH 056/129] fix rename folder from sidebar --- plugin/rename_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index fb5a91ffc..97d1ca975 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -58,7 +58,7 @@ def input(self, args: dict) -> sublime_plugin.TextInputHandler | None: if "new_name" in args: return None view = self.window.active_view() - old_path = view.file_name() if view else None + old_path = args.get('old_path') or view.file_name() if view else None return RenamePathInputHandler(Path(old_path or "").name) def run(self, new_name: str, old_path: str | None = None) -> None: From fb9a7107827793fc5973fc5d0ef4f8149631f5ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 22 Nov 2025 22:53:14 +0100 Subject: [PATCH 057/129] remove if for pattern --- plugin/core/types.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index 87718512e..d9714801e 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -461,18 +461,17 @@ def matches(file_operation_filter: FileOperationFilter) -> bool: scheme = file_operation_filter.get('scheme') if scheme and uri_scheme != scheme: return False - if pattern: - matches = pattern.get('matches') - if matches == FileOperationPatternKind.File and os.path.isdir(file_name): - return False - if matches == FileOperationPatternKind.Folder and os.path.isfile(file_name): - return False - options = pattern.get('options', {}) - flags = GLOBSTAR | BRACE - if options.get('ignoreCase', False): - flags |= IGNORECASE - if not globmatch(file_name, pattern['glob'], flags=flags): - return False + matches = pattern.get('matches') + if matches == FileOperationPatternKind.File and os.path.isdir(file_name): + return False + if matches == FileOperationPatternKind.Folder and os.path.isfile(file_name): + return False + options = pattern.get('options', {}) + flags = GLOBSTAR | BRACE + if options.get('ignoreCase', False): + flags |= IGNORECASE + if not globmatch(file_name, pattern['glob'], flags=flags): + return False return True filters = file_operation_options['filters'] From 06accb266a7118a51234e76ecd99843cc6cef9b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 22 Nov 2025 22:53:41 +0100 Subject: [PATCH 058/129] Update plugin/core/types.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Chłodnicki --- plugin/core/types.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index d9714801e..8ad222785 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -452,9 +452,7 @@ def matches(self, view: sublime.View) -> bool: return any(f(view) for f in self.filters) if self.filters else True -def match_file_operation_filters( - file_operation_options: FileOperationRegistrationOptions, uri: URI -) -> bool: +def match_file_operation_filters(file_operation_options: FileOperationRegistrationOptions, uri: URI) -> bool: def matches(file_operation_filter: FileOperationFilter) -> bool: uri_scheme, file_name = parse_uri(uri) pattern = file_operation_filter['pattern'] From 28b1e1b72794f375f01f6feaa703aab6fe582cdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 22 Nov 2025 22:54:01 +0100 Subject: [PATCH 059/129] Update plugin/rename_file.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Chłodnicki --- plugin/rename_file.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 97d1ca975..66e870dea 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -71,7 +71,8 @@ def run(self, new_name: str, old_path: str | None = None) -> None: # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy resolved_new_path = (Path(old_path).parent / new_name).resolve() if resolved_new_path.exists(): - return self.window.status_message('Unable to Rename. Already exists') + self.window.status_message('Unable to Rename. Already exists') + return new_path = str(resolved_new_path) new_path_uri = filename_to_uri(new_path) old_path_uri = filename_to_uri(old_path) From add6233c42cce8146ddb2e65c4368f09d8286f84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 22 Nov 2025 23:14:19 +0100 Subject: [PATCH 060/129] fix flake --- plugin/rename_file.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 66e870dea..85340808d 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -1,5 +1,5 @@ from __future__ import annotations -from ..protocol import WorkspaceEdit +from ..protocol import WorkspaceEdit from ..protocol import RenameFilesParams from .core.open import open_file_uri from .core.protocol import Notification, Request @@ -88,21 +88,21 @@ def run(self, new_name: str, old_path: str | None = None) -> None: request = Request.willRenameFiles(rename_file_params) session.send_request( request, - lambda response: self._handle_response_async(response, session.config.name, old_path, new_path, rename_file_params) + lambda response: self._handle_response_async(response, session.config.name, + old_path, new_path, rename_file_params) ) else: self.rename_path(old_path, new_path) self.notify_did_rename(rename_file_params) def _handle_response_async(self, response: WorkspaceEdit | None, session_name: str, - old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None: - - def on_workspace_edits_applied(): + old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None: + def on_workspace_edits_applied(_) -> None: self.rename_path(old_path, new_path) self.notify_did_rename(rename_file_params) if (session := self.session_by_name(session_name)) and response: - session.apply_workspace_edit_async(response, is_refactoring=True).then(lambda _: on_workspace_edits_applied()) + session.apply_workspace_edit_async(response, is_refactoring=True).then(on_workspace_edits_applied) def rename_path(self, old_path: str, new_path: str) -> None: old_regions: list[sublime.Region] = [] From e004c0b62646315e3b90f785421b1d7fdeb1c898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Fri, 28 Nov 2025 20:43:00 +0100 Subject: [PATCH 061/129] early return if new_name is like old name --- plugin/rename_file.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 85340808d..fd2c78d2d 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -68,6 +68,8 @@ def run(self, new_name: str, old_path: str | None = None) -> None: if view: view.set_name(new_name) return + if new_name == old_path: + return # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy resolved_new_path = (Path(old_path).parent / new_name).resolve() if resolved_new_path.exists(): From d0b7d49bd447e9f0824fd9507dbf821770a7fcfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Fri, 28 Nov 2025 20:48:25 +0100 Subject: [PATCH 062/129] have same check for is_case_change as ST native rename command --- plugin/rename_file.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index fd2c78d2d..cab17727c 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -72,10 +72,10 @@ def run(self, new_name: str, old_path: str | None = None) -> None: return # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy resolved_new_path = (Path(old_path).parent / new_name).resolve() - if resolved_new_path.exists(): + new_path = str(resolved_new_path) + if resolved_new_path.exists() and not self.is_case_change(old_path, new_path): self.window.status_message('Unable to Rename. Already exists') return - new_path = str(resolved_new_path) new_path_uri = filename_to_uri(new_path) old_path_uri = filename_to_uri(old_path) rename_file_params: RenameFilesParams = { @@ -97,6 +97,13 @@ def run(self, new_name: str, old_path: str | None = None) -> None: self.rename_path(old_path, new_path) self.notify_did_rename(rename_file_params) + def is_case_change(self, path_a: str, path_b: str) -> bool: + if path_a.lower() != path_b.lower(): + return False + if os.stat(path_a).st_ino != os.stat(path_b).st_ino: + return False + return True + def _handle_response_async(self, response: WorkspaceEdit | None, session_name: str, old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None: def on_workspace_edits_applied(_) -> None: From a5c06091dd2baf0e5ca24271e5df63debfdaad20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Fri, 28 Nov 2025 20:50:11 +0100 Subject: [PATCH 063/129] be consistent, this was the only method that started with _ --- plugin/rename_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index cab17727c..58a715e53 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -90,7 +90,7 @@ def run(self, new_name: str, old_path: str | None = None) -> None: request = Request.willRenameFiles(rename_file_params) session.send_request( request, - lambda response: self._handle_response_async(response, session.config.name, + lambda response: self.handle_response_async(response, session.config.name, old_path, new_path, rename_file_params) ) else: @@ -104,7 +104,7 @@ def is_case_change(self, path_a: str, path_b: str) -> bool: return False return True - def _handle_response_async(self, response: WorkspaceEdit | None, session_name: str, + def handle_response_async(self, response: WorkspaceEdit | None, session_name: str, old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None: def on_workspace_edits_applied(_) -> None: self.rename_path(old_path, new_path) From 9f667a38b59b1c523e8ef0c8ac0fe2d4c2aeea22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Fri, 28 Nov 2025 21:05:49 +0100 Subject: [PATCH 064/129] notify_did_rename only after a successful rename --- plugin/rename_file.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 58a715e53..b8bc02c65 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -94,8 +94,7 @@ def run(self, new_name: str, old_path: str | None = None) -> None: old_path, new_path, rename_file_params) ) else: - self.rename_path(old_path, new_path) - self.notify_did_rename(rename_file_params) + self.rename_path(old_path, new_path, rename_file_params) def is_case_change(self, path_a: str, path_b: str) -> bool: if path_a.lower() != path_b.lower(): @@ -107,13 +106,12 @@ def is_case_change(self, path_a: str, path_b: str) -> bool: def handle_response_async(self, response: WorkspaceEdit | None, session_name: str, old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None: def on_workspace_edits_applied(_) -> None: - self.rename_path(old_path, new_path) - self.notify_did_rename(rename_file_params) + self.rename_path(old_path, new_path, rename_file_params) if (session := self.session_by_name(session_name)) and response: session.apply_workspace_edit_async(response, is_refactoring=True).then(on_workspace_edits_applied) - def rename_path(self, old_path: str, new_path: str) -> None: + def rename_path(self, old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None: old_regions: list[sublime.Region] = [] if view := self.window.find_open_file(old_path): view.run_command('save', {'async': False}) @@ -125,6 +123,7 @@ def rename_path(self, old_path: str, new_path: str) -> None: is_directory = os.path.isdir(old_path) try: os.rename(old_path, new_path) + self.notify_did_rename(rename_file_params) except Exception: sublime.status_message("Unable to rename") return From 7f1d82971a2ec67c7ae97a4914ef04586eb477b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 29 Nov 2025 19:47:37 +0100 Subject: [PATCH 065/129] Update plugin/rename_file.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Chłodnicki --- plugin/rename_file.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index b8bc02c65..bb99aa7b6 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -87,9 +87,8 @@ def run(self, new_name: str, old_path: str | None = None) -> None: session = self.session() file_operation_options = session.get_capability('workspace.fileOperations.willRename') if session else None if session and file_operation_options and match_file_operation_filters(file_operation_options, old_path_uri): - request = Request.willRenameFiles(rename_file_params) session.send_request( - request, + Request.willRenameFiles(rename_file_params), lambda response: self.handle_response_async(response, session.config.name, old_path, new_path, rename_file_params) ) From a83b9566843f729fa853c9eef6a7ef5eeea8798f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 29 Nov 2025 19:47:49 +0100 Subject: [PATCH 066/129] Update plugin/rename_file.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Chłodnicki --- plugin/rename_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index bb99aa7b6..5204dc3d3 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -90,7 +90,7 @@ def run(self, new_name: str, old_path: str | None = None) -> None: session.send_request( Request.willRenameFiles(rename_file_params), lambda response: self.handle_response_async(response, session.config.name, - old_path, new_path, rename_file_params) + old_path, new_path, rename_file_params) ) else: self.rename_path(old_path, new_path, rename_file_params) From 4089bfe3bd2372c58d462ce9aff56f7e8dd5d538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 29 Nov 2025 19:48:30 +0100 Subject: [PATCH 067/129] Update plugin/rename_file.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Chłodnicki --- plugin/rename_file.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 5204dc3d3..7bf5cc69d 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -96,11 +96,7 @@ def run(self, new_name: str, old_path: str | None = None) -> None: self.rename_path(old_path, new_path, rename_file_params) def is_case_change(self, path_a: str, path_b: str) -> bool: - if path_a.lower() != path_b.lower(): - return False - if os.stat(path_a).st_ino != os.stat(path_b).st_ino: - return False - return True + return path_a.lower() == path_b.lower() and os.stat(path_a).st_ino == os.stat(path_b).st_ino def handle_response_async(self, response: WorkspaceEdit | None, session_name: str, old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None: From 02bbbd8feb359ea679db83aaad4d307098ad2989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 29 Nov 2025 19:49:09 +0100 Subject: [PATCH 068/129] Update plugin/rename_file.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Chłodnicki --- plugin/rename_file.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 7bf5cc69d..28c0f584c 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -100,11 +100,10 @@ def is_case_change(self, path_a: str, path_b: str) -> bool: def handle_response_async(self, response: WorkspaceEdit | None, session_name: str, old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None: - def on_workspace_edits_applied(_) -> None: - self.rename_path(old_path, new_path, rename_file_params) - if (session := self.session_by_name(session_name)) and response: - session.apply_workspace_edit_async(response, is_refactoring=True).then(on_workspace_edits_applied) + session.apply_workspace_edit_async(response, is_refactoring=True) \ + .then(lambda _: self.rename_path(old_path, new_path, rename_file_params)) + def rename_path(self, old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None: old_regions: list[sublime.Region] = [] From 55dbdd7926ff90fdaaa50602ce762850a2d2e694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 29 Nov 2025 20:19:33 +0100 Subject: [PATCH 069/129] Use Path instead of os.path --- plugin/rename_file.py | 66 +++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 28c0f584c..c978bc8e1 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -1,13 +1,12 @@ from __future__ import annotations +from ..protocol import FileRename from ..protocol import WorkspaceEdit -from ..protocol import RenameFilesParams from .core.open import open_file_uri from .core.protocol import Notification, Request from .core.registry import LspWindowCommand from .core.types import match_file_operation_filters -from .core.url import filename_to_uri +from .core.url import filename_to_uri, parse_uri from pathlib import Path -import os import sublime import sublime_plugin @@ -38,7 +37,7 @@ def initial_text(self) -> str: return self.placeholder() def initial_selection(self) -> list[tuple[int, int]]: - name, _ext = os.path.splitext(self.path) + name = Path(self.path).stem return [(0, len(name))] def validate(self, path: str) -> bool: @@ -76,57 +75,51 @@ def run(self, new_name: str, old_path: str | None = None) -> None: if resolved_new_path.exists() and not self.is_case_change(old_path, new_path): self.window.status_message('Unable to Rename. Already exists') return - new_path_uri = filename_to_uri(new_path) - old_path_uri = filename_to_uri(old_path) - rename_file_params: RenameFilesParams = { - "files": [{ - "newUri": new_path_uri, - "oldUri": old_path_uri - }] + file_rename: FileRename = { + "newUri": filename_to_uri(new_path), + "oldUri": filename_to_uri(old_path) } session = self.session() file_operation_options = session.get_capability('workspace.fileOperations.willRename') if session else None - if session and file_operation_options and match_file_operation_filters(file_operation_options, old_path_uri): + if session and file_operation_options and match_file_operation_filters(file_operation_options, file_rename['oldUri']): session.send_request( - Request.willRenameFiles(rename_file_params), - lambda response: self.handle_response_async(response, session.config.name, - old_path, new_path, rename_file_params) + Request.willRenameFiles({'files': [file_rename] }), + lambda response: self.handle_response_async(response, session.config.name, file_rename) ) else: - self.rename_path(old_path, new_path, rename_file_params) + self.rename_path(file_rename) def is_case_change(self, path_a: str, path_b: str) -> bool: - return path_a.lower() == path_b.lower() and os.stat(path_a).st_ino == os.stat(path_b).st_ino + return path_a.lower() == path_b.lower() and Path(path_a).stat().st_ino == Path(path_b).stat().st_ino - def handle_response_async(self, response: WorkspaceEdit | None, session_name: str, - old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None: + def handle_response_async(self, response: WorkspaceEdit | None, session_name: str, file_rename: FileRename) -> None: if (session := self.session_by_name(session_name)) and response: session.apply_workspace_edit_async(response, is_refactoring=True) \ - .then(lambda _: self.rename_path(old_path, new_path, rename_file_params)) + .then(lambda _: self.rename_path(file_rename)) - - def rename_path(self, old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None: + def rename_path(self, file_rename: FileRename) -> None: + old_path = Path(parse_uri(file_rename['oldUri'])[1]) + new_path = Path(parse_uri(file_rename['newUri'])[1]) old_regions: list[sublime.Region] = [] - if view := self.window.find_open_file(old_path): + if view := self.window.find_open_file(str(old_path)): view.run_command('save', {'async': False}) old_regions = list(view.sel()) view.close() # LSP spec - send didClose for the old file - new_dir = Path(new_path).parent + new_dir = new_path.parent if not new_dir.exists(): - os.makedirs(new_dir) - is_directory = os.path.isdir(old_path) + new_dir.mkdir() try: - os.rename(old_path, new_path) - self.notify_did_rename(rename_file_params) + old_path.rename(new_path) + self.notify_did_rename(file_rename) except Exception: sublime.status_message("Unable to rename") return - if is_directory: + if old_path.is_dir(): for view in self.window.views(): file_name = view.file_name() - if file_name and file_name.startswith(old_path): - view.retarget(file_name.replace(old_path, new_path)) - if os.path.isfile(new_path): + if file_name and file_name.startswith(str(old_path)): + view.retarget(file_name.replace(str(old_path), str(new_path))) + if new_path.is_file(): def restore_regions(view: sublime.View | None) -> None: if not view: return @@ -134,11 +127,10 @@ def restore_regions(view: sublime.View | None) -> None: view.sel().add_all(old_regions) # LSP spec - send didOpen for the new file - open_file_uri(self.window, new_path).then(restore_regions) + open_file_uri(self.window, str(new_path)).then(restore_regions) - def notify_did_rename(self, rename_file_params: RenameFilesParams): + def notify_did_rename(self, file_rename: FileRename): for session in self.sessions(): file_operation_options = session.get_capability('workspace.fileOperations.didRename') - old_uri = rename_file_params['files'][0]['oldUri'] - if file_operation_options and match_file_operation_filters(file_operation_options, old_uri): - session.send_notification(Notification.didRenameFiles(rename_file_params)) + if file_operation_options and match_file_operation_filters(file_operation_options, file_rename['oldUri']): + session.send_notification(Notification.didRenameFiles({'files': [file_rename] })) From ca5246278cc639162fe78e4eec13a1eca59bf8c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 29 Nov 2025 20:22:31 +0100 Subject: [PATCH 070/129] flake --- plugin/rename_file.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index c978bc8e1..225d71da6 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -80,10 +80,10 @@ def run(self, new_name: str, old_path: str | None = None) -> None: "oldUri": filename_to_uri(old_path) } session = self.session() - file_operation_options = session.get_capability('workspace.fileOperations.willRename') if session else None - if session and file_operation_options and match_file_operation_filters(file_operation_options, file_rename['oldUri']): + file_operations = session.get_capability('workspace.fileOperations.willRename') if session else None + if session and file_operations and match_file_operation_filters(file_operations, file_rename['oldUri']): session.send_request( - Request.willRenameFiles({'files': [file_rename] }), + Request.willRenameFiles({'files': [file_rename]}), lambda response: self.handle_response_async(response, session.config.name, file_rename) ) else: @@ -133,4 +133,4 @@ def notify_did_rename(self, file_rename: FileRename): for session in self.sessions(): file_operation_options = session.get_capability('workspace.fileOperations.didRename') if file_operation_options and match_file_operation_filters(file_operation_options, file_rename['oldUri']): - session.send_notification(Notification.didRenameFiles({'files': [file_rename] })) + session.send_notification(Notification.didRenameFiles({'files': [file_rename]})) From dc23125990521b90286b06aea77105068ed1573f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 29 Nov 2025 20:24:37 +0100 Subject: [PATCH 071/129] move a bit closer to the if branch --- plugin/rename_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 225d71da6..ae11fc71d 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -75,12 +75,12 @@ def run(self, new_name: str, old_path: str | None = None) -> None: if resolved_new_path.exists() and not self.is_case_change(old_path, new_path): self.window.status_message('Unable to Rename. Already exists') return + session = self.session() + file_operations = session.get_capability('workspace.fileOperations.willRename') if session else None file_rename: FileRename = { "newUri": filename_to_uri(new_path), "oldUri": filename_to_uri(old_path) } - session = self.session() - file_operations = session.get_capability('workspace.fileOperations.willRename') if session else None if session and file_operations and match_file_operation_filters(file_operations, file_rename['oldUri']): session.send_request( Request.willRenameFiles({'files': [file_rename]}), From 092a179b8fe7e27b4faf13ef04ffd5eb1a828acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 29 Nov 2025 20:25:25 +0100 Subject: [PATCH 072/129] rename file_operation_options to file_operations for consistency --- plugin/rename_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index ae11fc71d..204e2f2cf 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -131,6 +131,6 @@ def restore_regions(view: sublime.View | None) -> None: def notify_did_rename(self, file_rename: FileRename): for session in self.sessions(): - file_operation_options = session.get_capability('workspace.fileOperations.didRename') - if file_operation_options and match_file_operation_filters(file_operation_options, file_rename['oldUri']): + file_operations = session.get_capability('workspace.fileOperations.didRename') + if file_operations and match_file_operation_filters(file_operations, file_rename['oldUri']): session.send_notification(Notification.didRenameFiles({'files': [file_rename]})) From 4af0ae60dbd2551c80e34b3e89b7993842bb1efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 29 Nov 2025 20:29:31 +0100 Subject: [PATCH 073/129] move notify did rename later --- plugin/rename_file.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 204e2f2cf..e0d409996 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -110,7 +110,6 @@ def rename_path(self, file_rename: FileRename) -> None: new_dir.mkdir() try: old_path.rename(new_path) - self.notify_did_rename(file_rename) except Exception: sublime.status_message("Unable to rename") return @@ -127,7 +126,9 @@ def restore_regions(view: sublime.View | None) -> None: view.sel().add_all(old_regions) # LSP spec - send didOpen for the new file - open_file_uri(self.window, str(new_path)).then(restore_regions) + open_file_uri(self.window, str(new_path)).then(restore_regions) \ + .then(lambda _: self.notify_did_rename(file_rename)) + def notify_did_rename(self, file_rename: FileRename): for session in self.sessions(): From be87d19feeeef16ccc2ceaae0ad1e8ca89996e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 29 Nov 2025 21:06:45 +0100 Subject: [PATCH 074/129] check if old_path is a directory before we rename --- plugin/rename_file.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index e0d409996..c6e353485 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -106,6 +106,7 @@ def rename_path(self, file_rename: FileRename) -> None: old_regions = list(view.sel()) view.close() # LSP spec - send didClose for the old file new_dir = new_path.parent + old_path_is_dir = old_path.is_dir() if not new_dir.exists(): new_dir.mkdir() try: @@ -113,7 +114,7 @@ def rename_path(self, file_rename: FileRename) -> None: except Exception: sublime.status_message("Unable to rename") return - if old_path.is_dir(): + if old_path_is_dir: for view in self.window.views(): file_name = view.file_name() if file_name and file_name.startswith(str(old_path)): From 6afa7c2f78314666c20188853e424d14ca84f9f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 29 Nov 2025 21:49:36 +0100 Subject: [PATCH 075/129] fix flake --- plugin/rename_file.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index c6e353485..ce0baf59f 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -130,7 +130,6 @@ def restore_regions(view: sublime.View | None) -> None: open_file_uri(self.window, str(new_path)).then(restore_regions) \ .then(lambda _: self.notify_did_rename(file_rename)) - def notify_did_rename(self, file_rename: FileRename): for session in self.sessions(): file_operations = session.get_capability('workspace.fileOperations.didRename') From 72a162de1eeca594aed64a0b1068044536d72db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 29 Nov 2025 21:56:18 +0100 Subject: [PATCH 076/129] don't consider matching if filters is an empty array --- plugin/core/types.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index 8ad222785..1620b7c22 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -452,7 +452,7 @@ def matches(self, view: sublime.View) -> bool: return any(f(view) for f in self.filters) if self.filters else True -def match_file_operation_filters(file_operation_options: FileOperationRegistrationOptions, uri: URI) -> bool: +def match_file_operation_filters(file_operations: FileOperationRegistrationOptions, uri: URI) -> bool: def matches(file_operation_filter: FileOperationFilter) -> bool: uri_scheme, file_name = parse_uri(uri) pattern = file_operation_filter['pattern'] @@ -472,8 +472,7 @@ def matches(file_operation_filter: FileOperationFilter) -> bool: return False return True - filters = file_operation_options['filters'] - return any(matches(_filter) for _filter in filters) if filters else True + return any(matches(_filter) for _filter in file_operations['filters']) # method -> (capability dotted path, optional registration dotted path) From fbcbec591378fb37f48bd21e819929bed64e5ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 29 Nov 2025 22:37:25 +0100 Subject: [PATCH 077/129] send request to all sessions --- plugin/core/types.py | 5 ++++- plugin/rename_file.py | 32 +++++++++++++++++++++----------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index 1620b7c22..8337c0300 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -452,7 +452,10 @@ def matches(self, view: sublime.View) -> bool: return any(f(view) for f in self.filters) if self.filters else True -def match_file_operation_filters(file_operations: FileOperationRegistrationOptions, uri: URI) -> bool: +def match_file_operation_filters(file_operations: FileOperationRegistrationOptions | None, uri: URI) -> bool: + if not file_operations: + return False + def matches(file_operation_filter: FileOperationFilter) -> bool: uri_scheme, file_name = parse_uri(uri) pattern = file_operation_filter['pattern'] diff --git a/plugin/rename_file.py b/plugin/rename_file.py index ce0baf59f..d2d205d95 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -2,13 +2,16 @@ from ..protocol import FileRename from ..protocol import WorkspaceEdit from .core.open import open_file_uri +from .core.promise import Promise from .core.protocol import Notification, Request from .core.registry import LspWindowCommand +from .core.sessions import Session from .core.types import match_file_operation_filters from .core.url import filename_to_uri, parse_uri from pathlib import Path import sublime import sublime_plugin +import weakref class LspRenameFromSidebarOverride(LspWindowCommand): @@ -75,27 +78,34 @@ def run(self, new_name: str, old_path: str | None = None) -> None: if resolved_new_path.exists() and not self.is_case_change(old_path, new_path): self.window.status_message('Unable to Rename. Already exists') return - session = self.session() - file_operations = session.get_capability('workspace.fileOperations.willRename') if session else None file_rename: FileRename = { "newUri": filename_to_uri(new_path), "oldUri": filename_to_uri(old_path) } - if session and file_operations and match_file_operation_filters(file_operations, file_rename['oldUri']): - session.send_request( - Request.willRenameFiles({'files': [file_rename]}), - lambda response: self.handle_response_async(response, session.config.name, file_rename) - ) + + def create_request_async(session: Session) -> Promise[tuple[WorkspaceEdit | None, weakref.ref[Session]]]: + return session.send_request_task( + Request.willRenameFiles({'files': [file_rename]}) + ).then(lambda response: (response, weakref.ref(session))) + + sessions = [session for session in self.sessions() if match_file_operation_filters( + session.get_capability('workspace.fileOperations.willRename'), file_rename['oldUri'] + )] + promises = [create_request_async(session) for session in sessions] + if promises: + Promise.all(promises).then(lambda responses: self.handle_responses_async(responses, file_rename)) else: self.rename_path(file_rename) def is_case_change(self, path_a: str, path_b: str) -> bool: return path_a.lower() == path_b.lower() and Path(path_a).stat().st_ino == Path(path_b).stat().st_ino - def handle_response_async(self, response: WorkspaceEdit | None, session_name: str, file_rename: FileRename) -> None: - if (session := self.session_by_name(session_name)) and response: - session.apply_workspace_edit_async(response, is_refactoring=True) \ - .then(lambda _: self.rename_path(file_rename)) + def handle_responses_async(self, responses: list[tuple[WorkspaceEdit | None, weakref.ref[Session]]], + file_rename: FileRename) -> None: + for response, weak_session in responses: + if (session := weak_session()) and response: + session.apply_workspace_edit_async(response, is_refactoring=True) \ + .then(lambda _: self.rename_path(file_rename)) def rename_path(self, file_rename: FileRename) -> None: old_path = Path(parse_uri(file_rename['oldUri'])[1]) From 140ffca3206783bdcdc6fbc067c2e0724262d176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 29 Nov 2025 22:38:52 +0100 Subject: [PATCH 078/129] fix flake --- plugin/rename_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index d2d205d95..8c34c268a 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -101,7 +101,7 @@ def is_case_change(self, path_a: str, path_b: str) -> bool: return path_a.lower() == path_b.lower() and Path(path_a).stat().st_ino == Path(path_b).stat().st_ino def handle_responses_async(self, responses: list[tuple[WorkspaceEdit | None, weakref.ref[Session]]], - file_rename: FileRename) -> None: + file_rename: FileRename) -> None: for response, weak_session in responses: if (session := weak_session()) and response: session.apply_workspace_edit_async(response, is_refactoring=True) \ From 057391a0e6074018a34933b243d10b96b1f6a0cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 29 Nov 2025 23:46:07 +0100 Subject: [PATCH 079/129] schedule the requests to be done on the async thread --- plugin/rename_file.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 8c34c268a..c432f6e9d 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -93,7 +93,9 @@ def create_request_async(session: Session) -> Promise[tuple[WorkspaceEdit | None )] promises = [create_request_async(session) for session in sessions] if promises: - Promise.all(promises).then(lambda responses: self.handle_responses_async(responses, file_rename)) + sublime.set_timeout_async( + lambda: Promise.all(promises).then(lambda responses: self.handle_responses_async(responses, file_rename)) + ) else: self.rename_path(file_rename) From 48650c5498cd2959ab70d4daf1b50ec51d0d33e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 29 Nov 2025 23:49:12 +0100 Subject: [PATCH 080/129] fix flake by renaming the method --- plugin/rename_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index c432f6e9d..4973e7849 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -94,7 +94,7 @@ def create_request_async(session: Session) -> Promise[tuple[WorkspaceEdit | None promises = [create_request_async(session) for session in sessions] if promises: sublime.set_timeout_async( - lambda: Promise.all(promises).then(lambda responses: self.handle_responses_async(responses, file_rename)) + lambda: Promise.all(promises).then(lambda responses: self.handle_rename_async(responses, file_rename)) ) else: self.rename_path(file_rename) @@ -102,7 +102,7 @@ def create_request_async(session: Session) -> Promise[tuple[WorkspaceEdit | None def is_case_change(self, path_a: str, path_b: str) -> bool: return path_a.lower() == path_b.lower() and Path(path_a).stat().st_ino == Path(path_b).stat().st_ino - def handle_responses_async(self, responses: list[tuple[WorkspaceEdit | None, weakref.ref[Session]]], + def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakref.ref[Session]]], file_rename: FileRename) -> None: for response, weak_session in responses: if (session := weak_session()) and response: From 7e87b26104698066e6bf66e4d5773ec81407d276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 29 Nov 2025 23:51:29 +0100 Subject: [PATCH 081/129] visual indent --- plugin/rename_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 4973e7849..1acc1338d 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -103,7 +103,7 @@ def is_case_change(self, path_a: str, path_b: str) -> bool: return path_a.lower() == path_b.lower() and Path(path_a).stat().st_ino == Path(path_b).stat().st_ino def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakref.ref[Session]]], - file_rename: FileRename) -> None: + file_rename: FileRename) -> None: for response, weak_session in responses: if (session := weak_session()) and response: session.apply_workspace_edit_async(response, is_refactoring=True) \ From 0db107e4af3074e44983a45d0a1558508583a08e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sun, 30 Nov 2025 19:29:29 +0100 Subject: [PATCH 082/129] Update plugin/core/types.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Chłodnicki --- plugin/core/types.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index 8337c0300..5cd5c975a 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -471,9 +471,7 @@ def matches(file_operation_filter: FileOperationFilter) -> bool: flags = GLOBSTAR | BRACE if options.get('ignoreCase', False): flags |= IGNORECASE - if not globmatch(file_name, pattern['glob'], flags=flags): - return False - return True + return globmatch(file_name, pattern['glob'], flags=flags) return any(matches(_filter) for _filter in file_operations['filters']) From a485fef4954392cfd0297f4ef487a98740b24c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sun, 30 Nov 2025 19:30:44 +0100 Subject: [PATCH 083/129] this check makes more sense --- plugin/core/types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index 5cd5c975a..5e6429b99 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -463,9 +463,9 @@ def matches(file_operation_filter: FileOperationFilter) -> bool: if scheme and uri_scheme != scheme: return False matches = pattern.get('matches') - if matches == FileOperationPatternKind.File and os.path.isdir(file_name): + if matches == FileOperationPatternKind.File and not os.path.isfile(file_name): return False - if matches == FileOperationPatternKind.Folder and os.path.isfile(file_name): + if matches == FileOperationPatternKind.Folder and not os.path.isdir(file_name): return False options = pattern.get('options', {}) flags = GLOBSTAR | BRACE From 9cd3a8b01d62e3bc612b6d4befe0683dc0a1e93d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sun, 30 Nov 2025 19:41:09 +0100 Subject: [PATCH 084/129] slightly move line down --- plugin/rename_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 1acc1338d..64795e4ba 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -118,9 +118,9 @@ def rename_path(self, file_rename: FileRename) -> None: old_regions = list(view.sel()) view.close() # LSP spec - send didClose for the old file new_dir = new_path.parent - old_path_is_dir = old_path.is_dir() if not new_dir.exists(): new_dir.mkdir() + old_path_is_dir = old_path.is_dir() try: old_path.rename(new_path) except Exception: From 211d151d6d1078a65c42fa2a07804e8fec900be3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sun, 30 Nov 2025 19:44:55 +0100 Subject: [PATCH 085/129] Update plugin/rename_file.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Chłodnicki --- plugin/rename_file.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 64795e4ba..1226065de 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -88,10 +88,10 @@ def create_request_async(session: Session) -> Promise[tuple[WorkspaceEdit | None Request.willRenameFiles({'files': [file_rename]}) ).then(lambda response: (response, weakref.ref(session))) - sessions = [session for session in self.sessions() if match_file_operation_filters( - session.get_capability('workspace.fileOperations.willRename'), file_rename['oldUri'] - )] - promises = [create_request_async(session) for session in sessions] + promises = [ + create_request_async(session) for session in self.sessions() + if match_file_operation_filters( + session.get_capability('workspace.fileOperations.willRename'), file_rename['oldUri'])] if promises: sublime.set_timeout_async( lambda: Promise.all(promises).then(lambda responses: self.handle_rename_async(responses, file_rename)) From cd97fa3075a4a504044536a940d94a64fccddd9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sun, 30 Nov 2025 19:45:55 +0100 Subject: [PATCH 086/129] create_request_async -> create_request --- plugin/rename_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 1226065de..28d6a974a 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -83,13 +83,13 @@ def run(self, new_name: str, old_path: str | None = None) -> None: "oldUri": filename_to_uri(old_path) } - def create_request_async(session: Session) -> Promise[tuple[WorkspaceEdit | None, weakref.ref[Session]]]: + def create_request(session: Session) -> Promise[tuple[WorkspaceEdit | None, weakref.ref[Session]]]: return session.send_request_task( Request.willRenameFiles({'files': [file_rename]}) ).then(lambda response: (response, weakref.ref(session))) promises = [ - create_request_async(session) for session in self.sessions() + create_request(session) for session in self.sessions() if match_file_operation_filters( session.get_capability('workspace.fileOperations.willRename'), file_rename['oldUri'])] if promises: From cfa5799d3ff91d6a5056d0411a4f8a8342025b27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sun, 30 Nov 2025 19:53:20 +0100 Subject: [PATCH 087/129] call self.run_async from set_timeout_async --- plugin/rename_file.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 28d6a974a..a709cc76d 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -78,6 +78,9 @@ def run(self, new_name: str, old_path: str | None = None) -> None: if resolved_new_path.exists() and not self.is_case_change(old_path, new_path): self.window.status_message('Unable to Rename. Already exists') return + sublime.set_timeout_async(lambda: self.run_async(old_path, new_path)) + + def run_async(self, old_path: str, new_path: str) -> None: file_rename: FileRename = { "newUri": filename_to_uri(new_path), "oldUri": filename_to_uri(old_path) From 083245b11e3dea03cd32e32a73d29beb4b0dc761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sun, 30 Nov 2025 20:42:41 +0100 Subject: [PATCH 088/129] Apply Rafal's suggestions --- plugin/core/types.py | 8 ++------ plugin/rename_file.py | 32 +++++++++++++++++--------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/plugin/core/types.py b/plugin/core/types.py index 5e6429b99..a916d5de6 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -1,7 +1,6 @@ from __future__ import annotations from ...protocol import FileOperationFilter from ...protocol import FileOperationPatternKind -from ...protocol import FileOperationRegistrationOptions from ...protocol import ServerCapabilities from ...protocol import TextDocumentSyncKind from ...protocol import TextDocumentSyncOptions @@ -452,10 +451,7 @@ def matches(self, view: sublime.View) -> bool: return any(f(view) for f in self.filters) if self.filters else True -def match_file_operation_filters(file_operations: FileOperationRegistrationOptions | None, uri: URI) -> bool: - if not file_operations: - return False - +def match_file_operation_filters(filters: list[FileOperationFilter], uri: URI) -> bool: def matches(file_operation_filter: FileOperationFilter) -> bool: uri_scheme, file_name = parse_uri(uri) pattern = file_operation_filter['pattern'] @@ -473,7 +469,7 @@ def matches(file_operation_filter: FileOperationFilter) -> bool: flags |= IGNORECASE return globmatch(file_name, pattern['glob'], flags=flags) - return any(matches(_filter) for _filter in file_operations['filters']) + return any(matches(_filter) for _filter in filters) # method -> (capability dotted path, optional registration dotted path) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index a709cc76d..127c37eb5 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -9,10 +9,14 @@ from .core.types import match_file_operation_filters from .core.url import filename_to_uri, parse_uri from pathlib import Path +from typing import TYPE_CHECKING import sublime import sublime_plugin import weakref +if TYPE_CHECKING: + from collections.abc import Generator + class LspRenameFromSidebarOverride(LspWindowCommand): def is_enabled(self) -> bool: @@ -85,23 +89,21 @@ def run_async(self, old_path: str, new_path: str) -> None: "newUri": filename_to_uri(new_path), "oldUri": filename_to_uri(old_path) } - - def create_request(session: Session) -> Promise[tuple[WorkspaceEdit | None, weakref.ref[Session]]]: - return session.send_request_task( - Request.willRenameFiles({'files': [file_rename]}) - ).then(lambda response: (response, weakref.ref(session))) - - promises = [ - create_request(session) for session in self.sessions() - if match_file_operation_filters( - session.get_capability('workspace.fileOperations.willRename'), file_rename['oldUri'])] + promises = list(self.create_will_rename_requests_async(file_rename)) if promises: - sublime.set_timeout_async( - lambda: Promise.all(promises).then(lambda responses: self.handle_rename_async(responses, file_rename)) - ) + Promise.all(promises).then(lambda responses: self.handle_rename_async(responses, file_rename)) else: self.rename_path(file_rename) + def create_will_rename_requests_async( + self, file_rename: FileRename + ) -> Generator[Promise[tuple[WorkspaceEdit | None, weakref.ref[Session]]]]: + for session in self.sessions(): + filters = session.get_capability('workspace.fileOperations.willRename.filters') or [] + if match_file_operation_filters(filters, file_rename['oldUri']): + yield session.send_request_task(Request.willRenameFiles({'files': [file_rename]})) \ + .then(lambda response: (response, weakref.ref(session))) + def is_case_change(self, path_a: str, path_b: str) -> bool: return path_a.lower() == path_b.lower() and Path(path_a).stat().st_ino == Path(path_b).stat().st_ino @@ -147,6 +149,6 @@ def restore_regions(view: sublime.View | None) -> None: def notify_did_rename(self, file_rename: FileRename): for session in self.sessions(): - file_operations = session.get_capability('workspace.fileOperations.didRename') - if file_operations and match_file_operation_filters(file_operations, file_rename['oldUri']): + filters = session.get_capability('workspace.fileOperations.didRename.filters') or [] + if filters and match_file_operation_filters(filters, file_rename['oldUri']): session.send_notification(Notification.didRenameFiles({'files': [file_rename]})) From 0521ed23b4b72cbf2396a43b362b676b70a7a7b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 1 Dec 2025 18:22:46 +0100 Subject: [PATCH 089/129] Revert "Merge branch 'main' into add-will-rename-and-did-rename" because it bring back the problem I faced with project_a I will try to investigate this, but maybe not today, in the meantime will try to address other review comments This reverts commit 23d745ee23906ea07aae76d53ce833a00002fa5a, reversing changes made to 083245b11e3dea03cd32e32a73d29beb4b0dc761. --- plugin/documents.py | 4 +--- plugin/session_buffer.py | 11 ++++------- plugin/session_view.py | 3 --- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/plugin/documents.py b/plugin/documents.py index c5a1ddfca..8a8aaf683 100644 --- a/plugin/documents.py +++ b/plugin/documents.py @@ -244,12 +244,10 @@ def on_documentation_popup_toggle(self, *, opened: bool) -> None: def on_session_initialized_async(self, session: Session) -> None: assert not self.view.is_loading() if session.config.name not in self._session_views: - session_view = SessionView(self, session, self._uri) - self._session_views[session.config.name] = session_view + self._session_views[session.config.name] = SessionView(self, session, self._uri) if buf := self.view.buffer(): if text_change_listener := TextChangeListener.ids_to_listeners.get(buf.buffer_id): text_change_listener.view_listeners.add(self) - session_view.on_initialized() self.view.settings().set("lsp_active", True) # Check whether this session is the new best session for color boxes, inlay hints, and semantic tokens. If # that is the case, remove the color boxes, inlay hints or semantic tokens from the previously best session. diff --git a/plugin/session_buffer.py b/plugin/session_buffer.py index 755a1ff18..6915b97b4 100644 --- a/plugin/session_buffer.py +++ b/plugin/session_buffer.py @@ -158,6 +158,8 @@ def __init__(self, session_view: SessionViewProtocol, buffer_id: int, uri: Docum self._dynamically_registered_commands: dict[str, list[str]] = {} self._supported_commands: set[str] = set() self._update_supported_commands() + # set_timeout_async is required to ensure that the SessionView is initialized before running requests + sublime.set_timeout_async(lambda: self._check_did_open(view)) @property def session(self) -> Session: @@ -175,9 +177,6 @@ def diagnostics(self) -> list[tuple[Diagnostic, sublime.Region]]: def last_synced_version(self) -> int: return self._last_synced_version - def on_session_view_initialized(self, view: sublime.View) -> None: - self._check_did_open(view) - def _check_did_open(self, view: sublime.View) -> None: if not self.opened and self.should_notify_did_open(): language_id = self.get_language_id() @@ -447,12 +446,10 @@ def some_view(self) -> sublime.View | None: # Prefer active view if possible active_view = self.session.window.active_view() for sv in self.session_views: - if sv.view == active_view and sv.view.is_valid(): + if sv.view == active_view: return active_view for sv in self.session_views: - if sv.view.is_valid(): - return sv.view - return None + return sv.view def _if_view_unchanged(self, f: Callable[[sublime.View, Any], None], version: int) -> CallableWithOptionalArguments: """ diff --git a/plugin/session_view.py b/plugin/session_view.py index 0113e0fe9..0fd918684 100644 --- a/plugin/session_view.py +++ b/plugin/session_view.py @@ -104,9 +104,6 @@ def on_before_remove(self) -> None: if listener := self.listener(): listener.on_diagnostics_updated_async(False) - def on_initialized(self) -> None: - self.session_buffer.on_session_view_initialized(self._view) - @property def session(self) -> Session: return self._session From 8a93340099e9ab838837d58daae9e58877d3ec33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 1 Dec 2025 18:23:50 +0100 Subject: [PATCH 090/129] use functools.partial --- plugin/rename_file.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 127c37eb5..06f95eaaf 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -8,6 +8,7 @@ from .core.sessions import Session from .core.types import match_file_operation_filters from .core.url import filename_to_uri, parse_uri +from functools import partial from pathlib import Path from typing import TYPE_CHECKING import sublime @@ -102,7 +103,7 @@ def create_will_rename_requests_async( filters = session.get_capability('workspace.fileOperations.willRename.filters') or [] if match_file_operation_filters(filters, file_rename['oldUri']): yield session.send_request_task(Request.willRenameFiles({'files': [file_rename]})) \ - .then(lambda response: (response, weakref.ref(session))) + .then(partial(lambda weak_session, response: (response, weak_session), weakref.ref(session))) def is_case_change(self, path_a: str, path_b: str) -> bool: return path_a.lower() == path_b.lower() and Path(path_a).stat().st_ino == Path(path_b).stat().st_ino From 5dde5533ff475f7a20ba36b30fd27592f820d43b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 1 Dec 2025 18:28:37 +0100 Subject: [PATCH 091/129] set parents=True to prevent exception that can be thrown if renaming to a folder doesn't exist yet --- plugin/rename_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 06f95eaaf..1da13fda3 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -125,7 +125,7 @@ def rename_path(self, file_rename: FileRename) -> None: view.close() # LSP spec - send didClose for the old file new_dir = new_path.parent if not new_dir.exists(): - new_dir.mkdir() + new_dir.mkdir(parents=True) old_path_is_dir = old_path.is_dir() try: old_path.rename(new_path) From cf959641737ed658701b7a23054e003047f0dc00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 1 Dec 2025 18:38:47 +0100 Subject: [PATCH 092/129] be consistent - Rename LspRenameFromSidebarOverride to LspRenameFromSidebarOverrideCommand --- boot.py | 4 ++-- plugin/rename_file.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/boot.py b/boot.py index facf4f07d..09e648811 100644 --- a/boot.py +++ b/boot.py @@ -71,7 +71,7 @@ from .plugin.references import LspSymbolReferencesCommand from .plugin.rename import LspHideRenameButtonsCommand from .plugin.rename import LspSymbolRenameCommand -from .plugin.rename_file import LspRenameFromSidebarOverride +from .plugin.rename_file import LspRenameFromSidebarOverrideCommand from .plugin.rename_file import LspRenamePathCommand from .plugin.save_command import LspSaveAllCommand from .plugin.save_command import LspSaveCommand @@ -151,7 +151,7 @@ "LspSymbolImplementationCommand", "LspSymbolReferencesCommand", "LspSymbolRenameCommand", - "LspRenameFromSidebarOverride", + "LspRenameFromSidebarOverrideCommand", "LspRenamePathCommand", "LspSymbolTypeDefinitionCommand", "LspToggleCodeLensesCommand", diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 1da13fda3..7e8df56bb 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -19,7 +19,7 @@ from collections.abc import Generator -class LspRenameFromSidebarOverride(LspWindowCommand): +class LspRenameFromSidebarOverrideCommand(LspWindowCommand): def is_enabled(self) -> bool: return True From 630b29e4343e52a95e991359aa71431bc147e961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 1 Dec 2025 18:39:30 +0100 Subject: [PATCH 093/129] sort --- boot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/boot.py b/boot.py index 09e648811..33094f963 100644 --- a/boot.py +++ b/boot.py @@ -132,6 +132,8 @@ "LspParseVscodePackageJson", "LspPrevDiagnosticCommand", "LspRefactorCommand", + "LspRenameFromSidebarOverrideCommand", + "LspRenamePathCommand", "LspResolveDocsCommand", "LspRestartServerCommand", "LspRunTextCommandHelperCommand", @@ -151,8 +153,6 @@ "LspSymbolImplementationCommand", "LspSymbolReferencesCommand", "LspSymbolRenameCommand", - "LspRenameFromSidebarOverrideCommand", - "LspRenamePathCommand", "LspSymbolTypeDefinitionCommand", "LspToggleCodeLensesCommand", "LspToggleHoverPopupsCommand", From 91494542c1d714c09393254f1753b38a404150ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 1 Dec 2025 19:18:57 +0100 Subject: [PATCH 094/129] notify_did_rename after all renames --- plugin/rename_file.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 7e8df56bb..cff42ae56 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -10,13 +10,14 @@ from .core.url import filename_to_uri, parse_uri from functools import partial from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Literal import sublime import sublime_plugin import weakref if TYPE_CHECKING: from collections.abc import Generator + FileRenameStatus = Literal['renamed', 'aborted'] class LspRenameFromSidebarOverrideCommand(LspWindowCommand): @@ -110,12 +111,18 @@ def is_case_change(self, path_a: str, path_b: str) -> bool: def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakref.ref[Session]]], file_rename: FileRename) -> None: + def notify_file_rename(statuses: list[FileRenameStatus]): + if any(status == 'renamed' for status in statuses): + self.notify_did_rename(file_rename) + + edits_promises: list[Promise[FileRenameStatus]] = [] for response, weak_session in responses: if (session := weak_session()) and response: - session.apply_workspace_edit_async(response, is_refactoring=True) \ - .then(lambda _: self.rename_path(file_rename)) + edits_promises.append(session.apply_workspace_edit_async(response, is_refactoring=True) + .then(lambda _: self.rename_path(file_rename))) + Promise.all(edits_promises).then(notify_file_rename) - def rename_path(self, file_rename: FileRename) -> None: + def rename_path(self, file_rename: FileRename) -> Promise[FileRenameStatus]: old_path = Path(parse_uri(file_rename['oldUri'])[1]) new_path = Path(parse_uri(file_rename['newUri'])[1]) old_regions: list[sublime.Region] = [] @@ -131,7 +138,7 @@ def rename_path(self, file_rename: FileRename) -> None: old_path.rename(new_path) except Exception: sublime.status_message("Unable to rename") - return + return Promise.resolve('aborted') if old_path_is_dir: for view in self.window.views(): file_name = view.file_name() @@ -143,10 +150,9 @@ def restore_regions(view: sublime.View | None) -> None: return view.sel().clear() view.sel().add_all(old_regions) - # LSP spec - send didOpen for the new file - open_file_uri(self.window, str(new_path)).then(restore_regions) \ - .then(lambda _: self.notify_did_rename(file_rename)) + return open_file_uri(self.window, str(new_path)).then(restore_regions).then(lambda _: 'renamed') + return Promise.resolve('renamed') def notify_did_rename(self, file_rename: FileRename): for session in self.sessions(): From 2028af243ac9dcebdc19975fea6faebf6abdcd58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 1 Dec 2025 19:27:21 +0100 Subject: [PATCH 095/129] notify_did_rename after all renames - but a bit differently --- plugin/rename_file.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index cff42ae56..9dc140626 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -10,14 +10,13 @@ from .core.url import filename_to_uri, parse_uri from functools import partial from pathlib import Path -from typing import TYPE_CHECKING, Literal +from typing import TYPE_CHECKING, Any import sublime import sublime_plugin import weakref if TYPE_CHECKING: from collections.abc import Generator - FileRenameStatus = Literal['renamed', 'aborted'] class LspRenameFromSidebarOverrideCommand(LspWindowCommand): @@ -111,18 +110,14 @@ def is_case_change(self, path_a: str, path_b: str) -> bool: def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakref.ref[Session]]], file_rename: FileRename) -> None: - def notify_file_rename(statuses: list[FileRenameStatus]): - if any(status == 'renamed' for status in statuses): - self.notify_did_rename(file_rename) - - edits_promises: list[Promise[FileRenameStatus]] = [] + promises: list[Promise[Any]] = [] for response, weak_session in responses: if (session := weak_session()) and response: - edits_promises.append(session.apply_workspace_edit_async(response, is_refactoring=True) + promises.append(session.apply_workspace_edit_async(response, is_refactoring=True) .then(lambda _: self.rename_path(file_rename))) - Promise.all(edits_promises).then(notify_file_rename) + Promise.all(promises).then(lambda _: self.notify_did_rename(file_rename)) - def rename_path(self, file_rename: FileRename) -> Promise[FileRenameStatus]: + def rename_path(self, file_rename: FileRename) -> Promise[Any]: old_path = Path(parse_uri(file_rename['oldUri'])[1]) new_path = Path(parse_uri(file_rename['newUri'])[1]) old_regions: list[sublime.Region] = [] @@ -138,7 +133,7 @@ def rename_path(self, file_rename: FileRename) -> Promise[FileRenameStatus]: old_path.rename(new_path) except Exception: sublime.status_message("Unable to rename") - return Promise.resolve('aborted') + return Promise.resolve(None) if old_path_is_dir: for view in self.window.views(): file_name = view.file_name() @@ -151,8 +146,8 @@ def restore_regions(view: sublime.View | None) -> None: view.sel().clear() view.sel().add_all(old_regions) # LSP spec - send didOpen for the new file - return open_file_uri(self.window, str(new_path)).then(restore_regions).then(lambda _: 'renamed') - return Promise.resolve('renamed') + return open_file_uri(self.window, str(new_path)).then(restore_regions) + return Promise.resolve(None) def notify_did_rename(self, file_rename: FileRename): for session in self.sessions(): From 4616c1a93d0d3515ffc8c6f272e130d8b572d931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 1 Dec 2025 21:17:02 +0100 Subject: [PATCH 096/129] Reapply "Merge branch 'main' into add-will-rename-and-did-rename" because it bring back the problem I faced with project_a This reverts commit 0521ed23b4b72cbf2396a43b362b676b70a7a7b9. --- plugin/documents.py | 4 +++- plugin/session_buffer.py | 11 +++++++---- plugin/session_view.py | 3 +++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/plugin/documents.py b/plugin/documents.py index 8a8aaf683..c5a1ddfca 100644 --- a/plugin/documents.py +++ b/plugin/documents.py @@ -244,10 +244,12 @@ def on_documentation_popup_toggle(self, *, opened: bool) -> None: def on_session_initialized_async(self, session: Session) -> None: assert not self.view.is_loading() if session.config.name not in self._session_views: - self._session_views[session.config.name] = SessionView(self, session, self._uri) + session_view = SessionView(self, session, self._uri) + self._session_views[session.config.name] = session_view if buf := self.view.buffer(): if text_change_listener := TextChangeListener.ids_to_listeners.get(buf.buffer_id): text_change_listener.view_listeners.add(self) + session_view.on_initialized() self.view.settings().set("lsp_active", True) # Check whether this session is the new best session for color boxes, inlay hints, and semantic tokens. If # that is the case, remove the color boxes, inlay hints or semantic tokens from the previously best session. diff --git a/plugin/session_buffer.py b/plugin/session_buffer.py index 6915b97b4..755a1ff18 100644 --- a/plugin/session_buffer.py +++ b/plugin/session_buffer.py @@ -158,8 +158,6 @@ def __init__(self, session_view: SessionViewProtocol, buffer_id: int, uri: Docum self._dynamically_registered_commands: dict[str, list[str]] = {} self._supported_commands: set[str] = set() self._update_supported_commands() - # set_timeout_async is required to ensure that the SessionView is initialized before running requests - sublime.set_timeout_async(lambda: self._check_did_open(view)) @property def session(self) -> Session: @@ -177,6 +175,9 @@ def diagnostics(self) -> list[tuple[Diagnostic, sublime.Region]]: def last_synced_version(self) -> int: return self._last_synced_version + def on_session_view_initialized(self, view: sublime.View) -> None: + self._check_did_open(view) + def _check_did_open(self, view: sublime.View) -> None: if not self.opened and self.should_notify_did_open(): language_id = self.get_language_id() @@ -446,10 +447,12 @@ def some_view(self) -> sublime.View | None: # Prefer active view if possible active_view = self.session.window.active_view() for sv in self.session_views: - if sv.view == active_view: + if sv.view == active_view and sv.view.is_valid(): return active_view for sv in self.session_views: - return sv.view + if sv.view.is_valid(): + return sv.view + return None def _if_view_unchanged(self, f: Callable[[sublime.View, Any], None], version: int) -> CallableWithOptionalArguments: """ diff --git a/plugin/session_view.py b/plugin/session_view.py index 0fd918684..0113e0fe9 100644 --- a/plugin/session_view.py +++ b/plugin/session_view.py @@ -104,6 +104,9 @@ def on_before_remove(self) -> None: if listener := self.listener(): listener.on_diagnostics_updated_async(False) + def on_initialized(self) -> None: + self.session_buffer.on_session_view_initialized(self._view) + @property def session(self) -> Session: return self._session From 1f36a76f85f2e14014de5d81771443827e7f099a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 1 Dec 2025 21:17:48 +0100 Subject: [PATCH 097/129] Update plugin/rename_file.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Chłodnicki --- plugin/rename_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 9dc140626..c4ba7aa0e 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -81,7 +81,7 @@ def run(self, new_name: str, old_path: str | None = None) -> None: resolved_new_path = (Path(old_path).parent / new_name).resolve() new_path = str(resolved_new_path) if resolved_new_path.exists() and not self.is_case_change(old_path, new_path): - self.window.status_message('Unable to Rename. Already exists') + self.window.status_message('Unable to rename - target already exists') return sublime.set_timeout_async(lambda: self.run_async(old_path, new_path)) From 0a3fe1611a99d32a4737431164d91a9696e8f52a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 1 Dec 2025 21:24:26 +0100 Subject: [PATCH 098/129] apply all edit promises, then call rename file, then send didRename notification --- plugin/rename_file.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index c4ba7aa0e..cd1adde4d 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -113,11 +113,10 @@ def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakre promises: list[Promise[Any]] = [] for response, weak_session in responses: if (session := weak_session()) and response: - promises.append(session.apply_workspace_edit_async(response, is_refactoring=True) - .then(lambda _: self.rename_path(file_rename))) - Promise.all(promises).then(lambda _: self.notify_did_rename(file_rename)) + promises.append(session.apply_workspace_edit_async(response, is_refactoring=True)) + Promise.all(promises).then(lambda _: self.rename_path(file_rename)) - def rename_path(self, file_rename: FileRename) -> Promise[Any]: + def rename_path(self, file_rename: FileRename) -> None: old_path = Path(parse_uri(file_rename['oldUri'])[1]) new_path = Path(parse_uri(file_rename['newUri'])[1]) old_regions: list[sublime.Region] = [] @@ -133,7 +132,7 @@ def rename_path(self, file_rename: FileRename) -> Promise[Any]: old_path.rename(new_path) except Exception: sublime.status_message("Unable to rename") - return Promise.resolve(None) + return if old_path_is_dir: for view in self.window.views(): file_name = view.file_name() @@ -146,8 +145,8 @@ def restore_regions(view: sublime.View | None) -> None: view.sel().clear() view.sel().add_all(old_regions) # LSP spec - send didOpen for the new file - return open_file_uri(self.window, str(new_path)).then(restore_regions) - return Promise.resolve(None) + open_file_uri(self.window, str(new_path)).then(restore_regions) + self.notify_did_rename(file_rename) def notify_did_rename(self, file_rename: FileRename): for session in self.sessions(): From 37fe35f10e1ed0446b3e465cc0b934619451841a Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Mon, 1 Dec 2025 21:46:13 +0100 Subject: [PATCH 099/129] simplifications --- plugin/rename_file.py | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index cd1adde4d..24af289c4 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -5,20 +5,24 @@ from .core.promise import Promise from .core.protocol import Notification, Request from .core.registry import LspWindowCommand -from .core.sessions import Session from .core.types import match_file_operation_filters from .core.url import filename_to_uri, parse_uri from functools import partial from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING import sublime import sublime_plugin import weakref if TYPE_CHECKING: + from .core.sessions import Session from collections.abc import Generator +def is_case_change(path_a: str, path_b: str) -> bool: + return path_a.lower() == path_b.lower() and Path(path_a).stat().st_ino == Path(path_b).stat().st_ino + + class LspRenameFromSidebarOverrideCommand(LspWindowCommand): def is_enabled(self) -> bool: return True @@ -80,7 +84,7 @@ def run(self, new_name: str, old_path: str | None = None) -> None: # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy resolved_new_path = (Path(old_path).parent / new_name).resolve() new_path = str(resolved_new_path) - if resolved_new_path.exists() and not self.is_case_change(old_path, new_path): + if resolved_new_path.exists() and not is_case_change(old_path, new_path): self.window.status_message('Unable to rename - target already exists') return sublime.set_timeout_async(lambda: self.run_async(old_path, new_path)) @@ -90,11 +94,10 @@ def run_async(self, old_path: str, new_path: str) -> None: "newUri": filename_to_uri(new_path), "oldUri": filename_to_uri(old_path) } - promises = list(self.create_will_rename_requests_async(file_rename)) - if promises: - Promise.all(promises).then(lambda responses: self.handle_rename_async(responses, file_rename)) - else: - self.rename_path(file_rename) + Promise.all(list(self.create_will_rename_requests_async(file_rename))) \ + .then(lambda responses: self.handle_rename_async(responses)) \ + .then(lambda _: self.rename_path(file_rename)) \ + .then(lambda success: self.notify_did_rename(file_rename) if success else None) def create_will_rename_requests_async( self, file_rename: FileRename @@ -105,18 +108,14 @@ def create_will_rename_requests_async( yield session.send_request_task(Request.willRenameFiles({'files': [file_rename]})) \ .then(partial(lambda weak_session, response: (response, weak_session), weakref.ref(session))) - def is_case_change(self, path_a: str, path_b: str) -> bool: - return path_a.lower() == path_b.lower() and Path(path_a).stat().st_ino == Path(path_b).stat().st_ino - - def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakref.ref[Session]]], - file_rename: FileRename) -> None: - promises: list[Promise[Any]] = [] + def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakref.ref[Session]]]) -> Promise: + promises: list[Promise] = [] for response, weak_session in responses: if (session := weak_session()) and response: promises.append(session.apply_workspace_edit_async(response, is_refactoring=True)) - Promise.all(promises).then(lambda _: self.rename_path(file_rename)) + return Promise.all(promises) - def rename_path(self, file_rename: FileRename) -> None: + def rename_path(self, file_rename: FileRename) -> Promise[bool]: old_path = Path(parse_uri(file_rename['oldUri'])[1]) new_path = Path(parse_uri(file_rename['newUri'])[1]) old_regions: list[sublime.Region] = [] @@ -132,7 +131,7 @@ def rename_path(self, file_rename: FileRename) -> None: old_path.rename(new_path) except Exception: sublime.status_message("Unable to rename") - return + return Promise.resolve(False) if old_path_is_dir: for view in self.window.views(): file_name = view.file_name() @@ -145,8 +144,8 @@ def restore_regions(view: sublime.View | None) -> None: view.sel().clear() view.sel().add_all(old_regions) # LSP spec - send didOpen for the new file - open_file_uri(self.window, str(new_path)).then(restore_regions) - self.notify_did_rename(file_rename) + return open_file_uri(self.window, str(new_path)).then(restore_regions).then(lambda _: Promise.resolve(True)) + return Promise.resolve(True) def notify_did_rename(self, file_rename: FileRename): for session in self.sessions(): From f1af5196e2d38bfc1edc8a0ce73527d01f97062d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 1 Dec 2025 22:11:36 +0100 Subject: [PATCH 100/129] Revert "simplifications" This reverts commit 37fe35f10e1ed0446b3e465cc0b934619451841a. --- plugin/rename_file.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 24af289c4..cd1adde4d 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -5,24 +5,20 @@ from .core.promise import Promise from .core.protocol import Notification, Request from .core.registry import LspWindowCommand +from .core.sessions import Session from .core.types import match_file_operation_filters from .core.url import filename_to_uri, parse_uri from functools import partial from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any import sublime import sublime_plugin import weakref if TYPE_CHECKING: - from .core.sessions import Session from collections.abc import Generator -def is_case_change(path_a: str, path_b: str) -> bool: - return path_a.lower() == path_b.lower() and Path(path_a).stat().st_ino == Path(path_b).stat().st_ino - - class LspRenameFromSidebarOverrideCommand(LspWindowCommand): def is_enabled(self) -> bool: return True @@ -84,7 +80,7 @@ def run(self, new_name: str, old_path: str | None = None) -> None: # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy resolved_new_path = (Path(old_path).parent / new_name).resolve() new_path = str(resolved_new_path) - if resolved_new_path.exists() and not is_case_change(old_path, new_path): + if resolved_new_path.exists() and not self.is_case_change(old_path, new_path): self.window.status_message('Unable to rename - target already exists') return sublime.set_timeout_async(lambda: self.run_async(old_path, new_path)) @@ -94,10 +90,11 @@ def run_async(self, old_path: str, new_path: str) -> None: "newUri": filename_to_uri(new_path), "oldUri": filename_to_uri(old_path) } - Promise.all(list(self.create_will_rename_requests_async(file_rename))) \ - .then(lambda responses: self.handle_rename_async(responses)) \ - .then(lambda _: self.rename_path(file_rename)) \ - .then(lambda success: self.notify_did_rename(file_rename) if success else None) + promises = list(self.create_will_rename_requests_async(file_rename)) + if promises: + Promise.all(promises).then(lambda responses: self.handle_rename_async(responses, file_rename)) + else: + self.rename_path(file_rename) def create_will_rename_requests_async( self, file_rename: FileRename @@ -108,14 +105,18 @@ def create_will_rename_requests_async( yield session.send_request_task(Request.willRenameFiles({'files': [file_rename]})) \ .then(partial(lambda weak_session, response: (response, weak_session), weakref.ref(session))) - def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakref.ref[Session]]]) -> Promise: - promises: list[Promise] = [] + def is_case_change(self, path_a: str, path_b: str) -> bool: + return path_a.lower() == path_b.lower() and Path(path_a).stat().st_ino == Path(path_b).stat().st_ino + + def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakref.ref[Session]]], + file_rename: FileRename) -> None: + promises: list[Promise[Any]] = [] for response, weak_session in responses: if (session := weak_session()) and response: promises.append(session.apply_workspace_edit_async(response, is_refactoring=True)) - return Promise.all(promises) + Promise.all(promises).then(lambda _: self.rename_path(file_rename)) - def rename_path(self, file_rename: FileRename) -> Promise[bool]: + def rename_path(self, file_rename: FileRename) -> None: old_path = Path(parse_uri(file_rename['oldUri'])[1]) new_path = Path(parse_uri(file_rename['newUri'])[1]) old_regions: list[sublime.Region] = [] @@ -131,7 +132,7 @@ def rename_path(self, file_rename: FileRename) -> Promise[bool]: old_path.rename(new_path) except Exception: sublime.status_message("Unable to rename") - return Promise.resolve(False) + return if old_path_is_dir: for view in self.window.views(): file_name = view.file_name() @@ -144,8 +145,8 @@ def restore_regions(view: sublime.View | None) -> None: view.sel().clear() view.sel().add_all(old_regions) # LSP spec - send didOpen for the new file - return open_file_uri(self.window, str(new_path)).then(restore_regions).then(lambda _: Promise.resolve(True)) - return Promise.resolve(True) + open_file_uri(self.window, str(new_path)).then(restore_regions) + self.notify_did_rename(file_rename) def notify_did_rename(self, file_rename: FileRename): for session in self.sessions(): From 08ae452429bcd7fb72ffb7c7afb654328dff75c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 1 Dec 2025 22:02:11 +0100 Subject: [PATCH 101/129] Make sure to send didClose even when we rename a folder because we are closing the many views, we need to make sure that we put them back to where they were Note, the logic is still missing focusing the previous active view --- plugin/rename_file.py | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index cd1adde4d..69cc1539f 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -17,6 +17,8 @@ if TYPE_CHECKING: from collections.abc import Generator + FileName = str + Group = tuple[int, int] class LspRenameFromSidebarOverrideCommand(LspWindowCommand): @@ -119,33 +121,33 @@ def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakre def rename_path(self, file_rename: FileRename) -> None: old_path = Path(parse_uri(file_rename['oldUri'])[1]) new_path = Path(parse_uri(file_rename['newUri'])[1]) - old_regions: list[sublime.Region] = [] - if view := self.window.find_open_file(str(old_path)): - view.run_command('save', {'async': False}) - old_regions = list(view.sel()) - view.close() # LSP spec - send didClose for the old file - new_dir = new_path.parent - if not new_dir.exists(): + restore_files: list[tuple[FileName, Group, list[sublime.Region]]] = [] + for view in self.window.views(): + if (file_name := view.file_name()) and file_name.startswith(str(old_path)): + new_file_name = file_name.replace(str(old_path), str(new_path)) + restore_files.append((new_file_name, self.window.get_view_index(view), list(view.sel()))) + view.run_command('save', {'async': False}) + view.close() # LSP spec - send didClose for the old file + if (new_dir := new_path.parent) and not new_dir.exists(): new_dir.mkdir(parents=True) - old_path_is_dir = old_path.is_dir() try: old_path.rename(new_path) except Exception: sublime.status_message("Unable to rename") return - if old_path_is_dir: - for view in self.window.views(): - file_name = view.file_name() - if file_name and file_name.startswith(str(old_path)): - view.retarget(file_name.replace(str(old_path), str(new_path))) - if new_path.is_file(): - def restore_regions(view: sublime.View | None) -> None: - if not view: - return + + def restore_view(selection: list[sublime.Region], group_index: Group, view: sublime.View | None) -> None: + if not view: + return + self.window.set_view_index(view, group_index[0], group_index[1]) + if selection: view.sel().clear() - view.sel().add_all(old_regions) + view.sel().add_all(selection) + + for file_name, group_index, selection in restore_files: # LSP spec - send didOpen for the new file - open_file_uri(self.window, str(new_path)).then(restore_regions) + open_file_uri(self.window, file_name, group=group_index[0]) \ + .then(partial(restore_view, selection, group_index)) self.notify_did_rename(file_rename) def notify_did_rename(self, file_rename: FileRename): From 0d31e498e14255e4aee90eb3f5b6c1b7d448f3f8 Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Fri, 5 Dec 2025 10:51:11 +0100 Subject: [PATCH 102/129] await promise chain --- plugin/rename_file.py | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 69cc1539f..1af1b993a 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -1,21 +1,21 @@ from __future__ import annotations -from ..protocol import FileRename -from ..protocol import WorkspaceEdit from .core.open import open_file_uri from .core.promise import Promise from .core.protocol import Notification, Request from .core.registry import LspWindowCommand -from .core.sessions import Session from .core.types import match_file_operation_filters from .core.url import filename_to_uri, parse_uri from functools import partial from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING import sublime import sublime_plugin import weakref if TYPE_CHECKING: + from ..protocol import FileRename + from ..protocol import WorkspaceEdit + from .core.sessions import Session from collections.abc import Generator FileName = str Group = tuple[int, int] @@ -92,11 +92,10 @@ def run_async(self, old_path: str, new_path: str) -> None: "newUri": filename_to_uri(new_path), "oldUri": filename_to_uri(old_path) } - promises = list(self.create_will_rename_requests_async(file_rename)) - if promises: - Promise.all(promises).then(lambda responses: self.handle_rename_async(responses, file_rename)) - else: - self.rename_path(file_rename) + Promise.all(list(self.create_will_rename_requests_async(file_rename))) \ + .then(lambda responses: self.handle_rename_async(responses)) \ + .then(lambda _: self.rename_path(file_rename)) \ + .then(lambda success: self.notify_did_rename(file_rename) if success else None) def create_will_rename_requests_async( self, file_rename: FileRename @@ -110,15 +109,14 @@ def create_will_rename_requests_async( def is_case_change(self, path_a: str, path_b: str) -> bool: return path_a.lower() == path_b.lower() and Path(path_a).stat().st_ino == Path(path_b).stat().st_ino - def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakref.ref[Session]]], - file_rename: FileRename) -> None: - promises: list[Promise[Any]] = [] + def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakref.ref[Session]]]) -> Promise: + promises: list[Promise] = [] for response, weak_session in responses: if (session := weak_session()) and response: promises.append(session.apply_workspace_edit_async(response, is_refactoring=True)) - Promise.all(promises).then(lambda _: self.rename_path(file_rename)) + return Promise.all(promises) - def rename_path(self, file_rename: FileRename) -> None: + def rename_path(self, file_rename: FileRename) -> Promise[bool]: old_path = Path(parse_uri(file_rename['oldUri'])[1]) new_path = Path(parse_uri(file_rename['newUri'])[1]) restore_files: list[tuple[FileName, Group, list[sublime.Region]]] = [] @@ -134,7 +132,7 @@ def rename_path(self, file_rename: FileRename) -> None: old_path.rename(new_path) except Exception: sublime.status_message("Unable to rename") - return + return Promise.resolve(False) def restore_view(selection: list[sublime.Region], group_index: Group, view: sublime.View | None) -> None: if not view: @@ -144,11 +142,12 @@ def restore_view(selection: list[sublime.Region], group_index: Group, view: subl view.sel().clear() view.sel().add_all(selection) + promises: list[Promise] = [] for file_name, group_index, selection in restore_files: # LSP spec - send didOpen for the new file - open_file_uri(self.window, file_name, group=group_index[0]) \ - .then(partial(restore_view, selection, group_index)) - self.notify_did_rename(file_rename) + promises.append(open_file_uri(self.window, file_name, group=group_index[0]) + .then(partial(restore_view, selection, group_index))) + return Promise.all(promises).then(lambda _: True) def notify_did_rename(self, file_rename: FileRename): for session in self.sessions(): From 43ef16b578773fddf8bf39d12c49a74eda155ea8 Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Fri, 5 Dec 2025 10:54:38 +0100 Subject: [PATCH 103/129] simpler --- plugin/rename_file.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 1af1b993a..8fc9c257a 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -142,12 +142,11 @@ def restore_view(selection: list[sublime.Region], group_index: Group, view: subl view.sel().clear() view.sel().add_all(selection) - promises: list[Promise] = [] - for file_name, group_index, selection in restore_files: - # LSP spec - send didOpen for the new file - promises.append(open_file_uri(self.window, file_name, group=group_index[0]) - .then(partial(restore_view, selection, group_index))) - return Promise.all(promises).then(lambda _: True) + return Promise.all([ + open_file_uri(self.window, file_name, group=group_index[0]) \ + .then(partial(restore_view, selection, group_index)) + for file_name, group_index, selection in restore_files + ]).then(lambda _: True) def notify_did_rename(self, file_rename: FileRename): for session in self.sessions(): From e96b38280bee7c6f1bf0e2cf9a306792f295a14b Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Fri, 5 Dec 2025 10:58:46 +0100 Subject: [PATCH 104/129] silly lint --- plugin/rename_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 8fc9c257a..4ed1503c8 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -143,8 +143,8 @@ def restore_view(selection: list[sublime.Region], group_index: Group, view: subl view.sel().add_all(selection) return Promise.all([ - open_file_uri(self.window, file_name, group=group_index[0]) \ - .then(partial(restore_view, selection, group_index)) + open_file_uri(self.window, file_name, group=group_index[0]) + .then(partial(restore_view, selection, group_index)) for file_name, group_index, selection in restore_files ]).then(lambda _: True) From ad57ceb7a8bbe21b1db1e51ca76a3f3879c16685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Fri, 5 Dec 2025 12:22:47 +0100 Subject: [PATCH 105/129] Update plugin/rename_file.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Chłodnicki --- plugin/rename_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 4ed1503c8..c865df287 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -148,7 +148,7 @@ def restore_view(selection: list[sublime.Region], group_index: Group, view: subl for file_name, group_index, selection in restore_files ]).then(lambda _: True) - def notify_did_rename(self, file_rename: FileRename): + def notify_did_rename(self, file_rename: FileRename) -> None: for session in self.sessions(): filters = session.get_capability('workspace.fileOperations.didRename.filters') or [] if filters and match_file_operation_filters(filters, file_rename['oldUri']): From 59c982a3d7629c9f464292d287f766ab99cc34e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Fri, 5 Dec 2025 21:31:43 +0100 Subject: [PATCH 106/129] remove FileName and Group types --- plugin/rename_file.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index c865df287..28e5c2df1 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -17,8 +17,6 @@ from ..protocol import WorkspaceEdit from .core.sessions import Session from collections.abc import Generator - FileName = str - Group = tuple[int, int] class LspRenameFromSidebarOverrideCommand(LspWindowCommand): @@ -119,7 +117,7 @@ def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakre def rename_path(self, file_rename: FileRename) -> Promise[bool]: old_path = Path(parse_uri(file_rename['oldUri'])[1]) new_path = Path(parse_uri(file_rename['newUri'])[1]) - restore_files: list[tuple[FileName, Group, list[sublime.Region]]] = [] + restore_files: list[tuple[str, tuple[int, int], list[sublime.Region]]] = [] for view in self.window.views(): if (file_name := view.file_name()) and file_name.startswith(str(old_path)): new_file_name = file_name.replace(str(old_path), str(new_path)) @@ -134,7 +132,7 @@ def rename_path(self, file_rename: FileRename) -> Promise[bool]: sublime.status_message("Unable to rename") return Promise.resolve(False) - def restore_view(selection: list[sublime.Region], group_index: Group, view: sublime.View | None) -> None: + def restore_view(selection: list[sublime.Region], group_index: tuple[int, int], view: sublime.View | None) -> None: if not view: return self.window.set_view_index(view, group_index[0], group_index[1]) From 8f856943e3d484f6aa4a6f0b860b851ebaa3f24b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Fri, 5 Dec 2025 21:33:06 +0100 Subject: [PATCH 107/129] replace only one occurrence --- plugin/rename_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 28e5c2df1..200428fdd 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -120,7 +120,7 @@ def rename_path(self, file_rename: FileRename) -> Promise[bool]: restore_files: list[tuple[str, tuple[int, int], list[sublime.Region]]] = [] for view in self.window.views(): if (file_name := view.file_name()) and file_name.startswith(str(old_path)): - new_file_name = file_name.replace(str(old_path), str(new_path)) + new_file_name = file_name.replace(str(old_path), str(new_path), 1) restore_files.append((new_file_name, self.window.get_view_index(view), list(view.sel()))) view.run_command('save', {'async': False}) view.close() # LSP spec - send didClose for the old file From 4e707c0a32cf1abbda38f5fc81a2dfc177e1f917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Fri, 5 Dec 2025 21:34:58 +0100 Subject: [PATCH 108/129] only save view if dirty --- plugin/rename_file.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 200428fdd..908a6f1c3 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -122,7 +122,8 @@ def rename_path(self, file_rename: FileRename) -> Promise[bool]: if (file_name := view.file_name()) and file_name.startswith(str(old_path)): new_file_name = file_name.replace(str(old_path), str(new_path), 1) restore_files.append((new_file_name, self.window.get_view_index(view), list(view.sel()))) - view.run_command('save', {'async': False}) + if view.is_dirty(): + view.run_command('save', {'async': False}) view.close() # LSP spec - send didClose for the old file if (new_dir := new_path.parent) and not new_dir.exists(): new_dir.mkdir(parents=True) From 3fd0a0e14d91ddaf2616edbcdeba2d23233e37b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Fri, 5 Dec 2025 21:36:22 +0100 Subject: [PATCH 109/129] extract restore_view to method --- plugin/rename_file.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 908a6f1c3..6b97d3f3b 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -133,17 +133,9 @@ def rename_path(self, file_rename: FileRename) -> Promise[bool]: sublime.status_message("Unable to rename") return Promise.resolve(False) - def restore_view(selection: list[sublime.Region], group_index: tuple[int, int], view: sublime.View | None) -> None: - if not view: - return - self.window.set_view_index(view, group_index[0], group_index[1]) - if selection: - view.sel().clear() - view.sel().add_all(selection) - return Promise.all([ open_file_uri(self.window, file_name, group=group_index[0]) - .then(partial(restore_view, selection, group_index)) + .then(partial(self.restore_view, selection, group_index)) for file_name, group_index, selection in restore_files ]).then(lambda _: True) @@ -152,3 +144,11 @@ def notify_did_rename(self, file_rename: FileRename) -> None: filters = session.get_capability('workspace.fileOperations.didRename.filters') or [] if filters and match_file_operation_filters(filters, file_rename['oldUri']): session.send_notification(Notification.didRenameFiles({'files': [file_rename]})) + + def restore_view(self, selection: list[sublime.Region], group_index: tuple[int, int], view: sublime.View | None) -> None: + if not view: + return + self.window.set_view_index(view, group_index[0], group_index[1]) + if selection: + view.sel().clear() + view.sel().add_all(selection) From 1b0f2c0578da08920d86b58af64ddfb8cef2db34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Fri, 5 Dec 2025 21:37:13 +0100 Subject: [PATCH 110/129] fix lint --- plugin/rename_file.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 6b97d3f3b..bb0f0fbe7 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -145,7 +145,8 @@ def notify_did_rename(self, file_rename: FileRename) -> None: if filters and match_file_operation_filters(filters, file_rename['oldUri']): session.send_notification(Notification.didRenameFiles({'files': [file_rename]})) - def restore_view(self, selection: list[sublime.Region], group_index: tuple[int, int], view: sublime.View | None) -> None: + def restore_view(self, selection: list[sublime.Region], + group_index: tuple[int, int], view: sublime.View | None) -> None: if not view: return self.window.set_view_index(view, group_index[0], group_index[1]) From 84a41238872b36cc93fe6b1759018dc6fe4a85a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 6 Dec 2025 12:32:01 +0100 Subject: [PATCH 111/129] fix bug where the filename_to_uri will set with "res:" When I open a LSP-* plugin or (any plugin). When I rename a file, the rename will happen, but the file will be gone, because the newUri is not a file system path. file_rename {'newUri': 'res:/Packages/LSP-ast-grep/LSP-m.sublime-settings', 'oldUri': 'file:///Users/predrag/Library/Application%20Support/Sublime%20Text/Packages/LSP-ast-grep/LSP-marksman.sublime-settings'} --- plugin/rename_file.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index bb0f0fbe7..1161d1599 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -92,7 +92,7 @@ def run_async(self, old_path: str, new_path: str) -> None: } Promise.all(list(self.create_will_rename_requests_async(file_rename))) \ .then(lambda responses: self.handle_rename_async(responses)) \ - .then(lambda _: self.rename_path(file_rename)) \ + .then(lambda _: self.rename_path(old_path, new_path)) \ .then(lambda success: self.notify_did_rename(file_rename) if success else None) def create_will_rename_requests_async( @@ -114,9 +114,9 @@ def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakre promises.append(session.apply_workspace_edit_async(response, is_refactoring=True)) return Promise.all(promises) - def rename_path(self, file_rename: FileRename) -> Promise[bool]: - old_path = Path(parse_uri(file_rename['oldUri'])[1]) - new_path = Path(parse_uri(file_rename['newUri'])[1]) + def rename_path(self, old: str, new: str) -> Promise[bool]: + old_path = Path(old) + new_path = Path(new) restore_files: list[tuple[str, tuple[int, int], list[sublime.Region]]] = [] for view in self.window.views(): if (file_name := view.file_name()) and file_name.startswith(str(old_path)): From 2e7c60bb201e02ae3e5c2649e4836f03e2069dd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 8 Dec 2025 17:35:03 +0100 Subject: [PATCH 112/129] Update plugin/rename_file.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Chłodnicki --- plugin/rename_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 1161d1599..da2bcd8cf 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -129,8 +129,8 @@ def rename_path(self, old: str, new: str) -> Promise[bool]: new_dir.mkdir(parents=True) try: old_path.rename(new_path) - except Exception: - sublime.status_message("Unable to rename") + except Exception as error: + sublime.status_message(f"Unable to rename: {error}") return Promise.resolve(False) return Promise.all([ From 03031487d5263d48f9b86aeb0e6a00c5bd96ef65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 8 Dec 2025 17:36:41 +0100 Subject: [PATCH 113/129] make errors messages consistent --- plugin/rename_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index da2bcd8cf..344b58689 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -81,7 +81,7 @@ def run(self, new_name: str, old_path: str | None = None) -> None: resolved_new_path = (Path(old_path).parent / new_name).resolve() new_path = str(resolved_new_path) if resolved_new_path.exists() and not self.is_case_change(old_path, new_path): - self.window.status_message('Unable to rename - target already exists') + self.window.status_message('Rename error: Target already exists') return sublime.set_timeout_async(lambda: self.run_async(old_path, new_path)) @@ -130,7 +130,7 @@ def rename_path(self, old: str, new: str) -> Promise[bool]: try: old_path.rename(new_path) except Exception as error: - sublime.status_message(f"Unable to rename: {error}") + sublime.status_message(f"Rename error: {error}") return Promise.resolve(False) return Promise.all([ From 3332ab51ee63eb662a38f868441b2fb5ca3ff144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 8 Dec 2025 17:37:13 +0100 Subject: [PATCH 114/129] remove unused import --- plugin/rename_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 344b58689..e8b16354a 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -4,7 +4,7 @@ from .core.protocol import Notification, Request from .core.registry import LspWindowCommand from .core.types import match_file_operation_filters -from .core.url import filename_to_uri, parse_uri +from .core.url import filename_to_uri from functools import partial from pathlib import Path from typing import TYPE_CHECKING From 5c347fb4d315409a3de0532c330e14f35628cdb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 8 Dec 2025 17:40:36 +0100 Subject: [PATCH 115/129] rename group_index to group MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Chłodnicki --- plugin/rename_file.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index e8b16354a..74621a939 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -145,11 +145,11 @@ def notify_did_rename(self, file_rename: FileRename) -> None: if filters and match_file_operation_filters(filters, file_rename['oldUri']): session.send_notification(Notification.didRenameFiles({'files': [file_rename]})) - def restore_view(self, selection: list[sublime.Region], - group_index: tuple[int, int], view: sublime.View | None) -> None: + def restore_view(self, selection: list[sublime.Region], group: tuple[int, int], view: sublime.View | None) -> None: if not view: return - self.window.set_view_index(view, group_index[0], group_index[1]) + group_index, tab_index = group + self.window.set_view_index(view, group_index, tab_index) if selection: view.sel().clear() view.sel().add_all(selection) From 627cd9245efc6b3c46904e922a116aeef7efe927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 8 Dec 2025 17:40:51 +0100 Subject: [PATCH 116/129] rename group_index to group MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Chłodnicki --- plugin/rename_file.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 74621a939..d7cdfd04e 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -134,8 +134,7 @@ def rename_path(self, old: str, new: str) -> Promise[bool]: return Promise.resolve(False) return Promise.all([ - open_file_uri(self.window, file_name, group=group_index[0]) - .then(partial(self.restore_view, selection, group_index)) + open_file_uri(self.window, file_name, group=group[0]).then(partial(self.restore_view, selection, group)) for file_name, group_index, selection in restore_files ]).then(lambda _: True) From 4c3673950d7094016dbf1cf4eef0d2cf3345f3a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 8 Dec 2025 17:41:09 +0100 Subject: [PATCH 117/129] Update plugin/rename_file.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Chłodnicki --- plugin/rename_file.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index d7cdfd04e..ab70b4683 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -132,7 +132,6 @@ def rename_path(self, old: str, new: str) -> Promise[bool]: except Exception as error: sublime.status_message(f"Rename error: {error}") return Promise.resolve(False) - return Promise.all([ open_file_uri(self.window, file_name, group=group[0]).then(partial(self.restore_view, selection, group)) for file_name, group_index, selection in restore_files From c672e2b5e92560e551cb1544ad8464fe6f94e906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 8 Dec 2025 17:42:40 +0100 Subject: [PATCH 118/129] rename group_index to group on one missed place --- plugin/rename_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index ab70b4683..d3929b3b2 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -134,7 +134,7 @@ def rename_path(self, old: str, new: str) -> Promise[bool]: return Promise.resolve(False) return Promise.all([ open_file_uri(self.window, file_name, group=group[0]).then(partial(self.restore_view, selection, group)) - for file_name, group_index, selection in restore_files + for file_name, group, selection in restore_files ]).then(lambda _: True) def notify_did_rename(self, file_rename: FileRename) -> None: From a625909651bf54741358215e4630b101fb2b2888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 8 Dec 2025 23:27:42 +0100 Subject: [PATCH 119/129] Bug, compare new_path with old_path instead of new_name with old_path old_path is a full path - "/full/path/to/hello.txt" new_name is not a full path - "./../hello.txt" new_path is a full path - "/full/path/to/hello.txt" --- plugin/rename_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index d3929b3b2..0483a0952 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -75,11 +75,11 @@ def run(self, new_name: str, old_path: str | None = None) -> None: if view: view.set_name(new_name) return - if new_name == old_path: - return # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy resolved_new_path = (Path(old_path).parent / new_name).resolve() new_path = str(resolved_new_path) + if new_path == old_path: + return if resolved_new_path.exists() and not self.is_case_change(old_path, new_path): self.window.status_message('Rename error: Target already exists') return From 69a5dfb49b55e21dd84ede72f90c0b772c0b37dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 8 Dec 2025 23:36:06 +0100 Subject: [PATCH 120/129] make sure to restore the views in the original left-to-right order. self.window.views() returns views ordered from left to right. When we iterate to close the views in the first loop, because closing a view shifts the indices of all subsequent views (to its right), we must iterate and grab the views from right to left (in reversed order). This prevents the shifting indices from causing errors when calling get_view_index for the remaining views. When we restore the views, we do the opposite: we iterate from left to right (in original order). --- plugin/rename_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 0483a0952..d24faebaf 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -118,7 +118,7 @@ def rename_path(self, old: str, new: str) -> Promise[bool]: old_path = Path(old) new_path = Path(new) restore_files: list[tuple[str, tuple[int, int], list[sublime.Region]]] = [] - for view in self.window.views(): + for view in reversed(self.window.views()): if (file_name := view.file_name()) and file_name.startswith(str(old_path)): new_file_name = file_name.replace(str(old_path), str(new_path), 1) restore_files.append((new_file_name, self.window.get_view_index(view), list(view.sel()))) @@ -134,7 +134,7 @@ def rename_path(self, old: str, new: str) -> Promise[bool]: return Promise.resolve(False) return Promise.all([ open_file_uri(self.window, file_name, group=group[0]).then(partial(self.restore_view, selection, group)) - for file_name, group, selection in restore_files + for file_name, group, selection in reversed(restore_files) ]).then(lambda _: True) def notify_did_rename(self, file_rename: FileRename) -> None: From fce69910d91894d58760461ebc4a1a16b4c36a14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Mon, 8 Dec 2025 23:51:17 +0100 Subject: [PATCH 121/129] focus the last active view after restore --- plugin/rename_file.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index d24faebaf..43a79880c 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -118,9 +118,13 @@ def rename_path(self, old: str, new: str) -> Promise[bool]: old_path = Path(old) new_path = Path(new) restore_files: list[tuple[str, tuple[int, int], list[sublime.Region]]] = [] + active_view = self.window.active_view() + last_active_view: str | None = active_view.file_name() if active_view else None for view in reversed(self.window.views()): if (file_name := view.file_name()) and file_name.startswith(str(old_path)): new_file_name = file_name.replace(str(old_path), str(new_path), 1) + if view == active_view: + last_active_view = new_file_name restore_files.append((new_file_name, self.window.get_view_index(view), list(view.sel()))) if view.is_dirty(): view.run_command('save', {'async': False}) @@ -135,7 +139,7 @@ def rename_path(self, old: str, new: str) -> Promise[bool]: return Promise.all([ open_file_uri(self.window, file_name, group=group[0]).then(partial(self.restore_view, selection, group)) for file_name, group, selection in reversed(restore_files) - ]).then(lambda _: True) + ]).then(lambda _: self.focus_view(last_active_view)).then(lambda _: True) def notify_did_rename(self, file_rename: FileRename) -> None: for session in self.sessions(): @@ -143,6 +147,10 @@ def notify_did_rename(self, file_rename: FileRename) -> None: if filters and match_file_operation_filters(filters, file_rename['oldUri']): session.send_notification(Notification.didRenameFiles({'files': [file_rename]})) + def focus_view(self, path: str | None) -> None: + if path and (view := self.window.find_open_file(path)): + self.window.focus_view(view) + def restore_view(self, selection: list[sublime.Region], group: tuple[int, int], view: sublime.View | None) -> None: if not view: return From af4815610e8133561fa319066a15c972ecab6111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 9 Dec 2025 08:46:45 +0100 Subject: [PATCH 122/129] reorder methods by the order they are called in the code --- plugin/rename_file.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 43a79880c..3c9d70191 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -85,6 +85,9 @@ def run(self, new_name: str, old_path: str | None = None) -> None: return sublime.set_timeout_async(lambda: self.run_async(old_path, new_path)) + def is_case_change(self, path_a: str, path_b: str) -> bool: + return path_a.lower() == path_b.lower() and Path(path_a).stat().st_ino == Path(path_b).stat().st_ino + def run_async(self, old_path: str, new_path: str) -> None: file_rename: FileRename = { "newUri": filename_to_uri(new_path), @@ -104,9 +107,6 @@ def create_will_rename_requests_async( yield session.send_request_task(Request.willRenameFiles({'files': [file_rename]})) \ .then(partial(lambda weak_session, response: (response, weak_session), weakref.ref(session))) - def is_case_change(self, path_a: str, path_b: str) -> bool: - return path_a.lower() == path_b.lower() and Path(path_a).stat().st_ino == Path(path_b).stat().st_ino - def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakref.ref[Session]]]) -> Promise: promises: list[Promise] = [] for response, weak_session in responses: @@ -147,10 +147,6 @@ def notify_did_rename(self, file_rename: FileRename) -> None: if filters and match_file_operation_filters(filters, file_rename['oldUri']): session.send_notification(Notification.didRenameFiles({'files': [file_rename]})) - def focus_view(self, path: str | None) -> None: - if path and (view := self.window.find_open_file(path)): - self.window.focus_view(view) - def restore_view(self, selection: list[sublime.Region], group: tuple[int, int], view: sublime.View | None) -> None: if not view: return @@ -159,3 +155,7 @@ def restore_view(self, selection: list[sublime.Region], group: tuple[int, int], if selection: view.sel().clear() view.sel().add_all(selection) + + def focus_view(self, path: str | None) -> None: + if path and (view := self.window.find_open_file(path)): + self.window.focus_view(view) From 42c701f8bddf96461d20a0c029ee297738bb56da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 9 Dec 2025 21:23:14 +0100 Subject: [PATCH 123/129] is_case_change as staticmethod Co-authored-by: jwortmann --- plugin/rename_file.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 3c9d70191..bf5244921 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -85,7 +85,8 @@ def run(self, new_name: str, old_path: str | None = None) -> None: return sublime.set_timeout_async(lambda: self.run_async(old_path, new_path)) - def is_case_change(self, path_a: str, path_b: str) -> bool: + @staticmethod + def is_case_change(path_a: str, path_b: str) -> bool: return path_a.lower() == path_b.lower() and Path(path_a).stat().st_ino == Path(path_b).stat().st_ino def run_async(self, old_path: str, new_path: str) -> None: From 288c6cc2033eeb5db412d1bb60dcfb8bfcc60536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 9 Dec 2025 21:41:19 +0100 Subject: [PATCH 124/129] make it a bit easier to see what happens --- plugin/rename_file.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index bf5244921..f3c50f29b 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -64,13 +64,15 @@ def want_event(self) -> bool: def input(self, args: dict) -> sublime_plugin.TextInputHandler | None: if "new_name" in args: return None - view = self.window.active_view() - old_path = args.get('old_path') or view.file_name() if view else None + old_path = args.get('old_path') + if old_path is None and (view := self.window.active_view()): + old_path = view.file_name() return RenamePathInputHandler(Path(old_path or "").name) def run(self, new_name: str, old_path: str | None = None) -> None: view = self.window.active_view() - old_path = old_path or view.file_name() if view else None + if old_path is None and view: + old_path = view.file_name() if old_path is None: # handle renaming buffers if view: view.set_name(new_name) From b27aa61f6e3fdfffa109309409b3fecfbb97fc0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Tue, 9 Dec 2025 22:09:55 +0100 Subject: [PATCH 125/129] remove the LspRenameFromSidebarOverrideCommand command --- boot.py | 4 +--- plugin/rename_file.py | 31 ++++++++++--------------------- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/boot.py b/boot.py index fde6d9415..e02ab33a5 100644 --- a/boot.py +++ b/boot.py @@ -71,7 +71,6 @@ from .plugin.references import LspSymbolReferencesCommand from .plugin.rename import LspHideRenameButtonsCommand from .plugin.rename import LspSymbolRenameCommand -from .plugin.rename_file import LspRenameFromSidebarOverrideCommand from .plugin.rename_file import LspRenamePathCommand from .plugin.save_command import LspSaveAllCommand from .plugin.save_command import LspSaveCommand @@ -132,7 +131,6 @@ "LspParseVscodePackageJson", "LspPrevDiagnosticCommand", "LspRefactorCommand", - "LspRenameFromSidebarOverrideCommand", "LspRenamePathCommand", "LspResolveDocsCommand", "LspRestartServerCommand", @@ -276,7 +274,7 @@ def on_pre_close(self, view: sublime.View) -> None: def on_window_command(self, window: sublime.Window, command_name: str, args: dict) -> tuple[str, dict] | None: if command_name == "rename_path": - return ('lsp_rename_from_sidebar_override', args) + return ('lsp_rename_path', args) def on_post_window_command(self, window: sublime.Window, command_name: str, args: dict[str, Any] | None) -> None: if command_name == "show_panel": diff --git a/plugin/rename_file.py b/plugin/rename_file.py index f3c50f29b..676056d72 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -19,34 +19,21 @@ from collections.abc import Generator -class LspRenameFromSidebarOverrideCommand(LspWindowCommand): - def is_enabled(self) -> bool: - return True - - def run(self, paths: list[str] | None = None) -> None: - old_path = paths[0] if paths else None - if old_path: - self.window.run_command('lsp_rename_path', { - "old_path": old_path - }) - - class RenamePathInputHandler(sublime_plugin.TextInputHandler): def __init__(self, path: str) -> None: - self.path = path + self.path = Path(path) def name(self) -> str: return "new_name" def placeholder(self) -> str: - return self.path + return self.path.name def initial_text(self) -> str: return self.placeholder() def initial_selection(self) -> list[tuple[int, int]]: - name = Path(self.path).stem - return [(0, len(name))] + return [(0, len(self.path.stem))] def validate(self, path: str) -> bool: return len(path) > 0 @@ -64,12 +51,14 @@ def want_event(self) -> bool: def input(self, args: dict) -> sublime_plugin.TextInputHandler | None: if "new_name" in args: return None - old_path = args.get('old_path') - if old_path is None and (view := self.window.active_view()): - old_path = view.file_name() - return RenamePathInputHandler(Path(old_path or "").name) + if paths := args.get('paths'): # command was called from side bar context menu + return RenamePathInputHandler(paths[0]) + if (view := self.window.active_view()) and (file_name := view.file_name()): + return RenamePathInputHandler(file_name) + return RenamePathInputHandler("") - def run(self, new_name: str, old_path: str | None = None) -> None: + def run(self, new_name: str, paths: list[str] | None = None) -> None: + old_path = paths[0] if paths else None view = self.window.active_view() if old_path is None and view: old_path = view.file_name() From 44dd412e4868875f7b59ed9180c23496811279d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 13 Dec 2025 14:35:03 +0100 Subject: [PATCH 126/129] only apply workspace edits from one server and ignore the rest because required_view_version might throw an error --- plugin/rename_file.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 676056d72..8dc22e861 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -104,6 +104,7 @@ def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakre for response, weak_session in responses: if (session := weak_session()) and response: promises.append(session.apply_workspace_edit_async(response, is_refactoring=True)) + break return Promise.all(promises) def rename_path(self, old: str, new: str) -> Promise[bool]: From 3267e02f1eb531fdb30f0d410f397c960946638f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 13 Dec 2025 17:24:08 +0100 Subject: [PATCH 127/129] send will rename request to only one session Initially it was implemented to be send to only one server later in fbcbec59 it was changed to be send to all servers the benefit is that multiple server can receive WorkspaceEdit, however the downside is that apply workspace edits in the LSP can fail if the document version changes. So for now I will only send the request to one server --- plugin/rename_file.py | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 8dc22e861..017386454 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -10,13 +10,10 @@ from typing import TYPE_CHECKING import sublime import sublime_plugin -import weakref if TYPE_CHECKING: from ..protocol import FileRename from ..protocol import WorkspaceEdit - from .core.sessions import Session - from collections.abc import Generator class RenamePathInputHandler(sublime_plugin.TextInputHandler): @@ -85,27 +82,24 @@ def run_async(self, old_path: str, new_path: str) -> None: "newUri": filename_to_uri(new_path), "oldUri": filename_to_uri(old_path) } - Promise.all(list(self.create_will_rename_requests_async(file_rename))) \ - .then(lambda responses: self.handle_rename_async(responses)) \ + self.create_will_rename_request_async(file_rename) \ + .then(self.handle_rename_async) \ .then(lambda _: self.rename_path(old_path, new_path)) \ .then(lambda success: self.notify_did_rename(file_rename) if success else None) - def create_will_rename_requests_async( + def create_will_rename_request_async( self, file_rename: FileRename - ) -> Generator[Promise[tuple[WorkspaceEdit | None, weakref.ref[Session]]]]: - for session in self.sessions(): + ) -> Promise[WorkspaceEdit | None]: + if session := self.session(): filters = session.get_capability('workspace.fileOperations.willRename.filters') or [] if match_file_operation_filters(filters, file_rename['oldUri']): - yield session.send_request_task(Request.willRenameFiles({'files': [file_rename]})) \ - .then(partial(lambda weak_session, response: (response, weak_session), weakref.ref(session))) - - def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakref.ref[Session]]]) -> Promise: - promises: list[Promise] = [] - for response, weak_session in responses: - if (session := weak_session()) and response: - promises.append(session.apply_workspace_edit_async(response, is_refactoring=True)) - break - return Promise.all(promises) + return session.send_request_task(Request.willRenameFiles({'files': [file_rename]})) + return Promise.resolve(None) + + def handle_rename_async(self, response: WorkspaceEdit | None) -> Promise[None]: + if (session := self.session()) and response: + return session.apply_workspace_edit_async(response, is_refactoring=True) + return Promise.resolve(None) def rename_path(self, old: str, new: str) -> Promise[bool]: old_path = Path(old) From 9cdde00d73160f9509fe8bb2762f45c3caf2077d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 13 Dec 2025 18:19:52 +0100 Subject: [PATCH 128/129] Revert "send will rename request to only one session" This reverts commit 3267e02f1eb531fdb30f0d410f397c960946638f. --- plugin/rename_file.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 017386454..8dc22e861 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -10,10 +10,13 @@ from typing import TYPE_CHECKING import sublime import sublime_plugin +import weakref if TYPE_CHECKING: from ..protocol import FileRename from ..protocol import WorkspaceEdit + from .core.sessions import Session + from collections.abc import Generator class RenamePathInputHandler(sublime_plugin.TextInputHandler): @@ -82,24 +85,27 @@ def run_async(self, old_path: str, new_path: str) -> None: "newUri": filename_to_uri(new_path), "oldUri": filename_to_uri(old_path) } - self.create_will_rename_request_async(file_rename) \ - .then(self.handle_rename_async) \ + Promise.all(list(self.create_will_rename_requests_async(file_rename))) \ + .then(lambda responses: self.handle_rename_async(responses)) \ .then(lambda _: self.rename_path(old_path, new_path)) \ .then(lambda success: self.notify_did_rename(file_rename) if success else None) - def create_will_rename_request_async( + def create_will_rename_requests_async( self, file_rename: FileRename - ) -> Promise[WorkspaceEdit | None]: - if session := self.session(): + ) -> Generator[Promise[tuple[WorkspaceEdit | None, weakref.ref[Session]]]]: + for session in self.sessions(): filters = session.get_capability('workspace.fileOperations.willRename.filters') or [] if match_file_operation_filters(filters, file_rename['oldUri']): - return session.send_request_task(Request.willRenameFiles({'files': [file_rename]})) - return Promise.resolve(None) - - def handle_rename_async(self, response: WorkspaceEdit | None) -> Promise[None]: - if (session := self.session()) and response: - return session.apply_workspace_edit_async(response, is_refactoring=True) - return Promise.resolve(None) + yield session.send_request_task(Request.willRenameFiles({'files': [file_rename]})) \ + .then(partial(lambda weak_session, response: (response, weak_session), weakref.ref(session))) + + def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakref.ref[Session]]]) -> Promise: + promises: list[Promise] = [] + for response, weak_session in responses: + if (session := weak_session()) and response: + promises.append(session.apply_workspace_edit_async(response, is_refactoring=True)) + break + return Promise.all(promises) def rename_path(self, old: str, new: str) -> Promise[bool]: old_path = Path(old) From 58b76e48714f2e4c061d11cdf8f265015f498d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=80=D0=B5=D0=B4=D1=80=D0=B0=D0=B3=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=9B?= Date: Sat, 13 Dec 2025 18:25:43 +0100 Subject: [PATCH 129/129] send the request to all sessions, but then only apply one of the responses. The response type for the request is WorkspaceEdit | null, so maybe there could be a case where only one of the servers responds with a WorkspaceEdit, and this way would increase the chance that we get at least one response which is not null --- plugin/rename_file.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/plugin/rename_file.py b/plugin/rename_file.py index 8dc22e861..31dbd848b 100644 --- a/plugin/rename_file.py +++ b/plugin/rename_file.py @@ -99,13 +99,11 @@ def create_will_rename_requests_async( yield session.send_request_task(Request.willRenameFiles({'files': [file_rename]})) \ .then(partial(lambda weak_session, response: (response, weak_session), weakref.ref(session))) - def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakref.ref[Session]]]) -> Promise: - promises: list[Promise] = [] + def handle_rename_async(self, responses: list[tuple[WorkspaceEdit | None, weakref.ref[Session]]]) -> Promise[None]: for response, weak_session in responses: if (session := weak_session()) and response: - promises.append(session.apply_workspace_edit_async(response, is_refactoring=True)) - break - return Promise.all(promises) + return session.apply_workspace_edit_async(response, is_refactoring=True) + return Promise.resolve(None) def rename_path(self, old: str, new: str) -> Promise[bool]: old_path = Path(old)