Skip to content

Commit

Permalink
Highlight some semantic tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
xhcoding committed Nov 5, 2023
1 parent cba7cd6 commit dc0ce37
Show file tree
Hide file tree
Showing 7 changed files with 457 additions and 3 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ Note:
- `lsp-bridge-peek-tree-next-node`: Select the next lower-level node in the browsing history (default binding to `<right>`)
- `lsp-bridge-indent-left`: Indents the pasted text to the left according to the indent values defined in `lsp-bridge-formatting-indent-alist`
- `lsp-bridge-indent-right`: Indents the pasted text to the right according to the indent values defined in `lsp-bridge-formatting-indent-alist`
- `lsp-bridge-semantic-tokens-mode`: Enable or disable semantic token highlighting, Please refer to [Semantic Tokens Wiki](https://github.com/manateelazycat/lsp-bridge/wiki/Semantic-Tokens) for detailed instructions on how to use.

## LSP server options
lsp-bridge provides support for more than two language servers for many languages. You can customize the following options to choose the language server you prefer:
Expand Down Expand Up @@ -379,6 +380,7 @@ The following is the directory structure of the lsp-bridge project:
| lsp-bridge-inlay-hint.el | Provides code type hints, more useful for static languages, such as Rust or Haskell |
| lsp-bridge-jdtls.el | Provides third-party library jump function for Java language |
| lsp-bridge-dart.el | Provides support for Dart's private protocols, such as Dart's Closing Labels protocol |
| lsp-bridge-semantic-tokens.el | Flexible display of certain semantic symbols is especially useful for static languages such as C or C++. |
| lsp-bridge-lsp-installer.el | Install TabNine and Omnisharp |
| lsp-bridge-peek.el | Use peek windows to view definitions and references |
| lsp-bridge.py | The main Python logic part of lsp-bridge, providing event loops, message scheduling, and status management |
Expand Down
6 changes: 4 additions & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ lsp-bridge 开箱即用, 安装好语言对应的 [LSP 服务器](https://gith
- `lsp-bridge-peek-tree-next-node`: 选择浏览历史上下一级节点 (默认绑定到 `<right>` )
- `lsp-bridge-indent-left`: 根据 `lsp-bridge-formatting-indent-alist` 定义的缩进值, 向左缩进刚刚粘贴的文本
- `lsp-bridge-indent-right`: 根据 `lsp-bridge-formatting-indent-alist` 定义的缩进值, 向右缩进刚刚粘贴的文本
- `lsp-bridge-semantic-tokens-mode`: 开启或者关闭语义符号高亮,详细用法请看 [Semantic Tokens Wiki](https://github.com/manateelazycat/lsp-bridge/wiki/Semantic-Tokens-%5B%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87%5D)

## LSP 服务器选项
lsp-bridge 针对许多语言都提供 2 个以上的语言服务器支持, 您可以通过定制下面的选项来选择你喜欢的语言服务器:
Expand Down Expand Up @@ -376,9 +377,10 @@ lsp-bridge 每种语言的服务器配置存储在 [lsp-bridge/langserver](https
| lsp-bridge-code-action.el | 代码修复相关代码 |
| lsp-bridge-diagnostic.el | 诊断信息相关代码 |
| lsp-bridge-ref.el | 代码引用查看框架, 提供引用查看、 批量重命名、 引用结果正则过滤等, 核心代码 fork 自 color-rg.el |
| lsp-bridge-inlay-hint.el | 提供代码类型提示, 对于静态语言, 比如 Rust 或 Haskell 比较有用 |
| lsp-bridge-inlay-hint.el | 提供代码类型提示, 对于静态语言, 比如 Rust 或 Haskell 比较有用 |
| lsp-bridge-jdtls.el | 提供 Java 语言第三方库跳转功能 |
| lsp-bridge-dart.el | 提供对 Dart 私有协议的支持, 比如 Dart 的 Closing Labels 协议 |
| lsp-bridge-dart.el | 提供对 Dart 私有协议的支持, 比如 Dart 的 Closing Labels 协议 |
| lsp-bridge-semantic-tokens.el | 灵活显示某些语义符号, 对于静态语言, 比如 C 或 C++ 比较有用 |
| lsp-bridge-lsp-installer.el | 安装 TabNine 和 Omnisharp |
| lsp-bridge-peek.el | 用 peek windows 来查看定义和引用 |
| lsp-bridge.py | lsp-bridge 的 Python 主逻辑部分, 提供事件循环、 消息调度和状态管理 |
Expand Down
1 change: 1 addition & 0 deletions core/handler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,4 @@ def handle_response(self, request_id, response):
from core.handler.jdtls.jdtls_list_overridable_methods import JdtlsListOverridableMethods
from core.handler.jdtls.jdtls_add_overridable_methods import JdtlsAddOverridableMethods
from core.handler.inlay_hint import InlayHint
from core.handler.semantic_tokens import SemanticTokens
162 changes: 162 additions & 0 deletions core/handler/semantic_tokens.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import time

from core.handler import Handler
from core.utils import *

class SemanticTokens(Handler):
name = "semantic_tokens"
method = "textDocument/semanticTokens/full"
cancel_on_change = False
send_document_uri = True
provider = "semantic_tokens_provider"

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.buffer_name = None
self.range_begin = None
self.range_end = None

self.pre_last_change = (time.time(), time.time())
self.tokens = []
self.render_tokens = set()

self.type_face_names = None
self.type_face_names_dict = dict()
self.type_modifier_face_names = None
self.ignore_modifier_limit_types = None


def process_request(self, buffer_name, range_begin, range_end, use_cache):
self.buffer_name = buffer_name
self.range_begin = range_begin
self.range_end = range_end
if not use_cache:
self.render_tokens = set()
return dict()


def process_response(self, response):
if response is None:
return
data = response.get("data")
if data is None:
return
self.tokens = data
self.update_tokens(self.tokens)

def update_tokens(self, tokens):
if tokens is None:
return
index = 0
cur_line = 0
start_character = 0
render_tokens = set()
while index < len(tokens):
if tokens[index] != 0:
start_character = 0
cur_line += tokens[index]
start_character += tokens[index + 1]
if (cur_line >= self.range_begin["line"] and
cur_line <= self.range_end["line"]):
faces_index = self.get_faces_index(tokens[index + 3], tokens[index + 4])
if faces_index is not None:
render_tokens.add((cur_line, start_character, tokens[index + 2], faces_index[0], faces_index[1]))
index = index + 5

(new_tokens, old_tokens) = self.calc_diff_tokens(self.render_tokens, render_tokens)

if len(new_tokens) != 0 or len(old_tokens) != 0:
eval_in_emacs("lsp-bridge-semantic-tokens--update", self.buffer_name, list(old_tokens), self.absolute_line_to_relative(new_tokens))
self.render_tokens = render_tokens

def get_faces_index(self, type_index, type_modifier_index):
type_name = self.file_action.single_server.semantic_tokens_provider["legend"]["tokenTypes"][type_index]
ignore_modifier = self.is_ignore_modifier(type_name)
if type_modifier_index == 0 and not ignore_modifier:
return None
type_face_index = self.get_type_face_index(type_name)
if type_face_index is None:
return None
type_modifier_faces_index = self.get_type_modifier_faces_index(type_modifier_index)
if len(type_modifier_faces_index) == 0 and not ignore_modifier:
return None
return (type_face_index, type_modifier_faces_index)

def get_type_face_index(self, type_name):
if self.type_face_names is None:
[type_faces] = get_emacs_vars(["lsp-bridge-semantic-tokens-type-faces"])
if type_faces is not None:
faces = type_faces.I
self.type_face_names = dict()
for index in range(len(faces)):
self.type_face_names[faces[index][0]] = index
else:
return None
face_index = self.type_face_names.get(type_name)
return face_index

def get_type_modifier_faces_index(self, type_modifier_index):
if self.type_modifier_face_names is None:
[type_modifier_faces] = get_emacs_vars(["lsp-bridge-semantic-tokens-type-modifier-faces"])
if type_modifier_faces is not None:
faces = type_modifier_faces.I
self.type_modifier_face_names = dict()
for index in range(len(faces)):
self.type_modifier_face_names[faces[index][0]] = index
else:
return ()

token_modifiers = self.file_action.single_server.semantic_tokens_provider["legend"]["tokenModifiers"]
type_modifier_names = [token_modifiers[index] for index in self.find_ones(type_modifier_index)]
type_modifier_faces_index = []
for name in type_modifier_names:
index = self.type_modifier_face_names.get(name)
if index is not None:
type_modifier_faces_index.append(index)
return tuple(type_modifier_faces_index)

def is_ignore_modifier(self, type_name):
if self.ignore_modifier_limit_types is None:
[types] = get_emacs_vars(["lsp-bridge-semantic-tokens-ignore-modifier-limit-types"])
types = types.I
if types is not None:
self.ignore_modifier_limit_types = dict()
for index in range(len(types)):
self.ignore_modifier_limit_types[types[index]] = index

return type_name in self.ignore_modifier_limit_types

def calc_diff_tokens(self, pre_tokens, cur_tokens):
common_tokens = cur_tokens & pre_tokens
new_tokens = list(cur_tokens - common_tokens)
old_tokens = list(pre_tokens - common_tokens)
new_tokens.sort(key = lambda token : token[0])
old_tokens.sort(key = lambda token : token[0])
return (new_tokens, old_tokens)

def absolute_line_to_relative(self, tokens):
relative_tokens = []
cur_line = 0
delta_line = 0
start_character = 0
delta_character = 0
for token in tokens:
delta_line = token[0] - cur_line
if token[0] != cur_line:
cur_line = token[0]
start_character = 0
delta_character = token[1] - start_character
start_character = token[1]
relative_tokens.append((delta_line, delta_character, token[2], token[3], token[4]))

return relative_tokens

def find_ones(self, num):
ones = []
bit = 0
while num > 0:
if num & 1:
ones.append(bit)
num >>= 1
bit += 1
return ones
4 changes: 3 additions & 1 deletion core/lspserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ def __init__(self, message_queue, project_path, server_info, server_name, enable
self.signature_help_provider = False
self.workspace_symbol_provider = False
self.inlay_hint_provider = False
self.semantic_tokens_provider = False

self.code_action_kinds = [
"quickfix",
Expand Down Expand Up @@ -608,7 +609,8 @@ def save_attribute_from_message(self, message):
("workspace_symbol_provider", ["result", "capabilities", "workspaceSymbolProvider"]),
("inlay_hint_provider", ["result", "capabilities", "inlayHintProvider", "resolveProvider"]),
("save_include_text", ["result", "capabilities", "textDocumentSync", "save", "includeText"]),
("text_document_sync", ["result", "capabilities", "textDocumentSync"])]
("text_document_sync", ["result", "capabilities", "textDocumentSync"]),
("semantic_tokens_provider", ["result", "capabilities", "semanticTokensProvider"])]

for attr, path in attributes_to_set:
self.set_attribute_from_message(message, attr, path)
Expand Down
Loading

0 comments on commit dc0ce37

Please sign in to comment.