vscode插件开发总结

一、关于vscode插件

相信大家对vscode应该都不陌生,VSCode是微软出的一款轻量级代码编辑器,免费而且功能强大,以功能强大、提示友好、不错的性能和颜值俘获了大量开发者的青睐,对JavaScript和NodeJS的支持非常好,自带很多功能,例如代码格式化,代码智能提示补全、颜色高亮插件等。

再强大的IDE那也不可能面面俱到什么功能都塞进去,那样只会导致IDE本身太臃肿。功能嘛,按需索取,所以,vscode的很多强大功能都是基于插件实现的,IDE只提供一个最基本的框子和最基本功能,由插件来丰富和扩展它的功能。

vscode插件可以很轻松的在应用商店搜索并下载到,应用商店官网是:https://marketplace.visualstudio.com/vscode ,vscode推出时间本身并不长,但生态发展得非常好,应用商店已经有各种各样丰富的插件供大家使用了。

二、HelloWorld

官方脚手架
安装脚手架:

npm install -g yo generator-code

然后cd到你的工作目录,运行yo code

vscode插件开发总结_第1张图片
项目结构其实很简单,主要是清单文件package.json以及extension.js这个插件入口文件:
vscode插件开发总结_第2张图片
package.json部分关键内容如下(已省略其它)

{
	// 扩展的激活事件
	"activationEvents": [
		"onCommand:extension.sayHello"
	],
	// 入口文件
	"main": "./src/extension",
	// 贡献点,vscode插件大部分功能配置都在这里
	"contributes": {
		"commands": [
			{
				"command": "extension.sayHello",
				"title": "Hello World"
			}
		]
	}
}

src/extension.js内容如下:

const vscode = require('vscode');

/**
 * 插件被激活时触发,所有代码总入口
 * @param {*} context 插件上下文
 */
exports.activate = function(context) {
	console.log('恭喜,您的扩展“vscode-plugin-demo”已被激活!');
	// 注册命令
	context.subscriptions.push(vscode.commands.registerCommand('extension.sayHello', function () {
		vscode.window.showInformationMessage('Hello World!');
	}));
};

/**
 * 插件被释放时触发
 */
exports.deactivate = function() {
	console.log('您的扩展“vscode-plugin-demo”已被释放!')
};

解读

  • main定义了整个插件的主入口;
  • 我们在contributes.commands里面注册了一个名为extension.sayHello的命令,并在src/extension.js中去实现了它(弹出一个Hello World的提示);
  • 但是仅仅这样还不够,命令虽然定义了,但是vscode还不知道啥时候去执行它,还需要在activationEvents添加上onCommand:extension.sayHello用来告诉vscode,当用户执行了这个命令操作时去执行前面我们定义的内容;
  • 除了onCommand之外,还有onViewonUrionLanguage等等

运行调试

按下F5就会弹出一个新的vscode窗口:

这个新窗口已经加载了我们的插件,窗口标题会注明扩展开发主机,对于只有单显示器的同学来说,很容易写着写着就忘了哪个是主窗口,哪个是新窗口,所以可以通过这个来区分。

vscode插件开发总结_第3张图片
为了描述方便,我们约定,后续把新弹出来的那个窗口叫新窗口,之前老的那个叫旧窗口

然后按下Ctrl+Shift+P,输入HelloWorld执行对应命令,当你发现右下角弹出了HelloWorld的提示时,恭喜你,你已经掌握了插件开发基本技巧。

三、package.json

package.json

{
	// 插件的名字,应全部小写,不能有空格
	"name": "vscode-plugin-demo",
	// 插件的友好显示名称,用于显示在应用市场,支持中文
	"displayName": "VSCode插件demo",
	// 描述
	"description": "VSCode插件demo集锦",
	// 关键字,用于应用市场搜索
	"keywords": ["vscode", "plugin", "demo"],
	// 版本号
	"version": "1.0.0",
	// 发布者,如果要发布到应用市场的话,这个名字必须与发布者一致
	"publisher": "sxei",
	// 表示插件最低支持的vscode版本
	"engines": {
		"vscode": "^1.27.0"
	},
	// 插件应用市场分类,可选值: [Programming Languages, Snippets, Linters, Themes, Debuggers, Formatters, Keymaps, SCM Providers, Other, Extension Packs, Language Packs]
	"categories": [
		"Other"
	],
	// 插件图标,至少128x128像素
	"icon": "images/icon.png",
	// 扩展的激活事件数组,可以被哪些事件激活扩展,后文有详细介绍
	"activationEvents": [
		"onCommand:extension.sayHello"
	],
	// 插件的主入口
	"main": "./src/extension",
	// 贡献点,整个插件最重要最多的配置项
	"contributes": {
		// 插件配置项
		"configuration": {
			"type": "object",
			// 配置项标题,会显示在vscode的设置页
			"title": "vscode-plugin-demo",
			"properties": {
				// 这里我随便写了2个设置,配置你的昵称
				"vscodePluginDemo.yourName": {
					"type": "string",
					"default": "guest",
					"description": "你的名字"
				},
				// 是否在启动时显示提示
				"vscodePluginDemo.showTip": {
					"type": "boolean",
					"default": true,
					"description": "是否在每次启动时显示欢迎提示!"
				}
			}
		},
		// 命令
		"commands": [
			{
				"command": "extension.sayHello",
				"title": "Hello World"
			}
		],
		// 快捷键绑定
		"keybindings": [
			{
				"command": "extension.sayHello",
				"key": "ctrl+f10",
				"mac": "cmd+f10",
				"when": "editorTextFocus"
			}
		],
		// 菜单
		"menus": {
			// 编辑器右键菜单
			"editor/context": [
				{
					// 表示只有编辑器具有焦点时才会在菜单中出现
					"when": "editorFocus",
					"command": "extension.sayHello",
					// navigation是一个永远置顶的分组,后面的@6是人工进行组内排序
					"group": "navigation@6"
				},
				{
					"when": "editorFocus",
					"command": "extension.demo.getCurrentFilePath",
					"group": "navigation@5"
				},
				{
					// 只有编辑器具有焦点,并且打开的是JS文件才会出现
					"when": "editorFocus && resourceLangId == javascript",
					"command": "extension.demo.testMenuShow",
					"group": "z_commands"
				},
				{
					"command": "extension.demo.openWebview",
					"group": "navigation"
				}
			],
			// 编辑器右上角图标,不配置图片就显示文字
			"editor/title": [
				{
					"when": "editorFocus && resourceLangId == javascript",
					"command": "extension.demo.testMenuShow",
					"group": "navigation"
				}
			],
			// 编辑器标题右键菜单
			"editor/title/context": [
				{
					"when": "resourceLangId == javascript",
					"command": "extension.demo.testMenuShow",
					"group": "navigation"
				}
			],
			// 资源管理器右键菜单
			"explorer/context": [
				{
					"command": "extension.demo.getCurrentFilePath",
					"group": "navigation"
				},
				{
					"command": "extension.demo.openWebview",
					"group": "navigation"
				}
			]
		},
		// 代码片段
		"snippets": [
			{
				"language": "javascript",
				"path": "./snippets/javascript.json"
			},
			{
				"language": "html",
				"path": "./snippets/html.json"
			}
		],
		// 自定义新的activitybar图标,也就是左侧侧边栏大的图标
		"viewsContainers": {
			"activitybar": [
				{
					"id": "beautifulGirl",
					"title": "美女",
					"icon": "images/beautifulGirl.svg"
				}
			]
		},
		// 自定义侧边栏内view的实现
		"views": {
			// 和 viewsContainers 的id对应
			"beautifulGirl": [
				{
					"id": "beautifulGirl1",
					"name": "国内美女"
				},
				{
					"id": "beautifulGirl2",
					"name": "国外美女"
				},
				{
					"id": "beautifulGirl3",
					"name": "人妖"
				}
			]
		},
		// 图标主题
		"iconThemes": [
			{
				"id": "testIconTheme",
				"label": "测试图标主题",
				"path": "./theme/icon-theme.json"
			}
		]
	},
	// 同 npm scripts
	"scripts": {
		"postinstall": "node ./node_modules/vscode/bin/install",
		"test": "node ./node_modules/vscode/bin/test"
	},
	// 开发依赖
	"devDependencies": {
		"typescript": "^2.6.1",
		"vscode": "^1.1.6",
		"eslint": "^4.11.0",
		"@types/node": "^7.0.43",
		"@types/mocha": "^2.2.42"
	},
	// 后面这几个应该不用介绍了
	"license": "SEE LICENSE IN LICENSE.txt",
	"bugs": {
		"url": "https://github.com/sxei/vscode-plugin-demo/issues"
	},
	"repository": {
		"type": "git",
		"url": "https://github.com/sxei/vscode-plugin-demo"
	},
	// 主页
	"homepage": "https://github.com/sxei/vscode-plugin-demo/blob/master/README.md"
}

activationEvents

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

  • onLanguage:${language}
  • onCommand:${command}
  • onDebug
  • workspaceContains:${toplevelfilename} //每当打开一个文件夹并且该文件夹包含至少一个匹配的文件时,就会发出此激活事件并且感兴趣的扩展将被激活。
  • onFileSystem:${scheme} // 读取来自特定方案的文件或文件夹时,都会发出此激活事件
  • onView:${viewId} //只要在 VS Code 侧边栏中展开指定 id 的视图,就会发出此激活事件
  • onUri // 每当打开该扩展的系统范围 Uri 时,就会发出此激活事件
  • *

都比较好懂,我就不做一一介绍了,举个例子,如果我配置了`onLanguage:javascript``,那么只要我打开了JS类型的文件,插件就会被激活。

重点说一下*,如果配置了*,只要一启动vscode,插件就会被激活,为了出色的用户体验,官方不推荐这么做。看到这里相信大家知道了我们前面HelloWord里面为啥要配置onCommand了吧。

contributes

  • configuration:设置
  • commands:命令
  • menus:菜单
  • keybindings:快捷键绑定
  • languages:新语言支持
  • debuggers:调试
  • breakpoints:断点
  • grammars 语法
  • themes:主题
  • snippets:代码片段
  • jsonValidation:自定义JSON校验
  • views:左侧侧边栏视图
  • viewsContainers:自定义activitybar
  • problemMatchers 匹配器
  • problemPatterns // 文档中只写了同上
  • taskDefinitions 任务
  • colors 主题颜色

参考

https://code.visualstudio.com/api/references/extension-manifest

https://code.visualstudio.com/api/references/activation-events

https://code.visualstudio.com/api/references/contribution-points

四、命令、菜单、快捷键

命令

我们在前面HelloWord章节中已经提到了命令写法,这里再重温一下。

context.subscriptions.push(vscode.commands.registerCommand('extension.sayHello', () => {
	vscode.window.showInformationMessage('您执行了extension.sayHello命令!');
}));

然后在清单文件声明:

"commands": [
	{
		"command": "extension.sayHello",
		"title": "Hello World"
	},
]

vscode.commands.registerCommand是注册命令的API,执行后会返回一个Disposable对象,所有注册类的API执行后都需要将返回结果放到context.subscriptions中去。

回调函数参数

回调函数接收一个可选参数uri

  • 当从资源管理器中右键执行命令时会把当前选中资源路径uri作为参数传过;
  • 当从编辑器中右键菜单执行时则会将当前打开文件路径URI传过去;
  • 当直接按Ctrl+Shift+P执行命令时,这个参数为空;

示例:

context.subscriptions.push(vscode.commands.registerCommand('extension.demo.getCurrentFilePath', (uri) => {
	vscode.window.showInformationMessage(`当前文件(夹)路径是:${uri ? uri.path : '空'}`);
}));

package.json如下:

	"menus": {
			"editor/context": [
				{
					"when": "editorFocus",
					"command": "extension.demo.getCurrentFilePath",
					"group": "navigation"
				}
			],
			"explorer/context": [
				{
					"command": "extension.demo.getCurrentFilePath",
					"group": "navigation"
				}
			]
		}
	}

最终效果:

vscode插件开发总结_第4张图片

编辑器命令

除了上面的注册普通命令之外,还有一个vscode.commands.registerTextEditorCommand命令,文本编辑器命令与普通命令不同,它们仅在有被编辑器被激活时调用才生效,此外,这个命令可以访问到当前活动编辑器textEditor

// 编辑器命令
context.subscriptions.push(vscode.commands.registerTextEditorCommand('extension.testEditorCommand', (textEditor, edit) => {
	console.log('您正在执行编辑器命令!');
	console.log(textEditor, edit);
}));

执行命令

这里先说一下vscode api的一个习惯设计,很多命令都是返回一个类似于Promise的Thenable对象,如果发现api里面返回的是这个对象,说明这个方法不是直接返回结果的。

使用代码执行某个命令:

vscode.commands.executeCommand('命令', 'params1', 'params2', ...).then(result => {
	console.log('命令结果', result);
});

获取所有命令

前面说到了执行命令,那我怎么知道某些操作它的命令是什么呢?

有2种方法,第一种通过代码,getCommands接收一个参数表示是否过滤内部命令,默认否:

// 获取所有命令
vscode.commands.getCommands().then(allCommands => {
	console.log('所有命令:', allCommands);
});

一般有上千个命令:
vscode插件开发总结_第5张图片
另外一种方法是直接打开快捷键设置,这里就能看到所有命令列表,右键可以复制命令:

vscode插件开发总结_第6张图片

复杂命令

vscode内部有一些复杂命令,所谓复杂命令,就是指一些需要特殊参数并且通常有返回值、执行一些诸如跳转到定义、执行代码高亮等特殊操作、这类命令有几十个,作为插件开发者,很多时候你可能正需要这类命令,复杂命令列表参阅:
https://code.visualstudio.com/docs/extensionAPI/vscode-api-commands

以下是演示如何在VS代码中打开新文件夹的示例:

let uri = Uri.file('/some/path/to/folder');
commands.executeCommand('vscode.openFolder', uri).then(sucess => {
	console.log(success);
});

菜单

一个菜单项的完整配置如下:

"contributes": {
	"menus": {
		"editor/title": [{
			"when": "resourceLangId == markdown",
			"command": "markdown.showPreview",
			"alt": "markdown.showPreviewToSide",
			"group": "navigation"
		}]
	}
}

  • editor/title是key值,定义这个菜单出现在哪里;
  • when控制菜单合适出现;
  • command定义菜单被点击后要执行什么操作;
  • alt定义备用命令,按住alt键打开菜单时将执行对应命令;
  • group定义菜单分组;
出现的位置

目前插件可以给以下场景配置自定义菜单:

  • 资源管理器上下文菜单 - explorer/context
  • 编辑器上下文菜单 - editor/context
  • 编辑标题菜单栏 - editor/title
  • 编辑器标题上下文菜单 - editor/title/context
  • 调试callstack视图上下文菜单 - debug/callstack/context
  • SCM标题菜单 -scm/title
  • SCM资源组菜单 -scm/resourceGroup/context
  • SCM资源菜单 -scm/resource/context
  • SCM更改标题菜单 -scm/change/title
  • 左侧视图标题菜单 -view/title
  • 视图项菜单 -view/item/context
  • 控制命令是否显示在命令选项板中 - commandPalette

其中,最常见的应该就explorer/contexteditor/context了,这2个应该不用多做介绍。

editor/title
vscode插件开发总结_第7张图片
图标在commands里面配置,light和dark分别对应浅色和深色主题,如果不配置图标则直接显示文字:

"commands": [
	{
		"command": "extension.demo.testMenuShow",
		"title": "这个菜单仅在JS文件中出现",
		"icon": {
			"light": "./images/tool-light.svg",
			"dark": "./images/tool-light.svg"
		}
	}
]

editor/title/context:
vscode插件开发总结_第8张图片

when

通过可选的when语句,VS Code可以很好地控制什么时候显示菜单项,当然,when语句语法不仅仅适用于菜单项的控制。

when语句语法有很多,这里列举几个常用的:

  • resourceLangId == javascript:当编辑的文件是js文件时;
  • resourceFilename == test.js:当当前打开文件名是test.js时;
  • isLinux、isMac、isWindows:判断当前操作系统;
  • editorFocus:编辑器具有焦点时;
  • editorHasSelection:编辑器中有文本被选中时;
  • view == someViewId:当当前视图ID等于someViewId时;
  • 等等等

多个条件可以通过与或非进行组合,例如:editorFocus && isWindows && resourceLangId == javascript

有关when语句的更多完整语法请参考官方文档:

https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts

alt

alt很好理解,表示没有按下alt键时,点击右键菜单执行的是command对应的命令,而按下了alt键后执行的是alt对应的命令。

右键group

组间排序

控制菜单的分组和排序。不同的菜单拥有不同的默认分组。

editor/context中有这些默认组:

  • navigation- 放在这个组的永远排在最前面;
  • 1_modification - 更改组;
  • 9_cutcopypaste - 编辑组
  • z_commands - 最后一个默认组,其中包含用于打开命令选项板的条目。
    vscode插件开发总结_第9张图片
    除了navigation是强制放在最前面之外,其它分组都是按照0-9、a-z的顺序排列的,所以如果你想在1_modification9_cutcopypaste插入一个新的组别的话,你可以定义一个诸如6_test

vscode插件开发总结_第10张图片
explorer/context有这些默认组:

  • navigation - 放在这个组的永远排在最前面;
  • 2_workspace - 与工作空间操作相关的命令。
  • 3_compare - 与差异编辑器中的文件比较相关的命令。
  • 4_search - 与在搜索视图中搜索相关的命令。
  • 5_cutcopypaste - 与剪切,复制和粘贴文件相关的命令。
  • 7_modification - 与修改文件相关的命令。

编辑器选项卡上下文菜单有这些默认组:

  • 1_close - 与关闭编辑器相关的命令。
  • 3_preview - 与固定编辑器相关的命令。

editor/title有这些默认组:

  • 1_diff - 与使用差异编辑器相关的命令。
  • 3_open - 与打开编辑器相关的命令。
  • 5_close - 与关闭编辑器相关的命令。
组内排序

默认同一个组的顺序取决于菜单名称,如果想自定义排序的话可以再组后面通过@的方式来自定义顺序,例如:

"editor/context": [
	{
		"when": "editorFocus",
		"command": "extension.sayHello",
		// 强制放在navigation组的第2个
		"group": "navigation@2"
	},
	{
		"when": "editorFocus",
		"command": "extension.demo.getCurrentFilePath",
		// 强制放在navigation组的第1个
		"group": "navigation@1"
	}
]

如上,默认情况下,按照菜单名排序,sayHellogetCurrentFilePath的前面,但是通过自定义顺序,把后者放到了前面。

快捷键

快捷键设置的写法比较简单,如下所示:

"contributes": {
	"keybindings": [{
		// 指定快捷键执行的操作
		"command": "extension.sayHello",
		// windows下快捷键
		"key": "ctrl+f10",
		// mac下快捷键
		"mac": "cmd+f10",
		// 快捷键何时生效
		"when": "editorTextFocus"
	}]
}

这个快捷键最终会出现在整个vscode快捷键设置界面:
vscode插件开发总结_第11张图片
如果您想了解更多有关快捷键绑定的详细细节可以继续阅读官方文档:
https://code.visualstudio.com/docs/getstarted/keybindings

五、WebView

大家都知道,整个VSCode编辑器就是一张大的网页,其实,我们还可以在Visual Studio Code中创建完全自定义的、可以间接和nodejs通信的特殊网页(通过一个acquireVsCodeApi特殊方法),这个网页就叫WebView。内置的Markdown的预览就是使用WebView实现的。使用Webview可以构建复杂的、支持本地文件操作的用户界面。

VSCode插件的WebView类似于iframe的实现,但并不是真正的iframe(我猜底层应该还是基于iframe实现的,只不过上层包装了一层),通过开发者工具可以看到.

vscode插件开发总结_第12张图片

什么时候适合使用WebView

虽然Webview令人很振奋,因为基于它我们可以随意发挥不受限制,但必须注意还是要慎用,毕竟VSCode是很注重性能的,不能因为你一个插件拖累了整个IDE,一般仅在原有API和功能以及交互方式无法满足你时才需要考虑,另外,设计糟糕的Webview也很容易在VS Code中让人感觉不舒适,不能让人家一看就觉得你这是一张网页,好看的UI也很重要。

这是官网给出的建议,在使用webview之前请考虑以下事项:

  • 这个功能真的需要放在VSCode中吗?作为单独的应用程序或网站会不会更好呢?
  • webview是实现这个功能的唯一方法吗?可以使用常规VS Code API吗?
  • 您的webview是否会带来足够的用户价值以证明其高资源成本?

创建WebView

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

几点说明:

  • 默认情况下,在Web视图中禁用JavaScript,但可以通过传入enableScripts: true选项轻松启用;
  • 默认情况下当webview被隐藏时资源会被销毁,通过retainContextWhenHidden: true会一直保存,但会占用较大内存开销,仅在需要时开启;

加载本地资源

出于安全考虑,Webview默认无法直接访问本地资源,它在一个孤立的上下文中运行,想要加载本地图片、js、css等必须通过特殊的vscode-resource:协议,网页里面所有的静态资源都要转换成这种格式,否则无法被正常加载。

vscode-resource:协议类似于file:协议,但它只允许访问特定的本地文件。和file:一样,vscode-resource:从磁盘加载绝对路径的资源。

我简单封装了一个转换方法:

import * as vscode from 'vscode';

export class Util {
    public static buildPath(data: string, webview: vscode.Webview, contextPath: string): string {
        return data.replace(/((src|href)=("|')?)(\/\/)/gi, "$1http://")
            .replace(/((src|href)=("|'))((?!(http))[^"']+?\.(css|js|properties|json|png|jpg))\b/gi, "$1" + webview.asWebviewUri(vscode.Uri.file(`${contextPath}`)) + "/$4");
    }
}

转换之后 -》例:https://file+.vscode-resource.vscode-webview.net/Users/liuchongyang/site/vscode/extensions/a-text-verify/media/image/u187.png

从文件加载HTML内容

import { readFileSync } from 'fs';
import { Util } from './common/util';
...
workbenchPanel.webview.html = Util.buildPath(
	readFileSync(`${this._extensionUri.path}/media/workbench.html`, 'utf8')
		.replace("{{rootPath}}", rootPath)
		.replace("{{baseUrl}}", workbenchPanel.webview.asWebviewUri(this._extensionUri).toString()),
	workbenchPanel.webview, contextPath);

消息通信

重头戏来了,Webview和普通网页非常类似,不能直接调用任何VSCodeAPI,但是,它唯一特别之处就在于多了一个名叫acquireVsCodeApi的方法,执行这个方法会返回一个超级阉割版的vscode对象,这个对象里面有且仅有如下3个可以和插件通信的API:
vscode插件开发总结_第13张图片
插件和Webview之间如何互相通信呢?
插件给Webview发送消息(支持发送任意可以被JSON化的数据)

panel.webview.postMessage({text: '你好,我是小茗同学!'});

Webview端接收:

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

Webview主动发送消息给插件:

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

插件接收:

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

state

在webview的js中我们可以使用vscode.getState()vscode.setState()方法来保存和恢复JSON可序列化状态对象。当webview被隐藏时,即使webview内容本身被破坏,这些状态仍然会保存。当然了,当webview被销毁时,状态将被销毁。

序列化

通过注册WebviewPanelSerializer可以实现在VScode重启后自动恢复你的webview,当然,序列化其实也是建立在getStatesetState之上的。

注册方法:vscode.window.registerWebviewPanelSerializer

retainContextWhenHidden

对于具有非常复杂的UI或状态且无法快速保存和恢复的webview,我们可以直接使用retainContextWhenHidden选项。设置retainContextWhenHidden: true后即使webview被隐藏到后台其状态也不会丢失。

尽管retainContextWhenHidden很有吸引力,但它需要很高的内存开销,一般建议在实在没办法的时候才启用。
getStatesetState是持久化的首选方式,因为它们的性能开销要比retainContextWhenHidden低得多。

六、实现案例

关键字高亮实现

extension.ts

import * as vscode from 'vscode'
import { decorate } from './decorator';

export function activate(context: vscode.ExtensionContext) {

    vscode.workspace.onDidSaveTextDocument(e => {
        decorate()
    })

    vscode.workspace.onDidChangeTextDocument(e => {
        decorate()
    })

    vscode.workspace.onDidOpenTextDocument(e => {
        decorate()
    })
    vscode.window.onDidChangeVisibleTextEditors(e => {
        decorate();
    })
}

export function deactivate() { }

decorator.ts

import * as vscode from "vscode"
import { rainborColors } from "./rainbow";
import { clearLine } from "readline";

let colors = Array.from(rainborColors)
let rainbows = colors.map(x => vscode.window.createTextEditorDecorationType({ color: x }))

function getRandomInt(min: number, max: number): number {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

function replaceComments(buffer: string) {
    var comment = /\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm
    return buffer.replace(comment, (x) => {
        var len = x.length
        var r = " ".repeat(len)
        return r
    })
}

function notContains<T>(datas: Array<T>, value: T) {
    return datas.indexOf(value) == -1;
}

export function decorate() {
    let editor = vscode.window.activeTextEditor;
    let allText = editor.document.getText()
    let noCommentText = replaceComments(allText)

    // let regex = /(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)')|`([^`\\]*(?:\\.[^`\\]*)*)`)/g;
    // let regex = /(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)')/g;
    // let regex = /\"(.*)\"/g;
    let regex = /(北京)|(沧海)|(夕)|(兮)|(十年生死)|(茫茫)|(不思量)|(自难忘)/g;

    let decorators = colors.map(color => [])
    let match: RegExpMatchArray;
    let offset: number = getRandomInt(0, colors.length);

    // console.log(888, regex.exec(noCommentText))
    while ((match = regex.exec(noCommentText))) {
        // console.log('match[1]', match); //['"好啊后"', '好啊后', index: 8, input: '好玩 111  "好啊后"', groups: undefined]
        let chars: string[] = [...(match[0] || match[1] || match[2])];
        offset--;

        let startWord = match.index + 1
        let endWord = startWord + chars.length

        chars.forEach((_, i) => {
            var matchIndex = match.index + 1
            let rainbowIndex = Math.abs((i + offset) % colors.length);
            let startIndex = matchIndex + i - 1
            let endIndex = matchIndex + i
            let start = editor.document.positionAt(startIndex)
            let end = editor.document.positionAt(endIndex)
            decorators[rainbowIndex].push(new vscode.Range(start, end))
        });
    }
    decorators.forEach((d, index) => {
        editor.setDecorations(rainbows[index], d)
    })
}

rainbow.ts

function byte2Hex(n) {
    var nybHexString = "0123456789ABCDEF"
    return String(nybHexString.substr((n >> 4) & 0x0F, 1)) + nybHexString.substr(n & 0x0F, 1)
}

function RGB2Color(r, g, b) {
    return '#' + byte2Hex(r) + byte2Hex(g) + byte2Hex(b)
}

function* makeColorGradient(frequency1, frequency2, frequency3, phase1, phase2, phase3, center = 128, width = 127, len = 50) {
    for (var i = 0; i < len; ++i) {
        var red = Math.sin(frequency1 * i + phase1) * width + center
        var grn = Math.sin(frequency2 * i + phase2) * width + center
        var blu = Math.sin(frequency3 * i + phase3) * width + center
        yield RGB2Color(red, grn, blu)
    }
}

// export const rainborColors = makeColorGradient(.3, .3, .3, 0, 2, 4)
export const rainborColors = makeColorGradient(.2, .2, .2, 0, 2, 4)

在这里插入图片描述

文章片段

vscode插件开发总结_第14张图片
package.json

{
  "name": "plaintext",
  "displayName": "%displayName%",
  "description": "%description%",
  "version": "1.0.0",
  "publisher": "vscode",
  "license": "MIT",
  "engines": {
    "vscode": "0.10.x"
  },
  "contributes": {
    "configurationDefaults": {
      "[plaintext]": {
        "editor.maxTokenizationLineLength": 2500
      }
    },
    "languages": [
      {
        "id": "plaintext",
        "aliases": [
          "plaintext",
          "txt"
        ],
        "extensions": [
          ".txt"
        ]
      }
    ],
    "snippets": [
      {
        "language": "plaintext",
        "path": "./snippets/plaintext.code-snippets"
      }
    ]
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/microsoft/vscode.git"
  }
}

plaintext.code-snippets

{
    "大连文章": {
        "prefix": "沧海",
        "body": [
           "曾经沧海难为水",
           "除却巫山不是云",
           "曾经沧海难为水",
           "除却巫山不是云",
           "曾经沧海难为水",
           "除却巫山不是云",
           "曾经沧海难为水",
           "除却巫山不是云",
           "曾经沧海难为水",
           "除却巫山不是云",
           "曾经沧海难为水",
           "除却巫山不是云"
        ],
        "description": "好诗啊好诗"
    },
    "大连文章c": {
        "prefix": "沧海",
        "body": [
           "秋风清,秋月明,",
           "落叶聚还散,寒鸦栖复惊。",
           "相思相见知何日?此时此夜难为情!",
           "入我相思门,知我相思苦,",
           "长相思兮长相忆,短相思兮无穷极,",
           "早知如此绊人心,何如当初莫相识。",
        ],
        "description": "好诗啊好诗"
    },
    "大连文章a": {
        "prefix": "断肠",
        "body": [
           "十年生死两茫茫,不思量,自难忘。千里孤坟,无处话凄凉。纵使相逢应不识,尘满面,鬓如霜。",
           "夜来幽梦忽还乡,小轩窗,正梳妆。相顾无言,惟有泪千行。料得年年肠断处,明月夜,短松冈。",
        ],
        "description": "好诗啊好诗"
    },
    "大连文章b": {
        "prefix": "断肠",
        "body": [
           "今夕何夕兮,搴舟中流。",
           "蒙羞被好兮,不訾诟耻",
           "今日何日兮,得与王子同舟。",
           "心几烦而不绝兮,得知王子。"
        ],
        "description": "好诗啊好诗"
    }
}

添加背景色

vscode插件开发总结_第15张图片

let decorationType = vscode.window.createTextEditorDecorationType({
        backgroundColor: 'red',
    });
let editor = vscode.window.activeTextEditor;
editor.setDecorations(decorationType, [new vscode.Range(0, 3, 0, 4), new vscode.Range(0, 1, 0, 2)]);

文本替换

// 通过科大讯飞返回值转化vscode位置规范
const range = new vscode.Range(editor.document.positionAt(Number(message.value.pos)),editor.document.positionAt(Number(message.value.pos)+ message.value.word.length));

if (editor) {
	const document = editor.document;
	const selection = editor.selection;
	// var p1 = new vscode.Position(0,79);
	// var p2 = new vscode.Position(0,99);
	// var r = new vscode.Range(p1,p2);
	// const word = document.getText(r)
	editor.edit(editBuilder => {
		editBuilder.replace(range  , message.value.word);
	});

大纲视图

vscode插件开发总结_第16张图片

/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
const laws = ['合同法', '劳动法', '民法典', '宪法', '刑法', '教育法', '治安管理处罚条例', '婚姻法', '隐私保护法'
			, '民事诉讼法', '人民法院在线诉讼规则', '中华人民共和国人民法院法庭规则', '最高人民法院关于互联网法院审理案件若干问题的规定', '法庭纪律'];
const providerDocumentSymbol = (context: vscode.ExtensionContext) => {
	context.subscriptions.push(vscode.languages.registerDocumentSymbolProvider(
        {language: "lawdawn"}, new FooDocumentSymbolProvider()
    ));
};
class FooDocumentSymbolProvider implements vscode.DocumentSymbolProvider {
    public provideDocumentSymbols(document: vscode.TextDocument,token: vscode.CancellationToken): Thenable<vscode.DocumentSymbol[]> {
        return new Promise((resolve, reject) => {
            let symbols: vscode.DocumentSymbol[] = [];
			
			for(let i = 0; i < document.lineCount; i++) {
				let line = document.lineAt(i);
				for (let j = 0; j < laws.length; j++) {
					if (line.text.includes(laws[j])) {
						symbols.push(new vscode.DocumentSymbol(laws[j], line.text, vscode.SymbolKind.Key, line.range,  line.range ));
					}
				}
			}
            resolve(symbols);
        });
    }
}

export default providerDocumentSymbol;
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
import providerDocumentSymbol from './language-extensions/outline';

export function activate(context: vscode.ExtensionContext) {
	// 大纲provider
	providerDocumentSymbol(context);
}

比较好的文章可供参考:

https://zhaomenghuan.js.org/note/vscode/awesome-vscode.html

https://zhuanlan.zhihu.com/p/99587182

插件:https://www.kancloud.cn/shangyewangchuan/vs_code/972974
坑:如果启动不来,去掉环境变量unset ELECTRON_RUN_AS_NODE

vscode插件demo: https://github.com/microsoft/vscode-extension-samples

vscode插件开发总结_第17张图片

你可能感兴趣的:(技术,vscode,编辑器,typescript)