rufio 1.1.0 → 1.3.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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +74 -3
  4. data/README_ja.md +71 -2
  5. data/Rakefile +14 -1
  6. data/bin/rufio +31 -2
  7. data/docs/fast_zip_design_ja.md +167 -0
  8. data/info/help.md +2 -0
  9. data/info/keybindings.md +3 -1
  10. data/info/welcome.md +1 -1
  11. data/lib/rufio/application.rb +12 -1
  12. data/lib/rufio/archive_controller.rb +211 -0
  13. data/lib/rufio/background_command_executor.rb +9 -0
  14. data/lib/rufio/background_status.rb +49 -0
  15. data/lib/rufio/bookmark_controller.rb +38 -121
  16. data/lib/rufio/bookmark_manager.rb +77 -331
  17. data/lib/rufio/command_completion.rb +6 -1
  18. data/lib/rufio/command_history.rb +5 -2
  19. data/lib/rufio/command_mode.rb +40 -134
  20. data/lib/rufio/command_mode_ui.rb +4 -42
  21. data/lib/rufio/config.rb +88 -136
  22. data/lib/rufio/config_loader.rb +3 -11
  23. data/lib/rufio/dialog_renderer.rb +64 -152
  24. data/lib/rufio/dialog_support.rb +69 -0
  25. data/lib/rufio/document_text_extractor.rb +167 -0
  26. data/lib/rufio/file_operation_controller.rb +49 -222
  27. data/lib/rufio/file_preview.rb +46 -2
  28. data/lib/rufio/global_search.rb +121 -9
  29. data/lib/rufio/help_menu.rb +8 -2
  30. data/lib/rufio/interpreter_resolver.rb +6 -11
  31. data/lib/rufio/job_manager.rb +2 -5
  32. data/lib/rufio/key_input.rb +77 -0
  33. data/lib/rufio/keybind_handler.rb +81 -115
  34. data/lib/rufio/local_script_scanner.rb +6 -18
  35. data/lib/rufio/native/aarch64-linux-gnu/librufio_zig.so +0 -0
  36. data/lib/rufio/native/aarch64-linux-musl/librufio_zig.so +0 -0
  37. data/lib/rufio/native/aarch64-macos/librufio_zig.dylib +0 -0
  38. data/lib/rufio/native/aarch64-windows/rufio_zig.dll +0 -0
  39. data/lib/rufio/native/x86_64-linux-gnu/librufio_zig.so +0 -0
  40. data/lib/rufio/native/x86_64-linux-musl/librufio_zig.so +0 -0
  41. data/lib/rufio/native/x86_64-macos/librufio_zig.dylib +0 -0
  42. data/lib/rufio/native/x86_64-windows/rufio_zig.dll +0 -0
  43. data/lib/rufio/native_content_search.rb +153 -0
  44. data/lib/rufio/native_scanner.rb +13 -68
  45. data/lib/rufio/native_scanner_zig.rb +106 -56
  46. data/lib/rufio/native_zip.rb +121 -0
  47. data/lib/rufio/navigation_controller.rb +18 -20
  48. data/lib/rufio/screen.rb +2 -12
  49. data/lib/rufio/script_executor.rb +6 -73
  50. data/lib/rufio/script_file.rb +44 -0
  51. data/lib/rufio/script_path_manager.rb +9 -43
  52. data/lib/rufio/script_runner.rb +13 -50
  53. data/lib/rufio/search_controller.rb +1 -1
  54. data/lib/rufio/shell_command_completion.rb +1 -5
  55. data/lib/rufio/shell_init.rb +90 -0
  56. data/lib/rufio/task_status.rb +5 -1
  57. data/lib/rufio/terminal_ui.rb +55 -294
  58. data/lib/rufio/text_utils.rb +26 -3
  59. data/lib/rufio/ui_renderer.rb +63 -40
  60. data/lib/rufio/version.rb +1 -1
  61. data/lib/rufio/zip_archive.rb +314 -0
  62. data/lib/rufio/zip_extractor.rb +346 -0
  63. data/lib/rufio/zip_writer.rb +268 -0
  64. data/lib/rufio/zoxide_integration.rb +5 -43
  65. data/lib/rufio.rb +5 -0
  66. data/lib_zig/rufio_native/Makefile +54 -17
  67. data/lib_zig/rufio_native/bench.sh +36 -0
  68. data/lib_zig/rufio_native/build.zig +32 -35
  69. data/lib_zig/rufio_native/src/cli.zig +99 -0
  70. data/lib_zig/rufio_native/src/main.zig +187 -83
  71. data/lib_zig/rufio_native/src/os.zig +889 -0
  72. data/lib_zig/rufio_native/src/search.zig +919 -0
  73. data/lib_zig/rufio_native/src/search_test.zig +88 -0
  74. data/lib_zig/rufio_native/src/zip.zig +482 -0
  75. metadata +125 -3
  76. data/lib/rufio/native/rufio_zig.bundle +0 -0
@@ -4,39 +4,18 @@ require 'io/console'
4
4
  require_relative 'text_utils'
5
5
  require_relative 'help_menu'
6
6
  require_relative 'global_search'
7
+ require_relative 'ui_renderer'
7
8
 
8
9
  module Rufio
9
10
  class TerminalUI
10
- # Layout constants
11
- HEADER_HEIGHT = 1 # Header占有行数(モードタブ+パス+バージョン 1行に統合)
12
- FOOTER_HEIGHT = 1 # Footer占有行数(ブックマーク一覧 + ステータス情報)
13
- HEADER_FOOTER_MARGIN = 2 # Header(1行) + Footer(1行)分のマージン
14
-
15
- # Panel layout ratios
16
- LEFT_PANEL_RATIO = 0.5 # 左パネルの幅比率
17
- RIGHT_PANEL_RATIO = 1.0 - LEFT_PANEL_RATIO
18
-
19
11
  # Display constants
20
12
  DEFAULT_SCREEN_WIDTH = 80 # デフォルト画面幅
21
13
  DEFAULT_SCREEN_HEIGHT = 24 # デフォルト画面高さ
22
- HEADER_PADDING = 2 # ヘッダーのパディング
23
- FILTER_TEXT_RESERVED = 15 # フィルタテキスト表示の予約幅
24
- TAB_SEPARATOR = ">" # タブ間セパレータ
25
-
26
- # File display constants
27
- ICON_SIZE_PADDING = 12 # アイコン、選択マーク、サイズ情報分
28
- CURSOR_OFFSET = 1 # カーソル位置のオフセット
14
+ HEADER_FOOTER_MARGIN = UIRenderer::HEADER_FOOTER_MARGIN
15
+ CONTENT_START_LINE = UIRenderer::CONTENT_START_LINE
29
16
 
30
- # Size display constants (bytes)
31
- KILOBYTE = 1024
32
- MEGABYTE = KILOBYTE * 1024
33
- GIGABYTE = MEGABYTE * 1024
34
-
35
- # Bookmark highlight duration (seconds)
36
- BOOKMARK_HIGHLIGHT_DURATION = 0.5
37
-
38
- # Line offsets
39
- CONTENT_START_LINE = 1 # コンテンツ開始行(フッタ1行: Y=0)
17
+ # T キーで終了した時のディレクトリ(それ以外の終了では nil)
18
+ attr_reader :exit_directory
40
19
 
41
20
  def initialize(test_mode: false)
42
21
  console = IO.console
@@ -73,17 +52,6 @@ module Rufio
73
52
  @in_job_mode = false
74
53
  @job_mode_needs_redraw = false
75
54
 
76
- # Preview cache
77
- @preview_cache = {}
78
- @last_preview_path = nil
79
-
80
- # シンタックスハイライター(bat が利用可能な場合のみ動作)
81
- @syntax_highlighter = SyntaxHighlighter.new
82
-
83
-
84
- # Tab mode manager
85
- @tab_mode_manager = TabModeManager.new
86
-
87
55
  # UIRenderer(描画ロジックを担当)
88
56
  ui_opts = ConfigLoader.ui_options
89
57
  @ui_renderer = UIRenderer.new(
@@ -93,9 +61,11 @@ module Rufio
93
61
  left_panel_ratio: ui_opts[:panel_ratio],
94
62
  preview_enabled: ui_opts[:preview_enabled]
95
63
  )
64
+ @tab_mode_manager = @ui_renderer.tab_mode_manager
65
+ @background_status = @ui_renderer.background_status
96
66
  end
97
67
 
98
- attr_reader :ui_renderer
68
+ attr_reader :ui_renderer, :screen_width, :screen_height
99
69
 
100
70
  def start(directory_listing, keybind_handler, file_preview, background_executor = nil)
101
71
  @directory_listing = directory_listing
@@ -137,24 +107,10 @@ module Rufio
137
107
  # ウィンドウサイズを更新してから画面をクリアして再描画
138
108
  update_screen_size
139
109
 
140
- # プレビューキャッシュをクリア(ディレクトリ変更やリフレッシュ時)
141
- @preview_cache.clear
142
- @last_preview_path = nil
143
-
144
- # ブックマークキャッシュもクリア
145
- @cached_bookmarks = nil
146
- @cached_bookmark_time = nil
147
-
148
- # バッファベースの描画が利用可能な場合は全画面を再描画
149
- if @screen && @renderer
150
- # レンダラーの前フレーム情報をリセット(差分レンダリングを強制的に全体描画にする)
151
- @renderer.clear
152
- @screen.clear
153
- draw_screen_to_buffer(@screen, nil, nil)
154
- @renderer.render(@screen)
155
- # カーソルを画面外に移動
156
- print "\e[#{@screen_height};#{@screen_width}H"
157
- end
110
+ @ui_renderer.clear_preview_cache
111
+ @ui_renderer.clear_bookmark_cache
112
+
113
+ render_full_screen
158
114
  end
159
115
 
160
116
  # スクリプトランナーを設定
@@ -207,6 +163,8 @@ module Rufio
207
163
  rescue
208
164
  # スレッド終了
209
165
  end
166
+ # ダイアログのキー入力もこのキューから読ませ、入力スレッドとの奪い合いを防ぐ
167
+ KeyInput.attach(@input_queue)
210
168
  end
211
169
 
212
170
  # SGR拡張マウスレポートを有効化
@@ -238,11 +196,14 @@ module Rufio
238
196
  end
239
197
 
240
198
  # エスケープシーケンスの後続バイトを読み取る(矢印キー等の短いシーケンス用)。
241
- # Windows: IO.select を使わずキューから取得(5ms タイムアウト)。
199
+ # Windows: IO.select を使わずキューから取得。
242
200
  def read_next_input_byte
243
201
  if windows?
244
- # ConPTY ではバイト到着が遅れることがあるため 5ms では ESC 単体と誤判定する
245
- b = windows_queue_pop_with_timeout(0.025)
202
+ # ConPTY ではバイト到着が遅れることがあるため 5ms では ESC 単体と誤判定する。
203
+ # G mode は検索中に全画面を再描画し続けて遅延が大きくなりやすく、
204
+ # しかも ESC 単体で閉じてしまうため、待ち時間を長めに取る。
205
+ timeout = global_search_active? ? 0.1 : 0.025
206
+ b = windows_queue_pop_with_timeout(timeout)
246
207
  b.nil? ? nil : b.chr(Encoding::BINARY)
247
208
  else
248
209
  STDIN.read_nonblock(1) rescue nil
@@ -263,46 +224,12 @@ module Rufio
263
224
  # Windows 専用: キューから1文字(マルチバイト対応)を組み立てる。
264
225
  # first_byte_int は getbyte が返す Integer。
265
226
  def windows_build_char(first_byte_int)
266
- remaining = case first_byte_int
267
- when 0x00..0x7F then 0
268
- when 0xC0..0xDF then 1
269
- when 0xE0..0xEF then 2
270
- when 0xF0..0xF7 then 3
271
- else 0
272
- end
273
- if remaining == 0
274
- first_byte_int.chr(Encoding::UTF_8)
275
- else
276
- buf = first_byte_int.chr(Encoding::BINARY)
277
- remaining.times do
278
- b = windows_queue_pop_with_timeout(0.005)
279
- return nil if b.nil?
280
- buf << b.chr(Encoding::BINARY)
281
- end
282
- result = buf.force_encoding(Encoding::UTF_8)
283
- result.valid_encoding? ? result : nil
284
- end
227
+ KeyInput.build_char(first_byte_int, @input_queue)
285
228
  end
286
229
 
287
230
  # Windows 専用: @input_queue からタイムアウト付きで Integer バイトを取り出す。
288
- # Ruby 3.2+ は Queue#pop(timeout:) で正確に待機する(sleep ポーリングは
289
- # Windows のタイマー分解能 約15ms により遅延する)。それ以前はポーリング。
290
- QUEUE_POP_TIMEOUT_SUPPORTED = Queue.instance_method(:pop).parameters.any? { |_, n| n == :timeout }
291
-
292
231
  def windows_queue_pop_with_timeout(timeout_sec)
293
- if QUEUE_POP_TIMEOUT_SUPPORTED
294
- return @input_queue.pop(timeout: timeout_sec)
295
- end
296
-
297
- deadline = Time.now + timeout_sec
298
- loop do
299
- begin
300
- return @input_queue.pop(true)
301
- rescue ThreadError
302
- return nil if Time.now >= deadline
303
- sleep 0.001
304
- end
305
- end
232
+ KeyInput.pop_with_timeout(@input_queue, timeout_sec)
306
233
  end
307
234
 
308
235
  def cleanup_terminal
@@ -317,6 +244,7 @@ module Rufio
317
244
 
318
245
  # Windows 入力スレッドを停止
319
246
  if windows? && @input_thread
247
+ KeyInput.detach
320
248
  @stop_input_thread = true
321
249
  @input_thread.kill rescue nil
322
250
  @input_thread.join(0.5) rescue nil
@@ -365,7 +293,6 @@ module Rufio
365
293
  # 最小スリープ時間(入力チェック間隔)
366
294
  # Windows は入力をキュー経由で受けるため、入力遅延を抑えるよう短めにする
367
295
  min_sleep_interval = windows? ? 0.008 : 0.0333 # Windows: 約8ms / Unix: 30FPS
368
- check_interval = 0.1 # バックグラウンドタスクのチェック間隔
369
296
 
370
297
  # Phase 3: Screen/Rendererを初期化
371
298
  @screen = Screen.new(@screen_width, @screen_height)
@@ -376,13 +303,6 @@ module Rufio
376
303
  draw_screen_to_buffer(@screen, nil, nil)
377
304
  @renderer.render(@screen)
378
305
 
379
- last_notification_check = Time.now
380
- last_lamp_check = Time.now
381
- notification_message = nil
382
- notification_time = nil
383
- previous_notification = nil
384
- previous_lamp_message = @ui_renderer.completion_lamp_message
385
-
386
306
  # FPS計測用
387
307
  frame_times = []
388
308
  last_frame_time = Time.now
@@ -429,56 +349,11 @@ module Rufio
429
349
  end
430
350
  needs_redraw = true if had_input
431
351
 
432
- # バックグラウンドコマンドの完了チェック(0.1秒ごと)
433
- if @background_executor && (start - last_notification_check) > check_interval
434
- if !@background_executor.running? && @background_executor.get_completion_message
435
- completion_msg = @background_executor.get_completion_message
436
- # 通知メッセージとして表示
437
- notification_message = completion_msg
438
- notification_time = start
439
- # フッターのランプ表示用にも設定(UIRenderer が管理)
440
- @ui_renderer.completion_lamp_message = completion_msg
441
- @ui_renderer.completion_lamp_time = start
442
- @background_executor.instance_variable_set(:@completion_message, nil) # メッセージをクリア
443
- needs_redraw = true
444
- end
445
- last_notification_check = start
446
- end
447
-
448
- # バックグラウンドコマンドの実行状態が変わった場合も再描画
449
- if @background_executor
450
- current_running = @background_executor.running?
451
- if @last_bg_running != current_running
452
- @last_bg_running = current_running
453
- needs_redraw = true
454
- end
455
- end
456
-
457
- # 完了ランプの表示状態をチェック(0.5秒ごと)
458
- if (start - last_lamp_check) > 0.5
459
- current_lamp = @ui_renderer.completion_lamp_message
460
- if current_lamp != previous_lamp_message
461
- previous_lamp_message = current_lamp
462
- needs_redraw = true
463
- end
464
- # 完了ランプのタイムアウトチェック
465
- if @ui_renderer.completion_lamp_message && @ui_renderer.completion_lamp_time &&
466
- (start - @ui_renderer.completion_lamp_time) >= 3.0
467
- @ui_renderer.completion_lamp_message = nil
468
- needs_redraw = true
469
- end
470
- last_lamp_check = start
471
- end
472
-
473
- # 通知メッセージの変化をチェック
474
- current_notification = notification_message && (start - notification_time) < 3.0 ? notification_message : nil
475
- if current_notification != previous_notification
476
- previous_notification = current_notification
477
- notification_message = nil if current_notification.nil?
478
- needs_redraw = true
479
- end
352
+ # 通知・ランプ・実行状態の更新を一か所に集約する。
353
+ needs_redraw = true if @background_status.update(@background_executor, now: start)
480
354
 
481
355
  needs_redraw = true if @global_search.poll
356
+ needs_redraw = true if @keybind_handler&.poll_archive_jobs
482
357
 
483
358
  # 非同期シンタックスハイライト完了チェック(UIRenderer側のフラグを参照)
484
359
  if @ui_renderer.highlight_updated?
@@ -495,11 +370,7 @@ module Rufio
495
370
  # DRAW & RENDER phase - 変更があった場合のみ描画
496
371
  if needs_redraw
497
372
  # Screenバッファに描画(clearは呼ばない。必要な部分だけ更新)
498
- if notification_message && (start - notification_time) < 3.0
499
- draw_screen_to_buffer(@screen, notification_message, current_fps)
500
- else
501
- draw_screen_to_buffer(@screen, nil, current_fps)
502
- end
373
+ draw_screen_to_buffer(@screen, @background_status.message, current_fps)
503
374
 
504
375
  # ヘルプまたはコマンドモードのダイアログをオーバーレイに描画
505
376
  if help_dialog_active?
@@ -668,6 +539,12 @@ module Rufio
668
539
  @running = false
669
540
  end
670
541
 
542
+ # T キー: 確認なしで終了し、現在のディレクトリを終了後の移動先にする
543
+ if result == :quit_cd
544
+ @exit_directory = @directory_listing.current_path
545
+ @running = false
546
+ end
547
+
671
548
  # 入力があったことを返す
672
549
  true
673
550
  end
@@ -757,13 +634,14 @@ module Rufio
757
634
 
758
635
  # ファイルリストのクリック行からエントリを選択する(ダブルクリック対応)
759
636
  def handle_mouse_file_click(row)
760
- # コンテンツ行はターミナル行2〜(screen_height-1)
761
- content_height = @screen_height - 2
762
- return unless row >= 2 && row <= @screen_height - 1
637
+ # コンテンツ行はターミナル行(1 始まり)で CONTENT_START_LINE+1 〜 screen_height-1
638
+ first_row = CONTENT_START_LINE + 1
639
+ content_height = @screen_height - HEADER_FOOTER_MARGIN
640
+ return unless row >= first_row && row <= @screen_height - 1
763
641
 
764
642
  current_idx = @keybind_handler.current_index
765
643
  start_index = [current_idx - content_height / 2, 0].max
766
- target_index = start_index + (row - 2)
644
+ target_index = start_index + (row - first_row)
767
645
 
768
646
  entries = @keybind_handler.send(:get_active_entries)
769
647
  return unless target_index >= 0 && target_index < entries.length
@@ -809,37 +687,7 @@ module Rufio
809
687
 
810
688
  # モード変更を適用
811
689
  def apply_mode_change(mode)
812
- case mode
813
- when :config
814
- @keybind_handler.enter_config_mode
815
- when :global
816
- @keybind_handler.start_global_search
817
- when :files
818
- # ヘルプモードまたはログビューワモードから戻る
819
- if @keybind_handler.help_mode?
820
- @keybind_handler.send(:exit_help_mode)
821
- elsif @keybind_handler.log_viewer_mode?
822
- @keybind_handler.send(:exit_log_viewer_mode)
823
- elsif @keybind_handler.in_job_mode?
824
- @keybind_handler.send(:exit_job_mode)
825
- end
826
- when :help
827
- show_help_dialog
828
- when :logs
829
- # ログビューワモードに入る
830
- unless @keybind_handler.log_viewer_mode?
831
- @keybind_handler.send(:exit_help_mode) if @keybind_handler.help_mode?
832
- @keybind_handler.send(:exit_job_mode) if @keybind_handler.in_job_mode?
833
- @keybind_handler.send(:enter_log_viewer_mode)
834
- end
835
- when :jobs
836
- # ジョブモードに入る
837
- unless @keybind_handler.in_job_mode?
838
- @keybind_handler.send(:exit_help_mode) if @keybind_handler.help_mode?
839
- @keybind_handler.send(:exit_log_viewer_mode) if @keybind_handler.log_viewer_mode?
840
- @keybind_handler.enter_job_mode
841
- end
842
- end
690
+ @keybind_handler.switch_mode(mode)
843
691
  end
844
692
 
845
693
  # コマンドモード関連のメソッドは public にする
@@ -915,22 +763,9 @@ module Rufio
915
763
  @command_input = ""
916
764
  when "\e"
917
765
  # Escape キーでコマンドモードをキャンセル
918
- # まずコマンドウィンドウをクリア
919
766
  @command_mode_ui.clear_prompt
920
767
  deactivate_command_mode
921
- # ファイラー画面を再描画(バッファベース)
922
- if @screen && @renderer
923
- # レンダラーの前フレーム情報をリセット(差分レンダリングを強制的に全体描画にする)
924
- @renderer.clear
925
- @screen.clear
926
- draw_screen_to_buffer(@screen, nil, nil)
927
- @renderer.render(@screen)
928
- # カーソルを画面外に移動(メインループと同じ処理)
929
- print "\e[#{@screen_height};#{@screen_width}H"
930
- else
931
- # フォールバック(古い実装)
932
- draw_screen
933
- end
768
+ render_full_screen
934
769
  when "\t"
935
770
  # Tab キーで補完
936
771
  handle_tab_completion
@@ -1057,16 +892,6 @@ module Rufio
1057
892
  info_notice.mark_as_shown(notice[:file])
1058
893
  end
1059
894
 
1060
- # ログモードに入る(廃止済み: 空のメソッド)
1061
- def enter_log_mode(_project_log)
1062
- # プロジェクトモード廃止により何もしない
1063
- end
1064
-
1065
- # ログモードを終了(廃止済み: 空のメソッド)
1066
- def exit_log_mode
1067
- # プロジェクトモード廃止により何もしない
1068
- end
1069
-
1070
895
  # ジョブモードを設定
1071
896
  def set_job_mode(job_mode, job_manager, notification_manager)
1072
897
  @job_mode_instance = job_mode
@@ -1084,17 +909,8 @@ module Rufio
1084
909
  @in_job_mode = false
1085
910
  @job_mode_instance = nil
1086
911
  @job_manager = nil
1087
- # バッファベースの全画面再描画を使用
1088
912
  update_screen_size
1089
- if @screen && @renderer
1090
- @renderer.clear
1091
- @screen.clear
1092
- draw_screen_to_buffer(@screen, nil, nil)
1093
- @renderer.render(@screen)
1094
- print "\e[#{@screen_height};#{@screen_width}H"
1095
- else
1096
- draw_screen
1097
- end
913
+ render_full_screen
1098
914
  end
1099
915
 
1100
916
  # ジョブモード再描画をトリガー
@@ -1102,70 +918,6 @@ module Rufio
1102
918
  @job_mode_needs_redraw = true
1103
919
  end
1104
920
 
1105
- # ジョブモード画面を描画(バッファベース描画への橋渡し)
1106
- def draw_job_mode_screen
1107
- return unless @in_job_mode && @job_mode_instance && @job_manager
1108
- return unless @screen && @renderer
1109
-
1110
- # バッファベースの描画を使用
1111
- draw_screen_to_buffer(@screen, nil, nil)
1112
- @renderer.render(@screen)
1113
- print "\e[#{@screen_height};#{@screen_width}H"
1114
-
1115
- STDOUT.flush
1116
- @job_mode_needs_redraw = false
1117
- end
1118
-
1119
- # Noice風の通知を描画
1120
- def draw_notifications
1121
- nm = @notification_manager || @keybind_handler&.notification_manager
1122
- return unless nm
1123
-
1124
- # 期限切れの通知を削除
1125
- nm.expire_old_notifications
1126
-
1127
- notifications = nm.notifications
1128
- return if notifications.empty?
1129
-
1130
- # 通知の幅と位置
1131
- notification_width = 22
1132
- x = @screen_width - notification_width - 2 # 右端から2文字マージン
1133
-
1134
- notifications.each_with_index do |notif, i|
1135
- y = 2 + (i * 5) # 各通知4行 + 間隔1行
1136
-
1137
- # 色設定
1138
- border_color = notif[:border_color] == :green ? "\e[32m" : "\e[31m"
1139
- reset = "\e[0m"
1140
-
1141
- # ステータスアイコン
1142
- icon = notif[:type] == :success ? '✓' : '✗'
1143
-
1144
- # 通知の内容を作成
1145
- name_line = "#{icon} #{notif[:name]}"[0...notification_width - 4]
1146
- status_line = notif[:status_text][0...notification_width - 4]
1147
-
1148
- # 上部ボーダー
1149
- print "\e[#{y};#{x}H#{border_color}╭#{'─' * (notification_width - 2)}╮#{reset}"
1150
-
1151
- # 1行目: アイコン + 名前
1152
- print "\e[#{y + 1};#{x}H#{border_color}│#{reset} #{name_line.ljust(notification_width - 4)} #{border_color}│#{reset}"
1153
-
1154
- # 2行目: ステータス
1155
- print "\e[#{y + 2};#{x}H#{border_color}│#{reset} #{status_line.ljust(notification_width - 6)} #{border_color}│#{reset}"
1156
-
1157
- # Exit code行(失敗時のみ)
1158
- if notif[:type] == :error && notif[:exit_code]
1159
- exit_line = "Exit code: #{notif[:exit_code]}"[0...notification_width - 6]
1160
- print "\e[#{y + 3};#{x}H#{border_color}│#{reset} #{exit_line.ljust(notification_width - 6)} #{border_color}│#{reset}"
1161
- print "\e[#{y + 4};#{x}H#{border_color}╰#{'─' * (notification_width - 2)}╯#{reset}"
1162
- else
1163
- # 下部ボーダー
1164
- print "\e[#{y + 3};#{x}H#{border_color}╰#{'─' * (notification_width - 2)}╯#{reset}"
1165
- end
1166
- end
1167
- end
1168
-
1169
921
  # オーバーレイダイアログを表示してキー入力を待つヘルパーメソッド
1170
922
  # @param title [String] ダイアログタイトル
1171
923
  # @param content_lines [Array<String>] コンテンツ行
@@ -1212,7 +964,7 @@ module Rufio
1212
964
  @renderer.render(@screen)
1213
965
 
1214
966
  # キー入力を待つ
1215
- key = block_given? ? yield : STDIN.getch
967
+ key = block_given? ? yield : KeyInput.getch
1216
968
 
1217
969
  # オーバーレイを無効化
1218
970
  @screen.disable_overlay
@@ -1241,10 +993,8 @@ module Rufio
1241
993
  end
1242
994
  result = @global_search.handle_key(input)
1243
995
  if result.is_a?(Hash) && File.file?(result[:path])
1244
- @keybind_handler.exit_log_viewer_mode if @keybind_handler.log_viewer_mode?
1245
- @keybind_handler.exit_help_mode if @keybind_handler.help_mode?
1246
- @keybind_handler.exit_job_mode if @keybind_handler.in_job_mode?
1247
- if @keybind_handler.send(:navigate_to_directory, File.dirname(result[:path]))
996
+ @keybind_handler.switch_mode(:files)
997
+ if @keybind_handler.navigate_to_directory(File.dirname(result[:path]))
1248
998
  index = @keybind_handler.get_active_entries.index { |entry| entry[:path] == result[:path] }
1249
999
  @keybind_handler.select_index(index) if index
1250
1000
  @keybind_handler.unfocus_preview_pane
@@ -1291,6 +1041,17 @@ module Rufio
1291
1041
  end
1292
1042
  end
1293
1043
 
1044
+ # 全画面をバッファから再描画する
1045
+ def render_full_screen
1046
+ return unless @screen && @renderer
1047
+
1048
+ @renderer.clear
1049
+ @screen.clear
1050
+ draw_screen_to_buffer(@screen, nil, nil)
1051
+ @renderer.render(@screen)
1052
+ print "\e[#{@screen_height};#{@screen_width}H"
1053
+ end
1054
+
1294
1055
  end
1295
1056
  end
1296
1057
 
@@ -134,7 +134,7 @@ module Rufio
134
134
  line = line.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
135
135
  # Remove trailing whitespace
136
136
  line = line.rstrip
137
- rescue EncodingError, ArgumentError => e
137
+ rescue EncodingError, ArgumentError
138
138
  # If encoding fails completely, skip this line
139
139
  wrapped << '[encoding error]'
140
140
  next
@@ -152,7 +152,7 @@ module Rufio
152
152
  wrapped << line
153
153
  next
154
154
  end
155
- rescue ArgumentError => e
155
+ rescue ArgumentError
156
156
  # If display_width fails, just truncate by byte length
157
157
  if line.bytesize <= max_width
158
158
  wrapped << line
@@ -181,7 +181,7 @@ module Rufio
181
181
 
182
182
  # Add remaining characters
183
183
  wrapped << current_line.join unless current_line.empty?
184
- rescue ArgumentError, EncodingError => e
184
+ rescue ArgumentError, EncodingError
185
185
  # If character iteration fails, just add the line truncated
186
186
  truncated = line.byteslice(0, [max_width, line.bytesize].min)
187
187
  wrapped << (truncated || line)
@@ -190,5 +190,28 @@ module Rufio
190
190
 
191
191
  wrapped
192
192
  end
193
+
194
+ # Shorten a path by keeping the tail and prefixing with ellipsis if longer than max_length.
195
+ # Optionally replaces ENV['HOME'] / Dir.home with '~' when tilde: true.
196
+ def shorten_path_from_end(path, max_length, tilde: false, ellipsis: '...')
197
+ return path.to_s if max_length <= 0
198
+
199
+ display = path.to_s
200
+ if tilde
201
+ home = Dir.home rescue ENV['HOME']
202
+ if home && !home.empty?
203
+ if display == home
204
+ display = '~'
205
+ elsif display.start_with?(home + '/')
206
+ display = "~#{display.delete_prefix(home)}"
207
+ end
208
+ end
209
+ end
210
+
211
+ return display if display.length <= max_length
212
+ return display[-max_length..] || display if max_length <= ellipsis.length
213
+
214
+ "#{ellipsis}#{display[-(max_length - ellipsis.length)..]}"
215
+ end
193
216
  end
194
217
  end