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)
quelle
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. :)Antworten:
Interessanterweise scheint es dafür keine eingebaute Funktion zu geben.
Im folgenden Code wird eine eindeutige Markierung am
buffer-undo-list
Anfang eines reduzierbaren Blocks eingefügt und alle Begrenzungen (nil
Elemente) am Ende eines Blocks entfernt. Anschließend wird die Markierung entfernt. Falls etwas schief geht, hat der Marker die Form,(apply identity nil)
um sicherzustellen, dass er nichts tut, wenn er auf der Rückgängig-Liste bleibt.Im Idealfall sollten Sie das
with-undo-collapse
Makro verwenden, nicht die zugrunde liegenden Funktionen. Da Sie erwähnt haben, dass Sie den Zeilenumbruch nicht ausführen können, stellen Sie sicher, dass Sie zu den Funktionsmarkierungen auf niedriger Ebene übergehen, bei denen eseq
sich nicht nur um Markierungen handeltequal
.Wenn der aufgerufene Code Puffer wechselt, müssen Sie sicherstellen, dass dieser
undo-collapse-end
im gleichen Puffer wie aufgerufen wirdundo-collapse-begin
. In diesem Fall werden nur die Rückgängig-Einträge im Anfangspuffer reduziert.Hier ist ein Anwendungsbeispiel:
quelle
(apply identity nil)
nichts bewirkt, wenn Sie ihn aufrufen.primitive-undo
Er bricht nichts, wenn er aus irgendeinem Grund in der Liste verbleibt .(eq (cadr l) nil)
statt zu tun(null (cadr l))
?Einige Änderungen an der Undo-Maschinerie haben "vor kurzem" dazu geführt, dass ein Hack
viper-mode
diese Art von Zusammenbruch durchführte (für die Neugierigen wird dies in folgendem Fall verwendet: Wenn Sie drücken ESC, um eine Einfügung / Ersetzung / Ausgabe zu beenden, möchte Viper das Ganze zusammenbrechen in einen einzigen Undo-Schritt verwandeln).Um es sauber zu machen, haben wir eine neue Funktion eingeführt
undo-amalgamate-change-group
(die mehr oder weniger Ihrer entsprichtundo-stop-collapsing
) und die vorhandene wiederverwendetprepare-change-group
, um den Anfang zu markieren (dh sie entspricht mehr oder weniger Ihrerundo-start-collapsing
).Als Referenz ist hier der entsprechende neue Viper-Code:
Diese neue Funktion wird in Emacs-26 angezeigt. Wenn Sie sie in der Zwischenzeit verwenden möchten, können Sie ihre Definition kopieren (erfordert
cl-lib
):quelle
undo-amalgamate-change-group
, und es scheint keine bequeme Möglichkeit zu geben, dies wie daswith-undo-collapse
auf dieser Seite definierte Makro zu verwenden , daatomic-change-group
es nicht so funktioniert, dass die Gruppe mit aufgerufen werden kannundo-amalgamate-change-group
.atomic-change-group
: Sie verwenden es mitprepare-change-group
, wodurch das Handle zurückgegeben wird, an das Sie dann übergeben müssen,undo-amalgamate-change-group
wenn Sie fertig sind.(with-undo-amalgamate ...)
welches die Änderungsgruppen-Sachen handhabt. Ansonsten ist es ein bisschen mühsam, ein paar Operationen zusammenzubrechen.Dies ist ein
with-undo-collapse
Makro, das die Emacs-26-Funktion zum Ändern von Gruppen verwendet.Dies wird
atomic-change-group
mit einer Zeile geändert und hinzugefügtundo-amalgamate-change-group
.Es hat die Vorteile, dass:
quelle