Skip to content

Commit 6a134b9

Browse files
committed
Add TerminalSize module
1 parent d9aab76 commit 6a134b9

4 files changed

Lines changed: 257 additions & 4 deletions

File tree

spec/terminal_size_spec.cr

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
require "./spec_helper"
2+
3+
describe UnicodePlot::TerminalSize do
4+
it "prefers ENV LINES/COLUMNS for displaysize" do
5+
old_lines = ENV["LINES"]?
6+
old_columns = ENV["COLUMNS"]?
7+
8+
ENV["LINES"] = "31"
9+
ENV["COLUMNS"] = "111"
10+
11+
begin
12+
UnicodePlot::TerminalSize.displaysize.should eq({31, 111})
13+
ensure
14+
if old_lines
15+
ENV["LINES"] = old_lines
16+
else
17+
ENV.delete("LINES")
18+
end
19+
if old_columns
20+
ENV["COLUMNS"] = old_columns
21+
else
22+
ENV.delete("COLUMNS")
23+
end
24+
end
25+
end
26+
27+
it "falls back to defaults when ENV is missing" do
28+
old_lines = ENV["LINES"]?
29+
old_columns = ENV["COLUMNS"]?
30+
31+
ENV.delete("LINES")
32+
ENV.delete("COLUMNS")
33+
34+
begin
35+
UnicodePlot::TerminalSize.displaysize.should eq({24, 80})
36+
ensure
37+
if old_lines
38+
ENV["LINES"] = old_lines
39+
else
40+
ENV.delete("LINES")
41+
end
42+
if old_columns
43+
ENV["COLUMNS"] = old_columns
44+
else
45+
ENV.delete("COLUMNS")
46+
end
47+
end
48+
end
49+
50+
it "falls back to defaults when ENV is invalid" do
51+
old_lines = ENV["LINES"]?
52+
old_columns = ENV["COLUMNS"]?
53+
54+
ENV["LINES"] = "0"
55+
ENV["COLUMNS"] = "abc"
56+
57+
begin
58+
UnicodePlot::TerminalSize.displaysize.should eq({24, 80})
59+
ensure
60+
if old_lines
61+
ENV["LINES"] = old_lines
62+
else
63+
ENV.delete("LINES")
64+
end
65+
if old_columns
66+
ENV["COLUMNS"] = old_columns
67+
else
68+
ENV.delete("COLUMNS")
69+
end
70+
end
71+
end
72+
73+
it "displaysize(io) falls back for non-tty IO" do
74+
old_lines = ENV["LINES"]?
75+
old_columns = ENV["COLUMNS"]?
76+
77+
ENV["LINES"] = "29"
78+
ENV["COLUMNS"] = "101"
79+
80+
begin
81+
io = IO::Memory.new
82+
UnicodePlot::TerminalSize.displaysize(io).should eq({29, 101})
83+
ensure
84+
if old_lines
85+
ENV["LINES"] = old_lines
86+
else
87+
ENV.delete("LINES")
88+
end
89+
if old_columns
90+
ENV["COLUMNS"] = old_columns
91+
else
92+
ENV.delete("COLUMNS")
93+
end
94+
end
95+
end
96+
end
97+
98+
describe UnicodePlot do
99+
it "out_stream_size without io uses TerminalSize.displaysize" do
100+
old_lines = ENV["LINES"]?
101+
old_columns = ENV["COLUMNS"]?
102+
103+
ENV["LINES"] = "27"
104+
ENV["COLUMNS"] = "99"
105+
106+
begin
107+
UnicodePlot.out_stream_size.should eq({27, 99})
108+
ensure
109+
if old_lines
110+
ENV["LINES"] = old_lines
111+
else
112+
ENV.delete("LINES")
113+
end
114+
if old_columns
115+
ENV["COLUMNS"] = old_columns
116+
else
117+
ENV.delete("COLUMNS")
118+
end
119+
end
120+
end
121+
122+
it "out_stream_height/width derive from out_stream_size" do
123+
old_lines = ENV["LINES"]?
124+
old_columns = ENV["COLUMNS"]?
125+
126+
ENV["LINES"] = "26"
127+
ENV["COLUMNS"] = "98"
128+
129+
begin
130+
UnicodePlot.out_stream_height.should eq(26)
131+
UnicodePlot.out_stream_width.should eq(98)
132+
ensure
133+
if old_lines
134+
ENV["LINES"] = old_lines
135+
else
136+
ENV.delete("LINES")
137+
end
138+
if old_columns
139+
ENV["COLUMNS"] = old_columns
140+
else
141+
ENV.delete("COLUMNS")
142+
end
143+
end
144+
end
145+
146+
it "out_stream_size(io) uses io overload and falls back" do
147+
old_lines = ENV["LINES"]?
148+
old_columns = ENV["COLUMNS"]?
149+
150+
ENV["LINES"] = "25"
151+
ENV["COLUMNS"] = "97"
152+
153+
begin
154+
io = IO::Memory.new
155+
UnicodePlot.out_stream_size(io).should eq({25, 97})
156+
ensure
157+
if old_lines
158+
ENV["LINES"] = old_lines
159+
else
160+
ENV.delete("LINES")
161+
end
162+
if old_columns
163+
ENV["COLUMNS"] = old_columns
164+
else
165+
ENV.delete("COLUMNS")
166+
end
167+
end
168+
end
169+
end

src/unicode_plot.cr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
require "./unicode_plot/terminal_size"
12
require "./unicode_plot/common"
23
require "./unicode_plot/color"
34
require "./unicode_plot/graphics"

src/unicode_plot/common.cr

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -333,11 +333,19 @@ module UnicodePlot
333333
default.empty? ? "#{name}(x)" : default
334334
end
335335

336-
def out_stream_size(_io : IO) : {Int32, Int32}
337-
{24, 80}
336+
def out_stream_size(io : IO? = nil) : {Int32, Int32}
337+
if io
338+
TerminalSize.displaysize(io)
339+
else
340+
TerminalSize.displaysize
341+
end
342+
end
343+
344+
def out_stream_height(io : IO? = nil) : Int32
345+
out_stream_size(io)[0]
338346
end
339347

340-
def out_stream_size : {Int32, Int32}
341-
{24, 80}
348+
def out_stream_width(io : IO? = nil) : Int32
349+
out_stream_size(io)[1]
342350
end
343351
end

src/unicode_plot/terminal_size.cr

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
module UnicodePlot
2+
module TerminalSize
3+
extend self
4+
5+
DEFAULT_HEIGHT = 24
6+
DEFAULT_WIDTH = 80
7+
8+
{% if flag?(:linux) %}
9+
TIOCGWINSZ = 0x5413_u64
10+
{% elsif flag?(:darwin) || flag?(:freebsd) %}
11+
TIOCGWINSZ = 0x40087468_u64
12+
{% end %}
13+
14+
{% if flag?(:linux) || flag?(:darwin) || flag?(:freebsd) %}
15+
lib LibTerminalSize
16+
struct Winsize
17+
ws_row : UInt16
18+
ws_col : UInt16
19+
ws_xpixel : UInt16
20+
ws_ypixel : UInt16
21+
end
22+
23+
fun ioctl(fd : Int32, request : ULong, ...) : Int32
24+
end
25+
{% end %}
26+
27+
def displaysize : {Int32, Int32}
28+
env_size || {DEFAULT_HEIGHT, DEFAULT_WIDTH}
29+
end
30+
31+
def displaysize(io : IO) : {Int32, Int32}
32+
tty_size(io) || displaysize
33+
end
34+
35+
private def env_size : {Int32, Int32}?
36+
rows = positive_env_int("LINES")
37+
cols = positive_env_int("COLUMNS")
38+
return {rows, cols} if rows && cols
39+
end
40+
41+
private def positive_env_int(name : String) : Int32?
42+
value = ENV[name]?.try(&.to_i?)
43+
return unless value
44+
return unless value > 0
45+
value
46+
end
47+
48+
private def tty_size(io : IO) : {Int32, Int32}?
49+
{% if flag?(:linux) || flag?(:darwin) || flag?(:freebsd) %}
50+
fdio = io.as?(IO::FileDescriptor)
51+
return nil unless fdio
52+
return nil unless fdio.tty?
53+
54+
size_from_fd(fdio.fd)
55+
{% else %}
56+
nil
57+
{% end %}
58+
rescue
59+
nil
60+
end
61+
62+
{% if flag?(:linux) || flag?(:darwin) || flag?(:freebsd) %}
63+
private def size_from_fd(fd : Int32) : {Int32, Int32}?
64+
ws = LibTerminalSize::Winsize.new
65+
rc = LibTerminalSize.ioctl(fd, TIOCGWINSZ, pointerof(ws))
66+
return nil unless rc == 0
67+
return nil if ws.ws_row == 0 || ws.ws_col == 0
68+
69+
{ws.ws_row.to_i32, ws.ws_col.to_i32}
70+
rescue
71+
nil
72+
end
73+
{% end %}
74+
end
75+
end

0 commit comments

Comments
 (0)