Add code snippets for CLANG in VS Code
日志:
推广:
前记:今天试着用了下 Atom,发现 Atom 居然预装了 CLANG 的 snippets,而且远比 VSCode 的已有拓展「C/C++ Snippets」中的丰富!身为 VSCode 的死忠粉,我决定立马把 Atom 的 C snippets 搬到 VSCode 上来。
既然你点开了这个页面,那就说明要么你不知道 VSCode 上已有拓展「C/C++ Snippets」,要么你对这个拓展不甚满意。对于后者,本文将为你介绍如何在 VSCode 上设置 snippets,并为你提供一套可以直接用的 C 语言 snippets。
snippet[ˈsnɪpɪt],或者说「code snippet」,也即代码片,指的是能够帮助输入重复代码模式,比如循环或条件语句,的模板。通过 snippet ,我们仅仅输入一小段字符串,就可以在代码片引擎的帮助下,生成预定义的模板代码,接着我们还可以通过在预定义的光标位置之间跳转,来快速补全模板。
当然,看图更易懂。下图将 aja
补全为 JQuery 的 ajax() 方法,并通过光标的跳转,快速补全了待填键值对:
自 1.57 版本开始,vscode 引入提示预览功能,再也不同担心混淆关键字和代码片段了。
首先,进入代码片设置文件,这里提供了三种方法:
接着,在设置文件里补全代码片。以 C 语言为例,选中后你将打开一个设置文件,c.json,在文件头部你会看见一个注释,这其实是一个示例和对它的介绍。你可以试着将第 7~14 行反注释掉(选中后 Ctrl + “/”),从而尝试使用它。了解过「json」就不会对此感到奇怪。
{
// Place your snippets for c here. Each snippet is defined under a snippet name and has a prefix, body and
// description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the
// same ids are connected.
// Example:
"Print to console": {
"prefix": "log",
"body": [
"console.log('$1');",
"$2"
],
"description": "Log output to console"
}
}
这个示例中定义了一个描述为「Print to console」的代码片,其功能为:在 IntelliSense 中输入 log
并选中对应代码片后,可将原文本替换为 console.log('');
。效果预览如下:
在第二章中,你已经接触到了代码片最简单的功能,而 VSCode 的代码片引擎所能做的远不止这些。本章将以官方教程1为本,详细地为大家介绍代码片。
代码片由四部分组成:
Print to console
。
前缀部分没有什么好介绍的,唯一值得注意的是,前缀支持 N:1,也即允许多条前缀对应同一条代码片。在使用时,只需将前缀定义为数组即可,数组中的每一个前缀都能对应本代码片。下面就是一个很简单的示例。
{
"prefix": [ "header", "stub", "copyright"],
"body": "Copyright. Foo Corp 2028",
"description": "Adds copyright...",
"scope": "javascript,typescript"
}
前缀部分没有什么好介绍的,不过在引入了域的概念之后,会不由自主地想起一些问题,比如如何让同一条代码片根据语言进行微调,从而取得良好的可移植性。一个很容易想到的例子就是注释风格了。
某公司希望所有代码文件的头部都有公司的版权声明,但 python 风格的注释是 #
而 C 风格的注释是 //
,在每个语言的设置文件下都定义类似但注释风格不同的代码段显然会引入巨大的冗余。对此,VSCode 提供的解决方案是提供一些在不同语言下表现不同的变量。下一小节中已经更新了相关介绍。
Body 部分可以使用特殊语法结构,来控制光标和要插入的文本,其支持的基本结构如下:
Tabstops:制表符
用「Tabstops」可以让编辑器的指针在 snippet 内跳转。使用 1,2 等指定光标位置。这些数字指定了光标跳转的顺序。特别地,0表示最终光标位置。相同序号的「Tabstops」被链接在一起,将会同步更新,比如下列用于生成头文件封装的 snippet 被替换到编辑器上时,光标就将同时出现在所有1位置。
"#ifndef $1"
"#define $1"
"#end // $1"
Placeholders:占位符
「Placeholder」是带有默认值的「Tabstops」,如{1:foo}。「placeholder」文本将被插入「Tabstops」位置,并在跳转时被全选,以方便修改。占位符还可以嵌套,例如{1:another
比如,结构体的代码片主体可以这样写:
struct ${1:name_t} {\n\t$2\n};
作为「Placeholder」的name_t
一方面可以提供默认的结构名称,另一方面可以作为输入的提示。
Choice:可选项
「Choice」是提供可选值的「Placeholder」。其语法为一系列用逗号隔开,并最终被两个竖线圈起来的枚举值,比如 ${1|one,two,three|}
。当光标跳转到该位置的时候,用户将会被提供多个值(one 或 two 或 three)以供选择。
Variables:变量
使用name或{name:default}可以插入变量的值。当变量未赋值时(如),将插入其缺省值或空字符串。 当varibale未知(即,其名称未定义)时,将插入变量的名称,并将其转换为「Placeholder」。可以使用的「Variable」如下:
TM_SELECTED_TEXT
:当前选定的文本或空字符串;
注:v1.49 起,直接输入代码片段前缀并补全,即可对选中文本适用代码片段,见 RP105440。
TM_CURRENT_LINE
:当前行的内容;TM_CURRENT_WORD
:光标所处单词或空字符串
注:所谓光标一般为文本输入处那条闪来闪去的竖线,该项可定制。单词使用 VSCode 选词(Word Wrap)器选择。你最好只用它选择英文单词,因为这个选择器明显没有针对宽字符优化过,它甚至无法识别宽字符的标点符号。TM_LINE_INDEX
:行号(从零开始);TM_LINE_NUMBER
:行号(从一开始);TM_FILENAME
:当前文档的文件名;TM_FILENAME_BASE
:当前文档的文件名(不含后缀名);TM_DIRECTORY
:当前文档所在目录;TM_FILEPATH
:当前文档的完整文件路径;RELATIVE_FILEPATH
:当前文档的相对路径(相对于当前工作目录);CLIPBOARD
:当前剪贴板中内容;WORKSPACE_NAME
:当前工作目录的名称(而非完整路径);WORKSPACE_FOLDER
:当前工作目录的路径。还有一些用于插入当前时间的变量,这里单独列出:
CURRENT_YEAR
: 当前年份;CURRENT_YEAR_SHORT
: 当前年份的后两位;CURRENT_MONTH
: 格式化为两位数字的当前月份,如 02;CURRENT_MONTH_NAME
: 当前月份的全称,如 July;CURRENT_MONTH_NAME_SHORT
: 当前月份的简称,如 Jul;CURRENT_DATE
: 当天月份第几天;CURRENT_DAY_NAME
: 当天周几,如 Monday;CURRENT_DAY_NAME_SHORT
: 当天周几的简称,如 Mon;CURRENT_HOUR
: 当前小时(24 小时制);CURRENT_MINUTE
: 当前分钟;CURRENT_SECOND
: 当前秒数;CURRENT_SECONDS_UNIX
:Unix 时间戳。还有一些用于插入随机值的变量:
RANDOM
6个随机十进制数
UUID
生成 UUIDv4
还有一些用于插入行/块注释的变量,其将根据当前文件的语言模式自动调整:
BLOCK_COMMENT_START
块注释上半段,输出示例: /*
<!--
BLOCK_COMMENT_END
块注释下半段,输出示例: */
-->
LINE_COMMENT
行注释,输出示例: //
<!-- -->
注:这些都是变量名,不是宏,在实际使用的时要加上
$
符。
变量转换可将变量的值格式化处理后插入预定的位置。
我们可以通过 ${var_name/regular_expression/format_string/options}
插入格式化后的代码片。显然,「variable transformations」由 4 部分构成:
var_name
:变量名;regular_expression
:正则表达式;format_string
:格式串;options
:正则表达式匹配选项。其中正则表达式的写法和匹配选项部分不在本篇博文的讲解范围之内,具体内容请分别参考 javascript 有关 RegExp(pattern [, flags])
构造函数中的 pattern
及 flags
参数项的说明2。
本文只对 format_string
部分进行详细介绍。
根据其 EBNF 范式,我们可以知道 format_string
其实是 format
或 text
的线性组合:
text
:也即没有任何作用的普通文本,你甚至可以使用汉字;format 的后三条理解起来可能比较困难。这里我们以倒数第三条为例进行说明。假设我们有一个「make.c」文件,我们有这么一条 snippet: "body": "
注:
sn
表示捕捉项的序号if
表示捕捉项捕捉成功时替换的文本else
表示捕捉项捕失败时替换的文本下面笔者再介绍一个简单的例子,帮助大家理解「variable transformations」。
假设有一个名为「make.c」的文件中,并且我们已经定义如下 snippet。
"#ifndef HEADER … #define … #endif":{
"prefix": "defheader",
"body": "#ifndef ${1:${TM_FILENAME/(.*)\\.C$/${1:/upcase}_H/i}} \n#define $1 \n${2:header content}\n#endif\t// $1"
}
这段 snippet 将生成下图所示代码:
其中最复杂的模式为:{1:{TM_FILENAME/(.*)\\.C/{1:/upcase}_H/i}},我们将之拆解为如下五部分:
${1:...}
:嵌套的 placeholder
;${TM_FILENAME/.../.../.}
:「variable transformations」中的「var_name」,表示带后缀的文件名;${.../.../.../i}
:「variable transformations」中的「options」,表示无视大小写。我们可以通过 ${int/regular_expression/format_string/options}
插入格式化后的代码片。显然,与变量转换,「placeholder transformations」也由 4 部分构成:
int
:占位符相应光标序号;regular_expression
:正则表达式;format_string
:格式串;options
:正则表达式匹配选项。上述全部内容我们都在前文介绍过了,因此此处不做赘述。我们唯一需要关注的是转换触发的时机:占位符转换将在进行占位符跳转(假设 1→2)的时候自动适用到当前占位符(1)。
假设我们已经这样的 Snippet:
"HelloWorld": {
"prefix": "say_hello",
"body": " -> ${1/Hello/Hallo/} ${2/World/Welt/}"
}
那么我们在两个制表位同时键入 Hello 并跳转的时候,第一个制表位依然保持 Hello 不变,而第二个制表位(占位符)被替换为 Hallo。键入 Welt 亦然。效果图:
官网给出了 snippet 的 EBNF 范式的正则文法。
any ::= tabstop | placeholder | choice | variable | text
tabstop ::= '$' int
| '${' int '}'
| '${' int transform '}'
placeholder ::= '${' int ':' any '}'
choice ::= '${' int '|' text (',' text)* '|}'
variable ::= '$' var | '${' var '}'
| '${' var ':' any '}'
| '${' var transform '}'
transform ::= '/' regex '/' (format | text)+ '/' options
format ::= '$' int | '${' int '}'
| '${' int ':' '/upcase' | '/downcase' | '/capitalize' | '/camelcase' | '/pascalcase' '}'
| '${' int ':+' if '}'
| '${' int ':?' if ':' else '}'
| '${' int ':-' else '}' | '${' int ':' else '}'
regex ::= JavaScript Regular Expression value (ctor-string)
options ::= JavaScript Regular Expression option (ctor-options)
var ::= [_a-zA-Z] [_a-zA-Z0-9]*
int ::= [0-9]+
text ::= .*
注意,作普通字符使用时, , } 和 \ 可使用 \(反斜杠)转义,比如评论中有朋友问到,应如何打印 this.message.success('xx'),所采用的代码片为 this.\\message.success('xx'),其中 string 表达式里的「\」实际被转义为「\」,「」再被引擎处理为「
我们在第二章中就已经理解了代码片设置文件的概念,但当时这并不是我们的核心关注点。现在,你已经对代码片有了深刻的理解了,你可能会面临着这样新的需求,也即只为某工程,某语言,或所有语言单独配置代码片的进阶要求。这里就将为你这样的需求提供解决思路。
当你使用 VSCode 打开一个文件夹时,这个文件夹就成了所谓的 Project 或 Workspace。所以,如果你希望单独为某工程配置 snippet 时,你首先应该打开一个目录。在打开目录之后,你只需按照第二章中介绍的方法,在进入代码片设置文件时点选「新建”xxx”文件夹的代码片段文件」。VSCode 会使用 GUI 引导着你在当前工程下的「.vscode」中新建一个「*.code-snippets」的文件,这就是当前工作目录的设置文件。
在呼出代码片的时候,IntelliSense 会注明哪些代码片是「Workspace Snippet」。如:
默认情况下 snippet 在 IntelliSense 中的显示优先级并不高,而且在 IntelliSense 中选择相应 snippet 需要按「enter」键,这对于手指短的人来说并不是什么很好的体验。
所幸,VSCode 意识到了这一点,并为我们提供了改进的方式。我们可以在 VSCode 的用户设置(「Ctrl+P」在输入框中写「user settings」后点选)中,检索代码片,然后根据提示修改代码片的相关设置。
我们可以设置在 IntelliSense 中优先显示代码片,并可以通过「TAB」补全。
修改后设置文件中会多出这两行:
"editor.snippetSuggestions": "top",
"editor.tabCompletion": true
注:v1.28 之后,
editor.tabCompletion
得到了增强。现在 TAB 能补全所有前缀了,而非仅 snippet。另外,在插入非代码片的前缀 之后,可以使用 TAB 向下切换别的建议,或使用 Shit + TAB 向上切换。
说好的附录。
另,我对 Atom 的 C snippet3 作了部分修改,使之更适合我的习惯,若有兴致你可自行修改,反正也不难。
{
/* // Place your snippets for C here. Each snippet is defined under a snippet name and has a prefix, body and // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are: // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the // same ids are connected. // Example: "Print to console": { "prefix": "log",, "body": [ "console.log('$1');", "$2" ], "description": "Log output to console" } */
"#ifndef … #define … #endif":{
"prefix": "def",
"body": "#ifndef ${1:SYMBOL}\n#define $1 ${2:value}\n#endif\t// ${1:SYMBOL}"
},
"#include <>":{
"prefix": "Inc",
"body": "#include <${1:.h}>"
},
"#include \"\"":{
"prefix": "inc",
"body": "#include \"${1:.h}\""
},
"#pragma mark":{
"prefix": "mark",
"body": "#if 0\n${1:#pragma mark -\n}#pragma mark $2\n#endif\n\n$0"
},
"main()":{
"prefix": "main",
"body": "int main(int argc, char const *argv[]) {\n\t$1\n\treturn 0;\n}"
},
"For Loop":{
"prefix": "for",
"body": "for (${1:i} = 0; ${1:i} < ${2:count}; ${1:i}${3:++}) {\n\t$4\n}"
},
"Define and For Loop":{
"prefix": "dfor",
"body": "size_t ${1:i};\nfor (${1:i} = ${2:0}; ${1:i} < ${3:count}; ${1:i}${4:++}) {\n\t$5\n}"
},
"Header Include-Guard":{
"prefix": "once",
"body": "#ifndef ${1:SYMBOL}\n#define $1\n\n${2}\n\n#endif /* end of include guard: $1 */\n"
},
"Typedef":{
"prefix": "td",
"body": "typedef ${1:int} ${2:MyCustomType};"
},
"Typedef Struct":{
"prefix": "tst",
"body": "typedef struct ${1:StructName} {\n\t$2\n}${3:MyCustomType};"
},
"Do While Loop":{
"prefix": "do",
"body": "do {\n\t$0\n} while($1);"
},
"While Loop":{
"prefix": "while",
"body": "while ($1) {\n\t$2\n}"
},
"fprintf":{
"prefix": "fprintf",
"body": "fprintf(${1:stderr}, \"${2:%s}\\\\n\", $3);$4"
},
"If Condition":{
"prefix": "if",
"body": "if ($1) {\n\t$2\n}"
},
"If Else":{
"prefix": "ife",
"body": "if ($1) {\n\t$2\n} else {\n\t$3\n}"
},
"If ElseIf":{
"prefix": "iff",
"body": "if ($1) {\n\t$2\n} else if ($3) {\n\t$4\n}"
},
"If ElseIf Else":{
"prefix": "iffe",
"body": "if ($1) {\n\t$2\n} else if ($3) {\n\t$4\n} else {\n\t$5\n}"
},
"Switch Statement":{
"prefix": "sw",
"body": "switch ($1) {\n$2default:\n\t${3:break;}\n}$0"
},
"case break":{
"prefix": "cs",
"body": "case $1:\n\t$2\n\tbreak;\n$0"
},
"printf":{
"prefix": "printf",
"body": "printf(\"${1:%s }\\n\", $2);$3"
},
"scanf":{
"prefix": "scanf",
"body": "scanf(\"${1:%s}\\n\", $2);$3"
},
"Struct":{
"prefix": "st",
"body": "struct ${1:name_t} {\n\t$2\n};"
},
"void":{
"prefix": "void",
"body": "void ${1:name}($2) {\n\t$3\n}"
},
"any function":{
"prefix": "func",
"body": "${1:int} ${2:name}($3) {\n\t$5\n\treturn ${4:0};\n}"
},
"write file":{
"prefix": "wf",
"body": "FILE *${1:fp};\n${1:fp} = fopen (\"${2:filename.txt}\",\"w\");\nif (${1:fp}!=NULL)\n{\n\tfprintf(${1:fp},\"${3:Some String\\\\n}\");\n\tfclose (${1:fp});\n}"
},
"read file":{
"prefix": "rf",
"body": "FILE *${1:fp};\n${1:fp} = fopen (\"${2:filename.txt}\",\"r\");\nif (${1:fp}!=NULL)\n{\n\tfscanf(${1:fp},\"${3:Some String\\\\n}\", ${3:&var});\n\tfclose (${1:fp});\n}",
"description": "read file opeartion including fopen, fscanf and fclose."
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/186805.html原文链接:https://javaforall.cn