ruvim 0.3.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 +68 -7
- data/README.md +30 -7
- 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 +18 -1
- data/docs/command.md +156 -10
- data/docs/config.md +10 -2
- data/docs/done.md +23 -0
- data/docs/spec.md +162 -25
- data/docs/todo.md +9 -0
- data/docs/tutorial.md +33 -1
- data/docs/vim_diff.md +31 -8
- data/ext/ruvim/extconf.rb +5 -0
- data/ext/ruvim/ruvim_ext.c +519 -0
- data/lib/ruvim/app.rb +246 -2525
- data/lib/ruvim/browser.rb +104 -0
- data/lib/ruvim/buffer.rb +43 -20
- data/lib/ruvim/cli.rb +6 -0
- 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 +74 -80
- data/lib/ruvim/ex_command_registry.rb +3 -1
- data/lib/ruvim/file_watcher.rb +243 -0
- data/lib/ruvim/gh/link.rb +207 -0
- data/lib/ruvim/git/blame.rb +255 -0
- data/lib/ruvim/git/branch.rb +112 -0
- data/lib/ruvim/git/commit.rb +102 -0
- data/lib/ruvim/git/diff.rb +129 -0
- data/lib/ruvim/git/grep.rb +107 -0
- data/lib/ruvim/git/handler.rb +125 -0
- data/lib/ruvim/git/log.rb +41 -0
- data/lib/ruvim/git/status.rb +103 -0
- data/lib/ruvim/global_commands.rb +351 -77
- data/lib/ruvim/highlighter.rb +4 -11
- data/lib/ruvim/input.rb +1 -0
- 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 +43 -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 +40 -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/json_renderer.rb +131 -0
- data/lib/ruvim/rich_view/jsonl_renderer.rb +57 -0
- data/lib/ruvim/rich_view/table_renderer.rb +3 -3
- data/lib/ruvim/rich_view.rb +30 -7
- data/lib/ruvim/screen.rb +135 -84
- 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 +31 -4
- data/test/app_command_test.rb +382 -0
- data/test/app_completion_test.rb +65 -16
- 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 +182 -8
- 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 +77 -0
- data/test/clipboard_test.rb +67 -0
- data/test/command_invocation_test.rb +33 -0
- data/test/command_line_test.rb +118 -0
- data/test/config_dsl_test.rb +134 -0
- data/test/dispatcher_test.rb +74 -4
- data/test/display_width_test.rb +41 -0
- data/test/ex_command_registry_test.rb +106 -0
- data/test/file_watcher_test.rb +197 -0
- data/test/follow_test.rb +198 -0
- data/test/gh_link_test.rb +141 -0
- data/test/git_blame_test.rb +792 -0
- data/test/git_grep_test.rb +64 -0
- data/test/highlighter_test.rb +169 -0
- data/test/indent_test.rb +223 -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 +279 -23
- data/test/run_command_test.rb +307 -0
- data/test/screen_test.rb +68 -5
- data/test/search_option_test.rb +19 -0
- data/test/stream_test.rb +165 -0
- data/test/test_helper.rb +9 -0
- data/test/window_test.rb +59 -0
- metadata +68 -2
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
5
|
+
module RuVim
|
|
6
|
+
module Browser
|
|
7
|
+
ALLOWED_SCHEMES = %w[https http].freeze
|
|
8
|
+
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def open_url(url)
|
|
12
|
+
return false unless valid_url?(url)
|
|
13
|
+
|
|
14
|
+
backend = detect_backend
|
|
15
|
+
return false unless backend
|
|
16
|
+
|
|
17
|
+
if backend[:type] == :powershell
|
|
18
|
+
cmd = powershell_encoded_command(backend[:ps_path], url)
|
|
19
|
+
_, _, status = Open3.capture3(*cmd)
|
|
20
|
+
else
|
|
21
|
+
_, _, status = Open3.capture3(*backend[:command], url)
|
|
22
|
+
end
|
|
23
|
+
status.success?
|
|
24
|
+
rescue StandardError
|
|
25
|
+
false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def valid_url?(url)
|
|
29
|
+
scheme = url.to_s.split(":", 2).first.to_s.downcase
|
|
30
|
+
ALLOWED_SCHEMES.include?(scheme)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def powershell_encoded_command(ps_path, url)
|
|
34
|
+
# Encode as UTF-16LE Base64 to avoid any command injection via -Command.
|
|
35
|
+
# This matches the approach used by Node.js "open" package.
|
|
36
|
+
ps_script = "Start-Process '#{url.gsub("'", "''")}'"
|
|
37
|
+
encoded = [ps_script.encode("UTF-16LE")].pack("m0")
|
|
38
|
+
[ps_path, "-NoProfile", "-NonInteractive", "-EncodedCommand", encoded]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def detect_backend
|
|
42
|
+
return { command: %w[open], type: :macos } if command_available?("open") && macos?
|
|
43
|
+
return { command: %w[xdg-open], type: :xdg } if command_available?("xdg-open") && !wsl?
|
|
44
|
+
return { command: %w[wslview], type: :wslview } if command_available?("wslview")
|
|
45
|
+
|
|
46
|
+
ps_path = powershell_path(wsl_mount_point)
|
|
47
|
+
if wsl? && File.exist?(ps_path)
|
|
48
|
+
return { ps_path: ps_path, type: :powershell }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def powershell_path(mount_point)
|
|
55
|
+
"#{mount_point}c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def wsl_mount_point(config: nil)
|
|
59
|
+
config ||= begin
|
|
60
|
+
File.read("/etc/wsl.conf")
|
|
61
|
+
rescue StandardError
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
default = "/mnt/"
|
|
66
|
+
return default unless config
|
|
67
|
+
|
|
68
|
+
config.each_line do |line|
|
|
69
|
+
stripped = line.strip
|
|
70
|
+
next if stripped.start_with?("#")
|
|
71
|
+
|
|
72
|
+
if stripped.match?(/\Aroot\s*=/)
|
|
73
|
+
value = stripped.sub(/\Aroot\s*=\s*/, "").strip
|
|
74
|
+
value += "/" unless value.end_with?("/")
|
|
75
|
+
return value
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
default
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def macos?
|
|
83
|
+
RUBY_PLATFORM.include?("darwin")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def wsl?
|
|
87
|
+
return @wsl if defined?(@wsl)
|
|
88
|
+
|
|
89
|
+
@wsl = begin
|
|
90
|
+
File.read("/proc/version").include?("microsoft")
|
|
91
|
+
rescue StandardError
|
|
92
|
+
false
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def reset!
|
|
97
|
+
@wsl = nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def command_available?(name)
|
|
101
|
+
system("which", name, out: File::NULL, err: File::NULL)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
data/lib/ruvim/buffer.rb
CHANGED
|
@@ -1,17 +1,33 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "stream"
|
|
4
|
+
|
|
3
5
|
module RuVim
|
|
4
6
|
class Buffer
|
|
5
7
|
attr_reader :id, :kind, :name
|
|
6
8
|
attr_accessor :path
|
|
7
9
|
attr_reader :options
|
|
8
10
|
attr_writer :modified
|
|
9
|
-
attr_accessor :
|
|
11
|
+
attr_accessor :stream
|
|
12
|
+
|
|
13
|
+
def stream_status
|
|
14
|
+
@stream&.status
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def stream_command
|
|
18
|
+
@stream&.command
|
|
19
|
+
end
|
|
20
|
+
|
|
10
21
|
attr_accessor :lang_module
|
|
11
22
|
|
|
23
|
+
def self.ensure_regular_file!(path)
|
|
24
|
+
raise RuVim::CommandError, "Not a regular file: #{path}" if File.exist?(path) && !File.file?(path)
|
|
25
|
+
end
|
|
26
|
+
|
|
12
27
|
def self.from_file(id:, path:)
|
|
28
|
+
ensure_regular_file!(path)
|
|
13
29
|
lines =
|
|
14
|
-
if File.
|
|
30
|
+
if File.file?(path)
|
|
15
31
|
data = decode_text(File.binread(path))
|
|
16
32
|
split_lines(data)
|
|
17
33
|
else
|
|
@@ -32,34 +48,33 @@ module RuVim
|
|
|
32
48
|
end
|
|
33
49
|
|
|
34
50
|
def self.decode_text(bytes)
|
|
35
|
-
s = bytes.to_s
|
|
36
|
-
return s if s.encoding == Encoding::UTF_8 && s.valid_encoding?
|
|
51
|
+
s = bytes.to_s
|
|
52
|
+
return s.dup if s.encoding == Encoding::UTF_8 && s.valid_encoding?
|
|
37
53
|
|
|
38
54
|
utf8 = s.dup.force_encoding(Encoding::UTF_8)
|
|
39
55
|
return utf8 if utf8.valid_encoding?
|
|
40
56
|
|
|
41
57
|
ext = Encoding.default_external
|
|
42
58
|
if ext && ext != Encoding::UTF_8
|
|
43
|
-
return
|
|
59
|
+
return utf8.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
|
|
44
60
|
end
|
|
45
61
|
|
|
46
62
|
utf8.scrub
|
|
47
63
|
rescue StandardError
|
|
48
|
-
|
|
64
|
+
bytes.to_s.dup.force_encoding(Encoding::UTF_8).scrub
|
|
49
65
|
end
|
|
50
66
|
|
|
51
67
|
def initialize(id:, path: nil, lines: [""], kind: :file, name: nil, readonly: false, modifiable: true)
|
|
52
68
|
@id = id
|
|
53
69
|
@path = path
|
|
54
|
-
@kind = kind
|
|
70
|
+
@kind = kind
|
|
55
71
|
@name = name
|
|
56
72
|
@lines = lines.dup
|
|
57
73
|
@lines = [""] if @lines.empty?
|
|
58
74
|
@modified = false
|
|
59
75
|
@readonly = !!readonly
|
|
60
76
|
@modifiable = !!modifiable
|
|
61
|
-
@
|
|
62
|
-
@loading_state = nil
|
|
77
|
+
@stream = nil
|
|
63
78
|
@undo_stack = []
|
|
64
79
|
@redo_stack = []
|
|
65
80
|
@change_group_depth = 0
|
|
@@ -87,7 +102,7 @@ module RuVim
|
|
|
87
102
|
end
|
|
88
103
|
|
|
89
104
|
def modifiable?
|
|
90
|
-
@modifiable &&
|
|
105
|
+
@modifiable && !@stream&.live?
|
|
91
106
|
end
|
|
92
107
|
|
|
93
108
|
def modifiable=(value)
|
|
@@ -111,12 +126,11 @@ module RuVim
|
|
|
111
126
|
end
|
|
112
127
|
|
|
113
128
|
def configure_special!(kind:, name: nil, readonly: true, modifiable: false)
|
|
114
|
-
@kind = kind
|
|
129
|
+
@kind = kind
|
|
115
130
|
@name = name
|
|
116
131
|
@readonly = !!readonly
|
|
117
132
|
@modifiable = !!modifiable
|
|
118
|
-
@
|
|
119
|
-
@loading_state = nil
|
|
133
|
+
@stream = nil unless @kind == :stream
|
|
120
134
|
self
|
|
121
135
|
end
|
|
122
136
|
|
|
@@ -126,8 +140,7 @@ module RuVim
|
|
|
126
140
|
@path = nil
|
|
127
141
|
@readonly = false
|
|
128
142
|
@modifiable = true
|
|
129
|
-
@
|
|
130
|
-
@loading_state = nil
|
|
143
|
+
@stream = nil
|
|
131
144
|
@lines = [""]
|
|
132
145
|
@modified = false
|
|
133
146
|
@undo_stack.clear
|
|
@@ -342,18 +355,26 @@ module RuVim
|
|
|
342
355
|
|
|
343
356
|
# Append externally-streamed text without touching undo history or modifiable state.
|
|
344
357
|
def append_stream_text!(text)
|
|
345
|
-
|
|
346
|
-
return [@lines.length - 1, @lines[-1].length] if chunk.empty?
|
|
358
|
+
return [@lines.length - 1, @lines[-1].length] if text.empty?
|
|
347
359
|
|
|
348
|
-
parts =
|
|
360
|
+
parts = text.split("\n", -1)
|
|
349
361
|
head = parts.shift || ""
|
|
350
|
-
@lines[-1] = @lines[-1]
|
|
362
|
+
@lines[-1] = @lines[-1] + head
|
|
351
363
|
@lines.concat(parts)
|
|
352
364
|
@lines = [""] if @lines.empty?
|
|
353
365
|
@modified = false
|
|
354
366
|
[@lines.length - 1, @lines[-1].length]
|
|
355
367
|
end
|
|
356
368
|
|
|
369
|
+
# Append pre-split lines (from background thread).
|
|
370
|
+
# head_suffix is concatenated to the current last line.
|
|
371
|
+
def append_stream_lines!(head_suffix, lines)
|
|
372
|
+
@lines[-1] = @lines[-1] + head_suffix unless head_suffix.empty?
|
|
373
|
+
@lines.concat(lines) unless lines.empty?
|
|
374
|
+
@lines = [""] if @lines.empty?
|
|
375
|
+
@modified = false
|
|
376
|
+
end
|
|
377
|
+
|
|
357
378
|
def finalize_async_file_load!(ended_with_newline:)
|
|
358
379
|
if ended_with_newline && @lines.length > 1 && @lines[-1] == ""
|
|
359
380
|
@lines.pop
|
|
@@ -379,7 +400,9 @@ module RuVim
|
|
|
379
400
|
target = path || @path
|
|
380
401
|
raise RuVim::CommandError, "No file name" if target.nil? || target.empty?
|
|
381
402
|
|
|
382
|
-
|
|
403
|
+
self.class.ensure_regular_file!(target)
|
|
404
|
+
|
|
405
|
+
data = File.file?(target) ? self.class.decode_text(File.binread(target)) : ""
|
|
383
406
|
@lines = self.class.split_lines(data)
|
|
384
407
|
@path = target
|
|
385
408
|
@modified = false
|
data/lib/ruvim/cli.rb
CHANGED
|
@@ -16,6 +16,7 @@ module RuVim
|
|
|
16
16
|
:no_swap,
|
|
17
17
|
:nomodifiable,
|
|
18
18
|
:restricted_mode,
|
|
19
|
+
:follow,
|
|
19
20
|
:verbose_level,
|
|
20
21
|
:startup_time_path,
|
|
21
22
|
:startup_open_layout,
|
|
@@ -70,6 +71,7 @@ module RuVim
|
|
|
70
71
|
quickfix_errorfile: opts.quickfix_errorfile,
|
|
71
72
|
session_file: opts.session_file,
|
|
72
73
|
nomodifiable: opts.nomodifiable,
|
|
74
|
+
follow: opts.follow,
|
|
73
75
|
restricted: opts.restricted_mode,
|
|
74
76
|
verbose_level: opts.verbose_level,
|
|
75
77
|
verbose_io: stderr,
|
|
@@ -101,6 +103,7 @@ module RuVim
|
|
|
101
103
|
no_swap: false,
|
|
102
104
|
nomodifiable: false,
|
|
103
105
|
restricted_mode: false,
|
|
106
|
+
follow: false,
|
|
104
107
|
verbose_level: 0,
|
|
105
108
|
startup_time_path: nil,
|
|
106
109
|
startup_open_layout: nil,
|
|
@@ -159,6 +162,8 @@ module RuVim
|
|
|
159
162
|
end
|
|
160
163
|
when "-n"
|
|
161
164
|
opts.no_swap = true
|
|
165
|
+
when "-f"
|
|
166
|
+
opts.follow = true
|
|
162
167
|
when "-M"
|
|
163
168
|
opts.nomodifiable = true
|
|
164
169
|
when "-Z"
|
|
@@ -240,6 +245,7 @@ module RuVim
|
|
|
240
245
|
-h, --help Show this help
|
|
241
246
|
-v, --version Show version
|
|
242
247
|
--clean Start without user config and ftplugin
|
|
248
|
+
-f Open file in follow mode (tail -f style)
|
|
243
249
|
-R Open file readonly (disallow :w on current buffer)
|
|
244
250
|
-d Diff mode requested (compat placeholder; not implemented yet)
|
|
245
251
|
-q {errorfile} Quickfix startup placeholder (not implemented yet)
|
|
@@ -4,12 +4,12 @@ module RuVim
|
|
|
4
4
|
class CommandInvocation
|
|
5
5
|
attr_accessor :id, :argv, :kwargs, :count, :bang, :raw_keys
|
|
6
6
|
|
|
7
|
-
def initialize(id:, argv: nil, kwargs: nil, count: nil, bang:
|
|
7
|
+
def initialize(id:, argv: nil, kwargs: nil, count: nil, bang: false, raw_keys: nil)
|
|
8
8
|
@id = id
|
|
9
9
|
@argv = argv || []
|
|
10
10
|
@kwargs = kwargs || {}
|
|
11
11
|
@count = count
|
|
12
|
-
@bang =
|
|
12
|
+
@bang = bang
|
|
13
13
|
@raw_keys = raw_keys
|
|
14
14
|
end
|
|
15
15
|
end
|