diff options
| author | Federico Igne <undyamon@disroot.org> | 2024-01-11 19:31:31 +0100 |
|---|---|---|
| committer | Federico Igne <undyamon@disroot.org> | 2024-01-11 19:31:31 +0100 |
| commit | 055c743c55bde27f4475d3434c26d8383c0c3ea1 (patch) | |
| tree | aabf2173a9995f5795da86d5676181b62fee0e9e | |
| parent | 416c56656af65d656f637dc8c8fdb62d0ba03e29 (diff) | |
| download | sandy-055c743c55bde27f4475d3434c26d8383c0c3ea1.tar.gz sandy-055c743c55bde27f4475d3434c26d8383c0c3ea1.zip | |
bulk: add PoC of vim-like modular editor
| -rw-r--r-- | bin/main.ml | 9 | ||||
| -rw-r--r-- | lib/command.ml | 115 | ||||
| -rw-r--r-- | lib/config.ml | 9 | ||||
| -rw-r--r-- | lib/editor.ml | 271 | ||||
| -rw-r--r-- | lib/editorBuffer.ml | 72 | ||||
| -rw-r--r-- | lib/key.ml | 72 | ||||
| -rw-r--r-- | lib/modes.ml | 18 | ||||
| -rw-r--r-- | lib/terminal.ml | 130 | ||||
| -rw-r--r-- | lib/terminal.mli | 67 | ||||
| -rw-r--r-- | lib/text.ml | 10 | ||||
| -rw-r--r-- | lib/util.ml | 26 | ||||
| -rw-r--r-- | lib/zipper.ml | 131 | ||||
| -rw-r--r-- | lib/zipper.mli | 258 |
13 files changed, 1187 insertions, 1 deletions
diff --git a/bin/main.ml b/bin/main.ml index 7bf6048..75328c9 100644 --- a/bin/main.ml +++ b/bin/main.ml | |||
| @@ -1 +1,8 @@ | |||
| 1 | let () = print_endline "Hello, World!" | 1 | open Sand |
| 2 | |||
| 3 | let () = | ||
| 4 | let open Editor in | ||
| 5 | let cli = Config.parse Sys.argv in | ||
| 6 | let editor = Editor.init cli in | ||
| 7 | let rec loop () = Action.(render *> handle_next_command2 >>= loop) in | ||
| 8 | Action.eval ~editor loop | ||
diff --git a/lib/command.ml b/lib/command.ml new file mode 100644 index 0000000..7dedc6c --- /dev/null +++ b/lib/command.ml | |||
| @@ -0,0 +1,115 @@ | |||
| 1 | open Base | ||
| 2 | open Key | ||
| 3 | |||
| 4 | type register = char option | ||
| 5 | type count = int option | ||
| 6 | type operation = Noop | Yank | Paste | Delete | Change | ||
| 7 | type scope = Line | To_bol | To_eol | Down | Left | Right | Up | ||
| 8 | |||
| 9 | type command = | ||
| 10 | | Type of char | ||
| 11 | | Simple of Key.t | ||
| 12 | | Partial of Key.t | ||
| 13 | | Shortcut of register * count * operation * scope | ||
| 14 | | Chord of register * count * operation * count * scope | ||
| 15 | |||
| 16 | type t = command | ||
| 17 | |||
| 18 | let shortcut ?r ?n c s = Shortcut (r, n, c, s) | ||
| 19 | let chord ?r ?n1 c ?n2 m = Chord (r, n1, c, n2, m) | ||
| 20 | |||
| 21 | let i_stream = | ||
| 22 | let step s k = | ||
| 23 | let open Sequence.Step in | ||
| 24 | match (s, k) with | ||
| 25 | | `start, Key c -> Yield { value = Type c; state = `start } | ||
| 26 | | `start, _ -> Yield { value = Simple k; state = `start } | ||
| 27 | | _, _ -> Skip { state = `start } | ||
| 28 | in | ||
| 29 | Sequence.unfold_with ~init:`start ~f:step Key.stream | ||
| 30 | |||
| 31 | let simple_movements = | ||
| 32 | [ | ||
| 33 | Key 'h'; | ||
| 34 | Key 'j'; | ||
| 35 | Key 'k'; | ||
| 36 | Key 'l'; | ||
| 37 | Key ' '; | ||
| 38 | Arrow_up; | ||
| 39 | Arrow_down; | ||
| 40 | Arrow_left; | ||
| 41 | Arrow_right; | ||
| 42 | Backspace; | ||
| 43 | ] | ||
| 44 | |||
| 45 | let to_scope = function | ||
| 46 | | Key 'j' | Arrow_down -> Down | ||
| 47 | | Key 'h' | Arrow_left | Backspace -> Left | ||
| 48 | | Key 'l' | Key ' ' | Arrow_right -> Right | ||
| 49 | | Key 'k' | Arrow_up -> Up | ||
| 50 | | _ -> failwith "Invalid motion." | ||
| 51 | |||
| 52 | let n_stream = | ||
| 53 | let step s k = | ||
| 54 | let open Sequence.Step in | ||
| 55 | let is_chord_op c = String.contains "ydc" (Char.lowercase c) in | ||
| 56 | let is_simple_movement k = List.mem ~equal:Poly.equal simple_movements k in | ||
| 57 | let to_op c = | ||
| 58 | match Char.lowercase c with | ||
| 59 | | 'y' -> Yank | ||
| 60 | | 'p' -> Paste | ||
| 61 | | 'd' -> Delete | ||
| 62 | | 'c' -> Change | ||
| 63 | | _ -> failwith "Invalid operation in chord." | ||
| 64 | in | ||
| 65 | match (s, k) with | ||
| 66 | | `start, Key '"' -> Yield { value = Partial k; state = `chord_reg_pre } | ||
| 67 | | `chord_reg_pre, Key c -> Yield { value = Partial k; state = `chord_reg c } | ||
| 68 | | `chord_reg r, Key n when Char.('1' <= n && n <= '9') -> | ||
| 69 | let n = Char.to_int n - 48 in | ||
| 70 | Yield { value = Partial k; state = `chord_n (Some r, n) } | ||
| 71 | | `start, Key n when Char.('1' <= n && n <= '9') -> | ||
| 72 | let n = Char.to_int n - 48 in | ||
| 73 | Yield { value = Partial k; state = `chord_n (None, n) } | ||
| 74 | | `chord_n (r, m), Key n when Char.('0' <= n && n <= '9') -> | ||
| 75 | let n = (10 * m) + Char.to_int n - 48 in | ||
| 76 | Yield { value = Partial k; state = `chord_n (r, n) } | ||
| 77 | | `start, Key c when is_chord_op c -> | ||
| 78 | if Char.is_uppercase c then | ||
| 79 | Yield { value = shortcut (to_op c) To_eol; state = `start } | ||
| 80 | else | ||
| 81 | Yield { value = Partial k; state = `chord_cmd (None, None, to_op c) } | ||
| 82 | | `chord_reg r, Key c when is_chord_op c -> | ||
| 83 | if Char.is_uppercase c then | ||
| 84 | Yield { value = shortcut ~r (to_op c) To_eol; state = `start } | ||
| 85 | else | ||
| 86 | Yield | ||
| 87 | { value = Partial k; state = `chord_cmd (Some r, None, to_op c) } | ||
| 88 | | `chord_n (r, n), Key c when is_chord_op c -> | ||
| 89 | if Char.is_uppercase c then | ||
| 90 | Yield { value = shortcut ?r ~n (to_op c) To_eol; state = `start } | ||
| 91 | else | ||
| 92 | Yield { value = Partial k; state = `chord_cmd (r, Some n, to_op c) } | ||
| 93 | | `chord_cmd (r, n, c), Key ch when is_chord_op ch && Poly.(c = to_op ch) -> | ||
| 94 | if Char.is_uppercase ch then | ||
| 95 | Yield { value = shortcut ?r ?n c To_bol; state = `start } | ||
| 96 | else Yield { value = shortcut ?r ?n c Line; state = `start } | ||
| 97 | | (`start | `chord_reg _), k when is_simple_movement k -> | ||
| 98 | Yield { value = chord Noop (to_scope k); state = `start } | ||
| 99 | | `chord_n (_, n), k when is_simple_movement k -> | ||
| 100 | Yield { value = chord ~n1:n Noop (to_scope k); state = `start } | ||
| 101 | | `chord_cmd (r, n, c), k when is_simple_movement k -> | ||
| 102 | Yield { value = chord ?r ?n1:n c (to_scope k); state = `start } | ||
| 103 | | `chord_cmd (r, n1, c), Key n when Char.('1' <= n && n <= '9') -> | ||
| 104 | let n = Char.to_int n - 48 in | ||
| 105 | Yield { value = Partial k; state = `chord_mv_n (r, n1, c, n) } | ||
| 106 | | `chord_mv_n (r, n1, c, n2), Key n when Char.('0' <= n && n <= '9') -> | ||
| 107 | let n2 = (10 * n2) + Char.to_int n - 48 in | ||
| 108 | Yield { value = Partial k; state = `chord_mv_n (r, n1, c, n2) } | ||
| 109 | | `chord_mv_n (r, n1, c, n2), k when is_simple_movement k -> | ||
| 110 | Yield { value = chord ?r ?n1 c ~n2 (to_scope k); state = `start } | ||
| 111 | (* Catch-all rules *) | ||
| 112 | | `start, _ -> Yield { value = Simple k; state = `start } | ||
| 113 | | _, _ -> Skip { state = `start } | ||
| 114 | in | ||
| 115 | Sequence.unfold_with ~init:`start ~f:step Key.stream | ||
diff --git a/lib/config.ml b/lib/config.ml new file mode 100644 index 0000000..4ebf436 --- /dev/null +++ b/lib/config.ml | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | open Base | ||
| 2 | |||
| 3 | type config = { | ||
| 4 | files : string list; | ||
| 5 | } | ||
| 6 | type t = config | ||
| 7 | |||
| 8 | let parse args = | ||
| 9 | { files = List.(of_array args |> tl_exn) } | ||
diff --git a/lib/editor.ml b/lib/editor.ml new file mode 100644 index 0000000..c3e298e --- /dev/null +++ b/lib/editor.ml | |||
| @@ -0,0 +1,271 @@ | |||
| 1 | open Base | ||
| 2 | module Buffer = EditorBuffer | ||
| 3 | open Util | ||
| 4 | |||
| 5 | type mode = Normal | Insert | ||
| 6 | type cursor = int * int | ||
| 7 | |||
| 8 | type editor = { | ||
| 9 | term : Terminal.state; | ||
| 10 | mode : mode; | ||
| 11 | offset : int * int; | ||
| 12 | cursor : cursor; | ||
| 13 | buffer : Buffer.t option; | ||
| 14 | pending : Key.t Sequence.t; | ||
| 15 | i_pending : Command.t Sequence.t; | ||
| 16 | n_pending : Command.t Sequence.t; | ||
| 17 | } | ||
| 18 | |||
| 19 | type t = editor | ||
| 20 | |||
| 21 | let init (c : Config.t) : editor = | ||
| 22 | { | ||
| 23 | term = Terminal.init (); | ||
| 24 | mode = Normal; | ||
| 25 | offset = (0, 0); | ||
| 26 | cursor = (1, 1); | ||
| 27 | buffer = List.hd c.files |> Option.map ~f:Buffer.from_file; | ||
| 28 | pending = Key.stream; | ||
| 29 | i_pending = Command.i_stream; | ||
| 30 | n_pending = Command.n_stream; | ||
| 31 | } | ||
| 32 | |||
| 33 | type 'a action = t -> 'a * t | ||
| 34 | |||
| 35 | module Action = struct | ||
| 36 | let run ~editor action = action () editor | ||
| 37 | let eval ~editor action = run ~editor action |> fst | ||
| 38 | let exec ~editor action = run ~editor action |> snd | ||
| 39 | |||
| 40 | include Applicative.Make (struct | ||
| 41 | type 'a t = 'a action | ||
| 42 | |||
| 43 | let return a s = (a, s) | ||
| 44 | |||
| 45 | let apply f a e = | ||
| 46 | let f, e' = f e in | ||
| 47 | let x, e'' = a e' in | ||
| 48 | (f x, e'') | ||
| 49 | |||
| 50 | let map = `Define_using_apply | ||
| 51 | end) | ||
| 52 | |||
| 53 | include Monad.Make (struct | ||
| 54 | type 'a t = 'a action | ||
| 55 | |||
| 56 | let return x s = (x, s) | ||
| 57 | |||
| 58 | let bind a ~f x = | ||
| 59 | let y, a' = a x in | ||
| 60 | f y a' | ||
| 61 | |||
| 62 | let map = `Define_using_bind | ||
| 63 | end) | ||
| 64 | |||
| 65 | let get e = (e, e) | ||
| 66 | let put e _ = ((), e) | ||
| 67 | let modify ~f e = ((), f e) | ||
| 68 | |||
| 69 | let update_cursor = | ||
| 70 | let aux e = | ||
| 71 | let dx, dy = e.offset and rs, cs = e.term.size in | ||
| 72 | match Option.map ~f:Buffer.cursor e.buffer with | ||
| 73 | | None -> { e with cursor = (1, 1); offset = (0, 0) } | ||
| 74 | | Some (cx, cy) -> | ||
| 75 | let dx' = Int.clamp_exn ~min:(cx - rs) ~max:cx dx | ||
| 76 | and dy' = Int.clamp_exn ~min:(cy - cs) ~max:cy dy in | ||
| 77 | { e with cursor = (cx - dx' + 1, cy - dy' + 1); offset = (dx', dy') } | ||
| 78 | in | ||
| 79 | modify ~f:aux | ||
| 80 | |||
| 81 | let get_mode s = (s.mode, s) | ||
| 82 | let set_mode m s = ((), { s with mode = m }) | ||
| 83 | let get_focused_buffer e = (e.buffer, e) | ||
| 84 | let set_focused_buffer b e = ((), { e with buffer = Some b }) | ||
| 85 | |||
| 86 | let on_focused_buffer f = | ||
| 87 | let f e = { e with buffer = Option.map ~f e.buffer } in | ||
| 88 | modify ~f *> update_cursor | ||
| 89 | |||
| 90 | let on_focused_buffer_or_new f = | ||
| 91 | (get_focused_buffer | ||
| 92 | >>| Option.value ~default:Buffer.empty | ||
| 93 | >>| f >>= set_focused_buffer) | ||
| 94 | *> update_cursor | ||
| 95 | |||
| 96 | let render = | ||
| 97 | let welcome (r, c) = | ||
| 98 | let open Text in | ||
| 99 | let hfill = ' ' and vfill = Sequence.empty in | ||
| 100 | "Welcome to the sand editor!" |> String.to_list |> Sequence.of_list | ||
| 101 | |> center ~fill:hfill c |> Sequence.singleton |> center ~fill:vfill r | ||
| 102 | in | ||
| 103 | let aux e = | ||
| 104 | let x, y = e.offset | ||
| 105 | and ((r, c) as size) = e.term.size | ||
| 106 | and fill = Sequence.singleton '~' in | ||
| 107 | let view = | ||
| 108 | Option.( | ||
| 109 | e.buffer >>| Buffer.view x y r c | ||
| 110 | |> value ~default:(welcome size) | ||
| 111 | |> Text.extend ~fill r) | ||
| 112 | in | ||
| 113 | Terminal.redraw view e.cursor | ||
| 114 | in | ||
| 115 | get >>| aux | ||
| 116 | |||
| 117 | (* TODO: save logic *) | ||
| 118 | let quit n = Stdlib.exit n | ||
| 119 | let noop = return () | ||
| 120 | end | ||
| 121 | |||
| 122 | let move ?(up = 0) ?(down = 0) ?(left = 0) ?(right = 0) (x, y) = | ||
| 123 | (x + down - up, y + right - left) | ||
| 124 | |||
| 125 | let move_to ?x ?y (sx, sy) = Option.(value x ~default:sx, value y ~default:sy) | ||
| 126 | |||
| 127 | (* let get_next_command s = *) | ||
| 128 | (* match Sequence.next s.pending with *) | ||
| 129 | (* | None -> (None, s) *) | ||
| 130 | (* | Some (h, t) -> (Some h, { s with pending = t }) *) | ||
| 131 | |||
| 132 | (* let handle_insert_key = *) | ||
| 133 | (* let open Action in *) | ||
| 134 | (* let open Key in *) | ||
| 135 | (* function *) | ||
| 136 | (* | Arrow_down -> Buffer.Action.down |> on_focused_buffer *) | ||
| 137 | (* | Arrow_left -> Buffer.Action.left |> on_focused_buffer *) | ||
| 138 | (* | Arrow_right -> Buffer.Action.right |> on_focused_buffer *) | ||
| 139 | (* | Arrow_up -> Buffer.Action.up |> on_focused_buffer *) | ||
| 140 | (* | Backspace -> Buffer.Action.delete_before |> on_focused_buffer *) | ||
| 141 | (* | Ctrl 'Q' -> quit 0 *) | ||
| 142 | (* | Delete -> Buffer.Action.delete_after |> on_focused_buffer *) | ||
| 143 | (* | Enter -> Buffer.Action.newline |> on_focused_buffer *) | ||
| 144 | (* | Esc -> (Buffer.Action.left |> on_focused_buffer) *> set_mode Normal *) | ||
| 145 | (* | Key k -> Buffer.Action.insert k |> on_focused_buffer *) | ||
| 146 | (* | _ -> noop *) | ||
| 147 | |||
| 148 | (* let handle_normal_key = *) | ||
| 149 | (* let open Action in *) | ||
| 150 | (* let open Key in *) | ||
| 151 | (* function *) | ||
| 152 | (* | Arrow_down | Key 'j' -> Buffer.Action.down |> on_focused_buffer *) | ||
| 153 | (* | Arrow_left | Backspace | Key 'h' -> Buffer.Action.left |> on_focused_buffer *) | ||
| 154 | (* | Arrow_right | Key ' ' | Key 'l' -> Buffer.Action.right |> on_focused_buffer *) | ||
| 155 | (* | Arrow_up | Key 'k' -> Buffer.Action.up |> on_focused_buffer *) | ||
| 156 | (* | Ctrl 'Q' -> quit 0 *) | ||
| 157 | (* | Key '0' -> Buffer.Action.bol |> on_focused_buffer_or_new *) | ||
| 158 | (* | Key 'A' -> *) | ||
| 159 | (* (Buffer.Action.eol |> on_focused_buffer_or_new) *> set_mode Insert *) | ||
| 160 | (* | Key 'a' -> *) | ||
| 161 | (* (Buffer.Action.right |> on_focused_buffer_or_new) *> set_mode Insert *) | ||
| 162 | (* | Key 'G' -> Buffer.Action.eof |> on_focused_buffer_or_new *) | ||
| 163 | (* | Key 'I' -> *) | ||
| 164 | (* noop *) | ||
| 165 | (* (1* (Buffer.Action.bol |> on_focused_buffer_or_new) *> set_mode Insert *1) *) | ||
| 166 | (* | Key 'i' -> (Fn.id |> on_focused_buffer_or_new) *> set_mode Insert *) | ||
| 167 | (* | Key 's' -> *) | ||
| 168 | (* (Buffer.Action.delete_after |> on_focused_buffer_or_new) *) | ||
| 169 | (* *> set_mode Insert *) | ||
| 170 | (* | Key 'x' -> Buffer.Action.delete_after |> on_focused_buffer_or_new *) | ||
| 171 | (* | Key 'X' -> Buffer.Action.delete_before |> on_focused_buffer_or_new *) | ||
| 172 | (* | Key '$' -> Buffer.Action.eol |> on_focused_buffer_or_new *) | ||
| 173 | (* | _ -> noop *) | ||
| 174 | |||
| 175 | (* let handle_next_command = *) | ||
| 176 | (* let f m = function *) | ||
| 177 | (* | None -> Action.return () *) | ||
| 178 | (* | Some k -> ( *) | ||
| 179 | (* match m with *) | ||
| 180 | (* | Insert -> handle_insert_key k *) | ||
| 181 | (* | Normal -> handle_normal_key k) *) | ||
| 182 | (* in *) | ||
| 183 | (* Action.(map2 ~f get_mode get_next_command |> join) *) | ||
| 184 | |||
| 185 | let handle_insert_command = | ||
| 186 | let open Command in | ||
| 187 | let open Action in | ||
| 188 | function | ||
| 189 | | Simple Arrow_down -> Buffer.Action.down |> on_focused_buffer | ||
| 190 | | Simple Arrow_left -> Buffer.Action.left |> on_focused_buffer | ||
| 191 | | Simple Arrow_right -> Buffer.Action.right |> on_focused_buffer | ||
| 192 | | Simple Arrow_up -> Buffer.Action.up |> on_focused_buffer | ||
| 193 | | Simple Backspace -> Buffer.Action.delete_before ~n:1 |> on_focused_buffer | ||
| 194 | | Simple (Ctrl 'Q') -> quit 0 | ||
| 195 | | Simple Delete -> Buffer.Action.delete_after ~n:1 |> on_focused_buffer | ||
| 196 | | Simple Enter -> Buffer.Action.newline |> on_focused_buffer | ||
| 197 | | Simple Esc -> (Buffer.Action.left |> on_focused_buffer) *> set_mode Normal | ||
| 198 | | Type k -> Buffer.Action.insert k |> on_focused_buffer | ||
| 199 | | _ -> noop | ||
| 200 | |||
| 201 | let handle_normal_command = | ||
| 202 | let open Command in | ||
| 203 | let open Action in | ||
| 204 | function | ||
| 205 | (* Movements *) | ||
| 206 | | Chord (_, n, Noop, _, Down) -> Buffer.Action.down ?n |> on_focused_buffer | ||
| 207 | | Chord (_, n, Noop, _, Left) -> Buffer.Action.left ?n |> on_focused_buffer | ||
| 208 | | Chord (_, n, Noop, _, Right) -> Buffer.Action.right ?n |> on_focused_buffer | ||
| 209 | | Chord (_, n, Noop, _, Up) -> Buffer.Action.up ?n |> on_focused_buffer | ||
| 210 | | Simple (Ctrl 'Q') -> quit 0 | ||
| 211 | (* | Key '0' -> Buffer.Action.bol |> on_focused_buffer_or_new *) | ||
| 212 | | Simple (Key 'A') -> | ||
| 213 | (Buffer.Action.eol |> on_focused_buffer_or_new) *> set_mode Insert | ||
| 214 | | Simple (Key 'a') -> | ||
| 215 | (Buffer.Action.right |> on_focused_buffer_or_new) *> set_mode Insert | ||
| 216 | (* | Key 'G' -> Buffer.Action.eof |> on_focused_buffer_or_new *) | ||
| 217 | (* | Key 'I' -> noop *) | ||
| 218 | | Simple (Key 'i') -> (Fn.id |> on_focused_buffer_or_new) *> set_mode Insert | ||
| 219 | (* | Key 's' -> *) | ||
| 220 | (* (Buffer.Action.delete_after |> on_focused_buffer_or_new) *) | ||
| 221 | (* *> set_mode Insert *) | ||
| 222 | (* | Key 'x' -> Buffer.Action.delete_after |> on_focused_buffer_or_new *) | ||
| 223 | (* | Key 'X' -> Buffer.Action.delete_before |> on_focused_buffer_or_new *) | ||
| 224 | (* | Key '$' -> Buffer.Action.eol |> on_focused_buffer_or_new *) | ||
| 225 | | Shortcut (_, n, Change, Line) -> | ||
| 226 | let n = Option.value ~default:1 n - 1 in | ||
| 227 | (Buffer.Action.(delete_lines ~n &> bol &> delete_to_eol) | ||
| 228 | |> on_focused_buffer_or_new) | ||
| 229 | *> set_mode Insert | ||
| 230 | | Shortcut (_, _, Change, To_eol) -> | ||
| 231 | (Buffer.Action.delete_to_eol |> on_focused_buffer_or_new) | ||
| 232 | *> set_mode Insert | ||
| 233 | | Shortcut (_, _, Change, To_bol) -> | ||
| 234 | (Buffer.Action.delete_to_bol |> on_focused_buffer_or_new) | ||
| 235 | *> set_mode Insert | ||
| 236 | (* Delete *) | ||
| 237 | | Chord (_, n1, Delete, n2, Down) -> | ||
| 238 | let n = Option.((value ~default:1 n1 * value ~default:1 n2) + 1) in | ||
| 239 | Buffer.Action.delete_lines ~n |> on_focused_buffer_or_new | ||
| 240 | | Chord (_, n1, Delete, n2, Left) -> | ||
| 241 | let n = Option.(value ~default:1 n1 * value ~default:1 n2) in | ||
| 242 | Buffer.Action.delete_before ~n |> on_focused_buffer_or_new | ||
| 243 | | Chord (_, n1, Delete, n2, Right) -> | ||
| 244 | let n = Option.(value ~default:1 n1 * value ~default:1 n2) in | ||
| 245 | Buffer.Action.delete_after ~n |> on_focused_buffer_or_new | ||
| 246 | | Chord (_, n1, Delete, n2, Up) -> | ||
| 247 | let n = Option.((value ~default:1 n1 * value ~default:1 n2) + 1) in | ||
| 248 | Buffer.Action.delete_lines_before ~n |> on_focused_buffer_or_new | ||
| 249 | | Shortcut (_, n, Delete, Line) -> | ||
| 250 | Buffer.Action.delete_lines ~n:Option.(value ~default:1 n) | ||
| 251 | |> on_focused_buffer_or_new | ||
| 252 | | Shortcut (_, _, Delete, To_eol) -> | ||
| 253 | Buffer.Action.delete_to_eol |> on_focused_buffer_or_new | ||
| 254 | | Shortcut (_, _, Delete, To_bol) -> | ||
| 255 | Buffer.Action.delete_to_bol |> on_focused_buffer_or_new | ||
| 256 | | _ -> noop | ||
| 257 | |||
| 258 | let handle_next_command2 m e = | ||
| 259 | match m with | ||
| 260 | | Insert -> ( | ||
| 261 | match Sequence.next e.i_pending with | ||
| 262 | | None -> ((), e) | ||
| 263 | | Some (h, t) -> handle_insert_command h { e with i_pending = t }) | ||
| 264 | | Normal -> ( | ||
| 265 | match Sequence.next e.n_pending with | ||
| 266 | | None -> ((), e) | ||
| 267 | | Some (h, t) -> handle_normal_command h { e with n_pending = t }) | ||
| 268 | |||
| 269 | let handle_next_command2 = | ||
| 270 | let open Action in | ||
| 271 | get_mode >>= handle_next_command2 | ||
diff --git a/lib/editorBuffer.ml b/lib/editorBuffer.ml new file mode 100644 index 0000000..5104549 --- /dev/null +++ b/lib/editorBuffer.ml | |||
| @@ -0,0 +1,72 @@ | |||
| 1 | open Base | ||
| 2 | open Zipper | ||
| 3 | open Util | ||
| 4 | |||
| 5 | type kind = File of string | No_name | Scratch | ||
| 6 | type error = No_such_file | Other | ||
| 7 | type buffer = { kind : kind; content : (char zipper zipper, error) Result.t } | ||
| 8 | type t = buffer | ||
| 9 | |||
| 10 | let empty = | ||
| 11 | { kind = No_name; content = empty |> push_after empty |> Result.return } | ||
| 12 | |||
| 13 | module Action = struct | ||
| 14 | let on_content f b = { b with content = Result.map ~f b.content } | ||
| 15 | |||
| 16 | let up, down, left, right = | ||
| 17 | let vertical f ?(n = 1) = | ||
| 18 | on_content (fun z -> | ||
| 19 | let col = focus_or ~default:Zipper.empty z |> left_length in | ||
| 20 | Fn.apply_n_times ~n f z |> map_focus (goto col)) | ||
| 21 | and horizontal f ?(n = 1) = | ||
| 22 | Fn.apply_n_times ~n (map_focus f) |> on_content | ||
| 23 | in | ||
| 24 | (vertical left, vertical right, horizontal left, horizontal right) | ||
| 25 | |||
| 26 | let bol = map_focus far_left |> on_content | ||
| 27 | let eol = map_focus far_right |> on_content | ||
| 28 | let bof = far_left |> on_content | ||
| 29 | let eof = far_right |> on_content | ||
| 30 | let insert k = map_focus (push k) |> on_content | ||
| 31 | let delete_after ~n = Fn.apply_n_times ~n (map_focus pop_after) |> on_content | ||
| 32 | let delete_before ~n = Fn.apply_n_times ~n (map_focus pop) |> on_content | ||
| 33 | let delete_to_eol = map_focus (split &> fst) |> on_content | ||
| 34 | let delete_to_bol = map_focus (split &> snd) |> on_content | ||
| 35 | let delete_lines ~n = Fn.apply_n_times ~n pop_after |> on_content | ||
| 36 | |||
| 37 | let delete_lines_before ~n = | ||
| 38 | on_content (fun z -> pop_after z |> Fn.apply_n_times ~n:(n - 1) pop_before) | ||
| 39 | |||
| 40 | let newline = | ||
| 41 | let aux z = | ||
| 42 | let l1, l2 = focus_or ~default:Zipper.empty z |> split in | ||
| 43 | push_before l1 z |> map_focus (Fn.const l2) | ||
| 44 | in | ||
| 45 | on_content aux | ||
| 46 | |||
| 47 | (* let save_history_to ?(clear = true) r = () *) | ||
| 48 | end | ||
| 49 | |||
| 50 | let from_file f = | ||
| 51 | let lines = Stdio.In_channel.read_lines f in | ||
| 52 | let line_to_zipper l = String.to_list l |> Sequence.of_list |> of_seq in | ||
| 53 | let content = Sequence.(of_list lines |> map ~f:line_to_zipper) |> of_seq in | ||
| 54 | { kind = File f; content = Ok content } | ||
| 55 | |||
| 56 | let cursor b = | ||
| 57 | let open Option in | ||
| 58 | let x = Result.(map ~f:left_length b.content |> ok |> value ~default:0) | ||
| 59 | and y = | ||
| 60 | Result.(map ~f:focus b.content |> ok) | ||
| 61 | |> join |> map ~f:left_length |> value ~default:0 | ||
| 62 | in | ||
| 63 | (x, y) | ||
| 64 | |||
| 65 | let view x y h w b = | ||
| 66 | match b.content with | ||
| 67 | | Error _ -> Sequence.empty | ||
| 68 | | Ok z -> | ||
| 69 | let cx, _ = cursor b in | ||
| 70 | context ~b:(cx - x) ~a:(x + h - cx) z | ||
| 71 | |> to_seq | ||
| 72 | |> Sequence.map ~f:(window ~from:y ~len:w) | ||
diff --git a/lib/key.ml b/lib/key.ml new file mode 100644 index 0000000..85aa282 --- /dev/null +++ b/lib/key.ml | |||
| @@ -0,0 +1,72 @@ | |||
| 1 | open Base | ||
| 2 | |||
| 3 | type key = | ||
| 4 | | Arrow_down | ||
| 5 | | Arrow_left | ||
| 6 | | Arrow_right | ||
| 7 | | Arrow_up | ||
| 8 | | Backspace | ||
| 9 | | Ctrl of char | ||
| 10 | | Delete | ||
| 11 | | End | ||
| 12 | | Enter | ||
| 13 | | Esc | ||
| 14 | | Home | ||
| 15 | | Key of char | ||
| 16 | | Nul | ||
| 17 | | Page_down | ||
| 18 | | Page_up | ||
| 19 | |||
| 20 | type t = key | ||
| 21 | |||
| 22 | let ctrl c = Ctrl c | ||
| 23 | let key c = Key c | ||
| 24 | |||
| 25 | let of_char = function | ||
| 26 | | '\000' -> Nul | ||
| 27 | | '\013' -> Enter | ||
| 28 | | '\027' -> Esc | ||
| 29 | | '\127' -> Backspace | ||
| 30 | | c when Char.(c < ' ') -> Char.to_int c + 64 |> Char.of_int_exn |> ctrl | ||
| 31 | | c -> Key c | ||
| 32 | |||
| 33 | let stream = | ||
| 34 | let step s c = | ||
| 35 | let open Sequence.Step in | ||
| 36 | let escaped = function | ||
| 37 | | 'A' -> Some Arrow_up | ||
| 38 | | 'B' -> Some Arrow_down | ||
| 39 | | 'C' -> Some Arrow_right | ||
| 40 | | 'D' -> Some Arrow_left | ||
| 41 | | 'F' -> Some End | ||
| 42 | | 'H' -> Some Home | ||
| 43 | | _ -> None | ||
| 44 | and tilda = function | ||
| 45 | | '1' -> Some Home | ||
| 46 | | '3' -> Some Delete | ||
| 47 | | '4' -> Some End | ||
| 48 | | '5' -> Some Page_up | ||
| 49 | | '6' -> Some Page_down | ||
| 50 | | '7' -> Some Home | ||
| 51 | | '8' -> Some End | ||
| 52 | | _ -> None | ||
| 53 | in | ||
| 54 | match (s, c) with | ||
| 55 | | `start, Some '\027' -> Skip { state = `esc } | ||
| 56 | | `esc, None -> Yield { value = Esc; state = `start } | ||
| 57 | | `esc, Some '[' | `escaped, Some 'O' -> Skip { state = `escaped } | ||
| 58 | | `escaped, Some i when Char.(i = '1' || ('3' <= i && i <= '8')) -> | ||
| 59 | Skip { state = `tilda i } | ||
| 60 | | `escaped, c -> ( | ||
| 61 | match Option.(c >>= escaped) with | ||
| 62 | | None -> Skip { state = `state } | ||
| 63 | | Some c -> Yield { value = c; state = `start }) | ||
| 64 | | `tilda i, Some '~' -> ( | ||
| 65 | match tilda i with | ||
| 66 | | None -> Skip { state = `start } | ||
| 67 | | Some k -> Yield { value = k; state = `start }) | ||
| 68 | | `esc, Some _ | `tilda _, _ -> Skip { state = `start } | ||
| 69 | | _, None -> Skip { state = `start } | ||
| 70 | | _, Some c -> Yield { value = of_char c; state = `start } | ||
| 71 | in | ||
| 72 | Sequence.unfold_with ~init:`start ~f:step Terminal.char_stream | ||
diff --git a/lib/modes.ml b/lib/modes.ml new file mode 100644 index 0000000..3d0e354 --- /dev/null +++ b/lib/modes.ml | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | type mode = Normal | Insert | ||
| 2 | type t = mode | ||
| 3 | type state = int | ||
| 4 | type 'a state_monad = state -> 'a * state | ||
| 5 | |||
| 6 | let run (f : 'a state_monad) (s : state) : 'a = f s |> fst | ||
| 7 | let return (a : 'a) : 'a state_monad = fun s -> (a, s) | ||
| 8 | |||
| 9 | let ( >>= ) (f : 'a state_monad) (g : 'a -> 'b state_monad) : 'b state_monad = | ||
| 10 | fun s -> | ||
| 11 | let a, s' = f s in | ||
| 12 | g a s' | ||
| 13 | |||
| 14 | let draw () : unit state_monad = return () | ||
| 15 | let get_keypress () : char state_monad = return 'a' | ||
| 16 | let handle_key (_ : char) : unit state_monad = return () | ||
| 17 | let rec loop () = () |> draw >>= get_keypress >>= handle_key >>= loop | ||
| 18 | let test = run (loop ()) 0 | ||
diff --git a/lib/terminal.ml b/lib/terminal.ml new file mode 100644 index 0000000..408f7a8 --- /dev/null +++ b/lib/terminal.ml | |||
| @@ -0,0 +1,130 @@ | |||
| 1 | open Base | ||
| 2 | open Unix | ||
| 3 | open Util | ||
| 4 | |||
| 5 | type terminal_size = int * int | ||
| 6 | type state = { tio : terminal_io; size : terminal_size } | ||
| 7 | |||
| 8 | let escape ?(prefix = "") ?(args = []) op = | ||
| 9 | let open Bytes in | ||
| 10 | let args = List.map ~f:Int.to_string args |> String.concat ~sep:";" in | ||
| 11 | let lenp = String.length prefix in | ||
| 12 | let lena = String.length args in | ||
| 13 | let lenb = 3 + lena + lenp in | ||
| 14 | let dst = Bytes.create lenb in | ||
| 15 | set dst 0 '\x1b'; | ||
| 16 | set dst 1 '['; | ||
| 17 | From_string.blit ~src:prefix ~src_pos:0 ~dst ~dst_pos:2 ~len:lenp; | ||
| 18 | From_string.blit ~src:args ~src_pos:0 ~dst ~dst_pos:(2 + lenp) ~len:lena; | ||
| 19 | set dst (length dst - 1) op; | ||
| 20 | sequence_of_bytes dst | ||
| 21 | |||
| 22 | let clear_screen = escape 'J' ~args:[ 2 ] | ||
| 23 | let clear_to_eol = escape 'K' | ||
| 24 | let move_cursor x y = escape 'H' ~args:[ x; y ] | ||
| 25 | let move_down n = escape 'B' ~args:[ n ] | ||
| 26 | let move_right n = escape 'C' ~args:[ n ] | ||
| 27 | let query_cursor_pos = escape 'n' ~args:[ 6 ] | ||
| 28 | let reset_cursor = move_cursor 1 1 | ||
| 29 | |||
| 30 | let show_cursor show = | ||
| 31 | let cmd = if show then 'h' else 'l' in | ||
| 32 | escape cmd ~prefix:"?" ~args:[ 25 ] | ||
| 33 | |||
| 34 | let input_bytes = Bytes.create 1 | ||
| 35 | |||
| 36 | let get_char () = | ||
| 37 | let syscall () = read stdin input_bytes 0 1 in | ||
| 38 | match handle_unix_error syscall () with | ||
| 39 | | 0 -> None | ||
| 40 | | _ -> Some (Bytes.get input_bytes 0) | ||
| 41 | |||
| 42 | let char_stream : char option Sequence.t = | ||
| 43 | Sequence.unfold ~init:() ~f:(fun s -> Some (get_char (), s)) | ||
| 44 | |||
| 45 | let write_seq = | ||
| 46 | let syscall s = | ||
| 47 | let buf = Sequence.to_list s |> Bytes.of_char_list in | ||
| 48 | single_write stdout buf 0 (Bytes.length buf) |> ignore | ||
| 49 | in | ||
| 50 | handle_unix_error syscall | ||
| 51 | |||
| 52 | let write_lines (lines : char Sequence.t Sequence.t) = | ||
| 53 | let crnl = Sequence.of_list [ '\r'; '\n' ] in | ||
| 54 | let clear s = Sequence.append s clear_to_eol in | ||
| 55 | let syscall seq = | ||
| 56 | let buf = | ||
| 57 | Sequence.(map ~f:clear seq |> intersperse ~sep:crnl |> concat |> to_list) | ||
| 58 | |> Bytes.of_char_list | ||
| 59 | in | ||
| 60 | single_write stdout buf 0 (Bytes.length buf) |> ignore | ||
| 61 | in | ||
| 62 | handle_unix_error syscall lines | ||
| 63 | |||
| 64 | let cmds_to_sequence l = Sequence.(of_list l |> concat) | ||
| 65 | |||
| 66 | let restore_screen () = | ||
| 67 | cmds_to_sequence [ clear_screen; reset_cursor; show_cursor true ] |> write_seq | ||
| 68 | |||
| 69 | let redraw screen (x, y) = | ||
| 70 | let pre = cmds_to_sequence [ show_cursor false; reset_cursor ] | ||
| 71 | and post = cmds_to_sequence [ move_cursor x y; show_cursor true ] in | ||
| 72 | write_seq pre; | ||
| 73 | write_lines screen; | ||
| 74 | write_seq post | ||
| 75 | |||
| 76 | let get_state () = { tio = handle_unix_error tcgetattr stdin; size = (-1, -1) } | ||
| 77 | |||
| 78 | let size = | ||
| 79 | let query () = | ||
| 80 | cmds_to_sequence [ move_right 999; move_down 999; query_cursor_pos ] | ||
| 81 | |> write_seq | ||
| 82 | and get_reply () = | ||
| 83 | Sequence.( | ||
| 84 | char_stream | ||
| 85 | |> take_while ~f:Option.is_some | ||
| 86 | |> Fn.flip drop_eagerly 2 (* Drop escape sequence '<esc>[' *) | ||
| 87 | |> map ~f:(Option.value ~default:'R') | ||
| 88 | |> take_while ~f:(fun c -> Char.(c <> 'R')) | ||
| 89 | |> to_list |> String.of_char_list |> Stdlib.Scanf.sscanf | ||
| 90 | |> fun scanner -> scanner "%d;%d" (fun a b -> (a, b))) | ||
| 91 | in | ||
| 92 | query &> get_reply | ||
| 93 | |||
| 94 | let enable_raw_mode tio = | ||
| 95 | let syscall () = | ||
| 96 | tcsetattr stdin TCSAFLUSH | ||
| 97 | { | ||
| 98 | tio with | ||
| 99 | c_brkint = false; | ||
| 100 | c_csize = 8; | ||
| 101 | c_echo = false; | ||
| 102 | c_echonl = false; | ||
| 103 | c_icanon = false; | ||
| 104 | c_icrnl = false; | ||
| 105 | c_ignbrk = false; | ||
| 106 | c_igncr = false; | ||
| 107 | c_inlcr = false; | ||
| 108 | c_inpck = false; | ||
| 109 | c_isig = false; | ||
| 110 | c_istrip = false; | ||
| 111 | c_ixon = false; | ||
| 112 | c_opost = false; | ||
| 113 | c_parenb = false; | ||
| 114 | c_parmrk = false; | ||
| 115 | c_vmin = 0; | ||
| 116 | c_vtime = 1; | ||
| 117 | } | ||
| 118 | in | ||
| 119 | handle_unix_error syscall | ||
| 120 | |||
| 121 | let restore_status tio = | ||
| 122 | let syscall () = tcsetattr stdin TCSAFLUSH tio in | ||
| 123 | handle_unix_error syscall | ||
| 124 | |||
| 125 | let init () = | ||
| 126 | let state = get_state () in | ||
| 127 | enable_raw_mode state.tio (); | ||
| 128 | restore_status state.tio |> Stdlib.at_exit; | ||
| 129 | restore_screen |> Stdlib.at_exit; | ||
| 130 | { state with size = size () } | ||
diff --git a/lib/terminal.mli b/lib/terminal.mli new file mode 100644 index 0000000..0fd11ed --- /dev/null +++ b/lib/terminal.mli | |||
| @@ -0,0 +1,67 @@ | |||
| 1 | (** A module to simplify communication with the underlying terminal | ||
| 2 | emulator. *) | ||
| 3 | |||
| 4 | open Base | ||
| 5 | open Unix | ||
| 6 | |||
| 7 | type terminal_size = int * int | ||
| 8 | (** Size of the terminal window. *) | ||
| 9 | |||
| 10 | type state = { | ||
| 11 | tio : terminal_io; (** Status flags for the terminal window *) | ||
| 12 | size : terminal_size; (** Size of the terminal window *) | ||
| 13 | } | ||
| 14 | (** Global state of the terminal window. *) | ||
| 15 | |||
| 16 | val get_char : unit -> char option | ||
| 17 | (** Non-blocking request for a keypress. | ||
| 18 | Use {!val:Terminal.char_stream} for an infinite sequence of input | ||
| 19 | bytes. | ||
| 20 | |||
| 21 | @return A [char] if a key was pressed, nothing otherwise. *) | ||
| 22 | |||
| 23 | val char_stream : char option Sequence.t | ||
| 24 | (** The infinite stream of input bytes from stdin. | ||
| 25 | Returns [None] if no key was pressed; this is to be able to | ||
| 26 | interpret the absense of a keypress as an action. *) | ||
| 27 | |||
| 28 | val write_seq : char Sequence.t -> unit | ||
| 29 | (** Write a sequence of strings to standard output. | ||
| 30 | |||
| 31 | @param seq The sequence of strings to output. *) | ||
| 32 | |||
| 33 | val restore_screen : unit -> unit | ||
| 34 | (** Clear screen and show cursor. Meant to be called on exit. *) | ||
| 35 | |||
| 36 | val redraw : char Sequence.t Sequence.t -> int * int -> unit | ||
| 37 | (** Redraw the screen, using the provided sequence of lines. | ||
| 38 | |||
| 39 | @param seq A sequence of lines. | ||
| 40 | @param cur Cursor position. *) | ||
| 41 | |||
| 42 | val get_state : unit -> state | ||
| 43 | (** Get current state for the terminal window *) | ||
| 44 | |||
| 45 | val size : unit -> int * int | ||
| 46 | (** Compute the current size of the terminal. | ||
| 47 | |||
| 48 | This is done by moving the cursor to the far bottom right position | ||
| 49 | and querying for the cursor position using the appropriate escape | ||
| 50 | sequences. | ||
| 51 | |||
| 52 | @return size of the terminal window in terms of character rows and columns. *) | ||
| 53 | |||
| 54 | val enable_raw_mode : terminal_io -> unit -> unit | ||
| 55 | (** Turn on raw mode by overriding the correct terminal flags. | ||
| 56 | This is done according to [man 3 termios]. | ||
| 57 | |||
| 58 | @param tio The current status of the terminal for [stdin]. *) | ||
| 59 | |||
| 60 | val restore_status : terminal_io -> unit -> unit | ||
| 61 | (** Override the terminal status. | ||
| 62 | |||
| 63 | @param tio The new terminal status for [stdin]. *) | ||
| 64 | |||
| 65 | val init : unit -> state | ||
| 66 | (** Turns on raw mode and makes sure to restore the previous terminal | ||
| 67 | status on exit. *) | ||
diff --git a/lib/text.ml b/lib/text.ml new file mode 100644 index 0000000..ff1f727 --- /dev/null +++ b/lib/text.ml | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | open Base | ||
| 2 | open Sequence | ||
| 3 | open Sequence.Infix | ||
| 4 | |||
| 5 | let center ~fill n text = | ||
| 6 | let padding = repeat fill and len_t = length text in | ||
| 7 | let len_p = (n - len_t) / 2 in | ||
| 8 | take (take padding len_p @ text @ padding) n | ||
| 9 | |||
| 10 | let extend ~fill n text = take (text @ repeat fill) n | ||
diff --git a/lib/util.ml b/lib/util.ml new file mode 100644 index 0000000..9ad3b59 --- /dev/null +++ b/lib/util.ml | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | (** The infamous [Util] module. A graveyard of broken expressions. *) | ||
| 2 | |||
| 3 | open Base | ||
| 4 | |||
| 5 | (** [f &> g] composes [g] with [f] to obrain [fun x -> g (f x)]; | ||
| 6 | in other words, it applies [f] {i and then} [g]. | ||
| 7 | |||
| 8 | @param f the first funtion to apply. | ||
| 9 | @param g the second function to apply. | ||
| 10 | @return the composition of [g] with [f]. *) | ||
| 11 | let ( &> ) f g = Fn.compose g f | ||
| 12 | |||
| 13 | (** Turn a sequence of bytes into a sequence. | ||
| 14 | |||
| 15 | @param b the input bytes. | ||
| 16 | @return a sequence of bytes. *) | ||
| 17 | let sequence_of_bytes (b : Bytes.t) : char Sequence.t = | ||
| 18 | let open Sequence.Generator in | ||
| 19 | let traverse b = | ||
| 20 | let len = Bytes.length b in | ||
| 21 | let rec loop i _ = | ||
| 22 | if i >= len then return () else yield (Bytes.get b i) >>= loop (i + 1) | ||
| 23 | in | ||
| 24 | loop 0 () | ||
| 25 | in | ||
| 26 | traverse b |> run | ||
diff --git a/lib/zipper.ml b/lib/zipper.ml new file mode 100644 index 0000000..2322a58 --- /dev/null +++ b/lib/zipper.ml | |||
| @@ -0,0 +1,131 @@ | |||
| 1 | (* Module [Zipper]: functional zippers *) | ||
| 2 | |||
| 3 | open Base | ||
| 4 | |||
| 5 | type !+'a zipper = { | ||
| 6 | pos : int; | ||
| 7 | popped : 'a Sequence.t; | ||
| 8 | before : 'a Sequence.t; | ||
| 9 | after : 'a Sequence.t; | ||
| 10 | } | ||
| 11 | |||
| 12 | type !+'a t = 'a zipper | ||
| 13 | |||
| 14 | let empty = | ||
| 15 | { | ||
| 16 | pos = 0; | ||
| 17 | popped = Sequence.empty; | ||
| 18 | before = Sequence.empty; | ||
| 19 | after = Sequence.empty; | ||
| 20 | } | ||
| 21 | |||
| 22 | let before z = z.before | ||
| 23 | let after z = z.after | ||
| 24 | let focus z = after z |> Sequence.next |> Option.map ~f:fst | ||
| 25 | let focus_or z ~default = Option.value ~default (focus z) | ||
| 26 | let history z = z.popped | ||
| 27 | let is_far_left z = before z |> Sequence.is_empty | ||
| 28 | let is_far_right z = after z |> Sequence.is_empty | ||
| 29 | let is_empty z = is_far_left z && is_far_right z | ||
| 30 | let left_length z = z.pos | ||
| 31 | let right_length z = after z |> Sequence.length | ||
| 32 | let length z = left_length z + right_length z | ||
| 33 | |||
| 34 | let left z = | ||
| 35 | match Sequence.next z.before with | ||
| 36 | | None -> z | ||
| 37 | | Some (h, t) -> | ||
| 38 | { | ||
| 39 | z with | ||
| 40 | pos = z.pos - 1; | ||
| 41 | before = t; | ||
| 42 | after = Sequence.shift_right z.after h; | ||
| 43 | } | ||
| 44 | |||
| 45 | let rec left_while f z = | ||
| 46 | if (not (is_far_left z)) && Option.(focus z |> map ~f |> value ~default:false) | ||
| 47 | then left z |> left_while f | ||
| 48 | else z | ||
| 49 | |||
| 50 | let rec far_left z = if is_far_left z then z else z |> left |> far_left | ||
| 51 | |||
| 52 | let right z = | ||
| 53 | match Sequence.next z.after with | ||
| 54 | | None -> z | ||
| 55 | | Some (h, t) -> | ||
| 56 | { | ||
| 57 | z with | ||
| 58 | pos = z.pos + 1; | ||
| 59 | before = Sequence.shift_right z.before h; | ||
| 60 | after = t; | ||
| 61 | } | ||
| 62 | |||
| 63 | let rec right_while f z = | ||
| 64 | if | ||
| 65 | (not (is_far_right z)) && Option.(focus z |> map ~f |> value ~default:false) | ||
| 66 | then right z |> right_while f | ||
| 67 | else z | ||
| 68 | |||
| 69 | let rec far_right z = if is_far_right z then z else z |> right |> far_right | ||
| 70 | |||
| 71 | let goto n z = | ||
| 72 | let n = n - z.pos in | ||
| 73 | let step = if n < 0 then left else right in | ||
| 74 | Fn.apply_n_times ~n:(abs n) step z | ||
| 75 | |||
| 76 | let pop_after z = { z with after = Sequence.drop_eagerly z.after 1 } | ||
| 77 | let pop_before z = if is_far_left z then z else z |> left |> pop_after | ||
| 78 | let pop = pop_before | ||
| 79 | let push_after x z = { z with after = Sequence.shift_right z.after x } | ||
| 80 | |||
| 81 | let push_before x z = | ||
| 82 | { z with pos = z.pos + 1; before = Sequence.shift_right z.before x } | ||
| 83 | |||
| 84 | let push = push_before | ||
| 85 | |||
| 86 | let split z = | ||
| 87 | ( { z with after = Sequence.empty }, | ||
| 88 | { z with pos = 0; before = Sequence.empty } ) | ||
| 89 | |||
| 90 | let iter_before f z = Sequence.iter ~f z.before | ||
| 91 | let iter_after f z = Sequence.iter ~f z.after | ||
| 92 | |||
| 93 | let iter f z = | ||
| 94 | iter_before f z; | ||
| 95 | iter_after f z | ||
| 96 | |||
| 97 | let for_all f z = Sequence.(for_all ~f z.before && for_all ~f z.after) | ||
| 98 | let exists f z = Sequence.(exists ~f z.before || exists ~f z.after) | ||
| 99 | let find_before f z = Sequence.find ~f z.before | ||
| 100 | let find_after f z = Sequence.find ~f z.after | ||
| 101 | let map_before f z = { z with before = Sequence.map ~f z.before } | ||
| 102 | let map_after f z = { z with after = Sequence.map ~f z.after } | ||
| 103 | |||
| 104 | let map_focus f z = | ||
| 105 | match Sequence.next z.after with | ||
| 106 | | None -> z | ||
| 107 | | Some (h, t) -> { z with after = Sequence.shift_right t (f h) } | ||
| 108 | |||
| 109 | let map f z = | ||
| 110 | { z with before = Sequence.map ~f z.before; after = Sequence.map ~f z.after } | ||
| 111 | |||
| 112 | let mapi_before f z = { z with before = Sequence.mapi ~f z.before } | ||
| 113 | let mapi_after f z = { z with after = Sequence.mapi ~f z.after } | ||
| 114 | |||
| 115 | let mapi f z = | ||
| 116 | { | ||
| 117 | z with | ||
| 118 | before = Sequence.mapi ~f z.before; | ||
| 119 | after = Sequence.mapi ~f z.after; | ||
| 120 | } | ||
| 121 | |||
| 122 | let filter_before f z = { z with before = Sequence.filter ~f z.before } | ||
| 123 | let filter_after f z = { z with after = Sequence.filter ~f z.after } | ||
| 124 | let filter p z = z |> filter_before p |> filter_after p | ||
| 125 | let context_before n z = { z with before = Sequence.take z.before n } | ||
| 126 | let context_after n z = { z with after = Sequence.take z.after n } | ||
| 127 | let context ~b ?(a = b) z = z |> context_before b |> context_after a | ||
| 128 | let clear_history z = { z with popped = Sequence.empty } | ||
| 129 | let of_seq s = { empty with after = s } | ||
| 130 | let to_seq z = z |> far_left |> after | ||
| 131 | let window ~from ~len z = goto from z |> context_after len |> after | ||
diff --git a/lib/zipper.mli b/lib/zipper.mli new file mode 100644 index 0000000..c3c79a6 --- /dev/null +++ b/lib/zipper.mli | |||
| @@ -0,0 +1,258 @@ | |||
| 1 | open Base | ||
| 2 | |||
| 3 | (** Linear zippers. | ||
| 4 | |||
| 5 | A zipper represents {b a paused traversal} of a certain data | ||
| 6 | structure. A linear zipper of type ['a zipper] represents a zipper | ||
| 7 | over a sequence of ['a] elements. | ||
| 8 | |||
| 9 | One can access the {b focused element} of the zipper with [focus]. | ||
| 10 | The focus of a zipper is also known as the the {b cursor} of the | ||
| 11 | zipper, and can be moved back and forth with [left] and [right]. | ||
| 12 | |||
| 13 | Elements can be added or removed at the cursor's position with | ||
| 14 | [push] and [pop] operations. Variants are available, e.g., to act | ||
| 15 | before the cursor, with suffix [_before], or after the cursor, with | ||
| 16 | suffix [_after]. *) | ||
| 17 | |||
| 18 | type !+'a zipper | ||
| 19 | (** A zipper is represented as a {b pair} of sequences of type ['a]. The | ||
| 20 | focus of the zipper is the element at the head of the second | ||
| 21 | sequence, if any. | ||
| 22 | |||
| 23 | Note that the first sequence is reversed. Given a sequence | ||
| 24 | [[1; 2; 3; 4; {5}; 6; 7]], where we represent the cursor using | ||
| 25 | curly braces [{_}], its representation as a zipper is | ||
| 26 | [([4; 3; 2; 1], [5; 6; 7])]. *) | ||
| 27 | |||
| 28 | type !+'a t = 'a zipper | ||
| 29 | (** An alias for the ['a zipper] type. *) | ||
| 30 | |||
| 31 | val empty : 'a zipper | ||
| 32 | (** Return an empty zipper *) | ||
| 33 | |||
| 34 | val before : 'a zipper -> 'a Sequence.t | ||
| 35 | (** Return the sequence before the cursor *) | ||
| 36 | |||
| 37 | val after : 'a zipper -> 'a Sequence.t | ||
| 38 | (** Return the sequence after the cursor *) | ||
| 39 | |||
| 40 | val focus : 'a zipper -> 'a option | ||
| 41 | (** Return the focus of the zipper, if any. *) | ||
| 42 | |||
| 43 | val focus_or : 'a zipper -> default:'a -> 'a | ||
| 44 | (** Return the focus of the zipper, or a user-provided default, otherwise. *) | ||
| 45 | |||
| 46 | val history : 'a zipper -> 'a Sequence.t | ||
| 47 | (** Returns the sequence of elements [pop]ped so far from the zipper. *) | ||
| 48 | |||
| 49 | val is_far_left : 'a zipper -> bool | ||
| 50 | (** Return whether the cursor is at the beginning of the zipper. *) | ||
| 51 | |||
| 52 | val is_far_right : 'a zipper -> bool | ||
| 53 | (** Return whether the cursor is at the end of the zipper. *) | ||
| 54 | |||
| 55 | val is_empty : 'a zipper -> bool | ||
| 56 | (** Return whether the zipper is empty. *) | ||
| 57 | |||
| 58 | val left_length : 'a zipper -> int | ||
| 59 | (** Return the number of elements before the cursor. *) | ||
| 60 | |||
| 61 | val right_length : 'a zipper -> int | ||
| 62 | (** Return the number of elements after the cursor. *) | ||
| 63 | |||
| 64 | val length : 'a zipper -> int | ||
| 65 | (** Return the length of the zipper. *) | ||
| 66 | |||
| 67 | (** {1 Moving the cursor} *) | ||
| 68 | |||
| 69 | val left : 'a zipper -> 'a zipper | ||
| 70 | (** Move the cursor one step to the left, if possible. | ||
| 71 | Calling [left z], | ||
| 72 | |||
| 73 | - if [z] is [([3; 2; 1], [4; 5])], the result is [([2; 1], [3; 4; 5])], | ||
| 74 | - if [z] is [([], [1; 2; 3])], the result is [([], [1; 2; 3])]. | ||
| 75 | *) | ||
| 76 | |||
| 77 | val left_while : ('a -> bool) -> 'a zipper -> 'a zipper | ||
| 78 | (** [left_while f z] moves the cursor in [z] to the left as long as the | ||
| 79 | predicate [f] is [true] when applied to the focus, or the left end | ||
| 80 | of the zipper is reached. *) | ||
| 81 | |||
| 82 | val far_left : 'a zipper -> 'a zipper | ||
| 83 | (** Move the cursor to the left, as much as possible. *) | ||
| 84 | |||
| 85 | val right : 'a zipper -> 'a zipper | ||
| 86 | (** Move the cursor one step to the right, if possible. | ||
| 87 | Calling [right z], | ||
| 88 | |||
| 89 | - if [z] is [([3; 2; 1], [4; 5])], the result is [([4; 3; 2; 1], [5])], | ||
| 90 | - if [z] is [([1; 2; 3], [])], the result is [([1; 2; 3], [])]. | ||
| 91 | *) | ||
| 92 | |||
| 93 | val right_while : ('a -> bool) -> 'a zipper -> 'a zipper | ||
| 94 | (** [right_while f z] moves the cursor in [z] to the right as long as | ||
| 95 | the predicate [f] is [true] when applied to the focus, or the right | ||
| 96 | end of the zipper is reached. *) | ||
| 97 | |||
| 98 | val far_right : 'a zipper -> 'a zipper | ||
| 99 | (** Move the cursor to the right, as much as possible. *) | ||
| 100 | |||
| 101 | val goto : int -> 'a zipper -> 'a zipper | ||
| 102 | (** Move the cursor to a specific (absolute) position in the zipper. | ||
| 103 | Depending on the current position, it either moves the cursor | ||
| 104 | forward or backwards, without crossing the zipper boundaries. *) | ||
| 105 | |||
| 106 | (** {1 Changes at the cursor} *) | ||
| 107 | |||
| 108 | (** Zippers provide [O(1)] operations performed at the cursor position. | ||
| 109 | This involve [push]ing and [pop]ping elements before and after the | ||
| 110 | cursor. *) | ||
| 111 | |||
| 112 | val pop_after : 'a zipper -> 'a zipper | ||
| 113 | (** Remove the element at the cursor position, if any, and return the | ||
| 114 | modified zipper. Calling [pop_after z], | ||
| 115 | |||
| 116 | - if [z] is [([3; 2; 1], [4; 5])], the result is [([3; 2; 1], [5])], | ||
| 117 | - if [z] is [([1; 2; 3], [])], the result is [([1; 2; 3], [])]. | ||
| 118 | *) | ||
| 119 | |||
| 120 | val pop_before : 'a zipper -> 'a zipper | ||
| 121 | (** Remove the element before the cursor, if any, and return the | ||
| 122 | modified zipper. Calling [pop_before z], | ||
| 123 | |||
| 124 | - if [z] is [([3; 2; 1], [4; 5])], the result is [([2; 1], [4, 5])], | ||
| 125 | - if [z] is [([], [1; 2; 3])], the result is [([], [1; 2; 3])]. | ||
| 126 | *) | ||
| 127 | |||
| 128 | val pop : 'a zipper -> 'a zipper | ||
| 129 | (** [pop] is an alias for [pop_before]. *) | ||
| 130 | |||
| 131 | val push_after : 'a -> 'a zipper -> 'a zipper | ||
| 132 | (** Insert an element after the cursor. | ||
| 133 | Calling [push_after 0 z], if [z] is [([3; 2; 1], [4; 5])], | ||
| 134 | the result is [([3; 2; 1], [0; 4, 5]))], *) | ||
| 135 | |||
| 136 | val push_before : 'a -> 'a zipper -> 'a zipper | ||
| 137 | (** Insert an element before the cursor. Return the modified zipper. | ||
| 138 | Calling [push_before 0 z], if [z] is [([3; 2; 1], [4; 5])], | ||
| 139 | the result is [([0; 3; 2; 1], [4, 5]))]. *) | ||
| 140 | |||
| 141 | val push : 'a -> 'a zipper -> 'a zipper | ||
| 142 | (** [push] is an alias for [push_before]. *) | ||
| 143 | |||
| 144 | val split : 'a zipper -> 'a zipper * 'a zipper | ||
| 145 | (** [split z] splits the zipper in two. [([3; 2; 1], [4; 5])] becomes | ||
| 146 | [([3; 2; 1], []), ([], [4; 5])]. *) | ||
| 147 | |||
| 148 | (** {1 Consuming zippers} *) | ||
| 149 | |||
| 150 | (** Since zippers are based on sequences, iterating over zippers | ||
| 151 | terminates only when both sequences are finite. | ||
| 152 | Unless otherwise stated, these functions will iterate on the | ||
| 153 | elements before the cursor, first. *) | ||
| 154 | |||
| 155 | val iter_before : ('a -> unit) -> 'a zipper -> unit | ||
| 156 | (** [iter_before f z] will call [f x] for all [x], elements before the | ||
| 157 | cursor in [z].*) | ||
| 158 | |||
| 159 | val iter_after : ('a -> unit) -> 'a zipper -> unit | ||
| 160 | (** [iter_after f z] will call [f x] for all [x], elements after the | ||
| 161 | cursor in [z].*) | ||
| 162 | |||
| 163 | val iter : ('a -> unit) -> 'a zipper -> unit | ||
| 164 | (** [iter f z] is equivalent to [iter_before f z; iter_after f z] *) | ||
| 165 | |||
| 166 | val for_all : ('a -> bool) -> 'a zipper -> bool | ||
| 167 | (** [for_all p z] tests whether a predicate [p] is [true] for all | ||
| 168 | elements in a zipper. *) | ||
| 169 | |||
| 170 | val exists : ('a -> bool) -> 'a zipper -> bool | ||
| 171 | (** [exists p z] tests whether at least one element in the zipper is | ||
| 172 | [true] according to the predicate [p]. *) | ||
| 173 | |||
| 174 | val find_before : ('a -> bool) -> 'a zipper -> 'a option | ||
| 175 | (** [find_before p z] will return the first element before the cursor in | ||
| 176 | [z] satisfying the predicate [p], if any. *) | ||
| 177 | |||
| 178 | val find_after : ('a -> bool) -> 'a zipper -> 'a option | ||
| 179 | (** [find_after p z] will return the first element after the cursor in | ||
| 180 | [z] satisfying the predicate [p], if any. *) | ||
| 181 | |||
| 182 | (** {1 Transforming zippers} *) | ||
| 183 | |||
| 184 | (** Since zippers are based on sequences, the functions in this section | ||
| 185 | are lazy; i.e., resulting elements of the zipper are computed only | ||
| 186 | when demanded. *) | ||
| 187 | |||
| 188 | val map_before : ('a -> 'a) -> 'a zipper -> 'a zipper | ||
| 189 | (** Map a function over all elements before the cursor. *) | ||
| 190 | |||
| 191 | val map_after : ('a -> 'a) -> 'a zipper -> 'a zipper | ||
| 192 | (** Map a function over all elements after the cursor. *) | ||
| 193 | |||
| 194 | val map_focus : ('a -> 'a) -> 'a zipper -> 'a zipper | ||
| 195 | (** Map a function over the element focused by the cursor, if any. *) | ||
| 196 | |||
| 197 | val map : ('a -> 'a) -> 'a zipper -> 'a zipper | ||
| 198 | (** Map a function over all elements of a zipper. *) | ||
| 199 | |||
| 200 | val mapi_before : (int -> 'a -> 'a) -> 'a zipper -> 'a zipper | ||
| 201 | (** [mapi_before] is analogous to {!Zipper.map_before}, but the function | ||
| 202 | takes an index and an element. | ||
| 203 | The index indicates the distance of an element from the cursor. *) | ||
| 204 | |||
| 205 | val mapi_after : (int -> 'a -> 'a) -> 'a zipper -> 'a zipper | ||
| 206 | (** [mapi_after] is analogous to {!Zipper.map_after}, but the function | ||
| 207 | takes an index and an element. | ||
| 208 | The index indicates the distance of an element from the cursor. *) | ||
| 209 | |||
| 210 | val mapi : (int -> 'a -> 'a) -> 'a zipper -> 'a zipper | ||
| 211 | (** [mapi] is analogous to {!Zipper.map}, but the function takes an | ||
| 212 | index and an element. | ||
| 213 | The index indicates the distance of an element from the cursor. *) | ||
| 214 | |||
| 215 | val filter_before : ('a -> bool) -> 'a zipper -> 'a zipper | ||
| 216 | (** [filter_before p z] filters the elements before the cursor in a | ||
| 217 | zipper [z] according to a predicate [p], i.e., keeping the elements | ||
| 218 | that satisfy the predicate. *) | ||
| 219 | |||
| 220 | val filter_after : ('a -> bool) -> 'a zipper -> 'a zipper | ||
| 221 | (** [filter_after p z] filters the elements after the cursor in a | ||
| 222 | zipper [z] according to a predicate [p], i.e., keeping the elements | ||
| 223 | that satisfy the predicate. *) | ||
| 224 | |||
| 225 | val filter : ('a -> bool) -> 'a zipper -> 'a zipper | ||
| 226 | (** [filter p z] filters the elements of the zipper [z] according to a | ||
| 227 | predicate [p], i.e., keeping the elements that satisfy the | ||
| 228 | predicate. *) | ||
| 229 | |||
| 230 | val context_before : int -> 'a zipper -> 'a zipper | ||
| 231 | (** [context_before n z] will limit the zipper [z] to [n] elements before | ||
| 232 | the cursor. *) | ||
| 233 | |||
| 234 | val context_after : int -> 'a zipper -> 'a zipper | ||
| 235 | (** [context_after n z] will limit the zipper [z] to [n] elements after | ||
| 236 | the cursor. *) | ||
| 237 | |||
| 238 | val context : b:int -> ?a:int -> 'a zipper -> 'a zipper | ||
| 239 | (** [context ~b ~a z] will limit the zipper [z] to [b] elements before | ||
| 240 | the cursor and [a] elements after the cursor. When [a] is not | ||
| 241 | provided, it defaults to [b]. *) | ||
| 242 | |||
| 243 | val clear_history : 'a zipper -> 'a zipper | ||
| 244 | (** Clear the history of the zipper. See {!Zipper.history}. *) | ||
| 245 | |||
| 246 | (** {1 Zippers and sequences} *) | ||
| 247 | |||
| 248 | val of_seq : 'a Sequence.t -> 'a zipper | ||
| 249 | (** Turn a sequence into a zipper with the cursor at the beginning. *) | ||
| 250 | |||
| 251 | val to_seq : 'a zipper -> 'a Sequence.t | ||
| 252 | (** Return the zipper as a sequence. | ||
| 253 | Calling [to_seq z], with [z] being [([3; 2; 1], [4; 5])], results in | ||
| 254 | [[1; 2; 3; 4; 5]]. *) | ||
| 255 | |||
| 256 | val window : from:int -> len:int -> 'a zipper -> 'a Sequence.t | ||
| 257 | (** [windows from len z] returns a sequence containing [len] elements | ||
| 258 | starting from [from]. *) | ||
