【VSC】Snippets不完全指南

大搜車無線開發中心發表於2019-04-08

更多文章,參見大搜車技術部落格:blog.souche.com/

大搜車無線開發中心持續招聘中,前端,Nodejs,android 均有 HC,簡歷直接發到:sunxinyu@souche.com

前言

大家都是出來寫程式碼的,少不了要寫上千萬來行程式碼,其中重複性的程式碼佔比又會很大。那麼如何避免一次又一次寫重複性的程式碼呢?除了程式碼自身的優雅、可複用,Jetbrains系如Intellij IDEA的Live Templates或Visual Studio Code的Snippet等內建工具都能很大程度上幫我們少寫很多重複性的程式碼。下面就vsc的Snippet,結合demo,講講它的用法。

建立Snippet

快捷鍵Cmd + Shift + PF1開啟命令視窗,輸入snippet,選中配置使用者程式碼片段,這時候會彈出

【VSC】Snippets不完全指南

可以在現有的程式碼片段檔案上新增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(非必填).

上述英文解釋引自官方,怕翻譯不到位影響讀者理解,就不作翻譯了。

下面用一張圖展示在使用中各個屬性對應的位置

【VSC】Snippets不完全指南
上圖左側框中的為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的過程

【VSC】Snippets不完全指南

Placeholders:佔位符。使用者直接跳過Tabstops不輸入新值時,會使用佔位符。它還能內嵌。

以下是佔位符內嵌的示例

{
    "placeholder": {
    "prefix": "ph",
        "body": "${1:hello ${2:world}}$0"
    }
}
複製程式碼

以下是使用上述snippet的過程

【VSC】Snippets不完全指南

Choice: 可選的佔位符

以如下snippet為例

{
    "choice": {
        "prefix": "ch",
        "body": "${1|hello,hi,how are you|}"
    }
}
複製程式碼

輸入ch按下tab鍵後,就會顯示

【VSC】Snippets不完全指南
注: 在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
}
複製程式碼

上述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

【VSC】Snippets不完全指南

然後可以新增如下

{
  "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)"
    ]
  }
複製程式碼

只需要輸入元件名,就可以一次性生成必備的格式。

【VSC】Snippets不完全指南

最後

安利一下prettier,配合snippet使用,簡直絕配。

相關文章