katakata_irb 0.1.0

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.
@@ -0,0 +1,25 @@
1
+ diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb
2
+ index bbf5e6c..c9e613e 100644
3
+ --- a/lib/reline/line_editor.rb
4
+ +++ b/lib/reline/line_editor.rb
5
+ @@ -1707,17 +1707,18 @@ class Reline::LineEditor
6
+ end
7
+ new_lines = whole_lines
8
+ new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
9
+ - new_indent = @cursor_max if new_indent&.> @cursor_max
10
+ if new_indent&.>= 0
11
+ md = new_lines[@line_index].match(/\A */)
12
+ prev_indent = md[0].count(' ')
13
+ if @check_new_auto_indent
14
+ - @buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
15
+ + line = @buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
16
+ @cursor = new_indent
17
+ + @cursor_max = calculate_width(line)
18
+ @byte_pointer = new_indent
19
+ else
20
+ @line = ' ' * new_indent + @line.lstrip
21
+ @cursor += new_indent - prev_indent
22
+ + @cursor_max = calculate_width(@line)
23
+ @byte_pointer += new_indent - prev_indent
24
+ end
25
+ end
@@ -0,0 +1,95 @@
1
+ diff --git a/lib/reline.rb b/lib/reline.rb
2
+ index f22b573..8716a0c 100644
3
+ --- a/lib/reline.rb
4
+ +++ b/lib/reline.rb
5
+ @@ -281,19 +281,21 @@ module Reline
6
+ Reline::DEFAULT_DIALOG_CONTEXT = Array.new
7
+
8
+ def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
9
+ - unless confirm_multiline_termination
10
+ - raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
11
+ - end
12
+ - inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
13
+ + Reline::IOGate.with_raw_input do
14
+ + unless confirm_multiline_termination
15
+ + raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
16
+ + end
17
+ + inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
18
+
19
+ - whole_buffer = line_editor.whole_buffer.dup
20
+ - whole_buffer.taint if RUBY_VERSION < '2.7'
21
+ - if add_hist and whole_buffer and whole_buffer.chomp("\n").size > 0
22
+ - Reline::HISTORY << whole_buffer
23
+ - end
24
+ + whole_buffer = line_editor.whole_buffer.dup
25
+ + whole_buffer.taint if RUBY_VERSION < '2.7'
26
+ + if add_hist and whole_buffer and whole_buffer.chomp("\n").size > 0
27
+ + Reline::HISTORY << whole_buffer
28
+ + end
29
+
30
+ - line_editor.reset_line if line_editor.whole_buffer.nil?
31
+ - whole_buffer
32
+ + line_editor.reset_line if line_editor.whole_buffer.nil?
33
+ + whole_buffer
34
+ + end
35
+ end
36
+
37
+ def readline(prompt = '', add_hist = false)
38
+ diff --git a/lib/reline/ansi.rb b/lib/reline/ansi.rb
39
+ index ab147a6..ccebe15 100644
40
+ --- a/lib/reline/ansi.rb
41
+ +++ b/lib/reline/ansi.rb
42
+ @@ -142,6 +142,10 @@ class Reline::ANSI
43
+ @@output = val
44
+ end
45
+
46
+ + def self.with_raw_input
47
+ + @@input.raw { yield }
48
+ + end
49
+ +
50
+ @@buf = []
51
+ def self.inner_getc
52
+ unless @@buf.empty?
53
+ diff --git a/lib/reline/general_io.rb b/lib/reline/general_io.rb
54
+ index 92c76cb..9929846 100644
55
+ --- a/lib/reline/general_io.rb
56
+ +++ b/lib/reline/general_io.rb
57
+ @@ -31,6 +31,10 @@ class Reline::GeneralIO
58
+ @@input = val
59
+ end
60
+
61
+ + def self.with_raw_input
62
+ + yield
63
+ + end
64
+ +
65
+ def self.getc
66
+ unless @@buf.empty?
67
+ return @@buf.shift
68
+ diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb
69
+ index 6acf969..e3985a3 100644
70
+ --- a/lib/reline/line_editor.rb
71
+ +++ b/lib/reline/line_editor.rb
72
+ @@ -452,7 +452,7 @@ class Reline::LineEditor
73
+ new_lines = whole_lines
74
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
75
+ modify_lines(new_lines).each_with_index do |line, index|
76
+ - @output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\n"
77
+ + @output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\r\n"
78
+ Reline::IOGate.erase_after_cursor
79
+ end
80
+ @output.flush
81
+ diff --git a/lib/reline/windows.rb b/lib/reline/windows.rb
82
+ index b952329..7ea2a00 100644
83
+ --- a/lib/reline/windows.rb
84
+ +++ b/lib/reline/windows.rb
85
+ @@ -291,6 +291,10 @@ class Reline::Windows
86
+ end
87
+ end
88
+
89
+ + def self.with_raw_input
90
+ + yield
91
+ + end
92
+ +
93
+ def self.getc
94
+ check_input_event
95
+ @@output_buf.shift
@@ -0,0 +1,43 @@
1
+ diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb
2
+ index e3985a3..8d785db 100644
3
+ --- a/lib/reline/line_editor.rb
4
+ +++ b/lib/reline/line_editor.rb
5
+ @@ -680,6 +680,9 @@ class Reline::LineEditor
6
+ end
7
+ height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT
8
+ height = dialog.contents.size if dialog.contents.size < height
9
+ + if dialog.scroll_top >= dialog.contents.size - height
10
+ + dialog.scroll_top = dialog.contents.size - height
11
+ + end
12
+ if dialog.contents.size > height
13
+ if dialog.pointer
14
+ if dialog.pointer < 0
15
+ @@ -690,17 +693,20 @@ class Reline::LineEditor
16
+ dialog.scroll_top = dialog.pointer
17
+ end
18
+ pointer = dialog.pointer - dialog.scroll_top
19
+ + else
20
+ + dialog.scroll_top = 0
21
+ end
22
+ dialog.contents = dialog.contents[dialog.scroll_top, height]
23
+ end
24
+ - if dialog.contents and dialog.scroll_top >= dialog.contents.size
25
+ - dialog.scroll_top = dialog.contents.size - height
26
+ + if dialog_render_info.contents and dialog.scroll_top > dialog_render_info.contents.size - height
27
+ + dialog.scroll_top = dialog_render_info.contents.size - height
28
+ end
29
+ if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
30
+ bar_max_height = height * 2
31
+ moving_distance = (dialog_render_info.contents.size - height) * 2
32
+ position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance)
33
+ bar_height = (bar_max_height * ((dialog.contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
34
+ + bar_height = 1 if bar_height.zero?
35
+ dialog.scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
36
+ else
37
+ dialog.scrollbar_pos = nil
38
+ @@ -745,4 +751,4 @@ class Reline::LineEditor
39
+ - if dialog.scrollbar_pos and (dialog.scrollbar_pos != old_dialog.scrollbar_pos or dialog.column != old_dialog.column)
40
+ + if dialog.scrollbar_pos
41
+ @output.write "\e[37m"
42
+ if dialog.scrollbar_pos <= (i * 2) and (i * 2 + 1) < (dialog.scrollbar_pos + bar_height)
43
+ @output.write @full_block
@@ -0,0 +1,102 @@
1
+ diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb
2
+ index 8153aab..1c33a4b 100644
3
+ --- a/lib/reline/line_editor.rb
4
+ +++ b/lib/reline/line_editor.rb
5
+ @@ -449,12 +449,8 @@ class Reline::LineEditor
6
+ Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
7
+ Reline::IOGate.move_cursor_column(0)
8
+ @scroll_partial_screen = nil
9
+ - prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
10
+ - if @previous_line_index
11
+ - new_lines = whole_lines(index: @previous_line_index, line: @line)
12
+ - else
13
+ - new_lines = whole_lines
14
+ - end
15
+ + new_lines = whole_lines
16
+ + prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
17
+ modify_lines(new_lines).each_with_index do |line, index|
18
+ @output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\n"
19
+ Reline::IOGate.erase_after_cursor
20
+ @@ -490,11 +486,7 @@ class Reline::LineEditor
21
+ if @is_multiline
22
+ if finished?
23
+ # Always rerender on finish because output_modifier_proc may return a different output.
24
+ - if @previous_line_index
25
+ - new_lines = whole_lines(index: @previous_line_index, line: @line)
26
+ - else
27
+ - new_lines = whole_lines
28
+ - end
29
+ + new_lines = whole_lines
30
+ line = modify_lines(new_lines)[@line_index]
31
+ clear_dialog
32
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
33
+ @@ -1013,11 +1005,7 @@ class Reline::LineEditor
34
+ end
35
+
36
+ private def rerender_changed_current_line
37
+ - if @previous_line_index
38
+ - new_lines = whole_lines(index: @previous_line_index, line: @line)
39
+ - else
40
+ - new_lines = whole_lines
41
+ - end
42
+ + new_lines = whole_lines
43
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
44
+ all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
45
+ diff = all_height - @highest_in_all
46
+ @@ -1698,7 +1686,7 @@ class Reline::LineEditor
47
+ return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
48
+ if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
49
+ # Fix indent of a line when a newline is inserted to the next
50
+ - new_lines = whole_lines(index: @previous_line_index, line: @line)
51
+ + new_lines = whole_lines
52
+ new_indent = @auto_indent_proc.(new_lines[0..-3].push(''), @line_index - 1, 0, true)
53
+ md = @line.match(/\A */)
54
+ prev_indent = md[0].count(' ')
55
+ @@ -1713,11 +1701,7 @@ class Reline::LineEditor
56
+ @line = ' ' * new_indent + @line.lstrip
57
+ end
58
+ end
59
+ - if @previous_line_index
60
+ - new_lines = whole_lines(index: @previous_line_index, line: @line)
61
+ - else
62
+ - new_lines = whole_lines
63
+ - end
64
+ + new_lines = whole_lines
65
+ new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
66
+ new_indent = @cursor_max if new_indent&.> @cursor_max
67
+ if new_indent&.>= 0
68
+ @@ -1803,11 +1787,7 @@ class Reline::LineEditor
69
+ target = before
70
+ end
71
+ if @is_multiline
72
+ - if @previous_line_index
73
+ - lines = whole_lines(index: @previous_line_index, line: @line)
74
+ - else
75
+ - lines = whole_lines
76
+ - end
77
+ + lines = whole_lines
78
+ if @line_index > 0
79
+ preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
80
+ end
81
+ @@ -1907,7 +1887,7 @@ class Reline::LineEditor
82
+ @cursor_max = calculate_width(@line)
83
+ end
84
+
85
+ - def whole_lines(index: @line_index, line: @line)
86
+ + def whole_lines(index: @previous_line_index || @line_index, line: @line)
87
+ temp_lines = @buffer_of_lines.dup
88
+ temp_lines[index] = line
89
+ temp_lines
90
+ @@ -1917,11 +1897,7 @@ class Reline::LineEditor
91
+ if @buffer_of_lines.size == 1 and @line.nil?
92
+ nil
93
+ else
94
+ - if @previous_line_index
95
+ - whole_lines(index: @previous_line_index, line: @line).join("\n")
96
+ - else
97
+ - whole_lines.join("\n")
98
+ - end
99
+ + whole_lines.join("\n")
100
+ end
101
+ end
102
+
@@ -0,0 +1,197 @@
1
+ require 'irb'
2
+ require_relative 'trex'
3
+
4
+ module KatakataIrb::RubyLexPatch
5
+ def self.patch_to_ruby_lex
6
+ (RubyLex.instance_methods(false) - [:initialize_input, :set_prompt, :process_continue, :check_code_block]).each { RubyLex.remove_method _1 }
7
+ RubyLex.prepend self
8
+ end
9
+
10
+ def self.complete_tokens(code, context: nil)
11
+ incomplete_tokens = RubyLex.ripper_lex_without_warning(code, context: context)
12
+ KatakataIrb::TRex.interpolate_ripper_ignored_tokens(code, incomplete_tokens)
13
+ end
14
+
15
+ def calc_nesting_depth(tokens)
16
+ indent_level = 0
17
+ nesting_level = 0
18
+ tokens.each_with_index do |t, index|
19
+ case t.event
20
+ when :on_heredoc_beg
21
+ if tokens[index + 1]&.event != :on_heredoc_beg
22
+ if t.tok.start_with?('<<~')
23
+ indent_level += 1
24
+ else
25
+ indent_level = 0
26
+ end
27
+ end
28
+ when :on_tstring_beg, :on_regexp_beg
29
+ indent_level += 1 if t.tok[0] == '%'
30
+ when :on_embdoc_beg
31
+ indent_level = 0
32
+ else
33
+ nesting_level += 1
34
+ indent_level += 1
35
+ end
36
+ end
37
+ [indent_level, nesting_level]
38
+ end
39
+
40
+ def process_indent_level(tokens)
41
+ opens, heredocs = KatakataIrb::TRex.parse(tokens)
42
+ indent, _nesting = calc_nesting_depth(opens.map(&:first) + heredocs)
43
+ indent * 2
44
+ end
45
+
46
+ def check_corresponding_token_depth(tokens, line_index)
47
+ lines, = KatakataIrb::TRex.parse_line(tokens)
48
+ result = lines[line_index]
49
+ return unless result
50
+ _tokens, prev, opens, min_depth = result
51
+ depth, = calc_nesting_depth(opens.take(min_depth).map(&:first))
52
+ prev_depth, = calc_nesting_depth(prev.map(&:first))
53
+ depth * 2 if depth < prev_depth
54
+ end
55
+
56
+ def ltype_from_open_tokens(opens)
57
+ return nil if opens.empty?
58
+ case opens.last.tok
59
+ when ?`, /^<<[-~]?`/, /^%x.$/
60
+ ?`
61
+ when ?', /^<<[-~]?'/, /^%q.$/
62
+ ?'
63
+ when ?", /^<</, /^%.$/, /^%Q.$/
64
+ ?"
65
+ when ":'", ':"', ':', /^%s$/
66
+ ':'
67
+ when /^%[iwIW]$/
68
+ ']'
69
+ when '/', /^%r.$/
70
+ '/'
71
+ end
72
+ end
73
+
74
+ def check_termination_in_prev_line(code, context: nil)
75
+ *lines, last_line = code.lines
76
+ last_line if lines.any? && check_termination(lines.join, context: context)
77
+ end
78
+
79
+ def check_termination(code, context: nil)
80
+ tokens = KatakataIrb::RubyLexPatch.complete_tokens(code, context: context)
81
+ opens, heredocs = KatakataIrb::TRex.parse(tokens)
82
+ opens.empty? && heredocs.empty? && !process_continue(tokens)
83
+ end
84
+
85
+ def set_input(io, p = nil, context: nil, &block)
86
+ @io = io
87
+ if @io.respond_to?(:check_termination)
88
+ @io.check_termination do |code|
89
+ if Reline::IOGate.in_pasting?
90
+ lex = RubyLex.new
91
+ rest = lex.check_termination_in_prev_line(code, context: context)
92
+ if rest
93
+ Reline.delete_text
94
+ rest.bytes.reverse_each do |c|
95
+ Reline.ungetc(c)
96
+ end
97
+ true
98
+ else
99
+ false
100
+ end
101
+ else
102
+ check_termination(code, context: context)
103
+ end
104
+ end
105
+ end
106
+ if @io.respond_to?(:dynamic_prompt)
107
+ @io.dynamic_prompt do |lines|
108
+ lines << '' if lines.empty?
109
+ code = lines.map{ |l| l + "\n" }.join
110
+ tokens = KatakataIrb::RubyLexPatch.complete_tokens code, context: context
111
+ lines, _unclosed_heredocs = KatakataIrb::TRex.parse_line(tokens)
112
+ continue = false
113
+ lines.map.with_index do |(line, _prev_opens, next_opens), line_num_offset|
114
+ unless (c = process_continue(line.map(&:first))).nil?
115
+ continue = c
116
+ end
117
+ prompt next_opens, continue, line_num_offset
118
+ end
119
+ end
120
+ end
121
+
122
+ if p.respond_to?(:call)
123
+ @input = p
124
+ elsif block_given?
125
+ @input = block
126
+ else
127
+ @input = Proc.new{@io.gets}
128
+ end
129
+ end
130
+
131
+ def set_auto_indent(context)
132
+ if @io.respond_to?(:auto_indent) and context.auto_indent_mode
133
+ @io.auto_indent do |lines, line_index, byte_pointer, is_newline|
134
+ if is_newline
135
+ tokens = KatakataIrb::RubyLexPatch.complete_tokens(lines[0..line_index].join("\n"), context: context)
136
+ process_indent_level tokens
137
+ else
138
+ code = line_index.zero? ? '' : lines[0..(line_index - 1)].map{ |l| l + "\n" }.join
139
+ last_line = lines[line_index]&.byteslice(0, byte_pointer)
140
+ code += last_line if last_line
141
+ tokens = KatakataIrb::RubyLexPatch.complete_tokens(code, context: context)
142
+ check_corresponding_token_depth(tokens, line_index)
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ def prompt(opens, continue, line_num_offset)
149
+ ltype = ltype_from_open_tokens opens.map(&:first)
150
+ _indent, nesting_level = calc_nesting_depth opens.map(&:first)
151
+ @prompt.call(ltype, nesting_level, opens.any? || continue, @line_no + line_num_offset)
152
+ end
153
+
154
+ def store_prompt_to_irb(...)
155
+ prompt(...) # TODO: do not use this. change the api. example: @input.call(prompt)
156
+ end
157
+
158
+ def readmultiline(context)
159
+ if @io.respond_to? :check_termination
160
+ store_prompt_to_irb [], false, 0
161
+ @input.call
162
+ else
163
+ # nomultiline
164
+ line = ''
165
+ line_offset = 0
166
+ store_prompt_to_irb [], false, 0
167
+ loop do
168
+ l = @input.call
169
+ next if l.nil?
170
+ line << l
171
+ tokens = KatakataIrb::RubyLexPatch.complete_tokens(line, context: context)
172
+ _line, _prev_opens, next_opens = KatakataIrb::TRex.parse_line(tokens).first.last
173
+ return line if next_opens.empty?
174
+ line_offset += 1
175
+ store_prompt_to_irb next_opens, true, line_offset
176
+ end
177
+ end
178
+ end
179
+
180
+ def each_top_level_statement(context = nil)
181
+ initialize_input
182
+ loop do
183
+ begin
184
+ line = readmultiline(context)
185
+ break unless line
186
+ if line != "\n"
187
+ line.force_encoding(@io.encoding)
188
+ yield line, @line_no
189
+ end
190
+ @line_no += line.count("\n")
191
+ raise RubyLex::TerminateLineInput if @io.eof?
192
+ rescue RubyLex::TerminateLineInput
193
+ initialize_input
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,207 @@
1
+ module KatakataIrb; end
2
+ module KatakataIrb::TRex
3
+ def self.interpolate_ripper_ignored_tokens(code, tokens)
4
+ line_positions = code.lines.reduce([0]) { _1 << _1.last + _2.bytesize }
5
+ prev_byte_pos = 0
6
+ interpolated = []
7
+ prev_line = 1
8
+ event = :on_ignored_by_ripper
9
+ tokens.each do |t|
10
+ line, col = t.pos
11
+ byte_pos = line_positions[line - 1] + col
12
+ if prev_byte_pos < byte_pos
13
+ tok = code.byteslice(prev_byte_pos...byte_pos)
14
+ pos = [prev_line, prev_byte_pos - line_positions[prev_line - 1]]
15
+ interpolated << Ripper::Lexer::Elem.new(pos, event, tok, 0)
16
+ prev_line += tok.count("\n")
17
+ end
18
+ interpolated << t
19
+ prev_byte_pos = byte_pos + t.tok.bytesize
20
+ prev_line += t.tok.count("\n")
21
+ end
22
+ if prev_byte_pos < code.bytesize
23
+ tok = code.byteslice(prev_byte_pos..)
24
+ pos = [prev_line, prev_byte_pos - line_positions[prev_line - 1]]
25
+ interpolated << Ripper::Lexer::Elem.new(pos, event, tok, 0)
26
+ end
27
+ interpolated
28
+ end
29
+
30
+ def self.parse(tokens)
31
+ opens = []
32
+ pending_heredocs = []
33
+ first_token_on_line = true
34
+ tokens.each_with_index do |t, index|
35
+ skip = false
36
+ last_tok, state, args = opens.last
37
+ case state
38
+ when :in_unquoted_symbol
39
+ opens.pop
40
+ skip = true if %i[on_ident on_const on_op on_cvar on_ivar on_gvar on_kw on_int on_backtick].include? t.event
41
+ when :in_method_head
42
+ unless %i[on_sp on_ignored_nl].include?(t.event)
43
+ next_args = []
44
+ body = nil
45
+ if args.include? :receiver
46
+ case t.event
47
+ when :on_lparen, :on_ivar, :on_gvar, :on_cvar
48
+ next_args << :dot
49
+ when :on_kw
50
+ if t.tok in 'self' | 'true' | 'false' | 'nil'
51
+ next_args.push :arg, :dot
52
+ else
53
+ skip = true
54
+ next_args << :arg
55
+ end
56
+ when :on_op
57
+ skip = true
58
+ next_args << :arg
59
+ when :on_ident, :on_const
60
+ next_args.push :arg, :dot
61
+ end
62
+ end
63
+ if args.include? :dot
64
+ next_args << :name if t.event == :on_period || (t.event == :on_op && t.tok == '::')
65
+ end
66
+ if args.include? :name
67
+ if %i[on_ident on_const on_op on_kw].include? t.event
68
+ next_args << :arg
69
+ skip = true
70
+ end
71
+ end
72
+ if args.include? :arg
73
+ case t.event
74
+ when :on_op
75
+ body = :oneliner if t.tok == '='
76
+ when :on_nl, :on_semicolon
77
+ body = :normal
78
+ when :on_lparen
79
+ next_args << :eq
80
+ else
81
+ next_args << :arg_without_paren
82
+ end
83
+ end
84
+ if args.include? :eq
85
+ if t.event == :on_op && t.tok == '='
86
+ body = :oneliner
87
+ elsif t.event != :on_embdoc_beg
88
+ body = :normal
89
+ end
90
+ end
91
+ if args.include? :args_without_paren
92
+ body = :normal if %i[on_semicolon on_nl].include? t.event
93
+ end
94
+ if body == :oneliner
95
+ opens.pop
96
+ elsif body
97
+ opens.pop
98
+ opens << [last_tok, nil]
99
+ else
100
+ opens.pop
101
+ opens << [last_tok, :in_method_head, next_args]
102
+ end
103
+ end
104
+ when :in_for_while_until_condition
105
+ if t.event == :on_semicolon || t.event == :on_nl || (t.event == :on_kw && t.tok == 'do')
106
+ skip = true if t.event == :on_kw && t.tok == 'do'
107
+ opens.pop
108
+ opens << [last_tok, nil]
109
+ end
110
+ end
111
+
112
+ unless skip
113
+ case t.event
114
+ when :on_kw
115
+ case t.tok
116
+ when 'begin', 'class', 'module', 'do', 'case'
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)
124
+ opens << [t, nil]
125
+ end
126
+ when 'while', 'until'
127
+ unless t.state.allbits?(Ripper::EXPR_LABEL)
128
+ opens << [t, :in_for_while_until_condition]
129
+ end
130
+ when 'ensure', 'rescue'
131
+ unless t.state.allbits?(Ripper::EXPR_LABEL)
132
+ opens.pop
133
+ opens << [t, nil]
134
+ end
135
+ when 'elsif', 'else', 'when'
136
+ opens.pop
137
+ opens << [t, nil]
138
+ when 'for'
139
+ opens << [t, :in_for_while_until_condition]
140
+ when 'in'
141
+ if last_tok&.event == :on_kw && %w[case in].include?(last_tok.tok) && first_token_on_line
142
+ opens.pop
143
+ opens << [t, nil]
144
+ end
145
+ end
146
+ when :on_lparen, :on_lbracket, :on_lbrace, :on_tlambeg, :on_embexpr_beg, :on_embdoc_beg
147
+ opens << [t, nil]
148
+ when :on_rparen, :on_rbracket, :on_rbrace, :on_embexpr_end, :on_embdoc_end
149
+ opens.pop
150
+ when :on_heredoc_beg
151
+ pending_heredocs << t
152
+ when :on_heredoc_end
153
+ opens.pop
154
+ when :on_backtick
155
+ opens << [t, nil] if t.state.allbits?(Ripper::EXPR_BEG)
156
+ when :on_tstring_beg, :on_words_beg, :on_qwords_beg, :on_symbols_beg, :on_qsymbols_beg, :on_regexp_beg
157
+ opens << [t, nil]
158
+ when :on_tstring_end, :on_regexp_end, :on_label_end
159
+ opens.pop
160
+ when :on_symbeg
161
+ if t.tok == ':'
162
+ opens << [t, :in_unquoted_symbol]
163
+ else
164
+ opens << [t, nil]
165
+ end
166
+ end
167
+ end
168
+ if t.event == :on_nl || t.event == :on_semicolon
169
+ first_token_on_line = true
170
+ elsif t.event != :on_sp
171
+ first_token_on_line = false
172
+ end
173
+ if pending_heredocs.any? && t.tok.include?("\n")
174
+ pending_heredocs.reverse_each { opens << [_1, nil] }
175
+ pending_heredocs = []
176
+ end
177
+ yield t, index, opens if block_given?
178
+ end
179
+ [opens, pending_heredocs.reverse]
180
+ end
181
+
182
+ def self.parse_line(tokens)
183
+ line_tokens = []
184
+ prev_opens = []
185
+ min_depth = 0
186
+ output = []
187
+ last_opens, unclosed_heredocs = KatakataIrb::TRex.parse(tokens) do |t, _index, opens|
188
+ depth = t == opens.last&.first ? opens.size - 1 : opens.size
189
+ min_depth = depth if depth < min_depth
190
+ if t.tok.include? "\n"
191
+ t.tok.each_line do |line|
192
+ line_tokens << [t, line]
193
+ next if line[-1] != "\n"
194
+ next_opens = opens.dup
195
+ output << [line_tokens, prev_opens, next_opens, min_depth]
196
+ prev_opens = next_opens
197
+ min_depth = prev_opens.size
198
+ line_tokens = []
199
+ end
200
+ else
201
+ line_tokens << [t, t.tok]
202
+ end
203
+ end
204
+ output << [line_tokens, prev_opens, last_opens, min_depth] if line_tokens.any?
205
+ [output, unclosed_heredocs]
206
+ end
207
+ end