1
0
Fork 0
mirror of https://github.com/pry/pry.git synced 2022-11-09 12:35:05 -05:00

Updated Windows Ansi escape codes detection, through ConPty.

Fixed unit tests to also work on Windows.
This commit is contained in:
SilverPhoenix99 2021-07-04 10:27:44 +01:00
parent b58c7bd86c
commit 7c08e24dba
13 changed files with 157 additions and 97 deletions

View file

@ -37,7 +37,8 @@ class Pry
def use_ansi_codes?
Pry::Helpers::Platform.windows_ansi? ||
((term = Pry::Env['TERM']) && term != "dumb")
Pry::Helpers::Platform.windows_conpty? ||
(term = Pry::Env['TERM']; term != nil && term != "dumb")
end
def colorize_code(code)

View file

@ -10,25 +10,60 @@ class Pry
module Platform
# @return [Boolean]
def self.mac_osx?
!!(RbConfig::CONFIG['host_os'] =~ /\Adarwin/i)
RbConfig::CONFIG['host_os'].match? /\Adarwin/i
end
# @return [Boolean]
def self.linux?
!!(RbConfig::CONFIG['host_os'] =~ /linux/i)
RbConfig::CONFIG['host_os'].match? /linux/i
end
# @return [Boolean] true when Pry is running on Windows with ANSI support,
# false otherwise
def self.windows?
!!(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/)
RbConfig::CONFIG['host_os'].match? /mswin|mingw/
end
# Checks older version of Windows console that required alternative
# libraries to work with Ansi escapes codes.
# @return [Boolean]
def self.windows_ansi?
return false unless windows?
# ensures that ConPty isn't available before checking anything else
windows? && !windows_conpty? && !!(defined?(Win32::Console) || Pry::Env['ANSICON'] || mri_2?)
end
!!(defined?(Win32::Console) || Pry::Env['ANSICON'] || mri_2?)
# New version of Windows console that understands Ansi escapes codes.
# @return [Boolean]
def self.windows_conpty?
@conpty ||= windows? && begin
require 'fiddle/import'
require 'fiddle/types'
kernel32 = Module.new do
extend Fiddle::Importer
dlload 'kernel32'
include Fiddle::Win32Types
extern 'HANDLE GetStdHandle(DWORD)'
extern 'BOOL GetConsoleMode(HANDLE, DWORD*)'
end
mode = kernel32.create_value('DWORD')
std_output_handle = -11
enable_virtual_terminal_processing = 0x4
stdout_handle = kernel32.GetStdHandle(std_output_handle)
stdout_handle > 0 &&
kernel32.GetConsoleMode(stdout_handle, mode) != 0 &&
mode.value & enable_virtual_terminal_processing != 0
rescue
false
ensure
Fiddle.free mode.to_ptr if mode
kernel32.handler.handlers.each(&:close) if kernel32
end
end
# @return [Boolean]
@ -53,7 +88,7 @@ class Pry
# @return [Boolean]
def self.mri_2?
mri? && RUBY_VERSION.start_with?('2')
mri? && RUBY_VERSION.start_with?('2.')
end
end
end

View file

@ -144,7 +144,7 @@ class Pry
@system_pager =
begin
pager_executable = default_pager.split(' ').first
if Helpers::Platform.windows? || Helpers::Platform.windows_ansi?
if Helpers::Platform.windows?
`where /Q #{pager_executable}`
else
`which #{pager_executable}`

View file

@ -143,7 +143,12 @@ you can add "Pry.config.windows_console_warning = false" to your pryrc.
load_requires if Pry.config.should_load_requires
load_history if Pry.config.history_load
load_traps if Pry.config.should_trap_interrupts
load_win32console if Helpers::Platform.windows? && !Helpers::Platform.windows_ansi?
windows_no_ansi = Helpers::Platform.windows? &&
!Helpers::Platform.windows_ansi? &&
!Helpers::Platform.windows_conpty?
load_win32console if windows_no_ansi
end
# Start a Pry REPL.

View file

@ -2,8 +2,8 @@
describe "hist" do
before do
Pry.history.clear
@hist = Pry.history
# different platforms require different types of Readline, so best not to rely on it for these tests:
@hist = Pry.history = Pry::History.new
@str_output = StringIO.new
@t = pry_tester history: @hist do

View file

@ -13,6 +13,7 @@ RSpec.describe Pry::Config do
specify { expect(subject.unrescued_exceptions).to be_an(Array) }
specify { expect(subject.hooks).to be_a(Pry::Hooks) }
specify { expect(subject.pager).to be(true).or be(false) }
specify { expect(subject.escape_headers).to be(true).or be(false) }
specify { expect(subject.system).to be_a(Method) }
specify { expect(subject.color).to be(true).or be(false) }
specify { expect(subject.default_window_size).to be_a(Numeric) }

View file

@ -57,6 +57,7 @@ describe Pry::Editor do
context "when no editor is detected" do
before do
allow(Pry::Helpers::Platform).to receive(:windows?).and_return(false)
allow(ENV).to receive(:key?).and_return(false)
allow(Kernel).to receive(:system)
end
@ -71,7 +72,11 @@ describe Pry::Editor do
end
end
describe "build_editor_invocation_string", skip: !Pry::Helpers::Platform.windows? do
describe "build_editor_invocation_string" do
before do
allow(Pry::Helpers::Platform).to receive(:windows?).and_return(false)
end
it 'should shell-escape files' do
invocation_str = @editor.build_editor_invocation_string(@tf_path, 5, true)
expect(invocation_str).to match(/#{@tf_dir}.+hello\\ world\.rb/)

View file

@ -4,21 +4,13 @@ require 'tempfile'
require 'rbconfig'
RSpec.describe Pry::History do
# different platforms require different types of Readline, so best not to rely on it for these tests:
let(:history) { Pry::History.new }
before do
Pry.history.clear
@saved_history = "1\n2\n3\ninvalid\0 line\n"
Pry.history.loader = proc do |&blk|
@saved_history.lines.each { |l| blk.call(l) }
end
Pry.load_history
end
after do
Pry.history.clear
Pry.history.instance_variable_set(:@original_lines, 0)
# don't write anywhere by default
history.saver = ->(_line) {}
end
describe ".default_file" do
@ -51,34 +43,34 @@ RSpec.describe Pry::History do
context "when $XDG_DATA_HOME is defined" do
it "returns config location relative to $XDG_DATA_HOME" do
stub_hist has_default: false, xdg_home: '/my/path'
expect(described_class.default_file).to eq('/my/path/pry/pry_history')
expect(described_class.default_file).to end_with('/my/path/pry/pry_history')
end
it "returns config location relative to $XDG_DATA_HOME when ~/.pryrc exists" do
stub_hist has_default: true, xdg_home: '/my/path'
expect(described_class.default_file).to eq('/my/path/pry/pry_history')
expect(described_class.default_file).to end_with('/my/path/pry/pry_history')
end
end
end
describe '#push' do
it "does not record duplicated lines" do
Pry.history << '3'
Pry.history << '_ += 1'
Pry.history << '_ += 1'
expect(Pry.history.to_a.grep('_ += 1').count).to eq 1
history << '3'
history << '_ += 1'
history << '_ += 1'
expect(history.to_a.grep('_ += 1').count).to eq 1
end
it "does not record lines that contain a NULL byte" do
c = Pry.history.to_a.size
Pry.history << "a\0b"
expect(Pry.history.to_a.size).to eq c
c = history.to_a.size
history << "a\0b"
expect(history.to_a.size).to eq c
end
it "does not record empty lines" do
c = Pry.history.to_a.size
Pry.history << ''
expect(Pry.history.to_a.size).to eq c
c = history.to_a.size
history << ''
expect(history.to_a.size).to eq c
end
end
@ -87,8 +79,8 @@ RSpec.describe Pry::History do
@old_file = Pry.config.history_file
@hist_file_path = File.expand_path('spec/fixtures/pry_history')
Pry.config.history_file = @hist_file_path
Pry.history.clear
Pry.load_history
history.clear
history.load
end
after do
@ -96,15 +88,15 @@ RSpec.describe Pry::History do
end
it "clears this session's history" do
expect(Pry.history.to_a.size).to be > 0
Pry.history.clear
expect(Pry.history.to_a.size).to eq 0
expect(Pry.history.original_lines).to eq 0
expect(history.to_a.size).to be > 0
history.clear
expect(history.to_a.size).to eq 0
expect(history.original_lines).to eq 0
end
it "doesn't affect the contents of the history file" do
expect(Pry.history.to_a.size).to eq 3
Pry.history.clear
expect(history.to_a.size).to eq 3
history.clear
File.open(@hist_file_path, 'r') do |fh|
file = fh.to_a
@ -117,28 +109,37 @@ RSpec.describe Pry::History do
describe "#history_line_count" do
it "counts entries in history" do
Pry.history.clear
history.clear
saved_history = "olgierd\ngustlik\njanek\ngrzes\ntomek\n"
Pry.history.loader = proc do |&blk|
history.loader = proc do |&blk|
saved_history.lines.each { |l| blk.call(l) }
end
Pry.load_history
history.load
expect(Pry.history.history_line_count).to eq 5
expect(history.history_line_count).to eq 5
end
end
describe "#session_line_count" do
it "returns the number of lines in history from just this session" do
Pry.history << 'you?'
Pry.history << 'you are so precious'
expect(Pry.history.session_line_count).to eq 2
history << 'you?'
history << 'you are so precious'
expect(history.session_line_count).to eq 2
end
end
describe ".load_history" do
before do
history.loader = proc do |&blk|
"1\n2\n3\ninvalid\0 line\n".each_line { |l| blk.call(l) }
end
history.load
end
it "reads the contents of the file" do
expect(Pry.history.to_a[-2..-1]).to eq %w[2 3]
expect(history.to_a[-2..-1]).to eq %w[2 3]
end
end
@ -203,13 +204,18 @@ RSpec.describe Pry::History do
[Errno::EACCES, Errno::ENOENT].each do |error_class|
it "handles #{error_class} failure to read from history" do
expect(File).to receive(:foreach).and_raise(error_class)
allow(File).to receive(:exist?).and_return(true)
allow(File).to receive(:foreach).and_raise(error_class)
expect(history).to receive(:warn).with(/Unable to read history file:/)
expect { history.load }.to_not raise_error
end
it "handles #{error_class} failure to write history" do
Pry.config.history_save = true
# restore default saver
history.saver = history.method(:save_to_file)
expect(File).to receive(:open).with(file_path, "a", 0o600).and_raise(error_class)
expect(history).to receive(:warn).with(/Unable to write history file:/)
expect { history.push("anything") }.to_not raise_error

View file

@ -2,9 +2,9 @@
require 'rbconfig'
RSpec.describe 'Bundler' do
let(:ruby) { RbConfig.ruby.shellescape }
let(:pry_dir) { File.expand_path(File.join(__FILE__, '../../../lib')).shellescape }
RSpec.describe 'Bundler', slow: true do
let(:ruby) { RbConfig.ruby }
let(:pry_dir) { File.expand_path(File.join(__FILE__, '../../../lib')) }
context "when Pry requires Gemfile, which doesn't specify Pry as a dependency" do
it "loads auto-completion correctly" do
@ -23,7 +23,7 @@ RSpec.describe 'Bundler' do
end
exit 42 if Pry.config.completer
RUBY
`#{ruby} -I#{pry_dir} -e'#{code}'`
IO.popen([ruby, '-I', pry_dir, '-e', code, err: [:child, :out]], &:read)
expect($CHILD_STATUS.exitstatus).to eq(42)
end
end

View file

@ -2,39 +2,48 @@
require 'rbconfig'
RSpec.describe 'The bin/pry CLI' do
let(:ruby) { RbConfig.ruby.shellescape }
let(:pry_dir) { File.expand_path(File.join(__FILE__, '../../../lib')).shellescape }
let(:clean_output) do
# Pry will emit silent garbage because of our auto indent feature.
# This lambda cleans the output of that garbage.
->(out) { out.strip.sub("\e[0G", "") }
end
RSpec.describe 'The bin/pry CLI', slow: true do
let(:call_pry) {
->(*args) {
pry_dir = File.expand_path(File.join(__FILE__, '../../../lib'))
# the :err option is equivalent to 2>&1
out = IO.popen([RbConfig.ruby, "-I", pry_dir, 'bin/pry', *args, err: [:child, :out]], &:read)
status = $CHILD_STATUS
# Pry will emit silent garbage because of our auto indent feature.
# This lambda cleans the output of that garbage.
out = out.strip.sub(/^\e\[0[FG]/, "")
[out, status]
}
}
context "ARGV forwarding" do
let(:code) { "p(ARGV) and exit".shellescape }
let(:code) { "p(ARGV) and exit" }
it "forwards ARGV as an empty array when - is passed without following arguments" do
out = clean_output.call(`#{ruby} -I#{pry_dir} bin/pry -e #{code} -`)
out, status = call_pry.call('-e', code, '-')
expect(status).to be_success
expect(out).to eq([].inspect)
end
it "forwards ARGV as an empty array when -- is passed without following arguments" do
out = clean_output.call(`#{ruby} -I#{pry_dir} bin/pry -e #{code} --`)
out, status = call_pry.call('-e', code, '--')
expect(status).to be_success
expect(out).to eq([].inspect)
end
it "forwards its remaining arguments as ARGV when - is passed" do
out = clean_output.call(
`#{ruby} -I#{pry_dir} bin/pry -e #{code} - 1 -foo --bar --baz daz`
)
out, status = call_pry.call('-e', code, '-', '1', '-foo', '--bar', '--baz', 'daz')
expect(status).to be_success
expect(out).to eq(%w[1 -foo --bar --baz daz].inspect)
end
it "forwards its remaining arguments as ARGV when -- is passed" do
out = clean_output.call(
`#{ruby} -I#{pry_dir} bin/pry -e #{code} -- 1 -foo --bar --baz daz`
)
out, status = call_pry.call('-e', code, '--', '1', '-foo', '--bar', '--baz', 'daz')
expect(status).to be_success
expect(out).to eq(%w[1 -foo --bar --baz daz].inspect)
end
end
@ -42,19 +51,15 @@ RSpec.describe 'The bin/pry CLI' do
context '-I path' do
it 'adds an additional path to $LOAD_PATH' do
code = 'p($LOAD_PATH) and exit'
out = clean_output.call(
`#{ruby} -I#{pry_dir} bin/pry -I /added/at/cli -e '#{code}'`
)
out, status = call_pry.call('-I', '/added/at/cli', '-e', code)
expect(status).to be_success
expect(out).to include('/added/at/cli')
end
it 'adds multiple additional paths to $LOAD_PATH' do
code = 'p($LOAD_PATH) and exit'
out = clean_output.call(
# rubocop:disable Metrics/LineLength
`#{ruby} -I#{pry_dir} bin/pry -I /added-1/at/cli -I /added/at/cli/also -e '#{code}'`
# rubocop:enable Metrics/LineLength
)
out, status = call_pry.call('-I', '/added-1/at/cli', '-I', '/added/at/cli/also', '-e', code)
expect(status).to be_success
expect(out).to include('/added-1/at/cli')
expect(out).to include('/added/at/cli/also')
end

View file

@ -1,12 +1,11 @@
# frozen_string_literal: true
require "shellwords"
require 'rbconfig'
RSpec.describe "Hanami integration" do
RSpec.describe "Hanami integration", slow: true do
before :all do
@ruby = RbConfig.ruby.shellescape
@pry_dir = File.expand_path(File.join(__FILE__, '../../../lib')).shellescape
@ruby = RbConfig.ruby
@pry_dir = File.expand_path(File.join(__FILE__, '../../../lib'))
end
it "does not enter an infinite loop (#1471, #1621)" do
@ -36,7 +35,7 @@ RSpec.describe "Hanami integration" do
Timeout.timeout(1) { Action.new.call("define prison, in the abstract sense") }
exit 42
RUBY
`#{@ruby} -I#{@pry_dir} -e'#{code}'`
IO.popen([@ruby, '-I', @pry_dir, '-e', code, err: [:child, :out]], &:read)
expect($CHILD_STATUS.exitstatus).to eq(42)
end
end

View file

@ -3,13 +3,12 @@
# These specs ensure that Pry doesn't require readline until the first time a
# REPL is started.
require "shellwords"
require 'rbconfig'
RSpec.describe "Readline" do
RSpec.describe "Readline", slow: true do
before :all do
@ruby = RbConfig.ruby.shellescape
@pry_dir = File.expand_path(File.join(__FILE__, '../../../lib')).shellescape
@ruby = RbConfig.ruby
@pry_dir = File.expand_path(File.join(__FILE__, '../../../lib'))
end
it "is not loaded on requiring 'pry'" do
@ -17,7 +16,8 @@ RSpec.describe "Readline" do
require "pry"
p defined?(Readline)
RUBY
expect(`#{@ruby} -I #{@pry_dir} -e '#{code}'`).to eq("nil\n")
out = IO.popen([@ruby, '-I', @pry_dir, '-e', code, err: [:child, :out]], &:read)
expect(out).to eq("nil\n")
end
it "is loaded on invoking 'pry'" do
@ -26,7 +26,8 @@ RSpec.describe "Readline" do
Pry.start self, input: StringIO.new("exit-all"), output: StringIO.new
puts defined?(Readline)
RUBY
expect(`#{@ruby} -I #{@pry_dir} -e '#{code}'`.end_with?("constant\n")).to eq(true)
out = IO.popen([@ruby, '-I', @pry_dir, '-e', code, err: [:child, :out]], &:read)
expect(out).to end_with("constant\n")
end
it "is not loaded on invoking 'pry' if Pry.input is set" do
@ -36,6 +37,7 @@ RSpec.describe "Readline" do
Pry.start self, output: StringIO.new
p defined?(Readline)
RUBY
expect(`#{@ruby} -I #{@pry_dir} -e '#{code}'`.end_with?("nil\n")).to eq(true)
out = IO.popen([@ruby, '-I', @pry_dir, '-e', code, err: [:child, :out]], &:read)
expect(out).to end_with("nil\n")
end
end

View file

@ -87,7 +87,7 @@ describe Pry::REPL do
end
end
ReplTester.start(commands: set) do
ReplTester.start(commands: set, auto_indent: true) do
input 'def x'
output ''
prompt(/\* $/)
@ -125,6 +125,7 @@ describe Pry::REPL do
describe "autoindent" do
it "should raise no exception when indented with a tab" do
allow(Pry::Helpers::BaseHelpers).to receive(:use_ansi_codes?).and_return(true)
ReplTester.start(correct_indent: true, auto_indent: true) do
output = @pry.config.output
def output.tty?