3

I am building a command line tool in Swift and am wondering if there is any way to determine the current width of the viewport while running the app, in order to limit the text on screen to what will fit without wrapping.

I know this is possible in a "real" command line app, since things like pico, man, pine, etc. all render their textual interfaces based on the window size, but I can't seem to find any info on how they do it.

In fact you can even resize the window while they are running and they will refresh. Does anyone know how this works and if it's possible to add support to a command line tool written in Swift?

devios1
  • 36,899
  • 45
  • 162
  • 260
  • In a bash shell, the `LINES` and `COLUMNS` environment variables give the screen size. But those don't appear for me using `ProcessInfo.processInfo.environment` in a Swift command line tool on a Mac. – rmaddy Dec 12 '17 at 16:12
  • Did you care about the xcode debug window or just Terminal? As @Martin R said, you can get `ioctl` to get a width and height fine with Terminal but this doesn't work in XCode's debug window [ even if you change the `Behavior` to launch the Debug Console in a new tab ]. – rustyMagnet Mar 26 '20 at 11:17

1 Answers1

8

The C code from Getting terminal width in C? is easily translated to Swift:

import Darwin

var w = winsize()
if ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0 {
    print("rows:", w.ws_row, "cols", w.ws_col)
}

(For some reason, this does not work in the Xcode debugger console, you have to call the executable in the Terminal window.)

Alternatively, using the ncurses library (from Getting terminal width in C?):

import Darwin.ncurses

initscr()
let s = "rows: \(LINES), cols: \(COLS)"
mvaddstr(1, 1, s);
refresh();
getch()
endwin()

To keep track of window resize events you have to handle the SIGWINCH signal, compare Trapping signals in a Swift command line application:

import Darwin
import Dispatch

var w = winsize()
if ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0 {
    print("rows:", w.ws_row, "cols", w.ws_col)
}

let sigwinchSrc = DispatchSource.makeSignalSource(signal: SIGWINCH, queue: .main)
sigwinchSrc.setEventHandler {
    if ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0 {
        print("rows:", w.ws_row, "cols", w.ws_col)
    }
}
sigwinchSrc.resume()

dispatchMain()
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382