代码导航
跳转到定义
跨 TU 跳转到定义
从一个翻译单元中的使用位置,可以跳转到同一项目中另一个源文件提供的定义,查找范围覆盖整个项目,而不局限于当前文件
定义与声明相互跳转
在声明与定义之间相互跳转
从使用位置发起请求会跳转到定义,从声明或定义处发起请求则会跳转到另一处。对于没有单独声明的内联符号,跳转目标仍是其定义。
仅有声明的符号导航
对于只有声明的符号,如纯虚函数、extern 变量、类内静态常量,会跳转到该声明,而不是不返回任何结果
在 #include 指令上跳转到定义
在包含指令上执行跳转到定义,会打开其引用的文件
文件开头的包含指令与文件后面普通的包含指令行为一致。
局部符号导航
在局部变量或参数上执行跳转到定义,会跳转到它在函数体内的声明
宏包装导航
宏实参中直接写出的名称以其书写位置为定位点,因此可以像普通位置一样在定义与声明之间相互跳转;从后续使用位置发起跳转时,也能穿过宏包装,定位到它所声明的函数
宏生成的名称
通过 Token 拼接生成的名称在源码中没有独立的书写位置,因此以生成它的宏调用为定位点:该调用就是它的定义位置,在普通代码中使用该名称时,可以跳转回该调用
宏体导航
宏体中写出的 Token 只有在宏展开时才获得具体含义,因此在其上执行导航不会返回结果,而宏调用处的 Token 始终会跳转到所展开的宏
错误恢复
变量类型无法解析时,无法跳转到该变量的声明
当变量的类型名无法解析时,即使仍然记录了变量自身的声明,目前在该变量的后续使用位置执行跳转到定义也不会返回结果。
待决成员导航
在从未实例化的模板中,访问待决类型(dependent type)对象的成员时,可以跳转到对应类模板中声明的成员
依赖调用的重载候选
依赖调用可能命中多个重载时,会逐一列出这些重载
每个候选各自给出结果:有定义的跳转到定义,没有定义的跳转到声明。
依赖表达式的成员
在依赖下标、调用或 auto 变量的结果上访问成员时,解析到类模板中声明的成员
调用存在 const 重载时,由对象是否为 const 决定解析到哪一个重载;参数不同的各个重载则全部列出。
隐式目标
跳转到隐式调用的代码定义。在 C++ 中,许多语法结构会隐式调用构造函数、运算符、转换函数等。从语法结构(花括号、关键字、运算符 Token)跳转到实际调用的函数,对于理解究竟执行了哪些代码至关重要。
隐式导航要求源码中的 Token 没有歧义。如果 Token 已有明确的“转到定义”目标(例如,变量名始终跳转到其声明),就不能再用它来导航到隐式调用。
override / final
override 和 final 尚不能跳转到被重写的基类方法
在 override 或 final 说明符上执行“转到定义”,无法跳转到被重写的基类虚方法。
break / continue
break 和 continue 尚不能跳转到其所在的控制语句
在 break 或 continue 上执行“转到定义”,无法跳转到其控制的循环或 switch 语句的头部。
delete 表达式
delete 尚不能跳转到所调用的析构函数
在 delete 上执行“转到定义”,无法跳转到它调用的析构函数。
new 表达式
new 可跳转到重载的内存分配函数,但不能跳转到构造函数
在 new 上执行“转到定义”,可跳转到类中重载的 operator new。返回结果不包含同一表达式调用的构造函数。
重载运算符
重载运算符的 Token 可跳转到其定义
在重载运算符的 Token 上执行“转到定义”,可跳转到该运算符的定义。二元、下标、调用和箭头运算符(+、[]、()、->)均可解析。
C++20 重写运算符
重写后的比较可跳转到实际实现该比较的运算符
对于按 C++20 重写规则合成的比较,在源码中写出的运算符上执行“转到定义”,可跳转到实际实现该比较的运算符:!= 跳转到 operator==,> 跳转到 operator<=>。
用户定义字面量(user-defined literals)
字面量后缀尚不能跳转到对应的用户定义字面量运算符
在用户定义字面量的后缀上执行“转到定义”,无法跳转到对应的 operator""。
强制类型转换导航
调用构造函数的强制类型转换可跳转到所选的构造函数
调用用户定义转换运算符的 static_cast 尚不能跳转到该运算符。
范围 for 循环(range-based for)
范围 for 循环中的冒号尚不能跳转到 begin() 或 end()
在范围 for 循环的 : 上执行“转到定义”,无法跳转到为该范围选用的 begin() 或 end()。
co_await / co_yield / co_return
co_yield 可跳转到 promise 对象的方法,其他协程关键字则不能
在 co_yield 上执行“转到定义”,可跳转到 promise 对象的 yield_value。co_await 和 co_return 关键字尚不能跳转到等待器(awaiter)或 promise 对象的方法。
隐式构造
从初始化、返回、捕获和分解语法执行导航,可跳转到隐式选定的构造函数、聚合类型定义或绑定。
构造函数调用
圆括号和花括号可跳转到所选的构造函数
在构造函数调用的左圆括号或左花括号上执行“跳转到定义”,可跳转到重载决议选中的构造函数,适用于 T(args) 和 T{args} 两种形式。
拷贝/移动构造与赋值
赋值中的 = 可跳转到赋值运算符,拷贝初始化和移动初始化中的 = 则不可以
在赋值中的 = 上执行“跳转到定义”,可跳转到赋值运算符。引入拷贝初始化或移动初始化的 =(T b = a;)属于初始化语法,而非运算符调用,目前尚无法解析。
CTAD
使用 CTAD 的构造调用可跳转到推导出的特化的构造函数
当类模板实参推导选中特化时,在构造函数调用上执行“跳转到定义”,可跳转到选中的构造函数,而不只是类模板。
聚合初始化
聚合初始化的花括号可跳转到聚合类型的定义
聚合类型没有构造函数,因此在其初始化器的花括号上执行“跳转到定义”,会跳转到聚合类型的定义。
成员初始化列表
成员初始化器的圆括号可跳转到选中的基类或成员构造函数
在初始化列表中各初始化器的左圆括号上,可跳转到该初始化器调用的基类或成员构造函数。初始化器名称本身会解析到基类类型或成员,因此需要通过圆括号跳转到构造函数。
委托构造函数
委托构造函数中委托调用的圆括号可跳转到目标构造函数
在委托调用的左圆括号上,可跳转到委托构造函数的目标构造函数。构造函数名称本身会解析到类类型,因此需要通过圆括号跳转到目标构造函数。
继承构造函数
继承构造函数的声明可跳转到引入的每一个基类构造函数
在继承构造函数的声明(using Base::Base;)上执行“跳转到定义”,会列出它所引入的基类的每一个构造函数。
返回值的隐式构造
花括号形式的返回值可跳转到选中的构造函数
花括号形式的 return {args} 会隐式构造函数返回类型的对象;在花括号上执行“跳转到定义”,可跳转到选中的构造函数。
Lambda 初始化捕获
Lambda 初始化捕获尚无法跳转到其移动构造函数
在 Lambda 初始化捕获的 = 上执行“跳转到定义”,无法跳转到构造捕获值的构造函数。
结构化绑定(structured bindings)
结构化绑定名称可跳转到绑定本身,而非底层字段或访问器
在结构化绑定名称上执行“跳转到定义”,会解析到绑定本身,而非该名称所对应的底层字段或访问器。
跳转到声明
从符号的使用处或定义处跳转到其声明。在 C++ 中,许多实体的声明和定义是分开的。
clice 返回声明和定义的位置,并排除光标当前所在的位置;内联定义的符号没有单独的声明。因此,声明与定义位置会像“跳转到定义”一样交替跳转。
跨 TU 跳转到声明
在使用处执行“跳转到声明”可找到其他文件中的位置:函数原型位于共享头文件中,定义则单独放在同级源文件中,从另一文件中的使用处跳转时,两者都会列出
函数
从使用处和单独给出的定义处跳转到函数原型
从调用处和单独给出的定义处执行“跳转到声明”,都能到达函数原型;原型会与这两处非当前光标位置交替跳转。
前置声明的记录类型
如果一个类先有前置声明,随后才有定义,从使用处跳转时会同时列出两者;前置声明仍保留在声明集合中,不会因为有了定义而被移除
静态数据成员
从静态成员的使用处跳转到类内声明
静态数据成员在类内声明、类外定义;在使用处执行“跳转到声明”时,会同时列出类内声明和定义。
extern 变量
从外部变量的使用处跳转到其声明
从 extern 变量的使用处跳转时,会同时列出 extern 声明和定义性声明,因此始终可以从使用处到达头文件中的声明。
多处声明
从使用处跳转到所有声明位置
当一个实体在多处声明时,在使用处执行“跳转到声明”会列出所有声明位置,而不只是最近的一处。
签名的表面差异
参数名和参数的顶层 const 都不属于函数类型的一部分:下方的声明与定义对同一个函数采用了不同的写法,但“跳转到声明”仍能从使用处跳转到函数原型
跳转到实现
重写链
沿重写链逐级跳转到实现
在三级重写链中,从每个方法执行“跳转到实现”都会到达下一级的重写方法:从基类到中间类,再从中间类到叶子类。
同级重写
跳转到实现时列出所有同级重写方法
在虚方法上执行“跳转到实现”,会列出各个同级派生类中的所有重写方法。
基类
从基类跳转到所有派生类
在基类名称上执行“跳转到实现”,会列出从该类派生的类。
模板鸭子类型导航
尚不能将依赖调用解析到已知实例化中的方法
这适用于函数模板和泛型 Lambda,但目前两者都不会返回实现目标。
跳转到类型定义
跳转到符号的类型定义。适用于变量、参数、字段以及其他具有类型的命名实体。当类型是类型别名或类似指针的包装类型时,应解开别名或包装,跳转到其底层类型或所指向类型的定义。
变量和参数
对局部变量或参数执行“跳转到类型定义”,可跳转到其类型的定义
类和结构体字段
对字段访问执行“跳转到类型定义”,可跳转到该字段类型的定义
由 auto 推导类型的变量
由 auto 推导类型的变量目前还无法跳转到其推导出的类型定义
类型别名
对使用类型别名的变量执行“跳转到类型定义”,会跳转到 using 或 typedef 声明;目前还无法解开别名并跳转到底层类型的定义
结构化绑定(structured bindings)变量
对结构化绑定执行“跳转到类型定义”,可跳转到所绑定成员的类型定义
指针、引用和数组
“跳转到类型定义”会透过指针、引用和数组,跳转到元素类型的定义
查找引用
跨 TU 查找引用
查找引用也会收集其他文件中的使用位置:对于在一个源文件中定义、在另一个同级源文件中调用的函数,结果会同时列出两处调用位置以及共享头文件中的声明,而不局限于当前文件中的使用位置
引用结果中的声明
引用查询会返回声明、在声明之外编写的定义以及所有使用位置,因此从符号的任一位置都能跳转到它的所有相关位置
隐式构造函数和析构函数调用
对构造函数查找引用只会返回显式引用位置;隐式调用该构造函数或对应析构函数的对象定义不会出现在结果中
宏引用
宏引用搜索会包含宏展开、条件测试和取消定义的位置
同一名称的每次 #define 都被视为独立的符号,因此在 #undef 之后重新定义的宏只会收集自身的使用位置。
标签和 goto 引用
查找标签的引用时,会列出标签本身以及所有跳转到该标签的 goto
调用层次结构
调用层次结构准备
自由函数和成员函数都支持准备调用层次结构,并以光标处的实体为定位点创建条目
传入调用
传入调用列出函数的所有调用者;同一调用者多次调用该函数时,会列出每个调用位置
传出调用
传出调用列出函数体内调用的所有函数,每个被调用函数对应一个条目
调用层次结构条目详情
调用层次结构条目仅包含名称,详情字段中没有附带函数签名,因此无法在层次结构中区分重载
成员函数的限定名称
成员函数会生成调用层次结构条目,但名称字段仅包含函数名(draw),不包含可将其与自由函数区分开的限定名称 Circle::draw
跟踪虚函数分派
基类虚函数的传入调用不包含通过派生类中的重写函数发起的调用;对重写函数的调用仅归属于该重写函数,不会归属于被它重写的基类函数
Lambda 内的调用
Lambda 体内的调用会出现在被调用函数的传入调用中,并归属于包含该 Lambda 的函数
类型层次结构
类型层次结构准备
准备类型层次结构时,可以在任意用户定义的类型标记上定位条目,包括类、结构体、枚举和联合体
超类型
超类型列出类的所有直接基类,包括多重继承中派生类型的每个基类
子类型
子类型列出从某个基类派生的所有类,涵盖各个同级派生类型
模板继承
基类的子类型包括通过类模板(例如 CRTP 包装类)从该基类派生的类
工作区符号
按名称在整个项目中搜索符号(workspace/symbol)。
基本的工作区全局符号搜索
工作区符号搜索匹配名称时不区分大小写
查询以子序列的形式匹配符号名称,并对齐到名称中的各个单词,不区分大小写:函数、类型、枚举项和宏都在搜索范围内;没有匹配项时返回空列表,不会报错。
搜索覆盖整个项目
工作区符号搜索会返回项目中未打开文件里的匹配项
查询也会返回编辑器中尚未打开的项目文件里的符号:此处 other.h 一直未打开,因此其中的匹配结果由后台索引提供。
重载消歧
工作区符号结果省略了参数类型,导致无法区分重载
查询重载名称会找到所有重载,但每个条目都只显示名称本身,因此只有分别打开两个 process 结果对应的位置,才能区分它们。
模糊匹配
查询以子序列的形式匹配名称,并对齐到名称中的各个单词
LinLis 能找到 LinkedList,pconf 能找到 parse_config:除首字母外,查询中的每个字母要么接着前一个字母连成一段,要么落在名称中某个单词的词首,所以 pcfg 什么也找不到——它的 f 落在了 config 中间。名称完全一致的排在最前,其次是以查询开头的名称,最后才是匹配位置更深入名称内部的。
限定名称搜索
带限定的查询指明符号必须位于哪些容器之内
net::Socket 能找到 deep::net::Socket:这些限定符必须按同样的顺序出现在符号的容器链中,其间和两端允许有别的容器;而以 :: 开头则要求容器链与之完全一致。对限定查询的回复会写出限定名称,这样那些按查询文本过滤结果的编辑器就不会把它们滤掉。
按修饰后的名称(链接器名称)搜索
尚无法通过修饰后的链接器名称找到源代码中对应的函数
查询语法
引号、通配符、作用域和过滤条件都能收窄一次搜索
"process" 只匹配完整的名称,proc* 则匹配它通配到的一切;io::* 列出一个命名空间的成员,io::** 列出它的整棵子树;kind:function 只保留一种种类。各词项之间以空格分隔,可以组合使用。
模块导航
模块分区导航
在分区导入语句中冒号后的分区名称上执行“转到定义”,会打开声明该分区的分区单元
模块接口与实现导航
在实现单元的模块名称(module m;)上执行“转到定义”,会跳转到声明该模块的接口单元;尚不支持从接口名称反向跳转到实现
以点分隔的模块名称
以点分隔的模块名称中,每一段都可导航到模块接口
在以点分隔的模块名称的任意一段上执行“转到定义”,都会跳转到该模块的接口单元;整个名称视为同一个引用。
文档高亮
高亮显示当前文件中对光标所在符号的所有引用(textDocument/documentHighlight)。
文档引用高亮
文档高亮尚未实现,因此声明和使用处都不会高亮显示
符号高亮的读写分类
文档高亮尚不报告读写访问类型
切换源文件/头文件
源文件与头文件切换
源文件与头文件切换尚未实现,因此用户无法在配对文件之间直接跳转
