レジスタ挿入時にポイント移動する

毎度毎度 Emacs の話です。

頻繁にという程でもないですが、文字列をレジスタに入れて (C-x r s) 後で取り出す (C-x r i) ということを割とします。しかし、デフォルトでは文字列を挿入しても、その場からポイントが動きません。yank (C-y) と同じように挿入した文字列の後ろにポイントが移動して欲しいと思うのですが…。

常々この動作を疑問に思っていて、暇なときに調べたら C-u C-x r i とすればポイントが移動するということを知りました。それって、デフォルトは移動して、C-u 付きの時に移動しない方が便利じゃない?ってことで反転させるアドバイス.emacs に書きました。

;; レジスタ挿入時にポイントが挿入した文字列の後ろに移動するように
(defadvice insert-register (before put-point-after activate)
  (setq arg (not arg)))

このデフォルト挙動を不満に思う記事をネット上で見た事ないのですが、誰も不便に思っていないのでしょうか。

ゴースト暗算みたいなものを練習してみた

先日の記事 で「ゴースト暗算」の凄さが分からないということを書きましたが、その後 Emacs を使って練習してみました。以下に結果をまとめます。

注意点

  • 私個人の感想です。誹謗のつもりも宣伝のつもりもありません。
  • 私は件の本を読んだ事がないので、頓珍漢なことを書いている可能性が高いです。手法自体は、こちらのまとめ を読んで、私が勝手に推測したものです。さらに、自分用にアレンジもしています。
  • 私は暗算が得意ではなく、(小学校の授業以外での) 珠算の経験もありません。
  • のべ 1 〜 2 時間の練習をしました。

2 桁の数字同士の乗算の練習結果

問題を見ながら 問題を見ずに
(大体) 筆算のように暗算 25 〜 40 秒、遅いと 1 分以上 1 分以上、あるいは途中で問題を忘れてしまう
ゴースト暗算もどき 10 〜 15 秒、遅くても 20 秒以内 20 〜 40 秒、遅くても 60 秒以内
筆算 10 秒前後 -

上の表の通り、効果は確かにありました。「筆算より遅い暗算に意味があるか?」という意見もあるかもしれませんが、聞く限り筆算より速いことを売りにした手法でもないようですし、そこは問題ではないと思います。また、筆算は「書く」時間を一定以下に縮めるのが非常に難しいので、ゴースト暗算もどきをさらに練習すれば、安定して筆算以上のスピードで答えを出せるようになるだろうと思います。

筆算の暗算に比べてゴースト暗算もどきが有利なのは、計算途中に覚えておかなければならない数字が少ない点です。大体 3 個 (瞬間的には 4 個) 覚えておけばいいです。問題を見ずに計算する場合も、7 個から 8 個の数字を覚えておけばいいので、私の頭でもギリギリやれます。

日常生活において 2 桁の乗算が速くできて嬉しいことはそれほど無さそうですが、子供が計算に対して苦手意識を持ちにくくなるとか、そういう効果は期待できるのかもしれませんね。

ちなみにこれ、継続して練習しないとすぐに錆び付きそうです。

ゴースト暗算もどきについて

先に書いた通り、私は本を読んだ訳ではないのでオリジナルのゴースト暗算の詳細も実は分かっていないのですが、この図 の内容そのままではなく、自分の頭 (たくさんの数字を覚えておけない) に合わせてアレンジしています。

下表は、AB と CD という 2 桁の数字同士を乗算した場合の思考の流れです。

計算位置 計算する事 覚える事 補足
1 AB x CD
AB x CD
-
A x C = EF
B x C = GH
(EF + G) x 10 + H = IJH
IJH お魚プレート計算。
2 AB x CD
-
B x D = K?
IJH + K = LMN
LMN B x D の 1 の位は覚え (られ) ない。
3 AB x CD
-
A x D = OP
LMN + OP = QRS
QRS 繰り上がりで頭が混乱する時は
LMN + (O * 10) + P
という風に 2 回に分ける事もある。
4 AB x CD
-
B x D = ?T
QRS * 10 + T = QRST
手順 2 で覚えなかった 1 の位を最後に計算。
QRST が答え。

具体的に、64 x 72 を当てはめてみます。

計算位置 計算する事 覚える事
1 64 x 72
64 x 72
-
6 x 7 = 42
4 x 7 = 28
(42 + 2) x 10 + 8 = 448
448
2 64 x 72
-
4 x 2 = 0?
448 + 0 = 448
448
3 64 x 72
-
6 x 2 = 12
448 + 12 = 460
460
4 64 x 72
-
4 x 2 = ?8
460 * 10 + 8 = 4608

正直、人に薦めるほどのものではないですが、一部で批判されている程悪い (意味が無い) ものでもないと私は思います。興味のある方は、一度 Emacs で練習してみてくださいませ。

ひたすら 2 桁の掛け算がしたい

世間では「ゴースト暗算」という計算メソッドが話題になっているようです。それに関する本も売れているようで、私は買っていないのですが、ネットで 2 桁の数字同士の掛け算の方法が紹介されていました。結構複雑 (に思える) な手順を踏むので、「むしろ筆算を頭の中でやる方が簡単ではないか?」というのが今のところの正直な感想です。

とはいえ、ただ批判していても面白くないので、練習したら速くなるのかどうか試すために、Emacs でランダムに問題を出して解答までの時間を計るコマンドを用意しました。

(defun 2dmul ()
  (interactive)
  (while t
    (let* ((rand (lambda () (+ (* (1+ (random 9)) 10) (1+ (random 9)))))
           (a (funcall rand))
           (b (funcall rand))
           (answer (* a b))
           (start (current-time))
           (input (string-to-number (read-buffer (format "%d x %d = " a b))))
           (end (current-time))
           (elapsed (+ (* 65536 (- (car end) (car start))) (- (nth 1 end) (nth 1 start)))))
      (message "%s [%d sec], %d x %d = %d (your answer: %d)"
               (if (= input answer) "OK" "NG") elapsed a b answer input))
    (read-event)))

M-x 2dmul で起動します。やめる時は C-g で抜けてください。

昔から計算が苦手なので、速くて 20 秒、普通は 30 から 40 秒、遅いと 1 分以上かかることもあります。ただし「問題は見てもいい」という条件で計っています。問題を見ずに (覚えて) となると、軽く 1 分は超えてしまいました。

さて、まずはゴースト暗算のあの手順を覚えるところからです…。

追記

この記事には 続き があります。

Magit で複数のファイルに対して操作する

職場では Windows を使っているのですが、Cygwin だからか git が非常に遅いです。今困っているのは、Magit で複数ファイルを一括してステージしたり、アンステージする方法が見つからない事。全部、というのは S とか U とかでできるのですが…。git status が遅い環境では、1 ファイルずつ s でステージするのは遅過ぎて現実的ではないのです。

仕方ないので、手っ取り早く問題を軽減する処理を書きました。

ソースは以下ですが、はっきり言って自分用なので、デバッグも甘く、使うにあたって内部動作の理解が多少必要だと思います。

;;;
;;; Magit のバッファからファイル名を抽出して利用
;;;

(defvar my-magit-selected-files ()
  "選択されているファイルの名前のリスト。")

(defconst my-magit-file-beginning-regexp
  "^\t\\(Unmerged +\\|New +\\|Deleted +\\|Renamed +\\|Modified +\\|\\? +\\)?"
  "ファイルを表す行の行頭の正規表現。")

(defface my-magit-selected-face
  '((((class color) (background light))
     :background "LightGoldenRod")
    (((class color) (background dark))
     :background "DarkGoldenRod"))
  "選択中のファイルを表すフェイス。")

(defun my-magit-valid-buffer-p ()
  "操作可能な Magit のバッファかどうか。"
  (or (and (boundp 'magit-version)
           (eq major-mode 'magit-status-mode)) ; ver >= 1.1.0
      (eq major-mode 'magit-mode))) ; ver < 1.1.0

(defun my-magit-select-files (prefix)
  "ポイントのある行、もしくはリージョンのある行のファイルを選択。
PREFIX が t の場合 (前置引数がある場合) は、これまでの選択を一旦破棄してから処理します。"
  (interactive "P")
  (when (my-magit-valid-buffer-p)
    (when prefix (my-magit-clear-selected-files))
    (let (beg end lines)
      (if (and transient-mark-mode mark-active)
          (progn
            (setq mark-active nil)
            (setq beg (min (point) (mark)))
            (setq end (max (point) (mark)))
            (save-excursion
              (goto-char beg)
              (setq beg (line-beginning-position))
              (goto-char end)
              (unless (eq end (line-beginning-position))
                (setq end (line-end-position)))))
        (setq beg (line-beginning-position))
        (setq end (line-end-position)))
      (setq lines (split-string (buffer-substring-no-properties beg end) "[\n]+"))
      (while lines
        (let ((line (car lines)))
          (setq lines (cdr lines))
          (when (string-match (concat my-magit-file-beginning-regexp "\\(.+\\)") line)
            (let* ((file (match-string 2 line))
                   (hi-regexp (concat my-magit-file-beginning-regexp (regexp-quote file))))
              (if (member file my-magit-selected-files)
                  (progn
                    (setq my-magit-selected-files (delete file my-magit-selected-files))
                    (hi-lock-unface-buffer hi-regexp))
                (setq my-magit-selected-files (cons file my-magit-selected-files))
                (hi-lock-face-phrase-buffer hi-regexp 'my-magit-selected-face)))))))))

(defun my-magit-clear-selected-files ()
  "`my-magit-select-files' で選択されたファイルを全て破棄する。"
  (interactive)
  (when (my-magit-valid-buffer-p)
    (while my-magit-selected-files
      (hi-lock-unface-buffer (concat my-magit-file-beginning-regexp
                                     (regexp-quote (car my-magit-selected-files))))
      (setq my-magit-selected-files (cdr my-magit-selected-files)))))

(defun my-magit-re-hi-lock (&optional buffer)
  "`my-magit-select-files' で選択されたファイルを再度強調表示する。"
  (when (my-magit-valid-buffer-p)
    (let ((files my-magit-selected-files))
      (while files
        (let ((hi-regexp (concat my-magit-file-beginning-regexp
                                 (regexp-quote (car files)))))
          (hi-lock-unface-buffer hi-regexp)
          (hi-lock-face-phrase-buffer hi-regexp 'my-magit-selected-face)
          (setq files (cdr files)))))))

(defadvice magit-refresh-buffer (after my-hi-lock activate)
  "Magit のバッファの表示が更新された際には選択中のファイルを再度強調表示する。"
  (with-current-buffer (or buffer (current-buffer))
    (my-magit-re-hi-lock buffer)))

(defun my-magit-selected-files-string ()
  "`my-magit-select-files' で選択されたファイルを空白区切りの文字列として返す。"
  (if my-magit-selected-files
      (mapconcat (lambda (x) (concat "'" x "'")) my-magit-selected-files " ")
    ""))

(defun my-magit-insert-selected-files ()
  "`my-magit-select-files' で選択されたファイルを空白区切りの文字列としてバッファに挿入する。"
  (interactive)
  (insert (my-magit-selected-files-string)))

ユーザがインタラクティブに呼び出すのは、

  • my-magit-select-files (ファイルの選択、非選択切り替え)
  • my-magit-clear-selected-files (全選択解除)
  • my-magit-insert-selected-files (選択ファイルを文字列化してバッファに挿入)

の 3 つです。以下のように、自分の好きなキーに割り当てるといいと思います。私は今のところ、選択の切り替えは「@」に、全解除は「`」に、文字列挿入は C-c i に割り当てています。

(add-hook 'magit-mode-hook
          ;; Magit で有効なキー設定
          (lambda ()
            (local-set-key (kbd "@") 'my-magit-select-files)
            (local-set-key (kbd "`") 'my-magit-clear-selected-files)))

(global-set-key (kbd "C-c i") 'my-magit-insert-selected-files)

動作確認環境は以下の通りです。

それにしても…

Magit に dired のような、操作対象ファイルのマーク機能があってもよさそうなものなのですが…見当たりません。「そんな面倒なことしなくても、こうしたらいいよ!」という情報をお待ちしております。

そもそも、Windows で快適に使える git バイナリを用意する方が良さそうですけれど。

Emacs で Excel のオートフィルのようなことをする

皆さんこんにちは。ken_m あるいは @kurenai_nobuta です。
これは、Emacs Advent Calendar jp: 2011 の 20 日目の記事として書いています。昨日 19 日目は @myuhe さんの 連続操作を素敵にするsmartrep.el作った でした。明日 21 日目は /dev/null さんです。

いや、久しぶりに自分の血の気が引く音を聞きました。何故って、この記事のアップロード直前に @myuhe さんの記事を読んだからです。まさかこのタイミングでネタが被ったかと…。幸いネタはほとんど被っていなかったですが、名前は完全に被ってしまいました。@myuhe さん、ごめんなさい。悪気はなかったです。

ちなみに、私のニコニコ動画のアカウント名は null なので、明日の分ともちょっと被ってます。嗚呼…。

何を作ったのか?

今回作ったのは srep (スレップ)という小さなプログラムです。その目的は、Excel のオートフィル機能のようなことを Emacs 上で実現することです。ご存知の方も多いと思いますが、規則性のある値を持ったセルの並びを選択した後、その範囲をマウスで広げると、広げた先のセルにも値が自動的に埋まるアレです。

実際に動作を見てもらった方が分かると思うので、動画を作りました。ページ埋め込みのままだと小さくて何が何だか分からないと思うので、直接 YouTube で見てもらった方がいいと思います。

ソースは github に置いてあります。

何故作ったのか?

元々は、第 3 回の関西 Emacs 勉強会で何か発表しようと考えていた時に思いついたネタなのですが、仕事の都合もあって第 3 回でも第 4 回でも準備(あるいは参加)できず、今日にまで至ってしまいました。つまり、発表のために無理矢理ひねり出したネタで、以前から欲しかったとかではないです。ただ、思いついてから今までに、月に 1 回くらいのペースで「今これがあれば嬉しかったな」と思うことがあったので、自分の中では全く需要がないというわけでもなさそうです。

まぁ、たまには elisp も書かないと忘れる一方ですし、意思が弱くて締め切りがないとやれないタイプなので、Advent Calendar が丁度いい機会だったわけです。

名前の由来

動画でも触れていますが、srep は smart text repeater の略です。「オートフィル」というのは Emacs では別の機能のことを指すので使わない方がいいと思いますし、短くて覚えやすそうな名前がいいと考えました。何故なら、srep のような「ごくまれに便利なこともあるけれど普段はほとんど使わない」コマンドは、キーにバインドしても何にバインドしたか忘れてしまうんですよね。であれば、もう常に M-x で呼び出す事を前提に考えて、その時名前だけは思い出せるようなものにしよう、と。

今後の予定

今回は時間の都合で、本当にやりたかったところまで実装できませんでした。基本機能は完成していますが、繰り返し要素はもっと色々対応する予定だったのです。例えば、

  • 実数(今は整数にしか対応していない)
  • 時間(HH:MM:SS)
  • 日付の色々な表現(2011/12/20、Dec. 20, 2011、etc)
  • 漢数字、全角数字

などなど。それぞれはちょっと時間があればすぐに実装できるのですが、この先そのモチベーションを保てるかどうか…。前述の通り、この機能が欲しかったわけではなく、elisp を書く事自体が目的だったので。


でも久しぶりに elisp を書いて楽しかったです。他の方々の記事もとても楽しんでます。皆さん、よいクリスマスを。

ElScreen のスクリーン毎に背景色を変える

何のために?と言われそうですが、私は ElScreen のスクリーン毎に背景色を切り替えています。 前回の 記事の通り、私はスクリーン番号を 0 番ではなく 1 番から始めていて、 1 番は暗い緑、2 番は暗い赤、3 番は黒、そして 4 番以降は前述の 3 色のローテーションという感じ。3 色しか用意していないのは、4 つ以上のスクリーンを使うことがないからです。

以下がそのプログラムです。

;;;
;;; スクリーンごとに背景色を切り替える
;;;

(defvar my-screen-background-colors '("#101010" "#102800" "#401000"))
(defun my-set-screen-background-color (&optional dummy)
  (set-background-color (nth (% (elscreen-get-current-screen) (length my-screen-background-colors))
			     my-screen-background-colors)))
(add-hook 'elscreen-goto-hook 'my-set-screen-background-color)
(add-hook 'elscreen-kill-hook 'my-set-screen-background-color)

変数 my-screen-background-colors には、最大 10 種類の色を設定できます。

動作確認環境は以下の通りです。

動機など

何故背景色を変えたいのか、何が便利なのか、と言われると色々自分なりの歴史があって、しかし明確な何かはなくて「うーん」と言葉に詰まります。私はスクリーンの 1 〜 3 の使い方がほぼ固定されていて、1 番は ChangeLog メモ、2 番は主作業用、3 番は一時作業用あるいはシェル…という感じです。ElScreen 導入前は、C-x 5 2 でフレームを作成して、フレーム毎に背景色を変更していました。フレームの場合は、一部分でも見えていれば目的のフレームをデスクトップから素早く探し出せる、という明確な (?) 背景色変えのメリットがあります。その後 ElScreen を使うようになったのですが、それまでの流れで「ChangeLog の背景は暗い緑、作業時の背景は暗い赤」というような決まり事が自分の中でできてしまったんですね。

さらに遡ると、10 年以上前に秀丸を使っていた際、拡張子ごとに固有の背景色を設定していた、という経緯に辿り着くんですけど…。

ElScreen のスクリーン番号を 1 から始める

ElScreen のスクリーン番号は 0 から始まるのですが、タブの並びとキーボードの並びと合わないことが気になる人は結構いるようです。かく言う私もその一人で、下記プログラムで無理矢理 1 から始まるようにしています。

;;;
;;; elscreen のタブの並びと数字キーの並びを合わせる
;;;

;; 既存スクリーンのリストを要求された際、0 番が存在しているかのように偽装する
(defadvice elscreen-get-screen-list (after my-ad-elscreen-get-screenlist disable)
  (add-to-list 'ad-return-value 0))

;; スクリーン生成時に 0 番が作られないようにする
(defadvice elscreen-create (around my-ad-elscreen-create activate)
  (interactive)
  ;; 0 番が存在しているかのように偽装
  (ad-enable-advice 'elscreen-get-screen-list 'after 'my-ad-elscreen-get-screenlist)
  (ad-activate 'elscreen-get-screen-list)
  ;; 新規スクリーン生成
  ad-do-it
  ;; 偽装解除
  (ad-disable-advice 'elscreen-get-screen-list 'after 'my-ad-elscreen-get-screenlist)
  (ad-activate 'elscreen-get-screen-list))

;; スクリーン 1 番を作成し 0 番を削除 (起動時、フレーム生成時用)
(defun my-elscreen-kill-0 ()
  (when (and (elscreen-one-screen-p)
             (elscreen-screen-live-p 0))
    (elscreen-create)
    (elscreen-kill 0)))

;; フレーム生成時のスクリーン番号が 1 番になるように
(defadvice elscreen-make-frame-confs (after my-ad-elscreen-make-frame-confs activate)
  (let ((selected-frame (selected-frame)))
    (select-frame frame)
    (my-elscreen-kill-0)
    (select-frame selected-frame)))

;; 起動直後のスクリーン番号が 1 番になるように
(add-hook 'after-init-hook 'my-elscreen-kill-0)

動作確認環境は以下の通りです。

実装について

正直、スクリーン番号を 1 から始めるだけなら、elscreen.el を直接改造してしまった方が簡単です。ElScreen は十分に枯れているようですが、仮に本家が更新されたとしても、.emacs.d をローカルでバージョン管理していれば直接改造して問題になることもないと思います。にも関わらず、上記のようにアドバイスで無理矢理なんとかしたのは、単に気分の問題です。

スクリーンを操作するキーバインディング

普段仕事では Windows がほとんどなので、スクリーン操作を Windows 風にしています。

;; C-tab/C-S-tab でスクリーンの切り替え、C-F4 でスクリーンの削除
(global-set-key [C-tab] 'elscreen-next)
(global-set-key [C-S-tab] 'elscreen-previous)
(global-set-key [C-f4] 'elscreen-kill)

;; M-0 〜 M-9 で指定番号のスクリーンに切り替え
(let ((i 0))
  (while (<= i 9)
    (define-key esc-map (number-to-string i)
                        `(lambda () (interactive) (elscreen-goto ,i)))
    (setq i (1+ i))))

"M-数字" は Magit と重なっているので、Magit の方を封じないと不便だなぁ、と思っている今日この頃です。Linux で仮想コンソールを切り替えることに慣れている人は、"M-数字" より "M-ファンクションキー" の方がいいかもしれません。