这是一个简短的演练,描述了当前的 Emacs-Copilot 设置。Copilot 主要作为 VSCode 工具进行宣传,但实际上在 Emacs 中运行得非常好。考虑到调整的便利性(例如何时查看补全),甚至可以说 Emacs 可能提供更好的体验。
目录
变更日志
- 2023-03-20:zerolfx/copilot.el 已更新,因此切换为仅引用它而不是我的分支。感谢 Jason H!
介绍与免责声明
- Github Copilot是一种使用机器学习生成文本补全的工具,主要用于源代码,尽管它在各种文本中都能令人惊讶地良好运行。它是微软的商业产品,不过如果您有一些获得了一些星标的公共 Github 存储库,目前可以免费使用。它基于OpenAI 的 Codex 模型,该模型本身源自 GPT-3 大型语言模型 (LLM)。所以在某种意义上,它是您编辑器的 ChatGPT。
- 基于开放(源代码)数据训练的 LLM,特别是 Copilot,是有争议的。许多人对这些工具构建的可疑法律依据表示担忧,例如目前有一个未决诉讼,重点关注潜在的开源许可证违规。所以让我说,我很清楚这种争议,我建议谨慎使用 Copilot,特别是在非自己的代码库中使用时。
- 也就是说,它可以是一个非常有用的工具。例如,在学习语言或框架或处理新库的 API 时,它在提供指导方面非常出色,经常节省您许多前往文档的行程。此外,它在检测代码中的某些模式方面非常出色,同时还可以看到变化并基于此建议新代码 - 巧妙的复制/粘贴强化版。测试也是 Copilot 可以提供帮助的一个好领域。
- 当然要检查生成的代码。那东西会出错。它在正确获取语法方面非常出色(尽管有时不会正确关闭括号),但要注意逻辑问题、反模式或过时的代码。
Copilot API 与集成
尽管 Copilot 主要是 VSCode 工具,但在 Emacs 中使其工作相当简单。本质上,它与语言服务器没有太大区别。VSCode 扩展不是开源的,但由于它是用 JavaScript 实现的,您可以将 vsix 包提取为 zip 文件并获取 JS 文件。据我所知,copilot.vim 插件是第一个使用该方法的非 VSCode 集成。vsix 扩展的
worker.js
文件可以作为 node.js 进程启动,该进程将从 stdin 读取 JSON-RPC 数据。有许多命令是使用此格式实现的,这里是当前的完整列表:getCompletions
getCompletionsCycling
getPanelCompletions
getVersion
setEditorInfo
checkStatus
signInInitiate
signInConfirm
signOut
notifyShown
notifyAccepted
notifyRejected
telemetry/exception
testing/createContext
testing/alwaysAuth
testing/neverAuth
testing/useTestingToken
testing/setCompletionDocuments
testing/setPanelCompletionDocuments
testing/triggerShowMessageRequest
testing/getDocument
debug/verifyState
debug/verifyCertificate
debug/verifyWorkspaceState
- 像 Emacs 或 VIM 这样的编辑器可以在子进程中启动工作进程,然后进行交互,通过 stdout 发送 JSON 消息并读取 JSON 响应。
- 鉴于 VSCode 使用完全相同的接口,并且从我迄今为止尝试的所有内容来看,我认为使用 Emacs 的 Copilot 绝不是 inferior。相反,如下所示,自定义 Copilot 的工作方式实际上更方便(至少如果您了解一些 elisp :)。我特别提到这一点,因为一些人似乎认为情况并非如此。如果我在这方面(现在或以后)是错误的,请告诉我。令人恼火的是,甚至 Copilot 代理也不是开源的(没有开源,这一切都不可能!),但弄清楚它的作用并不那么复杂。这反过来意味着其他客户端可以赶上。
Copilot Emacs 包
在 Github 上,我找到了三个实现 Copilot 集成的 Emacs 包:
- zerolfx/copilot.el似乎是最受欢迎的一个,也是我在下面使用的一个。它通过覆盖提供补全,类似于 VSCodes 扩展。
- fkr-0/flight-attendant.el可能处于不活跃状态,最后一次提交是在一年前。我没有尝试过。
- tyler-dodge/eglot-copilot/似乎在积极开发中,但没有文档。它使用 eglot 管理子进程,并使用 counsel 进行补全。
设置与自定义
- 您可以在rksm/copilot-emacsd找到一个基于
zerolfx/copilot.el
的完全可用的配置,也可以独立运行。在下面我将解释一些自定义。 zerolfx/copilot.el
不在 MELPA 上,因此您必须通过 straight.el 或其他方式安装它(请参阅README)。我通常将这样的 Emacs 包作为子模块添加到我的配置中:git submodule add https://github.com/zerolfx/copilot.el git submodule update --init
它工作所需的包是:
s
、dash
、editorconfig
(我在这里也使用company
、use-package
和 Emacs 内置的cl
包,因为我发现这些非常有帮助)。像这样安装它们:(require 'cl) (let ((pkg-list '(use-package s dash editorconfig company))) (package-initialize) (when-let ((to-install (map-filter (lambda (pkg _) (not (package-installed-p pkg))) pkg-list))) (package-refresh-contents) (mapc (lambda (pkg) (package-install pkg)) pkg-list)))
假设上述 git 子模块被克隆到
copilot.el/
中,我们现在可以加载该包:(use-package copilot :load-path (lambda () (expand-file-name "copilot.el" user-emacs-directory)) ;; 不在模式行中显示 :diminish)
- 现在您可以运行
M-x copilot-login
以使用您的 Github 帐户进行身份验证(需要订阅 Copilot 产品),然后运行M-x global-copilot-mode
以在任何地方激活 Copilot。
限制显示补全的时机
global-copilot-mode
有时会有点过于急切,因此我们在某些模式中完全禁用它:(defun rk/no-copilot-mode () "辅助函数`rk/no-copilot-modes'。" (copilot-mode -1)) (defvar rk/no-copilot-modes '(shell-mode inferior-python-mode eshell-mode term-mode vterm-mode comint-mode compilation-mode debugger-mode dired-mode-hook compilation-mode-hook flutter-mode-hook minibuffer-mode-hook) "在其中 Copilot 不方便的模式。") (defun rk/copilot-disable-predicate () "当 Copilot 不应自动显示补全时。" (or rk/copilot-manual-mode (member major-mode rk/no-copilot-modes) (company--active-p))) (add-to-list 'copilot-disable-predicates #'rk/copilot-disable-predicate)
然后,方便的是,覆盖不会自动出现,而是按需出现:
(defvar rk/copilot-manual-mode nil "当`t'时,仅在手动触发时(例如通过 M-C-<return>)显示补全。") (defun rk/copilot-change-activation ()
- 自动:Copilot 将自动覆盖补全
- 手动:您需要按一个键(M-C-<return>)来触发补全
关闭:Copilot 完全被禁用。"
(interactive)
(if (and copilot-mode rk/copilot-manual-mode)(progn (message "停用 Copilot") (global-copilot-mode -1) (setq rk/copilot-manual-mode nil))
(if copilot-mode
(progn (message "激活 Copilot 手动模式") (setq rk/copilot-manual-mode t)) (message "激活 Copilot 模式") (global-copilot-mode))))
(define-key global-map (kbd "M-C-<escape>") #'rk/copilot-change-activation)
M-C-<escape>
现在将在自动、手动和关闭三种状态之间循环。
自定义键
特定于 Copilot 的
我喜欢我的键绑定保持一定的一致性,并将
M-C-...
(Alt + Control + 其他键)分配给与 Copilot 相关的命令:(defun rk/copilot-complete-or-accept () "命令,要么触发补全,要么接受可用的补全。如果您像我一样倾向于敲击键盘,这很有用。" (interactive) (if (copilot--overlay-visible) (progn (copilot-accept-completion) (open-line 1) (next-line)) (copilot-complete))) (define-key copilot-mode-map (kbd "M-C-<next>") #'copilot-next-completion) (define-key copilot-mode-map (kbd "M-C-<prior>") #'copilot-previous-completion) (define-key copilot-mode-map (kbd "M-C-<right>") #'copilot-accept-completion-by-word) (define-key copilot-mode-map (kbd "M-C-<down>") #'copilot-accept-completion-by-line) (define-key global-map (kbd "M-C-<return>") #'rk/copilot-complete-or-accept)
Tab 键
如果您还希望将
<tab>
键用于补全,但仍保留其正常功能,请执行以下操作:(defun rk/copilot-tab () "Tab 命令,如果有补全可用,将使用 Copilot 进行补全。否则将尝试 company、yasnippet 或正常的 tab 缩进。" (interactive) (or (copilot-accept-completion) (company-yasnippet-or-completion) (indent-for-tab-command))) (define-key global-map (kbd "<tab>") #'rk/copilot-tab)
Ctrl-g / 取消
我喜欢使用
C-g
取消命令。这在取消 Copilot 补全时不能直接使用,但我们可以这样做:(defun rk/copilot-quit () "运行`copilot-clear-overlay'或`keyboard-quit'。如果清除了 Copilot,请确保覆盖不会很快回来。" (interactive) (condition-case err (when copilot--overlay (lexical-let ((pre-copilot-disable-predicates copilot-disable-predicates)) (setq copilot-disable-predicates (list (lambda () t))) (copilot-clear-overlay) (run-with-idle-timer 1.0 nil (lambda () (setq copilot-disable-predicates pre-copilot-disable-predicates))))) (error handler))) (advice-add 'keyboard-quit :before #'rk/copilot-quit)
这就是全部内容!感谢所有使这一切成为可能的出色的 Emacs 黑客!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。