aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Coquand <marc@mccd.space>2024-05-15 14:05:03 -0500
committerMarc Coquand <marc@mccd.space>2024-05-15 14:05:03 -0500
commit3f696169ab1a560d94d169c1a5b744346da4c081 (patch)
treed2aa5d6b0b9d70a594ede7795e423250a92729e1
parent961339f0bd28c0f30bdb3c995a27927def8a991e (diff)
downloadstitch-3f696169ab1a560d94d169c1a5b744346da4c081.tar.gz
stitch-3f696169ab1a560d94d169c1a5b744346da4c081.tar.bz2
stitch-3f696169ab1a560d94d169c1a5b744346da4c081.zip
Add done view + visual
-rw-r--r--lib/done.ml141
-rw-r--r--lib/grep.ml12
-rw-r--r--lib/headlines.ml23
-rw-r--r--lib/help_screen.ml11
-rw-r--r--lib/stitch.ml35
-rw-r--r--lib/stitched_article.ml15
-rw-r--r--lib/todos.ml36
7 files changed, 240 insertions, 33 deletions
diff --git a/lib/done.ml b/lib/done.ml
new file mode 100644
index 0000000..5daad60
--- /dev/null
+++ b/lib/done.ml
@@ -0,0 +1,141 @@
+module Grep = Grep
+module Common = Common
+open Notty
+module Help_screen = Help_screen
+
+type state =
+ { pos : int * int
+ ; scroll : int
+ ; content : (string * string) array
+ ; content_pretty : string array
+ ; goto_headlines : (unit -> unit) -> unit
+ ; goto_todo : (unit -> unit) -> unit
+ }
+
+let title = I.strf ~attr:A.(st bold) "%s" "Done View" |> I.pad ~l:0 ~t:0
+let content_start = 2
+
+let init ~goto_todo ~goto_headlines =
+ let content = Grep.get_done () |> Grep.parse_todo_string in
+ let content_pretty = Grep.pretty_format_todo content in
+ { pos = 0, content_start
+ ; scroll = 0
+ ; content = content |> Array.of_list
+ ; content_pretty = content_pretty |> Array.of_list
+ ; goto_headlines
+ ; goto_todo
+ }
+
+
+let load_done () =
+ let done_content = Grep.get_done () |> Grep.parse_todo_string in
+ let done_pretty = Grep.pretty_format_todo done_content in
+ done_content, done_pretty
+
+
+let rec render
+ t
+ ({ pos; scroll; content; content_pretty; goto_headlines; goto_todo } as state)
+ =
+ let x, y = pos in
+ let content_position = y - content_start in
+ let img =
+ let dot = I.string A.(st bold) ">" |> I.pad ~l:0 ~t:(y - scroll)
+ and elements =
+ Array.mapi
+ (fun i el ->
+ if i == y - scroll
+ then I.strf "%s" el |> I.pad ~l:2 ~t:(i + content_start)
+ else I.strf "%s" el |> I.pad ~l:2 ~t:(i + content_start))
+ (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
+ 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 }
+ 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 }
+ in
+ match Common.Term.event t with
+ | `End | `Key (`Escape, []) | `Key (`ASCII 'q', []) | `Key (`ASCII 'C', [ `Ctrl ]) -> ()
+ | `Mouse (`Press (`Scroll s), _, _) ->
+ (match s with
+ | `Down -> scroll_down ()
+ | `Up -> scroll_up ())
+ | `Resize _ -> render t state
+ | `Mouse ((`Press _ | `Drag), (_, y), _) ->
+ render t { state with pos = 0, min y content_end }
+ | `Key (`ASCII '?', []) -> Help_screen.render t { go_back = (fun () -> render t state) }
+ | `Key (`ASCII 's', []) ->
+ let (input_state : Input_screen.state) =
+ { screen = img
+ ; user_input = ""
+ ; prompt = "GREP: "
+ ; on_enter =
+ (fun tag ->
+ 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 { state with content; content_pretty })
+ ; on_cancel =
+ (fun _ ->
+ Common.Term.cursor t None;
+ render t state)
+ }
+ in
+ Input_screen.render t input_state
+ | `Key (`ASCII '1', []) -> goto_headlines (fun () -> render t state)
+ | `Key (`ASCII '2', []) -> goto_todo (fun () -> render t state)
+ | `Key (`ASCII 'j', []) | `Key (`ASCII 'N', [ `Ctrl ]) -> scroll_down ()
+ | `Key (`ASCII 'k', []) | `Key (`ASCII 'P', [ `Ctrl ]) -> scroll_up ()
+ | `Key (`ASCII 't', []) ->
+ let selected_file, _ = Array.get content (y - content_start) in
+ let _ = Grep.toggle_todo selected_file in
+ let content, content_pretty = load_done () in
+ render
+ t
+ { state with
+ content = content |> Array.of_list
+ ; content_pretty = Array.of_list content_pretty
+ }
+ | `Key (`Arrow d, _) ->
+ (match d with
+ | `Up -> scroll_up ()
+ | `Down -> scroll_down ()
+ | _ -> render t 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) =
+ String.split_on_char ' ' (Sys.getenv "EDITOR")
+ in
+ let selected_file, _ = Array.get content content_position in
+ let full_path_file = Grep.execution_directory ^ "/" ^ selected_file in
+ let full_args = Array.append (Array.of_list args) [| full_path_file |] in
+ Common.Term.cursor t (Some (0, 0));
+ let _ =
+ Unix.create_process_env
+ editor
+ full_args
+ (Unix.environment ())
+ Unix.stdin
+ Unix.stdout
+ Unix.stderr
+ in
+ let rec run_editor () =
+ match Unix.wait () with
+ | _, _ ->
+ Common.Term.cursor t None;
+ render t state
+ (* Capture resizing events *)
+ | exception Unix.Unix_error (Unix.EINTR, _, _) -> run_editor ()
+ | exception Unix.Unix_error (_, _, _) -> failwith "ERROR"
+ in
+ run_editor ()
+ | _ -> render t state
diff --git a/lib/grep.ml b/lib/grep.ml
index 6ed2a98..0e8294a 100644
--- a/lib/grep.ml
+++ b/lib/grep.ml
@@ -52,6 +52,14 @@ let pad str n =
exception Not_A_Tuple of string * string
(* todo parsing *)
+let done_get_args = [ grep_cmd; done_pattern_regexp; "-H"; "-r"; "-n"; "--no-messages" ]
+
+let get_done () =
+ let open Shexp_process in
+ let open Shexp_process.Infix in
+ run_calls (call done_get_args |- call [ "sort"; "-n"; "-r" ])
+
+
let todo_get_args = [ grep_cmd; todo_pattern_regexp; "-H"; "-r"; "-n"; "--no-messages" ]
let get_todos () =
@@ -158,6 +166,8 @@ let get_tagged_headlines tag () =
(find_sort_name ()
|- call headline_args
|- call [ grep_cmd; "--no-messages"; "-E"; tag ]
+ |- call filter_todos_args
+ |- call filter_done_args
|- call [ "sort"; "-n"; "-r" ]
|- read_all))
@@ -231,6 +241,6 @@ let pretty_print_parsed_content parsed_files =
List.concat_map
(fun (file_name, line_number, line_content, _) ->
if line_number == 0
- then [ Bold ("------- " ^ file_name); Normal line_content ]
+ then [ Bold ("--------- " ^ file_name); Normal line_content ]
else [ Normal (padding ^ line_content) ])
parsed_files
diff --git a/lib/headlines.ml b/lib/headlines.ml
index f61d8b9..51bcc6b 100644
--- a/lib/headlines.ml
+++ b/lib/headlines.ml
@@ -11,9 +11,10 @@ type state =
; content : (string * string) array
; content_pretty : string array
; goto_todos_view : (unit -> unit) -> unit
+ ; goto_done_view : (unit -> unit) -> unit
}
-let init ~goto_todos_view ~regexp =
+let init ~goto_done_view ~goto_todos_view ~regexp =
let tag = if String.equal regexp "" then None else Some regexp in
let content =
match tag with
@@ -26,15 +27,19 @@ let init ~goto_todos_view ~regexp =
exit 0)
else (
let content_pretty = content |> Grep.pretty_format in
- { pos = 0, 2; scroll = 0; content; content_pretty; goto_todos_view })
+ { pos = 0, 2; scroll = 0; content; content_pretty; goto_done_view; goto_todos_view })
+let title = I.strf ~attr:A.(st bold) "%s" "Note View" |> I.pad ~l:0 ~t:0
+
(* TODO: Add page title *)
-let rec render t ({ pos; scroll; content; content_pretty; goto_todos_view } as state) =
- let title = I.strf ~attr:A.(st underline) "%s" "Note View" |> I.pad ~l:0 ~t:0 in
+let rec render
+ t
+ ({ pos; scroll; content; content_pretty; goto_todos_view; goto_done_view } as state)
+ =
let content_start = 2 in
let x, y = pos in
- let content_position = y - content_start + 1 in
+ let content_position = y - content_start in
let img =
let dot = I.string A.(st bold) ">" |> I.pad ~l:0 ~t:(y - scroll)
and elements =
@@ -56,7 +61,6 @@ let rec render t ({ pos; scroll; content; content_pretty; goto_todos_view } as s
render t { state with pos = x, max (y - 1) content_start; scroll }
in
let scroll_down () =
- print_endline (Int.to_string y);
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 }
in
@@ -71,6 +75,7 @@ let rec render t ({ pos; scroll; content; content_pretty; goto_todos_view } as s
render t { state with pos = 0, min y content_end }
| `Key (`ASCII '?', []) -> Help_screen.render t { go_back = (fun () -> render t state) }
| `Key (`ASCII '2', []) -> goto_todos_view (fun () -> render t state)
+ | `Key (`ASCII '3', []) -> goto_done_view (fun () -> render t state)
| `Key (`ASCII 's', []) ->
let content =
Array.map
@@ -84,7 +89,7 @@ let rec render t ({ pos; scroll; content; content_pretty; goto_todos_view } as s
in
Stitched_article.render
t
- { pos = 0, 0
+ { pos = 0, Stitched_article.content_start
; content = full_content |> Array.of_list
; content_pretty = full_content_pretty
; scroll = 0
@@ -101,7 +106,9 @@ let rec render t ({ pos; scroll; content; content_pretty; goto_todos_view } as s
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 { state with content; content_pretty; pos = 0, 0; scroll = 0 })
+ render
+ t
+ { state with content; content_pretty; pos = 0, content_start; scroll = 0 })
; on_cancel =
(fun _ ->
Common.Term.cursor t None;
diff --git a/lib/help_screen.ml b/lib/help_screen.ml
index b07cad2..b74e91c 100644
--- a/lib/help_screen.ml
+++ b/lib/help_screen.ml
@@ -4,15 +4,18 @@ open Notty
type state = { go_back : unit -> unit }
let render_info =
- let title = I.strf ~attr:A.(st bold ++ st underline) "%s" "Stitch" |> I.pad ~l:2 ~t:0 in
+ let title = I.strf ~attr:A.(st bold) "%s" "Stitch" |> I.pad ~l:0 ~t:0 in
let description =
I.strf
~attr:A.(st bold)
"%s"
- "Minimal Note Composer. Run with stitch --help for more info."
- |> I.pad ~l:2 ~t:1
+ "Minimal Note Composer. Run stitch --help for more info."
+ |> I.pad ~l:2 ~t:2
+ in
+ let license =
+ I.strf "%s" "Made by Marc Coquand (https://mccd.space). Licensed under BSD-3."
+ |> I.pad ~l:2 ~t:4
in
- let license = I.strf "%s" "Licensed under BSD-3" |> I.pad ~l:2 ~t:3 in
let open I in
5, title </> description </> license
diff --git a/lib/stitch.ml b/lib/stitch.ml
index e49ed9c..2fc0a9c 100644
--- a/lib/stitch.ml
+++ b/lib/stitch.ml
@@ -5,7 +5,8 @@ module Headlines = Headlines
let start (tag : string) () =
(* This is a rather funky state management that isn't maybe entirely functional.
- What we do is store a function for each view that restores it's state.
+ What we do is store a function for each view that restores it's state. Since the render function is
+ void -> void
This allows us to remember the state of the view and restore it as we travel between different views.
@@ -13,17 +14,47 @@ let start (tag : string) () =
*)
let term = Common.Term.create () in
let restore_headline_state = ref (fun () -> ()) in
+ let restore_done_state = ref (fun () -> ()) in
let restore_todo_state = ref (fun () -> ()) in
+ (* DONE *)
+ let goto_todo_from_done new_done_state =
+ restore_done_state := new_done_state;
+ !restore_todo_state ()
+ in
+ let goto_headlines_from_done new_done_state =
+ restore_done_state := new_done_state;
+ !restore_headline_state ()
+ in
+ (restore_done_state
+ := fun () ->
+ let done_state =
+ Done.init
+ ~goto_headlines:goto_headlines_from_done
+ ~goto_todo:goto_todo_from_done
+ in
+ Done.render term done_state);
+ (* TODO *)
+ let goto_done_from_todo new_todo_state =
+ restore_todo_state := new_todo_state;
+ !restore_done_state ()
+ in
let goto_headline_from_todo new_todo_state =
restore_todo_state := new_todo_state;
!restore_headline_state ()
in
(restore_todo_state
:= fun () ->
- let todo = Todos.init ~goto_headlines:goto_headline_from_todo in
+ let todo =
+ Todos.init
+ ~goto_headlines:goto_headline_from_todo
+ ~goto_done:goto_done_from_todo
+ in
Todos.render term todo);
let headline =
Headlines.init
+ ~goto_done_view:(fun new_state ->
+ restore_headline_state := new_state;
+ !restore_done_state ())
~goto_todos_view:(fun new_state ->
restore_headline_state := new_state;
!restore_todo_state ())
diff --git a/lib/stitched_article.ml b/lib/stitched_article.ml
index 66a108e..56250c8 100644
--- a/lib/stitched_article.ml
+++ b/lib/stitched_article.ml
@@ -13,6 +13,9 @@ type state =
; goto_todos_view : (unit -> unit) -> unit
}
+let title = I.strf ~attr:A.(st bold) "%s" "Note View" |> I.pad ~l:0 ~t:0
+let content_start = 1
+
(* TODO: Use grep -l to filter notes by regexp and rerender those files*)
let rec render
t
@@ -23,18 +26,18 @@ let rec render
let img =
let elements =
Array.mapi
- (fun i el -> Compontent.current_line size_x y scroll el i)
+ (fun i el -> Compontent.current_line size_x y scroll el (i + content_start))
(* TODO: Fix this ugly slow conversion *)
(Array.to_seq content_pretty |> Seq.drop scroll |> Array.of_seq)
in
let open I in
- Array.fold_left (fun sum el -> el </> sum) I.empty elements
+ Array.fold_left (fun sum el -> el </> sum) title elements
in
Common.Term.image t img;
let content_length = Array.length content_pretty in
let scroll_up () =
- let scroll = if y - scroll = 0 then max (scroll - 1) 0 else scroll in
- render t @@ { state with pos = x, max (y - 1) 0; scroll }
+ 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 }
in
let scroll_down () =
let scroll = if y - scroll >= size_y - 1 then scroll + 1 else scroll in
@@ -59,7 +62,9 @@ let rec render
let[@warning "-8"] (editor :: args) =
String.split_on_char ' ' (Sys.getenv "EDITOR")
in
- let selected_file, line_number, _, file_number_offset = Array.get content y in
+ let selected_file, line_number, _, file_number_offset =
+ Array.get content (y - content_start)
+ in
let full_path_file = Grep.execution_directory ^ "/" ^ selected_file in
(* Because each file title consists of two lines, we need to account for the offset
it adds by removing the file_number *)
diff --git a/lib/todos.ml b/lib/todos.ml
index d2af47f..fd9eaf1 100644
--- a/lib/todos.ml
+++ b/lib/todos.ml
@@ -9,16 +9,21 @@ type state =
; content : (string * string) array
; content_pretty : string array
; goto_headlines : (unit -> unit) -> unit
+ ; goto_done : (unit -> unit) -> unit
}
-let init ~goto_headlines =
+let title = I.strf ~attr:A.(st bold) "%s" "Todo View" |> I.pad ~l:0 ~t:0
+let content_start = 2
+
+let init ~goto_done ~goto_headlines =
let content = Grep.get_todos () |> Grep.parse_todo_string in
let content_pretty = Grep.pretty_format_todo content in
- { pos = 0, 0
+ { pos = 0, content_start
; scroll = 0
; content = content |> Array.of_list
; content_pretty = content_pretty |> Array.of_list
; goto_headlines
+ ; goto_done
}
@@ -28,31 +33,35 @@ let load_todos () =
todo_content, todo_pretty
-let rec render t ({ pos; scroll; content; content_pretty; goto_headlines } as state) =
+let rec render
+ t
+ ({ pos; scroll; content; content_pretty; goto_headlines; goto_done } as state)
+ =
let x, y = pos in
+ let content_position = y - content_start in
let img =
let dot = I.string A.(st bold) ">" |> I.pad ~l:0 ~t:(y - scroll)
and elements =
Array.mapi
(fun i el ->
if i == y - scroll
- then I.strf ~attr:A.(st underline) "%s" el |> I.pad ~l:2 ~t:i
- else I.strf "%s" el |> I.pad ~l:2 ~t:i)
+ then I.strf "%s" el |> I.pad ~l:2 ~t:(i + content_start)
+ else I.strf "%s" el |> I.pad ~l:2 ~t:(i + content_start))
(Array.to_seq content_pretty |> Seq.drop scroll |> Array.of_seq)
in
let open I in
- Array.fold_left (fun sum el -> el </> sum) dot elements
+ Array.fold_left (fun sum el -> el </> sum) (title </> dot) elements
in
let _, size_y = Common.Term.size t in
Common.Term.image t img;
- let content_length = Array.length content_pretty in
+ let content_end = Array.length content_pretty + (content_start - 1) in
let scroll_up () =
- let scroll = if y - scroll = 0 then max (scroll - 1) 0 else scroll in
- render t @@ { state with pos = x, max (y - 1) 0; scroll }
+ 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 }
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_length - 1); scroll }
+ render t { state with pos = x, min (y + 1) content_end; scroll }
in
match Common.Term.event t with
| `End | `Key (`Escape, []) | `Key (`ASCII 'q', []) | `Key (`ASCII 'C', [ `Ctrl ]) -> ()
@@ -62,7 +71,7 @@ let rec render t ({ pos; scroll; content; content_pretty; goto_headlines } as st
| `Up -> scroll_up ())
| `Resize _ -> render t state
| `Mouse ((`Press _ | `Drag), (_, y), _) ->
- render t { state with pos = 0, min y content_length }
+ render t { state with pos = 0, min y content_end }
| `Key (`ASCII '?', []) -> Help_screen.render t { go_back = (fun () -> render t state) }
| `Key (`ASCII 's', []) ->
let (input_state : Input_screen.state) =
@@ -83,10 +92,11 @@ 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 (fun () -> render t state)
+ | `Key (`ASCII '3', []) -> goto_done (fun () -> render t state)
| `Key (`ASCII 'j', []) | `Key (`ASCII 'N', [ `Ctrl ]) -> scroll_down ()
| `Key (`ASCII 'k', []) | `Key (`ASCII 'P', [ `Ctrl ]) -> scroll_up ()
| `Key (`ASCII 't', []) ->
- let selected_file, _ = Array.get content y in
+ let selected_file, _ = Array.get content (y - content_start) in
let _ = Grep.toggle_done selected_file in
let content, content_pretty = load_todos () in
render
@@ -105,7 +115,7 @@ let rec render t ({ pos; scroll; content; content_pretty; goto_headlines } as st
let[@warning "-8"] (editor :: args) =
String.split_on_char ' ' (Sys.getenv "EDITOR")
in
- let selected_file, _ = Array.get content y in
+ let selected_file, _ = Array.get content content_position in
let full_path_file = Grep.execution_directory ^ "/" ^ selected_file in
let full_args = Array.append (Array.of_list args) [| full_path_file |] in
Common.Term.cursor t (Some (0, 0));