Skip to content

Commit 56d9be0

Browse files
Add basic syntax highlighting to signature/quickinfo popups (#707)
Add basic syntax highlighting to signature/quickinfo popups
2 parents 4565f79 + 908d90e commit 56d9be0

File tree

7 files changed

+175
-15
lines changed

7 files changed

+175
-15
lines changed

quickinfo_and_error_popup.html

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,47 @@
11
<style>
2-
body {
2+
div.wrapper {
33
margin: 5px;
44
}
55
div.error {
66
color: red;
77
margin-bottom: 5px;
88
}
9+
span.punctuation {
10+
${punctuationStyles}
11+
}
12+
span.text {
13+
${textStyles}
14+
}
15+
span.className {
16+
${typeStyles}
17+
}
18+
span.propertyName {
19+
${propertyStyles}
20+
}
21+
span.keyword {
22+
${keywordStyles}
23+
}
24+
span.localName {
25+
${variableStyles}
26+
}
27+
span.stringLiteral {
28+
${stringStyles}
29+
}
30+
span.numericLiteral {
31+
${numberStyles}
32+
}
33+
span.aliasName {
34+
${typeStyles}
35+
}
36+
span.interfaceName {
37+
${interfaceStyles}
38+
}
39+
span.parameterName {
40+
${paramStyles}
41+
}
942
</style>
10-
<body>
11-
<div class="error">${error}</div>
12-
<div>${info_str}</div>
13-
<div>${doc_str}</div>
14-
</body>
43+
<div class="wrapper">
44+
<div class="error">${error}</div>
45+
<div class="info">${info_str}</div>
46+
<div class="doc">${doc_str}</div>
47+
</div>

signature_popup.html

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,20 @@
66
margin: 5px;
77
word-wrap:break-word;
88
}
9+
.name {
10+
${nameStyles}
11+
}
12+
.type {
13+
${typeStyles}
14+
}
15+
.param {
16+
${paramStyles}
17+
}
18+
.text {
19+
${textStyles}
20+
}
921
</style>
1022
<div><b>${signature}</b></div>
1123
<div style="margin-left: 15px">${description}</div>
1224
<div>${activeParam}</div>
13-
<div><a href="${link}">${index}</a></div>
25+
<div><a href="${link}">${index}</a></div>

typescript/commands/quick_info.py

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from ..libs.text_helpers import escape_html
33
from .base_command import TypeScriptBaseTextCommand
44
from ..libs.popup_manager import load_html_template
5+
from ..libs.popup_formatter import get_theme_styles
56
from string import Template
67

78
def load_quickinfo_and_error_popup_template():
@@ -39,10 +40,14 @@ class TypescriptQuickInfoDoc(TypeScriptBaseTextCommand):
3940
def handle_quick_info(self, quick_info_resp_dict, display_point):
4041
info_str = ""
4142
doc_str = ""
43+
4244
if quick_info_resp_dict["success"]:
43-
info_str = quick_info_resp_dict["body"]["displayString"]
44-
status_info_str = info_str
45-
doc_str = quick_info_resp_dict["body"]["documentation"]
45+
info_str = self.format_display_parts_html(quick_info_resp_dict["body"]["displayParts"])
46+
status_info_str = self.format_display_parts_plain(quick_info_resp_dict["body"]["displayParts"])
47+
48+
if "documentation" in quick_info_resp_dict["body"]:
49+
doc_str = self.format_display_parts_html(quick_info_resp_dict["body"]["documentation"])
50+
4651
# process documentation
4752
if len(doc_str) > 0:
4853
if not TOOLTIP_SUPPORT:
@@ -64,10 +69,33 @@ def handle_quick_info(self, quick_info_resp_dict, display_point):
6469
if TOOLTIP_SUPPORT and (info_str != "" or doc_str != "" or error_html != ""):
6570
if self.template is None:
6671
self.template = Template(load_quickinfo_and_error_popup_template())
67-
text_parts = { "error": error_html, "info_str": escape_html(info_str), "doc_str": escape_html(doc_str) }
68-
html = self.template.substitute(text_parts)
72+
73+
html = self.get_popup_html(error_html, info_str, doc_str)
6974
self.view.show_popup(html, flags=sublime.HIDE_ON_MOUSE_MOVE_AWAY, location=display_point, max_height=300, max_width=1500)
7075

76+
def get_popup_html(self, error, info, doc):
77+
theme_styles = get_theme_styles(self.view)
78+
79+
parameters = {
80+
"error": error,
81+
"info_str": info,
82+
"doc_str": doc,
83+
"typeStyles": theme_styles["type"],
84+
"keywordStyles": theme_styles["keyword"],
85+
"nameStyles": theme_styles["name"],
86+
"paramStyles": theme_styles["param"],
87+
"propertyStyles": theme_styles["property"],
88+
"punctuationStyles": theme_styles["punctuation"],
89+
"variableStyles": theme_styles["variable"],
90+
"functionStyles": theme_styles["function"],
91+
"interfaceStyles": theme_styles["interface"],
92+
"stringStyles": theme_styles["string"],
93+
"numberStyles": theme_styles["number"],
94+
"textStyles": theme_styles["text"]
95+
}
96+
97+
return self.template.substitute(parameters)
98+
7199
def get_error_text_html(self, pt):
72100
client_info = cli.get_or_add_file(self.view.file_name())
73101
error_text = ""
@@ -86,6 +114,28 @@ def run(self, text, hover_point=None):
86114
display_point = self.view.sel()[0].begin() if hover_point is None else hover_point
87115
word_at_sel = self.view.classify(display_point)
88116
if word_at_sel & SUBLIME_WORD_MASK:
89-
cli.service.quick_info(self.view.file_name(), get_location_from_position(self.view, display_point), lambda response: self.handle_quick_info(response, display_point))
117+
cli.service.quick_info_full(self.view.file_name(), get_location_from_position(self.view, display_point), lambda response: self.handle_quick_info(response, display_point))
90118
else:
91119
self.view.erase_status("typescript_info")
120+
121+
def map_kind_to_html_class(self, kind):
122+
return kind
123+
124+
def format_display_parts_html(self, display_parts):
125+
def html_escape(str):
126+
return str.replace('&', '&amp;').replace('<', '&lt;').replace('>', "&gt;").replace('\n', '<br>').replace(' ', '&nbsp;')
127+
128+
result = ""
129+
template = '<span class="{0}">{1}</span>'
130+
131+
for part in display_parts:
132+
result += template.format(self.map_kind_to_html_class(part["kind"]), html_escape(part["text"]))
133+
134+
return result
135+
136+
def format_display_parts_plain(self, display_parts):
137+
result = ""
138+
for part in display_parts:
139+
result += part["text"]
140+
141+
return result

typescript/libs/node_client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import os
1+
import os
22
import subprocess
33
import threading
44
import time
@@ -183,6 +183,7 @@ def read_msg(stream, msgq, asyncReq, proc, asyncEventHandlers):
183183
data = stream.read(body_length)
184184
log.debug('Read body of length: {0}'.format(body_length))
185185
data_json = data.decode("utf-8")
186+
186187
data_dict = json_helpers.decode(data_json)
187188
if data_dict['type'] == "response":
188189
request_seq = data_dict['request_seq']

typescript/libs/popup_formatter.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
2+
def format_css(style):
3+
"""
4+
Given a style object from view.style_for_scope, converts the styles into
5+
a CSS string
6+
"""
7+
result = ""
8+
9+
if (style["foreground"]):
10+
result += "color: {0};".format(style["foreground"])
11+
12+
if (style["bold"]):
13+
result += "font-weight: bold;"
14+
15+
if (style["italic"]):
16+
result += "font-style: italic;"
17+
18+
return result
19+
20+
def get_theme_styles(view):
21+
"""
22+
Given a view object, pulls styling information from the current theme for
23+
syntax highlighting popups
24+
"""
25+
return {
26+
"type": format_css(view.style_for_scope("entity.name.type.class.ts")),
27+
"keyword": format_css(view.style_for_scope("keyword.control.flow.ts")),
28+
"name": format_css(view.style_for_scope("entity.name.function")),
29+
"param": format_css(view.style_for_scope("variable.language.arguments.ts")),
30+
"property": format_css(view.style_for_scope("variable.other.property.ts")),
31+
"punctuation": format_css(view.style_for_scope("punctuation.definition.block.ts")),
32+
"variable": format_css(view.style_for_scope("meta.var.expr.ts")),
33+
"function": format_css(view.style_for_scope("entity.name.function.ts")),
34+
"interface": format_css(view.style_for_scope("entity.name.type.interface.ts")),
35+
"string": format_css(view.style_for_scope("string.quoted.single.ts")),
36+
"number": format_css(view.style_for_scope("constant.numeric.decimal.ts")),
37+
"text": format_css(view.style_for_scope("source.ts"))
38+
}

typescript/libs/popup_manager.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .work_scheduler import work_scheduler
88
from .text_helpers import Location
99
from .editor_client import cli
10+
from .popup_formatter import get_theme_styles
1011
from ..libs.view_helpers import reload_buffer
1112

1213
_POPUP_DEFAULT_FONT_SIZE = 12
@@ -263,13 +264,20 @@ def encode(str, kind):
263264
else:
264265
activeParam = ''
265266

267+
theme_styles = get_theme_styles(self.current_view)
268+
266269
return {"signature": signature,
267270
"description": description,
268271
"activeParam": activeParam,
269272
"index": "{0}/{1}".format(self.signature_index + 1,
270273
len(self.signature_help["items"])),
271274
"link": "link",
272-
"fontSize": PopupManager.font_size}
275+
"fontSize": PopupManager.font_size,
276+
"typeStyles": theme_styles["type"],
277+
"keywordStyles": theme_styles["keyword"],
278+
"nameStyles": theme_styles["name"],
279+
"paramStyles": theme_styles["param"],
280+
"textStyles": theme_styles["text"]}
273281

274282
_popup_manager = None
275283

typescript/libs/service_proxy.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,24 @@ def quick_info(self, path, location=Location(1, 1), on_completed=None):
294294
req_dict["seq"]
295295
)
296296

297+
def quick_info_full(self, path, location=Location(1, 1), on_completed=None):
298+
args = {"file": path, "line": location.line, "offset": location.offset}
299+
req_dict = self.create_req_dict("quickinfo-full", args)
300+
json_str = json_helpers.encode(req_dict)
301+
callback = on_completed or (lambda: None)
302+
if not IS_ST2:
303+
self.__comm.sendCmdAsync(
304+
json_str,
305+
callback,
306+
req_dict["seq"]
307+
)
308+
else:
309+
self.__comm.sendCmd(
310+
json_str,
311+
callback,
312+
req_dict["seq"]
313+
)
314+
297315
def save_to(self, path, alternatePath):
298316
args = {"file": path, "tmpfile": alternatePath}
299317
req_dict = self.create_req_dict("saveto", args)

0 commit comments

Comments
 (0)