VSCode 插件開發(fā)入門到實踐

VSCode 有非常強大的功能,原因在于它的很多功能都是基于插件來實現(xiàn)的,可以使用插件來豐富和擴展它的功能。
但是我們的需求總是復雜多變的,總有一些場景是現(xiàn)有的插件無法滿足,這時候就需要我們借助 VSCode 的開放接口,手動實現(xiàn)我們需要的功能。

這篇文章主要帶大家從一個簡單插件開發(fā)入手來介紹插件開發(fā)的一些具體事項,最后根據(jù)實際需要實現(xiàn)一個插件。

VSCode 插件能做什么

  • 自定義命令、快捷鍵
  • 自定義上下文菜單操作,如:平時我們右鍵的菜單欄
  • 自定義跳轉(zhuǎn)、自動補全、懸浮提示
  • 自定義主題
  • 自定義代碼片段
  • 新增語言支持
  • 語法檢查
  • 語法高亮
  • 代碼格式化
  • ……

新手教程——如何編寫一個插進

VSCode 官方提供了腳手架 yomen,用它來生成項目:

  npm install -g yo generator-code

初始化一個插件項目,這個腳手架會生成一個可以立馬開發(fā)的項目:

  yo code

選擇生成插件(JS 或 TS),初始化過程中需要我們做一些偏好設(shè)置,按照需求選擇:

1280X1280.PNG

打開生成的項目,其中最重要的兩個文件是插件的入口 extension.js 和配置文件 package.json

image.png

注冊命令

extension.js 文件主要會導出兩個方法:activatedeactivate

  • activate,插件被激活時執(zhí)行的方法;
  • deactivate,插件被銷毀時調(diào)用的方法;
    在插件被激活時(activate方法內(nèi)),使用 vscode.commands.registerCommand來注冊命令,第一個參數(shù)是命令I(lǐng)D,第二個參數(shù)是執(zhí)行命令后的回調(diào)函數(shù)。
    例如,下面這段代碼:
// 入口文件 extension.js
const vscode = require('vscode');

function activate(context) {
  let disposable = vscode.commands.registerCommand('helloWorld', function () {
    vscode.window.showInformationMessage('Hello World from !');
  });

  context.subscriptions.push(disposable);
}

function deactivate() { }

module.exports = {
  activate,
  deactivate
}

注冊了一個命令 helloWorld。當用戶執(zhí)行 helloWorld 命令,就會調(diào)用對應的處理函數(shù)。你也可以通過 vscode.commands.executeCommandAPI 去調(diào)用它。

創(chuàng)建面向用戶的命令

vscode.commands.registerCommand 僅僅是將命令id綁定到了處理函數(shù)上,如果想讓用戶從命令面板中搜索到這個命令,還需要在 package.json 中配置對應的命令配置項:

{
    "contributes": {
        "commands": [
            {
                "command": "helloWorld",
                "title": "Hello World"
            }
        ]
    }
}

上面的配置,給命令 helloWorld 設(shè)置了一個 title Hello World,通過設(shè)置title控制命令在UI中的顯示。在搜索命令時,顯示的是標題 Hello World。

調(diào)試

在開發(fā)插件的時候,按下F5就會彈出一個新的 VSCode 窗口。
新窗口標題會注明擴展開發(fā)宿主,表示是插件調(diào)試,新窗口已經(jīng)成功加載了我們的插件:


image.png

按下Ctrl+Shift+P搜索命令,輸入剛剛注冊的命令Hello World,選擇執(zhí)行命令,就會觸發(fā)命令的回調(diào)函數(shù)(右下角彈出Hello World from !提示)

image.png

最簡單的插件就實現(xiàn)了!


接下來,了解一下插件的一些配置字段。

插件激活

插件并不是安裝啟用之后就會激活。像這樣,這個插件就處于安裝啟用但是尚未激活狀態(tài):

image.png

插件具體什么時候激活是在 package.json 中的 activationEvents 聲明。所有插件都是按需加載的,每個插件都應該聲明具體的加載時機。
以下是幾種常用的聲明方式:

  • * ,VSCode 啟動時,插件開始觸發(fā):
{
    "activationEvents": ['*']
}
image.png
  • onStartupFinished,VSCode 啟動一段時間后才會激活插件。類似于 * 類激活事件,但它不會減慢 VSCode 啟動,該事件在所有 * 類插件激活完成后觸發(fā):
{
    "activationEvents": [
        "onStartupFinished"
    ]
}
image.png
  • onCommand:Hello World , 當執(zhí)行命令 Hello World 時激活插件:
"activationEvents": [
  "onCommand:Hello World"
]
  • onLanguage:languageId, 編輯區(qū)包含特定語言文件時激活插件。
image.png

只要編輯區(qū)(上圖紅色區(qū)域)打開 json、markdown 或者 ts文件時,激活該插件:

"activationEvents": [
  "onLanguage:json",
  "onLanguage:markdown",
  "onLanguage:typescript"
]

這幾個激活事件可以覆蓋大部分情況,還有其他一些激活事件,沒有一一列舉,感興趣的可以閱讀一下官方文檔。

內(nèi)置命令

在開發(fā)插件的時候,經(jīng)常需要通過 VSCode 提供的 API 來進行一些UI操作,例如打開文檔、修改并保存文檔內(nèi)容、關(guān)閉文檔、刷新文件夾等。
使用 vscode.commands.executeCommandAPI 可以調(diào)用一個命令,你可以通過它將 VSCode 的內(nèi)置函數(shù)構(gòu)建在你的插件中。
例如,下面的代碼用來關(guān)閉 VSCode 當前打開的文檔:

  vscode.commands.executeCommand('workbench.action.closeActiveEditor');

關(guān)閉所有打開的文檔:

  vscode.commands.executeCommand('workbench.action.closeAllEditors');

效果就和這個一樣


image.png

刷新當前工作區(qū)文件夾:

vscode.commands.executeCommand('workbench.files.action.refreshFilesExplorer');

更多命令詳見:

有些功能可以通過查看 VSCode 的官方文檔找到對應的 API 接口和相應的對象,但很多功能是沒有寫到文檔上的。

最簡單的查找命令的方式,直接看 VSCode 的鍵盤快捷方式


image.png

image.png

contributes

contributes 是 VSCode 插件開發(fā)中的一個重要配置屬性,用來定義插件的命令、快捷鍵、語言支持、主題、配置等。

下面列舉一些常用的屬性及其作用:

命令配置

contributes.commands,定義插件提供的命令。
包括命令的標識符(command)、命令的標題(title)。
一個命令注冊完后,需要在 commands中配置,用戶才能搜索到該命令。

{
    "contributes": {
        "commands": [
            {
                "command": "helloWorld",
                "title": "Hello World"
            }
        ]
    }
}

配置右鍵菜單

右鍵菜單有很多配置,這里只列舉比較常見的。

編輯器面板菜單

editor/context 配置命令什么時候出現(xiàn)在編輯文件右鍵菜單中:

{
    "contributes": {
        "menus": {
             "editor/context": [
                {
                  "command": "helloWorld", 
                  "group": "navigation",
                  "when": "true"
                }
             ]
        }
    }
}
  • command 定義菜單被點擊后要執(zhí)行什么操作;
  • group 定義菜單分組;
  • when 控制菜單合適出現(xiàn);

when語句語法有很多,這里列舉幾個常用的:

  • resourceLangId == javascript:當編輯的文件是js文件時;
  • resourceFilename == test.js:當當前打開文件名是test.js時;
  • isLinux、isMac、isWindows:判斷當前操作系統(tǒng);
  • editorFocus:編輯器具有焦點時;
  • editorHasSelection:編輯器中有文本被選中時;
  • view == someViewId:當當前視圖ID等于someViewId時;
  • ……
    多個條件可以通過與或非進行組合,例如:editorFocus && isWindows && resourceLangId == javascript。

資源管理器面板菜單

explorer/context 配置命令什么時候出現(xiàn)在資源管理區(qū)右鍵菜單中:

{
    "contributes": {
        "menus": {
             "explorer/context": [
                {
                  "command": "helloWorld",
                  "group": "navigation",
                  "when": "true"
                }
             ]
        }
    }
}

右鍵打開資源管理區(qū)菜單,多了一個 Hello World命令,如下:

image.png

控制命令何時可見

有些命令是場景相關(guān)的,比如在特定的語言的編輯器中,或者只有用戶設(shè)置了某些選項時才展示。
menus.commandPalette 限制命令出現(xiàn)在命令面板的時機,你需要配置命令I(lǐng)D和一條when語句:

{
    "contributes": {
        "menus": {
            "commandPalette": [
                {
                    "command": "helloWorld",
                    "when": "editorLangId == markdown"
                }
            ]
        }
    }
}

現(xiàn)在只有在編輯器打開 Markdown 文件,才能搜索到命令 helloWorld。

快捷鍵

快捷鍵設(shè)置的寫法比較簡單,contributes.keybindings :

"contributes": {
   "keybindings": [
       {
            // 指定快捷鍵執(zhí)行的操作
            "command": "helloWorld",
            // windows下快捷鍵
            "key": "ctrl+shift+l",
            // mac下快捷鍵
            "mac": "cmd+shift+l",
        }
   ]
 }

這個快捷鍵最終會在命令后面提示快捷鍵


image.png

代碼片段

snippets,輸入一個前綴,有許多提示,選擇回車后生成代碼。
提供了一些快捷鍵來幫助你更快速地使用代碼片段。

 // 配置
 "contributes": {
     "snippets": [
          {
            "language": "typescript",
            "path": "./snippets/index.json"
          }
       ]
}

這里 language 設(shè)置了 snippets 作用于哪種語言(打開文件時判斷),path 設(shè)置了代碼片段地址:

// snippets/index.json:
{
  "Snippets測試": {
    "prefix": "hello",
    "body": [
      "console.log($1)"
    ],
    "description": "描述"
  }
}

"Snippets測試" ,自定義snippet的名稱;
"prefix", 輸入什么可以出現(xiàn)snippets的提示;
"body", 按回車后出現(xiàn)的一大段代碼,是一個數(shù)組,數(shù)組里面是字符串,每個字符串代表一行代碼,{1}表示第一個光標的位置,同樣,{2}表示第二個光標的位置;
"description", 對于這個snippet的描述,當我們選中這個snipets提示時,描述會出現(xiàn)在后面。

配置

configuration,通過這個配置項我們可以設(shè)置屬性,這個屬性可以在 vscode 的 settings.json 中設(shè)置,然后在插件工程中可以讀取用戶設(shè)置的這個值,進行相應的邏輯。

 "contributes": {
     "configuration": {
         // 顯示在配置頁左側(cè)
          "title": "測試configuration",
          "properties": {
          // 全局唯一的配置ID
            "test_boolean": {
              "type": "boolean",
              "default": false,
              "description": "測試布爾"
            },
            // 全局唯一的配置ID
            "test_number": {
              "type": "number",
              "default": 14,
              "description": "測試number"
            },
            // 全局唯一的配置ID
            "test_string": {
              "type": "string",
              "default": "1",
              "description": "測試字符串"
            }
          }
    }
 }

打開設(shè)置頁,搜索配置如下:


image.png

讀取用戶配置的值:

function activate(context) {
   let disposable = vscode.commands.registerCommand('helloWorld', function () {
   // 獲取用戶設(shè)置
    const configurate = vscode.workspace.getConfiguration().get('test_string')
    vscode.window.showInformationMessage(configurate);
  });
 }

執(zhí)行命令 helloWorld:

image.png

其他

  • languages:定義插件對特定語言的支持,可以為特定的文件類型提供語法高亮、代碼片段、自動完成等功能。
  • grammars: 語法
  • debuggers:調(diào)試
  • breakpoints:斷點
  • themes:主題
  • jsonValidation:自定義JSON校驗
  • views:左側(cè)側(cè)邊欄視圖
  • viewsContainers:自定義activitybar
  • ……

實踐——自定義插件

針對業(yè)務,可以開發(fā)這樣一個插件:

  • 右鍵點擊文件夾,在菜單中增加兩個選項:
    1. "生成ReactLynx頁面"
    2. "生成ReactLynx組件"
      具體功能:
  • 根據(jù)"文件夾名稱",寫入預先定義好的代碼,類名首字母大寫
  • 把代碼寫入 "文件夾/index.tsx" 文件中
  • 刷新文件目錄,打開文件

新建項目

image.png

右鍵生成組件/頁面

注冊命令
在插件里面注冊兩個命令 reactLynxPage 和 reactLynxComponent, 根據(jù)命令執(zhí)行 generateCode 函數(shù):

export function activate(context: vscode.ExtensionContext) {
  let pageDisposable = vscode.commands.registerCommand(pageCommand, (url: vscode.Uri) => generateCode(url, pageCommand));

  let componentDisposable = vscode.commands.registerCommand(componentCommand, (url: vscode.Uri) => generateCode(url, componentCommand));
  
  context.subscriptions.push(pageDisposable);
  context.subscriptions.push(componentDisposable);
}

在 generateCode 函數(shù)中,包含兩個參數(shù) url 和 commandId:

  • 如果是通過右鍵點擊文件執(zhí)行命令,參數(shù) url 才有文件夾地址,如果不存在 url?.fsPath 則為不規(guī)范的命令執(zhí)行;
  • 通過 commondId 判斷是生成組件還是生成頁面;
function generateCode(url: vscode.Uri, commandId: string) {
  // 獲取右鍵點擊的文件夾路徑, 絕對路徑
  const folderPath = url?.fsPath;

  if (!folderPath) {
    return;
  }
  
 // …… 省略一些判斷邏輯

 // 獲取文件夾名稱, 首字母大寫, 改為駝峰式命名
 
  // 生成代碼片段內(nèi)容
  const codeSnippet = commandId === pageCommand ? generatePageSnippet(name) : generateComponentSnippet(name);
  
  // ......
 }

folderPath 是文件夾的絕對路徑,那么該文件夾下組件或頁面的地址就是 ${folderPath}/index.tsx,樣式文件地址 ${folderPath}/index.less。

通過 fs.existsSync 判斷文件是否存在,文件已存在則不生成(防止誤操作),如果不存在則生成文件寫入代碼。

    // 待生成文件地址
  const tsxFileName = `${folderPath}/index.tsx`;
  const styleFileName = `${folderPath}/index.less`;
  
   // 將代碼片段寫入文件
  if (fs.existsSync(tsxFileName)) {
      vscode.window.showInformationMessage('該目錄下已經(jīng)存在index.tsx');
  }
  else {
      fs.writeFileSync(tsxFileName, codeSnippet);
   }

  if (fs.existsSync(styleFileName)) {
    vscode.window.showInformationMessage('該目錄下已經(jīng)存在index.less');
   }
  else {fs.writeFileSync(styleFileName, `.container { 
  }`);}
  
  }

通過 vscode 提供的內(nèi)置命令刷新文件目錄:

vscode.commands.executeCommand('workbench.files.action.refreshFilesExplorer');

最后,在編輯區(qū)自動打開生成的 index.tsx :

  vscode.workspace.openTextDocument(tsxFileName).then(document => {
    vscode.window.showTextDocument(document, {
      selection: new vscode.Range(0, 0, 0, 0)
    });
  }, () => {
     vscode.window.showErrorMessage(`打開${tsxFileName}失??!`);
  });

配置命令
注冊完命令之后,在 package.json 配置 contributes.commands,這樣就能使用 reactLynxPage 和 reactLynxComponent 這兩個命令了。

"main": "./out/extension.js",
"contributes": {
    "commands": [
      {
        "command": "reactLynxPage",
        "title": "生成ReactLynx頁面"
      },
      {
        "command": "reactLynxComponent",
        "title": "生成ReactLynx組件"
      }
    ]
}

繼續(xù)配置 contributes.menus 。當右鍵點擊文件夾("when"等于"explorerResourceIsFolder"),在彈出的菜單中增加 "生成ReactLynx頁面" 和 "生成ReactLynx組件" 兩個選項。

 "contributes": {
    // .......
    "menus": {
      "explorer/context": [
        {
          "command": "reactLynxPage",
          "group": "1_modification",
          "when": "explorerResourceIsFolder"
        },
        {
          "command": "reactLynxComponent",
          "group": "1_modification",
          "when": "explorerResourceIsFolder"
        }
      ]
    }
  },

group是分組,下圖是 vscode 官方菜單分組:

image.png

配置 commandPalette 隱藏命令,無法在命令面板中搜索到 ,但是右鍵菜單的命令能照常執(zhí)行。

 "contributes": {
    // .......
    "menus": {
      "explorer/context": [
          // .......
      ],
       "commandPalette": [
            {
              "command": "reactLynxPage",
              "when": "false"
            },
            {
              "command": "reactLynxComponent",
              "when": "false"
            }
      ],
    }
  },

打包

安裝對應的模塊 vsce

  npm i vsce -g

利用 vsce 打包,生成對應的 vsix 插件文件

  vsce package
image.png

如果不想發(fā)到插件市場,直接右鍵安裝到插:


image.png

發(fā)布

注冊開發(fā)者賬號,發(fā)布到官網(wǎng)應用市場……

總結(jié)

以上就是一個簡單的入門級實戰(zhàn)教程,帶大家了解開發(fā)一個 VSCode 插件的基本思路。插件開發(fā),說簡單也簡單,說復雜也不簡單,后續(xù)大家如果遇到更復雜更定制化的需求,基本上需要查閱 官方文檔 深入學習。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容