From 53916fc3f7dcbefd90e3d0340a2a8f32bf331d1d Mon Sep 17 00:00:00 2001 From: Federico Igne Date: Sun, 28 Jan 2024 01:22:52 +0100 Subject: feat: add plain search functionality (with history) --- lib/editor.ml | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 120 insertions(+), 17 deletions(-) (limited to 'lib/editor.ml') 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 module Buffer = EditorBuffer open Util -type mode = Normal | Insert | Control - type selection = | Empty | Glyphwise of char Sequence.t Sequence.t @@ -13,7 +11,7 @@ type cursor = int * int type editor = { term : Terminal.state; - mode : mode; + mode : Mode.t; offset : int * int; cursor : cursor; buffer : Buffer.t; @@ -26,6 +24,8 @@ type editor = { message_duration : float; pending_command : string; registers : selection Array.t; + control : Control.t; + search_history : (bool * char Sequence.t) Zipper.t; } type t = editor @@ -49,19 +49,16 @@ let init (c : Config.t) : editor = message_duration = 5.; pending_command = ""; registers = Array.create ~len:128 Empty; + control = Control.create Key.Nul; + search_history = Zipper.empty; } -let string_of_mode = function - | Insert -> " I " - | Normal -> " N " - | Control -> " C " - let statusbar e = let open Text in (* let open Sequence.Infix in *) let w = e.term.size |> snd in let status = - let mode = e.mode |> string_of_mode |> sequence_of_string in + let mode = e.mode |> Mode.to_string |> sequence_of_string in let lsize = Sequence.length mode and c = e.buffer.kind |> Buffer.string_of_kind |> sequence_of_string and br, bc = Buffer.size e.buffer @@ -79,9 +76,12 @@ let statusbar e = let rsize = Sequence.length nav in spread ~l:(bold mode) ~lsize ~c ~r:(bold nav) ~rsize ~fill:' ' w |> invert and control = - let msg = Option.value ~default:"" e.message |> sequence_of_string - and cmd = e.pending_command |> sequence_of_string in - spread ~l:msg ~r:cmd ~fill:' ' w + match e.mode with + | Control -> Control.render e.control + | _ -> + let msg = Option.value ~default:"" e.message |> sequence_of_string + and cmd = e.pending_command |> sequence_of_string in + spread ~l:msg ~r:cmd ~fill:' ' w in Sequence.(take (of_list [ status; control ]) e.status_size) @@ -117,8 +117,16 @@ module Action = struct let map = `Define_using_bind end) + let ( let+ ) e f = map e ~f let ( let* ) e f = bind e ~f let ( and* ) = both + + let rec repeat ?(n = 1) a = + match n with + | _ when n <= 0 -> return () + | 1 -> a + | _ -> a *> repeat ~n:(n - 1) a + let get e = (e, e) let put e _ = ((), e) let modify ~f e = ((), f e) @@ -149,8 +157,22 @@ module Action = struct let* b = get_focused_buffer in return (f b |> fst) + let get_control_buffer e = (e.control, e) + let set_control_buffer c e = ((), { e with control = c }) + + let on_control_buffer f = + let* c = get_control_buffer in + set_control_buffer (f c) + let get_mode e = (e.mode, e) let set_mode m e = ((), { e with mode = m }) + let get_search_history e = (e.search_history, e) + let set_search_history h e = ((), { e with search_history = h }) + let on_search_history f = get_search_history >>| f >>= set_search_history + + let set_last_search dir word = + Zipper.(far_left &> swap_focus (dir, word)) |> on_search_history + let get_terminal_size e = (e.term.size, e) let get_register ?(r = '"') e = @@ -179,8 +201,14 @@ module Action = struct |> Text.extend ~fill r |> Fn.flip Sequence.take (r - ssize) in - let screen = Sequence.append bufview status in - Terminal.redraw screen e.cursor + let screen = Sequence.append bufview status + and cursor = + let open Mode in + match e.mode with + | Control -> (r, Control.cursor e.control) + | _ -> e.cursor + in + Terminal.redraw screen cursor in get >>| aux @@ -189,7 +217,7 @@ module Action = struct (* Statusbar *) let set_message m e = - ((), { e with message = m; message_timestamp = Unix.time () }) + ((), { e with message = Some m; message_timestamp = Unix.time () }) let get_pending_command e = (e.pending_command, e) let set_pending_command p e = ((), { e with pending_command = p }) @@ -209,6 +237,15 @@ module Action = struct in get >>| check_message_timestamp >>= put + (* Control line *) + let search dir word = + let* coords = Buffer.Action.(search dir word |> on_focused_buffer) in + match coords with + | None -> + let word = word |> Sequence.to_list |> String.of_list in + set_message (Printf.sprintf "Pattern not found: %s" word) + | Some (r, c) -> Buffer.Action.goto r c |> on_focused_buffer + (* Debug *) let get_rendered e = (e.rendered, e) let set_rendered r e = ((), { e with rendered = r }) @@ -448,7 +485,28 @@ let handle_normal_command c = | Shortcut (_, n, Join) -> let n = Option.(value ~default:2 n) in Buffer.Action.join_lines ~n |> on_focused_buffer - (* Quit *) + (* Control *) + | Simple (Key ':' as k) -> + let c = Control.create k in + set_control_buffer c *> set_mode Control + | Simple (Key '/' as k) -> + let c = Control.create k in + (Zipper.(far_left &> push (true, Sequence.empty)) |> on_search_history) + *> set_control_buffer c *> set_mode Control + | Simple (Key '?' as k) -> + let c = Control.create k in + (Zipper.(far_left &> push (false, Sequence.empty)) |> on_search_history) + *> set_control_buffer c *> set_mode Control + | Shortcut (_, n, Search) -> ( + let* h = get_search_history in + match Zipper.focus h with + | None -> set_message "No search history" + | Some (dir, word) -> search dir word |> repeat ?n) + | Shortcut (_, n, Search_rev) -> ( + let* h = get_search_history in + match Zipper.focus h with + | None -> set_message "No search history" + | Some (dir, word) -> search (not dir) word |> repeat ?n) | Simple (Ctrl 'Q') -> quit 0 (* Misc *) | Simple (Key 'A') -> @@ -467,7 +525,49 @@ let handle_normal_command c = in compute_action *> update_command_cue +let handle_control_command = + let open Command in + let open Action in + function + | Simple Arrow_down -> + let* c = get_control_buffer in + if Control.is_search c then + let* () = Zipper.left |> on_search_history + and* h = get_search_history in + match Zipper.focus h with + | None -> noop + | Some (_, word) -> Control.set_content word |> on_control_buffer + else failwith "Control line command history unimplemented!" + | Simple Arrow_left -> Control.move_left |> on_control_buffer + | Simple Arrow_right -> Control.move_right |> on_control_buffer + | Simple Arrow_up -> + let* c = get_control_buffer in + if Control.is_search c then + let* () = Zipper.right |> on_search_history + and* h = get_search_history in + match Zipper.focus h with + | None -> noop + | Some (_, word) -> Control.set_content word |> on_control_buffer + else failwith "Control line command history unimplemented!" + | Simple Backspace -> Control.delete_before |> on_control_buffer + | Simple Delete -> Control.delete_after |> on_control_buffer + | Simple Enter -> ( + let* () = set_mode Normal and* c = get_control_buffer in + match Control.get_result c with + | Search (dir, word) -> search dir word *> set_last_search dir word + | No_result -> noop) + | Simple Esc -> ( + let* () = set_mode Normal and* c = get_control_buffer in + match Control.get_result c with + | Search _ -> Zipper.(far_left &> pop &> snd) |> on_search_history + | No_result -> noop) + | Simple Home -> Control.bol |> on_control_buffer + | Simple End -> Control.eol |> on_control_buffer + | Type k -> Control.insert k |> on_control_buffer + | _ -> noop + let handle_next_command m e = + let open Mode in match m with | Insert -> ( match Sequence.next e.istream with @@ -477,7 +577,10 @@ let handle_next_command m e = match Sequence.next e.nstream with | None -> ((), e) | Some (h, t) -> handle_normal_command h { e with nstream = t }) - | Control -> failwith "unimplemented" + | Control -> ( + match Sequence.next e.istream with + | None -> ((), e) + | Some (h, t) -> handle_control_command h { e with istream = t }) let handle_next_command = let open Action in -- cgit v1.2.3