ruvim 0.2.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 +23 -0
- data/docs/command.md +85 -0
- data/docs/config.md +2 -2
- data/docs/done.md +21 -0
- data/docs/spec.md +157 -12
- data/docs/todo.md +1 -5
- data/docs/vim_diff.md +94 -172
- data/lib/ruvim/app.rb +882 -69
- 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 +2 -0
- data/lib/ruvim/context.rb +2 -0
- data/lib/ruvim/dispatcher.rb +143 -13
- data/lib/ruvim/display_width.rb +3 -0
- data/lib/ruvim/editor.rb +455 -71
- data/lib/ruvim/ex_command_registry.rb +2 -0
- data/lib/ruvim/global_commands.rb +890 -63
- data/lib/ruvim/highlighter.rb +16 -21
- data/lib/ruvim/input.rb +39 -28
- data/lib/ruvim/keymap_manager.rb +83 -0
- data/lib/ruvim/keyword_chars.rb +2 -0
- data/lib/ruvim/lang/base.rb +25 -0
- data/lib/ruvim/lang/csv.rb +18 -0
- data/lib/ruvim/lang/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 +503 -106
- data/lib/ruvim/terminal.rb +18 -1
- data/lib/ruvim/text_metrics.rb +2 -0
- data/lib/ruvim/version.rb +1 -1
- data/lib/ruvim/window.rb +2 -0
- data/lib/ruvim.rb +14 -0
- data/test/app_completion_test.rb +73 -0
- data/test/app_dot_repeat_test.rb +13 -0
- data/test/app_motion_test.rb +13 -0
- data/test/app_scenario_test.rb +729 -1
- data/test/app_startup_test.rb +187 -0
- data/test/arglist_test.rb +113 -0
- data/test/buffer_test.rb +49 -30
- data/test/dispatcher_test.rb +322 -0
- data/test/editor_register_test.rb +23 -0
- data/test/highlighter_test.rb +121 -0
- data/test/indent_test.rb +201 -0
- data/test/input_screen_integration_test.rb +40 -2
- data/test/markdown_renderer_test.rb +279 -0
- data/test/on_save_hook_test.rb +150 -0
- data/test/rich_view_test.rb +478 -0
- data/test/screen_test.rb +304 -0
- metadata +33 -2
data/lib/ruvim/terminal.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "io/console"
|
|
2
4
|
|
|
3
5
|
module RuVim
|
|
@@ -20,11 +22,26 @@ module RuVim
|
|
|
20
22
|
|
|
21
23
|
def with_ui
|
|
22
24
|
@stdin.raw do
|
|
23
|
-
write("\e[?1049h\e[?25l")
|
|
25
|
+
write("\e]112\a\e[?1049h\e[?25l")
|
|
24
26
|
yield
|
|
25
27
|
ensure
|
|
26
28
|
write("\e[?25h\e[?1049l")
|
|
27
29
|
end
|
|
28
30
|
end
|
|
31
|
+
|
|
32
|
+
def suspend_for_tstp
|
|
33
|
+
prev_tstp = Signal.trap("TSTP", "DEFAULT")
|
|
34
|
+
@stdin.cooked do
|
|
35
|
+
write("\e[?25h\e[?1049l")
|
|
36
|
+
Process.kill("TSTP", 0)
|
|
37
|
+
end
|
|
38
|
+
ensure
|
|
39
|
+
begin
|
|
40
|
+
Signal.trap("TSTP", prev_tstp) if defined?(prev_tstp)
|
|
41
|
+
rescue StandardError
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
write("\e[?1049h\e[?25l")
|
|
45
|
+
end
|
|
29
46
|
end
|
|
30
47
|
end
|
data/lib/ruvim/text_metrics.rb
CHANGED
data/lib/ruvim/version.rb
CHANGED
data/lib/ruvim/window.rb
CHANGED
data/lib/ruvim.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "singleton"
|
|
2
4
|
|
|
3
5
|
module RuVim
|
|
@@ -11,6 +13,11 @@ require_relative "ruvim/display_width"
|
|
|
11
13
|
require_relative "ruvim/keyword_chars"
|
|
12
14
|
require_relative "ruvim/text_metrics"
|
|
13
15
|
require_relative "ruvim/clipboard"
|
|
16
|
+
require_relative "ruvim/lang/base"
|
|
17
|
+
require_relative "ruvim/lang/markdown"
|
|
18
|
+
require_relative "ruvim/lang/ruby"
|
|
19
|
+
require_relative "ruvim/lang/json"
|
|
20
|
+
require_relative "ruvim/lang/scheme"
|
|
14
21
|
require_relative "ruvim/highlighter"
|
|
15
22
|
require_relative "ruvim/context"
|
|
16
23
|
require_relative "ruvim/buffer"
|
|
@@ -24,6 +31,13 @@ require_relative "ruvim/keymap_manager"
|
|
|
24
31
|
require_relative "ruvim/command_line"
|
|
25
32
|
require_relative "ruvim/input"
|
|
26
33
|
require_relative "ruvim/terminal"
|
|
34
|
+
require_relative "ruvim/rich_view"
|
|
35
|
+
|
|
36
|
+
# Register renderers after RichView is defined
|
|
37
|
+
RuVim::RichView.register("markdown", RuVim::RichView::MarkdownRenderer)
|
|
38
|
+
|
|
39
|
+
require_relative "ruvim/lang/tsv"
|
|
40
|
+
require_relative "ruvim/lang/csv"
|
|
27
41
|
require_relative "ruvim/screen"
|
|
28
42
|
require_relative "ruvim/config_dsl"
|
|
29
43
|
require_relative "ruvim/config_loader"
|
data/test/app_completion_test.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require_relative "test_helper"
|
|
2
2
|
require "tmpdir"
|
|
3
|
+
require "fileutils"
|
|
3
4
|
|
|
4
5
|
class AppCompletionTest < Minitest::Test
|
|
5
6
|
def setup
|
|
@@ -51,6 +52,35 @@ class AppCompletionTest < Minitest::Test
|
|
|
51
52
|
end
|
|
52
53
|
end
|
|
53
54
|
|
|
55
|
+
def test_path_completion_returns_relative_match_without_dot_slash_prefix
|
|
56
|
+
Dir.mktmpdir("ruvim-complete-rel") do |dir|
|
|
57
|
+
Dir.chdir(dir) do
|
|
58
|
+
FileUtils.mkdir_p("lib")
|
|
59
|
+
|
|
60
|
+
matches = @app.send(:path_completion_candidates, "li")
|
|
61
|
+
|
|
62
|
+
assert_includes matches, "lib/"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_path_completion_sorts_hidden_entries_after_visible_by_default
|
|
68
|
+
Dir.mktmpdir("ruvim-complete-hidden") do |dir|
|
|
69
|
+
FileUtils.mkdir_p(File.join(dir, ".git"))
|
|
70
|
+
File.write(File.join(dir, "aaa.txt"), "")
|
|
71
|
+
File.write(File.join(dir, "bbb.txt"), "")
|
|
72
|
+
|
|
73
|
+
matches = @app.send(:path_completion_candidates, File.join(dir, ""))
|
|
74
|
+
|
|
75
|
+
assert_includes matches, File.join(dir, ".git/")
|
|
76
|
+
visible_idx = matches.index(File.join(dir, "aaa.txt"))
|
|
77
|
+
hidden_idx = matches.index(File.join(dir, ".git/"))
|
|
78
|
+
refute_nil visible_idx
|
|
79
|
+
refute_nil hidden_idx
|
|
80
|
+
assert_operator visible_idx, :<, hidden_idx
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
54
84
|
def test_command_line_completion_respects_wildmode_list_full_and_wildmenu
|
|
55
85
|
@editor.materialize_intro_buffer!
|
|
56
86
|
@editor.set_option("wildmode", "list,full", scope: :global)
|
|
@@ -103,6 +133,49 @@ class AppCompletionTest < Minitest::Test
|
|
|
103
133
|
end
|
|
104
134
|
end
|
|
105
135
|
|
|
136
|
+
def test_command_line_completion_full_cycles_across_original_match_set
|
|
137
|
+
@editor.materialize_intro_buffer!
|
|
138
|
+
@editor.set_option("wildmode", "full", scope: :global)
|
|
139
|
+
|
|
140
|
+
Dir.mktmpdir("ruvim-wild-full") do |dir|
|
|
141
|
+
FileUtils.mkdir_p(File.join(dir, ".git"))
|
|
142
|
+
a = File.join(dir, "aa.txt")
|
|
143
|
+
b = File.join(dir, "ab.txt")
|
|
144
|
+
File.write(a, "")
|
|
145
|
+
File.write(b, "")
|
|
146
|
+
|
|
147
|
+
@editor.enter_command_line_mode(":")
|
|
148
|
+
cmd = @editor.command_line
|
|
149
|
+
cmd.replace_text("e #{File.join(dir, "a")}")
|
|
150
|
+
|
|
151
|
+
@app.send(:command_line_complete)
|
|
152
|
+
first = cmd.text.dup
|
|
153
|
+
@app.send(:command_line_complete)
|
|
154
|
+
second = cmd.text.dup
|
|
155
|
+
|
|
156
|
+
refute_equal first, second
|
|
157
|
+
assert([a, b].any? { |p| first.end_with?(p) })
|
|
158
|
+
assert([a, b].any? { |p| second.end_with?(p) })
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def test_command_line_completion_menu_is_not_limited_by_pumheight
|
|
163
|
+
@editor.materialize_intro_buffer!
|
|
164
|
+
@editor.set_option("wildmode", "list", scope: :global)
|
|
165
|
+
@editor.set_option("pumheight", 1, scope: :global)
|
|
166
|
+
|
|
167
|
+
@editor.enter_command_line_mode(":")
|
|
168
|
+
cmd = @editor.command_line
|
|
169
|
+
cmd.replace_text("se")
|
|
170
|
+
|
|
171
|
+
@app.send(:command_line_complete)
|
|
172
|
+
|
|
173
|
+
assert_equal "se", cmd.text
|
|
174
|
+
assert_includes @editor.message, "set"
|
|
175
|
+
assert_includes @editor.message, "setlocal"
|
|
176
|
+
assert_includes @editor.message, "setglobal"
|
|
177
|
+
end
|
|
178
|
+
|
|
106
179
|
def test_insert_completion_respects_completeopt_noselect_and_pumheight
|
|
107
180
|
@editor.materialize_intro_buffer!
|
|
108
181
|
@editor.set_option("completeopt", "menu,menuone,noselect", scope: :global)
|
data/test/app_dot_repeat_test.rb
CHANGED
|
@@ -51,4 +51,17 @@ class AppDotRepeatTest < Minitest::Test
|
|
|
51
51
|
|
|
52
52
|
assert_equal "xxcd", @buffer.line_at(0)
|
|
53
53
|
end
|
|
54
|
+
|
|
55
|
+
def test_dot_repeats_substitute_char
|
|
56
|
+
@buffer.replace_all_lines!(["abcd"])
|
|
57
|
+
@win.cursor_x = 0
|
|
58
|
+
|
|
59
|
+
press("s")
|
|
60
|
+
@app.send(:handle_key, "X")
|
|
61
|
+
@app.send(:handle_key, :escape)
|
|
62
|
+
press("l")
|
|
63
|
+
press(".")
|
|
64
|
+
|
|
65
|
+
assert_equal "XbXd", @buffer.line_at(0)
|
|
66
|
+
end
|
|
54
67
|
end
|
data/test/app_motion_test.rb
CHANGED
|
@@ -165,4 +165,17 @@ class AppMotionTest < Minitest::Test
|
|
|
165
165
|
press("h", "h")
|
|
166
166
|
assert_equal 4, @editor.current_window.cursor_x
|
|
167
167
|
end
|
|
168
|
+
|
|
169
|
+
def test_e_moves_to_next_word_end_when_already_on_word_end
|
|
170
|
+
b = @editor.current_buffer
|
|
171
|
+
b.replace_all_lines!(["one two three"])
|
|
172
|
+
@editor.current_window.cursor_y = 0
|
|
173
|
+
@editor.current_window.cursor_x = 2 # end of "one"
|
|
174
|
+
|
|
175
|
+
press("e")
|
|
176
|
+
assert_equal 6, @editor.current_window.cursor_x # end of "two"
|
|
177
|
+
|
|
178
|
+
press("e")
|
|
179
|
+
assert_equal 12, @editor.current_window.cursor_x # end of "three"
|
|
180
|
+
end
|
|
168
181
|
end
|