1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
|
open Base
open Stdio
open Zipper
open Util
type kind = File of string | No_name | Scratch
type error = No_such_file | Other
type buffer = {
kind : kind;
content : (char zipper zipper, error) Result.t;
rendered : char Sequence.t zipper;
last_modified : float;
}
type t = buffer
let empty () =
{
kind = No_name;
content = empty |> push empty |> Result.return;
rendered = push Sequence.empty empty;
last_modified = Unix.gettimeofday ();
}
let kind b = b.kind
let string_of_kind = function
| No_name -> "[No Name]"
| Scratch -> "[Scratch]"
| File name -> name
let size e =
match e.content with
| Error _ -> (0, 0)
| Ok z -> (length z, apply_focus_or ~default:0 length z)
let render =
let open Sequence in
let tabsize = 8 in
let f i = function
| '\t' ->
let width = tabsize - (i % tabsize) in
(i + width, take (repeat '.') width)
| c -> (i + 1, singleton c)
in
folding_map ~init:0 ~f &> join
let cursor ?(rendered = true) b =
match b.content with
| Error _ -> (0, 0)
| Ok c ->
let hlen z =
if rendered then
Sequence.(length (before z |> to_list_rev |> of_list |> render))
else left_length z
in
(left_length c, apply_focus_or ~default:0 hlen c)
type 'a action = t -> 'a * t
module Action = struct
include Applicative.Make (struct
type 'a t = 'a action
let return a b = (a, b)
let apply f a b =
let g, b = f b in
let x, b = a b in
(g x, b)
let map = `Define_using_apply
end)
include Monad.Make (struct
type 'a t = 'a action
let return x b = (x, b)
let bind a ~f x =
let y, a' = a x in
f y a'
let map = `Define_using_bind
end)
let ( let* ) b f = bind b ~f
let ( and* ) = both
let ( let+ ) b f = map b ~f
let get b = (b, b)
let put b _ = ((), b)
let modify ~f = get >>| f >>= put
let get_content b = (b.content, b)
let set_content c b = ((), { b with content = c })
let on_content f = get_content >>| Result.map ~f >>= set_content
let on_content_with_output ~default f b =
match b.content with
| Error _ -> (default, b)
| Ok z ->
let a, z = f z in
(a, { b with content = Ok z })
let on_rendered f b = ((), { b with rendered = f b.rendered })
let update_render ?(before = false) ~n b =
match b.content with
| Error _ -> ((), b)
| Ok c ->
let rstep = if before then left else right ~by_one:false in
let cstep = if before then left else right ~by_one:false in
let rec aux i r c =
if i = 0 then r
else
let default = Sequence.empty in
let l = apply_focus_or ~default (to_seq &> render) c in
let c' = cstep c and r' = swap_focus l r |> rstep in
aux (i - 1) r' c'
in
((), { b with rendered = aux n b.rendered c |> goto (left_length c) })
let update_render_at_cursor = update_render ~before:false ~n:1
let move_up ?(n = 1) =
let change_content =
let act z =
let col = apply_focus_or ~default:0 left_length z in
Fn.apply_n_times ~n left z |> map_focus (goto col)
in
on_content act
and change_rendered = on_rendered (Fn.apply_n_times ~n left) in
change_content *> change_rendered
let move_down ?(n = 1) =
let change_content =
let act z =
let col = apply_focus_or ~default:0 left_length z in
Fn.apply_n_times ~n right z |> map_focus (goto col)
in
on_content act
and change_rendered = on_rendered (Fn.apply_n_times ~n right) in
change_content *> change_rendered
let move_left, move_right =
let horizontal f ?(n = 1) =
map_focus (Fn.apply_n_times ~n f) |> on_content
in
(horizontal left, horizontal right)
let goto ~r ?(c = 0) =
let change_content = Zipper.(goto r &> map_focus (goto c)) |> on_content
and change_rendered = Zipper.goto r |> on_rendered in
change_content *> change_rendered
let bol ?(n = 0) = move_down ~n *> (map_focus far_left |> on_content)
let eol ?(n = 0) = move_down ~n *> (map_focus far_right |> on_content)
let bof =
let change_content = far_left |> on_content
and change_rendered = far_left |> on_rendered in
change_content *> change_rendered
let eof =
let change_content = far_right |> on_content
and change_rendered = far_right |> on_rendered in
change_content *> change_rendered
let insert k =
let change_content = map_focus (push_before k) |> on_content in
change_content *> update_render_at_cursor
let replace k =
let change_content = map_focus (swap_focus k) |> on_content in
change_content *> update_render_at_cursor
let insert_line ?(before = false) =
let change_content =
let push = if before then push_before else push_after in
push Zipper.empty |> on_content
and change_rendered =
let push = if before then push_before else push_after in
push Sequence.empty |> on_rendered
in
change_content *> change_rendered
let join_lines ?(n = 2) =
let change_content =
let join_2_lines z =
if right_length z < 2 then z
else
let to_join = right z |> focus_or ~default:Zipper.empty in
map_focus (Zipper.join ~z2:to_join) z |> pop_after |> snd
in
Fn.apply_n_times ~n:(n - 1) join_2_lines |> on_content
and change_rendered = pop_after ~n:(n - 1) &> snd |> on_rendered in
change_content *> change_rendered *> update_render_at_cursor
let delete_after ?(cross_lines = false) ~n =
let lines_to_delete b =
match b.content with
| Error _ -> 0
| Ok z -> (
match Sequence.next (after z) with
| None -> 0
| Some (h, t) ->
let init = right_length h
and f acc z =
let acc' = acc + length z + 1 in
(acc', acc')
in
Sequence.shift_right (Sequence.folding_map t ~init ~f) init
|> Sequence.findi ~f:(fun _ a -> n <= a)
|> Option.map ~f:fst
|> Option.value ~default:(Sequence.length t))
and change_content =
let default = Sequence.empty in
on_content_with_output ~default (fun z ->
let rec aux i acc z =
let line = focus_or ~default:Zipper.empty z in
if right_length line >= i || (not cross_lines) || right_length z < 2
then
let s, z1 = pop ~n:i line in
(s :: acc, swap_focus z1 z)
else
let rlen = right_length line in
let s, z1 = pop ~n:rlen line in
let z = swap_focus z1 z in
let to_join = right z |> focus_or ~default:Zipper.empty in
let z =
map_focus (Zipper.join ~z2:to_join) z |> pop_after |> snd
in
aux (i - rlen - 1) (s :: acc) z
in
let acc, z = aux n [] z in
(List.rev acc |> Sequence.of_list, z))
and change_rendered n =
if cross_lines then pop_after ~n &> snd |> on_rendered else return ()
in
let* n = get >>| lines_to_delete in
let* out = change_content
and* () = change_rendered n
and* () = update_render_at_cursor in
return out
let delete_before ?(cross_lines = false) ~n =
let lines_to_delete b =
match b.content with
| Error _ -> 0
| Ok z ->
let init = apply_focus_or ~default:0 left_length z
and f acc z =
let acc' = acc + length z + 1 in
(acc', acc')
in
Sequence.shift_right (Sequence.folding_map (before z) ~init ~f) init
|> Sequence.findi ~f:(fun _ a -> n <= a)
|> Option.map ~f:fst
|> Option.value ~default:(left_length z)
and change_content =
let default = Sequence.empty in
on_content_with_output ~default (fun z ->
let rec aux i acc z =
let line = focus_or ~default:Zipper.empty z in
if left_length line >= i || (not cross_lines) || is_far_left z then
let s, z1 = pop_before ~n:i line in
let z' = swap_focus z1 z in
(s :: acc, z')
else
let llen = left_length line in
let s, z1 = pop_before ~n:llen line in
let z' = swap_focus z1 z in
let to_join = focus_or ~default:Zipper.empty z' in
let z'' =
left z'
|> map_focus (Zipper.join ~z2:to_join)
|> pop_after |> snd
in
aux (i - llen - 1) (s :: acc) z''
in
let acc, z' = aux n [] z in
(List.rev acc |> Sequence.of_list, z'))
and change_rendered n =
if cross_lines then pop_before ~n &> snd |> on_rendered else return ()
in
let* n = get >>| lines_to_delete in
let* out = change_content
and* () = change_rendered n
and* () = update_render_at_cursor in
return out
let delete_lines ~n =
let default = Sequence.empty in
let* out = pop ~n |> on_content_with_output ~default
and* () = pop ~n &> snd |> on_rendered in
return (Sequence.map ~f:to_seq out)
(* TODO: maybe not needed *)
(* let delete_lines_before ~n b = *)
(* let default = Sequence.empty in *)
(* let out, b = on_content_with_output ~default (pop_before ~n) b in *)
(* (Sequence.map ~f:to_seq out, on_rendered (pop_before ~n &> snd) b) *)
let delete_to_eol ?(n = 0) =
let act z =
let default = (Zipper.empty, Zipper.empty) in
let z1, z2 = apply_focus_or ~default split z in
(to_seq z2, swap_focus z1 z)
in
let default = Sequence.empty in
let* h = act |> on_content_with_output ~default
and* t = move_down *> delete_lines ~n
and* () = move_up *> eol
and* () = update_render_at_cursor in
return (Sequence.shift_right t h)
let delete_to_bol =
let act z =
let default = (Zipper.empty, Zipper.empty) in
let z1, z2 = apply_focus_or ~default split z in
(Sequence.singleton (to_seq z1), swap_focus z2 z)
in
let default = Sequence.empty in
let* out = act |> on_content_with_output ~default
and* () = update_render_at_cursor in
return out
let newline =
let change_content =
let aux z =
let default = (Zipper.empty, Zipper.empty) in
let z1, z2 = apply_focus_or ~default split z in
push_before z1 z |> swap_focus z2
in
on_content aux
and change_rendered =
(push_before Sequence.empty |> on_rendered)
*> update_render ~before:true ~n:2
in
change_content *> change_rendered
let paste ?(before = false) ?(linewise = false) ?(n = 1) s =
let change_content =
if linewise then
let push = if before then push_before_seq else push_after_seq in
push (Sequence.map ~f:of_seq s) |> on_content
else
let aux z =
match Sequence.next s with
| None -> z
| Some (h, t) ->
let init =
let default = Zipper.(of_seq h |> far_right) in
map_focus_or ~default (push_before_seq h) z
and f z l =
let default = (Zipper.empty, Zipper.empty) in
let z1, z2 = apply_focus_or ~default split z in
z |> push_before z1 |> swap_focus z2
|> map_focus (push_before_seq l)
in
let folded = Sequence.fold ~init ~f t in
if before then folded
else
folded
|> Fn.apply_n_times ~n:(Sequence.length t) left
|> map_focus (Fn.apply_n_times ~n:(Sequence.length h) left)
in
on_content aux
and change_rendered =
if linewise then
let push = if before then push_before_seq else push_after_seq in
push (Sequence.map ~f:render s) |> on_rendered
else
let len = Sequence.length s in
let push = if before then push_before else push in
let aux z = Fn.apply_n_times ~n:(len - 1) (push Sequence.empty) z in
on_rendered aux *> update_render ~before ~n:len
in
let rec bind_n_times ~n act =
match n with
| _ when n <= 0 -> return ()
| 1 -> act
| _ -> act *> bind_n_times ~n:(n - 1) act
in
bind_n_times ~n (change_content *> change_rendered)
let search forward word =
let rec tails s =
match Sequence.next s with
| None -> Sequence.empty
| Some (_, t) -> Sequence.shift_right (tails t) s
and prefix p l =
match Sequence.(next p, next l) with
| Some (ph, pt), Some (lh, lt) when Char.(ph = lh) -> prefix pt lt
| None, _ -> true
| _ -> false
in
let search_line w l =
Sequence.findi ~f:(fun _ -> prefix w) (tails l) |> Option.map ~f:fst
in
let* b = get in
let cr, cc = cursor ~rendered:false b in
let* c = get_content in
match c with
| Error _ -> return None
| Ok c -> (
if forward then
match Sequence.next (Zipper.after c) with
| None -> return None
| Some (h, t) -> (
match Zipper.(h |> right |> after) |> search_line word with
| Some i -> return (Some (cr, cc + i + 1))
| None ->
let f r z =
z |> to_seq |> search_line word
|> Option.map ~f:(fun c -> (cr + r + 1, c))
in
return (Sequence.find_mapi t ~f))
else
let word = Sequence.(word |> to_list_rev |> of_list) in
let wlen = Sequence.length word in
match Zipper.(c |> right |> before) |> Sequence.next with
| None -> return None
| Some (h, t) -> (
match h |> Zipper.before |> search_line word with
| Some i -> return (Some (cr, cc - wlen - i))
| None ->
let f r z =
let z = z |> far_right in
let len = left_length z in
z |> before |> search_line word
|> Option.map ~f:(fun c -> (cr - r - 1, len - wlen - c))
in
return (Sequence.find_mapi t ~f)))
(* let save_history_to ?(clear = true) r = () *)
end
let from_file f =
let lines =
let line_to_seq l = String.to_list l |> Sequence.of_list in
try
let fd = Unix.(openfile f [ O_RDONLY ] 0o640) in
let ic = Unix.in_channel_of_descr fd in
let lines = In_channel.input_lines ic in
In_channel.close ic;
Sequence.(of_list lines |> map ~f:line_to_seq)
with Unix.Unix_error (ENOENT, _, _) -> Sequence.empty
in
let kind = File f
and content = Sequence.map ~f:of_seq lines |> of_seq |> Result.return
and rendered = Sequence.map ~f:render lines |> of_seq
and last_modified = Unix.gettimeofday () in
{ kind; content; rendered; last_modified }
let unrendered_view x y h w b =
match b.content with
| Error _ -> Sequence.empty
| Ok z ->
let cx, _ = cursor b in
context ~l:(cx - x) ~r:(x + h - cx) z
|> to_seq
|> Sequence.map ~f:(window ~from:y ~len:w)
let rendered_view x y h w b =
let window from len seq = Sequence.(take (drop_eagerly seq from) len) in
let cx, _ = cursor ~rendered:false b in
context ~l:(cx - x) ~r:(x + h - cx) b.rendered
|> to_seq
|> Sequence.map ~f:(window y w)
|