-
Notifications
You must be signed in to change notification settings - Fork 184
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
sort diagnostics and select the closest one on navigation #2273
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
from .core.protocol import DiagnosticSeverity | ||
from .core.protocol import DocumentUri | ||
from .core.protocol import Location | ||
from .core.protocol import Point | ||
from .core.registry import windows | ||
from .core.sessions import Session | ||
from .core.settings import userprefs | ||
|
@@ -15,11 +16,13 @@ | |
from .core.url import parse_uri, unparse_uri | ||
from .core.views import DIAGNOSTIC_KINDS | ||
from .core.views import diagnostic_severity | ||
from .core.views import first_selection_region | ||
from .core.views import format_diagnostic_for_html | ||
from .core.views import format_diagnostic_source_and_code | ||
from .core.views import format_severity | ||
from .core.views import get_uri_and_position_from_location | ||
from .core.views import MissingUriError | ||
from .core.views import point_to_offset | ||
from .core.views import to_encoded_filename | ||
from .core.views import uri_from_view | ||
from abc import ABCMeta | ||
|
@@ -32,6 +35,9 @@ | |
import sublime_plugin | ||
|
||
|
||
SessionIndex = int | ||
SelectedIndex = int | ||
|
||
PREVIEW_PANE_CSS = """ | ||
.diagnostics {padding: 0.5em} | ||
.diagnostics a {color: var(--bluish)} | ||
|
@@ -93,8 +99,9 @@ def input_description(self) -> str: | |
return "Goto Diagnostic" | ||
|
||
|
||
ListItemsReturn = Union[List[str], Tuple[List[str], int], List[Tuple[str, Any]], Tuple[List[Tuple[str, Any]], int], | ||
List[sublime.ListInputItem], Tuple[List[sublime.ListInputItem], int]] | ||
ListItemsReturn = Union[List[str], Tuple[List[str], SelectedIndex], | ||
List[Tuple[str, Any]], Tuple[List[Tuple[str, Any]], SelectedIndex], | ||
List[sublime.ListInputItem], Tuple[List[sublime.ListInputItem], SelectedIndex]] | ||
|
||
|
||
class PreselectedListInputHandler(sublime_plugin.ListInputHandler, metaclass=ABCMeta): | ||
|
@@ -145,7 +152,7 @@ def __init__(self, window: sublime.Window, view: sublime.View, initial_value: Op | |
def name(self) -> str: | ||
return "uri" | ||
|
||
def get_list_items(self) -> Tuple[List[sublime.ListInputItem], int]: | ||
def get_list_items(self) -> Tuple[List[sublime.ListInputItem], SelectedIndex]: | ||
max_severity = userprefs().diagnostics_panel_include_severity_level | ||
# collect severities and location of first diagnostic per uri | ||
severities_per_path = OrderedDict() # type: OrderedDict[ParsedUri, List[DiagnosticSeverity]] | ||
|
@@ -221,10 +228,11 @@ def _project_path(self, parsed_uri: ParsedUri) -> str: | |
return path | ||
|
||
|
||
class DiagnosticInputHandler(sublime_plugin.ListInputHandler): | ||
class DiagnosticInputHandler(PreselectedListInputHandler): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should not be a PreselectedListInputHandler. I mean it wouldn't break anything, but it also makes no sense if it's always gonna be instantiated without a initial value to preselect, so it would just be confusing here. PreselectedListInputHandler only makes sense if it's not the last InputHandler on the stack. |
||
_preview = None # type: Optional[sublime.View] | ||
|
||
def __init__(self, window: sublime.Window, view: sublime.View, uri: DocumentUri) -> None: | ||
super().__init__(window, initial_value=None) | ||
self.window = window | ||
self.view = view | ||
self.sessions = list(get_sessions(window)) | ||
|
@@ -233,29 +241,40 @@ def __init__(self, window: sublime.Window, view: sublime.View, uri: DocumentUri) | |
def name(self) -> str: | ||
return "diagnostic" | ||
|
||
def list_items(self) -> List[sublime.ListInputItem]: | ||
list_items = [] # type: List[sublime.ListInputItem] | ||
def get_list_items(self) -> Tuple[List[sublime.ListInputItem], SelectedIndex]: | ||
max_severity = userprefs().diagnostics_panel_include_severity_level | ||
diagnostics = [] # type: List[Tuple[SessionIndex, Diagnostic]] | ||
for i, session in enumerate(self.sessions): | ||
for diagnostic in filter(is_severity_included(max_severity), | ||
session.diagnostics.diagnostics_by_parsed_uri(self.parsed_uri)): | ||
lines = diagnostic["message"].splitlines() | ||
first_line = lines[0] if lines else "" | ||
if len(lines) > 1: | ||
first_line += " …" | ||
text = "{}: {}".format(format_severity(diagnostic_severity(diagnostic)), first_line) | ||
annotation = format_diagnostic_source_and_code(diagnostic) | ||
kind = DIAGNOSTIC_KINDS[diagnostic_severity(diagnostic)] | ||
list_items.append(sublime.ListInputItem(text, (i, diagnostic), annotation=annotation, kind=kind)) | ||
return list_items | ||
for diagnostic in filter(is_severity_included(max_severity), session.diagnostics.diagnostics_by_parsed_uri( | ||
self.parsed_uri, sort_order='asc')): | ||
diagnostics.append((i, diagnostic)) | ||
selected_index = 0 | ||
Comment on lines
247
to
+251
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, this is only gonna sort per-session so not really how it should be... |
||
selection_region = first_selection_region(self.view) | ||
selection_offset = selection_region.b if selection_region is not None else 0 | ||
list_items = [] # type: List[sublime.ListInputItem] | ||
for i, diagnostic_tuple in enumerate(diagnostics): | ||
diagnostic = diagnostic_tuple[1] | ||
lines = diagnostic["message"].splitlines() | ||
first_line = lines[0] if lines else "" | ||
if len(lines) > 1: | ||
first_line += " …" | ||
text = "{}: {}".format(format_severity(diagnostic_severity(diagnostic)), first_line) | ||
annotation = format_diagnostic_source_and_code(diagnostic) | ||
kind = DIAGNOSTIC_KINDS[diagnostic_severity(diagnostic)] | ||
list_items.append(sublime.ListInputItem(text, diagnostic_tuple, annotation=annotation, kind=kind)) | ||
# Pick as a selected index if before or equal the first selection point. | ||
range_start_offset = point_to_offset(Point.from_lsp(diagnostic['range']['start']), self.view) | ||
if range_start_offset <= selection_offset: | ||
selected_index = i | ||
return (list_items, selected_index) | ||
|
||
def placeholder(self) -> str: | ||
return "Select diagnostic" | ||
|
||
def next_input(self, args: dict) -> Optional[sublime_plugin.CommandInputHandler]: | ||
return None if args.get("diagnostic") else sublime_plugin.BackInputHandler() # type: ignore | ||
|
||
def confirm(self, value: Optional[Tuple[int, Diagnostic]]) -> None: | ||
def confirm(self, value: Optional[Tuple[SessionIndex, Diagnostic]]) -> None: | ||
if not value: | ||
return | ||
i, diagnostic = value | ||
|
@@ -272,7 +291,7 @@ def cancel(self) -> None: | |
self._preview.close() | ||
self.window.focus_view(self.view) | ||
|
||
def preview(self, value: Optional[Tuple[int, Diagnostic]]) -> Union[str, sublime.Html]: | ||
def preview(self, value: Optional[Tuple[SessionIndex, Diagnostic]]) -> Union[str, sublime.Html]: | ||
if not value: | ||
return "" | ||
i, diagnostic = value | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, do you really want to add type aliases for such basic types like int? I would think that it makes the code more difficult to follow, rather than easier, because when you see SelectedIndex you might not remember whether it's a special class, or or a TypedDict or something else. Then you first need to look up the definition to see what it is.
And where would we stop with it; should every int and str get a special name to describe what it is, for example
session_name: SessionName
etc. ...I'm aware that URI and DocumentUri exist as aliases for str in the protocol, but they have some kind of special meaning, namely it guarantees that the contents of those strings can be parsed as a valid URI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me it makes the type more explicit and easier to follow>
If we used int
Instead of SelectedIndex
I would not know in the first example if int should represent a SelectedIndex or a SessionIndex, or something else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe then we should add docstrings to type stubs used by LSP?
Compare (missing) info from the type stubs in this repository
with the stubs shipped by LSP-pyright:
Otherwise, where would we stop? Should all and every int used in LSP get a type alias, or do we need to define certain rules for that beforehand...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of storing the session index as an int, I would probably recommend to refactor that code anyways and use the session name instead. This code
session = self.sessions[i]
which is used in the DiagnosticInputHandler class looks quite fragile to me (is it safe to still work correctly even when one of the sessions is closed or a new server is started in the meanwhile?)Edit: I see the sessions are fixed when the InputHandler instance gets created, so I guess it's safe to use here. (I was thinking of the
self.sessions()
from the LSP commands)