1
2#' Determine the width of the console
3#'
4#' It uses the `cli.width` option, if set. Otherwise it tries to
5#' determine the size of the terminal or console window.
6#'
7#' These are the exact rules:
8#' * If the `cli.width` option is set to a positive integer, it is used.
9#' * If the `cli.width` option is set, but it is not a positive integer,
10#'   and error is thrown.
11#'
12#' Then we try to determine the size of the terminal or console window:
13#' * If we are not in RStudio, or we are in an RStudio terminal,
14#'   then we try to use the `tty_size()` function to query the
15#'   terminal size. This might fail if R is not running in a terminal,
16#'   but failures are ignored.
17#' * If we are in the RStudio build pane, then the `RSTUDIO_CONSOLE_WIDTH`
18#'   environment variable is used. If the build pane is resized, then this
19#'   environment variable is not accurate any more, and the output might
20#'   get garbled.
21#' * We are _not_ using the `RSTUDIO_CONSOLE_WIDTH` environment variable
22#'   if we are in the RStudio console.
23#'
24#' If we cannot determine the size of the terminal or console window, then
25#' we use the `width` option. If the `width` option is not set, then
26#' we return 80L.
27#'
28#' @return Integer scalar, the console with, in number of characters.
29#'
30#' @export
31#' @examples
32#' console_width()
33
34console_width <- function() {
35  # cli.width option always takes priotity
36  cwopt <- getOption("cli.width")
37  if (!is.null(cwopt)) {
38    if (!is.numeric(cwopt)) stop("options(\"cli.width\") must be integer")
39    if (length(cwopt) != 1) stop("options(\"cli.width\") must be a scalar")
40    if (is.na(cwopt)) stop("options(\"cli.width\") cannot be NA")
41    if (cwopt == Inf) {
42      cwopti <- .Machine$integer.max
43    } else {
44      cwopti <- as.integer(cwopt)
45    }
46    if (cwopti <= 0) stop("options(\"cli.width\") must be a positive integer")
47    return(cwopti)
48  }
49
50  # detect if in RStudio
51  rs <- rstudio$detect()
52  if (rs$type == "not_rstudio") {
53    # maybe a terminal?
54    width <- terminal_width()
55
56  } else if (rs$type == "rstudio_console_starting") {
57    # there isn't much we can do here, options and env vars are not set
58    width <- NULL
59
60  } else if (rs$type == "rstudio_console") {
61    # will just use getOption("width"), in case the user changed it,
62    # and ignore the RSTUDIO_CONSOLE_WIDTH env var
63    width <- NULL
64
65  } else if (rs$type == "rstudio_build_pane") {
66    # RStudio explicitly sets this for build pane processes
67    # It is only good when the build starts, but we cannot do better
68    width <- rs_console_width()
69
70  } else if (rs$type == "rstudio_terminal") {
71    # Can also be a subprocess of the terminal, with a pty,
72    # but that's fine, the pty should have a width set up.
73    # We do not fall back to the RSTUDIO_CONSOLE_WIDTH env var,
74    # because the user might have changed options("width") and the env
75    # var is only good when the terminal starts, anyway.
76    width <- terminal_width()
77
78  } else { # rstudio_subprocess
79    width <- NULL
80  }
81
82  # If not set, then use the option
83  width <- width %||% getOption("width") %||% 80L
84
85  width
86}
87
88tty_size <- function() {
89  tryCatch(
90    ret <- .Call(clic_tty_size),
91    error = function(err) {
92      class(err) <- c("ps_unknown_tty_size", class(err))
93      stop(err)
94    }
95  )
96  c(width = ret[1], height = ret[2])
97}
98
99terminal_width <- function() {
100  w <- tryCatch(tty_size()[["width"]], error = function(e) NULL)
101  # this is probably a pty that does not set the width, use st sensible
102  if (!is.null(w) && w == 0) w <- 80L
103  w
104}
105
106rs_console_width <- function() {
107  ev <- as.integer(Sys.getenv("RSTUDIO_CONSOLE_WIDTH", ""))[1]
108  if (!is.na(ev)) ev else NULL
109}
110