複数のメジャーモードを文脈に応じて切り替え

multi-mode.elを使うと, 一つのバッファ内で文脈に応じてメジャーモードを切り替えることができる. けれど, カーソル位置を受け取って, 切り替えるべきメジャーモードとその有効範囲を返す関数を定義しないといけなくて, 使い方が難しい.

そこで, 開始・終了の正規表現と, その範囲内で有効になるメジャーモードを指定するとmulti-modeに渡すべき関数を作って登録してくれる関数を書いた.

まず,

(multi-mode-init)

すると, バッファ内でmulti-modeを使う準備ができる. さらに,

(multi-mode-install-chunk-finder "^>|javascript|$" "^||<$" 'javascript-mode)

とすると, ">|javascript|"だけが書かれた行と"||<"だけが書かれた行に挟まれたテキストにカーソルが移動したときに自動的にjavascript-modeに切り替わる.

さらに応用して,

(setq hatena-diary-super-pre-languages '(java javascript lisp ruby))
(defun hatena-diary-super-pre-notation ()
  (interactive)
  (multi-mode-init)
  (dolist (l hatena-diary-super-pre-languages)
    (let ((str (symbol-name l)))
      (multi-install-chunk-finder (concat "^>|" str "|$") "^||<$"
                                  (intern (concat str "-mode"))))))

などとしておけば, hatena-diary-super-pre-notationを呼び出すと, 同じような記法で様々なプログラミング言語用のメジャーモードに切り替えることができる.

おまけ

viper-modeの状態が各メジャーモード間で共有されないので, その辺りもうまくいくようにした.

既知の不具合

  • transient-mark-modeのアクティブな選択状態がモード間で共有されない修正済!
    • 最初vimpulseのvisual-stateにだけ対応していたけれど, よく考えたらvimpulseでなくても同じことだった
    • アクティブな選択状態を維持する限り, 範囲選択を開始したメジャーモードに留まるようにした
  • undoバッファがモードをまたぐとおかしい気がする修正済!
    • multi-modeの問題ではなくて, 間接バッファ共通の問題っぽい
    • 間接バッファでundo/redoしようとしたときには実際には元バッファでやるようにadviceした
      • それでもやっぱりredo.elだと微妙な挙動のときがある
      • undo-tree.elならうまく動く
  • 変更を加えるまではシンタックスハイライトが適用されないことが多い修正済!

制限

  • inhibit-eval-during-redisplayしているので, multi-modeなバッファではモードラインでevalしている部分が無視される(こうしないとエラーメッセージがたくさん出てうざい; multi-mode.elの問題?)
  • multi-install-chunk-finderはネストした構文をサポートしていない