如何开发一款 vscode 摸鱼插件

2,370 阅读8分钟

插件工程

利用脚手架搭建插件工程

vscode 官方贴心的为我们提供了插件工程生成器,我们可以用生成器很轻松的创建一个插件工程。

使用以下命令安装 Yeoman 和 VS Code Extension Generator

sudo npm install -g yo generator-code

安装完成后,生成器搭建了一个准备开发的 TypeScript 或 JavaScript 项目。运行生成器并为 TypeScript 项目填写一些字段:

yo code  
# ? What type of extension do you want to create? New Extension (TypeScript)// 使用 TypeScript 还是 JavaScript 书写插件 
# ? What's the name of your extension? HelloWorld  // 插件名称 
# ? What's the identifier of your extension? helloworld// 插件标识符 
# ? What's the description of your extension? LEAVE BLANK// 插件描述 
# ? Initialize a git repository? Yes // 是否初始化git仓库 
# ? Bundle the source code with webpack? Yes // 是否使用webpack打包项目
# ? Which package manager to use? npm// 使用哪个包管理器

工程目录

helloworld
├─ .eslintrc.json
├─ .vscode
  ├─ extensions.json
  ├─ launch.json
  ├─ settings.json
  └─ tasks.json
├─ .vscodeignore
├─ CHANGELOG.md
├─ README.md
├─ dist
  ├─ extension.js
  └─ extension.js.map
├─ package-lock.json
├─ package.json
├─ src
  ├─ extension.ts
  └─ test
     ├─ runTest.ts
     └─ suite
        ├─ extension.test.ts
        └─ index.ts
├─ tsconfig.json
├─ vsc-extension-quickstart.md
└─ webpack.config.js

工程启动

其中 .vscode 文件夹下的是 vscode 专属调试文件,在vscode中点击 F5 即可打开插件调试页面,然后按下 Ctrl+Shift+P,输入HelloWorld执行对应命令,可以发现右下角弹出了HelloWorld提示,说明你的第一个插件启动成功,如下所示:

image.png

image.png

工程解读

插件源码文件在 src 文件夹下的 extension.ts 文件中

import * as vscode from 'vscode';  
export function activate(context: vscode.ExtensionContext) {   
    let disposable = vscode.commands.registerCommand('helloworld.helloWorld', () => {
    vscode.window.showInformationMessage('Hello World from HelloWorld!');
    });    
    context.subscriptions.push(disposable); 
}  

export function deactivate() {}  
{ 
...,   
"activationEvents": [     
    "onCommand:helloworld.helloWorld"   
],   
"main": "./dist/extension.js",   
"contributes": {     
    "commands": [{         
        "command": "helloworld.helloWorld",         
        "title": "Hello World"       
    }]   
 } 
 ..., 
}

根据  extension.ts 和 package.json 文件

  • package.json文件中main字段定义了整个插件的主入口,即 webpack 编译完成后的插件入口;
  • 我们在 contributes.commands 里面注册了一个名为 helloworld.helloWorld 的命令,并在 src/extension.ts 中去实现了它(弹出一个Hello World的提示);
  • 但是仅仅这样还不够,命令虽然定义了,但是vscode还不知道啥时候去执行它,还需要在activationEvents添加上 onCommand:helloworld.helloWorld 用来告诉vscode,当用户执行了这个命令操作时去执行前面我们定义的内容;

至此,一个简单的 hello world 插件便完成了。

package.json文件解读

activationEvents

插件在VS Code中默认是没有被激活的,那什么时候才被激活呢?就是通过 activationEvents 来配置,目前支持以下 12 种配置:

配置说明示例
onLanguage**每当打开解析为某种语言的文件时,就会发出此激活事件并激活注册的扩展名,**如果我配置了onLanguage:javascript,那么只要我打开了JS类型的文件,插件就会被激活"activationEvents":["onLanguage:python"]
onCommand每当调用命令时,将发出此激活事件并激活注册的扩展****即使用 command + shift + p 调出命令行中输入对应命令后插件被激活"activationEvents": ["onCommand:extension.HelloWorld" ]
onDebug启动调试会话之前,将发出此激活事件并激活注册的扩展"activationEvents": [  "onDebug" ]
workspaceContains每当打开文件夹并且该文件夹至少包含一个与glob模式匹配的文件时,就会发出此激活事件并激活感兴趣的扩展名"activationEvents": [  "workspaceContains:**/.eslintrc.js" ]
onFileSystem每当读取特定方案中的文件或文件夹时,将发出此激活事件并激活注册的扩展名"activationEvents": [  "onFileSystem:sftp" ]
onView每当在 VS Code 侧边栏中展开指定 id 的视图(扩展或源代码控制是内置视图的示例)时,都会发出此激活事件并激活注册的扩展"activationEvents": ["onView:nodeDependencies" ]
onUri每当打开该扩展的系统范围的 Uri 时,就会发出此激活事件,并且会激活注册的扩展即访问系统范围内的文件时就会触发-
onWebviewPanel该激活事件发出后,有兴趣的扩展将被激活,只要VS代码需要恢复WebView与匹配viewType-
onCustomEditor该激活事件发出后,有兴趣的扩展将被激活,只要VS代码需要创建一个自定义编辑器与匹配viewType-
onAuthenticationRequest每当扩展请求身份验证会话(通过authentication.getSession()API)与匹配的providerId,扩展被激活-
onStartupFinished**在VS Code 启动后的一段时间内,会发出此激活事件并激活感兴趣的扩展。**这与*激活事件类似,但不会减慢 VS Code 的启动速度。目前,此事件在所有*激活的扩展完成激活后发出。"activationEvents": [  "onStartupFinished" ]
*如果配置了*,只要一启动vscode,插件就会被激活"activationEvents": ["*" ]

contributes

配置说明示例
commands为由标题和(可选)图标、类别和启用状态组成的命令提供 UI"commands": [{"command": "helloworld.helloWorld", "title": "自定义插件:hello world" }]
snippets配置代码片段支持"snippets": [{ "language": "javascript", "path": "./src/test.json" }]
viewsContainers.activitybar配置活动栏图标"viewsContainers": { "activitybar": [{ "id": "rabbit", "title": "小白兔", "icon": "./src/logo.svg" }] }
views配置活动栏对应的view视图"views": { "rabbit": [{ "id": "rabbit1", "name": "小白兔" }] }
keybindings配置快捷键"keybindings": [{ "command": "helloworld.helloWorld", "key": "ctrl+f10", "mac": "cmd+f10", "when": "editorTextFocus" }]
menus自定义编辑器菜单,包括右键菜单、头部菜单等"menus": { "editor/title": [{ "when": "editorFocus", "command": "helloworld.helloWorld", "group": "navigation" }], "editor/context": [{ "when": "editorFocus", "command": "helloworld.helloWorld", "group": "z_commands" }] }
configuration配置可以在 setting.json 文件中配置的字段"configuration": { "title": "helloworld", "properties": { "helloworld.attr1": { "type": "boolean", "default": false, "description": "Complete functions with their parameter signature." }, "helloworld.attr2": { "type": ["string", "null"], "default": null, "description": "Specifies the folder path containing the tsserver and lib*.d.ts files to use." } } } 插件中使用如下方式获取配置项vscode.workspace.getConfiguration('helloworld')

extension.ts文件解读

extension.js 是插件工程的入口文件,当插件被激活,即触发 package.json 中的 activationEvents 配置项时,extension.js 文件开始执行。
在 extension.js 中对需要的功能进行注册,主要使用 vscode.commands.register... 相关的api,来为 package.json 中的 contributes 配置项中的事件绑定方法或者监听器。
vscode.commands.register... 相关的api主要有:

  • vscode.languages.registerCompletionItemProvider() - 代码补全
  • vscode.commands.registerCommand() - 注册命令实现
  • vscode.languages.registerHoverProvider() - 代码悬浮提示
  • ......

WebView

vscode 提供了webview 支持,可以在 vscode 中塞进各种网页内容。

可以将 webview 视为iframe。webview 可以在此框架中呈现几乎所有 HTML 内容,并且可以使用消息传递与扩展进行通信。这种自由使 webviews 非常强大,并开辟了一个全新的扩展范围。

插件 vstoolkit 就是基于 webview 实现的。 iconfont 其实就是一个个网页。

创建 webview

const panel = vscode.window.createWebviewPanel(     
    'testWebview', // viewType     
    "WebView演示", // 视图标题     
    vscode.ViewColumn.One, // 显示在编辑器的哪个部位     
    {
        enableScripts: true, // 启用JS,默认禁用       
        retainContextWhenHidden: true, // webview被隐藏时保持状态,避免被重置 
     }
); 
panel.webview.html = `<html><body>你好,我是Webview</body></html>`;

访问本地文件

出于安全考虑,webview 默认无法直接访问本地资源,它在一个孤立的上下文中执行,想要加载本地的图片、js、css等必须通过 vscode 提供的 vscode-resource 协议。

 vscode-resource 协议类似 file 协议,但它只允许访问特定的本地文件。

image.png

如果想加载 html 文件中的内容,则需要使用 nodejs 的访问本地html文件,读取其内容,并将其中涉及到 script 、link、img 等标签的路径并且是从本地加载文件的地方转换为使用 vscode-resource 协议加载文件的方式 ,涉及到从网络下载的文件时注意必须使用 https 方式,否则会导致请求失败,以下是封装的方法:

/**  
* 从某个HTML文件读取能被Webview加载的HTML内容  
* @param {Object} context 上下文  
* @param {String} templatePath 相对于插件根目录的html文件相对路径  
*/ 
function getWebViewContent(context: vscode.ExtensionContext, templatePath: string):string {
  const resourcePath:string = path.join(context.extensionPath,templatePath);
  const dirPath:string = path.dirname(resourcePath);   
  let html:string = fs.readFileSync(resourcePath, "utf-8");   
  html = html.replace(     
    /(<link.+?href="|<script.+?src="|<img.+?src=")(.+?)"/g,     
    (m: any, $1: string, $2: string) => {       
      if ($2.indexOf("https") < 0) {         
        return ($1 + vscode.Uri.file(path.resolve(dirPath, $2)).with({ scheme: "vscode-resource" }).toString() + '"');       
      } else {return $1 + $2;}     
    });   
  return html; 
}  

消息通信

Webview和普通网页非常类似,不能直接调用任何VSCodeAPI,但是,它唯一特别之处就在于多了一个名叫 acquireVsCodeApi 的方法,执行这个方法会返回一个超级阉割版的 vscode 对象,这个对象里面有且仅有如下3个可以和插件通信:

  • postMessage - 发送消息
  • getState - 获取state,state的作用主要是在恢复webview是提供可恢复数据支持
  • setState - 设置state

1、插件向 webview 发送消息

panel.webview.postMessage({text: '你好!'});

2、webview端接收消息

window.addEventListener('message', event => {
    const message = event.data; 	
    console.log('Webview接收到的消息:', message); 
}

3、webview向插件发送消息

vscode.postMessage({text: '你好,我是Webview啊!'});

4、插件接收webview消息

panel.webview.onDidReceiveMessage(message => {
    console.log('插件收到的消息:', message); 
}, undefined, context.subscriptions);

插件打包

vsce

vsce是“Visual Studio Code Extensions”的缩写,是用于打包、发布和管理 VS Code 扩展的命令行工具。

npm install -g vsce vsce package # 打包插件 vsce publish # 发布插件,需要到vscode官网创建插件发布者,并把发布者放到package.json的publisher字段

如果您想在本地安装的 VS Code 上测试扩展或分发扩展而不将其发布到 VS Code Marketplace,您可以选择打包您的扩展。vsce可以将您的扩展程序打包成一个VSIX文件,用户可以从中轻松安装。

.vscodeignore

您可以创建一个.vscodeignore文件来排除某些文件包含在您的扩展程序包中。该文件是一组glob模式,每行一个。

例如:

**/*.ts **/tsconfig.json !file.ts 您应该忽略运行时不需要的所有文件。例如,如果你的扩展是用 TypeScript 编写的,你应该忽略所有**/*.ts文件,就像前面的例子一样。

注意: package.json 中列出的开发依赖项devDependencies将被自动忽略,您无需将它们添加到.vscodeignore文件中。