1/* Part of SWI-Prolog 2 3 Author: Jan Wielemaker 4 E-mail: jan@swi-prolog.org 5 WWW: https://www.swi-prolog.org 6 Copyright (c) 2010-2026, VU University Amsterdam 7 CWI, Amsterdam 8 SWI-Prolog Solutions b.v. 9 All rights reserved. 10 11 Redistribution and use in source and binary forms, with or without 12 modification, are permitted provided that the following conditions 13 are met: 14 15 1. Redistributions of source code must retain the above copyright 16 notice, this list of conditions and the following disclaimer. 17 18 2. Redistributions in binary form must reproduce the above copyright 19 notice, this list of conditions and the following disclaimer in 20 the documentation and/or other materials provided with the 21 distribution. 22 23 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 29 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 31 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 33 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 POSSIBILITY OF SUCH DAMAGE. 35*/ 36 37:- module(ansi_term, 38 [ ansi_format/3, % +Attr, +Format, +Args 39 ansi_format/4, % +Stream, +Attr, +Format, +Args 40 ansi_sgr/2, % +Attr, -Sequence 41 ansi_get_color/2, % +Which, -rgb(R,G,B) 42 ansi_hyperlink/2, % +Stream,+Location 43 ansi_hyperlink/3 % +Stream,+URL,+Label 44 ]). 45:- autoload(library(error), [domain_error/2, must_be/2, instantiation_error/1]). 46:- autoload(library(lists), [append/3, selectchk/3]). 47:- autoload(library(utf8), [utf8_codes/3]). 48:- autoload(library(apply), [maplist/2]). 49 50/** <module> Print decorated text to ANSI consoles 51 52This library allows for exploiting the color and attribute facilities of 53most modern terminals using ANSI escape sequences. This library provides 54the following: 55 56 - ansi_format/3 allows writing messages to the terminal with ansi 57 attributes. 58 - It defines the hook prolog:message_line_element/2, which provides 59 ansi attributes and hyperlinks for print_message/2. 60 61The behavior of this library is controlled by two Prolog flags: 62 63 - `color_term` 64 When `true`, activate the color output for this library. Otherwise 65 simply call format/3. 66 - `hyperlink_term` 67 Emit terminal hyperlinks for url(Location) and url(URL, Label) 68 elements of Prolog messages. The default is `true` if the 69 environment identifies a terminal known to support OSC 8 70 hyperlinks. 71 72@see http://en.wikipedia.org/wiki/ANSI_escape_code 73*/ 74 75:- multifile 76 prolog:console_color/2, % +Term, -AnsiAttrs 77 prolog:console_color/3, % +Theme, +Term, -AnsiAttrs 78 supports_get_color/0, 79 hyperlink/2, % +Stream, +Spec 80 tty_url_hook/2. % +For, -URL 81 82color_term_flag_default(true) :- 83 stream_property(user_input, tty(true)), 84 stream_property(user_error, tty(true)), 85 stream_property(user_output, tty(true)), 86 \+ getenv('TERM', dumb), 87 !. 88color_term_flag_default(false). 89 90%! hyperlink_term_flag_default(-Bool) is det. 91% 92% True when Bool tells whether the terminal is known to support 93% OSC 8 hyperlinks. There is no escape sequence to ask whether a 94% terminal supports them, so we recognise the terminal from the 95% environment variables it sets. A multiplexer (tmux, screen) hides 96% the terminal it runs in and only passes hyperlinks on if configured 97% to do so, so we do not enable them inside one. 98 99hyperlink_term_flag_default(true) :- 100 color_term_flag_default(true), 101 \+ getenv('TMUX', _), 102 \+ getenv('STY', _), 103 osc8_terminal, 104 !. 105hyperlink_term_flag_default(false). 106 107osc8_terminal :- 108 getenv('TERM_PROGRAM', Program), 109 osc8_term_program(Program), 110 !. 111osc8_terminal :- 112 getenv('TERM', Term), 113 osc8_term(Term), 114 !. 115osc8_terminal :- 116 getenv('VTE_VERSION', Atom), % GNOME Terminal, Tilix, ... 117 atom_number(Atom, Version), 118 Version >= 5000, 119 !. 120osc8_terminal :- 121 getenv('KITTY_WINDOW_ID', _), 122 !. 123osc8_terminal :- 124 getenv('WT_SESSION', _). % Windows Terminal 125 126osc8_term_program('Epilog'). 127osc8_term_program('iTerm.app'). 128osc8_term_program('WezTerm'). 129osc8_term_program(ghostty). 130osc8_term_program(vscode). 131 132osc8_term('xterm-kitty'). 133osc8_term('xterm-ghostty'). 134osc8_term(foot). 135osc8_term('foot-extra'). 136osc8_term(wezterm). 137osc8_term(alacritty). 138 139init_color_term_flag :- 140 color_term_flag_default(Color), 141 create_prolog_flag(color_term, Color, 142 [ type(boolean), 143 keep(true) 144 ]), 145 hyperlink_term_flag_default(Hyperlink), 146 create_prolog_flag(hyperlink_term, Hyperlink, 147 [ type(boolean), 148 keep(true) 149 ]). 150 151:- initialization 152 init_color_term_flag. 153 154:- multifile 155 user:message_property/2. 156 157%! ansi_format(+ClassOrAttributes, +Format, +Args) is det. 158%! ansi_format(+Stream, +ClassOrAttributes, +Format, +Args) is det. 159% 160% Format text with ANSI attributes. This predicate behaves as 161% format/2 using Format and Args, but if the `current_output` is a 162% terminal, it adds ANSI escape sequences according to Attributes. 163% For example, to print a text in bold cyan, do 164% 165% ``` 166% ?- ansi_format([bold,fg(cyan)], 'Hello ~w', [world]). 167% ``` 168% 169% Attributes is either a single attribute, a list thereof or a term 170% that is mapped to concrete attributes based on the current theme 171% (see prolog:console_color/3). The attribute names are derived from 172% the ANSI specification. See the source for sgr_code/2 for details. 173% Some commonly used attributes are: 174% 175% - bold 176% - underline 177% - fg(Color), bg(Color), hfg(Color), hbg(Color) 178% For fg(Color) and bg(Color), the colour name can be `'#RGB'` or 179% `'#RRGGBB'` 180% - fg8(Spec), bg8(Spec) 181% 8-bit color specification. Spec is a colour name, h(Color) 182% or an integer 0..255. 183% - fg(R,G,B), bg(R,G,B) 184% 24-bit (direct color) specification. The components are 185% integers in the range 0..255. 186% - href(URL) 187% Wrap the output as a link using ansi_hyperlink/3. 188% 189% Defined color constants are below. `default` can be used to access 190% the default color of the terminal. 191% 192% - black, red, green, yellow, blue, magenta, cyan, white 193% 194% ANSI sequences are sent if and only if 195% 196% - The `current_output` has the property tty(true) (see 197% stream_property/2). 198% - The Prolog flag `color_term` is `true`. 199 200ansi_format(Attr, Format, Args) :- 201 ansi_format(current_output, Attr, Format, Args). 202 203ansi_format(Stream, Class, Format, Args) :- 204 stream_property(Stream, tty(true)), 205 current_prolog_flag(color_term, true), 206 class_attrs(Class, Attr), 207 Attr \== [], 208 !, 209 ( selectchk(href(HREF), Attr, Attr1) 210 -> true 211 ; Attr1 = Attr 212 ), 213 sgr_sequence(Attr1, Sequence), 214 with_output_to( 215 Stream, 216 ( write(Sequence), 217 format_content(Format, Args, HREF), 218 format('\e[0m') 219 ) 220 ), 221 flush_output. 222ansi_format(Stream, _Attr, Format, Args) :- 223 format(Stream, Format, Args). 224 225format_content(Format, Args, HREF) :- 226 var(HREF), 227 !, 228 format(Format, Args). 229format_content(Format, Args, HREF) :- 230 format(string(Label), Format, Args), 231 ansi_hyperlink(current_output, HREF, Label). 232 233%! ansi_sgr(+ClassOrAttributes, -Sequence:string) is det. 234% 235% True when Sequence is the ANSI _Select Graphic Rendition_ sequence 236% that activates the attributes of ClassOrAttributes. Sequence is the 237% empty string if the Prolog flag `color_term` is `false` or the class 238% resolves to no attributes. 239% 240% Unlike ansi_format/4 this does not write to a stream and thus does 241% not require a terminal. It is used to decorate strings that are 242% handed to code that is not aware of colors, notably the toplevel 243% prompt (see the Prolog flag `toplevel_prompt`). As the caller 244% controls where the sequence ends up, the caller is also responsible 245% for verifying that the destination is a terminal and for emitting 246% the reset sequence `\e[0m`. 247% 248% @see ansi_format/3 for the possible values of ClassOrAttributes. 249 250ansi_sgr(Class, Sequence) :- 251 current_prolog_flag(color_term, true), 252 class_attrs(Class, Attr), 253 Attr \== [], 254 ( selectchk(href(_), Attr, Attr1) 255 -> true 256 ; Attr1 = Attr 257 ), 258 !, 259 sgr_sequence(Attr1, Sequence). 260ansi_sgr(_, ""). 261 262%! sgr_sequence(+ClassOrAttributes, -Sequence:string) is det. 263% 264% Sequence is the SGR escape sequence for Attributes. Note that a 265% single attribute may map to multiple codes, e.g., bg8(Color). 266 267sgr_sequence(Attrs, Sequence) :- 268 phrase(sgr_codes_ex(Attrs), Codes), 269 codes_sequence(Codes, Sequence). 270 271codes_sequence(Codes, Sequence) :- 272 atomics_to_string(Codes, ;, Code), 273 format(string(Sequence), '\e[~wm', [Code]). 274 275%! sgr_codes(+ClassOrAttributes)// is semidet. 276% 277% As sgr_codes_ex//1, but fails rather than raising an exception if 278% Attributes is not a valid attribute (list). This is used where the 279% decoration is optional and the plain text is a fine alternative. 280 281sgr_codes(X) --> 282 { var(X), 283 !, 284 fail 285 }. 286sgr_codes([]) --> 287 !. 288sgr_codes([H|T]) --> 289 !, 290 sgr_codes(H), 291 sgr_codes(T). 292sgr_codes(Attr) --> 293 { sgr_code(Attr, Code) }, 294 ( { is_list(Code) } 295 -> list(Code) 296 ; [Code] 297 ). 298 299sgr_codes_ex(X) --> 300 { var(X), 301 !, 302 instantiation_error(X) 303 }. 304sgr_codes_ex([]) --> 305 !. 306sgr_codes_ex([H|T]) --> 307 !, 308 sgr_codes_ex(H), 309 sgr_codes_ex(T). 310sgr_codes_ex(Attr) --> 311 ( { sgr_code(Attr, Code) } 312 -> ( { is_list(Code) } 313 -> list(Code) 314 ; [Code] 315 ) 316 ; { domain_error(sgr_code, Attr) } 317 ). 318 319list([]) --> []. 320list([H|T]) --> [H], list(T). 321 322 323%! sgr_code(+Name, -Code) 324% 325% True when code is the Select Graphic Rendition code for Name. 326% The defined names are given below. Note that most terminals only 327% implement this partially. 328% 329% | reset | all attributes off | 330% | bold | | 331% | faint | | 332% | italic | | 333% | underline | | 334% | blink(slow) | | 335% | blink(rapid) | | 336% | negative | | 337% | conceal | | 338% | crossed_out | | 339% | font(primary) | | 340% | font(N) | Alternate font (1..8) | 341% | fraktur | | 342% | underline(double) | | 343% | intensity(normal) | | 344% | fg(Name) | Color name | 345% | bg(Name) | Color name | 346% | framed | | 347% | encircled | | 348% | overlined | | 349% | ideogram(underline) | | 350% | right_side_line | | 351% | ideogram(underline(double)) | | 352% | right_side_line(double) | | 353% | ideogram(overlined) | | 354% | left_side_line | | 355% | ideogram(stress_marking) | | 356% | -Off | Switch attributes off | 357% | hfg(Name) | Color name | 358% | hbg(Name) | Color name | 359% 360% @see http://en.wikipedia.org/wiki/ANSI_escape_code 361 362sgr_code(reset, 0). 363sgr_code(bold, 1). 364sgr_code(faint, 2). 365sgr_code(italic, 3). 366sgr_code(underline, 4). 367sgr_code(blink(slow), 5). 368sgr_code(blink(rapid), 6). 369sgr_code(negative, 7). 370sgr_code(conceal, 8). 371sgr_code(crossed_out, 9). 372sgr_code(font(primary), 10) :- !. 373sgr_code(font(N), C) :- 374 C is 10+N. 375sgr_code(fraktur, 20). 376sgr_code(underline(double), 21). 377sgr_code(intensity(normal), 22). 378sgr_code(fg(Name), C) :- 379 ( ansi_color(Name, N) 380 -> C is N+30 381 ; rgb(Name, R, G, B) 382 -> sgr_code(fg(R,G,B), C) 383 ). 384sgr_code(bg(Name), C) :- 385 !, 386 ( ansi_color(Name, N) 387 -> C is N+40 388 ; rgb(Name, R, G, B) 389 -> sgr_code(bg(R,G,B), C) 390 ). 391sgr_code(framed, 51). 392sgr_code(encircled, 52). 393sgr_code(overlined, 53). 394sgr_code(ideogram(underline), 60). 395sgr_code(right_side_line, 60). 396sgr_code(ideogram(underline(double)), 61). 397sgr_code(right_side_line(double), 61). 398sgr_code(ideogram(overlined), 62). 399sgr_code(left_side_line, 62). 400sgr_code(ideogram(stress_marking), 64). 401sgr_code(-X, Code) :- 402 off_code(X, Code). 403sgr_code(hfg(Name), C) :- 404 ansi_color(Name, N), 405 C is N+90. 406sgr_code(hbg(Name), C) :- 407 !, 408 ansi_color(Name, N), 409 C is N+100. 410sgr_code(fg8(Name), [38,5,N]) :- 411 ansi_color8(Name, N). 412sgr_code(bg8(Name), [48,5,N]) :- 413 ansi_color8(Name, N). 414sgr_code(fg(R,G,B), [38,2,R,G,B]) :- 415 between(0, 255, R), 416 between(0, 255, G), 417 between(0, 255, B). 418sgr_code(bg(R,G,B), [48,2,R,G,B]) :- 419 between(0, 255, R), 420 between(0, 255, G), 421 between(0, 255, B). 422 423off_code(italic_and_franktur, 23). 424off_code(underline, 24). 425off_code(blink, 25). 426off_code(negative, 27). 427off_code(conceal, 28). 428off_code(crossed_out, 29). 429off_code(framed, 54). 430off_code(overlined, 55). 431 432ansi_color8(h(Name), N) :- 433 !, 434 ansi_color(Name, N0), 435 N is N0+8. 436ansi_color8(Name, N) :- 437 atom(Name), 438 !, 439 ansi_color(Name, N). 440ansi_color8(N, N) :- 441 between(0, 255, N). 442 443ansi_color(black, 0). 444ansi_color(red, 1). 445ansi_color(green, 2). 446ansi_color(yellow, 3). 447ansi_color(blue, 4). 448ansi_color(magenta, 5). 449ansi_color(cyan, 6). 450ansi_color(white, 7). 451ansi_color(default, 9). 452 453rgb(Name, R, G, B) :- 454 atom_codes(Name, [0'#,R1,R2,G1,G2,B1,B2]), 455 hex_color(R1,R2,R), 456 hex_color(G1,G2,G), 457 hex_color(B1,B2,B). 458rgb(Name, R, G, B) :- 459 atom_codes(Name, [0'#,R1,G1,B1]), 460 hex_color(R1,R), 461 hex_color(G1,G), 462 hex_color(B1,B). 463 464hex_color(D1,D2,V) :- 465 code_type(D1, xdigit(V1)), 466 code_type(D2, xdigit(V2)), 467 V is 16*V1+V2. 468 469hex_color(D1,V) :- 470 code_type(D1, xdigit(V1)), 471 V is 16*V1+V1. 472 473%! prolog:console_color(+Theme, +Term, -AnsiAttributes) is semidet. 474% 475% Hook that allows for mapping abstract terms to concrete ANSI 476% attributes. This hook is used by _theme_ files to adjust the 477% rendering based on user preferences and context. Theme is the 478% current theme: the value of the Prolog flag `theme` or, if no 479% theme is selected, the theme of a loaded theme file. Defaults 480% are defined in the file `boot/messages.pl`, default_theme/2. 481% 482% Besides the classes used for messages (`code`, `comment`, `var`, 483% `warning`, `error`, `truth(Truth)`, `port(Port)`, `message(Kind)`, 484% ...), the interactive toplevel uses these: 485% 486% - prompt 487% The `?- ` prompt and its `| ` continuation. 488% - input 489% The text typed by the user at the prompt. 490% - answer(Parity) 491% An answer written by the toplevel. Parity is `odd` or `even` 492% and alternates over the answers of a single query, which allows 493% for _striping_ the answers using a background color. Only an 494% answer that shows bindings, residual goals or delays uses this 495% class: ``true.`` and ``false.`` are not answers to stripe, and 496% neither is the empty line that separates the answer from the 497% next query. 498% - binding(name) 499% The variable name in a binding such as ``X = 1``. 500% 501% The debugger uses, besides `frame(level)` and `port(Port)`: 502% 503% - goal(Port, Parity) 504% The goal of a frame reported for Port. Parity is `odd` or `even` 505% and alternates over the steps of a trace, which allows for 506% _striping_ the goals using a background color. Match on Port to 507% color the goal by port instead of (or in addition to) striping. 508% 509% Note that a background color on `prompt`, `input` or `answer(_)` is 510% painted up to the right margin using `\e[K`. A background on 511% `goal(_,_)` is not: the debugger writes its ``? `` prompt on the same 512% line. 513% 514% If Theme does not define Term, the theme it is based on is used (see 515% prolog:theme_base/2). 516% 517% @see library(theme/dark) for an example implementation and the Term 518% values used by the system messages. 519 520%! prolog:console_color(+Term, -AnsiAttributes) is semidet. 521% 522% As prolog:console_color/3, but for any theme. This allows the user 523% to define colours in the init file that apply to all themes. 524 525%! prolog:theme_base(?Theme, ?Base) is nondet. 526% 527% Hook that makes Theme a variation of the theme Base. If Theme does 528% not define the colours of a class (prolog:console_color/3), those 529% of Base are used, and so on. The same applies to the styles of the 530% online help and the theme colours of the development tools. The 531% theme file of Theme must load the theme file of Base, e.g. 532% 533% ``` 534% :- module(prolog_theme_my_dark, []). 535% :- use_module(library(theme/dark)). 536% :- multifile prolog:theme/1, prolog:theme_base/2. 537% 538% prolog:theme(my_dark). 539% prolog:theme_base(my_dark, dark). 540% ``` 541 542 543 /******************************* 544 * HOOK * 545 *******************************/ 546 547%! prolog:message_line_element(+Stream, +Term) is semidet. 548% 549% Hook implementation that colours the message elements produced by 550% print_message_lines/3. Handled elements are: 551% 552% - ansi(Class, Fmt, Args) 553% - ansi(Class, Fmt, Args, Ctx) 554% Write Fmt/Args using the attributes of Class. As the element 555% ends with a full reset, the 4th argument version re-installs 556% the decoration of the message as a whole afterwards. 557% - url(Location) 558% - url(URL, Label) 559% Write a hyperlink. See ansi_hyperlink/2,3. Label is an atom or 560% string, a Format-Args pair or an ansi/3 or ansi/4 term. The 561% latter combines a hyperlink with a style class. 562% - begin(Class, Ctx) 563% - end(Ctx) 564% Decorate the message as a whole. See below. 565% - nl(Ctx), flush(Ctx) 566% End a line. If the message has a background colour, the 567% remainder of the line is painted using `\e[K` (_Erase in Line_) 568% such that the coloured block extends to the right margin. 569% - eol(Ctx) 570% As above, but also reset the attributes: this ends the decorated 571% part of the line. A message uses this for its last line if that 572% line is not ended using `nl`. Resetting matters because a 573% terminal that scrolls while a background colour is in effect 574% paints the newly exposed line with it. 575% 576% Ctx is the message _context_. It is created by the handler for 577% begin/2 as a term 578% 579% ansi(Reset, ReInstall, EraseEol) 580% 581% where Reset is the sequence written by end/1, ReInstall is a 582% Format-Args pair that re-installs the attributes of the message and 583% EraseEol is the sequence that paints the remainder of the line or 584% the empty atom. Callers must treat Ctx as opaque. It is left 585% unbound if Stream is not a terminal, if the `color_term` flag is 586% `false` or if the message has no attributes. All handlers that use 587% Ctx therefore fail if it is unbound, which makes 588% print_message_lines/3 fall back to writing plain text. 589 590prolog:message_line_element(S, ansi(Class, Fmt, Args)) :- 591 class_attrs(Class, Attr), 592 ansi_format(S, Attr, Fmt, Args). 593prolog:message_line_element(S, ansi(Class, Fmt, Args, Ctx)) :- 594 class_attrs(Class, Attr), 595 ansi_format(S, Attr, Fmt, Args), 596 reinstall_message_attrs(S, Ctx). 597prolog:message_line_element(S, nl(Ctx)) :- 598 nonvar(Ctx), 599 Ctx = ansi(_, _, EOL), 600 write(S, EOL), 601 nl(S). 602prolog:message_line_element(S, flush(Ctx)) :- 603 nonvar(Ctx), 604 Ctx = ansi(_, _, EOL), 605 write(S, EOL), 606 flush_output(S). 607prolog:message_line_element(S, eol(Ctx)) :- 608 nonvar(Ctx), 609 Ctx = ansi(Reset, _, EOL), 610 write(S, EOL), 611 write(S, Reset). 612prolog:message_line_element(S, url(Location)) :- 613 ansi_hyperlink(S, Location). 614prolog:message_line_element(S, url(URL, Label)) :- 615 link_label(Label, Class, Fmt, Args, Ctx), 616 !, 617 format(string(Text), Fmt, Args), 618 ( ansi_sgr_for(S, Class, Sequence) 619 -> write(S, Sequence), 620 ansi_hyperlink(S, URL, Text), 621 write(S, '\e[0m'), 622 reinstall_message_attrs(S, Ctx) 623 ; ansi_hyperlink(S, URL, Text) 624 ). 625prolog:message_line_element(S, url(URL, Label)) :- 626 ansi_hyperlink(S, URL, Label). 627prolog:message_line_element(S, begin(Level, Ctx)) :- 628 level_attrs(Level, Attr), 629 Attr \== [], 630 stream_property(S, tty(true)), 631 current_prolog_flag(color_term, true), 632 phrase(sgr_codes(Attr), Codes), % fails on a kind without a theme 633 !, 634 codes_sequence(Codes, Sequence), 635 write(S, Sequence), 636 erase_eol(Attr, EOL), 637 Ctx = ansi('\e[0m', '\e[0m~w'-[Sequence], EOL). 638prolog:message_line_element(S, end(Ctx)) :- 639 nonvar(Ctx), 640 Ctx = ansi(Reset, _, _), 641 write(S, Reset). 642 643%! reinstall_message_attrs(+Stream, +Ctx) is det. 644% 645% Re-install the attributes of the message as a whole after an element 646% that ended with a full reset. See prolog:message_line_element/2. 647 648reinstall_message_attrs(S, Ctx) :- 649 ( nonvar(Ctx), 650 Ctx = ansi(_, RI-RA, _) 651 -> format(S, RI, RA) 652 ; true 653 ). 654 655%! link_label(+Label, -Class, -Format, -Args, -Ctx) is semidet. 656% 657% Decompose a _decorated_ label of an url/2 message element. Fails if 658% Label is plain text, which is written using ansi_hyperlink/3. 659 660link_label(Fmt-Args, -, Fmt, Args, _) :- 661 atom(Fmt), 662 is_list(Args), 663 !. 664link_label(ansi(Class, Fmt, Args), Class, Fmt, Args, _). 665link_label(ansi(Class, Fmt, Args, Ctx), Class, Fmt, Args, Ctx). 666 667%! ansi_sgr_for(+Stream, +Class, -Sequence) is semidet. 668% 669% Sequence activates the attributes of Class on Stream. Fails if the 670% Stream is not a terminal, if colour output is disabled or if Class 671% has no attributes. 672 673ansi_sgr_for(S, Class, Sequence) :- 674 Class \== (-), 675 stream_property(S, tty(true)), 676 ansi_sgr(Class, Sequence), 677 Sequence \== "". 678 679%! erase_eol(+Attrs, -EOL) is det. 680% 681% If Attrs sets a background color we must paint the remainder of the 682% line to make the colored block extend to the right margin. `\e[K` 683% (_Erase in Line_) does so on terminals that implement _background 684% color erase_ and does not move the cursor. 685 686erase_eol(Attrs, EOL) :- 687 ( is_list(Attrs), 688 has_background(Attrs) 689 -> EOL = '\e[K' 690 ; EOL = '' 691 ). 692 693has_background([H|T]) :- 694 ( background(H) 695 -> true 696 ; has_background(T) 697 ). 698 699background(bg(_)). 700background(bg(_,_,_)). 701background(bg8(_)). 702background(hbg(_)). 703 704level_attrs(Level, Attrs) :- 705 user:message_property(Level, color(Attrs)), 706 !. 707level_attrs(Level, Attrs) :- 708 class_attrs(message(Level), Attrs). 709 710class_attrs(Class, Attrs) :- 711 user:message_property(Class, color(Attrs)), 712 !. 713class_attrs(Class, Attrs) :- 714 prolog:console_color(Class, Attrs), 715 !. 716class_attrs(Class, Attrs) :- 717 '$messages':current_theme(Theme), 718 '$messages':theme_ancestor(Theme, Ancestor), 719 prolog:console_color(Ancestor, Class, Attrs), 720 !. 721class_attrs(Class, Attrs) :- 722 '$messages':default_theme(Class, Attrs), 723 !. 724class_attrs(Attrs, Attrs). 725 726%! ansi_hyperlink(+Stream, +Location) is det. 727%! ansi_hyperlink(+Stream, +Location, +Label) is det. 728% 729% Create a hyperlink for a terminal emulator using the ``OSC 8`` 730% escape sequence. Location is one of 731% 732% - An absolute URL 733% - An atom (interpreted as a file name) 734% - A term `File:Line` 735% - A term `File:Line:Column` 736% 737% There is no official standard for encoding the `Line` and `Column`. 738% We emit 739% 740% ``file://AbsFileName[#Line[:Column]]`` 741% 742% Both `Line` and `Column` count from 1, as in the messages we 743% print and as used by e.g., ``rg --hyperlink-format=...``. Note that 744% a capital ``L`` before the line, as used by GitHub, is accepted by 745% Epilog as well. 746% 747% The sequence is emitted if and only if the Prolog flag 748% `hyperlink_term` is `true` and Stream has the property tty(true). 749% ansi_format/4 is guarded the same way for `color_term`, so that a 750% message captured using with_output_to/2 is plain text. 751% 752% @see https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda 753 754ansi_hyperlink(Stream, Location) :- 755 hyperlink(Stream, url(Location)), 756 !. 757ansi_hyperlink(Stream, Location) :- 758 location_label(Location, Label), 759 ansi_hyperlink(Stream, Location, Label). 760 761location_label(File:Line:Column, Label) => 762 format(string(Label), '~w:~w:~w', [File,Line,Column]). 763location_label(File:Line, Label) => 764 format(string(Label), '~w:~w', [File,Line]). 765location_label(File, Label) => 766 format(string(Label), '~w', [File]). 767 768ansi_hyperlink(Stream, Location, Label), 769 hyperlink(Stream, url(Location, Label)) => 770 true. 771ansi_hyperlink(Stream, Location, Label) => 772 ( hyperlink_stream(Stream), 773 location_url(Location, URL) 774 -> format(Stream, '\e]8;;~w\e\\', [URL]), 775 format(Stream, '~w', [Label]), 776 format(Stream, '\e]8;;\e\\', []) 777 ; format(Stream, '~w', [Label]) 778 ). 779 780%! hyperlink_stream(+Stream) is semidet. 781% 782% True when Stream may carry OSC 8 hyperlinks. Compare ansi_sgr_for/3, 783% which decides the same question for SGR sequences. 784 785hyperlink_stream(Stream) :- 786 current_prolog_flag(hyperlink_term, true), 787 stream_property(Stream, tty(true)). 788 789%! is_url(@URL) is semidet. 790% 791% True if URL is an absolute URL. This means it has a scheme and is 792% not a (Windows) absolute file name as in `c:...` 793 794is_url(URL) :- 795 ( atom(URL) 796 -> true 797 ; string(URL) 798 ), 799 sub_string(URL, Before, _, _, :), 800 !, 801 Before > 0, 802 sub_string(URL, 0, Before, _, Scheme), 803 atom_codes(Scheme, Codes), 804 maplist(between(0'a, 0'z), Codes), 805 not_drive_scheme(Scheme). 806 807:- if(current_prolog_flag(windows, true)). 808not_drive_scheme(Scheme) :- 809 \+ string_length(Scheme, 1). 810:- else. 811not_drive_scheme(_). 812:- endif. 813 814%! location_url(+Location, -URL) is det. 815% 816% Translate Location into a (file) URL. This predicate is hooked by 817% tty_url_hook/2 with the same signature to allow for actions, 818% location specifiers or URL schemes. 819 820location_url(Location, URL), 821 tty_url_hook(Location, URL0) => 822 URL = URL0. 823location_url(File:Line:Column, URL) => 824 url_file_name(FileURL, File), 825 format(string(URL), '~w#~d:~d', [FileURL, Line, Column]). 826location_url(File:Line, URL) => 827 url_file_name(FileURL, File), 828 format(string(URL), '~w#~w', [FileURL, Line]). 829location_url(File, URL) => 830 url_file_name(URL, File). 831 832%! tty_url_hook(+Location, -URL) 833% 834% Hook for location_url/2. 835 836 837%! url_file_name(-URL, +File) is semidet. 838% 839% Same as uri_file_name/2 in mode (-,+), but as a core library we do 840% not wish to depend on the `clib` package and its foreign support. 841 842url_file_name(URL, File) :- 843 is_url(File), !, 844 URL = File. 845url_file_name(URL, File) :- 846 absolute_file_name(File, AbsFile), 847 ensure_leading_slash(AbsFile, AbsFile1), 848 url_encode_path(AbsFile1, Encoded), 849 format(string(URL), 'file://~s', [Encoded]). 850 851ensure_leading_slash(Path, SlashPath) :- 852 ( sub_atom(Path, 0, _, _, /) 853 -> SlashPath = Path 854 ; atom_concat(/, Path, SlashPath) 855 ). 856 857url_encode_path(Name, Encoded) :- 858 atom_codes(Name, Codes), 859 phrase(utf8_codes(Codes), UTF8), 860 phrase(encode(UTF8), Encoded). 861 862encode([]) --> []. 863encode([H|T]) --> encode1(H), encode(T). 864 865encode1(C) --> 866 { reserved(C), 867 !, 868 format(codes([C1,C2]), '~`0t~16r~2|', [C]) 869 }, 870 "%", [C1,C2]. 871encode1(C) --> 872 [C]. 873 874reserved(C) :- C =< 0'\s. 875reserved(C) :- C >= 127. 876reserved(0'#). 877 878%! ansi_get_color(+Which, -RGB) is semidet. 879% 880% Obtain the RGB color for an ANSI color parameter. Which is either a 881% color alias or an integer ANSI color id. Defined aliases are 882% `foreground` and `background`. This predicate sends a request to the 883% console (`user_output`) and reads the reply. This assumes an `xterm` 884% compatible terminal. 885% 886% @arg RGB is a term rgb(Red,Green,Blue). The color components are 887% floats in the range 0.0..1.0. They are sRGB encoded, as that is 888% what the terminal reports; linearise them before doing colour 889% arithmetic that assumes light intensities. 890% @compat Up to version 10.1.13 the components were integers in the 891% range 0..65535. The terminal reports one to four hexadecimal digits 892% per component, so that range suggested a precision the reply does 893% not have, and it clashed with the rgb/3 term of win_window_color/2, 894% whose components are in the range 0..255. 895 896ansi_get_color(Which0, RGB) :- 897 \+ current_prolog_flag(console_menu, true), 898 stream_property(user_input, tty(true)), 899 stream_property(user_output, tty(true)), 900 stream_property(user_error, tty(true)), 901 supports_get_color, 902 ( color_alias(Which0, Which) 903 -> true 904 ; must_be(between(0,15),Which0) 905 -> Which = Which0 906 ), 907 catch(ansi_get_color_(Which, RGB), 908 error(timeout_error(_,_), _), 909 no_xterm). 910 911supports_get_color :- 912 \+ current_prolog_flag(windows, true), 913 getenv('TERM', Term), 914 sub_atom(Term, 0, _, _, xterm), 915 \+ getenv('TERM_PROGRAM', 'Apple_Terminal'). 916 917color_alias(foreground, 10). 918color_alias(background, 11). 919 920ansi_get_color_(Which, RGB) :- 921 stream_property(user_input, timeout(Old)), 922 setup_call_cleanup( 923 set_stream(user_input, timeout(0.05)), 924 with_tty_raw(exchange_color(Which, RGB)), 925 set_stream(user_input, timeout(Old))), 926 !. 927 928no_xterm :- 929 print_message(warning, ansi(no_xterm_get_colour)), 930 fail. 931 932%! exchange_color(+Which, -RGB) is semidet. 933% 934% Ask the terminal for colour Which and read its reply. 935 936exchange_color(Which, RGB) :- 937 format(user_output, '\e]~w;?\a', [Which]), 938 flush_output(user_output), 939 format(codes(Id), '~w', [Which]), 940 read_osc_reply(user_input, Id, Codes), 941 phrase(color_reply(RGB), Codes). 942 943%! read_osc_reply(+In, +Param:codes, -Body:codes) is semidet. 944% 945% Read the body of the OSC reply to our query, i.e., the text between 946% ``ESC ] Param ;`` and the _string terminator_. Terminals reply with 947% either BEL or ST (``ESC \``), so we accept both. 948% 949% Anything that is not our reply is echoed: it was typed by the user 950% while we were waiting rather than sent by the terminal. 951 952read_osc_reply(In, Param, Body) :- 953 get_code(In, C), 954 C \== -1, 955 ( C == 0'\e, 956 osc_reply(In, Param, Body0) 957 -> Body = Body0 958 ; put_code(user_output, C), 959 read_osc_reply(In, Param, Body) 960 ). 961 962osc_reply(In, Param, Body) :- 963 get_code(In, 0']), 964 read_osc_param(In, Param), 965 read_osc_string(In, Body). 966 967read_osc_param(In, Param) :- 968 read_osc_param_(In, Codes), 969 Codes == Param. 970 971read_osc_param_(In, Codes) :- 972 get_code(In, C), 973 C \== -1, 974 ( C == 0'; 975 -> Codes = [] 976 ; Codes = [C|T], 977 read_osc_param_(In, T) 978 ). 979 980read_osc_string(In, Codes) :- 981 get_code(In, C), 982 C \== -1, 983 ( C == 0'\a % BEL 984 -> Codes = [] 985 ; C == 0'\e % ST: ESC \ 986 -> get_code(In, 0'\\), 987 Codes = [] 988 ; Codes = [C|T], 989 read_osc_string(In, T) 990 ). 991 992%! color_reply(-RGB)// is semidet. 993% 994% Parse the body of the reply. This is an X11 colour specification as 995% understood by XParseColor(). Terminals differ in the number of 996% digits they use per component: xterm replies with four, others with 997% one, two or three. A component of N digits is expressed as the 998% fraction of its maximum, which is what XParseColor() scaling amounts 999% to and avoids claiming a precision the reply does not have. 1000 1001color_reply(rgb(R,G,B)) --> 1002 "rgb:", !, 1003 hex_component(R), "/", hex_component(G), "/", hex_component(B). 1004color_reply(rgb(R,G,B)) --> % #RGB, #RRGGBB, ... 1005 "#", 1006 hex_digits(Ds), 1007 { length(Ds, Len), 1008 N is Len//3, 1009 N > 0, N =< 4, 1010 length(RDs, N), length(GDs, N), length(BDs, N), 1011 append(RDs, Rest, Ds), 1012 append(GDs, BDs, Rest), 1013 hex_value(RDs, R), 1014 hex_value(GDs, G), 1015 hex_value(BDs, B) 1016 }. 1017 1018hex_component(V) --> 1019 hex_digits(Ds), 1020 { Ds \== [], 1021 length(Ds, N), 1022 N =< 4, 1023 hex_value(Ds, V) 1024 }. 1025 1026hex_digits([H|T]) --> 1027 [H], 1028 { code_type(H, xdigit(_)) }, 1029 !, 1030 hex_digits(T). 1031hex_digits([]) --> 1032 []. 1033 1034%! hex_value(+Digits, -Fraction) is det. 1035% 1036% Fraction is a float in 0.0..1.0. Note that we must not rely on 1037% (/)/2 to produce a float: that depends on the Prolog flag 1038% `prefer_rationals`. 1039 1040hex_value(Ds, V) :- 1041 hex_value(Ds, 0, V0), 1042 length(Ds, N), 1043 Max is 16^N - 1, 1044 V is float(V0)/Max. 1045 1046hex_value([], V, V). 1047hex_value([D|T], V0, V) :- 1048 code_type(D, xdigit(DV)), 1049 V1 is V0*16+DV, 1050 hex_value(T, V1, V). 1051 1052:- multifile prolog:message//1. 1053 1054prologmessage(ansi(no_xterm_get_colour)) --> 1055 [ 'Terminal claims to be xterm compatible,'-[], nl, 1056 'but does not report colour info'-[] 1057 ]