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.
@@ -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
@@ -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
- draw_screen
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 selected_entry && selected_entry[:type] == 'file' && i >= 2
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
- @command_mode_ui.show_result(result) if result
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
- 'p - Project mode',
1081
+ 'P - Project mode',
1032
1082
  ': - Command mode',
1033
1083
  'q - Quit',
1034
1084
  ''
data/lib/rufio/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rufio
4
- VERSION = '0.32.0'
4
+ VERSION = '0.34.0'
5
5
  end
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
+ }