在 Emacs 中设置 Github Copilot - Robert Krahn

这是一个简短的演练,描述了当前的 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
  • 它工作所需的包是:sdasheditorconfig(我在这里也使用companyuse-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 黑客!

阅读 9
0 条评论