Skip to content

Instantly share code, notes, and snippets.

@Blacksmoke16
Last active May 5, 2024 03:41
Show Gist options
  • Save Blacksmoke16/fc04a1b3ffe00d89b4237c66c8d94c35 to your computer and use it in GitHub Desktop.
Save Blacksmoke16/fc04a1b3ffe00d89b4237c66c8d94c35 to your computer and use it in GitHub Desktop.
OS Agnostic way to determine terminal height/width
{% 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 %}
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