本篇內容主要講解“VS Code 插件是如何提高編碼效率的”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“VS Code 插件是如何提高編碼效率的”吧!
成都創(chuàng)新互聯(lián)公司專注于前郭企業(yè)網站建設,響應式網站設計,商城網站建設。前郭網站建設公司,為前郭等地區(qū)提供建站服務。全流程定制開發(fā),專業(yè)設計,全程項目跟蹤,成都創(chuàng)新互聯(lián)公司專業(yè)和態(tài)度為您提供的服務
在開始正題之前,我們先回憶一下自己在 VS Code 上常用并且獲得編碼幸福度的是不是包含以下幾個點。
我們經常可以在不同后綴的文件還有文件里不同地方都看到代碼片段。輸入約定的幾個短短字符,就可以擁有一片或大或小的代碼段,解放雙手,節(jié)約時間,還能提升每日代碼量。
以下圖片來自插件: vue-vscode-snippets
解救“懶癌”的另一個常用“解藥”就是代碼提示了??赡芷綍r你并不會注意到它,但是這個功能對于像我一樣單詞記憶水平一般且記不全所有枚舉值的人來說,簡直就是完美!
以下圖片來自插件: vue-helper
相信看了上面的例子,聰明的你已經深有體感啦。那接下去我們就直奔主題——實現(xiàn)上面所說的代碼片段和代碼提示功能!在這之前,我們先回到 VS Code 官網來看一下 Language Extensions API以及他可以幫我們實現(xiàn)哪些。
首先 Visual Studio Code 通過語言擴展為不同的編程語言提供了智能編輯功能。雖然他不提供內置語言支持,但卻提供了一組支持豐富語言功能的 API??偟膩碚f,VS Code 插件語言類相關的 API 分為兩大類,一類是「聲明語言特性」,一類是「程序語言特性」。前者主要通過在配置文件中定義,而后者通過在代碼中注冊而激活。
我們首先從「聲明語言特性」的代碼片段入手,看看僅僅一份配置文件是如何幫助我們提高工作效率的。
首先,我們在 package.json 里面增加一個 snippets
的入口,位于 contributes
的下級:
"contributes": { "commands": [ { "command": "test.helloGitHub", "title": "Hello World" }, { "command": "test.button", "title": "按鈕", "icon": { "light": "./media/light/preview.svg", "dark": "./media/dark/preview.svg" } } ], "menus": { "editor/title": [ { "command": "test.button", "group": "navigation", "when": "resourceLangId == javascript" } ], "editor/context": [ { "command": "test.button", "group": "navigation", "when": "resourceLangId == javascript" } ] }, // 就是這里了??! "snippets": [ { "language": "javascript", "path": "./snippets/javascript.json" } ] },
也就是這個位置,需要你手動新建一個文件夾和文件:
接下去就是重點、重點、重點。我們如何寫代碼片段的配置文件呢?如果你抱著強烈的好奇心,你可以前往官網查看這份詳細的教程。如果你想先看一眼簡單的配置該如何寫,那就隨著本文一起來看吧~
我們還是先「眼見為實」來看看下面的這份配置,會有什么奇妙的效果,先上配置代碼:
{ "forLoop": { "prefix": "for", "body": [ "for(let i = 0; i < ${1: array.length}); i++) {", "\t$BLOCK_COMMENT_START HelloGitHub: 這里可以寫你的代碼 $BLOCK_COMMENT_END", "}" ], "description": "for 循環(huán)" } }
再來看看插件運行后的提示效果(一定要看仔細哪個是來自我們插件的哦):
最后我們自信的按下「Enter」回車鍵,就會看到一段代碼已經在我們的 js
文件里了
for(let i = 0; i < array.length); i++) { /* HelloGitHub: 這里可以寫你的代碼 */ }
那我們就來回顧一下上面那份配置文件,究竟是如何生成這一份代碼的。
字段 | 含義 |
---|---|
forLoop | 是代碼段名稱。如果未提供 description,則通過 IntelliSense 顯示 |
prefix | 定義一個或多個在 IntelliSense 中顯示摘要的觸發(fā)詞。 |
body | 是一或多個內容行,插入時將作為多行加入。換行符和嵌入的選項卡將根據(jù)插入代碼段的上下文進行格式化 |
description | IntelliSense 顯示的代碼段的描述(非必填) |
首先這份配置會有一個名字即 forLoop
,是可以用戶隨意自定義的,我們可以看到它支持大小寫,加空格還有加橫杠,當然你或許要問它支不支持中文,那我可以告訴你:支持。但是并不建議這么寫,因為我們的眼界要放大嘛,走向國際(international)~
其次如果你想要匹配多個 prefix
,你可以修改你的代碼如下:
{ "forLoop": { "prefix": ["for", "for-const"], "body": [ "for(let i = 0; i < ${1:array.length}); i++) {", "\t$BLOCK_COMMENT_START HelloGitHub: 這里可以寫你的代碼 $BLOCK_COMMENT_END", // \t 表示縮進,$BLOCK_COMMENT_START 和 $BLOCK_COMMENT_END 表示注釋的開始和結束。 // 和 /**/ 這兩種都支持 "}" ], "description": "for 循環(huán)" } }
效果如下:
而且子字符串匹配是在前綴上執(zhí)行的,因此,在這種情況下, fc
可以匹配 for-const
:
呈現(xiàn)的代碼片段:
控制編輯器光標在代碼內移動。你可以使用$1
,$2
指定游標的位置,數(shù)字表示 Tab 鍵訪問的順序,出現(xiàn)相同的會被同步更新,$0
表示光標最后一個位置,當光標位于指定位置的情況下就會退出這個模式。
可能光看文字你會有點迷糊,那我們直接修改上面的 for
循環(huán):
{ "For Loop": { "prefix": ["for", "for-const"], "body": ["for (const ${2:element} of ${1:array}) {", "\t$0", "}"], "description": "A for loop." } }
效果(用 tab 切換),順序是 $1
> $2
> $0
:
其實從前面的例子你應該就知道了占位符這個東西就是一個帶有默認值的語法,例如${1:foo}
。占位符文本將被插入和選擇,以便用戶可以輕松更改。并且占位符還可以進行嵌套,例如${1:another ${2:placeholder}}
當然啦對于喜歡偷懶的“我們”來說,能省一點時間是一點時間,因此占位符也可以讓我們只動動上下鍵就可以完成輸入。語法是用逗號分隔的值枚舉,觸發(fā)插入代碼段并選擇占位符后,選項將提示用戶選擇其中一個值。
修改我們的代碼如下:
{ "forLoop": { "prefix": ["for", "for-const"], "body": [ "for(let i = 0; i < ${1:array.length}); i++) {", "\t$BLOCK_COMMENT_START HelloGitHub: 這里可以寫你的代碼 $BLOCK_COMMENT_END", "\t\t${2|one,two,three|}", "}" ], "description": "for 循環(huán)" }, }
效果:
不知道你有沒有注意上面代碼中的一個小注釋:
{ ... "\t$BLOCK_COMMENT_START HelloGitHub: 這里可以寫你的代碼 $BLOCK_COMMENT_END", // \t 表示縮進,$BLOCK_COMMENT_START 和 $BLOCK_COMMENT_END 表示注釋的開始和結束。 // 和 /**/ 這兩種都支持 ... }
里面就用到了一個注釋的變量 $BLOCK_COMMENT_START
和 $BLOCK_COMMENT_END
。這個語法允許我們使用$name
或${name:default}
這兩種方式來設置插入的變量值。未設置變量時,將插入其默認值或空字符串。當變量未知(即未定義其名稱)時,將插入該變量的名稱,并將其轉換為占位符。從 VS Code 官網上可以看到所有支持的變量:
比如我們修改我們的例子如下:
{ "forLoop": { "prefix": ["for", "for-const"], "body": [ "for(let i = 0; i < ${1:array.length}); i++) {", "\t$BLOCK_COMMENT_START HelloGitHub: 這里可以寫你的代碼 $BLOCK_COMMENT_END", "\t\tconsole.log('choice', ${2|one,two,three|})", "\t\tconsole.log('year', ${CURRENT_YEAR})", "\t\treturn ${name:value}", "}" ], "description": "for 循環(huán)" }
效果:
到這個例子為止你會發(fā)現(xiàn)我們的代碼片段變得越來越長,越來越豐富,也就是我們可以偷的懶就“越來越多”,不經意間就可以提高開發(fā)效率有沒有?
可能我的例子太簡單你沒有體感,那我們來看一個這個,應該有非常多的人眼熟:
對應的代碼配置其實也就是我們上面說的那幾個語法:
{ "hellogithub": { "prefix": "swiper", "body": [ "", "\t $7" ], "description": "滑塊視圖容器" } }", "\t\t ", "", "\t\t\t", "\t\t ", "\t
當然啦如果你有志于寫一個非常好用的代碼片段,上面這些可能還不能滿足你的話,可以學習一下 TextMate 更多高級的語法(上文中其實算是 TextMate 的基礎語法,言外之意就是比較常用而且看起來就很簡單易懂)。簡單的介紹一下 TextMate,它是 Mac下的著名的文本編輯器軟件,它可以根據(jù)一定的語言規(guī)則可以匹配文檔的結構,也可以按照一定的語法規(guī)則快速生成代碼片段。
上面介紹了通過配置就可以完成的「聲明類語言特性」,讓我們再來看一個「程序類語言特性」—— registerCompletionItemProvider
。
我們首先看個圖,是不是也覺得是個“偷懶”神器呀!但是你有沒有疑惑過,為什么這個編輯器知道我們即將要寫的是什么?為什么它還可以給我們推薦寫什么?如果你覺得這是計算機時代智慧的結晶的話,那我也不能說你錯。那么今天,我們就親自來“揭秘”這個功能,可以用registerCompletionItemProvider
這個來實現(xiàn)。
接下去我們就進入代碼實現(xiàn)了,還記得上一篇文章的 extension.js
嗎?我們在這里加上這么一段代碼:
const completion = vscode.languages.registerCompletionItemProvider( 'javascript', { provideCompletionItems(document, position) { const linePrefix = document.lineAt(position).text.substr(0, position.character); if (!linePrefix.endsWith('hello.')) { return undefined; } return [ new vscode.CompletionItem('HelloGitHub', vscode.CompletionItemKind.Property), new vscode.CompletionItem('HelloWorld', vscode.CompletionItemKind.Property), new vscode.CompletionItem('HelloPeople', vscode.CompletionItemKind.Property), ]; } }, '.' // triggered whenever a '.' is being typed ); context.subscriptions.push(completion);
然后先來看一下效果:
這里可能會有小伙伴掉進“坑里”——如果你在實現(xiàn)的過程中發(fā)現(xiàn)效果出不來可以按下面的思路先判斷和解決試試:
1、看一下當前文件的后綴是不是正確的。比如上面代碼里規(guī)定了 javascript
,那就要在 .js
后綴的文件里面才有效
2、注冊命令當然也和插件的生命周期息息相關,如果你發(fā)現(xiàn)上一步是正確的,那你就要去 package.json
文件里面看看 activationEvents
里面的命令是否觸發(fā)了。如果你忘記如何觸發(fā)插件激活的生命周期,那你就改成這樣。
... "activationEvents": [ "*" ], ...
3、如果上面兩個還沒有解決你的問題的話,那肯定是你上面代碼 ctrl+c ctrl+v 的不對!開個玩笑,如果你還是不能實現(xiàn)的話……那你就留言評論點個贊來個三聯(lián)么么噠~
回歸一下正題,我們來分析一下上面的代碼是如何實現(xiàn)的:
const completion = vscode.languages.registerCompletionItemProvider( // 這里是注冊這個 Provider 有效的相關文件,支持字符串類型或 DocumentFilter 對象。 // 如果你要對多個后綴的文件做操作的話可以用數(shù)組的形式,例如 ['javascript', 'plaintext'] // DocumentFilter 對象包含三個字段(均非必須),例如:{ language: 'json', scheme: 'untitled', pattern: '**/package.json' } 'javascript', ... }
... { // 這是代表了一個 provider provideCompletionItems(document, position) { // 拿到當前 `position` 的 text 并且判斷一下是否以 `hello.` 開頭 const linePrefix = document.lineAt(position).text.substr(0, position.character); // 沒有匹配到則不予提示 if (!linePrefix.endsWith('hello.')) { return undefined; } // 如果匹配成功就返回 CompletionItem 有:HelloGitHub、HelloWorld、HelloPeople return [ new vscode.('HelloGitHub', vscode.CompletionItemKind.Property), new vscode.CompletionItem('HelloWorld', vscode.CompletionItemKind.Property), new vscode.CompletionItem('HelloPeople', vscode.CompletionItemKind.Property), ]; } }, ...
可能你會疑惑, vscode.CompletionItemKind.Property
是什么東西呢?說簡單一點其實就是個圖標的配置。我們可以換幾個屬性來看看差別:
... return [ new vscode.CompletionItem('HelloGitHub', vscode.CompletionItemKind.Method), new vscode.CompletionItem('HelloWorld', vscode.CompletionItemKind.Enum), new vscode.CompletionItem('HelloPeople', vscode.CompletionItemKind.Property), ]; ...
從 index.d.ts
可以看到它支持以下這么多類型的圖標,可以根據(jù)不同的需求來選擇你想要的圖標,當然啦這里就不重點展開啦,有興趣的可以自己把這些圖標都整理一下~
/** * Completion item kinds. */ export enum CompletionItemKind { Text = 0, Method = 1, Function = 2, Constructor = 3, Field = 4, Variable = 5, Class = 6, Interface = 7, Module = 8, Property = 9, Unit = 10, Value = 11, Enum = 12, Keyword = 13, Snippet = 14, Color = 15, Reference = 17, File = 16, Folder = 18, EnumMember = 19, Constant = 20, Struct = 21, Event = 22, Operator = 23, TypeParameter = 24, User = 25, Issue = 26, }
最后就解釋一下這個觸發(fā)條件:
... '.' // 當鍵盤打 . 的時候觸發(fā),支持多個觸發(fā) ...
我們可能會遇到不同場景需要不同的觸發(fā)條件,這時候就盡管往后加就好了,例如我們新加幾個特殊符號的觸發(fā)條件(這里先去掉匹配字符串的邏輯,以便于更好的觸發(fā)):
const completion = vscode.languages.registerCompletionItemProvider( ['javascript', 'xml'], { provideCompletionItems(document, position) { // const linePrefix = document.lineAt(position).text.substr(0, position.character); // if (!linePrefix.endsWith('hello')) { // return undefined; // } return [ new vscode.CompletionItem('HelloGitHub', vscode.CompletionItemKind.Method), new vscode.CompletionItem('HelloWorld', vscode.CompletionItemKind.Enum), new vscode.CompletionItem('HelloPeople', vscode.CompletionItemKind.Property), ]; } }, '.', ',', ' ' );
但是正常情況下,我們往往需要去解析用戶輸入的不同內容,來給與不同對應的 completion item。所以接下去我們就以 xml
文件為例,來寫一個“功能強大”的 Completion Proviwder。
先來分析一下 xml
這種文件常見的 Completion Provider 大致有這么三種:
標簽名
屬性名
屬性值
當然啦,如果像是 vue
里面 template
模板的寫法,其實還有事件名這類等。那我們就以 @ 符號作為事件名提示的觸發(fā)條件,以 < 作為標簽名提示的觸發(fā)條件,以空格、回車作為屬性名的觸發(fā)條件,以單雙引號作為屬性值的觸發(fā)條件,先寫一個簡單的實現(xiàn):
// 引入兩個 mock 文件 const testEventName = require("./mock/testEventName"); const testTagName = require("./mock/testTagName"); ... const completion = vscode.languages.registerCompletionItemProvider( 'xml', { provideCompletionItems( document, // 命令被調用的文檔 position, // 命令被調用的位置 token, // 取消令牌 context // 自動補全是怎么觸發(fā)的 ) { // 如果校驗命中了取消令牌,就不提示 if (token.isCancellationRequested) { return Promise.resolve([]) } let char = context.triggerCharacter switch (char) { case '<': // 標簽名提示 // todo case '@': // 綁定事件 // todo default: // 屬性名、屬性值等 // todo } } }, '@', '\n', ' ', '"', "'", '<' )
mock 文件可以隨便定一個結構,下面是本文例子中用到的 mock 數(shù)據(jù)結構(兩個文件):
// ./mock/testEventName module.exports = [ { name: 'onTap', id: 'ontap', desc: '這是一個點擊事件的描述' }, { name: 'for', id: 'for', desc: '這是一個循環(huán)事件的描述' } ] // ./mock/testTagName module.exports = [ { name: 'HelloGitHub', id: 'hg', description: '這是我們的名字' }, { name: 'Welcome', id: 'wlc', description: '歡迎關注和喜歡我們' } ]
先來實現(xiàn)一下標簽名的 Completion Provider:
const completionArr = [] for (let i = 0; i < testTagName.length; i++) { const commandCompletion = new vscode.CompletionItem(testTagName[i].name); commandCompletion.kind = vscode.CompletionItemKind.Property; commandCompletion.documentation = new vscode.MarkdownString(testTagName[i].description); let snippet = `${testTagName[i].name}\n` + ' name="${1:HelloGitHub}"\n' + ' desc="${2:We are serious about open source}"\n' + '>\n' + ``; commandCompletion.insertText = new vscode.SnippetString(snippet); completionArr.push(commandCompletion) } return completionArr;
我們可以看到和上面講過的內容差不多,也是需要 new
一個 CompletionItem
對象,但是這里把這個對象更加的“豐富化”了,通過增加屬性的方式給這個 CompletionItem
增加了圖標——kind
、說明——documentation
、還有片段——insertText
。
讓我們來看一下效果,如果沒有自動出現(xiàn)說明,就點一下 Completion 最右側的小箭頭:
同樣的我們也來寫一下事件的 Completion Provider,簡直就是 ctrl+c 和 ctrl+v:
if (testEventName && testEventName.length > 0) { const arr = [] for(let i = 0; i < testEventName.length; i++) { const item = testEventName[i] const commandCompletion = new vscode.CompletionItem(item.name); commandCompletion.kind = vscode.CompletionItemKind.Property; commandCompletion.documentation = new vscode.MarkdownString(item.desc || '暫無介紹'); let snippet = `${item.name}{}`; commandCompletion.insertText = new vscode.SnippetString(snippet); arr.push(commandCompletion) } return arr } return []
效果:
接下去我們就要攻克最后的一個點:屬性值和屬性名。這就涉及到分析當前文本的結構,我們默認單雙引號所在的位置標示屬性值,挨著 < 符號的是標簽名,剩下的就都是作為屬性值。
所以第一步,我們寫一個方法,用來解析和獲取我們上面想要知道的文檔結構,這一部分的代碼我們寫到一個新的文件引用過去(getTagAtPosition.js
):
function getTagAtPosition(doc, pos) { let offset = doc.offsetAt(pos); let text = doc.getText(); // 因為引號里可能會有任何字符,所以做一層替換處理 let attrFlagText = text.replace(/("[^"]*"|'[^']*')/g, replacer('%')); // 標簽起始位置 [start,length] const range = getBracketRange(attrFlagText, offset); if (!range) { return null } const [start, end] = range; offset = offset - start; text = text.substr(start, end); attrFlagText = attrFlagText.substr(start, end); const tagNameMatcher = attrFlagText.match(/^<([\w-:.]+)/); if (!tagNameMatcher) { return null; } const name = tagNameMatcher[1]; // 標簽名稱 const isOnAttrValue = attrFlagText[offset] === '%'; const attrName = isOnAttrValue ? getAttrName(attrFlagText.substring(0, offset)) : '' // 當前輸入對應的屬性 const isOnTagName = offset <= name.length + 1; const isOnAttrName = !isOnTagName && !isOnAttrValue return { name, // 標簽名 attrName, // 屬性名 isOnTagName, // 是否處于 tag 上 isOnAttrName, // 是否處于屬性名上 isOnAttrValue, // 是否處于屬性值上 } } // 字符替換的方法 const replacer = (char) => (raw) => char.repeat(raw.length); // 獲取 <> 標簽的位置 function getBracketRange(text, pos) { const textBeforePos = text.substr(0, pos) const startBracket = textBeforePos.lastIndexOf('<') if (startBracket < 0 || textBeforePos[startBracket + 1] === '!' || textBeforePos.lastIndexOf('>') > startBracket) { // 前沒有開始符<, // 或者正在注釋中: // 或者不在標簽中:| return null } // 從光標位置后面找 > 標簽 let endBracket = text.indexOf('>', pos + 1) if (endBracket < 0) { // 未找到閉合 > 文件結束位置為結束 // 如 const nextStart = text.indexOf('<', pos + 1) if (nextStart > 0 && nextStart < endBracket) { endBracket = nextStart } return [startBracket, endBracket - startBracket] }
對應 extension.js
里面加上我們新寫的邏輯:
... default: // 屬性、標簽等 // step1. 找最近的標簽名 let tag = getTagAtPosition(document, position); if (!tag) { return null } // 屬性值提示 if (tag.isOnAttrValue) { return getAttrValueCompletionArr(tag.attrName || '', targetObj.children) } else { // 屬性提示 return getAttrCompletionArr(targetObj.children) } ...
接下來我們加一個新的 mock 數(shù)據(jù),并且結構是一個樹狀結構,每個標簽下面都有它可能的屬性名列表(children
),同時每一個屬性名都有對應的屬性值列表(children
):
module.exports = [ { name: 'HelloGitHub', id: 'hg', description: '這是我們的名字', children: [ { name: 'hgAttrName1', children: [ { name: 'hgAttrVal1' }, { name: 'hgAttrVal2' } ] }, { name: 'hgAttrName2' } ] }, { name: 'Welcome', id: 'wlc', description: '歡迎關注和喜歡我們' } ]
看一下上面 getAttrCompletionArr
這個方法做的事情,其實就是從數(shù)據(jù)里取值出來展示這么簡單:
function getAttrCompletionArr (completionArr) { const arr = [] if (completionArr.length > 0) { for(let j = 0; j < completionArr.length; j++) { if (completionArr[j] && completionArr[j].name) { const commandCompletion = new vscode.CompletionItem(completionArr[j].name); commandCompletion.kind = vscode.CompletionItemKind.Property; arr.push(commandCompletion) } } } return arr } module.exports = getAttrCompletionArr;
那屬性值的列表的話,我們就要知道它是在哪個標簽名下的屬性名下面了:
function getAttrValueCompletionArr (attrName, completionArr) { const enumValue = completionArr.find(item => item.name === attrName) || {}; if (enumValue.children && enumValue.children.length > 0) { const arr = [] for(let i = 0; i < enumValue.children.length; i++) { const commandCompletion = new vscode.CompletionItem(enumValue.children[i].name); commandCompletion.kind = vscode.CompletionItemKind.Property; arr.push(commandCompletion) } return arr } return [] }
最后的效果:
可能有的朋友對于上面一串解析文檔的方法有很多疑惑,代碼里雖然有注釋,但是可能還是沒有體感,這時候就建議最好動手實踐一下,因為都是 VS Code Extension 提供的方法,所以這里不會過多展開,畢竟也不是這篇文章的重點內容嘛~
那今天給大家介紹了兩種“偷懶”并且可以幫助我們提高打代碼效率的兩種方法:
代碼片段(Snippet)
自動補充(Completion Provider)
也是眾多 VS Code 插件中非常常見的功能之一,其實走近了看也不是很難吧~
今天的內容可能略多一點,如果你看完了第一篇,第二篇是在第一篇基礎上改的,相信你一定可以跟得上。那下篇文章,我們就要來看看 VS Code 插件中另一個非常強大的功能——WebView。也就是支持在插件中打開網頁、和網頁通信、還可以寫酷炫的 CSS 樣式等等。雖然它的功能很強大,但是像一把雙刃劍,他對于資源的占用也是很大的,想知道可以怎么用嗎?請期待下一期。
到此,相信大家對“VS Code 插件是如何提高編碼效率的”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!