Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clickable links in the diagnostics panel #1520

Merged
merged 3 commits into from
Dec 11, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Syntaxes/Diagnostics.sublime-syntax
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ contexts:
- include: line

file:
- match: ^(.*)(:)$
- match: ^(?!\s*\d+:\d+)(.*)(:)$
captures:
0: meta.diagnostic.preamble.lsp
1: string.unquoted.lsp
Expand Down Expand Up @@ -54,7 +54,7 @@ contexts:

expect-source-and-code:
- include: pop-at-end
- match: ([^:\]]+)((:)(\S+))?
- match: ([^:\]]+)((:)(\S+)?)?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(\S+)? is basically (\S*) I guess?

captures:
1: comment.line.source.lsp
3: punctuation.separator.lsp
Expand Down
28 changes: 23 additions & 5 deletions plugin/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from .protocol import Point
from .protocol import Range
from .protocol import Request
from .typing import Optional, Dict, Any, Iterable, List, Union, Callable
from .typing import Optional, Dict, Any, Iterable, List, Union, Callable, Tuple
from .url import filename_to_uri
from .url import uri_to_filename
import html
Expand Down Expand Up @@ -542,10 +542,26 @@ def format_severity(severity: int) -> str:
return "???"


def format_diagnostic_for_panel(diagnostic: Diagnostic) -> str:
def format_diagnostic_for_panel(diagnostic: Diagnostic) -> Tuple[str, Optional[int], Optional[str], Optional[str]]:
"""
Turn an LSP diagnostic into a string suitable for an output panel.

:param diagnostic: The diagnostic
:returns: Tuple of (content, optional offset, optional code, optional href)
When the last three elements are optional, don't show an inline phantom
When the last three elemenst are not optional, show an inline phantom
using the information given.
"""
formatted = [diagnostic.source if diagnostic.source else "unknown-source"]
if diagnostic.code:
formatted.extend((":", str(diagnostic.code)))
offset = None
href = None
code = str(diagnostic.code) if diagnostic.code else None
if code:
formatted.append(":")
if diagnostic.code_description:
href = diagnostic.code_description["href"]
else:
formatted.append(code)
lines = diagnostic.message.splitlines() or [""]
# \u200B is the zero-width space
result = "{:>4}:{:<4}{:<8}{} \u200B{}".format(
Expand All @@ -555,9 +571,11 @@ def format_diagnostic_for_panel(diagnostic: Diagnostic) -> str:
lines[0],
"".join(formatted)
)
if href:
offset = len(result)
for line in itertools.islice(lines, 1, None):
result += "\n" + 17 * " " + line
return result
return result, offset, code, href


def _format_diagnostic_related_info(info: DiagnosticRelatedInformation, base_dir: Optional[str] = None) -> str:
Expand Down
45 changes: 34 additions & 11 deletions plugin/core/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,19 @@
from .protocol import Diagnostic
from .protocol import Error
from .protocol import Point
from .sessions import SessionBufferProtocol, get_plugin
from .sessions import Logger
from .sessions import Manager
from .sessions import Session
from .sessions import SessionBufferProtocol
from .sessions import get_plugin
from .sessions import SessionViewProtocol
from .settings import userprefs
from .transports import create_transport
from .types import ClientConfig
from .typing import Optional, Any, Dict, Deque, List, Generator, Tuple, Iterable
from .views import diagnostic_to_phantom
from .views import extract_variables
from .views import make_link
from .workspace import disable_in_project
from .workspace import enable_in_project
from .workspace import ProjectFolders
Expand Down Expand Up @@ -67,7 +69,7 @@ def diagnostics_async(self) -> Iterable[Tuple[SessionBufferProtocol, List[Diagno
raise NotImplementedError()

@abstractmethod
def diagnostics_panel_contribution_async(self) -> List[str]:
def diagnostics_panel_contribution_async(self) -> List[Tuple[str, Optional[int], Optional[str], Optional[str]]]:
raise NotImplementedError()

@abstractmethod
Expand Down Expand Up @@ -122,6 +124,7 @@ def __init__(
self._new_session = None # type: Optional[Session]
self._cursor = DiagnosticsCursor(userprefs().show_diagnostics_severity_level)
self._diagnostic_phantom_set = None # type: Optional[sublime.PhantomSet]
self._panel_code_phantoms = None # type: Optional[sublime.PhantomSet]
self.total_error_count = 0
self.total_warning_count = 0

Expand Down Expand Up @@ -407,14 +410,13 @@ 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:
panel = ensure_diagnostics_panel(self._window)
if not panel:
return
to_render = [] # type: List[str]
base_dir = None
self.total_error_count = 0
self.total_warning_count = 0
listeners = list(self._listeners)
prephantoms = [] # type: List[Tuple[int, int, str, str]]
row = 0
for listener in listeners:
local_errors, local_warnings = listener.sum_total_errors_and_warnings_async()
self.total_error_count += local_errors
Expand All @@ -426,14 +428,35 @@ def update_diagnostics_panel_async(self) -> None:
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.extend(contribution)
if isinstance(base_dir, str):
panel.settings().set("result_base_dir", base_dir)
else:
panel.settings().erase("result_base_dir")
panel.run_command("lsp_update_panel", {"characters": "\n".join(to_render)})
row += 1
for content, offset, code, href in contribution:
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
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)
if not panel or not panel.is_valid():
return
if isinstance(base_dir, str):
panel.settings().set("result_base_dir", base_dir)
else:
panel.settings().erase("result_base_dir")
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]
for row, col, code, href in prephantoms:
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)

sublime.set_timeout(update)

def _can_manipulate_diagnostics_panel(self) -> bool:
active_panel = self._window.active_panel()
Expand Down
4 changes: 2 additions & 2 deletions plugin/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,8 @@ def on_session_shutdown_async(self, session: Session) -> None:
# SessionView was likely not created for this config so remove status here.
session.config.erase_view_status(self.view)

def diagnostics_panel_contribution_async(self) -> List[str]:
result = [] # type: List[str]
def diagnostics_panel_contribution_async(self) -> List[Tuple[str, Optional[int], Optional[str], Optional[str]]]:
result = [] # type: List[Tuple[str, Optional[int], Optional[str], Optional[str]]]
# Sort by severity
for severity in range(1, len(DIAGNOSTIC_SEVERITY) + 1):
for sb in self.session_buffers_async():
Expand Down
2 changes: 1 addition & 1 deletion plugin/session_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class DiagnosticSeverityData:
def __init__(self, severity: int) -> None:
self.regions = [] # type: List[sublime.Region]
self.annotations = [] # type: List[str]
self.panel_contribution = [] # type: List[str]
self.panel_contribution = [] # type: List[Tuple[str, Optional[int], Optional[str], Optional[str]]]
_, __, self.scope, self.icon = DIAGNOSTIC_SEVERITY[severity - 1]
if userprefs().diagnostics_gutter_marker != "sign":
self.icon = userprefs().diagnostics_gutter_marker
Expand Down