一.扩展能力
VS Code插件不适合作UI定制,好比Atom的tool-bar 在VS Code很难实现:javascript
提供了丰富的扩展能力模型,但不容许插件直接访问底层UI DOM(也就是说插件难以改变IDE外观,UI定制受限),这是出于方便底层持续优化考虑:css
With VS Code, we’re continually trying to optimize use of the underlying web technologies to deliver an always available, highly responsive editor and we will continue to tune our use of the DOM as these technologies and our product evolve.
UI DOM这一层可能会随着优化频繁变更,VS Code不但愿这些优化项受限于插件依赖,因此干脆把UI定制能力限制起来java
除UI定制以外的,IDE相关的功能型特性都是支持扩展的,如基础的语法高亮/API提示、引用跳转(转到定义)/文件搜索、主题定制,高级的debug协议等等node
P.S.实际上,非要扩展UI,也是有办法的(逃出插件运行环境,但要费很多力气),具体见access electron API from vscode extension,后续笔记会详细介绍git
二.运行环境
为了性能与兼容性,插件在独立的进程(称为extension host process)中运行,而且不容许直接访问DOM,因此提供了一套内置的UI组件,好比智能提示(IntelliSense)github
因此插件崩溃或无响应不影响IDE正常运行,例如:web
// ref: my-extension/src/extension.ts export function activate(context: vscode.ExtensionContext) { // hang up while (true); }
一个插件的死循环并不影响IDE的正常使用和其它插件的加载/激活,但在进程列表可以看到Code Helper的CPU占用接近100%,进程级沙箱保证了插件机制的稳定性npm
三.核心理念
稳定性:插件隔离
插件可能会影响启动性能和IDE自身的稳定性,因此经过进程隔离来解决这个问题,插件运行在独立的进程中,不影响IDE及其启动时间编程
这样作是从用户角度考虑的,但愿用户对IDE拥有彻底的控制力,不管插件在作什么,都不影响IDE基本功能的正常使用json
P.S.extension host process是个特殊的Node进程,可以访问VS Code扩展API,VS Code也对这种进程提供了debug支持
性能:插件激活
插件都是懒加载的(as late as possible),只在特定场景才加载/激活,全部在此以前也不耗费内存等资源
实现上是插件注册特定激活事件(activation events),由IDE来触发执行,好比markdown插件只在用户代开md文件时才须要激活
激活方式
插件有6种激活方式:
onLanguage:${language} 打开特定语言的文档
onCommand:${command} 经过Command Palette执行特定命令
onDebug 进入调试模式
workspaceContains:${toplevelfilename} 打开的文件夹里含有特定文件
onView:${viewId} 展开指定view
插件清单文件
清单文件用来描述插件的meta信息,直接把package.json做为清单文件,并增长了一些特有字段,好比触发插件加载的激活事件(activation events)、插件想要加强的扩展点(contribution points)
IDE在启动过程当中扫一遍插件清单文件,UI相关的就扩展UI,UI无关的就把扩展点与插件功能关联起来
另外,因为插件的执行环境是Node进程,因此npm package都是可用的,依赖模块一样声明在package.json里。注意,用户安装插件时不会自动npm install,因此须要在发布插件前把依赖模块打包进去,具体见Installation and Packaging
P.S.扩展点相似于AOP里的Join point(链接点),即“容许在这里扩展/加强”,好比新增一个自定义命令,就是对commands扩展点的加强
manifest // package.json { // 插件名称 "name": "my-extension", // 显示名称 "displayName": "MyExtension", // 描述信息 "description": "An awesome vscode extension", // 版本号 semver格式 "version": "0.0.1", // 在插件市场展现的图标 "icon": "img/icon.png", // 发布者名字 "publisher": "ayqy", // vscode版本要求 "engines": { "vscode": "^1.19.0" }, // 所属分类,可选Languages, Snippets, Linters, Themes等等 "categories": ["Other"], // 加载/激活方式 "activationEvents": ["onLanguage:javascript"], // 入口文件路径 "main": "./out/extension", // 注册扩展点关联 "contributes": { "languages": [ { "id": "javascript", "aliases": ["JavaScript", "javascript"], "extensions": [".js"] } ] } }
P.S.完整的见Extension Manifest File – package.json
extension.ts/activate只触发一次,根据package.json声明的activationEvents来触发,触发条件能够是打开特定语言的文件,或者执行特定命令。激活以后,直到IDE被关闭/崩溃才会触发extension.ts/deactivate,因此通常用法是:
activate: 插件被激活,初始化功能模块单例(只执行一次)
deactivate: IDE即将关闭,清理现场,但不宜作太耗时的操做,由于听说最多只等待10s
扩展点
即支持的扩展类型,都声明在package.json/contributes下,包括:
configuration 插件配置项,用户能够经过Settings设置
configurationDefaults 插件配置项默认值
commands 添加命令,用户能够经过Command Palette输入特定命令激活插件功能
menus 添加与命令关联的菜单项,用户点击菜单项时执行对应命令
keybindings 添加与命令关联的快捷键,用户按下特定快捷键时执行对应命令
languages 与文件类型创建关联或扩展新语言,用户打开(知足某些要求的)特定文件类型时执行对应命令
debuggers 添加debugger,经过VS Code debug协议与IDE通讯
breakpoints 配合debuggers,声明对debugger支持的(编程)语言类型
grammars 新增TextMate语法描述,语法高亮
themes 添加定制主题
snippets 添加代码片断
jsonValidation 添加json格式校验
views 新增左侧文件查看器视图和调试视图分栏
problemMatchers 添加错误匹配,从lint结果解析出error,warning等
problemPatterns 配合problemMatchers,定义匹配模式
menus是惟一的UI扩展官方途径,支持扩展的菜单具体以下:
Command Palette搜索框下方菜单 commandPalette
文件查看器右键菜单 explorer/context
编辑器
右键菜单 editor/context
标题栏菜单 editor/title
标题栏右键菜单 editor/title/context
调试视图
调用栈右键菜单 debug/callstack/context
SCM(源码管理)视图
标题栏菜单 scm/title
文件分组菜单 scm/resourceGroup/context
文件状态菜单 scm/resource/context
文件变更菜单 scm/change/title
左侧视图
文件查看器分栏 view/title
调试视图分栏 view/item/context
P.S.都是些不起眼的位置,大刀阔斧的UI定制是不支持的,好比想在左端侧边栏(Activity Bar)加个Icon都是作不到的
标题栏上的菜单扩展支持自定义icon,但定义方式比较奇怪,例如:
"commands": [{ "command": "markdown.showPreviewToSide", "title": "%markdown.previewSide.title%", "category": "Markdown", "icon": { "light": "./media/PreviewOnRightPane_16x.svg", "dark": "./media/PreviewOnRightPane_16x_dark.svg" } }], "menus": { "editor/title": [ { "command": "markdown.showPreviewToSide", "when": "editorLangId == markdown", "alt": "markdown.showPreview", "group": "navigation" } ] }
给command定义icon,menu关联到command,而后menu展现对应的icon
扩展API
环境隔离让严格限制插件可用API变得容易不少,插件只能访问IDE提供的扩展性API,不能胡乱搞事情(好比修改UI DOM和样式,官方支持的主题定制项除外)
API设计原则
插件API遵循一些原则:
基于Promise:异步操做都用Promise来描述
取消token:传入CancellationToken做为额外参数来检查取消状态,以及接收取消通知
可释放式资源管理:持有的资源都须要手动释放,例如事件监听,命令,UI交互等
事件API:调用订阅方法(on[Will|Did]VerbNoun)传入listener(接收event参数)返回Disposable
严格空检查:经过TypeScript严格区分undefined和null
P.S.关于“可释放式”(Disposable)的更多信息,请查看Dispose pattern
API概览
API按命名空间组织,全局命名空间以下:
commands 执行/注册命令,IDE自身的和其它插件注册的命令均可以,如executeCommand
debug 调试相关API,好比startDebugging
env IDE相关的环境信息,好比machineId, sessionId
extensions 跨插件API调用,extensionDependency声明插件依赖
languages 编程语言相关API,如createDiagnosticCollection, registerDocumentFormattingEditProvider
scm 源码版本控制API,如createSourceControl
window 编辑器窗体相关API,如onDidChangeTextEditorSelection, createTerminal, showTextDocument
workspace 工做空间级API(打开了文件夹才有工做空间),如findFiles, openTextDocument, saveAll
好比能够经过workspace.findFiles + languages.registerDefinitionProvider实现Haste的全局模块引用跳转支持
另外,一些API以命令形式提供(即上面提到的“IDE自身的”命令),例如vscode.previewHtml、vscode.openFolder、editorScroll等等
基于协议的扩展
插件进程与IDE之间经过特定协议来通讯,实现上是以JSON形式的stdin/stdout来通讯
这种模式更强大的一点是:插件能够用任意语言来实现,只要遵照这套约定的通讯协议便可
四.语言相关扩展
经过配置文件来支持语法高亮、代码片断和智能括号匹配,更复杂的经过扩展API或language server来作
配置型扩展
语法高亮:基础支持区分字符串、注释、关键字等语法角色,高级支持变量、函数引用等语义区分
代码片断:snippets快捷输入,基础支持简单占位符,高级支持嵌套占位符
智能括号匹配:高级支持自动补充成对出现的东西,好比括号、引号、跨行注释等
注意,语言扩展VS Code支持标准Text Mate Grammar(tmLanguage格式),好比Monaco Editor的非主流Monarch-style友好不少,具体见Colorization Clarification
编程型扩展
简单配置搞不定的,都经过扩展API(写插件)来实现,有2种方式:
实现language server protocol与IDE通讯,彻底独立
注册Provider提供自定义能力,相似于hook的方式
使用上,第一种麻烦但更强大灵活,第二种方便直接但没那么灵活。支持的扩展能力以下:
hover提示:基础支持类型、文档等信息,高级支持方法签名语法高亮
补全提示:高级支持在补全提示项旁边展现额外信息
检查报错:基础支持保存时对打开的文件内容检查报错,高级支持对打开的文件目录里的任意资源检查报错
方法签名:基础支持在方法签名中包含参数说明文档
跳转到定义:基础支持存在多处定义时都展现出来
引用查找:基础支持返回全部引用处的具体位置
选中查找高亮:基础支持返回当前文档的全部相同引用
方法/变量声明目录:基础支持返回文档中声明的全部标识符,及其定义位置
快速修复:对Warning和Error给出建议作法,快捷修复。基础支持纠错动做,高级支持修改源码,好比重复代码提出函数
上下文操做选项:容许根据用户处代码上下文,提供额外的信息与可操做选项。基础支持展现,高级能够添加自定义命令
重命名:基础不支持按引用重命名,高级支持工做空间下跨文件重命名
代码格式化:基础不支持代码格式化,高级支持全文/选中/输入中格式化
五.开发步骤
环境要求
VS Code
Yeoman与Yo Code – Extension Generator:npm install -g yo generator-code一步搞定
步骤
经过脚手架生成项目模版:
yo code
命令交互选择插件类型:
New Extension (TypeScript) New Extension (JavaScript) New Color Theme New Language Support New Code Snippets New Extension Pack
建议TypeScript,其它都是字面意思,其中Extension Pack(插件包)比较有意思,即插件组装成的插件,相似于React Native的Nuclide
输入插件名称等meta信息,就获得一个插件项目,而后用VS Code单独打开该项目(工做空间不能有其它项目目录),F5启动debug进入插件调试
插件入口文件是my-extension/src/extension.ts,项目结构规范能够参照VS Code内置插件:
// ref: https://github.com/Microsoft/vscode/tree/master/extensions/markdown markdown/ media/ *.svg *.css snippets/ markdown.json syntaxes/ *.tmLanguage src/ features/ *Provider.ts typings/ *.d.ts commandManager.ts commands.ts logger.ts markdownEngine.ts security.ts telemetryReporter.ts
六.打包发布
提供了CLI工具,vsce:
npm install -g vsce
打包
进入插件目录,打包成.vsix文件:
cd my-extension vsce package
会获得一个my-extesion.vsix本地包(包括node_modules依赖),而后不想公开的话,本身想办法传播安装,由于不像npm registry,能够手动部署一份,在内网环境放私有插件,Visual Studio Marketplace(VS Code插件市场)没有这么开放的心态:
If you want to share your extension with others privately, you can send them your packaged extension .vsix file.
(见Sharing Privately with Others)
没有办法部署一套Visual Studio Marketplace,因此只能想办法手动解决插件更新问题,好比自动下载/提示安装
发布
要发布到插件市场的话,须要作几件事情:
注册Visual Studio Team Services帐号
进入Security页面建立个Personal Access Token
vsce create-publisher (publisher name)命令新增publisher
vsce login (publisher name)命令登陆
vsce publish -p <token>命令发布
具体见Publishing Extensions
参考资料Extending Visual Studio Code