折叠与大纲
折叠(Folding)让长代码块一键收起,大纲(Outline)提供文档结构导航。两者让大文件「可压缩、可导航」。
注册折叠 Provider
languages.registerFoldingRangeProvider 注册折叠区域:
typescript
import * as vscode from 'vscode';
vscode.languages.registerFoldingRangeProvider(
'plaintext',
{
provideFoldingRanges(
document: vscode.TextDocument,
context: vscode.FoldingContext,
token: vscode.CancellationToken
): vscode.FoldingRange[] {
// 返回折叠区域数组
return [
new vscode.FoldingRange(
0, // startLine
5 // endLine
)
];
}
}
);FoldingRange 属性
typescript
const range = new vscode.FoldingRange(
0, // 起始行
10, // 结束行
vscode.FoldingRangeKind.Region, // 类型
'代码块' // 折叠后显示文本
);| 属性 | 说明 |
|---|---|
startLine | 起始行 |
endLine | 结束行 |
kind | 类型(Comment/Imports/Region) |
collapsedText | 折叠后显示的文本 |
FoldingRangeKind 类型
| 类型 | 用途 |
|---|---|
Comment | 注释区域 |
Imports | import 区域 |
Region | 自定义区域 |
undefined | 普通代码块 |
实现折叠:代码块检测
typescript
class FoldingProvider implements vscode.FoldingRangeProvider {
provideFoldingRanges(document): vscode.FoldingRange[] {
const ranges: vscode.FoldingRange[] = [];
const stack: { line: number; char: number }[] = [];
for (let line = 0; line < document.lineCount; line++) {
const text = document.lineAt(line).text;
// 统计花括号深度
for (let i = 0; i < text.length; i++) {
const char = text[i];
if (char === '{') {
stack.push({ line, char: i });
} else if (char === '}') {
const open = stack.pop();
if (open && open.line !== line) {
ranges.push(
new vscode.FoldingRange(
open.line,
line,
vscode.FoldingRangeKind.Region
)
);
}
}
}
// 区域标记 //#region ... //#endregion
if (text.includes('//#region')) {
stack.push({ line, char: 0 });
}
if (text.includes('//#endregion')) {
const open = stack.pop();
if (open) {
ranges.push(
new vscode.FoldingRange(
open.line,
line,
vscode.FoldingRangeKind.Region
)
);
}
}
}
return ranges;
}
}注册文档符号 Provider
registerDocumentSymbolProvider 提供大纲结构:
typescript
vscode.languages.registerDocumentSymbolProvider(
'plaintext',
{
provideDocumentSymbols(
document: vscode.TextDocument,
token: vscode.CancellationToken
): vscode.ProviderResult<vscode.DocumentSymbol[]> {
// 返回符号数组
return this.buildSymbols(document);
}
}
);DocumentSymbol 树形结构
DocumentSymbol 支持嵌套层级:
typescript
const symbol = new vscode.DocumentSymbol(
'myFunction', // 名称
'函数说明', // 详细描述
vscode.SymbolKind.Function, // 类型
new vscode.Range(0, 0, 10, 0), // 范围
new vscode.Range(0, 0, 0, 20) // 选择范围(名称)
);
// 添加子符号
symbol.children = [childSymbol];DocumentSymbol 参数
| 参数 | 说明 |
|---|---|
name | 符号名称(大纲显示) |
detail | 描述文本 |
kind | 符号类型(图标) |
range | 符号完整范围 |
selectionRange | 名称范围(高亮) |
SymbolKind 类型
typescript
vscode.SymbolKind.File
vscode.SymbolKind.Module
vscode.SymbolKind.Namespace
vscode.SymbolKind.Package
vscode.SymbolKind.Class
vscode.SymbolKind.Method
vscode.SymbolKind.Property
vscode.SymbolKind.Field
vscode.SymbolKind.Constructor
vscode.SymbolKind.Function
vscode.SymbolKind.Variable
vscode.SymbolKind.Constant
vscode.SymbolKind.Interface
vscode.SymbolKind.Enum实现大纲:函数解析
typescript
class SymbolProvider implements vscode.DocumentSymbolProvider {
provideDocumentSymbols(document): vscode.DocumentSymbol[] {
const symbols: vscode.DocumentSymbol[] = [];
for (let line = 0; line < document.lineCount; line++) {
const text = document.lineAt(line).text;
// 解析类定义
const classMatch = text.match(/class\s+(\w+)/);
if (classMatch) {
const classSymbol = new vscode.DocumentSymbol(
classMatch[1],
'类定义',
vscode.SymbolKind.Class,
new vscode.Range(line, 0, line, text.length),
new vscode.Range(
line,
text.indexOf(classMatch[1]),
line,
text.indexOf(classMatch[1]) + classMatch[1].length
)
);
// 扫描类内方法
classSymbol.children = this.scanMethods(document, line + 1);
symbols.push(classSymbol);
continue;
}
// 解析函数定义
const funcMatch = text.match(/function\s+(\w+)\s*\(([^)]*)\)/);
if (funcMatch) {
const funcSymbol = new vscode.DocumentSymbol(
funcMatch[1],
`函数(参数: ${funcMatch[2]})`,
vscode.SymbolKind.Function,
new vscode.Range(line, 0, line, text.length),
new vscode.Range(
line,
text.indexOf(funcMatch[1]),
line,
text.indexOf(funcMatch[1]) + funcMatch[1].length
)
);
symbols.push(funcSymbol);
}
// 解析变量
const varMatch = text.match(/^\s*(?:const|let|var)\s+(\w+)\s*=/);
if (varMatch) {
symbols.push(
new vscode.DocumentSymbol(
varMatch[1],
'变量',
vscode.SymbolKind.Variable,
new vscode.Range(line, 0, line, text.length),
new vscode.Range(line, 0, line, varMatch[1].length)
)
);
}
}
return symbols;
}
private scanMethods(
document: vscode.TextDocument,
startLine: number
): vscode.DocumentSymbol[] {
const methods: vscode.DocumentSymbol[] = [];
for (let line = startLine; line < document.lineCount; line++) {
const text = document.lineAt(line).text;
if (text.includes('}') && !text.includes('{')) {
break; // 类结束
}
const methodMatch = text.match(/(\w+)\s*\([^)]*\)\s*\{/);
if (methodMatch) {
methods.push(
new vscode.DocumentSymbol(
methodMatch[1],
'方法',
vscode.SymbolKind.Method,
new vscode.Range(line, 0, line, text.length),
new vscode.Range(line, 0, line, methodMatch[1].length)
)
);
}
}
return methods;
}
}完整示例:折叠 + 大纲组合
typescript
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
// 折叠 Provider
context.subscriptions.push(
vscode.languages.registerFoldingRangeProvider(
'plaintext',
{
provideFoldingRanges(document) {
const ranges: vscode.FoldingRange[] = [];
// 注释区域折叠
for (let line = 0; line < document.lineCount; line++) {
const text = document.lineAt(line).text;
if (text.trim().startsWith('// ===')) {
// 查找区域结束
let end = line;
while (end < document.lineCount &&
!document.lineAt(end).text.includes('// ===')) {
end++;
}
if (end > line + 1) {
ranges.push(
new vscode.FoldingRange(
line, end,
vscode.FoldingRangeKind.Region,
'...注释区域'
)
);
}
}
// 花括号代码块
if (text.includes('{')) {
const depth = countBraceDepth(document, line);
if (depth > 0) {
ranges.push(
new vscode.FoldingRange(
line, line + depth - 1
)
);
}
}
}
return ranges;
}
}
)
);
// 大纲 Provider
context.subscriptions.push(
vscode.languages.registerDocumentSymbolProvider(
'plaintext',
{
provideDocumentSymbols(document) {
const symbols: vscode.DocumentSymbol[] = [];
for (let line = 0; line < document.lineCount; line++) {
const text = document.lineAt(line).text;
const match = text.match(/^#+\s+(.+)/);
if (match) {
symbols.push(
new vscode.DocumentSymbol(
match[1],
'标题',
vscode.SymbolKind.String,
new vscode.Range(line, 0, line, text.length),
new vscode.Range(line, 0, line, text.length)
)
);
}
}
return symbols;
}
}
)
);
}折叠与大纲联动
| 能力 | 提供者 | 用途 |
|---|---|---|
| 代码折叠 | FoldingRangeProvider | 收起大块代码 |
| 大纲导航 | DocumentSymbolProvider | 快速跳转 |
| 面包屑 | DocumentSymbol | 显示层级路径 |
| 符号搜索 | DocumentSymbol | 工作区符号搜索 |
常见问题
| 问题 | 处理 |
|---|---|
| 折叠无效 | 检查行号范围正确 |
| 大纲为空 | 确认 SymbolKind 与范围 |
| 嵌套错误 | 用 children 构造树形 |
| 图标错误 | 选择合适的 SymbolKind |
折叠与大纲让文档「结构化」,长文件也能高效浏览与导航,是语言体验的必备能力。