Skip to content

Commit 61560b9

Browse files
authored
Restructure workspace management (#888)
* Remove dead irb_level method * Restructure workspace management Currently, workspace is an attribute of IRB::Context in most use cases. But when some workspace commands are used, like `pushws` or `popws`, a workspace will be created and used along side with the original workspace attribute. This complexity is not necessary and will prevent us from expanding multi-workspace support in the future. So this commit introduces a @workspace_stack ivar to IRB::Context so IRB can have a more natural way to manage workspaces. * Fix pushws without args * Always display workspace stack after related commands are used
1 parent 06f43aa commit 61560b9

8 files changed

Lines changed: 86 additions & 78 deletions

File tree

lib/irb.rb

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -933,7 +933,7 @@ def debug_break
933933

934934
def debug_readline(binding)
935935
workspace = IRB::WorkSpace.new(binding)
936-
context.workspace = workspace
936+
context.replace_workspace(workspace)
937937
context.workspace.load_commands_to_main
938938
@line_no += 1
939939

@@ -1269,12 +1269,11 @@ def suspend_name(path = nil, name = nil)
12691269
# Used by the irb command +irb_load+, see IRB@IRB+Sessions for more
12701270
# information.
12711271
def suspend_workspace(workspace)
1272-
@context.workspace, back_workspace = workspace, @context.workspace
1273-
begin
1274-
yield back_workspace
1275-
ensure
1276-
@context.workspace = back_workspace
1277-
end
1272+
current_workspace = @context.workspace
1273+
@context.replace_workspace(workspace)
1274+
yield
1275+
ensure
1276+
@context.replace_workspace current_workspace
12781277
end
12791278

12801279
# Evaluates the given block using the given +input_method+ as the
@@ -1534,7 +1533,7 @@ def irb(show_code: true)
15341533

15351534
if debugger_irb
15361535
# If we're already in a debugger session, set the workspace and irb_path for the original IRB instance
1537-
debugger_irb.context.workspace = workspace
1536+
debugger_irb.context.replace_workspace(workspace)
15381537
debugger_irb.context.irb_path = irb_path
15391538
# If we've started a debugger session and hit another binding.irb, we don't want to start an IRB session
15401539
# instead, we want to resume the irb:rdbg session.

lib/irb/command/pushws.rb

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,23 @@ class Workspaces < Base
1515
description "Show workspaces."
1616

1717
def execute(*obj)
18-
irb_context.workspaces.collect{|ws| ws.main}
18+
inspection_resuls = irb_context.instance_variable_get(:@workspace_stack).map do |ws|
19+
truncated_inspect(ws.main)
20+
end
21+
22+
puts "[" + inspection_resuls.join(", ") + "]"
23+
end
24+
25+
private
26+
27+
def truncated_inspect(obj)
28+
obj_inspection = obj.inspect
29+
30+
if obj_inspection.size > 20
31+
obj_inspection = obj_inspection[0, 19] + "...>"
32+
end
33+
34+
obj_inspection
1935
end
2036
end
2137

lib/irb/context.rb

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ class Context
2222
# +other+:: uses this as InputMethod
2323
def initialize(irb, workspace = nil, input_method = nil)
2424
@irb = irb
25+
@workspace_stack = []
2526
if workspace
26-
@workspace = workspace
27+
@workspace_stack << workspace
2728
else
28-
@workspace = WorkSpace.new
29+
@workspace_stack << WorkSpace.new
2930
end
3031
@thread = Thread.current
3132

@@ -229,15 +230,24 @@ def history_file=(hist)
229230
IRB.conf[:HISTORY_FILE] = hist
230231
end
231232

233+
# Workspace in the current context.
234+
def workspace
235+
@workspace_stack.last
236+
end
237+
238+
# Replace the current workspace with the given +workspace+.
239+
def replace_workspace(workspace)
240+
@workspace_stack.pop
241+
@workspace_stack.push(workspace)
242+
end
243+
232244
# The top-level workspace, see WorkSpace#main
233245
def main
234-
@workspace.main
246+
workspace.main
235247
end
236248

237249
# The toplevel workspace, see #home_workspace
238250
attr_reader :workspace_home
239-
# WorkSpace in the current context.
240-
attr_accessor :workspace
241251
# The current thread in this context.
242252
attr_reader :thread
243253
# The current input method.
@@ -489,7 +499,7 @@ def prompting?
489499
# to #last_value.
490500
def set_last_value(value)
491501
@last_value = value
492-
@workspace.local_variable_set :_, value
502+
workspace.local_variable_set :_, value
493503
end
494504

495505
# Sets the +mode+ of the prompt in this context.
@@ -585,7 +595,7 @@ def evaluate(line, line_no) # :nodoc:
585595

586596
if IRB.conf[:MEASURE] && !IRB.conf[:MEASURE_CALLBACKS].empty?
587597
last_proc = proc do
588-
result = @workspace.evaluate(line, @eval_path, line_no)
598+
result = workspace.evaluate(line, @eval_path, line_no)
589599
end
590600
IRB.conf[:MEASURE_CALLBACKS].inject(last_proc) do |chain, item|
591601
_name, callback, arg = item
@@ -596,7 +606,7 @@ def evaluate(line, line_no) # :nodoc:
596606
end
597607
end.call
598608
else
599-
result = @workspace.evaluate(line, @eval_path, line_no)
609+
result = workspace.evaluate(line, @eval_path, line_no)
600610
end
601611

602612
set_last_value(result)

lib/irb/ext/change-ws.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def home_workspace
1212
if defined? @home_workspace
1313
@home_workspace
1414
else
15-
@home_workspace = @workspace
15+
@home_workspace = workspace
1616
end
1717
end
1818

@@ -25,11 +25,11 @@ def home_workspace
2525
# See IRB::WorkSpace.new for more information.
2626
def change_workspace(*_main)
2727
if _main.empty?
28-
@workspace = home_workspace
28+
replace_workspace(home_workspace)
2929
return main
3030
end
3131

32-
@workspace = WorkSpace.new(_main[0])
32+
replace_workspace(WorkSpace.new(_main[0]))
3333

3434
if !(class<<main;ancestors;end).include?(ExtendCommandBundle)
3535
main.extend ExtendCommandBundle

lib/irb/ext/eval_history.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def set_last_value(value)
1818

1919
if defined?(@eval_history) && @eval_history
2020
@eval_history_values.push @line_no, @last_value
21-
@workspace.evaluate "__ = IRB.CurrentContext.instance_eval{@eval_history_values}"
21+
workspace.evaluate "__ = IRB.CurrentContext.instance_eval{@eval_history_values}"
2222
end
2323

2424
@last_value
@@ -49,7 +49,7 @@ def eval_history=(no)
4949
else
5050
@eval_history_values = EvalHistory.new(no)
5151
IRB.conf[:__TMP__EHV__] = @eval_history_values
52-
@workspace.evaluate("__ = IRB.conf[:__TMP__EHV__]")
52+
workspace.evaluate("__ = IRB.conf[:__TMP__EHV__]")
5353
IRB.conf.delete(:__TMP_EHV__)
5454
end
5555
else

lib/irb/ext/use-loader.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ def use_loader=(opt)
4949
if IRB.conf[:USE_LOADER] != opt
5050
IRB.conf[:USE_LOADER] = opt
5151
if opt
52-
(class<<@workspace.main;self;end).instance_eval {
52+
(class<<workspace.main;self;end).instance_eval {
5353
alias_method :load, :irb_load
5454
alias_method :require, :irb_require
5555
}
5656
else
57-
(class<<@workspace.main;self;end).instance_eval {
57+
(class<<workspace.main;self;end).instance_eval {
5858
alias_method :load, :__original__load__IRB_use_loader__
5959
alias_method :require, :__original__require__IRB_use_loader__
6060
}

lib/irb/ext/workspaces.rb

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,42 +6,23 @@
66

77
module IRB # :nodoc:
88
class Context
9-
10-
# Size of the current WorkSpace stack
11-
def irb_level
12-
workspace_stack.size
13-
end
14-
15-
# WorkSpaces in the current stack
16-
def workspaces
17-
if defined? @workspaces
18-
@workspaces
19-
else
20-
@workspaces = []
21-
end
22-
end
23-
249
# Creates a new workspace with the given object or binding, and appends it
2510
# onto the current #workspaces stack.
2611
#
2712
# See IRB::Context#change_workspace and IRB::WorkSpace.new for more
2813
# information.
2914
def push_workspace(*_main)
3015
if _main.empty?
31-
if workspaces.empty?
32-
print "No other workspace\n"
33-
return nil
16+
if @workspace_stack.size > 1
17+
# swap the top two workspaces
18+
previous_workspace, current_workspace = @workspace_stack.pop(2)
19+
@workspace_stack.push current_workspace, previous_workspace
20+
end
21+
else
22+
@workspace_stack.push WorkSpace.new(workspace.binding, _main[0])
23+
if !(class<<main;ancestors;end).include?(ExtendCommandBundle)
24+
main.extend ExtendCommandBundle
3425
end
35-
ws = workspaces.pop
36-
workspaces.push @workspace
37-
@workspace = ws
38-
return workspaces
39-
end
40-
41-
workspaces.push @workspace
42-
@workspace = WorkSpace.new(@workspace.binding, _main[0])
43-
if !(class<<main;ancestors;end).include?(ExtendCommandBundle)
44-
main.extend ExtendCommandBundle
4526
end
4627
end
4728

@@ -50,11 +31,7 @@ def push_workspace(*_main)
5031
#
5132
# Also, see #push_workspace.
5233
def pop_workspace
53-
if workspaces.empty?
54-
print "workspace stack empty\n"
55-
return
56-
end
57-
@workspace = workspaces.pop
34+
@workspace_stack.pop if @workspace_stack.size > 1
5835
end
5936
end
6037
end

test/irb/test_command.rb

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,8 @@ class Foo; end
482482
class CwwsTest < WorkspaceCommandTestCase
483483
def test_cwws_returns_the_current_workspace_object
484484
out, err = execute_lines(
485-
"cwws.class",
485+
"cwws",
486+
"self.class"
486487
)
487488

488489
assert_empty err
@@ -493,51 +494,56 @@ def test_cwws_returns_the_current_workspace_object
493494
class PushwsTest < WorkspaceCommandTestCase
494495
def test_pushws_switches_to_new_workspace_and_pushes_the_current_one_to_the_stack
495496
out, err = execute_lines(
496-
"pushws #{self.class}::Foo.new\n",
497-
"cwws.class",
497+
"pushws #{self.class}::Foo.new",
498+
"self.class",
499+
"popws",
500+
"self.class"
498501
)
499502
assert_empty err
500-
assert_include(out, "#{self.class}::Foo")
503+
504+
assert_match(/=> #{self.class}::Foo\n/, out)
505+
assert_match(/=> #{self.class}\n$/, out)
501506
end
502507

503508
def test_pushws_extends_the_new_workspace_with_command_bundle
504509
out, err = execute_lines(
505-
"pushws Object.new\n",
510+
"pushws Object.new",
506511
"self.singleton_class.ancestors"
507512
)
508513
assert_empty err
509514
assert_include(out, "IRB::ExtendCommandBundle")
510515
end
511516

512-
def test_pushws_prints_help_message_when_no_arg_is_given
517+
def test_pushws_prints_workspace_stack_when_no_arg_is_given
513518
out, err = execute_lines(
514-
"pushws\n",
519+
"pushws",
515520
)
516521
assert_empty err
517-
assert_match(/No other workspace/, out)
522+
assert_include(out, "[#<TestIRB::PushwsTe...>]")
518523
end
519-
end
520524

521-
class WorkspacesTest < WorkspaceCommandTestCase
522-
def test_workspaces_returns_the_array_of_non_main_workspaces
525+
def test_pushws_without_argument_swaps_the_top_two_workspaces
523526
out, err = execute_lines(
524-
"pushws #{self.class}::Foo.new\n",
525-
"workspaces.map { |w| w.class.name }",
527+
"pushws #{self.class}::Foo.new",
528+
"self.class",
529+
"pushws",
530+
"self.class"
526531
)
527-
528532
assert_empty err
529-
# self.class::Foo would be the current workspace
530-
# self.class would be the old workspace that's pushed to the stack
531-
assert_include(out, "=> [\"#{self.class}\"]")
533+
assert_match(/=> #{self.class}::Foo\n/, out)
534+
assert_match(/=> #{self.class}\n$/, out)
532535
end
536+
end
533537

534-
def test_workspaces_returns_empty_array_when_no_workspaces_were_added
538+
class WorkspacesTest < WorkspaceCommandTestCase
539+
def test_workspaces_returns_the_stack_of_workspaces
535540
out, err = execute_lines(
536-
"workspaces.map(&:to_s)",
541+
"pushws #{self.class}::Foo.new\n",
542+
"workspaces",
537543
)
538544

539545
assert_empty err
540-
assert_include(out, "=> []")
546+
assert_match(/\[#<TestIRB::Workspac...>, #<TestIRB::Workspac...>]\n/, out)
541547
end
542548
end
543549

@@ -557,7 +563,7 @@ def test_popws_prints_help_message_if_the_workspace_is_empty
557563
"popws\n",
558564
)
559565
assert_empty err
560-
assert_match(/workspace stack empty/, out)
566+
assert_match(/\[#<TestIRB::PopwsTes...>\]\n/, out)
561567
end
562568
end
563569

0 commit comments

Comments
 (0)