diff --git a/jj_tui/lib/commit_render.ml b/jj_tui/lib/commit_render.ml index dc14123..a9eb720 100644 --- a/jj_tui/lib/commit_render.ml +++ b/jj_tui/lib/commit_render.ml @@ -24,6 +24,8 @@ let graph_node_attr (node : Render_jj_graph.node) : Notty.A.t = then fg green ++ st bold else if node.conflict then fg red ++ st bold + else if node.divergent + then fg red ++ st bold else if node.immutable then fg cyan else fg white @@ -42,9 +44,9 @@ let render_commit_content (node : Render_jj_graph.node) : Notty.image list = let open Notty in let open Notty.A in - let magenta= if node.working_copy then lightmagenta else magenta in -(*make style bold if working copy*) -let bs = if node.working_copy then st bold else st A.no_style in + let magenta = if node.working_copy then lightmagenta else magenta in + (* make style bold if working copy *) + let bs = if node.working_copy then st bold else st A.no_style in let styled_text attr text = I.string attr text in let description_line = match String.split_on_char '\n' node.description with @@ -55,13 +57,16 @@ let bs = if node.working_copy then st bold else st A.no_style in in (* Line 1: change_id email timestamp bookmarks commit_id_short *) let line1_parts = ref [] in - (* Determine base color for change_id based on node state *) - (* Render change_id with prefix highlighting *) - let change_id_prefix_attr = - fg magenta ++ st bold + let change_id_prefix_attr, change_id_rest_attr = + if node.hidden + then + let duplicate_attr = fg white ++ st bold in + duplicate_attr, duplicate_attr + else if node.divergent + then fg red ++ st bold, fg red ++ bs + else fg magenta ++ st bold, fg lightblack ++ bs in - let change_id_rest_attr = fg lightblack ++bs in let change_id_img = render_id ~prefix_attr:change_id_prefix_attr @@ -71,18 +76,18 @@ let bs = if node.working_copy then st bold else st A.no_style in in line1_parts := change_id_img :: !line1_parts; (* Author email and timestamp *) - line1_parts - := styled_text (fg yellow ++bs) (" " ^ node.author_email) :: !line1_parts; - line1_parts - := styled_text (fg cyan++bs ) (" " ^ node.author_timestamp) :: !line1_parts; + line1_parts := styled_text (fg yellow ++ bs) (" " ^ node.author_email) :: !line1_parts; + line1_parts := styled_text (fg cyan ++ bs) (" " ^ node.author_timestamp) :: !line1_parts; (* Add bookmarks after timestamp if they exist *) if List.length node.bookmarks > 0 then ( let bookmarks_str = " " ^ String.concat " " node.bookmarks in - line1_parts := styled_text (fg magenta ) bookmarks_str :: !line1_parts); + line1_parts := styled_text (fg magenta) bookmarks_str :: !line1_parts); (* Render commit_id with prefix highlighting *) - let commit_id_prefix_attr = (if node.working_copy then fg lightblue else fg blue) ++ st bold in - let commit_id_rest_attr = fg lightblack ++bs in + let commit_id_prefix_attr = + (if node.working_copy then fg lightblue else fg blue) ++ st bold + in + let commit_id_rest_attr = fg lightblack ++ bs in let commit_id_img = render_id ~prefix_attr:commit_id_prefix_attr @@ -91,19 +96,29 @@ let bs = if node.working_copy then st bold else st A.no_style in ~rest:node.commit_id_rest in line1_parts := commit_id_img :: !line1_parts; + let labels = + [ if node.hidden then Some (fg lightblack ++ bs, "(hidden)") else None + ; if node.divergent then Some (fg red ++ bs, "(divergent)") else None + ] + |> List.filter_map Fun.id + in + labels + |> List.iter (fun (attr, label) -> + line1_parts := styled_text attr (" " ^ label) :: !line1_parts); let line1 = !line1_parts |> List.rev |> I.hcat in (* Line 2: (empty) description *) let desc_attr = - ( if node.is_preview || node.empty - then lightgreen - else if node.description="" - then yellow - else white) - |>fg|> (++) bs + (if node.is_preview || node.empty + then lightgreen + else if node.description = "" + then yellow + else white) + |> fg + |> ( ++ ) bs in let description_with_prefix = if node.empty then "(empty) " ^ description_line else description_line in let line2 = styled_text desc_attr description_with_prefix in - [add_padding line1; add_padding line2 ] + [ add_padding line1; add_padding line2 ] ;; diff --git a/jj_tui/lib/commit_render_tests.ml b/jj_tui/lib/commit_render_tests.ml index 7cac1d6..b2da5d1 100644 --- a/jj_tui/lib/commit_render_tests.ml +++ b/jj_tui/lib/commit_render_tests.ml @@ -5,6 +5,7 @@ *) open Commit_render +open AnsiReverse (** Render Notty image to string for testing (text-only, no ANSI codes) *) let image_to_string img = @@ -14,6 +15,11 @@ let image_to_string img = Buffer.contents buf ;; +let image_to_ansi_string img = + Notty.Render.pp_image Format.str_formatter img; + Format.flush_str_formatter () +;; + let render_and_print node = render_commit_content node |> List.iter (fun img -> print_endline (image_to_string img)) ;; @@ -25,6 +31,8 @@ let make_test_node ?(wip = false) ?(empty = false) ?(is_preview = false) + ?(hidden = false) + ?(divergent = false) ?(bookmarks = []) ~change_id_prefix ~change_id_rest @@ -47,8 +55,8 @@ let make_test_node ; author_email = "test@example.com" ; author_timestamp = "2024-01-01" ; empty - ; hidden = false - ; divergent = false + ; hidden + ; divergent ; conflict = false ; is_preview ; change_id_prefix @@ -187,3 +195,110 @@ let%expect_test "render_no_description" = (no description set) |}] ;; + +let%expect_test "render_hidden_duplicate_commit" = + let node = + make_test_node + ~change_id_prefix:"upnslvuv" + ~change_id_rest:"/2" + ~commit_id_prefix:"4c63c987" + ~commit_id_rest:"" + ~description:"make bookmarks render origin if needed" + ~bookmarks:[ "re-based@origin" ] + ~hidden:true + () + in + render_and_print node; + [%expect + {| + upnslvuv/2 test@example.com 2024-01-01 re-based@origin 4c63c987 (hidden) + make bookmarks render origin if needed + |}] +;; + +let%expect_test "render_divergent_commit" = + let node = + make_test_node + ~change_id_prefix:"lqzzqwqx" + ~change_id_rest:"/0" + ~commit_id_prefix:"5ab39974" + ~commit_id_rest:"" + ~description:"disable worker mode" + ~bookmarks:[ "main??"; "main@git" ] + ~divergent:true + () + in + render_and_print node; + [%expect + {| + lqzzqwqx/0 test@example.com 2024-01-01 main?? main@git 5ab39974 (divergent) + disable worker mode + |}] +;; + +let%expect_test "render_divergent_commit_uses_red_styling" = + let node = + make_test_node + ~change_id_prefix:"lqzzqwqx" + ~change_id_rest:"/0" + ~commit_id_prefix:"5ab39974" + ~commit_id_rest:"" + ~description:"disable worker mode" + ~bookmarks:[ "main??"; "main@git" ] + ~divergent:true + () + in + let line1 = render_commit_content node |> List.hd in + line1 + |> image_to_ansi_string + |> Parser.parse_ansi_escape_codes + |> Result.get_ok + |> List.iter (fun (attr, text) -> + if String.trim text <> "" + then ( + AnsiReverse.Internal.print_attr attr; + Printf.printf "Text: %S\n" text)); + [%expect + {| + attr: + \e[0m<\e[0;31;1mATTR\e[0m\e[K\e[0m>\e[0m + Text: "lqzzqwqx" + attr: + \e[0m<\e[0;31mATTR\e[0m\e[K\e[0m>\e[0m + Text: "/0" + attr: + \e[0m<\e[0;33mATTR\e[0m\e[K\e[0m>\e[0m + Text: " test@example.com" + attr: + \e[0m<\e[0;36mATTR\e[0m\e[K\e[0m>\e[0m + Text: " 2024-01-01" + attr: + \e[0m<\e[0;35mATTR\e[0m\e[K\e[0m>\e[0m + Text: " main?? main@git" + attr: + \e[0m<\e[0;34;1mATTR\e[0m\e[K\e[0m>\e[0m + Text: " 5ab39974" + attr: + \e[0m<\e[0;31mATTR\e[0m\e[K\e[0m>\e[0m + Text: " (divergent)" + |}] +;; + +let%expect_test "graph_node_attr_divergent_is_red_bold" = + let node = + make_test_node + ~change_id_prefix:"lqzzqwqx" + ~change_id_rest:"/0" + ~commit_id_prefix:"5ab39974" + ~commit_id_rest:"" + ~description:"disable worker mode" + ~divergent:true + () + in + node |> graph_node_attr |> AnsiReverse.Internal.print_attr; + [%expect + {| + attr: + \e[0m<\e[0;31;1mATTR\e[0m\e[K\e[0m>\e[0m + |}] +;; diff --git a/jj_tui/lib/render_jj_graph_tests.ml b/jj_tui/lib/render_jj_graph_tests.ml index 1d26e10..4841530 100644 --- a/jj_tui/lib/render_jj_graph_tests.ml +++ b/jj_tui/lib/render_jj_graph_tests.ml @@ -913,6 +913,58 @@ let%expect_test "render_nodes_structured_row_types" = |}] ;; +let%expect_test "render_nodes_structured_divergent_node_row" = + let divergent : node = + { + parents = [] + ; creation_time = Int64.zero + ; working_copy = false + ; immutable = false + ; wip = false + ; change_id = "lqzzqwqx/0" + ; commit_id = "5ab39974" + ; description = "disable worker mode" + ; bookmarks = [] + ; author_email = "test@example.com" + ; author_timestamp = "2024-01-01" + ; empty = false + ; hidden = false + ; divergent = true + ; conflict = false + ; is_preview = false + ; change_id_prefix = "lqzzqwqx" + ; change_id_rest = "/0" + ; commit_id_prefix = "5ab39974" + ; commit_id_rest = "" + } + in + let state : state = { depth = 0; columns = [||]; pending_joins = [] } in + let rows = + Render_jj_graph.render_nodes_structured state [ divergent ] ~info_lines:(fun _ -> 0) + in + Printf.printf "Total rows: %d\n" (List.length rows); + List.iter + (fun row -> + let row_type_str = + match row.row_type with + | NodeRow -> + "NodeRow" + | LinkRow -> + "LinkRow" + | PadRow -> + "PadRow" + | TermRow -> + "TermRow" + in + Printf.printf "%s: '%s'\n" row_type_str row.graph_chars) + rows; + [%expect + {| + Total rows: 1 + NodeRow: '○' + |}] +;; + let%expect_test "elided_parent_creates_termination_line" = let elided = Render_jj_graph.make_elided_node () in let child : node =