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
@@ -21,9 +21,9 @@ module RuVim
21
21
  end
22
22
 
23
23
  def dispatch_ex(editor, line)
24
- raw = line.to_s.strip
24
+ raw = line.strip
25
25
  if raw.start_with?("!")
26
- command = raw[1..].to_s.strip
26
+ command = raw[1..].strip
27
27
  invocation = CommandInvocation.new(id: "__shell__", argv: [command])
28
28
  ctx = Context.new(editor:, invocation:)
29
29
  @command_host.ex_shell(ctx, command:)
@@ -52,15 +52,21 @@ module RuVim
52
52
  return if parsed.nil?
53
53
 
54
54
  spec = @ex_registry.fetch(parsed.name)
55
- validate_ex_args!(spec, parsed.argv, parsed.bang)
56
- invocation = CommandInvocation.new(id: spec.name, argv: parsed.argv, bang: parsed.bang)
55
+ argv = parsed.argv
56
+ if spec.raw_args
57
+ # Re-extract raw text after command name (preserving shell quoting)
58
+ raw_rest = rest.strip.sub(/\A\S+\s*/, "")
59
+ argv = raw_rest.empty? ? [] : [raw_rest]
60
+ end
61
+ validate_ex_args!(spec, argv, parsed.bang)
62
+ invocation = CommandInvocation.new(id: spec.name, argv: argv, bang: parsed.bang)
57
63
  ctx = Context.new(editor:, invocation:)
58
64
  range_kwargs = {}
59
65
  if range_result
60
66
  range_kwargs[:range_start] = range_result[:range_start]
61
67
  range_kwargs[:range_end] = range_result[:range_end]
62
68
  end
63
- @command_host.call(spec.call, ctx, argv: parsed.argv, bang: parsed.bang, count: 1, kwargs: range_kwargs)
69
+ @command_host.call(spec.call, ctx, argv: argv, bang: parsed.bang, count: 1, kwargs: range_kwargs)
64
70
  rescue StandardError => e
65
71
  editor.echo_error("Error: #{e.message}")
66
72
  ensure
@@ -68,7 +74,7 @@ module RuVim
68
74
  end
69
75
 
70
76
  def parse_ex(line)
71
- raw = line.to_s.strip
77
+ raw = line.strip
72
78
  return nil if raw.empty?
73
79
 
74
80
  tokens = Shellwords.shellsplit(raw)
@@ -85,7 +91,7 @@ module RuVim
85
91
  # Parse a substitute command: s/pat/repl/flags
86
92
  # Returns {pattern:, replacement:, flags_str:} or nil
87
93
  def parse_substitute(line)
88
- raw = line.to_s.strip
94
+ raw = line.strip
89
95
  return nil unless raw.match?(/\As[^a-zA-Z]/)
90
96
  return nil if raw.length < 2
91
97
 
@@ -189,7 +195,7 @@ module RuVim
189
195
  # Parse a range from the beginning of raw.
190
196
  # Returns {range_start:, range_end:, rest:} or nil.
191
197
  def parse_range(raw, editor)
192
- str = raw.to_s
198
+ str = raw
193
199
  return nil if str.empty?
194
200
 
195
201
  # % = whole file
@@ -1,24 +1,103 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Try loading the C extension (built by rake compile)
4
+ begin
5
+ require_relative "ruvim_ext"
6
+ rescue LoadError
7
+ # C extension not available — pure Ruby fallback below
8
+ end
9
+
3
10
  module RuVim
4
11
  module DisplayWidth
5
12
  module_function
6
13
 
7
- def cell_width(ch, col: 0, tabstop: 2)
8
- return 1 if ch.nil? || ch.empty?
14
+ if defined?(RuVim::DisplayWidthExt)
15
+ # ---- C extension paths ----
16
+
17
+ def cell_width(ch, col: 0, tabstop: 2)
18
+ sync_ambiguous_width
19
+ DisplayWidthExt.cell_width(ch, col:, tabstop:)
20
+ end
21
+
22
+ def display_width(str, tabstop: 2, start_col: 0)
23
+ sync_ambiguous_width
24
+ DisplayWidthExt.display_width(str, tabstop:, start_col:)
25
+ end
26
+
27
+ def expand_tabs(str, tabstop: 2, start_col: 0)
28
+ sync_ambiguous_width
29
+ DisplayWidthExt.expand_tabs(str, tabstop:, start_col:)
30
+ end
31
+
32
+ def ambiguous_width
33
+ sync_ambiguous_width
34
+ end
35
+
36
+ def sync_ambiguous_width
37
+ env = ::ENV["RUVIM_AMBIGUOUS_WIDTH"]
38
+ if !defined?(@ambiguous_width_cached) || @ambiguous_width_env != env
39
+ @ambiguous_width_env = env
40
+ @ambiguous_width_cached = (env == "2" ? 2 : 1)
41
+ DisplayWidthExt.set_ambiguous_width(@ambiguous_width_cached)
42
+ end
43
+ @ambiguous_width_cached
44
+ end
45
+ else
46
+ # ---- Pure Ruby fallback ----
47
+
48
+ def cell_width(ch, col: 0, tabstop: 2)
49
+ return 1 if ch.nil? || ch.empty?
50
+
51
+ if ch == "\t"
52
+ width = tabstop - (col % tabstop)
53
+ return width.zero? ? tabstop : width
54
+ end
55
+
56
+ # Fast path: byte length 1 means ASCII — always width 1
57
+ return 1 if ch.bytesize == 1
58
+
59
+ code = ch.ord
60
+ return cached_codepoint_width(code) if codepoint_cacheable?(code)
9
61
 
10
- if ch == "\t"
11
- width = tabstop - (col % tabstop)
12
- return width.zero? ? tabstop : width
62
+ uncached_codepoint_width(code)
13
63
  end
14
64
 
15
- code = ch.ord
16
- return 1 if code <= 0xA0 && !code.zero?
17
- return cached_codepoint_width(code) if codepoint_cacheable?(code)
65
+ def display_width(str, tabstop: 2, start_col: 0)
66
+ col = start_col
67
+ str.to_s.each_char { |ch| col += cell_width(ch, col:, tabstop:) }
68
+ col - start_col
69
+ end
70
+
71
+ def expand_tabs(str, tabstop: 2, start_col: 0)
72
+ col = start_col
73
+ out = +""
74
+ str.to_s.each_char do |ch|
75
+ if ch == "\t"
76
+ n = cell_width(ch, col:, tabstop:)
77
+ out << (" " * n)
78
+ col += n
79
+ else
80
+ out << ch
81
+ col += cell_width(ch, col:, tabstop:)
82
+ end
83
+ end
84
+ out
85
+ end
86
+
87
+ def ambiguous_width
88
+ env = ::ENV["RUVIM_AMBIGUOUS_WIDTH"]
89
+ if !defined?(@ambiguous_width_cached) || @ambiguous_width_env != env
90
+ @ambiguous_width_env = env
91
+ @ambiguous_width_cached = (env == "2" ? 2 : 1)
92
+ @codepoint_width_cache = {}
93
+ end
18
94
 
19
- uncached_codepoint_width(code)
95
+ @ambiguous_width_cached
96
+ end
20
97
  end
21
98
 
99
+ # Shared helpers (used by pure Ruby path; kept available for tests)
100
+
22
101
  def codepoint_cacheable?(code)
23
102
  !code.nil? && !code.zero?
24
103
  end
@@ -43,28 +122,6 @@ module RuVim
43
122
  1
44
123
  end
45
124
 
46
- def display_width(str, tabstop: 2, start_col: 0)
47
- col = start_col
48
- str.to_s.each_char { |ch| col += cell_width(ch, col:, tabstop:) }
49
- col - start_col
50
- end
51
-
52
- def expand_tabs(str, tabstop: 2, start_col: 0)
53
- col = start_col
54
- out = +""
55
- str.to_s.each_char do |ch|
56
- if ch == "\t"
57
- n = cell_width(ch, col:, tabstop:)
58
- out << (" " * n)
59
- col += n
60
- else
61
- out << ch
62
- col += cell_width(ch, col:, tabstop:)
63
- end
64
- end
65
- out
66
- end
67
-
68
125
  def combining_mark?(code)
69
126
  (0x0300..0x036F).cover?(code) ||
70
127
  (0x1AB0..0x1AFF).cover?(code) ||
@@ -74,9 +131,9 @@ module RuVim
74
131
  end
75
132
 
76
133
  def zero_width_codepoint?(code)
77
- (0x200D..0x200D).cover?(code) || # ZWJ
78
- (0xFE00..0xFE0F).cover?(code) || # variation selectors
79
- (0xE0100..0xE01EF).cover?(code) # variation selectors supplement
134
+ (0x200D..0x200D).cover?(code) ||
135
+ (0xFE00..0xFE0F).cover?(code) ||
136
+ (0xE0100..0xE01EF).cover?(code)
80
137
  end
81
138
 
82
139
  def wide_codepoint?(code)
@@ -121,16 +178,5 @@ module RuVim
121
178
  (0x2460..0x24E9).cover?(code) ||
122
179
  (0x2500..0x257F).cover?(code)
123
180
  end
124
-
125
- def ambiguous_width
126
- env = ::ENV["RUVIM_AMBIGUOUS_WIDTH"]
127
- if !defined?(@ambiguous_width_cached) || @ambiguous_width_env != env
128
- @ambiguous_width_env = env
129
- @ambiguous_width_cached = (env == "2" ? 2 : 1)
130
- @codepoint_width_cache = {}
131
- end
132
-
133
- @ambiguous_width_cached
134
- end
135
181
  end
136
182
  end
data/lib/ruvim/editor.rb CHANGED
@@ -54,24 +54,12 @@ module RuVim
54
54
  "filetype" => { default_scope: :buffer, type: :string, default: nil },
55
55
  "onsavehook" => { default_scope: :buffer, type: :bool, default: true },
56
56
  "grepprg" => { default_scope: :global, type: :string, default: "grep -nH" },
57
- "grepformat" => { default_scope: :global, type: :string, default: "%f:%l:%m" }
57
+ "grepformat" => { default_scope: :global, type: :string, default: "%f:%l:%m" },
58
+ "runprg" => { default_scope: :buffer, type: :string, default: nil }
58
59
  }.freeze
59
- SHEBANG_FILETYPE_RULES = [
60
- [/\Aruby(?:\d+(?:\.\d+)*)?\z/, "ruby"],
61
- [/\Apython(?:\d+(?:\.\d+)*)?\z/, "python"],
62
- ["node", "javascript"],
63
- ["nodejs", "javascript"],
64
- ["deno", "javascript"],
65
- ["bash", "sh"],
66
- ["sh", "sh"],
67
- ["zsh", "sh"],
68
- ["ksh", "sh"],
69
- ["dash", "sh"],
70
- [/\Aperl(?:\d+(?:\.\d+)*)?\z/, "perl"]
71
- ].freeze
72
60
 
73
61
  attr_reader :buffers, :windows, :layout_tree
74
- attr_accessor :current_window_id, :mode, :message, :pending_count, :alternate_buffer_id, :restricted_mode, :current_window_view_height_hint, :stdin_stream_stop_handler, :open_path_handler, :keymap_manager, :app_action_handler
62
+ attr_accessor :current_window_id, :mode, :message, :pending_count, :alternate_buffer_id, :restricted_mode, :current_window_view_height_hint, :open_path_handler, :keymap_manager, :app_action_handler, :git_stream_handler, :git_stream_stop_handler, :shell_executor, :run_stream_handler
75
63
 
76
64
  def initialize
77
65
  @buffers = {}
@@ -93,13 +81,13 @@ module RuVim
93
81
  @restricted_mode = false
94
82
  @current_window_view_height_hint = 1
95
83
  @running = true
96
- @stdin_stream_stop_handler = nil
97
84
  @open_path_handler = nil
98
85
  @keymap_manager = nil
99
86
  @app_action_handler = nil
100
87
  @global_options = default_global_options
101
88
  @command_line = CommandLine.new
102
89
  @last_search = nil
90
+ @hlsearch_suppressed = false
103
91
  @last_find = nil
104
92
  @registers = {}
105
93
  @active_register_name = nil
@@ -116,6 +104,8 @@ module RuVim
116
104
  @arglist = []
117
105
  @arglist_index = 0
118
106
  @hit_enter_lines = nil
107
+ @run_history = {} # buffer_id => last run command (unexpanded)
108
+ @run_output_buffer_id = nil
119
109
  end
120
110
 
121
111
  def running?
@@ -130,6 +120,18 @@ module RuVim
130
120
  @running = false
131
121
  end
132
122
 
123
+ def run_history
124
+ @run_history
125
+ end
126
+
127
+ def run_output_buffer_id
128
+ @run_output_buffer_id
129
+ end
130
+
131
+ def run_output_buffer_id=(id)
132
+ @run_output_buffer_id = id
133
+ end
134
+
133
135
  def command_line
134
136
  @command_line
135
137
  end
@@ -151,11 +153,20 @@ module RuVim
151
153
  end
152
154
 
153
155
  def set_last_search(pattern:, direction:)
154
- @last_search = { pattern: pattern.to_s, direction: direction.to_sym }
156
+ @last_search = { pattern: pattern, direction: direction }
157
+ @hlsearch_suppressed = false
158
+ end
159
+
160
+ def suppress_hlsearch!
161
+ @hlsearch_suppressed = true
162
+ end
163
+
164
+ def hlsearch_suppressed?
165
+ @hlsearch_suppressed
155
166
  end
156
167
 
157
168
  def set_last_find(char:, direction:, till:)
158
- @last_find = { char: char.to_s, direction: direction.to_sym, till: !!till }
169
+ @last_find = { char: char, direction: direction, till: !!till }
159
170
  end
160
171
 
161
172
  def current_window
@@ -166,19 +177,18 @@ module RuVim
166
177
  @buffers.fetch(current_window.buffer_id)
167
178
  end
168
179
 
169
- def stdin_stream_stop_or_cancel!
170
- handler = @stdin_stream_stop_handler
180
+ def stream_stop_or_cancel!
181
+ handler = current_buffer&.stream&.stop_handler
171
182
  return false unless handler
172
183
 
173
184
  handler.call
174
- true
175
185
  end
176
186
 
177
187
  def invoke_app_action(name, **kwargs)
178
188
  handler = @app_action_handler
179
189
  return false unless handler
180
190
 
181
- handler.call(name.to_sym, **kwargs)
191
+ handler.call(name, **kwargs)
182
192
  true
183
193
  end
184
194
 
@@ -203,7 +213,7 @@ module RuVim
203
213
 
204
214
  def get_option(name, scope: :effective, window: current_window, buffer: current_buffer)
205
215
  key = name.to_s
206
- case scope.to_sym
216
+ case scope
207
217
  when :global
208
218
  @global_options[key]
209
219
  when :buffer
@@ -218,7 +228,7 @@ module RuVim
218
228
  def set_option(name, value, scope: :auto, window: current_window, buffer: current_buffer)
219
229
  key = name.to_s
220
230
  value = coerce_option_value(key, value)
221
- actual_scope = (scope.to_sym == :auto ? option_default_scope(key) : scope.to_sym)
231
+ actual_scope = (scope == :auto ? option_default_scope(key) : scope)
222
232
  case actual_scope
223
233
  when :global
224
234
  @global_options[key] = value
@@ -252,33 +262,13 @@ module RuVim
252
262
  return nil if p.empty?
253
263
 
254
264
  base = File.basename(p)
255
- return "ruby" if %w[Gemfile Rakefile Guardfile].include?(base)
256
-
257
- ext_ft = {
258
- ".rb" => "ruby",
259
- ".rake" => "ruby",
260
- ".ru" => "ruby",
261
- ".py" => "python",
262
- ".js" => "javascript",
263
- ".mjs" => "javascript",
264
- ".cjs" => "javascript",
265
- ".ts" => "typescript",
266
- ".tsx" => "typescriptreact",
267
- ".jsx" => "javascriptreact",
268
- ".json" => "json",
269
- ".yml" => "yaml",
270
- ".yaml" => "yaml",
271
- ".md" => "markdown",
272
- ".txt" => "text",
273
- ".html" => "html",
274
- ".css" => "css",
275
- ".sh" => "sh",
276
- ".tsv" => "tsv",
277
- ".csv" => "csv",
278
- ".scm" => "scheme",
279
- ".ss" => "scheme",
280
- ".sld" => "scheme"
281
- }[File.extname(base).downcase]
265
+
266
+ # Basename-based detection (exact match, then prefix)
267
+ base_ft = Lang::Registry.detect_by_basename(base)
268
+ return base_ft if base_ft
269
+
270
+ # Extension-based detection
271
+ ext_ft = Lang::Registry.detect_by_extension(File.extname(base))
282
272
  return ext_ft if ext_ft
283
273
 
284
274
  detect_filetype_from_shebang(p)
@@ -290,9 +280,9 @@ module RuVim
290
280
 
291
281
  def set_register(name = "\"", text:, type: :charwise)
292
282
  key = name.to_s
293
- return { text: text.to_s, type: type.to_sym } if key == "_"
283
+ return { text: text, type: type } if key == "_"
294
284
 
295
- payload = write_register_payload(key, text: text.to_s, type: type.to_sym)
285
+ payload = write_register_payload(key, text: text, type: type)
296
286
  write_clipboard_register(key, payload)
297
287
  if key == "\""
298
288
  if (default_clip = clipboard_default_register_key)
@@ -305,14 +295,14 @@ module RuVim
305
295
  end
306
296
 
307
297
  def store_operator_register(name = "\"", text:, type:, kind:)
308
- key = (name || "\"").to_s
309
- payload = { text: text.to_s, type: type.to_sym }
298
+ key = (name || "\"")
299
+ payload = { text: text, type: type }
310
300
  return payload if key == "_"
311
301
 
312
302
  written = set_register(key, text: payload[:text], type: payload[:type])
313
303
  op_payload = dup_register_payload(written)
314
304
 
315
- case kind.to_sym
305
+ case kind
316
306
  when :yank
317
307
  @registers["0"] = dup_register_payload(op_payload)
318
308
  when :delete, :change
@@ -474,9 +464,9 @@ module RuVim
474
464
  end
475
465
 
476
466
  def enter_visual(mode)
477
- @mode = mode.to_sym
467
+ @mode = mode
478
468
  @visual_state = {
479
- mode: mode.to_sym,
469
+ mode: mode,
480
470
  anchor_y: current_window.cursor_y,
481
471
  anchor_x: current_window.cursor_x
482
472
  }
@@ -634,10 +624,10 @@ module RuVim
634
624
  win.row_offset = src.row_offset
635
625
  win.col_offset = src.col_offset
636
626
 
637
- split_type = (layout.to_sym == :vertical ? :vsplit : :hsplit)
627
+ split_type = (layout == :vertical ? :vsplit : :hsplit)
638
628
  new_leaf = { type: :window, id: win.id }
639
629
 
640
- @layout_tree = tree_split_leaf(@layout_tree, src.id, split_type, new_leaf, place.to_sym)
630
+ @layout_tree = tree_split_leaf(@layout_tree, src.id, split_type, new_leaf, place)
641
631
 
642
632
  @current_window_id = win.id
643
633
  save_current_tabpage_state! unless @suspend_tab_autosave
@@ -838,8 +828,7 @@ module RuVim
838
828
  end
839
829
 
840
830
  def delete_buffer(buffer_id)
841
- id = buffer_id.to_i
842
- buffer = @buffers[id]
831
+ buffer = @buffers[buffer_id]
843
832
  return nil unless buffer
844
833
 
845
834
  if @buffers.length <= 1
@@ -852,13 +841,13 @@ module RuVim
852
841
  if replacement
853
842
  replacement.id
854
843
  else
855
- candidates = @buffers.keys.reject { |bid| bid == id }
844
+ candidates = @buffers.keys.reject { |bid| bid == buffer_id }
856
845
  alt = @alternate_buffer_id
857
- (alt && alt != id && @buffers.key?(alt)) ? alt : candidates.first
846
+ (alt && alt != buffer_id && @buffers.key?(alt)) ? alt : candidates.first
858
847
  end
859
848
 
860
849
  @windows.each_value do |win|
861
- next unless win.buffer_id == id
850
+ next unless win.buffer_id == buffer_id
862
851
  next unless fallback_id
863
852
 
864
853
  win.buffer_id = fallback_id
@@ -868,9 +857,9 @@ module RuVim
868
857
  win.col_offset = 0
869
858
  end
870
859
 
871
- @buffers.delete(id)
872
- @local_marks.delete(id)
873
- @alternate_buffer_id = nil if @alternate_buffer_id == id
860
+ @buffers.delete(buffer_id)
861
+ @local_marks.delete(buffer_id)
862
+ @alternate_buffer_id = nil if @alternate_buffer_id == buffer_id
874
863
  save_current_tabpage_state! unless @suspend_tab_autosave
875
864
  ensure_bootstrap_buffer! if @buffers.empty?
876
865
  true
@@ -1012,11 +1001,10 @@ module RuVim
1012
1001
  end
1013
1002
 
1014
1003
  def find_window_ids_by_buffer_kind(kind)
1015
- sym = kind.to_sym
1016
1004
  window_order.select do |wid|
1017
1005
  win = @windows[wid]
1018
1006
  buf = win && @buffers[win.buffer_id]
1019
- buf && buf.kind == sym
1007
+ buf && buf.kind == kind
1020
1008
  end
1021
1009
  end
1022
1010
 
@@ -1154,15 +1142,24 @@ module RuVim
1154
1142
  def assign_filetype(buffer, ft)
1155
1143
  buffer.options["filetype"] = ft
1156
1144
  buffer.lang_module = resolve_lang_module(ft)
1145
+ apply_filetype_defaults(buffer, ft)
1146
+ end
1147
+
1148
+ def apply_filetype_defaults(buffer, ft)
1149
+ unless buffer.options.key?("runprg")
1150
+ runprg = filetype_default_runprg(ft)
1151
+ buffer.options["runprg"] = runprg if runprg
1152
+ end
1153
+ end
1154
+
1155
+ def filetype_default_runprg(ft)
1156
+ Lang::Registry.runprg_for(ft)
1157
1157
  end
1158
1158
 
1159
1159
  private
1160
1160
 
1161
1161
  def resolve_lang_module(ft)
1162
- case ft
1163
- when "ruby" then Lang::Ruby
1164
- else Lang::Base
1165
- end
1162
+ Lang::Registry.resolve_module(ft)
1166
1163
  end
1167
1164
 
1168
1165
  def detect_filetype_from_shebang(path)
@@ -1172,10 +1169,7 @@ module RuVim
1172
1169
  cmd = shebang_command_name(line)
1173
1170
  return nil if cmd.nil? || cmd.empty?
1174
1171
 
1175
- rule = SHEBANG_FILETYPE_RULES.find do |matcher, _filetype|
1176
- matcher.is_a?(Regexp) ? matcher.match?(cmd) : matcher.to_s == cmd
1177
- end
1178
- rule && rule[1]
1172
+ Lang::Registry.detect_by_shebang(cmd)
1179
1173
  rescue StandardError
1180
1174
  nil
1181
1175
  end
@@ -1211,7 +1205,7 @@ module RuVim
1211
1205
  prog = tokens[i].to_s
1212
1206
  end
1213
1207
 
1214
- File.basename(prog.to_s)
1208
+ File.basename(prog)
1215
1209
  end
1216
1210
 
1217
1211
  def default_global_options
@@ -1266,7 +1260,7 @@ module RuVim
1266
1260
  def dup_register_payload(payload)
1267
1261
  return nil unless payload
1268
1262
 
1269
- { text: payload[:text].to_s.dup, type: payload[:type].to_sym }
1263
+ { text: payload[:text].dup, type: payload[:type] }
1270
1264
  end
1271
1265
 
1272
1266
  def assign_detected_filetype(buffer)
@@ -9,6 +9,7 @@ module RuVim
9
9
  :desc,
10
10
  :nargs,
11
11
  :bang,
12
+ :raw_args,
12
13
  :source,
13
14
  keyword_init: true
14
15
  )
@@ -20,7 +21,7 @@ module RuVim
20
21
  @lookup = {}
21
22
  end
22
23
 
23
- def register(name, call:, aliases: [], desc: "", nargs: :any, bang: false, source: :builtin)
24
+ def register(name, call:, aliases: [], desc: "", nargs: :any, bang: false, raw_args: false, source: :builtin)
24
25
  canonical = name.to_s
25
26
  if @specs.key?(canonical)
26
27
  raise RuVim::CommandError, "Ex command already exists: #{canonical}"
@@ -32,6 +33,7 @@ module RuVim
32
33
  desc: desc,
33
34
  nargs: nargs,
34
35
  bang: bang,
36
+ raw_args: raw_args,
35
37
  source: source
36
38
  )
37
39