# Implementation inspired from https://github.com/crystal-term/screen/blob/master/src/term-screen.cr. module Reply::Term::Size extend self DEFAULT_SIZE = {80, 27} def width self.size[0] end def height self.size[1] end private def check_size(size) if size && (cols = size[0]) && (rows = size[1]) && cols != 0 && rows != 0 {cols, rows} end end {% if flag?(:win32) %} def size : {Int32, Int32} check_size(size_from_screen_buffer) || check_size(size_from_ansicon) || DEFAULT_SIZE end # Detect terminal size Windows GetConsoleScreenBufferInfo private def size_from_screen_buffer LibC.GetConsoleScreenBufferInfo(LibC.GetStdHandle(LibC::STD_OUTPUT_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 size_from_ansicon return unless ENV["ANSICON"]?.to_s =~ /\((.*)x(.*)\)/ rows, cols = [$2, $1].map(&.to_i) {cols, rows} end {% else %} def size : {Int32, Int32} size_from_ioctl(0) || # STDIN size_from_ioctl(1) || # STDOUT size_from_ioctl(2) || # STDERR check_size(size_from_tput) || check_size(size_from_stty) || check_size(size_from_env) || DEFAULT_SIZE end # Read terminal size from Unix ioctl private def 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 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 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 # Detect terminal size from environment private def size_from_env return unless ENV["COLUMNS"]?.to_s =~ /^\d+$/ rows = ENV["LINES"]? || ENV["ROWS"]? cols = ENV["COLUMNS"]? {cols.try &.to_i?, rows.try &.to_i?} end {% end %} end {% if flag?(:win32) %} lib LibC struct COORD x : Int16 y : Int16 end struct SMALL_RECT left : Int16 top : Int16 right : Int16 bottom : Int16 end struct CONSOLE_SCREEN_BUFFER_INFO dwSize : COORD dwCursorPosition : COORD wAttributes : UInt16 srWindow : SMALL_RECT dwMaximumWindowSize : COORD end fun GetConsoleScreenBufferInfo(hConsoleOutput : Void*, lpConsoleScreenBufferInfo : CONSOLE_SCREEN_BUFFER_INFO*) : Void end {% else %} lib LibC struct Winsize ws_row : UShort ws_col : UShort ws_xpixel : UShort ws_ypixel : UShort end # TIOCGWINSZ is a magic number passed to ioctl that requests the current # terminal window size. It is platform dependent (see # https://stackoverflow.com/a/4286840). {% begin %} {% if flag?(:darwin) || flag?(:bsd) %} TIOCGWINSZ = 0x40087468 {% elsif flag?(:unix) %} TIOCGWINSZ = 0x5413 {% end %} {% end %} {% if flag?(:android) %} fun ioctl(__fd : Int, __request : Int, ...) : Int {% else %} fun ioctl(fd : Int, request : ULong, ...) : Int {% end %} end {% end %}