一、Snippet 概述
代码片段(Snippet)是预定义的代码模板,用户只需输入简短的触发词,就能插入完整的代码块。本文详细介绍代码片段系统的核心设计,包括模板语法、变量系统、制表位导航与性能优化。
二、Snippet 定义格式
主流编辑器的 Snippet 格式通常包含触发前缀、模板主体与描述:
// JSON 格式的 Snippet 定义
const snippets = {
"for": {
prefix": "for", // 触发前缀
"body": [
"for (let ${1:i} = 0; ${1:i} < ${2:length}; ${1:i}++) {",
" $0",
"}"
],
"description": "for 循环",
"scope": "javascript" // 作用域
},
"afn": {
"prefix": "afn",
"body": ["async function ${1:name}(${2:params}) {\n $0\n}"],
"description": "异步函数"
},
"log": {
"prefix": "log",
"body": ["console.log('$1:', $1)"],
"description": "console.log 快捷方式"
}
};
三、制表位系统(Tabstops)
制表位是 Snippet 的核心特性,允许用户在插入后通过 Tab 键快速跳转和编辑占位符。
// 制表位编号规则
// $0 - 最终光标位置(最后一次跳转)
// $1, $2, $3... - 按顺序跳转的制表位
// ${1:default} - 带默认值的制表位
// ${1:default|another} - 多个默认值选项
// 示例:React 函数组件
"rfc": {
"prefix": "rfc",
"body": [
"function ${1:ComponentName}(${2:props}) {",
" return (",
" $0",
" );",
"}",
"",
"export default ${1:ComponentName}"
]
}
四、变量系统
Snippet 支持丰富的内置变量和自定义变量:
// 内置变量
const variables = {
"TM_FILENAME": "当前文件名",
"TM_FILENAME_BASE": "不含扩展名的文件名",
"TM_DIRECTORY": "当前目录名",
"TM_LINE_NUMBER": "行号",
"TM_COLUMN_NUMBER": "列号",
"TM_CURRENT_LINE": "当前行内容",
"TM_SELECTED_TEXT": "选中的文本",
"CLIPBOARD": "剪贴板内容",
"CURRENT_YEAR": "当前年份",
"CURRENT_DATE": "当前日期",
"CURRENT_TIME": "当前时间"
};
// 使用示例
"header": {
"body": [
"/**",
" * @file ${TM_FILENAME}",
" * @author ${AUTHOR_NAME}",
" * @date ${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}",
" */"
]
}
五、镜像制表位(Linked Editing)
多个相同编号的制表位会同步编辑,一个修改则全部更新:
// 镜像制表位示例
// $1 在多处出现,编辑一处时其他位置同步更新
"wrapper": {
"prefix": "wrapper",
"body": [
"const ${1:name} = ($\{2:arg}) => {\n",
" console.log('${1:name} called with:', ${2:arg});\n",
" return ${3:result};\n",
"};\n",
"export { ${1:name} };"
]
}
// 在 VS Code 中,会自动识别 $1 和 ${2:arg} 的镜像关系
六、模板引擎实现
Snippet 的解析和渲染是核心功能:
// Snippet 解析器
class SnippetParser {
parse(body) {
// 1. 按行分割
const lines = Array.isArray(body) ? body : body.split('\n');
// 2. 提取制表位信息
const tabstops = [];
const text = lines.map((line, lineIndex) => {
return line.replace(/\$\{(\d+)(?::([^}]*))?\}/g, (match, num, defaultVal) => {
tabstops.push({
number: parseInt(num),
defaultValue: defaultVal || '',
line: lineIndex
});
return defaultVal || '';
});
}).join('\n');
// 3. 替换内置变量
const result = this.replaceVariables(text);
return { text: result, tabstops };
}
replaceVariables(text) {
const now = new Date();
return text
.replace(/\$\{CURRENT_YEAR\}/g, now.getFullYear().toString())
.replace(/\$\{CURRENT_DATE\}, now.toLocaleDateString())
.replace(/\$\{TM_FILENAME\}/g, editor.getFilename())
.replace(/\$\{TM_LINE_NUMBER\}/g, editor.getCursorLine());
}
}
七、制表位导航实现
用户在插入 Snippet 后,通过 Tab 键在不同占位符间跳转:
// 制表位导航
class TabstopNavigator {
constructor(tabstops) {
// 按编号分组制表位
this.stops = {};
tabstops.forEach(ts => {
if (!this.stops[ts.number]) this.stops[ts.number] = [];
this.stops[ts.number].push(ts);
});
// 0 是结束位置,优先跳转到 1
this.current = this.stops[1] ? 1 : (this.stops[0] ? 0 : -1);
}
next() {
// 按 1 -> 2 -> 3... -> 0 顺序循环
const keys = Object.keys(this.stops).map(Number).sort((a,b) => a-b);
let idx = keys.indexOf(this.current);
if (idx === -1 || idx === keys.length - 1) {
this.current = keys[0];
} else {
this.current = keys[idx + 1];
}
return this.stops[this.current];
}
prev() {
const keys = Object.keys(this.stops).map(Number).sort((a,b) => a-b);
let idx = keys.indexOf(this.current);
if (idx === -1 || idx === 0) {
this.current = keys[keys.length - 1];
} else {
this.current = keys[idx - 1];
}
return this.stops[this.current];
}
jumpTo(number) {
this.current = number;
return this.stops[number] || [];
}
}
八、选择变换(Transformations)
高级功能:选中区域后可以应用大小写、替换等变换:
// 选择变换语法
// ${TM_SELECTED_TEXT:default} - 使用选中文本或默认值
// ${TM_SELECTED_TEXT/pattern/replacement/flags} - 正则替换
// 示例:将选中的文本转为大写
"upper": {
"body": ["${TM_SELECTED_TEXT/.*/${upcase($0)/g}"],
"description": "大写转换"
}
// 示例:添加引号
"quote": {
"body": ["'${TM_SELECTED_TEXT}'"],
"description": "添加单引号"
}
// 示例:JSON key 转 camelCase
"jsonToCamel": {
"body": ["${TM_SELECTED_TEXT/_([a-z])/${uppercase($1)}/g}"]
}
九、性能优化
- 索引预加载:启动时加载所有 Snippet 定义,建立前缀索引
- 延迟匹配:用户停止输入 100-150ms 后才触发匹配
- 虚拟滚动:候选项超过一定数量时,只渲染可见区域
- 缓存结果:相同前缀的匹配结果缓存,避免重复计算
十、总结
- 核心要素:触发前缀、模板主体、描述信息
- 制表位:$0-$9 定义跳转顺序,支持默认值
- 变量:内置变量 + 自定义变量,支持日期、文件名等
- 镜像:相同编号的制表位同步编辑
- 变换:选中区域支持正则替换和大小写转换