beniya 0.7.0 → 0.9.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.
@@ -0,0 +1,168 @@
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 input [String] 現在の入力文字列
45
+ # @param suggestions [Array<String>] 補完候補(オプション)
46
+ def show_input_prompt(input, suggestions = [])
47
+ # タイトル
48
+ title = "コマンドモード"
49
+
50
+ # コンテンツ行を構築
51
+ content_lines = [""]
52
+ content_lines << "#{input}_" # カーソルを_で表現
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
+ content_lines << "Tab: 補完 | Enter: 実行 | ESC: キャンセル"
65
+
66
+ # ウィンドウの色設定(青)
67
+ border_color = "\e[34m" # Blue
68
+ title_color = "\e[1;34m" # Bold blue
69
+ content_color = "\e[37m" # White
70
+
71
+ # ウィンドウサイズを計算
72
+ width, height = @dialog_renderer.calculate_dimensions(content_lines, {
73
+ title: title,
74
+ min_width: 50,
75
+ max_width: 80
76
+ })
77
+
78
+ # 中央位置を計算
79
+ x, y = @dialog_renderer.calculate_center(width, height)
80
+
81
+ # フローティングウィンドウを描画
82
+ @dialog_renderer.draw_floating_window(x, y, width, height, title, content_lines, {
83
+ border_color: border_color,
84
+ title_color: title_color,
85
+ content_color: content_color
86
+ })
87
+ end
88
+
89
+ # コマンド実行結果をフローティングウィンドウで表示
90
+ # @param result [String, nil] コマンド実行結果
91
+ def show_result(result)
92
+ # nil または空文字列の場合は何も表示しない
93
+ return if result.nil? || result.empty?
94
+
95
+ # 結果を行に分割
96
+ result_lines = result.split("\n")
97
+
98
+ # エラーメッセージかどうかを判定
99
+ is_error = result.include?("⚠️") || result.include?("エラー")
100
+
101
+ # ウィンドウの色設定
102
+ if is_error
103
+ border_color = "\e[31m" # Red
104
+ title_color = "\e[1;31m" # Bold red
105
+ content_color = "\e[37m" # White
106
+ else
107
+ border_color = "\e[32m" # Green
108
+ title_color = "\e[1;32m" # Bold green
109
+ content_color = "\e[37m" # White
110
+ end
111
+
112
+ # ウィンドウタイトル
113
+ title = "コマンド実行結果"
114
+
115
+ # コンテンツ行を構築
116
+ content_lines = [""] + result_lines + ["", "Press any key to close"]
117
+
118
+ # ウィンドウサイズを計算
119
+ width, height = @dialog_renderer.calculate_dimensions(content_lines, {
120
+ title: title,
121
+ min_width: 40,
122
+ max_width: 100
123
+ })
124
+
125
+ # 中央位置を計算
126
+ x, y = @dialog_renderer.calculate_center(width, height)
127
+
128
+ # フローティングウィンドウを描画
129
+ @dialog_renderer.draw_floating_window(x, y, width, height, title, content_lines, {
130
+ border_color: border_color,
131
+ title_color: title_color,
132
+ content_color: content_color
133
+ })
134
+
135
+ # キー入力を待つ
136
+ STDIN.getch
137
+
138
+ # ウィンドウをクリア
139
+ @dialog_renderer.clear_area(x, y, width, height)
140
+ end
141
+
142
+ private
143
+
144
+ # 文字列配列の共通プレフィックスを見つける
145
+ # @param strings [Array<String>] 文字列配列
146
+ # @return [String] 共通プレフィックス
147
+ def find_common_prefix(strings)
148
+ return "" if strings.empty?
149
+ return strings.first if strings.length == 1
150
+
151
+ # 最短の文字列の長さを取得
152
+ min_length = strings.map(&:length).min
153
+
154
+ # 各文字位置で全ての文字列が同じ文字を持っているかチェック
155
+ common_length = 0
156
+ min_length.times do |i|
157
+ char = strings.first[i]
158
+ if strings.all? { |s| s[i] == char }
159
+ common_length = i + 1
160
+ else
161
+ break
162
+ end
163
+ end
164
+
165
+ strings.first[0...common_length]
166
+ end
167
+ end
168
+ 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
  # カーソルを画面下部の入力行に移動
@@ -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
- # move cursor to invisible position
130
- print "\e[#{@screen_height};#{@screen_width}H"
134
+ # コマンドモードがアクティブな場合はコマンド入力ウィンドウを表示
135
+ if @command_mode_active
136
+ # 補完候補を取得
137
+ suggestions = @command_mode_ui.autocomplete(@command_input)
138
+ # フローティングウィンドウで表示
139
+ @command_mode_ui.show_input_prompt(@command_input, suggestions)
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,61 @@ 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
554
628
  end
555
629
  end
556
630
 
@@ -23,8 +23,8 @@ module Beniya
23
23
  def display_width(string)
24
24
  string.each_char.map do |char|
25
25
  case char
26
- when /[\u3000-\u303F\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF\uFF00-\uFFEF]/
27
- FULLWIDTH_CHAR_WIDTH # Japanese characters (hiragana, katakana, kanji, full-width symbols)
26
+ when /[\u3000-\u303F\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF\uFF00-\uFFEF\u2500-\u257F\u2580-\u259F]/
27
+ FULLWIDTH_CHAR_WIDTH # Japanese characters (hiragana, katakana, kanji, full-width symbols, box drawing, block elements)
28
28
  when /[\u0020-\u007E]/
29
29
  HALFWIDTH_CHAR_WIDTH # ASCII characters
30
30
  else
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Beniya
4
- VERSION = '0.7.0'
4
+ VERSION = '0.9.0'
5
5
  end
data/lib/beniya.rb CHANGED
@@ -25,6 +25,8 @@ require_relative "beniya/health_checker"
25
25
  require_relative "beniya/plugin_config"
26
26
  require_relative "beniya/plugin"
27
27
  require_relative "beniya/plugin_manager"
28
+ require_relative "beniya/command_mode"
29
+ require_relative "beniya/command_mode_ui"
28
30
 
29
31
  module Beniya
30
32
  class Error < StandardError; 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.7.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - masisz
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-11-29 00:00:00.000000000 Z
10
+ date: 2025-12-13 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: io-console
@@ -121,17 +121,23 @@ files:
121
121
  - CHANGELOG_v0.5.0.md
122
122
  - CHANGELOG_v0.6.0.md
123
123
  - CHANGELOG_v0.7.0.md
124
+ - CHANGELOG_v0.8.0.md
125
+ - CHANGELOG_v0.9.0.md
124
126
  - README.md
125
127
  - README_EN.md
126
128
  - Rakefile
127
129
  - beniya.gemspec
128
130
  - bin/beniya
129
131
  - config_example.rb
132
+ - docs/PLUGIN_GUIDE.md
133
+ - docs/plugin_example.rb
130
134
  - lib/beniya.rb
131
135
  - lib/beniya/application.rb
132
136
  - lib/beniya/bookmark.rb
133
137
  - lib/beniya/bookmark_manager.rb
134
138
  - lib/beniya/color_helper.rb
139
+ - lib/beniya/command_mode.rb
140
+ - lib/beniya/command_mode_ui.rb
135
141
  - lib/beniya/config.rb
136
142
  - lib/beniya/config_loader.rb
137
143
  - lib/beniya/dialog_renderer.rb