Como hacer undo-tree linear - undo-tree-undo / redo

10

Al ejecutar el comando undo-tree-undo/redosucesivamente, las cosas se deshacen / rehacen según la rama activa actual. Aunque el usuario recibe un mensaje de punto de bifurcación en el camino, las bifurcaciones anteriores se ignoran a menos que un usuario seleccione manualmente una bifurcación diferente.

Entiendo que puedo abrir el visualizador y seleccionar una rama diferente, sin embargo, sería muy útil mantener presionada la tecla Deshacer / Rehacer y ver que todo suceda en el orden exactamente opuesto. Idealmente, esto debería funcionar independientemente de si el visualizerbúfer está abierto, es decir, calcular mediante programación el orden de principio a fin y de principio a fin.

P : Esta es esencialmente una solicitud de función que se extiende undo-treepara permitir deshacer / rehacer sucesivamente lineal, independientemente de si el búfer del visualizador está abierto. [Nuevas funciones y métodos abreviados de teclado alternativos para esta nueva característica son ciertamente aceptables.]

lista de leyes
fuente
El código de undo-treeparece que es parte del concepto que puede cambiar libremente entre buffer-undo-treey buffer-undo-listalternando undo-tree-mode. Según entiendo su pregunta, el manejo original de buffer-undo-listhace lo que quiere. Por lo tanto, sugiere que se apague temporalmente undo-tree-modepara su propósito. Lamentablemente, el cambio entre buffer-undo-treey buffer-undo-listparece ser defectuoso (al menos para mí). Entonces, tal vez, el camino a seguir sería solo un informe de error para el mantenedor de undo-tree. Pero, tal vez mi suposición sobre el concepto original es incorrecta.
Tobias
Creo que la mejor solución a su problema sería repararlo de undo-tree-modemanera que el cambio entre buffer-undo-treey buffer-undo-listfuncione sin problemas. ¿Ha considerado emitir un informe de error en `toby-undo-tree @ dr-qubit.org`?
Tobias
@Tobias: me gustaría ver y usar el visualizador undo-treemientras uso la nueva linearfunción; y me gustaría que la nueva linearfunción funcione sin que el búfer del visualizador sea necesariamente visible. En general, implemento mis propias mejoras / modificaciones a Emacs, tanto sus bibliotecas de terceros integradas como las opcionales. Cuando me atoro o veo algo súper complejo como la undofunción, pido ayuda. Una solicitud de función al responsable no podría perjudicar, pero preferiría manejarla aquí.
abogados el
@Tobias: al mirar el undo-tree.elcódigo hoy, vi que hay una función de marca de tiempo de nodo. No estoy seguro de si cada nodo tiene una marca de tiempo válida y si esos sobrevivirán a la próxima sesión de Emacs (al restaurar el historial), pero parece que podría ser un atajo para resolver esta nueva solicitud de función, es decir, ordenar y seleccionar el anterior o el siguiente en el tiempo. Todavía no he visto cómo se ve el diseño de la tierra en un punto de ramificación, pero ... esos son mis pensamientos preliminares. . . .
ley
Creo que la clave para un paseo lineal a través del árbol de deshacer es undo-list-rebuild-from-tree. Uno debe letvincular la variable buffer-undo-listy dejar que undo-list-rebuild-from-treehaga su trabajo. Luego copie ese valor en otra variable local, digamos my-undo-list, y deje el letformulario para el enlace buffer-undo-list. La variable my-undo-listdicta la ruta lineal a través de undo-tree. Al echarle un vistazo undo-list-rebuild-from-tree, verá que esta función también utiliza marcas de tiempo. Anteriormente consideré usar marcas de tiempo. Pero tuve la impresión de que cambian con demasiada frecuencia.
Tobias

Respuestas:

7

La siguiente es una implementación prototipo de una aproximación de lo que desea. Explota el hecho de que se agregan nuevas ramas en el árbol de deshacer en el lado izquierdo del nodo actual.

La secuencia de teclas C-M-_está vinculada a la undo-tree-walkque recorre la parte superior derecha del árbol de deshacer comenzando en el nodo actual.

El comportamiento difiere de lo que desea si la rama activa de algún subárbol en el lado derecho del nodo actual no es la rama más a la izquierda en ese subárbol.

Puede obtener dicho estado mediante secuencias de deshacer / rehacer no trivales.

Simplemente pruébelo usted mismo para ver si eso es suficiente para su aplicación.

(defvar-local undo-tree-walk
  "Possible values:
nil: not in undo/redo-chain
undo: traversing undo tree upwards
redo: traversing undo tree downwards
")

(setq undo-tree-walk nil)

(defun undo-tree-walk ()
  "Walk the right-upper part of the undo-tree starting at the current node."
  (interactive)
  (when (eq buffer-undo-list t)
    (user-error "No undo information."))
  (unless undo-tree-mode
    (user-error "`undo-tree-walk' should only be used with `undo-tree-mode'."))
  (undo-list-transfer-to-tree)
  (set-transient-map undo-tree-walk-map t #'undo-tree-walk-off)
  (let ((num-branches (undo-tree-num-branches)))
    (cond
     ((= num-branches 0) ; arrived at leaf
      (setq undo-tree-walk 'undo))
     ((> num-branches 1) ; 
      (let ((branch (undo-tree-node-branch (undo-tree-current buffer-undo-tree))))
    (setf (undo-tree-node-branch (undo-tree-current buffer-undo-tree))
          (if (>= (1+ branch) (undo-tree-num-branches))
          (progn
            (setq undo-tree-walk 'undo)
            (1- (undo-tree-num-branches)))
        (setq undo-tree-walk 'redo)
        (1+ branch))))
      ;; no state change for (= num-branches 1)
      )
     ))
  (case undo-tree-walk
   (undo
    (undo-tree-undo-1))
   (redo
    (undo-tree-redo-1))
   (t
    (setq undo-tree-walk 'undo)
    (undo-tree-undo-1)))
  (let ((curbuf (current-buffer)))
    (undo-tree-visualize)
    (switch-to-buffer-other-window curbuf)))

(defun undo-tree-walk-off ()
  "Switch `undo-tree-walk' off."
  (setq undo-tree-walk nil))

(defvar undo-tree-walk-map
  (make-sparse-keymap)
  "Keymap active while `undo-tree-walk'.")

(define-key undo-tree-walk-map (kbd "C-M-_") #'undo-tree-walk)

(global-set-key (kbd "C-M-_") #'undo-tree-walk)

Tenga en cuenta que agregué undo-tree-visualizeal final de undo-tree-walkpara mostrar las consecuencias de caminar con el árbol de deshacer undo-tree-walk. Siéntase libre de modificar el código a su gusto.

Tenga en cuenta también que tuve que elegir esta aproximación más simple de una solución para su problema debido a restricciones de tiempo.

Tobias
fuente
Al principio agregué lo siguiente para undo-tree-walksuperar algunos errores iniciales, tal vez porque no tengo este modo globalmente activo. (when (eq buffer-undo-list t) (user-error "No undo information.")) (undo-list-transfer-to-tree) Eso silenció mis errores iniciales en un nuevo búfer al intentar esta respuesta. Mi siguiente observación fue que undo-tree-walkllega al punto de bifurcación y luego cambia a la bifurcación a la derecha, pero solo baja una muesca / nódulo de la bifurcación antes de volver a subir la bifurcación al tronco. Mi configuración preferida crea una muesca / nódulo para cada pulsación de tecla.
leyes el
No hay absolutamente ninguna prisa en una solución. No dude en tomarse todo el tiempo que necesite, más allá del período de recompensa es perfectamente aceptable. Cuando esté implementado, estoy seguro de que usaré esta función a diario en el futuro previsible y, a juzgar por los votos positivos hasta ahora, a otras personas también les gustaría usar esta función.
leyes el
@lawlist Lamentablemente, mi tiempo está restringido. Si fuera posible, habría escrito esta respuesta más bien como un comentario que como una respuesta. Con suerte, a alguien más se le ocurre algo mejor antes de que termine el período de recompensa.
Tobias
@lawlist agregué undo-list-transfer-to-treecomo propusiste . Antes de eso, pruebo si undo-tree-modeestá activo, de lo contrario undo-list-transfer-to-treepuede ser fatal.
Tobias
Otorgado recompensa reciente por respuestas en el hilo relacionado: emacs.stackexchange.com/a/32415/2287 y emacs.stackexchange.com/a/32416/2287 ya que son los ingredientes clave para implementar la nueva característica. En pocas palabras, cada nodo tendrá una lista de marcas de tiempo, una por cada vez que un nodo se actualice después de deshacer / rehacer con éxito y también cuando se importe inicialmente. Se me ocurrió una idea para visualizar mostrando marcas de tiempo verticalmente debajo de cada nodo, y alargando las ramas en consecuencia: llevará algún tiempo resolverlo mediante programación.
abogados
3

Un agradecimiento especial a @Tobias por escribir una función para localizar la marca de tiempo siguiente / anterior en el historial de deshacer / rehacer: /emacs//a/32415/2287 ; y, también para escribir una serie de funciones para copiar el árbol de deshacer: /emacs//a/32230/2287 .

Como algunos lectores ya saben, MELPA acepta los tenedores solo en circunstancias extremas. La creación de un complemento probablemente sea factible, pero no parece práctico dada la cantidad de cambios que ha realizado @lawlist, que incluye, entre otros, agregar elementos a los vectores de estructura de datos subyacentes y cambiar los nombres de varios funciones / variables que no se ajustaban a la undo-tree-...convención de nomenclatura de prefijos, etc. @lawlist ya se ha comunicado con el autor original (Dr. Cubitt) para ofrecer esta nueva característica, así como varias correcciones de errores y mejoras.

Si alguien está interesado, no dude en darle un giro a esta nueva función. El comentario contiene un ejemplo de formulario de envío de informe de errores a partir del emacs -qcaso de que alguien tenga problemas.

Código fuente:   https://github.com/lawlist/undo_tree

Comentario:

;;; This unofficial modification by @lawlist to the `undo-tree.el` library authored
;;; by Toby Cubitt adds semi-linear undo/redo support and a corresponding visualizer
;;; view accessible with `C-u C-x u` or by using the 3-way toggle with the letter `t`
;;; in the visualization buffer.  This entire library is meant to be a replacement
;;; of the stock version of `undo-tree.el`, which would need to be completely removed
;;; from the `load-path'.  In the visualization buffer, the letters `u` / `r`
;;; or `z` / `Z` are used for semi-linear undo/redo.  In the working buffer,
;;; `super-u` / `super-r` or `super-z`/`super-Z` are used for semi-linear undo/redo.
;;; Semi-linear undo/redo also work in the classic views of the visualization buffer.
;;; All previous keyboard shortcuts remain unchanged.  The mouse can be used to
;;; select semi-linear nodes or branch-point timestamps in the visualization buffer.
;;;
;;; The term `semi-linear` was chosen because the time-line is generally structured
;;; as follows:  When undoing, the movement is in an upward direction from the
;;; leaf to the branch-point and then the previous branch begins undoing from the
;;; leaf.  When redoing, the movement is in a downward direction from the branch-
;;; point to the leaf and then the next branch begins redoing from the branch-point.
;;; It is not a situation where we walk-up and back-down the same branch, or walk-
;;; down and back-up the same branch again.  If that missing feature is useful,
;;; then perhaps it could be implemented someday....
;;;
;;; In a nutshell, the classic version of undo-tree undo/redo limits a user to
;;; the active branch (skipping over inactive branches), unless the user calls
;;; `undo-tree-switch-branch' or `undo-tree-visualize-switch-branch-right' or
;;; `undo-tree-visualize-switch-branch-left' to select an alternative branch.  This
;;; generally means a user must pop-open the visualizer buffer to see what is going
;;; on to make a proper decision.  The new semi-linear feature is essentially
;;; "mindless" where the user can just hold down the forward/reverse button and
;;; go through every node of the tree in chronological order -- i.e., all branches
;;; and nodes are visited in the process (nothing is skipped over).
;;;
;;; The labels in the visualization buffer remain the same:  `o`, `x`, `s`, register.
;;; The branches are labeled consecutively as they are drawn with lowercase letters.
;;; The branch-points are labeled consecutively as they are drawn with uppercase
;;; letters.  The branches coming off of each branch-point are labeled with the nth
;;; numeric position of the branch -- e.g., far left is always nth 0.  The nodes of
;;; each branch are numbered consecutively commencing just after the branch-point.
;;;
;;; The features that are available in `undo-tree.el` version 0.6.6 remain the same;
;;; however, some of the functions have been consolidated and the names have changed.
;;;
;;; `undo-tree-history-save' and `undo-tree-history-restore' support input/output
;;; to/from a string or a file.  The history string/file contains three components:
;;; `buffer-file-name' (if it exists; else `nil`); SHA1 string; the `undo-tree-list'.
;;; Histories created with the unmodified stock version of `undo-tree.el` contained 2
;;; components and those previous versions are no longer supported.  Saving/exporting
;;; excludes all text-properties, yasnippet entries, and multiple-cursors entries.
;;; `read' chokes when encountering #<marker in no buffer> or #<overlay in no buffer>,
;;; that can make their way into the `undo-tree-list' when killing the visualizer
;;; buffer by brute force or when using the yasnippet library.  Those two known
;;; situations have been dealt with programmatically.  However, there are surely
;;; other libraries that use markers and/or overlays that could make their way into
;;; the tree and new ways of dealing with those entries will be required.  If you
;;; encounter an error message when performing `undo-tree-history-save', please
;;; inspect the `*Messages*` buffer for clues such as the above examples.  Inasmuch
;;; as there is now a sanity check at the tail end of `undo-tree-history-save', any
;;; problems should materialize before a user actually tries to restore the history.
;;;
;;; The persistent undo storage has been expanded by adding certain features borrowed
;;; from the built-in `image-dired.el' library:
;;;
;;; `undo-tree-history-autosave':  When non-nil, `undo-tree-mode' will save undo
;;;                                history to a file when a buffer is saved; and,
;;;                                the save/restore functions attached to the
;;;                                following hooks will become active:
;;;                                -  `write-file-functions'
;;;                                -  `find-file-hook'
;;;                                To exclude certain files, users may wish to let-
;;;                                bind this variable to `nil` if it has a global
;;;                                non-nil value.  See also the next variable below.
;;;
;;; `undo-tree-history-file-exclusions':  A list of absolute file names that will be
;;;                                       excluded from the auto save/restore process.
;;;
;;; `undo-tree-history-alist':  Used when `undo-tree-history-storage' is 'classic.
;;;                             See the doc-string for customization tips/tricks.
;;;
;;; `undo-tree-history-directory':  Directory where history files are stored when
;;;                                `undo-tree-history-storage' is 'central.
;;;
;;; `undo-tree-history-storage':  How to store undo-tree history files.
;;;                               'classic:  See `undo-tree-history-alist'.
;;;                               'home (md5):  A folder in the HOME directory.
;;;                               'central (md5):  See `undo-tree-history-directory'.
;;;                               'local:  Create sub-directory in working directory.
;;;
;;; For those users who wish to use Emacs to view the saved/exported history, be
;;; aware that the undo history is one long string, and Emacs has trouble viewing a
;;; buffer with very long lines.  `(setq-default bidi-display-reordering nil)` will
;;; help permit Emacs to view buffers with very long lines without bogging down.
;;;
;;; The primary interactive functions for undo/redo in the working buffer are:
;;;
;;;   M-x undo-tree-classic-undo
;;;   M-x undo-tree-classic-redo
;;;   M-x undo-tree-linear-undo
;;;   M-x undo-tree-linear-redo
;;;
;;; The primary interactive functions for undo/redo in the visualization buffer are:
;;;
;;;   M-x undo-tree-visualize-classic-undo
;;;   M-x undo-tree-visualize-classic-redo
;;;   M-x undo-tree-visualize-linear-undo
;;;   M-x undo-tree-visualize-linear-redo
;;;
;;; If the built-in undo amalgamation business is not to your liking, it can be
;;; disabled to permit undo boundaries after every command:
;;;
;;;   ;;; /programming//a/41560712/2112489
;;;   (advice-add 'undo-auto--last-boundary-amalgamating-number :override #'ignore)
;;;
;;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=27214
;;; /emacs//q/33248/2287
;;; GARBAGE COLLECTION:  @lawlist has encountered a few situations where garbage
;;; collection truncates the `undo-tree-canary' in the `buffer-undo-list', which
;;; causes `undo-tree-transfer-list' to replace the existing `undo-tree-list'
;;; with the new tree fragment obtained from the `buffer-undo-list'.  In this
;;; circumstance, the user loses the entire undo-tree saved history!  The internal
;;; function responsible is `truncate_undo_list' in `undo.c`.  @lawlist has added a
;;; programmatic warning when loss of the existing `undo-tree-list' is about to
;;; occur; however, that does not fix the problem.  The relevant section from
;;; `truncate_undo_list' in `undo.c` is as follows:
;;;          /* When we get to a boundary, decide whether to truncate
;;;      either before or after it.  The lower threshold, undo_limit,
;;;      tells us to truncate after it.  If its size pushes past
;;;      the higher threshold undo_strong_limit, we truncate before it.  */
;;;          if (NILP (elt))
;;;     {
;;;       if (size_so_far > undo_strong_limit)
;;;         break;
;;;       last_boundary = prev;
;;;       if (size_so_far > undo_limit)
;;;         break;
;;;     }
;;; @lawlist opines that setting the `undo-limit' to the same value as
;;; `undo-strong-limit' will cause `truncate_undo_list' to preserve the
;;; `undo-tree-canary' in the `buffer-undo-list' by truncating before the boundary.
;;; This workaround is not ideal because a more recent undo would be truncated in
;;; lieu of an older undo.  One idea would be to convince the Emacs team to modify
;;; `truncate_undo_list' to preserve certain user-defined elements; e.g., a symbol
;;; of `undo-tree-canary'.
;;;
;;; The built-in function named `primitive-undo' defined in `simple.el` was used
;;; in the original version of `undo-tree.el`.  @lawlist created a modified
;;; function named `undo-tree--primitive-undo' that serves the same purpose, but
;;; permits setting a window-point in the working buffer while a user is in a
;;; different window such as the visualization buffer.  The revised version also
;;; merely reports a problem with a message instead of throwing an error when it
;;; encounters an `undo-tree-canary' in the wrong location.  This bug was noticed
;;; by @lawlist when performing undo/redo in region, and a Google search revealed
;;; that others too have experienced the same problem.  The bug is fairly easy to
;;; reproduce, but @lawlist has not yet invested the time to look for the cause
;;; and try to come up with a solution.  For anyone who wishes to work on fixing
;;; this and view other mentions of the same problem on the internet, Google:
;;;   "Unrecognized entry in undo list undo-tree-canary"
;;;
;;; The semi-linear visualization buffer view looks like this:
;;;
;;;        o-00001-a-0
;;;        20.34.55.46
;;;             |
;;;        o-br/pt-A-0
;;;        20.47.57.25
;;;        20.34.55.47
;;;         ____|_______________________________
;;;        /                                    \
;;;  o-00001-b-0                            o-00001-c-1
;;;  20.47.57.26                            20.34.55.48
;;;       |                                      |
;;;  o-00002-b-0                            o-00002-c-1
;;;  20.47.57.27                            20.34.55.49
;;;       |                                      |
;;;  o-00003-b-0                            o-00003-c-1
;;;  20.47.57.28                            20.34.55.50
;;;                                              |
;;;                                         o-00004-c-1
;;;                                         20.34.55.51
;;;                                              |
;;;                                         o-br/pt-B-1
;;;                                         21.25.32.05
;;;                                         20.35.06.89
;;;                                         20.35.02.23
;;;                                         20.34.58.43
;;;                                         20.34.55.57
;;;         _____________________________________|________________________
;;;        /            /                        |                        \
;;;  o-00001-d-0  o-00001-e-1               o-br/pt-C-2               o-00001-f-3
;;;  21.25.32.06  20.35.06.90               23.03.45.34               20.34.58.44
;;;                    |                    00.27.40.07                    |
;;;               o-00002-e-1               20.35.02.24               o-00002-f-3
;;;               20.35.06.91         ___________|___________         20.34.58.45
;;;                    |             /           |           \             |
;;;               o-00003-e-1  o-00001-g-0  o-00001-h-1  o-00001-i-2  o-00003-f-3
;;;               20.35.06.92  23.03.45.35  00.27.40.08  20.35.02.25  20.34.58.46
;;;                    |            |            |            |            |
;;;               o-00004-e-1  x-00002-g-0  o-00002-h-1  o-00002-i-2  o-00004-f-3
;;;               20.35.06.93  23:03:45:36  00.27.44.51  20.35.02.26  20.34.58.47
;;;                    |                                      |            |
;;;               o-00005-e-1                            o-00003-i-2  o-00005-f-3
;;;               20.35.06.94                            20.35.02.27  20.34.58.48
;;;                    |                                      |            |
;;;               o-00006-e-1                            o-00004-i-2  o-00006-f-3
;;;               20.35.06.95                            20.35.02.28  20.34.58.49
;;;                    |                                      |            |
;;;               o-00007-e-1                            o-00005-i-2  o-00007-f-3
;;;               20.35.06.96                            20.35.02.29  20.34.58.50
;;;                    |                                      |            |
;;;               o-00008-e-1                            o-00006-i-2  o-00008-f-3
;;;               20.35.06.97                            20.35.02.30  20.34.58.51
;;;
;;; To check for updates, please visit the source-code of the link listed at the
;;; top and also review the "Change Log" at the bottom.
;;;
;;; Bug reports and feature requests may be submitted via email to the address at
;;; the top.  Essentially, if it breaks in half, I can guarantee that you will
;;; have 2 pieces that may not necessarily be the same size.  :)  That being said,
;;; I will certainly make efforts to fix any problem that may arise relating to
;;; the semi-linear undo/redo feature.  A step 1-2-3 recipe starting from emacs -q
;;; would be very helpful so that @lawlist can observe the same behavior described
;;; in the bug report.  Here is an example to get you started:
;;;
;;; 1.  In an internet browser, visit: https://www.lawlist.com/lisp/undo-tree.el
;;;
;;;     Select/highlight all and copy everything to the clipboard.
;;;
;;; 2.  Launch Emacs without any user settings whatsoever:  emacs -q
;;;
;;;     If possible, please use the latest stable public release of Emacs.
;;;     @lawlist is using the GUI version of Emacs 25.2.1 on OSX.
;;;
;;; 3.  Switch to the `*scratch*` buffer.
;;;
;;; 4.  Paste the entire contents of the clpipboard into the `*scratch*` buffer.
;;;
;;; 5.  M-x eval-buffer RET
;;;
;;; 6.  M-x eval-expression RET (setq undo-tree-history-autosave t) RET
;;;
;;; 7.  M-x undo-tree-mode RET
;;;
;;;     The mode-line indicates `UT`, meaning that `undo-tree-mode' is active.
;;;
;;; 8.  M-x save-buffer RET
;;;
;;;     @lawlist chose to save the file to his desktop with the name `foo`, and
;;;     also chose to overwrite the file if it already existed; i.e., `y`.
;;;
;;;     Look at the lower left-hand side of the mode-line and notice that it
;;;     indicates an unmodified state; i.e., U:--- foo ....
;;;
;;; 9.  M-x undo-tree-classic-undo RET
;;;
;;;     Look at the lower left-hand side of the mode-line and notice that it
;;;     indicates we have returned to a modified state; i.e., U:**- foo ....
;;;
;;; 10. M-x undo-tree-classic-undo RET
;;;
;;;     The `undo-tree' library that we had previously pasted to the `*scratch*`
;;;     buffer should now be completely undone; i.e., removed.
;;;
;;; 11. M-x undo-tree-classic-undo RET
;;;
;;;     The buffer should be completely empty at this point; i.e., the initial
;;;     `*scratch*` message has been removed.
;;;
;;; 12. M-x undo-tree-classic-undo RET
;;;
;;;     The following `user-error' appears:
;;;
;;;       "user-error:  undo-tree--undo-or-redo:  No further undo information."
;;;
;;;     This is exactly the behavior that @lawlist expected would happen, so
;;;     everything up to this point appears to be working correctly.
lista de leyes
fuente