如何开发一个 vscode 插件

重要组成

  • 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",
        "onLanguage:javascript"
    ],
    // 插件的主入口
    "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"
}
  • 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”已被释放!')
};

步骤

接下来制作一个字数统计的 demo 来叙述流程

  • 用 yo 生成项目模板
npm install -g yo generator-code
yo vscode-plugin-word-count
  • 将生成的项目在 vscode 中打开,按F5,如果你可以看到VSCode又启动了一个窗口运行插件项目,shift+ctrl+p 输入Hello World如果在右下角能看到Hello World的提示信息就OK 了


  • 插件内容编写
    新建wordCounter.js

class WordCounter {
    constructor(_vscode) {        //构造函数,传入vscode对象
        this.vscode = _vscode;
        this.init();
    }
 
    init() {                      //初始化
        var vscode = this.vscode;
        var StatusBarAlignment = vscode.StatusBarAlignment;
        var window = this.vscode.window;
 
        //statusBar,是需要手动释放的
        this.statusBar = window.createStatusBarItem(StatusBarAlignment.Left);
 
        //跟注册事件相配合的数组,事件的注册,也是需要释放的
        var disposable = [];
        //事件在注册的时候,会自动填充一个回调的dispose到数组
        window.onDidChangeTextEditorSelection(this.updateText, this, disposable);
 
        //保存需要释放的资源
        this.disposable = vscode.Disposable.from(disposable);
 
        this.updateText();
        this.statusBar.show();
    }
 
    updateText() {       //现在快凌晨两点,偷个懒早点睡,临时改成字符数量了。
        var window = this.vscode.window;
        this.editor = window.activeTextEditor;
        var content = this.editor.document.getText();
        var len = content.replace(/[\r\n\s]+/g, '').length;
        this.statusBar.text = `啦啦啦...已经敲了${len}个字符了`;
    }
 
    dispose() {  //实现dispose方法
        this.disposable.dispose();
        this.statusBar.dispose();
    }
}
 
module.exports = WordCounter;

修改extension.js

// vscode这个包,包含了里面所有的api
const vscode = require('vscode');
// 在插件被激活的时候,这个方法会被调用
function activate(context) {
    var WordCounter = require('./wordCounter');
    var counter = new WordCounter(vscode);
    //需要释放的资源都在这里依次push到这个数组里面
    context.subscriptions.push(counter);
}
exports.activate = activate;

// this method is called when your extension is deactivated
function deactivate() {
}
exports.deactivate = deactivate;

修改package.json

{
    "name": "wordcount",
    "displayName": "WordCount",
    "description": "wordcount",
    "version": "0.0.1",
    "publisher": "fengyinchao",
    "engines": {
        "vscode": "^1.29.0"
    },
    "categories": [
        "Other"
    ],
    "activationEvents": [
        "*"
    ],
    "main": "./extension",
    "contributes": {
        "commands": [
            {
                "command": "extension.sayHello",
                "title": "Hello World"
            }
        ]
    },
    "scripts": {
        "postinstall": "node ./node_modules/vscode/bin/install",
        "test": "node ./node_modules/vscode/bin/test"
    },
    "devDependencies": {
        "typescript": "^3.1.4",
        "vscode": "^1.1.25",
        "eslint": "^4.11.0",
        "@types/node": "^8.10.25",
        "@types/mocha": "^2.2.42"
    }
}

重启F5,就可以看到


  • 发布

1、注册账号 https://aka.ms/SignupAzureDevOps
2、得到 token,登录后选择Security,按照下图上的方式勾选,这里注意得到token一定要复制下来保存,因为第二次进入就不会再显示了

https://image-static.segmentfault.com/225/828/2258281917-5c089e50cc66c_articlex

3、安装 vsce 打包工具

npm install -g vsce

4、使用 vsce工具命令创建发布账号

vsce create-publisher your-publisher-name

5、登录账号

vsce login your-publisher-name

6、发布

vsce publish

7、查看已发布包
https://marketplace.visualstudio.com/search?target=VSCode&category=Other&sortBy=Relevance

小结

可以看出开发vscode插件,最主要的文件就是 package.json 和 extension.js 。前者负责注册插件的触发时机以及插件的展示位置等(开头部分的package.json是一个很全的配置demo),后者负责插件内容的具体实现。

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容