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.
- checksums.yaml +4 -4
- data/AGENTS.md +68 -7
- data/README.md +30 -7
- data/Rakefile +7 -0
- data/benchmark/cext_compare.rb +165 -0
- data/benchmark/chunked_load.rb +256 -0
- data/benchmark/file_load.rb +140 -0
- data/benchmark/hotspots.rb +178 -0
- data/docs/binding.md +18 -1
- data/docs/command.md +156 -10
- data/docs/config.md +10 -2
- data/docs/done.md +23 -0
- data/docs/spec.md +162 -25
- data/docs/todo.md +9 -0
- data/docs/tutorial.md +33 -1
- data/docs/vim_diff.md +31 -8
- data/ext/ruvim/extconf.rb +5 -0
- data/ext/ruvim/ruvim_ext.c +519 -0
- data/lib/ruvim/app.rb +246 -2525
- data/lib/ruvim/browser.rb +104 -0
- data/lib/ruvim/buffer.rb +43 -20
- data/lib/ruvim/cli.rb +6 -0
- data/lib/ruvim/command_invocation.rb +2 -2
- data/lib/ruvim/completion_manager.rb +708 -0
- data/lib/ruvim/dispatcher.rb +14 -8
- data/lib/ruvim/display_width.rb +91 -45
- data/lib/ruvim/editor.rb +74 -80
- data/lib/ruvim/ex_command_registry.rb +3 -1
- data/lib/ruvim/file_watcher.rb +243 -0
- data/lib/ruvim/gh/link.rb +207 -0
- data/lib/ruvim/git/blame.rb +255 -0
- data/lib/ruvim/git/branch.rb +112 -0
- data/lib/ruvim/git/commit.rb +102 -0
- data/lib/ruvim/git/diff.rb +129 -0
- data/lib/ruvim/git/grep.rb +107 -0
- data/lib/ruvim/git/handler.rb +125 -0
- data/lib/ruvim/git/log.rb +41 -0
- data/lib/ruvim/git/status.rb +103 -0
- data/lib/ruvim/global_commands.rb +351 -77
- data/lib/ruvim/highlighter.rb +4 -11
- data/lib/ruvim/input.rb +1 -0
- data/lib/ruvim/key_handler.rb +1510 -0
- data/lib/ruvim/keymap_manager.rb +7 -7
- data/lib/ruvim/lang/base.rb +5 -0
- data/lib/ruvim/lang/c.rb +116 -0
- data/lib/ruvim/lang/cpp.rb +107 -0
- data/lib/ruvim/lang/csv.rb +4 -1
- data/lib/ruvim/lang/diff.rb +43 -0
- data/lib/ruvim/lang/dockerfile.rb +36 -0
- data/lib/ruvim/lang/elixir.rb +85 -0
- data/lib/ruvim/lang/erb.rb +30 -0
- data/lib/ruvim/lang/go.rb +83 -0
- data/lib/ruvim/lang/html.rb +34 -0
- data/lib/ruvim/lang/javascript.rb +83 -0
- data/lib/ruvim/lang/json.rb +40 -0
- data/lib/ruvim/lang/lua.rb +76 -0
- data/lib/ruvim/lang/makefile.rb +36 -0
- data/lib/ruvim/lang/markdown.rb +3 -4
- data/lib/ruvim/lang/ocaml.rb +77 -0
- data/lib/ruvim/lang/perl.rb +91 -0
- data/lib/ruvim/lang/python.rb +85 -0
- data/lib/ruvim/lang/registry.rb +102 -0
- data/lib/ruvim/lang/ruby.rb +7 -0
- data/lib/ruvim/lang/rust.rb +95 -0
- data/lib/ruvim/lang/scheme.rb +5 -0
- data/lib/ruvim/lang/sh.rb +76 -0
- data/lib/ruvim/lang/sql.rb +52 -0
- data/lib/ruvim/lang/toml.rb +36 -0
- data/lib/ruvim/lang/tsv.rb +4 -1
- data/lib/ruvim/lang/typescript.rb +53 -0
- data/lib/ruvim/lang/yaml.rb +62 -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/table_renderer.rb +3 -3
- data/lib/ruvim/rich_view.rb +30 -7
- data/lib/ruvim/screen.rb +135 -84
- data/lib/ruvim/stream/file_load.rb +85 -0
- data/lib/ruvim/stream/follow.rb +40 -0
- data/lib/ruvim/stream/git.rb +43 -0
- data/lib/ruvim/stream/run.rb +74 -0
- data/lib/ruvim/stream/stdin.rb +55 -0
- data/lib/ruvim/stream.rb +35 -0
- data/lib/ruvim/stream_mixer.rb +394 -0
- data/lib/ruvim/terminal.rb +18 -4
- data/lib/ruvim/text_metrics.rb +84 -65
- data/lib/ruvim/version.rb +1 -1
- data/lib/ruvim/window.rb +5 -5
- data/lib/ruvim.rb +31 -4
- data/test/app_command_test.rb +382 -0
- data/test/app_completion_test.rb +65 -16
- data/test/app_dot_repeat_test.rb +27 -3
- data/test/app_ex_command_test.rb +154 -0
- data/test/app_motion_test.rb +13 -12
- data/test/app_register_test.rb +2 -1
- data/test/app_scenario_test.rb +182 -8
- data/test/app_startup_test.rb +70 -27
- data/test/app_text_object_test.rb +2 -1
- data/test/app_unicode_behavior_test.rb +3 -2
- data/test/browser_test.rb +88 -0
- data/test/buffer_test.rb +24 -0
- data/test/cli_test.rb +77 -0
- data/test/clipboard_test.rb +67 -0
- data/test/command_invocation_test.rb +33 -0
- data/test/command_line_test.rb +118 -0
- data/test/config_dsl_test.rb +134 -0
- data/test/dispatcher_test.rb +74 -4
- data/test/display_width_test.rb +41 -0
- data/test/ex_command_registry_test.rb +106 -0
- data/test/file_watcher_test.rb +197 -0
- data/test/follow_test.rb +198 -0
- data/test/gh_link_test.rb +141 -0
- data/test/git_blame_test.rb +792 -0
- data/test/git_grep_test.rb +64 -0
- data/test/highlighter_test.rb +169 -0
- data/test/indent_test.rb +223 -0
- data/test/input_screen_integration_test.rb +1 -1
- data/test/keyword_chars_test.rb +85 -0
- data/test/lang_test.rb +634 -0
- data/test/markdown_renderer_test.rb +5 -5
- data/test/on_save_hook_test.rb +12 -8
- data/test/render_snapshot_test.rb +78 -0
- data/test/rich_view_test.rb +279 -23
- data/test/run_command_test.rb +307 -0
- data/test/screen_test.rb +68 -5
- data/test/search_option_test.rb +19 -0
- data/test/stream_test.rb +165 -0
- data/test/test_helper.rb +9 -0
- data/test/window_test.rb +59 -0
- 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, ¬ify)
|
|
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
|
data/lib/ruvim/stream.rb
ADDED
|
@@ -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
|
data/lib/ruvim/terminal.rb
CHANGED
|
@@ -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
|