TLDR:概念代码证明在这里https://github.com/haukot/copilot_labs_plugin_base
vs代码插件本质上是JS文件,VS代码使用其自己的回调运行。因此,我们可以编写自己的包装器,该包装器将定义插件所需的功能,并存根所有其他功能。
一个简单的例子是Github Copilot Labs扩展,因为它只需要选择和命令。
vs代码扩展本质上是一个JS模块,它导出了几个功能。我们对init()
和activate()
感兴趣。
现在,让我们下载它,解压缩并将其移至extension
文件夹。
然后我们可以初始化它
var extension = require('./extension/extension/dist/extension.js');
await extension.init();
await extension.activate();
它失败了,因为它需要运行的VSCODE(显然!)。因此,让我们创建自己的VS代码。
// vscode/index.js
let vscode = {}
vscode.version = 'MINE'
module.exports = vscode;
// package.json
...
"dependencies": {
"vscode": "file:vscode"
}
...
现在,我们的扩展名将使用我们自己的VS代码!不过,它仍然失败。
我们需要将扩展名等待的方法存根,但是其中大多数将是像这样的简单虚拟方法
vscode.Uri = {
parse: (e) => {
return {}
}
}
您可以看到所有方法here。
现在让我们初始化扩展名(我们还在activate
中添加了几个虚拟设置)
await extension.init();
await extension.activate({
extension: {
packageJSON: {
name: 'vscode-copilot',
}
},
subscriptions: [],
globalState: {
setKeysForSync: () => {},
},
});
很好,没有错误!但是我们想要输出。
GitHub Copilot Labs的主要功能是“使用刷子”,该功能可作为vs Code中的executecommand。
让我们看一下命令的存根
// NOTE: this is our variable, not vscode's
vscode._registeredCommands = {}
vscode.commands = {
registerCommand: (e, t) => {
let command = { name: e, callback: t }
vscode._registeredCommands[e] = t
return command
},
// NOTE: you need to replace this method to implement your functionality
executeCommand: async (e, ...z) => {
let command = { name: e, args: z }
if (vscode._registeredCommands[e]) {
vscode._registeredCommands[e](...z)
} else {
return { status: 'NotRegistered' }
}
}
}
它没有做任何复杂的事情,只需从extension.js注册命令,然后通过executecommand运行。
让我们为Copilot Labs更改它。它的命令接受刷子名称并更改当前选择。
因此,让我们重新定义ExecuteCommand,以便它接受外部源的文本!
vscode.commands.executeCommand = async (name, ourParams, ...args) => {
let command = { name, args }
if (vscode._registeredCommands[name]) {
return new Promise((resolve, reject) => {
vscode.window.activeTextEditor = {
...vscode.window.activeTextEditor,
...{
document: {
getText: (currentSelection) => {
// we already return content, so don't need to use currentSelection
return ourParams.fileContent;
},
languageId: ourParams.languageId,
},
async edit(callback) {
let toChange = {
replace: (selection, newContent) => {
// we'll return whole content, so don't need to use selection
resolve(newContent)
}
}
callback(toChange)
}
}
}
vscode._registeredCommands[name](...args)
});
} else {
return { status: 'NotRegistered' }
}
}
这里发生了什么:
- 我们重新定义了
window.activeTextEditor.document.getText
函数,因此它将返回我们的代码。 - 我们重新定义了
window.activeTextEditor.edit
函数,该功能将在扩展程序完成命令并尝试更改当前选择后调用。 - 我们运行
vscode._registeredCommands[name](...args)
,它将在extension.js中运行命令并启动整个过程。 - 我们将所有这些都包裹在承诺中,以便我们获得执行的结果。
现在我们可以像这样执行命令
let fileContent = 'def hello():\n print("Hello, world!")\n\nhello()'
let languageId = 'python';
vscode.commands.executeCommand('copilot-labs.use-brush', { fileContent, languageId }, 'debug')
.then((result) => {
console.log("RESULT", result);
})
.catch((error) => {
console.log("ERROR", error);
});
很好!要将其与IDE集成,我们需要一些接口来运行它,例如JSONRPC服务器。 IDE插件将启动并将命令发送到它。
这并不是那么有趣,所以我将仅链接实现:IDE plugin's side和jsonrpc-server。 (对不起,有很多评论,现在我不必清理所有这些:')
这是一个简单的示例,具有简单的扩展名,但它也可能为整合更复杂的扩展铺平了道路。
我在LSP wrapper for typescript extension of vscode中发现的类似方法的例子。
它走得更远并使用vscode本身(请参阅files 1,2,3)。
对于像副副实验室这样的扩展名似乎过于杀伤,但也许对于包装一些复杂的扩展可能更有用。
再见,快乐黑客!