私は、音声認識でEmacsを制御できるEmacsモードに取り組んでいます。私が遭遇した問題の1つは、Emacsが元に戻す操作を処理する方法が、音声で制御するときの動作の期待と一致しないことです。
ユーザーがいくつかの単語を話してから一時停止すると、それは「発話」と呼ばれます。発話は、Emacsが実行する複数のコマンドで構成される場合があります。多くの場合、認識エンジンが発話内の1つ以上のコマンドを誤って認識します。その時点で、「元に戻す」と言って、発話内の最後のアクションだけでなく、発話によって行われたすべてのアクションをEmacsで元に戻すことができます。言い換えれば、発話が複数のコマンドで構成されている場合でも、アンドゥに関する限り、Emacsが発話を単一のコマンドとして扱うようにします。また、発話前の正確な場所に戻りたいのですが、通常のEmacsの取り消しではこれが行われないことに気付きました。
各発言の最初と最後にコールバックを取得するようにEmacsをセットアップしているので、状況を検出できます。Emacsに何をさせるかを把握する必要があります。理想的には私のような何かを呼びたい(undo-start-collapsing)
し、その後(undo-stop-collapsing)
、何完了挟んは魔法のように一つのレコードにまとめられます。
ドキュメントをいくつか探して見つけましたundo-boundary
が、それは私が望むものの反対です-発話内のすべてのアクションを、それらを分割せずに1つのアンドゥレコードに折りたたむ必要があります。undo-boundary
発話間で使用して、挿入が個別と見なされるようにすることができます(Emacsは、デフォルトでは連続する挿入アクションをある制限までの1つのアクションと見なします)が、それだけです。
その他の合併症:
- 私の音声認識デーモンは、X11キー押下をシミュレートすることによっていくつかのコマンドをEmacsに送信し、いくつかのコマンドを
emacsclient -e
そのように送信します(undo-collapse &rest ACTIONS)
。 - を使用しますが
undo-tree
、これにより事態がさらに複雑になるかどうかはわかりません。理想的には、ソリューションはundo-tree
Emacsの通常のアンドゥ動作で動作します。 - 発話内のコマンドの1つが「元に戻す」または「やり直し」の場合はどうなりますか?コールバックロジックを変更して、これらを常に個別の発話としてEmacsに送信して、物事をよりシンプルに保つことができると考えています。キーボードを使用する場合と同じように処理する必要があります。
- ストレッチ目標:発話には、現在アクティブなウィンドウまたはバッファを切り替えるコマンドが含まれる場合があります。この場合、各バッファで個別に「元に戻す」と言う必要がありますが、それほど派手である必要はありません。ただし、単一のバッファー内のすべてのコマンドはグループ化する必要があります。したがって、「do-x do-y do-zスイッチバッファーdo-a do-b do-c」と言うと、x、y、zは1つの取り消しになります。元のバッファのレコードとa、b、cは、バッファに切り替えられた1つのレコードでなければなりません。
これを行う簡単な方法はありますか?AFAICTには何も組み込まれていませんが、Emacsは広大で深い...
更新:少し余分なコードを追加して、以下のjhcのソリューションを使用することになりました。グローバルでbefore-change-hook
は、変更されているバッファがこの発言を変更したバッファのグローバルリストにあるかどうかをチェックし、そうでない場合はリストに入れてundo-collapse-begin
呼び出されます。次に、発話の最後に、リスト内のすべてのバッファーを反復処理し、呼び出しますundo-collapse-end
。次のコード(md-名前空間のために関数名の前に追加):
(defvar md-utterance-changed-buffers nil)
(defvar-local md-collapse-undo-marker nil)
(defun md-undo-collapse-begin (marker)
"Mark the beginning of a collapsible undo block.
This must be followed with a call to undo-collapse-end with a marker
eq to this one.
Taken from jch's stackoverflow answer here:
http://emacs.stackexchange.com/a/7560/2301
"
(push marker buffer-undo-list))
(defun md-undo-collapse-end (marker)
"Collapse undo history until a matching marker.
Taken from jch's stackoverflow answer here:
http://emacs.stackexchange.com/a/7560/2301"
(cond
((eq (car buffer-undo-list) marker)
(setq buffer-undo-list (cdr buffer-undo-list)))
(t
(let ((l buffer-undo-list))
(while (not (eq (cadr l) marker))
(cond
((null (cdr l))
(error "md-undo-collapse-end with no matching marker"))
((eq (cadr l) nil)
(setf (cdr l) (cddr l)))
(t (setq l (cdr l)))))
;; remove the marker
(setf (cdr l) (cddr l))))))
(defmacro md-with-undo-collapse (&rest body)
"Execute body, then collapse any resulting undo boundaries.
Taken from jch's stackoverflow answer here:
http://emacs.stackexchange.com/a/7560/2301"
(declare (indent 0))
(let ((marker (list 'apply 'identity nil)) ; build a fresh list
(buffer-var (make-symbol "buffer")))
`(let ((,buffer-var (current-buffer)))
(unwind-protect
(progn
(md-undo-collapse-begin ',marker)
,@body)
(with-current-buffer ,buffer-var
(md-undo-collapse-end ',marker))))))
(defun md-check-undo-before-change (beg end)
"When a modification is detected, we push the current buffer
onto a list of buffers modified this utterance."
(unless (or
;; undo itself causes buffer modifications, we
;; don't want to trigger on those
undo-in-progress
;; we only collapse utterances, not general actions
(not md-in-utterance)
;; ignore undo disabled buffers
(eq buffer-undo-list t)
;; ignore read only buffers
buffer-read-only
;; ignore buffers we already marked
(memq (current-buffer) md-utterance-changed-buffers)
;; ignore buffers that have been killed
(not (buffer-name)))
(push (current-buffer) md-utterance-changed-buffers)
(setq md-collapse-undo-marker (list 'apply 'identity nil))
(undo-boundary)
(md-undo-collapse-begin md-collapse-undo-marker)))
(defun md-pre-utterance-undo-setup ()
(setq md-utterance-changed-buffers nil)
(setq md-collapse-undo-marker nil))
(defun md-post-utterance-collapse-undo ()
(unwind-protect
(dolist (i md-utterance-changed-buffers)
;; killed buffers have a name of nil, no point
;; in undoing those
(when (buffer-name i)
(with-current-buffer i
(condition-case nil
(md-undo-collapse-end md-collapse-undo-marker)
(error (message "Couldn't undo in buffer %S" i))))))
(setq md-utterance-changed-buffers nil)
(setq md-collapse-undo-marker nil)))
(defun md-force-collapse-undo ()
"Forces undo history to collapse, we invoke when the user is
trying to do an undo command so the undo itself is not collapsed."
(when (memq (current-buffer) md-utterance-changed-buffers)
(md-undo-collapse-end md-collapse-undo-marker)
(setq md-utterance-changed-buffers (delq (current-buffer) md-utterance-changed-buffers))))
(defun md-resume-collapse-after-undo ()
"After the 'undo' part of the utterance has passed, we still want to
collapse anything that comes after."
(when md-in-utterance
(md-check-undo-before-change nil nil)))
(defun md-enable-utterance-undo ()
(setq md-utterance-changed-buffers nil)
(when (featurep 'undo-tree)
(advice-add #'md-force-collapse-undo :before #'undo-tree-undo)
(advice-add #'md-resume-collapse-after-undo :after #'undo-tree-undo)
(advice-add #'md-force-collapse-undo :before #'undo-tree-redo)
(advice-add #'md-resume-collapse-after-undo :after #'undo-tree-redo))
(advice-add #'md-force-collapse-undo :before #'undo)
(advice-add #'md-resume-collapse-after-undo :after #'undo)
(add-hook 'before-change-functions #'md-check-undo-before-change)
(add-hook 'md-start-utterance-hooks #'md-pre-utterance-undo-setup)
(add-hook 'md-end-utterance-hooks #'md-post-utterance-collapse-undo))
(defun md-disable-utterance-undo ()
;;(md-force-collapse-undo)
(when (featurep 'undo-tree)
(advice-remove #'md-force-collapse-undo :before #'undo-tree-undo)
(advice-remove #'md-resume-collapse-after-undo :after #'undo-tree-undo)
(advice-remove #'md-force-collapse-undo :before #'undo-tree-redo)
(advice-remove #'md-resume-collapse-after-undo :after #'undo-tree-redo))
(advice-remove #'md-force-collapse-undo :before #'undo)
(advice-remove #'md-resume-collapse-after-undo :after #'undo)
(remove-hook 'before-change-functions #'md-check-undo-before-change)
(remove-hook 'md-start-utterance-hooks #'md-pre-utterance-undo-setup)
(remove-hook 'md-end-utterance-hooks #'md-post-utterance-collapse-undo))
(md-enable-utterance-undo)
;; (md-disable-utterance-undo)
buffer-undo-list
マーカーとして、おそらくフォームのエントリに、独自のエントリを挿入できる場合があります(apply FUN-NAME . ARGS)
か?次にundo
、次のマーカーが見つかるまで繰り返し呼び出す発話を元に戻します。しかし、ここにはあらゆる種類の合併症があると思います。:)