From 7901a7c3410d46efd2aaf86e12a444a9552b848f Mon Sep 17 00:00:00 2001 From: Predrag Nikolic Date: Sat, 12 Dec 2020 23:38:15 +0100 Subject: [PATCH] propose: class Panel remove ensure_*_panel and ensure_panel and create_panel functions with a class Panel that abstracts all the interaction with the panels --- plugin/core/diagnostics.py | 7 -- plugin/core/panels.py | 147 +++++++++++++++++++--------- plugin/core/types.py | 3 - plugin/core/windows.py | 17 ++-- plugin/panels.py | 23 ++--- plugin/references.py | 29 ++---- plugin/rename.py | 29 ++---- tests/test_server_panel_circular.py | 11 +-- 8 files changed, 136 insertions(+), 130 deletions(-) diff --git a/plugin/core/diagnostics.py b/plugin/core/diagnostics.py index e03fcd97a..1bbebc221 100644 --- a/plugin/core/diagnostics.py +++ b/plugin/core/diagnostics.py @@ -1,17 +1,10 @@ -from .panels import ensure_panel from .protocol import Diagnostic from .protocol import Point from .sessions import SessionBufferProtocol -from .types import PANEL_FILE_REGEX, PANEL_LINE_REGEX from .typing import List, Tuple, Callable, Optional, Iterable import sublime -def ensure_diagnostics_panel(window: sublime.Window) -> Optional[sublime.View]: - return ensure_panel(window, "diagnostics", PANEL_FILE_REGEX, PANEL_LINE_REGEX, - "Packages/LSP/Syntaxes/Diagnostics.sublime-syntax") - - class DiagnosticsUpdateWalk(object): def begin(self) -> None: diff --git a/plugin/core/panels.py b/plugin/core/panels.py index 87e5c0024..83a28c4fa 100644 --- a/plugin/core/panels.py +++ b/plugin/core/panels.py @@ -1,9 +1,12 @@ +from re import sub from .typing import Dict, Optional, List, Generator, Tuple from .types import debounced from contextlib import contextmanager import sublime import sublime_plugin +PANEL_FILE_REGEX = r"^(?!\s+\d+:\d+)(.*)(:)$" +PANEL_LINE_REGEX = r"^\s+(\d+):(\d+)" # about 80 chars per line implies maintaining a buffer of about 40kb per window SERVER_PANEL_MAX_LINES = 500 @@ -32,11 +35,97 @@ } -class PanelName: - Diagnostics = "diagnostics" - References = "references" - Rename = "rename" - LanguageServers = "language servers" +class Panel: + def __init__(self, name: str, syntax="", file_regex="", line_regex=""): + self.name = name + self._panel = None # type: Optional[sublime.View] + self._syntax = syntax + self._file_regex = file_regex + self._line_regex = line_regex + + def view(self, w: sublime.Window) -> sublime.View: + """ Returns the view contained within the panel. """ + return self._ensure_panel(w) + + def is_open(self, w: sublime.Window) -> bool: + return w.active_panel() == "output.{}".format(self.name) + + def open(self, w: sublime.Window) -> None: + self._panel = self._ensure_panel(w) + w.run_command("show_panel", {"panel": "output.{}".format(self.name)}) + + # HACK: Focus the panel to make the next_result prev_result commands work, + # than focus back to the currently open view + current_view = w.active_view() + w.focus_view(self._panel) + w.focus_view(current_view) + + def close(self, w: sublime.Window) -> None: + self._panel = self._ensure_panel(w) + w.run_command("hide_panel", {"panel": "output.{}".format(self.name)}) + + def toggle(self, w: sublime.Window) -> None: + if self.is_open(w): + self.close(w) + else: + self.open(w) + + def update(self, w: sublime.Window, content: str) -> None: + self._panel = self._ensure_panel(w) + self._panel.run_command("lsp_update_panel", {"characters": content}) + + def clear(self, w: sublime.Window) -> None: + self._panel = self._ensure_panel(w) + self._panel.run_command("lsp_clear_panel") + + def destroy(self, w: sublime.Window) -> None: + w.destroy_output_panel("output.{}".format(self.name)) + + def _ensure_panel(self, w: sublime.Window) -> sublime.View: + panel = w.find_output_panel(self.name); + if panel: + return panel + panel = create_output_panel(w, self.name) + + if self._file_regex: + panel.settings().set("result_file_regex", self._file_regex) + if self._line_regex: + panel.settings().set("result_line_regex", self._line_regex) + if self._syntax: + panel.assign_syntax(self._syntax) + + # All our panels are read-only + panel.set_read_only(True) + return panel + + +diagnostics_panel = Panel( + "diagnostics", + syntax="Packages/LSP/Syntaxes/Diagnostics.sublime-syntax", + file_regex=PANEL_FILE_REGEX, + line_regex=PANEL_LINE_REGEX +) + +reference_panel = Panel( + "references", + syntax="Packages/LSP/Syntaxes/References.sublime-syntax", + file_regex=PANEL_FILE_REGEX, + line_regex=PANEL_LINE_REGEX +) + +language_servers_panel = Panel( + "language servers", + syntax="Packages/LSP/Syntaxes/ServerLog.sublime-syntax", +) + +rename_panel = Panel( + "rename", + syntax="Packages/LSP/Syntaxes/References.sublime-syntax", + file_regex=PANEL_FILE_REGEX, + line_regex=PANEL_LINE_REGEX +) + +lsp_panels = [diagnostics_panel, reference_panel, language_servers_panel, rename_panel] @contextmanager @@ -46,7 +135,7 @@ def mutable(view: sublime.View) -> Generator: view.set_read_only(True) -def create_output_panel(window: sublime.Window, name: str) -> Optional[sublime.View]: +def create_output_panel(window: sublime.Window, name: str) -> sublime.View: panel = window.create_output_panel(name) settings = panel.settings() for key, value in OUTPUT_PANEL_SETTINGS.items(): @@ -55,36 +144,8 @@ def create_output_panel(window: sublime.Window, name: str) -> Optional[sublime.V def destroy_output_panels(window: sublime.Window) -> None: - for field in filter(lambda a: not a.startswith('__'), PanelName.__dict__.keys()): - panel_name = getattr(PanelName, field) - panel = window.find_output_panel(panel_name) - if panel and panel.is_valid(): - panel.settings().set("syntax", "Packages/Text/Plain text.tmLanguage") - window.destroy_output_panel(panel_name) - - -def create_panel(window: sublime.Window, name: str, result_file_regex: str, result_line_regex: str, - syntax: str) -> Optional[sublime.View]: - panel = create_output_panel(window, name) - if not panel: - return None - if result_file_regex: - panel.settings().set("result_file_regex", result_file_regex) - if result_line_regex: - panel.settings().set("result_line_regex", result_line_regex) - panel.assign_syntax(syntax) - # Call create_output_panel a second time after assigning the above - # settings, so that it'll be picked up as a result buffer - # see: Packages/Default/exec.py#L228-L230 - panel = window.create_output_panel(name) - # All our panels are read-only - panel.set_read_only(True) - return panel - - -def ensure_panel(window: sublime.Window, name: str, result_file_regex: str, result_line_regex: str, - syntax: str) -> Optional[sublime.View]: - return window.find_output_panel(name) or create_panel(window, name, result_file_regex, result_line_regex, syntax) + for panel in lsp_panels: + panel.destroy(window) class LspClearPanelCommand(sublime_plugin.TextCommand): @@ -114,24 +175,20 @@ def run(self, edit: sublime.Edit, characters: Optional[str] = "") -> None: selection.clear() -def ensure_server_panel(window: sublime.Window) -> Optional[sublime.View]: - return ensure_panel(window, PanelName.LanguageServers, "", "", "Packages/LSP/Syntaxes/ServerLog.sublime-syntax") - - def update_server_panel(window: sublime.Window, prefix: str, message: str) -> None: if not window.is_valid(): return window_id = window.id() - panel = ensure_server_panel(window) - if not panel: + panel_view = language_servers_panel.view(window) + if not panel_view.is_valid(): return LspUpdateServerPanelCommand.to_be_processed.setdefault(window_id, []).append((prefix, message)) previous_length = len(LspUpdateServerPanelCommand.to_be_processed[window_id]) def condition() -> bool: - if not panel: + if not panel_view: return False - if not panel.is_valid(): + if not panel_view.is_valid(): return False to_process = LspUpdateServerPanelCommand.to_be_processed.get(window_id) if to_process is None: @@ -144,7 +201,7 @@ def condition() -> bool: return current_length == previous_length debounced( - lambda: panel.run_command("lsp_update_server_panel", {"window_id": window_id}) if panel else None, + lambda: panel_view.run_command("lsp_update_server_panel", {"window_id": window_id}) if panel_view else None, SERVER_PANEL_DEBOUNCE_TIME_MS, condition ) diff --git a/plugin/core/types.py b/plugin/core/types.py index 7fd20ef1f..c46b2988a 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -17,9 +17,6 @@ TCP_CONNECT_TIMEOUT = 5 # seconds FEATURES_TIMEOUT = 300 # milliseconds -PANEL_FILE_REGEX = r"^(?!\s+\d+:\d+)(.*)(:)$" -PANEL_LINE_REGEX = r"^\s+(\d+):(\d+)" - def basescope2languageid(base_scope: str) -> str: # This the connection between Language IDs and ST selectors. diff --git a/plugin/core/windows.py b/plugin/core/windows.py index f55acf522..6d1fea505 100644 --- a/plugin/core/windows.py +++ b/plugin/core/windows.py @@ -3,11 +3,10 @@ from .configurations import WindowConfigManager from .diagnostics import DiagnosticsCursor from .diagnostics import DiagnosticsWalker -from .diagnostics import ensure_diagnostics_panel from .logging import debug from .logging import exception_log from .message_request_handler import MessageRequestHandler -from .panels import update_server_panel +from .panels import update_server_panel, diagnostics_panel from .protocol import Diagnostic from .protocol import Error from .protocol import Point @@ -440,19 +439,19 @@ def update_diagnostics_panel_async(self) -> None: set_diagnostics_count(listener.view, self.total_error_count, self.total_warning_count) def update() -> None: - panel = ensure_diagnostics_panel(self._window) - if not panel or not panel.is_valid(): + panel_view = diagnostics_panel.view(self._window) + if not panel_view.is_valid(): return if isinstance(base_dir, str): - panel.settings().set("result_base_dir", base_dir) + panel_view.settings().set("result_base_dir", base_dir) else: - panel.settings().erase("result_base_dir") - panel.run_command("lsp_update_panel", {"characters": to_render}) + panel_view.settings().erase("result_base_dir") + diagnostics_panel.update(self._window, to_render) if self._panel_code_phantoms is None: - self._panel_code_phantoms = sublime.PhantomSet(panel, "hrefs") + self._panel_code_phantoms = sublime.PhantomSet(panel_view, "hrefs") phantoms = [] # type: List[sublime.Phantom] for row, col, code, href in prephantoms: - point = panel.text_point(row, col) + point = panel_view.text_point(row, col) region = sublime.Region(point, point) phantoms.append(sublime.Phantom(region, make_link(href, code), sublime.LAYOUT_INLINE)) self._panel_code_phantoms.update(phantoms) diff --git a/plugin/panels.py b/plugin/panels.py index 56028ea2f..d3610bc23 100644 --- a/plugin/panels.py +++ b/plugin/panels.py @@ -1,23 +1,12 @@ -from .core.diagnostics import ensure_diagnostics_panel -from .core.panels import ensure_server_panel -from .core.panels import PanelName -from sublime import Window -from sublime_plugin import WindowCommand +from .core.panels import diagnostics_panel, language_servers_panel +import sublime_plugin -def toggle_output_panel(window: Window, panel_type: str) -> None: - panel_name = "output.{}".format(panel_type) - command = "{}_panel".format("hide" if window.active_panel() == panel_name else "show") - window.run_command(command, {"panel": panel_name}) - - -class LspToggleServerPanelCommand(WindowCommand): +class LspToggleServerPanelCommand(sublime_plugin.WindowCommand): def run(self) -> None: - ensure_server_panel(self.window) - toggle_output_panel(self.window, PanelName.LanguageServers) + language_servers_panel.toggle(self.window) -class LspShowDiagnosticsPanelCommand(WindowCommand): +class LspShowDiagnosticsPanelCommand(sublime_plugin.WindowCommand): def run(self) -> None: - ensure_diagnostics_panel(self.window) - toggle_output_panel(self.window, PanelName.Diagnostics) + diagnostics_panel.toggle(self.window) diff --git a/plugin/references.py b/plugin/references.py index 099ebf303..2ec8b9ff2 100644 --- a/plugin/references.py +++ b/plugin/references.py @@ -2,14 +2,13 @@ import sublime import linecache -from .core.panels import ensure_panel +from .core.panels import reference_panel from .core.protocol import Request, Point from .core.registry import get_position from .core.registry import LspTextCommand from .core.registry import windows from .core.settings import PLUGIN_NAME from .core.settings import userprefs -from .core.types import PANEL_FILE_REGEX, PANEL_LINE_REGEX from .core.typing import List, Dict, Optional, Tuple, TypedDict from .core.url import uri_to_filename from .core.views import get_line, text_document_position_params @@ -17,11 +16,6 @@ ReferenceDict = TypedDict('ReferenceDict', {'uri': str, 'range': dict}) -def ensure_references_panel(window: sublime.Window) -> 'Optional[sublime.View]': - return ensure_panel(window, "references", PANEL_FILE_REGEX, PANEL_LINE_REGEX, - "Packages/" + PLUGIN_NAME + "/Syntaxes/References.sublime-syntax") - - class LspSymbolReferencesCommand(LspTextCommand): capability = 'referencesProvider' @@ -117,11 +111,11 @@ def open_ref_index(self, index: int, transient: bool = False) -> None: def show_references_panel(self, references_by_file: Dict[str, List[Tuple[Point, str]]]) -> None: window = self.view.window() if window: - panel = ensure_references_panel(window) - if not panel: + panel_view = reference_panel.view(window) + if not panel_view.is_valid(): return - text = '' + text = "" references_count = 0 for file, references in references_by_file.items(): text += '{}:\n'.format(self.get_relative_path(file)) @@ -133,19 +127,14 @@ def show_references_panel(self, references_by_file: Dict[str, List[Tuple[Point, text += '\n' base_dir = windows.lookup(window).get_project_path(self.view.file_name() or "") - panel.settings().set("result_base_dir", base_dir) + panel_view.settings().set("result_base_dir", base_dir) - panel.run_command("lsp_clear_panel") - window.run_command("show_panel", {"panel": "output.references"}) - panel.run_command('append', { - 'characters': "{} references for '{}'\n\n{}".format(references_count, self.word, text), - 'force': True, - 'scroll_to_end': False - }) + reference_panel.update(window, "{} references for '{}'\n\n{}".format(references_count, self.word, text)) + reference_panel.open(window) # highlight all word occurrences - regions = panel.find_all(r"\b{}\b".format(self.word)) - panel.add_regions('ReferenceHighlight', regions, 'comment', flags=sublime.DRAW_OUTLINED) + regions = panel_view.find_all(r"\b{}\b".format(self.word)) + panel_view.add_regions('ReferenceHighlight', regions, 'comment', flags=sublime.DRAW_OUTLINED) def get_selected_file_path(self, index: int) -> str: return self.get_full_path(self.reflist[index][0]) diff --git a/plugin/rename.py b/plugin/rename.py index 0af0af49f..11c62f264 100644 --- a/plugin/rename.py +++ b/plugin/rename.py @@ -1,14 +1,12 @@ from .core.edit import apply_workspace_edit from .core.edit import parse_workspace_edit from .core.edit import TextEdit -from .core.panels import ensure_panel -from .core.panels import PanelName +from .core.panels import rename_panel from .core.protocol import Range from .core.protocol import Request from .core.registry import get_position from .core.registry import LspTextCommand from .core.registry import windows -from .core.types import PANEL_FILE_REGEX, PANEL_LINE_REGEX from .core.typing import Any, Optional, Dict, List from .core.views import range_to_region, get_line from .core.views import text_document_position_params @@ -158,8 +156,8 @@ def _render_rename_panel(self, changes: Dict[str, List[TextEdit]], total_changes window = self.view.window() if not window: return - panel = ensure_rename_panel(window) - if not panel: + panel_view = rename_panel.view(window) + if not panel_view.is_valid(): return text = '' for file, file_changes in changes.items(): @@ -171,22 +169,7 @@ def _render_rename_panel(self, changes: Dict[str, List[TextEdit]], total_changes # append a new line after each file name text += '\n' base_dir = windows.lookup(window).get_project_path(self.view.file_name() or "") - panel.settings().set("result_base_dir", base_dir) - panel.run_command("lsp_clear_panel") - window.run_command("show_panel", {"panel": "output.rename"}) + panel_view.settings().set("result_base_dir", base_dir) fmt = "{} changes across {} files.\n\n{}" - panel.run_command('append', { - 'characters': fmt.format(total_changes, file_count, text), - 'force': True, - 'scroll_to_end': False - }) - - -def ensure_rename_panel(window: sublime.Window) -> Optional[sublime.View]: - return ensure_panel( - window=window, - name=PanelName.Rename, - result_file_regex=PANEL_FILE_REGEX, - result_line_regex=PANEL_LINE_REGEX, - syntax="Packages/LSP/Syntaxes/References.sublime-syntax" - ) + rename_panel.update(window, fmt.format(total_changes, file_count, text)) + rename_panel.open(window) diff --git a/tests/test_server_panel_circular.py b/tests/test_server_panel_circular.py index 732142056..f652579d8 100644 --- a/tests/test_server_panel_circular.py +++ b/tests/test_server_panel_circular.py @@ -1,7 +1,6 @@ -from LSP.plugin.core.panels import ensure_server_panel from LSP.plugin.core.panels import SERVER_PANEL_DEBOUNCE_TIME_MS from LSP.plugin.core.panels import SERVER_PANEL_MAX_LINES -from LSP.plugin.core.panels import update_server_panel +from LSP.plugin.core.panels import update_server_panel, language_servers_panel from unittesting import DeferrableTestCase import sublime @@ -15,12 +14,12 @@ def setUp(self): self.skipTest("window is None!") return self.view = self.window.active_view() - panel = ensure_server_panel(self.window) - if panel is None: + panel_view = language_servers_panel.view(self.window) + if not panel_view.is_valid(): self.skipTest("panel is None!") return - panel.run_command("lsp_clear_panel") - self.panel = panel + language_servers_panel.clear(self.window) + self.panel = panel_view def assert_total_lines_equal(self, expected_total_lines): actual_total_lines = len(self.panel.split_by_newlines(sublime.Region(0, self.panel.size())))