【Emacs】坎坷之路,终抵群星
2023-04-17 11:15:49

关键词:Emacs、Emacs Lisp

引子

Emacs是什么?让我们来看看Emacs吧的标语:“不能煮咖啡的编辑器不是一个好操作系统”。

首先,Emacs是个编辑器,由Richard M Stallman等人最开始编写而成,后来有很多的衍生,现在常常使用的是1984年由Stallman发起维护的GNU Emacs,如果您对这段历史感兴趣,强烈推荐您读一读Steven Levy的《黑客》这本书。这本书对黑客道德、开源文化的历史等等有着非常详细的介绍。。

与此同时,Emacs又不只是一个编辑器。经过合理配置之后,Emacs可以变成不同语言的编程环境,可以编写Markdown、LaTeX文件,可以浏览PDF、网页,可以播放音乐、视频,也可以接发邮件,进行IRC聊天等等。至于“煮咖啡”“操作系统”一类的说法,仅仅是为了体现其功能强大而已。

本文只作为自己的Emacs学习记录而已,若可以给您一些启发,再好不过。用多了Emacs,真觉得把手从键盘挪到鼠标上太费劲了。

我的配置已经上传到github上:coder109/.emacs.d。Emacs的配置主要用到Emacs Lisp语言。

为什么要用Emacs?

  • 快捷键多。熟练之后,写代码双手几乎不需要离开键盘。
  • 自由度、模块化程度高。有需要的配置可以找现成的模块,或者用强大的Emacs Lisp写个新的。如果自己配置,可以完全贴近自己的需求,避免功能冗余。
  • 兼容性高。Window、Linux、MacOS都可以用Emacs。
  • 配置后功能强大。需要其它软件的频率会降低。
  • 资源比较丰富。自带文档、Emacs China论坛里都有很多资料可供参考。

1. 配置文件的结构

通常来说,一般采用如下的方法:

  • ~/.emacs.d/init.el作为主配置文件,通过设置,Emacs可以从这个文件中调取配置。

  • ~/.emacs.d/lisp/作为其它配置文件的文件夹,init.el可以从这个文件夹里读取其它的配置文件。你可以使用providerequire这两个过程进行配置文件的读取,比如,在~/.emacs.d/lisp/init-emacs.el的末尾写下如下的代码:

    1
    (provide 'init-emacs)

    这就相当于把init-emacs.el文件以init-emacs的名字暴露在init.el前面,而在init.el中,只需要:

    1
    2
    (add-to-list 'load-path "~/.emacs.d/lisp")
    (require 'init-emacs)

    就可以将init-emacs.el引入主配置文件了。

2. 包的安装

可以手动安装,输入如下命令:

1
M-x package-install

然后安装想要的包就可以了。

但这显然不方便,可以采用use-package包管理器统一管理包的安装,在相应的设置文件中设置:

1
2
3
4
5
6
7
8
9
;; 如果没安装use-package,就安装它。
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package))

(require 'use-package)

;; 自动安装所有需要的软件包
(setq use-package-always-ensure t)

之后你就可以应用它了。格式大概如下:

1
2
3
(use-package rainbow-delimiters
:ensure t
:hook (prog-mode . rainbow-delimiters-mode))

3. 主题设置

您可以根据自己所需,进行配置。

1. 基本的UI设置

令工具栏、滚动栏等不显示,并显示行号。在右下角显示时间。

1
2
3
4
5
6
7
8
9
(tool-bar-mode -1)
(scroll-bar-mode -1)
(menu-bar-mode -1)
(global-display-line-numbers-mode t)
(column-number-mode t)

(display-time-mode t)
(setq display-time-24hr-format t)
(setq display-time-day-and-date t)

2. 主题和modeline设置

您可以去github上搜索主题。笔者采用moe-theme+powerline的方案,顺便使用nyan-mode,在右下角显示一个彩虹猫,权当装饰,配置如下:

1
2
3
4
5
6
7
8
(add-hook 'after-init-hook 'nyan-mode)

(use-package moe-theme)
(show-paren-mode t)
(setq moe-theme-mode-line-color 'blue)
(setq show-paren-style 'expression)
(moe-dark)
(powerline-default-theme)

3. 字体

笔者曾经在Emacs China论坛上找到一个中英文都能妥善设置的方案:

1
2
3
4
5
6
7
8
9
10
11
(setq fonts '("Source Code Pro Medium" "Microsoft YaHei"))
(set-fontset-font t 'unicode "Segoe UI Emoji" nil 'prepend)
(set-face-attribute 'default nil
:font (format "%s:pixelsize=%d" (car fonts) 20))

(setq default-frame-alist '((font . "Source Code Pro-16")))

(if (display-graphic-p)
(dolist (charset '(kana han symbol cjk-misc bopomofo))
(set-fontset-font (frame-parameter nil 'font) charset
(font-spec :family (car (cdr fonts))))))

4. 欢迎界面

使用dashboard

1
2
3
4
5
6
7
8
(use-package dashboard
:ensure t
:config (dashboard-setup-startup-hook))
(setq dashboard-banner-logo-title "Let's all love Lain...")
(setq dashboard-startup-banner "/home/bruce/Lain_avatar.png")
(setq dashboard-image-banner-max-height 300) ;; 设置图片高度
(setq dashboard-image-banner-max-width 300) ;; 设置图片宽度
(setq dashboard-center-content t)

4. 编程相关设置

1. 编码设置

笔者将编码设置为UTF-8。毕竟,看见乱码再修改编码方式实在令人恼火。

1
2
3
4
5
6
7
8
(set-keyboard-coding-system 'utf-8)
(set-clipboard-coding-system 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-buffer-file-coding-system 'utf-8)
(set-selection-coding-system 'utf-8)
(modify-coding-system-alist 'process "*" 'utf-8)
(setq default-process-coding-system '(utf-8 . utf-8))
(setq-default pathname-coding-system 'utf-8)

2. LSP与DAP的设置

LSP,全称叫做Language Server Protocol,大概的作用就是能提供语法高亮、语法提示功能的工具。

DAP,全称叫做Debug Adapter Protocol,和调试有关的工具。

建议参考github进行lsp的配置以及相应语言服务器的下载。

我的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
(use-package lsp-mode
:init
;; set prefix for lsp-command-keymap (few alternatives - "C-l", "C-c l")
(setq lsp-keymap-prefix "C-c l")
:hook (;; replace XXX-mode with concrete major-mode(e. g. python-mode)
(c-mode . lsp)
(julia-mode . lsp)
(lua-mode . lsp)
(ocaml-mode . lsp)
(perl-mode . lsp)
(dart-mode . lsp)
(haskell-mode . lsp)
(go-mode . lsp)
(php-mode . lsp)
(racket-mode . lsp)
(ruby-mode . lsp)
(rust-mode . lsp)
(erlang-mode . lsp)
(elixir-mode . lsp)
(clojure-mode . lsp)
(verilog-mode . lsp)
)
:commands lsp
:init
(setq lsp-auto-configure t
lsp-auto-guess-root t
lsp-idle-delay 0.500
lsp-session-file "~/.emacs/.cache.lsp-sessions"))
(require 'lsp-python-ms)
(setq lsp-python-ms-auto-install-server t)
(add-hook 'python-mode-hook 'lsp)
(add-hook 'c++-mode-hook 'lsp)

;; LSP-latex
(setq lsp-latex-java-executable "usr/bin/java")
(setq lsp-latex-texlab-jar-file 'search-from-exec-path)

;; LSP-juliaLang
(use-package lsp-julia
:config
(setq lsp-julia-default-environment "~/.julia/environments/v1.7"))

;; Set up before-save hooks to format buffer and add/delete imports.
;; Make sure you don't have other gofmt/goimports hooks enabled.
(defun lsp-go-install-save-hooks ()
(add-hook 'before-save-hook #'lsp-format-buffer t t)
(add-hook 'before-save-hook #'lsp-organize-imports t t))
(add-hook 'go-mode-hook #'lsp-go-install-save-hooks)

;; LSP-Haskell
(add-hook 'haskell-mode-hook #'lsp)
(add-hook 'haskell-literate-mode-hook #'lsp)

;; optional
(use-package lsp-ui :commands lsp-ui-mode)
;; if you are helm user
(use-package helm-lsp :commands helm-lsp-workspace-symbol)
;; if you are ivy user
;;(use-package lsp-ivy :commands lsp-ivy-workspace-symbol)
;;(use-package lsp-treemacs :commands lsp-treemacs-errors-list)

;; Debugger
(use-package dap-mode
:ensure t
:commands dap-debug
:custom
(dap-auto-configure-mode t)
:config
(dap-ui-mode 1)
)
(require 'dap-python)

3. Emacs命令补全与加强

我使用Helm+smex

1
2
(helm-mode 1)
(smex-initialize)

4. 文本补全

我使用company。它会自动根据你在当前文件中输入过的单词进行补全。

1
2
3
4
5
(global-company-mode t)
(setq company-minimum-prefix-length 2)
(setq company-idle-delay 0)
(setq company-selection-wrap-around t)
(company-quickhelp-mode)

5. 括号的分级显示

Lisp语言的一大特点就是:括号太tm多了。

有必要对不同等级的括号,用不同的颜色显示出来,我选用rainbow-delimiters

1
2
3
(use-package rainbow-delimiters
:ensure t
:hook (prog-mode . rainbow-delimiters-mode))

6. 撤销操作树

通过使用undo-tree包,可以通过C-u快捷键方便地回滚到过去的状态。我的第二行配置是为了将undo-tree文件,也就是记录撤销操作树的文件都存到统一的文件夹中,相信我,这很有必要。

1
2
(add-hook 'after-init-hook 'global-undo-tree-mode)
(setq undo-tree-history-directory-alist '(("." . "~/.emacs.d/undo")))

7. 语法检查

我使用flycheck,感觉比自带的flymake好用一些。

1
(global-flycheck-mode t)

8. Vim键位

Vim键位实在是太香了。我使用evil包,将Vim的键位和Emacs的键位结合在一起,非常好用。

1
(evil-mode 1)

9. 搜索强化

您可以试一试ieditavy

10. 杂项

我还使用用于项目管理的projectile和版本管理的magit

5. 自编写过程

我自己写了几个自动编译运行的过程,有挺多bug的。

但是我懒得改了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(defun c-quick-compile ()
(interactive)
(compile (format "gcc %s && ./a.out" (buffer-name) ) t)
)

(defun c-compile-no-run ()
(interactive)
(compile (format "gcc %s" (buffer-name)))
)

(defun compile-no-run ()
(interactive)
(if (eq major-mode 'java-mode)
(java-compile-no-run))
(if (eq major-mode 'c-mode)
(c-compile-no-run))
)

6. 键位绑定

您可以使用如下的键位绑定过程:

1
2
3
4
5
(global-set-key (kbd "C-c a l") 'avy-goto-line)
(global-set-key (kbd "C-c a a") 'avy-copy-line)
(global-set-key (kbd "C-c a b") 'avy-copy-region)
(global-set-key (kbd "C-c a m") 'avy-move-line)
(global-set-key (kbd "C-c a n") 'avy-move-region)

后记

参考了Emacs China、Reddit(r/emacs)、知乎、CSDN等平台的配置。有一些小package没有列出。本文会持续更新。私以为Emacs的配置也是如此,从编程菜鸟到编程老鸟的过程中,Emacs的配置也会随着需求逐渐变化迭代,有现成的包就用,没有符合自己需求的包,便自己写一个。