Skip to content

Commit

Permalink
Merge branch 'main' into no-restart
Browse files Browse the repository at this point in the history
  • Loading branch information
jwortmann committed May 3, 2024
2 parents 4f57303 + 2a47052 commit 8d0daff
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 24 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
os:
- ubuntu-latest
# - macOS-latest
- windows-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
Expand Down
8 changes: 8 additions & 0 deletions LSP.sublime-settings
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,14 @@
// "region",
],

// Controls if files that were part of a refactoring (e.g. rename) are saved automatically:
// "always" - save all affected files
// "preserve" - only save files that didn't have unsaved changes beforehand
// "preserve_opened" - only save opened files that didn't have unsaved changes beforehand
// and open other files that were affected by the refactoring
// "never" - never save files automatically
"refactoring_auto_save": "never",

// --- Debugging ----------------------------------------------------------------------

// Show verbose debug messages in the sublime console.
Expand Down
4 changes: 2 additions & 2 deletions docs/src/keyboard_shortcuts.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ Refer to the [Customization section](customization.md#keyboard-shortcuts-key-bin
| Run Code Lens | unbound | `lsp_code_lens`
| Run Refactor Action | unbound | `lsp_code_actions`<br>With args: `{"only_kinds": ["refactor"]}`.
| Run Source Action | unbound | `lsp_code_actions`<br>With args: `{"only_kinds": ["source"]}`.
| Save All | unbound | `lsp_save_all`<br>Supports optional args `{"only_files": true}` - to ignore buffers which have no associated file on disk.
| Save All | unbound | `lsp_save_all`<br>Supports optional args `{"only_files": true | false}` - whether to ignore buffers which have no associated file on disk.
| Show Call Hierarchy | unbound | `lsp_call_hierarchy`
| Show Type Hierarchy | unbound | `lsp_type_hierarchy`
| Signature Help | <kbd>ctrl</kbd> <kbd>alt</kbd> <kbd>space</kbd> | `lsp_signature_help_show`
| Toggle Diagnostics Panel | <kbd>ctrl</kbd> <kbd>alt</kbd> <kbd>m</kbd> | `lsp_show_diagnostics_panel`
| Toggle Inlay Hints | unbound | `lsp_toggle_inlay_hints`<br>Supports optional args: `{"enable": true/false}`.
| Toggle Inlay Hints | unbound | `lsp_toggle_inlay_hints`<br>Supports optional args: `{"enable": true | false}`.
| Toggle Log Panel | unbound | `lsp_toggle_server_panel`
2 changes: 1 addition & 1 deletion plugin/core/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def get_position(view: sublime.View, event: dict | None = None, point: int | Non
if x is not None and y is not None:
return view.window_to_text((x, y))
try:
return view.sel()[0].begin()
return view.sel()[0].b
except IndexError:
return None

Expand Down
72 changes: 65 additions & 7 deletions plugin/core/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
from .protocol import WorkspaceEdit
from .settings import client_configs
from .settings import globalprefs
from .settings import userprefs
from .transports import Transport
from .transports import TransportCallbacks
from .types import Capabilities
Expand All @@ -111,7 +112,7 @@
from abc import ABCMeta
from abc import abstractmethod
from abc import abstractproperty
from enum import IntEnum
from enum import IntEnum, IntFlag
from typing import Any, Callable, Generator, List, Protocol, TypeVar
from typing import cast
from typing_extensions import TypeAlias, TypeGuard
Expand All @@ -126,6 +127,11 @@
T = TypeVar('T')


class ViewStateActions(IntFlag):
Close = 2
Save = 1


def is_workspace_full_document_diagnostic_report(
report: WorkspaceDocumentDiagnosticReport
) -> TypeGuard[WorkspaceFullDocumentDiagnosticReport]:
Expand Down Expand Up @@ -1785,7 +1791,8 @@ def _apply_code_action_async(
self.window.status_message(f"Failed to apply code action: {code_action}")
return Promise.resolve(None)
edit = code_action.get("edit")
promise = self.apply_workspace_edit_async(edit) if edit else Promise.resolve(None)
is_refactoring = code_action.get('kind') == CodeActionKind.Refactor
promise = self.apply_workspace_edit_async(edit, is_refactoring) if edit else Promise.resolve(None)
command = code_action.get("command")
if command is not None:
execute_command: ExecuteCommandParams = {
Expand All @@ -1797,32 +1804,83 @@ def _apply_code_action_async(
return promise.then(lambda _: self.execute_command(execute_command, progress=False, view=view))
return promise

def apply_workspace_edit_async(self, edit: WorkspaceEdit) -> Promise[None]:
def apply_workspace_edit_async(self, edit: WorkspaceEdit, is_refactoring: bool = False) -> Promise[None]:
"""
Apply workspace edits, and return a promise that resolves on the async thread again after the edits have been
applied.
"""
return self.apply_parsed_workspace_edits(parse_workspace_edit(edit))
return self.apply_parsed_workspace_edits(parse_workspace_edit(edit), is_refactoring)

def apply_parsed_workspace_edits(self, changes: WorkspaceChanges) -> Promise[None]:
def apply_parsed_workspace_edits(self, changes: WorkspaceChanges, is_refactoring: bool = False) -> Promise[None]:
active_sheet = self.window.active_sheet()
selected_sheets = self.window.selected_sheets()
promises: list[Promise[None]] = []
auto_save = userprefs().refactoring_auto_save if is_refactoring else 'never'
for uri, (edits, view_version) in changes.items():
view_state_actions = self._get_view_state_actions(uri, auto_save)
promises.append(
self.open_uri_async(uri).then(functools.partial(self._apply_text_edits, edits, view_version, uri))
.then(functools.partial(self._set_view_state, view_state_actions))
)
return Promise.all(promises) \
.then(lambda _: self._set_selected_sheets(selected_sheets)) \
.then(lambda _: self._set_focused_sheet(active_sheet))

def _apply_text_edits(
self, edits: list[TextEdit], view_version: int | None, uri: str, view: sublime.View | None
) -> None:
) -> sublime.View | None:
if view is None or not view.is_valid():
print(f'LSP: ignoring edits due to no view for uri: {uri}')
return
return None
apply_text_edits(view, edits, required_view_version=view_version)
return view

def _get_view_state_actions(self, uri: DocumentUri, auto_save: str) -> int:
"""
Determine the required actions for a view after applying a WorkspaceEdit, depending on the
"refactoring_auto_save" user setting. Returns a bitwise combination of ViewStateActions.Save and
ViewStateActions.Close, or 0 if no action is necessary.
"""
if auto_save == 'never':
return 0 # Never save or close automatically
scheme, filepath = parse_uri(uri)
if scheme != 'file':
return 0 # Can't save or close unsafed buffers (and other schemes) without user dialog
view = self.window.find_open_file(filepath)
if view:
is_opened = True
is_dirty = view.is_dirty()
else:
is_opened = False
is_dirty = False
actions = 0
if auto_save == 'always':
actions |= ViewStateActions.Save # Always save
if not is_opened:
actions |= ViewStateActions.Close # Close if file was previously closed
elif auto_save == 'preserve':
if not is_dirty:
actions |= ViewStateActions.Save # Only save if file didn't have unsaved changes
if not is_opened:
actions |= ViewStateActions.Close # Close if file was previously closed
elif auto_save == 'preserve_opened':
if is_opened and not is_dirty:
# Only save if file was already open and didn't have unsaved changes, but never close
actions |= ViewStateActions.Save
return actions

def _set_view_state(self, actions: int, view: sublime.View | None) -> None:
if not view:
return
should_save = bool(actions & ViewStateActions.Save)
should_close = bool(actions & ViewStateActions.Close)
if should_save and view.is_dirty():
# The save operation must be blocking in case the tab should be closed afterwards
view.run_command('save', {'async': not should_close, 'quiet': True})
if should_close and not view.is_dirty():
if view != self.window.active_view():
self.window.focus_view(view)
self.window.run_command('close')

def _set_selected_sheets(self, sheets: list[sublime.Sheet]) -> None:
if len(sheets) > 1 and len(self.window.selected_sheets()) != len(sheets):
Expand Down
2 changes: 2 additions & 0 deletions plugin/core/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ class Settings:
only_show_lsp_completions = cast(bool, None)
popup_max_characters_height = cast(int, None)
popup_max_characters_width = cast(int, None)
refactoring_auto_save = cast(str, None)
semantic_highlighting = cast(bool, None)
show_code_actions = cast(str, None)
show_code_lens = cast(str, None)
Expand Down Expand Up @@ -265,6 +266,7 @@ def r(name: str, default: bool | int | str | list | dict) -> None:
r("completion_insert_mode", 'insert')
r("popup_max_characters_height", 1000)
r("popup_max_characters_width", 120)
r("refactoring_auto_save", "never")
r("semantic_highlighting", False)
r("show_code_actions", "annotation")
r("show_code_lens", "annotation")
Expand Down
4 changes: 2 additions & 2 deletions plugin/edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ def temporary_setting(settings: sublime.Settings, key: str, val: Any) -> Generat

class LspApplyWorkspaceEditCommand(LspWindowCommand):

def run(self, session_name: str, edit: WorkspaceEdit) -> None:
def run(self, session_name: str, edit: WorkspaceEdit, is_refactoring: bool = False) -> None:
session = self.session_by_name(session_name)
if not session:
debug('Could not find session', session_name, 'required to apply WorkspaceEdit')
return
sublime.set_timeout_async(lambda: session.apply_workspace_edit_async(edit))
sublime.set_timeout_async(lambda: session.apply_workspace_edit_async(edit, is_refactoring))


class LspApplyDocumentEditCommand(sublime_plugin.TextCommand):
Expand Down
11 changes: 3 additions & 8 deletions plugin/hover.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
from .core.protocol import Position
from .core.protocol import Range
from .core.protocol import Request
from .core.registry import get_position
from .core.registry import LspTextCommand
from .core.registry import windows
from .core.sessions import AbstractViewListener
from .core.sessions import SessionBufferProtocol
from .core.settings import userprefs
from .core.url import parse_uri
from .core.views import diagnostic_severity
from .core.views import first_selection_region
from .core.views import format_code_actions_for_quick_panel
from .core.views import format_diagnostic_for_html
from .core.views import FORMAT_MARKED_STRING
Expand Down Expand Up @@ -115,17 +115,12 @@ def run(
point: int | None = None,
event: dict | None = None
) -> None:
temp_point = point
if temp_point is None:
region = first_selection_region(self.view)
if region is not None:
temp_point = region.begin()
if temp_point is None:
hover_point = get_position(self.view, event, point)
if hover_point is None:
return
wm = windows.lookup(self.view.window())
if not wm:
return
hover_point = temp_point
self._base_dir = wm.get_project_path(self.view.file_name() or "")
self._hover_responses: list[tuple[Hover, MarkdownLangMap | None]] = []
self._document_links: list[DocumentLink] = []
Expand Down
6 changes: 3 additions & 3 deletions plugin/rename.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,13 @@ def _on_rename_result_async(self, session: Session, response: WorkspaceEdit | No
changes = parse_workspace_edit(response)
file_count = len(changes.keys())
if file_count == 1:
session.apply_parsed_workspace_edits(changes)
session.apply_parsed_workspace_edits(changes, True)
return
total_changes = sum(map(len, 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.DIALOG_YES:
session.apply_parsed_workspace_edits(changes)
session.apply_parsed_workspace_edits(changes, True)
elif choice == sublime.DIALOG_NO:
self._render_rename_panel(response, changes, total_changes, file_count, session.config.name)

Expand Down Expand Up @@ -298,7 +298,7 @@ def _render_rename_panel(
'commands': [
[
'lsp_apply_workspace_edit',
{'session_name': session_name, 'edit': workspace_edit}
{'session_name': session_name, 'edit': workspace_edit, 'is_refactoring': True}
],
[
'hide_panel',
Expand Down
17 changes: 17 additions & 0 deletions sublime-package.json
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,23 @@
},
"uniqueItems": true,
"markdownDescription": "Determines ranges which initially should be folded when a document is opened, provided that the language server has support for this."
},
"refactoring_auto_save": {
"type": "string",
"enum": [
"always",
"preserve",
"preserve_opened",
"never"
],
"markdownEnumDescriptions": [
"Save all affected files",
"Only save files that didn't have unsaved changes beforehand",
"Only save opened files that didn't have unsaved changes beforehand and open other files that were affected by the refactoring",
"Never save files automatically"
],
"default": "never",
"markdownDescription": "Controls if files that were part of a refactoring (e.g. rename) are saved automatically."
}
},
"additionalProperties": false
Expand Down

0 comments on commit 8d0daff

Please sign in to comment.