Skip to content

Commit

Permalink
html-escape diagnostic-related strings (#2228)
Browse files Browse the repository at this point in the history
  • Loading branch information
rchl authored Apr 13, 2023
1 parent 121c592 commit 6225b00
Show file tree
Hide file tree
Showing 5 changed files with 27 additions and 35 deletions.
3 changes: 1 addition & 2 deletions plugin/completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from .core.typing import Callable, List, Dict, Optional, Generator, Tuple, Union, cast, Any, TypeGuard
from .core.views import COMPLETION_KINDS
from .core.views import FORMAT_STRING, FORMAT_MARKUP_CONTENT
from .core.views import make_link
from .core.views import MarkdownLangMap
from .core.views import minihtml
from .core.views import range_to_region
Expand Down Expand Up @@ -63,7 +62,7 @@ def format_completion(
args = '{{"view_id":{},"command":"lsp_resolve_docs","args":{{"index":{},"session_name":"{}"}}}}'.format(
view_id, index, session_name)
href = 'subl:lsp_run_text_command_helper {}'.format(args)
details.append(make_link(href, 'More'))
details.append("<a href='{}'>More</a>".format(href))
if lsp_label_detail and (lsp_label + lsp_label_detail).startswith(lsp_filter_text):
if lsp_label_detail[0].isalnum() and lsp_label.startswith(lsp_filter_text):
# labelDetails.detail is likely a type annotation
Expand Down
41 changes: 16 additions & 25 deletions plugin/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -780,13 +780,12 @@ def text2html(content: str) -> str:


def make_link(href: str, text: Any, class_name: Optional[str] = None, tooltip: Optional[str] = None) -> str:
if isinstance(text, str):
text = text.replace(' ', '&nbsp;')
link = "<a href='{}'".format(href)
if class_name:
link += " class='{}'".format(class_name)
if tooltip:
link += " title='{}'".format(html.escape(tooltip))
text = text2html(str(text)).replace(' ', '&nbsp;')
link += ">{}</a>".format(text)
return link

Expand Down Expand Up @@ -976,44 +975,36 @@ def _format_diagnostic_related_info(
location = info["location"]
return '<a href="{}">{}</a>: {}'.format(
location_to_href(config, location),
location_to_human_readable(config, base_dir, location),
info["message"]
text2html(location_to_human_readable(config, base_dir, location)),
text2html(info["message"])
)


def _with_color(text: Any, hexcolor: str) -> str:
return '<span style="color: {};">{}</span>'.format(hexcolor, text)
def _with_color(text: str, hexcolor: str) -> str:
return '<span style="color: {};">{}</span>'.format(hexcolor, text2html(text))


def format_diagnostic_for_html(
view: sublime.View,
config: ClientConfig,
diagnostic: Diagnostic,
base_dir: Optional[str] = None
) -> str:
def format_diagnostic_for_html(config: ClientConfig, diagnostic: Diagnostic, base_dir: Optional[str] = None) -> str:
formatted = [
'<pre class="',
DIAGNOSTIC_SEVERITY[diagnostic_severity(diagnostic) - 1][1],
'">',
text2html(diagnostic["message"])
]
code_description = diagnostic.get("codeDescription")
if "code" in diagnostic:
code = [_with_color("(", "color(var(--foreground) alpha(0.6))")]
if code_description:
code.append(make_link(code_description["href"], diagnostic.get("code")))
else:
code.append(_with_color(diagnostic["code"], "color(var(--foreground) alpha(0.6))"))
code.append(_with_color(")", "color(var(--foreground) alpha(0.6))"))
else:
code = None
code = diagnostic.get("code")
source = diagnostic.get("source")
if source or code:
if source or code is not None:
formatted.append(" ")
if source:
formatted.append(_with_color(source, "color(var(--foreground) alpha(0.6))"))
if code:
formatted.extend(code)
if code is not None:
formatted.append(_with_color("(", "color(var(--foreground) alpha(0.6))"))
code_description = diagnostic.get("codeDescription")
if code_description:
formatted.append(make_link(code_description["href"], str(code)))
else:
formatted.append(_with_color(str(code), "color(var(--foreground) alpha(0.6))"))
formatted.append(_with_color(")", "color(var(--foreground) alpha(0.6))"))
related_infos = diagnostic.get("relatedInformation")
if related_infos:
formatted.append('<pre class="related_info">')
Expand Down
4 changes: 2 additions & 2 deletions plugin/goto_diagnostic.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,8 @@ def open_location(session: Session, location: Location, flags: int = 0, group: i

def diagnostic_html(view: sublime.View, config: ClientConfig, diagnostic: Diagnostic,
base_dir: Optional[Path]) -> sublime.Html:
content = format_diagnostic_for_html(view, config, truncate_message(diagnostic),
None if base_dir is None else str(base_dir))
content = format_diagnostic_for_html(
config, truncate_message(diagnostic), None if base_dir is None else str(base_dir))
return sublime.Html('<style>{}</style><div class="diagnostics {}">{}</div>'.format(
PREVIEW_PANE_CSS, format_severity(diagnostic_severity(diagnostic)), content))

Expand Down
2 changes: 1 addition & 1 deletion plugin/hover.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ def diagnostics_content(self) -> str:
formatted.append('<div class="diagnostics">')
for diagnostic in diagnostics:
by_severity.setdefault(diagnostic_severity(diagnostic), []).append(
format_diagnostic_for_html(self.view, sb.session.config, diagnostic, self._base_dir))
format_diagnostic_for_html(sb.session.config, diagnostic, self._base_dir))
for items in by_severity.values():
formatted.extend(items)
formatted.append("</div>")
Expand Down
12 changes: 7 additions & 5 deletions tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from copy import deepcopy
from LSP.plugin.core.protocol import CodeActionKind
from LSP.plugin.core.protocol import Diagnostic
from LSP.plugin.core.protocol import Point
from LSP.plugin.core.protocol import DiagnosticSeverity
from LSP.plugin.core.types import Any
from LSP.plugin.core.url import filename_to_uri
from LSP.plugin.core.views import did_change
Expand Down Expand Up @@ -342,7 +344,7 @@ def test_text_document_code_action_params(self) -> None:
self.view.settings().set("lsp_uri", filename_to_uri(self.mock_file_name))
diagnostic = {
"message": "oops",
"severity": 1,
"severity": DiagnosticSeverity.Error,
"range": {
"start": {
"character": 0,
Expand All @@ -359,14 +361,14 @@ def test_text_document_code_action_params(self) -> None:
view=self.view,
region=sublime.Region(0, 1),
diagnostics=[diagnostic],
only_kinds=["refactor"]
only_kinds=[CodeActionKind.Refactor]
)
self.assertEqual(params["textDocument"], {"uri": filename_to_uri(self.mock_file_name)})

def test_format_diagnostic_for_html(self) -> None:
diagnostic1 = {
"message": "oops",
"severity": 1,
"severity": DiagnosticSeverity.Error,
# The relatedInformation is present here, but it's an empty list.
# This should have the same behavior as having no relatedInformation present.
"relatedInformation": [],
Expand All @@ -389,8 +391,8 @@ def test_format_diagnostic_for_html(self) -> None:
client_config = make_stdio_test_config()
# They should result in the same minihtml.
self.assertEqual(
format_diagnostic_for_html(self.view, client_config, diagnostic1, "/foo/bar"),
format_diagnostic_for_html(self.view, client_config, diagnostic2, "/foo/bar")
format_diagnostic_for_html(client_config, diagnostic1, "/foo/bar"),
format_diagnostic_for_html(client_config, diagnostic2, "/foo/bar")
)

def test_escaped_newline_in_markdown(self) -> None:
Expand Down

0 comments on commit 6225b00

Please sign in to comment.