auto-complete.el で scheme のシンボル補完なんとなくできた

auto-complete.elの流儀がよくわからないのでソース読みつつ空気を読んで書いてみた。
ノリとしては

  • 補完候補の素(ファイルで定義されてる関数名とか全部)を集めて
  • それを ac-target でフィルタリングして (イディオムとしては (all-completions ac-target (補完候補ぜんぶのリスト...)) というかんじらしい) 返す
  • 関数を ac-sources につっこむ

というかんじであった。候補を集める関数を gather とすると

(setq ac-source-hogehoge
      '((candidates . (lambda ()
                        (all-completions ac-target (gather))))))
(add-to-list 'ac-sources 'ac-source-hogehoge)

とかやるとよい。

ac-target は、auto-complete-modeがonになってるバッファで適当に打鍵すると、カーソルがある位置と、その直前のシンボル境界の間の文字列が設定されている。要は今入力途中の単語ということ。これは何も設定してないと (ac-default-find) と (point) の間の文字列ということになるが、ac-default-find のかわりとなる関数名を ac-find-function に設定してやることでカスタマイズできる。ac-default-find は thingatpt.el (thing at point) というなんかまさにそのものであるかんじのパッケージの bounds-of-thing-at-point をつかってる。

なんか前に id:suztomo がカーソルのある単語を検索したいとかいって面倒なことしてた気がするけど、(thing-at-point 'symbol)でいいんじゃねとかおもった。閑話休題

あとはやったことといえば、毎回 gosh つかってシンボルの走査とかするのもアレなので、after-save-hook と gauche-mode-hook で走査してキャッシュして、それをcandidatesのほうに使ったりした。

とりあえず auto-complete.el つかうために読んでみて思ったのは、すげー簡単に拡張できる(↑の2行に gather を実装して適当に名前つければよい)というか補完候補を追加できるようになってるけど、そのすげー簡単な方法がなんか「推して知るべし!」みたいになっててちょっと手間取った。

あとTODOみてて、俺の名前あってはずかしいとかじゃなくて、omni completionとかいてあったけどもはやomni completionのための土台はできているので、みなさんがんばって各言語の補完書きましょうという感じである。機は熟した。

そんなわけで auto-complete.el はすごいですのでみなさん使いましょう。

とここまで書いて思いだしたけど、でかいC言語のファイル開いてるとときどき auto-complete が死ぬことがある。今日なんとなく内部の仕組みがわかったので、今度死ぬようなことがあったら検死解剖してバグだったら報告などしよう。

おまけ:Schemeのシンボル補完

Schemeワンライナーとか謎なことになっております。gauche-modeじゃなくて標準的なscheme-mode(だっけ?)をつかってるひとは、gaucheschemeにおきかえればたぶん動くでしょう。

(defvar ac-source-gauche-file-symbols
  '((candidates
     . (lambda () 
	 (all-completions ac-target ac-source-gauche-file-symbols-cache)
	 ))))
(defvar ac-source-gauche-file-symbols-cache nil)
(defun ac-source-gauche-collect-file-symbols ()
  (interactive)
  (if (not (eq 0 (shell-command "gosh -b" nil)))
      nil
    (setq ac-source-gauche-file-symbols-cache
	  (delete "" (split-string (shell-command-to-string
				  (format "gosh -b -e'(letrec ((car-of-car (lambda (x) (if (pair? x) (car-of-car (car x)) x)))) (with-input-from-file \"%s\" (lambda () (let loop((sexp (read))) (if (eof-object? sexp) #f (begin (cond ((not (pair? sexp)) #f) ((or (eq? (quote define) (car sexp)) (eq? (quote define-syntax) (car sexp)) (eq? (quote define-macro) (car sexp)) (eq? (quote define-class) (car sexp))) (print (car-of-car (cadr sexp))))) (loop (read))))))))'" (buffer-file-name))) "\n")))))

(add-hook 'gauche-mode-hook
	  (lambda ()
	    (make-local-variable 'ac-sources)
	    (make-local-variable 'ac-source-gauche-file-symbols-cache)
	    (make-local-variable 'after-save-hook)
	    (add-to-list 'ac-sources 'ac-source-gauche-file-symbols)
	    (add-hook 'after-save-hook
		      'ac-source-gauche-collect-file-symbols)
	    (ac-source-gauche-collect-file-symbols)))

さらにおまけ

調子にのって R5RS の手続きのシンボル名の補完候補つくってみた。inferior-scheme-mode とかで使うといいんじゃないかな!

http://gist.github.com/52878