yap-rawline 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|