terminal user interface to jujutsu. Focused on speed and clarity
9
fork

Configure Feed

Select the types of activity you want to include in your feed.

progress on ui, added boxes

+329 -94
+1
.gitignore
··· 1 1 _build 2 2 _opam 3 + .jj
-1
.ocamlformat
··· 1 - version=0.24.1 2 1 profile=conventional
+44 -1
jj_tui/.ocamlformat
··· 1 + profile = janestreet 2 + #good to stop errors propigating 3 + let-binding-spacing=double-semicolon 1 4 2 - profile=conventional 5 + type-decl=sparse 6 + 7 + #==== This section is basically for getting trailing commas ==== 8 + 9 + #We want space around these for ease of adding newlines 10 + space-around-records=true 11 + space-around-lists=true 12 + space-around-arrays=true 13 + #This makes types lists etc start with a newline 14 + #let a=[ 15 + # item 16 + #instead of 17 + # let a=[ item 18 + dock-collection-brackets=true 19 + #We need this too otherwires the semicolons wee be before 20 + break-separators=after 21 + 22 + break-colon=after 23 + 24 + break-sequences=true 25 + 26 + #ensures 27 + 28 + #this ensures our match statements stay on multiple lines 29 + break-cases=nested 30 + 31 + 32 + 33 + #ensures that 'then' lines up with 'else' 34 + if-then-else=keyword-first 35 + 36 + #break-sequences=true 37 + 38 + 39 + #break-fun-decl=wrap 40 + #break-fun-sig=wrap 41 + #wrap-fun-args=true 42 + # assignment-operator=end-line 43 + 44 + #stops thing @@ fun a-> from ending up on multiple lines 45 + break-infix-before-func=false
+1 -1
jj_tui/bin/dune
··· 1 1 (executable 2 2 (public_name jj_tui) 3 3 (name main) 4 - (libraries jj_tui feather lwd nottui base core stdio core_unix.command_unix ) 4 + (libraries jj_tui feather lwd nottui base core stdio core_unix.command_unix eio_main ) 5 5 )
+222 -75
jj_tui/bin/main.ml
··· 2 2 open Feather 3 3 open Lwd_infix 4 4 open Notty 5 + open Eio.Std 5 6 module W = Nottui_widgets 6 7 8 + module Vars = struct 9 + let quit = Lwd.var false 10 + 11 + (* let pool : Task_pool.t option ref = ref None *) 12 + 13 + (* let action : top_level_action option ref = ref None *) 14 + 15 + let eio_env : Eio_unix.Stdenv.base option ref = ref None 16 + 17 + (* let input_mode : input_mode Lwd.var = Lwd.var Navigate *) 18 + 19 + (* let init_ui_mode : ui_mode ref = ref Ui_multi_file *) 20 + 21 + (* let ui_mode : ui_mode Lwd.var = Lwd.var Ui_multi_file *) 22 + 23 + let term : Notty_unix.Term.t option ref = ref None 24 + let commit_message = Lwd.var ("", 0) 25 + let term_width_height : (int * int) Lwd.var = Lwd.var (0, 0) 26 + end 27 + 28 + (* let task_pool () = *) 29 + (* Option.get !Vars.pool *) 30 + 31 + let _eio_env () = Option.get !Vars.eio_env 32 + let term () = Option.get !Vars.term 33 + 34 + (** Makes a new process that has acess to all input and output 35 + This should be used for running other tui sub-programs *) 36 + let switch_to_process env command = 37 + Switch.run @@ fun sw -> 38 + let mgr = Eio.Stdenv.process_mgr env in 39 + let stdout = Eio.Stdenv.stdout env in 40 + let stdin = Eio.Stdenv.stdin env in 41 + let stderr = Eio.Stdenv.stderr env in 42 + let proc = Eio.Process.spawn ~sw mgr ~stderr ~stdin ~stdout command in 43 + proc |> Eio.Process.await 44 + ;; 45 + 46 + let term' : unit -> Notty_unix.Term.t = term 47 + 48 + let full_term_sized_background = 49 + let$ term_width, term_height = Lwd.get Vars.term_width_height in 50 + Notty.I.void term_width term_height |> Nottui.Ui.atom 51 + ;; 52 + 7 53 let colored_string = Jj_tui.AnsiReverse.colored_string 8 54 9 55 (* Ui_loop.run (Lwd.pure (W.printf "Hello world"));; *) 10 56 let cmdArgs cmd args = 11 - let stdout, stderr = 12 - Feather.process cmd args |> Feather.collect stdout_and_stderr 13 - in 57 + let stdout, stderr = Feather.process cmd args |> Feather.collect stdout_and_stderr in 14 58 stdout ^ stderr 59 + ;; 15 60 16 61 let jj args = cmdArgs "jj" (List.concat [ args; [ "--color"; "always" ] ]) 17 62 let vcount = Lwd.var I.empty 18 63 19 64 let _button = 20 65 W.button (Printf.sprintf "run jj") (fun () -> 21 - vcount $= (cmdArgs "jj" [ "log"; "--color"; "always" ] |> colored_string)) 66 + vcount $= (cmdArgs "jj" [ "log"; "--color"; "always" ] |> colored_string)) 22 67 |> Lwd.pure 68 + ;; 23 69 24 - let vQuit = Lwd.var false 70 + (* let vQuit = Lwd.var false *) 71 + let vExtern = Lwd.var `Nothing 25 72 26 73 let _quitButton = 27 - W.button (Printf.sprintf "quit ") (fun () -> vQuit $= true) |> Lwd.pure 74 + W.button (Printf.sprintf "quit ") (fun () -> Vars.quit $= true) |> Lwd.pure 75 + ;; 28 76 29 77 let ( <-$ ) f v = Lwd.map ~f (Lwd.get v) 30 78 ··· 37 85 vShowStatus $= res; 38 86 let res = jj [] in 39 87 vcount $= colored_string res 88 + ;; 89 + 90 + let post_change state = 91 + onChange (); 92 + Lwd.set vExtern state 93 + ;; 40 94 41 95 let changeInputs key = 42 96 let noOut args = ··· 44 98 `Handled 45 99 in 46 100 match key with 47 - | 'P' -> noOut [ "prev" ] 48 - | 'p' -> noOut [ "prev"; "--edit" ] 49 - | 'N' -> noOut [ "next" ] 50 - | 'n' -> noOut [ "next"; "--edit" ] 51 - | 'S' -> noOut [ "unsquash"; "-i"; "--tool"; "sublime_merge" ] 52 - | _ -> `Unhandled 101 + | 'P' -> 102 + noOut [ "prev" ] 103 + | 'p' -> 104 + noOut [ "prev"; "--edit" ] 105 + | 'N' -> 106 + noOut [ "next" ] 107 + | 'n' -> 108 + noOut [ "next"; "--edit" ] 109 + | 'h' -> 110 + noOut [ "new" ] 111 + | 'c' -> 112 + post_change `Commit; 113 + `Handled 114 + | 'S' -> 115 + Lwd.set vExtern (`Cmd [ "jj"; "unsquash"; "-i" ]); 116 + `Handled 117 + | 's' -> 118 + Lwd.set vExtern (`Cmd [ "jj"; "squash"; "-i" ]); 119 + `Handled 120 + | 'R' -> 121 + Lwd.set vExtern (`Cmd [ "jj"; "resolve" ]); 122 + `Handled 123 + | _ -> 124 + `Unhandled 125 + ;; 53 126 54 127 let inputs ui = 55 128 Ui.event_filter 56 129 (fun event -> 57 130 match event with 58 131 | `Key (`ASCII 's', _) -> 59 - let res = jj [ "show" ] in 60 - vShowStatus $= (res |> colored_string); 61 - 62 - `Handled 132 + let res = jj [ "show" ] in 133 + vShowStatus $= (res |> colored_string); 134 + `Handled 63 135 | `Key (`ASCII 'l', _) -> 64 - let res = jj [] in 65 - vcount $= colored_string res; 66 - vother $= res; 67 - 68 - `Handled 136 + let res = jj [] in 137 + vcount $= colored_string res; 138 + vother $= res; 139 + `Handled 69 140 | `Key (`ASCII 'q', _) -> 70 - vQuit $= true; 141 + Vars.quit $= true; 142 + `Handled 143 + | `Key (`ASCII key, _) -> 144 + (match changeInputs key with 145 + | `Handled -> 146 + onChange (); 147 + `Handled 148 + | `Unhandled -> 149 + `Unhandled) 150 + | _ -> 151 + `Unhandled) 152 + ui 153 + ;; 154 + 155 + (* let squashButton = *) 156 + (* W.button "squash" (fun _ -> Lwd.set vExtern (`Cmd [ "jj"; "squash"; "-i" ])) *) 157 + (* ;; *) 71 158 72 - `Handled 73 - | `Key (`ASCII key, _) -> ( 74 - match changeInputs key with 75 - | `Handled -> 76 - onChange (); 77 - `Handled 78 - | `Unhandled -> `Unhandled) 79 - | _ -> `Unhandled) 159 + let mainUi env = 160 + let$* running = Lwd.get vExtern in 161 + match running with 162 + | `Cmd cmd -> 163 + (*We have this extra step to paint the terminal empty for one step*) 164 + Lwd.set vExtern @@ `RunCmd cmd; 165 + full_term_sized_background 166 + | `RunCmd cmd -> 167 + let exit_status_to_str y = 168 + match match y with `Exited x -> x | `Signaled x -> x with 169 + | 0 -> 170 + "success" 171 + | 1 -> 172 + "failure" 173 + | a -> 174 + Printf.sprintf "unknown code %d" a 175 + in 176 + let res = switch_to_process env cmd in 177 + let$ ui = 178 + W.vbox 179 + [ 180 + W.string (Printf.sprintf "exit code:%s" (res |> exit_status_to_str)) |> Lwd.pure; 181 + W.button "back to main UI" (fun _ -> post_change `Nothing) |> Lwd.pure; 182 + ] 183 + in 80 184 ui 185 + |> Ui.event_filter (fun event -> 186 + match event with 187 + | `Key (`ASCII ' ', _) -> 188 + post_change `Nothing; 189 + `Handled 190 + | _ -> 191 + `Unhandled) 192 + | (`Nothing | `Commit) as rest -> 193 + let$* pane = 194 + W.h_pane 195 + (Nottui_widgets.vbox 196 + [ 197 + (* squashButton |> Lwd.pure; *) 198 + (* button; *) Ui.atom <-$ vcount (* quitButton *) ]) 199 + (Ui.atom <-$ vShowStatus) 200 + in 201 + (match rest with 202 + | `Nothing -> 203 + inputs pane |> Lwd.pure 204 + | `Commit -> 205 + let exit () = 206 + post_change `Nothing; 207 + Lwd.set Vars.commit_message ("", 0) 208 + in 209 + let$ commit_field = 210 + W.zbox 211 + [ 212 + W.string ~attr:A.(st underline) " " 213 + |> Lwd.pure; 214 + W.edit_field 215 + (Lwd.get Vars.commit_message) 216 + ~on_change:(fun state -> Lwd.set Vars.commit_message state) 217 + ~on_submit:(fun (str, _) -> 218 + let _ = jj [ "commit"; "-m"; str ] in 219 + exit ()); 220 + ] 221 + in 222 + Ui.zcat 223 + [ 224 + pane; 225 + commit_field 226 + |> Widgets.border_box ~pad:Gravity.default ~label:"commit msg" 227 + |> Ui.resize ~pad:Widgets.neutral_grav; 228 + ] 229 + |> Ui.event_filter (fun event -> 230 + match event with 231 + | `Key (`Escape, _) -> 232 + exit (); 233 + `Handled 234 + | _ -> 235 + `Unhandled)) 236 + ;; 81 237 82 - let mainUi = 83 - let$ pane = 84 - W.h_pane 85 - (Nottui_widgets.vbox 86 - [ (* button; *) Ui.atom <-$ vcount (* quitButton *) ]) 87 - (Ui.atom <-$ vShowStatus) 238 + let ui_loop ~quit ~term root = 239 + let renderer = Nottui.Renderer.make () in 240 + (* let root = *) 241 + (* let$ root = root in *) 242 + (* root *) 243 + (* |> Nottui.Ui.event_filter (fun x -> *) 244 + (* match x with *) 245 + (* | `Key (`Escape, []) -> *) 246 + (* Lwd.set quit true; *) 247 + (* `Handled *) 248 + (* | _ -> `Unhandled) *) 249 + (* in *) 250 + let rec loop () = 251 + if not (Lwd.peek quit) 252 + then ( 253 + let term_width, term_height = Notty_unix.Term.size (term' ()) in 254 + let prev_term_width, prev_term_height = Lwd.peek Vars.term_width_height in 255 + if term_width <> prev_term_width || term_height <> prev_term_height 256 + then Lwd.set Vars.term_width_height (term_width, term_height); 257 + Nottui.Ui_loop.step 258 + ~process_event:true 259 + ~timeout:0.05 260 + ~renderer 261 + term 262 + (Lwd.observe @@ root); 263 + Eio.Fiber.yield (); 264 + loop ()) 88 265 in 89 - inputs pane 266 + loop () 90 267 ;; 91 268 92 269 (*TODO:For hosting a subprocess i should look into using EIO and Ui_loop.step like some of the other libraries built with nottui*) 93 - Ui_loop.run ~quit:vQuit mainUi 94 - (* let my_image=(Notty.I.string Notty.A.empty "\027[32mThis is in green %s\027[0m" ) in *) 95 - (* let my_image = 96 - Jj_tui.AnsiReverse.Cap.parse_ansi_escape_codes 97 - "\027[32mThis is in green %s\027[0m " 98 - |> List.map (fun (x, str) -> Notty.I.string x str) 99 - ;; 100 - 101 - Notty_unix.output_image (my_image |> List.hd) *) 102 - 103 - (* 104 - type tree = Tree of string * (unit -> tree list) 105 - 106 - let rec tree_ui (Tree (label, child)) = 107 - let opened = Lwd.var false in 108 - let render is_opened = 109 - let btn_text = if is_opened then "[-] " else "[+] " in 110 - let btn_action () = Lwd.set opened (not is_opened) in 111 - let btn = W.button (btn_text ^ label) btn_action in 112 - let layout node forest = 113 - Ui.join_y node (Ui.join_x (Ui.space 2 0) forest) 114 - in 115 - if is_opened 116 - then Lwd.map ~f:(layout btn) (forest_ui (child ())) 117 - else Lwd.pure btn 118 - in 119 - Lwd.join (Lwd.map ~f:render (Lwd.get opened)) 120 - 121 - and forest_ui nodes = 122 - Lwd_utils.pack Ui.pack_y 123 - (List.map tree_ui nodes) 270 + let start_ui env = 271 + (*initialse the state*) 272 + onChange (); 273 + let term = Notty_unix.Term.create () in 274 + Vars.term := Some term; 275 + ui_loop ~quit:Vars.quit ~term (mainUi env) 124 276 ;; 125 277 126 - let rec fake_fs () = [ 127 - Tree ("bin", fake_fs); 128 - Tree ("home", fake_fs); 129 - Tree ("usr", fake_fs); 130 - ] in 278 + let start () = Eio_main.run @@ fun env -> Fiber.all [ (fun _ -> start_ui env) ];; 131 279 132 - Ui_loop.run (forest_ui (fake_fs ()));; 133 - *) 280 + start ()
+53
jj_tui/bin/widgets.ml
··· 1 + open Notty 2 + open Nottui 3 + module W = Nottui_widgets 4 + 5 + let neutral_grav = Gravity.make ~h:`Neutral ~v:`Neutral 6 + let make_even num = num + (num mod 2 * 1) 7 + 8 + (** This is for shifting something away from the edge it is pushed against *) 9 + let pad_edge x_pad y_pad grav ui = 10 + let y_pad = 11 + match grav |> Gravity.v with 12 + | `Negative -> 13 + -y_pad 14 + | `Neutral -> 15 + 0 16 + | `Positive -> 17 + y_pad 18 + in 19 + match grav |> Gravity.h with 20 + | `Negative -> 21 + ui |> Ui.shift_area (-x_pad) y_pad 22 + | `Neutral -> 23 + ui 24 + | `Positive -> 25 + ui |> Ui.shift_area x_pad y_pad 26 + ;; 27 + 28 + let border_box ?(pad = neutral_grav) ?(pad_h = 4) ?(pad_v = 2) ?(label = "") input = 29 + let width = Ui.layout_width input in 30 + let height = Ui.layout_height input in 31 + let edit = 32 + Ui.zcat 33 + [ 34 + I.char A.empty ' ' (width + pad_h) (height + pad_v) |> Ui.atom; 35 + input |> Ui.resize ~pad |> pad_edge (pad_h / 2) (pad_v / 2) pad; 36 + ] 37 + in 38 + let width = Ui.layout_width edit |> make_even in 39 + let h_border = String.init width (fun _ -> '=') |> W.string in 40 + let v_body = 41 + Ui.vcat 42 + [ 43 + Ui.zcat [ h_border; W.string label |> Ui.resize ~pad |> pad_edge 1 0 pad ]; 44 + edit; 45 + h_border; 46 + ] 47 + in 48 + let p = I.string A.empty "|" in 49 + let v_border = 50 + I.vcat (List.init (v_body |> Ui.layout_height) (fun _ -> p)) |> Ui.atom 51 + in 52 + Ui.hcat [ v_border; v_body; v_border ] 53 + ;;
+1 -16
jj_tui/lib/ansiReverse.ml
··· 10 10 let ( <| ), ( <. ), ( <! ) = Buffer.(add_string, add_char, add_decimal) *) 11 11 let invalid_arg fmt = Format.kasprintf invalid_arg fmt 12 12 13 - let rgb ~r ~g ~b = 14 - if r < 0 || g < 0 || b < 0 || r > 5 || g > 5 || b > 5 then 15 - invalid_arg "Notty.A.rgb %d %d %d: channel out of range" r g b 16 - else 0x01000000 lor ((r * 36) + (g * 6) + b + 16) 17 - 18 - let gray level = 19 - if level < 0 || level > 23 then 20 - invalid_arg "Notty.A.gray %d: level out of range" level 21 - else 0x01000000 lor (level + 232) 22 - 23 - let rgb_888 ~r ~g ~b = 24 - if r < 0 || g < 0 || b < 0 || r > 255 || g > 255 || b > 255 then 25 - invalid_arg "Notty.A.rgb_888 %d %d %d: channel out of range" r g b 26 - else 0x02000000 lor ((r lsl 16) lor (g lsl 8) lor b) 27 - 28 13 let sts = [ ";1"; ";3"; ";4"; ";5"; ";7" ] 29 14 30 15 let attr_of_ints fg bg st = ··· 88 73 | 46 :: _ -> A.bg A.cyan 89 74 | 47 :: _ -> A.bg A.white 90 75 | 48 :: 5 :: color :: _ -> 91 - A.bg (A.unsafe_color_of_int (0x02000000 lor color)) 76 + A.bg (A.unsafe_color_of_int (0x01000000 lor color)) 92 77 | _ -> A.empty 93 78 in 94 79 (attr, !j + 1)
+7
jj_tui/testing/log
··· 1 + [?1049h[?25l[?1000;1002;1005;1015;1006h[?2004h[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?1049l[?25h[?1000;1002;1005;1015;1006l[?2004l[?1049h[?25l[?1000;1002;1005;1015;1006h[?2004h[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?25l[?1049l[?25h[?1000;1002;1005;1015;1006l[?2004l@ mtxzlotn eli.jambu@gmail.com 2024-05-08 12:19:37 bb87f772 2 + │ (no description set) 3 + ◉ zknznuln eli.jambu@gmail.com 2024-05-08 12:11:59 521c21d7 4 + │ coloured output doesn't crash 5 + ◉ qotxovut eli.jambu@gmail.com 2024-05-08 01:52:09 c3e259d4 6 + │ hi 7 + ◉ zzzzzzzz root() 00000000