ฉันกำลังทำงานในโหมด Emacs ที่ให้คุณควบคุม Emacs ด้วยการรู้จำเสียง หนึ่งในปัญหาที่ฉันพบคือวิธีที่ Emacs จัดการเลิกทำไม่ตรงกับที่คุณคาดหวังว่ามันจะทำงานเมื่อควบคุมด้วยเสียง
เมื่อผู้ใช้พูดหลาย ๆ คำและหยุดชั่วคราวนั่นเรียกว่า 'คำพูด' คำพูดอาจประกอบด้วยหลายคำสั่งเพื่อให้ Emacs ดำเนินการ บ่อยครั้งที่ตัวจำแนกลายมือรู้จักหนึ่งคำสั่งหรือมากกว่านั้นภายในคำพูดที่ไม่ถูกต้อง ณ จุดนั้นฉันต้องการที่จะพูดว่า "เลิกทำ" และให้ Emacs เลิกทำการกระทำทั้งหมดด้วยคำพูดไม่ใช่แค่การกระทำสุดท้ายภายในคำพูด กล่าวอีกนัยหนึ่งฉันต้องการให้ Emacs ใช้คำพูดเดียวกับคำสั่งที่เกี่ยวข้องกับการเลิกทำแม้ว่าคำพูดนั้นจะประกอบด้วยหลายคำสั่งก็ตาม ฉันต้องการที่จะกลับไปที่เดิมก่อนคำพูดฉันสังเกตเห็นว่า Emacs การเลิกทำตามปกติไม่ได้ทำเช่นนี้
ฉันมีการตั้งค่า Emacs เพื่อรับการเรียกกลับที่จุดเริ่มต้นและจุดสิ้นสุดของคำพูดแต่ละคำเพื่อให้ฉันสามารถตรวจสอบสถานการณ์ได้ฉันต้องคิดว่าจะให้ Emacs ทำอะไร เป็นการดีที่ฉันจะเรียกสิ่งที่ชอบ(undo-start-collapsing)
แล้ว(undo-stop-collapsing)
และสิ่งที่ทำในระหว่างนั้นจะถูกยุบลงอย่างน่าอัศจรรย์เป็นหนึ่งระเบียน
ฉันทำการสืบค้นผ่านเอกสารและพบundo-boundary
แต่มันตรงกันข้ามกับสิ่งที่ฉันต้องการ - ฉันต้องยุบการกระทำทั้งหมดภายในคำพูดเป็นหนึ่งในการยกเลิกการบันทึกไม่ใช่การแยกพวกเขาออก ฉันสามารถใช้undo-boundary
ระหว่างคำพูดเพื่อให้แน่ใจว่าการแทรกได้รับการพิจารณาแยกต่างหาก (โดยค่าเริ่มต้นจะพิจารณาการแทรกที่ต่อเนื่องกันเป็นหนึ่งการกระทำจนถึงขีด จำกัด ) แต่นั่นก็คือ
ภาวะแทรกซ้อนอื่น ๆ :
- daemon การจดจำเสียงของฉันส่งคำสั่งไปยัง Emacs โดยการจำลองปุ่มกด X11 และส่งผ่านบางอย่าง
emacsclient -e
ดังนั้นหากมีการพูดว่า(undo-collapse &rest ACTIONS)
ไม่มีที่กลางที่ฉันสามารถห่อได้ - ฉันใช้
undo-tree
ไม่แน่ใจว่าสิ่งนี้ทำให้สิ่งที่ซับซ้อนมากขึ้นหรือไม่ วิธีแก้ปัญหาในอุดมคติจะทำงานร่วมกับundo-tree
และพฤติกรรมการเลิกทำตามปกติของ Emacs - เกิดอะไรขึ้นถ้าคำสั่งอย่างใดอย่างหนึ่งภายในคำพูดคือ "เลิกทำ" หรือ "ทำซ้ำ" ฉันคิดว่าฉันสามารถเปลี่ยนตรรกะการโทรกลับเพื่อส่งสิ่งเหล่านี้ไปยัง Emacs เป็นคำพูดที่แตกต่างกันเพื่อให้สิ่งต่าง ๆ ง่ายขึ้นจากนั้นควรจัดการเช่นเดียวกับถ้าฉันใช้แป้นพิมพ์
- เป้าหมายการยืด: คำพูดอาจมีคำสั่งที่สลับหน้าต่างหรือบัฟเฟอร์ที่ใช้งานอยู่ในปัจจุบัน ในกรณีนี้มันก็โอเคที่จะต้องพูดว่า "เลิกทำ" ครั้งเดียวในแต่ละบัฟเฟอร์แยกกันฉันไม่ต้องการให้มันเป็นแบบนั้น แต่คำสั่งทั้งหมดในบัฟเฟอร์เดียวควรยังคงถูกจัดกลุ่มดังนั้นถ้าฉันพูดว่า "do-x do-y do-z switch-buffer do-a do-b do-c" ดังนั้น x, y, z ควรเป็นหนึ่งในการเลิกทำ บันทึกในบัฟเฟอร์เดิมและ a, b, c ควรเป็นหนึ่งระเบียนในสวิตช์เป็นบัฟเฟอร์
มีวิธีง่าย ๆ ในการทำเช่นนี้? AFAICT ไม่มีอะไรในตัว แต่ Emacs นั้นกว้างใหญ่และลึก ...
อัปเดต: ฉันลงเอยด้วยการใช้โซลูชันของ jhc ด้านล่างด้วยรหัสพิเศษเล็กน้อย ในส่วนกลางbefore-change-hook
ฉันตรวจสอบว่าบัฟเฟอร์ที่ถูกเปลี่ยนนั้นอยู่ในรายการส่วนกลางของบัฟเฟอร์ที่แก้ไขคำพูดนี้หรือไม่ถ้าไม่ใช่มันจะเข้าไปในรายการและundo-collapse-begin
ถูกเรียก undo-collapse-end
จากนั้นในตอนท้ายของคำพูดที่ผมย้ำบัฟเฟอร์ทั้งหมดในรายการและโทร โค้ดด้านล่าง (md- เพิ่มก่อนชื่อฟังก์ชั่นเพื่อวัตถุประสงค์ในการกำหนดเนมสเปซ):
(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
เครื่องหมายเป็น - บางทีรายการของแบบฟอร์ม(apply FUN-NAME . ARGS)
? จากนั้นเพื่อยกเลิกคำพูดที่คุณโทรซ้ำ ๆundo
จนกระทั่งพบเครื่องหมายถัดไปของคุณ แต่ฉันสงสัยว่ามีภาวะแทรกซ้อนทุกประเภทที่นี่ :)