aboutsummaryrefslogtreecommitdiff
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
parent12ba5ed258b3a1ff30c3ece030e8eeed0dc417e9 (diff)
downloadstitch-256397061fe5ae5649b4569c45bb53c8e45e0cbe.tar.gz
stitch-256397061fe5ae5649b4569c45bb53c8e45e0cbe.tar.bz2
stitch-256397061fe5ae5649b4569c45bb53c8e45e0cbe.zip
Add support for arbitrary command in the note view
-rw-r--r--bin/main.ml20
-rw-r--r--lib/arbitrary_command.ml33
-rw-r--r--lib/headlines.ml84
-rw-r--r--lib/help_screen.ml1
-rw-r--r--stitch.opam2
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"]