elm-community / undo-redo / UndoList

UndoList Data Structure.

Definition


type alias UndoList state =
{ past : List state
, present : state
, future : List state 
}

The UndoList data structure. An UndoList has:

  1. A list of past states
  2. A present state
  3. A list of future states

The head of the past list is the most recent state and the head of the future list is the next state. (i.e., the tails of both lists point away from the present)

Basic Operations

undo : UndoList state -> UndoList state

If the undolist has any past states, set the most recent past state as the current state and turn the old present state into a future state.

i.e.

undo (UndoList [ 3, 2, 1 ] 4 [ 5, 6 ]) --> UndoList [ 2, 1 ] 3 [ 4, 5, 6 ]

redo : UndoList state -> UndoList state

If the undo-list has any future states, set the next future state as the current state and turn the old present state into a past state.

i.e.

redo (UndoList [ 3, 2, 1 ] 4 [ 5, 6 ]) --> UndoList [ 4, 3, 2, 1 ] 5 [ 6 ]

fresh : state -> UndoList state

Turn a state into an undo-list with neither past nor future.

new : state -> UndoList state -> UndoList state

Add a new present state to the undo-list, turning the old present state into a past state and erasing the future.

forget : UndoList state -> UndoList state

Forget the past and look to the future! This simply clears the past list.

i.e.

forget (UndoList [3,2,1] 4 [5,6]) --> UndoList [] 4 [5,6]

reset : UndoList state -> UndoList state

Reset the undo-list by returning to the very first state and clearing all other states.

i.e.

reset (UndoList [ 3, 2, 1 ] 4 [ 5, 6 ]) --> UndoList [] 1 []

Query UndoList

hasPast : UndoList state -> Basics.Bool

Check if the undo-list has any past states.

hasPast (UndoList [] 1 []) --> False
hasPast (UndoList [ 1, 2, 3 ] 4 []) --> True

hasFuture : UndoList state -> Basics.Bool

Check if the undo-list has any future states.

hasFuture (UndoList [] 1 []) --> False
hasFuture (UndoList [] 1 [ 2, 3, 4 ]) --> True

length : UndoList state -> Basics.Int

Get the full length of an undo-list

length (UndoList [ 0 ] 1 [ 2, 3, 4 ]) --> 5

lengthPast : UndoList state -> Basics.Int

Get the length of the past.

lengthPast (UndoList [ 0 ] 1 [ 2, 3, 4 ]) --> 1

lengthFuture : UndoList state -> Basics.Int

Get the length of the future

lengthFuture (UndoList [ 0 ] 1 [ 2, 3, 4 ]) --> 3

Messages


type Msg msg
    = Reset
    | Redo
    | Undo
    | Forget
    | New msg

Simple UndoList Msg type. This is a simple type that can be used for most use cases. This works best when paired with the update function as update will perform the corresponding operations on the undolist automatically.

Consider using your own data type only if you really need it.

mapMsg : (a -> b) -> Msg a -> Msg b

Map a function over a msg.

mapMsg sqrt (New 100) --> New 10
mapMsg sqrt Undo --> Undo

Functional Operations

map : (a -> b) -> UndoList a -> UndoList b

Map a function over an undo-list. Be careful with this. The function will be applied to the past and the future as well. If you just want to change the present, use mapPresent.

A good use case for map is to encode an undo-list as JSON.

Example:

import UndoList.Encode as Encode

encode encoder undolist =
    map encoder undolist
        |> Encode.undolist

mapPresent : (a -> a) -> UndoList a -> UndoList a

Apply a function only to the present.

update : (msg -> state -> state) -> Msg msg -> UndoList state -> UndoList state

Convert a function that updates the state to a function that updates an undo-list. This is very useful to allow you to write update functions that only deal with the individual states of your system and treat undo/redo as an add on.

Example:

-- Your update function
update msg state =
  case msg of
    ... -- some implementation

-- Your new update function
updateWithUndo = UndoList.update update

connect : UndoList state -> UndoList state -> UndoList state

Connect two undo-lists end to end. The present of the first undolist is considered the present of the output undolist.

reduce : (a -> b -> b) -> b -> UndoList a -> b

Alias for foldl

foldl : (a -> b -> b) -> b -> UndoList a -> b

Reduce an undo-list from the left (or from the past)

foldr : (a -> b -> b) -> b -> UndoList a -> b

Reduce an undo-list from the right (or from the future)

reverse : UndoList a -> UndoList a

Reverse an undo-list.

flatten : UndoList (UndoList a) -> UndoList a

Flatten an undo-list of undo-lists into a single undo-list.

flatMap : (a -> UndoList b) -> UndoList a -> UndoList b

Map over an undo-list and then flatten the result.

andThen : (a -> UndoList b) -> UndoList a -> UndoList b

Chain undo-list operations. This is simply an alias of flatMap

map2 : (a -> b -> c) -> UndoList a -> UndoList b -> UndoList c

Map a function over a pair of undo-lists.

andMap : UndoList a -> UndoList (a -> b) -> UndoList b

Map a function over any number of undo-lists.

map f xs
    |> andMap ys
    |> andMap zs

Shorthands

view : (state -> view) -> UndoList state -> view

Function to help not having to deal with the full undolist from with your actual view function.

Suppose you define the following:

initial : model

update : msg -> model -> model

view : model -> Html (UndoList.Msg msg)

Then, you could construct the main function as follows:

main =
    Html.beginnerProgram
        { model = UndoList.fresh initial
        , update = UndoList.update update
        , view = UndoList.view view
        }

Conversions

toList : UndoList state -> List state

Convert an undo-list to a list :

toList (UndoList [ 3, 2, 1 ] 4 [ 5, 6 ]) --> [ 1, 2, 3, 4, 5, 6 ]

fromList : state -> List state -> UndoList state

Convert a list to undolist. The provided state is used as the present state and the list is used as the future states.

fromList 1 [ 2, 3, 4 ] --> UndoList [] 1 [ 2, 3, 4 ]