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

Semantic highlighting (v2) #1839

Merged
merged 61 commits into from
Dec 27, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
54c5725
Add support for semantic tokens
jwortmann Aug 15, 2021
df71b31
Address some comments
jwortmann Sep 5, 2021
fc4c57a
Update stub file
jwortmann Sep 5, 2021
1fe2740
Make semantic tokens mapping be part of the configuration
jwortmann Sep 8, 2021
8ca086b
Merge branch 'main' into semantic-highlighting
jwortmann Sep 9, 2021
7c9c911
Fix linter error
jwortmann Sep 9, 2021
9a5d518
Only update regions which actually changed
jwortmann Sep 10, 2021
202a3e3
Merge branch 'main' into semantic-highlighting
rwols Sep 12, 2021
10d467f
Initialize region keys for new SessionViews
jwortmann Sep 14, 2021
992324a
Combine all instance variables into a SemanticTokensData class
jwortmann Sep 14, 2021
c7e34f7
Type annotation
jwortmann Sep 14, 2021
21ef85f
I hate this linter
jwortmann Sep 14, 2021
b4983dc
Fix upper bound for diagnostic tags when initializing regins
jwortmann Sep 14, 2021
3519985
Merge branch 'main' into semantic-highlighting
rwols Sep 18, 2021
4f4e7ae
Fix off-by-one error in scope name popup
jwortmann Sep 19, 2021
41ad058
Merge branch 'main' into semantic-highlighting
jwortmann Sep 19, 2021
6beb9d5
Don't update regions if there were changes to the buffer between requ…
jwortmann Sep 23, 2021
bbd04ce
Merge branch 'main' into semantic-highlighting
jwortmann Sep 23, 2021
3f32aaa
Draw scope-less tokens if there is a color scheme rule for it
jwortmann Sep 30, 2021
8cec036
Ensure lowercase scope name and update docs
jwortmann Oct 1, 2021
34f8c60
Merge branch 'main' into semantic-highlighting
jwortmann Oct 1, 2021
d94d2d8
Merge branch 'main' into semantic-highlighting
jwortmann Oct 25, 2021
16ffaeb
Merge branch 'main' into semantic-highlighting
jwortmann Oct 28, 2021
cdc74af
Add error response handler and rename state variable
jwortmann Oct 28, 2021
a8bc0bc
Merge branch 'main' into semantic-highlighting
jwortmann Oct 31, 2021
1aa70fc
Merge branch 'main' into semantic-highlighting
jwortmann Nov 7, 2021
513a901
Adjust scope mapping for constants
jwortmann Nov 13, 2021
8a40191
Add caching for token decoding
jwortmann Nov 13, 2021
dd004c0
Listen for show_scope_name command if semantic highlighting is enabled
jwortmann Nov 14, 2021
0f3aa4a
Move function for token decoding to Session class
jwortmann Nov 14, 2021
2aadb1a
Add required rule for semantic highlighting to the default color schemes
jwortmann Nov 14, 2021
1ee0cd5
Don't check for pending response
jwortmann Nov 16, 2021
d01a3c7
Merge branch 'main' into semantic-highlighting
jwortmann Nov 19, 2021
75ee9b9
Use different strategy for semantic tokens request after text change
jwortmann Nov 19, 2021
bbbb4b8
notify sessionbuffer of sessionview being removed
rchl Nov 19, 2021
5c58891
Address some comments
jwortmann Nov 19, 2021
4ede8d0
Remove token regions when server gets disabled
jwortmann Nov 19, 2021
f32fe9c
Small tweak
jwortmann Nov 20, 2021
0a6f7fb
Use caching for token decoding
jwortmann Nov 21, 2021
eac3ff6
Don't mutate dict while iterating
jwortmann Nov 21, 2021
84f68da
Merge branch 'main' into semantic-highlighting
jwortmann Nov 21, 2021
a162b77
Code formatting and indentation
jwortmann Nov 21, 2021
1a77566
Code cleanup
jwortmann Nov 21, 2021
5885b61
Request semantic tokens for all visible views on refresh
jwortmann Nov 23, 2021
80feebd
Always redraw all regions after semantic tokens response
jwortmann Dec 4, 2021
0a167c1
Allow color schemes to target tokens with modifiers
jwortmann Dec 4, 2021
dd565bb
Update ST API type stub
jwortmann Dec 4, 2021
9ba3a11
Merge branch 'main' into semantic-highlighting
jwortmann Dec 6, 2021
c884364
Allow scope overrides for tokens with one modifier
jwortmann Dec 6, 2021
19a3685
Update tokens for visible views in all groups after refresh request
jwortmann Dec 6, 2021
d96a25f
Update docs
jwortmann Dec 9, 2021
a658a7c
Update meta scopes for tokens with modifiers
jwortmann Dec 9, 2021
d545a1f
Merge branch 'main' into semantic-highlighting
jwortmann Dec 15, 2021
d17ad0d
Declare required methods on SessionBufferProtocol interface
jwortmann Dec 15, 2021
ea80e28
Revert "Add required rule for semantic highlighting to the default co…
jwortmann Dec 15, 2021
6a582d5
Declare getter method for tokens on SessionBufferProtocol
jwortmann Dec 15, 2021
06538c9
Merge branch 'main' into semantic-highlighting
jwortmann Dec 16, 2021
5014576
Cancel request with pending response on new request
jwortmann Dec 20, 2021
814905c
Remove unnecessary type hint
jwortmann Dec 20, 2021
8d7d488
Revert "Revert "Add required rule for semantic highlighting to the de…
jwortmann Dec 20, 2021
1a9e1a9
Cleanup
jwortmann Dec 23, 2021
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
14 changes: 14 additions & 0 deletions Default.sublime-keymap
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,20 @@
// }
// ]
// },
// Show Scope Name (a replacement for ST's "Show Scope Name", but including semantic tokens if applicable)
// {
// "command": "lsp_show_scope_name",
// "keys": [
// "ctrl+alt+shift+p" // Default keybinding for Windows/Linux
// ],
// "context": [
// {
// "key": "lsp.session_with_capability",
// "operator": "equal",
// "operand": "semanticTokensProvider"
// }
// ]
// },
// Internal key-binding
{
"keys": [
Expand Down
7 changes: 7 additions & 0 deletions LSP.sublime-settings
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@
// Valid values are "dot", "circle", "bookmark", "sign" or ""
"diagnostics_gutter_marker": "dot",

// Enable semantic highlighting in addition to standard syntax highlighting (experimental!).
// Note: Must be supported by the language server and also requires a special rule
// in the color scheme to work. Please see the documentation under
// https://lsp.sublimetext.io/customization/#semantic-highlighting
// for a description about how to configure your color scheme for semantic highlighting.
"semantic_highlighting": false,
rwols marked this conversation as resolved.
Show resolved Hide resolved

// Show code actions:
// "annotation" - show an annotation on the right when code actions are available.
// "bulb" - show a bulb in the gutter when code actions are available.
Expand Down
1 change: 1 addition & 0 deletions boot.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
from .plugin.rename import LspSymbolRenameCommand
from .plugin.save_command import LspSaveCommand
from .plugin.selection_range import LspExpandSelectionCommand
from .plugin.semantic_highlighting import LspShowScopeNameCommand
from .plugin.symbols import LspDocumentSymbolsCommand
from .plugin.symbols import LspSelectionAddCommand
from .plugin.symbols import LspSelectionClearCommand
Expand Down
51 changes: 51 additions & 0 deletions docs/src/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,57 @@ There is an example in the [official ST documentation](https://www.sublimetext.c

The following tables give an overview about the scope names used by LSP.

### Semantic Highlighting

!!! info "This feature is only available if the server has the *semanticTokensProvider* capability."
Language servers which support semantic highlighting are for example *clangd*, *rust-analyzer* and *lua-language-server*.

In order to support semantic highlighting, the color scheme requires a special rule with a background color set for semantic tokens, which is (marginally) different from the original background.
To add such a rule to your color scheme, select `UI: Customize Color Scheme` from the Command Palette and add for example the following code:

```json
{
"rules": [
{
"scope": "meta.semantic-token",
"background": "#00000101"
},
]
}
```

Furthermore it is possible to adjust the colors for semantic tokens by applying a foreground color to the individual token types:

| scope | [SemanticTokenTypes](https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#semanticTokenTypes) |
jwortmann marked this conversation as resolved.
Show resolved Hide resolved
| ----- | ------------------ |
| `meta.semantic-token.namespace` | namespace |
| `meta.semantic-token.type` | type |
| `meta.semantic-token.class` | class |
| `meta.semantic-token.enum` | enum |
| `meta.semantic-token.interface` | interface |
| `meta.semantic-token.struct` | struct |
| `meta.semantic-token.typeparameter` | typeParameter |
| `meta.semantic-token.parameter` | parameter |
| `meta.semantic-token.variable` | variable |
| `meta.semantic-token.property` | property |
| `meta.semantic-token.enummember` | enumMember |
| `meta.semantic-token.event` | event |
| `meta.semantic-token.function` | function |
| `meta.semantic-token.method` | method |
| `meta.semantic-token.macro` | macro |
| `meta.semantic-token.keyword` | keyword |
| `meta.semantic-token.modifier` | modifier |
| `meta.semantic-token.comment` | comment |
| `meta.semantic-token.string` | string |
| `meta.semantic-token.number` | number |
| `meta.semantic-token.regexp` | regexp |
| `meta.semantic-token.operator` | operator |

By default, LSP will assign scopes based on the [scope naming guideline](https://www.sublimetext.com/docs/scope_naming.html) to each of these token types, but if you define rules (with foreground colors) for the scopes specified above, the latter will take precedence.

Language servers can also add their own non-standard token types.
Those will only be highlighted if they are supported by a "LSP-*" helper package.

### Document Highlights

!!! info "This feature is only available if the server has the *documentHighlightProvider* capability."
Expand Down
12 changes: 12 additions & 0 deletions plugin/core/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,18 @@ def documentSymbols(cls, params: Mapping[str, Any], view: sublime.View) -> 'Requ
def documentHighlight(cls, params: Mapping[str, Any], view: sublime.View) -> 'Request':
return Request("textDocument/documentHighlight", params, view)

@classmethod
def semanticTokensFull(cls, params: Mapping[str, Any], view: sublime.View) -> 'Request':
return Request("textDocument/semanticTokens/full", params, view)

@classmethod
def semanticTokensFullDelta(cls, params: Mapping[str, Any], view: sublime.View) -> 'Request':
return Request("textDocument/semanticTokens/full/delta", params, view)

@classmethod
def semanticTokensRange(cls, params: Mapping[str, Any], view: sublime.View) -> 'Request':
return Request("textDocument/semanticTokens/range", params, view)

@classmethod
def resolveCompletionItem(cls, params: CompletionItem, view: sublime.View) -> 'Request':
return Request("completionItem/resolve", params, view)
Expand Down
83 changes: 82 additions & 1 deletion plugin/core/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,56 @@ def get_initialize_params(variables: Dict[str, str], workspace_folders: List[Wor
},
"codeLens": {
"dynamicRegistration": True
},
"semanticTokens": {
"dynamicRegistration": True,
"requests": {
"range": True,
"full": {
"delta": True
}
},
"tokenTypes": [ # TODO append additional token types supported by a helper plugin
"namespace",
"type",
"class",
"enum",
"interface",
"struct",
"typeParameter",
"parameter",
"variable",
"property",
"enumMember",
"event",
"function",
"method",
"macro",
"keyword",
"modifier",
"comment",
"string",
"number",
"regexp",
"operator"
],
"tokenModifiers": [
"declaration",
"definition",
"readonly",
"static",
"deprecated",
"abstract",
"async",
"modification",
"documentation",
"defaultLibrary"
],
"formats": [
"relative"
],
"overlappingTokenSupport": False,
"multilineTokenSupport": True
}
}
workspace_capabilites = {
Expand All @@ -301,7 +351,10 @@ def get_initialize_params(variables: Dict[str, str], workspace_folders: List[Wor
"valueSet": symbol_tag_value_set
}
},
"configuration": True
"configuration": True,
"semanticTokens": {
"refreshSupport": True
}
}
window_capabilities = {
"showDocument": {
Expand Down Expand Up @@ -500,6 +553,15 @@ def additional_variables(cls) -> Optional[Dict[str, str]]:
"""
return None

@classmethod
def semantic_tokens(cls) -> Dict[str, str]:
"""
If the server is a semanticTokensProvider and uses custom token types that are not defined in the protocol,
you can provide a mapping which assigns scopes to token types and is used by the color scheme for semantic
highlighting. Keys of the returned dictionary should be token types and values are the corresponding scopes.
"""
return {}

@classmethod
def storage_path(cls) -> str:
"""
Expand Down Expand Up @@ -1104,6 +1166,12 @@ def _handle_initialize_success(self, result: Any) -> None:
code_action_kinds = self.get_capability('codeActionProvider.codeActionKinds')
if code_action_kinds:
debug('{}: supported code action kinds: {}'.format(self.config.name, code_action_kinds))
semantic_token_types = self.get_capability('semanticTokensProvider.legend.tokenTypes')
jwortmann marked this conversation as resolved.
Show resolved Hide resolved
if semantic_token_types is not None:
debug('{}: Supported semantic token types: {}'.format(self.config.name, semantic_token_types))
semantic_token_modifiers = self.get_capability('semanticTokensProvider.legend.tokenModifiers')
if semantic_token_modifiers is not None:
debug('{}: Supported semantic token modifiers: {}'.format(self.config.name, semantic_token_modifiers))
if self._watcher_impl:
config = self.config.file_watcher
pattern = config.get('pattern')
Expand Down Expand Up @@ -1323,6 +1391,19 @@ def m_workspace_applyEdit(self, params: Any, request_id: Any) -> None:
self._apply_workspace_edit_async(params.get('edit', {})).then(
lambda _: self.send_response(Response(request_id, {"applied": True})))

def m_workspace_semanticTokens_refresh(self, params: Any, request_id: Any) -> None:
"""handles the workspace/semanticTokens/refresh request"""
self.send_response(Response(request_id, None))
debug("server requested a refresh of semantic tokens")
jwortmann marked this conversation as resolved.
Show resolved Hide resolved
# TODO should we send a request only for the active view, or also for all other SessionBuffers of the Session?
active_view = sublime.active_window().active_view()
if not active_view:
return
session_view = self.session_view_for_view_async(active_view)
if not session_view:
return
session_view.session_buffer.do_semantic_tokens_async()

def m_textDocument_publishDiagnostics(self, params: Any) -> None:
"""handles the textDocument/publishDiagnostics notification"""
uri = params["uri"]
Expand Down
2 changes: 2 additions & 0 deletions plugin/core/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ class Settings:
only_show_lsp_completions = None # type: bool
popup_max_characters_height = None # type: int
popup_max_characters_width = None # type: int
semantic_highlighting = None # type: bool
rwols marked this conversation as resolved.
Show resolved Hide resolved
show_code_actions = None # type: str
show_code_lens = None # type: str
show_code_actions_in_hover = None # type: bool
Expand Down Expand Up @@ -236,6 +237,7 @@ def r(name: str, default: Union[bool, int, str, list, dict]) -> None:
r("only_show_lsp_completions", False)
r("popup_max_characters_height", 1000)
r("popup_max_characters_width", 120)
r("semantic_highlighting", False)
rwols marked this conversation as resolved.
Show resolved Hide resolved
r("show_code_actions", "annotation")
r("show_code_lens", "annotation")
r("show_code_actions_in_hover", True)
Expand Down
15 changes: 15 additions & 0 deletions plugin/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ class DocumentSyncListener(sublime_plugin.ViewEventListener, AbstractViewListene
color_boxes_debounce_time = FEATURES_TIMEOUT
highlights_debounce_time = FEATURES_TIMEOUT
code_lenses_debounce_time = FEATURES_TIMEOUT
semantic_tokens_debounce_time = FEATURES_TIMEOUT

_uri = None # type: str

Expand Down Expand Up @@ -227,6 +228,7 @@ def on_session_initialized_async(self, session: Session) -> None:
if added:
self._do_color_boxes_async()
self._do_code_lenses_async()
self._do_semantic_tokens_async()

def on_session_shutdown_async(self, session: Session) -> None:
removed_session = self._session_views.pop(session.config.name, None)
Expand Down Expand Up @@ -340,6 +342,8 @@ def on_text_changed_async(self, change_count: int, changes: Iterable[sublime.Tex
self.do_signature_help_async(manual=False)
self._when_selection_remains_stable_async(self._do_code_lenses_async, current_region,
after_ms=self.code_lenses_debounce_time)
self._when_selection_remains_stable_async(self._do_semantic_tokens_async, current_region,
after_ms=self.semantic_tokens_debounce_time)

def get_uri(self) -> str:
return self._uri
Expand Down Expand Up @@ -635,6 +639,17 @@ def render_highlights_on_main_thread() -> None:

sublime.set_timeout(render_highlights_on_main_thread)

# --- textDocument/semanticTokens ----------------------------------------------------------------------------------

def _do_semantic_tokens_async(self) -> None:
session = self.session_async("semanticTokensProvider")
if not session:
return
session_view = session.session_view_for_view_async(self.view)
if not session_view:
return
session_view.session_buffer.do_semantic_tokens_async()
rchl marked this conversation as resolved.
Show resolved Hide resolved

# --- textDocument/complete ----------------------------------------------------------------------------------------

def _on_query_completions_async(self, resolve_completion_list: ResolveCompletionsFn, location: int) -> None:
Expand Down
Loading