stitch

Note taking for messy minimalists

git clone git://mccd.space/stitch

done.ml (8411B)

      1 module Grep = Grep
      2 module Common = Common
      3 open Notty
      4 module Help_screen = Help_screen
      5 
      6 type state =
      7   { pos : int * int
      8   ; scroll : int
      9   ; content : (string * string) array
     10   ; content_pretty : string array
     11   ; goto_headlines : (unit -> unit) -> unit
     12   ; goto_todo : (unit -> unit) -> unit
     13   ; tag : string
     14   ; output : string option
     15   ; hide_file_name : bool
     16   }
     17 
     18 let title ~tag =
     19   match tag with
     20   | "" -> I.strf ~attr:A.(st bold) "%s" "Done" |> I.pad ~l:0 ~t:0
     21   | a -> I.strf ~attr:A.(st bold) "%s > %s" "Done" a |> I.pad ~l:0 ~t:0
     22 
     23 
     24 let content_start = 2
     25 
     26 let init ~goto_todo ~goto_headlines =
     27   let hide_file_name = true in
     28   let content = Grep.get_done () |> Grep.parse_todo_string in
     29   let content_pretty = Grep.pretty_format_todo ~hide_file_name content in
     30   { pos = 0, content_start
     31   ; scroll = 0
     32   ; content = content |> Array.of_list
     33   ; content_pretty = content_pretty |> Array.of_list
     34   ; goto_headlines
     35   ; goto_todo
     36   ; tag = ""
     37   ; output = None
     38   ; hide_file_name
     39   }
     40 
     41 
     42 let load_done ?(hide_file_name = false) tag =
     43   let done_content = Grep.get_tagged_done tag () |> Grep.parse_todo_string in
     44   let done_pretty = Grep.pretty_format_todo ~hide_file_name done_content in
     45   done_content, done_pretty
     46 
     47 
     48 let rec render
     49   t
     50   ({ pos
     51    ; scroll
     52    ; content
     53    ; content_pretty
     54    ; goto_headlines
     55    ; goto_todo
     56    ; output
     57    ; hide_file_name
     58    ; tag
     59    } as state)
     60   =
     61   let x, y = pos in
     62   let size_x, size_y = Common.Term.size t in
     63   let content_position = y - content_start in
     64   let img =
     65     let output_info =
     66       match output with
     67       | Some line ->
     68         I.strf "%s%s" (String.escaped line) (String.make size_x ' ')
     69         |> I.pad ~t:(size_y - 1)
     70       | None -> I.empty
     71     and dot =
     72       if Array.length content_pretty = 0
     73       then I.empty
     74       else I.string A.(st bold) ">" |> I.pad ~l:0 ~t:(y - scroll)
     75     and elements =
     76       Array.mapi
     77         (fun i el ->
     78           if i == y - scroll
     79           then I.strf "%s" el |> I.pad ~l:2 ~t:(i + content_start)
     80           else I.strf "%s" el |> I.pad ~l:2 ~t:(i + content_start))
     81         (content_pretty |> Basic.array_drop scroll)
     82     in
     83     let open I in
     84     Array.fold_left
     85       (fun sum el -> el </> sum)
     86       (title ~tag </> dot </> output_info)
     87       elements
     88   in
     89   Common.Term.image t img;
     90   let content_end = Array.length content_pretty + (content_start - 1) in
     91   let scroll_up ?(amount = 1) () =
     92     let scroll =
     93       if y - content_start - scroll - amount < 0 then max (scroll - amount) 0 else scroll
     94     in
     95     render t { state with pos = x, max (y - amount) content_start; scroll; output = None }
     96   in
     97   let scroll_down ?(amount = 1) () =
     98     let scroll = if y - scroll >= size_y - amount then scroll + amount else scroll in
     99     render t { state with pos = x, min (y + amount) content_end; scroll; output = None }
    100   in
    101   match Common.Term.event t with
    102   | `End | `Key (`Escape, []) | `Key (`ASCII 'q', []) | `Key (`ASCII 'C', [ `Ctrl ]) -> ()
    103   | `Mouse (`Press (`Scroll s), _, _) ->
    104     (match s with
    105      | `Down -> scroll_down ()
    106      | `Up -> scroll_up ())
    107   | `Resize _ -> render t state
    108   | `Mouse ((`Press _ | `Drag), (_, y), _) ->
    109     render t { state with pos = 0, min y content_end }
    110   | `Key (`ASCII '?', []) -> Help_screen.render t { go_back = (fun () -> render t state) }
    111   | `Key (`ASCII 's', []) ->
    112     let (input_state : Input_prompt.state) =
    113       { screen = img
    114       ; user_input_pre = ""
    115       ; user_input_post = ""
    116       ; prompt = "REGEXP"
    117       ; on_enter =
    118           (fun tag ->
    119             let content = Grep.get_tagged_done tag () |> Grep.parse_todo_string in
    120             let content_pretty = Grep.pretty_format_todo content in
    121             Common.Term.cursor t None;
    122             render
    123               t
    124               { state with
    125                 content = content |> Array.of_list
    126               ; content_pretty = content_pretty ~hide_file_name |> Array.of_list
    127               ; tag
    128               })
    129       ; on_cancel =
    130           (fun _ ->
    131             Common.Term.cursor t None;
    132             render t state)
    133       }
    134     in
    135     Input_prompt.render t input_state
    136   | `Key (`ASCII '1', []) -> goto_headlines (fun () -> render t state)
    137   | `Key (`ASCII '2', []) -> goto_todo (fun () -> render t state)
    138   | `Key (`ASCII 'D', [ `Ctrl ]) | `Key (`Page `Down, []) ->
    139     scroll_down ~amount:(size_y / 2) ()
    140   | `Key (`ASCII 'U', [ `Ctrl ]) | `Key (`Page `Up, []) ->
    141     scroll_up ~amount:(size_y / 2) ()
    142   | `Key (`ASCII 'h', []) ->
    143     let hide_file_name = not hide_file_name in
    144     let content_pretty =
    145       Grep.pretty_format_todo ~hide_file_name (content |> Array.to_list) |> Array.of_list
    146     in
    147     render t { state with hide_file_name; content_pretty }
    148   | `Key (`ASCII 'g', []) ->
    149     let content, content_pretty = load_done ~hide_file_name tag in
    150     let y = min (List.length content_pretty + content_start) y in
    151     render
    152       t
    153       { state with
    154         pos = x, y
    155       ; content = content |> Array.of_list
    156       ; content_pretty = Array.of_list content_pretty
    157       }
    158   | `Key (`ASCII '!', []) ->
    159     let selected_file, content = Array.get content content_position in
    160     let selected_file = Grep.execution_directory ^ "/" ^ selected_file in
    161     let (input_state : Input_prompt.state) =
    162       { screen = img
    163       ; user_input_pre = ""
    164       ; user_input_post = ""
    165       ; prompt = "COMMAND"
    166       ; on_enter =
    167           (fun command ->
    168             if String.equal (String.trim command) String.empty
    169             then (
    170               Common.Term.cursor t None;
    171               render t state)
    172             else
    173               Arbitrary_command.run
    174                 t
    175                 ~command:(Scanf.unescaped command)
    176                 ~content
    177                 ~selected_file
    178                 ~on_return:(fun result ->
    179                   let content, content_pretty = load_done ~hide_file_name tag in
    180                   let y = min (List.length content_pretty + content_start) y in
    181                   Common.Term.cursor t None;
    182                   render
    183                     t
    184                     { state with
    185                       content = Array.of_list content
    186                     ; content_pretty = Array.of_list content_pretty
    187                     ; pos = 0, y
    188                     ; scroll = 0
    189                     ; output = Some result
    190                     }))
    191       ; on_cancel =
    192           (fun _ ->
    193             Common.Term.cursor t None;
    194             render t state)
    195       }
    196     in
    197     Input_prompt.render t input_state
    198   | `Key (`ASCII 'j', []) | `Key (`ASCII 'N', [ `Ctrl ]) -> scroll_down ()
    199   | `Key (`ASCII 'k', []) | `Key (`ASCII 'P', [ `Ctrl ]) -> scroll_up ()
    200   | `Key (`ASCII 'T', [ `Ctrl ]) ->
    201     let selected_file, _ = Array.get content (y - content_start) in
    202     let _ = Grep.toggle_todo selected_file in
    203     let content, content_pretty = load_done ~hide_file_name tag in
    204     let y = min (List.length content_pretty + content_start) y in
    205     render
    206       t
    207       { state with
    208         pos = x, y
    209       ; content = content |> Array.of_list
    210       ; content_pretty = Array.of_list content_pretty
    211       }
    212   | `Key (`Arrow d, _) ->
    213     (match d with
    214      | `Up -> scroll_up ()
    215      | `Down -> scroll_down ()
    216      | _ -> render t state)
    217   | `Key (`ASCII 'e', []) | `Key (`Enter, []) ->
    218     (* Editor might be set with extra args, in that case we need to separate these *)
    219     let[@warning "-8"] (editor :: args) =
    220       String.split_on_char ' ' (Sys.getenv "EDITOR")
    221     in
    222     let selected_file, _ = Array.get content content_position in
    223     let full_path_file = Grep.execution_directory ^ "/" ^ selected_file in
    224     let full_args = Array.append (Array.of_list args) [| "+1"; full_path_file |] in
    225     Common.Term.cursor t (Some (0, 0));
    226     let _ =
    227       Unix.create_process_env
    228         editor
    229         full_args
    230         (Unix.environment ())
    231         Unix.stdin
    232         Unix.stdout
    233         Unix.stderr
    234     in
    235     let rec run_editor () =
    236       match Unix.wait () with
    237       | _, _ ->
    238         Common.Term.cursor t None;
    239         let content, content_pretty = load_done ~hide_file_name tag in
    240         render
    241           t
    242           { state with
    243             content = content |> Array.of_list
    244           ; content_pretty = content_pretty |> Array.of_list
    245           }
    246       (* Capture resizing events *)
    247       | exception Unix.Unix_error (Unix.EINTR, _, _) -> run_editor ()
    248       | exception Unix.Unix_error (_, _, _) -> failwith "ERROR"
    249     in
    250     run_editor ()
    251   | _ -> render t state