ruvim 0.4.0 → 0.6.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/AGENTS.md +53 -4
- data/README.md +15 -6
- data/Rakefile +7 -0
- data/benchmark/cext_compare.rb +165 -0
- data/benchmark/chunked_load.rb +256 -0
- data/benchmark/file_load.rb +140 -0
- data/benchmark/hotspots.rb +178 -0
- data/docs/binding.md +3 -2
- data/docs/command.md +81 -9
- data/docs/done.md +23 -0
- data/docs/spec.md +105 -19
- data/docs/todo.md +9 -0
- data/docs/tutorial.md +9 -1
- data/docs/vim_diff.md +13 -0
- data/ext/ruvim/extconf.rb +5 -0
- data/ext/ruvim/ruvim_ext.c +519 -0
- data/lib/ruvim/app.rb +217 -2778
- data/lib/ruvim/browser.rb +104 -0
- data/lib/ruvim/buffer.rb +39 -28
- data/lib/ruvim/command_invocation.rb +2 -2
- data/lib/ruvim/completion_manager.rb +708 -0
- data/lib/ruvim/dispatcher.rb +14 -8
- data/lib/ruvim/display_width.rb +91 -45
- data/lib/ruvim/editor.rb +64 -81
- data/lib/ruvim/ex_command_registry.rb +3 -1
- data/lib/ruvim/gh/link.rb +207 -0
- data/lib/ruvim/git/blame.rb +16 -6
- data/lib/ruvim/git/branch.rb +20 -5
- data/lib/ruvim/git/grep.rb +107 -0
- data/lib/ruvim/git/handler.rb +42 -1
- data/lib/ruvim/global_commands.rb +175 -35
- data/lib/ruvim/highlighter.rb +4 -13
- data/lib/ruvim/key_handler.rb +1510 -0
- data/lib/ruvim/keymap_manager.rb +7 -7
- data/lib/ruvim/lang/base.rb +5 -0
- data/lib/ruvim/lang/c.rb +116 -0
- data/lib/ruvim/lang/cpp.rb +107 -0
- data/lib/ruvim/lang/csv.rb +4 -1
- data/lib/ruvim/lang/diff.rb +2 -0
- data/lib/ruvim/lang/dockerfile.rb +36 -0
- data/lib/ruvim/lang/elixir.rb +85 -0
- data/lib/ruvim/lang/erb.rb +30 -0
- data/lib/ruvim/lang/go.rb +83 -0
- data/lib/ruvim/lang/html.rb +34 -0
- data/lib/ruvim/lang/javascript.rb +83 -0
- data/lib/ruvim/lang/json.rb +6 -0
- data/lib/ruvim/lang/lua.rb +76 -0
- data/lib/ruvim/lang/makefile.rb +36 -0
- data/lib/ruvim/lang/markdown.rb +3 -4
- data/lib/ruvim/lang/ocaml.rb +77 -0
- data/lib/ruvim/lang/perl.rb +91 -0
- data/lib/ruvim/lang/python.rb +85 -0
- data/lib/ruvim/lang/registry.rb +102 -0
- data/lib/ruvim/lang/ruby.rb +7 -0
- data/lib/ruvim/lang/rust.rb +95 -0
- data/lib/ruvim/lang/scheme.rb +5 -0
- data/lib/ruvim/lang/sh.rb +76 -0
- data/lib/ruvim/lang/sql.rb +52 -0
- data/lib/ruvim/lang/toml.rb +36 -0
- data/lib/ruvim/lang/tsv.rb +4 -1
- data/lib/ruvim/lang/typescript.rb +53 -0
- data/lib/ruvim/lang/yaml.rb +62 -0
- data/lib/ruvim/rich_view/table_renderer.rb +3 -3
- data/lib/ruvim/rich_view.rb +14 -7
- data/lib/ruvim/screen.rb +126 -72
- data/lib/ruvim/stream/file_load.rb +85 -0
- data/lib/ruvim/stream/follow.rb +40 -0
- data/lib/ruvim/stream/git.rb +43 -0
- data/lib/ruvim/stream/run.rb +74 -0
- data/lib/ruvim/stream/stdin.rb +55 -0
- data/lib/ruvim/stream.rb +35 -0
- data/lib/ruvim/stream_mixer.rb +394 -0
- data/lib/ruvim/terminal.rb +18 -4
- data/lib/ruvim/text_metrics.rb +84 -65
- data/lib/ruvim/version.rb +1 -1
- data/lib/ruvim/window.rb +5 -5
- data/lib/ruvim.rb +23 -6
- data/test/app_command_test.rb +382 -0
- data/test/app_completion_test.rb +43 -19
- data/test/app_dot_repeat_test.rb +27 -3
- data/test/app_ex_command_test.rb +154 -0
- data/test/app_motion_test.rb +13 -12
- data/test/app_register_test.rb +2 -1
- data/test/app_scenario_test.rb +15 -10
- data/test/app_startup_test.rb +70 -27
- data/test/app_text_object_test.rb +2 -1
- data/test/app_unicode_behavior_test.rb +3 -2
- data/test/browser_test.rb +88 -0
- data/test/buffer_test.rb +24 -0
- data/test/cli_test.rb +63 -0
- data/test/command_invocation_test.rb +33 -0
- data/test/config_dsl_test.rb +47 -0
- data/test/dispatcher_test.rb +74 -4
- data/test/ex_command_registry_test.rb +106 -0
- data/test/follow_test.rb +20 -21
- data/test/gh_link_test.rb +141 -0
- data/test/git_blame_test.rb +96 -17
- data/test/git_grep_test.rb +64 -0
- data/test/highlighter_test.rb +125 -0
- data/test/indent_test.rb +137 -0
- data/test/input_screen_integration_test.rb +1 -1
- data/test/keyword_chars_test.rb +85 -0
- data/test/lang_test.rb +634 -0
- data/test/markdown_renderer_test.rb +5 -5
- data/test/on_save_hook_test.rb +12 -8
- data/test/render_snapshot_test.rb +78 -0
- data/test/rich_view_test.rb +42 -42
- data/test/run_command_test.rb +307 -0
- data/test/screen_test.rb +68 -5
- data/test/stream_test.rb +165 -0
- data/test/window_test.rb +59 -0
- metadata +52 -2
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
|
|
5
|
+
class RunCommandTest < Minitest::Test
|
|
6
|
+
TerminalStub = Struct.new(:winsize) do
|
|
7
|
+
def write(_data) = nil
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def setup
|
|
11
|
+
@app = RuVim::App.new(clean: true)
|
|
12
|
+
@editor = @app.instance_variable_get(:@editor)
|
|
13
|
+
@key_handler = @app.instance_variable_get(:@key_handler)
|
|
14
|
+
@editor.materialize_intro_buffer!
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def feed(*keys)
|
|
18
|
+
keys.each { |k| @key_handler.handle(k) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def buf
|
|
22
|
+
@editor.current_buffer
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def win
|
|
26
|
+
@editor.current_window
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# --- runprg option ---
|
|
30
|
+
|
|
31
|
+
def test_runprg_option_exists
|
|
32
|
+
assert_nil @editor.get_option("runprg")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_runprg_set_via_ex
|
|
36
|
+
feed(*":set runprg=ruby\\ -w\\ %".chars, :enter)
|
|
37
|
+
assert_equal "ruby -w %", @editor.get_option("runprg")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# --- % expansion ---
|
|
41
|
+
|
|
42
|
+
def test_expand_run_command_replaces_percent_with_filename
|
|
43
|
+
buf.instance_variable_set(:@path, "/tmp/test.rb")
|
|
44
|
+
gc = RuVim::GlobalCommands.instance
|
|
45
|
+
result = gc.send(:expand_run_command, "ruby -w %", buf)
|
|
46
|
+
assert_equal "ruby -w /tmp/test.rb", result
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_expand_run_command_without_percent
|
|
50
|
+
gc = RuVim::GlobalCommands.instance
|
|
51
|
+
result = gc.send(:expand_run_command, "echo hello", buf)
|
|
52
|
+
assert_equal "echo hello", result
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def test_expand_run_command_percent_with_no_path_raises
|
|
56
|
+
gc = RuVim::GlobalCommands.instance
|
|
57
|
+
assert_raises(RuVim::CommandError) do
|
|
58
|
+
gc.send(:expand_run_command, "ruby %", buf)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# --- :run with args ---
|
|
63
|
+
|
|
64
|
+
def test_run_with_args_creates_shell_output_buffer
|
|
65
|
+
feed(*":run echo hello".chars, :enter)
|
|
66
|
+
output_buf = @editor.buffers.values.find { |b| b.name == "[Shell Output]" }
|
|
67
|
+
assert output_buf, "Expected [Shell Output] buffer to be created"
|
|
68
|
+
assert output_buf.readonly?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_run_opens_in_horizontal_split
|
|
72
|
+
feed(*":run echo hello".chars, :enter)
|
|
73
|
+
# Should have 2 windows after split
|
|
74
|
+
leaves = @editor.send(:tree_leaves, @editor.layout_tree)
|
|
75
|
+
assert_equal 2, leaves.length, "Expected 2 windows (source + output)"
|
|
76
|
+
# Current window should show the output buffer
|
|
77
|
+
output_buf = @editor.buffers.values.find { |b| b.name == "[Shell Output]" }
|
|
78
|
+
assert_equal output_buf.id, @editor.current_window.buffer_id
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def test_run_reuses_existing_output_window
|
|
82
|
+
feed(*":run echo first".chars, :enter)
|
|
83
|
+
leaves_after_first = @editor.send(:tree_leaves, @editor.layout_tree)
|
|
84
|
+
|
|
85
|
+
# Focus source window and run again
|
|
86
|
+
feed(*":wincmd w".chars, :enter)
|
|
87
|
+
feed(*":run echo second".chars, :enter)
|
|
88
|
+
|
|
89
|
+
leaves_after_second = @editor.send(:tree_leaves, @editor.layout_tree)
|
|
90
|
+
assert_equal leaves_after_first.length, leaves_after_second.length, "Should not create additional splits"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def test_run_reuses_shell_output_buffer
|
|
94
|
+
feed(*":run echo first".chars, :enter)
|
|
95
|
+
first_buf = @editor.buffers.values.find { |b| b.name == "[Shell Output]" }
|
|
96
|
+
first_id = first_buf.id
|
|
97
|
+
|
|
98
|
+
# Switch to source window and run again
|
|
99
|
+
feed(*":wincmd w".chars, :enter)
|
|
100
|
+
feed(*":run echo second".chars, :enter)
|
|
101
|
+
second_buf = @editor.buffers.values.find { |b| b.name == "[Shell Output]" }
|
|
102
|
+
|
|
103
|
+
assert_equal first_id, second_buf.id, "Should reuse the same [Shell Output] buffer"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# --- :run without args uses runprg ---
|
|
107
|
+
|
|
108
|
+
def test_run_no_args_uses_runprg
|
|
109
|
+
buf.instance_variable_set(:@path, "/tmp/test.rb")
|
|
110
|
+
buf.options["runprg"] = "echo %"
|
|
111
|
+
feed(*":run".chars, :enter)
|
|
112
|
+
output_buf = @editor.buffers.values.find { |b| b.name == "[Shell Output]" }
|
|
113
|
+
assert output_buf, "Expected [Shell Output] buffer to be created"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def test_run_no_args_no_runprg_no_history_shows_error
|
|
117
|
+
feed(*":run".chars, :enter)
|
|
118
|
+
assert_match(/runprg/, @editor.message.to_s)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# --- per-buffer run history ---
|
|
122
|
+
|
|
123
|
+
def test_run_remembers_last_command_per_buffer
|
|
124
|
+
buf.instance_variable_set(:@path, "/tmp/test.rb")
|
|
125
|
+
feed(*":run echo test1".chars, :enter)
|
|
126
|
+
|
|
127
|
+
# Switch back and run again without args
|
|
128
|
+
feed(*":bprev".chars, :enter)
|
|
129
|
+
feed(*":run".chars, :enter)
|
|
130
|
+
|
|
131
|
+
output_buf = @editor.buffers.values.find { |b| b.name == "[Shell Output]" }
|
|
132
|
+
assert output_buf
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# --- filetype default runprg ---
|
|
136
|
+
|
|
137
|
+
def test_ruby_filetype_sets_default_runprg
|
|
138
|
+
buf.instance_variable_set(:@path, "/tmp/test.rb")
|
|
139
|
+
@editor.send(:assign_filetype, buf, "ruby")
|
|
140
|
+
assert_equal "ruby -w %", buf.options["runprg"]
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def test_python_filetype_sets_default_runprg
|
|
144
|
+
buf.instance_variable_set(:@path, "/tmp/test.py")
|
|
145
|
+
@editor.send(:assign_filetype, buf, "python")
|
|
146
|
+
assert_equal "python3 %", buf.options["runprg"]
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# --- Ctrl-C stops run stream ---
|
|
150
|
+
|
|
151
|
+
def test_ctrl_c_on_run_output_buffer_stops_stream
|
|
152
|
+
feed(*":run echo hello".chars, :enter)
|
|
153
|
+
output_buf = @editor.buffers.values.find { |b| b.name == "[Shell Output]" }
|
|
154
|
+
assert output_buf
|
|
155
|
+
assert output_buf.stream.stop_handler, "Expected stream to have a stop_handler"
|
|
156
|
+
|
|
157
|
+
@editor.stream_stop_or_cancel!
|
|
158
|
+
assert_includes @editor.message.to_s, "stopped"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# --- auto-save before :run ---
|
|
162
|
+
|
|
163
|
+
def test_run_auto_saves_modified_buffer
|
|
164
|
+
Tempfile.create(["ruvim-run-save", ".rb"]) do |f|
|
|
165
|
+
f.write("original\n")
|
|
166
|
+
f.flush
|
|
167
|
+
|
|
168
|
+
app = RuVim::App.new(path: f.path, clean: true)
|
|
169
|
+
editor = app.instance_variable_get(:@editor)
|
|
170
|
+
kh = app.instance_variable_get(:@key_handler)
|
|
171
|
+
buf = editor.current_buffer
|
|
172
|
+
|
|
173
|
+
# Simulate editing
|
|
174
|
+
buf.lines[0] = "modified"
|
|
175
|
+
buf.modified = true
|
|
176
|
+
|
|
177
|
+
":run echo ok".chars.each { |k| kh.handle(k) }
|
|
178
|
+
kh.handle(:enter)
|
|
179
|
+
|
|
180
|
+
# File should be saved
|
|
181
|
+
assert_equal "modified", File.read(f.path)
|
|
182
|
+
assert_equal false, buf.modified?
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def test_run_does_not_save_unmodified_buffer
|
|
187
|
+
Tempfile.create(["ruvim-run-nosave", ".rb"]) do |f|
|
|
188
|
+
f.write("original\n")
|
|
189
|
+
f.flush
|
|
190
|
+
mtime_before = File.mtime(f.path)
|
|
191
|
+
|
|
192
|
+
app = RuVim::App.new(path: f.path, clean: true)
|
|
193
|
+
kh = app.instance_variable_get(:@key_handler)
|
|
194
|
+
|
|
195
|
+
sleep 0.01 # ensure mtime would differ if written
|
|
196
|
+
":run echo ok".chars.each { |k| kh.handle(k) }
|
|
197
|
+
kh.handle(:enter)
|
|
198
|
+
|
|
199
|
+
assert_equal mtime_before, File.mtime(f.path)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# --- streaming output arrives incrementally ---
|
|
204
|
+
|
|
205
|
+
def test_run_streams_output_incrementally
|
|
206
|
+
sh = @app.instance_variable_get(:@stream_mixer)
|
|
207
|
+
# Run a command that prints lines with small delays
|
|
208
|
+
cmd = "ruby -e 'STDOUT.sync=true; 3.times{puts _1; sleep 0.1}'"
|
|
209
|
+
feed(*":run #{cmd}".chars, :enter)
|
|
210
|
+
output_buf = @editor.buffers.values.find { |b| b.name == "[Shell Output]" }
|
|
211
|
+
assert output_buf, "Expected [Shell Output] buffer"
|
|
212
|
+
|
|
213
|
+
# Wait for at least one line to arrive (but not necessarily all)
|
|
214
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 5
|
|
215
|
+
loop do
|
|
216
|
+
sh.drain_events!
|
|
217
|
+
break if output_buf.lines.any? { |l| l.include?("0") }
|
|
218
|
+
if Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline
|
|
219
|
+
flunk "Timed out waiting for streamed output; lines=#{output_buf.lines.inspect}"
|
|
220
|
+
end
|
|
221
|
+
sleep 0.05
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Should have received at least the first line while command is still running
|
|
225
|
+
assert_includes output_buf.lines, "0"
|
|
226
|
+
ensure
|
|
227
|
+
output_buf&.stream&.stop!
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# --- status line shows run command ---
|
|
231
|
+
|
|
232
|
+
def test_ctrl_c_key_stops_running_stream
|
|
233
|
+
sh = @app.instance_variable_get(:@stream_mixer)
|
|
234
|
+
feed(*":run sleep 10".chars, :enter)
|
|
235
|
+
output_buf = @editor.buffers.values.find { |b| b.name == "[Shell Output]" }
|
|
236
|
+
assert output_buf, "Expected [Shell Output] buffer"
|
|
237
|
+
assert_equal :live, output_buf.stream.state, "Buffer should be streaming"
|
|
238
|
+
assert output_buf.stream.stop_handler, "Buffer should have stop handler"
|
|
239
|
+
|
|
240
|
+
# Simulate Ctrl-C key press
|
|
241
|
+
feed(:ctrl_c)
|
|
242
|
+
sh.drain_events!
|
|
243
|
+
|
|
244
|
+
assert_equal :closed, output_buf.stream.state, "Stream should be stopped after Ctrl-C"
|
|
245
|
+
ensure
|
|
246
|
+
output_buf&.stream&.stop! rescue nil
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# --- stream_status shows correct label ---
|
|
250
|
+
|
|
251
|
+
def test_run_output_stream_status_live
|
|
252
|
+
feed(*":run sleep 10".chars, :enter)
|
|
253
|
+
output_buf = @editor.buffers.values.find { |b| b.name == "[Shell Output]" }
|
|
254
|
+
assert_equal "run", output_buf.stream_status
|
|
255
|
+
ensure
|
|
256
|
+
output_buf&.stream&.stop! rescue nil
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def test_run_output_stream_status_exit
|
|
260
|
+
feed(*":run echo done".chars, :enter)
|
|
261
|
+
output_buf = @editor.buffers.values.find { |b| b.name == "[Shell Output]" }
|
|
262
|
+
sh = @app.instance_variable_get(:@stream_mixer)
|
|
263
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 5
|
|
264
|
+
loop do
|
|
265
|
+
sh.drain_events!
|
|
266
|
+
break if output_buf.stream.state == :closed
|
|
267
|
+
flunk "Timed out" if Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline
|
|
268
|
+
sleep 0.02
|
|
269
|
+
end
|
|
270
|
+
assert_match(/\Arun\/exit 0\z/, output_buf.stream_status)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def test_stdin_stream_status_live
|
|
274
|
+
r, w = IO.pipe
|
|
275
|
+
buf = @editor.current_buffer
|
|
276
|
+
buf.stream = RuVim::Stream::Stdin.new(io: r, buffer_id: buf.id, queue: Queue.new) {}
|
|
277
|
+
assert_equal "stdin", buf.stream_status
|
|
278
|
+
ensure
|
|
279
|
+
buf&.stream&.stop!
|
|
280
|
+
w&.close rescue nil
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def test_stdin_stream_status_closed
|
|
284
|
+
r, w = IO.pipe
|
|
285
|
+
buf = @editor.current_buffer
|
|
286
|
+
buf.stream = RuVim::Stream::Stdin.new(io: r, buffer_id: buf.id, queue: Queue.new) {}
|
|
287
|
+
buf.stream.stop!
|
|
288
|
+
assert_equal "stdin/EOF", buf.stream_status
|
|
289
|
+
ensure
|
|
290
|
+
w&.close rescue nil
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def test_run_stores_command_on_output_buffer
|
|
294
|
+
feed(*":run echo hello".chars, :enter)
|
|
295
|
+
output_buf = @editor.buffers.values.find { |b| b.name == "[Shell Output]" }
|
|
296
|
+
assert_equal "echo hello", output_buf.stream_command
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def test_status_line_includes_run_command
|
|
300
|
+
feed(*":run echo hello".chars, :enter)
|
|
301
|
+
term = TerminalStub.new([6, 80])
|
|
302
|
+
screen = RuVim::Screen.new(terminal: term)
|
|
303
|
+
line = screen.send(:status_line, @editor, 80)
|
|
304
|
+
assert_includes line, "echo hello"
|
|
305
|
+
assert_includes line, "[run]"
|
|
306
|
+
end
|
|
307
|
+
end
|
data/test/screen_test.rb
CHANGED
|
@@ -104,14 +104,18 @@ class ScreenTest < Minitest::Test
|
|
|
104
104
|
def test_status_line_shows_stream_state_for_stdin_buffer
|
|
105
105
|
editor = RuVim::Editor.new
|
|
106
106
|
buf = editor.add_virtual_buffer(kind: :stream, name: "[stdin]", lines: [""], readonly: true, modifiable: false)
|
|
107
|
-
|
|
107
|
+
r, w = IO.pipe
|
|
108
|
+
buf.stream = RuVim::Stream::Stdin.new(io: r, buffer_id: buf.id, queue: Queue.new) {}
|
|
109
|
+
buf.stream.stop!
|
|
108
110
|
editor.add_window(buffer_id: buf.id)
|
|
109
111
|
|
|
110
112
|
term = TerminalStub.new([6, 60])
|
|
111
113
|
screen = RuVim::Screen.new(terminal: term)
|
|
112
114
|
line = screen.send(:status_line, editor, 60)
|
|
113
115
|
|
|
114
|
-
assert_includes line, "[stdin/
|
|
116
|
+
assert_includes line, "[stdin/EOF]"
|
|
117
|
+
ensure
|
|
118
|
+
w&.close rescue nil
|
|
115
119
|
end
|
|
116
120
|
|
|
117
121
|
def test_render_uses_dim_and_brighter_line_number_gutter_colors
|
|
@@ -380,7 +384,7 @@ class ScreenTest < Minitest::Test
|
|
|
380
384
|
# Create rows where raw lines differ in length but formatted lines are aligned
|
|
381
385
|
buf.replace_all_lines!(["Short\tSecond\tThird", "LongerField\tB\tC"])
|
|
382
386
|
buf.options["filetype"] = "tsv"
|
|
383
|
-
RuVim::RichView.open!(editor, format:
|
|
387
|
+
RuVim::RichView.open!(editor, format: :tsv)
|
|
384
388
|
|
|
385
389
|
# Move cursor to end of line (like pressing $)
|
|
386
390
|
win.cursor_y = 0
|
|
@@ -420,7 +424,7 @@ class ScreenTest < Minitest::Test
|
|
|
420
424
|
"緑\tキウイフルーツジャム\t静岡"
|
|
421
425
|
])
|
|
422
426
|
buf.options["filetype"] = "tsv"
|
|
423
|
-
RuVim::RichView.open!(editor, format:
|
|
427
|
+
RuVim::RichView.open!(editor, format: :tsv)
|
|
424
428
|
|
|
425
429
|
# Move to end of line to trigger horizontal scroll
|
|
426
430
|
win.cursor_y = 2
|
|
@@ -503,7 +507,7 @@ class ScreenTest < Minitest::Test
|
|
|
503
507
|
win = editor.add_window(buffer_id: buf.id)
|
|
504
508
|
buf.replace_all_lines!(["A\tB\tC", "DD\tEE\tFF"])
|
|
505
509
|
buf.options["filetype"] = "tsv"
|
|
506
|
-
RuVim::RichView.open!(editor, format:
|
|
510
|
+
RuVim::RichView.open!(editor, format: :tsv)
|
|
507
511
|
|
|
508
512
|
# Move cursor to end of raw line
|
|
509
513
|
win.cursor_y = 0
|
|
@@ -590,4 +594,63 @@ class ScreenTest < Minitest::Test
|
|
|
590
594
|
# win2 + win3 heights + separator should equal text_rows
|
|
591
595
|
assert_equal text_rows, rects[win2.id][:height] + rects[win3.id][:height] + 1
|
|
592
596
|
end
|
|
597
|
+
|
|
598
|
+
def test_cursor_hidden_in_normal_mode
|
|
599
|
+
editor = RuVim::Editor.new
|
|
600
|
+
buf = editor.add_empty_buffer
|
|
601
|
+
editor.add_window(buffer_id: buf.id)
|
|
602
|
+
editor.enter_normal_mode
|
|
603
|
+
|
|
604
|
+
term = TerminalStub.new([8, 20])
|
|
605
|
+
screen = RuVim::Screen.new(terminal: term)
|
|
606
|
+
screen.render(editor)
|
|
607
|
+
|
|
608
|
+
output = term.writes.join
|
|
609
|
+
# Normal mode hides terminal cursor (cell rendering handles visibility)
|
|
610
|
+
refute_includes output, "\e[?25h", "Normal mode should hide terminal cursor"
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
def test_cursor_bar_in_insert_mode
|
|
614
|
+
editor = RuVim::Editor.new
|
|
615
|
+
buf = editor.add_empty_buffer
|
|
616
|
+
editor.add_window(buffer_id: buf.id)
|
|
617
|
+
editor.enter_insert_mode
|
|
618
|
+
|
|
619
|
+
term = TerminalStub.new([8, 20])
|
|
620
|
+
screen = RuVim::Screen.new(terminal: term)
|
|
621
|
+
screen.render(editor)
|
|
622
|
+
|
|
623
|
+
output = term.writes.join
|
|
624
|
+
assert_includes output, "\e[6 q", "Insert mode should set steady bar cursor"
|
|
625
|
+
assert_includes output, "\e[?25h", "Insert mode should show terminal cursor"
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
def test_cursor_hidden_in_visual_mode
|
|
629
|
+
editor = RuVim::Editor.new
|
|
630
|
+
buf = editor.add_empty_buffer
|
|
631
|
+
editor.add_window(buffer_id: buf.id)
|
|
632
|
+
editor.enter_visual(:visual_char)
|
|
633
|
+
|
|
634
|
+
term = TerminalStub.new([8, 20])
|
|
635
|
+
screen = RuVim::Screen.new(terminal: term)
|
|
636
|
+
screen.render(editor)
|
|
637
|
+
|
|
638
|
+
output = term.writes.join
|
|
639
|
+
refute_includes output, "\e[?25h", "Visual mode should hide terminal cursor"
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
def test_cursor_bar_in_command_line_mode
|
|
643
|
+
editor = RuVim::Editor.new
|
|
644
|
+
buf = editor.add_empty_buffer
|
|
645
|
+
editor.add_window(buffer_id: buf.id)
|
|
646
|
+
editor.enter_command_line_mode
|
|
647
|
+
|
|
648
|
+
term = TerminalStub.new([8, 20])
|
|
649
|
+
screen = RuVim::Screen.new(terminal: term)
|
|
650
|
+
screen.render(editor)
|
|
651
|
+
|
|
652
|
+
output = term.writes.join
|
|
653
|
+
assert_includes output, "\e[6 q", "Command-line mode should set steady bar cursor"
|
|
654
|
+
assert_includes output, "\e[?25h", "Command-line mode should show terminal cursor"
|
|
655
|
+
end
|
|
593
656
|
end
|
data/test/stream_test.rb
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
|
|
5
|
+
class StreamTest < Minitest::Test
|
|
6
|
+
def make_queue
|
|
7
|
+
Queue.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def noop
|
|
11
|
+
-> {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# --- Stream::Stdin ---
|
|
15
|
+
|
|
16
|
+
def test_stdin_starts_live
|
|
17
|
+
r, w = IO.pipe
|
|
18
|
+
s = RuVim::Stream::Stdin.new(io: r, buffer_id: 1, queue: make_queue, &noop)
|
|
19
|
+
assert_equal :live, s.state
|
|
20
|
+
assert_equal "stdin", s.status
|
|
21
|
+
ensure
|
|
22
|
+
s&.stop!
|
|
23
|
+
w&.close rescue nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_stdin_status_closed
|
|
27
|
+
r, w = IO.pipe
|
|
28
|
+
s = RuVim::Stream::Stdin.new(io: r, buffer_id: 1, queue: make_queue, &noop)
|
|
29
|
+
s.stop!
|
|
30
|
+
assert_equal :closed, s.state
|
|
31
|
+
assert_equal "stdin/EOF", s.status
|
|
32
|
+
ensure
|
|
33
|
+
w&.close rescue nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_stdin_command_is_nil
|
|
37
|
+
r, w = IO.pipe
|
|
38
|
+
s = RuVim::Stream::Stdin.new(io: r, buffer_id: 1, queue: make_queue, &noop)
|
|
39
|
+
assert_nil s.command
|
|
40
|
+
ensure
|
|
41
|
+
s&.stop!
|
|
42
|
+
w&.close rescue nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# --- Stream::Run ---
|
|
46
|
+
|
|
47
|
+
def test_run_starts_live
|
|
48
|
+
q = make_queue
|
|
49
|
+
s = RuVim::Stream::Run.new(command: "echo hello", buffer_id: 1, queue: q, &noop)
|
|
50
|
+
assert_equal :live, s.state
|
|
51
|
+
assert_equal "run", s.status
|
|
52
|
+
assert_equal "echo hello", s.command
|
|
53
|
+
ensure
|
|
54
|
+
s&.stop!
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_run_status_closed_with_exit
|
|
58
|
+
q = make_queue
|
|
59
|
+
s = RuVim::Stream::Run.new(command: "echo hello", buffer_id: 1, queue: q, &noop)
|
|
60
|
+
s.stop!
|
|
61
|
+
s.exit_status = Struct.new(:exitstatus).new(0)
|
|
62
|
+
assert_equal "run/exit 0", s.status
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_run_status_error
|
|
66
|
+
q = make_queue
|
|
67
|
+
s = RuVim::Stream::Run.new(command: "echo hello", buffer_id: 1, queue: q, &noop)
|
|
68
|
+
s.state = :error
|
|
69
|
+
assert_equal "run/error", s.status
|
|
70
|
+
ensure
|
|
71
|
+
s&.stop!
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# --- Stream::Follow ---
|
|
75
|
+
|
|
76
|
+
def test_follow_starts_live
|
|
77
|
+
Tempfile.create(["stream_test", ".txt"]) do |f|
|
|
78
|
+
f.write("test\n"); f.flush
|
|
79
|
+
s = RuVim::Stream::Follow.new(path: f.path, buffer_id: 1, queue: make_queue, &noop)
|
|
80
|
+
assert_equal :live, s.state
|
|
81
|
+
assert_match(/\Afollow/, s.status)
|
|
82
|
+
assert_nil s.command
|
|
83
|
+
ensure
|
|
84
|
+
s&.stop!
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_follow_stop_clears_state
|
|
89
|
+
Tempfile.create(["stream_test", ".txt"]) do |f|
|
|
90
|
+
f.write("test\n"); f.flush
|
|
91
|
+
s = RuVim::Stream::Follow.new(path: f.path, buffer_id: 1, queue: make_queue, &noop)
|
|
92
|
+
s.stop!
|
|
93
|
+
assert_nil s.state
|
|
94
|
+
assert_nil s.status
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# --- Stream::FileLoad ---
|
|
99
|
+
|
|
100
|
+
def test_file_load_starts_live
|
|
101
|
+
Tempfile.create(["stream_test", ".txt"]) do |f|
|
|
102
|
+
f.write("data\n"); f.flush
|
|
103
|
+
io = File.open(f.path, "rb")
|
|
104
|
+
s = RuVim::Stream::FileLoad.new(io: io, file_size: f.size, buffer_id: 1, queue: make_queue, &noop)
|
|
105
|
+
assert_equal :live, s.state
|
|
106
|
+
assert_equal "load", s.status
|
|
107
|
+
ensure
|
|
108
|
+
s&.stop!
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def test_file_load_status_error
|
|
113
|
+
Tempfile.create(["stream_test", ".txt"]) do |f|
|
|
114
|
+
f.write("data\n"); f.flush
|
|
115
|
+
io = File.open(f.path, "rb")
|
|
116
|
+
s = RuVim::Stream::FileLoad.new(io: io, file_size: f.size, buffer_id: 1, queue: make_queue, &noop)
|
|
117
|
+
s.state = :error
|
|
118
|
+
assert_equal "load/error", s.status
|
|
119
|
+
ensure
|
|
120
|
+
s&.stop!
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# --- Stream::Git ---
|
|
125
|
+
|
|
126
|
+
def test_git_status_always_nil
|
|
127
|
+
q = make_queue
|
|
128
|
+
s = RuVim::Stream::Git.new(cmd: ["echo", "test"], root: ".", buffer_id: 1, queue: q, &noop)
|
|
129
|
+
assert_nil s.status
|
|
130
|
+
ensure
|
|
131
|
+
s&.stop!
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# --- common ---
|
|
135
|
+
|
|
136
|
+
def test_live_predicate
|
|
137
|
+
q = make_queue
|
|
138
|
+
s = RuVim::Stream::Run.new(command: "sleep 10", buffer_id: 1, queue: q, &noop)
|
|
139
|
+
assert s.live?
|
|
140
|
+
s.stop!
|
|
141
|
+
refute s.live?
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def test_buffer_stream_status_delegates
|
|
145
|
+
q = make_queue
|
|
146
|
+
app = RuVim::App.new(clean: true)
|
|
147
|
+
editor = app.instance_variable_get(:@editor)
|
|
148
|
+
editor.materialize_intro_buffer!
|
|
149
|
+
buf = editor.current_buffer
|
|
150
|
+
buf.stream = RuVim::Stream::Run.new(command: "echo test", buffer_id: buf.id, queue: q, &noop)
|
|
151
|
+
assert_equal "run", buf.stream_status
|
|
152
|
+
assert_equal "echo test", buf.stream_command
|
|
153
|
+
ensure
|
|
154
|
+
buf&.stream&.stop!
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def test_buffer_stream_status_nil_without_stream
|
|
158
|
+
app = RuVim::App.new(clean: true)
|
|
159
|
+
editor = app.instance_variable_get(:@editor)
|
|
160
|
+
editor.materialize_intro_buffer!
|
|
161
|
+
buf = editor.current_buffer
|
|
162
|
+
assert_nil buf.stream_status
|
|
163
|
+
assert_nil buf.stream_command
|
|
164
|
+
end
|
|
165
|
+
end
|
data/test/window_test.rb
CHANGED
|
@@ -31,6 +31,65 @@ class WindowTest < Minitest::Test
|
|
|
31
31
|
assert_operator win.col_offset, :>, 0
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
def test_move_left
|
|
35
|
+
buffer = RuVim::Buffer.new(id: 1, lines: ["abcde"])
|
|
36
|
+
win = RuVim::Window.new(id: 1, buffer_id: 1)
|
|
37
|
+
win.cursor_x = 3
|
|
38
|
+
|
|
39
|
+
win.move_left(buffer)
|
|
40
|
+
assert_equal 2, win.cursor_x
|
|
41
|
+
|
|
42
|
+
win.move_left(buffer, 2)
|
|
43
|
+
assert_equal 0, win.cursor_x
|
|
44
|
+
|
|
45
|
+
# Should not go below 0
|
|
46
|
+
win.move_left(buffer)
|
|
47
|
+
assert_equal 0, win.cursor_x
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_move_right
|
|
51
|
+
buffer = RuVim::Buffer.new(id: 1, lines: ["abcde"])
|
|
52
|
+
win = RuVim::Window.new(id: 1, buffer_id: 1)
|
|
53
|
+
win.cursor_x = 0
|
|
54
|
+
|
|
55
|
+
win.move_right(buffer)
|
|
56
|
+
assert_equal 1, win.cursor_x
|
|
57
|
+
|
|
58
|
+
win.move_right(buffer, 2)
|
|
59
|
+
assert_equal 3, win.cursor_x
|
|
60
|
+
|
|
61
|
+
# Should not go beyond line length
|
|
62
|
+
win.move_right(buffer, 10)
|
|
63
|
+
assert_equal 5, win.cursor_x
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_move_left_with_multibyte
|
|
67
|
+
buffer = RuVim::Buffer.new(id: 1, lines: ["ab日本c"])
|
|
68
|
+
win = RuVim::Window.new(id: 1, buffer_id: 1)
|
|
69
|
+
win.cursor_x = 4 # on "c"
|
|
70
|
+
|
|
71
|
+
win.move_left(buffer)
|
|
72
|
+
assert_equal 3, win.cursor_x # on "本"
|
|
73
|
+
|
|
74
|
+
win.move_left(buffer)
|
|
75
|
+
assert_equal 2, win.cursor_x # on "日"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_move_up
|
|
79
|
+
buffer = RuVim::Buffer.new(id: 1, lines: ["abc", "def", "ghi"])
|
|
80
|
+
win = RuVim::Window.new(id: 1, buffer_id: 1)
|
|
81
|
+
win.cursor_y = 2
|
|
82
|
+
win.cursor_x = 1
|
|
83
|
+
|
|
84
|
+
win.move_up(buffer)
|
|
85
|
+
assert_equal 1, win.cursor_y
|
|
86
|
+
assert_equal 1, win.cursor_x
|
|
87
|
+
|
|
88
|
+
# Should not go below 0
|
|
89
|
+
win.move_up(buffer, 5)
|
|
90
|
+
assert_equal 0, win.cursor_y
|
|
91
|
+
end
|
|
92
|
+
|
|
34
93
|
def test_move_down_preserves_preferred_column_across_empty_line
|
|
35
94
|
long = "x" * 80
|
|
36
95
|
buffer = RuVim::Buffer.new(id: 1, lines: [long, "", long])
|