Konuşma tanıma ile Emacs'ı kontrol etmenizi sağlayan bir Emacs modu üzerinde çalışıyorum. Karşılaştığım sorunlardan biri Emacs'ın geri alma yönteminin sesle kontrol ederken nasıl çalışacağını beklememesi.
Kullanıcı birkaç kelime söyledikten sonra durakladığında buna 'söyleme' denir. Bir ifade, Emacs'ın yürütmesi için birden fazla komuttan oluşabilir. Genellikle tanıyıcı, bir ifadedeki bir veya daha fazla komutu yanlış tanır. O noktada "geri al" diyebiliyorum ve Emacs'ın sadece söyleme içindeki son eylemi değil, söyleme tarafından yapılan tüm eylemleri geri almalarını istiyorum . Başka bir deyişle, bir ifadenin birden çok komuttan oluşması durumunda bile, Emacs'ın bir ifadeyi geri almayla ilgili olarak tek bir komut olarak ele almasını istiyorum. Ayrıca söylenmeden önce tam olarak nerede olduğuna geri dönmek istiyorum, normal Emacs'ın geri alma işleminin bunu yapmadığını fark ettim.
Her ifadenin başında ve sonunda geri çağrı almak için Emacs ayarladım, bu yüzden durumu tespit edebiliyorum, sadece Emacs'ın ne yapması gerektiğini bulmam gerekiyor. İdeal olarak (undo-start-collapsing)
ve sonra (undo-stop-collapsing)
böyle bir şey çağırırdım ve aralarında yapılan her şey sihirli bir şekilde tek bir kayıtta daraltılır.
Belgelerde biraz trol yaptım ve buldum undo-boundary
, ama istediğimin tam tersi - Bir ifadedeki tüm eylemleri tek bir geri alma kaydına daraltmam, onları ayırmam gerekiyor. undo-boundary
Eklemeler ayrı olarak kabul edildiğinden emin olmak için ifadeler arasında kullanabilirim (Emacs varsayılan olarak ardışık ekleme eylemlerini bir sınıra kadar bir eylem olarak kabul eder), ama hepsi bu.
Diğer komplikasyonlar:
- Konuşma tanıma arka plan programım, X11 tuş basımlarını simüle ederek Emacs'a bazı komutlar gönderir ve
emacsclient -e
bu yolla bazılarını gönderir(undo-collapse &rest ACTIONS)
; - Kullanıyorum
undo-tree
, bunun işleri daha karmaşık hale getirip getirmediğinden emin değilim. İdeal olarak bir çözümundo-tree
ve Emacs'ın normal geri alma davranışı ile çalışır. - Bir ifadedeki komutlardan biri "geri al" veya "yeniden yap" ise ne olur? Ben her zaman işleri daha basit tutmak için farklı ifadeler olarak bunları Emacs göndermek için geri arama mantığını değiştirebileceğimi düşünüyorum, o zaman tıpkı klavyeyi kullanıyormuşum gibi ele alınmalıdır.
- Esnek hedef: Bir ifade, o anda etkin olan pencereyi veya arabelleği değiştiren bir komut içerebilir. Bu durumda, her arabellekte bir kez ayrı olarak "geri al" demek iyi olur, bu kadar süslü olmasına gerek yok. Ancak tek bir arabellekteki tüm komutlar hala gruplandırılmalıdır, bu yüzden "do-x do-y do-z anahtar-buffer do-a do-b do-c" dersem, x, y, z bir geri alınmalıdır orijinal tampondaki kayıt ve a, b, c, tampona geçirilen bir kayıt olmalıdır.
Bunu yapmanın kolay bir yolu var mı? AFAICT yerleşik bir şey yok ama Emacs geniş ve derin ...
Güncelleme: Jhc'nin çözümünü biraz ekstra kodla bitirdim. Genelde, before-change-hook
değiştirilen arabellek genel bir arabellek listesinde olup olmadığını kontrol ederim, değilse bu ifadeyi değiştirdi, eğer listeye girer ve undo-collapse-begin
çağrılır. Sonra sözün sonunda, listedeki tüm arabellekleri tekrarlıyorum ve çağırıyorum undo-collapse-end
. Aşağıdaki kod (ad alanı için işlev adlarından önce md- eklendi):
(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
bir işaretleyici olarak ekleyebilirsiniz - belki de formun bir girişi(apply FUN-NAME . ARGS)
? Ardından bir ifadeyi geri almak için birundo
sonraki işaretçinizi bulana kadar tekrar tekrar ararsınız. Ama sanırım burada her türlü komplikasyon var. :)