···195195 and lightmagenta = 0x0100000d
196196 and lightcyan = 0x0100000e
197197 and lightwhite = 0x0100000f
198198+ and no_color = 0x01000010
198199199200 let tag c = (c land 0x03000000) lsr 24
200201···223224 and underline = 4
224225 and blink = 8
225226 and reverse = 16
227227+ and hidden = 32
228228+ and strike = 64
229229+ and dim = 128
230230+ and no_style = 0
231231+226232227233 let empty = { fg = 0; bg = 0; st = 0 }
228234···231237 { fg = (match a2.fg with 0 -> a1.fg | x -> x)
232238 ; bg = (match a2.bg with 0 -> a1.bg | x -> x)
233239 ; st = a1.st lor a2.st }
240240+241241+ let (--) a1 a2 =
242242+ if a1 == empty then a2 else if a2 == empty then a1 else
243243+ { fg = (match a2.fg with no_color -> 0 | _ -> a1.fg)
244244+ ; bg = (match a2.bg with no_color -> 0 | _ -> a1.bg)
245245+ ; st = a1.st land (lnot a2.st) }
234246235247 let fg fg = { empty with fg }
236248 let bg bg = { empty with bg }
···520532521533 let ((<|), (<.), (<!)) = Buffer.(add_string, add_char, add_decimal)
522534523523- let sts = [ ";1"; ";3"; ";4"; ";5"; ";7" ]
535535+ let sts = [ ";1"; ";3"; ";4"; ";5"; ";7"; ";8"; ";9";";2" ]
524536525537 let sgr { A.fg; bg; st } buf =
526538 buf <| "\x1b[0";
+12
forks/notty/src/notty.mli
···104104 val lightmagenta : color
105105 val lightcyan : color
106106 val lightwhite : color
107107+ val no_color : color
107108108109 (** {2 Extended 256-color palette} *)
109110···145146 val underline : style
146147 val blink : style
147148 val reverse : style
149149+ val hidden : style
150150+ val strike : style
151151+ val dim : style
152152+ val no_style : style
148153149154 (** {1 Attribute construction and composition} *)
150155···162167 is [a1]'s, and the union of both style sets.
163168164169 [++] is left-associative, and forms a monoid with [empty]. *)
170170+171171+ val (--) : attr -> attr -> attr
172172+ (** [a1 -- a2] is the difference of [a1] and [a2], the attribute that has
173173+ [a1]'s foreground (resp. background), unless {e unset}, in which case it
174174+ is [a2]'s, and the difference of both style sets.
175175+176176+ [--] is left-associative, and forms a monoid with [empty]. *)
165177166178 val fg : color -> attr
167179 (** [fg c] is [empty] with foreground [c]. *)
+249-47
jj_tui/lib/ansiReverse.ml
···4646module Parser = struct
4747 open Internal
48484949+ type attr_action =
5050+ | Apply of A.t
5151+ | Reset of A.t
5252+ | FullyReset
4953 let parse_escape_seq =
5054 let open A in
5155 let open Angstrom in
···5761 let escape_sequence = char '\027' *> char '[' *> params <* char 'm' in
5862 let attr_of_params = function
5963 | [] ->
6060- empty
6464+ Apply empty
6165 | 0 :: _ ->
6262- empty
6666+ FullyReset
6367 | 1 :: _ ->
6464- st bold
6868+ Apply (st bold)
6569 | 2 :: _ ->
6666- st italic
7070+ Apply (st dim)
7171+ | 3 :: _ ->
7272+ Apply (st italic)
6773 | 4 :: _ ->
6868- st underline
7474+ Apply (st underline)
6975 | 5 :: _ ->
7070- st blink
7676+ Apply (st blink)
7177 | 7 :: _ ->
7272- st reverse
7878+ Apply (st reverse)
7979+ | 8 :: _ ->
8080+ Apply (st hidden)
8181+ | 9 :: _ ->
8282+ Apply (st strike)
8383+ | 21 :: _ ->
8484+ Reset (st bold) (* Double underline or bold off *)
8585+ | 22 :: _ ->
8686+ Reset (st bold ++ st dim) (* Normal intensity - reset bold and dim *)
8787+ | 23 :: _ ->
8888+ Reset (st italic) (* Reset italic *)
8989+ | 24 :: _ ->
9090+ Reset (st underline ) (* Reset underline *)
9191+ | 25 :: _ ->
9292+ Reset (st blink) (* Reset blink *)
9393+ | 27 :: _ ->
9494+ Reset (st reverse) (* Reset reverse *)
9595+ | 28 :: _ ->
9696+ Reset (st hidden) (* Reset hidden *)
9797+ | 29 :: _ ->
9898+ Reset (st strike) (* Reset strikethrough *)
7399 | 30 :: _ ->
7474- fg black
100100+ Apply (fg black)
75101 | 31 :: _ ->
7676- fg red
102102+ Apply (fg red)
77103 | 32 :: _ ->
7878- fg green
104104+ Apply (fg green)
79105 | 33 :: _ ->
8080- fg yellow
106106+ Apply (fg yellow)
81107 | 34 :: _ ->
8282- fg blue
108108+ Apply (fg blue)
83109 | 35 :: _ ->
8484- fg magenta
110110+ Apply (fg magenta)
85111 | 36 :: _ ->
8686- fg cyan
112112+ Apply (fg cyan)
87113 | 37 :: _ ->
8888- fg white
114114+ Apply (fg white)
89115 | 38 :: 5 :: color :: _ ->
9090- fg (unsafe_color_of_int (0x01000000 lor color))
116116+ Apply (fg (unsafe_color_of_int (0x01000000 lor color)))
117117+ | 38 :: 2 :: r :: g :: b :: _ ->
118118+ Apply (fg (rgb_888 ~r ~g ~b))
119119+ | 39 :: _ ->
120120+ Reset (fg no_color) (* Default foreground color *)
91121 | 40 :: _ ->
9292- bg black
122122+ Apply (bg black)
93123 | 41 :: _ ->
9494- bg red
124124+ Apply (bg red)
95125 | 42 :: _ ->
9696- bg green
126126+ Apply (bg green)
97127 | 43 :: _ ->
9898- bg yellow
128128+ Apply (bg yellow)
99129 | 44 :: _ ->
100100- bg blue
130130+ Apply (bg blue)
101131 | 45 :: _ ->
102102- bg magenta
132132+ Apply (bg magenta)
103133 | 46 :: _ ->
104104- bg cyan
134134+ Apply (bg cyan)
105135 | 47 :: _ ->
106106- bg white
136136+ Apply (bg white)
107137 | 48 :: 5 :: color :: _ ->
108108- bg (unsafe_color_of_int (0x01000000 lor color))
138138+ Apply (bg (unsafe_color_of_int (0x01000000 lor color)))
139139+ | 48 :: 2 :: r :: g :: b :: _ ->
140140+ Apply (bg (rgb_888 ~r ~g ~b))
141141+ | 49 :: _ ->
142142+ Reset (bg no_color) (* Default background color *)
143143+ | 90 :: _ ->
144144+ Apply (fg lightblack) (* Bright black (gray) *)
145145+ | 91 :: _ ->
146146+ Apply (fg lightred)
147147+ | 92 :: _ ->
148148+ Apply (fg lightgreen)
149149+ | 93 :: _ ->
150150+ Apply (fg lightyellow)
151151+ | 94 :: _ ->
152152+ Apply (fg lightblue)
153153+ | 95 :: _ ->
154154+ Apply (fg lightmagenta)
155155+ | 96 :: _ ->
156156+ Apply (fg lightcyan)
157157+ | 97 :: _ ->
158158+ Apply (fg lightwhite)
159159+ | 100 :: _ ->
160160+ Apply (bg lightblack)
161161+ | 101 :: _ ->
162162+ Apply (bg lightred)
163163+ | 102 :: _ ->
164164+ Apply (bg lightgreen)
165165+ | 103 :: _ ->
166166+ Apply (bg lightyellow)
167167+ | 104 :: _ ->
168168+ Apply (bg lightblue)
169169+ | 105 :: _ ->
170170+ Apply (bg lightmagenta)
171171+ | 106 :: _ ->
172172+ Apply (bg lightcyan)
173173+ | 107 :: _ ->
174174+ Apply (bg lightwhite)
109175 | _ ->
110110- empty
176176+ Apply empty
111177 in
112178 escape_sequence >>| attr_of_params
113179 ;;
···117183 let res =
118184 Angstrom.parse_string ~consume:All parse_escape_seq test_str |> Result.get_ok
119185 in
120120- print_attr res;
121121- [%expect {|
186186+ (match res with
187187+ | Apply attr ->
188188+ print_attr attr
189189+ | Reset _ ->
190190+ print_endline "Reset attribute"
191191+ | FullyReset ->
192192+ print_endline "Fully reset attribute");
193193+194194+ [%expect
195195+ {|
122196 attr:
123123- [0m<[0;32mATTR[0m[K[0m>[0m |}]
197197+ [0m<[0;32mATTR[0m[K[0m>[0m |}]
124198 ;;
125199126200 let parse_ansi_escape_codes (input : string) =
201201+ let attr_state = ref A.empty in
127202 let open Angstrom in
128203 let attr = parse_escape_seq in
129204 let substring = take_while (fun c -> c <> '\027') in
130205 let pair =
131131- attr >>= fun a ->
132132- substring >>= fun s -> return (a, s)
206206+ attr >>= fun action ->
207207+ substring >>= fun s ->
208208+ (match action with
209209+ | Apply a ->
210210+ attr_state := A.( ++ ) !attr_state a
211211+ | Reset a ->
212212+ attr_state := A.( -- ) !attr_state a
213213+ | FullyReset ->
214214+ attr_state := A.empty);
215215+ return (!attr_state, s)
133216 in
134217 (* if we don't start on an escape we can match one here*)
135218 let prefix = option "" substring >>| fun s -> A.empty, s in
···140223 in
141224 parse_string ~consume:Prefix pairs input
142225 ;;
226226+227227+ let%expect_test "parse_ansi_escape_codes_test" =
228228+ let test_str = "\027[4m\027[38;5;1m\"success\"\027[38;5;2mNone\027[24m\027[39mrest" in
229229+ (match parse_ansi_escape_codes test_str with
230230+ | Error err ->
231231+ Printf.printf "Error: %s\n" err
232232+ | Ok result ->
233233+ Printf.printf "Parsed %d segments:\n" (List.length result);
234234+ List.iter
235235+ (fun (attr, text) ->
236236+ print_attr attr;
237237+ Printf.printf "Text: \"%s\"\n" (String.escaped text))
238238+ result);
239239+ [%expect
240240+ {|
241241+ Parsed 6 segments:
242242+ attr:
243243+ \e[0m<\e[0mATTR\e[0m\e[K\e[0m>\e[0m
244244+ Text: ""
245245+ attr:
246246+ \e[0m<\e[0;4mATTR\e[0m\e[K\e[0m>\e[0m
247247+ Text: ""
248248+ attr:
249249+ \e[0m<\e[0;31;4mATTR\e[0m\e[K\e[0m>\e[0m
250250+ Text: "\"success\""
251251+ attr:
252252+ \e[0m<\e[0;32;4mATTR\e[0m\e[K\e[0m>\e[0m
253253+ Text: "None"
254254+ attr:
255255+ \e[0m<\e[0mATTR\e[0m\e[K\e[0m>\e[0m
256256+ Text: ""
257257+ attr:
258258+ \e[0m<\e[0mATTR\e[0m\e[K\e[0m>\e[0m
259259+ Text: "rest"
260260+ |}]
261261+ ;;
262262+ let%expect_test "parse_ansi_strikethrough_test" =
263263+ let open A in
264264+ print_attr (A.st A.strike);
265265+ print_attr (A.st A.strike ++ A.fg A.red);
266266+ print_attr (A.st A.blink);
267267+ print_attr (A.st A.dim);
268268+ print_attr (A.st A.italic);
269269+ print_attr (A.st A.underline);
270270+ print_attr (A.st A.bold);
271271+ print_attr (A.st A.reverse ++ A.fg A.red);
272272+ print_attr (A.st A.hidden);
273273+274274+ [%expect
275275+ {|
276276+ attr:
277277+ \e[0m<\e[0;9mATTR\e[0m\e[K\e[0m>\e[0m
278278+ attr:
279279+ \e[0m<\e[0;31;9mATTR\e[0m\e[K\e[0m>\e[0m
280280+ attr:
281281+ \e[0m<\e[0;5mATTR\e[0m\e[K\e[0m>\e[0m
282282+ attr:
283283+ \e[0m<\e[0;2mATTR\e[0m\e[K\e[0m>\e[0m
284284+ attr:
285285+ \e[0m<\e[0;3mATTR\e[0m\e[K\e[0m>\e[0m
286286+ attr:
287287+ \e[0m<\e[0;4mATTR\e[0m\e[K\e[0m>\e[0m
288288+ attr:
289289+ \e[0m<\e[0;1mATTR\e[0m\e[K\e[0m>\e[0m
290290+ attr:
291291+ \e[0m<\e[0;31;7mATTR\e[0m\e[K\e[0m>\e[0m
292292+ attr:
293293+ \e[0m<\e[0;8mATTR\e[0m\e[K\e[0m>\e[0m
294294+ |}]
295295+ ;;
296296+297297+298298+ let%expect_test "attribute_removal_test" =
299299+ let open A in
300300+ (* Test removing styles *)
301301+ let base_attr = st bold ++ st underline ++ st italic in
302302+ let result = base_attr -- st italic in
303303+ Internal.print_attr result;
304304+ [%expect
305305+ {|
306306+ attr:
307307+ \e[0m<\e[0;1;4mATTR\e[0m\e[K\e[0m>\e[0m
308308+ |}];
309309+ (* Test removing multiple styles at once *)
310310+ let result2 = base_attr -- (st underline ++ st italic) in
311311+ Internal.print_attr result2;
312312+ [%expect
313313+ {|
314314+ attr:
315315+ \e[0m<\e[0;1mATTR\e[0m\e[K\e[0m>\e[0m
316316+ |}];
317317+ (* Test removing foreground color *)
318318+ let colored = base_attr ++ fg red in
319319+ let no_color = colored -- fg no_color in
320320+ Internal.print_attr no_color;
321321+ [%expect
322322+ {|
323323+ attr:
324324+ \e[0m<\e[0;1;3;4mATTR\e[0m\e[K\e[0m>\e[0m
325325+ |}];
326326+ (* Test removing background color *)
327327+ let bg_colored = base_attr ++ bg blue in
328328+ let no_bg = bg_colored -- bg blue in
329329+ Internal.print_attr no_bg;
330330+ [%expect
331331+ {|
332332+ attr:
333333+ \e[0m<\e[0;1;3;4mATTR\e[0m\e[K\e[0m>\e[0m
334334+ |}];
335335+ (* Test resetting to empty *)
336336+ let full_attr = st bold ++ fg red ++ bg green in
337337+ let empty_result = full_attr -- full_attr in
338338+ Internal.print_attr empty_result;
339339+ [%expect
340340+ {|
341341+ attr:
342342+ \e[0m<\e[0mATTR\e[0m\e[K\e[0m>\e[0m
343343+ |}]
344344+ ;;
143345end
144346145347(** Converts a string with ansi escape codes to a notty image.
···150352 let last_char = ref '\000' in
151353 String.iter
152354 (fun c ->
153153- match c with
154154- | '\r' ->
155155- last_char := '\r'
156156- | '\n' when !last_char <> '\r' ->
157157- Buffer.add_char buffer '\n'
158158- | '\n' ->
159159- Buffer.add_char buffer '\n';
160160- last_char := '\000'
161161- | '\t' ->
162162- Buffer.add_string buffer " "
163163- | '\x7F' ->
164164- Buffer.add_string buffer " "
165165- | '\x0C' ->
166166- Buffer.add_string buffer "↡"
167167- | _ ->
168168- Buffer.add_char buffer c)
355355+ match c with
356356+ | '\r' ->
357357+ last_char := '\r'
358358+ | '\n' when !last_char <> '\r' ->
359359+ Buffer.add_char buffer '\n'
360360+ | '\n' ->
361361+ Buffer.add_char buffer '\n';
362362+ last_char := '\000'
363363+ | '\t' ->
364364+ Buffer.add_string buffer " "
365365+ | '\x7F' ->
366366+ Buffer.add_string buffer " "
367367+ | '\x0C' ->
368368+ Buffer.add_string buffer "↡"
369369+ | _ ->
370370+ Buffer.add_char buffer c)
169371 str;
170372 Buffer.contents buffer
171373 in