stitch

Note taking for messy minimalists

git clone git://mccd.space/stitch
commit fbbbdca67c79145d43d6fdd59202a36fddfc8cd0
parent 256397061fe5ae5649b4569c45bb53c8e45e0cbe
Author: Marc Coquand <marc@mccd.space>
Date:   Sat, 18 May 2024 13:13:29 -0500

Add command execution to TODO

Diffstat:
Mlib/todos.ml | 64+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 59 insertions(+), 5 deletions(-)
diff --git a/lib/todos.ml b/lib/todos.ml
@@ -10,6 +10,8 @@ type state =
   ; content_pretty : string array
   ; goto_headlines : (unit -> unit) -> unit
   ; goto_done : (unit -> unit) -> unit
+  ; output : string option
+  ; tag : string
   }
 
 let title = I.strf ~attr:A.(st bold) "%s" "Todo View" |> I.pad ~l:0 ~t:0
@@ -24,6 +26,8 @@ let init ~goto_done ~goto_headlines =
   ; content_pretty = content_pretty |> Array.of_list
   ; goto_headlines
   ; goto_done
+  ; output = None
+  ; tag = ""
   }
 
 
@@ -35,11 +39,19 @@ let load_todos () =
 
 let rec render
   t
-  ({ pos; scroll; content; content_pretty; goto_headlines; goto_done } as state)
+  ({ pos; scroll; content; content_pretty; goto_headlines; goto_done; output; _ } as state)
   =
   let x, y = pos in
+  let size_x, size_y = Common.Term.size t in
   let content_position = y - content_start in
   let img =
+    let output_info =
+      match output with
+      | Some line ->
+        I.strf "%s%s" (String.escaped line) (String.make size_x ' ')
+        |> I.pad ~t:(size_y - 1)
+      | None -> I.empty
+    in
     let dot =
       if Array.length content_pretty = 0
       then I.empty
@@ -53,18 +65,17 @@ let rec render
         (Array.to_seq content_pretty |> Seq.drop scroll |> Array.of_seq)
     in
     let open I in
-    Array.fold_left (fun sum el -> el </> sum) (title </> dot) elements
+    Array.fold_left (fun sum el -> el </> sum) (title </> dot </> output_info) elements
   in
-  let _, size_y = Common.Term.size t in
   Common.Term.image t img;
   let content_end = Array.length content_pretty + (content_start - 1) in
   let scroll_up () =
     let scroll = if y - content_start - scroll = 0 then max (scroll - 1) 0 else scroll in
-    render t { state with pos = x, max (y - 1) content_start; scroll }
+    render t { state with pos = x, max (y - 1) content_start; scroll; output = None }
   in
   let scroll_down () =
     let scroll = if y - scroll >= size_y - 1 then scroll + 1 else scroll in
-    render t { state with pos = x, min (y + 1) content_end; scroll }
+    render t { state with pos = x, min (y + 1) content_end; scroll; output = None }
   in
   match Common.Term.event t with
   | `End | `Key (`Escape, []) | `Key (`ASCII 'q', []) | `Key (`ASCII 'C', [ `Ctrl ]) -> ()
@@ -103,6 +114,7 @@ let rec render
               { state with
                 content = content |> Array.of_list
               ; content_pretty = content_pretty |> Array.of_list
+              ; tag
               })
       ; on_cancel =
           (fun _ ->
@@ -113,6 +125,48 @@ let rec render
     Input_prompt.render t input_state
   | `Key (`ASCII 'j', []) | `Key (`ASCII 'N', [ `Ctrl ]) -> scroll_down ()
   | `Key (`ASCII 'k', []) | `Key (`ASCII 'P', [ `Ctrl ]) -> scroll_up ()
+  | `Key (`ASCII '!', []) ->
+    let selected_file, _ = Array.get content content_position in
+    let selected_file = Grep.execution_directory ^ "/" ^ selected_file in
+    let content =
+      Array.map (fun (_, c) -> c) content |> Array.to_list |> String.concat "\n"
+    in
+    let (input_state : Input_prompt.state) =
+      { screen = img
+      ; user_input = ""
+      ; prompt = "COMMAND"
+      ; on_enter =
+          (fun command ->
+            if String.equal (String.trim command) String.empty
+            then (
+              Common.Term.cursor t None;
+              render t state)
+            else
+              Arbitrary_command.run
+                t
+                ~command:(Scanf.unescaped command)
+                ~content
+                ~selected_file
+                ~on_return:(fun result ->
+                  let content, content_pretty = load_todos () in
+                  let y = min (List.length content_pretty + content_start) y in
+                  Common.Term.cursor t None;
+                  render
+                    t
+                    { state with
+                      content = Array.of_list content
+                    ; content_pretty = Array.of_list content_pretty
+                    ; pos = 0, y
+                    ; scroll = 0
+                    ; output = Some result
+                    }))
+      ; on_cancel =
+          (fun _ ->
+            Common.Term.cursor t None;
+            render t state)
+      }
+    in
+    Input_prompt.render t input_state
   | `Key (`ASCII 'T', [ `Ctrl ]) ->
     let selected_file, _ = Array.get content (y - content_start) in
     let _ = Grep.toggle_done selected_file in