rufio 0.20.0 → 0.30.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_v0.21.0.md +175 -0
- data/CHANGELOG_v0.30.0.md +148 -0
- data/README.md +21 -15
- data/README_EN.md +8 -12
- data/info/help.md +111 -0
- data/info/keybindings.md +102 -0
- data/info/welcome.md +40 -0
- data/lib/rufio/application.rb +0 -1
- data/lib/rufio/directory_listing.rb +2 -1
- data/lib/rufio/keybind_handler.rb +415 -40
- data/lib/rufio/selection_manager.rb +10 -1
- data/lib/rufio/terminal_ui.rb +147 -156
- data/lib/rufio/text_utils.rb +49 -0
- data/lib/rufio/version.rb +1 -1
- metadata +6 -2
- data/info/welcome.txt +0 -21
data/info/welcome.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Welcome to rufio! 🚀
|
|
2
|
+
|
|
3
|
+
Thank you for using **rufio** - a terminal-based file manager inspired by [Yazi](https://github.com/sxyazi/yazi).
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
|
|
7
|
+
- **Vim-like keybindings** - Navigate with `j/k/h/l` keys
|
|
8
|
+
- **Real-time filtering** - Press `f` to filter files instantly
|
|
9
|
+
- **Advanced search** - Use `fzf` for file names and `rga` for content search
|
|
10
|
+
- **Bookmark support** - Quick access to favorite directories with number keys
|
|
11
|
+
- **Project mode** - Manage multiple projects efficiently
|
|
12
|
+
- **Plugin system** - Extend functionality with custom plugins
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
| Key | Action |
|
|
17
|
+
|-----|--------|
|
|
18
|
+
| `j/k` | Move down/up |
|
|
19
|
+
| `h/l` | Go to parent / Enter directory |
|
|
20
|
+
| `f` | Filter files |
|
|
21
|
+
| `b` | Add bookmark |
|
|
22
|
+
| `p` | Project mode |
|
|
23
|
+
| `?` | Help mode |
|
|
24
|
+
| `:` | Command mode |
|
|
25
|
+
| `q` | Quit |
|
|
26
|
+
|
|
27
|
+
## Getting Help
|
|
28
|
+
|
|
29
|
+
Press `?` to enter **Help Mode** where you can browse all documentation files.
|
|
30
|
+
Press `ESC` to exit Help Mode.
|
|
31
|
+
|
|
32
|
+
## More Information
|
|
33
|
+
|
|
34
|
+
Visit the GitHub repository for detailed documentation:
|
|
35
|
+
- **Repository**: https://github.com/masisz/rufio
|
|
36
|
+
- **Issues**: https://github.com/masisz/rufio/issues
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
*Happy file managing! 📁*
|
data/lib/rufio/application.rb
CHANGED
|
@@ -4,10 +4,11 @@ require 'fileutils'
|
|
|
4
4
|
|
|
5
5
|
module Rufio
|
|
6
6
|
class DirectoryListing
|
|
7
|
-
attr_reader :current_path
|
|
7
|
+
attr_reader :current_path, :start_directory
|
|
8
8
|
|
|
9
9
|
def initialize(path = Dir.pwd)
|
|
10
10
|
@current_path = File.expand_path(path)
|
|
11
|
+
@start_directory = @current_path # 起動時のディレクトリを保存
|
|
11
12
|
@entries = []
|
|
12
13
|
refresh
|
|
13
14
|
end
|
|
@@ -52,8 +52,13 @@ module Rufio
|
|
|
52
52
|
@in_project_mode = false
|
|
53
53
|
@in_log_mode = false
|
|
54
54
|
|
|
55
|
-
#
|
|
56
|
-
@
|
|
55
|
+
# Help mode
|
|
56
|
+
@in_help_mode = false
|
|
57
|
+
@pre_help_directory = nil
|
|
58
|
+
|
|
59
|
+
# Preview pane focus and scroll
|
|
60
|
+
@preview_focused = false
|
|
61
|
+
@preview_scroll_offset = 0
|
|
57
62
|
end
|
|
58
63
|
|
|
59
64
|
def set_directory_listing(directory_listing)
|
|
@@ -65,15 +70,18 @@ module Rufio
|
|
|
65
70
|
@terminal_ui = terminal_ui
|
|
66
71
|
end
|
|
67
72
|
|
|
68
|
-
def set_base_directory(base_dir)
|
|
69
|
-
@base_directory = File.expand_path(base_dir)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
73
|
def selected_items
|
|
73
74
|
@selection_manager.selected_items
|
|
74
75
|
end
|
|
75
76
|
|
|
76
77
|
def is_selected?(entry_name)
|
|
78
|
+
# Only show as selected if we're in the same directory where selection happened
|
|
79
|
+
current_path = @directory_listing&.current_path || Dir.pwd
|
|
80
|
+
source_path = @selection_manager.source_directory
|
|
81
|
+
|
|
82
|
+
# If no source directory or different directory, nothing is selected
|
|
83
|
+
return false if source_path.nil? || current_path != source_path
|
|
84
|
+
|
|
77
85
|
@selection_manager.selected?(entry_name)
|
|
78
86
|
end
|
|
79
87
|
|
|
@@ -85,6 +93,16 @@ module Rufio
|
|
|
85
93
|
return handle_project_mode_key(key)
|
|
86
94
|
end
|
|
87
95
|
|
|
96
|
+
# プレビューペインフォーカス中の特別処理
|
|
97
|
+
if @preview_focused
|
|
98
|
+
return handle_preview_focus_key(key)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# ヘルプモード中のESCキー特別処理
|
|
102
|
+
if @in_help_mode && key == "\e"
|
|
103
|
+
return exit_help_mode
|
|
104
|
+
end
|
|
105
|
+
|
|
88
106
|
# フィルターモード中は他のキーバインドを無効化
|
|
89
107
|
return handle_filter_input(key) if @filter_manager.filter_mode
|
|
90
108
|
|
|
@@ -94,9 +112,11 @@ module Rufio
|
|
|
94
112
|
when 'k'
|
|
95
113
|
move_up
|
|
96
114
|
when 'h'
|
|
97
|
-
|
|
98
|
-
when 'l'
|
|
115
|
+
navigate_parent_with_restriction
|
|
116
|
+
when 'l' # l - navigate into directory
|
|
99
117
|
navigate_enter
|
|
118
|
+
when "\r", "\n" # Enter - focus preview pane or navigate
|
|
119
|
+
handle_enter_key
|
|
100
120
|
when 'g'
|
|
101
121
|
move_to_top
|
|
102
122
|
when 'G'
|
|
@@ -141,10 +161,10 @@ module Rufio
|
|
|
141
161
|
create_file
|
|
142
162
|
when 'A' # A
|
|
143
163
|
create_directory
|
|
144
|
-
when 'm' # m - move selected files to
|
|
145
|
-
|
|
146
|
-
when '
|
|
147
|
-
|
|
164
|
+
when 'm' # m - move selected files to current directory
|
|
165
|
+
move_selected_to_current
|
|
166
|
+
when 'c' # c - copy selected files to current directory
|
|
167
|
+
copy_selected_to_current
|
|
148
168
|
when 'x' # x - delete selected files
|
|
149
169
|
delete_selected_files
|
|
150
170
|
when 'p' # p - project mode
|
|
@@ -153,8 +173,12 @@ module Rufio
|
|
|
153
173
|
add_bookmark
|
|
154
174
|
when 'z' # z - zoxide history navigation
|
|
155
175
|
show_zoxide_menu
|
|
176
|
+
when '0' # 0 - go to start directory
|
|
177
|
+
goto_start_directory
|
|
156
178
|
when '1', '2', '3', '4', '5', '6', '7', '8', '9' # number keys - go to bookmark
|
|
157
179
|
goto_bookmark(key.to_i)
|
|
180
|
+
when '?' # ? - enter help mode
|
|
181
|
+
enter_help_mode
|
|
158
182
|
when ':' # : - command mode
|
|
159
183
|
activate_command_mode
|
|
160
184
|
else
|
|
@@ -184,16 +208,197 @@ module Rufio
|
|
|
184
208
|
end
|
|
185
209
|
end
|
|
186
210
|
|
|
211
|
+
# ヘルプモード関連メソッド
|
|
212
|
+
|
|
213
|
+
# ヘルプモード中かどうか
|
|
214
|
+
def help_mode?
|
|
215
|
+
@in_help_mode
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# ヘルプモードに入る
|
|
219
|
+
def enter_help_mode
|
|
220
|
+
return false unless @directory_listing
|
|
221
|
+
|
|
222
|
+
# 現在のディレクトリを保存
|
|
223
|
+
@pre_help_directory = @directory_listing.current_path
|
|
224
|
+
|
|
225
|
+
# info ディレクトリに移動
|
|
226
|
+
rufio_root = File.expand_path('../..', __dir__)
|
|
227
|
+
info_dir = File.join(rufio_root, 'info')
|
|
228
|
+
|
|
229
|
+
# info ディレクトリが存在することを確認
|
|
230
|
+
return false unless Dir.exist?(info_dir)
|
|
231
|
+
|
|
232
|
+
# ヘルプモードを有効化
|
|
233
|
+
@in_help_mode = true
|
|
234
|
+
|
|
235
|
+
# info ディレクトリに移動
|
|
236
|
+
navigate_to_directory(info_dir)
|
|
237
|
+
|
|
238
|
+
true
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# ヘルプモードを終了
|
|
242
|
+
def exit_help_mode
|
|
243
|
+
return false unless @in_help_mode
|
|
244
|
+
return false unless @pre_help_directory
|
|
245
|
+
|
|
246
|
+
# ヘルプモードを無効化
|
|
247
|
+
@in_help_mode = false
|
|
248
|
+
|
|
249
|
+
# 元のディレクトリに戻る
|
|
250
|
+
navigate_to_directory(@pre_help_directory)
|
|
251
|
+
|
|
252
|
+
# 保存したディレクトリをクリア
|
|
253
|
+
@pre_help_directory = nil
|
|
254
|
+
|
|
255
|
+
true
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# ヘルプモード時の制限付き親ディレクトリナビゲーション
|
|
259
|
+
def navigate_parent_with_restriction
|
|
260
|
+
if @in_help_mode
|
|
261
|
+
# info ディレクトリより上には移動できない
|
|
262
|
+
rufio_root = File.expand_path('../..', __dir__)
|
|
263
|
+
info_dir = File.join(rufio_root, 'info')
|
|
264
|
+
|
|
265
|
+
current_path = @directory_listing.current_path
|
|
266
|
+
|
|
267
|
+
# 現在のパスが info ディレクトリ以下でない場合は移動を許可しない
|
|
268
|
+
unless current_path.start_with?(info_dir)
|
|
269
|
+
return false
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# 現在のパスが info ディレクトリそのものの場合は移動を許可しない
|
|
273
|
+
if current_path == info_dir
|
|
274
|
+
return false
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# info ディレクトリ配下であれば、通常のナビゲーションを実行
|
|
278
|
+
navigate_parent
|
|
279
|
+
else
|
|
280
|
+
# ヘルプモード外では通常のナビゲーション
|
|
281
|
+
navigate_parent
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# プレビューペイン関連メソッド
|
|
286
|
+
|
|
287
|
+
# プレビューペインがフォーカスされているか
|
|
288
|
+
def preview_focused?
|
|
289
|
+
@preview_focused
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# プレビューペインにフォーカスを移す
|
|
293
|
+
def focus_preview_pane
|
|
294
|
+
# ファイルが選択されている場合のみフォーカス可能
|
|
295
|
+
entry = current_entry
|
|
296
|
+
return false unless entry
|
|
297
|
+
return false unless entry[:type] == 'file'
|
|
298
|
+
|
|
299
|
+
@preview_focused = true
|
|
300
|
+
@preview_scroll_offset = 0 # フォーカス時にスクロール位置をリセット
|
|
301
|
+
true
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# プレビューペインのフォーカスを解除
|
|
305
|
+
def unfocus_preview_pane
|
|
306
|
+
return false unless @preview_focused
|
|
307
|
+
|
|
308
|
+
@preview_focused = false
|
|
309
|
+
true
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# 現在のプレビュースクロールオフセット
|
|
313
|
+
def preview_scroll_offset
|
|
314
|
+
@preview_scroll_offset
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# プレビューを1行下にスクロール
|
|
318
|
+
def scroll_preview_down
|
|
319
|
+
return false unless @preview_focused
|
|
320
|
+
|
|
321
|
+
@preview_scroll_offset += 1
|
|
322
|
+
true
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# プレビューを1行上にスクロール
|
|
326
|
+
def scroll_preview_up
|
|
327
|
+
return false unless @preview_focused
|
|
328
|
+
|
|
329
|
+
@preview_scroll_offset = [@preview_scroll_offset - 1, 0].max
|
|
330
|
+
true
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# プレビューを半画面下にスクロール(Ctrl+D)
|
|
334
|
+
def scroll_preview_page_down
|
|
335
|
+
return false unless @preview_focused
|
|
336
|
+
|
|
337
|
+
# 半画面分スクロール(仮に20行とする)
|
|
338
|
+
page_size = 20
|
|
339
|
+
@preview_scroll_offset += page_size
|
|
340
|
+
true
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# プレビューを半画面上にスクロール(Ctrl+U)
|
|
344
|
+
def scroll_preview_page_up
|
|
345
|
+
return false unless @preview_focused
|
|
346
|
+
|
|
347
|
+
# 半画面分スクロール(仮に20行とする)
|
|
348
|
+
page_size = 20
|
|
349
|
+
@preview_scroll_offset = [@preview_scroll_offset - page_size, 0].max
|
|
350
|
+
true
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# プレビュースクロール位置をリセット(ファイル変更時など)
|
|
354
|
+
def reset_preview_scroll
|
|
355
|
+
@preview_scroll_offset = 0
|
|
356
|
+
end
|
|
357
|
+
|
|
187
358
|
private
|
|
188
359
|
|
|
360
|
+
# Enterキーの処理:ファイルならプレビューフォーカス、ディレクトリならナビゲート
|
|
361
|
+
def handle_enter_key
|
|
362
|
+
entry = current_entry
|
|
363
|
+
return false unless entry
|
|
364
|
+
|
|
365
|
+
if entry[:type] == 'file'
|
|
366
|
+
# ファイルの場合はプレビューペインにフォーカス
|
|
367
|
+
focus_preview_pane
|
|
368
|
+
else
|
|
369
|
+
# ディレクトリの場合は通常のナビゲーション
|
|
370
|
+
navigate_enter
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# プレビューペインフォーカス中のキー処理
|
|
375
|
+
def handle_preview_focus_key(key)
|
|
376
|
+
case key
|
|
377
|
+
when 'j', "\e[B" # j or Down arrow
|
|
378
|
+
scroll_preview_down
|
|
379
|
+
when 'k', "\e[A" # k or Up arrow
|
|
380
|
+
scroll_preview_up
|
|
381
|
+
when "\x04" # Ctrl+D
|
|
382
|
+
scroll_preview_page_down
|
|
383
|
+
when "\x15" # Ctrl+U
|
|
384
|
+
scroll_preview_page_up
|
|
385
|
+
when "\e" # ESC
|
|
386
|
+
unfocus_preview_pane
|
|
387
|
+
else
|
|
388
|
+
false # Unknown key in preview mode
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
189
392
|
def move_down
|
|
190
393
|
entries = get_active_entries
|
|
191
394
|
@current_index = [@current_index + 1, entries.length - 1].min
|
|
395
|
+
reset_preview_scroll # ファイル変更時にスクロール位置をリセット
|
|
192
396
|
true
|
|
193
397
|
end
|
|
194
398
|
|
|
195
399
|
def move_up
|
|
196
400
|
@current_index = [@current_index - 1, 0].max
|
|
401
|
+
reset_preview_scroll # ファイル変更時にスクロール位置をリセット
|
|
197
402
|
true
|
|
198
403
|
end
|
|
199
404
|
|
|
@@ -483,11 +688,29 @@ module Rufio
|
|
|
483
688
|
end
|
|
484
689
|
|
|
485
690
|
def delete_current_file_with_confirmation
|
|
691
|
+
current_path = @directory_listing&.current_path || Dir.pwd
|
|
692
|
+
|
|
693
|
+
# Check if there are selected items
|
|
694
|
+
if !@selection_manager.empty?
|
|
695
|
+
# Delete multiple selected items
|
|
696
|
+
source_path = @selection_manager.source_directory || current_path
|
|
697
|
+
|
|
698
|
+
if show_delete_confirmation(@selection_manager.count, source_path)
|
|
699
|
+
result = @file_operations.delete(@selection_manager.selected_items, source_path)
|
|
700
|
+
@selection_manager.clear
|
|
701
|
+
@directory_listing.refresh if @directory_listing
|
|
702
|
+
@terminal_ui&.refresh_display
|
|
703
|
+
return result.success
|
|
704
|
+
else
|
|
705
|
+
return false
|
|
706
|
+
end
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
# Single file deletion (current item)
|
|
486
710
|
current_item = current_entry()
|
|
487
711
|
return false unless current_item
|
|
488
712
|
|
|
489
713
|
current_name = current_item[:name]
|
|
490
|
-
current_path = @directory_listing&.current_path || Dir.pwd
|
|
491
714
|
is_directory = current_item[:type] == :directory
|
|
492
715
|
|
|
493
716
|
# 確認ダイアログを表示
|
|
@@ -554,19 +777,24 @@ module Rufio
|
|
|
554
777
|
entry = current_entry
|
|
555
778
|
return false unless entry
|
|
556
779
|
|
|
557
|
-
@
|
|
780
|
+
current_path = @directory_listing&.current_path || Dir.pwd
|
|
781
|
+
@selection_manager.toggle_selection(entry, current_path)
|
|
558
782
|
true
|
|
559
783
|
end
|
|
560
784
|
|
|
561
|
-
def move_selected_to_base
|
|
562
|
-
return false if @selection_manager.empty? || @base_directory.nil?
|
|
563
785
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
786
|
+
def move_selected_to_current
|
|
787
|
+
return false if @selection_manager.empty?
|
|
788
|
+
|
|
789
|
+
current_path = @directory_listing&.current_path || Dir.pwd
|
|
790
|
+
source_path = @selection_manager.source_directory || current_path
|
|
567
791
|
|
|
568
|
-
|
|
569
|
-
|
|
792
|
+
# Move selected files/directories from source directory to current directory
|
|
793
|
+
# This allows moving files even after navigating to a different directory
|
|
794
|
+
if show_move_confirmation(@selection_manager.count, source_path, current_path)
|
|
795
|
+
result = @file_operations.move(@selection_manager.selected_items, source_path, current_path)
|
|
796
|
+
|
|
797
|
+
# Clear selection and refresh
|
|
570
798
|
@selection_manager.clear
|
|
571
799
|
@directory_listing.refresh if @directory_listing
|
|
572
800
|
true
|
|
@@ -575,15 +803,18 @@ module Rufio
|
|
|
575
803
|
end
|
|
576
804
|
end
|
|
577
805
|
|
|
578
|
-
def
|
|
579
|
-
return false if @selection_manager.empty?
|
|
806
|
+
def copy_selected_to_current
|
|
807
|
+
return false if @selection_manager.empty?
|
|
580
808
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
809
|
+
current_path = @directory_listing&.current_path || Dir.pwd
|
|
810
|
+
source_path = @selection_manager.source_directory || current_path
|
|
811
|
+
|
|
812
|
+
# Copy selected files/directories from source directory to current directory
|
|
813
|
+
# This allows copying files even after navigating to a different directory
|
|
814
|
+
if show_copy_confirmation(@selection_manager.count, source_path, current_path)
|
|
815
|
+
result = @file_operations.copy(@selection_manager.selected_items, source_path, current_path)
|
|
584
816
|
|
|
585
|
-
#
|
|
586
|
-
show_operation_result(result)
|
|
817
|
+
# Clear selection and refresh
|
|
587
818
|
@selection_manager.clear
|
|
588
819
|
@directory_listing.refresh if @directory_listing
|
|
589
820
|
true
|
|
@@ -627,17 +858,31 @@ module Rufio
|
|
|
627
858
|
end
|
|
628
859
|
end
|
|
629
860
|
|
|
630
|
-
def
|
|
631
|
-
|
|
861
|
+
def show_move_confirmation(count, source_path, dest_path)
|
|
862
|
+
show_floating_move_confirmation(count, source_path, dest_path)
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
def show_copy_confirmation(count, source_path, dest_path)
|
|
866
|
+
show_floating_copy_confirmation(count, source_path, dest_path)
|
|
632
867
|
end
|
|
633
868
|
|
|
634
|
-
def
|
|
869
|
+
def show_delete_confirmation(count, source_path)
|
|
870
|
+
show_floating_delete_confirmation(count, source_path)
|
|
871
|
+
end
|
|
872
|
+
|
|
873
|
+
def show_floating_delete_confirmation(count, source_path)
|
|
635
874
|
# コンテンツの準備
|
|
636
875
|
title = 'Delete Confirmation'
|
|
876
|
+
|
|
877
|
+
# パスの短縮表示
|
|
878
|
+
source_display = shorten_path(source_path, 35)
|
|
879
|
+
|
|
637
880
|
content_lines = [
|
|
638
881
|
'',
|
|
639
882
|
"Delete #{count} item(s)?",
|
|
640
883
|
'',
|
|
884
|
+
"From: #{source_display}",
|
|
885
|
+
'',
|
|
641
886
|
' [Y]es - Delete',
|
|
642
887
|
' [N]o - Cancel',
|
|
643
888
|
''
|
|
@@ -685,6 +930,128 @@ module Rufio
|
|
|
685
930
|
end
|
|
686
931
|
end
|
|
687
932
|
|
|
933
|
+
def show_floating_move_confirmation(count, source_path, dest_path)
|
|
934
|
+
# コンテンツの準備
|
|
935
|
+
title = 'Move Confirmation'
|
|
936
|
+
|
|
937
|
+
# パスの短縮表示
|
|
938
|
+
source_display = shorten_path(source_path, 35)
|
|
939
|
+
dest_display = shorten_path(dest_path, 35)
|
|
940
|
+
|
|
941
|
+
content_lines = [
|
|
942
|
+
'',
|
|
943
|
+
"Move #{count} item(s)?",
|
|
944
|
+
'',
|
|
945
|
+
"From: #{source_display}",
|
|
946
|
+
"To: #{dest_display}",
|
|
947
|
+
'',
|
|
948
|
+
' [Y]es - Move',
|
|
949
|
+
' [N]o - Cancel',
|
|
950
|
+
''
|
|
951
|
+
]
|
|
952
|
+
|
|
953
|
+
# ダイアログのサイズ設定
|
|
954
|
+
dialog_width = CONFIRMATION_DIALOG_WIDTH
|
|
955
|
+
dialog_height = DIALOG_BORDER_HEIGHT + content_lines.length
|
|
956
|
+
|
|
957
|
+
# ダイアログの位置を中央に設定
|
|
958
|
+
x, y = @dialog_renderer.calculate_center(dialog_width, dialog_height)
|
|
959
|
+
|
|
960
|
+
# ダイアログの描画(移動は青色で表示)
|
|
961
|
+
@dialog_renderer.draw_floating_window(x, y, dialog_width, dialog_height, title, content_lines, {
|
|
962
|
+
border_color: "\e[34m", # 青色(情報)
|
|
963
|
+
title_color: "\e[1;34m", # 太字青色
|
|
964
|
+
content_color: "\e[37m" # 白色
|
|
965
|
+
})
|
|
966
|
+
|
|
967
|
+
# キー入力待機
|
|
968
|
+
loop do
|
|
969
|
+
input = STDIN.getch.downcase
|
|
970
|
+
|
|
971
|
+
case input
|
|
972
|
+
when 'y'
|
|
973
|
+
# ダイアログをクリア
|
|
974
|
+
@dialog_renderer.clear_area(x, y, dialog_width, dialog_height)
|
|
975
|
+
@terminal_ui&.refresh_display # 画面を再描画
|
|
976
|
+
return true
|
|
977
|
+
when 'n', "\e", "\x03" # n, ESC, Ctrl+C
|
|
978
|
+
# ダイアログをクリア
|
|
979
|
+
@dialog_renderer.clear_area(x, y, dialog_width, dialog_height)
|
|
980
|
+
@terminal_ui&.refresh_display # 画面を再描画
|
|
981
|
+
return false
|
|
982
|
+
when 'q' # qキーでもキャンセル
|
|
983
|
+
@dialog_renderer.clear_area(x, y, dialog_width, dialog_height)
|
|
984
|
+
@terminal_ui&.refresh_display
|
|
985
|
+
return false
|
|
986
|
+
end
|
|
987
|
+
# 無効なキー入力の場合は再度ループ
|
|
988
|
+
end
|
|
989
|
+
end
|
|
990
|
+
|
|
991
|
+
def show_floating_copy_confirmation(count, source_path, dest_path)
|
|
992
|
+
# コンテンツの準備
|
|
993
|
+
title = 'Copy Confirmation'
|
|
994
|
+
|
|
995
|
+
# パスの短縮表示
|
|
996
|
+
source_display = shorten_path(source_path, 35)
|
|
997
|
+
dest_display = shorten_path(dest_path, 35)
|
|
998
|
+
|
|
999
|
+
content_lines = [
|
|
1000
|
+
'',
|
|
1001
|
+
"Copy #{count} item(s)?",
|
|
1002
|
+
'',
|
|
1003
|
+
"From: #{source_display}",
|
|
1004
|
+
"To: #{dest_display}",
|
|
1005
|
+
'',
|
|
1006
|
+
' [Y]es - Copy',
|
|
1007
|
+
' [N]o - Cancel',
|
|
1008
|
+
''
|
|
1009
|
+
]
|
|
1010
|
+
|
|
1011
|
+
# ダイアログのサイズ設定
|
|
1012
|
+
dialog_width = CONFIRMATION_DIALOG_WIDTH
|
|
1013
|
+
dialog_height = DIALOG_BORDER_HEIGHT + content_lines.length
|
|
1014
|
+
|
|
1015
|
+
# ダイアログの位置を中央に設定
|
|
1016
|
+
x, y = @dialog_renderer.calculate_center(dialog_width, dialog_height)
|
|
1017
|
+
|
|
1018
|
+
# ダイアログの描画(コピーは緑色で表示)
|
|
1019
|
+
@dialog_renderer.draw_floating_window(x, y, dialog_width, dialog_height, title, content_lines, {
|
|
1020
|
+
border_color: "\e[32m", # 緑色(安全な操作)
|
|
1021
|
+
title_color: "\e[1;32m", # 太字緑色
|
|
1022
|
+
content_color: "\e[37m" # 白色
|
|
1023
|
+
})
|
|
1024
|
+
|
|
1025
|
+
# キー入力待機
|
|
1026
|
+
loop do
|
|
1027
|
+
input = STDIN.getch.downcase
|
|
1028
|
+
|
|
1029
|
+
case input
|
|
1030
|
+
when 'y'
|
|
1031
|
+
# ダイアログをクリア
|
|
1032
|
+
@dialog_renderer.clear_area(x, y, dialog_width, dialog_height)
|
|
1033
|
+
@terminal_ui&.refresh_display # 画面を再描画
|
|
1034
|
+
return true
|
|
1035
|
+
when 'n', "\e", "\x03" # n, ESC, Ctrl+C
|
|
1036
|
+
# ダイアログをクリア
|
|
1037
|
+
@dialog_renderer.clear_area(x, y, dialog_width, dialog_height)
|
|
1038
|
+
@terminal_ui&.refresh_display # 画面を再描画
|
|
1039
|
+
return false
|
|
1040
|
+
when 'q' # qキーでもキャンセル
|
|
1041
|
+
@dialog_renderer.clear_area(x, y, dialog_width, dialog_height)
|
|
1042
|
+
@terminal_ui&.refresh_display
|
|
1043
|
+
return false
|
|
1044
|
+
end
|
|
1045
|
+
# 無効なキー入力の場合は再度ループ
|
|
1046
|
+
end
|
|
1047
|
+
end
|
|
1048
|
+
|
|
1049
|
+
# パスを指定した長さに短縮
|
|
1050
|
+
def shorten_path(path, max_length)
|
|
1051
|
+
return path if path.length <= max_length
|
|
1052
|
+
"...#{path[-(max_length - 3)..-1]}"
|
|
1053
|
+
end
|
|
1054
|
+
|
|
688
1055
|
def perform_delete_operation(items)
|
|
689
1056
|
Logger.debug('Starting delete operation', context: { items: items, count: items.length })
|
|
690
1057
|
|
|
@@ -850,20 +1217,22 @@ module Rufio
|
|
|
850
1217
|
end
|
|
851
1218
|
end
|
|
852
1219
|
|
|
1220
|
+
def goto_start_directory
|
|
1221
|
+
start_dir = @directory_listing&.start_directory
|
|
1222
|
+
return false unless start_dir
|
|
1223
|
+
|
|
1224
|
+
# 起動ディレクトリに移動
|
|
1225
|
+
navigate_to_directory(start_dir)
|
|
1226
|
+
end
|
|
1227
|
+
|
|
853
1228
|
def goto_bookmark(number)
|
|
854
1229
|
bookmark = @bookmark_manager.find_by_number(number)
|
|
855
1230
|
|
|
856
|
-
return
|
|
857
|
-
return
|
|
1231
|
+
return false unless bookmark
|
|
1232
|
+
return false unless @bookmark_manager.path_exists?(bookmark)
|
|
858
1233
|
|
|
859
1234
|
# ディレクトリに移動
|
|
860
|
-
|
|
861
|
-
puts "\n#{ConfigLoader.message('bookmark.navigated') || 'Navigated to bookmark'}: #{bookmark[:name]}"
|
|
862
|
-
sleep(0.5) # 短時間表示
|
|
863
|
-
true
|
|
864
|
-
else
|
|
865
|
-
show_error_and_wait('bookmark.navigate_failed', bookmark[:name])
|
|
866
|
-
end
|
|
1235
|
+
navigate_to_directory(bookmark[:path])
|
|
867
1236
|
end
|
|
868
1237
|
|
|
869
1238
|
def add_bookmark
|
|
@@ -1243,5 +1612,11 @@ module Rufio
|
|
|
1243
1612
|
def in_log_mode?
|
|
1244
1613
|
@in_log_mode
|
|
1245
1614
|
end
|
|
1615
|
+
|
|
1616
|
+
# ヘルプダイアログを表示
|
|
1617
|
+
def show_help_dialog
|
|
1618
|
+
@terminal_ui&.show_help_dialog if @terminal_ui
|
|
1619
|
+
true
|
|
1620
|
+
end
|
|
1246
1621
|
end
|
|
1247
1622
|
end
|
|
@@ -3,20 +3,28 @@
|
|
|
3
3
|
module Rufio
|
|
4
4
|
# Manages selected items (files/directories) for bulk operations
|
|
5
5
|
class SelectionManager
|
|
6
|
+
attr_reader :source_directory
|
|
7
|
+
|
|
6
8
|
def initialize
|
|
7
9
|
@selected_items = []
|
|
10
|
+
@source_directory = nil
|
|
8
11
|
end
|
|
9
12
|
|
|
10
13
|
# Toggle selection for an entry
|
|
11
14
|
# @param entry [Hash] Entry with :name key
|
|
15
|
+
# @param current_directory [String, nil] Current directory path (optional)
|
|
12
16
|
# @return [Boolean] true if now selected, false if unselected
|
|
13
|
-
def toggle_selection(entry)
|
|
17
|
+
def toggle_selection(entry, current_directory = nil)
|
|
14
18
|
return false unless entry
|
|
15
19
|
|
|
16
20
|
if @selected_items.include?(entry[:name])
|
|
17
21
|
@selected_items.delete(entry[:name])
|
|
22
|
+
# Clear source_directory if no items are selected
|
|
23
|
+
@source_directory = nil if @selected_items.empty?
|
|
18
24
|
false
|
|
19
25
|
else
|
|
26
|
+
# Set source directory on first selection
|
|
27
|
+
@source_directory = current_directory if @selected_items.empty? && current_directory
|
|
20
28
|
@selected_items << entry[:name]
|
|
21
29
|
true
|
|
22
30
|
end
|
|
@@ -38,6 +46,7 @@ module Rufio
|
|
|
38
46
|
# Clear all selections
|
|
39
47
|
def clear
|
|
40
48
|
@selected_items.clear
|
|
49
|
+
@source_directory = nil
|
|
41
50
|
end
|
|
42
51
|
|
|
43
52
|
# Check if any items are selected
|