{"id":14459429,"url":"https://github.com/opsnull/emacs","last_synced_at":"2025-03-23T11:14:24.115Z","repository":{"id":160241779,"uuid":"266672067","full_name":"opsnull/emacs","owner":"opsnull","description":null,"archived":false,"fork":false,"pushed_at":"2025-01-12T04:55:11.000Z","size":4813,"stargazers_count":14,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-28T17:26:48.726Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Emacs Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/opsnull.png","metadata":{"files":{"readme":"README.org","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-05-25T03:12:55.000Z","updated_at":"2025-01-12T04:55:15.000Z","dependencies_parsed_at":"2024-04-13T14:51:35.256Z","dependency_job_id":"f3daba88-bbf1-483c-90eb-e321cefbbb2b","html_url":"https://github.com/opsnull/emacs","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opsnull%2Femacs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opsnull%2Femacs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opsnull%2Femacs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opsnull%2Femacs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/opsnull","download_url":"https://codeload.github.com/opsnull/emacs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245090875,"owners_count":20559298,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-09-01T20:01:59.705Z","updated_at":"2025-03-23T11:14:24.083Z","avatar_url":"https://github.com/opsnull.png","language":"Emacs Lisp","readme":"#+Title: My Emacs Dotfile\n#+DATE: 2023-08-20 Sun\n#+LASTMOD: 2025-01-05T20:01:32+0800\n#+AUTHOR: 张俊(geekard@qq.com)\n#+STARTUP: overview hideblocks\n#+PROPERTY: header-args:emacs-lisp :tangle yes :eval no :results silent :exports code\n#+HUGO_BASE_DIR: ~/blog/blog.opsnull.com\n#+HUGO_SECTION: emacs\n#+HUGO_BUNDLE: my-emacs-dotfile\n#+EXPORT_FILE_NAME: index\n#+HUGO_AUTO_SET_LASTMOD: t\n#+HUGO_TAGS: emacs\n#+HUGO_LOCALE: zh\n#+OPTIONS: title:t prop:t ^:nil\n#+HUGO_WEIGHT: -1\n\nEmacs 是 Hacker 的一种生活方式，而这是符合我口味的私人定制。\n\n#+hugo: more\n\n更多 Emacs 内容请访问[[https://blog.opsnull.com/emacs/][我的博客]]。\n\n#+ATTR_HTML: :width 400 :align center\n[[file:static/images/2024-09-03_10-55-30_screenshot.png]]\n\n* 编译安装\n\n从源码编译、安装最新 Emacs 30 版本:\n\n#+begin_src bash :tangle ~/.emacs.d/init.sh\nbrew uninstall emacs-plus@30\nbrew install emacs-plus@30 --HEAD --with-xwidgets --with-imagemagick --with-dragon-icon --with-native-comp\nbrew unlink emacs-plus@30 \u0026\u0026 brew link emacs-plus@30\nln -sf /opt/homebrew/opt/emacs-plus@30/Emacs.app /Applications/\n#+end_src\n\n* 初始化\n\n安装 native 编译所需的 gcc 工具链：\n\n#+begin_src bash :tangle ~/.emacs.d/init.sh\nbrew install gcc\ncurrent_gcc=\"/opt/homebrew/opt/gcc/lib/gcc/current\"\nexport LIBRARY_PATH=\"${LIBRARY_PATH}:${current_gcc}:${current_gcc}/gcc/aarch64-apple-darwin23/14/\"\n#+end_src\n\n=early-init.el= 是 Emacs 启动早期执行的第一个初始化文件：\n\n#+begin_src emacs-lisp :tangle ~/.emacs.d/early-init.el\n;; 个人信息。\n(setq user-full-name \"zhangjun\")\n(setq user-mail-address \"geekard@qq.com\")\n\n;; native 编译。\n(when (fboundp 'native-compile-async)\n  (setenv \"LIBRARY_PATH\"\n\t  (concat (getenv \"LIBRARY_PATH\")\n\t\t  \":/opt/homebrew/opt/gcc/lib/gcc/current/\"\n\t\t  \":/opt/homebrew/opt/gcc/lib/gcc/current/gcc/aarch64-apple-darwin23/14/\"))\n  (setq native-comp-speed 4)\n  (setq native-comp-async-jobs-number 8)\n  ;;(setq inhibit-automatic-native-compilation t)\n  (setq native-comp-async-report-warnings-errors 'silent))\n\n;; 解决 cmake 和 Xcode 15.0 兼容性问题，否则后续编译 vterm-module 时报错。\n;;; 查看当前 Xcode SDK 路径：xcrun --sdk macosx --show-sdk-path\n(setenv \"SDKROOT\" \"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk\")\n\n;; 加载较新的 .el 文件。\n(setq-default load-prefer-newer t)\n(setq-default lexical-binding t)\n(setq lexical-binding t)\n\n;; 在独立文件保存 Emacs 自动写入的配置参数，避免污染 ~/.emacs 文件。\n(setq custom-file (expand-file-name \"~/.emacs.d/custom.el\"))\n(add-hook 'after-init-hook\n\t  (lambda ()\n\t    (when (file-exists-p custom-file)\n\t      (load custom-file))))\n\n;; 非系统标准路径的二进制目录列表（优先查找靠后目录）\n(setq my-bin-path\n      '(\"/opt/homebrew/bin\"\n        \"/opt/homebrew/opt/findutils/libexec/gnubin\"\n        \"/opt/homebrew/opt/openjdk/bin\"\n        \"/Users/alizj/go/bin\"\n\t;; .cargo/bin 的优先级要高于 rustup/bin，这样优先使用编译安装的命令。\n\t\"/opt/homebrew/opt/rustup/bin\"\n        \"/Users/alizj/.cargo/bin\"))\n\n;; 添加到 PATH 环境变量。\n(mapc (lambda (p)\n\t(setenv \"PATH\" (concat p \":\" (getenv \"PATH\"))))\n      my-bin-path)\n\n;; 添加到 Emacs 搜索路径：Emacs 查找外部程序时使用 `exec-path` 而非 `PATH` 变量。\n(let ((paths my-bin-path))\n  (dolist (path paths)\n    (setq exec-path (cons path exec-path))))\n#+end_src\n\n设置软件包源：\n+ =M-x use-package-report= : 查看包加载时间（按 S 排序）；\n+ =M-x package-vc-upgrade-all= ：更新使用 ~:vc~ 指令配置的，从 ~github~ 安装的包；\n\n#+begin_src emacs-lisp\n(require 'package)\n(setq package-archives\n      '((\"elpa\" . \"http://mirrors.tuna.tsinghua.edu.cn/elpa/gnu/\")\n        (\"elpa-devel\" . \"http://mirrors.tuna.tsinghua.edu.cn/elpa/gnu-devel/\")\n        (\"melpa\" . \"http://mirrors.tuna.tsinghua.edu.cn/elpa/melpa/\")\n        (\"nongnu\" . \"http://mirrors.tuna.tsinghua.edu.cn/elpa/nongnu/\")\n        (\"nongnu-devel\" . \"http://mirrors.tuna.tsinghua.edu.cn/elpa/nongnu-devel/\")))\n(package-initialize)\n(when (not package-archive-contents)\n  (package-refresh-contents))\n\n(setq use-package-verbose t)\n(setq use-package-always-ensure t)\n(setq use-package-always-demand t)\n(setq use-package-compute-statistics t)\n(setq use-package-vc-prefer-newest t)\n\n;; 允许升级 Emacs 内置的包。\n;;(setq package-install-upgrade-built-in t)\n#+end_src\n\n设置 GPG 加解密：\n\n#+begin_src emacs-lisp\n(setq auth-sources '(\"~/.authinfo.gpg\"))\n;;(setq auth-source-debug t)\n\n(use-package epa\n  :config\n  (setq-default\n   ;; 缺省使用 email 地址加密。\n   epa-file-encrypt-to user-mail-address\n   ;; 使用 minibuffer 输入 GPG 密码。\n   epa-pinentry-mode 'loopback)\n\n  (require 'epa-file)\n  (epa-file-enable))\n#+end_src\n\n按键调整：\n\n#+begin_src emacs-lisp\n;; command 作为 Meta 键。\n(setq mac-command-modifier 'meta)\n\n;; option 作为 Super 键。\n(setq mac-option-modifier 'super)\n\n;; fn 作为 Hyper 键。\n(setq ns-function-modifier 'hyper)\n\n;; 关闭容易误操作的按键。\n;; s- 表示 Super，S- 表示 Shift, H- 表示 Hyper:\n(let ((keys '(\n              \"s-w\"\n              \"C-z\"\n              \"\u003cmouse-2\u003e\"\n              \"s-k\"\n              \"s-,\"\n              \"s-.\"\n              \"s--\"\n              \"s-+\"\n              \"C-\u003cwheel-down\u003e\"\n              \"C-\u003cwheel-up\u003e\"\n              \"C-M-\u003cwheel-down\u003e\"\n              \"C-M-\u003cwheel-up\u003e\"\n              ;;\"\u003cdown-mouse-1\u003e\"\n              ;;\"\u003cdrag-mouse-1\u003e\"\n              )))\n  (dolist (key keys)\n    (global-unset-key (kbd key))))\n#+end_src\n\n提升 IO 性能，参考 [[https://github.com/hlissner/doom-emacs/blob/develop/core/core.el][doom core.el]] ：\n\n#+begin_src emacs-lisp\n(setq process-adaptive-read-buffering nil)\n(setq read-process-output-max (* 1024 1024 4))\n\n(setq inhibit-compacting-font-caches t)\n(setq-default message-log-max t)\n\n;; Garbage Collector Magic Hack, 提升 GC 性能。\n(use-package gcmh\n  :init\n  ;;(setq gcmh-verbose t)\n  (setq gcmh-idle-delay 'auto) ;; 缺省 15s\n  (setq gcmh-auto-idle-delay-factor 10)\n  (setq gcmh-high-cons-threshold (* 32 1024 1024))\n  (gcmh-mode 1)\n  (gcmh-set-high-threshold))\n\n;;(setq garbage-collection-messages t)\n(add-hook 'after-init-hook #'garbage-collect t)\n#+end_src\n\n* 代理\n\n~MacOS~ 自带的 ~curl~ 不支持 ~socks5~ 代理, 这里安装支持 ~socks5~ 的 ~GNU curl~ 版本:\n\n#+begin_src bash :tangle ~/.emacs.d/init.sh\nbrew install curl\nexport PATH=\"/opt/homebrew/opt/curl/bin:$PATH\"\n#+end_src\n\n将 ~GNU curl~ 添加到 ~Emacs~ 的 ~PATH~ 环境变量和 ~exec-path~ 变量中：\n\n#+begin_src emacs-lisp\n(setq my-coreutils-path \"/opt/homebrew/opt/curl/bin/\")\n(setenv \"PATH\" (concat my-coreutils-path \":\" (getenv \"PATH\")))\n(setq exec-path (cons my-coreutils-path  exec-path))\n#+end_src\n\n~Emacs~ 使用 ~url-retrieve~ 访问 URL，这里设置它使用 ~curl~ 后端，这样全局可使用 socks5 代理：\n\n#+begin_src emacs-lisp\n;; socks5 代理信息。\n(setq my/socks-host \"127.0.0.1\")\n(setq my/socks-port 1080)\n(setq my/socks-proxy (format \"socks5h://%s:%d\" my/socks-host my/socks-port))\n\n;; 不经过 socks 代理的 CIDR 或域名列表, 需要同时满足 socks-noproxy 和 NO_RROXY 值要求:\n;; + socks-noproxy: 域名是正则表达式, 如 \\\\.baidu.com;\n;; + NO_PROXY: 域名支持 *.baidu.com 或 baidu.com;\n;; 所以这里使用的是同时满足两者的域名后缀形式, 如 .baidu.com;\n(setq my/no-proxy\n      '(\n        \"127.0.0.1/32\"\n        \"10.0.0.0/8\"\n        \"172.0.0.0/8\"\n        \"0.0.0.0/32\"\n        \"localhost\"\n        \"192.168.0.0/16\"\n        \".cn\"\n        \".alibaba-inc.com\"\n        \".taobao.com\"\n        \".antfin-inc.com\"\n        \".openai.azure.com\"\n        \".baidu.com\"\n        \".aliyun-inc.com\"\n        \".aliyun-inc.test\"\n        ))\n\n(setq my/user-agent\n      \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36\")\n\n(use-package mb-url-http\n  :demand\n  :vc (:url \"https://github.com/dochang/mb-url\")\n  :init\n  (require 'auth-source)\n  (let ((credential (auth-source-user-and-password \"api.github.com\")))\n    (setq github-user (car credential)\n          github-password (cadr credential))\n    (setq github-auth (concat github-user \":\" github-password))\n    (setq mb-url-http-backend 'mb-url-http-curl\n          mb-url-http-curl-program \"/opt/homebrew/opt/curl/bin/curl\"\n          mb-url-http-curl-switches\n          `(\"-k\"\n            \"-x\" ,my/socks-proxy\n            \"--keepalive-time\" \"60\"\n            \"--keepalive\"\n            \"--max-time\" \"300\"\n            ;;防止 POST 超过 1024 Bytes 时发送 `Expect: 100-continue` 导致 1s 延迟。\n            \"-H\" \"Expect: ''\"\n            ;;\"-u\" ,github-auth\n            \"--user-agent\" ,my/user-agent\n            ))))\n\n;; 开启 socks5 代理。\n(defun proxy-socks-enable ()\n  (interactive)\n  (require 'socks)\n  (setq url-gateway-method 'socks\n        socks-noproxy my/no-proxy\n        socks-server `(\"Default server\" ,my/socks-host ,my/socks-port 5))\n  (let ((no-proxy (mapconcat 'identity my/no-proxy \",\")))\n    (setenv \"no_proxy\" no-proxy))\n  (setenv \"ALL_PROXY\" my/socks-proxy)\n  (setenv \"ALL_PROXY\" my/socks-proxy)\n  (setenv \"HTTP_PROXY\" nil)\n  (setenv \"HTTPS_PROXY\" nil)\n  (advice-add 'url-http :around 'mb-url-http-around-advice))\n\n;; 关闭 socks5 代理。\n(defun proxy-socks-disable ()\n  (interactive)\n  (require 'socks)\n  (setq url-gateway-method 'native socks-noproxy nil)\n  (setenv \"all_proxy\" \"\")\n  (setenv \"ALL_PROXY\" \"\"))\n\n;; 默认启动时开启 socks5 代理。\n(proxy-socks-enable)\n#+end_src\n\n* 界面\n\n关闭部分 UI 元素：\n\n#+begin_src emacs-lisp\n(when (memq window-system '(mac ns x))\n  (tool-bar-mode -1)\n  (scroll-bar-mode -1)\n  (menu-bar-mode -1)\n  (setq use-file-dialog nil)\n  (setq use-dialog-box nil))\n#+end_src\n\n光标和行号：\n\n#+begin_src emacs-lisp\n;; 高亮当前行。\n(global-hl-line-mode t)\n(setq global-hl-line-sticky-flag t)\n\n;; 显示行号。\n(global-display-line-numbers-mode t)\n\n;; 设置光标样式。\n(setq-default cursor-type 'bar)\n\n;; 光标和字符宽度一致（如 TAB)。\n(setq x-stretch-cursor t)\n#+end_src\n\nFrame 设置：\n\n#+begin_src emacs-lisp\n;; frame 边角样式：undecorated, round corner: undecorated-round\n(add-to-list 'default-frame-alist '(undecorated . t))\n(add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))\n(add-to-list 'default-frame-alist '(selected-frame) 'name nil)\n(add-to-list 'default-frame-alist '(ns-appearance . dark))\n;; 新建 frame window 的大小。\n(add-to-list 'default-frame-alist '(height . 24))\n(add-to-list 'default-frame-alist '(width . 80))\n\n;; 不在新 frame 打开文件（如 Finder 的 \"Open with Emacs\") 。\n(setq ns-pop-up-frames nil)\n\n;; 复用当前 frame。\n(setq display-buffer-reuse-frames t)\n(setq frame-resize-pixelwise t)\n\n;; 30: 左右分屏, nil: 上下分屏。\n(setq split-width-threshold nil)\n\n;; 刷新显示。\n(global-set-key (kbd \"\u003cf5\u003e\") #'redraw-display)\n\n(setq switch-to-buffer-obey-display-actions t)\n\n;; 在 frame 底部显示的窗口列表。\n(add-to-list\n 'display-buffer-alist\n `((,(regexp-opt\n      '(\"\\\\*compilation\\\\*\"\n        \"\\\\*Apropos\\\\*\"\n        \"\\\\*Help\\\\*\"\n        \"\\\\*helpful\"\n        \"\\\\*info\\\\*\"\n        \"\\\\*Summary\\\\*\"\n        \"\\\\*vt\"\n        \"\\\\*lsp-bridge\"\n        \"\\\\*Org\"\n        \"\\\\*Google Translate\\\\*\"\n        \" \\\\*eglot\"\n        \"Shell Command Output\"))\n    ;; 复用同名 buffer 窗口。\n    (display-buffer-reuse-window\n     . (\n\t;; 在 frame 底部显示窗口。\n\t(side . bottom)\n\t;; 窗口高度比例。\n\t(window-height . 0.35)\n\t)))))\n\n;; 启动后显示模式，加 t 参数让 togg-frame-XX 最后运行，这样才生效：\n(add-hook 'window-setup-hook 'toggle-frame-maximized t) ;; toggle-frame-fullscreen\n#+end_src\n\n窗口和滚动：\n\n#+begin_src emacs-lisp\n;; 切换窗口。\n(global-set-key (kbd \"s-o\") #'other-window)\n\n(setq window-combination-resize t)\n\n;; 像素平滑滚动。\n(pixel-scroll-precision-mode t)\n(setq fast-but-imprecise-scrolling t)\n(setq scroll-conservatively 10\n      scroll-margin 2\n      scroll-preserve-screen-position t\n      mouse-wheel-scroll-amount '(2 ((shift) . hscroll))\n      mouse-wheel-scroll-amount-horizontal 2)\n#+end_src\n\ndashboard：\n\n#+begin_src emacs-lisp\n(use-package dashboard\n  :config\n  (dashboard-setup-startup-hook)\n  (setq-local global-hl-line-mode nil)\n  (setq dashboard-banner-logo-title \"Happy Hacking \u0026 Writing 🎯\")\n  (setq dashboard-projects-backend #'project-el)\n  (setq dashboard-center-content t)\n  (setq dashboard-set-heading-icons t)\n  (setq dashboard-set-navigator t)\n  (setq dashboard-set-file-icons t)\n  (setq dashboard-path-max-length 30)\n  ;; 显示 org-mode agenda。\n  (add-to-list 'dashboard-items '(agenda) t)\n  (setq dashboard-items '((recents . 20) (projects . 8) (agenda . 3))))\n#+end_src\n\ndoom-modeline：\n\n#+begin_src emacs-lisp\n;; 使用 Symbols Nerd Fonts Mono 在 modeline 上显示 icons，需要单独下载和安装该字体。\n(use-package nerd-icons)\n\n(use-package doom-modeline\n  :hook (after-init . doom-modeline-mode)\n  :custom\n  (doom-modeline-buffer-encoding nil)\n  (doom-modeline-env-version nil)\n  (doom-modeline-env-enable-rust nil)\n  (doom-modeline-env-enable-go nil)\n  (doom-modeline-buffer-file-name-style 'truncate-nil)\n  (doom-modeline-vcs-max-length 30)\n  (doom-modeline-github nil)\n  (doom-modeline-time-icon nil)\n  (doom-modeline-check-simple-format t)\n  :config\n  (display-battery-mode 0)\n  (column-number-mode t)\n  (display-time-mode t)\n  (setq display-time-24hr-format t)\n  (setq display-time-default-load-average nil)\n  (setq display-time-load-average-threshold 20)\n  (setq display-time-format \"%H:%M \") ;; 默认：\"%m/%d[%w]%H:%M \"\n  (setq indicate-buffer-boundaries (quote left)))\n\n;; 为 vterm-mode 定义简化的 modeline，避免 vterm buffer 内容过多时更新 modeline 影响性能。\n(doom-modeline-def-modeline 'my-vterm-modeline\n  '(buffer-info) ;; 左侧\n  '(misc-info minor-modes input-method)) ;; 右侧\n(add-to-list 'doom-modeline-mode-alist '(vterm-mode . my-vterm-modeline))\n#+end_src\n\ndired-sidebar：使用 dired 显示目录，相比 treemacs/neotree 的优势是速度快和使用 dired 按键：\n\n#+begin_src emacs-lisp\n(use-package vscode-icon\n  :commands (vscode-icon-for-file))\n\n(use-package dired-sidebar\n  :bind ((\"s-0\" . dired-sidebar-toggle-sidebar))\n  :commands (dired-sidebar-toggle-sidebar)\n  :init\n  (add-hook 'dired-sidebar-mode-hook\n            (lambda ()\n              (unless (file-remote-p default-directory)\n                (auto-revert-mode))))\n  :config\n  (push 'toggle-window-split dired-sidebar-toggle-hidden-commands)\n  (push 'rotate-windows dired-sidebar-toggle-hidden-commands)\n  (setq dired-sidebar-subtree-line-prefix \"-\")\n  (setq dired-sidebar-theme 'vscode) ;;'ascii\n  (setq dired-sidebar-use-term-integration t)\n  (setq dired-sidebar-use-one-instance t)\n  (setq dired-sidebar-use-custom-font t)\n  (setq dired-sidebar-icon-scale 0.1)\n  ;;(setq dired-sidebar-window-fixed nil) ;; 可以手动调整宽度和高度。\n  ;;(setq dired-sidebar-resize-on-open t)\n  (setq dired-sidebar-should-follow-file t)\n  (setq dired-sidebar-follow-file-idle-delay 0.5))\n#+end_src\n\n字体：英文 Iosevka/Sarasa 和中文 LxgwWenKai，按照 1:1 缩放，在偶数字号的情况下可以实现中英文等宽等高。\n+ 英文：[[https://github.com/protesilaos/iosevka-comfy][Iosevka Comfy]];\n+ 中文：霞鹜文楷屏幕阅读版 [[https://github.com/lxgw/LxgwWenKai-Screen/releases][LxgwWenKai-Screen]]，对字体做了加粗，便于屏幕阅读;\n\n常用字体命令:\n+ 查看 Emacs 支持的字体名称： =(print (font-family-list))=\n+ 查看光标处字体： =M-x describe-char=\n+ 查看 Emacs 支持的字体名称： =(print (font-family-list))=\n\n#+begin_src emacs-lisp\n(use-package fontaine\n  :config\n  (setq fontaine-latest-state-file (locate-user-emacs-file \"fontaine-latest-state.eld\"))\n  (setq fontaine-presets\n\t'((regular) ;; 使用缺省配置。\n\t  (t\n\t   :default-family \"Iosevka Comfy\"\n\t   :default-weight regular\n\t   :default-height 180 ;; 默认字号, 需要是偶数才能实现中英文等宽等高。\n\t   :fixed-pitch-family \"Iosevka Comfy\"\n\t   :fixed-pitch-weight nil\n\t   :fixed-pitch-height 1.0\n\t   :fixed-pitch-serif-family \"Iosevka Comfy\"\n\t   :fixed-pitch-serif-weight nil\n\t   :fixed-pitch-serif-height 1.0\n\t   :variable-pitch-family \"Iosevka Comfy Duo\"\n\t   :variable-pitch-weight nil\n\t   :variable-pitch-height 1.0\n\t   :line-spacing nil)))\n  (fontaine-mode 1)\n  (add-hook 'enable-theme-functions #'fontaine-apply-current-preset)\n  (fontaine-set-preset (or (fontaine-restore-latest-preset) 'regular))\n  (add-hook 'kill-emacs-hook #'fontaine-store-latest-preset))\n\n;; 设置 emoji/symbol 和中文字体。\n(defun my/set-font ()\n  (when window-system\n    (setq use-default-font-for-symbols nil)\n    (set-fontset-font t 'emoji (font-spec :family \"Apple Color Emoji\")) ;; Noto Color Emoji\n    (set-fontset-font t 'symbol (font-spec :family \"Symbola\")) ;; Apple Symbols, Symbola\n    (let ((font (frame-parameter nil 'font))\n\t  (font-spec (font-spec :family \"LXGW WenKai Screen\")))\n      (dolist (charset '(kana han hangul cjk-misc bopomofo))\n\t(set-fontset-font font charset font-spec)))))\n\n;; Emacs 启动后或 fontaine preset 切换时设置字体。\n(add-hook 'after-init-hook 'my/set-font)\n(add-hook 'fontaine-set-preset-hook 'my/set-font)\n\n;; 设置字体缩放比例，设置为 1.172 可以确保 2 倍放大后对应的是 22 号偶数字体，这样表格\n;; 可以对齐。16 * 1.172 * 1.172 = 21.97（Emacs 取整为 22）。\n(setq text-scale-mode-step 1.172)\n\n;; org-table 只使用中英文严格等宽的 LXGW WenKai Mono Screen 字体, 避免中英文不对齐。\n(custom-theme-set-faces 'user '(org-table ((t (:family \"LXGW WenKai Mono Screen\")))))\n#+end_src\n\nef-themes: Emacs 主题列表：https://emacsthemes.com/popular/index.html\n\n#+begin_src emacs-lisp\n(use-package ef-themes\n  :demand\n  :config\n  (mapc #'disable-theme custom-enabled-themes)\n  (setq ef-themes-variable-pitch-ui t)\n  (setq ef-themes-mixed-fonts t)\n  (setq ef-themes-headings\n        '(\n          ;; level 0 是文档 title，1-8 是文档 header。\n          (0 . (variable-pitch light 1.9))\n          (1 . (variable-pitch light 1.8))\n          (2 . (variable-pitch regular 1.7))\n          (3 . (variable-pitch regular 1.6))\n          (4 . (variable-pitch regular 1.5))\n          (5 . (variable-pitch 1.4))\n          (6 . (variable-pitch 1.3))\n          (7 . (variable-pitch 1.2))\n          (8 . (variable-pitch 1.1))\n          (t . (variable-pitch 1.1))))\n  (setq ef-themes-region '(intense no-extend neutral)))\n#+end_src\n\n自动切换深浅主题:\n\n#+begin_src emacs-lisp\n(defun my/load-theme (appearance)\n  (interactive)\n  (pcase appearance\n    ('light (load-theme 'ef-light t))\n    ('dark (load-theme 'ef-elea-dark t))))\n(add-hook 'ns-system-appearance-change-functions 'my/load-theme)\n(add-hook 'after-init-hook (lambda () (my/load-theme ns-system-appearance)))\n#+end_src\n\ntab-bar：\n\n#+begin_src emacs-lisp\n(use-package tab-bar\n  :custom\n  (tab-bar-close-button-show nil)\n  (tab-bar-new-button-show nil)\n  (tab-bar-history-limit 20)\n  (tab-bar-new-tab-choice \"*dashboard*\")\n  (tab-bar-show 1)\n  ;; 使用 super + N 切换 tab。\n  (tab-bar-select-tab-modifiers \"super\")\n  :config\n  ;; 去掉最左侧的 \u003c 和 \u003e 。\n  (setq tab-bar-format '(tab-bar-format-tabs tab-bar-separator))\n  ;; 开启 tar-bar history mode 后才支持 history-back/forward 命令。\n  (tab-bar-history-mode t)\n  (global-set-key (kbd \"s-f\") 'tab-bar-history-forward)\n  (global-set-key (kbd \"s-b\") 'tab-bar-history-back)\n  (global-set-key (kbd \"s-t\") 'tab-bar-new-tab)\n  (keymap-global-set \"s-n\" 'tab-bar-switch-to-next-tab)\n  (keymap-global-set \"s-p\" 'tab-bar-switch-to-prev-tab)\n  (keymap-global-set \"s-w\" 'tab-bar-close-tab)\n\n  ;; 为 tab 添加序号，用于快速切换。\n  (defvar ct/circle-numbers-alist\n    '((0 . \"⓪\")\n      (1 . \"①\")\n      (2 . \"②\")\n      (3 . \"③\")\n      (4 . \"④\")\n      (5 . \"⑤\")\n      (6 . \"⑥\")\n      (7 . \"⑦\")\n      (8 . \"⑧\")\n      (9 . \"⑨\"))\n    \"Alist of integers to strings of circled unicode numbers.\")\n  (setq tab-bar-tab-hints t)\n  (defun ct/tab-bar-tab-name-format-default (tab i)\n    (let ((current-p (eq (car tab) 'current-tab))\n          (tab-num (if (and tab-bar-tab-hints (\u003c i 10))\n                       (alist-get i ct/circle-numbers-alist) \"\")))\n      (propertize\n       (concat tab-num\n               \" \"\n               (alist-get 'name tab)\n               (or (and tab-bar-close-button-show\n                        (not (eq tab-bar-close-button-show\n                                 (if current-p 'non-selected 'selected)))\n                        tab-bar-close-button)\n                   \"\")\n               \" \")\n       'face (funcall tab-bar-tab-face-function tab))))\n  (setq tab-bar-tab-name-format-function #'ct/tab-bar-tab-name-format-default)\n\n  (global-set-key (kbd \"s-1\") 'tab-bar-select-tab)\n  (global-set-key (kbd \"s-2\") 'tab-bar-select-tab)\n  (global-set-key (kbd \"s-3\") 'tab-bar-select-tab)\n  (global-set-key (kbd \"s-4\") 'tab-bar-select-tab)\n  (global-set-key (kbd \"s-5\") 'tab-bar-select-tab)\n  (global-set-key (kbd \"s-6\") 'tab-bar-select-tab)\n  (global-set-key (kbd \"s-7\") 'tab-bar-select-tab)\n  (global-set-key (kbd \"s-8\") 'tab-bar-select-tab)\n  (global-set-key (kbd \"s-9\") 'tab-bar-select-tab))\n#+end_src\n\n* 输入法\n\n使用 ~RIME~ 输入法 + ~iDevel/rime-ice~ 雾凇拼音输入法方案。\n\n安装 ~RIME~ 输入法后端引擎 [[https://github.com/rime/librime/releases][librime]] ： ~emacs-rime~ 直接和该引擎打交道，不需要安装前端程序\n\"鼠须管squirrel\"。\n\n#+begin_src bash :tangle ~/.emacs.d/init.sh\nwget https://github.com/rime/librime/releases/download/1.11.2/rime-5b09f35-macOS-universal.tar.bz2\ntar -xvf rime-5b09f35-macOS-universal.tar.bz2\nmv ~/.emacs.d/librime/dist{,.bak}\nmv dist ~/.emacs.d/librime\n# 如果 MacOS Gatekeeper 阻止第三方软件运行，可以暂时关闭它：\nsudo spctl --master-disable\n# 后续再开启：sudo spctl --master-enable\n#+end_src\n\n下载 [[https://github.com/iDvel/rime-ice.git][iDvel/rime-ice]] 雾凇拼音输入法方案：\n\n#+begin_src bash :tangle ~/.emacs.d/init.sh\nmv ~/Library/Rime ~/Library/Rime.bak\ngit clone https://github.com/iDvel/rime-ice --depth=1\nmv rime-ice ~/Library/Rime\n# 后续可以 git pull 更新 rime-ice。\ncd ~/Library/Rime\n# 自定义词频文件\ncp custom_phrase.txt  opsnull_custom_phrase.txt\n# 修改其中的 db_name\nsed -i -e 's/custom_phrase.txt/opsnull_custom_phrase/g' opsnull_custom_phrase.txt\n#+end_src\n\nrime_ice 拼音方案调整(如模糊音，动态词频，自定义词语文件等):\n+ 自定义短语：向自定义短语词典文件 =opsnull_custom_phrase.txt= 添加自定义短语，\n  custom_prase/db_class 为 stabledb，是只读的，不会动态调频。（可以设置为 tabledb 来\n  动态调频）。\n+ 首次添加该文件后需要执行 =M-x rime-deploy= 和 =M-x rime-sync= 生效。\n\n#+begin_src yaml :tangle ~/Library/Rime/rime_ice.custom.yaml\npatch:\n  switches:\n  - name: ascii_mode\n    states: [ 中, Ａ ]\n  - name: ascii_punct  # 中英标点\n    states: [ ¥, $ ]\n  # 下面这些开关一般用不到, 故关闭(如候选词中不再显示 emoji).\n  # - name: traditionalization\n  #   states: [ 简, 繁 ]\n  #   reset: 0\n  # - name: emoji\n  #   states: [ 💀, 😄 ]\n  #   reset: 1\n  # - name: full_shape\n  #   states: [ 半角, 全角 ]\n  #   reset: 0\n  # - name: search_single_char  # search.lua 的功能开关，辅码查词时是否单字优先\n  #   abbrev: [词, 单]\n  #   states: [正常, 单字]\n  #   reset: 0\n\n  translator/spelling_hints: 0           # 不显示候选词的拼音。\n  translator/always_show_comments: false #不显示候选者的拼音。\n  translator/enable_user_dict: true      # 根据上屏自动调整词频, 否则根据 *.dict.yaml 中的静态定义的词频率。\n  custom_phrase/user_dict: \"opsnull_custom_phrase\"  # 自定义短语词典文件，权重最高。\n\n  speller/algebra:\n  # 模糊拼音\n  # 声母\n  - derive/^([zcs])h/$1/          # z c s → zh ch sh\n  - derive/^([zcs])([^h])/$1h$2/  # zh ch sh → z c s\n  #- derive/^l/n/  # n → l\n  #- derive/^n/l/  # l → n\n  # 韵母\n  - derive/eng$/en/\n  - derive/en$/eng/\n  - derive/in/ing/\n  - derive/ing/in/\n\n  # 自动纠错(后者用前者替换)\n  # ai\n  - derive/^([wghk])ai$/$1ia/  # wia → wai\n  # ei\n  - derive/([wfghkz])ei$/$1ie/  # wie → wei\n  # ie\n  - derive/([jqx])ie$/$1ei/  # jei → jie\n#+end_src\n\nRime 输入法全局配置，详细参考 [[https://github.com/iDvel/rime-ice/blob/main/default.yaml][iDvel/rime-ice]]\n#+begin_src yaml :tangle ~/Library/Rime/default.custom.yaml\npatch:\n  schema_list:\n  - schema: rime_ice  # 只启用 rime_ice 雾凇拼音输入法方案。\n  menu/page_size: 9   # 显示 9 个候选词。\n  # 方案选单切换\n  switcher/hotkeys:\n  - F4\n  - \"Control+plus\" # 按 C-Shit-+ 调出方案选单。\n  switcher/fold_options: false # 呼出时不折叠。\n  switcher/abbreviate_options: false # 折叠时不缩写选项\n  ascii_composer: # 中英文切换\n    switch_key:   # 关闭左边 Shift 中西文切换，而是使用右侧 Shift（避免频繁误按）。\n      Shift_L: noop\n      Shift_R: commit_code\n  key_binder/bindings:\n  - { when: has_menu, accept: equal, send: Page_Down }             # 下一页\n  - { when: paging, accept: minus, send: Page_Up }                 # 上一页\n  - { when: always, accept: \"Control+period\", toggle: ascii_mode}  # 中英文切换\n  - { when: always, accept: \"Control+comma\", toggle: ascii_punct}  # 中英文标点切换\n  #- { when: always, accept: \"Control+comma\", toggle: full_shape}  # 全角/半角切换\n\n  # 开启 emacs 绑定惯例，这样可以使用 C-x 来修正拼音。需要将这些按键加到\n  # rime-translate-keybindings变量里后才会生效。 composing 指的是出现候选词列表的时机。\n  - { When: composing, accept: Control+p, send: Up }\n  - { when: composing, accept: Control+n, send: Down }\n  - { when: composing, accept: Control+b, send: Left }\n  - { when: composing, accept: Control+f, send: Right }\n  - { when: composing, accept: Control+a, send: Home }\n  - { when: composing, accept: Control+e, send: End }\n  - { when: composing, accept: Control+d, send: Delete }\n  # 从用户数据库中删除误上屏的词语\n  - { when: composing, accept: Control+k, send: Shift+Delete }\n  - { when: composing, accept: Control+h, send: BackSpace }\n  - { when: composing, accept: Control+g, send: Escape }\n  - { when: composing, accept: Control+bracketleft, send: Escape }\n  - { when: composing, accept: Control+y, send: Page_Up }\n  - { when: composing, accept: Alt+v, send: Page_Up }\n  - { when: composing, accept: Control+v, send: Page_Down }\n\n# 更多按键名称参考: https://github.com/LEOYoon-Tsaw/Rime_collections/blob/master/Rime_description.md\n#+end_src\n\n配置 Emacs:\n+ =rime-disable-predicates= 定义了一组断言函数，当任一函数断言成立时，Rime 自动将输入法\n  切换为英文（inline、ascii-inline、ascii-mode 都指的是英文）。如果同时定义了\n  rime-inline-predicates 变量，则当这两组函数都至少有一个断言成立时才会切换为英文。\n+ =rime-predicate-after-alphabet-char-p= 和 =rime-predicate-in-code-string-p= 条件都会导\n  致不能正确的中英文混排。\n\n#+begin_src emacs-lisp\n(use-package rime\n  :custom\n  (rime-user-data-dir \"~/Library/Rime/\")\n  (rime-librime-root \"~/.emacs.d/librime/dist\")\n  (rime-emacs-module-header-root \"/opt/homebrew/opt/emacs-plus@30/include\")\n  :hook\n  (emacs-startup . (lambda () (setq default-input-method \"rime\")))\n  :bind\n  (\n   :map rime-active-mode-map\n   ;; 在已经激活 Rime 候选菜单时，强制切换到英文直到按回车。\n   (\"M-j\" . 'rime-inline-ascii)\n   :map rime-mode-map\n   ;; 强制切换到中文模式.\n   (\"M-j\" . 'rime-force-enable)\n   ;; 下面这些快捷键需要发送给 rime 来处理, 需要与 default.custom.yaml 文件中的\n   ;; key_binder/bindings配置相匹配。\n   (\"C-.\" . 'rime-send-keybinding)      ;; 中英文切换\n   (\"C-+\" . 'rime-send-keybinding)      ;; 输入法菜单\n   (\"C-,\" . 'rime-send-keybinding)      ;; 中英文标点切换\n   ;;(\"C-,\" . 'rime-send-keybinding)    ;; 全半角切换\n   )\n  :config\n  ;; 在 modline 高亮输入法图标, 可用来快速分辨分中英文输入状态。\n  (setq mode-line-mule-info '((:eval (rime-lighter))))\n  ;; 将如下快捷键发送给 rime，同时需要在 rime 的 key_binder/bindings 的部分配置才会生\n  ;; 效。\n  (add-to-list 'rime-translate-keybindings \"C-h\") ;; 删除拼音字符\n  (add-to-list 'rime-translate-keybindings \"C-d\")\n  (add-to-list 'rime-translate-keybindings \"C-k\") ;; 删除误上屏的词语\n  (add-to-list 'rime-translate-keybindings \"C-a\") ;; 跳转到第一个拼音字符\n  (add-to-list 'rime-translate-keybindings \"C-e\") ;; 跳转到最后一个拼音字符support\n  ;; shift-l, shift-r, control-l, control-r, 只有当使用系统 RIME 输入法时才有效。\n  (setq rime-inline-ascii-trigger 'shift-r)\n  ;; 临时英文模式, 该列表中任何一个断言返回 t 时自动切换到英文。如果\n  ;; rime-inline-predicates 不为空，则当其中任意一个断言也返回 t 时才会自动切换到英文\n  ;; （inline 等效于 ascii-mode）。自定义 avy 断言函数。\n  (defun rime-predicate-avy-p () (bound-and-true-p avy-command))\n  (setq rime-disable-predicates\n        '(rime-predicate-ace-window-p\n          rime-predicate-hydra-p\n          ;;rime-predicate-current-uppercase-letter-p\n          ;; 在上一个字符是英文时才自动切换到英文，适合字符串中中英文混合的情况。\n          ;;rime-predicate-in-code-string-after-ascii-p\n          ;; 代码块内不能输入中文, 但注释和字符串不受影响。\n          ;;rime-predicate-prog-in-code-p\n          ;;rime-predicate-avy-p\n          ))\n  (setq rime-show-candidate 'posframe)\n  (setq default-input-method \"rime\")\n\n  (setq rime-posframe-properties\n        (list :background-color \"#333333\"\n              :foreground-color \"#dcdccc\"\n              :internal-border-width 2))\n\n  ;; 部分 mode 关闭 RIME 输入法。\n  (defadvice switch-to-buffer (after activate-input-method activate)\n    (if (or (string-match \"vterm-mode\" (symbol-name major-mode))\n            (string-match \"dired-mode\" (symbol-name major-mode))\n            (string-match \"image-mode\" (symbol-name major-mode))\n            (string-match \"compilation-mode\" (symbol-name major-mode))\n            (string-match \"isearch-mode\" (symbol-name major-mode))\n            (string-match \"minibuffer-mode\" (symbol-name major-mode)))\n        (activate-input-method nil)\n      (activate-input-method \"rime\"))))\n#+end_src\n\n* 补全和预览\n\n本部分使用的三方包说明：\n1. vertico：minibuffer 补全；\n2. corfu：光标处补全；\n3. orderless：提供 flex/regex 过滤风格；\n4. consult：实时预览和高级过滤；\n5. embark：为 minibuffer/buffer 选中的内容提供快捷操作；\n6. marginalia：为候选者提供元数据（如 dired 风格的权限、修改时间等）；\n\nvertico 提供 minibuffer 自动补全（corfu 提供光标处的自动补全）, 可以使用 orderless 过滤候选者：\n+ =C-] (abort-recursive-edit)= 关闭 minibuffer 编辑和补全状态。\n+ =M-RET（vertico-exit-input)= 退出输入候选模式，直接使用输入的内容，可以是 file 或 buffer name。\n+ =M-} (vertico-next-group)= 选择候选者列表中的下一个分组，如不同的 file 或 project。\n+ =TAB (vertico-insert)= 插入当前选中的候选者；\n\n#+begin_src emacs-lisp\n(use-package vertico\n  :config\n  (setq vertico-count 15)\n  (vertico-mode 1)\n  (define-key vertico-map (kbd \"\u003cbackspace\u003e\") #'vertico-directory-delete-char)\n  (define-key vertico-map (kbd \"RET\") #'vertico-directory-enter))\n\n(use-package emacs\n  :init\n  ;; minibuffer 不显示光标。\n  (setq minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt))\n  (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)\n  ;; M-x 只显示当前 mode 支持的命令。\n  (setq read-extended-command-predicate #'command-completion-default-include-p)\n  ;; 开启 minibuffer 递归编辑。\n  (setq enable-recursive-minibuffers t))\n#+end_src\n\ncorf 在光标处显示候选列表和文档, 可以使用 orderless 来过滤候选者：\n\n#+begin_src emacs-lisp\n(use-package corfu\n  :init\n  (global-corfu-mode 1)\n  (corfu-popupinfo-mode 1) ;; 显示候选者文档。\n  :bind\n  ;; 滚动显示 corfu-popupinfo 内容的快捷键。\n  (:map corfu-popupinfo-map\n        (\"C-M-j\" . corfu-popupinfo-scroll-up)\n        (\"C-M-k\" . corfu-popupinfo-scroll-down))\n  :custom\n  (corfu-cycle t)                ;; 自动轮转。\n  (corfu-auto t)                 ;; 自动补全(不需要按 TAB)。\n  (corfu-auto-prefix 2)          ;; 触发自动补全的前缀长度。\n  (corfu-auto-delay 0.1)         ;; 触发自动补全的延迟, 当满足前缀长度或延迟时, 都会自动补全。\n  (corfu-separator ?\\s)          ;; 使用 Orderless 过滤分隔符。\n  (corfu-preselect 'prompt)      ;; Preselect the prompt\n  (corfu-scroll-margin 5)\n  (corfu-on-exact-match nil)     ;; 默认不选中候选者(即使只有一个)。\n  (corfu-popupinfo-delay '(0.1 . 0.2)) ;; 候选者帮助文档显示延迟。\n  (corfu-popupinfo-max-width 80)\n  (corfu-popupinfo-max-height 50)\n  (corfu-popupinfo-direction '(force-right)) ;; 强制在右侧显示文档。\n  :config\n  (defun corfu-enable-always-in-minibuffer ()\n    (setq-local corfu-auto nil)\n    (corfu-mode 1))\n  (add-hook 'minibuffer-setup-hook #'corfu-enable-always-in-minibuffer 1)\n\n  ;; corfu 支持 eshell 的 pcomplete 自动补全。\n  (add-hook 'eshell-mode-hook\n            (lambda ()\n              (setq-local corfu-auto nil)\n              (corfu-mode))))\n\n;; 记录 minibuffer 和 corfu 补全历史，后续显示候选者时按照频率排序。\n(use-package savehist\n  :hook (after-init . savehist-mode)\n  :config\n  (setq history-length 100)\n  (setq savehist-save-minibuffer-history t)\n  (setq savehist-autosave-interval 300)\n  (add-to-list 'savehist-additional-variables #'corfu-history)\n  (add-to-list 'savehist-additional-variables 'mark-ring)\n  (add-to-list 'savehist-additional-variables 'global-mark-ring)\n  (add-to-list 'savehist-additional-variables 'extended-command-history))\n\n(use-package emacs\n  :init\n  ;; 总是在弹出菜单中显示候选者。\n  (setq completion-cycle-threshold nil)\n  ;; 使用 TAB 来 indentation + completion(completion-at-point 默认是 M-TAB) 。\n  (setq tab-always-indent 'complete))\n#+end_src\n\norderless 补全风格：使用空格分割一个或多个匹配模式，模式无顺序，是 AND 关系。\n\norderless 默认使用 =orderless-matching-styles= 变量配置的 =正则和字面量= 匹配方式，通过给各模式指定前\n缀或后缀字符, 也可以灵活指定其它匹配模式:\n+ ~=~ : =orderless-literal=, 字面量匹配;\n+ ~~~ : =orderless-flex=, 模糊匹配，如 abc 实际对应正则 a.*b.*c;\n+ ~^~ : =orderless-literal-prefix= ，前缀匹配；\n+ ~\u0026~ : =orderless-annotation= ，使用 marginalia 等提供的 meta 信息来过滤；\n+ ~,~ : =orderless-initialism=, 首字母缩写，如 ,abc 实际对应正则 \\\u003ca.*\\\u003cb.*\\c;\n+ ~!~ : makes the rest of the component match using =orderless-without-literal=, that is, both =!bad\n  and bad!= will match strings that =do not contain the substring bad=.\n+ ~%~ : makes the string match ignoring diacritics and similar inflections on characters (it uses\n  the function =char-fold-to-regexp= to do this).\n\n! 只能对 =字面量= 匹配取反（orderless-without-literal) ，和其他 dispatch 字符连用时, ! 需要前缀形式，\n如 ~!=.go~ 将不匹配含有字面量 .go 的候选者。\n\n#+begin_src  emacs-lisp\n(use-package orderless\n  :demand t\n  :config\n  ;; https://github.com/minad/consult/wiki#minads-orderless-configuration\n  (defun +orderless--consult-suffix ()\n    \"Regexp which matches the end of string with Consult tofu support.\"\n    (if (and (boundp 'consult--tofu-char) (boundp 'consult--tofu-range))\n        (format \"[%c-%c]*$\"\n                consult--tofu-char\n                (+ consult--tofu-char consult--tofu-range -1))\n      \"$\"))\n\n  ;; Recognizes the following patterns:\n  ;; * .ext (file extension)\n  ;; * regexp$ (regexp matching at end)\n  (defun +orderless-consult-dispatch (word _index _total)\n    (cond\n     ;; Ensure that $ works with Consult commands, which add disambiguation suffixes\n     ((string-suffix-p \"$\" word)\n      `(orderless-regexp . ,(concat (substring word 0 -1) (+orderless--consult-suffix))))\n     ;; File extensions\n     ((and (or minibuffer-completing-file-name\n               (derived-mode-p 'eshell-mode))\n           (string-match-p \"\\\\`\\\\..\" word))\n      `(orderless-regexp . ,(concat \"\\\\.\" (substring word 1) (+orderless--consult-suffix))))))\n\n  ;; 在 orderless-affix-dispatch 的基础上添加上面支持文件名扩展和正则表达式的 dispatchers。\n  (setq orderless-style-dispatchers\n        (list #'+orderless-consult-dispatch\n              #'orderless-affix-dispatch))\n\n  ;; 自定义名为 +orderless-with-initialism 的 orderless 风格。\n  (orderless-define-completion-style +orderless-with-initialism\n    (orderless-matching-styles '(orderless-initialism orderless-literal orderless-regexp)))\n\n  ;; 使用 orderless 和 Emacs 原生的 basic 补全风格，但 orderless 的优先级更高。\n  (setq completion-styles '(orderless basic))\n  (setq completion-category-defaults nil)\n\n  ;; 设置 Emacs minibuffer 各 category 使用的补全风格。\n  (setq completion-category-overrides\n        '(\n          ;; buffer name 补全\n          ;;(buffer (styles +orderless-with-initialism))\n\n          ;; 文件名和路径补全, partial-completion 提供了 wildcard 支持。\n          (file (styles partial-completion))\n          (command (styles +orderless-with-initialism))\n          (variable (styles +orderless-with-initialism))\n          (symbol (styles +orderless-with-initialism))\n\n          ;; eglot will change the completion-category-defaults to flex, BAD!\n          ;; https://github.com/minad/corfu/issues/136#issuecomment-eglot\n          ;; 使用 M-SPC 来分隔光标处的多个筛选条件。\n          (eglot (styles . (orderless basic)))\n          (eglot-capf (styles . (orderless basic)))\n          ))\n\n  ;; 使用 SPACE 来分割过滤字符串。\n  (setq orderless-component-separator #'orderless-escapable-split-on-space))\n#+end_src\n+ partial-completion 支持 shell wildcards 和部分文件路径，如 /u/s/l 对应 /usr/share/local;\n+ 已知的 [[https://gitlab.com/protesilaos/dotfiles/-/blob/master/emacs/.emacs.d/prot-emacs-modules/prot-emacs-completion-common.el#L60][completion categories]];\n\n在多个过滤模式间插入分隔符：\n+ 对于 buffer 中光标处的连续输入, 使用 =M-SPC(corfu-insert-separator)= 插入 orderless 分隔符；\n+ 对于 minibuffer 区域的补全, 使用 =SPC= (orderless 默认的分隔符) 分割多个过滤条件，如果要插入 SPC\n  本身，需要使用 \\ 转义，如 =results\\ no=.\n\nconsult：提供候选者实时过滤和预览等功能：\n\n#+begin_src emacs-lisp\n(use-package consult\n  :hook\n  (completion-list-mode . consult-preview-at-point-mode)\n  :init\n  ;; 如果搜索字符少于 3，可以添加后缀 # 开始搜索，如 #gr#。\n  (setq consult-async-min-input 3)\n  ;; 从头开始搜索（而非前位置）。\n  (setq consult-line-start-from-top t)\n  ;; 寄存器预览。\n  (setq register-preview-function #'consult-register-format)\n  (advice-add #'register-preview :override #'consult-register-window)\n  :config\n  ;; 不搜索 go vendor 目录。\n  (setq consult-ripgrep-args (concat consult-ripgrep-args \" -g !vendor/\"))\n  ;; 按 C-l 才激活预览，否则 Buffer 列表中有大文件或远程文件时会卡住。\n  (setq consult-preview-key \"C-l\")\n  ;; 不对 consult-line 结果进行排序（按行号排序）。\n  (consult-customize consult-line :prompt \"Search: \" :sort nil)\n  ;; Buffer 列表中不显示的 Buffer 名称。\n  (mapcar\n   (lambda (pattern) (add-to-list 'consult-buffer-filter pattern))\n   '(\"\\\\*scratch\\\\*\"\n     \"\\\\*Warnings\\\\*\"\n     \"\\\\*helpful.*\"\n     \"\\\\*Help\\\\*\"\n     \"\\\\*Org Src.*\"\n     \"Pfuture-Callback.*\"\n     \"\\\\*epc con\"\n     \"\\\\*dashboard\"\n     \"\\\\*Ibuffer\"\n     \"\\\\*sort-tab\"\n     \"\\\\*Google Translate\\\\*\"\n     \"\\\\*straight-process\\\\*\"\n     \"\\\\*Native-compile-Log\\\\*\"\n     \"\\\\*EGLOT\"\n     \"[0-9]+.gpg\")))\n\n;; 执行 consult-line 命令时自动展开 org 内容。\n;; https://github.com/minad/consult/issues/563#issuecomment-1186612641\n(defun my/org-show-entry (fn \u0026rest args)\n  (interactive)\n  (when-let ((pos (apply fn args)))\n    (when (derived-mode-p 'org-mode)\n      (org-fold-show-entry))))\n(advice-add 'consult-line :around #'my/org-show-entry)\n\n;; 显示 mode 相关的命令。\n(global-set-key (kbd \"C-c M-x\") #'consult-mode-command)\n\n;; 搜索 Emacs 各 package/mode 的 info 和 man 文档。\n(global-set-key (kbd \"C-c i\") #'consult-info)\n(global-set-key (kbd \"C-c m\") #'consult-man)\n\n;; 使用 savehist 持久化保存的 minibuffer 历史。\n(global-set-key (kbd \"C-M-;\") #'consult-complex-command)\n\n;; consult-buffer 显示的 File 列表来源于变量 recentf-list。\n(global-set-key (kbd \"C-x b\") #'consult-buffer)\n(global-set-key (kbd \"C-x 4 b\") #'consult-buffer-other-window)\n(global-set-key (kbd \"C-x 5 b\") #'consult-buffer-other-frame)\n(global-set-key (kbd \"C-x r b\") #'consult-bookmark)\n(global-set-key (kbd \"C-x p b\") #'consult-project-buffer)\n\n(global-set-key (kbd \"M-y\") #'consult-yank-pop)\n(global-set-key (kbd \"M-Y\") #'consult-yank-from-kill-ring)\n\n(global-set-key (kbd \"M-g g\") #'consult-goto-line)\n(global-set-key (kbd \"M-g o\") #'consult-outline)\n\n;; 寄存器，保存 point、file、window、frame 的位置。\n(global-set-key (kbd \"C-'\") #'consult-register-store)\n(global-set-key (kbd \"C-M-'\") #'consult-register)\n\n;; 显示编译错误列表。\n(global-set-key (kbd \"M-g e\") #'consult-compile-error)\n;; 显示 flymake 诊断错误列表。\n(global-set-key (kbd \"M-g f\") #'consult-flymake)\n\n;; consult-buffer 默认已包含 recent file。\n;;(global-set-key (kbd \"M-g r\") #'consult-recent-file)\n\n(global-set-key (kbd \"M-g m\") #'consult-mark)\n(global-set-key (kbd \"M-g k\") #'consult-global-mark)\n\n;; 预览当前 buffer 的 imenu。\n(global-set-key (kbd \"M-g i\") #'consult-imenu)\n;; 预览当前 project 打开的所有 buffer 的 imenu。\n(global-set-key (kbd \"M-g I\") #'consult-imenu-multi)\n\n;; 搜索文件内容。\n(global-set-key (kbd \"M-s g\") #'consult-grep)\n(global-set-key (kbd \"M-s G\") #'consult-git-grep)\n(global-set-key (kbd \"M-s r\") #'consult-ripgrep)\n\n;; 搜索文件名（正则匹配）。\n(global-set-key (kbd \"M-s d\") #'consult-find)\n(global-set-key (kbd \"M-s D\") #'consult-locate)\n\n;; 搜索当前 buffer\n(global-set-key (kbd \"M-s l\") #'consult-line)\n(global-set-key (kbd \"M-s M-l\") #'consult-line)\n;; 搜索多个 buffer，默认为 project 的多个 buffers。\n;; 如果使用前缀参数，则搜索所有 buffers。\n(global-set-key (kbd \"M-s L\") #'consult-line-multi)\n\n;; Isearch 集成。\n(global-set-key (kbd \"M-s e\") #'consult-isearch-history)\n;;:map isearch-mode-map\n(define-key isearch-mode-map (kbd \"M-e\") #'consult-isearch-history)\n(define-key isearch-mode-map (kbd \"M-s e\") #'consult-isearch-history)\n(define-key isearch-mode-map (kbd \"M-s l\") #'consult-line)\n(define-key isearch-mode-map (kbd \"M-s L\") #'consult-line-multi)\n\n;; Minibuffer 历史。\n;;:map minibuffer-local-map)\n(define-key minibuffer-local-map (kbd \"M-s\") #'consult-history)\n(define-key minibuffer-local-map (kbd \"M-r\") #'consult-history)\n\n;; 使用 consult 来预览 xref 的引用定义和跳转。\n(setq xref-show-xrefs-function #'consult-xref)\n(setq xref-show-definitions-function #'consult-xref)\n\n;; 限制 xref history 仅局限于当前窗口（默认全局）。\n(setq xref-history-storage 'xref-window-local-history)\n\n;; 在其它窗口查看定义。\n(global-set-key (kbd \"C-M-.\") 'xref-find-definitions-other-window)\n#+end_src\n\nembark：为选中的内容提供快捷操作命令：\n\n#+begin_src emacs-lisp\n(use-package embark\n  :init\n  ;; 使用 C-h 显示 key preifx 绑定。\n  (setq prefix-help-command #'embark-prefix-help-command)\n  :config\n  (setq embark-prompter 'embark-keymap-prompter)\n  (global-set-key (kbd \"C-;\") #'embark-act) ;; embark-dwim\n  ;; 根据当前 buffer 的 mode，显示可以使用的快捷键。\n  (define-key global-map [remap describe-bindings] #'embark-bindings))\n\n;; embark-consult 支持 embark 和 consult 集成，使用 wgrep 编辑 consult grep/line 的 export 的结果。\n(use-package embark-consult\n  :after (embark consult)\n  :hook  (embark-collect-mode . consult-preview-at-point-mode))\n\n;; 编辑 grep buffers, 可以和 consult-grep 和 embark-export 联合使用。\n(use-package wgrep\n  :config\n  ;; 执行 wgre-finished-edit 时保存所有修改的 buffer。\n  (setq wgrep-auto-save-buffer t)\n  (setq wgrep-change-readonly-file t))\n#+end_src\n\nmarginalia：为候选者提供元数据（如 dired 风格的权限、修改时间等）：\n\n#+begin_src  emacs-lisp\n(use-package marginalia\n  :init\n  ;; 显示绝对时间。\n  (setq marginalia-max-relative-age 0)\n  (marginalia-mode))\n#+end_src\n\n* org-mode\n\n#+begin_src emacs-lisp\n(use-package org\n  :config\n  (setq\n   org-ellipsis \"...\" ;; \" ⭍\"\n\n   ;; 使用 UTF-8 显示 LaTeX 或 \\xxx 特殊字符， M-x org-entities-help 查看所有特殊字符。\n   org-pretty-entities t\n   org-highlight-latex-and-related '(latex)\n\n   ;; 只显示而不处理和解释 latex 标记，例如 \\xxx 或 \\being{xxx}, 避免 export pdf 时出错。\n   org-export-with-latex 'verbatim\n   org-export-with-broken-links 'mark\n   ;; export 时不处理 super/sub scripting, 等效于 #+OPTIONS: ^:nil 。\n   org-export-with-sub-superscripts nil\n   org-export-default-language \"zh-CN\"\n   org-export-coding-system 'utf-8\n\n   ;; 使用 R_{s} 形式的下标（默认是 R_s, 容易与正常内容混淆) 。\n   org-use-sub-superscripts nil\n\n   ;; 文件链接使用相对路径, 解决 hugo 等 image 引用的问题。\n   org-link-file-path-type 'relative\n   org-html-validation-link nil\n   ;; 关闭鼠标点击链接。\n   org-mouse-1-follows-link nil\n\n   org-hide-emphasis-markers t\n   org-hide-block-startup t\n   org-hidden-keywords '(title)\n   org-hide-leading-stars t\n\n   org-cycle-separator-lines 2\n   org-cycle-level-faces t\n   org-n-level-faces 4\n   org-indent-indentation-per-level 2\n\n   ;; 内容缩进与对应 headerline 一致。\n   org-adapt-indentation t\n   org-list-indent-offset 2\n\n   ;; 代码块缩进。\n   org-src-preserve-indentation t\n   org-edit-src-content-indentation 0\n\n   ;; TODO 状态更新记录到 LOGBOOK Drawer 中。\n   org-log-into-drawer t\n   ;; TODO 状态更新时记录 note.\n   org-log-done 'note ;; note, time\n\n   ;; 不显示图片（手动点击显示更容易控制大小）。\n   org-startup-with-inline-images nil\n   org-startup-folded 'content\n   org-cycle-inline-images-display nil\n\n   ;; 如果对 headline 编号则 latext 输出时会导致 toc 缺失，故关闭。\n   org-startup-numerated nil\n   org-startup-indented t\n\n   ;; 先从 #+ATTR.* 获取宽度，如果没有设置则默认为 300 。\n   org-image-actual-width '(300)\n\n   ;; org-timer 到期时发送声音提示。\n   org-clock-sound t\n   ;; 关闭容易误按的 archive 命令。\n   org-archive-default-command nil\n\n   ;; 不自动对齐 tag。\n   org-tags-column 0\n   org-auto-align-tags nil\n\n   ;; 显示不可见的编辑。\n   org-catch-invisible-edits 'show-and-error\n   org-fold-catch-invisible-edits t\n\n   ;; 支持 ID property 作为 internal link target(默认是 CUSTOM_ID property)\n   org-id-link-to-org-use-id t\n   org-M-RET-may-split-line nil\n\n   ;; 关闭频繁弹出的 org-element-cache 警告 buffer 。\n   org-element-use-cache nil\n\n   org-todo-keywords\n   '((sequence \"TODO(t!)\" \"DOING(d@)\" \"|\" \"DONE(D)\")\n     (sequence \"WAITING(w@/!)\" \"NEXT(n!/!)\" \"SOMEDAY(S)\" \"|\" \"CANCELLED(c@/!)\"))\n\n   org-special-ctrl-a/e t\n   org-insert-heading-respect-content t)\n\n  ;;(add-hook 'org-mode-hook 'turn-on-auto-fill)\n  (add-hook 'org-mode-hook (lambda () (display-line-numbers-mode 0))))\n\n(global-set-key (kbd \"C-c l\") #'org-store-link)\n(global-set-key (kbd \"C-c a\") #'org-agenda)\n(global-set-key (kbd \"C-c c\") #'org-capture)\n(global-set-key (kbd \"C-c b\") #'org-switchb)\n\n;; 关闭 org-mode 的 C-c C-j 快捷键, 与 journal 冲突.\n(define-key org-mode-map (kbd \"C-c C-j\") nil)\n;; 关闭 org-mode 的 C-' 对应的 org-cycle-agenda-files 命令, 与 consult-register-store 冲突。\n(define-key org-mode-map (kbd \"C-'\") nil)\n\n;; 光标位于 src block 中执行 C-c C-f 时自动格式化 block 中代码。\n(defun my/format-src-block ()\n  \"Formats the code in the current src block.\"\n  (interactive)\n  (org-edit-special)\n  (indent-region (point-min) (point-max))\n  (org-edit-src-exit))\n\n(defun my/org-mode-keys ()\n  \"Modify keymaps used in org-mode.\"\n  (let ((map (if (org-in-src-block-p)\n                 org-src-mode-map\n               org-mode-map)))\n    (define-key map (kbd \"C-c C-f\") 'my/format-src-block)))\n(add-hook 'org-mode-hook 'my/org-mode-keys)\n\n;; 建立 org 相关目录。\n(dolist (dir '(\"~/docs/org\" \"~/docs/org/journal\"))\n  (unless (file-directory-p dir)\n    (make-directory dir)))\n#+end_SRC\n\n配置 babel：\n\n#+begin_src emacs-lisp\n;; 关闭 C-c C-c 触发执行代码.\n(setq org-babel-no-eval-on-ctrl-c-ctrl-c t)\n\n;; 确认执行代码的操作。\n(setq org-confirm-babel-evaluate t)\n\n;; 使用语言的 mode 来格式化代码.\n(setq org-src-fontify-natively t)\n\n;; 使用各语言的 Major Mode 来编辑 src block。\n(setq org-src-tab-acts-natively t)\n\n;; yaml 从外部的 yaml-mode 切换到内置的 yaml-ts-mode，告诉 babel 使用该内置 mode，否则编辑 yaml src\n;; block 时提示找不到 yaml-mode。\n(add-to-list 'org-src-lang-modes '(\"yaml\" . yaml-ts))\n(add-to-list 'org-src-lang-modes '(\"cue\" . cue))\n\n(require 'org)\n;; org bable 完整支持的语言列表（ob- 开头的文件）：\n;; https://git.savannah.gnu.org/cgit/emacs/org-mode.git/tree/lisp 对于官方不支持的语言，可以通过\n;; use-pacakge 来安装。\n(use-package ob-go)\n(use-package ob-rust)\n(org-babel-do-load-languages\n 'org-babel-load-languages\n '((shell . t)\n   (js . t)\n   (makefile . t)\n   (go . t)\n   (emacs-lisp . t)\n   (rust . t)\n   (python . t)\n   (C . t) ;; 支持 C/C++/D\n   (java . t)\n   (awk . t)\n   (css . t)))\n\n(use-package org-contrib)\n#+end_src\n\norg-mode 内容居中显示：\n\n#+begin_src emacs-lisp\n(use-package olivetti\n  :config\n  ;; 文本区域宽度，超过后自动折行。\n  (setq-default olivetti-body-width 130)\n  (add-hook 'org-mode-hook 'olivetti-mode))\n\n;; fill-column 值要小于 olivetti-body-width 才能正常折行。\n(setq-default fill-column 100)\n\n;; 由于 auto-fill 可能会打乱代码的字符串和注释，故为 prog-mode/text-mode 等全局关闭 auto-fill。\n;;(add-hook 'text-mode-hook 'turn-on-auto-fill)\n#+end_src\n\norg-modern 和 org-appear 美化：\n\n#+begin_src emacs-lisp\n(use-package org-modern\n  :after (org)\n  :config\n  ;; 各种符号字体：https://github.com/rime/rime-prelude/blob/master/symbols.yaml\n  ;;(setq org-modern-star '(\"◉\" \"○\" \"✸\" \"✿\" \"✤\" \"✜\" \"◆\" \"▶\"))\n  (setq org-modern-star '(\"⚀\" \"⚁\" \"⚂\" \"⚃\" \"⚄\" \"⚅\"))\n  (setq org-modern-block-fringe nil)\n  (setq org-modern-block-name\n        '((t . t)\n          (\"src\" \"»\" \"«\")\n          (\"SRC\" \"»\" \"«\")\n          (\"example\" \"»–\" \"–«\")\n          (\"quote\" \"❝\" \"❞\")))\n  ;; 美化表格。\n  (setq org-modern-table t)\n  (setq org-modern-list\n        '(\n          (?* . \"✤\")\n          (?+ . \"▶\")\n          (?- . \"◆\")))\n  (with-eval-after-load 'org (global-org-modern-mode)))\n\n;; 显示转义字符。\n(use-package org-appear\n  :custom\n  (org-appear-autolinks t)\n  :hook (org-mode . org-appear-mode))\n#+end_src\n\norg-download：拖拽图片或将剪贴板中图片插入到 org-mode buffer 中（使用 pngpaste 命令）:\n+ 需要编译 Emacs 时指定 =--with-imagemagick= 参数，Emacs 使用 imagemagick 命令来实时转换图片大小。\n\n#+begin_src emacs-lisp\n(use-package org-download\n  :config\n  ;; 保存路径包含 /static/ 时, ox-hugo 在导出时保留后面的目录层次。\n  (setq-default org-download-image-dir \"./static/images/\")\n  (setq org-download-method 'directory\n        org-download-display-inline-images 'posframe\n        org-download-screenshot-method \"pngpaste %s\"\n        org-download-image-attr-list '(\"#+ATTR_HTML: :width 400 :align center\"))\n  (add-hook 'dired-mode-hook 'org-download-enable)\n  (org-download-enable)\n  (global-set-key (kbd \"\u003cf6\u003e\") #'org-download-screenshot)\n  ;; 不添加 #+DOWNLOADED: 注释。\n  (setq org-download-annotate-function (lambda (link) (previous-line 1) \"\")))\n#+end_src\n\ntex 和 PDF 导出：\n\n#+begin_src emacs-lisp\n;; 将安装的 tex 二进制目录添加到 PATH 环境变量和 exec-path 变量中，Emacs 执行 xelatex 命令时使用。\n(setq my-tex-path \"/Library/TeX/texbin\")\n(setenv \"PATH\" (concat my-tex-path \":\" (getenv \"PATH\")))\n(setq exec-path (cons my-tex-path  exec-path))\n\n;; engrave-faces 比 minted 渲染速度更快。\n(use-package engrave-faces\n  :after ox-latex\n  :config\n  (require 'engrave-faces-latex)\n  (setq org-latex-src-block-backend 'engraved)\n  ;; 代码块左侧添加行号。\n  (add-to-list 'org-latex-engraved-options '(\"numbers\" . \"left\"))\n  ;; 代码块主题。\n  (setq org-latex-engraved-theme 'ef-light))\n\n(defun my/export-pdf (backend)\n  (progn\n    ;;(setq org-export-with-toc nil)\n    (setq org-export-headline-levels 2))\n  )\n(add-hook 'org-export-before-processing-functions #'my/export-pdf)\n\n;; ox- 为 org-mode 的导出后端包的惯例前缀。\n\n;;(use-package ox-reveal) ;; reveal.js\n(use-package ox-gfm :defer t) ;; github flavor markdown\n\n(require 'ox-latex)\n(with-eval-after-load 'ox-latex\n  ;; latex image 的默认宽度, 可以通过 #+ATTR_LATEX :width xx 配置。\n  (setq org-latex-image-default-width \"0.7\\\\linewidth\")\n  ;; 使用 booktabs style 来显示表格，例如支持隔行颜色, 这样 #+ATTR_LATEX: 中不需要添加 :booktabs t。\n  (setq org-latex-tables-booktabs t)\n  ;; 不保存 LaTeX 日志文件（调试时设置为 nil）。\n  (setq org-latex-remove-logfiles t)\n  ;; 使用支持中文的 xelatex。\n  (setq org-latex-pdf-process '(\"latexmk -xelatex -quiet -shell-escape -f %f\"))\n  (add-to-list 'org-latex-classes\n\t       '(\"ctexart\"\n                 \"\\\\documentclass[lang=cn,11pt,a4paper,table]{ctexart}\n                    [NO-DEFAULT-PACKAGES]\n                    [PACKAGES]\n                    [EXTRA]\"\n                 (\"\\\\section{%s}\" . \"\\\\section*{%s}\")\n                 (\"\\\\subsection{%s}\" . \"\\\\subsection*{%s}\")\n                 (\"\\\\subsubsection{%s}\" . \"\\\\subsubsection*{%s}\")\n                 (\"\\\\paragraph{%s}\" . \"\\\\paragraph*{%s}\")\n                 (\"\\\\subparagraph{%s}\" . \"\\\\subparagraph*{%s}\"))))\n\n;; org export html 格式时需要 htmlize.el 包来格式化代码。\n(use-package htmlize)\n#+end_src\n\n自定义导出 PDF 的 LaTeX 样式 mystyle.sty: 对于表格，如果列内容过宽则导出的 PDF 中该列的内容会被截断，\n可以为表格设置如下属性，将超宽列 align 设置为 X 来解决：\n: #+ATTR_LATEX: :environment tabularx :booktabs t :width \\linewidth :align l|l|X\n\n#+begin_src latex :tangle  ~/emacs/mystyle.sty\n\\usepackage{wallpaper} % 显示封面图片或页面图片。\n\n\\usepackage{color}\n\\usepackage{xcolor}\n\\definecolor{winered}{rgb}{0.5,0,0}\n\\definecolor{lightgrey}{rgb}{0.9,0.9,0.9}\n\\definecolor{tableheadcolor}{gray}{0.92}\n\\definecolor{commentcolor}{RGB}{0,100,0}\n\\definecolor{frenchplum}{RGB}{190,20,83}\n\n% 提示 title\n\\usepackage[explicit]{titlesec}\n% 每个 chapter 另起一页\n\\newcommand{\\sectionbreak}{\\clearpage}\n\\usepackage{titling}\n\\setlength{\\droptitle}{-6em}\n\n% 超链接和书签\n\\usepackage[colorlinks]{hyperref}\n\\hypersetup{\n  pdfborder={0 0 0},\n  colorlinks=true,\n  bookmarksopen=true,\n  bookmarksnumbered=true, % 书签目录显示编号。\n  linkcolor={winered},\n  urlcolor={winered},\n  filecolor={winered},\n  citecolor={winered},\n  linktoc=all}\n\n% 安装 noto-cjk 中文字体: git clone https://github.com/googlefonts/noto-cjk.git\n\\usepackage{fontspec}\n\\usepackage[utf8x]{inputenc}\n\\setmainfont{Noto Serif SC}\n\\setsansfont{Noto Sans SC}[Scale=MatchLowercase]\n\\setmonofont{Noto Sans Mono CJK SC}[Scale=MatchLowercase]\n\\setCJKmainfont[BoldFont=Noto Serif SC]{Noto Serif SC}\n\\setCJKsansfont{Noto Sans SC}\n\\setCJKmonofont{Noto Sans Mono CJK SC}\n\n\\XeTeXlinebreaklocale \"zh\"\n\\XeTeXlinebreakskip = 0pt plus 1pt minus 0.1pt\n\n% 添加 email 命令。\n\\newcommand\\email[1]{\\href{mailto:#1}{\\nolinkurl{#1}}}\n\n% sidewaytable 依赖 rotfloat\n\\usepackage {rotfloat}\n\n% tabularx 的特殊 align 参数 X 用来对指定列内容自动换行，否则该列内容有可能被截断，\n% 解决办法是：在 org-mode 表格前需要加如下属性：\n% #+ATTR_LATEX: :environment tabularx :booktabs t :width \\linewidth :align l|X\n\\usepackage{tabularx}\n% 美化表格显示效果\n\\usepackage{booktabs}\n% 表格隔行颜色, {1} 开始行, {lightgrep} 奇数行颜色, {} 偶数行颜色(空表示白色)\n\\rowcolors{1}{lightgrey}{}\n\n\\usepackage{parskip}\n\\setlength{\\parskip}{1em}\n\\setlength{\\parindent}{0pt}\n\n\\usepackage{etoolbox}\n\\usepackage{calc}\n\n\\usepackage[scale=0.85]{geometry}\n%\\setlength{\\headsep}{5pt}\n\n\\usepackage{amsthm}\n\\usepackage{amsmath}\n\\usepackage{amssymb}\n\\usepackage{indentfirst}\n\\usepackage{multicol}\n\\usepackage{multirow}\n\\usepackage{linegoal}\n\\usepackage{graphicx}\n\\usepackage{fancyvrb}\n\\usepackage{abstract}\n\\usepackage{hologo}\n\n\\linespread{1}\n\\graphicspath{{image/}{figure/}{fig/}{img/}{images/}}\n\n\\usepackage[font=small,labelfont={bf}]{caption}\n\\captionsetup[table]{skip=3pt}\n\\captionsetup[figure]{skip=3pt}\n\n% 下划线、强调和删除线等\n\\usepackage[normalem]{ulem}\n% 列表\n\\usepackage[shortlabels,inline]{enumitem}\n\\setlist{nolistsep}\n% xeCJK 默认会把黑点用汉字显示，而 Noto 没有这个字体，所以显示效果为一个小点。解决办法是将它设置为\n% \\bullet, 这样显示为实心黑点。Windows 带的楷体、仿宋没有这个问题。\n\\setlist[itemize]{label=$\\bullet$}\n% 或者：\n%\\renewcommand\\labelitemi{\\ensuremath{\\bullet}}\n#+end_src\n\n创建一个 tempel 模板，便于在 org-mode 文件中快速插入导出 PDF 的 tex 配置参数：\n+ 如果生成的 pdf 不显示目录，检查文档 ~#+OPTIONS~ 参数中的 ~toc:nil~ 和 ~num: 2~ 是否生效；\n\n#+begin_src emacs-lisp :tangle no\n(my-latex \"#+DATE: \" (format-time-string \"%Y-%m-%d %a\") n\n\t  \"#+SUBTITLE: 内部资料，注意保密!\n#+AUTHOR: 张俊(zj@opsnull.com)\n# 中文语言环境（目录等用中文显示）。\n#+LANGUAGE: zh-CN\n# 不自动输出 titile 和 toc，后续 latext mystyle 中定制输出。\n# 但是需要明确通过 num 控制输出的目录级别。\n#+OPTIONS: prop:t title:nil num:2 toc:nil ^:nil\n#+LATEX_COMPILER: xelatex\n#+LATEX_CLASS: ctexart\n#+LATEX_HEADER: \\\\usepackage{/Users/alizj/emacs/mystyle}\n\n# 定制 PDF 封面和目录。\n#+begin_export latex\n% 封面页\n\\\\begin{titlepage}\n% 插入标题\n\\\\maketitle\n% 插入封面图\n%\\\\ThisCenterWallPaper{0.4}{/path/to/image.png}\n% 封面页不编号\n\\\\noindent\\\\fboxsep=0pt\n\\\\setcounter{page}{0}\n\\\\thispagestyle{empty}\n\\\\end{titlepage}\n\n% 摘要页\n\\\\begin{abstract}\n这是一个摘要。\n\\\\end{abstract}\n\n% 目录页\n\\\\newpage\n\\\\tableofcontents\n\\\\newpage\n#+end_export\n\")\n#+end_src\n\nslide 演示：org-tree-slide 不再活跃维护了，dslide 是它的的替代品。\n\n#+begin_src emacs-lisp\n(use-package dslide\n  :vc(:url \"https://github.com/positron-solutions/dslide.git\")\n  :hook\n  ((dslide-start\n    .\n    (lambda ()\n      (org-fold-hide-block-all)\n      (setq-default x-stretch-cursor -1)\n      (redraw-display)\n      (blink-cursor-mode -1)\n      (setq cursor-type 'bar)\n      ;;(org-display-inline-images)\n      ;;(hl-line-mode -1)\n      (text-scale-increase 2)\n      (read-only-mode 1)))\n   (dslide-stop\n    .\n    (lambda ()\n      (blink-cursor-mode +1)\n      (setq-default x-stretch-cursor t)\n      (setq cursor-type t)\n      (text-scale-increase 0)\n      ;;(hl-line-mode 1)\n      (read-only-mode -1))))\n  :config\n  (setq dslide-margin-content 0.5)\n  (setq dslide-animation-duration 0.5)\n  (setq dslide-margin-title-above 0.3)\n  (setq dslide-margin-title-below 0.3)\n  (setq dslide-header-email nil)\n  (setq dslide-header-date nil)\n  (define-key org-mode-map (kbd \"\u003cf8\u003e\") #'dslide-deck-start)\n  (define-key dslide-mode-map (kbd \"\u003cf9\u003e\") #'dslide-deck-stop))\n#+end_src\n\njournal 日记：\n\n#+begin_src emacs-lisp\n(use-package org-journal\n  :commands org-journal-new-entry\n  :bind ((\"C-c j\" . org-journal-new-entry))\n  :init\n  (setq org-journal-prefix-key \"C-c j\")\n  (defun org-journal-save-entry-and-exit()\n    (interactive)\n    (save-buffer)\n    (kill-buffer-and-window))\n  :config\n  (define-key org-journal-mode-map (kbd \"C-c C-e\") #'org-journal-save-entry-and-exit)\n  (define-key org-journal-mode-map (kbd \"C-c C-j\") #'org-journal-new-entry)\n  (global-set-key (kbd \"C-c C-j\") #'org-journal-new-entry)\n\n  ;; 设置日志文件头。\n  (defun org-journal-file-header-func (time)\n    \"Custom function to create journal header.\"\n    (concat\n     (pcase org-journal-file-type\n       (`daily \"#+TITLE: Daily Journal\\n#+STARTUP: showeverything\")\n       (`weekly \"#+TITLE: Weekly Journal\\n#+STARTUP: folded\")\n       (`monthly \"#+TITLE: Monthly Journal\\n#+STARTUP: folded\")\n       (`yearly \"#+TITLE: Yearly Journal\\n#+STARTUP: folded\"))))\n  (setq org-journal-file-header 'org-journal-file-header-func)\n  (setq org-journal-file-type 'daily) ;; 按天记录。\n\n  (setq org-journal-dir \"~/docs/org/journal\")\n  (setq org-journal-find-file 'find-file)\n\n  ;; 加密日记文件。\n  (setq org-journal-enable-encryption t)\n  (setq org-journal-encrypt-journal t)\n  (defun my-old-carryover (old_carryover)\n    (save-excursion\n      (let ((matcher (cdr (org-make-tags-matcher org-journal-carryover-items))))\n\t(dolist (entry (reverse old_carryover))\n          (save-restriction\n            (narrow-to-region (car entry) (cadr entry))\n            (goto-char (point-min))\n            (org-scan-tags '(lambda ()\n                              (org-set-tags \":carried:\"))\n                           matcher org--matcher-tags-todo-only))))))\n  (setq org-journal-handle-old-carryover 'my-old-carryover))\n#+end_src\n\n创建一个 templ 模板，便于在文件开头添加内容，可避免每次打开时提示选择 GPG key:\n\n#+begin_example :tangle no\n;; 插入自己的 GnuPG 加密 key。\n(my-gpg \"# -*- mode:org; epa-file-encrypt-to: (\\\"geekard@qq.com\\\") -*-\")\n#+end_example\n\nox-hugo 博客：\n\n#+begin_src emacs-lisp\n(use-package ox-hugo\n  :demand\n  :config\n  (setq org-hugo-base-dir (expand-file-name \"~/blog/blog.opsnull.com/\"))\n  (setq org-hugo-section \"posts\")\n  (setq org-hugo-front-matter-format \"yaml\")\n  (setq org-hugo-export-with-section-numbers t)\n  (setq org-export-backends '(go md gfm html latex man hugo))\n  (setq org-hugo-auto-set-lastmod t))\n#+end_src\n\n* 编程开发\n** 缩进\n\n缩进层次可视化：\n\n#+begin_src emacs-lisp\n(use-package indent-bars\n  :vc (:url \"https://github.com/jdtsmith/indent-bars\")\n  :config\n  (require 'indent-bars-ts)\n  :custom\n  (indent-bars-treesit-support t)\n  (indent-bars-treesit-ignore-blank-lines-types '(\"module\"))\n  (indent-bars-treesit-scope\n   '((python\n      function_definition\n      class_definition\n      for_statement\n      if_statement\n      with_statement\n      while_statement)))\n  :hook\n  ((python-base-mode\n    yaml-ts-mode\n    json-ts-mode\n    js-ts-mode) . indent-bars-mode))\n#+end_src\n\n缩进风格：c/c++/go-mode/kernel 均使用 tab 缩进：\n\n#+begin_src emacs-lisp\n;;(setq indent-tabs-mode t)\n(setq c-ts-mode-indent-offset 8)\n(setq c-ts-common-indent-offset 8)\n(setq c-basic-offset 8)\n;; kernel 风格：table 和 offset 都是 tab 缩进，而且都是 8 字符。\n;; https://www.kernel.org/doc/html/latest/process/coding-style.html\n(setq c-default-style \"linux\")\n(setq tab-width 8)\n#+end_src\n\n** 括号\n\n彩色括号：\n\n#+begin_src emacs-lisp\n(use-package rainbow-delimiters\n  :hook (prog-mode . rainbow-delimiters-mode))\n#+end_src\n\n高亮匹配的括号：\n\n#+begin_src emacs-lisp\n(use-package paren\n  :hook (after-init . show-paren-mode)\n  :init\n  (setq show-paren-delay 0.1)\n  (setq show-paren-when-point-inside-paren t\n        show-paren-when-point-in-periphery t)\n  (setq show-paren-style 'parenthesis) ;; parenthesis, expression\n  (set-face-attribute 'show-paren-match nil :weight 'extra-bold))\n#+end_src\n\n智能补全括号：\n\n#+begin_src emacs-lisp\n(electric-pair-mode 1)\n(setq electric-pair-pairs\n      '(\n        (?\\\" . ?\\\")\n        (?\\{ . ?\\})))\n(setq electric-pair-preserve-balance t\n      electric-pair-delete-adjacent-pairs t\n      electric-pair-skip-self 'electric-pair-default-skip-self\n      electric-pair-open-newline-between-pairs t)\n#+end_src\n\n** project\n\nproject 使用 top-down 方式来检查项目路径中是否存在 .project 文件，所以在上层各路径的\n目录中不应该存在 .project 文件，否则会导致判断失败。\n\n1. 手动标记项目根目录：在目录下创建 =.project= 文件\n2. 查看当前项目的 project root： =(project-current)=\n3. 手动添加 project 目录： =M-x project-remember-projects-under=\n4. 调试目录的 project root 识别情况： =(my/project-try-local \"/path/to/directory\")=\n\n#+begin_src emacs-lisp\n(use-package project\n  :custom\n  (project-switch-commands\n   '(\n     (consult-project-buffer \"buffer\" ?b)\n     (project-dired \"dired\" ?d)\n     (magit-project-status \"magit status\" ?g)\n     (project-find-file \"find file\" ?p)\n     (consult-ripgrep \"rigprep\" ?r)\n     (vterm-toggle-cd \"vterm\" ?t)))\n  (project-vc-merge-submodules nil)\n  :config\n  ;; project-find-file 忽略的目录或文件列表。\n  (add-to-list 'vc-directory-exclusion-list \"vendor\") ;; go\n  (add-to-list 'vc-directory-exclusion-list \"node_modules\") ;; node\n  (add-to-list 'vc-directory-exclusion-list \"target\") ;; rust\n  )\n\n(defun my/project-try-local (dir)\n  \"Determine if DIR is a non-Git project.\"\n  (catch 'ret\n    (let ((pr-flags '(\n\t\t      ;; 顺着目录 top-down 查找第一个匹配的文件。所以中间目录不能有\n\t\t      ;; .project 等文件，否则判断 project root 错误。\n\t\t      (\"go.mod\" \"Cargo.toml\" \"pom.xml\" \"package.json\" \".project\" )\n                      ;; 以下文件容易导致 project root 判断错误, 故不添加。\n                      ;; (\"Makefile\" \"README.org\" \"README.md\")\n                      )))\n      (dolist (current-level pr-flags)\n        (dolist (f current-level)\n          (when-let ((root (locate-dominating-file dir f)))\n            (throw 'ret (cons 'local root))))))))\n(setq project-find-functions '(my/project-try-local project-try-vc))\n\n(cl-defmethod project-root ((project (head local)))\n  (cdr project))\n\n(defun my/project-discover ()\n  (interactive)\n  ;; 去掉 \"~/go/src/k8s.io/*\" 目录。\n  (dolist (search-path\n\t   '(\"~/go/src/github.com/*\"\n\t     \"~/go/src/github.com/*/*\"\n\t     \"~/go/src/gitlab.*/*/*\"))\n    (dolist (file (file-expand-wildcards search-path))\n      (when (file-directory-p file)\n        (message \"dir %s\" file)\n        ;; project-remember-projects-under 列出 file 下的目录, 分别加到\n        ;; project-list-file 中。\n        (project-remember-projects-under file nil)\n        (message \"added project %s\" file)))))\n\n;; 不将 tramp 项目记录到 projects 文件中，防止 emacs-dashboard 启动时检查 project 卡\n;; 住。\n(defun my/project-remember-advice (fn pr \u0026optional no-write)\n  (let* ((remote? (file-remote-p (project-root pr)))\n         (no-write (if remote? t no-write)))\n    (funcall fn pr no-write)))\n(advice-add 'project-remember-project :around 'my/project-remember-advice)\n#+end_src\n\n** magit\n\n#+begin_src emacs-lisp\n(setq vc-follow-symlinks t)\n\n;; 自动 revert buffer，确保 modeline 上的分支名正确。\n(setq auto-revert-check-vc-info t)\n\n(use-package magit\n  :custom\n  ;; 在当前 window 中显示 magit buffer。\n  (magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1)\n  (magit-log-arguments '(\"-n256\" \"--graph\" \"--decorate\" \"--color\"))\n  ;; 按照 word 展示 diff。\n  (magit-diff-refine-hunk t)\n  (magit-clone-default-directory \"~/go/src/\")\n  :config\n  ;; diff org-mode 时展开内容。\n  (add-hook 'magit-diff-visit-file-hook (lambda() (when (derived-mode-p 'org-mode)(org-fold-show-entry)))))\n#+end_src\n\ngit-link 为光标位置生成 git 仓库 URL:\n\n#+begin_src emacs-lisp\n(use-package git-link\n  :config\n  (setq git-link-use-commit t)\n  ;; 重写 gitlab 的 format 字符串以匹配内部系统。\n  (defun git-link-commit-gitlab (hostname dirname commit)\n    (format \"https://%s/%s/commit/%s\" hostname dirname commit))\n  (defun git-link-gitlab (hostname dirname filename branch commit start end)\n    (format \"https://%s/%s/blob/%s/%s\" hostname dirname\n\t    (or branch commit)\n            (concat filename\n                    (when start\n                      (concat \"#\"\n                              (if end\n                                  (format \"L%s-%s\" start end)\n\t\t\t\t(format \"L%s\" start))))))))\n#+end_src\n\n** treesit\n\ntreesit-auto 自动安装 grammer 和自动将 major-mode remap 到对应的 xx-ts-mode 上。具体\n参考变 =treesit-auto-recipe-list= :\n\n#+begin_src emacs-lisp\n(use-package treesit-auto\n  :demand t\n  :config\n  (setq treesit-auto-install 'prompt)\n  (global-treesit-auto-mode))\n#+end_src\n\ngrammer 被安装到 =~/.emacs.d/tree-sitter=, 如\n=~/.emacs.d/tree-sitter/libtree-sitter-python.dylib=\n+ 执行 =M-x treesit-auto-install-all= 来安装所有的 treesit modules。\n+ 如果要重新安装(升级) grammer, 需要先删除 dylib 文件或 tree-sitter 目录, 重启 emacs\n  后再执行 =M-x treesit-auto-install-all=.\n\n代码折叠：\n\n#+begin_src emacs-lisp\n(use-package treesit-fold\n  :vc (:url \"https://github.com/emacs-tree-sitter/treesit-fold\")\n  :config\n  (global-set-key (kbd \"C-c f f\") 'treesit-fold-close)\n  (global-set-key (kbd \"C-c f o\") 'treesit-fold-open)\n  (global-set-key (kbd \"C-c f O\") 'treesit-fold-open-recursively)\n  (global-set-key (kbd \"C-c f F\") 'treesit-fold-close-all)\n  (global-set-key (kbd \"C-c f u\") 'treesit-fold-open-all)\n  (global-set-key (kbd \"C-c f t\") 'treesit-fold-toggle))\n#+end_src\n\n** flymake\n\nflymake 为当前 buffer 提供错误检查/诊断功能，它在如下情况检查（诊断）buffer 是否有错\n误，错误信息直接显示在 buffer 区域，并发送给 eldoc：\n1. 执行 ~M-x flymake-start~;\n2. 超过 ~flymake-no-changes-timeout(默认 0.5)~ ；\n3. 保存 buffer 时 (除非设置 flymake-start-on-save-buffer 为 nil);\n\n将 flymake-no-changes-timeout 设置为 nil 后，eglot 不会显示 LSP 实时诊断消息，而是当\n保存 buffer 后经过 eglot-send-changes-idle-time 时间后才显示诊断消息，这样可以避免显\n示编码过程无意义的错误。\n\n#+begin_src emacs-lisp\n(use-package flymake\n  :config\n  ;; 不自动检查 buffer 错误。\n  (setq flymake-no-changes-timeout nil)\n\n  ;; 在行尾显示诊断消息（Emacs 30 开始支持）, 'short 只显示一条最重要信息，t 显示所有\n  ;; 信息。\n  (setq flymake-show-diagnostics-at-end-of-line 'short)\n\n  ;; 如果 buffer 出现错误的诊断消息，执行 flymake-start 重新触发诊断。\n  (define-key flymake-mode-map (kbd \"C-c C-c\") #'flymake-start)\n\n  ;; 显示诊断错误列表\n  (global-set-key (kbd \"C-s-l\") #'consult-flymake)\n  (define-key flymake-mode-map (kbd \"C-s-n\") #'flymake-goto-next-error)\n  (define-key flymake-mode-map (kbd \"C-s-p\") #'flymake-goto-prev-error))\n\n;; 解决 flymake-no-changes-timeout 为 nil 时诊断延迟的问题。\n;;; https://github.com/joaotavora/eglot/issues/1296\n;; (cl-defmethod eglot-handle-notification :after\n;;   (_server (_method (eql textDocument/publishDiagnostics)) \u0026key uri\n;;            \u0026allow-other-keys)\n;;   (when-let ((buffer (find-buffer-visiting (eglot-uri-to-path uri))))\n;;     (with-current-buffer buffer\n;;       (if (and (eq nil flymake-no-changes-timeout)\n;;                (not (buffer-modified-p)))\n;;           (flymake-start t)))))\n#+end_src\n\n** eglot\n\nelgot 使用 Emacs 内置的 flymake（而非 flycheck）、xref、eldoc、project 等包。\n\neglot 使用 flymake 来接收和显示 LSP Server 发送的 publishDiagnostics 事件，这是通过向\nflymake-diagnostic-functions hook 添加 'eglot-flymake-backend 实现的。\n\neglot 默认将 flymake 的 backend 清空，只保留 eglot 自身，可以通过配置 ~(add-to-list\n'eglot-stay-out-of 'flymake)~ 来关闭 eglot 对 flymake 的清空行为，这样可以使用自定义的\nflymake backends，但后续需要添加 hook 来手动启动和配置 eglot-flymake-backend。\n\n#+begin_src emacs-lisp\n(use-package eglot\n  :demand\n  :after (flymake)\n  :preface\n  (defun my/eglot-eldoc ()\n    ;; eglot will change the completion-category-defaults to flex, BAD!\n    ;; https://github.com/minad/corfu/issues/136#issuecomment-eglot\n    ;; 这里将 completion-category-defaults 设置为 nil，然后在 completion-category-overrides\n    ;; 中设置 eglot 使用 orderless 补全风格。\n    (setq completion-category-defaults nil)\n\n    ;; 在 eldoc buffer 开始优先显示 flymake 诊断信息。\n    (setq eldoc-documentation-functions\n          (cons #'flymake-eldoc-function\n                (remove #'flymake-eldoc-function eldoc-documentation-functions)))\n    )\n  :hook ((eglot-managed-mode . my/eglot-eldoc))\n  :bind\n  (:map eglot-mode-map\n        (\"C-c C-a\" . eglot-code-actions)\n        (\"C-c C-f\" . eglot-format-buffer)\n        (\"C-c C-r\" . eglot-rename)\n\t(\"C-c C-c\" . flymake-start)\n\t(\"C-c C-d\" . eldoc))\n  :config\n  ;; 将 eglot-events-buffer-size 设置为 0 后将关闭显示 *EGLOT event* bufer，不便于调\n  ;; 试问题。也不能设置的太大，否则可能影响性能。\n  (setq eglot-events-buffer-size (* 1024 1024 1))\n\n  ;; 将 flymake-no-changes-timeout 设置为 nil 后，eglot 保存 buffer 内容后，经过 idle\n  ;; time 才会向LSP 发送诊断请求。\n  (setq eglot-send-changes-idle-time 0.1)\n\n  ;; 当最后一个源码 buffer 关闭时自动关闭 eglot server。\n  (customize-set-variable 'eglot-autoshutdown t)\n  (customize-set-variable 'eglot-connect-timeout 60)\n\n  ;;不给所有 prog-mode 都开启 eglot，否则当它没有 language server 时 eglot 报错。\n  ;;\n  ;;由于 treesit-auto 已经对 major-mode 做了 remap ，需要对 xx-ts-mode-hook 添加 hook，\n  ;;而不是以前的 xx-mode-hook, 否则添加到 xx-mode-hook 的内容不会被自动执行。\n  (add-hook 'c-ts-mode-hook #'eglot-ensure)\n  (add-hook 'go-ts-mode-hook #'eglot-ensure)\n  (add-hook 'bash-ts-mode-hook #'eglot-ensure)\n  (add-hook 'python-mode-hook #'eglot-ensure)\n  (add-hook 'python-ts-mode-hook #'eglot-ensure)\n  (add-hook 'rust-ts-mode-hook #'eglot-ensure)\n  (add-hook 'rust-mode-hook #'eglot-ensure)\n  (add-hook 'yaml-mode-hook #'eglot-ensure)\n  (add-hook 'yaml-ts-mode-hook #'eglot-ensure)\n\n  (setq eglot-ignored-server-capabilities\n        '(\n          ;;:hoverProvider ;; 显示光标位置信息。\n          ;;:documentHighlightProvider ;; 高亮当前 symbol。\n          ;;:inlayHintProvider ;; 显示 inlay hint 提示。\n          ))\n\n  ;; 加强高亮的 symbol 效果。\n  ;;(set-face-attribute 'eglot-highlight-symbol-face nil :background \"#b3d7ff\")\n\n  ;; t: true, false: :json-false(不是 nil)。\n  ;; gopls 配置参数: https://github.com/golang/tools/blob/master/gopls/doc/settings.setq\n  (setq-default eglot-workspace-configuration\n                '((:gopls . ((staticcheck . t)\n                             (usePlaceholders . :json-false)\n                             ;; gopls 默认设置 GOPROXY=Off, 可能会导致 package 缺失进\n                             ;; 而引起补全异常. 开启 allowImplicitNetworkAccess 后将\n                             ;; 关闭 GOPROXY=Off.\n                             ;;(allowImplicitNetworkAccess . t)\n                             )))))\n#+end_src\n\nconsult-eglot 提供 ~consult-eglot-symbols~ 函数，方便选择 workspace 中的 symbol：\n\n#+begin_src emacs-lisp\n(use-package consult-eglot\n  :after (eglot consult))\n#+end_src\n\n下载 [[https://github.com/blahgeek/emacs-lsp-booster][emacs-lsp-booster]] 可执行程序，然后使用 emacs-lsp-booster 来加速 eglot 的响应性能：\n\n#+begin_src emacs-lisp\n(use-package eglot-booster\n  :vc (:url \"https://github.com/jdtsmith/eglot-booster\")\n  :after (eglot)\n  :config (eglot-booster-mode))\n#+end_src\n\n** eldoc\n\neldoc 在 minibuffer echo-area 或 eldoc buffer 中显示文档和函数签名信息。\n\n#+begin_src emacs-lisp\n(use-package eldoc\n  :after (eglot)\n  :bind\n  (:map eglot-mode-map (\"C-c C-d\" . eldoc))\n  :config\n  (setq eldoc-idle-delay 0.1)\n\n  ;; 打开 eldoc-buffer 时关闭 echo-area 显示, eldoc-buffer 会跟随显示 hover 信息, 如\n  ;; 函数签名。\n  (setq eldoc-echo-area-prefer-doc-buffer t)\n\n  ;; 在屏幕右侧显示 eldoc-buffer\n  (add-to-list 'display-buffer-alist\n               '(\"^\\\\*eldoc.*\\\\*\"\n                 (display-buffer-reuse-window display-buffer-in-side-window)\n                 (dedicated . t)\n                 (side . right)\n                 (inhibit-same-window . t)))\n\n  ;; 将 minibuffer 窗口高度设为 1，可以确保只显示一行（默认为小数，表示 frame 高度占\n  ;; 比，会导致显示多行）。\n  (setq max-mini-window-height 1)\n  ;; 为 nil 时只单行显示 eldoc 信息.\n  (setq eldoc-echo-area-use-multiline-p nil)\n\n  ;; 一键显示和关闭 eldoc buffer。\n  (global-set-key (kbd \"M-`\")\n                  (lambda()\n                    (interactive)\n                    (if (get-buffer-window \"*eldoc*\")\n\t\t\t(delete-window (get-buffer-window \"*eldoc*\"))\n                      (display-buffer \"*eldoc*\")))))\n#+end_src\n\n注：eglot 不给 eldoc 提供在 echo-area 显示的结构化成员或函数签名信息, 但是可以在 =M-x\neldoc-doc-buffer(C-h-.)= 打开的 eldoc buffer 中会显示这些信息。\n\neldoc-box 在 frame 右上角或光标位置显示 eldoc-doc-buffer 的内容。\n\n#+begin_src emacs-lisp\n(use-package eldoc-box\n  :after (eglot eldoc)\n  :bind\n  (:map eglot-mode-map\n        (\"C-M-k\" . (lambda () (interactive) (eldoc-box-scroll-down 1)))\n        (\"C-M-j\" . (lambda () (interactive) (eldoc-box-scroll-up 1)))\n\t;; 按需弹出 posframe 来显示 eldoc buffer 内容。\n\t(\"C-c C-d\" . eldoc-box-help-at-point)\n\t)\n\n  :config\n  (setq eldoc-box-max-pixel-height 600)\n  (setq eldoc-box-max-pixel-width 1200)\n\n  ;; C-g 关闭弹出的 child frame。\n  (setq eldoc-box-clear-with-C-g t)\n\n  ;; 在右上角显示 eldoc 帮助；\n  ;;(add-hook 'eglot-managed-mode-hook #'eldoc-box-hover-mode t)\n\n  ;; 在光标位置显示 eldoc 帮助；\n  ;;(add-hook 'eglot-managed-mode-hook #'eldoc-box-hover-at-point-mode t)\n  )\n#+end_src\n\n** python\n\n=brew install python= 目前(2024.03.17)安装的是 python@12 版本，从该版本开始, 如果要 pip\n安装 python 包, 必须安装到用户自己的 venv 环境, 否则报错(error:\nexternally-managed-environment)。具体参考: https://docs.brew.sh/Homebrew-and-Python\n\n#+begin_src shell :tangle no\n$ brew reinstall python\n$ brew unlink python@3.12 \u0026\u0026 brew link python@3.12\n# 查看安装的位置\n$ ls -l $(brew --prefix python)/libexec/bin\n\n$ pip3 install  pygments\nerror: externally-managed-environment\n#+end_src\n\n创建一个 =~/.venv= python 虚拟环境, 然后将 pip 包安装到该环境中:\n#+begin_src shell :tangle no\nzj@a:~$ python3 -m venv .venv\nzj@a:~$ source ~/.venv/bin/activate\n# 安装相关的包到虚拟环境中\n(.venv) zj@a:~$ pip3 install pygments jinji2 ipython markdown flake8 yapf pyright grip debugpy\n\n# 将 /Users/alizj/.venv/bin 添加到 PATH 中，这样后续不需要每次手动 active\n# 更新 ~/.bashrc 中的 PATH： PATH=/Users/alizj/.venv/bin:$PATH\n#+end_src\n\n配置 Emacs 使用内置的 python-ts-mode 和 venv 虚拟环境:\n#+begin_src emacs-lisp\n;; 将 ~/.venv/bin 添加到 PATH 环境变量和 exec-path 变量中。\n(setq my-venv-path \"/Users/alizj/.venv/bin\")\n(setenv \"PATH\" (concat my-venv-path \":\" (getenv \"PATH\")))\n(setq exec-path (cons my-venv-path  exec-path))\n\n;; 指定 python.el 使用虚拟环境目录。\n(setq python-shell-virtualenv-root \"/Users/alizj/.venv\")\n\n(defun my/python-setup-shell (\u0026rest args)\n  (if (executable-find \"ipython3\")\n      (progn\n        ;; 使用 ipython3 作为 python shell.\n        (setq python-shell-interpreter \"ipython3\")\n        (setq python-shell-interpreter-args \"--simple-prompt -i --InteractiveShell.display_page=True\"))\n    (progn\n      ;; 查找 python-shell-virtualenv-root 中的解释器.\n      (setq python-shell-interpreter \"python3\")\n      (setq python-interpreter \"python3\")\n      (setq python-shell-interpreter-args \"-i\"))))\n\n;; 使用内置 python mode 和 LSP 来格式化代码（不适用 yapfify）\n(use-package python\n  :init\n  ;;(setq python-indent-guess-indent-offset t)\n  ;;(setq python-indent-guess-indent-offset-verbose nil)\n  ;;(setq python-indent-offset 2)\n  :hook\n  (python-mode . (lambda ()\n                   (my/python-setup-shell))))\n#+end_src\n\nPython LSP Server 使用 basedpyright，它是微软 VSCode 使用的 pyright 和 pyglance 的开\n源 fork 版本。\n\n安装 basepyright:\n\n#+begin_src shell :tangle no\nwhich  basedpyright || pip install basedpyright\n#+end_src\n\n配置 eglot 使用 basedpyright：\n\n#+begin_src emacs-lisp\n(add-to-list 'eglot-server-programs\n             '((python-mode python-ts-mode)\n               \"basedpyright-langserver\" \"--stdio\"))\n#+end_src\n\npyright _不使用_ pyenv ~.python-version~ 指定的 python 版本或 venv 来搜索依赖的 module，\n而是使用=pyrightconfig.json= 文件中配置的 venv 和 venvPath:\n+ venvPath：指定查找 venv 目录的上级目录，可以包含多个 venv 环境；\n+ venv：指定 venvPath 目录下的、使用的虚拟环境名称, pyright 在该 venv 中搜索依赖的\n  package;\n\n安装 =pyenv-pyright= 插件来方便的创建和更新 =pyrightconfig.json= 文件：\n\n#+begin_src bash :tangle ~/.emacs.d/init.sh\ngit clone https://github.com/alefpereira/pyenv-pyright.git $(pyenv root)/plugins/pyenv-pyright\n#+end_src\n\n使用方法：\n1. 使用 =pyenv local= 为项目指定 ~pyenv virtualenv~;\n2. 使用 =pyenv pyright= 来自动配置 =pyrightconfig.json= 使用上一步指定的 virtualenv；\n\npyright 假设源文件位于项目 scr 目录下，但实际可能会在多个其它子目录（甚至嵌套情况）中\n放置项目源码，即 =multi-root= 模式（对应于 vscode 中的多 worksapce 目录)，这时可能出现\n大量 import 错误，可以通过在项目根目录配置 =pyrightconfig.json= 文件来解决，例如（参考：\npython module [[https://github.com/microsoft/pyright/blob/main/docs/import-resolution.md][Import Resolution]]）：\n#+begin_src javascript :tangle no\n{\n    \"venv\": \"venv-2.7.18\",\n    \"venvPath\": \"/Users/zhangjun/.pyenv/versions\",\n    \"verboseOutput\": true,\n    \"reportMissingTypeStubs\": false,\n    \"executionEnvironments\": [\n        {\n            \"root\": \"scripts\",\n            \"extraPaths\": [\n                \".\",  // scripts 目录下 py 文件导入同级 py 文件的情况\n                \"scripts/appinstance_apply\"\n            ]\n        }\n    ]\n}\n#+end_src\n\nexecutionEnvironments：\n1. 列表中 root 指定各 workspace 的子目录，是有搜索优先级的，所以如果有相同路径前缀的\n   情况，应该从长到短依列出来：根据 python 文件的 from/import 语句来确定root 路径：即\n   从项目根目录（pyrightconfig.json 文件所在目录）开始到文件中导入路径最开始所在目录\n   之间的目录，都应该是 root。\n2. extraPaths 列表中的路径可以是绝对路径或相对路径（相对于 pyrightconfig.json 文件），\n   用于添加额外的 python module 搜索路径；\n   + 添加 \".\" 是因为需要将 scripts 所在的目录也添加到 module 搜索路径，而不仅仅是 scripts 下的子目录；\n3. 官方的实例参考：[[https://github.com/microsoft/pyright/blob/main/docs/configuration.md#sample-config-file][Sample Config File]] 和 [[https://github.com/microsoft/pyright/blob/main/packages/pyright-internal/src/tests/testState.test.ts][testState.test.ts]]；\n\n[[https://github.com/Microsoft/pyright/issues/21][pyright 不支持 python 2.x]]，如果在上面文件配置 =\"pythonVersion\": \"2.7\"= 则会报错。\n\n** go\n\n安装最新 gopls 工具:\n\n#+begin_src bash :tangle ~/.emacs.d/init.sh\ngo install golang.org/x/tools/gopls@latest\n#+end_src\n\n使用 Emacs 内置的 go-ts-mode, 故不需要再单独安装 go-mode 包：\n\n#+begin_src emacs-lisp\n(require 'go-ts-mode)\n;; go 使用 TAB 缩进。\n(add-hook 'go-ts-mode-hook (lambda () (setq indent-tabs-mode t)))\n#+end_src\n\n设置 go 环境变量, eglot 启动 gopls 时传递它们:\n\n#+begin_src emacs-lisp\n(dolist (env '((\"GOPATH\" \"/Users/alizj/go\")\n               (\"GOPROXY\" \"https://goproxy.cn,https://goproxy.io,direct\")\n               (\"GOPRIVATE\" \"*.alibaba-inc.com\")\n\t       (\"GOOS\" \"linux\")\n\t       (\"GOARCH\" \"arm64\")))\n  (setenv (car env) (cadr env)))\n#+end_src\n\n查看本地和在线 go 文档:\n\n#+begin_src emacs-lisp\n(require 'go-ts-mode)\n;; 查看光标处符号的本地文档.\n(define-key go-ts-mode-map (kbd \"C-c d .\") #'godoc-at-point)\n\n;; 查看 go std 文档。\n(defun my/browser-gostd ()\n  (interactive)\n  (xwidget-webkit-browse-url \"https://pkg.go.dev/std\"))\n(define-key go-ts-mode-map (kbd \"C-c d s\") 'my/browser-gostd)\n\n;; 搜索 pkg.go.dev 在线 web 文档。\n(defun my/browser-pkggo (query)\n  (interactive \"ssearch: \")\n  (xwidget-webkit-browse-url\n   (concat \"https://pkg.go.dev/search?q=\" (string-replace \" \" \"%20\" query)) t))\n(define-key go-ts-mode-map (kbd \"C-c d w\") 'my/browser-pkggo) ;; 助记: w -\u003e web\n#+end_src\n\n安装或更新工具：\n#+begin_src emacs-lisp\n;; (setq gofmt-command \"golangci-lint\")\n;; (setq gofmt-args \"run --config /Users/alizj/.golangci.yml --fix\")\n\n(defvar go--tools '(\"golang.org/x/tools/gopls\"\n                    \"github.com/rogpeppe/godef\"\n                    \"golang.org/x/tools/cmd/goimports\"\n                    \"honnef.co/go/tools/cmd/staticcheck\"\n                    \"github.com/go-delve/delve/cmd/dlv\"\n                    \"github.com/zmb3/gogetdoc\"\n                    \"github.com/josharian/impl\"\n                    \"github.com/cweill/gotests/...\"\n                    \"github.com/fatih/gomodifytags\"\n                    \"github.com/golangci/golangci-lint/cmd/golangci-lint\"\n                    \"github.com/davidrjenni/reftools/cmd/fillstruct\"))\n\n(defun go-update-tools ()\n  (interactive)\n  (unless (executable-find \"go\")\n    (user-error \"Unable to find `go' in `exec-path'!\"))\n  (message \"Installing go tools...\")\n  (dolist (pkg go--tools)\n    (set-process-sentinel\n     (start-process \"go-tools\" \"*Go Tools*\" \"go\" \"install\" \"-v\" \"-x\" (concat pkg \"@latest\"))\n     (lambda (proc _)))))\n\n(use-package go-fill-struct)\n\n(use-package go-impl)\n\n;; 自动为 struct field 添加 json tag。\n(use-package go-tag\n  :init\n  (setq go-tag-args (list \"-transform\" \"camelcase\"))\n  :config\n  (require 'go-ts-mode)\n  (define-key go-ts-mode-map (kbd \"C-c t a\") #'go-tag-add)\n  (define-key go-ts-mode-map (kbd \"C-c t r\") #'go-tag-remove))\n\n(use-package go-playground\n  :commands (go-playground-mode)\n  :config\n  (setq go-playground-init-command \"go mod init\"))\n#+end_src\n\n调试:\n1. 如果一个 git 项目下有多个 go module, 则需要在上层目录创建 workspace, 并将各 module\n   加入其中，否则可能出现 package import 失败的情况：\n\n   #+begin_src bash :tangle no\n   go work init\n   go work use ./path/to/module1 ./path/to/module2\n   #+end_src\n\n2. 如果补全或自动提示异常, 执行 ~M-x eglot-events-buffer~ 看是否有报错(例如 GOPROXY=Off\n   导致的问题.)\n\n** rust\n\n将 Rust 工具链目录添加到 PATH 环境变量和 Emacs 变量 exec-path 中:\n+ =~/.cargo/bin= 和 =/opt/homebrew/opt/rustup/bin= 目录已经在初始化时添加到 PATH 和 exec-path 中。\n\n#+begin_src emacs-lisp\n;; brew install sccache\n(setenv \"RUSTC_WRAPPER\" \"/opt/homebrew/bin/sccache\")\n#+end_src\n\n配置 rust-mode:\n\n#+begin_src emacs-lisp\n;; https://github.com/jwiegley/dot-emacs/blob/master/init.org#rust-mode\n(use-package rust-mode\n  :after (eglot)\n  :init\n  (require 'rust-ts-mode)\n  ;; rust-mode 作为 rust-ts-mode 而非 prog-mode 的子 mode。\n  (setq rust-mode-treesitter-derive t)\n  :config\n\n  ;; rust-analyzer 使用 rustfmt 来格式化代码\n  ;;(setq rust-format-on-save t)\n  (setq rust-rustfmt-switches '(\"--edition\" \"2021\"))\n\n  ;; treesit-auto 默认不将 XX-mode-hook 添加到对应的 XX-ts-mode-hook 上, 需要手动指定。\n  (setq rust-ts-mode-hook rust-mode-hook)\n\n  ;; rust 建议使用空格而非 TAB 来缩进。\n  (add-hook 'rust-ts-mode-hook (lambda () (setq indent-tabs-mode nil)))\n\n  ;; 参数列表参考：https://rust-analyzer.github.io/manual.html#configuration\n  (add-to-list\n   'eglot-server-programs\n   '((rust-ts-mode rust-mode) .\n     (\"rust-analyzer\"\n      :initializationOptions\n      (\n       :rustfmt\n       (\n\t:extraArgs [\"+nightly\"]\n\t)\n       :completion (:fullFunctionSignatures (:enable t))\n       ;; 20240910 不能关闭 checkOnSave，否则 flymake diagnose 可能不生效。\n       ;;:checkOnSave :json-false\n       :check\n       (\n        :command \"clippy\"\n        ;;https://esp-rs.github.io/book/tooling/visual-studio-code.html#using-rust-analyzer-with-no_std\n        :allTargets :json-false\n\t;; 不发送 --workspace 给 cargo check, 只检查当前 package.\n\t;; 20240910 可能导致基于 workspace 的 标准库 lsp 不生效，故不能设置。\n        :workspace :json-false\n        )\n       ;;:procMacro (:attributes (:enable t) :enable :json-false)\n       :cargo\n       (\n        ;;:buildScripts (:enable :json-false)\n        ;;:features \"all\"\n        ;;:noDefaultFeatures t\n        :cfgs (:tokio_unstable \"\")\n        ;;:autoreload :json-false\n        )\n       :diagnostics\n       (\n\t;;:enable :json-false\n\t:disabled [\"unresolved-proc-macro\" \"unresolved-macro-call\"]\n\t)\n       :inlayHints\n       (\n\t:bindingModeHints (:enable t)\n\t:closureCaptureHints (:enable t)\n\t:closureReturnTypeHints (:enable t)\n\t:lifetimeElisionHints (:enable t)\n\t:expressionAdjustmentHints (:enable t)\n\t)\n       ;; :linkedProjects\n       ;; [\n       ;;  \"/Users/alizj/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/Cargo.toml\",\n       ;;  \"/Users/alizj/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/Cargo.toml\",\n       ;;  \"/Users/alizj/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/proc_macro/Cargo.toml\",\n       ;;  \"/Users/alizj/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/test/Cargo.toml\"\n       ;;  ]\n       )))))\n#+end_src\n\n创建如下全局 rustfmt 配置文件；\n\n#+begin_src toml :tangle ~/.rustfmt.toml\n# 配置选项参考：\n# https://rust-lang.github.io/rustfmt/?version=v1.7.1\u0026search=#chain_width\nedition = \"2021\"\n# 确保方法链式调用时，每行一个方法调用，这样 inlayhint 会显示上一个方法的返回值类型。\nchain_width = 0\n# 是否格式化为单行函数。\nfn_single_line = true\n# 最大行长度，超过后自动折行。\nmax_width = 80\n# 函数各参数单独一行\nfn_args_layout = \"Vertical\"\n\n# 以下是 unstable features，只能在 nightly channel 使用。\nwrap_comments = true\nnormalize_comments = true\nformat_code_in_doc_comments = true\ncomment_width = 80\nformat_strings = true\nimports_granularity = \"Crate\"\nenum_discrim_align_threshold = 20\n#+end_src\n\nrust-playground 快速测试环境:\n1. BUGFIX: https://github.com/grafov/rust-playground/pull/11/files\n2. 设置 Cargo.toml 模板文件变量中 edition 值为 2021（默认是 2018）;\n\n#+begin_src emacs-lisp\n(use-package rust-playground\n  :config\n  (setq rust-playground-cargo-toml-template\n        \"[package]\nname = \\\"foo\\\"\nversion = \\\"0.1.0\\\"\nauthors = [\\\"opsnull \u003cgeekard@qq.com\u003e\\\"]\nedition = \\\"2021\\\"\n\n[dependencies]\"))\n#+end_src\n\neglot-x 为 Rust 提供了几个好用的命令：\n1. =M-x eglot-x-reload-workspace= ：在 Cargo.toml 文件发生变化时手动执行而不需要重启 eglot；\n2. =M-x eglot-x-expand-macro= ：展开宏定义（或者使用 cargo expand 命令来显示宏展开后的定义）；\n3. =M-x eglot-x-open-external-documentation= ：用浏览器查看光标处的 rust 文档；\n#+begin_src emacs-lisp\n(use-package eglot-x\n  :after (eglot rust-mode)\n  :vc (:url \"https://github.com/nemethf/eglot-x\")\n  :init\n  (require 'rust-ts-mode) ;; 绑定 rust-ts-mode-map 需要。\n  :config\n  (eglot-x-setup))\n#+end_src\n\n查看本地和在线文档:\n\n#+begin_src emacs-lisp\n(with-eval-after-load 'rust-ts-mode\n  ;; 使用 xwidget 打开光标处 symbol 的本地 crate 文档（需要先执行 cargo doc 命令来生成本地文档）\n  ;; RA bug 导致查看 macro 文档的链接是错的：https://github.com/rust-lang/rust-analyzer/issues/16724\n  (define-key rust-ts-mode-map (kbd \"C-c d .\") #'eglot-x-open-external-documentation)\n\n  ;; 查看本地 rust std 文档;\n  (defun my/browser-ruststd ()\n    (interactive)\n    (xwidget-webkit-browse-url \"file:///Users/alizj/.rustup/toolchains/stable-aarch64-apple-darwin/share/doc/rust/html/std/index.html\"  t))\n  (define-key rust-ts-mode-map (kbd \"C-c d s\") 'my/browser-ruststd)\n\n  ;; 在线 https:://docs.rs/ 搜索文档.\n  (defun my/browser-docsrs (query)\n    (interactive \"ssearch: \")\n    (xwidget-webkit-browse-url\n     (concat \"https://docs.rs/releases/search?query=\" (string-replace \" \" \"%20\" query)) t))\n  (define-key rust-ts-mode-map (kbd \"C-c d w\") 'my/browser-docsrs) ;; 助记: w -\u003e web\n\n  ;; 在线搜索 crate 包。\n  (defun my/search-crates.io (query)\n    (interactive \"ssearch: \")\n    (xwidget-webkit-browse-url\n     (concat \"https://crates.io/search?q=\" (string-replace \" \" \"%20\" query)) t))\n  (global-set-key (kbd \"C-c d c\") 'my/browser-docsrs) ;; 助记: c -\u003e crates.io\n  )\n#+end_src\n\ncargo package 不再维护, 故切换到 cargo-mode package, 它提供了 Cargo.toml 管理命令。\n+ C-c a e(cargo-execute-task) ：列出所有支持的 cargo task，如 build/test 等，同时可以添加和删除依赖\n  （需要指定 PREFIX 命令来输入依赖名称）。\n\n#+begin_src emacs-lisp\n(use-package cargo-mode\n  :after (rust-mode)\n  :custom\n  ;; cargo-mode 缺省为 compilation buffer 使用 comint mode, 设置为 nil 使用 compilation。\n  (cargo-mode-use-comint nil)\n  :hook\n  (rust-ts-mode . cargo-minor-mode)\n  :config\n  ;; 自动滚动显示 compilation buffer 内容。\n  (setq compilation-scroll-output t))\n#+end_src\n\n其他技巧:\n1. 创建一个 struct 对象时, 可以使用 eglot code-action 来自动填充对象成员;\n3. Cargo.toml 文件发生变化时, rust-analyzer 不会自动更新处理, 需要重启 eglot 才能自动补全新的 crate。\n    两个解决办法:\n     1. 使用 eglot-x 中的 M-x eglot-x-reload-workspace 命令;\n     2. 或者先将 =所有依赖= 提前添加到 Cargo.toml 文件, 然后再启动 eglot;\n\n** markdown\n\n安装依赖工具：\n\n#+begin_src bash :tangle ~/.emacs.d/init.sh\nbrew install multimarkdown\npip3 install grip\n#+end_src\n\nmultimarkdown 将 markdown 转换为 html 进行 preview，可以结合 xwidget webkit 或 grip\n进行实时预览：\n\n#+begin_src emacs-lisp\n(use-package markdown-mode\n  :commands (markdown-mode gfm-mode)\n  :mode\n  ((\"README\\\\.md\\\\'\" . gfm-mode)\n   (\"\\\\.md\\\\'\" . markdown-mode)\n   (\"\\\\.markdown\\\\'\" . markdown-mode))\n  :init\n  (when (executable-find \"multimarkdown\")\n    (setq markdown-command \"multimarkdown\"))\n  (setq markdown-enable-wiki-links t)\n  (setq markdown-italic-underscore t)\n  (setq markdown-asymmetric-header t)\n  (setq markdown-make-gfm-checkboxes-buttons t)\n  (setq markdown-gfm-uppercase-checkbox t)\n  (setq markdown-fontify-code-blocks-natively t)\n  (setq markdown-gfm-additional-languages \"Mermaid\")\n  (setq markdown-content-type \"application/xhtml+xml\")\n  (setq markdown-css-paths\n\t'(\"https://cdn.jsdelivr.net/npm/github-markdown-css/github-markdown.min.css\"\n          \"https://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/styles/github.min.css\"))\n  (setq markdown-xhtml-header-content \"\n\u003cmeta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'\u003e\n\u003cstyle\u003e\nbody {\n  box-sizing: border-box;\n  max-width: 740px;\n  width: 100%;\n  margin: 40px auto;\n  padding: 0 10px;\n}\n\u003c/style\u003e\n\u003clink rel='stylesheet' href='https://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/styles/default.min.css'\u003e\n\u003cscript src='https://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/highlight.min.js'\u003e\u003c/script\u003e\n\u003cscript\u003e\ndocument.addEventListener('DOMContentLoaded', () =\u003e {\n  document.body.classList.add('markdown-body');\n  document.querySelectorAll('pre code').forEach((code) =\u003e {\n    if (code.className != 'mermaid') {\n      hljs.highlightBlock(code);\n    }\n  });\n});\n\u003c/script\u003e\n\u003cscript src='https://unpkg.com/mermaid@8.4.8/dist/mermaid.min.js'\u003e\u003c/script\u003e\n\u003cscript\u003e\nmermaid.initialize({\n  theme: 'default',  // default, forest, dark, neutral\n  startOnLoad: true\n});\n\u003c/script\u003e\n\"))\n#+end_src\n\n使用 grip 来预览 markdown 文件，它调用 github markdown API 来渲染文件，从而确保渲染后\n风格和 Github一致。为了避免 API 调用频率限制，可以创建一个空 scop 的 Access Token，然\n后将 username 和 token 保存到 =~/.authinfo.gpg= 文件中：\n\n#+begin_src bash :tangle no\nmachine api.github.com login geekard@qq.com password YOUR_TOKEN\n#+end_src\n\n在 Markdown Buffer 中，执行 =M-x grip-mode= 来启用实时预览，然后可以执行如下命令：\n+ M-x grip-start-preview\n+ M-x grip-stop-preview\n+ M-x grip-restart-preview\n+ M-x grip-browse-preview\n#+begin_src emacs-lisp\n(use-package grip-mode\n  :defer\n  :after (markdown-mode)\n  :config\n  (setq grip-preview-use-webkit nil)\n  (setq grip-preview-host \"127.0.0.1\")\n  ;; 保存文件时才更新预览。\n  (setq grip-update-after-change nil)\n  ;; 从 ~/.authinfo 文件获取认证信息。\n  (require 'auth-source)\n  (let ((credential (auth-source-user-and-password \"api.github.com\")))\n    (setq grip-github-user (car credential)\n          grip-github-password (cadr credential)))\n  (define-key markdown-mode-command-map (kbd \"g\") #'grip-mode))\n#+end_src\n\n为 markdown 文件添加目录：\n#+begin_src emacs-lisp\n(use-package markdown-toc\n  :after(markdown-mode)\n  :config\n  (define-key markdown-mode-command-map (kbd \"r\") #'markdown-toc-generate-or-refresh-toc))\n#+end_src\n\n** yaml\n\n使用 emacs 内置的 yaml-ts-mode。\n\n安装 yaml 语言服务器：\n\n#+begin_src shell :tangle no\nwhich yaml-language-server || npm install -g yaml-language-server\n#+end_src\n\nyaml 格式说明：\n1. 不使用 TAB 而使用空格缩进；\n2. 对于多行字符串，使用 name: | 格式，后续第一行缩进必须大于 name: 所在行，而且以后续\n   以第一行缩进为准来删除后续各行前面的空白，所以各行的缩进必须大于等于第一行的缩进，\n   超过的部分空白得以保留；\n\n** shell\n\nEmacs 使用 =bash-ts-mode= 来编辑 shell 脚本。\n\n安装 bash language server:\n\n#+begin_src bash :tangle ~/.emacs.d/init.sh\nnpm i -g bash-language-server\n#+end_src\n\nbash language server 使用 shellcheck 工具来做语法检查和静态分析:\n\n#+begin_src bash ~/.emacs.d/init.sh\nbrew install shellcheck\n#+end_src\n\n设置脚本缩进规则：\n#+begin_src emacs-lisp\n(setq sh-basic-offset 4)\n(setq sh-indentation 4)\n#+end_src\n\n参考：\n1. [[https://google.github.io/styleguide/shellguide.html][Google Shell Style Guide]]\n\n** clang\n\n安装 llvm/clang/clang-format 包:\n\n#+begin_src bash :tangle ~/.emacs.d/init.sh\nbrew install llvm lld clang-format\n\nexport LDFLAGS=\"-L/opt/homebrew/opt/llvm/lib\"\nexport CPPFLAGS=\"-I/opt/homebrew/opt/llvm/include\"\nexport PATH=\"/opt/homebrew/opt/llvm/bin:$PATH\"\n\n# 打印格式化配置参数\nclang-format --dump-config\n#+end_src\n\n将 llvm bin 目录添加到 emacs：\n\n#+begin_src emacs-lisp\n(setq my-llvm-path \"/opt/homebrew/opt/llvm/bin\")\n(setenv \"PATH\" (concat my-llvm-path \":\" (getenv \"PATH\")))\n(setq exec-path (cons my-llvm-path  exec-path))\n#+end_src\n\n创建全局 ~/.clang-format 文件，也可以在各 project root 目录创建项目配置文件，主要配置\n的是：\n1. Tab 和 Indent 缩进；\n2. 不对头文件进行排序，防止编译报错；\n\n#+begin_src text :tangle ~/.clang-format\n# clang-format configuration file. Intended for clang-format \u003e= 11.\n#\n# For more information, see:\n#\n#   Documentation/process/clang-format.rst\n#   https://clang.llvm.org/docs/ClangFormat.html\n#   https://clang.llvm.org/docs/ClangFormatStyleOptions.html\n\n# linux 内核开发风格：\n# https://raw.githubusercontent.com/torvalds/linux/master/.clang-format\n---\n# 基本配置\nDisableFormat: false\nLanguage:        Cpp  # 可以是 'Cpp', 'Java', 'JavaScript', 'Proto', 'TableGen' 等等\nBasedOnStyle:    WebKit  # 基于 WebKit 风格，因为它最接近 Linux 内核的风格\n\n# 缩进\nIndentWidth:     8  # 缩进宽度\nTabWidth:        8  # 制表符宽度\nUseTab:          ForIndentation  # 使用制表符进行缩进，空格用于对齐\n\n# 换行\nAllowShortIfStatementsOnASingleLine: false  # 禁止 if 语句在单行内\nAllowShortLoopsOnASingleLine: false  # 禁止循环语句在单行内\nAlwaysBreakBeforeMultilineStrings: true  # 多行字符串之前总是换行\nBreakBeforeBraces: Linux  # 使用 Linux 风格的大括号位置\nColumnLimit: 100  # 每行的字符限制\n\n# 空格\nSpaceBeforeParens: ControlStatements  # 在控制语句的括号前加空格（if, for, while, 等）\nSpaceAfterCStyleCast: true  # C 风格的强制类型转换后加空格\n\n# 注释\nCommentPragmas: '^ IWYU pragma:'  # 支持 IWYU pragmas 注释\nIndentExternBlock: AfterExternBlock  # 在 extern \"C\" 块后进行缩进\n\n# 包含文件\nSortIncludes: false  # 禁用头文件排序，防止编译出错。\nIncludeBlocks: Preserve  # 保持头文件分组\n\n# 函数定义\nAlignTrailingComments: true  # 尾部注释对齐\nAlignConsecutiveDeclarations: true  # 连续的声明对齐\n\n# 其他\nNamespaceIndentation: All  # 所有命名空间内的代码都缩进\nReflowComments: true  # 重新格式化注释\n\n# 特定于 C 语言的设置\nStandard:        Cpp11  # 使用 C++11 标准\n#+end_src\n\n** tempel\n\n#+begin_src emacs-lisp\n(use-package tempel\n  :bind\n  ((\"M-+\" . tempel-complete)\n   (\"M-*\" . tempel-insert))\n  :init\n  ;; 自定义模板文件。\n  (setq tempel-path \"/Users/alizj/emacs/templates\")\n  (add-hook 'conf-mode-hook 'tempel-setup-capf)\n  (add-hook 'prog-mode-hook 'tempel-setup-capf)\n  (add-hook 'text-mode-hook 'tempel-setup-capf)\n\n  (defun tempel-setup-capf ()\n    (setq-local completion-at-point-functions (cons #'tempel-expand completion-at-point-functions)))\n  ;; 确保 tempel-setup-capf 位于 eglot-managed-mode-hook 前，这样 corfu 才会显示\n  ;; tempel 的自动补全。\n  ;; https://github.com/minad/tempel/issues/103#issuecomment-1543510550\n  (add-hook #'eglot-managed-mode-hook 'tempel-setup-capf))\n\n(use-package tempel-collection)\n#+end_src\n\n** compilation\n\n#+begin_src emacs-lisp\n;; https://gitlab.com/skybert/my-little-friends/-/blob/master/emacs/.emacs#L295\n(setq compilation-ask-about-save nil\n      compilation-always-kill t\n      compilation-scroll-output 'first-error ;; 滚动显示到第一个出错位置。\n      compilation-context-lines 10\n      compilation-skip-threshold 2\n      ;;compilation-window-height 100\n      )\n\n(define-key compilation-mode-map (kbd \"q\") 'delete-window)\n\n;; 显示 shell 转义字符的颜色。\n(add-hook 'compilation-filter-hook\n          (lambda ()\n\t    (ansi-color-apply-on-region (point-min) (point-max))))\n\n;; 编译结束且失败时自动切换到 compilation buffer。\n(setq compilation-finish-functions\n      (lambda (buf str)\n        (if (null (string-match \".*exited abnormally.*\" str))\n            ;; 没有错误, 什么也不做。\n            nil\n          ;; 有错误时切换到 compilation buffer。\n          (switch-to-buffer-other-window buf)\n          (end-of-buffer))))\n#+end_src\n\n** citre\n\ncitre 是基于 Ctags（Universal Ctags 版本）的代码浏览器工具，也支持[[https://github.com/universal-ctags/citre/blob/master/docs/user-manual/citre-global.md][集成使用 GNU global\n的 GTAGS 文件]]。\n\n安装 GNU global 和 pygments, global 依赖并自动安装 universal-ctags, 通过 pygments 能\n生成更丰富的TAG 内容，同时支持 reference 搜索。\n+ https://github.com/universal-ctags/citre/blob/master/docs/user-manual/citre-global.md\n+ global 默认使用 brew 安装的 python@3.12 和 pygments, 而不能直接使用 pip install\n  pygments.\n\n#+begin_src bash :tangle ~/.emacs.d/init.sh\nbrew install global pygments # 提供 global、gtags 命令, gtags 使用 pygments 支持多语言\n\n# 在 ~/.bashrc 中添加如下配置：\n# 统一的 tags 文件目录\nexport GTAGSOBJDIRPREFIX=~/.cache/gtags/\nmkdir $GTAGSOBJDIRPREFIX\nexport GTAGSCONF=/opt/homebrew/opt/global/share/gtags/gtags.conf\n# 使用 pygments 支持更多的语言，支持 reference 搜索。\nexport GTAGSLABEL=pygments\n#+end_src\n\n创建和更新 GNU global GTAGS 文件（保存到 GTAGSOBJDIRPREFIX 环境变量指定的位置，如\n~/.cache/gtags/)：\n+ M-x citre-global-create-database\n+ M-x citre-global-update-database\n\n#+begin_src shell :tangle no\nzj@a:~/.cache/gtags/Users/alizj/go/src/**/bp-agent$ ls -l\ntotal 2.8M\n-rw-r--r-- 1 alizj  32K  3 30 11:53 GPATH\n-rw-r--r-- 1 alizj 600K  3 30 11:53 GRTAGS  # reference tags\n-rw-r--r-- 1 alizj 1.9M  3 30 11:53 GTAGS   # tags\n#+end_src\n\n注意：以下两个命令创建 Universal Ctags 的 ctags 文件（项目有 .tags/ 目录或 .tags 或\ntags 文件），而非 GNU global GTAGS 文件，不支持 references，故不建议使用：\n+ M-x citre-create-tags-file\n+ M-x citre-update-tags-file\n\n如果误使用了上面的命令创建 ctags 文件则后续使用 xref-find-references 会 hang，需要删\n除。\n\n对于开启了 citre-mode 的 buffer，citre 向 xref-backend-functions 中添加\ncitre-xref-backend, 而且位于列表的开始，这样 xref 先使用 citre-xref-backend，当无返回\n结果时再查找其它注册的 xref backend 如eglot-xref-backend 等。\n+ xref-backend-functions 可能会被添加多个 backend，但 xref 使用第一个返回非空数据的 backend，而忽\n  略后续 backend。\n\n新版本的 citre-xref-backend 支持自动集成 eglot，它先使用 eglot 的结果，如果为空，再使\n用 tags 文件的结果作为后备，所以不管项目是否存在 tags 文件，都可以为所有 prog-mode 开\n启 citre-mode。\n\n其他 xref 特性，如 imenu/xref-find-references/xref-find-definitions 等都会使用 citre\n提供的输入。同时 xref 和 consult 结合，可以使用 consult 来预览 xref 的结果。\n\n配置 citre：\n\n#+begin_src emacs-lisp\n(setenv \"GTAGSOBJDIRPREFIX\" (expand-file-name \"~/.cache/gtags/\"))\n(setenv \"GTAGSCONF\" (car (file-expand-wildcards \"/opt/homebrew/opt/global/share/gtags/gtags.conf\")))\n(setenv \"GTAGSLABEL\" \"pygments\")\n\n(use-package citre\n  :after (eglot)\n  :config\n  ;; 只使用支持 reference 的 GNU Global tags。\n  (setq citre-completion-backends '(global))\n  (setq citre-find-definition-backends '(global))\n  (setq citre-find-reference-backends '(global))\n  (setq citre-tags-in-buffer-backends  '(global))\n  (setq citre-auto-enable-citre-mode-backends '(global))\n  (setq citre-use-project-root-when-creating-tags t)\n  (setq citre-peek-file-content-height 20)\n\n  ;; 打开列表中的 major mode 文件且项目具有 global tags 文件时，才自动开启 citre。\n  (setq citre-auto-enable-citre-mode-modes\n\t'(\n\t  c-mode\n\t  c-ts-mode\n\t  rust-mode\n\t  rust-ts-mode\n\t  ;; go-mode\n\t  ;; go-ts-mode\n\t  ))\n\n  ;; 使用 eglot-managed-mode-hook 而非 find-file-hook，从而确保 citre-mode 在 eglot\n  ;; 启动后才开启。\n\n  ;; 执行 citre-auto-enable-citre-mode 而非 citre-mode 命令：\n\n  ;; 1. 前者会检查 citre-auto-enable-citre-mode-modes 变量中的 major mode 和项目是否\n  ;; 有 global tags文件，只有两者均满足时，才开启 citre。\n\n  ;; 2. 后者是不管 major mode 类型和是否有 tags 文件，均开启 citre。\n  (add-hook 'eglot-managed-mode-hook #'citre-auto-enable-citre-mode)\n\n  (define-key citre-mode-map (kbd \"s-.\") 'citre-jump)\n  (define-key citre-mode-map (kbd \"s-,\") 'citre-jump-back)\n  (define-key citre-mode-map (kbd \"s-?\") 'citre-peek-reference)\n  (define-key citre-mode-map (kbd \"s-p\") 'citre-peek)\n  (define-key citre-peek-keymap (kbd \"s-n\") 'citre-peek-next-line)\n  (define-key citre-peek-keymap (kbd \"s-p\") 'citre-peek-prev-line)\n  (define-key citre-peek-keymap (kbd \"s-N\") 'citre-peek-next-tag)\n  (define-key citre-peek-keymap (kbd \"s-P\") 'citre-peek-prev-tag))\n#+end_src\n\n* gptel\n\n在 ~/.authinfo.gpg 中添加 api.openai.com key，然后使用本地 socks5h 代理访问 API。\n+ azure 各 region 的访问速度测试：https://www.azurespeed.com/Azure/Latency\n\n#+begin_src emacs-lisp\n(use-package gptel\n  :ensure t\n  :config\n  (setq\n   gptel-default-mode 'org-mode\n   gptel-model 'gpt-4o\n   gptel-backend\n   (gptel-make-azure \"Azure\"\n     :protocol \"https\"\n     :host \"westus3ai.openai.azure.com\"\n     :endpoint \"/openai/deployments/4fouro/chat/completions?api-version=2024-02-15-preview\"\n     :stream t\n     :key #'gptel-api-key\n     :models '(gpt-4o))))\n#+end_src\n\n* 终端\n\n安装 vterm 依赖:\n\n#+begin_src bash :tangle ~/.emacs.d/init.sh\nbrew install cmake libtool exiftran\n#+end_src\n\n配置 vterm:\n\n#+begin_src emacs-lisp\n(use-package vterm\n  :hook\n  (vterm-mode . (lambda ()\n\t\t  ;; 关闭一些 mode，提升显示性能。\n\t\t  (setf truncate-lines nil)\n\t\t  (setq-local show-paren-mode nil)\n\t\t  (setq-local global-hl-line-mode nil)\n\t          (display-line-numbers-mode -1) ;; 不显示行号。\n\t\t  ;;; vterm buffer 使用 fixed pitch 的 mono 字体，否则部分终端表格之\n\t\t  ;;; 类的程序会对不齐。\n\t\t  (set (make-local-variable 'buffer-face-mode-face) 'fixed-pitch)\n\t\t  (buffer-face-mode t)))\n  :config\n  (setq vterm-set-bold-hightbright t)\n  (setq vterm-always-compile-module t)\n  (setq vterm-max-scrollback 100000)\n  (setq vterm-timer-delay 0.01) ;; nil: no delay\n  (add-to-list 'vterm-tramp-shells '(\"ssh\" \"/bin/bash\"))\n  ;; vterm buffer 名称，%s 为 shell 的 PROMPT_COMMAND 变量的输出。\n  (setq vterm-buffer-name-string \"*vt: %s\")\n  ;; 使用 M-y(consult-yank-pop) 粘贴剪贴板历史中的内容。\n  (define-key vterm-mode-map [remap consult-yank-pop] #'vterm-yank-pop)\n  (define-key vterm-mode-map (kbd \"C-l\") nil)\n  ;; 防止输入法切换冲突。\n  (define-key vterm-mode-map (kbd \"C-\\\\\") nil))\n\n(use-package multi-vterm\n  :after (vterm)\n  :config\n  (define-key vterm-mode-map  [(control return)] #'multi-vterm))\n#+end_src\n\nvterm-toggle:\n\n#+begin_src emacs-lisp\n(use-package vterm-toggle\n  :after (vterm)\n  :custom\n  ;; 由于 TRAMP 模式下关闭了 projectile，scope 不能设置为 'project。\n  ;;(vterm-toggle-scope 'dedicated)\n  (vterm-toggle-scope 'project)\n  :config\n  (global-set-key (kbd \"C-`\") 'vterm-toggle)\n  (global-set-key (kbd \"C-M-`\") 'vterm-toggle-cd)\n  (define-key vterm-mode-map (kbd \"M-RET\") #'vterm-toggle-insert-cd)\n  ;; 切换到空闲的 vterm buffer 并插入一个 cd 命令，或者创建一个新的 vterm buffer。\n  (define-key vterm-mode-map (kbd \"M-i\") 'vterm-toggle-cd-show)\n  (define-key vterm-mode-map (kbd \"M-n\") 'vterm-toggle-forward)\n  (define-key vterm-mode-map (kbd \"M-p\") 'vterm-toggle-backward)\n  (define-key vterm-copy-mode-map (kbd \"M-i\") 'vterm-toggle-cd-show)\n  (define-key vterm-copy-mode-map (kbd \"M-n\") 'vterm-toggle-forward)\n  (define-key vterm-copy-mode-map (kbd \"M-p\") 'vterm-toggle-backward))\n#+end_src\n\nvterm-extra 提供了 vterm buffer 命令行编辑的能力，结束后按 C-c C-c 自动粘贴到对应的 vterm 中：\n\n#+begin_src emacs-lisp\n(use-package vterm-extra\n  :vc (:url \"https://github.com/Sbozzolo/vterm-extra\")\n  :config\n  (define-key vterm-mode-map (kbd \"C-c C-e\") #'vterm-extra-edit-command-in-new-buffer))\n#+end_src\n\neshell:\n\n#+begin_src emacs-lisp\n(setq eshell-history-size 300)\n(setq explicit-shell-file-name \"/bin/bash\")\n(setq shell-file-name \"/bin/bash\")\n(setq shell-command-prompt-show-cwd t)\n(setq explicit-bash-args '(\"--noediting\" \"--login\" \"-i\"))\n;; 提示符只读\n(setq comint-prompt-read-only t)\n;; 命令补全\n(setq shell-command-completion-mode t)\n;; 高亮模式\n(autoload 'ansi-color-for-comint-mode-on \"ansi-color\" nil t)\n(add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on t)\n(setenv \"SHELL\" shell-file-name)\n(setenv \"ESHELL\" \"bash\")\n(add-hook 'comint-output-filter-functions 'comint-strip-ctrl-m)\n\n;; 在当前 frame 下方打开或关闭 eshell buffer。\n(defun startup-eshell ()\n  \"Fire up an eshell buffer or open the previous one\"\n  (interactive)\n  (if (get-buffer-window \"*eshell*\u003c42\u003e\")\n      (delete-window (get-buffer-window \"*eshell*\u003c42\u003e\"))\n    (progn\n      (eshell 42))))\n(global-set-key (kbd \"s-`\") 'startup-eshell)\n\n(add-to-list 'display-buffer-alist\n\t     '(\"\\\\*eshell\\\\*\u003c42\u003e\"\n\t       (display-buffer-below-selected display-buffer-at-bottom)\n\t       (inhibit-same-window . t)\n\t       (window-height . 0.33)))\n\n;; eshell history 使用 consult-history。\n(load-library \"em-hist.el\")\n(keymap-set eshell-hist-mode-map \"C-s\" #'consult-history)\n(keymap-set eshell-hist-mode-map \"C-r\" #'consult-history)\n;; 重置 M-r/s 快捷键，这样 consult-line 等可用。\n(define-key eshell-hist-mode-map (kbd \"M-r\") nil)\n(define-key eshell-hist-mode-map (kbd \"M-s\") nil)\n#+end_src\n\n* 其它\n\n使用 GNU 系列替换 MacOS 自带的 BSD 风格的 coreutils 包：\n\n#+begin_src bash :tangle  ~/.emacs.d/init.sh\nwhich tac || brew install coreutils\nwhich trash || brew install trash\n#+end_src\n\n#+begin_src emacs-lisp\n;; 避免 undo-more: No further undo information 报错.\n;; 10X bump of the undo limits to avoid issues with premature.\n;; Emacs GC which truncages the undo history very aggresively\n(setq undo-limit 800000)\n(setq undo-strong-limit 12000000)\n(setq undo-outer-limit 120000000)\n\n(global-auto-revert-mode 1)\n(setq revert-without-query (list \"\\\\.png$\" \"\\\\.svg$\")\n      auto-revert-verbose nil)\n\n(setq global-mark-ring-max 600)\n(setq mark-ring-max 600)\n(setq kill-ring-max 600)\n\n(use-package emacs\n  :init\n  ;; 粘贴于光标处, 而不是鼠标指针处。\n  (setq mouse-yank-at-point t)\n  (setq initial-major-mode 'fundamental-mode)\n  ;; 按中文折行。\n  (setq word-wrap-by-category t)\n  ;; 退出自动杀掉进程。\n  (setq confirm-kill-processes nil)\n  (setq use-short-answers t)\n  (setq confirm-kill-emacs #'y-or-n-p)\n  (setq ring-bell-function 'ignore)\n  ;; 不显示行号, 否则鼠标会飘。\n  (add-hook 'artist-mode-hook (lambda () (display-line-numbers-mode -1)))\n  ;; bookmark 发生变化时自动保存（默认是 Emacs 正常退出时保存）。\n  (setq bookmark-save-flag 1)\n  ;; 不创建 lock 文件。\n  (setq create-lockfiles nil)\n  ;; 启动 Server 。\n  (unless (and (fboundp 'server-running-p)\n               (server-running-p))\n    (server-start)))\n\n(use-package hydra :commands defhydra)\n#+end_src\n+ 参考： [[https://www.masteringemacs.org/article/mastering-key-bindings-emacs][Mastering Key Bindings in Emacs]]\n\n历史记录:\n#+begin_src emacs-lisp\n(use-package recentf\n  :config\n  (setq recentf-save-file \"~/.emacs.d/recentf\")\n\n  ;; 自动清理 recentf 记录（无效的、重复的、被 exclude 的等），防止已经删除的文件继续\n  ;; 出现在 consult-buffer 列表中\n  (setq recentf-auto-cleanup 'mode)\n\n  ;; 每 5min 以及 emacs 退出时保存 recentf-list。\n  ;; 20241017: 配置这两个参数后，recentf 将被清空。\n  ;;(run-at-time nil (* 5 60) 'recentf-save-list)\n  ;;(add-hook 'kill-emacs-hook #'recentf-save-list)\n\n  (setq recentf-max-menu-items 100)\n  (setq recentf-max-saved-items 100)\n\n  ;; recentf-exclude 的参数是正则表达式列表，不支持 ~ 引用家目录。\n  ;;; emacs-dashboard 不显示这里排除的文件。\n  (setq recentf-exclude\n\t`(\n\t  ,(recentf-expand-file-name \"~/.emacs.d/\\\\(straight\\\\|ln-cache\\\\|etc\\\\|var\\\\|.cache\\\\|backup\\\\|elfeed\\\\)/.*\")\n          ,(recentf-expand-file-name \"~/.emacs.d/\\\\(recentf\\\\|bookmarks\\\\|archived.org\\\\)\")\n\t  ,(recentf-expand-file-name \"~/go/mod/.*\")\n\t  ;; 不在 recentf 中记录 tramp 文件，防止 tramp 扫描时卡住。\n          ,tramp-file-name-regexp\n          \"^/tmp\"\n\t  \"\\\\.bak\\\\'\"\n\t  \"\\\\.gpg\\\\'\"\n\t  \"\\\\.gz\\\\'\"\n\t  \"\\\\.tgz\\\\'\"\n\t  \"\\\\.xz\\\\'\"\n\t  \"\\\\.zip\\\\'\"\n\t  \"^/ssh:\"\n\t  \"\\\\.png\\\\'\"\n          \"\\\\.jpg\\\\'\"\n\t  \"/\\\\.git/\"\n\t  \"\\\\.gitignore\\\\'\"\n\t  \"\\\\.log\\\\'\"\n\t  \"COMMIT_EDITMSG\"\n\t  \"\\\\.pyi\\\\'\"\n\t  \"\\\\.pyc\\\\'\"\n          \"/private/var/.*\"\n\t  \"^/usr/local/Cellar/.*\"\n\t  \".*/vendor/.*\"\n\t  \".*/target/.*\"\n\t  \"/Applications/.*\"\n          ,(concat package-user-dir \"/.*-autoloads\\\\.egl\\\\'\")))\n  (recentf-mode 1))\n#+end_src\n\ndired:\n\n#+begin_src emacs-lisp\n;; dired\n(setq my-coreutils-path \"/opt/homebrew/opt/coreutils/libexec/gnubin\")\n(setenv \"PATH\" (concat my-coreutils-path \":\" (getenv \"PATH\")))\n(setq exec-path (cons my-coreutils-path  exec-path))\n\n(use-package emacs\n  :config\n  (setq dired-dwim-target t)\n  ;; @see\n  ;; https://emacs.stackexchange.com/questions/5649/sort-file-names-numbered-in-dired/5650#5650\n  ;; 下面的参数只对安装了 coreutils (brew install coreutils) 的包有效，否则会报错。\n  (setq dired-listing-switches \"-laGh1v --group-directories-first\"))\n\n(use-package diredfl :config (diredfl-global-mode))\n#+end_src\n\n搜索 grep/isearch:\n\n#+begin_src emacs-lisp\n(use-package grep\n  :config\n  (setq grep-highlight-matches t)\n  (setq grep-find-ignored-directories\n        (append (list \".git\" \".cache\" \"vendor\" \"node_modules\" \"target\")\n                grep-find-ignored-directories))\n  (setq grep-find-ignored-files\n        (append (list \"*.blob\" \"*.gz\" \"TAGS\" \"projectile.cache\" \"GPATH\" \"GRTAGS\" \"GTAGS\" \"TAGS\" \".project\" )\n                grep-find-ignored-files)))\n\n(global-set-key \"\\C-cn\" 'find-dired)\n(global-set-key \"\\C-cN\" 'grep-find)\n\n(setq isearch-allow-scroll 'unlimited)\n;; 显示当前和总的数量。\n(setq isearch-lazy-count t)\n(setq isearch-lazy-highlight t)\n#+end_src\n\ndiff/ediff:\n\n#+begin_src emacs-lisp\n;; diff\n(use-package diff-mode\n  :init\n  (setq diff-default-read-only t)\n  (setq diff-advance-after-apply-hunk t)\n  (setq diff-update-on-the-fly t))\n\n(use-package ediff\n  :config\n  (setq ediff-keep-variants nil)\n  (setq ediff-split-window-function 'split-window-horizontally)\n  ;; 不创建新的 frame 来显示 Control-Panel。\n  (setq ediff-window-setup-function #'ediff-setup-windows-plain))\n#+end_src\n\n剪贴板和字符编码:\n\n#+begin_src emacs-lisp\n;; 使用系统剪贴板，实现与其它程序相互粘贴。\n(setq x-select-enable-clipboard t)\n(setq select-enable-clipboard t)\n(setq x-select-enable-primary t)\n(setq select-enable-primary t)\n\n;; UTF8 字符。\n(prefer-coding-system 'utf-8)\n(setq locale-coding-system 'utf-8\n      default-buffer-file-coding-system 'utf-8)\n(set-buffer-file-coding-system 'utf-8)\n(set-language-environment \"UTF-8\")\n(setq-default buffer-file-coding-system 'utf8)\n(set-default-coding-systems 'utf-8)\n(setenv \"LC_ALL\" \"zh_CN.UTF-8\")\n#+end_src\n\nbuffer/file:\n\n#+begin_src emacs-lisp\n(use-package ibuffer\n  :config\n  (setq ibuffer-expert t)\n  (setq ibuffer-use-other-window nil)\n  (setq ibuffer-movement-cycle nil)\n  (setq ibuffer-default-sorting-mode 'recency)\n  (setq ibuffer-use-header-line t)\n  (add-hook 'ibuffer-mode-hook #'hl-line-mode)\n  (global-set-key (kbd \"C-x C-b\") #'ibuffer))\n\n;; 保存 Buffer 时自动更新 #+LASTMOD: 时间戳。\n(setq time-stamp-start \"#\\\\+\\\\(LASTMOD\\\\|lastmod\\\\):[ \\t]*\")\n(setq time-stamp-end \"$\")\n(setq time-stamp-format \"%Y-%m-%dT%02H:%02m:%02S%5z\")\n;; #+LASTMOD: 必须位于文件开头的 line-limit 行内, 否则自动更新不生效。\n(setq time-stamp-line-limit 30)\n(add-hook 'before-save-hook 'time-stamp t)\n\n;; 以下自定义函数参考自：https://github.com/jiacai2050/dotfiles/blob/master/.config/emacs/i-edit.el\n(defun my/json-format ()\n  (interactive)\n  (save-excursion\n    (if mark-active\n        (json-pretty-print (mark) (point))\n      (json-pretty-print-buffer))))\n\n(defun my/delete-file-and-buffer (buffername)\n  \"Delete the file visited by the buffer named BUFFERNAME.\"\n  (interactive \"bDelete file\")\n  (let* ((buffer (get-buffer buffername))\n         (filename (buffer-file-name buffer)))\n    (when filename\n      (delete-file filename)\n      (message \"Deleted file %s\" filename)\n      (kill-buffer))))\n\n(defun my/diff-buffer-with-file ()\n  \"Compare the current modified buffer with the saved version.\"\n  (interactive)\n  (let ((diff-switches \"-u\")) ;; unified diff\n    (diff-buffer-with-file (current-buffer))\n    (other-window 1)))\n\n(defun my/copy-current-filename-to-clipboard ()\n  \"Copy `buffer-file-name' to system clipboard.\"\n  (interactive)\n  (let ((filename (if-let (f buffer-file-name)\n                      f\n                    default-directory)))\n    (if filename\n        (progn\n          (message (format \"Copying %s to clipboard...\" filename))\n          (kill-new filename))\n      (message \"Not a file...\"))))\n\n;; https://gitlab.com/skybert/my-little-friends/-/blob/2022-emacs-from-scratch/emacs/.emacs\n;; Rename current buffer, as well as doing the related version control commands to\n;; rename the file.\n(defun my/rename-this-buffer-and-file ()\n  \"Renames current buffer and file it is visiting.\"\n  (interactive)\n  (let ((filename (buffer-file-name)))\n    (if (not (and filename (file-exists-p filename)))\n        (message \"Buffer is not visiting a file!\")\n      (let ((new-name (read-file-name \"New name: \" filename)))\n        (cond\n         ((vc-backend filename) (vc-rename-file filename new-name))\n         (t\n          (rename-file filename new-name t)\n          (rename-buffer new-name)\n          (set-visited-file-name new-name)\n          (set-buffer-modified-p nil)\n          (message\n           \"File '%s' successfully renamed to '%s'\"\n           filename\n           (file-name-nondirectory new-name))))))))\n(global-set-key (kbd \"C-x C-r\") 'my/rename-this-buffer-and-file)\n#+end_src\n\nC-a/C-e 移动到行或代码的开头、结尾：\n\n#+begin_src emacs-lisp\n(use-package mwim\n  :config\n  (define-key global-map [remap move-beginning-of-line] #'mwim-beginning-of-code-or-line)\n  (define-key global-map [remap move-end-of-line] #'mwim-end-of-code-or-line))\n#+end_src\n\n增量扩展选择的区域：\n\n#+begin_src emacs-lisp\n(use-package expand-region\n  :config\n  (global-set-key (kbd \"C-=\") #'er/expand-region))\n#+end_src\n\n自动备份:\n\n#+begin_src emacs-lisp\n(defvar backup-dir (expand-file-name \"~/.emacs.d/backup/\"))\n(if (not (file-exists-p backup-dir))\n    (make-directory backup-dir t))\n;; 文件第一次保存时备份。\n(setq make-backup-files t)\n(setq backup-by-copying t)\n;; 不备份 tramp 文件，其它文件都保存到 backup-dir, https://stackoverflow.com/a/22077775\n(setq backup-directory-alist `((,tramp-file-name-regexp . nil) (\".*\" . ,backup-dir)))\n;; 备份文件时使用版本号。\n(setq version-control t)\n;; 删除过多的版本。\n(setq delete-old-versions t)\n(setq kept-new-versions 6)\n(setq kept-old-versions 2)\n;; 不备份版本控制的文件.\n(setq vc-make-backup-files nil)\n\n(defvar autosave-dir (expand-file-name \"~/.emacs.d/autosave/\"))\n(if (not (file-exists-p autosave-dir))\n    (make-directory autosave-dir t))\n\n;; auto-save 访问的文件。\n(setq auto-save-default t)\n(setq auto-save-list-file-prefix autosave-dir)\n(setq auto-save-file-name-transforms `((\".*\" ,autosave-dir t)))\n(setq kill-buffer-delete-auto-save-files t)\n(setq auto-save-include-big-deletions t)\n#+end_src\n\nEmacs 30 xwidget-webkit 对 Mac 支持不好( [[https://github.com/d12frosted/homebrew-emacs-plus/issues/519][Better support for xwidget-webkit]]), 部分功能只有 GTK/X11才\n支持, 如: buffer 内搜索 increase-search/webkit-history:\n+ 如果要复制 xwidget 的内容，需要选择后右击，从上下文菜单中选择 copy。\n+ 如果 window 窗口较小，可以按 a 来自动调整（对应 xwidget-webkit-adjust-size-dispatch 命令）。\n#+begin_src emacs-lisp\n(setq url-user-agent\n      \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36\")\n(setq xwidget-webkit-buffer-name-format \"*webkit* [%T] - %U\")\n(setq xwidget-webkit-enable-plugins t)\n(setq browse-url-firefox-program \"/Applications/Firefox.app/Contents/MacOS/firefox\")\n;; browse-url-firefox, browse-url-default-macosx-browser\n(setq browse-url-browser-function 'xwidget-webkit-browse-url)\n(setq xwidget-webkit-cookie-file \"~/.emacs.d/cookie.txt\")\n\n(add-hook 'xwidget-webkit-mode-hook\n          (lambda ()\n            ;;(setq kill-buffer-query-functions nil)\n            (setq header-line-format nil)\n            (display-line-numbers-mode 0)\n            ;;(local-set-key \"q\" (lambda () (interactive) (kill-this-buffer)))\n            (local-set-key (kbd \"C-t\") (lambda () (interactive) (xwidget-webkit-browse-url \"https://google.com\" t)))))\n\n(defun my/browser-open-at-point (url)\n  (interactive\n   (list (let ((url (thing-at-point 'url)))\n           (if (equal major-mode 'xwidget-webkit-mode)\n               (read-string \"url: \" (xwidget-webkit-uri (xwidget-webkit-current-session)))\n             (read-string \"url: \" url)))))\n  (xwidget-webkit-browse-url url t))\n\n(defun my/browser-search (query)\n  (interactive \"ssearch: \")\n  (xwidget-webkit-browse-url\n   (concat \"https://duckduckgo.com?q=\" (string-replace \" \" \"%20\" query)) t))\n\n(define-prefix-command 'my-browser-prefix)\n(global-set-key (kbd \"C-c o\") 'my-browser-prefix)\n(define-key my-browser-prefix (kbd \"o\") 'my/browser-open-at-point)\n(define-key my-browser-prefix (kbd \"s\") 'my/browser-search)\n\n;; https://github.com/syl20bnr/spacemacs/issues/6587#issuecomment-232890021\n;; make these keys behave like normal browser\n(require 'xwidget)\n(define-key xwidget-webkit-mode-map [mouse-4] 'xwidget-webkit-scroll-down)\n(define-key xwidget-webkit-mode-map [mouse-5] 'xwidget-webkit-scroll-up)\n(define-key xwidget-webkit-mode-map (kbd \"\u003cup\u003e\") 'xwidget-webkit-scroll-down)\n(define-key xwidget-webkit-mode-map (kbd \"\u003cdown\u003e\") 'xwidget-webkit-scroll-up)\n(define-key xwidget-webkit-mode-map (kbd \"M-w\") 'xwidget-webkit-copy-selection-as-kill)\n(define-key xwidget-webkit-mode-map (kbd \"C-c\") 'xwidget-webkit-copy-selection-as-kill)\n\n;; 自动调整 xwidget-webkit 窗口大小（也可以手动按 a 来调整）。\n(add-hook 'window-configuration-change-hook\n\t  (lambda ()\n\t    (when (equal major-mode 'xwidget-webkit-mode)\n\t      (xwidget-webkit-adjust-size-dispatch))))\n\n;; make xwidget default browser\n(setq browse-url-browser-function\n      (lambda (url session)\n\t(other-window 1)\n\t(xwidget-webkit-browse-url url)))\n#+end_src\n\n在线文档和翻译:\n\n#+begin_src emacs-lisp\n;;在线搜索, 先选中 region 再执行搜索。\n(use-package engine-mode\n  :config\n  (engine/set-keymap-prefix (kbd \"C-c s\"))\n  (engine-mode t)\n  ;;(setq engine/browser-function 'eww-browse-url)\n  (setq engine/browser-function 'xwidget-webkit-browse-url)\n  (defengine github \"https://github.com/search?ref=simplesearch\u0026q=%s\" :keybinding \"h\")\n  (defengine google \"https://google.com/search?q=%s\" :keybinding \"g\"))\n\n;; Google 翻译\n(use-package google-translate\n  :config\n  ;; C-n/p 切换翻译类型。\n  (setq google-translate-translation-directions-alist\n        '((\"en\" . \"zh-CN\") (\"zh-CN\" . \"en\")))\n  (global-set-key (kbd \"C-c d t\") #'google-translate-smooth-translate))\n#+end_src\n\nMacOS 互操作:\n+ osx-trash 不支持 TRAMP 删除远程文件，解决办法：用 %m 标记文件，然后按 ! 执行 rm 命\n  令。\n+ 在 finder 中打开当前文件或目录：M-! 后执行命令： =open .=\n\n#+begin_src emacs-lisp\n;; 删除文件时, 将文件移动到回收站。\n(use-package osx-trash\n  :config\n  (when (eq system-type 'darwin)\n    (osx-trash-setup))\n  (setq-default delete-by-moving-to-trash t))\n\n;; 在 Finder 中打开当前文件。\n(use-package reveal-in-osx-finder\n  :commands (reveal-in-osx-finder))\n#+end_src\n\n帮助增强:\n\n#+begin_src emacs-lisp\n;; 在帮助文档底部显示 lisp demo.\n(use-package elisp-demos\n  :config\n  (advice-add 'describe-function-1 :after #'elisp-demos-advice-describe-function-1)\n  (advice-add 'helpful-update :after #'elisp-demos-advice-helpful-update))\n\n;; 相比 Emacs 内置 Help, 提供更多上下文信息。\n(use-package helpful\n  :config\n  (global-set-key (kbd \"C-h f\") #'helpful-callable)\n  (global-set-key (kbd \"C-h v\") #'helpful-variable)\n  (global-set-key (kbd \"C-h k\") #'helpful-key)\n  (global-set-key (kbd \"C-c C-d\") #'helpful-at-point)\n  (global-set-key (kbd \"C-h F\") #'helpful-function)\n  (global-set-key (kbd \"C-h C\") #'helpful-command))\n#+end_src\n\n* pdf\n\n安装依赖：\n#+begin_src shell :tangle no\nbrew install poppler automake mupdf\n#+end_src\n\n配置 pdf-tools：\n#+begin_src emacs-lisp\n(use-package pdf-tools\n  ;; :ensure-system-package\n  ;; ((pdfinfo . poppler)\n  ;;  (automake . automake)\n  ;;  (mutool . mupdf)\n  ;;  (\"/usr/local/opt/zlib\" . zlib))\n  :init\n  ;; 使用 scaling 确保中文字体不模糊\n  (setq pdf-view-use-scaling t)\n  (setq pdf-view-use-imagemagick nil)\n  (setq pdf-annot-activate-created-annotations t)\n  (setq pdf-view-resize-factor 1.1)\n  (setq-default pdf-view-display-size 'fit-page)\n  (setq pdf-annot-activate-created-annotations t)\n  :hook\n  ((pdf-view-mode . pdf-view-themed-minor-mode)\n   (pdf-view-mode . pdf-view-auto-slice-minor-mode)\n   (pdf-view-mode . pdf-isearch-minor-mode))\n  :config\n  (define-key pdf-view-mode-map (kbd \"C-s\") 'isearch-forward)\n  ;;(add-hook 'pdf-view-mode-hook (lambda() (linum-mode -1)))\n  (setq pdf-info-epdfinfo-program \"/opt/homebrew/bin/epdfinfo\")\n  (setenv \"PKG_CONFIG_PATH\" \"/opt/homebrew/opt/zlib/lib/pkgconfig:/opt/homebrew/opt/pkgconfig:/opt/homebrew/lib/pkgconfig\")\n  (pdf-tools-install))\n\n;; pdf 转为 png 时使用更高分辨率（默认 90）。\n(setq doc-view-resolution 144)\n\n;;(use-package org-noter)\n#+end_src\n\n+ pdf-tools 默认是白底黑字，可以：\n  + 深色模式： =M-x pdf-view-midnight-minor-mode=\n  + 主题模式： =M-x pdf-view-themed-minor-mode=\n+ 搜索中文时，需要使用系统中文输入法和 isearch 模式, 或者使用 =M-s o(occur)= ；\n\n* refs\n:PROPERTIES:\n:ANKI_NOTE_HASH: 64901ec4f38ae35f38b42523c75cb9ea\n:ANKI_NOTE_ID: 1703514360198\n:END:\n\n本配置参考了以下仓库代码：\n\n1. [[https://github.com/seagle0128/.emacs.d][seagle0128/.emacs.d]]\n2. [[https://gitlab.com/protesilaos/dotfiles][protesilaos/dotfiles]]\n3. [[https://github.com/bbatsov/prelude][bbatsov/prelude]]\n4. [[https://github.com/MatthewZMD/.emacs.d][MatthewZMD/.emacs.d]]\n5. [[https://github.com/condy0919/.emacs.d][condy0919/.emacs.d]]\n6. [[https://github.com/manateelazycat/lazycat-emacs][manateelazycat/lazycat-emacs]]\n7. [[https://github.com/jiacai2050/dotfiles][jiacai2050/dotfiles]]\n8. [[https://github.com/natecox/dotfiles/][natecox/dotfiles]]\n9. [[https://config.daviwil.com/emacs][daviwil]]\n10. [[https://gitlab.com/skybert/my-little-friends/-/blob/master/emacs/.emacs][skybert/my-little-friends]]\n11. [[https://github.com/casouri/lunarymacs/commits/master][casouri/lunarymacs]]\n12. [[https://code.tecosaur.net/tec/emacs-config/src/branch/master/config.org][tec/emacs-config]]\n","funding_links":[],"categories":["Emacs Lisp"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopsnull%2Femacs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopsnull%2Femacs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopsnull%2Femacs/lists"}