大文字小文字を切り替え

downcase-wordとupcase-wordの二つがxyzzyには標準の機能で付いているわけだけど、両方同時には使えないので、一個で良いんじゃないかと。大文字の時には小文字にして、小文字の時は大文字にするようにすれば、キーも一つ空くわけで。デフォルトだとM-lとかM-uとかの結構押しやすいキーに割り当てられているのでもったいないような。まあ、大文字小文字を問わずに連打して全部大文字とかにする時には役に立つけど、リージョン単位での大文字化小文字化機能もあるわけで。

(defun up-or-down-case-word(&optional (arg 1))
  "現在の文字が小文字なら大文字に大文字ならば小文字にする。downcase-wordとupcase-wordを一つにしたもの。"
  (interactive "*p")
  (if (upper-case-p (character (buffer-substring (point)(1+ (point)))))
	  (downcase-word)
	(upcase-word)))

英語標準ソフトにありがちだけど、余り使用頻度の高くない機能にまで、やけに豪華にキーが割り当てられていて、使いそうな機能に割り当てるキーとかが残っていなかったりするのがなんともいえない。

ついでに前に書いたのを一つにしておいたのも保存もかねて、置いておくことに。virtual-line-key.l key-saving.l

■追記
色々コメントがあったので、適当に参考にして変更。

(defun up-or-down-case-word(&optional (arg 1))
  "現在の文字が小文字なら大文字に大文字ならば小文字にする。downcase-wordとupcase-wordを一つにしたもの。"
  (interactive "*p")
  (if (upper-case-p (following-char))
	  (downcase-word arg))
  (if (lower-case-p (following-char))
	  (upcase-word arg)))

使用感としてはやっぱり、連打する時にやや微妙な気もする。後は、[0aaa]見たいに数字とか大文字でも小文字でもない文字が文字列の先頭に来て且つ、カーソルがその位置にある場合とかは微妙。まあ、あまりこの機能使わないし。いいことにした。

と思ったけど、何故か直してみた。記号が来たときには無視して次のアルファベットを探すことにした。M-uとかの挙動が現在のカーソル位置が空白でも何故か無視して次の単語を大文字とかにしている理由も少し分かったような分からないような。

適当に変更。

(defun up-or-down-case-word(&optional (arg 1))
  "現在の文字が小文字なら大文字に大文字ならば小文字にする。downcase-wordとupcase-wordを一つにしたもの。"
  (interactive "*p")
  (let ((end (save-excursion (forward-word) (point))))
	(while (scan-buffer "[A-Za-z]" :regexp t :limit end)
	  (if (upper-case-p (following-char))
		  (downcase-word arg))
	  (if (lower-case-p (following-char))
		  (upcase-word arg)))))

やっぱり連打は多少不便。はじめの時に大文字にしたら連打している間は同じ動作にするとかそういう風にすれば解決するかな。まあいいや。
と思ったけど、少し試しにやってみた。

(defvar *case-word-history* nil)
(defvar *repeat-time* (get-universal-time))
(defun up-or-down-case-word(&optional (arg 1))
  "現在の文字が小文字なら大文字に大文字ならば小文字にする。downcase-wordとupcase-wordを一つにしたもの。"
  (interactive "*p")
  (if (eq *last-command* 'up-or-down-case-word)
	  (if *case-word-history*
		  (downcase-word arg)
		(upcase-word arg)))
  (let ((end (save-excursion (forward-word) (point))))
	(while (scan-buffer "[A-Za-z]" :regexp t :limit end)
	  (when (upper-case-p (following-char))
		(downcase-word arg)
		(setq *case-word-history* t))
	  (when (lower-case-p (following-char))
		(upcase-word arg)
		(setq *case-word-history* nil))
	  )))

今度は、LOWER caseを lower CASEに変更したいような時に面倒な気もする。Lにカーソルがあるときに、押すと小文字にしてlowerにしてくれるのはいいが、連続してcaseを大文字に変えようとして小文字にしてしまう。タイムスタンプでもとって比較すれば良いのかもしれないが。
と思ったのでやってみた。

(defvar *case-word-history* nil)
(defvar *repeat-time* (get-internal-real-time))
(defun up-or-down-case-word (&optional (arg 1))
  "現在の文字が小文字なら大文字に大文字ならば小文字にする。downcase-wordとupcase-wordを一つにしたもの。"
  (interactive "*p")
  (if (and (eq *last-command* 'up-or-down-case-word)
		   (> *repeat-time* (- (get-internal-real-time) 500)))
	  (if *case-word-history*
		  (progn
			(downcase-word arg)
			(return-from up-or-down-case-word t))
		(progn
		  (upcase-word arg)
		  (return-from up-or-down-case-word t)))
	(let ((end (save-excursion (forward-word) (point))))
	  (while (scan-buffer "[A-Za-z]" :regexp t :limit end)
		(when (upper-case-p (following-char))
		  (downcase-word arg)
		  (setq *case-word-history* t))
		(when (lower-case-p (following-char))
		  (upcase-word arg)
		  (setq *case-word-history* nil)))
	    (goto-char end)))
  (setq *repeat-time* (get-internal-real-time)))

変数を2つも作ってしまうのが微妙だけど。まあ一応動くのでいいことにした。*repeat-time*とか見たいな物は結構汎用性が高そうなので元々xyzzyにも何かあるかもしれないが。暫く試してみたら何かリピートするタイミングが微妙。0.5とか適当に引くのは微妙なのかもしれない。というかあのget-universal-timeで帰ってくる数字が秒単位でしか変動していないんじゃ。(get-internal-real-time)と言うのの方が具合が良さそう。だったので変えてみた。カーソル位置が平仮名の時とかは一応M-uとかと挙動を合わせて見た。