Skip to content
This repository was archived by the owner on Mar 12, 2023. It is now read-only.

Commit a03870a

Browse files
committed
Move logic related to custom sequence to a separated file
1 parent 4ad96ec commit a03870a

File tree

6 files changed

+125
-19
lines changed

6 files changed

+125
-19
lines changed

lib/ruby_jard.rb

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
require 'ruby_jard/repl_manager'
1818
require 'ruby_jard/repl_interceptor'
1919
require 'ruby_jard/repl_state'
20+
require 'ruby_jard/repl_sequence'
2021
require 'ruby_jard/screen_manager'
2122
require 'ruby_jard/reflection'
2223

lib/ruby_jard/pry_proxy.rb

+4-5
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,11 @@ def initialize(options = {})
5757
end
5858

5959
def handle_line(line, *args)
60-
index = line.to_s.rindex(RubyJard::ReplManager::COMMAND_ESCAPE_SEQUENCE)
61-
if !index.nil?
62-
command = line[(index + RubyJard::ReplManager::COMMAND_ESCAPE_SEQUENCE.length)..-1]
63-
super(command, *args)
64-
else
60+
command = RubyJard::ReplSequence.detect(line)
61+
if command.nil?
6562
super(line, *args)
63+
else
64+
super(command, *args)
6665
end
6766
ensure
6867
exec_hook :after_handle_line, *args, self

lib/ruby_jard/repl_interceptor.rb

+18-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# frozen_string_literal: true
22

3+
begin
4+
require 'pty'
5+
rescue LoadError
6+
# Ignore, fallback not to use interceptor
7+
end
8+
39
module RubyJard
410
# Pry depends heavily on GNU Readline, or any Readline-like input libraries. Those libraries
511
# serve limited use cases, and specific interface to support those. Unfortunately, to serve
@@ -44,8 +50,8 @@ class ReplInterceptor
4450
RubyJard::Keys::CTRL_C => (KEY_BINDING_INTERRUPT = :interrupt)
4551
}.freeze
4652

47-
KEY_READ_TIMEOUT = 0.2 # 200ms
48-
PTY_OUTPUT_TIMEOUT = 1.to_f / 60 # 60hz
53+
KEY_READ_TIMEOUT = 0.2 # 200ms
54+
OUTPUT_TICK = 1.to_f / 60 # 60hz
4955

5056
def initialize(state, console, key_bindings)
5157
@state = state
@@ -68,14 +74,16 @@ def start
6874
def stop
6975
@key_listen_thread&.exit if @key_listen_thread&.alive?
7076
if interceptable?
71-
sleep PTY_OUTPUT_TIMEOUT until @state.exited?
77+
sleep OUTPUT_TICK until @state.exited?
7278
else
7379
@state.exited!
7480
end
7581
end
7682

7783
def dispatch_command(command)
78-
@input_writer.write("#{RubyJard::ReplManager::COMMAND_ESCAPE_SEQUENCE}#{command}\n")
84+
@input_writer.write(
85+
"#{RubyJard::ReplSequence.encode(command)}\n"
86+
)
7987
end
8088

8189
def feed_output(content)
@@ -105,6 +113,7 @@ def redirected_output
105113
end
106114

107115
def interceptable?
116+
return false unless defined?(PTY)
108117
return false if defined?(Reline) && Readline == Reline
109118
return false if RubyJard::Reflection.instance.call_method(::Readline, :input=).source_location != nil
110119
return false if RubyJard::Reflection.instance.call_method(::Readline, :output=).source_location != nil
@@ -160,7 +169,7 @@ def output_bridge
160169
@state.exited!
161170
end
162171
elsif @state.exited?
163-
sleep PTY_OUTPUT_TIMEOUT
172+
sleep OUTPUT_TICK
164173
else
165174
content = @output_reader.read_nonblock(2048)
166175
unless content.nil?
@@ -169,7 +178,7 @@ def output_bridge
169178
end
170179
rescue IO::WaitReadable, IO::WaitWritable
171180
# Retry
172-
sleep PTY_OUTPUT_TIMEOUT
181+
sleep OUTPUT_TICK
173182
end
174183
rescue StandardError
175184
# This thread shoud never die, or the user may be freezed, and cannot type anything
@@ -184,7 +193,7 @@ def listen_key_press
184193

185194
if @state.processing? && @state.pager?
186195
# Discard all keys unfortunately
187-
sleep PTY_OUTPUT_TIMEOUT
196+
sleep OUTPUT_TICK
188197
else
189198
key = @key_bindings.match { @console.getch(KEY_READ_TIMEOUT) }
190199
if key.is_a?(RubyJard::KeyBinding)
@@ -217,7 +226,7 @@ def handle_interrupt_command
217226
end
218227
loop do
219228
begin
220-
sleep PTY_OUTPUT_TIMEOUT
229+
sleep OUTPUT_TICK
221230
rescue Interrupt
222231
# Interrupt spam. Ignore.
223232
end
@@ -226,7 +235,7 @@ def handle_interrupt_command
226235
end
227236

228237
def write_output(content)
229-
return if content.include?(RubyJard::ReplManager::COMMAND_ESCAPE_SEQUENCE)
238+
return if RubyJard::ReplSequence.detect(content)
230239

231240
@console.write content.force_encoding('UTF-8')
232241
end

lib/ruby_jard/repl_manager.rb

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
# frozen_string_literal: true
22

3-
require 'pty'
43
require 'ruby_jard/pager'
54
require 'ruby_jard/pry_proxy'
65

76
module RubyJard
87
##
98
# Manage the dance between REPL components
109
class ReplManager
11-
# Escape sequence used to mark command from key binding
12-
COMMAND_ESCAPE_SEQUENCE = '\e]711;Command~'
13-
PTY_OUTPUT_TIMEOUT = 1.to_f / 60 # 60hz
10+
OUTPUT_TICK = 1.to_f / 60 # 60hz
1411

1512
def initialize(console:, key_bindings: nil)
1613
@console = console
@@ -53,7 +50,7 @@ def pry_proxy
5350
set_console_cooked!
5451
@state.processing!
5552
# Sleep 2 ticks, wait for pry to print out all existing output in the queue
56-
sleep PTY_OUTPUT_TIMEOUT * 2
53+
sleep OUTPUT_TICK * 2
5754
},
5855
after_handle_line: proc {
5956
set_console_raw!

lib/ruby_jard/repl_sequence.rb

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# frozen_string_literal: true
2+
3+
module RubyJard
4+
##
5+
# Feed a custom escape sequence into Repl, then detect and parse
6+
# to dispatch captured command.
7+
class ReplSequence
8+
COMMAND_ESCAPE_SEQUENCE_PREFIX = '\e]711;Command~'
9+
COMMAND_ESCAPE_SEQUENCE_SUFFIX = ';'
10+
COMMAND_ESCAPE_SEQUENCE_REGEXP = /\\e\]711;Command~([a-z\-\ ]*);/.freeze
11+
12+
def self.encode(command)
13+
if command.nil? || command.empty?
14+
''
15+
else
16+
"#{COMMAND_ESCAPE_SEQUENCE_PREFIX}#{command}#{COMMAND_ESCAPE_SEQUENCE_SUFFIX}"
17+
end
18+
end
19+
20+
def self.detect(content)
21+
matches = COMMAND_ESCAPE_SEQUENCE_REGEXP.match(content)
22+
if matches.nil?
23+
nil
24+
else
25+
matches[1]
26+
end
27+
end
28+
end
29+
end

spec/ruby_jard/repl_sequence_spec.rb

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RubyJard::ReplSequence do
4+
describe '#encode' do
5+
context 'when command is nil' do
6+
it 'returns an empty string' do
7+
expect(described_class.encode(nil)).to eql('')
8+
end
9+
end
10+
11+
context 'when command is empty' do
12+
it 'returns an empty string' do
13+
expect(described_class.encode('')).to eql('')
14+
end
15+
end
16+
17+
context 'when command is present' do
18+
it 'returns escaped sequence' do
19+
expect(described_class.encode('hello')).to eql('\e]711;Command~hello;')
20+
end
21+
end
22+
end
23+
24+
describe '#detect' do
25+
context 'when content is empty' do
26+
it 'returns nil' do
27+
expect(described_class.detect('')).to be(nil)
28+
end
29+
end
30+
31+
context 'when content is nil' do
32+
it 'returns nil' do
33+
expect(described_class.detect(nil)).to be(nil)
34+
end
35+
end
36+
37+
context 'when content does not contain an escaped sequence' do
38+
it 'returns nil' do
39+
expect(described_class.detect('hello 123')).to be(nil)
40+
end
41+
end
42+
43+
context 'when content contains an empty escaped sequence' do
44+
it 'returns nil' do
45+
expect(described_class.detect('\e]711;Command~')).to be(nil)
46+
end
47+
end
48+
49+
context 'when content contains a valid escaped sequence' do
50+
it 'returns mentioned command' do
51+
expect(described_class.detect('1251\e]711;Command~list;abcabc')).to eql('list')
52+
end
53+
end
54+
55+
context 'when content contains an escaped sequence with spaces' do
56+
it 'returns mentioned command' do
57+
expect(
58+
described_class.detect('1251\e]711;Command~jard filter switch;abcabc')
59+
).to eql('jard filter switch')
60+
end
61+
end
62+
63+
context 'when content contains multiple escaped sequences' do
64+
it 'returns first mentioned command' do
65+
expect(
66+
described_class.detect('1251\e]711;Command~list;abcabc\e]711;Command~patch;123')
67+
).to eql('list')
68+
end
69+
end
70+
end
71+
end

0 commit comments

Comments
 (0)