vscode插件开发

目录

插件在 VSCode 中能做什么

1、发布应用市场

1、申请Microsoft账号

2、创建Azure DevOps组织

3、创建令牌

 4、创建发布账号

 5、发布应用市场

 2、本地打包不发布

1、本地打包

 2、导入应用商店

​编辑

3、插件开发前的环境准备

2、项目初始化

3、运行项目

4、文件介绍 

1、package.json

1、main

2、activationEvents

3、contributes

2、extension.js

activate:这是插件被激活时执行的函数

deactivate:这是插件被销毁时调用的方法,比如释放内存等。

5、实战-小试牛刀

1、package.json

2、extension.ts文件 

6、webview详解Webview API | Visual Studio Code Extension API

1、实现一个简单的webview功能

2、加载本地资源

1、线引入头文件

2、替换路径

3、执行函数

3、消息通信

 1、实现消息发送:

2、简单通信封装

4、生命周期

5、实现webiew的状态保持

 6、调试

7、菜单

1、菜单出现的位置

 2、菜单出现的条件when

 3、控制菜单的分组和排序group

1、editor/context中的默认组:

 2、explorer/context中的默认组:


插件在 VSCode 中能做什么

  • 主题
    • 界面和文本(TextMate 语法)主题色
    • 图标样式
  • 通用功能
    • 添加命令
    • 添加配置项
    • 添加快捷键
    • 添加菜单项
    • 添加右键菜单
    • 从文本输入框获取输入(QuickPick)
    • 存储数据(localStorage)
  • 工作区扩展
    • 活动栏项目
    • 显示提示框
    • 状态栏信息
    • 显示进度条
    • 打开文件
    • 显示网页(web view)
  • 程序语言
    • 实现新语言的高亮
    • 实现新语言的调试器
    • 代码库管理
    • 定义和执行 Task
    • 定义 snippet

1、发布应用市场

对于一个初学者来说,我们先了解下vscode的应用市场,做好开发前的准备工作

Visual Studio Code的应用市场基于微软自己的Azure DevOps,插件的身份验证、托管和管理都是在这里。

  • 要发布到应用市场首先得有应用市场的publisher账号;
  • 而要有发布账号首先得有Azure DevOps组织;
  • 而创建组织之前,首先得创建Azure账号;
  • 创建Azure账号首先得有Microsoft账号;

我们来梳理下:

  • 一个Microsoft账号可以创建多个Azure组织;
  • 一个组织可以创建多个publisher账号;
  • 同时一个组织可以创建多个PATPersonal Access Token,个人访问令牌);

1、申请Microsoft账号

访问 Sign in to your Microsoft account 登录你的Microsoft账号,没有的先注册一个

2、创建Azure DevOps组织

访问: https://aka.ms/SignupAzureDevOps

 点击继续,默认会创建一个以邮箱前缀为名的组织。

3、创建令牌

进入组织的主页后,点击右上角的Security,点击创建新的个人访问令牌,这里特别要注意Organization要选择all accessible organizationsScopes要选择Full access,否则后面发布会失败。

 

创建令牌成功后你需要本地记下来,因为网站是不会帮你保存的,后面有可能会用到,如果没保存,后面需要用到的话,就只能重新创建令牌了

 4、创建发布账号

访问https://aka.ms/vscode-create-publisher


 保存无反应,可以f12看下控制台报什么错误,如果是无法加载recaptcha,可以下载安装火狐浏览器,使用firefox浏览器下载插件gooreplacer – 下载 🦊 Firefox 扩展(zh-CN)
安装完成后就可在firefox浏览器右上角看到此插件


配置gooreplacer的设置页面


当然也可以写全路径

然后在火狐浏览器中访问上述链接就通了

输入完成后点击Submit完成后,接下来再试试,之前无法加载的recaptcha应该就正常了 ;

这里需要注意,使用vsce命令创建会报错,只能使用上面的网页方式

具体步骤如下:

先全局安装vsce

npm i vsce -g

创建发布者:

vsce create-publisher fuyun

lzfuyun是发布者的名字,然后就会报如下错误,提示让走网页方式创建:

 5、发布应用市场

上面一切准备好后,package.json中添加刚刚创建的发布者,就可以发布了

对应项目目录终端输入命令

vsce publish

等个几分钟就可以看到发布的应用了

 在vscode的应用市场上搜索一下,已经有了 

 2、本地打包不发布

本地打包安装是在不能公开的,私密性比较高的应用,使用本地打包后,把包发给需要使用的人,手动导入安装到自己的vscode中;

1、本地打包

 打包之前需要在package.json中配置发布人: “publisher”:“fuyun”

注意:如果不需要发布应用市场的话,这里的publisher可以随便取个名字,也不需要想上面发布应用市场一样申请一堆东西的长流程。

然后把README.md删干净,随便添加一句话就行,如果不删除会报错提示你打包之前需要先编辑README.md文件

准备就绪,就可以打包了,终端执行命令:

vsce package

然后一路y下去就行ok了 

 vsix就生成了

 2、导入应用商店

选择本地刚刚生成的vsix文件,就安装成功了

3、插件开发前的环境准备

在前面我们已经准备好了发布的账号、Personal Access Token、以及创建发布者后,我们就可以开始准备我们的开发环境了

1、安装依赖

官方为了方便开发人员进行vscode插件的开发,提供了对yo应的脚手架来生成对应的项目。vscode 扩展开发官方文档

// 全局安装需要的包
npm install -g yo generator-code

 在 mac 安装失败,报错没有权限 permission denied 字眼的时候,可以加 sudo 来授权

// 全局安装需要的包
sudo npm install -g yo generator-code

上述命令其实安装了两个包(yo 和 generator-code),这两个包用途如下:

  • yo模块全局安装后就安装了Yeoman,Yeoman是通用型项目脚手架工具,可以根据一套模板,生成一个对应的项目结构
  • generator-code模块是VS Code扩展生成器,与yo配合使用才能构建项目。
     

2、项目初始化

// 运行
yo code

 执行 yo code之后,会有下面几个选项:

  • ? What type of extension do you want to create(您想要创建什么类型的扩展?)? New Extension (JavaScript)
  • ? What's the name of your extension(你的分机名是什么,也就是项目名)? demo
  • ? What's the identifier of your extension(你的扩展的标识符是什么)? demo
  • ? What's the description of your extension(什么是您的扩展的描述)? learn vscode plugin
  • ? Enable JavaScript type checking in 'jsconfig.json'(在'jsconfig.json'中启用JavaScript类型检查)? Yes
  • ? Initialize a git repository(初始化一个git仓库)? Yes
  • ? bundel the source code with webpack (是否用webpack打包源码)? Yes
  • ? Which package manager to use(使用哪个包管理器)? yarn

在项目生成之后,目录结构如下所示

3、运行项目

然后,在编辑器中,按F5。这将在新的扩展开发主机窗口中编译和运行扩展。

在新窗口中从命令面板 ( shift + win + p )运行Hello World命令:

您 vscode 界面会看到Hello World from HelloWorld!显示的通知。成功!

注意:如果按键F5无法启动台调试,鼠标点击debug


这样下次直接点击F5就会直接运行插件 

4、文件介绍 

1、package.json

该文件是vscode扩展的清单文件,里面有很多字段,官方 (opens new window)对每个字段都进行了详细阐述,本次我们重点阐述以下初始化后期清单文件。

{
    "name": "demo", // 插件名
    "displayName": "demo", // 显示在应用市场的名字
    "description": "learn vscode plugin", // 具体描述
    "version": "0.0.1", // 插件的版本号
    "publisher": "lzfuyun", // 发布publisher账户名
    "engines": {
        "vscode": "^1.60.0" // 最低支持的vscode版本
    },
    "categories": [
        "Other" // 扩展类别
    ],
    // 激活事件组,在那些事件情况下被激活
    "activationEvents": [
        "onCommand:hello.helloWorld"
    ],
    // 插件的主入口文件
    "main": "./extension.js",
    // 贡献点
    "contributes": {
        // 命令
        "commands": [
            {
                "command": "hello.helloWorld",
                "title": "Hello World"
            }
        ]
    },
    "scripts": {
        "lint": "eslint .",
        "pretest": "npm run lint",
        "test": "node ./test/runTest.js"
    },
    // 开发依赖项
    "devDependencies": {
		"@types/vscode": "^1.63.0",
		"@types/glob": "^7.1.4",
		"@types/mocha": "^9.0.0",
		"@types/node": "14.x",
		"eslint": "^8.1.0",
		"glob": "^7.1.7",
		"mocha": "^9.1.3",
		"typescript": "^4.4.4",
		"@vscode/test-electron": "^1.6.2"
	}
}
{
    // 插件的名字,应全部小写,不能有空格
    "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"
}

在这package.json文件中,重点关注的主要有三部分内容:activationEventsmain以及contributes,其是整个文件中的重中之重。 

1、main

指明了该插件的主入口在哪,只有找到主入口整个项目才能正常的运转

2、activationEvents

指明该插件在何种情况下才会被激活,因为只有激活后插件才能被正常使用,官网已经指明了激活的时机 (opens new window),这样我们就可以按需设置对应时机。(具体每个时机用的时候详细查看即可)

  • onLanguage 打开解析为特定语言文件时被激活,例如"onLanguage:python"
  • onCommand 在调用命令时被激活
  • onDebug 在启动调试话之前被激活
    • onDebugInitialConfigurations
    • onDebugResolve
  • workspaceContains 每当打开文件夹并且该文件夹包含至少一个与 glob 模式匹配的文件时
  • onFileSystem 每当读取来自特定方案的文件或文件夹时
  • onView 每当在 VS Code 侧栏中展开指定 id 的视图
  • onUri 每当打开该扩展的系统范围的 Uri 时
  • onWebviewPanel
  • onCustomEditor
  • onAuthenticationRequest
  • 只要一启动vscode,插件就会被激活
  • onStartupFinished

3、contributes

通过扩展注册contributes用来扩展Visual Studio Code中的各项技能,其有多个配置,如下所示:

  • breakpoints 断点
  • colors 主题颜色
  • commands 命令
  • configuration 配置
  • configurationDefaults 默认的特定于语言的编辑器配置
  • customEditors 自定义编辑器
  • debuggers
  • grammars
  • iconThemes
  • jsonValidation
  • keybindings 快捷键绑定
  • languages
  • menus
  • problemMatchers
  • problemPatterns
  • productIconThemes
  • resourceLabelFormatters
  • snippets 特定语言的片段
  • submenus
  • taskDefinitions
  • themes 颜色主题
  • typescriptServerPlugins
  • views
  • viewsContainers
  • viewsWelcome
  • walkthroughs

2、extension.js

该文件时其入口文件,即 package.json 中 main 字段对应的文件(不一定叫extension.js这个名字),该文件中将导出两个方法:activatedeactivate,两个方法的执行时机如下所示:

  • activate:这是插件被激活时执行的函数

  • deactivate:这是插件被销毁时调用的方法,比如释放内存等。

5、实战-小试牛刀

需求:实现简单的选中大小写转换(document-editing)

1、package.json

{
  "name": "demo",
  "displayName": "demo",
  "description": "demo",
  "version": "0.0.1",
  "engines": {
    "vscode": "^1.69.0"
  },
  "categories": [
    "Other"
  ],
  "activationEvents": [
    "*"
  ],
  "main": "./extension",
  "contributes": {
    "commands": [
      {
        "command": "extension.toLowerCase",
        "title": "Lower"
      },
      {
        "command": "extension.toUpperCase",
        "title": "Upper"
      }
    ]
  },
  "scripts": {
    "vscode:prepublish": "npm run compile",
    "compile": "tsc -p ./",
    "lint": "eslint . --ext .ts,.tsx",
    "watch": "tsc -watch -p ./"
  },
  "devDependencies": {
    "@types/glob": "^7.2.0",
    "@types/mocha": "^9.1.1",
    "@types/node": "16.x",
    "@types/vscode": "^1.69.0",
    "@vscode/test-electron": "^2.1.5",
    "eslint": "^8.18.0",
    "glob": "^8.0.3",
    "mocha": "^10.0.0",
    "typescript": "^4.7.4"
  }
}

配置两个命令一个转大些,一个转小写,如果有多个事件组,activationEvents就可以用✳️代替

"activationEvents": [
    "*"
  ],

2、extension.ts文件 

/*
 * @Descripttion: automobile
 * @version: 1.0
 * @Author: 刘钊
 * @Date: 2022-07-23 10:51:31
 */

const vscode = require('vscode');

/**
 * @param {vscode.ExtensionContext} context
 */
function activate(context) {
  let lowerCase = vscode.commands.registerCommand('extension.toLowerCase',toLowerCase);
  let upperCase = vscode.commands.registerCommand('extension.toUpperCase',toUpperCase);
  context.subscriptions.push(lowerCase);
  context.subscriptions.push(upperCase);
}

function toLowerCase() {
  toLowerCaseOrUpperCase('toLowerCase');
}

function toUpperCase() {
  toLowerCaseOrUpperCase('toUpperCase');
}

//转小写
function toLowerCaseOrUpperCase(command) {
  //获取activeTextEditor
  const editor = vscode.window.activeTextEditor;
  if (editor) {
    const document = editor.document;
    const selection = editor.selection;
    //获取选中单词文本
    const word = document.getText(selection);
    //文本转大小写
    const newWord = command == 'toLowerCase' ? word.toLowerCase() : word.toUpperCase();
    //替换原来文本
    editor.edit((editBuilder) => {
      editBuilder.replace(selection, newWord);
    });
  }
}

// this method is called when your extension is deactivated
function deactivate() {}

module.exports = {
  activate,
  deactivate,
};

我们运行看看效果

6、webview详解Webview API | Visual Studio Code Extension API

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

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

1、实现一个简单的webview功能

const vscode = require('vscode');

/**
 * @param {vscode.ExtensionContext} context
 */
function activate(context) {
  let disposable = vscode.commands.registerCommand(
    'demo1.webview',
    showWebview
  );
  context.subscriptions.push(disposable);
}

function showWebview() {
  // 创建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>`;
}

function deactivate() {}

module.exports = {
  activate,
  deactivate,
};

可以看到创建一个panel,然后直接写html就可以了,是不是很简单,package.json配置好后,看看运行效果

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

2、加载本地资源

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

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

默认情况下,vscode-resource:只能访问以下位置中的资源:

  • 扩展程序安装目录中的文件。
  • 用户当前活动的工作区内。
  • 当然,你还可以使用dataURI直接在Webview中嵌入资源,这种方式没有限制;

因此我们先抽个加载资源的函数:


1、线引入头文件

import { posix } from 'path';
const fs = require('fs');

2、替换路径

/**
 * 从某个HTML文件读取能被Webview加载的HTML内容
 * @param {*} context 上下文
 * @param {*} templatePath 相对于插件根目录的html文件相对路径
 */
 function getWebViewContent(context, templatePath) {
  const resourcePath = posix.join(context.extensionPath, templatePath);
  const dirPath = posix.dirname(resourcePath);
  let html = fs.readFileSync(resourcePath, 'utf-8');
  // vscode不支持直接加载本地资源,需要替换成其专有路径格式,这里只是简单的将样式和JS的路径替换
  html = html.replace(/(<link.+?href="|<script.+?src="|<img.+?src=")(.+?)"/g, (m, $1, $2) => {
      return $1 + vscode.Uri.file(posix.resolve(dirPath, $2)).with({ scheme: 'vscode-resource' }).toString() + '"';
  });
  return html;
}

运行这段代码之后,会自动将HTML文件中linkhrefscriptimg的资源相对路径全部替换成正确的vscode-resource:绝对路径,例如:

./src/view/index.html
变成
vscode-resource:/Users/liuzhao/Documents/liuzhao/01_learn/15_vscode插件开发/demo1/src/view/index.html

3、执行函数

panel.webview.html = getWebViewContent(context, 'src/view/index.html');
/*
 * @Descripttion: automobile
 * @version: 1.0
 * @Author: 刘钊
 * @Date: 2022-07-21 19:22:34
 */
/*
 * @Descripttion: automobile
 * @version: 1.0
 * @Author: 刘钊
 * @Date: 2022-07-21 19:22:34
 */
const vscode = require('vscode');
const { posix } = require('path');
// import { posix } from 'path';
const fs = require('fs');

/**
 * @param {vscode.ExtensionContext} context
 */
function activate(context) {
  let disposable = vscode.commands.registerCommand('demo1.webview', () => {
    // 创建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>`;
    panel.webview.html = getWebViewContent(context, 'src/view/index.html');
  });
  context.subscriptions.push(disposable);
}

function deactivate() {}

/**
 * 从某个HTML文件读取能被Webview加载的HTML内容
 * @param {*} context 上下文
 * @param {*} templatePath 相对于插件根目录的html文件相对路径
 */
function getWebViewContent(context, templatePath) {
  const resourcePath = posix.join(context.extensionPath, templatePath);
  const dirPath = posix.dirname(resourcePath);
  let html = fs.readFileSync(resourcePath, 'utf-8');
  // vscode不支持直接加载本地资源,需要替换成其专有路径格式,这里只是简单的将样式和JS的路径替换
  html = html.replace(
    /(<link.+?href="|<script.+?src="|<img.+?src=")(.+?)"/g,
    (m, $1, $2) => {
      return (
        $1 +
        vscode.Uri.file(posix.resolve(dirPath, $2))
          .with({ scheme: 'vscode-resource' })
          .toString() +
        '"'
      );
    }
  );
  return html;
}

module.exports = {
  activate,
  deactivate,
};

3、消息通信

webview内部不允许发送ajax请求,所有ajax请求都是跨域的,因为webview本身是没有host

而VSCode并不会让我们接触electron配置,所以通过简单的electron配置webSecurity: false就可以开放跨域权限是行不通的,那么该如何实现网络请求呢?

webview和普通网页一样,并不能直接调用任何VSCode API。但是,它唯一特别之处就在于多了一个名叫acquireVsCodeApi的方法,执行这个方法会返回一个简易版的vscode对象,具有如下三个方法:

  • getState()
  • postMessage(msg)
  • setState(newState)

这样的话,我们可以发消息让extension去帮我们发送http请求!

 1、实现消息发送:

// 插件给Webview发送消息(支持发送任意可以被JSON化的数据)
panel.webview.postMessage(message);

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

// Webview主动发送消息给插件
const vscode = acquireVsCodeApi();
vscode.postMessage(message);

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

2、简单通信封装

Webview端: 

const callbacks = {}; // 存放所有的回调函数
/**
 * 调用vscode原生api
 * @param data 可以是类似 {cmd: 'xxx', param1: 'xxx'},也可以直接是 cmd 字符串
 * @param cb 可选的回调函数
 */
function callVscode(data, cb) {
    if (typeof data === 'string') {
        data = { cmd: data };
    }
    if (cb) {
        // 时间戳加上5位随机数
        const cbid = Date.now() + '' + Math.round(Math.random() * 100000);
		// 将回调函数分配一个随机cbid然后存起来,后续需要执行的时候再捞起来
        callbacks[cbid] = cb;
        data.cbid = cbid;
    }
    vscode.postMessage(data);
}
window.addEventListener('message', event => {
    const message = event.data;
    switch (message.cmd) {
		// 来自vscode的回调
        case 'vscodeCallback':
            console.log(message.data);
            (callbacks[message.cbid] || function () { })(message.data);
            delete callbacks[message.cbid]; // 执行完回调删除
            break;
        default: break;
    }
});

插件端:

let global = { projectPath, panel};
panel.webview.onDidReceiveMessage(message => {
    if (messageHandler[message.cmd]) {
		// cmd表示要执行的方法名称
        messageHandler[message.cmd](global, message);
    } else {
        util.showError(`未找到名为 ${message.cmd} 的方法!`);
    }
}, undefined, context.subscriptions);

/**
 * 存放所有消息回调函数,根据 message.cmd 来决定调用哪个方法,
 * 想调用什么方法,就在这里写一个和cmd同名的方法实现即可
 */
const messageHandler = {
    // 弹出提示
    alert(global, message) {
        util.showInfo(message.info);
    },
    // 显示错误提示
    error(global, message) {
        util.showError(message.info);
    },
    // 回调示例:获取工程名
    getProjectName(global, message) {
        invokeCallback(global.panel, message, util.getProjectName(global.projectPath));
    }
}
/**
 * 执行回调函数
 * @param {*} panel 
 * @param {*} message 
 * @param {*} resp 
 */
function invokeCallback(panel, message, resp) {
    console.log('回调消息:', resp);
    // 错误码在400-600之间的,默认弹出错误提示
    if (typeof resp == 'object' && resp.code && resp.code >= 400 && resp.code < 600) {
        util.showError(resp.message || '发生未知错误!');
    }
    panel.webview.postMessage({cmd: 'vscodeCallback', cbid: message.cbid, data: resp});
}

4、生命周期

webview由创建它的扩展程序所有,返回的panel对象你必须自己保存,如果你的扩展程序丢失了这个引用,那么将无法再次重新访问该webview,即使Web视图继续显示在vscode中。

用户也可以随时关闭webview面板。当用户关闭webview面板时,webview本身将被销毁,此时不能再使用panel引用,否则将会出现异常,可以通过监听onDidDispose事件在这里面做一些销毁操作。

可以通过panel.dispose()方法主动关闭webview。

5、实现webiew的状态保持

当webview移动到后台又再次显示时,webview中的任何状态都将丢失。

解决此问题的最佳方法是使你的webview无状态,通过消息传递来保存webview的状态。

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

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

注册方法:vscode.window.registerWebviewPanelSerializer

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

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

 6、调试

按下Ctrl+Shift+P然后执行打开Webview开发工具,英文版应该是Open Webview Developer Tools:

7、菜单

菜单配置是在 package.json 的 contributes 中添加:

"menus": {
    "editor/title": [{
    "when": "editorFocus",
    "command": "plugin-demo.helloWorld",
    "alt": "",
    "group": "navigation"
    }]
}

以上是一个菜单项的完整配置.

  • editor/title: 定义这个菜单出现在哪里,这里是定义出现在编辑标题菜单栏。
  • when: 菜单在什么时候出现,这里是有光标的时候出现
  • command: 点击这个菜单要执行的命令
  • alt: 按住alt再选择菜单时应该执行的命令
  • group: 定义菜单分组

 

1、菜单出现的位置

  • 资源管理器上下文菜单: 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

 2、菜单出现的条件when

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

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

 3、控制菜单的分组和排序group

不同的菜单拥有不同的默认分组

1、editor/context中的默认组:

  • navigation:放在这个组的永远排在最前面;
  • 1_modification:更改组;
  • 9_cutcopypaste:编辑组
  • z_commands:最后一个默认组,其中包含用于打开命令选项板的条目。

 2、explorer/context中的默认组:

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

8、快捷键 

快捷键的设置比较简单,其执行功能同样依赖于其关联的命令command

"keybindings": [
    {
        "command": "plugin-demo.helloWorld",
        "key": "ctrl+h",
        "mac": "cmd+h",
        "when": "editorTextFocus"
    }
]
  • command: 快捷键关联的命令
  • key: Windows平台对应的快捷键
  • mac: mac平台对应的快捷键
  • when: 什么时候快捷键有效

当插件被激活后,并且满足快捷键有效的时间,按快捷键就可以找到extension.js中与快捷键关联的command所不绑定的事件并执行。

  • 26
    点赞
  • 109
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值