わさっきhb

大学(教育研究)とか ,親馬鹿とか,和歌山とか,とか,とか.

ElScreenに機能拡張

月の頭に書きましたが,現在,WindowsでのテキストエディタMeadowからNTEmacsに移行しています.
MeadowのNetinstallで入っていたElScreenが,NTEmacsにはなく,インストールしました.
使っていると,いろいろ不満も出てきたので,Emacs Lispの復習を兼ねて,機能拡張を試みました.

ElScreenのインストール

シェルを開いて,こうすればいいんですかね.

wget ftp://ftp.morishima.net/pub/morishima.net/naoto/ElScreen/elscreen-1.4.6.tar.gz
tar xf elscreen-1.4.6.tar.gz
cp elscreen-1.4.6/elscreen.el ~/.ntemacs.d/elisp

「~/.ntemacs.d」というのはあまり自然ではありませんが,Meadowと棲み分けるためです.あるマシンには,MeadowなしでNTEmacsを入れる予定で,そういうのでも大丈夫になることを念頭に置いています.もちろん,~/.ntemacs.d/elispというディレクトリは,あらかじめ作っておきます.
~/.emacsに書くのはこんな感じですか.

(add-to-list 'load-path "~/.ntemacs.d/elisp")

(when (require 'elscreen nil t)
  (if window-system
      (define-key elscreen-map (kbd "C-z") 'iconify-or-deiconify-frame)
    (define-key elscreen-map (kbd "C-z") 'suspend-emacs)))

しかし,起動すると,エラーが出ました.少し苦労した結果,APEL (A Portable Emacs Library)というのが必要そうです.コマンドは

wget http://kanji.zinbun.kyoto-u.ac.jp/~tomo/lemi/dist/apel/apel-10.8.tar.gz
tar xf apel-10.8.tar.gz
mv apel-10.8 ~/.ntemacs.d/elisp/apel

とし,~/.emacs

(add-to-list 'load-path "~/.ntemacs.d/elisp/apel")

を書き加えました.
これで,うまく動きました.

Shiftを押しながらDragで,複数のファイルを別々のスクリーンで開く

C-z C-cでスクリーンを開き,C-z C-nで次のスクリーンに移動.不要なスクリーンはC-z kとするか,上のタブの「[X]」のところをクリック.
…と,しばらくは快適だったのですが,不満が出てきました.ドラッグ&ドロップでファイルを開いたときに,今参照なり編集なりしていたスクリーンで開かれます.
これを,新規にスクリーンを作って開くようにしましょう.複数のファイルをドラッグ&ドロップしたら,別々にスクリーンを作りましょう.キーバインドは,Shiftを押しながらドラッグ&ドロップとしましょう.…
出来上がったコードは,以下のとおりです.

(defun w32-drag-n-drop-elscreen-onefile (event)
  "Edit the file using ElScreen"
  (interactive "e")
  (elscreen-create)
  (find-file event))
(defun w32-drag-n-drop-elscreen (event)
  "Edit the files using ElScreen"
  (interactive "e")
  (mapcar 'w32-drag-n-drop-elscreen-onefile (car (cdr (cdr event)))))
(global-set-key [S-drag-n-drop] 'w32-drag-n-drop-elscreen)

参考にしたのは,(NTEmacsディレクトリ)/lisp/term/w32-win.elです.この中には,w32-drag-n-drop,w32-drag-n-drop-other-frame*1という関数が定義されています.前者の関数と,

(defun drag-n-drop-trial (event)
  "Edit the files using ElScreen"
  (interactive "e")
  (insert event))
(global-set-key [S-drag-n-drop] 'drag-n-drop-trial)

をM-x eval-regionしたあとで,Shiftを押しながらドラッグ&ドロップをしたときの結果から,(car (cdr (cdr event)))がファイル名のリストになっていることを確認しました.
mapcarは,w32-drag-n-drop-other-frameの定義で知りました.http://www.offshorecad.com.ph/autocad/lesson/autolisp/entry709/も参考にしました.

…と,苦労したところで,ElScreen | Fragments of Realityを読み直すと,ElScreen-dndというのが,同じことをしてくれるようです.elscreen-dnd-0.0.0.tar.gzをダウンロードし,elscreen-dnd.elを見てみたものの,訳の分からないコードのオンパレードです.理解する体力も時間の余裕もなく,心の中でごめんなさいと唱えてファイルを削除しました.

番号を指定してスクリーンを消す

C-x k RETでバッファを消すのと同様に,C-z k RETでスクリーンを消したいと思うようになりました.素の設定では,C-z kでスクリーンが消え,その後のRETは,バッファで改行をしてしまうことになります.
RETの前に数字を入力できるようにしたいものです.デフォルトはカレントのスクリーンです.存在しないスクリーン番号を指定したときには,何も消さないようにします.
あれこれ苦労しながら出来上がったコードは:

(defun elscreen-kill-by-number (arg)
  "Kill screen"
  (interactive "sKill screen: ")
  (if (string= arg "")
      (elscreen-kill)
    (let* ((scrnum (elscreen-get-current-screen))
           (killnum (string-to-number arg)))
      (if (eq scrnum killnum)
          (elscreen-kill)
        (if (elscreen-goto killnum)
            (progn
              (elscreen-kill)
              (elscreen-goto scrnum)))))))
(define-key elscreen-map "k" 'elscreen-kill-by-number)

interactiveに関しては,http://kzk9.net/column/emacs/elisp_interactive.htmlと,自分がこれまでに書き残したEmacs Lispを参考にしました.「"sKill screen: "」の先頭文字のsは任意の文字列をとる(ミニバッファでユーザが入力できる)ことを意味します.ここをnにすると,どうも必ず数値を入力することが要求され,RETのみを押すことができません.
動作確認を終えてほどなく,elscreen-kill-screen-and-buffersの存在に気づきました.現在のスクリーンだけでなくバッファも消去するという関数です.
C-z k RETでスクリーンを消すのと同様に,C-z M-k RETでスクリーンとバッファを消したいと思うようになりました.コードを作り替えます.

(defun elscreen-kill-by-number-and-method (arg method)
  (if (string= arg "")
      (eval method)
    (let* ((scrnum (elscreen-get-current-screen))
           (killnum (string-to-number arg)))
      (if (eq scrnum killnum)
          (eval method)
        (if (elscreen-goto killnum)
            (progn
              (eval method)
              (elscreen-goto scrnum)))))))
(defun elscreen-kill-by-number (arg)
  "Kill screen"
  (interactive "sKill screen: ")
  (elscreen-kill-by-number-and-method arg (list 'elscreen-kill)))
(defun elscreen-kill-screen-and-buffer-by-number (arg)
  "Kill screen and buffer"
  (interactive "sKill screen: ")
  (elscreen-kill-by-number-and-method arg (list 'elscreen-kill-screen-and-buffers)))
(define-key elscreen-map "k" 'elscreen-kill-by-number)
(define-key elscreen-map "\M-k" 'elscreen-kill-screen-and-buffer-by-number)

期待通りの動作をしてくれます.
「(list 'elscreen-kill)」と「(list 'elscreen-kill-screen-and-buffers)」という書き方は,もっと簡潔な記述に置き換えられそうですが,分かりません.

elscreen-createしてからfind-file

C-x C-fは言わずと知れたfind-fileです.C-x fで,「今日のファイル」を開けるようにしたのは,「今日のファイル」「昨日のファイル」「数日前のファイル」を開く - わさっきのことです.
ファイル名を指定すると,elscreen-createしてからfind-fileするような機能があるといいのですが…これもコードにしました.

(defun find-file-new-screen (filename)
  "Edit file on new screen"
  (interactive "fFind file: ")
  (elscreen-create)
  (find-file filename))
(global-set-key "\C-xg" 'find-file-new-screen)

C-x gというキーバインドは,たまたま空いていたので割り当てたのですが,これが分かりやすいかどうかは何とも言えません.C-x C-gとすると,C-xが無効になって,何もしないのと同じになるのですが.

*1:Ctrlを押しながらドラッグ&ドロップだと,フレーム(いわゆる別ウィンドウ)を作ってそこにファイルを開きます.これはこれで,使いどころがあるように思います.