# doom **Repository Path**: nathanjoy/doom ## Basic Information - **Project Name**: doom - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-02 - **Last Updated**: 2026-09-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Doom Emacs 自定义配置说明 > 本仓库是 Doom Emacs 的 `$DOOMDIR`(配置文件目录 `~/.config/doom/`)。 > 下列「自定义操作」绝大部分写在 **`config.el`** 里,属于个人增强,**不依赖新插件、 > 改完无需 `doom sync`**,重启 Emacs 即生效(或在 Emacs 里 `M-x doom/reload`)。 > 例外:改动了 **`init.el`** 里的模块 / flag(如第 8 项 `window-select +numbers`、 > 第 11 项 `(cc +lsp)` / `(lsp +eglot)`),或改动 **`packages.el`**(如禁用 > `opencl-mode`),都必须跑 `doom sync` 才真正生效。 --- ## 自定义操作总览 | # | 功能 | 触发方式 | 依赖 | |---|------|----------|------| | 1 | 终端鼠标滚轮 | SSH/tmux 里直接滚轮 | tmux `mouse on` | | 2 | 复制同步到本地剪贴板 | 终端里 `y` 复制 | tmux OSC 52 + 外层终端支持 | | 3 | 文件标签栏 | 每个窗口顶部自动显示 | Emacs 28+ 内置 | | 4 | 退出免确认 | `C-x C-c` / `SPC q q` | 无 | | 5 | treemacs 递归搜索 | treemacs 里 `g f` / `g s` | consult + vertico + rg/fd | | 6 | treemacs 打开终端 | treemacs 里 `g t` | `:term vterm` 模块 | | 7 | jk 快键退出 | insert 态连按 `j k` | evil-escape(内置) | | 8 | 数字窗口切换 | `SPC 1`~`9` 直接跳窗口 | `window-select +numbers`(需 sync) | | 9 | C-s 保存文件 | normal/insert 态 `C-s` | 无 | | 10 | 跳转历史返回/前进 | `g d` 后 `C-o` / `C-i` | better-jumper(内置) | | 11 | C/C++ 补全与跳转 | 打开 `.c`/`.h` 自动 | `lsp +eglot` + `cc +lsp` + clangd(需 sync) | | 12 | 终端补全弹窗(Emacs 30) | 补全候选就地弹出 | `corfu-terminal` + `popon`(需 sync) | | 13 | AI 代码补全 | 输入时幽灵文本内联补全 | `minuet` + DeepSeek FIM(需 sync) | | 14 | treemacs 根目录固定启动目录 | `SPC o p` 恒显示启动目录 | 无 | | 15 | AI 对话 / 代码改写 | `SPC o l` 系列 | `:tools llm`(gptel,需 sync) | --- ## 1. 终端鼠标滚轮 **适用场景**:SSH + tmux 的终端版 Emacs(`DISPLAY` 为空)。 - **作用**:终端里用鼠标滚轮滚动。 - **原理**:开启 `xterm-mouse-mode`(Emacs 内建,零依赖直接开;`doom sync` 不涉及)。 - **前置条件**:`.tmux.conf` 需 `set -g mouse on`(已配)。 ```elisp (when (not (display-graphic-p)) (xterm-mouse-mode +1)) ``` --- ## 2. 复制同步到本地剪贴板(OSC 52) **适用场景**:SSH 远程 Emacs,复制后能直接 `Ctrl+V` 到 Windows 本地。 - **作用**:终端里 `y`(或其它复制)后,文本经 OSC 52 转义序列推回本地终端模拟器,写进系统剪贴板。 - **原理**:手动激活 Emacs 30.1 **内置**的 `xterm--set-selection`(默认 tmux 下未激活)。 - **零额外依赖**:本方案走 Emacs 内置,不装 `clipetty`/`xclip`。 - **链路缺一不可**: 1. Emacs 激活 setSelection(本配置 ✅) 2. tmux `set -g set-clipboard on` + `Ms` 覆盖(`.tmux.conf` 已配 ✅) 3. 外层终端模拟器支持 OSC 52(Windows Terminal / WezTerm / Kitty / MobaXterm 均支持;PuTTY 类不支持) ```elisp (require 'term/xterm) (xterm--init-activate-set-selection) ``` > 改了 `.tmux.conf` 后若 tmux 是旧 server,记得 `tmux source ~/.tmux.conf`(或 `prefix+r`)。 --- ## 3. 文件标签栏(tab-line-mode) **对应 VSCode 顶部那排「打开的文件」标签**。 - **作用**:每个**当前窗口**顶部显示该窗口打开的文件标签,带 `✕` 关闭按钮。 - **原理**:`tab-line-mode`(Emacs 28+ 内置),不用 Doom 的 `:ui tabs`(那依赖 `centaur-tabs` 需联网)。 - **键盘**:`SPC ]` 下一个标签 / `SPC [` 上一个标签(**环形循环**,到末尾回开头);鼠标点标签切换、点 `✕` 关闭。 ```elisp (global-tab-line-mode t) (setq tab-line-tabs-function #'tab-line-tabs-fixed-window-buffers) (setq tab-line-close-button-show t) (defun +my/tab-cycle (direction) "在标签间环形切换。DIRECTION: 1=下一个, -1=上一个。" (let ((bufs (tab-line-tabs-fixed-window-buffers))) (when bufs (let* ((cur (current-buffer)) (pos (cl-position cur bufs :test #'eq)) (idx (mod (+ (or pos -1) direction) (length bufs)))) (switch-to-buffer (nth idx bufs)))))) (map! :leader "[" (cmd! (+my/tab-cycle -1)) "]" (cmd! (+my/tab-cycle +1))) ``` **两个易踩的坑(本配置已规避)**: 1. **不能用内置 `tab-line-switch-to-next/prev-tab` 绑键盘**:那两个是绑在 tab-line header-line 上左右箭头按钮的命令,需要鼠标 event 坐标定位 tab,键盘调用拿不到 event,等于无效。 2. **排序函数必须用 `...-fixed-window-buffers`**:默认的 `tab-line-tabs-window-buffers` 每次都把「当前 buffer」居中重排,切一下就左右对调,`cl-position` 定位漂移 → 只能在相邻两个标签间来回打转、循环不起来。`...-fixed-window-buffers` 保持切换顺序稳定、新 buffer 追加末尾。 > 区分两个概念:`tab-line`(文件标签,本项)、`tab-bar`(workspace/窗口布局的 tab)。本配置用的是前者。 --- ## 4. 退出免确认 - **作用**:退出不再弹「Really quit Emacs?」。 - **原理**:Doom 的 `:ui doom-quit` 把 `confirm-kill-emacs` 设成了随机吐槽确认函数,这里覆盖回 `nil`。 ```elisp (setq confirm-kill-emacs nil) ``` `C-x C-c` / `SPC q q` 现在直接退出。 --- ## 5. treemacs 递归搜索 **适用场景**:在 treemacs 里选中某个目录,就地递归搜它下面的文件。 | 键 | 作用 | |----|------| | `g f` | 从选中节点目录**递归找文件名**(consult-find / `fd`) | | `g s` | 从选中节点目录**递归 ripgrep 搜内容**(`+vertico/project-search`) | - 光标停在**目录节点**:搜该目录。 - 光标停在**文件节点**:自动用它的父目录。 **为什么不用改 init.el**:底层能力(vertico + consult + rg/fd)配置已备齐,这里只是把 consult 挂到 treemacs 键盘上。 **关键实现点**:调 consult 前先 `get-mru-window` 把焦点切回最近使用的编辑窗口。否则焦点还在 treemacs 专用窗口(side window),consult 会把候选文件灌进去、最终 `find-file` 无法就地显示而 split 出新窗口。切回编辑窗口后,打开结果表现为**新 tab** 而非**新窗口**。 ```elisp (after! treemacs-evil (defun +my/treemacs--jump-to-edit-window () ...) (defun +my/treemacs-search-file () ...) ; g f (defun +my/treemacs-search-content () ...) ; g s (map! :map evil-treemacs-state-map "g f" #'+my/treemacs-search-file "g s" #'+my/treemacs-search-content)) ``` --- ## 6. treemacs 打开终端(vterm) **适用场景**:在 treemacs 里选中某个目录,就地开个终端并 `cd` 到该目录。 | 键 | 作用 | |----|------| | `g t` | 在选中节点目录新开 vterm(cwd 即该目录) | - 光标停在**目录节点**:在该目录开 vterm。 - 光标停在**文件节点**:在其父目录开 vterm。 **为什么用 vterm、不用 eshell**:eshell 是纯 Elisp 解释器,跑 `git commit`(会拉起 `$EDITOR` 的 vim/nano)、`top`、交互式 `less` 等需要原始终端模式的程序会乱或卡。vterm 是 libvterm 驱动的**真终端**,跑这些都正常。 **cd 的实现**:复用 vterm 新建 buffer 时以 buffer-local 的 `default-directory` 作为 shell 初始 cwd 的特性,用 `let` 绑到目标目录再建 vterm 即 cd 过去(与 doom 自带 `+vterm/here` 同一原理)。buffer 名带上目录名 `*vterm:<目录>*`,多个终端一眼可辨;同目录重开复用同一 buffer。 **落在标签里(而非新开窗口)**:Doom 的 `:term vterm` 模块给所有 `^\*vterm` buffer 配了 popup 规则(`set-popup-rule! "^\\*vterm" ...`),裸调 `(vterm NAME)` 内部走 `pop-to-buffer-same-window` 会被拦截成 side-window 新窗口。这里学 Doom 官方 `+vterm/here` 的写法,用 `let (display-buffer-alist)` 临时清空 popup 规则,让 vterm 在当前编辑窗口打开 → 成为 tab-line 上的一个标签,可被 `SPC [` / `SPC ]` 切换。 ```elisp (after! treemacs-evil (defun +my/treemacs-open-vterm () (interactive) (let* ((path (treemacs--nearest-path (treemacs-current-button))) (dir (if (file-directory-p path) path (file-name-directory path))) (label (file-name-nondirectory (directory-file-name dir)))) (+my/treemacs--jump-to-edit-window) (let ((default-directory (file-name-as-directory dir))) (require 'vterm) (let (display-buffer-alist) (vterm (format "*vterm:%s*" label)))))) (map! :map evil-treemacs-state-map "g t" #'+my/treemacs-open-vterm)) ``` **启用前提**:vterm 是 Doom 的 `:term vterm` 模块,需在 `init.el` 里启用 + `doom sync`(会拉 emacs-libvterm 并 cmake 编译出 `vterm-module.so`)。本配置已在 `init.el` 启用,并已手动把索引仓库 origin 对齐到 gitclone 镜像、提前 clone 好 emacs-libvterm(落后于 pin 的 commit)、编译好 `vterm-module.so`,故首次 `g t` 不再卡交互。 > vterm 在 vim 里是 insert-mode,`C-c` 之外按 ``/`jk` 会从 evil 退回 vterm 的 normal(char)模式,用 `C-\` 或 vterm 内建键位;想发字面键位用 `C-c C-q` 之类(doom 绑的是 `C-q` = `vterm-send-next-key`)。 > 常见坑 1:`(eshell 1)` 里的 `1` 是**数字**前缀,会被当成「会话编号」→ `*eshell*<1>`,第一次创建后一直复用同一个 buffer(所以只有第一次有效)。必须传 `t`(非数字)才走新建分支。 > > 常见坑 2:`lexical-binding: t` 下 `let` 绑定一个 defcustom/defvar 声明的动态变量,需先 `(defvar eshell-buffer-name)` 声明为 dynamic(正是 eshell.el 自己的做法),否则绑定失效 + 告警。 --- ## 7. jk 快键退出(insert → normal) **对应 nvim 里「insert 态连按 j k 等价于 Esc」**。 - **作用**:insert / replace 态里 **0.15s 内连按 `j k`** → 回到 normal 态,无需够到 Esc。 - **原理**:Doom 的 evil 模块内置 `evil-escape` 包,靠 `evil-escape-key-sequence` 变量定义连击序列。该变量在 pre-command-hook 里被运行时读取,所以加载后再 setq 即生效。 - **`C-g` 退出仍保留**(不变)。 - treemacs / vterm / ghostel 等已在 `evil-escape-excluded-major-modes` 中排除,不受影响。 - 零新依赖,不需要 `doom sync`。 ```elisp (after! evil-escape (setq evil-escape-key-sequence (kbd "jk") evil-escape-delay 0.5)) ; 连击窗口放宽到 0.5s ``` **可调参数**: | 想改 | 写法 | |---|---| | 换成 `fd` 等其它序列 | `(setq evil-escape-key-sequence (kbd "fd"))` | | 调连击时限(秒) | `(setq evil-escape-delay 0.5)` | | 让 `kj` 也生效(无序) | `(setq evil-escape-unordered-key-sequence t)` | > 代价:insert 态想真的敲出 `jk` 俩字母时需间隔 >0.15s,或用其它序列(这是所有 jk-esc 方案的共同代价)。 --- ## 8. 数字窗口切换(window-select +numbers) **作用**:给每个窗口标一个数字字母,按 `SPC 1` 直接跳到对应编号的窗口,无需循环切窗或记字母。 - **原理**:`:ui window-select` 模块的 `+numbers` flag,让 `ace-window` 用**数字**标注窗口,并自动绑定 `SPC 0`~`SPC 9`。 - **与 config.el 里 ace-window 增强块互补**:`+numbers` 新增 `SPC 1`~`9` 直接跳窗;而 `config.el` 里 `(after! ace-window ...)` 那段(把 `C-w C-w` / `C-w w` 也改绑到 ace-window、`aw-dispatch-when-more-than` 降到 0)仍保留,保证evil 的换窗键也弹数字标注。两者叠加生效。 ```elisp ;; init.el (window-select +numbers) ; visually switch windows ``` - **注意**:这是 `init.el` 改动(模块 flag),**必须 `doom sync`** 才生效,与 config.el 的「改完即生效」不同。 --- ## 9. C-s 保存文件 **对应常见编辑器「Ctrl+S 保存」的肌肉记忆**。 - **作用**:normal / insert 态按 `C-s` 立即保存当前文件。 - **原理**:用 `basic-save-buffer`(与 Doom `SPC f s` 同款,有只读判断),覆盖 Emacs 默认的 `isearch-forward` 增量搜索。 - **搜索能力不受损**:normal 态 `/`(evil-ex-search)和 `SPC s` / `SPC /` 仍可用。 ```elisp (map! :n "C-s" #'basic-save-buffer :i "C-s" #'basic-save-buffer) ``` > 只绑 normal + insert 两态;visual 态 `C-s` 保留 isearch;minibuffer 里的 `C-s`(consult-history)走 `minibuffer-local-map`,不受影响。 --- ## 10. 跳转历史返回 / 前进(g d 之后) **`g d` 跳到定义后,如何原路返回 / 再前进。** | 键 | 作用 | |----|------| | `g d` | 跳转到定义(`+lookup/definition`) | | `C-o` | 返回上一个位置(`better-jumper-jump-backward`) | | `C-i` | 前进到下一个位置(`better-jumper-jump-forward`) | **原理**:Doom 用 `better-jumper` 接管跳转历史,`g d`(走 xref / eglot)跳转前会把当前位置压进跳转栈;`C-o` / `C-i` 沿栈来回走。 ```elisp (map! :m [C-i] #'better-jumper-jump-forward) ``` **踩坑**:终端里 `C-i` 与 `TAB` 是同一个键(ASCII 9)。Doom 的 `default` 模块把 normal 态 `TAB` 绑成 smart-tab(`[tab]`,在 evil 的 `[C-i] → evil-jump-forward` 之后加载),把「前进」覆盖成了缩进/折叠。上面这行显式把 `C-i` 绑回 `better-jumper-jump-forward` 恢复对称。 > `C-o` 返回本身是 evil 默认行为 + Doom remap,无需额外绑定;`C-i` 因被 TAB 抢占才需上面那行显式恢复。 --- ## 11. C/C++ 补全与跳转(clangd + eglot) **芯片 BSP 开发的 C/C++ 智能感知。** - **后端链路**:`(cc +lsp)` → `:tools lsp +eglot` → 用 Emacs 内置 eglot 驱动 **clangd**,读取项目的 [`compile_commands.json`](https://clang.llvm.org/docs/JSONCompilationDatabase.html)。 - **启用模块**(`init.el`):`(cc +lsp)`、`(lsp +eglot)`,已 `doom sync`。 **clangd 自动识别**:clangd 从被打开的文件目录**向上递归**找 `compile_commands.json`(或 `.clangd`),无需手动配置。chips 项目里 `CMakeLists.txt` 启用 `CMAKE_EXPORT_COMPILE_COMMANDS=ON` 即可。 **项目级补充**(`~/chip_vi/.clangd`):当 `compile_commands.json` 的头文件搜索路径不全时(如漏了平台 hal 子目录),在该文件里补 `CompileFlags.Add`: ```yaml CompileFlags: Add: - -I/home/jian.ning/chip_vi/include/hal/GUA100 ``` > 该 `.clangd` 是**项目级**文件(clangd 会从文件目录向上找它),独立于本 `~/.config/doom` 仓库,不在这里面维护。 --- ## 12. 终端补全弹窗(corfu-terminal,Emacs 30 only) **解决问题:Emacs 30.x 纯终端下,C/C++ 补全候选「不就地弹出」。** - **现象**:corfu 弹窗开关是 `(or (display-graphic-p) (featurep 'tty-child-frames))`。Emacs 30 纯 TTY 下 `display-graphic-p` = nil;`tty-child-frames` 是 Emacs 31 新特性 → corfu 判定「不支持弹窗」,退回原生 `*Completions*` buffer,看起来像「补全完全不生效」。 - **解决**:在 Emacs 30 上单独启用 `corfu-terminal`(用 `popon` 在终端里渲染候选菜单),**不引整个 `:os tty` 模块**。 ```elisp ;; packages.el —— 单独声明 (repo 在 codeberg.org, 非 github) (package! corfu-terminal :recipe (:host nil :repo "https://codeberg.org/akib/emacs-corfu-terminal") :pin "501548c3d51f926c687e8cd838c5865ec45d03cc") (package! popon :recipe (:host nil :repo "https://codeberg.org/akib/emacs-popon") :pin "bf8174cb7e6e8fe0fe91afe6b01b6562c4dc39da") ;; config.el —— 仅 Emacs < 31 启用 (31 自带 tty-child-frames, 不再需要) (after! corfu (unless (featurep 'tty-child-frames) (corfu-terminal-mode +1))) ``` - **兼容策略**:packages.el 始终声明这两个包(保证 Emacs 30 能装上),config.el 用 `(featurep 'tty-child-frames)` 守护 `corfu-terminal-mode`——Emacs 30 上调 mode → 触发 autoload → corfu-terminal.el 加载 → 终端弹窗生效;Emacs 31 上不调 mode → corfu-terminal.el 不 load → 无 warning(corfu 2.13+ 会在 corfu-terminal.el 加载时主动 warn "not needed on Emacs 31")。 - **注意**:这是 `packages.el` 改动,**必须 `doom sync`**;完成后需**完全重启** Emacs 生效。 > **为什么不直接启用 `:os tty`**:Doom 默认只在 `:os tty` 模块启用时才门控声明 `corfu-terminal`,看似最省事。但它自带的两种剪贴板方案在本环境(SSH + tmux,远程 Linux 无 `DISPLAY`)都不可用——`xclip` 会掐掉内置 `term/xterm` 的 OSC 52 且写不进无显示的 X 剪贴板;`+osc` 的 `clipetty` 则直接往 `SSH_TTY` 指向的 `/dev/pts/N` 写字节,在 tmux 嵌套下 `Permission denied`。因此只取 `corfu-terminal`,剪贴板仍走第 2 项 config.el 的内置 OSC 52。 --- ## 13. AI 代码补全(minuet + DeepSeek FIM) **解决问题:在 Emacs 里写代码时像 Copilot 一样有 AI 实时补全(幽灵文本),不需要切到 chat 面板。** - **后端**:`minuet` 插件(MELPA 包,源仓 `milanglacier/minuet-ai.el`),走 **DeepSeek 的 OpenAI FIM 兼容端点** `https://api.deepseek.com/beta/completions`(FIM = fill-in-middle,光标左右各看一段上下文,比 chat 模式省 token 且适合内联补全)。 - **渲染**:minuet 默认用 corfu 的 child-frame 弹层显示幽灵文本,所以本配置节流参数(debounce 0.6s + throttle 1.5s)是在「少花 token + 少打 API」和「补全跟手感」之间取的折中。 - **API key**:从 `~/.bashrc` export 的 `DEEPSEEK_API_KEY` 读取,**不硬编码**(跟第 15 项 gptel 同模式)。注意 minuet 的 `:api-key` slot 是「env var 名字」而不是 key 本身(minuet 内部 `minuet--get-api-key` 会调 `(getenv ...)`),所以配置里直接写字符串 `"DEEPSEEK_API_KEY"`,不要包 `(getenv ...)`。bashrc 里加: ```bash export DEEPSEEK_API_KEY=sk-... ``` ```elisp ;; packages.el —— 启用 minuet (MELPA) (package! minuet) ;; config.el —— 配置 DeepSeek FIM 后端 (after! minuet (setq minuet-provider 'openai-fim-compatible) (setq minuet-auto-suggestion-throttle-delay 1.5) (setq minuet-auto-suggestion-debounce-delay 0.6) (plist-put minuet-openai-fim-compatible-options :end-point "https://api.deepseek.com/beta/completions") (plist-put minuet-openai-fim-compatible-options :api-key "DEEPSEEK_API_KEY") (plist-put minuet-openai-fim-compatible-options :model "deepseek-v4-flash") (minuet-set-optional-options minuet-openai-fim-compatible-options :max_tokens 56) (minuet-set-optional-options minuet-openai-fim-compatible-options :top_p 0.9)) ``` - **模型 `deepseek-v4-flash`**:私有/自部署模型名,按需替换为 DeepSeek 公网模型(如 `deepseek-coder` / `deepseek-chat`)。 - **使用**:进入编辑态就会自动出现补全,按 `M-x minuet-complete` 手动触发,`TAB` 接受补全(具体按键参考 minuet 文档)。 - **注意**:这是 `packages.el` 改动,**必须 `doom sync`**;完成后需**完全重启** Emacs 生效。 --- ## 14. treemacs 根目录固定启动目录 **解决问题:项目里有很多子仓库时,treemacs 一进文件就「切到当前 git 仓库」、看不到别的仓库/目录。** - **现象**:`SPC o p` 打开 treemacs,根目录不是启动 Emacs 的目录,而是「当前 buffer 所在的那个 git 仓库根」。多子仓库大项目里切几个文件,treemacs 就只剩当前子仓库。 - **根因**:Doom 的 `SPC o p` → `+treemacs/toggle` → `treemacs-add-and-display-current-project-exclusively`。这个 `exclusively`(排他)函数每次**清空 workspace**、只留「当前项目」一个根;而「当前项目」按 projectile → `project.el` → cwd 判定,光标一进子仓库文件就定位成那个子仓库的 git 根。 - **解决**:把 `SPC o p` 改绑到自定义命令 `+my/treemacs-toggle-at-startup`,根目录**恒为** `command-line-default-directory`(Emacs 内置变量,记录启动时的 cwd),永不随当前 buffer 切走。 ```elisp (after! treemacs (defun +my/treemacs-toggle-at-startup () "以启动 Emacs 的目录为唯一根打开/切换 treemacs。" (interactive) (pcase (treemacs-current-visibility) (`visible (delete-window (treemacs-get-local-window))) (_ (cl-letf (((symbol-function 'treemacs--find-current-user-project) (lambda () (file-truename (or command-line-default-directory default-directory))))) (treemacs-add-and-display-current-project-exclusively))))) (map! :leader "o p" #'+my/treemacs-toggle-at-startup)) ``` - **实现要点**:用 `cl-letf` 把 `treemacs--find-current-user-project` 临时覆盖成「返回启动目录」,复用 exclusive 函数完整的「清空 / 展开根 / 成功脉冲」逻辑,只把「找根」这一步换成固定目录。 - **toggle 语义保留**:treemacs 已可见时 `SPC o p` 仍负责关闭(复刻 `+treemacs/toggle` 的 `visible → delete-window` 分支)。 - **注意**:这是 `config.el` 改动,**无需 `doom sync`**,重启 Emacs 或 `M-x doom/reload` 生效。 > 若偶尔想看「当前子仓库」这一层,可临时 `SPC o P`(`treemacs-find-file`)定位当前文件,或用 `M-x treemacs-display-current-project-exclusively` 手动切;本配置的 `SPC o p` 始终保持启动目录不同步。 --- ## 15. AI 对话 / 代码改写(gptel) **解决问题:在 Emacs 里直接对话 LLM、改写/解释代码、生成提交信息。** - **后端链**:`:tools llm` 模块(gptel)+ 公司 horizon 的 OpenAI 兼容端点 `https://llmapi.horizon.auto/v1/chat/completions`,模型 `HORIZON-DeepSeek-Pro`。 - **key / model 走环境变量**(`~/.bashrc` 已 export,不硬编码):`OPENAI_API_KEY` → key,`OPENAI_MODEL` → 模型名。 **键位**: | 键 | 作用 | |----|------| | `SPC o l l` | 打开对话 buffer | | `SPC o l s` | 发送文本/选区给 LLM | | `SPC o l r` | 改写/重构选中代码 | | `SPC o l e` | 解释选中项(浮层/回声区,**不写进文件**) | | `SPC o l f` | 把文件加进上下文 | | `SPC o l a` | 把文本加进上下文 | | `SPC o l m` | gptel 配置菜单(换 model/backend/指令) | | `M-g`(magit commit) | 生成提交信息 | **对话 buffer 操作**:`SPC o l l` 打开后是 org-mode,**打字 → `C-c RET` 发送**,回复流式显示在下方;`C-g` 中断。往上下文加内容用 `SPC o l f` / `SPC o l a`。 **`SPC o l e` 的输出去向(重要)**:`gptel-quick` 的输出**永远不写进代码文件**——GUI 下是光标旁 posframe 浮层,终端下走 `message` 显示在底部回声区,10 秒后自动消失。显示消失前可 `C-g` 关闭、`+` 要更长解释、`M-RET` 转成常驻对话 buffer 继续追问。 **实现**(config.el,`:tools llm` 已在 init.el 启用 + `doom sync`): ```elisp (after! gptel (setq gptel-backend (gptel-make-openai "Horizon" :host "llmapi.horizon.auto" :key (or (getenv "OPENAI_API_KEY") "") :models (list (intern (or (getenv "OPENAI_MODEL") "HORIZON-DeepSeek-Pro"))) :stream t)) (setq gptel-model (intern (or (getenv "OPENAI_MODEL") "HORIZON-DeepSeek-Pro")))) ``` - **注意**:`OPENAI_API_KEY`/`OPENAI_MODEL` 必须出现在 Emacs 进程的环境里——终端里 `emacs` 启动(读 `~/.bashrc`)没问题;daemon/systemd 启动读不到,需显式注入。 > **clone 源**:gptel / gptel-quick / gptel-magit 的 github 源在 gitclone 镜像下 502,packages.el 已改走 `ghfast.top` 反代(`ob-gptel` 在 github 直连可达,不动)。 --- ## treemacs 键位速查 | 键 | 作用 | |----|------| | `g f` | 递归找文件名 | | `g s` | 递归搜内容 | | `g t` | 开 vterm(已 cd,落在标签里) | --- ## 附:配置生效方式 - **`config.el` 改动** → 无需 `doom sync`,重启 Emacs 或 `M-x doom/reload`。 - **`init.el` / `packages.el` 改动** → 必须 `doom sync`。 ## 附:关于「国内源」 Doom 用 **straight.el** 管包,不走 Emacs 内置 `package.el`,所以 `package-archives`(TUNA/清华 ELPA tarball 镜像等)**对 Doom 无效**。straight 是**直接 `git clone`** 每个包。 在 straight 体系里: - `melpa` 不是「下载源」,是一个 **recipe 索引仓库**(git 仓库,记录「包名 → 真实 git URL」)。 - 「国内源」的正确形态是 **git 镜像**:`packages.el` 已把 straight 的 `github` host 模板重写为 `gitclone.com/github.com/...`。 - 它严格只重写**代码仓库**;`el-get`/`gnu-elpa-mirror`/`emacsmirror-mirror`/`nongnu-elpa` 等索引仓库因历史老 origin 是 github 直连,会触发 straight 的一致性校验弹交互菜单(后台/非交互下卡死)。本配置已把这些索引仓库的 `.git/config` origin 手动对齐到 gitclone 消除冲突。 > 环境:Emacs 30.1 + Doom Emacs,SSH 远程 (Windows 本地 + tmux)。本配置中「终端专用」项(滚轮、剪贴板)只在非 GUI 时生效,GUI 下不影响。