diff --git a/Context.sublime-menu b/Context.sublime-menu
index 08cb70ee5..5defb140f 100644
--- a/Context.sublime-menu
+++ b/Context.sublime-menu
@@ -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"]},
diff --git a/Default.sublime-commands b/Default.sublime-commands
index bd9c22766..92cfa689b 100644
--- a/Default.sublime-commands
+++ b/Default.sublime-commands
@@ -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",
@@ -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": [
diff --git a/docs/src/features.md b/docs/src/features.md
index 998ad3409..c70630831 100644
--- a/docs/src/features.md
+++ b/docs/src/features.md
@@ -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.
diff --git a/docs/src/keyboard_shortcuts.md b/docs/src/keyboard_shortcuts.md
index dbad8b40f..4e16185f5 100644
--- a/docs/src/keyboard_shortcuts.md
+++ b/docs/src/keyboard_shortcuts.md
@@ -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 | ctrl alt space | `lsp_signature_help_show`
| Toggle Diagnostics Panel | ctrl alt m | `lsp_show_diagnostics_panel`
diff --git a/plugin/code_actions.py b/plugin/code_actions.py
index 62d3a4901..e1946fdcd 100644
--- a/plugin/code_actions.py
+++ b/plugin/code_actions.py
@@ -7,227 +7,198 @@
from .core.protocol import Request
from .core.registry import LspTextCommand
from .core.registry import windows
+from .core.sessions import AbstractViewListener
+from .core.sessions import Session
from .core.sessions import SessionBufferProtocol
from .core.settings import userprefs
-from .core.typing import Any, List, Dict, Callable, Optional, Tuple, Union, cast
+from .core.typing import Any, List, Dict, Callable, Optional, Tuple, TypeGuard, 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
from .core.views import text_document_code_action_params
from .save_command import LspSaveCommand, SaveTask
+from functools import partial
import sublime
+ConfigName = str
CodeActionOrCommand = Union[CodeAction, Command]
-CodeActionsResponse = Optional[List[CodeActionOrCommand]]
-CodeActionsByConfigName = Dict[str, List[CodeActionOrCommand]]
+CodeActionsByConfigName = Tuple[ConfigName, List[CodeActionOrCommand]]
-class CodeActionsCollector:
- """
- Collects code action responses from multiple sessions. Calls back the "on_complete_handler" with
- results when all responses are received.
-
- Usage example:
-
- with CodeActionsCollector() as collector:
- actions_manager.request_with_diagnostics(collector.create_collector('test_config'))
- actions_manager.request_with_diagnostics(collector.create_collector('another_config'))
-
- The "create_collector()" must only be called within the "with" context. Once the context is
- exited, the "on_complete_handler" will be called once all the created collectors receive the
- response (are called).
- """
-
- def __init__(self, on_complete_handler: Callable[[CodeActionsByConfigName], None]):
- self._on_complete_handler = on_complete_handler
- self._commands_by_config = {} # type: CodeActionsByConfigName
- self._request_count = 0
- self._response_count = 0
- self._all_requested = False
-
- def __enter__(self) -> 'CodeActionsCollector':
- return self
-
- def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
- self._all_requested = True
- self._notify_if_all_finished()
-
- def create_collector(self, config_name: str) -> Callable[[CodeActionsResponse], None]:
- self._request_count += 1
- return lambda actions: self._collect_response(config_name, actions)
-
- def _collect_response(self, config_name: str, actions: CodeActionsResponse) -> None:
- self._response_count += 1
- self._commands_by_config[config_name] = self._get_enabled_actions(actions or [])
- self._notify_if_all_finished()
-
- def _get_enabled_actions(self, actions: List[CodeActionOrCommand]) -> List[CodeActionOrCommand]:
- return [action for action in actions if not action.get('disabled')]
-
- def _notify_if_all_finished(self) -> None:
- if self._all_requested and self._request_count == self._response_count:
- # Call back on Sublime's async thread
- sublime.set_timeout_async(lambda: self._on_complete_handler(self._commands_by_config))
-
- def get_actions(self) -> CodeActionsByConfigName:
- return self._commands_by_config
+def is_command(action: CodeActionOrCommand) -> TypeGuard[Command]:
+ return isinstance(action.get('command'), str)
class CodeActionsManager:
"""Manager for per-location caching of code action responses."""
def __init__(self) -> None:
- self._response_cache = None # type: Optional[Tuple[str, CodeActionsCollector]]
+ self._response_cache = None # type: Optional[Tuple[str, Promise[List[CodeActionsByConfigName]]]]
def request_for_region_async(
self,
view: sublime.View,
region: sublime.Region,
session_buffer_diagnostics: List[Tuple[SessionBufferProtocol, List[Diagnostic]]],
- actions_handler: Callable[[CodeActionsByConfigName], None],
only_kinds: Optional[List[CodeActionKind]] = None,
manual: bool = False,
- ) -> None:
+ ) -> Promise[List[CodeActionsByConfigName]]:
"""
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,
- only_with_diagnostics=False,
- actions_handler=actions_handler,
- on_save_actions=None,
- only_kinds=only_kinds,
- manual=manual)
-
- def request_on_save(
- self,
- view: sublime.View,
- actions_handler: Callable[[CodeActionsByConfigName], None],
- on_save_actions: Dict[str, bool]
- ) -> None:
- """
- Requests code actions on save.
- """
- 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,
- only_with_diagnostics=False,
- actions_handler=actions_handler,
- on_save_actions=on_save_actions,
- only_kinds=None,
- manual=False)
-
- def _request_async(
- self,
- view: sublime.View,
- region: sublime.Region,
- session_buffer_diagnostics: List[Tuple[SessionBufferProtocol, List[Diagnostic]]],
- 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
+ return Promise.resolve([])
location_cache_key = None
- use_cache = on_save_actions is None and not manual
+ use_cache = not manual
if use_cache:
- location_cache_key = "{}#{}:{}:{}".format(
- view.buffer_id(), view.change_count(), region, only_with_diagnostics)
+ location_cache_key = "{}#{}:{}".format(view.buffer_id(), view.change_count(), region)
if self._response_cache:
- cache_key, cache_collector = self._response_cache
+ cache_key, task = self._response_cache
if location_cache_key == cache_key:
- sublime.set_timeout(lambda: actions_handler(cache_collector.get_actions()))
- return
+ return task
else:
self._response_cache = None
- collector = CodeActionsCollector(actions_handler)
- with collector:
- 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, *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 request_factory(session: Session) -> Optional[Request]:
+ diagnostics = [] # type: List[Diagnostic]
+ for sb, diags in session_buffer_diagnostics:
+ if sb.session == session:
+ diagnostics = diags
+ break
+ params = text_document_code_action_params(view, region, diagnostics, only_kinds, manual)
+ return Request.codeAction(params, view)
+
+ def response_filter(session: Session, actions: List[CodeActionOrCommand]) -> List[CodeActionOrCommand]:
+ # Filter out non "quickfix" code actions unless "only_kinds" is provided.
+ if only_kinds:
+ return [a for a in actions if not is_command(a) and kinds_include_kind(only_kinds, a.get('kind'))]
+ if manual:
+ return actions
+ # On implicit (selection change) request, only return commands and quick fix kinds.
+ return [
+ a for a in actions
+ if is_command(a) or not a.get('kind') or kinds_include_kind([CodeActionKind.QuickFix], a.get('kind'))
+ ]
+
+ task = self._collect_code_actions_async(listener, request_factory, response_filter)
+ if location_cache_key:
+ self._response_cache = (location_cache_key, task)
+ return task
-def filtering_collector(
- config_name: str,
- kinds: List[CodeActionKind],
- actions_collector: CodeActionsCollector
-) -> Tuple[Callable[[CodeActionsResponse], None], Callable[[Any], None]]:
- """
- Filters actions returned from the session so that only matching kinds are collected.
+ def request_on_save_async(
+ self, view: sublime.View, on_save_actions: Dict[str, bool]
+ ) -> Promise[List[CodeActionsByConfigName]]:
+ listener = windows.listener_for_view(view)
+ if not listener:
+ return Promise.resolve([])
+ region = entire_content_region(view)
+ session_buffer_diagnostics, _ = listener.diagnostics_intersecting_region_async(region)
- Since older servers don't support the "context.only" property, these will return all
- actions that need to be filtered.
- """
+ def request_factory(session: Session) -> Optional[Request]:
+ session_kinds = get_session_kinds(session)
+ matching_kinds = get_matching_on_save_kinds(on_save_actions, session_kinds)
+ if not matching_kinds:
+ return None
+ diagnostics = [] # type: List[Diagnostic]
+ for sb, diags in session_buffer_diagnostics:
+ if sb.session == session:
+ diagnostics = diags
+ break
+ params = text_document_code_action_params(view, region, diagnostics, matching_kinds, manual=False)
+ return Request.codeAction(params, view)
+
+ def response_filter(session: Session, actions: List[CodeActionOrCommand]) -> List[CodeActionOrCommand]:
+ # Filter actions returned from the session so that only matching kinds are collected.
+ # Since older servers don't support the "context.only" property, those will return all
+ # actions that need to be then manually filtered.
+ session_kinds = get_session_kinds(session)
+ matching_kinds = get_matching_on_save_kinds(on_save_actions, session_kinds)
+ return [a for a in actions if a.get('kind') in matching_kinds]
+
+ return self._collect_code_actions_async(listener, request_factory, response_filter)
+
+ def _collect_code_actions_async(
+ self,
+ listener: AbstractViewListener,
+ request_factory: Callable[[Session], Optional[Request]],
+ response_filter: Optional[Callable[[Session, List[CodeActionOrCommand]], List[CodeActionOrCommand]]] = None,
+ ) -> Promise[List[CodeActionsByConfigName]]:
+
+ def on_response(
+ session: Session, response: Union[Error, Optional[List[CodeActionOrCommand]]]
+ ) -> CodeActionsByConfigName:
+ if isinstance(response, Error):
+ actions = []
+ else:
+ actions = [action for action in (response or []) if not action.get('disabled')]
+ if actions and response_filter:
+ actions = response_filter(session, actions)
+ return (session.config.name, actions)
+
+ tasks = [] # type: List[Promise[CodeActionsByConfigName]]
+ for session in listener.sessions_async('codeActionProvider'):
+ request = request_factory(session)
+ if request:
+ response_handler = partial(on_response, session)
+ task = session.send_request_task(request) # type: Promise[Optional[List[CodeActionOrCommand]]]
+ tasks.append(task.then(response_handler))
+ # Return only results for non-empty lists.
+ return Promise.all(tasks).then(lambda sessions: list(filter(lambda session: len(session[1]), sessions)))
- def actions_filter(actions: CodeActionsResponse) -> List[CodeActionOrCommand]:
- return [a for a in (actions or []) if a.get('kind') in kinds]
- collector = actions_collector.create_collector(config_name)
- return (
- lambda actions: collector(actions_filter(actions)),
- lambda error: collector([])
- )
+actions_manager = CodeActionsManager()
-actions_manager = CodeActionsManager()
+def get_session_kinds(session: Session) -> List[CodeActionKind]:
+ session_kinds = session.get_capability('codeActionProvider.codeActionKinds') # type: Optional[List[CodeActionKind]]
+ return session_kinds or []
-def get_matching_kinds(user_actions: Dict[str, bool], session_actions: List[CodeActionKind]) -> List[CodeActionKind]:
+def get_matching_on_save_kinds(
+ user_actions: Dict[str, bool], session_kinds: 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
- specific, disabled actions.
+ Filters user-enabled or disabled actions so that only ones matching the session kinds
+ are returned. Returned kinds are those that are enabled and are not overridden by more
+ specific, disabled kinds.
- Filtering only returns actions that exactly match the ones supported by given session.
+ Filtering only returns kinds that exactly match the ones supported by given session.
If user has enabled a generic action that matches more specific session action
(for example user's a.b matching session's a.b.c), then the more specific (a.b.c) must be
- returned as servers must receive only actions that they advertise support for.
+ returned as servers must receive only kinds that they advertise support for.
"""
matching_kinds = []
- for session_action in session_actions:
+ for session_kind in session_kinds:
enabled = False
- action_parts = cast(str, session_action).split('.')
+ action_parts = session_kind.split('.')
for i in range(len(action_parts)):
current_part = '.'.join(action_parts[0:i + 1])
user_value = user_actions.get(current_part, None)
if isinstance(user_value, bool):
enabled = user_value
if enabled:
- matching_kinds.append(session_action)
+ matching_kinds.append(session_kind)
return matching_kinds
+def kinds_include_kind(kinds: List[CodeActionKind], kind: Optional[CodeActionKind]) -> bool:
+ """
+ The "kinds" include "kind" if "kind" matches one of the "kinds" exactly or one of the "kinds" is a prefix
+ of the whole "kind" (where prefix must be followed by a dot).
+ """
+ if not kind:
+ return False
+ for kinds_item in kinds:
+ if kinds_item == kind:
+ return True
+ kinds_item_len = len(kinds_item)
+ if len(kind) > kinds_item_len and kind.startswith(kinds_item) and kind[kinds_item_len] == '.':
+ return True
+ return False
+
+
class CodeActionOnSaveTask(SaveTask):
"""
The main task that requests code actions from sessions and runs them.
@@ -241,7 +212,7 @@ def is_applicable(cls, view: sublime.View) -> bool:
@classmethod
def _get_code_actions_on_save(cls, view: sublime.View) -> Dict[str, bool]:
- view_code_actions = view.settings().get('lsp_code_actions_on_save') or {}
+ view_code_actions = cast(Dict[str, bool], view.settings().get('lsp_code_actions_on_save') or {})
code_actions = userprefs().lsp_code_actions_on_save.copy()
code_actions.update(view_code_actions)
allowed_code_actions = dict()
@@ -257,20 +228,17 @@ def run_async(self) -> None:
def _request_code_actions_async(self) -> None:
self._purge_changes_async()
on_save_actions = self._get_code_actions_on_save(self._task_runner.view)
- actions_manager.request_on_save(self._task_runner.view, self._handle_response_async, on_save_actions)
+ actions_manager.request_on_save_async(self._task_runner.view, on_save_actions).then(self._handle_response_async)
- def _handle_response_async(self, responses: CodeActionsByConfigName) -> None:
+ def _handle_response_async(self, responses: List[CodeActionsByConfigName]) -> None:
if self._cancelled:
return
document_version = self._task_runner.view.change_count()
tasks = [] # type: List[Promise]
- for config_name, code_actions in responses.items():
- if code_actions:
- for session in self._task_runner.sessions('codeActionProvider'):
- if session.config.name == config_name:
- for code_action in code_actions:
- tasks.append(session.run_code_action_async(code_action, progress=False))
- break
+ 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])
Promise.all(tasks).then(lambda _: self._on_code_actions_completed(document_version))
def _on_code_actions_completed(self, previous_document_version: int) -> None:
@@ -293,65 +261,65 @@ def run(
edit: sublime.Edit,
event: Optional[dict] = None,
only_kinds: Optional[List[CodeActionKind]] = None,
- commands_by_config: Optional[CodeActionsByConfigName] = None
+ code_actions_by_config: Optional[List[CodeActionsByConfigName]] = None
) -> None:
- self.commands = [] # type: List[Tuple[str, CodeActionOrCommand]]
- self.commands_by_config = {} # type: CodeActionsByConfigName
- if commands_by_config:
- self.handle_responses_async(commands_by_config, run_first=True)
- else:
- view = self.view
- region = first_selection_region(view)
- if region is None:
- return
- listener = windows.listener_for_view(view)
- if not listener:
- return
- session_buffer_diagnostics, covering = listener.diagnostics_intersecting_async(region)
- actions_manager.request_for_region_async(
- 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
- self.commands = self.combine_commands()
- if len(self.commands) == 1 and run_first:
- self.handle_select(0)
- else:
- self.show_code_actions()
-
- def combine_commands(self) -> 'List[Tuple[str, CodeActionOrCommand]]':
- results = []
- for config, commands in self.commands_by_config.items():
- for command in commands:
- results.append((config, command))
- return results
+ if code_actions_by_config:
+ self._handle_code_actions(code_actions_by_config, run_first=True)
+ return
+ self._run_async(only_kinds)
- def show_code_actions(self) -> None:
- if len(self.commands) > 0:
+ def _run_async(self, only_kinds: Optional[List[CodeActionKind]] = None) -> None:
+ view = self.view
+ region = first_selection_region(view)
+ if region is None:
+ return
+ listener = windows.listener_for_view(view)
+ if not listener:
+ return
+ session_buffer_diagnostics, covering = listener.diagnostics_intersecting_async(region)
+ actions_manager \
+ .request_for_region_async(view, covering, session_buffer_diagnostics, only_kinds, manual=True) \
+ .then(lambda actions: sublime.set_timeout(lambda: self._handle_code_actions(actions)))
+
+ def _handle_code_actions(self, response: List[CodeActionsByConfigName], run_first: bool = False) -> None:
+ # Flatten response to a list of (config_name, code_action) tuples.
+ actions = [] # type: List[Tuple[ConfigName, CodeActionOrCommand]]
+ for config_name, session_actions in response:
+ actions.extend([(config_name, action) for action in session_actions])
+ if actions:
+ if len(actions) == 1 and run_first:
+ self._handle_select(0, actions)
+ else:
+ self._show_code_actions(actions)
+ else:
window = self.view.window()
if window:
- items, selected_index = format_code_actions_for_quick_panel([command[1] for command in self.commands])
- window.show_quick_panel(
- items,
- self.handle_select,
- selected_index=selected_index,
- placeholder="Code action")
- else:
- self.view.show_popup('No actions available', sublime.HIDE_ON_MOUSE_MOVE_AWAY)
+ window.status_message("No code actions available")
- def handle_select(self, index: int) -> None:
- if index > -1:
+ def _show_code_actions(self, actions: List[Tuple[ConfigName, CodeActionOrCommand]]) -> None:
+ window = self.view.window()
+ if not window:
+ return
+ items, selected_index = format_code_actions_for_quick_panel(actions)
+ window.show_quick_panel(
+ items,
+ lambda i: self._handle_select(i, actions),
+ selected_index=selected_index,
+ placeholder="Code action")
+
+ def _handle_select(self, index: int, actions: List[Tuple[ConfigName, CodeActionOrCommand]]) -> None:
+ if index == -1:
+ return
- def run_async() -> None:
- selected = self.commands[index]
- session = self.session_by_name(selected[0])
- if session:
- name = session.config.name
- session.run_code_action_async(selected[1], progress=True).then(
- lambda resp: self.handle_response_async(name, resp))
+ 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) \
+ .then(lambda response: self._handle_response_async(config_name, response))
- sublime.set_timeout_async(run_async)
+ sublime.set_timeout_async(run_async)
- def handle_response_async(self, session_name: str, response: Any) -> None:
+ def _handle_response_async(self, session_name: str, response: Any) -> None:
if isinstance(response, Error):
sublime.error_message("{}: {}".format(session_name, str(response)))
diff --git a/plugin/core/protocol.py b/plugin/core/protocol.py
index 49daa701e..8220070c6 100644
--- a/plugin/core/protocol.py
+++ b/plugin/core/protocol.py
@@ -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
@@ -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. """
diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py
index 4efa757c0..92355d0eb 100644
--- a/plugin/core/sessions.py
+++ b/plugin/core/sessions.py
@@ -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,
]
}
diff --git a/plugin/core/typing.py b/plugin/core/typing.py
index fd3cd8398..bcb251a85 100644
--- a/plugin/core/typing.py
+++ b/plugin/core/typing.py
@@ -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
@@ -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
@@ -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
@@ -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
diff --git a/plugin/core/views.py b/plugin/core/views.py
index 53e0cb26e..ebf87ee4d 100644
--- a/plugin/core/views.py
+++ b/plugin/core/views.py
@@ -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
diff --git a/plugin/documents.py b/plugin/documents.py
index 3a5219107..020ad3935 100644
--- a/plugin/documents.py
+++ b/plugin/documents.py
@@ -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:
@@ -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
@@ -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()
@@ -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)]
@@ -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 = "
".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 = ["