History

class History(val document: HistoryDocument)

The mechanism underlying undo and redo. It is called History, because I think of undo as going back in time, and redo as going forward in time.

You can think of History being a list of changes made to a document. Internally, History keeps an index into this list. If the index is 0, then we cannot undo. If the index is at the end of the list, then we cannot redo.

To make a change to a document, we create a Change object, add it to the history, and then apply the Change.

Any Changes after the old index are lost. For example : * Start with an empty document (index is 0) * Add the word "Hello" (index is 1) * Undo (so we are back to an empty document) (index is 0 again) * Add the word "Goodbye" (index is 1 again)

The Change which added the word "Hello" is lost forever.

Up until now, I've implied that the History is a list of changes, but that's not quite correct. History is a list of Batches, where a Batch contains one or more Change. This is because Changes are very simplistic, and complicated changes are implemented as a Batch of simple Changes. For example, to replace the word "Hi" with the word "Hello" is a deletion followed by an insertion. There is no Change for a replacement.

Undo and redo works on Batches, not individual Changes.

Every Change instance must have enough information to undo and redo that change.

Merging Changes

Consider what happens when you type the word "Hello", and then press the Undo button. Each individual key-press creates an insert Change. But for efficiency (in both memory and speed), these changes can be merged together. So we end up with a single Batch containing a single insert Change containing the word "Hello".

Merging is only allowed on the last Change of the current Batch.

Merging is only permitted within a short time-window. For example, if you type "Hello", then have some coffee, then type " World", the result is two Batches, and you can undo/redo each individually. mergeTimeThreshold defaults to 10,000 milliseconds (10 seconds). As this is an arbitrary value, TextDocuments bind this to GlokSettings.historyMergeTimeThresholdProperty.

Change Implementations

Each concrete implementation of HistoryDocument must be able to create Change objects, but the public API must NOT expose the ChangeImpl interface. Only History should have access to the change's undo() and redo() methods. Calling these from anywhere else will break the undo/redo mechanism.

Class Diagram

    ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐           ┏━━━━━━━━━━━━━━━━━━━━━┓
    ┆ HistoryDocument   ┆           ┃       History       ┃
    ┆                   ┆           ┃                     ┃
    ┆ history           ├───────────┨ document            ┃         ┏━━━━━━━━━━━━━━━━━━━━━┓
┌──▶┆                   ┆           ┃ undoable : Boolean  ┃         ┃        Batch        ┃
│   ┆                   ┆           ┃ redoable : Boolean  ┃◆────────┨                     ┃
│   └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘           ┃ isSaved : Boolean   ┃         ┃                     ┃
│                                   ┃                     ┃         ┃                     ┃
│                                   ┃ undo()              ┃    ┌───◆┃                     ┃
│                                   ┃ redo()              ┃    │    ┗━━━━━━━━━━━━━━━━━━━━━┛
│                                   ┃ saved()             ┃    │
│  ┌┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┐           ┃                     ┃    │
│  ┊       Change       ┊           ┗━━━━━━━━━━━━━━━━━━━━━┛    │
│  ┊                    ┊                                      │
└──┤ document           ├──────────────────────────────────────┘
   ┊                    ┊
   ┊                    ┊
   └┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┘
              △
              │
    ┌┄┄┄┄┄┄┄┄┄┴┄┄┄┄┄┄┄┄┄┄┐
    ┊     ChangeImpl     ┊
    ┊                    ┊
    ┊                    ┊
    ┊ undo()             ┊
    ┊ redo()             ┊
    └┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┘

Notes

History is used by Glok's TextArea and StyledTextArea, but it can also application developers for their own documents. It is intended to be general purpose, and includes features which glok-core doesn't use (such as SkippableChange).

History is part of the glok-model sub-project (not glok-core), so that application developers can use this history mechanism within their model, with no dependency on glok-core. For example, suppose I write a diagram editor, with classes Diagram and DiagramView. The Diagram class has no GUI component, and therefore shouldn't depend on glok-core.

Constructors

Link copied to clipboard
constructor(document: HistoryDocument)

Types

Link copied to clipboard
inner class BatchImpl(label: String) : Batch

Properties

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard

true if the document was last saved at the current point in the history. i.e. it returns true immediately after saved is called, but also returns true if changes are made, and then undo is performed to get back to the point that saved was called.

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard

The time between changes in milliseconds, where changes can be merged. The default is 10,000 milliseconds (10 seconds). i.e. if we type with less than 10 seconds between keystrokes, the changes will be merged to form a single batch, so undo/redo will not operate one character at a time.

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard

Functions

Link copied to clipboard
Link copied to clipboard
fun batch(block: Batch.() -> Unit)

fun batch(label: String = "", block: Batch.() -> Unit)

Begins a new Batch, runs the block, and then closes the Batch.

Link copied to clipboard
fun beginBatch(label: String = ""): Batch
Link copied to clipboard
fun clear()
Link copied to clipboard
Link copied to clipboard
fun endBatch()
Link copied to clipboard
Link copied to clipboard
fun redo(skipSelectionChanges: Boolean = false): Change?
Link copied to clipboard
fun saved()
Link copied to clipboard
fun undo(skipSelectionChanges: Boolean = false): Change?