GNU Emacs 配置

最后修改时间 2024-09-13 01:51:30 +0800 23805字.

在Org文件里,写好了代码块,保存文件的时候,Org mode是不会帮你自动写入文件的(未来我们可以通过配置实现),现在,我们需要通过 C-c C-v C-t or M-x org-babel-tangle 命令来触发 tangle 这个动作。

(org-babel-tangle)

xx.el 已经写好了,但并没有生效,需要重启一下Emacs,让Emacs重新加载这个配置文件,才会生效。

elisp代码执行 :

文本编辑:

1. init.el (学习)

开启学习

:PROPERTIES:
:HEADER-ARGS: :tangle init.el
:END:

1.1. 安装

本文emacs安装在windows环境中,使用 msys2 工具安装。如安装到 D:\msys64, 并设置PATH变量(D:\msys64\mingw64\bin, D:\msys64\usr\bin)

更新msys2, 如下更新命令执行2遍

pacman -Suy

添加msys2右键菜单: 创建 emacs-shell.reg注册表添加如下内容,双击执行即可

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Directory\Background\shell\MSYS2]
@="MSYS2 Bash Here"
"Icon"="d:\\msys64\\msys2.ico"

[HKEY_CLASSES_ROOT\Directory\Background\shell\MSYS2\command]
@="d:\\msys64\\msys2_shell.cmd -mingw64 -here"

1.1.1. 二进制安装

安装emacs

pacman -S mingw-w64-x86_64-emacs

1.1.2. 源码安装

下载源码,查看INSTALL文件中不同操作系统安装指引

windows nt/INSTALL.W64

参考:https://eason0210.github.io/post/install-gccemacs/

在 Mingw64 终端中执行以下命令安装依赖。

pacman -S --needed base-devel \
mingw-w64-x86_64-toolchain \
mingw-w64-x86_64-xpm-nox \
mingw-w64-x86_64-gmp \
mingw-w64-x86_64-gnutls \
mingw-w64-x86_64-libtiff \
mingw-w64-x86_64-giflib \
mingw-w64-x86_64-libpng \
mingw-w64-x86_64-libjpeg-turbo \
mingw-w64-x86_64-librsvg \
mingw-w64-x86_64-libwebp \
mingw-w64-x86_64-lcms2 \
mingw-w64-x86_64-libxml2 \
mingw-w64-x86_64-zlib \
mingw-w64-x86_64-harfbuzz \
mingw-w64-x86_64-libgccjit \
mingw-w64-x86_64-sqlite3 \
mingw-w64-x86_64-tree-sitter

下载 Emacs 源代码

git clone -b master git://git.sv.gnu.org/emacs.git
cd emacs

#如果执行./autogen.sh过程中出错,可能是因为克隆代码后 checkout 时用了 CRLF.
git config core.autocrlf false

编译 Emac

./autogen.sh  #如果提示版本不够 pacman -Su autoconf
./configure --prefix=/d/programs/emacs --without-dbus

echo $(nproc)

make -j4
make install
#make install prefix=/d/opt/emacs #指定安装目录
  1. echo $(nproc) 会显示当前系统的 CPU 核心数;然后在 make -j4 就是使用 4 个核心进行编译, 推荐使用总数的一半,既提高了编译速度,也不影响其他应用的运行。
  2. 如果需要卸载的话在源码目录里面 make uninstall 就可以了。建议安装在指定目录,注意在路径中使用斜杠“/”, 而不是反斜杠“\”。
  3. 如果编译过程出错了,记得 make clean 之后重新 configuremake

测试运行 Emacs

bin\runemacs.exe -Q

从 mingw64 shell 外部运行 Emacs 时,您需要 将 d:\msys64\mingw64\bin 添加到您的 Windows PATH,或复制所需的 DLL 添加到 Emacs 的 bin/ 目录中。 否则,TLS 等功能会导致 取决于那些DLL将丢失

d:\programs\emacs\bin 加入到环境变量。 如果和msys2中的emacs同存,将path中的源码安装的路径放在msys2的emacs前面。

创建快捷方式

请右键选择新建快捷方式,选择bin\runemacs.exe, 并起个名。 右键快捷方式 属性 ,目标处添加命令行选项 --debug-init

1.2. 改键

模块化配置中添加了键绑定(init-basic.el 一些默认的配置)

  • M(eta):统一对应键盘按键(PC/Mac):left windows/option
  • s(uper):统一对应键盘按键(PC/Mac):left Alt/command
  • C(trl):统一对应键盘按键(PC/Mac): Caps Lock

Mac 只需要改大写键,Windows 对齐mac的布局

MacOS
在 MacOS 中, 大家可以在 system -> keyboard -> function al keys 中调整所有功能键的键位.

1.Windows SharpKeys 改键

来把 Left Window 键改成 Left Alt 键,Left Alt 键改成 A pp 键,大小写键改成 Ctrl 键。

官方下载:https://github.com/randyrants/sharpkeys,安装后 win 菜单搜 索 sharpkey,修改完成后写入注册表重新开机生效。

原键 目标键 说明
Caps Lock(00_3A) Left Ctrl(01_1D) 大小写键改成Ctrl键
Left Alt(00_38) Application(E0_5D) 左Alt键改成App键
Left Windows(E0_5B) Left Alt(00_38) 左视窗键改成左Alt键
Right Alt(E0_38) Left Windows(E0_5B) 右Alt键改成左视窗键

2.配置

Emacs 默认 Alt 键就是 Meta,而 App 键我们改成 super 键,可以通过下面 的 Elisp 代码来完成。

window 配置

(setq w32-apps-modifier 'super)       ; 通过SharpKeys改成了 Application

mac 配置

;;  Opt -> meta : M, Cmd -> super : s
(setq mac-option-modifier 'meta
      mac-command-modifier 'super)

3.定义常用操作

现在我们可以把 Mac 下面的复制、粘贴、剪切、全选等命令移植到 Emacs 中 来了,并且这一套按键是跨平台的。

(global-set-key (kbd "s-a") 'mark-whole-buffer) ;;对应Windows上面的Ctrl-a 全选
(global-set-key (kbd "s-c") 'kill-ring-save) ;;对应Windows上面的Ctrl-c 复制
(global-set-key (kbd "s-s") 'save-buffer) ;; 对应Windows上面的Ctrl-s 保存
(global-set-key (kbd "s-v") 'yank) ;对应Windows上面的Ctrl-v 粘贴
(global-set-key (kbd "s-z") 'undo) ;对应Windows上面的Ctrol-z 撤销

1.3. 常用配置

1.光标移动

光标的移动是编辑器中最常用的操作所以必须熟知。

  • C-f 为前移一个字符, f 代表 forward。
  • C-b 为后移一个字符, b 代表 backward。
  • C-p 为上移至前一行, p 代表 previous。
  • C-n 为上移至下一行, n 代表 next。
  • C-a 为移至行首, a 代表 ahead。
  • C-e 为移至行尾, e 代表 end。

同样适用于 Shell 终端里面。

2.内置功能

Emacs 功能强大,但是部分功能默认情况下并未开启。

如编辑器内显示行号可使用 M-x linum-mode 来开启。

配置文件

(global-display-line-numbers-mode 1) ;显示行号 29版本。29之前用(global-linum-mode 1)
(setq inhibit-startup-screen t) ; 尝试关掉启动界面

3.帮助

Emacs 是一个富文档编辑器(Self document, extensible editor)而下面的三种方法在学 习 Emacs 的过程中也非常重要。他们分别是,

C-h k 寻找快捷键的帮助信息 C-h v 寻找变量的帮助信息 C-h f 寻找函数的帮助信息

4.windows 右键菜单添加 Open With Emacs 功能

创建注册表文件 emacs-context.reg

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\*\shell]
[HKEY_CLASSES_ROOT\*\shell\openwemacs]
@="&Edit with Emacs"
[HKEY_CLASSES_ROOT\*\shell\openwemacs\command]
@="D:\\msys64\\mingw64\\bin\\emacsclientw.exe -n \"%1\""
[HKEY_CLASSES_ROOT\Directory\shell\openwemacs]
@="Edit &with Emacs"
[HKEY_CLASSES_ROOT\Directory\shell\openwemacs\command]
@="D:\\msys64\\mingw64\\bin\\emacsclientw.exe -n \"%1\""

使用这个 OpenWithEmacs 的功能,Emacs 需要开启 Server Mode,代码如下:

(server-mode 1)

双击注册文件。这时右键打开文件就可以选择emacs打开了。

5.补全

在我的配置中添加内置补全(init-completion.el 内置补全)

;; 内置补全功能n
;;(setq tab-always-indent 'complete)           ;; 使用 TAB 来列出当前补全选项
;;(icomplete-mode t)                         ;; 开启的,在按 M-x 时就有可选的选项了。使用 vertico-mode,关闭此项

6.基础Elisp

Emacs Lisp 是世界上第二古老高级编程语言 Lisp 专为 Emacs 打造的方言。官方提供了 Emacs Lisp 的入门介绍和参考手册

;; 2 + 2
(+ 2 2)

;; 2 + 3 * 4
(+ 2 (* 3 4))

;; 定义变量
(setq name "username")
(message name) ; -> "username"

;; 定义函数
(defun func ()
  (message "Hello, %s" name))

;; 执行函数
(func) ; C-x C-e -> Hello, username

;; 设置快捷键
(global-set-key (kbd "<f1>") 'func)

;; 使函数可直接被调用可添加 (interactive)
(defun func ()
  (interactive)
  (message "Hello, %s" name))

定义f4快速打开配置文件

;; 快速打开配置文件
(defun open-init-file()
  (interactive)
  (find-file "~/.emacs.d/init.el"))

;; 这一行代码,将函数 open-init-file 绑定到 <f4> 键上
(global-set-key (kbd "<f4>") 'open-init-file)

7.外观配置

我的模块化配置(init-ui.el 内置配置)

;;(toggle-frame-maximized)                   ; 全屏编辑器
(tool-bar-mode -1)                           ; 关闭工具栏,tool-bar-mode 即为一个 Minor Mode
(scroll-bar-mode -1)                         ; 关闭文件滑动控件
;; (menu-bar-mode -1)                        ; 关闭菜单栏 (不关闭,使用插件时会用到)

(setq-default cursor-type 'bar)              ; 更改光标的样式,默认比较粗. 更多C-h v 查询帮助
;;(setq cursor-type 'bar)                    ; 更改光标的样式。setq当前buffer生效,不能全局生效

;;(set-face-attribute 'default nil :height 150) ; 修改字号,大小为16pt
;;让鼠标滚动更好用。默认滚动很快
(setq mouse-wheel-scroll-amount '(3 ((shift) . 1) ((control) . nil)))
(setq mouse-wheel-progressive-speed nil)

(global-hl-line-mode t)                      ;; 高亮当前行
(setq make-backup-files nil)                 ; 关闭文件自动备份。如果是有git来管理文件,备份文件没有太大意义。~ 为后缀的文件为自动生成的备份文件
(setq auto-save-default nil)   ; 关闭自动保存文件,#为后缀的文件
(fset 'yes-or-no-p 'y-or-n-p)                ;; 某个命令时需要输入 (yes or no) 

1.4. 关于 lexical binding

;; 默认没有激活。在文件最开头添加文件作用域的变量设置,设置变量的绑定方式。设置在当前文件作用域内是激活的。
;; -*- lexical-binding: t -*-
(let ((x 1))    ; x is lexically bound.
  (+ x 3))
     ⇒ 4       ; 1 + 3 的值为 4

(defun getx ()
  x)            ; x is used free in this function.

(let ((x 1))    ; x is lexically bound. getx 是用不到 let 定义的 x 的
  (getx))
;;error→ Symbol's value as variable is void: x

关于lexical binding更多的细节,可以自行阅读 Emacs 的官方文档。程序代码使用 lexical binding 会更利于并发,编辑器会执行一些优化让代码执行的更快。很多插件要求安装 lexical binding。

范例:init.el

;;; init.el --- Load the full configuration -*- lexical-binding: t -*-
;;; Commentary:

;; This file bootstraps the configuration, which is divided into
;; a number of other files.

;;; Code:

(+ 1 2)

;;; init.el ends here

三个分号开头的注释表示“节”,两个分号开头的注释表示“段落”。(一个分号开头的是一行代码后面的行内注释)。

1.5. 插件源

(require 'package)
(setq package-check-signature nil 
      load-prefer-newer t) ;; 个别时候会出现签名校验失败
(setq package-archives
          '(("melpa"  . "https://melpa.org/packages/")
            ("gnu"    . "https://elpa.gnu.org/packages/")
            ("nongnu" . "https://elpa.nongnu.org/nongnu/")))

(unless (bound-and-true-p package--initialized)
  (package-initialize)) ;; 刷新软件源索引

;;防止反复调用 package-refresh-contents 会影响加载速度
(when (not package-archive-contents)
  (package-refresh-contents))

;;modeline上显示我的所有的按键和执行的命令
(package-install 'keycast) ;包会安装在elpa目录中
(keycast-mode-line-mode t)

1.6. 安装补全插件

Company插件

我的emacs模块配置(init-completion.el 补全 company)

他是一个用于代码补全的插件

;; 开启全局 Company 补全
(package-install 'company)
(global-company-mode 1)

;; company mode 默认选择上一条和下一条候选项命令 M-n M-p
(define-key company-active-map (kbd "C-n") 'company-select-next)
(define-key company-active-map (kbd "C-p") 'company-select-previous)

增强 minibuffer 补全:vertico 和 Orderless

(package-install 'vertico)
(vertico-mode t)

(package-install 'orderless)
(setq completion-styles '(orderless))

有了 vertico-mode,把之前的 icomplete-mode 关掉。 补全变为垂直,垂直对于我们是更友好的,所有 minibuffer 的行为的可以补全。如使用 M-x 补全变为垂直, C-x C-f 增强显示目录下文件、 C-x b 变为垂直。

orderless 支持 M-x 模糊搜索,可以无序的。 如 edebug-defun 函数搜索, M-x fun debug

配置 Marginalia 增强 minubuffer 的 annotation

我的emacs模块配置(init-completion.el minubuffer增强注释 marginalia)

(package-install 'marginalia)
(marginalia-mode t)

M-x 可以看到它会把命令的注释直接显示在minibuffer中

C-h v 查看一个变量的内容,可以发现不用再按回车看到里面的值。直接显示在minibuffer中

C-h f 键位绑定都已经在minibuffer中显示出来了

C-x C-f 显示文件大小权限等, C-x b 显 buffer 大小状态, C-h f 显示函数快捷键等

minibuffer action 和自适应的 context menu:Embark

我的emacs模块配置(init-completion.el minibuffer增强 embark)

(package-install 'embark)
(global-set-key (kbd "C-;") 'embark-act)
(setq prefix-help-command 'embark-prefix-help-command)

使用

光标在函数位置,按 C-;

  • RET 查看光标处函数定义文件位置,并打开
  • c 修改变量的值
    • 如 C-h v 变量名,按 C-; c 修改变量的值

优点:不需要记快捷键了

  • 以前查看函数的绑定键
    • C-h b 查看函数快捷键绑定:
    • 以前按 C-x C-h 时,会告诉你 C-x 绑定了哪些快捷键
  • 设置了 (setq prefix-help-command #'embark-prefix-help-command)
    • C-x C-h 模糊输入函数名可看到绑定的快捷键,按回车可直接执行命令,就不需要再记快捷键了

增强文件内搜索和跳转函数定义:Consult

我的emacs模块配置(init-completion.el 增强文件内搜索 consult)

(package-install 'consult)
;;replace swiper 可以替代老的 ivy mode 的 swiper 功能
(global-set-key (kbd "C-s") 'consult-line)
(global-set-key (kbd "M-s i") 'consult-imenu)  ;跳转函数定义

使用 C-s 搜索的内容就会在minibuffer中垂直显示,使用C-p 或 C-n来上下选择

使用 M-s i 跳转函数定义或者org-mode标题

这些组合可以秒杀ivy helm这种一统的插件。

括号

(electric-pair-mode t)                       ; 括号补全
(add-hook 'prog-mode-hook #'show-paren-mode) ;; 编程模式下,光标在括号上时高亮另一个括号

1.7. 手工安装插件

awesome-tab 为例

创建目录,使用内容Dir包。 C-x d 回车, + 创建目录输入目录名site-lisp 回车, q 退出

使用 M-x eshell 克隆代码

cd site-lisp
git clone --depth=1 https://github.com/manateelazycat/awesome-tab.git

配置文件

(add-to-list 'load-path (expand-file-name "~/.emacs.d/site-lisp/awesome-tab/"))
(require 'awesome-tab)
(awesome-tab-mode t)

;;(use-package awesome-tab
;;  :ensure nil
;;  :quelpa (awesome-tab :fetcher github :repo "manateelazycat/awesome-tab" :upgrade nil)
;;  :init (awesome-tab-mode t)
;;  )

(defun awesome-tab-buffer-groups ()
"`awesome-tab-buffer-groups' control buffers' group rules.
Group awesome-tab with mode if buffer is derived from `eshell-mode' `emacs-lisp-mode' `dired-mode' `org-mode' `magit-mode'.
All buffer name start with * will group to \"Emacs\".
Other buffer group by `awesome-tab-get-group-name' with project name."
(list
(cond
    ((or (string-equal "*" (substring (buffer-name) 0 1))
        (memq major-mode '(magit-process-mode
                            magit-status-mode
                            magit-diff-mode
                            magit-log-mode
                            magit-file-mode
                            magit-blob-mode
                            magit-blame-mode)))
    "Emacs")
    ((derived-mode-p 'eshell-mode)
    "EShell")
    ((derived-mode-p 'dired-mode)
    "Dired")
    ((memq major-mode '(org-mode org-agenda-mode diary-mode))
    "OrgMode")
    ((derived-mode-p 'eaf-mode)
    "EAF")
    (t
    (awesome-tab-get-group-name (current-buffer))))))

尽量用内置的包。 tab-bar

1.8. 其他小配置

最近打开文件

最近打开过文件的选项让我们更快捷的在图形界面的菜单中打开最近 编辑过的文件。

(require 'recentf)
(recentf-mode 1)
(setq recentf-max-menu-item 10)

;; 这个快捷键绑定可以用之后的插件 counsel 代替
;; (global-set-key (kbd "C-x C-r") 'recentf-open-files)

使用下面的配置文件将删除功能配置成与其他图形界面的编辑器相同,即当你选中一段文字 之后输入一个字符会替换掉你选中部分的文字。

(delete-selection-mode t)                    ;; 选中文本后输入文本会替换文本(更符合我们习惯了的其它编辑器的逻辑。默认选中放后面

下面的这些函数可以让你找到不同函数,变量以及快捷键所定义的文件位置。 因为非常常用 所以我们建议将其设置为与查找文档类似的快捷键(如下所示),

可以把它们绑定到配置中

;; 查询函数、变量、key 定义的文件位置
(global-set-key (kbd "C-h C-f") 'find-function)
(global-set-key (kbd "C-h C-v") 'find-variable)
(global-set-key (kbd "C-h C-k") 'find-function-on-key)

1.9. Emacs作为超级前端

可以非常快的找到文件或目录

1.9.1. 使用 Emacs 来打开文件管理器

我的emacs模块配置(j-embark.el 库)

windows 用户

(shell-command-to-string "explorer.exe C:\\")

;;(shell-command-to-string "explorer.exe ~/.emacs.d") ; windows 不识别~

(shell-command-to-string
 (encode-coding-string
  (replace-regexp-in-string "/" "\\\\\\\\"
                (format "explorer.exe %s" (expand-file-name "~/.emacs.d")))
  'gbk))
(defun consult-directory-externally (file)
  "Open FILE externally using the default application of the system."
  (interactive "fOpen externally: ")
  (if (and (eq system-type 'windows-nt)
           (fboundp 'w32-shell-execute))
      (shell-command-to-string (encode-coding-string (replace-regexp-in-string "/" "\\\\\\\\"
            (format "explorer.exe %s" (file-name-directory (expand-file-name file)))) 'gbk))
    (call-process (pcase system-type
                    ('darwin "open")
                    ('cygwin "cygstart")
                    (_ "xdg-open"))
                  nil 0 nil
                  (file-name-directory (expand-file-name file)))))

(require 'embark)
(define-key embark-file-map (kbd "E") #'consult-directory-externally)


;;打开当前文件的目录
(defun my-open-current-directory ()
  (interactive)
  (consult-directory-externally default-directory))

快速打开文件所在目录:

  • M-x consult-directory-externally 输入文件路径,如 ~/.emacs.d/auto-save-list/.saves-2000-JASPER~
  • C-x C-f 选择文件时,可以用embark来打开, C; 选择E,用文件管理器打开对应目录的

1.9.2. 增强 embark 和 consult,批量搜索替换大杀器

我的emacs模块配置(init-completion.el 增强 embark 和 consult,批量搜索替换大杀)

性能: grep < ack < ag < ripgrep(rg)

使用ripgrep来进行搜索, M-x consult-ripqgrep 搜索hello 会出现报错找不到rg命令。

Error running timer: (file-missing "Searching for program" "No such file or directory" "rg")

下载ripgrep: https://github.com/BurntSushi/ripgrep msys2 https://packages.msys2.org/base/mingw-w64-ripgrep

安装方式:

  • PC msys2 pacman -S mingw-w64-x86_64-ripgrep
  • PC github release 下载windows包,解压并加入到PARH变量中
  • Arch Linux sudo pacman -S ripgrep
  • macOS brew install ripgrep

再使用ripgrep来进行搜索, M-x consult-ripgrep 搜索hello ,所有当前目录下包含hello字符串的信息都在minibuffer中展示出来了。 使用 C-nC-p 来预览文件或进入文件里。

下面介绍批量修改插件

;; 批量修改
(package-install 'embark-consult)
(package-install 'wgrep)
(setq wgrep-auto-save-buffer t)

(eval-after-load 'consult
  '(eval-after-load 'embark
     '(progn
        (require 'embark-consult)
        (add-hook 'embark-collect-mode-hook #'consult-preview-at-point-mode)))) ;hook 第一个参数执行前,先执行第2个参数的功能

(define-key minibuffer-local-map (kbd "C-c C-e") 'embark-export-write)

(defun embark-export-write ()
  "Export the current vertico results to a writable buffer if possible.
Supports exporting consult-grep to wgrep, file to wdeired, and consult-location to occur-edit"
  (interactive)
  (require 'embark)
  (require 'wgrep)
  (pcase-let ((`(,type . ,candidates)
               (run-hook-with-args-until-success 'embark-candidate-collectors)))
    (pcase type
      ('consult-grep (let ((embark-after-export-hook #'wgrep-change-to-wgrep-mode))
                       (embark-export)))
      ('file (let ((embark-after-export-hook #'wdired-change-to-wdired-mode))
               (embark-export)))
      ('consult-location (let ((embark-after-export-hook #'occur-edit-mode))
                           (embark-export)))
      (x (user-error "embark category %S doesn't support writable export" x)))))

批量替换操作

  • M-x consult-ripgrep 默认会在以 git 为根目录搜索。如 #hello ,搜索包含hello字符的文件。 C-n/C-p 下上搜索同时可以预览。
  • C-c C-e 打开写
  • M-x query-replace-regexp 输入hello 回车, 替换为hello 回车。 按 y 同意当前行替换,n 不同意修改
  • C-c C-c 执行替换,按 q 退出

当前buffer替换操作

  • C-s 搜索, 输入hello
  • C-c C-e 编辑
  • M-x query-replace-regexp 输入要替换的内容
  • C-c C-c 执行替换,按 q 退出

配置搜索中文文件

everything 工具可以快速搜索本地文件。下载地址: https://www.voidtools.com/zh-cn/downloads/

安装好后,同时下载 ES.exe 客户端工具加入环境变量。

;;everyting
;;consult-locate
;; 配置搜索中文文件
(progn
  (setq consult-locate-args (encode-coding-string "es.exe -i -p -r" 'gbk))
  (add-to-list 'process-coding-system-alist '("es" gbk . gbk))
  )
(eval-after-load 'consult
  (progn
      (setq
        consult-narrow-key "<"
        consult-line-numbers-widen t
        consult-async-min-input 2         ;; 搜索 2 个字符显示输出。默认 3
        consult-async-refresh-delay  0.15
        consult-async-input-throttle 0.2
        consult-async-input-debounce 0.1)
    ))

操作:

  • 前提:PC 始终打开everything, 转为 ES 会调用everthing
  • M-x consult-locate 回车,输入要搜索的文件如 #init.el 。 可以看到minibuffer中显示的和everthing界面搜索的是一样的。
  • 再输入个#号就可以使用 orderless 的功能进行过滤。如只想看29.4中init.el文件 #init.el#29.4 选中回车即可打开对应文件

使用外部程序打开:

  • M-x consult-locate 回车,输入要搜索的文件. 如搜索projeckt中的txt文件 #\.txt#project
  • 使用embark C; 按x使用外部程序打开txt文件

中文搜索操作:

  • M-x consult-locate 回车,输入要搜索的文件. 如 #学习

1.9.3. 使用拼音进行搜索

我的emacs模块配置(init-completion.el 增强文件内搜索 consult)

;; 使用拼音进行搜索
(package-install 'pyim)

(defun eh-orderless-regexp (orig_func component)
  (let ((result (funcall orig_func component)))
    (pyim-cregexp-build result)))


(defun toggle-chinese-search ()
  (interactive)
  (if (not (advice-member-p #'eh-orderless-regexp 'orderless-regexp))
      (advice-add 'orderless-regexp :around #'eh-orderless-regexp)
    (advice-remove 'orderless-regexp #'eh-orderless-regexp)))

(defun disable-py-search (&optional args)
  (if (advice-member-p #'eh-orderless-regexp 'orderless-regexp)
      (advice-remove 'orderless-regexp #'eh-orderless-regexp)))

;; (advice-add 'exit-minibuffer :after #'disable-py-search)
(add-hook 'minibuffer-exit-hook 'disable-py-search) ;退出minibuffer时自动退出拼音搜索

(global-set-key (kbd "s-p") 'toggle-chinese-search) ;需要时打开拼音搜索。因为拼音搜索性能不稳定

中文拼音操作:

  • 前提:PC 始终打开everything, 转为ES会调用everthing
  • M-x consult-locate 回车,输入要搜索的文件. 如打包含“学习”的txt文件 #\.txt#s-p 输入 #\.txt#xx 可以看到过滤学习的txt文件

1.9.4. 更换主题

customize theme 内置主题

自带的主题颜色,可以使用 M-x customize themes 图形化来选择,或者使用命令 load-theme 主题 。如 M-x load-theme light-blue

  • leuven,我最喜欢的亮色主题之一了。其实它也有暗色主题,只是没有内置。
  • modus,应该是从Emacs 28开始内置的一款包含亮/暗色的主题。非常好,我很喜欢。暗色有的时候,我感觉眼睛有点累。
(load-theme 'tango-dark)

1.10. 模块化配置

1.10.1. 使用多文件存储配置文件

创建 custom.el 文件

一些自动生成的配置会自动写到这里。默认是写到init.el文件结尾的。

(setq custom-file (expand-file-name "~/custom.el")) ;没有则自动创建
(load custom-file 'no-error 'no-message)

拆分init.el文件

#----------
├── custom.el
├── early-init.el              #启动前初始化
├── init.el                    #主配置
├── lisp
│   ├── init-package.el        #插件源
│   ├── init-basic.el          #默认的配置
│   ├── init-ui.el             #视觉相关
│   ├── init-completion.el     #补全
│   ├── init-tools.el
│   ├── init-org.el
│   ├── init-keybindings.el
│   ├── init-funcs.el          #自定义函数
│   ├── init-c.el

加载配置

(add-to-list 'load-path
    (expand-file-name (concat user-emacs-directory "lisp")))

各个文件通过 provide 暴露对外调用的名称。如:

(provide 'init-basic)

然后在 init.el 文件中通过 `require` 调用:

require 'init-basic

1.10.2. 使用Org-mode 管理 Emacs 配置

官方文档:https://orgmode.org/manual/Extracting-Source-Code.html

Org-mode 下的文学编程将颠覆你对于 Emacs 的看法。因为我们也可以使用 Org 来管理 Emacs 的配置文件。

我们可以让我们的配置文件更加清晰有序,并且可以添加很多注释之外的资料、链接等,让我们的配置更加易读和可管理。

  • 创建org文件。如 ~/.emacs.d/emacs-config.org
  • 代码块 它能够自动的将代码块里的代码,写入到指定的文件里去,不指定默认在当前目录生成与文件同名.el文件。

    而且它的配置方式也非常灵活:

    • 代码块配置tangle
    • 标题行配置tangle
  • 执行 M-x org-babel-tangle 命令来触发 tangle 这个动作。

范例-代码块配置tangle

#+BEGIN_SRC emacs-lisp :tangle ~/.emacs.d/test.el
  (+ 1 2)
#+END_SRC

执行 M-x org-babel-tangle ,代码 (+ 1 2) 将写入到 ~/.emacs.d/test.el 这个文件里。

范例-标题行配置tangle

* early-init.el
:PROPERTIES:
:HEADER-ARGS: :tangle early-init.el
:END

支持的参数

  • 代码不写到文件 :tangle no
  • 创建目录, :mkdir yes

    C-c C-v C-d org-babel-demarcate-block 代码划分为2块

添加代码块

  • 旧版使用快捷键 < + 快捷码 + TAB
    • 如代码块 <s + Tab 可以直接插入代码块的代码片段(Snippet)
  • 从 orgmode 9.2 版本后, org-insert-structure-template 变为 C-c C-, 给出列表选择

还想使用旧版快捷键

我的emacs模块配置(init-org.el 内置配置)

(with-eval-after-load 'org
  (require 'org-tempo))

;; 禁用左尖括号
(setq electric-pair-inhibit-predicate
      `(lambda (c)
         (if (char-equal c ?\<) t (,electric-pair-inhibit-predicate c))))

(add-hook 'org-mode-hook
          (lambda ()
            (setq-local electric-pair-inhibit-predicate
                        `(lambda (c)
                           (if (char-equal c ?\<) t (,electric-pair-inhibit-predicate c))))))
(setq org-src-preserve-indentation t)             ; 编辑源代码时保留原有的缩进,t代表启用该功能。默认nil不保留. 
(setq org-src-tab-acts-natively t)                ; 源代码编辑模式下,Tab键的行为与原生代码编辑器一致,t代表启用该功能,  默认t
(setq org-edit-src-content-indentation 0)          ; 代码块里缩进,0不缩进,默认是2 (重要)

1.11. 模块列表

1

2.一些好用的包

3.org

4

5.evil

前期vim用户快速切emacs用户

6.精选 packages, 大大提升你的 Emacs 编辑效率

7.使用 Treesit + eglot 来打造现代编程 IDE

8.窗口

9.工作区间管理

10.org 进阶

11.Org Roam

  • corfu 来进行补全
  • Org Oram
  • Org roam UI

12.Emacs 配置问题排查

13.优化性能,借鉴其他人的配置

1.12. 清理学习内容

(write-region "" nil "~/.emacs.d/init.el")

2. Emacs初始化 early-init.el

这是 Emacs 在启动时读取的第一个文件。它应该包含不依赖于任何包或 Emacs 帧比例的代码。初始化文件是为了在 Emacs 生成初始化之前设置一些基本的东西

2.1. early-init.el 对启动时间和垃圾回收的调整

(setq gc-cons-threshold (* 50 1000 1000))

2.2. early-init.el 初始化包缓存

这里使用标准的 package.el 来管理我的Emacs包。 (init.el 配置软件包)

3. Emacs主配置文件 (init.el)

;;; init.el --- The main init entry for Emacs -*- lexical-binding: t -*-
;;; Commentary:

;; This file bootstraps the configuration, which is divided into
;; a number of other files.

;;; Code:


3.1. init.el 加载前自定义配置

3.1.1. init.el 判断操作系统

;;; 判断操作系统, 定义一个新变量
(setq *is-a-mac* (eq system-type 'darwin))
(setq *win64* (eq system-type 'windows-nt))
(setq *cygwin* (eq system-type 'cygwin) )
(setq *linux* (or (eq system-type 'gnu/linux) (eq system-type 'linux)) )
(setq *unix* (or *linux* (eq system-type 'usg-unix-v) (eq system-type 'berkeley-unix)) )

3.2. init.el 基础配置禁用备份和锁定文件

默认情况下,Emacs 会尝试锁定文件,以免它们被其他程序修改。它还保留备份。这些是我不需要的功能

;;; 禁用备份和锁定文件
(setq make-backup-files nil)
(setq backup-inhibited nil) ; Not sure if needed, given `make-backup-files'
(setq create-lockfiles nil)

3.3. init.el 指定自定义文件 custom.el

默认情况下,Emacs 将持久自定义写入用户的 init 文件末尾。当用户 M-x customize 或与之相关时,Emacs 都会写这些内容。指定自定义文件位置与init.el区分开。

;;; 指定自己义配置
(setq custom-file (make-temp-file "emacs-custom-")) ;没有则自动创建emacs-custom-开头随机文件
(load custom-file 'no-error 'no-message)

3.4. init.el 始终从 *scratch* 缓冲区开始

;;; 始终从 *scratch* 缓冲区开始
(setq initial-buffer-choice t)

3.5. init.el 加载目录

加载目录下所有配置

list 目录
各种插件配置
person 目录
个人的配置

init.el 配置中调用插件(init.el 加载各模块)

;;; 加载目录
(mapc
 (lambda (string)
   (add-to-list 'load-path (locate-user-emacs-file string)))
 '("lisp" "j-lisp"))

3.6. init.el 软件包 (package.el)

package.el 内置于 Emacs 中。我们不需要显式加载它,在需要时会被 use-package 调用。 early-init.el 处激活缓存(early-init.el 初始化包缓存)

;;; 软件包
(setq package-vc-register-as-project nil) ; Emacs 30

;; 设置插件源优先级
(require 'package)
(setq package-check-signature nil 
      load-prefer-newer t) ;; 个别时候会出现签名校验失败
(setq package-archives
      '(("gnu-elpa" . "https://elpa.gnu.org/packages/")
        ("gnu-elpa-devel" . "https://elpa.gnu.org/devel/")
        ("nongnu" . "https://elpa.nongnu.org/nongnu/")
        ("melpa" . "https://melpa.org/packages/")))

;; 最大的数字优先(未提及的优先级为 0)
(setq package-archive-priorities
      '(("gnu-elpa" . 3)
        ("melpa" . 2)
        ("nongnu" . 1)))

(unless (bound-and-true-p package--initialized)
  (package-initialize)) ;; 刷新软件源索引

;;防止反复调用 package-refresh-contents 会影响加载速度
(when (not package-archive-contents)
  (package-refresh-contents))

强制指定某些软件的源

(defvar j-emacs-my-packages
  '(vertico tmr)
  "List of symbols representing the packages I develop/maintain.")

;; 指定某些软件使用特点源安装 
(setq package-pinned-packages
      `(,@(mapcar
           (lambda (package)
             (cons package "gnu-elpa-devel"))
           j-emacs-my-packages)))

3.6.1. init.el use-pacakge 插件

29版本后内置包

;; make use-package default behavior better
;; with `use-package-always-ensure' you won't need ":ensure t" all the time
;; with `use-package-always-defer' you won't need ":defer t" all the time
;; (setq use-package-always-ensure t           ; 自动安装
;;      use-package-always-defer t            ; 软件包延迟加载 
;;      use-package-enable-imenu-support t
;;      use-package-expand-minimally t)

使用样例

;;; xxx包
(use-package xxx
  :ensure nil                          ; 确认安装,nil表示不需要安装。nil 可配置内置包。
  :pin melpa                           ; 指向插件源
  :defer nil                           ; 是否要延迟加载
  :demand nil                          ; 是否立即执行,t 即忽略延迟加载
  :diminish (flymake " Flym.")         ; 状态栏显示的样式
  :after yasnippet                     ; 别的包安装后启动. 多个包用括号括起来(abc ddd)
  :custom                              ; 相当于(setq <key> <value>)
  (ivy-use-virtual-buffers t)
  :hook                                 ; hook模式的绑定
  (prog-mode . flycheck-mode)
  :commands (isearch-moccur isearch-all)    
  :init                                ; 在加载插件前执行一些命令
  (setq smooth-scrolling-margin 2)
  :config                              ; 在加载插件后执行一些命令
  (smooth-scrolling-mode t)
  :bind                                 ; 快捷键的绑定,全局
  (("<f2>" . xx) ("M-p" . xx2))
  :bind                                 ; 指定映射绑定, bind可以写多个 
  ( :map global-map
    ("<insert>" . nil)
    ("<menu>" . nil)
    ("C-z" . nil) ; I have a window manager, thanks!
    ("C-x C-z" . nil) ; same idea as above
    ("M-`" . nil)
    ("M-SPC" . cycle-spacing)
    ("M-=" . count-words)
    :map prog-mode-map          
    ("c" . rainbow-mode)
    ))

3.7. init.el 加载各个模块

我把配置拆分为几个模块(init.el 加载目录)

各个模块都记录在下面我的 Emacs 模块配置

;; 加载模块
(load (locate-user-emacs-file "jasper-emacs-pre-custom.el") :no-error :no-message)

;; Packages
(require 'init-packages)
(use-package benchmark-init
  :ensure t
  :demand t
  :config
  ;; To disable collection of benchmark data after init is done.
  (add-hook 'after-init-hook 'benchmark-init/deactivate))

;; Core
(require 'init-ui)
(require 'init-essentials)
(require 'init-basic)
(require 'init-modeline)
(require 'init-completion)
(require 'init-evil)
(require 'init-tools)
;; uis
(require 'init-window)
;; Tools
(require 'init-org)
;; Frameworks
(require 'init-persp)
;; Languages
(require 'init-programming)
;; personal
(require 'init-keybindings)
(require 'init-funcs)

(load (locate-user-emacs-file "jasper-emacs-post-custom.el") :no-error :no-message)

3.8. init.el 计算启动时间

(defun efs/display-startup-time ()
  (message "Emacs loaded in %s with %d garbage collections."
           (format "%.2f seconds"
                   (float-time
                   (time-subtract after-init-time before-init-time)))
           gcs-done))

(add-hook 'emacs-startup-hook #'efs/display-startup-time)


(setq gc-cons-threshold (* 2 1000 1000))

4. 我的Emacs模块配置(lisp/)

init.el 部分中,通过 load-path 让 Emacs 可以运行他们的代码(init.el 加载目录)。本章的副标题定义了模块,每个模块都加载在我的 init.el 末尾调用(init.el 加载各模块)

4.1. init-packages.el

;;; config

4.1.1. init-packages.el 安装 quelpa 插件

在学习章节提到过(精选 packages, 大大提升你的 Emacs 编辑效率)

emacs30版本提供vc内置包,拉取非elpa源软件包

;;(use-package quelpa :ensure t)
(use-package quelpa
  :ensure t
  :commands quelpa
  :config
  :custom
  (quelpa-git-clone-depth 1)
  (quelpa-update-melpa-p nil)
  (quelpa-self-upgrade-p nil)
  (quelpa-checkout-melpa-p nil))
(use-package quelpa-use-package
  :ensure t
  :init
  (setq quelpa-use-package-inhibit-loading-quelpa t)
  :demand t)

(unless (package-installed-p 'quelpa-use-package)
  (quelpa
   '(quelpa-use-package
     :fetcher git
     :url "https://github.com/quelpa/quelpa-use-package.git")))

4.1.2. ini-package.el vc package-vc.el

https://github.com/slotThe/vc-use-package

emacs30版本后vc内置。可以直接使用。

;;; 添加vc宏方便安装非elpa源包
(require 'cl-lib)
(require 'use-package-core)

(cl-defun slot/vc-install (&key (fetcher "github") repo name rev backend)
  (let* ((url (format "https://www.%s.com/%s" fetcher repo))
         (iname (when name (intern name)))
         (package-name (or iname (intern (file-name-base repo)))))
    (unless (package-installed-p package-name)
      (package-vc-install url iname rev backend))))

(defvar package-vc-use-package-keyword :vc)

(defun package-vc-use-package-set-keyword ()
  (unless (member package-vc-use-package-keyword use-package-keywords)
    (setq use-package-keywords
          (let* ((pos (cl-position :unless use-package-keywords))
                 (head (cl-subseq use-package-keywords 0 (+ 1 pos)))
                 (tail (nthcdr (+ 1 pos) use-package-keywords)))
            (append head (list package-vc-use-package-keyword) tail)))))

(defun use-package-normalize/:vc (name-symbol keyword args)
  (let ((arg (car args)))
    (pcase arg
      ((or `nil `t) (list name-symbol))
      ((pred symbolp) args)
      ((pred listp) (cond
                     ((listp (car arg)) arg)
                     ((string-match "^:" (symbol-name (car arg))) (cons name-symbol arg))
                     ((symbolp (car arg)) args)))
      (_ nil))))

(defun use-package-handler/:vc (name-symbol keyword args rest state)
  (let ((body (use-package-process-keywords name-symbol rest state)))
    ;; This happens at macro expansion time, not when the expanded code is
    ;; compiled or evaluated.
    (if args
        (use-package-concat
         `((unless (package-installed-p ',(pcase (car args)
                                            ((pred symbolp) (car args))
                                            ((pred listp) (car (car args)))))
             (apply #'slot/vc-install ',(cdr args))))
         body)
      body)))

(defun package-vc-use-package-override-:ensure (func name-symbol keyword ensure rest state)
  (let ((ensure (if (plist-member rest :vc)
                    nil
                  ensure)))
    (funcall func name-symbol keyword ensure rest state)))

(defun package-vc-use-package-activate-advice ()
  (advice-add
   'use-package-handler/:ensure
   :around
   #'package-vc-use-package-override-:ensure))

(defun package-vc-use-package-deactivate-advice ()
  (advice-remove
   'use-package-handler/:ensure
   #'package-vc-use-package-override-:ensure))

;; register keyword on require
(package-vc-use-package-set-keyword)

样例:

(use-package org-roam-ui
  :ensure nil
  :vc (:fetcher "github" :repo "org-roam/org-roam-ui"))
(message "Load init-packages done...")
(provide 'init-packages)

4.2. init-ui.el

4.2.1. init-ui.el 内置配置

在学习章节提到过(外观配置)

(setq inhibit-startup-screen t) ; 尝试关掉启动界面
(setq-default cursor-type 'bar)              ; 更改光标的样式,默认比较粗. 更多C-h v 查询帮助
;;(setq cursor-type 'bar)                    ;; 更改光标的样式。setq当前buffer生效,不能全局生效

(global-hl-line-mode t)                      ;; 高亮当前行

(global-display-line-numbers-mode 1) ;显示行号 29版本。29之前用(global-linum-mode 1)
(setq frame-title-format
      `((buffer-file-name "%f" "%b")
        ,(format " - GNU Emacs %s" emacs-version)))


;;(toggle-frame-maximized)                     ; 全屏编辑器
(setq  initial-frame-alist (quote ((fullscreen . maximized))))


;;(set-face-attribute 'default nil :height 150) ; 修改字号,大小为16pt
;;让鼠标滚动更好用。默认滚动很快
(setq mouse-wheel-scroll-amount '(3 ((shift) . 1) ((control) . nil)))
(setq mouse-wheel-progressive-speed nil)

(tool-bar-mode -1)                           ;; 关闭工具栏,tool-bar-mode 即为一个 Minor Mode
(scroll-bar-mode -1)                         ;; 关闭文件滑动控件
;; (menu-bar-mode -1)                        ;; 关闭菜单栏 (不关闭,使用插件时会用到)


;;主题
;;(load-theme 'tango-dark) ;这里使用doom-theme


4.2.2. init-ui.el 主题 doom-emacs

在我的学习文章提到过(模块列表)

;; 这里的执行顺序非常重要,doom-modeline-mode 的激活时机一定要在设置global-mode-string 之后‘
(use-package doom-themes
  :ensure t)
(load-theme 'doom-one 1)

(use-package doom-modeline
  :ensure t
  :custom-face
  (mode-line ((t (:height 0.9))))
  (mode-line-inactive ((t (:height 0.9))))
  :init
  (doom-modeline-mode t))

(use-package nerd-icons
  :ensure t)

现在更推荐用 rainstormstudio/nerd-icons.el (github.com) 。直接下载一个nerd fonts就可以 (M-x nerd-icons-install-fonts)或者手动下载fonts目录中的 NFM.ttf 。GUI和terminal都可以显示icon,大小也更统一。

;; 这里的执行顺序非常重要,doom-modeline-mode 的激活时机一定要在设置global-mode-string 之后‘
(use-package doom-themes
  :ensure t
  :init
  (if (display-graphic-p)
      (load-theme 'doom-solarized-light t) ; t表示自动确认运行代码
    (load-theme 'doom-tomorrow-night t)))

;; (set-face-attribute 'default nil :height 150) ; 修改字号,大小为16pt
(set-face-attribute 'default nil :height 130) ; 修改字号,大小为16pt

(use-package doom-modeline
  :ensure t
  :init
  (setq doom-modeline-minor-modes t)
  :custom-face
  (mode-line ((t (:height 0.95))))
  (mode-line-inactive ((t (:height 0.95))))
  :hook (after-init . doom-modeline-mode))
(message "Load init-ui done...")
(provide 'init-ui)

4.3. init-essentials.el

;;;; 常规设置和常用自定义函数(j-simple.el)

4.3.1. init-essentials.el 自定义命令 j-simple.el

自定义命令( j-simple.el)

(use-package j-simple
  :ensure nil
  :demand t
  :bind
  ( ("ESC ESC" . j-simple-keyboard-quit-dwim)
    ("C-g" . j-simple-keyboard-quit-dwim)
    ("C-M-SPC" . j-simple-mark-sexp)   ; 选中区域
    ))
(message "Load init-essentials done...")
(provide 'init-essentials)

4.4. init-basic.el

4.4.1. init-basic.el 一些默认的配置

这里涉及到改键(改键)

;;; 一些默认的配置
;;;
(setq w32-apps-modifier 'super)       ;PC 通过SharpKeys改成了 Application

(global-set-key (kbd "s-a") 'mark-whole-buffer) ;;对应Windows上面的Ctrl-a 全选
(global-set-key (kbd "s-c") 'kill-ring-save) ;;对应Windows上面的Ctrl-c 复制
(global-set-key (kbd "s-s") 'save-buffer) ;; 对应Windows上面的Ctrl-s 保存
(global-set-key (kbd "s-v") 'yank) ;对应Windows上面的Ctrl-v 粘贴
(global-set-key (kbd "s-z") 'undo) ;对应Windows上面的Ctrol-z 撤销

;; 查询函数、变量、key 定义的文件位置
(global-set-key (kbd "C-h C-f") 'find-function)
(global-set-key (kbd "C-h C-v") 'find-variable)
(global-set-key (kbd "C-h C-k") 'find-function-on-key)

;; 设置系统的编码,避免各处的乱码
;; UTF-8 as the default coding system
(when (fboundp 'set-charset-priority)
  (set-charset-priority 'unicode))
(prefer-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(setq default-buffer-file-coding-system 'utf-8)

;; emoji
;; (set-fontset-font "fontset-default"
;;                   'unicode
;;                   (font-spec :family "Segoe UI Emoji" :size 12))

;;; emoji表情符号
;; Noto Emoji Font 是google开源项目https://fonts.google.com/noto/specimen/Noto+Emoji
;; Segoe UI Emoji 字体 https://www.freefontdownload.org/ch/segoe-ui-emoji.font
;; 下载所有表情符号
;; MacOS
;;(set-fontset-font t 'symbol (font-spec :family "Apple Color Emoji") nil 'prepend)
;; Windows
(when (eq system-type 'windows-nt)
  (set-frame-font "Segoe UI Emoji-12" nil t))

;; 开启服务
(server-mode 1)

(electric-pair-mode t)                       ; 括号补全
(add-hook 'prog-mode-hook #'show-paren-mode) ; 编程模式下,光标在括号上时高亮另一个括号
(delete-selection-mode t)                    ; 选中文本后输入文本会替换文本(更符合我们习惯了的其它编辑器的逻辑。默认选中放后面

(global-auto-revert-mode t)                  ; 自动加载外部修改过的文件
(setq auto-save-default nil)                 ; 关闭自动保存文件,#为后缀的文件
(setq ring-bell-function 'ignore)            ; 关闭提示声音
(fset 'yes-or-no-p 'y-or-n-p)                ;; 某个命令时需要输入 (yes or no)

4.4.2. init-basic.el 最近编辑过的文件 recentf

;;; 最近编辑过的文件
(use-package recentf
  :ensure nil
  :bind (("C-x C-r" . recentf-open-files))
  :hook (after-init . recentf-mode)
  :init (setq recentf-max-saved-items 300
              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:" "/persp-confs/"
                (lambda (file) (file-in-directory-p file package-user-dir))))
  :config
  (push (expand-file-name recentf-save-file) recentf-exclude)
  (add-to-list 'recentf-filename-handlers #'abbreviate-file-name))

4.4.3. init-basic.el 历史命令 savehist

在我的学习文章提到过(模块列表)

savehist-mode 默认开启,重启 emacs 会记住使用过的命令,同时会保存到 ~/.emacs.d/history 文件中。 history 文件还保存了 minibuffer 的变量、查找过的文件等。相当于记住了上次的工作状态。

save-place-mode 开启后,会记住光标所在文件的位置,下次打开文件时自动光标指向这个位置

;;; Save and restore editor sessions between restarts
;; 记住使用过的命令
(use-package savehist
  :ensure nil
  :hook (after-init . savehist-mode)
  :init (setq enable-recursive-minibuffers t ; Allow commands in minibuffers
              history-length 1000
              savehist-additional-variables '(mark-ring
                                              global-mark-ring
                                              search-ring
                                              regexp-search-ring
                                              extended-command-history)
              savehist-autosave-interval 300)
  )

;; 记住光标所在文件的位置
(use-package saveplace
  :ensure nil
  :hook (after-init . save-place-mode)) ;在emacs配置完全加载好以后,执行save-place-mode

测试:

  • 记住光标的位置,重启emacs
  • C-x C-f 重新打开该文件,可以看到光标是上次退出的位置。

4.4.4. init-basic.el 内置包 simple

在我的学习文章提到过(模块列表)

modeline上显示文件大小、列号

;;; modeline上显示文件大小、 列号
(use-package simple
  :ensure nil
  :hook (after-init . size-indication-mode)
  :init
  (progn
    (setq column-number-mode t)
    ))

4.4.5. init-basic.el 键位绑定 general

在自定义evil键位绑定用到(init.evil.el 自定义快捷键)

;;; 自定义类似evil中的leader键
(use-package general
  :ensure t
  :init
  ;; 针对evil插件。设置了一些类似leader键的函数. 如 leader 为空格对应函数global-definer , leader 为逗号对应函数global-leader
  (with-eval-after-load 'evil
    (general-add-hook 'after-init-hook
                      (lambda (&rest _)
                        (when-let ((messages-buffer (get-buffer "*Messages*")))
                          (with-current-buffer messages-buffer
                            (evil-normalize-keymaps))))
                      nil
                      nil
                      t))


  (general-create-definer global-definer
    :keymaps 'override
    :states '(insert emacs normal hybrid motion visual operator)
    :prefix "SPC"
    :non-normal-prefix "C-SPC")

  ;; 定义一个宏。这个宏的作用是继承global-dfiner函数的leader键SPC空格,接收一个inix-key变量的值为子leader
  (defmacro +general-global-menu! (name infix-key &rest body)
    "Create a definer named +general-global-NAME wrapping global-definer.
Create prefix map: +general-global-NAME. Prefix bindings in BODY with INFIX-KEY."
    (declare (indent 2))
    `(progn
       (general-create-definer ,(intern (concat "+general-global-" name))
         :wrapping global-definer
         :prefix-map ',(intern (concat "+general-global-" name "-map"))
         :infix ,infix-key
         :wk-full-keys nil
         "" '(:ignore t :which-key ,name))
       (,(intern (concat "+general-global-" name))
        ,@body)))

  (general-create-definer global-leader
    :keymaps 'override
    :states '(emacs normal hybrid motion visual operator)
    :prefix ","
    "" '(:ignore t :which-key (lambda (arg) `(,(cadr (split-string (car arg) " ")) . ,(replace-regexp-in-string "-mode$" "" (symbol-name major-mode)))))))

键位绑定示例:

(use-package general
  :init
  (general-emacs-define-key 'global [remap imenu] 'consult-imenu)
  (general-emacs-define-key 'global [remap apropos] 'consult-apropos)
  (global-definer
    "!" 'shell-command)

  (+general-global-menu! "search" "s"
    "p" 'consult-ripgrep)

  (+general-global-menu! "buffer" "b"
    "d" 'kill-current-buffer)

python-mode

(use-package python
  :ensure t
  :hook (inferior-python-mode . (lambda ()
                                  (process-query-on-exit-flag
                                   (get-process "Python"))))
  :init
  ;; Disable readline based native completion
  (setq python-shell-completion-native-enable nil)
  :config
  (global-leader
    :major-modes
    '(python-mode t)
    ;;and the keymaps:
    :keymaps
    '(python-mode-map)
    "e" 'live-py-set-version)
  (setq python-shell-interpreter "python3")

  ;; Live Coding in Python
  (use-package live-py-mode))

rust-mode

(global-leader
  :major-modes
  '(rust-mode t)
  ;;and the keymaps:
  :keymaps
  '(rust-mode-map)
  "=" 'rust-format-buffer
  "c" 'rust-compile
  "r" 'rust-run
  "t" 'rust-test)

org-mode

;; mode specific major key
(global-leader
  :major-modes
  '(org-mode t)
  ;;and the keymaps:
  :keymaps
  '(org-mode-map)
  "p" 'org-pomodoro
  "t" 'org-todo
  "e" 'org-set-effort)

(global-leader
  :major-modes
  '(org-agenda-mode t)
  ;;and the keymaps:
  :keymaps
  '(org-agenda-mode-map)
  "d" 'org-agenda-day-view
  "w" 'org-agenda-week-view
  "," 'org-agenda-priority
  "e" 'org-agenda-set-effort
  ":" 'org-agenda-set-tags
  "T" 'org-agenda-show-tags
  "p" 'org-pomodoro)
(message "Load init-basic done...")
(provide 'init-basic)

4.4.6. init-basic.el 输入法

4.4.6.1. init-basic.el 输入法 pyim

在学习章节提到过(使用拼音进行搜索)

;;; 输入法 (pyim)
(use-package pyim
  :ensure t)

4.5. init-modeline.el

;;; config

4.5.1. init-modeline.el 按键显示 keycast

在我的学习文章提到过(模块列表)

modeline上显示我的所有的按键和执行的命令

;;; modeline上显示我的所有的按键和执行的命令
(use-package keycast
  :ensure t
  :init (keycast-mode-line-mode 1)) ; 在标题显示

在使用doom-modeline上面配置会报错。这里用下面的配置

(use-package keycast
  :ensure t
  :config
  (define-minor-mode keycast-mode
    "Show current command and its key binding in the mode line (fix for use with doom-mode-line)."
    :global t
    (if keycast-mode
        (add-hook 'pre-command-hook 'keycast--update t)
      (remove-hook 'pre-command-hook 'keycast--update)))
  (add-to-list 'global-mode-string '("" mode-line-keycast)))

我的配置如下

(use-package keycast
  :ensure t
  :commands (+toggle-keycast)
  :config
  (defun +toggle-keycast()
    (interactive)
    (if (member '("" keycast-mode-line " ") global-mode-string)
        (progn (setq global-mode-string (delete '("" keycast-mode-line " ") global-mode-string))
               (remove-hook 'pre-command-hook 'keycast--update)
               (message "Keycast OFF"))
      (add-to-list 'global-mode-string '("" keycast-mode-line " "))
      (add-hook 'pre-command-hook 'keycast--update t)
      (message "Keycast ON"))))
  ;; 这里的执行顺序非常重要,doom-modeline-mode 的激活时机一定要在设置global-mode-string 之后
(message "Load init-modeline done...")
(provide 'init-modeline)

4.6. init-completion.el

4.6.1. init-completion.el 内置补全

在学习章节提到过(补全)

;; 内置补全功能n
;;(setq tab-always-indent 'complete)           ;; 使用 TAB 来列出当前补全选项

4.6.2. init-completion.el 补全 company

在学习章节提到过(Company插件)

他是一个用于代码补全的插件

;;; company 补全。 替换方案 corfu 更轻量
(use-package company
  :ensure t
  :init
  (global-company-mode t)                    ;; 全局开启 company 补全
  :config
  (setq company-idle-delay 0)                ;; 补全时间快些
  (setq company-minimum-prefix-length 1)     ;; 最少输入1个字符开启
  (setq company-show-numbers t)              ;; 给选项编号 (按快捷键 M-1、M-2 等等来进行选择)
  (setq company-dabbrev-other-buffers 'all)  ;; 从所有缓冲区收集补全信息
  (setq company-tooltip-align-annotations t) ;; 右侧附加注释
  (setq company-selection-wrap-around t)
  (setq company-transformers '(company-sort-by-occurrence)) ; 根据选择的频率进行排序,如果不喜欢可以去掉
  :bind (:map company-active-map
              ("C-n" . 'company-select-next)
              ("C-p" . 'company-select-previous)))  ;; 使用 `C-n` 与 `C-p` 来选择补全项,默认选择上一条和下一条候选项命令 M-n M-p

4.6.3. init-completion.el 补全 corfu

安装 corfu 来进行补全

之前的company软件包可以去掉了,这个更轻量

;;; corfu 轻量补全  替代company
(use-package corfu
  :ensure t
  :init
  (progn
    (setq corfu-auto t)
    (setq corfu-cycle t)
    (setq corfu-quit-at-boundary t)
    (setq corfu-quit-no-match t)
    (setq corfu-preview-current nil)
    (setq corfu-min-width 80)
    (setq corfu-max-width 100)
    (setq corfu-auto-delay 0.2)
    (setq corfu-auto-prefix 1)
    (setq corfu-on-exact-match nil)
    (global-corfu-mode)
    ))

4.6.4. init-completion.el minibuffer增强 orderless

在学习章节提到过(增强 minibuffer 补全:vertico 和 Orderless)

orderless 支持 M-x 模糊搜索,可以无序的。 如 edebug-defun 函数搜索, M-x fun debug

;;; minibuffer增强,模糊搜索 (orderless)
(use-package orderless
  :ensure t
  :after minibuffer
  :custom
  (completion-styles '(orderless basic))
  (completion-category-overrides '((file (styles basic partial-completion))))
  (orderless-component-separator #'orderless-escapable-split-on-space))

4.6.5. init-completion.el 增强文件内搜索 consult

在学习章节提到过(增强文件内搜索和跳转函数定义:Consult)

性能: grep < ack < ag < ripgrep(rg)

使用ripgrep来进行搜索, M-x consult-ripqgrep 搜索hello 会出现报错找不到rg命令。

Error running timer: (file-missing "Searching for program" "No such file or directory" "rg")

下载ripgrep: https://github.com/BurntSushi/ripgrep msys2 https://packages.msys2.org/base/mingw-w64-ripgrep

安装方式:

  • PC msys2 pacman -S mingw-w64-x86_64-ripgrep
  • PC github release 下载windows包,解压并加入到PARH变量中
  • Arch Linux sudo pacman -S ripgrep
  • macOS brew install ripgrep

再使用ripgrep来进行搜索, M-x consult-ripgrep 搜索hello ,所有当前目录下包含hello字符串的信息都在minibuffer中展示出来了。 使用 C-nC-p 来预览文件或进入文件里。

;;; 增强文件内搜索和跳转函数定义(consult.el)
(use-package consult
  :ensure t
  ;;replace swiper 可以替代老的 ivy mode 的 swiper 功能
  :bind (
         ("C-s" . consult-line) ;; replace swipe
         ("M-s i" . consult-imenu)  ;跳转函数定义
         ;;("C-x b" .  consult-buffer) ;; 多显示最近打开文件。替换默认 C-x b buffer
         ;;("C-c p s" . consult-ripgrep) ;;  查找文件内容,需要安装 ripgrep 命令
         )
  )

使用 C-s 搜索的内容就会在minibuffer中垂直显示,使用C-p 或 C-n来上下选择

使用 M-s i 跳转函数定义或者org-mode标题

配置搜索中文文件

everythin工具可以快速搜索本地文件。下载地址: https://www.voidtools.com/zh-cn/downloads/

安装好后,同时下载 ES.exe 客户端工具加入环境变量。

;;; 配置搜索中文文件
;; PC提前安装 everyting 及其客户端ES. 利用M-x consult-locate搜索
(progn
  (setq consult-locate-args (encode-coding-string "es.exe -i -p -r" 'gbk))
  (add-to-list 'process-coding-system-alist '("es" gbk . gbk))
  )
(eval-after-load 'consult
  (progn
      (setq
        consult-narrow-key "<"
        consult-line-numbers-widen t
        consult-async-min-input 2         ;; 搜索 2 个字符显示输出。默认 3
        consult-async-refresh-delay  0.15
        consult-async-input-throttle 0.2
        consult-async-input-debounce 0.1)
      ))

操作:

  • 前提:PC 始终打开everything, 转为ES会调用everthing
  • M-x consult-locate 回车,输入要搜索的文件如 #init.el 。 可以看到minibuffer中显示的和everthing界面搜索的是一样的。
  • 再输入个#号就可以使用 orderless 的功能进行过滤。如只想看29.4中init.el文件 #init.el#29.4 选中回车即可打开对应文件

使用外部程序打开:

  • M-x consult-locate 回车,输入要搜索的文件. 如搜索projeckt中的txt文件 #\.txt#project
  • 使用embark C; 按x使用外部程序打开txt文件

中文搜索操作:

  • M-x consult-locate 回车,输入要搜索的文件. 如 #学习

使用拼音进行搜索

在学习章节提到过(使用拼音进行搜索)

中文拼音操作:

  • 前提:PC 始终打开everything, 转为ES会调用everthing
  • M-x consult-locate 回车,输入要搜索的文件. 如打包含“学习”的txt文件 #\.txt#s-p 输入 #\.txt#xx 可以看到过滤学习的txt文件

修复consult-ripgrep搜索不了问题

windows上有时,consult-ripgrep搜索不了

;; make consult-ripgrep work 有时consult-ripgrep搜索不了
(cond
 ;; macOS
 ((eq system-type 'darwin)
  "afplay")
 ;; Windows
 ((eq system-type 'windows-nt)
  (add-to-list 'process-coding-system-alist 
               '("[rR][gG]" . (utf-8-dos . windows-1251-dos)))
  )
 (t
  "Nothing"))

完整配置, 增加了中文搜索(j-consult.el 库)

;;; 增强文件内搜索和跳转函数定义(consult.el and j-consult.el)
(use-package consult
  :ensure t
  :hook (completion-list-mode . consult-preview-at-point-mode)
  ;;replace swiper 可以替代老的 ivy mode 的 swiper 功能
  :bind
  ( :map global-map
    ("M-g M-g" . consult-goto-line)
    ("C-s" . consult-line) ;; replace swipe
    ("M-s i" . consult-imenu)  ;跳转函数定义
    ;;("C-x b" .  consult-buffer) ;; 多显示最近打开文件。替换默认 C-x b buffer
    ;;("C-c p s" . consult-ripgrep) ;;  查找文件内容,需要安装 ripgrep 命令
    )
  :config
  (setq
   consult-narrow-key "<"
   consult-line-numbers-widen t
   consult-async-min-input 2         ;; 搜索 2 个字符显示输出。默认 3
   consult-async-refresh-delay  0.15
   consult-async-input-throttle 0.2
   consult-async-input-debounce 0.1
   consult-line-start-from-top t)
  )

(use-package j-consult
  :ensure nil
  ;;:after (consult orderless)
  ;;:demand t
  :config

  (setq
   consult-narrow-key "<"
   consult-line-numbers-widen t
   consult-async-min-input 5         ;; 搜索 2 个字符显示输出。默认 3
   consult-async-refresh-delay  0.15
   consult-async-input-throttle 0.2
   consult-async-input-debounce 0.1
   consult-line-start-from-top t)

  ;; 使用拼音进行搜索文件 (pyim)
  (require 'pyim)

  (defun eh-orderless-regexp (orig_func component)
    (let ((result (funcall orig_func component)))
      (pyim-cregexp-build result)))

  (defun toggle-chinese-search ()
    (interactive)
    (if (not (advice-member-p #'eh-orderless-regexp 'orderless-regexp))
        (advice-add 'orderless-regexp :around #'eh-orderless-regexp)
      (advice-remove 'orderless-regexp #'eh-orderless-regexp)))

  (defun disable-py-search (&optional args)
    (if (advice-member-p #'eh-orderless-regexp 'orderless-regexp)
        (advice-remove 'orderless-regexp #'eh-orderless-regexp)))

  ;; (advice-add 'exit-minibuffer :after #'disable-py-search)
  (add-hook 'minibuffer-exit-hook 'disable-py-search) ;退出minibuffer时自动退出拼音搜索

  (global-set-key (kbd "s-p") 'toggle-chinese-search) ;需要时打开拼音搜索。因为拼音搜索性能不稳定
  )

4.6.6. init-completion.el minibuffer增强 embark

在学习章节提到过(minibuffer action 和自适应的 context menu:Embark)

;;; minibuffer操作扩展 (embark.el and j-embark.el)
(use-package embark
  :ensure t
  :defer 1
  :init
  (setq which-key-use-C-h-commands nil
        ;; press C-h after a prefix key, it shows all the possible key bindings and let you choose what you want
        prefix-help-command #'embark-prefix-help-command)
  :bind
  ( :map minibuffer-local-map
    ("C-;" . embark-act)         ;; pick some comfortable binding
   )) ;; alternative for `describe-bindings'

使用

光标在函数位置,按 C-;

  • RET 查看光标处函数定义文件位置,并打开
  • c 修改变量的值
    • 如 C-h v 变量名,按 C-; c 修改变量的值

优点:不需要记快捷键了

  • 以前查看函数的绑定键
    • C-h b 查看函数快捷键绑定:
    • 以前按 C-x C-h 时,会告诉你 C-x 绑定了哪些快捷键
  • 设置了 (setq prefix-help-command #'embark-prefix-help-command)
    • C-x C-h 模糊输入函数名可看到绑定的快捷键,按回车可直接执行命令,就不需要再记快捷键了

这些组合可以秒杀ivy helm这种一统的插件。

4.6.6.1. init-completion.el 增强 embarkconsult ,批量搜索替换大杀器

在学习章节提到过(增强 embark 和 consult,批量搜索替换大杀器)

下面介绍批量修改插件

(j-embark.el 库)

;;; 批量修改文件内容
(use-package embark-consult
  :ensure t
  :after (embark consult)
  :demand
  :config
  (add-hook 'embark-collect-mode-hook #'consult-preview-at-point-mode) ;hook 第一个参数执行前,先执行第2个参数的功能
  )

(use-package wgrep
  :ensure t
  :commands wgrep-change-to-wgrep-mode
  :config (setq wgrep-auto-save-buffer t))

(use-package j-embark
  :ensure nil
  :after embark
  :bind
  ( :map minibuffer-local-map
    ("C-c C-e" . my/embark-export-write) ; 打开写,配合query-replace-regexp实现文本替换
    :map embark-file-map ; 打开pc文件管理器
    ("E" . my/consult-directory-externally)
    ))

批量替换操作

  • M-x consult-ripgrep 默认会在以 git 为根目录搜索。如 #hello ,搜索包含hello字符的文件。 C-n/C-p 下上搜索同时可以预览。
  • C-c C-e 打开写
  • M-x query-replace-regexp 输入hello 回车, 替换为hello 回车。 按 y 同意当前行替换,n 不同意修改
  • C-c C-c 执行替换,按 q 退出

当前buffer替换操作

  • C-s 搜索, 输入hello
  • C-c C-e 编辑
  • M-x query-replace-regexp 输入要替换的内容
  • C-c C-c 执行替换,按 q 退出

快速打开文件所在目录:

  • M-x my/consult-directory-externally 输入文件路径,如 ~/.emacs.d/auto-save-list/.saves-2000-JASPER~
  • C-x C-f 选择文件时,可以用embark来打开, C; 选择E,用文件管理器打开对应目录的

4.6.7. init-completion.el minubuffer增强注释 marginalia

在学习章节提到过(配置 Marginalia 增强 minubuffer 的 annotation)

;;; Minubuffer 详细的注释信息 (marginalia.el)
(use-package marginalia
  :ensure t
  :defer 1
  :config
  (setq marginalia-max-relative-age 0) ; absolute time
  (marginalia-mode 1))

M-x 可以看到它会把命令的注释直接显示在minibuffer中

C-h v 查看一个变量的内容,可以发现不用再按回车看到里面的值。直接显示在minibuffer中

C-h f 键位绑定都已经在minibuffer中显示出来了

C-x C-f 显示文件大小权限等, C-x b 显 buffer 大小状态, C-h f 显示函数快捷键等

4.6.8. init-completion.el minibuffer增强 vertico

在学习章节提到过(增强 minibuffer 补全:vertico 和 Orderless)

有了 vertico-mode,把之前的 icomplete-mode 关掉。 补全变为垂直,垂直对于我们是更友好的,所有 minibuffer 的行为的可以补全。如使用 M-x 补全变为垂直, C-x C-f 增强显示目录下文件、 C-x b 变为垂直。

orderless 支持 M-x 模糊搜索,可以无序的。 如 edebug-defun 函数搜索, M-x fun debug

我更喜欢用下下面的配置:

;;; Minibuffer 垂直补全布局(vertico)
(use-package vertico
  :ensure t
  :hook (after-init . vertico-mode)
  :config
  (setq vertico-resize nil
        vertico-count 17
        vertico-cycle t)
  ;; Cleans up path when moving directories with shadowed paths syntax, e.g.
  ;; cleans ~/foo/bar/// to /, and ~/foo/bar/~/ to ~/.
  ;; 适合vim用户习惯,上下移动 C-k, C-j 
  (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy)
  (add-hook 'minibuffer-setup-hook #'vertico-repeat-save)
  (define-key vertico-map (kbd "C-j") 'vertico-next)
  (define-key vertico-map (kbd "C-'") 'vertico-quick-jump)
  (define-key vertico-map (kbd "C-k") 'vertico-previous)
  (define-key vertico-map [backspace] #'vertico-directory-delete-char)
  (define-key vertico-map (kbd "s-SPC") #'+vertico/embark-preview)
  )
(message "Load init-completion done...")
(provide 'init-completion)

4.7. init-evil.el

在学习章节提到过(evil)

4.7.1. init-evil.el 安装

;;; evil 
(use-package evil
  :ensure t
  :init
  (setq evil-want-keybinding nil) ;不使用自带的键位绑定。默认加载不同模式下键位绑定
  (setq evil-want-C-u-scroll t) ;C-u 实现向上滚动。默认C-u 是emacs中的功能
  (evil-mode)

  ;; https://emacs.stackexchange.com/questions/46371/how-can-i-get-ret-to-follow-org-mode-links-when-using-evil-mode
  (with-eval-after-load 'evil-maps
    (define-key evil-motion-state-map (kbd "RET") nil)) ;如果有链接,按回车能访问这个链接
  )

安装undo-tree

;;; evil 使用undo-tree来管理undo redo
(use-package undo-tree
  :ensure t
  :diminish
  :init
  (global-undo-tree-mode 1)
  (setq undo-tree-auto-save-history nil)
  (evil-set-undo-system 'undo-tree))

Emacs中使用vim和外部使用vim的区别

vim 中有normal和insert模式,在emacs中是各种state, 如 noarmal state, insert state

Normal State (N)
这是 Evil 默认的“静止状态”,其中定义了 vi 绑定的主体。 M-x evil-normal-state
Insert State (I)
这是插入文本的状态,其中未修改的键将在缓冲区中插入相应的字符。
Emacs State (E)
一种尽可能接近默认 Emacs 行为的状态,通过 C-z 从emacs和 vi 相互切换。
Visual State (V)
用于选择文本区域的状态。
Motion State (M) 运动状态 (M)
一种特殊状态,适用于只读缓冲区,其中运动可用,但编辑操作不可用。只读的文档一般都是Motion state 如帮助文档。 M-x evil-motion-state
Replace State (R) 替换状态 (R)
一种特殊状态,与插入状态非常相似,只不过它替换文本而不是插入。 按住大写的 R , 一直替换状态

Evil 基础用法

增删改查 (text-obj, commands, replace)

  • text-obj 更好地操作文本
    • 选中单词 viw
    • 删除单词 diw
  • 跳到r字符的位置: fr
  • commands
    • : 进行命令行模式。 :%s/commands/111/ 可以替换, 按 u 回退

evil-guide: https://github.com/noctuid/evil-guide.git

上述文档花2小时认真读就可以完全从vim用户切到emacs上使用了。

interactive replace 交互式替换文本

在学习章节提到过(精选 packages, 大大提升你的 Emacs 编辑效率)

交互式替换文本. 在evil模式中,选中的单词填写在 :%s/<word>//

;;;###autoload
(defun my/evil-quick-replace (beg end )
  (interactive "r")
  (when (evil-visual-state-p)
    (evil-exit-visual-state)
    (let ((selection (regexp-quote (buffer-substring-no-properties beg end))))
      (setq command-string (format "%%s /%s//g" selection))
      (minibuffer-with-setup-hook
          (lambda () (backward-char 2))
        (evil-ex command-string)))))

(define-key evil-visual-state-map (kbd "C-r") 'my/evil-quick-replace)

交互式替换文本操作:

  • SPC v 或者选中要替换的字符串,按 C-r 替换

比较全的evil配置

;;; evil 
(use-package evil
  :ensure t
  :init
  (setq evil-want-keybinding nil) ;不使用自带的键位绑定。默认加载不同模式下键位绑定
  (setq evil-want-C-u-scroll t) ;C-u 实现向上滚动。默认C-u 是emacs中的功能
  (evil-mode)

  ;; https://emacs.stackexchange.com/questions/46371/how-can-i-get-ret-to-follow-org-mode-links-when-using-evil-mode
  (with-eval-after-load 'evil-maps
    (define-key evil-motion-state-map (kbd "RET") nil)) ;如果有链接,按回车能访问这个链接

  ;;; 自定义设置
  ;; 在进行插入模式后,希望使用emacs的快捷键,而又不希望进入 Emacs State
  (setcdr evil-insert-state-map nil)
  (define-key evil-insert-state-map [escape] 'evil-normal-state) ;按 [Esc] 回到normal state. 下面会介绍更方便的evil-escape插件

  ;; 加空行, 默认 o + Esc 需要2次操作. 下面的设置直接在normal 模式中操作
  (define-key evil-normal-state-map (kbd "[ SPC") (lambda () (interactive) (evil-insert-newline-above) (forward-line))) ; 向上加空行
  (define-key evil-normal-state-map (kbd "] SPC") (lambda () (interactive) (evil-insert-newline-below) (forward-line -1))) ; 向上加空行

  ;; 在normal state中切换不同的buffer
  (define-key evil-normal-state-map (kbd "[ b") 'previous-buffer) ;切到前一个buffer
  (define-key evil-normal-state-map (kbd "] b") 'next-buffer)
  (define-key evil-motion-state-map (kbd "[ b") 'previous-buffer)
  (define-key evil-motion-state-map (kbd "] b") 'next-buffer)

  ;; 在dired模式下,添加快捷键
  (evil-define-key 'normal dired-mode-map ; 只修改dired模式下normal模式的快捷键绑定
    (kbd "<RET>") 'dired-find-alternate-file
    (kbd "C-k") 'dired-up-directory ;往上一级目录
    "`" 'dired-open-term
    "q" 'quit-window ; q 退出。默认没有。 可以查看emacs state下的q绑定的函数 =C-x d= 进入dired, =C-z= 进入emacs state, =C-h k= 按 q 可查到对应的函数为 quit-window
    "o" 'dired-find-file-other-window ; 在另外一个窗口打开文件
    "z" 'dired-get-size
    ")" 'dired-omit-mode)
  )


(use-package j-evil
  :ensure nil
  :bind
  ( :map evil-visual-state-map 
  ("C-r" . my/evil-quick-replace) ;交互式替换文本 选中文本C-r, :%s/<words>//
  ))

;;; evil 使用undo-tree来管理undo redo
(use-package undo-tree
  :ensure t
  :diminish
  :init
  (global-undo-tree-mode 1)
  (setq undo-tree-auto-save-history nil)
  (evil-set-undo-system 'undo-tree))

4.7.2. init.evil.el 自定义快捷键

定义不同 state 下面的快捷键

;;; 自定义设置
;; 在进行插入模式后,希望使用emacs的快捷键,而又不希望进入 Emacs State
(setcdr evil-insert-state-map nil)
(define-key evil-insert-state-map [escape] 'evil-normal-state) ;按 [Esc] 回到normal state

;; 加空行, 默认 o + Esc 需要2次操作. 下面的设置直接在normal 模式中操作
(define-key evil-normal-state-map (kbd "[ SPC") (lambda () (interactive) (evil-insert-newline-above) (forward-line))) ; [ + 空格,向上加空行
(define-key evil-normal-state-map (kbd "] SPC") (lambda () (interactive) (evil-insert-newline-below) (forward-line -1))) ; [ + 空格,向上加空行

;; 在normal state中切换不同的buffer
(define-key evil-normal-state-map (kbd "[ b") 'previous-buffer) ; [ + b 切到前一个buffer
(define-key evil-normal-state-map (kbd "] b") 'next-buffer)
(define-key evil-motion-state-map (kbd "[ b") 'previous-buffer)
(define-key evil-motion-state-map (kbd "] b") 'next-buffer)

;; 在dired模式下,添加快捷键
(evil-define-key 'normal dired-mode-map ; 只修改dired模式下normal模式的快捷键绑定
  (kbd "<RET>") 'dired-find-alternate-file
  (kbd "C-k") 'dired-up-directory ;往上一级目录
  "`" 'dired-open-term
  "q" 'quit-window ; q 退出。默认没有。 可以查看emacs state下的q绑定的函数 =C-x d= 进入dired, =C-z= 进入emacs state, =C-h k= 按 q 可查到对应的函数为 quit-window
  "o" 'dired-find-file-other-window ; 在另外一个窗口打开文件
  "z" 'dired-get-size
  ")" 'dired-omit-mode)

spacemacs like 快捷键如何定义

general插件中global-definer在init-basic.el中定义(init-basic.el 键位绑定 general)

(use-package general
  :init
  ;; global-definer 对应leader键为空格
  (global-definer
    "!" 'shell-command ;空格+! SPC ! 进入shell命令行
    "SPC" 'execute-extended-command ; 按2下空格SPC-SPC就可以实现 M-x 的效果
    "'" 'vertico-repeat
    "+" 'text-scale-increase
    "-" 'text-scale-decrease
    "u" 'universal-argument ;SPC u 相当于emacs的C-u 
    "hdf" 'describe-function ;SPC hdf 相当于emacs的C-h f
    "hdv" 'describe-variable ;SPC hdv 相当于emacs的C-h v
    "hdk" 'describe-key ;SPC hdk 相当于emacs的C-h k
    ;; 查看init-funcs.el 自定义函数
    "hh" 'my/highlight-dwim ;SPC hh 高亮选中区域并标上颜色
    "hc" 'my/clearn-highlight ;SPC hc 清除高亮颜色
    "v" 'er/expand-region ;SPC v 扩大区域
    )
  ;; leader 键为SPC空格,子leader键为b. 如 SPC b b查看buffer缓冲区
  (+general-global-menu! "buffer" "b"
    "d" 'kill-current-buffer
    "b" '(consult-buffer :which-key "consult buffer") ; 查看buffer缓冲区
    "B" 'switch-to-buffer
    "p" 'previous-buffer
    "R" 'rename-buffer
    "M" '((lambda () (interactive) (switch-to-buffer "*Messages*")) 
          :which-key "messages-buffer") ; SPC b M查看*Message*缓冲区
    "n" 'next-buffer
    "i" 'ibuffer
    "f" 'my-open-current-directory
    "k" 'kill-buffer

    "y" 'copy-buffer-name
    "K" 'kill-other-buffers)
  )

我的emacs模块配置(init-keybindings.el 键位绑定 general)

参考配置: https://github.com/zilongshanren/emacs.d/blob/eglot/lisp/init-keybindings.el 实现自己的文件、窗口、项目多种快捷键绑定

4.7.3. 相关插件列表

4.7.3.1. evil-escape插件回退到normal state

任何状态下按 kj 就能回到normal state。很实用,相当于把esc键集中在右手, 一只手就可以操作vim常用功能。

;; kj 退回到普通模式
(use-package evil-escape
  :ensure t
  :init
  ;; {{ https://github.com/syl20bnr/evil-escape
  (setq-default evil-escape-delay 0.3)
  (setq evil-escape-excluded-major-modes '(dired-mode))
  (setq-default evil-escape-key-sequence "kj")
  ;; disable evil-escape when input method is on
  (evil-escape-mode 1)
  ;; }}
)
4.7.3.2. evil-anzu

小功能ui。 当用 Shift * 查找单词重复出现几次时,下方状态栏会多一个标识显示,显示当前重复出现单词的位置和总重复次数。

;;下方状态栏显示单词重复出现位置
(use-package evil-anzu
  :ensure t
  :after evil
  :diminish
  :demand t
  :init
  (global-anzu-mode t))
4.7.3.3. evil-collections

使用社区的按键绑定。相对稳定不会改变

同时可以设置不同模式中默认的evil状态

;;; 使用社区稳定的按键绑定。 并设置不同模式中默认的evil状态
(use-package evil-collection
  :ensure t
  :after evil
  :config
  (setq evil-collection-mode-list (remove 'lispy evil-collection-mode-list)) ; 移除社区中插件的改键对你键位影响
  (evil-collection-init)

  ;; 设置进入mode时对应的vim 模式
  (cl-loop for (mode . state) in
           '((org-agenda-mode . normal) ;进入agenda时默认Normal State状态
             (Custom-mode . emacs)
             (eshell-mode . emacs) ;进入eshell模式时默认Emacs State状态
             (makey-key-mode . motion))
           do (evil-set-initial-state mode state)))
4.7.3.4. evil-surround

S用法,选中一个单词后加引号。

  • viw S " 相当于原生vim text object用法 viw Shift i " 操作
;;; S用法,选中一个单词后加引号。viw S " 相当于原生vim text object用法 viw Shift i " 
(use-package evil-surround
  :ensure t
  :init
  (global-evil-surround-mode 1))
4.7.3.5. evil-nerd-commenter

对不同语言加的注释

;;; 添加不同语言的注释
(use-package evil-nerd-commenter
  :ensure t
  :init
  ;; normal 或 visual 模式下按 ,/ 可加注释
  ;; (define-key evil-normal-state-map (kbd ",ci") 'evilnc-comment-or-uncomment-lines)
  (define-key evil-normal-state-map (kbd ",/") 'evilnc-comment-or-uncomment-lines)
  (define-key evil-visual-state-map (kbd ",") 'evilnc-comment-or-uncomment-lines)
  )

操作:加注释

  • 单行注释, ,/
  • 多行注释,选中行 ,/
4.7.3.6. evil-snipe

小功能ui。高亮查找的字符。 如查找m字符。 fm 当前行的m字符高亮。按 ; 重复查找动作

(use-package evil-snipe
  :ensure t
  :diminish
  :init
  (evil-snipe-mode +1)
  (evil-snipe-override-mode +1))
4.7.3.7. evil-matchit

% 可以在函数、括号之间跳转

参考我的文章(如何提高编程速度)

;;; 按 % 可以在函数、括号之间跳转
(use-package evil-matchit
  :ensure
  :init
  (global-evil-matchit-mode 1))

4.7.4. 高阶用法(自定义 text-obj 或者 自定义 commands)

(message "Load init-evil done...")
(provide 'init-evil)

我的建议

使用emacs原生按键。 用多了evil,你还是个vim用户,无法轻松操作无配置或精简内置配置的emacs。

4.8. init-tools.el

4.8.1. init-tools.el 多光标操作 iedit & evil-multiedit

在学习章节提到过(精选 packages, 大大提升你的 Emacs 编辑效率)

此插件需要配合着evil插件使用。

;;; 多光标操作 iedit & evil-multiedit
(use-package iedit
  :ensure t
  :init
  (setq iedit-toggle-key-default nil)
  :config
  (define-key iedit-mode-keymap (kbd "M-h") 'iedit-restrict-function)
  (define-key iedit-mode-keymap (kbd "M-i") 'iedit-restrict-current-line))

(use-package evil-multiedit
  :ensure t
  :commands (evil-multiedit-default-keybinds)
  :init
  (evil-multiedit-default-keybinds))

edit操作:

  • evil normal 状态下,选中单词 viw 再按 R. 进入 evil-multiedit 模式。 这时选中区域高亮,同时下方状态栏显示单词出现次数和当前所在位置
  • evil-multiedit 模式下,使用vim方式操作。如
    • gg 到匹配单词的第一次出现位置; 大G 到匹配单词的最后一个; 按 0 光标跳到当前匹单词的开头
    • i 插入模式输入字符,所有匹配的单词跟着一直修改
    • esc 回到normal模式,按 C-g 退出edit模式

4.8.2. init-tools.el 快速选中区域 expand-region

在学习章节提到过(精选 packages, 大大提升你的 Emacs 编辑效率)

快速选中区域

(use-package expand-region
  :ensure t
  :config
  ;; 定义子菜单
  (defadvice er/prepare-for-more-expansions-internal
      (around helm-ag/prepare-for-more-expansions-internal activate)
    ad-do-it
    (let ((new-msg (concat (car ad-return-value)
                           ", H to highlight in buffers"
                           ", / to search in project, "
                           "e iedit mode in functions"
                           "f to search in files, "
                           "b to search in opened buffers"))
          (new-bindings (cdr ad-return-value)))
      (cl-pushnew  ; 高亮
       '("H" (lambda ()
               (interactive)
               (call-interactively
                'my/highlight-dwim))) ;自定库j-highlight-global
       new-bindings)
      (cl-pushnew ; 查找当前项目(git项目)目录中匹配选中字符串的文件
       '("/" (lambda ()
               (interactive)
               (call-interactively
                'my/search-project-for-symbol-at-point))) ;自定库j-expand-region
       new-bindings)
      (cl-pushnew ; 等同于evil multiedit 中 R 操
       '("e" (lambda ()
               (interactive)
               (call-interactively
                'evil-multiedit-match-all)))
       new-bindings)
      (cl-pushnew
       '("f" (lambda ()
               (interactive)
               (call-interactively
                'find-file)))
       new-bindings)
      (cl-pushnew  ; 搜索选中的字符
       '("b" (lambda ()
               (interactive)
               (call-interactively
                'consult-line)))
       new-bindings)
      (setq ad-return-value (cons new-msg new-bindings)))))


(use-package j-expand-region
  :ensure nil)

扩大缩小区域操作:

  • 选中字符或光标位置
  • M-x er/expand-region 扩大区域。 回车回车扩大; - 减号缩小区域;0 重置 /查找
  • 在expand-region下,又自定义快捷键绑定可以方便操作。

添加一个快捷键(init-keybindings.el 键位绑定 general),让标记和搜索功能更方便(j-expand-region.el 库)

;;;###autoload
(defun my/search-project-for-symbol-at-point ()
  (interactive)
  (if (use-region-p)
      (progn
        (consult-ripgrep (project-root (project-current))
                         (buffer-substring (region-beginning) (region-end))))))

(global-definer
  "hc" 'my/clearn-highlight
  "hH" 'my/highlight-dwim
  "v" 'er/expand-region
  )

扩大区域快捷操作:

4.8.3. init-tools.el 文本高亮 symbol-overlay & highlight-global

在学习章节提到过(精选 packages, 大大提升你的 Emacs 编辑效率)

高亮插件

highlight-global是github插件,使用quelpa插件辅助安装(init-packages.el 安装 quelpa 插) , 自定义了高亮函数(j-highlight-global.el 库)做为快捷键引用(init-tools.el 快速选中区域 expand-region)

(use-package highlight-global
  :ensure nil
  :commands (highlight-frame-toggle)
  :quelpa (highlight-global :fetcher github :repo "glen-dai/highlight-global" :upgrade nil)
  :config
  ;; 高亮默认随机红、粉、蓝、青、紫等颜色循环
  (progn
    (setq-default highlight-faces
                  '(('hi-red-b . 0)
                    ('hi-aquamarine . 0)
                    ('hi-pink . 0)
                    ('hi-blue-b . 0)))))
(use-package symbol-overlay
  :ensure t
  :config
  (define-key symbol-overlay-map (kbd "h") 'nil))
(use-package j-highlight-global
  :ensure nil)

高亮插件操作:

  • SPC v H 选中部分以一种颜色高亮。重复操作可以看到不高亮颜色。
    • 场景:查找代码或日志时,标记重点区域
    • general 快捷键 SPC v SPC hh
  • M-x clear-highlight-frame 清除颜色,或者 SPC hc 清除高亮颜色
(message "Load init-tools done...")
(provide 'init-tools)

4.9. init-window.el

在学习中提供过(窗口)

Evil 的窗口选择操作 C-w h/j/j/k

4.9.1. init-window.el window numbering

;;; 切换窗口
(use-package window-numbering
  :ensure t
  :init
  :hook (after-init . window-numbering-mode))
  • 切换窗口 M-<窗口编号>

定义快捷键

(global-definer
  "0" 'select-window-0
  "1" 'select-window-1
  "2" 'select-window-2
  "3" 'select-window-3
  "4" 'select-window-4
  "5" 'select-window-5
  )

(+general-global-menu! "window" "w"
  "/" 'split-window-right
  "-" 'split-window-below
  "m" 'delete-other-windows
  • 左右分屏 SPC w /
  • 上下分屏 SPC w -
  • 删除所有其他窗口 SPC w m

4.9.2. init-window.el es-windows

可以交换窗口、删除窗口、分屏

;;; 可以交换窗口、删除窗口、分屏
(use-package es-windows
  :ensure t)

4.9.3. init-window.el Buffer move

移动窗口

;;; config
(use-package buffer-move
  :ensure t)

SPC w s 指定交的换窗口

4.9.4. init-window.el Resize windows

调整窗口大小

;;; 调整窗口大小
(use-package resize-window
  :ensure t
  :init
  (defvar resize-window-dispatch-alist
    '((?n resize-window--enlarge-down " 向下Resize - Expand down " t)
      (?p resize-window--enlarge-up " 向上Resize - Expand up" t)
      (?f resize-window--enlarge-horizontally " 放大Resize - horizontally" t)
      (?b resize-window--shrink-horizontally " 缩小Resize - shrink horizontally" t)
      (?r resize-window--reset-windows " 重置Resize - reset window layout" nil)
      (?w resize-window--cycle-window-positive "  选择分屏Resize - cycle window" nil)
      (?W resize-window--cycle-window-negative " Resize - cycle window" nil)
      (?2 split-window-below " 水平Split window horizontally" nil)
      (?3 split-window-right " 垂直Slit window vertically" nil)
      (?0 resize-window--delete-window " 删除Delete window" nil)
      (?K resize-window--kill-other-windows " Kill other windows (save state)" nil)
      (?y resize-window--restore-windows " 恢复(when state) Restore window configuration" nil)
      (?? resize-window--display-menu " Resize - display menu" nil))
    "List of actions for `resize-window-dispatch-default.
Main data structure of the dispatcher with the form:
\(char function documentation match-capitals\)"))

4.9.5. init-window.el Winner(builtin)

内置插件。用来切换2个不同窗口布局

;;; 内置插件。用来切换2个不同窗口布局
(use-package winner
  :ensure nil
  :commands (winner-undo winner-redo)
  :hook (after-init . winner-mode)
  :init (setq winner-boring-buffers '("*Completions*"
                                      "*Compile-Log*"
                                      "*inferior-lisp*"
                                      "*Fuzzy Completions*"
                                      "*Apropos*"
                                      "*Help*"
                                      "*cvs*"
                                      "*Buffer List*"
                                      "*Ibuffer*"
                                      "*esh command on file*")))
  • ’winner-undo ; SPC w u 返回布局
  • ’winner-redo ; SPC w z 撤销返回布局。 和返回布局结合实现2个布局间切换

4.9.6. init-window.el 关闭弹窗 Popper

;; Enforce rules for popups
(use-package popper
  :ensure t
  :defines popper-echo-dispatch-actions
  :commands popper-group-by-directory
  :bind (:map popper-mode-map
              ("s-`" . popper-toggle-latest)
              ("s-o"   . popper-cycle)
              ("M-`" . popper-toggle-type))
  :hook (emacs-startup . popper-mode)
  :init
  (setq popper-reference-buffers
        '("\\*Messages\\*"
          "Output\\*$" "\\*Pp Eval Output\\*$"
          "\\*Compile-Log\\*"
          "\\*Completions\\*"
          "\\*Warnings\\*"
          "\\*Flymake diagnostics.*\\*"
          "\\*Async Shell Command\\*"
          "\\*Apropos\\*"
          "\\*Backtrace\\*"
          "\\*prodigy\\*"
          "\\*Calendar\\*"
          "\\*Embark Actions\\*"
          "\\*Finder\\*"
          "\\*Kill Ring\\*"
          "\\*Embark Export:.*\\*"
          "\\*Edit Annotation.*\\*"
          "\\*Flutter\\*"
          bookmark-bmenu-mode
          comint-mode
          compilation-mode
          help-mode helpful-mode
          tabulated-list-mode
          Buffer-menu-mode
          occur-mode
          gnus-article-mode devdocs-mode
          grep-mode occur-mode rg-mode deadgrep-mode ag-mode pt-mode
          ivy-occur-mode ivy-occur-grep-mode
          process-menu-mode list-environment-mode cargo-process-mode
          youdao-dictionary-mode osx-dictionary-mode fanyi-mode

          "^\\*eshell.*\\*.*$" eshell-mode
          "^\\*shell.*\\*.*$"  shell-mode
          "^\\*terminal.*\\*.*$" term-mode
          "^\\*vterm.*\\*.*$"  vterm-mode

          "\\*DAP Templates\\*$" dap-server-log-mode
          "\\*ELP Profiling Restuls\\*" profiler-report-mode
          "\\*Flycheck errors\\*$" " \\*Flycheck checker\\*$"
          "\\*Paradox Report\\*$" "\\*package update results\\*$" "\\*Package-Lint\\*$"
          "\\*[Wo]*Man.*\\*$"
          "\\*ert\\*$" overseer-buffer-mode
          "\\*gud-debug\\*$"
          "\\*lsp-help\\*$" "\\*lsp session\\*$"
          "\\*quickrun\\*$"
          "\\*tldr\\*$"
          "\\*vc-.*\\*$"
          "\\*eldoc\\*"
          "^\\*elfeed-entry\\*$"
          "^\\*macro expansion\\**"

          "\\*Agenda Commands\\*" "\\*Org Select\\*" "\\*Capture\\*" "^CAPTURE-.*\\.org*"
          "\\*Gofmt Errors\\*$" "\\*Go Test\\*$" godoc-mode
          "\\*docker-containers\\*" "\\*docker-images\\*" "\\*docker-networks\\*" "\\*docker-volumes\\*"
          "\\*prolog\\*" inferior-python-mode inf-ruby-mode swift-repl-mode
          "\\*rustfmt\\*$" rustic-compilation-mode rustic-cargo-clippy-mode))

  (when (display-grayscale-p)
    (setq popper-mode-line
          '(:eval
            (concat
             (propertize " " 'face 'mode-line-emphasis)
             (propertize " " 'face 'mode-line-emphasis)))))

  (setq popper-echo-dispatch-actions t)
  (setq popper-group-function nil)
  :config
  (popper-echo-mode 1)

  (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 popper-close-window-hack (&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 #'popper-close-window-hack)))

按键绑定

我的模块化配置(init-keybindings.el 键位绑定 general)

(global-definer
  ;; 这里是其他的快捷键
  "0" 'select-window-0  ; SPC 0 切0号窗口
  "1" 'select-window-1  ; SPC 1 切1号窗口
  "2" 'select-window-2
  "3" 'select-window-3
  "4" 'select-window-4
  "5" 'select-window-5)

(+general-global-menu! "window" "w"
  "/" 'split-window-right   ; SPC w / 左右分屏
  "-" 'split-window-below   ; SPC w - 上下分屏
  "m" 'delete-other-windows ; SPC w m 仅保留当前窗口
  "u" 'winner-undo           ; SPC w u 返回布局
  "z" 'winner-redo           ; SPC w z 撤销返回布局。 和返回布局结合实现2个布局间切换
  "w" 'esw/select-window     ; SPC w w 选择分屏方式 >向右分,<向左分,^向上分,v向下分
  "s" 'esw/swap-two-windows  ; SPC w s 交换窗口 
  "d" 'esw/delete-window     ; SPC w d 删除指定窗口
  "=" 'balance-windows-area  ; SPC w = 等宽布局
  "r" 'esw/move-window
  "x" 'resize-window         ; SPC w x 调整窗口大小。如f水平变大;b缩小;n向下扩;p向上扩;
  "H" 'buf-move-left         ; SPC w H 右边窗口左移
  "L" 'buf-move-right        ; SPC w L 窗口右移
  "J" 'buf-move-down         ; SPC w J 窗口下移
  "K" 'buf-move-up)          ; SPC w K 窗口上移
(message "Load init-window done...")
(provide 'init-window)

4.10. init-org.el

4.10.1. init-org.el 内置配置

在学习章节提到过(learn org)

;;; config

(with-eval-after-load 'org  ; 启动org文件时加载
  ;; 使用旧版快捷键<s +Tab
  (require 'org-tempo)

  ;; 其他
  (require 'org-protocol) ; 加载内置包, 在摘取网页内容时会用到
  (setq org-image-actual-width nil) ; 内嵌图片是否显示实际宽度, 默认为t
  (setq org-return-follows-link t)  ; 链接位置回车,使用外部程序打开

  ;; 禁用左尖括号
  (setq electric-pair-inhibit-predicate
        `(lambda (c)
           (if (char-equal c ?\<) t (,electric-pair-inhibit-predicate c))))

  (add-hook 'org-mode-hook
            (lambda ()
              (setq-local electric-pair-inhibit-predicate
                          `(lambda (c)
                             (if (char-equal c ?\<) t (,electric-pair-inhibit-predicate c))))))
)

4.10.2. init-org.el 安装最新org包

;;; 安装 org,这个配置一定要配置在 use-package 的初始化之前,否则无法正常安装
;; (assq-delete-all 'org package--builtins)
;; (assq-delete-all 'org package--builtin-versions)
;; (use-package org
;;   :pin gnu-elpa
;;   :ensure t)

(use-package org-contrib  ;非org的官方贡献的插件
  :ensure t
  :pin nongnu)

;;; Org-mode (personal information manager)
(use-package org
  :ensure nil
  :init
  (setq org-imenu-depth 7)

  (add-to-list 'safe-local-variable-values '(org-hide-leading-stars . t))
  (add-to-list 'safe-local-variable-values '(org-hide-macro-markers . t))
  :bind
  ( :map global-map
    ("C-c l" . org-store-link) ; 存储当前连接
    ("C-c o" . org-open-at-point-global)
    :map org-mode-map
    ("C-c M-l" . org-insert-last-stored-link) ; 插入最近存储的连接
    ("C-c C-M-l" . org-toggle-link-display) ; 显示连接内容
    :map ctl-x-x-map
    ("i" . j-org-id-headlines) ; 为标题创建属性CUSTOM_ID
    ("h" . j-org-ox-html)      ; org导出为html
    ))
;;;; links
(use-package org
  :ensure nil
  :config
  (require 'j-org) ; for the above commands

  (setq org-link-context-for-files t)
  (setq org-link-keep-stored-after-insertion nil)
  (setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id))

;;;; code blocks
(use-package org
  :ensure nil
  :config
  ;;(setq org-confirm-babel-evaluate nil)             ; 使用Babel执行代码块时, 是否弹出确认对话框. nil 禁止弹出, 默认t弹出
  ;;(setq org-src-window-setup 'current-window)       ; 源代码窗口的布局位置. 默认 'reorganize-frame 侧边。 'current-window 当前窗口 
  ;;(setq org-edit-src-persistent-message nil)        ; 编辑源代码时显示持久的消息。nil 不显示, 默认 t
  ;;(setq org-src-fontify-natively t)                 ; 源代码本地高亮。默认t
  (setq org-src-preserve-indentation t)             ; 编辑源代码时保留原有的缩进,t代表启用该功能。默认nil不保留. 
  (setq org-src-tab-acts-natively t)                ; 源代码编辑模式下,Tab键的行为与原生代码编辑器一致,t代表启用该功能,  默认t
  (setq org-edit-src-content-indentation 0))          ; 代码块里缩进,0不缩进,默认是2(重要)

4.10.3. init-org.el org todo

在我的学习文章提到过(模块列表)

(with-eval-after-load 'org  ; 启动org文件时加载
  ;; 自定义org todo  C-c C-t 
  (setq org-todo-keywords
        (quote ((sequence "TODO(t)" "STARTED(s)" "|" "DONE(d!/!)")
                (sequence "WAITING(w@/!)" "SOMEDAY(S)" "|" "CANCELLED(c@/!)" "MEETING(m)" "PHONE(p)"))))
)

以上代码让你在为一个标题提供待办事项状态时添加了一个键序列,你可以键入 C-c C-t 来查看效果。

其中 @ 代表记录日志,而 ! 代表记录时间戳,你也可以让他们具有两种功能: TODO(t@/!)

任务进度

创建文件 ~/gtd.org

* WAITING Daily routine
- [ ] leetcode
- [ ] execrcise
* TODO Daily routine [100%]
SCHEDULED: <2022-06-10 Fri 10:00 +1d>
:PROPERTIES:
:LAST_REPEAT: [2022-06-09 Thu 02:31]
:END:
- State "DONE"       from "TODO"       [2022-06-09 Thu 02:31]
- State "TODO"       from "WAITING"    [2022-06-09 Thu 02:26]
- [X] leetcode
- [X] execrcise
  • C-c C-s 开始计划: <2022-06-09 Thu 10:00> 10点
  • 每天 10 点做:需要 +d 加一天 SCHEDULED: <2022-06-09 Thu 10:00 +1d> ,后面在 agenda 中会用到
  • 完成情况进度:每一项任务使用复选框,todo 部分加 [%] ,复选框使用 C-c C-c 打 叉 表示完成。

标记完成:=C-c C-t d= ,可以看到 SCHEDULED 时间自动加 1 SCHEDULED: <2022-06-10 Fri 10:00 +1d>

* TODO Daily routine [100%]
SCHEDULED: <2022-06-10 Fri 10:00 +1d>
:PROPERTIES:
:LAST_REPEAT: [2022-06-09 Thu 02:31]
:END:
- State "DONE"       from "TODO"       [2022-06-09 Thu 02:31]
- State "TODO"       from "WAITING"    [2022-06-09 Thu 02:26]
- [X] leetcode
- [X] execrcise

不过进入新的计划,每一项还需要新状态。这很麻烦,所以这边用另一个插件包 org-contrib。 目的是要引入 check list

(with-eval-after-load 'org
  ;; 引入checklist, 自动更新状态
  ;; org-set-property 添加 RESET_CHECK_BOXS, 设置为t
  (require 'org-checklist)
  ;; need repeat task and properties
  (setq org-log-done t)
  (setq org-log-into-drawer t)

)

通过 M-x org-set-property 添加 RESET_CHECK_BOXESRESET_CHECK_BOXES 设置为 t 开启, 会在 PROPERTIES 中增加属性。

C-c C-t d 把标题设置为 DONE,自动进入下一个时间点。 这对自定义一些重复性工作比较方便。

* TODO Daily routine [0%]
SCHEDULED: <2022-06-15 Wed 10:00 +1d>
:PROPERTIES:
:LAST_REPEAT: [2022-06-09 Thu 07:24]
:RESET_CHECK_BOXES: t
:END:
- State "DONE"       from "TODO"       [2022-06-09 Thu 07:24]
- State "DONE"       from "TODO"       [2022-06-09 Thu 02:31]
- State "TODO"       from "WAITING"    [2022-06-09 Thu 02:26]
- [ ] leetcode
- [ ] execrcise

每次完成任务会自动插入完成状态,使用下面设置可以隐藏到 PROPERTIES 里:

;; need repeat task and properties
(setq org-log-done t)
(setq org-log-into-drawer t)

使用 C-c C-t d 完成任务, 完成状态会增加到 LOGBOOK 属性中。把之前的状态手动移过来。

* TODO Daily routine [0%]
SCHEDULED: <2022-06-17 Fri 10:00 +1d>
:PROPERTIES:
:LAST_REPEAT: [2022-06-09 Thu 07:44]
:RESET_CHECK_BOXES: t
:END:
:LOGBOOK:
- State "DONE"       from "TODO"       [2022-06-09 Thu 07:44]
- State "DONE"       from "TODO"       [2022-06-09 Thu 07:34]
- State "DONE"       from "TODO"       [2022-06-09 Thu 07:24]
- State "DONE"       from              [2022-06-09 Thu 03:03]
- State "DONE"       from              [2022-06-09 Thu 02:50]
- State "DONE"       from "TODO"       [2022-06-09 Thu 02:31]
- State "TODO"       from "WAITING"    [2022-06-09 Thu 02:26]  
:END:
- [ ] leetcode
- [ ] execrcise

4.10.4. init-org.el org agenda

相当于有一个日历实时告诉你每天要做什么。通过 M-x org-agenda 来打开. 这里需要一个存放日程的目录,同时绑定快捷键 C-c a 方便查看。

;;; 日历
;; C-c C-s schedule
;; C-c C-d deadline
(global-set-key (kbd "C-c a") 'org-agenda)
(setq org-agenda-files '("~/gtd.org")) ;; 定义 agenda 文件,可以是多个
(setq org-agenda-span 'day) ;; 按天观察

基本操作:

  • C-c a a 可以看到每周日程。
  • f/b 下一个/上一个阶段安排, d/w 天/周视图,=n/p= 上/下移动光标
  • ~/gtd.org 设置 deadline C-c C-d ,在日程中也能看到。

4.10.5. init-org.el org capture

(setq org-capture-templates
      '(("t" "Todo" entry (file+headline "~/gtd.org" "Workspace")
         "* TODO [#B] %?\n  %i\n %U"
         :empty-lines 1)))

(global-set-key (kbd "C-c r") 'org-capture)

这个模板包含五个部分,分别是

模板组成 对应默认模板中的内容 描述
key “t” 用来选择模板的字符
description “Todo” 展示用的模板描述
type entry 新增内容的类型
target (file+headline “~/gtd.org” “Workspace”) 新增内容的存储位置
template “* TODO [#B] %?\n %i\n %U” 新增内容的模板

新增内容的模板:

  • “%?” 是一个更特殊的标记,它不会产生任何内容,当所有其他的特殊标记都展开完毕或者输入完毕后,光标将会停留在这个标记所在的位置。
  • %i 可以插入一段初始化内容
  • %U 当前包含日期和时间的未激活的 timestamp,如: [2018-03-04 周日 19:26]

定义捕获模板,绑定到快捷键 C-c r 。 更写详细的参考官方文档。

操作:

  • C-c a t 向指定文件中写内容。 C-c C-c 完成. 会记录到gtd.org文件中
  • 在文件中设置开始时间 C-c C-s 21:00 点, 再执行 C-c a a 就可看到日程更新了。

我用以下配置

(setq org-capture-templates
      '(("t" "Todo" entry (file+headline "~/gtd.org" "Workspace")
         "* TODO [#B] %?\n  %i\n %U"
         :empty-lines 1)
        ;; 用来摘取网页内容
        ("x" "Web Collections" entry
         (file+headline org-agenda-file-note "Web")
         "* %U %:annotation\n\n%:initial\n\n%?")
        ))

(setq org-agenda-file-note (expand-file-name "~/notes.org")) ; 保存网页内容

(global-set-key (kbd "C-c r") 'org-capture)

4.10.6. init-org.el org effect

设置任务完成需要的时间。

在 agenda view 中, e 键可以设置effort, 按 _ (org-agenda-filter-by-effort) 可以过滤指定effort 的heading

范例:设置 30分钟任务

  • 进入agenda C-c a a
  • 设置完成任务需要30分钟,按 e 输入 00:30
  • 过滤 1 小时内能完成任务,按 _ 输入 < 选择 [4]1:00 对应的数字 4

4.10.7. init-org.el org tags

在 agenda view 中添加tag, 使用 : , 也可以 M-x org-agenda-set-tags 添加。

4.10.8. init-org.el org priority

有三种优先级模式,A-C,若不指定优先级,’B’等级是默认的。

  • C-c , 或者 M-x org-priority : 设置当前标题的优先级,键入A-C设置级别,或者键入SPC清除标记
  • 提升/降低当前标题的优先级

设置agenda中优先级A的为“重要且紧急的事”

(setq org-agenda-custom-commands
      '(("c" "重要且紧急的事"
         ((tags-todo "+PRIORITY=\"A\"")))
        ;; ...other commands here
        ))

查看重要且紧急的事:

  • 进行agenda 视图 C-c a a=,设置优先级A, 按 =, 逗号,输入a。
  • 查看重要且紧急的事,=C-c a c= 展示重要且紧急的事(必须有 TODO 状态)。

4.10.9. init-org.el org 截图

在我的学习文章提到过(模块列表)

  • org-download 29版本支持不友好
  • 自定义函数,灵活点

org-download

;;; 截图
(use-package org-download
    :ensure t
    :demand t
    :after org
    :config
    (add-hook 'dired-mode-hook 'org-download-enable)
    (setq org-download-screenshot-method "powershell -c Add-Type -AssemblyName System.Windows.Forms;$image = [Windows.Forms.Clipboard]::GetImage();$image.Save('%s', [System.Drawing.Imaging.ImageFormat]::Png)")
    (defun org-download-annotate-default (link)
      "Annotate LINK with the time of download."
      (make-string 0 ?\s))
    (setq org-download-image-attr-list
            `(,(concat "\n#+DOWNLOADED: screenshot @ "
                     (format-time-string "%Y-%m-%d %a %H:%M:%S" (current-time))
                     "\n#+CAPTION: \n#+ATTR_ORG: :width 80%"
                     "\n#+ATTR_HTML: :width 80%")))
    (setq-default org-download-heading-lvl nil
                  org-download-image-dir "./images"
                  ;; org-download-screenshot-method "screencapture -i %s"
                    org-download-timestamp "%Y%m%d_%H%M%S_"
                  org-download-screenshot-file (expand-file-name "screenshot.jpg" temporary-file-directory)))

安装截图工具

  • Linux / MacOS
    • snipaste
  • pc
    • 系统截图工具 <Win>+<shift>+s
    • pacman -S mingw-w64-x86_64-imagemagick

使用方法

  • 使用系统截图工具 (Windows 下用 <Win>+<shift>+s) 将截图保存到剪贴板
  • M-x org-download-screenshot 将剪贴板中图片粘贴到org文件中.

org-toggle-inline-images

自定义函数

;;; 截图
(use-package j-org-download
  :ensure nil
  :demand t
  :bind
  ("<f2>" . my/org-insert-clipboard-image))

提示输入图片显示大小,默认800px, 可以指定如 80%显示。

可以使用压缩工具来压缩图片,这样使用的ffmpeg媒体流处理工具。官方下载安装配置path路径就可使用。

改变图片大小

显示和关闭内嵌图片 M-x org-toggle-inline-images

#+CAPTION:  <图片标题>
#+ATTR_ORG: :width 800px
#+ATTR_HTML: :width 800px

默认在org中 org-image-actual-width 变量值t, 显示的图片大小是实际图片大小. 修改变量的值,按指定大小显示。

- 配置文件中设置变量的值
(setq org-image-actual-width nil)

- 单独为文件设置局部变量,方法是在 org 文件的第1行加下面内容
# -*- org-image-actual-width: nil; -*-

使用yasnippets创建图片属性段

  • M-x yas-new-snippet 创建新代码片段

    # -*- mode: snippet -*-
    # key: imw
    # name: image-width
    # --
    #+DOWNLOADED: screenshot @ `(format-time-string "%Y%m%d %a %H:%M:%S" (current-time))`
    #+CAPTION: $0
    #+ATTR_ORG: :width ${1:80%}
    #+ATTR_HTML: :width ${2:80%}
    

    $0 光标位置, $1..n 使用tab, shift-tab切换占位符位置,可以设置默认值如 ${1:aa}

    `()` 可以写lisp语法

  • C-c C-c=完成编程,选择片段生效的模式,保存指定位置 =~/.emacs.d/snippets/org-mode/<name>
  • M-x yas-describe-tables 查看
  • M-x yas-visit-snippet-file 编辑

imw后按TAB键补全

#+DOWNLOADED: screenshot @ 20240728 周日 12:29:42
#+CAPTION: 
#+ATTR_ORG: :width 80%
#+ATTR_HTML: :width 80%

4.10.10. init-org.el 从网页摘取内容 Org Protocol

在我的学习文章提到过(模块列表)

官方文档:https://orgmode.org/worg/org-contrib/org-protocol.html

从网页摘取内容

1.Step 1: Get emacsclient to work

设置快速打开emacs. Emacs 需要开启 Server Mode,代码如下:

(server-mode 1)
(require 'org-protocol) ; 加载内置包

2.创建注册表文件 org-protocol.reg

指定emacsclientw位置

PC

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\org-protocol]
"URL Protocol"=""
@="URL:Org Protocol"

[HKEY_CLASSES_ROOT\org-protocol\shell]

[HKEY_CLASSES_ROOT\org-protocol\shell\open]

[HKEY_CLASSES_ROOT\org-protocol\shell\open\command]
@="\"D:\\msys64\\mingw64\\bin\\emacsclientw.exe\"  \"%1\""

双击注册文件运行。

3.add org templates

定义capture模板,用来快速摘取网页内容(init-org.el org capture)

(setq org-agenda-file-note (expand-file-name "~/notes.org"))
(setq org-capture-templates
      '(
        ("x" "Web Collections" entry
         (file+headline org-agenda-file-note "Web")
         "* %U %:annotation\n\n%:initial\n\n%?")
        ))

4.add chrome bookmark

在chrome浏览器上添加新标签,将下面js代码复制进行地址,标签名自定义,保存

javascript:location.href='org-protocol://capture?template=x&url=%27+encodeURIComponent(location.href)+%27&title=%27+encodeURIComponent(document.title)+%27&body=%27+encodeURIComponent(function(){var html = "";var sel = window.getSelection();if (sel.rangeCount) {var container = document.createElement("div");for (var i = 0, len = sel.rangeCount; i < len; ++i) {container.appendChild(sel.getRangeAt(i).cloneContents());}html = container.innerHTML;}var dataDom = document.createElement(%27div%27);dataDom.innerHTML = html;dataDom.querySelectorAll(%27a%27).forEach(function(item, idx) {console.log(%27find a link%27);var url = new URL(item.href, window.location.href).href;var content = item.innerText;item.innerText = %27[[%27+url+%27][%27+content+%27]]%27;});[%27p%27, %27h1%27, %27h2%27, %27h3%27, %27h4%27].forEach(function(tag, idx){dataDom.querySelectorAll(tag).forEach(function(item, index) {var content = item.innerHTML.trim();if (content.length > 0) {item.innerHTML = content + %27&#13;&#10;';}});});return dataDom.innerText.trim();}())

使用: 选中网页文字, 点击标签。网页的内容就会写到org-capture中,=C-c C-c= 完成, 内容被保存在 ~/notes.org

5.return follow link

在org中的光标在连接位置回车不能打开链接。如下设置回车后可在浏览器中打开。

(setq org-return-follows-link t)  ; 链接位置回车,使用外部程序打开

4.10.11. init-org.el 拼写检查 Spell checking

在我的学习文章提到过(模块列表)

(use-package flyspell-correct
  :ensure t
  :init)

;; ispell 为内置包,需要使用外部程序来做语法检查
(use-package ispell
  :ensure nil
  :init
  (setq ispell-program-name "aspell")
  (dolist (hook '(text-mode-hook))
    (add-hook hook (lambda () (flyspell-mode 1))))
  (setq ispell-personal-dictionary "d:/msys64/mingw64/lib/aspell-0.60/en_GB")
  )

(define-key evil-insert-state-map (kbd "C-;") 'flyspell-correct-previous)

Install spell

PC: mysys2 中安装

pacman -S mingw64/mingw-w64-x86_64-aspell    #程序
pacman -S mingw64/mingw-w64-x86_64-aspell-en #英语语法检查文件

Configuration

1.add “*C:\msys64\mingw64\bin” to to PATH 2.Error: The file “c:\msys64\mingw64/lib/aspell-0.60/en_US” can not be opened for reading.

使用:

  • M-x ispell-minor-mode flyspell-mode 手动激活
  • M-x flyspell-correct-previous 或使用绑定的快捷键 C; 选择正常的拼写

4.10.12. init-org.el 翻译 Fanyi Dictionary

在我的学习文章提到过(模块列表)

有多种翻译工具,这里使用fanyi软件包

;;; 有道翻译
(use-package youdao-dictionary
  :ensure t
  :commands (youdao-dictionary-search-at-point+)
  :init
  (global-set-key (kbd "C-c y") 'youdao-dictionary-search-at-point+))
;;; fanyi翻译
(use-package fanyi
  :ensure t
  :custom
  (fanyi-providers '(;; 海词
                     ;; fanyi-haici-provider
                     ;; 有道同义词词典
                     fanyi-youdao-thesaurus-provider
                     ;; Etymonline
                     fanyi-etymon-provider
                     ;; Longman
                     ;; fanyi-longman-provider)
                   )))

fanyi使用

  • 光标停在单词处执行 M-x fanyi-dwim2

Some useful keybindings

C-RET  for creating new headlines
M-RET  for creating new lists
M-left/right/up/down

4.10.13. init-org.el 双链笔记 org-Roam

双链笔记系统

安装 Org Oram

在 HOME 新建一个 org 目录夹,用来存储你的 org roam 笔记。

(use-package org-roam
  :ensure t
  :custom
  (org-roam-directory (file-truename "~/org"))  ; 默认笔记目录,提前手动创建好
  :bind (("C-c n l" . org-roam-buffer-toggle)  ; 显示反向链接
         ("C-c n f" . org-roam-node-find)      ; 查找
         ("C-c n g" . org-roam-graph)
         ("C-c n i" . org-roam-node-insert)    ; 插入
         ("C-c n c" . org-roam-capture)        ; 捕获
         ;; Dailies
         ("C-c n j" . org-roam-dailies-capture-today)) ; 记日志功能
  :config
  ;; If you're using a vertical completion framework, you might want a more informative completion interface
  ;; 可以按标题搜索、tags搜索
  (setq org-roam-node-display-template (concat "${title:*} " (propertize "${tags:10}" 'face 'org-tag)))
  (org-roam-db-autosync-mode)    ; 刷新数据 sqlite3 库 ~/.emacs.d/org-roam.db
  ;; If using org-roam-protocol
  (require 'org-roam-protocol))

Org roam 基本使用

新建笔记和查找笔记

  • org-roam-node-find(C-c n f) 如果没有,会创建新节点. 如节点名为 Elisp
    • 输入节点名, Alt-回车 创建
  • 在新建节点上 C-c C-c 保存
  • org-roam-capture(C-c n c)

    创建一条新笔记. 未来可以预设不同主题的默认模板. 每一条笔记就是一个 org 文件

  • org-roam-insert(C-c n i)

    插入一条笔记的链接

    在查找笔记时利用 org-roam-find 的界面

    若笔记不存在, 则利用 org-roam-capture 的界面创建笔记

  • C-c C-k 删除当前capture

加tags

  • 添加tag M-x org-roam-tag-add , 使用 C-c n f 可以看到对应的tag

建立笔记引用

[[]]
如:
[[输入要引用的节点名称,自动补全]]

查找引用

  • M-x org-roam-buffer-toggle

已有的 headline 转换为一个节点

  • M-x org-id-get-create 标题下会多个ID,
  • =M-x refile node 把当前标题移动一个已存在节点上.

刷新数据库

所有文件都保存后刷新库

  • 自动更新: 通过 (org-roam-db-autosync-mode) 实现
  • 手动更新 M-x org-roam-db-sync
4.10.13.1. init-org.el org org-roam-ui

org-roam-ui 在pc下安装存在问题。系统默认按linux 系统安装tar解压. windows 上面需要使用 system32/tar 这个程序去解压。

解决方式: 使用vc来安装. 在emacs30已经集成(package-vc.el)

方法如下

直接从 github 安装插件

(use-package org-roam-ui
  :ensure nil
  :vc (:fetcher "github" :repo "org-roam/org-roam-ui"))
(use-package org-roam-ui
  :ensure t
  :after org-roam)

鼠标操作, 节点右键从emacs中打开笔记

可视化

实时显示链接

(message "Load init-org done...")
(provide 'init-org)

4.11. init-persp.el 工作区间管理

在我的学习文章提到过(模块列表)

4.11.1. init-window.el 窗口 tab-bar(built-in)

(use-package tab-bar
  :ensure nil
  :init
  (tab-bar-mode t)
  (setq tab-bar-new-tab-choice "*scratch*") ;; buffer to show in new tabs
  (setq tab-bar-close-button-show nil)      ;; hide tab close / X button
  (setq tab-bar-show 1)                     ;; hide bar if <= 1 tabs open
  (setq tab-bar-format '(tab-bar-format-tabs tab-bar-separator))

  (custom-set-faces
   '(tab-bar ((t (:inherit mode-line))))
   '(tab-bar-tab ((t (:inherit mode-line :foreground "#993644"))))
   '(tab-bar-tab-inactive ((t (:inherit mode-line-inactive :foreground "black")))))

  (defvar ct/circle-numbers-alist
    '((0 . "⓪")
      (1 . "①")
      (2 . "②")
      (3 . "③")
      (4 . "④")
      (5 . "⑤")
      (6 . "⑥")
      (7 . "⑦")
      (8 . "⑧")
      (9 . "⑨"))
    "Alist of integers to strings of circled unicode numbers.")

  (defun ct/tab-bar-tab-name-format-default (tab i)
    (let ((current-p (eq (car tab) 'current-tab))
          (tab-num (if (and tab-bar-tab-hints (< i 10))
                       (alist-get i ct/circle-numbers-alist) "")))
      (propertize
       (concat tab-num
               " "
               (alist-get 'name tab)
               (or (and tab-bar-close-button-show
                        (not (eq tab-bar-close-button-show
                                 (if current-p 'non-selected 'selected)))
                        tab-bar-close-button)
                   "")
               " ")
       'face (funcall tab-bar-tab-face-function tab))))
  (setq tab-bar-tab-name-format-function #'ct/tab-bar-tab-name-format-default)
  (setq tab-bar-tab-hints t))

配置生效后,可以看到窗口上方有tab页。

通过 M-x tab-bar-new-tab 生成一个的 tab-bar

M-x tab-bar-rename 保持tab-bar名字不变,不跟随文件变化 。

4.11.2. init-persp.el 工作区间 tabspaces

(use-package tabspaces
  ;; use this next line only if you also use straight, otherwise ignore it.
  :ensure t
  :hook (after-init . tabspaces-mode) ;; use this only if you want the minor-mode loaded at startup.
  :defer t
  :commands (tabspaces-switch-or-create-workspace
             tabspaces-open-or-create-project-and-workspace)
  :custom
  (tabspaces-use-filtered-buffers-as-default t)
  (tabspaces-default-tab "Default")
  (tabspaces-remove-to-default t)
  (tabspaces-include-buffers '("*scratch*"))
  ;; maybe slow 关闭emacs后自动恢复工作空间。怕影响性能可以注释掉
  ;;(tabspaces-session t)
  ;;(tabspaces-session-auto-restore t)
  :config
  ;; Filter Buffers for Consult-Buffer

  (with-eval-after-load 'consult
    ;; hide full buffer list (still available with "b" prefix)
    (consult-customize consult--source-buffer :hidden nil :default nil)
    ;; set consult-workspace buffer list
    (defvar consult--source-workspace
      (list :name "Workspace Buffers"
            :narrow ?w
            :history 'buffer-name-history
            :category 'buffer
            :state #'consult--buffer-state
            :default t
            :items (lambda () (consult--buffer-query
                               :predicate #'tabspaces--local-buffer-p
                               :sort 'visibility
                               :as #'buffer-name)))

      "Set workspace buffer list for consult-buffer.")
    (add-to-list 'consult-buffer-sources 'consult--source-workspace)))

重启或执行 M-x tabspaces-mode 生效。

C-x b 打开缓冲区,发现多了一个 Workspace Buffer ,这里的buffer优先使用。

按键绑定

(+general-global-menu! "layout" "l"
  "l" 'tabspaces-switch-or-create-workspace
  "L" 'tabspaces-restore-session
  "p" 'tabspaces-open-or-create-project-and-workspace
  "f" 'tabspaces-project-switch-project-open-file
  "s" 'tabspaces-save-session
  "B" 'tabspaces-switch-buffer-and-tab
  "b" 'tabspaces-switch-to-buffer
  "R" 'tab-rename
  "TAB" 'tab-bar-switch-to-recent-tab
  "r" 'tabspaces-remove-current-buffer
  "k" 'tabspaces-close-workspace)

工作流

每一个工作空间都可以有布局

(message "Load init-persp done...")
(provide 'init-persp)

4.12. init-programming.el

4.12.1. init-programming.el c++

4.12.1.1. init-programming.el 代码编写 eglot

在我的学习文章提到过(模块列表)

使用 eglot 来编写、运行和调试 C/C++ 代码

安装 mysys2

1.下载 https://www.msys2.org/

2.安装 C/C++ 必要的软件工具

#安装c++必要软件 gcc gdb make clang clang-tool
pacman -Syu
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-gdb make mingw-w64-x86_64-clang mingw-w64-x86_64-clang-tools-extra

3.设置环境变量

d:\msys64\mingw64\bin
d:\msys64\usr\bin

安装 eglot(Emacs 29 自带 eglot)

;;; c++ eglot
(require 'eglot)
(add-to-list 'eglot-server-programs '((c++-mode c-mode) "clangd"))
(add-hook 'c-mode-hook #'eglot-ensure)
(add-hook 'c++-mode-hook #'eglot-ensure)

测试一段c++代码

~/test.cpp 在emacs菜单栏flymake可以列出所有问题

一键运行代码

1.安装 quickrun:

可以运行任意一个单文件程序

;; 运行任意单文件程序 快捷键F5
(use-package quickrun
  :ensure t
  :commands (quickrun)
  :init
  (quickrun-add-command "c++/c1z"
    '((:command . "g++")
      (:exec . ("%c -std=c++1z %o -o %e %s"
                "%e %a"))
      (:remove . ("%e")))
    :default "c++"))
(global-set-key (kbd "<f5>") 'quickrun)

使用 gdb 调试

  1. 运行 M-x compile, 输入 g++ -g -o test.o test.cpp #-g编译时生成调试信息
  2. 使用 M-x gud-gdb 输入 gdb ./test.o
  3. 常用调试命令
name function
list 显示源代码
break 新增断点, break main, break 12(行号)
info 查看断点或者局部变量信息 info breakpoints, info locals
run 开始调试
next 类似 step over
step 跳转到函数内部
continue 继续运行到下一个断点
quit 退出调试
watch 内存断点
display 类似 IDE 里面的 watch 功能
break 11 if xxx 条件断点
  • list 显示源代码
  • break 11 在第11行打断点
  • break 12 在第12行打断点.
    • 如果有函数名add,也可以给函数下断点 b add;
    • 条件断点 b 20 if i == 2500 在第20行i等于时加断点
  • info breakpoints 查看断点信息
  • run 调试,程序停在第一个断点位置
  • print myVec的值
  • next下一个
  • print myVec的值,发生变化
  • continue 运行到下一个断点
(gdb) list
1	#include<iostream>
2	#include<iterator>
3	#include<vector>
4	
5	using namespace std;
6	
7	int main()
8	{
9	  vector<int> myVec;
10	  myVec.push_back(2);
(gdb) 
11	  myVec.push_back(3);
12	  cout << "Hello World"<<endl;
13	  
14	  cout <<"vect size: "<<myVec.size() <<endl;
15	  return 0;
16	}
(gdb) break 11
Breakpoint 1 at 0x140001483: file test.cpp, line 11.
(gdb) break 12
Breakpoint 2 at 0x14000149a: file test.cpp, line 12.
(gdb) info breakpoints
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000140001483 in main() at test.cpp:11
2       breakpoint     keep y   0x000000014000149a in main() at test.cpp:12
(gdb) run
Starting program: d:\tmp\emacs\test.o 
[New Thread 7812.0x4d34]
[New Thread 7812.0x4864]
[New Thread 7812.0x3bdc]

Thread 1 hit Breakpoint 1, main () at test.cpp:11
11	  myVec.push_back(3);
(gdb) print myVec
$1 = std::vector of length 1, capacity 1 = {2}
(gdb) next

Thread 1 hit Breakpoint 2, main () at test.cpp:12
12	  cout << "Hello World"<<endl;
(gdb) print myVec
$2 = std::vector of length 2, capacity 2 = {2, 3}
(gdb) continue
Continuing.
Hello World
vect size: 2
[Thread 7812.0x4d34 exited with code 0]
[Thread 7812.0x4864 exited with code 0]
[Thread 7812.0x3bdc exited with code 0]
[Inferior 1 (process 7812) exited normally]
(gdb) 

使用 Treesit + eglot 来打造现代编程 IDE

Emacs29新特性介绍:https://emacs.liujiacai.net/post/031-emacs-29-feature-preview/

4.12.1.2. init-programming.el 语法高亮 treesit-auto

在我的学习文章提到过(模块列表)

https://github.com/renzmann/treesit-auto

语法高亮, *ts-mode , 性能要比原生的好。

;;; treesit-auto
;; 语法高亮, *ts-mode , M-x treesit-auto-install-all
(use-package treesit-auto
  :ensure t
  :demand t
  :config
  (setq treesit-auto-install 'prompt)
  (global-treesit-auto-mode))

安装完之后运行 M-x treesit-auto-install-all , 它自动下载编译好的各种语言 .dll 文件, 文件安装在 ~/.emacs.d/tree-sitter

重启emacs,打开一个c++程序,查看当前的主模式 C-h v major=mode 可以看到是 c++-ts-mode ,而不是原先的 c++-mode

默认情况下,treesit会对所有语言做映射 *ts.mode, 如果需要指定某些语言不映射,可这样写

(use-package treesit-auto
  :ensure t
  :demand t
  :config
  (progn
  (setq treesit-font-lock-level 4) ; 任意能高亮的都高亮
  (add-to-list 'major-mode-remap-alist '(c++-mode . c++-ts-mode)) ;指定不映射
  )
  :config
  (global-treesit-auto-mode)
  )
4.12.1.3. init-programming.el 跳转函数列表 consult-imenu

在学习章节提到过(使用 Treesit + eglot 来打造现代编程 IDE)

(+general-global-menu! "search" "s"
  "j" 'consult-imenu
  "p" 'consult-ripgrep
  "k" 'consult-keep-lines
  "f" 'consult-focus-lines)

上面配置在我的emacs模块配置(=init-keybindings.el= 键位绑定 =general=)

修改了j对应自定义了my/imenu函数(j-consult.el 库)

范例:跳转函数列表操作

  • SPC s j 跳转函数列表
4.12.1.4. init-programming.el 查找定义和引用

vim 模式是

  • gd 跳转函数定义
  • gr 查找函数引用
  • C-o 返回

使用 ctrl-o 返回

(global-definer
  "gg" 'xref-find-definitions
  "gr" 'xref-find-references
4.12.1.5. init-programming.el 添加 snippets 支持
(use-package yasnippet
  :ensure t
  :hook ((prog-mode . yas-minor-mode)
         (org-mode . yas-minor-mode))
  :init
  :config
  (progn
    (setq hippie-expand-try-functions-list
          '(yas/hippie-try-expand
            try-complete-file-name-partially
            try-expand-all-abbrevs
            try-expand-dabbrev
            try-expand-dabbrev-all-buffers
            try-expand-dabbrev-from-kill
            try-complete-lisp-symbol-partially
            try-complete-lisp-symbol))))

(use-package yasnippet-snippets
  :ensure t
  :after yasnippet)

自定义代码片段

  • M-x yas-new-snippet 创建新代码片段

    # -*- mode: snippet -*-
    # name: <name>  指Snippet的名称
    # key: <key>  指Snippet的名称
    # contributor : Jasper Hsu <[email protected]>  作者信息
    # --
    `(message "hello from my yasnippet")`$0
    Hello ${1:world)
    

    $0 光标位置, $1..n 使用tab, shift-tab切换占位符位置,可以设置默认值如 ${1:aa}

    `()` 可以写lisp语法

  • C-c C-c=完成编程,选择片段生效的模式,保存指定位置 =~/.emacs.d/snippets/<xxx>-mode/<name>
  • M-x yas-describe-tables 查看
  • M-x yas-visit-snippet-file 编辑
4.12.1.6. init-programming.el 在头文件和源文件之间进行跳转

ff-find-related-file

(message "Load init-programming done...")
(provide 'init-programming)

4.13. init-keybindings.el

4.13.1. init-keybindings.el 文件搜索

配置划分成不同文件,好以后方式方便查找

;;; config
;;; 文件搜索
(global-set-key (kbd "C-c p f") 'project-find-file) ;;   查找文件,默认绑定在 C-x p f
(global-set-key (kbd "C-c p s") 'consult-ripgrep)  ;;  查找文件内容

C-c 是给用户定义的快捷键,p 代表 project , f 代表 find-file

consult-ripgrep 需要安装 consult 和 ripgrep , 方便查找关键字

操作:快速找到文件

  • C-c p f 选择目标目录, 回车 如 ~/.emacs.d
  • 输入要查找文件名对应的关键字符,即可打开文件。 如打开 ~/.emacs.d/lisp/init-basic.el 文件, 输入basic即可

4.13.2. init-keybindings.el 键位绑定 general

在我的学习文章提到过(模块列表)

general插件中global-definer在init-basic.el中定义(init-basic.el general)

(use-package general
  :init
  ;; global-definer 对应leader键为空格 SPC
  (global-definer
    "!" 'shell-command                ; 空格+! SPC ! 进入shell命令行
    "SPC" 'execute-extended-command   ; 按2下空格SPC-SPC就可以实现 M-x 的效果
    "TAB" 'spacemacs/alternate-buffer ; SPC TAB 在最近的2个buffer之间切换
    "'" 'vertico-repeat
    "+" 'text-scale-increase   ; SPC + 放大,相当于C-x C-+
    "-" 'text-scale-decrease   ; SPC - 缩小
    "u" 'universal-argument    ; SPC u 相当于emacs的C-u 
    "hdf" 'describe-function   ; SPC hdf 相当于emacs的C-h f
    "hdv" 'describe-variable   ; SPC hdv 相当于emacs的C-h v
    "hdk" 'describe-key        ; SPC hdk 相当于emacs的C-h k
    ;; 查看自定义函数库j-highlight-global, j-expand-region
    "hh" 'my/highlight-dwim    ; SPC hh 高亮选中区域并标上颜色
    "hc" 'my/clearn-highlight  ; SPC hc 清除高亮颜色
    ;; 切换窗口
    "v" 'er/expand-region      ; SPC v 扩大区域
    "0" 'select-window-0       ; SPC 0 切0号窗口
    "1" 'select-window-1       ; SPC 1 切1号窗口
    "2" 'select-window-2
    "3" 'select-window-3
    "4" 'select-window-4
    "5" 'select-window-5
    ;; 函数定义、引用跳转
    "gg" 'xref-find-definitions
    "gr" 'xref-find-references
    )
  ;; 子菜单
  ;;(+general-global-menu! "<name>" "<keyword>" 
  ;; leader 键为SPC空格,name为描述,子leader键为keyword. 如 SPC b b查看buffer缓冲区

  ;; SPC s 针对函数列表跳转的快捷键
  (+general-global-menu! "search" "s"
    "j" 'my/imenu              ; SPC s j 函数列表跳转(常用)
    "p" 'consult-ripgrep       ; SPC s p 查找文件内容(常用)
    "k" 'consult-keep-lines    ; SPC s k 查找的内容覆盖整个文件
    "f" 'consult-focus-lines)  ; SPC s f 在选中区域,隐藏匹配字符的行. 再次执行恢复显示

  ;; SPC b 针对buffer缓冲区的快捷键
  (+general-global-menu! "buffer" "b"
    "d" 'kill-current-buffer
    "b" '(consult-buffer :which-key "consult buffer") ; SPC b b 查看buffer缓冲区(常用)
    "B" 'switch-to-buffer
    "p" 'previous-buffer
    "R" 'rename-buffer
    "M" '((lambda () (interactive) (switch-to-buffer "*Messages*")) 
          :which-key "messages-buffer") ; SPC b M查看*Message*缓冲区(常用 配合SPC b b快速切换)
    "n" 'next-buffer
    "i" 'ibuffer
    "f" 'my-open-current-directory ; SPC b f 从外部打开当前buffer对应的目录 
    "k" 'kill-buffer
    "y" 'fast/copy-buffer-name
    "K" 'fast/kill-other-buffers)

  (+general-global-menu! "layout" "l"
    "l" 'tabspaces-switch-or-create-workspace           ; SPC l l 切换工作空间(1)
    "L" 'tabspaces-restore-session                      ; SPC l L 恢复工作空间(5)
    "p" 'tabspaces-open-or-create-project-and-workspace ; SPC l p 打开一个目录并创建workspace(3)
    "f" 'tabspaces-project-switch-project-open-file
    "s" 'tabspaces-save-session                         ; SPC l s 保存工作空间(4)
    "B" 'tabspaces-switch-buffer-and-tab
    "b" 'tabspaces-switch-to-buffer
    "R" 'tab-rename                                     ; SPC l R 重命名tab-bar
    "TAB" 'tab-bar-switch-to-recent-tab                 ; SPC l TAB 在两个挨着最近的空间进行切换(2)
    "r" 'tabspaces-remove-current-buffer
    "k" 'tabspaces-close-workspace)

  ;; SPC f 针对文件快捷键
  (+general-global-menu! "file" "f"
    "f" 'find-file               ; SPC f f 查找文件
    "r" 'consult-recent-file     ; SPC f r 列出最近打开过的文件(常用)
    "L" 'consult-locate          ; SPC f L 文件查找
    "d" 'consult-dir
    "ed" 'fast/open-my-init-file
    "s" 'save-buffer             ; SPC f s 保存文件
    "w" 'sudo-edit
    "S" 'save-some-buffers
    "j"  'dired-jump             ; SPC f j 跳到目录
    "y" 'fast/copy-file-name
    "R" 'my/rename-current-buffer-file
    "k" 'my/delete-file-and-buffer
    "!" 'my/exec-shell-on-buffer)

  ;; SPC w 针对窗口的快捷键
  (+general-global-menu! "window" "w"
    "/" 'split-window-right    ; SPC w / 左右分屏
    "-" 'split-window-below    ; SPC w - 上下分屏
    "m" 'delete-other-windows  ; SPC w m 仅保留当前窗口
    "u" 'winner-undo           ; SPC w u 返回布局
    "z" 'winner-redo           ; SPC w z 撤销返回布局。 和返回布局结合实现2个布局间切换
    "w" 'esw/select-window     ; SPC w w 选择分屏方式 >向右分,<向左分,^向上分,v向下分
    "s" 'esw/swap-two-windows  ; SPC w s 交换窗口 
    "d" 'esw/delete-window     ; SPC w d 删除指定窗口
    "=" 'balance-windows-area  ; SPC w = 等宽布局
    "r" 'esw/move-window
    "x" 'resize-window         ; SPC w x 调整窗口大小。如f水平变大;b缩小;n向下扩;p向上扩;
    "H" 'buf-move-left         ; SPC w H 右边窗口左移
    "L" 'buf-move-right        ; SPC w L 窗口右移
    "J" 'buf-move-down         ; SPC w J 窗口下移
    "K" 'buf-move-up)          ; SPC w K 窗口上移

  ;; SPC p 针对项目的快捷键
  (+general-global-menu! "project" "p"
    "f" 'project-find-file    ; SPC p f 在项目中快速找到文件(常用)
    "r" 'consult-recent-file
    "s" 'project-find-regexp
    "d" 'project-dired
    "b" 'consult-project-buffer
    "e" 'project-eshell
    "m" 'my/project-run-makefile-target
    "c" 'project-compile
    "t" 'my/project-citre
    "p" 'project-switch-project
    "i" 'my/project-info
    "a" 'project-remember-projects-under
    "x" 'project-forget-project)
  )

参考配置: https://github.com/zilongshanren/emacs.d/blob/eglot/lisp/init-keybindings.el 实现自己的文件、窗口、项目多种快捷键绑定

(message "Load init-keybindings done...")
(provide 'init-keybindings)

4.14. init-funcs.el

;;(defun open-init-file()
;;  (interactive)
;;  (find-file "~/.emacs.d/init.el"))
;;
;;;; 这一行代码,将函数 open-init-file 绑定到 <f4> 键上
;;(global-set-key (kbd "<f4>") 'open-init-file)


;;; 快捷使用emacs
(use-package j-emacs-fast
  :ensure nil
  :demand t
  :diminish
  :bind 
  ( :map global-map
    ("<f4>" . fast/open-my-init-file) ; 快速打开配置文件
    ))
(message "Load init-funcs done...")
(provide 'init-funcs)

5. 我的自定义库(j-lisp/)

一个自定义库会对某一个软件包做扩展,软件包配置文件会分别引用它们。

5.1. j-simple.el

;;; j-simple.el --- Extensions for Consult -*- lexical-binding: t -*-

;; Copyright (C) 2024  Jasper Hsu

;; Author: Jasper Hsu <[email protected]>
;; URL: https://xuchangwei.com/lisp/jasper-emacs.html
;; Version: 0.1.0
;; Package-Requires: ((emacs "30.1"))

;; This file is NOT part of GNU Emacs.

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or (at
;; your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; Remember that every piece of Elisp that I write is for my own
;; educational and recreational purposes.  I do not recommend that
;; you copy any of this if you are not certain of what it does.

;;; Code:

;;; Commands

;;;; General commands

(defun j-simple--mark (bounds)
  "Mark between BOUNDS as a cons cell of beginning and end positions."
  (push-mark (car bounds))
  (goto-char (cdr bounds))
  (activate-mark))

;;; autoload这是一个文档字符串的前缀,表示这个函数可以通过 Emacs 的自动加载机制被加载。当用户在 Emacs 命令行中输入函数名时,Emacs 会尝试自动加载这个函数的定义。
;;;###autoload
(defun j-simple-mark-sexp ()
  "Mark symbolic expression at or near point.
Repeat to extend the region forward to the next symbolic
expression. 选中区域"
  (interactive)
  (if (and (region-active-p)
           (eq last-command this-command))
      (ignore-errors (forward-sexp 1))
    (when-let ((thing (cond
                       ((thing-at-point 'url) 'url)
                       ((thing-at-point 'sexp) 'sexp)
                       ((thing-at-point 'string) 'string)
                       ((thing-at-point 'word) 'word))))
      (j-simple--mark (bounds-of-thing-at-point thing)))))

;;;###autoload
(defun j-simple-keyboard-quit-dwim ()
  "Do-What-I-Mean behaviour for a general `keyboard-quit'.

The generic `keyboard-quit' does not do the expected thing when
the minibuffer is open.  Whereas we want it to close the
minibuffer, even without explicitly focusing it.

The DWIM behaviour of this command is as follows:

- When the region is active, disable it.
- When a minibuffer is open, but not focused, close the minibuffer.
- When the Completions buffer is selected, close it.
- In every other case use the regular `keyboard-quit'."
  (interactive)
  (cond
   ((region-active-p)
    (keyboard-quit))
   ((derived-mode-p 'completion-list-mode)
    (delete-completion-window))
   ((> (minibuffer-depth) 0)
    (abort-recursive-edit))
   (t
    (keyboard-quit))))

(provide 'j-simple)
;;; j-simple.el ends here

5.2. j-consult.el

;;; j-consult.el --- Extensions for Consult -*- lexical-binding: t -*-

;; Copyright (C) 2024  Jasper Hsu

;; Author: Jasper Hsu <[email protected]>
;; URL: https://xuchangwei.com/lisp/jasper-emacs.html
;; Version: 0.1.0
;; Package-Requires: ((emacs "30.1"))

;; This file is NOT part of GNU Emacs.

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or (at
;; your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; Remember that every piece of Elisp that I write is for my own
;; educational and recreational purposes.  I do not recommend that
;; you copy any of this if you are not certain of what it does.

;;; Code:

;;;; 配置搜索中文文件

(require 'consult)

;; PC提前安装 everyting 及其客户端ES. 利用M-x consult-locate搜索
;; 如 #学习#.txt
;; (progn
;;   (setq consult-locate-args (encode-coding-string "es.exe -i -p -r" 'gbk))
;;   (add-to-list 'process-coding-system-alist '("es" gbk . gbk))
;;   )
  (if (eq system-type 'windows-nt)
      (progn
        (add-to-list 'process-coding-system-alist '("es" gbk . gbk))
        (add-to-list 'process-coding-system-alist '("explorer" gbk . gbk))
        (setq consult-locate-args (encode-coding-string "es.exe -i -p -r" 'gbk))))

;; make consult-ripgrep work 有时consult-ripgrep搜索不了
(cond
 ;; macOS
 ((eq system-type 'darwin)
  "afplay")
 ;; Windows
 ((eq system-type 'windows-nt)
  (add-to-list 'process-coding-system-alist 
               '("[rR][gG]" . (utf-8-dos . windows-1251-dos)))
  )
 (t
  "Nothing"))

;;;; 函数定义跳转
(defun my/imenu ()
  "跳转函数列表,当模式为org-mode跳转标题列表"
  (interactive)
  (if (eq major-mode #'org-mode)
      (call-interactively #'consult-org-heading)
    (call-interactively #'consult-imenu)))

(provide 'j-consult)
;;; j-consult.el ends here

5.3. j-embark.el

我的emacs模块配置(init-completion.el minibuffer增强 embark)

;;; j-embark.el --- Extensions for Embark -*- lexical-binding: t -*-

;; Copyright (C) 2024  Jasper Hsu

;; Author: Jasper Hsu <[email protected]>
;; URL: https://xuchangwei.com/lisp/jasper-emacs.html
;; Version: 0.1.0
;; Package-Requires: ((emacs "30.1"))

;; This file is NOT part of GNU Emacs.

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or (at
;; your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; Remember that every piece of Elisp that I write is for my own
;; educational and recreational purposes.  I do not recommend that
;; you copy any of this if you are not certain of what it does.

;;; Code:

(require 'embark)


;;;; 批量改写; 配合query-replace-regexp实现文本替换
;; 范例:批量替换操作
;; - ~M-x consult-ripgrep~  默认会在以 git 为根目录搜索。如 =#hello= ,搜索包含hello字符的文件。 ~C-n/C-p~ 下上搜索同时可以预览。
;; - ~C-c C-e~ 打开写
;; - ~M-x query-replace-regexp~ 输入hello 回车, 替换为hello 回车。 按 y 同意当前行替换,n 不同意修改
;; - ~C-c C-c~ 执行替换,按 q 退出
;; 
;; 范例:当前buffer替换操作
;; - =C-s= 搜索, 输入hello
;; - =C-c C-e= 编辑
;; - ~M-x query-replace-regexp~ 输入要替换的内容
;; - ~C-c C-c~ 执行替换,按 q 退出
(defun my/embark-export-write()
 "Export the current vertico results to a writable buffer if
possible.Supports exporting
consult-grep to wgrep, file to
wdeired, and consult-location to occur-edit."
  (interactive)
  (require 'embark)
  (require 'wgrep)
  (pcase-let ((`(,type . ,candidates)
               (run-hook-with-args-until-success 'embark-candidate-collectors)))
    (pcase type
      ('consult-grep (let ((embark-after-export-hook #'wgrep-change-to-wgrep-mode))
                       (embark-export)))
      ('file (let ((embark-after-export-hook #'wdired-change-to-wdired-mode))
               (embark-export)))
      ('consult-location (let ((embark-after-export-hook #'occur-edit-mode))
                           (embark-export)))
      (x (user-error "embark category %S doesn't support writable export" x)))))



;;;; 使用 Emacs 打开windows文件管理器
;; 函数在embark-file-map 映射为E
;; (define-key embark-file-map (kbd "E") #'consult-directory-externally)
;; 范例: 快速打开文件所在目录
;; - =M-x my/consult-directory-externally= 输入文件路径,如 =~/.emacs.d/auto-save-list/.saves-2000-JASPER~=
;; - =C-x C-f= 选择文件时,可以用embark来打开, =C;= 选择E,用文件管理器打开对应目录的
(defun my/consult-directory-externally (file)
  "Open FILE externally using the default application of the system."
  (interactive "fOpen externally: ")
  (if (and (eq system-type 'windows-nt)
           (fboundp 'w32-shell-execute))
      (shell-command-to-string (encode-coding-string (replace-regexp-in-string "/" "\\\\\\\\"
      ;;(shell-command-to-string (encode-coding-string (replace-regexp-in-string "/" "\\\\"
            (format "explorer.exe %s" (file-name-directory (expand-file-name file)))) 'gbk))
    (call-process (pcase system-type
                    ('darwin "open")
                    ('cygwin "cygstart")
                    (_ "xdg-open"))
                  nil 0 nil
                  (file-name-directory (expand-file-name file)))))

;;打开当前文件的目录
(defun my/open-current-directory ()
  (interactive)
  (consult-directory-externally default-directory))

(provide 'j-embark)
;;; j-embark.el ends here

5.4. j-expand-region.el

;;; j-expand-region.el --- Extensions for expand-region -*- lexical-binding: t -*-

;; Copyright (C) 2024  Jasper Hsu

;; Author: Jasper Hsu <[email protected]>
;; URL: https://xuchangwei.com/lisp/jasper-emacs.html
;; Version: 0.1.0
;; Package-Requires: ((emacs "30.1"))

;; This file is NOT part of GNU Emacs.

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or (at
;; your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; Remember that every piece of Elisp that I write is for my own
;; educational and recreational purposes.  I do not recommend that
;; you copy any of this if you are not certain of what it does.

;;; Code:

(require 'expand-region)

;;;###autoload
(defun my/search-project-for-symbol-at-point ()
  "查找当前项目(git项目)目录中匹配选中字符串的文件"
  (interactive)
  (if (use-region-p)
      (progn
        (consult-ripgrep (project-root (project-current))
                         (buffer-substring (region-beginning) (region-end))))))

(provide 'j-expand-region)
;;; j-expand-region.el ends here

5.5. j-evil.el

;;; j-evil.el --- Extensions for evil -*- lexical-binding: t -*-

;; Copyright (C) 2024  Jasper Hsu

;; Author: Jasper Hsu <[email protected]>
;; URL: https://xuchangwei.com/lisp/jasper-emacs.html
;; Version: 0.1.0
;; Package-Requires: ((emacs "30.1"))

;; This file is NOT part of GNU Emacs.

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or (at
;; your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; Remember that every piece of Elisp that I write is for my own
;; educational and recreational purposes.  I do not recommend that
;; you copy any of this if you are not certain of what it does.

;;; Code:

;;;###autoload
(defun my/evil-quick-replace (beg end )
  "交互式替换文本"
  (interactive "r")
  (when (evil-visual-state-p)
    (evil-exit-visual-state)
    (let ((selection (regexp-quote (buffer-substring-no-properties beg end))))
      (setq command-string (format "%%s /%s//g" selection))
      (minibuffer-with-setup-hook
          (lambda () (backward-char 2))
        (evil-ex command-string)))))

;;(define-key evil-visual-state-map (kbd "C-r") 'my/evil-quick-replace)

(provide 'j-evil)
;;; j-evil.el ends here

5.6. j-highlight-global.el

在我的学习文章提到过(模块列表)

;;; j-highlight-global.el --- Extensions for highlight-global -*- lexical-binding: t -*-

;; Copyright (C) 2024  Jasper Hsu

;; Author: Jasper Hsu <[email protected]>
;; URL: https://xuchangwei.com/lisp/jasper-emacs.html
;; Version: 0.1.0
;; Package-Requires: ((emacs "30.1"))

;; This file is NOT part of GNU Emacs.

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or (at
;; your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; Remember that every piece of Elisp that I write is for my own
;; educational and recreational purposes.  I do not recommend that
;; you copy any of this if you are not certain of what it does.

;;; Code:

;;; 选中文本高亮,general插件中做了键位绑定

;;;###autoload
(defun my/highlight-dwim ()
  "do what i mean 如果选中区域调用highlight-frame-toggle高亮,
没有则调用symbol-overlay高亮"
  (interactive)
  (if (use-region-p)
      (progn
        (highlight-frame-toggle)
        (deactivate-mark))
    (symbol-overlay-put)))

;;;###autoload
(defun my/clearn-highlight ()
  (interactive)
  (clear-highlight-frame)
  (symbol-overlay-remove-all))

(provide 'j-highlight-global)
;;; j-highlight-global.el ends here

5.7. j-org-download.el

;;; j-org-download.el --- Extensions for org -*- lexical-binding: t -*-

;; Copyright (C) 2024  Jasper Hsu

;; Author: Jasper Hsu <[email protected]>
;; URL: https://xuchangwei.com/lisp/jasper-emacs.html
;; Version: 0.1.0
;; Package-Requires: ((emacs "30.1"))

;; This file is NOT part of GNU Emacs.

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or (at
;; your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; Remember that every piece of Elisp that I write is for my own
;; educational and recreational purposes.  I do not recommend that
;; you copy any of this if you are not certain of what it does.

;;; Code:

;;;; 截图
;; 将剪切板中内容粘贴到org文件中

;;;###autoload
(defun my/org-insert-clipboard-image (width)
  "保存目录 `foldername'
图片名称 `imgName'
图片显示与关闭 `org-redisplay-inline-images'
图片的质量跟是否采用压缩有关"
  (interactive (list
                (read-string (format "Input image width, default is 800: ")
                             nil nil "800")))
  ;; 设置图片存放的文件夹位置为 `当前Org文件同名.assets'
  (setq foldername "./images/")
  (if (not (file-exists-p foldername))
      (mkdir foldername))
  ;; 设置图片的文件名,格式为 `img_年月日_时分秒.png'
  (setq srcImgName (concat "img_" (format-time-string "%Y%m%d_%H%M%S") "_src.png"))
  (setq imgName (concat "img_" (format-time-string "%Y%m%d_%H%M%S") ".png"))
  ;; 图片文件的相对路径
  (setq srcRelativeFilename (concat "./images/" srcImgName))
  (setq relativeFilename (concat "./images/" imgName))
  ;; 根据不同的操作系统设置不同的命令行工具
  (cond ((string-equal system-type "gnu/linux")
         (shell-command (concat "xclip -selection clipboard -t image/png -o > " relativeFilename)))
        ((string-equal system-type "darwin")
         (shell-command (concat "pngpaste " relativeFilename)))
        ((string-equal system-type "windows-nt")
         ;; 原 (shell-command (concat "powershell -command \"Add-Type -AssemblyName System.Windows.Forms;if ($([System.Windows.Forms.Clipboard]::ContainsImage())) {$image = [System.Windows.Forms.Clipboard]::GetImage();[System.Drawing.Bitmap]$image.Save('"relativeFilename "',[System.Drawing.Imaging.ImageFormat]::Png); Write-Output 'clipboard content saved as file'} else {Write-Output 'clipboard does not contain image data'}\""))))
         ;; 简单压缩 (shell-command (concat "powershell -command \"Add-Type -AssemblyName System.Windows.Forms; if ($([System.Windows.Forms.Clipboard]::ContainsImage())) { $image = [System.Windows.Forms.Clipboard]::GetImage(); $image.Save('"srcRelativeFilename "', [System.Drawing.Imaging.ImageFormat]::Png); Write-Output 'Clipboard content saved as file'; & ffmpeg -i "srcRelativeFilename " -q:v 4 -loglevel quiet -y "relativeFilename " } else { Write-Output 'Clipboard does not contain image data' }\""))))
         ;; (shell-command (concat "powershell -command \"Add-Type -AssemblyName System.Windows.Forms; if ($([System.Windows.Forms.Clipboard]::ContainsImage())) { $image = [System.Windows.Forms.Clipboard]::GetImage(); $image.Save('"srcRelativeFilename "', [System.Drawing.Imaging.ImageFormat]::Png); Write-Output 'Clipboard content saved as file'; & ffmpeg -i "srcRelativeFilename " -q:v 4 -loglevel quiet -y "relativeFilename " ;& Remove-Item -Path "srcRelativeFilename" -Force} else { Write-Output 'Clipboard does not contain image data' }\""))))
         ;;调色板压缩 (shell-command (concat "powershell -command \"Add-Type -AssemblyName System.Windows.Forms; if ($([System.Windows.Forms.Clipboard]::ContainsImage())) { $image = [System.Windows.Forms.Clipboard]::GetImage(); $image.Save('"srcRelativeFilename "', [System.Drawing.Imaging.ImageFormat]::Png); Write-Output 'Clipboard content saved as file'; ffmpeg -i "srcRelativeFilename " -vf 'palettegen=max_colors=256:stats_mode=single' -loglevel quiet -y  out_3.png ; ffmpeg -i "srcRelativeFilename " -i out_3.png -lavfi '[0][1:v] paletteuse' -pix_fmt pal8 -loglevel quiet -y "relativeFilename "} else { Write-Output 'Clipboard does not contain image data' }\""))))
         (shell-command (concat "powershell -command \"Add-Type -AssemblyName System.Windows.Forms; if ($([System.Windows.Forms.Clipboard]::ContainsImage())) { $image = [System.Windows.Forms.Clipboard]::GetImage(); $image.Save('"srcRelativeFilename "', [System.Drawing.Imaging.ImageFormat]::Png); Write-Output 'Clipboard content saved as file'; ffmpeg -i "srcRelativeFilename " -vf 'palettegen=max_colors=256:stats_mode=single' -loglevel quiet -y  out_3.png ; ffmpeg -i "srcRelativeFilename " -i out_3.png -lavfi '[0][1:v] paletteuse' -pix_fmt pal8 -loglevel quiet -y "relativeFilename "; Remove-Item -Path "srcRelativeFilename" -Force ; Remove-Item -Path out_3.png  -Force} else { Write-Output 'Clipboard does not contain image data' }\""))))
  ;; 给粘贴好的图片链接加上宽度属性,方便导出
  (insert (concat "\n#+DOWNLOADED: screenshot @ "
                  (format-time-string "%Y-%m-%d %a %H:%M:%S" (current-time))
                  "\n#+CAPTION: \n#+ATTR_ORG: :width "
                  width
                  "\n#+ATTR_LATEX: :width "
                  (if (>= (/ (string-to-number width) 800.0) 1.0)
                      "1.0"
                    (number-to-string (/ (string-to-number width) 800.0)))
                  "\\linewidth :float nil\n"
                  "#+ATTR_HTML: :width "
                  width
                  "\n[[file:" relativeFilename "]]\n"))
  ;; 重新显示一下图片
  (org-redisplay-inline-images)
  (message "OKKKKKK")
  )

;;(global-set-key (kbd "<f2>") 'my/org-insert-clipboard-image)


(provide 'j-org-download)
;;; j-org-download.el ends here

5.8. j-org.el

;;; j-org.el --- Extensions for org -*- lexical-binding: t -*-

;; Copyright (C) 2024  Jasper Hsu

;; Author: Jasper Hsu <[email protected]>
;; URL: https://xuchangwei.com/lisp/jasper-emacs.html
;; Version: 0.1.0
;; Package-Requires: ((emacs "30.1"))

;; This file is NOT part of GNU Emacs.

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or (at
;; your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; Remember that every piece of Elisp that I write is for my own
;; educational and recreational purposes.  I do not recommend that
;; you copy any of this if you are not certain of what it does.

;;; Code:

;;;; org-export

(declare-function org-html-export-as-html "org")
(declare-function org-texinfo-export-to-info "org")

;;;###autoload
(defun j-org-ox-html ()
  "导出为html
Streamline HTML export."
  (interactive)
  (org-html-export-as-html nil nil nil t nil))

;;;###autoload
(defun j-org-ox-texinfo ()
  "导出Info
Streamline Info export."
  (interactive)
  (org-texinfo-export-to-info))

;;;; org-id

(declare-function org-id-add-location "org")
(declare-function org-with-point-at "org")
(declare-function org-entry-get "org")
(declare-function org-id-new "org")
(declare-function org-entry-put "org")

;; Original idea:
;; <https://writequit.org/articles/emacs-org-mode-generate-ids.html>.
(defun j-org--id-get ()
  "获取当前的CUSTOM_ID
Get the CUSTOM_ID of the current entry.
If the entry already has a CUSTOM_ID, return it as-is, else
create a new one."
  (let* ((pos (point))
         (id (org-entry-get pos "CUSTOM_ID")))
    (if (and id (stringp id) (string-match-p "\\S-" id))
        id
      (setq id (org-id-new "h"))
      (org-entry-put pos "CUSTOM_ID" id)
      id)))

(declare-function org-map-entries "org")

;;;###autoload
(defun j-org-id-headlines ()
  "添加CUSTOM_ID
Add missing CUSTOM_ID to all headlines in current file."
  (interactive)
  (org-map-entries
   (lambda () (j-org--id-get))))

;;;###autoload
(defun j-org-id-headline ()
  "Add missing CUSTOM_ID to headline at point."
  (interactive)
  (j-org--id-get))

(provide 'j-org)
;;; j-org.el ends here

5.9. j-emacs-fast.el

;;; j-emacs-fast.el --- Extensions for org -*- lexical-binding: t -*-

;; Copyright (C) 2024  Jasper Hsu

;; Author: Jasper Hsu <[email protected]>
;; URL: https://xuchangwei.com/lisp/jasper-emacs.html
;; Version: 0.1.0
;; Package-Requires: ((emacs "30.1"))

;; This file is NOT part of GNU Emacs.

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or (at
;; your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; Remember that every piece of Elisp that I write is for my own
;; educational and recreational purposes.  I do not recommend that
;; you copy any of this if you are not certain of what it does.

;;; Code:

;;;; 放置一些快捷使用emacs函数

;;; Dos2Unix/Unix2Dos
(defun fast/dos2unix ()
  "Convert the current buffer to UNIX file format."
  (interactive)
  (set-buffer-file-coding-system 'undecided-unix nil))

(defun fast/unix2dos ()
  "Convert the current buffer to DOS file format."
  (interactive)
  (set-buffer-file-coding-system 'undecided-dos nil))


;;; 文件操作
(defun fast/open-my-init-file()
  "快速打开主配置文件"
  (interactive)
  (find-file (expand-file-name "init.el" user-emacs-directory )))

(defun fast/copy-file-name ()
  "复制文件绝对路径,使用 =C-y= 粘贴
Copy the current buffer file name to the clipboard."
  (interactive)
  (if-let ((filename (if (equal major-mode 'dired-mode)
                         default-directory
                       (buffer-file-name))))
      (progn
        (kill-new filename)
        (message "Copied '%s'" filename))
    (warn "Current buffer is not attached to a file!")))

;;; buffer缓冲区
(defun fast/copy-buffer-name ()
  "复制缓冲区名
Copy name of the current buffer."
  (interactive)
  (kill-new (buffer-name)))

(defun fast/kill-other-buffers ()
  "杀掉其他缓冲区
Kill all other buffers."
  (interactive)
  (mapc 'kill-buffer
        (delq (current-buffer)
              (cl-remove-if-not 'buffer-file-name (buffer-list)))))

;;; 返回上一个buffer
(defun spacemacs/alternate-buffer (&optional window)
  "Switch back and forth between current and last buffer in the
current window.
If `spacemacs-layouts-restrict-spc-tab' is `t' then this only switches between
the current layouts buffers."
  (interactive)
  (cl-destructuring-bind (buf start pos)
      (if (bound-and-true-p spacemacs-layouts-restrict-spc-tab)
          (let ((buffer-list (persp-buffer-list))
                (my-buffer (window-buffer window)))
            ;; find buffer of the same persp in window
            (seq-find (lambda (it) ;; predicate
                        (and (not (eq (car it) my-buffer))
                             (member (car it) buffer-list)))
                      (window-prev-buffers)
                      ;; default if found none
                      (list nil nil nil)))
        (or (cl-find (window-buffer window) (window-prev-buffers)
                     :key #'car :test-not #'eq)
            (list (other-buffer) nil nil)))
    (if (not buf)
        (message "Last buffer not found.")
      (set-window-buffer-start-and-point window buf start pos))))

(provide 'j-emacs-fast)
;;; j-emacs-fast.el ends here

6. Emacs 配置问题排查

配置最好用git管理起来。

6.1. 配置有问题,启动不了怎么办?

emacs-debug –init

6.2. 不能安装包,提示签名错误

Failed to verify signature archive-contents.sig

(setq package-check-signature nil)

6.3. 包解压失败,

a pax extended header, 包解压失败,可以使用 use-package :vc 直接安装

使用 mysys2 的时候,系统会使用 unix 的 tar 去解压会有问题 windows 上面需要使用 system32/tar 这个程序去解压。

解决办法可以是

mv /usr/bin/tar /usr/bin/tarbak

7. 优化性能,借鉴其他人的配置

how to Profile performace

7.1. 运行时性能

  • M-x profiler-start 选择cpu+mem
  • 复现卡的操作。比如光标移动卡顿,反复重现
  • M-x profiler-report 打开报告,分析

7.2. 度量启动性能

(use-package benchmark-init
  :ensure t
  :demand t
  :config
  ;; To disable collection of benchmark data after init is done.
  (add-hook 'after-init-hook 'benchmark-init/deactivate))
  • benchmark-init/show-durations-tree 树状展示耗时
  • benchmark-init/show-durations-tabulated 排序展示, 鼠标点击字段进行排序。 分析

启动时间

(defun efs/display-startup-time ()
  (message "启动加载时间Emacs loaded in %s with %d garbage collections."
           (format "%.2f seconds"
                   (float-time
                   (time-subtract after-init-time before-init-time)))
           gcs-done))

(add-hook 'emacs-startup-hook #'efs/display-startup-time)

减小gc次数

  • 在开始加载时gc设置大些,加载完成后gc调小
;;; beginning of init.el
;; The default is 800 kilobytes.  Measured in bytes.
(setq gc-cons-threshold (* 50 1000 1000))

;; ................

;;; end of init.el
;; Make gc pauses faster by decreasing the threshold.
(setq gc-cons-threshold (* 2 1000 1000))

不要加载任何包裹!

use-package 哪些操作可以优化性能, 几种不同的方法来延迟包加载:

  • :hook - 首次调用其中一个钩子时,将加载包
  • :bind - 首次使用其中一个键绑定时,将加载包
  • :commands - 当使用其中一个命令时,将加载包
  • :mode - 首次打开具有特定扩展名的文件时,将加载包
  • :after - 在其他特定包加载后加载此包
  • :defer - 如果您不使用任何其他选项,则此选项将延迟加载到启动后

https://systemcrafters.net/emacs-from-scratch/cut-start-up-time-in-half/

7.4. 小技巧:

;;; 返回上一个buffer
(defun spacemacs/alternate-buffer (&optional window)
  "Switch back and forth between current and last buffer in the
current window.
If `spacemacs-layouts-restrict-spc-tab' is `t' then this only switches between
the current layouts buffers."
  (interactive)
  (cl-destructuring-bind (buf start pos)
      (if (bound-and-true-p spacemacs-layouts-restrict-spc-tab)
          (let ((buffer-list (persp-buffer-list))
                (my-buffer (window-buffer window)))
            ;; find buffer of the same persp in window
            (seq-find (lambda (it) ;; predicate
                        (and (not (eq (car it) my-buffer))
                             (member (car it) buffer-list)))
                      (window-prev-buffers)
                      ;; default if found none
                      (list nil nil nil)))
        (or (cl-find (window-buffer window) (window-prev-buffers)
                     :key #'car :test-not #'eq)
            (list (other-buffer) nil nil)))
    (if (not buf)
        (message "Last buffer not found.")
      (set-window-buffer-start-and-point window buf start pos))))

按键绑定:

"TAB" 'spacemacs/alternate-buffer