ruvim 0.3.0 → 0.4.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 +18 -6
- data/README.md +15 -1
- data/docs/binding.md +16 -0
- data/docs/command.md +78 -4
- data/docs/config.md +10 -2
- data/docs/spec.md +60 -9
- data/docs/tutorial.md +24 -0
- data/docs/vim_diff.md +18 -8
- data/lib/ruvim/app.rb +290 -8
- data/lib/ruvim/buffer.rb +14 -2
- data/lib/ruvim/cli.rb +6 -0
- data/lib/ruvim/editor.rb +12 -1
- data/lib/ruvim/file_watcher.rb +243 -0
- data/lib/ruvim/git/blame.rb +245 -0
- data/lib/ruvim/git/branch.rb +97 -0
- data/lib/ruvim/git/commit.rb +102 -0
- data/lib/ruvim/git/diff.rb +129 -0
- data/lib/ruvim/git/handler.rb +84 -0
- data/lib/ruvim/git/log.rb +41 -0
- data/lib/ruvim/git/status.rb +103 -0
- data/lib/ruvim/global_commands.rb +176 -42
- data/lib/ruvim/highlighter.rb +3 -1
- data/lib/ruvim/input.rb +1 -0
- data/lib/ruvim/lang/diff.rb +41 -0
- data/lib/ruvim/lang/json.rb +34 -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.rb +16 -0
- data/lib/ruvim/screen.rb +9 -12
- data/lib/ruvim/version.rb +1 -1
- data/lib/ruvim.rb +10 -0
- data/test/app_completion_test.rb +25 -0
- data/test/app_scenario_test.rb +169 -0
- data/test/cli_test.rb +14 -0
- data/test/clipboard_test.rb +67 -0
- data/test/command_line_test.rb +118 -0
- data/test/config_dsl_test.rb +87 -0
- data/test/display_width_test.rb +41 -0
- data/test/file_watcher_test.rb +197 -0
- data/test/follow_test.rb +199 -0
- data/test/git_blame_test.rb +713 -0
- data/test/highlighter_test.rb +44 -0
- data/test/indent_test.rb +86 -0
- data/test/rich_view_test.rb +256 -0
- data/test/search_option_test.rb +19 -0
- data/test/test_helper.rb +9 -0
- metadata +17 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
5
|
+
module RuVim
|
|
6
|
+
module Git
|
|
7
|
+
module Diff
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# Run git diff with optional 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", "diff", *args]
|
|
17
|
+
out, err, status = Open3.capture3(*cmd, chdir: root)
|
|
18
|
+
unless status.success?
|
|
19
|
+
return [nil, nil, err.strip]
|
|
20
|
+
end
|
|
21
|
+
[out.lines(chomp: true), root, nil]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Parse diff output to find file and line number at cursor_y.
|
|
25
|
+
# Returns [filename, line_number] or nil.
|
|
26
|
+
def parse_location(lines, cursor_y)
|
|
27
|
+
return nil if lines.empty? || cursor_y < 0 || cursor_y >= lines.length
|
|
28
|
+
|
|
29
|
+
current_file = nil
|
|
30
|
+
new_line = nil
|
|
31
|
+
|
|
32
|
+
(0..cursor_y).each do |i|
|
|
33
|
+
l = lines[i]
|
|
34
|
+
case l
|
|
35
|
+
when /\Adiff --git a\/.+ b\/(.+)/
|
|
36
|
+
current_file = $1
|
|
37
|
+
when /\A\+\+\+ b\/(.+)/
|
|
38
|
+
current_file = $1
|
|
39
|
+
when /\A@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/
|
|
40
|
+
new_line = $1.to_i
|
|
41
|
+
when /\A[ +]/
|
|
42
|
+
# Context or added line: current new_line is this line's number
|
|
43
|
+
new_line += 1 if new_line && i < cursor_y
|
|
44
|
+
end
|
|
45
|
+
# Deleted lines ("-") don't advance new_line
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
return nil unless current_file
|
|
49
|
+
|
|
50
|
+
l = lines[cursor_y]
|
|
51
|
+
case l
|
|
52
|
+
when /\A@@ /
|
|
53
|
+
# On hunk header: new_line already set
|
|
54
|
+
when /\Adiff --git /, /\A---/, /\A\+\+\+/, /\Aindex /
|
|
55
|
+
new_line ||= 1
|
|
56
|
+
when /\A-/
|
|
57
|
+
# Deleted line: point to current new-side position
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
[current_file, new_line || 1]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Command handler methods
|
|
64
|
+
module HandlerMethods
|
|
65
|
+
def git_diff(ctx, argv: [], **)
|
|
66
|
+
file_path = git_resolve_path(ctx)
|
|
67
|
+
unless file_path
|
|
68
|
+
ctx.editor.echo_error("No file or directory to resolve git repo")
|
|
69
|
+
return
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
lines, root, err = Diff.run(file_path, args: argv)
|
|
73
|
+
unless lines
|
|
74
|
+
ctx.editor.echo_error("git diff: #{err}")
|
|
75
|
+
return
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
if lines.empty?
|
|
79
|
+
ctx.editor.echo("No diff output (working tree clean)")
|
|
80
|
+
return
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
buf = ctx.editor.add_virtual_buffer(
|
|
84
|
+
kind: :git_diff,
|
|
85
|
+
name: "[Git Diff]",
|
|
86
|
+
lines: lines,
|
|
87
|
+
filetype: "diff",
|
|
88
|
+
readonly: true,
|
|
89
|
+
modifiable: false
|
|
90
|
+
)
|
|
91
|
+
buf.options["git_repo_root"] = root
|
|
92
|
+
ctx.editor.switch_to_buffer(buf.id)
|
|
93
|
+
bind_git_buffer_keys(ctx.editor, buf.id)
|
|
94
|
+
ctx.editor.echo("[Git Diff]")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def git_diff_open_file(ctx, **)
|
|
98
|
+
buf = ctx.buffer
|
|
99
|
+
unless buf.kind == :git_diff || buf.kind == :git_log
|
|
100
|
+
ctx.editor.echo_error("Not a git diff buffer")
|
|
101
|
+
return
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
filename, line_num = Diff.parse_location(buf.lines, ctx.window.cursor_y)
|
|
105
|
+
unless filename
|
|
106
|
+
ctx.editor.echo_error("No file on this line")
|
|
107
|
+
return
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
root = buf.options["git_repo_root"]
|
|
111
|
+
full_path = File.join(root, filename)
|
|
112
|
+
unless File.exist?(full_path)
|
|
113
|
+
ctx.editor.echo_error("File not found: #{filename}")
|
|
114
|
+
return
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
existing = ctx.editor.buffers.values.find { |b| b.path == full_path }
|
|
118
|
+
if existing
|
|
119
|
+
ctx.editor.switch_to_buffer(existing.id)
|
|
120
|
+
else
|
|
121
|
+
new_buf = ctx.editor.add_buffer_from_file(full_path)
|
|
122
|
+
ctx.editor.switch_to_buffer(new_buf.id)
|
|
123
|
+
end
|
|
124
|
+
ctx.editor.current_window.cursor_y = [line_num - 1, 0].max
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
5
|
+
module RuVim
|
|
6
|
+
module Git
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# Find git repository root from a file path.
|
|
10
|
+
# Returns [root_path, error_message].
|
|
11
|
+
def repo_root(file_path)
|
|
12
|
+
dir = File.directory?(file_path) ? file_path : File.dirname(file_path)
|
|
13
|
+
out, err, status = Open3.capture3("git", "rev-parse", "--show-toplevel", chdir: dir)
|
|
14
|
+
unless status.success?
|
|
15
|
+
return [nil, err.strip]
|
|
16
|
+
end
|
|
17
|
+
[out.strip, nil]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
module Handler
|
|
21
|
+
GIT_SUBCOMMANDS = {
|
|
22
|
+
"blame" => :git_blame,
|
|
23
|
+
"blameprev" => :git_blame_prev,
|
|
24
|
+
"blameback" => :git_blame_back,
|
|
25
|
+
"blamecommit" => :git_blame_commit,
|
|
26
|
+
"status" => :git_status,
|
|
27
|
+
"diff" => :git_diff,
|
|
28
|
+
"log" => :git_log,
|
|
29
|
+
"branch" => :git_branch,
|
|
30
|
+
"commit" => :git_commit,
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
include Blame::HandlerMethods
|
|
34
|
+
include Status::HandlerMethods
|
|
35
|
+
include Diff::HandlerMethods
|
|
36
|
+
include Log::HandlerMethods
|
|
37
|
+
include Branch::HandlerMethods
|
|
38
|
+
include Commit::HandlerMethods
|
|
39
|
+
|
|
40
|
+
def enter_git_command_mode(ctx, **)
|
|
41
|
+
ctx.editor.enter_command_line_mode(":")
|
|
42
|
+
ctx.editor.command_line.replace_text("git ")
|
|
43
|
+
ctx.editor.clear_message
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def ex_git(ctx, argv: [], **)
|
|
47
|
+
sub = argv.first.to_s.downcase
|
|
48
|
+
if sub.empty?
|
|
49
|
+
ctx.editor.echo("Git subcommands: #{GIT_SUBCOMMANDS.keys.join(', ')}")
|
|
50
|
+
return
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
method = GIT_SUBCOMMANDS[sub]
|
|
54
|
+
unless method
|
|
55
|
+
ctx.editor.echo_error("Unknown Git subcommand: #{sub}")
|
|
56
|
+
return
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
public_send(method, ctx, argv: argv[1..], kwargs: {}, bang: false, count: 1)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def git_close_buffer(ctx, **)
|
|
63
|
+
buf_id = ctx.buffer.id
|
|
64
|
+
ctx.editor.git_stream_stop_handler&.call(buf_id)
|
|
65
|
+
ctx.editor.delete_buffer(buf_id)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def git_resolve_path(ctx)
|
|
71
|
+
path = ctx.buffer.path
|
|
72
|
+
return path if path && File.exist?(path)
|
|
73
|
+
dir = Dir.pwd
|
|
74
|
+
File.directory?(dir) ? dir : nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def bind_git_buffer_keys(editor, buffer_id)
|
|
78
|
+
km = editor.keymap_manager
|
|
79
|
+
km.bind_buffer(buffer_id, "\e", "git.close_buffer")
|
|
80
|
+
km.bind_buffer(buffer_id, "<C-c>", "git.close_buffer")
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuVim
|
|
4
|
+
module Git
|
|
5
|
+
module Log
|
|
6
|
+
# Command handler methods
|
|
7
|
+
module HandlerMethods
|
|
8
|
+
def git_log(ctx, argv: [], **)
|
|
9
|
+
file_path = git_resolve_path(ctx)
|
|
10
|
+
unless file_path
|
|
11
|
+
ctx.editor.echo_error("No file or directory to resolve git repo")
|
|
12
|
+
return
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
root, err = Git.repo_root(file_path)
|
|
16
|
+
unless root
|
|
17
|
+
ctx.editor.echo_error("git log: #{err}")
|
|
18
|
+
return
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
filetype = argv.include?("-p") ? "diff" : nil
|
|
22
|
+
buf = ctx.editor.add_virtual_buffer(
|
|
23
|
+
kind: :git_log,
|
|
24
|
+
name: "[Git Log]",
|
|
25
|
+
lines: [""],
|
|
26
|
+
filetype: filetype,
|
|
27
|
+
readonly: true,
|
|
28
|
+
modifiable: false
|
|
29
|
+
)
|
|
30
|
+
buf.options["git_repo_root"] = root
|
|
31
|
+
ctx.editor.switch_to_buffer(buf.id)
|
|
32
|
+
bind_git_buffer_keys(ctx.editor, buf.id)
|
|
33
|
+
ctx.editor.echo("[Git Log] loading...")
|
|
34
|
+
|
|
35
|
+
cmd = ["git", "log", *argv]
|
|
36
|
+
ctx.editor.git_stream_handler&.call(buf.id, cmd, root)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
5
|
+
module RuVim
|
|
6
|
+
module Git
|
|
7
|
+
module Status
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# Run git status.
|
|
11
|
+
# Returns [lines, root, error_message].
|
|
12
|
+
def run(file_path)
|
|
13
|
+
root, err = Git.repo_root(file_path)
|
|
14
|
+
return [nil, nil, err] unless root
|
|
15
|
+
|
|
16
|
+
out, err, status = Open3.capture3("git", "status", chdir: root)
|
|
17
|
+
unless status.success?
|
|
18
|
+
return [nil, nil, err.strip]
|
|
19
|
+
end
|
|
20
|
+
[out.lines(chomp: true), root, nil]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Extract filename from a git status output line.
|
|
24
|
+
# Returns relative path or nil.
|
|
25
|
+
def parse_filename(line)
|
|
26
|
+
stripped = line.to_s.strip
|
|
27
|
+
case stripped
|
|
28
|
+
when /\A(?:modified|new file|deleted|renamed|copied|typechange):\s+(.+)/
|
|
29
|
+
$1.strip
|
|
30
|
+
when /\A(\S.+)/
|
|
31
|
+
# Untracked file lines (no prefix keyword)
|
|
32
|
+
path = $1.strip
|
|
33
|
+
# Skip section headers and hints
|
|
34
|
+
return nil if path.start_with?("(")
|
|
35
|
+
return nil if path.match?(/\A[A-Z]/)
|
|
36
|
+
path
|
|
37
|
+
else
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Command handler methods
|
|
43
|
+
module HandlerMethods
|
|
44
|
+
def git_status(ctx, **)
|
|
45
|
+
file_path = git_resolve_path(ctx)
|
|
46
|
+
unless file_path
|
|
47
|
+
ctx.editor.echo_error("No file or directory to resolve git repo")
|
|
48
|
+
return
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
lines, root, err = Status.run(file_path)
|
|
52
|
+
unless lines
|
|
53
|
+
ctx.editor.echo_error("git status: #{err}")
|
|
54
|
+
return
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
buf = ctx.editor.add_virtual_buffer(
|
|
58
|
+
kind: :git_status,
|
|
59
|
+
name: "[Git Status]",
|
|
60
|
+
lines: lines,
|
|
61
|
+
readonly: true,
|
|
62
|
+
modifiable: false
|
|
63
|
+
)
|
|
64
|
+
buf.options["git_repo_root"] = root
|
|
65
|
+
ctx.editor.switch_to_buffer(buf.id)
|
|
66
|
+
bind_git_buffer_keys(ctx.editor, buf.id)
|
|
67
|
+
ctx.editor.echo("[Git Status]")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def git_status_open_file(ctx, **)
|
|
71
|
+
buf = ctx.buffer
|
|
72
|
+
unless buf.kind == :git_status
|
|
73
|
+
ctx.editor.echo_error("Not a git status buffer")
|
|
74
|
+
return
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
line = buf.line_at(ctx.window.cursor_y)
|
|
78
|
+
filename = Status.parse_filename(line)
|
|
79
|
+
unless filename
|
|
80
|
+
ctx.editor.echo_error("No file on this line")
|
|
81
|
+
return
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
root = buf.options["git_repo_root"]
|
|
85
|
+
full_path = File.join(root, filename)
|
|
86
|
+
unless File.exist?(full_path)
|
|
87
|
+
ctx.editor.echo_error("File not found: #{filename}")
|
|
88
|
+
return
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
existing = ctx.editor.buffers.values.find { |b| b.path == full_path }
|
|
92
|
+
if existing
|
|
93
|
+
ctx.editor.switch_to_buffer(existing.id)
|
|
94
|
+
else
|
|
95
|
+
new_buf = ctx.editor.add_buffer_from_file(full_path)
|
|
96
|
+
ctx.editor.switch_to_buffer(new_buf.id)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -467,6 +467,8 @@ module RuVim
|
|
|
467
467
|
when "k" then delete_lines_up(ctx, ncount)
|
|
468
468
|
when "$" then delete_to_end_of_line(ctx)
|
|
469
469
|
when "w" then delete_word_forward(ctx, ncount)
|
|
470
|
+
when "G" then delete_lines_to_end(ctx)
|
|
471
|
+
when "gg" then delete_lines_to_start(ctx)
|
|
470
472
|
when "iw" then delete_text_object_word(ctx, around: false)
|
|
471
473
|
when "aw" then delete_text_object_word(ctx, around: true)
|
|
472
474
|
else
|
|
@@ -478,9 +480,22 @@ module RuVim
|
|
|
478
480
|
|
|
479
481
|
def change_motion(ctx, count:, kwargs:, **)
|
|
480
482
|
materialize_intro_buffer_if_needed(ctx)
|
|
481
|
-
|
|
482
|
-
|
|
483
|
+
motion = (kwargs[:motion] || kwargs["motion"]).to_s
|
|
484
|
+
result = delete_motion(ctx, count:, kwargs:)
|
|
485
|
+
return unless result
|
|
483
486
|
|
|
487
|
+
if result == :linewise
|
|
488
|
+
case motion
|
|
489
|
+
when "G"
|
|
490
|
+
y = ctx.buffer.lines.length
|
|
491
|
+
ctx.buffer.insert_lines_at(y, [""])
|
|
492
|
+
ctx.window.cursor_y = y
|
|
493
|
+
when "gg"
|
|
494
|
+
ctx.buffer.insert_lines_at(0, [""])
|
|
495
|
+
ctx.window.cursor_y = 0
|
|
496
|
+
end
|
|
497
|
+
ctx.window.cursor_x = 0
|
|
498
|
+
end
|
|
484
499
|
enter_insert_mode(ctx)
|
|
485
500
|
end
|
|
486
501
|
|
|
@@ -610,6 +625,10 @@ module RuVim
|
|
|
610
625
|
text = ctx.buffer.span_text(y, x, target[:row], target[:col])
|
|
611
626
|
store_yank_register(ctx, text:, type: :charwise)
|
|
612
627
|
ctx.editor.echo("yanked")
|
|
628
|
+
when "G"
|
|
629
|
+
yank_lines_to_end(ctx)
|
|
630
|
+
when "gg"
|
|
631
|
+
yank_lines_to_start(ctx)
|
|
613
632
|
when "iw"
|
|
614
633
|
yank_text_object_word(ctx, around: false)
|
|
615
634
|
when "aw"
|
|
@@ -756,6 +775,11 @@ module RuVim
|
|
|
756
775
|
end
|
|
757
776
|
|
|
758
777
|
def file_write(ctx, argv:, bang:, **)
|
|
778
|
+
if ctx.buffer.kind == :git_commit
|
|
779
|
+
git_commit_execute(ctx)
|
|
780
|
+
return
|
|
781
|
+
end
|
|
782
|
+
|
|
759
783
|
path = argv[0]
|
|
760
784
|
target = ctx.buffer.write_to(path)
|
|
761
785
|
size = File.exist?(target) ? File.size(target) : 0
|
|
@@ -767,6 +791,22 @@ module RuVim
|
|
|
767
791
|
end
|
|
768
792
|
|
|
769
793
|
def app_quit(ctx, bang:, **)
|
|
794
|
+
if ctx.buffer.kind == :filter
|
|
795
|
+
saved_y = ctx.buffer.options["filter_source_cursor_y"]
|
|
796
|
+
saved_x = ctx.buffer.options["filter_source_cursor_x"]
|
|
797
|
+
saved_row_offset = ctx.buffer.options["filter_source_row_offset"]
|
|
798
|
+
saved_col_offset = ctx.buffer.options["filter_source_col_offset"]
|
|
799
|
+
ctx.editor.delete_buffer(ctx.buffer.id)
|
|
800
|
+
if saved_y
|
|
801
|
+
win = ctx.editor.current_window
|
|
802
|
+
win.cursor_y = saved_y
|
|
803
|
+
win.cursor_x = saved_x || 0
|
|
804
|
+
win.row_offset = saved_row_offset || 0
|
|
805
|
+
win.col_offset = saved_col_offset || 0
|
|
806
|
+
end
|
|
807
|
+
return
|
|
808
|
+
end
|
|
809
|
+
|
|
770
810
|
if ctx.editor.window_count > 1
|
|
771
811
|
ctx.editor.close_current_window
|
|
772
812
|
ctx.editor.echo("closed window")
|
|
@@ -1336,6 +1376,69 @@ module RuVim
|
|
|
1336
1376
|
RuVim::RichView.toggle!(ctx.editor)
|
|
1337
1377
|
end
|
|
1338
1378
|
|
|
1379
|
+
def rich_view_close_buffer(ctx, **)
|
|
1380
|
+
ctx.editor.delete_buffer(ctx.buffer.id)
|
|
1381
|
+
end
|
|
1382
|
+
|
|
1383
|
+
def search_filter(ctx, **)
|
|
1384
|
+
editor = ctx.editor
|
|
1385
|
+
search = editor.last_search
|
|
1386
|
+
unless search
|
|
1387
|
+
editor.echo_error("No search pattern")
|
|
1388
|
+
return
|
|
1389
|
+
end
|
|
1390
|
+
|
|
1391
|
+
regex = compile_search_regex(search[:pattern], editor: editor, window: ctx.window, buffer: ctx.buffer)
|
|
1392
|
+
source_buffer = ctx.buffer
|
|
1393
|
+
|
|
1394
|
+
# Collect matching lines with origin mapping
|
|
1395
|
+
origins = []
|
|
1396
|
+
matching_lines = []
|
|
1397
|
+
source_buffer.lines.each_with_index do |line, row|
|
|
1398
|
+
if regex.match?(line)
|
|
1399
|
+
# If source is a filter buffer, chain back to the original
|
|
1400
|
+
if source_buffer.kind == :filter && source_buffer.options["filter_origins"]
|
|
1401
|
+
origins << source_buffer.options["filter_origins"][row]
|
|
1402
|
+
else
|
|
1403
|
+
origins << { buffer_id: source_buffer.id, row: row }
|
|
1404
|
+
end
|
|
1405
|
+
matching_lines << line
|
|
1406
|
+
end
|
|
1407
|
+
end
|
|
1408
|
+
|
|
1409
|
+
if matching_lines.empty?
|
|
1410
|
+
editor.echo_error("Pattern not found: #{search[:pattern]}")
|
|
1411
|
+
return
|
|
1412
|
+
end
|
|
1413
|
+
|
|
1414
|
+
filetype = source_buffer.options["filetype"]
|
|
1415
|
+
filter_buf = editor.add_virtual_buffer(
|
|
1416
|
+
kind: :filter,
|
|
1417
|
+
name: "[Filter: /#{search[:pattern]}/]",
|
|
1418
|
+
lines: matching_lines,
|
|
1419
|
+
filetype: filetype,
|
|
1420
|
+
readonly: false,
|
|
1421
|
+
modifiable: false
|
|
1422
|
+
)
|
|
1423
|
+
filter_buf.options["filter_origins"] = origins
|
|
1424
|
+
filter_buf.options["filter_source_buffer_id"] = source_buffer.id
|
|
1425
|
+
filter_buf.options["filter_source_cursor_y"] = ctx.window.cursor_y
|
|
1426
|
+
filter_buf.options["filter_source_cursor_x"] = ctx.window.cursor_x
|
|
1427
|
+
filter_buf.options["filter_source_row_offset"] = ctx.window.row_offset
|
|
1428
|
+
filter_buf.options["filter_source_col_offset"] = ctx.window.col_offset
|
|
1429
|
+
editor.switch_to_buffer(filter_buf.id)
|
|
1430
|
+
editor.echo("filter: #{matching_lines.length} line(s)")
|
|
1431
|
+
end
|
|
1432
|
+
|
|
1433
|
+
def ex_filter(ctx, argv:, **)
|
|
1434
|
+
if argv.any?
|
|
1435
|
+
pattern = parse_vimgrep_pattern(argv.join(" "))
|
|
1436
|
+
editor = ctx.editor
|
|
1437
|
+
editor.set_last_search(pattern: pattern, direction: :forward)
|
|
1438
|
+
end
|
|
1439
|
+
search_filter(ctx)
|
|
1440
|
+
end
|
|
1441
|
+
|
|
1339
1442
|
def submit_search(ctx, pattern:, direction:)
|
|
1340
1443
|
text = pattern.to_s
|
|
1341
1444
|
if text.empty?
|
|
@@ -1349,6 +1452,8 @@ module RuVim
|
|
|
1349
1452
|
move_to_search(ctx, pattern: text, direction:, count: 1)
|
|
1350
1453
|
end
|
|
1351
1454
|
|
|
1455
|
+
include RuVim::Git::Handler
|
|
1456
|
+
|
|
1352
1457
|
private
|
|
1353
1458
|
|
|
1354
1459
|
def reindent_range(ctx, start_row, end_row)
|
|
@@ -1723,6 +1828,48 @@ module RuVim
|
|
|
1723
1828
|
true
|
|
1724
1829
|
end
|
|
1725
1830
|
|
|
1831
|
+
def delete_lines_to_end(ctx)
|
|
1832
|
+
y = ctx.window.cursor_y
|
|
1833
|
+
total = ctx.buffer.lines.length - y
|
|
1834
|
+
deleted = ctx.buffer.line_block_text(y, total)
|
|
1835
|
+
ctx.buffer.begin_change_group
|
|
1836
|
+
total.times { ctx.buffer.delete_line(y) }
|
|
1837
|
+
ctx.buffer.end_change_group
|
|
1838
|
+
store_delete_register(ctx, text: deleted, type: :linewise)
|
|
1839
|
+
ctx.window.clamp_to_buffer(ctx.buffer)
|
|
1840
|
+
:linewise
|
|
1841
|
+
end
|
|
1842
|
+
|
|
1843
|
+
def delete_lines_to_start(ctx)
|
|
1844
|
+
y = ctx.window.cursor_y
|
|
1845
|
+
total = y + 1
|
|
1846
|
+
deleted = ctx.buffer.line_block_text(0, total)
|
|
1847
|
+
ctx.buffer.begin_change_group
|
|
1848
|
+
total.times { ctx.buffer.delete_line(0) }
|
|
1849
|
+
ctx.buffer.end_change_group
|
|
1850
|
+
store_delete_register(ctx, text: deleted, type: :linewise)
|
|
1851
|
+
ctx.window.cursor_y = 0
|
|
1852
|
+
ctx.window.cursor_x = 0
|
|
1853
|
+
ctx.window.clamp_to_buffer(ctx.buffer)
|
|
1854
|
+
:linewise
|
|
1855
|
+
end
|
|
1856
|
+
|
|
1857
|
+
def yank_lines_to_end(ctx)
|
|
1858
|
+
y = ctx.window.cursor_y
|
|
1859
|
+
total = ctx.buffer.lines.length - y
|
|
1860
|
+
text = ctx.buffer.line_block_text(y, total)
|
|
1861
|
+
store_yank_register(ctx, text: text, type: :linewise)
|
|
1862
|
+
ctx.editor.echo("#{total} line(s) yanked")
|
|
1863
|
+
end
|
|
1864
|
+
|
|
1865
|
+
def yank_lines_to_start(ctx)
|
|
1866
|
+
y = ctx.window.cursor_y
|
|
1867
|
+
total = y + 1
|
|
1868
|
+
text = ctx.buffer.line_block_text(0, total)
|
|
1869
|
+
store_yank_register(ctx, text: text, type: :linewise)
|
|
1870
|
+
ctx.editor.echo("#{total} line(s) yanked")
|
|
1871
|
+
end
|
|
1872
|
+
|
|
1726
1873
|
def delete_to_end_of_line(ctx)
|
|
1727
1874
|
y = ctx.window.cursor_y
|
|
1728
1875
|
x = ctx.window.cursor_x
|
|
@@ -1754,22 +1901,22 @@ module RuVim
|
|
|
1754
1901
|
end
|
|
1755
1902
|
|
|
1756
1903
|
def delete_text_object_word(ctx, around:)
|
|
1757
|
-
|
|
1758
|
-
return false unless span
|
|
1759
|
-
|
|
1760
|
-
text = ctx.buffer.span_text(span[:start_row], span[:start_col], span[:end_row], span[:end_col])
|
|
1761
|
-
ctx.buffer.begin_change_group
|
|
1762
|
-
ctx.buffer.delete_span(span[:start_row], span[:start_col], span[:end_row], span[:end_col])
|
|
1763
|
-
ctx.buffer.end_change_group
|
|
1764
|
-
store_delete_register(ctx, text:, type: :charwise) unless text.empty?
|
|
1765
|
-
ctx.window.cursor_y = span[:start_row]
|
|
1766
|
-
ctx.window.cursor_x = span[:start_col]
|
|
1767
|
-
ctx.window.clamp_to_buffer(ctx.buffer)
|
|
1768
|
-
true
|
|
1904
|
+
delete_span(ctx, word_object_span(ctx.buffer, ctx.window, around:))
|
|
1769
1905
|
end
|
|
1770
1906
|
|
|
1771
1907
|
def delete_text_object(ctx, motion)
|
|
1772
|
-
|
|
1908
|
+
delete_span(ctx, text_object_span(ctx.buffer, ctx.window, motion))
|
|
1909
|
+
end
|
|
1910
|
+
|
|
1911
|
+
def yank_text_object_word(ctx, around:)
|
|
1912
|
+
yank_span(ctx, word_object_span(ctx.buffer, ctx.window, around:))
|
|
1913
|
+
end
|
|
1914
|
+
|
|
1915
|
+
def yank_text_object(ctx, motion)
|
|
1916
|
+
yank_span(ctx, text_object_span(ctx.buffer, ctx.window, motion))
|
|
1917
|
+
end
|
|
1918
|
+
|
|
1919
|
+
def delete_span(ctx, span)
|
|
1773
1920
|
return false unless span
|
|
1774
1921
|
|
|
1775
1922
|
text = ctx.buffer.span_text(span[:start_row], span[:start_col], span[:end_row], span[:end_col])
|
|
@@ -1783,18 +1930,7 @@ module RuVim
|
|
|
1783
1930
|
true
|
|
1784
1931
|
end
|
|
1785
1932
|
|
|
1786
|
-
def
|
|
1787
|
-
span = word_object_span(ctx.buffer, ctx.window, around:)
|
|
1788
|
-
return false unless span
|
|
1789
|
-
|
|
1790
|
-
text = ctx.buffer.span_text(span[:start_row], span[:start_col], span[:end_row], span[:end_col])
|
|
1791
|
-
store_yank_register(ctx, text:, type: :charwise) unless text.empty?
|
|
1792
|
-
ctx.editor.echo("yanked")
|
|
1793
|
-
true
|
|
1794
|
-
end
|
|
1795
|
-
|
|
1796
|
-
def yank_text_object(ctx, motion)
|
|
1797
|
-
span = text_object_span(ctx.buffer, ctx.window, motion)
|
|
1933
|
+
def yank_span(ctx, span)
|
|
1798
1934
|
return false unless span
|
|
1799
1935
|
|
|
1800
1936
|
text = ctx.buffer.span_text(span[:start_row], span[:start_col], span[:end_row], span[:end_col])
|
|
@@ -1979,9 +2115,9 @@ module RuVim
|
|
|
1979
2115
|
x = [window.cursor_x, line.length - 1].min
|
|
1980
2116
|
return nil if x.negative?
|
|
1981
2117
|
|
|
1982
|
-
left =
|
|
2118
|
+
left = find_quote(line, x, quote, :left)
|
|
1983
2119
|
right_from = [x, (left ? left + 1 : 0)].max
|
|
1984
|
-
right =
|
|
2120
|
+
right = find_quote(line, right_from, quote, :right)
|
|
1985
2121
|
return nil unless left && right && left < right
|
|
1986
2122
|
|
|
1987
2123
|
if around
|
|
@@ -2053,20 +2189,18 @@ module RuVim
|
|
|
2053
2189
|
end
|
|
2054
2190
|
end
|
|
2055
2191
|
|
|
2056
|
-
def
|
|
2192
|
+
def find_quote(line, x, quote, direction)
|
|
2057
2193
|
i = x
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
return i if line[i] == quote && !escaped?(line, i)
|
|
2069
|
-
i += 1
|
|
2194
|
+
if direction == :left
|
|
2195
|
+
while i >= 0
|
|
2196
|
+
return i if line[i] == quote && !escaped?(line, i)
|
|
2197
|
+
i -= 1
|
|
2198
|
+
end
|
|
2199
|
+
else
|
|
2200
|
+
while i < line.length
|
|
2201
|
+
return i if line[i] == quote && !escaped?(line, i)
|
|
2202
|
+
i += 1
|
|
2203
|
+
end
|
|
2070
2204
|
end
|
|
2071
2205
|
nil
|
|
2072
2206
|
end
|
data/lib/ruvim/highlighter.rb
CHANGED
|
@@ -19,12 +19,14 @@ module RuVim
|
|
|
19
19
|
case ft
|
|
20
20
|
when "ruby"
|
|
21
21
|
Lang::Ruby.color_columns(text)
|
|
22
|
-
when "json"
|
|
22
|
+
when "json", "jsonl"
|
|
23
23
|
Lang::Json.color_columns(text)
|
|
24
24
|
when "markdown"
|
|
25
25
|
Lang::Markdown.color_columns(text)
|
|
26
26
|
when "scheme"
|
|
27
27
|
Lang::Scheme.color_columns(text)
|
|
28
|
+
when "diff"
|
|
29
|
+
Lang::Diff.color_columns(text)
|
|
28
30
|
else
|
|
29
31
|
{}
|
|
30
32
|
end
|