ruvim 0.1.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 +29 -0
- data/docs/command.md +101 -0
- data/docs/config.md +203 -84
- data/docs/done.md +21 -0
- data/docs/lib_cleanup_report.md +79 -0
- data/docs/plugin.md +13 -15
- data/docs/spec.md +195 -33
- data/docs/todo.md +183 -10
- data/docs/tutorial.md +1 -1
- data/docs/vim_diff.md +94 -171
- data/lib/ruvim/app.rb +1543 -172
- 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 +21 -5
- data/lib/ruvim/context.rb +2 -7
- data/lib/ruvim/dispatcher.rb +153 -13
- data/lib/ruvim/display_width.rb +28 -2
- data/lib/ruvim/editor.rb +622 -69
- data/lib/ruvim/ex_command_registry.rb +2 -0
- data/lib/ruvim/global_commands.rb +1386 -114
- data/lib/ruvim/highlighter.rb +16 -21
- data/lib/ruvim/input.rb +52 -29
- data/lib/ruvim/keymap_manager.rb +83 -0
- data/lib/ruvim/keyword_chars.rb +48 -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 +851 -119
- data/lib/ruvim/terminal.rb +18 -1
- data/lib/ruvim/text_metrics.rb +28 -0
- data/lib/ruvim/version.rb +2 -2
- data/lib/ruvim/window.rb +37 -10
- data/lib/ruvim.rb +15 -0
- data/test/app_completion_test.rb +174 -0
- data/test/app_dot_repeat_test.rb +13 -0
- data/test/app_motion_test.rb +110 -2
- data/test/app_scenario_test.rb +998 -0
- data/test/app_startup_test.rb +197 -0
- data/test/arglist_test.rb +113 -0
- data/test/buffer_test.rb +49 -30
- data/test/config_loader_test.rb +37 -0
- data/test/dispatcher_test.rb +438 -0
- data/test/display_width_test.rb +18 -0
- data/test/editor_register_test.rb +23 -0
- data/test/fixtures/render_basic_snapshot.txt +7 -8
- data/test/fixtures/render_basic_snapshot_nonumber.txt +1 -2
- data/test/fixtures/render_unicode_scrolled_snapshot.txt +6 -7
- data/test/highlighter_test.rb +121 -0
- data/test/indent_test.rb +201 -0
- data/test/input_screen_integration_test.rb +65 -14
- 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 +470 -0
- data/test/window_test.rb +26 -0
- metadata +37 -2
data/test/indent_test.rb
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
require_relative "test_helper"
|
|
2
|
+
|
|
3
|
+
class RubyIndentTest < Minitest::Test
|
|
4
|
+
def calc(lines, target_row, sw = 2)
|
|
5
|
+
RuVim::Lang::Ruby.calculate_indent(lines, target_row, sw)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def test_first_line_is_zero
|
|
9
|
+
assert_equal 0, calc(["hello"], 0)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_after_def
|
|
13
|
+
lines = ["def foo", " bar"]
|
|
14
|
+
assert_equal 2, calc(lines, 1)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_end_returns_to_zero
|
|
18
|
+
lines = ["def foo", " bar", "end"]
|
|
19
|
+
assert_equal 0, calc(lines, 2)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_class_def_end_nesting
|
|
23
|
+
lines = [
|
|
24
|
+
"class Foo",
|
|
25
|
+
" def bar",
|
|
26
|
+
" baz",
|
|
27
|
+
" end",
|
|
28
|
+
"end"
|
|
29
|
+
]
|
|
30
|
+
assert_equal 2, calc(lines, 1) # def bar
|
|
31
|
+
assert_equal 4, calc(lines, 2) # baz
|
|
32
|
+
assert_equal 2, calc(lines, 3) # end (inner)
|
|
33
|
+
assert_equal 0, calc(lines, 4) # end (outer)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_if_else_end
|
|
37
|
+
lines = [
|
|
38
|
+
"if cond",
|
|
39
|
+
" a",
|
|
40
|
+
"else",
|
|
41
|
+
" b",
|
|
42
|
+
"end"
|
|
43
|
+
]
|
|
44
|
+
assert_equal 2, calc(lines, 1) # a
|
|
45
|
+
assert_equal 0, calc(lines, 2) # else
|
|
46
|
+
assert_equal 2, calc(lines, 3) # b
|
|
47
|
+
assert_equal 0, calc(lines, 4) # end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_if_elsif_else_end
|
|
51
|
+
lines = [
|
|
52
|
+
"if a",
|
|
53
|
+
" x",
|
|
54
|
+
"elsif b",
|
|
55
|
+
" y",
|
|
56
|
+
"else",
|
|
57
|
+
" z",
|
|
58
|
+
"end"
|
|
59
|
+
]
|
|
60
|
+
assert_equal 2, calc(lines, 1) # x
|
|
61
|
+
assert_equal 0, calc(lines, 2) # elsif
|
|
62
|
+
assert_equal 2, calc(lines, 3) # y
|
|
63
|
+
assert_equal 0, calc(lines, 4) # else
|
|
64
|
+
assert_equal 2, calc(lines, 5) # z
|
|
65
|
+
assert_equal 0, calc(lines, 6) # end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def test_modifier_if_does_not_increase_indent
|
|
69
|
+
lines = [
|
|
70
|
+
"def foo",
|
|
71
|
+
" return if true",
|
|
72
|
+
" bar",
|
|
73
|
+
"end"
|
|
74
|
+
]
|
|
75
|
+
assert_equal 2, calc(lines, 1) # return if true
|
|
76
|
+
assert_equal 2, calc(lines, 2) # bar
|
|
77
|
+
assert_equal 0, calc(lines, 3) # end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_do_end_block
|
|
81
|
+
lines = [
|
|
82
|
+
"items.each do |x|",
|
|
83
|
+
" puts x",
|
|
84
|
+
"end"
|
|
85
|
+
]
|
|
86
|
+
assert_equal 2, calc(lines, 1) # puts x
|
|
87
|
+
assert_equal 0, calc(lines, 2) # end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def test_brace_block
|
|
91
|
+
lines = [
|
|
92
|
+
"items.map {",
|
|
93
|
+
" |x| x + 1",
|
|
94
|
+
"}"
|
|
95
|
+
]
|
|
96
|
+
assert_equal 2, calc(lines, 1)
|
|
97
|
+
assert_equal 0, calc(lines, 2)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_case_when
|
|
101
|
+
lines = [
|
|
102
|
+
"case x",
|
|
103
|
+
"when 1",
|
|
104
|
+
" a",
|
|
105
|
+
"when 2",
|
|
106
|
+
" b",
|
|
107
|
+
"end"
|
|
108
|
+
]
|
|
109
|
+
assert_equal 0, calc(lines, 1) # when 1
|
|
110
|
+
assert_equal 2, calc(lines, 2) # a
|
|
111
|
+
assert_equal 0, calc(lines, 3) # when 2
|
|
112
|
+
assert_equal 2, calc(lines, 4) # b
|
|
113
|
+
assert_equal 0, calc(lines, 5) # end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def test_rescue_ensure
|
|
117
|
+
lines = [
|
|
118
|
+
"begin",
|
|
119
|
+
" risky",
|
|
120
|
+
"rescue => e",
|
|
121
|
+
" handle",
|
|
122
|
+
"ensure",
|
|
123
|
+
" cleanup",
|
|
124
|
+
"end"
|
|
125
|
+
]
|
|
126
|
+
assert_equal 2, calc(lines, 1) # risky
|
|
127
|
+
assert_equal 0, calc(lines, 2) # rescue
|
|
128
|
+
assert_equal 2, calc(lines, 3) # handle
|
|
129
|
+
assert_equal 0, calc(lines, 4) # ensure
|
|
130
|
+
assert_equal 2, calc(lines, 5) # cleanup
|
|
131
|
+
assert_equal 0, calc(lines, 6) # end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def test_unless_until_while_for
|
|
135
|
+
%w[unless until while for].each do |kw|
|
|
136
|
+
lines = ["#{kw} cond", " body", "end"]
|
|
137
|
+
assert_equal 2, calc(lines, 1), "body after #{kw}"
|
|
138
|
+
assert_equal 0, calc(lines, 2), "end after #{kw}"
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def test_module_nesting
|
|
143
|
+
lines = [
|
|
144
|
+
"module A",
|
|
145
|
+
" module B",
|
|
146
|
+
" def foo",
|
|
147
|
+
" bar",
|
|
148
|
+
" end",
|
|
149
|
+
" end",
|
|
150
|
+
"end"
|
|
151
|
+
]
|
|
152
|
+
assert_equal 2, calc(lines, 1) # module B
|
|
153
|
+
assert_equal 4, calc(lines, 2) # def foo
|
|
154
|
+
assert_equal 6, calc(lines, 3) # bar
|
|
155
|
+
assert_equal 4, calc(lines, 4) # end (inner)
|
|
156
|
+
assert_equal 2, calc(lines, 5) # end (mid)
|
|
157
|
+
assert_equal 0, calc(lines, 6) # end (outer)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def test_comment_lines_are_skipped
|
|
161
|
+
lines = [
|
|
162
|
+
"def foo",
|
|
163
|
+
" # comment",
|
|
164
|
+
" bar",
|
|
165
|
+
"end"
|
|
166
|
+
]
|
|
167
|
+
assert_equal 2, calc(lines, 1) # comment
|
|
168
|
+
assert_equal 2, calc(lines, 2) # bar
|
|
169
|
+
assert_equal 0, calc(lines, 3) # end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def test_shiftwidth_4
|
|
173
|
+
lines = ["def foo", " bar", "end"]
|
|
174
|
+
assert_equal 4, calc(lines, 1, 4)
|
|
175
|
+
assert_equal 0, calc(lines, 2, 4)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def test_paren_bracket_nesting
|
|
179
|
+
lines = [
|
|
180
|
+
"x = [",
|
|
181
|
+
" 1,",
|
|
182
|
+
" 2",
|
|
183
|
+
"]"
|
|
184
|
+
]
|
|
185
|
+
assert_equal 2, calc(lines, 1)
|
|
186
|
+
assert_equal 2, calc(lines, 2)
|
|
187
|
+
assert_equal 0, calc(lines, 3)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def test_empty_lines_preserve_depth
|
|
191
|
+
lines = [
|
|
192
|
+
"def foo",
|
|
193
|
+
"",
|
|
194
|
+
" bar",
|
|
195
|
+
"end"
|
|
196
|
+
]
|
|
197
|
+
assert_equal 2, calc(lines, 1) # empty line inside def
|
|
198
|
+
assert_equal 2, calc(lines, 2) # bar
|
|
199
|
+
assert_equal 0, calc(lines, 3) # end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
@@ -30,6 +30,21 @@ class InputScreenIntegrationTest < Minitest::Test
|
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
def with_fake_select
|
|
34
|
+
io_sc = IO.singleton_class
|
|
35
|
+
verbose, $VERBOSE = $VERBOSE, nil
|
|
36
|
+
io_sc.alias_method(:__orig_select_for_input_screen_test, :select)
|
|
37
|
+
io_sc.define_method(:select) do |readers, *_rest|
|
|
38
|
+
ready = Array(readers).select { |io| io.respond_to?(:ready?) && io.ready? }
|
|
39
|
+
ready.empty? ? nil : [ready, [], []]
|
|
40
|
+
end
|
|
41
|
+
yield
|
|
42
|
+
ensure
|
|
43
|
+
io_sc.alias_method(:select, :__orig_select_for_input_screen_test)
|
|
44
|
+
io_sc.remove_method(:__orig_select_for_input_screen_test) rescue nil
|
|
45
|
+
$VERBOSE = verbose
|
|
46
|
+
end
|
|
47
|
+
|
|
33
48
|
def test_input_pagedown_to_app_and_screen_render
|
|
34
49
|
app = RuVim::App.new(clean: true)
|
|
35
50
|
editor = app.instance_variable_get(:@editor)
|
|
@@ -41,17 +56,9 @@ class InputScreenIntegrationTest < Minitest::Test
|
|
|
41
56
|
app.instance_variable_set(:@screen, screen)
|
|
42
57
|
|
|
43
58
|
stdin = FakeTTY.new("\e[6~")
|
|
44
|
-
input = RuVim::Input.new(stdin
|
|
45
|
-
|
|
46
|
-
io_sc = IO.singleton_class
|
|
47
|
-
verbose, $VERBOSE = $VERBOSE, nil
|
|
48
|
-
io_sc.alias_method(:__orig_select_for_input_screen_test, :select)
|
|
49
|
-
io_sc.define_method(:select) do |readers, *_rest|
|
|
50
|
-
ready = Array(readers).select { |io| io.respond_to?(:ready?) && io.ready? }
|
|
51
|
-
ready.empty? ? nil : [ready, [], []]
|
|
52
|
-
end
|
|
59
|
+
input = RuVim::Input.new(stdin)
|
|
53
60
|
|
|
54
|
-
|
|
61
|
+
with_fake_select do
|
|
55
62
|
key = input.read_key(timeout: 0.2)
|
|
56
63
|
assert_equal :pagedown, key
|
|
57
64
|
|
|
@@ -60,10 +67,54 @@ class InputScreenIntegrationTest < Minitest::Test
|
|
|
60
67
|
|
|
61
68
|
assert_operator editor.current_window.cursor_y, :>, 0
|
|
62
69
|
assert_includes term.writes.last, "line "
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_input_keeps_repeated_arrow_sequences_separate
|
|
74
|
+
stdin = FakeTTY.new("\e[A\e[A")
|
|
75
|
+
input = RuVim::Input.new(stdin)
|
|
76
|
+
|
|
77
|
+
with_fake_select do
|
|
78
|
+
assert_equal :up, input.read_key(timeout: 0.2)
|
|
79
|
+
assert_equal :up, input.read_key(timeout: 0.2)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def test_input_reads_ctrl_z
|
|
84
|
+
stdin = FakeTTY.new("\u001a")
|
|
85
|
+
input = RuVim::Input.new(stdin)
|
|
86
|
+
|
|
87
|
+
with_fake_select do
|
|
88
|
+
assert_equal :ctrl_z, input.read_key(timeout: 0.2)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_input_shift_arrow_sequences
|
|
93
|
+
{
|
|
94
|
+
"\e[1;2A" => :shift_up,
|
|
95
|
+
"\e[1;2B" => :shift_down,
|
|
96
|
+
"\e[1;2C" => :shift_right,
|
|
97
|
+
"\e[1;2D" => :shift_left
|
|
98
|
+
}.each do |seq, expected|
|
|
99
|
+
stdin = FakeTTY.new(seq)
|
|
100
|
+
input = RuVim::Input.new(stdin)
|
|
101
|
+
|
|
102
|
+
with_fake_select do
|
|
103
|
+
assert_equal expected, input.read_key(timeout: 0.2), "Expected #{expected} for sequence #{seq.inspect}"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def test_has_pending_input_returns_true_when_data_available
|
|
109
|
+
stdin = FakeTTY.new("abc")
|
|
110
|
+
input = RuVim::Input.new(stdin)
|
|
111
|
+
|
|
112
|
+
with_fake_select do
|
|
113
|
+
assert input.has_pending_input?
|
|
114
|
+
input.read_key(timeout: 0)
|
|
115
|
+
input.read_key(timeout: 0)
|
|
116
|
+
input.read_key(timeout: 0)
|
|
117
|
+
refute input.has_pending_input?
|
|
67
118
|
end
|
|
68
119
|
end
|
|
69
120
|
end
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
require_relative "test_helper"
|
|
2
|
+
|
|
3
|
+
class MarkdownRendererTest < Minitest::Test
|
|
4
|
+
def renderer
|
|
5
|
+
RuVim::RichView::MarkdownRenderer
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# --- Registration ---
|
|
9
|
+
|
|
10
|
+
def test_renderer_registered_for_markdown
|
|
11
|
+
assert_equal renderer, RuVim::RichView.renderer_for("markdown")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_delimiter_for_markdown
|
|
15
|
+
assert_nil renderer.delimiter_for("markdown")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# --- Headings ---
|
|
19
|
+
|
|
20
|
+
def test_heading_h1_bold
|
|
21
|
+
lines = ["# Hello"]
|
|
22
|
+
result = renderer.render_visible(lines, delimiter: nil)
|
|
23
|
+
assert_equal 1, result.length
|
|
24
|
+
assert_match(/\e\[1[;m]/, result[0]) # bold (standalone or combined)
|
|
25
|
+
assert_includes result[0], "# Hello"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_heading_h2
|
|
29
|
+
lines = ["## Section"]
|
|
30
|
+
result = renderer.render_visible(lines, delimiter: nil)
|
|
31
|
+
assert_includes result[0], "## Section"
|
|
32
|
+
assert_match(/\e\[1[;m]/, result[0]) # bold
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_heading_h3_to_h6
|
|
36
|
+
(3..6).each do |level|
|
|
37
|
+
hashes = "#" * level
|
|
38
|
+
lines = ["#{hashes} Title"]
|
|
39
|
+
result = renderer.render_visible(lines, delimiter: nil)
|
|
40
|
+
assert_includes result[0], "#{hashes} Title", "H#{level} text should be preserved"
|
|
41
|
+
assert_includes result[0], "\e[", "H#{level} should have ANSI styling"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# --- Inline elements ---
|
|
46
|
+
|
|
47
|
+
def test_inline_bold
|
|
48
|
+
lines = ["hello **bold** world"]
|
|
49
|
+
result = renderer.render_visible(lines, delimiter: nil)
|
|
50
|
+
assert_includes result[0], "\e[1m" # bold on
|
|
51
|
+
assert_includes result[0], "\e[22m" # bold off
|
|
52
|
+
assert_includes result[0], "**bold**"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def test_inline_italic
|
|
56
|
+
lines = ["hello *italic* world"]
|
|
57
|
+
result = renderer.render_visible(lines, delimiter: nil)
|
|
58
|
+
assert_includes result[0], "\e[3m" # italic on
|
|
59
|
+
assert_includes result[0], "\e[23m" # italic off
|
|
60
|
+
assert_includes result[0], "*italic*"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_inline_code
|
|
64
|
+
lines = ["use `foo()` here"]
|
|
65
|
+
result = renderer.render_visible(lines, delimiter: nil)
|
|
66
|
+
assert_includes result[0], "\e[33m" # yellow
|
|
67
|
+
assert_includes result[0], "`foo()`"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_inline_link
|
|
71
|
+
lines = ["click [here](http://example.com) now"]
|
|
72
|
+
result = renderer.render_visible(lines, delimiter: nil)
|
|
73
|
+
assert_includes result[0], "\e[4m" # underline for text
|
|
74
|
+
assert_includes result[0], "here"
|
|
75
|
+
assert_includes result[0], "http://example.com"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_checkbox_unchecked
|
|
79
|
+
lines = ["- [ ] todo item"]
|
|
80
|
+
result = renderer.render_visible(lines, delimiter: nil)
|
|
81
|
+
assert_includes result[0], "\e[90m" # dim
|
|
82
|
+
assert_includes result[0], "[ ]"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def test_checkbox_checked
|
|
86
|
+
lines = ["- [x] done item"]
|
|
87
|
+
result = renderer.render_visible(lines, delimiter: nil)
|
|
88
|
+
assert_includes result[0], "\e[32m" # green
|
|
89
|
+
assert_includes result[0], "[x]"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# --- Code blocks ---
|
|
93
|
+
|
|
94
|
+
def test_code_fence_styling
|
|
95
|
+
lines = ["```ruby", "puts 'hi'", "```"]
|
|
96
|
+
result = renderer.render_visible(lines, delimiter: nil)
|
|
97
|
+
# Fence lines should be dim
|
|
98
|
+
assert_includes result[0], "\e[90m"
|
|
99
|
+
# Content should be warm-colored
|
|
100
|
+
assert_includes result[1], "\e[38;5;223m"
|
|
101
|
+
# Closing fence should be dim
|
|
102
|
+
assert_includes result[2], "\e[90m"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def test_code_fence_tilde
|
|
106
|
+
lines = ["~~~", "code line", "~~~"]
|
|
107
|
+
result = renderer.render_visible(lines, delimiter: nil)
|
|
108
|
+
assert_includes result[0], "\e[90m"
|
|
109
|
+
assert_includes result[1], "\e[38;5;223m"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def test_code_block_context_from_pre_context
|
|
113
|
+
# Simulate rendering in the middle of a code block
|
|
114
|
+
# pre_context_lines should carry the open fence state
|
|
115
|
+
pre_context = ["```ruby", "line1"]
|
|
116
|
+
lines = ["line2", "```"]
|
|
117
|
+
result = renderer.render_visible(lines, delimiter: nil, context: { pre_context_lines: pre_context })
|
|
118
|
+
# line2 should be inside code block (warm color)
|
|
119
|
+
assert_includes result[0], "\e[38;5;223m"
|
|
120
|
+
# closing fence
|
|
121
|
+
assert_includes result[1], "\e[90m"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_needs_pre_context
|
|
125
|
+
assert renderer.needs_pre_context?
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# --- HR ---
|
|
129
|
+
|
|
130
|
+
def test_hr_dashes
|
|
131
|
+
lines = ["---"]
|
|
132
|
+
result = renderer.render_visible(lines, delimiter: nil)
|
|
133
|
+
assert_includes result[0], "\u2500" # box drawing horizontal
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def test_hr_asterisks
|
|
137
|
+
lines = ["***"]
|
|
138
|
+
result = renderer.render_visible(lines, delimiter: nil)
|
|
139
|
+
assert_includes result[0], "\u2500"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def test_hr_underscores
|
|
143
|
+
lines = ["___"]
|
|
144
|
+
result = renderer.render_visible(lines, delimiter: nil)
|
|
145
|
+
assert_includes result[0], "\u2500"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# --- Block quotes ---
|
|
149
|
+
|
|
150
|
+
def test_block_quote
|
|
151
|
+
lines = ["> quoted text"]
|
|
152
|
+
result = renderer.render_visible(lines, delimiter: nil)
|
|
153
|
+
assert_includes result[0], "\e[36m" # cyan
|
|
154
|
+
assert_includes result[0], "> quoted text"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# --- Tables ---
|
|
158
|
+
|
|
159
|
+
def test_table_basic
|
|
160
|
+
lines = [
|
|
161
|
+
"| Name | Age |",
|
|
162
|
+
"| ----- | --- |",
|
|
163
|
+
"| Alice | 30 |"
|
|
164
|
+
]
|
|
165
|
+
result = renderer.render_visible(lines, delimiter: nil)
|
|
166
|
+
assert_equal 3, result.length
|
|
167
|
+
# Data rows should use box-drawing vertical bar
|
|
168
|
+
assert_includes result[0], "\u2502" # │
|
|
169
|
+
assert_includes result[2], "\u2502"
|
|
170
|
+
# Separator row should use box-drawing
|
|
171
|
+
assert_includes result[1], "\u2500" # ─
|
|
172
|
+
assert_includes result[1], "\u253c" # ┼
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def test_table_column_alignment
|
|
176
|
+
lines = [
|
|
177
|
+
"| Short | Long column |",
|
|
178
|
+
"| ----- | ----------- |",
|
|
179
|
+
"| A | B |"
|
|
180
|
+
]
|
|
181
|
+
result = renderer.render_visible(lines, delimiter: nil)
|
|
182
|
+
# Both data rows should have same display width
|
|
183
|
+
w0 = display_width_without_ansi(result[0])
|
|
184
|
+
w2 = display_width_without_ansi(result[2])
|
|
185
|
+
assert_equal w0, w2
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# --- cursor_display_col ---
|
|
189
|
+
|
|
190
|
+
def test_cursor_display_col_non_table_line
|
|
191
|
+
# For non-table lines, should return screen_col_for_char_index
|
|
192
|
+
line = "hello world"
|
|
193
|
+
col = renderer.cursor_display_col(line, 5, visible_lines: [line], delimiter: nil)
|
|
194
|
+
expected = RuVim::TextMetrics.screen_col_for_char_index(line, 5)
|
|
195
|
+
assert_equal expected, col
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def test_cursor_display_col_table_line
|
|
199
|
+
lines = [
|
|
200
|
+
"| Name | Age |",
|
|
201
|
+
"| ----- | --- |",
|
|
202
|
+
"| Alice | 30 |"
|
|
203
|
+
]
|
|
204
|
+
# Cursor at start of "Alice" (index within raw line)
|
|
205
|
+
raw_line = lines[2]
|
|
206
|
+
col = renderer.cursor_display_col(raw_line, 2, visible_lines: lines, delimiter: nil)
|
|
207
|
+
# Should be > 0 (after the leading │ and padding)
|
|
208
|
+
assert col >= 0
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# --- ANSI support in render_rich_view_line_sc ---
|
|
212
|
+
|
|
213
|
+
def test_render_rich_view_line_sc_with_ansi
|
|
214
|
+
screen = create_test_screen
|
|
215
|
+
# Line with ANSI bold: "\e[1m" is 4 chars but 0 display width
|
|
216
|
+
text = "\e[1mhello\e[m world"
|
|
217
|
+
result = screen.send(:render_rich_view_line_sc, text, width: 20, skip_sc: 0)
|
|
218
|
+
# Should contain the ANSI sequences and the text
|
|
219
|
+
assert_includes result, "\e[1m"
|
|
220
|
+
assert_includes result, "hello"
|
|
221
|
+
assert_includes result, "world"
|
|
222
|
+
# Should end with reset
|
|
223
|
+
assert result.end_with?("\e[m") || result.include?("\e[m")
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def test_render_rich_view_line_sc_ansi_skip
|
|
227
|
+
screen = create_test_screen
|
|
228
|
+
# ANSI at start, skip some display columns
|
|
229
|
+
text = "\e[1mhello\e[m world"
|
|
230
|
+
result = screen.send(:render_rich_view_line_sc, text, width: 5, skip_sc: 3)
|
|
231
|
+
# Should show "lo wo" or similar (skipping 3 display cols of "hello")
|
|
232
|
+
assert_includes result, "lo"
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# --- Integration test ---
|
|
236
|
+
|
|
237
|
+
def test_markdown_rich_mode_integration
|
|
238
|
+
editor = fresh_editor
|
|
239
|
+
buf = editor.current_buffer
|
|
240
|
+
buf.replace_all_lines!(["# Title", "", "Some **bold** text"])
|
|
241
|
+
buf.options["filetype"] = "markdown"
|
|
242
|
+
|
|
243
|
+
RuVim::RichView.open!(editor, format: "markdown")
|
|
244
|
+
assert_equal :rich, editor.mode
|
|
245
|
+
state = editor.rich_state
|
|
246
|
+
assert_equal "markdown", state[:format]
|
|
247
|
+
|
|
248
|
+
# Render visible lines
|
|
249
|
+
lines = (0...buf.line_count).map { |i| buf.line_at(i) }
|
|
250
|
+
rendered = RuVim::RichView.render_visible_lines(editor, lines)
|
|
251
|
+
assert_equal 3, rendered.length
|
|
252
|
+
# Heading should have styling
|
|
253
|
+
assert_includes rendered[0], "\e["
|
|
254
|
+
|
|
255
|
+
RuVim::RichView.close!(editor)
|
|
256
|
+
assert_equal :normal, editor.mode
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def test_detect_format_markdown
|
|
260
|
+
editor = fresh_editor
|
|
261
|
+
buf = editor.current_buffer
|
|
262
|
+
buf.options["filetype"] = "markdown"
|
|
263
|
+
assert_equal "markdown", RuVim::RichView.detect_format(buf)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
private
|
|
267
|
+
|
|
268
|
+
def display_width_without_ansi(str)
|
|
269
|
+
# Strip ANSI escape sequences for width calculation
|
|
270
|
+
clean = str.gsub(/\e\[[0-9;]*m/, "")
|
|
271
|
+
RuVim::DisplayWidth.display_width(clean)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def create_test_screen
|
|
275
|
+
terminal = Object.new
|
|
276
|
+
def terminal.winsize; [24, 80]; end
|
|
277
|
+
RuVim::Screen.new(terminal: terminal)
|
|
278
|
+
end
|
|
279
|
+
end
|