已放弃自己配置,现已全面拥抱doom emacs

emacs 操作

光标移动

下面的C指键盘的Ctrl键,M指键盘的Alt键, <RET>指回车键,DEL指退格键

下一行:C-n

上一行:C-p

下一个字符:C-f

前一个字符:C-b

下一个词:M-f

上一个词:M-b

将光标所在行至于屏幕中间:C-l

向下滚动一屏:C-v

向上滚动一屏:M-v

光标置于行首:C-a

光标置于行尾:C-e

光标置于行首的第一个字符: M-m

光标置于段首:M-a

光标置于段㞑:M-e

M-<移动光标到最前端

M->移动光标到最末端

移动光标到指定行:M-g M-g 行号


复制、移除、召回、删除、撤销

emacs里面的剪切称为“移除”,粘贴称为“召回(yanking)”

移除:C-w

复制:M-w

召回:C-y

C-d删除光标后的一个字符

M-d删除光标后的一个单词

C-k移除从光标到行尾的字符

M-k移除从光标到句尾的字符

使用curx包,C-c k删除当前行
使用hungry-delete包,C-c DEL向前删除多行空白,C-c d向后删除多行空白

选中一个区域删除:

C-Space,配合光标移动的快捷键选中一个区域,C-w删除该段
M-h,选中一个段

撤销:C-/


缓冲区、窗口

打开最近文件:C-c f r

保存当前正在编辑的缓冲区:C-x C-s,保存多个缓冲区:C-x s

离开emacs:C-x C-c

在 minibuffer 里列出缓冲区:C-x b

在IBuffer里列出缓冲区:C-x C-b

关闭当前buffer:C-x k

关闭指定buffer: C-x k buffer_name

切换相邻的缓冲区 C-x <LEFT>/<RIGHT>

查找文件并打开:C-x C-f(此时emacs会进入Dired文件管理器,^ 键返回上一层目录(没错就是Shift + 数字 6),q 键退出)

全选:C-x h

向前搜索:C-s,再按一次,定位到下一个位置

向后搜索:C-r

替换:M-x repl s <RET> changed <RET> altered

多窗格:C-x 2将屏幕划分成上下两个窗格,光标默认停留在上面的窗格;C-x 3将屏幕划分成左右两个窗格;C-x o将光标切换到其它窗格。C-x 0关闭当前光标所在的窗口。

关闭当前窗格以外的其他窗格:C-x 1

C-M-v 滚动另一个窗格的内容,但是光标仍然在当前窗格。

C-x } 增大当前窗口的宽度

C-x { 减小当前窗口宽度

C-x ^ 增大当前窗口高度

C-x-+ 平衡所有窗口大小


查找和替换

C-s,输入要查找的内容后,再次按下 C-s,即可向后搜索。

C-r,输入要查找的内容后,再次按下 C-r,即可向前搜索。

可配合 C-w,使用,查找光标处的单词。

C-s M-p 向前检索曾经搜索过的关键词,C-s M-n 向后检索曾经搜索过的关键词

M-% 替换,先输入要查找的关键词,再输入要替换的内容。y 肯定此次替换,n 否定此次替换,q! 替换所有。


注释

emacs 注释:

选中一段区域后,按 C-x r t,输入注释内容

emacs 取消注释:

选中一段区域后,紧挨着注释字符之后的位置,C-x r k

CCMode 下:

C-c C-c 批量注释选中代码

C-u C-c C-c 取消注释


缩进

C-M-\ 取消光标行所有缩进

C-x <TAB> 通过<LEFT>/<RIGHT>S-<LEFT> S-<RIGHT>键来改变所有行的缩进

C-M-o 光标后面的内容移动到下一行当前位置处

M-^ 恢复上面的移动

M-m 将光标移动到当前行开头处(不包括缩进部分)

M-i 缩进

emacs 模式介绍

Emacs 每打开一个buffer都会有一个Mode,可以手动指定,例如 M-x text-mode,可以通过 C-h m 查看当前Mode的文档说明。

Info 模式

Info 模式是emacs内置的一个功能强大的阅读器,用来构建、查阅文档树。Emacs手册也可以用Info阅读器打开,你也可以自己创造Info格式的手册。更多参考见 Emacs Wiki: InfoMode以及Emacs手册中Info部分。

C-h i 打开 Info 阅读器,默认打开Emacs手册

n 下一节(同级),如果已经在本级最后一个节中,再按 n 无效
p 上一节(同级),如果已经在本级最后一个节中,再按 p 无效

SPC 向下滚动一屏
BACKSPACE 向上滚动一屏

b 回到本节开始

] 下一节(可跨级,如1.5只包含一个1.5.1,当前处于1.5.1中,此时按 np 都无效,可以按 ] 进入 1.6节)
[ 上一节(可跨级,如1.5只包含一个1.5.1,当前处于1.5.1中,此时按 np 都无效,可以按 [ 返回 1.5节)

m 输入菜单包含的子节名,进入该节
u 返回菜单
f 进入引用部分
l 从引用的部分返回原文中
r 返回按 l 之前的节中

M-TAB 在菜单和引用之间切换
S-TAB 在菜单和引用之间切换

L 新建一个虚拟节,列出你访问过的所有节供选择跳转
d 返回根节点
t 返回手册的顶层节点

q 退出Info模式

? 查看命令

书签

既然 Info模式是一个阅读模式,免不了遇到看不完下次打开再看的情况,emacs也有书签这一功能满足该需求。

C-x r m 建立书签

C-x r l 显示书签列表,这会打开一个buffer,这里面的快捷键如下:

a 显示当前书签的标注信息
d 标记书签,按 x 删除
e 编辑当前书签的标注信息
r 重命名当前书签
m 标记书签,按 v 进入该书签

C-x r b 跳转到书签所指示的位置

doomemacs 配置

安装相关的包

我曾在 windowsubuntuarchlinux 上都安装过 doomemacs,下面是在 ubuntu 上时安装的一些依赖包,其他系统请自己查找对应的包。

  1. 编译安装 glslan
  2. 开启 :lang sh 后需要安装 shellchecksudo apt install shellcheck
  3. 开启 :lang markdown 后需要安装的依赖参考 doomemacs markdown ,这里我安装的是:sudo apt install pandocpip install proselintsudo npm install -g markdownlint
  4. 开启 :lang (java +lsp) 后需要安装 JDK,安装方式略。
  5. 开启 :lang python 后需要安装 pip install pytest, pip install nose, pip install pyflakespip install isort,如果是开启了 :lang (python +conda) 还需要安装 anaconda,安装方式略。

vterm

$DOOMDIR/init.el:term vterm 打开。

在emacs终端中执行 M-x vterm(需提前安装插件并安装apt-get install libvterm-dev cmake libtool-bin)即可运行 vterm

在开启 evil 模式的情况下, SPC 键才有定义,注意 SPC 键在 doom emacs 中默认按 M+SPC 才可以,在 evil 模式关闭的情况下,M+SPC 无定义,以 C-c 代替。

$DOOMDIR/init.el 中的 :editor evil 注释掉即可关闭 evil模式,同时 SPC 键失效。

参考 Doom Emacs 按键绑定

按键 用途 备注
M-SPC o T +vterm/here 开启一个新的 vterm
M-SPC o t +vterm/toggle 在 Emacs 窗口底部弹出迷你 vterm 或者隐藏迷你 vterm
C-c C-c vterm-send-C-c
C-c C-g vterm-send-C-g
C-c C-t vterm-copy-mode
C-c C-z evil-collection-vterm-toggle 切换 ESC 按键是送给 vterm 还是送给 Emac

在 vterm 中,按照 https://github.com/akermu/emacs-libvterm#shell-side-configuration-files 说明配置好 shell,即可通过命令 emacsclient filename 将文件在 emacs 中打开

tips: 很多时候我都会面临在 emacs 中编辑代码,在终端中编译运行的情形。如果是用 M-SPC o t 在 minibuffer 中生成一个 vterm,那么用 C-x o 切换到我正在编辑的代码窗口后就没办法再用 C-x o 中切换回来了。而如果用 M-SPC o T 创建一个新 buffer,切换回代码的时候不光每次都要 emacsclient filename 麻烦不说,如果代码文件的 buffer 没有被 kill,返回 vterm 后还不能使用。因此比较优雅的做法是,使用 C-x 2C-x 3 分割成两个窗口,其中一个是代码文件的 buffer,另一个是用 M-SPC o T 打开的 vterm 的 buffer,这样两个 buffer 之间就可以使用 C-x o 进行切换了。

Markdown

$DOOMDIR/init.el 文件中开启 :lang markdown 即可解锁在 emacs 中编辑、预览 markdown 文件的功能。

打开 .md 文件的方式同其他文件一样,但是预览的快捷键为:C-c C-c p(不开 evil模式的快捷键)。

自动保存

每次都要手动保存 buffer 是很麻烦的一件事,如果有时候忘记保存了更糟,下面的步骤可以使你的 buffer 每隔10s 自动保存一次。

  1. 打开 $DOOMDIR/config.el

  2. 添加下面的代码

1
2
(setq auto-save-visited-interval 15)
(quto-save-visited-mode +1)
  1. 重启 emacs

emacs 跨应用粘贴

如上文所示的那样,在emacs 中的复制命令是 M-w,粘贴(召回)命令是 C-y,而对系统中的其他软件来说,复制粘贴快捷键一般都是 Ctrl+cCtrl+v。从 emacs 中复制的内容是无法在其他应用程序中粘贴的,而从其他应用中复制的内容也无法粘贴到 emacs 中。解决办法如下:

  1. 打开 $DOOMDIR/config.el

  2. 添加下面的代码

1
(setq x-select-enable-clipborad t)
  1. 重启 emacs

这样,在 emacs 中通过 M-w 复制的内容在其他应用中可通过 Ctrl+v 粘贴;在其他应用中通过 Ctrl+c 复制的内容也可以通过 C+y 粘贴到 emacs 中。

但是,该方式只在点击应用图标运行 emacs 的时候有效,在终端通过 emacs -nw 命令运行的 emacs 仍然无法跨应用粘贴。

EAF

Emacs Application Framework (EAF)的愿景是在保留 Emacs 古老的黑客文化和庞大的开发者插件生态前提下,通过 EAF 框架扩展 Emacs 的多线程和图形渲染能力,实现 Live In Emacs 的理想。

项目地址:emacs-eaf/emacs-application-framework

$DOOMDIR/config.el

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(use-package eaf
;; 设定只有手动调用以下命令后,eaf才会加载
:commands (eaf-open eaf-open-bookmark eaf-open-browser eaf-open-browser-with-history)
:init
;; 设定emacs中打开链接默认使用eaf打开
(setq browse-url-browser-function 'eaf-open-browser)
(defalias 'browse-web #'eaf-open-browser)
:config
;; 下面的require都是引入你已经安装的eaf扩展
(require 'eaf)
(require 'eaf-image-viewer)
(require 'eaf-demo)
(require 'eaf-browser)
(require 'eaf-pdf-viewer)
(require 'eaf-music-player)
(require 'eaf-jupyter)
(require 'eaf-video-player)
;; 设定eaf默认搜索引擎
(setq eaf-browser-default-search-engine "google")
;; 设定eaf开启广告屏蔽器
(setq eaf-browser-enable-adblocker t)
;; 设定eaf浏览器的缩放
(setq eaf-browser-default-zoom 1.2)
)

每次更新 eaf 及安装新的app后,都需要重新删除 emacs/.local/straight/build-xx.x 下的 eaf 文件夹,并执行 doom sync

eaf browser

此处记录一个小问题,在使用 eaf browser 逛 B 站的时候,发现无法观看视频,深入研究了一下。最初是在 EAF Browser 支持 HTML5 视频 这里看到的原因,eaf 的浏览器是用 Qt 的 QWebEngine 来构建的,QtWebEngine 默认使用 Chromium 内核。Chromium 内核并不能像 Chrome 那样默认就带一些视频的解码器, 比如 H.264 和 MPEG 等编码(可能是由于版权、许可证的原因),但是可以通过参数 -webengine-proprietary-codecs 在编译 QWebEngine 的时候指定,参照官方文档 Qt WebEngine Features

如果是 arch linux 的话还是很容易解决的,使用 pacman 安装一下 pyqt6 即可。pacman 的源中的 pyqt6 webengine,默认是用 -webengine-proprietary-codecs 编译过的。但是如果是其他系统,事情就有点麻烦。

我们知道 eaf 是通过 python 安装的(eaf项目目录下手动执行 ./install-eaf.py),它用的是 pyqt6,目前 eaf 所依赖的 pyqt6 的版本是 6.5.0(可以在项目目录下 /home/lsn/.config/emacs/.local/straight/repos/emacs-application-framework 查看 dependencies.json 得知)。正在我不知道编译 pyqt6 的 webengine 要不要安装编译好的 C++ 的 QWebEngine 库时,我在官方文档 Installing PyQt6 中看到了 sip。这意味着:

  1. 如果想要让 eaf browser 支持 html5 播放器,就得用支持 H.264 和 MPEG 等编码的 PyQt6-QWebEngine 去构建。
  2. 在非 arch linux 系统下,无论是通过 pip 还是 apt 等安装的 PyQt6-QWebEngine 默认都是不支持 H.264 和 MPEG 等编码的,除非自己手动编译安装 PyQt6-QWebEngine。
  3. 手动编译安装 PyQt6,用到了 sip,意味着你系统上要有 C++ 版本的 QT6 的库,而且其中的 WebEngine 是用 -webengine-proprietary-codecs 编译过的。

下面准备编译 C++ 的 QWebEngine。

这里需要区分一下 QT 的版本,QT5 和 QT6,编译 QT5 下的 QWebEngine 需要 python 2 (Qt5 WebEngine Platform Notes),不支持 python 3,因此这里我选择的是编译 QT6 下的 QWebEngine。

按照官方文档 Qt6 WebEngine Platform Notes,截至 2024-03-28,ubuntu 22.04 需要如下的包:

1
2
3
4
5
6
7
8
libfontconfig1-dev libfreetype6-dev libx11-dev libx11-xcb-dev libxext-dev 
libxfixes-dev libxi-dev libxrender-dev libxcb1-dev libxcb-cursor-dev libxcb-glx0-dev
libxcb-keysyms1-dev libxcb-image0-dev libxcb-shm0-dev libxcb-icccm4-dev
libxcb-sync-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-randr0-dev libxkbfile-dev
libxcb-render-util0-dev libxcb-util-dev libxcb-xinerama0-dev libxcb-xkb-dev libxkbcommon-dev
libxkbcommon-x11-dev dbus fontconfig libdrm-dev libxcomposite1 libxcursor1 libxdamage-dev
libxss1 libxtst-dev bison build-essential gperf flex python3-html5lib libnss3-dev ninja-build
libxcomposite-dev libxrandr-dev libxi-dev libxcursor-dev libxss-dev libxshmfence-dev xorg-dev libcups2-dev

另外,nodejs 需要 14.0 以上的版本。

这里大概说一下目录,如下图所示。其中,/opt/Qt/6.5.0 是通过 Qt Maintenance Tool 安装的,gcc_64 是安装好的可执行文件、头文件、库目录;Src 是 Qt 及各个模块的源代码。

1
2
3
4
cd /opt/Qt/6.5.0/Src/qtwebengine
sudo /opt/Qt/6.5.0/gcc_64/bin/qt-configure-module /opt/Qt/6.5.0/Src/qtwebengine -webengine-proprietary-codecs
sudo cmake --build . --parallel
cmake --install .

另外需要注意的是,编译前先将你的内存搞大一点,我是 16G 的内存 + 9G 的交换空间,中间内存爆掉了,手动添加了个 10G 的交换文件,还爆内存。最后索性直接加了个 50G 的交换文件,编译过程中监测了一下内存占用,最高的时候竟然达到了 47G,也是离谱。


接下来编译 PyQt6-WebEngine 6.5.0,下载下来解压出来后,还需要下载 PyQt6 6.5.0,将 PyQt6-6.5.0/sip 下面的 QtPrintSupportQtWidgets, QtCoreQtGuiQtNetworkQtQmlQtWebChannel 复制到 PyQt6_WebEngine-6.5.0/sip 下。

需要安装如下的包:

1
2
pip install PyQt-builder
sudo apt install python3-sipbuild

然后执行:

1
2
3
4
5
cd PyQt6_WebEngine-6.5.0
sip-build --qmake /opt/Qt/6.5.0/gcc_64/bin/qmake --no-make --target-dir /home/lsn/.local/lib/python3.10/site-packages
cd build
make
sudo make install

注意将上面的绝对路径改成你自己的。

treemacs

与neotree类似的插件,类似于ranger,用目录树的方式浏览文件。

C-c o p 打开 treemacsC-c C-p a 添加目录,C-c C-p d 移除目录,其他快捷键通过 ? 查看。

doomemacs 中没有绑定在 treemacs 和其它窗口间进行切换的快捷键,我这里借用 treemacs-select-window 来实现切换的功能。

$DOOMDIR/config.el 中添加:

1
(global-set-key (kbd "M-0") 'treemacs-select-window)

即可实现按 M-0 切换。

其他

编程时选择智能提示的代码:M-编号

在终端打开emacs: emacs -nw

如果需要代码补全,安装相应的language server。如CMake需要安装pip install cmake-language-server,java M-x package-install [RET] lsp-java [RET]即可,其他语言参考LSP Mode - LSP support for Emacs

emacs 有很多内置变量,可通过 M-x find-variable 查看。

emacs 编程配置

C++

编写C++ 代码时,lsp默认的缩进是4个空格,但是使用 lsp-format-buffer 格式后,又变成了两个空格,参考 我想了解下, emacs 下的缩进到底是怎么设置的…?,如果使用的是 doom emacs 的话,可以直接通过 M+x doom/set-indent-width 来设置。

我使用的是 lsp,用 clangd 作后端,系统上装有 gcc 和 clang。最开始用的时候,下面最简单的 hello world 程序 emacs 都报错:

1
2
3
4
5
6
7
#include <iostream>

int main()
{
std::cout << "hello, world" << std::endl;
return 0;
}

报错 'iostream' file not found

这可是最基本的 C++ 库。经查询 Locating iostream in Clang++: fatal error: ‘iostream’ file not found,clang 使用的标准库与 gcc 使用的不一样,需要单独安装一下。

1
sudo apt install libc++-dev libc++abi-dev

另外,需要在项目根目录下新建 .clangd 文件,为 clangd 指定使用的标准库:

.clangd

1
2
3
CompileFlags:
Add:
- "-stdlib=libc++"

此外,如果项目用到了第三方库,也可以在这里将第三方库的头文件引入,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CompileFlags:
Add:
- "-I/opt/Qt/5.15.2/gcc_64/include"
- "-I/opt/Qt/5.15.2/gcc_64/include/QtWidgets"
- "-I/opt/Qt/5.15.2/gcc_64/include/QtCore"
- "-I/opt/Qt/5.15.2/gcc_64/include/QtGui"
- "-I/opt/Qt/5.15.2/gcc_64/include/QtNetwork"
- "-I/opt/Qt/5.15.2/gcc_64/include/QtSql"
- "-I/opt/Qt/5.15.2/gcc_64/include/QtXml"
- "-I/opt/Qt/5.15.2/gcc_64/include/QtMultimedia"
- "-I/opt/Qt/5.15.2/gcc_64/include/QtWebSockets"
- "-I/opt/Qt/5.15.2/gcc_64/include/QtSerialPort"
- "-I/usr/local/include/opencv4"
- "-I/usr/local/include/opencv4/opencv2"
- "-stdlib=libc++"

clangd 的其它配置见 clangd configuration

代码跳转

写代码的过程中我们经常需要在某个函数使用的地方跳转到其定义的地方,这里借助 emacs 的 cctags 插件和 linux 的 gnu global
首先安装 gnu global:ubuntu 下直接 sudo apt install global

然后,在 doom emacs 的 packages.el 配置文件最后加上

1
(package! ggtags)

然后执行 doom sync 即可。

使用方法:

  1. 从调用一个函数的地方跳转到函数的定义的地方:M-.
  2. 从 1. 跳转后,再返回调用函数的地方:M-,
  3. 从函数定义的地方列出所有调用这个函数的地方:M-]
  4. 看导航过的历史记录:C-c M-h

rust

python、rust等debug用,参考 :tools debugger:lang rust,需要安装 gdblldbllvmllvm-mi,需要将 :tools lsp 打开,并将 :lang rust 改为 :lang (rust +lsp) 才能支持 debug。改为 :lang (rust +lsp),需要安装 rust-analyzer

使用 C-h m 打开 rustic 模式说明,可以看到,使用 C-c C-c C-r 来运行 rust,对应的是 cargo run 命令。如果需要添加参数,如希望运行 cargo run --release,需要按 C-u C-c C-c C-r

文件模板更改

遇到需求如下:编写 C++ 代码时,新建 .h.hpp 文件时,默认会在文件开始补上一些信息,例如,新建一个 test.h 文件,在文件头将会补全如下信息:

1
2
3
4
5
6
#ifndef TEST_H_
#define TEST_H_



#endif // TEST_H_

如果我想更换别的信息,例如将上面的代码换成 #pragmra once,或者在文件的开头补上作者的信息,该如何做呢?

我看完 官方文档 中的介绍还是云里雾里的,于是又参考了一个 issue How to create files using the file-templates module?,加上自己的摸索,现将方法总结如下。

要使用文件模板的功能,首先要在 init.el 中开启 :editor file-templates,当然该选项默认就是开启的。

我们在安装 doomemacs 的时候,如果你是参照官方的安装方式 doomemacs#install,会先将项目 git 下来。这个路径里面 modules/editor/file-templates 存放的即是文件模板,config.el 告诉了我们在什么样的情况下去加载这些模板。

**不要手动去更改这里的文件模板!**而是在你的 $DOOMDIR 路径下新建一些模板。这里我的路径是 ~/.config/doom,在这个路径下去新建模板文件。模板文件的路径及结构为 {+file-templates-dir}/major-mode/{snippet-name},例如我这里新建了两个模板文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
~/.config/doom ❯ tree
.
├── config.el
├── custom.el
├── init.el
├── packages.el
└── snippets
├── c++-mode
│   └── __hpp
└── c-mode
└── __h

3 directories, 6 files

其中 __hpp 的内容为:

1
2
3
4
5
6
7
8
# -*- mode: snippet -*-
# group: file templates
# contributor: 路双宁
# --
#pragma once

$0

注意这里的目录名称 snippets/c++-mode 一定不要错,c++-mode 指的是你新建的文件如果运行在 c++-mode 的话,就会来这个路径下找相应的模板文件。

至于为什么要这么做,文档中有这么一句话:

…They are also regular yasnippet snippets, which can be expanded by typing their trigger and pressing TAB. By convention, the triggers for file templates are prefixed with two underscores __.

原来文件模板这个功能实际上是基于 yasnippet的,在 :editor snippets 章节中的 Usage 中有描述。

此处再多提一嘴,我们写代码时的代码补全功能也是用的 snippets,在 .config/emacs/.local/straight/repos/snippets 下会发现各种 mode 下的补全方式。至于如何自定义补全的内容,后面研究一下再记录。

如果不清楚运行在哪个模式,可以做个测试,新建并打开一个文件后运行 M+x +file-templates/debug,会显示当前的模板文件。__hpp 是文件模板名,这个可以自定义。c-mode 同理。

此外,编辑 $DOOMDIR/config.el 文件,添加:

1
2
(set-file-template! "\\.h\\(?:h\\|pp\\|xx\\)$" :trigger "__hpp" :mode 'c++-mode)
(set-file-template! "\\.h$" :trigger "__h" :mode 'c-mode)

使用 set-file-template! 函数来将我们自定义的文件模板的优先级设置为高于内置的文件模板,其中 :trigger 后面的是我们自定义的文件模板名。也可以使用 set-file-templates! 来批量设置文件模板,可以自己搜索一下该函数的使用方式。

之后,执行 doom sync 使配置文件生效,打开 emacs再执行 M+x doom/reload 更新配置

这样,再次新建 test.h 时,文件将会被添加上 #pragma once 了。