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
|
@@ -0,0 +1,734 @@
|
|
|
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
|
+
# --- JSON Rich View tests ---
|
|
463
|
+
|
|
464
|
+
def test_json_registered
|
|
465
|
+
assert RuVim::RichView.renderer_for("json")
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def test_json_open_creates_virtual_buffer
|
|
469
|
+
editor = fresh_editor
|
|
470
|
+
buf = editor.current_buffer
|
|
471
|
+
buf.replace_all_lines!(['{"a":1,"b":[2,3]}'])
|
|
472
|
+
buf.options["filetype"] = "json"
|
|
473
|
+
count_before = editor.buffers.length
|
|
474
|
+
|
|
475
|
+
RuVim::RichView.open!(editor, format: "json")
|
|
476
|
+
assert_equal count_before + 1, editor.buffers.length
|
|
477
|
+
new_buf = editor.current_buffer
|
|
478
|
+
refute_equal buf.id, new_buf.id
|
|
479
|
+
assert_equal :json_formatted, new_buf.kind
|
|
480
|
+
assert new_buf.readonly?
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
def test_json_open_binds_close_keys
|
|
484
|
+
editor = fresh_editor
|
|
485
|
+
editor.keymap_manager = RuVim::KeymapManager.new
|
|
486
|
+
buf = editor.current_buffer
|
|
487
|
+
buf.replace_all_lines!(['{"a":1}'])
|
|
488
|
+
buf.options["filetype"] = "json"
|
|
489
|
+
|
|
490
|
+
RuVim::RichView.open!(editor, format: "json")
|
|
491
|
+
result = editor.keymap_manager.resolve_with_context(:normal, ["\e"], editor: editor)
|
|
492
|
+
assert_equal "rich.close_buffer", result.invocation.id
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
def test_json_open_pretty_prints
|
|
496
|
+
editor = fresh_editor
|
|
497
|
+
buf = editor.current_buffer
|
|
498
|
+
buf.replace_all_lines!(['{"a":1,"b":[2,3]}'])
|
|
499
|
+
buf.options["filetype"] = "json"
|
|
500
|
+
|
|
501
|
+
RuVim::RichView.open!(editor, format: "json")
|
|
502
|
+
new_buf = editor.current_buffer
|
|
503
|
+
lines = new_buf.lines
|
|
504
|
+
assert lines.length > 1, "Minified JSON should be expanded to multiple lines"
|
|
505
|
+
assert_equal "{", lines.first.strip
|
|
506
|
+
assert_equal "}", lines.last.strip
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def test_json_open_multiline_buffer
|
|
510
|
+
editor = fresh_editor
|
|
511
|
+
buf = editor.current_buffer
|
|
512
|
+
buf.replace_all_lines!(['{', '"key": "value"', '}'])
|
|
513
|
+
buf.options["filetype"] = "json"
|
|
514
|
+
|
|
515
|
+
RuVim::RichView.open!(editor, format: "json")
|
|
516
|
+
new_buf = editor.current_buffer
|
|
517
|
+
lines = new_buf.lines
|
|
518
|
+
assert lines.length >= 3
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def test_json_open_invalid_json_shows_error
|
|
522
|
+
editor = fresh_editor
|
|
523
|
+
buf = editor.current_buffer
|
|
524
|
+
buf.replace_all_lines!(['{"invalid json'])
|
|
525
|
+
buf.options["filetype"] = "json"
|
|
526
|
+
|
|
527
|
+
RuVim::RichView.open!(editor, format: "json")
|
|
528
|
+
# Should stay on original buffer
|
|
529
|
+
assert_equal buf.id, editor.current_buffer.id
|
|
530
|
+
assert_match(/JSON/, editor.message.to_s)
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
def test_json_open_does_not_enter_rich_mode
|
|
534
|
+
editor = fresh_editor
|
|
535
|
+
buf = editor.current_buffer
|
|
536
|
+
buf.replace_all_lines!(['{"a":1}'])
|
|
537
|
+
buf.options["filetype"] = "json"
|
|
538
|
+
|
|
539
|
+
RuVim::RichView.open!(editor, format: "json")
|
|
540
|
+
# Virtual buffer approach — no rich mode
|
|
541
|
+
assert_equal :normal, editor.mode
|
|
542
|
+
assert_nil editor.rich_state
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
def test_json_cursor_maps_to_formatted_line
|
|
546
|
+
editor = fresh_editor
|
|
547
|
+
buf = editor.current_buffer
|
|
548
|
+
# {"a":1,"b":{"c":2}}
|
|
549
|
+
buf.replace_all_lines!(['{"a":1,"b":{"c":2}}'])
|
|
550
|
+
buf.options["filetype"] = "json"
|
|
551
|
+
|
|
552
|
+
# Place cursor at "c" key — find its offset
|
|
553
|
+
line = buf.line_at(0)
|
|
554
|
+
idx = line.index('"c"')
|
|
555
|
+
editor.current_window.cursor_x = idx
|
|
556
|
+
|
|
557
|
+
RuVim::RichView.open!(editor, format: "json")
|
|
558
|
+
new_buf = editor.current_buffer
|
|
559
|
+
# Cursor should be on the line containing "c"
|
|
560
|
+
cursor_line = new_buf.line_at(editor.current_window.cursor_y)
|
|
561
|
+
assert_match(/"c"/, cursor_line, "Cursor should be on the line with \"c\" key")
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
def test_json_cursor_maps_multiline_source
|
|
565
|
+
editor = fresh_editor
|
|
566
|
+
buf = editor.current_buffer
|
|
567
|
+
buf.replace_all_lines!(['{', ' "x": [1, 2, 3]', '}'])
|
|
568
|
+
buf.options["filetype"] = "json"
|
|
569
|
+
|
|
570
|
+
# Place cursor on line 1 at the "x" key (col 2 = opening quote)
|
|
571
|
+
editor.current_window.cursor_y = 1
|
|
572
|
+
editor.current_window.cursor_x = 2
|
|
573
|
+
|
|
574
|
+
RuVim::RichView.open!(editor, format: "json")
|
|
575
|
+
new_buf = editor.current_buffer
|
|
576
|
+
cursor_line = new_buf.line_at(editor.current_window.cursor_y)
|
|
577
|
+
assert_match(/"x"/, cursor_line, "Cursor should be on the line with \"x\" key")
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
def test_json_cursor_at_start_stays_at_start
|
|
581
|
+
editor = fresh_editor
|
|
582
|
+
buf = editor.current_buffer
|
|
583
|
+
buf.replace_all_lines!(['{"a":1}'])
|
|
584
|
+
buf.options["filetype"] = "json"
|
|
585
|
+
editor.current_window.cursor_x = 0
|
|
586
|
+
|
|
587
|
+
RuVim::RichView.open!(editor, format: "json")
|
|
588
|
+
assert_equal 0, editor.current_window.cursor_y
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
def test_json_significant_offset
|
|
592
|
+
r = RuVim::RichView::JsonRenderer
|
|
593
|
+
# {"a" — 4 significant chars: { " a "
|
|
594
|
+
assert_equal 4, r.significant_char_count('{"a"', 4)
|
|
595
|
+
# { "a" — space outside string skipped, still 4 significant
|
|
596
|
+
assert_equal 4, r.significant_char_count('{ "a"', 5)
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
def test_json_line_for_significant_offset
|
|
600
|
+
formatted = "{\n \"a\": 1\n}"
|
|
601
|
+
r = RuVim::RichView::JsonRenderer
|
|
602
|
+
# count 0 → line 0 (before any char)
|
|
603
|
+
assert_equal 0, r.line_for_significant_count(formatted, 0)
|
|
604
|
+
# count 1 → line 0 ({ is the 1st significant char, on line 0)
|
|
605
|
+
assert_equal 0, r.line_for_significant_count(formatted, 1)
|
|
606
|
+
# count 2 → line 1 (" opening quote of "a" is on line 1)
|
|
607
|
+
assert_equal 1, r.line_for_significant_count(formatted, 2)
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
def test_json_filetype_detected
|
|
611
|
+
editor = fresh_editor
|
|
612
|
+
buf = editor.current_buffer
|
|
613
|
+
buf.options["filetype"] = "json"
|
|
614
|
+
assert_equal "json", RuVim::RichView.detect_format(buf)
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
# --- Filetype detection tests ---
|
|
618
|
+
|
|
619
|
+
def test_detect_filetype_tsv
|
|
620
|
+
editor = RuVim::Editor.new
|
|
621
|
+
assert_equal "tsv", editor.detect_filetype("data.tsv")
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
def test_detect_filetype_csv
|
|
625
|
+
editor = RuVim::Editor.new
|
|
626
|
+
assert_equal "csv", editor.detect_filetype("data.csv")
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
def test_detect_filetype_tsv_uppercase
|
|
630
|
+
editor = RuVim::Editor.new
|
|
631
|
+
assert_equal "tsv", editor.detect_filetype("DATA.TSV")
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
def test_detect_filetype_jsonl
|
|
635
|
+
editor = RuVim::Editor.new
|
|
636
|
+
assert_equal "jsonl", editor.detect_filetype("data.jsonl")
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
# --- JSONL Rich View tests ---
|
|
640
|
+
|
|
641
|
+
def test_jsonl_registered
|
|
642
|
+
assert RuVim::RichView.renderer_for("jsonl")
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
def test_jsonl_open_creates_virtual_buffer
|
|
646
|
+
editor = fresh_editor
|
|
647
|
+
buf = editor.current_buffer
|
|
648
|
+
buf.replace_all_lines!(['{"a":1}', '{"b":2}'])
|
|
649
|
+
buf.options["filetype"] = "jsonl"
|
|
650
|
+
count_before = editor.buffers.length
|
|
651
|
+
|
|
652
|
+
RuVim::RichView.open!(editor, format: "jsonl")
|
|
653
|
+
assert_equal count_before + 1, editor.buffers.length
|
|
654
|
+
new_buf = editor.current_buffer
|
|
655
|
+
refute_equal buf.id, new_buf.id
|
|
656
|
+
assert_equal :jsonl_formatted, new_buf.kind
|
|
657
|
+
assert new_buf.readonly?
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
def test_jsonl_open_binds_close_keys
|
|
661
|
+
editor = fresh_editor
|
|
662
|
+
editor.keymap_manager = RuVim::KeymapManager.new
|
|
663
|
+
buf = editor.current_buffer
|
|
664
|
+
buf.replace_all_lines!(['{"a":1}', '{"b":2}'])
|
|
665
|
+
buf.options["filetype"] = "jsonl"
|
|
666
|
+
|
|
667
|
+
RuVim::RichView.open!(editor, format: "jsonl")
|
|
668
|
+
result = editor.keymap_manager.resolve_with_context(:normal, ["\e"], editor: editor)
|
|
669
|
+
assert_equal "rich.close_buffer", result.invocation.id
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
def test_jsonl_open_pretty_prints_each_line
|
|
673
|
+
editor = fresh_editor
|
|
674
|
+
buf = editor.current_buffer
|
|
675
|
+
buf.replace_all_lines!(['{"a":1,"b":[2,3]}', '{"c":4}'])
|
|
676
|
+
buf.options["filetype"] = "jsonl"
|
|
677
|
+
|
|
678
|
+
RuVim::RichView.open!(editor, format: "jsonl")
|
|
679
|
+
new_buf = editor.current_buffer
|
|
680
|
+
lines = new_buf.lines
|
|
681
|
+
# Each JSON object should be expanded; separated by "---"
|
|
682
|
+
assert lines.length > 2, "JSONL should be expanded to multiple lines"
|
|
683
|
+
assert lines.any? { |l| l.include?("---") }, "Entries should be separated"
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
def test_jsonl_open_maps_cursor_to_correct_entry
|
|
687
|
+
editor = fresh_editor
|
|
688
|
+
buf = editor.current_buffer
|
|
689
|
+
buf.replace_all_lines!(['{"a":1}', '{"b":2}', '{"c":3}'])
|
|
690
|
+
buf.options["filetype"] = "jsonl"
|
|
691
|
+
editor.current_window.cursor_y = 1 # on second entry
|
|
692
|
+
|
|
693
|
+
RuVim::RichView.open!(editor, format: "jsonl")
|
|
694
|
+
new_buf = editor.current_buffer
|
|
695
|
+
cy = editor.current_window.cursor_y
|
|
696
|
+
# Cursor should be within the second entry's formatted block
|
|
697
|
+
nearby = (cy..[cy + 2, new_buf.lines.length - 1].min).map { |r| new_buf.line_at(r) }.join("\n")
|
|
698
|
+
assert_match(/"b"/, nearby, "Cursor should be near the entry with \"b\"")
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
def test_jsonl_open_skips_blank_lines
|
|
702
|
+
editor = fresh_editor
|
|
703
|
+
buf = editor.current_buffer
|
|
704
|
+
buf.replace_all_lines!(['{"a":1}', '', '{"b":2}'])
|
|
705
|
+
buf.options["filetype"] = "jsonl"
|
|
706
|
+
|
|
707
|
+
RuVim::RichView.open!(editor, format: "jsonl")
|
|
708
|
+
new_buf = editor.current_buffer
|
|
709
|
+
lines = new_buf.lines
|
|
710
|
+
# Should contain both entries
|
|
711
|
+
assert lines.any? { |l| l.include?('"a"') }
|
|
712
|
+
assert lines.any? { |l| l.include?('"b"') }
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
def test_jsonl_open_shows_parse_error_inline
|
|
716
|
+
editor = fresh_editor
|
|
717
|
+
buf = editor.current_buffer
|
|
718
|
+
buf.replace_all_lines!(['{"a":1}', 'bad json', '{"b":2}'])
|
|
719
|
+
buf.options["filetype"] = "jsonl"
|
|
720
|
+
|
|
721
|
+
RuVim::RichView.open!(editor, format: "jsonl")
|
|
722
|
+
new_buf = editor.current_buffer
|
|
723
|
+
lines = new_buf.lines
|
|
724
|
+
# Invalid line should show an error marker
|
|
725
|
+
assert lines.any? { |l| l.include?("PARSE ERROR") }, "Invalid JSON line should show error"
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
def test_jsonl_filetype_detected
|
|
729
|
+
editor = fresh_editor
|
|
730
|
+
buf = editor.current_buffer
|
|
731
|
+
buf.options["filetype"] = "jsonl"
|
|
732
|
+
assert_equal "jsonl", RuVim::RichView.detect_format(buf)
|
|
733
|
+
end
|
|
734
|
+
end
|