ruvim 0.1.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 (66) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yml +15 -0
  3. data/README.md +135 -0
  4. data/Rakefile +36 -0
  5. data/docs/binding.md +125 -0
  6. data/docs/command.md +306 -0
  7. data/docs/config.md +155 -0
  8. data/docs/done.md +112 -0
  9. data/docs/plugin.md +559 -0
  10. data/docs/spec.md +655 -0
  11. data/docs/todo.md +63 -0
  12. data/docs/tutorial.md +490 -0
  13. data/docs/vim_diff.md +179 -0
  14. data/exe/ruvim +6 -0
  15. data/lib/ruvim/app.rb +1600 -0
  16. data/lib/ruvim/buffer.rb +421 -0
  17. data/lib/ruvim/cli.rb +264 -0
  18. data/lib/ruvim/clipboard.rb +73 -0
  19. data/lib/ruvim/command_invocation.rb +14 -0
  20. data/lib/ruvim/command_line.rb +63 -0
  21. data/lib/ruvim/command_registry.rb +38 -0
  22. data/lib/ruvim/config_dsl.rb +134 -0
  23. data/lib/ruvim/config_loader.rb +68 -0
  24. data/lib/ruvim/context.rb +26 -0
  25. data/lib/ruvim/dispatcher.rb +120 -0
  26. data/lib/ruvim/display_width.rb +110 -0
  27. data/lib/ruvim/editor.rb +1025 -0
  28. data/lib/ruvim/ex_command_registry.rb +80 -0
  29. data/lib/ruvim/global_commands.rb +1889 -0
  30. data/lib/ruvim/highlighter.rb +52 -0
  31. data/lib/ruvim/input.rb +66 -0
  32. data/lib/ruvim/keymap_manager.rb +96 -0
  33. data/lib/ruvim/screen.rb +452 -0
  34. data/lib/ruvim/terminal.rb +30 -0
  35. data/lib/ruvim/text_metrics.rb +96 -0
  36. data/lib/ruvim/version.rb +5 -0
  37. data/lib/ruvim/window.rb +71 -0
  38. data/lib/ruvim.rb +30 -0
  39. data/sig/ruvim.rbs +4 -0
  40. data/test/app_completion_test.rb +39 -0
  41. data/test/app_dot_repeat_test.rb +54 -0
  42. data/test/app_motion_test.rb +73 -0
  43. data/test/app_register_test.rb +47 -0
  44. data/test/app_scenario_test.rb +77 -0
  45. data/test/app_startup_test.rb +199 -0
  46. data/test/app_text_object_test.rb +54 -0
  47. data/test/app_unicode_behavior_test.rb +66 -0
  48. data/test/buffer_test.rb +72 -0
  49. data/test/cli_test.rb +165 -0
  50. data/test/config_dsl_test.rb +78 -0
  51. data/test/dispatcher_test.rb +124 -0
  52. data/test/editor_mark_test.rb +69 -0
  53. data/test/editor_register_test.rb +64 -0
  54. data/test/fixtures/render_basic_snapshot.txt +8 -0
  55. data/test/fixtures/render_basic_snapshot_nonumber.txt +8 -0
  56. data/test/fixtures/render_unicode_scrolled_snapshot.txt +7 -0
  57. data/test/highlighter_test.rb +16 -0
  58. data/test/input_screen_integration_test.rb +69 -0
  59. data/test/keymap_manager_test.rb +48 -0
  60. data/test/render_snapshot_test.rb +70 -0
  61. data/test/screen_test.rb +123 -0
  62. data/test/search_option_test.rb +39 -0
  63. data/test/test_helper.rb +15 -0
  64. data/test/text_metrics_test.rb +42 -0
  65. data/test/window_test.rb +21 -0
  66. metadata +106 -0
@@ -0,0 +1,421 @@
1
+ module RuVim
2
+ class Buffer
3
+ attr_reader :id, :kind, :name
4
+ attr_accessor :path
5
+ attr_reader :options
6
+ attr_writer :modified
7
+
8
+ def self.from_file(id:, path:)
9
+ lines =
10
+ if File.exist?(path)
11
+ data = decode_text(File.binread(path))
12
+ split_lines(data)
13
+ else
14
+ [""]
15
+ end
16
+ new(id:, path:, lines:)
17
+ end
18
+
19
+ def self.split_lines(data)
20
+ return [""] if data.empty?
21
+
22
+ lines = data.split("\n", -1)
23
+ if data.end_with?("\n")
24
+ lines.pop
25
+ end
26
+ lines = [""] if lines.empty?
27
+ lines
28
+ end
29
+
30
+ def self.decode_text(bytes)
31
+ s = bytes.to_s.dup
32
+ return s if s.encoding == Encoding::UTF_8 && s.valid_encoding?
33
+
34
+ utf8 = s.dup.force_encoding(Encoding::UTF_8)
35
+ return utf8 if utf8.valid_encoding?
36
+
37
+ ext = Encoding.default_external
38
+ if ext && ext != Encoding::UTF_8
39
+ return s.dup.force_encoding(ext).encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
40
+ end
41
+
42
+ utf8.scrub
43
+ rescue StandardError
44
+ s.dup.force_encoding(Encoding::UTF_8).scrub
45
+ end
46
+
47
+ def initialize(id:, path: nil, lines: [""], kind: :file, name: nil, readonly: false, modifiable: true)
48
+ @id = id
49
+ @path = path
50
+ @kind = kind.to_sym
51
+ @name = name
52
+ @lines = lines.dup
53
+ @lines = [""] if @lines.empty?
54
+ @modified = false
55
+ @readonly = !!readonly
56
+ @modifiable = !!modifiable
57
+ @undo_stack = []
58
+ @redo_stack = []
59
+ @change_group_depth = 0
60
+ @group_before_snapshot = nil
61
+ @group_changed = false
62
+ @recording_suspended = false
63
+ @options = {}
64
+ end
65
+
66
+ def lines
67
+ @lines
68
+ end
69
+
70
+ def modified?
71
+ !!@modified
72
+ end
73
+
74
+ def readonly?
75
+ @readonly
76
+ end
77
+
78
+ def readonly=(value)
79
+ @readonly = !!value
80
+ end
81
+
82
+ def modifiable?
83
+ @modifiable
84
+ end
85
+
86
+ def modifiable=(value)
87
+ @modifiable = !!value
88
+ end
89
+
90
+ def file_buffer?
91
+ @kind == :file
92
+ end
93
+
94
+ def intro_buffer?
95
+ @kind == :intro
96
+ end
97
+
98
+ def virtual_buffer?
99
+ !file_buffer?
100
+ end
101
+
102
+ def display_name
103
+ @name || @path || "[No Name]"
104
+ end
105
+
106
+ def configure_special!(kind:, name: nil, readonly: true, modifiable: false)
107
+ @kind = kind.to_sym
108
+ @name = name
109
+ @readonly = !!readonly
110
+ @modifiable = !!modifiable
111
+ self
112
+ end
113
+
114
+ def become_normal_empty_buffer!
115
+ @kind = :file
116
+ @name = nil
117
+ @path = nil
118
+ @readonly = false
119
+ @modifiable = true
120
+ @lines = [""]
121
+ @modified = false
122
+ @undo_stack.clear
123
+ @redo_stack.clear
124
+ @change_group_depth = 0
125
+ @group_before_snapshot = nil
126
+ @group_changed = false
127
+ self
128
+ end
129
+
130
+ def can_undo?
131
+ !@undo_stack.empty?
132
+ end
133
+
134
+ def can_redo?
135
+ !@redo_stack.empty?
136
+ end
137
+
138
+ def begin_change_group
139
+ @change_group_depth += 1
140
+ nil
141
+ end
142
+
143
+ def end_change_group
144
+ return nil if @change_group_depth.zero?
145
+
146
+ @change_group_depth -= 1
147
+ return nil unless @change_group_depth.zero?
148
+
149
+ if @group_changed && @group_before_snapshot
150
+ @undo_stack << @group_before_snapshot
151
+ @redo_stack.clear
152
+ end
153
+ @group_before_snapshot = nil
154
+ @group_changed = false
155
+ nil
156
+ end
157
+
158
+ def undo!
159
+ close_change_group_if_open
160
+ return false unless can_undo?
161
+
162
+ current = snapshot
163
+ prev = @undo_stack.pop
164
+ with_recording_suspended do
165
+ restore_snapshot(prev)
166
+ end
167
+ @redo_stack << current
168
+ true
169
+ end
170
+
171
+ def redo!
172
+ close_change_group_if_open
173
+ return false unless can_redo?
174
+
175
+ current = snapshot
176
+ nxt = @redo_stack.pop
177
+ with_recording_suspended do
178
+ restore_snapshot(nxt)
179
+ end
180
+ @undo_stack << current
181
+ true
182
+ end
183
+
184
+ def line_count
185
+ @lines.length
186
+ end
187
+
188
+ def line_at(row)
189
+ @lines.fetch(row)
190
+ end
191
+
192
+ def line_length(row)
193
+ @lines.fetch(row).length
194
+ end
195
+
196
+ def insert_char(row, col, char)
197
+ record_change_before_mutation!
198
+ line = @lines.fetch(row)
199
+ @lines[row] = line.dup.insert(col, char)
200
+ @modified = true
201
+ end
202
+
203
+ def insert_text(row, col, text)
204
+ text.each_char do |ch|
205
+ if ch == "\n"
206
+ row, col = insert_newline(row, col)
207
+ else
208
+ insert_char(row, col, ch)
209
+ col += 1
210
+ end
211
+ end
212
+ [row, col]
213
+ end
214
+
215
+ def insert_newline(row, col)
216
+ record_change_before_mutation!
217
+ line = @lines.fetch(row)
218
+ head = line[0...col]
219
+ tail = line[col..] || ""
220
+ @lines[row] = head
221
+ @lines.insert(row + 1, tail)
222
+ @modified = true
223
+ [row + 1, 0]
224
+ end
225
+
226
+ def backspace(row, col)
227
+ if col > 0
228
+ record_change_before_mutation!
229
+ line = @lines.fetch(row)
230
+ @lines[row] = line[0...(col - 1)] + line[col..].to_s
231
+ @modified = true
232
+ return [row, col - 1]
233
+ end
234
+
235
+ return [row, col] if row.zero?
236
+
237
+ record_change_before_mutation!
238
+ prev = @lines.fetch(row - 1)
239
+ cur = @lines.fetch(row)
240
+ new_col = prev.length
241
+ @lines[row - 1] = prev + cur
242
+ @lines.delete_at(row)
243
+ @modified = true
244
+ [row - 1, new_col]
245
+ end
246
+
247
+ def delete_char(row, col)
248
+ line = @lines.fetch(row)
249
+ if col < line.length
250
+ record_change_before_mutation!
251
+ @lines[row] = line[0...col] + line[(col + 1)..].to_s
252
+ @modified = true
253
+ return true
254
+ end
255
+
256
+ return false if row >= @lines.length - 1
257
+
258
+ record_change_before_mutation!
259
+ @lines[row] = line + @lines[row + 1]
260
+ @lines.delete_at(row + 1)
261
+ @modified = true
262
+ true
263
+ end
264
+
265
+ def delete_line(row)
266
+ row = [[row, 0].max, @lines.length - 1].min
267
+ record_change_before_mutation!
268
+ deleted = @lines.delete_at(row)
269
+ @lines << "" if @lines.empty?
270
+ @modified = true
271
+ deleted
272
+ end
273
+
274
+ def delete_span(start_row, start_col, end_row, end_col)
275
+ s_row, s_col, e_row, e_col = normalize_span(start_row, start_col, end_row, end_col)
276
+ return false if s_row == e_row && s_col == e_col
277
+
278
+ record_change_before_mutation!
279
+
280
+ if s_row == e_row
281
+ line = @lines.fetch(s_row)
282
+ @lines[s_row] = line[0...s_col] + line[e_col..].to_s
283
+ else
284
+ head = @lines.fetch(s_row)[0...s_col]
285
+ tail = @lines.fetch(e_row)[e_col..].to_s
286
+ @lines[s_row] = head + tail
287
+ (e_row - s_row).times { @lines.delete_at(s_row + 1) }
288
+ end
289
+
290
+ @lines << "" if @lines.empty?
291
+ @modified = true
292
+ true
293
+ end
294
+
295
+ def span_text(start_row, start_col, end_row, end_col)
296
+ s_row, s_col, e_row, e_col = normalize_span(start_row, start_col, end_row, end_col)
297
+ return "" if s_row == e_row && s_col == e_col
298
+
299
+ if s_row == e_row
300
+ return @lines.fetch(s_row)[s_col...e_col].to_s
301
+ end
302
+
303
+ parts = []
304
+ parts << @lines.fetch(s_row)[s_col..].to_s
305
+ ((s_row + 1)...e_row).each { |row| parts << @lines.fetch(row) }
306
+ parts << @lines.fetch(e_row)[0...e_col].to_s
307
+ parts.join("\n")
308
+ end
309
+
310
+ def line_block_text(start_row, count = 1)
311
+ rows = @lines[start_row, count] || []
312
+ rows.join("\n") + "\n"
313
+ end
314
+
315
+ def insert_lines_at(index, new_lines)
316
+ lines = Array(new_lines).map(&:to_s)
317
+ return if lines.empty?
318
+
319
+ record_change_before_mutation!
320
+ idx = [[index, 0].max, @lines.length].min
321
+ @lines.insert(idx, *lines)
322
+ @modified = true
323
+ end
324
+
325
+ def replace_all_lines!(new_lines)
326
+ record_change_before_mutation!
327
+ @lines = Array(new_lines).map(&:dup)
328
+ @lines = [""] if @lines.empty?
329
+ @modified = true
330
+ end
331
+
332
+ def write_to(path = nil)
333
+ raise RuVim::CommandError, "Buffer is readonly" if readonly?
334
+
335
+ target = path || @path
336
+ raise RuVim::CommandError, "No file name" if target.nil? || target.empty?
337
+
338
+ File.binwrite(target, @lines.join("\n"))
339
+ @path = target
340
+ @modified = false
341
+ target
342
+ end
343
+
344
+ def reload_from_file!(path = nil)
345
+ target = path || @path
346
+ raise RuVim::CommandError, "No file name" if target.nil? || target.empty?
347
+
348
+ data = File.exist?(target) ? self.class.decode_text(File.binread(target)) : ""
349
+ @lines = self.class.split_lines(data)
350
+ @path = target
351
+ @modified = false
352
+ @undo_stack.clear
353
+ @redo_stack.clear
354
+ @change_group_depth = 0
355
+ @group_before_snapshot = nil
356
+ @group_changed = false
357
+ target
358
+ end
359
+
360
+ private
361
+
362
+ def normalize_span(start_row, start_col, end_row, end_col)
363
+ a_before_b = (start_row < end_row) || (start_row == end_row && start_col <= end_col)
364
+ s_row, s_col, e_row, e_col =
365
+ if a_before_b
366
+ [start_row, start_col, end_row, end_col]
367
+ else
368
+ [end_row, end_col, start_row, start_col]
369
+ end
370
+ [s_row, s_col, e_row, e_col]
371
+ end
372
+
373
+ def close_change_group_if_open
374
+ return if @change_group_depth.zero?
375
+
376
+ @change_group_depth = 1
377
+ end_change_group
378
+ end
379
+
380
+ def record_change_before_mutation!
381
+ ensure_modifiable!
382
+ return if @recording_suspended
383
+
384
+ if @change_group_depth.positive?
385
+ unless @group_changed
386
+ @group_before_snapshot = snapshot
387
+ @group_changed = true
388
+ end
389
+ return
390
+ end
391
+
392
+ @undo_stack << snapshot
393
+ @redo_stack.clear
394
+ end
395
+
396
+ def ensure_modifiable!
397
+ raise RuVim::CommandError, "Buffer is not modifiable" unless modifiable?
398
+ end
399
+
400
+ def snapshot
401
+ {
402
+ lines: @lines.map(&:dup),
403
+ modified: @modified
404
+ }
405
+ end
406
+
407
+ def restore_snapshot(snap)
408
+ @lines = snap.fetch(:lines).map(&:dup)
409
+ @lines = [""] if @lines.empty?
410
+ @modified = snap.fetch(:modified)
411
+ end
412
+
413
+ def with_recording_suspended
414
+ prev = @recording_suspended
415
+ @recording_suspended = true
416
+ yield
417
+ ensure
418
+ @recording_suspended = prev
419
+ end
420
+ end
421
+ end
data/lib/ruvim/cli.rb ADDED
@@ -0,0 +1,264 @@
1
+ module RuVim
2
+ class CLI
3
+ Options = Struct.new(
4
+ :files,
5
+ :pre_config_actions,
6
+ :startup_actions,
7
+ :clean,
8
+ :skip_user_config,
9
+ :config_path,
10
+ :readonly,
11
+ :diff_mode,
12
+ :quickfix_errorfile,
13
+ :session_file,
14
+ :no_swap,
15
+ :nomodifiable,
16
+ :restricted_mode,
17
+ :verbose_level,
18
+ :startup_time_path,
19
+ :startup_open_layout,
20
+ :startup_open_count,
21
+ :show_help,
22
+ :show_version,
23
+ keyword_init: true
24
+ )
25
+
26
+ class ParseError < RuVim::Error; end
27
+
28
+ def self.run(argv = ARGV, stdin: STDIN, stdout: STDOUT, stderr: STDERR)
29
+ opts = parse(argv)
30
+
31
+ if opts.show_help
32
+ stdout.write(help_text)
33
+ return 0
34
+ end
35
+
36
+ if opts.show_version
37
+ stdout.puts("RuVim #{RuVim::VERSION}")
38
+ return 0
39
+ end
40
+
41
+ if opts.files.length > 1 && opts.startup_open_layout.nil?
42
+ raise ParseError, "multiple files are not supported yet"
43
+ end
44
+
45
+ if opts.config_path && !File.file?(opts.config_path)
46
+ raise ParseError, "config file not found: #{opts.config_path}"
47
+ end
48
+
49
+ app = RuVim::App.new(
50
+ path: opts.files.first,
51
+ paths: opts.files,
52
+ stdin: stdin,
53
+ stdout: stdout,
54
+ pre_config_actions: opts.pre_config_actions,
55
+ startup_actions: opts.startup_actions,
56
+ clean: opts.clean,
57
+ skip_user_config: opts.skip_user_config,
58
+ config_path: opts.config_path,
59
+ readonly: opts.readonly,
60
+ diff_mode: opts.diff_mode,
61
+ quickfix_errorfile: opts.quickfix_errorfile,
62
+ session_file: opts.session_file,
63
+ nomodifiable: opts.nomodifiable,
64
+ restricted: opts.restricted_mode,
65
+ verbose_level: opts.verbose_level,
66
+ verbose_io: stderr,
67
+ startup_time_path: opts.startup_time_path,
68
+ startup_open_layout: opts.startup_open_layout,
69
+ startup_open_count: opts.startup_open_count
70
+ )
71
+ app.run
72
+ 0
73
+ rescue ParseError => e
74
+ stderr.puts("ruvim: #{e.message}")
75
+ stderr.puts("Try 'ruvim --help'.")
76
+ 2
77
+ end
78
+
79
+ def self.parse(argv)
80
+ args = Array(argv).dup
81
+ opts = Options.new(
82
+ files: [],
83
+ pre_config_actions: [],
84
+ startup_actions: [],
85
+ clean: false,
86
+ skip_user_config: false,
87
+ config_path: nil,
88
+ readonly: false,
89
+ diff_mode: false,
90
+ quickfix_errorfile: nil,
91
+ session_file: nil,
92
+ no_swap: false,
93
+ nomodifiable: false,
94
+ restricted_mode: false,
95
+ verbose_level: 0,
96
+ startup_time_path: nil,
97
+ startup_open_layout: nil,
98
+ startup_open_count: nil,
99
+ show_help: false,
100
+ show_version: false
101
+ )
102
+
103
+ i = 0
104
+ stop_options = false
105
+ while i < args.length
106
+ arg = args[i]
107
+ if stop_options
108
+ opts.files << arg
109
+ i += 1
110
+ next
111
+ end
112
+
113
+ case arg
114
+ when "--"
115
+ stop_options = true
116
+ when "--help", "-h"
117
+ opts.show_help = true
118
+ when "--version", "-v"
119
+ opts.show_version = true
120
+ when "--verbose"
121
+ opts.verbose_level = [opts.verbose_level.to_i, 1].max
122
+ when /\A--verbose=(\d+)\z/
123
+ opts.verbose_level = Regexp.last_match(1).to_i
124
+ when "--startuptime"
125
+ i += 1
126
+ raise ParseError, "--startuptime requires a file path" if i >= args.length
127
+ opts.startup_time_path = args[i]
128
+ when "--cmd"
129
+ i += 1
130
+ raise ParseError, "--cmd requires an argument" if i >= args.length
131
+ opts.pre_config_actions << { type: :ex, value: args[i] }
132
+ when /\A--cmd=(.+)\z/
133
+ opts.pre_config_actions << { type: :ex, value: Regexp.last_match(1) }
134
+ when "--clean"
135
+ opts.clean = true
136
+ when "-R"
137
+ opts.readonly = true
138
+ when "-d"
139
+ opts.diff_mode = true
140
+ when "-q"
141
+ i += 1
142
+ raise ParseError, "-q requires an errorfile path" if i >= args.length
143
+ opts.quickfix_errorfile = args[i]
144
+ when "-S"
145
+ if i + 1 < args.length && !args[i + 1].start_with?("-")
146
+ i += 1
147
+ opts.session_file = args[i]
148
+ else
149
+ opts.session_file = "Session.vim"
150
+ end
151
+ when "-n"
152
+ opts.no_swap = true
153
+ when "-M"
154
+ opts.nomodifiable = true
155
+ when "-Z"
156
+ opts.restricted_mode = true
157
+ when "-V"
158
+ opts.verbose_level = [opts.verbose_level.to_i, 1].max
159
+ when /\A-V(\d+)\z/
160
+ opts.verbose_level = Regexp.last_match(1).to_i
161
+ when "-o", "-O", "-p"
162
+ apply_layout_option(opts, arg, nil)
163
+ when /\A-(o|O|p)(\d+)\z/
164
+ apply_layout_option(opts, Regexp.last_match(1), Regexp.last_match(2).to_i)
165
+ when "-u"
166
+ i += 1
167
+ raise ParseError, "-u requires an argument" if i >= args.length
168
+ apply_u_option(opts, args[i])
169
+ when /\A-u(.+)\z/
170
+ apply_u_option(opts, Regexp.last_match(1))
171
+ when "-c"
172
+ i += 1
173
+ raise ParseError, "-c requires an argument" if i >= args.length
174
+ opts.startup_actions << { type: :ex, value: args[i] }
175
+ when /\A\+/
176
+ apply_plus_option(opts, arg)
177
+ else
178
+ if arg.start_with?("-")
179
+ raise ParseError, "unknown option: #{arg}"
180
+ end
181
+ opts.files << arg
182
+ end
183
+ i += 1
184
+ end
185
+
186
+ opts
187
+ end
188
+
189
+ def self.apply_u_option(opts, value)
190
+ if value == "NONE"
191
+ opts.skip_user_config = true
192
+ opts.config_path = nil
193
+ else
194
+ opts.skip_user_config = false
195
+ opts.config_path = value
196
+ end
197
+ end
198
+ private_class_method :apply_u_option
199
+
200
+ def self.apply_plus_option(opts, arg)
201
+ rest = arg[1..].to_s
202
+ if rest.empty?
203
+ opts.startup_actions << { type: :line_end }
204
+ elsif rest.match?(/\A\d+\z/)
205
+ opts.startup_actions << { type: :line, value: rest.to_i }
206
+ else
207
+ opts.startup_actions << { type: :ex, value: rest }
208
+ end
209
+ end
210
+ private_class_method :apply_plus_option
211
+
212
+ def self.apply_layout_option(opts, token, count)
213
+ layout =
214
+ case token
215
+ when "-o", "o" then :horizontal
216
+ when "-O", "O" then :vertical
217
+ when "-p", "p" then :tab
218
+ else
219
+ raise ParseError, "unknown layout option: #{token}"
220
+ end
221
+ opts.startup_open_layout = layout
222
+ opts.startup_open_count = count
223
+ end
224
+ private_class_method :apply_layout_option
225
+
226
+ def self.help_text
227
+ <<~TXT
228
+ Usage: ruvim [options] [file]
229
+
230
+ Options:
231
+ -h, --help Show this help
232
+ -v, --version Show version
233
+ --clean Start without user config and ftplugin
234
+ -R Open file readonly (disallow :w on current buffer)
235
+ -d Diff mode requested (compat placeholder; not implemented yet)
236
+ -q {errorfile} Quickfix startup placeholder (not implemented yet)
237
+ -S [session] Session startup placeholder (not implemented yet)
238
+ -M Open file unmodifiable (disallow editing; also readonly)
239
+ -Z Restricted mode (skip config/ftplugin, disable :ruby)
240
+ -V[N], --verbose[=N]
241
+ Verbose startup/config/command logs to stderr
242
+ --startuptime FILE
243
+ Write startup timing log
244
+ --cmd {cmd} Execute Ex command before loading user config
245
+ -n No-op (reserved for swap/persistent features compatibility)
246
+ -o[N] Open files in horizontal splits
247
+ -O[N] Open files in vertical splits
248
+ -p[N] Open files in tabs
249
+ -u {path|NONE} Use config file path, or disable user config with NONE
250
+ -c {cmd} Execute Ex command after startup
251
+ +{cmd} Execute Ex command after startup
252
+ +{line} Move cursor to line after startup
253
+ + Move cursor to last line after startup
254
+
255
+ Examples:
256
+ ruvim file.txt
257
+ ruvim +10 file.txt
258
+ ruvim --cmd 'set number' file.txt
259
+ ruvim -c 'set number' file.txt
260
+ ruvim --clean -u NONE
261
+ TXT
262
+ end
263
+ end
264
+ end