Skip to content

Commit 288c95d

Browse files
rwolstomv564
authored andcommitted
Implement textdocument/documentHighlight
- Using a queue/purge model like in notifying document changes. - Using "text" scope for unknown and text kinds, "markup" for read/write. - Add the kind of document highlights to protocol.py - Allow users to change the presentation style: * Users can choose underline, stippled or squiggly. * Users can change the scope per kind. - Timeout of 500ms after no selection changes before sending out a document highlight request.
1 parent 7ecade4 commit 288c95d

File tree

5 files changed

+137
-0
lines changed

5 files changed

+137
-0
lines changed

LSP.sublime-settings

+12
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,18 @@
155155
// Valid values are "underline" or "box"
156156
"diagnostics_highlight_style": "underline",
157157

158+
// Highlighting style of "highlights": accentuating nearby text entities that
159+
// are related to the one under your cursor.
160+
// Valid values are "underline", "stippled" or "squiggly".
161+
"document_highlight_style": "underline",
162+
163+
"document_highlight_scopes": {
164+
"unknown": "text",
165+
"text": "text",
166+
"read": "markup.inserted",
167+
"write": "markup.changed"
168+
},
169+
158170
// Gutter marker for code diagnostics.
159171
// Valid values are "bookmark", "circle", "cross", "dot" or ""
160172
"diagnostics_gutter_marker": "dot",

boot.py

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from .plugin.diagnostics import *
99
from .plugin.configuration import *
1010
from .plugin.formatting import *
11+
from .plugin.highlights import *
1112
from .plugin.definition import *
1213
from .plugin.hover import *
1314
from .plugin.references import *

plugin/core/protocol.py

+12
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ class CompletionItemKind(object):
5757
Reference = 18
5858

5959

60+
class DocumentHighlightKind(object):
61+
Unknown = 0
62+
Text = 1
63+
Read = 2
64+
Write = 3
65+
Count = 4
66+
67+
6068
class Request:
6169
def __init__(self, method: str, params: 'Optional[dict]') -> None:
6270
self.method = method
@@ -111,6 +119,10 @@ def rangeFormatting(cls, params: dict):
111119
def documentSymbols(cls, params: dict):
112120
return Request("textDocument/documentSymbol", params)
113121

122+
@classmethod
123+
def documentHighlight(cls, params: dict):
124+
return Request("textDocument/documentHighlight", params)
125+
114126
@classmethod
115127
def resolveCompletionItem(cls, params: dict):
116128
return Request("completionItem/resolve", params)

plugin/core/settings.py

+15
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ def __init__(self):
4343
self.show_diagnostics_in_view_status = True
4444
self.only_show_lsp_completions = False
4545
self.diagnostics_highlight_style = "underline"
46+
self.document_highlight_style = "stippled"
47+
self.document_highlight_scopes = {
48+
"unknown": "text",
49+
"text": "text",
50+
"read": "markup.inserted",
51+
"write": "markup.changed"
52+
}
4653
self.diagnostics_gutter_marker = "dot"
4754
self.complete_all_chars = False
4855
self.completion_hint_type = "auto"
@@ -59,6 +66,14 @@ def update(self, settings_obj: sublime.Settings):
5966
self.show_diagnostics_phantoms = read_bool_setting(settings_obj, "show_diagnostics_phantoms", False)
6067
self.show_diagnostics_in_view_status = read_bool_setting(settings_obj, "show_diagnostics_in_view_status", True)
6168
self.diagnostics_highlight_style = read_str_setting(settings_obj, "diagnostics_highlight_style", "underline")
69+
self.document_highlight_style = read_str_setting(settings_obj, "document_highlight_style", "stippled")
70+
self.document_highlight_scopes = read_dict_setting(
71+
settings_obj, "document_highlight_scopes", {
72+
"unknown": "text",
73+
"text": "text",
74+
"read": "markup.inserted",
75+
"write": "markup.changed"
76+
})
6277
self.diagnostics_gutter_marker = read_str_setting(settings_obj, "diagnostics_gutter_marker", "dot")
6378
self.only_show_lsp_completions = read_bool_setting(settings_obj, "only_show_lsp_completions", False)
6479
self.complete_all_chars = read_bool_setting(settings_obj, "complete_all_chars", True)

plugin/highlights.py

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import sublime_plugin
2+
3+
from .core.configurations import is_supported_syntax
4+
from .core.protocol import Request, Range, DocumentHighlightKind
5+
from .core.clients import client_for_view
6+
from .core.documents import get_document_position
7+
from .core.settings import settings
8+
9+
import sublime # only for typing
10+
11+
12+
_kind2name = {
13+
DocumentHighlightKind.Unknown: "unknown",
14+
DocumentHighlightKind.Text: "text",
15+
DocumentHighlightKind.Read: "read",
16+
DocumentHighlightKind.Write: "write"
17+
}
18+
19+
20+
class DocumentHighlightListener(sublime_plugin.ViewEventListener):
21+
22+
@classmethod
23+
def is_applicable(cls, settings):
24+
syntax = settings.get('syntax')
25+
return syntax and is_supported_syntax(syntax)
26+
27+
def __init__(self, view: sublime.View) -> None:
28+
super().__init__(view)
29+
self._initialized = False
30+
self._enabled = False
31+
self._version = 0
32+
33+
def on_selection_modified_async(self) -> None:
34+
if not self._initialized:
35+
self._initialize()
36+
if self._enabled:
37+
self._clear_regions()
38+
self._queue()
39+
40+
def _queue(self) -> None:
41+
self._version += 1
42+
current_version = self._version
43+
sublime.set_timeout_async(lambda: self._purge(current_version), 500)
44+
45+
def _purge(self, this_version: int) -> None:
46+
if this_version == self._version:
47+
self._on_document_highlight()
48+
49+
def _clear_regions(self) -> None:
50+
for kind in settings.document_highlight_scopes.keys():
51+
self.view.erase_regions("LspDocumentHighlightListener_{}".format(kind))
52+
53+
def _on_document_highlight(self) -> None:
54+
self._clear_regions()
55+
if len(self.view.sel()) != 1:
56+
return
57+
point = self.view.sel()[0].begin()
58+
if self.view.match_selector(point, "comment"):
59+
# We're inside a comment, go home.
60+
return
61+
client = client_for_view(self.view)
62+
if client:
63+
params = get_document_position(self.view, point)
64+
if params:
65+
request = Request.documentHighlight(params)
66+
client.send_request(request, self._handle_response)
67+
68+
def _handle_response(self, response: list) -> None:
69+
if not response:
70+
return
71+
kind2regions = {} # type: Dict[str, List[sublime.Region]]
72+
for kind in range(0, DocumentHighlightKind.Count):
73+
kind2regions[_kind2name[kind]] = []
74+
for highlight in response:
75+
if highlight:
76+
r = Range.from_lsp(highlight["range"]).to_region(self.view)
77+
kind = highlight.get("kind", DocumentHighlightKind.Unknown)
78+
kind2regions[_kind2name[kind]].append(r)
79+
flags = sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE
80+
if settings.document_highlight_style == "underline":
81+
flags |= sublime.DRAW_SOLID_UNDERLINE
82+
elif settings.document_highlight_style == "stippled":
83+
flags |= sublime.DRAW_STIPPLED_UNDERLINE
84+
elif settings.document_highlight_style == "squiggly":
85+
flags |= sublime.DRAW_SQUIGGLY_UNDERLINE
86+
self._clear_regions()
87+
for kind, regions in kind2regions.items():
88+
if regions:
89+
scope = settings.document_highlight_scopes.get(kind, None)
90+
self.view.add_regions("LspDocumentHighlightListener_{}".format(kind),
91+
regions, scope=scope, flags=flags)
92+
93+
def _initialize(self) -> None:
94+
self._initialized = True
95+
client = client_for_view(self.view)
96+
if client:
97+
self._enabled = client.get_capability("documentHighlightProvider")

0 commit comments

Comments
 (0)