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
|
@@ -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,29 +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
|
|
10
12
|
|
|
11
13
|
def stream_status
|
|
12
|
-
|
|
14
|
+
@stream&.status
|
|
15
|
+
end
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
elsif @follow_backend == :inotify
|
|
17
|
-
"follow/i"
|
|
18
|
-
else
|
|
19
|
-
"follow"
|
|
20
|
-
end
|
|
17
|
+
def stream_command
|
|
18
|
+
@stream&.command
|
|
21
19
|
end
|
|
20
|
+
|
|
22
21
|
attr_accessor :lang_module
|
|
23
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
|
+
|
|
24
27
|
def self.from_file(id:, path:)
|
|
28
|
+
ensure_regular_file!(path)
|
|
25
29
|
lines =
|
|
26
|
-
if File.
|
|
30
|
+
if File.file?(path)
|
|
27
31
|
data = decode_text(File.binread(path))
|
|
28
32
|
split_lines(data)
|
|
29
33
|
else
|
|
@@ -44,34 +48,33 @@ module RuVim
|
|
|
44
48
|
end
|
|
45
49
|
|
|
46
50
|
def self.decode_text(bytes)
|
|
47
|
-
s = bytes.to_s
|
|
48
|
-
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?
|
|
49
53
|
|
|
50
54
|
utf8 = s.dup.force_encoding(Encoding::UTF_8)
|
|
51
55
|
return utf8 if utf8.valid_encoding?
|
|
52
56
|
|
|
53
57
|
ext = Encoding.default_external
|
|
54
58
|
if ext && ext != Encoding::UTF_8
|
|
55
|
-
return
|
|
59
|
+
return utf8.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
|
|
56
60
|
end
|
|
57
61
|
|
|
58
62
|
utf8.scrub
|
|
59
63
|
rescue StandardError
|
|
60
|
-
|
|
64
|
+
bytes.to_s.dup.force_encoding(Encoding::UTF_8).scrub
|
|
61
65
|
end
|
|
62
66
|
|
|
63
67
|
def initialize(id:, path: nil, lines: [""], kind: :file, name: nil, readonly: false, modifiable: true)
|
|
64
68
|
@id = id
|
|
65
69
|
@path = path
|
|
66
|
-
@kind = kind
|
|
70
|
+
@kind = kind
|
|
67
71
|
@name = name
|
|
68
72
|
@lines = lines.dup
|
|
69
73
|
@lines = [""] if @lines.empty?
|
|
70
74
|
@modified = false
|
|
71
75
|
@readonly = !!readonly
|
|
72
76
|
@modifiable = !!modifiable
|
|
73
|
-
@
|
|
74
|
-
@loading_state = nil
|
|
77
|
+
@stream = nil
|
|
75
78
|
@undo_stack = []
|
|
76
79
|
@redo_stack = []
|
|
77
80
|
@change_group_depth = 0
|
|
@@ -99,7 +102,7 @@ module RuVim
|
|
|
99
102
|
end
|
|
100
103
|
|
|
101
104
|
def modifiable?
|
|
102
|
-
@modifiable &&
|
|
105
|
+
@modifiable && !@stream&.live?
|
|
103
106
|
end
|
|
104
107
|
|
|
105
108
|
def modifiable=(value)
|
|
@@ -123,12 +126,11 @@ module RuVim
|
|
|
123
126
|
end
|
|
124
127
|
|
|
125
128
|
def configure_special!(kind:, name: nil, readonly: true, modifiable: false)
|
|
126
|
-
@kind = kind
|
|
129
|
+
@kind = kind
|
|
127
130
|
@name = name
|
|
128
131
|
@readonly = !!readonly
|
|
129
132
|
@modifiable = !!modifiable
|
|
130
|
-
@
|
|
131
|
-
@loading_state = nil
|
|
133
|
+
@stream = nil unless @kind == :stream
|
|
132
134
|
self
|
|
133
135
|
end
|
|
134
136
|
|
|
@@ -138,8 +140,7 @@ module RuVim
|
|
|
138
140
|
@path = nil
|
|
139
141
|
@readonly = false
|
|
140
142
|
@modifiable = true
|
|
141
|
-
@
|
|
142
|
-
@loading_state = nil
|
|
143
|
+
@stream = nil
|
|
143
144
|
@lines = [""]
|
|
144
145
|
@modified = false
|
|
145
146
|
@undo_stack.clear
|
|
@@ -354,18 +355,26 @@ module RuVim
|
|
|
354
355
|
|
|
355
356
|
# Append externally-streamed text without touching undo history or modifiable state.
|
|
356
357
|
def append_stream_text!(text)
|
|
357
|
-
|
|
358
|
-
return [@lines.length - 1, @lines[-1].length] if chunk.empty?
|
|
358
|
+
return [@lines.length - 1, @lines[-1].length] if text.empty?
|
|
359
359
|
|
|
360
|
-
parts =
|
|
360
|
+
parts = text.split("\n", -1)
|
|
361
361
|
head = parts.shift || ""
|
|
362
|
-
@lines[-1] = @lines[-1]
|
|
362
|
+
@lines[-1] = @lines[-1] + head
|
|
363
363
|
@lines.concat(parts)
|
|
364
364
|
@lines = [""] if @lines.empty?
|
|
365
365
|
@modified = false
|
|
366
366
|
[@lines.length - 1, @lines[-1].length]
|
|
367
367
|
end
|
|
368
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
|
+
|
|
369
378
|
def finalize_async_file_load!(ended_with_newline:)
|
|
370
379
|
if ended_with_newline && @lines.length > 1 && @lines[-1] == ""
|
|
371
380
|
@lines.pop
|
|
@@ -391,7 +400,9 @@ module RuVim
|
|
|
391
400
|
target = path || @path
|
|
392
401
|
raise RuVim::CommandError, "No file name" if target.nil? || target.empty?
|
|
393
402
|
|
|
394
|
-
|
|
403
|
+
self.class.ensure_regular_file!(target)
|
|
404
|
+
|
|
405
|
+
data = File.file?(target) ? self.class.decode_text(File.binread(target)) : ""
|
|
395
406
|
@lines = self.class.split_lines(data)
|
|
396
407
|
@path = target
|
|
397
408
|
@modified = false
|
|
@@ -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
|