Last active
March 19, 2025 20:39
-
-
Save Blacksmoke16/fc04a1b3ffe00d89b4237c66c8d94c35 to your computer and use it in GitHub Desktop.
OS Agnostic way to determine terminal height/width
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% if flag?(:win32) %} | |
lib LibC | |
STDOUT_HANDLE = 0xFFFFFFF5 | |
struct Point | |
x : UInt16 | |
y : UInt16 | |
end | |
struct SmallRect | |
left : UInt16 | |
top : UInt16 | |
right : UInt16 | |
bottom : UInt16 | |
end | |
struct ScreenBufferInfo | |
dwSize : Point | |
dwCursorPosition : Point | |
wAttributes : UInt16 | |
srWindow : SmallRect | |
dwMaximumWindowSize : Point | |
end | |
alias Handle = Void* | |
alias ScreenBufferInfoPtr = ScreenBufferInfo* | |
fun GetConsoleScreenBufferInfo(handle : Handle, info : ScreenBufferInfoPtr) : Bool | |
fun GetStdHandle(handle : UInt32) : Handle | |
end | |
{% else %} | |
lib LibC | |
struct Winsize | |
ws_row : UShort | |
ws_col : UShort | |
ws_xpixel : UShort | |
ws_ypixel : UShort | |
end | |
# TIOCGWINSZ is a platform dependent magic number passed to ioctl that requests the current terminal window size. | |
# Values lifted from https://github.com/crystal-term/screen/blob/ea51ee8d1f6c286573c41a7e784d31c80af7b9bb/src/term-screen.cr#L86-L88. | |
{% begin %} | |
{% if flag?(:darwin) || flag?(:bsd) %} | |
TIOCGWINSZ = 0x40087468 | |
{% elsif flag?(:unix) %} | |
TIOCGWINSZ = 0x5413 | |
{% else %} # Solaris | |
TIOCGWINSZ = 0x5468 | |
{% end %} | |
{% end %} | |
fun ioctl(fd : Int, request : ULong, ...) : Int | |
end | |
{% end %} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "./lib_c" | |
# :nodoc: | |
struct Athena::Console::Terminal | |
@@width : Int32? = nil | |
@@height : Int32? = nil | |
@@stty : Bool = false | |
def self.has_stty_available? : Bool | |
if stty = @@stty | |
return stty | |
end | |
@@stty = !Process.find_executable("stty").nil? | |
end | |
def width : Int32 | |
if env_width = ENV["COLUMNS"]? | |
return env_width.to_i | |
end | |
if @@width.nil? | |
self.class.init_dimensions | |
end | |
@@width || 80 | |
end | |
def height : Int32 | |
if env_height = ENV["LINES"]? | |
return env_height.to_i | |
end | |
if @@height.nil? | |
self.class.init_dimensions | |
end | |
@@height || 50 | |
end | |
def size : {Int32, Int32} | |
return self.width, self.height | |
end | |
private def self.check_size(size) : Bool | |
if size && (cols = size[0]) && (rows = size[1]) && cols != 0 && rows != 0 | |
@@width = cols | |
@@height = rows | |
return true | |
end | |
false | |
end | |
{% if flag?(:win32) %} | |
protected def self.init_dimensions : Nil | |
return if check_size(size_from_screen_buffer) | |
return if check_size(size_from_ansicon) | |
end | |
# Detect terminal size Windows `GetConsoleScreenBufferInfo`. | |
private def self.size_from_screen_buffer | |
return unless LibC.GetConsoleScreenBufferInfo(LibC.GetStdHandle(LibC::STDOUT_HANDLE), out csbi) | |
cols = csbi.srWindow.right - csbi.srWindow.left + 1 | |
rows = csbi.srWindow.bottom - csbi.srWindow.top + 1 | |
{cols.to_i32, rows.to_i32} | |
end | |
# Detect terminal size from Windows ANSICON | |
private def self.size_from_ansicon | |
return unless ENV["ANSICON"]?.to_s =~ /\((.*)x(.*)\)/ | |
rows, cols = [$2, $1].map(&.to_i) | |
{cols, rows} | |
end | |
{% else %} | |
protected def self.init_dimensions : Nil | |
return if self.check_size(self.size_from_ioctl(0)) # STDIN | |
return if self.check_size(self.size_from_ioctl(1)) # STDOUT | |
return if self.check_size(self.size_from_ioctl(2)) # STDERR | |
return if self.check_size(self.size_from_tput) | |
return if self.check_size(self.size_from_stty) | |
end | |
# Read terminal size from Unix ioctl | |
private def self.size_from_ioctl(fd) | |
winsize = uninitialized LibC::Winsize | |
ret = LibC.ioctl(fd, LibC::TIOCGWINSZ, pointerof(winsize)) | |
return if ret < 0 | |
{winsize.ws_col.to_i32, winsize.ws_row.to_i32} | |
end | |
# Detect terminal size from tput utility | |
private def self.size_from_tput | |
return unless STDOUT.tty? | |
lines = `tput lines`.to_i? | |
cols = `tput cols`.to_i? | |
{cols, lines} | |
rescue | |
nil | |
end | |
# Detect terminal size from stty utility | |
private def self.size_from_stty | |
return unless STDOUT.tty? | |
parts = `stty size`.split(/\s+/) | |
return unless parts.size > 1 | |
lines, cols = parts.map(&.to_i?) | |
{cols, lines} | |
rescue | |
nil | |
end | |
{% end %} | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment