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 /lib/zipper.ml | |
| parent | 416c56656af65d656f637dc8c8fdb62d0ba03e29 (diff) | |
| download | sandy-055c743c55bde27f4475d3434c26d8383c0c3ea1.tar.gz sandy-055c743c55bde27f4475d3434c26d8383c0c3ea1.zip | |
bulk: add PoC of vim-like modular editor
Diffstat (limited to 'lib/zipper.ml')
| -rw-r--r-- | lib/zipper.ml | 131 |
1 files changed, 131 insertions, 0 deletions
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 | ||
