Skip to content

Commit

Permalink
Filter out non-quickfix actions in view (#2081)
Browse files Browse the repository at this point in the history
  • Loading branch information
rchl authored Oct 11, 2022
1 parent ddf18e8 commit 18a533d
Show file tree
Hide file tree
Showing 13 changed files with 310 additions and 288 deletions.
5 changes: 5 additions & 0 deletions Context.sublime-menu
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
"command": "lsp_code_actions",
"caption": "Code Action…"
},
{
"command": "lsp_code_actions",
"args": {"only_kinds": ["refactor"]},
"caption": "Refactor…"
},
{
"command": "lsp_code_actions",
"args": {"only_kinds": ["source"]},
Expand Down
17 changes: 14 additions & 3 deletions Default.sublime-commands
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@
{
"caption": "LSP: Goto Diagnostic",
"command": "lsp_goto_diagnostic",
"args": {"uri": "$view_uri"}
"args": {
"uri": "$view_uri"
}
},
{
"caption": "LSP: Goto Diagnostic in Project",
Expand All @@ -107,11 +109,20 @@
"command": "lsp_symbol_rename"
},
{
"caption": "LSP: Run Code Action",
"caption": "LSP: Code Action",
"command": "lsp_code_actions"
},
{
"caption": "LSP: Run Source Action",
"caption": "LSP: Refactor…",
"command": "lsp_code_actions",
"args": {
"only_kinds": [
"refactor"
]
},
},
{
"caption": "LSP: Source Action…",
"command": "lsp_code_actions",
"args": {
"only_kinds": [
Expand Down
2 changes: 1 addition & 1 deletion docs/src/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ Code Actions are an umbrella term for "Quick Fixes" and "Refactorings". They are

Formatting is different from Code Actions, because Formatting is supposed to _not_ mutate the abstract syntax tree of the file, only move around white space. Any Code Action will mutate the abstract syntax tree.

This package presents Code Actions as a bluish clickable annotation positioned to the right of the viewport. Alternatively, they can be presented as a light bulb in the Gutter Area.
This package presents "Quick Fix" Code Actions as a bluish clickable annotation positioned to the right of the viewport. Alternatively, they can be presented as a light bulb in the Gutter Area.

Sublime Text has no concept of Code Actions.

Expand Down
1 change: 1 addition & 0 deletions docs/src/keyboard_shortcuts.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Refer to the [Customization section](customization.md#keyboard-shortcuts-key-bin
| Restart Server | unbound | `lsp_restart_server`
| Run Code Action | unbound | `lsp_code_actions`
| Run Code Lens | unbound | `lsp_code_lens`
| Run Refactor Action | unbound | `lsp_code_actions` (with args: `{"only_kinds": ["refactor"]}`)
| Run Source Action | unbound | `lsp_code_actions` (with args: `{"only_kinds": ["source"]}`)
| Signature Help | <kbd>ctrl</kbd> <kbd>alt</kbd> <kbd>space</kbd> | `lsp_signature_help_show`
| Toggle Diagnostics Panel | <kbd>ctrl</kbd> <kbd>alt</kbd> <kbd>m</kbd> | `lsp_show_diagnostics_panel`
Expand Down
408 changes: 188 additions & 220 deletions plugin/code_actions.py

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions plugin/core/protocol.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .typing import Enum, IntEnum, IntFlag
from .typing import Enum, IntEnum, IntFlag, StrEnum
from .typing import Any, Dict, Iterable, List, Literal, Mapping, NotRequired, Optional, TypedDict, Union
import sublime

Expand Down Expand Up @@ -327,7 +327,7 @@ class DocumentHighlightKind(IntEnum):
""" Write-access of a symbol, like writing to a variable. """


class CodeActionKind(Enum):
class CodeActionKind(StrEnum):
""" A set of predefined code action kinds """
Empty = ''
""" Empty kind. """
Expand Down
1 change: 1 addition & 0 deletions plugin/core/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ def get_initialize_params(variables: Dict[str, str], workspace_folders: List[Wor
CodeActionKind.RefactorExtract,
CodeActionKind.RefactorInline,
CodeActionKind.RefactorRewrite,
CodeActionKind.SourceFixAll,
CodeActionKind.SourceOrganizeImports,
]
}
Expand Down
9 changes: 8 additions & 1 deletion plugin/core/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

if sys.version_info >= (3, 11, 0):

from enum import Enum, IntEnum, IntFlag
from enum import Enum, IntEnum, IntFlag, StrEnum
from typing import Any
from typing import Callable
from typing import cast
Expand All @@ -25,6 +25,7 @@
from typing import Tuple
from typing import Type
from typing import TypedDict
from typing import TypeGuard
from typing import TypeVar
from typing import Union

Expand Down Expand Up @@ -57,6 +58,9 @@ class TypedDict(Type, dict): # type: ignore
def __init__(*args, **kwargs) -> None: # type: ignore
pass

class TypeGuard(Type): # type: ignore
pass

class Enum(Type): # type: ignore
pass

Expand All @@ -66,6 +70,9 @@ class IntEnum(Type): # type: ignore
class IntFlag(Type): # type: ignore
pass

class StrEnum(Type): # type: ignore
pass

class Any(Type): # type: ignore
pass

Expand Down
6 changes: 3 additions & 3 deletions plugin/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1049,15 +1049,15 @@ def format_completion(


def format_code_actions_for_quick_panel(
code_actions: List[Union[CodeAction, Command]]
session_actions: Iterable[Tuple[str, Union[CodeAction, Command]]]
) -> Tuple[List[sublime.QuickPanelItem], int]:
items = [] # type: List[sublime.QuickPanelItem]
selected_index = -1
for idx, code_action in enumerate(code_actions):
for idx, (config_name, code_action) in enumerate(session_actions):
lsp_kind = code_action.get("kind", "")
first_kind_component = cast(CodeActionKind, str(lsp_kind).split(".")[0])
kind = CODE_ACTION_KINDS.get(first_kind_component, sublime.KIND_AMBIGUOUS)
items.append(sublime.QuickPanelItem(code_action["title"], kind=kind))
items.append(sublime.QuickPanelItem(code_action["title"], annotation=config_name, kind=kind))
if code_action.get('isPreferred', False):
selected_index = idx
return items, selected_index
Expand Down
54 changes: 31 additions & 23 deletions plugin/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def _setup(self) -> None:
self._stored_region = sublime.Region(-1, -1)
self._sighelp = None # type: Optional[SigHelp]
self._lightbulb_line = None # type: Optional[int]
self._actions_by_config = {} # type: Dict[str, List[CodeActionOrCommand]]
self._actions_by_config = [] # type: List[CodeActionsByConfigName]
self._registered = False

def _cleanup(self) -> None:
Expand Down Expand Up @@ -284,7 +284,7 @@ def diagnostics_touching_point_async(
def on_diagnostics_updated_async(self) -> None:
self._clear_code_actions_annotation()
if userprefs().show_code_actions:
self._do_code_actions()
self._do_code_actions_async()
self._update_diagnostic_in_status_bar_async()
window = self.view.window()
is_active_view = window and window.active_view() == self.view
Expand Down Expand Up @@ -369,7 +369,7 @@ def on_selection_modified_async(self) -> None:
after_ms=self.highlights_debounce_time)
self._clear_code_actions_annotation()
if userprefs().show_code_actions:
self._when_selection_remains_stable_async(self._do_code_actions, current_region,
self._when_selection_remains_stable_async(self._do_code_actions_async, current_region,
after_ms=self.code_actions_debounce_time)
self._update_diagnostic_in_status_bar_async()
self._resolve_visible_code_lenses_async()
Expand Down Expand Up @@ -590,13 +590,17 @@ def _on_sighelp_navigate(self, href: str) -> None:

# --- textDocument/codeAction --------------------------------------------------------------------------------------

def _do_code_actions(self) -> None:
def _do_code_actions_async(self) -> None:
diagnostics_by_config, covering = self.diagnostics_intersecting_async(self._stored_region)
actions_manager.request_for_region_async(
self.view, covering, diagnostics_by_config, self._on_code_actions, manual=False)

def _on_code_actions(self, responses: CodeActionsByConfigName) -> None:
action_count = sum(map(len, responses.values()))
actions_manager \
.request_for_region_async(self.view, covering, diagnostics_by_config, manual=False) \
.then(self._on_code_actions)

def _on_code_actions(self, responses: List[CodeActionsByConfigName]) -> None:
# flatten list
action_lists = [actions for _, actions in responses if len(actions)]
all_actions = [action for actions in action_lists for action in actions]
action_count = len(all_actions)
if action_count == 0:
return
regions = [sublime.Region(self._stored_region.b, self._stored_region.a)]
Expand All @@ -614,9 +618,9 @@ def _on_code_actions(self, responses: CodeActionsByConfigName) -> None:
if action_count > 1:
title = '{} code actions'.format(action_count)
else:
title = next(itertools.chain.from_iterable(responses.values()))['title']
title = all_actions[0]['title']
title = "<br>".join(textwrap.wrap(title, width=30))
code_actions_link = make_command_link('lsp_code_actions', title, {"commands_by_config": responses})
code_actions_link = make_command_link('lsp_code_actions', title, {"code_actions_by_config": responses})
annotations = ["<div class=\"actions\" style=\"font-family:system\">{}</div>".format(code_actions_link)]
annotation_color = self.view.style_for_scope("region.bluish markup.accent.codeaction.lsp")["foreground"]
self.view.add_regions(SessionView.CODE_ACTIONS_KEY, regions, scope, icon, flags, annotations, annotation_color)
Expand All @@ -628,26 +632,30 @@ def _clear_code_actions_annotation(self) -> None:
def _on_navigate(self, href: str, point: int) -> None:
if href.startswith('code-actions:'):
_, config_name = href.split(":")
actions = self._actions_by_config[config_name]
actions = next(actions for name, actions in self._actions_by_config if name == config_name)
if len(actions) > 1:
window = self.view.window()
if window:
items, selected_index = format_code_actions_for_quick_panel(actions)
items, selected_index = format_code_actions_for_quick_panel(
map(lambda action: (config_name, action), actions))
window.show_quick_panel(
items,
lambda i: self.handle_code_action_select(config_name, i),
lambda i: self.handle_code_action_select(config_name, actions, i),
selected_index=selected_index,
placeholder="Code actions")
else:
self.handle_code_action_select(config_name, 0)

def handle_code_action_select(self, config_name: str, index: int) -> None:
if index > -1:
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], progress=True)
sublime.set_timeout_async(run_async)
self.handle_code_action_select(config_name, actions, 0)

def handle_code_action_select(self, config_name: str, actions: List[CodeActionOrCommand], index: int) -> None:
if index == -1:
return

def run_async() -> None:
session = self.session_by_name(config_name)
if session:
session.run_code_action_async(actions[index], progress=True)

sublime.set_timeout_async(run_async)

# --- textDocument/codeLens ----------------------------------------------------------------------------------------

Expand Down
61 changes: 33 additions & 28 deletions plugin/hover.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .code_actions import actions_manager
from .code_actions import CodeActionOrCommand
from .code_actions import CodeActionsByConfigName
from .core.open import open_file_uri
from .core.open import open_in_browser
from .core.promise import Promise
Expand Down Expand Up @@ -79,18 +80,20 @@ def link(self, point: int, view: sublime.View) -> str:
]


def code_actions_content(actions_by_config: Dict[str, List[CodeActionOrCommand]]) -> str:
def code_actions_content(actions_by_config: List[CodeActionsByConfigName]) -> str:
formatted = []
for config_name, actions in actions_by_config.items():
for config_name, actions in actions_by_config:
action_count = len(actions)
if action_count > 0:
href = "{}:{}".format('code-actions', config_name)
if action_count > 1:
text = "choose code action ({} available)".format(action_count)
else:
text = actions[0].get('title', 'code action')
formatted.append('<div class="actions">[{}] Code action: {}</div>'.format(
config_name, make_link(href, text)))
if action_count == 0:
continue
if action_count > 1:
text = "choose ({} available)".format(action_count)
else:
text = actions[0].get('title', 'code action')
href = "{}:{}".format('code-actions', config_name)
link = make_link(href, text)
formatted.append(
'<div class="actions">Quick Fix: {} <span class="color-muted">{}</span></div>'.format(link, config_name))
return "".join(formatted)


Expand Down Expand Up @@ -122,7 +125,7 @@ def run(
self._base_dir = wm.get_project_path(self.view.file_name() or "")
self._hover_responses = [] # type: List[Tuple[Hover, Optional[MarkdownLangMap]]]
self._document_link = ('', False, None) # type: Tuple[str, bool, Optional[sublime.Region]]
self._actions_by_config = {} # type: Dict[str, List[CodeActionOrCommand]]
self._actions_by_config = [] # type: List[CodeActionsByConfigName]
self._diagnostics_by_config = [] # type: Sequence[Tuple[SessionBufferProtocol, Sequence[Diagnostic]]]
# TODO: For code actions it makes more sense to use the whole selection under mouse (if available)
# rather than just the hover point.
Expand All @@ -140,9 +143,9 @@ def run_async() -> None:
if self._diagnostics_by_config:
self.show_hover(listener, hover_point, only_diagnostics)
if not only_diagnostics and userprefs().show_code_actions_in_hover:
actions_manager.request_for_region_async(
self.view, covering, self._diagnostics_by_config,
functools.partial(self.handle_code_actions, listener, hover_point), manual=False)
actions_manager \
.request_for_region_async(self.view, covering, self._diagnostics_by_config, manual=False) \
.then(lambda results: self._handle_code_actions(listener, hover_point, results))

sublime.set_timeout_async(run_async)

Expand Down Expand Up @@ -230,11 +233,11 @@ def _on_all_document_links_resolved(
self._document_link = ('<br>'.join(contents) if contents else '', link_has_standard_tooltip, link_range)
self.show_hover(listener, point, only_diagnostics=False)

def handle_code_actions(
def _handle_code_actions(
self,
listener: AbstractViewListener,
point: int,
responses: Dict[str, List[CodeActionOrCommand]]
responses: List[CodeActionsByConfigName]
) -> None:
self._actions_by_config = responses
self.show_hover(listener, point, only_diagnostics=False)
Expand Down Expand Up @@ -329,20 +332,21 @@ def _on_navigate(self, href: str, point: int) -> None:
if window:
open_file_uri(window, href)
elif href.startswith('code-actions:'):
_, config_name = href.split(":")
actions = self._actions_by_config[config_name]
self.view.run_command("lsp_selection_set", {"regions": [(point, point)]})
_, config_name = href.split(":")
actions = next(actions for name, actions in self._actions_by_config if name == config_name)
if len(actions) > 1:
window = self.view.window()
if window:
items, selected_index = format_code_actions_for_quick_panel(actions)
items, selected_index = format_code_actions_for_quick_panel(
map(lambda action: (config_name, action), actions))
window.show_quick_panel(
items,
lambda i: self.handle_code_action_select(config_name, i),
lambda i: self.handle_code_action_select(config_name, actions, i),
selected_index=selected_index,
placeholder="Code actions")
else:
self.handle_code_action_select(config_name, 0)
self.handle_code_action_select(config_name, actions, 0)
elif is_location_href(href):
session_name, uri, row, col_utf16 = unpack_href_location(href)
session = self.session_by_name(session_name)
Expand All @@ -353,12 +357,13 @@ def _on_navigate(self, href: str, point: int) -> None:
else:
open_in_browser(href)

def handle_code_action_select(self, config_name: str, index: int) -> None:
if index > -1:
def handle_code_action_select(self, config_name: str, actions: List[CodeActionOrCommand], index: int) -> None:
if index == -1:
return

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], progress=True)
def run_async() -> None:
session = self.session_by_name(config_name)
if session:
session.run_code_action_async(actions[index], progress=True)

sublime.set_timeout_async(run_async)
sublime.set_timeout_async(run_async)
3 changes: 3 additions & 0 deletions popups.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
border-width: 0;
border-radius: 0;
}
.color-muted {
color: color(var(--foreground) alpha(0.50));
}
.diagnostics {
margin-bottom: 0.5rem;
font-family: var(--mdpopups-font-mono);
Expand Down
Loading

0 comments on commit 18a533d

Please sign in to comment.