Skip to content

Commit

Permalink
refactor handling of custom editor commands (#2280)
Browse files Browse the repository at this point in the history
  • Loading branch information
rchl authored Jun 24, 2023
1 parent 1b435a9 commit 757908a
Show file tree
Hide file tree
Showing 10 changed files with 60 additions and 38 deletions.
9 changes: 6 additions & 3 deletions plugin/code_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,11 +253,14 @@ def _request_code_actions_async(self) -> None:
def _handle_response_async(self, responses: List[CodeActionsByConfigName]) -> None:
if self._cancelled:
return
view = self._task_runner.view
tasks = [] # type: List[Promise]
for config_name, code_actions in responses:
session = self._task_runner.session_by_name(config_name, 'codeActionProvider')
if session:
tasks.extend([session.run_code_action_async(action, progress=False) for action in code_actions])
tasks.extend([
session.run_code_action_async(action, progress=False, view=view) for action in code_actions
])
Promise.all(tasks).then(lambda _: self._on_complete())


Expand Down Expand Up @@ -337,7 +340,7 @@ def run_async() -> None:
config_name, action = actions[index]
session = self.session_by_name(config_name)
if session:
session.run_code_action_async(action, progress=True) \
session.run_code_action_async(action, progress=True, view=self.view) \
.then(lambda response: self._handle_response_async(config_name, response))

sublime.set_timeout_async(run_async)
Expand Down Expand Up @@ -401,7 +404,7 @@ def run_async(self, id: int, event: Optional[dict]) -> None:
config_name, action = self.actions_cache[id]
session = self.session_by_name(config_name)
if session:
session.run_code_action_async(action, progress=True) \
session.run_code_action_async(action, progress=True, view=self.view) \
.then(lambda response: self._handle_response_async(config_name, response))

def _handle_response_async(self, session_name: str, response: Any) -> None:
Expand Down
7 changes: 3 additions & 4 deletions plugin/core/registry.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from .protocol import Diagnostic
from .protocol import Location
from .protocol import LocationLink
from .protocol import Point
from .sessions import AbstractViewListener
from .sessions import Session
from .tree_view import TreeDataProvider
Expand All @@ -10,7 +9,7 @@
from .views import first_selection_region
from .views import get_uri_and_position_from_location
from .views import MissingUriError
from .views import point_to_offset
from .views import position_to_offset
from .views import uri_from_view
from .windows import WindowManager
from .windows import WindowRegistry
Expand Down Expand Up @@ -299,11 +298,11 @@ def navigate_diagnostics(view: sublime.View, point: Optional[int], forward: bool
# this view after/before the cursor
op_func = operator.gt if forward else operator.lt
for diagnostic in diagnostics:
diag_pos = point_to_offset(Point.from_lsp(diagnostic['range']['start']), view)
diag_pos = position_to_offset(diagnostic['range']['start'], view)
if op_func(diag_pos, point):
break
else:
diag_pos = point_to_offset(Point.from_lsp(diagnostics[0]['range']['start']), view)
diag_pos = position_to_offset(diagnostics[0]['range']['start'], view)
view.run_command('lsp_selection_set', {'regions': [(diag_pos, diag_pos)]})
view.show_at_center(diag_pos)
# We need a small delay before showing the popup to wait for the scrolling animation to finish. Otherwise ST would
Expand Down
39 changes: 33 additions & 6 deletions plugin/core/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1548,13 +1548,34 @@ def _template_variables(self) -> Dict[str, str]:
variables.update(extra_vars)
return variables

def execute_command(self, command: ExecuteCommandParams, progress: bool) -> Promise:
def execute_command(
self, command: ExecuteCommandParams, progress: bool, view: Optional[sublime.View] = None
) -> Promise:
"""Run a command from any thread. Your .then() continuations will run in Sublime's worker thread."""
if self._plugin:
task = Promise.packaged_task() # type: PackagedTask[None]
promise, resolve = task
if self._plugin.on_pre_server_command(command, lambda: resolve(None)):
return promise
command_name = command['command']
# Handle VSCode-specific command for triggering AC/sighelp
if command_name == "editor.action.triggerSuggest" and view:
# Triggered from set_timeout as suggestions popup doesn't trigger otherwise.
sublime.set_timeout(lambda: view.run_command("auto_complete"))
return Promise.resolve(None)
if command_name == "editor.action.triggerParameterHints" and view:

def run_async() -> None:
session_view = self.session_view_for_view_async(view)
if not session_view:
return
listener = session_view.listener()
if not listener:
return
listener.do_signature_help_async(manual=False)

sublime.set_timeout_async(run_async)
return Promise.resolve(None)
# TODO: Our Promise class should be able to handle errors/exceptions
return Promise(
lambda resolve: self.send_request(
Expand All @@ -1564,7 +1585,9 @@ def execute_command(self, command: ExecuteCommandParams, progress: bool) -> Prom
)
)

def run_code_action_async(self, code_action: Union[Command, CodeAction], progress: bool) -> Promise:
def run_code_action_async(
self, code_action: Union[Command, CodeAction], progress: bool, view: Optional[sublime.View] = None
) -> Promise:
command = code_action.get("command")
if isinstance(command, str):
code_action = cast(Command, code_action)
Expand All @@ -1573,12 +1596,13 @@ def run_code_action_async(self, code_action: Union[Command, CodeAction], progres
arguments = code_action.get('arguments', None)
if isinstance(arguments, list):
command_params['arguments'] = arguments
return self.execute_command(command_params, progress)
return self.execute_command(command_params, progress, view)
# At this point it cannot be a command anymore, it has to be a proper code action.
# A code action can have an edit and/or command. Note that it can have *both*. In case both are present, we
# must apply the edits before running the command.
code_action = cast(CodeAction, code_action)
return self._maybe_resolve_code_action(code_action).then(self._apply_code_action_async)
return self._maybe_resolve_code_action(code_action) \
.then(lambda code_action: self._apply_code_action_async(code_action, view))

def open_uri_async(
self,
Expand Down Expand Up @@ -1670,7 +1694,9 @@ def _maybe_resolve_code_action(self, code_action: CodeAction) -> Promise[Union[C
return self.send_request_task(request)
return Promise.resolve(code_action)

def _apply_code_action_async(self, code_action: Union[CodeAction, Error, None]) -> Promise[None]:
def _apply_code_action_async(
self, code_action: Union[CodeAction, Error, None], view: Optional[sublime.View]
) -> Promise[None]:
if not code_action:
return Promise.resolve(None)
if isinstance(code_action, Error):
Expand All @@ -1687,7 +1713,8 @@ def _apply_code_action_async(self, code_action: Union[CodeAction, Error, None])
arguments = command.get("arguments")
if arguments is not None:
execute_command['arguments'] = arguments
return promise.then(lambda _: self.execute_command(execute_command, False))
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]:
Expand Down
8 changes: 5 additions & 3 deletions plugin/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,10 @@ def position(view: sublime.View, offset: int) -> Position:
return offset_to_point(view, offset).to_lsp()


def position_to_offset(position: Position, view: sublime.View) -> int:
return point_to_offset(Point.from_lsp(position), view)


def get_symbol_kind_from_scope(scope_name: str) -> SublimeKind:
best_kind = sublime.KIND_AMBIGUOUS
best_kind_score = 0
Expand All @@ -363,9 +367,7 @@ def get_symbol_kind_from_scope(scope_name: str) -> SublimeKind:


def range_to_region(range: Range, view: sublime.View) -> sublime.Region:
start = Point.from_lsp(range['start'])
end = Point.from_lsp(range['end'])
return sublime.Region(point_to_offset(start, view), point_to_offset(end, view))
return sublime.Region(position_to_offset(range['start'], view), position_to_offset(range['end'], view))


def region_to_range(view: sublime.View, region: sublime.Region) -> Range:
Expand Down
2 changes: 1 addition & 1 deletion plugin/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -693,7 +693,7 @@ def handle_code_action_select(self, config_name: str, actions: List[CodeActionOr
def run_async() -> None:
session = self.session_by_name(config_name)
if session:
session.run_code_action_async(actions[index], progress=True)
session.run_code_action_async(actions[index], progress=True, view=self.view)

sublime.set_timeout_async(run_async)

Expand Down
21 changes: 6 additions & 15 deletions plugin/execute_command.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from .core.protocol import Error
from .core.protocol import ExecuteCommandParams
from .core.registry import LspTextCommand
from .core.registry import windows
from .core.typing import List, Optional, Any
from .core.views import first_selection_region
from .core.views import uri_from_view, offset_to_point, region_to_range, text_document_identifier, text_document_position_params # noqa: E501
from .core.views import offset_to_point
from .core.views import region_to_range
from .core.views import text_document_identifier
from .core.views import text_document_position_params
from .core.views import uri_from_view
import sublime


Expand All @@ -19,18 +22,6 @@ def run(self,
command_args: Optional[List[Any]] = None,
session_name: Optional[str] = None,
event: Optional[dict] = None) -> None:
# Handle VSCode-specific command for triggering AC/sighelp
if command_name == "editor.action.triggerSuggest":
# Triggered from set_timeout as suggestions popup doesn't trigger otherwise.
return sublime.set_timeout(lambda: self.view.run_command("auto_complete"))
if command_name == "editor.action.triggerParameterHints":

def run_async() -> None:
listener = windows.listener_for_view(self.view)
if listener:
listener.do_signature_help_async(manual=False)

return sublime.set_timeout_async(run_async)
session = self.session_by_name(session_name if session_name else self.session_name)
if session and command_name:
params = {"command": command_name} # type: ExecuteCommandParams
Expand All @@ -44,7 +35,7 @@ def handle_response(response: Any) -> None:
return
self.handle_success_async(response, command_name)

session.execute_command(params, progress=True).then(handle_response)
session.execute_command(params, progress=True, view=self.view).then(handle_response)

def handle_success_async(self, result: Any, command_name: str) -> None:
"""
Expand Down
2 changes: 1 addition & 1 deletion plugin/hover.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,6 @@ def handle_code_action_select(self, config_name: str, actions: List[CodeActionOr
def run_async() -> None:
session = self.session_by_name(config_name)
if session:
session.run_code_action_async(actions[index], progress=True)
session.run_code_action_async(actions[index], progress=True, view=self.view)

sublime.set_timeout_async(run_async)
5 changes: 2 additions & 3 deletions plugin/inlay_hint.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@
from .core.protocol import InlayHint
from .core.protocol import InlayHintLabelPart
from .core.protocol import MarkupContent
from .core.protocol import Point
from .core.protocol import Request
from .core.registry import LspTextCommand
from .core.registry import LspWindowCommand
from .core.sessions import Session
from .core.settings import userprefs
from .core.typing import cast, Optional, Union
from .core.views import point_to_offset
from .core.views import position_to_offset
from .formatting import apply_text_edits_to_view
import html
import sublime
Expand Down Expand Up @@ -88,7 +87,7 @@ def handle_label_part_command(self, session_name: str, label_part: Optional[Inla

def inlay_hint_to_phantom(view: sublime.View, inlay_hint: InlayHint, session: Session) -> sublime.Phantom:
position = inlay_hint["position"]
region = sublime.Region(point_to_offset(Point.from_lsp(position), view))
region = sublime.Region(position_to_offset(position, view))
phantom_uuid = str(uuid.uuid4())
content = get_inlay_hint_html(view, inlay_hint, session, phantom_uuid)
p = sublime.Phantom(region, content, sublime.LAYOUT_INLINE)
Expand Down
2 changes: 1 addition & 1 deletion tests/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def await_promise(cls, promise: Union[YieldPromise, Promise]) -> Generator:
def await_run_code_action(self, code_action: Dict[str, Any]) -> Generator:
promise = YieldPromise()
sublime.set_timeout_async(
lambda: self.session.run_code_action_async(code_action, progress=False).then(
lambda: self.session.run_code_action_async(code_action, progress=False, view=self.view).then(
promise.fulfill))
yield from self.await_promise(promise)

Expand Down
3 changes: 2 additions & 1 deletion tests/test_single_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,8 @@ def test_run_command(self) -> 'Generator':
sublime.set_timeout_async(
lambda: self.session.execute_command(
{"command": "foo", "arguments": ["hello", "there", "general", "kenobi"]},
progress=False
progress=False,
view=self.view,
).then(promise.fulfill)
)
yield from self.await_promise(promise)
Expand Down

0 comments on commit 757908a

Please sign in to comment.