ruvim 0.4.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 +53 -4
- data/README.md +15 -6
- 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 +3 -2
- data/docs/command.md +81 -9
- data/docs/done.md +23 -0
- data/docs/spec.md +105 -19
- data/docs/todo.md +9 -0
- data/docs/tutorial.md +9 -1
- data/docs/vim_diff.md +13 -0
- data/ext/ruvim/extconf.rb +5 -0
- data/ext/ruvim/ruvim_ext.c +519 -0
- data/lib/ruvim/app.rb +217 -2778
- data/lib/ruvim/browser.rb +104 -0
- data/lib/ruvim/buffer.rb +39 -28
- 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 +64 -81
- data/lib/ruvim/ex_command_registry.rb +3 -1
- data/lib/ruvim/gh/link.rb +207 -0
- data/lib/ruvim/git/blame.rb +16 -6
- data/lib/ruvim/git/branch.rb +20 -5
- data/lib/ruvim/git/grep.rb +107 -0
- data/lib/ruvim/git/handler.rb +42 -1
- data/lib/ruvim/global_commands.rb +175 -35
- data/lib/ruvim/highlighter.rb +4 -13
- 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 +2 -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 +6 -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/table_renderer.rb +3 -3
- data/lib/ruvim/rich_view.rb +14 -7
- data/lib/ruvim/screen.rb +126 -72
- 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 +23 -6
- data/test/app_command_test.rb +382 -0
- data/test/app_completion_test.rb +43 -19
- 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 +15 -10
- 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 +63 -0
- data/test/command_invocation_test.rb +33 -0
- data/test/config_dsl_test.rb +47 -0
- data/test/dispatcher_test.rb +74 -4
- data/test/ex_command_registry_test.rb +106 -0
- data/test/follow_test.rb +20 -21
- data/test/gh_link_test.rb +141 -0
- data/test/git_blame_test.rb +96 -17
- data/test/git_grep_test.rb +64 -0
- data/test/highlighter_test.rb +125 -0
- data/test/indent_test.rb +137 -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 +42 -42
- data/test/run_command_test.rb +307 -0
- data/test/screen_test.rb +68 -5
- data/test/stream_test.rb +165 -0
- data/test/window_test.rb +59 -0
- metadata +52 -2
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Benchmark file loading hotspots
|
|
5
|
+
#
|
|
6
|
+
# Usage: ruby benchmark/file_load.rb [path]
|
|
7
|
+
# Default: huge_file in project root
|
|
8
|
+
|
|
9
|
+
require "benchmark"
|
|
10
|
+
|
|
11
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
12
|
+
require "ruvim/buffer"
|
|
13
|
+
require "ruvim/lang/registry"
|
|
14
|
+
require "ruvim/lang/base"
|
|
15
|
+
|
|
16
|
+
FILE = ARGV[0] || File.expand_path("../huge_file", __dir__)
|
|
17
|
+
unless File.exist?(FILE)
|
|
18
|
+
abort "File not found: #{FILE}\nUsage: ruby benchmark/file_load.rb [path]"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
size_mb = File.size(FILE) / 1024.0 / 1024.0
|
|
22
|
+
puts "File Load Benchmark"
|
|
23
|
+
puts "=" * 60
|
|
24
|
+
puts "Ruby: #{RUBY_VERSION}"
|
|
25
|
+
puts "File: #{FILE}"
|
|
26
|
+
puts "Size: %.1f MB" % size_mb
|
|
27
|
+
puts
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
# 1. Raw IO read
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
puts "--- 1. Raw file read ---"
|
|
33
|
+
Benchmark.bm(25) do |x|
|
|
34
|
+
x.report("File.binread") do
|
|
35
|
+
data = File.binread(FILE)
|
|
36
|
+
data.size # force read
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
puts
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# 2. decode_text (encoding detection)
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
puts "--- 2. decode_text ---"
|
|
45
|
+
raw = File.binread(FILE)
|
|
46
|
+
Benchmark.bm(25) do |x|
|
|
47
|
+
x.report("decode_text") do
|
|
48
|
+
RuVim::Buffer.decode_text(raw)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
puts
|
|
52
|
+
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
# 3. split_lines (String#split)
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
puts "--- 3. split_lines ---"
|
|
57
|
+
decoded = RuVim::Buffer.decode_text(raw)
|
|
58
|
+
Benchmark.bm(25) do |x|
|
|
59
|
+
x.report("split(\"\\n\", -1)") do
|
|
60
|
+
decoded.split("\n", -1)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
puts
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
# 4. Full Buffer.from_file
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
puts "--- 4. Full Buffer.from_file ---"
|
|
69
|
+
Benchmark.bm(25) do |x|
|
|
70
|
+
x.report("Buffer.from_file") do
|
|
71
|
+
RuVim::Buffer.from_file(id: 1, path: FILE)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
puts
|
|
75
|
+
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
# 5. Chunked read (simulating Stream::FileLoad)
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
puts "--- 5. Chunked read (4MB flush, simulating async load) ---"
|
|
80
|
+
CHUNK = 1 * 1024 * 1024
|
|
81
|
+
FLUSH = 4 * 1024 * 1024
|
|
82
|
+
Benchmark.bm(25) do |x|
|
|
83
|
+
x.report("chunked read+split") do
|
|
84
|
+
io = File.open(FILE, "rb")
|
|
85
|
+
lines = [""]
|
|
86
|
+
pending = "".b
|
|
87
|
+
begin
|
|
88
|
+
loop do
|
|
89
|
+
chunk = io.readpartial(CHUNK)
|
|
90
|
+
pending << chunk
|
|
91
|
+
next if pending.bytesize < FLUSH
|
|
92
|
+
|
|
93
|
+
last_nl = pending.rindex("\n".b)
|
|
94
|
+
if last_nl
|
|
95
|
+
send_bytes = pending[0..last_nl]
|
|
96
|
+
pending = pending[(last_nl + 1)..] || "".b
|
|
97
|
+
else
|
|
98
|
+
send_bytes = pending
|
|
99
|
+
pending = "".b
|
|
100
|
+
end
|
|
101
|
+
decoded = RuVim::Buffer.decode_text(send_bytes)
|
|
102
|
+
parts = decoded.split("\n", -1)
|
|
103
|
+
head = parts.shift || ""
|
|
104
|
+
lines[-1] = lines[-1] + head unless head.empty?
|
|
105
|
+
lines.concat(parts) unless parts.empty?
|
|
106
|
+
end
|
|
107
|
+
rescue EOFError
|
|
108
|
+
unless pending.empty?
|
|
109
|
+
decoded = RuVim::Buffer.decode_text(pending)
|
|
110
|
+
parts = decoded.split("\n", -1)
|
|
111
|
+
head = parts.shift || ""
|
|
112
|
+
lines[-1] = lines[-1] + head unless head.empty?
|
|
113
|
+
lines.concat(parts) unless parts.empty?
|
|
114
|
+
end
|
|
115
|
+
ensure
|
|
116
|
+
io.close
|
|
117
|
+
end
|
|
118
|
+
puts " lines loaded: #{lines.size}"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
puts
|
|
122
|
+
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
# 6. Memory profile
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
puts "--- 6. Memory usage ---"
|
|
127
|
+
GC.start
|
|
128
|
+
before = GC.stat[:malloc_increase_bytes_limit]
|
|
129
|
+
mem_before = `ps -o rss= -p #{$$}`.strip.to_i
|
|
130
|
+
buf = RuVim::Buffer.from_file(id: 2, path: FILE)
|
|
131
|
+
GC.start
|
|
132
|
+
mem_after = `ps -o rss= -p #{$$}`.strip.to_i
|
|
133
|
+
puts " Lines: #{buf.line_count}"
|
|
134
|
+
puts " RSS before: #{mem_before / 1024} MB"
|
|
135
|
+
puts " RSS after: #{mem_after / 1024} MB"
|
|
136
|
+
puts " RSS delta: #{(mem_after - mem_before) / 1024} MB"
|
|
137
|
+
|
|
138
|
+
puts
|
|
139
|
+
puts "=" * 60
|
|
140
|
+
puts "Done."
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Benchmark for C-extension candidate hotspots in RuVim
|
|
5
|
+
#
|
|
6
|
+
# Usage: ruby benchmark/hotspots.rb
|
|
7
|
+
|
|
8
|
+
require "benchmark"
|
|
9
|
+
|
|
10
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
11
|
+
require "ruvim/display_width"
|
|
12
|
+
require "ruvim/text_metrics"
|
|
13
|
+
require "ruvim/highlighter"
|
|
14
|
+
require "ruvim/lang/registry"
|
|
15
|
+
require "ruvim/lang/base"
|
|
16
|
+
require "ruvim/lang/ruby"
|
|
17
|
+
require "ruvim/lang/c"
|
|
18
|
+
require "ruvim/lang/markdown"
|
|
19
|
+
require "ruvim/lang/json"
|
|
20
|
+
require "ruvim/lang/javascript"
|
|
21
|
+
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
# Test data
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
ASCII_LINE = ' def foo(bar, baz) = bar + baz # comment with some padding text here!!!' * 2
|
|
26
|
+
CJK_LINE = "日本語テキスト 漢字カタカナ テスト用の行 全角文字を含む行" * 2
|
|
27
|
+
MIXED_LINE = "Hello 世界! def foo(x) = x + 1 # コメント 🚀 emoji test"
|
|
28
|
+
EMOJI_LINE = "🎉🔥💡🚀✨🎊🌟💎🏆🎯" * 5
|
|
29
|
+
TAB_LINE = "\t\tif (x > 0) {\n\t\t\treturn x;\n\t\t}"
|
|
30
|
+
|
|
31
|
+
RUBY_LINE = ' def initialize(name, value: nil) # keyword args'
|
|
32
|
+
C_LINE = ' int *ptr = malloc(sizeof(struct node)); /* alloc */'
|
|
33
|
+
MD_LINE = '## Heading with **bold** and `code` and [link](url)'
|
|
34
|
+
JSON_LINE = ' {"key": "value", "number": 42, "bool": true, "null": null}'
|
|
35
|
+
JS_LINE = ' const fn = async (x) => { return await fetch(url); };'
|
|
36
|
+
|
|
37
|
+
LINES_80COL = [ASCII_LINE, CJK_LINE, MIXED_LINE, RUBY_LINE, C_LINE].freeze
|
|
38
|
+
SCREEN_WIDTH = 120
|
|
39
|
+
SCREEN_ROWS = 50
|
|
40
|
+
|
|
41
|
+
N_ITER = 10_000
|
|
42
|
+
N_RENDER = 2_000
|
|
43
|
+
|
|
44
|
+
puts "RuVim Hotspot Benchmark"
|
|
45
|
+
puts "=" * 60
|
|
46
|
+
puts "Ruby: #{RUBY_VERSION} (#{RUBY_PLATFORM})"
|
|
47
|
+
puts "Iterations: #{N_ITER} (width), #{N_RENDER} (render)"
|
|
48
|
+
puts
|
|
49
|
+
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
# 1. DisplayWidth.cell_width — per-character width lookup
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
puts "--- 1. DisplayWidth.cell_width ---"
|
|
54
|
+
chars = {
|
|
55
|
+
"ASCII 'A'" => "A",
|
|
56
|
+
"CJK '漢'" => "漢",
|
|
57
|
+
"Emoji '🚀'" => "🚀",
|
|
58
|
+
"Combining" => "\u0301", # combining acute
|
|
59
|
+
"Tab" => "\t",
|
|
60
|
+
}
|
|
61
|
+
Benchmark.bm(20) do |x|
|
|
62
|
+
chars.each do |label, ch|
|
|
63
|
+
x.report(label) { N_ITER.times { RuVim::DisplayWidth.cell_width(ch) } }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
puts
|
|
67
|
+
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
# 2. DisplayWidth.display_width — whole-line width
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
puts "--- 2. DisplayWidth.display_width ---"
|
|
72
|
+
lines = {
|
|
73
|
+
"ASCII (140c)" => ASCII_LINE,
|
|
74
|
+
"CJK (56c)" => CJK_LINE,
|
|
75
|
+
"Mixed" => MIXED_LINE,
|
|
76
|
+
"Emoji (50c)" => EMOJI_LINE,
|
|
77
|
+
"Tabs" => TAB_LINE,
|
|
78
|
+
}
|
|
79
|
+
Benchmark.bm(20) do |x|
|
|
80
|
+
lines.each do |label, line|
|
|
81
|
+
x.report(label) { N_ITER.times { RuVim::DisplayWidth.display_width(line) } }
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
puts
|
|
85
|
+
|
|
86
|
+
# ---------------------------------------------------------------------------
|
|
87
|
+
# 3. TextMetrics.clip_cells_for_width — cell array construction
|
|
88
|
+
# ---------------------------------------------------------------------------
|
|
89
|
+
puts "--- 3. TextMetrics.clip_cells_for_width ---"
|
|
90
|
+
Benchmark.bm(20) do |x|
|
|
91
|
+
lines.each do |label, line|
|
|
92
|
+
x.report(label) { N_RENDER.times { RuVim::TextMetrics.clip_cells_for_width(line, SCREEN_WIDTH) } }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
puts
|
|
96
|
+
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
# 4. TextMetrics.char_index_for_screen_col — cursor positioning
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
puts "--- 4. TextMetrics.char_index_for_screen_col ---"
|
|
101
|
+
Benchmark.bm(20) do |x|
|
|
102
|
+
lines.each do |label, line|
|
|
103
|
+
target = RuVim::DisplayWidth.display_width(line) / 2
|
|
104
|
+
x.report(label) { N_ITER.times { RuVim::TextMetrics.char_index_for_screen_col(line, target) } }
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
puts
|
|
108
|
+
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
# 5. Highlighter.color_columns — syntax highlighting
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
puts "--- 5. Highlighter.color_columns ---"
|
|
113
|
+
highlight_cases = {
|
|
114
|
+
"Ruby" => ["ruby", RUBY_LINE],
|
|
115
|
+
"C" => ["c", C_LINE],
|
|
116
|
+
"Markdown" => ["markdown", MD_LINE],
|
|
117
|
+
"JSON" => ["json", JSON_LINE],
|
|
118
|
+
"JavaScript" => ["javascript", JS_LINE],
|
|
119
|
+
}
|
|
120
|
+
Benchmark.bm(20) do |x|
|
|
121
|
+
highlight_cases.each do |label, (ft, line)|
|
|
122
|
+
x.report(label) { N_RENDER.times { RuVim::Highlighter.color_columns(ft, line) } }
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
puts
|
|
126
|
+
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
# 6. Simulated full-screen render pass (clip + highlight per line)
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
puts "--- 6. Simulated screen render (#{SCREEN_ROWS} lines × #{SCREEN_WIDTH} cols) ---"
|
|
131
|
+
screen_lines = Array.new(SCREEN_ROWS) { |i| LINES_80COL[i % LINES_80COL.size] }
|
|
132
|
+
Benchmark.bm(20) do |x|
|
|
133
|
+
x.report("clip_cells only") do
|
|
134
|
+
N_RENDER.times do
|
|
135
|
+
screen_lines.each { |line| RuVim::TextMetrics.clip_cells_for_width(line, SCREEN_WIDTH) }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
x.report("highlight only") do
|
|
139
|
+
N_RENDER.times do
|
|
140
|
+
screen_lines.each_with_index do |line, i|
|
|
141
|
+
ft = %w[ruby c markdown json javascript][i % 5]
|
|
142
|
+
RuVim::Highlighter.color_columns(ft, line)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
x.report("clip + highlight") do
|
|
147
|
+
N_RENDER.times do
|
|
148
|
+
screen_lines.each_with_index do |line, i|
|
|
149
|
+
ft = %w[ruby c markdown json javascript][i % 5]
|
|
150
|
+
RuVim::TextMetrics.clip_cells_for_width(line, SCREEN_WIDTH)
|
|
151
|
+
RuVim::Highlighter.color_columns(ft, line)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
puts
|
|
157
|
+
|
|
158
|
+
# ---------------------------------------------------------------------------
|
|
159
|
+
# 7. DisplayWidth.expand_tabs
|
|
160
|
+
# ---------------------------------------------------------------------------
|
|
161
|
+
puts "--- 7. DisplayWidth.expand_tabs ---"
|
|
162
|
+
tab_lines = {
|
|
163
|
+
"2 tabs" => "\t\tcode here",
|
|
164
|
+
"mixed" => "abc\tdef\tghi\tjkl",
|
|
165
|
+
"heavy" => "\t" * 20 + "end",
|
|
166
|
+
}
|
|
167
|
+
Benchmark.bm(20) do |x|
|
|
168
|
+
tab_lines.each do |label, line|
|
|
169
|
+
x.report(label) { N_ITER.times { RuVim::DisplayWidth.expand_tabs(line) } }
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
puts
|
|
173
|
+
|
|
174
|
+
# ---------------------------------------------------------------------------
|
|
175
|
+
# Summary
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
puts "=" * 60
|
|
178
|
+
puts "Done. Compare user+sys times to identify C-extension priorities."
|
data/docs/binding.md
CHANGED
|
@@ -90,8 +90,9 @@
|
|
|
90
90
|
- `gr` : Rich mode をトグル(TSV/CSV をテーブル整形表示、同一バッファ上で動作)
|
|
91
91
|
- `g/` : 検索パターンにマッチする行だけを集めたフィルタバッファを作成(再帰フィルタ可、Enter で元行へジャンプ、`:q` で戻る)
|
|
92
92
|
- `Ctrl-g` : Git コマンドモード(`:git ` がプリセットされたコマンドラインに入る)
|
|
93
|
-
- `blame` → Git blame を開く / `status` → Git status / `diff` → Git diff / `log` → Git log
|
|
93
|
+
- `blame` → Git blame を開く / `status` → Git status / `diff` → Git diff / `log` → Git log / `branch` → ブランチ一覧
|
|
94
94
|
- Blame バッファ内: `p` 親コミットの blame へ遷移 / `P` 前の blame へ戻る / `c` コミット詳細を表示
|
|
95
|
+
- Branch バッファ内: `Enter` で `:git checkout <branch>` をコマンドラインにプリセット(即時実行ではなく確認ステップあり)
|
|
95
96
|
- `Ctrl-c` : stdin stream 停止(stdin バッファ表示時)
|
|
96
97
|
- `Esc` : メッセージ/保留入力のクリア
|
|
97
98
|
- `Ctrl-z` : shell へ suspend(`fg` で復帰)
|
|
@@ -148,7 +149,7 @@
|
|
|
148
149
|
- `Backspace` : 1文字削除
|
|
149
150
|
- `Up` / `Down` : 履歴移動
|
|
150
151
|
- `Left` / `Right` : カーソル移動
|
|
151
|
-
- `Tab` (`Ctrl-i`) : Ex 補完(`:` prefix
|
|
152
|
+
- `Tab` (`Ctrl-i`) : Ex 補完(`:` prefix 時、コマンド名/一部引数の文脈対応。`:git`/`:gh` サブコマンドも補完可能)
|
|
152
153
|
- `Esc` : キャンセル
|
|
153
154
|
- `Ctrl-c` : キャンセル
|
|
154
155
|
- `Ctrl-z` : shell へ suspend(`fg` で復帰)
|
data/docs/command.md
CHANGED
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
- 未保存変更がある場合は `!` なしで拒否
|
|
72
72
|
- `:e!` は未保存変更を破棄して開き直す(undo/redo もクリア)
|
|
73
73
|
- 大きいファイル(閾値以上)は段階読み込みになる場合がある
|
|
74
|
-
- 読み込み中は
|
|
74
|
+
- 読み込み中は statusline に `[load/live]`
|
|
75
75
|
- デフォルトでは先頭 `8MB` を先に表示して、残りを後から追加
|
|
76
76
|
- 閾値: `RUVIM_ASYNC_FILE_THRESHOLD_BYTES`
|
|
77
77
|
- 先読みサイズ: `RUVIM_ASYNC_FILE_PREFIX_BYTES`
|
|
@@ -115,13 +115,49 @@
|
|
|
115
115
|
- current window の location list を開く / 閉じる
|
|
116
116
|
- `:lnext` / `:ln`, `:lprev` / `:lp`
|
|
117
117
|
- location list 項目を前後移動して該当位置へジャンプ
|
|
118
|
-
- `:grep
|
|
118
|
+
- `:grep pattern [path...]`
|
|
119
119
|
- 外部 grep(`grepprg`)を実行して quickfix list を作成
|
|
120
|
-
-
|
|
120
|
+
- シェル経由ではなく argv 配列で安全に実行(シェルインジェクション対策済み)
|
|
121
|
+
- ワイルドカード(`*.txt` 等)は Ruby の `Dir.glob` で展開
|
|
122
|
+
- `-Z`(restricted mode)では無効
|
|
123
|
+
- `:lgrep pattern [path...]`
|
|
121
124
|
- 外部 grep(`grepprg`)を実行して location list を作成
|
|
125
|
+
- `:grep` と同じセキュリティ仕様
|
|
126
|
+
- 一覧バッファ上で `Enter` : 選択項目へジャンプ(一覧ウィンドウから元の編集ウィンドウへ戻る)
|
|
122
127
|
- 現状の制限:
|
|
123
128
|
- `:make`, `:cfile`, `:lfile` は未実装
|
|
124
|
-
|
|
129
|
+
|
|
130
|
+
### `:git`
|
|
131
|
+
|
|
132
|
+
- 形式: `:git <subcommand> [args...]`
|
|
133
|
+
- Git 操作の統合インターフェース
|
|
134
|
+
- `-Z`(restricted mode)では無効
|
|
135
|
+
- サブコマンド:
|
|
136
|
+
- `blame` : 現在ファイルの git blame を表示(read-only バッファ)
|
|
137
|
+
- blame バッファ内: `p` 親コミットの blame / `P` 前の blame へ戻る / `c` コミット詳細
|
|
138
|
+
- `status` : git status を表示
|
|
139
|
+
- `diff` : git diff を表示
|
|
140
|
+
- `log` : git log を表示
|
|
141
|
+
- `branch` : ブランチ一覧を表示
|
|
142
|
+
- branch バッファ内: `Enter` で `:git checkout <branch>` をコマンドラインにプリセット(確認ステップあり)
|
|
143
|
+
- `checkout <branch>` : 指定ブランチへチェックアウト
|
|
144
|
+
- `commit` : コミットメッセージ入力バッファを開く
|
|
145
|
+
- `grep <pattern> [args...]` : git grep -n を実行、結果バッファで Enter ジャンプ
|
|
146
|
+
- その他の未知サブコマンド: `:!` と同様に alternate screen でシェル実行(例: `:git stash`, `:git rebase -i`)
|
|
147
|
+
- Git バッファは `Esc` / `Ctrl-c` で閉じる
|
|
148
|
+
- Tab 補完: `git help -a` の出力から全サブコマンドを補完候補として提示(セッション内キャッシュ)
|
|
149
|
+
|
|
150
|
+
### `:gh`
|
|
151
|
+
|
|
152
|
+
- 形式: `:gh <subcommand> [args...]`
|
|
153
|
+
- GitHub 連携の統合インターフェース
|
|
154
|
+
- `-Z`(restricted mode)では無効
|
|
155
|
+
- サブコマンド:
|
|
156
|
+
- `link` : 現在ファイル/選択範囲の GitHub URL を生成してクリップボードにコピー
|
|
157
|
+
- `browse` : 現在ファイルの GitHub URL をブラウザで開く
|
|
158
|
+
- `pr` : 現在ブランチの PR ページをブラウザで開く
|
|
159
|
+
- その他の未知サブコマンド: `:!` と同様に alternate screen でシェル実行(例: `:gh issue list`)
|
|
160
|
+
- Tab 補完: `gh help` の出力からサブコマンドを補完候補として提示(セッション内キャッシュ)
|
|
125
161
|
|
|
126
162
|
### `:rich`
|
|
127
163
|
|
|
@@ -154,7 +190,7 @@
|
|
|
154
190
|
- `G` で末尾に移動すれば追従が再開される
|
|
155
191
|
- `:follow` を再度実行すると停止
|
|
156
192
|
- `Ctrl-C`(ノーマルモード)で follow を停止
|
|
157
|
-
-
|
|
193
|
+
- statusline に `[follow]` と表示される(inotify 使用時は `[follow/i]`)
|
|
158
194
|
- ファイルが紐づいていないバッファではエラー
|
|
159
195
|
- 未保存の変更があるバッファでは follow 開始不可
|
|
160
196
|
- follow 中のバッファは変更不可(modifiable が false)
|
|
@@ -179,14 +215,50 @@
|
|
|
179
215
|
- `stdout` / `stderr` に出力があった場合は `[Ruby Output]` 仮想バッファに表示(返り値も末尾に表示)
|
|
180
216
|
- 利用可能: `ctx`, `editor`, `buffer`, `window`
|
|
181
217
|
|
|
182
|
-
###
|
|
218
|
+
### `:r` / `:read`(ファイル・コマンド出力の挿入)
|
|
219
|
+
|
|
220
|
+
- 形式: `:r <file>` または `:r !<command>`
|
|
221
|
+
- ファイルの内容またはシェルコマンドの stdout をカーソル行の下に挿入します
|
|
222
|
+
- 行番号指定可: `:3r file.txt`(3行目の下に挿入)
|
|
223
|
+
- `:r !command` で stderr が出た場合、最初の行をエラーメッセージとして表示します
|
|
224
|
+
|
|
225
|
+
### `:w !`(バッファ内容をコマンドへパイプ)
|
|
226
|
+
|
|
227
|
+
- 形式: `:w !<command>` または `:'<,'>w !<command>`
|
|
228
|
+
- バッファ(または範囲指定された行)の内容をシェルコマンドの stdin に渡します
|
|
229
|
+
- 例: `:w !wc -l`(行数カウント)、`:'<,'>w !sort`(選択範囲をソート)
|
|
230
|
+
|
|
231
|
+
### `:!`(shell 実行)
|
|
183
232
|
|
|
184
233
|
- 形式: `:!<command>`
|
|
185
|
-
- shell
|
|
186
|
-
-
|
|
187
|
-
-
|
|
234
|
+
- alternate screen を一時的に抜けて main screen 上で shell コマンドを同期実行します(Vim 互換)
|
|
235
|
+
- 実行後 "Press ENTER or type command to continue" を表示し、入力待ち後にエディタに復帰
|
|
236
|
+
- 完了後は `shell exit N` をステータス表示
|
|
188
237
|
- `-Z`(restricted mode)では無効
|
|
189
238
|
|
|
239
|
+
### `:run`(コマンド実行+結果バッファ)
|
|
240
|
+
|
|
241
|
+
- 形式: `:run [command]`
|
|
242
|
+
- シェルコマンドを実行し、出力を `[Shell Output]` バッファにストリーム表示(PTY 経由でリアルタイム)
|
|
243
|
+
- `[Shell Output]` バッファは read-only で、最大1つ(再利用される)
|
|
244
|
+
- stdout / stderr はマージされて表示
|
|
245
|
+
- コマンド中の `%` は現在のファイル名に展開される
|
|
246
|
+
- コマンド文字列はシェル引用符を保持(raw 解析)
|
|
247
|
+
- 変更のあるバッファは実行前に自動保存される
|
|
248
|
+
- 引数なしの場合:
|
|
249
|
+
- そのバッファで直前に実行したコマンドがあればそれを再実行
|
|
250
|
+
- なければ `runprg` オプションの値を使用
|
|
251
|
+
- バッファごとに実行履歴(直前のコマンド)を保持
|
|
252
|
+
- 実行中のコマンドはステータスラインに表示(`[run] command`、完了後は `[run/exit N] command`)
|
|
253
|
+
- `Ctrl-C` で実行中のプロセスを停止
|
|
254
|
+
- `runprg` オプション: ファイルタイプごとのデフォルト実行コマンド
|
|
255
|
+
- Ruby: `ruby -w %`
|
|
256
|
+
- Python: `python3 %`
|
|
257
|
+
- C: `gcc -Wall -o /tmp/a.out % && /tmp/a.out`
|
|
258
|
+
- C++: `g++ -Wall -o /tmp/a.out % && /tmp/a.out`
|
|
259
|
+
- Scheme: `gosh %`
|
|
260
|
+
- JavaScript: `node %`
|
|
261
|
+
|
|
190
262
|
### `:ls` / `:buffers`
|
|
191
263
|
|
|
192
264
|
- 形式: `:ls`
|
data/docs/done.md
CHANGED
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
- `docs/plugin.md`(拡張 / plugin 的な書き方)
|
|
48
48
|
- block-based keymap DSL(`nmap/imap/map_global ... do |ctx, ...| end`)
|
|
49
49
|
- plugin 向け `ctx.editor / ctx.buffer / ctx.window` API リファレンス(未確定 API 注記つき)
|
|
50
|
+
- `:run` コマンド(PTY ストリーミング、`runprg` オプション、バッファごと実行履歴、`%` ファイル名展開、auto-save、Ctrl-C 停止、status line にコマンド表示)
|
|
50
51
|
|
|
51
52
|
## Rich mode / 構造化データ表示
|
|
52
53
|
|
|
@@ -77,6 +78,7 @@
|
|
|
77
78
|
- PageUp / PageDown 対応
|
|
78
79
|
- `:q` の window/tab/app クローズ挙動を Vim 寄りに調整
|
|
79
80
|
- hit-enter prompt(複数行メッセージ表示: `:ls`, `:args`, `:set`, `:command`)
|
|
81
|
+
- カーソル形状制御(DECSCUSR: Normal では非表示+セル反転、Insert ではバーカーソル)
|
|
80
82
|
|
|
81
83
|
## Unicode / 日本語 / 表示幅 / 座標系
|
|
82
84
|
|
|
@@ -99,7 +101,11 @@
|
|
|
99
101
|
- CI / 開発体験(GitHub Actions, `rake docs:check`, `rake ci`)
|
|
100
102
|
- シンタックスハイライト(最小)
|
|
101
103
|
- Markdown シンタックスハイライト(`Lang::Markdown` に抽出、通常モード + Rich mode で共有)
|
|
104
|
+
- C 言語モード(`Lang::C`): シンタックスハイライト・スマートインデント・保存時 gcc チェック
|
|
105
|
+
- C++ 言語モード(`Lang::Cpp`): C の全機能 + C++ 固有キーワード・アクセス指定子インデント・保存時 g++ チェック
|
|
102
106
|
- 補完基盤(Ex 補完 / Insert mode 補完)
|
|
107
|
+
- git/gh サブコマンドの Tab 補完(`git help -a` / `gh help` から動的取得、セッション内キャッシュ)
|
|
108
|
+
- 未知の `:git` / `:gh` サブコマンドのシェルフォールバック実行
|
|
103
109
|
|
|
104
110
|
## CLI オプション(Vim 風・実装済み / placeholder 含む)
|
|
105
111
|
|
|
@@ -124,6 +130,23 @@
|
|
|
124
130
|
- `-q {errorfile}`(quickfix 読み込み起動)
|
|
125
131
|
- `-S [session]`(session 読み込み)
|
|
126
132
|
|
|
133
|
+
## セキュリティ修正
|
|
134
|
+
|
|
135
|
+
- `:grep` / `:lgrep` のシェルインジェクション対策(argv 配列実行 + `Dir.glob` 展開)
|
|
136
|
+
- Rich view レンダリング時のターミナルエスケープインジェクション対策(制御文字の無害化)
|
|
137
|
+
- Restricted mode (`-Z`) の網羅強化(`:grep`, `:lgrep`, `:git`, `:gh` を無効化)
|
|
138
|
+
- Git branch checkout の安全化(Enter で即時実行せずコマンドラインにプリセットして確認ステップを挟む)
|
|
139
|
+
- 非同期ファイルローダーの OOM 対策(`bulk_once` モード廃止、チャンク読み込み)
|
|
140
|
+
- 特殊ファイル(FIFO/デバイス/ソケット)の読み込み拒否(`Buffer.ensure_regular_file!` で統一ガード)
|
|
141
|
+
|
|
142
|
+
## Stream クラス階層化 / StreamMixer リネーム
|
|
143
|
+
|
|
144
|
+
- `@loading_state` を Stream に統合し、非同期ファイルロード状態を `Stream::FileLoad` で管理
|
|
145
|
+
- Stream を基底クラス + 5 サブクラス(`Stdin`, `Run`, `Follow`, `FileLoad`, `Git`)に分離
|
|
146
|
+
- fire-on-new パターン: `new` 時点でスレッド/ウォッチャーを即時起動
|
|
147
|
+
- `stop_handler` をコンストラクタで渡す(`attr_reader`)
|
|
148
|
+
- `StreamHandler` を `StreamMixer` にリネーム(責務を明確化: イベントキューの合流・分配)
|
|
149
|
+
|
|
127
150
|
## ドキュメント整理 / 仕様整備
|
|
128
151
|
|
|
129
152
|
- `docs/spec.md`, `docs/tutorial.md`, `docs/binding.md`, `docs/command.md`, `docs/config.md`, `docs/vim_diff.md` の継続更新
|