Saya sedang mengerjakan mode Emacs yang memungkinkan Anda mengontrol Emacs dengan pengenalan suara. Salah satu masalah yang saya temui adalah cara Emacs menangani undo tidak cocok dengan yang Anda harapkan berfungsi saat mengendalikan dengan suara.
Ketika pengguna mengucapkan beberapa kata dan kemudian berhenti, itu disebut 'ucapan.' Ucapan dapat terdiri dari beberapa perintah untuk dijalankan oleh Emacs. Seringkali pengenal mengenali satu atau lebih perintah dalam ucapan yang salah. Pada titik itu saya ingin bisa mengatakan "undo" dan minta Emacs membatalkan semua tindakan yang dilakukan oleh ujaran, bukan hanya tindakan terakhir dalam ujaran. Dengan kata lain, saya ingin Emacs memperlakukan ucapan sebagai satu perintah sejauh yang berkaitan dengan undo, bahkan ketika ucapan terdiri dari banyak perintah. Saya juga ingin poin untuk kembali ke tempat persisnya sebelum ujaran, saya perhatikan Emacs normal membatalkan tidak melakukan ini.
Saya memiliki pengaturan Emacs untuk mendapatkan panggilan balik di awal dan akhir setiap ucapan, sehingga saya dapat mendeteksi situasinya, saya hanya perlu mencari tahu apa yang harus dilakukan Emacs. Idealnya saya akan memanggil sesuatu seperti (undo-start-collapsing)
dan kemudian (undo-stop-collapsing)
dan apa pun yang dilakukan antara akan secara ajaib runtuh menjadi satu rekaman.
Saya melakukan beberapa penjelajahan melalui dokumentasi dan menemukan undo-boundary
, tetapi itu kebalikan dari apa yang saya inginkan - saya perlu menciutkan semua tindakan dalam ucapan menjadi satu rekaman undo, tidak membaginya. Saya dapat menggunakan undo-boundary
antara ucapan untuk memastikan bahwa penyisipan dianggap terpisah (Emacs secara default menganggap tindakan penyisipan berurutan menjadi satu tindakan hingga batas tertentu), tetapi hanya itu.
Komplikasi lain:
- Daemon pengenalan ucapan saya mengirim beberapa perintah ke Emacs dengan mensimulasikan penekanan tombol X11 dan mengirimkan beberapa melalui
emacsclient -e
begitu, jika ada yang mengatakan(undo-collapse &rest ACTIONS)
tidak ada tempat pusat yang bisa saya bungkus. - Saya menggunakan
undo-tree
, tidak yakin apakah ini membuat segalanya lebih rumit. Idealnya solusi akan bekerja denganundo-tree
dan perilaku undo normal Emacs. - Bagaimana jika salah satu perintah dalam ucapan adalah "undo" atau "redo"? Saya berpikir saya bisa mengubah logika panggilan balik untuk selalu mengirim ini ke Emacs sebagai ucapan berbeda untuk menjaga hal-hal yang lebih sederhana, maka itu harus ditangani sama seperti jika saya menggunakan keyboard.
- Regangkan tujuan: Ucapan dapat berisi perintah yang mengganti jendela atau buffer yang aktif. Dalam hal ini tidak apa-apa untuk mengatakan "undo" satu kali secara terpisah di setiap buffer, saya tidak perlu seperti itu. Tetapi semua perintah dalam buffer tunggal masih harus dikelompokkan, jadi jika saya mengatakan "do-x do-y do-z switch-buffer do-a do-b do-c" maka x, y, z harus menjadi satu catatan dalam buffer asli dan a, b, c harus menjadi satu catatan di switched ke buffer.
Apakah ada cara mudah untuk melakukan ini? AFAICT tidak ada built-in tapi Emacs luas dan dalam ...
Pembaruan: Saya akhirnya menggunakan solusi jhc di bawah ini dengan sedikit kode tambahan. Dalam global before-change-hook
saya memeriksa apakah buffer yang diubah ada dalam daftar global buffer yang dimodifikasi ucapan ini, jika tidak masuk ke dalam daftar dan undo-collapse-begin
dipanggil. Kemudian pada akhir ucapan saya mengulangi semua buffer dalam daftar dan panggilan undo-collapse-end
. Kode di bawah ini (md- ditambahkan sebelum nama fungsi untuk keperluan penempatan nama):
(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)
sumber
buffer-undo-list
sebagai penanda - mungkin entri formulir(apply FUN-NAME . ARGS)
? Kemudian untuk membatalkan ucapan Anda berulang kali meneleponundo
sampai menemukan penanda Anda berikutnya. Tapi saya curiga ada banyak jenis komplikasi di sini. :)Jawaban:
Yang cukup menarik, tampaknya tidak ada fungsi bawaan untuk melakukan itu.
Kode berikut berfungsi dengan menyisipkan penanda unik pada
buffer-undo-list
di awal blok yang dapat dilipat, dan menghapus semua batas (nil
elemen) di ujung blok, kemudian menghapus penanda. Dalam hal terjadi kesalahan, penanda berbentuk(apply identity nil)
untuk memastikan bahwa ia tidak melakukan apa-apa jika tetap pada daftar undo.Idealnya, Anda harus menggunakan
with-undo-collapse
makro, bukan fungsi yang mendasarinya. Karena Anda menyebutkan bahwa Anda tidak dapat melakukan pembungkus, pastikan bahwa Anda beralih ke penanda fungsi tingkat rendaheq
, bukan hanyaequal
.Jika kode yang dipanggil mengganti buffer, Anda harus memastikan bahwa
undo-collapse-end
dipanggil dalam buffer yang sama denganundo-collapse-begin
. Dalam hal ini, hanya entri yang dibatalkan di buffer awal yang akan diciutkan.Berikut ini contoh penggunaan:
sumber
(apply identity nil)
tidak akan melakukan apa-apa jika Anda memanggilnyaprimitive-undo
- itu tidak akan merusak apa pun jika karena alasan tertentu dibiarkan dalam daftar.(eq (cadr l) nil)
bukan(null (cadr l))
?Beberapa perubahan pada mesin undo "baru-baru ini" memecahkan beberapa hack
viper-mode
yang digunakan untuk melakukan collapsing semacam ini (untuk yang penasaran, ini digunakan dalam kasus berikut: ketika Anda menekan ESCuntuk menyelesaikan penyisipan / penggantian / edisi, Viper ingin menutup keseluruhan ubah menjadi satu langkah undo).Untuk memperbaikinya dengan bersih, kami memperkenalkan fungsi baru
undo-amalgamate-change-group
(yang sesuai dengan Anda atau kurangundo-stop-collapsing
) dan menggunakan yang sudah adaprepare-change-group
untuk menandai awal (yaitu sesuai lebih atau kurang untuk Andaundo-start-collapsing
).Untuk referensi, inilah kode Viper baru yang sesuai:
Fungsi baru ini akan muncul di Emacs-26, jadi jika Anda ingin menggunakannya dalam waktu yang bersamaan, Anda dapat menyalin definisi (memerlukan
cl-lib
):sumber
undo-amalgamate-change-group
, dan sepertinya tidak ada cara yang nyaman untuk menggunakan ini sepertiwith-undo-collapse
makro yang didefinisikan pada halaman ini, karenaatomic-change-group
tidak berfungsi dengan cara yang memungkinkan memanggil grup denganundo-amalgamate-change-group
.atomic-change-group
: Anda menggunakannya denganprepare-change-group
, yang mengembalikan gagang yang Anda perlukan untuk dilewatiundo-amalgamate-change-group
ketika Anda selesai.(with-undo-amalgamate ...)
yang menangani hal-hal perubahan grup. Kalau tidak, ini sedikit merepotkan untuk runtuh beberapa operasi.Ini adalah
with-undo-collapse
makro yang menggunakan fitur grup perubahan Emacs-26.Ini
atomic-change-group
dengan satu perubahan baris, menambahkanundo-amalgamate-change-group
.Ini memiliki kelebihan yaitu:
sumber