Perldoc を引きたい。

;; もう今さら、な感じも無きにしもあらずですが。
少し前のはてなの質問に、

Emacs ユーザーの方に質問です。これは便利! と思える elisp プログラムを教えてください。

というのがあって、私も回答 (過去、答えてみようと思ったはてなの質問が二件あるのですが、何れも id:naoya さんの質問でした。別に他意は無いのですが、どうも私の深層意識の何かに触れるのでしょうか?) なんぞしてみた訳なんですが、私の回答への naoya さんのコメントに、

カーソル位置のパッケージの perldoc をワンアクションで開ける lisp とかないかなあ。

とあったので、少しゴニョってみた訳です。
しかし、色々あって放置状態になっていたのですが、

■[Perl][Emacs] Perlモジュール名にカーソルを合わせて M-.するとソースを開く2

といった一連の id:higepon さんのエントリを見付けてしまったので、ちょっと掘り起こしてみました。

higepon さんとは異なり、私の場合は先の質問の回答にもあった様に、Emacs 標準パッケージである `Man' を利用して perldoc を引いてみようというものです。

(defun perldoc-by-module (word)
  "Run `perldoc' with `Man' package, by module."
  (interactive "sperldoc: ")
  (let ((Manual-program "perldoc")      ; for XEmacs
        (Manual-switches '(""))         ; for XEmacs
        (manual-program "perldoc"))     ; for Emacs
    (manual-entry word)
    (and (featurep 'xemacs) (rename-buffer (concat "Perldoc: " word)))))

(defun perldoc-by-module-at-point ()
  "Run `perldoc' with `Man' package, by the word around point."
  (interactive)
  (let ((word (thing-at-point 'word)))
    (perldoc-by-module word)))

これで Man パッケージを利用して、ポインタ付近の単語を Perldoc で引くことができます。
;; Emacs/XEmacs で動作します。
;; もしも Man 関連の関数が使えなかったら (require 'man) して下さい。
thing-at-point は、cperl を利用しているなら、cperl-word-at-point とかにした方が良いかもしれません。

しかし、これだけだと、引きたい単語によって、わざわざモジュールなのか関数なのかで実行するコマンドを変えないとならないので、ちょっと面倒ですね。
そこで、少し長くなりますが、

(defun perldoc (&optional module word)
  "Run `perldoc' with `Man' package."
  (interactive "P")
  (require 'man)
  (let* ((Manual-program "perldoc")     ; for XEmacs
         (default (thing-at-point 'word))
         (word (or word (read-string "perldoc: " default 'perldoc-history))))
    (let ((switches 
           (if (featurep 'xemacs)
               (if module '("-m") '("" "-f" "-q")) ; for XEmacs
             (if module '("perldoc -m")       ; for Emacs
               '("perldoc" "perldoc -f" "perldoc -q")))))
      (if (setq ret (catch 'match
            (mapcar (lambda (sw)
                      (let ((Manual-switches (list sw))
                            (manual-program sw))
                        (if (featurep 'xemacs)
                            ;; XEmacs
                            (when (condition-case nil (manual-entry word)
                                    (error nil))
                              (throw 'match sw))
                          ;; Emacs
                          (Man-getpage-in-background word)
                          (sit-for 1)
                          (when (get-buffer (concat "*Man " word "*"))
                            (throw 'match sw))))) switches) nil))
          (if (featurep 'xemacs)
              ;; Emacs のときは rename するとまずいことになるので。
              (let ((name (concat "*Perldoc"
                                  (when (string-match "-" ret)
                                    (concat " " ret)) ": " word "*")))
                (if (get-buffer name)
                    ;; 既にあるなら、新しいのは消してそれを使う。
                    (progn
                      (kill-buffer (current-buffer))
                      (switch-to-buffer name))
                  ;; `*Man XXX*' -> `*Perldoc: XXX*'
                  (rename-buffer name))))
        (message "perldoc %s not found." word)))))

(defun perldoc-at-point (&optional module)
  "Run `perldoc' with `Man' package, by the word around point."
  (interactive "P")
  (let ((word (thing-at-point 'word)))
    (perldoc module word)))

という具合にすると、その辺りを意識しなくて良くなります。

これもポインタ付近の単語を perldoc で引く訳ですが、

  1. オプション無し (perldoc)
  2. 関数検索指定 (perldoc -f)
  3. FAQ 検索指定 (perldoc -q) ;; ここまでは不要かな。

の順に対象の単語が見付かるまで検索し、最終的に何も見付からなければ error となります。
また、前置引数を指定すると強制的に perldoc -m し、対象単語をモジュール名と看倣してそのモジュールのソースを表示します。

これも Emacs/XEmacs で動作しますが、私は XEmacs を常用しており、FSF Emacs は今の Debian Sarge の環境では、先の質問の回答時に初めて起動したという具合です。
そのため、Emacs での利用には問題があるかもしれません。
;; XEmacs でも問題が無いと保証できる訳ではありませんが。

また、FSF Emacs の Man パッケージは、バックグラウンドで非同期プロセスとして実行する様な仕組みになっており、該当コンテンツが無かったときには、非同期プロセスの番兵で error を throw する様に作られているため、condition-case で error を捕捉できない様です。
そのため、XEmacs の様に単純にコマンドを順次実行することができず、無理矢理な判断を噛ませていますので、動作がモタつくということもあります。
Man-getpage-in-background の直後に sit-for を入れていることもその一つですが、ここで待つ秒数は環境に依存すると思いますので、それぞれの環境で調整して貰った方が良いと思います。

Man パッケージ自身を変更してしまう手もありますが、既存のパッケージを使う際にはできるだけオリジナルのままにしておきたいのでこんな具合になっています。advice では解決できそうにないので。
ここは FSF Emacs に詳しい方に何とかして頂きたいところです。

あと、調子に乗って XEmacs では作ったバッファの名前も rename していますが、これは必要なものではないです。何となく *Man: Perlfunc(1)* とかになっているのが気に入らないというだけの話ですので。
そのせいで、本来なら Man パッケージが、既存のバッファの場合にはそれを表示する動作をしてくれるのにも係わらず、同じ単語を引いてしまったときは、もう一度バッファを作ってから既存のバッファを消すという、本末転倒な動作になってしまっています。
;; やっぱりこれは外した方が良さそうですね。

本来は Emacs 標準の Man パッケージを利用して簡単に実現するところがミソだったのですが、余計なことをし出したために、何だか長く面倒な elisp になってしまいました。
それでも perldoc の option を変更しながら順次実行して、該当するコンテンツが見付かるまでやるということに意義が感じられる様なら使えるかもしれませんね。

でもね、実は衝撃的なことに、

  M-x cperl-perldoc-at-point (C-c C-h P)

なんてコマンドも実はあったんですよ……

;; 実は皆さん、知っててやってるとか?