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