From bb74b932f367e7ae97486cd13f6cd4d28d62ff1f Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Wed, 17 Dec 2025 13:55:34 +0100 Subject: [PATCH 1/7] Return number of changes and changed files when applying WorkspaceEdit --- plugin/core/sessions.py | 23 +++++++++++++++-------- plugin/rename.py | 4 ++-- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py index f80d96b35..69aed25da 100644 --- a/plugin/core/sessions.py +++ b/plugin/core/sessions.py @@ -1710,7 +1710,7 @@ def _template_variables(self) -> dict[str, str]: def execute_command( self, command: ExecuteCommandParams, *, progress: bool = False, view: sublime.View | None = None, is_refactoring: bool = False, - ) -> Promise: + ) -> Promise[None]: """Run a command from any thread. Your .then() continuations will run in Sublime's worker thread.""" if self._plugin: task: PackagedTask[None] = Promise.packaged_task() @@ -1915,8 +1915,8 @@ def _apply_code_action_async( title = code_action['title'] edit = code_action.get("edit") is_refactoring = kind_contains_other_kind(CodeActionKind.Refactor, code_action.get('kind', '')) - promise = self.apply_workspace_edit_async(edit, label=title, is_refactoring=is_refactoring) if edit else \ - Promise.resolve(None) + promise = self.apply_workspace_edit_async(edit, label=title, is_refactoring=is_refactoring) \ + .then(lambda _: None) if edit else Promise.resolve(None) command = code_action.get("command") if command is not None: execute_command: ExecuteCommandParams = { @@ -1931,15 +1931,19 @@ def _apply_code_action_async( def apply_workspace_edit_async( self, edit: WorkspaceEdit, *, label: str | None = None, is_refactoring: bool = False - ) -> Promise[None]: + ) -> Promise[tuple[int, int]]: """ - Apply workspace edits, and return a promise that resolves on the async thread again after the edits have been - applied. + Apply a WorkspaceEdit, and return a promise that resolves on the async thread again after the edits have been + applied. The resolved promise contains the total number of changes and the number of affected files in the + WorkspaceEdit. """ is_refactoring = self._is_executing_refactoring_command or is_refactoring return self.apply_parsed_workspace_edits(parse_workspace_edit(edit, label), is_refactoring) - def apply_parsed_workspace_edits(self, changes: WorkspaceChanges, is_refactoring: bool = False) -> Promise[None]: + def apply_parsed_workspace_edits( + self, changes: WorkspaceChanges, is_refactoring: bool = False + ) -> Promise[tuple[int, int]]: + def handle_view( edits: list[TextEdit], label: str | None, @@ -1958,6 +1962,8 @@ def handle_view( selected_sheets = self.window.selected_sheets() promises: list[Promise[None]] = [] auto_save = userprefs().refactoring_auto_save if is_refactoring else 'never' + total_changes = sum(map(lambda val: len(val[0]), changes.values())) + files = len(changes) for uri, (edits, label, view_version) in changes.items(): view_state_actions = self._get_view_state_actions(uri, auto_save) promises.append( @@ -1966,7 +1972,8 @@ def handle_view( ) return Promise.all(promises) \ .then(lambda _: self._set_selected_sheets(selected_sheets)) \ - .then(lambda _: self._set_focused_sheet(active_sheet)) + .then(lambda _: self._set_focused_sheet(active_sheet)) \ + .then(lambda _: (total_changes, files)) def _get_view_state_actions(self, uri: DocumentUri, auto_save: str) -> ViewStateActions: """ diff --git a/plugin/rename.py b/plugin/rename.py index 132de8298..f2510e7b4 100644 --- a/plugin/rename.py +++ b/plugin/rename.py @@ -196,11 +196,11 @@ def _on_rename_result_async(self, session: Session, label: str, response: Worksp if not response: return session.window.status_message('Nothing to rename') changes = parse_workspace_edit(response, label) - file_count = len(changes.keys()) + file_count = len(changes) if file_count == 1: session.apply_parsed_workspace_edits(changes, True) return - total_changes = sum(map(len, changes.values())) + total_changes = sum(map(lambda val: len(val[0]), changes.values())) message = f"Replace {total_changes} occurrences across {file_count} files?" choice = sublime.yes_no_cancel_dialog(message, "Replace", "Preview", title="Rename") if choice == sublime.DialogResult.YES: From 1c2287f28821e05dc5074adef56390637ca24a35 Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Thu, 18 Dec 2025 15:24:41 +0100 Subject: [PATCH 2/7] Use consistent variable names across files --- plugin/core/sessions.py | 6 +++--- plugin/rename.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py index 69aed25da..2eafd2fb4 100644 --- a/plugin/core/sessions.py +++ b/plugin/core/sessions.py @@ -1962,8 +1962,8 @@ def handle_view( selected_sheets = self.window.selected_sheets() promises: list[Promise[None]] = [] auto_save = userprefs().refactoring_auto_save if is_refactoring else 'never' - total_changes = sum(map(lambda val: len(val[0]), changes.values())) - files = len(changes) + total_changes = sum(map(lambda value: len(value[0]), changes.values())) + file_count = len(changes) for uri, (edits, label, view_version) in changes.items(): view_state_actions = self._get_view_state_actions(uri, auto_save) promises.append( @@ -1973,7 +1973,7 @@ def handle_view( return Promise.all(promises) \ .then(lambda _: self._set_selected_sheets(selected_sheets)) \ .then(lambda _: self._set_focused_sheet(active_sheet)) \ - .then(lambda _: (total_changes, files)) + .then(lambda _: (total_changes, file_count)) def _get_view_state_actions(self, uri: DocumentUri, auto_save: str) -> ViewStateActions: """ diff --git a/plugin/rename.py b/plugin/rename.py index f2510e7b4..4befbdeec 100644 --- a/plugin/rename.py +++ b/plugin/rename.py @@ -200,7 +200,7 @@ def _on_rename_result_async(self, session: Session, label: str, response: Worksp if file_count == 1: session.apply_parsed_workspace_edits(changes, True) return - total_changes = sum(map(lambda val: len(val[0]), changes.values())) + total_changes = sum(map(lambda value: len(value[0]), changes.values())) message = f"Replace {total_changes} occurrences across {file_count} files?" choice = sublime.yes_no_cancel_dialog(message, "Replace", "Preview", title="Rename") if choice == sublime.DialogResult.YES: From d4d7f1b8d58c2404923759bf19d73e5f2afa240a Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Fri, 19 Dec 2025 15:36:16 +0100 Subject: [PATCH 3/7] Simplify --- plugin/core/sessions.py | 2 +- plugin/rename.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py index 2eafd2fb4..2bfe2c381 100644 --- a/plugin/core/sessions.py +++ b/plugin/core/sessions.py @@ -1962,7 +1962,7 @@ def handle_view( selected_sheets = self.window.selected_sheets() promises: list[Promise[None]] = [] auto_save = userprefs().refactoring_auto_save if is_refactoring else 'never' - total_changes = sum(map(lambda value: len(value[0]), changes.values())) + total_changes = sum(len(value[0]) for value in changes.values()) file_count = len(changes) for uri, (edits, label, view_version) in changes.items(): view_state_actions = self._get_view_state_actions(uri, auto_save) diff --git a/plugin/rename.py b/plugin/rename.py index 4befbdeec..044fe4181 100644 --- a/plugin/rename.py +++ b/plugin/rename.py @@ -200,7 +200,7 @@ def _on_rename_result_async(self, session: Session, label: str, response: Worksp if file_count == 1: session.apply_parsed_workspace_edits(changes, True) return - total_changes = sum(map(lambda value: len(value[0]), changes.values())) + total_changes = sum(len(value[0]) for value in changes.values()) message = f"Replace {total_changes} occurrences across {file_count} files?" choice = sublime.yes_no_cancel_dialog(message, "Replace", "Preview", title="Rename") if choice == sublime.DialogResult.YES: From 84d7dc562ab6fc26558dde412050fc35c9c6a60d Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Sat, 20 Dec 2025 14:31:52 +0100 Subject: [PATCH 4/7] Introduce WorkspaceEditSummary dict as return type --- plugin/core/edit.py | 10 +++++++++- plugin/core/sessions.py | 16 +++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/plugin/core/edit.py b/plugin/core/edit.py index 66ffdadde..de2c066ff 100644 --- a/plugin/core/edit.py +++ b/plugin/core/edit.py @@ -6,13 +6,21 @@ from .logging import debug from .promise import Promise from .protocol import UINT_MAX -from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, List, NotRequired, Optional, Tuple, TypedDict, Union import sublime WorkspaceChanges = Dict[str, Tuple[List[Union[TextEdit, AnnotatedTextEdit]], Optional[str], Optional[int]]] +class WorkspaceEditSummary(TypedDict): + total_changes: int + file_count: int + created_files: NotRequired[int] + renamed_files: NotRequired[int] + deleted_files: NotRequired[int] + + def parse_workspace_edit(workspace_edit: WorkspaceEdit, label: str | None = None) -> WorkspaceChanges: changes: WorkspaceChanges = {} document_changes = workspace_edit.get('documentChanges') diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py index 2bfe2c381..555d03f61 100644 --- a/plugin/core/sessions.py +++ b/plugin/core/sessions.py @@ -76,6 +76,7 @@ from .edit import apply_text_edits from .edit import parse_workspace_edit from .edit import WorkspaceChanges +from .edit import WorkspaceEditSummary from .file_watcher import DEFAULT_WATCH_KIND from .file_watcher import file_watcher_event_type_to_lsp_file_change_type from .file_watcher import FileWatcher @@ -1931,18 +1932,17 @@ def _apply_code_action_async( def apply_workspace_edit_async( self, edit: WorkspaceEdit, *, label: str | None = None, is_refactoring: bool = False - ) -> Promise[tuple[int, int]]: + ) -> Promise[WorkspaceEditSummary]: """ Apply a WorkspaceEdit, and return a promise that resolves on the async thread again after the edits have been - applied. The resolved promise contains the total number of changes and the number of affected files in the - WorkspaceEdit. + applied. The resolved promise contains a summary of the changes in the WorkspaceEdit. """ is_refactoring = self._is_executing_refactoring_command or is_refactoring return self.apply_parsed_workspace_edits(parse_workspace_edit(edit, label), is_refactoring) def apply_parsed_workspace_edits( self, changes: WorkspaceChanges, is_refactoring: bool = False - ) -> Promise[tuple[int, int]]: + ) -> Promise[WorkspaceEditSummary]: def handle_view( edits: list[TextEdit], @@ -1962,8 +1962,10 @@ def handle_view( selected_sheets = self.window.selected_sheets() promises: list[Promise[None]] = [] auto_save = userprefs().refactoring_auto_save if is_refactoring else 'never' - total_changes = sum(len(value[0]) for value in changes.values()) - file_count = len(changes) + summary: WorkspaceEditSummary = { + 'total_changes': sum(len(value[0]) for value in changes.values()), + 'file_count': len(changes) + } for uri, (edits, label, view_version) in changes.items(): view_state_actions = self._get_view_state_actions(uri, auto_save) promises.append( @@ -1973,7 +1975,7 @@ def handle_view( return Promise.all(promises) \ .then(lambda _: self._set_selected_sheets(selected_sheets)) \ .then(lambda _: self._set_focused_sheet(active_sheet)) \ - .then(lambda _: (total_changes, file_count)) + .then(lambda _: summary) def _get_view_state_actions(self, uri: DocumentUri, auto_save: str) -> ViewStateActions: """ From 4bbd570f9d4ff959e94e7dc6f3cedc8d85f38478 Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Sat, 20 Dec 2025 14:36:05 +0100 Subject: [PATCH 5/7] Fix import --- plugin/core/edit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/core/edit.py b/plugin/core/edit.py index de2c066ff..19b6855fa 100644 --- a/plugin/core/edit.py +++ b/plugin/core/edit.py @@ -6,7 +6,8 @@ from .logging import debug from .promise import Promise from .protocol import UINT_MAX -from typing import Dict, List, NotRequired, Optional, Tuple, TypedDict, Union +from typing import Dict, List, Optional, Tuple, TypedDict, Union +from typing_extensions import NotRequired import sublime From fe1ecfa8e09c1c630105ddf5804c7156d91a6d9d Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Sat, 20 Dec 2025 15:53:45 +0100 Subject: [PATCH 6/7] Tweak naming --- plugin/core/edit.py | 2 +- plugin/core/sessions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/core/edit.py b/plugin/core/edit.py index 19b6855fa..7da4d1510 100644 --- a/plugin/core/edit.py +++ b/plugin/core/edit.py @@ -16,7 +16,7 @@ class WorkspaceEditSummary(TypedDict): total_changes: int - file_count: int + updated_files: int created_files: NotRequired[int] renamed_files: NotRequired[int] deleted_files: NotRequired[int] diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py index 555d03f61..64ee4b1ed 100644 --- a/plugin/core/sessions.py +++ b/plugin/core/sessions.py @@ -1964,7 +1964,7 @@ def handle_view( auto_save = userprefs().refactoring_auto_save if is_refactoring else 'never' summary: WorkspaceEditSummary = { 'total_changes': sum(len(value[0]) for value in changes.values()), - 'file_count': len(changes) + 'updated_files': len(changes) } for uri, (edits, label, view_version) in changes.items(): view_state_actions = self._get_view_state_actions(uri, auto_save) From cf6c994938efd39f05a63306b538524f93d8943b Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Sat, 20 Dec 2025 16:10:40 +0100 Subject: [PATCH 7/7] Tweak naming again --- plugin/core/edit.py | 2 +- plugin/core/sessions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/core/edit.py b/plugin/core/edit.py index 7da4d1510..9ae0b4434 100644 --- a/plugin/core/edit.py +++ b/plugin/core/edit.py @@ -16,7 +16,7 @@ class WorkspaceEditSummary(TypedDict): total_changes: int - updated_files: int + edited_files: int created_files: NotRequired[int] renamed_files: NotRequired[int] deleted_files: NotRequired[int] diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py index 64ee4b1ed..9e5c02cb7 100644 --- a/plugin/core/sessions.py +++ b/plugin/core/sessions.py @@ -1964,7 +1964,7 @@ def handle_view( auto_save = userprefs().refactoring_auto_save if is_refactoring else 'never' summary: WorkspaceEditSummary = { 'total_changes': sum(len(value[0]) for value in changes.values()), - 'updated_files': len(changes) + 'edited_files': len(changes) } for uri, (edits, label, view_version) in changes.items(): view_state_actions = self._get_view_state_actions(uri, auto_save)