vscode插件开发

·  阅读 1109
vscode插件开发

vscode插件开发

​ vscode作为一款免费,轻量,可扩展性强的编辑器,在开发中越来越受欢迎。vscode是基于TS开发的,用的是Electron框架。那么作为前端开发,想要在vscode上开发一款扩展插件就很方便。

vscode插件可以做什么

  • 使用颜色或文件图标主题更改 VS Code 的外观 -主题

  • 在 UI 中添加自定义组件和视图 -扩展工作台

  • 创建一个 Webview 以显示使用 HTML/CSS/JS 构建的自定义网页

  • 支持一种新的编程语言

  • 支持调试特定的运行时

    ...

初步尝试扩展插件开发

安装Yeoman

首先,vscode官方给我们提供了 Yeoman 和 VS Code 扩展生成器

全局安装yo generator-code

npm install -g yo generator-code
复制代码

开始搭建插件项目

安装完成之后首先运行

yo code
复制代码

​ 会出现下图所示,这里是选择你要创建的插件类型(扩展插件,颜色主题,代码语言支持等等),以及可以根据个人开发习惯选择ts或js

vscode1

好的,接下来

? What type of extension do you want to create? New Extension (TypeScript)
	// 这里我们先选择第一项,New Extension (TypeScript),进入下一步
? What's the name of your extension? HelloWorld
	// 插件名称,简单取个 HelloWorld
? What's the identifier of your extension? hello-world
	// 插件标识符(唯一id),这个是你后面发布上去后插件的名称,一般和上一步名称一样,这里随便取一个,反正后面都可以改
? What's the description of your extension?
	// 插件描述,可以直接为空
? Initialize a git repository? Yes
	// 是否初始化git,选择yes
? Bundle the source code with webpack? No
	// 是否使用webpack构建你的项目,这里默认No
? Which package manager to use? npm
	// 这里是采用什么包管理器,这里有npm和yarn两个选项,笔者这里选择npm
复制代码

选择完毕后项目就搭建完毕了

vscode3

项目尝试

​ 用vscode打开生成的项目,打开项目后(有时候需要先运行 npm i 安装相关的依赖),然后按下 f5 键 或者 按下图点击红框处

vscode4

ps:如果出现没有反应的情况,可以在页面尝试按几次 ctrl + s 保存就会有反应了,亲测有效,我也不知道为什么,哈哈

正常情况下,这时候就会弹出另一个vscode实例,叫扩展开发宿主,我们可以在这个上面尝试我们开发的插件,

在扩展开发宿主上我们按下 shift+ctrl+p 弹出命令面板输入Hello World命令

vscode5

这时,底部就会出现 Hello World from HelloWorld! 的通知

vscode6

至此,我们初步创建的项目就成功了。

分析项目结构

vscode10

重点看 扩展入口文件 extension.ts 和 配置文件package.json

扩展入口文件 extension.ts

// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
	
	// Use the console to output diagnostic information (console.log) and errors (console.error)
	// This line of code will only be executed once when your extension is activated
	console.log('Congratulations, your extension "hello-world" is now active!');

	// The command has been defined in the package.json file
	// Now provide the implementation of the command with registerCommand
	// The commandId parameter must match the command field in package.json
	let disposable = vscode.commands.registerCommand('hello-world.helloWorld', () => {
		// The code you place here will be executed every time your command is executed
		// Display a message box to the user
		vscode.window.showInformationMessage('Hello World from HelloWorld!');
	});

	context.subscriptions.push(disposable);
}

// this method is called when your extension is deactivated
export function deactivate() {}
复制代码

文件中导出两个方法 activate 和 deactivate

  • activate : 当插件激活时,会触发activate函数
  • deactivate: 用于扩展停用时,相应的处理函数,一般是把一些在 activate 中注册的实例销毁,如果扩展需要在 VS Code 关闭或扩展被禁用或卸载时执行操作,这是执行此操作的方法。

配置文件package.json

{
	"name": "hello-world", // 插件名称
	"displayName": "HelloWorld", // 插件唯一id,也是插件市场的名称
	"description": "", // 描述
	"version": "0.0.1", // 版本号
	"engines": {
		"vscode": "^1.61.0" // 扩展所依赖的 VS Code 的最低版本。
	},
	"categories": [ // 分类,发布到市场后显示的类别
		"Other"
	],
	"activationEvents": [ // 激活事件
		"onCommand:hello-world.helloWorld"
	],
	"main": "./out/extension.js", // 扩展入口点
	"contributes": { // 贡献点
		"commands": [ // 命令,ctrl+shift+p激活的命令面板输入的命令
			{
				"command": "hello-world.helloWorld",
				"title": "Hello World"
			}
		]
	},
	"scripts": {
		"vscode:prepublish": "npm run compile",
		"compile": "tsc -p ./",
		"watch": "tsc -watch -p ./",
		"pretest": "npm run compile && npm run lint",
		"lint": "eslint src --ext ts",
		"test": "node ./out/test/runTest.js"
	},
	"devDependencies": {
		"@types/vscode": "^1.61.0",
		"@types/glob": "^7.1.3",
		"@types/mocha": "^8.2.2",
		"@types/node": "14.x",
		"eslint": "^7.27.0",
		"@typescript-eslint/eslint-plugin": "^4.26.0",
		"@typescript-eslint/parser": "^4.26.0",
		"glob": "^7.1.7",
		"mocha": "^8.4.0",
		"typescript": "^4.3.2",
		"vscode-test": "^1.5.2"
	}
}

复制代码

综合上面我们可以发现

整个“ 输入Hello World命令,到弹出 Hello World from HelloWorld! ” 的过程的产生是

package.json 里的 contributes 中 commands 数组每个个项中, title 即是命令面板中对应的命令标题,command则是对应的命令Id(即hello-world.helloWorld),命令id对应的触发事件是由extension.ts文件中vscode.commands.registerCommand注册相同的命令Id,并绑定对应事件(即弹出提示框)。

值得一提的是 package.json 里的 activationEvents (激活事件),是标识扩展程序激活的事件。上述代码中的激活事件是 onCommand:hello-world.helloWorld , 代表在调用 hello-world.helloWorld 这个命令时激活此扩展。

激活事件有

  • onLanguage:在打开对应语言文件时
  • onCommand:在执行对应命令时
  • onDebug:启动debug调试会话之前
    • onDebugInitialConfigurations : 在调用的provideDebugConfigurations方法之前触发
    • onDebugResolve:onDebugResolve:type在调用指定类型的resolveDebugConfiguration方法之前触发
  • workspaceContains:每当打开文件夹并且该文件夹包含至少一个与 glob 模式匹配的文件时
  • onFileSystem:打开指定的类型或协议的文件或文件夹时
  • onView:侧边栏中指定id的视图打开时
  • onUri:在基于 vscode 或 vscode-insiders 协议的 url 打开时
  • onWebviewPanel : 打开对应的Webview
  • onCustomEditor : 打开指定的自定义编辑器时
  • onAuthenticationRequest:每当扩展请求身份验证会话时
  • *:启动 vscode 时,如果不是必须一般不建议这么设置
  • onStartupFinished:此激活事件会VS Code 启动后的一段时间内发出,和*激活事件类似,但不会减慢 VS Code 的启动速度

整个流程大概如下图

footstep11.png

实战示例

这里以笔者之前开发的插件为例

笔者开发的插件名字叫 footstep-mark , 在插件市场搜索 footstep-mark ,即可安装

插件的主要功能是 通过快捷键可以在代码编辑处标记位置,右侧可以打开一个侧边栏显示已经标记的文本信息,点击对应的标记记录可以跳转相应的标记位置。

img

整个插件的功能大概如下图

footstepmark.png

实现这个插件功能,首先是了解怎么创建自定义的Webview(即上图中的侧边操作栏)

创建自定义的Webview

​ webview API 允许扩展在 Visual Studio Code 中创建完全可自定义的视图。在这个自定义视图中可以使用完全自定义的html、css、js。

Webview无法直接获取vscode的数据,他通过消息传递的方式与扩展进行通信。

创建Webview,通过api(vscode.window.createWebviewPanel)

const webView = window.createWebviewPanel(
      "footstepMark.extensionWebView", // 标识webview
      "操作栏", // 扩展页面的title
      { 
        preserveFocus: false,  // 是否聚焦到新建的页面
       	viewColumn: 1, // 页面显示的行号
      }
);
复制代码

webview设置html

首先简单介绍一下vscode中的Uri对象

Uri : 指通用资源标识符,可以表示磁盘上的文件或其他资源,可以理解为vscode可以加载的特殊资源路径

通过 webview.html 设置webview的Html内容

export async function activate(context: ExtensionContext) {
  	// 将上下文储存至store
  	state.context = context;
	const webView = window.createWebviewPanel(
      "footstepMark.extensionWebView", // 标识webview
      "操作栏", // 扩展页面的title
      { 
        preserveFocus: false,  // 是否聚焦到新建的页面
       	viewColumn: 1, // 页面显示的行号
      }
	);
    webView.webview.html = getHtmlForWebview(); // 设置html
    webView.webview.options = {
      enableScripts: true, // 允许使用脚本
      localResourceRoots: [
        Uri.joinPath(state.context.extensionUri, "media"),
      ], // 允许访问其他本地资源,并定义加载本地内容的根 URI
    };
}

function getHtmlForWebview() {
    const scriptPathOnDisk = Uri.joinPath(
      state.context.extensionUri,
      "media",
      "main.js"
    );
    const scriptUri = scriptPathOnDisk.with({ scheme: "vscode-resource" });

    const styleMainPath = Uri.joinPath(
      state.context.extensionUri,
      "media",
      "main.css"
    );
    const stylesMainUri = webview.asWebviewUri(styleMainPath);

    return `<!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link href="${stylesMainUri}" rel="stylesheet">
      </head>
      <body>
        <div class='mark-list'></div>
        <script src="${scriptUri}"></script>
      </body>
    </html>`;
  }
复制代码

webview与扩展通信

1、扩展传递数据给webview

​ 为了避免自定义的webview 对于vscode的影响,webview 并不能直接访问到vscode的信息,webview与扩展之间需要通过api 进行通信,类似webwork。

​ 扩展程序可以使用webview.postMessage()将数据传递给我们自定义的webview,例如在本插件中需要将 快捷键标记的代码编辑区的位置数据传递给webview,使webview的 HTML 增加一条标记信息

this.FMwebView.webview.postMessage(
     ... // 标记的数据
);
// this.FMwebView 是 上面 window.createWebviewPanel 创建的自定义webview
// postMessage括号中的内容是自定义的内容,可根据具体情况发送任意数据信息
复制代码

而在自定义的webview的 js 代码中 可以通过 window.addEventListener('message', event => { ... }) 来接受 扩展postMessage的数据

  window.addEventListener("message", (event) => {
    const data = event.data;
    // data就是标记的数据
  });
复制代码

2、webview传递数据给扩展

​ Webviews 还可以将消息传递回其扩展程序。例如在本插件中在点击操作类的webview的标记数据时,代码编辑要跳转到对应的标记位置

​ 具体数据传递,在自定义webview的 js 逻辑代码中 使用 特殊 VS Code API 对象,即

const vscode = acquireVsCodeApi();  // acquireVsCodeApi函数可以直接调用

vscode.postMessage(
	... // 需跳转的位置数据
);
复制代码

​ 而vscode扩展则通过 webview.onDidReceiveMessage来接受数据

this.FMwebView.webview.onDidReceiveMessage((message) => {
    ... // message 为上述传递过来的位置数据
});
复制代码

接下来,看下怎么注册快捷键

注册快捷键

这个插件的快捷键功能为:

  • Ctrl+Alt+M :此快捷键会在当前文档聚焦处(即光标位置)增加高亮背景色标记,并将该处位置,文本信息记录,如果侧边操作栏处于打开状态,会在侧边操作栏渲染; 如果聚焦处已被标记,则会取消该处标记,并删除所有相关信息

  • Ctrl+Alt+N:此快捷键会在当前文档聚焦处(即光标位置),如果该位置已被 Ctrl+Alt+M 标记,则会弹出输入框,输入备注信息后回车,会在该位置增加备注信息,并将该信息显示于该位置后面,如果侧边操作栏处于打开状态,会在侧边操作栏同步渲染

首先,在package.json文件中的contributes(贡献点)写入对应的快捷键以及绑定的命令ID,when字段代表生效的时间(这里是编辑器聚焦时)

  "contributes": {
    "keybindings": [
      {
        "command": "footstepMark.locationMark",
        "key": "ctrl+alt+m",
        "mac": "cmd+alt+m",
        "when": "editorTextFocus"
      },
      {
        "command": "footstepMark.markRecord",
        "key": "ctrl+alt+n",
        "mac": "cmd+alt+n",
        "when": "editorTextFocus"
      }
    ]
  },
复制代码

接着,插件代码中注册绑定对应的命令ID事件,

    this.LocationMarkDisposable = commands.registerCommand(
      'footstepMark.locationMark',
      () => {
        this.locationMark();
      }
    );
复制代码
    this.MarkRecordDisposable = commands.registerCommand(
      'footstepMark.markRecord',
      async () => {
        this.markRecord();
      }
    );
复制代码

下面看下快捷键对应的事件详细操作

简单介绍vscode中的几个对象

  • Position : 表示行和字符的位置,例如光标的位置
  • Range :范围表示两个有序的位置对,确保结束的位置大于等于开始的位置

Ctrl+Alt+M

Ctrl+Alt+M (命令ID:footstepMark.locationMark) 事件对应代码

 import {
  commands,
  window,
  Range,
  Position,
  OverviewRulerLane,
  Disposable,
  Uri,
  ExtensionContext
} from "vscode";

 async locationMark() {
    // 获取当前激活的文档
    const activeEditor = window.activeTextEditor;
    try {
      // 获取鼠标聚焦处位置信息
      let startLine = activeEditor?.selection.start.line;
      let endLine = activeEditor?.selection.end.line;
      // 获取当前激活的文件名,用于后面点击标记位置跳转文件
      let fileName = activeEditor?.document.fileName;
      if (startLine !== undefined && endLine !== undefined && fileName) {
          // 这里是计算该位置是否已经标记过了,如果标记过了则返回
        if (this.calculateRange(startLine, endLine, fileName)) {
          return;
        }
        // 获取最后的文本宽度
        let endPosition = activeEditor?.document.lineAt(endLine).text.length;
        if (endPosition) {
          // 计算范围
          let range = new Range(
            new Position(startLine, 0),
            new Position(endLine, endPosition)
          );
            
          const imgUri = Uri.joinPath(
            (state.context as ExtensionContext).extensionUri,
            "media",
            "icon.svg"
          );
          let textEditorDecorationType = window.createTextEditorDecorationType({
            overviewRulerColor: "rgba(208,2,27,1)",
            backgroundColor: "rgba(208,2,27,0.1)",
            // 右侧光标
            isWholeLine: true,
            overviewRulerLane: OverviewRulerLane.Full,
            gutterIconPath: imgUri,
            gutterIconSize: 'contain'
          });
          // 代码编辑器样式渲染
          activeEditor?.setDecorations(textEditorDecorationType, [{ range }]);
        }
      }
    } catch (error: any) {
      const message = error.message ?? error;
      window.showErrorMessage(message);
    }
  }
复制代码

通过获取聚焦点处的位置信息,并使用window.createTextEditorDecorationTypeapi 创建可用于向文本编辑器添加装饰的实例,

再使用 setDecorations ,将装饰实例渲染到编辑器对应的标记位置上,这里是将标记的背景高亮显示。

而且 Ctrl+Alt+M 同时会将位置数据传递给 webview (即上面创建的自定义Webview),具体代码不做演示了

Ctrl+Alt+N

功能:将 Ctrl+Alt+M 标记的位置数据加上自定义的备注信息

Ctrl+Alt+N (命令ID:footstepMark.markRecord) 事件对应代码

  async markRecord() {
    // 当前激活的文档
    const activeEditor = window.activeTextEditor;
    try {
      let startLine = activeEditor?.selection.start.line;
      let endLine = activeEditor?.selection.end.line;
      let fileName = activeEditor?.document.fileName;
      if (startLine && endLine && fileName) {
        let markData = state.markData[fileName].markDetails; // 这里是在所有标记的位置信息中找出对应的标记数据
        if (markData) {
          let target = markData.find((e) => {
            let start = e.range[0];
            let end = e.range[1];
            return startLine && endLine && start <= startLine && end >= endLine;
          }); // 找到已经标记的对应数据信息
          if (target && target.textEditorDecorationType) {
            let record = await window.showInputBox({
              placeHolder: '请输入标记备注'
            });
            target.record = record;
            let range = new Range(
              new Position(target.range[0], 0),
              new Position(target.range[1], target.range[2])
            );
            activeEditor?.setDecorations(target.textEditorDecorationType, [
              {
                range,
                renderOptions: {
                  after: {
                    contentText: record,
                    color: "rgba(208,2,27,0.4)",
                    margin: "0 0 0 20px",
                  },
                },
              },
            ]);
            // 和webview脚本信息交流, sendMessage是封装的 postMessage 信息传递方法
            FmWebViewPanel.currentPanel?.sendMessage(
                ... // record,
            );
          }
        }
      }
    } catch (error: any) {
      const message = error.message ?? error;
      window.showErrorMessage(message);
    }
  }
复制代码

上面代码通过 api window.showInputBox 来打开一个输入框要求用户输入备注信息,再将输入的信息加到位置数据中以及postMessage到webview中显示

扩展工作台

“工作台” 整个 Visual Studio Code UI ,包括以下部分

  • 标题栏
  • 活动栏
  • 侧边栏
  • 控制板
  • 编辑组
  • 状态栏

VS Code 提供了各种 API,允许您将自己的组件添加到工作台。

在这个插件中我们要添加的工作区是 Status Bar Item 区域,添加两个按钮

  • on/off : 自定义webview打开或关闭按钮

  • clear : 清空数据按钮

createStatusBarItem

通过 window.createStatusBarItem 来添加对应的的按钮到左下方底部位置,

on/off 按钮相关代码

import { window, StatusBarItem, StatusBarAlignment, commands  } from "vscode";
let statusBar = window.createStatusBarItem(StatusBarAlignment.Left); 
// StatusBarAlignment是vscode内置的对象,表示状态栏项目的对齐方式,有left和right两个值。
statusBar.command = 'extension.statusBarShowWebView'; // 绑定命令ID
statusBar.text = `$(repo-clone)off`;
复制代码

然后通过注册对应的命令 ID 来注册按钮的对应事件

commands.registerCommand(
      'extension.statusBarShowWebView',
      async () => {
        // 扩展webview打开或关闭
      }
    );
复制代码

clear 按钮也是同理,这里不多加赘述。

整个笔者的 footstep-mark 插件大致实现逻辑如上,当然还有很多复杂的细节,包括跨文件的跳转以及标记位置后在编辑对应的文件编辑时位置的错乱问题处理等等,具体相应的代码

github :github.com/chenkai77/f…

打包与发布

如何将写好的插件发布到vscode市场呢

首先要有一个Microsoft账号

marketplace.visualstudio.com/ 中注册并登录,然后如下图点击

image-20211121181732363.png

然后创建新组织

image-20211121182023330.png

点击同意条款继续

image-20211121182129072.png

输入组织名

image-20211121182254609.png

组织创建完成之后,如下图点击

image-20211121182826632.png

创建 token

image-20211121182912936.png

然后,按下图选择,scopes范围选择 Full access 比较方便

image-20211121183301096.png

点击创建成功后复制token,token网站不会帮你保存,所以最好自己保存下来,免得遗忘。

vsce

安装官方工具 vsce (Visual Studio Code Extensions)

npm install -g vsce
复制代码

通过 create-publisher 创建发布者名字 ,已经创建过的可以使用 login

vsce create-publisher <publisher name>(发布者名)
// 或者
vsce login <publisher name>
复制代码

然后终端会让你输入对应的token,将上面的 token 复制输入即可

最后在对应的插件项目文件夹下运行

  • 打包

    vsce package
    复制代码
  • 发布

    vsce publish
    复制代码

注意

  • 需要在package.json中输入对应的 publisher (发布者名字)以及version(版本号)
  • README.md文件的内容会显示在市场的插件介绍详情区域
  • CHANGELOG.md文件对应的是插件的version history ,历史记录中

参考文档:vscode官方地址 code.visualstudio.com/api

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改