rufio 1.1.0 → 1.4.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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +26 -297
  4. data/README_ja.md +24 -261
  5. data/Rakefile +14 -1
  6. data/bin/rufio +31 -2
  7. data/docs/fast_zip_design_ja.md +167 -0
  8. data/docs/guide.md +410 -0
  9. data/docs/guide_ja.md +410 -0
  10. data/info/help.md +54 -17
  11. data/info/keybindings.md +50 -22
  12. data/info/welcome.md +14 -5
  13. data/lib/rufio/application.rb +12 -1
  14. data/lib/rufio/archive_controller.rb +211 -0
  15. data/lib/rufio/background_command_executor.rb +9 -0
  16. data/lib/rufio/background_status.rb +49 -0
  17. data/lib/rufio/bookmark_controller.rb +38 -121
  18. data/lib/rufio/bookmark_manager.rb +77 -331
  19. data/lib/rufio/command_completion.rb +28 -2
  20. data/lib/rufio/command_history.rb +5 -2
  21. data/lib/rufio/command_mode.rb +60 -134
  22. data/lib/rufio/command_mode_ui.rb +4 -42
  23. data/lib/rufio/config.rb +88 -136
  24. data/lib/rufio/config_loader.rb +4 -11
  25. data/lib/rufio/dialog_renderer.rb +90 -152
  26. data/lib/rufio/dialog_support.rb +69 -0
  27. data/lib/rufio/document_text_extractor.rb +167 -0
  28. data/lib/rufio/file_operation_controller.rb +49 -222
  29. data/lib/rufio/file_operations.rb +28 -6
  30. data/lib/rufio/file_preview.rb +46 -2
  31. data/lib/rufio/global_search.rb +125 -13
  32. data/lib/rufio/help_menu.rb +19 -7
  33. data/lib/rufio/interpreter_resolver.rb +6 -11
  34. data/lib/rufio/job_manager.rb +2 -5
  35. data/lib/rufio/key_input.rb +77 -0
  36. data/lib/rufio/keybind_handler.rb +83 -116
  37. data/lib/rufio/local_script_scanner.rb +6 -18
  38. data/lib/rufio/native/aarch64-linux-gnu/librufio_zig.so +0 -0
  39. data/lib/rufio/native/aarch64-linux-musl/librufio_zig.so +0 -0
  40. data/lib/rufio/native/aarch64-macos/librufio_zig.dylib +0 -0
  41. data/lib/rufio/native/aarch64-windows/rufio_zig.dll +0 -0
  42. data/lib/rufio/native/x86_64-linux-gnu/librufio_zig.so +0 -0
  43. data/lib/rufio/native/x86_64-linux-musl/librufio_zig.so +0 -0
  44. data/lib/rufio/native/x86_64-macos/librufio_zig.dylib +0 -0
  45. data/lib/rufio/native/x86_64-windows/rufio_zig.dll +0 -0
  46. data/lib/rufio/native_content_search.rb +153 -0
  47. data/lib/rufio/native_scanner.rb +13 -68
  48. data/lib/rufio/native_scanner_zig.rb +106 -56
  49. data/lib/rufio/native_zip.rb +121 -0
  50. data/lib/rufio/navigation_controller.rb +20 -26
  51. data/lib/rufio/path_jump.rb +55 -0
  52. data/lib/rufio/screen.rb +2 -12
  53. data/lib/rufio/script_executor.rb +6 -73
  54. data/lib/rufio/script_file.rb +44 -0
  55. data/lib/rufio/script_path_manager.rb +9 -43
  56. data/lib/rufio/script_runner.rb +13 -50
  57. data/lib/rufio/search_controller.rb +1 -1
  58. data/lib/rufio/shell_command_completion.rb +1 -5
  59. data/lib/rufio/shell_init.rb +90 -0
  60. data/lib/rufio/task_status.rb +5 -1
  61. data/lib/rufio/terminal_ui.rb +84 -298
  62. data/lib/rufio/text_utils.rb +26 -3
  63. data/lib/rufio/ui_renderer.rb +73 -45
  64. data/lib/rufio/version.rb +1 -1
  65. data/lib/rufio/zip_archive.rb +314 -0
  66. data/lib/rufio/zip_extractor.rb +346 -0
  67. data/lib/rufio/zip_writer.rb +268 -0
  68. data/lib/rufio/zoxide_integration.rb +5 -43
  69. data/lib/rufio.rb +6 -0
  70. data/lib_zig/rufio_native/Makefile +54 -17
  71. data/lib_zig/rufio_native/bench.sh +36 -0
  72. data/lib_zig/rufio_native/build.zig +32 -35
  73. data/lib_zig/rufio_native/src/cli.zig +99 -0
  74. data/lib_zig/rufio_native/src/main.zig +187 -83
  75. data/lib_zig/rufio_native/src/os.zig +889 -0
  76. data/lib_zig/rufio_native/src/search.zig +919 -0
  77. data/lib_zig/rufio_native/src/search_test.zig +88 -0
  78. data/lib_zig/rufio_native/src/zip.zig +482 -0
  79. metadata +128 -3
  80. data/lib/rufio/native/rufio_zig.bundle +0 -0
@@ -3,10 +3,18 @@
3
3
  module Rufio
4
4
  # コマンド補完機能を提供するクラス
5
5
  class CommandCompletion
6
+ # CommandMode が直接処理する(DSL コマンドではない)コマンド
7
+ NAVIGATION_COMMANDS = %w[cd].freeze
8
+
6
9
  # 初期化
7
- # @param history [CommandHistory, nil] コマンド履歴(オプション)
10
+ # @param history [CommandHistory, CommandMode, nil] 履歴、または単独で渡すコマンドモード
8
11
  # @param command_mode [CommandMode, nil] コマンドモード(スクリプト補完に使用)
9
12
  def initialize(history = nil, command_mode = nil)
13
+ # CommandMode だけを渡す従来の呼び方も受け付ける。
14
+ if history.is_a?(CommandMode) && command_mode.nil?
15
+ command_mode = history
16
+ history = nil
17
+ end
10
18
  @command_mode = command_mode || CommandMode.new
11
19
  @shell_completion = ShellCommandCompletion.new
12
20
  @history = history
@@ -23,6 +31,11 @@ module Rufio
23
31
  rake_candidates('')
24
32
  end
25
33
 
34
+ # ディレクトリ補完(cd で始まる場合)
35
+ if (match = input.match(/\A\s*cd\s+(.*)\z/))
36
+ return complete_cd(match[1])
37
+ end
38
+
26
39
  # シェルコマンド補完(!で始まる場合)
27
40
  if input.strip.start_with?('!')
28
41
  return complete_shell_command(input.strip)
@@ -40,7 +53,7 @@ module Rufio
40
53
  end
41
54
 
42
55
  # 通常のコマンド補完(内部コマンド + rakeタスク)
43
- available_commands = @command_mode.available_commands.map(&:to_s)
56
+ available_commands = @command_mode.available_commands.map(&:to_s) + NAVIGATION_COMMANDS
44
57
  input_lower = input.downcase
45
58
  candidates = available_commands.select do |command|
46
59
  command.downcase.start_with?(input_lower)
@@ -82,6 +95,19 @@ module Rufio
82
95
  # スクリプト候補を取得(ScriptRunner + ローカルスクリプト)
83
96
  # @param prefix [String] 入力中の文字列
84
97
  # @return [Array<String>] スクリプト候補(@付き)
98
+ # :cd のディレクトリ補完。相対パスは閲覧中ディレクトリを基準にする
99
+ def complete_cd(argument)
100
+ if argument.start_with?('~', '/') || File.absolute_path?(argument)
101
+ candidates = @shell_completion.complete_path(argument, directories_only: true)
102
+ else
103
+ base = @command_mode.browsing_directory || Dir.pwd
104
+ prefix = File.join(base, '')
105
+ candidates = @shell_completion.complete_path(File.join(base, argument), directories_only: true)
106
+ .map { |path| path.delete_prefix(prefix) }
107
+ end
108
+ candidates.map { |path| "cd #{path}/" }
109
+ end
110
+
85
111
  def script_candidates(prefix)
86
112
  @command_mode.complete_script("@#{prefix}")
87
113
  end
@@ -5,8 +5,6 @@ module Rufio
5
5
  class CommandHistory
6
6
  DEFAULT_MAX_SIZE = 1000
7
7
 
8
- attr_reader :size
9
-
10
8
  # 初期化
11
9
  # @param history_file [String] 履歴ファイルのパス
12
10
  # @param max_size [Integer] 履歴の最大保存数
@@ -37,6 +35,11 @@ module Rufio
37
35
  reset_position
38
36
  end
39
37
 
38
+ # 呼び出し側の変更が履歴本体や上下移動の位置に影響しないスナップショット。
39
+ def entries
40
+ @history.map(&:dup)
41
+ end
42
+
40
43
  # 前のコマンドを取得(上矢印キー相当)
41
44
  # @return [String, nil] 前のコマンド、存在しない場合はnil
42
45
  def previous
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'open3'
4
+ require_relative 'script_runner'
4
5
 
5
6
  module Rufio
6
7
  # コマンドモード - DSLコマンドを実行するための統一インターフェース
@@ -37,19 +38,14 @@ module Rufio
37
38
  # @param config_file [String] 設定ファイルのパス
38
39
  # @param job_manager [JobManager] ジョブマネージャー
39
40
  def setup_script_path_manager(config_file:, job_manager:)
40
- @job_manager = job_manager
41
41
  @script_path_manager = ScriptPathManager.new(config_file)
42
- # ScriptRunnerも設定(ScriptPathManagerのパスを使用)
43
- @script_runner = ScriptRunner.new(
44
- script_paths: @script_path_manager.paths,
45
- job_manager: job_manager,
46
- command_logger: @background_executor&.command_logger
47
- )
42
+ setup_script_runner(script_paths: @script_path_manager.paths, job_manager: job_manager)
48
43
  end
49
44
 
50
45
  # 閲覧中ディレクトリを更新
51
46
  # @param directory [String] 現在の閲覧ディレクトリ
52
47
  def update_browsing_directory(directory)
48
+ @browsing_directory = directory
53
49
  @local_script_scanner.update_directory(directory)
54
50
  @rakefile_parser.update_directory(directory)
55
51
  end
@@ -61,6 +57,11 @@ module Rufio
61
57
  # 空のコマンドは無視
62
58
  return nil if command_string.nil? || command_string.strip.empty?
63
59
 
60
+ # ディレクトリ移動 (cd <path>)。実際の移動は TerminalUI が行う
61
+ if (match = command_string.strip.match(/\Acd(?:\s+(.*))?\z/m))
62
+ return resolve_cd(match[1].to_s, working_dir)
63
+ end
64
+
64
65
  # スクリプト実行 (@ で始まる場合)
65
66
  if command_string.strip.start_with?('@')
66
67
  return execute_script(command_string.strip[1..-1], working_dir)
@@ -108,6 +109,9 @@ module Rufio
108
109
  "⚠️ コマンドが見つかりません: #{command_name}"
109
110
  end
110
111
 
112
+ # 閲覧中ディレクトリ(:cd の補完基準)
113
+ attr_reader :browsing_directory
114
+
111
115
  # 利用可能なコマンドのリストを取得
112
116
  def available_commands
113
117
  @commands.keys
@@ -190,6 +194,17 @@ module Rufio
190
194
  # @param command_name [Symbol] コマンド名
191
195
  # @param command [Hash] コマンド情報 { command: DslCommand, source: String }
192
196
  # @return [Hash] 実行結果
197
+ # :cd の引数を移動先に解決する
198
+ # @return [Hash] { cd: { directory:, file: } } / { cd: :previous }、失敗時はエラーメッセージ
199
+ def resolve_cd(argument, working_dir)
200
+ return { cd: :previous } if argument.strip == '-'
201
+
202
+ result = PathJump.resolve(argument, working_dir || @browsing_directory || Dir.pwd)
203
+ return "⚠️ #{result[:error]}" unless result[:directory]
204
+
205
+ { cd: { directory: result[:directory], file: result[:file] } }
206
+ end
207
+
193
208
  def execute_unified_command(command_name, command)
194
209
  dsl_cmd = command[:command]
195
210
 
@@ -214,27 +229,28 @@ module Rufio
214
229
  # コマンドが空の場合
215
230
  return { success: false, error: "コマンドが指定されていません" } if shell_command.strip.empty?
216
231
 
217
- begin
218
- # Open3を使って標準出力と標準エラーを分離して取得
219
- stdout, stderr, status = Open3.capture3(shell_command)
220
-
221
- result = {
222
- success: status.success?,
223
- output: stdout.strip,
224
- stderr: stderr.strip
225
- }
232
+ run_shell_capture(shell_command)
233
+ end
226
234
 
227
- # コマンドが失敗した場合、エラーメッセージを追加
228
- unless status.success?
229
- result[:error] = "コマンドが失敗しました (終了コード: #{status.exitstatus})"
230
- end
235
+ # Open3.capture3 によるコマンド実行と結果ハッシュの組み立て
236
+ def run_shell_capture(command, chdir: nil, not_found_label: "コマンド", error_label: "コマンド実行")
237
+ stdout, stderr, status = if chdir
238
+ Open3.capture3(command, chdir: chdir)
239
+ else
240
+ Open3.capture3(command)
241
+ end
231
242
 
232
- result
233
- rescue Errno::ENOENT => e
234
- { success: false, error: "コマンドが見つかりません: #{e.message}" }
235
- rescue StandardError => e
236
- { success: false, error: "コマンド実行エラー: #{e.message}" }
237
- end
243
+ result = {
244
+ success: status.success?,
245
+ output: stdout.strip,
246
+ stderr: stderr.strip
247
+ }
248
+ result[:error] = "コマンドが失敗しました (終了コード: #{status.exitstatus})" unless status.success?
249
+ result
250
+ rescue Errno::ENOENT => e
251
+ { success: false, error: "#{not_found_label}が見つかりません: #{e.message}" }
252
+ rescue StandardError => e
253
+ { success: false, error: "#{error_label}エラー: #{e.message}" }
238
254
  end
239
255
 
240
256
  # スクリプトを実行する(@プレフィックス用)
@@ -276,36 +292,17 @@ module Rufio
276
292
  # @param args [String, nil] スクリプトに渡す引数
277
293
  # @return [String, Hash] 実行結果メッセージ
278
294
  def execute_local_script(script, working_dir, args = nil)
279
- command = build_script_command(script)
280
- command = "#{command} #{args}" if args && !args.empty?
281
-
282
- if @job_manager
283
- job = @job_manager.add_job(
284
- name: script[:name],
285
- path: working_dir,
286
- command: command
287
- )
288
- job.start
289
-
290
- Thread.new do
291
- execute_script_in_background(job, script, working_dir, command)
292
- end
293
-
294
- "🚀 ジョブを開始: #{script[:name]}"
295
- else
296
- # 同期実行
297
- stdout, stderr, status = Open3.capture3(command, chdir: working_dir)
298
- result = {
299
- success: status.success?,
300
- output: stdout.strip,
301
- stderr: stderr.strip
302
- }
303
-
304
- # Logsに記録
305
- log_execution("@#{script[:name]}", result)
295
+ runner = ScriptRunner.new(
296
+ script_paths: [], job_manager: @job_manager,
297
+ command_logger: @background_executor&.command_logger
298
+ )
299
+ result = runner.execute(script, working_dir: working_dir, args: args)
300
+ return "🚀 ジョブを開始: #{script[:name]}" if @job_manager
306
301
 
307
- result
308
- end
302
+ # CommandMode の表示用の戻り値は維持する。
303
+ result = { success: result[:success], output: result[:output].strip, stderr: result[:error].strip }
304
+ log_execution("@#{script[:name]}", result)
305
+ result
309
306
  end
310
307
 
311
308
  # 実行結果をCommandLoggerに記録
@@ -324,59 +321,6 @@ module Rufio
324
321
  )
325
322
  end
326
323
 
327
- # スクリプトの実行コマンドを構築
328
- # @param script [Hash] スクリプト情報
329
- # @return [String] 実行コマンド
330
- def build_script_command(script)
331
- path = script[:path]
332
- ext = File.extname(path).downcase
333
-
334
- case ext
335
- when '.rb'
336
- "ruby #{path.shellescape}"
337
- when '.py'
338
- "python3 #{path.shellescape}"
339
- when '.js'
340
- "node #{path.shellescape}"
341
- when '.ts'
342
- "ts-node #{path.shellescape}"
343
- when '.pl'
344
- "perl #{path.shellescape}"
345
- when '.ps1'
346
- "pwsh #{path.shellescape}"
347
- else
348
- path.shellescape
349
- end
350
- end
351
-
352
- # ローカルスクリプトをバックグラウンドで実行
353
- def execute_script_in_background(job, script, working_dir, command)
354
- stdout, stderr, status = Open3.capture3(command, chdir: working_dir)
355
-
356
- job.append_log(stdout) unless stdout.empty?
357
- job.append_log(stderr) unless stderr.empty?
358
-
359
- if status.success?
360
- job.complete(exit_code: status.exitstatus)
361
- else
362
- job.fail(exit_code: status.exitstatus)
363
- end
364
-
365
- # Logsに記録
366
- log_execution("@#{script[:name]}", {
367
- success: status.success?,
368
- output: stdout.strip,
369
- stderr: stderr.strip
370
- })
371
-
372
- @job_manager&.notify_completion(job)
373
- rescue StandardError => e
374
- job.append_log("Error: #{e.message}")
375
- job.fail(exit_code: -1)
376
- log_execution("@#{script[:name]}", { success: false, output: '', stderr: e.message })
377
- @job_manager&.notify_completion(job)
378
- end
379
-
380
324
  # rakeタスクを実行する
381
325
  # @param task_name [String] タスク名
382
326
  # @param working_dir [String, nil] 作業ディレクトリ
@@ -393,32 +337,14 @@ module Rufio
393
337
  working_dir ||= Dir.pwd
394
338
  shell_command = "rake #{task_name.shellescape}"
395
339
 
396
- begin
397
- stdout, stderr, status = Open3.capture3(shell_command, chdir: working_dir)
398
-
399
- result = {
400
- success: status.success?,
401
- output: stdout.strip,
402
- stderr: stderr.strip
403
- }
404
-
405
- unless status.success?
406
- result[:error] = "コマンドが失敗しました (終了コード: #{status.exitstatus})"
407
- end
408
-
409
- # Logsに記録
410
- log_execution("rake:#{task_name}", result)
411
-
412
- result
413
- rescue Errno::ENOENT => e
414
- result = { success: false, error: "rakeが見つかりません: #{e.message}" }
415
- log_execution("rake:#{task_name}", result)
416
- result
417
- rescue StandardError => e
418
- result = { success: false, error: "rake実行エラー: #{e.message}" }
419
- log_execution("rake:#{task_name}", result)
420
- result
421
- end
340
+ result = run_shell_capture(
341
+ shell_command,
342
+ chdir: working_dir,
343
+ not_found_label: "rake",
344
+ error_label: "rake実行"
345
+ )
346
+ log_execution("rake:#{task_name}", result)
347
+ result
422
348
  end
423
349
 
424
350
  # スクリプトパスからスクリプトを検索して実行を試みる
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'dialog_support'
4
+
3
5
  require 'io/console'
4
6
 
5
7
  module Rufio
6
8
  # コマンドモードのUI - Tab補完とフローティングウィンドウでの結果表示
7
9
  class CommandModeUI
10
+ include DialogSupport
11
+
8
12
  def initialize(command_mode, dialog_renderer)
9
13
  @command_mode = command_mode
10
14
  @dialog_renderer = dialog_renderer
@@ -155,48 +159,6 @@ module Rufio
155
159
 
156
160
  private
157
161
 
158
- # オーバーレイダイアログを表示してキー入力を待つヘルパーメソッド
159
- def show_overlay_dialog(title, content_lines, options = {}, &block)
160
- # terminal_ui が利用可能で、screen と renderer が存在する場合のみオーバーレイを使用
161
- use_overlay = @terminal_ui &&
162
- @terminal_ui.respond_to?(:screen) &&
163
- @terminal_ui.respond_to?(:renderer) &&
164
- @terminal_ui.screen &&
165
- @terminal_ui.renderer
166
-
167
- if use_overlay
168
- # オーバーレイを使用
169
- @terminal_ui.show_overlay_dialog(title, content_lines, options, &block)
170
- else
171
- # フォールバック: 従来の方法
172
- width = options[:width]
173
- height = options[:height]
174
-
175
- unless width && height
176
- width, height = @dialog_renderer.calculate_dimensions(content_lines, {
177
- title: title,
178
- min_width: options[:min_width] || 40,
179
- max_width: options[:max_width] || 80
180
- })
181
- end
182
-
183
- x, y = @dialog_renderer.calculate_center(width, height)
184
-
185
- @dialog_renderer.draw_floating_window(x, y, width, height, title, content_lines, {
186
- border_color: options[:border_color] || "\e[37m",
187
- title_color: options[:title_color] || "\e[1;33m",
188
- content_color: options[:content_color] || "\e[37m"
189
- })
190
-
191
- key = block_given? ? yield : STDIN.getch
192
-
193
- @dialog_renderer.clear_area(x, y, width, height)
194
- @terminal_ui&.refresh_display
195
-
196
- key
197
- end
198
- end
199
-
200
162
  # 文字列配列の共通プレフィックスを見つける
201
163
  # @param strings [Array<String>] 文字列配列
202
164
  # @return [String] 共通プレフィックス
data/lib/rufio/config.rb CHANGED
@@ -19,147 +19,92 @@ module Rufio
19
19
  YAML_CONFIG_PATH = File.join(CONFIG_DIR, 'config.yml').freeze
20
20
  LOCAL_YAML_PATH = './rufio.yml'
21
21
 
22
+ # Base English message definitions
23
+ BASE_MESSAGES = {
24
+ # Application messages
25
+ 'app.interrupted' => 'rufio interrupted',
26
+ 'app.error_occurred' => 'Error occurred',
27
+ 'app.terminated' => 'rufio terminated',
28
+
29
+ # File operations
30
+ 'file.not_found' => 'File not found',
31
+ 'file.not_readable' => 'File not readable',
32
+ 'file.read_error' => 'File read error',
33
+ 'file.binary_file' => 'Binary file',
34
+ 'file.cannot_preview' => 'Cannot preview',
35
+ 'file.encoding_error' => 'Character encoding error - cannot read file',
36
+ 'file.invalid_archive' => 'Invalid ZIP archive',
37
+ 'file.preview_error' => 'Preview error',
38
+ 'file.error_prefix' => 'Error',
39
+
40
+ # Keybind messages
41
+ 'keybind.invalid_key' => 'invalid key',
42
+ 'keybind.search_text' => 'Search text: ',
43
+ 'keybind.no_matches' => 'No matches found.',
44
+ 'keybind.press_any_key' => 'Press any key to continue...',
45
+ 'keybind.input_filename' => 'Enter filename: ',
46
+ 'keybind.input_dirname' => 'Enter directory name: ',
47
+ 'keybind.invalid_filename' => 'Invalid filename (cannot contain / or \\)',
48
+ 'keybind.invalid_dirname' => 'Invalid directory name (cannot contain / or \\)',
49
+ 'keybind.file_exists' => 'File already exists',
50
+ 'keybind.directory_exists' => 'Directory already exists',
51
+ 'keybind.file_created' => 'File created',
52
+ 'keybind.directory_created' => 'Directory created',
53
+ 'keybind.creation_error' => 'Creation error',
54
+
55
+ # UI messages
56
+ 'ui.operation_prompt' => 'Operation: ',
57
+
58
+ # Help text
59
+ 'help.full' => 'j/k:move h:back l:enter o:open g/}:top/bottom G:global-search r:refresh f:filter s:search F:content a/A:create m/p/x:ops b:bookmark z:zoxide 1-9:goto q:quit',
60
+ 'help.short' => 'j/k:move h:back l:enter o:open f:filter s:search b:bookmark z:zoxide 1-9:goto q:quit',
61
+
62
+ # Health check messages
63
+ 'health.title' => 'rufio Health Check',
64
+ 'health.ruby_version' => 'Ruby version',
65
+ 'health.required_gems' => 'Required gems',
66
+ 'health.fzf' => 'fzf (file search)',
67
+ 'health.rga' => 'rga (content search)',
68
+ 'health.zoxide' => 'zoxide (directory history)',
69
+ 'health.bat' => 'bat (syntax highlight)',
70
+ 'health.file_opener' => 'System file opener',
71
+ 'health.summary' => 'Summary:',
72
+ 'health.ok' => 'OK',
73
+ 'health.warnings' => 'Warnings',
74
+ 'health.errors' => 'Errors',
75
+ 'health.all_passed' => 'All checks passed! rufio is ready to use.',
76
+ 'health.critical_missing' => 'Some critical components are missing. rufio may not work properly.',
77
+ 'health.optional_missing' => 'Some optional features are unavailable. Basic functionality will work.',
78
+ 'health.all_gems_installed' => 'All required gems installed',
79
+ 'health.missing_gems' => 'Missing gems',
80
+ 'health.gem_install_instruction' => 'Run: gem install',
81
+ 'health.tool_not_found' => 'not found',
82
+ 'health.unknown_platform' => 'Unknown platform',
83
+ 'health.file_open_may_not_work' => 'File opening may not work properly',
84
+ 'health.macos_opener' => 'macOS file opener',
85
+ 'health.linux_opener' => 'Linux file opener',
86
+ 'health.windows_opener' => 'Windows file opener',
87
+ 'health.install_brew' => 'Install: brew install',
88
+ 'health.install_apt' => 'Install: apt install',
89
+ 'health.install_guide' => 'Install: Check the installation guide for your platform',
90
+ 'health.rga_releases' => 'Install: https://github.com/phiresky/ripgrep-all/releases',
91
+ 'health.ruby_upgrade_needed' => 'Please upgrade Ruby to version 2.7.0 or higher'
92
+ }.freeze
93
+
22
94
  # Multi-language message definitions
23
95
  MESSAGES = {
24
- 'en' => {
25
- # Application messages
26
- 'app.interrupted' => 'rufio interrupted',
27
- 'app.error_occurred' => 'Error occurred',
28
- 'app.terminated' => 'rufio terminated',
29
-
30
- # File operations
31
- 'file.not_found' => 'File not found',
32
- 'file.not_readable' => 'File not readable',
33
- 'file.read_error' => 'File read error',
34
- 'file.binary_file' => 'Binary file',
35
- 'file.cannot_preview' => 'Cannot preview',
36
- 'file.encoding_error' => 'Character encoding error - cannot read file',
37
- 'file.preview_error' => 'Preview error',
38
- 'file.error_prefix' => 'Error',
39
-
40
- # Keybind messages
41
- 'keybind.invalid_key' => 'invalid key',
42
- 'keybind.search_text' => 'Search text: ',
43
- 'keybind.no_matches' => 'No matches found.',
44
- 'keybind.press_any_key' => 'Press any key to continue...',
45
- 'keybind.input_filename' => 'Enter filename: ',
46
- 'keybind.input_dirname' => 'Enter directory name: ',
47
- 'keybind.invalid_filename' => 'Invalid filename (cannot contain / or \\)',
48
- 'keybind.invalid_dirname' => 'Invalid directory name (cannot contain / or \\)',
49
- 'keybind.file_exists' => 'File already exists',
50
- 'keybind.directory_exists' => 'Directory already exists',
51
- 'keybind.file_created' => 'File created',
52
- 'keybind.directory_created' => 'Directory created',
53
- 'keybind.creation_error' => 'Creation error',
54
-
55
- # UI messages
56
- 'ui.operation_prompt' => 'Operation: ',
57
-
58
- # Help text
59
- 'help.full' => 'j/k:move h:back l:enter o:open g/}:top/bottom G:global-search r:refresh f:filter s:search F:content a/A:create m/p/x:ops b:bookmark z:zoxide 1-9:goto q:quit',
60
- 'help.short' => 'j/k:move h:back l:enter o:open f:filter s:search b:bookmark z:zoxide 1-9:goto q:quit',
61
-
62
- # Health check messages
63
- 'health.title' => 'rufio Health Check',
64
- 'health.ruby_version' => 'Ruby version',
65
- 'health.required_gems' => 'Required gems',
66
- 'health.fzf' => 'fzf (file search)',
67
- 'health.rga' => 'rga (content search)',
68
- 'health.zoxide' => 'zoxide (directory history)',
69
- 'health.bat' => 'bat (syntax highlight)',
70
- 'health.file_opener' => 'System file opener',
71
- 'health.summary' => 'Summary:',
72
- 'health.ok' => 'OK',
73
- 'health.warnings' => 'Warnings',
74
- 'health.errors' => 'Errors',
75
- 'health.all_passed' => 'All checks passed! rufio is ready to use.',
76
- 'health.critical_missing' => 'Some critical components are missing. rufio may not work properly.',
77
- 'health.optional_missing' => 'Some optional features are unavailable. Basic functionality will work.',
78
- 'health.all_gems_installed' => 'All required gems installed',
79
- 'health.missing_gems' => 'Missing gems',
80
- 'health.gem_install_instruction' => 'Run: gem install',
81
- 'health.tool_not_found' => 'not found',
82
- 'health.unknown_platform' => 'Unknown platform',
83
- 'health.file_open_may_not_work' => 'File opening may not work properly',
84
- 'health.macos_opener' => 'macOS file opener',
85
- 'health.linux_opener' => 'Linux file opener',
86
- 'health.windows_opener' => 'Windows file opener',
87
- 'health.install_brew' => 'Install: brew install',
88
- 'health.install_apt' => 'Install: apt install',
89
- 'health.install_guide' => 'Install: Check the installation guide for your platform',
90
- 'health.rga_releases' => 'Install: https://github.com/phiresky/ripgrep-all/releases',
91
- 'health.ruby_upgrade_needed' => 'Please upgrade Ruby to version 2.7.0 or higher'
92
- },
93
-
94
- 'ja' => {
95
- # Application messages
96
- 'app.interrupted' => 'rufio interrupted',
97
- 'app.error_occurred' => 'Error occurred',
98
- 'app.terminated' => 'rufio terminated',
99
-
100
- # File operations
101
- 'file.not_found' => 'File not found',
102
- 'file.not_readable' => 'File not readable',
103
- 'file.read_error' => 'File read error',
104
- 'file.binary_file' => 'Binary file',
105
- 'file.cannot_preview' => 'Cannot preview',
96
+ 'en' => BASE_MESSAGES,
97
+ 'ja' => BASE_MESSAGES.merge(
106
98
  'file.encoding_error' => 'Encoding error - cannot read file',
107
- 'file.preview_error' => 'Preview error',
108
- 'file.error_prefix' => 'Error',
109
-
110
- # Keybind messages
111
99
  'keybind.invalid_key' => 'Invalid key',
112
100
  'keybind.search_text' => 'Search: ',
113
- 'keybind.no_matches' => 'No matches found.',
114
- 'keybind.press_any_key' => 'Press any key to continue...',
115
- 'keybind.input_filename' => 'Enter filename: ',
116
- 'keybind.input_dirname' => 'Enter directory name: ',
117
- 'keybind.invalid_filename' => 'Invalid filename (cannot contain / or \\)',
118
- 'keybind.invalid_dirname' => 'Invalid directory name (cannot contain / or \\)',
119
- 'keybind.file_exists' => 'File already exists',
120
- 'keybind.directory_exists' => 'Directory already exists',
121
- 'keybind.file_created' => 'File created',
122
- 'keybind.directory_created' => 'Directory created',
123
- 'keybind.creation_error' => 'Creation error',
124
-
125
- # UI messages
126
- 'ui.operation_prompt' => 'Operation: ',
127
-
128
- # Help text
129
101
  'help.full' => 'j/k:move h:back l:enter o:open g/}:top/end G:global-search r:refresh f:filter s:search F:content a/A:create m/c/x:ops b:bookmark z:zoxide 1-9:jump q:quit',
130
102
  'help.short' => 'j/k:move h:back l:enter o:open f:filter s:search b:bookmark z:zoxide 1-9:jump q:quit',
131
-
132
- # Health check messages
133
- 'health.title' => 'rufio Health Check',
134
- 'health.ruby_version' => 'Ruby version',
135
- 'health.required_gems' => 'Required gems',
136
- 'health.fzf' => 'fzf (file search)',
137
- 'health.rga' => 'rga (content search)',
138
- 'health.zoxide' => 'zoxide (directory history)',
139
- 'health.bat' => 'bat (syntax highlight)',
140
- 'health.file_opener' => 'System file opener',
141
- 'health.summary' => 'Summary:',
142
- 'health.ok' => 'OK',
143
- 'health.warnings' => 'Warnings',
144
- 'health.errors' => 'Errors',
145
- 'health.all_passed' => 'All checks passed! rufio is ready to use.',
146
103
  'health.critical_missing' => 'Critical components missing. rufio may not work properly.',
147
104
  'health.optional_missing' => 'Optional features unavailable. Basic features will work.',
148
105
  'health.all_gems_installed' => 'All required gems are installed',
149
- 'health.missing_gems' => 'Missing gems',
150
- 'health.gem_install_instruction' => 'Run: gem install',
151
- 'health.tool_not_found' => 'not found',
152
- 'health.unknown_platform' => 'Unknown platform',
153
- 'health.file_open_may_not_work' => 'File open may not work properly',
154
- 'health.macos_opener' => 'macOS file opener',
155
- 'health.linux_opener' => 'Linux file opener',
156
- 'health.windows_opener' => 'Windows file opener',
157
- 'health.install_brew' => 'Install: brew install',
158
- 'health.install_apt' => 'Install: apt install',
159
- 'health.install_guide' => 'Install: Check the installation guide for your platform',
160
- 'health.rga_releases' => 'Install: https://github.com/phiresky/ripgrep-all/releases',
161
- 'health.ruby_upgrade_needed' => 'Please upgrade Ruby to version 2.7.0 or higher'
162
- }
106
+ 'health.file_open_may_not_work' => 'File open may not work properly'
107
+ ).freeze
163
108
  }.freeze
164
109
 
165
110
  class << self
@@ -272,14 +217,21 @@ module Rufio
272
217
  return [] unless File.exist?(path)
273
218
 
274
219
  yaml = YAML.safe_load(File.read(path), symbolize_names: true)
275
- return [] unless yaml.is_a?(Array)
276
-
277
- yaml.select { |b| b.is_a?(Hash) && b[:path] && b[:name] }
220
+ filter_valid_bookmarks(yaml)
278
221
  rescue StandardError => e
279
222
  warn "Failed to load bookmarks: #{e.message}"
280
223
  []
281
224
  end
282
225
 
226
+ # 有効なブックマーク要素のみを抽出
227
+ # @param bookmarks [Array<Hash>] ブックマーク配列
228
+ # @return [Array<Hash>] フィルタ済みブックマーク配列
229
+ def filter_valid_bookmarks(bookmarks)
230
+ return [] unless bookmarks.is_a?(Array)
231
+
232
+ bookmarks.select { |b| b.is_a?(Hash) && b[:path] && b[:name] }
233
+ end
234
+
283
235
  # ブックマークを保存
284
236
  # @param path [String] YAMLファイルのパス
285
237
  # @param bookmarks [Array<Hash>] ブックマーク配列
@@ -398,9 +350,9 @@ module Rufio
398
350
  end
399
351
 
400
352
  def detect_language
401
- # Only BENIYA_LANG environment variable takes precedence
353
+ # RUFIO_LANG or BENIYA_LANG (legacy) environment variable takes precedence
402
354
  # This ensures English is default unless explicitly requested
403
- env_lang = ENV['BENIYA_LANG']
355
+ env_lang = ENV['RUFIO_LANG'] || ENV['BENIYA_LANG']
404
356
 
405
357
  if env_lang && !env_lang.empty?
406
358
  # Extract language code (e.g., 'ja_JP.UTF-8' -> 'ja')