Ich arbeite an einem Emacs-Modus, mit dem Sie Emacs mit Spracherkennung steuern können. Eines der Probleme, auf das ich gestoßen bin, ist, dass die Art und Weise, wie Emacs mit dem Rückgängigmachen umgeht, nicht mit der erwarteten Funktionsweise bei der Steuerung per Sprache übereinstimmt.
Wenn der Benutzer mehrere Wörter spricht und dann pausiert, spricht man von einer Äußerung. Eine Äußerung kann aus mehreren Befehlen bestehen, die Emacs ausführen soll. Es ist häufig der Fall, dass der Erkenner einen oder mehrere Befehle innerhalb einer Äußerung falsch erkennt. An diesem Punkt möchte ich in der Lage sein, "Rückgängig" zu sagen und Emacs alle Aktionen rückgängig machen zu lassen , die von der Äußerung ausgeführt werden, nicht nur die letzte Aktion innerhalb der Äußerung. Mit anderen Worten, ich möchte, dass Emacs eine Äußerung in Bezug auf das Rückgängigmachen als einen einzigen Befehl behandelt, auch wenn eine Äußerung aus mehreren Befehlen besteht. Ich würde auch gerne darauf hinweisen, genau dorthin zurückzukehren, wo es vor der Äußerung war. Mir ist aufgefallen, dass normale Emacs-Rückgängigmachungen dies nicht tun.
Ich habe Emacs so eingerichtet, dass zu Beginn und am Ende jeder Äußerung Rückrufe erfolgen, damit ich die Situation erkennen kann. Ich muss nur herausfinden, was Emacs tun soll. Im Idealfall würde ich so etwas wie (undo-start-collapsing)
und dann nennen (undo-stop-collapsing)
und alles, was dazwischen gemacht wird, würde auf magische Weise zu einer einzigen Platte zusammengefasst.
Ich habe die Dokumentation durchsucht und festgestellt undo-boundary
, aber es ist das Gegenteil von dem, was ich will - ich muss alle Aktionen innerhalb einer Äußerung in einem Undo-Datensatz zusammenfassen und nicht aufteilen. Ich kann undo-boundary
zwischen Äußerungen verwenden, um sicherzustellen, dass Einfügungen als getrennt betrachtet werden (Emacs betrachtet aufeinanderfolgende Einfügeaktionen standardmäßig bis zu einer gewissen Grenze als eine Aktion), aber das war's.
Andere Komplikationen:
- Meine Spracherkennung Daemon sendet einige Befehle Emacs von X11 Drücken von Tasten simuliert und sendet über einige
emacsclient -e
so, wenn es sagen würde eine(undo-collapse &rest ACTIONS)
gibt es keinen zentralen Ort , wo ich wickeln kann. - Ich bin mir
undo-tree
nicht sicher, ob das die Dinge komplizierter macht. Idealerweise würde eine Lösung mitundo-tree
dem normalen Rückgängig-Verhalten von Emacs funktionieren . - Was ist, wenn einer der Befehle in einer Äußerung "Rückgängig" oder "Wiederherstellen" ist? Ich denke, ich könnte die Rückruflogik ändern, um diese immer als eindeutige Äußerungen an Emacs zu senden, um die Dinge einfacher zu halten. Dann sollte es genauso gehandhabt werden, als ob ich die Tastatur benutzen würde.
- Ziel strecken: Eine Äußerung kann einen Befehl enthalten, der das aktuell aktive Fenster oder den Puffer wechselt. In diesem Fall ist es in Ordnung, in jedem Puffer einmal "rückgängig machen" zu müssen, ich brauche es nicht, um so schick zu sein. Aber alle Befehle in einem einzelnen Puffer sollten immer noch gruppiert sein. Wenn ich also "do-x do-y do-z Schaltpuffer do-a do-b do-c" sage, sollte x, y, z eins sein undo Datensatz im ursprünglichen Puffer und a, b, c sollten ein Datensatz im Umschaltpuffer sein.
Gibt es eine einfache Möglichkeit, dies zu tun? AFAICT, es ist nichts eingebaut, aber Emacs ist riesig und tief ...
Update: Am Ende habe ich die unten stehende jhc-Lösung mit ein wenig zusätzlichem Code verwendet. Bei der globalen before-change-hook
Prüfung überprüfe ich, ob der zu ändernde Puffer in einer globalen Liste von Puffern enthalten ist, die diese Äußerung modifiziert haben, wenn sie nicht in die Liste eingeht und undo-collapse-begin
aufgerufen wird. Am Ende der Äußerung durchlaufe ich dann alle Puffer in der Liste und rufe auf undo-collapse-end
. Code unten (md- vor Funktionsnamen für Namespace-Zwecke hinzugefügt):
(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
als Markierung in das einfügen - vielleicht einen Eintrag des Formulars(apply FUN-NAME . ARGS)
? Um eine Äußerung rückgängig zu machen, rufen Sie wiederholt auf,undo
bis Sie Ihren nächsten Marker gefunden haben. Aber ich vermute, dass es hier alle möglichen Komplikationen gibt. :)