From 256397061fe5ae5649b4569c45bb53c8e45e0cbe Mon Sep 17 00:00:00 2001 From: Marc Coquand Date: Sat, 18 May 2024 13:03:40 -0500 Subject: Add support for arbitrary command in the note view --- lib/arbitrary_command.ml | 33 +++++++++++++++++++ lib/headlines.ml | 84 +++++++++++++++++++++++++++++++++++++++++++----- lib/help_screen.ml | 1 + 3 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 lib/arbitrary_command.ml (limited to 'lib') 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" ] -- cgit v1.2.3