共计 4724 个字符,预计需要花费 12 分钟才能阅读完成。
欢送关注我的公众号 睿 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
- 设置
webviewPanel
的html
- 在
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 十分实用,能够用于开发读者本人设计的插件。
本文是本系列的终章,心愿对你有所帮忙,后会有期。