ruvim 0.2.0 → 0.4.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/.github/workflows/test.yml +4 -0
- data/AGENTS.md +96 -0
- data/CLAUDE.md +1 -0
- data/README.md +15 -1
- data/docs/binding.md +39 -0
- data/docs/command.md +163 -4
- data/docs/config.md +12 -4
- data/docs/done.md +21 -0
- data/docs/spec.md +214 -18
- data/docs/todo.md +1 -5
- data/docs/tutorial.md +24 -0
- data/docs/vim_diff.md +105 -173
- data/lib/ruvim/app.rb +1165 -70
- data/lib/ruvim/buffer.rb +47 -1
- data/lib/ruvim/cli.rb +18 -3
- data/lib/ruvim/clipboard.rb +2 -0
- data/lib/ruvim/command_invocation.rb +3 -1
- data/lib/ruvim/command_line.rb +2 -0
- data/lib/ruvim/command_registry.rb +2 -0
- data/lib/ruvim/config_dsl.rb +2 -0
- data/lib/ruvim/config_loader.rb +2 -0
- data/lib/ruvim/context.rb +2 -0
- data/lib/ruvim/dispatcher.rb +143 -13
- data/lib/ruvim/display_width.rb +3 -0
- data/lib/ruvim/editor.rb +466 -71
- data/lib/ruvim/ex_command_registry.rb +2 -0
- data/lib/ruvim/file_watcher.rb +243 -0
- data/lib/ruvim/git/blame.rb +245 -0
- data/lib/ruvim/git/branch.rb +97 -0
- data/lib/ruvim/git/commit.rb +102 -0
- data/lib/ruvim/git/diff.rb +129 -0
- data/lib/ruvim/git/handler.rb +84 -0
- data/lib/ruvim/git/log.rb +41 -0
- data/lib/ruvim/git/status.rb +103 -0
- data/lib/ruvim/global_commands.rb +1066 -105
- data/lib/ruvim/highlighter.rb +19 -22
- data/lib/ruvim/input.rb +40 -28
- data/lib/ruvim/keymap_manager.rb +83 -0
- data/lib/ruvim/keyword_chars.rb +2 -0
- data/lib/ruvim/lang/base.rb +25 -0
- data/lib/ruvim/lang/csv.rb +18 -0
- data/lib/ruvim/lang/diff.rb +41 -0
- data/lib/ruvim/lang/json.rb +52 -0
- data/lib/ruvim/lang/markdown.rb +170 -0
- data/lib/ruvim/lang/ruby.rb +236 -0
- data/lib/ruvim/lang/scheme.rb +44 -0
- data/lib/ruvim/lang/tsv.rb +19 -0
- data/lib/ruvim/rich_view/json_renderer.rb +131 -0
- data/lib/ruvim/rich_view/jsonl_renderer.rb +57 -0
- data/lib/ruvim/rich_view/markdown_renderer.rb +248 -0
- data/lib/ruvim/rich_view/table_renderer.rb +176 -0
- data/lib/ruvim/rich_view.rb +109 -0
- data/lib/ruvim/screen.rb +503 -109
- data/lib/ruvim/terminal.rb +18 -1
- data/lib/ruvim/text_metrics.rb +2 -0
- data/lib/ruvim/version.rb +1 -1
- data/lib/ruvim/window.rb +2 -0
- data/lib/ruvim.rb +24 -0
- data/test/app_completion_test.rb +98 -0
- data/test/app_dot_repeat_test.rb +13 -0
- data/test/app_motion_test.rb +13 -0
- data/test/app_scenario_test.rb +898 -1
- data/test/app_startup_test.rb +187 -0
- data/test/arglist_test.rb +113 -0
- data/test/buffer_test.rb +49 -30
- data/test/cli_test.rb +14 -0
- data/test/clipboard_test.rb +67 -0
- data/test/command_line_test.rb +118 -0
- data/test/config_dsl_test.rb +87 -0
- data/test/dispatcher_test.rb +322 -0
- data/test/display_width_test.rb +41 -0
- data/test/editor_register_test.rb +23 -0
- data/test/file_watcher_test.rb +197 -0
- data/test/follow_test.rb +199 -0
- data/test/git_blame_test.rb +713 -0
- data/test/highlighter_test.rb +165 -0
- data/test/indent_test.rb +287 -0
- data/test/input_screen_integration_test.rb +40 -2
- data/test/markdown_renderer_test.rb +279 -0
- data/test/on_save_hook_test.rb +150 -0
- data/test/rich_view_test.rb +734 -0
- data/test/screen_test.rb +304 -0
- data/test/search_option_test.rb +19 -0
- data/test/test_helper.rb +9 -0
- metadata +49 -2
data/test/app_startup_test.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require_relative "test_helper"
|
|
2
2
|
require "tempfile"
|
|
3
3
|
require "stringio"
|
|
4
|
+
require "tmpdir"
|
|
4
5
|
|
|
5
6
|
class AppStartupTest < Minitest::Test
|
|
6
7
|
def test_startup_ex_command_runs_after_boot
|
|
@@ -107,6 +108,34 @@ class AppStartupTest < Minitest::Test
|
|
|
107
108
|
end
|
|
108
109
|
end
|
|
109
110
|
|
|
111
|
+
def test_startup_horizontal_split_focuses_first_file
|
|
112
|
+
Tempfile.create(["ruvim-a", ".txt"]) do |a|
|
|
113
|
+
Tempfile.create(["ruvim-b", ".txt"]) do |b|
|
|
114
|
+
a.write("a\n"); a.flush
|
|
115
|
+
b.write("b\n"); b.flush
|
|
116
|
+
|
|
117
|
+
app = RuVim::App.new(paths: [a.path, b.path], clean: true, startup_open_layout: :horizontal)
|
|
118
|
+
editor = app.instance_variable_get(:@editor)
|
|
119
|
+
|
|
120
|
+
assert_equal a.path, editor.current_buffer.path
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def test_startup_vertical_split_focuses_first_file
|
|
126
|
+
Tempfile.create(["ruvim-a", ".txt"]) do |a|
|
|
127
|
+
Tempfile.create(["ruvim-b", ".txt"]) do |b|
|
|
128
|
+
a.write("a\n"); a.flush
|
|
129
|
+
b.write("b\n"); b.flush
|
|
130
|
+
|
|
131
|
+
app = RuVim::App.new(paths: [a.path, b.path], clean: true, startup_open_layout: :vertical)
|
|
132
|
+
editor = app.instance_variable_get(:@editor)
|
|
133
|
+
|
|
134
|
+
assert_equal a.path, editor.current_buffer.path
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
110
139
|
def test_startup_tab_layout_opens_multiple_tabs
|
|
111
140
|
Tempfile.create(["ruvim-a", ".txt"]) do |a|
|
|
112
141
|
Tempfile.create(["ruvim-b", ".txt"]) do |b|
|
|
@@ -121,6 +150,21 @@ class AppStartupTest < Minitest::Test
|
|
|
121
150
|
end
|
|
122
151
|
end
|
|
123
152
|
|
|
153
|
+
def test_startup_tab_layout_focuses_first_tab
|
|
154
|
+
Tempfile.create(["ruvim-a", ".txt"]) do |a|
|
|
155
|
+
Tempfile.create(["ruvim-b", ".txt"]) do |b|
|
|
156
|
+
a.write("a\n"); a.flush
|
|
157
|
+
b.write("b\n"); b.flush
|
|
158
|
+
|
|
159
|
+
app = RuVim::App.new(paths: [a.path, b.path], clean: true, startup_open_layout: :tab)
|
|
160
|
+
editor = app.instance_variable_get(:@editor)
|
|
161
|
+
|
|
162
|
+
assert_equal 1, editor.current_tabpage_number
|
|
163
|
+
assert_equal a.path, editor.current_buffer.path
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
124
168
|
def test_restricted_mode_disables_ex_ruby
|
|
125
169
|
app = RuVim::App.new(clean: true, restricted: true)
|
|
126
170
|
editor = app.instance_variable_get(:@editor)
|
|
@@ -206,4 +250,147 @@ class AppStartupTest < Minitest::Test
|
|
|
206
250
|
assert_match(/startup_actions\.done/, text)
|
|
207
251
|
end
|
|
208
252
|
end
|
|
253
|
+
|
|
254
|
+
def test_stdin_stream_mode_uses_stream_buffer_and_appends_input
|
|
255
|
+
app = RuVim::App.new(
|
|
256
|
+
clean: true,
|
|
257
|
+
stdin: StringIO.new("line1\nline2\n"),
|
|
258
|
+
ui_stdin: StringIO.new,
|
|
259
|
+
stdin_stream_mode: true
|
|
260
|
+
)
|
|
261
|
+
editor = app.instance_variable_get(:@editor)
|
|
262
|
+
|
|
263
|
+
20.times do
|
|
264
|
+
app.send(:drain_stream_events!)
|
|
265
|
+
thread = app.instance_variable_get(:@stream_reader_thread)
|
|
266
|
+
break unless thread&.alive?
|
|
267
|
+
sleep 0.005
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
buf = editor.current_buffer
|
|
271
|
+
assert_equal :stream, buf.kind
|
|
272
|
+
assert_equal "[stdin]", buf.display_name
|
|
273
|
+
assert_equal true, buf.readonly?
|
|
274
|
+
assert_equal false, buf.modifiable?
|
|
275
|
+
assert_includes [:live, :closed], buf.stream_state
|
|
276
|
+
assert_equal ["line1", "line2", ""], buf.lines
|
|
277
|
+
assert_match(/\[stdin\] (follow|EOF)/, editor.message)
|
|
278
|
+
ensure
|
|
279
|
+
app&.send(:shutdown_stream_reader!)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def test_large_file_threshold_uses_async_loader
|
|
283
|
+
prev = ENV["RUVIM_ASYNC_FILE_THRESHOLD_BYTES"]
|
|
284
|
+
app = nil
|
|
285
|
+
Tempfile.create(["ruvim-large-async", ".txt"]) do |f|
|
|
286
|
+
begin
|
|
287
|
+
f.write("a\nb\nc\n")
|
|
288
|
+
f.flush
|
|
289
|
+
|
|
290
|
+
ENV["RUVIM_ASYNC_FILE_THRESHOLD_BYTES"] = "1"
|
|
291
|
+
app = RuVim::App.new(clean: true)
|
|
292
|
+
editor = app.instance_variable_get(:@editor)
|
|
293
|
+
|
|
294
|
+
buf = editor.open_path(f.path)
|
|
295
|
+
assert_match(/loading/i, editor.message)
|
|
296
|
+
assert_includes [:live, :closed], buf.loading_state
|
|
297
|
+
assert_equal false, buf.modifiable?
|
|
298
|
+
|
|
299
|
+
100.times do
|
|
300
|
+
app.send(:drain_stream_events!)
|
|
301
|
+
break if buf.loading_state != :live
|
|
302
|
+
state = app.instance_variable_get(:@async_file_loads)[buf.id]
|
|
303
|
+
break unless state && state[:thread]&.alive?
|
|
304
|
+
sleep 0.005
|
|
305
|
+
end
|
|
306
|
+
app.send(:drain_stream_events!)
|
|
307
|
+
|
|
308
|
+
assert_equal :closed, buf.loading_state
|
|
309
|
+
assert_equal true, buf.modifiable?
|
|
310
|
+
assert_equal %w[a b c], buf.lines
|
|
311
|
+
assert_match(/#{Regexp.escape(f.path)}/, editor.message)
|
|
312
|
+
ensure
|
|
313
|
+
ENV["RUVIM_ASYNC_FILE_THRESHOLD_BYTES"] = prev
|
|
314
|
+
app&.send(:shutdown_background_readers!)
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def test_very_large_file_shows_prefix_then_appends_rest
|
|
320
|
+
prev_async = ENV["RUVIM_ASYNC_FILE_THRESHOLD_BYTES"]
|
|
321
|
+
prev_prefix = ENV["RUVIM_ASYNC_FILE_PREFIX_BYTES"]
|
|
322
|
+
app = nil
|
|
323
|
+
Tempfile.create(["ruvim-large-prefix", ".txt"]) do |f|
|
|
324
|
+
begin
|
|
325
|
+
f.write("ab\ncd\nef\n")
|
|
326
|
+
f.flush
|
|
327
|
+
|
|
328
|
+
ENV["RUVIM_ASYNC_FILE_THRESHOLD_BYTES"] = "1"
|
|
329
|
+
ENV["RUVIM_ASYNC_FILE_PREFIX_BYTES"] = "4"
|
|
330
|
+
app = RuVim::App.new(clean: true)
|
|
331
|
+
editor = app.instance_variable_get(:@editor)
|
|
332
|
+
|
|
333
|
+
buf = editor.open_path(f.path)
|
|
334
|
+
assert_equal :live, buf.loading_state
|
|
335
|
+
assert_equal ["ab", "c"], buf.lines
|
|
336
|
+
assert_match(/showing first/i, editor.message)
|
|
337
|
+
|
|
338
|
+
100.times do
|
|
339
|
+
app.send(:drain_stream_events!)
|
|
340
|
+
break if buf.loading_state != :live
|
|
341
|
+
sleep 0.005
|
|
342
|
+
end
|
|
343
|
+
app.send(:drain_stream_events!)
|
|
344
|
+
|
|
345
|
+
assert_equal :closed, buf.loading_state
|
|
346
|
+
assert_equal %w[ab cd ef], buf.lines
|
|
347
|
+
ensure
|
|
348
|
+
ENV["RUVIM_ASYNC_FILE_THRESHOLD_BYTES"] = prev_async
|
|
349
|
+
ENV["RUVIM_ASYNC_FILE_PREFIX_BYTES"] = prev_prefix
|
|
350
|
+
app&.send(:shutdown_background_readers!)
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def test_command_line_history_persists_in_xdg_state_home
|
|
356
|
+
Dir.mktmpdir("ruvim-history-xdg") do |dir|
|
|
357
|
+
prev_xdg = ENV["XDG_STATE_HOME"]
|
|
358
|
+
prev_home = ENV["HOME"]
|
|
359
|
+
ENV["XDG_STATE_HOME"] = dir
|
|
360
|
+
ENV["HOME"] = dir
|
|
361
|
+
|
|
362
|
+
app1 = RuVim::App.new(clean: true)
|
|
363
|
+
app1.send(:push_command_line_history, ":", "set number")
|
|
364
|
+
app1.send(:push_command_line_history, "/", "foo")
|
|
365
|
+
app1.send(:push_command_line_history, "?", "bar")
|
|
366
|
+
app1.send(:save_command_line_history!)
|
|
367
|
+
|
|
368
|
+
path = File.join(dir, "ruvim", "history.json")
|
|
369
|
+
assert_equal true, File.file?(path)
|
|
370
|
+
|
|
371
|
+
app2 = RuVim::App.new(clean: true)
|
|
372
|
+
hist = app2.instance_variable_get(:@cmdline_history)
|
|
373
|
+
assert_equal ["set number"], hist[":"]
|
|
374
|
+
assert_equal ["foo"], hist["/"]
|
|
375
|
+
assert_equal ["bar"], hist["?"]
|
|
376
|
+
ensure
|
|
377
|
+
ENV["XDG_STATE_HOME"] = prev_xdg
|
|
378
|
+
ENV["HOME"] = prev_home
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def test_command_line_history_path_falls_back_to_home_dot_ruvim
|
|
383
|
+
Dir.mktmpdir("ruvim-history-home") do |dir|
|
|
384
|
+
prev_xdg = ENV["XDG_STATE_HOME"]
|
|
385
|
+
prev_home = ENV["HOME"]
|
|
386
|
+
ENV.delete("XDG_STATE_HOME")
|
|
387
|
+
ENV["HOME"] = dir
|
|
388
|
+
|
|
389
|
+
app = RuVim::App.new(clean: true)
|
|
390
|
+
assert_equal File.join(dir, ".ruvim", "history.json"), app.send(:command_line_history_file_path)
|
|
391
|
+
ensure
|
|
392
|
+
ENV["XDG_STATE_HOME"] = prev_xdg
|
|
393
|
+
ENV["HOME"] = prev_home
|
|
394
|
+
end
|
|
395
|
+
end
|
|
209
396
|
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
class ArglistTest < Minitest::Test
|
|
4
|
+
def setup
|
|
5
|
+
@editor = RuVim::Editor.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def test_initial_arglist_is_empty
|
|
9
|
+
assert_empty @editor.arglist
|
|
10
|
+
assert_equal 0, @editor.arglist_index
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_set_arglist
|
|
14
|
+
paths = %w[file1.txt file2.txt file3.txt]
|
|
15
|
+
@editor.set_arglist(paths)
|
|
16
|
+
|
|
17
|
+
assert_equal paths, @editor.arglist
|
|
18
|
+
assert_equal 0, @editor.arglist_index
|
|
19
|
+
assert_equal "file1.txt", @editor.arglist_current
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_arglist_next
|
|
23
|
+
@editor.set_arglist(%w[file1.txt file2.txt file3.txt])
|
|
24
|
+
|
|
25
|
+
assert_equal "file2.txt", @editor.arglist_next
|
|
26
|
+
assert_equal 1, @editor.arglist_index
|
|
27
|
+
|
|
28
|
+
assert_equal "file3.txt", @editor.arglist_next
|
|
29
|
+
assert_equal 2, @editor.arglist_index
|
|
30
|
+
|
|
31
|
+
error = assert_raises(RuVim::CommandError) do
|
|
32
|
+
@editor.arglist_next
|
|
33
|
+
end
|
|
34
|
+
assert_equal "Already at last argument", error.message
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_arglist_prev
|
|
38
|
+
@editor.set_arglist(%w[file1.txt file2.txt file3.txt])
|
|
39
|
+
@editor.arglist_next(2) # Move to index 2
|
|
40
|
+
|
|
41
|
+
assert_equal "file2.txt", @editor.arglist_prev
|
|
42
|
+
assert_equal 1, @editor.arglist_index
|
|
43
|
+
|
|
44
|
+
assert_equal "file1.txt", @editor.arglist_prev
|
|
45
|
+
assert_equal 0, @editor.arglist_index
|
|
46
|
+
|
|
47
|
+
error = assert_raises(RuVim::CommandError) do
|
|
48
|
+
@editor.arglist_prev
|
|
49
|
+
end
|
|
50
|
+
assert_equal "Already at first argument", error.message
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_arglist_first
|
|
54
|
+
@editor.set_arglist(%w[file1.txt file2.txt file3.txt])
|
|
55
|
+
@editor.arglist_next(2) # Move to index 2
|
|
56
|
+
|
|
57
|
+
assert_equal "file1.txt", @editor.arglist_first
|
|
58
|
+
assert_equal 0, @editor.arglist_index
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_arglist_last
|
|
62
|
+
@editor.set_arglist(%w[file1.txt file2.txt file3.txt])
|
|
63
|
+
|
|
64
|
+
assert_equal "file3.txt", @editor.arglist_last
|
|
65
|
+
assert_equal 2, @editor.arglist_index
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def test_arglist_next_with_count
|
|
69
|
+
@editor.set_arglist(%w[file1.txt file2.txt file3.txt file4.txt])
|
|
70
|
+
|
|
71
|
+
assert_equal "file3.txt", @editor.arglist_next(2)
|
|
72
|
+
assert_equal 2, @editor.arglist_index
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def test_arglist_prev_with_count
|
|
76
|
+
@editor.set_arglist(%w[file1.txt file2.txt file3.txt file4.txt])
|
|
77
|
+
@editor.arglist_last
|
|
78
|
+
|
|
79
|
+
assert_equal "file2.txt", @editor.arglist_prev(2)
|
|
80
|
+
assert_equal 1, @editor.arglist_index
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def test_arglist_empty_operations
|
|
84
|
+
assert_nil @editor.arglist_current
|
|
85
|
+
assert_nil @editor.arglist_first
|
|
86
|
+
assert_nil @editor.arglist_last
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def test_startup_multiple_files_creates_buffers
|
|
90
|
+
# Simulate what open_startup_paths! does without layout option:
|
|
91
|
+
# all files should be loaded as buffers so they appear in :ls
|
|
92
|
+
paths = [File.expand_path("../../t.md", __FILE__),
|
|
93
|
+
File.expand_path("../../t.rb", __FILE__)]
|
|
94
|
+
existing = paths.select { |p| File.exist?(p) }
|
|
95
|
+
skip "need t.md and t.rb in project root" if existing.length < 2
|
|
96
|
+
|
|
97
|
+
@editor.ensure_bootstrap_buffer!
|
|
98
|
+
@editor.set_arglist(paths)
|
|
99
|
+
|
|
100
|
+
# Open first file (displayed in current window)
|
|
101
|
+
first_buf = @editor.add_buffer_from_file(paths[0])
|
|
102
|
+
@editor.switch_to_buffer(first_buf.id)
|
|
103
|
+
|
|
104
|
+
# Load remaining files as buffers (not displayed, but registered)
|
|
105
|
+
paths[1..].each { |p| @editor.add_buffer_from_file(p) }
|
|
106
|
+
|
|
107
|
+
# All files should be in the buffer list
|
|
108
|
+
all_paths = @editor.buffer_ids.map { |id| @editor.buffers[id].path }
|
|
109
|
+
paths.each do |p|
|
|
110
|
+
assert_includes all_paths, p, "#{p} should appear in buffer list"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
data/test/buffer_test.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require_relative "test_helper"
|
|
2
|
+
require "tmpdir"
|
|
2
3
|
|
|
3
4
|
class BufferTest < Minitest::Test
|
|
4
5
|
def test_insert_and_undo_redo_group
|
|
@@ -25,48 +26,66 @@ class BufferTest < Minitest::Test
|
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def test_reload_from_file_clears_modified_and_history
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
Dir.mktmpdir do |dir|
|
|
30
|
+
path = File.join(dir, "ruvim_buffer_reload_test.txt")
|
|
31
|
+
File.write(path, "one\n")
|
|
32
|
+
b = RuVim::Buffer.from_file(id: 1, path: path)
|
|
33
|
+
b.insert_char(0, 0, "X")
|
|
34
|
+
assert b.modified?
|
|
35
|
+
assert b.can_undo?
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
b.reload_from_file!
|
|
38
|
+
assert_equal ["one"], b.lines
|
|
39
|
+
refute b.modified?
|
|
40
|
+
refute b.can_undo?
|
|
41
|
+
end
|
|
39
42
|
end
|
|
40
43
|
|
|
41
44
|
def test_utf8_file_is_loaded_as_utf8_text_not_binary_bytes
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
Dir.mktmpdir do |dir|
|
|
46
|
+
path = File.join(dir, "ruvim_utf8_test.txt")
|
|
47
|
+
File.binwrite(path, "bar 日本語 編集\n".encode("UTF-8"))
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
b = RuVim::Buffer.from_file(id: 1, path: path)
|
|
50
|
+
assert_equal Encoding::UTF_8, b.line_at(0).encoding
|
|
51
|
+
assert_equal "bar 日本語 編集", b.line_at(0)
|
|
52
|
+
assert_equal 10, b.line_length(0)
|
|
53
|
+
end
|
|
49
54
|
end
|
|
50
55
|
|
|
51
56
|
def test_invalid_bytes_are_decoded_to_valid_utf8_with_replacement
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
Dir.mktmpdir do |dir|
|
|
58
|
+
path = File.join(dir, "ruvim_invalid_bytes_test.txt")
|
|
59
|
+
File.binwrite(path, "A\xFFB\n".b)
|
|
54
60
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
b = RuVim::Buffer.from_file(id: 1, path: path)
|
|
62
|
+
line = b.line_at(0)
|
|
63
|
+
assert_equal Encoding::UTF_8, line.encoding
|
|
64
|
+
assert_equal true, line.valid_encoding?
|
|
65
|
+
assert_match(/A.*B/, line)
|
|
66
|
+
end
|
|
60
67
|
end
|
|
61
68
|
|
|
62
69
|
def test_write_to_saves_utf8_text
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
Dir.mktmpdir do |dir|
|
|
71
|
+
path = File.join(dir, "ruvim_write_utf8_test.txt")
|
|
72
|
+
b = RuVim::Buffer.new(id: 1, lines: ["日本語", "abc"])
|
|
73
|
+
b.write_to(path)
|
|
74
|
+
|
|
75
|
+
data = File.binread(path)
|
|
76
|
+
text = data.force_encoding(Encoding::UTF_8)
|
|
77
|
+
assert_equal true, text.valid_encoding?
|
|
78
|
+
assert_equal "日本語\nabc", text
|
|
79
|
+
end
|
|
80
|
+
end
|
|
66
81
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
82
|
+
def test_append_stream_text_updates_lines_without_marking_modified
|
|
83
|
+
b = RuVim::Buffer.new(id: 1, lines: [""])
|
|
84
|
+
b.append_stream_text!("a\n")
|
|
85
|
+
b.append_stream_text!("b")
|
|
86
|
+
b.append_stream_text!("\n\nc\n")
|
|
87
|
+
|
|
88
|
+
assert_equal ["a", "b", "", "c", ""], b.lines
|
|
89
|
+
refute b.modified?
|
|
71
90
|
end
|
|
72
91
|
end
|
data/test/cli_test.rb
CHANGED
|
@@ -153,6 +153,20 @@ class CLITest < Minitest::Test
|
|
|
153
153
|
assert_equal "", err.string
|
|
154
154
|
end
|
|
155
155
|
|
|
156
|
+
def test_parse_follow_option
|
|
157
|
+
opts = RuVim::CLI.parse(["-f", "log.txt"])
|
|
158
|
+
|
|
159
|
+
assert_equal ["log.txt"], opts.files
|
|
160
|
+
assert_equal true, opts.follow
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def test_help_mentions_follow_option
|
|
164
|
+
out = StringIO.new
|
|
165
|
+
err = StringIO.new
|
|
166
|
+
RuVim::CLI.run(["--help"], stdout: out, stderr: err, stdin: StringIO.new)
|
|
167
|
+
assert_match(/-f\s+Open file in follow mode/, out.string)
|
|
168
|
+
end
|
|
169
|
+
|
|
156
170
|
def test_run_returns_error_for_missing_config_file
|
|
157
171
|
out = StringIO.new
|
|
158
172
|
err = StringIO.new
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
require_relative "test_helper"
|
|
2
|
+
|
|
3
|
+
class ClipboardTest < Minitest::Test
|
|
4
|
+
def setup
|
|
5
|
+
RuVim::Clipboard.reset_backend!
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def teardown
|
|
9
|
+
RuVim::Clipboard.reset_backend!
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_available_returns_true_when_backend_exists
|
|
13
|
+
RuVim::Clipboard.instance_variable_set(:@backend, { write: %w[echo], read: %w[echo] })
|
|
14
|
+
assert RuVim::Clipboard.available?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_available_returns_false_when_no_backend
|
|
18
|
+
RuVim::Clipboard.instance_variable_set(:@backend, false)
|
|
19
|
+
refute RuVim::Clipboard.available?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_read_returns_nil_when_no_read_cmd
|
|
23
|
+
RuVim::Clipboard.instance_variable_set(:@backend, { write: nil, read: nil })
|
|
24
|
+
assert_nil RuVim::Clipboard.read
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_read_returns_output_on_success
|
|
28
|
+
RuVim::Clipboard.instance_variable_set(:@backend, { write: %w[true], read: %w[echo hello] })
|
|
29
|
+
result = RuVim::Clipboard.read
|
|
30
|
+
assert_equal "hello\n", result
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_read_returns_nil_on_command_failure
|
|
34
|
+
RuVim::Clipboard.instance_variable_set(:@backend, { write: %w[true], read: %w[false] })
|
|
35
|
+
assert_nil RuVim::Clipboard.read
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_write_returns_false_when_no_write_cmd
|
|
39
|
+
RuVim::Clipboard.instance_variable_set(:@backend, { write: nil, read: nil })
|
|
40
|
+
refute RuVim::Clipboard.write("test")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def test_write_sends_text_to_command
|
|
44
|
+
RuVim::Clipboard.instance_variable_set(:@backend, { write: %w[cat], read: %w[true] })
|
|
45
|
+
assert RuVim::Clipboard.write("hello")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_pbcopy_backend_format
|
|
49
|
+
expected = { write: %w[pbcopy], read: %w[pbpaste] }
|
|
50
|
+
assert_equal expected, RuVim::Clipboard.pbcopy_backend
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_wayland_backend_format
|
|
54
|
+
expected = { write: %w[wl-copy], read: %w[wl-paste -n] }
|
|
55
|
+
assert_equal expected, RuVim::Clipboard.wayland_backend
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_xclip_backend_format
|
|
59
|
+
expected = { write: %w[xclip -selection clipboard -in], read: %w[xclip -selection clipboard -out] }
|
|
60
|
+
assert_equal expected, RuVim::Clipboard.xclip_backend
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_xsel_backend_format
|
|
64
|
+
expected = { write: %w[xsel --clipboard --input], read: %w[xsel --clipboard --output] }
|
|
65
|
+
assert_equal expected, RuVim::Clipboard.xsel_backend
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
require_relative "test_helper"
|
|
2
|
+
|
|
3
|
+
class CommandLineTest < Minitest::Test
|
|
4
|
+
def setup
|
|
5
|
+
@cl = RuVim::CommandLine.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def test_initial_state
|
|
9
|
+
assert_equal ":", @cl.prefix
|
|
10
|
+
assert_equal "", @cl.text
|
|
11
|
+
assert_equal 0, @cl.cursor
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_reset_with_custom_prefix
|
|
15
|
+
@cl.reset(prefix: "/")
|
|
16
|
+
assert_equal "/", @cl.prefix
|
|
17
|
+
assert_equal "", @cl.text
|
|
18
|
+
assert_equal 0, @cl.cursor
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_insert_appends_at_cursor
|
|
22
|
+
@cl.insert("abc")
|
|
23
|
+
assert_equal "abc", @cl.text
|
|
24
|
+
assert_equal 3, @cl.cursor
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_insert_at_middle
|
|
28
|
+
@cl.insert("ac")
|
|
29
|
+
@cl.instance_variable_set(:@cursor, 1)
|
|
30
|
+
@cl.insert("b")
|
|
31
|
+
assert_equal "abc", @cl.text
|
|
32
|
+
assert_equal 2, @cl.cursor
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_backspace_at_zero_does_nothing
|
|
36
|
+
@cl.backspace
|
|
37
|
+
assert_equal "", @cl.text
|
|
38
|
+
assert_equal 0, @cl.cursor
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_backspace_deletes_character
|
|
42
|
+
@cl.insert("abc")
|
|
43
|
+
@cl.backspace
|
|
44
|
+
assert_equal "ab", @cl.text
|
|
45
|
+
assert_equal 2, @cl.cursor
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_move_left
|
|
49
|
+
@cl.insert("abc")
|
|
50
|
+
@cl.move_left
|
|
51
|
+
assert_equal 2, @cl.cursor
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_move_left_at_zero_stays
|
|
55
|
+
@cl.move_left
|
|
56
|
+
assert_equal 0, @cl.cursor
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_move_right
|
|
60
|
+
@cl.insert("abc")
|
|
61
|
+
@cl.instance_variable_set(:@cursor, 1)
|
|
62
|
+
@cl.move_right
|
|
63
|
+
assert_equal 2, @cl.cursor
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_move_right_at_end_stays
|
|
67
|
+
@cl.insert("abc")
|
|
68
|
+
@cl.move_right
|
|
69
|
+
assert_equal 3, @cl.cursor
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_content_includes_prefix
|
|
73
|
+
@cl.insert("hello")
|
|
74
|
+
assert_equal ":hello", @cl.content
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def test_content_with_custom_prefix
|
|
78
|
+
@cl.reset(prefix: "/")
|
|
79
|
+
@cl.insert("search")
|
|
80
|
+
assert_equal "/search", @cl.content
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def test_clear_resets_text_and_cursor
|
|
84
|
+
@cl.insert("hello")
|
|
85
|
+
@cl.clear
|
|
86
|
+
assert_equal "", @cl.text
|
|
87
|
+
assert_equal 0, @cl.cursor
|
|
88
|
+
assert_equal ":", @cl.prefix
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def test_replace_text
|
|
92
|
+
@cl.insert("old")
|
|
93
|
+
@cl.replace_text("new text")
|
|
94
|
+
assert_equal "new text", @cl.text
|
|
95
|
+
assert_equal 8, @cl.cursor
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def test_replace_span_end_cursor
|
|
99
|
+
@cl.insert("hello world")
|
|
100
|
+
@cl.replace_span(6, 11, "ruby")
|
|
101
|
+
assert_equal "hello ruby", @cl.text
|
|
102
|
+
assert_equal 10, @cl.cursor
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def test_replace_span_start_cursor
|
|
106
|
+
@cl.insert("hello world")
|
|
107
|
+
@cl.replace_span(6, 11, "ruby", cursor_at: :start)
|
|
108
|
+
assert_equal "hello ruby", @cl.text
|
|
109
|
+
assert_equal 6, @cl.cursor
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def test_replace_span_integer_cursor
|
|
113
|
+
@cl.insert("hello world")
|
|
114
|
+
@cl.replace_span(6, 11, "ruby", cursor_at: 8)
|
|
115
|
+
assert_equal "hello ruby", @cl.text
|
|
116
|
+
assert_equal 8, @cl.cursor
|
|
117
|
+
end
|
|
118
|
+
end
|