【VSC】Snippets不彻底指南

更多文章,参见大搜车技术博客:blog.souche.com/javascript

大搜车无线开发中心持续招聘中,前端,Nodejs,android 均有 HC,简历直接发到:sunxinyu@souche.com前端

前言

你们都是出来写代码的,少不了要写上千万来行代码,其中重复性的代码占比又会很大。那么如何避免一次又一次写重复性的代码呢?除了代码自身的优雅、可复用,Jetbrains系如Intellij IDEA的Live Templates或Visual Studio Code的Snippet等内置工具都能很大程度上帮咱们少写不少重复性的代码。下面就vsc的Snippet,结合demo,讲讲它的用法。java

建立Snippet

快捷键Cmd + Shift + PF1打开命令窗口,输入snippet,选中配置用户代码片断,这时候会弹出react

能够在现有的代码片断文件上新增snippet,也能够本身新建代码片断文件。android

Snippets片断以JSON格式定义,官方提供的例子以下正则表达式

{
    "For_Loop": {
        "prefix": "for",
        
        /**
         * 在全局代码片断文件新增snippet时,
         * 经过该项来指定该snippet使用范围,
         * 以下所示,在文件以`.js`或者`.ts`结尾时可以使用该snippet
         */
        "scope": "javascript,typescript",   
        
        "body": [
          "for (const ${2:element} of ${1:array}) {",
          "\t$0",
          "}"
        ],
        "description": "For Loop"
    }
}
复制代码
  • For_Loop is the snippet name(后续在指定快捷键绑定的时候会用到).
  • prefix defines how this snippet is selected from IntelliSense and tab completion. In this case for.(前缀支持N:1,好比prefix值为["for","fof"],则forfof对应同一条代码片断)
  • body is the content and either a single string or an array of strings of which each element will be inserted as separate line.
  • description is the description used in the IntelliSense drop down(非必填).

上述英文解释引自官方,怕翻译不到位影响读者理解,就不做翻译了。typescript

下面用一张图展现在使用中各个属性对应的位置express

上图左侧框中的为 prefix,右侧圈中的为 description, description下方的为 body部份内容

Snippet语法

Snippet的语法集中在body上。json

Tabstops: 即$1$2等。使用$1$2等表示按下键盘tab后光标将要指向的位置。根据数字大小表示前后顺序。$0表示光标最后指向的位置。能够存在多个相同的Tabstops,并会同步更新。c#

如下是多个相同的Tabstops同步更新的示例

{
    "For_Loop": {
        "prefix": "for",
        "scope": "javascript,typescript",
        "body": [
          "for (const ${2:element} of ${1:array}) {",
          "\tconst item = $1[$2];$0",
          "}"
        ],
        "description": "For Loop"
    }
}
复制代码

如下是使用上述snippet的过程

Placeholders:占位符。用户直接跳过Tabstops不输入新值时,会使用占位符。它还能内嵌。

如下是占位符内嵌的示例

{
    "placeholder": {
    "prefix": "ph",
        "body": "${1:hello ${2:world}}$0"
    }
}
复制代码

如下是使用上述snippet的过程

Choice: 可选的占位符

以以下snippet为例

{
    "choice": {
        "prefix": "ch",
        "body": "${1|hello,hi,how are you|}"
    }
}
复制代码

输入ch按下tab键后,就会显示

注: 在Choice中,,|$}\可使用\转义;其余状况有且仅$}\可使用\转义,其余字符没法转义, 详见https://code.visualstudio.com/docs/editor/userdefinedsnippets#_grammar

Variables: 官方定义的变量

TM_SELECTED_TEXT:当前选定的文本或空字符串; 
    TM_CURRENT_LINE:当前行的内容;
    TM_CURRENT_WORD:光标所处单词或空字符串 
    TM_LINE_INDEX:行号(从零开始);
    TM_LINE_NUMBER:行号(从一开始);
    TM_FILENAME:当前文档的文件名;
    TM_FILENAME_BASE:当前文档的文件名(不含后缀名);
    TM_DIRECTORY:当前文档所在目录;
    TM_FILEPATH:当前文档的完整文件路径;
    CLIPBOARD:当前剪贴板中内容。
    时间相关
    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: 当前秒数。
    注释相关
    BLOCK_COMMENT_START: 在PHP中输出 /* ,在HTML中输出 <!--
    BLOCK_COMMENT_END: 在PHP中输出 */ ,在HTML中输出 -->
    LINE_COMMENT: 在PHP中输出 // ,在HTML中输出 <!-- -->
复制代码

Variable transforms 变量转换可将变量的值格式化处理后插入预约的位置。 它包括三个部分:

  1. 正则表达式
  2. 格式字符串
  3. 正则表达式匹配选项

正则表达式和正则表达式匹配选项不做解释。

格式字符串在官方文档的Grammar中以下

format      ::= '$' int | '${' int '}'
                | '${' int ':' '/upcase' | '/downcase' | '/capitalize' '}'
                | '${' int ':+' if '}'
                | '${' int ':?' if ':' else '}'
                | '${' int ':-' else '}' | '${' int ':' else '}'
复制代码

假设某总体片断为${variable/regexp/(format|text)/options},再结合上述语法, 总体片断可变为

  1. ${var_name/regular_expression/$1/options}
  2. ${var_name/regular_expression/${1}/options}
  3. ${var_name/regular_expression/${1:/upcase}/options} // /downcase/capitalize使用方式同/upcase
  4. ${var_name/regular_expression/${1:+if}/options}
  5. ${var_name/regular_expression/${1:?if:else}/options}
  6. ${var_name/regular_expression/${1:-else}/options}
  7. ${var_name/regular_expression/${1:else}/options}
  8. ${var_name/regular_expression/text/options}

上述format$后的数字1表示分组捕获组(正则表达式中()匹配到的数组)的第1项,同理$2表示分组捕获项第2项。分组捕获相关信息详见分组捕获

现有文件名global-js.json,以demo说明上述片断含义。为了精简,输出值直接以body末尾注释表示,另外多个body直接放在一个snippet(实际操做不规范,仅仅为了精简)

注:使用transform末尾必须加/,才能被识别。如${TM_FILENAME_BASE/(global).*/$1/}


其中12意义相同

代码片断
"transform": {
    "prefix": "tr",
    "body": "${TM_FILENAME_BASE/(global).*/$1/}"    // 输出: global
}
// 待匹配字符串 global-js
// 正则表达式 (global).*
// 所有匹配项 global-js
// 分组捕获组 ['global'],即$1=global
// $1替换global-js,即输出 global
复制代码

3表示对匹配到的相应分组捕获组转化成大写,或小写,或首字母大写,以替换正则表达式匹配到的所有字符串

代码片断(该片断仅展现转化成大写)
"transform": {
    "prefix": "tr",
    "body": "${TM_FILENAME_BASE/(global).*/${1:/upcase}/}"  // 输出: GLOBAL
}
// 待匹配字符串 global-js
// 正则表达式 (global).*
// 所有匹配项 global-js
// 分组捕获组 ['global'],即$1=global
// 经${1:/upcase}转换后,为GLOABl
// ${1:/upcase}替换global-js,即输出GLOABl
复制代码

4表示匹配成功时,将if所述语句彻底替换正则表达式匹配项。

代码片断
"transform": {
    "prefix": "tr",
    "body": "${TM_FILENAME_BASE/(global)/${1:+if}/}",   //输出: if-js
    // 待匹配字符串 global-js
    // 正则表达式 (global)
    // 所有匹配项 global
    // 分组捕获组 ['global'],即$1=global
    // $1匹配成功, if替换global,即输出if-js

    "body": "${TM_FILENAME_BASE/(global).*/${1:+if}/}"   //输出: if
    // 待匹配字符串 global-js
    // 正则表达式 (global).*
    // 所有匹配项 global-js
    // 分组捕获组 ['global'],即$1=global
    // $1匹配成功, if替换global-js,即输出if
}
复制代码

上述三、4两个示例匹配失败时,不作转换,即仍输出global.js


5表示匹配成功,且相应的分组捕获成功时,将if所述语句彻底替换正则表达式匹配项;

匹配成功,且相应的分组捕获为空时,将else所述语句彻底替换正则表达式匹配项;

匹配失败时,else所述语句即为处理后的变量

代码片断
"transform": {
    "prefix": "tr",
    "body": "${TM_FILENAME_BASE/(global)/${1:?if:else}/}",   //输出: if-js
    // 待匹配字符串 global-js
    // 正则表达式 (global)
    // 所有匹配项 global
    // 分组捕获组 ['global'],即$1=global
    // $1匹配成功, if替换global,即输出if-js

    "body": "${TM_FILENAME_BASE/global/${1:?if:else}/}",   //输出: else-js
    // 待匹配字符串 global-js
    // 正则表达式 global
    // 所有匹配项 global
    // 分组捕获组 [],$1对应的分组捕获为空
    // else替换global,即输出else-js

    "body": "${TM_FILENAME_BASE/(globald)/${1:?if:else}/}",   //输出: else
    // 待匹配字符串 global-js
    // 正则表达式 (globald),匹配失败
    // 所有匹配项 null
    // 分组捕获组 无
    // 输出 else
}
复制代码

67,表示匹配成功,且分组捕获(正则表达式中()匹配到的项)成功时,不变;

匹配成功,且分组捕获为空时,将else所述语句彻底替换正则表达式匹配项;

匹配失败时,else所述语句即为处理后的变量

代码片断
"transform": {
    "prefix": "tr",
    "body": "${TM_FILENAME_BASE/(global)/${1:else}/}",   //输出: global-js
    // 待匹配字符串 global-js
    // 正则表达式 (global)
    // 所有匹配项 global
    // 分组捕获组 ['global'],即$1=global
    // 不变,输出global-js

    "body": "${TM_FILENAME_BASE/global/${1:else}/}",   //输出: else-js
    // 待匹配字符串 global-js
    // 正则表达式 global
    // 所有匹配项 global
    // 分组捕获组 [],$1对应的分组捕获为空
    // else替换global,输出else-js

     "body": "${TM_FILENAME_BASE/(globald)/${1:else}/}",   //输出: else
    // 待匹配字符串 global-js
    // 正则表达式 (globald),匹配失败
    // 所有匹配项 null
    // 分组捕获组 无
    // 输出 else
}
复制代码

8表示匹配成功时,将text彻底替换正则表达式匹配项; 匹配失败时,不变

代码片断
"transform": {
    "prefix": "tr",
    "body": "${TM_FILENAME_BASE/(global)/text/}",   //输出: text-js
}
复制代码

Placeholder Transform 本质与Variable Transform的第8条一致,只是正则表达式变为占位符常量

代码片断
"transform": {
    "prefix": "tr",
    "body": "$1 -> ${1/placeholder/text/}",   //输入: placeholder 输出: placeholder -> text
}
复制代码

快捷键导入snippet

首先打开keybindings.json

而后能够添加以下

{
  "key": "cmd+k 1",
  "command": "editor.action.insertSnippet",
  "when": "editorTextFocus",
  "args": {
    "snippet": "console.log($1)$0"
  }
}
复制代码

亦或者导入已有的snippet

{
  "key": "cmd+k 1",
  "command": "editor.action.insertSnippet",
  "when": "editorTextFocus",
  "args": {
    "langId": "csharp", // 语言包,如javascript,typescript
    "name": "myFavSnippet"  // 对应最开始提过的name,以下面使用场景的`import snippet`
  }
}
复制代码

使用场景

  1. snippet写snippet
"import snipppet": {
    "prefix": "sni",
    "body": ["\"$1\": {", "\t\"prefix\": \"$2\",", "\t\"body\": [\"$3\"]", "}"]
  },
复制代码

加个可选的description

"snipppet": {
    "prefix": "sni",
    "body": [
      "\"$1\": {",
      "\t\"prefix\": \"$2\",",
      "\t\"body\": [\"$3\"]${4:,\n\t\"description\":\"${5}\"}",
      "}"
    ]
  }
复制代码
  1. 某些固定格式的数组对象

如某个数组长度较大的对象{title:'input',dataIndex:'input'},该数组中对象的每一个titledataIndex不尽相同,则能够写个临时snippet,直接生成该结构。

"templates": {
    "prefix": "tem",
    "body": [",{dataIndex:$1,", "title:$2}"],
    "description": ""
}
复制代码
  1. 某些固定内容的文件或者某些经常使用的import组合
"muji store file": {
    "prefix": "store",
    "body": [
      "import { createStore } from '@souche-f2e/muji'",
      "",
      "type IState = {",
      "",
      "}",
      "const state: IState = {",
      "",
      "}",
      "const store = createStore({",
      " state,",
      " reducers: {",
      " ",
      " },",
      " effects: {",
      " ",
      " },",
      "})",
      "export default store"
    ]
  },
复制代码
  1. muji react 下的pages文件
"index.tsx under pages": {
    "prefix": "ind",
    "scope": "typescriptreact",
    "body": [
      "import React, { Component } from 'react'",
      "import { dispatch, IRootState } from '@@/store'",
      "import { connect } from '@souche-f2e/muji'",
      "const mapStateToProps = (state: IRootState) => state.${1/(.*)/${1:/downcase}/}",
      "",
      "interface I$1Props extends ReturnType<typeof mapStateToProps> {",
      " ",
      "}",
      "interface I${1}State {",
      "",
      "}",
      "class $1 extends Component<I${1}Props, I${1}State> {",
      " render() {",
      " return ${3:null}",
      " }",
      "}",

      "export default connect(mapStateToProps)($1)"
    ]
  }
复制代码

只须要输入组件名,就能够一次性生成必备的格式。

最后

安利一下prettier,配合snippet使用,简直绝配。