再谈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的缩进方式,我又懒得在多种缩进风格中慢慢折腾),再采用我这里提供的方法吧。

浙公网安备 33010602011771号