@@ -116,25 +116,71 @@ func ioctl(_ a: Int32, _ b: Int32, _ p: UnsafeMutableRawPointer) -> Int32 {
116116
117117extension Platform {
118118 /// The default terminal size.
119- static var defaultTerminalSize : ( width: Int , height: Int ) {
120- ( 80 , 25 )
119+ private static var defaultTerminalSize : ( width: Int , height: Int ) {
120+ ( width : 80 , height : 25 )
121121 }
122122
123- /// Returns the current terminal size, or the default if the size is
124- /// unavailable.
125- static func terminalSize( ) -> ( width: Int , height: Int ) {
123+ /// The terminal size specified by the COLUMNS and LINES overrides
124+ /// (if present).
125+ ///
126+ /// Per the [Linux environ(7) manpage][linenv]:
127+ ///
128+ /// ```
129+ /// * COLUMNS and LINES tell applications about the window size,
130+ /// possibly overriding the actual size.
131+ /// ```
132+ ///
133+ /// And the [FreeBSD environ(7) version][bsdenv]:
134+ ///
135+ /// ```
136+ /// COLUMNS The user's preferred width in column positions for the
137+ /// terminal. Utilities such as ls(1) and who(1) use this
138+ /// to format output into columns. If unset or empty,
139+ /// utilities will use an ioctl(2) call to ask the termi-
140+ /// nal driver for the width.
141+ /// ```
142+ ///
143+ /// > Note: Always returns `(nil, nil)` on Windows and WASI.
144+ ///
145+ /// - Returns: A tuple consisting of a width found in the `COLUMNS` environment
146+ /// variable (or `nil` if the variable is not present) and a height found in
147+ /// the `LINES` environment variable (or `nil` if that variable is not present).
148+ ///
149+ /// [linenv]: https://man7.org/linux/man-pages/man7/environ.7.html:~:text=COLUMNS
150+ /// [bsdenv]: https://man.freebsd.org/cgi/man.cgi?environ(7)#:~:text=COLUMNS
151+ private static func userSpecifiedTerminalSize( ) -> ( width: Int ? , height: Int ? ) {
152+ var width : Int ? = nil , height : Int ? = nil
153+
154+ #if !os(Windows) && !os(WASI)
155+ if let colsCStr = getenv ( " COLUMNS " ) , let colsVal = Int ( String ( cString: colsCStr) ) {
156+ width = colsVal
157+ }
158+ if let linesCStr = getenv ( " LINES " ) , let linesVal = Int ( String ( cString: linesCStr) ) {
159+ height = linesVal
160+ }
161+ #endif
162+
163+ return ( width: width, height: height)
164+ }
165+
166+ /// The current terminal size as reported by the windowing system,
167+ /// if available.
168+ ///
169+ /// Returns (nil, nil) if no reported size is available.
170+ private static func reportedTerminalSize( ) -> ( width: Int ? , height: Int ? ) {
126171#if os(WASI)
127172 // WASI doesn't yet support terminal size
128- return defaultTerminalSize
173+ return ( width : nil , height : nil )
129174#elseif os(Windows)
130- var csbi : CONSOLE_SCREEN_BUFFER_INFO = CONSOLE_SCREEN_BUFFER_INFO ( )
175+ var csbi = CONSOLE_SCREEN_BUFFER_INFO ( )
131176 guard GetConsoleScreenBufferInfo ( GetStdHandle ( STD_OUTPUT_HANDLE) , & csbi) else {
132- return defaultTerminalSize
177+ return ( width : nil , height : nil )
133178 }
134179 return ( width: Int ( csbi. srWindow. Right - csbi. srWindow. Left) + 1 ,
135180 height: Int ( csbi. srWindow. Bottom - csbi. srWindow. Top) + 1 )
136181#else
137182 var w = winsize ( )
183+
138184#if os(OpenBSD)
139185 // TIOCGWINSZ is a complex macro, so we need the flattened value.
140186 let tiocgwinsz = Int32 ( 0x40087468 )
@@ -144,17 +190,38 @@ extension Platform {
144190#else
145191 let err = ioctl ( STDOUT_FILENO, TIOCGWINSZ, & w)
146192#endif
147- let width = Int ( w. ws_col)
148- let height = Int ( w. ws_row)
149- guard err == 0 else { return defaultTerminalSize }
150- return ( width: width > 0 ? width : defaultTerminalSize. width,
151- height: height > 0 ? height : defaultTerminalSize. height)
193+ guard err == 0 else { return ( width: nil , height: nil ) }
194+
195+ let width = Int ( w. ws_col) , height = Int ( w. ws_row)
196+
197+ return ( width: width > 0 ? width : nil ,
198+ height: height > 0 ? height : nil )
152199#endif
153200 }
154201
202+ /// Returns the current terminal size, or the default if the size is unavailable.
203+ static func terminalSize( ) -> ( width: Int , height: Int ) {
204+ let specifiedSize = self . userSpecifiedTerminalSize ( )
205+
206+ // Avoid needlessly calling ioctl() if a complete override is in effect
207+ if let specifiedWidth = specifiedSize. width, let specifiedHeight = specifiedSize. height {
208+ return ( width: specifiedWidth, height: specifiedHeight)
209+ }
210+
211+ // Get the size self-reported by the terminal, if available
212+ let reportedSize = self . reportedTerminalSize ( )
213+
214+ // As it isn't required that both width and height always be specified
215+ // together, either by the user or the terminal itself, they are
216+ // handled separately.
217+ return (
218+ width: specifiedSize. width ?? reportedSize. width ?? defaultTerminalSize. width,
219+ height: specifiedSize. height ?? reportedSize. height ?? defaultTerminalSize. height
220+ )
221+ }
222+
155223 /// The current terminal size, or the default if the width is unavailable.
156224 static var terminalWidth : Int {
157- terminalSize ( ) . width
225+ self . terminalSize ( ) . width
158226 }
159227}
160-
0 commit comments