ラベル EmacsLisp の投稿を表示しています。 すべての投稿を表示
ラベル EmacsLisp の投稿を表示しています。 すべての投稿を表示

2011年4月1日金曜日

hippie-expandの略語展開関数をmodeごとに指定する

Emacsの補完機能の一つにhippie-expandというものがあります。

hippie-expandは補完用関数のリストを設定すると、そのリストの先頭から順番に補完を試してくれます。

私は主にlisp系言語で遊んでいるので、補完用関数のリストに lispのシンボル補完用関数を設定していたのですが、lispプログラミング以外を行っている時にもlispのシンボルが候補にあがってしまいます。

Emacsのことなのですでに解決策はあるのでしょうが、とりあえず自作でmojor-mode/minor-modeごとに補完用関数を切り替えられるようなelispを書いてみました。

(require 'cl)

(defvar mode-specified-try-functions-table (make-hash-table))

(defun set-mode-specified-try-functions (mode functions)
(setf (gethash mode mode-specified-try-functions-table)
functions))
(defun set-default-try-functions (functions)
(setf (gethash :default mode-specified-try-functions-table)
functions))

(defun expand-try-functions-of (mode)
(let ((result
(gethash mode mode-specified-try-functions-table)))
(if (listp result) result
(list result))))

(defun current-hippie-expand-try-function-list ()
(remove-duplicates
(remove nil
(append
(apply
'append
(mapcar 'expand-try-functions-of minor-mode-list))
(expand-try-functions-of major-mode)
(expand-try-functions-of :default)))
:from-end t))

(defadvice hippie-expand (around mode-specified-hippie-expand)
(let ((hippie-expand-try-functions-list
(current-hippie-expand-try-function-list)))
ad-do-it))

(defun enable-mode-specified-hippie-expand ()
(interactive)
(ad-enable-advice 'hippie-expand
'around
'mode-specified-hippie-expand)
(ad-activate 'hippie-expand))

(defun disable-mode-specified-hippie-expand ()
(interactive)
(ad-disable-advice 'hippie-expand
'around
'mode-specified-hippie-expand)
(ad-deactivate 'hippie-expand))

;;(provide 'mode-specified-hippie-expand)

;;;; examples
(set-default-try-functions
'(try-complete-file-name-partially
try-complete-file-name
try-expand-all-abbrevs
try-expand-dabbrev
try-expand-dabbrev-all-buffers
try-expand-dabbrev-from-kill))

(dolist (mode
'(emacs-lisp-mode
slimre-repl-mode
lisp-mode
common-lisp-mode
lisp-interaction-mode))
(set-mode-specified-try-functions
mode
'(try-complete-lisp-symbol-partially
try-complete-lisp-symbol)))

;;;; enable
(enable-mode-specified-hippie-expand)

2011年2月23日水曜日

エターナル・フォース・コントロール

Emacsのキーマップについて調べていたら、入力イベントを変換する機能というのを見つけたので無駄機能を使って遊んでみます。

(defun enable-force-ctrl ()
(interactive)
(aset keyboard-translate-table ?a ?\^a)
(aset keyboard-translate-table ?b ?\^b)
(aset keyboard-translate-table ?c ?\^c)
(aset keyboard-translate-table ?d ?\^d)
(aset keyboard-translate-table ?e ?\^e)
(aset keyboard-translate-table ?f ?\^f)
(aset keyboard-translate-table ?g ?\^g)
(aset keyboard-translate-table ?h ?\^h)
(aset keyboard-translate-table ?i ?\^i)
(aset keyboard-translate-table ?j ?\^j)
(aset keyboard-translate-table ?k ?\^k)
(aset keyboard-translate-table ?l ?\^l)
(aset keyboard-translate-table ?m ?\^m)
(aset keyboard-translate-table ?n ?\^n)
(aset keyboard-translate-table ?o ?\^o)
(aset keyboard-translate-table ?p ?\^p)
(aset keyboard-translate-table ?q ?\^q)
(aset keyboard-translate-table ?r ?\^r)
(aset keyboard-translate-table ?s ?\^s)
(aset keyboard-translate-table ?t ?\^t)
(aset keyboard-translate-table ?u ?\^u)
(aset keyboard-translate-table ?v ?\^v)
(aset keyboard-translate-table ?w ?\^w)
(aset keyboard-translate-table ?x ?\^x)
(aset keyboard-translate-table ?y ?\^y)
(aset keyboard-translate-table ?z ?\^z))

このコマンドを実行すると、アルファベット小文字のキー入力は問答無用でCtrl付きキーシーケンスに変換されます。一度実行されると解除するのが極めて困難な状況に陥ることでしょう。

以下、キーマップについてのメモ。

Emacsで入力イベントがどのようにキー列となり、どのキーマップのコマンドが実行されるか、ということを学ぶには、「37.8.2 入力イベント」や「21 キーマップ」あたりを見ると良さそう。

キーマップの探索順序は、このようになるようです。

  1. key-translation-map
  2. テキスト属性local-mapによる代替ローカルキーマップ
  3. マイナーモードキーマップ(リストの先頭から順番)
  4. ローカルキーマップ(メジャーモード毎のマップ)
  5. グローバルキーマップ

テキスト属性によるキーマップの位置がちょっとあやしい。

(訂正:マイナーモードキーマップとテキスト属性のキーマップが案の定逆っぽいので修正. 2011/02/23)

キー探索を行うためにlookup-key、key-binding、local-key-binding、global-key-binding、minor-mode-key-bindingといった関数が存在するので、自力で探索することもそこまで大変ではなさそうです。

この他に、一部のキーマップの定義を無効化して、代替となるキーマップを利用するために overriding-*-mapというような名前の変数が用意されています。

キーマップは他のキーマップを敬称する(親とする)こともできるようです。 set-keymap-parent関数でキーマップに親マップを設定します。

2010年12月15日水曜日

Emacsで単語の省略形を定義する

最近どうにかして同じようなコードを何度も何度も何度も書くような真似をしなくて良い方法はないだろうかと考えています。

基本的にEmacsユーザーなので、Elispでそれっぽい機能を書けばいいんじゃないかと考えて思いついたネタの一つが、劣化yasnippetといいますか、単にある文章(単語)の省略形を登録しておいて、それを展開してもとの文章(単語)になるようにする、というものでした。

まぁ、補完があるのでたいして役に立たないかもしれません。

(require 'cl)
;;; メジャーモードで分けたほうがよいだろうか
(defvar shorthand:*shorthand-expand-ht*
(make-hash-table :test 'equal))
(defvar shorthand:*shorthand-fold-ht*
(make-hash-table :test 'equal))

(defun shorthand:add (short long)
(interactive "Sshort:\nslong:")
(setf (gethash long shorthand:*shorthand-fold-ht*) short
(gethash short shorthand:*shorthand-expand-ht*) long))


(fset 'sh:add #'shorthand:add)


;; syntax-tableによっては. や / で区切られてしまう
(defun shorthand:word-at-point ()
(let ((s (sexp-at-point)))
(typecase s
(string (format "\"%s\"" s))
(list nil)
(symbol (symbol-name s))
(t nil))))

(defun shorthand:symbol-at-point ()
(symbol-at-point))

(defun shorthand:add-at-point-short (long)
(interactive "slong:")
(let ((short (shorthand:symbol-at-point)))
(when short
(shorthand:add short long))))
(fset 'sh:add-at-point-short #'shorthand:add-at-point-short)

(defun shorthand:add-at-point-long (short)
(interactive "Sshort:")
(let ((long (shorthand:word-at-point)))
(when long
(shorthand:add short long))))
(fset 'sh:add-at-point-long #'shorthand:add-at-point-long)

(defun shorthand:get (short)
(gethash short shorthand:*shorthand-expand-ht*))
(fset 'sh:get #'shorthand:get)

(defun shorthand:get-short (long)
(gethash long shorthand:*shorthand-fold-ht*))

(defun shorthand:expand (short)
(interactive (list (shorthand:symbol-at-point)))
(let ((long (shorthand:get short)))
(when long
(cond
((functionp long) (funcall long short))
((not (interactive-p)) long)
(t (shorthand:replace long))))))
(fset 'sh:expand #'shorthand:expand)

(defun shorthand:fold (long)
(interactive (list (shorthand:word-at-point)))
(let ((short (shorthand:get-short long)))
(when short
(if (not (interactive-p))
short
(let ((short (format "%S" short)))
(shorthand:replace short))))))
(fset 'sh:fold #'shorthand:fold)

(defun shorthand:replace (new)
(backward-sexp)
(kill-sexp)
(pop kill-ring-yank-pointer)
(let ((pos (point))
(len (length new)))
(insert new)
(goto-char (+ pos len))))

(defvar shorthand:*expand-fold-toggle-flag* nil)

(defun shorthand:expand-and-fold ()
(interactive)
(if (and (eq this-command last-command)
shorthand:*expand-fold-toggle-flag*)
(progn
(setf shorthand:*expand-fold-toggle-flag* nil)
(command-execute 'shorthand:fold))
(progn
(setf shorthand:*expand-fold-toggle-flag* t)
(command-execute 'shorthand:expand)))
(when (interactive-p)
(setf this-command 'shorthand:expand-and-fold)))

;; key bindings
(global-set-key (kbd "C-o") 'shorthand:expand-and-fold)
(global-set-key (kbd "M-RET") 'shorthand:add-at-point-long)
(global-set-key (kbd "M-SPC") 'shorthand:add-at-point-short)

;; example
;; (sh:add 'sysout "System.out.println")
;; sysout [M-x sh:expand]
;; =>
;; System.out.println
;; System.out.println [M-x sh:fold]
;; =>
;; sysout


(sh:add 'file
(lambda (short)
(let ((name (read-file-name "Filename:")))
(when name
(shorthand:replace name)))))

単語の展開だけではつまらないので関数の呼び出しも行えるようにしました。

syntax-tableによって、単語やS式(シンボル)の範囲が異なるあたりがめんどくさそうです。

とりあえず、interactiveについての勉強にはなりました。