Skip to content

Commit

Permalink
Include diagnostics when triggering codeAction request on save (subli…
Browse files Browse the repository at this point in the history
  • Loading branch information
rchl authored Nov 14, 2021
1 parent 3fc2f21 commit 92cc7f6
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 14 deletions.
22 changes: 14 additions & 8 deletions plugin/code_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,12 @@ def request_on_save(
"""
Requests code actions on save.
"""
self._request_async(view, entire_content_region(view), [], False, actions_handler, on_save_actions)
listener = windows.listener_for_view(view)
if not listener:
return
region = entire_content_region(view)
session_buffer_diagnostics, _ = listener.diagnostics_intersecting_region_async(region)
self._request_async(view, region, session_buffer_diagnostics, False, actions_handler, on_save_actions)

def _request_async(
self,
Expand All @@ -108,6 +113,7 @@ def _request_async(
actions_handler: Callable[[CodeActionsByConfigName], None],
on_save_actions: Optional[Dict[str, bool]] = None
) -> None:
location_cache_key = None
use_cache = on_save_actions is None
if use_cache:
location_cache_key = "{}#{}:{}:{}".format(
Expand All @@ -125,26 +131,26 @@ def _request_async(
listener = windows.listener_for_view(view)
if listener:
for session in listener.sessions_async('codeActionProvider'):
diagnostics = [] # type: Sequence[Diagnostic]
for sb, diags in session_buffer_diagnostics:
if sb.session == session:
diagnostics = diags
break
if on_save_actions:
supported_kinds = session.get_capability('codeActionProvider.codeActionKinds')
matching_kinds = get_matching_kinds(on_save_actions, supported_kinds or [])
if matching_kinds:
params = text_document_code_action_params(view, region, [], matching_kinds)
params = text_document_code_action_params(view, region, diagnostics, matching_kinds)
request = Request.codeAction(params, view)
session.send_request_async(
request, *filtering_collector(session.config.name, matching_kinds, collector))
else:
diagnostics = [] # type: Sequence[Diagnostic]
for sb, diags in session_buffer_diagnostics:
if sb.session == session:
diagnostics = diags
break
if only_with_diagnostics and not diagnostics:
continue
params = text_document_code_action_params(view, region, diagnostics)
request = Request.codeAction(params, view)
session.send_request_async(request, collector.create_collector(session.config.name))
if use_cache:
if location_cache_key:
self._response_cache = (location_cache_key, collector)


Expand Down
6 changes: 4 additions & 2 deletions plugin/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,12 +250,14 @@ def diagnostics_intersecting_region_async(
self,
region: sublime.Region
) -> Tuple[List[Tuple[SessionBuffer, List[Diagnostic]]], sublime.Region]:
covering = sublime.Region(region.a, region.b)
covering = sublime.Region(region.begin(), region.end())
result = [] # type: List[Tuple[SessionBuffer, List[Diagnostic]]]
for sb, diagnostics in self.diagnostics_async():
intersections = [] # type: List[Diagnostic]
for diagnostic, candidate in diagnostics:
if region.intersects(candidate):
# Checking against points is inclusive unlike checking whether region intersects another
# region which is exclusive (at region end) and we want an inclusive behavior in this case.
if region.contains(candidate.a) or region.contains(candidate.b):
covering = covering.cover(candidate)
intersections.append(diagnostic)
if intersections:
Expand Down
23 changes: 19 additions & 4 deletions tests/test_code_actions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from copy import deepcopy
from LSP.plugin.code_actions import CodeActionsByConfigName
from LSP.plugin.code_actions import get_matching_kinds
from LSP.plugin.core.protocol import Point, Range
from LSP.plugin.core.typing import Any, Dict, Generator, List, Tuple, Optional
Expand Down Expand Up @@ -106,6 +105,24 @@ def test_applies_matching_kind(self) -> Generator:
self.assertEquals(entire_content(self.view), 'const x = 1;')
self.assertEquals(self.view.is_dirty(), False)

def test_requests_with_diagnostics(self) -> Generator:
yield from self._setup_document_with_missing_semicolon()
code_action_kind = 'source.fixAll'
code_action = create_test_code_action(
self.view,
self.view.change_count(),
[(';', Range(Point(0, 11), Point(0, 11)))],
code_action_kind
)
self.set_response('textDocument/codeAction', [code_action])
self.view.run_command('lsp_save')
code_action_request = yield from self.await_message('textDocument/codeAction')
self.assertEquals(len(code_action_request['context']['diagnostics']), 1)
self.assertEquals(code_action_request['context']['diagnostics'][0]['message'], 'Missing semicolon')
yield from self.await_message('textDocument/didSave')
self.assertEquals(entire_content(self.view), 'const x = 1;')
self.assertEquals(self.view.is_dirty(), False)

def test_applies_in_two_iterations(self) -> Generator:
self.insert_characters('const x = 1')
initial_change_count = self.view.change_count()
Expand Down Expand Up @@ -222,7 +239,7 @@ def setUp(self) -> Generator:
self.original_debounce_time = DocumentSyncListener.code_actions_debounce_time
DocumentSyncListener.code_actions_debounce_time = 0

def tearDown(self) -> Generator:
def tearDown(self) -> None:
DocumentSyncListener.code_actions_debounce_time = self.original_debounce_time
super().tearDown()

Expand Down Expand Up @@ -282,8 +299,6 @@ def test_requests_with_no_diagnostics(self) -> Generator:
self.assertEquals(annotations_range[0].b, 0)

def test_extends_range_to_include_diagnostics(self) -> Generator:
def handle_response(actions_by_config: CodeActionsByConfigName) -> None:
pass
self.insert_characters('x diagnostic')
yield from self.await_message("textDocument/didChange")
yield from self.await_client_notification(
Expand Down

0 comments on commit 92cc7f6

Please sign in to comment.