Skip to content

Commit

Permalink
Ensure more requests are ran from the async thread:
Browse files Browse the repository at this point in the history
- code actions
- formatting
- rename
- expand selection

I had to change quite a bit in the tests to make this work.
  • Loading branch information
rwols committed Oct 26, 2020
1 parent 448b05e commit 4d5bb06
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 64 deletions.
17 changes: 9 additions & 8 deletions plugin/code_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class CodeActionsManager:
def __init__(self) -> None:
self._response_cache = None # type: Optional[Tuple[str, CodeActionsCollector]]

def request_with_diagnostics(
def request_with_diagnostics_async(
self,
view: sublime.View,
request_range: Range,
Expand All @@ -89,9 +89,9 @@ def request_with_diagnostics(
Requests code actions *only* for provided diagnostics. If session has no diagnostics then
it will be skipped.
"""
return self._request(view, request_range, diagnostics_by_config, True, actions_handler)
return self._request_async(view, request_range, diagnostics_by_config, True, actions_handler)

def request_for_range(
def request_for_range_async(
self,
view: sublime.View,
request_range: Range,
Expand All @@ -102,7 +102,7 @@ def request_for_range(
Requests code actions with provided diagnostics and specified range. If there are
no diagnostics for given session, the request will be made with empty diagnostics list.
"""
return self._request(view, request_range, diagnostics_by_config, False, actions_handler)
return self._request_async(view, request_range, diagnostics_by_config, False, actions_handler)

def request_on_save(
self,
Expand All @@ -114,9 +114,9 @@ def request_on_save(
Requests code actions on save.
"""
request_range = entire_content_range(view)
return self._request(view, request_range, dict(), False, actions_handler, on_save_actions)
return self._request_async(view, request_range, dict(), False, actions_handler, on_save_actions)

def _request(
def _request_async(
self,
view: sublime.View,
request_range: Range,
Expand Down Expand Up @@ -258,7 +258,7 @@ def _handle_response_async(self, responses: CodeActionsByConfigName) -> None:
for session in sessions_for_view(self._view, 'codeActionProvider'):
if session.config.name == config_name:
for code_action in code_actions:
tasks.append(session.run_code_action(code_action))
tasks.append(session.run_code_action_async(code_action))
break
if document_version != self._view.change_count():
# Give on_text_changed_async a chance to trigger.
Expand All @@ -284,7 +284,8 @@ def run(self, edit: sublime.Edit, event: Optional[dict] = None) -> None:
return
selection_range = region_to_range(view, region)
diagnostics_by_config, extended_range = filter_by_range(view_diagnostics(view), selection_range)
actions_manager.request_for_range(view, extended_range, diagnostics_by_config, self.handle_responses_async)
actions_manager.request_for_range_async(
view, extended_range, diagnostics_by_config, self.handle_responses_async)

def combine_commands(self) -> 'List[Tuple[str, str, CodeActionOrCommand]]':
results = []
Expand Down
2 changes: 1 addition & 1 deletion plugin/core/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -883,7 +883,7 @@ def run_command(self, command: Mapping[str, Any]) -> Promise:
)
)

def run_code_action(self, code_action: Mapping[str, Any]) -> Promise:
def run_code_action_async(self, code_action: Mapping[str, Any]) -> Promise:
command = code_action.get("command")
if isinstance(command, str):
# This is actually a command.
Expand Down
2 changes: 1 addition & 1 deletion plugin/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ def _on_sighelp_navigate(self, href: str) -> None:
def _do_code_actions(self) -> None:
stored_range = region_to_range(self.view, self._stored_region)
diagnostics_by_config, extended_range = filter_by_range(view_diagnostics(self.view), stored_range)
actions_manager.request_for_range(self.view, extended_range, diagnostics_by_config, self._on_code_actions)
actions_manager.request_for_range_async(self.view, extended_range, diagnostics_by_config, self._on_code_actions)

def _on_code_actions(self, responses: CodeActionsByConfigName) -> None:
action_count = sum(map(len, responses.values()))
Expand Down
28 changes: 21 additions & 7 deletions plugin/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,24 @@ def run(self, edit: sublime.Edit, event: Optional[dict] = None) -> None:
session = self.best_session(self.capability)
if session:
# Either use the documentFormattingProvider ...
session.send_request(text_document_formatting(self.view), self.on_result)
req = text_document_formatting(self.view)

def run_async() -> None:
assert session # TODO: How to make mypy shut up about an Optional[Session]?
session.send_request(req, self.on_result)

sublime.set_timeout_async(run_async)
else:
session = self.best_session(LspFormatDocumentRangeCommand.capability)
if session:
# ... or use the documentRangeFormattingProvider and format the entire range.
req = text_document_range_formatting(self.view, entire_content_region(self.view))
session.send_request(req, self.on_result)

def run_async() -> None:
assert session # TODO: How to make mypy shut up about an Optional[Session]?
session.send_request(req, self.on_result)

sublime.set_timeout_async(run_async)

def on_result(self, params: Any) -> None:
apply_response_to_view(params, self.view)
Expand All @@ -122,8 +133,11 @@ def is_enabled(self, event: Optional[dict] = None) -> bool:
return False

def run(self, edit: sublime.Edit, event: Optional[dict] = None) -> None:
session = self.best_session(self.capability)
if session:
session.send_request(
text_document_range_formatting(self.view, self.view.sel()[0]),
lambda response: apply_response_to_view(response, self.view))
req = text_document_range_formatting(self.view, self.view.sel()[0])

def run_async() -> None:
session = self.best_session(self.capability)
if session:
session.send_request(req, lambda response: apply_response_to_view(response, self.view))

sublime.set_timeout_async(run_async)
27 changes: 18 additions & 9 deletions plugin/hover.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,17 @@ def run(self, edit: sublime.Edit, point: Optional[int] = None, event: Optional[d
# TODO: For code actions it makes more sense to use the whole selection under mouse (if available)
# rather than just the hover point.
request_point = offset_to_point(self.view, hover_point)
self._diagnostics_by_config, code_actions_range = filter_by_point(view_diagnostics(self.view), request_point)
if self._diagnostics_by_config:
actions_manager.request_with_diagnostics(
self.view, code_actions_range, self._diagnostics_by_config,
lambda response: self.handle_code_actions(response, hover_point))
self.show_hover(hover_point)

def run_async() -> None:
self._diagnostics_by_config, code_actions_range = filter_by_point(
view_diagnostics(self.view), request_point)
if self._diagnostics_by_config:
actions_manager.request_with_diagnostics_async(
self.view, code_actions_range, self._diagnostics_by_config,
lambda response: self.handle_code_actions(response, hover_point))
self.show_hover(hover_point)

sublime.set_timeout_async(run_async)

def request_symbol_hover(self, point: int) -> None:
session = self.best_session('hoverProvider', point)
Expand Down Expand Up @@ -197,9 +202,13 @@ def _on_navigate(self, href: str, point: int) -> None:

def handle_code_action_select(self, config_name: str, index: int) -> None:
if index > -1:
session = self.session_by_name(config_name)
if session:
session.run_code_action(self._actions_by_config[config_name][index])

def run_async() -> None:
session = self.session_by_name(config_name)
if session:
session.run_code_action_async(self._actions_by_config[config_name][index])

sublime.set_timeout_async(run_async)


class LspRunCommandFromPointCommand(sublime_plugin.TextCommand):
Expand Down
21 changes: 16 additions & 5 deletions plugin/rename.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,13 @@ def run(
if session:
params = text_document_position_params(self.view, get_position(self.view, event))
request = Request.prepareRename(params, self.view)
session.send_request(request, self.on_prepare_result, self.on_prepare_error)
self.event = event

def run_async() -> None:
assert session # TODO: How to make mypy shut up about an Optional[Session]?
session.send_request(request, self.on_prepare_result, self.on_prepare_error)

sublime.set_timeout_async(run_async)
else:
# trigger InputHandler manually
raise TypeError("required positional argument")
Expand All @@ -90,10 +95,16 @@ def _do_rename(self, position: int, new_name: str) -> None:
if session:
params = text_document_position_params(self.view, position)
params["newName"] = new_name
session.send_request(
Request.rename(params, self.view),
# This has to run on the main thread due to calling apply_workspace_edit
lambda r: sublime.set_timeout(lambda: self.on_rename_result(r)))

def run_async() -> None:
assert session # TODO: How to make mypy shut up about an Optional[Session]?
session.send_request(
Request.rename(params, self.view),
# This has to run on the main thread due to calling apply_workspace_edit
lambda r: sublime.set_timeout(lambda: self.on_rename_result(r))
)

sublime.set_timeout_async(run_async)

def on_rename_result(self, response: Any) -> None:
window = self.view.window()
Expand Down
7 changes: 6 additions & 1 deletion plugin/selection_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ def run(self, edit: sublime.Edit, event: Optional[dict] = None) -> None:
params = selection_range_params(self.view)
self._regions.extend(self.view.sel())
self._change_count = self.view.change_count()
session.send_request(Request(self.method, params), self.on_result, self.on_error)

def run_async() -> None:
assert session # TODO: How to make mypy shut up about an Optional[Session]?
session.send_request(Request(self.method, params), self.on_result, self.on_error)

sublime.set_timeout_async(run_async)
else:
self._run_builtin_expand_selection("No {} found".format(self.capability))

Expand Down
4 changes: 2 additions & 2 deletions stubs/sublime.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,11 @@ def save_settings(base_name: str) -> None:
...


def set_timeout(f: Callable, timeout_ms: int = ...) -> None:
def set_timeout(f: Callable[[], None], timeout_ms: int = ...) -> None:
...


def set_timeout_async(f: Callable, timeout_ms: int = ...) -> None:
def set_timeout_async(f: Callable[[], None], timeout_ms: int = ...) -> None:
...


Expand Down
31 changes: 23 additions & 8 deletions tests/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from LSP.plugin.core.sessions import Session
from LSP.plugin.core.settings import client_configs
from LSP.plugin.core.types import ClientConfig, LanguageConfig, ClientStates
from LSP.plugin.core.typing import Any, Generator, List, Optional, Tuple, Union
from LSP.plugin.core.typing import Any, Generator, List, Optional, Tuple, Union, Dict
from LSP.plugin.documents import DocumentSyncListener
from os import environ
from os.path import join
Expand Down Expand Up @@ -175,7 +175,9 @@ def handler(params: Any) -> None:
def error_handler(params: 'Any') -> None:
debug("Got error:", params, "awaiting timeout :(")

self.session.send_request(Request("$test/getReceived", {"method": method}), handler, error_handler)
sublime.set_timeout_async(
lambda: self.session.send_request(Request("$test/getReceived", {"method": method}), handler, error_handler)
)
yield from self.await_promise(promise)
return promise.result()

Expand All @@ -189,7 +191,7 @@ def on_error(params: Any) -> None:
promise.fulfill(params)

req = Request("$test/fakeRequest", {"method": method, "params": params})
self.session.send_request(req, on_result, on_error)
sublime.set_timeout_async(lambda: self.session.send_request(req, on_result, on_error))
return promise

def await_promise(self, promise: Union[YieldPromise, Promise]) -> Generator:
Expand All @@ -200,11 +202,19 @@ def await_promise(self, promise: Union[YieldPromise, Promise]) -> Generator:
promise.then(lambda result: yielder.fulfill(result))
yield {"condition": yielder, "timeout": TIMEOUT_TIME}

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).then(promise.fulfill))
yield from self.await_promise(promise)

def set_response(self, method: str, response: 'Any') -> None:
self.assertIsNotNone(self.session)
assert self.session # mypy
self.session.send_notification(
Notification("$test/setResponse", {"method": method, "response": response}))
sublime.set_timeout_async(
lambda: self.session.send_notification(
Notification("$test/setResponse", {"method": method, "response": response})
)
)

def set_responses(self, responses: List[Tuple[str, Any]]) -> Generator:
self.assertIsNotNone(self.session)
Expand All @@ -218,7 +228,9 @@ def error_handler(params: Any) -> None:
debug("Got error:", params, "awaiting timeout :(")

payload = [{"method": method, "response": responses} for method, responses in responses]
self.session.send_request(Request("$test/setResponses", payload), handler, error_handler)
sublime.set_timeout_async(
lambda: self.session.send_request(Request("$test/setResponses", payload), handler, error_handler)
)
yield from self.await_promise(promise)

def await_client_notification(self, method: str, params: Any = None) -> 'Generator':
Expand All @@ -232,8 +244,11 @@ def handler(params: Any) -> None:
def error_handler(params: Any) -> None:
debug("Got error:", params, "awaiting timeout :(")

self.session.send_request(
Request("$test/sendNotification", {"method": method, "params": params}), handler, error_handler)
sublime.set_timeout_async(
lambda: self.session.send_request(
Request("$test/sendNotification", {"method": method, "params": params}), handler, error_handler
)
)
yield from self.await_promise(promise)

def await_boilerplate_begin(self) -> 'Generator':
Expand Down
Loading

0 comments on commit 4d5bb06

Please sign in to comment.