Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export(ansi_hide_cursor)
export(ansi_html)
export(ansi_html_style)
export(ansi_nchar)
export(ansi_palette_show)
export(ansi_palettes)
export(ansi_regex)
export(ansi_show_cursor)
export(ansi_simplify)
Expand Down Expand Up @@ -213,6 +215,7 @@ export(symbol)
export(test_that_cli)
export(ticking)
export(tree)
export(truecolor)
export(utf8_graphemes)
export(utf8_nchar)
export(utf8_substr)
Expand Down
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# cli (development version)

* Support for palettes, including a colorblind friendly palette.
See `?ansi_palettes` for details.

* True color support: `num_ansi_colors()` now detects terminals with
24 bit color support, and `make_ansi_style()` uses the exact RGB colors
on these terminals.
Expand Down
219 changes: 219 additions & 0 deletions R/ansi-palette.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@

get_palette_color <- function(style, colors = num_ansi_colors()) {
opt <- getOption("cli.palette")
if (is.null(opt) || colors < 256) return(style)
cache_palette_color(opt, style$palette, colors)
}

palette_cache <- new.env(parent = emptyenv())

cache_palette_color <- function(pal, idx, colors = num_ansi_colors()) {
if (is_string(pal)) {
if (! pal %in% rownames(ansi_palettes)) {
stop("Cannot find cli ANSI palette '", pal, "'.")
}
pal <- ansi_palettes[pal, ]
}

bg <- idx < 0
idx <- abs(idx)
col <- pal[[idx]]

colkey <- as.character(colors)
key <- paste0(col, bg)
if (key %in% names(palette_cache[[colkey]])) {
return(palette_cache[[colkey]][[key]])
}

val <- ansi_style_from_r_color(
col,
bg = bg,
colors,
grey = FALSE
)

if (is.null(palette_cache[[colkey]])) {
palette_cache[[colkey]] <- new.env(parent = emptyenv())
}
palette_cache[[colkey]][[key]] <- val

return(val)
}

#' @details
#' `truecolor` is an integer constant for the number of 24 bit ANSI colors.
#'
#' @format `truecolor` is an integer scalar.
#'
#' @export
#' @rdname ansi_palettes

truecolor <- as.integer(256 ^ 3)

#' ANSI colors palettes
#'
#' If your platform supports at least 256 colors, then you can configure
#' the colors that cli uses for the eight base and the eight bright colors.
#' (I.e. the colors of [col_black()], [col_red()], and [col_br_black()],
#' [col_br_red()], etc.
#'
#' To customize the default palette, set the `cli.palette` option to the
#' name of a built-in palette (see `ansi_palettes()`), or the list of
#' 16 colors. Colors can be specified with RGB colors strings:
#' `#rrggbb` or R color names (see the output of [grDevices::colors()]).
#'
#' For example, you can put this in your R profile:
#' ```r
#' options(cli.palette = "vscode")
#' ```
#'
#' It is currently not possible to configure the background colors
#' separately, these will be always the same as the foreground colors.
#'
#' If your platform only has 256 colors, then the colors specified in the
#' palette have to be interpolated. On true color platforms they RGB
#' values are used as-is.
#'
#' `ansi_palettes` is a data frame of the built-in palettes, each row
#' is one palette.
#'
#' `ansi_palette_show()` shows the colors of an ANSI palette on the screen.
#'
#' @format `ansi_palettes` is a data frame with one row for each palette,
#' and one column for each base ANSI color. `attr(ansi_palettes, "info")`
#' contains a list with information about each palette.
#'
#' @export
#' @examples
#' ansi_palettes
#' ansi_palette_show("dichro", colors = truecolor)

ansi_palettes <- rbind(
read.table(
"tools/ansi-palettes.txt",
comment = ";",
stringsAsFactors = FALSE
),
read.table(
"tools/ansi-iterm-palettes.txt",
comment = ";",
stringsAsFactors = FALSE
)
)

attr(ansi_palettes, "info") <-
list(
dichro = paste(
"Colorblind friendly palette, from",
"https://github.com/romainl/vim-dichromatic#dichromatic."
),
vga = paste(
"Typical colors that are used when booting PCs and leaving them in",
"text mode, which used a 16-entry color table. The colors are",
"different in the EGA/VGA graphic modes.",
"From https://en.wikipedia.org/wiki/ANSI_escape_code#SGR."
),
winxp = paste(
"Windows XP Console. Seen in Windows XP through Windows 8.1.",
"From https://en.wikipedia.org/wiki/ANSI_escape_code#SGR."
),
vscode = paste(
"Visual Studio Debug console, 'Dark+' theme.",
"From https://en.wikipedia.org/wiki/ANSI_escape_code#SGR."
),
win10 = paste0(
"Campbell theme, used as of Windows 10 version 1709. Also used",
"by PowerShell 6.",
"From https://en.wikipedia.org/wiki/ANSI_escape_code#SGR."
),
macos = paste0(
"Terminal.app in macOS",
"From https://en.wikipedia.org/wiki/ANSI_escape_code#SGR."
),
putty = paste0(
"From https://en.wikipedia.org/wiki/ANSI_escape_code#SGR."
),
mirc = paste0(
"From https://en.wikipedia.org/wiki/ANSI_escape_code#SGR."
),
xterm = paste0(
"From https://en.wikipedia.org/wiki/ANSI_escape_code#SGR."
),
ubuntu = paste0(
"For virtual terminals, from /etc/vtrgb.",
"From https://en.wikipedia.org/wiki/ANSI_escape_code#SGR."
),
eclipse = paste0(
"From https://en.wikipedia.org/wiki/ANSI_escape_code#SGR."
),
iterm = "Built-in iTerm2 theme.",
"iterm-pastel" = "Built-In iTerm2 theme.",
"iterm-smoooooth" = "Built-In iTerm2 theme.",
"iterm-snazzy" = "From https://github.com/sindresorhus/iterm2-snazzy.",
"iterm-solarized" = "Built-In iTerm2 theme.",
"iterm-tango" = "Built-In iTerm2 theme."
)

#' @param palette The palette to show, in the same format as for the
#' `cli.palette` option, so it can be the name of a built-in palette,
#' of a list of 16 colors.
#' @param colors Number of ANSI colors to use the show the palette. If the
#' platform does not have sufficient support, the output might have
#' a lower color resolution. Without color support it will have no color
#' at all.
#' @param rows The number of colored rows to print.
#' @return `ansi_palette_show` returns a character vector, the rows that
#' are printed to the screen, invisibly.
#'
#' @export
#' @rdname ansi_palettes

ansi_palette_show <- function(palette = NULL, colors = num_ansi_colors(),
rows = 4) {
opts <- options(
cli.palette = palette %||% getOption("cli.palette"),
cli.num_colors = colors
)
on.exit(options(opts), add = TRUE)

blk <- strrep(symbol$lower_block_8, 4)
blks <- c(
"blck" = col_black(blk),
"red " = col_red(blk),
"grn " = col_green(blk),
"yllw" = col_yellow(blk),
"blue" = col_blue(blk),
"mgnt" = col_magenta(blk),
"cyan" = col_cyan(blk),
"whte" = col_white(blk),
"blck" = col_br_black(blk),
"red " = col_br_red(blk),
"grn " = col_br_green(blk),
"yllw" = col_br_yellow(blk),
"blue" = col_br_blue(blk),
"mgnt" = col_br_magenta(blk),
"cyan" = col_br_cyan(blk),
"whte" = col_br_white(blk)
)

join <- function(x) {
paste0(
paste(x[1:8], collapse = " "),
" ",
paste(x[9:16], collapse = " ")
)
}

nms <- join(names(blks))
str <- join(blks)

out <- c(
paste(strrep(" ", 52), "bright variants"),
nms,
"",
rep(str, rows)
)

cat_line(out)
invisible(out)
}
101 changes: 60 additions & 41 deletions R/ansi.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@

TRUE_COLORS <- as.integer(256^3)
palette_idx <- function(id) {
ifelse(
id < 38,
id - (30 - 1),
ifelse(
id < 48,
-(id - (40 - 1)),
ifelse(
id < 98,
id - (90 - 9),
-(id - (100 - 9))
)))
}

palette_color <- function(x) {
c(x, palette = palette_idx(x[[1]]))
}

ansi_builtin_styles <- list(
reset = list(0, c(0, 22, 23, 24, 27, 28, 29, 39, 49)),
Expand All @@ -11,42 +27,42 @@ ansi_builtin_styles <- list(
hidden = list(8, 28),
strikethrough = list(9, 29),

black = list(30, 39),
red = list(31, 39),
green = list(32, 39),
yellow = list(33, 39),
blue = list(34, 39),
magenta = list(35, 39),
cyan = list(36, 39),
white = list(37, 39),
black = palette_color(list(30, 39)),
red = palette_color(list(31, 39)),
green = palette_color(list(32, 39)),
yellow = palette_color(list(33, 39)),
blue = palette_color(list(34, 39)),
magenta = palette_color(list(35, 39)),
cyan = palette_color(list(36, 39)),
white = palette_color(list(37, 39)),
silver = list(90, 39),

br_black = list(90, 39),
br_red = list(91, 39),
br_green = list(92, 39),
br_yellow = list(93, 39),
br_blue = list(94, 39),
br_magenta = list(95, 39),
br_cyan = list(96, 39),
br_white = list(97, 39),

bg_black = list(40, 49),
bg_red = list(41, 49),
bg_green = list(42, 49),
bg_yellow = list(43, 49),
bg_blue = list(44, 49),
bg_magenta = list(45, 49),
bg_cyan = list(46, 49),
bg_white = list(47, 49),

bg_br_black = list(100, 39),
bg_br_red = list(101, 39),
bg_br_green = list(102, 39),
bg_br_yellow = list(103, 39),
bg_br_blue = list(104, 39),
bg_br_magenta = list(105, 39),
bg_br_cyan = list(106, 39),
bg_br_white = list(107, 39),
br_black = palette_color(list(90, 39)),
br_red = palette_color(list(91, 39)),
br_green = palette_color(list(92, 39)),
br_yellow = palette_color(list(93, 39)),
br_blue = palette_color(list(94, 39)),
br_magenta = palette_color(list(95, 39)),
br_cyan = palette_color(list(96, 39)),
br_white = palette_color(list(97, 39)),

bg_black = palette_color(list(40, 49)),
bg_red = palette_color(list(41, 49)),
bg_green = palette_color(list(42, 49)),
bg_yellow = palette_color(list(43, 49)),
bg_blue = palette_color(list(44, 49)),
bg_magenta = palette_color(list(45, 49)),
bg_cyan = palette_color(list(46, 49)),
bg_white = palette_color(list(47, 49)),

bg_br_black = palette_color(list(100, 39)),
bg_br_red = palette_color(list(101, 39)),
bg_br_green = palette_color(list(102, 39)),
bg_br_yellow = palette_color(list(103, 39)),
bg_br_blue = palette_color(list(104, 39)),
bg_br_magenta = palette_color(list(105, 39)),
bg_br_cyan = palette_color(list(106, 39)),
bg_br_white = palette_color(list(107, 39)),

# similar to reset, but only for a single property
no_bold = list(c(0, 23, 24, 27, 28, 29, 39, 49), 22),
Expand Down Expand Up @@ -97,19 +113,21 @@ ansi_style_str <- function(x) {
paste0("\u001b[", x, "m", collapse = "")
}

create_ansi_style_tag <- function(name, open, close) {
create_ansi_style_tag <- function(name, open, close, palette = NULL) {
structure(
list(list(open = open, close = close)),
list(list(open = open, close = close, palette = palette)),
names = name
)
}

create_ansi_style_fun <- function(styles) {
fun <- eval(substitute(function(...) {
mystyles <- .styles
txt <- paste0(...)
if (num_ansi_colors() > 1) {
nc <- num_ansi_colors()
if (nc > 1) {
mystyles <- .styles
for (st in rev(mystyles)) {
if (!is.null(st$palette)) st <- get_palette_color(st, nc)
txt <- paste0(
st$open,
gsub(st$close, st$open, txt, fixed = TRUE),
Expand All @@ -129,7 +147,8 @@ create_ansi_style_fun <- function(styles) {
create_ansi_style <- function(name, open = NULL, close = NULL) {
open <- open %||% ansi_style_str(ansi_builtin_styles[[name]][[1]])
close <- close %||% ansi_style_str(ansi_builtin_styles[[name]][[2]])
style <- create_ansi_style_tag(name, open, close)
palette <- ansi_builtin_styles[[name]]$palette
style <- create_ansi_style_tag(name, open, close, palette)
create_ansi_style_fun(style)
}

Expand Down Expand Up @@ -263,7 +282,7 @@ ansi_style_8_from_rgb <- function(rgb, bg) {

ansi_style_from_rgb <- function(rgb, bg, num_colors, grey) {
if (num_colors < 256) { return(ansi_style_8_from_rgb(rgb, bg)) }
if (num_colors < TRUE_COLORS || grey) return(ansi256(rgb, bg, grey))
if (num_colors < truecolor || grey) return(ansi256(rgb, bg, grey))
return(ansitrue(rgb, bg))
}

Expand Down
Loading