From d263878631a0b1763aed22e1ed46b52c63c2396b Mon Sep 17 00:00:00 2001 From: Predrag Nikolic Date: Sat, 12 Dec 2020 10:17:56 +0100 Subject: [PATCH 01/15] make reference panel consistent with diagnostic panel --- Syntaxes/References.sublime-syntax | 9 ++++----- plugin/references.py | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Syntaxes/References.sublime-syntax b/Syntaxes/References.sublime-syntax index 46d642fdf..91d8229b9 100644 --- a/Syntaxes/References.sublime-syntax +++ b/Syntaxes/References.sublime-syntax @@ -6,8 +6,8 @@ hidden: true scope: output.lsp.references variables: - start_of_reference_body: ^\s+(?=\d) - filename_and_colon: ^\s*(\S)\s+(.*)(:)$ + start_of_reference_body: ^\s*(?=\d) + filename_and_colon: ^(?!\s*\d+:\d+)(.*)(:)$ contexts: main: @@ -18,9 +18,8 @@ contexts: - match: '{{filename_and_colon}}' captures: 0: meta.reference.preamble.lsp - 1: punctuation.section.references.preample.lsp - 2: string.unquoted.lsp entity.name.file.references.lsp - 3: punctuation.separator.lsp + 1: string.unquoted.lsp entity.name.file.references.lsp + 2: punctuation.separator.lsp references-body: - match: '{{start_of_reference_body}}' diff --git a/plugin/references.py b/plugin/references.py index 749ed3015..bed088d0f 100644 --- a/plugin/references.py +++ b/plugin/references.py @@ -17,7 +17,7 @@ def ensure_references_panel(window: sublime.Window) -> 'Optional[sublime.View]': - return ensure_panel(window, "references", r"^\s*\S\s+(\S.*):$", r"^\s+([0-9]+):?([0-9]+).*$", + return ensure_panel(window, "references", r"^(?!\s*\d+:\d+)(.*)(:)$", r"^\s*(\d+):(\d+)", "Packages/" + PLUGIN_NAME + "/Syntaxes/References.sublime-syntax") @@ -123,11 +123,11 @@ def show_references_panel(self, references_by_file: Dict[str, List[Tuple[Point, text = '' references_count = 0 for file, references in references_by_file.items(): - text += '◌ {}:\n'.format(self.get_relative_path(file)) + text += '{}:\n'.format(self.get_relative_path(file)) for reference in references: references_count += 1 point, line = reference - text += '\t{:>8}:{:<4} {}\n'.format(point.row + 1, point.col + 1, line) + text += '{:>4}:{:<4} {}\n'.format(point.row + 1, point.col + 1, line) # append a new line after each file name text += '\n' From 82d4bae6585476f817940386e551de3923d910ce Mon Sep 17 00:00:00 2001 From: Predrag Nikolic Date: Sat, 12 Dec 2020 10:31:14 +0100 Subject: [PATCH 02/15] indent the lines with a tab file_regex will not have a space in front line_regex will have a space in front to tell them appart --- Syntaxes/Diagnostics.sublime-syntax | 4 ++-- Syntaxes/References.sublime-syntax | 4 ++-- plugin/core/diagnostics.py | 2 +- plugin/core/views.py | 2 +- plugin/references.py | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Syntaxes/Diagnostics.sublime-syntax b/Syntaxes/Diagnostics.sublime-syntax index 9b071f506..f8525b703 100644 --- a/Syntaxes/Diagnostics.sublime-syntax +++ b/Syntaxes/Diagnostics.sublime-syntax @@ -11,14 +11,14 @@ contexts: - include: line file: - - match: ^(?!\s*\d+:\d+)(.*)(:)$ + - match: ^(?!\s+\d+:\d+)(.*)(:)$ captures: 0: meta.diagnostic.preamble.lsp 1: string.unquoted.lsp 2: punctuation.separator.lsp line: - - match: ^\s*(?=\d) + - match: ^\s+(?=\d) push: - ensure-diag-meta-scope - expect-source-and-code diff --git a/Syntaxes/References.sublime-syntax b/Syntaxes/References.sublime-syntax index 91d8229b9..fd227ae56 100644 --- a/Syntaxes/References.sublime-syntax +++ b/Syntaxes/References.sublime-syntax @@ -6,8 +6,8 @@ hidden: true scope: output.lsp.references variables: - start_of_reference_body: ^\s*(?=\d) - filename_and_colon: ^(?!\s*\d+:\d+)(.*)(:)$ + start_of_reference_body: ^\s+(?=\d) + filename_and_colon: ^(?!\s+\d+:\d+)(.*)(:)$ contexts: main: diff --git a/plugin/core/diagnostics.py b/plugin/core/diagnostics.py index 96986b433..f0f6ba4ca 100644 --- a/plugin/core/diagnostics.py +++ b/plugin/core/diagnostics.py @@ -7,7 +7,7 @@ def ensure_diagnostics_panel(window: sublime.Window) -> Optional[sublime.View]: - return ensure_panel(window, "diagnostics", r"^(.*):$", r"^\s*(\d+):(\d+)", + return ensure_panel(window, "diagnostics", r"(?!\s+\d+:\d+)(.*)(:)$", r"^\s+(\d+):(\d+)", "Packages/LSP/Syntaxes/Diagnostics.sublime-syntax") diff --git a/plugin/core/views.py b/plugin/core/views.py index 4b7bc1ac8..f11e0b561 100644 --- a/plugin/core/views.py +++ b/plugin/core/views.py @@ -564,7 +564,7 @@ def format_diagnostic_for_panel(diagnostic: Diagnostic) -> Tuple[str, Optional[i formatted.append(code) lines = diagnostic.message.splitlines() or [""] # \u200B is the zero-width space - result = "{:>4}:{:<4}{:<8}{} \u200B{}".format( + result = "\t{:>4}:{:<4}{:<8}{} \u200B{}".format( diagnostic.range.start.row + 1, diagnostic.range.start.col + 1, format_severity(diagnostic.severity), diff --git a/plugin/references.py b/plugin/references.py index bed088d0f..a8cc29dd3 100644 --- a/plugin/references.py +++ b/plugin/references.py @@ -17,7 +17,7 @@ def ensure_references_panel(window: sublime.Window) -> 'Optional[sublime.View]': - return ensure_panel(window, "references", r"^(?!\s*\d+:\d+)(.*)(:)$", r"^\s*(\d+):(\d+)", + return ensure_panel(window, "references", r"^(?!\s+\d+:\d+)(.*)(:)$", r"^\s+(\d+):(\d+)", "Packages/" + PLUGIN_NAME + "/Syntaxes/References.sublime-syntax") @@ -127,7 +127,7 @@ def show_references_panel(self, references_by_file: Dict[str, List[Tuple[Point, for reference in references: references_count += 1 point, line = reference - text += '{:>4}:{:<4} {}\n'.format(point.row + 1, point.col + 1, line) + text += '\t{:>4}:{:<4} {}\n'.format(point.row + 1, point.col + 1, line) # append a new line after each file name text += '\n' From 71f3e2a14c8ae18d229be240a47d2766bb43f5b8 Mon Sep 17 00:00:00 2001 From: Predrag Nikolic Date: Sat, 12 Dec 2020 16:17:52 +0100 Subject: [PATCH 03/15] feat: make rename panel consistent with reference panel and show line contents --- plugin/rename.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/plugin/rename.py b/plugin/rename.py index 1dc3d6460..311fa9282 100644 --- a/plugin/rename.py +++ b/plugin/rename.py @@ -9,7 +9,7 @@ from .core.registry import LspTextCommand from .core.registry import windows from .core.typing import Any, Optional, Dict, List -from .core.views import range_to_region +from .core.views import range_to_region, get_line from .core.views import text_document_position_params import os import sublime @@ -162,17 +162,18 @@ def _render_rename_panel(self, changes: Dict[str, List[TextEdit]], total_changes return text = '' for file, file_changes in changes.items(): - text += '◌ {}:\n'.format(self._get_relative_path(file)) + text += '{}:\n'.format(self._get_relative_path(file)) for edit in file_changes: start = edit[0] - text += '\t{:>8}:{}\n'.format(start[0] + 1, start[1] + 1) + line_content = get_line(self.view.window(), file, start[0]) + text += '\t{:>4}:{:<4} {}\n'.format(start[0] + 1, start[1] + 1, line_content) # 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"}) - fmt = "{} changes across {} files. Double-click on a row:col number to jump to that location.\n\n{}" + fmt = "{} changes across {} files.\n\n{}" panel.run_command('append', { 'characters': fmt.format(total_changes, file_count, text), 'force': True, @@ -184,7 +185,7 @@ def ensure_rename_panel(window: sublime.Window) -> Optional[sublime.View]: return ensure_panel( window=window, name=PanelName.Rename, - result_file_regex=r"^\s*\S\s+(\S.*):$", - result_line_regex=r"^\s*([0-9]+):([0-9]+)\s*$", - syntax="Packages/LSP/Syntaxes/Rename.sublime-syntax" + result_file_regex=r"^(?!\s+\d+:\d+)(.*)(:)$", + result_line_regex=r"^\s+(\d+):(\d+)", + syntax="Packages/LSP/Syntaxes/References.sublime-syntax" ) From 2895ba04625239c53d63f443f3a821c5c2a8507d Mon Sep 17 00:00:00 2001 From: Predrag Nikolic Date: Sat, 12 Dec 2020 16:19:22 +0100 Subject: [PATCH 04/15] add a new line separator between filenames --- plugin/core/views.py | 2 +- plugin/core/windows.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/plugin/core/views.py b/plugin/core/views.py index f11e0b561..aaefcf2ef 100644 --- a/plugin/core/views.py +++ b/plugin/core/views.py @@ -564,7 +564,7 @@ def format_diagnostic_for_panel(diagnostic: Diagnostic) -> Tuple[str, Optional[i formatted.append(code) lines = diagnostic.message.splitlines() or [""] # \u200B is the zero-width space - result = "\t{:>4}:{:<4}{:<8}{} \u200B{}".format( + result = "\t{:>4}:{:<4}{:<8}{} \u200B{}\n".format( diagnostic.range.start.row + 1, diagnostic.range.start.col + 1, format_severity(diagnostic.severity), diff --git a/plugin/core/windows.py b/plugin/core/windows.py index f0b39f18b..f55acf522 100644 --- a/plugin/core/windows.py +++ b/plugin/core/windows.py @@ -410,7 +410,7 @@ def handle_show_message(self, session: Session, params: Any) -> None: sublime.status_message("{}: {}".format(session.config.name, extract_message(params))) def update_diagnostics_panel_async(self) -> None: - to_render = [] # type: List[str] + to_render = "" # type: str base_dir = None self.total_error_count = 0 self.total_warning_count = 0 @@ -427,16 +427,17 @@ def update_diagnostics_panel_async(self) -> None: file_path = listener.view.file_name() or "" base_dir = self.get_project_path(file_path) # What about different base dirs for multiple folders? file_path = os.path.relpath(file_path, base_dir) if base_dir else file_path - to_render.append("{}:".format(file_path)) + to_render += "{}:\n".format(file_path) row += 1 for content, offset, code, href in contribution: - to_render.append(content) + to_render += content if offset is not None and code is not None and href is not None: prephantoms.append((row, offset, code, href)) row += content.count("\n") + 1 + # append a new line after each file name + to_render += '\n' for listener in listeners: set_diagnostics_count(listener.view, self.total_error_count, self.total_warning_count) - characters = "\n".join(to_render) def update() -> None: panel = ensure_diagnostics_panel(self._window) @@ -446,7 +447,7 @@ def update() -> None: panel.settings().set("result_base_dir", base_dir) else: panel.settings().erase("result_base_dir") - panel.run_command("lsp_update_panel", {"characters": characters}) + panel.run_command("lsp_update_panel", {"characters": to_render}) if self._panel_code_phantoms is None: self._panel_code_phantoms = sublime.PhantomSet(panel, "hrefs") phantoms = [] # type: List[sublime.Phantom] From 4c192a4c2c265650381858637d0a24f6e3efcaac Mon Sep 17 00:00:00 2001 From: Predrag Nikolic Date: Sat, 12 Dec 2020 16:47:10 +0100 Subject: [PATCH 05/15] extract regexes to constants --- plugin/core/diagnostics.py | 3 ++- plugin/core/types.py | 2 ++ plugin/references.py | 3 ++- plugin/rename.py | 5 +++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/plugin/core/diagnostics.py b/plugin/core/diagnostics.py index f0f6ba4ca..e03fcd97a 100644 --- a/plugin/core/diagnostics.py +++ b/plugin/core/diagnostics.py @@ -2,12 +2,13 @@ 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", r"(?!\s+\d+:\d+)(.*)(:)$", r"^\s+(\d+):(\d+)", + return ensure_panel(window, "diagnostics", PANEL_FILE_REGEX, PANEL_LINE_REGEX, "Packages/LSP/Syntaxes/Diagnostics.sublime-syntax") diff --git a/plugin/core/types.py b/plugin/core/types.py index c46b2988a..562f43608 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -17,6 +17,8 @@ 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/references.py b/plugin/references.py index a8cc29dd3..099ebf303 100644 --- a/plugin/references.py +++ b/plugin/references.py @@ -9,6 +9,7 @@ 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,7 +18,7 @@ def ensure_references_panel(window: sublime.Window) -> 'Optional[sublime.View]': - return ensure_panel(window, "references", r"^(?!\s+\d+:\d+)(.*)(:)$", r"^\s+(\d+):(\d+)", + return ensure_panel(window, "references", PANEL_FILE_REGEX, PANEL_LINE_REGEX, "Packages/" + PLUGIN_NAME + "/Syntaxes/References.sublime-syntax") diff --git a/plugin/rename.py b/plugin/rename.py index 311fa9282..0af0af49f 100644 --- a/plugin/rename.py +++ b/plugin/rename.py @@ -8,6 +8,7 @@ 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 @@ -185,7 +186,7 @@ def ensure_rename_panel(window: sublime.Window) -> Optional[sublime.View]: return ensure_panel( window=window, name=PanelName.Rename, - result_file_regex=r"^(?!\s+\d+:\d+)(.*)(:)$", - result_line_regex=r"^\s+(\d+):(\d+)", + result_file_regex=PANEL_FILE_REGEX, + result_line_regex=PANEL_LINE_REGEX, syntax="Packages/LSP/Syntaxes/References.sublime-syntax" ) From f6e17d36c51160baf787d5207451aeef8a432c69 Mon Sep 17 00:00:00 2001 From: Predrag Nikolic Date: Sat, 12 Dec 2020 17:28:09 +0100 Subject: [PATCH 06/15] make pyflakes happy --- plugin/core/types.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/core/types.py b/plugin/core/types.py index 562f43608..7fd20ef1f 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -20,6 +20,7 @@ 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. base_scope_map = sublime.load_settings("language-ids.sublime-settings") From 2a962e0a9ebc9ca06b1b235ae2f426120c9091c4 Mon Sep 17 00:00:00 2001 From: Predrag Nikolic Date: Sat, 12 Dec 2020 23:38:15 +0100 Subject: [PATCH 07/15] 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 | 149 +++++++++++++++++++--------- 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, 138 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..78a24ed76 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,99 @@ } -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: + self._panel = self._ensure_panel(w) + self._panel.settings().set("syntax", "Packages/Text/Plain text.tmLanguage") + w.destroy_output_panel(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 +137,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 +146,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 +177,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 +203,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()))) From d4ec11ec278a6d9cd2f33d196b696faec2ef8a35 Mon Sep 17 00:00:00 2001 From: Predrag Nikolic Date: Sat, 12 Dec 2020 23:54:08 +0100 Subject: [PATCH 08/15] Revert "propose: class Panel" This reverts commit 2a962e0a9ebc9ca06b1b235ae2f426120c9091c4. --- plugin/core/diagnostics.py | 7 ++ plugin/core/panels.py | 149 +++++++++------------------- 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, 130 insertions(+), 138 deletions(-) diff --git a/plugin/core/diagnostics.py b/plugin/core/diagnostics.py index 1bbebc221..e03fcd97a 100644 --- a/plugin/core/diagnostics.py +++ b/plugin/core/diagnostics.py @@ -1,10 +1,17 @@ +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 78a24ed76..87e5c0024 100644 --- a/plugin/core/panels.py +++ b/plugin/core/panels.py @@ -1,12 +1,9 @@ -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 @@ -35,99 +32,11 @@ } -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: - self._panel = self._ensure_panel(w) - self._panel.settings().set("syntax", "Packages/Text/Plain text.tmLanguage") - w.destroy_output_panel(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] +class PanelName: + Diagnostics = "diagnostics" + References = "references" + Rename = "rename" + LanguageServers = "language servers" @contextmanager @@ -137,7 +46,7 @@ def mutable(view: sublime.View) -> Generator: view.set_read_only(True) -def create_output_panel(window: sublime.Window, name: str) -> sublime.View: +def create_output_panel(window: sublime.Window, name: str) -> Optional[sublime.View]: panel = window.create_output_panel(name) settings = panel.settings() for key, value in OUTPUT_PANEL_SETTINGS.items(): @@ -146,8 +55,36 @@ def create_output_panel(window: sublime.Window, name: str) -> sublime.View: def destroy_output_panels(window: sublime.Window) -> None: - for panel in lsp_panels: - panel.destroy(window) + 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) class LspClearPanelCommand(sublime_plugin.TextCommand): @@ -177,20 +114,24 @@ 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_view = language_servers_panel.view(window) - if not panel_view.is_valid(): + panel = ensure_server_panel(window) + if not panel: 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_view: + if not panel: return False - if not panel_view.is_valid(): + if not panel.is_valid(): return False to_process = LspUpdateServerPanelCommand.to_be_processed.get(window_id) if to_process is None: @@ -203,7 +144,7 @@ def condition() -> bool: return current_length == previous_length debounced( - lambda: panel_view.run_command("lsp_update_server_panel", {"window_id": window_id}) if panel_view else None, + lambda: panel.run_command("lsp_update_server_panel", {"window_id": window_id}) if panel else None, SERVER_PANEL_DEBOUNCE_TIME_MS, condition ) diff --git a/plugin/core/types.py b/plugin/core/types.py index c46b2988a..7fd20ef1f 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -17,6 +17,9 @@ 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 6d1fea505..f55acf522 100644 --- a/plugin/core/windows.py +++ b/plugin/core/windows.py @@ -3,10 +3,11 @@ 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, diagnostics_panel +from .panels import update_server_panel from .protocol import Diagnostic from .protocol import Error from .protocol import Point @@ -439,19 +440,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_view = diagnostics_panel.view(self._window) - if not panel_view.is_valid(): + panel = ensure_diagnostics_panel(self._window) + if not panel or not panel.is_valid(): return if isinstance(base_dir, str): - panel_view.settings().set("result_base_dir", base_dir) + panel.settings().set("result_base_dir", base_dir) else: - panel_view.settings().erase("result_base_dir") - diagnostics_panel.update(self._window, to_render) + panel.settings().erase("result_base_dir") + panel.run_command("lsp_update_panel", {"characters": to_render}) if self._panel_code_phantoms is None: - self._panel_code_phantoms = sublime.PhantomSet(panel_view, "hrefs") + self._panel_code_phantoms = sublime.PhantomSet(panel, "hrefs") phantoms = [] # type: List[sublime.Phantom] for row, col, code, href in prephantoms: - point = panel_view.text_point(row, col) + point = panel.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 d3610bc23..56028ea2f 100644 --- a/plugin/panels.py +++ b/plugin/panels.py @@ -1,12 +1,23 @@ -from .core.panels import diagnostics_panel, language_servers_panel -import sublime_plugin +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 -class LspToggleServerPanelCommand(sublime_plugin.WindowCommand): +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): def run(self) -> None: - language_servers_panel.toggle(self.window) + ensure_server_panel(self.window) + toggle_output_panel(self.window, PanelName.LanguageServers) -class LspShowDiagnosticsPanelCommand(sublime_plugin.WindowCommand): +class LspShowDiagnosticsPanelCommand(WindowCommand): def run(self) -> None: - diagnostics_panel.toggle(self.window) + ensure_diagnostics_panel(self.window) + toggle_output_panel(self.window, PanelName.Diagnostics) diff --git a/plugin/references.py b/plugin/references.py index 2ec8b9ff2..099ebf303 100644 --- a/plugin/references.py +++ b/plugin/references.py @@ -2,13 +2,14 @@ import sublime import linecache -from .core.panels import reference_panel +from .core.panels import ensure_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 @@ -16,6 +17,11 @@ 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' @@ -111,11 +117,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_view = reference_panel.view(window) - if not panel_view.is_valid(): + panel = ensure_references_panel(window) + if not panel: return - text = "" + text = '' references_count = 0 for file, references in references_by_file.items(): text += '{}:\n'.format(self.get_relative_path(file)) @@ -127,14 +133,19 @@ 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_view.settings().set("result_base_dir", base_dir) + panel.settings().set("result_base_dir", base_dir) - reference_panel.update(window, "{} references for '{}'\n\n{}".format(references_count, self.word, text)) - reference_panel.open(window) + 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 + }) # highlight all word occurrences - regions = panel_view.find_all(r"\b{}\b".format(self.word)) - panel_view.add_regions('ReferenceHighlight', regions, 'comment', flags=sublime.DRAW_OUTLINED) + regions = panel.find_all(r"\b{}\b".format(self.word)) + panel.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 11c62f264..0af0af49f 100644 --- a/plugin/rename.py +++ b/plugin/rename.py @@ -1,12 +1,14 @@ from .core.edit import apply_workspace_edit from .core.edit import parse_workspace_edit from .core.edit import TextEdit -from .core.panels import rename_panel +from .core.panels import ensure_panel +from .core.panels import PanelName 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 @@ -156,8 +158,8 @@ def _render_rename_panel(self, changes: Dict[str, List[TextEdit]], total_changes window = self.view.window() if not window: return - panel_view = rename_panel.view(window) - if not panel_view.is_valid(): + panel = ensure_rename_panel(window) + if not panel: return text = '' for file, file_changes in changes.items(): @@ -169,7 +171,22 @@ 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_view.settings().set("result_base_dir", base_dir) + panel.settings().set("result_base_dir", base_dir) + panel.run_command("lsp_clear_panel") + window.run_command("show_panel", {"panel": "output.rename"}) fmt = "{} changes across {} files.\n\n{}" - rename_panel.update(window, fmt.format(total_changes, file_count, text)) - rename_panel.open(window) + 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" + ) diff --git a/tests/test_server_panel_circular.py b/tests/test_server_panel_circular.py index f652579d8..732142056 100644 --- a/tests/test_server_panel_circular.py +++ b/tests/test_server_panel_circular.py @@ -1,6 +1,7 @@ +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, language_servers_panel +from LSP.plugin.core.panels import update_server_panel from unittesting import DeferrableTestCase import sublime @@ -14,12 +15,12 @@ def setUp(self): self.skipTest("window is None!") return self.view = self.window.active_view() - panel_view = language_servers_panel.view(self.window) - if not panel_view.is_valid(): + panel = ensure_server_panel(self.window) + if panel is None: self.skipTest("panel is None!") return - language_servers_panel.clear(self.window) - self.panel = panel_view + panel.run_command("lsp_clear_panel") + self.panel = panel def assert_total_lines_equal(self, expected_total_lines): actual_total_lines = len(self.panel.split_by_newlines(sublime.Region(0, self.panel.size()))) From 02d6859f4b0c6703a1f8a36bcaee7e0be41566d4 Mon Sep 17 00:00:00 2001 From: Predrag Date: Tue, 15 Dec 2020 23:08:45 +0100 Subject: [PATCH 09/15] don't require \t and use "".join() instead of += --- Syntaxes/Diagnostics.sublime-syntax | 4 ++-- Syntaxes/References.sublime-syntax | 4 ++-- plugin/core/views.py | 2 +- plugin/core/windows.py | 11 +++++------ plugin/references.py | 12 +++++------- plugin/rename.py | 11 +++++------ 6 files changed, 20 insertions(+), 24 deletions(-) diff --git a/Syntaxes/Diagnostics.sublime-syntax b/Syntaxes/Diagnostics.sublime-syntax index f8525b703..9b071f506 100644 --- a/Syntaxes/Diagnostics.sublime-syntax +++ b/Syntaxes/Diagnostics.sublime-syntax @@ -11,14 +11,14 @@ contexts: - include: line file: - - match: ^(?!\s+\d+:\d+)(.*)(:)$ + - match: ^(?!\s*\d+:\d+)(.*)(:)$ captures: 0: meta.diagnostic.preamble.lsp 1: string.unquoted.lsp 2: punctuation.separator.lsp line: - - match: ^\s+(?=\d) + - match: ^\s*(?=\d) push: - ensure-diag-meta-scope - expect-source-and-code diff --git a/Syntaxes/References.sublime-syntax b/Syntaxes/References.sublime-syntax index fd227ae56..91d8229b9 100644 --- a/Syntaxes/References.sublime-syntax +++ b/Syntaxes/References.sublime-syntax @@ -6,8 +6,8 @@ hidden: true scope: output.lsp.references variables: - start_of_reference_body: ^\s+(?=\d) - filename_and_colon: ^(?!\s+\d+:\d+)(.*)(:)$ + start_of_reference_body: ^\s*(?=\d) + filename_and_colon: ^(?!\s*\d+:\d+)(.*)(:)$ contexts: main: diff --git a/plugin/core/views.py b/plugin/core/views.py index aaefcf2ef..4b7bc1ac8 100644 --- a/plugin/core/views.py +++ b/plugin/core/views.py @@ -564,7 +564,7 @@ def format_diagnostic_for_panel(diagnostic: Diagnostic) -> Tuple[str, Optional[i formatted.append(code) lines = diagnostic.message.splitlines() or [""] # \u200B is the zero-width space - result = "\t{:>4}:{:<4}{:<8}{} \u200B{}\n".format( + result = "{:>4}:{:<4}{:<8}{} \u200B{}".format( diagnostic.range.start.row + 1, diagnostic.range.start.col + 1, format_severity(diagnostic.severity), diff --git a/plugin/core/windows.py b/plugin/core/windows.py index f55acf522..f0b39f18b 100644 --- a/plugin/core/windows.py +++ b/plugin/core/windows.py @@ -410,7 +410,7 @@ def handle_show_message(self, session: Session, params: Any) -> None: sublime.status_message("{}: {}".format(session.config.name, extract_message(params))) def update_diagnostics_panel_async(self) -> None: - to_render = "" # type: str + to_render = [] # type: List[str] base_dir = None self.total_error_count = 0 self.total_warning_count = 0 @@ -427,17 +427,16 @@ def update_diagnostics_panel_async(self) -> None: file_path = listener.view.file_name() or "" base_dir = self.get_project_path(file_path) # What about different base dirs for multiple folders? file_path = os.path.relpath(file_path, base_dir) if base_dir else file_path - to_render += "{}:\n".format(file_path) + to_render.append("{}:".format(file_path)) row += 1 for content, offset, code, href in contribution: - to_render += content + to_render.append(content) if offset is not None and code is not None and href is not None: prephantoms.append((row, offset, code, href)) row += content.count("\n") + 1 - # append a new line after each file name - to_render += '\n' for listener in listeners: set_diagnostics_count(listener.view, self.total_error_count, self.total_warning_count) + characters = "\n".join(to_render) def update() -> None: panel = ensure_diagnostics_panel(self._window) @@ -447,7 +446,7 @@ def update() -> None: panel.settings().set("result_base_dir", base_dir) else: panel.settings().erase("result_base_dir") - panel.run_command("lsp_update_panel", {"characters": to_render}) + panel.run_command("lsp_update_panel", {"characters": characters}) if self._panel_code_phantoms is None: self._panel_code_phantoms = sublime.PhantomSet(panel, "hrefs") phantoms = [] # type: List[sublime.Phantom] diff --git a/plugin/references.py b/plugin/references.py index 099ebf303..fe7b8cf8a 100644 --- a/plugin/references.py +++ b/plugin/references.py @@ -121,24 +121,22 @@ def show_references_panel(self, references_by_file: Dict[str, List[Tuple[Point, if not panel: return - text = '' + to_render = [] # type: List[str] references_count = 0 for file, references in references_by_file.items(): - text += '{}:\n'.format(self.get_relative_path(file)) + to_render.append('{}:'.format(self.get_relative_path(file))) for reference in references: references_count += 1 point, line = reference - text += '\t{:>4}:{:<4} {}\n'.format(point.row + 1, point.col + 1, line) - # append a new line after each file name - text += '\n' - + to_render.append('{:>4}:{:<4} {}'.format(point.row + 1, point.col + 1, line)) + characters = "\n".join(to_render) 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.references"}) panel.run_command('append', { - 'characters': "{} references for '{}'\n\n{}".format(references_count, self.word, text), + 'characters': "{} references for '{}'\n\n{}".format(references_count, self.word, characters), 'force': True, 'scroll_to_end': False }) diff --git a/plugin/rename.py b/plugin/rename.py index 0af0af49f..ef5d83f2b 100644 --- a/plugin/rename.py +++ b/plugin/rename.py @@ -161,22 +161,21 @@ def _render_rename_panel(self, changes: Dict[str, List[TextEdit]], total_changes panel = ensure_rename_panel(window) if not panel: return - text = '' + to_render = [] # type: List[str] for file, file_changes in changes.items(): - text += '{}:\n'.format(self._get_relative_path(file)) + to_render.append('{}:'.format(self._get_relative_path(file))) for edit in file_changes: start = edit[0] line_content = get_line(self.view.window(), file, start[0]) - text += '\t{:>4}:{:<4} {}\n'.format(start[0] + 1, start[1] + 1, line_content) - # append a new line after each file name - text += '\n' + to_render.append('{:>4}:{:<4} {}'.format(start[0] + 1, start[1] + 1, line_content)) + characters = "\n".join(to_render) 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"}) fmt = "{} changes across {} files.\n\n{}" panel.run_command('append', { - 'characters': fmt.format(total_changes, file_count, text), + 'characters': fmt.format(total_changes, file_count, characters), 'force': True, 'scroll_to_end': False }) From c530fe96c83af2770a45b76d0fa5a86bb972c8e3 Mon Sep 17 00:00:00 2001 From: Predrag Date: Tue, 15 Dec 2020 23:15:15 +0100 Subject: [PATCH 10/15] set space padding to 5 Account if a user encounters files with more the 10000 lines --- plugin/core/views.py | 2 +- plugin/references.py | 2 +- plugin/rename.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/core/views.py b/plugin/core/views.py index 4b7bc1ac8..709f34147 100644 --- a/plugin/core/views.py +++ b/plugin/core/views.py @@ -564,7 +564,7 @@ def format_diagnostic_for_panel(diagnostic: Diagnostic) -> Tuple[str, Optional[i formatted.append(code) lines = diagnostic.message.splitlines() or [""] # \u200B is the zero-width space - result = "{:>4}:{:<4}{:<8}{} \u200B{}".format( + result = "{:>5}:{:<4}{:<8}{} \u200B{}".format( diagnostic.range.start.row + 1, diagnostic.range.start.col + 1, format_severity(diagnostic.severity), diff --git a/plugin/references.py b/plugin/references.py index fe7b8cf8a..9013c0921 100644 --- a/plugin/references.py +++ b/plugin/references.py @@ -128,7 +128,7 @@ def show_references_panel(self, references_by_file: Dict[str, List[Tuple[Point, for reference in references: references_count += 1 point, line = reference - to_render.append('{:>4}:{:<4} {}'.format(point.row + 1, point.col + 1, line)) + to_render.append('{:>5}:{:<4} {}'.format(point.row + 1, point.col + 1, line)) characters = "\n".join(to_render) base_dir = windows.lookup(window).get_project_path(self.view.file_name() or "") panel.settings().set("result_base_dir", base_dir) diff --git a/plugin/rename.py b/plugin/rename.py index ef5d83f2b..cbc6f1230 100644 --- a/plugin/rename.py +++ b/plugin/rename.py @@ -167,7 +167,7 @@ def _render_rename_panel(self, changes: Dict[str, List[TextEdit]], total_changes for edit in file_changes: start = edit[0] line_content = get_line(self.view.window(), file, start[0]) - to_render.append('{:>4}:{:<4} {}'.format(start[0] + 1, start[1] + 1, line_content)) + to_render.append('{:>5}:{:<4} {}'.format(start[0] + 1, start[1] + 1, line_content)) characters = "\n".join(to_render) base_dir = windows.lookup(window).get_project_path(self.view.file_name() or "") panel.settings().set("result_base_dir", base_dir) From dd93e5cdabfea05823a12de9cb46727504082ce3 Mon Sep 17 00:00:00 2001 From: Predrag Date: Tue, 15 Dec 2020 23:28:53 +0100 Subject: [PATCH 11/15] add spacing between filenames - to be consistent with Sublime text find in project - and it just gives a visual separation of the results, although there is no need for \n between filenames, I think that they help readability --- plugin/core/windows.py | 1 + plugin/references.py | 1 + plugin/rename.py | 1 + 3 files changed, 3 insertions(+) diff --git a/plugin/core/windows.py b/plugin/core/windows.py index f0b39f18b..0f0cbbf8d 100644 --- a/plugin/core/windows.py +++ b/plugin/core/windows.py @@ -434,6 +434,7 @@ def update_diagnostics_panel_async(self) -> None: if offset is not None and code is not None and href is not None: prephantoms.append((row, offset, code, href)) row += content.count("\n") + 1 + to_render.append("") # add spacing between filenames for listener in listeners: set_diagnostics_count(listener.view, self.total_error_count, self.total_warning_count) characters = "\n".join(to_render) diff --git a/plugin/references.py b/plugin/references.py index 9013c0921..d2d03a579 100644 --- a/plugin/references.py +++ b/plugin/references.py @@ -129,6 +129,7 @@ def show_references_panel(self, references_by_file: Dict[str, List[Tuple[Point, references_count += 1 point, line = reference to_render.append('{:>5}:{:<4} {}'.format(point.row + 1, point.col + 1, line)) + to_render.append("") # add spacing between filenames characters = "\n".join(to_render) base_dir = windows.lookup(window).get_project_path(self.view.file_name() or "") panel.settings().set("result_base_dir", base_dir) diff --git a/plugin/rename.py b/plugin/rename.py index cbc6f1230..22804fe53 100644 --- a/plugin/rename.py +++ b/plugin/rename.py @@ -168,6 +168,7 @@ def _render_rename_panel(self, changes: Dict[str, List[TextEdit]], total_changes start = edit[0] line_content = get_line(self.view.window(), file, start[0]) to_render.append('{:>5}:{:<4} {}'.format(start[0] + 1, start[1] + 1, line_content)) + to_render.append("") # this adds a spacing between filenames characters = "\n".join(to_render) base_dir = windows.lookup(window).get_project_path(self.view.file_name() or "") panel.settings().set("result_base_dir", base_dir) From 1eb6cf913ed22dad21a5a7e424a463502bde9245 Mon Sep 17 00:00:00 2001 From: Predrag Date: Tue, 15 Dec 2020 23:29:30 +0100 Subject: [PATCH 12/15] remove Rename.sublime-syntax --- Syntaxes/Rename.sublime-syntax | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 Syntaxes/Rename.sublime-syntax diff --git a/Syntaxes/Rename.sublime-syntax b/Syntaxes/Rename.sublime-syntax deleted file mode 100644 index e1f299fb6..000000000 --- a/Syntaxes/Rename.sublime-syntax +++ /dev/null @@ -1,31 +0,0 @@ -%YAML 1.2 ---- -# [Subl]: https://www.sublimetext.com/docs/3/syntax.html -# [LSP]: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md -hidden: true -scope: output.lsp.rename - -variables: - filename_and_colon: ^\s*(\S)\s+(.*)(:)$ - -contexts: - main: - - match: (\d+) changes across (\d+) files. Double-click on a (row)(:)(col) number to jump to that location. - captures: - 1: constant.numeric.integer.decimal.lsp - 2: constant.numeric.integer.decimal.lsp - 3: constant.numeric.integer.decimal.lsp - 4: punctuation.separator.lsp - 5: constant.numeric.integer.decimal.lsp - set: - - match: ^\s*(\S)\s+(.*)(:)$ - captures: - 0: meta.reference.preamble.lsp - 1: punctuation.section.rename.preample.lsp - 2: string.unquoted.lsp entity.name.file.rename.lsp - 3: punctuation.separator.lsp - - match: ^\s*(\d+)(:)(\d+) - captures: - 1: constant.numeric.integer.decimal.lsp - 2: punctuation.separator.lsp - 3: constant.numeric.integer.decimal.lsp From c655b4a147deacc2c4b1d17718e52b5e936d838f Mon Sep 17 00:00:00 2001 From: Predrag Date: Tue, 15 Dec 2020 23:34:14 +0100 Subject: [PATCH 13/15] do not match the first line digits for references and rename panel before the References.sublime-syntax would match 16 references for 'Response' ^^ number 17 changes across 6 files. ^^ number after this commit those number won't be matched 16 references for 'Response' 17 changes across 6 files. --- Syntaxes/References.sublime-syntax | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Syntaxes/References.sublime-syntax b/Syntaxes/References.sublime-syntax index 91d8229b9..dcefb0048 100644 --- a/Syntaxes/References.sublime-syntax +++ b/Syntaxes/References.sublime-syntax @@ -6,7 +6,7 @@ hidden: true scope: output.lsp.references variables: - start_of_reference_body: ^\s*(?=\d) + start_of_reference_body: ^\s*(?=\s*\d+:\d+) filename_and_colon: ^(?!\s*\d+:\d+)(.*)(:)$ contexts: From cd71f790cc4845dfaa7088b088ff81bbd7698f62 Mon Sep 17 00:00:00 2001 From: Predrag Date: Fri, 18 Dec 2020 21:29:39 +0100 Subject: [PATCH 14/15] fix: phantom placement increment the row to account the \n for the spacing between filenames so that the phantoms appear at the right place Co-authored-by @rwols --- plugin/core/windows.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/core/windows.py b/plugin/core/windows.py index 1de39606a..9089b9787 100644 --- a/plugin/core/windows.py +++ b/plugin/core/windows.py @@ -440,6 +440,7 @@ def update_diagnostics_panel_async(self) -> None: prephantoms.append((row, offset, code, href)) row += content.count("\n") + 1 to_render.append("") # add spacing between filenames + row += 1 for listener in listeners: set_diagnostics_count(listener.view, self.total_error_count, self.total_warning_count) characters = "\n".join(to_render) From e729f45307dcf9131440718c60f49736a5797835 Mon Sep 17 00:00:00 2001 From: Predrag Date: Fri, 18 Dec 2020 21:51:09 +0100 Subject: [PATCH 15/15] add missing pop Co-authored-by @rchl --- Syntaxes/Diagnostics.sublime-syntax | 1 + 1 file changed, 1 insertion(+) diff --git a/Syntaxes/Diagnostics.sublime-syntax b/Syntaxes/Diagnostics.sublime-syntax index 40a8cd98a..9ab29602f 100644 --- a/Syntaxes/Diagnostics.sublime-syntax +++ b/Syntaxes/Diagnostics.sublime-syntax @@ -53,6 +53,7 @@ contexts: pop: true - match: \bhint\b scope: markup.inserted.lsp sublimelinter.gutter-mark markup.info.hint.lsp + pop: true expect-source-and-code: - include: pop-at-end