【VSC】Snippets不完全指南

OnePiece  |  2019. 03. 29   |  阅读 784 次
无线开发

前言

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

创建Snippet

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

可以在现有的代码片段文件上新增snippet,也可以自己新建代码片段文件。

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(非必填).

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

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

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

Snippet语法

Snippet的语法集中在body上。

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

以下是多个相同的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末尾必须加/,才能被识别。如${TMFILENAMEBASE/(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
}

上述3、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`
  }
}

使用场景

  • 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}\"}",
    "}"
  ]
}  
  • 某些固定格式的数组对象

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

"templates": {
    "prefix": "tem",
    "body": [",{dataIndex:$1,", "title:$2}"],
    "description": ""
}
  • 某些固定内容的文件或者某些常用的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"
    ]
  },
  • 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使用,简直绝配。

分享到

   
聊一聊前端换肤
加入我们