Advertisement
Guest User

Untitled

a guest
Mar 30th, 2021
329
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lisp 8.13 KB | None | 0 0
  1. (require 'cl)
  2. (require 'diff)
  3.  
  4. ;; changes with less than this many characters are ignored to avoid
  5. ;; having too many little undos stored
  6. (setq complex-undo-minimum-change-length 20)
  7.  
  8. ;; number of complex undos stored for a file
  9. (setq complex-undo-max-stored-diffs 50)
  10.  
  11. ;; do not store undos for files bigger than this to avoid
  12. ;; unnecessarily processing large data files, for example
  13. (setq complex-undo-max-file-size 100000)
  14.  
  15. ;; idle delay in seconds, before putting new undos into to the history
  16. ;; list, so that undo processing does not interfere with typing. you
  17. ;; do not need older undos instantly anyway, for instant undo of the
  18. ;; last operation you use regular emacs undo as usual.
  19. (setq complex-undo-process-delay 2)
  20.  
  21. ;; do not show the names of these trivial commands in undo history
  22. ;; descriptors
  23. (setq complex-undo-ignore-commands '(self-insert-command
  24.                                      delete-char
  25.                                      backward-char
  26.                                      delete-backward-char
  27.                                      backward-delete-char
  28.                                      backward-delete-char-untabify
  29.                                      kill-word
  30.                                      backward-kill-word
  31.                                      undo
  32.                                      newline
  33.                                      dabbrev-expand))
  34.  
  35.  
  36. (make-variable-buffer-local 'complex-undo-buffer-tick)
  37. (make-variable-buffer-local 'complex-undo-buffer-undo-state)
  38. (make-variable-buffer-local 'complex-undo-unprocessed)
  39. (make-variable-buffer-local 'complex-undo-items)
  40.  
  41.  
  42. (defun complex-undo-store-current-state ()
  43.   (setq complex-undo-buffer-tick (buffer-chars-modified-tick))
  44.   (setq complex-undo-buffer-undo-state buffer-undo-list)
  45.   (setq complex-undo-unprocessed nil))
  46.  
  47.  
  48.  
  49. (defun complex-undo-post-command ()
  50.   (unless (eq complex-undo-buffer-tick
  51.               (buffer-chars-modified-tick))
  52.     (push real-this-command complex-undo-unprocessed)
  53.     (setq complex-undo-buffer-tick (buffer-chars-modified-tick))))
  54.  
  55.  
  56. (defun complex-undo-process-changes ()
  57.   (when complex-undo-unprocessed    
  58.     (if (catch 'exitloop                 ;; only consider non-trivial changes
  59.           (let ((undos buffer-undo-list)
  60.                 (count 0))
  61.             (while (and undos
  62.                         (not (eq undos complex-undo-buffer-undo-state)))
  63.               (let ((undo (pop undos)))
  64.                 (when (listp undo)
  65.                   (if (stringp (car undo))   ;; deleted text
  66.                       (incf count (length (car undo)))
  67.  
  68.                     (if (numberp (car undo)) ;; inserted text
  69.                         (incf count (- (cdr undo) (car undo)))))
  70.                  
  71.                   (if (>= count complex-undo-minimum-change-length)
  72.                       (throw 'exitloop t))))))
  73.           nil)
  74.  
  75.         (let ((oldbuf (generate-new-buffer "undo"))
  76.               (newbuf (current-buffer))
  77.               (filename (buffer-file-name))
  78.               (command (string-join
  79.                         (delete-dups
  80.                          (mapcar
  81.                           (lambda (command)
  82.                             (if (symbolp command)
  83.                                 (if (member command complex-undo-ignore-commands)
  84.                                     "small commands"
  85.                                   (symbol-name command))))
  86.                           (reverse complex-undo-unprocessed)))
  87.                         ", "
  88.                         )))
  89.  
  90.           (let ((text (buffer-string))
  91.                 (undo buffer-undo-list)
  92.                 (oldundo complex-undo-buffer-undo-state))
  93.             (with-current-buffer oldbuf
  94.               (insert text)
  95.               (let (filtered-undo)
  96.                 (while (and undo
  97.                             (not (eq undo oldundo)))
  98.                   (unless (and (listp (car undo))
  99.                                ;; skip marker movements which refer to the old
  100.                                ;; buffer, so they are not useful here
  101.                                (markerp (caar undo)))
  102.                     (push (car undo) filtered-undo))
  103.                   (setq undo (cdr undo)))
  104.                 (primitive-undo (length filtered-undo) (reverse filtered-undo)))))
  105.  
  106.           (let ((diff (with-temp-buffer
  107.                         (diff-no-select oldbuf newbuf
  108.                                         nil t (current-buffer))
  109.                         (buffer-string))))
  110.             (push (list 'diff (replace-regexp-in-string
  111.                                (format "#<buffer %s>"
  112.                                        (buffer-name newbuf))
  113.                                filename
  114.                                diff)
  115.                         'command command
  116.                         'time (format-time-string "%Y-%m-%d %H:%M"))
  117.                   complex-undo-items)
  118.  
  119.             (kill-buffer oldbuf)
  120.  
  121.             (if (> (length complex-undo-items)
  122.                    complex-undo-max-stored-diffs)
  123.                 (setq complex-undo-items
  124.                       (butlast complex-undo-items))))))
  125.  
  126.     (complex-undo-store-current-state)))
  127.  
  128.  
  129. (defun complex-undo-show-diffs ()
  130.   (interactive)
  131.   (unless complex-undo-items
  132.     (if (buffer-file-name)
  133.         (error "no stored undo diffs for this file")
  134.  
  135.       (error "Undo for buffers without an associated file is currently not supported. The file does not have to be saved, but the buffer has to have an associated file. If the buffer is opened with find-file then it has a file associated.")))
  136.  
  137.   (setq complex-undo-previous-window-cfg
  138.         (current-window-configuration))
  139.  
  140.   (let ((items complex-undo-items)
  141.         (file (buffer-file-name)))
  142.     (pop-to-buffer "*undo diffs")
  143.     (erase-buffer)
  144.     (insert (propertize (concat " Undo items for file " file)
  145.                         'face 'tool-bar)
  146.          "\n\n")
  147.     (save-excursion
  148.       (dolist (item items)
  149.         (insert (propertize (plist-get item 'time)
  150.                             'face 'line-number)
  151.                 "   "
  152.                 (propertize (plist-get item 'command)
  153.                             'face 'font-lock-function-name-face))
  154.         (put-text-property (line-beginning-position)
  155.                            (1+ (line-beginning-position))
  156.                            'complex-undo-data
  157.                            item)
  158.         (insert "\n")))
  159.  
  160.     (local-set-key "q" (lambda ()
  161.                          (interactive)
  162.                          (set-window-configuration complex-undo-previous-window-cfg)))
  163.     (local-set-key (kbd "<return>")
  164.                    (lambda ()
  165.                      (interactive)
  166.                      (pop-to-buffer "*undo diff*")
  167.                      ))
  168.     (add-hook 'post-command-hook  'complex-undo-show-diff nil t)
  169.     (setq complex-undo-diffs-current-line nil)))
  170.  
  171.  
  172. (defun complex-undo-show-diff ()
  173.   (interactive)
  174.   (if (and (not (eq complex-undo-diffs-current-line (line-number-at-pos)))
  175.            (sit-for 0.3))
  176.       (let ((data (get-text-property (line-beginning-position) 'complex-undo-data)))
  177.         (if data
  178.             (save-selected-window
  179.               (pop-to-buffer "*undo diff*")
  180.               (let ((inhibit-read-only t))
  181.                 (erase-buffer)
  182.                 (unless (eq major-mode 'diff-mode)
  183.                   (diff-mode))
  184.                 (save-excursion
  185.                   (insert (plist-get data 'diff))))
  186.               (read-only-mode 1)))
  187.  
  188.         (setq complex-undo-diffs-current-line (line-number-at-pos)))))
  189.  
  190.  
  191. (define-minor-mode complex-undo-mode
  192.   "Complex undo."
  193.   :lighter " CU"
  194.  
  195.   (if complex-undo-mode
  196.       (progn
  197.         (if (> (buffer-size) complex-undo-max-file-size)
  198.             (progn
  199.               (setq complex-undo-mode nil)
  200.               (message "File size is over the limit for complex undo."))
  201.          
  202.           (add-hook 'post-command-hook  'complex-undo-post-command nil t)
  203.           (complex-undo-store-current-state)))
  204.  
  205.     (remove-hook 'post-command-hook  'complex-undo-post-command t)))
  206.  
  207.  
  208. (run-with-idle-timer complex-undo-process-delay t 'complex-undo-process-changes)
  209.  
  210.  
  211. (provide 'complex-undo)
  212.  
  213.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement