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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -0
- data/README.md +74 -3
- data/README_ja.md +71 -2
- data/Rakefile +14 -1
- data/bin/rufio +31 -2
- data/docs/fast_zip_design_ja.md +167 -0
- data/info/help.md +2 -0
- data/info/keybindings.md +3 -1
- data/info/welcome.md +1 -1
- data/lib/rufio/application.rb +12 -1
- data/lib/rufio/archive_controller.rb +211 -0
- data/lib/rufio/background_command_executor.rb +9 -0
- data/lib/rufio/background_status.rb +49 -0
- data/lib/rufio/bookmark_controller.rb +38 -121
- data/lib/rufio/bookmark_manager.rb +77 -331
- data/lib/rufio/command_completion.rb +6 -1
- data/lib/rufio/command_history.rb +5 -2
- data/lib/rufio/command_mode.rb +40 -134
- data/lib/rufio/command_mode_ui.rb +4 -42
- data/lib/rufio/config.rb +88 -136
- data/lib/rufio/config_loader.rb +3 -11
- data/lib/rufio/dialog_renderer.rb +64 -152
- data/lib/rufio/dialog_support.rb +69 -0
- data/lib/rufio/document_text_extractor.rb +167 -0
- data/lib/rufio/file_operation_controller.rb +49 -222
- data/lib/rufio/file_preview.rb +46 -2
- data/lib/rufio/global_search.rb +121 -9
- data/lib/rufio/help_menu.rb +8 -2
- data/lib/rufio/interpreter_resolver.rb +6 -11
- data/lib/rufio/job_manager.rb +2 -5
- data/lib/rufio/key_input.rb +77 -0
- data/lib/rufio/keybind_handler.rb +81 -115
- data/lib/rufio/local_script_scanner.rb +6 -18
- data/lib/rufio/native/aarch64-linux-gnu/librufio_zig.so +0 -0
- data/lib/rufio/native/aarch64-linux-musl/librufio_zig.so +0 -0
- data/lib/rufio/native/aarch64-macos/librufio_zig.dylib +0 -0
- data/lib/rufio/native/aarch64-windows/rufio_zig.dll +0 -0
- data/lib/rufio/native/x86_64-linux-gnu/librufio_zig.so +0 -0
- data/lib/rufio/native/x86_64-linux-musl/librufio_zig.so +0 -0
- data/lib/rufio/native/x86_64-macos/librufio_zig.dylib +0 -0
- data/lib/rufio/native/x86_64-windows/rufio_zig.dll +0 -0
- data/lib/rufio/native_content_search.rb +153 -0
- data/lib/rufio/native_scanner.rb +13 -68
- data/lib/rufio/native_scanner_zig.rb +106 -56
- data/lib/rufio/native_zip.rb +121 -0
- data/lib/rufio/navigation_controller.rb +18 -20
- data/lib/rufio/screen.rb +2 -12
- data/lib/rufio/script_executor.rb +6 -73
- data/lib/rufio/script_file.rb +44 -0
- data/lib/rufio/script_path_manager.rb +9 -43
- data/lib/rufio/script_runner.rb +13 -50
- data/lib/rufio/search_controller.rb +1 -1
- data/lib/rufio/shell_command_completion.rb +1 -5
- data/lib/rufio/shell_init.rb +90 -0
- data/lib/rufio/task_status.rb +5 -1
- data/lib/rufio/terminal_ui.rb +55 -294
- data/lib/rufio/text_utils.rb +26 -3
- data/lib/rufio/ui_renderer.rb +63 -40
- data/lib/rufio/version.rb +1 -1
- data/lib/rufio/zip_archive.rb +314 -0
- data/lib/rufio/zip_extractor.rb +346 -0
- data/lib/rufio/zip_writer.rb +268 -0
- data/lib/rufio/zoxide_integration.rb +5 -43
- data/lib/rufio.rb +5 -0
- data/lib_zig/rufio_native/Makefile +54 -17
- data/lib_zig/rufio_native/bench.sh +36 -0
- data/lib_zig/rufio_native/build.zig +32 -35
- data/lib_zig/rufio_native/src/cli.zig +99 -0
- data/lib_zig/rufio_native/src/main.zig +187 -83
- data/lib_zig/rufio_native/src/os.zig +889 -0
- data/lib_zig/rufio_native/src/search.zig +919 -0
- data/lib_zig/rufio_native/src/search_test.zig +88 -0
- data/lib_zig/rufio_native/src/zip.zig +482 -0
- metadata +125 -3
- data/lib/rufio/native/rufio_zig.bundle +0 -0
data/lib/rufio/terminal_ui.rb
CHANGED
|
@@ -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
|
-
|
|
23
|
-
|
|
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
|
-
#
|
|
31
|
-
|
|
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
|
-
@
|
|
142
|
-
|
|
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
|
|
199
|
+
# Windows: IO.select を使わずキューから取得。
|
|
242
200
|
def read_next_input_byte
|
|
243
201
|
if windows?
|
|
244
|
-
# ConPTY ではバイト到着が遅れることがあるため 5ms では ESC
|
|
245
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
433
|
-
if @background_executor
|
|
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
|
-
|
|
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
|
-
#
|
|
761
|
-
|
|
762
|
-
|
|
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 -
|
|
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
|
-
|
|
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
|
-
|
|
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 :
|
|
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.
|
|
1245
|
-
|
|
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
|
|
data/lib/rufio/text_utils.rb
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|