Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/condy0919/.emacs.d

一个快速且实用的 Emacs 配置
https://github.com/condy0919/.emacs.d

config emacs emacs-lisp evil-mode

Last synced: about 2 months ago
JSON representation

一个快速且实用的 Emacs 配置

Awesome Lists containing this project

README

        

A fast and incredible Emacs config



[![Build Status](https://github.com/condy0919/.emacs.d/workflows/CI/badge.svg)](https://github.com/condy0919/.emacs.d/actions)
[![License](http://img.shields.io/:license-gpl3-blue.svg)](LICENSE)
![Supports Emacs 28.1-30.x](https://img.shields.io/badge/Supports-Emacs_28.1_--_30.x-blueviolet.svg?style=flat-square&logo=GNU%20Emacs&logoColor=white)

**Table of Contents**

- [个人**Emacs**配置](#个人emacs配置)
- [需要的依赖](#需要的依赖)
- [基础配置](#基础配置)
- [插件配置、升级](#插件配置升级)
- [界面](#界面)
- [趁手的工具](#趁手的工具)
- [按键绑定](#按键绑定)
- [evil-mode](#evil-mode)
- [Emacs](#emacs)
- [通用开发设置](#通用开发设置)
- [prog-mode](#prog-mode)
- [cc-mode](#cc-mode)
- [rust-mode](#rust-mode)
- [ocaml-mode](#ocaml-mode)
- [haskell-mode](#haskell-mode)
- [截图](#截图)
- [FAQ](#faq)
- [其他](#其他)

个人**Emacs**配置
====

![overview](https://user-images.githubusercontent.com/4024656/83358470-63a6dd00-a3a6-11ea-8489-f86b08b48d13.png
"Emacs Overview")

仿 [Centaur Emacs](https://github.com/seagle0128/.emacs.d) 的个人配置.

```bash
git clone --depth 1 https://github.com/condy0919/.emacs.d ~/.emacs.d
```

仅包含**C/C++/Rust/OCaml/Haskell**相关配置,且全线使用`lsp`。当前由于`haskell-ide-engine`水土不服,故`haskell`没有采用`lsp`。

保持着尽量使用`Emacs`自带功能的原则,能用自带的就用自带的。

# 需要的依赖

- `hunspell` (optional) 拼写检查,目前仅在`git-commit-mode`下默认启用
- `rg` 更快的`grep`
- `pandoc` (optional) 文本转换工具,`markdown-mode`渲染需要
- `markdown` (optional) 文本转换工具,`markdown-mode`渲染需要
- `cmake` `c++`项目的构建工具
- `git` 这个就不用说了吧?
- `gcc` 这个就不用说了吧?
- `fd` (optional) 更现代的 `find`, `projectile` 会自动检测

# 基础配置

最基础的配置包含了那些在所有`mode`下都不会变更的配置,包含了:

| 包名 | 功能 |
|---------------|------------------------------------------------|
| align | `align-regexp`可以自动对齐选择的符号 |
| appt | 任务提醒,可以与`org-mode`结合 |
| hippie-expand | 用来展开文本 |
| hl-line | 高亮当前行 |
| newcomment | 注释、反注释功能 |
| paren | 高亮匹配的括号 |
| saveplace | 自动记录上次打开文件的位置 |
| simple | 在`modeline`里显示行号、列号以及当前文本的大小 |
| so-long | 打开长行的文件不再痛苦 (`Emacs` 27+ 自带) |
| tab-bar | 窗口布局管理 (`Emacs` 27+ 自带) |
| tramp | 远程编辑就靠它 |

而这几个包也是`Emacs`自带的。

为了保持界面的整洁,禁用了菜单栏、工具栏和滚动条。

# 插件配置、升级

`package.el`(自带的)来安装包、`use-package`来管理配置。对于`elpa`, `melpa`里没有的包,使用`quelpa`辅助下载。

为什么我会从`straight.el`切换至`quelpa`呢?

主要是`straight.el`不支持单个文件的下载、配置,为了使用`llvm-mode.el`而 clone 整个 llvm repo 就显得有点得不尝失了。相关配置见[`init-cpp.el`](lisp/lang/init-cpp.el)内的`llvm-mode`配置项。另外由于`quelpa`与 `package-quickstart`冲突,`llvm-mode`和`tablegen-mode`需要人工执行对应的`quelpa`代码块来提前安装,而不是通过`use-package`自动检测、下载。不过因为`quelpa`安装过后的包也会在`~/.emacs.d/elpa/`里放一份,所以最终效果跟`package.el`是一样的。

Emacs 29 引入了 `package-upgrade-all`,需要更新直接 M-x package-upgrade-all 即可。

# 界面

使用了`doom-themes`和`doom-modeline`,简直惊艳!`doom-one`的界面非常好看!

# 趁手的工具

`rg`是比较常用的工具,更有`projectile`管理项目,让项目编译、测试、运行变得更加方便。

`avy`用来代替`vim-easymotion`。而且`avy`还提供了`goto-line`的功能,这下都不用开相对行号`8k` `9j`这样跳了。

以前是`ivy`用户,现在则是仅使用`vertico`, `embark`, `consult` 和 `marginalia` 了。

`Emacs`下的`org-mode`/`markdown-mode`让人惊艳,突然觉得写文档也会这么快乐。与之相辅相成的还有`separedit`,让人在代码里写`documentation comments`不再烦恼。

[valign][valign] 提供了像素级别的表格对齐,终于不用再靠西文半宽的字体了!

从`neovim`迁移过来的我,自然是常开`evil-mode`,相关的`evil`套件有:

- `evil`
- `evil-collection` (已包含 `evil-magit`)
- `evil-surround`

# 按键绑定

## evil-mode

开启了`evil-collection-want-unimpaired-p` (由`evil-collection`提供) 而获得了如下键绑定:

| key | function |
|------------------|---------------------------------------------------------------------|
| [b | `previous-buffer` 切换至上一个 `buffer` |
| ]b | `next-buffer` 切换至下一个 `buffer` |
| [e | `evil-collection-unimpaired-move-text-up` 将当前行移动至上一行 |
| ]e | `evil-collection-unimpaired-move-text-down` 将当前行移动至下一行 |
| [l | `evil-collection-unimpaired-previous-error` 上一个错误 |
| ]l | `evil-collection-unimpaired-next-error` 下一个错误 |
| [ SPC | `evil-collection-unimpaired-insert-newline-above` 在上方插入一空行 |
| ] SPC | `evil-collection-unimpaired-insert-newline-below` 在下方插入一空行 |
| [u | `evil-collection-unimpaired-url-encode` 对所选内容进行`url`参数编码 |
| ]u | `evil-collection-unimpaired-url-decode` 对所选内容进行`url`参数解码 |

此外,凭借 avy 模拟了 [evil-snipe][evil-snipe] 的 `s` 和 `f` 功能。

| key | function |
|--------------|------------------------------|
| s | `evil-avy-goto-char-timer` |
| f | `evil-avy-goto-char-in-line` |

本配置里使用`hideshow`来`fold`代码块。由于`hideshow`本身提供的快捷键非常长,非常推荐使用`evil-mode`在`normal`状态下定义的键绑定。

| key | function |
|---------------|-----------------------------------------------------|
| zm | `evil-close-folds`隐藏所有代码块 |
| zr | `evil-open-folds`显示所有被隐藏的代码块 |
| zo | `evil-open-fold`隐藏当前代码块 |
| zO | `evil-open-fold-rec`递归地隐藏当前以及之内的代码块 |
| zc | `evil-close-fold`显示当前被隐藏的代码块 |
| zC | `evil-close-fold-rec`递归地显示当前以及之内的代码块 |
| za | `evil-toggle-fold`来切换是否隐藏代码 |

与文件相关的`Leader`键绑定如下:

| key | function |
|---------------|-----------------------------------------------------------------------------|
| ff | `find-file`打开文件, f.有相同效果 |
| fF | `find-file-other-window`同上,不过是在另一窗口打开, f/有相同效果 |
| f/ | 同上 |
| fD | `+delete-current-file`删除当前文件 |
| fC | `+copy-current-file`拷贝当前文件至其他地方 |
| fy | `+copy-current-filename`拷贝当前文件的绝对路径 |
| fR | `+rename-current-file`重命名当前文件 |
| fr | `recentf-open-files`访问最近使用过的文件 |
| fl | `find-file-literally`采用朴素模式打开文件 |
| fj | `dired-jump`进入当前文件的目录 |
| fJ | `dired-jump-other-window`同上,不过是在另一窗口打开 |

与`buffer`、`bookmark`相关的键绑定:

| key | function |
|---------------|---------------------------------------------------------------------------------------|
| bb | `switch-to-buffer`切换`buffer` |
| bB | `switch-to-buffer-other-window`同上,不过是在另一窗口打开 |
| bc | `clone-indirect-buffer`将当前`buffer`克隆至另一`buffer`,它们可以使用不同`major-mode` |
| bC | `clone-indirect-buffer-other-window`同上,不过是在另一窗口打开 |
| bv | `revert-buffer`重新读取当前`buffer`对应的文件 |
| bx | `scratch-buffer`直接跳转到 `*scratch*` buffer |
| by | `+copy-current-buffer-name`复制当前`buffer`的名字 |
| bz | `bury-buffer`退出当前`buffer`的显示,当前`buffer`未被 kill |

| key | function |
|---------------|---------------------------------------------------------|
| bj | `bookmark-jump`跳转至书签 |
| bJ | `bookmark-jump-other-window`同上,不过是在另一窗口打开 |
| bm | `bookmark-set`设置书签 |
| bM | `bookmark-set-no-overwrite`同上,但是不会覆盖同名的书签 |
| bd | `bookmark-delete`删除书签 |
| bi | `bookmark-insert`插入书签的内容 |
| bl | `bookmark-bmenu-list`打开书签列表 |
| br | `bookmark-rename`重命名书签 |
| bs | `bookmark-save`保存书签 |
| bw | `bookmark-write`将书签保存至其他文件 |

打开其他程序的`Leader`键绑定:

| key | function |
|---------------|------------------------------------------------------------|
| ot | `ansi-term`打开`ansi-term` |
| oe | `eshell`打开`eshell` |
| os | `shell`打开`shell` |
| ol | `org-store-link`存储URL |
| oc | `org-capture`随时记录一些想法、URL等 |

打开一些看起来像是独立的应用:

| key | function |
|---------------|------------------|
| aa | `org-agenda`日程 |
| ac | `calendar`日历 |
| ag | `gnus`查看新闻组 |
| ai | `rcirc`上 IRC |

搜索相关的`Leader`键绑定:

| key | function |
|---------------|-----------------------|
| si | `imenu` |
| sj | `evil-show-jumps` |
| sm | `evil-show-marks` |
| sr | `evil-show-registers` |
| sp | `consult-ripgrep` |
| ss | `consult-line` |

与代码相关的`Leader`键绑定:

| key | function |
|---------------|---------------------------------------------------|
| cc | `compile`编译 |
| cC | `recompile`重新编译 |
| ck | `kill-compilation`打断当前的编译过程 |
| cx | `quickrun`快速运行当前程序 |
| cX | `quickrun-shell`在`eshell`里查看输出 |
| cd | `rmsbolt-compile`查看编译器的输出,如汇编、IR表示 |
| cw | `delete-trailing-whitespace`删除行末空白字符 |

## Emacs

| key | function |
|--------------------|-----------------------------------------|
| M-\` | 打开一个弹出式`shell`以临时执行一些命令 |
| M-; | `comment-or-uncomment` 注释与反注释 |
| C-c ' | 通过`separedit`在注释中快乐地写代码 |
| C-c p | `projectile`调用前缀 |
| C-x g | 呼出 `magit` |
| M-g M-l | 调用`avy-goto-line` |

因为[projectile](https://github.com/bbatsov/projectile)比较常用,故把它单独拿出来说。

| key | function |
|----------------------|----------------------------------------------------------------------|
| C-c p f | `projectile-find-file`在项目内查找其他文件 |
| C-c p b | `projectile-switch-to-buffer`切换至其他`buffer`(限定在本`project`下) |
| C-c p C | `projectile-configure-project`配置当前项目 |
| C-c p c | `projectile-compile-project`编译当前项目 |
| C-c p u | `projectile-run-project`运行当前项目 |
| C-c p P | `projectile-test-project`测试当前项目 |
| C-c p p | `projectile-switch-project`切换至其他项目 |
| C-c p s r | `projectile-ripgrep`使用`ripgrep`来搜索当前项目内的文本。 |

基于同样的理由,把`flycheck`单独拎了出来。

| key | function |
|--------------------|-------------------------------------------|
| C-c ! l | `flycheck-list-errors`列出所有`lint`错误 |
| C-c ! n | `flycheck-next-error`下一个`lint`错误 |
| C-c ! p | `flycheck-previous-error`上一下`lint`错误 |

更详细的按键绑定请直接看[代码](lisp/init-evil.el). :-)

C-c h是所有`hydra`的前缀,目前有 2 个,分别是:

1. `background-opacity-menu`方便执行调整真透明度
2. `scroll-other-window-menu`在不改变焦点的情况下移动另一窗口的`buffer`

# 通用开发设置

- 显示行末空白字符
- 高亮**TODO** **FIXME**等关键字
- `dumb-jump`作为`lsp-find-definition`失败后的备份手段
- `magit`作为`git`客户端
- `hideshow`来显示/隐藏结构化的代码块,如 "{ }" 函数体等
- `rmsbolt`作为一个本地的 **Compiler Explorer** 相比于`godbolt`快速一点
- `ispell`拼写检查器, `evil`用户可以快速通过z= (`ispell-word`) 来检查
- `flyspell`拼写检查器,仅在`magit`写提交信息时启用
- `quickrun`作为一个能够执行部分区域内的代码块,方便快速验证函数功能
- `tempo`作为代码片段展开工具, `spdx`然后再M-x tempo-expand-if-complete即可。也可以通过 `hippie-expand` 来触发

# prog-mode

## cc-mode

使用`lsp-mode`作为补全、符号查找的工具,默认后端使用`clangd`,一般发行版的源里都会有对应的包。如果想使用[ccls][ccls],可以`customize`对应的变量:

``` emacs-lisp
(setq lsp-clients-clangd-executable "ccls"
lsp-clients-clangd-args nil)
```

如果想使用`ccls`的`lsp`扩展功能,需要安装[ccls][emacs-ccls]扩展。

此外,

- `cmake-mode`可使用`company-mode`进行符号补全
- 启用了`hide-ifdef-mode`,可以令`#if 0`到`#endif`之间的代码看起来像注释一样。也可以`#define`一些宏,放入`hide-ifdef-env`中即生效。
- 部分常用`snippet`,如`ifndef`,`main`等等。详细列表见[`init-cpp.el`](lisp/lang/init-cpp.el)文件
- `cmake-mode`增加了一个简单 lib 的`snippet`,可以通过`lib`关键字展开

## rust-mode

使用`lsp-mode`作为补全、符号查找的工具,默认后端使用`rust-analyzer`,需要额外安装`rust-analyzer`的包。`lsp-mode`会首先考虑`rust-analyzer`,如果未在`exec-path`中找到则会转而使用`rls`。`rls` 通常与`rust`这个包捆绑在一起。

- `rust-mode`开启了保存时格式化文件,需要确保`rustfmt`二进制包存在
- 使用了[cargo][cargo]来提供深度集成化的`cargo`命令

## ocaml-mode

使用`lsp-mode`作为补全、符号查找的工具。在`Arch Linux`上,可以使用 [ocaml-lsp-git][ocaml-lsp-git] 这个包。

由于`ocaml-lsp-git`目前**只**实现了`lsp-format-buffer`且额外依赖`ocamlformat`。

所以这里额外使用了[ocp-indent][ocp-indent],通过`ocp-indent-region`, `ocp-indent-buffer`来提供格式化代码的功能。

同时也集成了[dune][dune]。

`ocp-indent`和`dune`都依赖系统级别的包。

如果你是`Arch Linux`可以直接通过如下命令安装:

``` bash
yay -S ocaml-ocp-indent dune
```

## haskell-mode

非常纯粹, ~~其实是平常不怎么写 haskell~~。

# 截图

![dashboard](https://user-images.githubusercontent.com/4024656/83343041-46cbc480-a328-11ea-800d-6cd88540186e.png
"Dashboard")

![magit_markdown](https://user-images.githubusercontent.com/4024656/83343152-30723880-a329-11ea-90ff-ec5f3b7d504a.png
"Magit and markdown")

![cpp_company](https://user-images.githubusercontent.com/4024656/83343182-965ec000-a329-11ea-862c-a0b25305a1b4.png
"cc-mode and company")

# FAQ

## [dashboard][dashboard] 图标显示异常

依赖 [nerd-icons][nerd-icons], 请确保 `M-x nerd-icons-install-fonts` 安装对应的字体以显示图标。

## 更新时提示对应包版本不存在

如果您在使用国内镜像源时出现这个问题,多数情况都是由镜像源同步不一致导致的,可以切换成上游来规避这个问题。

``` elisp
(setq package-archives
'(("melpa" . "https://melpa.org/packages/")
("gnu" . "https://elpa.gnu.org/packages/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")))
```

## Emacs 配置挂了

可以使用 [init-mini.el](init-mini.el) 这个最小配置来临时救急一下。

``` bash
emacs -Q -l init-mini.el
```

~~虽然咱都是直接开 nvim 的~~

# 其他

欢迎提`issue`给出建议,感谢!

[nerd-icons]: https://github.com/rainstormstudio/nerd-icons.el
[ccls]: https://github.com/MaskRay/ccls/
[cargo]: https://melpa.org/#/cargo
[dashboard]: https://github.com/emacs-dashboard/emacs-dashboard/
[dune]: https://github.com/ocaml/dune/
[valign]: https://github.com/casouri/valign/
[emacs-ccls]: https://melpa.org/#/ccls
[ocp-indent]: https://melpa.org/#/ocp-indent
[ocaml-lsp-git]: https://aur.archlinux.org/packages/ocaml-lsp-git
[evil-snipe]: https://github.com/hlissner/evil-snipe