Geri alma geçmişi nasıl daraltılır?


17

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-boundaryEklemeler 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 -ebu 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üm undo-treeve 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-hookdeğ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)

Bunun için yerleşik bir mekanizmanın farkında değil. Kendi girişlerinizi buffer-undo-listbir işaretleyici olarak ekleyebilirsiniz - belki de formun bir girişi (apply FUN-NAME . ARGS)? Ardından bir ifadeyi geri almak için bir undosonraki işaretçinizi bulana kadar tekrar tekrar ararsınız. Ama sanırım burada her türlü komplikasyon var. :)
glucas

Sınırları kaldırmak daha iyi bir bahis gibi görünebilir.
JCH

Geri alma ağacı kullanıyorsam, buffer-undo-list manipüle etmek işe yarar mı? Ben ağaç geri alma kaynağında referans görüyorum bu yüzden evet tahmin ediyorum ama tüm mod anlam ifade etmek büyük bir çaba olacaktır.
Joseph Garvin

@JosephGarvin Emacs'ı da konuşma ile kontrol etmekle ilgileniyorum. Herhangi bir kaynağınız var mı?
PythonNut

@PythonNut: yes :) github.com/jgarvin/mandimus ambalaj eksik ... ve kod da kısmen joe-etc repo'mda: p Ama bütün gün kullanıyorum ve çalışıyor.
Joseph Garvin

Yanıtlar:


13

İlginçtir ki, bunu yapmak için yerleşik bir fonksiyon yok gibi görünüyor.

Aşağıdaki kod buffer-undo-list, daraltılabilir bir bloğun başına benzersiz bir işaretleyici ekleyerek nilve bir bloğun sonunda tüm sınırları ( öğeleri) kaldırarak ve sonra işaretçiyi kaldırarak çalışır. Bir şeyler ters giderse, işaretleyici (apply identity nil)geri alma listesinde kalırsa hiçbir şey yapmamasını sağlamak için formdadır .

İdeal olarak, with-undo-collapsetemel fonksiyonları değil, makroyu kullanmalısınız . Eğer sarma yapamaz dile beri emin olan düşük seviyeli fonksiyonları belirteçleri geçmek olun eqsadece, equal.

Eğer çağrılan kod arabellekleri değiştirirse, undo-collapse-endbunun aynı arabellekte çağrıldığından emin olmalısınız undo-collapse-begin. Bu durumda, yalnızca ilk arabellekteki geri alma girdileri daraltılır.

(defun 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."
  (push marker buffer-undo-list))

(defun undo-collapse-end (marker)
  "Collapse undo history until a matching marker."
  (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 "undo-collapse-end with no matching marker"))
           ((null (cadr l))
            (setf (cdr l) (cddr l)))
           (t (setq l (cdr l)))))
       ;; remove the marker
       (setf (cdr l) (cddr l))))))

 (defmacro with-undo-collapse (&rest body)
  "Execute body, then collapse any resulting undo boundaries."
  (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
              (undo-collapse-begin ',marker)
              ,@body)
         (with-current-buffer ,buffer-var
           (undo-collapse-end ',marker))))))

İşte bir kullanım örneği:

(defun test-no-collapse ()
  (interactive)
  (insert "toto")
  (undo-boundary)
  (insert "titi"))

(defun test-collapse ()
  (interactive)
  (with-undo-collapse
    (insert "toto")
    (undo-boundary)
    (insert "titi")))

İşaretçinizin neden yeni bir liste olduğunu anlıyorum, ancak bu belirli öğelerin bir nedeni var mı?
Malabarba

@Malabarba, (apply identity nil)eğer bir giriş eğer onu çağırırsanız hiçbir şey yapmaz primitive-undo- eğer bir sebepten dolayı listede bırakılırsa hiçbir şeyi kırmaz.
JCH

Sorumu eklediğim kodu içerecek şekilde güncellendi. Teşekkürler!
Joseph Garvin

Yapmak için herhangi bir neden (eq (cadr l) nil)yerine(null (cadr l)) mı?
ideasman42

@ ideasman42 önerinize göre değiştirildi.
jch

3

Geri alma makinesinde yapılan bazı değişiklikler "son zamanlarda" viper-modebu tür bir çöküşü yapmak için bazı hackleri kırdı (meraklı için, aşağıdaki durumda kullanılır: ESCbir ekleme / değiştirme / baskıyı bitirmek için bastığınızda , Viper bütününü daraltmak istiyor tek bir geri alma adımına değiştirin).

Temiz bir şekilde düzeltmek için, yeni bir işlev undo-amalgamate-change-group(daha çok veya daha azına karşılık gelir) tanıttık undo-stop-collapsingve mevcut olanı prepare-change-groupbaşlangıcını işaretlemek için yeniden kullanır (yani az çok kendinize karşılık gelir undo-start-collapsing).

Referans olarak, ilgili yeni Viper kodu şöyledir:

(viper-deflocalvar viper--undo-change-group-handle nil)
(put 'viper--undo-change-group-handle 'permanent-local t)

(defun viper-adjust-undo ()
  (when viper--undo-change-group-handle
    (undo-amalgamate-change-group
     (prog1 viper--undo-change-group-handle
       (setq viper--undo-change-group-handle nil)))))

(defun viper-set-complex-command-for-undo ()
  (and (listp buffer-undo-list)
       (not viper--undo-change-group-handle)
       (setq viper--undo-change-group-handle
             (prepare-change-group))))

Bu yeni işlev Emacs-26'da görünecektir, bu nedenle bu süre zarfında kullanmak istiyorsanız tanımını kopyalayabilirsiniz (gerektirir cl-lib):

(defun undo-amalgamate-change-group (handle)
  "Amalgamate changes in change-group since HANDLE.
Remove all undo boundaries between the state of HANDLE and now.
HANDLE is as returned by `prepare-change-group'."
  (dolist (elt handle)
    (with-current-buffer (car elt)
      (setq elt (cdr elt))
      (when (consp buffer-undo-list)
        (let ((old-car (car-safe elt))
              (old-cdr (cdr-safe elt)))
          (unwind-protect
              (progn
                ;; Temporarily truncate the undo log at ELT.
                (when (consp elt)
                  (setcar elt t) (setcdr elt nil))
                (when
                    (or (null elt)        ;The undo-log was empty.
                        ;; `elt' is still in the log: normal case.
                        (eq elt (last buffer-undo-list))
                        ;; `elt' is not in the log any more, but that's because
                        ;; the log is "all new", so we should remove all
                        ;; boundaries from it.
                        (not (eq (last buffer-undo-list) (last old-cdr))))
                  (cl-callf (lambda (x) (delq nil x))
                      (if (car buffer-undo-list)
                          buffer-undo-list
                        ;; Preserve the undo-boundaries at either ends of the
                        ;; change-groups.
                        (cdr buffer-undo-list)))))
            ;; Reset the modified cons cell ELT to its original content.
            (when (consp elt)
              (setcar elt old-car)
              (setcdr elt old-cdr))))))))

Araştırdım undo-amalgamate-change-groupve bunu with-undo-collapsebu sayfada tanımlanan makro gibi kullanmanın uygun bir yolu yok gibi görünüyor , çünkü atomic-change-groupgrubun çağrılmasına izin verecek şekilde çalışmıyor undo-amalgamate-change-group.
ideasman42

Tabii ki, ile birlikte kullanmıyorsunuz atomic-change-group: ile birlikte kullanıyorsunuz prepare-change-group, bu undo-amalgamate-change-groupda işiniz bittiğinde geçmeniz gereken tutamacı döndürüyor .
Stefan

Bununla ilgilenen bir makro yararlı olmaz mı? (with-undo-amalgamate ...)bu da değişiklik grubu öğelerini işler. Aksi takdirde bu, birkaç işlemi daraltmak için biraz güçlük çeker.
ideasman42

Şimdiye kadar sadece engerek IIRC tarafından kullanıldı ve Viper böyle bir makroyu kullanamazdı çünkü iki çağrı ayrı komutlarda gerçekleşiyor, bu yüzden ağlamaya gerek yok. Ancak elbette böyle bir makro yazmak önemsiz olacaktır.
Stefan

1
Bu makro yazılabilir ve emac'lara dahil edilebilir mi? Deneyimli bir geliştirici için önemsiz olsa da, geri alma geçmişini çökertmek isteyen ve nereden başlayacağını bilmeyen biri için - çevrimiçi olarak uğraşmak ve bu iş parçacığını tökezlemek biraz zaman ... sonra hangi cevabın en iyi olduğunu bulmak zorunda - anlatabilecek kadar deneyimli olmadıklarında. Buraya bir cevap ekledim: emacs.stackexchange.com/a/54412/2418
ideasman42

2

İşte with-undo-collapseEmacs-26 değişiklik grupları özelliğini kullanan bir makro.

Bu, atomic-change-grouptek satırlık bir değişiklikle ekleniyor undo-amalgamate-change-group.

Avantajları vardır:

  • Geri alma verilerini doğrudan değiştirmeye gerek yoktur.
  • Geri alınan verilerin kesilmemesini sağlar.
(defmacro with-undo-collapse (&rest body)
  "Like `progn' but perform BODY with undo collapsed."
  (declare (indent 0) (debug t))
  (let ((handle (make-symbol "--change-group-handle--"))
        (success (make-symbol "--change-group-success--")))
    `(let ((,handle (prepare-change-group))
            ;; Don't truncate any undo data in the middle of this.
            (undo-outer-limit nil)
            (undo-limit most-positive-fixnum)
            (undo-strong-limit most-positive-fixnum)
            (,success nil))
       (unwind-protect
         (progn
           (activate-change-group ,handle)
           (prog1 ,(macroexp-progn body)
             (setq ,success t)))
         (if ,success
           (progn
             (accept-change-group ,handle)
             (undo-amalgamate-change-group ,handle))
           (cancel-change-group ,handle))))))
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.