ruvim 0.3.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +68 -7
  3. data/README.md +30 -7
  4. data/Rakefile +7 -0
  5. data/benchmark/cext_compare.rb +165 -0
  6. data/benchmark/chunked_load.rb +256 -0
  7. data/benchmark/file_load.rb +140 -0
  8. data/benchmark/hotspots.rb +178 -0
  9. data/docs/binding.md +18 -1
  10. data/docs/command.md +156 -10
  11. data/docs/config.md +10 -2
  12. data/docs/done.md +23 -0
  13. data/docs/spec.md +162 -25
  14. data/docs/todo.md +9 -0
  15. data/docs/tutorial.md +33 -1
  16. data/docs/vim_diff.md +31 -8
  17. data/ext/ruvim/extconf.rb +5 -0
  18. data/ext/ruvim/ruvim_ext.c +519 -0
  19. data/lib/ruvim/app.rb +246 -2525
  20. data/lib/ruvim/browser.rb +104 -0
  21. data/lib/ruvim/buffer.rb +43 -20
  22. data/lib/ruvim/cli.rb +6 -0
  23. data/lib/ruvim/command_invocation.rb +2 -2
  24. data/lib/ruvim/completion_manager.rb +708 -0
  25. data/lib/ruvim/dispatcher.rb +14 -8
  26. data/lib/ruvim/display_width.rb +91 -45
  27. data/lib/ruvim/editor.rb +74 -80
  28. data/lib/ruvim/ex_command_registry.rb +3 -1
  29. data/lib/ruvim/file_watcher.rb +243 -0
  30. data/lib/ruvim/gh/link.rb +207 -0
  31. data/lib/ruvim/git/blame.rb +255 -0
  32. data/lib/ruvim/git/branch.rb +112 -0
  33. data/lib/ruvim/git/commit.rb +102 -0
  34. data/lib/ruvim/git/diff.rb +129 -0
  35. data/lib/ruvim/git/grep.rb +107 -0
  36. data/lib/ruvim/git/handler.rb +125 -0
  37. data/lib/ruvim/git/log.rb +41 -0
  38. data/lib/ruvim/git/status.rb +103 -0
  39. data/lib/ruvim/global_commands.rb +351 -77
  40. data/lib/ruvim/highlighter.rb +4 -11
  41. data/lib/ruvim/input.rb +1 -0
  42. data/lib/ruvim/key_handler.rb +1510 -0
  43. data/lib/ruvim/keymap_manager.rb +7 -7
  44. data/lib/ruvim/lang/base.rb +5 -0
  45. data/lib/ruvim/lang/c.rb +116 -0
  46. data/lib/ruvim/lang/cpp.rb +107 -0
  47. data/lib/ruvim/lang/csv.rb +4 -1
  48. data/lib/ruvim/lang/diff.rb +43 -0
  49. data/lib/ruvim/lang/dockerfile.rb +36 -0
  50. data/lib/ruvim/lang/elixir.rb +85 -0
  51. data/lib/ruvim/lang/erb.rb +30 -0
  52. data/lib/ruvim/lang/go.rb +83 -0
  53. data/lib/ruvim/lang/html.rb +34 -0
  54. data/lib/ruvim/lang/javascript.rb +83 -0
  55. data/lib/ruvim/lang/json.rb +40 -0
  56. data/lib/ruvim/lang/lua.rb +76 -0
  57. data/lib/ruvim/lang/makefile.rb +36 -0
  58. data/lib/ruvim/lang/markdown.rb +3 -4
  59. data/lib/ruvim/lang/ocaml.rb +77 -0
  60. data/lib/ruvim/lang/perl.rb +91 -0
  61. data/lib/ruvim/lang/python.rb +85 -0
  62. data/lib/ruvim/lang/registry.rb +102 -0
  63. data/lib/ruvim/lang/ruby.rb +7 -0
  64. data/lib/ruvim/lang/rust.rb +95 -0
  65. data/lib/ruvim/lang/scheme.rb +5 -0
  66. data/lib/ruvim/lang/sh.rb +76 -0
  67. data/lib/ruvim/lang/sql.rb +52 -0
  68. data/lib/ruvim/lang/toml.rb +36 -0
  69. data/lib/ruvim/lang/tsv.rb +4 -1
  70. data/lib/ruvim/lang/typescript.rb +53 -0
  71. data/lib/ruvim/lang/yaml.rb +62 -0
  72. data/lib/ruvim/rich_view/json_renderer.rb +131 -0
  73. data/lib/ruvim/rich_view/jsonl_renderer.rb +57 -0
  74. data/lib/ruvim/rich_view/table_renderer.rb +3 -3
  75. data/lib/ruvim/rich_view.rb +30 -7
  76. data/lib/ruvim/screen.rb +135 -84
  77. data/lib/ruvim/stream/file_load.rb +85 -0
  78. data/lib/ruvim/stream/follow.rb +40 -0
  79. data/lib/ruvim/stream/git.rb +43 -0
  80. data/lib/ruvim/stream/run.rb +74 -0
  81. data/lib/ruvim/stream/stdin.rb +55 -0
  82. data/lib/ruvim/stream.rb +35 -0
  83. data/lib/ruvim/stream_mixer.rb +394 -0
  84. data/lib/ruvim/terminal.rb +18 -4
  85. data/lib/ruvim/text_metrics.rb +84 -65
  86. data/lib/ruvim/version.rb +1 -1
  87. data/lib/ruvim/window.rb +5 -5
  88. data/lib/ruvim.rb +31 -4
  89. data/test/app_command_test.rb +382 -0
  90. data/test/app_completion_test.rb +65 -16
  91. data/test/app_dot_repeat_test.rb +27 -3
  92. data/test/app_ex_command_test.rb +154 -0
  93. data/test/app_motion_test.rb +13 -12
  94. data/test/app_register_test.rb +2 -1
  95. data/test/app_scenario_test.rb +182 -8
  96. data/test/app_startup_test.rb +70 -27
  97. data/test/app_text_object_test.rb +2 -1
  98. data/test/app_unicode_behavior_test.rb +3 -2
  99. data/test/browser_test.rb +88 -0
  100. data/test/buffer_test.rb +24 -0
  101. data/test/cli_test.rb +77 -0
  102. data/test/clipboard_test.rb +67 -0
  103. data/test/command_invocation_test.rb +33 -0
  104. data/test/command_line_test.rb +118 -0
  105. data/test/config_dsl_test.rb +134 -0
  106. data/test/dispatcher_test.rb +74 -4
  107. data/test/display_width_test.rb +41 -0
  108. data/test/ex_command_registry_test.rb +106 -0
  109. data/test/file_watcher_test.rb +197 -0
  110. data/test/follow_test.rb +198 -0
  111. data/test/gh_link_test.rb +141 -0
  112. data/test/git_blame_test.rb +792 -0
  113. data/test/git_grep_test.rb +64 -0
  114. data/test/highlighter_test.rb +169 -0
  115. data/test/indent_test.rb +223 -0
  116. data/test/input_screen_integration_test.rb +1 -1
  117. data/test/keyword_chars_test.rb +85 -0
  118. data/test/lang_test.rb +634 -0
  119. data/test/markdown_renderer_test.rb +5 -5
  120. data/test/on_save_hook_test.rb +12 -8
  121. data/test/render_snapshot_test.rb +78 -0
  122. data/test/rich_view_test.rb +279 -23
  123. data/test/run_command_test.rb +307 -0
  124. data/test/screen_test.rb +68 -5
  125. data/test/search_option_test.rb +19 -0
  126. data/test/stream_test.rb +165 -0
  127. data/test/test_helper.rb +9 -0
  128. data/test/window_test.rb +59 -0
  129. metadata +68 -2
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "test_helper"
4
+
5
+ class GitGrepTest < Minitest::Test
6
+ def setup
7
+ @app = RuVim::App.new(clean: true)
8
+ @editor = @app.instance_variable_get(:@editor)
9
+ @dispatcher = @app.instance_variable_get(:@dispatcher)
10
+ @key_handler = @app.instance_variable_get(:@key_handler)
11
+ @editor.materialize_intro_buffer!
12
+ end
13
+
14
+ def feed(*keys)
15
+ keys.each { |k| @key_handler.handle(k) }
16
+ end
17
+
18
+ # --- Parsing ---
19
+
20
+ def test_parse_location_basic
21
+ line = "lib/ruvim/app.rb:42: def run"
22
+ result = RuVim::Git::Grep.parse_location(line)
23
+ assert_equal ["lib/ruvim/app.rb", 42], result
24
+ end
25
+
26
+ def test_parse_location_no_line_number
27
+ line = "lib/ruvim/app.rb: def run"
28
+ result = RuVim::Git::Grep.parse_location(line)
29
+ assert_nil result
30
+ end
31
+
32
+ def test_parse_location_empty
33
+ result = RuVim::Git::Grep.parse_location("")
34
+ assert_nil result
35
+ end
36
+
37
+ def test_parse_location_separator_line
38
+ result = RuVim::Git::Grep.parse_location("--")
39
+ assert_nil result
40
+ end
41
+
42
+ def test_parse_location_with_colon_in_content
43
+ line = "config.rb:10: url = \"http://example.com\""
44
+ result = RuVim::Git::Grep.parse_location(line)
45
+ assert_equal ["config.rb", 10], result
46
+ end
47
+
48
+ def test_parse_location_windows_style_path
49
+ line = "src/main.rb:5:hello"
50
+ result = RuVim::Git::Grep.parse_location(line)
51
+ assert_equal ["src/main.rb", 5], result
52
+ end
53
+
54
+ # --- Command registration ---
55
+
56
+ def test_git_grep_subcommand_registered
57
+ assert RuVim::Git::Handler::GIT_SUBCOMMANDS.key?("grep")
58
+ end
59
+
60
+ def test_git_grep_open_file_command_registered
61
+ cmd = RuVim::CommandRegistry.instance
62
+ assert cmd.registered?("git.grep.open_file")
63
+ end
64
+ end
@@ -14,6 +14,12 @@ class HighlighterTest < Minitest::Test
14
14
  assert_equal "\e[33m", cols[6] # number start
15
15
  end
16
16
 
17
+ def test_jsonl_highlighter_reuses_json_colors
18
+ cols = RuVim::Highlighter.color_columns("jsonl", '{"a": 10}')
19
+ assert_equal "\e[36m", cols[1] # key chars
20
+ assert_equal "\e[33m", cols[6] # number start
21
+ end
22
+
17
23
  def test_ruby_highlighter_marks_instance_variables_and_constants
18
24
  cols = RuVim::Highlighter.color_columns("ruby", "@x = Foo")
19
25
  assert_equal "\e[93m", cols[0] # @x
@@ -134,4 +140,167 @@ class HighlighterTest < Minitest::Test
134
140
  cols = RuVim::Highlighter.color_columns("scheme", "")
135
141
  assert_empty cols
136
142
  end
143
+
144
+ # --- Diff ---
145
+
146
+ def test_diff_add_line_green
147
+ cols = RuVim::Highlighter.color_columns("diff", "+added line")
148
+ refute_empty cols
149
+ assert_equal "\e[32m", cols[0]
150
+ assert_equal "\e[32m", cols[5]
151
+ end
152
+
153
+ def test_diff_delete_line_red
154
+ cols = RuVim::Highlighter.color_columns("diff", "-removed line")
155
+ refute_empty cols
156
+ assert_equal "\e[31m", cols[0]
157
+ end
158
+
159
+ def test_diff_hunk_header_cyan
160
+ cols = RuVim::Highlighter.color_columns("diff", "@@ -1,3 +1,4 @@ def foo")
161
+ refute_empty cols
162
+ assert_equal "\e[36m", cols[0]
163
+ end
164
+
165
+ def test_diff_header_bold
166
+ cols = RuVim::Highlighter.color_columns("diff", "diff --git a/foo.rb b/foo.rb")
167
+ refute_empty cols
168
+ assert_equal "\e[1m", cols[0]
169
+ end
170
+
171
+ def test_diff_context_line_no_color
172
+ cols = RuVim::Highlighter.color_columns("diff", " context line")
173
+ assert_empty cols
174
+ end
175
+
176
+ def test_diff_meta_line_yellow
177
+ cols = RuVim::Highlighter.color_columns("diff", "index abc..def 100644")
178
+ refute_empty cols
179
+ assert_equal "\e[33m", cols[0]
180
+ end
181
+
182
+ # --- C ---
183
+
184
+ def test_c_keyword_if
185
+ cols = RuVim::Highlighter.color_columns("c", "if (x > 0) {")
186
+ assert_equal "\e[36m", cols[0] # "if"
187
+ assert_equal "\e[36m", cols[1]
188
+ end
189
+
190
+ def test_c_keyword_return
191
+ cols = RuVim::Highlighter.color_columns("c", " return 0;")
192
+ assert_equal "\e[36m", cols[2] # "return"
193
+ end
194
+
195
+ def test_c_type_keyword
196
+ cols = RuVim::Highlighter.color_columns("c", "int main(void) {")
197
+ assert_equal "\e[36m", cols[0] # "int"
198
+ assert_equal "\e[36m", cols[9] # "void"
199
+ end
200
+
201
+ def test_c_string
202
+ cols = RuVim::Highlighter.color_columns("c", 'printf("hello");')
203
+ assert_equal "\e[32m", cols[7] # opening quote
204
+ assert_equal "\e[32m", cols[13] # closing quote
205
+ end
206
+
207
+ def test_c_char_literal
208
+ cols = RuVim::Highlighter.color_columns("c", "char c = 'a';")
209
+ assert_equal "\e[32m", cols[9] # opening quote
210
+ assert_equal "\e[32m", cols[11] # closing quote
211
+ end
212
+
213
+ def test_c_number_decimal
214
+ cols = RuVim::Highlighter.color_columns("c", "int x = 42;")
215
+ assert_equal "\e[33m", cols[8] # "4"
216
+ assert_equal "\e[33m", cols[9] # "2"
217
+ end
218
+
219
+ def test_c_number_hex
220
+ cols = RuVim::Highlighter.color_columns("c", "int x = 0xFF;")
221
+ assert_equal "\e[33m", cols[8] # "0"
222
+ assert_equal "\e[33m", cols[11] # "F"
223
+ end
224
+
225
+ def test_c_line_comment
226
+ cols = RuVim::Highlighter.color_columns("c", "x = 1; // comment")
227
+ assert_equal "\e[90m", cols[7] # "//"
228
+ assert_equal "\e[90m", cols[16] # end of comment
229
+ end
230
+
231
+ def test_c_block_comment_single_line
232
+ cols = RuVim::Highlighter.color_columns("c", "x = 1; /* comment */")
233
+ assert_equal "\e[90m", cols[7] # "/*"
234
+ assert_equal "\e[90m", cols[19] # "*/"
235
+ end
236
+
237
+ def test_c_preprocessor
238
+ cols = RuVim::Highlighter.color_columns("c", "#include <stdio.h>")
239
+ assert_equal "\e[35m", cols[0] # "#"
240
+ assert_equal "\e[35m", cols[7] # "e" of include
241
+ end
242
+
243
+ def test_c_define_preprocessor
244
+ cols = RuVim::Highlighter.color_columns("c", "#define MAX 100")
245
+ assert_equal "\e[35m", cols[0] # "#"
246
+ end
247
+
248
+ def test_c_constant_macro
249
+ cols = RuVim::Highlighter.color_columns("c", "if (ptr == NULL) {")
250
+ assert_equal "\e[96m", cols[11] # "N" of NULL
251
+ end
252
+
253
+ def test_c_all_caps_identifier
254
+ cols = RuVim::Highlighter.color_columns("c", "x = MAX_SIZE;")
255
+ assert_equal "\e[96m", cols[4] # "M"
256
+ end
257
+
258
+ def test_c_empty_line
259
+ cols = RuVim::Highlighter.color_columns("c", "")
260
+ assert_empty cols
261
+ end
262
+
263
+ # --- C++ ---
264
+
265
+ def test_cpp_keyword_class
266
+ cols = RuVim::Highlighter.color_columns("cpp", "class Foo {")
267
+ assert_equal "\e[36m", cols[0] # "class"
268
+ assert_equal "\e[36m", cols[4]
269
+ end
270
+
271
+ def test_cpp_keyword_namespace
272
+ cols = RuVim::Highlighter.color_columns("cpp", "namespace std {")
273
+ assert_equal "\e[36m", cols[0] # "namespace"
274
+ end
275
+
276
+ def test_cpp_keyword_template
277
+ cols = RuVim::Highlighter.color_columns("cpp", "template <typename T>")
278
+ assert_equal "\e[36m", cols[0] # "template"
279
+ assert_equal "\e[36m", cols[10] # "typename"
280
+ end
281
+
282
+ def test_cpp_keyword_nullptr
283
+ cols = RuVim::Highlighter.color_columns("cpp", "int* p = nullptr;")
284
+ assert_equal "\e[36m", cols[9] # "nullptr"
285
+ end
286
+
287
+ def test_cpp_keyword_auto
288
+ cols = RuVim::Highlighter.color_columns("cpp", "auto x = 42;")
289
+ assert_equal "\e[36m", cols[0] # "auto"
290
+ end
291
+
292
+ def test_cpp_inherits_c_string
293
+ cols = RuVim::Highlighter.color_columns("cpp", 'std::cout << "hello";')
294
+ assert_equal "\e[32m", cols[13] # opening quote
295
+ end
296
+
297
+ def test_cpp_inherits_c_comment
298
+ cols = RuVim::Highlighter.color_columns("cpp", "x = 1; // comment")
299
+ assert_equal "\e[90m", cols[7] # "//"
300
+ end
301
+
302
+ def test_cpp_empty_line
303
+ cols = RuVim::Highlighter.color_columns("cpp", "")
304
+ assert_empty cols
305
+ end
137
306
  end
data/test/indent_test.rb CHANGED
@@ -1,5 +1,91 @@
1
1
  require_relative "test_helper"
2
2
 
3
+ class JsonIndentTest < Minitest::Test
4
+ def calc(lines, target_row, sw = 2)
5
+ RuVim::Lang::Json.calculate_indent(lines, target_row, sw)
6
+ end
7
+
8
+ def test_first_line_is_zero
9
+ assert_equal 0, calc(["{"], 0)
10
+ end
11
+
12
+ def test_after_open_brace
13
+ lines = ["{", ' "key": "value"']
14
+ assert_equal 2, calc(lines, 1)
15
+ end
16
+
17
+ def test_close_brace
18
+ lines = ["{", ' "key": "value"', "}"]
19
+ assert_equal 0, calc(lines, 2)
20
+ end
21
+
22
+ def test_after_open_bracket
23
+ lines = ["[", " 1"]
24
+ assert_equal 2, calc(lines, 1)
25
+ end
26
+
27
+ def test_close_bracket
28
+ lines = ["[", " 1,", " 2", "]"]
29
+ assert_equal 0, calc(lines, 3)
30
+ end
31
+
32
+ def test_nested_objects
33
+ lines = [
34
+ "{",
35
+ ' "a": {',
36
+ ' "b": 1',
37
+ " }",
38
+ "}"
39
+ ]
40
+ assert_equal 2, calc(lines, 1)
41
+ assert_equal 4, calc(lines, 2)
42
+ assert_equal 2, calc(lines, 3)
43
+ assert_equal 0, calc(lines, 4)
44
+ end
45
+
46
+ def test_array_in_object
47
+ lines = [
48
+ "{",
49
+ ' "items": [',
50
+ " 1,",
51
+ " 2",
52
+ " ]",
53
+ "}"
54
+ ]
55
+ assert_equal 2, calc(lines, 1)
56
+ assert_equal 4, calc(lines, 2)
57
+ assert_equal 4, calc(lines, 3)
58
+ assert_equal 2, calc(lines, 4)
59
+ assert_equal 0, calc(lines, 5)
60
+ end
61
+
62
+ def test_shiftwidth_4
63
+ lines = ["{", ' "key": 1', "}"]
64
+ assert_equal 4, calc(lines, 1, 4)
65
+ assert_equal 0, calc(lines, 2, 4)
66
+ end
67
+
68
+ def test_indent_trigger_open_brace
69
+ assert RuVim::Lang::Json.indent_trigger?("{")
70
+ assert RuVim::Lang::Json.indent_trigger?(' "key": {')
71
+ assert RuVim::Lang::Json.indent_trigger?(' "key": [')
72
+ end
73
+
74
+ def test_indent_trigger_no_trigger
75
+ refute RuVim::Lang::Json.indent_trigger?(' "key": "value"')
76
+ refute RuVim::Lang::Json.indent_trigger?("}")
77
+ end
78
+
79
+ def test_dedent_trigger_close_brace
80
+ assert_kind_of Regexp, RuVim::Lang::Json.dedent_trigger("}")
81
+ assert_kind_of Regexp, RuVim::Lang::Json.dedent_trigger("]")
82
+ end
83
+
84
+ def test_dedent_trigger_no_trigger
85
+ assert_nil RuVim::Lang::Json.dedent_trigger("a")
86
+ end
87
+ end
88
+
3
89
  class RubyIndentTest < Minitest::Test
4
90
  def calc(lines, target_row, sw = 2)
5
91
  RuVim::Lang::Ruby.calculate_indent(lines, target_row, sw)
@@ -199,3 +285,140 @@ class RubyIndentTest < Minitest::Test
199
285
  assert_equal 0, calc(lines, 3) # end
200
286
  end
201
287
  end
288
+
289
+ class CIndentTest < Minitest::Test
290
+ def calc(lines, target_row, sw = 2)
291
+ RuVim::Lang::C.calculate_indent(lines, target_row, sw)
292
+ end
293
+
294
+ def test_first_line_is_zero
295
+ assert_equal 0, calc(["int main() {"], 0)
296
+ end
297
+
298
+ def test_after_open_brace
299
+ lines = ["int main() {", " return 0;"]
300
+ assert_equal 2, calc(lines, 1)
301
+ end
302
+
303
+ def test_close_brace
304
+ lines = ["int main() {", " return 0;", "}"]
305
+ assert_equal 0, calc(lines, 2)
306
+ end
307
+
308
+ def test_nested_braces
309
+ lines = [
310
+ "void foo() {",
311
+ " if (x) {",
312
+ " bar();",
313
+ " }",
314
+ "}"
315
+ ]
316
+ assert_equal 2, calc(lines, 1)
317
+ assert_equal 4, calc(lines, 2)
318
+ assert_equal 2, calc(lines, 3)
319
+ assert_equal 0, calc(lines, 4)
320
+ end
321
+
322
+ def test_switch_case
323
+ lines = [
324
+ "switch (x) {",
325
+ "case 1:",
326
+ " foo();",
327
+ " break;",
328
+ "case 2:",
329
+ " bar();",
330
+ " break;",
331
+ "default:",
332
+ " baz();",
333
+ "}"
334
+ ]
335
+ assert_equal 0, calc(lines, 1) # case 1:
336
+ assert_equal 2, calc(lines, 2) # foo()
337
+ assert_equal 2, calc(lines, 3) # break
338
+ assert_equal 0, calc(lines, 4) # case 2:
339
+ assert_equal 2, calc(lines, 5) # bar()
340
+ assert_equal 0, calc(lines, 7) # default:
341
+ assert_equal 2, calc(lines, 8) # baz()
342
+ assert_equal 0, calc(lines, 9) # }
343
+ end
344
+
345
+ def test_shiftwidth_4
346
+ lines = ["void foo() {", " bar();", "}"]
347
+ assert_equal 4, calc(lines, 1, 4)
348
+ assert_equal 0, calc(lines, 2, 4)
349
+ end
350
+
351
+ def test_indent_trigger_open_brace
352
+ assert RuVim::Lang::C.indent_trigger?("int main() {")
353
+ assert RuVim::Lang::C.indent_trigger?("if (x) {")
354
+ end
355
+
356
+ def test_indent_trigger_no_trigger
357
+ refute RuVim::Lang::C.indent_trigger?("return 0;")
358
+ refute RuVim::Lang::C.indent_trigger?("}")
359
+ end
360
+
361
+ def test_dedent_trigger_close_brace
362
+ assert_kind_of Regexp, RuVim::Lang::C.dedent_trigger("}")
363
+ end
364
+
365
+ def test_dedent_trigger_no_trigger
366
+ assert_nil RuVim::Lang::C.dedent_trigger("a")
367
+ end
368
+ end
369
+
370
+ class CppIndentTest < Minitest::Test
371
+ def calc(lines, target_row, sw = 2)
372
+ RuVim::Lang::Cpp.calculate_indent(lines, target_row, sw)
373
+ end
374
+
375
+ def test_class_body
376
+ lines = [
377
+ "class Foo {",
378
+ " int x;",
379
+ "};"
380
+ ]
381
+ assert_equal 2, calc(lines, 1)
382
+ assert_equal 0, calc(lines, 2)
383
+ end
384
+
385
+ def test_namespace_body
386
+ lines = [
387
+ "namespace ns {",
388
+ " class Bar {",
389
+ " void f();",
390
+ " };",
391
+ "}"
392
+ ]
393
+ assert_equal 2, calc(lines, 1)
394
+ assert_equal 4, calc(lines, 2)
395
+ assert_equal 2, calc(lines, 3)
396
+ assert_equal 0, calc(lines, 4)
397
+ end
398
+
399
+ def test_access_specifier_dedent
400
+ lines = [
401
+ "class Foo {",
402
+ "public:",
403
+ " int x;",
404
+ "private:",
405
+ " int y;",
406
+ "};"
407
+ ]
408
+ assert_equal 0, calc(lines, 1) # public:
409
+ assert_equal 2, calc(lines, 2) # int x
410
+ assert_equal 0, calc(lines, 3) # private:
411
+ assert_equal 2, calc(lines, 4) # int y
412
+ assert_equal 0, calc(lines, 5) # };
413
+ end
414
+
415
+ def test_indent_trigger
416
+ assert RuVim::Lang::Cpp.indent_trigger?("class Foo {")
417
+ assert RuVim::Lang::Cpp.indent_trigger?("namespace ns {")
418
+ end
419
+
420
+ def test_dedent_trigger
421
+ assert_kind_of Regexp, RuVim::Lang::Cpp.dedent_trigger("}")
422
+ assert_nil RuVim::Lang::Cpp.dedent_trigger("x")
423
+ end
424
+ end
@@ -62,7 +62,7 @@ class InputScreenIntegrationTest < Minitest::Test
62
62
  key = input.read_key(timeout: 0.2)
63
63
  assert_equal :pagedown, key
64
64
 
65
- app.send(:handle_normal_key, key)
65
+ app.instance_variable_get(:@key_handler).send(:handle_normal_key, key)
66
66
  screen.render(editor)
67
67
 
68
68
  assert_operator editor.current_window.cursor_y, :>, 0
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "test_helper"
4
+
5
+ class KeywordCharsTest < Minitest::Test
6
+ def setup
7
+ # Clear caches between tests
8
+ RuVim::KeywordChars.instance_variable_set(:@char_class_cache, nil)
9
+ RuVim::KeywordChars.instance_variable_set(:@regex_cache, nil)
10
+ end
11
+
12
+ # --- char_class ---
13
+
14
+ def test_char_class_default_for_empty
15
+ assert_equal "[:alnum:]_", RuVim::KeywordChars.char_class("")
16
+ end
17
+
18
+ def test_char_class_default_for_nil
19
+ assert_equal "[:alnum:]_", RuVim::KeywordChars.char_class(nil)
20
+ end
21
+
22
+ def test_char_class_single_char
23
+ result = RuVim::KeywordChars.char_class("-")
24
+ assert_includes result, "[:alnum:]_"
25
+ assert_includes result, "\\-"
26
+ end
27
+
28
+ def test_char_class_range
29
+ result = RuVim::KeywordChars.char_class("65-90")
30
+ assert_includes result, "[:alnum:]_"
31
+ # Should include A-Z range
32
+ assert_includes result, "A-Z"
33
+ end
34
+
35
+ def test_char_class_reversed_range
36
+ # minmax should handle reversed ranges
37
+ result = RuVim::KeywordChars.char_class("90-65")
38
+ assert_includes result, "A-Z"
39
+ end
40
+
41
+ def test_char_class_skips_at_sign
42
+ result = RuVim::KeywordChars.char_class("@")
43
+ assert_equal "[:alnum:]_", result
44
+ end
45
+
46
+ def test_char_class_skips_out_of_range
47
+ result = RuVim::KeywordChars.char_class("256-300")
48
+ assert_equal "[:alnum:]_", result
49
+ end
50
+
51
+ def test_char_class_mixed_spec
52
+ result = RuVim::KeywordChars.char_class("-,65-90")
53
+ assert_includes result, "\\-"
54
+ assert_includes result, "A-Z"
55
+ end
56
+
57
+ def test_char_class_caches_result
58
+ result1 = RuVim::KeywordChars.char_class("-")
59
+ result2 = RuVim::KeywordChars.char_class("-")
60
+ assert_same result1, result2
61
+ end
62
+
63
+ # --- regex ---
64
+
65
+ def test_regex_default_for_empty
66
+ assert_equal RuVim::KeywordChars::DEFAULT_REGEX, RuVim::KeywordChars.regex("")
67
+ end
68
+
69
+ def test_regex_default_for_nil
70
+ assert_equal RuVim::KeywordChars::DEFAULT_REGEX, RuVim::KeywordChars.regex(nil)
71
+ end
72
+
73
+ def test_regex_matches_with_spec
74
+ re = RuVim::KeywordChars.regex("-")
75
+ assert_match re, "a"
76
+ assert_match re, "-"
77
+ refute_match re, " "
78
+ end
79
+
80
+ def test_regex_caches_result
81
+ result1 = RuVim::KeywordChars.regex("-")
82
+ result2 = RuVim::KeywordChars.regex("-")
83
+ assert_same result1, result2
84
+ end
85
+ end