rufio 1.3.0 → 1.4.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/keybindings.md CHANGED
@@ -9,22 +9,24 @@ Complete reference of all keyboard shortcuts in **rufio**.
9
9
  | `j` / `Down` | Move down |
10
10
  | `k` / `Up` | Move up |
11
11
  | `h` / `Left` | Go to parent directory |
12
- | `l` / `Right` / `Enter` | Enter directory or open file |
12
+ | `l` / `Right` / `Enter` | Enter directory |
13
13
  | `g` | Jump to top of list |
14
- | `G` | Jump to bottom of list |
14
+ | `}` | Jump to bottom of list |
15
15
 
16
16
  ## File Operations
17
17
 
18
18
  | Key | Action |
19
19
  |-----|--------|
20
20
  | `o` | Open file with external editor |
21
- | `e` | Open in file explorer (system default) |
21
+ | `e` | Open current directory in file explorer |
22
22
  | `R` | Refresh directory contents |
23
23
  | `r` | Rename file or directory |
24
24
  | `d` | Delete file or directory (with confirmation) |
25
25
  | `a` | Create new file |
26
26
  | `A` | Create new directory |
27
27
 
28
+ `.zip` previews list the archive entries (size, method, modified time, name) without extracting them.
29
+
28
30
  ## File Selection
29
31
 
30
32
  | Key | Action |
@@ -33,7 +35,9 @@ Complete reference of all keyboard shortcuts in **rufio**.
33
35
  | `m` | Move selected items to current directory |
34
36
  | `c` | Copy selected items to current directory |
35
37
  | `x` | Delete selected items |
36
- | `z` | Extract selected `.zip` files / compress selected items into a `.zip` |
38
+ | `z` | Extract selected `.zip` files (each into `<name>/`) / compress selected items into one `.zip` |
39
+
40
+ ZIP extraction and compression run as background jobs. Check progress in Jobs mode (`J`).
37
41
 
38
42
  ## Filtering & Search
39
43
 
@@ -41,7 +45,9 @@ Complete reference of all keyboard shortcuts in **rufio**.
41
45
  |-----|--------|
42
46
  | `f` | Enter filter mode (type to filter) |
43
47
  | `s` | Search file names with fzf |
48
+ | `/` | Search file names with fzf (same as `s`) |
44
49
  | `F` | Search file contents with rga |
50
+ | `G` | Global search: file names or contents across all bookmarks |
45
51
 
46
52
  ## Bookmarks & Navigation
47
53
 
@@ -51,31 +57,33 @@ Complete reference of all keyboard shortcuts in **rufio**.
51
57
  | `B` | Bookmark menu (view/add/remove/script paths) |
52
58
  | `0` | Jump to startup directory |
53
59
  | `1-9` | Jump to bookmark 1-9 |
60
+ | `Tab` | Cycle to next bookmark (Files mode) |
61
+ | `Shift+Tab` | Cycle to previous bookmark (Files mode) |
54
62
  | `z` | Navigate using zoxide history (when nothing is selected) |
55
63
 
56
- ## Command Mode & Tool Runtime
64
+ ## Modes & Tool Runtime
57
65
 
58
66
  | Key | Action |
59
67
  |-----|--------|
60
68
  | `:` | Enter command mode |
69
+ | `G` | Global search mode |
70
+ | `C` | Config mode: open `~/.config/rufio/` |
61
71
  | `J` | Job mode (view background jobs) |
62
72
  | `L` | View execution logs |
63
-
64
- ## Mode Switching
65
-
66
- | Key | Action |
67
- |-----|--------|
68
- | `Tab` | Switch mode: Files -> Logs -> Jobs -> Help |
69
- | `Shift+Tab` | Switch mode (reverse direction) |
70
- | `?` | Enter help mode |
71
- | `ESC` | Exit current mode (help/filter/command) |
72
- | `q` | Quit rufio |
73
+ | `?` | Open the floating help menu |
74
+ | `q` | Quit rufio (with confirmation) |
73
75
  | `T` | Quit rufio and cd the shell to the current directory (requires `rufio --shell-init`) |
74
76
 
75
77
  ---
76
78
 
77
79
  ## Mode-Specific Keys
78
80
 
81
+ ### Preview
82
+ - **P**: Open fullscreen file preview (hides the file list)
83
+ - **j/k**: Scroll preview
84
+ - **Ctrl+D/U**: Page down/up
85
+ - **ESC**: Back to the file list
86
+
79
87
  ### Filter Mode
80
88
  - **Any character**: Add to filter query
81
89
  - **Backspace**: Remove last character
@@ -84,7 +92,7 @@ Complete reference of all keyboard shortcuts in **rufio**.
84
92
 
85
93
  ### Command Mode
86
94
  - **Any character**: Add to command input
87
- - **Tab**: Auto-complete (scripts, rake tasks, commands)
95
+ - **Tab**: Auto-complete (scripts, rake tasks, commands, paths)
88
96
  - **Up/Down**: Navigate command history
89
97
  - **Enter**: Execute command
90
98
  - **Backspace**: Remove last character
@@ -94,27 +102,45 @@ Complete reference of all keyboard shortcuts in **rufio**.
94
102
  - `@script` - Execute a registered or local script
95
103
  - `rake:task` - Execute a Rakefile task
96
104
  - `!command` - Execute a shell command
105
+ - `cd <path>` - Go to a directory (paste a path; a file path selects the file, `cd -` goes back, `Tab` completes directories)
97
106
  - `command` - Execute a built-in or plugin command
98
107
 
99
- ### Help Mode
100
- - **j/k/h/l**: Navigate help files
101
- - **ESC**: Exit help mode and return to previous directory
108
+ ### Global Search (G)
109
+ - **Type**: Filter results (literal, case insensitive)
110
+ - **Tab**: Switch between file names and content
111
+ - **Up/Down** or **Ctrl+J/K or Ctrl+N/P**: Select a result
112
+ - **Enter**: Go to the selected file in Files mode
113
+ - **Ctrl+U**: Clear the query
114
+ - **ESC**: Return to the previous mode
115
+ - Content search also reads `.pdf`, `.xlsx` and `.pptx`
102
116
 
103
- ### Job Mode
117
+ ### Config Mode (C)
118
+ - Same navigation, preview and file operations as Files mode
119
+ - Leaving `~/.config/rufio/` returns to Files mode
120
+
121
+ ### Job Mode (J)
104
122
  - **j/k**: Navigate through jobs
123
+ - **g/G**: First / last job
124
+ - **Space**: View job log
125
+ - **`x`: Cancel** the selected job (including ZIP jobs)
105
126
  - **ESC**: Exit job mode
106
127
 
128
+ ### Help Menu (?)
129
+ - **1/2/3** or **Tab/Shift+Tab**: Switch section (Controls / Modes / Scripts)
130
+ - **j/k**: Scroll | **Ctrl+D/U**: Page down/up | **g/G**: Top / bottom
131
+ - **ESC**, **q** or **?**: Close and keep your current mode and directory
132
+
107
133
  ---
108
134
 
109
135
  ## Tips
110
136
 
111
137
  - **Vim users**: Navigation keys (`hjkl`) work exactly like Vim
112
138
  - **Selection**: Use `Space` to mark multiple files, then operate on all at once
113
- - **Quick access**: Number keys `1-9` provide instant access to bookmarks
139
+ - **Quick access**: Number keys `1-9` and `Tab` provide instant access to bookmarks
114
140
  - **Filtering**: Press `f` and start typing for real-time filtering
115
141
  - **Rake tasks**: Type `:rake:` then `Tab` to see available tasks
116
142
  - **Local scripts**: Scripts in the current directory are auto-detected with `@` prefix
117
143
 
118
144
  ---
119
145
 
120
- *Press `?` anytime to return to help mode*
146
+ *Press `?` anytime to open the help menu*
data/info/welcome.md CHANGED
@@ -10,6 +10,8 @@ A TUI file manager that executes and coordinates tools and scripts from files.
10
10
  - **Vim-like Keybindings** - Navigate with `j/k/h/l` keys
11
11
  - **Real-time Filtering** - Press `f` to filter files instantly
12
12
  - **Advanced Search** - Use `fzf` for file names and `rga` for content search
13
+ - **Global Search** - Press `G` to search names or contents (including PDF/Excel/PowerPoint) across all bookmarks
14
+ - **ZIP Support** - Preview `.zip` contents, extract or compress selected items with `z`
13
15
  - **Bookmark Support** - Quick access to favorite directories
14
16
  - **Job Management** - Run commands in the background
15
17
  - **Plugin System** - Extend functionality with custom plugins
@@ -24,12 +26,18 @@ A TUI file manager that executes and coordinates tools and scripts from files.
24
26
  | `s` | Search with fzf |
25
27
  | `b` | Add bookmark |
26
28
  | `B` | Bookmark menu |
29
+ | `Tab` | Next bookmark |
27
30
  | `z` | zoxide history / extract or compress selected items |
31
+ | `G` | Global search across bookmarks |
32
+ | `C` | Config mode |
28
33
  | `:` | Command mode |
29
34
  | `J` | Job mode |
30
- | `?` | Help mode |
31
- | `Tab` | Switch mode (Files / Logs / Jobs / Help) |
35
+ | `L` | Execution logs |
36
+ | `?` | Help menu |
32
37
  | `q` | Quit |
38
+ | `T` | Quit and cd to the current directory |
39
+
40
+ In G mode, use `Ctrl+J` / `Ctrl+K` to select the next / previous result and `Enter` to open it.
33
41
 
34
42
  ## Command Mode
35
43
 
@@ -38,12 +46,13 @@ Press `:` to enter command mode:
38
46
  - `@script.sh` - Execute a script
39
47
  - `rake:test` - Execute a rake task
40
48
  - `!git status` - Run a shell command
49
+ - `cd ~/projects` - Go to a pasted path
41
50
  - `Tab` - Auto-complete commands
42
51
 
43
52
  ## Getting Help
44
53
 
45
- Press `?` to enter **Help Mode** where you can browse all documentation files.
46
- Press `ESC` to exit Help Mode.
54
+ Press `?` to open the floating **Help Menu** (Controls / Modes / Scripts).
55
+ Switch sections with `1/2/3` or `Tab`, scroll with `j/k`, and close with `ESC`.
47
56
 
48
57
  ## More Information
49
58
 
@@ -3,6 +3,9 @@
3
3
  module Rufio
4
4
  # コマンド補完機能を提供するクラス
5
5
  class CommandCompletion
6
+ # CommandMode が直接処理する(DSL コマンドではない)コマンド
7
+ NAVIGATION_COMMANDS = %w[cd].freeze
8
+
6
9
  # 初期化
7
10
  # @param history [CommandHistory, CommandMode, nil] 履歴、または単独で渡すコマンドモード
8
11
  # @param command_mode [CommandMode, nil] コマンドモード(スクリプト補完に使用)
@@ -28,6 +31,11 @@ module Rufio
28
31
  rake_candidates('')
29
32
  end
30
33
 
34
+ # ディレクトリ補完(cd で始まる場合)
35
+ if (match = input.match(/\A\s*cd\s+(.*)\z/))
36
+ return complete_cd(match[1])
37
+ end
38
+
31
39
  # シェルコマンド補完(!で始まる場合)
32
40
  if input.strip.start_with?('!')
33
41
  return complete_shell_command(input.strip)
@@ -45,7 +53,7 @@ module Rufio
45
53
  end
46
54
 
47
55
  # 通常のコマンド補完(内部コマンド + rakeタスク)
48
- available_commands = @command_mode.available_commands.map(&:to_s)
56
+ available_commands = @command_mode.available_commands.map(&:to_s) + NAVIGATION_COMMANDS
49
57
  input_lower = input.downcase
50
58
  candidates = available_commands.select do |command|
51
59
  command.downcase.start_with?(input_lower)
@@ -87,6 +95,19 @@ module Rufio
87
95
  # スクリプト候補を取得(ScriptRunner + ローカルスクリプト)
88
96
  # @param prefix [String] 入力中の文字列
89
97
  # @return [Array<String>] スクリプト候補(@付き)
98
+ # :cd のディレクトリ補完。相対パスは閲覧中ディレクトリを基準にする
99
+ def complete_cd(argument)
100
+ if argument.start_with?('~', '/') || File.absolute_path?(argument)
101
+ candidates = @shell_completion.complete_path(argument, directories_only: true)
102
+ else
103
+ base = @command_mode.browsing_directory || Dir.pwd
104
+ prefix = File.join(base, '')
105
+ candidates = @shell_completion.complete_path(File.join(base, argument), directories_only: true)
106
+ .map { |path| path.delete_prefix(prefix) }
107
+ end
108
+ candidates.map { |path| "cd #{path}/" }
109
+ end
110
+
90
111
  def script_candidates(prefix)
91
112
  @command_mode.complete_script("@#{prefix}")
92
113
  end
@@ -45,6 +45,7 @@ module Rufio
45
45
  # 閲覧中ディレクトリを更新
46
46
  # @param directory [String] 現在の閲覧ディレクトリ
47
47
  def update_browsing_directory(directory)
48
+ @browsing_directory = directory
48
49
  @local_script_scanner.update_directory(directory)
49
50
  @rakefile_parser.update_directory(directory)
50
51
  end
@@ -56,6 +57,11 @@ module Rufio
56
57
  # 空のコマンドは無視
57
58
  return nil if command_string.nil? || command_string.strip.empty?
58
59
 
60
+ # ディレクトリ移動 (cd <path>)。実際の移動は TerminalUI が行う
61
+ if (match = command_string.strip.match(/\Acd(?:\s+(.*))?\z/m))
62
+ return resolve_cd(match[1].to_s, working_dir)
63
+ end
64
+
59
65
  # スクリプト実行 (@ で始まる場合)
60
66
  if command_string.strip.start_with?('@')
61
67
  return execute_script(command_string.strip[1..-1], working_dir)
@@ -103,6 +109,9 @@ module Rufio
103
109
  "⚠️ コマンドが見つかりません: #{command_name}"
104
110
  end
105
111
 
112
+ # 閲覧中ディレクトリ(:cd の補完基準)
113
+ attr_reader :browsing_directory
114
+
106
115
  # 利用可能なコマンドのリストを取得
107
116
  def available_commands
108
117
  @commands.keys
@@ -185,6 +194,17 @@ module Rufio
185
194
  # @param command_name [Symbol] コマンド名
186
195
  # @param command [Hash] コマンド情報 { command: DslCommand, source: String }
187
196
  # @return [Hash] 実行結果
197
+ # :cd の引数を移動先に解決する
198
+ # @return [Hash] { cd: { directory:, file: } } / { cd: :previous }、失敗時はエラーメッセージ
199
+ def resolve_cd(argument, working_dir)
200
+ return { cd: :previous } if argument.strip == '-'
201
+
202
+ result = PathJump.resolve(argument, working_dir || @browsing_directory || Dir.pwd)
203
+ return "⚠️ #{result[:error]}" unless result[:directory]
204
+
205
+ { cd: { directory: result[:directory], file: result[:file] } }
206
+ end
207
+
188
208
  def execute_unified_command(command_name, command)
189
209
  dsl_cmd = command[:command]
190
210
 
@@ -51,6 +51,7 @@ module Rufio
51
51
  refresh: 'R',
52
52
  # ファイル操作
53
53
  open_file: 'o',
54
+ preview: 'P',
54
55
  rename: 'r',
55
56
  delete: 'd',
56
57
  create_file: 'a',
@@ -19,6 +19,9 @@ module Rufio
19
19
  # @option options [String] :border_color Border color ANSI code
20
20
  # @option options [String] :title_color Title color ANSI code
21
21
  # @option options [String] :content_color Content color ANSI code
22
+ # @option options [Integer] :scroll_offset First visible content line
23
+ # @option options [Integer] :total_lines Total scrollable content lines
24
+ # @option options [Integer] :visible_lines Number of visible scrollable lines
22
25
  def draw_floating_window_to_buffer(screen, x, y, width, height, title, content_lines, options = {})
23
26
  draw_window_cells(x, y, width, height, title, content_lines, options,
24
27
  put_cell: ->(cx, cy, ch, fg: nil) { screen.put(cx, cy, ch, fg: fg) },
@@ -66,6 +69,9 @@ module Rufio
66
69
 
67
70
  bottom_y = y + height - 1
68
71
  print "\e[#{bottom_y};#{x}H#{border_color}└#{'─' * inner_width}┘#{reset_color}"
72
+ draw_scroll_indicators(x, y, width, height, options) do |cx, cy, label|
73
+ print "\e[#{cy};#{cx}H#{border_color}#{label}#{reset_color}"
74
+ end
69
75
  end
70
76
 
71
77
  # Calculate center position for a window
@@ -217,6 +223,26 @@ module Rufio
217
223
 
218
224
  bottom_y = y + height - 1
219
225
  put_str.call(x, bottom_y, "└#{'─' * inner_width}┘", fg: border_color)
226
+ draw_scroll_indicators(x, y, width, height, options) do |cx, cy, label|
227
+ put_str.call(cx, cy, label, fg: border_color)
228
+ end
229
+ end
230
+
231
+ # 枠内に表示し、本文や操作ガイドの行を消費しない。
232
+ def draw_scroll_indicators(x, y, width, height, options)
233
+ total = options[:total_lines]
234
+ visible = options[:visible_lines]
235
+ return unless total && visible && total > visible
236
+ return if width < 3 || height < 2
237
+
238
+ offset = [[options.fetch(:scroll_offset, 0), 0].max, total - visible].min
239
+ [[offset > 0, y, '▲ 上に続く'],
240
+ [offset + visible < total, y + height - 1, '▼ 下に続く']].each do |shown, row, text|
241
+ next unless shown
242
+
243
+ label = TextUtils.truncate_to_width(text, width - 2)
244
+ yield x + 1 + (width - 2 - TextUtils.display_width(label)) / 2, row, label
245
+ end
220
246
  end
221
247
 
222
248
  # ASCII character range constants
@@ -246,16 +246,17 @@ module Rufio
246
246
  dest_path = File.join(destination, item_name)
247
247
 
248
248
  begin
249
- if File.exist?(dest_path)
250
- error_messages << "#{item_name}: Already exists in destination"
251
- next
252
- end
253
-
254
249
  case operation
255
250
  when :move
251
+ if File.exist?(dest_path)
252
+ error_messages << "#{item_name}: Already exists in destination"
253
+ next
254
+ end
256
255
  FileUtils.mv(source_path, dest_path)
257
256
  when :copy
258
- if File.directory?(source_path)
257
+ is_directory = File.directory?(source_path)
258
+ dest_path = resolve_copy_destination(destination, item_name, is_directory)
259
+ if is_directory
259
260
  FileUtils.cp_r(source_path, dest_path)
260
261
  else
261
262
  FileUtils.cp(source_path, dest_path)
@@ -281,6 +282,27 @@ module Rufio
281
282
  )
282
283
  end
283
284
 
285
+ def resolve_copy_destination(destination, item_name, is_directory)
286
+ dest_path = File.join(destination, item_name)
287
+ return dest_path unless File.exist?(dest_path)
288
+
289
+ if is_directory
290
+ base = item_name
291
+ ext = ''
292
+ else
293
+ ext = File.extname(item_name)
294
+ base = File.basename(item_name, ext)
295
+ end
296
+
297
+ index = 1
298
+ loop do
299
+ candidate = File.join(destination, "#{base}_#{index}#{ext}")
300
+ return candidate unless File.exist?(candidate)
301
+
302
+ index += 1
303
+ end
304
+ end
305
+
284
306
  # Build delete operation message
285
307
  # @param success_count [Integer] Number of successfully deleted items
286
308
  # @param total_count [Integer] Total number of items
@@ -106,11 +106,11 @@ module Rufio
106
106
  when "\t"
107
107
  @search_type = @search_type == :filename ? :content : :filename
108
108
  start_scan
109
- when "\r", "\n" then return @results[@selected_index]
109
+ when "\r" then return @results[@selected_index]
110
110
  when :down then @selected_index = [@selected_index + 1, [@results.length - 1, 0].max].min
111
111
  when :up then @selected_index = [@selected_index - 1, 0].max
112
- when "\x0e" then handle_key(:down) # Ctrl+N
113
- when "\x10" then handle_key(:up) # Ctrl+P
112
+ when "\x0a", "\x0e" then handle_key(:down) # Ctrl+J / Ctrl+N
113
+ when "\x0b", "\x10" then handle_key(:up) # Ctrl+K / Ctrl+P
114
114
  when "\x15" then update_query('') # Ctrl+U
115
115
  when "\u007f", "\b" then update_query(@query[0...-1])
116
116
  else
@@ -169,7 +169,7 @@ module Rufio
169
169
  write_line(screen, 3, message, width)
170
170
  end
171
171
  write_line(screen, screen.height - 3, @results[@selected_index]&.[](:path) || @errors.first.to_s, width, "\e[90m")
172
- write_line(screen, screen.height - 2, 'ESC: Back | Enter: Go to file | Up/Down: Select | Type: Search', width)
172
+ write_line(screen, screen.height - 2, 'ESC: Back | Enter: Go to file | Up/Down or Ctrl+J/K: Select | Type: Search', width)
173
173
  end
174
174
 
175
175
  private
@@ -54,20 +54,23 @@ module Rufio
54
54
  'Navigation',
55
55
  "#{key(:move_down)}/#{key(:move_up)} or arrows: Move down/up",
56
56
  "#{key(:navigate_parent)}/#{key(:navigate_enter)}: Parent / enter directory",
57
- 'Enter: Open file preview | ESC: Back to file list',
57
+ "#{key(:preview)}: Fullscreen file preview | ESC: Back to file list",
58
58
  'Preview: j/k scroll, Ctrl+D/U page down/up',
59
59
  "#{key(:top)}/#{key(:bottom)}: First / last entry",
60
60
  "#{key(:open_file)}: Open in external editor",
61
+ "#{key(:open_explorer)}: Open directory in file explorer",
62
+ 'Preview .zip: Entry list (size, method, date, name)',
61
63
  "#{key(:refresh)}: Refresh directory",
62
64
  '', 'Selection and file operations',
63
65
  "#{key(:select)}: Select / unselect file",
64
66
  "#{key(:copy_selected)}/#{key(:move_selected)}/#{key(:delete_selected)}: Copy / move / delete selected files",
65
67
  "#{key(:zoxide)}: Extract selected .zip files / compress selected items",
68
+ " Runs as a job: #{key(:job_mode)} Jobs mode shows progress; x cancels",
66
69
  "#{key(:rename)}/#{key(:delete)}: Rename / delete current file",
67
70
  "#{key(:create_file)}/#{key(:create_dir)}: Create file / directory",
68
71
  '', 'Search and bookmarks',
69
72
  "#{key(:filter)}: Filter | Enter: Apply | ESC: Clear",
70
- "#{key(:fzf_search)}: Filename search (fzf)",
73
+ "#{key(:fzf_search)} or #{key(:fzf_search_alt)}: Filename search (fzf)",
71
74
  "#{key(:rga_search)}: Content search (rga)",
72
75
  "#{key(:global_search)}: Search file names or contents across all bookmarks",
73
76
  "#{key(:add_bookmark)}/#{key(:bookmark_menu)}: Add / manage bookmarks",
@@ -99,14 +102,15 @@ module Rufio
99
102
  'GLOBAL_SEARCH_EXCLUDED_EXTENSIONS = %w[.zip .log]',
100
103
  'Applies to names and contents. Restart Rufio after editing.',
101
104
  'Extensions ignore case; leading dots are optional; [] excludes none.',
102
- 'Type to filter | Up/Down or Ctrl+N/P: Select',
105
+ 'Type to filter | Up/Down or Ctrl+J/K or Ctrl+N/P: Select',
103
106
  'Enter: Go to the selected file in Files mode',
104
107
  'ESC: Return to the previous mode | Ctrl+U: Clear query',
105
108
  '', "[#{key(:log_viewer)}] Logs",
106
- 'Browse command logs. Enter opens a preview.',
109
+ "Browse command logs. #{key(:preview)} opens a fullscreen preview.",
107
110
  'ESC: Leave preview; ESC again: Return to Files.',
108
111
  '', "[#{key(:job_mode)}] Jobs",
109
112
  'View background commands and their status.',
113
+ 'ZIP extract/compress jobs show progress here.',
110
114
  'Space: View log | x: Cancel job | ESC: Back',
111
115
  '', "[#{key(:help)}] Help",
112
116
  'This floating menu keeps your current mode and directory.',
@@ -115,6 +119,7 @@ module Rufio
115
119
  'Type a command, then Enter to run it.',
116
120
  'Tab: Complete | Up/Down: History | ESC: Cancel',
117
121
  ':!git status: Run a shell command',
122
+ ':cd <path>: Go to a pasted path (file: select it, cd -: back)',
118
123
  ':@hello.rb: Run a script in the current directory'
119
124
  ]
120
125
  when 'Scripts'
@@ -161,7 +166,8 @@ module Rufio
161
166
  DialogRenderer.new.draw_floating_window_to_overlay(
162
167
  screen, (screen.width - width) / 2, (screen.height - height) / 2,
163
168
  width, height, TextUtils.truncate_to_width(title, width - 2), visible,
164
- border_color: "\e[36m", title_color: "\e[1;36m"
169
+ border_color: "\e[36m", title_color: "\e[1;36m",
170
+ scroll_offset: @scroll_offset, total_lines: lines.length, visible_lines: @page_size
165
171
  )
166
172
  end
167
173
 
@@ -228,7 +228,7 @@ module Rufio
228
228
 
229
229
  entries = get_active_entries
230
230
 
231
- # Enter キー(特殊処理: プレビューペインフォーカスまたはナビゲーション)
231
+ # Enter キー(ディレクトリ移動)
232
232
  if key == "\r" || key == "\n"
233
233
  return @nav_controller.handle_enter_key(current_entry, @in_help_mode, @in_log_viewer_mode)
234
234
  end
@@ -476,6 +476,7 @@ module Rufio
476
476
  when :top then @nav_controller.move_to_top
477
477
  when :bottom then @nav_controller.move_to_bottom(entries)
478
478
  when :refresh then @nav_controller.refresh
479
+ when :preview then focus_preview_pane
479
480
  when :open_file then @nav_controller.open_current_file(current_entry)
480
481
  when :open_explorer then @nav_controller.open_directory_in_explorer
481
482
  when :rename then @file_op_controller.rename_current_file(current_entry)
@@ -236,18 +236,14 @@ module Rufio
236
236
  end
237
237
  end
238
238
 
239
- # Enterキーの処理:ファイルならプレビューフォーカス、ディレクトリならナビゲート
239
+ # Enterキーの処理:ディレクトリへ移動する
240
240
  # @param entry [Hash] 現在選択中のエントリ
241
241
  # @param in_help_mode [Boolean]
242
242
  # @param in_log_viewer_mode [Boolean]
243
243
  def handle_enter_key(entry, in_help_mode, in_log_viewer_mode)
244
244
  return false unless entry
245
245
 
246
- if entry[:type] == 'file'
247
- focus_preview_pane(entry)
248
- else
249
- navigate_enter(entry, in_help_mode, in_log_viewer_mode)
250
- end
246
+ navigate_enter(entry, in_help_mode, in_log_viewer_mode)
251
247
  end
252
248
 
253
249
  private
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+
5
+ module Rufio
6
+ # :cd に貼り付けられたパスを移動先に解決する。
7
+ # 引用符・末尾の改行・バックスラッシュエスケープされた空白・file:// URL を受け付け、
8
+ # ファイルを指す場合はその親ディレクトリとファイル自体を返す。
9
+ module PathJump
10
+ QUOTE_PAIRS = [%w[' '], %w[" "]].freeze
11
+
12
+ class << self
13
+ # @param input [String] 貼り付けられたパス(空なら ホーム)
14
+ # @param base_dir [String] 相対パスの基準ディレクトリ
15
+ # @return [Hash] { directory:, file: } または { directory: nil, error: }
16
+ def resolve(input, base_dir)
17
+ path = normalize(input.to_s)
18
+ path = '~' if path.empty?
19
+ expanded = File.expand_path(path, base_dir)
20
+
21
+ if File.directory?(expanded)
22
+ { directory: expanded, file: nil }
23
+ elsif File.file?(expanded)
24
+ { directory: File.dirname(expanded), file: expanded }
25
+ else
26
+ { directory: nil, error: "Path not found: #{path}" }
27
+ end
28
+ rescue ArgumentError => e
29
+ { directory: nil, error: "Invalid path: #{e.message}" }
30
+ end
31
+
32
+ private
33
+
34
+ def normalize(text)
35
+ path = text.strip
36
+ QUOTE_PAIRS.each do |open, close|
37
+ if path.length >= 2 && path.start_with?(open) && path.end_with?(close)
38
+ path = path[1..-2]
39
+ break
40
+ end
41
+ end
42
+ path = from_file_url(path) if path.start_with?('file://')
43
+ path = path.gsub(/\\(.)/, '\1') unless Gem.win_platform?
44
+ path
45
+ end
46
+
47
+ def from_file_url(url)
48
+ uri = URI.parse(url)
49
+ URI.decode_www_form_component(uri.path.to_s.gsub('+', '%2B'))
50
+ rescue URI::InvalidURIError
51
+ url.delete_prefix('file://')
52
+ end
53
+ end
54
+ end
55
+ end
@@ -617,6 +617,8 @@ module Rufio
617
617
 
618
618
  # マウス左クリックを処理する
619
619
  def handle_mouse_left_click(col, row)
620
+ return if @keybind_handler.preview_focused?
621
+
620
622
  left_width = left_panel_col_width
621
623
 
622
624
  if col <= left_width
@@ -662,10 +664,8 @@ module Rufio
662
664
  end
663
665
 
664
666
  # マウスホイールスクロールを処理する
665
- def handle_mouse_scroll(direction, col, _row)
666
- left_width = left_panel_col_width
667
-
668
- if col > left_width && @keybind_handler.preview_focused?
667
+ def handle_mouse_scroll(direction, _col, _row)
668
+ if @keybind_handler.preview_focused?
669
669
  # プレビューペインのスクロール
670
670
  case direction
671
671
  when :up then @keybind_handler.scroll_preview_up
@@ -790,6 +790,9 @@ module Rufio
790
790
 
791
791
  result = @command_mode.execute(command_string, working_dir: working_dir)
792
792
 
793
+ # :cd はディレクトリを移動してコマンドモードを閉じる
794
+ return change_directory(result[:cd]) if result.is_a?(Hash) && result.key?(:cd)
795
+
793
796
  # バックグラウンドコマンドの場合は結果表示をスキップ
794
797
  # (完了通知は別途メインループで表示される)
795
798
  if result && !result.to_s.include?("🔄 Running in background")
@@ -802,6 +805,28 @@ module Rufio
802
805
  # 使用するとコマンドプロンプトの枠線が残る不具合が発生する
803
806
  end
804
807
 
808
+ # :cd の移動先へ移動する。ファイルが指定されていればそのファイルを選択する
809
+ # @param target [Hash, Symbol] { directory:, file: } または :previous(cd -)
810
+ def change_directory(target)
811
+ if target == :previous
812
+ return @command_mode_ui.show_result('⚠️ No previous directory') unless @previous_directory
813
+
814
+ target = { directory: @previous_directory, file: nil }
815
+ end
816
+
817
+ origin = @directory_listing&.current_path
818
+ unless @keybind_handler.navigate_to_directory(target[:directory])
819
+ return @command_mode_ui.show_result("⚠️ Cannot open directory: #{target[:directory]}")
820
+ end
821
+
822
+ @previous_directory = origin
823
+ index = target[:file] && @keybind_handler.get_active_entries.index { |entry| entry[:path] == target[:file] }
824
+ @keybind_handler.select_index(index || 0)
825
+ @keybind_handler.unfocus_preview_pane
826
+ @tab_mode_manager&.switch_to(@keybind_handler.config_mode? ? :config : :files)
827
+ deactivate_command_mode
828
+ end
829
+
805
830
  # Tab補完を処理
806
831
  def handle_tab_completion
807
832
  # 補完候補を取得