irb 1.6.4 → 1.8.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 +4 -4
- data/Gemfile +1 -0
- data/README.md +135 -14
- data/Rakefile +0 -7
- data/doc/irb/irb.rd.ja +1 -3
- data/irb.gemspec +2 -1
- data/lib/irb/cmd/chws.rb +2 -2
- data/lib/irb/cmd/debug.rb +36 -92
- data/lib/irb/cmd/edit.rb +6 -7
- data/lib/irb/cmd/help.rb +12 -46
- data/lib/irb/cmd/irb_info.rb +14 -18
- data/lib/irb/cmd/ls.rb +17 -7
- data/lib/irb/cmd/nop.rb +4 -8
- data/lib/irb/cmd/pushws.rb +3 -3
- data/lib/irb/cmd/show_cmds.rb +17 -3
- data/lib/irb/cmd/show_doc.rb +48 -0
- data/lib/irb/cmd/show_source.rb +4 -60
- data/lib/irb/cmd/subirb.rb +49 -6
- data/lib/irb/color.rb +2 -0
- data/lib/irb/completion.rb +2 -2
- data/lib/irb/context.rb +52 -21
- data/lib/irb/debug/ui.rb +104 -0
- data/lib/irb/debug.rb +115 -0
- data/lib/irb/ext/{history.rb → eval_history.rb} +4 -4
- data/lib/irb/ext/loader.rb +2 -0
- data/lib/irb/ext/tracer.rb +1 -1
- data/lib/irb/extend-command.rb +7 -5
- data/lib/irb/help.rb +1 -3
- data/lib/irb/{ext/save-history.rb → history.rb} +4 -55
- data/lib/irb/init.rb +4 -10
- data/lib/irb/input-method.rb +65 -131
- data/lib/irb/locale.rb +10 -43
- data/lib/irb/nesting_parser.rb +227 -0
- data/lib/irb/pager.rb +86 -0
- data/lib/irb/ruby-lex.rb +401 -747
- data/lib/irb/source_finder.rb +64 -0
- data/lib/irb/statement.rb +80 -0
- data/lib/irb/version.rb +2 -2
- data/lib/irb/workspace.rb +4 -0
- data/lib/irb.rb +222 -120
- metadata +28 -11
- data/lib/irb/cmd/fork.rb +0 -34
- data/lib/irb/lc/ja/encoding_aliases.rb +0 -13
- data/lib/irb/magic-file.rb +0 -38
- data/lib/irb/src_encoding.rb +0 -7
data/lib/irb/locale.rb
CHANGED
@@ -15,7 +15,11 @@ module IRB # :nodoc:
|
|
15
15
|
]x
|
16
16
|
LOCALE_DIR = "/lc/"
|
17
17
|
|
18
|
-
|
18
|
+
LEGACY_ENCODING_ALIAS_MAP = {
|
19
|
+
'ujis' => Encoding::EUC_JP,
|
20
|
+
'euc' => Encoding::EUC_JP
|
21
|
+
}
|
22
|
+
|
19
23
|
@@loaded = []
|
20
24
|
|
21
25
|
def initialize(locale = nil)
|
@@ -26,11 +30,11 @@ module IRB # :nodoc:
|
|
26
30
|
@lang, @territory, @encoding_name, @modifier = m[:language], m[:territory], m[:codeset], m[:modifier]
|
27
31
|
|
28
32
|
if @encoding_name
|
29
|
-
|
30
|
-
if @encoding = @@legacy_encoding_alias_map[@encoding_name]
|
33
|
+
if @encoding = LEGACY_ENCODING_ALIAS_MAP[@encoding_name]
|
31
34
|
warn(("%s is obsolete. use %s" % ["#{@lang}_#{@territory}.#{@encoding_name}", "#{@lang}_#{@territory}.#{@encoding.name}"]), uplevel: 1)
|
35
|
+
else
|
36
|
+
@encoding = Encoding.find(@encoding_name) rescue nil
|
32
37
|
end
|
33
|
-
@encoding = Encoding.find(@encoding_name) rescue nil
|
34
38
|
end
|
35
39
|
end
|
36
40
|
@encoding ||= (Encoding.find('locale') rescue Encoding::ASCII_8BIT)
|
@@ -78,39 +82,12 @@ module IRB # :nodoc:
|
|
78
82
|
super(*ary)
|
79
83
|
end
|
80
84
|
|
81
|
-
def
|
82
|
-
rex = Regexp.new("lc/#{Regexp.quote(file)}\.(so|o|sl|rb)?")
|
83
|
-
return false if $".find{|f| f =~ rex}
|
84
|
-
|
85
|
-
case file
|
86
|
-
when /\.rb$/
|
87
|
-
begin
|
88
|
-
load(file, priv)
|
89
|
-
$".push file
|
90
|
-
return true
|
91
|
-
rescue LoadError
|
92
|
-
end
|
93
|
-
when /\.(so|o|sl)$/
|
94
|
-
return super
|
95
|
-
end
|
96
|
-
|
97
|
-
begin
|
98
|
-
load(f = file + ".rb")
|
99
|
-
$".push f #"
|
100
|
-
return true
|
101
|
-
rescue LoadError
|
102
|
-
return ruby_require(file)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
alias toplevel_load load
|
107
|
-
|
108
|
-
def load(file, priv=nil)
|
85
|
+
def load(file)
|
109
86
|
found = find(file)
|
110
87
|
if found
|
111
88
|
unless @@loaded.include?(found)
|
112
89
|
@@loaded << found # cache
|
113
|
-
|
90
|
+
Kernel.load(found)
|
114
91
|
end
|
115
92
|
else
|
116
93
|
raise LoadError, "No such file to load -- #{file}"
|
@@ -129,16 +106,6 @@ module IRB # :nodoc:
|
|
129
106
|
end
|
130
107
|
end
|
131
108
|
|
132
|
-
private
|
133
|
-
def real_load(path, priv)
|
134
|
-
src = MagicFile.open(path){|f| f.read}
|
135
|
-
if priv
|
136
|
-
eval("self", TOPLEVEL_BINDING).extend(Module.new {eval(src, nil, path)})
|
137
|
-
else
|
138
|
-
eval(src, TOPLEVEL_BINDING, path)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
109
|
# @param paths load paths in which IRB find a localized file.
|
143
110
|
# @param dir directory
|
144
111
|
# @param file basename to be localized
|
@@ -0,0 +1,227 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module IRB
|
3
|
+
module NestingParser
|
4
|
+
IGNORE_TOKENS = %i[on_sp on_ignored_nl on_comment on_embdoc_beg on_embdoc on_embdoc_end]
|
5
|
+
|
6
|
+
# Scan each token and call the given block with array of token and other information for parsing
|
7
|
+
def self.scan_opens(tokens)
|
8
|
+
opens = []
|
9
|
+
pending_heredocs = []
|
10
|
+
first_token_on_line = true
|
11
|
+
tokens.each do |t|
|
12
|
+
skip = false
|
13
|
+
last_tok, state, args = opens.last
|
14
|
+
case state
|
15
|
+
when :in_unquoted_symbol
|
16
|
+
unless IGNORE_TOKENS.include?(t.event)
|
17
|
+
opens.pop
|
18
|
+
skip = true
|
19
|
+
end
|
20
|
+
when :in_lambda_head
|
21
|
+
opens.pop if t.event == :on_tlambeg || (t.event == :on_kw && t.tok == 'do')
|
22
|
+
when :in_method_head
|
23
|
+
unless IGNORE_TOKENS.include?(t.event)
|
24
|
+
next_args = []
|
25
|
+
body = nil
|
26
|
+
if args.include?(:receiver)
|
27
|
+
case t.event
|
28
|
+
when :on_lparen, :on_ivar, :on_gvar, :on_cvar
|
29
|
+
# def (receiver). | def @ivar. | def $gvar. | def @@cvar.
|
30
|
+
next_args << :dot
|
31
|
+
when :on_kw
|
32
|
+
case t.tok
|
33
|
+
when 'self', 'true', 'false', 'nil'
|
34
|
+
# def self(arg) | def self.
|
35
|
+
next_args.push(:arg, :dot)
|
36
|
+
else
|
37
|
+
# def if(arg)
|
38
|
+
skip = true
|
39
|
+
next_args << :arg
|
40
|
+
end
|
41
|
+
when :on_op, :on_backtick
|
42
|
+
# def +(arg)
|
43
|
+
skip = true
|
44
|
+
next_args << :arg
|
45
|
+
when :on_ident, :on_const
|
46
|
+
# def a(arg) | def a.
|
47
|
+
next_args.push(:arg, :dot)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
if args.include?(:dot)
|
51
|
+
# def receiver.name
|
52
|
+
next_args << :name if t.event == :on_period || (t.event == :on_op && t.tok == '::')
|
53
|
+
end
|
54
|
+
if args.include?(:name)
|
55
|
+
if %i[on_ident on_const on_op on_kw on_backtick].include?(t.event)
|
56
|
+
# def name(arg) | def receiver.name(arg)
|
57
|
+
next_args << :arg
|
58
|
+
skip = true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
if args.include?(:arg)
|
62
|
+
case t.event
|
63
|
+
when :on_nl, :on_semicolon
|
64
|
+
# def recever.f;
|
65
|
+
body = :normal
|
66
|
+
when :on_lparen
|
67
|
+
# def recever.f()
|
68
|
+
next_args << :eq
|
69
|
+
else
|
70
|
+
if t.event == :on_op && t.tok == '='
|
71
|
+
# def receiver.f =
|
72
|
+
body = :oneliner
|
73
|
+
else
|
74
|
+
# def recever.f arg
|
75
|
+
next_args << :arg_without_paren
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
if args.include?(:eq)
|
80
|
+
if t.event == :on_op && t.tok == '='
|
81
|
+
body = :oneliner
|
82
|
+
else
|
83
|
+
body = :normal
|
84
|
+
end
|
85
|
+
end
|
86
|
+
if args.include?(:arg_without_paren)
|
87
|
+
if %i[on_semicolon on_nl].include?(t.event)
|
88
|
+
# def f a;
|
89
|
+
body = :normal
|
90
|
+
else
|
91
|
+
# def f a, b
|
92
|
+
next_args << :arg_without_paren
|
93
|
+
end
|
94
|
+
end
|
95
|
+
if body == :oneliner
|
96
|
+
opens.pop
|
97
|
+
elsif body
|
98
|
+
opens[-1] = [last_tok, nil]
|
99
|
+
else
|
100
|
+
opens[-1] = [last_tok, :in_method_head, next_args]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
when :in_for_while_until_condition
|
104
|
+
if t.event == :on_semicolon || t.event == :on_nl || (t.event == :on_kw && t.tok == 'do')
|
105
|
+
skip = true if t.event == :on_kw && t.tok == 'do'
|
106
|
+
opens[-1] = [last_tok, nil]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
unless skip
|
111
|
+
case t.event
|
112
|
+
when :on_kw
|
113
|
+
case t.tok
|
114
|
+
when 'begin', 'class', 'module', 'do', 'case'
|
115
|
+
opens << [t, nil]
|
116
|
+
when 'end'
|
117
|
+
opens.pop
|
118
|
+
when 'def'
|
119
|
+
opens << [t, :in_method_head, [:receiver, :name]]
|
120
|
+
when 'if', 'unless'
|
121
|
+
unless t.state.allbits?(Ripper::EXPR_LABEL)
|
122
|
+
opens << [t, nil]
|
123
|
+
end
|
124
|
+
when 'while', 'until'
|
125
|
+
unless t.state.allbits?(Ripper::EXPR_LABEL)
|
126
|
+
opens << [t, :in_for_while_until_condition]
|
127
|
+
end
|
128
|
+
when 'ensure', 'rescue'
|
129
|
+
unless t.state.allbits?(Ripper::EXPR_LABEL)
|
130
|
+
opens.pop
|
131
|
+
opens << [t, nil]
|
132
|
+
end
|
133
|
+
when 'elsif', 'else', 'when'
|
134
|
+
opens.pop
|
135
|
+
opens << [t, nil]
|
136
|
+
when 'for'
|
137
|
+
opens << [t, :in_for_while_until_condition]
|
138
|
+
when 'in'
|
139
|
+
if last_tok&.event == :on_kw && %w[case in].include?(last_tok.tok) && first_token_on_line
|
140
|
+
opens.pop
|
141
|
+
opens << [t, nil]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
when :on_tlambda
|
145
|
+
opens << [t, :in_lambda_head]
|
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 { |t| opens << [t, nil] }
|
175
|
+
pending_heredocs = []
|
176
|
+
end
|
177
|
+
yield t, opens if block_given?
|
178
|
+
end
|
179
|
+
opens.map(&:first) + pending_heredocs.reverse
|
180
|
+
end
|
181
|
+
|
182
|
+
def self.open_tokens(tokens)
|
183
|
+
# scan_opens without block will return a list of open tokens at last token position
|
184
|
+
scan_opens(tokens)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Calculates token information [line_tokens, prev_opens, next_opens, min_depth] for each line.
|
188
|
+
# Example code
|
189
|
+
# ["hello
|
190
|
+
# world"+(
|
191
|
+
# First line
|
192
|
+
# line_tokens: [[lbracket, '['], [tstring_beg, '"'], [tstring_content("hello\nworld"), "hello\n"]]
|
193
|
+
# prev_opens: []
|
194
|
+
# next_tokens: [lbracket, tstring_beg]
|
195
|
+
# min_depth: 0 (minimum at beginning of line)
|
196
|
+
# Second line
|
197
|
+
# line_tokens: [[tstring_content("hello\nworld"), "world"], [tstring_end, '"'], [op, '+'], [lparen, '(']]
|
198
|
+
# prev_opens: [lbracket, tstring_beg]
|
199
|
+
# next_tokens: [lbracket, lparen]
|
200
|
+
# min_depth: 1 (minimum just after tstring_end)
|
201
|
+
def self.parse_by_line(tokens)
|
202
|
+
line_tokens = []
|
203
|
+
prev_opens = []
|
204
|
+
min_depth = 0
|
205
|
+
output = []
|
206
|
+
last_opens = scan_opens(tokens) do |t, opens|
|
207
|
+
depth = t == opens.last&.first ? opens.size - 1 : opens.size
|
208
|
+
min_depth = depth if depth < min_depth
|
209
|
+
if t.tok.include?("\n")
|
210
|
+
t.tok.each_line do |line|
|
211
|
+
line_tokens << [t, line]
|
212
|
+
next if line[-1] != "\n"
|
213
|
+
next_opens = opens.map(&:first)
|
214
|
+
output << [line_tokens, prev_opens, next_opens, min_depth]
|
215
|
+
prev_opens = next_opens
|
216
|
+
min_depth = prev_opens.size
|
217
|
+
line_tokens = []
|
218
|
+
end
|
219
|
+
else
|
220
|
+
line_tokens << [t, t.tok]
|
221
|
+
end
|
222
|
+
end
|
223
|
+
output << [line_tokens, prev_opens, last_opens, min_depth] if line_tokens.any?
|
224
|
+
output
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
data/lib/irb/pager.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IRB
|
4
|
+
# The implementation of this class is borrowed from RDoc's lib/rdoc/ri/driver.rb.
|
5
|
+
# Please do NOT use this class directly outside of IRB.
|
6
|
+
class Pager
|
7
|
+
PAGE_COMMANDS = [ENV['RI_PAGER'], ENV['PAGER'], 'less', 'more'].compact.uniq
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def page_content(content)
|
11
|
+
if content_exceeds_screen_height?(content)
|
12
|
+
page do |io|
|
13
|
+
io.puts content
|
14
|
+
end
|
15
|
+
else
|
16
|
+
$stdout.puts content
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def page
|
21
|
+
if STDIN.tty? && pager = setup_pager
|
22
|
+
begin
|
23
|
+
pid = pager.pid
|
24
|
+
yield pager
|
25
|
+
ensure
|
26
|
+
pager.close
|
27
|
+
end
|
28
|
+
else
|
29
|
+
yield $stdout
|
30
|
+
end
|
31
|
+
# When user presses Ctrl-C, IRB would raise `IRB::Abort`
|
32
|
+
# But since Pager is implemented by running paging commands like `less` in another process with `IO.popen`,
|
33
|
+
# the `IRB::Abort` exception only interrupts IRB's execution but doesn't affect the pager
|
34
|
+
# So to properly terminate the pager with Ctrl-C, we need to catch `IRB::Abort` and kill the pager process
|
35
|
+
rescue IRB::Abort
|
36
|
+
Process.kill("TERM", pid) if pid
|
37
|
+
nil
|
38
|
+
rescue Errno::EPIPE
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def content_exceeds_screen_height?(content)
|
44
|
+
screen_height, screen_width = begin
|
45
|
+
Reline.get_screen_size
|
46
|
+
rescue Errno::EINVAL
|
47
|
+
[24, 80]
|
48
|
+
end
|
49
|
+
|
50
|
+
pageable_height = screen_height - 3 # leave some space for previous and the current prompt
|
51
|
+
|
52
|
+
# If the content has more lines than the pageable height
|
53
|
+
content.lines.count > pageable_height ||
|
54
|
+
# Or if the content is a few long lines
|
55
|
+
pageable_height * screen_width < Reline::Unicode.calculate_width(content, true)
|
56
|
+
end
|
57
|
+
|
58
|
+
def setup_pager
|
59
|
+
require 'shellwords'
|
60
|
+
|
61
|
+
PAGE_COMMANDS.each do |pager|
|
62
|
+
pager = Shellwords.split(pager)
|
63
|
+
next if pager.empty?
|
64
|
+
|
65
|
+
if pager.first == 'less' || pager.first == 'more'
|
66
|
+
pager << '-R' unless pager.include?('-R')
|
67
|
+
end
|
68
|
+
|
69
|
+
begin
|
70
|
+
io = IO.popen(pager, 'w')
|
71
|
+
rescue
|
72
|
+
next
|
73
|
+
end
|
74
|
+
|
75
|
+
if $? && $?.pid == io.pid && $?.exited? # pager didn't work
|
76
|
+
next
|
77
|
+
end
|
78
|
+
|
79
|
+
return io
|
80
|
+
end
|
81
|
+
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|