xyzzyでディレクトリにあるファイルの連動ビュー

まえおき

どうもつかさです。
今日やるのは、引数として受け取ったディレクトリにあるファイルを一覧表示して、連動ビュー。

画像で見た方が早いですね。

こんなのです。

rendou-view.l

;;; -*- Mode: Lisp; Package: EDITOR -*-
;;; rendou-view.l

(provide "rendou-view")

(in-package "editor")

(export '(rendou-view-window
	  rendou-view))

(defvar *rendou-view-dir* nil)


;;;本体
(defun rendou-view-window ()
  (interactive)
  (if si:*command-line-args*
    (setq *rendou-view-dir* (pop si:*command-line-args*))
    (setq *rendou-view-dir* "D:/"))
  (delete-other-windows)
  (switch-to-buffer "view")
  (rendou-view-mode)
  (set-local-window-flags (selected-buffer)
                          *window-flag-line-number* nil)
  (split-window -20 nil)
  (other-window)
  (switch-to-buffer "files")
  (set-local-window-flags (selected-buffer)
                          *window-flag-line-number* nil)
  (rendou-view-mode)
  (dolist (x (directory *rendou-view-dir* :file-only t))
    (insert x "\n"))
  (goto-line 0)
  (set-buffer-fold-width nil)
  (setq buffer-read-only t)
  (rendou-view))

;;;連動ビュー
(defun rendou-view ()
  (interactive)
  (let (filepath dir)
    (setq filepath (get-line))
    (setq dir *rendou-view-dir*)
    (other-window)
    (switch-to-buffer "view")
    (erase-buffer(selected-buffer))
    (insert-file-contents  (merge-pathnames filepath dir)))
  (other-window))

;;;カーソル下に割当て
(defun forward-open ()
  (interactive)
  (forward-line)
  (unless (eobp)
    (rendou-view)))

;;;カーソル上に割当て
(defun backward-open ()
  (interactive)
  (backward-line)
  (unless (eobp)
    (rendou-view)))

;;;カーソル行の取得
(defun get-line ()
  (interactive)
  (save-excursion
  (let (start end)
    (setq start (progn (goto-bol) (point)))
    (setq end (progn (goto-eol) (point)))
    (buffer-substring start end))))

;;;モードの設定
(defun rendou-view-mode ()
  (interactive)
  (kill-all-local-variables)
  (setq buffer-mode 'rendou-view-mode)
  (setq mode-name "rendou-view")
  (use-keymap *rendou-view-mode-map*)
  (make-local-variable 'need-not-save)
  (setq need-not-save t)
  (make-local-variable 'auto-save)
  (setq auto-save nil)
  (run-hooks '*rendou-view-mode-hook*))

(defvar *rendou-view-mode-map* nil)
  (unless *rendou-view-mode-map*
    (setq *rendou-view-mode-map* (make-sparse-keymap))
    (define-key *rendou-view-mode-map* #\Down 'forward-open)
    (define-key *rendou-view-mode-map* #\Up 'backward-open))

説明

基本となっているのは、split-howm-windowとfile-openとget-filenameの三つ。split-howm-windowが本体でウィンドウの分割だとかパスの表示だとかをしている。file-openでは、上下ウィンドウの連動を担当。get-filenameは、カーソル下のパスを取得するための関数です。

forward-openとか、my-howm-view-modeだとかは、カーソル上下で、連動してファイルの中身が表示させるための設定。

使い方

.xyzzyに次をコピペ。

(require "rendou-view")

PPxで登録するなら、前回したようにする。

%Ob D:\bin\xyzzy\xyzzy.exe -e (rendou-view-window) %1

解説

今後、自分の参考のためにもどうやって作ったかというのを書いておく。

ウィンドウ

骨組みだけ取り出すとこんな感じ。

(defun rendou-view-window ()
  (interactive)
  (switch-to-buffer "view")
  (split-window -20 nil)
  (other-window)
  (switch-to-buffer "files"))

split-windowとswitch-to-bufferを使います。
二つのウィンドウを開き、それぞれに別のバッファを対応させたいわけですね。一つが、ファイル名を一覧表示するためのバッファ。ここでは、filesという名前を付けてます。もう一つがその内容を表示するためのバッファ。viewという名前です。
まず、switch-to-bufferでfilesというバッファを開く。次いで、split-windowで二つに分割。この時点では、上下両方ともfilesバッファになってます。
今、カーソルは上のウィンドウにある。そこで、other-windowで下のウィンドウに行き、switch-to-bufferでviewバッファに切り替え。これで、上には、filesバッファ、下にはviewバッファというように、二つのウィンドウそれぞれ別のバッファが対応という状態になります。

文字列の表示

次に、今作ったバッファにファイル名一覧を表示することを考えます。
今回したいのは、あるディレクトリの中に入っているファイル名の取得。これは、directoryという関数でリストとして取得出来ます。

(directory "D:/")

というのを試しに実行してみましょう。Dドライブ下にあるファイルが、リストとして表示されますね。ちなみに、*scratch*にこれをコピペして、Ctrl+Jで出来ます。
次に、このリストを文字列として出力しなければならない。そのために、dolistを使います。これは、リストの要素それぞれに対して何かのコマンドを実行するというもの。

  (dolist (x (directory "D:/" :file-only t))
    (insert x "\n"))

insertというのは、文字列をバッファに挿入するもの。これで、リストになっていた要素、つまりパスが出力されるわけです。
で、これをさっきのウィンドウに組み込むと、次のようになる。

(defun rendou-view-window ()
  (interactive)
  (switch-to-buffer "view")
  (split-window -20 nil)
  (other-window)
  (switch-to-buffer "files")
  (dolist (x (directory "D:/" :file-only t))
    (insert x "\n")))
連動ビュー

次に、連動ビューです。filesバッファにおいてカーソルがある行からパスを取得し、その内容をviewバッファに出力すればいい。

カーソル行の取得には、buffer-substringを使います。これは、範囲を指定して文字列を取得するもの。カーソル行の取得は、

(defun get-line ()
  (interactive)
  (save-excursion
  (let (start end)
    (setq start (progn (goto-bol) (point)))
    (setq end (progn (goto-eol) (point)))
    (buffer-substring start end))))

まあこういうふうに行う。

けれど、これで取得できるのはファイル名のみ。directoryでは別にフルパスでも出力出来るんだけど、フルパスでない方が見やすいのでこうしている。そこで、これを元のフルパスに戻す必要がある。
そこで、 merge-pathnamesを使う。これは、ファイル名とディレクトリ名をくっつけるというものです。

 (merge-pathnames "bar.l" "c:/foo")

たとえばこういうようにすると、フルパスとしてc:/foo/bar.lが返ってくる。
このようにして取得したフルパスを、insert-file-contentsで下のバッファに出力します。これは、パスが与えられたらその内容を出力する関数。で、次のようになる。

(defun rendou-view ()
  (interactive)
  (let (filepath dir)
    (setq filepath (get-line))
    (setq dir "D:/")
    (other-window)
    (switch-to-buffer "view")
    (erase-buffer(selected-buffer))
    (insert-file-contents  (merge-pathnames filepath dir)))
  (other-window))
他のところ

上下キーに対応させたりとかいうので、他の関数があったりする。あと、それぞれのバッファの表示設定とかで色々付け足してたりします。リファレンスとか読んだら多分分かります。

今後

あとは、別ウィンドウで開いて編集をするだとかが出来たらいいだろうか。一行目の表示だとか、txtファイル以外は表示しないだとか。

やりたかったのは、こういう連動ビューの骨組みだけを作ること。Ugrepも近いことやってるけど、色々組み込んでて今見ると意味がわからない。今回のも、見た目的にはアレだけどね。ここで作ったのを応用していきたい。
Hyper Estraierを使うとか。今だったらGrepとかももっとうまく書ける気がするな。