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,207 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
5
|
+
module RuVim
|
|
6
|
+
module Gh
|
|
7
|
+
module Link
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# Parse a git remote URL and return the GitHub HTTPS base URL.
|
|
11
|
+
# Returns nil if the remote is not a GitHub URL.
|
|
12
|
+
def github_url_from_remote(remote_url)
|
|
13
|
+
url = remote_url.to_s.strip
|
|
14
|
+
return nil if url.empty?
|
|
15
|
+
|
|
16
|
+
case url
|
|
17
|
+
when %r{\Agit@github\.com:(.+?)(?:\.git)?\z}
|
|
18
|
+
"https://github.com/#{$1}"
|
|
19
|
+
when %r{\Ahttps://github\.com/(.+?)(?:\.git)?\z}
|
|
20
|
+
"https://github.com/#{$1}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Build a GitHub blob URL.
|
|
25
|
+
def build_url(base_url, ref, relative_path, line_start, line_end = nil)
|
|
26
|
+
fragment = if line_end && line_end != line_start
|
|
27
|
+
"#L#{line_start}-L#{line_end}"
|
|
28
|
+
else
|
|
29
|
+
"#L#{line_start}"
|
|
30
|
+
end
|
|
31
|
+
"#{base_url}/blob/#{ref}/#{relative_path}#{fragment}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Generate OSC 52 escape sequence for clipboard copy.
|
|
35
|
+
def osc52_copy_sequence(text)
|
|
36
|
+
encoded = [text].pack("m0")
|
|
37
|
+
"\e]52;c;#{encoded}\a"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Find a GitHub remote. If remote_name is given, use that specific remote.
|
|
41
|
+
# Otherwise, scan all remotes (preferring "origin", then "upstream", then others).
|
|
42
|
+
# Returns [remote_name, base_url] or [nil, nil].
|
|
43
|
+
def find_github_remote(root, remote_name = nil)
|
|
44
|
+
if remote_name
|
|
45
|
+
url, _, status = Open3.capture3("git", "remote", "get-url", remote_name, chdir: root)
|
|
46
|
+
return [nil, nil] unless status.success?
|
|
47
|
+
|
|
48
|
+
base = github_url_from_remote(url.strip)
|
|
49
|
+
return base ? [remote_name, base] : [nil, nil]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
remotes_out, _, status = Open3.capture3("git", "remote", chdir: root)
|
|
53
|
+
return [nil, nil] unless status.success?
|
|
54
|
+
|
|
55
|
+
remotes = remotes_out.lines(chomp: true)
|
|
56
|
+
# Prefer origin, then upstream, then others
|
|
57
|
+
ordered = []
|
|
58
|
+
ordered << "origin" if remotes.include?("origin")
|
|
59
|
+
ordered << "upstream" if remotes.include?("upstream")
|
|
60
|
+
remotes.each { |r| ordered << r unless ordered.include?(r) }
|
|
61
|
+
|
|
62
|
+
ordered.each do |name|
|
|
63
|
+
url, _, st = Open3.capture3("git", "remote", "get-url", name, chdir: root)
|
|
64
|
+
next unless st.success?
|
|
65
|
+
|
|
66
|
+
base = github_url_from_remote(url.strip)
|
|
67
|
+
return [name, base] if base
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
[nil, nil]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Check if a file differs from the remote tracking branch.
|
|
74
|
+
def file_differs_from_remote?(root, remote_name, branch, file_path)
|
|
75
|
+
remote_ref = "#{remote_name}/#{branch}"
|
|
76
|
+
diff_out, _, status = Open3.capture3("git", "diff", remote_ref, "--", file_path, chdir: root)
|
|
77
|
+
# If the remote ref doesn't exist or diff fails, consider it as differing
|
|
78
|
+
return true unless status.success?
|
|
79
|
+
|
|
80
|
+
!diff_out.empty?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Resolve GitHub link for a file path at given line(s).
|
|
84
|
+
# Returns [url, warning, error_message].
|
|
85
|
+
def resolve(file_path, line_start, line_end = nil, remote_name: nil)
|
|
86
|
+
root, err = RuVim::Git.repo_root(file_path)
|
|
87
|
+
return [nil, nil, err] unless root
|
|
88
|
+
|
|
89
|
+
found_remote, base_url = find_github_remote(root, remote_name)
|
|
90
|
+
unless base_url
|
|
91
|
+
msg = remote_name ? "Remote '#{remote_name}' is not a GitHub remote" : "No GitHub remote found"
|
|
92
|
+
return [nil, nil, msg]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
branch, _, status = Open3.capture3("git", "rev-parse", "--abbrev-ref", "HEAD", chdir: root)
|
|
96
|
+
unless status.success?
|
|
97
|
+
return [nil, nil, "Cannot determine branch"]
|
|
98
|
+
end
|
|
99
|
+
branch = branch.strip
|
|
100
|
+
|
|
101
|
+
relative_path = file_path.sub(%r{\A#{Regexp.escape(root)}/?}, "")
|
|
102
|
+
url = build_url(base_url, branch, relative_path, line_start, line_end)
|
|
103
|
+
|
|
104
|
+
warning = nil
|
|
105
|
+
if file_differs_from_remote?(root, found_remote, branch, file_path)
|
|
106
|
+
warning = "(remote may differ)"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
[url, warning, nil]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Build a GitHub PR search URL for a branch.
|
|
113
|
+
def pr_search_url(base_url, branch)
|
|
114
|
+
"#{base_url}/pulls?q=head:#{branch}"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Resolve GitHub PR URL for the current repo.
|
|
118
|
+
# Returns [url, error_message].
|
|
119
|
+
def resolve_pr(file_path)
|
|
120
|
+
root, err = RuVim::Git.repo_root(file_path)
|
|
121
|
+
return [nil, err] unless root
|
|
122
|
+
|
|
123
|
+
_, base_url = find_github_remote(root)
|
|
124
|
+
return [nil, "No GitHub remote found"] unless base_url
|
|
125
|
+
|
|
126
|
+
branch, _, status = Open3.capture3("git", "rev-parse", "--abbrev-ref", "HEAD", chdir: root)
|
|
127
|
+
return [nil, "Cannot determine branch"] unless status.success?
|
|
128
|
+
|
|
129
|
+
[pr_search_url(base_url, branch.strip), nil]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
module HandlerMethods
|
|
133
|
+
def gh_link(ctx, argv: [], kwargs: {}, **)
|
|
134
|
+
url, warning = gh_resolve_url(ctx, argv: argv, kwargs: kwargs, command: "gh link")
|
|
135
|
+
return unless url
|
|
136
|
+
|
|
137
|
+
# Copy to clipboard via OSC 52
|
|
138
|
+
$stdout.write(Link.osc52_copy_sequence(url))
|
|
139
|
+
$stdout.flush
|
|
140
|
+
|
|
141
|
+
msg = warning ? "#{url} #{warning}" : url
|
|
142
|
+
ctx.editor.echo(msg)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def gh_browse(ctx, argv: [], kwargs: {}, **)
|
|
146
|
+
url, warning = gh_resolve_url(ctx, argv: argv, kwargs: kwargs, command: "gh browse")
|
|
147
|
+
return unless url
|
|
148
|
+
|
|
149
|
+
unless Browser.open_url(url)
|
|
150
|
+
ctx.editor.echo_error("gh browse: could not open browser")
|
|
151
|
+
return
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
msg = warning ? "Opened #{url} #{warning}" : "Opened #{url}"
|
|
155
|
+
ctx.editor.echo(msg)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def gh_pr(ctx, **)
|
|
159
|
+
path = ctx.buffer.path || Dir.pwd
|
|
160
|
+
url, err = Link.resolve_pr(path)
|
|
161
|
+
unless url
|
|
162
|
+
ctx.editor.echo_error("gh pr: #{err}")
|
|
163
|
+
return
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
unless Browser.open_url(url)
|
|
167
|
+
ctx.editor.echo_error("gh pr: could not open browser")
|
|
168
|
+
return
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
ctx.editor.echo("Opened #{url}")
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
private
|
|
175
|
+
|
|
176
|
+
def gh_resolve_url(ctx, argv:, kwargs:, command:)
|
|
177
|
+
path = ctx.buffer.path
|
|
178
|
+
unless path && File.exist?(path)
|
|
179
|
+
ctx.editor.echo_error("Buffer has no file path")
|
|
180
|
+
return nil
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
line_start = kwargs[:range_start]
|
|
184
|
+
line_end = kwargs[:range_end]
|
|
185
|
+
|
|
186
|
+
if line_start
|
|
187
|
+
line_start += 1
|
|
188
|
+
line_end += 1 if line_end
|
|
189
|
+
else
|
|
190
|
+
line_start = ctx.window.cursor_y + 1
|
|
191
|
+
line_end = nil
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
remote_name = argv.first
|
|
195
|
+
|
|
196
|
+
url, warning, err = Link.resolve(path, line_start, line_end, remote_name: remote_name)
|
|
197
|
+
unless url
|
|
198
|
+
ctx.editor.echo_error("#{command}: #{err}")
|
|
199
|
+
return nil
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
[url, warning]
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
data/lib/ruvim/git/blame.rb
CHANGED
|
@@ -47,15 +47,20 @@ module RuVim
|
|
|
47
47
|
entries
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
# Format entries into
|
|
50
|
+
# Format entries into code lines and gutter labels.
|
|
51
|
+
# Returns [lines, labels] where lines are code-only and labels are metadata strings.
|
|
51
52
|
def format_lines(entries)
|
|
52
53
|
max_author = entries.map { |e| e[:author].to_s.length }.max || 0
|
|
53
54
|
max_author = [max_author, 20].min
|
|
54
55
|
|
|
55
|
-
|
|
56
|
+
lines = []
|
|
57
|
+
labels = []
|
|
58
|
+
entries.each do |e|
|
|
56
59
|
author = (e[:author] || "").ljust(max_author)[0, max_author]
|
|
57
|
-
"#{e[:short_hash]} #{author} #{e[:date]}
|
|
60
|
+
labels << "#{e[:short_hash]} #{author} #{e[:date]} "
|
|
61
|
+
lines << e[:text].to_s
|
|
58
62
|
end
|
|
63
|
+
[lines, labels]
|
|
59
64
|
end
|
|
60
65
|
|
|
61
66
|
# Run git blame for a file at a given revision.
|
|
@@ -106,19 +111,22 @@ module RuVim
|
|
|
106
111
|
return
|
|
107
112
|
end
|
|
108
113
|
|
|
109
|
-
lines = Blame.format_lines(entries)
|
|
114
|
+
lines, labels = Blame.format_lines(entries)
|
|
110
115
|
cursor_y = ctx.window.cursor_y
|
|
116
|
+
source_ft = source_buf.options["filetype"]
|
|
111
117
|
|
|
112
118
|
blame_buf = ctx.editor.add_virtual_buffer(
|
|
113
119
|
kind: :blame,
|
|
114
120
|
name: "[Blame] #{File.basename(source_buf.path)}",
|
|
115
121
|
lines: lines,
|
|
122
|
+
filetype: source_ft,
|
|
116
123
|
readonly: true,
|
|
117
124
|
modifiable: false
|
|
118
125
|
)
|
|
119
126
|
blame_buf.options["blame_entries"] = entries
|
|
120
127
|
blame_buf.options["blame_source_path"] = source_buf.path
|
|
121
128
|
blame_buf.options["blame_history"] = []
|
|
129
|
+
blame_buf.options["gutter_labels"] = labels
|
|
122
130
|
|
|
123
131
|
ctx.editor.switch_to_buffer(blame_buf.id)
|
|
124
132
|
ctx.window.cursor_y = [cursor_y, lines.length - 1].min
|
|
@@ -160,9 +168,10 @@ module RuVim
|
|
|
160
168
|
|
|
161
169
|
history.push({ entries: entries, cursor_y: cursor_y })
|
|
162
170
|
|
|
163
|
-
new_lines = Blame.format_lines(new_entries)
|
|
171
|
+
new_lines, new_labels = Blame.format_lines(new_entries)
|
|
164
172
|
buf.instance_variable_set(:@lines, new_lines)
|
|
165
173
|
buf.options["blame_entries"] = new_entries
|
|
174
|
+
buf.options["gutter_labels"] = new_labels
|
|
166
175
|
ctx.window.cursor_y = [cursor_y, new_lines.length - 1].min
|
|
167
176
|
ctx.window.cursor_x = 0
|
|
168
177
|
ctx.editor.echo("[Blame] #{commit_hash[0, 8]}^")
|
|
@@ -182,9 +191,10 @@ module RuVim
|
|
|
182
191
|
end
|
|
183
192
|
|
|
184
193
|
state = history.pop
|
|
185
|
-
lines = Blame.format_lines(state[:entries])
|
|
194
|
+
lines, labels = Blame.format_lines(state[:entries])
|
|
186
195
|
buf.instance_variable_set(:@lines, lines)
|
|
187
196
|
buf.options["blame_entries"] = state[:entries]
|
|
197
|
+
buf.options["gutter_labels"] = labels
|
|
188
198
|
ctx.window.cursor_y = [state[:cursor_y], lines.length - 1].min
|
|
189
199
|
ctx.window.cursor_x = 0
|
|
190
200
|
ctx.editor.echo("[Blame] restored")
|
data/lib/ruvim/git/branch.rb
CHANGED
|
@@ -80,15 +80,30 @@ module RuVim
|
|
|
80
80
|
return
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
ctx.editor.enter_command_line_mode(":")
|
|
84
|
+
ctx.editor.command_line.replace_text("git checkout #{branch}")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def git_branch_execute_checkout(ctx, argv: [], **)
|
|
88
|
+
branch = argv.first.to_s.strip
|
|
89
|
+
raise RuVim::CommandError, "Usage: :git checkout <branch>" if branch.empty?
|
|
90
|
+
|
|
91
|
+
root = ctx.buffer.kind == :git_branch ? ctx.buffer.options["git_repo_root"] : nil
|
|
92
|
+
unless root
|
|
93
|
+
file_path = git_resolve_path(ctx)
|
|
94
|
+
root, err = Git.repo_root(file_path) if file_path
|
|
95
|
+
raise RuVim::CommandError, "Not in a git repository" unless root
|
|
96
|
+
end
|
|
97
|
+
|
|
84
98
|
_out, err, status = Open3.capture3("git", "checkout", branch, chdir: root)
|
|
85
99
|
unless status.success?
|
|
86
|
-
|
|
87
|
-
return
|
|
100
|
+
raise RuVim::CommandError, "git checkout: #{err.strip}"
|
|
88
101
|
end
|
|
89
102
|
|
|
90
|
-
#
|
|
91
|
-
ctx.
|
|
103
|
+
# Close git branch buffer if we're in one
|
|
104
|
+
if ctx.buffer.kind == :git_branch
|
|
105
|
+
ctx.editor.delete_buffer(ctx.buffer.id)
|
|
106
|
+
end
|
|
92
107
|
ctx.editor.echo("Switched to branch '#{branch}'")
|
|
93
108
|
end
|
|
94
109
|
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
5
|
+
module RuVim
|
|
6
|
+
module Git
|
|
7
|
+
module Grep
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# Run git grep -n with extra args.
|
|
11
|
+
# Returns [lines, root, error_message].
|
|
12
|
+
def run(file_path, args: [])
|
|
13
|
+
root, err = Git.repo_root(file_path)
|
|
14
|
+
return [nil, nil, err] unless root
|
|
15
|
+
|
|
16
|
+
cmd = ["git", "grep", "-n", *args]
|
|
17
|
+
out, err, status = Open3.capture3(*cmd, chdir: root)
|
|
18
|
+
# git grep exits 1 when no matches found (not an error)
|
|
19
|
+
unless status.success? || status.exitstatus == 1
|
|
20
|
+
return [nil, nil, err.strip]
|
|
21
|
+
end
|
|
22
|
+
[out.lines(chomp: true), root, nil]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Parse a git grep output line (file:line:content).
|
|
26
|
+
# Returns [filename, line_number] or nil.
|
|
27
|
+
def parse_location(line)
|
|
28
|
+
return nil if line.empty? || line == "--"
|
|
29
|
+
|
|
30
|
+
m = line.match(/\A(.+?):(\d+):/)
|
|
31
|
+
return nil unless m
|
|
32
|
+
|
|
33
|
+
[m[1], m[2].to_i]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
module HandlerMethods
|
|
37
|
+
def git_grep(ctx, argv: [], **)
|
|
38
|
+
if argv.empty?
|
|
39
|
+
ctx.editor.echo_error("Usage: :git grep <pattern> [<args>...]")
|
|
40
|
+
return
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
file_path = git_resolve_path(ctx)
|
|
44
|
+
unless file_path
|
|
45
|
+
ctx.editor.echo_error("No file or directory to resolve git repo")
|
|
46
|
+
return
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
lines, root, err = Grep.run(file_path, args: argv)
|
|
50
|
+
unless lines
|
|
51
|
+
ctx.editor.echo_error("git grep: #{err}")
|
|
52
|
+
return
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
if lines.empty?
|
|
56
|
+
ctx.editor.echo("No matches found")
|
|
57
|
+
return
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
buf = ctx.editor.add_virtual_buffer(
|
|
61
|
+
kind: :git_grep,
|
|
62
|
+
name: "[Git Grep]",
|
|
63
|
+
lines: lines,
|
|
64
|
+
readonly: true,
|
|
65
|
+
modifiable: false
|
|
66
|
+
)
|
|
67
|
+
buf.options["git_repo_root"] = root
|
|
68
|
+
ctx.editor.switch_to_buffer(buf.id)
|
|
69
|
+
bind_git_buffer_keys(ctx.editor, buf.id)
|
|
70
|
+
ctx.editor.echo("[Git Grep] #{lines.length} match(es)")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def git_grep_open_file(ctx, **)
|
|
74
|
+
buf = ctx.buffer
|
|
75
|
+
unless buf.kind == :git_grep
|
|
76
|
+
ctx.editor.echo_error("Not a git grep buffer")
|
|
77
|
+
return
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
line = buf.lines[ctx.window.cursor_y]
|
|
81
|
+
location = Grep.parse_location(line)
|
|
82
|
+
unless location
|
|
83
|
+
ctx.editor.echo_error("No file on this line")
|
|
84
|
+
return
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
filename, line_num = location
|
|
88
|
+
root = buf.options["git_repo_root"]
|
|
89
|
+
full_path = File.join(root, filename)
|
|
90
|
+
unless File.exist?(full_path)
|
|
91
|
+
ctx.editor.echo_error("File not found: #{filename}")
|
|
92
|
+
return
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
existing = ctx.editor.buffers.values.find { |b| b.path == full_path }
|
|
96
|
+
if existing
|
|
97
|
+
ctx.editor.switch_to_buffer(existing.id)
|
|
98
|
+
else
|
|
99
|
+
new_buf = ctx.editor.add_buffer_from_file(full_path)
|
|
100
|
+
ctx.editor.switch_to_buffer(new_buf.id)
|
|
101
|
+
end
|
|
102
|
+
ctx.editor.current_window.cursor_y = [line_num - 1, 0].max
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
data/lib/ruvim/git/handler.rb
CHANGED
|
@@ -27,7 +27,9 @@ module RuVim
|
|
|
27
27
|
"diff" => :git_diff,
|
|
28
28
|
"log" => :git_log,
|
|
29
29
|
"branch" => :git_branch,
|
|
30
|
+
"checkout" => :git_branch_execute_checkout,
|
|
30
31
|
"commit" => :git_commit,
|
|
32
|
+
"grep" => :git_grep,
|
|
31
33
|
}.freeze
|
|
32
34
|
|
|
33
35
|
include Blame::HandlerMethods
|
|
@@ -36,6 +38,14 @@ module RuVim
|
|
|
36
38
|
include Log::HandlerMethods
|
|
37
39
|
include Branch::HandlerMethods
|
|
38
40
|
include Commit::HandlerMethods
|
|
41
|
+
include Grep::HandlerMethods
|
|
42
|
+
include Gh::Link::HandlerMethods
|
|
43
|
+
|
|
44
|
+
GH_SUBCOMMANDS = {
|
|
45
|
+
"link" => :gh_link,
|
|
46
|
+
"browse" => :gh_browse,
|
|
47
|
+
"pr" => :gh_pr,
|
|
48
|
+
}.freeze
|
|
39
49
|
|
|
40
50
|
def enter_git_command_mode(ctx, **)
|
|
41
51
|
ctx.editor.enter_command_line_mode(":")
|
|
@@ -44,6 +54,8 @@ module RuVim
|
|
|
44
54
|
end
|
|
45
55
|
|
|
46
56
|
def ex_git(ctx, argv: [], **)
|
|
57
|
+
raise RuVim::CommandError, "Restricted mode: :git is disabled" if ctx.editor.respond_to?(:restricted_mode?) && ctx.editor.restricted_mode?
|
|
58
|
+
|
|
47
59
|
sub = argv.first.to_s.downcase
|
|
48
60
|
if sub.empty?
|
|
49
61
|
ctx.editor.echo("Git subcommands: #{GIT_SUBCOMMANDS.keys.join(', ')}")
|
|
@@ -52,13 +64,31 @@ module RuVim
|
|
|
52
64
|
|
|
53
65
|
method = GIT_SUBCOMMANDS[sub]
|
|
54
66
|
unless method
|
|
55
|
-
ctx
|
|
67
|
+
run_shell_fallback(ctx, "git", argv)
|
|
56
68
|
return
|
|
57
69
|
end
|
|
58
70
|
|
|
59
71
|
public_send(method, ctx, argv: argv[1..], kwargs: {}, bang: false, count: 1)
|
|
60
72
|
end
|
|
61
73
|
|
|
74
|
+
def ex_gh(ctx, argv: [], kwargs: {}, **)
|
|
75
|
+
raise RuVim::CommandError, "Restricted mode: :gh is disabled" if ctx.editor.respond_to?(:restricted_mode?) && ctx.editor.restricted_mode?
|
|
76
|
+
|
|
77
|
+
sub = argv.first.to_s.downcase
|
|
78
|
+
if sub.empty?
|
|
79
|
+
ctx.editor.echo("GitHub subcommands: #{GH_SUBCOMMANDS.keys.join(', ')}")
|
|
80
|
+
return
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
method = GH_SUBCOMMANDS[sub]
|
|
84
|
+
unless method
|
|
85
|
+
run_shell_fallback(ctx, "gh", argv)
|
|
86
|
+
return
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
public_send(method, ctx, argv: argv[1..], kwargs: kwargs, bang: false, count: 1)
|
|
90
|
+
end
|
|
91
|
+
|
|
62
92
|
def git_close_buffer(ctx, **)
|
|
63
93
|
buf_id = ctx.buffer.id
|
|
64
94
|
ctx.editor.git_stream_stop_handler&.call(buf_id)
|
|
@@ -67,6 +97,17 @@ module RuVim
|
|
|
67
97
|
|
|
68
98
|
private
|
|
69
99
|
|
|
100
|
+
def run_shell_fallback(ctx, cmd, argv)
|
|
101
|
+
command = ([cmd] + argv).join(" ")
|
|
102
|
+
executor = ctx.editor.shell_executor
|
|
103
|
+
if executor
|
|
104
|
+
status = executor.call(command)
|
|
105
|
+
ctx.editor.echo("shell exit #{status.exitstatus}")
|
|
106
|
+
else
|
|
107
|
+
ctx.editor.echo_error("Unknown #{cmd} subcommand: #{argv.first}")
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
70
111
|
def git_resolve_path(ctx)
|
|
71
112
|
path = ctx.buffer.path
|
|
72
113
|
return path if path && File.exist?(path)
|