From 38d74529b45dd894260b8bbf0dd4ab251c34017c Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Tue, 29 Aug 2023 19:09:36 +0200 Subject: [PATCH 1/6] Add menu item to disable/enable hover popups --- Main.sublime-menu | 5 +++++ boot.py | 1 + plugin/core/constants.py | 4 ++++ plugin/core/sessions.py | 3 +++ plugin/core/windows.py | 1 + plugin/documents.py | 2 +- plugin/hover.py | 32 +++++++++++++++++++++++++++++++- plugin/session_view.py | 23 +++++++++++++++-------- 8 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 plugin/core/constants.py diff --git a/Main.sublime-menu b/Main.sublime-menu index 0b39db9c5..0b5d71bd1 100644 --- a/Main.sublime-menu +++ b/Main.sublime-menu @@ -159,6 +159,11 @@ { "caption": "LSP: Show Inlay Hints", "command": "lsp_toggle_inlay_hints" + }, + { + "caption": "LSP: Show Hover Popups", + "command": "lsp_toggle_hover_popups", + "checkbox": true } ] }, diff --git a/boot.py b/boot.py index 686215f2f..f16116d15 100644 --- a/boot.py +++ b/boot.py @@ -56,6 +56,7 @@ from .plugin.hierarchy import LspHierarchyToggleCommand from .plugin.hierarchy import LspTypeHierarchyCommand from .plugin.hover import LspHoverCommand +from .plugin.hover import LspToggleHoverPopupsCommand from .plugin.inlay_hint import LspInlayHintClickCommand from .plugin.inlay_hint import LspToggleInlayHintsCommand from .plugin.panels import LspClearLogPanelCommand diff --git a/plugin/core/constants.py b/plugin/core/constants.py new file mode 100644 index 000000000..fa003364b --- /dev/null +++ b/plugin/core/constants.py @@ -0,0 +1,4 @@ +# TODO: Move all constants into this file, so that all other modules can import them without causing import loops + +HOVER_HIGHLIGHT_KEY = "lsp_hover_highlight" +HOVER_PROVIDER_COUNT_KEY = "lsp_hover_provider_count" diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py index 7c814aab9..ef81f8654 100644 --- a/plugin/core/sessions.py +++ b/plugin/core/sessions.py @@ -575,6 +575,9 @@ def start_code_lenses_async(self) -> None: def set_code_lenses_pending_refresh(self, needs_refresh: bool = True) -> None: ... + def reset_show_definitions(self) -> None: + ... + class SessionBufferProtocol(Protocol): diff --git a/plugin/core/windows.py b/plugin/core/windows.py index 39481ea13..3b14b0541 100644 --- a/plugin/core/windows.py +++ b/plugin/core/windows.py @@ -82,6 +82,7 @@ def __init__(self, window: sublime.Window, workspace: ProjectFolders, config_man self.tree_view_sheets = {} # type: Dict[str, TreeViewSheet] self.total_error_count = 0 self.total_warning_count = 0 + self.hover_enabled = True sublime.set_timeout(functools.partial(self._update_panel_main_thread, _NO_DIAGNOSTICS_PLACEHOLDER, [])) self.panel_manager.ensure_log_panel() self._config_manager.add_change_listener(self) diff --git a/plugin/documents.py b/plugin/documents.py index 243a443b3..3d679fd91 100644 --- a/plugin/documents.py +++ b/plugin/documents.py @@ -466,7 +466,7 @@ def on_query_context(self, key: str, operator: int, operand: Any, match_all: boo def on_hover(self, point: int, hover_zone: int) -> None: if self.view.is_popup_visible(): return - if hover_zone == sublime.HOVER_TEXT: + if hover_zone == sublime.HOVER_TEXT and self._manager and self._manager.hover_enabled: self.view.run_command("lsp_hover", {"point": point}) elif hover_zone == sublime.HOVER_GUTTER: # Lightbulb must be visible and at the same line diff --git a/plugin/hover.py b/plugin/hover.py index e0b1257f2..fc1e0f49f 100644 --- a/plugin/hover.py +++ b/plugin/hover.py @@ -1,6 +1,8 @@ from .code_actions import actions_manager from .code_actions import CodeActionOrCommand from .code_actions import CodeActionsByConfigName +from .core.constants import HOVER_HIGHLIGHT_KEY +from .core.constants import HOVER_PROVIDER_COUNT_KEY from .core.open import open_file_uri from .core.open import open_in_browser from .core.promise import Promise @@ -17,6 +19,7 @@ from .core.sessions import SessionBufferProtocol from .core.settings import userprefs from .core.typing import List, Optional, Dict, Tuple, Sequence, Union +from .core.typing import cast from .core.url import parse_uri from .core.views import diagnostic_severity from .core.views import first_selection_region @@ -34,10 +37,10 @@ from .core.views import text_document_position_params from .core.views import unpack_href_location from .core.views import update_lsp_popup -from .session_view import HOVER_HIGHLIGHT_KEY from functools import partial import html import sublime +import sublime_plugin SUBLIME_WORD_MASK = 515 @@ -366,3 +369,30 @@ def run_async() -> None: session.run_code_action_async(actions[index], progress=True, view=self.view) sublime.set_timeout_async(run_async) + + +class LspToggleHoverPopupsCommand(sublime_plugin.TextCommand): + + def is_enabled(self) -> bool: + return self._has_hover_provider() + + def is_checked(self) -> bool: + return self._is_hover_enabled() + + def run(self, edit: sublime.Edit) -> None: + window_manager = windows.lookup(self.view.window()) + if window_manager: + window_manager.hover_enabled = not window_manager.hover_enabled + for session in window_manager.get_sessions(): + for session_view in session.session_views_async(): + if window_manager.hover_enabled: + session_view.view.settings().set('show_definitions', False) + else: + session_view.reset_show_definitions() + + def _has_hover_provider(self) -> bool: + return cast(int, self.view.settings().get(HOVER_PROVIDER_COUNT_KEY, 0)) > 0 + + def _is_hover_enabled(self) -> bool: + window_manager = windows.lookup(self.view.window()) + return window_manager.hover_enabled if window_manager else False diff --git a/plugin/session_view.py b/plugin/session_view.py index 6a205b3aa..3a6953f50 100644 --- a/plugin/session_view.py +++ b/plugin/session_view.py @@ -1,5 +1,7 @@ from .code_lens import CodeLensView from .core.active_request import ActiveRequest +from .core.constants import HOVER_HIGHLIGHT_KEY +from .core.constants import HOVER_PROVIDER_COUNT_KEY from .core.promise import Promise from .core.protocol import CodeLens from .core.protocol import CodeLensExtended @@ -7,8 +9,10 @@ from .core.protocol import DocumentUri from .core.protocol import Notification from .core.protocol import Request +from .core.registry import windows from .core.sessions import AbstractViewListener from .core.sessions import Session +from .core.settings import globalprefs from .core.settings import userprefs from .core.typing import Any, Iterable, List, Tuple, Optional, Dict, Generator from .core.views import DIAGNOSTIC_SEVERITY @@ -22,7 +26,6 @@ import sublime DIAGNOSTIC_TAG_VALUES = [v for (k, v) in DiagnosticTag.__dict__.items() if not k.startswith('_')] # type: List[int] -HOVER_HIGHLIGHT_KEY = "lsp_hover_highlight" class TagData: @@ -41,7 +44,6 @@ class SessionView: SHOW_DEFINITIONS_KEY = "show_definitions" HOVER_PROVIDER_KEY = "hoverProvider" - HOVER_PROVIDER_COUNT_KEY = "lsp_hover_provider_count" AC_TRIGGERS_KEY = "auto_complete_triggers" COMPLETION_PROVIDER_KEY = "completionProvider" TRIGGER_CHARACTERS_KEY = "completionProvider.triggerCharacters" @@ -224,20 +226,25 @@ def _apply_auto_complete_triggers( def _increment_hover_count(self) -> None: settings = self.view.settings() - count = settings.get(self.HOVER_PROVIDER_COUNT_KEY, 0) + count = settings.get(HOVER_PROVIDER_COUNT_KEY, 0) if isinstance(count, int): count += 1 - settings.set(self.HOVER_PROVIDER_COUNT_KEY, count) - settings.set(self.SHOW_DEFINITIONS_KEY, False) + settings.set(HOVER_PROVIDER_COUNT_KEY, count) + manager = windows.lookup(self.view.window()) + if manager and manager.hover_enabled: + settings.set(self.SHOW_DEFINITIONS_KEY, False) def _decrement_hover_count(self) -> None: settings = self.view.settings() - count = settings.get(self.HOVER_PROVIDER_COUNT_KEY) + count = settings.get(HOVER_PROVIDER_COUNT_KEY) if isinstance(count, int): count -= 1 if count == 0: - settings.erase(self.HOVER_PROVIDER_COUNT_KEY) - settings.set(self.SHOW_DEFINITIONS_KEY, True) + settings.erase(HOVER_PROVIDER_COUNT_KEY) + self.reset_show_definitions() + + def reset_show_definitions(self) -> None: + self.view.settings().set(self.SHOW_DEFINITIONS_KEY, globalprefs().get(self.SHOW_DEFINITIONS_KEY)) def get_uri(self) -> Optional[DocumentUri]: listener = self.listener() From 5e9215562e0f0d19ca6d46760527df867a66d1e5 Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Tue, 29 Aug 2023 20:22:13 +0200 Subject: [PATCH 2/6] Don't need this helper function --- plugin/hover.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugin/hover.py b/plugin/hover.py index fc1e0f49f..727e64822 100644 --- a/plugin/hover.py +++ b/plugin/hover.py @@ -377,7 +377,8 @@ def is_enabled(self) -> bool: return self._has_hover_provider() def is_checked(self) -> bool: - return self._is_hover_enabled() + window_manager = windows.lookup(self.view.window()) + return window_manager.hover_enabled if window_manager else False def run(self, edit: sublime.Edit) -> None: window_manager = windows.lookup(self.view.window()) @@ -392,7 +393,3 @@ def run(self, edit: sublime.Edit) -> None: def _has_hover_provider(self) -> bool: return cast(int, self.view.settings().get(HOVER_PROVIDER_COUNT_KEY, 0)) > 0 - - def _is_hover_enabled(self) -> bool: - window_manager = windows.lookup(self.view.window()) - return window_manager.hover_enabled if window_manager else False From 772a54c9e46ea86e27f13a4f7ff4854a3f1a730e Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Mon, 23 Oct 2023 17:37:23 +0200 Subject: [PATCH 3/6] Update note --- plugin/core/constants.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/core/constants.py b/plugin/core/constants.py index fa003364b..fdc24187c 100644 --- a/plugin/core/constants.py +++ b/plugin/core/constants.py @@ -1,4 +1,5 @@ -# TODO: Move all constants into this file, so that all other modules can import them without causing import loops +# TODO: Move all constants which are shared by multiple modules into this file, so that they can be imported without +# causing import loops HOVER_HIGHLIGHT_KEY = "lsp_hover_highlight" HOVER_PROVIDER_COUNT_KEY = "lsp_hover_provider_count" From 01a67cf200dbacd2b65156be49397690c69fd3e4 Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Mon, 30 Oct 2023 07:23:19 +0100 Subject: [PATCH 4/6] Make toggle survive ST restart --- plugin/core/constants.py | 9 ++++++-- plugin/core/windows.py | 1 - plugin/documents.py | 4 +++- plugin/hover.py | 45 ++++++++++++++++++++++++---------------- plugin/session_view.py | 12 +++++------ 5 files changed, 43 insertions(+), 28 deletions(-) diff --git a/plugin/core/constants.py b/plugin/core/constants.py index fdc24187c..188ea4910 100644 --- a/plugin/core/constants.py +++ b/plugin/core/constants.py @@ -1,5 +1,10 @@ # TODO: Move all constants which are shared by multiple modules into this file, so that they can be imported without # causing import loops -HOVER_HIGHLIGHT_KEY = "lsp_hover_highlight" -HOVER_PROVIDER_COUNT_KEY = "lsp_hover_provider_count" +# Keys for View.add_regions +HOVER_HIGHLIGHT_KEY = 'lsp_hover_highlight' + +# Setting keys +HOVER_ENABLED_KEY = 'lsp_show_hover_popups' +HOVER_PROVIDER_COUNT_KEY = 'lsp_hover_provider_count' +SHOW_DEFINITIONS_KEY = 'show_definitions' diff --git a/plugin/core/windows.py b/plugin/core/windows.py index 5d3e6b911..5ebebcc36 100644 --- a/plugin/core/windows.py +++ b/plugin/core/windows.py @@ -84,7 +84,6 @@ def __init__(self, window: sublime.Window, workspace: ProjectFolders, config_man self.suppress_sessions_restart_on_project_update = False self.total_error_count = 0 self.total_warning_count = 0 - self.hover_enabled = True sublime.set_timeout(functools.partial(self._update_panel_main_thread, _NO_DIAGNOSTICS_PLACEHOLDER, [])) self.panel_manager.ensure_log_panel() self._config_manager.add_change_listener(self) diff --git a/plugin/documents.py b/plugin/documents.py index e43b3869b..33afd3a57 100644 --- a/plugin/documents.py +++ b/plugin/documents.py @@ -2,6 +2,7 @@ from .code_actions import CodeActionOrCommand from .code_actions import CodeActionsByConfigName from .completion import QueryCompletionsTask +from .core.constants import HOVER_ENABLED_KEY from .core.logging import debug from .core.panels import PanelName from .core.protocol import Diagnostic @@ -466,7 +467,8 @@ def on_query_context(self, key: str, operator: int, operand: Any, match_all: boo def on_hover(self, point: int, hover_zone: int) -> None: if self.view.is_popup_visible(): return - if hover_zone == sublime.HOVER_TEXT and self._manager and self._manager.hover_enabled: + window = self.view.window() + 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 diff --git a/plugin/hover.py b/plugin/hover.py index 162dd8d00..3b82e91fc 100644 --- a/plugin/hover.py +++ b/plugin/hover.py @@ -1,8 +1,10 @@ from .code_actions import actions_manager from .code_actions import CodeActionOrCommand from .code_actions import CodeActionsByConfigName +from .core.constants import HOVER_ENABLED_KEY from .core.constants import HOVER_HIGHLIGHT_KEY from .core.constants import HOVER_PROVIDER_COUNT_KEY +from .core.constants import SHOW_DEFINITIONS_KEY from .core.open import lsp_range_from_uri_fragment from .core.open import open_file_uri from .core.open import open_in_browser @@ -390,25 +392,32 @@ def try_open_custom_uri_async(self, href: str) -> None: return -class LspToggleHoverPopupsCommand(sublime_plugin.TextCommand): +class LspToggleHoverPopupsCommand(sublime_plugin.WindowCommand): def is_enabled(self) -> bool: - return self._has_hover_provider() + view = self.window.active_view() + if view: + return self._has_hover_provider(view) + return False def is_checked(self) -> bool: - window_manager = windows.lookup(self.view.window()) - return window_manager.hover_enabled if window_manager else False - - def run(self, edit: sublime.Edit) -> None: - window_manager = windows.lookup(self.view.window()) - if window_manager: - window_manager.hover_enabled = not window_manager.hover_enabled - for session in window_manager.get_sessions(): - for session_view in session.session_views_async(): - if window_manager.hover_enabled: - session_view.view.settings().set('show_definitions', False) - else: - session_view.reset_show_definitions() - - def _has_hover_provider(self) -> bool: - return cast(int, self.view.settings().get(HOVER_PROVIDER_COUNT_KEY, 0)) > 0 + return bool(self.window.settings().get(HOVER_ENABLED_KEY, True)) + + def run(self) -> None: + enable = not self.is_checked() + self.window.settings().set(HOVER_ENABLED_KEY, enable) + sublime.set_timeout_async(partial(self._update_views_async, enable)) + + def _has_hover_provider(self, view: sublime.View) -> bool: + return cast(int, view.settings().get(HOVER_PROVIDER_COUNT_KEY, 0)) > 0 + + def _update_views_async(self, enable: bool) -> None: + window_manager = windows.lookup(self.window) + if not window_manager: + return + for session in window_manager.get_sessions(): + for session_view in session.session_views_async(): + if enable: + session_view.view.settings().set(SHOW_DEFINITIONS_KEY, False) + else: + session_view.reset_show_definitions() diff --git a/plugin/session_view.py b/plugin/session_view.py index 3a6953f50..1098a199d 100644 --- a/plugin/session_view.py +++ b/plugin/session_view.py @@ -1,7 +1,9 @@ from .code_lens import CodeLensView from .core.active_request import ActiveRequest +from .core.constants import HOVER_ENABLED_KEY from .core.constants import HOVER_HIGHLIGHT_KEY from .core.constants import HOVER_PROVIDER_COUNT_KEY +from .core.constants import SHOW_DEFINITIONS_KEY from .core.promise import Promise from .core.protocol import CodeLens from .core.protocol import CodeLensExtended @@ -9,7 +11,6 @@ from .core.protocol import DocumentUri from .core.protocol import Notification from .core.protocol import Request -from .core.registry import windows from .core.sessions import AbstractViewListener from .core.sessions import Session from .core.settings import globalprefs @@ -42,7 +43,6 @@ class SessionView: Holds state per session per view. """ - SHOW_DEFINITIONS_KEY = "show_definitions" HOVER_PROVIDER_KEY = "hoverProvider" AC_TRIGGERS_KEY = "auto_complete_triggers" COMPLETION_PROVIDER_KEY = "completionProvider" @@ -230,9 +230,9 @@ def _increment_hover_count(self) -> None: if isinstance(count, int): count += 1 settings.set(HOVER_PROVIDER_COUNT_KEY, count) - manager = windows.lookup(self.view.window()) - if manager and manager.hover_enabled: - settings.set(self.SHOW_DEFINITIONS_KEY, False) + window = self.view.window() + if window and window.settings().get(HOVER_ENABLED_KEY, True): + settings.set(SHOW_DEFINITIONS_KEY, False) def _decrement_hover_count(self) -> None: settings = self.view.settings() @@ -244,7 +244,7 @@ def _decrement_hover_count(self) -> None: self.reset_show_definitions() def reset_show_definitions(self) -> None: - self.view.settings().set(self.SHOW_DEFINITIONS_KEY, globalprefs().get(self.SHOW_DEFINITIONS_KEY)) + self.view.settings().set(SHOW_DEFINITIONS_KEY, globalprefs().get(SHOW_DEFINITIONS_KEY)) def get_uri(self) -> Optional[DocumentUri]: listener = self.listener() From 05b0ae9414111c83de9f5d0dd725492bde86f8fb Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Mon, 30 Oct 2023 10:31:51 +0100 Subject: [PATCH 5/6] Reset popups properly --- plugin/session_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/session_view.py b/plugin/session_view.py index 1098a199d..43a406611 100644 --- a/plugin/session_view.py +++ b/plugin/session_view.py @@ -244,7 +244,7 @@ def _decrement_hover_count(self) -> None: self.reset_show_definitions() def reset_show_definitions(self) -> None: - self.view.settings().set(SHOW_DEFINITIONS_KEY, globalprefs().get(SHOW_DEFINITIONS_KEY)) + self.view.settings().erase(SHOW_DEFINITIONS_KEY) def get_uri(self) -> Optional[DocumentUri]: listener = self.listener() From e3b277952af4a7366c15c253420f5c54ec2ba61d Mon Sep 17 00:00:00 2001 From: Janos Wortmann Date: Mon, 30 Oct 2023 10:33:50 +0100 Subject: [PATCH 6/6] Unused import --- plugin/session_view.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/session_view.py b/plugin/session_view.py index 43a406611..4044a375e 100644 --- a/plugin/session_view.py +++ b/plugin/session_view.py @@ -13,7 +13,6 @@ from .core.protocol import Request from .core.sessions import AbstractViewListener from .core.sessions import Session -from .core.settings import globalprefs from .core.settings import userprefs from .core.typing import Any, Iterable, List, Tuple, Optional, Dict, Generator from .core.views import DIAGNOSTIC_SEVERITY