36
37:- module(ansi_term,
38 [ ansi_format/3, 39 ansi_format/4, 40 ansi_get_color/2, 41 ansi_hyperlink/2, 42 ansi_hyperlink/3 43 ]). 44:- autoload(library(error), [domain_error/2, must_be/2, instantiation_error/1]). 45:- autoload(library(lists), [append/3]). 46:- autoload(library(utf8), [utf8_codes/3]). 47
70
71:- multifile
72 prolog:console_color/2, 73 supports_get_color/0,
74 hyperlink/2, 75 tty_url_hook/2. 76
77color_term_flag_default(true) :-
78 stream_property(user_input, tty(true)),
79 stream_property(user_error, tty(true)),
80 stream_property(user_output, tty(true)),
81 \+ getenv('TERM', dumb),
82 !.
83color_term_flag_default(false).
84
85init_color_term_flag :-
86 color_term_flag_default(Default),
87 create_prolog_flag(color_term, Default,
88 [ type(boolean),
89 keep(true)
90 ]),
91 create_prolog_flag(hyperlink_term, false,
92 [ type(boolean),
93 keep(true)
94 ]).
95
96:- initialization
97 init_color_term_flag. 98
99:- meta_predicate
100 keep_line_pos(+, 0). 101
102:- multifile
103 user:message_property/2. 104
145
146ansi_format(Attr, Format, Args) :-
147 ansi_format(current_output, Attr, Format, Args).
148
149ansi_format(Stream, Class, Format, Args) :-
150 stream_property(Stream, tty(true)),
151 current_prolog_flag(color_term, true),
152 class_attrs(Class, Attr),
153 Attr \== [],
154 !,
155 phrase(sgr_codes_ex(Attr), Codes),
156 atomic_list_concat(Codes, ;, Code),
157 with_output_to(
158 Stream,
159 ( keep_line_pos(current_output, format('\e[~wm', [Code])),
160 format(Format, Args),
161 keep_line_pos(current_output, format('\e[0m'))
162 )
163 ),
164 flush_output.
165ansi_format(Stream, _Attr, Format, Args) :-
166 format(Stream, Format, Args).
167
168sgr_codes_ex(X) -->
169 { var(X),
170 !,
171 instantiation_error(X)
172 }.
173sgr_codes_ex([]) -->
174 !.
175sgr_codes_ex([H|T]) -->
176 !,
177 sgr_codes_ex(H),
178 sgr_codes_ex(T).
179sgr_codes_ex(Attr) -->
180 ( { sgr_code(Attr, Code) }
181 -> ( { is_list(Code) }
182 -> list(Code)
183 ; [Code]
184 )
185 ; { domain_error(sgr_code, Attr) }
186 ).
187
188list([]) --> [].
189list([H|T]) --> [H], list(T).
190
191
230
231sgr_code(reset, 0).
232sgr_code(bold, 1).
233sgr_code(faint, 2).
234sgr_code(italic, 3).
235sgr_code(underline, 4).
236sgr_code(blink(slow), 5).
237sgr_code(blink(rapid), 6).
238sgr_code(negative, 7).
239sgr_code(conceal, 8).
240sgr_code(crossed_out, 9).
241sgr_code(font(primary), 10) :- !.
242sgr_code(font(N), C) :-
243 C is 10+N.
244sgr_code(fraktur, 20).
245sgr_code(underline(double), 21).
246sgr_code(intensity(normal), 22).
247sgr_code(fg(Name), C) :-
248 ( ansi_color(Name, N)
249 -> C is N+30
250 ; rgb(Name, R, G, B)
251 -> sgr_code(fg(R,G,B), C)
252 ).
253sgr_code(bg(Name), C) :-
254 !,
255 ( ansi_color(Name, N)
256 -> C is N+40
257 ; rgb(Name, R, G, B)
258 -> sgr_code(bg(R,G,B), C)
259 ).
260sgr_code(framed, 51).
261sgr_code(encircled, 52).
262sgr_code(overlined, 53).
263sgr_code(ideogram(underline), 60).
264sgr_code(right_side_line, 60).
265sgr_code(ideogram(underline(double)), 61).
266sgr_code(right_side_line(double), 61).
267sgr_code(ideogram(overlined), 62).
268sgr_code(left_side_line, 62).
269sgr_code(ideogram(stress_marking), 64).
270sgr_code(-X, Code) :-
271 off_code(X, Code).
272sgr_code(hfg(Name), C) :-
273 ansi_color(Name, N),
274 C is N+90.
275sgr_code(hbg(Name), C) :-
276 !,
277 ansi_color(Name, N),
278 C is N+100.
279sgr_code(fg8(Name), [38,5,N]) :-
280 ansi_color8(Name, N).
281sgr_code(bg8(Name), [48,5,N]) :-
282 ansi_color8(Name, N).
283sgr_code(fg(R,G,B), [38,2,R,G,B]) :-
284 between(0, 255, R),
285 between(0, 255, G),
286 between(0, 255, B).
287sgr_code(bg(R,G,B), [48,2,R,G,B]) :-
288 between(0, 255, R),
289 between(0, 255, G),
290 between(0, 255, B).
291
292off_code(italic_and_franktur, 23).
293off_code(underline, 24).
294off_code(blink, 25).
295off_code(negative, 27).
296off_code(conceal, 28).
297off_code(crossed_out, 29).
298off_code(framed, 54).
299off_code(overlined, 55).
300
301ansi_color8(h(Name), N) :-
302 !,
303 ansi_color(Name, N0),
304 N is N0+8.
305ansi_color8(Name, N) :-
306 atom(Name),
307 !,
308 ansi_color(Name, N).
309ansi_color8(N, N) :-
310 between(0, 255, N).
311
312ansi_color(black, 0).
313ansi_color(red, 1).
314ansi_color(green, 2).
315ansi_color(yellow, 3).
316ansi_color(blue, 4).
317ansi_color(magenta, 5).
318ansi_color(cyan, 6).
319ansi_color(white, 7).
320ansi_color(default, 9).
321
322rgb(Name, R, G, B) :-
323 atom_codes(Name, [0'#,R1,R2,G1,G2,B1,B2]),
324 hex_color(R1,R2,R),
325 hex_color(G1,G2,G),
326 hex_color(B1,B2,B).
327rgb(Name, R, G, B) :-
328 atom_codes(Name, [0'#,R1,G1,B1]),
329 hex_color(R1,R),
330 hex_color(G1,G),
331 hex_color(B1,B).
332
333hex_color(D1,D2,V) :-
334 code_type(D1, xdigit(V1)),
335 code_type(D2, xdigit(V2)),
336 V is 16*V1+V2.
337
338hex_color(D1,V) :-
339 code_type(D1, xdigit(V1)),
340 V is 16*V1+V1.
341
351
352
353 356
361
362prolog:message_line_element(S, ansi(Class, Fmt, Args)) :-
363 class_attrs(Class, Attr),
364 ansi_format(S, Attr, Fmt, Args).
365prolog:message_line_element(S, ansi(Class, Fmt, Args, Ctx)) :-
366 class_attrs(Class, Attr),
367 ansi_format(S, Attr, Fmt, Args),
368 ( nonvar(Ctx),
369 Ctx = ansi(_, RI-RA)
370 -> keep_line_pos(S, format(S, RI, RA))
371 ; true
372 ).
373prolog:message_line_element(S, url(Location)) :-
374 ansi_hyperlink(S, Location).
375prolog:message_line_element(S, url(URL, Label)) :-
376 ansi_hyperlink(S, URL, Label).
377prolog:message_line_element(S, begin(Level, Ctx)) :-
378 level_attrs(Level, Attr),
379 stream_property(S, tty(true)),
380 current_prolog_flag(color_term, true),
381 !,
382 ( is_list(Attr)
383 -> sgr_codes(Attr, Codes),
384 atomic_list_concat(Codes, ;, Code)
385 ; sgr_code(Attr, Code)
386 ),
387 keep_line_pos(S, format(S, '\e[~wm', [Code])),
388 Ctx = ansi('\e[0m', '\e[0m\e[~wm'-[Code]).
389prolog:message_line_element(S, end(Ctx)) :-
390 nonvar(Ctx),
391 Ctx = ansi(Reset, _),
392 keep_line_pos(S, write(S, Reset)).
393
394sgr_codes([], []).
395sgr_codes([H0|T0], [H|T]) :-
396 sgr_code(H0, H),
397 sgr_codes(T0, T).
398
399level_attrs(Level, Attrs) :-
400 user:message_property(Level, color(Attrs)),
401 !.
402level_attrs(Level, Attrs) :-
403 class_attrs(message(Level), Attrs).
404
405class_attrs(Class, Attrs) :-
406 user:message_property(Class, color(Attrs)),
407 !.
408class_attrs(Class, Attrs) :-
409 prolog:console_color(Class, Attrs),
410 !.
411class_attrs(Class, Attrs) :-
412 '$messages':default_theme(Class, Attrs),
413 !.
414class_attrs(Attrs, Attrs).
415
427
428ansi_hyperlink(Stream, Location) :-
429 hyperlink(Stream, url(Location)),
430 !.
431ansi_hyperlink(Stream, Location) :-
432 location_label(Location, Label),
433 ansi_hyperlink(Stream, Location, Label).
434
435location_label(File:Line:Column, Label) =>
436 format(string(Label), '~w:~w:~w', [File,Line,Column]).
437location_label(File:Line, Label) =>
438 format(string(Label), '~w:~w', [File,Line]).
439location_label(File, Label) =>
440 format(string(Label), '~w', [File]).
441
442ansi_hyperlink(Stream, Location, Label),
443 hyperlink(Stream, url(Location, Label)) =>
444 true.
445ansi_hyperlink(Stream, Location, Label) =>
446 ( location_url(Location, URL)
447 -> keep_line_pos(Stream,
448 format(Stream, '\e]8;;~w\e\\', [URL])),
449 format(Stream, '~w', [Label]),
450 keep_line_pos(Stream,
451 format(Stream, '\e]8;;\e\\', []))
452 ; format(Stream, '~w', [Label])
453 ).
454
455is_url(URL) :-
456 ( atom(URL)
457 -> true
458 ; string(URL)
459 ),
460 url_prefix(Prefix),
461 sub_string(URL, 0, _, _, Prefix).
462
463url_prefix('http://').
464url_prefix('https://').
465url_prefix('file://').
466
472
473location_url(Location, URL),
474 tty_url_hook(Location, URL0) =>
475 URL = URL0.
476location_url(File:Line:Column, URL) =>
477 url_file_name(FileURL, File),
478 format(string(URL), '~w#~d:~d', [FileURL, Line, Column]).
479location_url(File:Line, URL) =>
480 url_file_name(FileURL, File),
481 format(string(URL), '~w#~w', [FileURL, Line]).
482location_url(File, URL) =>
483 url_file_name(URL, File).
484
488
489
494
495url_file_name(URL, File) :-
496 is_url(File), !,
497 current_prolog_flag(hyperlink_term, true),
498 URL = File.
499url_file_name(URL, File) :-
500 current_prolog_flag(hyperlink_term, true),
501 absolute_file_name(File, AbsFile),
502 ensure_leading_slash(AbsFile, AbsFile1),
503 url_encode_path(AbsFile1, Encoded),
504 format(string(URL), 'file://~s', [Encoded]).
505
506ensure_leading_slash(Path, SlashPath) :-
507 ( sub_atom(Path, 0, _, _, /)
508 -> SlashPath = Path
509 ; atom_concat(/, Path, SlashPath)
510 ).
511
512url_encode_path(Name, Encoded) :-
513 atom_codes(Name, Codes),
514 phrase(utf8_codes(Codes), UTF8),
515 phrase(encode(UTF8), Encoded).
516
517encode([]) --> [].
518encode([H|T]) --> encode1(H), encode(T).
519
520encode1(C) -->
521 { reserved(C),
522 !,
523 format(codes([C1,C2]), '~`0t~16r~2|', [C])
524 },
525 "%", [C1,C2].
526encode1(C) -->
527 [C].
528
529reserved(C) :- C =< 0'\s.
530reserved(C) :- C >= 127.
531reserved(0'#).
532
538
539keep_line_pos(S, G) :-
540 stream_property(S, position(Pos)),
541 !,
542 setup_call_cleanup(
543 stream_position_data(line_position, Pos, LPos),
544 G,
545 set_stream(S, line_position(LPos))).
546keep_line_pos(_, G) :-
547 call(G).
548
559
560ansi_get_color(Which0, RGB) :-
561 \+ current_prolog_flag(console_menu, true),
562 stream_property(user_input, tty(true)),
563 stream_property(user_output, tty(true)),
564 stream_property(user_error, tty(true)),
565 supports_get_color,
566 ( color_alias(Which0, Which)
567 -> true
568 ; must_be(between(0,15),Which0)
569 -> Which = Which0
570 ),
571 catch(keep_line_pos(user_output,
572 ansi_get_color_(Which, RGB)),
573 error(timeout_error(_,_), _),
574 no_xterm).
575
576supports_get_color :-
577 getenv('TERM', Term),
578 sub_atom(Term, 0, _, _, xterm),
579 \+ getenv('TERM_PROGRAM', 'Apple_Terminal').
580
581color_alias(foreground, 10).
582color_alias(background, 11).
583
584ansi_get_color_(Which, rgb(R,G,B)) :-
585 format(codes(Id), '~w', [Which]),
586 hex4(RH),
587 hex4(GH),
588 hex4(BH),
589 phrase(("\e]", Id, ";rgb:", RH, "/", GH, "/", BH, "\a"), Pattern),
590 stream_property(user_input, timeout(Old)),
591 setup_call_cleanup(
592 set_stream(user_input, timeout(0.05)),
593 with_tty_raw(exchange_pattern(Which, Pattern)),
594 set_stream(user_input, timeout(Old))),
595 !,
596 hex_val(RH, R),
597 hex_val(GH, G),
598 hex_val(BH, B).
599
600no_xterm :-
601 print_message(warning, ansi(no_xterm_get_colour)),
602 fail.
603
604hex4([_,_,_,_]).
605
606hex_val([D1,D2,D3,D4], V) :-
607 code_type(D1, xdigit(V1)),
608 code_type(D2, xdigit(V2)),
609 code_type(D3, xdigit(V3)),
610 code_type(D4, xdigit(V4)),
611 V is (V1<<12)+(V2<<8)+(V3<<4)+V4.
612
613exchange_pattern(Which, Pattern) :-
614 format(user_output, '\e]~w;?\a', [Which]),
615 flush_output(user_output),
616 read_pattern(user_input, Pattern, []).
617
618read_pattern(From, Pattern, NotMatched0) :-
619 copy_term(Pattern, TryPattern),
620 append(Skip, Rest, NotMatched0),
621 append(Rest, RestPattern, TryPattern),
622 !,
623 echo(Skip),
624 try_read_pattern(From, RestPattern, NotMatched, Done),
625 ( Done == true
626 -> Pattern = TryPattern
627 ; read_pattern(From, Pattern, NotMatched)
628 ).
629
631
632try_read_pattern(_, [], [], true) :-
633 !.
634try_read_pattern(From, [H|T], [C|RT], Done) :-
635 get_code(C),
636 ( C = H
637 -> try_read_pattern(From, T, RT, Done)
638 ; RT = [],
639 Done = false
640 ).
641
642echo([]).
643echo([H|T]) :-
644 put_code(user_output, H),
645 echo(T).
646
647:- multifile prolog:message//1. 648
649prolog:message(ansi(no_xterm_get_colour)) -->
650 [ 'Terminal claims to be xterm compatible,'-[], nl,
651 'but does not report colour info'-[]
652 ]