rufio 1.3.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +28 -368
- data/README_ja.md +26 -330
- data/docs/guide.md +412 -0
- data/docs/guide_ja.md +412 -0
- data/info/help.md +55 -17
- data/info/keybindings.md +50 -22
- data/info/welcome.md +15 -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/file_preview.rb +19 -56
- data/lib/rufio/global_search.rb +4 -4
- data/lib/rufio/help_menu.rb +12 -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,26 @@ 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
|
+
|
|
30
|
+
Text previews read at most the first 64 KiB. PDF bodies are not previewed, keeping file navigation responsive.
|
|
31
|
+
|
|
28
32
|
## File Selection
|
|
29
33
|
|
|
30
34
|
| Key | Action |
|
|
@@ -33,7 +37,9 @@ Complete reference of all keyboard shortcuts in **rufio**.
|
|
|
33
37
|
| `m` | Move selected items to current directory |
|
|
34
38
|
| `c` | Copy selected items to current directory |
|
|
35
39
|
| `x` | Delete selected items |
|
|
36
|
-
| `z` | Extract selected `.zip` files / compress selected items into
|
|
40
|
+
| `z` | Extract selected `.zip` files (each into `<name>/`) / compress selected items into one `.zip` |
|
|
41
|
+
|
|
42
|
+
ZIP extraction and compression run as background jobs. Check progress in Jobs mode (`J`).
|
|
37
43
|
|
|
38
44
|
## Filtering & Search
|
|
39
45
|
|
|
@@ -41,7 +47,9 @@ Complete reference of all keyboard shortcuts in **rufio**.
|
|
|
41
47
|
|-----|--------|
|
|
42
48
|
| `f` | Enter filter mode (type to filter) |
|
|
43
49
|
| `s` | Search file names with fzf |
|
|
50
|
+
| `/` | Search file names with fzf (same as `s`) |
|
|
44
51
|
| `F` | Search file contents with rga |
|
|
52
|
+
| `G` | Global search: file names or contents across all bookmarks |
|
|
45
53
|
|
|
46
54
|
## Bookmarks & Navigation
|
|
47
55
|
|
|
@@ -51,31 +59,33 @@ Complete reference of all keyboard shortcuts in **rufio**.
|
|
|
51
59
|
| `B` | Bookmark menu (view/add/remove/script paths) |
|
|
52
60
|
| `0` | Jump to startup directory |
|
|
53
61
|
| `1-9` | Jump to bookmark 1-9 |
|
|
62
|
+
| `Tab` | Cycle to next bookmark (Files mode) |
|
|
63
|
+
| `Shift+Tab` | Cycle to previous bookmark (Files mode) |
|
|
54
64
|
| `z` | Navigate using zoxide history (when nothing is selected) |
|
|
55
65
|
|
|
56
|
-
##
|
|
66
|
+
## Modes & Tool Runtime
|
|
57
67
|
|
|
58
68
|
| Key | Action |
|
|
59
69
|
|-----|--------|
|
|
60
70
|
| `:` | Enter command mode |
|
|
71
|
+
| `G` | Global search mode |
|
|
72
|
+
| `C` | Config mode: open `~/.config/rufio/` |
|
|
61
73
|
| `J` | Job mode (view background jobs) |
|
|
62
74
|
| `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 |
|
|
75
|
+
| `?` | Open the floating help menu |
|
|
76
|
+
| `q` | Quit rufio (with confirmation) |
|
|
73
77
|
| `T` | Quit rufio and cd the shell to the current directory (requires `rufio --shell-init`) |
|
|
74
78
|
|
|
75
79
|
---
|
|
76
80
|
|
|
77
81
|
## Mode-Specific Keys
|
|
78
82
|
|
|
83
|
+
### Preview
|
|
84
|
+
- **P**: Open fullscreen file preview (hides the file list)
|
|
85
|
+
- **j/k**: Scroll preview
|
|
86
|
+
- **Ctrl+D/U**: Page down/up
|
|
87
|
+
- **ESC**: Back to the file list
|
|
88
|
+
|
|
79
89
|
### Filter Mode
|
|
80
90
|
- **Any character**: Add to filter query
|
|
81
91
|
- **Backspace**: Remove last character
|
|
@@ -84,7 +94,7 @@ Complete reference of all keyboard shortcuts in **rufio**.
|
|
|
84
94
|
|
|
85
95
|
### Command Mode
|
|
86
96
|
- **Any character**: Add to command input
|
|
87
|
-
- **Tab**: Auto-complete (scripts, rake tasks, commands)
|
|
97
|
+
- **Tab**: Auto-complete (scripts, rake tasks, commands, paths)
|
|
88
98
|
- **Up/Down**: Navigate command history
|
|
89
99
|
- **Enter**: Execute command
|
|
90
100
|
- **Backspace**: Remove last character
|
|
@@ -94,27 +104,45 @@ Complete reference of all keyboard shortcuts in **rufio**.
|
|
|
94
104
|
- `@script` - Execute a registered or local script
|
|
95
105
|
- `rake:task` - Execute a Rakefile task
|
|
96
106
|
- `!command` - Execute a shell command
|
|
107
|
+
- `cd <path>` - Go to a directory (paste a path; a file path selects the file, `cd -` goes back, `Tab` completes directories)
|
|
97
108
|
- `command` - Execute a built-in or plugin command
|
|
98
109
|
|
|
99
|
-
###
|
|
100
|
-
- **
|
|
101
|
-
- **
|
|
110
|
+
### Global Search (G)
|
|
111
|
+
- **Type**: Filter results (literal, case insensitive)
|
|
112
|
+
- **Tab**: Switch between file names and content
|
|
113
|
+
- **Up/Down** or **Ctrl+J/K or Ctrl+N/P**: Select a result
|
|
114
|
+
- **Enter**: Go to the selected file in Files mode
|
|
115
|
+
- **Ctrl+U**: Clear the query
|
|
116
|
+
- **ESC**: Return to the previous mode
|
|
117
|
+
- Content search also reads `.pdf`, `.xlsx` and `.pptx`
|
|
102
118
|
|
|
103
|
-
###
|
|
119
|
+
### Config Mode (C)
|
|
120
|
+
- Same navigation, preview and file operations as Files mode
|
|
121
|
+
- Leaving `~/.config/rufio/` returns to Files mode
|
|
122
|
+
|
|
123
|
+
### Job Mode (J)
|
|
104
124
|
- **j/k**: Navigate through jobs
|
|
125
|
+
- **g/G**: First / last job
|
|
126
|
+
- **Space**: View job log
|
|
127
|
+
- **`x`: Cancel** the selected job (including ZIP jobs)
|
|
105
128
|
- **ESC**: Exit job mode
|
|
106
129
|
|
|
130
|
+
### Help Menu (?)
|
|
131
|
+
- **1/2/3** or **Tab/Shift+Tab**: Switch section (Controls / Modes / Scripts)
|
|
132
|
+
- **j/k**: Scroll | **Ctrl+D/U**: Page down/up | **g/G**: Top / bottom
|
|
133
|
+
- **ESC**, **q** or **?**: Close and keep your current mode and directory
|
|
134
|
+
|
|
107
135
|
---
|
|
108
136
|
|
|
109
137
|
## Tips
|
|
110
138
|
|
|
111
139
|
- **Vim users**: Navigation keys (`hjkl`) work exactly like Vim
|
|
112
140
|
- **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
|
|
141
|
+
- **Quick access**: Number keys `1-9` and `Tab` provide instant access to bookmarks
|
|
114
142
|
- **Filtering**: Press `f` and start typing for real-time filtering
|
|
115
143
|
- **Rake tasks**: Type `:rake:` then `Tab` to see available tasks
|
|
116
144
|
- **Local scripts**: Scripts in the current directory are auto-detected with `@` prefix
|
|
117
145
|
|
|
118
146
|
---
|
|
119
147
|
|
|
120
|
-
*Press `?` anytime to
|
|
148
|
+
*Press `?` anytime to open the help menu*
|
data/info/welcome.md
CHANGED
|
@@ -10,10 +10,14 @@ 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
|
|
16
18
|
|
|
19
|
+
Text previews read at most the first 64 KiB. PDF bodies are not previewed, keeping file navigation responsive.
|
|
20
|
+
|
|
17
21
|
## Quick Start
|
|
18
22
|
|
|
19
23
|
| Key | Action |
|
|
@@ -24,12 +28,18 @@ A TUI file manager that executes and coordinates tools and scripts from files.
|
|
|
24
28
|
| `s` | Search with fzf |
|
|
25
29
|
| `b` | Add bookmark |
|
|
26
30
|
| `B` | Bookmark menu |
|
|
31
|
+
| `Tab` | Next bookmark |
|
|
27
32
|
| `z` | zoxide history / extract or compress selected items |
|
|
33
|
+
| `G` | Global search across bookmarks |
|
|
34
|
+
| `C` | Config mode |
|
|
28
35
|
| `:` | Command mode |
|
|
29
36
|
| `J` | Job mode |
|
|
30
|
-
|
|
|
31
|
-
|
|
|
37
|
+
| `L` | Execution logs |
|
|
38
|
+
| `?` | Help menu |
|
|
32
39
|
| `q` | Quit |
|
|
40
|
+
| `T` | Quit and cd to the current directory |
|
|
41
|
+
|
|
42
|
+
In G mode, use `Ctrl+J` / `Ctrl+K` to select the next / previous result and `Enter` to open it.
|
|
33
43
|
|
|
34
44
|
## Command Mode
|
|
35
45
|
|
|
@@ -38,12 +48,13 @@ Press `:` to enter command mode:
|
|
|
38
48
|
- `@script.sh` - Execute a script
|
|
39
49
|
- `rake:test` - Execute a rake task
|
|
40
50
|
- `!git status` - Run a shell command
|
|
51
|
+
- `cd ~/projects` - Go to a pasted path
|
|
41
52
|
- `Tab` - Auto-complete commands
|
|
42
53
|
|
|
43
54
|
## Getting Help
|
|
44
55
|
|
|
45
|
-
Press `?` to
|
|
46
|
-
|
|
56
|
+
Press `?` to open the floating **Help Menu** (Controls / Modes / Scripts).
|
|
57
|
+
Switch sections with `1/2/3` or `Tab`, scroll with `j/k`, and close with `ESC`.
|
|
47
58
|
|
|
48
59
|
## More Information
|
|
49
60
|
|
|
@@ -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/file_preview.rb
CHANGED
|
@@ -5,6 +5,7 @@ module Rufio
|
|
|
5
5
|
BINARY_THRESHOLD = 0.3 # treat as binary if 30% or more binary characters
|
|
6
6
|
DEFAULT_MAX_LINES = 50
|
|
7
7
|
MAX_LINE_LENGTH = 500
|
|
8
|
+
MAX_PREVIEW_BYTES = 64 * 1024
|
|
8
9
|
|
|
9
10
|
# Binary detection constants
|
|
10
11
|
BINARY_SAMPLE_SIZE = 512
|
|
@@ -28,6 +29,9 @@ module Rufio
|
|
|
28
29
|
return empty_response if file_size == 0
|
|
29
30
|
|
|
30
31
|
begin
|
|
32
|
+
# PDF bodies are not displayed; never scan their binary streams as text.
|
|
33
|
+
return binary_response(file_path) if File.extname(file_path).casecmp?('.pdf')
|
|
34
|
+
|
|
31
35
|
return archive_response(file_path, max_lines) if zip_archive?(file_path)
|
|
32
36
|
|
|
33
37
|
# binary file detection
|
|
@@ -102,71 +106,30 @@ module Rufio
|
|
|
102
106
|
end
|
|
103
107
|
|
|
104
108
|
def read_text_file(file_path, max_lines)
|
|
109
|
+
# Bound the read before decoding or splitting: a binary stream or minified
|
|
110
|
+
# file can contain a single line spanning the entire file.
|
|
111
|
+
content = File.binread(file_path, MAX_PREVIEW_BYTES + 1)
|
|
112
|
+
truncated = content.bytesize > MAX_PREVIEW_BYTES
|
|
113
|
+
content = content.byteslice(0, MAX_PREVIEW_BYTES) if truncated
|
|
114
|
+
content = content.force_encoding('UTF-8').encode(
|
|
115
|
+
'UTF-8', invalid: :replace, undef: :replace, replace: '?'
|
|
116
|
+
)
|
|
105
117
|
lines = []
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
File.open(file_path, "r:UTF-8:UTF-8", invalid: :replace, undef: :replace, replace: '�') do |file|
|
|
111
|
-
file.each_line.with_index do |line, index|
|
|
112
|
-
break if index >= max_lines
|
|
113
|
-
|
|
114
|
-
begin
|
|
115
|
-
# Ensure line is properly encoded as UTF-8
|
|
116
|
-
line = line.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
|
|
117
|
-
|
|
118
|
-
# truncate too long lines
|
|
119
|
-
if line.length > MAX_LINE_LENGTH
|
|
120
|
-
line = line[0...MAX_LINE_LENGTH] + "..."
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
lines << line.chomp
|
|
124
|
-
rescue EncodingError, ArgumentError
|
|
125
|
-
# If encoding fails, add placeholder
|
|
126
|
-
lines << "[encoding error in line #{index + 1}]"
|
|
127
|
-
end
|
|
118
|
+
content.each_line.with_index do |line, index|
|
|
119
|
+
if index >= max_lines
|
|
120
|
+
truncated = true
|
|
121
|
+
break
|
|
128
122
|
end
|
|
129
123
|
|
|
130
|
-
|
|
131
|
-
|
|
124
|
+
line = line[0...MAX_LINE_LENGTH] + '...' if line.length > MAX_LINE_LENGTH
|
|
125
|
+
lines << line.chomp
|
|
132
126
|
end
|
|
133
127
|
|
|
134
128
|
{
|
|
135
129
|
content: lines,
|
|
136
130
|
truncated: truncated,
|
|
137
|
-
encoding:
|
|
131
|
+
encoding: 'UTF-8'
|
|
138
132
|
}
|
|
139
|
-
rescue Encoding::InvalidByteSequenceError, Encoding::CompatibilityError
|
|
140
|
-
# try Shift_JIS if UTF-8 fails
|
|
141
|
-
begin
|
|
142
|
-
lines = []
|
|
143
|
-
File.open(file_path, "r:Shift_JIS:UTF-8", invalid: :replace, undef: :replace, replace: '�') do |file|
|
|
144
|
-
file.each_line.with_index do |line, index|
|
|
145
|
-
break if index >= max_lines
|
|
146
|
-
|
|
147
|
-
begin
|
|
148
|
-
# Ensure line is properly encoded as UTF-8
|
|
149
|
-
line = line.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
|
|
150
|
-
lines << line.chomp
|
|
151
|
-
rescue EncodingError, ArgumentError
|
|
152
|
-
# If encoding fails, add placeholder
|
|
153
|
-
lines << "[encoding error in line #{index + 1}]"
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
truncated = !file.eof?
|
|
157
|
-
end
|
|
158
|
-
{
|
|
159
|
-
content: lines,
|
|
160
|
-
truncated: truncated,
|
|
161
|
-
encoding: "Shift_JIS"
|
|
162
|
-
}
|
|
163
|
-
rescue
|
|
164
|
-
{
|
|
165
|
-
content: ["(#{ConfigLoader.message('file.encoding_error')})"],
|
|
166
|
-
truncated: false,
|
|
167
|
-
encoding: "unknown"
|
|
168
|
-
}
|
|
169
|
-
end
|
|
170
133
|
end
|
|
171
134
|
|
|
172
135
|
def determine_file_type(file_path)
|
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,24 @@ 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
|
+
'Text preview: first 64 KiB; PDF bodies are not previewed.',
|
|
58
59
|
'Preview: j/k scroll, Ctrl+D/U page down/up',
|
|
59
60
|
"#{key(:top)}/#{key(:bottom)}: First / last entry",
|
|
60
61
|
"#{key(:open_file)}: Open in external editor",
|
|
62
|
+
"#{key(:open_explorer)}: Open directory in file explorer",
|
|
63
|
+
'Preview .zip: Entry list (size, method, date, name)',
|
|
61
64
|
"#{key(:refresh)}: Refresh directory",
|
|
62
65
|
'', 'Selection and file operations',
|
|
63
66
|
"#{key(:select)}: Select / unselect file",
|
|
64
67
|
"#{key(:copy_selected)}/#{key(:move_selected)}/#{key(:delete_selected)}: Copy / move / delete selected files",
|
|
65
68
|
"#{key(:zoxide)}: Extract selected .zip files / compress selected items",
|
|
69
|
+
" Runs as a job: #{key(:job_mode)} Jobs mode shows progress; x cancels",
|
|
66
70
|
"#{key(:rename)}/#{key(:delete)}: Rename / delete current file",
|
|
67
71
|
"#{key(:create_file)}/#{key(:create_dir)}: Create file / directory",
|
|
68
72
|
'', 'Search and bookmarks',
|
|
69
73
|
"#{key(:filter)}: Filter | Enter: Apply | ESC: Clear",
|
|
70
|
-
"#{key(:fzf_search)}: Filename search (fzf)",
|
|
74
|
+
"#{key(:fzf_search)} or #{key(:fzf_search_alt)}: Filename search (fzf)",
|
|
71
75
|
"#{key(:rga_search)}: Content search (rga)",
|
|
72
76
|
"#{key(:global_search)}: Search file names or contents across all bookmarks",
|
|
73
77
|
"#{key(:add_bookmark)}/#{key(:bookmark_menu)}: Add / manage bookmarks",
|
|
@@ -99,14 +103,15 @@ module Rufio
|
|
|
99
103
|
'GLOBAL_SEARCH_EXCLUDED_EXTENSIONS = %w[.zip .log]',
|
|
100
104
|
'Applies to names and contents. Restart Rufio after editing.',
|
|
101
105
|
'Extensions ignore case; leading dots are optional; [] excludes none.',
|
|
102
|
-
'Type to filter | Up/Down or Ctrl+N/P: Select',
|
|
106
|
+
'Type to filter | Up/Down or Ctrl+J/K or Ctrl+N/P: Select',
|
|
103
107
|
'Enter: Go to the selected file in Files mode',
|
|
104
108
|
'ESC: Return to the previous mode | Ctrl+U: Clear query',
|
|
105
109
|
'', "[#{key(:log_viewer)}] Logs",
|
|
106
|
-
|
|
110
|
+
"Browse command logs. #{key(:preview)} opens a fullscreen preview.",
|
|
107
111
|
'ESC: Leave preview; ESC again: Return to Files.',
|
|
108
112
|
'', "[#{key(:job_mode)}] Jobs",
|
|
109
113
|
'View background commands and their status.',
|
|
114
|
+
'ZIP extract/compress jobs show progress here.',
|
|
110
115
|
'Space: View log | x: Cancel job | ESC: Back',
|
|
111
116
|
'', "[#{key(:help)}] Help",
|
|
112
117
|
'This floating menu keeps your current mode and directory.',
|
|
@@ -115,6 +120,7 @@ module Rufio
|
|
|
115
120
|
'Type a command, then Enter to run it.',
|
|
116
121
|
'Tab: Complete | Up/Down: History | ESC: Cancel',
|
|
117
122
|
':!git status: Run a shell command',
|
|
123
|
+
':cd <path>: Go to a pasted path (file: select it, cd -: back)',
|
|
118
124
|
':@hello.rb: Run a script in the current directory'
|
|
119
125
|
]
|
|
120
126
|
when 'Scripts'
|
|
@@ -161,7 +167,8 @@ module Rufio
|
|
|
161
167
|
DialogRenderer.new.draw_floating_window_to_overlay(
|
|
162
168
|
screen, (screen.width - width) / 2, (screen.height - height) / 2,
|
|
163
169
|
width, height, TextUtils.truncate_to_width(title, width - 2), visible,
|
|
164
|
-
border_color: "\e[36m", title_color: "\e[1;36m"
|
|
170
|
+
border_color: "\e[36m", title_color: "\e[1;36m",
|
|
171
|
+
scroll_offset: @scroll_offset, total_lines: lines.length, visible_lines: @page_size
|
|
165
172
|
)
|
|
166
173
|
end
|
|
167
174
|
|
|
@@ -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
|