Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

handle editor.action.rename command for interoperability with VSCode #2249

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions plugin/code_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,12 +253,15 @@ def _request_code_actions_async(self) -> None:
def _handle_response_async(self, responses: List[CodeActionsByConfigName]) -> None:
if self._cancelled:
return
document_version = self._task_runner.view.change_count()
view = self._task_runner.view
document_version = view.change_count()
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, source_view=view) for action in code_actions
])
Promise.all(tasks).then(lambda _: self._on_code_actions_completed(document_version))

def _on_code_actions_completed(self, previous_document_version: int) -> None:
Expand Down Expand Up @@ -345,7 +348,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, source_view=self.view) \
.then(lambda response: self._handle_response_async(config_name, response))

sublime.set_timeout_async(run_async)
Expand Down Expand Up @@ -409,7 +412,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, source_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 @@ -310,11 +309,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
49 changes: 43 additions & 6 deletions plugin/core/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
from .protocol import LSPObject
from .protocol import MarkupKind
from .protocol import Notification
from .protocol import Position
from .protocol import PrepareSupportDefaultBehavior
from .protocol import PreviousResultId
from .protocol import PublishDiagnosticsParams
Expand Down Expand Up @@ -95,6 +96,7 @@
from .views import get_storage_path
from .views import get_uri_and_range_from_location
from .views import MarkdownLangMap
from .views import position_to_offset
from .views import SEMANTIC_TOKENS_MAP
from .workspace import is_subpath_of
from .workspace import WorkspaceFolder
Expand Down Expand Up @@ -1545,13 +1547,42 @@ 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, source_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 source_view:
# Triggered from set_timeout as suggestions popup doesn't trigger otherwise.
sublime.set_timeout(lambda: source_view.run_command("auto_complete"))
return Promise.resolve(None)
if command_name == "editor.action.rename" and source_view:
command_args = command.get('arguments')
offset = None
if isinstance(command_args, list) and len(command_args) >= 2:
position = cast(Position, command_args[1])
offset = position_to_offset(position, source_view)
source_view.run_command("lsp_symbol_rename", {'position': offset})
return Promise.resolve(None)
if command_name == "editor.action.triggerParameterHints" and source_view:

def run_async() -> None:
session_view = self.session_view_for_view_async(source_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 @@ -1561,7 +1592,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, source_view: Optional[sublime.View] = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just call source_view -> view.

) -> Promise:
command = code_action.get("command")
if isinstance(command, str):
code_action = cast(Command, code_action)
Expand All @@ -1570,12 +1603,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, source_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, source_view))

def open_uri_async(
self,
Expand Down Expand Up @@ -1667,7 +1701,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], source_view: Optional[sublime.View]
) -> Promise[None]:
if not code_action:
return Promise.resolve(None)
if isinstance(code_action, Error):
Expand All @@ -1684,7 +1720,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, source_view=source_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 @@ -339,6 +339,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 @@ -351,9 +355,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, source_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, source_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 @@ -377,6 +377,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, source_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, source_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,
source_view=self.view,
).then(promise.fulfill)
)
yield from self.await_promise(promise)
Expand Down