欢送关注我的公众号睿Talk,获取我最新的文章:

一、前言

在 上一篇文章 中,我介绍了服务于区块开发的命令行工具是如何实现的,本文将沿着主题解说 VSCode 插件的实现形式。

二、区块列表展现

如果刚接触 VSCode 插件开发,能够先看看我之前写的 VS Code插件开发介绍。

为了不便用户应用,我心愿有一个专门的 tab 页分类列出所有的区块。这里就会用到 Tree View 这个 API。先看一下成果:

要在左侧工具栏增加 tab,须要先在 package.json 文件中配置一个 View Container 和一个 View

"contributes": {  "viewsContainers": {    "activitybar": [      {        "id": "tce-block",        "title": "TCE Block",        "icon": "media/block.svg"      }    ]  },  "views": {    "tce-block": [      {        "id": "tceBlock",        "name": "TCE Block",        "icon": "media/dep.svg",        "contextualTitle": "TCE Block"      }    ]  }}

这里指定了 tab 的地位放在左侧工具栏 activitybar,另一个可选项是 panel,在编辑器底部(终端)的地位。而后再给刚增加的这个 View Container 指定一个 View,通过 tce-block 这个 ID 进行关联。

下一步就是定制 View 的显示内容了。因为显示的内容就是一棵目录树,所以用到了 VSCode 插件开发中内置的 Tree View API。上面咱们来定义一棵树,要害是实现 vscode.TreeDataProvider 这一接口:

class BlockProvider implements vscode.TreeDataProvider<Block> {  constructor(private workspaceRoot: string) {}  getTreeItem(element: Block): vscode.TreeItem {    return element;  }  async getChildren(element?: Block) {    if (!this.workspaceRoot) {      return [];    }    // 不是根目录,返回元素的children    if (element) {      if (element.children.length > 0) {        return element.children;      }      return null;    }    // 根目录,结构树形数据结构    else {      const resp = await fetchData<BlockCategories>(        'http://xxx.com/block-categories.json'      );      if (!resp) {        vscode.window.showErrorMessage('获取区块列表失败');        return [];      }      const { blocks } = resp;      return toBlock(blocks);    }  }}

这里的重点是实现 getChildren 办法,返回树形构造。这里有 2 种状况:

  • 参数 element 为空时,阐明是根目录,须要结构出树的第一层数据结构(数组)。
  • 参数 element 非空时,返回子节点数组。

toBlock 函数的作用是结构出树的所有节点。我设计的树只有 2 层,第一层是区块分类,第二层是区块实例:

function toBlock(categories: BlockItem[]): Block[] {  // 区块分类  return categories.map((category) => {    const { id, label, children } = category;    const categoryItem = new Block(      id,      label,      '',      vscode.TreeItemCollapsibleState.Collapsed    );        // 区块实例    categoryItem.children = children!.map((blockItem) => {      const { id: blockId, label: blockLabel, url } = blockItem;      const block = new Block(blockId, blockLabel, url!);      block.type = blockItem.type;      return block;    });    return categoryItem;  });}

上面再来看树节点的定义,继承自 vscode.TreeItem

export class Block extends vscode.TreeItem {  children: Block[] = [];  type: number = 2;  constructor(    public readonly id: string,    public readonly label: string,    public readonly url: string,    public readonly collapsibleState?: vscode.TreeItemCollapsibleState  ) {    super(label, collapsibleState);    this.id = id;    this.tooltip = `${this.label}区块`;    // 区块实例    if (url) {      this.contextValue = 'block'; // 管制操作按钮的显示暗藏      this.command = {        title: this.label, // 题目        command: 'tceBlock.openWebview', // 命令 ID        tooltip: this.label, // 鼠标笼罩时的小小提示框        arguments: [this], // 向 registerCommand 传递的参数。      };    }  }}

Block 的定义是蕴含所有类型的节点的(区块分类和区块实例),所以须要依据构造函数传入的值来定义不同的行为,比方这里对于区块实例,会有 url 属性,点击他会关上一个 webview,这块会在前面解说。

到此为止,树形构造曾经能失常展现了。

三、预览区块

区块的预览实质上来说就是在 vscode 外面关上一个网页,这里就用到了 Webviews API。外围代码如下:

let currentPanel: vscode.WebviewPanel | undefined = undefined;export function openWebView(url: string) {  if (currentPanel) {    currentPanel.dispose();  }  currentPanel = vscode.window.createWebviewPanel(    'tceBlock',    'TCE Block',    vscode.ViewColumn.One,    {      retainContextWhenHidden: true, // 管制是否放弃webview面板的内容(iframe),即便面板不再可见。      enableScripts: true, // 上面的 html 页能够应用 Scripts    }  );  currentPanel.webview.html = getWebviewContent(url);  // Reset when the current panel is closed  currentPanel.onDidDispose(() => {    currentPanel = undefined;  }, null);}function getWebviewContent(url: string) {  return `<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Cat Coding</title>    <style>      html,      body {          margin: 0 !important;          padding: 0 !important;          width: 100%;          height: 100%;      }      #blockFrame {          width: 100%;          height: 100%;      }  </style></head><body><iframe id='blockFrame' src="${url}" scrolling="auto"></iframe></body></html>`;}

代码很好了解,分为以下几步:

  • 新建一个 webviewPanel
  • 设置 webviewPanelhtml
  • html 中嵌入一个 iframe 来动静加载网页

这个操作会注册成 vscode 的一个命令,而后在点击区块实例的时候被调用:

vscode.commands.registerCommand('tceBlock.openWebview', (node: Block) => {  openWebView(node.url);});export class Block extends vscode.TreeItem {  constructor(...) {    ...    // 区块实例    if (url) {      ...      this.command = {        command: 'tceBlock.openWebview', // 执行下面定义的命令        arguments: [this], // 向 registerCommand 传递的参数        ...      };    }  }}

四、装置区块

通过区块列表装置

咱们心愿插入区块这个操作显示在区块实例的边上,当鼠标挪动到对应区块时被激活:

这就须要在 package.json 文件中定义这个操作:

"contributes": {  "menus": {    "view/item/context": [      {        "command": "tceBlock.addBlock",        "when": "view == tceBlock && viewItem == block",        "group": "inline"      }    ]  }}

具体的区块插入代码跟 上一篇文章 大同小异,在此就不反复了。这里会用到一些 VSCode 的 API,如通过对话框的形式获取用户心愿区块插入的地位:

const options: vscode.OpenDialogOptions = {  title: '请抉择区块插入地位',  openLabel: '插入区块',  canSelectMany: false,  canSelectFiles: false,  canSelectFolders: true,};const fileUri = await vscode.window.showOpenDialog(options);insertPath = fileUri[0].fsPath;
通过上下文菜单装置

为了省却抉择区块装置目录的麻烦,还能间接在我的项目中通过上下文菜单的形式装置区块:

这须要在 package.json 中配置上下文菜单:

"contributes": {  "menus": {    "explorer/context": [      {        "command": "tceBlock.generateBlock",        "group": "1_modification"      }    ]  }}

抉择插入区块后会晋升抉择区块实例:

这里用到了 VSCode 的另一个 API:

const blockNames: any[] = []...const blockItem = await vscode.window.showQuickPick(blockNames, {  placeHolder: '请抉择要插入的区块',});

五、总结

本文解说了基于区块开发的 VSCode 插件的实现细节,次要性能是以树的模式展示区块列表、预览区块和装置区块。当中用到的 VSCode API 十分实用,能够用于开发读者本人设计的插件。

本文是本系列的终章,心愿对你有所帮忙,后会有期。