rufio 0.31.0 → 0.32.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 +109 -12
- data/README.md +21 -3
- data/README_EN.md +30 -2
- data/docs/CHANGELOG_v0.32.0.md +288 -0
- data/{CHANGELOG_v0.4.0.md → docs/CHANGELOG_v0.4.0.md} +2 -2
- data/{CHANGELOG_v0.5.0.md → docs/CHANGELOG_v0.5.0.md} +3 -0
- data/{CHANGELOG_v0.7.0.md → docs/CHANGELOG_v0.7.0.md} +1 -1
- data/{CHANGELOG_v0.8.0.md → docs/CHANGELOG_v0.8.0.md} +1 -1
- data/{CHANGELOG_v0.9.0.md → docs/CHANGELOG_v0.9.0.md} +1 -1
- data/lib/rufio/command_completion.rb +101 -0
- data/lib/rufio/command_history.rb +109 -0
- data/lib/rufio/command_mode.rb +35 -0
- data/lib/rufio/command_mode_ui.rb +48 -15
- data/lib/rufio/config_loader.rb +9 -0
- data/lib/rufio/plugins/hello.rb +30 -0
- data/lib/rufio/shell_command_completion.rb +120 -0
- data/lib/rufio/terminal_ui.rb +89 -4
- data/lib/rufio/version.rb +1 -1
- data/lib/rufio.rb +6 -0
- metadata +18 -13
- /data/{CHANGELOG_v0.10.0.md → docs/CHANGELOG_v0.10.0.md} +0 -0
- /data/{CHANGELOG_v0.20.0.md → docs/CHANGELOG_v0.20.0.md} +0 -0
- /data/{CHANGELOG_v0.21.0.md → docs/CHANGELOG_v0.21.0.md} +0 -0
- /data/{CHANGELOG_v0.30.0.md → docs/CHANGELOG_v0.30.0.md} +0 -0
- /data/{CHANGELOG_v0.31.0.md → docs/CHANGELOG_v0.31.0.md} +0 -0
- /data/{CHANGELOG_v0.6.0.md → docs/CHANGELOG_v0.6.0.md} +0 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rufio
|
|
4
|
+
# コマンド補完機能を提供するクラス
|
|
5
|
+
class CommandCompletion
|
|
6
|
+
# 初期化
|
|
7
|
+
# @param history [CommandHistory, nil] コマンド履歴(オプション)
|
|
8
|
+
def initialize(history = nil)
|
|
9
|
+
@command_mode = CommandMode.new
|
|
10
|
+
@shell_completion = ShellCommandCompletion.new
|
|
11
|
+
@history = history
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# コマンドの補完候補を取得
|
|
15
|
+
# @param input [String] 入力されたテキスト
|
|
16
|
+
# @return [Array<String>] 補完候補のリスト
|
|
17
|
+
def complete(input)
|
|
18
|
+
# 入力が空の場合は内部コマンドを返す
|
|
19
|
+
if input.nil? || input.strip.empty?
|
|
20
|
+
return @command_mode.available_commands.map(&:to_s)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# シェルコマンド補完(!で始まる場合)
|
|
24
|
+
if input.strip.start_with?('!')
|
|
25
|
+
return complete_shell_command(input.strip)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# 通常のコマンド補完(内部コマンド)
|
|
29
|
+
available_commands = @command_mode.available_commands.map(&:to_s)
|
|
30
|
+
input_lower = input.downcase
|
|
31
|
+
candidates = available_commands.select do |command|
|
|
32
|
+
command.downcase.start_with?(input_lower)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
candidates
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# 補完候補の共通プレフィックスを取得
|
|
39
|
+
# @param input [String] 入力されたテキスト
|
|
40
|
+
# @return [String] 共通プレフィックス
|
|
41
|
+
def common_prefix(input)
|
|
42
|
+
candidates = complete(input)
|
|
43
|
+
|
|
44
|
+
# 候補がない場合は元の入力を返す
|
|
45
|
+
return input if candidates.empty?
|
|
46
|
+
|
|
47
|
+
# 候補が1つの場合はそのコマンド名を返す
|
|
48
|
+
return candidates.first if candidates.size == 1
|
|
49
|
+
|
|
50
|
+
# 複数の候補がある場合、共通プレフィックスを計算
|
|
51
|
+
min_candidate = candidates.min
|
|
52
|
+
max_candidate = candidates.max
|
|
53
|
+
|
|
54
|
+
min_candidate.chars.zip(max_candidate.chars).each_with_index do |(char_min, char_max), index|
|
|
55
|
+
return min_candidate[0...index] if char_min != char_max
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# すべての文字が一致した場合は最小の候補を返す
|
|
59
|
+
min_candidate
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
# シェルコマンドの補完
|
|
65
|
+
# @param input [String] ! で始まる入力
|
|
66
|
+
# @return [Array<String>] 補完候補のリスト
|
|
67
|
+
def complete_shell_command(input)
|
|
68
|
+
# ! を除去
|
|
69
|
+
command_part = input[1..-1]
|
|
70
|
+
|
|
71
|
+
# スペースが含まれる場合、コマンドと引数に分離
|
|
72
|
+
if command_part.include?(' ')
|
|
73
|
+
parts = command_part.split(' ', 2)
|
|
74
|
+
cmd = parts[0]
|
|
75
|
+
arg = parts[1] || ''
|
|
76
|
+
|
|
77
|
+
# 引数部分がパスっぽい場合、ファイルパス補完
|
|
78
|
+
if arg.include?('/') || arg.start_with?('~')
|
|
79
|
+
path_candidates = @shell_completion.complete_path(arg)
|
|
80
|
+
return path_candidates.map { |path| "!#{cmd} #{path}" }
|
|
81
|
+
else
|
|
82
|
+
# 引数部分のファイル補完(カレントディレクトリ)
|
|
83
|
+
path_candidates = @shell_completion.complete_path(arg)
|
|
84
|
+
return path_candidates.map { |path| "!#{cmd} #{path}" }
|
|
85
|
+
end
|
|
86
|
+
else
|
|
87
|
+
# コマンド名の補完
|
|
88
|
+
cmd_candidates = @shell_completion.complete_command(command_part)
|
|
89
|
+
|
|
90
|
+
# 履歴からの補完も追加
|
|
91
|
+
if @history
|
|
92
|
+
history_candidates = @shell_completion.complete_from_history(command_part, @history)
|
|
93
|
+
cmd_candidates += history_candidates
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# ! を付けて返す
|
|
97
|
+
cmd_candidates.uniq.map { |cmd| "!#{cmd}" }
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rufio
|
|
4
|
+
# コマンド履歴管理クラス
|
|
5
|
+
class CommandHistory
|
|
6
|
+
DEFAULT_MAX_SIZE = 1000
|
|
7
|
+
|
|
8
|
+
attr_reader :size
|
|
9
|
+
|
|
10
|
+
# 初期化
|
|
11
|
+
# @param history_file [String] 履歴ファイルのパス
|
|
12
|
+
# @param max_size [Integer] 履歴の最大保存数
|
|
13
|
+
def initialize(history_file, max_size: DEFAULT_MAX_SIZE)
|
|
14
|
+
@history_file = history_file
|
|
15
|
+
@max_size = max_size
|
|
16
|
+
@history = []
|
|
17
|
+
@position = -1 # -1 = 最新の位置(履歴の外)
|
|
18
|
+
|
|
19
|
+
load_from_file if File.exist?(@history_file)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# コマンドを履歴に追加
|
|
23
|
+
# @param command [String] 追加するコマンド
|
|
24
|
+
def add(command)
|
|
25
|
+
# 空のコマンドは無視
|
|
26
|
+
return if command.nil? || command.strip.empty?
|
|
27
|
+
|
|
28
|
+
# 連続する重複は無視
|
|
29
|
+
return if !@history.empty? && @history.last == command
|
|
30
|
+
|
|
31
|
+
@history << command
|
|
32
|
+
|
|
33
|
+
# 最大サイズを超えたら古いものを削除
|
|
34
|
+
@history.shift if @history.size > @max_size
|
|
35
|
+
|
|
36
|
+
# 位置をリセット
|
|
37
|
+
reset_position
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# 前のコマンドを取得(上矢印キー相当)
|
|
41
|
+
# @return [String, nil] 前のコマンド、存在しない場合はnil
|
|
42
|
+
def previous
|
|
43
|
+
return nil if @history.empty?
|
|
44
|
+
|
|
45
|
+
# 初回は最後のコマンドを返す
|
|
46
|
+
if @position == -1
|
|
47
|
+
@position = @history.size - 1
|
|
48
|
+
return @history[@position]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# これ以上前がない場合
|
|
52
|
+
return nil if @position <= 0
|
|
53
|
+
|
|
54
|
+
# 一つ前に移動
|
|
55
|
+
@position -= 1
|
|
56
|
+
@history[@position]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# 次のコマンドを取得(下矢印キー相当)
|
|
60
|
+
# @return [String] 次のコマンド、最新位置の場合は空文字列
|
|
61
|
+
def next
|
|
62
|
+
return "" if @history.empty? || @position == -1
|
|
63
|
+
|
|
64
|
+
# 一つ次に移動
|
|
65
|
+
@position += 1
|
|
66
|
+
|
|
67
|
+
# 最新位置を超えた場合は空文字列を返す
|
|
68
|
+
if @position >= @history.size
|
|
69
|
+
@position = -1
|
|
70
|
+
return ""
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
@history[@position]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# 履歴をファイルに保存
|
|
77
|
+
def save
|
|
78
|
+
File.write(@history_file, @history.join("\n") + "\n")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# 位置をリセット(新しいコマンド入力時などに使用)
|
|
82
|
+
def reset_position
|
|
83
|
+
@position = -1
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# 履歴のサイズを取得
|
|
87
|
+
def size
|
|
88
|
+
@history.size
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
# ファイルから履歴を読み込む
|
|
94
|
+
def load_from_file
|
|
95
|
+
return unless File.exist?(@history_file)
|
|
96
|
+
|
|
97
|
+
File.readlines(@history_file, chomp: true).each do |line|
|
|
98
|
+
next if line.strip.empty?
|
|
99
|
+
|
|
100
|
+
@history << line
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# 最大サイズを超えている場合は調整
|
|
104
|
+
while @history.size > @max_size
|
|
105
|
+
@history.shift
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
data/lib/rufio/command_mode.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'open3'
|
|
4
|
+
|
|
3
5
|
module Rufio
|
|
4
6
|
# コマンドモード - プラグインコマンドを実行するためのインターフェース
|
|
5
7
|
class CommandMode
|
|
@@ -13,6 +15,11 @@ module Rufio
|
|
|
13
15
|
# 空のコマンドは無視
|
|
14
16
|
return nil if command_string.nil? || command_string.strip.empty?
|
|
15
17
|
|
|
18
|
+
# シェルコマンドの実行 (! で始まる場合)
|
|
19
|
+
if command_string.strip.start_with?('!')
|
|
20
|
+
return execute_shell_command(command_string.strip[1..-1])
|
|
21
|
+
end
|
|
22
|
+
|
|
16
23
|
# コマンド名を取得 (前後の空白を削除)
|
|
17
24
|
command_name = command_string.strip.to_sym
|
|
18
25
|
|
|
@@ -48,6 +55,34 @@ module Rufio
|
|
|
48
55
|
|
|
49
56
|
private
|
|
50
57
|
|
|
58
|
+
# シェルコマンドを実行する
|
|
59
|
+
def execute_shell_command(shell_command)
|
|
60
|
+
# コマンドが空の場合
|
|
61
|
+
return { success: false, error: "コマンドが指定されていません" } if shell_command.strip.empty?
|
|
62
|
+
|
|
63
|
+
begin
|
|
64
|
+
# Open3を使って標準出力と標準エラーを分離して取得
|
|
65
|
+
stdout, stderr, status = Open3.capture3(shell_command)
|
|
66
|
+
|
|
67
|
+
result = {
|
|
68
|
+
success: status.success?,
|
|
69
|
+
output: stdout.strip,
|
|
70
|
+
stderr: stderr.strip
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# コマンドが失敗した場合、エラーメッセージを追加
|
|
74
|
+
unless status.success?
|
|
75
|
+
result[:error] = "コマンドが失敗しました (終了コード: #{status.exitstatus})"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
result
|
|
79
|
+
rescue Errno::ENOENT => e
|
|
80
|
+
{ success: false, error: "コマンドが見つかりません: #{e.message}" }
|
|
81
|
+
rescue StandardError => e
|
|
82
|
+
{ success: false, error: "コマンド実行エラー: #{e.message}" }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
51
86
|
# プラグインからコマンドを読み込む
|
|
52
87
|
def load_plugin_commands
|
|
53
88
|
# 有効なプラグインを取得
|
|
@@ -51,16 +51,6 @@ module Rufio
|
|
|
51
51
|
content_lines = [""]
|
|
52
52
|
content_lines << "#{input}_" # カーソルを_で表現
|
|
53
53
|
content_lines << ""
|
|
54
|
-
|
|
55
|
-
# 補完候補がある場合は表示
|
|
56
|
-
unless suggestions.empty?
|
|
57
|
-
content_lines << "補完候補:"
|
|
58
|
-
suggestions.each do |suggestion|
|
|
59
|
-
content_lines << " #{suggestion}"
|
|
60
|
-
end
|
|
61
|
-
content_lines << ""
|
|
62
|
-
end
|
|
63
|
-
|
|
64
54
|
content_lines << "Tab: 補完 | Enter: 実行 | ESC: キャンセル"
|
|
65
55
|
|
|
66
56
|
# ウィンドウの色設定(青)
|
|
@@ -87,16 +77,23 @@ module Rufio
|
|
|
87
77
|
end
|
|
88
78
|
|
|
89
79
|
# コマンド実行結果をフローティングウィンドウで表示
|
|
90
|
-
# @param result [String, nil] コマンド実行結果
|
|
80
|
+
# @param result [String, Hash, nil] コマンド実行結果
|
|
91
81
|
def show_result(result)
|
|
92
82
|
# nil または空文字列の場合は何も表示しない
|
|
93
83
|
return if result.nil? || result.empty?
|
|
94
84
|
|
|
95
|
-
#
|
|
96
|
-
|
|
85
|
+
# Hash形式の結果を処理
|
|
86
|
+
if result.is_a?(Hash)
|
|
87
|
+
result_text = format_hash_result(result)
|
|
88
|
+
is_error = !result[:success]
|
|
89
|
+
else
|
|
90
|
+
# 文字列形式の結果(従来の動作)
|
|
91
|
+
result_text = result
|
|
92
|
+
is_error = result.include?("⚠️") || result.include?("エラー")
|
|
93
|
+
end
|
|
97
94
|
|
|
98
|
-
#
|
|
99
|
-
|
|
95
|
+
# 結果を行に分割
|
|
96
|
+
result_lines = result_text.split("\n")
|
|
100
97
|
|
|
101
98
|
# ウィンドウの色設定
|
|
102
99
|
if is_error
|
|
@@ -164,5 +161,41 @@ module Rufio
|
|
|
164
161
|
|
|
165
162
|
strings.first[0...common_length]
|
|
166
163
|
end
|
|
164
|
+
|
|
165
|
+
# Hash形式の結果を文字列に変換
|
|
166
|
+
# @param result [Hash] コマンド実行結果
|
|
167
|
+
# @return [String] フォーマットされた結果
|
|
168
|
+
def format_hash_result(result)
|
|
169
|
+
lines = []
|
|
170
|
+
|
|
171
|
+
# エラーメッセージがある場合
|
|
172
|
+
if result[:error]
|
|
173
|
+
lines << result[:error]
|
|
174
|
+
lines << ""
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# 標準出力
|
|
178
|
+
if result[:output] && !result[:output].empty?
|
|
179
|
+
lines << result[:output]
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# 標準エラー出力(空でない場合のみ)
|
|
183
|
+
if result[:stderr] && !result[:stderr].empty?
|
|
184
|
+
lines << "" if lines.any?
|
|
185
|
+
lines << "--- stderr ---"
|
|
186
|
+
lines << result[:stderr]
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# 何も出力がない場合
|
|
190
|
+
if lines.empty?
|
|
191
|
+
if result[:success]
|
|
192
|
+
lines << "コマンドが正常に実行されました"
|
|
193
|
+
else
|
|
194
|
+
lines << "コマンドが失敗しました"
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
lines.join("\n")
|
|
199
|
+
end
|
|
167
200
|
end
|
|
168
201
|
end
|
data/lib/rufio/config_loader.rb
CHANGED
|
@@ -56,6 +56,10 @@ module Rufio
|
|
|
56
56
|
File.expand_path('~/.config/rufio/scripts')
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
def command_history_size
|
|
60
|
+
load_config[:command_history_size] || 1000
|
|
61
|
+
end
|
|
62
|
+
|
|
59
63
|
private
|
|
60
64
|
|
|
61
65
|
def load_config_file
|
|
@@ -79,6 +83,11 @@ module Rufio
|
|
|
79
83
|
config[:scripts_dir] = Object.const_get(:SCRIPTS_DIR)
|
|
80
84
|
end
|
|
81
85
|
|
|
86
|
+
# Load command history size if defined
|
|
87
|
+
if Object.const_defined?(:COMMAND_HISTORY_SIZE)
|
|
88
|
+
config[:command_history_size] = Object.const_get(:COMMAND_HISTORY_SIZE)
|
|
89
|
+
end
|
|
90
|
+
|
|
82
91
|
config
|
|
83
92
|
rescue StandardError => e
|
|
84
93
|
warn "Failed to load config file: #{e.message}"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rufio
|
|
4
|
+
module Plugins
|
|
5
|
+
# Hello コマンドを提供するプラグイン
|
|
6
|
+
# Rubyコードで挨拶を返す簡単な例
|
|
7
|
+
class Hello < Plugin
|
|
8
|
+
def name
|
|
9
|
+
"Hello"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def description
|
|
13
|
+
"Rubyで実装された挨拶コマンドの例"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def commands
|
|
17
|
+
{
|
|
18
|
+
hello: method(:say_hello)
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
# 挨拶メッセージを返す
|
|
25
|
+
def say_hello
|
|
26
|
+
"Hello, World! 🌍\n\nこのコマンドはRubyで実装されています。"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rufio
|
|
4
|
+
# シェルコマンド補完機能を提供するクラス
|
|
5
|
+
class ShellCommandCompletion
|
|
6
|
+
# PATHからコマンドを補完
|
|
7
|
+
# @param input [String] 入力されたコマンドの一部
|
|
8
|
+
# @return [Array<String>] 補完候補のリスト
|
|
9
|
+
def complete_command(input)
|
|
10
|
+
return [] if input.nil? || input.empty?
|
|
11
|
+
|
|
12
|
+
input_lower = input.downcase
|
|
13
|
+
path_commands.select { |cmd| cmd.downcase.start_with?(input_lower) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# ファイルパスを補完
|
|
17
|
+
# @param input [String] 入力されたパスの一部
|
|
18
|
+
# @param options [Hash] オプション
|
|
19
|
+
# @option options [Boolean] :directories_only ディレクトリのみを補完
|
|
20
|
+
# @return [Array<String>] 補完候補のリスト
|
|
21
|
+
def complete_path(input, options = {})
|
|
22
|
+
return [] if input.nil?
|
|
23
|
+
|
|
24
|
+
# ~を展開
|
|
25
|
+
expanded_input = File.expand_path(input) rescue input
|
|
26
|
+
|
|
27
|
+
# 入力が/で終わる場合(ディレクトリ内を補完)
|
|
28
|
+
if input.end_with?("/")
|
|
29
|
+
dir = expanded_input
|
|
30
|
+
pattern = File.join(dir, "*")
|
|
31
|
+
else
|
|
32
|
+
# ディレクトリ部分とファイル名部分を分離
|
|
33
|
+
dir = File.dirname(expanded_input)
|
|
34
|
+
basename = File.basename(expanded_input)
|
|
35
|
+
pattern = File.join(dir, "#{basename}*")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# ディレクトリが存在しない場合は空の配列を返す
|
|
39
|
+
return [] unless Dir.exist?(dir)
|
|
40
|
+
|
|
41
|
+
# マッチするファイル/ディレクトリを取得
|
|
42
|
+
candidates = Dir.glob(pattern)
|
|
43
|
+
|
|
44
|
+
# ディレクトリのみのフィルタリング
|
|
45
|
+
if options[:directories_only]
|
|
46
|
+
candidates.select! { |path| File.directory?(path) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# 元の入力が~で始まる場合、結果も~で始まるように変換
|
|
50
|
+
if input.start_with?("~")
|
|
51
|
+
home = ENV['HOME']
|
|
52
|
+
candidates.map! { |path| path.sub(home, "~") }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
candidates.sort
|
|
56
|
+
rescue StandardError
|
|
57
|
+
[]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# コマンド履歴から補完
|
|
61
|
+
# @param input [String] 入力されたコマンドの一部
|
|
62
|
+
# @param history [CommandHistory] コマンド履歴オブジェクト
|
|
63
|
+
# @return [Array<String>] 補完候補のリスト
|
|
64
|
+
def complete_from_history(input, history)
|
|
65
|
+
return [] if input.nil? || input.empty?
|
|
66
|
+
return [] unless history
|
|
67
|
+
|
|
68
|
+
# 履歴から全てのコマンドを取得
|
|
69
|
+
# CommandHistoryクラスから履歴を取得する方法が必要
|
|
70
|
+
# 現在の実装では、@historyインスタンス変数にアクセスできないため、
|
|
71
|
+
# 新しいメソッドを追加する必要がある
|
|
72
|
+
# 一旦、空の配列を返す実装にして、後でCommandHistoryを拡張する
|
|
73
|
+
commands = get_history_commands(history)
|
|
74
|
+
|
|
75
|
+
input_lower = input.downcase
|
|
76
|
+
commands.select { |cmd| cmd.downcase.start_with?(input_lower) }.uniq
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
# PATHから実行可能なコマンドのリストを取得
|
|
82
|
+
# @return [Array<String>] コマンドのリスト
|
|
83
|
+
def path_commands
|
|
84
|
+
@path_commands ||= begin
|
|
85
|
+
paths = ENV['PATH'].split(File::PATH_SEPARATOR)
|
|
86
|
+
commands = []
|
|
87
|
+
|
|
88
|
+
paths.each do |path|
|
|
89
|
+
next unless Dir.exist?(path)
|
|
90
|
+
|
|
91
|
+
Dir.foreach(path) do |file|
|
|
92
|
+
next if file == '.' || file == '..'
|
|
93
|
+
|
|
94
|
+
filepath = File.join(path, file)
|
|
95
|
+
# 実行可能なファイルのみを追加
|
|
96
|
+
commands << file if File.executable?(filepath) && !File.directory?(filepath)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
commands.uniq.sort
|
|
101
|
+
rescue StandardError
|
|
102
|
+
[]
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# CommandHistoryオブジェクトからコマンドリストを取得
|
|
107
|
+
# @param history [CommandHistory] 履歴オブジェクト
|
|
108
|
+
# @return [Array<String>] コマンドのリスト
|
|
109
|
+
def get_history_commands(history)
|
|
110
|
+
# CommandHistoryの内部データにアクセスするため、
|
|
111
|
+
# instance_variable_getを使用(本来はpublicメソッドを追加すべき)
|
|
112
|
+
history_array = history.instance_variable_get(:@history) || []
|
|
113
|
+
|
|
114
|
+
# ! を除去したコマンドを返す
|
|
115
|
+
history_array.map do |cmd|
|
|
116
|
+
cmd.start_with?('!') ? cmd[1..-1] : cmd
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
data/lib/rufio/terminal_ui.rb
CHANGED
|
@@ -48,6 +48,12 @@ module Rufio
|
|
|
48
48
|
@dialog_renderer = DialogRenderer.new
|
|
49
49
|
@command_mode_ui = CommandModeUI.new(@command_mode, @dialog_renderer)
|
|
50
50
|
|
|
51
|
+
# コマンド履歴と補完
|
|
52
|
+
history_file = File.join(Dir.home, '.rufio', 'command_history.txt')
|
|
53
|
+
FileUtils.mkdir_p(File.dirname(history_file))
|
|
54
|
+
@command_history = CommandHistory.new(history_file, max_size: ConfigLoader.command_history_size)
|
|
55
|
+
@command_completion = CommandCompletion.new(@command_history)
|
|
56
|
+
|
|
51
57
|
# Project mode
|
|
52
58
|
@project_mode = nil
|
|
53
59
|
@project_command = nil
|
|
@@ -151,10 +157,8 @@ module Rufio
|
|
|
151
157
|
|
|
152
158
|
# コマンドモードがアクティブな場合はコマンド入力ウィンドウを表示
|
|
153
159
|
if @command_mode_active
|
|
154
|
-
# 補完候補を取得
|
|
155
|
-
suggestions = @command_mode_ui.autocomplete(@command_input)
|
|
156
160
|
# フローティングウィンドウで表示
|
|
157
|
-
@command_mode_ui.show_input_prompt(@command_input
|
|
161
|
+
@command_mode_ui.show_input_prompt(@command_input)
|
|
158
162
|
else
|
|
159
163
|
# move cursor to invisible position
|
|
160
164
|
print "\e[#{@screen_height};#{@screen_width}H"
|
|
@@ -546,7 +550,7 @@ module Rufio
|
|
|
546
550
|
deactivate_command_mode
|
|
547
551
|
when "\t"
|
|
548
552
|
# Tab キーで補完
|
|
549
|
-
|
|
553
|
+
handle_tab_completion
|
|
550
554
|
when "\u007F", "\b"
|
|
551
555
|
# Backspace
|
|
552
556
|
@command_input.chop! unless @command_input.empty?
|
|
@@ -560,6 +564,9 @@ module Rufio
|
|
|
560
564
|
def execute_command(command_string)
|
|
561
565
|
return if command_string.nil? || command_string.empty?
|
|
562
566
|
|
|
567
|
+
# コマンド履歴に追加
|
|
568
|
+
@command_history.add(command_string)
|
|
569
|
+
|
|
563
570
|
result = @command_mode.execute(command_string)
|
|
564
571
|
|
|
565
572
|
# コマンド実行結果をフローティングウィンドウで表示
|
|
@@ -569,6 +576,84 @@ module Rufio
|
|
|
569
576
|
draw_screen
|
|
570
577
|
end
|
|
571
578
|
|
|
579
|
+
# Tab補完を処理
|
|
580
|
+
def handle_tab_completion
|
|
581
|
+
# 補完候補を取得
|
|
582
|
+
candidates = @command_completion.complete(@command_input)
|
|
583
|
+
|
|
584
|
+
# 候補がない場合は何もしない
|
|
585
|
+
return if candidates.empty?
|
|
586
|
+
|
|
587
|
+
# 候補が1つの場合はそれに補完
|
|
588
|
+
if candidates.size == 1
|
|
589
|
+
@command_input = candidates.first
|
|
590
|
+
return
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
# 複数の候補がある場合、共通プレフィックスまで補完
|
|
594
|
+
prefix = @command_completion.common_prefix(@command_input)
|
|
595
|
+
|
|
596
|
+
# 入力が変わる場合は補完して終了
|
|
597
|
+
if prefix != @command_input
|
|
598
|
+
@command_input = prefix
|
|
599
|
+
return
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
# 入力が変わらない場合は候補リストを表示
|
|
603
|
+
show_completion_candidates(candidates)
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
# 補完候補を一時的に表示
|
|
607
|
+
def show_completion_candidates(candidates)
|
|
608
|
+
title = "補完候補 (#{candidates.size}件)"
|
|
609
|
+
|
|
610
|
+
# 候補を表示用にフォーマット(最大20件)
|
|
611
|
+
display_candidates = candidates.first(20)
|
|
612
|
+
content_lines = [""]
|
|
613
|
+
display_candidates.each do |candidate|
|
|
614
|
+
content_lines << " #{candidate}"
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
if candidates.size > 20
|
|
618
|
+
content_lines << ""
|
|
619
|
+
content_lines << " ... 他 #{candidates.size - 20} 件"
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
content_lines << ""
|
|
623
|
+
content_lines << "Press any key to continue..."
|
|
624
|
+
|
|
625
|
+
# ウィンドウの色設定(黄色)
|
|
626
|
+
border_color = "\e[33m"
|
|
627
|
+
title_color = "\e[1;33m"
|
|
628
|
+
content_color = "\e[37m"
|
|
629
|
+
|
|
630
|
+
# ウィンドウサイズを計算
|
|
631
|
+
width, height = @dialog_renderer.calculate_dimensions(content_lines, {
|
|
632
|
+
title: title,
|
|
633
|
+
min_width: 40,
|
|
634
|
+
max_width: 80
|
|
635
|
+
})
|
|
636
|
+
|
|
637
|
+
# 中央位置を計算
|
|
638
|
+
x, y = @dialog_renderer.calculate_center(width, height)
|
|
639
|
+
|
|
640
|
+
# フローティングウィンドウを描画
|
|
641
|
+
@dialog_renderer.draw_floating_window(x, y, width, height, title, content_lines, {
|
|
642
|
+
border_color: border_color,
|
|
643
|
+
title_color: title_color,
|
|
644
|
+
content_color: content_color
|
|
645
|
+
})
|
|
646
|
+
|
|
647
|
+
# キー入力を待つ
|
|
648
|
+
STDIN.getch
|
|
649
|
+
|
|
650
|
+
# ウィンドウをクリア
|
|
651
|
+
@dialog_renderer.clear_area(x, y, width, height)
|
|
652
|
+
|
|
653
|
+
# 画面を再描画
|
|
654
|
+
draw_screen
|
|
655
|
+
end
|
|
656
|
+
|
|
572
657
|
# Show info notices from the info directory if any are unread
|
|
573
658
|
def show_info_notices
|
|
574
659
|
require_relative 'info_notice'
|
data/lib/rufio/version.rb
CHANGED
data/lib/rufio.rb
CHANGED
|
@@ -27,12 +27,18 @@ require_relative "rufio/plugin"
|
|
|
27
27
|
require_relative "rufio/plugin_manager"
|
|
28
28
|
require_relative "rufio/command_mode"
|
|
29
29
|
require_relative "rufio/command_mode_ui"
|
|
30
|
+
require_relative "rufio/command_history"
|
|
31
|
+
require_relative "rufio/command_completion"
|
|
32
|
+
require_relative "rufio/shell_command_completion"
|
|
30
33
|
|
|
31
34
|
# プロジェクトモード
|
|
32
35
|
require_relative "rufio/project_mode"
|
|
33
36
|
require_relative "rufio/project_command"
|
|
34
37
|
require_relative "rufio/project_log"
|
|
35
38
|
|
|
39
|
+
# プラグインをロード
|
|
40
|
+
Rufio::PluginManager.load_all
|
|
41
|
+
|
|
36
42
|
module Rufio
|
|
37
43
|
class Error < StandardError; end
|
|
38
44
|
end
|