package quill

  1. Overview
  2. Docs

Source file state.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
module Int_set = Set.Make (Int)

type load_state =
  | Idle
  | Loading of { path : string }
  | Load_failed of { path : string; error : string }

type caret = { block_id : int; inline_id : int option; offset : int }

type selection =
  | No_selection
  | Caret of caret
  | Range of { anchor : caret; focus : caret }

type config = { history_limit : int; auto_normalize : bool }
type snapshot = { document : Document.t; selection : selection }
type history = { past : snapshot list; future : snapshot list; capacity : int }

type t = {
  document : Document.t;
  selection : selection;
  load_state : load_state;
  running_blocks : Int_set.t;
  history : history;
  config : config;
}

let default_config = { history_limit = 100; auto_normalize = true }
let empty_history capacity = { past = []; future = []; capacity }

let create ?(config = default_config) ?(document = Document.empty)
    ?(selection = No_selection) () =
  {
    document;
    selection;
    load_state = Idle;
    running_blocks = Int_set.empty;
    history = empty_history config.history_limit;
    config;
  }

let init = create ()

let with_document ?config ?selection document =
  create ?config ?selection ~document ()

let set_document state document = { state with document }
let set_load_state state load_state = { state with load_state }
let set_selection state selection = { state with selection }
let clear_selection state = { state with selection = No_selection }

let mark_block_running state block_id =
  let running_blocks = Int_set.add block_id state.running_blocks in
  { state with running_blocks }

let mark_block_idle state block_id =
  let running_blocks = Int_set.remove block_id state.running_blocks in
  { state with running_blocks }

let is_block_running state block_id = Int_set.mem block_id state.running_blocks
let snapshot state = { document = state.document; selection = state.selection }

let trim_history capacity lst =
  let rec aux acc count = function
    | [] -> List.rev acc
    | _ when count >= capacity -> List.rev acc
    | x :: xs -> aux (x :: acc) (count + 1) xs
  in
  aux [] 0 lst

let push_history state =
  let snap = snapshot state in
  let history =
    let past =
      trim_history state.history.capacity (snap :: state.history.past)
    in
    { state.history with past; future = [] }
  in
  { state with history }

let normalize_if_enabled state document =
  if state.config.auto_normalize then Document.normalize_blanklines document
  else document

let restore snap ~config ~history =
  {
    document = snap.document;
    selection = snap.selection;
    load_state = Idle;
    running_blocks = Int_set.empty;
    history;
    config;
  }

let record_document_change ?selection state document =
  let selection = Option.value selection ~default:state.selection in
  let state = push_history state in
  let document = normalize_if_enabled state document in
  { state with document; selection }

let has_undo state = state.history.past <> []
let has_redo state = state.history.future <> []

let undo state =
  match state.history.past with
  | [] -> None
  | snap :: past ->
      let future = snapshot state :: state.history.future in
      let history = { state.history with past; future } in
      let state =
        {
          state with
          document = snap.document;
          selection = snap.selection;
          history;
        }
      in
      Some state

let redo state =
  match state.history.future with
  | [] -> None
  | snap :: future ->
      let past =
        trim_history state.history.capacity
          (snapshot state :: state.history.past)
      in
      let history = { state.history with past; future } in
      let state =
        {
          state with
          document = snap.document;
          selection = snap.selection;
          history;
        }
      in
      Some state

let selection_blocks state =
  match state.selection with
  | No_selection -> []
  | Caret caret -> [ caret.block_id ]
  | Range { anchor; focus } ->
      Document.block_ids_between state.document ~start_id:anchor.block_id
        ~end_id:focus.block_id