[ruby/irb] Fix nested IRB sessions' history saving

(https://github.com/ruby/irb/pull/652)

1. Dynamically including `HistorySavingAbility` makes things unnecessarily
   complicated and should be avoided.
2. Because both `Reline` and `Readline` use a single `HISTORY` constant
   to store history data. When nesting IRB sessions, only the first IRB
   session should handle history loading and saving so we can avoid
   duplicating history.
3. History saving callback should NOT be stored in `IRB.conf` as it's
   recreated every time `IRB.setup` is called, which would happen when
   nesting IRB sessions.

https://github.com/ruby/irb/commit/0fef0ae160
This commit is contained in:
Stan Lo 2023-08-09 15:57:47 +01:00 коммит произвёл git
Родитель 6acfc50bcc
Коммит ab0f90f1f5
5 изменённых файлов: 101 добавлений и 23 удалений

Просмотреть файл

@ -482,9 +482,16 @@ module IRB
end
def run(conf = IRB.conf)
in_nested_session = !!conf[:MAIN_CONTEXT]
conf[:IRB_RC].call(context) if conf[:IRB_RC]
conf[:MAIN_CONTEXT] = context
save_history = !in_nested_session && conf[:SAVE_HISTORY] && context.io.support_history_saving?
if save_history
context.io.load_history
end
prev_trap = trap("SIGINT") do
signal_handle
end
@ -496,6 +503,7 @@ module IRB
ensure
trap("SIGINT", prev_trap)
conf[:AT_EXIT].each{|hook| hook.call}
context.io.save_history if save_history
end
end

Просмотреть файл

@ -8,7 +8,6 @@ require_relative "workspace"
require_relative "inspector"
require_relative "input-method"
require_relative "output-method"
require_relative "history"
module IRB
# A class that wraps the current state of the irb session, including the
@ -130,8 +129,6 @@ module IRB
else
@io = input_method
end
self.save_history = IRB.conf[:SAVE_HISTORY] if IRB.conf[:SAVE_HISTORY]
@extra_doc_dirs = IRB.conf[:EXTRA_DOC_DIRS]
@echo = IRB.conf[:ECHO]
@ -154,13 +151,6 @@ module IRB
def save_history=(val)
IRB.conf[:SAVE_HISTORY] = val
if val
context = (IRB.conf[:MAIN_CONTEXT] || self)
if context.io.support_history_saving? && !context.io.singleton_class.include?(HistorySavingAbility)
context.io.extend(HistorySavingAbility)
end
end
end
def save_history

Просмотреть файл

@ -1,9 +1,7 @@
module IRB
module HistorySavingAbility # :nodoc:
def HistorySavingAbility.extended(obj)
IRB.conf[:AT_EXIT].push proc{obj.save_history}
obj.load_history
obj
def support_history_saving?
true
end
def load_history

Просмотреть файл

@ -5,6 +5,7 @@
#
require_relative 'completion'
require_relative "history"
require 'io/console'
require 'reline'
@ -167,6 +168,8 @@ module IRB
include ::Readline
end
include HistorySavingAbility
# Creates a new input method object using Readline
def initialize
self.class.initialize_readline
@ -219,10 +222,6 @@ module IRB
true
end
def support_history_saving?
true
end
# Returns the current line number for #io.
#
# #line counts the number of times #gets is called.
@ -250,6 +249,7 @@ module IRB
class RelineInputMethod < InputMethod
HISTORY = Reline::HISTORY
include HistorySavingAbility
# Creates a new input method object using Reline
def initialize
IRB.__send__(:set_encoding, Reline.encoding_system_needs.name, override: false)
@ -451,10 +451,6 @@ module IRB
str += " and #{inputrc_path}" if File.exist?(inputrc_path)
str
end
def support_history_saving?
true
end
end
class ReidlineInputMethod < RelineInputMethod

Просмотреть файл

@ -1,9 +1,12 @@
# frozen_string_literal: false
require 'irb'
require 'readline'
require "tempfile"
require_relative "helper"
return if RUBY_PLATFORM.match?(/solaris|mswin|mingw/i)
module TestIRB
class HistoryTest < TestCase
def setup
@ -205,4 +208,87 @@ module TestIRB
end
end
end
end if not RUBY_PLATFORM.match?(/solaris|mswin|mingw/i)
class NestedIRBHistoryTest < IntegrationTestCase
def test_history_saving_with_nested_sessions
write_history ""
write_ruby <<~'RUBY'
def foo
binding.irb
end
binding.irb
RUBY
run_ruby_file do
type "'outer session'"
type "foo"
type "'inner session'"
type "exit"
type "'outer session again'"
type "exit"
end
assert_equal <<~HISTORY, @history_file.open.read
'outer session'
foo
'inner session'
exit
'outer session again'
exit
HISTORY
end
def test_history_saving_with_nested_sessions_and_prior_history
write_history <<~HISTORY
old_history_1
old_history_2
old_history_3
HISTORY
write_ruby <<~'RUBY'
def foo
binding.irb
end
binding.irb
RUBY
run_ruby_file do
type "'outer session'"
type "foo"
type "'inner session'"
type "exit"
type "'outer session again'"
type "exit"
end
assert_equal <<~HISTORY, @history_file.open.read
old_history_1
old_history_2
old_history_3
'outer session'
foo
'inner session'
exit
'outer session again'
exit
HISTORY
end
private
def write_history(history)
@history_file = Tempfile.new('irb_history')
@history_file.write(history)
@history_file.close
@irbrc = Tempfile.new('irbrc')
@irbrc.write <<~RUBY
IRB.conf[:HISTORY_FILE] = "#{@history_file.path}"
RUBY
@irbrc.close
@envs['IRBRC'] = @irbrc.path
end
end
end