如何以Github Copilot Labs的示例将其向您喜欢的编辑器(例如EMAC)移植到您喜欢的编辑器(例如EMAC)。
#javascript #vim #emacs #githubcopilot

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' }
  }
}

这里发生了什么:

  1. 我们重新定义了window.activeTextEditor.document.getText函数,因此它将返回我们的代码。
  2. 我们重新定义了window.activeTextEditor.edit函数,该功能将在扩展程序完成命令并尝试更改当前选择后调用。
  3. 我们运行vscode._registeredCommands[name](...args),它将在extension.js中运行命令并启动整个过程。
  4. 我们将所有这些都包裹在承诺中,以便我们获得执行的结果。

现在我们可以像这样执行命令

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 sidejsonrpc-server。 (对不起,有很多评论,现在我不必清理所有这些:')

这是一个简单的示例,具有简单的扩展名,但它也可能为整合更复杂的扩展铺平了道路。

我在LSP wrapper for typescript extension of vscode中发现的类似方法的例子。
它走得更远并使用vscode本身(请参阅files 123)。

对于像副副实验室这样的扩展名似乎过于杀伤,但也许对于包装一些复杂的扩展可能更有用。

再见,快乐黑客!