summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bin/main.ml9
-rw-r--r--lib/command.ml115
-rw-r--r--lib/config.ml9
-rw-r--r--lib/editor.ml271
-rw-r--r--lib/editorBuffer.ml72
-rw-r--r--lib/key.ml72
-rw-r--r--lib/modes.ml18
-rw-r--r--lib/terminal.ml130
-rw-r--r--lib/terminal.mli67
-rw-r--r--lib/text.ml10
-rw-r--r--lib/util.ml26
-rw-r--r--lib/zipper.ml131
-rw-r--r--lib/zipper.mli258
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 @@
1let () = print_endline "Hello, World!" 1open Sand
2
3let () =
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 @@
1open Base
2open Key
3
4type register = char option
5type count = int option
6type operation = Noop | Yank | Paste | Delete | Change
7type scope = Line | To_bol | To_eol | Down | Left | Right | Up
8
9type 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
16type t = command
17
18let shortcut ?r ?n c s = Shortcut (r, n, c, s)
19let chord ?r ?n1 c ?n2 m = Chord (r, n1, c, n2, m)
20
21let 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
31let 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
45let 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
52let 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 @@
1open Base
2
3type config = {
4 files : string list;
5}
6type t = config
7
8let 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 @@
1open Base
2module Buffer = EditorBuffer
3open Util
4
5type mode = Normal | Insert
6type cursor = int * int
7
8type 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
19type t = editor
20
21let 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
33type 'a action = t -> 'a * t
34
35module 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 ()
120end
121
122let move ?(up = 0) ?(down = 0) ?(left = 0) ?(right = 0) (x, y) =
123 (x + down - up, y + right - left)
124
125let 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
185let 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
201let 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
258let 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
269let 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 @@
1open Base
2open Zipper
3open Util
4
5type kind = File of string | No_name | Scratch
6type error = No_such_file | Other
7type buffer = { kind : kind; content : (char zipper zipper, error) Result.t }
8type t = buffer
9
10let empty =
11 { kind = No_name; content = empty |> push_after empty |> Result.return }
12
13module 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 = () *)
48end
49
50let 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
56let 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
65let 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 @@
1open Base
2
3type 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
20type t = key
21
22let ctrl c = Ctrl c
23let key c = Key c
24
25let 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
33let 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 @@
1type mode = Normal | Insert
2type t = mode
3type state = int
4type 'a state_monad = state -> 'a * state
5
6let run (f : 'a state_monad) (s : state) : 'a = f s |> fst
7let return (a : 'a) : 'a state_monad = fun s -> (a, s)
8
9let ( >>= ) (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
14let draw () : unit state_monad = return ()
15let get_keypress () : char state_monad = return 'a'
16let handle_key (_ : char) : unit state_monad = return ()
17let rec loop () = () |> draw >>= get_keypress >>= handle_key >>= loop
18let 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 @@
1open Base
2open Unix
3open Util
4
5type terminal_size = int * int
6type state = { tio : terminal_io; size : terminal_size }
7
8let 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
22let clear_screen = escape 'J' ~args:[ 2 ]
23let clear_to_eol = escape 'K'
24let move_cursor x y = escape 'H' ~args:[ x; y ]
25let move_down n = escape 'B' ~args:[ n ]
26let move_right n = escape 'C' ~args:[ n ]
27let query_cursor_pos = escape 'n' ~args:[ 6 ]
28let reset_cursor = move_cursor 1 1
29
30let show_cursor show =
31 let cmd = if show then 'h' else 'l' in
32 escape cmd ~prefix:"?" ~args:[ 25 ]
33
34let input_bytes = Bytes.create 1
35
36let 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
42let char_stream : char option Sequence.t =
43 Sequence.unfold ~init:() ~f:(fun s -> Some (get_char (), s))
44
45let 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
52let 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
64let cmds_to_sequence l = Sequence.(of_list l |> concat)
65
66let restore_screen () =
67 cmds_to_sequence [ clear_screen; reset_cursor; show_cursor true ] |> write_seq
68
69let 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
76let get_state () = { tio = handle_unix_error tcgetattr stdin; size = (-1, -1) }
77
78let 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
94let 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
121let restore_status tio =
122 let syscall () = tcsetattr stdin TCSAFLUSH tio in
123 handle_unix_error syscall
124
125let 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
4open Base
5open Unix
6
7type terminal_size = int * int
8(** Size of the terminal window. *)
9
10type 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
16val 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
23val 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
28val 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
33val restore_screen : unit -> unit
34(** Clear screen and show cursor. Meant to be called on exit. *)
35
36val 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
42val get_state : unit -> state
43(** Get current state for the terminal window *)
44
45val 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
54val 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
60val restore_status : terminal_io -> unit -> unit
61(** Override the terminal status.
62
63 @param tio The new terminal status for [stdin]. *)
64
65val 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 @@
1open Base
2open Sequence
3open Sequence.Infix
4
5let 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
10let 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
3open 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]. *)
11let ( &> ) 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. *)
17let 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
3open Base
4
5type !+'a zipper = {
6 pos : int;
7 popped : 'a Sequence.t;
8 before : 'a Sequence.t;
9 after : 'a Sequence.t;
10}
11
12type !+'a t = 'a zipper
13
14let empty =
15 {
16 pos = 0;
17 popped = Sequence.empty;
18 before = Sequence.empty;
19 after = Sequence.empty;
20 }
21
22let before z = z.before
23let after z = z.after
24let focus z = after z |> Sequence.next |> Option.map ~f:fst
25let focus_or z ~default = Option.value ~default (focus z)
26let history z = z.popped
27let is_far_left z = before z |> Sequence.is_empty
28let is_far_right z = after z |> Sequence.is_empty
29let is_empty z = is_far_left z && is_far_right z
30let left_length z = z.pos
31let right_length z = after z |> Sequence.length
32let length z = left_length z + right_length z
33
34let 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
45let 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
50let rec far_left z = if is_far_left z then z else z |> left |> far_left
51
52let 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
63let 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
69let rec far_right z = if is_far_right z then z else z |> right |> far_right
70
71let 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
76let pop_after z = { z with after = Sequence.drop_eagerly z.after 1 }
77let pop_before z = if is_far_left z then z else z |> left |> pop_after
78let pop = pop_before
79let push_after x z = { z with after = Sequence.shift_right z.after x }
80
81let push_before x z =
82 { z with pos = z.pos + 1; before = Sequence.shift_right z.before x }
83
84let push = push_before
85
86let split z =
87 ( { z with after = Sequence.empty },
88 { z with pos = 0; before = Sequence.empty } )
89
90let iter_before f z = Sequence.iter ~f z.before
91let iter_after f z = Sequence.iter ~f z.after
92
93let iter f z =
94 iter_before f z;
95 iter_after f z
96
97let for_all f z = Sequence.(for_all ~f z.before && for_all ~f z.after)
98let exists f z = Sequence.(exists ~f z.before || exists ~f z.after)
99let find_before f z = Sequence.find ~f z.before
100let find_after f z = Sequence.find ~f z.after
101let map_before f z = { z with before = Sequence.map ~f z.before }
102let map_after f z = { z with after = Sequence.map ~f z.after }
103
104let 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
109let map f z =
110 { z with before = Sequence.map ~f z.before; after = Sequence.map ~f z.after }
111
112let mapi_before f z = { z with before = Sequence.mapi ~f z.before }
113let mapi_after f z = { z with after = Sequence.mapi ~f z.after }
114
115let mapi f z =
116 {
117 z with
118 before = Sequence.mapi ~f z.before;
119 after = Sequence.mapi ~f z.after;
120 }
121
122let filter_before f z = { z with before = Sequence.filter ~f z.before }
123let filter_after f z = { z with after = Sequence.filter ~f z.after }
124let filter p z = z |> filter_before p |> filter_after p
125let context_before n z = { z with before = Sequence.take z.before n }
126let context_after n z = { z with after = Sequence.take z.after n }
127let context ~b ?(a = b) z = z |> context_before b |> context_after a
128let clear_history z = { z with popped = Sequence.empty }
129let of_seq s = { empty with after = s }
130let to_seq z = z |> far_left |> after
131let 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 @@
1open 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
18type !+'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
28type !+'a t = 'a zipper
29(** An alias for the ['a zipper] type. *)
30
31val empty : 'a zipper
32(** Return an empty zipper *)
33
34val before : 'a zipper -> 'a Sequence.t
35(** Return the sequence before the cursor *)
36
37val after : 'a zipper -> 'a Sequence.t
38(** Return the sequence after the cursor *)
39
40val focus : 'a zipper -> 'a option
41(** Return the focus of the zipper, if any. *)
42
43val focus_or : 'a zipper -> default:'a -> 'a
44(** Return the focus of the zipper, or a user-provided default, otherwise. *)
45
46val history : 'a zipper -> 'a Sequence.t
47(** Returns the sequence of elements [pop]ped so far from the zipper. *)
48
49val is_far_left : 'a zipper -> bool
50(** Return whether the cursor is at the beginning of the zipper. *)
51
52val is_far_right : 'a zipper -> bool
53(** Return whether the cursor is at the end of the zipper. *)
54
55val is_empty : 'a zipper -> bool
56(** Return whether the zipper is empty. *)
57
58val left_length : 'a zipper -> int
59(** Return the number of elements before the cursor. *)
60
61val right_length : 'a zipper -> int
62(** Return the number of elements after the cursor. *)
63
64val length : 'a zipper -> int
65(** Return the length of the zipper. *)
66
67(** {1 Moving the cursor} *)
68
69val 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
77val 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
82val far_left : 'a zipper -> 'a zipper
83(** Move the cursor to the left, as much as possible. *)
84
85val 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
93val 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
98val far_right : 'a zipper -> 'a zipper
99(** Move the cursor to the right, as much as possible. *)
100
101val 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
112val 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
120val 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
128val pop : 'a zipper -> 'a zipper
129(** [pop] is an alias for [pop_before]. *)
130
131val 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
136val 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
141val push : 'a -> 'a zipper -> 'a zipper
142(** [push] is an alias for [push_before]. *)
143
144val 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
155val 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
159val 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
163val iter : ('a -> unit) -> 'a zipper -> unit
164(** [iter f z] is equivalent to [iter_before f z; iter_after f z] *)
165
166val 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
170val 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
174val 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
178val 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
188val map_before : ('a -> 'a) -> 'a zipper -> 'a zipper
189(** Map a function over all elements before the cursor. *)
190
191val map_after : ('a -> 'a) -> 'a zipper -> 'a zipper
192(** Map a function over all elements after the cursor. *)
193
194val map_focus : ('a -> 'a) -> 'a zipper -> 'a zipper
195(** Map a function over the element focused by the cursor, if any. *)
196
197val map : ('a -> 'a) -> 'a zipper -> 'a zipper
198(** Map a function over all elements of a zipper. *)
199
200val 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
205val 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
210val 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
215val 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
220val 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
225val 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
230val 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
234val 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
238val 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
243val clear_history : 'a zipper -> 'a zipper
244(** Clear the history of the zipper. See {!Zipper.history}. *)
245
246(** {1 Zippers and sequences} *)
247
248val of_seq : 'a Sequence.t -> 'a zipper
249(** Turn a sequence into a zipper with the cursor at the beginning. *)
250
251val 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
256val 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]. *)