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.
- checksums.yaml +7 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +23 -0
- data/LICENSE.txt +21 -0
- data/README.md +51 -0
- data/Rakefile +12 -0
- data/bin/console +10 -0
- data/bin/setup +8 -0
- data/exe/kirb +10 -0
- data/katakata_irb.gemspec +36 -0
- data/lib/katakata_irb/completor.rb +187 -0
- data/lib/katakata_irb/reline_patch.rb +40 -0
- data/lib/katakata_irb/reline_patches/escapeseq.patch +45 -0
- data/lib/katakata_irb/reline_patches/fullwidth.patch +200 -0
- data/lib/katakata_irb/reline_patches/indent.patch +25 -0
- data/lib/katakata_irb/reline_patches/raw.patch +95 -0
- data/lib/katakata_irb/reline_patches/scrollbar.patch +43 -0
- data/lib/katakata_irb/reline_patches/wholelines.patch +102 -0
- data/lib/katakata_irb/ruby_lex_patch.rb +197 -0
- data/lib/katakata_irb/trex.rb +207 -0
- data/lib/katakata_irb/type_simulator.rb +1108 -0
- data/lib/katakata_irb/types.rb +341 -0
- data/lib/katakata_irb/version.rb +5 -0
- data/lib/katakata_irb.rb +15 -0
- data/sig/katakata_irb.rbs +4 -0
- metadata +84 -0
@@ -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
|