Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show popup with code actions when hovering over lightbulb icon #1929

Merged
merged 5 commits into from
Jan 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions LSP.sublime-settings
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,6 @@
// "annotation" - show an annotation on the right when code actions are available.
// "bulb" - show a bulb in the gutter when code actions are available.
// "" - don't show code actions.
// Note: Due to API limitations, the "bulb" icon can not be clicked so the code actions can only be triggered
// using a keyboard shortcut or the context menu.
"show_code_actions": "annotation",

// Show code lens:
Expand Down
44 changes: 42 additions & 2 deletions plugin/documents.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .code_actions import actions_manager
from .code_actions import CodeActionOrCommand
from .code_actions import CodeActionsByConfigName
from .completion import LspResolveDocsCommand
from .core.logging import debug
Expand Down Expand Up @@ -35,6 +36,7 @@
from .core.views import text_document_position_params
from .core.views import update_lsp_popup
from .core.windows import WindowManager
from .hover import code_actions_content
from .session_buffer import SessionBuffer
from .session_view import SessionView
from functools import partial
Expand Down Expand Up @@ -174,6 +176,8 @@ def _setup(self) -> None:
self._session_views = {} # type: Dict[str, SessionView]
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._registered = False

def _cleanup(self) -> None:
Expand Down Expand Up @@ -410,9 +414,22 @@ def on_query_context(self, key: str, operator: int, operand: Any, match_all: boo
return None

def on_hover(self, point: int, hover_zone: int) -> None:
if hover_zone != sublime.HOVER_TEXT or self.view.is_popup_visible():
if self.view.is_popup_visible():
return
self.view.run_command("lsp_hover", {"point": point})
if hover_zone == sublime.HOVER_TEXT:
self.view.run_command("lsp_hover", {"point": point})
elif hover_zone == sublime.HOVER_GUTTER:
# Lightbulb must be visible and at the same line
if self._lightbulb_line != self.view.rowcol(point)[0]:
return
content = code_actions_content(self._actions_by_config)
if content:
show_lsp_popup(
self.view,
content,
flags=sublime.HIDE_ON_MOUSE_MOVE_AWAY,
location=point,
on_navigate=lambda href: self._on_navigate(href, point))

def on_text_command(self, command_name: str, args: Optional[dict]) -> Optional[Tuple[str, dict]]:
if command_name == "show_scope_name" and userprefs().semantic_highlighting:
Expand Down Expand Up @@ -540,6 +557,8 @@ def _on_code_actions(self, responses: CodeActionsByConfigName) -> None:
if userprefs().show_code_actions == 'bulb':
scope = 'region.yellowish lightbulb.lsp'
icon = 'Packages/LSP/icons/lightbulb.png'
self._lightbulb_line = self.view.rowcol(regions[0].begin())[0]
self._actions_by_config = responses
else: # 'annotation'
if action_count > 1:
title = '{} code actions'.format(action_count)
Expand All @@ -553,6 +572,27 @@ def _on_code_actions(self, responses: CodeActionsByConfigName) -> None:

def _clear_code_actions_annotation(self) -> None:
self.view.erase_regions(SessionView.CODE_ACTIONS_KEY)
self._lightbulb_line = None

def _on_navigate(self, href: str, point: int) -> None:
if href.startswith('code-actions:'):
_, config_name = href.split(":")
titles = [command["title"] for command in self._actions_by_config[config_name]]
if len(titles) > 1:
window = self.view.window()
if window:
window.show_quick_panel(titles, lambda i: self.handle_code_action_select(config_name, i),
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)

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

Expand Down
31 changes: 16 additions & 15 deletions plugin/hover.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,21 @@ def link(self, point: int, view: sublime.View) -> str:
]


def code_actions_content(actions_by_config: Dict[str, List[CodeActionOrCommand]]) -> str:
formatted = []
for config_name, actions in actions_by_config.items():
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)))
return "".join(formatted)


class LspHoverCommand(LspTextCommand):

def __init__(self, view: sublime.View) -> None:
Expand Down Expand Up @@ -200,20 +215,6 @@ def diagnostics_content(self) -> str:
formatted.append("</div>")
return "".join(formatted)

def code_actions_content(self) -> str:
formatted = []
for config_name, actions in self._actions_by_config.items():
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)))
return "".join(formatted)

def hover_content(self) -> str:
contents = []
for hover, language_map in self._hover_responses:
Expand All @@ -227,7 +228,7 @@ def show_hover(self, listener: AbstractViewListener, point: int, only_diagnostic

def _show_hover(self, listener: AbstractViewListener, point: int, only_diagnostics: bool) -> None:
hover_content = self.hover_content()
contents = self.diagnostics_content() + hover_content + self.code_actions_content()
contents = self.diagnostics_content() + hover_content + code_actions_content(self._actions_by_config)
if contents and not only_diagnostics and hover_content:
contents += self.symbol_actions_content(listener, point)

Expand Down