Hatena::ブログ(Diary)

Alone Like a Rhinoceros Horn

2007-10-14

shell-toggle の動作をちゃんとトグルに

shell-toggle によってシェルに切り替えたり、元のバッファに戻ったりといった動作を文字通りトグルにするために、ちょっと一手間加えてみた。

(autoload 'shell-toggle "shell-toggle"
  "Toggles between the *shell* buffer and whatever buffer you are editing." t)
(autoload 'shell-toggle-cd "shell-toggle"
  "Pops up a shell-buffer and insert a \"cd \" command." t)

(global-set-key "\C-cs" 'my-shell-toggle)

(defun my-shell-toggle ()
  "Toggles between the *shell* buffer and whatever buffer you are editing."
  (interactive)
  (if (string= (buffer-name) "*shell*")
	  (delete-window)
	(command-execute 'shell-toggle)))

元々の動作では shell-toggle を呼び出す度に、

バッファシェル(二分割) → シェルバッファ

をローテーションしてしまい、使い勝手が悪いなと感じたので、シェルが出たり引っ込んだり、文字通りトグルするようにしてみました。やっつけです。

2007-08-03

next-word-break, previous-word-break --- 前後の単語境界への移動

前後の単語境界へ移動するためのコマンド。

M-f, M-b と使い分ければ、「次の単語の先頭」、「前の単語の終端」への移動がちょっとラクになるかも。

(defun next-word-break (&optional arg)
  "Move point forward ARG word breaks (backward if ARG is negative)."
  (interactive "P")
  (setq arg (if arg (prefix-numeric-value arg) 1))
  (if (< arg 0)
      (previous-word-break (- arg))
    (while (and (> arg 0)
		(< (point) (point-max))
		(progn (forward-char)
		       (re-search-forward "\\b" nil t)))
      (setq arg (1- arg)))))

(defun previous-word-break (&optional arg)
  "Move point backward ARG word breaks (forward if ARG is negative)."
  (interactive "P")
  (setq arg (if arg (prefix-numeric-value arg) 1))
  (if (< arg 0)
      (next-word-break (- arg))
    (while (and (> arg 0)
		(> (point) (point-min))
		(progn (backward-char)
		       (re-search-backward "\\b" nil t)))
      (setq arg (1- arg)))))
(global-set-key "\M-F" 'next-word-break)
(global-set-key "\M-B" 'previous-word-break)

2007-07-29

実質的な行頭、行末への移動

beginning-of-line, end-of-line をちょっと便利に。
ポイントが既に行頭、行末にある場合はそれぞれ、

にポイントを移動します。

(defun my-beginning-of-line ()
  "Move point to beginning of current line.
If point is already at the beginning of line, move point to the first
non-whitespace character on this line."
  (interactive)
  (if (bolp) (skip-syntax-forward "-")
    (beginning-of-line)))

(defun my-end-of-line ()
  "Move point to end of current line.
If point is already at the end of line, move point to the last
non-whitespace character on this line."
  (interactive)
  (if (eolp) (skip-syntax-backward "-")
    (end-of-line)))
(global-set-key "\C-a" 'my-beginning-of-line)
(global-set-key "\C-e" 'my-end-of-line)

2007-07-01

shell-command-dwim

shell-command、shell-command-on-region を1つのコマンドにまとめたもの。使い分けが面倒なのと、出力をバッファへ挿入するのにいちいち前置引数がいるのが面倒だったので作ってみた。

リージョンが活性なら shell-command-on-region を、そうでないなら shell-command を使ってシェルに外部コマンドを実行させる。コマンドの出力でリージョンの内容を置換(リージョン活性時)/コマンドの出力をポイント位置に挿入、がデフォルトの動作。

リージョンが活性のときに前置引数を指定すると、コマンドからの出力でリージョンの内容を置き換えるのではなく、リージョンの下に出力を挿入する。

shell-command.el を入れている場合は、TAB でコマンドの補完入力も可能。

(defun shell-command-dwim (command &optional arg)
  "Execute string COMMAND in inferior shell; insert the COMMAND's output
at point.  If the mark is active, exexute COMMAND with region as input;
replace the region with the COMMAND's output.
With prefix argument, insert the COMMAND's output after the region
instead of replacing."
  (interactive (list
		;; shell-command.el による補完機能を利用
		(if (and (featurep 'shell-command) shell-command-completion-mode)
		    (shell-command-read-minibuffer shell-command-prompt
						   default-directory
						   nil nil nil 'shell-command-history)
		  (read-from-minibuffer "Shell command: "))
		current-prefix-arg))
  (if mark-active
      (progn (if arg (progn (command-execute 'copy-region-as-kill) ; Duplicate region
			    (goto-char (max (point) (mark)))
			    (newline 2)
			    (yank)))
	     (shell-command-on-region (region-beginning) (region-end) command t t))
    (shell-command command t))
  (goto-char (max (point) (mark t)))
  (if (= (point) (point-at-bol))
      (delete-backward-char 1)))

2007-06-30

rotate-buffer --- バッファの巡回

next-buffer、previous-buffer では、名前が * で始まるようなバッファ(ヘルプ用のバッファとか)も巡回の対象になり、移動したいバッファになかなか辿り着けない。

そこで、そういうのは適当にスキップして、ファイルを訪問しているバッファのみを対象とする、次のようなバッファ巡回用の関数を作ってみた。

(defvar rotate-buffer-ignored-buffers
  '()
  "*List of file-visiting buffer names ignored by `rotate-buffer'.")

(defvar rotate-buffer-not-ignored-*-buffers
  '("*Abbrevs*" "*Ibuffer*" "*info*" "*shell*")
  "*List of not-file-visiting buffer names not ignored by `rotate-buffer'.")

(defun rotate-buffer (&optional to-previous-p rotate-*-buffer-p)
  "Switch to the next file-visiting buffer in cyclic order.
If TO-PREVIOUS-P is non-nil, rotate in reversed order.
If ROTATE-*-BUFFER-P is non-nil, switch to the next not-file-visiting
buffer in cyclic order."
  (let ((rotate-buffer-function (if to-previous-p 'previous-buffer 'next-buffer))
        (old-buffer (current-buffer))
        (found nil))
    (catch 'not-found
      (while (not found)
        (funcall rotate-buffer-function)
	(if (rotate-buffer-found-p rotate-*-buffer-p)
	    (setq found t))
        (if (eq (current-buffer) old-buffer)
            (progn (message "No buffer")
                   (throw 'not-found nil)))
        ))
    ))

;; PRIVATE
(defun rotate-buffer-found-p (rotate-*-buffer-p)
  (let ((found (if buffer-file-name
		   (not (member (buffer-name) rotate-buffer-ignored-buffers))
		 (member (buffer-name) rotate-buffer-not-ignored-*-buffers))))
    (if rotate-*-buffer-p
	(setq found (not found)))
    found))

(defun my-next-buffer ()
  "Switch to the next file-visiting buffer in cyclic order."
  (interactive)
  (rotate-buffer nil nil))

(defun my-previous-buffer ()
  "Switch to the previous file-visiting buffer in cyclic order."
  (interactive)
  (rotate-buffer t nil))

(defun my-next-*-buffer ()
  "Switch to the next not-file-visiting buffer in cyclic order."
  (interactive)
  (rotate-buffer nil t))

(defun my-previous-*-buffer ()
  "Switch to the previous not-file-visiting buffer in cyclic order."
  (interactive)
  (rotate-buffer t t))

ファイルを訪問していないバッファでも、変数 rotate-buffer-not-ignored-*-buffers にバッファの名前を登録することで巡回の対象にできる。

また、my-next-*-buffer、my-previous-*-buffer で、通常は巡回の対象にならないバッファのみを巡回できる。

自分の場合、現在の設定はこう。

(global-set-key (kbd "M-]") 'my-next-buffer)
(global-set-key (kbd "M-:") 'my-previous-buffer)
(global-set-key (kbd "C-M-]") 'my-next-*-buffer)
(global-set-key (kbd "C-M-:") 'my-previous-*-buffer)

Emacs の場合、バッファを効率的に巡回する方法は他にも色々ありそうなので、(゚д゚)ウマー な機能なりパッケージなりを発見したらこんなのは早々に捨てるかも。

2007-06-29

copy-rectangle-as-kill

copy-region-as-kill の矩形版。なかったので作ってみた。

(defun copy-rectangle-as-kill (start end)
  "Save the region-rectangle as if killed, but don't kill it.
In Transient Mark mode, deactivate the mark."
  (interactive "r")
  (let ((saved-point (point)))
    (goto-char start)
    (kill-rectangle start end)
    (yank-rectangle)
    (goto-char saved-point)))
(global-set-key (kbd "C-x r M-w") 'copy-rectangle-as-kill)

2007-06-26

my-comment-dwim --- コメントアウト/解除

comment-or-uncomment-region の使い勝手を少し向上させたもの。リージョンが活性のときは通常通りリージョンのコメントアウト/解除を行うが、リージョンが活性でない場合はポイントのある行をコメントアウト/解除する。

リージョンは行単位に補正されるので、リージョンの開始位置、終了位置は行の途中にあってもよい。

前置引数を指定すると、リージョンを複製してからコメントアウト/解除を行う。修正前のコピーをとりあえずコメントとして残しておきたい場合などに便利。

(defun my-comment-dwim (arg)
  (interactive "*P")
  (make-line-atomic-region)		; Make mark active
  (if arg (progn (command-execute 'copy-region-as-kill)
		 (save-excursion
		   (goto-char (max (point) (mark)))
		   (newline 1) (yank))))
  (command-execute 'comment-or-uncomment-region))

以下は下請け。
リージョンを行単位に補正するコマンドとか。

(defun make-line-atomic-region (&optional arg)
  "Make line-atomic region.
If the mark is not active, set region to a line with point.
If interactively called and the region is already line-atomic, or 
with prefix argument, the region is extended to include its end's 
newline."
  (interactive "P")
  (if mark-active
      (let ((start (region-beginning))
	    (end (region-end)))
	(if (and (interactive-p) (line-atomic-region-p))
	    (setq arg t))
	(goto-char start)
	(beginning-of-line)
	(set-mark (point))
	(goto-char end))
    (progn (set-mark (point-at-bol))
	   (goto-char (point-at-eol))))
    (unless (= (point) (point-at-bol))
      (end-of-line)
      (if arg (progn (if (= (point) (point-max)) (save-excursion (newline)))
		     (forward-char 1))))
    (setq deactivate-mark nil))

(defun line-atomic-region-p ()
  "Return t if current region is line-atomic."
  (and
   (save-excursion
     (goto-char (min (point) (mark t)))
     (bolp))
   (save-excursion
     (goto-char (max (point) (mark t)))
     (eolp))))

2007-06-24

略語の登録をちょっと便利に

add-global-abbrev, add-mode-abbrev のラッパー。リージョンを登録するのに C-u 0 を指定するのがめんどくさかったので作ってみた。

リージョンが活性ならリージョンを展開形として登録する。そうでない場合は数引数の扱いも含めて通常通り。

(global-set-key "\C-xag" 'my-add-global-abbrev)
(global-set-key "\C-xal" 'my-add-mode-abbrev)

(defun my-add-global-abbrev (arg)
  "Define global (all modes) abbrev for last word(s) before point.
If the mark is active, the region is the expansion."
  (interactive "p")
  (if mark-active
      (add-global-abbrev 0)
    (add-global-abbrev arg)))

(defun my-add-mode-abbrev (arg)
  "Define mode-specific abbrev for last word(s) before point.
If the mark is active, the region is the expansion."
  (interactive "p")
  (if mark-active
      (add-mode-abbrev 0)
    (add-mode-abbrev arg)))

add-global-abbrev, add-mode-abbrev のドキュメントには

Don't use this function in a Lisp program; use `define-abbrev' instead.

とあるが、これは略語の登録をプログラムで非対話的に行う場合は〜、という意味だろう。
ラップする分には問題なし。

2007-05-20

Emacs Lisp と Ruby

Emacs Lisp と Ruby の勉強を並行して行っている。その率直な印象として、Lisp と Ruby は似ている。Ruby はユーザーフレンドリーな Lisp という感じだ。表層部分(字句、構文)は違えど、内部の実装(構文木になる辺りから)はかなり似通ってるんじゃなかろうか。素人の勝手な想像だけど、通底しているものがあると思う。

Lisp、Smalltalk → Ruby

という感じか。*1

Lisp をちょっとかじってから Ruby の勉強を始めると、Ruby の構文の向こう側、舞台裏にあたるところに(なんとなくではあるが)Lisp が透けて見えてくる。

イテレータとか、ブロックつき呼び出しって、Lisp で書くとこんな感じになってるんだろうな。とすると構文木はこんな感じか……

みたいなことを考えながら理解していくので、勉強も比較的さくさくと進む。*2

一方、似たような名前で似たような機能が提供されているため、同時に学習を進めると頭の中がゴチャゴチャになるところもある。

しかし、Ruby の理解が深まる上に Emacs もセットでものにできるこの並行学習の組み合わせは非常に (゚д゚)ウマー だと思う。

*1:Smalltalk は使ったことない。Objective-C からの類推

*2:Ruby のソースを読んだわけではないので、実際は全然違うのかも(;^ω^)

2007-04-10

Lisp 事始め

.emacs の内容もわかるようになってきたところで、早速ちょっとしたユーティリティ関数を二三、Emacs Lisp で書いてみた。テキストに区切り線を挿入する、という、たったそれだけのものなんだが、なんか、ものすごい時間がかかった。

Emacs の基本編集コマンドにもまだ不慣れだし、リファレンスを逐一参照しながらだったっていうのもあるが、一番難渋したのが、Lisp では手続き(関数)の実行順序(値を返す順序)*1と、それを記述する順序がまったく逆になってしまうことだった。

例えば、target に対し手続き proc1 を適用し、その返値に対して手続き proc2 を適用し、その返値に対して…… というようなことを記述する場合、Ruby や JavaScript などの、C++系(?)のメソッド呼び出し構文では、

target.proc1().proc2().proc3();

とすればいい*2が、Lisp では、

(proc3 (proc2 (proc1 target)))

という書き方になるので、慣れてないとまず最も内側の (proc1 target) を書いて、その後「ああ」と気付いてそれを (proc2 ) で囲む…… というようなことが頻発する。

また、object.method() とか object.property という書き方に慣れていると、ついつい Lisp でも関数と引数の順序を間違えて (object func) みたいなミスまでやってしまう。

そういうどんくさいことを何度もやっていたので、ちょっとした関数を書くだけでなんだかえらい時間がかかってしまった。

Lisp でばりばりプログラムを書く人の頭ん中って一体どうなってんだと正直思ったが、これも慣れの問題なのかなあ?

*1:うまく書けない。苦肉の表現。

*2:構文木にすると Lisp で書いた通りの木になるけど。