aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--README.org4
-rw-r--r--bin/main.ml17
-rw-r--r--lib/grep.ml34
-rw-r--r--lib/headlines.ml92
-rw-r--r--lib/input_screen.ml35
5 files changed, 159 insertions, 23 deletions
diff --git a/README.org b/README.org
index 1f61c32..9e06ad3 100644
--- a/README.org
+++ b/README.org
@@ -30,8 +30,8 @@ Set the environment variables:
STICH_DIRECTORY
STICH_GREP_CMD (default "grep")
-STITCH_HEADLINE_PATTERN (default "^\* ")
-STITCH_TAG_PATTERN (default ":[a-z]:")
+STITCH_HEADLINE_PATTERN (default "^\\* ")
+STITCH_TAG_PATTERN (default ":[a-z_-]+:")
** SPEED UP
diff --git a/bin/main.ml b/bin/main.ml
index bb7dc1e..78d30ba 100644
--- a/bin/main.ml
+++ b/bin/main.ml
@@ -1,3 +1,18 @@
open Stitch
+open Cmdliner
-let () = Headlines.start ()
+let tag_arg =
+ let doc = "Search entries for a given tag." in
+ Arg.(value & opt string "" & info [ "t"; "tag" ] ~docv:"TAG" ~doc)
+
+
+let headlines_t = Term.(const Headlines.start $ tag_arg $ const ())
+
+let headlines_cmd =
+ let doc = "Show titles in a condensed list" in
+ let man = [ `S Manpage.s_bugs; `P "Email bug reports to marc@mccd.space" ] in
+ let info = Cmd.info "headlines" ~version:"0.1" ~doc ~man in
+ Cmd.v info headlines_t
+
+
+let () = exit (Cmd.eval headlines_cmd)
diff --git a/lib/grep.ml b/lib/grep.ml
index a6ba300..e438d0a 100644
--- a/lib/grep.ml
+++ b/lib/grep.ml
@@ -4,16 +4,42 @@ let execution_directory =
let grep_cmd = Sys.getenv_opt "STICH_GREP_CMD" |> Option.value ~default:"ugrep"
+let tag_pattern =
+ Sys.getenv_opt "STITCH_TAG_PATTERN" |> Option.value ~default:":[a-z_-]+:"
+
+
+let headline_pattern =
+ Sys.getenv_opt "STITCH_HEADLINE_PATTERN" |> Option.value ~default:"^\\* "
+
+
let run_print ~dir args =
let open Shexp_process in
let open Shexp_process.Infix in
eval (chdir dir (call args |- read_all))
-let get_headlines () =
- run_print
- ~dir:execution_directory
- [ grep_cmd; "^\\*"; "-H"; "-r"; "-n"; "--separator=|" ]
+let headline_args =
+ [ grep_cmd; "^\\*"; "-H"; "-r"; "-n"; "--separator=|"; "--no-messages" ]
+
+
+let get_headlines () = run_print ~dir:execution_directory headline_args
+
+let get_tagged_headlines tag () =
+ let open Shexp_process in
+ let open Shexp_process.Infix in
+ eval
+ (chdir
+ execution_directory
+ (call headline_args |- call [ grep_cmd; "-E"; "--no-messages"; tag ] |- read_all))
+
+
+let get_tags () =
+ let open Shexp_process in
+ let open Shexp_process.Infix in
+ eval
+ (chdir
+ execution_directory
+ (call headline_args |- call [ grep_cmd; "-E"; tag_pattern; "-o" ] |- read_all))
exception Not_A_Tuple of string * string
diff --git a/lib/headlines.ml b/lib/headlines.ml
index 8fab7f6..f32b6ef 100644
--- a/lib/headlines.ml
+++ b/lib/headlines.ml
@@ -1,16 +1,27 @@
module Grep = Grep
module Common = Common
open Notty
+module Input_screen = Input_screen
-let content = Grep.get_headlines () |> Grep.parse_headlines
-let content_pretty = content |> Grep.pretty_format
+type state =
+ { tag : string option
+ ; pos : int * int
+ ; scroll : int
+ ; content : (string * string) array
+ ; content_pretty : string array
+ }
-let rec headline_screen t (((x, y) as pos), scroll) =
+let rec headline_screen t ({ tag; pos; scroll; content; content_pretty } as state) =
+ print_endline (Option.value ~default:"No tag" tag);
+ let x, y = pos in
let img =
- let dot = I.string A.(fg black) ">" |> I.pad ~l:0 ~t:(y - scroll)
+ let dot = I.string A.(st bold) ">" |> I.pad ~l:0 ~t:(y - scroll)
and elements =
Array.mapi
- (fun i el -> I.strf ~attr:A.(fg black) "%s" el |> I.pad ~l:2 ~t:i)
+ (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)
(Array.to_seq content_pretty |> Seq.drop scroll |> Array.of_seq)
in
let open I in
@@ -21,11 +32,11 @@ let rec headline_screen t (((x, y) as pos), scroll) =
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
- headline_screen t @@ ((x, max (y - 1) 0), scroll)
+ headline_screen t @@ { state with pos = x, max (y - 1) 0; scroll }
in
let scroll_down () =
let scroll = if y - scroll >= size_y - 1 then scroll + 1 else scroll in
- headline_screen t @@ ((x, min (y + 1) content_length), scroll)
+ headline_screen t @@ { state with pos = x, min (y + 1) content_length; scroll }
in
match Common.Term.event t with
| `End | `Key (`Escape, []) | `Key (`ASCII 'q', []) | `Key (`ASCII 'C', [ `Ctrl ]) -> ()
@@ -33,18 +44,52 @@ let rec headline_screen t (((x, y) as pos), scroll) =
(match s with
| `Down -> scroll_down ()
| `Up -> scroll_up ())
- | `Resize _ -> headline_screen t (pos, scroll)
+ | `Resize _ -> headline_screen t state
| `Mouse ((`Press _ | `Drag), (_, y), _) ->
- headline_screen t ((0, min y content_length), scroll)
+ headline_screen t { state with pos = 0, min y content_length }
+ | `Key (`ASCII 't', []) ->
+ let (input_state : Input_screen.state) =
+ { screen = img
+ ; user_input = ""
+ ; prompt = "TAG: "
+ ; 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;
+ headline_screen
+ t
+ { state with content; content_pretty; pos = 0, 0; scroll = 0 })
+ ; on_cancel =
+ (fun _ ->
+ Common.Term.cursor t None;
+ headline_screen t state)
+ }
+ in
+ Input_screen.render t input_state
+ | `Key (`ASCII 's', []) ->
+ let (input_state : Input_screen.state) =
+ { screen = img
+ ; user_input = ""
+ ; prompt = "SEARCH: "
+ ; on_enter =
+ (fun _ ->
+ Common.Term.cursor t None;
+ headline_screen t { state with pos = 0, 0; scroll = 0 })
+ ; on_cancel =
+ (fun _ ->
+ Common.Term.cursor t None;
+ headline_screen t state)
+ }
+ in
+ Input_screen.render t input_state
| `Key (`ASCII 'j', []) | `Key (`ASCII 'N', [ `Ctrl ]) -> scroll_down ()
- | `Key (`ASCII 'k', []) | `Key (`ASCII 'P', [ `Ctrl ]) ->
- let scroll = if y - scroll = 0 then max (scroll - 1) 0 else scroll in
- headline_screen t @@ ((x, max (y - 1) 0), scroll)
+ | `Key (`ASCII 'k', []) | `Key (`ASCII 'P', [ `Ctrl ]) -> scroll_up ()
| `Key (`Arrow d, _) ->
(match d with
| `Up -> scroll_up ()
| `Down -> scroll_down ()
- | _ -> headline_screen t ((x, y), scroll))
+ | _ -> headline_screen 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) =
@@ -67,13 +112,28 @@ let rec headline_screen t (((x, y) as pos), scroll) =
match Unix.wait () with
| _, _ ->
Common.Term.cursor t None;
- headline_screen t ((x, y), scroll)
+ headline_screen t state
(* Capture resizing events *)
| exception Unix.Unix_error (Unix.EINTR, _, _) -> run_editor ()
| exception Unix.Unix_error (_, _, _) -> failwith "ERROR"
in
run_editor ()
- | _ -> headline_screen t (pos, scroll)
+ | _ -> headline_screen t state
-let start () = headline_screen (Common.Term.create ()) ((0, 0), 0)
+let start (tag : string) () =
+ let tag = if String.equal tag "" then None else Some tag in
+ let content =
+ match tag with
+ | None -> Grep.get_headlines () |> Grep.parse_headlines
+ | Some tag -> Grep.get_tagged_headlines tag () |> Grep.parse_headlines
+ in
+ if Array.length content == 0
+ then (
+ print_endline "No entry for tag";
+ exit 0)
+ else (
+ let content_pretty = content |> Grep.pretty_format in
+ headline_screen
+ (Common.Term.create ())
+ { tag; pos = 0, 0; scroll = 0; content; content_pretty })
diff --git a/lib/input_screen.ml b/lib/input_screen.ml
new file mode 100644
index 0000000..3fae53c
--- /dev/null
+++ b/lib/input_screen.ml
@@ -0,0 +1,35 @@
+module Grep = Grep
+module Common = Common
+open Notty
+
+type state =
+ { user_input : string
+ ; on_enter : string -> unit
+ ; on_cancel : unit -> unit
+ ; prompt : string
+ ; screen : I.t
+ }
+
+let rec render t ({ user_input; on_enter; on_cancel; screen; prompt } as state) =
+ let _, size_y = Common.Term.size t in
+ Common.Term.cursor t (Some (String.length user_input + String.length prompt, size_y));
+ let img =
+ let open I in
+ I.strf "%s%s" prompt user_input |> I.pad ~l:0 ~t:(size_y - 1) </> screen
+ in
+ Common.Term.image t img;
+ match Common.Term.event t with
+ | `End | `Key (`ASCII 'G', [ `Ctrl ]) | `Key (`ASCII 'C', [ `Ctrl ]) -> on_cancel ()
+ | `Key (`Enter, []) -> on_enter user_input
+ | `Key (`Backspace, []) ->
+ let state =
+ { state with
+ user_input = String.sub user_input 0 (max (String.length user_input - 1) 0)
+ }
+ in
+ render t state
+ | `Resize _ -> render t state
+ | `Key (`ASCII c, []) ->
+ let state = { state with user_input = user_input ^ String.make 1 c } in
+ render t state
+ | _ -> render t state