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