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.

using change_id when possible and commit id when duplicates are present

+148 -83
+2 -2
jj_tui/bin/file_view.ml
··· 34 34 "squash" 35 35 ; "-u" 36 36 ; "--from" 37 - ; Lwd.peek ui_state.selected_revision 37 + ; get_selected_rev() 38 38 ; "--into" 39 39 ; rev 40 40 ; Lwd.peek selected_file ··· 73 73 (**Get the status for the currently selected file*) 74 74 let file_status () = 75 75 let$ selected = Lwd.get selected_file 76 - and$ rev = Lwd.get Vars.ui_state.selected_revision in 76 + and$ rev = Vars.get_selected_rev_lwd()in 77 77 if selected != "" then jj_no_log [ "diff"; "-r"; rev; selected ] else "" 78 78 ;; 79 79 end
+32 -32
jj_tui/bin/global_funcs.ml
··· 36 36 (**Updates the status windows; Without snapshotting the working copy by default 37 37 This should be called after any command that performs a change *) 38 38 let update_status ?(update_graph = true) ?(cause_snapshot = false) () = 39 - let rev = Lwd.peek Vars.ui_state.selected_revision in 40 - let log_res = 41 - jj_no_log ~snapshot:cause_snapshot [ "log" ] 42 - |> colored_string 43 - in 44 - if update_graph then Vars.ui_state.trigger_update $= (); 39 + safe_jj (fun () -> 40 + let rev = Lwd.peek Vars.ui_state.selected_revision in 41 + let log_res = jj_no_log ~snapshot:cause_snapshot [ "log" ] |> colored_string in 42 + if update_graph then Vars.ui_state.trigger_update $= ()) 45 43 ;; 44 + 46 45 (**Updates the status windows; Without snapshotting the working copy by default 47 46 This should be called after any command that performs a change *) 48 - let update_views ?(cause_snapshot = false) () = 49 - let rev = Lwd.peek Vars.ui_state.selected_revision in 50 - Eio.Switch.run @@ fun sw -> 51 - let log_res = 52 - jj_no_log ~snapshot:cause_snapshot [ "show"; "-s"; "--color-words"; "-r"; rev ] 53 - |> colored_string 54 - in 55 - (* From now on we use ignore-working-copy so we don't re-snapshot the state and so 56 - we can operate in paralell *) 57 - let tree = 58 - Eio.Fiber.fork_promise ~sw (fun _ -> 59 - jj_no_log ~snapshot:false [ "log"; "-r"; rev ] |> colored_string) 60 - (* TODO: stop using dop last twice *) 61 - and branches = 62 - Eio.Fiber.fork_promise ~sw (fun _ -> 63 - jj_no_log ~snapshot:false [ "branch"; "list"; "-a" ] |> colored_string) 64 - and files_list = Eio.Fiber.fork_promise ~sw (fun _ -> list_files ~rev ()) in 65 - (*wait for all our tasks*) 66 - let tree = Eio.Promise.await_exn tree 67 - and files_list = Eio.Promise.await_exn files_list 68 - and branches = Eio.Promise.await_exn branches in 69 - (*now we can assign our results*) 70 - Vars.ui_state.jj_show $= log_res; 71 - Vars.ui_state.jj_branches $= branches; 72 - Vars.ui_state.jj_tree $= tree; 73 - Vars.ui_state.jj_change_files $= files_list 47 + let update_views ?(cause_snapshot = false) () = 48 + safe_jj (fun () -> 49 + let rev = Vars.get_selected_rev() in 50 + Eio.Switch.run @@ fun sw -> 51 + let log_res = 52 + jj_no_log ~snapshot:cause_snapshot [ "show"; "-s"; "--color-words"; "-r"; rev ] 53 + |> colored_string 54 + in 55 + (* From now on we use ignore-working-copy so we don't re-snapshot the state and so 56 + we can operate in paralell *) 57 + let tree = 58 + Eio.Fiber.fork_promise ~sw (fun _ -> 59 + jj_no_log ~snapshot:false [ "log"; "-r"; rev ] |> colored_string) 60 + (* TODO: stop using dop last twice *) 61 + and branches = 62 + Eio.Fiber.fork_promise ~sw (fun _ -> 63 + jj_no_log ~snapshot:false [ "branch"; "list"; "-a" ] |> colored_string) 64 + and files_list = Eio.Fiber.fork_promise ~sw (fun _ -> list_files ~rev ()) in 65 + (*wait for all our tasks*) 66 + let tree = Eio.Promise.await_exn tree 67 + and files_list = Eio.Promise.await_exn files_list 68 + and branches = Eio.Promise.await_exn branches in 69 + (*now we can assign our results*) 70 + Vars.ui_state.jj_show $= log_res; 71 + Vars.ui_state.jj_branches $= branches; 72 + Vars.ui_state.jj_tree $= tree; 73 + Vars.ui_state.jj_change_files $= files_list) 74 74 ;;
+37 -2
jj_tui/bin/global_vars.ml
··· 1 1 open Notty 2 2 open Nottui 3 3 open Eio.Std 4 + open Lwd_infix 4 5 5 6 type cmd_args = string list 7 + 8 + type rev_id = { 9 + change_id : string 10 + ; commit_id : string 11 + } 12 + 13 + type 'a maybe_unique = 14 + | Unique of 'a 15 + | Duplicate of 'a 6 16 7 17 type ui_state_t = { 8 18 view : ··· 22 32 ; jj_show : I.t Lwd.var 23 33 ; jj_branches : I.t Lwd.var 24 34 ; jj_change_files : (string * string) list Lwd.var 25 - ; selected_revision : string Lwd.var 35 + ; selected_revision : rev_id maybe_unique Lwd.var 26 36 ; trigger_update : unit Lwd.var 27 37 } 28 38 ··· 44 54 val ui_state : ui_state_t 45 55 val update_ui_state : (ui_state_t -> unit) -> unit 46 56 val render_mutex : Eio.Mutex.t 57 + 58 + (**returns either a change_id or if their are change_id conflicts, a commit_id *) 59 + val get_selected_rev : unit -> string 60 + 61 + (**returns either a change_id or if their are change_id conflicts, a commit_id *) 62 + val get_selected_rev_lwd : unit -> string Lwd.t 47 63 end 48 64 49 65 module Vars : Vars = struct ··· 64 80 ; jj_show = Lwd.var I.empty 65 81 ; jj_branches = Lwd.var I.empty 66 82 ; jj_change_files = Lwd.var [] 67 - ; selected_revision = Lwd.var "@" 83 + ; selected_revision = Lwd.var (Unique { change_id = "@"; commit_id = "@" }) 68 84 ; input = Lwd.var `Normal 69 85 ; show_popup = Lwd.var None 70 86 ; show_prompt = Lwd.var None ··· 92 108 let get_eio_env () = (Option.get !eio).env 93 109 let get_eio_vars () = Option.get !eio 94 110 let get_term () = Option.get !term 111 + 112 + (**Gets an id for the selected revision. If the change_id is unique we use that, if it's not we return a commit_id instead*) 113 + let get_selected_rev () = 114 + match Lwd.peek ui_state.selected_revision with 115 + | Unique { change_id; _ } -> 116 + change_id 117 + | Duplicate { commit_id; _ } -> 118 + commit_id 119 + ;; 120 + 121 + (**see [get_selected_rev]*) 122 + let get_selected_rev_lwd () = 123 + let$ a = Lwd.get ui_state.selected_revision in 124 + match a with 125 + | Unique { change_id; _ } -> 126 + change_id 127 + | Duplicate { commit_id; _ } -> 128 + commit_id 129 + ;; 95 130 end
+8 -8
jj_tui/bin/graph_view.ml
··· 56 56 SubCmd 57 57 [ 58 58 { 59 - key = 'S' 60 - ; cmd = Dynamic_r (fun rev -> Cmd_I [ "unsquash"; "-r"; rev; "-i" ]) 61 - ; description = "Interactivaly unsquash" 62 - } 63 - ; { 64 59 key = 's' 65 60 ; description = "Squash into parent" 66 61 ; cmd = ··· 68 63 (fun _ -> 69 64 let curr_msg, prev_msg = get_messages () in 70 65 let new_msg = prev_msg ^ curr_msg in 71 - let rev = Lwd.peek Vars.ui_state.selected_revision in 66 + let rev = Vars.get_selected_rev () in 72 67 jj [ "squash"; "--quiet"; "-r"; rev; "-m"; new_msg ] |> ignore) 73 68 } 74 69 ; { ··· 80 75 , fun str -> 81 76 let curr_msg, prev_msg = get_messages () in 82 77 let new_msg = prev_msg ^ curr_msg in 83 - Cmd_r [ "squash"; "--quiet"; "-m"; new_msg; "--into"; str ] ) 78 + Dynamic_r(fun rev->Cmd [ "squash"; "--quiet"; "-m"; new_msg;"--from";rev; "--into"; str ] )) 79 + } 80 + ; { 81 + key = 'u' 82 + ; cmd = Dynamic_r (fun rev -> Cmd_I [ "unsquash"; "-r"; rev; "-i" ]) 83 + ; description = "Interactivaly unsquash" 84 84 } 85 85 ; { 86 86 key = 'i' ··· 239 239 let ui = 240 240 let$ graph, rev_ids = 241 241 (*TODO I think this ads a slight delay to everything becasue it makes things need to be renedered twice. maybe I could try getting rid of it*) 242 - Vars.ui_state.trigger_update |> Lwd.get |> Lwd.map ~f:(fun _ -> seperate_revs ()) 242 + Vars.ui_state.trigger_update |> Lwd.get |> Lwd.map ~f:(fun _ -> graph_and_revs ()) 243 243 in 244 244 let selectable_idx = ref 0 in 245 245 graph
+6 -20
jj_tui/bin/jj_commands.ml
··· 47 47 open! Jj_tui.Util 48 48 module Wd = Jj_tui.Widgets 49 49 50 + 50 51 exception Handled 51 52 52 53 let render_command_line ~indent_level key desc = ··· 75 76 :: render_commands ~indent_level:(indent_level + 1) subs 76 77 ;; 77 78 78 - (*handle exception from jj*) 79 - let handle_jj_error error = 80 - ui_state.show_prompt $= None; 81 - ui_state.show_popup 82 - $= Some 83 - ( error 84 - |> Jj_tui.AnsiReverse.colored_string 85 - |> Ui.atom 86 - |> Ui.resize ~sw:1 ~sh:1 87 - |> Lwd.pure 88 - , "An error occured running that command" ); 89 - ui_state.input $= `Mode (fun _ -> `Unhandled) 90 - ;; 91 79 92 - (*catch any exceptions from jj*) 93 - let safe_jj f = try f () with JJError error -> handle_jj_error error 94 80 95 81 let commands_list_ui commands = 96 82 let move_command = ··· 146 132 raise Handled 147 133 | Cmd_r args -> 148 134 ui_state.show_popup $= None; 149 - noOut (args@["-r";Lwd.peek ui_state.selected_revision]); 135 + noOut (args@["-r";Vars.get_selected_rev()]); 150 136 raise Handled 151 137 | Prompt (str, args) -> 152 138 ui_state.show_popup $= None; ··· 154 140 raise Handled 155 141 | Prompt_r (str, args) -> 156 142 ui_state.show_popup $= None; 157 - prompt str (`Cmd (args@["-r";Lwd.peek ui_state.selected_revision])); 143 + prompt str (`Cmd (args@["-r";Vars.get_selected_rev()])); 158 144 raise Handled 159 145 | PromptThen (label, next) -> 160 146 ui_state.show_popup $= None; ··· 177 163 | Dynamic f -> 178 164 f () |> handleCommand description 179 165 | Dynamic_r f -> 180 - f (Lwd.peek Vars.ui_state.selected_revision) |> handleCommand description 166 + f (Vars.get_selected_rev()) |> handleCommand description 181 167 182 168 (** Try mapching the command mapping to the provided key and run the command if it matches *) 183 169 and command_input ~is_sub keymap key = ··· 191 177 | Handled -> 192 178 if is_sub then ui_state.input $= `Normal; 193 179 `Handled 194 - | JJError error -> 195 - handle_jj_error error; 180 + | JJError (cmd,error) -> 181 + handle_jj_error cmd error; 196 182 `Unhandled 197 183 198 184 and command_no_input description cmd =
+23 -4
jj_tui/bin/jj_process.ml
··· 60 60 Mutex.lock access_lock; 61 61 true) 62 62 else false 63 - in 63 + in 64 64 let res = 65 65 cmdArgs 66 66 "jj" ··· 75 75 res 76 76 ;; 77 77 78 - exception JJError of string 78 + exception JJError of string*string 79 + 79 80 (** Run a jj command without outputting to the command_log. 80 81 @param ?snapshot=true 81 82 When true snapshots the state when running the command and also aquires a lock before running it. Set to false for commands you wish to run concurrently. like those for generating content in the UI ··· 86 87 | Ok a -> 87 88 a 88 89 | Error (`BadExit (code, str)) -> 89 - raise (JJError (Printf.sprintf "Exited with code %i; Message:\n%s" code str)) 90 + raise (JJError( "jj"::args|>String.concat " " ,Printf.sprintf "Exited with code %i; Message:\n%s" code str)) 90 91 | Error (`EioErr a) -> 91 - raise (JJError (Printf.sprintf 92 + raise (JJError ("jj"::args|>String.concat " ",Printf.sprintf 92 93 "Error running jj process:\n%a" 93 94 (fun _ -> Base.Error.to_string_hum) 94 95 a)) ··· 119 120 let current, prev = output |>Jj_tui.OutputParsing.parse_descriptions|>Result.get_ok in 120 121 current |> String.concat "", prev |> String.concat "" 121 122 ;; 123 + open Vars 124 + open Nottui 125 + open Lwd_infix 126 + (*handle exception from jj*) 127 + let handle_jj_error cmd error = 128 + ui_state.show_prompt $= None; 129 + ui_state.show_popup 130 + $= Some 131 + ( error 132 + |> Jj_tui.AnsiReverse.colored_string 133 + |> Ui.atom 134 + |> Ui.resize ~sw:1 ~sh:1 135 + |> Lwd.pure 136 + , Printf.sprintf"An error occured running %s" cmd ); 137 + ui_state.input $= `Mode (fun _ -> `Unhandled) 138 + ;; 139 + (*catch any exceptions from jj*) 140 + let safe_jj f = try f () with JJError (cmd,error) -> handle_jj_error cmd error 122 141 end
+40 -15
jj_tui/bin/jj_widgets.ml
··· 10 10 11 11 module Make (Vars : Global_vars.Vars) = struct 12 12 open Vars 13 + open Global_vars 13 14 open Jj_process.Make (Vars) 14 15 15 16 exception FoundStart ··· 62 63 (*chars like these: ├─╮*) 63 64 let is_pipe = i > 0x2500 && i < 0x259f in 64 65 let is_whitespace = is_whitespace_char i in 65 - is_pipe || is_whitespace 66 + is_pipe || is_whitespace 66 67 ;; 67 68 68 69 let test_data = ··· 134 135 |> String.iteri (fun i char -> 135 136 let uchar = String.get_utf_8_uchar line i |> Uchar.utf_decode_uchar in 136 137 (*I've removed the part that tries to precisely skip all the start chars. this is becasue it gets all stuffed up by the terminal escape codes 137 - FIXME currently this will get stuffed up if a line has that rev symbol in it 138 + FIXME currently this will get stuffed up if a line has that rev symbol in it 138 139 *) 139 140 140 141 (* if not (uchar |> is_graph_start_char) *) 141 142 (* then *) 142 - if uchar |> Uchar.equal rev_symbol || char == '@' 143 - then raise FoundStart 144 - (* else raise FoundFiller *) 145 - (* else () *) 146 - ) 143 + if uchar |> Uchar.equal rev_symbol || char == '@' then raise FoundStart 144 + (* else raise FoundFiller *) 145 + (* else () *)) 146 + ;; 147 + 148 + (** Function to tag duplicated items in a list *) 149 + let tag_duplicates lst = 150 + (* Create a frequency map to count occurrences of each element *) 151 + let freq_map = 152 + List.fold_left (fun acc {change_id;_} -> 153 + let count = try List.assoc change_id acc with Not_found -> 0 in 154 + (change_id, count + 1) :: List.remove_assoc change_id acc 155 + ) [] lst 156 + in 157 + (* Tag each item in the list based on the frequency map *) 158 + List.map (fun ({change_id;_} as x) -> 159 + if List.assoc change_id freq_map > 1 then Duplicate x else Unique x 160 + ) lst 161 + 162 + (**Returns a list of revs with both the change_id and commit_id*) 163 + let get_revs () = 164 + jj_no_log 165 + ~color:false 166 + [ "log"; "-T"; {|"|"++change_id++"|"++commit_id++"\n"|} ] 167 + |> String.split_on_char '\n' 168 + |> List.filter_map (fun x -> 169 + let items = x |> String.split_on_char '|' in 170 + match items with 171 + | [ _graph; change_id; commit_id ] -> 172 + Some { change_id; commit_id } 173 + | _ -> 174 + None) 175 + |>tag_duplicates 176 + |> Array.of_list 147 177 ;; 148 178 149 - let seperate_revs () = 179 + (** returns the graph and a list of revs within that graph*) 180 + let graph_and_revs () = 150 181 let graph = 151 182 jj_no_log [ "log" ] 152 183 |> String.split_on_char '\n' ··· 171 202 |> List.rev 172 203 |> Array.of_list 173 204 in 174 - let revs = 175 - jj_no_log ~color:false [ "log"; "--no-graph"; "-T"; {|change_id++"\n"|} ] 176 - |> String.split_on_char '\n' 177 - |> List.filter (fun x -> x |> String.trim <> "") 178 - |> Array.of_list 179 - in 205 + let revs = get_revs () in 180 206 graph, revs 181 207 ;; 182 208 ··· 220 246 string) 221 247 else string 222 248 ;; 223 - 224 249 225 250 (* (*TODO:make a custom widget the renders the commit with and without selection. *) 226 251 (* with selection replace the dot with a blue version and slightly blue tint the background *) *)