ruvim 0.1.0 → 0.2.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.
@@ -1,4 +1,5 @@
1
1
  require_relative "test_helper"
2
+ require "tmpdir"
2
3
 
3
4
  class DispatcherTest < Minitest::Test
4
5
  def setup
@@ -43,6 +44,35 @@ class DispatcherTest < Minitest::Test
43
44
  assert_equal "ruby: 3", @editor.message
44
45
  end
45
46
 
47
+ def test_dispatch_ex_ruby_captures_stdout_and_stderr_into_virtual_buffer
48
+ @dispatcher.dispatch_ex(@editor, "ruby STDOUT.puts(%q[out]); STDERR.puts(%q[err]); 42")
49
+
50
+ assert_equal "[Ruby Output]", @editor.message
51
+ assert_equal :help, @editor.current_buffer.kind
52
+ body = @editor.current_buffer.lines.join("\n")
53
+ assert_includes body, "[stdout]"
54
+ assert_includes body, "out"
55
+ assert_includes body, "[stderr]"
56
+ assert_includes body, "err"
57
+ assert_includes body, "[result]"
58
+ assert_includes body, "42"
59
+ end
60
+
61
+ def test_dispatch_ex_shell_captures_stdout_and_stderr_into_virtual_buffer
62
+ @dispatcher.dispatch_ex(@editor, "!echo out; echo err 1>&2")
63
+
64
+ assert_equal "[Shell Output]", @editor.message
65
+ assert_equal :help, @editor.current_buffer.kind
66
+ body = @editor.current_buffer.lines.join("\n")
67
+ assert_includes body, "[command]"
68
+ assert_includes body, "echo out; echo err 1>&2"
69
+ assert_includes body, "[stdout]"
70
+ assert_includes body, "out"
71
+ assert_includes body, "[stderr]"
72
+ assert_includes body, "err"
73
+ assert_includes body, "[status]"
74
+ end
75
+
46
76
  def test_dispatch_ex_set_commands
47
77
  @dispatcher.dispatch_ex(@editor, "set number")
48
78
  assert_equal true, @editor.current_window.options["number"]
@@ -121,4 +151,90 @@ class DispatcherTest < Minitest::Test
121
151
  @dispatcher.dispatch_ex(@editor, "lnext")
122
152
  assert_equal 1, @editor.current_window.cursor_y
123
153
  end
154
+
155
+ def test_hidden_option_allows_buffer_switch_without_bang
156
+ @editor.materialize_intro_buffer!
157
+ @editor.current_buffer.replace_all_lines!(["x"])
158
+ @editor.current_buffer.modified = true
159
+ other = @editor.add_empty_buffer(path: "other.txt")
160
+ @editor.set_option("hidden", true, scope: :global)
161
+
162
+ @dispatcher.dispatch_ex(@editor, "buffer #{other.id}")
163
+
164
+ assert_equal other.id, @editor.current_buffer.id
165
+ refute @editor.message_error?
166
+ end
167
+
168
+ def test_autowrite_saves_current_buffer_before_buffer_switch
169
+ Dir.mktmpdir("ruvim-autowrite") do |dir|
170
+ path = File.join(dir, "a.txt")
171
+ File.write(path, "old\n")
172
+ @editor.materialize_intro_buffer!
173
+ @editor.current_buffer.path = path
174
+ @editor.current_buffer.replace_all_lines!(["new"])
175
+ @editor.current_buffer.modified = true
176
+ other = @editor.add_empty_buffer(path: "other.txt")
177
+ @editor.set_option("autowrite", true, scope: :global)
178
+
179
+ @dispatcher.dispatch_ex(@editor, "buffer #{other.id}")
180
+
181
+ assert_equal other.id, @editor.current_buffer.id
182
+ assert_equal "new", File.read(path).strip
183
+ end
184
+ end
185
+
186
+ def test_bdelete_deletes_current_buffer_and_switches_to_another
187
+ @editor.materialize_intro_buffer!
188
+ first = @editor.current_buffer
189
+ other = @editor.add_empty_buffer(path: "other.txt")
190
+ @dispatcher.dispatch_ex(@editor, "buffer #{other.id}")
191
+ assert_equal other.id, @editor.current_buffer.id
192
+
193
+ @dispatcher.dispatch_ex(@editor, "bd")
194
+
195
+ assert_equal first.id, @editor.current_buffer.id
196
+ refute @editor.buffers.key?(other.id)
197
+ assert_equal "buffer #{other.id} deleted", @editor.message
198
+ end
199
+
200
+ def test_bdelete_rejects_modified_buffer_without_bang
201
+ @editor.materialize_intro_buffer!
202
+ @editor.current_buffer.replace_all_lines!(["x"])
203
+ @editor.current_buffer.modified = true
204
+
205
+ @dispatcher.dispatch_ex(@editor, "bd")
206
+
207
+ assert_equal true, @editor.message_error?
208
+ assert_match(/No write since last change/, @editor.message)
209
+ assert @editor.buffers.key?(@editor.current_buffer.id)
210
+ end
211
+
212
+ def test_bdelete_bang_deletes_modified_buffer
213
+ @editor.materialize_intro_buffer!
214
+ first = @editor.current_buffer
215
+ other = @editor.add_empty_buffer(path: "other.txt")
216
+ @dispatcher.dispatch_ex(@editor, "buffer #{other.id}")
217
+ @editor.current_buffer.replace_all_lines!(["dirty"])
218
+ @editor.current_buffer.modified = true
219
+
220
+ @dispatcher.dispatch_ex(@editor, "bd!")
221
+
222
+ assert_equal first.id, @editor.current_buffer.id
223
+ refute @editor.buffers.key?(other.id)
224
+ refute @editor.message_error?
225
+ end
226
+
227
+ def test_splitbelow_and_splitright_change_insertion_side
228
+ @editor.set_option("splitbelow", false, scope: :global)
229
+ first = @editor.current_window_id
230
+ @dispatcher.dispatch_ex(@editor, "split")
231
+ assert_equal @editor.window_order[0], @editor.current_window_id
232
+ assert_equal first, @editor.window_order[1]
233
+
234
+ @editor.set_option("splitright", false, scope: :global)
235
+ @editor.current_window_id = first
236
+ @dispatcher.dispatch_ex(@editor, "vsplit")
237
+ idx = @editor.window_order.index(@editor.current_window_id)
238
+ assert_equal 1, idx
239
+ end
124
240
  end
@@ -0,0 +1,18 @@
1
+ require_relative "test_helper"
2
+
3
+ class DisplayWidthTest < Minitest::Test
4
+ def test_ambiguous_width_cache_tracks_env_changes
5
+ prev = ENV["RUVIM_AMBIGUOUS_WIDTH"]
6
+ ENV["RUVIM_AMBIGUOUS_WIDTH"] = nil
7
+ assert_equal 1, RuVim::DisplayWidth.cell_width("Ω")
8
+
9
+ ENV["RUVIM_AMBIGUOUS_WIDTH"] = "2"
10
+ assert_equal 2, RuVim::DisplayWidth.cell_width("Ω")
11
+ ensure
12
+ if prev.nil?
13
+ ENV.delete("RUVIM_AMBIGUOUS_WIDTH")
14
+ else
15
+ ENV["RUVIM_AMBIGUOUS_WIDTH"] = prev
16
+ end
17
+ end
18
+ end
@@ -1,8 +1,7 @@
1
- 1 # title
2
- 2
3
- 3 foo
4
- 4 bar 日本語 編集
5
- 5 baz
6
- ~
7
- ~
8
- -- NORMAL -- t1/1 w 1:1
1
+ 1 # title
2
+ 2
3
+ 3 foo
4
+ 4 bar 日本語 編集
5
+ 5 baz
6
+ ~
7
+ -- NORMAL -- [No Na 1:1
@@ -4,5 +4,4 @@ foo
4
4
  bar 日本語 編集
5
5
  baz
6
6
  ~
7
- ~
8
- -- NORMAL -- t1/1 w 1:1
7
+ -- NORMAL -- [No Na 1:1
@@ -1,7 +1,6 @@
1
- 1 # title
2
- 2
3
- 3 foo
4
- 4 bar 日本語 編集
5
- 5 baz
6
- 6 qux
7
- -- NORMAL -- t1 4:5
1
+ 1 # title
2
+ 2
3
+ 3 foo
4
+ 4 bar 日本語 編集
5
+ 5 baz
6
+ -- NORMAL -- [N 4:5
@@ -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)
@@ -43,15 +58,7 @@ class InputScreenIntegrationTest < Minitest::Test
43
58
  stdin = FakeTTY.new("\e[6~")
44
59
  input = RuVim::Input.new(stdin: stdin)
45
60
 
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
53
-
54
- begin
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,16 @@ 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
- ensure
64
- io_sc.alias_method(:select, :__orig_select_for_input_screen_test)
65
- io_sc.remove_method(:__orig_select_for_input_screen_test) rescue nil
66
- $VERBOSE = verbose
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: 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)
67
80
  end
68
81
  end
69
82
  end
data/test/screen_test.rb CHANGED
@@ -55,6 +55,38 @@ class ScreenTest < Minitest::Test
55
55
  assert_equal " 3 ", screen.send(:line_number_prefix, editor, win, buf, 2, 3) # current line is absolute when both enabled
56
56
  end
57
57
 
58
+ def test_signcolumn_yes_reserves_one_column_in_gutter
59
+ editor = RuVim::Editor.new
60
+ buf = editor.add_empty_buffer
61
+ win = editor.add_window(buffer_id: buf.id)
62
+ editor.set_option("number", true, scope: :window, window: win, buffer: buf)
63
+ editor.set_option("signcolumn", "yes", scope: :window, window: win, buffer: buf)
64
+ term = TerminalStub.new([8, 20])
65
+ screen = RuVim::Screen.new(terminal: term)
66
+
67
+ w = screen.send(:number_column_width, editor, win, buf)
68
+ prefix = screen.send(:line_number_prefix, editor, win, buf, 0, w)
69
+
70
+ assert_equal 6, w # sign(1) + default numberwidth(4) + trailing space
71
+ assert_equal " 1 ", prefix
72
+ end
73
+
74
+ def test_signcolumn_yes_with_width_reserves_multiple_columns
75
+ editor = RuVim::Editor.new
76
+ buf = editor.add_empty_buffer
77
+ win = editor.add_window(buffer_id: buf.id)
78
+ editor.set_option("number", true, scope: :window, window: win, buffer: buf)
79
+ editor.set_option("signcolumn", "yes:2", scope: :window, window: win, buffer: buf)
80
+ term = TerminalStub.new([8, 20])
81
+ screen = RuVim::Screen.new(terminal: term)
82
+
83
+ w = screen.send(:number_column_width, editor, win, buf)
84
+ prefix = screen.send(:line_number_prefix, editor, win, buf, 0, w)
85
+
86
+ assert_equal 7, w # sign(2) + default numberwidth(4) + trailing space
87
+ assert_equal " 1 ", prefix
88
+ end
89
+
58
90
  def test_render_shows_error_message_on_command_line_row_with_highlight
59
91
  editor = RuVim::Editor.new
60
92
  buf = editor.add_empty_buffer
@@ -120,4 +152,138 @@ class ScreenTest < Minitest::Test
120
152
  assert_equal 10, RuVim::DisplayWidth.display_width(visible, tabstop: 2)
121
153
  refute_includes out, "\n"
122
154
  end
155
+
156
+ def test_termguicolors_uses_truecolor_sequences_for_search_highlight
157
+ editor = RuVim::Editor.new
158
+ buf = editor.add_empty_buffer
159
+ win = editor.add_window(buffer_id: buf.id)
160
+ buf.replace_all_lines!(["foo"])
161
+ editor.set_last_search(pattern: "foo", direction: :forward)
162
+ editor.set_option("termguicolors", true, scope: :global)
163
+
164
+ term = TerminalStub.new([6, 20])
165
+ screen = RuVim::Screen.new(terminal: term)
166
+ out = screen.send(:render_text_line, buf.line_at(0), editor, buffer_row: 0, window: win, buffer: buf, width: 6)
167
+
168
+ assert_includes out, "\e[48;2;255;215;0m"
169
+ end
170
+
171
+ def test_render_text_line_respects_list_and_listchars_for_tab_trail_and_nbsp
172
+ editor = RuVim::Editor.new
173
+ buf = editor.add_empty_buffer
174
+ win = editor.add_window(buffer_id: buf.id)
175
+ buf.replace_all_lines!(["\tA \u00A0 "])
176
+ editor.set_option("list", true, scope: :window, window: win, buffer: buf)
177
+ editor.set_option("listchars", "tab:>.,trail:~,nbsp:*", scope: :window, window: win, buffer: buf)
178
+
179
+ term = TerminalStub.new([6, 20])
180
+ screen = RuVim::Screen.new(terminal: term)
181
+ out = screen.send(:render_text_line, buf.line_at(0), editor, buffer_row: 0, window: win, buffer: buf, width: 12)
182
+ visible = out.gsub(/\e\[[0-9;?]*[A-Za-z]/, "")
183
+
184
+ assert_includes visible, ">."
185
+ assert_includes visible, "*"
186
+ assert_includes visible, "~"
187
+ end
188
+
189
+ def test_render_text_line_sanitizes_terminal_escape_controls
190
+ editor = RuVim::Editor.new
191
+ buf = editor.add_empty_buffer
192
+ win = editor.add_window(buffer_id: buf.id)
193
+ buf.replace_all_lines!(["A\e]52;c;owned\aB"])
194
+ win.cursor_y = 1 # avoid cursor highlight on the tested row
195
+
196
+ term = TerminalStub.new([6, 40])
197
+ screen = RuVim::Screen.new(terminal: term)
198
+ out = screen.send(:render_text_line, buf.line_at(0), editor, buffer_row: 0, window: win, buffer: buf, width: 20)
199
+
200
+ refute_includes out, "\e]52"
201
+ visible = out.gsub(/\e\[[0-9;?]*[A-Za-z]/, "")
202
+ assert_includes visible, "A"
203
+ assert_includes visible, "B"
204
+ assert_includes visible, "?"
205
+ end
206
+
207
+ def test_wrap_and_showbreak_render_continuation_rows
208
+ editor = RuVim::Editor.new
209
+ buf = editor.add_empty_buffer
210
+ win = editor.add_window(buffer_id: buf.id)
211
+ buf.replace_all_lines!(["abcdef ghijkl"])
212
+ editor.set_option("wrap", true, scope: :window, window: win, buffer: buf)
213
+ editor.set_option("showbreak", ">>", scope: :window, window: win, buffer: buf)
214
+
215
+ term = TerminalStub.new([6, 8])
216
+ screen = RuVim::Screen.new(terminal: term)
217
+ rows, cols = term.winsize
218
+ text_rows, text_cols = editor.text_viewport_size(rows:, cols:)
219
+ rects = screen.send(:window_rects, editor, text_rows:, text_cols:)
220
+ frame = screen.send(:build_frame, editor, rows:, cols:, text_rows:, text_cols:, rects:)
221
+
222
+ row1 = frame[:lines][1].to_s.gsub(/\e\[[0-9;?]*[A-Za-z]/, "")
223
+ row2 = frame[:lines][2].to_s.gsub(/\e\[[0-9;?]*[A-Za-z]/, "")
224
+ assert_includes row1, "abcdef"
225
+ assert_includes row2, ">>"
226
+ end
227
+
228
+ def test_cursor_screen_position_supports_virtualedit_past_eol
229
+ editor = RuVim::Editor.new
230
+ buf = editor.add_empty_buffer
231
+ win = editor.add_window(buffer_id: buf.id)
232
+ buf.replace_all_lines!(["abc"])
233
+ editor.set_option("virtualedit", "onemore", scope: :global)
234
+ win.cursor_y = 0
235
+ win.cursor_x = 4
236
+
237
+ term = TerminalStub.new([6, 20])
238
+ screen = RuVim::Screen.new(terminal: term)
239
+ rows, cols = term.winsize
240
+ text_rows, text_cols = editor.text_viewport_size(rows:, cols:)
241
+ rects = screen.send(:window_rects, editor, text_rows:, text_cols:)
242
+ pos = screen.send(:cursor_screen_position, editor, text_rows, rects)
243
+
244
+ # col 1-based; "abc" places cursor at 4, one-past-eol should be 5
245
+ assert_equal [1, 5], pos
246
+ end
247
+
248
+ def test_cursor_screen_position_is_clamped_to_text_area_under_wrap
249
+ editor = RuVim::Editor.new
250
+ buf = editor.add_empty_buffer
251
+ win = editor.add_window(buffer_id: buf.id)
252
+ buf.replace_all_lines!(["x" * 40, "tail"])
253
+ editor.set_option("wrap", true, scope: :window, window: win, buffer: buf)
254
+ win.row_offset = 0
255
+ win.cursor_y = 1
256
+ win.cursor_x = 0
257
+
258
+ term = TerminalStub.new([6, 8]) # text_rows = 4 (footer 2 rows)
259
+ screen = RuVim::Screen.new(terminal: term)
260
+ rows, cols = term.winsize
261
+ text_rows, text_cols = editor.text_viewport_size(rows:, cols:)
262
+ rects = screen.send(:window_rects, editor, text_rows:, text_cols:)
263
+ row, _col = screen.send(:cursor_screen_position, editor, text_rows, rects)
264
+
265
+ assert_operator row, :<=, text_rows
266
+ end
267
+
268
+ def test_linebreak_and_breakindent_prefer_space_wrap
269
+ editor = RuVim::Editor.new
270
+ buf = editor.add_empty_buffer
271
+ win = editor.add_window(buffer_id: buf.id)
272
+ buf.replace_all_lines!([" foo bar baz"])
273
+ editor.set_option("wrap", true, scope: :window, window: win, buffer: buf)
274
+ editor.set_option("linebreak", true, scope: :window, window: win, buffer: buf)
275
+ editor.set_option("breakindent", true, scope: :window, window: win, buffer: buf)
276
+ editor.set_option("showbreak", ">", scope: :window, window: win, buffer: buf)
277
+
278
+ term = TerminalStub.new([6, 10])
279
+ screen = RuVim::Screen.new(terminal: term)
280
+ rows, cols = term.winsize
281
+ text_rows, text_cols = editor.text_viewport_size(rows:, cols:)
282
+ rects = screen.send(:window_rects, editor, text_rows:, text_cols:)
283
+ frame = screen.send(:build_frame, editor, rows:, cols:, text_rows:, text_cols:, rects:)
284
+
285
+ row2 = frame[:lines][2].to_s.gsub(/\e\[[0-9;?]*[A-Za-z]/, "")
286
+ assert_includes row2, ">"
287
+ assert_match(/\s>|\>\s/, row2)
288
+ end
123
289
  end
data/test/window_test.rb CHANGED
@@ -18,4 +18,30 @@ class WindowTest < Minitest::Test
18
18
  assert_operator cursor_col, :<, offset_col + 4
19
19
  assert_equal 3, win.col_offset
20
20
  end
21
+
22
+ def test_ensure_visible_respects_scrolloff_and_sidescrolloff
23
+ buffer = RuVim::Buffer.new(id: 1, lines: ["0123456789", "aaaaa", "bbbbb", "ccccc", "0123456789", "eeeee"])
24
+ win = RuVim::Window.new(id: 1, buffer_id: 1)
25
+ win.cursor_y = 4
26
+ win.cursor_x = 8
27
+
28
+ win.ensure_visible(buffer, height: 3, width: 5, tabstop: 2, scrolloff: 1, sidescrolloff: 1)
29
+
30
+ assert_equal 3, win.row_offset
31
+ assert_operator win.col_offset, :>, 0
32
+ end
33
+
34
+ def test_move_down_preserves_preferred_column_across_empty_line
35
+ long = "x" * 80
36
+ buffer = RuVim::Buffer.new(id: 1, lines: [long, "", long])
37
+ win = RuVim::Window.new(id: 1, buffer_id: 1)
38
+ win.cursor_y = 0
39
+ win.cursor_x = 50
40
+
41
+ win.move_down(buffer)
42
+ assert_equal [1, 0], [win.cursor_y, win.cursor_x]
43
+
44
+ win.move_down(buffer)
45
+ assert_equal [2, 50], [win.cursor_y, win.cursor_x]
46
+ end
21
47
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruvim
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Koichi Sasada
@@ -24,6 +24,7 @@ files:
24
24
  - docs/command.md
25
25
  - docs/config.md
26
26
  - docs/done.md
27
+ - docs/lib_cleanup_report.md
27
28
  - docs/plugin.md
28
29
  - docs/spec.md
29
30
  - docs/todo.md
@@ -49,6 +50,7 @@ files:
49
50
  - lib/ruvim/highlighter.rb
50
51
  - lib/ruvim/input.rb
51
52
  - lib/ruvim/keymap_manager.rb
53
+ - lib/ruvim/keyword_chars.rb
52
54
  - lib/ruvim/screen.rb
53
55
  - lib/ruvim/terminal.rb
54
56
  - lib/ruvim/text_metrics.rb
@@ -66,7 +68,9 @@ files:
66
68
  - test/buffer_test.rb
67
69
  - test/cli_test.rb
68
70
  - test/config_dsl_test.rb
71
+ - test/config_loader_test.rb
69
72
  - test/dispatcher_test.rb
73
+ - test/display_width_test.rb
70
74
  - test/editor_mark_test.rb
71
75
  - test/editor_register_test.rb
72
76
  - test/fixtures/render_basic_snapshot.txt