org-mode + remember-mode でEmacs内で瞬時にメモをする→コードリーディングに生かす・メモ検索する

Emacsで即座にメモを取る方法はいろいろある。日本ならばHowmとかChangeLogメモが人気なのかな。俺はorg-mode + remember-modeを使っている。

org-rememberをインストールする

remember-modeは頭の中で思い付いたものを即座にメモをするためのシンプルで便利なツールだ。M-x rememberでメモ用バッファがポップアップするから、メモを書いてC-c C-cを押せば記録される、それだけ。https://gna.org/p/remember-el なり「apt-get install remember-el」なりでインストールしてみよう。
org-modeはoutline-modeの超超拡張版だ。もともと構造化テキストを扱うものだったのが、本格的なアウトラインプロセッサになっていたり、TODO管理や予定表やスプレッドシート(まじで表計算できる!)などと結び付いている。Emacs22に標準添付なのだが、日進月歩で開発が進められているので、gitリポジトリからインストールすればよいだろう。あまりに高機能すぎてどこから手をつければいいのかわからないと思うが、まずはただのアウトラインプロセッサやTODO管理として使ってみるとよい。慣れたらGTDもできるぞ。Emacsで文章を書く人はorg-modeの使用方法を習得しておいて損はない。

$ git clone git://repo.or.cz/org-mode.git

org-modeにはremember-modeとの合わせ技であるorg-rememberコマンドが定義されている。これを使えば特定のファイルの特定の場所につらつらとメモを記録することができるぞ。最新版のorg-modeでこんな設定をしてみよう。

(require 'org-install)
(setq org-startup-truncated nil)
(setq org-return-follows-link t)
(add-to-list 'auto-mode-alist '("\\.org$" . org-mode))
(org-remember-insinuate)
(setq org-directory "~/memo/")
(setq org-default-notes-file (concat org-directory "agenda.org"))
(setq org-remember-templates
      '(("Todo" ?t "** TODO %?\n   %i\n   %a\n   %t" nil "Inbox")
        ("Bug" ?b "** TODO %?   :bug:\n   %i\n   %a\n   %t" nil "Inbox")
        ("Idea" ?i "** %?\n   %i\n   %a\n   %t" nil "New Ideas")
        ))

org-rememberを使ってみる

この状態でM-x org-rememberを実行すると、

Select template: [t]odo [b]ug [i]dea

なんてプロンプトがエコーエリアに表示されるので「t」、「b」、「i」のどれかを選択しよう。それぞれ「todo」、「bug」、「idea」に相当する。やることを思い付いたら「t」を押す。すると、*Remember*バッファが出てくるのでタイトルと中身(書かなくてもよい)を書いてC-c C-cで登録できる。

http://www.rubyist.net/~rubikitch/archive/org-remember.png
親切なことに、いろいろな登録方法がバッファに書いてある^^ 書き込まれるファイルは ~/memo/agenda.org だ。開いてみると、「Inbox」という見出しの中で「てすと」という項目が登録されたのがわかる。メモをすべてひとつのファイルに置いておくことでメモにおける「ポケットひとつの原則」も遵守できていい感じ。
こんな感じで手軽に使えるので、Emacsでなにかいいメモツールがないかなーと思ったらorg-rememberをおすすめするよ。planner.elも同じように瞬時にメモをする機能があるが、org-modeは汎用的に作られているため応用範囲が広い。実際に俺はplannerからorgに乗り換えた経緯がある。

org-rememberをコードリーディングのメモ

ここからはプログラミングをする人向けの話。org-rememberで瞬時にメモを取れることは上に書いてあることを実際に試してみればわかるはず。これをコードリーディングに生かしてみよう。
プログラミング技術の向上のためには、人のコードを読むのが大事だ。プログラマの仕事はコードを書くよりも人のコードを読む機会の方が多いといわれている。プロでなくてもデバッグ時やコードを書く際の参考にコードを読むことになるだろう。
コードを読む場合、適宜メモを取ることが大切となる。コードに隠された意図や何をやっているかなどをいちいち頭の中に覚えているのは、よほど記憶力のいい人にしかできないだろう。そこでふつうの人にできることといえばメモだ。
org-rememberではメモの種類を選択してメモを記録するのだが、コードリーディング時は連続してコードリーディング関連のメモとなるから、その選択のステップを省くとよい。具体的にはorg-remember-templatesをletでくるむ。
さて、先程のスクリーンショットで水色の文字で「file:~/memo/hatena/2009-01-21.txt::〜」なんてのが出てきているだろう。これはorg-modeにおけるハイパーリンクでその上で「C-c C-o」か「Enter」*1を押すとそのリンクを辿ることができる。org-rememberはメモを開いた時点でのファイルの位置を自動的に記録しているため、人手で位置をメモする必要がない。これはコードリーディング的に嬉しい機能だ。

以下のコードはorg-rememberでコードリーディングのメモを取る「org-remember-code-reading」コマンドの定義だ。押しやすいキーに割り当てよう。

(defvar org-code-reading-software-name nil)
;; ~/memo/code-reading.org に記録する
(defvar org-code-reading-file "code-reading.org")
(defun org-code-reading-read-software-name ()
  (set (make-local-variable 'org-code-reading-software-name)
       (read-string "Code Reading Software: " 
                    (or org-code-reading-software-name
                        (file-name-nondirectory
                         (buffer-file-name))))))

(defun org-code-reading-get-prefix (lang)
  (concat "[" lang "]"
          "[" (org-code-reading-read-software-name) "]"))
(defun org-remember-code-reading ()
  (interactive)
  (let* ((prefix (org-code-reading-get-prefix (substring (symbol-name major-mode) 0 -5)))
         (org-remember-templates
          `(("CodeReading" ?r "** %(identity prefix)%?\n   \n   %a\n   %t"
             ,org-code-reading-file "Memo"))))
    (org-remember)))

org-remember-code-readingはまずソフトウェアの名前を聞いてくる。単体のファイルだったらファイル名のままEnterを押そう。複数ファイルのソフトウェアの場合はそのソフトウェアの名前を入力しよう。ソフトウェア名は org-code-reading-read-software-name に記録されるが、バッファローカルなので同じファイルについては前回入力されたソフトウェア名がそのまま使われる。その後は通常のorg-rememberのように*Remember*バッファが出てくるのでメモをしよう。メモのタイトルには言語名とソフトウェア名が出てきている。ガンガンメモれ!

メモを検索する

メモをデジタル化する上で大切なのが検索だ。org-modeで文字列検索すると、その文字列が含まれるエントリがアウトライン展開された、その他のエントリは見えないようになっている。
たとえば、org-remember-code-readingでメモしていたファイルがこんな感じだったとする。

http://www.rubyist.net/~rubikitch/archive/org-remember-code-reading-1.png

超整理法の観点であえて整理しない状態にいろいろなソフトウェアのメモが羅列されている。この中でorg-modeのメモだけを取り出したければ「C-c / /」(M-x org-occur)を押してみる。すると、「Regexp: 」というプロンプトが出てくるのでorg-modeと入力すると、このように他のメモは隠され、org-modeの部分がハイライトされている。このように整理の手間は検索でまかなえる。あとはリンクを飛べばメモしている対象のファイルにたどりつける。

http://www.rubyist.net/~rubikitch/archive/org-remember-code-reading-2.png

リンク間移動

「C-c C-x C-p」と「C-c C-x C-n」*2を押すとブラウザでTabを押すようにリンクからリンクの移動ができるのだが、そのままだと隠れているリンクに到達してしまうのでいまいち不便だ。見えている部分のみのリンク間移動のコマンドを定義する。「M-p」と「M-n」に割り当てておいた。これで検索結果から快適にリンク先に飛べるぞ。

(defun org-next-visible-link ()
  "Move forward to the next link.
If the link is in hidden text, expose it."
  (interactive)
  (when (and org-link-search-failed (eq this-command last-command))
    (goto-char (point-min))
    (message "Link search wrapped back to beginning of buffer"))
  (setq org-link-search-failed nil)
  (let* ((pos (point))
	 (ct (org-context))
	 (a (assoc :link ct))
         srch)
    (if a (goto-char (nth 2 a)))
    (while (and (setq srch (re-search-forward org-any-link-re nil t))
                (goto-char (match-beginning 0))
                (prog1 (not (eq (org-invisible-p) 'org-link))
                  (goto-char (match-end 0)))))
    (if srch
        (goto-char (match-beginning 0))
      (goto-char pos)
      (setq org-link-search-failed t)
      (error "No further link found"))))

(defun org-previous-visible-link ()
  "Move backward to the previous link.
If the link is in hidden text, expose it."
  (interactive)
  (when (and org-link-search-failed (eq this-command last-command))
    (goto-char (point-max))
    (message "Link search wrapped back to end of buffer"))
  (setq org-link-search-failed nil)
  (let* ((pos (point))
	 (ct (org-context))
	 (a (assoc :link ct))
         srch)
    (if a (goto-char (nth 1 a)))
    (while (and (setq srch (re-search-backward org-any-link-re nil t))
                (goto-char (match-beginning 0))
                (not (eq (org-invisible-p) 'org-link))))
    (if srch
        (goto-char (match-beginning 0))
      (goto-char pos)
      (setq org-link-search-failed t)
      (error "No further link found"))))
(define-key org-mode-map "\M-n" 'org-next-visible-link)
(define-key org-mode-map "\M-p" 'org-previous-visible-link)

*1:org-return-follows-linkがtのとき。

*2:なんつぅひどいキー割り当てだよ!