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.

render divergent nodes

+206 -24
+37 -22
jj_tui/lib/commit_render.ml
··· 24 24 then fg green ++ st bold 25 25 else if node.conflict 26 26 then fg red ++ st bold 27 + else if node.divergent 28 + then fg red ++ st bold 27 29 else if node.immutable 28 30 then fg cyan 29 31 else fg white ··· 42 44 let open Notty in 43 45 let open Notty.A in 44 46 45 - let magenta= if node.working_copy then lightmagenta else magenta in 46 - (*make style bold if working copy*) 47 - let bs = if node.working_copy then st bold else st A.no_style in 47 + let magenta = if node.working_copy then lightmagenta else magenta in 48 + (* make style bold if working copy *) 49 + let bs = if node.working_copy then st bold else st A.no_style in 48 50 let styled_text attr text = I.string attr text in 49 51 let description_line = 50 52 match String.split_on_char '\n' node.description with ··· 55 57 in 56 58 (* Line 1: change_id email timestamp bookmarks commit_id_short *) 57 59 let line1_parts = ref [] in 58 - (* Determine base color for change_id based on node state *) 59 - 60 60 (* Render change_id with prefix highlighting *) 61 - let change_id_prefix_attr = 62 - fg magenta ++ st bold 61 + let change_id_prefix_attr, change_id_rest_attr = 62 + if node.hidden 63 + then 64 + let duplicate_attr = fg white ++ st bold in 65 + duplicate_attr, duplicate_attr 66 + else if node.divergent 67 + then fg red ++ st bold, fg red ++ bs 68 + else fg magenta ++ st bold, fg lightblack ++ bs 63 69 in 64 - let change_id_rest_attr = fg lightblack ++bs in 65 70 let change_id_img = 66 71 render_id 67 72 ~prefix_attr:change_id_prefix_attr ··· 71 76 in 72 77 line1_parts := change_id_img :: !line1_parts; 73 78 (* Author email and timestamp *) 74 - line1_parts 75 - := styled_text (fg yellow ++bs) (" " ^ node.author_email) :: !line1_parts; 76 - line1_parts 77 - := styled_text (fg cyan++bs ) (" " ^ node.author_timestamp) :: !line1_parts; 79 + line1_parts := styled_text (fg yellow ++ bs) (" " ^ node.author_email) :: !line1_parts; 80 + line1_parts := styled_text (fg cyan ++ bs) (" " ^ node.author_timestamp) :: !line1_parts; 78 81 (* Add bookmarks after timestamp if they exist *) 79 82 if List.length node.bookmarks > 0 80 83 then ( 81 84 let bookmarks_str = " " ^ String.concat " " node.bookmarks in 82 - line1_parts := styled_text (fg magenta ) bookmarks_str :: !line1_parts); 85 + line1_parts := styled_text (fg magenta) bookmarks_str :: !line1_parts); 83 86 (* Render commit_id with prefix highlighting *) 84 - let commit_id_prefix_attr = (if node.working_copy then fg lightblue else fg blue) ++ st bold in 85 - let commit_id_rest_attr = fg lightblack ++bs in 87 + let commit_id_prefix_attr = 88 + (if node.working_copy then fg lightblue else fg blue) ++ st bold 89 + in 90 + let commit_id_rest_attr = fg lightblack ++ bs in 86 91 let commit_id_img = 87 92 render_id 88 93 ~prefix_attr:commit_id_prefix_attr ··· 91 96 ~rest:node.commit_id_rest 92 97 in 93 98 line1_parts := commit_id_img :: !line1_parts; 99 + let labels = 100 + [ if node.hidden then Some (fg lightblack ++ bs, "(hidden)") else None 101 + ; if node.divergent then Some (fg red ++ bs, "(divergent)") else None 102 + ] 103 + |> List.filter_map Fun.id 104 + in 105 + labels 106 + |> List.iter (fun (attr, label) -> 107 + line1_parts := styled_text attr (" " ^ label) :: !line1_parts); 94 108 let line1 = !line1_parts |> List.rev |> I.hcat in 95 109 (* Line 2: (empty) description *) 96 110 let desc_attr = 97 - ( if node.is_preview || node.empty 98 - then lightgreen 99 - else if node.description="" 100 - then yellow 101 - else white) 102 - |>fg|> (++) bs 111 + (if node.is_preview || node.empty 112 + then lightgreen 113 + else if node.description = "" 114 + then yellow 115 + else white) 116 + |> fg 117 + |> ( ++ ) bs 103 118 in 104 119 let description_with_prefix = 105 120 if node.empty then "(empty) " ^ description_line else description_line 106 121 in 107 122 let line2 = styled_text desc_attr description_with_prefix in 108 - [add_padding line1; add_padding line2 ] 123 + [ add_padding line1; add_padding line2 ] 109 124 ;;
+117 -2
jj_tui/lib/commit_render_tests.ml
··· 5 5 *) 6 6 7 7 open Commit_render 8 + open AnsiReverse 8 9 9 10 (** Render Notty image to string for testing (text-only, no ANSI codes) *) 10 11 let image_to_string img = ··· 12 13 let w, h = Notty.I.(width img, height img) in 13 14 Notty.Render.to_buffer buf Notty.Cap.dumb (0, 0) (w, h) img; 14 15 Buffer.contents buf 16 + ;; 17 + 18 + let image_to_ansi_string img = 19 + Notty.Render.pp_image Format.str_formatter img; 20 + Format.flush_str_formatter () 15 21 ;; 16 22 17 23 let render_and_print node = ··· 25 31 ?(wip = false) 26 32 ?(empty = false) 27 33 ?(is_preview = false) 34 + ?(hidden = false) 35 + ?(divergent = false) 28 36 ?(bookmarks = []) 29 37 ~change_id_prefix 30 38 ~change_id_rest ··· 47 55 ; author_email = "test@example.com" 48 56 ; author_timestamp = "2024-01-01" 49 57 ; empty 50 - ; hidden = false 51 - ; divergent = false 58 + ; hidden 59 + ; divergent 52 60 ; conflict = false 53 61 ; is_preview 54 62 ; change_id_prefix ··· 187 195 (no description set) 188 196 |}] 189 197 ;; 198 + 199 + let%expect_test "render_hidden_duplicate_commit" = 200 + let node = 201 + make_test_node 202 + ~change_id_prefix:"upnslvuv" 203 + ~change_id_rest:"/2" 204 + ~commit_id_prefix:"4c63c987" 205 + ~commit_id_rest:"" 206 + ~description:"make bookmarks render origin if needed" 207 + ~bookmarks:[ "re-based@origin" ] 208 + ~hidden:true 209 + () 210 + in 211 + render_and_print node; 212 + [%expect 213 + {| 214 + upnslvuv/2 test@example.com 2024-01-01 re-based@origin 4c63c987 (hidden) 215 + make bookmarks render origin if needed 216 + |}] 217 + ;; 218 + 219 + let%expect_test "render_divergent_commit" = 220 + let node = 221 + make_test_node 222 + ~change_id_prefix:"lqzzqwqx" 223 + ~change_id_rest:"/0" 224 + ~commit_id_prefix:"5ab39974" 225 + ~commit_id_rest:"" 226 + ~description:"disable worker mode" 227 + ~bookmarks:[ "main??"; "main@git" ] 228 + ~divergent:true 229 + () 230 + in 231 + render_and_print node; 232 + [%expect 233 + {| 234 + lqzzqwqx/0 test@example.com 2024-01-01 main?? main@git 5ab39974 (divergent) 235 + disable worker mode 236 + |}] 237 + ;; 238 + 239 + let%expect_test "render_divergent_commit_uses_red_styling" = 240 + let node = 241 + make_test_node 242 + ~change_id_prefix:"lqzzqwqx" 243 + ~change_id_rest:"/0" 244 + ~commit_id_prefix:"5ab39974" 245 + ~commit_id_rest:"" 246 + ~description:"disable worker mode" 247 + ~bookmarks:[ "main??"; "main@git" ] 248 + ~divergent:true 249 + () 250 + in 251 + let line1 = render_commit_content node |> List.hd in 252 + line1 253 + |> image_to_ansi_string 254 + |> Parser.parse_ansi_escape_codes 255 + |> Result.get_ok 256 + |> List.iter (fun (attr, text) -> 257 + if String.trim text <> "" 258 + then ( 259 + AnsiReverse.Internal.print_attr attr; 260 + Printf.printf "Text: %S\n" text)); 261 + [%expect 262 + {| 263 + attr: 264 + \e[0m<\e[0;31;1mATTR\e[0m\e[K\e[0m>\e[0m 265 + Text: "lqzzqwqx" 266 + attr: 267 + \e[0m<\e[0;31mATTR\e[0m\e[K\e[0m>\e[0m 268 + Text: "/0" 269 + attr: 270 + \e[0m<\e[0;33mATTR\e[0m\e[K\e[0m>\e[0m 271 + Text: " test@example.com" 272 + attr: 273 + \e[0m<\e[0;36mATTR\e[0m\e[K\e[0m>\e[0m 274 + Text: " 2024-01-01" 275 + attr: 276 + \e[0m<\e[0;35mATTR\e[0m\e[K\e[0m>\e[0m 277 + Text: " main?? main@git" 278 + attr: 279 + \e[0m<\e[0;34;1mATTR\e[0m\e[K\e[0m>\e[0m 280 + Text: " 5ab39974" 281 + attr: 282 + \e[0m<\e[0;31mATTR\e[0m\e[K\e[0m>\e[0m 283 + Text: " (divergent)" 284 + |}] 285 + ;; 286 + 287 + let%expect_test "graph_node_attr_divergent_is_red_bold" = 288 + let node = 289 + make_test_node 290 + ~change_id_prefix:"lqzzqwqx" 291 + ~change_id_rest:"/0" 292 + ~commit_id_prefix:"5ab39974" 293 + ~commit_id_rest:"" 294 + ~description:"disable worker mode" 295 + ~divergent:true 296 + () 297 + in 298 + node |> graph_node_attr |> AnsiReverse.Internal.print_attr; 299 + [%expect 300 + {| 301 + attr: 302 + \e[0m<\e[0;31;1mATTR\e[0m\e[K\e[0m>\e[0m 303 + |}] 304 + ;;
+52
jj_tui/lib/render_jj_graph_tests.ml
··· 913 913 |}] 914 914 ;; 915 915 916 + let%expect_test "render_nodes_structured_divergent_node_row" = 917 + let divergent : node = 918 + { 919 + parents = [] 920 + ; creation_time = Int64.zero 921 + ; working_copy = false 922 + ; immutable = false 923 + ; wip = false 924 + ; change_id = "lqzzqwqx/0" 925 + ; commit_id = "5ab39974" 926 + ; description = "disable worker mode" 927 + ; bookmarks = [] 928 + ; author_email = "test@example.com" 929 + ; author_timestamp = "2024-01-01" 930 + ; empty = false 931 + ; hidden = false 932 + ; divergent = true 933 + ; conflict = false 934 + ; is_preview = false 935 + ; change_id_prefix = "lqzzqwqx" 936 + ; change_id_rest = "/0" 937 + ; commit_id_prefix = "5ab39974" 938 + ; commit_id_rest = "" 939 + } 940 + in 941 + let state : state = { depth = 0; columns = [||]; pending_joins = [] } in 942 + let rows = 943 + Render_jj_graph.render_nodes_structured state [ divergent ] ~info_lines:(fun _ -> 0) 944 + in 945 + Printf.printf "Total rows: %d\n" (List.length rows); 946 + List.iter 947 + (fun row -> 948 + let row_type_str = 949 + match row.row_type with 950 + | NodeRow -> 951 + "NodeRow" 952 + | LinkRow -> 953 + "LinkRow" 954 + | PadRow -> 955 + "PadRow" 956 + | TermRow -> 957 + "TermRow" 958 + in 959 + Printf.printf "%s: '%s'\n" row_type_str row.graph_chars) 960 + rows; 961 + [%expect 962 + {| 963 + Total rows: 1 964 + NodeRow: '○' 965 + |}] 966 + ;; 967 + 916 968 let%expect_test "elided_parent_creates_termination_line" = 917 969 let elided = Render_jj_graph.make_elided_node () in 918 970 let child : node =