bunt_ansi.ex (17665B)
1 defmodule Bunt.ANSI.Sequence do 2 @moduledoc false 3 4 defmacro defalias(alias_name, original_name) do 5 quote bind_quoted: [alias_name: alias_name, original_name: original_name] do 6 def unquote(alias_name)() do 7 unquote(original_name)() 8 end 9 10 defp format_sequence(unquote(alias_name)) do 11 unquote(original_name)() 12 end 13 end 14 end 15 16 defmacro defsequence(name, code, prefix \\ "", terminator \\ "m") do 17 quote bind_quoted: [name: name, code: code, prefix: prefix, terminator: terminator] do 18 def unquote(name)() do 19 "\e[#{unquote(prefix)}#{unquote(code)}#{unquote(terminator)}" 20 end 21 22 defp format_sequence(unquote(name)) do 23 unquote(name)() 24 end 25 end 26 end 27 end 28 29 defmodule Bunt.ANSI do 30 @moduledoc """ 31 Functionality to render ANSI escape sequences. 32 33 [ANSI escape sequences](https://en.wikipedia.org/wiki/ANSI_escape_code) 34 are characters embedded in text used to control formatting, color, and 35 other output options on video text terminals. 36 """ 37 38 import Bunt.ANSI.Sequence 39 40 @color_tuples [ 41 {nil, :color16, 16, {0, 0, 0}}, 42 {nil, :color17, 17, {0, 0, 95}}, 43 {"darkblue", :color18, 18, {0, 0, 135}}, 44 {nil, :color19, 19, {0, 0, 175}}, 45 {"mediumblue", :color20, 20, {0, 0, 215}}, 46 {nil, :color21, 21, {0, 0, 255}}, 47 {"darkgreen", :color22, 22, {0, 95, 0}}, 48 {"darkslategray", :color23, 23, {0, 95, 95}}, 49 {nil, :color24, 24, {0, 95, 135}}, 50 {nil, :color25, 25, {0, 95, 175}}, 51 {nil, :color26, 26, {0, 95, 215}}, 52 {nil, :color27, 27, {0, 95, 255}}, 53 {nil, :color28, 28, {0, 135, 0}}, 54 {nil, :color29, 29, {0, 135, 95}}, 55 {"darkcyan", :color30, 30, {0, 135, 135}}, 56 {nil, :color31, 31, {0, 135, 175}}, 57 {nil, :color32, 32, {0, 135, 215}}, 58 {nil, :color33, 33, {0, 135, 255}}, 59 {nil, :color34, 34, {0, 175, 0}}, 60 {nil, :color35, 35, {0, 175, 95}}, 61 {nil, :color36, 36, {0, 175, 135}}, 62 {nil, :color37, 37, {0, 175, 175}}, 63 {nil, :color38, 38, {0, 175, 215}}, 64 {"deepskyblue", :color39, 39, {0, 175, 255}}, 65 {nil, :color40, 40, {0, 215, 0}}, 66 {nil, :color41, 41, {0, 215, 95}}, 67 {nil, :color42, 42, {0, 215, 135}}, 68 {nil, :color43, 43, {0, 215, 175}}, 69 {nil, :color44, 44, {0, 215, 215}}, 70 {nil, :color45, 45, {0, 215, 255}}, 71 {nil, :color46, 46, {0, 255, 0}}, 72 {nil, :color47, 47, {0, 255, 95}}, 73 {"springgreen", :color48, 48, {0, 255, 135}}, 74 {nil, :color49, 49, {0, 255, 175}}, 75 {nil, :color50, 50, {0, 255, 215}}, 76 {"aqua", :color51, 51, {0, 255, 255}}, 77 {nil, :color52, 52, {95, 0, 0}}, 78 {nil, :color53, 53, {95, 0, 95}}, 79 {nil, :color54, 54, {95, 0, 135}}, 80 {nil, :color55, 55, {95, 0, 175}}, 81 {nil, :color56, 56, {95, 0, 215}}, 82 {nil, :color57, 57, {95, 0, 255}}, 83 {nil, :color58, 58, {95, 95, 0}}, 84 {"dimgray", :color59, 59, {95, 95, 95}}, 85 {nil, :color60, 60, {95, 95, 135}}, 86 {nil, :color61, 61, {95, 95, 175}}, 87 {nil, :color62, 62, {95, 95, 215}}, 88 {nil, :color63, 63, {95, 95, 255}}, 89 {nil, :color64, 64, {95, 135, 0}}, 90 {nil, :color65, 65, {95, 135, 95}}, 91 {nil, :color66, 66, {95, 135, 135}}, 92 {"steelblue", :color67, 67, {95, 135, 175}}, 93 {nil, :color68, 68, {95, 135, 215}}, 94 {nil, :color69, 69, {95, 135, 255}}, 95 {nil, :color70, 70, {95, 175, 0}}, 96 {nil, :color71, 71, {95, 175, 95}}, 97 {nil, :color72, 72, {95, 175, 135}}, 98 {nil, :color73, 73, {95, 175, 175}}, 99 {nil, :color74, 74, {95, 175, 215}}, 100 {nil, :color75, 75, {95, 175, 255}}, 101 {nil, :color76, 76, {95, 215, 0}}, 102 {nil, :color77, 77, {95, 215, 95}}, 103 {nil, :color78, 78, {95, 215, 135}}, 104 {nil, :color79, 79, {95, 215, 175}}, 105 {nil, :color80, 80, {95, 215, 215}}, 106 {nil, :color81, 81, {95, 215, 255}}, 107 {nil, :color82, 82, {95, 255, 0}}, 108 {nil, :color83, 83, {95, 255, 95}}, 109 {nil, :color84, 84, {95, 255, 135}}, 110 {nil, :color85, 85, {95, 255, 175}}, 111 {nil, :color86, 86, {95, 255, 215}}, 112 {nil, :color87, 87, {95, 255, 255}}, 113 {"darkred", :color88, 88, {135, 0, 0}}, 114 {nil, :color89, 89, {135, 0, 95}}, 115 {"darkmagenta", :color90, 90, {135, 0, 135}}, 116 {nil, :color91, 91, {135, 0, 175}}, 117 {nil, :color92, 92, {135, 0, 215}}, 118 {nil, :color93, 93, {135, 0, 255}}, 119 {nil, :color94, 94, {135, 95, 0}}, 120 {nil, :color95, 95, {135, 95, 95}}, 121 {nil, :color96, 96, {135, 95, 135}}, 122 {nil, :color97, 97, {135, 95, 175}}, 123 {nil, :color98, 98, {135, 95, 215}}, 124 {nil, :color99, 99, {135, 95, 255}}, 125 {"olive", :color100, 100, {135, 135, 0}}, 126 {nil, :color101, 101, {135, 135, 95}}, 127 {nil, :color102, 102, {135, 135, 135}}, 128 {nil, :color103, 103, {135, 135, 175}}, 129 {nil, :color104, 104, {135, 135, 215}}, 130 {nil, :color105, 105, {135, 135, 255}}, 131 {nil, :color106, 106, {135, 175, 0}}, 132 {nil, :color107, 107, {135, 175, 95}}, 133 {nil, :color108, 108, {135, 175, 135}}, 134 {nil, :color109, 109, {135, 175, 175}}, 135 {nil, :color110, 110, {135, 175, 215}}, 136 {nil, :color111, 111, {135, 175, 255}}, 137 {nil, :color112, 112, {135, 215, 0}}, 138 {nil, :color113, 113, {135, 215, 95}}, 139 {nil, :color114, 114, {135, 215, 135}}, 140 {nil, :color115, 115, {135, 215, 175}}, 141 {nil, :color116, 116, {135, 215, 215}}, 142 {nil, :color117, 117, {135, 215, 255}}, 143 {"chartreuse", :color118, 118, {135, 255, 0}}, 144 {nil, :color119, 119, {135, 255, 95}}, 145 {nil, :color120, 120, {135, 255, 135}}, 146 {nil, :color121, 121, {135, 255, 175}}, 147 {"aquamarine", :color122, 122, {135, 255, 215}}, 148 {nil, :color123, 123, {135, 255, 255}}, 149 {nil, :color124, 124, {175, 0, 0}}, 150 {nil, :color125, 125, {175, 0, 95}}, 151 {nil, :color126, 126, {175, 0, 135}}, 152 {nil, :color127, 127, {175, 0, 175}}, 153 {nil, :color128, 128, {175, 0, 215}}, 154 {nil, :color129, 129, {175, 0, 255}}, 155 {nil, :color130, 130, {175, 95, 0}}, 156 {nil, :color131, 131, {175, 95, 95}}, 157 {nil, :color132, 132, {175, 95, 135}}, 158 {nil, :color133, 133, {175, 95, 175}}, 159 {nil, :color134, 134, {175, 95, 215}}, 160 {nil, :color135, 135, {175, 95, 255}}, 161 {nil, :color136, 136, {175, 135, 0}}, 162 {nil, :color137, 137, {175, 135, 95}}, 163 {nil, :color138, 138, {175, 135, 135}}, 164 {nil, :color139, 139, {175, 135, 175}}, 165 {nil, :color140, 140, {175, 135, 215}}, 166 {nil, :color141, 141, {175, 135, 255}}, 167 {nil, :color142, 142, {175, 175, 0}}, 168 {nil, :color143, 143, {175, 175, 95}}, 169 {nil, :color144, 144, {175, 175, 135}}, 170 {nil, :color145, 145, {175, 175, 175}}, 171 {nil, :color146, 146, {175, 175, 215}}, 172 {nil, :color147, 147, {175, 175, 255}}, 173 {nil, :color148, 148, {175, 215, 0}}, 174 {nil, :color149, 149, {175, 215, 95}}, 175 {nil, :color150, 150, {175, 215, 135}}, 176 {nil, :color151, 151, {175, 215, 175}}, 177 {nil, :color152, 152, {175, 215, 215}}, 178 {nil, :color153, 153, {175, 215, 255}}, 179 {"greenyellow", :color154, 154, {175, 255, 0}}, 180 {nil, :color155, 155, {175, 255, 95}}, 181 {nil, :color156, 156, {175, 255, 135}}, 182 {nil, :color157, 157, {175, 255, 175}}, 183 {nil, :color158, 158, {175, 255, 215}}, 184 {nil, :color159, 159, {175, 255, 255}}, 185 {nil, :color160, 160, {215, 0, 0}}, 186 {nil, :color161, 161, {215, 0, 95}}, 187 {nil, :color162, 162, {215, 0, 135}}, 188 {nil, :color163, 163, {215, 0, 175}}, 189 {nil, :color164, 164, {215, 0, 215}}, 190 {nil, :color165, 165, {215, 0, 255}}, 191 {nil, :color166, 166, {215, 95, 0}}, 192 {nil, :color167, 167, {215, 95, 95}}, 193 {nil, :color168, 168, {215, 95, 135}}, 194 {nil, :color169, 169, {215, 95, 175}}, 195 {nil, :color170, 170, {215, 95, 215}}, 196 {nil, :color171, 171, {215, 95, 255}}, 197 {"chocolate", :color172, 172, {215, 135, 0}}, 198 {nil, :color173, 173, {215, 135, 95}}, 199 {nil, :color174, 174, {215, 135, 135}}, 200 {nil, :color175, 175, {215, 135, 175}}, 201 {nil, :color176, 176, {215, 135, 215}}, 202 {nil, :color177, 177, {215, 135, 255}}, 203 {"goldenrod", :color178, 178, {215, 175, 0}}, 204 {nil, :color179, 179, {215, 175, 95}}, 205 {nil, :color180, 180, {215, 175, 135}}, 206 {nil, :color181, 181, {215, 175, 175}}, 207 {nil, :color182, 182, {215, 175, 215}}, 208 {nil, :color183, 183, {215, 175, 255}}, 209 {nil, :color184, 184, {215, 215, 0}}, 210 {nil, :color185, 185, {215, 215, 95}}, 211 {nil, :color186, 186, {215, 215, 135}}, 212 {nil, :color187, 187, {215, 215, 175}}, 213 {"lightgray", :color188, 188, {215, 215, 215}}, 214 {nil, :color189, 189, {215, 215, 255}}, 215 {nil, :color190, 190, {215, 255, 0}}, 216 {nil, :color191, 191, {215, 255, 95}}, 217 {nil, :color192, 192, {215, 255, 135}}, 218 {nil, :color193, 193, {215, 255, 175}}, 219 {"beige", :color194, 194, {215, 255, 215}}, 220 {"lightcyan", :color195, 195, {215, 255, 255}}, 221 {nil, :color196, 196, {255, 0, 0}}, 222 {nil, :color197, 197, {255, 0, 95}}, 223 {nil, :color198, 198, {255, 0, 135}}, 224 {nil, :color199, 199, {255, 0, 175}}, 225 {nil, :color200, 200, {255, 0, 215}}, 226 {"fuchsia", :color201, 201, {255, 0, 255}}, 227 {"orangered", :color202, 202, {255, 95, 0}}, 228 {nil, :color203, 203, {255, 95, 95}}, 229 {nil, :color204, 204, {255, 95, 135}}, 230 {"hotpink", :color205, 205, {255, 95, 175}}, 231 {nil, :color206, 206, {255, 95, 215}}, 232 {nil, :color207, 207, {255, 95, 255}}, 233 {"darkorange", :color208, 208, {255, 135, 0}}, 234 {"coral", :color209, 209, {255, 135, 95}}, 235 {nil, :color210, 210, {255, 135, 135}}, 236 {nil, :color211, 211, {255, 135, 175}}, 237 {nil, :color212, 212, {255, 135, 215}}, 238 {nil, :color213, 213, {255, 135, 255}}, 239 {"orange", :color214, 214, {255, 175, 0}}, 240 {nil, :color215, 215, {255, 175, 95}}, 241 {nil, :color216, 216, {255, 175, 135}}, 242 {nil, :color217, 217, {255, 175, 175}}, 243 {nil, :color218, 218, {255, 175, 215}}, 244 {nil, :color219, 219, {255, 175, 255}}, 245 {"gold", :color220, 220, {255, 215, 0}}, 246 {nil, :color221, 221, {255, 215, 95}}, 247 {"khaki", :color222, 222, {255, 215, 135}}, 248 {"moccasin", :color223, 223, {255, 215, 175}}, 249 {"mistyrose", :color224, 224, {255, 215, 215}}, 250 {nil, :color225, 225, {255, 215, 255}}, 251 {nil, :color226, 226, {255, 255, 0}}, 252 {nil, :color227, 227, {255, 255, 95}}, 253 {nil, :color228, 228, {255, 255, 135}}, 254 {nil, :color229, 229, {255, 255, 175}}, 255 {"lightyellow", :color230, 230, {255, 255, 215}}, 256 {nil, :color231, 231, {255, 255, 255}}, 257 {nil, :color232, 232, {255, 255, 255}}, 258 {nil, :color233, 233, {255, 255, 255}}, 259 {nil, :color234, 234, {255, 255, 255}}, 260 {nil, :color235, 235, {255, 255, 255}}, 261 {nil, :color236, 236, {255, 255, 255}}, 262 {nil, :color237, 237, {255, 255, 255}}, 263 {nil, :color238, 238, {255, 255, 255}}, 264 {nil, :color239, 239, {255, 255, 255}}, 265 {nil, :color240, 240, {255, 255, 255}}, 266 {nil, :color241, 241, {255, 255, 255}}, 267 {nil, :color242, 242, {255, 255, 255}}, 268 {nil, :color243, 243, {255, 255, 255}}, 269 {nil, :color244, 244, {255, 255, 255}}, 270 {nil, :color245, 245, {255, 255, 255}}, 271 {nil, :color246, 246, {255, 255, 255}}, 272 {nil, :color247, 247, {255, 255, 255}}, 273 {nil, :color248, 248, {255, 255, 255}}, 274 {nil, :color249, 249, {255, 255, 255}}, 275 {nil, :color250, 250, {255, 255, 255}}, 276 {nil, :color251, 251, {255, 255, 255}}, 277 {nil, :color252, 252, {255, 255, 255}}, 278 {nil, :color253, 253, {255, 255, 255}}, 279 {nil, :color254, 254, {255, 255, 255}}, 280 {nil, :color255, 255, {255, 255, 255}} 281 ] 282 283 def color_tuples, do: @color_tuples 284 285 for {name, color, code, _} <- @color_tuples do 286 @doc "Sets foreground color to #{color}" 287 defsequence(color, code, "38;5;") 288 289 @doc "Sets background color to #{color}" 290 defsequence(:"#{color}_background", code, "48;5;") 291 292 if name do 293 @doc "Sets foreground color to #{name}" 294 defsequence(:"#{name}", code, "38;5;") 295 296 @doc "Sets background color to #{name}" 297 defsequence(:"#{name}_background", code, "48;5;") 298 end 299 end 300 301 if Version.match?(System.version(), ">= 1.14.0-dev") do 302 @color_aliases Application.compile_env(:bunt, :color_aliases, []) 303 else 304 function = :get_env 305 @color_aliases apply(Application, function, [:bunt, :color_aliases, []]) 306 end 307 308 def color_aliases, do: @color_aliases 309 310 for {alias_name, original_name} <- @color_aliases do 311 defalias(alias_name, original_name) 312 defalias(:"#{alias_name}_background", :"#{original_name}_background") 313 end 314 315 @typep ansicode :: atom() 316 @typep ansilist :: 317 maybe_improper_list( 318 char() | ansicode() | binary() | ansilist(), 319 binary() | ansicode() | [] 320 ) 321 @type ansidata :: ansilist() | ansicode() | binary() 322 323 @doc """ 324 Checks if ANSI coloring is supported and enabled on this machine. 325 326 This function simply reads the configuration value for 327 `:ansi_enabled` in the `:elixir` application. The value is by 328 default `false` unless Elixir can detect during startup that 329 both `stdout` and `stderr` are terminals. 330 """ 331 @spec enabled? :: boolean 332 def enabled? do 333 Application.get_env(:elixir, :ansi_enabled, false) 334 end 335 336 @doc "Resets all attributes" 337 defsequence(:reset, 0) 338 339 @doc "Bright (increased intensity) or Bold" 340 defsequence(:bright, 1) 341 342 @doc "Faint (decreased intensity), not widely supported" 343 defsequence(:faint, 2) 344 345 @doc "Italic: on. Not widely supported. Sometimes treated as inverse" 346 defsequence(:italic, 3) 347 348 @doc "Underline: Single" 349 defsequence(:underline, 4) 350 351 @doc "Blink: Slow. Less than 150 per minute" 352 defsequence(:blink_slow, 5) 353 354 @doc "Blink: Rapid. MS-DOS ANSI.SYS; 150 per minute or more; not widely supported" 355 defsequence(:blink_rapid, 6) 356 357 @doc "Image: Negative. Swap foreground and background" 358 defsequence(:inverse, 7) 359 360 @doc "Image: Negative. Swap foreground and background" 361 defsequence(:reverse, 7) 362 363 @doc "Conceal. Not widely supported" 364 defsequence(:conceal, 8) 365 366 @doc "Crossed-out. Characters legible, but marked for deletion. Not widely supported" 367 defsequence(:crossed_out, 9) 368 369 @doc "Sets primary (default) font" 370 defsequence(:primary_font, 10) 371 372 for font_n <- [1, 2, 3, 4, 5, 6, 7, 8, 9] do 373 @doc "Sets alternative font #{font_n}" 374 defsequence(:"font_#{font_n}", font_n + 10) 375 end 376 377 @doc "Normal color or intensity" 378 defsequence(:normal, 22) 379 380 @doc "Not italic" 381 defsequence(:not_italic, 23) 382 383 @doc "Underline: None" 384 defsequence(:no_underline, 24) 385 386 @doc "Blink: off" 387 defsequence(:blink_off, 25) 388 389 colors = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white] 390 391 for {color, code} <- Enum.with_index(colors) do 392 @doc "Sets foreground color to #{color}" 393 defsequence(color, code + 30) 394 395 @doc "Sets background color to #{color}" 396 defsequence(:"#{color}_background", code + 40) 397 end 398 399 @doc "Default text color" 400 defsequence(:default_color, 39) 401 402 @doc "Default background color" 403 defsequence(:default_background, 49) 404 405 @doc "Framed" 406 defsequence(:framed, 51) 407 408 @doc "Encircled" 409 defsequence(:encircled, 52) 410 411 @doc "Overlined" 412 defsequence(:overlined, 53) 413 414 @doc "Not framed or encircled" 415 defsequence(:not_framed_encircled, 54) 416 417 @doc "Not overlined" 418 defsequence(:not_overlined, 55) 419 420 @doc "Sends cursor home" 421 defsequence(:home, "", "H") 422 423 @doc "Clears screen" 424 defsequence(:clear, "2", "J") 425 426 @doc "Clears line" 427 defsequence(:clear_line, "2", "K") 428 429 defp format_sequence(other) do 430 raise ArgumentError, "invalid ANSI sequence specification: #{other}" 431 end 432 433 @doc ~S""" 434 Formats a chardata-like argument by converting named ANSI sequences into actual 435 ANSI codes. 436 437 The named sequences are represented by atoms. 438 439 It will also append an `IO.ANSI.reset/0` to the chardata when a conversion is 440 performed. If you don't want this behaviour, use `format_fragment/2`. 441 442 An optional boolean parameter can be passed to enable or disable 443 emitting actual ANSI codes. When `false`, no ANSI codes will emitted. 444 By default checks if ANSI is enabled using the `enabled?/0` function. 445 446 ## Examples 447 448 iex> IO.ANSI.format(["Hello, ", :red, :bright, "world!"], true) 449 [[[[[[], "Hello, "] | "\e[31m"] | "\e[1m"], "world!"] | "\e[0m"] 450 451 """ 452 def format(chardata, emit \\ enabled?()) when is_boolean(emit) do 453 do_format(chardata, [], [], emit, :maybe) 454 end 455 456 @doc ~S""" 457 Formats a chardata-like argument by converting named ANSI sequences into actual 458 ANSI codes. 459 460 The named sequences are represented by atoms. 461 462 An optional boolean parameter can be passed to enable or disable 463 emitting actual ANSI codes. When `false`, no ANSI codes will emitted. 464 By default checks if ANSI is enabled using the `enabled?/0` function. 465 466 ## Examples 467 468 iex> IO.ANSI.format_fragment([:bright, 'Word'], true) 469 [[[[[[] | "\e[1m"], 87], 111], 114], 100] 470 471 """ 472 def format_fragment(chardata, emit \\ enabled?()) when is_boolean(emit) do 473 do_format(chardata, [], [], emit, false) 474 end 475 476 defp do_format([term | rest], rem, acc, emit, append_reset) do 477 do_format(term, [rest | rem], acc, emit, append_reset) 478 end 479 480 defp do_format(term, rem, acc, true, append_reset) when is_atom(term) do 481 do_format([], rem, [acc | format_sequence(term)], true, !!append_reset) 482 end 483 484 defp do_format(term, rem, acc, false, append_reset) when is_atom(term) do 485 do_format([], rem, acc, false, append_reset) 486 end 487 488 defp do_format(term, rem, acc, emit, append_reset) when not is_list(term) do 489 do_format([], rem, [acc | [term]], emit, append_reset) 490 end 491 492 defp do_format([], [next | rest], acc, emit, append_reset) do 493 do_format(next, rest, acc, emit, append_reset) 494 end 495 496 defp do_format([], [], acc, true, true) do 497 [acc | IO.ANSI.reset()] 498 end 499 500 defp do_format([], [], acc, _emit, _append_reset) do 501 acc 502 end 503 end