irb 1.13.2 → 1.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/README.md +13 -293
- data/Rakefile +4 -6
- data/irb.gemspec +5 -3
- data/lib/irb/color.rb +1 -0
- data/lib/irb/color_printer.rb +10 -9
- data/lib/irb/command/base.rb +12 -14
- data/lib/irb/command/cd.rb +51 -0
- data/lib/irb/command/copy.rb +73 -0
- data/lib/irb/command/debug.rb +8 -6
- data/lib/irb/command/help.rb +1 -1
- data/lib/irb/command/history.rb +1 -1
- data/lib/irb/command/internal_helpers.rb +1 -1
- data/lib/irb/command/ls.rb +23 -11
- data/lib/irb/completion.rb +41 -14
- data/lib/irb/context.rb +131 -54
- data/lib/irb/debug/ui.rb +2 -4
- data/lib/irb/debug.rb +7 -10
- data/lib/irb/default_commands.rb +22 -8
- data/lib/irb/easter-egg.rb +10 -6
- data/lib/irb/history.rb +84 -55
- data/lib/irb/init.rb +4 -2
- data/lib/irb/input-method.rb +31 -24
- data/lib/irb/inspector.rb +42 -36
- data/lib/irb/lc/ja/help-message +4 -4
- data/lib/irb/nesting_parser.rb +196 -194
- data/lib/irb/pager.rb +129 -7
- data/lib/irb/ruby-lex.rb +84 -82
- data/lib/irb/ruby_logo.aa +75 -37
- data/lib/irb/source_finder.rb +3 -4
- data/lib/irb/statement.rb +22 -1
- data/lib/irb/version.rb +2 -2
- data/lib/irb/workspace.rb +13 -31
- data/lib/irb.rb +75 -947
- data/man/irb.1 +37 -2
- metadata +20 -8
- data/.document +0 -4
data/lib/irb/nesting_parser.rb
CHANGED
@@ -3,235 +3,237 @@ module IRB
|
|
3
3
|
module NestingParser
|
4
4
|
IGNORE_TOKENS = %i[on_sp on_ignored_nl on_comment on_embdoc_beg on_embdoc on_embdoc_end]
|
5
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
|
-
|
6
|
+
class << self
|
7
|
+
# Scan each token and call the given block with array of token and other information for parsing
|
8
|
+
def scan_opens(tokens)
|
9
|
+
opens = []
|
10
|
+
pending_heredocs = []
|
11
|
+
first_token_on_line = true
|
12
|
+
tokens.each do |t|
|
13
|
+
skip = false
|
14
|
+
last_tok, state, args = opens.last
|
15
|
+
case state
|
16
|
+
when :in_alias_undef
|
17
|
+
skip = t.event == :on_kw
|
18
|
+
when :in_unquoted_symbol
|
19
|
+
unless IGNORE_TOKENS.include?(t.event)
|
20
|
+
opens.pop
|
21
|
+
skip = true
|
22
|
+
end
|
23
|
+
when :in_lambda_head
|
24
|
+
opens.pop if t.event == :on_tlambeg || (t.event == :on_kw && t.tok == 'do')
|
25
|
+
when :in_method_head
|
26
|
+
unless IGNORE_TOKENS.include?(t.event)
|
27
|
+
next_args = []
|
28
|
+
body = nil
|
29
|
+
if args.include?(:receiver)
|
30
|
+
case t.event
|
31
|
+
when :on_lparen, :on_ivar, :on_gvar, :on_cvar
|
32
|
+
# def (receiver). | def @ivar. | def $gvar. | def @@cvar.
|
33
|
+
next_args << :dot
|
34
|
+
when :on_kw
|
35
|
+
case t.tok
|
36
|
+
when 'self', 'true', 'false', 'nil'
|
37
|
+
# def self(arg) | def self.
|
38
|
+
next_args.push(:arg, :dot)
|
39
|
+
else
|
40
|
+
# def if(arg)
|
41
|
+
skip = true
|
42
|
+
next_args << :arg
|
43
|
+
end
|
44
|
+
when :on_op, :on_backtick
|
45
|
+
# def +(arg)
|
40
46
|
skip = true
|
41
47
|
next_args << :arg
|
48
|
+
when :on_ident, :on_const
|
49
|
+
# def a(arg) | def a.
|
50
|
+
next_args.push(:arg, :dot)
|
42
51
|
end
|
43
|
-
when :on_op, :on_backtick
|
44
|
-
# def +(arg)
|
45
|
-
skip = true
|
46
|
-
next_args << :arg
|
47
|
-
when :on_ident, :on_const
|
48
|
-
# def a(arg) | def a.
|
49
|
-
next_args.push(:arg, :dot)
|
50
52
|
end
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
next_args << :name if t.event == :on_period || (t.event == :on_op && t.tok == '::')
|
55
|
-
end
|
56
|
-
if args.include?(:name)
|
57
|
-
if %i[on_ident on_const on_op on_kw on_backtick].include?(t.event)
|
58
|
-
# def name(arg) | def receiver.name(arg)
|
59
|
-
next_args << :arg
|
60
|
-
skip = true
|
53
|
+
if args.include?(:dot)
|
54
|
+
# def receiver.name
|
55
|
+
next_args << :name if t.event == :on_period || (t.event == :on_op && t.tok == '::')
|
61
56
|
end
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
57
|
+
if args.include?(:name)
|
58
|
+
if %i[on_ident on_const on_op on_kw on_backtick].include?(t.event)
|
59
|
+
# def name(arg) | def receiver.name(arg)
|
60
|
+
next_args << :arg
|
61
|
+
skip = true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
if args.include?(:arg)
|
65
|
+
case t.event
|
66
|
+
when :on_nl, :on_semicolon
|
67
|
+
# def receiver.f;
|
68
|
+
body = :normal
|
69
|
+
when :on_lparen
|
70
|
+
# def receiver.f()
|
71
|
+
next_args << :eq
|
72
|
+
else
|
73
|
+
if t.event == :on_op && t.tok == '='
|
74
|
+
# def receiver.f =
|
75
|
+
body = :oneliner
|
76
|
+
else
|
77
|
+
# def receiver.f arg
|
78
|
+
next_args << :arg_without_paren
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
if args.include?(:eq)
|
72
83
|
if t.event == :on_op && t.tok == '='
|
73
|
-
# def receiver.f =
|
74
84
|
body = :oneliner
|
75
85
|
else
|
76
|
-
|
77
|
-
next_args << :arg_without_paren
|
86
|
+
body = :normal
|
78
87
|
end
|
79
88
|
end
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
89
|
+
if args.include?(:arg_without_paren)
|
90
|
+
if %i[on_semicolon on_nl].include?(t.event)
|
91
|
+
# def f a;
|
92
|
+
body = :normal
|
93
|
+
else
|
94
|
+
# def f a, b
|
95
|
+
next_args << :arg_without_paren
|
96
|
+
end
|
86
97
|
end
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
body = :normal
|
98
|
+
if body == :oneliner
|
99
|
+
opens.pop
|
100
|
+
elsif body
|
101
|
+
opens[-1] = [last_tok, nil]
|
92
102
|
else
|
93
|
-
|
94
|
-
next_args << :arg_without_paren
|
103
|
+
opens[-1] = [last_tok, :in_method_head, next_args]
|
95
104
|
end
|
96
105
|
end
|
97
|
-
|
98
|
-
|
99
|
-
|
106
|
+
when :in_for_while_until_condition
|
107
|
+
if t.event == :on_semicolon || t.event == :on_nl || (t.event == :on_kw && t.tok == 'do')
|
108
|
+
skip = true if t.event == :on_kw && t.tok == 'do'
|
100
109
|
opens[-1] = [last_tok, nil]
|
101
|
-
else
|
102
|
-
opens[-1] = [last_tok, :in_method_head, next_args]
|
103
110
|
end
|
104
111
|
end
|
105
|
-
when :in_for_while_until_condition
|
106
|
-
if t.event == :on_semicolon || t.event == :on_nl || (t.event == :on_kw && t.tok == 'do')
|
107
|
-
skip = true if t.event == :on_kw && t.tok == 'do'
|
108
|
-
opens[-1] = [last_tok, nil]
|
109
|
-
end
|
110
|
-
end
|
111
112
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
opens << [t, nil]
|
118
|
-
when 'end'
|
119
|
-
opens.pop
|
120
|
-
when 'def'
|
121
|
-
opens << [t, :in_method_head, [:receiver, :name]]
|
122
|
-
when 'if', 'unless'
|
123
|
-
unless t.state.allbits?(Ripper::EXPR_LABEL)
|
113
|
+
unless skip
|
114
|
+
case t.event
|
115
|
+
when :on_kw
|
116
|
+
case t.tok
|
117
|
+
when 'begin', 'class', 'module', 'do', 'case'
|
124
118
|
opens << [t, nil]
|
125
|
-
end
|
126
|
-
|
127
|
-
|
128
|
-
opens << [t, :
|
129
|
-
|
130
|
-
|
131
|
-
|
119
|
+
when 'end'
|
120
|
+
opens.pop
|
121
|
+
when 'def'
|
122
|
+
opens << [t, :in_method_head, [:receiver, :name]]
|
123
|
+
when 'if', 'unless'
|
124
|
+
unless t.state.allbits?(Ripper::EXPR_LABEL)
|
125
|
+
opens << [t, nil]
|
126
|
+
end
|
127
|
+
when 'while', 'until'
|
128
|
+
unless t.state.allbits?(Ripper::EXPR_LABEL)
|
129
|
+
opens << [t, :in_for_while_until_condition]
|
130
|
+
end
|
131
|
+
when 'ensure', 'rescue'
|
132
|
+
unless t.state.allbits?(Ripper::EXPR_LABEL)
|
133
|
+
opens.pop
|
134
|
+
opens << [t, nil]
|
135
|
+
end
|
136
|
+
when 'alias'
|
137
|
+
opens << [t, :in_alias_undef, 2]
|
138
|
+
when 'undef'
|
139
|
+
opens << [t, :in_alias_undef, 1]
|
140
|
+
when 'elsif', 'else', 'when'
|
132
141
|
opens.pop
|
133
142
|
opens << [t, nil]
|
143
|
+
when 'for'
|
144
|
+
opens << [t, :in_for_while_until_condition]
|
145
|
+
when 'in'
|
146
|
+
if last_tok&.event == :on_kw && %w[case in].include?(last_tok.tok) && first_token_on_line
|
147
|
+
opens.pop
|
148
|
+
opens << [t, nil]
|
149
|
+
end
|
134
150
|
end
|
135
|
-
when
|
136
|
-
opens << [t, :
|
137
|
-
when
|
138
|
-
opens << [t,
|
139
|
-
when
|
151
|
+
when :on_tlambda
|
152
|
+
opens << [t, :in_lambda_head]
|
153
|
+
when :on_lparen, :on_lbracket, :on_lbrace, :on_tlambeg, :on_embexpr_beg, :on_embdoc_beg
|
154
|
+
opens << [t, nil]
|
155
|
+
when :on_rparen, :on_rbracket, :on_rbrace, :on_embexpr_end, :on_embdoc_end
|
156
|
+
opens.pop
|
157
|
+
when :on_heredoc_beg
|
158
|
+
pending_heredocs << t
|
159
|
+
when :on_heredoc_end
|
140
160
|
opens.pop
|
161
|
+
when :on_backtick
|
162
|
+
opens << [t, nil] unless t.state == Ripper::EXPR_ARG
|
163
|
+
when :on_tstring_beg, :on_words_beg, :on_qwords_beg, :on_symbols_beg, :on_qsymbols_beg, :on_regexp_beg
|
141
164
|
opens << [t, nil]
|
142
|
-
when
|
143
|
-
opens
|
144
|
-
when
|
145
|
-
if
|
146
|
-
opens
|
165
|
+
when :on_tstring_end, :on_regexp_end, :on_label_end
|
166
|
+
opens.pop
|
167
|
+
when :on_symbeg
|
168
|
+
if t.tok == ':'
|
169
|
+
opens << [t, :in_unquoted_symbol]
|
170
|
+
else
|
147
171
|
opens << [t, nil]
|
148
172
|
end
|
149
173
|
end
|
150
|
-
when :on_tlambda
|
151
|
-
opens << [t, :in_lambda_head]
|
152
|
-
when :on_lparen, :on_lbracket, :on_lbrace, :on_tlambeg, :on_embexpr_beg, :on_embdoc_beg
|
153
|
-
opens << [t, nil]
|
154
|
-
when :on_rparen, :on_rbracket, :on_rbrace, :on_embexpr_end, :on_embdoc_end
|
155
|
-
opens.pop
|
156
|
-
when :on_heredoc_beg
|
157
|
-
pending_heredocs << t
|
158
|
-
when :on_heredoc_end
|
159
|
-
opens.pop
|
160
|
-
when :on_backtick
|
161
|
-
opens << [t, nil] if t.state.allbits?(Ripper::EXPR_BEG)
|
162
|
-
when :on_tstring_beg, :on_words_beg, :on_qwords_beg, :on_symbols_beg, :on_qsymbols_beg, :on_regexp_beg
|
163
|
-
opens << [t, nil]
|
164
|
-
when :on_tstring_end, :on_regexp_end, :on_label_end
|
165
|
-
opens.pop
|
166
|
-
when :on_symbeg
|
167
|
-
if t.tok == ':'
|
168
|
-
opens << [t, :in_unquoted_symbol]
|
169
|
-
else
|
170
|
-
opens << [t, nil]
|
171
|
-
end
|
172
174
|
end
|
175
|
+
if t.event == :on_nl || t.event == :on_semicolon
|
176
|
+
first_token_on_line = true
|
177
|
+
elsif t.event != :on_sp
|
178
|
+
first_token_on_line = false
|
179
|
+
end
|
180
|
+
if pending_heredocs.any? && t.tok.include?("\n")
|
181
|
+
pending_heredocs.reverse_each { |t| opens << [t, nil] }
|
182
|
+
pending_heredocs = []
|
183
|
+
end
|
184
|
+
if opens.last && opens.last[1] == :in_alias_undef && !IGNORE_TOKENS.include?(t.event) && t.event != :on_heredoc_end
|
185
|
+
tok, state, arg = opens.pop
|
186
|
+
opens << [tok, state, arg - 1] if arg >= 1
|
187
|
+
end
|
188
|
+
yield t, opens if block_given?
|
173
189
|
end
|
174
|
-
|
175
|
-
first_token_on_line = true
|
176
|
-
elsif t.event != :on_sp
|
177
|
-
first_token_on_line = false
|
178
|
-
end
|
179
|
-
if pending_heredocs.any? && t.tok.include?("\n")
|
180
|
-
pending_heredocs.reverse_each { |t| opens << [t, nil] }
|
181
|
-
pending_heredocs = []
|
182
|
-
end
|
183
|
-
if opens.last && opens.last[1] == :in_alias_undef && !IGNORE_TOKENS.include?(t.event) && t.event != :on_heredoc_end
|
184
|
-
tok, state, arg = opens.pop
|
185
|
-
opens << [tok, state, arg - 1] if arg >= 1
|
186
|
-
end
|
187
|
-
yield t, opens if block_given?
|
190
|
+
opens.map(&:first) + pending_heredocs.reverse
|
188
191
|
end
|
189
|
-
opens.map(&:first) + pending_heredocs.reverse
|
190
|
-
end
|
191
192
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
193
|
+
def open_tokens(tokens)
|
194
|
+
# scan_opens without block will return a list of open tokens at last token position
|
195
|
+
scan_opens(tokens)
|
196
|
+
end
|
196
197
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
198
|
+
# Calculates token information [line_tokens, prev_opens, next_opens, min_depth] for each line.
|
199
|
+
# Example code
|
200
|
+
# ["hello
|
201
|
+
# world"+(
|
202
|
+
# First line
|
203
|
+
# line_tokens: [[lbracket, '['], [tstring_beg, '"'], [tstring_content("hello\nworld"), "hello\n"]]
|
204
|
+
# prev_opens: []
|
205
|
+
# next_tokens: [lbracket, tstring_beg]
|
206
|
+
# min_depth: 0 (minimum at beginning of line)
|
207
|
+
# Second line
|
208
|
+
# line_tokens: [[tstring_content("hello\nworld"), "world"], [tstring_end, '"'], [op, '+'], [lparen, '(']]
|
209
|
+
# prev_opens: [lbracket, tstring_beg]
|
210
|
+
# next_tokens: [lbracket, lparen]
|
211
|
+
# min_depth: 1 (minimum just after tstring_end)
|
212
|
+
def parse_by_line(tokens)
|
213
|
+
line_tokens = []
|
214
|
+
prev_opens = []
|
215
|
+
min_depth = 0
|
216
|
+
output = []
|
217
|
+
last_opens = scan_opens(tokens) do |t, opens|
|
218
|
+
depth = t == opens.last&.first ? opens.size - 1 : opens.size
|
219
|
+
min_depth = depth if depth < min_depth
|
220
|
+
if t.tok.include?("\n")
|
221
|
+
t.tok.each_line do |line|
|
222
|
+
line_tokens << [t, line]
|
223
|
+
next if line[-1] != "\n"
|
224
|
+
next_opens = opens.map(&:first)
|
225
|
+
output << [line_tokens, prev_opens, next_opens, min_depth]
|
226
|
+
prev_opens = next_opens
|
227
|
+
min_depth = prev_opens.size
|
228
|
+
line_tokens = []
|
229
|
+
end
|
230
|
+
else
|
231
|
+
line_tokens << [t, t.tok]
|
228
232
|
end
|
229
|
-
else
|
230
|
-
line_tokens << [t, t.tok]
|
231
233
|
end
|
234
|
+
output << [line_tokens, prev_opens, last_opens, min_depth] if line_tokens.any?
|
235
|
+
output
|
232
236
|
end
|
233
|
-
output << [line_tokens, prev_opens, last_opens, min_depth] if line_tokens.any?
|
234
|
-
output
|
235
237
|
end
|
236
238
|
end
|
237
239
|
end
|
data/lib/irb/pager.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'reline'
|
4
|
+
|
3
5
|
module IRB
|
4
6
|
# The implementation of this class is borrowed from RDoc's lib/rdoc/ri/driver.rb.
|
5
7
|
# Please do NOT use this class directly outside of IRB.
|
@@ -33,17 +35,56 @@ module IRB
|
|
33
35
|
# the `IRB::Abort` exception only interrupts IRB's execution but doesn't affect the pager
|
34
36
|
# So to properly terminate the pager with Ctrl-C, we need to catch `IRB::Abort` and kill the pager process
|
35
37
|
rescue IRB::Abort
|
36
|
-
|
38
|
+
begin
|
39
|
+
begin
|
40
|
+
Process.kill("TERM", pid) if pid
|
41
|
+
rescue Errno::EINVAL
|
42
|
+
# SIGTERM not supported (windows)
|
43
|
+
Process.kill("KILL", pid)
|
44
|
+
end
|
45
|
+
rescue Errno::ESRCH
|
46
|
+
# Pager process already terminated
|
47
|
+
end
|
37
48
|
nil
|
38
49
|
rescue Errno::EPIPE
|
39
50
|
end
|
40
51
|
|
41
|
-
private
|
42
|
-
|
43
52
|
def should_page?
|
44
53
|
IRB.conf[:USE_PAGER] && STDIN.tty? && (ENV.key?("TERM") && ENV["TERM"] != "dumb")
|
45
54
|
end
|
46
55
|
|
56
|
+
def page_with_preview(width, height, formatter_proc)
|
57
|
+
overflow_callback = ->(lines) do
|
58
|
+
modified_output = formatter_proc.call(lines.join, true)
|
59
|
+
content, = take_first_page(width, [height - 2, 0].max) {|o| o.write modified_output }
|
60
|
+
content = content.chomp
|
61
|
+
content = "#{content}\e[0m" if Color.colorable?
|
62
|
+
$stdout.puts content
|
63
|
+
$stdout.puts 'Preparing full inspection value...'
|
64
|
+
end
|
65
|
+
out = PageOverflowIO.new(width, height, overflow_callback, delay: 0.1)
|
66
|
+
yield out
|
67
|
+
content = formatter_proc.call(out.string, out.multipage?)
|
68
|
+
if out.multipage?
|
69
|
+
page(retain_content: true) do |io|
|
70
|
+
io.puts content
|
71
|
+
end
|
72
|
+
else
|
73
|
+
$stdout.puts content
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def take_first_page(width, height)
|
78
|
+
overflow_callback = proc do |lines|
|
79
|
+
return lines.join, true
|
80
|
+
end
|
81
|
+
out = Pager::PageOverflowIO.new(width, height, overflow_callback)
|
82
|
+
yield out
|
83
|
+
[out.string, false]
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
47
88
|
def content_exceeds_screen_height?(content)
|
48
89
|
screen_height, screen_width = begin
|
49
90
|
Reline.get_screen_size
|
@@ -53,10 +94,10 @@ module IRB
|
|
53
94
|
|
54
95
|
pageable_height = screen_height - 3 # leave some space for previous and the current prompt
|
55
96
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
97
|
+
return true if content.lines.size > pageable_height
|
98
|
+
|
99
|
+
_, overflow = take_first_page(screen_width, pageable_height) {|out| out.write content }
|
100
|
+
overflow
|
60
101
|
end
|
61
102
|
|
62
103
|
def setup_pager(retain_content:)
|
@@ -87,5 +128,86 @@ module IRB
|
|
87
128
|
nil
|
88
129
|
end
|
89
130
|
end
|
131
|
+
|
132
|
+
# Writable IO that has page overflow callback
|
133
|
+
class PageOverflowIO
|
134
|
+
attr_reader :string, :first_page_lines
|
135
|
+
|
136
|
+
# Maximum size of a single cell in terminal
|
137
|
+
# Assumed worst case: "\e[1;3;4;9;38;2;255;128;128;48;2;128;128;255mA\e[0m"
|
138
|
+
# bold, italic, underline, crossed_out, RGB forgound, RGB background
|
139
|
+
MAX_CHAR_PER_CELL = 50
|
140
|
+
|
141
|
+
def initialize(width, height, overflow_callback, delay: nil)
|
142
|
+
@lines = []
|
143
|
+
@first_page_lines = nil
|
144
|
+
@width = width
|
145
|
+
@height = height
|
146
|
+
@buffer = +''
|
147
|
+
@overflow_callback = overflow_callback
|
148
|
+
@col = 0
|
149
|
+
@string = +''
|
150
|
+
@multipage = false
|
151
|
+
@delay_until = (Time.now + delay if delay)
|
152
|
+
end
|
153
|
+
|
154
|
+
def puts(text = '')
|
155
|
+
text = text.to_s unless text.is_a?(String)
|
156
|
+
write(text)
|
157
|
+
write("\n") unless text.end_with?("\n")
|
158
|
+
end
|
159
|
+
|
160
|
+
def write(text)
|
161
|
+
text = text.to_s unless text.is_a?(String)
|
162
|
+
@string << text
|
163
|
+
if @multipage
|
164
|
+
if @delay_until && Time.now > @delay_until
|
165
|
+
@overflow_callback.call(@first_page_lines)
|
166
|
+
@delay_until = nil
|
167
|
+
end
|
168
|
+
return
|
169
|
+
end
|
170
|
+
|
171
|
+
overflow_size = (@width * (@height - @lines.size) + @width - @col) * MAX_CHAR_PER_CELL
|
172
|
+
if text.size >= overflow_size
|
173
|
+
text = text[0, overflow_size]
|
174
|
+
overflow = true
|
175
|
+
end
|
176
|
+
@buffer << text
|
177
|
+
@col += Reline::Unicode.calculate_width(text, true)
|
178
|
+
if text.include?("\n") || @col >= @width
|
179
|
+
@buffer.lines.each do |line|
|
180
|
+
wrapped_lines = Reline::Unicode.split_by_width(line.chomp, @width).first.compact
|
181
|
+
wrapped_lines.pop if wrapped_lines.last == ''
|
182
|
+
@lines.concat(wrapped_lines)
|
183
|
+
if line.end_with?("\n")
|
184
|
+
if @lines.empty? || @lines.last.end_with?("\n")
|
185
|
+
@lines << "\n"
|
186
|
+
else
|
187
|
+
@lines[-1] += "\n"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
@buffer.clear
|
192
|
+
@buffer << @lines.pop if !@lines.empty? && !@lines.last.end_with?("\n")
|
193
|
+
@col = Reline::Unicode.calculate_width(@buffer, true)
|
194
|
+
end
|
195
|
+
if overflow || @lines.size > @height || (@lines.size == @height && @col > 0)
|
196
|
+
@first_page_lines = @lines.take(@height)
|
197
|
+
if !@delay_until || Time.now > @delay_until
|
198
|
+
@overflow_callback.call(@first_page_lines)
|
199
|
+
@delay_until = nil
|
200
|
+
end
|
201
|
+
@multipage = true
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def multipage?
|
206
|
+
@multipage
|
207
|
+
end
|
208
|
+
|
209
|
+
alias print write
|
210
|
+
alias << write
|
211
|
+
end
|
90
212
|
end
|
91
213
|
end
|