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,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuVim
4
+ class Stream::Stdin < Stream
5
+ attr_accessor :io, :thread
6
+
7
+ def initialize(io:, buffer_id:, queue:, stop_handler: nil, &notify)
8
+ super(stop_handler: stop_handler)
9
+ @io = io
10
+ @state = :live
11
+ @thread = Thread.new do
12
+ loop do
13
+ chunk = io.readpartial(4096)
14
+ next if chunk.nil? || chunk.empty?
15
+
16
+ queue << { type: :stream_data, buffer_id: buffer_id, data: Buffer.decode_text(chunk) }
17
+ notify.call
18
+ end
19
+ rescue EOFError
20
+ unless @state == :closed
21
+ queue << { type: :stream_eof, buffer_id: buffer_id }
22
+ notify.call
23
+ end
24
+ rescue IOError, StandardError => e
25
+ unless @state == :closed
26
+ queue << { type: :stream_error, buffer_id: buffer_id, error: e.message.to_s }
27
+ notify.call
28
+ end
29
+ end
30
+ end
31
+
32
+ def status
33
+ case @state
34
+ when :live then "stdin"
35
+ when :closed then "stdin/EOF"
36
+ when :error then "stdin/error"
37
+ end
38
+ end
39
+
40
+ def stop!
41
+ io = @io; @io = nil
42
+ begin
43
+ io&.close unless io&.closed?
44
+ rescue IOError
45
+ nil
46
+ end
47
+ thread = @thread; @thread = nil
48
+ if thread&.alive?
49
+ thread.kill
50
+ thread.join(0.05)
51
+ end
52
+ @state = :closed
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuVim
4
+ class Stream
5
+ attr_accessor :state
6
+ attr_reader :stop_handler
7
+
8
+ def initialize(stop_handler: nil)
9
+ @state = nil
10
+ @stop_handler = stop_handler
11
+ end
12
+
13
+ def live?
14
+ @state == :live
15
+ end
16
+
17
+ def status
18
+ nil
19
+ end
20
+
21
+ def command
22
+ nil
23
+ end
24
+
25
+ def stop!
26
+ # subclasses override
27
+ end
28
+ end
29
+ end
30
+
31
+ require_relative "stream/stdin"
32
+ require_relative "stream/run"
33
+ require_relative "stream/follow"
34
+ require_relative "stream/file_load"
35
+ require_relative "stream/git"
@@ -0,0 +1,394 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuVim
4
+ class StreamMixer
5
+ LARGE_FILE_ASYNC_THRESHOLD_BYTES = 64 * 1024 * 1024
6
+ LARGE_FILE_STAGED_PREFIX_BYTES = 8 * 1024 * 1024
7
+
8
+ def initialize(editor:, signal_w:)
9
+ @editor = editor
10
+ @signal_w = signal_w
11
+ @stream_event_queue = nil
12
+ end
13
+
14
+ def prepare_stdin_stream_buffer!(io)
15
+ buf = @editor.current_buffer
16
+ if buf.intro_buffer?
17
+ @editor.materialize_intro_buffer!
18
+ buf = @editor.current_buffer
19
+ end
20
+
21
+ buf.replace_all_lines!([""])
22
+ buf.configure_special!(kind: :stream, name: "[stdin]", readonly: true, modifiable: false)
23
+ buf.modified = false
24
+ buf.options["filetype"] = "text"
25
+ ensure_event_queue!
26
+ move_window_to_stream_end!(@editor.current_window, buf)
27
+ @editor.echo("[stdin] follow")
28
+ @pending_stdin = { buf: buf, io: io }
29
+ buf
30
+ end
31
+
32
+ def start_pending_stdin!
33
+ return unless @pending_stdin
34
+
35
+ ps = @pending_stdin
36
+ @pending_stdin = nil
37
+ buf = ps[:buf]
38
+ ensure_event_queue!
39
+ buf.stream = Stream::Stdin.new(
40
+ io: ps[:io], buffer_id: buf.id, queue: @stream_event_queue,
41
+ stop_handler: -> { stop_buffer_stream!(buf) }, &method(:notify_signal_wakeup)
42
+ )
43
+ end
44
+
45
+ def start_command_stream!(buf, command)
46
+ ensure_event_queue!
47
+ buf.stream = Stream::Run.new(
48
+ command: command, buffer_id: buf.id, queue: @stream_event_queue,
49
+ stop_handler: -> { stop_buffer_stream!(buf) }, &method(:notify_signal_wakeup)
50
+ )
51
+ end
52
+
53
+ def start_git_stream_command(buffer_id, cmd, root)
54
+ ensure_event_queue!
55
+ buf = @editor.buffers[buffer_id]
56
+ return unless buf
57
+
58
+ buf.stream = Stream::Git.new(cmd: cmd, root: root, buffer_id: buffer_id, queue: @stream_event_queue, &method(:notify_signal_wakeup))
59
+ end
60
+
61
+ def stop_buffer_stream!(buf)
62
+ return false unless buf&.stream&.live?
63
+
64
+ buf.stream.stop!
65
+ @editor.echo("#{buf.display_name} stopped")
66
+ notify_signal_wakeup
67
+ true
68
+ end
69
+
70
+ def stop_git_stream!(buffer_id)
71
+ buf = @editor.buffers[buffer_id]
72
+ buf&.stream&.stop!
73
+ end
74
+
75
+ def drain_events!
76
+ return false unless @stream_event_queue
77
+
78
+ changed = false
79
+ loop do
80
+ event = @stream_event_queue.pop(true)
81
+ case event[:type]
82
+ when :stream_data
83
+ changed = apply_stream_chunk!(event[:buffer_id], event[:data]) || changed
84
+ when :stream_eof
85
+ changed = finish_stream!(event[:buffer_id], status: event[:status]) || changed
86
+ when :stream_error
87
+ changed = fail_stream!(event[:buffer_id], event[:error]) || changed
88
+ when :follow_data
89
+ changed = apply_stream_chunk!(event[:buffer_id], event[:data]) || changed
90
+ when :follow_truncated
91
+ if (buf = @editor.buffers[event[:buffer_id]])
92
+ @editor.echo("[follow] file truncated: #{buf.display_name}")
93
+ changed = true
94
+ end
95
+ when :follow_deleted
96
+ if (buf = @editor.buffers[event[:buffer_id]])
97
+ @editor.echo("[follow] file deleted, waiting for re-creation: #{buf.display_name}")
98
+ changed = true
99
+ end
100
+ when :file_lines
101
+ changed = apply_async_file_lines!(event[:buffer_id], event[:head], event[:lines], loaded_bytes: event[:loaded_bytes], file_size: event[:file_size]) || changed
102
+ when :file_eof
103
+ changed = finish_async_file_load!(event[:buffer_id], ended_with_newline: event[:ended_with_newline]) || changed
104
+ when :file_error
105
+ changed = fail_async_file_load!(event[:buffer_id], event[:error]) || changed
106
+ end
107
+ end
108
+ rescue ThreadError
109
+ changed
110
+ end
111
+
112
+ def ex_follow_toggle
113
+ buf = @editor.current_buffer
114
+ raise RuVim::CommandError, "No file associated with buffer" unless buf.path
115
+
116
+ if buf.stream.is_a?(Stream::Follow)
117
+ stop_follow!(buf)
118
+ else
119
+ raise RuVim::CommandError, "Buffer has unsaved changes" if buf.modified?
120
+ start_follow!(buf)
121
+ end
122
+ end
123
+
124
+ def start_follow!(buf)
125
+ ensure_event_queue!
126
+ Buffer.ensure_regular_file!(buf.path) if buf.path
127
+
128
+ if buf.path && File.file?(buf.path)
129
+ data = File.binread(buf.path)
130
+ if data.end_with?("\n") && buf.lines.last.to_s != ""
131
+ following_wins = @editor.windows.values.select do |w|
132
+ w.buffer_id == buf.id && stream_window_following_end?(w, buf)
133
+ end
134
+ buf.append_stream_text!("\n")
135
+ following_wins.each { |w| move_window_to_stream_end!(w, buf) }
136
+ end
137
+ end
138
+
139
+ buf.stream = Stream::Follow.new(
140
+ path: buf.path, buffer_id: buf.id, queue: @stream_event_queue,
141
+ stop_handler: -> { stop_follow!(buf) }, &method(:notify_signal_wakeup)
142
+ )
143
+ @editor.echo("[follow] #{buf.display_name}")
144
+ end
145
+
146
+ def stop_follow!(buf)
147
+ buf.stream&.stop!
148
+ # Remove trailing empty line added as sentinel by start_follow!
149
+ if buf.line_count > 1 && buf.lines.last.to_s == ""
150
+ buf.lines.pop
151
+ last = buf.line_count - 1
152
+ @editor.windows.each_value do |win|
153
+ next unless win.buffer_id == buf.id
154
+ win.cursor_y = last if win.cursor_y > last
155
+ end
156
+ end
157
+ buf.stream = nil
158
+ @editor.echo("[follow] stopped")
159
+ true
160
+ end
161
+
162
+ def follow_active?(buf)
163
+ buf.stream.is_a?(Stream::Follow)
164
+ end
165
+
166
+ def open_path_with_large_file_support(path)
167
+ return @editor.open_path_sync(path) unless should_open_path_async?(path)
168
+ return @editor.open_path_sync(path) unless can_start_async_file_load?
169
+
170
+ open_path_asynchronously!(path)
171
+ end
172
+
173
+ def shutdown!
174
+ @editor.buffers.each_value do |buf|
175
+ buf.stream&.stop!
176
+ rescue StandardError
177
+ nil
178
+ end
179
+ end
180
+
181
+ def ensure_event_queue!
182
+ @stream_event_queue ||= Queue.new
183
+ end
184
+
185
+ private
186
+
187
+ def apply_stream_chunk!(buffer_id, text)
188
+ return false if text.to_s.empty?
189
+
190
+ buf = @editor.buffers[buffer_id]
191
+ return false unless buf
192
+
193
+ following_win_ids = @editor.windows.values.filter_map do |win|
194
+ next unless win.buffer_id == buf.id
195
+ next unless stream_window_following_end?(win, buf)
196
+ win.id
197
+ end
198
+
199
+ buf.append_stream_text!(text)
200
+
201
+ following_win_ids.each do |win_id|
202
+ win = @editor.windows[win_id]
203
+ move_window_to_stream_end!(win, buf) if win
204
+ end
205
+
206
+ true
207
+ end
208
+
209
+ def finish_stream!(buffer_id, status: nil)
210
+ buf = @editor.buffers[buffer_id]
211
+ return false unless buf&.stream
212
+
213
+ stream = buf.stream
214
+ stream.thread = nil if stream.respond_to?(:thread=)
215
+ stream.io = nil if stream.respond_to?(:io=)
216
+
217
+ # Remove trailing empty line if present
218
+ if buf.lines.length > 1 && buf.lines[-1] == ""
219
+ buf.lines.pop
220
+ end
221
+ stream.state = :closed
222
+ stream.exit_status = status if stream.respond_to?(:exit_status=)
223
+
224
+ if status
225
+ @editor.echo("#{buf.display_name} exit #{status.exitstatus}")
226
+ elsif buf.kind == :stream
227
+ @editor.echo("[stdin] EOF")
228
+ else
229
+ @editor.echo("#{buf.display_name} #{buf.line_count} lines")
230
+ end
231
+ true
232
+ end
233
+
234
+ def fail_stream!(buffer_id, error)
235
+ buf = @editor.buffers[buffer_id]
236
+ return false unless buf&.stream
237
+
238
+ # Ignore errors from intentionally closed streams
239
+ if buf.stream.state == :closed
240
+ msg = error.to_s.downcase
241
+ return false if msg.include?("stream closed") || msg.include?("closed in another thread")
242
+ end
243
+
244
+ stream = buf.stream
245
+ stream.thread = nil if stream.respond_to?(:thread=)
246
+ stream.io = nil if stream.respond_to?(:io=)
247
+ stream.state = :error
248
+ @editor.echo_error("#{buf.display_name} stream error: #{error}")
249
+ true
250
+ end
251
+
252
+ def apply_async_file_lines!(buffer_id, head, lines, loaded_bytes: nil, file_size: nil)
253
+ buf = @editor.buffers[buffer_id]
254
+ return false unless buf
255
+
256
+ following_win_ids = if buf.stream.is_a?(Stream::Follow)
257
+ @editor.windows.values.filter_map do |win|
258
+ next unless win.buffer_id == buffer_id
259
+ next unless stream_window_following_end?(win, buf)
260
+ win.id
261
+ end
262
+ end
263
+
264
+ buf.append_stream_lines!(head, lines)
265
+
266
+ following_win_ids&.each do |win_id|
267
+ win = @editor.windows[win_id]
268
+ move_window_to_stream_end!(win, buf) if win
269
+ end
270
+
271
+ if loaded_bytes && file_size && file_size > 0
272
+ pct = (loaded_bytes * 100.0 / file_size).clamp(0, 100)
273
+ @editor.echo(format("\"%s\" loading... %d%%", buf.display_name, pct))
274
+ end
275
+
276
+ true
277
+ end
278
+
279
+ def finish_async_file_load!(buffer_id, ended_with_newline:)
280
+ buf = @editor.buffers[buffer_id]
281
+ return false unless buf
282
+
283
+ buf.finalize_async_file_load!(ended_with_newline: !!ended_with_newline)
284
+ buf.stream.state = :closed if buf.stream
285
+ @editor.echo(format("\"%s\" %dL", buf.display_name, buf.line_count))
286
+ true
287
+ end
288
+
289
+ def fail_async_file_load!(buffer_id, error)
290
+ buf = @editor.buffers[buffer_id]
291
+ if buf&.stream
292
+ buf.stream.state = :error
293
+ end
294
+ @editor.echo_error("\"#{(buf && buf.display_name) || buffer_id}\" load error: #{error}")
295
+ true
296
+ end
297
+
298
+ def stream_window_following_end?(win, buf)
299
+ return false unless win
300
+
301
+ last_row = buf.line_count - 1
302
+ win.cursor_y >= last_row
303
+ end
304
+
305
+ def move_window_to_stream_end!(win, buf)
306
+ return unless win && buf
307
+
308
+ last_row = buf.line_count - 1
309
+ win.cursor_y = last_row
310
+ win.cursor_x = buf.line_length(last_row)
311
+ win.clamp_to_buffer(buf)
312
+ end
313
+
314
+ def should_open_path_async?(path)
315
+ p = path.to_s
316
+ return false if p.empty?
317
+ return false unless File.file?(p)
318
+
319
+ File.size(p) >= large_file_async_threshold_bytes
320
+ rescue StandardError
321
+ false
322
+ end
323
+
324
+ def can_start_async_file_load?
325
+ @editor.buffers.none? { |_, buf| buf.stream.is_a?(Stream::FileLoad) && buf.stream.live? }
326
+ end
327
+
328
+ def large_file_async_threshold_bytes
329
+ raw = ENV["RUVIM_ASYNC_FILE_THRESHOLD_BYTES"]
330
+ n = raw.to_i if raw
331
+ return n if n && n.positive?
332
+
333
+ LARGE_FILE_ASYNC_THRESHOLD_BYTES
334
+ end
335
+
336
+ def open_path_asynchronously!(path)
337
+ file_size = File.size(path)
338
+ buf = @editor.add_empty_buffer(path: path)
339
+ @editor.switch_to_buffer(buf.id)
340
+ buf.modified = false
341
+
342
+ ensure_event_queue!
343
+ io = File.open(path, "rb")
344
+ staged_prefix_bytes = async_file_staged_prefix_bytes
345
+ staged_mode = file_size > staged_prefix_bytes
346
+ if staged_mode
347
+ prefix = io.read(staged_prefix_bytes) || "".b
348
+ unless prefix.empty?
349
+ last_nl = prefix.rindex("\n".b)
350
+ if last_nl && last_nl < prefix.bytesize - 1
351
+ remainder = prefix.bytesize - last_nl - 1
352
+ prefix = prefix[0..last_nl]
353
+ io.seek(-remainder, IO::SEEK_CUR)
354
+ end
355
+ buf.append_stream_text!(Buffer.decode_text(prefix))
356
+ end
357
+ end
358
+
359
+ if io.eof?
360
+ buf.finalize_async_file_load!(ended_with_newline: prefix&.end_with?("\n") || false)
361
+ io.close unless io.closed?
362
+ return buf
363
+ end
364
+
365
+ # Create FileLoad stream after prefix reading; starts background thread immediately
366
+ buf.stream = Stream::FileLoad.new(io: io, file_size: file_size, buffer_id: buf.id, queue: @stream_event_queue, &method(:notify_signal_wakeup))
367
+
368
+ size_mb = file_size.fdiv(1024 * 1024)
369
+ if staged_mode
370
+ @editor.echo(format("\"%s\" loading... (showing first %.0fMB of %.1fMB)", path, staged_prefix_bytes.fdiv(1024 * 1024), size_mb))
371
+ else
372
+ @editor.echo(format("\"%s\" loading... (%.1fMB)", path, size_mb))
373
+ end
374
+ buf
375
+ rescue StandardError
376
+ buf.stream = nil if buf
377
+ raise
378
+ end
379
+
380
+ def async_file_staged_prefix_bytes
381
+ raw = ENV["RUVIM_ASYNC_FILE_PREFIX_BYTES"]
382
+ n = raw.to_i if raw
383
+ return n if n && n.positive?
384
+
385
+ LARGE_FILE_STAGED_PREFIX_BYTES
386
+ end
387
+
388
+ def notify_signal_wakeup
389
+ @signal_w.write_nonblock(".")
390
+ rescue IO::WaitWritable, Errno::EPIPE
391
+ nil
392
+ end
393
+ end
394
+ end
@@ -22,17 +22,31 @@ module RuVim
22
22
 
23
23
  def with_ui
24
24
  @stdin.raw do
25
- write("\e]112\a\e[?1049h\e[?25l")
25
+ write("\e]112\a\e[2 q\e[?1049h\e[?25l")
26
26
  yield
27
27
  ensure
28
- write("\e[?25h\e[?1049l")
28
+ write("\e[0 q\e[?25h\e[?1049l")
29
+ end
30
+ end
31
+
32
+ def suspend_for_shell(command)
33
+ shell = ENV["SHELL"].to_s
34
+ shell = "/bin/sh" if shell.empty?
35
+ @stdin.cooked do
36
+ write("\e[0 q\e[?25h\e[?1049l")
37
+ system(shell, "-c", command)
38
+ status = $?
39
+ write("\r\nPress ENTER or type command to continue")
40
+ @stdin.raw { @stdin.getc }
41
+ write("\e[2 q\e[?1049h\e[?25l")
42
+ status
29
43
  end
30
44
  end
31
45
 
32
46
  def suspend_for_tstp
33
47
  prev_tstp = Signal.trap("TSTP", "DEFAULT")
34
48
  @stdin.cooked do
35
- write("\e[?25h\e[?1049l")
49
+ write("\e[0 q\e[?25h\e[?1049l")
36
50
  Process.kill("TSTP", 0)
37
51
  end
38
52
  ensure
@@ -41,7 +55,7 @@ module RuVim
41
55
  rescue StandardError
42
56
  nil
43
57
  end
44
- write("\e[?1049h\e[?25l")
58
+ write("\e[2 q\e[?1049h\e[?25l")
45
59
  end
46
60
  end
47
61
  end