来,用 Emacs 吧!

Table of Contents

teaser.png

1 emacs 是什么

Emacs 官网 给它的定义:

An extensible, customizable, free/libre text editor — and more.

Emacs 是个文本编辑器,就像 notepad.exe 一样,可以查看、编辑、修改文本文件。它的核心是个 lisp 方言 elisp 的解释器,人们可以使用 elisp 语言修改它、扩展它,于是它就有了各式各样的功能。现在人们可以使用它来收发邮件、浏览网页、编写程序代码。

对我而言, Org Mode 是我最喜欢的功能,可以说没有 Org Mode,我就不会使用 Emacs。 Org Mode 是个类似 Markdown 的文本标记语言,我使用它记录笔记,编写文档,管理进度。

我也经常使用 Emacs 编写程序代码, C/C++/Golang/Rust 的代码都在 Emacs 中编写,但使用 Idea 编写 java 代码。

实际上,Emacs 不管是作为笔记软件,还是作为代码编辑软件,它的功能并不是很丰富,表现也不完美,经常需要其它工具才能达成想要的功能。例如笔记里面的图片搜索,OCR,标签和分类;例如编辑源代码时的重构等等。这些都需要借助三方工具完成。我知道有人会反驳说某项功能有人写了 elisp 了可以用,如果需要某项功能你自己可以通过 elisp 这样这样实现,你们说的没错,可是我太菜了,对我而言就是没有。

简单地说:Emacs 是个配备了 elisp 解释器的文本编辑软件。他是个不太好用的文本编辑软件,但你可以使用 elisp 语言修改它的默认行为,增加它的功能。如果你愿意的话,这通过编写 elisp 代码可以实现你想要的几乎所有的功能。

2 Emacs 的缺点

这方面的问题,可以先看看 Emacs 社区的讨论:

可见它的问题就是不流行,不拉风 。在 Emacs 诞生的年代,使用计算机本身就是一件困难的事情,Emacs 的用户大多很专业, 也非常能面对困难,因此,Emacs/Vim/nano 等编辑器以及其他同时期的软件就专门为专业人员设计,根本不考虑 "开箱即用" 、"初学者友好"这些现代软件经常考虑的问题。它的目标一开始就是提供强大的功能,同时允许用户任意配置和修改,这个目标非常适合当时的环境,但它如今遇到了用户下沉的问题。

互联网普及以后,软件设计的目标发生了一些变化。在追求流量的今天,为了吸引更广泛的受众使用软件,"入门简单" 和 "界面美观" 成为了软件设计的重要目标。 而 Emacs 却反应缓慢:老用户不愿意改变;开发者都是业余时间开发,改变所需的代码量确实上不来。直到今天,Emacs 在一个新手眼里仍然表现得蹩脚:打开慢,复制粘贴文件窗口等概念与 office 等常用软件共识不同,默认界面丑。这些问题对于从 vscode 上转过来尝试使用的新用户来说基本是不能接受的。 “we hide the best functionalities until the user learns how to configure them (and some lisp).” 想要更好地使用 Emacs,用户必须先学习如何配置它。而 Emacs 的配置基本充满了个人色彩,每个人都按照自己的喜好配置 Emacs,使得每个人的快捷键、界面、功能都有很大不同,这让初学者相当迷惑。

难以吸引用户,造成了用户量少的问题。 "Anyone still using Emacs?" 类似的问题经常能遇到,一般问出来以后才发现并不孤单。Emacs 用户较少,社区不如现在流行的编辑器 (vscode) 活跃,因此开发人员也偏少,见过很多宣传介绍 Emacs 的文章,基本不说它多么完美,因为确实没脸说。它的很多功能都是能用但不甚完美。它的灵活性和自由似乎也与"完美"相冲突。

用户里有一类人特别让人讨厌,不知道其他软件的用户是不是也这样。这个工具有一定门槛,所以使用了之后,有一些人就开始自我感觉良好,到处吹嘘炫耀。这可能不是 Emacs 的问题,但确实多次引起我的厌烦。Emacs 不是小提琴,不是 CAD,他只是一个文本编辑软件,学会使用这个软件只意味着会使用,这不能称作一项技能。况且学会使用 Emacs 并不是什么困难的事情,学习的难度是 “懒得学” 那种难度,而不是 "学不会" 那种难度,使用 Emacs 不会产生任何可供炫耀的优越性。

3 使用 Emacs 的理由

既然 Emacs 有那么多缺点,为什么还有人使用它呢?下面的理由只是个人看法,并没有吸引所有人使用 Emacs,大概全是私货夹带私货,信不信服全看个人。

3.1 GPL 协议

工作中我比较喜欢寻找 MIT,BSD 这种可以 "白嫖" 商用的开源协议代码,这种行为简直就是开源运动中的一粒屎。我认为 GPL 协议才是真正尊重自己、尊重社区的公平的开源协议。作者将自己的劳动成果贡献出来,他人使用了这些劳动成果,于情于理都应该将自己的智慧回馈给。GPL 协议也是对用户的保护:开源后不能偷摸转闭源。 GPL 协议也是被法院承认的法律文件,曾经有过一些判例,其它开源协议我还没见过判决。这个判决书写的极好,如果对开源感兴趣,建议详细阅读。

Emacs 作者是 RMS,可以想见自由软件运动在 Emacs 中贯彻到何种程度。

3.2 Org Mode

Org Mode 是一种类似 Markdown 的文本标记语言,它规定了一些文本格式,比如开头写个 * 表示标题,开头写个 - 表示列表。可以使用他作笔记,以及管理代办事项。这篇文章也是使用 Org Mode 功能编写的。

org.png

在功能上,Org Mode 的优越性并不明显,我曾购买过 OneDrive 和 EverNote,但其中一个把我喜欢的功能删了,另一个开始给我发广告。后来的 notion 没用过,应该比 Org Mode 好用几倍吧。 Org Mode 的优势在于长期的可用性:软件是你的,文件是你的,功能也是你的,没人会因为网络封锁或者公司倒闭而失去自己的软件和内容。

使用上,Org Mode 是纯文本的,能让用户把注意力集中到内容上,而不是格式、链接、动画。

这些优势使用 markdown 也能做到,Emacs 也提供 markdown 的支持,但 Emacs 上使用 Org Mode 是大家共同的使用习惯,拥有很多现成的工具,遇到问题也更容易在互联网上找到解决办法。

Org Mode 是自己的,在团队管理与合作中使用 Org Mode 并没有那么方便,它没法像内部办公系统一样直接管理团队的任务,让所有人都能看到大家的进度。把 Org 文件发给同事,同事也没法打开,因为不能要求所有人都学习使用 Org Mode。所以我的 Org Mode 内容只是个人编辑,个人阅读。当然如果有人非要看我 Org 文件的内容,我会将其导出为 PDF 或 HTML 再发送给他。

Org Mode 的优势之一是纯文本,这使得我们常用的一些软件工具也可以生效。比如 grep, sed , awk

3.3 定制 (折腾)

一次使用,终生配置,满足你的折腾欲望。Emacs 是个 elisp 解释器,可以使用它实现各种各样的功能,打造只属于你自己的软件。

我相信对很多人而言这是个非常吸引人的特性,Emacs 可以作为你的玩具。

3.4 专注

Emacs 能实现的功能实在太多,有的人早上打开电脑以后就打开一次 Emacs,不用切换到别的软件,直接在 Emacs 上编辑、运行命令、收发邮件。只打开需要的功能, 保持在一个窗口上作业,没有外务干扰,免于多个软件反复切换的麻烦,可以更专注于工作内容。

3.5 不屈

我在怪物猎人游戏里非常喜欢的一个技能:"不屈"。它可以增加非常高的属性数值,但要失败至少一次技能效果才会触发。应该没人在了解了 Emacs 的全部内容之后才开始使用它,这就意味着会遇到各种各样的麻烦。在摸索或者与互联网上的朋友讨教之后很好地将麻烦解决,是个不错的体验。

4 使用 Emacs 的第一步

去下载一份, 安装好后打开,打开的界面输入 C-h t (在普通键盘上指的是快捷键 Ctrl+h 随后按 t ) 。 这将在 Emacs 中打开一个快速指南,按照这个指南操作一遍,你就学会 Emacs 基本操作了。

tt.png

其实有一个配置可以让你使用 vim 的方法来操作 Emacs,但学习下 Emacs 的操作总没有坏处,毕竟你不是在使用 vim。

5 我是如何使用 Emacs 的

我的经验其实无关紧要,在快速入门之后,推荐使用下列配置之一,然后关闭这个页面直接使用。

  • spacemacs 适合 vim 用户,也适合大多数初学用户,他们用 layer 组织的配置使得添加功能非常方便,基本上是开箱即用,而且界面美观,文档齐全。
  • doom-emacs An Emacs framework for the stubborn martian hacker. 如果你使用 spacemacs 配置功能时不断地遇到麻烦,可以试试这个,适合不那么初学的朋友。
  • percell 的 emacs 配置 文件组织得很好,我的配置也是从他这里学习的,一些文件也是直接复制它的。
  • scimax 作科研写论文推荐这个。
  • 我的 emacs 配置 不太推荐,结构跟 purcell 的很像,我会非常任性地修改它,你可以参考这个配置写一个自己的配置。

5.1 运行环境

我通常在 WSL 的 Ubuntu 终端中运行 Emacs 版本。偶尔也会在 Windows 中运行 Emacs 的图形界面版本。抱歉没有 Mac。

为了在 WSL 中使用 Windows 上的字体,需要修改 /etc/fonts/local.conf 文件内容如下,然后 fc-cache -f -v

<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
    <dir>/mnt/c/Windows/Fonts</dir>
</fontconfig>

注意字体是有版权的,一般我会用 source-han-sans,也叫 “思源黑体”。下载 OTF 文件安装即可。在 Linux 上复制到路径 ~/.local/share/fonts/ 然后 fc-cache -f -v 。在 windows 上右键安装即可。

下载图标字体 all-the-icons ,复制 all-the-icons/fonts/*.tty~/.local/share/fonts/ 然后 fc-cache -f -v 。在 windows 上选择所有 ttf 文件右键安装即可。

按照上面的步骤安装 source code pro 字体。

5.2 配置 Emacs

配置文件在 ~/.emacs.d/init.el 。可以把所有内容都写在里面,也可以参考 purcel 的结构按照功能和目的分成多个文件。配置文件修改完成后重启 emacs 生效。

5.2.1 字符编码

UTF-8 是现在文字编码的事实标准,大多数人都使用它,随大流即可。配置内容如下:

(when (fboundp 'set-charset-priority)
  (set-charset-priority 'unicode))
(prefer-coding-system 'utf-8-unix)
(setq locale-coding-system 'utf-8-unix)

5.2.2 扩展 elisp

引用 subr-x 可以使用 elisp 中有很多辅助宏,引用 cl-lib 可以使用 common lisp 中一些很有用的函数和宏。

(eval-when-compile
  (progn
    ;; Import some common lisp functions and macros
    (require 'cl-lib)
    ;; subr-x has a ton of useful macros and functions, and it would be nice
    ;; if it were available to packages that support emacs versions down to
    ;; 24.1.
    (eval-when-compile (require 'subr-x))))     

5.2.3 包管理

Emacs 有许多三方包,都可以从 MELPA 上下载, package 包提供这项功能。一般不直接使用 package 包,有个更方便的包 use-package

我们创建了个文件夹 ~/.emacs.d/cache/ ,设定把包都下载到这个位置。

默认 melpa 下载地址太慢了,这里用 tuna 的镜像。

必须调用一遍 package-refresh-contents ,不然 package 包会报错,后面会安装一个叫 “no-littering” 的包,它会创建 ~/.emacs.d/var/ 文件夹,我们通过判断这个文件夹是否已经创建,来确定是不是第一次加载配置,如果是第一次加载配置,就运行 package-refresh-contents

如果后续出现包安装问题,先 M-x (Alt-x) 输入 package-refresh-contents 回车来更新包信息,再重启 Emacs。

有些包可能通过 use-package 下载不了,可以将其下载到 ~/.emacs.d/vendor/ 目录下面,然后用 (require 'package-name) 加载。

(defvar my-cache-dir (concat user-emacs-directory "cache/")
   "Folder to save temperary files generated by Emacs.")
  (unless (file-exists-p my-cache-dir)
     (make-directory my-cache-dir t))
;; Install into separate package dirs for each Emacs version, to prevent
;; bytecode incompatibility
(let ((versioned-package-dir
       (expand-file-name (format "elpa-%s.%s" emacs-major-version
                 emacs-minor-version)
             my-cache-dir)))
  (setq package-user-dir versioned-package-dir))

;; Fast mirror for Chinese mainland
(setq package-archives
      '(("gnu"    . "https://mirrors.tuna.tsinghua.edu.cn/elpa/gnu/")
        ("melpa"  . "https://mirrors.tuna.tsinghua.edu.cn/elpa/melpa/")
        ("org-cn" . "https://mirrors.tuna.tsinghua.edu.cn/elpa/org/"))) 

;; Run package-refresh-contents if first start.
;; use folder "var" to check if it is the first start, see no-littering.
(unless (file-exists-p (expand-file-name "var" user-emacs-directory))
  (package-refresh-contents))

(setq package-enable-at-startup nil)
(package-initialize)

;; Initialize use-package
(unless (package-installed-p 'use-package)
  (package-install 'use-package))
;; this i only needed once
(eval-when-compile
  (require 'use-package))
(setq use-package-always-ensure t)

;; hiding mode line diplay of minor-modes
(use-package diminish)

;; load packages cloned into vendor/...
(defun add-subdirs-to-load-path (parent-dir)
  "Add every non-hidden subdir of PARENT-DIR to `load-path'."
  (let ((default-directory parent-dir))
    (setq load-path
          (append
           (cl-remove-if-not
            #'file-directory-p
            (directory-files (expand-file-name parent-dir) t "^[^\\.]"))
           load-path))))
(add-subdirs-to-load-path
 (expand-file-name "vendor/" user-emacs-directory))

;; Handier way to add modes to auto-mode-alist
(defun add-auto-mode (mode &rest patterns)
  "Add entries to `auto-mode-alist' to use `MODE' for all given file `PATTERNS'."
  (dolist (pattern patterns)
    (add-to-list 'auto-mode-alist (cons pattern mode))))

最近比较流行的包管理工具叫 straight.el 。我试过失败了,大概需要梯子吧。

5.2.4 让 .emacs.d 更干净

默认情况下,很多包都会把临时文件创建到 ~/.emacs.d/ 路径下,这会导致配置目录混乱。 no-littering 包会把这些临时文件的位置都改写为 ~/.emacs.d/var/~/.emacs.d/etc/ 目录。

Emacs 会把一些生成的配置内容写到 init.el 里,别让它这么搞,写到 ~/.emacs.d/etc/custom.el 里就可以。

后面我们会配置 recentf ,它记录我们打开过哪些文件,我们在这里让它忽略这些临时文件。

;; no littering, keep .emacs.d clean
(use-package no-littering
  :config
  (with-eval-after-load 'recentf
    (set 'recentf-exclude
         '(no-littering-var-directory
           no-littering-etc-directory
           (expand-file-name "elpa" user-emacs-directory)
           (expand-file-name "cache" user-emacs-directory))))
  (setq custom-file (no-littering-expand-etc-file-name "custom.el")))

5.2.5 选择一个配色方案

选择一个喜欢的配色方案。 solarized 不错,monokai 也很好,我选择 spacemacs :

;; theme
(use-package spacemacs-theme
  :defer t
  :init (load-theme 'spacemacs-dark t))

5.2.6 which-key

我总记不住快捷键, which-key 是快捷键提示包。

wk.png

;; feel free to forget shortkeys
(use-package which-key
  :diminish which-key-mode
  :config
  (which-key-mode))

5.2.7 字体设置

按照喜好设置吧。

;; default
(set-face-attribute 'default nil :font (font-spec :family "Source Code Pro"
                          :size 14))

(when (eq system-type 'darwin)
  (setq fonts '("SF Mono" "冬青黑体简体中文"))
  (set-fontset-font t 'unicode "Apple Color Emoji" nil 'prepend)
  (set-face-attribute 'default nil :font
                      (format "%s:pixelsize=%d" (car fonts) 14)))

(when (eq system-type 'windows-nt)
  (setq fonts '("Source Code Pro" "思源黑体"))
  (set-fontset-font t 'unicode "Segoe UI Emoji" nil 'prepend)
  (set-face-attribute 'default nil :font
                      (format "%s:pixelsize=%d" (car fonts) 20)))

(when (eq system-type 'gnu/linux)
  (setq fonts '("Source Code Pro" "思源黑体"))
  (set-fontset-font t 'unicode "Noto Color Emoji" nil 'prepend)
  (set-face-attribute 'default nil :font
                      (format "%s:pixelsize=%d" (car fonts) 20)))

5.2.8 括号匹配

smartparens 规定了括号匹配规则。 paren 可以把光标所在位置的括号对应的位置高亮显示。 rainbow-delimiters 可以将对称的括号用同一种颜色标记出来。

如果觉得括号太难搞,可以 M-x smartparens-strict-mode 关掉严格模式。

rb.png

;; parens
(use-package smartparens
  :diminish nil
  :config
  (sp-use-smartparens-bindings))
(use-package smartparens-config
  :diminish nil
  :ensure smartparens
  :config (progn (show-smartparens-global-mode t)))
(add-hook 'prog-mode-hook 'turn-on-smartparens-strict-mode)
(use-package paren
  :config
  (setq show-paren-delay 0.1
        show-paren-when-point-in-periphery t))
(use-package rainbow-delimiters
  :hook ((prog-mode . rainbow-delimiters-mode)))

smartparens 很多命令,都是 sp-* 开头的。比较常用的:

命令 作用
sp-rewrap-{round/curly/square} x -> (x)
sp-unwrap-sexp (x) -> x
sp-forward-slurp-sexp [a b] c -> [a b c]
sp-forward-barf-sexp [a b] c -> [a] b c

5.2.9 去掉菜单栏、工具栏、滚动条

经历过快速入门指南之后,这些都没必要了。

;; disable menu bar, tool-bar
(push '(menu-bar-lines . 0)   default-frame-alist)
(push '(tool-bar-lines . 0)   default-frame-alist)
(push '(vertical-scroll-bars) default-frame-alist)

5.2.10 用 y-n 代替 yes-no

yn.png

少打字保护手指。

;; oh my freaking god, just take my damn answer
(defalias 'yes-or-no-p 'y-or-n-p)

5.2.11 不创建临时文件

默认情况下,Emacs 为每个打开的文件创建一些临时的文件,这会搞乱我们的目录,不需要它。

;; Don't generate backups or lockfiles. While auto-save maintains a copy so long
;; as a buffer is unsaved, backups create copies once, when the file is first
;; written, and never again until it is killed and reopened. This is better
;; suited to version control, and I don't want world-readable copies of
;; potentially sensitive material floating around our filesystem.
(setq create-lockfiles nil
      make-backup-files nil
      ;; But in case the user does enable it, some sensible defaults:
      version-control t     ; number each backup file
      backup-by-copying t   ; instead of renaming current file (clobbers links)
      delete-old-versions t ; clean up after itself
      kept-old-versions 5
      kept-new-versions 5)
;; But turn on auto-save, so we have a fallback in case of crashes or lost data.
;; Use `recover-file' or `recover-session' to recover them.
(setq auto-save-default t
      ;; Don't auto-disable auto-save after deleting big chunks. This defeats
      ;; the purpose of a failsafe. This adds the risk of losing the data we
      ;; just deleted, but I believe that's VCS's jurisdiction, not ours.
      auto-save-include-big-deletions t
      auto-save-file-name-transforms
      (list (list "\\`/[^/]*:\\([^/]*/\\)*\\([^/]*\\)\\'"
                  ;; Prefix tramp autosaves to prevent conflicts with local ones
                  (concat auto-save-list-file-prefix "tramp-\\2") t)
            (list ".*" auto-save-list-file-prefix t)))

5.2.12 自动加载已修改的文件

如果其它程序修改了文件,Emacs 应该同步显示新的内容。

;; Keeping buffers automatically up-to-date.
(require 'autorevert)
(global-auto-revert-mode 1)
(setq auto-revert-verbose t
      auto-revert-use-notify nil
      auto-revert-stop-on-user-input nil)

5.2.13 快速切换窗口

使用 C-x o 快速切换窗口。

xo.png

;; switch window fast
(use-package ace-window
  :bind ("C-x o" . ace-window))

5.2.14 美化 mode-line

默认的 mode-line 不是很好看,用 doom-modeline 好一些。

dml.png

(use-package all-the-icons)

(use-package doom-modeline
  :hook
  (after-init . doom-modeline-mode)
  :config
  (setq doom-modeline-project-detection 'project))

5.2.15 浏览最近打开文件

recentf 包提供了这样的功能,使用 C-x C-b 打开。

rf.png

(defun my/recentf-save-list-silence ()
  "Save recentf."
  (interactive)
  (let ((mesage-log-max nil))
    (if (fboundp 'shut-up)
        (shut-up (recentf-save-list))
      (recentf-save-list)))
  (message ""))

(defun my/recentf-cleanup-silence()
  "Clean recentf."
  (interactive)
  (let ((message-log-max nil))
    (if (fboundp 'shut-up)
        (shut-up (recentf-cleanup))
      (recentf-cleanup)))
  (message ""))

(use-package recentf
  :config
  (recentf-mode 1)
  (global-set-key (kbd "C-x C-b") 'recentf-open-files)
  (setq recentf-max-saved-items 10000)
  (setq recentf-max-menu-items 5000)
  (setq recentf-auto-cleanup 'never)
  (add-hook 'focus-out-hook #'my/recentf-save-list-silence t nil)
  (add-hook 'focus-out-hook #'my/recentf-cleanup-silence t nil))

5.2.16 自动补全

包括 M-x 的自动不全,编辑区的自动补全。

(use-package counsel
  :config
  (global-set-key (kbd "M-x") 'counsel-M-x)
  (global-set-key (kbd "M-y") 'counsel-yank-pop))

(use-package yasnippet
  :hook
  (prog-mode . yas-minor-mode-on)
  (text-mode . yas-minor-mode-on)
  (yas-minor-mode . (lambda () (diminish 'yas-minor-mode)))
  :config
  (yas-global-mode 1))
(use-package yasnippet-snippets)

;; Add yasnippet support for all company backends
;; https://github.com/syl20bnr/spacemacs/pull/179
(defvar company-mode/enable-yas t
  "Enable yasnippet for all backends.")
(defun company-mode/backend-with-yas (backend)
  "If company-mode/enable-yas, put company-yasnippet into BACKEND."
  (if (or (not company-mode/enable-yas)
      (and (listp backend) (member 'company-yasnippet backend)))
      backend
    (append (if (consp backend) backend (list backend))
            '(:with company-yasnippet))))
(use-package company
  :ensure t
  :diminish company-mode
  :config
  (add-hook 'after-init-hook 'global-company-mode)
  (setq company-idle-delay 0.2
        company-tooltip-align-annotations t
        company-tooltip-limit 20
        company-show-quick-access t
        company-minimum-prefix-length 1)
  (setq company-backends
    (mapcar #'company-mode/backend-with-yas company-backends)))

(use-package company-box
  :hook (company-mode . company-box-mode))

5.2.17 格式检查

flycheck 提供了格式检查功能。记得安装检查工具,如 pylint, eslint 等。

flycheck-annotated.png

5.2.18 搜索

文件内搜索通过 C-s 调用 swiper

search.png

想要跨文件搜索可以使用 ag 包 要使用 ag 包需要安装 ag程序

apt-get install silversearcher-ag
apt install fd-find

ag_el_screenshot.png

;; use swiper to replace i-search
(use-package swiper
  :bind ("C-s" . swiper))

(use-package ag)

5.2.19 更方便的打开文件

ido 包提供了这样的功能。

(use-package ido
  :config
  (ido-mode 1)
  )

(use-package ido-vertical-mode
  :config
  (ido-vertical-mode 1)
  (setq ido-vertical-define-keys 'C-n-and-C-p-only)
  (setq ido-vertical-show-count t)
  (setq ido-use-faces t)
  (set-face-attribute 'ido-vertical-first-match-face nil
                      :background nil
                      :foreground "orange")
  (set-face-attribute 'ido-vertical-only-match-face nil
                      :background nil
                      :foreground nil)
  (set-face-attribute 'ido-vertical-match-face nil
                      :foreground nil))

5.2.20 让 Emacs 识别文件在项目里

projectile 提供了这个功能。 C-c p 会列举它的快捷键,其中包括在项目中搜索,切换项目等。

(use-package projectile
  :config
  (projectile-mode +1)
  (setq projectile-enable-caching t)
  (setq-default projectile-mode-line-prefix " Proj")
  (define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map))

5.2.21 git

magit 包是 Emacs 的 git 插件,使用 C-x g 打开,然后 ? 打开快捷键帮助界面,是个非常好用的工具。

(use-package magit)

5.2.22 Org Mode 配置

Org Mode 是个纯文本标记语言,Emacs 自带的,只要文件后缀是 .org 就会识别为 orgmode 格式。

我经常使用 OrgMode 的 TODO 功能,只要 * TODO 开头的行都会被识别为 TODO。所有TODO待办事项我都保存到 ~/TODO/*.org 文件里。这个配置下,可以使用 M-x org-agenda t 列举本周需要完成的事项。使用 C-c C-t 可以切换事项的状态。

参考 org 文档。

(defun my/org-mode-setup ()
    "Setup orgmode."
    (org-indent-mode)
    (variable-pitch-mode 1)
    (auto-fill-mode 0)
    (visual-line-mode 1))

(use-package org
  :hook (org-mode . my/org-mode-setup)
  :config
  (setq org-ellipsis " ▼")
  (setq org-agenda-start-with-log-mode t)
  (setq org-log-done 'time)
  (setq org-log-states-order-reversed t)
  (setq org-log-into-drawer t)
  (setq org-agenda-files
        (file-expand-wildcards "~/TODO/*.org"))
  (setq org-todo-keywords
      '(
        (sequence "IDEA(i)" "TODO(t)" "STARTED(s)" "NEXT(n)" "WAITING(w)" "|" "DONE(d)")
        (sequence "|" "CANCELED(c)" "DELEGATED(l)" "SOMEDAY(f)"))))

(use-package org-bullets
  :after org
  :hook (org-mode . org-bullets-mode))

5.2.23 F8 侧边打开项目目录

neot.png

这是 neotree 的功能。

(use-package neotree
  :config
  ;; f8 to view tree strucure of folder
  (defun neotree-project-dir ()
    "Open NeoTree using the git root."
    (interactive)
    (let ((project-dir (projectile-project-root))
          (file-name (buffer-file-name)))
      (neotree-toggle)
      (if project-dir
          (if (neo-global--window-exists-p)
              (progn
                (neotree-dir project-dir)
                (neotree-find file-name)))
        (message "Could not find git project root."))))
  (global-set-key [f8] 'neotree-project-dir)
  ;; switch with projectile
  (use-package projectile)
  (setq projectile-switch-project-action 'neotree-projectile-action))

使用 F8 开启,F8 关闭。

5.2.24 undo-tree

C-x u 打开 undo-tree,undo/redu 都在此完成。

undo.png

(use-package undo-tree
  :diminish nil
  :config
  (global-undo-tree-mode)
  (setq undo-tree-auto-save-history t))

5.2.25 LSP

LSP 是微软开发的语言服务器协议,一般开启一个 language-server 读取解析源代码文件, Emacs 通过 LSP 协议与 language-server 交流,获得函数引用、定义、文档等信息。

lsp-mode 实现了 LSP 的功能。lsp-ui 可以让 lsp-mode 的结果更好看些。

(use-package lsp-mode
  :commands (lsp lsp-deferred)
  :hook
  (lsp-mode . lsp-enable-which-key-integration)
  :init
  (setq lsp-keymap-prefix "C-c l")
  :config
  (eval-after-load 'whichkey
    (progn
    (setq lsp-enable-file-watchers t)
    (lsp-enable-which-key-integration t))))

;; Optional - provides fancier overlays.
(use-package lsp-ui
  :ensure t
  :requires lsp-mode flycheck
  :after (lsp-mode)
  :commands (lsp-ui-mode)
  :bind
  (:map lsp-ui-mode-map
        ;; use lsp to search definitions (M-.)
        ([remap xref-find-references] . lsp-ui-peek-find-references)
        ;; use lsp to search references (M-?)
        ([remap xref-find-definitions] . lsp-ui-peek-find-definitions)
        ;; symbol list
        ("C-c u" . lsp-ui-imenu))
  :hook (lsp-mode . lsp-ui-mode)
  :config
  (define-key lsp-ui-mode-map [remap xref-find-definitions]
    #'lsp-ui-peek-find-definitions)
  (define-key lsp-ui-mode-map [remap xref-find-references]
    #'lsp-ui-peek-find-references))

(setq lsp-headerline-arrow "/")

配置 (seq lsp-headerline-arrow "/") 是因为在终端界面的 emacs 里显示不了 all-the-icons 的字体,使用 "/" 代替。可能别的地方也会有问题,但这个最明显。

5.2.26 Golang

lsp-ui.png

Golang 是我的常用开发语言,使用 go-mode 包支持它。同时启用 lsp-mode

注意要启用 LSP ,必须安装 gopls :

# export GOPROXY=https://goproxy.io,direct
go install golang.org/x/tools/gopls@latest

5.2.27 C/C++

C/C++ 是我的主要开发语言,我使用 clangd 作为 language-server 。 ccls 表现得更好些,但是 clangd 比较快。

lsp 都需要一个项目跟目录的 compile_commands.json 文件,可以通过 cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON 选项生成,也可以通过 Bear 生成。

可以使用 clang-format 格式化代码。

apt-get install bear

# use =apt search clangd= to search the latest version
apt install clangd-12 && update-alternatives --install /usr/bin/clangd clangd /usr/bin/clangd-12 100
# or
apt install ccls

# https://clangd.llvm.org/installation.html
apt install clang-format-12 && update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-12 100
(use-package cc-mode
  :hook
  (c-mode . lsp)
  (c++-mode . lsp)
  ;; TODO: open these hooks.
  ;; Sadly I work with a dirty team, i will change the whole code base if
  ;; uncommend config below.
  ;; (before-save-hook . lsp-format-buffer)
  ;; (before-save-hook . lsp-organize-imports)
  :config
  (define-key c-mode-base-map (kbd "M-/") 'ff-find-related-file)
  ;; Open a header file in C++ mode by defaults
  (add-auto-mode 'c++-mode "\\.h\\'"))

(use-package cmake-mode
  :init
  :mode (("CMakeLists\\.txt\\'" . cmake-mode)
     ("\\.cmake\\'" . cmake-mode)))

;; C++20 highlighting
(use-package modern-cpp-font-lock
  :diminish nil
  :hook
  (c++-mode . modern-c++-font-lock-mode)
  (modern-c++-font-lock-mode . (lambda () (diminish
                       'modern-c++-font-lock-mode))))

;; google cpplint
(use-package flycheck-google-cpplint
  :config
  (with-eval-after-load 'flycheck
    '(progn
       (require 'flycheck-google-cpplint)
       ;; Add Google C++ Style checker.
       ;; In default, syntax checked by Clang and Cppcheck.
       (flycheck-add-next-checker 'c/c++-cppcheck
                                  '(warning . c/c++-googlelint)))))

;; google style, but with 4 space indent.
(defun google-set-c-style-with-4-indent ()
  "Set current buffer to google style, but with 4 space indent."
  (interactive)
  (make-local-variable 'c-tab-always-indent)
  (setq c-tab-always-indent t)
  (c-add-style "Google" google-c-style t)
  (setq tab-width 4
        c-indent-tabs-mode t
        c-indent-level 4
        c-basic-offset 4))

(use-package google-c-style)
(add-hook 'c-mode-common-hook 'google-set-c-style-with-4-indent)

5.2.28 其它功能

都看到这里了,你应该也知道去哪里能找到自己想要的包,如果没有,就去 开发一个


By .