yap-rawline 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/examples/rawline_shell.rb +1 -4
- data/lib/rawline.rb +3 -0
- data/lib/rawline/completer.rb +4 -0
- data/lib/rawline/dom_tree.rb +18 -0
- data/lib/rawline/editor.rb +443 -391
- data/lib/rawline/event_loop.rb +63 -26
- data/lib/rawline/non_blocking_input.rb +34 -0
- data/lib/rawline/renderer.rb +29 -0
- data/lib/rawline/terminal.rb +58 -3
- data/lib/rawline/terminal/vt220_terminal.rb +2 -2
- data/spec/editor_spec.rb +147 -92
- data/spec/spec_helper.rb +2 -0
- metadata +26 -7
data/lib/rawline/event_loop.rb
CHANGED
@@ -5,6 +5,7 @@ module RawLine
|
|
5
5
|
def initialize(registry:)
|
6
6
|
@registry = registry
|
7
7
|
@events = []
|
8
|
+
@counter = 0
|
8
9
|
end
|
9
10
|
|
10
11
|
# event looks like:
|
@@ -12,46 +13,79 @@ module RawLine
|
|
12
13
|
# * source
|
13
14
|
# * target
|
14
15
|
# * payload
|
15
|
-
def add_event(**event)
|
16
|
+
def add_event(**event, &blk)
|
17
|
+
unless event.has_key?(:_event_callback)
|
18
|
+
event[:_event_callback] = blk if blk
|
19
|
+
end
|
20
|
+
|
21
|
+
unless event.has_key?(:_event_id)
|
22
|
+
@counter += 1
|
23
|
+
event[:_event_id] = @counter
|
24
|
+
end
|
25
|
+
|
16
26
|
# if the last event is the same as the incoming then do there is no
|
17
27
|
# need to add it again. For example, rendering events that already
|
18
28
|
# back can be squashed into a single event.
|
19
29
|
if @events.last != event
|
20
30
|
@events << event
|
31
|
+
event[:_event_id]
|
32
|
+
else
|
33
|
+
@events.last[:_event_id]
|
21
34
|
end
|
22
35
|
end
|
23
36
|
|
24
|
-
def
|
25
|
-
|
26
|
-
# TODO: implement
|
27
|
-
elsif event
|
28
|
-
add_event event.merge(recur: { interval_in_ms: interval_in_ms, recur_at: recur_at(interval_in_ms) })
|
29
|
-
else
|
30
|
-
raise "Must pass in a block or an event."
|
31
|
-
end
|
37
|
+
def clear(event_id)
|
38
|
+
@events = @events.reject { |event| event[:_event_id] == event_id }
|
32
39
|
end
|
33
40
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
41
|
+
def reset
|
42
|
+
@events.clear
|
43
|
+
@counter = 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def once(interval_in_ms:, **event, &blk)
|
47
|
+
add_event event.merge(once: { run_at: recur_at(interval_in_ms) }), &blk
|
48
|
+
end
|
49
|
+
|
50
|
+
def recur(interval_in_ms:, **event, &blk)
|
51
|
+
add_event event.merge(recur: { interval_in_ms: interval_in_ms, recur_at: recur_at(interval_in_ms) }), &blk
|
52
|
+
end
|
53
|
+
|
54
|
+
def tick
|
55
|
+
event = @events.shift
|
56
|
+
if event
|
57
|
+
recur = event[:recur]
|
58
|
+
once = event[:once]
|
59
|
+
if recur
|
60
|
+
if current_time_in_ms >= recur[:recur_at]
|
61
|
+
dispatch_event(event)
|
62
|
+
interval_in_ms = recur[:interval_in_ms]
|
63
|
+
add_event event.merge(recur: { interval_in_ms: interval_in_ms, recur_at: recur_at(interval_in_ms) } )
|
49
64
|
else
|
65
|
+
# put it back on the queue
|
66
|
+
@events << event
|
67
|
+
dispatch_event(default_event)
|
68
|
+
end
|
69
|
+
elsif once
|
70
|
+
if current_time_in_ms >= once[:run_at]
|
50
71
|
dispatch_event(event)
|
72
|
+
else
|
73
|
+
# put it back on the queue
|
74
|
+
@events << event
|
75
|
+
# add_event event
|
76
|
+
dispatch_event(default_event)
|
51
77
|
end
|
52
78
|
else
|
53
|
-
dispatch_event(
|
79
|
+
dispatch_event(event)
|
54
80
|
end
|
81
|
+
else
|
82
|
+
dispatch_event(default_event)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def start
|
87
|
+
loop do
|
88
|
+
tick
|
55
89
|
end
|
56
90
|
end
|
57
91
|
|
@@ -62,7 +96,7 @@ module RawLine
|
|
62
96
|
end
|
63
97
|
|
64
98
|
def default_event
|
65
|
-
{ name: 'default', source: self }
|
99
|
+
{ name: 'default', source: self, _event_id: -1 }
|
66
100
|
end
|
67
101
|
|
68
102
|
def recur_at(interval_in_ms)
|
@@ -73,6 +107,9 @@ module RawLine
|
|
73
107
|
@registry.subscribers_for_event(event[:name]).each do |subscriber|
|
74
108
|
subscriber.call(event)
|
75
109
|
end
|
110
|
+
|
111
|
+
callback = event[:_event_callback]
|
112
|
+
callback.call(event) if callback
|
76
113
|
end
|
77
114
|
end
|
78
115
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module RawLine
|
2
|
+
class NonBlockingInput
|
3
|
+
DEFAULT_WAIT_TIMEOUT_IN_SECONDS = 0.01
|
4
|
+
|
5
|
+
attr_accessor :wait_timeout_in_seconds
|
6
|
+
|
7
|
+
def initialize(input)
|
8
|
+
@input = input
|
9
|
+
restore_default_timeout
|
10
|
+
end
|
11
|
+
|
12
|
+
def restore_default_timeout
|
13
|
+
@wait_timeout_in_seconds = DEFAULT_WAIT_TIMEOUT_IN_SECONDS
|
14
|
+
end
|
15
|
+
|
16
|
+
def read
|
17
|
+
bytes = []
|
18
|
+
begin
|
19
|
+
file_descriptor_flags = @input.fcntl(Fcntl::F_GETFL, 0)
|
20
|
+
loop do
|
21
|
+
string = @input.read_nonblock(4096)
|
22
|
+
bytes.concat string.bytes
|
23
|
+
end
|
24
|
+
rescue IO::WaitReadable
|
25
|
+
# reset flags so O_NONBLOCK is turned off on the file descriptor
|
26
|
+
# if it was turned on during the read_nonblock above
|
27
|
+
retry if IO.select([@input], [], [], @wait_timeout_in_seconds)
|
28
|
+
|
29
|
+
@input.fcntl(Fcntl::F_SETFL, file_descriptor_flags)
|
30
|
+
end
|
31
|
+
bytes
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module RawLine
|
2
|
+
class Renderer
|
3
|
+
def initialize(dom:, output:, width:, height:)
|
4
|
+
@dom = dom
|
5
|
+
@output = output
|
6
|
+
@renderer = TerminalLayout::TerminalRenderer.new(output: output)
|
7
|
+
@render_tree = TerminalLayout::RenderTree.new(
|
8
|
+
dom,
|
9
|
+
parent: nil,
|
10
|
+
style: { width: width, height: height },
|
11
|
+
renderer: @renderer
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
def render(reset: false)
|
16
|
+
@render_tree.layout
|
17
|
+
@renderer.render(@render_tree, reset: reset)
|
18
|
+
end
|
19
|
+
|
20
|
+
def render_cursor(input_box)
|
21
|
+
@renderer.render_cursor(input_box)
|
22
|
+
end
|
23
|
+
|
24
|
+
def update_dimensions(width:, height:)
|
25
|
+
@render_tree.width = width
|
26
|
+
@render_tree.height = height
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/rawline/terminal.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'terminfo'
|
4
4
|
require 'io/console'
|
5
5
|
require 'ostruct'
|
6
|
+
require 'termios'
|
6
7
|
|
7
8
|
#
|
8
9
|
# terminal.rb
|
@@ -23,22 +24,25 @@ module RawLine
|
|
23
24
|
# RawLine::Editor.
|
24
25
|
#
|
25
26
|
class Terminal
|
26
|
-
|
27
27
|
include HighLine::SystemExtensions
|
28
28
|
|
29
|
-
attr_accessor :escape_codes
|
29
|
+
attr_accessor :escape_codes, :input, :output
|
30
30
|
attr_reader :keys, :escape_sequences
|
31
31
|
|
32
32
|
#
|
33
33
|
# Create an instance of RawLine::Terminal.
|
34
34
|
#
|
35
|
-
def initialize
|
35
|
+
def initialize(input, output)
|
36
|
+
@input = input
|
37
|
+
@output = output
|
38
|
+
@snapshotted_tty_attrs = []
|
36
39
|
@keys =
|
37
40
|
{
|
38
41
|
:tab => [?\t.ord],
|
39
42
|
:return => [?\r.ord],
|
40
43
|
:newline => [?\n.ord],
|
41
44
|
:escape => [?\e.ord],
|
45
|
+
:space => [32],
|
42
46
|
|
43
47
|
:ctrl_a => [?\C-a.ord],
|
44
48
|
:ctrl_b => [?\C-b.ord],
|
@@ -74,6 +78,39 @@ module RawLine
|
|
74
78
|
|
75
79
|
CursorPosition = Struct.new(:column, :row)
|
76
80
|
|
81
|
+
def raw!
|
82
|
+
@input.raw!
|
83
|
+
end
|
84
|
+
|
85
|
+
def cooked!
|
86
|
+
@input.cooked!
|
87
|
+
end
|
88
|
+
|
89
|
+
def pseudo_cooked!
|
90
|
+
old_tty_attrs = Termios.tcgetattr(@input)
|
91
|
+
new_tty_attrs = old_tty_attrs.dup
|
92
|
+
|
93
|
+
|
94
|
+
new_tty_attrs.cflag |= Termios::BRKINT | Termios::ISTRIP | Termios::ICRNL | Termios::IXON
|
95
|
+
|
96
|
+
new_tty_attrs.iflag |= Termios::ICRNL | Termios::IGNBRK
|
97
|
+
|
98
|
+
new_tty_attrs.oflag |= Termios::OPOST
|
99
|
+
|
100
|
+
new_tty_attrs.lflag &= ~Termios::ECHONL
|
101
|
+
new_tty_attrs.lflag |= Termios::ECHO | Termios::ECHOE | Termios::ECHOK | Termios::ICANON | Termios::ISIG | Termios::IEXTEN
|
102
|
+
|
103
|
+
Termios::tcsetattr(@input, Termios::TCSANOW, new_tty_attrs)
|
104
|
+
end
|
105
|
+
|
106
|
+
def snapshot_tty_attrs
|
107
|
+
@snapshotted_tty_attrs << Termios.tcgetattr(@input)
|
108
|
+
end
|
109
|
+
|
110
|
+
def restore_tty_attrs
|
111
|
+
Termios::tcsetattr(@input, Termios::TCSANOW, @snapshotted_tty_attrs.pop)
|
112
|
+
end
|
113
|
+
|
77
114
|
def cursor_position
|
78
115
|
res = ''
|
79
116
|
$stdin.raw do |stdin|
|
@@ -131,6 +168,12 @@ module RawLine
|
|
131
168
|
n.times { term_info.control "cud1" }
|
132
169
|
end
|
133
170
|
|
171
|
+
def puts(*args)
|
172
|
+
@output.cooked do
|
173
|
+
@output.puts(*args)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
134
177
|
def preserve_cursor(&blk)
|
135
178
|
term_info.control "sc" # store cursor position
|
136
179
|
blk.call
|
@@ -138,6 +181,18 @@ module RawLine
|
|
138
181
|
term_info.control "rc" # restore cursor position
|
139
182
|
end
|
140
183
|
|
184
|
+
def width
|
185
|
+
terminal_size[0]
|
186
|
+
end
|
187
|
+
|
188
|
+
def height
|
189
|
+
terminal_size[1]
|
190
|
+
end
|
191
|
+
|
192
|
+
def cursor_position
|
193
|
+
cursor_position
|
194
|
+
end
|
195
|
+
|
141
196
|
#
|
142
197
|
# Update the terminal escape sequences. This method is called automatically
|
143
198
|
# by RawLine::Editor#bind().
|
data/spec/editor_spec.rb
CHANGED
@@ -12,20 +12,62 @@ end
|
|
12
12
|
require 'stringio'
|
13
13
|
require_relative "../lib/rawline.rb"
|
14
14
|
|
15
|
-
|
15
|
+
class DummyInput < RawLine::NonBlockingInput
|
16
|
+
def initialize
|
17
|
+
@input = StringIO.new
|
18
|
+
end
|
16
19
|
|
17
|
-
|
18
|
-
@
|
20
|
+
def read
|
21
|
+
@input.read.bytes
|
22
|
+
end
|
23
|
+
|
24
|
+
def clear
|
19
25
|
@input = StringIO.new
|
20
|
-
@editor = RawLine::Editor.new(@input, @output)
|
21
26
|
end
|
22
27
|
|
23
|
-
|
24
|
-
@input <<
|
28
|
+
def <<(bytes)
|
29
|
+
@input << bytes
|
30
|
+
end
|
31
|
+
|
32
|
+
def rewind
|
25
33
|
@input.rewind
|
26
|
-
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe RawLine::Editor do
|
38
|
+
let(:dom) { RawLine::DomTree.new }
|
39
|
+
let(:renderer) do
|
40
|
+
instance_double(RawLine::Renderer,
|
41
|
+
render_cursor: nil,
|
42
|
+
render: nil
|
43
|
+
)
|
44
|
+
end
|
45
|
+
let(:input) { DummyInput.new }
|
46
|
+
let(:terminal) do
|
47
|
+
output = double("IO", cooked: nil)
|
48
|
+
RawLine::VT220Terminal.new(input, output)
|
49
|
+
end
|
50
|
+
|
51
|
+
before do
|
52
|
+
@editor = RawLine::Editor.new(
|
53
|
+
dom: dom,
|
54
|
+
input: input,
|
55
|
+
renderer: renderer,
|
56
|
+
terminal: terminal
|
57
|
+
) do |editor|
|
58
|
+
editor.prompt = ">"
|
59
|
+
end
|
60
|
+
@editor.on_read_line do |event|
|
61
|
+
line = event[:payload][:line]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it "reads raw characters from @input" do
|
66
|
+
input << "test #1"
|
67
|
+
input.rewind
|
68
|
+
@editor.event_loop.tick
|
27
69
|
expect(@editor.line.text).to eq("test #1")
|
28
|
-
expect(@
|
70
|
+
expect(@editor.dom.input_box.content).to eq("test #1")
|
29
71
|
end
|
30
72
|
|
31
73
|
it "can bind keys to code blocks" do
|
@@ -38,9 +80,9 @@ describe RawLine::Editor do
|
|
38
80
|
@editor.terminal.escape_codes << ?\e.ord
|
39
81
|
expect {@editor.bind({:test => "\etest"}) { "test #2e" }}.to_not raise_error
|
40
82
|
expect {@editor.bind("\etest2") { "test #2f" }}.to_not raise_error
|
41
|
-
|
42
|
-
|
43
|
-
|
83
|
+
input << ?\C-w
|
84
|
+
input.rewind
|
85
|
+
@editor.event_loop.tick
|
44
86
|
expect(@editor.line.text).to eq("test #2a")
|
45
87
|
@editor.char = [?\C-q.ord]
|
46
88
|
expect(@editor.press_key).to eq("test #2b")
|
@@ -55,9 +97,9 @@ describe RawLine::Editor do
|
|
55
97
|
end
|
56
98
|
|
57
99
|
it "keeps track of the cursor position" do
|
58
|
-
|
59
|
-
|
60
|
-
@editor.
|
100
|
+
input << "test #4"
|
101
|
+
input.rewind
|
102
|
+
@editor.event_loop.tick
|
61
103
|
expect(@editor.line.position).to eq(7)
|
62
104
|
3.times { @editor.move_left }
|
63
105
|
expect(@editor.line.position).to eq(4)
|
@@ -68,103 +110,110 @@ describe RawLine::Editor do
|
|
68
110
|
describe "keeping track of the cursor position across terminal lines (e.g. multi-line editing)" do
|
69
111
|
let(:terminal_width){ 3 }
|
70
112
|
let(:terminal_height){ 7 }
|
71
|
-
let(:output){ @output.rewind ; @output.read }
|
72
113
|
let(:arrow_key_left_ansi){ "\e[D" }
|
73
114
|
let(:arrow_key_right_ansi){ "\e[C" }
|
74
115
|
|
75
116
|
before do
|
76
|
-
allow(@editor).to receive(:terminal_size).and_return [terminal_width, terminal_height]
|
117
|
+
allow(@editor.terminal).to receive(:terminal_size).and_return [terminal_width, terminal_height]
|
77
118
|
end
|
78
119
|
|
79
120
|
context "and the cursor position is at the first character of the second line" do
|
80
121
|
before do
|
81
|
-
|
122
|
+
input << "123"
|
123
|
+
input.rewind
|
82
124
|
end
|
83
125
|
|
84
126
|
it "is at the first character of a second line" do
|
85
|
-
@
|
86
|
-
@editor.read
|
127
|
+
@editor.event_loop.tick
|
87
128
|
expect(@editor.line.position).to eq(3)
|
88
129
|
end
|
89
130
|
|
90
131
|
describe "moving left from the first position of the second line" do
|
91
|
-
|
92
|
-
@
|
93
|
-
@
|
94
|
-
@editor.
|
95
|
-
end
|
132
|
+
it "moves the line and cursor position to left by 1 character" do
|
133
|
+
@editor.event_loop.tick
|
134
|
+
expect(@editor.line.position).to eq(3)
|
135
|
+
expect(@editor.input_box.cursor_position.x).to eq(3)
|
96
136
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
137
|
+
input.clear
|
138
|
+
input << arrow_key_left_ansi
|
139
|
+
input.rewind
|
140
|
+
|
141
|
+
@editor.event_loop.reset
|
142
|
+
@editor.event_loop.tick
|
101
143
|
|
102
|
-
it "correctly sets the line's position" do
|
103
144
|
expect(@editor.line.position).to eq(2)
|
145
|
+
expect(@editor.input_box.cursor_position.x).to eq(2)
|
104
146
|
end
|
105
147
|
end
|
106
148
|
|
107
149
|
describe "moving right from the first position of the second line" do
|
108
|
-
|
109
|
-
@
|
110
|
-
@
|
111
|
-
@editor.
|
112
|
-
end
|
150
|
+
it "doesnt move the line and cursor position" do
|
151
|
+
@editor.event_loop.tick
|
152
|
+
expect(@editor.line.position).to eq(3)
|
153
|
+
expect(@editor.input_box.cursor_position.x).to eq(3)
|
113
154
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
155
|
+
input.clear
|
156
|
+
input << arrow_key_right_ansi
|
157
|
+
input.rewind
|
158
|
+
|
159
|
+
@editor.event_loop.reset
|
160
|
+
@editor.event_loop.tick
|
118
161
|
|
119
|
-
it "doesn't move the cursor when it's at the end of the input" do
|
120
162
|
expect(@editor.line.position).to eq(3)
|
163
|
+
expect(@editor.input_box.cursor_position.x).to eq(3)
|
121
164
|
end
|
122
165
|
end
|
123
166
|
|
124
167
|
describe "moving left to the previous line then right to the next line" do
|
125
168
|
before do
|
169
|
+
@editor.event_loop.tick
|
170
|
+
|
126
171
|
# this is the one that moves us to the previous line
|
127
|
-
|
172
|
+
input.clear
|
173
|
+
input << arrow_key_left_ansi
|
174
|
+
input.rewind
|
175
|
+
@editor.event_loop.reset
|
176
|
+
@editor.event_loop.tick
|
128
177
|
|
129
178
|
# these are for fun to show that we don't generate unnecessary
|
130
179
|
# escape sequences
|
131
|
-
|
132
|
-
|
133
|
-
|
180
|
+
input.clear
|
181
|
+
input << arrow_key_left_ansi
|
182
|
+
input << arrow_key_left_ansi
|
183
|
+
input << arrow_key_left_ansi
|
184
|
+
input.rewind
|
185
|
+
@editor.event_loop.reset
|
186
|
+
@editor.event_loop.tick
|
134
187
|
|
135
188
|
# now let's move right again
|
136
|
-
|
137
|
-
|
138
|
-
|
189
|
+
input.clear
|
190
|
+
input << arrow_key_right_ansi
|
191
|
+
input << arrow_key_right_ansi
|
192
|
+
input << arrow_key_right_ansi
|
193
|
+
input.rewind
|
194
|
+
@editor.event_loop.reset
|
195
|
+
@editor.event_loop.tick
|
139
196
|
|
140
197
|
# this is the one that puts us on the next line
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
@editor.
|
145
|
-
|
146
|
-
|
147
|
-
it "sends the the escape sequence for moving to the previous line just once" do
|
148
|
-
expected_ansi_sequence = "\e[A\e[#{terminal_width}C"
|
149
|
-
expect(output.scan(expected_ansi_sequence).flatten.length).to eq(1)
|
150
|
-
end
|
151
|
-
|
152
|
-
it "sends the the escape sequence for moving to the next line just once" do
|
153
|
-
expected_ansi_sequence = "\e[B\e[#{terminal_width}D"
|
154
|
-
expect(output.scan(expected_ansi_sequence).flatten.length).to eq(1)
|
198
|
+
input.clear
|
199
|
+
input << arrow_key_right_ansi
|
200
|
+
input.rewind
|
201
|
+
@editor.event_loop.reset
|
202
|
+
@editor.event_loop.tick
|
155
203
|
end
|
156
204
|
|
157
|
-
it "correctly sets the line
|
205
|
+
it "correctly sets the line and cursor position" do
|
158
206
|
expect(@editor.line.position).to eq(3)
|
207
|
+
expect(@editor.input_box.cursor_position.x).to eq(3)
|
159
208
|
end
|
160
209
|
end
|
161
210
|
end
|
162
211
|
end
|
163
212
|
|
164
213
|
it "can delete characters" do
|
165
|
-
|
166
|
-
|
167
|
-
@editor.
|
214
|
+
input << "test #5"
|
215
|
+
input.rewind
|
216
|
+
@editor.event_loop.tick
|
168
217
|
3.times { @editor.move_left }
|
169
218
|
4.times { @editor.delete_left_character }
|
170
219
|
3.times { @editor.delete_character }
|
@@ -173,18 +222,18 @@ describe RawLine::Editor do
|
|
173
222
|
end
|
174
223
|
|
175
224
|
it "can clear the whole line" do
|
176
|
-
|
177
|
-
|
178
|
-
@editor.
|
225
|
+
input << "test #5"
|
226
|
+
input.rewind
|
227
|
+
@editor.event_loop.tick
|
179
228
|
@editor.clear_line
|
180
229
|
expect(@editor.line.text).to eq("")
|
181
230
|
expect(@editor.line.position).to eq(0)
|
182
231
|
end
|
183
232
|
|
184
233
|
it "supports undo and redo" do
|
185
|
-
|
186
|
-
|
187
|
-
@editor.
|
234
|
+
input << "test #6"
|
235
|
+
input.rewind
|
236
|
+
@editor.event_loop.tick
|
188
237
|
3.times { @editor.delete_left_character }
|
189
238
|
2.times { @editor.undo }
|
190
239
|
expect(@editor.line.text).to eq("test #")
|
@@ -192,9 +241,9 @@ describe RawLine::Editor do
|
|
192
241
|
expect(@editor.line.text).to eq("test")
|
193
242
|
end
|
194
243
|
|
195
|
-
|
196
|
-
|
197
|
-
|
244
|
+
xit "supports history" do
|
245
|
+
input << "test #7a"
|
246
|
+
input.rewind
|
198
247
|
@editor.read "", true
|
199
248
|
@editor.newline
|
200
249
|
@input << "test #7b"
|
@@ -218,15 +267,15 @@ describe RawLine::Editor do
|
|
218
267
|
end
|
219
268
|
|
220
269
|
it "can overwrite lines" do
|
221
|
-
|
222
|
-
|
223
|
-
@editor.
|
270
|
+
input << "test #8a"
|
271
|
+
input.rewind
|
272
|
+
@editor.event_loop.tick
|
224
273
|
@editor.overwrite_line("test #8b", 2)
|
225
274
|
expect(@editor.line.text).to eq("test #8b")
|
226
275
|
expect(@editor.line.position).to eq(2)
|
227
276
|
end
|
228
277
|
|
229
|
-
|
278
|
+
xit "can complete words" do
|
230
279
|
@editor.completion_append_string = "\t"
|
231
280
|
@editor.bind(:tab) { @editor.complete }
|
232
281
|
@editor.completion_proc = lambda do |word|
|
@@ -234,27 +283,33 @@ describe RawLine::Editor do
|
|
234
283
|
['select', 'update', 'delete', 'debug', 'destroy'].find_all { |e| e.match(/^#{Regexp.escape(word)}/) }
|
235
284
|
end
|
236
285
|
end
|
237
|
-
|
238
|
-
|
239
|
-
@editor.
|
286
|
+
input << "test #9 de" << ?\t.chr << ?\t.chr
|
287
|
+
input.rewind
|
288
|
+
@editor.event_loop.tick
|
240
289
|
expect(@editor.line.text).to eq("test #9 delete\t")
|
241
290
|
end
|
242
291
|
|
243
|
-
|
244
|
-
|
245
|
-
@editor.terminal.keys[:left_arrow].each { |k|
|
246
|
-
|
247
|
-
|
248
|
-
@editor.
|
292
|
+
xit "supports INSERT and REPLACE modes" do
|
293
|
+
input << "test 0"
|
294
|
+
@editor.terminal.keys[:left_arrow].each { |k| input << k.chr }
|
295
|
+
input << "#1"
|
296
|
+
input.rewind
|
297
|
+
@editor.event_loop.tick
|
249
298
|
expect(@editor.line.text).to eq("test #10")
|
250
299
|
@editor.toggle_mode
|
251
|
-
|
252
|
-
@editor.terminal.keys[:left_arrow].each { |k|
|
253
|
-
|
254
|
-
|
255
|
-
@editor.
|
300
|
+
input << "test 0"
|
301
|
+
@editor.terminal.keys[:left_arrow].each { |k| input << k.chr }
|
302
|
+
input << "#1"
|
303
|
+
input.rewind
|
304
|
+
@editor.event_loop.tick
|
256
305
|
expect(@editor.line.text).to eq("test #1test #1")
|
257
306
|
end
|
258
307
|
|
259
|
-
|
308
|
+
describe '#puts' do
|
309
|
+
it 'puts to the terminal, then re-renders' do
|
310
|
+
expect(terminal).to receive(:puts).with("A", "B", "C").ordered
|
311
|
+
expect(renderer).to receive(:render).with(reset: true)
|
312
|
+
@editor.puts("A", "B", "C")
|
313
|
+
end
|
314
|
+
end
|
260
315
|
end
|