再谈emacs的缩进设置

去年我写过一篇《Emacs的tab键与缩进》,但后来发现有一些内容当初理解不深,就匆忙地改用了其它方式。所以现在做一些补充:

1. 当时的想法: "缩进完全自行控制,不用syntax indentation来自动"。其实Emacs对缩进的控制基本上是在根据语法确定缩进这个方向上的,这样做的好处也很明显: 最终使用起来简单——你只需要按一个tab键就可以了,不需要考虑缩进多少。但这里有两个问题:

  1. 初学者需要了解这个规则,尤其是c-style(参见EmacsWiki: Indenting C)相关的设置。有很多人写了很久的代码,习惯了某种缩进风格,但他可能并不知道是那种风格,这个时候就抓狂了;
  2. 有些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的缩进方式,我又懒得在多种缩进风格中慢慢折腾),再采用我这里提供的方法吧。

posted @ 2011-05-28 05:48  巴蛮子  阅读(4987)  评论(0编辑  收藏  举报