record insertion changes so that they can be repeated

This commit is contained in:
Jean-Philippe Bernardy 2017-05-08 09:53:29 +02:00
parent 450d8901aa
commit 868dbe907b
2 changed files with 81 additions and 15 deletions

View file

@ -8,6 +8,7 @@
;;; Code:
(require 'cl-macs)
(require 'dash)
;; Maps
@ -60,6 +61,45 @@ those. See 'boon-special-map' for exceptinons.")
;; indeed: the special mode is good enough that it's not necessary to
;; switch to 'off' mode any longer.
(defvar boon/insert-command-history nil "History of changes in this insertion round.")
(defvar boon/insert-command nil "Command which started the insertion.")
(defvar boon/insert-origin 0 "Point at start of insert mode.")
(defun boon-interactive-insert (&rest args)
"Boon inserting functions must call this with ARGS being the actual arguments.
When repeated, the function will be called with a list of
changes, which should eventually be passed to
`boon-set-insert-like-state'."
(unless boon/insert-command
(setq boon/insert-command (cons this-command (-map (lambda (x) (list 'quote x)) args)))))
(defun boon/after-change-hook (begin end old-len)
"Remember the change defined by BEGIN END OLD-LEN in `boon/insert-command-history'."
(when (and boon-insert-state (not mc--executing-command-for-fake-cursor))
;; (message "bach: %s" boon/insert-command-history (list begin end old-len))
;; TODO concatenate consecutive deletes
(if (and boon/insert-command-history
(eq 0 (nth 1 (car boon/insert-command-history))) ;; no delete
(eq 0 old-len) ;; no delete
(eq begin (+ boon/insert-origin
(car (car boon/insert-command-history))
(length (nth 2 (car boon/insert-command-history))))))
;; two consecutive inserts: concat them.
(setq boon/insert-command-history (cons (list (car (car boon/insert-command-history))
0
(concat (nth 2 (car boon/insert-command-history)) (buffer-substring-no-properties begin end)))
(cdr boon/insert-command-history)))
(push (list (- begin boon/insert-origin) old-len (buffer-substring-no-properties begin end))
boon/insert-command-history))))
(defun boon/replay-changes (changes)
"Replay the CHANGES at the current point."
(let ((p0 (point)))
(setq boon/insert-command nil) ;; did not go to insert mode after all
(dolist (change changes)
(goto-char (+ p0 (nth 0 change)))
(delete-region (+ p0 (nth 0 change)) (+ p0 (nth 0 change) (nth 1 change)))
(insert (nth 2 change)))))
(defun boon-set-state (state)
"Set the boon state (as STATE) for this buffer."
@ -79,27 +119,27 @@ those. See 'boon-special-map' for exceptinons.")
(setq cursor-type 'bar))
(cond (boon-command-state
;; (do-auto-save)
(when (and boon/insert-command boon/insert-command-history)
(push `(,@boon/insert-command
(quote ,@(list (nreverse boon/insert-command-history))))
command-history))
(setq boon/insert-command nil)
(setq boon/insert-command-history nil)
(setq cursor-type 'box))
(boon-special-state (setq cursor-type 'box))
(boon-off-state)
(boon-insert-state
;; remember where the last edition was by pushing a mark
(push-mark))
(push-mark) ;; remember where the last edition was by pushing a mark
(setq boon/insert-command-history nil)
(setq boon/insert-origin (point)))
(t (message "Unknown state!")))
(force-mode-line-update))
(defun boon-set-insert-like-state ()
"Switch to special or insert state, depending on mode."
(interactive)
(if (boon-special-mode-p)
(boon-set-special-state)
(boon-set-state 'boon-insert-state)))
(defun boon-set-insert-state ()
"Switch to insert state."
(interactive)
(boon-set-state 'boon-insert-state))
(defun boon-set-command-state ()
"Switch to command state."
(interactive) (boon-set-state 'boon-command-state))
@ -143,6 +183,8 @@ those. See 'boon-special-map' for exceptinons.")
:keymap nil
(cond
(boon-local-mode
(unless (memq 'boon/after-change-hook after-change-functions)
(push 'boon/after-change-hook after-change-functions))
;; The initial state is usually setup by `boon-initialize' when
;; the major-mode in a buffer changes. This preliminary
;; initialization is only for the case when `boon-local-mode' is
@ -213,7 +255,6 @@ This is because no command mode is activated in the minibuffer."
boon-find-char-forward
boon-quote-character
boon-replace-by-character
boon-set-insert-like-state
boon-smarter-backward
boon-smarter-forward
boon-splice
@ -233,6 +274,7 @@ This is because no command mode is activated in the minibuffer."
boon-query-replace
boon-quit
boon-set-command-state
boon-set-insert-like-state
boon-substitute-region
boon-take-region
boon-toggle-character-case

View file

@ -15,10 +15,33 @@
(require 'subr-x)
(require 'dash)
;; TODO: rename
(defun boon-set-insert-like-state (&optional changes)
"Switch to special or insert state, depending on mode.
When CHANGES are non-nil, replay those instead."
(interactive)
(boon-interactive-insert)
(if (boon-special-mode-p)
(boon-set-special-state)
(boon-insert changes)))
;; TODO: rename
(defun boon-insert (&optional changes)
"Switch to insert state.
When CHANGES are non-nil, replay those instead."
(interactive)
(boon-interactive-insert)
(if changes ;; replay changes if we have them, otherwise switch to insert state normally
(progn
(mc/execute-command-for-all-fake-cursors (lambda () (interactive) (boon/replay-changes changes)))
(boon/replay-changes changes))
(boon-set-insert-state)))
(defun boon-repeat-command (count)
"Repeat the most recent command in the history, COUNT times."
(interactive "p")
(let ((cmd (car command-history)))
(dotimes (i count)
(dotimes (_ count)
(apply #'funcall-interactively
(car cmd)
(mapcar (lambda (e) (eval e t)) (cdr cmd))))))
@ -262,9 +285,10 @@ If there is more than one, use mc/create-fake-cursor-at-point."
(dolist (reg (boon-run-selector regs))
(kill-ring-save (boon-reg-begin reg) (boon-reg-end reg))))
(defun boon-substitute-region (regs)
"Kill the regions REGS, and switch to insertion mode."
(defun boon-substitute-region (regs &optional changes)
"Kill the regions REGS, and switch to insertion mode or replay CHANGES."
(interactive (list (boon-spec-select-top "replace")))
(boon-interactive-insert regs)
(let ((markers (mapcar 'boon-reg-to-markers (boon-run-selector regs))))
;; use markers so that deleting things does not mess the positions
(boon-take-region regs)
@ -272,7 +296,7 @@ If there is more than one, use mc/create-fake-cursor-at-point."
(boon-lay-multiple-cursors (lambda (reg)
(goto-char (boon-reg-point reg)))
markers)
(boon-set-insert-state)))
(boon-insert changes)))
(defun boon-replace-by-character (replacement)
"Replace the character at point by the REPLACEMENT character.