再谈emacs的缩进设置
去年我写过一篇《Emacs的tab键与缩进》,但后来发现有一些内容当初理解不深,就匆忙地改用了其它方式。所以现在做一些补充:
1. 当时的想法: "缩进完全自行控制,不用syntax indentation来自动"。其实Emacs对缩进的控制基本上是在根据语法确定缩进这个方向上的,这样做的好处也很明显: 最终使用起来简单——你只需要按一个tab键就可以了,不需要考虑缩进多少。但这里有两个问题:
- 初学者需要了解这个规则,尤其是c-style(参见EmacsWiki: Indenting C)相关的设置。有很多人写了很久的代码,习惯了某种缩进风格,但他可能并不知道是那种风格,这个时候就抓狂了;
- 有些major-mode,为了方便,也采用了c-style的缩进,但语法上跟C/C++/Java存在一些差异,直接套用c-style缩进,没法得到想要的效果(比如xahk-mode.el),而用户不知道该怎么调整了。
2. 与此相对照的是,Emacs没有提供一个简单的缩进,跟其它”现代编辑器“比较接近的是C-x TAB(即indent-rigidly)和M-i(即tab-to-tab-stop),但前者与tab-width无关,后者也只是跳到下一个tab-stop位置(缺省是8-16-24-...); 如果想unindent,也只能C-- 4 C-x TAB,操作起来还是太繁琐了。
于是我有了如下的实现:
;;*** simple indent/unindent just like other editors ;; unlike emacs' default settings, this would not use syntax-based indent, but: ;; - if region selected, indent/unindent the region (tab-width) ;; * the region mark would not deactivated automatically ;; - if no region selected, <TAB> would ;; * if cursor lies in line leading, always indent tab-width ;; * if cursor lies in word ending and `tab-always-indent' is `complete', try complete ;; * otherwise, always insert a TAB char or SPACEs ;; - if no region selected, <S-TAB> would ;; * if cursor lies in line leading, always unindent tab-width ;; * otherwise, the cursor would move backwards (tab-width) ;; Note: this implementation would hornor `tab-always-indent', `indent-tabs-mode' and `tab-with'. (defun abs-indent (arg) "Absolutely indent current line or region. Mimic other editors' indent." (interactive "P") (let ( (width (or arg tab-width)) ) (if mark-active ;;DONE: how to restore region after `indent-rigidly' (let ( (deactivate-mark nil) ) (indent-rigidly (region-beginning) (region-end) width)) (let ( (pt (point)) (pt-bol (line-beginning-position)) (pt-bol-nonws (save-excursion (back-to-indentation) (point))) ) (if (<;= pt pt-bol-nonws) ;;in leading whitespaces (progn (back-to-indentation) (if (looking-at "$") ;;all chars in this line are whitespaces or tabs (indent-to (+ (current-column) width)) (progn (indent-rigidly pt-bol (line-end-position) width) (back-to-indentation)))) (if (and (eq tab-always-indent 'complete) (looking-at "\\>")) (call-interactively abs-indent-complete-function) (if indent-tabs-mode (insert-char ?\t 1) (insert-char ? width)))))))) (defvar abs-indent-complete-function 'dabbrev-expand "The function used in `abs-indent' for completion.") (make-variable-buffer-local 'abs-indent-complete-function) (defun abs-unindent (arg) "Absolutely unindent current line or region." (interactive "P") (if mark-active (let ( (deactivate-mark nil) ) (indent-rigidly (region-beginning) (region-end) (- tab-width))) (let ( (pt (point)) (pt-bol (line-beginning-position)) (pt-bol-nonws (save-excursion (back-to-indentation) (point))) ) (if (>; pt pt-bol-nonws) ;;in content (move-to-column (max 0 (- (current-column) tab-width))) (progn (back-to-indentation) (backward-delete-char-untabify (min tab-width (current-column)))))))) ;; to used it ;; (add-hook 'ahk-mode-hook #'(lambda () ;; (local-set-key (kbd "<TAB>") 'abs-indent) ;; (local-set-key (kbd "<S-TAB>") 'abs-unindent)))
注意这里的实现跟上次采用的实现emacs, indent/unindent region as a block using the tab key有些小差别: 1) 现在这个会采用'tab-always-indent', 'indent-tabs-mode'和'tab-width'这几个变量,与Emacs内置版本的唯一不同就是不再采用基于语法的缩进;2) dave的版本在unindent时候的处理是不对的,它把制表符(\t)也看作了单个字符,而没有按tab-width换算,调用Emacs自己的backward-delete-char-untabify就可以解决这个问题。
3. 其实上面的缩进还可以包装成一个minor-mode:
(defvar abs-indent-mode-map (let ( (map (make-sparse-keymap)) ) (define-key map "\t" 'abs-indent) (define-key map [S-tab] 'abs-unindent) map) "keymap for `abs-indent-mode'.") (define-minor-mode abs-indent-mode "simple indent just like other editors." nil " ai" abs-indent-mode-map (if abs-indent-mode t t))
并且还可以再改改,将它包装成一个全局minor-mode,不过我觉得大多数情况下还是用Emacs内置的基于语法的缩进比较好,只是偶尔基于语法的缩进比较碍事的时候(比如前面说的xahk-mode里采用了c-mode的缩进方式,我又懒得在多种缩进风格中慢慢折腾),再采用我这里提供的方法吧。