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.
Files changed (129) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +68 -7
  3. data/README.md +30 -7
  4. data/Rakefile +7 -0
  5. data/benchmark/cext_compare.rb +165 -0
  6. data/benchmark/chunked_load.rb +256 -0
  7. data/benchmark/file_load.rb +140 -0
  8. data/benchmark/hotspots.rb +178 -0
  9. data/docs/binding.md +18 -1
  10. data/docs/command.md +156 -10
  11. data/docs/config.md +10 -2
  12. data/docs/done.md +23 -0
  13. data/docs/spec.md +162 -25
  14. data/docs/todo.md +9 -0
  15. data/docs/tutorial.md +33 -1
  16. data/docs/vim_diff.md +31 -8
  17. data/ext/ruvim/extconf.rb +5 -0
  18. data/ext/ruvim/ruvim_ext.c +519 -0
  19. data/lib/ruvim/app.rb +246 -2525
  20. data/lib/ruvim/browser.rb +104 -0
  21. data/lib/ruvim/buffer.rb +43 -20
  22. data/lib/ruvim/cli.rb +6 -0
  23. data/lib/ruvim/command_invocation.rb +2 -2
  24. data/lib/ruvim/completion_manager.rb +708 -0
  25. data/lib/ruvim/dispatcher.rb +14 -8
  26. data/lib/ruvim/display_width.rb +91 -45
  27. data/lib/ruvim/editor.rb +74 -80
  28. data/lib/ruvim/ex_command_registry.rb +3 -1
  29. data/lib/ruvim/file_watcher.rb +243 -0
  30. data/lib/ruvim/gh/link.rb +207 -0
  31. data/lib/ruvim/git/blame.rb +255 -0
  32. data/lib/ruvim/git/branch.rb +112 -0
  33. data/lib/ruvim/git/commit.rb +102 -0
  34. data/lib/ruvim/git/diff.rb +129 -0
  35. data/lib/ruvim/git/grep.rb +107 -0
  36. data/lib/ruvim/git/handler.rb +125 -0
  37. data/lib/ruvim/git/log.rb +41 -0
  38. data/lib/ruvim/git/status.rb +103 -0
  39. data/lib/ruvim/global_commands.rb +351 -77
  40. data/lib/ruvim/highlighter.rb +4 -11
  41. data/lib/ruvim/input.rb +1 -0
  42. data/lib/ruvim/key_handler.rb +1510 -0
  43. data/lib/ruvim/keymap_manager.rb +7 -7
  44. data/lib/ruvim/lang/base.rb +5 -0
  45. data/lib/ruvim/lang/c.rb +116 -0
  46. data/lib/ruvim/lang/cpp.rb +107 -0
  47. data/lib/ruvim/lang/csv.rb +4 -1
  48. data/lib/ruvim/lang/diff.rb +43 -0
  49. data/lib/ruvim/lang/dockerfile.rb +36 -0
  50. data/lib/ruvim/lang/elixir.rb +85 -0
  51. data/lib/ruvim/lang/erb.rb +30 -0
  52. data/lib/ruvim/lang/go.rb +83 -0
  53. data/lib/ruvim/lang/html.rb +34 -0
  54. data/lib/ruvim/lang/javascript.rb +83 -0
  55. data/lib/ruvim/lang/json.rb +40 -0
  56. data/lib/ruvim/lang/lua.rb +76 -0
  57. data/lib/ruvim/lang/makefile.rb +36 -0
  58. data/lib/ruvim/lang/markdown.rb +3 -4
  59. data/lib/ruvim/lang/ocaml.rb +77 -0
  60. data/lib/ruvim/lang/perl.rb +91 -0
  61. data/lib/ruvim/lang/python.rb +85 -0
  62. data/lib/ruvim/lang/registry.rb +102 -0
  63. data/lib/ruvim/lang/ruby.rb +7 -0
  64. data/lib/ruvim/lang/rust.rb +95 -0
  65. data/lib/ruvim/lang/scheme.rb +5 -0
  66. data/lib/ruvim/lang/sh.rb +76 -0
  67. data/lib/ruvim/lang/sql.rb +52 -0
  68. data/lib/ruvim/lang/toml.rb +36 -0
  69. data/lib/ruvim/lang/tsv.rb +4 -1
  70. data/lib/ruvim/lang/typescript.rb +53 -0
  71. data/lib/ruvim/lang/yaml.rb +62 -0
  72. data/lib/ruvim/rich_view/json_renderer.rb +131 -0
  73. data/lib/ruvim/rich_view/jsonl_renderer.rb +57 -0
  74. data/lib/ruvim/rich_view/table_renderer.rb +3 -3
  75. data/lib/ruvim/rich_view.rb +30 -7
  76. data/lib/ruvim/screen.rb +135 -84
  77. data/lib/ruvim/stream/file_load.rb +85 -0
  78. data/lib/ruvim/stream/follow.rb +40 -0
  79. data/lib/ruvim/stream/git.rb +43 -0
  80. data/lib/ruvim/stream/run.rb +74 -0
  81. data/lib/ruvim/stream/stdin.rb +55 -0
  82. data/lib/ruvim/stream.rb +35 -0
  83. data/lib/ruvim/stream_mixer.rb +394 -0
  84. data/lib/ruvim/terminal.rb +18 -4
  85. data/lib/ruvim/text_metrics.rb +84 -65
  86. data/lib/ruvim/version.rb +1 -1
  87. data/lib/ruvim/window.rb +5 -5
  88. data/lib/ruvim.rb +31 -4
  89. data/test/app_command_test.rb +382 -0
  90. data/test/app_completion_test.rb +65 -16
  91. data/test/app_dot_repeat_test.rb +27 -3
  92. data/test/app_ex_command_test.rb +154 -0
  93. data/test/app_motion_test.rb +13 -12
  94. data/test/app_register_test.rb +2 -1
  95. data/test/app_scenario_test.rb +182 -8
  96. data/test/app_startup_test.rb +70 -27
  97. data/test/app_text_object_test.rb +2 -1
  98. data/test/app_unicode_behavior_test.rb +3 -2
  99. data/test/browser_test.rb +88 -0
  100. data/test/buffer_test.rb +24 -0
  101. data/test/cli_test.rb +77 -0
  102. data/test/clipboard_test.rb +67 -0
  103. data/test/command_invocation_test.rb +33 -0
  104. data/test/command_line_test.rb +118 -0
  105. data/test/config_dsl_test.rb +134 -0
  106. data/test/dispatcher_test.rb +74 -4
  107. data/test/display_width_test.rb +41 -0
  108. data/test/ex_command_registry_test.rb +106 -0
  109. data/test/file_watcher_test.rb +197 -0
  110. data/test/follow_test.rb +198 -0
  111. data/test/gh_link_test.rb +141 -0
  112. data/test/git_blame_test.rb +792 -0
  113. data/test/git_grep_test.rb +64 -0
  114. data/test/highlighter_test.rb +169 -0
  115. data/test/indent_test.rb +223 -0
  116. data/test/input_screen_integration_test.rb +1 -1
  117. data/test/keyword_chars_test.rb +85 -0
  118. data/test/lang_test.rb +634 -0
  119. data/test/markdown_renderer_test.rb +5 -5
  120. data/test/on_save_hook_test.rb +12 -8
  121. data/test/render_snapshot_test.rb +78 -0
  122. data/test/rich_view_test.rb +279 -23
  123. data/test/run_command_test.rb +307 -0
  124. data/test/screen_test.rb +68 -5
  125. data/test/search_option_test.rb +19 -0
  126. data/test/stream_test.rb +165 -0
  127. data/test/test_helper.rb +9 -0
  128. data/test/window_test.rb +59 -0
  129. metadata +68 -2
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
5
+ module RuVim
6
+ module Git
7
+ module Branch
8
+ module_function
9
+
10
+ # Run git branch listing sorted by most recent commit.
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(
17
+ "git", "branch", "-a",
18
+ "--sort=-committerdate",
19
+ "--format=%(if)%(HEAD)%(then)* %(else) %(end)%(refname:short)\t%(committerdate:short)\t%(subject)",
20
+ chdir: root
21
+ )
22
+ unless status.success?
23
+ return [nil, nil, err.strip]
24
+ end
25
+ [out.lines(chomp: true), root, nil]
26
+ end
27
+
28
+ # Parse branch name from a branch list line.
29
+ # Returns branch name or nil.
30
+ def parse_branch_name(line)
31
+ stripped = line.to_s.strip
32
+ return nil if stripped.empty?
33
+
34
+ # Remove leading "* " marker for current branch
35
+ name = stripped.sub(/\A\*\s*/, "")
36
+ # Branch name is everything before the first tab
37
+ name = name.split("\t", 2).first
38
+ name&.strip
39
+ end
40
+
41
+ # Command handler methods
42
+ module HandlerMethods
43
+ def git_branch(ctx, **)
44
+ file_path = git_resolve_path(ctx)
45
+ unless file_path
46
+ ctx.editor.echo_error("No file or directory to resolve git repo")
47
+ return
48
+ end
49
+
50
+ lines, root, err = Branch.run(file_path)
51
+ unless lines
52
+ ctx.editor.echo_error("git branch: #{err}")
53
+ return
54
+ end
55
+
56
+ buf = ctx.editor.add_virtual_buffer(
57
+ kind: :git_branch,
58
+ name: "[Git Branch]",
59
+ lines: lines,
60
+ readonly: true,
61
+ modifiable: false
62
+ )
63
+ buf.options["git_repo_root"] = root
64
+ ctx.editor.switch_to_buffer(buf.id)
65
+ bind_git_buffer_keys(ctx.editor, buf.id)
66
+ ctx.editor.echo("[Git Branch]")
67
+ end
68
+
69
+ def git_branch_checkout(ctx, **)
70
+ buf = ctx.buffer
71
+ unless buf.kind == :git_branch
72
+ ctx.editor.echo_error("Not a git branch buffer")
73
+ return
74
+ end
75
+
76
+ line = buf.line_at(ctx.window.cursor_y)
77
+ branch = Branch.parse_branch_name(line)
78
+ unless branch
79
+ ctx.editor.echo_error("No branch on this line")
80
+ return
81
+ end
82
+
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
+
98
+ _out, err, status = Open3.capture3("git", "checkout", branch, chdir: root)
99
+ unless status.success?
100
+ raise RuVim::CommandError, "git checkout: #{err.strip}"
101
+ end
102
+
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
107
+ ctx.editor.echo("Switched to branch '#{branch}'")
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
5
+ module RuVim
6
+ module Git
7
+ module Commit
8
+ module_function
9
+
10
+ # Build the initial content for the commit message buffer.
11
+ # Returns [lines, root, error_message].
12
+ def prepare(file_path)
13
+ root, err = Git.repo_root(file_path)
14
+ return [nil, nil, err] unless root
15
+
16
+ status_out, err, status = Open3.capture3("git", "status", chdir: root)
17
+ unless status.success?
18
+ return [nil, nil, err.strip]
19
+ end
20
+
21
+ lines = [""]
22
+ lines << "# Enter commit message above. Lines starting with '#' are ignored."
23
+ lines << "# Close with :wq to commit, :q! to cancel."
24
+ lines << "#"
25
+ status_out.each_line(chomp: true) { |l| lines << "# #{l}" }
26
+ [lines, root, nil]
27
+ end
28
+
29
+ # Extract commit message from buffer lines (skip # comment lines and trim).
30
+ def extract_message(lines)
31
+ msg_lines = lines.reject { |l| l.start_with?("#") }
32
+ # Strip trailing blank lines
33
+ msg_lines.pop while msg_lines.last&.empty?
34
+ msg_lines.join("\n")
35
+ end
36
+
37
+ # Execute git commit with the given message.
38
+ # Returns [success, output_or_error].
39
+ def execute(root, message)
40
+ out, err, status = Open3.capture3("git", "commit", "-m", message, chdir: root)
41
+ if status.success?
42
+ [true, out.strip]
43
+ else
44
+ [false, err.strip]
45
+ end
46
+ end
47
+
48
+ # Command handler methods
49
+ module HandlerMethods
50
+ def git_commit(ctx, **)
51
+ file_path = git_resolve_path(ctx)
52
+ unless file_path
53
+ ctx.editor.echo_error("No file or directory to resolve git repo")
54
+ return
55
+ end
56
+
57
+ lines, root, err = Commit.prepare(file_path)
58
+ unless lines
59
+ ctx.editor.echo_error("git commit: #{err}")
60
+ return
61
+ end
62
+
63
+ buf = ctx.editor.add_virtual_buffer(
64
+ kind: :git_commit,
65
+ name: "[Commit Message]",
66
+ lines: lines,
67
+ readonly: false,
68
+ modifiable: true
69
+ )
70
+ buf.options["git_repo_root"] = root
71
+ ctx.editor.switch_to_buffer(buf.id)
72
+ ctx.editor.enter_insert_mode
73
+ ctx.editor.echo("[Commit Message] :wq to commit, :q! to cancel")
74
+ end
75
+
76
+ def git_commit_execute(ctx, **)
77
+ buf = ctx.buffer
78
+ unless buf.kind == :git_commit
79
+ ctx.editor.echo_error("Not a git commit buffer")
80
+ return
81
+ end
82
+
83
+ message = Commit.extract_message(buf.lines)
84
+ if message.empty?
85
+ ctx.editor.echo_error("Empty commit message, aborting")
86
+ return
87
+ end
88
+
89
+ root = buf.options["git_repo_root"]
90
+ success, output = Commit.execute(root, message)
91
+ ctx.editor.delete_buffer(buf.id)
92
+
93
+ if success
94
+ ctx.editor.echo(output)
95
+ else
96
+ ctx.editor.echo_error("git commit: #{output}")
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -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,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
@@ -0,0 +1,125 @@
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
+ "checkout" => :git_branch_execute_checkout,
31
+ "commit" => :git_commit,
32
+ "grep" => :git_grep,
33
+ }.freeze
34
+
35
+ include Blame::HandlerMethods
36
+ include Status::HandlerMethods
37
+ include Diff::HandlerMethods
38
+ include Log::HandlerMethods
39
+ include Branch::HandlerMethods
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
49
+
50
+ def enter_git_command_mode(ctx, **)
51
+ ctx.editor.enter_command_line_mode(":")
52
+ ctx.editor.command_line.replace_text("git ")
53
+ ctx.editor.clear_message
54
+ end
55
+
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
+
59
+ sub = argv.first.to_s.downcase
60
+ if sub.empty?
61
+ ctx.editor.echo("Git subcommands: #{GIT_SUBCOMMANDS.keys.join(', ')}")
62
+ return
63
+ end
64
+
65
+ method = GIT_SUBCOMMANDS[sub]
66
+ unless method
67
+ run_shell_fallback(ctx, "git", argv)
68
+ return
69
+ end
70
+
71
+ public_send(method, ctx, argv: argv[1..], kwargs: {}, bang: false, count: 1)
72
+ end
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
+
92
+ def git_close_buffer(ctx, **)
93
+ buf_id = ctx.buffer.id
94
+ ctx.editor.git_stream_stop_handler&.call(buf_id)
95
+ ctx.editor.delete_buffer(buf_id)
96
+ end
97
+
98
+ private
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
+
111
+ def git_resolve_path(ctx)
112
+ path = ctx.buffer.path
113
+ return path if path && File.exist?(path)
114
+ dir = Dir.pwd
115
+ File.directory?(dir) ? dir : nil
116
+ end
117
+
118
+ def bind_git_buffer_keys(editor, buffer_id)
119
+ km = editor.keymap_manager
120
+ km.bind_buffer(buffer_id, "\e", "git.close_buffer")
121
+ km.bind_buffer(buffer_id, "<C-c>", "git.close_buffer")
122
+ end
123
+ end
124
+ end
125
+ 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