语义 Token
clice 使用自有的 Token 类型体系对文档中的每个 Token 进行分类。这套体系比标准 LSP Token 类型更丰富,并在 clice 的所有响应中保持一致。偏好标准 LSP 类型的客户端可以通过配置进行映射。
词法 Token
直接从 Token 流本身推导出的类型,不依赖 AST。
注释
行注释、块注释和文档注释均标记为注释 Token
字面量
数字、字符和字符串均标记为字面量 Token
关键字
运算符的替代拼写和上下文说明符仍标记为关键字 Token
预处理指令
#if 指令链保留指令类型;未启用的分支保留词法类型;pragma 参数保持普通文本样式
非活动区域
未选中分支中的 Token 保留其词法类型,并带有 inactive 修饰符;未分类的 Token 则归为普通的 identifier 类型以承载该修饰符,因此即使某行只有一个 },也会显示为暗淡样式
头文件名
包含指令中用引号或尖括号括起的文件名均标记为字符串 Token
Preamble 中的非活动区域
文件开头各指令中未选中的分支也以相同方式显示为暗淡样式
字面量前缀和后缀
字面量的前缀、后缀和分隔符尚未标记为独立的 Token
转义序列
字面量中的转义序列尚未单独高亮显示
声明符与运算符的区分
声明符中的运算符和表达式中的运算符尚未使用不同的 Token 类型
基本类型的 Token 类型
内置类型使用独立的 Token 类型,而非普通的 keyword
括号的 Token 类型
匹配的括号尚未使用各对括号专属的 Token 类型
声明
名称按其所定义或引用的声明进行分类。
命名空间
命名空间的定义、引用、嵌套和别名均标记为命名空间 Token
类型
类型定义和引用保留各自的类型类别
函数和方法
函数声明、定义和调用均标记为函数 Token
变量
变量声明和引用保留各自的变量类别
模板
模板参数标记为类型或变量类别,模板名称带有 templated 修饰符
Concept
Concept 的定义及其在约束中的使用均标记为 Concept Token
标签
标签及其在 goto 中的引用均标记为标签 Token
结构化绑定(structured bindings)
结构化绑定名称在定义和使用处均标记为变量 Token
起始的 [ 特意不标记为任何 Token;仅高亮显示绑定名称本身。
Lambda 初始化捕获
Lambda 初始化捕获标记为变量 Token
推导指引(deduction guides)
推导指引及其所引导的模板均标记为类型 Token
显式实例化
类的显式实例化会高亮显示模板名称和显式写出的实参
变量模板
变量模板的声明、定义和特化均标记为变量 Token
类外成员定义
限定名称保留方法类别和修饰符
别名模板
别名名称带有类型类别和 templated 修饰符
模板模板参数
模板模板参数(template-template parameters)在声明和使用处均获得类型 Token
友元声明
友元声明中的名称解析到其目标;内联友元标记为定义
函数显式实例化指令
函数显式实例化指令中的标识符会被高亮
变量显式实例化指令
变量显式实例化指令中的标识符会被高亮
显式实例化的成员体
待决名(dependent names)按实际解析结果高亮:各实例化中的种类一致时,保留所有实例化共有的修饰符;种类不一致时,标记为冲突
引用
引用处保留其所解析到的声明的语义种类,通过语言特有的查找规则找到的名称也不例外。
成员初始化列表
成员初始化列表中被初始化的名称按字段高亮
using 声明
引入的名称保留其目标的种类
sizeof...
参数包保留其类型参数 Token
using enum
using 声明在 using 处高亮枚举名称
待决名
待决名通过已知的主模板解析
已知模板(Box<T>)的待决成员解析到主模板中的声明,并保留这些声明的种类。直接以模板参数为所属类型的成员没有候选声明,目前不获得 Token;对此类名称的启发式高亮仍有待解决。
Lambda 捕获
按复制捕获和按引用捕获都引用被捕获的变量;this 仍作为关键字
范围 for 循环
范围 for 循环变量在定义和使用处均保留变量 Token
枚举底层类型
枚举底层类型的引用保留其类型种类
待决 using 声明
待决 using 声明仍不高亮
引入的名称及其使用处目前均不获得 Token;预留的待决名修饰符尚未输出。
模块
模块声明
模块声明为上下文关键字、以点分隔的名称和私有模块片段生成 Token
模块分区
模块声明为分区名称生成 Token
作为标识符的 module 和 import
上下文关键字在模块声明之外保留其语义种类
Token 修饰符
声明与定义
声明修饰符和定义修饰符用于区分这两种位置
静态
静态成员和静态局部变量带有静态修饰符
只读
const 值、const 成员函数以及枚举成员带有只读修饰符
目前只读属性根据值判断:指向 const 对象的指针也视为只读,即使指针本身可以改变。
虚与抽象
虚成员函数和抽象类分别带有虚修饰符和抽象修饰符
已弃用
已弃用的声明及其使用处带有已弃用修饰符
默认库
来自系统头文件的符号带有默认库修饰符
作用域修饰符
符号尚不带有函数、类、文件或全局作用域修饰符
可变引用和指针
可变引用和指针实参尚不带有修饰符
推导
推导出的类型尚不带有专用修饰符
用户定义的运算符
重载运算符目前尚未与内置运算符区分
冲突与歧义
C++ 允许结构不同的实体共用一个名称。当代码中的同一个名称同时指向不同种类的实体时,没有任何单一的 Token 类型能准确表示它;这类名称会获得专用的 conflict(冲突)Token 类型,客户端通常以中性色显示。
类型与函数
同时指代类型和函数的名称标注为 conflict
类型与变量
同时指代类型和变量的名称标注为 conflict
同类重载集
仅指代函数的名称不会产生冲突
注入类名(injected class name)
注入类名用作构造函数名称时,仍保留类 Token
代码中的名称按类标注,其隐含的构造函数引用不产生额外高亮,( 仍不带 Token。
Token 的正确性
clice 明确固定的 Token 标注行为,包括 clangd 曾处理错误的情况。
构造函数与析构函数
构造函数和析构函数使用带有专用修饰符的方法 Token
析构函数名称标注为两个 Token:~ 带有方法类型以及声明/定义修饰符,其后的类名仍是对类的引用。
匿名参数
未命名参数不产生 Token
未命名参数类型后的标点仍不带 Token。
运算符名称
operator 关键字和调用处的标点保持原样
代码中的运算符名称由关键字和标点组成,因此不添加名称 Token:operator 保留其关键字分类,调用处的运算符符号不产生 Token。
类模板的析构函数
~ 的标注方式同样适用于模板
转换运算符
名称以关键字形式书写,转换调用不产生额外高亮
模板参数上的伪析构函数
~ 不产生高亮,类型名称保留其 Token 类型
显式默认与删除的成员
特殊成员名称保留其定义 Token
属性
属性名称
属性名称及其表达式目前尚不获得语义 Token
宏
宏定义体内的 Token 保留其词法类型;根据宏展开结果为它们添加高亮,将由未来的展开预览功能实现。
宏定义与展开
宏定义和展开会获得语义 Token
展开位置与实参
展开处的名称标注为宏,代码中的实参保留其语义,定义体仍按词法标注
对象式宏与函数式宏
对象式宏和函数式宏目前尚未使用不同的 Token 类型
其他已知不足
已整理但尚无测试用例的问题:
-
auto参数不得高亮为模板类型参数 (clangd#1390) - 成员指针中的嵌套名说明符(nested name specifier)应获得 Token (clangd#2235)
-
::new中的new关键字应保持高亮 (clangd#1627) - 当协程返回类型为模板时,
co_yield/co_await会丢失高亮(clangd#2437) - Token 修饰符应应用于重载运算符的操作数(clangd#2547)
- 依赖模板名(
obj.template get<int>())、通过using从依赖基类引入的成员, 以及重载集合种类混杂的依赖名(clangd#484、 clangd#686、 clangd#1057)
非活动代码区域
未选中的预处理器分支内,每个 Token 都带有 inactive 修饰符,同时保留其词法类别,因此编辑器可以通过设置该修饰符的样式来淡化显示整个区域,同时保留原有的语法着色。非活动代码中没有分类的 Token(普通标识符和标点符号)以不带样式的 identifier 类型输出,使 Token 覆盖整个区域。clice 的 VS Code 扩展默认会淡化显示这些区域;其他编辑器则直接为该修饰符设置样式(例如 Neovim 中的 @lsp.mod.inactive)。
- 淡化显示非活动预处理器分支(clangd#132)
- 正确处理
#elif链中的非活动区域边界(clangd#602) - 保留非活动区域内的语法高亮(clangd#1664)
- 保持非活动区域与注释的区别(clangd#1545)
- 淡化显示不可达代码(clangd#1828)
格式字符串高亮
-
std::format/std::print占位符高亮(clangd#1709) - 将无效的格式说明符高亮显示为错误
协议支持
- 全文档语义 Token(
textDocument/semanticTokens/full) - 以 UTF-16 码元为单位、采用差分编码的 Token 位置
- 指定范围的语义 Token(
textDocument/semanticTokens/range)——仅计算可见视口内的 Token,这对大文件至关重要 - 增量更新(
textDocument/semanticTokens/full/delta)——仅发送自上次响应以来的变化
