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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d3c498939bce28d320a22b6c702a489a2104735703ba4510ad6e81457451f9b5
4
- data.tar.gz: 16b83c1e9046f78ffe672f4cf7dcfcbfc9a1e4bcde1d2928dcfaab48e9f33cfe
3
+ metadata.gz: a880862d559dd867d819889c3458abac662ae2a262a93d09c552aec45f850f06
4
+ data.tar.gz: 1ede13dbac6ef6b6d0af6f0c0d10254a0a6cfa79869b6eafa8636bbd31f2e773
5
5
  SHA512:
6
- metadata.gz: 1a50608548e316135b5de39fa2b974cdf95682c80656153a96bf450d65c833967bdea84f3597eee7c1bb049f2daf4791ed60ab926400f7a54dbfaafd14753ae4
7
- data.tar.gz: b4bb9b1e61ec2594a039e43c17e1d4d3d90f91ef382161ac7cb2df75e7b37f301e1312e4b6456af6c91941553a11119db11b0d1d415d42142785c13166bf13c0
6
+ metadata.gz: 198ef8a599e51a0bf51e9935e06329a81e4d68fa1fd065665e6f26d44013a3dc9e4419b385296f29dbfb92a44ab1e744d6a72d1352c9c2fd270f8c9f08640f95
7
+ data.tar.gz: 062a514877116a7ee3c30211628a68740911ee98844693e9caea4571da783622c59fc4a8a7fe26ee512fad62f3b296833be74d815aab4d5ff9635cccac5261f3
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.60.0] - 2026-01-24
11
+
12
+ ### Added
13
+ - **⌨️ スクリプト補完機能**: コマンドモードでスクリプトのTab補完が可能に
14
+ - `@`プレフィックスでスクリプト専用補完(例: `@bu` + Tab → `@build.sh`)
15
+ - 通常補完時も登録済みスクリプトが候補に表示
16
+ - `CommandCompletion`が`CommandMode`と連携してスクリプト候補を取得
17
+
18
+ ### Removed
19
+ - **🗑️ プロジェクトモード廃止**: `P`キーで起動するプロジェクトモードを削除
20
+ - `lib/rufio/project_mode.rb` - ProjectModeクラス
21
+ - `lib/rufio/project_command.rb` - ProjectCommandクラス
22
+ - `lib/rufio/project_log.rb` - ProjectLogクラス
23
+ - 関連するUI描画メソッド(`draw_project_mode_screen`等)
24
+ - 関連するキーハンドリング(`handle_project_mode_key`等)
25
+ - 関連するテストファイル
26
+
27
+ ### Changed
28
+ - **📋 ヘルプ表示更新**: ヘルプダイアログに`J`キー(Job mode)を追加
29
+ - **🧹 コードクリーンアップ**: プロジェクトモード関連の未使用コードを削除
30
+
31
+ ### Technical Details
32
+ - **テストカバレッジ**: 684 tests, 2474 assertions (all passing)
33
+ - **削除ファイル**: 7ファイル(ライブラリ3、テスト4)
34
+ - **影響範囲**: `keybind_handler.rb`, `terminal_ui.rb`, `rufio.rb`
35
+
10
36
  ## [0.41.0] - 2026-01-13
11
37
 
12
38
  ### Changed
data/README.md CHANGED
@@ -8,13 +8,13 @@ Ruby製のターミナルベースファイルマネージャー
8
8
 
9
9
  rufioは、Yaziにインスパイアされたターミナル上で動作するファイルマネージャーです。Rubyで実装されており、DSLコマンドによる拡張機能を備えています。軽量で高速な操作性を提供し、ファイルの閲覧・管理・検索機能を備えています。
10
10
 
11
- ### 🚀 v0.33.0の重要な更新
11
+ ### 🚀 v0.60.0の重要な更新
12
12
 
13
- - **🚨 重大なパフォーマンス改善**: ファイルプレビューが40-86倍高速化(80ms → 1-2ms)
14
- - **⚡ Zig実装追加**: 最小バイナリサイズ(52.6 KB)のネイティブスキャナー
15
- - **📊 YJIT対応**: Ruby 3.4+ のJITコンパイラで全体的な性能向上
13
+ - **⌨️ スクリプト補完機能**: コマンドモードでスクリプトのTab補完が可能に
14
+ - **🗑️ プロジェクトモード廃止**: `P`キーのプロジェクトモードを削除(ジョブモード`J`に機能統合予定)
15
+ - **🧹 コードクリーンアップ**: 未使用コードの削除でコードベースをスリム化
16
16
 
17
- 詳細は [CHANGELOG v0.33.0](./docs/CHANGELOG_v0.33.0.md) を参照してください。
17
+ 詳細は [CHANGELOG](./CHANGELOG.md) を参照してください。
18
18
 
19
19
  ## 特徴
20
20
 
@@ -215,10 +215,16 @@ rufio --help # ヘルプメッセージを表示
215
215
  | キー | 機能 |
216
216
  | --------- | ------------------------------------ |
217
217
  | `b` | 現在のディレクトリをブックマークに追加 |
218
- | `P` | プロジェクトモードに入る (v0.33.0で変更) |
218
+ | `B` | ブックマークメニュー(スクリプトパス管理含む) |
219
219
  | `0` | 起動したディレクトリに移動 |
220
220
  | `1`-`9` | 対応する番号のブックマークに移動 |
221
221
 
222
+ #### ジョブモード
223
+
224
+ | キー | 機能 |
225
+ | --------- | ------------------------------------ |
226
+ | `J` | ジョブモードに入る |
227
+
222
228
  #### zoxide連携機能
223
229
 
224
230
  | キー | 機能 |
@@ -371,17 +377,11 @@ rufio --help # ヘルプメッセージを表示
371
377
  - 重複パスは自動検出
372
378
  - 緑色のダイアログで表示(作成操作)
373
379
 
374
- **プロジェクトモード (`p`キー):**
375
- - **`SPACE`キー**: ブックマークをプロジェクトとして選択
376
- - 選択されたブックマークは✓マークと緑背景で表示
377
- - **`r`キー**: カーソル位置のブックマークをリネーム
378
- - 黄色のダイアログで入力
379
- - 前後のスペースは自動削除
380
- - **`d`キー**: カーソル位置のブックマークを削除
381
- - 赤色の確認ダイアログ表示
382
- - **`l`キー**: ログモードに入る
383
- - **`:`キー**: コマンドモード起動
384
- - **`ESC`キー**: プロジェクトモードを終了
380
+ **ブックマークメニュー (`B`キー):**
381
+ - **`1`**: 現在のディレクトリをブックマークに追加
382
+ - **`2`**: 現在のディレクトリをスクリプトパスに追加
383
+ - **`3`**: スクリプトパスを管理(一覧表示、削除、ジャンプ)
384
+ - **`4`**: ブックマーク一覧を表示
385
385
 
386
386
  #### 高速ナビゲーション (`1`-`9`)
387
387
 
@@ -396,32 +396,27 @@ rufio --help # ヘルプメッセージを表示
396
396
  - 名前の前後のスペースは自動削除されて保存される
397
397
  - JSONファイルを直接編集することも可能
398
398
 
399
- ### プロジェクトモード機能の詳細
399
+ ### スクリプトパス機能の詳細
400
400
 
401
- #### プロジェクトモード (`p`)
401
+ #### スクリプトパスとは
402
402
 
403
- プロジェクトモードでは、ブックマークしたディレクトリをプロジェクトとして管理できます。
403
+ スクリプトパスは、スクリプトファイルを配置するディレクトリを登録する機能です。登録したディレクトリ内のスクリプトは、コマンドモードで`@`プレフィックスを使って実行できます。
404
404
 
405
- #### スクリプト実行機能
405
+ #### スクリプトパスの管理 (`B` → `3`)
406
406
 
407
- - **スクリプトディレクトリ**: `~/.config/rufio/scripts`
408
- - 初回起動時に自動作成
409
- - `config.rb`でカスタマイズ可能
410
- - サンプルスクリプト `hello.rb` が含まれる
411
- - **スクリプト実行**: `:`キーでコマンドモード起動
412
- - `.rb`ファイルを一覧表示
413
- - 選択したスクリプトを実行
414
- - プロジェクトコンテキストで実行
407
+ - **一覧表示**: 登録済みのスクリプトパスを一覧表示
408
+ - **j/k**: カーソル移動
409
+ - **d**: 選択したパスを削除
410
+ - **Enter**: 選択したディレクトリにジャンプ
411
+ - **ESC**: メニューを閉じる
415
412
 
416
- #### UI表示
413
+ #### スクリプト補完機能
417
414
 
418
- プロジェクトモードのUIは通常モードと統一されています:
415
+ コマンドモード(`:`)でスクリプトをTab補完できます:
419
416
 
420
- - **選択マーク**: ✓(チェックマーク)で選択を表示
421
- - **選択済みブックマーク**: 緑背景、黒文字で表示
422
- - **カーソル位置**: 設定ファイルの選択色でハイライト
423
- - **セパレータ**: 左右画面の間に`│`を表示
424
- - **フッター**: 反転表示でキーバインドを表示
417
+ - **`@`プレフィックス**: スクリプト専用補完
418
+ - 例: `@bu` + Tab → `@build.sh`
419
+ - **通常補完**: 内部コマンドとスクリプト両方を候補に表示
425
420
 
426
421
  #### 操作色の統一
427
422
 
@@ -429,6 +424,7 @@ rufio --help # ヘルプメッセージを表示
429
424
  - **青色ダイアログ**: ディレクトリ作成
430
425
  - **黄色ダイアログ**: リネーム操作
431
426
  - **赤色ダイアログ**: 削除操作
427
+ - **マゼンタダイアログ**: スクリプトパス管理
432
428
 
433
429
  ### zoxide連携機能の詳細
434
430
 
@@ -0,0 +1,8 @@
1
+ # rufio 設定ファイル (YAML)
2
+ # このファイルを ~/.config/rufio/config.yml に配置してください
3
+
4
+ # スクリプトパス - @コマンドで実行可能なスクリプトを検索するディレクトリ
5
+ script_paths:
6
+ - ~/.config/rufio/scripts/
7
+ - ~/scripts/
8
+ - ~/devs/scripts/
@@ -5,8 +5,9 @@ module Rufio
5
5
  class CommandCompletion
6
6
  # 初期化
7
7
  # @param history [CommandHistory, nil] コマンド履歴(オプション)
8
- def initialize(history = nil)
9
- @command_mode = CommandMode.new
8
+ # @param command_mode [CommandMode, nil] コマンドモード(スクリプト補完に使用)
9
+ def initialize(history = nil, command_mode = nil)
10
+ @command_mode = command_mode || CommandMode.new
10
11
  @shell_completion = ShellCommandCompletion.new
11
12
  @history = history
12
13
  end
@@ -15,9 +16,9 @@ module Rufio
15
16
  # @param input [String] 入力されたテキスト
16
17
  # @return [Array<String>] 補完候補のリスト
17
18
  def complete(input)
18
- # 入力が空の場合は内部コマンドを返す
19
+ # 入力が空の場合は内部コマンドとスクリプトを返す
19
20
  if input.nil? || input.strip.empty?
20
- return @command_mode.available_commands.map(&:to_s)
21
+ return @command_mode.available_commands.map(&:to_s) + script_candidates('')
21
22
  end
22
23
 
23
24
  # シェルコマンド補完(!で始まる場合)
@@ -25,7 +26,12 @@ module Rufio
25
26
  return complete_shell_command(input.strip)
26
27
  end
27
28
 
28
- # 通常のコマンド補完(内部コマンド)
29
+ # スクリプト補完(@で始まる場合)
30
+ if input.strip.start_with?('@')
31
+ return @command_mode.complete_script(input.strip)
32
+ end
33
+
34
+ # 通常のコマンド補完(内部コマンド + スクリプト)
29
35
  available_commands = @command_mode.available_commands.map(&:to_s)
30
36
  input_lower = input.downcase
31
37
  candidates = available_commands.select do |command|
@@ -61,6 +67,15 @@ module Rufio
61
67
 
62
68
  private
63
69
 
70
+ # スクリプト候補を取得
71
+ # @param prefix [String] 入力中の文字列
72
+ # @return [Array<String>] スクリプト候補(@付き)
73
+ def script_candidates(prefix)
74
+ return [] unless @command_mode&.script_runner
75
+
76
+ @command_mode.complete_script("@#{prefix}")
77
+ end
78
+
64
79
  # シェルコマンドの補完
65
80
  # @param input [String] ! で始まる入力
66
81
  # @return [Array<String>] 補完候補のリスト
@@ -7,19 +7,54 @@ module Rufio
7
7
  # すべてのコマンドはDslCommandとして扱われる
8
8
  class CommandMode
9
9
  attr_accessor :background_executor
10
+ attr_reader :script_runner, :script_path_manager
10
11
 
11
12
  def initialize(background_executor = nil)
12
13
  @commands = {}
13
14
  @background_executor = background_executor
15
+ @script_runner = nil
16
+ @script_path_manager = nil
17
+ @job_manager = nil
14
18
  load_builtin_commands
15
19
  load_dsl_commands
16
20
  end
17
21
 
22
+ # ScriptRunnerを設定する
23
+ # @param script_paths [Array<String>] スクリプトパス
24
+ # @param job_manager [JobManager] ジョブマネージャー
25
+ def setup_script_runner(script_paths:, job_manager:)
26
+ @job_manager = job_manager
27
+ @script_runner = ScriptRunner.new(
28
+ script_paths: script_paths,
29
+ job_manager: job_manager
30
+ )
31
+ end
32
+
33
+ # ScriptPathManagerを設定する(設定ファイルベース)
34
+ # @param config_file [String] 設定ファイルのパス
35
+ # @param job_manager [JobManager] ジョブマネージャー
36
+ def setup_script_path_manager(config_file:, job_manager:)
37
+ @job_manager = job_manager
38
+ @script_path_manager = ScriptPathManager.new(config_file)
39
+ # ScriptRunnerも設定(ScriptPathManagerのパスを使用)
40
+ @script_runner = ScriptRunner.new(
41
+ script_paths: @script_path_manager.paths,
42
+ job_manager: job_manager
43
+ )
44
+ end
45
+
18
46
  # コマンドを実行する
19
- def execute(command_string)
47
+ # @param command_string [String] コマンド文字列
48
+ # @param working_dir [String, nil] 作業ディレクトリ(スクリプト実行時に使用)
49
+ def execute(command_string, working_dir: nil)
20
50
  # 空のコマンドは無視
21
51
  return nil if command_string.nil? || command_string.strip.empty?
22
52
 
53
+ # スクリプト実行 (@ で始まる場合)
54
+ if command_string.strip.start_with?('@')
55
+ return execute_script(command_string.strip[1..-1], working_dir)
56
+ end
57
+
23
58
  # シェルコマンドの実行 (! で始まる場合)
24
59
  if command_string.strip.start_with?('!')
25
60
  shell_command = command_string.strip[1..-1]
@@ -42,12 +77,18 @@ module Rufio
42
77
 
43
78
  # 統一されたコマンドストアから検索
44
79
  command = @commands[command_name]
45
- unless command
46
- return "⚠️ コマンドが見つかりません: #{command_name}"
80
+ if command
81
+ # 内部コマンドを実行
82
+ return execute_unified_command(command_name, command)
47
83
  end
48
84
 
49
- # 統一された実行パス
50
- execute_unified_command(command_name, command)
85
+ # 内部コマンドが見つからない場合、スクリプトパスから検索
86
+ if @script_path_manager || @script_runner
87
+ script_result = try_execute_script_from_paths(command_string.strip, working_dir)
88
+ return script_result if script_result
89
+ end
90
+
91
+ "⚠️ コマンドが見つかりません: #{command_name}"
51
92
  end
52
93
 
53
94
  # 利用可能なコマンドのリストを取得
@@ -87,6 +128,17 @@ module Rufio
87
128
  end
88
129
  end
89
130
 
131
+ # スクリプト名を補完する
132
+ # @param prefix [String] 入力中の文字列(@を含む)
133
+ # @return [Array<String>] 補完候補(@付き)
134
+ def complete_script(prefix)
135
+ return [] unless @script_runner
136
+
137
+ # @を除去して検索
138
+ search_prefix = prefix.sub(/^@/, '')
139
+ @script_runner.complete(search_prefix).map { |name| "@#{name}" }
140
+ end
141
+
90
142
  private
91
143
 
92
144
  # 組み込みコマンドをロードする
@@ -150,5 +202,46 @@ module Rufio
150
202
  { success: false, error: "コマンド実行エラー: #{e.message}" }
151
203
  end
152
204
  end
205
+
206
+ # スクリプトを実行する(@プレフィックス用)
207
+ # @param script_name [String] スクリプト名
208
+ # @param working_dir [String, nil] 作業ディレクトリ
209
+ # @return [String] 実行結果メッセージ
210
+ def execute_script(script_name, working_dir)
211
+ unless @script_runner
212
+ return "⚠️ スクリプトランナーが設定されていません"
213
+ end
214
+
215
+ working_dir ||= Dir.pwd
216
+
217
+ job = @script_runner.run(script_name, working_dir: working_dir)
218
+
219
+ if job
220
+ "🚀 ジョブを開始: #{script_name}"
221
+ else
222
+ "⚠️ スクリプトが見つかりません: #{script_name}"
223
+ end
224
+ end
225
+
226
+ # スクリプトパスからスクリプトを検索して実行を試みる
227
+ # @param command_name [String] コマンド名
228
+ # @param working_dir [String, nil] 作業ディレクトリ
229
+ # @return [String, nil] 実行結果メッセージ、見つからない場合nil
230
+ def try_execute_script_from_paths(command_name, working_dir)
231
+ return nil unless @script_runner
232
+
233
+ script = @script_runner.find_script(command_name)
234
+ return nil unless script
235
+
236
+ working_dir ||= Dir.pwd
237
+
238
+ job = @script_runner.run(command_name, working_dir: working_dir)
239
+
240
+ if job
241
+ "🚀 ジョブを開始: #{script[:name]}"
242
+ else
243
+ nil
244
+ end
245
+ end
153
246
  end
154
247
  end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'yaml'
3
4
  require_relative 'config'
4
5
 
5
6
  module Rufio
6
7
  class ConfigLoader
7
8
  CONFIG_PATH = File.expand_path('~/.config/rufio/config.rb').freeze
9
+ YAML_CONFIG_PATH = File.expand_path('~/.config/rufio/config.yml').freeze
8
10
 
9
11
  class << self
10
12
  def load_config
@@ -60,6 +62,41 @@ module Rufio
60
62
  load_config[:command_history_size] || 1000
61
63
  end
62
64
 
65
+ # スクリプトパスの配列を取得
66
+ # @return [Array<String>] 展開済みのスクリプトパス
67
+ def script_paths
68
+ yaml_config = load_yaml_config
69
+ paths = yaml_config[:script_paths] || default_script_paths
70
+ expand_script_paths(paths)
71
+ end
72
+
73
+ # デフォルトのスクリプトパス
74
+ # @return [Array<String>] デフォルトパス
75
+ def default_script_paths
76
+ [File.expand_path('~/.config/rufio/scripts')]
77
+ end
78
+
79
+ # スクリプトパスを展開
80
+ # @param paths [Array<String>] パスの配列
81
+ # @return [Array<String>] 展開済みのパス
82
+ def expand_script_paths(paths)
83
+ paths.map { |p| File.expand_path(p) }
84
+ end
85
+
86
+ # YAML設定ファイルを読み込む
87
+ # @param path [String, nil] 設定ファイルのパス(nilの場合はデフォルト)
88
+ # @return [Hash] 設定内容
89
+ def load_yaml_config(path = nil)
90
+ config_path = path || YAML_CONFIG_PATH
91
+ return {} unless File.exist?(config_path)
92
+
93
+ yaml = YAML.safe_load(File.read(config_path), symbolize_names: true)
94
+ yaml || {}
95
+ rescue StandardError => e
96
+ warn "Failed to load YAML config: #{e.message}"
97
+ {}
98
+ end
99
+
63
100
  private
64
101
 
65
102
  def load_config_file
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'task_status'
4
+
5
+ module Rufio
6
+ # 複数のジョブを管理するクラス
7
+ # ジョブの追加、状態追跡、通知連携を行う
8
+ class JobManager
9
+ attr_reader :jobs
10
+
11
+ def initialize(notification_manager: nil)
12
+ @jobs = []
13
+ @notification_manager = notification_manager
14
+ @next_id = 1
15
+ end
16
+
17
+ # ジョブを追加
18
+ # @param name [String] ジョブ名
19
+ # @param path [String] 実行ディレクトリ
20
+ # @param command [String] 実行コマンド
21
+ # @return [TaskStatus] 作成されたジョブ
22
+ def add_job(name:, path:, command:)
23
+ job = TaskStatus.new(
24
+ id: @next_id,
25
+ name: name,
26
+ path: path
27
+ )
28
+ # commandを保存(TaskStatusに追加)
29
+ job.instance_variable_set(:@command, command)
30
+ job.define_singleton_method(:command) { @command }
31
+
32
+ @jobs << job
33
+ @next_id += 1
34
+ job
35
+ end
36
+
37
+ # IDでジョブを検索
38
+ # @param id [Integer] ジョブID
39
+ # @return [TaskStatus, nil]
40
+ def find_job(id)
41
+ @jobs.find { |job| job.id == id }
42
+ end
43
+
44
+ # ジョブ数を取得
45
+ # @return [Integer]
46
+ def job_count
47
+ @jobs.size
48
+ end
49
+
50
+ # 実行中のジョブ数
51
+ # @return [Integer]
52
+ def running_count
53
+ @jobs.count(&:running?)
54
+ end
55
+
56
+ # 完了したジョブ数
57
+ # @return [Integer]
58
+ def completed_count
59
+ @jobs.count(&:completed?)
60
+ end
61
+
62
+ # 失敗したジョブ数
63
+ # @return [Integer]
64
+ def failed_count
65
+ @jobs.count(&:failed?)
66
+ end
67
+
68
+ # ステータスサマリーを取得
69
+ # @return [Hash] { total:, running:, done:, failed: }
70
+ def status_summary
71
+ {
72
+ total: @jobs.size,
73
+ running: running_count,
74
+ done: completed_count,
75
+ failed: failed_count
76
+ }
77
+ end
78
+
79
+ # ジョブをキャンセル
80
+ # @param id [Integer] ジョブID
81
+ # @return [Boolean] キャンセル成功かどうか
82
+ def cancel_job(id)
83
+ job = find_job(id)
84
+ return false unless job
85
+
86
+ job.cancel
87
+ true
88
+ end
89
+
90
+ # 完了したジョブをクリア
91
+ def clear_completed
92
+ @jobs.reject! { |job| job.completed? || job.cancelled? }
93
+ end
94
+
95
+ # ステータスバー用のテキストを生成
96
+ # @return [String] "3 jobs: 2 running, 1 done" のような形式
97
+ def status_bar_text
98
+ summary = status_summary
99
+ "#{summary[:total]} jobs: #{summary[:running]} running, #{summary[:done]} done"
100
+ end
101
+
102
+ # ジョブがあるかどうか
103
+ # @return [Boolean]
104
+ def has_jobs?
105
+ !@jobs.empty?
106
+ end
107
+
108
+ # 実行中のジョブがあるかどうか
109
+ # @return [Boolean]
110
+ def any_running?
111
+ @jobs.any?(&:running?)
112
+ end
113
+
114
+ # ジョブ完了時の通知を送信(NotificationManagerと連携)
115
+ # @param job [TaskStatus] 完了したジョブ
116
+ def notify_completion(job)
117
+ return unless @notification_manager
118
+
119
+ type = job.completed? ? :success : :error
120
+ @notification_manager.add(
121
+ job.name,
122
+ type,
123
+ duration: job.duration || 0,
124
+ exit_code: job.exit_code
125
+ )
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rufio
4
+ # ジョブモードのUI管理クラス
5
+ # ジョブ一覧の表示、選択、操作を行う
6
+ class JobMode
7
+ attr_reader :selected_index
8
+
9
+ def initialize(job_manager:)
10
+ @job_manager = job_manager
11
+ @active = false
12
+ @selected_index = 0
13
+ @log_mode = false
14
+ end
15
+
16
+ # ジョブモードを有効化
17
+ def activate
18
+ @active = true
19
+ @selected_index = 0
20
+ @log_mode = false
21
+ end
22
+
23
+ # ジョブモードを無効化
24
+ def deactivate
25
+ @active = false
26
+ @log_mode = false
27
+ end
28
+
29
+ # ジョブモードが有効かどうか
30
+ # @return [Boolean]
31
+ def active?
32
+ @active
33
+ end
34
+
35
+ # ログモードが有効かどうか
36
+ # @return [Boolean]
37
+ def log_mode?
38
+ @log_mode
39
+ end
40
+
41
+ # ログモードに入る
42
+ def enter_log_mode
43
+ @log_mode = true
44
+ end
45
+
46
+ # ログモードを終了
47
+ def exit_log_mode
48
+ @log_mode = false
49
+ end
50
+
51
+ # 下に移動
52
+ def move_down
53
+ max_index = [@job_manager.job_count - 1, 0].max
54
+ @selected_index = [@selected_index + 1, max_index].min
55
+ end
56
+
57
+ # 上に移動
58
+ def move_up
59
+ @selected_index = [@selected_index - 1, 0].max
60
+ end
61
+
62
+ # 先頭に移動
63
+ def move_to_top
64
+ @selected_index = 0
65
+ end
66
+
67
+ # 末尾に移動
68
+ def move_to_bottom
69
+ @selected_index = [@job_manager.job_count - 1, 0].max
70
+ end
71
+
72
+ # 選択中のジョブを取得
73
+ # @return [TaskStatus, nil]
74
+ def selected_job
75
+ return nil if @job_manager.jobs.empty?
76
+
77
+ @job_manager.jobs[@selected_index]
78
+ end
79
+
80
+ # 選択中のジョブをキャンセル
81
+ # @return [Boolean]
82
+ def cancel_selected_job
83
+ job = selected_job
84
+ return false unless job
85
+
86
+ @job_manager.cancel_job(job.id)
87
+ end
88
+
89
+ # キー入力を処理
90
+ # @param key [String] 入力されたキー
91
+ # @return [Boolean, Symbol] 処理結果
92
+ def handle_key(key)
93
+ # ログモード中の処理
94
+ if @log_mode
95
+ return handle_log_mode_key(key)
96
+ end
97
+
98
+ case key
99
+ when 'j'
100
+ move_down
101
+ true
102
+ when 'k'
103
+ move_up
104
+ true
105
+ when 'g'
106
+ move_to_top
107
+ true
108
+ when 'G'
109
+ move_to_bottom
110
+ true
111
+ when 'x'
112
+ cancel_selected_job
113
+ true
114
+ when ' ' # Space
115
+ enter_log_mode if selected_job
116
+ :show_log
117
+ when "\e" # Escape
118
+ deactivate
119
+ :exit
120
+ else
121
+ false
122
+ end
123
+ end
124
+
125
+ private
126
+
127
+ # ログモード中のキー処理
128
+ # @param key [String] 入力されたキー
129
+ # @return [Boolean, Symbol]
130
+ def handle_log_mode_key(key)
131
+ case key
132
+ when "\e" # Escape
133
+ exit_log_mode
134
+ true
135
+ when 'j'
136
+ # ログのスクロール(将来実装)
137
+ true
138
+ when 'k'
139
+ # ログのスクロール(将来実装)
140
+ true
141
+ else
142
+ false
143
+ end
144
+ end
145
+ end
146
+ end