beniya 0.6.3 → 0.8.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 +24 -0
- data/CHANGELOG_v0.7.0.md +280 -0
- data/CHANGELOG_v0.8.0.md +267 -0
- data/README.md +234 -0
- data/README_EN.md +162 -0
- data/bin/beniya +3 -0
- data/docs/PLUGIN_GUIDE.md +431 -0
- data/docs/plugin_example.rb +119 -0
- data/lib/beniya/command_mode.rb +72 -0
- data/lib/beniya/command_mode_ui.rb +122 -0
- data/lib/beniya/keybind_handler.rb +8 -0
- data/lib/beniya/plugin.rb +89 -0
- data/lib/beniya/plugin_config.rb +59 -0
- data/lib/beniya/plugin_manager.rb +84 -0
- data/lib/beniya/plugins/file_operations.rb +44 -0
- data/lib/beniya/terminal_ui.rb +105 -2
- data/lib/beniya/text_utils.rb +27 -11
- data/lib/beniya/version.rb +1 -1
- data/lib/beniya.rb +7 -0
- metadata +12 -2
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'io/console'
|
|
4
|
+
|
|
5
|
+
module Beniya
|
|
6
|
+
# コマンドモードのUI - Tab補完とフローティングウィンドウでの結果表示
|
|
7
|
+
class CommandModeUI
|
|
8
|
+
def initialize(command_mode, dialog_renderer)
|
|
9
|
+
@command_mode = command_mode
|
|
10
|
+
@dialog_renderer = dialog_renderer
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# 入力文字列に対する補完候補を取得
|
|
14
|
+
# @param input [String] 現在の入力文字列
|
|
15
|
+
# @return [Array<String>] 補完候補の配列
|
|
16
|
+
def autocomplete(input)
|
|
17
|
+
# 利用可能なコマンド一覧を取得
|
|
18
|
+
available = @command_mode.available_commands.map(&:to_s)
|
|
19
|
+
|
|
20
|
+
# 入力が空の場合は全てのコマンドを返す
|
|
21
|
+
return available if input.empty?
|
|
22
|
+
|
|
23
|
+
# 入力に一致するコマンドをフィルタリング
|
|
24
|
+
available.select { |cmd| cmd.start_with?(input) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# コマンドを補完する
|
|
28
|
+
# @param input [String] 現在の入力文字列
|
|
29
|
+
# @return [String] 補完後の文字列
|
|
30
|
+
def complete_command(input)
|
|
31
|
+
suggestions = autocomplete(input)
|
|
32
|
+
|
|
33
|
+
# マッチするものがない場合は元の入力を返す
|
|
34
|
+
return input if suggestions.empty?
|
|
35
|
+
|
|
36
|
+
# 一つだけマッチする場合はそれを返す
|
|
37
|
+
return suggestions.first if suggestions.length == 1
|
|
38
|
+
|
|
39
|
+
# 複数マッチする場合は共通プレフィックスを返す
|
|
40
|
+
find_common_prefix(suggestions)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# コマンド実行結果をフローティングウィンドウで表示
|
|
44
|
+
# @param result [String, nil] コマンド実行結果
|
|
45
|
+
def show_result(result)
|
|
46
|
+
# nil または空文字列の場合は何も表示しない
|
|
47
|
+
return if result.nil? || result.empty?
|
|
48
|
+
|
|
49
|
+
# 結果を行に分割
|
|
50
|
+
result_lines = result.split("\n")
|
|
51
|
+
|
|
52
|
+
# エラーメッセージかどうかを判定
|
|
53
|
+
is_error = result.include?("⚠️") || result.include?("エラー")
|
|
54
|
+
|
|
55
|
+
# ウィンドウの色設定
|
|
56
|
+
if is_error
|
|
57
|
+
border_color = "\e[31m" # Red
|
|
58
|
+
title_color = "\e[1;31m" # Bold red
|
|
59
|
+
content_color = "\e[37m" # White
|
|
60
|
+
else
|
|
61
|
+
border_color = "\e[32m" # Green
|
|
62
|
+
title_color = "\e[1;32m" # Bold green
|
|
63
|
+
content_color = "\e[37m" # White
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# ウィンドウタイトル
|
|
67
|
+
title = "コマンド実行結果"
|
|
68
|
+
|
|
69
|
+
# コンテンツ行を構築
|
|
70
|
+
content_lines = [""] + result_lines + ["", "Press any key to close"]
|
|
71
|
+
|
|
72
|
+
# ウィンドウサイズを計算
|
|
73
|
+
width, height = @dialog_renderer.calculate_dimensions(content_lines, {
|
|
74
|
+
title: title,
|
|
75
|
+
min_width: 40,
|
|
76
|
+
max_width: 100
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
# 中央位置を計算
|
|
80
|
+
x, y = @dialog_renderer.calculate_center(width, height)
|
|
81
|
+
|
|
82
|
+
# フローティングウィンドウを描画
|
|
83
|
+
@dialog_renderer.draw_floating_window(x, y, width, height, title, content_lines, {
|
|
84
|
+
border_color: border_color,
|
|
85
|
+
title_color: title_color,
|
|
86
|
+
content_color: content_color
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
# キー入力を待つ
|
|
90
|
+
STDIN.getch
|
|
91
|
+
|
|
92
|
+
# ウィンドウをクリア
|
|
93
|
+
@dialog_renderer.clear_area(x, y, width, height)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
# 文字列配列の共通プレフィックスを見つける
|
|
99
|
+
# @param strings [Array<String>] 文字列配列
|
|
100
|
+
# @return [String] 共通プレフィックス
|
|
101
|
+
def find_common_prefix(strings)
|
|
102
|
+
return "" if strings.empty?
|
|
103
|
+
return strings.first if strings.length == 1
|
|
104
|
+
|
|
105
|
+
# 最短の文字列の長さを取得
|
|
106
|
+
min_length = strings.map(&:length).min
|
|
107
|
+
|
|
108
|
+
# 各文字位置で全ての文字列が同じ文字を持っているかチェック
|
|
109
|
+
common_length = 0
|
|
110
|
+
min_length.times do |i|
|
|
111
|
+
char = strings.first[i]
|
|
112
|
+
if strings.all? { |s| s[i] == char }
|
|
113
|
+
common_length = i + 1
|
|
114
|
+
else
|
|
115
|
+
break
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
strings.first[0...common_length]
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -136,6 +136,8 @@ module Beniya
|
|
|
136
136
|
show_zoxide_menu
|
|
137
137
|
when '1', '2', '3', '4', '5', '6', '7', '8', '9' # number keys - go to bookmark
|
|
138
138
|
goto_bookmark(key.to_i)
|
|
139
|
+
when ':' # : - command mode
|
|
140
|
+
activate_command_mode
|
|
139
141
|
else
|
|
140
142
|
false # #{ConfigLoader.message('keybind.invalid_key')}
|
|
141
143
|
end
|
|
@@ -769,6 +771,12 @@ module Beniya
|
|
|
769
771
|
end
|
|
770
772
|
end
|
|
771
773
|
|
|
774
|
+
# コマンドモードを起動
|
|
775
|
+
def activate_command_mode
|
|
776
|
+
@terminal_ui&.activate_command_mode
|
|
777
|
+
true
|
|
778
|
+
end
|
|
779
|
+
|
|
772
780
|
private
|
|
773
781
|
|
|
774
782
|
# カーソルを画面下部の入力行に移動
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Beniya
|
|
4
|
+
# プラグインを格納するモジュール
|
|
5
|
+
module Plugins
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# プラグインの基底クラス
|
|
9
|
+
class Plugin
|
|
10
|
+
# 依存gemが不足している場合に投げられるエラー
|
|
11
|
+
class DependencyError < StandardError; end
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
# 継承時に自動的にPluginManagerに登録する
|
|
15
|
+
def inherited(subclass)
|
|
16
|
+
super
|
|
17
|
+
PluginManager.register(subclass)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# 依存gemを宣言する
|
|
21
|
+
def requires(*gems)
|
|
22
|
+
@required_gems ||= []
|
|
23
|
+
@required_gems.concat(gems)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# 宣言された依存gemのリストを取得する
|
|
27
|
+
def required_gems
|
|
28
|
+
@required_gems || []
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# 初期化時に依存gemをチェックする
|
|
33
|
+
def initialize
|
|
34
|
+
check_dependencies!
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# プラグイン名(必須オーバーライド)
|
|
38
|
+
def name
|
|
39
|
+
raise NotImplementedError, "#{self.class}#name must be implemented"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# プラグインの説明(オプション)
|
|
43
|
+
def description
|
|
44
|
+
""
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# プラグインのバージョン(オプション)
|
|
48
|
+
def version
|
|
49
|
+
"1.0.0"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# コマンド定義(オプション)
|
|
53
|
+
# { command_name: method(:method_name) } の形式で返す
|
|
54
|
+
def commands
|
|
55
|
+
{}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
# 依存gemが全て利用可能かチェックする
|
|
61
|
+
def check_dependencies!
|
|
62
|
+
required_gems = self.class.required_gems
|
|
63
|
+
return if required_gems.empty?
|
|
64
|
+
|
|
65
|
+
missing_gems = []
|
|
66
|
+
|
|
67
|
+
required_gems.each do |gem_name|
|
|
68
|
+
begin
|
|
69
|
+
Gem::Specification.find_by_name(gem_name)
|
|
70
|
+
rescue Gem::LoadError
|
|
71
|
+
missing_gems << gem_name
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
return if missing_gems.empty?
|
|
76
|
+
|
|
77
|
+
# 不足しているgemがある場合はエラーを投げる
|
|
78
|
+
error_message = <<~ERROR
|
|
79
|
+
Plugin '#{name}' は以下のgemに依存していますが、インストールされていません:
|
|
80
|
+
- #{missing_gems.join("\n - ")}
|
|
81
|
+
|
|
82
|
+
以下のコマンドでインストールしてください:
|
|
83
|
+
gem install #{missing_gems.join(' ')}
|
|
84
|
+
ERROR
|
|
85
|
+
|
|
86
|
+
raise DependencyError, error_message
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module Beniya
|
|
6
|
+
# プラグインの設定を管理するクラス
|
|
7
|
+
class PluginConfig
|
|
8
|
+
class << self
|
|
9
|
+
# 設定ファイルを読み込む
|
|
10
|
+
def load
|
|
11
|
+
config_path = File.expand_path('~/.beniya/config.yml')
|
|
12
|
+
|
|
13
|
+
if File.exist?(config_path)
|
|
14
|
+
begin
|
|
15
|
+
@config = YAML.load_file(config_path) || {}
|
|
16
|
+
rescue StandardError => e
|
|
17
|
+
warn "⚠️ Failed to load config file: #{e.message}"
|
|
18
|
+
@config = {}
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
# 設定ファイルが存在しない場合はデフォルト設定(空のハッシュ)
|
|
22
|
+
@config = {}
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# プラグインが有効かどうかをチェックする
|
|
27
|
+
def plugin_enabled?(name)
|
|
28
|
+
# 設定が未読み込みの場合は読み込む
|
|
29
|
+
load if @config.nil?
|
|
30
|
+
|
|
31
|
+
# pluginsセクションがない場合は全プラグイン有効
|
|
32
|
+
return true unless @config.is_a?(Hash) && @config['plugins']
|
|
33
|
+
|
|
34
|
+
plugins_config = @config['plugins']
|
|
35
|
+
return true unless plugins_config.is_a?(Hash)
|
|
36
|
+
|
|
37
|
+
# プラグイン名を小文字に統一して検索
|
|
38
|
+
normalized_name = name.to_s.downcase
|
|
39
|
+
|
|
40
|
+
# 設定のキーも小文字に変換して検索
|
|
41
|
+
plugin_setting = nil
|
|
42
|
+
plugins_config.each do |key, value|
|
|
43
|
+
if key.downcase == normalized_name
|
|
44
|
+
plugin_setting = value
|
|
45
|
+
break
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# 設定が存在しない場合は有効とみなす
|
|
50
|
+
return true if plugin_setting.nil?
|
|
51
|
+
|
|
52
|
+
# enabled設定を確認(デフォルトはtrue)
|
|
53
|
+
return true unless plugin_setting.is_a?(Hash)
|
|
54
|
+
|
|
55
|
+
plugin_setting.fetch('enabled', true)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Beniya
|
|
4
|
+
# プラグインを管理するクラス
|
|
5
|
+
class PluginManager
|
|
6
|
+
class << self
|
|
7
|
+
# 登録済みプラグインクラスのリスト
|
|
8
|
+
def plugins
|
|
9
|
+
@plugins ||= []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# プラグインを登録する
|
|
13
|
+
def register(plugin_class)
|
|
14
|
+
@plugins ||= []
|
|
15
|
+
@plugins << plugin_class unless @plugins.include?(plugin_class)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# 全プラグインを読み込む(本体同梱 + ユーザープラグイン)
|
|
19
|
+
def load_all
|
|
20
|
+
load_builtin_plugins
|
|
21
|
+
load_user_plugins
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# 有効なプラグインインスタンスのリストを取得
|
|
25
|
+
def enabled_plugins
|
|
26
|
+
return @enabled_plugins if @enabled_plugins
|
|
27
|
+
|
|
28
|
+
@enabled_plugins = []
|
|
29
|
+
|
|
30
|
+
plugins.each do |plugin_class|
|
|
31
|
+
# プラグイン名を取得(クラス名から推測)
|
|
32
|
+
plugin_name = plugin_class.name.split('::').last
|
|
33
|
+
|
|
34
|
+
# PluginConfigで有効かチェック
|
|
35
|
+
next unless PluginConfig.plugin_enabled?(plugin_name)
|
|
36
|
+
|
|
37
|
+
# プラグインのインスタンスを作成
|
|
38
|
+
begin
|
|
39
|
+
plugin_instance = plugin_class.new
|
|
40
|
+
@enabled_plugins << plugin_instance
|
|
41
|
+
rescue Plugin::DependencyError => e
|
|
42
|
+
warn "⚠️ #{e.message}"
|
|
43
|
+
# プラグインは無効化されるが、beniyaは起動継続
|
|
44
|
+
rescue StandardError => e
|
|
45
|
+
warn "⚠️ Failed to load plugin #{plugin_name}: #{e.message}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
@enabled_plugins
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# 本体同梱プラグインを読み込む
|
|
55
|
+
def load_builtin_plugins
|
|
56
|
+
# plugin_manager.rbは/lib/beniya/にあるので、pluginsディレクトリは同じディレクトリ内
|
|
57
|
+
builtin_plugins_dir = File.join(__dir__, 'plugins')
|
|
58
|
+
return unless Dir.exist?(builtin_plugins_dir)
|
|
59
|
+
|
|
60
|
+
Dir.glob(File.join(builtin_plugins_dir, '*.rb')).sort.each do |file|
|
|
61
|
+
begin
|
|
62
|
+
require file
|
|
63
|
+
rescue StandardError => e
|
|
64
|
+
warn "⚠️ Failed to load builtin plugin #{File.basename(file)}: #{e.message}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# ユーザープラグインを読み込む
|
|
70
|
+
def load_user_plugins
|
|
71
|
+
user_plugins_dir = File.expand_path('~/.beniya/plugins')
|
|
72
|
+
return unless Dir.exist?(user_plugins_dir)
|
|
73
|
+
|
|
74
|
+
Dir.glob(File.join(user_plugins_dir, '*.rb')).sort.each do |file|
|
|
75
|
+
begin
|
|
76
|
+
require file
|
|
77
|
+
rescue SyntaxError, StandardError => e
|
|
78
|
+
warn "⚠️ Failed to load user plugin #{File.basename(file)}: #{e.message}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Beniya
|
|
4
|
+
module Plugins
|
|
5
|
+
# 基本的なファイル操作を提供するプラグイン
|
|
6
|
+
class FileOperations < Plugin
|
|
7
|
+
def name
|
|
8
|
+
"FileOperations"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def description
|
|
12
|
+
"基本的なファイル操作(コピー、移動、削除)"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def commands
|
|
16
|
+
{
|
|
17
|
+
copy: method(:copy),
|
|
18
|
+
move: method(:move),
|
|
19
|
+
delete: method(:delete)
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
# ファイルコピー(スタブ実装)
|
|
26
|
+
def copy
|
|
27
|
+
# 実装は将来追加
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# ファイル移動(スタブ実装)
|
|
32
|
+
def move
|
|
33
|
+
# 実装は将来追加
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# ファイル削除(スタブ実装)
|
|
38
|
+
def delete
|
|
39
|
+
# 実装は将来追加
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/beniya/terminal_ui.rb
CHANGED
|
@@ -43,6 +43,11 @@ module Beniya
|
|
|
43
43
|
@screen_height = DEFAULT_SCREEN_HEIGHT
|
|
44
44
|
end
|
|
45
45
|
@running = false
|
|
46
|
+
@command_mode_active = false
|
|
47
|
+
@command_input = ""
|
|
48
|
+
@command_mode = CommandMode.new
|
|
49
|
+
@dialog_renderer = DialogRenderer.new
|
|
50
|
+
@command_mode_ui = CommandModeUI.new(@command_mode, @dialog_renderer)
|
|
46
51
|
end
|
|
47
52
|
|
|
48
53
|
def start(directory_listing, keybind_handler, file_preview)
|
|
@@ -126,8 +131,16 @@ module Beniya
|
|
|
126
131
|
# footer
|
|
127
132
|
draw_footer
|
|
128
133
|
|
|
129
|
-
#
|
|
130
|
-
|
|
134
|
+
# コマンド実行結果を表示
|
|
135
|
+
draw_command_result
|
|
136
|
+
|
|
137
|
+
# コマンドモードがアクティブな場合はコマンド入力欄を表示
|
|
138
|
+
if @command_mode_active
|
|
139
|
+
draw_command_input
|
|
140
|
+
else
|
|
141
|
+
# move cursor to invisible position
|
|
142
|
+
print "\e[#{@screen_height};#{@screen_width}H"
|
|
143
|
+
end
|
|
131
144
|
end
|
|
132
145
|
|
|
133
146
|
def draw_header
|
|
@@ -543,6 +556,12 @@ module Beniya
|
|
|
543
556
|
end
|
|
544
557
|
end
|
|
545
558
|
|
|
559
|
+
# コマンドモードがアクティブな場合は、コマンド入力を処理
|
|
560
|
+
if @command_mode_active
|
|
561
|
+
handle_command_input(input)
|
|
562
|
+
return
|
|
563
|
+
end
|
|
564
|
+
|
|
546
565
|
# キーバインドハンドラーに処理を委譲
|
|
547
566
|
result = @keybind_handler.handle_key(input)
|
|
548
567
|
|
|
@@ -551,6 +570,90 @@ module Beniya
|
|
|
551
570
|
@running = false
|
|
552
571
|
end
|
|
553
572
|
end
|
|
573
|
+
|
|
574
|
+
# コマンドモード関連のメソッドは public にする
|
|
575
|
+
public
|
|
576
|
+
|
|
577
|
+
# コマンドモードを起動
|
|
578
|
+
def activate_command_mode
|
|
579
|
+
@command_mode_active = true
|
|
580
|
+
@command_input = ""
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
# コマンドモードを終了
|
|
584
|
+
def deactivate_command_mode
|
|
585
|
+
@command_mode_active = false
|
|
586
|
+
@command_input = ""
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
# コマンドモードがアクティブかどうか
|
|
590
|
+
def command_mode_active?
|
|
591
|
+
@command_mode_active
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
# コマンド入力を処理
|
|
595
|
+
def handle_command_input(input)
|
|
596
|
+
case input
|
|
597
|
+
when "\r", "\n"
|
|
598
|
+
# Enter キーでコマンドを実行
|
|
599
|
+
execute_command(@command_input)
|
|
600
|
+
deactivate_command_mode
|
|
601
|
+
when "\e"
|
|
602
|
+
# Escape キーでコマンドモードをキャンセル
|
|
603
|
+
deactivate_command_mode
|
|
604
|
+
when "\t"
|
|
605
|
+
# Tab キーで補完
|
|
606
|
+
@command_input = @command_mode_ui.complete_command(@command_input)
|
|
607
|
+
when "\u007F", "\b"
|
|
608
|
+
# Backspace
|
|
609
|
+
@command_input.chop! unless @command_input.empty?
|
|
610
|
+
else
|
|
611
|
+
# 通常の文字を追加
|
|
612
|
+
@command_input += input if input.length == 1
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
# コマンドを実行
|
|
617
|
+
def execute_command(command_string)
|
|
618
|
+
return if command_string.nil? || command_string.empty?
|
|
619
|
+
|
|
620
|
+
result = @command_mode.execute(command_string)
|
|
621
|
+
|
|
622
|
+
# コマンド実行結果をフローティングウィンドウで表示
|
|
623
|
+
@command_mode_ui.show_result(result) if result
|
|
624
|
+
|
|
625
|
+
# 画面を再描画
|
|
626
|
+
draw_screen
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
# コマンド入力欄を描画
|
|
630
|
+
def draw_command_input
|
|
631
|
+
# 画面最下部に描画
|
|
632
|
+
print "\e[#{@screen_height};1H"
|
|
633
|
+
print "\e[2K" # 行をクリア
|
|
634
|
+
|
|
635
|
+
# コマンドプロンプトと入力を表示
|
|
636
|
+
prompt = ":"
|
|
637
|
+
print "#{prompt}#{@command_input}"
|
|
638
|
+
|
|
639
|
+
# カーソルを表示
|
|
640
|
+
print "\e[?25h"
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
# コマンド実行結果を描画
|
|
644
|
+
def draw_command_result
|
|
645
|
+
return unless @command_result && @command_result_time
|
|
646
|
+
|
|
647
|
+
# 3秒間だけ表示
|
|
648
|
+
if Time.now - @command_result_time < 3
|
|
649
|
+
print "\e[#{@screen_height - 1};1H"
|
|
650
|
+
print "\e[2K" # 行をクリア
|
|
651
|
+
print @command_result
|
|
652
|
+
else
|
|
653
|
+
@command_result = nil
|
|
654
|
+
@command_result_time = nil
|
|
655
|
+
end
|
|
656
|
+
end
|
|
554
657
|
end
|
|
555
658
|
end
|
|
556
659
|
|
data/lib/beniya/text_utils.rb
CHANGED
|
@@ -37,20 +37,36 @@ module Beniya
|
|
|
37
37
|
def truncate_to_width(string, max_width)
|
|
38
38
|
return string if display_width(string) <= max_width
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
# If max_width is enough for ellipsis, truncate and add ellipsis
|
|
41
|
+
if max_width >= ELLIPSIS_MIN_WIDTH
|
|
42
|
+
result = ''
|
|
43
|
+
current_width = 0
|
|
44
|
+
target_width = max_width - ELLIPSIS_MIN_WIDTH
|
|
45
|
+
|
|
46
|
+
string.each_char do |char|
|
|
47
|
+
char_width = display_width(char)
|
|
48
|
+
break if current_width + char_width > target_width
|
|
49
|
+
|
|
50
|
+
result += char
|
|
51
|
+
current_width += char_width
|
|
52
|
+
end
|
|
42
53
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
54
|
+
result + ELLIPSIS
|
|
55
|
+
else
|
|
56
|
+
# Not enough room for ellipsis, just truncate
|
|
57
|
+
result = ''
|
|
58
|
+
current_width = 0
|
|
46
59
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
string.each_char do |char|
|
|
61
|
+
char_width = display_width(char)
|
|
62
|
+
break if current_width + char_width > max_width
|
|
50
63
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
64
|
+
result += char
|
|
65
|
+
current_width += char_width
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
result
|
|
69
|
+
end
|
|
54
70
|
end
|
|
55
71
|
|
|
56
72
|
# Pad string to target_width with spaces
|
data/lib/beniya/version.rb
CHANGED
data/lib/beniya.rb
CHANGED
|
@@ -21,6 +21,13 @@ require_relative "beniya/application"
|
|
|
21
21
|
require_relative "beniya/file_opener"
|
|
22
22
|
require_relative "beniya/health_checker"
|
|
23
23
|
|
|
24
|
+
# プラグインシステム
|
|
25
|
+
require_relative "beniya/plugin_config"
|
|
26
|
+
require_relative "beniya/plugin"
|
|
27
|
+
require_relative "beniya/plugin_manager"
|
|
28
|
+
require_relative "beniya/command_mode"
|
|
29
|
+
require_relative "beniya/command_mode_ui"
|
|
30
|
+
|
|
24
31
|
module Beniya
|
|
25
32
|
class Error < StandardError; end
|
|
26
33
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: beniya
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- masisz
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2025-
|
|
10
|
+
date: 2025-12-06 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: io-console
|
|
@@ -120,17 +120,23 @@ files:
|
|
|
120
120
|
- CHANGELOG_v0.4.0.md
|
|
121
121
|
- CHANGELOG_v0.5.0.md
|
|
122
122
|
- CHANGELOG_v0.6.0.md
|
|
123
|
+
- CHANGELOG_v0.7.0.md
|
|
124
|
+
- CHANGELOG_v0.8.0.md
|
|
123
125
|
- README.md
|
|
124
126
|
- README_EN.md
|
|
125
127
|
- Rakefile
|
|
126
128
|
- beniya.gemspec
|
|
127
129
|
- bin/beniya
|
|
128
130
|
- config_example.rb
|
|
131
|
+
- docs/PLUGIN_GUIDE.md
|
|
132
|
+
- docs/plugin_example.rb
|
|
129
133
|
- lib/beniya.rb
|
|
130
134
|
- lib/beniya/application.rb
|
|
131
135
|
- lib/beniya/bookmark.rb
|
|
132
136
|
- lib/beniya/bookmark_manager.rb
|
|
133
137
|
- lib/beniya/color_helper.rb
|
|
138
|
+
- lib/beniya/command_mode.rb
|
|
139
|
+
- lib/beniya/command_mode_ui.rb
|
|
134
140
|
- lib/beniya/config.rb
|
|
135
141
|
- lib/beniya/config_loader.rb
|
|
136
142
|
- lib/beniya/dialog_renderer.rb
|
|
@@ -142,6 +148,10 @@ files:
|
|
|
142
148
|
- lib/beniya/health_checker.rb
|
|
143
149
|
- lib/beniya/keybind_handler.rb
|
|
144
150
|
- lib/beniya/logger.rb
|
|
151
|
+
- lib/beniya/plugin.rb
|
|
152
|
+
- lib/beniya/plugin_config.rb
|
|
153
|
+
- lib/beniya/plugin_manager.rb
|
|
154
|
+
- lib/beniya/plugins/file_operations.rb
|
|
145
155
|
- lib/beniya/selection_manager.rb
|
|
146
156
|
- lib/beniya/terminal_ui.rb
|
|
147
157
|
- lib/beniya/text_utils.rb
|