rufio 0.32.0 → 0.34.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/CHANGELOG.md +65 -0
- data/README.md +63 -7
- data/README_EN.md +25 -0
- data/docs/CHANGELOG_v0.34.0.md +444 -0
- data/docs/file-preview-optimization-analysis.md +759 -0
- data/docs/file-preview-performance-issue-FIXED.md +547 -0
- data/lib/rufio/application.rb +9 -1
- data/lib/rufio/async_scanner_fiber.rb +154 -0
- data/lib/rufio/async_scanner_promise.rb +66 -0
- data/lib/rufio/background_command_executor.rb +98 -0
- data/lib/rufio/command_logger.rb +121 -0
- data/lib/rufio/command_mode.rb +17 -2
- data/lib/rufio/directory_listing.rb +60 -12
- data/lib/rufio/keybind_handler.rb +73 -2
- data/lib/rufio/native/rufio_zig.bundle +0 -0
- data/lib/rufio/native_scanner.rb +325 -0
- data/lib/rufio/native_scanner_zig.rb +354 -0
- data/lib/rufio/parallel_scanner.rb +173 -0
- data/lib/rufio/terminal_ui.rb +66 -16
- data/lib/rufio/version.rb +1 -1
- data/lib/rufio.rb +7 -0
- data/lib_zig/rufio_native/Makefile +34 -0
- data/lib_zig/rufio_native/build.zig +45 -0
- data/lib_zig/rufio_native/src/main.zig +378 -0
- data/lib_zig/rufio_native/src/main.zig.sync +205 -0
- metadata +17 -2
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thread'
|
|
4
|
+
|
|
5
|
+
module Rufio
|
|
6
|
+
# 並列スキャン最適化クラス
|
|
7
|
+
#
|
|
8
|
+
# 複数のディレクトリを並列にスキャンし、結果をマージします。
|
|
9
|
+
# スレッドプールを使用して効率的に並列処理を行います。
|
|
10
|
+
#
|
|
11
|
+
# 使用例:
|
|
12
|
+
# parallel_scanner = ParallelScanner.new(max_workers: 4)
|
|
13
|
+
# results = parallel_scanner.scan_all(['/path1', '/path2', '/path3'])
|
|
14
|
+
# all_entries = parallel_scanner.scan_all_merged(['/path1', '/path2'])
|
|
15
|
+
#
|
|
16
|
+
class ParallelScanner
|
|
17
|
+
DEFAULT_MAX_WORKERS = 4
|
|
18
|
+
|
|
19
|
+
# @param max_workers [Integer] 最大ワーカー数
|
|
20
|
+
# @param backend [Symbol] バックエンド (:ruby or :zig)
|
|
21
|
+
def initialize(max_workers: DEFAULT_MAX_WORKERS, backend: :ruby)
|
|
22
|
+
@max_workers = max_workers
|
|
23
|
+
@backend = backend
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# 複数のディレクトリを並列スキャン
|
|
27
|
+
#
|
|
28
|
+
# @param paths [Array<String>] スキャンするディレクトリパスのリスト
|
|
29
|
+
# @return [Array<Hash>] 各ディレクトリのスキャン結果
|
|
30
|
+
# [{path:, entries:, success:, error:}, ...]
|
|
31
|
+
def scan_all(paths)
|
|
32
|
+
return [] if paths.empty?
|
|
33
|
+
|
|
34
|
+
results = []
|
|
35
|
+
mutex = Mutex.new
|
|
36
|
+
queue = Queue.new
|
|
37
|
+
|
|
38
|
+
# キューにパスを追加
|
|
39
|
+
paths.each { |path| queue << path }
|
|
40
|
+
|
|
41
|
+
# ワーカースレッドを作成
|
|
42
|
+
workers = []
|
|
43
|
+
worker_count = [@max_workers, paths.length].min
|
|
44
|
+
|
|
45
|
+
worker_count.times do
|
|
46
|
+
workers << Thread.new do
|
|
47
|
+
loop do
|
|
48
|
+
path = queue.pop(true) rescue nil
|
|
49
|
+
break if path.nil?
|
|
50
|
+
|
|
51
|
+
result = scan_single_directory(path)
|
|
52
|
+
mutex.synchronize { results << result }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# 全ワーカーの完了を待つ
|
|
58
|
+
workers.each(&:join)
|
|
59
|
+
|
|
60
|
+
results
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# 複数のディレクトリを並列スキャンし、結果をマージ
|
|
64
|
+
#
|
|
65
|
+
# @param paths [Array<String>] スキャンするディレクトリパスのリスト
|
|
66
|
+
# @yield [entry] 各エントリをフィルタリングするブロック(オプション)
|
|
67
|
+
# @return [Array<Hash>] 全エントリのマージされた配列
|
|
68
|
+
def scan_all_merged(paths, &filter)
|
|
69
|
+
results = scan_all(paths)
|
|
70
|
+
|
|
71
|
+
# 成功した結果のみを取得
|
|
72
|
+
all_entries = results
|
|
73
|
+
.select { |r| r[:success] }
|
|
74
|
+
.flat_map { |r| r[:entries] }
|
|
75
|
+
|
|
76
|
+
# フィルタが指定されている場合は適用
|
|
77
|
+
all_entries = all_entries.select(&filter) if block_given?
|
|
78
|
+
|
|
79
|
+
all_entries
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# 進捗報告付き並列スキャン
|
|
83
|
+
#
|
|
84
|
+
# @param paths [Array<String>] スキャンするディレクトリパスのリスト
|
|
85
|
+
# @yield [completed, total] 進捗情報を受け取るブロック
|
|
86
|
+
# @return [Array<Hash>] 各ディレクトリのスキャン結果
|
|
87
|
+
def scan_all_with_progress(paths, &block)
|
|
88
|
+
return [] if paths.empty?
|
|
89
|
+
|
|
90
|
+
results = []
|
|
91
|
+
mutex = Mutex.new
|
|
92
|
+
queue = Queue.new
|
|
93
|
+
completed = 0
|
|
94
|
+
total = paths.length
|
|
95
|
+
|
|
96
|
+
# キューにパスを追加
|
|
97
|
+
paths.each { |path| queue << path }
|
|
98
|
+
|
|
99
|
+
# ワーカースレッドを作成
|
|
100
|
+
workers = []
|
|
101
|
+
worker_count = [@max_workers, paths.length].min
|
|
102
|
+
|
|
103
|
+
worker_count.times do
|
|
104
|
+
workers << Thread.new do
|
|
105
|
+
loop do
|
|
106
|
+
path = queue.pop(true) rescue nil
|
|
107
|
+
break if path.nil?
|
|
108
|
+
|
|
109
|
+
result = scan_single_directory(path)
|
|
110
|
+
|
|
111
|
+
mutex.synchronize do
|
|
112
|
+
results << result
|
|
113
|
+
completed += 1
|
|
114
|
+
yield(completed, total) if block_given?
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# 全ワーカーの完了を待つ
|
|
121
|
+
workers.each(&:join)
|
|
122
|
+
|
|
123
|
+
results
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
# 単一のディレクトリをスキャン
|
|
129
|
+
#
|
|
130
|
+
# @param path [String] スキャンするディレクトリパス
|
|
131
|
+
# @return [Hash] スキャン結果 {path:, entries:, success:, error:}
|
|
132
|
+
def scan_single_directory(path)
|
|
133
|
+
scanner = create_scanner
|
|
134
|
+
|
|
135
|
+
begin
|
|
136
|
+
scanner.scan_async(path)
|
|
137
|
+
entries = scanner.wait(timeout: 60)
|
|
138
|
+
|
|
139
|
+
{
|
|
140
|
+
path: path,
|
|
141
|
+
entries: entries,
|
|
142
|
+
success: true
|
|
143
|
+
}
|
|
144
|
+
rescue StandardError => e
|
|
145
|
+
{
|
|
146
|
+
path: path,
|
|
147
|
+
entries: [],
|
|
148
|
+
success: false,
|
|
149
|
+
error: e.message
|
|
150
|
+
}
|
|
151
|
+
ensure
|
|
152
|
+
scanner.close
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# バックエンドに応じたスキャナーを作成
|
|
157
|
+
#
|
|
158
|
+
# @return [NativeScannerRubyCore, NativeScannerZigCore] スキャナーインスタンス
|
|
159
|
+
def create_scanner
|
|
160
|
+
case @backend
|
|
161
|
+
when :zig
|
|
162
|
+
if defined?(NativeScannerZigFFI) && NativeScannerZigFFI.available?
|
|
163
|
+
NativeScannerZigCore.new
|
|
164
|
+
else
|
|
165
|
+
# Zigが利用できない場合はRubyにフォールバック
|
|
166
|
+
NativeScannerRubyCore.new
|
|
167
|
+
end
|
|
168
|
+
else
|
|
169
|
+
NativeScannerRubyCore.new
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
data/lib/rufio/terminal_ui.rb
CHANGED
|
@@ -62,13 +62,17 @@ module Rufio
|
|
|
62
62
|
@in_log_mode = false
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
def start(directory_listing, keybind_handler, file_preview)
|
|
65
|
+
def start(directory_listing, keybind_handler, file_preview, background_executor = nil)
|
|
66
66
|
@directory_listing = directory_listing
|
|
67
67
|
@keybind_handler = keybind_handler
|
|
68
68
|
@file_preview = file_preview
|
|
69
|
+
@background_executor = background_executor
|
|
69
70
|
@keybind_handler.set_directory_listing(@directory_listing)
|
|
70
71
|
@keybind_handler.set_terminal_ui(self)
|
|
71
72
|
|
|
73
|
+
# コマンドモードにバックグラウンドエグゼキュータを設定
|
|
74
|
+
@command_mode.background_executor = @background_executor if @background_executor
|
|
75
|
+
|
|
72
76
|
@running = true
|
|
73
77
|
setup_terminal
|
|
74
78
|
|
|
@@ -114,8 +118,29 @@ module Rufio
|
|
|
114
118
|
end
|
|
115
119
|
|
|
116
120
|
def main_loop
|
|
121
|
+
last_notification_check = Time.now
|
|
122
|
+
notification_message = nil
|
|
123
|
+
notification_time = nil
|
|
124
|
+
|
|
117
125
|
while @running
|
|
118
|
-
|
|
126
|
+
# バックグラウンドコマンドの完了チェック(0.5秒ごと)
|
|
127
|
+
if @background_executor && (Time.now - last_notification_check) > 0.5
|
|
128
|
+
if !@background_executor.running? && @background_executor.get_completion_message
|
|
129
|
+
notification_message = @background_executor.get_completion_message
|
|
130
|
+
notification_time = Time.now
|
|
131
|
+
@background_executor.instance_variable_set(:@completion_message, nil) # メッセージをクリア
|
|
132
|
+
end
|
|
133
|
+
last_notification_check = Time.now
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# 通知メッセージを表示(3秒間)
|
|
137
|
+
if notification_message && (Time.now - notification_time) < 3.0
|
|
138
|
+
draw_screen_with_notification(notification_message)
|
|
139
|
+
else
|
|
140
|
+
notification_message = nil if notification_message
|
|
141
|
+
draw_screen
|
|
142
|
+
end
|
|
143
|
+
|
|
119
144
|
handle_input
|
|
120
145
|
end
|
|
121
146
|
end
|
|
@@ -165,6 +190,23 @@ module Rufio
|
|
|
165
190
|
end
|
|
166
191
|
end
|
|
167
192
|
|
|
193
|
+
def draw_screen_with_notification(notification_message)
|
|
194
|
+
# 通常の画面を描画
|
|
195
|
+
draw_screen
|
|
196
|
+
|
|
197
|
+
# 通知メッセージを画面下部に表示
|
|
198
|
+
notification_line = @screen_height - 1
|
|
199
|
+
print "\e[#{notification_line};1H" # カーソルを画面下部に移動
|
|
200
|
+
|
|
201
|
+
# 通知メッセージを反転表示で目立たせる
|
|
202
|
+
message_display = " #{notification_message} "
|
|
203
|
+
if message_display.length > @screen_width
|
|
204
|
+
message_display = message_display[0...(@screen_width - 3)] + "..."
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
print "\e[7m#{message_display.ljust(@screen_width)}\e[0m"
|
|
208
|
+
end
|
|
209
|
+
|
|
168
210
|
def draw_header
|
|
169
211
|
current_path = @directory_listing.current_path
|
|
170
212
|
header = "📁 rufio - #{current_path}"
|
|
@@ -310,15 +352,22 @@ module Rufio
|
|
|
310
352
|
end
|
|
311
353
|
|
|
312
354
|
def draw_file_preview(selected_entry, width, height, left_offset)
|
|
355
|
+
# 事前計算(ループの外で一度だけ)
|
|
356
|
+
cursor_position = left_offset + CURSOR_OFFSET
|
|
357
|
+
max_chars_from_cursor = @screen_width - cursor_position
|
|
358
|
+
safe_width = [max_chars_from_cursor - 2, width - 2, 0].max
|
|
359
|
+
|
|
360
|
+
# プレビューコンテンツとWrapped linesを一度だけ計算
|
|
361
|
+
preview_content = nil
|
|
362
|
+
wrapped_lines = nil
|
|
363
|
+
|
|
364
|
+
if selected_entry && selected_entry[:type] == 'file'
|
|
365
|
+
preview_content = get_preview_content(selected_entry)
|
|
366
|
+
wrapped_lines = TextUtils.wrap_preview_lines(preview_content, safe_width - 1) if safe_width > 0
|
|
367
|
+
end
|
|
368
|
+
|
|
313
369
|
(0...height).each do |i|
|
|
314
370
|
line_num = i + CONTENT_START_LINE
|
|
315
|
-
# カーソル位置を左パネルの右端に設定
|
|
316
|
-
cursor_position = left_offset + CURSOR_OFFSET
|
|
317
|
-
|
|
318
|
-
# 画面の境界を厳密に計算
|
|
319
|
-
max_chars_from_cursor = @screen_width - cursor_position
|
|
320
|
-
# 区切り線(│)分を除いて、さらに安全マージンを取る
|
|
321
|
-
safe_width = [max_chars_from_cursor - 2, width - 2, 0].max
|
|
322
371
|
|
|
323
372
|
print "\e[#{line_num};#{cursor_position}H" # カーソル位置設定
|
|
324
373
|
print '│' # 区切り線
|
|
@@ -333,11 +382,8 @@ module Rufio
|
|
|
333
382
|
header += "[PREVIEW MODE]"
|
|
334
383
|
end
|
|
335
384
|
content_to_print = header
|
|
336
|
-
elsif
|
|
385
|
+
elsif wrapped_lines && i >= 2
|
|
337
386
|
# ファイルプレビュー(折り返し対応)
|
|
338
|
-
preview_content = get_preview_content(selected_entry)
|
|
339
|
-
wrapped_lines = TextUtils.wrap_preview_lines(preview_content, safe_width - 1) # スペース分を除く
|
|
340
|
-
|
|
341
387
|
# スクロールオフセットを適用
|
|
342
388
|
scroll_offset = @keybind_handler&.preview_scroll_offset || 0
|
|
343
389
|
display_line_index = i - 2 + scroll_offset
|
|
@@ -569,8 +615,12 @@ module Rufio
|
|
|
569
615
|
|
|
570
616
|
result = @command_mode.execute(command_string)
|
|
571
617
|
|
|
572
|
-
#
|
|
573
|
-
|
|
618
|
+
# バックグラウンドコマンドの場合は結果表示をスキップ
|
|
619
|
+
# (完了通知は別途メインループで表示される)
|
|
620
|
+
if result && !result.to_s.include?("🔄 バックグラウンドで実行中")
|
|
621
|
+
# コマンド実行結果をフローティングウィンドウで表示
|
|
622
|
+
@command_mode_ui.show_result(result)
|
|
623
|
+
end
|
|
574
624
|
|
|
575
625
|
# 画面を再描画
|
|
576
626
|
draw_screen
|
|
@@ -1028,7 +1078,7 @@ module Rufio
|
|
|
1028
1078
|
'z - Zoxide navigation',
|
|
1029
1079
|
'0 - Go to start directory',
|
|
1030
1080
|
'1-9 - Go to bookmark',
|
|
1031
|
-
'
|
|
1081
|
+
'P - Project mode',
|
|
1032
1082
|
': - Command mode',
|
|
1033
1083
|
'q - Quit',
|
|
1034
1084
|
''
|
data/lib/rufio/version.rb
CHANGED
data/lib/rufio.rb
CHANGED
|
@@ -30,6 +30,13 @@ require_relative "rufio/command_mode_ui"
|
|
|
30
30
|
require_relative "rufio/command_history"
|
|
31
31
|
require_relative "rufio/command_completion"
|
|
32
32
|
require_relative "rufio/shell_command_completion"
|
|
33
|
+
require_relative "rufio/command_logger"
|
|
34
|
+
require_relative "rufio/background_command_executor"
|
|
35
|
+
require_relative "rufio/native_scanner"
|
|
36
|
+
require_relative "rufio/native_scanner_zig"
|
|
37
|
+
require_relative "rufio/async_scanner_promise"
|
|
38
|
+
require_relative "rufio/async_scanner_fiber"
|
|
39
|
+
require_relative "rufio/parallel_scanner"
|
|
33
40
|
|
|
34
41
|
# プロジェクトモード
|
|
35
42
|
require_relative "rufio/project_mode"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Makefile for Zig Ruby extension
|
|
2
|
+
|
|
3
|
+
RUBY := ruby
|
|
4
|
+
ZIG := zig
|
|
5
|
+
|
|
6
|
+
# Rubyの設定を取得
|
|
7
|
+
RUBY_INCLUDE := $(shell $(RUBY) -e "puts RbConfig::CONFIG['rubyhdrdir']")
|
|
8
|
+
RUBY_ARCH_INCLUDE := $(shell $(RUBY) -e "puts RbConfig::CONFIG['rubyarchhdrdir']")
|
|
9
|
+
RUBY_LIB_DIR := $(shell $(RUBY) -e "puts RbConfig::CONFIG['libdir']")
|
|
10
|
+
RUBY_VERSION := $(shell $(RUBY) -e "puts RbConfig::CONFIG['MAJOR'] + '.' + RbConfig::CONFIG['MINOR']")
|
|
11
|
+
|
|
12
|
+
# コンパイルフラグ
|
|
13
|
+
ZIG_FLAGS := -O ReleaseFast -dynamic -lc
|
|
14
|
+
ZIG_FLAGS += -I$(RUBY_INCLUDE)
|
|
15
|
+
ZIG_FLAGS += -I$(RUBY_ARCH_INCLUDE)
|
|
16
|
+
ZIG_FLAGS += -L$(RUBY_LIB_DIR)
|
|
17
|
+
ZIG_FLAGS += -lruby.$(RUBY_VERSION)
|
|
18
|
+
|
|
19
|
+
# ターゲット
|
|
20
|
+
TARGET := librufio_zig.dylib
|
|
21
|
+
SRC := src/main.zig
|
|
22
|
+
|
|
23
|
+
all: $(TARGET)
|
|
24
|
+
|
|
25
|
+
$(TARGET): $(SRC)
|
|
26
|
+
$(ZIG) build-lib $(SRC) $(ZIG_FLAGS) -femit-bin=$@
|
|
27
|
+
|
|
28
|
+
clean:
|
|
29
|
+
rm -f $(TARGET) *.o
|
|
30
|
+
|
|
31
|
+
install: $(TARGET)
|
|
32
|
+
cp $(TARGET) ../../lib/rufio/native/rufio_zig.bundle
|
|
33
|
+
|
|
34
|
+
.PHONY: all clean install
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
|
|
3
|
+
pub fn build(b: *std.Build) void {
|
|
4
|
+
const target = b.standardTargetOptions(.{});
|
|
5
|
+
const optimize = b.standardOptimizeOption(.{});
|
|
6
|
+
|
|
7
|
+
// 動的ライブラリを作成
|
|
8
|
+
const lib = b.addExecutable(.{
|
|
9
|
+
.name = "rufio_zig",
|
|
10
|
+
.root_source_file = b.path("src/main.zig"),
|
|
11
|
+
.target = target,
|
|
12
|
+
.optimize = optimize,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// 共有ライブラリとしてリンク
|
|
16
|
+
lib.linkage = .dynamic;
|
|
17
|
+
lib.linkLibC();
|
|
18
|
+
|
|
19
|
+
// RubyのヘッダーとライブラリへのパスをRubyから取得
|
|
20
|
+
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
21
|
+
defer arena.deinit();
|
|
22
|
+
const allocator = arena.allocator();
|
|
23
|
+
|
|
24
|
+
// Rubyのインクルードパスを取得
|
|
25
|
+
const ruby_include_result = b.run(&.{ "ruby", "-e", "puts RbConfig::CONFIG['rubyhdrdir']" });
|
|
26
|
+
const ruby_include = std.mem.trim(u8, ruby_include_result, " \n\r\t");
|
|
27
|
+
|
|
28
|
+
const ruby_arch_include_result = b.run(&.{ "ruby", "-e", "puts RbConfig::CONFIG['rubyarchhdrdir']" });
|
|
29
|
+
const ruby_arch_include = std.mem.trim(u8, ruby_arch_include_result, " \n\r\t");
|
|
30
|
+
|
|
31
|
+
const ruby_lib_dir_result = b.run(&.{ "ruby", "-e", "puts RbConfig::CONFIG['libdir']" });
|
|
32
|
+
const ruby_lib_dir = std.mem.trim(u8, ruby_lib_dir_result, " \n\r\t");
|
|
33
|
+
|
|
34
|
+
// インクルードパスを追加
|
|
35
|
+
lib.addIncludePath(.{ .cwd_relative = ruby_include });
|
|
36
|
+
lib.addIncludePath(.{ .cwd_relative = ruby_arch_include });
|
|
37
|
+
|
|
38
|
+
// ライブラリパスを追加
|
|
39
|
+
lib.addLibraryPath(.{ .cwd_relative = ruby_lib_dir });
|
|
40
|
+
|
|
41
|
+
// Rubyライブラリとリンク
|
|
42
|
+
lib.linkSystemLibrary("ruby.3.4");
|
|
43
|
+
|
|
44
|
+
b.installArtifact(lib);
|
|
45
|
+
}
|