beniya 0.4.0 → 0.5.1

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: 5467834753b4cd28f856d6f2bec367deeca3718af5ac8ee3f538e284552f31b9
4
- data.tar.gz: 9615cf65617d91c6aa5e69d3768cebe89f9c8a7ec0053a4b2b99b56e04afebc1
3
+ metadata.gz: 6110d8a673eee02088b1d95fac741e95b4d470cbb5b5bb5826b3e6ac65893890
4
+ data.tar.gz: 9030f59ba585966f29faa34eb2d450cff37f836431368ac5e4b2a0bb011e0cb2
5
5
  SHA512:
6
- metadata.gz: faef534b972dcad18c504fa27baf110eaffb0762620bdb6d41372a546fc438752db973fe7d7703f3fa2e639fc598599afe0721ddf3ac175aff94d804210b1626
7
- data.tar.gz: 3b351e8907db499aeb4fbabbacfd1ee7c7bdfd3cb716ac98afc80d351c8a12b6c4ce2217783e610157b989fbe59c0b5adaea7632b37d02a2b48c98f439337002
6
+ metadata.gz: 06a6a371c75c3c36125f26ae556893c32e4e9d0eba25ebd6f14e20e9957408fed671ace6d587f0d98477ad8e5f856373fcb4b0eae9ec3b23e60840f8c8298dbf
7
+ data.tar.gz: cb5d756ba159db9ecc9018b6fb44c9b0f05d8a213668e686653c09963406cde6ccf136993e259fa88d2d0b9528001e456d9e3b9b1a0cf139e4ddc1f6e8baa72f
data/CHANGELOG.md CHANGED
@@ -7,7 +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.4.0] - 2025-01-13
10
+ ## [0.5.0] - 2025-09-20
11
+
12
+ ### Added
13
+ - **🔖 Bookmark System**: Complete bookmark functionality with persistent storage
14
+ - **Interactive Bookmark Menu**: Floating dialog with Add/List/Remove operations (`b` key)
15
+ - **Quick Navigation**: Number keys (1-9) for instant bookmark jumping
16
+ - **Persistent Storage**: Automatic save/load to `~/.config/beniya/bookmarks.json`
17
+ - **Comprehensive Test Suite**: Full TDD implementation with 15+ test cases
18
+ - **Multi-language Support**: English and Japanese bookmark interface
19
+ - **Safety Features**: Duplicate checking, path validation, error handling
20
+
21
+ ### Changed
22
+ - **Updated Help Messages**: Latest keybindings including bookmark operations
23
+ - **Enhanced KeybindHandler**: Integrated bookmark menu and navigation
24
+ - **Improved DirectoryListing**: Added `navigate_to_path` method for bookmark jumps
25
+ - **UI Layout Optimization**: Removed 3rd header row for cleaner interface
26
+ - **Documentation Updates**: Comprehensive README updates with bookmark usage
27
+
28
+ ### Technical Details
29
+ - New `Bookmark` class with full CRUD operations
30
+ - Maximum 9 bookmarks with automatic sorting
31
+ - Floating window system for bookmark management
32
+ - Integration with existing terminal UI components
33
+ - **Detailed changelog**: [CHANGELOG_v0.5.0.md](./CHANGELOG_v0.5.0.md)
34
+
35
+ ## [0.4.0] - 2025-09-13
11
36
 
12
37
  ### Added
13
38
  - **Floating Dialog System**: Modern floating confirmation dialogs for delete operations
@@ -40,7 +65,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
40
65
 
41
66
  For detailed information, see [CHANGELOG_v0.4.0.md](./CHANGELOG_v0.4.0.md)
42
67
 
43
- ## [0.3.0] - 2024-XX-XX
68
+ ## [0.3.0] - 2025-09-06
44
69
 
45
70
  ### Added
46
71
  - Enhanced file operations and management features
@@ -51,7 +76,7 @@ For detailed information, see [CHANGELOG_v0.4.0.md](./CHANGELOG_v0.4.0.md)
51
76
  - Performance improvements
52
77
  - Bug fixes and stability enhancements
53
78
 
54
- ## [0.2.0] - 2024-XX-XX
79
+ ## [0.2.0] - 2025-08-26
55
80
 
56
81
  ### Added
57
82
  - New features and functionality improvements
@@ -61,7 +86,7 @@ For detailed information, see [CHANGELOG_v0.4.0.md](./CHANGELOG_v0.4.0.md)
61
86
  - User interface improvements
62
87
  - Performance optimizations
63
88
 
64
- ## [0.1.0] - 2024-XX-XX
89
+ ## [0.1.0] - 2025-08-17
65
90
 
66
91
  ### Added
67
92
  - Initial release of beniya
@@ -96,4 +121,4 @@ When contributing to beniya:
96
121
  3. Link to detailed release notes for major versions
97
122
  4. Include migration notes for breaking changes
98
123
 
99
- For more information, see [Contributing Guidelines](./README.md#contributing).
124
+ For more information, see [Contributing Guidelines](./README.md#contributing).
@@ -0,0 +1,26 @@
1
+ # beniya v0.5.0 - Release Notes
2
+
3
+ ## Added
4
+ - **Bookmark System**: Complete bookmark functionality for quick directory navigation
5
+ - **Interactive Bookmark Menu**: Floating dialog accessed via `b` key with Add/List/Remove operations
6
+ - **Quick Navigation**: Number keys (1-9) for instant bookmark jumping
7
+ - **Persistent Storage**: Automatic save/load bookmarks to `~/.config/beniya/bookmarks.json`
8
+ - **Bookmark Management**: Add current directory with custom names, list all bookmarks, remove by selection
9
+ - **Safety Features**: Duplicate name/path checking, directory existence validation, maximum 9 bookmarks limit
10
+ - **Multi-language Support**: English and Japanese bookmark interface messages
11
+ - **Comprehensive Test Suite**: Full TDD implementation with 15+ test cases covering all bookmark operations
12
+ - **Error Handling**: Graceful handling of non-existent paths, permission errors, and invalid inputs
13
+
14
+ ## Changed
15
+ - **Help Messages Updated**: Latest keybinding information including bookmark operations in footer
16
+ - **KeybindHandler Enhanced**: Integrated bookmark menu and direct navigation functionality
17
+ - **DirectoryListing Improved**: Added `navigate_to_path` method for bookmark-based navigation
18
+ - **UI Layout Optimized**: Removed 3rd header row displaying bookmark shortcuts for cleaner interface
19
+ - **Documentation Updated**: Comprehensive README updates with bookmark usage examples and workflows
20
+
21
+ ## Technical Implementation
22
+ - New `Bookmark` class with full CRUD operations and JSON persistence
23
+ - Floating window system for bookmark management dialogs
24
+ - Integration with existing terminal UI components and color system
25
+ - Automatic bookmark sorting by name for consistent display
26
+ - File system verification and path expansion for reliability
data/README.md CHANGED
@@ -91,7 +91,7 @@ beniya --help # ヘルプメッセージを表示
91
91
 
92
92
  | キー | 機能 |
93
93
  | ----------- | ------------------------------------------ |
94
- | `s` | フィルターモード開始・再編集 |
94
+ | `f` | フィルターモード開始・再編集 |
95
95
  | 文字入力 | ファイル名で絞り込み(フィルターモード中) |
96
96
  | `Enter` | フィルター維持で通常モードに戻る |
97
97
  | `ESC` | フィルターをクリアして通常モードに戻る |
@@ -101,9 +101,16 @@ beniya --help # ヘルプメッセージを表示
101
101
 
102
102
  | キー | 機能 |
103
103
  | ---- | ----------------------------------------- |
104
- | `f` | fzfによるファイル名検索(プレビュー付き) |
104
+ | `s` | fzfによるファイル名検索(プレビュー付き) |
105
105
  | `F` | rgaによるファイル内容検索 |
106
106
 
107
+ #### ブックマーク機能
108
+
109
+ | キー | 機能 |
110
+ | --------- | ---------------------------- |
111
+ | `b` | ブックマークメニューを表示 |
112
+ | `1`-`9` | 対応する番号のブックマークに移動 |
113
+
107
114
  #### システム操作
108
115
 
109
116
  | キー | 機能 |
@@ -162,30 +169,30 @@ beniya起動時のディレクトリが**ベースディレクトリ**として
162
169
 
163
170
  ### フィルター機能の詳細
164
171
 
165
- #### リアルタイムフィルター (`s`)
172
+ #### リアルタイムフィルター (`f`)
166
173
 
167
- - **フィルター開始**: `s`キーを押してフィルターモードに入る
174
+ - **フィルター開始**: `f`キーを押してフィルターモードに入る
168
175
  - **文字入力による絞り込み**: 日本語・英数字・記号に対応
169
176
  - **リアルタイム更新**: 文字を入力するたびに表示が更新される
170
177
  - **フィルター維持**: `Enter`キーでフィルターを保持したまま通常操作に戻る
171
178
  - **フィルタークリア**: `ESC`キーでフィルターをクリアして通常表示に戻る
172
- - **再編集**: フィルター適用中に`s`キーで再編集モード
179
+ - **再編集**: フィルター適用中に`f`キーで再編集モード
173
180
  - **文字削除**: `Backspace`で文字削除、全削除時は自動でフィルタークリア
174
181
 
175
182
  #### 使用例
176
183
 
177
184
  ```
178
- 1. s → フィルターモード開始
185
+ 1. f → フィルターモード開始
179
186
  2. ".rb" → Rubyファイルのみ表示
180
187
  3. Enter → フィルター維持で通常操作
181
188
  4. j/k → フィルター結果内をナビゲーション
182
- 5. s → フィルター再編集
189
+ 5. f → フィルター再編集
183
190
  6. ESC → フィルタークリア
184
191
  ```
185
192
 
186
193
  ### 検索機能の詳細
187
194
 
188
- #### ファイル名検索 (`f`)
195
+ #### ファイル名検索 (`s`)
189
196
 
190
197
  - `fzf`を使用したインタラクティブなファイル名検索
191
198
  - リアルタイムプレビュー表示
@@ -197,6 +204,27 @@ beniya起動時のディレクトリが**ベースディレクトリ**として
197
204
  - PDF、Word文書、画像内テキストなども検索対象
198
205
  - 検索結果をfzfで絞り込み、該当行にジャンプしてファイルを開く
199
206
 
207
+ ### ブックマーク機能の詳細
208
+
209
+ #### ブックマーク操作 (`b`)
210
+
211
+ - **ブックマーク追加**: `[A]` - 現在のディレクトリをブックマークに追加
212
+ - **ブックマーク一覧**: `[L]` - 登録済みブックマークの一覧表示
213
+ - **ブックマーク削除**: `[R]` - ブックマークを削除
214
+ - **番号ジャンプ**: `1-9` - 対応する番号のブックマークに直接移動
215
+
216
+ #### 高速ナビゲーション (`1`-`9`)
217
+
218
+ - ブックマークメニューを経由せず、数字キーでブックマークに直接ジャンプ
219
+ - 最大9個のブックマークをサポート
220
+ - ブックマーク情報は画面上部に表示
221
+
222
+ #### ブックマークの永続化
223
+
224
+ - ブックマーク情報は `~/.config/beniya/bookmarks.json` に自動保存
225
+ - beniya再起動後もブックマーク情報が保持される
226
+ - JSONファイルを直接編集することも可能
227
+
200
228
  ### 必要な外部ツール
201
229
 
202
230
  検索機能を使用するには、以下のツールが必要です:
data/Rakefile ADDED
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+ require_relative 'lib/beniya/version'
6
+
7
+ # Test task
8
+ Rake::TestTask.new(:test) do |t|
9
+ t.libs << 'test'
10
+ t.libs << 'lib'
11
+ t.test_files = FileList['test/test_*.rb']
12
+ end
13
+
14
+ # Default task
15
+ task default: :test
16
+
17
+ # Gem build and push tasks
18
+ namespace :gem do
19
+ desc 'Build the gem file'
20
+ task :build do
21
+ puts "Building beniya v#{Beniya::VERSION}..."
22
+
23
+ # Clean old gem files
24
+ FileUtils.rm_f(Dir.glob('*.gem'))
25
+
26
+ # Build the gem
27
+ result = system('gem build beniya.gemspec')
28
+
29
+ if result
30
+ gem_file = "beniya-#{Beniya::VERSION}.gem"
31
+ puts "✅ Successfully built #{gem_file}"
32
+ else
33
+ puts "❌ Failed to build gem"
34
+ exit 1
35
+ end
36
+ end
37
+
38
+ desc 'Push the gem to RubyGems.org'
39
+ task :push => :build do
40
+ gem_file = "beniya-#{Beniya::VERSION}.gem"
41
+
42
+ unless File.exist?(gem_file)
43
+ puts "❌ Gem file not found: #{gem_file}"
44
+ exit 1
45
+ end
46
+
47
+ puts "Pushing #{gem_file} to RubyGems.org..."
48
+
49
+ result = system("gem push #{gem_file}")
50
+
51
+ if result
52
+ puts "✅ Successfully pushed #{gem_file} to RubyGems.org"
53
+ puts "🎉 beniya v#{Beniya::VERSION} is now available!"
54
+ puts "📦 Install with: gem install beniya"
55
+ else
56
+ puts "❌ Failed to push gem to RubyGems.org"
57
+ exit 1
58
+ end
59
+ end
60
+
61
+ desc 'Check if current version already exists on RubyGems.org'
62
+ task :check_version do
63
+ puts "Checking if version #{Beniya::VERSION} exists on RubyGems.org..."
64
+
65
+ result = system("gem list beniya --remote --exact --all | grep '#{Beniya::VERSION}'", out: File::NULL, err: File::NULL)
66
+
67
+ if result
68
+ puts "⚠️ Version #{Beniya::VERSION} already exists on RubyGems.org"
69
+ puts "💡 Please update the version in lib/beniya/version.rb before publishing"
70
+ exit 1
71
+ else
72
+ puts "✅ Version #{Beniya::VERSION} is available for publishing"
73
+ end
74
+ end
75
+
76
+ desc 'Clean built gem files'
77
+ task :clean do
78
+ puts "Cleaning gem files..."
79
+ removed_files = Dir.glob('*.gem')
80
+ FileUtils.rm_f(removed_files)
81
+
82
+ if removed_files.any?
83
+ puts "🗑️ Removed: #{removed_files.join(', ')}"
84
+ else
85
+ puts "✨ No gem files to clean"
86
+ end
87
+ end
88
+
89
+ desc 'Build and publish gem (with version check)'
90
+ task :publish => [:check_version, :test, :push] do
91
+ puts "🚀 Gem publishing completed successfully!"
92
+ end
93
+ end
94
+
95
+ # Release tasks
96
+ namespace :release do
97
+ desc 'Tag the current version in git'
98
+ task :tag do
99
+ version = "v#{Beniya::VERSION}"
100
+
101
+ puts "Creating git tag #{version}..."
102
+
103
+ # Check if tag already exists
104
+ if system("git tag -l | grep -q '^#{version}$'", out: File::NULL, err: File::NULL)
105
+ puts "⚠️ Tag #{version} already exists"
106
+ exit 1
107
+ end
108
+
109
+ # Create and push tag
110
+ system("git tag #{version}")
111
+ system("git push origin #{version}")
112
+
113
+ puts "✅ Created and pushed tag #{version}"
114
+ end
115
+
116
+ desc 'Prepare release (tag + gem publish)'
117
+ task :prepare => ['gem:publish', :tag] do
118
+ puts "🎉 Release v#{Beniya::VERSION} completed!"
119
+ puts "📋 Don't forget to:"
120
+ puts " - Update CHANGELOG.md"
121
+ puts " - Create GitHub release from tag"
122
+ puts " - Announce the release"
123
+ end
124
+ end
125
+
126
+ # Utility tasks
127
+ desc 'Display current version'
128
+ task :version do
129
+ puts "beniya v#{Beniya::VERSION}"
130
+ end
131
+
132
+ desc 'Run simple test for basic functionality'
133
+ task :simple_test do
134
+ puts "Running simple functionality test..."
135
+ result = system('ruby test/simple_test.rb')
136
+
137
+ if result
138
+ puts "✅ Simple test passed"
139
+ else
140
+ puts "❌ Simple test failed"
141
+ exit 1
142
+ end
143
+ end
144
+
145
+ desc 'Run bookmark tests'
146
+ task :test_bookmark do
147
+ puts "Running bookmark functionality test..."
148
+ result = system('ruby test/test_bookmark_simple.rb')
149
+
150
+ if result
151
+ puts "✅ Bookmark tests passed"
152
+ else
153
+ puts "❌ Bookmark tests failed"
154
+ exit 1
155
+ end
156
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'fileutils'
5
+
6
+ module Beniya
7
+ class Bookmark
8
+ MAX_BOOKMARKS = 9
9
+
10
+ def initialize(config_file = nil)
11
+ @config_file = config_file || default_config_file
12
+ @bookmarks = []
13
+ ensure_config_directory
14
+ load
15
+ end
16
+
17
+ def add(path, name)
18
+ return false if @bookmarks.length >= MAX_BOOKMARKS
19
+ return false if exists_by_name?(name)
20
+ return false if exists_by_path?(path)
21
+ return false unless Dir.exist?(path)
22
+
23
+ @bookmarks << { path: File.expand_path(path), name: name }
24
+ save
25
+ true
26
+ end
27
+
28
+ def remove(name)
29
+ initial_length = @bookmarks.length
30
+ @bookmarks.reject! { |bookmark| bookmark[:name] == name }
31
+
32
+ if @bookmarks.length < initial_length
33
+ save
34
+ true
35
+ else
36
+ false
37
+ end
38
+ end
39
+
40
+ def get_path(name)
41
+ bookmark = @bookmarks.find { |b| b[:name] == name }
42
+ bookmark&.[](:path)
43
+ end
44
+
45
+ def find_by_number(number)
46
+ return nil unless number.is_a?(Integer)
47
+ return nil if number < 1 || number > @bookmarks.length
48
+
49
+ sorted_bookmarks[number - 1]
50
+ end
51
+
52
+ def list
53
+ sorted_bookmarks
54
+ end
55
+
56
+ def save
57
+ begin
58
+ File.write(@config_file, JSON.pretty_generate(@bookmarks))
59
+ true
60
+ rescue StandardError => e
61
+ warn "Failed to save bookmarks: #{e.message}"
62
+ false
63
+ end
64
+ end
65
+
66
+ def load
67
+ return true unless File.exist?(@config_file)
68
+
69
+ begin
70
+ content = File.read(@config_file)
71
+ @bookmarks = JSON.parse(content, symbolize_names: true)
72
+ @bookmarks = [] unless @bookmarks.is_a?(Array)
73
+
74
+ # 無効なブックマークを除去
75
+ @bookmarks = @bookmarks.select do |bookmark|
76
+ bookmark.is_a?(Hash) &&
77
+ bookmark.key?(:path) &&
78
+ bookmark.key?(:name) &&
79
+ bookmark[:path].is_a?(String) &&
80
+ bookmark[:name].is_a?(String)
81
+ end
82
+
83
+ true
84
+ rescue JSON::ParserError, StandardError => e
85
+ warn "Failed to load bookmarks: #{e.message}"
86
+ @bookmarks = []
87
+ true
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def default_config_file
94
+ File.expand_path('~/.config/beniya/bookmarks.json')
95
+ end
96
+
97
+ def ensure_config_directory
98
+ config_dir = File.dirname(@config_file)
99
+ FileUtils.mkdir_p(config_dir) unless Dir.exist?(config_dir)
100
+ end
101
+
102
+ def exists_by_name?(name)
103
+ @bookmarks.any? { |bookmark| bookmark[:name] == name }
104
+ end
105
+
106
+ def exists_by_path?(path)
107
+ expanded_path = File.expand_path(path)
108
+ @bookmarks.any? { |bookmark| bookmark[:path] == expanded_path }
109
+ end
110
+
111
+ def sorted_bookmarks
112
+ @bookmarks.sort_by { |bookmark| bookmark[:name] }
113
+ end
114
+ end
115
+ end
data/lib/beniya/config.rb CHANGED
@@ -43,8 +43,8 @@ module Beniya
43
43
  'ui.operation_prompt' => 'Operation: ',
44
44
 
45
45
  # Help text
46
- 'help.full' => 'j/k:move h:back l:enter o/Space:open g/G:top/bottom r:refresh q:quit',
47
- 'help.short' => 'j/k:move h:back l:enter o:open q:quit',
46
+ 'help.full' => 'j/k:move h:back l:enter o:open g/G:top/bottom r:refresh f:filter s:search F:content a/A:create m/p/x:ops b:bookmark 1-9:goto q:quit',
47
+ 'help.short' => 'j/k:move h:back l:enter o:open f:filter s:search b:bookmark 1-9:goto q:quit',
48
48
 
49
49
  # Health check messages
50
50
  'health.title' => 'beniya Health Check',
@@ -111,8 +111,8 @@ module Beniya
111
111
  'ui.operation_prompt' => '操作: ',
112
112
 
113
113
  # Help text
114
- 'help.full' => 'j/k:移動 h:戻る l:入る o/Space:開く g/G:先頭/末尾 r:更新 q:終了',
115
- 'help.short' => 'j/k:移動 h:戻る l:入る o:開く q:終了',
114
+ 'help.full' => 'j/k:移動 h:戻る l:入る o:開く g/G:先頭/末尾 r:更新 f:絞込 s:検索 F:内容 a/A:作成 m/p/x:操作 b:ブックマーク 1-9:移動 q:終了',
115
+ 'help.short' => 'j/k:移動 h:戻る l:入る o:開く f:絞込 s:検索 b:ブックマーク 1-9:移動 q:終了',
116
116
 
117
117
  # Health check messages
118
118
  'health.title' => 'beniya ヘルスチェック',
@@ -67,6 +67,20 @@ module Beniya
67
67
  end
68
68
  end
69
69
 
70
+ def navigate_to_path(path)
71
+ return false if path.nil? || path.empty?
72
+
73
+ expanded_path = File.expand_path(path)
74
+
75
+ if File.directory?(expanded_path) && File.readable?(expanded_path)
76
+ @current_path = expanded_path
77
+ refresh
78
+ true
79
+ else
80
+ false
81
+ end
82
+ end
83
+
70
84
  private
71
85
 
72
86
  def determine_file_type(path)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'bookmark'
4
+
3
5
  module Beniya
4
6
  class KeybindHandler
5
7
  attr_reader :current_index, :filter_query
@@ -15,6 +17,7 @@ module Beniya
15
17
  @original_entries = []
16
18
  @selected_items = []
17
19
  @base_directory = nil
20
+ @bookmark = Bookmark.new
18
21
  end
19
22
 
20
23
  def set_directory_listing(directory_listing)
@@ -63,7 +66,7 @@ module Beniya
63
66
  open_current_file
64
67
  when 'e' # e - open directory in file explorer
65
68
  open_directory_in_explorer
66
- when 's' # s - filter files
69
+ when 'f' # f - filter files
67
70
  if !@filter_query.empty?
68
71
  # フィルタが設定されている場合は再編集モードに入る
69
72
  @filter_mode = true
@@ -86,7 +89,7 @@ module Beniya
86
89
  exit_request
87
90
  when '/' # /
88
91
  fzf_search
89
- when 'f' # f - file name search with fzf
92
+ when 's' # s - file name search with fzf
90
93
  fzf_search
91
94
  when 'F' # F - file content search with rga
92
95
  rga_search
@@ -100,6 +103,10 @@ module Beniya
100
103
  copy_selected_to_base
101
104
  when 'x' # x - delete selected files
102
105
  delete_selected_files
106
+ when 'b' # b - bookmark operations
107
+ show_bookmark_menu
108
+ when '1', '2', '3', '4', '5', '6', '7', '8', '9' # number keys - go to bookmark
109
+ goto_bookmark(key.to_i)
103
110
  else
104
111
  false # #{ConfigLoader.message('keybind.invalid_key')}
105
112
  end
@@ -872,5 +879,171 @@ module Beniya
872
879
  print "\e[#{y + row};#{x}H#{' ' * width}"
873
880
  end
874
881
  end
882
+
883
+ # ブックマーク機能
884
+ def show_bookmark_menu
885
+ current_path = @directory_listing&.current_path || Dir.pwd
886
+
887
+ # メニューの準備
888
+ title = 'Bookmark Menu'
889
+ content_lines = [
890
+ '',
891
+ '[A]dd current directory to bookmarks',
892
+ '[L]ist bookmarks',
893
+ '[R]emove bookmark',
894
+ '',
895
+ 'Press 1-9 to go to bookmark directly',
896
+ '',
897
+ 'Press any other key to cancel'
898
+ ]
899
+
900
+ dialog_width = 45
901
+ dialog_height = 4 + content_lines.length
902
+ x, y = get_screen_center(dialog_width, dialog_height)
903
+
904
+ # ダイアログの描画
905
+ draw_floating_window(x, y, dialog_width, dialog_height, title, content_lines, {
906
+ border_color: "\e[34m", # 青色
907
+ title_color: "\e[1;34m", # 太字青色
908
+ content_color: "\e[37m" # 白色
909
+ })
910
+
911
+ # キー入力待機
912
+ loop do
913
+ input = STDIN.getch.downcase
914
+
915
+ case input
916
+ when 'a'
917
+ clear_floating_window_area(x, y, dialog_width, dialog_height)
918
+ @terminal_ui&.refresh_display
919
+ add_bookmark_interactive(current_path)
920
+ return true
921
+ when 'l'
922
+ clear_floating_window_area(x, y, dialog_width, dialog_height)
923
+ @terminal_ui&.refresh_display
924
+ list_bookmarks_interactive
925
+ return true
926
+ when 'r'
927
+ clear_floating_window_area(x, y, dialog_width, dialog_height)
928
+ @terminal_ui&.refresh_display
929
+ remove_bookmark_interactive
930
+ return true
931
+ when '1', '2', '3', '4', '5', '6', '7', '8', '9'
932
+ clear_floating_window_area(x, y, dialog_width, dialog_height)
933
+ @terminal_ui&.refresh_display
934
+ goto_bookmark(input.to_i)
935
+ return true
936
+ else
937
+ # キャンセル
938
+ clear_floating_window_area(x, y, dialog_width, dialog_height)
939
+ @terminal_ui&.refresh_display
940
+ return false
941
+ end
942
+ end
943
+ end
944
+
945
+ def add_bookmark_interactive(path)
946
+ print ConfigLoader.message('bookmark.input_name') || "Enter bookmark name: "
947
+ name = STDIN.gets.chomp
948
+ return false if name.empty?
949
+
950
+ if @bookmark.add(path, name)
951
+ puts "\n#{ConfigLoader.message('bookmark.added') || 'Bookmark added'}: #{name}"
952
+ else
953
+ puts "\n#{ConfigLoader.message('bookmark.add_failed') || 'Failed to add bookmark'}"
954
+ end
955
+
956
+ print ConfigLoader.message('keybind.press_any_key') || 'Press any key to continue...'
957
+ STDIN.getch
958
+ true
959
+ end
960
+
961
+ def remove_bookmark_interactive
962
+ bookmarks = @bookmark.list
963
+
964
+ if bookmarks.empty?
965
+ puts "\n#{ConfigLoader.message('bookmark.no_bookmarks') || 'No bookmarks found'}"
966
+ print ConfigLoader.message('keybind.press_any_key') || 'Press any key to continue...'
967
+ STDIN.getch
968
+ return false
969
+ end
970
+
971
+ puts "\nBookmarks:"
972
+ bookmarks.each_with_index do |bookmark, index|
973
+ puts " #{index + 1}. #{bookmark[:name]} (#{bookmark[:path]})"
974
+ end
975
+
976
+ print ConfigLoader.message('bookmark.input_number') || "Enter number to remove: "
977
+ input = STDIN.gets.chomp
978
+ number = input.to_i
979
+
980
+ if number > 0 && number <= bookmarks.length
981
+ bookmark_to_remove = bookmarks[number - 1]
982
+ if @bookmark.remove(bookmark_to_remove[:name])
983
+ puts "\n#{ConfigLoader.message('bookmark.removed') || 'Bookmark removed'}: #{bookmark_to_remove[:name]}"
984
+ else
985
+ puts "\n#{ConfigLoader.message('bookmark.remove_failed') || 'Failed to remove bookmark'}"
986
+ end
987
+ else
988
+ puts "\n#{ConfigLoader.message('bookmark.invalid_number') || 'Invalid number'}"
989
+ end
990
+
991
+ print ConfigLoader.message('keybind.press_any_key') || 'Press any key to continue...'
992
+ STDIN.getch
993
+ true
994
+ end
995
+
996
+ def list_bookmarks_interactive
997
+ bookmarks = @bookmark.list
998
+
999
+ if bookmarks.empty?
1000
+ puts "\n#{ConfigLoader.message('bookmark.no_bookmarks') || 'No bookmarks found'}"
1001
+ print ConfigLoader.message('keybind.press_any_key') || 'Press any key to continue...'
1002
+ STDIN.getch
1003
+ return false
1004
+ end
1005
+
1006
+ puts "\nBookmarks:"
1007
+ bookmarks.each_with_index do |bookmark, index|
1008
+ puts " #{index + 1}. #{bookmark[:name]} (#{bookmark[:path]})"
1009
+ end
1010
+
1011
+ print ConfigLoader.message('keybind.press_any_key') || 'Press any key to continue...'
1012
+ STDIN.getch
1013
+ true
1014
+ end
1015
+
1016
+ def goto_bookmark(number)
1017
+ bookmark = @bookmark.find_by_number(number)
1018
+
1019
+ unless bookmark
1020
+ puts "\n#{ConfigLoader.message('bookmark.not_found') || 'Bookmark not found'}: #{number}"
1021
+ print ConfigLoader.message('keybind.press_any_key') || 'Press any key to continue...'
1022
+ STDIN.getch
1023
+ return false
1024
+ end
1025
+
1026
+ unless Dir.exist?(bookmark[:path])
1027
+ puts "\n#{ConfigLoader.message('bookmark.path_not_exist') || 'Bookmark path does not exist'}: #{bookmark[:path]}"
1028
+ print ConfigLoader.message('keybind.press_any_key') || 'Press any key to continue...'
1029
+ STDIN.getch
1030
+ return false
1031
+ end
1032
+
1033
+ # ディレクトリに移動
1034
+ result = @directory_listing.navigate_to_path(bookmark[:path])
1035
+ if result
1036
+ @current_index = 0
1037
+ clear_filter_mode
1038
+ puts "\n#{ConfigLoader.message('bookmark.navigated') || 'Navigated to bookmark'}: #{bookmark[:name]}"
1039
+ sleep(0.5) # 短時間表示
1040
+ return true
1041
+ else
1042
+ puts "\n#{ConfigLoader.message('bookmark.navigate_failed') || 'Failed to navigate to bookmark'}: #{bookmark[:name]}"
1043
+ print ConfigLoader.message('keybind.press_any_key') || 'Press any key to continue...'
1044
+ STDIN.getch
1045
+ return false
1046
+ end
1047
+ end
875
1048
  end
876
1049
  end
@@ -163,6 +163,7 @@ module Beniya
163
163
  print "\e[2;1H\e[44m\e[37m#{base_info.ljust(@screen_width)}\e[0m"
164
164
  end
165
165
 
166
+
166
167
  def draw_directory_list(entries, width, height)
167
168
  start_index = [@keybind_handler.current_index - height / 2, 0].max
168
169
  [start_index + height - 1, entries.length - 1].min
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Beniya
4
- VERSION = '0.4.0'
4
+ VERSION = '0.5.1'
5
5
  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.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - masisz
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-09-13 00:00:00.000000000 Z
10
+ date: 2025-09-23 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: io-console
@@ -118,13 +118,16 @@ extra_rdoc_files: []
118
118
  files:
119
119
  - CHANGELOG.md
120
120
  - CHANGELOG_v0.4.0.md
121
+ - CHANGELOG_v0.5.0.md
121
122
  - README.md
122
123
  - README_EN.md
124
+ - Rakefile
123
125
  - beniya.gemspec
124
126
  - bin/beniya
125
127
  - config_example.rb
126
128
  - lib/beniya.rb
127
129
  - lib/beniya/application.rb
130
+ - lib/beniya/bookmark.rb
128
131
  - lib/beniya/color_helper.rb
129
132
  - lib/beniya/config.rb
130
133
  - lib/beniya/config_loader.rb