35
36:- module(ansi_term,
37 [ ansi_format/3, 38 ansi_get_color/2 39 ]). 40:- autoload(library(error),[domain_error/2,must_be/2]). 41:- autoload(library(lists),[append/2,append/3]). 42:- if(exists_source(library(time))). 43:- autoload(library(time),[call_with_time_limit/2]). 44:- endif. 45
46
60
61:- multifile
62 prolog:console_color/2, 63 supports_get_color/0. 64
65
66color_term_flag_default(true) :-
67 stream_property(user_input, tty(true)),
68 stream_property(user_error, tty(true)),
69 stream_property(user_output, tty(true)),
70 \+ getenv('TERM', dumb),
71 !.
72color_term_flag_default(false).
73
74init_color_term_flag :-
75 color_term_flag_default(Default),
76 create_prolog_flag(color_term, Default,
77 [ type(boolean),
78 keep(true)
79 ]).
80
81:- init_color_term_flag. 82
83
84:- meta_predicate
85 keep_line_pos(+, 0). 86
87:- multifile
88 user:message_property/2. 89
129
130ansi_format(Attr, Format, Args) :-
131 ansi_format(current_output, Attr, Format, Args).
132
133ansi_format(Stream, Class, Format, Args) :-
134 stream_property(Stream, tty(true)),
135 current_prolog_flag(color_term, true),
136 !,
137 class_attrs(Class, Attr),
138 phrase(sgr_codes_ex(Attr), Codes),
139 atomic_list_concat(Codes, ;, Code),
140 with_output_to(
141 Stream,
142 ( keep_line_pos(current_output, format('\e[~wm', [Code])),
143 format(Format, Args),
144 keep_line_pos(current_output, format('\e[0m'))
145 )
146 ),
147 flush_output.
148ansi_format(Stream, _Attr, Format, Args) :-
149 format(Stream, Format, Args).
150
151sgr_codes_ex(X) -->
152 { var(X),
153 !,
154 instantiation_error(X)
155 }.
156sgr_codes_ex([]) -->
157 !.
158sgr_codes_ex([H|T]) -->
159 !,
160 sgr_codes_ex(H),
161 sgr_codes_ex(T).
162sgr_codes_ex(Attr) -->
163 ( { sgr_code(Attr, Code) }
164 -> ( { is_list(Code) }
165 -> list(Code)
166 ; [Code]
167 )
168 ; { domain_error(sgr_code, Attr) }
169 ).
170
171list([]) --> [].
172list([H|T]) --> [H], list(T).
173
174
213
214sgr_code(reset, 0).
215sgr_code(bold, 1).
216sgr_code(faint, 2).
217sgr_code(italic, 3).
218sgr_code(underline, 4).
219sgr_code(blink(slow), 5).
220sgr_code(blink(rapid), 6).
221sgr_code(negative, 7).
222sgr_code(conceal, 8).
223sgr_code(crossed_out, 9).
224sgr_code(font(primary), 10) :- !.
225sgr_code(font(N), C) :-
226 C is 10+N.
227sgr_code(fraktur, 20).
228sgr_code(underline(double), 21).
229sgr_code(intensity(normal), 22).
230sgr_code(fg(Name), C) :-
231 ( ansi_color(Name, N)
232 -> C is N+30
233 ; rgb(Name, R, G, B)
234 -> sgr_code(fg(R,G,B), C)
235 ).
236sgr_code(bg(Name), C) :-
237 !,
238 ( ansi_color(Name, N)
239 -> C is N+40
240 ; rgb(Name, R, G, B)
241 -> sgr_code(bg(R,G,B), C)
242 ).
243sgr_code(framed, 51).
244sgr_code(encircled, 52).
245sgr_code(overlined, 53).
246sgr_code(ideogram(underline), 60).
247sgr_code(right_side_line, 60).
248sgr_code(ideogram(underline(double)), 61).
249sgr_code(right_side_line(double), 61).
250sgr_code(ideogram(overlined), 62).
251sgr_code(left_side_line, 62).
252sgr_code(ideogram(stress_marking), 64).
253sgr_code(-X, Code) :-
254 off_code(X, Code).
255sgr_code(hfg(Name), C) :-
256 ansi_color(Name, N),
257 C is N+90.
258sgr_code(hbg(Name), C) :-
259 !,
260 ansi_color(Name, N),
261 C is N+100.
262sgr_code(fg8(Name), [38,5,N]) :-
263 ansi_color8(Name, N).
264sgr_code(bg8(Name), [48,5,N]) :-
265 ansi_color8(Name, N).
266sgr_code(fg(R,G,B), [38,2,R,G,B]) :-
267 between(0, 255, R),
268 between(0, 255, G),
269 between(0, 255, B).
270sgr_code(bg(R,G,B), [48,2,R,G,B]) :-
271 between(0, 255, R),
272 between(0, 255, G),
273 between(0, 255, B).
274
275off_code(italic_and_franktur, 23).
276off_code(underline, 24).
277off_code(blink, 25).
278off_code(negative, 27).
279off_code(conceal, 28).
280off_code(crossed_out, 29).
281off_code(framed, 54).
282off_code(overlined, 55).
283
284ansi_color8(h(Name), N) :-
285 !,
286 ansi_color(Name, N0),
287 N is N0+8.
288ansi_color8(Name, N) :-
289 atom(Name),
290 !,
291 ansi_color(Name, N).
292ansi_color8(N, N) :-
293 between(0, 255, N).
294
295ansi_color(black, 0).
296ansi_color(red, 1).
297ansi_color(green, 2).
298ansi_color(yellow, 3).
299ansi_color(blue, 4).
300ansi_color(magenta, 5).
301ansi_color(cyan, 6).
302ansi_color(white, 7).
303ansi_color(default, 9).
304
305rgb(Name, R, G, B) :-
306 atom_codes(Name, [0'#,R1,R2,G1,G2,B1,B2]),
307 hex_color(R1,R2,R),
308 hex_color(G1,G2,G),
309 hex_color(B1,B2,B).
310rgb(Name, R, G, B) :-
311 atom_codes(Name, [0'#,R1,G1,B1]),
312 hex_color(R1,R),
313 hex_color(G1,G),
314 hex_color(B1,B).
315
316hex_color(D1,D2,V) :-
317 code_type(D1, xdigit(V1)),
318 code_type(D2, xdigit(V2)),
319 V is 16*V1+V2.
320
321hex_color(D1,V) :-
322 code_type(D1, xdigit(V1)),
323 V is 16*V1+V1.
324
334
335
336 339
344
345prolog:message_line_element(S, ansi(Class, Fmt, Args)) :-
346 class_attrs(Class, Attr),
347 ansi_format(S, Attr, Fmt, Args).
348prolog:message_line_element(S, ansi(Class, Fmt, Args, Ctx)) :-
349 class_attrs(Class, Attr),
350 ansi_format(S, Attr, Fmt, Args),
351 ( nonvar(Ctx),
352 Ctx = ansi(_, RI-RA)
353 -> keep_line_pos(S, format(S, RI, RA))
354 ; true
355 ).
356prolog:message_line_element(S, begin(Level, Ctx)) :-
357 level_attrs(Level, Attr),
358 stream_property(S, tty(true)),
359 current_prolog_flag(color_term, true),
360 !,
361 ( is_list(Attr)
362 -> sgr_codes(Attr, Codes),
363 atomic_list_concat(Codes, ;, Code)
364 ; sgr_code(Attr, Code)
365 ),
366 keep_line_pos(S, format(S, '\e[~wm', [Code])),
367 Ctx = ansi('\e[0m', '\e[0m\e[~wm'-[Code]).
368prolog:message_line_element(S, end(Ctx)) :-
369 nonvar(Ctx),
370 Ctx = ansi(Reset, _),
371 keep_line_pos(S, write(S, Reset)).
372
373sgr_codes([], []).
374sgr_codes([H0|T0], [H|T]) :-
375 sgr_code(H0, H),
376 sgr_codes(T0, T).
377
378level_attrs(Level, Attrs) :-
379 user:message_property(Level, color(Attrs)),
380 !.
381level_attrs(Level, Attrs) :-
382 class_attrs(message(Level), Attrs).
383
384class_attrs(Class, Attrs) :-
385 user:message_property(Class, color(Attrs)),
386 !.
387class_attrs(Class, Attrs) :-
388 prolog:console_color(Class, Attrs),
389 !.
390class_attrs(Class, Attrs) :-
391 '$messages':default_theme(Class, Attrs),
392 !.
393class_attrs(Attrs, Attrs).
394
400
401keep_line_pos(S, G) :-
402 stream_property(S, position(Pos)),
403 !,
404 setup_call_cleanup(
405 stream_position_data(line_position, Pos, LPos),
406 G,
407 set_stream(S, line_position(LPos))).
408keep_line_pos(_, G) :-
409 call(G).
410
421
422
423:- if(current_predicate(call_with_time_limit/2)). 424ansi_get_color(Which0, RGB) :-
425 stream_property(user_input, tty(true)),
426 stream_property(user_output, tty(true)),
427 stream_property(user_error, tty(true)),
428 supports_get_color,
429 ( color_alias(Which0, Which)
430 -> true
431 ; must_be(between(0,15),Which0)
432 -> Which = Which0
433 ),
434 catch(keep_line_pos(user_output,
435 ansi_get_color_(Which, RGB)),
436 time_limit_exceeded,
437 no_xterm).
438
439supports_get_color :-
440 getenv('TERM', Term),
441 sub_atom(Term, 0, _, _, xterm),
442 \+ getenv('TERM_PROGRAM', 'Apple_Terminal').
443
444color_alias(foreground, 10).
445color_alias(background, 11).
446
447ansi_get_color_(Which, rgb(R,G,B)) :-
448 format(codes(Id), '~w', [Which]),
449 hex4(RH),
450 hex4(GH),
451 hex4(BH),
452 phrase(("\e]", Id, ";rgb:", RH, "/", GH, "/", BH, "\a"), Pattern),
453 call_with_time_limit(0.05,
454 with_tty_raw(exchange_pattern(Which, Pattern))),
455 !,
456 hex_val(RH, R),
457 hex_val(GH, G),
458 hex_val(BH, B).
459
460no_xterm :-
461 print_message(warning, ansi(no_xterm_get_colour)),
462 fail.
463
464hex4([_,_,_,_]).
465
466hex_val([D1,D2,D3,D4], V) :-
467 code_type(D1, xdigit(V1)),
468 code_type(D2, xdigit(V2)),
469 code_type(D3, xdigit(V3)),
470 code_type(D4, xdigit(V4)),
471 V is (V1<<12)+(V2<<8)+(V3<<4)+V4.
472
473exchange_pattern(Which, Pattern) :-
474 format(user_output, '\e]~w;?\a', [Which]),
475 flush_output(user_output),
476 read_pattern(user_input, Pattern, []).
477
478read_pattern(From, Pattern, NotMatched0) :-
479 copy_term(Pattern, TryPattern),
480 append(Skip, Rest, NotMatched0),
481 append(Rest, RestPattern, TryPattern),
482 !,
483 echo(Skip),
484 try_read_pattern(From, RestPattern, NotMatched, Done),
485 ( Done == true
486 -> Pattern = TryPattern
487 ; read_pattern(From, Pattern, NotMatched)
488 ).
489
491
492try_read_pattern(_, [], [], true) :-
493 !.
494try_read_pattern(From, [H|T], [C|RT], Done) :-
495 get_code(C),
496 ( C = H
497 -> try_read_pattern(From, T, RT, Done)
498 ; RT = [],
499 Done = false
500 ).
501
502echo([]).
503echo([H|T]) :-
504 put_code(user_output, H),
505 echo(T).
506
507:- else. 508ansi_get_color(_Which0, _RGB) :-
509 fail.
510:- endif. 511
512
513
514:- multifile prolog:message//1. 515
516prolog:message(ansi(no_xterm_get_colour)) -->
517 [ 'Terminal claims to be xterm compatible,'-[], nl,
518 'but does not report colour info'-[]
519 ]