rufio 0.80.0 → 0.81.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a5c4fc956ed9bcb90569c8c7fcdd023fec3d31cdf1852f075ba40c9746a654dc
4
- data.tar.gz: f07ae9f1f0071de307b407e5322f3f2a53c62fb654f023223f7214be79350dc3
3
+ metadata.gz: 533e0a5b32ced89d9d65bfd1abaafee73bada752cc24848538abc05e878a3f41
4
+ data.tar.gz: f614bb8ee10f320c3b8e3387f5374b5c1bc2c2b9299112f0640061dffcb47b05
5
5
  SHA512:
6
- metadata.gz: '097aed068bd564b961c159504b7396c4d0dec7b9eb4be819d2f83380fc10aa86e698c316b9e95bfb012863790ea53cbe0ee521d3561919cdde61fbafcbb22959'
7
- data.tar.gz: 6d1d2ce8640debc3fe6fbe282e75a959bb4c7a846f21cde65d0a4cd2a713329c7416591c2d182b05e7bd67b4df9895071cac32dc9ab85940459eed8f19660881
6
+ metadata.gz: a0f9f950cfa19fc467fe220614f298ac35473aa8e9112e7776b683b9414e39436f4cce7bf5f5d3cf3a481dbf48545e2e971a9773e3e68bc11bfae53c6b63fc13
7
+ data.tar.gz: cff712ea8904a749d0d3f646bc0b9a016c085a043bf08bc6cf304d1f1bbbc5a4e94daf7e05464334f6da56c0bdcf32f6411e8d0787a7763b303428bde0993f65
data/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+ - **Bookmark Jump Highlight**: When cycling bookmarks with `Tab`, the target bookmark in the top bar is highlighted in cyan for 500ms, then automatically fades back to gray
12
+
13
+ ### Changed
14
+ - **UI Layout**: Merged mode tabs and title bar into a single combined bottom row
15
+ - **Top bar (y=0)**: Bookmark list `0.start │ 1.name │ 2.name ...`, job status, FPS (test mode)
16
+ - **Bottom bar (y=h-1)**: Mode tabs + current path + version — all in one line
17
+ - Powerline-style separators: active tab gets a `\uE0B0` cyan exit arrow; version section uses `\uE0B2` cyan entry arrow with cyan background and black bold text
18
+ - **`Tab` key**: Changed from mode cycling to **bookmark cycling** (Files mode only)
19
+ - In Files mode: jump to next registered bookmark in sequence (wraps to first)
20
+ - In other modes (Logs / Jobs / Help): no action
21
+ - Mode switching is done via `J` / `L` / `?` keys and `Shift+Tab`
22
+
23
+ ### Fixed
24
+ - **zoxide 2-digit input**: Fixed a bug where pressing a single digit in the zoxide history dialog immediately navigated, making it impossible to enter 2-digit numbers (e.g., `12`)
25
+ - **Jobs mode tab highlight**: Fixed active tab not highlighting when entering Jobs mode from Logs or Help mode
26
+ - **Jobs mode navigation**: Fixed inability to switch away from Jobs mode using `L`, `?` keys
27
+
10
28
  ## [0.80.0] - 2026-02-21
11
29
 
12
30
  ### Added
data/README.md CHANGED
@@ -123,6 +123,7 @@ rufio /path/to # Launch in specified directory
123
123
  | `B` | Bookmark menu |
124
124
  | `0` | Return to startup directory |
125
125
  | `1-9` | Jump to bookmark |
126
+ | `Tab` | Cycle to next bookmark (Files mode only) |
126
127
  | `z` | zoxide history |
127
128
 
128
129
  ### Tool Runtime
@@ -133,7 +134,6 @@ rufio /path/to # Launch in specified directory
133
134
  | `J` | Job mode |
134
135
  | `L` | View execution logs |
135
136
  | `?` | Help |
136
- | `Tab` | Switch mode (Files → Logs → Jobs → Help) |
137
137
  | `Shift+Tab` | Switch mode (reverse) |
138
138
 
139
139
  ## Command Mode
data/README_ja.md CHANGED
@@ -125,6 +125,7 @@ rufio /path/to # 指定したディレクトリで起動
125
125
  | `B` | ブックマークメニュー |
126
126
  | `0` | 起動ディレクトリに戻る |
127
127
  | `1-9` | ブックマークにジャンプ |
128
+ | `Tab` | 次のブックマークへ循環移動(Filesモード限定) |
128
129
  | `z` | zoxide履歴 |
129
130
 
130
131
  ### ツールランタイム
@@ -135,6 +136,7 @@ rufio /path/to # 指定したディレクトリで起動
135
136
  | `J` | ジョブモード |
136
137
  | `L` | 実行ログ表示 |
137
138
  | `?` | ヘルプ |
139
+ | `Shift+Tab` | モード切り替え(逆順) |
138
140
 
139
141
  ## コマンドモード
140
142
 
@@ -436,6 +436,20 @@ module Rufio
436
436
  @preview_scroll_offset = 0
437
437
  end
438
438
 
439
+ # Tabキー: 次のブックマークへ循環移動
440
+ def goto_next_bookmark
441
+ bookmarks = @bookmark_manager.list
442
+ return nil unless bookmarks&.any?
443
+
444
+ current_path = @directory_listing.current_path
445
+ current_idx = bookmarks.find_index { |bm| bm[:path] == current_path }
446
+
447
+ next_idx = current_idx ? (current_idx + 1) % bookmarks.length : 0
448
+ next_bookmark = bookmarks[next_idx]
449
+ navigate_to_directory(next_bookmark[:path])
450
+ next_idx
451
+ end
452
+
439
453
  private
440
454
 
441
455
  # オーバーレイダイアログを表示してキー入力を待つヘルパーメソッド
@@ -1388,6 +1402,7 @@ module Rufio
1388
1402
  navigate_to_directory(bookmark[:path])
1389
1403
  end
1390
1404
 
1405
+
1391
1406
  def add_bookmark
1392
1407
  current_path = @directory_listing&.current_path || Dir.pwd
1393
1408
 
@@ -13,6 +13,13 @@ module Rufio
13
13
  help: 'Help'
14
14
  }.freeze
15
15
 
16
+ MODE_KEYS = {
17
+ files: nil,
18
+ logs: 'L',
19
+ jobs: 'J',
20
+ help: '?'
21
+ }.freeze
22
+
16
23
  attr_reader :current_mode
17
24
 
18
25
  def initialize
@@ -30,6 +37,11 @@ module Rufio
30
37
  MODE_LABELS
31
38
  end
32
39
 
40
+ # モードのキーバインド一覧
41
+ def mode_keys
42
+ MODE_KEYS
43
+ end
44
+
33
45
  # 次のモードに切り替え
34
46
  def next_mode
35
47
  current_index = MODES.index(@current_mode)
@@ -6,9 +6,9 @@ require_relative 'text_utils'
6
6
  module Rufio
7
7
  class TerminalUI
8
8
  # Layout constants
9
- HEADER_HEIGHT = 2 # Header占有行数(2段目のモードタブを含む)
9
+ HEADER_HEIGHT = 1 # Header占有行数(モードタブ+パス+バージョン 1行に統合)
10
10
  FOOTER_HEIGHT = 1 # Footer占有行数(ブックマーク一覧 + ステータス情報)
11
- HEADER_FOOTER_MARGIN = 3 # Header(2行) + Footer(1行)分のマージン
11
+ HEADER_FOOTER_MARGIN = 2 # Header(1行) + Footer(1行)分のマージン
12
12
 
13
13
  # Panel layout ratios
14
14
  LEFT_PANEL_RATIO = 0.5 # 左パネルの幅比率
@@ -19,6 +19,7 @@ module Rufio
19
19
  DEFAULT_SCREEN_HEIGHT = 24 # デフォルト画面高さ
20
20
  HEADER_PADDING = 2 # ヘッダーのパディング
21
21
  FILTER_TEXT_RESERVED = 15 # フィルタテキスト表示の予約幅
22
+ TAB_SEPARATOR = ">" # タブ間セパレータ
22
23
 
23
24
  # File display constants
24
25
  ICON_SIZE_PADDING = 12 # アイコン、選択マーク、サイズ情報分
@@ -29,8 +30,11 @@ module Rufio
29
30
  MEGABYTE = KILOBYTE * 1024
30
31
  GIGABYTE = MEGABYTE * 1024
31
32
 
33
+ # Bookmark highlight duration (seconds)
34
+ BOOKMARK_HIGHLIGHT_DURATION = 0.5
35
+
32
36
  # Line offsets
33
- CONTENT_START_LINE = 2 # コンテンツ開始行(ヘッダー2行: Y=0, Y=1
37
+ CONTENT_START_LINE = 1 # コンテンツ開始行(フッタ1行: Y=0)
34
38
 
35
39
  def initialize(test_mode: false)
36
40
  console = IO.console
@@ -76,6 +80,10 @@ module Rufio
76
80
  @cached_bookmark_time = nil
77
81
  @bookmark_cache_ttl = 1.0 # 1秒間キャッシュ
78
82
 
83
+ # Bookmark highlight (Tab ジャンプ時に 500ms ハイライト)
84
+ @highlighted_bookmark_index = nil # 1-based display index (0=start_dir, 1..9=bookmarks)
85
+ @highlighted_bookmark_time = nil
86
+
79
87
  # Command execution lamp (footer indicator)
80
88
  @completion_lamp_message = nil
81
89
  @completion_lamp_time = nil
@@ -158,6 +166,14 @@ module Rufio
158
166
 
159
167
  private
160
168
 
169
+ # ブックマークハイライトが期限切れかどうか
170
+ # @return [Boolean] true=期限切れ or ハイライト中でない, false=ハイライト中
171
+ def bookmark_highlight_expired?
172
+ return false unless @highlighted_bookmark_index && @highlighted_bookmark_time
173
+
174
+ (Time.now - @highlighted_bookmark_time) >= BOOKMARK_HIGHLIGHT_DURATION
175
+ end
176
+
161
177
  def setup_terminal
162
178
  # terminal setup
163
179
  system('tput smcup') # alternate screen
@@ -309,6 +325,13 @@ module Rufio
309
325
  needs_redraw = true
310
326
  end
311
327
 
328
+ # ブックマークハイライトのタイムアウトチェック(500ms 後に自動消去)
329
+ if bookmark_highlight_expired?
330
+ @highlighted_bookmark_index = nil
331
+ @highlighted_bookmark_time = nil
332
+ needs_redraw = true
333
+ end
334
+
312
335
  # DRAW & RENDER phase - 変更があった場合のみ描画
313
336
  if needs_redraw
314
337
  # Screenバッファに描画(clearは呼ばない。必要な部分だけ更新)
@@ -396,19 +419,18 @@ module Rufio
396
419
 
397
420
  # Phase 3: Screenバッファに描画する新しいメソッド
398
421
  def draw_screen_to_buffer(screen, notification_message = nil, fps = nil)
399
- # header (2 lines) - y=0, y=1(全モード共通)
400
- draw_header_to_buffer(screen, 0)
401
- draw_mode_tabs_to_buffer(screen, 1)
402
-
403
422
  # calculate height with header and footer margin
404
423
  content_height = @screen_height - HEADER_FOOTER_MARGIN
405
424
 
406
425
  if @in_job_mode
407
- # ジョブモード: ジョブ一覧を表示
426
+ # ジョブモード: フッタ y=0(上部)、コンテンツ y=1〜h-2、統合行 y=h-1(下部)
427
+ draw_job_footer_to_buffer(screen, 0)
408
428
  draw_job_list_to_buffer(screen, content_height)
409
- draw_job_footer_to_buffer(screen, @screen_height - 1)
429
+ draw_mode_tabs_to_buffer(screen, @screen_height - 1)
410
430
  else
411
- # 通常モード: ファイル一覧とプレビューを表示
431
+ # 通常モード: フッタ y=0(上部)、コンテンツ y=1〜h-2、統合行 y=h-1(下部)
432
+ draw_footer_to_buffer(screen, 0, fps)
433
+
412
434
  entries = get_display_entries
413
435
  selected_entry = entries[@keybind_handler.current_index]
414
436
 
@@ -421,8 +443,7 @@ module Rufio
421
443
  draw_directory_list_to_buffer(screen, entries, left_width, content_height)
422
444
  draw_file_preview_to_buffer(screen, selected_entry, right_width, content_height, left_width)
423
445
 
424
- # footer
425
- draw_footer_to_buffer(screen, @screen_height - 1, fps)
446
+ draw_mode_tabs_to_buffer(screen, @screen_height - 1)
426
447
  end
427
448
 
428
449
  # 通知メッセージがある場合は表示
@@ -543,24 +564,40 @@ module Rufio
543
564
  header += filter_text
544
565
  end
545
566
 
546
- # abbreviate if path is too long
547
- if header.length > @screen_width - HEADER_PADDING
567
+ # abbreviate if path is too long (use visual width to account for wide chars like emoji)
568
+ if TextUtils.display_width(header) > @screen_width - HEADER_PADDING
569
+ prefix = "💎 rufio v#{VERSION} - ..."
570
+ prefix_width = TextUtils.display_width(prefix)
548
571
  if @keybind_handler.help_mode?
549
572
  # prioritize showing help mode indicator
550
573
  help_text = " [Help Mode - Press ESC to exit]"
551
- base_length = @screen_width - help_text.length - FILTER_TEXT_RESERVED
552
- header = "💎 rufio v#{VERSION} - ...#{current_path[-base_length..-1]}#{help_text}"
574
+ available = [@screen_width - prefix_width - help_text.length, 0].max
575
+ header = "#{prefix}#{current_path[-available..-1]}#{help_text}"
553
576
  elsif @keybind_handler.filter_active?
554
577
  # prioritize showing filter when active
555
578
  filter_text = " [Filter: #{@keybind_handler.filter_query}]"
556
- base_length = @screen_width - filter_text.length - FILTER_TEXT_RESERVED
557
- header = "💎 rufio v#{VERSION} - ...#{current_path[-base_length..-1]}#{filter_text}"
579
+ available = [@screen_width - prefix_width - filter_text.length, 0].max
580
+ header = "#{prefix}#{current_path[-available..-1]}#{filter_text}"
558
581
  else
559
- header = "💎 rufio v#{VERSION} - ...#{current_path[-(@screen_width - FILTER_TEXT_RESERVED)..-1]}"
582
+ available = [@screen_width - prefix_width, 0].max
583
+ header = "#{prefix}#{current_path[-available..-1]}"
560
584
  end
561
585
  end
562
586
 
563
- screen.put_string(0, y, header.ljust(@screen_width), fg: "\e[7m")
587
+ # モードタブと同じ表示方法: グレー文字で1文字ずつ描画
588
+ # current_x は表示幅(カラム数)で管理(絵文字等の全角文字対応)
589
+ current_x = 0
590
+ header.each_char do |char|
591
+ char_width = TextUtils.display_width(char)
592
+ break if current_x + char_width > @screen_width
593
+ screen.put(current_x, y, char, fg: "\e[90m")
594
+ current_x += char_width
595
+ end
596
+ # 残りをスペースで埋める
597
+ while current_x < @screen_width
598
+ screen.put(current_x, y, ' ')
599
+ current_x += 1
600
+ end
564
601
  end
565
602
 
566
603
  # Phase 3: Screenバッファにモードタブを描画
@@ -571,10 +608,12 @@ module Rufio
571
608
  current_x = 0
572
609
  modes = @tab_mode_manager.available_modes
573
610
  labels = @tab_mode_manager.mode_labels
611
+ keys = @tab_mode_manager.mode_keys
574
612
  current_mode = @tab_mode_manager.current_mode
575
613
 
576
614
  modes.each_with_index do |mode, index|
577
- label = " #{labels[mode]} "
615
+ key = keys[mode]
616
+ label = key ? " #{key}:#{labels[mode]} " : " #{labels[mode]} "
578
617
 
579
618
  if mode == current_mode
580
619
  # 現在のモード: シアン背景 + 黒文字 + 太字
@@ -590,9 +629,57 @@ module Rufio
590
629
  end
591
630
  end
592
631
 
593
- # 区切り線(最後のモード以外)
632
+ # セパレータ: アクティブタブの後はPowerline塗りつぶし三角、それ以外は >
594
633
  if index < modes.length - 1
595
- screen.put(current_x, y, '│', fg: "\e[90m")
634
+ if mode == current_mode
635
+ screen.put(current_x, y, "\uE0B0", fg: "\e[36m")
636
+ else
637
+ screen.put(current_x, y, TAB_SEPARATOR, fg: "\e[90m")
638
+ end
639
+ current_x += 1
640
+ end
641
+ end
642
+
643
+ # パスとバージョン情報を行末に追加
644
+ current_path = @directory_listing.current_path
645
+ version_str = " rufio v#{VERSION}"
646
+ version_w = version_str.length # ASCII-only
647
+
648
+ remaining_w = @screen_width - current_x
649
+ # 必要な最小幅: (1) + " x " (3) + (1) + version_w
650
+ path_display_w = remaining_w - 2 - version_w
651
+
652
+ if path_display_w >= 3
653
+ # 最後のタブ→パスの区切り(最後のモードがアクティブかで色が変わる)
654
+ arrow_fg = modes.last == current_mode ? "\e[36m" : "\e[90m"
655
+ screen.put(current_x, y, TAB_SEPARATOR, fg: arrow_fg)
656
+ current_x += 1
657
+
658
+ # パスを描画(path_end までの領域)
659
+ path_end = @screen_width - 1 - version_w
660
+ path_str = " #{current_path} "
661
+ path_str.each_char do |char|
662
+ break if current_x >= path_end
663
+ char_w = TextUtils.display_width(char)
664
+ break if current_x + char_w > path_end
665
+ screen.put(current_x, y, char, fg: "\e[90m")
666
+ current_x += char_w
667
+ end
668
+
669
+ # path_end まで空白で埋める
670
+ while current_x < path_end
671
+ screen.put(current_x, y, ' ')
672
+ current_x += 1
673
+ end
674
+
675
+ # バージョンセパレータ(左向き三角、シアン色でシアン背景に遷移)
676
+ screen.put(current_x, y, "\uE0B2", fg: "\e[36m")
677
+ current_x += 1
678
+
679
+ # バージョン描画(シアン背景+黒太字)
680
+ version_str.each_char do |char|
681
+ break if current_x >= @screen_width
682
+ screen.put(current_x, y, char, fg: "\e[30m\e[1m", bg: "\e[46m")
596
683
  current_x += 1
597
684
  end
598
685
  end
@@ -608,12 +695,12 @@ module Rufio
608
695
  def sync_tab_mode_with_keybind_handler
609
696
  return unless @keybind_handler
610
697
 
611
- current_mode = if @keybind_handler.help_mode?
698
+ current_mode = if @in_job_mode || @keybind_handler.in_job_mode?
699
+ :jobs
700
+ elsif @keybind_handler.help_mode?
612
701
  :help
613
702
  elsif @keybind_handler.log_viewer_mode?
614
703
  :logs
615
- elsif @keybind_handler.in_job_mode?
616
- :jobs
617
704
  else
618
705
  :files
619
706
  end
@@ -636,20 +723,23 @@ module Rufio
636
723
  header += filter_text
637
724
  end
638
725
 
639
- # abbreviate if path is too long
640
- if header.length > @screen_width - HEADER_PADDING
726
+ # abbreviate if path is too long (use visual width to account for wide chars like emoji)
727
+ if TextUtils.display_width(header) > @screen_width - HEADER_PADDING
728
+ prefix = "💎 rufio v#{VERSION} - ..."
729
+ prefix_width = TextUtils.display_width(prefix)
641
730
  if @keybind_handler.help_mode?
642
731
  # prioritize showing help mode indicator
643
732
  help_text = " [Help Mode - Press ESC to exit]"
644
- base_length = @screen_width - help_text.length - FILTER_TEXT_RESERVED
645
- header = "💎 rufio v#{VERSION} - ...#{current_path[-base_length..-1]}#{help_text}"
733
+ available = [@screen_width - prefix_width - help_text.length, 0].max
734
+ header = "#{prefix}#{current_path[-available..-1]}#{help_text}"
646
735
  elsif @keybind_handler.filter_active?
647
736
  # prioritize showing filter when active
648
737
  filter_text = " [Filter: #{@keybind_handler.filter_query}]"
649
- base_length = @screen_width - filter_text.length - FILTER_TEXT_RESERVED
650
- header = "💎 rufio v#{VERSION} - ...#{current_path[-base_length..-1]}#{filter_text}"
738
+ available = [@screen_width - prefix_width - filter_text.length, 0].max
739
+ header = "#{prefix}#{current_path[-available..-1]}#{filter_text}"
651
740
  else
652
- header = "💎 rufio v#{VERSION} - ...#{current_path[-(@screen_width - FILTER_TEXT_RESERVED)..-1]}"
741
+ available = [@screen_width - prefix_width, 0].max
742
+ header = "#{prefix}#{current_path[-available..-1]}"
653
743
  end
654
744
  end
655
745
 
@@ -1164,7 +1254,7 @@ module Rufio
1164
1254
  unless bookmarks.empty?
1165
1255
  bookmark_parts.concat(bookmarks.take(9).map.with_index(1) { |bm, idx| "#{idx}.#{bm[:name]}" })
1166
1256
  end
1167
- bookmark_text = bookmark_parts.join(" ")
1257
+ bookmark_text = bookmark_parts.join(" ")
1168
1258
 
1169
1259
  # 右側の情報: ジョブ数 | コマンド実行ランプ | FPS(test modeの時のみ)| ?:help
1170
1260
  right_parts = []
@@ -1197,24 +1287,43 @@ module Rufio
1197
1287
  right_parts << "#{fps.round(1)} FPS"
1198
1288
  end
1199
1289
 
1200
- # ヘルプ表示
1201
- right_parts << "?:help"
1202
-
1203
1290
  right_info = right_parts.join(" | ")
1204
1291
 
1205
1292
  # ブックマーク一覧を利用可能な幅に収める
1206
- available_width = @screen_width - right_info.length - 3
1293
+ if right_info.empty?
1294
+ available_width = @screen_width
1295
+ else
1296
+ available_width = @screen_width - right_info.length - 3
1297
+ end
1207
1298
  if bookmark_text.length > available_width && available_width > 3
1208
1299
  bookmark_text = bookmark_text[0...available_width - 3] + "..."
1209
1300
  elsif available_width <= 3
1210
1301
  bookmark_text = ""
1211
1302
  end
1212
1303
 
1213
- # フッタ全体を構築
1214
- padding = @screen_width - bookmark_text.length - right_info.length
1215
- footer_content = "#{bookmark_text}#{' ' * padding}#{right_info}"
1216
- footer_content = footer_content.ljust(@screen_width)[0...@screen_width]
1217
- screen.put_string(0, y, footer_content, fg: "\e[7m")
1304
+ # フッタ全体を構築(左にブックマーク、右に情報がある場合のみ右寄せ)
1305
+ if right_info.empty?
1306
+ footer_content = bookmark_text.ljust(@screen_width)[0...@screen_width]
1307
+ else
1308
+ padding = @screen_width - bookmark_text.length - right_info.length
1309
+ footer_content = "#{bookmark_text}#{' ' * padding}#{right_info}"
1310
+ footer_content = footer_content.ljust(@screen_width)[0...@screen_width]
1311
+ end
1312
+ screen.put_string(0, y, footer_content, fg: "\e[90m")
1313
+
1314
+ # Tab ジャンプ時:対象ブックマークを 500ms ハイライト(セカンドパス)
1315
+ if @highlighted_bookmark_index && !bookmark_highlight_expired? && available_width > 3
1316
+ highlight_idx = @highlighted_bookmark_index
1317
+ if highlight_idx < bookmark_parts.length
1318
+ separator_len = 3 # " │ "
1319
+ x_pos = bookmark_parts[0...highlight_idx].sum { |p| p.length + separator_len }
1320
+ part_text = bookmark_parts[highlight_idx]
1321
+ if x_pos < available_width
1322
+ visible_len = [part_text.length, available_width - x_pos].min
1323
+ screen.put_string(x_pos, y, part_text[0...visible_len], fg: "\e[1;36m")
1324
+ end
1325
+ end
1326
+ end
1218
1327
  end
1219
1328
  end
1220
1329
 
@@ -1257,27 +1366,11 @@ module Rufio
1257
1366
  unless bookmarks.empty?
1258
1367
  bookmark_parts.concat(bookmarks.take(9).map.with_index(1) { |bm, idx| "#{idx}.#{bm[:name]}" })
1259
1368
  end
1260
- bookmark_text = bookmark_parts.join(" ")
1261
-
1262
- # ステータス情報を作成
1263
- time_info = render_time ? "#{(render_time * 1000).round(1)}ms" : "-ms"
1369
+ bookmark_text = bookmark_parts.join(" ")
1264
1370
 
1265
- # 右側の情報: 処理時間 | ?:help
1266
- right_info = "#{time_info} | ?:help"
1267
-
1268
- # ブックマーク一覧を利用可能な幅に収める
1269
- available_width = @screen_width - right_info.length - 3
1270
- if bookmark_text.length > available_width && available_width > 3
1271
- bookmark_text = bookmark_text[0...available_width - 3] + "..."
1272
- elsif available_width <= 3
1273
- bookmark_text = ""
1274
- end
1275
-
1276
- # フッタ全体を構築
1277
- padding = @screen_width - bookmark_text.length - right_info.length
1278
- footer_content = "#{bookmark_text}#{' ' * padding}#{right_info}"
1279
- footer_content = footer_content.ljust(@screen_width)[0...@screen_width]
1280
- print "\e[7m#{footer_content}\e[0m"
1371
+ # フッタ全体を構築(ブックマーク左寄せ)
1372
+ footer_content = bookmark_text.ljust(@screen_width)[0...@screen_width]
1373
+ print "\e[90m#{footer_content}\e[0m"
1281
1374
  end
1282
1375
  end
1283
1376
 
@@ -1333,12 +1426,21 @@ module Rufio
1333
1426
  end
1334
1427
  end
1335
1428
 
1336
- # Tabキーでモード切り替え
1337
- if input == "\t"
1429
+ # TabキーはFilesモードの時のみブックマーク循環移動
1430
+ if input == "\t" && @tab_mode_manager.current_mode == :files
1338
1431
  handle_tab_key
1339
1432
  return true
1340
1433
  end
1341
1434
 
1435
+ # Jobsモード中のモード切替キーをインターセプト(L:Logs, ?:Help, J:Files復帰)
1436
+ if @in_job_mode
1437
+ case input
1438
+ when 'L' then apply_mode_change(:logs); return true
1439
+ when '?' then apply_mode_change(:help); return true
1440
+ when 'J' then apply_mode_change(:files); return true
1441
+ end
1442
+ end
1443
+
1342
1444
  # キーバインドハンドラーに処理を委譲
1343
1445
  result = @keybind_handler.handle_key(input) if input
1344
1446
 
@@ -1422,10 +1524,17 @@ module Rufio
1422
1524
  end
1423
1525
  end
1424
1526
 
1425
- # Tabキーによるモード切り替え
1527
+ # Tabキー: 次のブックマークへ循環移動
1426
1528
  def handle_tab_key
1427
- @tab_mode_manager.next_mode
1428
- apply_mode_change(@tab_mode_manager.current_mode)
1529
+ next_idx = @keybind_handler.goto_next_bookmark
1530
+ if next_idx
1531
+ # display_index: 0=start_dir, 1..9=bookmarks(next_idx は 0-based bookmarks 配列)
1532
+ @highlighted_bookmark_index = next_idx + 1
1533
+ @highlighted_bookmark_time = Time.now
1534
+ # ブックマークキャッシュを即時クリア(移動先を反映させる)
1535
+ @cached_bookmarks = nil
1536
+ @cached_bookmark_time = nil
1537
+ end
1429
1538
  end
1430
1539
 
1431
1540
  # Shift+Tabによる逆順モード切り替え
data/lib/rufio/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rufio
4
- VERSION = '0.80.0'
4
+ VERSION = '0.81.0'
5
5
  end
@@ -219,12 +219,18 @@ module Rufio
219
219
  # Max 2 digits
220
220
  input_buffer = input_buffer[-2..-1] if input_buffer.length > 2
221
221
 
222
- # If number is within range, select immediately
223
222
  number = input_buffer.to_i
224
- if number > 0 && number <= display_history.length &&
225
- (number >= 10 || input_buffer.length == 1)
226
- selected_path = display_history[number - 1][:path]
227
- break
223
+ if number > 0 && number <= display_history.length
224
+ if input_buffer.length >= 2
225
+ # 2桁入力: 即時選択
226
+ selected_path = display_history[number - 1][:path]
227
+ break
228
+ elsif number * 10 > display_history.length
229
+ # 1桁入力: この数字を先頭とする2桁の有効な番号が存在しない → 即時選択
230
+ selected_path = display_history[number - 1][:path]
231
+ break
232
+ end
233
+ # else: number*10 以下のアイテムが存在しうるので次の入力を待つ
228
234
  end
229
235
  end
230
236
  end
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.80.0
4
+ version: 0.81.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-02-21 00:00:00.000000000 Z
11
+ date: 2026-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: io-console