ruvim 0.2.0 → 0.3.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 +84 -0
- data/CLAUDE.md +1 -0
- data/docs/binding.md +23 -0
- data/docs/command.md +85 -0
- data/docs/config.md +2 -2
- data/docs/done.md +21 -0
- data/docs/spec.md +157 -12
- data/docs/todo.md +1 -5
- data/docs/vim_diff.md +94 -172
- data/lib/ruvim/app.rb +882 -69
- data/lib/ruvim/buffer.rb +35 -1
- data/lib/ruvim/cli.rb +12 -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 +455 -71
- data/lib/ruvim/ex_command_registry.rb +2 -0
- data/lib/ruvim/global_commands.rb +890 -63
- data/lib/ruvim/highlighter.rb +16 -21
- data/lib/ruvim/input.rb +39 -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/json.rb +18 -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/markdown_renderer.rb +248 -0
- data/lib/ruvim/rich_view/table_renderer.rb +176 -0
- data/lib/ruvim/rich_view.rb +93 -0
- data/lib/ruvim/screen.rb +503 -106
- 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 +14 -0
- data/test/app_completion_test.rb +73 -0
- data/test/app_dot_repeat_test.rb +13 -0
- data/test/app_motion_test.rb +13 -0
- data/test/app_scenario_test.rb +729 -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/dispatcher_test.rb +322 -0
- data/test/editor_register_test.rb +23 -0
- data/test/highlighter_test.rb +121 -0
- data/test/indent_test.rb +201 -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 +478 -0
- data/test/screen_test.rb +304 -0
- metadata +33 -2
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
require_relative "test_helper"
|
|
2
|
+
|
|
3
|
+
class RichViewTest < Minitest::Test
|
|
4
|
+
# --- Framework tests ---
|
|
5
|
+
|
|
6
|
+
def test_register_and_renderer_for
|
|
7
|
+
assert RuVim::RichView.renderer_for("tsv")
|
|
8
|
+
assert RuVim::RichView.renderer_for("csv")
|
|
9
|
+
assert_nil RuVim::RichView.renderer_for("unknown")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_registered_filetypes
|
|
13
|
+
fts = RuVim::RichView.registered_filetypes
|
|
14
|
+
assert_includes fts, "tsv"
|
|
15
|
+
assert_includes fts, "csv"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_detect_format_from_filetype
|
|
19
|
+
editor = fresh_editor
|
|
20
|
+
buf = editor.current_buffer
|
|
21
|
+
buf.options["filetype"] = "csv"
|
|
22
|
+
assert_equal "csv", RuVim::RichView.detect_format(buf)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_detect_format_auto_tsv
|
|
26
|
+
editor = fresh_editor
|
|
27
|
+
buf = editor.current_buffer
|
|
28
|
+
buf.replace_all_lines!(["a\tb\tc", "d\te\tf"])
|
|
29
|
+
assert_equal "tsv", RuVim::RichView.detect_format(buf)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_detect_format_auto_csv
|
|
33
|
+
editor = fresh_editor
|
|
34
|
+
buf = editor.current_buffer
|
|
35
|
+
buf.replace_all_lines!(["a,b,c", "d,e,f"])
|
|
36
|
+
assert_equal "csv", RuVim::RichView.detect_format(buf)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_detect_format_returns_nil_for_plain_text
|
|
40
|
+
editor = fresh_editor
|
|
41
|
+
buf = editor.current_buffer
|
|
42
|
+
buf.replace_all_lines!(["hello world", "foo bar"])
|
|
43
|
+
assert_nil RuVim::RichView.detect_format(buf)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_open_raises_when_format_unknown
|
|
47
|
+
editor = fresh_editor
|
|
48
|
+
buf = editor.current_buffer
|
|
49
|
+
buf.replace_all_lines!(["hello world"])
|
|
50
|
+
assert_raises(RuVim::CommandError) { RuVim::RichView.open!(editor) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# --- Mode transition tests ---
|
|
54
|
+
|
|
55
|
+
def test_active_returns_false_for_normal_mode
|
|
56
|
+
editor = fresh_editor
|
|
57
|
+
refute RuVim::RichView.active?(editor)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_open_enters_rich_mode
|
|
61
|
+
editor = fresh_editor
|
|
62
|
+
buf = editor.current_buffer
|
|
63
|
+
buf.replace_all_lines!(["a\tb", "c\td"])
|
|
64
|
+
buf.options["filetype"] = "tsv"
|
|
65
|
+
|
|
66
|
+
RuVim::RichView.open!(editor, format: "tsv")
|
|
67
|
+
assert_equal :rich, editor.mode
|
|
68
|
+
assert RuVim::RichView.active?(editor)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_open_stays_on_same_buffer
|
|
72
|
+
editor = fresh_editor
|
|
73
|
+
buf = editor.current_buffer
|
|
74
|
+
buf.replace_all_lines!(["a\tb", "c\td"])
|
|
75
|
+
buf.options["filetype"] = "tsv"
|
|
76
|
+
original_id = buf.id
|
|
77
|
+
|
|
78
|
+
RuVim::RichView.open!(editor, format: "tsv")
|
|
79
|
+
assert_equal original_id, editor.current_buffer.id
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def test_close_returns_to_normal_mode
|
|
83
|
+
editor = fresh_editor
|
|
84
|
+
buf = editor.current_buffer
|
|
85
|
+
buf.replace_all_lines!(["x\ty"])
|
|
86
|
+
buf.options["filetype"] = "tsv"
|
|
87
|
+
|
|
88
|
+
RuVim::RichView.open!(editor, format: "tsv")
|
|
89
|
+
assert_equal :rich, editor.mode
|
|
90
|
+
|
|
91
|
+
RuVim::RichView.close!(editor)
|
|
92
|
+
assert_equal :normal, editor.mode
|
|
93
|
+
refute RuVim::RichView.active?(editor)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def test_close_keeps_same_buffer
|
|
97
|
+
editor = fresh_editor
|
|
98
|
+
buf = editor.current_buffer
|
|
99
|
+
buf.replace_all_lines!(["x\ty"])
|
|
100
|
+
buf.options["filetype"] = "tsv"
|
|
101
|
+
original_id = buf.id
|
|
102
|
+
|
|
103
|
+
RuVim::RichView.open!(editor, format: "tsv")
|
|
104
|
+
RuVim::RichView.close!(editor)
|
|
105
|
+
assert_equal original_id, editor.current_buffer.id
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def test_toggle_opens_and_closes
|
|
109
|
+
editor = fresh_editor
|
|
110
|
+
buf = editor.current_buffer
|
|
111
|
+
buf.replace_all_lines!(["a\tb"])
|
|
112
|
+
buf.options["filetype"] = "tsv"
|
|
113
|
+
|
|
114
|
+
RuVim::RichView.toggle!(editor)
|
|
115
|
+
assert RuVim::RichView.active?(editor)
|
|
116
|
+
|
|
117
|
+
RuVim::RichView.toggle!(editor)
|
|
118
|
+
refute RuVim::RichView.active?(editor)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def test_rich_state_stores_format_and_delimiter
|
|
122
|
+
editor = fresh_editor
|
|
123
|
+
buf = editor.current_buffer
|
|
124
|
+
buf.replace_all_lines!(["a\tb"])
|
|
125
|
+
buf.options["filetype"] = "tsv"
|
|
126
|
+
|
|
127
|
+
RuVim::RichView.open!(editor, format: "tsv")
|
|
128
|
+
state = editor.rich_state
|
|
129
|
+
assert_equal "tsv", state[:format]
|
|
130
|
+
assert_equal "\t", state[:delimiter]
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def test_rich_state_csv
|
|
134
|
+
editor = fresh_editor
|
|
135
|
+
buf = editor.current_buffer
|
|
136
|
+
buf.replace_all_lines!(["a,b"])
|
|
137
|
+
buf.options["filetype"] = "csv"
|
|
138
|
+
|
|
139
|
+
RuVim::RichView.open!(editor, format: "csv")
|
|
140
|
+
state = editor.rich_state
|
|
141
|
+
assert_equal "csv", state[:format]
|
|
142
|
+
assert_equal ",", state[:delimiter]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def test_rich_state_nil_after_close
|
|
146
|
+
editor = fresh_editor
|
|
147
|
+
buf = editor.current_buffer
|
|
148
|
+
buf.replace_all_lines!(["a\tb"])
|
|
149
|
+
buf.options["filetype"] = "tsv"
|
|
150
|
+
|
|
151
|
+
RuVim::RichView.open!(editor, format: "tsv")
|
|
152
|
+
RuVim::RichView.close!(editor)
|
|
153
|
+
assert_nil editor.rich_state
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def test_active_during_command_line_from_rich_mode
|
|
157
|
+
editor = fresh_editor
|
|
158
|
+
buf = editor.current_buffer
|
|
159
|
+
buf.replace_all_lines!(["a\tb"])
|
|
160
|
+
buf.options["filetype"] = "tsv"
|
|
161
|
+
|
|
162
|
+
RuVim::RichView.open!(editor, format: "tsv")
|
|
163
|
+
editor.enter_command_line_mode(":")
|
|
164
|
+
assert_equal :command_line, editor.mode
|
|
165
|
+
assert RuVim::RichView.active?(editor)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def test_cancel_command_line_returns_to_rich_mode
|
|
169
|
+
editor = fresh_editor
|
|
170
|
+
buf = editor.current_buffer
|
|
171
|
+
buf.replace_all_lines!(["a\tb"])
|
|
172
|
+
buf.options["filetype"] = "tsv"
|
|
173
|
+
|
|
174
|
+
RuVim::RichView.open!(editor, format: "tsv")
|
|
175
|
+
assert_equal :rich, editor.mode
|
|
176
|
+
|
|
177
|
+
editor.enter_command_line_mode(":")
|
|
178
|
+
assert_equal :command_line, editor.mode
|
|
179
|
+
assert editor.rich_state
|
|
180
|
+
|
|
181
|
+
editor.cancel_command_line
|
|
182
|
+
assert_equal :rich, editor.mode
|
|
183
|
+
assert editor.rich_state
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def test_leave_command_line_returns_to_rich_mode
|
|
187
|
+
editor = fresh_editor
|
|
188
|
+
buf = editor.current_buffer
|
|
189
|
+
buf.replace_all_lines!(["a\tb"])
|
|
190
|
+
buf.options["filetype"] = "tsv"
|
|
191
|
+
|
|
192
|
+
RuVim::RichView.open!(editor, format: "tsv")
|
|
193
|
+
editor.enter_command_line_mode(":")
|
|
194
|
+
editor.leave_command_line
|
|
195
|
+
assert_equal :rich, editor.mode
|
|
196
|
+
assert editor.rich_state
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def test_leave_command_line_returns_to_normal_without_rich_state
|
|
200
|
+
editor = fresh_editor
|
|
201
|
+
editor.enter_command_line_mode(":")
|
|
202
|
+
editor.leave_command_line
|
|
203
|
+
assert_equal :normal, editor.mode
|
|
204
|
+
assert_nil editor.rich_state
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def test_enter_normal_mode_clears_rich_state
|
|
208
|
+
editor = fresh_editor
|
|
209
|
+
buf = editor.current_buffer
|
|
210
|
+
buf.replace_all_lines!(["a\tb"])
|
|
211
|
+
buf.options["filetype"] = "tsv"
|
|
212
|
+
|
|
213
|
+
RuVim::RichView.open!(editor, format: "tsv")
|
|
214
|
+
editor.enter_normal_mode
|
|
215
|
+
assert_nil editor.rich_state
|
|
216
|
+
assert_equal :normal, editor.mode
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def test_render_visible_lines_integration
|
|
220
|
+
editor = fresh_editor
|
|
221
|
+
buf = editor.current_buffer
|
|
222
|
+
buf.replace_all_lines!(["a\tbb", "ccc\td"])
|
|
223
|
+
buf.options["filetype"] = "tsv"
|
|
224
|
+
|
|
225
|
+
RuVim::RichView.open!(editor, format: "tsv")
|
|
226
|
+
lines = [buf.line_at(0), buf.line_at(1)]
|
|
227
|
+
rendered = RuVim::RichView.render_visible_lines(editor, lines)
|
|
228
|
+
assert_equal 2, rendered.length
|
|
229
|
+
assert_includes rendered[0], " | "
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def test_buffer_count_unchanged_after_open
|
|
233
|
+
editor = fresh_editor
|
|
234
|
+
buf = editor.current_buffer
|
|
235
|
+
buf.replace_all_lines!(["a\tb"])
|
|
236
|
+
buf.options["filetype"] = "tsv"
|
|
237
|
+
count_before = editor.buffers.length
|
|
238
|
+
|
|
239
|
+
RuVim::RichView.open!(editor, format: "tsv")
|
|
240
|
+
assert_equal count_before, editor.buffers.length
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# --- TableRenderer tests ---
|
|
244
|
+
|
|
245
|
+
def test_basic_alignment
|
|
246
|
+
lines = ["a\tbb\tccc", "dddd\te\tf"]
|
|
247
|
+
result = RuVim::RichView::TableRenderer.render_visible(lines, delimiter: "\t")
|
|
248
|
+
assert_equal 2, result.length
|
|
249
|
+
# Both lines should have same total display width
|
|
250
|
+
w0 = RuVim::DisplayWidth.display_width(result[0])
|
|
251
|
+
w1 = RuVim::DisplayWidth.display_width(result[1])
|
|
252
|
+
assert_equal w0, w1
|
|
253
|
+
# Check separator presence
|
|
254
|
+
assert_includes result[0], " | "
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def test_uneven_column_count
|
|
258
|
+
lines = ["a\tb", "c\td\te"]
|
|
259
|
+
result = RuVim::RichView::TableRenderer.render_visible(lines, delimiter: "\t")
|
|
260
|
+
assert_equal 2, result.length
|
|
261
|
+
# First row should have 3 columns padded (missing column filled)
|
|
262
|
+
parts0 = result[0].split(" | ")
|
|
263
|
+
parts1 = result[1].split(" | ")
|
|
264
|
+
assert_equal 3, parts0.length
|
|
265
|
+
assert_equal 3, parts1.length
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def test_empty_cells
|
|
269
|
+
lines = ["a\t\tc", "\tb\t"]
|
|
270
|
+
result = RuVim::RichView::TableRenderer.render_visible(lines, delimiter: "\t")
|
|
271
|
+
assert_equal 2, result.length
|
|
272
|
+
assert_includes result[0], " | "
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def test_single_column_passthrough
|
|
276
|
+
lines = ["abc", "def", "ghi"]
|
|
277
|
+
result = RuVim::RichView::TableRenderer.render_visible(lines, delimiter: "\t")
|
|
278
|
+
assert_equal lines, result
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def test_cjk_characters_alignment
|
|
282
|
+
lines = ["名前\t年齢", "太郎\t25", "Alice\t30"]
|
|
283
|
+
result = RuVim::RichView::TableRenderer.render_visible(lines, delimiter: "\t")
|
|
284
|
+
assert_equal 3, result.length
|
|
285
|
+
# All rows should have the same display width
|
|
286
|
+
widths = result.map { |r| RuVim::DisplayWidth.display_width(r) }
|
|
287
|
+
assert_equal 1, widths.uniq.length, "All rows should have same display width: #{widths.inspect}"
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def test_csv_basic
|
|
291
|
+
lines = ["a,b,c", "dd,e,ff"]
|
|
292
|
+
result = RuVim::RichView::TableRenderer.render_visible(lines, delimiter: ",")
|
|
293
|
+
assert_equal 2, result.length
|
|
294
|
+
assert_includes result[0], " | "
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def test_csv_quoted_fields
|
|
298
|
+
lines = ['"hello, world",b,c', 'a,"say ""hi""",d']
|
|
299
|
+
result = RuVim::RichView::TableRenderer.render_visible(lines, delimiter: ",")
|
|
300
|
+
assert_equal 2, result.length
|
|
301
|
+
# First row first field should be unquoted
|
|
302
|
+
first_field = result[0].split(" | ").first.strip
|
|
303
|
+
assert_equal "hello, world", first_field
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def test_csv_quoted_field_with_escaped_quotes
|
|
307
|
+
fields = RuVim::RichView::TableRenderer.parse_csv_fields('"say ""hi""",b')
|
|
308
|
+
assert_equal ['say "hi"', "b"], fields
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def test_empty_lines
|
|
312
|
+
result = RuVim::RichView::TableRenderer.render_visible([], delimiter: "\t")
|
|
313
|
+
assert_equal [], result
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# --- TableRenderer helper method tests ---
|
|
317
|
+
|
|
318
|
+
def test_compute_col_widths_basic
|
|
319
|
+
lines = ["a\tbb\tccc", "dddd\te\tf"]
|
|
320
|
+
widths = RuVim::RichView::TableRenderer.compute_col_widths(lines, delimiter: "\t")
|
|
321
|
+
assert_equal [4, 2, 3], widths
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def test_compute_col_widths_single_column
|
|
325
|
+
lines = ["abc", "def"]
|
|
326
|
+
assert_nil RuVim::RichView::TableRenderer.compute_col_widths(lines, delimiter: "\t")
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def test_compute_col_widths_empty
|
|
330
|
+
assert_nil RuVim::RichView::TableRenderer.compute_col_widths([], delimiter: "\t")
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def test_compute_col_widths_cjk
|
|
334
|
+
lines = ["名前\t年齢", "Alice\t30"]
|
|
335
|
+
widths = RuVim::RichView::TableRenderer.compute_col_widths(lines, delimiter: "\t")
|
|
336
|
+
# 名前 = 4 display cols, Alice = 5 → max = 5
|
|
337
|
+
# 年齢 = 4 display cols, 30 = 2 → max = 4
|
|
338
|
+
assert_equal [5, 4], widths
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def test_format_line_basic
|
|
342
|
+
col_widths = [4, 2, 3]
|
|
343
|
+
result = RuVim::RichView::TableRenderer.format_line("a\tbb\tccc", delimiter: "\t", col_widths: col_widths)
|
|
344
|
+
assert_equal "a | bb | ccc", result
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def test_format_line_consistency_with_render_visible
|
|
348
|
+
lines = ["a\tbb\tccc", "dddd\te\tf"]
|
|
349
|
+
rendered = RuVim::RichView::TableRenderer.render_visible(lines, delimiter: "\t")
|
|
350
|
+
col_widths = RuVim::RichView::TableRenderer.compute_col_widths(lines, delimiter: "\t")
|
|
351
|
+
lines.each_with_index do |line, i|
|
|
352
|
+
formatted = RuVim::RichView::TableRenderer.format_line(line, delimiter: "\t", col_widths: col_widths)
|
|
353
|
+
assert_equal rendered[i], formatted, "format_line should match render_visible for line #{i}"
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def test_raw_to_formatted_char_index_first_field
|
|
358
|
+
# Raw: "Hello\tWorld\tFoo"
|
|
359
|
+
col_widths = [10, 10, 5]
|
|
360
|
+
r = RuVim::RichView::TableRenderer
|
|
361
|
+
# 'H' at raw 0 → formatted 0
|
|
362
|
+
assert_equal 0, r.raw_to_formatted_char_index("Hello\tWorld\tFoo", 0, delimiter: "\t", col_widths: col_widths)
|
|
363
|
+
# 'o' at raw 4 → formatted 4
|
|
364
|
+
assert_equal 4, r.raw_to_formatted_char_index("Hello\tWorld\tFoo", 4, delimiter: "\t", col_widths: col_widths)
|
|
365
|
+
# End of first field at raw 5 → formatted 5 (just past 'o', still in padded area)
|
|
366
|
+
assert_equal 5, r.raw_to_formatted_char_index("Hello\tWorld\tFoo", 5, delimiter: "\t", col_widths: col_widths)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def test_raw_to_formatted_char_index_second_field
|
|
370
|
+
col_widths = [10, 10, 5]
|
|
371
|
+
r = RuVim::RichView::TableRenderer
|
|
372
|
+
# 'W' at raw 6 → formatted 10 (col_widths[0]) + 3 (separator) = 13
|
|
373
|
+
assert_equal 13, r.raw_to_formatted_char_index("Hello\tWorld\tFoo", 6, delimiter: "\t", col_widths: col_widths)
|
|
374
|
+
# 'd' at raw 10 → formatted 13 + 4 = 17
|
|
375
|
+
assert_equal 17, r.raw_to_formatted_char_index("Hello\tWorld\tFoo", 10, delimiter: "\t", col_widths: col_widths)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def test_raw_to_formatted_char_index_third_field
|
|
379
|
+
col_widths = [10, 10, 5]
|
|
380
|
+
r = RuVim::RichView::TableRenderer
|
|
381
|
+
# 'F' at raw 12 → formatted 10 + 3 + 10 + 3 = 26
|
|
382
|
+
assert_equal 26, r.raw_to_formatted_char_index("Hello\tWorld\tFoo", 12, delimiter: "\t", col_widths: col_widths)
|
|
383
|
+
# Last 'o' at raw 14 → formatted 26 + 2 = 28
|
|
384
|
+
assert_equal 28, r.raw_to_formatted_char_index("Hello\tWorld\tFoo", 14, delimiter: "\t", col_widths: col_widths)
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def test_raw_to_formatted_alignment_across_rows
|
|
388
|
+
# When different rows map col_offset to different fields, the formatted
|
|
389
|
+
# positions should still be aligned (same column structure).
|
|
390
|
+
lines = ["Short\tField\tEnd", "LongerField\tF\tEnd"]
|
|
391
|
+
col_widths = RuVim::RichView::TableRenderer.compute_col_widths(lines, delimiter: "\t")
|
|
392
|
+
r = RuVim::RichView::TableRenderer
|
|
393
|
+
|
|
394
|
+
# Format both lines and verify separator positions match
|
|
395
|
+
f0 = r.format_line(lines[0], delimiter: "\t", col_widths: col_widths)
|
|
396
|
+
f1 = r.format_line(lines[1], delimiter: "\t", col_widths: col_widths)
|
|
397
|
+
assert_equal RuVim::DisplayWidth.display_width(f0), RuVim::DisplayWidth.display_width(f1)
|
|
398
|
+
|
|
399
|
+
# Map cursor at "End" field start for both lines — should give same formatted position
|
|
400
|
+
# Line 0: "Short\tField\tEnd" → raw 12 is 'E' in End
|
|
401
|
+
# Line 1: "LongerField\tF\tEnd" → raw 14 is 'E' in End
|
|
402
|
+
fi0 = r.raw_to_formatted_char_index(lines[0], 12, delimiter: "\t", col_widths: col_widths)
|
|
403
|
+
fi1 = r.raw_to_formatted_char_index(lines[1], 14, delimiter: "\t", col_widths: col_widths)
|
|
404
|
+
assert_equal fi0, fi1, "Same column start should map to same formatted position"
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def test_raw_to_formatted_char_index_cjk
|
|
408
|
+
# CJK fields: "太郎" is 2 chars but 4 display cols
|
|
409
|
+
col_widths = [5, 4]
|
|
410
|
+
r = RuVim::RichView::TableRenderer
|
|
411
|
+
# "太郎\t30" → formatted: "太郎 " (2+1 pad) + " | " (3) + "30 " (2+2 pad)
|
|
412
|
+
# Character counts: field "太郎"(2) + pad(1) + separator(3) = 6
|
|
413
|
+
# So "3" at raw 3 → formatted 6
|
|
414
|
+
assert_equal 6, r.raw_to_formatted_char_index("太郎\t30", 3, delimiter: "\t", col_widths: col_widths)
|
|
415
|
+
# "0" at raw 4 → formatted 7
|
|
416
|
+
assert_equal 7, r.raw_to_formatted_char_index("太郎\t30", 4, delimiter: "\t", col_widths: col_widths)
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def test_raw_to_formatted_display_col_basic
|
|
420
|
+
col_widths = [10, 10, 5]
|
|
421
|
+
r = RuVim::RichView::TableRenderer
|
|
422
|
+
# 'H' at raw 0 → display col 0
|
|
423
|
+
assert_equal 0, r.raw_to_formatted_display_col("Hello\tWorld\tFoo", 0, delimiter: "\t", col_widths: col_widths)
|
|
424
|
+
# 'o' at raw 4 → display col 4
|
|
425
|
+
assert_equal 4, r.raw_to_formatted_display_col("Hello\tWorld\tFoo", 4, delimiter: "\t", col_widths: col_widths)
|
|
426
|
+
# 'W' at raw 6 → display col 10 + 3 = 13
|
|
427
|
+
assert_equal 13, r.raw_to_formatted_display_col("Hello\tWorld\tFoo", 6, delimiter: "\t", col_widths: col_widths)
|
|
428
|
+
# 'F' at raw 12 → display col 10 + 3 + 10 + 3 = 26
|
|
429
|
+
assert_equal 26, r.raw_to_formatted_display_col("Hello\tWorld\tFoo", 12, delimiter: "\t", col_widths: col_widths)
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def test_raw_to_formatted_display_col_cjk
|
|
433
|
+
# "太郎" = 2 chars, 4 display cols; "Alice" = 5 chars, 5 display cols → max = 5
|
|
434
|
+
# "30" = 2 chars, 2 display cols; "年齢" = 2 chars, 4 display cols → max = 4
|
|
435
|
+
col_widths = [5, 4]
|
|
436
|
+
r = RuVim::RichView::TableRenderer
|
|
437
|
+
# "太郎\t30"
|
|
438
|
+
# "太" at raw 0 → display col 0
|
|
439
|
+
assert_equal 0, r.raw_to_formatted_display_col("太郎\t30", 0, delimiter: "\t", col_widths: col_widths)
|
|
440
|
+
# "郎" at raw 1 → display col = dw("太") = 2
|
|
441
|
+
assert_equal 2, r.raw_to_formatted_display_col("太郎\t30", 1, delimiter: "\t", col_widths: col_widths)
|
|
442
|
+
# end of first field at raw 2 → display col = dw("太郎") = 4
|
|
443
|
+
assert_equal 4, r.raw_to_formatted_display_col("太郎\t30", 2, delimiter: "\t", col_widths: col_widths)
|
|
444
|
+
# "3" at raw 3 → display col = 5 (col_widths[0]) + 3 (separator) = 8
|
|
445
|
+
assert_equal 8, r.raw_to_formatted_display_col("太郎\t30", 3, delimiter: "\t", col_widths: col_widths)
|
|
446
|
+
# "0" at raw 4 → display col = 8 + dw("3") = 9
|
|
447
|
+
assert_equal 9, r.raw_to_formatted_display_col("太郎\t30", 4, delimiter: "\t", col_widths: col_widths)
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def test_raw_to_formatted_display_col_alignment_across_cjk_rows
|
|
451
|
+
lines = ["太郎\t年齢", "Alice\t30"]
|
|
452
|
+
col_widths = RuVim::RichView::TableRenderer.compute_col_widths(lines, delimiter: "\t")
|
|
453
|
+
r = RuVim::RichView::TableRenderer
|
|
454
|
+
# Second field starts at same display col for both lines
|
|
455
|
+
# Line 0: "太郎\t年齢" → raw 3 is "年" → display col = col_widths[0]+3
|
|
456
|
+
# Line 1: "Alice\t30" → raw 6 is "3" → display col = col_widths[0]+3
|
|
457
|
+
dc0 = r.raw_to_formatted_display_col(lines[0], 3, delimiter: "\t", col_widths: col_widths)
|
|
458
|
+
dc1 = r.raw_to_formatted_display_col(lines[1], 6, delimiter: "\t", col_widths: col_widths)
|
|
459
|
+
assert_equal dc0, dc1, "Second field start should align across CJK and ASCII rows"
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
# --- Filetype detection tests ---
|
|
463
|
+
|
|
464
|
+
def test_detect_filetype_tsv
|
|
465
|
+
editor = RuVim::Editor.new
|
|
466
|
+
assert_equal "tsv", editor.detect_filetype("data.tsv")
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def test_detect_filetype_csv
|
|
470
|
+
editor = RuVim::Editor.new
|
|
471
|
+
assert_equal "csv", editor.detect_filetype("data.csv")
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def test_detect_filetype_tsv_uppercase
|
|
475
|
+
editor = RuVim::Editor.new
|
|
476
|
+
assert_equal "tsv", editor.detect_filetype("DATA.TSV")
|
|
477
|
+
end
|
|
478
|
+
end
|