diff options
| author | Federico Igne <undyamon@disroot.org> | 2024-01-28 01:22:52 +0100 |
|---|---|---|
| committer | Federico Igne <undyamon@disroot.org> | 2024-01-28 01:22:52 +0100 |
| commit | 53916fc3f7dcbefd90e3d0340a2a8f32bf331d1d (patch) | |
| tree | baf9b5d6bdfdc77d0338e230adf64f7f52aefa4e | |
| parent | 722a3d4d2d0d0630f57cfbfdddc229dee341505b (diff) | |
| download | sandy-53916fc3f7dcbefd90e3d0340a2a8f32bf331d1d.tar.gz sandy-53916fc3f7dcbefd90e3d0340a2a8f32bf331d1d.zip | |
feat: add plain search functionality (with history)
| -rw-r--r-- | lib/command.ml | 17 | ||||
| -rw-r--r-- | lib/control.ml | 32 | ||||
| -rw-r--r-- | lib/editor.ml | 137 | ||||
| -rw-r--r-- | lib/editorBuffer.ml | 60 | ||||
| -rw-r--r-- | lib/mode.ml | 3 | ||||
| -rw-r--r-- | lib/modes.ml | 18 |
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 | ||
| 18 | type scope = Line | To_bol | To_eol | Down | Left | Right | Up | 20 | type scope = Line | To_bol | To_eol | Down | Left | Right | Up |
| 19 | 21 | ||
| @@ -70,7 +72,18 @@ let to_scope = function | |||
| 70 | let is_simple_movement k = List.mem ~equal:Poly.equal simple_movements k | 72 | let is_simple_movement k = List.mem ~equal:Poly.equal simple_movements k |
| 71 | 73 | ||
| 72 | let instant_operation = | 74 | let 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 | ||
| 75 | let chord_operation = [ Key 'c'; Key 'd'; Key 'y' ] | 88 | let 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 @@ | |||
| 1 | open Base | ||
| 2 | |||
| 3 | type t = { prompt : Key.t; content : char Zipper.t } | ||
| 4 | type result = Search of bool * char Sequence.t | No_result | ||
| 5 | |||
| 6 | let create prompt = { prompt; content = Zipper.empty } | ||
| 7 | |||
| 8 | let 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 | |||
| 13 | let is_search c = match c.prompt with Key '/' | Key '?' -> true | _ -> false | ||
| 14 | |||
| 15 | let cursor c = | ||
| 16 | String.length (Key.to_string c.prompt) + Zipper.left_length c.content + 1 | ||
| 17 | |||
| 18 | let 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 | |||
| 25 | let set_content s c = { c with content = Zipper.(of_seq s |> far_right) } | ||
| 26 | let move_left c = { c with content = Zipper.left c.content } | ||
| 27 | let move_right c = { c with content = Zipper.right c.content } | ||
| 28 | let delete_before c = { c with content = Zipper.pop_before c.content |> snd } | ||
| 29 | let delete_after c = { c with content = Zipper.pop c.content |> snd } | ||
| 30 | let bol c = { c with content = Zipper.far_left c.content } | ||
| 31 | let eol c = { c with content = Zipper.far_right c.content } | ||
| 32 | let 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 | |||
| 2 | module Buffer = EditorBuffer | 2 | module Buffer = EditorBuffer |
| 3 | open Util | 3 | open Util |
| 4 | 4 | ||
| 5 | type mode = Normal | Insert | Control | ||
| 6 | |||
| 7 | type selection = | 5 | type 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 | ||
| 14 | type editor = { | 12 | type 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 | ||
| 31 | type t = editor | 31 | type 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 | ||
| 54 | let string_of_mode = function | ||
| 55 | | Insert -> " I " | ||
| 56 | | Normal -> " N " | ||
| 57 | | Control -> " C " | ||
| 58 | |||
| 59 | let statusbar e = | 56 | let 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 | ||
| 528 | let 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 | |||
| 470 | let handle_next_command m e = | 569 | let 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 | ||
| 482 | let handle_next_command = | 585 | let 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 = () *) |
| 378 | end | 434 | end |
| 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 @@ | |||
| 1 | type t = Normal | Insert | Control | ||
| 2 | |||
| 3 | let 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 @@ | |||
| 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 | ||
