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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +26 -368
- data/README_ja.md +24 -330
- data/docs/guide.md +410 -0
- data/docs/guide_ja.md +410 -0
- data/info/help.md +52 -17
- data/info/keybindings.md +48 -22
- data/info/welcome.md +13 -4
- data/lib/rufio/command_completion.rb +22 -1
- data/lib/rufio/command_mode.rb +20 -0
- data/lib/rufio/config_loader.rb +1 -0
- data/lib/rufio/dialog_renderer.rb +26 -0
- data/lib/rufio/file_operations.rb +28 -6
- data/lib/rufio/global_search.rb +4 -4
- data/lib/rufio/help_menu.rb +11 -5
- data/lib/rufio/keybind_handler.rb +2 -1
- data/lib/rufio/navigation_controller.rb +2 -6
- data/lib/rufio/path_jump.rb +55 -0
- data/lib/rufio/terminal_ui.rb +29 -4
- data/lib/rufio/ui_renderer.rb +10 -5
- data/lib/rufio/version.rb +1 -1
- data/lib/rufio.rb +1 -0
- metadata +5 -2
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
|
|
12
|
+
| `l` / `Right` / `Enter` | Enter directory |
|
|
13
13
|
| `g` | Jump to top of list |
|
|
14
|
-
| `
|
|
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
|
|
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
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
###
|
|
100
|
-
- **
|
|
101
|
-
- **
|
|
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
|
-
###
|
|
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
|
|
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
|
-
|
|
|
31
|
-
|
|
|
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
|
|
46
|
-
|
|
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
|
data/lib/rufio/command_mode.rb
CHANGED
|
@@ -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
|
|
data/lib/rufio/config_loader.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
data/lib/rufio/global_search.rb
CHANGED
|
@@ -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"
|
|
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
|
data/lib/rufio/help_menu.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/lib/rufio/terminal_ui.rb
CHANGED
|
@@ -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,
|
|
666
|
-
|
|
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
|
# 補完候補を取得
|