diff --git a/plugin/semantic_highlighting.py b/plugin/semantic_highlighting.py index 5621996e8..e4cec4585 100644 --- a/plugin/semantic_highlighting.py +++ b/plugin/semantic_highlighting.py @@ -1,6 +1,7 @@ from .core.registry import LspTextCommand -from .core.typing import List +from .core.typing import Any, List, Tuple, cast import sublime +import os class SemanticToken: @@ -13,6 +14,12 @@ def __init__(self, region: sublime.Region, type: str, modifiers: List[str]): self.modifiers = modifiers +def copy(view: sublime.View, text: str) -> None: + sublime.set_clipboard(text) + view.hide_popup() + sublime.status_message('Scope name copied to clipboard') + + class LspShowScopeNameCommand(LspTextCommand): """ Like the builtin show_scope_name command from Default/show_scope_name.py, @@ -21,14 +28,56 @@ class LspShowScopeNameCommand(LspTextCommand): capability = 'semanticTokensProvider' - def run(self, edit: sublime.Edit) -> None: + def run(self, _: sublime.Edit) -> None: point = self.view.sel()[-1].b - scope = self.view.scope_name(point).rstrip() scope_list = scope.replace(' ', '
') - stack = self.view.context_backtrace(point) - + token_type, token_modifiers = self._get_semantic_info(point) + if isinstance(stack, list) and len(stack) > 0 and not isinstance(stack[0], str): + self._render_with_fancy_stackframes( + scope, + scope_list, + cast(List[sublime.ContextStackFrame], stack), + token_type, + token_modifiers + ) + else: + self._render_with_plain_string_stackframes( + scope, + scope_list, + cast(List[str], stack), + token_type, + token_modifiers + ) + + def _get_semantic_info(self, point: int) -> Tuple[str, str]: + session = self.best_session('semanticTokensProvider') + session_buffer = None + if session: + for sv in session.session_views_async(): + if self.view == sv.view: + session_buffer = sv.session_buffer + break + token_type = '-' + token_modifiers = '-' + if session_buffer: + for token in session_buffer.get_semantic_tokens(): + if token.region.contains(point) and point < token.region.end(): + token_type = token.type + if token.modifiers: + token_modifiers = ', '.join(token.modifiers) + break + return token_type, token_modifiers + + def _render_with_plain_string_stackframes( + self, + scope: str, + scope_list: str, + stack: List[str], + token_type: str, + token_modifiers: str + ) -> None: backtrace = '' digits_len = 1 for i, ctx in enumerate(reversed(stack)): @@ -44,26 +93,84 @@ def run(self, edit: sublime.Edit) -> None: backtrace += '\n' backtrace += '
%s%s
' % (nums, ctx) - # ------------------------------------------------------ + html = """ + + +

Scope Name Copy

+

%s

+

Context Backtrace

+ %s +
+

Semantic Token

+

Type: %s
Modifiers: %s

+ + """ % (digits_len, scope, scope_list, backtrace, token_type, token_modifiers) - session = self.best_session('semanticTokensProvider') - session_buffer = None - if session: - for sv in session.session_views_async(): - if self.view == sv.view: - session_buffer = sv.session_buffer - break + self.view.show_popup(html, max_width=512, max_height=512, on_navigate=lambda x: copy(self.view, x)) - token_type = '-' - token_modifiers = '-' + def _render_with_fancy_stackframes( + self, + scope: str, + scope_list: str, + stack: List[Any], + token_type: str, + token_modifiers: str + ) -> None: + backtrace = '' + digits_len = 1 + for i, frame in enumerate(reversed(stack)): + digits = '%s' % (i + 1) + digits_len = max(len(digits), digits_len) + nums = '%s.' % digits - if session_buffer: - for token in session_buffer.get_semantic_tokens(): - if token.region.contains(point) and point < token.region.end(): - token_type = token.type - if token.modifiers: - token_modifiers = ', '.join(token.modifiers) - break + if frame.context_name.startswith("anonymous context "): + context_name = '%s' % frame.context_name + else: + context_name = frame.context_name + ctx = '%s' % context_name + + resource_path = frame.source_file + display_path = os.path.splitext(frame.source_file)[0] + if resource_path.startswith('Packages/'): + resource_path = '${packages}/' + resource_path[9:] + display_path = display_path[9:] + + if frame.source_location[0] > 0: + href = '%s:%d:%d' % (resource_path, frame.source_location[0], frame.source_location[1]) + location = '%s:%d:%d' % (display_path, frame.source_location[0], frame.source_location[1]) + else: + href = resource_path + location = display_path + link = '%s' % (href, location) + + if backtrace: + backtrace += '\n' + backtrace += '
%s%s%s
' % (nums, ctx, link) html = """ @@ -93,7 +200,7 @@ def run(self, edit: sublime.Edit) -> None: padding-left: 0.5em; } -

Scope Name Copy

+

Scope Name Copy

%s

Context Backtrace

%s @@ -103,9 +210,12 @@ def run(self, edit: sublime.Edit) -> None: """ % (digits_len, scope, scope_list, backtrace, token_type, token_modifiers) - def copy(view: sublime.View, text: str) -> None: - sublime.set_clipboard(text) - view.hide_popup() - sublime.status_message('Scope name copied to clipboard') + self.view.show_popup(html, max_width=512, max_height=512, on_navigate=self.on_navigate) - self.view.show_popup(html, max_width=512, max_height=512, on_navigate=lambda x: copy(self.view, x)) + def on_navigate(self, link: str) -> None: + if link.startswith('o:'): + window = self.view.window() + if window: + window.run_command('open_file', {'file': link[2:], 'encoded_position': True}) + else: + copy(self.view, link[2:]) diff --git a/stubs/sublime.pyi b/stubs/sublime.pyi index 655fba123..c27ed6143 100644 --- a/stubs/sublime.pyi +++ b/stubs/sublime.pyi @@ -666,6 +666,12 @@ class HtmlSheet: ... +class ContextStackFrame: + context_name = ... # type: str + source_file = ... # type: str + source_location = ... # type: Tuple[int, int] + + class View: view_id = ... # type: Any selection = ... # type: Any @@ -801,7 +807,7 @@ class View: def scope_name(self, pt: int) -> str: ... - def context_backtrace(self, pt: int) -> List[str]: + def context_backtrace(self, pt: int) -> Union[List[ContextStackFrame], List[str]]: ... def match_selector(self, pt: int, selector: str) -> bool: