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.
@@ -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
- # Project mode
59
- @project_mode = nil
60
- @project_command = nil
61
- @project_log = nil
62
- @in_project_mode = false
63
- @in_log_mode = false
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 @in_project_mode
316
- draw_project_mode_screen
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
- # プロジェクトモードの場合は既存の描画メソッドを使用(Phase 3では未実装)
355
- if @in_project_mode
356
- # プロジェクトモード用のバッファ描画は今後実装予定
357
- # 現在は既存のdraw_project_mode_screenを直接呼び出す
358
- # 注: レンダラーのクリアは状態遷移時のみ行う(set_project_mode等)
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
- result = @command_mode.execute(command_string)
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
- if @in_project_mode
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
- if @in_project_mode
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
- if @in_project_mode
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 set_project_mode(project_mode, project_command, project_log)
1367
- @project_mode = project_mode
1368
- @project_command = project_command
1369
- @project_log = project_log
1370
- @in_project_mode = true
1371
- @in_log_mode = false
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
- @project_mode_needs_redraw = true
1390
+ @job_mode_needs_redraw = true
1377
1391
  end
1378
1392
 
1379
- # プロジェクトモードを終了
1380
- def exit_project_mode
1381
- @in_project_mode = false
1382
- @in_log_mode = false
1383
- @project_mode = nil
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 enter_log_mode(project_log)
1402
- @in_log_mode = true
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 draw_bookmark_list(width, height)
1452
- bookmarks = @project_mode.list_bookmarks
1453
- current_index = @keybind_handler.current_index
1454
-
1455
- print "\e[#{CONTENT_START_LINE};1H"
1456
-
1457
- if bookmarks.empty?
1458
- print " No bookmarks found"
1459
- (height - 1).times { puts ' ' * width }
1460
- return
1461
- end
1462
-
1463
- selected_name = @project_mode.selected_name
1464
-
1465
- bookmarks.each_with_index do |bookmark, index|
1466
- line_num = CONTENT_START_LINE + index
1467
- break if index >= height
1468
-
1469
- # 選択マーク(通常モードと同じ)
1470
- is_project_selected = (bookmark[:name] == selected_name)
1471
- selection_mark = is_project_selected ? "✓ " : " "
1472
-
1473
- # ブックマーク名を表示(番号付き)
1474
- number = index + 1 # 1-based index
1475
- name = bookmark[:name]
1476
- max_name_length = width - 8 # selection_mark(2) + number(1-2) + ". "(2) + padding
1477
- display_name = name.length > max_name_length ? name[0...max_name_length - 3] + '...' : name
1478
- line_content = "#{selection_mark}#{number}. #{display_name}".ljust(width)
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
- if index == current_index
1481
- # カーソル位置は選択色でハイライト
1482
- selected_color = ColorHelper.color_to_selected_ansi(ConfigLoader.colors[:selected])
1483
- print "\e[#{line_num};1H#{selected_color}#{line_content[0...width]}#{ColorHelper.reset}"
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
- remaining_lines = height - bookmarks.length
1496
- remaining_lines.times do |i|
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
- return if bookmarks.empty? || current_index >= bookmarks.length
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
- bookmark = bookmarks[current_index]
1510
- path = bookmark[:path]
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
- details = [
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 draw_log_list(width, height)
1575
- log_files = @project_log.list_log_files
1576
- current_index = @keybind_handler.current_index
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
- cursor_mark = index == current_index ? '>' : ' '
1591
- display_name = filename.ljust(width - 3)
1502
+ # 期限切れの通知を削除
1503
+ nm.expire_old_notifications
1592
1504
 
1593
- if index == current_index
1594
- print "\e[#{line_num};1H\e[7m#{cursor_mark} #{display_name[0...width-3]}\e[0m"
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
- remaining_lines = height - log_files.length
1602
- remaining_lines.times do |i|
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
- def draw_log_preview(width, height, left_offset)
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
- return if log_files.empty? || current_index >= log_files.length
1515
+ # 色設定
1516
+ border_color = notif[:border_color] == :green ? "\e[32m" : "\e[31m"
1517
+ reset = "\e[0m"
1614
1518
 
1615
- filename = log_files[current_index]
1616
- content = @project_log.preview(filename)
1519
+ # ステータスアイコン
1520
+ icon = notif[:type] == :success ? '✓' : '✗'
1617
1521
 
1618
- lines = content.split("\n")
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
- height.times do |i|
1622
- line_num = CONTENT_START_LINE + i
1526
+ # 上部ボーダー
1527
+ print "\e[#{y};#{x}H#{border_color}╭#{'─' * (notification_width - 2)}╮#{reset}"
1623
1528
 
1624
- # セパレータを表示
1625
- cursor_position = left_offset + CURSOR_OFFSET
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
- if i < lines.length
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
- remaining = safe_width - content.length
1639
- print ' ' * remaining if remaining > 0
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 ' ' * (width - 2)
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
- 'P - Project mode',
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