/
term-screen.cr
167 lines (138 loc) · 3.73 KB
/
term-screen.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
require "./screen/version"
{% if flag?(:windows) %}
require "./screen/libs/winapi"
{% else %}
require "./screen/libs/libreadline"
require "ioctl"
{% end %}
module Term
module Screen
extend self
# Default terminal size
DEFAULT_SIZE = {27, 80}
class_property env : Hash(String, String) = ENV.to_h
class_property output : IO = STDERR
# Get terminal dimensions (rows, columns)
def size
{% if flag?(:windows) %}
check_size(size_from_win_api) ||
check_size(size_from_ansicon) ||
check_size(size_from_default) ||
size_from_default
{% else %}
size_from_ioctl(STDIN) ||
size_from_ioctl(STDOUT) ||
size_from_ioctl(STDERR) ||
check_size(size_from_tput) ||
check_size(size_from_readline) ||
check_size(size_from_stty) ||
check_size(size_from_env) ||
check_size(size_from_ansicon) ||
check_size(size_from_default) ||
size_from_default
{% end %}
end
def width
size[1]
end
def columns
width
end
def cols
width
end
def height
size[0]
end
def rows
height
end
def lines
height
end
# Default terminal size
def size_from_default
DEFAULT_SIZE
end
def size_from_win_api
LibC.GetConsoleScreenBufferInfo(LibC.GetStdHandle(LibC::STDOUT_HANDLE), out csbi)
rows = csbi.srWindow.right - csbi.srWindow.left + 1
cols = csbi.srWindow.bottom - csbi.srWindow.top + 1
{cols.to_i32, rows.to_i32}
end
# Detect terminal size from Windows ANSICON
def size_from_ansicon
return unless ENV["ANSICON"]?.to_s =~ /\((.*)x(.*)\)/
rows, cols = [$2, $1].map(&.to_i)
{cols, rows}
end
TIOCGWINSZ = 0x5413 # linux
TIOCGWINSZ_PPC = 0x40087468 # macos, freedbsd, netbsd, openbsd
TIOCGWINSZ_SOL = 0x5468 # solaris
# Read terminal size from Unix ioctl
def size_from_ioctl(file)
buffer = uninitialized LibC::Winsize
IOCTL.ioctl(file.fd, IOCTL::TIOCGWINSZ, pointerof(buffer))
{buffer.ws_row.to_i, buffer.ws_col.to_i}
rescue
nil
end
# Detect screen size using Readline
def size_from_readline
init_readline
LibReadline.get_screen_size(out rows, out cols)
{rows, cols}
end
# Detect terminal size from tput utility
def size_from_tput
return unless output.tty?
lines = `tput lines`.to_i?
cols = `tput cols`.to_i?
if lines && cols
{lines, cols}
else
nil
end
end
# Detect terminal size from stty utility
def size_from_stty
return unless output.tty?
parts = `stty size`.split(/\s+/)
return unless parts.size > 1
lines, cols = parts.map(&.to_i?)
return unless lines && cols
if lines && cols
{lines, cols}
else
nil
end
end
# Detect terminal size from environment
#
# After executing Crystal code if the user changes terminal
# dimensions during code runtime, the code won't be notified,
# and hence won't see the new dimensions reflected in its copy
# of LINES and COLUMNS environment variables.
def size_from_env
return unless env["COLUMNS"]?.to_s =~ /^\d+$/
rows = env["LINES"]? || env["ROWS"]?
cols = env["COLUMNS"]?
if (rows && rows.to_i?) && (cols && cols.to_i?)
size = {rows.to_i, cols.to_i}
else
nil
end
end
private def check_size(size)
if (size) && size[0] != 0 && size[1] != 0
return size
end
end
@@rl_initialized = false
private def init_readline
if !@@rl_initialized
LibReadline.rl_initialize
end
end
end
end