emacs.d
emacs.d copied to clipboard
A simple Emacs config on Windows
#+html:
A simple Emacs config on Windows/WSL/Linux
- 安装方法
** Windows
-
下载配置文件 #+begin_src shell git clone https://github.com/xhcoding/emacs.d.git ~/.emacs.d #+end_src
-
运行 =org-tangle.bat= 生成 =.el= 文件
-
启动 emacs
** Linux
*** guix pack
-
下载 Release 里面的 =emacs-x-minimal-x86_64-linux-installer.run= 安装包
-
添加可执行权限 =chmod +x emacs-x-minimal-x86_64-linux-installer.run=
-
执行 =./emacs-x-minimal-x86_64-linux-installer.run= 将 emacs 安装到 =/opt= 目录下
-
=emacs-x= 启动 emacs
- 目录 :TOC:
- [[#安装方法][安装方法]]
- [[#early-initel][early-init.el]]
- [[#设置配置启动模式][设置配置启动模式]]
- [[#开启-debug-on-error][开启 debug-on-error]]
- [[#将-gc-的阈值设置到最大避免启动的时候多次-gc-拖慢启动速度][将 gc 的阈值设置到最大,避免启动的时候多次 gc 拖慢启动速度]]
- [[#file-handler-临时设置为-nil加快启动速度][file handler 临时设置为 nil,加快启动速度]]
- [[#不要初始化-package][不要初始化 package]]
- [[#禁止-frame-缩放][禁止 frame 缩放]]
- [[#关闭菜单栏][关闭菜单栏]]
- [[#关闭工具栏][关闭工具栏]]
- [[#关闭滚动条][关闭滚动条]]
- [[#启动后全屏][启动后全屏]]
- [[#straight-包管理][straight 包管理]]
- [[#空闲时增量加载包][空闲时增量加载包]]
- [[#启动速度测试][启动速度测试]]
- [[#emacs-配置][emacs 配置]]
- [[#载入-custom-文件][载入 custom 文件]]
- [[#定义一些固定的常量][定义一些固定的常量]]
- [[#常用的工具函数][常用的工具函数]]
- [[#重命名当前文件][重命名当前文件]]
- [[#删除当前文件][删除当前文件]]
- [[#清理-message-buffer][清理 Message buffer]]
- [[#开关网络代理][开关网络代理]]
- [[#清理掉没用的-buffer][清理掉没用的 buffer]]
- [[#基本设置][基本设置]]
- [[#用户信息][用户信息]]
- [[#增加-io-性能][增加 IO 性能]]
- [[#设置编码][设置编码]]
- [[#设置-frame-标题][设置 frame 标题]]
- [[#关闭启动动画][关闭启动动画]]
- [[#scratch-buffer-设置][scratch buffer 设置]]
- [[#yes-or-no-p-改为-y-or-n-p][yes-or-no-p 改为 y-or-n-p]]
- [[#关掉提示声音][关掉提示声音]]
- [[#禁止光标闪烁][禁止光标闪烁]]
- [[#启动时不要显示][启动时不要显示]]
- [[#tab-设置][tab 设置]]
- [[#一行不超过-120-字符][一行不超过 120 字符]]
- [[#删除文件时放到回收站][删除文件时放到回收站]]
- [[#高亮当前行][高亮当前行]]
- [[#gcmh-优化垃圾回收][gcmh 优化垃圾回收]]
- [[#auto-save-自动保存][auto-save 自动保存]]
- [[#rime-智能的中文输入法][rime 智能的中文输入法]]
- [[#字体和主题][字体和主题]]
- [[#ligature-连字体][ligature 连字体]]
- [[#awesome-tray-底部信息显示][awesome-tray 底部信息显示]]
- [[#sort-tab-快速切换到其它-buffer][sort-tab 快速切换到其它 buffer]]
- [[#hydra-方便的快捷键设置][hydra 方便的快捷键设置]]
- [[#recentf-读取最近文件][recentf 读取最近文件]]
- [[#popper-更方便的弹出窗口][popper 更方便的弹出窗口]]
- [[#autorevert-自动同步外部文件改变][autorevert 自动同步外部文件改变]]
- [[#minibuffer-补全及增强][minibuffer 补全及增强]]
- [[#vundo-undo-增强][vundo undo 增强]]
- [[#avy-快速移动光标到屏幕上任意字符][avy 快速移动光标到屏幕上任意字符]]
- [[#ace-window-快速切换到其它-window][ace-window 快速切换到其它 window]]
- [[#toggle-one-window-快速切换到单窗口][toggle-one-window 快速切换到单窗口]]
- [[#yasnippet-快速插入代码片段][yasnippet 快速插入代码片段]]
- [[#separedit-快速编辑][separedit 快速编辑]]
- [[#projectileel-工程管理][projectile.el 工程管理]]
- [[#compile-增强][compile 增强]]
- [[#eshell-增强][eshell 增强]]
- [[#eshell-基本配置][eshell 基本配置]]
- [[#eshell-历史记录设置][eshell 历史记录设置]]
- [[#esh-mode-设置][esh-mode 设置]]
- [[#eshell-git-prompt-提示符美化][eshell-git-prompt 提示符美化]]
- [[#eshell-z-智能目录跳转][eshell-z 智能目录跳转]]
- [[#fingertip-智能括号插入][fingertip 智能括号插入]]
- [[#color-rg-快速搜索重构][color-rg 快速搜索重构]]
- [[#lsp-bridge-代码补全][lsp-bridge 代码补全]]
- [[#highlight-parentheses-高亮外层括号][highlight-parentheses 高亮外层括号]]
- [[#evil-nerd-commenter-智能注释代码][evil-nerd-commenter 智能注释代码]]
- [[#apheleia-智能格式化代码][apheleia 智能格式化代码]]
- [[#wuco-拼写检查][wuco 拼写检查]]
- [[#git-配置][git 配置]]
- [[#magit][magit]]
- [[#显示当前行的最后提交信息][显示当前行的最后提交信息]]
- [[#smerge-mode-解决冲突][smerge-mode 解决冲突]]
- [[#visual-regexp-可视化的正则替换][visual-regexp 可视化的正则替换]]
- [[#expand-region-递增选区][expand-region 递增选区]]
- [[#openwith-外部工具打开文件][openwith 外部工具打开文件]]
- [[#启用-treesit][启用 treesit]]
- [[#elisp-配置][elisp 配置]]
- [[#c-开发配置][C++ 开发配置]]
- [[#citre][citre]]
- [[#cmake-ts-mode-设置][cmake-ts-mode 设置]]
- [[#c-ts-mode-设置][c-ts-mode 设置]]
- [[#导入-vs-环境变量][导入 VS 环境变量]]
- [[#rust-开发配置][Rust 开发配置]]
- [[#python-开发配置][python 开发配置]]
- [[#java-开发配置][java 开发配置]]
- [[#web-开发][web 开发]]
- [[#javascript][Javascript]]
- [[#typescript][Typescript]]
- [[#html][HTML]]
- [[#css][css]]
- [[#json-ts-mode][json-ts-mode]]
- [[#qml-开发][qml 开发]]
- [[#lua-开发][lua 开发]]
- [[#plantuml-支持][plantuml 支持]]
- [[#dape-调试][dape 调试]]
- [[#dash-docs-查询-dash-文档][dash-docs 查询 dash 文档]]
- [[#shrface-让-eww-的阅读体验更好][shrface 让 eww 的阅读体验更好]]
- [[#eww-配置][eww 配置]]
- [[#olivetti-居中显示内容][olivetti 居中显示内容]]
- [[#org-配置][org 配置]]
- [[#org-mode-美化设置][org-mode 美化设置]]
- [[#org-基本配置][org 基本配置]]
- [[#org-babel-设置][org babel 设置]]
- [[#gtd-配置][gtd 配置]]
- [[#export-设置][export 设置]]
- [[#生成-toc][生成 TOC]]
- [[#org-contrib-wanderlust-支持][org-contrib wanderlust 支持]]
- [[#增量载入-org-包][增量载入 org 包]]
- [[#知识管理及博客配置][知识管理及博客配置]]
- [[#denote][denote]]
- [[#org-roam][org-roam]]
- [[#hugo-配置][hugo 配置]]
- [[#上传博客图片到腾讯云对象存储][上传博客图片到腾讯云对象存储]]
- [[#自动在中英文插入空格][自动在中英文插入空格]]
- [[#org-download-下载图片][org-download 下载图片]]
- [[#eaf-配置][eaf 配置]]
- [[#popweb-配置][popweb 配置]]
- [[#dictionary-overlay-方便阅读英文文章][dictionary-overlay 方便阅读英文文章]]
- [[#邮件配置][邮件配置]]
- [[#wanderlust-邮件前端][Wanderlust 邮件前端]]
- [[#alert-toast-邮件通知][alert-toast 邮件通知]]
- [[#elfeed-rss-订阅][elfeed RSS 订阅]]
- [[#leetcodeel-刷题][leetcode.el 刷题]]
- [[#latex][LaTex]]
- [[#emacs-aichat-ai-对话][emacs-aichat AI 对话]]
- [[#which-key-按键提示][which-key 按键提示]]
- [[#meow-模式编辑][meow 模式编辑]]
- [[#启动-emacs-server][启动 emacs server]]
- [[#载入私有配置文件][载入私有配置文件]]
- early-init.el :PROPERTIES: :HEADER-ARGS: :tangle early-init.el :END:
Emacs 启动过程的早期加载。
#+begin_src elisp ;;; early-init.el --- early init -- lexical-binding: t no-byte-compile: t; -- #+end_src
** 设置配置启动模式
#+begin_src elisp (defconst my-config-mode (intern (downcase (or (getenv "EMACS_CONFIG_MODE") "full"))))
(defconst full? (eq my-config-mode 'full))
(defconst minimal? (eq my-config-mode 'minimal))
(defconst debug? (eq my-config-mode 'debug))
(defconst not-full? (not full?))
(defconst not-minimal? (not minimal?))
(defconst not-debug? (not debug?)) #+end_src
** 开启 debug-on-error
#+begin_src elisp (when debug? (setq toggle-debug-on-error t)) #+end_src
** 将 gc 的阈值设置到最大,避免启动的时候多次 gc 拖慢启动速度
#+begin_src elisp (setq gc-cons-threshold most-positive-fixnum) #+end_src
** file handler 临时设置为 nil,加快启动速度
#+begin_src emacs-lisp (unless (or (daemonp) noninteractive init-file-debug) (let ((old-file-name-handler-alist file-name-handler-alist)) (setq file-name-handler-alist nil) (add-hook 'emacs-startup-hook (lambda () "Recover file name handlers." (setq file-name-handler-alist (delete-dups (append file-name-handler-alist old-file-name-handler-alist)))))))
#+end_src
** 不要初始化 package
#+begin_src elisp (setq package-enable-at-startup nil) #+end_src
** 禁止 frame 缩放
#+begin_src elisp (setq frame-inhibit-implied-resize t) #+end_src
** 关闭菜单栏
#+begin_src elisp (push '(menu-bar-lines . 0) default-frame-alist) #+end_src
** 关闭工具栏
#+begin_src elisp (push '(tool-bar-lines . 0) default-frame-alist) #+end_src
** 关闭滚动条
#+begin_src elisp (push '(vertical-scroll-bars) default-frame-alist) #+end_src
** 启动后全屏
#+begin_src elisp (when full? (push '(fullscreen . fullscreen) default-frame-alist))
#+end_src
** straight 包管理
#+begin_src elisp (defvar bootstrap-version) (let ((bootstrap-file (expand-file-name "straight/repos/straight.el/bootstrap.el" (or (bound-and-true-p straight-base-dir) user-emacs-directory))) (bootstrap-version 7)) (unless (file-exists-p bootstrap-file) (with-current-buffer (url-retrieve-synchronously "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el" 'silent 'inhibit-cookies) (goto-char (point-max)) (eval-print-last-sexp))) (load bootstrap-file nil 'nomessage)) #+end_src
** 空闲时增量加载包 from: https://github.com/doomemacs/doomemacs/blob/master/lisp/doom-start.el
#+begin_src emacs-lisp (defvar doom-incremental-packages '(t) "A list of packages to load incrementally after startup. Any large packages here may cause noticeable pauses, so it's recommended you break them up into sub-packages. For example, `org' is comprised of many packages, and can be broken up into:
(doom-load-packages-incrementally
'(calendar find-func format-spec org-macs org-compat
org-faces org-entities org-list org-pcomplete org-src
org-footnote org-macro ob org org-clock org-agenda
org-capture))
This is already done by the lang/org module, however.
If you want to disable incremental loading altogether, either remove
doom-load-packages-incrementally-h' from emacs-startup-hook' or set
`doom-incremental-first-idle-timer' to nil. Incremental loading does not occur
in daemon sessions (they are loaded immediately at startup).")
(defvar doom-incremental-first-idle-timer (if (daemonp) 0 2.0) "How long (in idle seconds) until incremental loading starts.
Set this to nil to disable incremental loading. Set this to 0 to load all incrementally deferred packages immediately at `emacs-startup-hook'.")
(defvar doom-incremental-idle-timer 0.75 "How long (in idle seconds) in between incrementally loading packages.")
(defun doom-load-packages-incrementally (packages &optional now) "Registers PACKAGES to be loaded incrementally.
If NOW is non-nil, load PACKAGES incrementally, in doom-incremental-idle-timer' intervals." (let ((gc-cons-threshold most-positive-fixnum)) (if (not now) (cl-callf append doom-incremental-packages packages) (while packages (let ((req (pop packages)) idle-time) (if (featurep req) (message "start:iloader: Already loaded %s (%d left)" req (length packages)) (condition-case-unless-debug e (and (or (null (setq idle-time (current-idle-time))) (< (float-time idle-time) doom-incremental-first-idle-timer) (not (while-no-input ;;(message "start:iloader: Loading %s (%d left)" req (length packages)) ;; If default-directory' doesn't exist or is
;; unreadable, Emacs throws file errors.
(let ((default-directory user-emacs-directory)
(inhibit-message t)
(file-name-handler-alist
(list (rassq 'jka-compr-handler file-name-handler-alist))))
(require req nil t)
t))))
(push req packages))
(error
(message "Error: failed to incrementally load %S because: %s" req e)
(setq packages nil)))
(if (null packages)
(message "start:iloader: Finished!")
(run-at-time (if idle-time
doom-incremental-idle-timer
doom-incremental-first-idle-timer)
nil #'doom-load-packages-incrementally
packages t)
(setq packages nil))))))))
(defun doom-load-packages-incrementally-h () "Begin incrementally loading packages in `doom-incremental-packages'.
If this is a daemon session, load them all immediately instead." (when (numberp doom-incremental-first-idle-timer) (if (zerop doom-incremental-first-idle-timer) (mapc #'require (cdr doom-incremental-packages)) (run-with-idle-timer doom-incremental-first-idle-timer nil #'doom-load-packages-incrementally (cdr doom-incremental-packages) t))))
(add-hook 'emacs-startup-hook #'doom-load-packages-incrementally-h 100)
#+end_src
** use-package 扩展
添加 =:ban= 关键字,不要执行 use-package
#+begin_src elisp (require 'use-package) (push :ban use-package-keywords)
(defalias 'use-package-normalize/:ban 'use-package-normalize-test)
(defalias 'use-package-handler/:ban 'use-package-handler/:unless) #+end_src
** 启动速度测试
#+begin_src elisp
(use-package benchmark-init
:ban not-debug?
:straight t
:demand t
:hook (after-init . benchmark-init/deactivate)
:bind ("
#+end_src
- emacs 配置 :PROPERTIES: :HEADER-ARGS: :tangle init.el :END:
#+begin_src elisp ;;; init.el --- init -- lexical-binding: t no-byte-compile: t; -- #+end_src
** 载入 custom 文件
#+begin_src elisp (setq custom-file (expand-file-name "custom.el" user-emacs-directory)) (load custom-file t) #+end_src
** 定义一些固定的常量 #+begin_src elisp (defconst sys-is-windows (memq system-type '(cygwin windows-nt ms-dos)))
(defconst sys-is-wsl2 (getenv "WSLENV"))
(defconst my-system-icon (cond (sys-is-wsl2 " ") ((eq system-type 'gnu/linux) " ") ((eq system-type 'windows-nt) " ") ((eq system-type 'darwin) " " ) (t " ? ")))
(setq global-mode-string (append global-mode-string '(my-system-icon)))
;; 动态库目录 (defconst my-lib-dir (expand-file-name "lib" user-emacs-directory)) (add-to-list 'load-path my-lib-dir)
;; 可执行文件目录 (defconst my-bin-dir (expand-file-name "bin" user-emacs-directory)) ;; 将 my-bin-dir 加入到 PATH 中 (setenv "PATH" (concat my-bin-dir (if sys-is-windows ";" ":") (getenv "PATH")))
;; 外部配置文件目录 (defconst my-etc-dir (expand-file-name "etc" user-emacs-directory))
;; 存放代码的目录 (if sys-is-windows (defconst my-code-dir (expand-file-name "D:/Code")) (defconst my-code-dir (expand-file-name "~/Code")))
;; 开源代码 (defconst my-code-opensource-dir (expand-file-name "Github" my-code-dir))
;; 工作代码 (defconst my-code-work-dir (expand-file-name "Work" my-code-dir))
;; 个人项目代码 (defconst my-code-project-dir (expand-file-name "Project" my-code-dir))
;; 练习代码或实例代码,这里面一般根据语言再分一层 (defconst my-code-demo-dir (expand-file-name "Demo" my-code-dir))
;; 数据目录 (if sys-is-windows (defconst my-docs-dir (expand-file-name "D:/Docs")) (if sys-is-wsl2 (defconst my-docs-dir (expand-file-name "/mnt/d/Docs")) (defconst my-docs-dir (expand-file-name "~/Docs"))))
;; 笔记目录 (defconst my-notes-dir (expand-file-name "Notes" my-docs-dir))
;; 博客相关目录 (if sys-is-windows (defconst my-blogs-dir (expand-file-name "D:/BaiduSyncdisk/Blogs")) (if sys-is-wsl2 (defconst my-blogs-dir (expand-file-name "/mnt/d/BaiduSyncdisk/Blogs")) (defconst my-blogs-dir (expand-file-name "~/BaiduSyncdisk/Blogs")))) (defconst my-blogs-org-dir (expand-file-name "org" my-blogs-dir)) (defconst my-blogs-image-dir (expand-file-name "images" my-blogs-dir))
;; Org 相关目录 (if sys-is-windows (defconst my-org-dir (expand-file-name "Org" my-docs-dir)) (if sys-is-wsl2 (defconst my-org-dir (expand-file-name "Org" my-docs-dir)) (defconst my-org-dir (expand-file-name "Org" my-docs-dir)))) ;; 私有文件目录 (defconst my-private-dir (expand-file-name "Private" my-org-dir))
;; 私有代码片段目录 (defconst my-private-snippets-dir (expand-file-name "snippets" my-private-dir))
#+end_src
** 常用的工具函数
*** 重命名当前文件
#+begin_src elisp (defun my/rename-this-file-and-buffer (new-name) "Rename both current buffer and file it's visiting to NEW_NAME." (interactive "sNew name: ") (let ((name (buffer-name)) (filename (buffer-file-name))) (unless filename (error "Buffer '%s' is not visiting a file" name)) (progn (when (file-exists-p filename) (rename-file filename new-name 1)) (set-visited-file-name new-name) (rename-buffer new-name)))) #+end_src
*** 删除当前文件
#+begin_src emacs-lisp (defun my/delete-file-and-buffer () "Kill the current buffer and deletes the file it is visiting." (interactive) (let ((filename (buffer-file-name))) (when (and filename (y-or-n-p (concat "Do you really want to delete file " filename "?"))) (delete-file filename t) (message "Deleted file %s." filename) (kill-buffer) ))) #+end_src
*** 清理 Message buffer
#+begin_src elisp (defun my/clear-messages-buffer () "Clear Messages buffer." (interactive) (let ((inhibit-read-only t)) (with-current-buffer "Messages" (erase-buffer)))) #+end_src
*** 开关网络代理
#+begin_src elisp (defun my/toggle-url-proxy () "Toggle proxy for the url.el library." (interactive) (require 'url) (cond (url-proxy-services (message "Turn off URL proxy") (setq url-proxy-services nil)) (t (message "Turn on URL proxy") (setq url-proxy-services '(("http" . "127.0.0.1:7890") ("https" . "127.0.0.1:7890") ("no_proxy" . "0.0.0.0"))))))
#+end_src
*** 清理掉没用的 buffer
#+begin_src elisp (defun my/kill-unused-buffers () "Kill unused buffers." (interactive) (ignore-errors (save-excursion (dolist (buf (buffer-list)) (set-buffer buf) (when (and (string-prefix-p "" (buffer-name)) (string-suffix-p "" (buffer-name))) (kill-buffer buf)) )))) #+end_src
** 基本设置 *** 用户信息
#+begin_src elisp (setq user-full-name "xhcoding" user-mail-address "[email protected]") #+end_src
*** 增加 IO 性能
#+begin_src elisp (setq process-adaptive-read-buffering nil) (setq read-process-output-max (* 1024 1024)) #+end_src
*** 设置编码
默认使用 utf-8 ,在 windows 文件名编码使用 gbk ,不然打不开中文文件 #+begin_src elisp (prefer-coding-system 'utf-8) (when sys-is-windows (setq file-name-coding-system 'gbk)) #+end_src
*** 设置 frame 标题
#+begin_src emacs-lisp (setq frame-title-format "Emacs") #+end_src
*** 关闭启动动画
#+begin_src elisp (setq inhibit-startup-message t) #+end_src
*** scratch buffer 设置
scratch 为 fundaemental-mode
#+begin_src elisp (setq initial-major-mode 'fundamental-mode) #+end_src
scratch buffer 内容为空
#+begin_src elisp (setq initial-scratch-message nil) #+end_src
*** yes-or-no-p 改为 y-or-n-p
#+begin_src elisp (setq use-short-answers t) #+end_src
*** 关掉提示
#+begin_src elisp (setq ring-bell-function 'ignore) #+end_src
*** 禁止光标闪烁
#+begin_src elisp (blink-cursor-mode -1) #+end_src
*** 启动时不要显示 For information about GNU Emacs...
#+begin_src elisp (advice-add #'display-startup-echo-area-message :override #'ignore) #+end_src
*** tab 设置
#+begin_src elisp (setq-default tab-width 4)
(setq-default indent-tabs-mode nil) #+end_src
*** 一行不超过 120 字符
#+begin_src elisp (setq-default fill-column 120) (column-number-mode +1) #+end_src
*** 删除文件时放到回收站
#+begin_src elisp (setq-default delete-by-moving-to-trash t) #+end_src
*** 高亮当前行
#+begin_src elisp (global-hl-line-mode +1) #+end_src
** gcmh 优化垃圾回收
#+begin_src elisp (use-package gcmh :straight t :hook (emacs-startup . gcmh-mode) :init (setq gcmh-idle-delay 'auto gcmh-auto-idle-delay-factor 10 gcmh-high-cons-threshold 33554432)) ; 32MB #+end_src
** auto-save 自动保存 ref: https://github.com/manateelazycat/auto-save
#+begin_src elisp (use-package auto-save :straight (auto-save :type git :host github :repo "manateelazycat/auto-save") :defer 3 :init ;; 关闭 emacs 默认的自动备份 (setq make-backup-files nil) ;; 关闭 emacs 默认的 自动保存 (setq auto-save-default nil) :config (setq auto-save-silent t) (auto-save-enable) )
#+end_src
** rime 智能的中文输入法
#+begin_src elisp (use-package rime :straight t :defer t :bind ("C-j" . rime-force-enable) :init (when sys-is-windows (setq rime--module-path (expand-file-name (concat "librime-emacs" module-file-suffix) my-lib-dir)) (setq rime-share-data-dir (expand-file-name "rime-data" my-etc-dir)))
(defun my-*require-rime(&rest _)
"Require rime when toggle-input-method."
(unless (featurep 'rime)
(require 'rime)))
(advice-add 'toggle-input-method :before #'my-*require-rime)
:custom
(default-input-method "rime")
(rime-user-data-dir (expand-file-name "rime-user" my-etc-dir))
:config
(setq
rime-disable-predicates '(rime-predicate-after-alphabet-char-p
rime-predicate-prog-in-code-p))
(if (display-graphic-p)
(setq rime-show-candidate 'posframe)
(setq rime-show-candidate 'minibuffer)))
#+end_src
** 字体和主题
#+begin_src elisp (defun my-font-installed-p (font-name) "Check if font with FONT-NAME is available." (find-font (font-spec :name font-name)))
(defun my-setup-fonts () "Setup fonts." (when (display-graphic-p) ;; Set default font (cl-loop for font in '("CaskaydiaCove NFM" "Fira Code" "Jetbrains Mono" "SF Mono" "Hack" "Source Code Pro" "Menlo" "Monaco" "DejaVu Sans Mono" "Consolas") when (my-font-installed-p font) return (set-face-attribute 'default nil :family font :height 120))
;; Specify font for all unicode characters
(cl-loop for font in '("Segoe UI Emoji" "Apple Symbols" "Symbola" "Symbol")
when (my-font-installed-p font)
return (set-fontset-font t 'symbol (font-spec :family font) nil 'prepend))
;; Emoji
(cl-loop for font in '("Segoe UI Emoji" "Noto Color Emoji" "Apple Color Emoji")
when (my-font-installed-p font)
return (set-fontset-font t
(if (< emacs-major-version 28)'symbol 'emoji)
(font-spec :family font) nil 'prepend))
;; Specify font for Chinese characters
(cl-loop for font in '("微软雅黑" "WenQuanYi Micro Hei Mono")
when (my-font-installed-p font)
return (set-fontset-font t 'han (font-spec :family font)))))
(my-setup-fonts) (add-hook 'window-setup-hook #'my-setup-fonts) (add-hook 'server-after-make-frame-hook #'my-setup-fonts)
(use-package modus-themes :straight (:type git :host github :repo "protesilaos/modus-themes") )
(use-package ef-themes :straight (:type git :host github :repo "protesilaos/ef-themes") :config (modus-themes-load-theme 'ef-owl) )
(use-package nerd-icons :straight t :init (cl-loop for font in '("CaskaydiaCove NFM" "Symbols Nerd Font Mono") when (my-font-installed-p font) return (setq nerd-icons-font-family font))) #+end_src
** ligature 连字体
#+begin_src elisp (use-package ligature :ban minimal? :straight t :defer t :hook prog-mode :config ;; Enable all Cascadia Code ligatures in programming modes (ligature-set-ligatures 'prog-mode '("|||>" "<|||" "<==>" "" "---" "-<<" "<~~" "<~>" "<>" "<||" "<|>" "<$>" "<==" "<=>" "<=<" "<->" "<--" "<-<" "<<=" "<<-" "<<<" "<+>" "</>" "###" "#(" "..<" "..." "+++" "/==" "///" "|_" "www" "&&" "^=" "~~" "~@" "~=" "~>" "~-" "**" ">" "/" "||" "|}" "|]" "|=" "|>" "|-" "{|" "[|" "]#" "::" ":=" ":>" ":<" "$>" "==" "=>" "!=" "!!" ">:" ">=" ">>" ">-" "-~" "-|" "->" "--" "-<" "<~" "<" "<|" "<:" "<$" "<=" "<>" "<-" "<<" "<+" "</" "#{" "#[" "#:" "#=" "#!" "##" "#(" "#?" "#_" "%%" ".=" ".-" ".." ".?" "+>" "++" "?:" "?=" "?." "??" ";;" "/" "/=" "/>" "//" "__" "~~" "(" "*)" "\\" "://"))) #+end_src
** doom-modeline
#+begin_src elisp (use-package doom-modeline :straight t :hook (after-init . doom-modeline-mode)) #+end_src
** hydra 方便的快捷键设置
#+begin_src elisp (use-package hydra :straight t)
(use-package pretty-hydra :straight t :after hydra :init (cl-defun pretty-hydra-title (title &optional icon-type icon-name &key face height v-adjust) "Add an icon in the hydra title." (let ((face (or face `(:foreground ,(face-background 'highlight)))) (height (or height 1.0)) (v-adjust (or v-adjust 0.0))) (concat (when (and icon-type icon-name) (let ((f (intern (format "all-the-icons-%s" icon-type)))) (when (fboundp f) (concat (apply f (list icon-name :face face :height height :v-adjust v-adjust)) " ")))) (propertize title 'face face)))))
#+end_src
** recentf 读取最近文件
#+begin_src elisp (use-package recentf :commands (recentf-open-files) :hook (after-init . recentf-mode) :bind ("C-x C-r" . recentf-open-files) :init (setq recentf-max-saved-items 500 recentf-exclude '("\.?cache" ".cask" "url" "COMMIT_EDITMSG\'" "bookmarks" "\.\(?:gz\|gif\|svg\|png\|jpe?g\|bmp\|xpm\)$" "\.?ido\.last$" "\.revive$" "/G?TAGS$" "/.elfeed/" "^/tmp/" "^/var/folders/.+$" ; "^/ssh:" (lambda (file) (file-in-directory-p file package-user-dir)))) :config (push (expand-file-name recentf-save-file) recentf-exclude))
#+end_src
** popper 更方便的弹出窗口
#+begin_src elisp (use-package popper :straight t :hook (emacs-startup . popper-mode) :init (setq popper-reference-buffers '( help-mode rustic-cargo-run-mode lsp-bridge-ref-mode "^\eshell.\$" eshell-mode ;; emacs-aichat "^\?[aA]ichat.\$"
"^\\*xref*\\*$"
"^\\*compilation*\\*$"
))
:config
(with-no-warnings
(defun my-popper-fit-window-height (win)
"Determine the height of popup window WIN by fitting it to the buffer's content."
(fit-window-to-buffer
win
(floor (frame-height) 3)
(floor (frame-height) 3)))
(setq popper-window-height #'my-popper-fit-window-height)
(defun my-popper-window-popper-p (buffer)
"Whether `buffer' is popper window."
(when-let* ((window (caar popper-open-popup-alist))
(buffer (cdar popper-open-popup-alist))
(window-p (string= (buffer-name) (buffer-name buffer))))
window))
(defun my-popper-close-window (&rest _)
"Close popper window via `C-g'."
;; `C-g' can deactivate region
(when (and (called-interactively-p 'interactive)
(not (region-active-p))
popper-open-popup-alist)
(let ((window (caar popper-open-popup-alist)))
(when (window-live-p window)
(delete-window window)))))
(advice-add #'keyboard-quit :before #'my-popper-close-window))
)
#+end_src
** autorevert 自动同步外部文件改变
#+begin_src elisp (use-package autorevert :config (global-auto-revert-mode +1))
#+end_src
** minibuffer 补全及增强
#+begin_src elisp (use-package pinyinlib :straight t :defer t)
(use-package orderless :straight t :custom (completion-styles '(orderless)) :config ;; 拼音搜索支持 (defun completion--regex-pinyin (str) (require 'pinyinlib) (orderless-regexp (pinyinlib-build-regexp-string str))) (add-to-list 'orderless-matching-styles 'completion--regex-pinyin) )
(use-package vertico :straight t :config (vertico-mode +1))
(use-package marginalia :after (vertico) :straight t :config (marginalia-mode +1))
(use-package consult :straight t :defer t :bind (("C-s" . consult-line) ("C-x b" . consult-buffer) ("C-x C-b" . consult-bookmark) ("C-x C-i" . consult-imenu)) :custom (consult-preview-key nil) (consult-buffer-sources '(consult--source-buffer consult--source-recent-file)) :config
(when sys-is-windows
(add-to-list 'process-coding-system-alist '("es.exe" gbk . gbk))
(add-to-list 'process-coding-system-alist '("explorer" gbk . gbk))
(add-to-list 'process-coding-system-alist '("rg" utf-8 . gbk))
(setq consult-locate-args (encode-coding-string "es.exe -i -p -r" 'gbk))))
#+end_src
** vundo undo 增强
#+begin_src emacs-lisp (use-package vundo :straight t :bind ("C-x u" . vundo)) #+end_src
** avy 快速移动光标到屏幕上任意字符
#+begin_src elisp (use-package avy :straight t :bind (("M-'" . my/avy-goto-char-timer)) :init (defun my/avy-goto-char-timer (&optional arg) "Make avy-goto-char-timer support pinyin" (interactive "P") (require 'pinyinlib) (require 'avy) (let ((avy-all-windows (if arg (not avy-all-windows) avy-all-windows))) (avy-with avy-goto-char-timer (setq avy--old-cands (avy--read-candidates 'pinyinlib-build-regexp-string)) (avy-process avy--old-cands))))
:config
(setq avy-all-windows nil
avy-all-windows-alt t
avy-background t
avy-style 'pre))
#+end_src
** ace-window 快速切换到其它 window
#+begin_src elisp (use-package ace-window :straight t :pretty-hydra ((:title (pretty-hydra-title "Window Management" 'faicon "th" :height 1.1 :v-adjust -0.1) :foreign-keys warn :quit-key ("q" "C-g")) ("Split" (("r" split-window-right "horizontally" :exit t) ("R" split-window-right "horizontally continue") ("v" split-window-below "vertically" :exit t) ("V" split-window-below "vertically continue"))
"Resize"
(("h" shrink-window-horizontally "←")
("j" enlarge-window "↓")
("k" shrink-window "↑")
("l" enlarge-window-horizontally "→")
("n" balance-windows "balance" :exit t))
"Zoom"
(("+" text-scale-increase "in")
("=" text-scale-increase "in")
("-" text-scale-decrease "out")
("0" (text-scale-increase 0) "reset"))))
:bind (("M-o" . ace-window)
("C-c w" . ace-window-hydra/body))
)
#+end_src
** toggle-one-window 快速切换到单窗口
ref:https://github.com/manateelazycat/toggle-one-window
#+begin_src elisp (defvar my-window--configuration nil "The window configuration use for `toggle-one-window'.")
(defun my-window--one-window-p () (equal 1 (length (cl-remove-if #'(lambda (w) (and (window-dedicated-p w) (not (window-parameter w 'quit-restore)))) (window-list)))))
(defun my/toggle-one-window () "Toggle between window layout and one window." (interactive) (cond ;; 如果当前 buffer 所在 Window 是 popper ((my-popper-window-popper-p (current-buffer)) (if (my-window--one-window-p) (when my-window--configuration (set-window-configuration my-window--configuration) (setq my-window--configuration nil))
(setq my-window--configuration (current-window-configuration))
(let ((buffer (current-buffer)))
(other-window 1)
(delete-other-windows)
(switch-to-buffer buffer))))
(t
(if (my-window--one-window-p)
(when my-window--configuration
(set-window-configuration my-window--configuration)
(setq my-window--configuration nil))
(setq my-window--configuration (current-window-configuration))
(delete-other-windows)))))
(global-set-key (kbd "M-;") #'my/toggle-one-window) #+end_src
** yasnippet 快速插入代码片段
#+begin_src elisp (use-package yasnippet :straight t :defer t :config (when (file-directory-p my-private-snippets-dir) (add-to-list 'yas-snippet-dirs my-private-snippets-dir)) (yas-reload-all))
(use-package yasnippet-snippets :straight t :after yasnippet)
#+end_src
** separedit 快速编辑
#+begin_src elisp (use-package separedit :straight t :bind ("C-c '" . separedit)) #+end_src
** compile 增强
#+begin_src elisp (use-package fancy-compilation :straight t :after compile :config (setq fancy-compilation-override-colors nil) (fancy-compilation-mode +1)) #+end_src
** eshell 增强
*** eshell 基本配置 #+begin_src elisp (use-package eshell :disabled :defer t :custom (eshell-kill-processes-on-exit t) :config
(my-cc--import-vcvars)
(setq exec-path (parse-colon-path (getenv "Path")))
;; 默认为插入模式
(add-to-list 'meow-mode-state-list '(eshell-mode . insert))
;; 配合 popper 实现 toggle 效果
(defun my/eshell ()
(interactive)
(if-let* ((window (caar popper-open-popup-alist))
(buffer (cdar popper-open-popup-alist))
(eshell-opened (string= eshell-buffer-name (buffer-name buffer))))
(when (window-live-p window)
(delete-window window))
(eshell)))
;; cat 高亮
(defun my-eshell-cat-with-syntax-highlight (file)
"Like cat but with syntax highlight."
(with-temp-buffer
(insert-file-contents file)
(let ((buffer-file-name file))
(delay-mode-hooks
(set-auto-mode)
(font-lock-ensure)))
(buffer-string)))
(advice-add 'eshell/cat :override #'my-eshell-cat-with-syntax-highlight)
)
#+end_src
*** eshell 历史记录设置
#+begin_src elisp (use-package em-hist :disabled :defer t :custom (eshell-history-size 10240) (eshell-hist-ignoredups t) (eshell-save-history-on-exit t)) #+end_src
*** esh-mode 设置
#+begin_src elisp (use-package esh-mode :disabled :bind (:map eshell-mode-map ("C-r" . consult-history))) #+end_src
*** eshell-git-prompt 提示符美化
#+begin_src elisp (use-package eshell-git-prompt :disabled :straight t :after esh-mode :config (eshell-git-prompt-use-theme 'powerline)) #+end_src
*** eshell-z 智能目录跳转
#+begin_src elisp (use-package eshell :disabled :defer t :config (require 'em-dirs) (defvar my-eshell-z--table nil)
(defvar my-eshell-z-file-name (expand-file-name "z" eshell-directory-name))
(defun my-eshell-z--load ()
(setq my-eshell-z--table (make-hash-table :test 'equal))
(when (file-exists-p my-eshell-z-file-name)
(dolist (element (with-temp-buffer
(insert-file-contents my-eshell-z-file-name)
(goto-char (point-min))
(read (current-buffer))))
(when (file-directory-p (car element))
(puthash (car element) (cadr element) my-eshell-z--table)))))
(defun my-eshell-z--save ()
(let ((dir (file-name-directory my-eshell-z-file-name)))
(unless (file-exists-p dir)
(make-directory dir t))
(with-temp-file my-eshell-z-file-name
(let ((result (list)))
(maphash #'(lambda (key value)
(when (> value 0)
(add-to-list 'result (list key (- value 0.1))))
)
my-eshell-z--table)
(prin1 result (current-buffer))))))
(defun my-eshell-z--update ()
(let ((cur-dir default-directory))
(if-let ((score (gethash cur-dir my-eshell-z--table)))
(puthash cur-dir (+ score 1) my-eshell-z--table)
(puthash cur-dir 1 my-eshell-z--table))))
(defun eshell/z (&rest args)
(let* ((first (car args))
(result first))
(if (not first)
(setq result "~")
(cond
((string-match-p "^[\\.]+$" first)
(let ((target ""))
(cl-loop repeat (length first) do
(setq target (concat target "../")))
(setq result target)))
((string= "-" first)
(setq result first))
(t (let ((regex "")
target-score
target-dir)
(dolist (arg args)
(setq regex (concat regex arg ".*")))
(maphash #'(lambda (key value)
(when (string-match-p regex key)
(when (and target-score (> value target-score))
(setq target-dir key
target-score value))
(unless target-score
(setq target-dir key
target-score value))))
my-eshell-z--table)
(if target-dir
(setq result target-dir)
(setq result args))
))))
;; (message "result: %s" result)
(eshell/cd result)))
(add-hook 'eshell-mode-hook #'my-eshell-z--load)
(add-hook 'eshell-directory-change-hook #'my-eshell-z--update)
(add-hook 'kill-emacs-hook #'my-eshell-z--save)
(defun eshell/zp (&rest args)
"Jump directory in current project."
(let* ((project-root (nth 2 (project-current))))
(unless project-root
(setq project-root default-directory))
(when-let* ((result (eshell-command-result
(concat "fd --type directory --absolute-path " (car args) " " project-root)))
(paths (split-string result "\n" t)))
(if (= (length paths) 1)
(eshell/cd (car paths))
(eshell/cd (completing-read "Choose: " paths nil t))))))
)
#+end_src
** emacs-libvterm #+begin_src elisp (use-package vterm :straight (vterm :type git :host github :repo "xhcoding/emacs-libvterm") :preface (defun my-vterm-setup() (setq-local global-hl-line-mode nil) (meow-mode -1)) :hook (vterm-mode . my-vterm-setup) :config (setq vterm-shell "pwsh.exe") (defun my-project-shell () "Start an inferior shell in the current project's root directory. If a buffer already exists for running a shell in the project's root, switch to it. Otherwise, create a new shell buffer. With \[universal-argument] prefix arg, create a new inferior shell buffer even if one already exists." (interactive) (require 'comint) (let* ((default-directory (project-root (project-current t))) (default-project-shell-name (project-prefixed-buffer-name "shell")) (shell-buffer (get-buffer default-project-shell-name))) (if (and shell-buffer (not current-prefix-arg)) (if (comint-check-proc shell-buffer) (pop-to-buffer shell-buffer (bound-and-true-p display-comint-buffer-action)) (vterm shell-buffer)) (vterm (generate-new-buffer-name default-project-shell-name)))))
(advice-add 'project-shell :override #'my-project-shell)
(defvar my-vterm-shells
'(("*vterm-pwsh*" . "pwsh.exe")
("*vterm-ros2*" . "wsl.exe -d ubuntu --cd ~ -e bash -c -l \"source /opt/ros/jazzy/setup.bash && exec bash -l\""))
"Alist of (BUFFER-NAME . SHELL-COMMAND) for vterm shells.")
(defun my/vterm-select-or-create ()
"Select an existing vterm, or create one from a shell or custom name."
(interactive)
(let* ((existing-vterms
(mapcar #'buffer-name
(seq-filter (lambda (buf)
(with-current-buffer buf
(eq major-mode 'vterm-mode)))
(buffer-list))))
(shell-names (mapcar #'car my-vterm-shells))
(candidates (append shell-names existing-vterms))
(chosen (completing-read "VTerm (select or type new name): "
existing-vterms nil nil "")))
(when (string-empty-p chosen)
(setq chosen (completing-read "Select shells: " shell-names nil nil "")))
(if (get-buffer chosen)
(switch-to-buffer chosen)
(let ((vterm-shell (cdr (assoc chosen my-vterm-shells))))
(vterm chosen)))))
)
#+end_src ** fingertip 智能括号插入
#+begin_src emacs-lisp (use-package fingertip :straight (fingertip :type git :host github :repo "manateelazycat/fingertip") :defer t :hook ((prog-mode toml-ts-mode) . my-enable-pair-parents) :bind (:map fingertip-mode-map ("(" . fingertip-open-round) ("[" . fingertip-open-bracket) ("{" . fingertip-open-curly) (")" . fingertip-close-round) ("]" . fingertip-close-bracket) ("}" . fingertip-close-curly) ("=" . fingertip-equal) (""" . fingertip-double-quote) ("SPC" . fingertip-space) ("RET". fingertip-newline) ("C-k" . fingertip-kill) ("M-"" . fingertip-wrap-double-quote) ("M-[" . fingertip-wrap-bracket) ("M-{" . fingertip-wrap-curly) ("M-(" . fingertip-wrap-round) ("M-]" . fingertip-unwrap) ("M-n" . fingertip-jump-right) ("M-p" . fingertip-jump-left) ("M-RET" . fingertip-jump-out-pair-and-newline)) :init (defun my-enable-pair-parents () (if (treesit-parser-list) (fingertip-mode) (electric-pair-mode)))) #+end_src
** color-rg 快速搜索重构
#+begin_src elisp (use-package color-rg :straight (color-rg :type git :host github :repo "manateelazycat/color-rg") :defer t :commands (color-rg-search-symbol-in-project color-rg-search-input-in-project) :custom (color-rg-search-no-ignore-file nil) :config (add-to-list 'meow-mode-state-list '(color-rg-mode . motion))) #+end_src
** markdown
#+begin_src elisp (use-package markdown-mode :straight t :defer t)
#+end_src
** lsp-bridge 代码补全
#+begin_src elisp
(use-package lsp-bridge :straight (lsp-bridge :type git :host github :repo "manateelazycat/lsp-bridge" :files ("*") :build nil) :defer t :bind (:map lsp-bridge-mode-map ([remap xref-find-definitions] . lsp-bridge-find-def) ([remap xref-find-references] . lsp-bridge-find-references) ([remap xref-go-back] . lsp-bridge-find-def-return)) :init
;; 手动添加到 load-path
(add-to-list 'load-path (straight--repos-dir "lsp-bridge"))
(setq lsp-bridge-org-babel-lang-list nil)
;; https://tecosaur.github.io/emacs-config/config.html#lsp-support-src
(cl-defmacro my-lsp-org-babel-enable (lang)
"Support LANG in org source code block."
(cl-check-type lang string)
(let* ((edit-pre (intern (format "org-babel-edit-prep:%s" lang)))
(intern-pre (intern (format "lsp--%s" (symbol-name edit-pre)))))
`(progn
(defun ,intern-pre (info)
(let ((file-name (->> info caddr (alist-get :file))))
(unless file-name
(setq file-name (expand-file-name "OrgBabel/org-src-babel" my-code-dir))
(write-region (point-min) (point-max) file-name))
(setq buffer-file-name file-name)
(my-enable-code-service)))
(put ',intern-pre 'function-documentation
(format "Enable lsp-bridge-mode in the buffer of org source block (%s)."
(upcase ,lang)))
(if (fboundp ',edit-pre)
(advice-add ',edit-pre :after ',intern-pre)
(progn
(defun ,edit-pre (info)
(,intern-pre info))
(put ',edit-pre 'function-documentation
(format "Prepare local buffer environment for org source block (%s)."
(upcase ,lang))))))))
(with-eval-after-load 'org
(dolist (lang '("c" "cpp" "python" "rust"))
(eval `(my-lsp-org-babel-enable ,lang))))
:config
(push '(scss-mode . "vscode-css-language-server") lsp-bridge-single-lang-server-mode-list)
(push '(json-ts-mode . "vscode-json-language-server") lsp-bridge-single-lang-server-mode-list)
(setq lsp-bridge-user-langserver-dir my-etc-dir
lsp-bridge-user-multiserver-dir my-etc-dir)
(setq lsp-bridge-cmake-lsp-server "neocmakelsp")
(setq acm-enable-tabnine nil
acm-enable-quick-access t
lsp-bridge-enable-hover-diagnostic t)
)
#+end_src
** acm-terminal
#+begin_src elisp (use-package popon :straight t :defer t) (use-package acm-terminal :defer t :straight (acm-terminal :type git :host github :repo "twlz0ne/acm-terminal") :init (unless (display-graphic-p) (with-eval-after-load 'acm (require 'acm-terminal)))) #+end_src
** company 补全
#+begin_src elisp (use-package company :straight t :defer t :bind (:map company-active-map ("TAB" . company-complete-selection))) #+end_src
** eglot-booster 加速 eglot
#+begin_src elisp (use-package eglot-booster :straight (eglot-booster :type git :host github :repo "jdtsmith/eglot-booster") :after eglot :config (eglot-booster-mode)) #+end_src
** 启动代码服务
#+begin_src elisp (defun my-enable-code-service () (require 'yasnippet) (yas-minor-mode) (if (getenv "EMACS_USE_EGLOT") (progn (company-mode) (eglot-ensure) ) (require 'lsp-bridge) (lsp-bridge-mode))) #+end_src
** highlight-parentheses 高亮外层括号
#+begin_src elisp (use-package highlight-parentheses :straight t :hook (prog-mode . highlight-parentheses-mode)) #+end_src
** evil-nerd-commenter 智能注释代码
#+begin_src elisp (use-package evil-nerd-commenter :straight t :bind ("C-/" . evilnc-comment-or-uncomment-lines)) #+end_src
** apheleia 智能格式化代码
#+begin_src elisp (use-package apheleia :straight t :defer t) #+end_src
** git 配置
*** magit
#+begin_src elisp (use-package magit :straight (magit :type git :host github :repo "magit/magit" :files ("lisp/magit" "lisp/magit*.el" "lisp/git-rebase.el" "lisp/git-commit.el" "docs/magit.texi" "docs/AUTHORS.md" "LICENSE" "Documentation/magit.texi" ; temporarily for stable "Documentation/AUTHORS.md" ; temporarily for stable )) :bind ("C-x g" . magit-status) :config (when sys-is-windows (setenv "GIT_ASKPASS" "git-gui--askpass")))
(use-package ssh-agency :straight t :after magit)
#+end_src
*** 显示当前行的最后提交信息
#+begin_src emacs-lisp (use-package git-messenger :straight t :bind (:map vc-prefix-map ("p" . git-messenger:popup-message) :map git-messenger-map ("m" . git-messenger:copy-message)) :init (setq git-messenger:show-detail t git-messenger:use-magit-popup t)
(defface posframe-border
`((t (:inherit region)))
"Face used by the `posframe' border."
:group 'posframe)
:config
(with-no-warnings
(with-eval-after-load 'hydra
(defhydra git-messenger-hydra (:color blue)
("s" git-messenger:popup-show "show")
("c" git-messenger:copy-commit-id "copy hash")
("m" git-messenger:copy-message "copy message")
("," (catch 'git-messenger-loop (git-messenger:show-parent)) "go parent")
("q" git-messenger:popup-close "quit")))
(defun my-git-messenger:format-detail (vcs commit-id author message)
(if (eq vcs 'git)
(let ((date (git-messenger:commit-date commit-id))
(colon (propertize ":" 'face 'font-lock-comment-face)))
(concat
(format "%s%s %s \n%s%s %s\n%s %s %s \n"
(propertize "Commit" 'face 'font-lock-keyword-face) colon
(propertize (substring commit-id 0 8) 'face 'font-lock-comment-face)
(propertize "Author" 'face 'font-lock-keyword-face) colon
(propertize author 'face 'font-lock-string-face)
(propertize "Date" 'face 'font-lock-keyword-face) colon
(propertize date 'face 'font-lock-string-face))
(propertize (make-string 38 ?─) 'face 'font-lock-comment-face)
message
(propertize "\nPress q to quit" 'face '(:inherit (font-lock-comment-face italic)))))
(git-messenger:format-detail vcs commit-id author message)))
(defun my-git-messenger:popup-message ()
"Popup message with `posframe', `pos-tip', `lv' or `message', and dispatch actions with `hydra'."
(interactive)
(let* ((hydra-hint-display-type 'message)
(vcs (git-messenger:find-vcs))
(file (buffer-file-name (buffer-base-buffer)))
(line (line-number-at-pos))
(commit-info (git-messenger:commit-info-at-line vcs file line))
(commit-id (car commit-info))
(author (cdr commit-info))
(msg (git-messenger:commit-message vcs commit-id))
(popuped-message (if (git-messenger:show-detail-p commit-id)
(my-git-messenger:format-detail vcs commit-id author msg)
(cl-case vcs
(git msg)
(svn (if (string= commit-id "-")
msg
(git-messenger:svn-message msg)))
(hg msg)))))
(setq git-messenger:vcs vcs
git-messenger:last-message msg
git-messenger:last-commit-id commit-id)
(run-hook-with-args 'git-messenger:before-popup-hook popuped-message)
(git-messenger-hydra/body)
(cond ((and (fboundp 'posframe-workable-p) (posframe-workable-p))
(let ((buffer-name "*git-messenger*"))
(posframe-show buffer-name
:string (concat (propertize "\n" 'face '(:height 0.3))
popuped-message
"\n"
(propertize "\n" 'face '(:height 0.3)))
:left-fringe 8
:right-fringe 8
:max-width (round (* (frame-width) 0.62))
:max-height (round (* (frame-height) 0.62))
:internal-border-width 1
:internal-border-color (face-background 'posframe-border nil t)
:background-color (face-background 'tooltip nil t))
(unwind-protect
(push (read-event) unread-command-events)
(posframe-hide buffer-name))))
((and (fboundp 'pos-tip-show) (display-graphic-p))
(pos-tip-show popuped-message))
((fboundp 'lv-message)
(lv-message popuped-message)
(unwind-protect
(push (read-event) unread-command-events)
(lv-delete-window)))
(t (message "%s" popuped-message)))
(run-hook-with-args 'git-messenger:after-popup-hook popuped-message)))
(advice-add #'git-messenger:popup-close :override #'ignore)
(advice-add #'git-messenger:popup-message :override #'my-git-messenger:popup-message)))
#+end_src
*** smerge-mode 解决冲突
#+begin_src elisp (use-package smerge-mode :ensure nil :pretty-hydra ((:title (pretty-hydra-title "Smerge" 'octicon "diff") :color pink :quit-key "q") ("Move" (("n" smerge-next "next") ("p" smerge-prev "previous")) "Keep" (("b" smerge-keep-base "base") ("u" smerge-keep-upper "upper") ("l" smerge-keep-lower "lower") ("a" smerge-keep-all "all") ("RET" smerge-keep-current "current") ("C-m" smerge-keep-current "current")) "Diff" (("<" smerge-diff-base-upper "upper/base") ("=" smerge-diff-upper-lower "upper/lower") (">" smerge-diff-base-lower "upper/lower") ("R" smerge-refine "refine") ("E" smerge-ediff "ediff")) "Other" (("C" smerge-combine-with-next "combine") ("r" smerge-resolve "resolve") ("k" smerge-kill-current "kill") ))) :bind (:map smerge-mode-map ("C-c m" . smerge-mode-hydra/body))) #+end_src
** visual-regexp 可视化的正则替换
#+begin_src elisp (use-package visual-regexp :straight t :defer t) #+end_src
** expand-region 递增选区
#+begin_src emacs-lisp (use-package expand-region :straight t :bind (("C-=" . er/expand-region)) :config (defun treesit-mark-bigger-node () (let* ((root (treesit-buffer-root-node)) (node (treesit-node-descendant-for-range root (region-beginning) (region-end))) (node-start (treesit-node-start node)) (node-end (treesit-node-end node))) ;; Node fits the region exactly. Try its parent node instead. (when (and (= (region-beginning) node-start) (= (region-end) node-end)) (when-let ((node (treesit-node-parent node))) (setq node-start (treesit-node-start node) node-end (treesit-node-end node)))) (set-mark node-end) (goto-char node-start)))
(add-to-list 'er/try-expand-list 'treesit-mark-bigger-node)
)
#+end_src
** 启用 treesit
#+begin_src elisp (setq major-mode-remap-alist '((c-mode . c-ts-mode) (c++-mode . c++-ts-mode) (c-or-c++-mode . c-or-c++-ts-mode) (cmake-mode . cmake-ts-mode) (conf-toml-mode . toml-ts-mode) (csharp-mode . csharp-ts-mode) (css-mode . css-ts-mode) (dockerfile-mode . dockerfile-ts-mode) (go-mode . go-ts-mode) (java-mode . java-ts-mode) (json-mode . json-ts-mode) (js-json-mode . json-ts-mode) (javascript-mode . js-ts-mode) (python-mode . python-ts-mode) (sh-mode . bash-ts-mode)))
(setq treesit-language-source-alist '((bash . ("https://github.com/tree-sitter/tree-sitter-bash")) (c . ("https://github.com/tree-sitter/tree-sitter-c")) (cpp . ("https://github.com/tree-sitter/tree-sitter-cpp")) (css . ("https://github.com/tree-sitter/tree-sitter-css")) (cmake . ("https://github.com/uyha/tree-sitter-cmake")) (csharp . ("https://github.com/tree-sitter/tree-sitter-c-sharp.git")) (dockerfile . ("https://github.com/camdencheek/tree-sitter-dockerfile")) (elisp . ("https://github.com/Wilfred/tree-sitter-elisp")) (go . ("https://github.com/tree-sitter/tree-sitter-go")) (gomod . ("https://github.com/camdencheek/tree-sitter-go-mod.git")) (html . ("https://github.com/tree-sitter/tree-sitter-html")) (java . ("https://github.com/tree-sitter/tree-sitter-java.git")) (javascript . ("https://github.com/tree-sitter/tree-sitter-javascript")) (json . ("https://github.com/tree-sitter/tree-sitter-json")) (lua . ("https://github.com/Azganoth/tree-sitter-lua")) (make . ("https://github.com/alemuller/tree-sitter-make")) (markdown . ("https://github.com/MDeiml/tree-sitter-markdown" nil "tree-sitter-markdown/src")) (ocaml . ("https://github.com/tree-sitter/tree-sitter-ocaml" nil "ocaml/src")) (org . ("https://github.com/milisims/tree-sitter-org")) (python . ("https://github.com/tree-sitter/tree-sitter-python")) (php . ("https://github.com/tree-sitter/tree-sitter-php")) (typescript . ("https://github.com/tree-sitter/tree-sitter-typescript" nil "typescript/src")) (tsx . ("https://github.com/tree-sitter/tree-sitter-typescript" nil "tsx/src")) (ruby . ("https://github.com/tree-sitter/tree-sitter-ruby")) (rust . ("https://github.com/tree-sitter/tree-sitter-rust")) (sql . ("https://github.com/m-novikov/tree-sitter-sql")) (vue . ("https://github.com/merico-dev/tree-sitter-vue")) (yaml . ("https://github.com/ikatyang/tree-sitter-yaml")) (toml . ("https://github.com/tree-sitter/tree-sitter-toml")) (zig . ("https://github.com/GrayJack/tree-sitter-zig"))))
(defun my-treesit-install-langs (langs) "" (dolist (lang langs) (let* ((out-dir (locate-user-emacs-file "tree-sitter")) (soext (car dynamic-library-suffixes)) (lib-name (concat "libtree-sitter-" (symbol-name lang) soext)) (lib-path (expand-file-name lib-name out-dir))) (unless (file-exists-p lib-path) (treesit-install-language-grammar lang)))))
(unless sys-is-windows (my-treesit-install-langs '(bash c cpp cmake dockerfile elisp json python rust yaml))) #+end_src
** elisp 配置
#+begin_src emacs-lisp (use-package elisp-mode :hook (emacs-lisp-mode . my-enable-elisp-dev) :config (defun my-enable-elisp-dev () (my-enable-code-service) (treesit-parser-create 'elisp))) #+end_src
** C++ 开发配置
*** cmake-ts-mode 设置
#+begin_src emacs-lisp (use-package cmake-ts-mode :mode "\CMakeLists.txt\'" :hook (cmake-ts-mode . my-enable-code-service)) #+end_src
*** c-ts-mode 设置
#+begin_src elisp (use-package c-ts-mode :hook ((c-ts-mode c++-ts-mode) . my-enable-code-service) :custom (c-ts-mode-indent-offset 4) (c-basic-offset 4)) #+end_src
*** 导入 VS 环境变量
#+begin_src elisp (defconst my-cc--msvc-env-vars '( "DevEnvDir" "Framework40Version" "FrameworkDir" "FrameworkDIR32" "FrameworkDIR64" "FrameworkVersion" "FrameworkVersion32" "FrameworkVersion64" "INCLUDE" "LIB" "LIBPATH" "NETFXSDKDir" "PATH" "UCRTVersion" "UniversalCRTSdkDir" "user_inputversion" "VCIDEInstallDir" "VCINSTALLDIR" "VCToolsInstallDir" "VCToolsRedistDir" "VCToolsVersion" "VS170COMNTOOLS" "VisualStudioVersion" "VSINSTALLDIR" "WindowsLibPath" "WindowsSdkBinPath" "WindowsSdkDir" "WindowsSDKLibVersion" "WindowsSDKVersion" "WindowsSDK_ExecutablePath_x64" "WindowsSDK_ExecutablePath_x86" ;;/* These are special also need to be cached */ "CL" "CL" "LINK" "LINK" "TMP" "UCRTCONTEXTROOT" "VCTARGETSPATH" ) "List of environment variables required for Visual C++ to run as expected for a VS installation.")
;; 导入 vs2022 community 64位构建环境变量 (defun my-cc--import-vcvars () "Import the environment variables corresponding to a VS dev batch file." (let* ((common-dir "C:/Program Files/Microsoft Visual Studio/2022/Community/Common7/Tools") (devbat "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/vcvarsall.bat") (args "amd64") (major-version "17") (bat-path (expand-file-name "cmake-tools-vcvars.bat" (temporary-file-directory))) (env-file-path (concat bat-path ".env")) (vars (make-hash-table :test 'equal))) (setq bat (format "@echo off cd /d "%%~dp0" set "VS%s0COMNTOOLS=%s" set "INCLUDE=" call "%s" %s setlocal enableextensions enabledelayedexpansion cd /d "%%~dp0" %s" major-version common-dir devbat args (mapconcat (lambda (env-var) (format "if DEFINED %s echo %s := %%%s%% >> %s" env-var env-var env-var env-file-path)) my-cc--msvc-env-vars "\n"))) (with-temp-file bat-path (insert bat)) (shell-command-to-string (concat "cmd.exe /c " bat-path))
(if (not (file-exists-p env-file-path))
nil
(dolist (line (with-temp-buffer
(insert-file-contents env-file-path)
(split-string (buffer-string) "\n" t)))
(let ((var (split-string line " := " t)))
(puthash (string-trim (car var)) (string-trim (cadr var)) vars)))
(if (not (gethash "INCLUDE" vars))
nil
(maphash #'(lambda (key value)
(setenv key value))
vars)))))
#+end_src
*** cmake-integration 配置
#+begin_src elisp (use-package cmake-integration :straight (cmake-integration :type git :host github :repo "darcamo/cmake-integration")) #+end_src
** Rust 开发配置
#+begin_src elisp (use-package rust-ts-mode :mode "\.rs\'" :hook (rust-ts-mode . my-enable-code-service)) #+end_src
** python 开发配置
#+begin_src emacs-lisp (use-package python :hook (python-ts-mode . my-enable-code-service)) #+end_src
** json-ts-mode
#+begin_src elisp (use-package json-ts-mode :hook (json-ts-mode . my-enable-code-service) :custom (json-ts-mode-indent-offset 2) :config (defun my-json-generate-language-server-json () (with-temp-file (expand-file-name "vscode-json-language-server.json" my-etc-dir) (url-insert-file-contents "https://www.schemastore.org/api/json/catalog.json") (let* ((catalog (json-parse-string (buffer-substring-no-properties (point-min) (point-max)) :object-type 'plist)) (schemas (plist-get catalog :schemas)) (exclude-pattern "..\(cff\|cjs\|js\|mjs\|toml\|yaml\|yml\)$") (json-validation )) (mapc #'(lambda (schema) (when-let ((url (plist-get schema :url)) (file-match (plist-get schema :fileMatch)) (filtered-file-match (seq-filter #'(lambda (match) (and (not (string-prefix-p "!" match)) (not (string-match-p exclude-pattern match)))) file-match))) (add-to-list 'json-validation `((:url . ,url) (:fileMatch ., filtered-file-match))))) schemas)
(let* ((ls '(:name "vscode-json-language-server" :languageId "json" :command ("vscode-json-language-server" "--stdio")))
(json `(:schemas ,json-validation))
(settings `(:json ,json))
(json-encoding-pretty-print t))
(plist-put ls :settings settings)
(erase-buffer)
(goto-char (point-min))
(insert (json-encode ls))))))
)
#+end_src
** qml 开发
#+begin_src elisp (use-package qml-ts-mode :ban minimal? :straight (qml-ts-mode :type git :host github :repo "xhcoding/qml-ts-mode") :hook (qml-ts-mode . my-enable-code-service) :custom (qml-ts-mode-indent-offset 4)) #+end_src
** plantuml 支持
#+begin_src emacs-lisp (use-package plantuml-mode :ban minimal? :straight t :defer t :custom (plantuml-jar-path (expand-file-name "plantuml.jar" my-lib-dir)) (plantuml-default-exec-mode 'jar)) #+end_src
** dape 调试
#+begin_src elisp (use-package dape :straight t) #+end_src
** dash-docs 查询 dash 文档
#+begin_src elisp
(use-package dash-docs
:ban minimal?
:straight t
:commands (my/dash-docs-search)
:bind (([remap apropos-documentation] . my/dash-docs-search)
("
:custom
(dash-docs-enable-debugging nil)
(dash-docs-browser-func 'eaf-open-browser)
:config
(when sys-is-windows
(setq dash-docs-docsets-path (expand-file-name "persist/zeal/docsets" (getenv "SCOOP"))))
;; fix can not open..
;; ref: https://github.com/dash-docs-el/dash-docs/pull/11
(defun dash-docs-sql (db-path sql)
"Run in the db located at DB-PATH the SQL command and parse the results.
If there are errors, print them in `dash-docs-debugging-buffer'" (dash-docs-parse-sql-results (with-output-to-string (let ((error-file (when dash-docs-enable-debugging (make-temp-file "dash-docs-errors-file")))) (call-process "sqlite3" nil (list standard-output error-file) nil ;; args for sqlite3: "-list" "-init" null-device db-path sql)
;; display errors, stolen from emacs' `shell-command` function
(when (and error-file (file-exists-p error-file))
(if (< 0 (nth 7 (file-attributes error-file)))
(with-current-buffer (dash-docs-debugging-buffer)
(let ((pos-from-end (- (point-max) (point))))
(or (bobp)
(insert "\f\n"))
;; Do no formatting while reading error file,
;; because that can run a shell command, and we
;; don't want that to cause an infinite recursion.
(format-insert-file error-file nil)
;; Put point after the inserted errors.
(goto-char (- (point-max) pos-from-end)))
(display-buffer (current-buffer))))
(delete-file error-file))))))
;; 搜索文档
(defun my/dash-docs-search (&optional pattern)
"Search doc."
(interactive)
(when-let ((search-pattern
(or pattern
(let* ((current-symbol
(if (use-region-p)
(buffer-substring-no-properties (region-beginning) (region-end))
(thing-at-point 'symbol)))
(input-string
(string-trim
(read-string
(format "Pattern (%s): " current-symbol) nil))))
(when (string-blank-p input-string)
(setq input-string current-symbol))
input-string))))
(dash-docs-create-buffer-connections)
(dash-docs-create-common-connections)
(when-let ((results (dash-docs-search search-pattern))
(select t)
(select-index -1)
(select-result t))
(setq select (completing-read "Select: "
(let ((index 0))
(mapcar (lambda (result)
(setq index (+ index 1))
(format "%s. %s" index (car result)))
results))
nil t))
(setq select-index (- (string-to-number (car (split-string select "\\. "))) 1))
(setq select-result (nth select-index results))
(dash-docs-browse-url (cdr select-result)))))
(defun my/dash-docs-search-docset (&optional docset)
"Search doc in `docset'"
(interactive (list (dash-docs-read-docset
"Docset"
(dash-docs-installed-docsets))))
(unless (boundp 'dash-docs-docsets)
(setq-local dash-docs-docsets `(,docset)))
(let ((old-dash-docs-docsets dash-docs-docsets))
(unwind-protect
(progn
(setq-local dash-docs-docsets `(,docset))
(call-interactively #'my/dash-docs-search)
(setq-local dash-docs-docsets old-dash-docs-docsets))
(setq-local dash-docs-docsets old-dash-docs-docsets))))
)
#+end_src
** shrface 让 eww 的阅读体验更好
#+begin_src elisp (use-package shrface :ban minimal? :straight t :defer t :config (shrface-basic) (shrface-trial) (shrface-default-keybindings) ; setup default keybindings (setq shrface-href-versatile t))
#+end_src
** eww 配置
#+begin_src elisp (use-package eww :ban minimal? :defer t :init (add-hook 'eww-after-render-hook #'shrface-mode) :config (require 'shrface))
#+end_src
** olivetti 居中显示内容
#+begin_src elisp (use-package olivetti :ban minimal? :straight t :defer t :hook (prog-mode text-mode outline-mode special-mode elfeed-show-mode) ) #+end_src
** org 配置 *** org-mode 美化设置
#+begin_src elisp (use-package org :ban minimal? :defer t :straight t :hook (org-mode . my--org-prettify-symbols) :custom-face ;; 设置Org mode标题以及每级标题行的大小 (org-document-title ((t (:height 1.75 :weight bold)))) (org-level-1 ((t (:height 1.2 :weight bold)))) (org-level-2 ((t (:height 1.15 :weight bold)))) (org-level-3 ((t (:height 1.1 :weight bold)))) (org-level-4 ((t (:height 1.05 :weight bold)))) (org-level-5 ((t (:height 1.0 :weight bold)))) (org-level-6 ((t (:height 1.0 :weight bold)))) (org-level-7 ((t (:height 1.0 :weight bold)))) (org-level-8 ((t (:height 1.0 :weight bold)))) (org-level-9 ((t (:height 1.0 :weight bold)))) ;; 设置代码块用上下边线包裹 (org-block-begin-line ((t (:underline t :background unspecified)))) (org-block-end-line ((t (:overline t :underline nil :background unspecified)))) :custom ;; 标题行美化 (org-fontify-whole-heading-line t) ;; 设置标题行折叠符号 (org-ellipsis " ▾") ;; TODO标签美化 (org-fontify-todo-headline t) ;; DONE标签美化 (org-fontify-done-headline t) ;; 引用块美化 (org-fontify-quote-and-verse-blocks t) ;; 隐藏宏标记 (org-hide-macro-markers t) ;; 隐藏强调标签 (org-hide-emphasis-markers t) ;; 高亮latex语法 (org-highlight-latex-and-related '(native script entities)) ;; 以UTF-8显示 (org-pretty-entities t) ;; 当启用缩进模式时自动隐藏前置星号 (org-indent-mode-turns-on-hiding-stars t) ;; 自动启用缩进 (org-startup-indented nil) ;; 根据标题栏自动缩进文本 (org-adapt-indentation nil) ;; 自动显示图片 (org-startup-with-inline-images t) ;; 默认以Overview的模式展示标题行 (org-startup-folded 'overview) ;; 允许字母列表 (org-list-allow-alphabetical t) ;; 编辑时检查是否在折叠的不可见区域 (org-fold-catch-invisible-edits 'smart) ;; 上标^下标_是否需要特殊字符包裹,这里设置需要用大括号包裹 (org-use-sub-superscripts '{}) :config
(when (my-font-installed-p "等距更纱黑体 SC")
(create-fontset-from-fontset-spec
(font-xlfd-name
(font-spec :family "等距更纱黑体 SC"
:weight 'regular
:slant 'normal
:registry "fontset-orgtable")))
(set-fontset-font "fontset-orgtable" '(#x0 . #xffff)
(font-spec :family "等距更纱黑体 SC"
:weight 'regular
:slant 'normal))
(set-face-attribute 'org-table nil :fontset "fontset-orgtable" :font "fontset-orgtable"))
(defun my--org-prettify-symbols ()
(setq prettify-symbols-alist
(mapcan (lambda (x) (list x (cons (upcase (car x)) (cdr x))))
'(
("#+begin_src" . ?✎)
("#+end_src" . ?□)
("#+results:" . ?💻)
("#+date:" . ?📅)
("#+author:" . ?👤)
("#+title:" . ?📓)
("#+identifier:" . ?🆔)
("#+hugo_tags:" . ?📍)
("#+hugo_categories:" . ?📁)
("#+hugo_locale:" . ?🌐)
("#+hugo_draft:" . ?🚮)
("#+hugo_custom_front_matter:" . ?📝)
("#+begin_quote" . ?«)
("#+end_quote" . ?»)
)))
(setq prettify-symbols-unprettify-at-point t)
(prettify-symbols-mode 1))
;; 设置标题行之间总是有空格;列表之间根据情况自动加空格
(setq org-blank-before-new-entry '((heading . t)
(plain-list-item . auto)
)))
(use-package org-modern
:ban minimal?
:straight t
:hook (org-mode . org-modern-mode)
:config
;; 额外的行间距,0.1表示10%,1表示1px
(setq-default line-spacing 0.1)
;; 复选框美化
(setq org-modern-checkbox
'((?X . #("▢✓" 0 2 (composition ((2)))))
(?- . #("▢–" 0 2 (composition ((2)))))
(?\s . #("▢" 0 1 (composition ((1)))))))
;; 列表符号美化
(setq org-modern-list
'((?- . "•")
(?+ . "◦")
(?* . "▹")))
;; 代码块类型美化,我们使用了 prettify-symbols-mode' (setq org-modern-block-name nil) ;; #+关键字美化,我们使用了 prettify-symbols-mode'
(setq org-modern-keyword nil)
;; 关闭表格美化
(setq org-modern-table nil)
)
(use-package org-appear :ban minimal? :straight t :hook (org-mode . org-appear-mode) :config (setq org-appear-autolinks t) (setq org-appear-autosubmarkers t) (setq org-appear-autoentities t) (setq org-appear-autokeywords t) (setq org-appear-inside-latex t)) #+end_src
*** org 基本配置
#+begin_src elisp (use-package org :ban minimal? :defer t :straight t :custom (org-directory my-org-dir) (org-modules '(ol-wl)) (org-structure-template-alist '(("q" . "quote\n") ("s" . "src") ("e" . "src elisp\n") ("c" . "src cpp\n") ("h" . "export html")) )) #+end_src
*** org babel 设置
#+begin_src elisp (use-package org :ban minimal? :defer t :straight t :custom (org-confirm-babel-evaluate nil) (org-export-use-babel nil) (org-src-lang-modes '(("C" . c-ts) ("C++" . c++-ts) ("asymptote" . asy) ("bash" . sh) ("beamer" . latex) ("calc" . fundamental) ("cpp" . c++-ts) ("ditaa" . artist) ("desktop" . conf-desktop) ("dot" . fundamental) ("elisp" . emacs-lisp) ("ocaml" . tuareg) ("screen" . shell-script) ("shell" . sh) ("sqlite" . sql) ("toml" . conf-toml))) :config (org-babel-do-load-languages 'org-babel-load-languages '((emacs-lisp . t) (perl . t) (python . t) (ruby . t) (js . t) (css . t) (sass . t) (C . t) (java . t) (plantuml . t) (sql . t))) ;; C 执行支持 :stdin 数据 (defun myorg-babel-execute-add-stdin(args) (let ((body (nth 0 args)) (params (nth 1 args)) (stdin (cdr (assq :stdin params))) (cmdline (cdr (assq :cmdline params))) (stdin-file (expand-file-name "input_data.txt" (temporary-file-directory))) (data)) (when stdin (setq data (save-excursion (org-babel-goto-named-src-block stdin) (org-element-property :value (org-element-at-point)))) (with-temp-file stdin-file (insert data)) (setq cmdline (concat (or cmdline "") " < " stdin-file)) (setf (alist-get :cmdline params) cmdline)) `(,body ,params) )) (advice-add #'org-babel-C-execute :filter-args 'my*org-babel-execute-add-stdin)
)
#+end_src
*** gtd 配置
#+begin_src elisp (use-package :org :ban minimal? :defer t :bind (("C-c a" . org-agenda) ("C-c c" . org-capture)) :init (setq my-org-gtd-dir (expand-file-name "Gtd" my-org-dir) my-org-inbox-file (expand-file-name "inbox.org" my-org-gtd-dir) my-org-projects-file (expand-file-name "projects.org" my-org-gtd-dir))
(defun my-org--verify-refile-target ()
"Exclude todo keywords with a done state from refile targets."
(not (member (nth 2 (org-heading-components)) org-done-keywords)))
:custom
(org-agenda-files `(,my-org-inbox-file ,my-org-projects-file))
(org-capture-templates `(("i" "Inbox" entry
(file ,my-org-inbox-file)
,(concat "* TODO %?\n"
"/Entered on/ %U"))))
(org-todo-keywords
'((sequence "TODO(t)" "NEXT(n)" "HOLD(h)" "|" "DONE(d)" "CANCELLED(c)")))
(org-refile-targets '((nil :maxlevel . 9)
(org-agenda-files :maxlevel . 9)))
(org-refile-use-outline-path t)
(org-outline-path-complete-in-steps nil)
(org-refile-allow-creating-parent-nodes 'confirm)
(org-refile-target-verify-function 'my-org--verify-refile-target)
(org-agenda-span 'day)
(org-agenda-hide-tags-regexp ".")
(org-agenda-prefix-format
'((agenda . " %i %-12:c%?-12t% s")
(todo . " ")
(tags . " %i %-12:c")
(search . " %i %-12:c")))
(org-agenda-custom-commands
'(("g" "Get Things Done (GTD)"
((agenda "" ((org-deadline-warning-days 0)))
(todo "NEXT"
((org-agenda-prefix-format " %i %-12:c ")
(org-agenda-overriding-header "\nTasks\n")))
(tags-todo "inbox"
((org-agenda-prefix-format " %?-12t% s")
(org-agenda-overriding-header "\nInbox\n")))))))
)
#+end_src
*** export 设置
#+begin_src emacs-lisp (use-package org :ban minimal? :straight t :defer t :hook (org-mode . my--set-org-html-head-extra) :custom (org-export-with-broken-links t) :config (defun my--set-org-html-head-extra () "Set org html head extra" (let ((path (expand-file-name "custom-head.html" my-etc-dir))) (when (file-exists-p path) (setq org-html-head-extra (with-temp-buffer (insert-file-contents path) (buffer-string)))))) )
(use-package htmlize :ban minimal? :straight t :after org)
#+end_src
*** 生成 TOC
#+begin_src emacs-lisp (use-package toc-org :ban minimal? :straight t :commands (toc-org-insert-toc) :custom (toc-org-max-depth 3)) #+end_src
*** org-contrib wanderlust 支持
#+begin_src elisp (use-package org-contrib :ban minimal? :straight t :after org) #+end_src
*** 增量载入 org 包
#+begin_src emacs-lisp (when full? (doom-load-packages-incrementally '(calendar find-func format-spec org-macs org-compat org-faces org-entities org-list org-pcomplete org-src org-footnote org-macro ob org org-agenda org-capture org-gtd))) #+end_src
** 知识管理及博客配置 *** denote
#+begin_src elisp (use-package denote :ban minimal? :straight t :custom (denote-directory my-notes-dir) :config (defun denote-blog () "Create blog for publish." (declare (interactive-only t)) (interactive) (let ((denote-directory my-blogs-org-dir) (denote-prompts '(title)) (denote-org-front-matter "#+title: %1$s ,#+date: %2$s ,#+author: xhcoding ,#+identifier: %4$s ,#+hugo_locale: zh ,#+hugo_tags: %3$s ,#+hugo_categories: %3$s ,#+hugo_draft: false
" )) (call-interactively #'denote-open-or-create)))
(defun denote-blog-image-insert (file)
"Insert image file to blog."
(declare (interactive-only t))
(interactive "fImage file: ")
(let* ((blog-file-title (denote-retrieve-filename-title (buffer-file-name)))
(blog-image-dir (concat (expand-file-name blog-file-title my-blogs-image-dir) "/"))
(blog-image-ext (concat "." (file-name-extension file)))
(blog-default-image-title (concat blog-file-title (format-time-string "-%H%M%S")))
(blog-image-title (read-string "Image title: " "" nil blog-default-image-title))
(blog-image-new-name (denote-format-file-name blog-image-dir (denote-retrieve-filename-identifier (buffer-file-name)) nil blog-image-title blog-image-ext nil)))
(unless (file-directory-p blog-image-dir)
(make-directory blog-image-dir t))
(copy-file file blog-image-new-name)
(insert (format "[[file:%s]]" (file-relative-name blog-image-new-name (file-name-directory (buffer-file-name)))))))
(defun my-denote-blog-link-ol-export (link description format)
""
(let* ((denote-directory my-blogs-org-dir)
(path-id (denote-link--ol-resolve-link-to-target link :path-id))
(path (file-relative-name (car path-id))))
(format "[%s](%s)" description (concat my-hugo-post-url (file-name-sans-extension (downcase path))))))
(with-eval-after-load 'org
(org-link-set-parameters "denote" :export #'my-denote-blog-link-ol-export))
)
#+end_src
*** hugo 配置
#+begin_src emacs-lisp (if sys-is-windows (defconst my-hugo-root-dir (expand-file-name "D:/Blog")) (if sys-is-wsl2 (defconst my-hugo-root-dir (expand-file-name "/mnt/d/Blog")) (defconst my-hugo-root-dir (expand-file-name "~/Blog")))) (defconst my-hugo-image-url "https://images.xhcoding.cn/blog")
(defconst my-hugo-post-url "https://xhcoding.cn/post/")
(use-package ox-hugo :ban minimal? :straight t :after org :custom (org-hugo-base-dir my-hugo-root-dir) (org-hugo-section "post") (org-hugo-default-section-directory "post") ;; (org-hugo-auto-set-lastmod t) :config (defun my-hugo--blogs-image-path-p (raw-path) (let ((file-path (expand-file-name raw-path))) (equal (string-match-p (regexp-quote (expand-file-name my-blogs-image-dir)) file-path) 0)))
;; D:/Data/Blogs/images/1.png ==> https://images.xhcoding.cn/blog/1.png
(defun my-hugo--image-path-to-url (raw-path)
(let ((file-path (expand-file-name raw-path)))
(concat my-hugo-image-url (string-trim-left file-path my-blogs-image-dir))))
;; D:/Data/Blogs/images/1.png ==> https://images.xhcoding.cn/blog/1.png
(defun my-hugo*convert-path-to-url (args)
(let* ((link (nth 0 args))
(desc (nth 1 args))
(info (nth 2 args))
(type (org-element-property :type link))
(raw-path (org-element-property :path link)))
(cond
((and (string-equal type "file") (my-hugo--blogs-image-path-p raw-path))
(progn
(let* ((image-url (my-hugo--image-path-to-url raw-path))
(new-link (org-element-put-property link :path image-url )))
`(,new-link ,desc ,info))))
(t `(,link ,desc ,info))
)))
(advice-add #'org-hugo-link :filter-args #'my-hugo*convert-path-to-url)
(defun my-hugo*author-to-authors(args)
(let* ((data (nth 0 args))
(info (nth 1 args))
(repl-str (plist-get info :hugo-front-matter-key-replace))
(repl-author "author>authors"))
(message "%s" repl-str)
(if repl-str
(unless (string-match-p repl-author repl-str)
(plist-put info :hugo-front-matter-key-replace (concat repl-str " " repl-author)))
(plist-put info :hugo-front-matter-key-replace repl-author))
`(,data ,info)))
(advice-add #'org-hugo--replace-keys-maybe :filter-args #'my-hugo*author-to-authors)
(defun my/hugo-export-all-blogs ()
"Export all blogs."
(interactive)
(dolist (file (directory-files my-blogs-org-dir))
(when (string-equal "org" (file-name-extension file))
(with-temp-buffer
(find-file (expand-file-name file my-blogs-org-dir))
(org-hugo-export-to-md)))))
)
(use-package easy-hugo :ban minimal? :straight t :defer t :config (setq easy-hugo-basedir (expand-file-name my-hugo-root-dir) easy-hugo-postdir (expand-file-name my-blogs-org-dir) easy-hugo-org-header t easy-hugo-github-deploy-script "deploy.bat"))
#+end_src
*** 自动在中英文插入空格
#+begin_src elisp (use-package pangu-spacing :ban minimal? :straight t :after org :hook (org-mode . pangu-spacing-mode) :custom (pangu-spacing-real-insert-separtor t)) #+end_src
*** org-download 下载图片
#+begin_src elisp (use-package org-download :disabled t :ban minimal? :straight t :after org :config (when sys-is-windows (setq org-download-screenshot-method "irfanview /capture=4 /convert="%s"" org-download-display-inline-images 'posframe org-download-abbreviate-filename-function 'expand-file-name))
(setq-default org-download-image-dir my-blog-img-dir
org-download-heading-lvl nil)
;; 截图的名称不要总是 screenshot
(defun my/org-download-screenshot ()
"Capture screenshot and insert the resulting file.
The screenshot tool is determined by `org-download-screenshot-method'." (interactive) (let* ((screenshot-dir (file-name-directory org-download-screenshot-file)) (org-file-path (buffer-file-name)) (org-file-name (file-name-sans-extension (file-name-nondirectory org-file-path))) (new-screenshot-name (concat org-file-name ".png")) (new-screenshot-path (expand-file-name new-screenshot-name screenshot-dir))) (when (and (featurep 'org-roam) (string-prefix-p org-roam-directory org-file-path)) (setq new-screenshot-name (substring new-screenshot-name (+ 1 (string-match-p "-" new-screenshot-name))) new-screenshot-path (expand-file-name new-screenshot-name screenshot-dir))) (make-directory screenshot-dir t) (if (functionp org-download-screenshot-method) (funcall org-download-screenshot-method org-download-screenshot-file) (shell-command-to-string (format org-download-screenshot-method org-download-screenshot-file))) (when (file-exists-p org-download-screenshot-file) (rename-file org-download-screenshot-file new-screenshot-path) (org-download-image new-screenshot-path) (delete-file new-screenshot-path))))
(defun my/org-download-clipboard()
"Download from clipboard"
(interactive)
(let ((org-download-screenshot-method "irfanview /clippaste /convert=\"%s\""))
(my/org-download-screenshot)))
;; 将图片保存到当前 buffer 名称目录下
;; ref: https://github.com/abo-abo/org-download/issues/195
(defun my-org-download-method (link)
(let* ((filename
(file-name-nondirectory
(car (url-path-and-query
(url-generic-parse-url link)))))
(org-file-path (buffer-file-name))
(org-file-name (file-name-sans-extension (file-name-nondirectory org-file-path)))
(dirname (expand-file-name org-file-name my-blog-img-dir)))
(when (and (featurep 'org-roam) (string-prefix-p org-roam-directory org-file-path))
(setq dirname (expand-file-name
(substring org-file-name (+ 1 (string-match-p "-" org-file-name)))
my-blog-img-dir)))
(make-directory dirname t)
(expand-file-name (funcall org-download-file-format-function filename) dirname)))
(setq org-download-method 'my-org-download-method))
#+end_src
** eaf 配置
#+begin_src elisp (use-package eaf :ban minimal? :disabled t :straight (emacs-application-framework :type git :host github :repo "emacs-eaf/emacs-application-framework" :files ("*") :build nil) :defer t :commands (eaf-open eaf-open-browser eaf-open-this-buffer eaf-open-pdf-from-history eaf-open-cloud-music) :init ;; 手动添加到 load-path (add-to-list 'load-path (straight--repos-dir "emacs-application-framework")) :config (setq eaf-proxy-type "http") (setq eaf-proxy-host "127.0.0.1") (setq eaf-proxy-port "7890")
(setq eaf-webengine-default-zoom 2.5)
(when sys-is-windows
(setq eaf-chrome-bookmark-file (expand-file-name "~/AppData/Local/Microsoft/Edge/User Data/Default/Bookmarks"))
(defun my-eaf--enable-python-utf8-mode (environment)
(append (list "PYTHONUTF8=1") environment))
(advice-add 'eaf--build-process-environment :filter-return #'my-eaf--enable-python-utf8-mode))
(require 'eaf-browser)
(require 'eaf-pdf-viewer)
(require 'eaf-org-previewer)
(require 'eaf-markdown-previewer)
)
#+end_src
** popweb 配置
#+begin_src elisp
(use-package popweb
:disabled t
:ban minimal?
:straight (popweb :type git :host github :repo "manateelazycat/popweb" :build nil)
:commands (popweb-dict-eudic-input popweb-import-browser-cookies)
:bind ("
;; 欧陆词典,可以登录后加入生词,方便手机同步
(popweb-dict-create "eudic" "https://dict.eudic.net/dicts/en/%s"
(concat
"document.getElementsByTagName('header')[0].style.display = 'none';"
"document.getElementById('head-bar').style.display = 'none';"
"document.getElementById('head-bk').style.display = 'none';"
"document.getElementById('scrollToTop').style.display = 'none';"
"document.getElementById('bodycontent').children[4].style.display = 'none';"
))
)
#+end_src
** dictionary-overlay 方便阅读英文文章
#+begin_src elisp (use-package websocket :disabled t :ban minimal? :straight t :defer t)
(use-package websocket-bridge :disabled t :ban minimal? :straight (websocket-bridge :type git :host github :repo "ginqi7/websocket-bridge") :defer t)
(use-package dictionary-overlay :disabled t :ban minimal? :straight (dictionary-overlay :type git :host github :repo "ginqi7/dictionary-overlay" :build nil) :defer t :init (add-to-list 'load-path (straight--repos-dir "dictionary-overlay")) :custom (dictionary-overlay-python "python")) #+end_src
** 邮件配置
*** Wanderlust 邮件前端
Wanderlust + offlineimap3 + mu
#+begin_src emacs-lisp (use-package wl :disabled t :ban minimal? :straight (wanderlust) :defer t :hook ((wl . meow-motion-mode)) :bind (:map wl-folder-mode-map (("q" . wl-folder-suspend))) :init (setq wl-init-file (expand-file-name "wl.el" my-private-dir) wl-folders-file (expand-file-name "folders.wl" my-private-dir) wl-address-file (expand-file-name "address.wl" my-private-dir)) :config (if (boundp 'mail-user-agent) (setq mail-user-agent 'wl-user-agent)) (if (fboundp 'define-mail-user-agent) (define-mail-user-agent 'wl-user-agent 'wl-user-agent-compose 'wl-draft-send 'wl-draft-kill 'mail-send-hook)) (setq wl-quicksearch-folder "[]")
(setq wl-message-ignored-field-list
'(".")
wl-message-visible-field-list
'("^\\(To\\|Cc\\):"
"^Subject:"
"^\\(From\\|Reply-To\\):"
"^\\(Posted\\|Date\\):"
"^Organization:"
"^X-\\(Face\\(-[0-9]+\\)?\\|Weather\\|Fortune\\|Now-Playing\\):")
wl-message-sort-field-list
(append wl-message-sort-field-list
'("^Reply-To" "^Posted" "^Date" "^Organization")))
;; windows 上 mu find 返回的路径以 /cygdrive/ 开头,我们需要自己处理一下
(defun my--elmo-search-parse-filename-list ()
(let (bol locations)
(goto-char (point-min))
(while (not (eobp))
(beginning-of-line)
(when (and elmo-search-use-drive-letter
(looking-at "^\\(/cygdrive/\\)?\\([A-Za-z]\\)\\([:|]\\)?/"))
(replace-match "/\\2:/")
(beginning-of-line))
(unless (looking-at "^file://")
(insert "file://")
(beginning-of-line))
(setq bol (point))
(end-of-line)
(setq locations (cons (buffer-substring bol (point)) locations))
(forward-line))
(nreverse locations)))
(elmo-search-register-engine
'mu-msys 'local-file
:prog "mu"
:args '("find" elmo-search-split-pattern-list "--fields" "l")
:charset 'utf-8
:parser 'my--elmo-search-parse-filename-list)
(setq elmo-search-default-engine 'mu-msys)
;; mu 的输入要用 gbk 编码,不然无法输入中文
(add-to-list 'process-coding-system-alist '("mu" utf-8 . gbk))
;; mime 附件保存目录
(setq mime-save-directory (expand-file-name "mails" my-archives-dir))
)
#+end_src
调用 compose-mail 前先 require wanderlust
#+begin_src emacs-lisp (when full?
(defun my-*require-wanderlust (&rest _)
(require 'wl))
(advice-add 'compose-mail :before #'my-*require-wanderlust))
#+end_src
*** alert-toast 邮件通知
#+begin_src emacs-lisp (use-package alert-toast :disabled t :ban minimal? :straight t :after wl :config (defun my--notify-new-mail-arrived (number) (alert-toast-notify `(:title "Wanderlust" :message ,(format "你有 %s 封未读邮件" number))))
(add-hook 'wl-biff-new-mail-hook #'my--notify-new-mail-arrived)
)
#+end_src
** elfeed RSS 订阅
#+begin_src emacs-lisp (use-package elfeed :ban minimal? :straight t :defer t :config (defun my-eaf-elfeed-open-url () "Display the currently selected item in an eaf buffer." (interactive) (let ((entry (elfeed-search-selected :ignore-region))) (require 'elfeed-show) (when (elfeed-entry-p entry) ;; Move to next feed item. (elfeed-untag entry 'unread) (elfeed-search-update-entry entry) (unless elfeed-search-remain-on-entry (forward-line)) (my/toggle-one-window) (eaf-open-browser (elfeed-entry-link entry)) ))) )
(use-package elfeed-org :ban minimal? :straight t :after elfeed :config (elfeed-org) (setq rmh-elfeed-org-files `(,(expand-file-name "elfeed.org" my-org-dir)))) #+end_src
** leetcode.el 刷题
#+begin_src elisp (use-package leetcode :ban minimal? :straight (leetcode :type git :host github :repo "xhcoding/leetcode.el") :defer t :custom (leetcode-prefer-language "cpp") (leetcode-save-solutions t) (leetcode-directory (expand-file-name "Project/LeetCode" my-code-dir)) :config ;; 在 leetcode 里关闭自动补全 (defun my-after-leetcode--start-coding (problem problem-info) (let-alist problem (let ((title (leetcode-problem-title problem-info)) (code-buf-name (leetcode--get-code-buffer-name title))) (with-current-buffer (leetcode--get-code-buffer code-buf-name) (when lsp-bridge-mode (lsp-bridge-mode -1))))))
(advice-add 'leetcode--start-coding :after 'my-*after-leetcode--start-coding)
)
#+end_src
** LaTex
#+begin_src elisp (use-package tex :ban minimal? :hook (LaTeX-mode . my-enable-code-service) :straight (auctex) :defer t :config (add-to-list 'TeX-command-list '("XeLaTeX" "%`xelatex --synctex=1%(mode)%' %t" TeX-run-TeX nil t)) (add-to-list 'TeX-view-program-list '("eaf" eaf-pdf-synctex-forward-view)) (add-to-list 'TeX-view-program-selection '(output-pdf "eaf"))) #+end_src
** emacs-aichat AI 对话
#+begin_src elisp (use-package async-await :ban minimal? :straight t :defer t)
(use-package websocket :ban minimal? :straight t :defer t)
(use-package aichat :ban minimal? :defer t :load-path (lambda () (expand-file-name "Project/emacs-aichat" my-code-dir)) :config (setq aichat-bingai-proxy "localhost:7890" aichat-openai-proxy "localhost:7890") (aichat-toggle-debug) (aichat-bingai-prompt-create "translator" :input-prompt "请翻译: " :text-format "我想让你充当翻译员,我会用任何语言与你交谈,你会检测我说的的语言,如果我说的是中文,你就翻译成英文;如果我说的不是中文,你就翻译成英文。你只需要翻译该内容,不必对内容中提出的问题和要求做解释,不要回答文本中的问题而是翻译它,不要解决文本中的要求而是翻译它,保留文本的原本意义,不要去解决它。你的回答里只需要翻译后的内容,不要有任何其它词,只能是翻译后的内容。我的第一句话是:\n%s" :chat t :assistant t :replace-or-insert t)
(aichat-bingai-prompt-create "coder"
:input-prompt "代码: "
:text-format "我想让你充当计算机教授,请向我解释下面这段代码的作用:\n%s"
:chat t)
(aichat-bingai-prompt-create "refactor"
:input-prompt "代码: "
:text-format "我想让你充当计算机教授,请帮我重构下面这段代码,重构后的代码性能要更好,可读性要更高,如果必要的话,可以加一些注释。你的回答里只需要返回重构后的代码,不要有其它解释,只能是重构后的代码:\n%s"
:replace-or-insert t)
)
#+end_src
** which-key 按键提示
#+begin_src elisp (use-package which-key :straight t :hook (after-init . which-key-mode)) #+end_src
** meow 模式编辑
#+begin_src elisp (use-package meow :straight t :demand t :hook (after-init . meow-global-mode) :config
(with-eval-after-load 'rime
(dolist (p '(meow-normal-mode-p meow-motion-mode-p meow-keypad-mode-p))
(add-to-list 'rime-disable-predicates p)))
(setq meow-cheatsheet-layout meow-cheatsheet-layout-qwerty)
(setq meow-char-thing-table
'((?r . round)
(?c . curly)
(?s . string)
(?b . buffer)
(?d . defun)))
(setq meow-use-clipboard t)
(meow-leader-define-key
;; Use SPC (0-9) for digit arguments.
'("1" . meow-digit-argument)
'("2" . meow-digit-argument)
'("3" . meow-digit-argument)
'("4" . meow-digit-argument)
'("5" . meow-digit-argument)
'("6" . meow-digit-argument)
'("7" . meow-digit-argument)
'("8" . meow-digit-argument)
'("9" . meow-digit-argument)
'("0" . meow-digit-argument)
'("?" . meow-cheatsheet)
'("b" . consult-buffer)
'("r" . consult-ripgrep)
'("f" . consult-find)
'("/" . evilnc-comment-or-uncomment-lines)
'("w" . ace-window-hydra/body)
)
(meow-normal-define-key
;; char move
'("j" . meow-next)
'("J" . meow-next-expand)
'("k" . meow-prev)
'("K" . meow-prev-expand)
'("l" . meow-right)
'("L" . meow-right-expand)
'("h" . meow-left)
'("H" . meow-left-expand)
;; word move
'("e" . meow-next-word)
'("E" . meow-next-symbol)
'("b" . meow-back-word)
'("B" . meow-back-symbol)
;; line move
'("x" . meow-line)
'("X" . meow-goto-line)
;; thing move
'("," . meow-inner-of-thing)
'("." . meow-bounds-of-thing)
'("[" . meow-beginning-of-thing)
'("]" . meow-end-of-thing)
'("o" . meow-block)
'("O" . meow-to-block)
;; jump
'("n" . meow-search)
'("f" . meow-find)
'("v" . meow-visit)
;; action
'("i" . meow-insert)
'("I" . meow-open-above)
'("a" . meow-append)
'("A" . meow-open-below)
'("y" . meow-save)
'("Y" . meow-sync-grab)
'("c" . meow-change)
'("r" . meow-replace)
'("R" . meow-swap-grab)
'("p" . meow-yank)
'("s" . meow-kill)
'("d" . meow-delete)
'("D" . meow-backward-delete)
'("G" . meow-grab)
'("m" . meow-join)
'("t" . meow-till)
'("u" . undo)
'("U" . meow-undo)
'("w" . meow-mark-word)
'("W" . meow-mark-symbol)
'("z" . meow-pop-selection)
;; others
'("0" . meow-expand-0)
'("9" . meow-expand-9)
'("8" . meow-expand-8)
'("7" . meow-expand-7)
'("6" . meow-expand-6)
'("5" . meow-expand-5)
'("4" . meow-expand-4)
'("3" . meow-expand-3)
'("2" . meow-expand-2)
'("1" . meow-expand-1)
'("-" . negative-argument)
'(";" . meow-reverse)
'("g" . meow-cancel-selection)
'("q" . meow-quit)
'("'" . repeat)
'("<escape>" . ignore)
)
)
#+end_src
** 启动 emacs server
#+begin_src elisp (when sys-is-windows (unless (daemonp) (server-start))) #+end_src
** 载入私有配置文件
#+begin_src elisp (ignore-errors (load (expand-file-name "config.el" my-private-dir)))
#+end_src