rufio 0.50.0 → 0.60.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 +26 -0
- data/README.md +32 -36
- data/examples/config.yml +8 -0
- data/lib/rufio/command_completion.rb +20 -5
- data/lib/rufio/command_mode.rb +98 -5
- data/lib/rufio/config_loader.rb +37 -0
- data/lib/rufio/job_manager.rb +128 -0
- data/lib/rufio/job_mode.rb +146 -0
- data/lib/rufio/keybind_handler.rb +243 -232
- data/lib/rufio/notification_manager.rb +77 -0
- data/lib/rufio/script_config_loader.rb +101 -0
- data/lib/rufio/script_path_manager.rb +386 -0
- data/lib/rufio/script_runner.rb +216 -0
- data/lib/rufio/task_status.rb +118 -0
- data/lib/rufio/terminal_ui.rb +181 -495
- data/lib/rufio/version.rb +1 -1
- data/lib/rufio.rb +8 -4
- data/scripts/test_jobs/build_simulation.sh +29 -0
- data/scripts/test_jobs/deploy_simulation.sh +37 -0
- data/scripts/test_jobs/quick.sh +11 -0
- data/scripts/test_jobs/random_result.sh +23 -0
- data/scripts/test_jobs/slow_fail.sh +10 -0
- data/scripts/test_jobs/slow_success.sh +10 -0
- data/scripts/test_jobs/very_slow.sh +19 -0
- metadata +17 -7
- data/docs/file-preview-optimization-analysis.md +0 -759
- data/docs/file-preview-performance-issue-FIXED.md +0 -547
- data/lib/rufio/project_command.rb +0 -147
- data/lib/rufio/project_log.rb +0 -68
- data/lib/rufio/project_mode.rb +0 -58
data/lib/rufio/terminal_ui.rb
CHANGED
|
@@ -53,15 +53,14 @@ module Rufio
|
|
|
53
53
|
history_file = File.join(Dir.home, '.rufio', 'command_history.txt')
|
|
54
54
|
FileUtils.mkdir_p(File.dirname(history_file))
|
|
55
55
|
@command_history = CommandHistory.new(history_file, max_size: ConfigLoader.command_history_size)
|
|
56
|
-
@command_completion = CommandCompletion.new(@command_history)
|
|
56
|
+
@command_completion = CommandCompletion.new(@command_history, @command_mode)
|
|
57
57
|
|
|
58
|
-
#
|
|
59
|
-
@
|
|
60
|
-
@
|
|
61
|
-
@
|
|
62
|
-
@
|
|
63
|
-
@
|
|
64
|
-
@project_mode_needs_redraw = false
|
|
58
|
+
# Job mode
|
|
59
|
+
@job_mode_instance = nil
|
|
60
|
+
@job_manager = nil
|
|
61
|
+
@notification_manager = nil
|
|
62
|
+
@in_job_mode = false
|
|
63
|
+
@job_mode_needs_redraw = false
|
|
65
64
|
|
|
66
65
|
# Preview cache
|
|
67
66
|
@preview_cache = {}
|
|
@@ -88,6 +87,9 @@ module Rufio
|
|
|
88
87
|
# コマンドモードにバックグラウンドエグゼキュータを設定
|
|
89
88
|
@command_mode.background_executor = @background_executor if @background_executor
|
|
90
89
|
|
|
90
|
+
# スクリプトランナーを設定(ジョブモードと連携)
|
|
91
|
+
setup_script_runner
|
|
92
|
+
|
|
91
93
|
@running = true
|
|
92
94
|
setup_terminal
|
|
93
95
|
|
|
@@ -119,8 +121,6 @@ module Rufio
|
|
|
119
121
|
# レンダラーの前フレーム情報をリセット(差分レンダリングを強制的に全体描画にする)
|
|
120
122
|
@renderer.clear
|
|
121
123
|
@screen.clear
|
|
122
|
-
# プロジェクトモードの場合は再描画フラグを立てる
|
|
123
|
-
@project_mode_needs_redraw = true if @in_project_mode
|
|
124
124
|
draw_screen_to_buffer(@screen, nil, nil)
|
|
125
125
|
@renderer.render(@screen)
|
|
126
126
|
# カーソルを画面外に移動
|
|
@@ -128,6 +128,23 @@ module Rufio
|
|
|
128
128
|
end
|
|
129
129
|
end
|
|
130
130
|
|
|
131
|
+
# スクリプトランナーを設定
|
|
132
|
+
def setup_script_runner
|
|
133
|
+
return unless @keybind_handler
|
|
134
|
+
|
|
135
|
+
# KeybindHandlerからジョブマネージャーを取得
|
|
136
|
+
job_manager = @keybind_handler.job_manager
|
|
137
|
+
|
|
138
|
+
# 設定からスクリプトパスを取得
|
|
139
|
+
script_paths = ConfigLoader.script_paths
|
|
140
|
+
|
|
141
|
+
# CommandModeにスクリプトランナーを設定
|
|
142
|
+
@command_mode.setup_script_runner(
|
|
143
|
+
script_paths: script_paths,
|
|
144
|
+
job_manager: job_manager
|
|
145
|
+
)
|
|
146
|
+
end
|
|
147
|
+
|
|
131
148
|
private
|
|
132
149
|
|
|
133
150
|
def setup_terminal
|
|
@@ -311,9 +328,9 @@ module Rufio
|
|
|
311
328
|
# move cursor to top of screen (don't clear)
|
|
312
329
|
print "\e[H"
|
|
313
330
|
|
|
314
|
-
#
|
|
315
|
-
if @
|
|
316
|
-
|
|
331
|
+
# ジョブモードの場合は専用の画面を描画
|
|
332
|
+
if @in_job_mode
|
|
333
|
+
draw_job_mode_screen
|
|
317
334
|
return
|
|
318
335
|
end
|
|
319
336
|
|
|
@@ -347,19 +364,18 @@ module Rufio
|
|
|
347
364
|
# move cursor to invisible position
|
|
348
365
|
print "\e[#{@screen_height};#{@screen_width}H"
|
|
349
366
|
end
|
|
367
|
+
|
|
368
|
+
# 通知を描画(右上にオーバーレイ)
|
|
369
|
+
draw_notifications
|
|
350
370
|
end
|
|
351
371
|
|
|
352
372
|
# Phase 3: Screenバッファに描画する新しいメソッド
|
|
353
373
|
def draw_screen_to_buffer(screen, notification_message = nil, fps = nil)
|
|
354
|
-
#
|
|
355
|
-
if @
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
# ちらつき防止: 再描画が必要な時だけ描画
|
|
360
|
-
if @project_mode_needs_redraw
|
|
361
|
-
draw_project_mode_screen
|
|
362
|
-
@project_mode_needs_redraw = false
|
|
374
|
+
# ジョブモードの場合は既存の描画メソッドを使用
|
|
375
|
+
if @in_job_mode
|
|
376
|
+
if @job_mode_needs_redraw
|
|
377
|
+
draw_job_mode_screen
|
|
378
|
+
@job_mode_needs_redraw = false
|
|
363
379
|
end
|
|
364
380
|
return
|
|
365
381
|
end
|
|
@@ -901,9 +917,15 @@ module Rufio
|
|
|
901
917
|
end
|
|
902
918
|
bookmark_text = bookmark_parts.join(" ")
|
|
903
919
|
|
|
904
|
-
# 右側の情報: コマンド実行ランプ | FPS(test modeの時のみ)| ?:help
|
|
920
|
+
# 右側の情報: ジョブ数 | コマンド実行ランプ | FPS(test modeの時のみ)| ?:help
|
|
905
921
|
right_parts = []
|
|
906
922
|
|
|
923
|
+
# ジョブ数を表示(ジョブがある場合のみ)
|
|
924
|
+
if @keybind_handler.has_jobs?
|
|
925
|
+
job_text = @keybind_handler.job_status_bar_text
|
|
926
|
+
right_parts << "[#{job_text}]" if job_text
|
|
927
|
+
end
|
|
928
|
+
|
|
907
929
|
# バックグラウンドコマンドの実行状態をランプで表示
|
|
908
930
|
if @background_executor
|
|
909
931
|
if @background_executor.running?
|
|
@@ -1154,11 +1176,6 @@ module Rufio
|
|
|
1154
1176
|
@command_mode_active
|
|
1155
1177
|
end
|
|
1156
1178
|
|
|
1157
|
-
# プロジェクトモードの再描画をトリガー
|
|
1158
|
-
def trigger_project_mode_redraw
|
|
1159
|
-
@project_mode_needs_redraw = true if @in_project_mode
|
|
1160
|
-
end
|
|
1161
|
-
|
|
1162
1179
|
# コマンド入力を処理
|
|
1163
1180
|
def handle_command_input(input)
|
|
1164
1181
|
case input
|
|
@@ -1204,7 +1221,10 @@ module Rufio
|
|
|
1204
1221
|
# コマンド履歴に追加
|
|
1205
1222
|
@command_history.add(command_string)
|
|
1206
1223
|
|
|
1207
|
-
|
|
1224
|
+
# 現在のディレクトリを取得
|
|
1225
|
+
working_dir = @directory_listing&.current_path || Dir.pwd
|
|
1226
|
+
|
|
1227
|
+
result = @command_mode.execute(command_string, working_dir: working_dir)
|
|
1208
1228
|
|
|
1209
1229
|
# バックグラウンドコマンドの場合は結果表示をスキップ
|
|
1210
1230
|
# (完了通知は別途メインループで表示される)
|
|
@@ -1214,12 +1234,7 @@ module Rufio
|
|
|
1214
1234
|
end
|
|
1215
1235
|
|
|
1216
1236
|
# 画面を再描画
|
|
1217
|
-
|
|
1218
|
-
# プロジェクトモードの場合は再描画フラグを立てる
|
|
1219
|
-
@project_mode_needs_redraw = true
|
|
1220
|
-
else
|
|
1221
|
-
draw_screen
|
|
1222
|
-
end
|
|
1237
|
+
draw_screen
|
|
1223
1238
|
end
|
|
1224
1239
|
|
|
1225
1240
|
# Tab補完を処理
|
|
@@ -1297,12 +1312,7 @@ module Rufio
|
|
|
1297
1312
|
@dialog_renderer.clear_area(x, y, width, height)
|
|
1298
1313
|
|
|
1299
1314
|
# 画面を再描画
|
|
1300
|
-
|
|
1301
|
-
# プロジェクトモードの場合は再描画フラグを立てる
|
|
1302
|
-
@project_mode_needs_redraw = true
|
|
1303
|
-
else
|
|
1304
|
-
draw_screen
|
|
1305
|
-
end
|
|
1315
|
+
draw_screen
|
|
1306
1316
|
end
|
|
1307
1317
|
|
|
1308
1318
|
# Show info notices from the info directory if any are unread
|
|
@@ -1354,35 +1364,37 @@ module Rufio
|
|
|
1354
1364
|
@dialog_renderer.clear_area(x, y, width, height)
|
|
1355
1365
|
|
|
1356
1366
|
# Redraw the screen
|
|
1357
|
-
|
|
1358
|
-
# プロジェクトモードの場合は再描画フラグを立てる
|
|
1359
|
-
@project_mode_needs_redraw = true
|
|
1360
|
-
else
|
|
1361
|
-
draw_screen
|
|
1362
|
-
end
|
|
1367
|
+
draw_screen
|
|
1363
1368
|
end
|
|
1364
1369
|
|
|
1365
|
-
#
|
|
1366
|
-
def
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1370
|
+
# ログモードに入る(廃止済み: 空のメソッド)
|
|
1371
|
+
def enter_log_mode(_project_log)
|
|
1372
|
+
# プロジェクトモード廃止により何もしない
|
|
1373
|
+
end
|
|
1374
|
+
|
|
1375
|
+
# ログモードを終了(廃止済み: 空のメソッド)
|
|
1376
|
+
def exit_log_mode
|
|
1377
|
+
# プロジェクトモード廃止により何もしない
|
|
1378
|
+
end
|
|
1379
|
+
|
|
1380
|
+
# ジョブモードを設定
|
|
1381
|
+
def set_job_mode(job_mode, job_manager, notification_manager)
|
|
1382
|
+
@job_mode_instance = job_mode
|
|
1383
|
+
@job_manager = job_manager
|
|
1384
|
+
@notification_manager = notification_manager
|
|
1385
|
+
@in_job_mode = true
|
|
1372
1386
|
# 画面を一度クリアしてレンダラーをリセット
|
|
1373
1387
|
print "\e[2J\e[H"
|
|
1374
1388
|
@renderer.clear if @renderer
|
|
1375
1389
|
# 再描画フラグを立てる
|
|
1376
|
-
@
|
|
1390
|
+
@job_mode_needs_redraw = true
|
|
1377
1391
|
end
|
|
1378
1392
|
|
|
1379
|
-
#
|
|
1380
|
-
def
|
|
1381
|
-
@
|
|
1382
|
-
@
|
|
1383
|
-
@
|
|
1384
|
-
@project_command = nil
|
|
1385
|
-
@project_log = nil
|
|
1393
|
+
# ジョブモードを終了
|
|
1394
|
+
def exit_job_mode
|
|
1395
|
+
@in_job_mode = false
|
|
1396
|
+
@job_mode_instance = nil
|
|
1397
|
+
@job_manager = nil
|
|
1386
1398
|
# バッファベースの全画面再描画を使用
|
|
1387
1399
|
update_screen_size
|
|
1388
1400
|
print "\e[2J\e[H"
|
|
@@ -1397,290 +1409,141 @@ module Rufio
|
|
|
1397
1409
|
end
|
|
1398
1410
|
end
|
|
1399
1411
|
|
|
1400
|
-
#
|
|
1401
|
-
def
|
|
1402
|
-
@
|
|
1403
|
-
@project_log = project_log
|
|
1404
|
-
# 画面を一度クリアしてレンダラーをリセット
|
|
1405
|
-
print "\e[2J\e[H"
|
|
1406
|
-
@renderer.clear if @renderer
|
|
1407
|
-
# 再描画フラグを立てる
|
|
1408
|
-
@project_mode_needs_redraw = true
|
|
1409
|
-
end
|
|
1410
|
-
|
|
1411
|
-
# プロジェクトモード画面を描画
|
|
1412
|
-
def draw_project_mode_screen
|
|
1413
|
-
# header
|
|
1414
|
-
print "\e[1;1H" # Move to top-left
|
|
1415
|
-
header = @in_log_mode ? "📋 Project Mode - Logs" : "📁 Project Mode - Bookmarks"
|
|
1416
|
-
print "\e[44m\e[97m#{header.ljust(@screen_width)}\e[0m\n"
|
|
1417
|
-
print "\e[0m#{' ' * @screen_width}\n"
|
|
1418
|
-
|
|
1419
|
-
# calculate dimensions
|
|
1420
|
-
content_height = @screen_height - HEADER_FOOTER_MARGIN
|
|
1421
|
-
left_width = (@screen_width * LEFT_PANEL_RATIO).to_i
|
|
1422
|
-
right_width = @screen_width - left_width
|
|
1423
|
-
|
|
1424
|
-
if @in_log_mode
|
|
1425
|
-
# ログモード: ログファイル一覧と内容
|
|
1426
|
-
draw_log_list(left_width, content_height)
|
|
1427
|
-
draw_log_preview(right_width, content_height, left_width)
|
|
1428
|
-
else
|
|
1429
|
-
# ブックマークモード: プロジェクト一覧と詳細
|
|
1430
|
-
draw_bookmark_list(left_width, content_height)
|
|
1431
|
-
draw_bookmark_detail(right_width, content_height, left_width)
|
|
1432
|
-
end
|
|
1433
|
-
|
|
1434
|
-
# footer(通常モードと同じスタイル)
|
|
1435
|
-
footer_line = @screen_height
|
|
1436
|
-
print "\e[#{footer_line};1H"
|
|
1437
|
-
footer_text = if @in_log_mode
|
|
1438
|
-
"ESC:exit log j/k:move"
|
|
1439
|
-
else
|
|
1440
|
-
"SPACE:select l:logs ::cmd r:rename d:delete ESC:exit j/k:move"
|
|
1441
|
-
end
|
|
1442
|
-
# 文字列を確実に画面幅に合わせる
|
|
1443
|
-
footer_content = footer_text.ljust(@screen_width)[0...@screen_width]
|
|
1444
|
-
print "\e[7m#{footer_content}\e[0m"
|
|
1445
|
-
|
|
1446
|
-
# move cursor to invisible position
|
|
1447
|
-
print "\e[#{@screen_height};#{@screen_width}H"
|
|
1412
|
+
# ジョブモード再描画をトリガー
|
|
1413
|
+
def trigger_job_mode_redraw
|
|
1414
|
+
@job_mode_needs_redraw = true
|
|
1448
1415
|
end
|
|
1449
1416
|
|
|
1450
|
-
#
|
|
1451
|
-
def
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1417
|
+
# ジョブモード画面を描画
|
|
1418
|
+
def draw_job_mode_screen
|
|
1419
|
+
return unless @in_job_mode && @job_mode_instance && @job_manager
|
|
1420
|
+
|
|
1421
|
+
# ヘッダー
|
|
1422
|
+
job_count = @job_manager.job_count
|
|
1423
|
+
header = "Running Jobs (#{job_count})"
|
|
1424
|
+
header_line = header.center(@screen_width)
|
|
1425
|
+
print "\e[1;1H\e[1;36m#{header_line}\e[0m"
|
|
1426
|
+
|
|
1427
|
+
# 区切り線
|
|
1428
|
+
separator = "━" * @screen_width
|
|
1429
|
+
print "\e[2;1H\e[36m#{separator}\e[0m"
|
|
1430
|
+
|
|
1431
|
+
# ジョブ一覧
|
|
1432
|
+
jobs = @job_manager.jobs
|
|
1433
|
+
selected_index = @job_mode_instance.selected_index
|
|
1434
|
+
|
|
1435
|
+
jobs.each_with_index do |job, i|
|
|
1436
|
+
line_num = i + 3
|
|
1437
|
+
break if line_num >= @screen_height - 2
|
|
1438
|
+
|
|
1439
|
+
# ステータスアイコン
|
|
1440
|
+
icon = job.status_icon
|
|
1441
|
+
icon_color = case job.status
|
|
1442
|
+
when :running then "\e[33m" # Yellow
|
|
1443
|
+
when :completed then "\e[32m" # Green
|
|
1444
|
+
when :failed then "\e[31m" # Red
|
|
1445
|
+
else "\e[37m" # White
|
|
1446
|
+
end
|
|
1479
1447
|
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1448
|
+
# ジョブ名とパス
|
|
1449
|
+
name = job.name
|
|
1450
|
+
path = "(#{job.path})"
|
|
1451
|
+
duration = job.formatted_duration
|
|
1452
|
+
duration_text = duration.empty? ? "" : "[#{duration}]"
|
|
1453
|
+
|
|
1454
|
+
# ステータステキスト
|
|
1455
|
+
status_text = case job.status
|
|
1456
|
+
when :running then "Running"
|
|
1457
|
+
when :completed then "Done"
|
|
1458
|
+
when :failed then "Failed"
|
|
1459
|
+
when :waiting then "Waiting"
|
|
1460
|
+
when :cancelled then "Cancelled"
|
|
1461
|
+
else ""
|
|
1462
|
+
end
|
|
1463
|
+
|
|
1464
|
+
# 行を構築
|
|
1465
|
+
line_content = "#{icon} #{name} #{path}".ljust(40)
|
|
1466
|
+
line_content += "#{duration_text.ljust(12)} #{status_text}"
|
|
1467
|
+
line_content = line_content[0...@screen_width - 1].ljust(@screen_width - 1)
|
|
1468
|
+
|
|
1469
|
+
# 選択状態の場合はハイライト
|
|
1470
|
+
if i == selected_index
|
|
1471
|
+
print "\e[#{line_num};1H\e[7m#{icon_color}#{line_content}\e[0m"
|
|
1484
1472
|
else
|
|
1485
|
-
#
|
|
1486
|
-
if is_project_selected
|
|
1487
|
-
print "\e[#{line_num};1H\e[42m\e[30m#{line_content[0...width]}\e[0m"
|
|
1488
|
-
else
|
|
1489
|
-
print "\e[#{line_num};1H#{line_content[0...width]}"
|
|
1490
|
-
end
|
|
1473
|
+
print "\e[#{line_num};1H#{icon_color}#{line_content}\e[0m"
|
|
1491
1474
|
end
|
|
1492
1475
|
end
|
|
1493
1476
|
|
|
1494
|
-
#
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
line_num = CONTENT_START_LINE + bookmarks.length + i
|
|
1498
|
-
print "\e[#{line_num};1H#{' ' * width}"
|
|
1477
|
+
# 空行をクリア
|
|
1478
|
+
((jobs.length + 3)...(@screen_height - 2)).each do |line_num|
|
|
1479
|
+
print "\e[#{line_num};1H#{' ' * @screen_width}"
|
|
1499
1480
|
end
|
|
1500
|
-
end
|
|
1501
|
-
|
|
1502
|
-
# ブックマーク詳細を描画
|
|
1503
|
-
def draw_bookmark_detail(width, height, left_offset)
|
|
1504
|
-
bookmarks = @project_mode.list_bookmarks
|
|
1505
|
-
current_index = @keybind_handler.current_index
|
|
1506
1481
|
|
|
1507
|
-
|
|
1482
|
+
# フッター
|
|
1483
|
+
footer_line = @screen_height - 1
|
|
1484
|
+
footer_separator = "━" * @screen_width
|
|
1485
|
+
print "\e[#{footer_line};1H\e[36m#{footer_separator}\e[0m"
|
|
1508
1486
|
|
|
1509
|
-
|
|
1510
|
-
|
|
1487
|
+
# ヘルプライン
|
|
1488
|
+
help_line = @screen_height
|
|
1489
|
+
help_text = "[Space] View Log | [x] Cancel | [Esc] Back to Files"
|
|
1490
|
+
help_content = help_text.center(@screen_width)
|
|
1491
|
+
print "\e[#{help_line};1H\e[7m#{help_content}\e[0m"
|
|
1511
1492
|
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
"Project: #{bookmark[:name]}",
|
|
1515
|
-
"Path: #{path}",
|
|
1516
|
-
"",
|
|
1517
|
-
"Directory contents:",
|
|
1518
|
-
""
|
|
1519
|
-
]
|
|
1520
|
-
|
|
1521
|
-
# ディレクトリが存在する場合、内容を表示
|
|
1522
|
-
if Dir.exist?(path)
|
|
1523
|
-
begin
|
|
1524
|
-
entries = Dir.entries(path).reject { |e| e == '.' || e == '..' }.sort
|
|
1525
|
-
|
|
1526
|
-
# 最大表示数を計算(ヘッダー分を引く)
|
|
1527
|
-
max_entries = height - details.length
|
|
1528
|
-
|
|
1529
|
-
entries.take(max_entries).each do |entry|
|
|
1530
|
-
full_path = File.join(path, entry)
|
|
1531
|
-
icon = File.directory?(full_path) ? '📁' : '📄'
|
|
1532
|
-
details << " #{icon} #{entry}"
|
|
1533
|
-
end
|
|
1534
|
-
|
|
1535
|
-
# 表示しきれない場合
|
|
1536
|
-
if entries.length > max_entries
|
|
1537
|
-
details << " ... and #{entries.length - max_entries} more"
|
|
1538
|
-
end
|
|
1539
|
-
rescue => e
|
|
1540
|
-
details << " Error reading directory: #{e.message}"
|
|
1541
|
-
end
|
|
1542
|
-
else
|
|
1543
|
-
details << " Directory does not exist"
|
|
1544
|
-
end
|
|
1545
|
-
|
|
1546
|
-
# 各行にセパレータと内容を表示(通常モードと同じ)
|
|
1547
|
-
height.times do |i|
|
|
1548
|
-
line_num = CONTENT_START_LINE + i
|
|
1549
|
-
|
|
1550
|
-
# セパレータを表示
|
|
1551
|
-
cursor_position = left_offset + CURSOR_OFFSET
|
|
1552
|
-
print "\e[#{line_num};#{cursor_position}H"
|
|
1553
|
-
print '│'
|
|
1554
|
-
|
|
1555
|
-
# 右画面の内容を表示
|
|
1556
|
-
if i < details.length
|
|
1557
|
-
line = details[i]
|
|
1558
|
-
safe_width = width - 2
|
|
1559
|
-
content = " #{line}"
|
|
1560
|
-
content = content[0...safe_width] if content.length > safe_width
|
|
1561
|
-
print content
|
|
1562
|
-
|
|
1563
|
-
# 残りをスペースで埋める
|
|
1564
|
-
remaining = safe_width - content.length
|
|
1565
|
-
print ' ' * remaining if remaining > 0
|
|
1566
|
-
else
|
|
1567
|
-
# 空行
|
|
1568
|
-
print ' ' * (width - 2)
|
|
1569
|
-
end
|
|
1570
|
-
end
|
|
1493
|
+
STDOUT.flush
|
|
1494
|
+
@job_mode_needs_redraw = false
|
|
1571
1495
|
end
|
|
1572
1496
|
|
|
1573
|
-
#
|
|
1574
|
-
def
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
print "\e[#{CONTENT_START_LINE};1H"
|
|
1579
|
-
|
|
1580
|
-
if log_files.empty?
|
|
1581
|
-
print " No log files found"
|
|
1582
|
-
(height - 1).times { puts ' ' * width }
|
|
1583
|
-
return
|
|
1584
|
-
end
|
|
1585
|
-
|
|
1586
|
-
log_files.each_with_index do |filename, index|
|
|
1587
|
-
line_num = CONTENT_START_LINE + index
|
|
1588
|
-
break if index >= height
|
|
1497
|
+
# Noice風の通知を描画
|
|
1498
|
+
def draw_notifications
|
|
1499
|
+
nm = @notification_manager || @keybind_handler&.notification_manager
|
|
1500
|
+
return unless nm
|
|
1589
1501
|
|
|
1590
|
-
|
|
1591
|
-
|
|
1502
|
+
# 期限切れの通知を削除
|
|
1503
|
+
nm.expire_old_notifications
|
|
1592
1504
|
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
else
|
|
1596
|
-
print "\e[#{line_num};1H #{display_name[0...width-3]}"
|
|
1597
|
-
end
|
|
1598
|
-
end
|
|
1505
|
+
notifications = nm.notifications
|
|
1506
|
+
return if notifications.empty?
|
|
1599
1507
|
|
|
1600
|
-
#
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
line_num = CONTENT_START_LINE + log_files.length + i
|
|
1604
|
-
print "\e[#{line_num};1H#{' ' * width}"
|
|
1605
|
-
end
|
|
1606
|
-
end
|
|
1508
|
+
# 通知の幅と位置
|
|
1509
|
+
notification_width = 22
|
|
1510
|
+
x = @screen_width - notification_width - 2 # 右端から2文字マージン
|
|
1607
1511
|
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
log_files = @project_log.list_log_files
|
|
1611
|
-
current_index = @keybind_handler.current_index
|
|
1512
|
+
notifications.each_with_index do |notif, i|
|
|
1513
|
+
y = 2 + (i * 5) # 各通知4行 + 間隔1行
|
|
1612
1514
|
|
|
1613
|
-
|
|
1515
|
+
# 色設定
|
|
1516
|
+
border_color = notif[:border_color] == :green ? "\e[32m" : "\e[31m"
|
|
1517
|
+
reset = "\e[0m"
|
|
1614
1518
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1519
|
+
# ステータスアイコン
|
|
1520
|
+
icon = notif[:type] == :success ? '✓' : '✗'
|
|
1617
1521
|
|
|
1618
|
-
|
|
1522
|
+
# 通知の内容を作成
|
|
1523
|
+
name_line = "#{icon} #{notif[:name]}"[0...notification_width - 4]
|
|
1524
|
+
status_line = notif[:status_text][0...notification_width - 4]
|
|
1619
1525
|
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
line_num = CONTENT_START_LINE + i
|
|
1526
|
+
# 上部ボーダー
|
|
1527
|
+
print "\e[#{y};#{x}H#{border_color}╭#{'─' * (notification_width - 2)}╮#{reset}"
|
|
1623
1528
|
|
|
1624
|
-
#
|
|
1625
|
-
|
|
1626
|
-
print "\e[#{line_num};#{cursor_position}H"
|
|
1627
|
-
print '│'
|
|
1529
|
+
# 1行目: アイコン + 名前
|
|
1530
|
+
print "\e[#{y + 1};#{x}H#{border_color}│#{reset} #{name_line.ljust(notification_width - 4)} #{border_color}│#{reset}"
|
|
1628
1531
|
|
|
1629
|
-
#
|
|
1630
|
-
|
|
1631
|
-
line = lines[i]
|
|
1632
|
-
safe_width = width - 2
|
|
1633
|
-
content = " #{line}"
|
|
1634
|
-
content = content[0...safe_width] if content.length > safe_width
|
|
1635
|
-
print content
|
|
1532
|
+
# 2行目: ステータス
|
|
1533
|
+
print "\e[#{y + 2};#{x}H#{border_color}│#{reset} #{status_line.ljust(notification_width - 6)} #{border_color}│#{reset}"
|
|
1636
1534
|
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1535
|
+
# Exit code行(失敗時のみ)
|
|
1536
|
+
if notif[:type] == :error && notif[:exit_code]
|
|
1537
|
+
exit_line = "Exit code: #{notif[:exit_code]}"[0...notification_width - 6]
|
|
1538
|
+
print "\e[#{y + 3};#{x}H#{border_color}│#{reset} #{exit_line.ljust(notification_width - 6)} #{border_color}│#{reset}"
|
|
1539
|
+
print "\e[#{y + 4};#{x}H#{border_color}╰#{'─' * (notification_width - 2)}╯#{reset}"
|
|
1640
1540
|
else
|
|
1641
|
-
#
|
|
1642
|
-
print
|
|
1541
|
+
# 下部ボーダー
|
|
1542
|
+
print "\e[#{y + 3};#{x}H#{border_color}╰#{'─' * (notification_width - 2)}╯#{reset}"
|
|
1643
1543
|
end
|
|
1644
1544
|
end
|
|
1645
1545
|
end
|
|
1646
1546
|
|
|
1647
|
-
# ログモードを終了してプロジェクトモードに戻る
|
|
1648
|
-
def exit_log_mode
|
|
1649
|
-
@in_log_mode = false
|
|
1650
|
-
# 画面を一度クリアしてレンダラーをリセット
|
|
1651
|
-
print "\e[2J\e[H"
|
|
1652
|
-
@renderer.clear if @renderer
|
|
1653
|
-
# 再描画フラグを立てる
|
|
1654
|
-
@project_mode_needs_redraw = true
|
|
1655
|
-
end
|
|
1656
|
-
|
|
1657
|
-
# プロジェクト未選択メッセージ
|
|
1658
|
-
def show_project_not_selected_message
|
|
1659
|
-
content_lines = [
|
|
1660
|
-
'',
|
|
1661
|
-
'Please select a project first by pressing SPACE',
|
|
1662
|
-
'',
|
|
1663
|
-
'Press any key to continue...'
|
|
1664
|
-
]
|
|
1665
|
-
|
|
1666
|
-
width = 50
|
|
1667
|
-
height = 8
|
|
1668
|
-
x, y = @dialog_renderer.calculate_center(width, height)
|
|
1669
|
-
|
|
1670
|
-
@dialog_renderer.draw_floating_window(x, y, width, height, 'No Project Selected', content_lines, {
|
|
1671
|
-
border_color: "\e[33m", # Yellow (warning)
|
|
1672
|
-
title_color: "\e[1;33m", # Bold yellow
|
|
1673
|
-
content_color: "\e[37m" # White
|
|
1674
|
-
})
|
|
1675
|
-
|
|
1676
|
-
require 'io/console'
|
|
1677
|
-
IO.console.getch
|
|
1678
|
-
@dialog_renderer.clear_area(x, y, width, height)
|
|
1679
|
-
|
|
1680
|
-
# 画面を再描画
|
|
1681
|
-
refresh_display
|
|
1682
|
-
end
|
|
1683
|
-
|
|
1684
1547
|
# ヘルプダイアログを表示
|
|
1685
1548
|
def show_help_dialog
|
|
1686
1549
|
content_lines = [
|
|
@@ -1702,7 +1565,7 @@ module Rufio
|
|
|
1702
1565
|
'z - Zoxide navigation',
|
|
1703
1566
|
'0 - Go to start directory',
|
|
1704
1567
|
'1-9 - Go to bookmark',
|
|
1705
|
-
'
|
|
1568
|
+
'J - Job mode',
|
|
1706
1569
|
': - Command mode',
|
|
1707
1570
|
'q - Quit',
|
|
1708
1571
|
''
|
|
@@ -1743,183 +1606,6 @@ module Rufio
|
|
|
1743
1606
|
refresh_display
|
|
1744
1607
|
end
|
|
1745
1608
|
|
|
1746
|
-
# プロジェクトモードでコマンドを実行
|
|
1747
|
-
def activate_project_command_mode(project_mode, project_command, project_log)
|
|
1748
|
-
return unless project_mode.selected_path
|
|
1749
|
-
|
|
1750
|
-
# スクリプトまたはコマンドを選択
|
|
1751
|
-
choice = show_script_or_command_dialog(project_mode.selected_name, project_command)
|
|
1752
|
-
return unless choice
|
|
1753
|
-
|
|
1754
|
-
command = nil
|
|
1755
|
-
result = nil
|
|
1756
|
-
|
|
1757
|
-
if choice[:type] == :script
|
|
1758
|
-
# スクリプトを実行
|
|
1759
|
-
command = "ruby script: #{choice[:value]}"
|
|
1760
|
-
result = project_command.execute_script(choice[:value], project_mode.selected_path)
|
|
1761
|
-
else
|
|
1762
|
-
# 通常のコマンドを実行
|
|
1763
|
-
command = choice[:value]
|
|
1764
|
-
result = project_command.execute(command, project_mode.selected_path)
|
|
1765
|
-
end
|
|
1766
|
-
|
|
1767
|
-
# ログを保存
|
|
1768
|
-
project_log.save(project_mode.selected_name, command, result[:output])
|
|
1769
|
-
|
|
1770
|
-
# 結果を表示
|
|
1771
|
-
show_project_command_result_dialog(command, result)
|
|
1772
|
-
|
|
1773
|
-
# 画面を再描画
|
|
1774
|
-
refresh_display
|
|
1775
|
-
end
|
|
1776
|
-
|
|
1777
|
-
# スクリプトまたはコマンドを選択
|
|
1778
|
-
def show_script_or_command_dialog(project_name, project_command)
|
|
1779
|
-
scripts = project_command.list_scripts
|
|
1780
|
-
|
|
1781
|
-
content_lines = [
|
|
1782
|
-
'',
|
|
1783
|
-
"Project: #{project_name}",
|
|
1784
|
-
''
|
|
1785
|
-
]
|
|
1786
|
-
|
|
1787
|
-
if scripts.empty?
|
|
1788
|
-
content_lines << 'No scripts found in scripts directory'
|
|
1789
|
-
content_lines << " (#{project_command.scripts_dir})"
|
|
1790
|
-
content_lines << ''
|
|
1791
|
-
content_lines << 'Press C to enter custom command'
|
|
1792
|
-
content_lines << 'Press ESC to cancel'
|
|
1793
|
-
else
|
|
1794
|
-
content_lines << 'Available scripts:'
|
|
1795
|
-
content_lines << ''
|
|
1796
|
-
scripts.each_with_index do |script, index|
|
|
1797
|
-
content_lines << " #{index + 1}. #{script}"
|
|
1798
|
-
end
|
|
1799
|
-
content_lines << ''
|
|
1800
|
-
content_lines << 'Press 1-9 to select script'
|
|
1801
|
-
content_lines << 'Press C to enter custom command'
|
|
1802
|
-
content_lines << 'Press ESC to cancel'
|
|
1803
|
-
end
|
|
1804
|
-
|
|
1805
|
-
width = 70
|
|
1806
|
-
height = [content_lines.length + 4, 25].min
|
|
1807
|
-
x, y = @dialog_renderer.calculate_center(width, height)
|
|
1808
|
-
|
|
1809
|
-
@dialog_renderer.draw_floating_window(x, y, width, height, 'Execute in Project', content_lines, {
|
|
1810
|
-
border_color: "\e[32m",
|
|
1811
|
-
title_color: "\e[1;32m",
|
|
1812
|
-
content_color: "\e[37m"
|
|
1813
|
-
})
|
|
1814
|
-
|
|
1815
|
-
require 'io/console'
|
|
1816
|
-
choice = nil
|
|
1817
|
-
|
|
1818
|
-
loop do
|
|
1819
|
-
input = IO.console.getch.downcase
|
|
1820
|
-
|
|
1821
|
-
case input
|
|
1822
|
-
when "\e" # ESC
|
|
1823
|
-
break
|
|
1824
|
-
when 'c' # Custom command
|
|
1825
|
-
@dialog_renderer.clear_area(x, y, width, height)
|
|
1826
|
-
command = show_project_command_input_dialog(project_name)
|
|
1827
|
-
choice = { type: :command, value: command } if command && !command.empty?
|
|
1828
|
-
break
|
|
1829
|
-
when '1'..'9'
|
|
1830
|
-
number = input.to_i
|
|
1831
|
-
if number > 0 && number <= scripts.length
|
|
1832
|
-
choice = { type: :script, value: scripts[number - 1] }
|
|
1833
|
-
break
|
|
1834
|
-
end
|
|
1835
|
-
end
|
|
1836
|
-
end
|
|
1837
|
-
|
|
1838
|
-
@dialog_renderer.clear_area(x, y, width, height)
|
|
1839
|
-
choice
|
|
1840
|
-
end
|
|
1841
|
-
|
|
1842
|
-
# プロジェクトコマンド入力ダイアログ
|
|
1843
|
-
def show_project_command_input_dialog(project_name)
|
|
1844
|
-
title = "Execute Command in: #{project_name}"
|
|
1845
|
-
prompt = "Enter command:"
|
|
1846
|
-
|
|
1847
|
-
@dialog_renderer.show_input_dialog(title, prompt, {
|
|
1848
|
-
border_color: "\e[32m", # Green
|
|
1849
|
-
title_color: "\e[1;32m", # Bold green
|
|
1850
|
-
content_color: "\e[37m" # White
|
|
1851
|
-
})
|
|
1852
|
-
end
|
|
1853
|
-
|
|
1854
|
-
# プロジェクトコマンド結果ダイアログ
|
|
1855
|
-
def show_project_command_result_dialog(command, result)
|
|
1856
|
-
title = result[:success] ? "Command Success" : "Command Failed"
|
|
1857
|
-
|
|
1858
|
-
# 出力を最初の10行まで表示
|
|
1859
|
-
output_lines = (result[:output] || result[:error] || '').split("\n").take(10)
|
|
1860
|
-
|
|
1861
|
-
content_lines = [
|
|
1862
|
-
'',
|
|
1863
|
-
"Command: #{command}",
|
|
1864
|
-
'',
|
|
1865
|
-
"Output:",
|
|
1866
|
-
''
|
|
1867
|
-
] + output_lines
|
|
1868
|
-
|
|
1869
|
-
if output_lines.length >= 10
|
|
1870
|
-
content_lines << '... (see log for full output)'
|
|
1871
|
-
end
|
|
1872
|
-
|
|
1873
|
-
content_lines << ''
|
|
1874
|
-
content_lines << 'Press any key to continue...'
|
|
1875
|
-
|
|
1876
|
-
width = 80
|
|
1877
|
-
height = [content_lines.length + 4, 20].min
|
|
1878
|
-
x, y = @dialog_renderer.calculate_center(width, height)
|
|
1879
|
-
|
|
1880
|
-
border_color = result[:success] ? "\e[32m" : "\e[31m" # Green or Red
|
|
1881
|
-
title_color = result[:success] ? "\e[1;32m" : "\e[1;31m"
|
|
1882
|
-
|
|
1883
|
-
@dialog_renderer.draw_floating_window(x, y, width, height, title, content_lines, {
|
|
1884
|
-
border_color: border_color,
|
|
1885
|
-
title_color: title_color,
|
|
1886
|
-
content_color: "\e[37m"
|
|
1887
|
-
})
|
|
1888
|
-
|
|
1889
|
-
require 'io/console'
|
|
1890
|
-
IO.console.getch
|
|
1891
|
-
@dialog_renderer.clear_area(x, y, width, height)
|
|
1892
|
-
end
|
|
1893
|
-
|
|
1894
|
-
# プロジェクト選択時の表示
|
|
1895
|
-
def show_project_selected
|
|
1896
|
-
# 選択完了メッセージを表示
|
|
1897
|
-
content_lines = [
|
|
1898
|
-
'',
|
|
1899
|
-
'Project selected!',
|
|
1900
|
-
'',
|
|
1901
|
-
'You can now press : to execute commands',
|
|
1902
|
-
'',
|
|
1903
|
-
'Press any key to continue...'
|
|
1904
|
-
]
|
|
1905
|
-
|
|
1906
|
-
width = 50
|
|
1907
|
-
height = 10
|
|
1908
|
-
x, y = @dialog_renderer.calculate_center(width, height)
|
|
1909
|
-
|
|
1910
|
-
@dialog_renderer.draw_floating_window(x, y, width, height, 'Project Selected', content_lines, {
|
|
1911
|
-
border_color: "\e[32m", # Green
|
|
1912
|
-
title_color: "\e[1;32m", # Bold green
|
|
1913
|
-
content_color: "\e[37m" # White
|
|
1914
|
-
})
|
|
1915
|
-
|
|
1916
|
-
require 'io/console'
|
|
1917
|
-
IO.console.getch
|
|
1918
|
-
@dialog_renderer.clear_area(x, y, width, height)
|
|
1919
|
-
|
|
1920
|
-
# 画面を再描画
|
|
1921
|
-
refresh_display
|
|
1922
|
-
end
|
|
1923
1609
|
end
|
|
1924
1610
|
end
|
|
1925
1611
|
|