ruvim 0.4.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.
- checksums.yaml +4 -4
- data/AGENTS.md +53 -4
- data/README.md +15 -6
- data/Rakefile +7 -0
- data/benchmark/cext_compare.rb +165 -0
- data/benchmark/chunked_load.rb +256 -0
- data/benchmark/file_load.rb +140 -0
- data/benchmark/hotspots.rb +178 -0
- data/docs/binding.md +3 -2
- data/docs/command.md +81 -9
- data/docs/done.md +23 -0
- data/docs/spec.md +105 -19
- data/docs/todo.md +9 -0
- data/docs/tutorial.md +9 -1
- data/docs/vim_diff.md +13 -0
- data/ext/ruvim/extconf.rb +5 -0
- data/ext/ruvim/ruvim_ext.c +519 -0
- data/lib/ruvim/app.rb +217 -2778
- data/lib/ruvim/browser.rb +104 -0
- data/lib/ruvim/buffer.rb +39 -28
- data/lib/ruvim/command_invocation.rb +2 -2
- data/lib/ruvim/completion_manager.rb +708 -0
- data/lib/ruvim/dispatcher.rb +14 -8
- data/lib/ruvim/display_width.rb +91 -45
- data/lib/ruvim/editor.rb +64 -81
- data/lib/ruvim/ex_command_registry.rb +3 -1
- data/lib/ruvim/gh/link.rb +207 -0
- data/lib/ruvim/git/blame.rb +16 -6
- data/lib/ruvim/git/branch.rb +20 -5
- data/lib/ruvim/git/grep.rb +107 -0
- data/lib/ruvim/git/handler.rb +42 -1
- data/lib/ruvim/global_commands.rb +175 -35
- data/lib/ruvim/highlighter.rb +4 -13
- data/lib/ruvim/key_handler.rb +1510 -0
- data/lib/ruvim/keymap_manager.rb +7 -7
- data/lib/ruvim/lang/base.rb +5 -0
- data/lib/ruvim/lang/c.rb +116 -0
- data/lib/ruvim/lang/cpp.rb +107 -0
- data/lib/ruvim/lang/csv.rb +4 -1
- data/lib/ruvim/lang/diff.rb +2 -0
- data/lib/ruvim/lang/dockerfile.rb +36 -0
- data/lib/ruvim/lang/elixir.rb +85 -0
- data/lib/ruvim/lang/erb.rb +30 -0
- data/lib/ruvim/lang/go.rb +83 -0
- data/lib/ruvim/lang/html.rb +34 -0
- data/lib/ruvim/lang/javascript.rb +83 -0
- data/lib/ruvim/lang/json.rb +6 -0
- data/lib/ruvim/lang/lua.rb +76 -0
- data/lib/ruvim/lang/makefile.rb +36 -0
- data/lib/ruvim/lang/markdown.rb +3 -4
- data/lib/ruvim/lang/ocaml.rb +77 -0
- data/lib/ruvim/lang/perl.rb +91 -0
- data/lib/ruvim/lang/python.rb +85 -0
- data/lib/ruvim/lang/registry.rb +102 -0
- data/lib/ruvim/lang/ruby.rb +7 -0
- data/lib/ruvim/lang/rust.rb +95 -0
- data/lib/ruvim/lang/scheme.rb +5 -0
- data/lib/ruvim/lang/sh.rb +76 -0
- data/lib/ruvim/lang/sql.rb +52 -0
- data/lib/ruvim/lang/toml.rb +36 -0
- data/lib/ruvim/lang/tsv.rb +4 -1
- data/lib/ruvim/lang/typescript.rb +53 -0
- data/lib/ruvim/lang/yaml.rb +62 -0
- data/lib/ruvim/rich_view/table_renderer.rb +3 -3
- data/lib/ruvim/rich_view.rb +14 -7
- data/lib/ruvim/screen.rb +126 -72
- data/lib/ruvim/stream/file_load.rb +85 -0
- data/lib/ruvim/stream/follow.rb +40 -0
- data/lib/ruvim/stream/git.rb +43 -0
- data/lib/ruvim/stream/run.rb +74 -0
- data/lib/ruvim/stream/stdin.rb +55 -0
- data/lib/ruvim/stream.rb +35 -0
- data/lib/ruvim/stream_mixer.rb +394 -0
- data/lib/ruvim/terminal.rb +18 -4
- data/lib/ruvim/text_metrics.rb +84 -65
- data/lib/ruvim/version.rb +1 -1
- data/lib/ruvim/window.rb +5 -5
- data/lib/ruvim.rb +23 -6
- data/test/app_command_test.rb +382 -0
- data/test/app_completion_test.rb +43 -19
- data/test/app_dot_repeat_test.rb +27 -3
- data/test/app_ex_command_test.rb +154 -0
- data/test/app_motion_test.rb +13 -12
- data/test/app_register_test.rb +2 -1
- data/test/app_scenario_test.rb +15 -10
- data/test/app_startup_test.rb +70 -27
- data/test/app_text_object_test.rb +2 -1
- data/test/app_unicode_behavior_test.rb +3 -2
- data/test/browser_test.rb +88 -0
- data/test/buffer_test.rb +24 -0
- data/test/cli_test.rb +63 -0
- data/test/command_invocation_test.rb +33 -0
- data/test/config_dsl_test.rb +47 -0
- data/test/dispatcher_test.rb +74 -4
- data/test/ex_command_registry_test.rb +106 -0
- data/test/follow_test.rb +20 -21
- data/test/gh_link_test.rb +141 -0
- data/test/git_blame_test.rb +96 -17
- data/test/git_grep_test.rb +64 -0
- data/test/highlighter_test.rb +125 -0
- data/test/indent_test.rb +137 -0
- data/test/input_screen_integration_test.rb +1 -1
- data/test/keyword_chars_test.rb +85 -0
- data/test/lang_test.rb +634 -0
- data/test/markdown_renderer_test.rb +5 -5
- data/test/on_save_hook_test.rb +12 -8
- data/test/render_snapshot_test.rb +78 -0
- data/test/rich_view_test.rb +42 -42
- data/test/run_command_test.rb +307 -0
- data/test/screen_test.rb +68 -5
- data/test/stream_test.rb +165 -0
- data/test/window_test.rb +59 -0
- metadata +52 -2
data/test/cli_test.rb
CHANGED
|
@@ -176,4 +176,67 @@ class CLITest < Minitest::Test
|
|
|
176
176
|
assert_equal 2, code
|
|
177
177
|
assert_match(/config file not found/, err.string)
|
|
178
178
|
end
|
|
179
|
+
|
|
180
|
+
def test_parse_double_dash_stops_options
|
|
181
|
+
opts = RuVim::CLI.parse(["--", "-R", "--help"])
|
|
182
|
+
assert_equal ["-R", "--help"], opts.files
|
|
183
|
+
assert_equal false, opts.readonly
|
|
184
|
+
assert_equal false, opts.show_help
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def test_parse_unknown_option_raises
|
|
188
|
+
assert_raises(RuVim::CLI::ParseError) do
|
|
189
|
+
RuVim::CLI.parse(["--unknown-flag"])
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def test_parse_startuptime_missing_arg_raises
|
|
194
|
+
assert_raises(RuVim::CLI::ParseError) do
|
|
195
|
+
RuVim::CLI.parse(["--startuptime"])
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def test_parse_cmd_missing_arg_raises
|
|
200
|
+
assert_raises(RuVim::CLI::ParseError) do
|
|
201
|
+
RuVim::CLI.parse(["--cmd"])
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def test_parse_q_missing_arg_raises
|
|
206
|
+
assert_raises(RuVim::CLI::ParseError) do
|
|
207
|
+
RuVim::CLI.parse(["-q"])
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def test_parse_u_missing_arg_raises
|
|
212
|
+
assert_raises(RuVim::CLI::ParseError) do
|
|
213
|
+
RuVim::CLI.parse(["-u"])
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def test_parse_c_missing_arg_raises
|
|
218
|
+
assert_raises(RuVim::CLI::ParseError) do
|
|
219
|
+
RuVim::CLI.parse(["-c"])
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def test_parse_combined_u_value
|
|
224
|
+
opts = RuVim::CLI.parse(["-u/tmp/myrc.rb"])
|
|
225
|
+
assert_equal "/tmp/myrc.rb", opts.config_path
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def test_parse_verbose_without_level
|
|
229
|
+
opts = RuVim::CLI.parse(["--verbose"])
|
|
230
|
+
assert_equal 1, opts.verbose_level
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def test_parse_multiple_files
|
|
234
|
+
opts = RuVim::CLI.parse(["a.txt", "b.txt", "c.txt"])
|
|
235
|
+
assert_equal ["a.txt", "b.txt", "c.txt"], opts.files
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def test_parse_plus_ex_command
|
|
239
|
+
opts = RuVim::CLI.parse(["+set number", "file.txt"])
|
|
240
|
+
assert_equal [{ type: :ex, value: "set number" }], opts.startup_actions
|
|
241
|
+
end
|
|
179
242
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require_relative "test_helper"
|
|
2
|
+
|
|
3
|
+
class CommandInvocationTest < Minitest::Test
|
|
4
|
+
def test_bang_defaults_to_false
|
|
5
|
+
inv = RuVim::CommandInvocation.new(id: "test")
|
|
6
|
+
assert_equal false, inv.bang
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def test_bang_true
|
|
10
|
+
inv = RuVim::CommandInvocation.new(id: "test", bang: true)
|
|
11
|
+
assert_equal true, inv.bang
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_bang_false
|
|
15
|
+
inv = RuVim::CommandInvocation.new(id: "test", bang: false)
|
|
16
|
+
assert_equal false, inv.bang
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_argv_defaults_to_empty_array
|
|
20
|
+
inv = RuVim::CommandInvocation.new(id: "test")
|
|
21
|
+
assert_equal [], inv.argv
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_kwargs_defaults_to_empty_hash
|
|
25
|
+
inv = RuVim::CommandInvocation.new(id: "test")
|
|
26
|
+
assert_equal({}, inv.kwargs)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_count_defaults_to_nil
|
|
30
|
+
inv = RuVim::CommandInvocation.new(id: "test")
|
|
31
|
+
assert_nil inv.count
|
|
32
|
+
end
|
|
33
|
+
end
|
data/test/config_dsl_test.rb
CHANGED
|
@@ -162,4 +162,51 @@ class ConfigDSLTest < Minitest::Test
|
|
|
162
162
|
dsl.set("tabstop=4")
|
|
163
163
|
assert_equal 4, editor.get_option("tabstop")
|
|
164
164
|
end
|
|
165
|
+
|
|
166
|
+
def test_ex_command_registers_and_allows_re_register
|
|
167
|
+
dsl = RuVim::ConfigDSL.new(
|
|
168
|
+
command_registry: @command_registry,
|
|
169
|
+
ex_registry: RuVim::ExCommandRegistry.instance,
|
|
170
|
+
keymaps: @keymaps,
|
|
171
|
+
command_host: RuVim::GlobalCommands.instance
|
|
172
|
+
)
|
|
173
|
+
saved_specs = RuVim::ExCommandRegistry.instance.instance_variable_get(:@specs).dup
|
|
174
|
+
saved_lookup = RuVim::ExCommandRegistry.instance.instance_variable_get(:@lookup).dup
|
|
175
|
+
|
|
176
|
+
dsl.ex_command("testusercmd", desc: "test") { |_ctx, **| }
|
|
177
|
+
assert RuVim::ExCommandRegistry.instance.registered?("testusercmd")
|
|
178
|
+
|
|
179
|
+
# Re-registering should not raise (unregisters first)
|
|
180
|
+
dsl.ex_command("testusercmd", desc: "test v2") { |_ctx, **| }
|
|
181
|
+
assert RuVim::ExCommandRegistry.instance.registered?("testusercmd")
|
|
182
|
+
ensure
|
|
183
|
+
RuVim::ExCommandRegistry.instance.instance_variable_set(:@specs, saved_specs)
|
|
184
|
+
RuVim::ExCommandRegistry.instance.instance_variable_set(:@lookup, saved_lookup)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def test_setlocal_sets_local_scope
|
|
188
|
+
editor = fresh_editor
|
|
189
|
+
dsl = RuVim::ConfigDSL.new(
|
|
190
|
+
command_registry: @command_registry,
|
|
191
|
+
ex_registry: @ex_registry,
|
|
192
|
+
keymaps: @keymaps,
|
|
193
|
+
command_host: RuVim::GlobalCommands.instance,
|
|
194
|
+
editor: editor
|
|
195
|
+
)
|
|
196
|
+
dsl.setlocal("number")
|
|
197
|
+
assert editor.get_option("number")
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def test_setglobal_sets_global_scope
|
|
201
|
+
editor = fresh_editor
|
|
202
|
+
dsl = RuVim::ConfigDSL.new(
|
|
203
|
+
command_registry: @command_registry,
|
|
204
|
+
ex_registry: @ex_registry,
|
|
205
|
+
keymaps: @keymaps,
|
|
206
|
+
command_host: RuVim::GlobalCommands.instance,
|
|
207
|
+
editor: editor
|
|
208
|
+
)
|
|
209
|
+
dsl.setglobal("ignorecase")
|
|
210
|
+
assert editor.get_option("ignorecase")
|
|
211
|
+
end
|
|
165
212
|
end
|
data/test/dispatcher_test.rb
CHANGED
|
@@ -119,19 +119,89 @@ class DispatcherTest < Minitest::Test
|
|
|
119
119
|
assert_includes body, "42"
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
-
def
|
|
122
|
+
def test_dispatch_ex_shell_uses_shell_executor_when_available
|
|
123
|
+
executed_command = nil
|
|
124
|
+
fake_status = Struct.new(:exitstatus).new(0)
|
|
125
|
+
@editor.shell_executor = ->(cmd) { executed_command = cmd; fake_status }
|
|
126
|
+
|
|
127
|
+
@dispatcher.dispatch_ex(@editor, "!echo hello")
|
|
128
|
+
|
|
129
|
+
assert_equal "echo hello", executed_command
|
|
130
|
+
assert_equal "shell exit 0", @editor.message
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def test_dispatch_ex_shell_falls_back_to_capture_without_executor
|
|
134
|
+
@editor.shell_executor = nil
|
|
123
135
|
@dispatcher.dispatch_ex(@editor, "!echo out; echo err 1>&2")
|
|
124
136
|
|
|
125
137
|
assert_equal "[Shell Output]", @editor.message
|
|
126
138
|
assert_equal :help, @editor.current_buffer.kind
|
|
127
139
|
body = @editor.current_buffer.lines.join("\n")
|
|
128
|
-
assert_includes body, "[command]"
|
|
129
|
-
assert_includes body, "echo out; echo err 1>&2"
|
|
130
140
|
assert_includes body, "[stdout]"
|
|
131
141
|
assert_includes body, "out"
|
|
132
142
|
assert_includes body, "[stderr]"
|
|
133
143
|
assert_includes body, "err"
|
|
134
|
-
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def test_dispatch_ex_read_file_inserts_after_cursor_line
|
|
147
|
+
@editor.materialize_intro_buffer!
|
|
148
|
+
Dir.mktmpdir do |dir|
|
|
149
|
+
path = File.join(dir, "data.txt")
|
|
150
|
+
File.write(path, "aaa\nbbb\n")
|
|
151
|
+
@editor.current_buffer.replace_all_lines!(%w[line1 line2 line3])
|
|
152
|
+
@editor.current_window.cursor_y = 1 # on line2
|
|
153
|
+
|
|
154
|
+
@dispatcher.dispatch_ex(@editor, "r #{path}")
|
|
155
|
+
|
|
156
|
+
assert_equal %w[line1 line2 aaa bbb line3], @editor.current_buffer.lines
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def test_dispatch_ex_read_with_range_inserts_after_specified_line
|
|
161
|
+
@editor.materialize_intro_buffer!
|
|
162
|
+
Dir.mktmpdir do |dir|
|
|
163
|
+
path = File.join(dir, "data.txt")
|
|
164
|
+
File.write(path, "inserted\n")
|
|
165
|
+
@editor.current_buffer.replace_all_lines!(%w[line1 line2 line3])
|
|
166
|
+
|
|
167
|
+
@dispatcher.dispatch_ex(@editor, "2r #{path}")
|
|
168
|
+
|
|
169
|
+
assert_equal %w[line1 line2 inserted line3], @editor.current_buffer.lines
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def test_dispatch_ex_read_shell_command_inserts_output
|
|
174
|
+
@editor.materialize_intro_buffer!
|
|
175
|
+
@editor.current_buffer.replace_all_lines!(%w[line1 line2])
|
|
176
|
+
@editor.current_window.cursor_y = 0
|
|
177
|
+
|
|
178
|
+
@dispatcher.dispatch_ex(@editor, "r !echo hello")
|
|
179
|
+
|
|
180
|
+
assert_equal %w[line1 hello line2], @editor.current_buffer.lines
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def test_dispatch_ex_write_to_shell_command
|
|
184
|
+
@editor.materialize_intro_buffer!
|
|
185
|
+
@editor.current_buffer.replace_all_lines!(%w[hello world])
|
|
186
|
+
|
|
187
|
+
Dir.mktmpdir do |dir|
|
|
188
|
+
outfile = File.join(dir, "out.txt")
|
|
189
|
+
@dispatcher.dispatch_ex(@editor, "w !cat > #{outfile}")
|
|
190
|
+
|
|
191
|
+
assert_equal "hello\nworld\n", File.read(outfile)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def test_dispatch_ex_write_range_to_shell_command
|
|
196
|
+
@editor.materialize_intro_buffer!
|
|
197
|
+
@editor.current_buffer.replace_all_lines!(%w[aaa bbb ccc])
|
|
198
|
+
|
|
199
|
+
Dir.mktmpdir do |dir|
|
|
200
|
+
outfile = File.join(dir, "out.txt")
|
|
201
|
+
@dispatcher.dispatch_ex(@editor, "2,3w !cat > #{outfile}")
|
|
202
|
+
|
|
203
|
+
assert_equal "bbb\nccc\n", File.read(outfile)
|
|
204
|
+
end
|
|
135
205
|
end
|
|
136
206
|
|
|
137
207
|
def test_dispatch_ex_set_commands
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
|
|
5
|
+
class ExCommandRegistryTest < Minitest::Test
|
|
6
|
+
def setup
|
|
7
|
+
@registry = RuVim::ExCommandRegistry.instance
|
|
8
|
+
@saved_specs = @registry.instance_variable_get(:@specs).dup
|
|
9
|
+
@saved_lookup = @registry.instance_variable_get(:@lookup).dup
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def teardown
|
|
13
|
+
@registry.instance_variable_set(:@specs, @saved_specs)
|
|
14
|
+
@registry.instance_variable_set(:@lookup, @saved_lookup)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_register_and_resolve
|
|
18
|
+
@registry.clear!
|
|
19
|
+
spec = @registry.register("testcmd", call: ->(_ctx) {}, desc: "test")
|
|
20
|
+
assert_equal "testcmd", spec.name
|
|
21
|
+
|
|
22
|
+
resolved = @registry.resolve("testcmd")
|
|
23
|
+
assert_equal spec, resolved
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_register_with_aliases
|
|
27
|
+
@registry.clear!
|
|
28
|
+
spec = @registry.register("testcmd", call: ->(_ctx) {}, aliases: ["tc", "tcmd"])
|
|
29
|
+
|
|
30
|
+
assert_equal @registry.resolve("tc"), spec
|
|
31
|
+
assert_equal @registry.resolve("tcmd"), spec
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_register_duplicate_raises
|
|
35
|
+
@registry.clear!
|
|
36
|
+
@registry.register("testcmd", call: ->(_ctx) {})
|
|
37
|
+
|
|
38
|
+
assert_raises(RuVim::CommandError) do
|
|
39
|
+
@registry.register("testcmd", call: ->(_ctx) {})
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def test_register_alias_collision_raises
|
|
44
|
+
@registry.clear!
|
|
45
|
+
@registry.register("cmd1", call: ->(_ctx) {}, aliases: ["shared"])
|
|
46
|
+
|
|
47
|
+
assert_raises(RuVim::CommandError) do
|
|
48
|
+
@registry.register("cmd2", call: ->(_ctx) {}, aliases: ["shared"])
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_resolve_returns_nil_for_unknown
|
|
53
|
+
@registry.clear!
|
|
54
|
+
assert_nil @registry.resolve("nonexistent")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_fetch_raises_for_unknown
|
|
58
|
+
@registry.clear!
|
|
59
|
+
assert_raises(RuVim::CommandError) do
|
|
60
|
+
@registry.fetch("nonexistent")
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_fetch_returns_spec
|
|
65
|
+
@registry.clear!
|
|
66
|
+
spec = @registry.register("testcmd", call: ->(_ctx) {})
|
|
67
|
+
assert_equal spec, @registry.fetch("testcmd")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_registered?
|
|
71
|
+
@registry.clear!
|
|
72
|
+
refute @registry.registered?("testcmd")
|
|
73
|
+
@registry.register("testcmd", call: ->(_ctx) {})
|
|
74
|
+
assert @registry.registered?("testcmd")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def test_all_returns_sorted
|
|
78
|
+
@registry.clear!
|
|
79
|
+
@registry.register("beta", call: ->(_ctx) {})
|
|
80
|
+
@registry.register("alpha", call: ->(_ctx) {})
|
|
81
|
+
names = @registry.all.map(&:name)
|
|
82
|
+
assert_equal ["alpha", "beta"], names
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def test_unregister
|
|
86
|
+
@registry.clear!
|
|
87
|
+
@registry.register("testcmd", call: ->(_ctx) {}, aliases: ["tc"])
|
|
88
|
+
|
|
89
|
+
removed = @registry.unregister("testcmd")
|
|
90
|
+
assert_equal "testcmd", removed.name
|
|
91
|
+
assert_nil @registry.resolve("testcmd")
|
|
92
|
+
assert_nil @registry.resolve("tc")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def test_unregister_unknown_returns_nil
|
|
96
|
+
@registry.clear!
|
|
97
|
+
assert_nil @registry.unregister("nonexistent")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_clear!
|
|
101
|
+
@registry.clear!
|
|
102
|
+
@registry.register("testcmd", call: ->(_ctx) {})
|
|
103
|
+
@registry.clear!
|
|
104
|
+
assert_equal [], @registry.all
|
|
105
|
+
end
|
|
106
|
+
end
|
data/test/follow_test.rb
CHANGED
|
@@ -13,26 +13,29 @@ class FollowTest < Minitest::Test
|
|
|
13
13
|
@app = RuVim::App.new(path: @path, clean: true)
|
|
14
14
|
@editor = @app.instance_variable_get(:@editor)
|
|
15
15
|
@dispatcher = @app.instance_variable_get(:@dispatcher)
|
|
16
|
+
@key_handler = @app.instance_variable_get(:@key_handler)
|
|
17
|
+
@stream_mixer = @app.instance_variable_get(:@stream_mixer)
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
def cleanup_follow_app
|
|
19
21
|
return unless @app
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
editor = @app&.instance_variable_get(:@editor)
|
|
24
|
+
return unless editor
|
|
25
|
+
editor.buffers.each_value do |buf|
|
|
26
|
+
buf.stream&.stop! rescue nil
|
|
27
|
+
end
|
|
24
28
|
@tmpfile&.close!
|
|
25
29
|
end
|
|
26
30
|
|
|
27
31
|
def test_follow_starts_on_file_buffer
|
|
28
32
|
create_follow_app
|
|
29
33
|
@dispatcher.dispatch_ex(@editor, "follow")
|
|
30
|
-
watchers = @app.instance_variable_get(:@follow_watchers)
|
|
31
34
|
buf = @editor.current_buffer
|
|
32
35
|
|
|
33
36
|
assert !@editor.message_error?, "Unexpected error: #{@editor.message}"
|
|
34
|
-
assert_equal :live, buf.
|
|
35
|
-
assert
|
|
37
|
+
assert_equal :live, buf.stream.state
|
|
38
|
+
assert buf.stream.watcher
|
|
36
39
|
assert_includes @editor.message.to_s, "[follow]"
|
|
37
40
|
ensure
|
|
38
41
|
cleanup_follow_app
|
|
@@ -42,10 +45,10 @@ class FollowTest < Minitest::Test
|
|
|
42
45
|
create_follow_app
|
|
43
46
|
@dispatcher.dispatch_ex(@editor, "follow")
|
|
44
47
|
buf = @editor.current_buffer
|
|
45
|
-
assert_equal :live, buf.
|
|
48
|
+
assert_equal :live, buf.stream.state
|
|
46
49
|
|
|
47
|
-
@
|
|
48
|
-
assert_nil buf.
|
|
50
|
+
@key_handler.handle(:ctrl_c)
|
|
51
|
+
assert_nil buf.stream
|
|
49
52
|
assert_includes @editor.message.to_s, "stopped"
|
|
50
53
|
ensure
|
|
51
54
|
cleanup_follow_app
|
|
@@ -55,12 +58,10 @@ class FollowTest < Minitest::Test
|
|
|
55
58
|
create_follow_app
|
|
56
59
|
@dispatcher.dispatch_ex(@editor, "follow")
|
|
57
60
|
buf = @editor.current_buffer
|
|
58
|
-
assert_equal :live, buf.
|
|
61
|
+
assert_equal :live, buf.stream.state
|
|
59
62
|
|
|
60
63
|
@dispatcher.dispatch_ex(@editor, "follow")
|
|
61
|
-
|
|
62
|
-
assert_nil buf.stream_state
|
|
63
|
-
refute watchers.key?(buf.id)
|
|
64
|
+
assert_nil buf.stream
|
|
64
65
|
assert_includes @editor.message.to_s, "stopped"
|
|
65
66
|
ensure
|
|
66
67
|
cleanup_follow_app
|
|
@@ -101,7 +102,7 @@ class FollowTest < Minitest::Test
|
|
|
101
102
|
|
|
102
103
|
@dispatcher.dispatch_ex(@editor, "follow")
|
|
103
104
|
assert_includes @editor.message.to_s, "unsaved changes"
|
|
104
|
-
assert_nil buf.
|
|
105
|
+
assert_nil buf.stream
|
|
105
106
|
ensure
|
|
106
107
|
cleanup_follow_app
|
|
107
108
|
end
|
|
@@ -129,7 +130,7 @@ class FollowTest < Minitest::Test
|
|
|
129
130
|
File.open(@path, "a") { |f| f.write("line3\nline4\n") }
|
|
130
131
|
|
|
131
132
|
assert_eventually(timeout: 3) do
|
|
132
|
-
@
|
|
133
|
+
@stream_mixer.drain_events!
|
|
133
134
|
buf.line_count > 3
|
|
134
135
|
end
|
|
135
136
|
|
|
@@ -153,7 +154,7 @@ class FollowTest < Minitest::Test
|
|
|
153
154
|
File.open(@path, "a") { |f| f.write("line3\n") }
|
|
154
155
|
|
|
155
156
|
assert_eventually(timeout: 3) do
|
|
156
|
-
@
|
|
157
|
+
@stream_mixer.drain_events!
|
|
157
158
|
buf.line_count > 2
|
|
158
159
|
end
|
|
159
160
|
|
|
@@ -170,16 +171,14 @@ class FollowTest < Minitest::Test
|
|
|
170
171
|
|
|
171
172
|
app = RuVim::App.new(paths: [tmp1.path, tmp2.path], follow: true, clean: true)
|
|
172
173
|
editor = app.instance_variable_get(:@editor)
|
|
173
|
-
watchers = app.instance_variable_get(:@follow_watchers)
|
|
174
|
-
|
|
175
174
|
bufs = editor.buffers.values.select(&:file_buffer?)
|
|
176
175
|
assert_equal 2, bufs.size
|
|
177
176
|
bufs.each do |buf|
|
|
178
|
-
assert_equal :live, buf.
|
|
179
|
-
assert
|
|
177
|
+
assert_equal :live, buf.stream.state, "#{buf.display_name} should be in follow mode"
|
|
178
|
+
assert buf.stream.watcher, "#{buf.display_name} should have a watcher"
|
|
180
179
|
end
|
|
181
180
|
ensure
|
|
182
|
-
|
|
181
|
+
bufs&.each { |b| b.stream&.stop! rescue nil }
|
|
183
182
|
tmp1&.close!
|
|
184
183
|
tmp2&.close!
|
|
185
184
|
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
|
|
5
|
+
class GhLinkTest < 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
|
+
@editor.materialize_intro_buffer!
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# --- URL parsing ---
|
|
14
|
+
|
|
15
|
+
def test_parse_ssh_remote
|
|
16
|
+
url = RuVim::Gh::Link.github_url_from_remote("git@github.com:user/repo.git")
|
|
17
|
+
assert_equal "https://github.com/user/repo", url
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_parse_ssh_remote_without_dot_git
|
|
21
|
+
url = RuVim::Gh::Link.github_url_from_remote("git@github.com:user/repo")
|
|
22
|
+
assert_equal "https://github.com/user/repo", url
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_parse_https_remote
|
|
26
|
+
url = RuVim::Gh::Link.github_url_from_remote("https://github.com/user/repo.git")
|
|
27
|
+
assert_equal "https://github.com/user/repo", url
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_parse_https_remote_without_dot_git
|
|
31
|
+
url = RuVim::Gh::Link.github_url_from_remote("https://github.com/user/repo")
|
|
32
|
+
assert_equal "https://github.com/user/repo", url
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_parse_non_github_remote_returns_nil
|
|
36
|
+
url = RuVim::Gh::Link.github_url_from_remote("git@gitlab.com:user/repo.git")
|
|
37
|
+
assert_nil url
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_parse_empty_remote_returns_nil
|
|
41
|
+
url = RuVim::Gh::Link.github_url_from_remote("")
|
|
42
|
+
assert_nil url
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# --- Link building ---
|
|
46
|
+
|
|
47
|
+
def test_build_link_single_line
|
|
48
|
+
link = RuVim::Gh::Link.build_url("https://github.com/user/repo", "main", "lib/foo.rb", 10)
|
|
49
|
+
assert_equal "https://github.com/user/repo/blob/main/lib/foo.rb#L10", link
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_build_link_line_range
|
|
53
|
+
link = RuVim::Gh::Link.build_url("https://github.com/user/repo", "main", "lib/foo.rb", 10, 20)
|
|
54
|
+
assert_equal "https://github.com/user/repo/blob/main/lib/foo.rb#L10-L20", link
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_build_link_same_start_end
|
|
58
|
+
link = RuVim::Gh::Link.build_url("https://github.com/user/repo", "main", "lib/foo.rb", 5, 5)
|
|
59
|
+
assert_equal "https://github.com/user/repo/blob/main/lib/foo.rb#L5", link
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# --- OSC 52 ---
|
|
63
|
+
|
|
64
|
+
def test_osc52_escape_sequence
|
|
65
|
+
seq = RuVim::Gh::Link.osc52_copy_sequence("hello")
|
|
66
|
+
expected = "\e]52;c;#{["hello"].pack("m0")}\a"
|
|
67
|
+
assert_equal expected, seq
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# --- Remote detection ---
|
|
71
|
+
|
|
72
|
+
def test_find_github_remote_with_specific_name
|
|
73
|
+
# This test runs in the actual ruvim repo
|
|
74
|
+
name, url = RuVim::Gh::Link.find_github_remote(Dir.pwd, "origin")
|
|
75
|
+
if url
|
|
76
|
+
assert_equal "origin", name
|
|
77
|
+
assert_match(%r{\Ahttps://github\.com/}, url)
|
|
78
|
+
else
|
|
79
|
+
skip "origin is not a GitHub remote in this repo"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def test_find_github_remote_auto_detect
|
|
84
|
+
name, url = RuVim::Gh::Link.find_github_remote(Dir.pwd)
|
|
85
|
+
if url
|
|
86
|
+
assert_kind_of String, name
|
|
87
|
+
assert_match(%r{\Ahttps://github\.com/}, url)
|
|
88
|
+
else
|
|
89
|
+
skip "No GitHub remote in this repo"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def test_find_github_remote_nonexistent_returns_nil
|
|
94
|
+
name, url = RuVim::Gh::Link.find_github_remote(Dir.pwd, "nonexistent_remote_xyz")
|
|
95
|
+
assert_nil name
|
|
96
|
+
assert_nil url
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# --- Resolve warning ---
|
|
100
|
+
|
|
101
|
+
def test_resolve_returns_warning_when_file_differs
|
|
102
|
+
# file_differs_from_remote? returns true for non-existent remote ref
|
|
103
|
+
assert RuVim::Gh::Link.file_differs_from_remote?(Dir.pwd, "nonexistent_remote_xyz", "main", __FILE__)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# --- Ex command integration ---
|
|
107
|
+
|
|
108
|
+
# --- PR URL ---
|
|
109
|
+
|
|
110
|
+
def test_pr_search_url
|
|
111
|
+
url = RuVim::Gh::Link.pr_search_url("https://github.com/user/repo", "feature-branch")
|
|
112
|
+
assert_equal "https://github.com/user/repo/pulls?q=head:feature-branch", url
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# --- Ex command integration ---
|
|
116
|
+
|
|
117
|
+
def test_gh_browse_listed_in_subcommands
|
|
118
|
+
@dispatcher.dispatch_ex(@editor, "gh")
|
|
119
|
+
assert_match(/browse/, @editor.message)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def test_gh_pr_listed_in_subcommands
|
|
123
|
+
@dispatcher.dispatch_ex(@editor, "gh")
|
|
124
|
+
assert_match(/pr/, @editor.message)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def test_gh_no_subcommand_shows_help
|
|
128
|
+
@dispatcher.dispatch_ex(@editor, "gh")
|
|
129
|
+
assert_match(/link/, @editor.message)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def test_gh_unknown_subcommand_runs_shell
|
|
133
|
+
executed = nil
|
|
134
|
+
fake_status = Struct.new(:exitstatus).new(0)
|
|
135
|
+
@editor.shell_executor = ->(cmd) { executed = cmd; fake_status }
|
|
136
|
+
|
|
137
|
+
@dispatcher.dispatch_ex(@editor, "gh issue list")
|
|
138
|
+
|
|
139
|
+
assert_equal "gh issue list", executed
|
|
140
|
+
end
|
|
141
|
+
end
|