rufio 0.62.0 → 0.63.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 +2 -0
- data/lib/rufio/keybind_handler.rb +3 -0
- data/lib/rufio/tab_mode_manager.rb +106 -0
- data/lib/rufio/terminal_ui.rb +240 -114
- data/lib/rufio/version.rb +1 -1
- data/lib/rufio.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 903b51d0ee611d29ad7ea8961e7948b9dceae16b6d04280c28252149ae2fdd20
|
|
4
|
+
data.tar.gz: 287941ec941e8200c98a881c3ce6de2f7cc7ce3b13e63ba17875ff2222c92bc8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ab00eea888ea729262a4b8c787ecdf43814afe0c573fdc2624597b014027836ae81da4342b413024a10ba6f3212faf5cee1f987af45357fc99134f93d3425314
|
|
7
|
+
data.tar.gz: c665a171a03ab2ebe1941d7b38eeac86ccbd1552c3ee5a7e8cf252fa20293470399cf4cf1ecdeacba8d0d6fb251d5cd4b87ae74a4b4e924814d6821c34d99857
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.63.0] - 2026-02-01
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Tab Mode Switching**: Seamless mode navigation via `Tab` / `Shift+Tab` keys
|
|
14
|
+
- New `TabModeManager` class for unified mode management
|
|
15
|
+
- Four modes: Files, Logs, Jobs, Help
|
|
16
|
+
- Mode tabs displayed in header row 2 (cyan highlight for current mode)
|
|
17
|
+
- Circular navigation: Files → Logs → Jobs → Help → Files
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **Header UI Overhaul**: Two-row header layout
|
|
21
|
+
- Row 1: Application name and current path
|
|
22
|
+
- Row 2: Interactive mode tabs
|
|
23
|
+
- Header icon changed from 📁 to 💎 (representing Ruby gem)
|
|
24
|
+
- **Job Mode Integration**: Unified buffer-based rendering
|
|
25
|
+
- `draw_job_list_to_buffer`, `draw_job_line_to_buffer`, `draw_job_footer_to_buffer`
|
|
26
|
+
- Consistent rendering pipeline with other modes
|
|
27
|
+
- **Help/Logs Mode Improvements**:
|
|
28
|
+
- Parent directory (`..`) hidden in Help and Logs modes
|
|
29
|
+
- Directory navigation disabled in these modes
|
|
30
|
+
|
|
31
|
+
### Technical Details
|
|
32
|
+
- **New Files**: `lib/rufio/tab_mode_manager.rb`, `test/test_tab_mode.rb`
|
|
33
|
+
- **Modified Files**: `lib/rufio.rb`, `lib/rufio/keybind_handler.rb`, `lib/rufio/terminal_ui.rb`
|
|
34
|
+
- **Layout Constants**: `HEADER_HEIGHT` changed from 1 to 2
|
|
35
|
+
|
|
10
36
|
## [0.62.0] - 2026-01-31
|
|
11
37
|
|
|
12
38
|
### Changed
|
data/README.md
CHANGED
|
@@ -491,6 +491,9 @@ module Rufio
|
|
|
491
491
|
return false unless entry
|
|
492
492
|
|
|
493
493
|
if entry[:type] == 'directory'
|
|
494
|
+
# ヘルプモードとLogsモードではディレクトリ移動を禁止
|
|
495
|
+
return false if @in_help_mode || @in_log_viewer_mode
|
|
496
|
+
|
|
494
497
|
result = @directory_listing.navigate_to(entry[:name])
|
|
495
498
|
if result
|
|
496
499
|
@current_index = 0 # select first entry in new directory
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rufio
|
|
4
|
+
# タブモード管理クラス
|
|
5
|
+
# 上部メニューの2段目に表示するモードタブを管理する
|
|
6
|
+
class TabModeManager
|
|
7
|
+
MODES = %i[files logs jobs help].freeze
|
|
8
|
+
|
|
9
|
+
MODE_LABELS = {
|
|
10
|
+
files: 'Files',
|
|
11
|
+
logs: 'Logs',
|
|
12
|
+
jobs: 'Jobs',
|
|
13
|
+
help: 'Help'
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
attr_reader :current_mode
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
@current_mode = :files
|
|
20
|
+
@mode_change_callback = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# 利用可能なモード一覧
|
|
24
|
+
def available_modes
|
|
25
|
+
MODES
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# モードのラベル一覧
|
|
29
|
+
def mode_labels
|
|
30
|
+
MODE_LABELS
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# 次のモードに切り替え
|
|
34
|
+
def next_mode
|
|
35
|
+
current_index = MODES.index(@current_mode)
|
|
36
|
+
new_index = (current_index + 1) % MODES.length
|
|
37
|
+
switch_to(MODES[new_index])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# 前のモードに切り替え
|
|
41
|
+
def previous_mode
|
|
42
|
+
current_index = MODES.index(@current_mode)
|
|
43
|
+
new_index = (current_index - 1) % MODES.length
|
|
44
|
+
switch_to(MODES[new_index])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# 特定のモードに切り替え
|
|
48
|
+
def switch_to(mode)
|
|
49
|
+
return unless MODES.include?(mode)
|
|
50
|
+
return if @current_mode == mode
|
|
51
|
+
|
|
52
|
+
@current_mode = mode
|
|
53
|
+
@mode_change_callback&.call(mode)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# 現在のモード情報を取得
|
|
57
|
+
def current_mode_info
|
|
58
|
+
{
|
|
59
|
+
mode: @current_mode,
|
|
60
|
+
label: MODE_LABELS[@current_mode],
|
|
61
|
+
index: MODES.index(@current_mode)
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# モード変更時のコールバックを設定
|
|
66
|
+
def on_mode_change(&block)
|
|
67
|
+
@mode_change_callback = block
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# タブ行をレンダリング
|
|
71
|
+
# @param width [Integer] 画面幅
|
|
72
|
+
# @return [String] ANSIエスケープシーケンスを含むタブ行
|
|
73
|
+
def render_tab_line(width)
|
|
74
|
+
tabs = MODES.map do |mode|
|
|
75
|
+
label = MODE_LABELS[mode]
|
|
76
|
+
if mode == @current_mode
|
|
77
|
+
# 現在のモード: シアン背景 + 黒文字 + 太字で目立たせる
|
|
78
|
+
"\e[46m\e[30m\e[1m #{label} \e[0m"
|
|
79
|
+
else
|
|
80
|
+
# 非選択モード: 暗めの色
|
|
81
|
+
"\e[90m #{label} \e[0m"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
tab_content = tabs.join("\e[90m│\e[0m")
|
|
86
|
+
|
|
87
|
+
# 幅に合わせてパディング
|
|
88
|
+
# ANSIエスケープシーケンスを除いた実際の表示幅を計算
|
|
89
|
+
display_width = strip_ansi(tab_content).length
|
|
90
|
+
padding = width - display_width
|
|
91
|
+
|
|
92
|
+
if padding > 0
|
|
93
|
+
tab_content + ' ' * padding
|
|
94
|
+
else
|
|
95
|
+
tab_content[0...width]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
# ANSIエスケープシーケンスを除去して実際の表示幅を取得
|
|
102
|
+
def strip_ansi(str)
|
|
103
|
+
str.gsub(/\e\[[0-9;]*m/, '')
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
data/lib/rufio/terminal_ui.rb
CHANGED
|
@@ -6,9 +6,9 @@ require_relative 'text_utils'
|
|
|
6
6
|
module Rufio
|
|
7
7
|
class TerminalUI
|
|
8
8
|
# Layout constants
|
|
9
|
-
HEADER_HEIGHT =
|
|
9
|
+
HEADER_HEIGHT = 2 # Header占有行数(2段目のモードタブを含む)
|
|
10
10
|
FOOTER_HEIGHT = 1 # Footer占有行数(ブックマーク一覧 + ステータス情報)
|
|
11
|
-
HEADER_FOOTER_MARGIN = 3 # Header + Footer分のマージン
|
|
11
|
+
HEADER_FOOTER_MARGIN = 3 # Header(2行) + Footer(1行)分のマージン
|
|
12
12
|
|
|
13
13
|
# Panel layout ratios
|
|
14
14
|
LEFT_PANEL_RATIO = 0.5 # 左パネルの幅比率
|
|
@@ -30,7 +30,7 @@ module Rufio
|
|
|
30
30
|
GIGABYTE = MEGABYTE * 1024
|
|
31
31
|
|
|
32
32
|
# Line offsets
|
|
33
|
-
CONTENT_START_LINE = 2 # コンテンツ開始行(ヘッダー1
|
|
33
|
+
CONTENT_START_LINE = 2 # コンテンツ開始行(ヘッダー2行: Y=0, Y=1)
|
|
34
34
|
|
|
35
35
|
def initialize(test_mode: false)
|
|
36
36
|
console = IO.console
|
|
@@ -74,6 +74,9 @@ module Rufio
|
|
|
74
74
|
# Command execution lamp (footer indicator)
|
|
75
75
|
@completion_lamp_message = nil
|
|
76
76
|
@completion_lamp_time = nil
|
|
77
|
+
|
|
78
|
+
# Tab mode manager
|
|
79
|
+
@tab_mode_manager = TabModeManager.new
|
|
77
80
|
end
|
|
78
81
|
|
|
79
82
|
def start(directory_listing, keybind_handler, file_preview, background_executor = nil)
|
|
@@ -371,35 +374,34 @@ module Rufio
|
|
|
371
374
|
|
|
372
375
|
# Phase 3: Screenバッファに描画する新しいメソッド
|
|
373
376
|
def draw_screen_to_buffer(screen, notification_message = nil, fps = nil)
|
|
374
|
-
#
|
|
375
|
-
if @in_job_mode
|
|
376
|
-
if @job_mode_needs_redraw
|
|
377
|
-
draw_job_mode_screen
|
|
378
|
-
@job_mode_needs_redraw = false
|
|
379
|
-
end
|
|
380
|
-
return
|
|
381
|
-
end
|
|
382
|
-
|
|
383
|
-
# header (1 line) - y=0
|
|
377
|
+
# header (2 lines) - y=0, y=1(全モード共通)
|
|
384
378
|
draw_header_to_buffer(screen, 0)
|
|
385
|
-
|
|
386
|
-
# main content (left: directory list, right: preview)
|
|
387
|
-
entries = get_display_entries
|
|
388
|
-
selected_entry = entries[@keybind_handler.current_index]
|
|
379
|
+
draw_mode_tabs_to_buffer(screen, 1)
|
|
389
380
|
|
|
390
381
|
# calculate height with header and footer margin
|
|
391
382
|
content_height = @screen_height - HEADER_FOOTER_MARGIN
|
|
392
|
-
left_width = (@screen_width * LEFT_PANEL_RATIO).to_i
|
|
393
|
-
right_width = @screen_width - left_width
|
|
394
383
|
|
|
395
|
-
|
|
396
|
-
|
|
384
|
+
if @in_job_mode
|
|
385
|
+
# ジョブモード: ジョブ一覧を表示
|
|
386
|
+
draw_job_list_to_buffer(screen, content_height)
|
|
387
|
+
draw_job_footer_to_buffer(screen, @screen_height - 1)
|
|
388
|
+
else
|
|
389
|
+
# 通常モード: ファイル一覧とプレビューを表示
|
|
390
|
+
entries = get_display_entries
|
|
391
|
+
selected_entry = entries[@keybind_handler.current_index]
|
|
397
392
|
|
|
398
|
-
|
|
399
|
-
|
|
393
|
+
left_width = (@screen_width * LEFT_PANEL_RATIO).to_i
|
|
394
|
+
right_width = @screen_width - left_width
|
|
400
395
|
|
|
401
|
-
|
|
402
|
-
|
|
396
|
+
# adjust so right panel doesn't overflow into left panel
|
|
397
|
+
right_width = @screen_width - left_width if left_width + right_width > @screen_width
|
|
398
|
+
|
|
399
|
+
draw_directory_list_to_buffer(screen, entries, left_width, content_height)
|
|
400
|
+
draw_file_preview_to_buffer(screen, selected_entry, right_width, content_height, left_width)
|
|
401
|
+
|
|
402
|
+
# footer
|
|
403
|
+
draw_footer_to_buffer(screen, @screen_height - 1, fps)
|
|
404
|
+
end
|
|
403
405
|
|
|
404
406
|
# 通知メッセージがある場合は表示
|
|
405
407
|
if notification_message
|
|
@@ -410,10 +412,80 @@ module Rufio
|
|
|
410
412
|
end
|
|
411
413
|
screen.put_string(0, notification_line, message_display.ljust(@screen_width), fg: "\e[7m")
|
|
412
414
|
end
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# ジョブ一覧をバッファに描画
|
|
418
|
+
def draw_job_list_to_buffer(screen, height)
|
|
419
|
+
return unless @job_manager
|
|
420
|
+
|
|
421
|
+
jobs = @job_manager.jobs
|
|
422
|
+
selected_index = @job_mode_instance&.selected_index || 0
|
|
423
|
+
|
|
424
|
+
(0...height).each do |i|
|
|
425
|
+
line_num = i + CONTENT_START_LINE
|
|
426
|
+
|
|
427
|
+
if i < jobs.length
|
|
428
|
+
job = jobs[i]
|
|
429
|
+
draw_job_line_to_buffer(screen, job, i == selected_index, line_num)
|
|
430
|
+
else
|
|
431
|
+
# 空行
|
|
432
|
+
screen.put_string(0, line_num, ' ' * @screen_width)
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
end
|
|
413
436
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
437
|
+
# ジョブ行をバッファに描画
|
|
438
|
+
def draw_job_line_to_buffer(screen, job, is_selected, y)
|
|
439
|
+
icon = job.status_icon
|
|
440
|
+
name = job.name
|
|
441
|
+
path = "(#{job.path})"
|
|
442
|
+
duration = job.formatted_duration
|
|
443
|
+
duration_text = duration.empty? ? "" : "[#{duration}]"
|
|
444
|
+
|
|
445
|
+
status_text = case job.status
|
|
446
|
+
when :running then "Running"
|
|
447
|
+
when :completed then "Done"
|
|
448
|
+
when :failed then "Failed"
|
|
449
|
+
when :waiting then "Waiting"
|
|
450
|
+
when :cancelled then "Cancelled"
|
|
451
|
+
else ""
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
# ステータスに応じた色
|
|
455
|
+
status_color = case job.status
|
|
456
|
+
when :running then "\e[33m" # Yellow
|
|
457
|
+
when :completed then "\e[32m" # Green
|
|
458
|
+
when :failed then "\e[31m" # Red
|
|
459
|
+
else "\e[37m" # White
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
# 行を構築
|
|
463
|
+
line_content = "#{icon} #{name} #{path}".ljust(40)
|
|
464
|
+
line_content += "#{duration_text.ljust(12)} #{status_text}"
|
|
465
|
+
line_content = line_content[0...@screen_width].ljust(@screen_width)
|
|
466
|
+
|
|
467
|
+
if is_selected
|
|
468
|
+
# 選択中: 反転表示
|
|
469
|
+
line_content.each_char.with_index do |char, x|
|
|
470
|
+
screen.put(x, y, char, fg: "\e[30m", bg: "\e[47m")
|
|
471
|
+
end
|
|
472
|
+
else
|
|
473
|
+
# 非選択: ステータス色
|
|
474
|
+
line_content.each_char.with_index do |char, x|
|
|
475
|
+
screen.put(x, y, char, fg: status_color)
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
# ジョブモード用フッターをバッファに描画
|
|
481
|
+
def draw_job_footer_to_buffer(screen, y)
|
|
482
|
+
job_count = @job_manager&.job_count || 0
|
|
483
|
+
help_text = "[Space] View Log | [x] Cancel | [Tab] Switch Mode | Jobs: #{job_count}"
|
|
484
|
+
footer_content = help_text.center(@screen_width)[0...@screen_width]
|
|
485
|
+
|
|
486
|
+
footer_content.each_char.with_index do |char, x|
|
|
487
|
+
screen.put(x, y, char, fg: "\e[30m", bg: "\e[47m")
|
|
488
|
+
end
|
|
417
489
|
end
|
|
418
490
|
|
|
419
491
|
def draw_screen_with_notification(notification_message)
|
|
@@ -436,7 +508,7 @@ module Rufio
|
|
|
436
508
|
# Phase 3: Screenバッファにヘッダーを描画
|
|
437
509
|
def draw_header_to_buffer(screen, y)
|
|
438
510
|
current_path = @directory_listing.current_path
|
|
439
|
-
header = "
|
|
511
|
+
header = "💎 rufio - #{current_path}"
|
|
440
512
|
|
|
441
513
|
# Add help mode indicator if in help mode
|
|
442
514
|
if @keybind_handler.help_mode?
|
|
@@ -455,23 +527,81 @@ module Rufio
|
|
|
455
527
|
# prioritize showing help mode indicator
|
|
456
528
|
help_text = " [Help Mode - Press ESC to exit]"
|
|
457
529
|
base_length = @screen_width - help_text.length - FILTER_TEXT_RESERVED
|
|
458
|
-
header = "
|
|
530
|
+
header = "💎 rufio - ...#{current_path[-base_length..-1]}#{help_text}"
|
|
459
531
|
elsif @keybind_handler.filter_active?
|
|
460
532
|
# prioritize showing filter when active
|
|
461
533
|
filter_text = " [Filter: #{@keybind_handler.filter_query}]"
|
|
462
534
|
base_length = @screen_width - filter_text.length - FILTER_TEXT_RESERVED
|
|
463
|
-
header = "
|
|
535
|
+
header = "💎 rufio - ...#{current_path[-base_length..-1]}#{filter_text}"
|
|
464
536
|
else
|
|
465
|
-
header = "
|
|
537
|
+
header = "💎 rufio - ...#{current_path[-(@screen_width - FILTER_TEXT_RESERVED)..-1]}"
|
|
466
538
|
end
|
|
467
539
|
end
|
|
468
540
|
|
|
469
541
|
screen.put_string(0, y, header.ljust(@screen_width), fg: "\e[7m")
|
|
470
542
|
end
|
|
471
543
|
|
|
544
|
+
# Phase 3: Screenバッファにモードタブを描画
|
|
545
|
+
def draw_mode_tabs_to_buffer(screen, y)
|
|
546
|
+
# タブモードマネージャの状態を同期
|
|
547
|
+
sync_tab_mode_with_keybind_handler
|
|
548
|
+
|
|
549
|
+
current_x = 0
|
|
550
|
+
modes = @tab_mode_manager.available_modes
|
|
551
|
+
labels = @tab_mode_manager.mode_labels
|
|
552
|
+
current_mode = @tab_mode_manager.current_mode
|
|
553
|
+
|
|
554
|
+
modes.each_with_index do |mode, index|
|
|
555
|
+
label = " #{labels[mode]} "
|
|
556
|
+
|
|
557
|
+
if mode == current_mode
|
|
558
|
+
# 現在のモード: シアン背景 + 黒文字 + 太字
|
|
559
|
+
label.each_char do |char|
|
|
560
|
+
screen.put(current_x, y, char, fg: "\e[30m\e[1m", bg: "\e[46m")
|
|
561
|
+
current_x += 1
|
|
562
|
+
end
|
|
563
|
+
else
|
|
564
|
+
# 非選択モード: グレー文字
|
|
565
|
+
label.each_char do |char|
|
|
566
|
+
screen.put(current_x, y, char, fg: "\e[90m")
|
|
567
|
+
current_x += 1
|
|
568
|
+
end
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
# 区切り線(最後のモード以外)
|
|
572
|
+
if index < modes.length - 1
|
|
573
|
+
screen.put(current_x, y, '│', fg: "\e[90m")
|
|
574
|
+
current_x += 1
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
# 残りをスペースで埋める
|
|
579
|
+
while current_x < @screen_width
|
|
580
|
+
screen.put(current_x, y, ' ')
|
|
581
|
+
current_x += 1
|
|
582
|
+
end
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
# キーバインドハンドラの状態とタブモードを同期
|
|
586
|
+
def sync_tab_mode_with_keybind_handler
|
|
587
|
+
return unless @keybind_handler
|
|
588
|
+
|
|
589
|
+
current_mode = if @keybind_handler.help_mode?
|
|
590
|
+
:help
|
|
591
|
+
elsif @keybind_handler.log_viewer_mode?
|
|
592
|
+
:logs
|
|
593
|
+
elsif @keybind_handler.in_job_mode?
|
|
594
|
+
:jobs
|
|
595
|
+
else
|
|
596
|
+
:files
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
@tab_mode_manager.switch_to(current_mode) if @tab_mode_manager.current_mode != current_mode
|
|
600
|
+
end
|
|
601
|
+
|
|
472
602
|
def draw_header
|
|
473
603
|
current_path = @directory_listing.current_path
|
|
474
|
-
header = "
|
|
604
|
+
header = "💎 rufio - #{current_path}"
|
|
475
605
|
|
|
476
606
|
# Add help mode indicator if in help mode
|
|
477
607
|
if @keybind_handler.help_mode?
|
|
@@ -490,14 +620,14 @@ module Rufio
|
|
|
490
620
|
# prioritize showing help mode indicator
|
|
491
621
|
help_text = " [Help Mode - Press ESC to exit]"
|
|
492
622
|
base_length = @screen_width - help_text.length - FILTER_TEXT_RESERVED
|
|
493
|
-
header = "
|
|
623
|
+
header = "💎 rufio - ...#{current_path[-base_length..-1]}#{help_text}"
|
|
494
624
|
elsif @keybind_handler.filter_active?
|
|
495
625
|
# prioritize showing filter when active
|
|
496
626
|
filter_text = " [Filter: #{@keybind_handler.filter_query}]"
|
|
497
627
|
base_length = @screen_width - filter_text.length - FILTER_TEXT_RESERVED
|
|
498
|
-
header = "
|
|
628
|
+
header = "💎 rufio - ...#{current_path[-base_length..-1]}#{filter_text}"
|
|
499
629
|
else
|
|
500
|
-
header = "
|
|
630
|
+
header = "💎 rufio - ...#{current_path[-(@screen_width - FILTER_TEXT_RESERVED)..-1]}"
|
|
501
631
|
end
|
|
502
632
|
end
|
|
503
633
|
|
|
@@ -869,13 +999,20 @@ module Rufio
|
|
|
869
999
|
|
|
870
1000
|
|
|
871
1001
|
def get_display_entries
|
|
872
|
-
if @keybind_handler.filter_active?
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
1002
|
+
entries = if @keybind_handler.filter_active?
|
|
1003
|
+
# Get filtered entries from keybind_handler
|
|
1004
|
+
all_entries = @directory_listing.list_entries
|
|
1005
|
+
query = @keybind_handler.filter_query.downcase
|
|
1006
|
+
query.empty? ? all_entries : all_entries.select { |entry| entry[:name].downcase.include?(query) }
|
|
1007
|
+
else
|
|
1008
|
+
@directory_listing.list_entries
|
|
1009
|
+
end
|
|
1010
|
+
|
|
1011
|
+
# ヘルプモードとLogsモードでは..を非表示にする
|
|
1012
|
+
if @keybind_handler.help_mode? || @keybind_handler.log_viewer_mode?
|
|
1013
|
+
entries.reject { |entry| entry[:name] == '..' }
|
|
877
1014
|
else
|
|
878
|
-
|
|
1015
|
+
entries
|
|
879
1016
|
end
|
|
880
1017
|
end
|
|
881
1018
|
|
|
@@ -1076,6 +1213,7 @@ module Rufio
|
|
|
1076
1213
|
when 'B' then 'j' # Down arrow
|
|
1077
1214
|
when 'C' then 'l' # Right arrow
|
|
1078
1215
|
when 'D' then 'h' # Left arrow
|
|
1216
|
+
when 'Z' then handle_shift_tab; return true # Shift+Tab
|
|
1079
1217
|
else "\e" # ESCキー(そのまま保持)
|
|
1080
1218
|
end
|
|
1081
1219
|
else
|
|
@@ -1083,6 +1221,12 @@ module Rufio
|
|
|
1083
1221
|
end
|
|
1084
1222
|
end
|
|
1085
1223
|
|
|
1224
|
+
# Tabキーでモード切り替え
|
|
1225
|
+
if input == "\t"
|
|
1226
|
+
handle_tab_key
|
|
1227
|
+
return true
|
|
1228
|
+
end
|
|
1229
|
+
|
|
1086
1230
|
# キーバインドハンドラーに処理を委譲
|
|
1087
1231
|
result = @keybind_handler.handle_key(input) if input
|
|
1088
1232
|
|
|
@@ -1156,6 +1300,54 @@ module Rufio
|
|
|
1156
1300
|
end
|
|
1157
1301
|
end
|
|
1158
1302
|
|
|
1303
|
+
# Tabキーによるモード切り替え
|
|
1304
|
+
def handle_tab_key
|
|
1305
|
+
@tab_mode_manager.next_mode
|
|
1306
|
+
apply_mode_change(@tab_mode_manager.current_mode)
|
|
1307
|
+
end
|
|
1308
|
+
|
|
1309
|
+
# Shift+Tabによる逆順モード切り替え
|
|
1310
|
+
def handle_shift_tab
|
|
1311
|
+
@tab_mode_manager.previous_mode
|
|
1312
|
+
apply_mode_change(@tab_mode_manager.current_mode)
|
|
1313
|
+
end
|
|
1314
|
+
|
|
1315
|
+
# モード変更を適用
|
|
1316
|
+
def apply_mode_change(mode)
|
|
1317
|
+
case mode
|
|
1318
|
+
when :files
|
|
1319
|
+
# ヘルプモードまたはログビューワモードから戻る
|
|
1320
|
+
if @keybind_handler.help_mode?
|
|
1321
|
+
@keybind_handler.send(:exit_help_mode)
|
|
1322
|
+
elsif @keybind_handler.log_viewer_mode?
|
|
1323
|
+
@keybind_handler.send(:exit_log_viewer_mode)
|
|
1324
|
+
elsif @keybind_handler.in_job_mode?
|
|
1325
|
+
@keybind_handler.send(:exit_job_mode)
|
|
1326
|
+
end
|
|
1327
|
+
when :help
|
|
1328
|
+
# ヘルプモードに入る
|
|
1329
|
+
unless @keybind_handler.help_mode?
|
|
1330
|
+
@keybind_handler.send(:exit_log_viewer_mode) if @keybind_handler.log_viewer_mode?
|
|
1331
|
+
@keybind_handler.send(:exit_job_mode) if @keybind_handler.in_job_mode?
|
|
1332
|
+
@keybind_handler.send(:enter_help_mode)
|
|
1333
|
+
end
|
|
1334
|
+
when :logs
|
|
1335
|
+
# ログビューワモードに入る
|
|
1336
|
+
unless @keybind_handler.log_viewer_mode?
|
|
1337
|
+
@keybind_handler.send(:exit_help_mode) if @keybind_handler.help_mode?
|
|
1338
|
+
@keybind_handler.send(:exit_job_mode) if @keybind_handler.in_job_mode?
|
|
1339
|
+
@keybind_handler.send(:enter_log_viewer_mode)
|
|
1340
|
+
end
|
|
1341
|
+
when :jobs
|
|
1342
|
+
# ジョブモードに入る
|
|
1343
|
+
unless @keybind_handler.in_job_mode?
|
|
1344
|
+
@keybind_handler.send(:exit_help_mode) if @keybind_handler.help_mode?
|
|
1345
|
+
@keybind_handler.send(:exit_log_viewer_mode) if @keybind_handler.log_viewer_mode?
|
|
1346
|
+
@keybind_handler.enter_job_mode
|
|
1347
|
+
end
|
|
1348
|
+
end
|
|
1349
|
+
end
|
|
1350
|
+
|
|
1159
1351
|
# コマンドモード関連のメソッドは public にする
|
|
1160
1352
|
public
|
|
1161
1353
|
|
|
@@ -1414,81 +1606,15 @@ module Rufio
|
|
|
1414
1606
|
@job_mode_needs_redraw = true
|
|
1415
1607
|
end
|
|
1416
1608
|
|
|
1417
|
-
#
|
|
1609
|
+
# ジョブモード画面を描画(バッファベース描画への橋渡し)
|
|
1418
1610
|
def draw_job_mode_screen
|
|
1419
1611
|
return unless @in_job_mode && @job_mode_instance && @job_manager
|
|
1612
|
+
return unless @screen && @renderer
|
|
1420
1613
|
|
|
1421
|
-
#
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
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
|
|
1447
|
-
|
|
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"
|
|
1472
|
-
else
|
|
1473
|
-
print "\e[#{line_num};1H#{icon_color}#{line_content}\e[0m"
|
|
1474
|
-
end
|
|
1475
|
-
end
|
|
1476
|
-
|
|
1477
|
-
# 空行をクリア
|
|
1478
|
-
((jobs.length + 3)...(@screen_height - 2)).each do |line_num|
|
|
1479
|
-
print "\e[#{line_num};1H#{' ' * @screen_width}"
|
|
1480
|
-
end
|
|
1481
|
-
|
|
1482
|
-
# フッター
|
|
1483
|
-
footer_line = @screen_height - 1
|
|
1484
|
-
footer_separator = "━" * @screen_width
|
|
1485
|
-
print "\e[#{footer_line};1H\e[36m#{footer_separator}\e[0m"
|
|
1486
|
-
|
|
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"
|
|
1614
|
+
# バッファベースの描画を使用
|
|
1615
|
+
draw_screen_to_buffer(@screen, nil, nil)
|
|
1616
|
+
@renderer.render(@screen)
|
|
1617
|
+
print "\e[#{@screen_height};#{@screen_width}H"
|
|
1492
1618
|
|
|
1493
1619
|
STDOUT.flush
|
|
1494
1620
|
@job_mode_needs_redraw = false
|
data/lib/rufio/version.rb
CHANGED
data/lib/rufio.rb
CHANGED
|
@@ -43,6 +43,7 @@ require_relative "rufio/async_scanner_fiber"
|
|
|
43
43
|
require_relative "rufio/parallel_scanner"
|
|
44
44
|
require_relative "rufio/screen"
|
|
45
45
|
require_relative "rufio/renderer"
|
|
46
|
+
require_relative "rufio/tab_mode_manager"
|
|
46
47
|
|
|
47
48
|
# ジョブ管理システム
|
|
48
49
|
require_relative "rufio/task_status"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rufio
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.63.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- masisz
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-01
|
|
11
|
+
date: 2026-02-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: io-console
|
|
@@ -189,6 +189,7 @@ files:
|
|
|
189
189
|
- lib/rufio/script_runner.rb
|
|
190
190
|
- lib/rufio/selection_manager.rb
|
|
191
191
|
- lib/rufio/shell_command_completion.rb
|
|
192
|
+
- lib/rufio/tab_mode_manager.rb
|
|
192
193
|
- lib/rufio/task_status.rb
|
|
193
194
|
- lib/rufio/terminal_ui.rb
|
|
194
195
|
- lib/rufio/text_utils.rb
|