diff options
author | Marc Coquand <marc@mccd.space> | 2024-05-18 13:03:40 -0500 |
---|---|---|
committer | Marc Coquand <marc@mccd.space> | 2024-05-18 13:03:40 -0500 |
commit | 256397061fe5ae5649b4569c45bb53c8e45e0cbe (patch) | |
tree | 652e7b5833ea32d13b0ae1ea9cc32ba421fed827 | |
parent | 12ba5ed258b3a1ff30c3ece030e8eeed0dc417e9 (diff) | |
download | stitch-256397061fe5ae5649b4569c45bb53c8e45e0cbe.tar.gz stitch-256397061fe5ae5649b4569c45bb53c8e45e0cbe.tar.bz2 stitch-256397061fe5ae5649b4569c45bb53c8e45e0cbe.zip |
Add support for arbitrary command in the note view
-rw-r--r-- | bin/main.ml | 20 | ||||
-rw-r--r-- | lib/arbitrary_command.ml | 33 | ||||
-rw-r--r-- | lib/headlines.ml | 84 | ||||
-rw-r--r-- | lib/help_screen.ml | 1 | ||||
-rw-r--r-- | stitch.opam | 2 |
5 files changed, 128 insertions, 12 deletions
diff --git a/bin/main.ml b/bin/main.ml index 757b391..8fbefb1 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -61,7 +61,9 @@ let envs = let headlines_cmd = let doc = "write notes seperately and compose" in let author = [ `S Manpage.s_authors; `P "Marc Coquand (mccd.space)" ] in - let bugs = [ `S Manpage.s_bugs; `P "Email bug reports to marc@mccd.space" ] in + let bugs = + [ `S Manpage.s_bugs; `P "Email bug reports to ~marcc/stitch-general@lists.sr.ht." ] + in let description = [ `S Manpage.s_description ; `P @@ -138,6 +140,16 @@ let headlines_cmd = toggle the todo items." ] in + let commands = + [ `S "COMMANDS" + ; `P "" + ; `P + "You can run arbitrary commands in Stitch. These commands can make use of \ + variable substitutions. Available substitutions are" + ; `I ("%(file)", "Currently selected file") + ; `I ("%(content)", "Currently selected content") + ] + in let credit = [ `S "CREDIT" ; `P "Stitch was inspired by the note-taking tool Howm for EMacs by HIRAOKA Kazuyuki." @@ -147,9 +159,11 @@ let headlines_cmd = Cmd.info ~envs "stitch" - ~version:"0.0.2 ALPHA" + ~version:"0.0.3 ALPHA" ~doc - ~man:(List.concat [ bugs; author; description; example_set_up_basic; credit ]) + ~man: + (List.concat + [ description; commands; example_set_up_basic; bugs; author; credit ]) in Cmd.v info headlines_t diff --git a/lib/arbitrary_command.ml b/lib/arbitrary_command.ml new file mode 100644 index 0000000..920a82f --- /dev/null +++ b/lib/arbitrary_command.ml @@ -0,0 +1,33 @@ +module Grep = Grep + +let run ~(on_return : string -> unit) t ~selected_file ~content ~command = + let command = String.split_on_char ' ' command in + (* Substitute after splitting to more easily deal with file names that have spaces *) + let command = + command + |> List.map (fun s -> + let s = Str.global_replace (Str.regexp "%(file)") selected_file s in + Str.global_replace (Str.regexp "%(content)") content s) + in + Common.Term.cursor t (Some (0, 0)); + try + let result = + let open Shexp_process in + let open Shexp_process.Infix in + eval (chdir Grep.execution_directory (call command |- read_all)) + in + Common.Term.cursor t None; + on_return result + with + | exn -> + let oc = open_out "/tmp/stitch-error" in + let commands_result = String.concat " " command in + Printf.fprintf + oc + "%s\n%s\n%s\n%s\n" + selected_file + commands_result + (Printexc.to_string exn) + (Printexc.get_backtrace ()); + close_out oc; + on_return "ERROR: Failed to run command. Error written to /tmp/stitch-error" diff --git a/lib/headlines.ml b/lib/headlines.ml index 4f905fd..654e389 100644 --- a/lib/headlines.ml +++ b/lib/headlines.ml @@ -12,6 +12,8 @@ type state = ; content_pretty : string array ; goto_todos_view : (unit -> unit) -> unit ; goto_done_view : (unit -> unit) -> unit + ; output : string option + ; tag : string } let init ~goto_done_view ~goto_todos_view ~regexp = @@ -27,7 +29,15 @@ let init ~goto_done_view ~goto_todos_view ~regexp = exit 0) else ( let content_pretty = content |> Grep.pretty_format in - { pos = 0, 2; scroll = 0; content; content_pretty; goto_done_view; goto_todos_view }) + { pos = 0, 2 + ; scroll = 0 + ; content + ; content_pretty + ; goto_done_view + ; goto_todos_view + ; output = None + ; tag = "" + }) let title = I.strf ~attr:A.(st bold) "%s" "Note View" |> I.pad ~l:0 ~t:0 @@ -35,12 +45,21 @@ 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; goto_done_view } as state) + ({ pos; scroll; content; content_pretty; goto_todos_view; goto_done_view; tag; output } + as state) = let content_start = 2 in + let size_x, size_y = Common.Term.size t in let x, y = pos 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 @@ -54,18 +73,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 -> sum </> el) (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 ]) -> () @@ -75,7 +93,7 @@ let rec render | `Up -> scroll_up ()) | `Resize _ -> render t state | `Mouse ((`Press _ | `Drag), (_, y), _) -> - render t { state with pos = 0, min y content_end } + render t { state with pos = 0, min y content_end; output = None } | `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) @@ -112,7 +130,13 @@ let rec render Common.Term.cursor t None; render t - { state with content; content_pretty; pos = 0, content_start; scroll = 0 }) + { state with + content + ; content_pretty + ; pos = 0, content_start + ; scroll = 0 + ; tag + }) ; on_cancel = (fun _ -> Common.Term.cursor t None; @@ -127,6 +151,50 @@ let rec render | `Up -> scroll_up () | `Down -> scroll_down () | _ -> render t state) + | `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 = + 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, content_start + ; 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 '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/help_screen.ml b/lib/help_screen.ml index 8480a8a..4c607e2 100644 --- a/lib/help_screen.ml +++ b/lib/help_screen.ml @@ -29,6 +29,7 @@ let general_help_menu = ; "Note View", "1" ; "Todo View", "2" ; "Done View", "3" + ; "Run Shell Command", "!" ; "Edit File in $EDITOR", "Enter, e" ] diff --git a/stitch.opam b/stitch.opam index 46bd710..adfb301 100644 --- a/stitch.opam +++ b/stitch.opam @@ -1,7 +1,7 @@ # This file is generated by dune, edit dune-project instead opam-version: "2.0" version: "efe45fe-dirty" -synopsis: "A Note Composer" +synopsis: "Note Composer" description: "A minimal CLI tool that allows you to compose notes together. Useful as part of a bigger system for building a PKM." maintainer: ["Marc Coquand"] |