本文主要讲解了 Emacs 的使用技巧和高效方法,包括各方面的实用提示。
TRAMP
TRAMP(Transparent Remote Access, Multiple Protocols)是一个扩展名,顾名思义,它可以通过多种协议对远程文件进行透明访问。当提示输入文件名时,输入一个特定的表单就会调用 TRAMP。举几个例子:
以根权限打开 /etc/hosts
前提示输入根密码:
C-x C-f /sudo::/etc/hosts
以“你”的身份通过 SSH 连接到 “remotehost”,并打开文件 ~/example.txt
:
C-x C-f /ssh:you@remotehost:~/example.txt
TRAMP 的路径形式通常为"/[协议]:[[用户@]主机]:<文件>"。
以“你”的身份连接“myhost”,并使用 sudo 编辑 /etc/hosts
:
/ssh:you@remotehost|sudo:remotehost:/etc/hosts
TRAMP 支持的功能远不止上面的例子。欲了解更多信息,请参阅随 Emacs 一起发布的 TRAMP info 手册。
将 Emacs 用作 git 合并工具
Git 默认支持使用 Emacs 的 Emerge 模式作为合并工具。不过,你可能更喜欢 Ediff 模式。遗憾的是,由于技术原因,git 并不支持这种模式。但还是有办法在调用 emacs 时评估一些 elisp 代码来使用它。
.gitconfig
[mergetool.ediff] cmd = emacs --eval \" (progn (defun ediff-write-merge-buffer () (let ((file ediff-merge-store-file)) (set-buffer ediff-buffer-C) (write-region (point-min) (point-max) file) (message \\\"Merge buffer saved in: %s\\\" file) (set-buffer-modified-p nil) (sit-for 1))) (setq ediff-quit-hook 'kill-emacs ediff-quit-merge-hook 'ediff-write-merge-buffer) (ediff-merge-files-with-ancestor \\\"$LOCAL\\\" \\\"$REMOTE\\\" \\\"$BASE\\\" nil \\\"$MERGED\\\"))\" [merge] tool = ediff
注意,命令必须在一行中。 在上例中,我们启动了一个新的 Emacs 实例。你可能想使用 emacsclient 更快地启动 Emacs,但不建议这样做,因为 Ediff 调用并不干净:它可能会扰乱你当前的 Emacs 会话。
如果想立即启动,可以使用 -q
参数。如果想快速启动 Emacs,同时至少保留部分配置,可以使用:
emacs -q -l ~/.emacs-light
其中的轻量配置文件只加载 Ediff 所需的内容。
有关此技巧和 Ediff 问题的更多详情,请参阅 kerneltrap.org 和 stackoverflow。
将大写锁定键用作 Control 键
有些用户喜欢这种行为,以避免出现所谓的 “emacs 小指”。 在图形用户界面桌面(Xorg 或 Wayland)、终端甚至控制台中实现这一功能的好方法是使用 keyd包。安装此软件包并创建此配置文件:
/etc/keyd/main.conf
[ids] * [main] capslock = overload(control, noop)
然后启用/启动 keyd 服务。
这样大写锁定键就会启 Control 键之作用。
复用 emacs 和 emacsclient
在同一个 emacs-session
中打开新文件,需要使用 emacsclient
。如果会话存在,emacs
命令本身也可以进行封装,以更智能地打开文件。
要启动会话,需要使用 start-server
。该代码段将在 emacs 的第一个会话中创建服务器。将其添加到 emacs
配置文件中。
.emacs or .emacs.d/init.el
(require 'server) (unless (server-running-p) (server-start))
Shell 别名方法并不能满足要求,因为你还需要传递变量或启动自己的独立会话。在 shell 的 .bashrc
或任何 rc 文件中添加此选项。这样,如果参数被传递,emacs
命令就会像 emacsclient 一样运行。
function emacs { if [[ $# -eq 0 ]]; then /usr/bin/emacs # "emacs" is function, will cause recursion return fi args=($*) for ((i=0; i <= ${#args}; i++)); do local a=${args[i]} # NOTE: -c for creating new frame if [[ ${a:0:1} == '-' && ${a} != '-c' && ${a} != '--' ]]; then /usr/bin/emacs ${args[*]} return fi done setsid emacsclient -n -a /usr/bin/emacs ${args[*]} }
如果要在新会话中运行,只需执行 emacs file -
。
多种配置
您可以使用多种配置,并告诉 Emacs 加载其中一种或另一种。
例如,让我们定义两种配置文件。
.emacs
(load "~/.emacs.d/main" nil t) (load "~/.emacs.d/functions" nil t) (load "~/.emacs.d/modes" nil t) (load "~/.emacs.d/plugins" nil t) (load "~/.emacs.d/theme" nil t)
这是我们针对守护进程加载的全部配置。但 plugins 文件很大,加载速度也很慢。如果我们想生成一个不需要 plugins 功能的新 Emacs 实例,那么从长远来看,每次加载都会很麻烦。
.emacs-light
(load "~/.emacs.d/main" nil t) (load "~/.emacs.d/functions" nil t) (load "~/.emacs.d/modes" nil t) (load "~/.emacs.d/theme" nil t)
现在我们用:
emacs -q -l ~/.emacs-light
您可以创建一个别名(如 emacs-light
),以便调用。
局部变量和自定义变量
您可以在配置文件中定义变量,以后可以在本地对文件进行修改。
(defcustom my-compiler "gcc" "Some documentation")
现在,在任何文件中都可以通过两种方式定义局部变量,详见手册。
- 使用
M-x add-file-local-variable-prop-line
可以在开头添加一行注释,类似于:
// -*- my-compiler:g++; mode:c++ -*-
- 或者使用
M-x add-file-local-variable
在文件末尾添加行:
// Local Variables: // my-compiler: g++ // mode: c++ // End:
请注意,要使这些值生效,您需要调用 M-x revert-buffer
。
默认情况下,自定义变量被认为是不安全的。如果你试图打开一个包含重新定义自定义变量的局部变量的文件,Emacs 会要求你确认。
您可以将变量声明为安全变量,从而消除 Emacs 的确认提示。您需要指定一个谓词,任何新值都必须经过该谓词的验证,这样才能被认为是安全的。
(defcustom my-compiler "gcc" "Some documentation" :safe 'stringp)
在上例中,如果您试图设置字符串以外的内容,Emacs 会认为这是不安全的。
自定义色彩和主题
可以使用 face 工具轻松定制颜色。
(set-face-background 'region "color-17") (set-face-foreground 'region "white") (set-face-bold-p 'font-lock-builtin-face t )
您可以让 Emacs 告诉您点所在的 face 的名称。为此,请使用 customize-face 功能。该功能会告诉你如何设置颜色、粗体、下划线等。
控制台中的 Emacs 可以处理 256 种颜色,但必须使用合适的终端。例如,URxvt 支持 256 种颜色。你可以使用 list-colors-display 查看支持颜色的完整列表。这在很大程度上取决于终端。
参见:
- https://www.emacswiki.org/emacs/ColorThemes
- https://www.gnu.org/software/emacs/manual/html_node/emacs/Custom-Themes.html
SyncTeX 支持
Emacs 是一款功能强大的 LaTeX 编辑器。这主要是因为你可以调整或创建最适合自己需要的 LaTeX 模式。
不过,也可能会遇到一些挑战,比如 SyncTeX 支持。首先,你需要确保你的 TeX 发行版支持 SyncTeX。如果你手动安装了 TeX Live,可能需要安装 synctex 软件包。
# umask 022 && tlmgr install synctex
SyncTeX 支持与阅读器有关。在此,我们将以 Zathura 为例,因此如果您想使用其他 PDF 阅读器,则需要对代码进行调整。
(defcustom tex-my-viewer "zathura --fork -s -x \"emacsclient --eval '(progn (switch-to-buffer (file-name-nondirectory \"'\"'\"%{input}\"'\"'\")) (goto-line %{line}))'\"" "PDF Viewer for TeX documents. You may want to fork the viewer so that it detects when the same document is launched twice, and persists when Emacs gets closed. Simple command: zathura --fork We can use emacsclient --eval '(progn (switch-to-buffer (file-name-nondirectory \"%{input}\")) (goto-line %{line}))' to reverse-search a pdf using SyncTeX. Note that the quotes and double-quotes matter and must be escaped appropriately." :safe 'stringp)
在这里,我们定义了自定义变量。如果您使用的是 AucTeX 或 Emacs 默认的 LaTeX 模式,则必须相应地设置查看器。
现在用 Emacs 打开一个 LaTeX 源文件,编译文档,然后启动查看器。Zathura 进程会自动创建。如果按下 Ctrl+Left click
键,Emacs 就会将点放在相应的位置。
systemd 文件的语法高亮显示
您可以使用 systemd-mode。
或者,你也可以在 init 文件中添加以下内容,让 emacs 为 systemd 文件(服务、定时器等)着色:
(add-to-list 'auto-mode-alist '("\\.service\\'" . conf-unix-mode)) (add-to-list 'auto-mode-alist '("\\.timer\\'" . conf-unix-mode)) (add-to-list 'auto-mode-alist '("\\.target\\'" . conf-unix-mode)) (add-to-list 'auto-mode-alist '("\\.mount\\'" . conf-unix-mode)) (add-to-list 'auto-mode-alist '("\\.automount\\'" . conf-unix-mode)) (add-to-list 'auto-mode-alist '("\\.slice\\'" . conf-unix-mode)) (add-to-list 'auto-mode-alist '("\\.socket\\'" . conf-unix-mode)) (add-to-list 'auto-mode-alist '("\\.path\\'" . conf-unix-mode)) (add-to-list 'auto-mode-alist '("\\.netdev\\'" . conf-unix-mode)) (add-to-list 'auto-mode-alist '("\\.network\\'" . conf-unix-mode)) (add-to-list 'auto-mode-alist '("\\.link\\'" . conf-unix-mode))
为 emacs-nox 提供剪贴板支持
要在 emacs-nox 中使用 Xorg 剪贴板,请安装。xclip包 并在 ~/.emacs
中添加以下函数[1]:
;; use xclip to copy/paste in emacs-nox (unless window-system (when (getenv "DISPLAY") (defun xclip-cut-function (text &optional push) (with-temp-buffer (insert text) (call-process-region (point-min) (point-max) "xclip" nil 0 nil "-i" "-selection" "clipboard"))) (defun xclip-paste-function() (let ((xclip-output (shell-command-to-string "xclip -o -selection clipboard"))) (unless (string= (car kill-ring) xclip-output) xclip-output ))) (setq interprogram-cut-function 'xclip-cut-function) (setq interprogram-paste-function 'xclip-paste-function) ))