katakata_irb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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