ruvim 0.1.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 +7 -0
- data/.github/workflows/test.yml +15 -0
- data/README.md +135 -0
- data/Rakefile +36 -0
- data/docs/binding.md +125 -0
- data/docs/command.md +306 -0
- data/docs/config.md +155 -0
- data/docs/done.md +112 -0
- data/docs/plugin.md +559 -0
- data/docs/spec.md +655 -0
- data/docs/todo.md +63 -0
- data/docs/tutorial.md +490 -0
- data/docs/vim_diff.md +179 -0
- data/exe/ruvim +6 -0
- data/lib/ruvim/app.rb +1600 -0
- data/lib/ruvim/buffer.rb +421 -0
- data/lib/ruvim/cli.rb +264 -0
- data/lib/ruvim/clipboard.rb +73 -0
- data/lib/ruvim/command_invocation.rb +14 -0
- data/lib/ruvim/command_line.rb +63 -0
- data/lib/ruvim/command_registry.rb +38 -0
- data/lib/ruvim/config_dsl.rb +134 -0
- data/lib/ruvim/config_loader.rb +68 -0
- data/lib/ruvim/context.rb +26 -0
- data/lib/ruvim/dispatcher.rb +120 -0
- data/lib/ruvim/display_width.rb +110 -0
- data/lib/ruvim/editor.rb +1025 -0
- data/lib/ruvim/ex_command_registry.rb +80 -0
- data/lib/ruvim/global_commands.rb +1889 -0
- data/lib/ruvim/highlighter.rb +52 -0
- data/lib/ruvim/input.rb +66 -0
- data/lib/ruvim/keymap_manager.rb +96 -0
- data/lib/ruvim/screen.rb +452 -0
- data/lib/ruvim/terminal.rb +30 -0
- data/lib/ruvim/text_metrics.rb +96 -0
- data/lib/ruvim/version.rb +5 -0
- data/lib/ruvim/window.rb +71 -0
- data/lib/ruvim.rb +30 -0
- data/sig/ruvim.rbs +4 -0
- data/test/app_completion_test.rb +39 -0
- data/test/app_dot_repeat_test.rb +54 -0
- data/test/app_motion_test.rb +73 -0
- data/test/app_register_test.rb +47 -0
- data/test/app_scenario_test.rb +77 -0
- data/test/app_startup_test.rb +199 -0
- data/test/app_text_object_test.rb +54 -0
- data/test/app_unicode_behavior_test.rb +66 -0
- data/test/buffer_test.rb +72 -0
- data/test/cli_test.rb +165 -0
- data/test/config_dsl_test.rb +78 -0
- data/test/dispatcher_test.rb +124 -0
- data/test/editor_mark_test.rb +69 -0
- data/test/editor_register_test.rb +64 -0
- data/test/fixtures/render_basic_snapshot.txt +8 -0
- data/test/fixtures/render_basic_snapshot_nonumber.txt +8 -0
- data/test/fixtures/render_unicode_scrolled_snapshot.txt +7 -0
- data/test/highlighter_test.rb +16 -0
- data/test/input_screen_integration_test.rb +69 -0
- data/test/keymap_manager_test.rb +48 -0
- data/test/render_snapshot_test.rb +70 -0
- data/test/screen_test.rb +123 -0
- data/test/search_option_test.rb +39 -0
- data/test/test_helper.rb +15 -0
- data/test/text_metrics_test.rb +42 -0
- data/test/window_test.rb +21 -0
- metadata +106 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
require_relative "test_helper"
|
|
2
|
+
|
|
3
|
+
class DispatcherTest < Minitest::Test
|
|
4
|
+
def setup
|
|
5
|
+
@app = RuVim::App.new
|
|
6
|
+
@editor = @app.instance_variable_get(:@editor)
|
|
7
|
+
@dispatcher = RuVim::Dispatcher.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def test_parse_ex_with_bang_and_args
|
|
11
|
+
parsed = @dispatcher.parse_ex("w! foo.txt")
|
|
12
|
+
assert_equal "w", parsed.name
|
|
13
|
+
assert parsed.bang
|
|
14
|
+
assert_equal ["foo.txt"], parsed.argv
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_dispatch_ex_help_sets_message
|
|
18
|
+
@dispatcher.dispatch_ex(@editor, "help")
|
|
19
|
+
assert_equal "[Help] help", @editor.message
|
|
20
|
+
assert_equal :help, @editor.current_buffer.kind
|
|
21
|
+
assert @editor.current_buffer.readonly?
|
|
22
|
+
refute @editor.current_buffer.modifiable?
|
|
23
|
+
assert_includes @editor.current_buffer.lines.join("\n"), "help"
|
|
24
|
+
assert_equal :normal, @editor.mode
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_dispatch_ex_help_topic_for_command
|
|
28
|
+
@dispatcher.dispatch_ex(@editor, "help w")
|
|
29
|
+
assert_equal "[Help] w", @editor.message
|
|
30
|
+
body = @editor.current_buffer.lines.join("\n")
|
|
31
|
+
assert_includes body, ":w"
|
|
32
|
+
assert_includes body, "Write current buffer"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_dispatch_ex_command_and_ruby
|
|
36
|
+
@dispatcher.dispatch_ex(@editor, "command Hi help")
|
|
37
|
+
assert_equal "Defined :Hi", @editor.message
|
|
38
|
+
|
|
39
|
+
@dispatcher.dispatch_ex(@editor, "Hi")
|
|
40
|
+
assert_equal "[Help] help", @editor.message
|
|
41
|
+
|
|
42
|
+
@dispatcher.dispatch_ex(@editor, "ruby 1+2")
|
|
43
|
+
assert_equal "ruby: 3", @editor.message
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_dispatch_ex_set_commands
|
|
47
|
+
@dispatcher.dispatch_ex(@editor, "set number")
|
|
48
|
+
assert_equal true, @editor.current_window.options["number"]
|
|
49
|
+
|
|
50
|
+
@dispatcher.dispatch_ex(@editor, "setlocal tabstop=4")
|
|
51
|
+
assert_equal 4, @editor.current_buffer.options["tabstop"]
|
|
52
|
+
|
|
53
|
+
@dispatcher.dispatch_ex(@editor, "setglobal tabstop=8")
|
|
54
|
+
assert_equal 8, @editor.global_options["tabstop"]
|
|
55
|
+
assert_equal 4, @editor.effective_option("tabstop")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_q_closes_current_window_when_multiple_windows_exist
|
|
59
|
+
@editor.split_current_window(layout: :horizontal)
|
|
60
|
+
assert_equal 2, @editor.window_count
|
|
61
|
+
|
|
62
|
+
@dispatcher.dispatch_ex(@editor, "q")
|
|
63
|
+
|
|
64
|
+
assert_equal 1, @editor.window_count
|
|
65
|
+
assert @editor.running?
|
|
66
|
+
assert_equal "closed window", @editor.message
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def test_q_closes_current_tab_when_multiple_tabs_exist
|
|
70
|
+
@editor.tabnew
|
|
71
|
+
assert_equal 2, @editor.tabpage_count
|
|
72
|
+
assert_equal 1, @editor.window_count
|
|
73
|
+
|
|
74
|
+
@dispatcher.dispatch_ex(@editor, "q")
|
|
75
|
+
|
|
76
|
+
assert_equal 1, @editor.tabpage_count
|
|
77
|
+
assert @editor.running?
|
|
78
|
+
assert_equal "closed tab", @editor.message
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def test_q_exits_app_when_last_window
|
|
82
|
+
assert_equal 1, @editor.window_count
|
|
83
|
+
assert_equal 1, @editor.tabpage_count
|
|
84
|
+
|
|
85
|
+
@dispatcher.dispatch_ex(@editor, "q")
|
|
86
|
+
|
|
87
|
+
refute @editor.running?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def test_dispatch_ex_error_marks_message_as_error
|
|
91
|
+
@dispatcher.dispatch_ex(@editor, "no_such_command")
|
|
92
|
+
|
|
93
|
+
assert_match(/Error:/, @editor.message)
|
|
94
|
+
assert_equal true, @editor.message_error?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_vimgrep_populates_quickfix_and_cnext_moves
|
|
98
|
+
@editor.materialize_intro_buffer!
|
|
99
|
+
@editor.current_buffer.replace_all_lines!(["foo", "bar foo", "baz"])
|
|
100
|
+
@dispatcher.dispatch_ex(@editor, "vimgrep /foo/")
|
|
101
|
+
|
|
102
|
+
assert_equal 2, @editor.quickfix_items.length
|
|
103
|
+
assert_equal 0, @editor.current_window.cursor_y
|
|
104
|
+
|
|
105
|
+
@dispatcher.dispatch_ex(@editor, "cnext")
|
|
106
|
+
assert_equal 1, @editor.current_window.cursor_y
|
|
107
|
+
|
|
108
|
+
@dispatcher.dispatch_ex(@editor, "copen")
|
|
109
|
+
qf_windows = @editor.find_window_ids_by_buffer_kind(:quickfix)
|
|
110
|
+
refute_empty qf_windows
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def test_lvimgrep_populates_location_list_and_lnext_moves
|
|
114
|
+
@editor.materialize_intro_buffer!
|
|
115
|
+
@editor.current_buffer.replace_all_lines!(["aa", "bb aa", "cc aa"])
|
|
116
|
+
wid = @editor.current_window_id
|
|
117
|
+
|
|
118
|
+
@dispatcher.dispatch_ex(@editor, "lvimgrep /aa/")
|
|
119
|
+
assert_equal 3, @editor.location_items(wid).length
|
|
120
|
+
|
|
121
|
+
@dispatcher.dispatch_ex(@editor, "lnext")
|
|
122
|
+
assert_equal 1, @editor.current_window.cursor_y
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require_relative "test_helper"
|
|
2
|
+
|
|
3
|
+
class EditorMarkTest < Minitest::Test
|
|
4
|
+
def test_local_and_global_marks
|
|
5
|
+
editor = fresh_editor
|
|
6
|
+
buffer = editor.current_buffer
|
|
7
|
+
window = editor.current_window
|
|
8
|
+
|
|
9
|
+
buffer.replace_all_lines!([" abc", "def"])
|
|
10
|
+
window.cursor_y = 1
|
|
11
|
+
window.cursor_x = 2
|
|
12
|
+
|
|
13
|
+
assert editor.set_mark("a")
|
|
14
|
+
assert editor.set_mark("A")
|
|
15
|
+
|
|
16
|
+
assert_equal 1, editor.mark_location("a")[:row]
|
|
17
|
+
assert_equal 2, editor.mark_location("A")[:col]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_jump_to_mark_linewise_uses_first_nonblank
|
|
21
|
+
editor = fresh_editor
|
|
22
|
+
buffer = editor.current_buffer
|
|
23
|
+
window = editor.current_window
|
|
24
|
+
buffer.replace_all_lines!([" abc"])
|
|
25
|
+
window.cursor_y = 0
|
|
26
|
+
window.cursor_x = 4
|
|
27
|
+
editor.set_mark("a")
|
|
28
|
+
|
|
29
|
+
window.cursor_x = 0
|
|
30
|
+
editor.jump_to_mark("a", linewise: true)
|
|
31
|
+
|
|
32
|
+
assert_equal 2, window.cursor_x
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_jumplist_older_and_newer
|
|
36
|
+
editor = fresh_editor
|
|
37
|
+
buffer = editor.current_buffer
|
|
38
|
+
window = editor.current_window
|
|
39
|
+
buffer.replace_all_lines!(["a", "b", "c"])
|
|
40
|
+
window.cursor_y = 0
|
|
41
|
+
window.cursor_x = 0
|
|
42
|
+
editor.push_jump_location
|
|
43
|
+
|
|
44
|
+
window.cursor_y = 2
|
|
45
|
+
window.cursor_x = 0
|
|
46
|
+
editor.push_jump_location
|
|
47
|
+
|
|
48
|
+
editor.jump_older
|
|
49
|
+
assert_equal 0, window.cursor_y
|
|
50
|
+
|
|
51
|
+
editor.jump_newer
|
|
52
|
+
assert_equal 2, window.cursor_y
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def test_macro_recording_and_append
|
|
56
|
+
editor = fresh_editor
|
|
57
|
+
|
|
58
|
+
assert editor.start_macro_recording("a")
|
|
59
|
+
editor.record_macro_key("i")
|
|
60
|
+
editor.record_macro_key("x")
|
|
61
|
+
editor.stop_macro_recording
|
|
62
|
+
assert_equal %w[i x], editor.macro_keys("a")
|
|
63
|
+
|
|
64
|
+
assert editor.start_macro_recording("A")
|
|
65
|
+
editor.record_macro_key("y")
|
|
66
|
+
editor.stop_macro_recording
|
|
67
|
+
assert_equal %w[i x y], editor.macro_keys("a")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require_relative "test_helper"
|
|
2
|
+
|
|
3
|
+
class EditorRegisterTest < Minitest::Test
|
|
4
|
+
def test_named_register_and_append_register
|
|
5
|
+
editor = fresh_editor
|
|
6
|
+
|
|
7
|
+
editor.set_register("a", text: "foo", type: :charwise)
|
|
8
|
+
assert_equal({ text: "foo", type: :charwise }, editor.get_register("a"))
|
|
9
|
+
assert_equal({ text: "foo", type: :charwise }, editor.get_register("\""))
|
|
10
|
+
|
|
11
|
+
editor.set_register("A", text: "bar", type: :charwise)
|
|
12
|
+
assert_equal({ text: "foobar", type: :charwise }, editor.get_register("a"))
|
|
13
|
+
assert_equal({ text: "foobar", type: :charwise }, editor.get_register("\""))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_active_register_is_consumed_once
|
|
17
|
+
editor = fresh_editor
|
|
18
|
+
editor.set_active_register("b")
|
|
19
|
+
|
|
20
|
+
assert_equal "b", editor.consume_active_register
|
|
21
|
+
assert_equal "\"", editor.consume_active_register
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_black_hole_register_discards_explicit_write
|
|
25
|
+
editor = fresh_editor
|
|
26
|
+
editor.set_register("\"", text: "keep", type: :charwise)
|
|
27
|
+
|
|
28
|
+
editor.set_register("_", text: "drop", type: :charwise)
|
|
29
|
+
|
|
30
|
+
assert_equal({ text: "keep", type: :charwise }, editor.get_register("\""))
|
|
31
|
+
assert_nil editor.get_register("_")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_operator_register_updates_yank_zero_and_delete_numbered
|
|
35
|
+
editor = fresh_editor
|
|
36
|
+
|
|
37
|
+
editor.store_operator_register("\"", text: "yank", type: :charwise, kind: :yank)
|
|
38
|
+
assert_equal({ text: "yank", type: :charwise }, editor.get_register("\""))
|
|
39
|
+
assert_equal({ text: "yank", type: :charwise }, editor.get_register("0"))
|
|
40
|
+
|
|
41
|
+
editor.store_operator_register("\"", text: "del1", type: :charwise, kind: :delete)
|
|
42
|
+
editor.store_operator_register("\"", text: "del2", type: :linewise, kind: :delete)
|
|
43
|
+
assert_equal({ text: "del2", type: :linewise }, editor.get_register("1"))
|
|
44
|
+
assert_equal({ text: "del1", type: :charwise }, editor.get_register("2"))
|
|
45
|
+
assert_equal({ text: "yank", type: :charwise }, editor.get_register("0"))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_black_hole_skips_auto_operator_registers
|
|
49
|
+
editor = fresh_editor
|
|
50
|
+
editor.store_operator_register("\"", text: "seed", type: :charwise, kind: :yank)
|
|
51
|
+
|
|
52
|
+
editor.store_operator_register("_", text: "drop", type: :charwise, kind: :delete)
|
|
53
|
+
|
|
54
|
+
assert_equal({ text: "seed", type: :charwise }, editor.get_register("\""))
|
|
55
|
+
assert_equal({ text: "seed", type: :charwise }, editor.get_register("0"))
|
|
56
|
+
assert_nil editor.get_register("1")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_detect_filetype_on_opened_buffer
|
|
60
|
+
editor = RuVim::Editor.new
|
|
61
|
+
buffer = editor.add_empty_buffer(path: "/tmp/example.rb")
|
|
62
|
+
assert_equal "ruby", buffer.options["filetype"]
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require_relative "test_helper"
|
|
2
|
+
|
|
3
|
+
class HighlighterTest < Minitest::Test
|
|
4
|
+
def test_ruby_highlighter_marks_keyword_and_string
|
|
5
|
+
cols = RuVim::Highlighter.color_columns("ruby", 'def x; "hi"; end')
|
|
6
|
+
refute_empty cols
|
|
7
|
+
assert_equal "\e[36m", cols[0] # "def"
|
|
8
|
+
assert_equal "\e[32m", cols[7] # opening quote
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def test_json_highlighter_marks_key_and_number
|
|
12
|
+
cols = RuVim::Highlighter.color_columns("json", '{"a": 10}')
|
|
13
|
+
assert_equal "\e[36m", cols[1] # key chars
|
|
14
|
+
assert_equal "\e[33m", cols[6] # number start
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require_relative "test_helper"
|
|
2
|
+
|
|
3
|
+
class InputScreenIntegrationTest < Minitest::Test
|
|
4
|
+
TerminalStub = Struct.new(:winsize) do
|
|
5
|
+
attr_reader :writes
|
|
6
|
+
|
|
7
|
+
def write(data)
|
|
8
|
+
@writes ||= []
|
|
9
|
+
@writes << data
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class FakeTTY
|
|
14
|
+
def initialize(bytes)
|
|
15
|
+
@bytes = bytes.dup
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def getch
|
|
19
|
+
@bytes.slice!(0)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def read_nonblock(_n)
|
|
23
|
+
raise IO::WaitReadable if @bytes.empty?
|
|
24
|
+
|
|
25
|
+
@bytes.slice!(0)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def ready?
|
|
29
|
+
!@bytes.empty?
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_input_pagedown_to_app_and_screen_render
|
|
34
|
+
app = RuVim::App.new(clean: true)
|
|
35
|
+
editor = app.instance_variable_get(:@editor)
|
|
36
|
+
editor.materialize_intro_buffer!
|
|
37
|
+
editor.current_buffer.replace_all_lines!((1..30).map { |i| "line #{i}" })
|
|
38
|
+
|
|
39
|
+
term = TerminalStub.new([8, 40])
|
|
40
|
+
screen = RuVim::Screen.new(terminal: term)
|
|
41
|
+
app.instance_variable_set(:@screen, screen)
|
|
42
|
+
|
|
43
|
+
stdin = FakeTTY.new("\e[6~")
|
|
44
|
+
input = RuVim::Input.new(stdin: 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
|
|
53
|
+
|
|
54
|
+
begin
|
|
55
|
+
key = input.read_key(timeout: 0.2)
|
|
56
|
+
assert_equal :pagedown, key
|
|
57
|
+
|
|
58
|
+
app.send(:handle_normal_key, key)
|
|
59
|
+
screen.render(editor)
|
|
60
|
+
|
|
61
|
+
assert_operator editor.current_window.cursor_y, :>, 0
|
|
62
|
+
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
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require_relative "test_helper"
|
|
2
|
+
|
|
3
|
+
class KeymapManagerTest < Minitest::Test
|
|
4
|
+
def setup
|
|
5
|
+
@km = RuVim::KeymapManager.new
|
|
6
|
+
@editor = fresh_editor
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def test_mode_local_beats_global
|
|
10
|
+
@km.bind_global("x", "global.x")
|
|
11
|
+
@km.bind(:normal, "x", "normal.x")
|
|
12
|
+
|
|
13
|
+
match = @km.resolve_with_context(:normal, ["x"], editor: @editor)
|
|
14
|
+
assert_equal :match, match.status
|
|
15
|
+
assert_equal "normal.x", match.invocation.id
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_buffer_local_beats_mode_local
|
|
19
|
+
@km.bind(:normal, "x", "normal.x")
|
|
20
|
+
@km.bind_buffer(@editor.current_buffer.id, "x", "buffer.x")
|
|
21
|
+
|
|
22
|
+
match = @km.resolve_with_context(:normal, ["x"], editor: @editor)
|
|
23
|
+
assert_equal "buffer.x", match.invocation.id
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_prefix_pending_and_match
|
|
27
|
+
@km.bind(:normal, "dd", "delete.line")
|
|
28
|
+
|
|
29
|
+
pending = @km.resolve_with_context(:normal, ["d"], editor: @editor)
|
|
30
|
+
assert_equal :pending, pending.status
|
|
31
|
+
|
|
32
|
+
exact = @km.resolve_with_context(:normal, %w[d d], editor: @editor)
|
|
33
|
+
assert_equal :match, exact.status
|
|
34
|
+
assert_equal "delete.line", exact.invocation.id
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_filetype_local_map_respects_mode
|
|
38
|
+
@editor.current_buffer.options["filetype"] = "ruby"
|
|
39
|
+
@km.bind(:normal, "x", "normal.x")
|
|
40
|
+
@km.bind_filetype("ruby", "x", "ruby.insert.x", mode: :insert)
|
|
41
|
+
|
|
42
|
+
normal = @km.resolve_with_context(:normal, ["x"], editor: @editor)
|
|
43
|
+
assert_equal "normal.x", normal.invocation.id
|
|
44
|
+
|
|
45
|
+
insert = @km.resolve_with_context(:insert, ["x"], editor: @editor)
|
|
46
|
+
assert_equal "ruby.insert.x", insert.invocation.id
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require_relative "test_helper"
|
|
2
|
+
|
|
3
|
+
class RenderSnapshotTest < Minitest::Test
|
|
4
|
+
TerminalStub = Struct.new(:winsize) do
|
|
5
|
+
def write(_data); end
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
FIXTURE = File.expand_path("fixtures/render_basic_snapshot.txt", __dir__)
|
|
9
|
+
FIXTURE_NONUM = File.expand_path("fixtures/render_basic_snapshot_nonumber.txt", __dir__)
|
|
10
|
+
FIXTURE_UNICODE_SCROLL = File.expand_path("fixtures/render_unicode_scrolled_snapshot.txt", __dir__)
|
|
11
|
+
|
|
12
|
+
def test_basic_render_frame_matches_snapshot
|
|
13
|
+
snapshot = build_snapshot(
|
|
14
|
+
lines: ["# title", "", "foo", "bar 日本語 編集", "baz"],
|
|
15
|
+
winsize: [8, 24],
|
|
16
|
+
number: true
|
|
17
|
+
)
|
|
18
|
+
expected = File.read(FIXTURE)
|
|
19
|
+
assert_equal expected, snapshot
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_basic_render_frame_without_number_matches_snapshot
|
|
23
|
+
snapshot = build_snapshot(
|
|
24
|
+
lines: ["# title", "", "foo", "bar 日本語 編集", "baz"],
|
|
25
|
+
winsize: [8, 24],
|
|
26
|
+
number: false
|
|
27
|
+
)
|
|
28
|
+
expected = File.read(FIXTURE_NONUM)
|
|
29
|
+
assert_equal expected, snapshot
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_unicode_scrolled_render_matches_snapshot
|
|
33
|
+
snapshot = build_snapshot(
|
|
34
|
+
lines: ["# title", "", "foo", "bar 日本語 編集", "baz", "qux", "quux"],
|
|
35
|
+
winsize: [7, 20],
|
|
36
|
+
number: true,
|
|
37
|
+
cursor_y: 3,
|
|
38
|
+
cursor_x: 4
|
|
39
|
+
)
|
|
40
|
+
expected = File.read(FIXTURE_UNICODE_SCROLL)
|
|
41
|
+
assert_equal expected, snapshot
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def build_snapshot(lines:, winsize:, number:, cursor_y: 0, cursor_x: 0)
|
|
47
|
+
editor = RuVim::Editor.new
|
|
48
|
+
buf = editor.add_empty_buffer
|
|
49
|
+
win = editor.add_window(buffer_id: buf.id)
|
|
50
|
+
buf.replace_all_lines!(lines)
|
|
51
|
+
editor.set_option("number", number, scope: :window, window: win, buffer: buf)
|
|
52
|
+
win.cursor_y = cursor_y
|
|
53
|
+
win.cursor_x = cursor_x
|
|
54
|
+
|
|
55
|
+
term = TerminalStub.new(winsize)
|
|
56
|
+
screen = RuVim::Screen.new(terminal: term)
|
|
57
|
+
|
|
58
|
+
rows, cols = term.winsize
|
|
59
|
+
text_rows, text_cols = editor.text_viewport_size(rows:, cols:)
|
|
60
|
+
rects = screen.send(:window_rects, editor, text_rows:, text_cols:)
|
|
61
|
+
win.ensure_visible(buf, height: text_rows, width: text_cols, tabstop: 2)
|
|
62
|
+
frame = screen.send(:build_frame, editor, rows:, cols:, text_rows:, text_cols:, rects:)
|
|
63
|
+
|
|
64
|
+
(1..rows).map { |row| strip_ansi(frame[:lines][row].to_s) }.join("\n")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def strip_ansi(str)
|
|
68
|
+
str.gsub(/\e\[[0-9;?]*[A-Za-z]/, "")
|
|
69
|
+
end
|
|
70
|
+
end
|
data/test/screen_test.rb
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
require_relative "test_helper"
|
|
2
|
+
|
|
3
|
+
class ScreenTest < Minitest::Test
|
|
4
|
+
TerminalStub = Struct.new(:winsize) do
|
|
5
|
+
attr_reader :writes
|
|
6
|
+
|
|
7
|
+
def write(data)
|
|
8
|
+
@writes ||= []
|
|
9
|
+
@writes << data
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_horizontal_render_draws_all_visible_rows
|
|
14
|
+
editor = RuVim::Editor.new
|
|
15
|
+
buf = editor.add_empty_buffer
|
|
16
|
+
editor.add_window(buffer_id: buf.id)
|
|
17
|
+
buf.replace_all_lines!(["# title", "", "foo", "bar 日本語 編集", "baz"])
|
|
18
|
+
|
|
19
|
+
term = TerminalStub.new([8, 20])
|
|
20
|
+
screen = RuVim::Screen.new(terminal: term)
|
|
21
|
+
|
|
22
|
+
rows, cols = term.winsize
|
|
23
|
+
text_rows, text_cols = editor.text_viewport_size(rows:, cols:)
|
|
24
|
+
rects = screen.send(:window_rects, editor, text_rows:, text_cols:)
|
|
25
|
+
editor.window_order.each do |win_id|
|
|
26
|
+
w = editor.windows.fetch(win_id)
|
|
27
|
+
b = editor.buffers.fetch(w.buffer_id)
|
|
28
|
+
w.ensure_visible(b, height: text_rows, width: text_cols)
|
|
29
|
+
end
|
|
30
|
+
frame = screen.send(:build_frame, editor, rows:, cols:, text_rows:, text_cols:, rects:)
|
|
31
|
+
|
|
32
|
+
assert_includes frame[:lines][1], "#"
|
|
33
|
+
assert frame[:lines].key?(2), "row 2 should be rendered"
|
|
34
|
+
assert_includes frame[:lines][3], "foo"
|
|
35
|
+
assert_includes frame[:lines][4], "bar"
|
|
36
|
+
assert_includes frame[:lines][5], "baz"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_line_number_prefix_supports_relativenumber
|
|
40
|
+
editor = RuVim::Editor.new
|
|
41
|
+
buf = editor.add_empty_buffer
|
|
42
|
+
win = editor.add_window(buffer_id: buf.id)
|
|
43
|
+
buf.replace_all_lines!(["aa", "bb", "cc", "dd"])
|
|
44
|
+
win.cursor_y = 2
|
|
45
|
+
editor.set_option("relativenumber", true, scope: :window, window: win, buffer: buf)
|
|
46
|
+
|
|
47
|
+
term = TerminalStub.new([8, 20])
|
|
48
|
+
screen = RuVim::Screen.new(terminal: term)
|
|
49
|
+
|
|
50
|
+
assert_equal " 2 ", screen.send(:line_number_prefix, editor, win, buf, 0, 3)
|
|
51
|
+
assert_equal " 1 ", screen.send(:line_number_prefix, editor, win, buf, 1, 3)
|
|
52
|
+
assert_equal " 0 ", screen.send(:line_number_prefix, editor, win, buf, 2, 3)
|
|
53
|
+
|
|
54
|
+
editor.set_option("number", true, scope: :window, window: win, buffer: buf)
|
|
55
|
+
assert_equal " 3 ", screen.send(:line_number_prefix, editor, win, buf, 2, 3) # current line is absolute when both enabled
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_render_shows_error_message_on_command_line_row_with_highlight
|
|
59
|
+
editor = RuVim::Editor.new
|
|
60
|
+
buf = editor.add_empty_buffer
|
|
61
|
+
editor.add_window(buffer_id: buf.id)
|
|
62
|
+
term = TerminalStub.new([6, 20])
|
|
63
|
+
screen = RuVim::Screen.new(terminal: term)
|
|
64
|
+
|
|
65
|
+
editor.echo_error("boom")
|
|
66
|
+
screen.render(editor)
|
|
67
|
+
out = term.writes.last
|
|
68
|
+
assert_includes out, "\e[97;41m"
|
|
69
|
+
assert_includes out, "boom"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_render_reuses_syntax_highlight_cache_for_same_line
|
|
73
|
+
editor = RuVim::Editor.new
|
|
74
|
+
buf = editor.add_empty_buffer
|
|
75
|
+
win = editor.add_window(buffer_id: buf.id)
|
|
76
|
+
buf.replace_all_lines!(['def x; "hi"; end'])
|
|
77
|
+
editor.set_option("filetype", "ruby", scope: :buffer, buffer: buf, window: win)
|
|
78
|
+
|
|
79
|
+
term = TerminalStub.new([6, 40])
|
|
80
|
+
screen = RuVim::Screen.new(terminal: term)
|
|
81
|
+
|
|
82
|
+
calls = 0
|
|
83
|
+
mod = RuVim::Highlighter.singleton_class
|
|
84
|
+
verbose, $VERBOSE = $VERBOSE, nil
|
|
85
|
+
mod.alias_method(:__orig_color_columns_for_screen_test, :color_columns)
|
|
86
|
+
mod.define_method(:color_columns) do |*args, **kwargs|
|
|
87
|
+
calls += 1
|
|
88
|
+
__orig_color_columns_for_screen_test(*args, **kwargs)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
begin
|
|
92
|
+
screen.render(editor)
|
|
93
|
+
screen.render(editor)
|
|
94
|
+
ensure
|
|
95
|
+
mod.alias_method(:color_columns, :__orig_color_columns_for_screen_test)
|
|
96
|
+
mod.remove_method(:__orig_color_columns_for_screen_test) rescue nil
|
|
97
|
+
$VERBOSE = verbose
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
assert_equal 1, calls
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def test_render_text_line_with_cursor_search_and_syntax_highlights_fits_width
|
|
104
|
+
editor = RuVim::Editor.new
|
|
105
|
+
buf = editor.add_empty_buffer
|
|
106
|
+
win = editor.add_window(buffer_id: buf.id)
|
|
107
|
+
buf.replace_all_lines!(['def x; "日本語"; end'])
|
|
108
|
+
editor.set_option("filetype", "ruby", scope: :buffer, buffer: buf, window: win)
|
|
109
|
+
editor.set_last_search(pattern: "日本", direction: :forward)
|
|
110
|
+
win.cursor_y = 0
|
|
111
|
+
win.cursor_x = 8 # around string body
|
|
112
|
+
|
|
113
|
+
term = TerminalStub.new([6, 18])
|
|
114
|
+
screen = RuVim::Screen.new(terminal: term)
|
|
115
|
+
line = buf.line_at(0)
|
|
116
|
+
|
|
117
|
+
out = screen.send(:render_text_line, line, editor, buffer_row: 0, window: win, buffer: buf, width: 10)
|
|
118
|
+
visible = out.gsub(/\e\[[0-9;?]*[A-Za-z]/, "")
|
|
119
|
+
|
|
120
|
+
assert_equal 10, RuVim::DisplayWidth.display_width(visible, tabstop: 2)
|
|
121
|
+
refute_includes out, "\n"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require_relative "test_helper"
|
|
2
|
+
|
|
3
|
+
class SearchOptionTest < Minitest::Test
|
|
4
|
+
TerminalStub = Struct.new(:winsize) do
|
|
5
|
+
def write(_data); end
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def setup
|
|
9
|
+
@editor = RuVim::Editor.new
|
|
10
|
+
@buf = @editor.add_empty_buffer
|
|
11
|
+
@win = @editor.add_window(buffer_id: @buf.id)
|
|
12
|
+
@gc = RuVim::GlobalCommands.instance
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_ignorecase_and_smartcase_affect_search_regex
|
|
16
|
+
regex = @gc.send(:compile_search_regex, "abc", editor: @editor, window: @win, buffer: @buf)
|
|
17
|
+
refute regex.match?("ABC")
|
|
18
|
+
|
|
19
|
+
@editor.set_option("ignorecase", true, scope: :global)
|
|
20
|
+
regex = @gc.send(:compile_search_regex, "abc", editor: @editor, window: @win, buffer: @buf)
|
|
21
|
+
assert regex.match?("ABC")
|
|
22
|
+
|
|
23
|
+
@editor.set_option("smartcase", true, scope: :global)
|
|
24
|
+
regex = @gc.send(:compile_search_regex, "Abc", editor: @editor, window: @win, buffer: @buf)
|
|
25
|
+
refute regex.match?("abc")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_hlsearch_option_controls_search_highlight
|
|
29
|
+
screen = RuVim::Screen.new(terminal: TerminalStub.new([10, 40]))
|
|
30
|
+
@editor.set_last_search(pattern: "foo", direction: :forward)
|
|
31
|
+
|
|
32
|
+
cols = screen.send(:search_highlight_source_cols, @editor, "foo bar", source_col_offset: 0)
|
|
33
|
+
assert_equal true, cols[0]
|
|
34
|
+
|
|
35
|
+
@editor.set_option("hlsearch", false, scope: :global)
|
|
36
|
+
cols = screen.send(:search_highlight_source_cols, @editor, "foo bar", source_col_offset: 0)
|
|
37
|
+
assert_equal({}, cols)
|
|
38
|
+
end
|
|
39
|
+
end
|
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
|
2
|
+
require "minitest/autorun"
|
|
3
|
+
require "ruvim"
|
|
4
|
+
|
|
5
|
+
module RuVimTestHelpers
|
|
6
|
+
def fresh_editor
|
|
7
|
+
editor = RuVim::Editor.new
|
|
8
|
+
editor.ensure_bootstrap_buffer!
|
|
9
|
+
editor
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class Minitest::Test
|
|
14
|
+
include RuVimTestHelpers
|
|
15
|
+
end
|