読者です 読者をやめる 読者になる 読者になる

point-undoについて

Emacsでプログラミングしてて、あれーこのデータ型のコンストラクタってなんて名前だったかなー、とか、この関数の引数ってどういう順番だったかなー、とか、ちょっとあっちを見てきてすぐ現在地に戻って来たい、なんてことはしょっちゅうあるのですが、これが上手く行かないのですよね。例えば、こういうとき良く使うisearch-forwardは、検索中にCtrl-gを押すとちゃんと検索を開始した位置に戻ってきてくれるので偉いですが、うっかり検索したいやつが見つかった嬉しさのあまりEnterを押しちゃうと、戻れなくなったりして、非常に緊張するのです。

ブラウザに戻る・進むボタンがあって、メーラにも付いてる今日このごろですが、ちゃんとエディタにも付けてあげないといけないと思うのです。戻る・進む、機能。というか、Eclipseには付いてたはずだ。

そういうわけで、@ikegami__さんにTwitterでpoint-undoを教えていただきました。

カーソル位置を前に戻す point-undo.el をリリース - http://rubikitch.com/に移転しました

そうそう。こういうのが欲しかったんですよね。でも、ちょっと違う。

僕は、バッファも保存して欲しいと思ってますし、保存した位置よりも前をちょっと編集したら意味不明になっちゃうのも嫌ですし、なにより「あ、現在地を一度保存しなきゃ!」とか考えるのが嫌なのです。

とりあえずざっくりと実装したやつを一番下に書いておきますが、これはとりあえずできることだけ確認したやつです。

本当は次のようなことを考えてちゃんと実装したい。

  • 最初に「戻る」ときには現在地を保存しておきたい
    • でも何回も連続で「戻る」ってる最中に、一々現在地を保存されるのはウザい
  • markerを使ってるので、沢山位置を保存すると重くなるかもしれない。ならないかもしれない。
    • ちょっと考えてみればわかるけど、実は「戻る」リストの中のmarkerを全部一々更新してあげる必要は無くて、「戻る」瞬間に今戻りたい位置だけ再計算してあげれば良いので、パフォーマンスの問題はちゃんと実装すれば解決できるはず
  • どのコマンドをフックして、現在地の保存をやれば良いのか良くわからない
    • バッファを切り替えるときには現在地を保存したほうが良いような気がするけど、「戻る」操作がバッファを切り替えることもあるので、単純にswitch-to-bufferをフックするとまずいんじゃないかと思う。
  • むしろタイマーで現在地を保存しまくれば良いんじゃないだろうか
    • 履歴が多くなりすぎて意味不明になりそうだし、重くなりそうだけど、重くなる問題は位置の再計算をちゃんと実装すれば解決できるはずだし。
    • 意味不明になりそうな問題は、いっぱい移動してる場合は保存する、とかで解決できるんじゃないか。

まあ、プログラミングは簡単な機能なんですけど、デザイン上の問題は沢山ありますね。おもしろい。

(defvar navigate-point-list (cons (list) (list)))
(defvar navigate-point-hook-list nil)

(setq navigate-point-list (cons '() '()))

navigate-point-list

(defun navigate-point-go-forward (zip)
  (let* ((prev-list (car zip))
         (next-list (cdr zip)))
    (let ((p (car next-list)))
      (if p
          (progn
            (setcar zip (cons p prev-list))
            (setcdr zip (cdr next-list))))
      p)))

(defun navigate-point-go-prev (zip)
  (let* ((prev-list (car zip))
         (next-list (cdr zip)))
    (let ((p (car prev-list)))
      (if p
          (progn
            (setcar zip (cdr prev-list))
            (setcdr zip (cons p next-list))))
      p)))

(defun navigate-point-add-marker (zip marker)
  (let* ((prev-list (car zip))
         (next-list (cdr zip)))
    (progn
      (dolist (x next-list) (if x (set-marker x nil)))
      (setcar zip (cons marker prev-list))
      (setcdr zip nil))))

(defun navigate-point-insert-point ()
  (interactive)
  (let ((p (point-marker)))
    (unless (equal (caar navigate-point-list) p)
      (navigate-point-add-marker navigate-point-list (point-marker))
      (message "Current saved"))))


(defun navigate-point-jump-next-point ()
  (interactive)
  (let ((next-point (navigate-point-go-forward navigate-point-list)))
    (if next-point
        (progn
          (switch-to-buffer (marker-buffer next-point))
          (goto-char (marker-position next-point)))
      (message "No next point"))))

(defun navigate-point-jump-prev-point ()
  (interactive)
  (let ((prev-point (navigate-point-go-prev navigate-point-list)))
    (if prev-point
        (progn
          (switch-to-buffer (marker-buffer prev-point))
          (goto-char (marker-position prev-point)))
      (message "No prev point")
      )))

(defun navigate-point-pre-command-hook ()
  (message (format "pre-command-hook: %s" this-command))
  (unless (or (and 
               (eq this-command 'navigate-point-jump-prev-point)
               (eq last-command 'navigate-point-jump-prev-point))
              (eq this-command 'navigate-point-jump-next-point)
              (eq this-command 'navigate-point-insert-point))
    (if (memq this-command navigate-point-hook-list)
        (navigate-point-insert-point))))

(setq navigate-point-hook-list '(isearch-forward isearch-backward beginning-of-buffer end-of-buffer))

(global-set-key [f5] 'navigate-point-insert-point)
(global-set-key [M-right] 'navigate-point-jump-next-point)
(global-set-key [M-left] 'navigate-point-jump-prev-point)
;;(add-hook 'pre-command-hook 'navigate-point-pre-command-hook)

ライセンスはNYSLとします。