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.

fixed anis parsing and printing to support all ansi codes properly

+274 -48
+13 -1
forks/notty/src/notty.ml
··· 195 195 and lightmagenta = 0x0100000d 196 196 and lightcyan = 0x0100000e 197 197 and lightwhite = 0x0100000f 198 + and no_color = 0x01000010 198 199 199 200 let tag c = (c land 0x03000000) lsr 24 200 201 ··· 223 224 and underline = 4 224 225 and blink = 8 225 226 and reverse = 16 227 + and hidden = 32 228 + and strike = 64 229 + and dim = 128 230 + and no_style = 0 231 + 226 232 227 233 let empty = { fg = 0; bg = 0; st = 0 } 228 234 ··· 231 237 { fg = (match a2.fg with 0 -> a1.fg | x -> x) 232 238 ; bg = (match a2.bg with 0 -> a1.bg | x -> x) 233 239 ; st = a1.st lor a2.st } 240 + 241 + let (--) a1 a2 = 242 + if a1 == empty then a2 else if a2 == empty then a1 else 243 + { fg = (match a2.fg with no_color -> 0 | _ -> a1.fg) 244 + ; bg = (match a2.bg with no_color -> 0 | _ -> a1.bg) 245 + ; st = a1.st land (lnot a2.st) } 234 246 235 247 let fg fg = { empty with fg } 236 248 let bg bg = { empty with bg } ··· 520 532 521 533 let ((<|), (<.), (<!)) = Buffer.(add_string, add_char, add_decimal) 522 534 523 - let sts = [ ";1"; ";3"; ";4"; ";5"; ";7" ] 535 + let sts = [ ";1"; ";3"; ";4"; ";5"; ";7"; ";8"; ";9";";2" ] 524 536 525 537 let sgr { A.fg; bg; st } buf = 526 538 buf <| "\x1b[0";
+12
forks/notty/src/notty.mli
··· 104 104 val lightmagenta : color 105 105 val lightcyan : color 106 106 val lightwhite : color 107 + val no_color : color 107 108 108 109 (** {2 Extended 256-color palette} *) 109 110 ··· 145 146 val underline : style 146 147 val blink : style 147 148 val reverse : style 149 + val hidden : style 150 + val strike : style 151 + val dim : style 152 + val no_style : style 148 153 149 154 (** {1 Attribute construction and composition} *) 150 155 ··· 162 167 is [a1]'s, and the union of both style sets. 163 168 164 169 [++] is left-associative, and forms a monoid with [empty]. *) 170 + 171 + val (--) : attr -> attr -> attr 172 + (** [a1 -- a2] is the difference of [a1] and [a2], the attribute that has 173 + [a1]'s foreground (resp. background), unless {e unset}, in which case it 174 + is [a2]'s, and the difference of both style sets. 175 + 176 + [--] is left-associative, and forms a monoid with [empty]. *) 165 177 166 178 val fg : color -> attr 167 179 (** [fg c] is [empty] with foreground [c]. *)
+249 -47
jj_tui/lib/ansiReverse.ml
··· 46 46 module Parser = struct 47 47 open Internal 48 48 49 + type attr_action = 50 + | Apply of A.t 51 + | Reset of A.t 52 + | FullyReset 49 53 let parse_escape_seq = 50 54 let open A in 51 55 let open Angstrom in ··· 57 61 let escape_sequence = char '\027' *> char '[' *> params <* char 'm' in 58 62 let attr_of_params = function 59 63 | [] -> 60 - empty 64 + Apply empty 61 65 | 0 :: _ -> 62 - empty 66 + FullyReset 63 67 | 1 :: _ -> 64 - st bold 68 + Apply (st bold) 65 69 | 2 :: _ -> 66 - st italic 70 + Apply (st dim) 71 + | 3 :: _ -> 72 + Apply (st italic) 67 73 | 4 :: _ -> 68 - st underline 74 + Apply (st underline) 69 75 | 5 :: _ -> 70 - st blink 76 + Apply (st blink) 71 77 | 7 :: _ -> 72 - st reverse 78 + Apply (st reverse) 79 + | 8 :: _ -> 80 + Apply (st hidden) 81 + | 9 :: _ -> 82 + Apply (st strike) 83 + | 21 :: _ -> 84 + Reset (st bold) (* Double underline or bold off *) 85 + | 22 :: _ -> 86 + Reset (st bold ++ st dim) (* Normal intensity - reset bold and dim *) 87 + | 23 :: _ -> 88 + Reset (st italic) (* Reset italic *) 89 + | 24 :: _ -> 90 + Reset (st underline ) (* Reset underline *) 91 + | 25 :: _ -> 92 + Reset (st blink) (* Reset blink *) 93 + | 27 :: _ -> 94 + Reset (st reverse) (* Reset reverse *) 95 + | 28 :: _ -> 96 + Reset (st hidden) (* Reset hidden *) 97 + | 29 :: _ -> 98 + Reset (st strike) (* Reset strikethrough *) 73 99 | 30 :: _ -> 74 - fg black 100 + Apply (fg black) 75 101 | 31 :: _ -> 76 - fg red 102 + Apply (fg red) 77 103 | 32 :: _ -> 78 - fg green 104 + Apply (fg green) 79 105 | 33 :: _ -> 80 - fg yellow 106 + Apply (fg yellow) 81 107 | 34 :: _ -> 82 - fg blue 108 + Apply (fg blue) 83 109 | 35 :: _ -> 84 - fg magenta 110 + Apply (fg magenta) 85 111 | 36 :: _ -> 86 - fg cyan 112 + Apply (fg cyan) 87 113 | 37 :: _ -> 88 - fg white 114 + Apply (fg white) 89 115 | 38 :: 5 :: color :: _ -> 90 - fg (unsafe_color_of_int (0x01000000 lor color)) 116 + Apply (fg (unsafe_color_of_int (0x01000000 lor color))) 117 + | 38 :: 2 :: r :: g :: b :: _ -> 118 + Apply (fg (rgb_888 ~r ~g ~b)) 119 + | 39 :: _ -> 120 + Reset (fg no_color) (* Default foreground color *) 91 121 | 40 :: _ -> 92 - bg black 122 + Apply (bg black) 93 123 | 41 :: _ -> 94 - bg red 124 + Apply (bg red) 95 125 | 42 :: _ -> 96 - bg green 126 + Apply (bg green) 97 127 | 43 :: _ -> 98 - bg yellow 128 + Apply (bg yellow) 99 129 | 44 :: _ -> 100 - bg blue 130 + Apply (bg blue) 101 131 | 45 :: _ -> 102 - bg magenta 132 + Apply (bg magenta) 103 133 | 46 :: _ -> 104 - bg cyan 134 + Apply (bg cyan) 105 135 | 47 :: _ -> 106 - bg white 136 + Apply (bg white) 107 137 | 48 :: 5 :: color :: _ -> 108 - bg (unsafe_color_of_int (0x01000000 lor color)) 138 + Apply (bg (unsafe_color_of_int (0x01000000 lor color))) 139 + | 48 :: 2 :: r :: g :: b :: _ -> 140 + Apply (bg (rgb_888 ~r ~g ~b)) 141 + | 49 :: _ -> 142 + Reset (bg no_color) (* Default background color *) 143 + | 90 :: _ -> 144 + Apply (fg lightblack) (* Bright black (gray) *) 145 + | 91 :: _ -> 146 + Apply (fg lightred) 147 + | 92 :: _ -> 148 + Apply (fg lightgreen) 149 + | 93 :: _ -> 150 + Apply (fg lightyellow) 151 + | 94 :: _ -> 152 + Apply (fg lightblue) 153 + | 95 :: _ -> 154 + Apply (fg lightmagenta) 155 + | 96 :: _ -> 156 + Apply (fg lightcyan) 157 + | 97 :: _ -> 158 + Apply (fg lightwhite) 159 + | 100 :: _ -> 160 + Apply (bg lightblack) 161 + | 101 :: _ -> 162 + Apply (bg lightred) 163 + | 102 :: _ -> 164 + Apply (bg lightgreen) 165 + | 103 :: _ -> 166 + Apply (bg lightyellow) 167 + | 104 :: _ -> 168 + Apply (bg lightblue) 169 + | 105 :: _ -> 170 + Apply (bg lightmagenta) 171 + | 106 :: _ -> 172 + Apply (bg lightcyan) 173 + | 107 :: _ -> 174 + Apply (bg lightwhite) 109 175 | _ -> 110 - empty 176 + Apply empty 111 177 in 112 178 escape_sequence >>| attr_of_params 113 179 ;; ··· 117 183 let res = 118 184 Angstrom.parse_string ~consume:All parse_escape_seq test_str |> Result.get_ok 119 185 in 120 - print_attr res; 121 - [%expect {| 186 + (match res with 187 + | Apply attr -> 188 + print_attr attr 189 + | Reset _ -> 190 + print_endline "Reset attribute" 191 + | FullyReset -> 192 + print_endline "Fully reset attribute"); 193 + 194 + [%expect 195 + {| 122 196 attr: 123 - <ATTR> |}] 197 + [0m<[0;32mATTR[0m[K[0m>[0m |}] 124 198 ;; 125 199 126 200 let parse_ansi_escape_codes (input : string) = 201 + let attr_state = ref A.empty in 127 202 let open Angstrom in 128 203 let attr = parse_escape_seq in 129 204 let substring = take_while (fun c -> c <> '\027') in 130 205 let pair = 131 - attr >>= fun a -> 132 - substring >>= fun s -> return (a, s) 206 + attr >>= fun action -> 207 + substring >>= fun s -> 208 + (match action with 209 + | Apply a -> 210 + attr_state := A.( ++ ) !attr_state a 211 + | Reset a -> 212 + attr_state := A.( -- ) !attr_state a 213 + | FullyReset -> 214 + attr_state := A.empty); 215 + return (!attr_state, s) 133 216 in 134 217 (* if we don't start on an escape we can match one here*) 135 218 let prefix = option "" substring >>| fun s -> A.empty, s in ··· 140 223 in 141 224 parse_string ~consume:Prefix pairs input 142 225 ;; 226 + 227 + let%expect_test "parse_ansi_escape_codes_test" = 228 + let test_str = "\027[4m\027[38;5;1m\"success\"\027[38;5;2mNone\027[24m\027[39mrest" in 229 + (match parse_ansi_escape_codes test_str with 230 + | Error err -> 231 + Printf.printf "Error: %s\n" err 232 + | Ok result -> 233 + Printf.printf "Parsed %d segments:\n" (List.length result); 234 + List.iter 235 + (fun (attr, text) -> 236 + print_attr attr; 237 + Printf.printf "Text: \"%s\"\n" (String.escaped text)) 238 + result); 239 + [%expect 240 + {| 241 + Parsed 6 segments: 242 + attr: 243 + \e[0m<\e[0mATTR\e[0m\e[K\e[0m>\e[0m 244 + Text: "" 245 + attr: 246 + \e[0m<\e[0;4mATTR\e[0m\e[K\e[0m>\e[0m 247 + Text: "" 248 + attr: 249 + \e[0m<\e[0;31;4mATTR\e[0m\e[K\e[0m>\e[0m 250 + Text: "\"success\"" 251 + attr: 252 + \e[0m<\e[0;32;4mATTR\e[0m\e[K\e[0m>\e[0m 253 + Text: "None" 254 + attr: 255 + \e[0m<\e[0mATTR\e[0m\e[K\e[0m>\e[0m 256 + Text: "" 257 + attr: 258 + \e[0m<\e[0mATTR\e[0m\e[K\e[0m>\e[0m 259 + Text: "rest" 260 + |}] 261 + ;; 262 + let%expect_test "parse_ansi_strikethrough_test" = 263 + let open A in 264 + print_attr (A.st A.strike); 265 + print_attr (A.st A.strike ++ A.fg A.red); 266 + print_attr (A.st A.blink); 267 + print_attr (A.st A.dim); 268 + print_attr (A.st A.italic); 269 + print_attr (A.st A.underline); 270 + print_attr (A.st A.bold); 271 + print_attr (A.st A.reverse ++ A.fg A.red); 272 + print_attr (A.st A.hidden); 273 + 274 + [%expect 275 + {| 276 + attr: 277 + \e[0m<\e[0;9mATTR\e[0m\e[K\e[0m>\e[0m 278 + attr: 279 + \e[0m<\e[0;31;9mATTR\e[0m\e[K\e[0m>\e[0m 280 + attr: 281 + \e[0m<\e[0;5mATTR\e[0m\e[K\e[0m>\e[0m 282 + attr: 283 + \e[0m<\e[0;2mATTR\e[0m\e[K\e[0m>\e[0m 284 + attr: 285 + \e[0m<\e[0;3mATTR\e[0m\e[K\e[0m>\e[0m 286 + attr: 287 + \e[0m<\e[0;4mATTR\e[0m\e[K\e[0m>\e[0m 288 + attr: 289 + \e[0m<\e[0;1mATTR\e[0m\e[K\e[0m>\e[0m 290 + attr: 291 + \e[0m<\e[0;31;7mATTR\e[0m\e[K\e[0m>\e[0m 292 + attr: 293 + \e[0m<\e[0;8mATTR\e[0m\e[K\e[0m>\e[0m 294 + |}] 295 + ;; 296 + 297 + 298 + let%expect_test "attribute_removal_test" = 299 + let open A in 300 + (* Test removing styles *) 301 + let base_attr = st bold ++ st underline ++ st italic in 302 + let result = base_attr -- st italic in 303 + Internal.print_attr result; 304 + [%expect 305 + {| 306 + attr: 307 + \e[0m<\e[0;1;4mATTR\e[0m\e[K\e[0m>\e[0m 308 + |}]; 309 + (* Test removing multiple styles at once *) 310 + let result2 = base_attr -- (st underline ++ st italic) in 311 + Internal.print_attr result2; 312 + [%expect 313 + {| 314 + attr: 315 + \e[0m<\e[0;1mATTR\e[0m\e[K\e[0m>\e[0m 316 + |}]; 317 + (* Test removing foreground color *) 318 + let colored = base_attr ++ fg red in 319 + let no_color = colored -- fg no_color in 320 + Internal.print_attr no_color; 321 + [%expect 322 + {| 323 + attr: 324 + \e[0m<\e[0;1;3;4mATTR\e[0m\e[K\e[0m>\e[0m 325 + |}]; 326 + (* Test removing background color *) 327 + let bg_colored = base_attr ++ bg blue in 328 + let no_bg = bg_colored -- bg blue in 329 + Internal.print_attr no_bg; 330 + [%expect 331 + {| 332 + attr: 333 + \e[0m<\e[0;1;3;4mATTR\e[0m\e[K\e[0m>\e[0m 334 + |}]; 335 + (* Test resetting to empty *) 336 + let full_attr = st bold ++ fg red ++ bg green in 337 + let empty_result = full_attr -- full_attr in 338 + Internal.print_attr empty_result; 339 + [%expect 340 + {| 341 + attr: 342 + \e[0m<\e[0mATTR\e[0m\e[K\e[0m>\e[0m 343 + |}] 344 + ;; 143 345 end 144 346 145 347 (** Converts a string with ansi escape codes to a notty image. ··· 150 352 let last_char = ref '\000' in 151 353 String.iter 152 354 (fun c -> 153 - match c with 154 - | '\r' -> 155 - last_char := '\r' 156 - | '\n' when !last_char <> '\r' -> 157 - Buffer.add_char buffer '\n' 158 - | '\n' -> 159 - Buffer.add_char buffer '\n'; 160 - last_char := '\000' 161 - | '\t' -> 162 - Buffer.add_string buffer " " 163 - | '\x7F' -> 164 - Buffer.add_string buffer " " 165 - | '\x0C' -> 166 - Buffer.add_string buffer "↡" 167 - | _ -> 168 - Buffer.add_char buffer c) 355 + match c with 356 + | '\r' -> 357 + last_char := '\r' 358 + | '\n' when !last_char <> '\r' -> 359 + Buffer.add_char buffer '\n' 360 + | '\n' -> 361 + Buffer.add_char buffer '\n'; 362 + last_char := '\000' 363 + | '\t' -> 364 + Buffer.add_string buffer " " 365 + | '\x7F' -> 366 + Buffer.add_string buffer " " 367 + | '\x0C' -> 368 + Buffer.add_string buffer "↡" 369 + | _ -> 370 + Buffer.add_char buffer c) 169 371 str; 170 372 Buffer.contents buffer 171 373 in