aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMarc Coquand <marc@mccd.space>2024-05-18 13:03:40 -0500
committerMarc Coquand <marc@mccd.space>2024-05-18 13:03:40 -0500
commit256397061fe5ae5649b4569c45bb53c8e45e0cbe (patch)
tree652e7b5833ea32d13b0ae1ea9cc32ba421fed827 /lib
parent12ba5ed258b3a1ff30c3ece030e8eeed0dc417e9 (diff)
downloadstitch-256397061fe5ae5649b4569c45bb53c8e45e0cbe.tar.gz
stitch-256397061fe5ae5649b4569c45bb53c8e45e0cbe.tar.bz2
stitch-256397061fe5ae5649b4569c45bb53c8e45e0cbe.zip
Add support for arbitrary command in the note view
Diffstat (limited to 'lib')
-rw-r--r--lib/arbitrary_command.ml33
-rw-r--r--lib/headlines.ml84
-rw-r--r--lib/help_screen.ml1
3 files changed, 110 insertions, 8 deletions
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"
]