Skip to content

Commit

Permalink
Merge branch 'main' into feat/diag-annotations
Browse files Browse the repository at this point in the history
* main:
  Fix issues with diagnostics panel toggling on save (#2063)
  Request color presentations when clicking on a color box (#2065)
  Import new imports like NotRequired from "typing"
  Ensure "Source Actions..." request includes the "source" kind (#2064)
  refactor(types): Mark properties with NotRequired (#2062)
  Add template variable `$text_document_position` in execute command (#2061)
  Only update content of diagnostics panel when visible (#2054)
  Remove Range class and rename RangeLsp to Range (#2058)
  • Loading branch information
rchl committed Sep 26, 2022
2 parents 4a2c753 + de2cd85 commit 2cd57c7
Show file tree
Hide file tree
Showing 48 changed files with 6,533 additions and 1,054 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ jobs:
python-version: '3.8'
- run: sudo apt update
- run: sudo apt install --no-install-recommends -y x11-xserver-utils
- run: pip3 install mypy==0.910 flake8==3.9.2 yapf==0.31.0 --user
- run: pip3 install mypy==0.971 flake8==5.0.4 pyright==1.1.271 yapf==0.31.0 --user
- run: echo "$HOME/.local/bin" >> $GITHUB_PATH
- run: mypy -p plugin
# - run: mypy -p plugin
- run: flake8 plugin tests
- run: pyright plugin
24 changes: 24 additions & 0 deletions Default.sublime-keymap
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,30 @@
// "shift+f8"
// ],
// },
// Jump to next Diagnostic in Tab
// {
// "command": "lsp_next_diagnostic",
// "keys": [
// "UNBOUND"
// ],
// "context": [
// {
// "key": "setting.lsp_active"
// }
// ]
// },
// Jump to previous Diagnostic in Tab
// {
// "command": "lsp_prev_diagnostic",
// "keys": [
// "UNBOUND"
// ],
// "context": [
// {
// "key": "setting.lsp_active"
// }
// ]
// },
// Rename
// {
// "command": "lsp_symbol_rename",
Expand Down
10 changes: 6 additions & 4 deletions boot.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# Please keep this list sorted (Edit -> Sort Lines)
from .plugin.code_actions import LspCodeActionsCommand
from .plugin.code_lens import LspCodeLensCommand
from .plugin.color import LspColorPresentationCommand
from .plugin.completion import LspCommitCompletionWithOppositeInsertMode
from .plugin.completion import LspResolveDocsCommand
from .plugin.completion import LspSelectCompletionItemCommand
Expand All @@ -22,10 +23,13 @@
from .plugin.core.panels import LspToggleLogPanelLinesLimitCommand
from .plugin.core.panels import LspUpdatePanelCommand
from .plugin.core.panels import LspUpdateLogPanelCommand
from .plugin.core.panels import PanelName
from .plugin.core.panels import WindowPanelListener
from .plugin.core.protocol import Error
from .plugin.core.protocol import Location
from .plugin.core.protocol import LocationLink
from .plugin.core.registry import LspNextDiagnosticCommand
from .plugin.core.registry import LspPrevDiagnosticCommand
from .plugin.core.registry import LspRecheckSessionsCommand
from .plugin.core.registry import LspRestartServerCommand
from .plugin.core.registry import windows
Expand Down Expand Up @@ -176,10 +180,8 @@ def on_pre_close(self, view: sublime.View) -> None:
break

def on_post_window_command(self, window: sublime.Window, command_name: str, args: Optional[Dict[str, Any]]) -> None:
if command_name in ("next_result", "prev_result"):
view = window.active_view()
if view:
view.run_command("lsp_hover", {"only_diagnostics": True})
if command_name == "show_panel" and args and args.get("panel") == "output.{}".format(PanelName.Diagnostics):
sublime.set_timeout_async(windows.lookup(window).update_diagnostics_panel_async)


class LspOpenLocationCommand(sublime_plugin.TextCommand):
Expand Down
1 change: 1 addition & 0 deletions docs/src/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@ You can include special variables in the `command_args` array that will be autom
| `"$selection_end"` or `"${selection_end}"` | int | Character offset of the end of the (topmost) selection |
| `"$position"` or `"${position}"` | object | JSON object `{ 'line': int, 'character': int }` of the (topmost) cursor position, see [Position](https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position) |
| `"$range"` or `"${range}"` | object | JSON object with `'start'` and `'end'` positions of the (topmost) selection, see [Range](https://microsoft.github.io/language-server-protocol/specifications/specification-current/#range) |
| `"$text_document_position"` or `"${text_document_position}"` | object | JSON object with `'textDocument'` and `'position'` of the (topmost) selection, see [TextDocumentPositionParams](https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams) |
7 changes: 2 additions & 5 deletions docs/src/keyboard_shortcuts.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ Refer to the [Customization section](customization.md#keyboard-shortcuts-key-bin
!!! Mac
If you using macOS, replace <kbd>ctrl</kbd> with <kbd>command</kbd>.

!!! Note
If <kbd>F4</kbd> / <kbd>shift</kbd> <kbd>F4</kbd> stop working, double-click a diagnostic in the Diagnostics Panel. It should work again afterwards.

| Feature | Shortcut | Command |
| ------- | -------- | ------- |
| Auto Complete | <kbd>ctrl</kbd> <kbd>space</kbd> (also on macOS) | `auto_complete`
Expand All @@ -23,8 +20,8 @@ Refer to the [Customization section](customization.md#keyboard-shortcuts-key-bin
| Goto Type Definition | unbound | `lsp_symbol_type_definition`
| Hover Popup | unbound | `lsp_hover`
| Insert/Replace Completions | <kbd>alt</kbd> <kbd>enter</kbd> | `lsp_commit_completion_with_opposite_insert_mode`
| Next Diagnostic | <kbd>F4</kbd> | -
| Previous Diagnostic | <kbd>shift</kbd> <kbd>F4</kbd> | -
| Next Diagnostic | unbound | `lsp_next_diagnostic`
| Previous Diagnostic | unbound | `lsp_prev_diagnostic`
| Rename | unbound | `lsp_symbol_rename`
| Restart Server | unbound | `lsp_restart_server`
| Run Code Action | unbound | `lsp_code_actions`
Expand Down
2 changes: 1 addition & 1 deletion plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from .core.protocol import Notification
from .core.protocol import Request
from .core.protocol import Response
from .core.protocol import WorkspaceFolder
from .core.registry import LspTextCommand
from .core.registry import LspWindowCommand
from .core.sessions import AbstractPlugin
Expand All @@ -23,6 +22,7 @@
from .core.url import uri_to_filename # deprecated
from .core.version import __version__
from .core.views import MarkdownLangMap
from .core.workspace import WorkspaceFolder

# This is the public API for LSP-* packages
__all__ = [
Expand Down
81 changes: 49 additions & 32 deletions plugin/code_actions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .core.promise import Promise
from .core.protocol import CodeAction
from .core.protocol import CodeActionKind
from .core.protocol import Command
from .core.protocol import Diagnostic
from .core.protocol import Error
Expand All @@ -8,7 +9,7 @@
from .core.registry import windows
from .core.sessions import SessionBufferProtocol
from .core.settings import userprefs
from .core.typing import Any, List, Dict, Callable, Optional, Tuple, Union
from .core.typing import Any, List, Dict, Callable, Optional, Tuple, Union, cast
from .core.views import entire_content_region
from .core.views import first_selection_region
from .core.views import format_code_actions_for_quick_panel
Expand Down Expand Up @@ -84,14 +85,22 @@ def request_for_region_async(
region: sublime.Region,
session_buffer_diagnostics: List[Tuple[SessionBufferProtocol, List[Diagnostic]]],
actions_handler: Callable[[CodeActionsByConfigName], None],
only_kinds: Optional[Dict[str, bool]] = None,
only_kinds: Optional[List[CodeActionKind]] = None,
manual: bool = False,
) -> None:
"""
Requests code actions with provided diagnostics and specified region. If there are
no diagnostics for given session, the request will be made with empty diagnostics list.
"""
self._request_async(view, region, session_buffer_diagnostics, False, actions_handler, only_kinds, manual)
self._request_async(
view,
region,
session_buffer_diagnostics,
only_with_diagnostics=False,
actions_handler=actions_handler,
on_save_actions=None,
only_kinds=only_kinds,
manual=manual)

def request_on_save(
self,
Expand All @@ -108,7 +117,14 @@ def request_on_save(
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, manual=False)
view,
region,
session_buffer_diagnostics,
only_with_diagnostics=False,
actions_handler=actions_handler,
on_save_actions=on_save_actions,
only_kinds=None,
manual=False)

def _request_async(
self,
Expand All @@ -118,8 +134,12 @@ def _request_async(
only_with_diagnostics: bool,
actions_handler: Callable[[CodeActionsByConfigName], None],
on_save_actions: Optional[Dict[str, bool]] = None,
only_kinds: Optional[List[CodeActionKind]] = None,
manual: bool = False,
) -> None:
listener = windows.listener_for_view(view)
if not listener:
return
location_cache_key = None
use_cache = on_save_actions is None and not manual
if use_cache:
Expand All @@ -134,35 +154,33 @@ def _request_async(
self._response_cache = None
collector = CodeActionsCollector(actions_handler)
with collector:
listener = windows.listener_for_view(view)
if listener:
for session in listener.sessions_async('codeActionProvider'):
diagnostics = [] # type: List[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, diagnostics, matching_kinds, manual)
request = Request.codeAction(params, view)
session.send_request_async(
request, *filtering_collector(session.config.name, matching_kinds, collector))
else:
if only_with_diagnostics and not diagnostics:
continue
params = text_document_code_action_params(view, region, diagnostics, None, manual)
for session in listener.sessions_async('codeActionProvider'):
diagnostics = [] # type: List[Diagnostic]
for sb, diags in session_buffer_diagnostics:
if sb.session == session:
diagnostics = diags
break
if on_save_actions is not None:
supported_kinds = session.get_capability('codeActionProvider.codeActionKinds') # type: Optional[List[CodeActionKind]] # noqa: E501
matching_kinds = get_matching_kinds(on_save_actions, supported_kinds or [])
if matching_kinds:
params = text_document_code_action_params(view, region, diagnostics, matching_kinds, manual)
request = Request.codeAction(params, view)
session.send_request_async(request, collector.create_collector(session.config.name))
session.send_request_async(
request, *filtering_collector(session.config.name, matching_kinds, collector))
else:
if only_with_diagnostics and not diagnostics:
continue
params = text_document_code_action_params(view, region, diagnostics, only_kinds, manual)
request = Request.codeAction(params, view)
session.send_request_async(request, collector.create_collector(session.config.name))
if location_cache_key:
self._response_cache = (location_cache_key, collector)


def filtering_collector(
config_name: str,
kinds: List[str],
kinds: List[CodeActionKind],
actions_collector: CodeActionsCollector
) -> Tuple[Callable[[CodeActionsResponse], None], Callable[[Any], None]]:
"""
Expand All @@ -173,7 +191,7 @@ def filtering_collector(
"""

def actions_filter(actions: CodeActionsResponse) -> List[CodeActionOrCommand]:
return [a for a in (actions or []) if a.get('kind') in kinds] # type: ignore
return [a for a in (actions or []) if a.get('kind') in kinds]

collector = actions_collector.create_collector(config_name)
return (
Expand All @@ -185,7 +203,7 @@ def actions_filter(actions: CodeActionsResponse) -> List[CodeActionOrCommand]:
actions_manager = CodeActionsManager()


def get_matching_kinds(user_actions: Dict[str, bool], session_actions: List[str]) -> List[str]:
def get_matching_kinds(user_actions: Dict[str, bool], session_actions: List[CodeActionKind]) -> List[CodeActionKind]:
"""
Filters user-enabled or disabled actions so that only ones matching the session actions
are returned. Returned actions are those that are enabled and are not overridden by more
Expand All @@ -199,7 +217,7 @@ def get_matching_kinds(user_actions: Dict[str, bool], session_actions: List[str]
matching_kinds = []
for session_action in session_actions:
enabled = False
action_parts = session_action.split('.')
action_parts = cast(str, session_action).split('.')
for i in range(len(action_parts)):
current_part = '.'.join(action_parts[0:i + 1])
user_value = user_actions.get(current_part, None)
Expand Down Expand Up @@ -274,7 +292,7 @@ def run(
self,
edit: sublime.Edit,
event: Optional[dict] = None,
only_kinds: Optional[List[str]] = None,
only_kinds: Optional[List[CodeActionKind]] = None,
commands_by_config: Optional[CodeActionsByConfigName] = None
) -> None:
self.commands = [] # type: List[Tuple[str, CodeActionOrCommand]]
Expand All @@ -290,9 +308,8 @@ def run(
if not listener:
return
session_buffer_diagnostics, covering = listener.diagnostics_intersecting_async(region)
dict_kinds = {kind: True for kind in only_kinds} if only_kinds else None
actions_manager.request_for_region_async(
view, covering, session_buffer_diagnostics, self.handle_responses_async, dict_kinds, manual=True)
view, covering, session_buffer_diagnostics, self.handle_responses_async, only_kinds, manual=True)

def handle_responses_async(self, responses: CodeActionsByConfigName, run_first: bool = False) -> None:
self.commands_by_config = responses
Expand Down
30 changes: 17 additions & 13 deletions plugin/code_lens.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .core.protocol import CodeLens, Error, Range
from .core.typing import List, Tuple, Dict, Iterable, Generator, Union
from .core.protocol import CodeLens, CodeLensExtended, Error
from .core.typing import List, Tuple, Dict, Iterable, Generator, Union, cast
from .core.registry import LspTextCommand
from .core.registry import windows
from .core.views import make_command_link
Expand All @@ -20,7 +20,7 @@ class CodeLensData:

def __init__(self, data: CodeLens, view: sublime.View, session_name: str) -> None:
self.data = data
self.region = range_to_region(Range.from_lsp(data['range']), view)
self.region = range_to_region(data['range'], view)
self.session_name = session_name
self.annotation = '...'
self.resolve_annotation()
Expand All @@ -33,8 +33,8 @@ def is_resolved(self) -> bool:
"""A code lens is considered resolved if the inner data contains the 'command' key."""
return 'command' in self.data or self.is_resolve_error

def to_lsp(self) -> CodeLens:
copy = self.data.copy()
def to_lsp(self) -> CodeLensExtended:
copy = cast(CodeLensExtended, self.data.copy())
copy['session_name'] = self.session_name
return copy

Expand Down Expand Up @@ -63,7 +63,7 @@ def resolve(self, view: sublime.View, code_lens_or_error: Union[CodeLens, Error]
self.annotation = html_escape(str(code_lens_or_error))
return
self.data = code_lens_or_error
self.region = range_to_region(Range.from_lsp(code_lens_or_error['range']), view)
self.region = range_to_region(code_lens_or_error['range'], view)
self.resolve_annotation()


Expand Down Expand Up @@ -165,7 +165,7 @@ def render(self, mode: str) -> None:
for index, lens in enumerate(self._flat_iteration()):
self.view.add_regions(self._region_key(index), [lens.region], "", "", 0, [lens.small_html], accent)

def get_resolved_code_lenses_for_region(self, region: sublime.Region) -> Generator[CodeLens, None, None]:
def get_resolved_code_lenses_for_region(self, region: sublime.Region) -> Generator[CodeLensExtended, None, None]:
region = self.view.line(region)
for lens in self._flat_iteration():
if lens.is_resolved() and lens.region.intersects(region):
Expand All @@ -178,19 +178,21 @@ def run(self, edit: sublime.Edit) -> None:
listener = windows.listener_for_view(self.view)
if not listener:
return
code_lenses = [] # type: List[CodeLens]
code_lenses = [] # type: List[CodeLensExtended]
for region in self.view.sel():
for sv in listener.session_views_async():
code_lenses.extend(sv.get_resolved_code_lenses_for_region(region))
if not code_lenses:
return
elif len(code_lenses) == 1:
command = code_lenses[0]["command"]
command = code_lenses[0].get("command")
assert command
if not command:
return
args = {
"session_name": code_lenses[0]["session_name"],
"command_name": command["command"],
"command_args": command["arguments"]
"command_args": command.get("arguments")
}
self.view.run_command("lsp_execute", args)
else:
Expand All @@ -199,16 +201,18 @@ def run(self, edit: sublime.Edit) -> None:
lambda i: self.on_select(code_lenses, i)
)

def on_select(self, code_lenses: List[CodeLens], index: int) -> None:
def on_select(self, code_lenses: List[CodeLensExtended], index: int) -> None:
try:
code_lens = code_lenses[index]
except IndexError:
return
command = code_lens["command"]
command = code_lens.get("command")
assert command
if not command:
return
args = {
"session_name": code_lens["session_name"],
"command_name": command["command"],
"command_args": command["arguments"]
"command_args": command.get("arguments")
}
self.view.run_command("lsp_execute", args)
Loading

0 comments on commit 2cd57c7

Please sign in to comment.