rufio 0.32.0 → 0.33.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,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rufio
4
+ # Magnus拡張のラッパー
5
+ # FFIを使わずに直接Rubyオブジェクトとして扱える
6
+ module NativeScannerMagnusLoader
7
+ LIB_PATH = File.expand_path('native/rufio_native.bundle', __dir__)
8
+
9
+ @loaded = false
10
+ @available = false
11
+
12
+ class << self
13
+ def load!
14
+ return @available if @loaded
15
+
16
+ @loaded = true
17
+
18
+ if File.exist?(LIB_PATH)
19
+ begin
20
+ # .bundleファイルを直接ロード
21
+ # 拡張子を外してrequireする
22
+ lib_path_without_ext = LIB_PATH.sub(/\.bundle$/, '')
23
+ require lib_path_without_ext
24
+ @available = defined?(Rufio::NativeScannerMagnus)
25
+ rescue LoadError => e
26
+ warn "Failed to load magnus extension: #{e.message}" if ENV['RUFIO_DEBUG']
27
+ @available = false
28
+ end
29
+ else
30
+ @available = false
31
+ end
32
+
33
+ @available
34
+ end
35
+
36
+ def available?
37
+ load! unless @loaded
38
+ @available
39
+ end
40
+ end
41
+ end
42
+
43
+ # Magnus拡張が利用可能な場合のみロード
44
+ if NativeScannerMagnusLoader.load!
45
+ # NativeScannerにmagnusモードを追加
46
+ class NativeScanner
47
+ class << self
48
+ # magnusモードを追加
49
+ alias_method :original_mode=, :mode=
50
+
51
+ def mode=(value)
52
+ case value
53
+ when 'magnus'
54
+ if NativeScannerMagnusLoader.available?
55
+ @mode = 'magnus'
56
+ @current_library = nil # magnus は FFI を使わない
57
+ else
58
+ @mode = 'ruby'
59
+ @current_library = nil
60
+ end
61
+ else
62
+ send(:original_mode=, value)
63
+ end
64
+ end
65
+
66
+ # magnusスキャン
67
+ def scan_directory_with_magnus(path)
68
+ raise StandardError, "Directory does not exist: #{path}" unless Dir.exist?(path)
69
+
70
+ entries = NativeScannerMagnus.scan_directory(path)
71
+
72
+ # 結果の形式を統一(すでに正しい形式だが念のため)
73
+ entries.map do |entry|
74
+ {
75
+ name: entry[:name],
76
+ type: entry[:is_dir] ? 'directory' : 'file',
77
+ size: entry[:size],
78
+ mtime: entry[:mtime],
79
+ mode: 0,
80
+ executable: entry[:executable],
81
+ hidden: entry[:hidden]
82
+ }
83
+ end
84
+ rescue StandardError => e
85
+ raise StandardError, "Magnus scan failed: #{e.message}"
86
+ end
87
+
88
+ # magnusで高速スキャン
89
+ def scan_directory_fast_with_magnus(path, max_entries)
90
+ raise StandardError, "Directory does not exist: #{path}" unless Dir.exist?(path)
91
+
92
+ entries = NativeScannerMagnus.scan_directory_fast(path, max_entries)
93
+
94
+ entries.map do |entry|
95
+ {
96
+ name: entry[:name],
97
+ type: entry[:is_dir] ? 'directory' : 'file',
98
+ size: entry[:size],
99
+ mtime: entry[:mtime],
100
+ mode: 0,
101
+ executable: entry[:executable],
102
+ hidden: entry[:hidden]
103
+ }
104
+ end
105
+ rescue StandardError => e
106
+ raise StandardError, "Magnus fast scan failed: #{e.message}"
107
+ end
108
+
109
+ # scan_directoryメソッドを拡張
110
+ alias_method :original_scan_directory, :scan_directory
111
+
112
+ def scan_directory(path)
113
+ mode if @mode.nil?
114
+
115
+ case @mode
116
+ when 'magnus'
117
+ scan_directory_with_magnus(path)
118
+ else
119
+ original_scan_directory(path)
120
+ end
121
+ end
122
+
123
+ # scan_directory_fastメソッドを拡張
124
+ alias_method :original_scan_directory_fast, :scan_directory_fast
125
+
126
+ def scan_directory_fast(path, max_entries = 1000)
127
+ mode if @mode.nil?
128
+
129
+ case @mode
130
+ when 'magnus'
131
+ scan_directory_fast_with_magnus(path, max_entries)
132
+ else
133
+ original_scan_directory_fast(path, max_entries)
134
+ end
135
+ end
136
+
137
+ # versionメソッドを拡張
138
+ alias_method :original_version, :version
139
+
140
+ def version
141
+ mode if @mode.nil?
142
+
143
+ case @mode
144
+ when 'magnus'
145
+ NativeScannerMagnus.version
146
+ else
147
+ original_version
148
+ end
149
+ end
150
+
151
+ # available_librariesを更新
152
+ alias_method :original_available_libraries, :available_libraries
153
+
154
+ def available_libraries
155
+ original = original_available_libraries
156
+ original.merge(magnus: NativeScannerMagnusLoader.available?)
157
+ end
158
+
159
+ # autoモードの優先順位を更新(magnus > rust > go > ruby)
160
+ alias_method :original_auto_mode, :mode=
161
+
162
+ def mode=(value)
163
+ case value
164
+ when 'auto'
165
+ # 優先順位: Magnus > Rust > Go > Ruby
166
+ if NativeScannerMagnusLoader.available?
167
+ @mode = 'magnus'
168
+ @current_library = nil
169
+ elsif RustLib.available?
170
+ @mode = 'rust'
171
+ @current_library = RustLib
172
+ elsif GoLib.available?
173
+ @mode = 'go'
174
+ @current_library = GoLib
175
+ else
176
+ @mode = 'ruby'
177
+ @current_library = nil
178
+ end
179
+ when 'magnus'
180
+ if NativeScannerMagnusLoader.available?
181
+ @mode = 'magnus'
182
+ @current_library = nil
183
+ else
184
+ @mode = 'ruby'
185
+ @current_library = nil
186
+ end
187
+ else
188
+ send(:original_auto_mode, value)
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rufio
4
+ # Zig拡張のラッパー
5
+ # FFIを使わずに直接Rubyオブジェクトとして扱える
6
+ module NativeScannerZigLoader
7
+ LIB_PATH = File.expand_path('native/rufio_zig.bundle', __dir__)
8
+
9
+ @loaded = false
10
+ @available = false
11
+
12
+ class << self
13
+ def load!
14
+ return @available if @loaded
15
+
16
+ @loaded = true
17
+
18
+ if File.exist?(LIB_PATH)
19
+ begin
20
+ # .bundleファイルを直接ロード
21
+ # 拡張子を外してrequireする
22
+ lib_path_without_ext = LIB_PATH.sub(/\.bundle$/, '')
23
+ require lib_path_without_ext
24
+ @available = defined?(Rufio::NativeScannerZig)
25
+ rescue LoadError => e
26
+ warn "Failed to load zig extension: #{e.message}" if ENV['RUFIO_DEBUG']
27
+ @available = false
28
+ end
29
+ else
30
+ @available = false
31
+ end
32
+
33
+ @available
34
+ end
35
+
36
+ def available?
37
+ load! unless @loaded
38
+ @available
39
+ end
40
+ end
41
+ end
42
+
43
+ # Zig拡張が利用可能な場合のみロード
44
+ if NativeScannerZigLoader.load!
45
+ # NativeScannerにzigモードを追加
46
+ class NativeScanner
47
+ class << self
48
+ # zigモードを追加
49
+ alias_method :original_mode=, :mode= unless method_defined?(:original_mode=)
50
+
51
+ def mode=(value)
52
+ case value
53
+ when 'zig'
54
+ if NativeScannerZigLoader.available?
55
+ @mode = 'zig'
56
+ @current_library = nil # zig は FFI を使わない
57
+ else
58
+ @mode = 'ruby'
59
+ @current_library = nil
60
+ end
61
+ else
62
+ original_mode=(value)
63
+ end
64
+ end
65
+
66
+ # zigスキャン
67
+ def scan_directory_with_zig(path)
68
+ raise StandardError, "Directory does not exist: #{path}" unless Dir.exist?(path)
69
+
70
+ entries = NativeScannerZig.scan_directory(path)
71
+
72
+ # 結果の形式を統一
73
+ entries.map do |entry|
74
+ {
75
+ name: entry[:name],
76
+ type: entry[:is_dir] ? 'directory' : 'file',
77
+ size: entry[:size],
78
+ mtime: entry[:mtime],
79
+ mode: 0,
80
+ executable: entry[:executable],
81
+ hidden: entry[:hidden]
82
+ }
83
+ end
84
+ rescue StandardError => e
85
+ raise StandardError, "Zig scan failed: #{e.message}"
86
+ end
87
+
88
+ # zigで高速スキャン
89
+ def scan_directory_fast_with_zig(path, max_entries)
90
+ raise StandardError, "Directory does not exist: #{path}" unless Dir.exist?(path)
91
+
92
+ entries = NativeScannerZig.scan_directory_fast(path, max_entries)
93
+
94
+ entries.map do |entry|
95
+ {
96
+ name: entry[:name],
97
+ type: entry[:is_dir] ? 'directory' : 'file',
98
+ size: entry[:size],
99
+ mtime: entry[:mtime],
100
+ mode: 0,
101
+ executable: entry[:executable],
102
+ hidden: entry[:hidden]
103
+ }
104
+ end
105
+ rescue StandardError => e
106
+ raise StandardError, "Zig fast scan failed: #{e.message}"
107
+ end
108
+
109
+ # scan_directoryメソッドを拡張
110
+ alias_method :original_scan_directory, :scan_directory unless method_defined?(:original_scan_directory)
111
+
112
+ def scan_directory(path)
113
+ mode if @mode.nil?
114
+
115
+ case @mode
116
+ when 'zig'
117
+ scan_directory_with_zig(path)
118
+ when 'magnus'
119
+ # magnusモードの場合は、native_scanner_magnus.rbで定義されたメソッドを呼ぶ
120
+ if defined?(NativeScannerMagnusLoader) && respond_to?(:scan_directory_with_magnus, true)
121
+ scan_directory_with_magnus(path)
122
+ else
123
+ original_scan_directory(path)
124
+ end
125
+ else
126
+ original_scan_directory(path)
127
+ end
128
+ end
129
+
130
+ # scan_directory_fastメソッドを拡張
131
+ alias_method :original_scan_directory_fast, :scan_directory_fast unless method_defined?(:original_scan_directory_fast)
132
+
133
+ def scan_directory_fast(path, max_entries = 1000)
134
+ mode if @mode.nil?
135
+
136
+ case @mode
137
+ when 'zig'
138
+ scan_directory_fast_with_zig(path, max_entries)
139
+ when 'magnus'
140
+ # magnusモードの場合は、native_scanner_magnus.rbで定義されたメソッドを呼ぶ
141
+ if defined?(NativeScannerMagnusLoader) && respond_to?(:scan_directory_fast_with_magnus, true)
142
+ scan_directory_fast_with_magnus(path, max_entries)
143
+ else
144
+ original_scan_directory_fast(path, max_entries)
145
+ end
146
+ else
147
+ original_scan_directory_fast(path, max_entries)
148
+ end
149
+ end
150
+
151
+ # versionメソッドを拡張
152
+ alias_method :original_version, :version unless method_defined?(:original_version)
153
+
154
+ def version
155
+ mode if @mode.nil?
156
+
157
+ case @mode
158
+ when 'zig'
159
+ NativeScannerZig.version
160
+ else
161
+ original_version
162
+ end
163
+ end
164
+
165
+ # available_librariesを更新
166
+ alias_method :original_available_libraries, :available_libraries unless method_defined?(:original_available_libraries)
167
+
168
+ def available_libraries
169
+ original = original_available_libraries
170
+ result = original.merge(zig: NativeScannerZigLoader.available?)
171
+ # magnusが既に追加されていなければ追加
172
+ if defined?(NativeScannerMagnusLoader) && !result.key?(:magnus)
173
+ result = result.merge(magnus: NativeScannerMagnusLoader.available?)
174
+ end
175
+ result
176
+ end
177
+
178
+ # autoモードの優先順位を更新(magnus > zig > rust > go > ruby)
179
+ def mode=(value)
180
+ case value
181
+ when 'auto'
182
+ # 優先順位: Magnus > Zig > Rust > Go > Ruby
183
+ if defined?(NativeScannerMagnusLoader) && NativeScannerMagnusLoader.available?
184
+ @mode = 'magnus'
185
+ @current_library = nil
186
+ elsif NativeScannerZigLoader.available?
187
+ @mode = 'zig'
188
+ @current_library = nil
189
+ elsif defined?(RustLib) && RustLib.available?
190
+ @mode = 'rust'
191
+ @current_library = RustLib
192
+ elsif defined?(GoLib) && GoLib.available?
193
+ @mode = 'go'
194
+ @current_library = GoLib
195
+ else
196
+ @mode = 'ruby'
197
+ @current_library = nil
198
+ end
199
+ when 'zig'
200
+ if NativeScannerZigLoader.available?
201
+ @mode = 'zig'
202
+ @current_library = nil
203
+ else
204
+ @mode = 'ruby'
205
+ @current_library = nil
206
+ end
207
+ when 'magnus'
208
+ if defined?(NativeScannerMagnusLoader) && NativeScannerMagnusLoader.available?
209
+ @mode = 'magnus'
210
+ @current_library = nil
211
+ else
212
+ send(:original_mode=, value)
213
+ end
214
+ else
215
+ send(:original_mode=, value)
216
+ end
217
+ end
218
+ end
219
+ end
220
+ end
221
+ 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.33.0'
5
5
  end
data/lib/rufio.rb CHANGED
@@ -30,6 +30,11 @@ 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_magnus"
37
+ require_relative "rufio/native_scanner_zig"
33
38
 
34
39
  # プロジェクトモード
35
40
  require_relative "rufio/project_mode"
@@ -0,0 +1,2 @@
1
+ [env]
2
+ RUBY = "/Users/miso/.rbenv/versions/3.4.2/bin/ruby"