ruvim 0.3.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/AGENTS.md +18 -6
- data/README.md +15 -1
- data/docs/binding.md +16 -0
- data/docs/command.md +78 -4
- data/docs/config.md +10 -2
- data/docs/spec.md +60 -9
- data/docs/tutorial.md +24 -0
- data/docs/vim_diff.md +18 -8
- data/lib/ruvim/app.rb +290 -8
- data/lib/ruvim/buffer.rb +14 -2
- data/lib/ruvim/cli.rb +6 -0
- data/lib/ruvim/editor.rb +12 -1
- 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 +176 -42
- data/lib/ruvim/highlighter.rb +3 -1
- data/lib/ruvim/input.rb +1 -0
- data/lib/ruvim/lang/diff.rb +41 -0
- data/lib/ruvim/lang/json.rb +34 -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.rb +16 -0
- data/lib/ruvim/screen.rb +9 -12
- data/lib/ruvim/version.rb +1 -1
- data/lib/ruvim.rb +10 -0
- data/test/app_completion_test.rb +25 -0
- data/test/app_scenario_test.rb +169 -0
- 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/display_width_test.rb +41 -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 +44 -0
- data/test/indent_test.rb +86 -0
- data/test/rich_view_test.rb +256 -0
- data/test/search_option_test.rb +19 -0
- data/test/test_helper.rb +9 -0
- metadata +17 -1
data/test/follow_test.rb
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
require "tempfile"
|
|
5
|
+
|
|
6
|
+
class FollowTest < Minitest::Test
|
|
7
|
+
def create_follow_app
|
|
8
|
+
@tmpfile = Tempfile.new(["follow_test", ".txt"])
|
|
9
|
+
@tmpfile.write("line1\nline2\n")
|
|
10
|
+
@tmpfile.flush
|
|
11
|
+
@path = @tmpfile.path
|
|
12
|
+
|
|
13
|
+
@app = RuVim::App.new(path: @path, clean: true)
|
|
14
|
+
@editor = @app.instance_variable_get(:@editor)
|
|
15
|
+
@dispatcher = @app.instance_variable_get(:@dispatcher)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def cleanup_follow_app
|
|
19
|
+
return unless @app
|
|
20
|
+
|
|
21
|
+
watchers = @app.instance_variable_get(:@follow_watchers)
|
|
22
|
+
watchers.each_value { |w| w.stop rescue nil }
|
|
23
|
+
watchers.clear
|
|
24
|
+
@tmpfile&.close!
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_follow_starts_on_file_buffer
|
|
28
|
+
create_follow_app
|
|
29
|
+
@dispatcher.dispatch_ex(@editor, "follow")
|
|
30
|
+
watchers = @app.instance_variable_get(:@follow_watchers)
|
|
31
|
+
buf = @editor.current_buffer
|
|
32
|
+
|
|
33
|
+
assert !@editor.message_error?, "Unexpected error: #{@editor.message}"
|
|
34
|
+
assert_equal :live, buf.stream_state
|
|
35
|
+
assert watchers.key?(buf.id)
|
|
36
|
+
assert_includes @editor.message.to_s, "[follow]"
|
|
37
|
+
ensure
|
|
38
|
+
cleanup_follow_app
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_follow_stops_on_ctrl_c
|
|
42
|
+
create_follow_app
|
|
43
|
+
@dispatcher.dispatch_ex(@editor, "follow")
|
|
44
|
+
buf = @editor.current_buffer
|
|
45
|
+
assert_equal :live, buf.stream_state
|
|
46
|
+
|
|
47
|
+
@app.send(:handle_key, :ctrl_c)
|
|
48
|
+
assert_nil buf.stream_state
|
|
49
|
+
assert_includes @editor.message.to_s, "stopped"
|
|
50
|
+
ensure
|
|
51
|
+
cleanup_follow_app
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_follow_toggle_stops
|
|
55
|
+
create_follow_app
|
|
56
|
+
@dispatcher.dispatch_ex(@editor, "follow")
|
|
57
|
+
buf = @editor.current_buffer
|
|
58
|
+
assert_equal :live, buf.stream_state
|
|
59
|
+
|
|
60
|
+
@dispatcher.dispatch_ex(@editor, "follow")
|
|
61
|
+
watchers = @app.instance_variable_get(:@follow_watchers)
|
|
62
|
+
assert_nil buf.stream_state
|
|
63
|
+
refute watchers.key?(buf.id)
|
|
64
|
+
assert_includes @editor.message.to_s, "stopped"
|
|
65
|
+
ensure
|
|
66
|
+
cleanup_follow_app
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def test_follow_stop_removes_sentinel_empty_line
|
|
70
|
+
create_follow_app
|
|
71
|
+
buf = @editor.current_buffer
|
|
72
|
+
lines_before = buf.line_count
|
|
73
|
+
|
|
74
|
+
@dispatcher.dispatch_ex(@editor, "follow")
|
|
75
|
+
@dispatcher.dispatch_ex(@editor, "follow")
|
|
76
|
+
|
|
77
|
+
assert_equal lines_before, buf.line_count, "Sentinel empty line should be removed on stop"
|
|
78
|
+
refute_equal "", buf.lines.last, "Last line should not be empty sentinel"
|
|
79
|
+
ensure
|
|
80
|
+
cleanup_follow_app
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def test_follow_makes_buffer_not_modifiable
|
|
84
|
+
create_follow_app
|
|
85
|
+
buf = @editor.current_buffer
|
|
86
|
+
assert buf.modifiable?
|
|
87
|
+
|
|
88
|
+
@dispatcher.dispatch_ex(@editor, "follow")
|
|
89
|
+
refute buf.modifiable?, "Buffer should not be modifiable during follow"
|
|
90
|
+
|
|
91
|
+
@dispatcher.dispatch_ex(@editor, "follow")
|
|
92
|
+
assert buf.modifiable?, "Buffer should be modifiable after follow stops"
|
|
93
|
+
ensure
|
|
94
|
+
cleanup_follow_app
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_follow_error_on_modified_buffer
|
|
98
|
+
create_follow_app
|
|
99
|
+
buf = @editor.current_buffer
|
|
100
|
+
buf.modified = true
|
|
101
|
+
|
|
102
|
+
@dispatcher.dispatch_ex(@editor, "follow")
|
|
103
|
+
assert_includes @editor.message.to_s, "unsaved changes"
|
|
104
|
+
assert_nil buf.stream_state
|
|
105
|
+
ensure
|
|
106
|
+
cleanup_follow_app
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_follow_error_on_no_path_buffer
|
|
110
|
+
@app = RuVim::App.new(clean: true)
|
|
111
|
+
@editor = @app.instance_variable_get(:@editor)
|
|
112
|
+
@dispatcher = @app.instance_variable_get(:@dispatcher)
|
|
113
|
+
@editor.materialize_intro_buffer!
|
|
114
|
+
buf = @editor.current_buffer
|
|
115
|
+
buf.instance_variable_set(:@path, nil)
|
|
116
|
+
|
|
117
|
+
@dispatcher.dispatch_ex(@editor, "follow")
|
|
118
|
+
assert_includes @editor.message.to_s, "No file"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def test_follow_appends_data_from_file
|
|
122
|
+
create_follow_app
|
|
123
|
+
win = @editor.current_window
|
|
124
|
+
buf = @editor.current_buffer
|
|
125
|
+
win.cursor_y = buf.line_count - 1
|
|
126
|
+
|
|
127
|
+
@dispatcher.dispatch_ex(@editor, "follow")
|
|
128
|
+
|
|
129
|
+
File.open(@path, "a") { |f| f.write("line3\nline4\n") }
|
|
130
|
+
|
|
131
|
+
assert_eventually(timeout: 3) do
|
|
132
|
+
@app.send(:drain_stream_events!)
|
|
133
|
+
buf.line_count > 3
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# line2 should NOT be joined with line3
|
|
137
|
+
assert_includes buf.lines, "line2"
|
|
138
|
+
assert_includes buf.lines, "line3"
|
|
139
|
+
assert_includes buf.lines, "line4"
|
|
140
|
+
assert_equal buf.line_count - 1, win.cursor_y
|
|
141
|
+
ensure
|
|
142
|
+
cleanup_follow_app
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def test_follow_no_scroll_when_cursor_not_at_end
|
|
146
|
+
create_follow_app
|
|
147
|
+
buf = @editor.current_buffer
|
|
148
|
+
win = @editor.current_window
|
|
149
|
+
win.cursor_y = 0
|
|
150
|
+
|
|
151
|
+
@dispatcher.dispatch_ex(@editor, "follow")
|
|
152
|
+
|
|
153
|
+
File.open(@path, "a") { |f| f.write("line3\n") }
|
|
154
|
+
|
|
155
|
+
assert_eventually(timeout: 3) do
|
|
156
|
+
@app.send(:drain_stream_events!)
|
|
157
|
+
buf.line_count > 2
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
assert_equal 0, win.cursor_y
|
|
161
|
+
ensure
|
|
162
|
+
cleanup_follow_app
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def test_startup_follow_applies_to_all_buffers
|
|
166
|
+
tmp1 = Tempfile.new(["follow_multi1", ".txt"])
|
|
167
|
+
tmp1.write("aaa\n"); tmp1.flush
|
|
168
|
+
tmp2 = Tempfile.new(["follow_multi2", ".txt"])
|
|
169
|
+
tmp2.write("bbb\n"); tmp2.flush
|
|
170
|
+
|
|
171
|
+
app = RuVim::App.new(paths: [tmp1.path, tmp2.path], follow: true, clean: true)
|
|
172
|
+
editor = app.instance_variable_get(:@editor)
|
|
173
|
+
watchers = app.instance_variable_get(:@follow_watchers)
|
|
174
|
+
|
|
175
|
+
bufs = editor.buffers.values.select(&:file_buffer?)
|
|
176
|
+
assert_equal 2, bufs.size
|
|
177
|
+
bufs.each do |buf|
|
|
178
|
+
assert_equal :live, buf.stream_state, "#{buf.display_name} should be in follow mode"
|
|
179
|
+
assert watchers.key?(buf.id), "#{buf.display_name} should have a watcher"
|
|
180
|
+
end
|
|
181
|
+
ensure
|
|
182
|
+
watchers&.each_value { |w| w.stop rescue nil }
|
|
183
|
+
tmp1&.close!
|
|
184
|
+
tmp2&.close!
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
private
|
|
188
|
+
|
|
189
|
+
def assert_eventually(timeout: 2, interval: 0.05)
|
|
190
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
191
|
+
loop do
|
|
192
|
+
return if yield
|
|
193
|
+
if Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline
|
|
194
|
+
flunk "Timed out waiting for condition"
|
|
195
|
+
end
|
|
196
|
+
sleep interval
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|