Skip to content

Commit b827bf7

Browse files
committed
Extract WorkspaceStack and loosen Context's coupling to Irb and WorkSpace
The workspace stack lived in Context, but several places changed it directly, and each had to remember the rules itself, like keeping the home workspace and loading helper methods. That caused a bug: `pushws foo` followed by a bare `chws` stayed on `foo` instead of going back to main. - Add WorkspaceStack and make it the only place that changes the stack. - Move the workspace methods from ext/workspaces.rb and ext/change-ws.rb into Context, and delete those files. - Store from_binding on Context, so it no longer asks Irb. - Stop reading Context's instance variables from outside it. - Extract WorkSpace.base_binding from WorkSpace#initialize. - Remove dead code: Context#workspace_home, IrbLoader#old and Measure#initialize. A file loaded with `irb_load file, true` now also gets helper methods like `conf`.
1 parent 09ba720 commit b827bf7

11 files changed

Lines changed: 183 additions & 160 deletions

File tree

‎lib/irb.rb‎

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,15 @@ class << self
9898
# The lexer used by this irb session
9999
attr_accessor :scanner
100100

101-
attr_reader :from_binding
101+
# Whether this session was started by Binding#irb
102+
def from_binding
103+
@context.from_binding?
104+
end
102105

103106
# Creates a new irb session
104107
def initialize(workspace = nil, input_method = nil, from_binding: false)
105-
@from_binding = from_binding
106108
@prompt_part_cache = nil
107-
@context = Context.new(self, workspace, input_method)
108-
@context.workspace.load_helper_methods_to_main
109+
@context = Context.new(self, workspace, input_method, from_binding: from_binding)
109110
@signal_status = :IN_IRB
110111
@scanner = RubyLex.new
111112
@line_no = 1
@@ -124,9 +125,7 @@ def debug_break
124125
end
125126

126127
def debug_readline(binding)
127-
workspace = IRB::WorkSpace.new(binding)
128-
context.replace_workspace(workspace)
129-
context.workspace.load_helper_methods_to_main
128+
context.replace_workspace(IRB::WorkSpace.new(binding))
130129
@line_no += 1
131130

132131
# When users run:
@@ -489,12 +488,8 @@ def suspend_name(path = nil, name = nil)
489488
# Context#workspace.
490489
#
491490
# Used by the irb command `irb_load`, see IRB@IRB+Sessions for more information.
492-
def suspend_workspace(workspace)
493-
current_workspace = @context.workspace
494-
@context.replace_workspace(workspace)
495-
yield
496-
ensure
497-
@context.replace_workspace current_workspace
491+
def suspend_workspace(workspace, &block)
492+
@context.workspaces.with(workspace, &block)
498493
end
499494

500495
# Evaluates the given block using the given `input_method` as the Context#io.
@@ -503,11 +498,11 @@ def suspend_workspace(workspace)
503498
# more information.
504499
def suspend_input_method(input_method)
505500
back_io = @context.io
506-
@context.instance_eval{@io = input_method}
501+
@context.io = input_method
507502
begin
508503
yield back_io
509504
ensure
510-
@context.instance_eval{@io = back_io}
505+
@context.io = back_io
511506
end
512507
end
513508

‎lib/irb/command/chws.rb‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
# change-ws.rb -
44
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
55
#
6-
require_relative "../ext/change-ws"
76

87
module IRB
98
# :stopdoc:

‎lib/irb/command/measure.rb‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ class Measure < Base
88
category "Misc"
99
description "`measure` enables the mode to measure processing time. `measure :off` disables it."
1010

11-
def initialize(*args)
12-
super(*args)
13-
end
14-
1511
def execute(arg)
1612
if arg&.match?(/^do$|^do[^\w]|^\{/)
1713
warn 'Configure IRB.conf[:MEASURE_PROC] to add custom measure methods.'

‎lib/irb/command/pushws.rb‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
55
#
66

7-
require_relative "../ext/workspaces"
8-
97
module IRB
108
# :stopdoc:
119

@@ -15,7 +13,7 @@ class Workspaces < Base
1513
description "Show workspaces."
1614

1715
def execute(_arg)
18-
inspection_results = irb_context.instance_variable_get(:@workspace_stack).map do |ws|
16+
inspection_results = irb_context.workspaces.map do |ws|
1917
truncated_inspect(ws.main)
2018
end
2119

‎lib/irb/context.rb‎

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#
66

77
require_relative "workspace"
8+
require_relative "workspace_stack"
89
require_relative "inspector"
910
require_relative "input-method"
1011
require_relative "output-method"
@@ -24,14 +25,10 @@ class Context
2425
# +nil+:: uses stdin or Reline or Readline
2526
# +String+:: uses a File
2627
# +other+:: uses this as InputMethod
27-
def initialize(irb, workspace = nil, input_method = nil)
28+
def initialize(irb, workspace = nil, input_method = nil, from_binding: false)
2829
@irb = irb
29-
@workspace_stack = []
30-
if workspace
31-
@workspace_stack << workspace
32-
else
33-
@workspace_stack << WorkSpace.new
34-
end
30+
@from_binding = from_binding
31+
@workspaces = WorkspaceStack.new(workspace || WorkSpace.new)
3532
@thread = Thread.current
3633

3734
# copy of default configuration
@@ -190,24 +187,61 @@ def history_file=(hist)
190187
IRB.conf[:HISTORY_FILE] = hist
191188
end
192189

190+
# The stack of workspaces in this context, see WorkspaceStack.
191+
attr_reader :workspaces
192+
193193
# Workspace in the current context.
194194
def workspace
195-
@workspace_stack.last
195+
@workspaces.current
196+
end
197+
198+
# The workspace this context started with.
199+
def home_workspace
200+
@workspaces.home
196201
end
197202

198203
# Replace the current workspace with the given +workspace+.
199204
def replace_workspace(workspace)
200-
@workspace_stack.pop
201-
@workspace_stack.push(workspace)
205+
@workspaces.replace(workspace)
206+
end
207+
208+
# Changes the current workspace to given object or binding.
209+
#
210+
# If the optional argument is omitted, the workspace will be
211+
# #home_workspace.
212+
#
213+
# See IRB::WorkSpace.new for more information.
214+
def change_workspace(*_main)
215+
if _main.empty?
216+
replace_workspace(home_workspace)
217+
return main
218+
end
219+
220+
replace_workspace(WorkSpace.new(_main[0]))
221+
end
222+
223+
# Creates a new workspace with the given object, and pushes it onto the
224+
# workspace stack. Without an argument, swaps the two topmost workspaces.
225+
#
226+
# See IRB::WorkSpace.new for more information.
227+
def push_workspace(*_main)
228+
if _main.empty?
229+
@workspaces.swap
230+
else
231+
@workspaces.push(WorkSpace.new(workspace.binding, _main[0]))
232+
end
233+
end
234+
235+
# Removes the current workspace from the stack and returns it, or +nil+ if
236+
# only the home workspace is left.
237+
def pop_workspace
238+
@workspaces.pop
202239
end
203240

204241
# The top-level workspace, see WorkSpace#main
205242
def main
206243
workspace.main
207244
end
208-
209-
# The toplevel workspace, see #home_workspace
210-
attr_reader :workspace_home
211245
# The current thread in this context.
212246
attr_reader :thread
213247
# The current input method.
@@ -565,8 +599,9 @@ def evaluate(statement, line_no) # :nodoc:
565599
nil
566600
end
567601

602+
# Whether this context was started by Binding#irb.
568603
def from_binding?
569-
@irb.from_binding
604+
@from_binding
570605
end
571606

572607
def evaluate_expression(code, line_no) # :nodoc:

‎lib/irb/ext/change-ws.rb‎

Lines changed: 0 additions & 37 deletions
This file was deleted.

‎lib/irb/ext/loader.rb‎

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -92,33 +92,5 @@ def load_file(path, priv = nil)
9292
end
9393
end
9494
end
95-
96-
def old # :nodoc:
97-
back_io = @io
98-
back_path = irb_path
99-
back_name = @irb_name
100-
back_scanner = @irb.scanner
101-
begin
102-
@io = FileInputMethod.new(path)
103-
@irb_name = File.basename(path)
104-
self.irb_path = path
105-
@irb.signal_status(:IN_LOAD) do
106-
if back_io.kind_of?(FileInputMethod)
107-
@irb.eval_input
108-
else
109-
begin
110-
@irb.eval_input
111-
rescue LoadAbort
112-
print "load abort!!\n"
113-
end
114-
end
115-
end
116-
ensure
117-
@io = back_io
118-
@irb_name = back_name
119-
self.irb_path = back_path
120-
@irb.scanner = back_scanner
121-
end
122-
end
12395
end
12496
end

‎lib/irb/ext/workspaces.rb‎

Lines changed: 0 additions & 36 deletions
This file was deleted.

‎lib/irb/workspace.rb‎

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,18 @@
99
IRB::TOPLEVEL_BINDING = binding
1010
module IRB # :nodoc:
1111
class WorkSpace
12-
# Creates a new workspace.
13-
#
14-
# set self to main if specified, otherwise
15-
# inherit main from TOPLEVEL_BINDING.
16-
def initialize(*main)
17-
if Binding === main[0]
18-
@binding = main.shift
19-
elsif IRB.conf[:SINGLE_IRB]
20-
@binding = TOPLEVEL_BINDING
21-
else
12+
class << self
13+
# The binding new workspaces are created from when none is given, as
14+
# configured by <code>IRB.conf[:CONTEXT_MODE]</code>.
15+
def base_binding
16+
return TOPLEVEL_BINDING if IRB.conf[:SINGLE_IRB]
17+
2218
case IRB.conf[:CONTEXT_MODE]
2319
when 0 # binding in proc on TOPLEVEL_BINDING
24-
@binding = eval("proc{binding}.call",
25-
TOPLEVEL_BINDING,
26-
__FILE__,
27-
__LINE__)
20+
eval("proc{binding}.call",
21+
TOPLEVEL_BINDING,
22+
__FILE__,
23+
__LINE__)
2824
when 1 # binding in loaded file
2925
require "tempfile"
3026
f = Tempfile.open("irb-binding")
@@ -33,7 +29,7 @@ def initialize(*main)
3329
EOF
3430
f.close
3531
load f.path
36-
@binding = $binding
32+
$binding
3733

3834
when 2 # binding in loaded file(thread use)
3935
unless defined? BINDING_QUEUE
@@ -44,17 +40,17 @@ def initialize(*main)
4440
end
4541
Thread.pass
4642
end
47-
@binding = BINDING_QUEUE.pop
43+
BINDING_QUEUE.pop
4844

4945
when 3 # binding in function on TOPLEVEL_BINDING
50-
@binding = eval("self.class.remove_method(:irb_binding) if defined?(irb_binding); private; def irb_binding; binding; end; irb_binding",
51-
TOPLEVEL_BINDING,
52-
__FILE__,
53-
__LINE__ - 3)
46+
eval("self.class.remove_method(:irb_binding) if defined?(irb_binding); private; def irb_binding; binding; end; irb_binding",
47+
TOPLEVEL_BINDING,
48+
__FILE__,
49+
__LINE__ - 3)
5450
when 4 # binding is a copy of TOPLEVEL_BINDING (default)
5551
# Note that this will typically be IRB::TOPLEVEL_BINDING
5652
# This is to avoid RubyGems' local variables (see issue #17623)
57-
@binding = TOPLEVEL_BINDING.dup
53+
TOPLEVEL_BINDING.dup
5854

5955
when 5 # binding in Ruby::Box
6056
unless defined?(Ruby::Box)
@@ -63,9 +59,17 @@ def initialize(*main)
6359
end
6460

6561
puts 'Context-mode 5 (binding in Ruby::Box) is experimental. It may be removed or changed without notice.'
66-
@binding = Ruby::Box.new.eval('Kernel.binding')
62+
Ruby::Box.new.eval('Kernel.binding')
6763
end
6864
end
65+
end
66+
67+
# Creates a new workspace.
68+
#
69+
# set self to main if specified, otherwise
70+
# inherit main from TOPLEVEL_BINDING.
71+
def initialize(*main)
72+
@binding = Binding === main[0] ? main.shift : self.class.base_binding
6973

7074
if main.empty?
7175
@main = eval("self", @binding)

0 commit comments

Comments
 (0)