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,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
|
@@ -30,6 +30,14 @@
|
|
|
30
30
|
- `/` : 前方検索入力に入る
|
|
31
31
|
- `?` : 後方検索入力に入る
|
|
32
32
|
- `x` : カーソル位置の文字削除
|
|
33
|
+
- `X` : カーソル前の文字削除
|
|
34
|
+
- `s` : カーソル位置の文字を削除して Insert mode(substitute)
|
|
35
|
+
- `~` : カーソル位置の文字の大文字小文字を切替
|
|
36
|
+
- `J` : 現在行と次の行を結合
|
|
37
|
+
- `D` : カーソルから行末まで削除
|
|
38
|
+
- `C` : カーソルから行末まで変更(削除して Insert mode)
|
|
39
|
+
- `S` : 行全体を変更(削除して Insert mode)
|
|
40
|
+
- `Y` : 行全体をヤンク
|
|
33
41
|
- `dd` : 現在行を削除
|
|
34
42
|
- `d` + motion : operator-pending delete(`dw`, `dj`, `dk`, `d$`, `dh`, `dl`)
|
|
35
43
|
- `diw` / `daw` : 単語 text object delete(簡易)
|
|
@@ -68,6 +76,9 @@
|
|
|
68
76
|
- `Ctrl-d` / `Ctrl-u` : 半ページ下/上へ移動(概ね表示高さの半分)
|
|
69
77
|
- `Ctrl-f` / `Ctrl-b` : 1ページ下/上へ移動(`PageDown` / `PageUp` 相当)
|
|
70
78
|
- `Ctrl-e` / `Ctrl-y` : カーソル位置をなるべく保ったまま画面を1行下/上へスクロール(最小実装)
|
|
79
|
+
- `zt` : カーソル行を画面上端に移動
|
|
80
|
+
- `zz` : カーソル行を画面中央に移動
|
|
81
|
+
- `zb` : カーソル行を画面下端に移動
|
|
71
82
|
- `q{reg}` : macro 記録開始/終了(再度 `q` で停止)
|
|
72
83
|
- `@{reg}` / `@@` : macro 再生 / 直前 macro 再生
|
|
73
84
|
- `n` : 直前検索を次へ
|
|
@@ -77,6 +88,12 @@
|
|
|
77
88
|
- `gf` : カーソル下のファイル名を開く(最小。`path` / `suffixesadd` を参照)
|
|
78
89
|
- `file:line` 形式なら開いた後に `line` 行目へ移動
|
|
79
90
|
- `gr` : Rich mode をトグル(TSV/CSV をテーブル整形表示、同一バッファ上で動作)
|
|
91
|
+
- `g/` : 検索パターンにマッチする行だけを集めたフィルタバッファを作成(再帰フィルタ可、Enter で元行へジャンプ、`:q` で戻る)
|
|
92
|
+
- `Ctrl-g` : Git コマンドモード(`:git ` がプリセットされたコマンドラインに入る)
|
|
93
|
+
- `blame` → Git blame を開く / `status` → Git status / `diff` → Git diff / `log` → Git log / `branch` → ブランチ一覧
|
|
94
|
+
- Blame バッファ内: `p` 親コミットの blame へ遷移 / `P` 前の blame へ戻る / `c` コミット詳細を表示
|
|
95
|
+
- Branch バッファ内: `Enter` で `:git checkout <branch>` をコマンドラインにプリセット(即時実行ではなく確認ステップあり)
|
|
96
|
+
- `Ctrl-c` : stdin stream 停止(stdin バッファ表示時)
|
|
80
97
|
- `Esc` : メッセージ/保留入力のクリア
|
|
81
98
|
- `Ctrl-z` : shell へ suspend(`fg` で復帰)
|
|
82
99
|
- `矢印キー` : 移動
|
|
@@ -132,7 +149,7 @@
|
|
|
132
149
|
- `Backspace` : 1文字削除
|
|
133
150
|
- `Up` / `Down` : 履歴移動
|
|
134
151
|
- `Left` / `Right` : カーソル移動
|
|
135
|
-
- `Tab` (`Ctrl-i`) : Ex 補完(`:` prefix
|
|
152
|
+
- `Tab` (`Ctrl-i`) : Ex 補完(`:` prefix 時、コマンド名/一部引数の文脈対応。`:git`/`:gh` サブコマンドも補完可能)
|
|
136
153
|
- `Esc` : キャンセル
|
|
137
154
|
- `Ctrl-c` : キャンセル
|
|
138
155
|
- `Ctrl-z` : shell へ suspend(`fg` で復帰)
|
data/docs/command.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
## 起動オプション(CLI, 現状)
|
|
4
4
|
|
|
5
5
|
- `--help`, `--version`
|
|
6
|
+
- `-f`(follow mode: ファイルを開いて末尾から `tail -f` 相当の追従モードで起動)
|
|
6
7
|
- `--clean`
|
|
7
8
|
- `-d`(diff mode placeholder, 未実装メッセージ表示)
|
|
8
9
|
- `-q {errorfile}`(quickfix startup placeholder。現状は未実装メッセージ表示)
|
|
@@ -70,7 +71,7 @@
|
|
|
70
71
|
- 未保存変更がある場合は `!` なしで拒否
|
|
71
72
|
- `:e!` は未保存変更を破棄して開き直す(undo/redo もクリア)
|
|
72
73
|
- 大きいファイル(閾値以上)は段階読み込みになる場合がある
|
|
73
|
-
- 読み込み中は
|
|
74
|
+
- 読み込み中は statusline に `[load/live]`
|
|
74
75
|
- デフォルトでは先頭 `8MB` を先に表示して、残りを後から追加
|
|
75
76
|
- 閾値: `RUVIM_ASYNC_FILE_THRESHOLD_BYTES`
|
|
76
77
|
- 先読みサイズ: `RUVIM_ASYNC_FILE_PREFIX_BYTES`
|
|
@@ -114,9 +115,49 @@
|
|
|
114
115
|
- current window の location list を開く / 閉じる
|
|
115
116
|
- `:lnext` / `:ln`, `:lprev` / `:lp`
|
|
116
117
|
- location list 項目を前後移動して該当位置へジャンプ
|
|
118
|
+
- `:grep pattern [path...]`
|
|
119
|
+
- 外部 grep(`grepprg`)を実行して quickfix list を作成
|
|
120
|
+
- シェル経由ではなく argv 配列で安全に実行(シェルインジェクション対策済み)
|
|
121
|
+
- ワイルドカード(`*.txt` 等)は Ruby の `Dir.glob` で展開
|
|
122
|
+
- `-Z`(restricted mode)では無効
|
|
123
|
+
- `:lgrep pattern [path...]`
|
|
124
|
+
- 外部 grep(`grepprg`)を実行して location list を作成
|
|
125
|
+
- `:grep` と同じセキュリティ仕様
|
|
126
|
+
- 一覧バッファ上で `Enter` : 選択項目へジャンプ(一覧ウィンドウから元の編集ウィンドウへ戻る)
|
|
117
127
|
- 現状の制限:
|
|
118
|
-
- `:
|
|
119
|
-
|
|
128
|
+
- `:make`, `:cfile`, `:lfile` は未実装
|
|
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` の出力からサブコマンドを補完候補として提示(セッション内キャッシュ)
|
|
120
161
|
|
|
121
162
|
### `:rich`
|
|
122
163
|
|
|
@@ -129,6 +170,36 @@
|
|
|
129
170
|
- 列幅は画面に見えている行だけから計算(大規模ファイルでも高速)
|
|
130
171
|
- `Esc` / `Ctrl-C` で Normal mode に戻る
|
|
131
172
|
|
|
173
|
+
### `:filter`
|
|
174
|
+
|
|
175
|
+
- 形式: `:filter [/pattern/]`
|
|
176
|
+
- 現在の検索パターン(引数なし)または指定パターンにマッチする行だけを集めたフィルタバッファを作成
|
|
177
|
+
- フィルタバッファ上で再度 `:filter` や `g/` を使うと再帰的に絞り込める
|
|
178
|
+
- Enter で元バッファの該当行へジャンプ(フィルタバッファは閉じる)
|
|
179
|
+
- `:q` でフィルタバッファを閉じて前のバッファに戻る
|
|
180
|
+
- 元バッファの filetype を引き継ぐ(TSV なら `gr` でテーブル表示可能)
|
|
181
|
+
- Normal mode では `g/` キーバインドで実行可能
|
|
182
|
+
|
|
183
|
+
### `:follow`
|
|
184
|
+
|
|
185
|
+
- 形式: `:follow`
|
|
186
|
+
- ファイルの follow mode をトグルする(`tail -f` 相当)
|
|
187
|
+
- follow 中はファイルへの追記がリアルタイムにバッファへ反映される
|
|
188
|
+
- カーソルが最終行にいる場合は末尾を自動追従(auto-scroll)
|
|
189
|
+
- カーソルが途中にある場合はスクロール位置を維持
|
|
190
|
+
- `G` で末尾に移動すれば追従が再開される
|
|
191
|
+
- `:follow` を再度実行すると停止
|
|
192
|
+
- `Ctrl-C`(ノーマルモード)で follow を停止
|
|
193
|
+
- statusline に `[follow]` と表示される(inotify 使用時は `[follow/i]`)
|
|
194
|
+
- ファイルが紐づいていないバッファではエラー
|
|
195
|
+
- 未保存の変更があるバッファでは follow 開始不可
|
|
196
|
+
- follow 中のバッファは変更不可(modifiable が false)
|
|
197
|
+
- ファイルが truncate された場合はメッセージを表示し監視を継続
|
|
198
|
+
- ファイルが削除された場合はメッセージを表示し、再作成を待って監視を再開
|
|
199
|
+
- follow 停止時、バッファ内容はそのまま保持(`:e!` で再読み込み可能)
|
|
200
|
+
- 監視方式: Linux では inotify(fiddle 経由)を優先し、使えない場合は polling にフォールバック
|
|
201
|
+
- polling は exponential backoff(0.1s → 3s)で CPU 負荷を抑える
|
|
202
|
+
|
|
132
203
|
### `:command`
|
|
133
204
|
|
|
134
205
|
- 形式: `:command Name ex_body`
|
|
@@ -144,14 +215,50 @@
|
|
|
144
215
|
- `stdout` / `stderr` に出力があった場合は `[Ruby Output]` 仮想バッファに表示(返り値も末尾に表示)
|
|
145
216
|
- 利用可能: `ctx`, `editor`, `buffer`, `window`
|
|
146
217
|
|
|
147
|
-
###
|
|
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 実行)
|
|
148
232
|
|
|
149
233
|
- 形式: `:!<command>`
|
|
150
|
-
- shell
|
|
151
|
-
-
|
|
152
|
-
-
|
|
234
|
+
- alternate screen を一時的に抜けて main screen 上で shell コマンドを同期実行します(Vim 互換)
|
|
235
|
+
- 実行後 "Press ENTER or type command to continue" を表示し、入力待ち後にエディタに復帰
|
|
236
|
+
- 完了後は `shell exit N` をステータス表示
|
|
153
237
|
- `-Z`(restricted mode)では無効
|
|
154
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
|
+
|
|
155
262
|
### `:ls` / `:buffers`
|
|
156
263
|
|
|
157
264
|
- 形式: `:ls`
|
|
@@ -247,6 +354,16 @@
|
|
|
247
354
|
- 各タブに含まれるウィンドウとバッファ名を表示
|
|
248
355
|
- 現在のタブ / ウィンドウは `>` マーカーで示す
|
|
249
356
|
|
|
357
|
+
### `:d` / `:delete`
|
|
358
|
+
|
|
359
|
+
- 形式: `:d [count]`, `:{range}d`
|
|
360
|
+
- 行を削除
|
|
361
|
+
|
|
362
|
+
### `:y` / `:yank`
|
|
363
|
+
|
|
364
|
+
- 形式: `:y [count]`, `:{range}y`
|
|
365
|
+
- 行をヤンク
|
|
366
|
+
|
|
250
367
|
## 内部コマンド(主なもの)
|
|
251
368
|
|
|
252
369
|
内部コマンドは主に key binding から使われ、`RuVim::CommandRegistry` に登録されます。
|
|
@@ -302,6 +419,33 @@
|
|
|
302
419
|
- `editor.buffer_next`
|
|
303
420
|
- `editor.buffer_prev`
|
|
304
421
|
- `buffer.replace_char`
|
|
422
|
+
- `buffer.substitute_char`
|
|
423
|
+
- `buffer.swapcase_char`
|
|
424
|
+
- `buffer.join_lines`
|
|
425
|
+
- `buffer.indent_lines`
|
|
426
|
+
- `buffer.indent_motion`
|
|
427
|
+
- `buffer.visual_indent`
|
|
428
|
+
- `buffer.visual_select_text_object`
|
|
429
|
+
- `mode.visual_block`
|
|
430
|
+
- `cursor.page_up` / `cursor.page_down`
|
|
431
|
+
- `cursor.page_up.default` / `cursor.page_down.default`
|
|
432
|
+
- `cursor.page_up.half` / `cursor.page_down.half`
|
|
433
|
+
- `cursor.match_bracket`
|
|
434
|
+
- `window.scroll_up` / `window.scroll_down`
|
|
435
|
+
- `window.scroll_up.line` / `window.scroll_down.line`
|
|
436
|
+
- `window.cursor_line_top` / `window.cursor_line_center` / `window.cursor_line_bottom`
|
|
437
|
+
- `window.focus_or_split_left` / `window.focus_or_split_right` / `window.focus_or_split_up` / `window.focus_or_split_down`
|
|
438
|
+
- `normal.register_pending_start`
|
|
439
|
+
- `normal.operator_delete_start` / `normal.operator_yank_start` / `normal.operator_change_start` / `normal.operator_indent_start`
|
|
440
|
+
- `normal.replace_pending_start`
|
|
441
|
+
- `normal.find_char_forward_start` / `normal.find_char_backward_start` / `normal.find_till_forward_start` / `normal.find_till_backward_start`
|
|
442
|
+
- `normal.find_repeat` / `normal.find_repeat_reverse`
|
|
443
|
+
- `normal.change_repeat`
|
|
444
|
+
- `normal.macro_record_toggle` / `normal.macro_play_pending_start`
|
|
445
|
+
- `normal.mark_pending_start`
|
|
446
|
+
- `normal.jump_mark_linewise_pending_start` / `normal.jump_mark_exact_pending_start`
|
|
447
|
+
- `quickfix.next` / `quickfix.prev` / `quickfix.open`
|
|
448
|
+
- `stdin.stream_stop`
|
|
305
449
|
- `ui.clear_message`
|
|
306
450
|
- `rich.toggle`
|
|
307
451
|
|
|
@@ -316,12 +460,14 @@
|
|
|
316
460
|
- `*` / `#` : カーソル下の単語検索(単語境界)
|
|
317
461
|
- `g*` / `g#` : カーソル下の単語検索(部分一致)
|
|
318
462
|
- `:vimgrep` / `:lvimgrep` : 検索結果を quickfix/location list に積む
|
|
463
|
+
- `:nohlsearch` (`:noh`) : 検索ハイライトを一時的にクリア(次の検索で自動復帰)
|
|
319
464
|
|
|
320
|
-
### `:%s/.../.../
|
|
465
|
+
### `:%s/.../.../[flags]`
|
|
321
466
|
|
|
322
|
-
-
|
|
467
|
+
- バッファ全体置換(レンジ指定対応: `:{range}s/.../.../`)
|
|
323
468
|
- Ruby 正規表現 + Ruby 置換文字列を利用
|
|
324
|
-
- `g`
|
|
469
|
+
- 対応フラグ: `g`(全置換), `i`(大文字小文字無視), `I`(大文字小文字区別), `n`(置換せずマッチ数表示), `e`(エラー抑制)
|
|
470
|
+
- `c`(確認)フラグは未実装
|
|
325
471
|
|
|
326
472
|
## Operator-pending(Normal mode)
|
|
327
473
|
|
data/docs/config.md
CHANGED
|
@@ -244,11 +244,11 @@ setglobal "tabstop=8"
|
|
|
244
244
|
- Tab 入力/削除時の編集幅を指定します。
|
|
245
245
|
- 現状は Insert mode の Tab 入力と、`expandtab` 時の空白 Backspace(最小)で使用します。
|
|
246
246
|
|
|
247
|
-
- `autoindent` (`bool`, default `
|
|
247
|
+
- `autoindent` (`bool`, default `true`) [`DONE`]
|
|
248
248
|
- 改行時に前行の先頭インデントを引き継ぎます。
|
|
249
249
|
- Insert mode `Enter`、`o`/`O` に反映されます。
|
|
250
250
|
|
|
251
|
-
- `smartindent` (`bool`, default `
|
|
251
|
+
- `smartindent` (`bool`, default `true`) [`PARTIAL`]
|
|
252
252
|
- 簡易な自動インデントを行います。
|
|
253
253
|
- 現状は前行が `{` `[` `(` で終わる場合に `shiftwidth` 分の空白を追加します。
|
|
254
254
|
|
|
@@ -265,6 +265,14 @@ setglobal "tabstop=8"
|
|
|
265
265
|
- ftplugin 読み込み対象、filetype-local keymap、簡易 syntax highlight の判定に使います。
|
|
266
266
|
- 手動で変更した場合の ftplugin 再適用は現状未対応です。
|
|
267
267
|
|
|
268
|
+
### Global options(検索・grep)
|
|
269
|
+
|
|
270
|
+
- `grepprg` (`string`, default `"grep -nH"`) [`DONE`]
|
|
271
|
+
- `:grep` / `:lgrep` が使用する外部コマンドを指定します。
|
|
272
|
+
|
|
273
|
+
- `grepformat` (`string`, default `"%f:%l:%m"`) [`DONE`]
|
|
274
|
+
- `:grep` / `:lgrep` の出力パース書式を指定します。
|
|
275
|
+
|
|
268
276
|
## 制限(現状)
|
|
269
277
|
|
|
270
278
|
- Vim の option 全部は未実装(ごく一部のみ)
|
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` の継続更新
|