stitch

Note taking for messy minimalists

git clone git://mccd.space/stitch
commit db4e445ee0f29c1179b5d0746217e5b5525ff3e2
parent 47be0ea3eeb774102f02b5f85ee974afe6a8170e
Author: Marc Coquand <marc@mccd.space>
Date:   Wed, 15 May 2024 11:17:48 -0500

State handling done

Diffstat:
Alib/compontent.ml | 16++++++++++++++++
Mlib/headlines.ml | 38++++++++++++++++++++++++++------------
Mlib/stitched_article.ml | 41+++++++++++++++++++++++------------------
Mlib/todos.ml | 4++--
4 files changed, 67 insertions(+), 32 deletions(-)
diff --git a/lib/compontent.ml b/lib/compontent.ml
@@ -0,0 +1,16 @@
+open Notty
+
+let current_line size_x y scroll (el : Grep.display_type) i =
+  match el with
+  | Bold el ->
+    if i == y - scroll
+    then (
+      let fill = String.make (max (size_x - String.length el) 0) ' ' in
+      I.strf "%s%s" ~attr:A.(st bold ++ st reverse) el fill |> I.pad ~l:0 ~t:i)
+    else I.strf "%s" ~attr:A.(st bold) el |> I.pad ~l:0 ~t:i
+  | Normal el ->
+    if i == y - scroll
+    then (
+      let fill = String.make (max (size_x - String.length el) 0) ' ' in
+      I.strf "%s%s" ~attr:A.(st reverse) el fill |> I.pad ~l:0 ~t:i)
+    else I.strf "%s" el |> I.pad ~l:0 ~t:i
diff --git a/lib/headlines.ml b/lib/headlines.ml
@@ -11,10 +11,11 @@ type state =
   ; scroll : int
   ; content : (string * string) array
   ; content_pretty : string array
+  ; todo_state : Todos.state option
   }
 
 (* TODO: Add page title *)
-let rec render t ({ pos; scroll; content; content_pretty } as state) =
+let rec render t ({ pos; scroll; content; content_pretty; todo_state } as state) =
   let x, y = pos in
   let img =
     let dot = I.string A.(st bold) ">" |> I.pad ~l:0 ~t:(y - scroll)
@@ -51,15 +52,25 @@ let rec render t ({ pos; scroll; content; content_pretty } as state) =
     render t { state with pos = 0, min y content_length }
   | `Key (`ASCII '?', []) -> Help_screen.render t { go_back = (fun () -> render t state) }
   | `Key (`ASCII '2', []) ->
-    let todo_content = Grep.get_todos () |> Grep.parse_todo_string in
-    let todo_pretty = Grep.pretty_format_todo todo_content in
-    let todo_state =
-      Todos.init
-        ~goto_headlines:(fun () -> render t state)
-        ~content:(todo_content |> Array.of_list)
-        ~content_pretty:(todo_pretty |> Array.of_list)
-    in
-    Todos.render t todo_state
+    (match todo_state with
+     | Some todo_state ->
+       Todos.render
+         t
+         { todo_state with
+           goto_headlines =
+             (fun new_state -> render t { state with todo_state = Some new_state })
+         }
+     | None ->
+       let todo_content = Grep.get_todos () |> Grep.parse_todo_string in
+       let todo_pretty = Grep.pretty_format_todo todo_content in
+       let todo_state =
+         Todos.init
+           ~goto_headlines:(fun new_state ->
+             render t { state with todo_state = Some new_state })
+           ~content:(todo_content |> Array.of_list)
+           ~content_pretty:(todo_pretty |> Array.of_list)
+       in
+       Todos.render t todo_state)
   | `Key (`ASCII 's', []) ->
     let content =
       Array.map
@@ -78,6 +89,7 @@ let rec render t ({ pos; scroll; content; content_pretty } as state) =
       ; content_pretty = full_content_pretty
       ; scroll = 0
       ; go_back = (fun () -> render t state)
+      ; todo_state
       }
   | `Key (`ASCII 'r', []) ->
     let (input_state : Input_screen.state) =
@@ -89,7 +101,7 @@ let rec render t ({ pos; scroll; content; content_pretty } as state) =
             let content = Grep.get_tagged_headlines tag () |> Grep.parse_headlines in
             let content_pretty = Grep.pretty_format content in
             Common.Term.cursor t None;
-            render t { content; content_pretty; pos = 0, 0; scroll = 0 })
+            render t { state with content; content_pretty; pos = 0, 0; scroll = 0 })
       ; on_cancel =
           (fun _ ->
             Common.Term.cursor t None;
@@ -148,4 +160,6 @@ let start (tag : string) () =
     exit 0)
   else (
     let content_pretty = content |> Grep.pretty_format in
-    render (Common.Term.create ()) { pos = 0, 0; scroll = 0; content; content_pretty })
+    render
+      (Common.Term.create ())
+      { pos = 0, 0; scroll = 0; content; content_pretty; todo_state = None })
diff --git a/lib/stitched_article.ml b/lib/stitched_article.ml
@@ -10,32 +10,17 @@ type state =
   ; content : (string * int * string * int) array
   ; go_back : unit -> unit
   ; content_pretty : Grep.display_type array
+  ; todo_state : Todos.state option
   }
 
-let render_line size_x y scroll (el : Grep.display_type) i =
-  match el with
-  | Bold el ->
-    if i == y - scroll
-    then (
-      let fill = String.make (max (size_x - String.length el) 0) ' ' in
-      I.strf "%s%s" ~attr:A.(st bold ++ st reverse) el fill |> I.pad ~l:0 ~t:i)
-    else I.strf "%s" ~attr:A.(st bold) el |> I.pad ~l:0 ~t:i
-  | Normal el ->
-    if i == y - scroll
-    then (
-      let fill = String.make (max (size_x - String.length el) 0) ' ' in
-      I.strf "%s%s" ~attr:A.(st reverse) el fill |> I.pad ~l:0 ~t:i)
-    else I.strf "%s" el |> I.pad ~l:0 ~t:i
-
-
 (* TODO: Use grep -l to filter notes by regexp and rerender those files*)
-let rec render t ({ pos; scroll; content_pretty; go_back; content } as state) =
+let rec render t ({ pos; scroll; content_pretty; go_back; content; todo_state } as state) =
   let size_x, size_y = Common.Term.size t in
   let x, y = pos in
   let img =
     let elements =
       Array.mapi
-        (fun i el -> render_line size_x y scroll el i)
+        (fun i el -> Compontent.current_line size_x y scroll el i)
         (* TODO: Fix this ugly slow conversion *)
         (Array.to_seq content_pretty |> Seq.drop scroll |> Array.of_seq)
     in
@@ -65,6 +50,26 @@ let rec render t ({ pos; scroll; content_pretty; go_back; content } as state) =
   | `Key (`ASCII 'j', []) | `Key (`ASCII 'N', [ `Ctrl ]) -> scroll_down ()
   | `Key (`ASCII 'k', []) | `Key (`ASCII 'P', [ `Ctrl ]) -> scroll_up ()
   | `Key (`ASCII '?', []) -> Help_screen.render t { go_back = (fun () -> render t state) }
+  | `Key (`ASCII '2', []) ->
+    (match todo_state with
+     | Some todo_state ->
+       Todos.render
+         t
+         { todo_state with
+           goto_headlines =
+             (fun new_state -> render t { state with todo_state = Some new_state })
+         }
+     | None ->
+       let todo_content = Grep.get_todos () |> Grep.parse_todo_string in
+       let todo_pretty = Grep.pretty_format_todo todo_content in
+       let todo_state =
+         Todos.init
+           ~goto_headlines:(fun new_state ->
+             render t { state with todo_state = Some new_state })
+           ~content:(todo_content |> Array.of_list)
+           ~content_pretty:(todo_pretty |> Array.of_list)
+       in
+       Todos.render t todo_state)
   | `Key (`ASCII 'e', []) | `Key (`Enter, []) ->
     (* Editor might be set with extra args, in that case we need to separate these *)
     let[@warning "-8"] (editor :: args) =
diff --git a/lib/todos.ml b/lib/todos.ml
@@ -8,7 +8,7 @@ type state =
   ; scroll : int
   ; content : (string * string) array
   ; content_pretty : string array
-  ; goto_headlines : unit -> unit
+  ; goto_headlines : state -> unit
   }
 
 let init ~goto_headlines ~content ~content_pretty =
@@ -75,7 +75,7 @@ let rec render t ({ pos; scroll; content; content_pretty; goto_headlines } as st
       }
     in
     Input_screen.render t input_state
-  | `Key (`ASCII '1', []) -> goto_headlines ()
+  | `Key (`ASCII '1', []) -> goto_headlines state
   | `Key (`ASCII 'j', []) | `Key (`ASCII 'N', [ `Ctrl ]) -> scroll_down ()
   | `Key (`ASCII 'k', []) | `Key (`ASCII 'P', [ `Ctrl ]) -> scroll_up ()
   | `Key (`ASCII 't', []) ->