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.
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! 📁*
@@ -15,7 +15,6 @@ module Rufio
15
15
  # 各コンポーネントを初期化
16
16
  directory_listing = DirectoryListing.new(@start_directory)
17
17
  keybind_handler = KeybindHandler.new
18
- keybind_handler.set_base_directory(@start_directory)
19
18
  file_preview = FilePreview.new
20
19
  terminal_ui = TerminalUI.new
21
20
 
@@ -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
- # Legacy fields for backward compatibility
56
- @base_directory = nil
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
- navigate_parent
98
- when 'l', "\r", "\n" # l, Enter
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 base directory
145
- move_selected_to_base
146
- when 'C' # C - copy selected files to base directory
147
- copy_selected_to_base
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
- @selection_manager.toggle_selection(entry)
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
- if show_confirmation_dialog('Move', @selection_manager.count)
565
- current_path = @directory_listing&.current_path || Dir.pwd
566
- result = @file_operations.move(@selection_manager.selected_items, current_path, @base_directory)
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
- # Show result and refresh
569
- show_operation_result(result)
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 copy_selected_to_base
579
- return false if @selection_manager.empty? || @base_directory.nil?
806
+ def copy_selected_to_current
807
+ return false if @selection_manager.empty?
580
808
 
581
- if show_confirmation_dialog('Copy', @selection_manager.count)
582
- current_path = @directory_listing&.current_path || Dir.pwd
583
- result = @file_operations.copy(@selection_manager.selected_items, current_path, @base_directory)
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
- # Show result and refresh
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 show_delete_confirmation(count)
631
- show_floating_delete_confirmation(count)
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 show_floating_delete_confirmation(count)
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 show_error_and_wait('bookmark.not_found', number) unless bookmark
857
- return show_error_and_wait('bookmark.path_not_exist', bookmark[:path]) unless @bookmark_manager.path_exists?(bookmark)
1231
+ return false unless bookmark
1232
+ return false unless @bookmark_manager.path_exists?(bookmark)
858
1233
 
859
1234
  # ディレクトリに移動
860
- if navigate_to_directory(bookmark[:path])
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