summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFederico Igne <undyamon@disroot.org>2024-01-28 01:22:52 +0100
committerFederico Igne <undyamon@disroot.org>2024-01-28 01:22:52 +0100
commit53916fc3f7dcbefd90e3d0340a2a8f32bf331d1d (patch)
treebaf9b5d6bdfdc77d0338e230adf64f7f52aefa4e
parent722a3d4d2d0d0630f57cfbfdddc229dee341505b (diff)
downloadsandy-53916fc3f7dcbefd90e3d0340a2a8f32bf331d1d.tar.gz
sandy-53916fc3f7dcbefd90e3d0340a2a8f32bf331d1d.zip
feat: add plain search functionality (with history)
-rw-r--r--lib/command.ml17
-rw-r--r--lib/control.ml32
-rw-r--r--lib/editor.ml137
-rw-r--r--lib/editorBuffer.ml60
-rw-r--r--lib/mode.ml3
-rw-r--r--lib/modes.ml18
6 files changed, 229 insertions, 38 deletions
diff --git a/lib/command.ml b/lib/command.ml
index 9ab36f6..ac1dbf1 100644
--- a/lib/command.ml
+++ b/lib/command.ml
@@ -14,6 +14,8 @@ type operation =
14 | Paste_after 14 | Paste_after
15 | Erase_before 15 | Erase_before
16 | Erase_after 16 | Erase_after
17 | Search
18 | Search_rev
17 19
18type scope = Line | To_bol | To_eol | Down | Left | Right | Up 20type scope = Line | To_bol | To_eol | Down | Left | Right | Up
19 21
@@ -70,7 +72,18 @@ let to_scope = function
70let is_simple_movement k = List.mem ~equal:Poly.equal simple_movements k 72let is_simple_movement k = List.mem ~equal:Poly.equal simple_movements k
71 73
72let instant_operation = 74let instant_operation =
73 [ Key 'C'; Key 'D'; Key 'Y'; Key 'J'; Key 'P'; Key 'X'; Key 'p'; Key 'x' ] 75 [
76 Key 'C';
77 Key 'D';
78 Key 'Y';
79 Key 'J';
80 Key 'n';
81 Key 'N';
82 Key 'P';
83 Key 'X';
84 Key 'p';
85 Key 'x';
86 ]
74 87
75let chord_operation = [ Key 'c'; Key 'd'; Key 'y' ] 88let chord_operation = [ Key 'c'; Key 'd'; Key 'y' ]
76 89
@@ -79,6 +92,8 @@ let to_op = function
79 | Key 'c' | Key 'C' -> Change 92 | Key 'c' | Key 'C' -> Change
80 | Key 'd' | Key 'D' -> Delete 93 | Key 'd' | Key 'D' -> Delete
81 | Key 'y' | Key 'Y' -> Yank 94 | Key 'y' | Key 'Y' -> Yank
95 | Key 'n' -> Search
96 | Key 'N' -> Search_rev
82 | Key 'p' -> Paste_after 97 | Key 'p' -> Paste_after
83 | Key 'P' -> Paste_before 98 | Key 'P' -> Paste_before
84 | Key 'x' -> Erase_after 99 | Key 'x' -> Erase_after
diff --git a/lib/control.ml b/lib/control.ml
new file mode 100644
index 0000000..670175c
--- /dev/null
+++ b/lib/control.ml
@@ -0,0 +1,32 @@
1open Base
2
3type t = { prompt : Key.t; content : char Zipper.t }
4type result = Search of bool * char Sequence.t | No_result
5
6let create prompt = { prompt; content = Zipper.empty }
7
8let render c =
9 let prompt = Key.to_string c.prompt |> String.to_list
10 and content = c.content |> Zipper.to_seq in
11 Sequence.shift_right_with_list content prompt
12
13let is_search c = match c.prompt with Key '/' | Key '?' -> true | _ -> false
14
15let cursor c =
16 String.length (Key.to_string c.prompt) + Zipper.left_length c.content + 1
17
18let get_result c =
19 let open Zipper in
20 match c.prompt with
21 | Key '/' -> Search (true, c.content |> to_seq)
22 | Key '?' -> Search (false, c.content |> to_seq)
23 | _ -> No_result
24
25let set_content s c = { c with content = Zipper.(of_seq s |> far_right) }
26let move_left c = { c with content = Zipper.left c.content }
27let move_right c = { c with content = Zipper.right c.content }
28let delete_before c = { c with content = Zipper.pop_before c.content |> snd }
29let delete_after c = { c with content = Zipper.pop c.content |> snd }
30let bol c = { c with content = Zipper.far_left c.content }
31let eol c = { c with content = Zipper.far_right c.content }
32let insert k c = { c with content = Zipper.push_before k c.content }
diff --git a/lib/editor.ml b/lib/editor.ml
index 043c00e..5a68e53 100644
--- a/lib/editor.ml
+++ b/lib/editor.ml
@@ -2,8 +2,6 @@ open Base
2module Buffer = EditorBuffer 2module Buffer = EditorBuffer
3open Util 3open Util
4 4
5type mode = Normal | Insert | Control
6
7type selection = 5type selection =
8 | Empty 6 | Empty
9 | Glyphwise of char Sequence.t Sequence.t 7 | Glyphwise of char Sequence.t Sequence.t
@@ -13,7 +11,7 @@ type cursor = int * int
13 11
14type editor = { 12type editor = {
15 term : Terminal.state; 13 term : Terminal.state;
16 mode : mode; 14 mode : Mode.t;
17 offset : int * int; 15 offset : int * int;
18 cursor : cursor; 16 cursor : cursor;
19 buffer : Buffer.t; 17 buffer : Buffer.t;
@@ -26,6 +24,8 @@ type editor = {
26 message_duration : float; 24 message_duration : float;
27 pending_command : string; 25 pending_command : string;
28 registers : selection Array.t; 26 registers : selection Array.t;
27 control : Control.t;
28 search_history : (bool * char Sequence.t) Zipper.t;
29} 29}
30 30
31type t = editor 31type t = editor
@@ -49,19 +49,16 @@ let init (c : Config.t) : editor =
49 message_duration = 5.; 49 message_duration = 5.;
50 pending_command = ""; 50 pending_command = "";
51 registers = Array.create ~len:128 Empty; 51 registers = Array.create ~len:128 Empty;
52 control = Control.create Key.Nul;
53 search_history = Zipper.empty;
52 } 54 }
53 55
54let string_of_mode = function
55 | Insert -> " I "
56 | Normal -> " N "
57 | Control -> " C "
58
59let statusbar e = 56let statusbar e =
60 let open Text in 57 let open Text in
61 (* let open Sequence.Infix in *) 58 (* let open Sequence.Infix in *)
62 let w = e.term.size |> snd in 59 let w = e.term.size |> snd in
63 let status = 60 let status =
64 let mode = e.mode |> string_of_mode |> sequence_of_string in 61 let mode = e.mode |> Mode.to_string |> sequence_of_string in
65 let lsize = Sequence.length mode 62 let lsize = Sequence.length mode
66 and c = e.buffer.kind |> Buffer.string_of_kind |> sequence_of_string 63 and c = e.buffer.kind |> Buffer.string_of_kind |> sequence_of_string
67 and br, bc = Buffer.size e.buffer 64 and br, bc = Buffer.size e.buffer
@@ -79,9 +76,12 @@ let statusbar e =
79 let rsize = Sequence.length nav in 76 let rsize = Sequence.length nav in
80 spread ~l:(bold mode) ~lsize ~c ~r:(bold nav) ~rsize ~fill:' ' w |> invert 77 spread ~l:(bold mode) ~lsize ~c ~r:(bold nav) ~rsize ~fill:' ' w |> invert
81 and control = 78 and control =
82 let msg = Option.value ~default:"" e.message |> sequence_of_string 79 match e.mode with
83 and cmd = e.pending_command |> sequence_of_string in 80 | Control -> Control.render e.control
84 spread ~l:msg ~r:cmd ~fill:' ' w 81 | _ ->
82 let msg = Option.value ~default:"" e.message |> sequence_of_string
83 and cmd = e.pending_command |> sequence_of_string in
84 spread ~l:msg ~r:cmd ~fill:' ' w
85 in 85 in
86 Sequence.(take (of_list [ status; control ]) e.status_size) 86 Sequence.(take (of_list [ status; control ]) e.status_size)
87 87
@@ -117,8 +117,16 @@ module Action = struct
117 let map = `Define_using_bind 117 let map = `Define_using_bind
118 end) 118 end)
119 119
120 let ( let+ ) e f = map e ~f
120 let ( let* ) e f = bind e ~f 121 let ( let* ) e f = bind e ~f
121 let ( and* ) = both 122 let ( and* ) = both
123
124 let rec repeat ?(n = 1) a =
125 match n with
126 | _ when n <= 0 -> return ()
127 | 1 -> a
128 | _ -> a *> repeat ~n:(n - 1) a
129
122 let get e = (e, e) 130 let get e = (e, e)
123 let put e _ = ((), e) 131 let put e _ = ((), e)
124 let modify ~f e = ((), f e) 132 let modify ~f e = ((), f e)
@@ -149,8 +157,22 @@ module Action = struct
149 let* b = get_focused_buffer in 157 let* b = get_focused_buffer in
150 return (f b |> fst) 158 return (f b |> fst)
151 159
160 let get_control_buffer e = (e.control, e)
161 let set_control_buffer c e = ((), { e with control = c })
162
163 let on_control_buffer f =
164 let* c = get_control_buffer in
165 set_control_buffer (f c)
166
152 let get_mode e = (e.mode, e) 167 let get_mode e = (e.mode, e)
153 let set_mode m e = ((), { e with mode = m }) 168 let set_mode m e = ((), { e with mode = m })
169 let get_search_history e = (e.search_history, e)
170 let set_search_history h e = ((), { e with search_history = h })
171 let on_search_history f = get_search_history >>| f >>= set_search_history
172
173 let set_last_search dir word =
174 Zipper.(far_left &> swap_focus (dir, word)) |> on_search_history
175
154 let get_terminal_size e = (e.term.size, e) 176 let get_terminal_size e = (e.term.size, e)
155 177
156 let get_register ?(r = '"') e = 178 let get_register ?(r = '"') e =
@@ -179,8 +201,14 @@ module Action = struct
179 |> Text.extend ~fill r 201 |> Text.extend ~fill r
180 |> Fn.flip Sequence.take (r - ssize) 202 |> Fn.flip Sequence.take (r - ssize)
181 in 203 in
182 let screen = Sequence.append bufview status in 204 let screen = Sequence.append bufview status
183 Terminal.redraw screen e.cursor 205 and cursor =
206 let open Mode in
207 match e.mode with
208 | Control -> (r, Control.cursor e.control)
209 | _ -> e.cursor
210 in
211 Terminal.redraw screen cursor
184 in 212 in
185 get >>| aux 213 get >>| aux
186 214
@@ -189,7 +217,7 @@ module Action = struct
189 217
190 (* Statusbar *) 218 (* Statusbar *)
191 let set_message m e = 219 let set_message m e =
192 ((), { e with message = m; message_timestamp = Unix.time () }) 220 ((), { e with message = Some m; message_timestamp = Unix.time () })
193 221
194 let get_pending_command e = (e.pending_command, e) 222 let get_pending_command e = (e.pending_command, e)
195 let set_pending_command p e = ((), { e with pending_command = p }) 223 let set_pending_command p e = ((), { e with pending_command = p })
@@ -209,6 +237,15 @@ module Action = struct
209 in 237 in
210 get >>| check_message_timestamp >>= put 238 get >>| check_message_timestamp >>= put
211 239
240 (* Control line *)
241 let search dir word =
242 let* coords = Buffer.Action.(search dir word |> on_focused_buffer) in
243 match coords with
244 | None ->
245 let word = word |> Sequence.to_list |> String.of_list in
246 set_message (Printf.sprintf "Pattern not found: %s" word)
247 | Some (r, c) -> Buffer.Action.goto r c |> on_focused_buffer
248
212 (* Debug *) 249 (* Debug *)
213 let get_rendered e = (e.rendered, e) 250 let get_rendered e = (e.rendered, e)
214 let set_rendered r e = ((), { e with rendered = r }) 251 let set_rendered r e = ((), { e with rendered = r })
@@ -448,7 +485,28 @@ let handle_normal_command c =
448 | Shortcut (_, n, Join) -> 485 | Shortcut (_, n, Join) ->
449 let n = Option.(value ~default:2 n) in 486 let n = Option.(value ~default:2 n) in
450 Buffer.Action.join_lines ~n |> on_focused_buffer 487 Buffer.Action.join_lines ~n |> on_focused_buffer
451 (* Quit *) 488 (* Control *)
489 | Simple (Key ':' as k) ->
490 let c = Control.create k in
491 set_control_buffer c *> set_mode Control
492 | Simple (Key '/' as k) ->
493 let c = Control.create k in
494 (Zipper.(far_left &> push (true, Sequence.empty)) |> on_search_history)
495 *> set_control_buffer c *> set_mode Control
496 | Simple (Key '?' as k) ->
497 let c = Control.create k in
498 (Zipper.(far_left &> push (false, Sequence.empty)) |> on_search_history)
499 *> set_control_buffer c *> set_mode Control
500 | Shortcut (_, n, Search) -> (
501 let* h = get_search_history in
502 match Zipper.focus h with
503 | None -> set_message "No search history"
504 | Some (dir, word) -> search dir word |> repeat ?n)
505 | Shortcut (_, n, Search_rev) -> (
506 let* h = get_search_history in
507 match Zipper.focus h with
508 | None -> set_message "No search history"
509 | Some (dir, word) -> search (not dir) word |> repeat ?n)
452 | Simple (Ctrl 'Q') -> quit 0 510 | Simple (Ctrl 'Q') -> quit 0
453 (* Misc *) 511 (* Misc *)
454 | Simple (Key 'A') -> 512 | Simple (Key 'A') ->
@@ -467,7 +525,49 @@ let handle_normal_command c =
467 in 525 in
468 compute_action *> update_command_cue 526 compute_action *> update_command_cue
469 527
528let handle_control_command =
529 let open Command in
530 let open Action in
531 function
532 | Simple Arrow_down ->
533 let* c = get_control_buffer in
534 if Control.is_search c then
535 let* () = Zipper.left |> on_search_history
536 and* h = get_search_history in
537 match Zipper.focus h with
538 | None -> noop
539 | Some (_, word) -> Control.set_content word |> on_control_buffer
540 else failwith "Control line command history unimplemented!"
541 | Simple Arrow_left -> Control.move_left |> on_control_buffer
542 | Simple Arrow_right -> Control.move_right |> on_control_buffer
543 | Simple Arrow_up ->
544 let* c = get_control_buffer in
545 if Control.is_search c then
546 let* () = Zipper.right |> on_search_history
547 and* h = get_search_history in
548 match Zipper.focus h with
549 | None -> noop
550 | Some (_, word) -> Control.set_content word |> on_control_buffer
551 else failwith "Control line command history unimplemented!"
552 | Simple Backspace -> Control.delete_before |> on_control_buffer
553 | Simple Delete -> Control.delete_after |> on_control_buffer
554 | Simple Enter -> (
555 let* () = set_mode Normal and* c = get_control_buffer in
556 match Control.get_result c with
557 | Search (dir, word) -> search dir word *> set_last_search dir word
558 | No_result -> noop)
559 | Simple Esc -> (
560 let* () = set_mode Normal and* c = get_control_buffer in
561 match Control.get_result c with
562 | Search _ -> Zipper.(far_left &> pop &> snd) |> on_search_history
563 | No_result -> noop)
564 | Simple Home -> Control.bol |> on_control_buffer
565 | Simple End -> Control.eol |> on_control_buffer
566 | Type k -> Control.insert k |> on_control_buffer
567 | _ -> noop
568
470let handle_next_command m e = 569let handle_next_command m e =
570 let open Mode in
471 match m with 571 match m with
472 | Insert -> ( 572 | Insert -> (
473 match Sequence.next e.istream with 573 match Sequence.next e.istream with
@@ -477,7 +577,10 @@ let handle_next_command m e =
477 match Sequence.next e.nstream with 577 match Sequence.next e.nstream with
478 | None -> ((), e) 578 | None -> ((), e)
479 | Some (h, t) -> handle_normal_command h { e with nstream = t }) 579 | Some (h, t) -> handle_normal_command h { e with nstream = t })
480 | Control -> failwith "unimplemented" 580 | Control -> (
581 match Sequence.next e.istream with
582 | None -> ((), e)
583 | Some (h, t) -> handle_control_command h { e with istream = t })
481 584
482let handle_next_command = 585let handle_next_command =
483 let open Action in 586 let open Action in
diff --git a/lib/editorBuffer.ml b/lib/editorBuffer.ml
index 52b1115..1f363ef 100644
--- a/lib/editorBuffer.ml
+++ b/lib/editorBuffer.ml
@@ -88,8 +88,10 @@ module Action = struct
88 let ( let+ ) b f = map b ~f 88 let ( let+ ) b f = map b ~f
89 let get b = (b, b) 89 let get b = (b, b)
90 let put b _ = ((), b) 90 let put b _ = ((), b)
91 let modify ~f = get >>= (put &> f) 91 let modify ~f = get >>| f >>= put
92 let on_content f b = ((), { b with content = Result.map ~f b.content }) 92 let get_content b = (b.content, b)
93 let set_content c b = ((), { b with content = c })
94 let on_content f = get_content >>| Result.map ~f >>= set_content
93 95
94 let on_content_with_output ~default f b = 96 let on_content_with_output ~default f b =
95 match b.content with 97 match b.content with
@@ -143,6 +145,11 @@ module Action = struct
143 in 145 in
144 (horizontal left, horizontal right) 146 (horizontal left, horizontal right)
145 147
148 let goto r c =
149 let change_content = Zipper.(goto r &> map_focus (goto c)) |> on_content
150 and change_rendered = Zipper.goto r |> on_rendered in
151 change_content *> change_rendered
152
146 let bol ?(n = 0) = move_down ~n *> (map_focus far_left |> on_content) 153 let bol ?(n = 0) = move_down ~n *> (map_focus far_left |> on_content)
147 let eol ?(n = 0) = move_down ~n *> (map_focus far_right |> on_content) 154 let eol ?(n = 0) = move_down ~n *> (map_focus far_right |> on_content)
148 155
@@ -374,6 +381,55 @@ module Action = struct
374 in 381 in
375 bind_n_times ~n (change_content *> change_rendered) 382 bind_n_times ~n (change_content *> change_rendered)
376 383
384 let search forward word =
385 let rec tails s =
386 match Sequence.next s with
387 | None -> Sequence.empty
388 | Some (_, t) -> Sequence.shift_right (tails t) s
389 and prefix p l =
390 match Sequence.(next p, next l) with
391 | Some (ph, pt), Some (lh, lt) when Char.(ph = lh) -> prefix pt lt
392 | None, _ -> true
393 | _ -> false
394 in
395 let search_line w l =
396 Sequence.findi ~f:(fun _ -> prefix w) (tails l) |> Option.map ~f:fst
397 in
398 let* b = get in
399 let cr, cc = cursor ~rendered:false b in
400 let* c = get_content in
401 match c with
402 | Error _ -> return None
403 | Ok c -> (
404 if forward then
405 match Sequence.next (Zipper.after c) with
406 | None -> return None
407 | Some (h, t) -> (
408 match Zipper.(h |> right |> after) |> search_line word with
409 | Some i -> return (Some (cr, cc + i + 1))
410 | None ->
411 let f r z =
412 z |> to_seq |> search_line word
413 |> Option.map ~f:(fun c -> (cr + r + 1, c))
414 in
415 return (Sequence.find_mapi t ~f))
416 else
417 let word = Sequence.(word |> to_list_rev |> of_list) in
418 let wlen = Sequence.length word in
419 match Zipper.(c |> right |> before) |> Sequence.next with
420 | None -> return None
421 | Some (h, t) -> (
422 match h |> Zipper.before |> search_line word with
423 | Some i -> return (Some (cr, cc - wlen - i))
424 | None ->
425 let f r z =
426 let z = z |> far_right in
427 let len = left_length z in
428 z |> before |> search_line word
429 |> Option.map ~f:(fun c -> (cr - r - 1, len - wlen - c))
430 in
431 return (Sequence.find_mapi t ~f)))
432
377 (* let save_history_to ?(clear = true) r = () *) 433 (* let save_history_to ?(clear = true) r = () *)
378end 434end
379 435
diff --git a/lib/mode.ml b/lib/mode.ml
new file mode 100644
index 0000000..86f76d5
--- /dev/null
+++ b/lib/mode.ml
@@ -0,0 +1,3 @@
1type t = Normal | Insert | Control
2
3let to_string = function Insert -> " I " | Normal -> " N " | Control -> " C "
diff --git a/lib/modes.ml b/lib/modes.ml
deleted file mode 100644
index 3d0e354..0000000
--- a/lib/modes.ml
+++ /dev/null
@@ -1,18 +0,0 @@
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