From 00ce15c40fccf81a218aa35c364667abd28b522c Mon Sep 17 00:00:00 2001 From: jwortmann Date: Thu, 9 Nov 2023 23:27:52 +0100 Subject: [PATCH 1/2] Small visual tweak for signature help popup (#2358) --- plugin/core/signature_help.py | 2 +- plugin/core/views.py | 21 ++++++++++++++++----- plugin/documents.py | 1 + popups.css | 7 +++++++ tests/test_signature_help.py | 2 +- 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/plugin/core/signature_help.py b/plugin/core/signature_help.py index 23a34105d..a15e2e499 100644 --- a/plugin/core/signature_help.py +++ b/plugin/core/signature_help.py @@ -105,7 +105,7 @@ def select_signature(self, forward: bool) -> None: def _render_intro(self) -> str: fmt = '

{} of {} overloads ' + \ - "(use ↑ ↓ to navigate, press Esc to hide):

" + '(use to navigate, press Esc to hide):

' return fmt.format( self._active_signature_index + 1, len(self._signatures), diff --git a/plugin/core/views.py b/plugin/core/views.py index a55d1a239..39e5ad07d 100644 --- a/plugin/core/views.py +++ b/plugin/core/views.py @@ -606,27 +606,30 @@ def text_document_code_action_params( } -# Workaround for a limited margin-collapsing capabilities of the minihtml. +# Workaround for limited margin-collapsing capabilities of the minihtml. LSP_POPUP_SPACER_HTML = '
' def show_lsp_popup( view: sublime.View, contents: str, + *, location: int = -1, md: bool = False, flags: int = 0, css: Optional[str] = None, wrapper_class: Optional[str] = None, + body_id: Optional[str] = None, on_navigate: Optional[Callable[..., None]] = None, on_hide: Optional[Callable[..., None]] = None ) -> None: css = css if css is not None else lsp_css().popups wrapper_class = wrapper_class if wrapper_class is not None else lsp_css().popups_classname contents += LSP_POPUP_SPACER_HTML + body_wrapper = '{{}}'.format(body_id) if body_id else '{}' mdpopups.show_popup( view, - contents, + body_wrapper.format(contents), css=css, md=md, flags=flags, @@ -638,12 +641,20 @@ def show_lsp_popup( on_hide=on_hide) -def update_lsp_popup(view: sublime.View, contents: str, md: bool = False, css: Optional[str] = None, - wrapper_class: Optional[str] = None) -> None: +def update_lsp_popup( + view: sublime.View, + contents: str, + *, + md: bool = False, + css: Optional[str] = None, + wrapper_class: Optional[str] = None, + body_id: Optional[str] = None +) -> None: css = css if css is not None else lsp_css().popups wrapper_class = wrapper_class if wrapper_class is not None else lsp_css().popups_classname contents += LSP_POPUP_SPACER_HTML - mdpopups.update_popup(view, contents, css=css, md=md, wrapper_class=wrapper_class) + body_wrapper = '{{}}'.format(body_id) if body_id else '{}' + mdpopups.update_popup(view, body_wrapper.format(contents), css=css, md=md, wrapper_class=wrapper_class) FORMAT_STRING = 0x1 diff --git a/plugin/documents.py b/plugin/documents.py index 33afd3a57..5f7e9b5c6 100644 --- a/plugin/documents.py +++ b/plugin/documents.py @@ -615,6 +615,7 @@ def _show_sighelp_popup(self, content: str, point: int) -> None: content, flags=sublime.COOPERATE_WITH_AUTO_COMPLETE, location=point, + body_id='lsp-signature-help', on_hide=self._on_sighelp_hide, on_navigate=self._on_sighelp_navigate) diff --git a/popups.css b/popups.css index 592044102..4e43362b0 100644 --- a/popups.css +++ b/popups.css @@ -18,6 +18,13 @@ .lsp_popup h6 { font-size: 1rem; } +.lsp_popup kbd { + font-size: 0.8rem; + line-height: 0.8rem; + color: var(--mdpopups-fg); + background-color: color(var(--mdpopups-bg) lightness(+ 5%)); + border-color: color(var(--mdpopups-fg) alpha(0.25)); +} .highlight { border-width: 0; border-radius: 0; diff --git a/tests/test_signature_help.py b/tests/test_signature_help.py index e10372212..8de8d43f6 100644 --- a/tests/test_signature_help.py +++ b/tests/test_signature_help.py @@ -202,7 +202,7 @@ def test_overloads(self) -> None: r'''

- 2 of 2 overloads \(use ↑ ↓ to navigate, press Esc to hide\): + 2 of 2 overloads \(use to navigate, press Esc to hide\):

f\(

From 0ef280ffb534ae0663bf121bbf4037cef2c2a7af Mon Sep 17 00:00:00 2001
From: jwortmann 
Date: Thu, 9 Nov 2023 23:33:48 +0100
Subject: [PATCH 2/2] Show diagnostics popup when hovering over gutter icons
 (#2349)

---
 plugin/documents.py | 63 +++++++++++++++++++++++++++++++++------------
 1 file changed, 46 insertions(+), 17 deletions(-)

diff --git a/plugin/documents.py b/plugin/documents.py
index 5f7e9b5c6..150a65ada 100644
--- a/plugin/documents.py
+++ b/plugin/documents.py
@@ -4,6 +4,7 @@
 from .completion import QueryCompletionsTask
 from .core.constants import HOVER_ENABLED_KEY
 from .core.logging import debug
+from .core.open import open_in_browser
 from .core.panels import PanelName
 from .core.protocol import Diagnostic
 from .core.protocol import DiagnosticSeverity
@@ -27,6 +28,7 @@
 from .core.settings import userprefs
 from .core.signature_help import SigHelp
 from .core.types import basescope2languageid
+from .core.types import ClientConfig
 from .core.types import debounced
 from .core.types import DebouncerNonThreadSafe
 from .core.types import FEATURES_TIMEOUT
@@ -40,6 +42,7 @@
 from .core.views import DOCUMENT_HIGHLIGHT_KINDS
 from .core.views import first_selection_region
 from .core.views import format_code_actions_for_quick_panel
+from .core.views import format_diagnostic_for_html
 from .core.views import make_link
 from .core.views import MarkdownLangMap
 from .core.views import range_to_region
@@ -176,7 +179,8 @@ def _setup(self) -> None:
         self._stored_selection = []
         self._sighelp = None  # type: Optional[SigHelp]
         self._lightbulb_line = None  # type: Optional[int]
-        self._actions_by_config = []  # type: List[CodeActionsByConfigName]
+        self._diagnostics_for_selection = []  # type: List[Tuple[SessionBufferProtocol, List[Diagnostic]]]
+        self._code_actions_for_selection = []  # type: List[CodeActionsByConfigName]
         self._registered = False
 
     def _cleanup(self) -> None:
@@ -471,17 +475,40 @@ def on_hover(self, point: int, hover_zone: int) -> None:
         if hover_zone == sublime.HOVER_TEXT and window and window.settings().get(HOVER_ENABLED_KEY, True):
             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)
+            sublime.set_timeout_async(partial(self._on_hover_gutter_async, point))
+
+    def _on_hover_gutter_async(self, point: int) -> None:
+        content = ''
+        if self._lightbulb_line == self.view.rowcol(point)[0]:
+            content += code_actions_content(self._code_actions_for_selection)
+        if userprefs().show_diagnostics_severity_level:
+            diagnostics_with_config = []  # type: List[Tuple[ClientConfig, Diagnostic]]
+            diagnostics_by_session_buffer = []  # type: List[Tuple[SessionBufferProtocol, List[Diagnostic]]]
+            max_severity_level = min(userprefs().show_diagnostics_severity_level, DiagnosticSeverity.Information)
+            if userprefs().diagnostics_gutter_marker:
+                diagnostics_by_session_buffer = self.diagnostics_intersecting_async(self.view.line(point))[0]
+            elif content:
+                diagnostics_by_session_buffer = self._diagnostics_for_selection
             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))
+                max_severity_level = userprefs().show_diagnostics_severity_level
+            for sb, diagnostics in diagnostics_by_session_buffer:
+                diagnostics_with_config.extend(
+                    (sb.session.config, diagnostic) for diagnostic in diagnostics
+                    if diagnostic_severity(diagnostic) <= max_severity_level
+                )
+            if diagnostics_with_config:
+                diagnostics_with_config.sort(key=lambda d: diagnostic_severity(d[1]))
+                content += '
' + for config, diagnostic in diagnostics_with_config: + content += format_diagnostic_for_html(config, diagnostic) + content += '
' + 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 == "auto_complete": @@ -638,13 +665,13 @@ def _on_sighelp_navigate(self, href: str) -> None: def _do_code_actions_async(self) -> None: if not self._stored_selection: return - diagnostics_by_config, covering = self.diagnostics_intersecting_async(self._stored_selection[0]) + self._diagnostics_for_selection, covering = self.diagnostics_intersecting_async(self._stored_selection[0]) actions_manager \ - .request_for_region_async(self.view, covering, diagnostics_by_config, manual=False) \ + .request_for_region_async(self.view, covering, self._diagnostics_for_selection, manual=False) \ .then(self._on_code_actions) def _on_code_actions(self, responses: List[CodeActionsByConfigName]) -> None: - self._actions_by_config = responses + self._code_actions_for_selection = responses action_count = 0 first_action_title = '' for _, actions in responses: @@ -678,8 +705,8 @@ def _on_code_actions(self, responses: List[CodeActionsByConfigName]) -> None: ) def _on_code_actions_annotation_click(self, href: str) -> None: - if href == 'code-actions:' and self._actions_by_config: - self.view.run_command('lsp_code_actions', {'code_actions_by_config': self._actions_by_config}) + if href == 'code-actions:' and self._code_actions_for_selection: + self.view.run_command('lsp_code_actions', {'code_actions_by_config': self._code_actions_for_selection}) def _clear_code_actions_annotation(self) -> None: self.view.erase_regions(SessionView.CODE_ACTIONS_KEY) @@ -688,7 +715,7 @@ 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 = next(actions for name, actions in self._actions_by_config if name == config_name) + actions = next(actions for name, actions in self._code_actions_for_selection if name == config_name) if len(actions) > 1: window = self.view.window() if window: @@ -701,6 +728,8 @@ def _on_navigate(self, href: str, point: int) -> None: placeholder="Code actions") else: self.handle_code_action_select(config_name, actions, 0) + else: + open_in_browser(href) def handle_code_action_select(self, config_name: str, actions: List[CodeActionOrCommand], index: int) -> None: if index == -1: