rufio 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG_v0.21.0.md +175 -0
- data/README.md +11 -15
- data/README_EN.md +8 -12
- data/lib/rufio/application.rb +0 -1
- data/lib/rufio/keybind_handler.rb +192 -30
- data/lib/rufio/selection_manager.rb +10 -1
- data/lib/rufio/terminal_ui.rb +9 -139
- data/lib/rufio/text_utils.rb +49 -0
- data/lib/rufio/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2273fb7a1731d8d60e7a617d9055bdff0f7f5cd92f85eaa36d91dd266cf01773
|
|
4
|
+
data.tar.gz: e599d21775fd19631fca769972977f3a98771afae998b2523e16cf84d2b30d12
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7c66f9c83af723f5a993bfdec6e68b801320394e0912d94431986747e36f86cceea8afc0fa63fc345f2f1a045c1eb8753fb9e1cc238762a49e4f4d5f162f8cb4
|
|
7
|
+
data.tar.gz: 36c12657770e86c3f36054a735761db1e0207bbf70b2a89588b988f33fffc42ed2c00e00ace7595a7f9cc93ed95c3ad1097bbafb732db14d488727417b859345
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Changelog v0.21.0 - Copy Feature & Code Refactoring
|
|
2
|
+
|
|
3
|
+
## [0.21.0] - 2025-12-28
|
|
4
|
+
|
|
5
|
+
### 🎨 Enhanced - File Operations & Code Quality
|
|
6
|
+
|
|
7
|
+
Complete implementation of copy functionality with floating dialog UI, cross-directory selection improvements, and major code refactoring for better maintainability.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
#### Copy Feature
|
|
12
|
+
- **`c` key**: Copy selected files to current directory
|
|
13
|
+
- Uses floating confirmation dialog (green border)
|
|
14
|
+
- Shows source and destination paths
|
|
15
|
+
- Supports cross-directory operations
|
|
16
|
+
- Silent completion (no result dialog)
|
|
17
|
+
- **`SelectionManager#source_directory`**: Track directory where files were selected
|
|
18
|
+
- Enables selecting files in one directory and copying/moving to another
|
|
19
|
+
- Automatically set on first selection
|
|
20
|
+
- Cleared when all selections are removed
|
|
21
|
+
- **Copy confirmation dialog**: Green-bordered floating window
|
|
22
|
+
- Displays source and destination paths with smart truncation
|
|
23
|
+
- Consistent with move/delete confirmation UI
|
|
24
|
+
- `[Y]es` / `[N]o` / `ESC` to cancel
|
|
25
|
+
|
|
26
|
+
#### Multiple Selection Delete
|
|
27
|
+
- **Enhanced `x` key**: Delete multiple selected files
|
|
28
|
+
- Checks for selected items first
|
|
29
|
+
- Falls back to single file deletion if no selection
|
|
30
|
+
- Uses floating confirmation dialog showing source path
|
|
31
|
+
- Red border for warning (dangerous operation)
|
|
32
|
+
|
|
33
|
+
#### UI Improvements
|
|
34
|
+
- **Cross-directory selection display**: Selection marks only show in source directory
|
|
35
|
+
- `is_selected?` method checks current directory matches source directory
|
|
36
|
+
- Prevents confusion when navigating to different directories
|
|
37
|
+
- Selection internally maintained for operations
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
#### File Operations
|
|
42
|
+
- **Move operation** (`m` key): Uses source directory from SelectionManager
|
|
43
|
+
- Select files → navigate to destination → press `m` to move
|
|
44
|
+
- Blue-bordered confirmation dialog with paths
|
|
45
|
+
- Silent completion
|
|
46
|
+
- **Delete operation** (`x` key):
|
|
47
|
+
- Multiple selection support added
|
|
48
|
+
- Shows source path in confirmation
|
|
49
|
+
- Red-bordered warning dialog
|
|
50
|
+
- **All file operations** now use consistent floating dialog pattern:
|
|
51
|
+
- Red: Delete (dangerous)
|
|
52
|
+
- Blue: Move (informational)
|
|
53
|
+
- Green: Copy (safe)
|
|
54
|
+
|
|
55
|
+
#### Code Quality
|
|
56
|
+
- **terminal_ui.rb refactored**: Reduced from 1139 to 1048 lines (-91 lines)
|
|
57
|
+
- Removed duplicate text utility methods
|
|
58
|
+
- Uses `TextUtils` module for all text operations
|
|
59
|
+
- Better separation of concerns
|
|
60
|
+
- **TextUtils module enhanced**: Added `wrap_preview_lines` method
|
|
61
|
+
- Centralized text display width calculation
|
|
62
|
+
- Multi-byte character support (Japanese, etc.)
|
|
63
|
+
- Consistent truncation and wrapping logic
|
|
64
|
+
- **Method call updates**: All `display_width`, `truncate_to_width` calls use `TextUtils.` prefix
|
|
65
|
+
- Proper module method invocation
|
|
66
|
+
- Fixed runtime errors from refactoring
|
|
67
|
+
|
|
68
|
+
#### Keybind Handler
|
|
69
|
+
- **Removed duplicate method definition**: `show_delete_confirmation`
|
|
70
|
+
- Fixed warning about method redefinition
|
|
71
|
+
- Single source of truth for delete confirmation
|
|
72
|
+
- **Unified confirmation methods**:
|
|
73
|
+
- `show_move_confirmation(count, source_path, dest_path)`
|
|
74
|
+
- `show_copy_confirmation(count, source_path, dest_path)`
|
|
75
|
+
- `show_delete_confirmation(count, source_path)`
|
|
76
|
+
|
|
77
|
+
### Technical Changes
|
|
78
|
+
|
|
79
|
+
#### SelectionManager Enhancement
|
|
80
|
+
- **`@source_directory` tracking**: Remember where files were selected
|
|
81
|
+
- **`toggle_selection` updated**: Accepts `current_directory` parameter
|
|
82
|
+
- Sets source directory on first selection
|
|
83
|
+
- Maintains source directory for subsequent selections
|
|
84
|
+
- Clears source when all items deselected
|
|
85
|
+
- **Cross-directory operation support**:
|
|
86
|
+
- Select in `/path/A`
|
|
87
|
+
- Navigate to `/path/B`
|
|
88
|
+
- Execute move/copy/delete from original location
|
|
89
|
+
|
|
90
|
+
#### TextUtils Module
|
|
91
|
+
- **New method**: `wrap_preview_lines(lines, max_width)`
|
|
92
|
+
- Intelligent line wrapping for preview pane
|
|
93
|
+
- Preserves empty lines
|
|
94
|
+
- Character-by-character width calculation
|
|
95
|
+
- **Improved display width calculation**: Handles full-width and half-width characters
|
|
96
|
+
- **Smart truncation**: Adds ellipsis when space permits
|
|
97
|
+
|
|
98
|
+
#### KeybindHandler Updates
|
|
99
|
+
- **`move_selected_to_current`**: Uses `@selection_manager.source_directory`
|
|
100
|
+
- **`copy_selected_to_current`**: New method following same pattern as move
|
|
101
|
+
- **`delete_current_file_with_confirmation`**: Checks for selected items first
|
|
102
|
+
- **`is_selected?(entry_name)`**: Validates current directory matches source
|
|
103
|
+
- **Path display helper**: `shorten_path(path, max_length)` for dialog display
|
|
104
|
+
|
|
105
|
+
### Bug Fixes
|
|
106
|
+
|
|
107
|
+
- **Fixed undefined method error**: `display_width` in terminal_ui.rb
|
|
108
|
+
- Changed to `TextUtils.display_width`
|
|
109
|
+
- Fixed all text utility method calls
|
|
110
|
+
- **Fixed method redefinition warning**: Removed duplicate `show_delete_confirmation`
|
|
111
|
+
- **Fixed cross-directory selection display**: Selection marks only show in source directory
|
|
112
|
+
- Prevents showing same filename selected in different directories
|
|
113
|
+
- Internal selection state maintained correctly
|
|
114
|
+
|
|
115
|
+
### Documentation
|
|
116
|
+
|
|
117
|
+
- **README.md updated**: Added copy operation documentation
|
|
118
|
+
- Updated File Operations table with Copy row
|
|
119
|
+
- Updated Operation Workflow with `c` key
|
|
120
|
+
- Added copy to File Operations section
|
|
121
|
+
- **README_EN.md updated**: Same updates for English documentation
|
|
122
|
+
|
|
123
|
+
### Key Bindings Summary
|
|
124
|
+
|
|
125
|
+
#### File Selection & Operations
|
|
126
|
+
- `SPACE`: Select/deselect files and directories
|
|
127
|
+
- `m`: Move selected items to current directory (blue dialog)
|
|
128
|
+
- `c`: Copy selected items to current directory (green dialog)
|
|
129
|
+
- `x`: Delete selected items (red dialog)
|
|
130
|
+
|
|
131
|
+
#### Operation Workflow
|
|
132
|
+
```
|
|
133
|
+
1. SPACE → Select files/directories (multiple selection possible)
|
|
134
|
+
2. Choose operation key:
|
|
135
|
+
- m → Move to current directory
|
|
136
|
+
- c → Copy to current directory
|
|
137
|
+
- x → Delete
|
|
138
|
+
3. Floating Dialog → Confirm with Y/N, ESC to cancel
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### Cross-Directory Operations
|
|
142
|
+
```
|
|
143
|
+
1. Navigate to source directory
|
|
144
|
+
2. SPACE → Select files
|
|
145
|
+
3. Navigate to destination directory (selection marks hidden)
|
|
146
|
+
4. m/c → Move or copy from source to current
|
|
147
|
+
OR navigate back to source
|
|
148
|
+
5. x → Delete selected files
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### UI Color Scheme
|
|
152
|
+
|
|
153
|
+
- **Copy dialog**: Green border (`\e[32m`) - safe operation
|
|
154
|
+
- **Move dialog**: Blue border (`\e[34m`) - informational
|
|
155
|
+
- **Delete dialog**: Red border (`\e[31m`) - warning/danger
|
|
156
|
+
- **Selection mark**: `✓` with green background (`\e[42m\e[30m`)
|
|
157
|
+
- **Selection visibility**: Only in source directory
|
|
158
|
+
|
|
159
|
+
### Code Metrics
|
|
160
|
+
|
|
161
|
+
- **Lines of code reduction**: terminal_ui.rb: 1139 → 1048 lines (-8%)
|
|
162
|
+
- **Test coverage**: 309 runs, 1407 assertions, 0 failures
|
|
163
|
+
- **Warnings fixed**: Method redefinition warning eliminated
|
|
164
|
+
- **Module organization**: Better separation with TextUtils module
|
|
165
|
+
|
|
166
|
+
### Migration Notes
|
|
167
|
+
|
|
168
|
+
- **No breaking changes**: All existing functionality preserved
|
|
169
|
+
- **New feature**: `c` key for copy operation
|
|
170
|
+
- **Behavior improvement**: Selection display now directory-aware
|
|
171
|
+
- **Code quality**: Internal refactoring with no user-facing changes
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
**Full Changelog**: [v0.20.0...v0.21.0](https://github.com/masisz/rufio/compare/v0.20.0...v0.21.0)
|
data/README.md
CHANGED
|
@@ -16,7 +16,6 @@ rufioは、Yaziにインスパイアされたターミナル上で動作する
|
|
|
16
16
|
- **コマンドモード**: Tab補完とフローティングウィンドウを備えた強力なコマンドシステム
|
|
17
17
|
- **ファイルプレビュー**: テキストファイルの内容をその場で確認
|
|
18
18
|
- **ファイル選択・操作**: 複数ファイルの選択、移動、コピー、削除が可能
|
|
19
|
-
- **ベースディレクトリ操作**: 起動ディレクトリへの一括ファイル移動・コピー
|
|
20
19
|
- **リアルタイムフィルター**: sキーでファイル名の絞り込み表示
|
|
21
20
|
- **高度な検索機能**: fzfとrgaを使った強力な検索
|
|
22
21
|
- **マルチプラットフォーム**: macOS、Linux、Windowsで動作
|
|
@@ -87,8 +86,8 @@ rufio --help # ヘルプメッセージを表示
|
|
|
87
86
|
| キー | 機能 |
|
|
88
87
|
| ------- | ---------------------------------------------------- |
|
|
89
88
|
| `Space` | ファイル・ディレクトリを選択・選択解除(複数選択可) |
|
|
90
|
-
| `m` |
|
|
91
|
-
| `
|
|
89
|
+
| `m` | 選択したアイテムをカレントディレクトリに移動 |
|
|
90
|
+
| `c` | 選択したアイテムをカレントディレクトリにコピー |
|
|
92
91
|
| `x` | 選択したアイテムを削除 |
|
|
93
92
|
|
|
94
93
|
#### リアルタイムフィルター機能
|
|
@@ -144,17 +143,14 @@ rufio --help # ヘルプメッセージを表示
|
|
|
144
143
|
- **選択・選択解除**: `Space`キーでファイルやディレクトリを選択・選択解除
|
|
145
144
|
- **複数選択**: 複数のファイルやディレクトリを同時に選択可能
|
|
146
145
|
- **視覚的表示**: 選択されたアイテムは✓マークと緑色の背景で表示
|
|
147
|
-
- **選択状態の表示**: 画面上部2行目に選択中のアイテム数が表示
|
|
148
146
|
|
|
149
|
-
####
|
|
150
|
-
|
|
151
|
-
rufio起動時のディレクトリが**ベースディレクトリ**として設定され、選択したファイルの移動・コピー先となります。
|
|
147
|
+
#### ファイル操作
|
|
152
148
|
|
|
153
|
-
| 操作
|
|
154
|
-
|
|
|
155
|
-
| **移動**
|
|
156
|
-
| **コピー** | `
|
|
157
|
-
| **削除**
|
|
149
|
+
| 操作 | キー | 機能 |
|
|
150
|
+
| ---------- | ---- | ---------------------------------------- |
|
|
151
|
+
| **移動** | `m` | 選択したアイテムをカレントディレクトリに移動 |
|
|
152
|
+
| **コピー** | `c` | 選択したアイテムをカレントディレクトリにコピー |
|
|
153
|
+
| **削除** | `x` | 選択したアイテムを削除 |
|
|
158
154
|
|
|
159
155
|
#### 削除操作の詳細
|
|
160
156
|
|
|
@@ -171,11 +167,11 @@ rufio起動時のディレクトリが**ベースディレクトリ**として
|
|
|
171
167
|
```
|
|
172
168
|
1. Space → ファイル・ディレクトリを選択(複数選択可)
|
|
173
169
|
2. 操作キーを選択:
|
|
174
|
-
- m →
|
|
175
|
-
-
|
|
170
|
+
- m → カレントディレクトリに移動
|
|
171
|
+
- c → カレントディレクトリにコピー
|
|
176
172
|
- x → 削除
|
|
177
173
|
3. フローティングダイアログ → Y/Nで確認、ESCでキャンセル
|
|
178
|
-
4.
|
|
174
|
+
4. 操作完了 → 選択解除、画面更新
|
|
179
175
|
```
|
|
180
176
|
|
|
181
177
|
#### 安全機能
|
data/README_EN.md
CHANGED
|
@@ -14,8 +14,7 @@ rufio is a terminal-based file manager inspired by Yazi. It's implemented in Rub
|
|
|
14
14
|
- **Intuitive Operation**: Vim-like key bindings
|
|
15
15
|
- **Plugin System**: Extensible plugin architecture
|
|
16
16
|
- **File Preview**: View text file contents on the fly
|
|
17
|
-
- **File Selection & Operations**: Select multiple files, move,
|
|
18
|
-
- **Base Directory Operations**: Batch file operations to startup directory
|
|
17
|
+
- **File Selection & Operations**: Select multiple files, move, and delete
|
|
19
18
|
- **Real-time Filter**: Filter files by name using s key
|
|
20
19
|
- **Advanced Search**: Powerful search using fzf and rga
|
|
21
20
|
- **Multi-platform**: Runs on macOS, Linux, and Windows
|
|
@@ -85,8 +84,8 @@ rufio --help # Show help message
|
|
|
85
84
|
| Key | Function |
|
|
86
85
|
| ------- | ------------------------------------------- |
|
|
87
86
|
| `Space` | Select/deselect files and directories |
|
|
88
|
-
| `m` | Move selected items to
|
|
89
|
-
| `
|
|
87
|
+
| `m` | Move selected items to current directory |
|
|
88
|
+
| `c` | Copy selected items to current directory |
|
|
90
89
|
| `x` | Delete selected items |
|
|
91
90
|
|
|
92
91
|
#### Real-time Filter
|
|
@@ -132,16 +131,13 @@ rufio --help # Show help message
|
|
|
132
131
|
- **Select/Deselect**: Use `Space` key to select or deselect files and directories
|
|
133
132
|
- **Multiple Selection**: Select multiple files and directories simultaneously
|
|
134
133
|
- **Visual Display**: Selected items are marked with ✓ and highlighted in green
|
|
135
|
-
- **Selection Counter**: The number of selected items is shown on the second line of the screen
|
|
136
134
|
|
|
137
|
-
####
|
|
138
|
-
|
|
139
|
-
The directory where rufio starts becomes the **base directory**, which serves as the destination for move and copy operations.
|
|
135
|
+
#### File Operations
|
|
140
136
|
|
|
141
137
|
| Operation | Key | Function |
|
|
142
138
|
| --------- | --- | --------------------------------------- |
|
|
143
|
-
| **Move** | `m` | Move selected items to
|
|
144
|
-
| **Copy** | `
|
|
139
|
+
| **Move** | `m` | Move selected items to current directory |
|
|
140
|
+
| **Copy** | `c` | Copy selected items to current directory |
|
|
145
141
|
| **Delete** | `x` | Delete selected items |
|
|
146
142
|
|
|
147
143
|
#### Delete Operation Details
|
|
@@ -159,8 +155,8 @@ The directory where rufio starts becomes the **base directory**, which serves as
|
|
|
159
155
|
```
|
|
160
156
|
1. Space → Select files/directories (multiple selection possible)
|
|
161
157
|
2. Choose operation key:
|
|
162
|
-
- m → Move to
|
|
163
|
-
-
|
|
158
|
+
- m → Move to current directory
|
|
159
|
+
- c → Copy to current directory
|
|
164
160
|
- x → Delete
|
|
165
161
|
3. Floating Dialog → Confirm with Y/N, ESC to cancel
|
|
166
162
|
4. Result Display → Review operation results in floating window
|
data/lib/rufio/application.rb
CHANGED
|
@@ -51,9 +51,6 @@ module Rufio
|
|
|
51
51
|
@project_log = ProjectLog.new(log_dir)
|
|
52
52
|
@in_project_mode = false
|
|
53
53
|
@in_log_mode = false
|
|
54
|
-
|
|
55
|
-
# Legacy fields for backward compatibility
|
|
56
|
-
@base_directory = nil
|
|
57
54
|
end
|
|
58
55
|
|
|
59
56
|
def set_directory_listing(directory_listing)
|
|
@@ -65,15 +62,18 @@ module Rufio
|
|
|
65
62
|
@terminal_ui = terminal_ui
|
|
66
63
|
end
|
|
67
64
|
|
|
68
|
-
def set_base_directory(base_dir)
|
|
69
|
-
@base_directory = File.expand_path(base_dir)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
65
|
def selected_items
|
|
73
66
|
@selection_manager.selected_items
|
|
74
67
|
end
|
|
75
68
|
|
|
76
69
|
def is_selected?(entry_name)
|
|
70
|
+
# Only show as selected if we're in the same directory where selection happened
|
|
71
|
+
current_path = @directory_listing&.current_path || Dir.pwd
|
|
72
|
+
source_path = @selection_manager.source_directory
|
|
73
|
+
|
|
74
|
+
# If no source directory or different directory, nothing is selected
|
|
75
|
+
return false if source_path.nil? || current_path != source_path
|
|
76
|
+
|
|
77
77
|
@selection_manager.selected?(entry_name)
|
|
78
78
|
end
|
|
79
79
|
|
|
@@ -141,10 +141,10 @@ module Rufio
|
|
|
141
141
|
create_file
|
|
142
142
|
when 'A' # A
|
|
143
143
|
create_directory
|
|
144
|
-
when 'm' # m - move selected files to
|
|
145
|
-
|
|
146
|
-
when '
|
|
147
|
-
|
|
144
|
+
when 'm' # m - move selected files to current directory
|
|
145
|
+
move_selected_to_current
|
|
146
|
+
when 'c' # c - copy selected files to current directory
|
|
147
|
+
copy_selected_to_current
|
|
148
148
|
when 'x' # x - delete selected files
|
|
149
149
|
delete_selected_files
|
|
150
150
|
when 'p' # p - project mode
|
|
@@ -483,11 +483,29 @@ module Rufio
|
|
|
483
483
|
end
|
|
484
484
|
|
|
485
485
|
def delete_current_file_with_confirmation
|
|
486
|
+
current_path = @directory_listing&.current_path || Dir.pwd
|
|
487
|
+
|
|
488
|
+
# Check if there are selected items
|
|
489
|
+
if !@selection_manager.empty?
|
|
490
|
+
# Delete multiple selected items
|
|
491
|
+
source_path = @selection_manager.source_directory || current_path
|
|
492
|
+
|
|
493
|
+
if show_delete_confirmation(@selection_manager.count, source_path)
|
|
494
|
+
result = @file_operations.delete(@selection_manager.selected_items, source_path)
|
|
495
|
+
@selection_manager.clear
|
|
496
|
+
@directory_listing.refresh if @directory_listing
|
|
497
|
+
@terminal_ui&.refresh_display
|
|
498
|
+
return result.success
|
|
499
|
+
else
|
|
500
|
+
return false
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
# Single file deletion (current item)
|
|
486
505
|
current_item = current_entry()
|
|
487
506
|
return false unless current_item
|
|
488
507
|
|
|
489
508
|
current_name = current_item[:name]
|
|
490
|
-
current_path = @directory_listing&.current_path || Dir.pwd
|
|
491
509
|
is_directory = current_item[:type] == :directory
|
|
492
510
|
|
|
493
511
|
# 確認ダイアログを表示
|
|
@@ -554,19 +572,24 @@ module Rufio
|
|
|
554
572
|
entry = current_entry
|
|
555
573
|
return false unless entry
|
|
556
574
|
|
|
557
|
-
@
|
|
575
|
+
current_path = @directory_listing&.current_path || Dir.pwd
|
|
576
|
+
@selection_manager.toggle_selection(entry, current_path)
|
|
558
577
|
true
|
|
559
578
|
end
|
|
560
579
|
|
|
561
|
-
def move_selected_to_base
|
|
562
|
-
return false if @selection_manager.empty? || @base_directory.nil?
|
|
563
580
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
581
|
+
def move_selected_to_current
|
|
582
|
+
return false if @selection_manager.empty?
|
|
583
|
+
|
|
584
|
+
current_path = @directory_listing&.current_path || Dir.pwd
|
|
585
|
+
source_path = @selection_manager.source_directory || current_path
|
|
586
|
+
|
|
587
|
+
# Move selected files/directories from source directory to current directory
|
|
588
|
+
# This allows moving files even after navigating to a different directory
|
|
589
|
+
if show_move_confirmation(@selection_manager.count, source_path, current_path)
|
|
590
|
+
result = @file_operations.move(@selection_manager.selected_items, source_path, current_path)
|
|
567
591
|
|
|
568
|
-
#
|
|
569
|
-
show_operation_result(result)
|
|
592
|
+
# Clear selection and refresh
|
|
570
593
|
@selection_manager.clear
|
|
571
594
|
@directory_listing.refresh if @directory_listing
|
|
572
595
|
true
|
|
@@ -575,15 +598,18 @@ module Rufio
|
|
|
575
598
|
end
|
|
576
599
|
end
|
|
577
600
|
|
|
578
|
-
def
|
|
579
|
-
return false if @selection_manager.empty?
|
|
601
|
+
def copy_selected_to_current
|
|
602
|
+
return false if @selection_manager.empty?
|
|
580
603
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
604
|
+
current_path = @directory_listing&.current_path || Dir.pwd
|
|
605
|
+
source_path = @selection_manager.source_directory || current_path
|
|
606
|
+
|
|
607
|
+
# Copy selected files/directories from source directory to current directory
|
|
608
|
+
# This allows copying files even after navigating to a different directory
|
|
609
|
+
if show_copy_confirmation(@selection_manager.count, source_path, current_path)
|
|
610
|
+
result = @file_operations.copy(@selection_manager.selected_items, source_path, current_path)
|
|
584
611
|
|
|
585
|
-
#
|
|
586
|
-
show_operation_result(result)
|
|
612
|
+
# Clear selection and refresh
|
|
587
613
|
@selection_manager.clear
|
|
588
614
|
@directory_listing.refresh if @directory_listing
|
|
589
615
|
true
|
|
@@ -627,17 +653,31 @@ module Rufio
|
|
|
627
653
|
end
|
|
628
654
|
end
|
|
629
655
|
|
|
630
|
-
def
|
|
631
|
-
|
|
656
|
+
def show_move_confirmation(count, source_path, dest_path)
|
|
657
|
+
show_floating_move_confirmation(count, source_path, dest_path)
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
def show_copy_confirmation(count, source_path, dest_path)
|
|
661
|
+
show_floating_copy_confirmation(count, source_path, dest_path)
|
|
632
662
|
end
|
|
633
663
|
|
|
634
|
-
def
|
|
664
|
+
def show_delete_confirmation(count, source_path)
|
|
665
|
+
show_floating_delete_confirmation(count, source_path)
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
def show_floating_delete_confirmation(count, source_path)
|
|
635
669
|
# コンテンツの準備
|
|
636
670
|
title = 'Delete Confirmation'
|
|
671
|
+
|
|
672
|
+
# パスの短縮表示
|
|
673
|
+
source_display = shorten_path(source_path, 35)
|
|
674
|
+
|
|
637
675
|
content_lines = [
|
|
638
676
|
'',
|
|
639
677
|
"Delete #{count} item(s)?",
|
|
640
678
|
'',
|
|
679
|
+
"From: #{source_display}",
|
|
680
|
+
'',
|
|
641
681
|
' [Y]es - Delete',
|
|
642
682
|
' [N]o - Cancel',
|
|
643
683
|
''
|
|
@@ -685,6 +725,128 @@ module Rufio
|
|
|
685
725
|
end
|
|
686
726
|
end
|
|
687
727
|
|
|
728
|
+
def show_floating_move_confirmation(count, source_path, dest_path)
|
|
729
|
+
# コンテンツの準備
|
|
730
|
+
title = 'Move Confirmation'
|
|
731
|
+
|
|
732
|
+
# パスの短縮表示
|
|
733
|
+
source_display = shorten_path(source_path, 35)
|
|
734
|
+
dest_display = shorten_path(dest_path, 35)
|
|
735
|
+
|
|
736
|
+
content_lines = [
|
|
737
|
+
'',
|
|
738
|
+
"Move #{count} item(s)?",
|
|
739
|
+
'',
|
|
740
|
+
"From: #{source_display}",
|
|
741
|
+
"To: #{dest_display}",
|
|
742
|
+
'',
|
|
743
|
+
' [Y]es - Move',
|
|
744
|
+
' [N]o - Cancel',
|
|
745
|
+
''
|
|
746
|
+
]
|
|
747
|
+
|
|
748
|
+
# ダイアログのサイズ設定
|
|
749
|
+
dialog_width = CONFIRMATION_DIALOG_WIDTH
|
|
750
|
+
dialog_height = DIALOG_BORDER_HEIGHT + content_lines.length
|
|
751
|
+
|
|
752
|
+
# ダイアログの位置を中央に設定
|
|
753
|
+
x, y = @dialog_renderer.calculate_center(dialog_width, dialog_height)
|
|
754
|
+
|
|
755
|
+
# ダイアログの描画(移動は青色で表示)
|
|
756
|
+
@dialog_renderer.draw_floating_window(x, y, dialog_width, dialog_height, title, content_lines, {
|
|
757
|
+
border_color: "\e[34m", # 青色(情報)
|
|
758
|
+
title_color: "\e[1;34m", # 太字青色
|
|
759
|
+
content_color: "\e[37m" # 白色
|
|
760
|
+
})
|
|
761
|
+
|
|
762
|
+
# キー入力待機
|
|
763
|
+
loop do
|
|
764
|
+
input = STDIN.getch.downcase
|
|
765
|
+
|
|
766
|
+
case input
|
|
767
|
+
when 'y'
|
|
768
|
+
# ダイアログをクリア
|
|
769
|
+
@dialog_renderer.clear_area(x, y, dialog_width, dialog_height)
|
|
770
|
+
@terminal_ui&.refresh_display # 画面を再描画
|
|
771
|
+
return true
|
|
772
|
+
when 'n', "\e", "\x03" # n, ESC, Ctrl+C
|
|
773
|
+
# ダイアログをクリア
|
|
774
|
+
@dialog_renderer.clear_area(x, y, dialog_width, dialog_height)
|
|
775
|
+
@terminal_ui&.refresh_display # 画面を再描画
|
|
776
|
+
return false
|
|
777
|
+
when 'q' # qキーでもキャンセル
|
|
778
|
+
@dialog_renderer.clear_area(x, y, dialog_width, dialog_height)
|
|
779
|
+
@terminal_ui&.refresh_display
|
|
780
|
+
return false
|
|
781
|
+
end
|
|
782
|
+
# 無効なキー入力の場合は再度ループ
|
|
783
|
+
end
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
def show_floating_copy_confirmation(count, source_path, dest_path)
|
|
787
|
+
# コンテンツの準備
|
|
788
|
+
title = 'Copy Confirmation'
|
|
789
|
+
|
|
790
|
+
# パスの短縮表示
|
|
791
|
+
source_display = shorten_path(source_path, 35)
|
|
792
|
+
dest_display = shorten_path(dest_path, 35)
|
|
793
|
+
|
|
794
|
+
content_lines = [
|
|
795
|
+
'',
|
|
796
|
+
"Copy #{count} item(s)?",
|
|
797
|
+
'',
|
|
798
|
+
"From: #{source_display}",
|
|
799
|
+
"To: #{dest_display}",
|
|
800
|
+
'',
|
|
801
|
+
' [Y]es - Copy',
|
|
802
|
+
' [N]o - Cancel',
|
|
803
|
+
''
|
|
804
|
+
]
|
|
805
|
+
|
|
806
|
+
# ダイアログのサイズ設定
|
|
807
|
+
dialog_width = CONFIRMATION_DIALOG_WIDTH
|
|
808
|
+
dialog_height = DIALOG_BORDER_HEIGHT + content_lines.length
|
|
809
|
+
|
|
810
|
+
# ダイアログの位置を中央に設定
|
|
811
|
+
x, y = @dialog_renderer.calculate_center(dialog_width, dialog_height)
|
|
812
|
+
|
|
813
|
+
# ダイアログの描画(コピーは緑色で表示)
|
|
814
|
+
@dialog_renderer.draw_floating_window(x, y, dialog_width, dialog_height, title, content_lines, {
|
|
815
|
+
border_color: "\e[32m", # 緑色(安全な操作)
|
|
816
|
+
title_color: "\e[1;32m", # 太字緑色
|
|
817
|
+
content_color: "\e[37m" # 白色
|
|
818
|
+
})
|
|
819
|
+
|
|
820
|
+
# キー入力待機
|
|
821
|
+
loop do
|
|
822
|
+
input = STDIN.getch.downcase
|
|
823
|
+
|
|
824
|
+
case input
|
|
825
|
+
when 'y'
|
|
826
|
+
# ダイアログをクリア
|
|
827
|
+
@dialog_renderer.clear_area(x, y, dialog_width, dialog_height)
|
|
828
|
+
@terminal_ui&.refresh_display # 画面を再描画
|
|
829
|
+
return true
|
|
830
|
+
when 'n', "\e", "\x03" # n, ESC, Ctrl+C
|
|
831
|
+
# ダイアログをクリア
|
|
832
|
+
@dialog_renderer.clear_area(x, y, dialog_width, dialog_height)
|
|
833
|
+
@terminal_ui&.refresh_display # 画面を再描画
|
|
834
|
+
return false
|
|
835
|
+
when 'q' # qキーでもキャンセル
|
|
836
|
+
@dialog_renderer.clear_area(x, y, dialog_width, dialog_height)
|
|
837
|
+
@terminal_ui&.refresh_display
|
|
838
|
+
return false
|
|
839
|
+
end
|
|
840
|
+
# 無効なキー入力の場合は再度ループ
|
|
841
|
+
end
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
# パスを指定した長さに短縮
|
|
845
|
+
def shorten_path(path, max_length)
|
|
846
|
+
return path if path.length <= max_length
|
|
847
|
+
"...#{path[-(max_length - 3)..-1]}"
|
|
848
|
+
end
|
|
849
|
+
|
|
688
850
|
def perform_delete_operation(items)
|
|
689
851
|
Logger.debug('Starting delete operation', context: { items: items, count: items.length })
|
|
690
852
|
|
|
@@ -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
|
data/lib/rufio/terminal_ui.rb
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'io/console'
|
|
4
|
+
require_relative 'text_utils'
|
|
4
5
|
|
|
5
6
|
module Rufio
|
|
6
7
|
class TerminalUI
|
|
7
8
|
# Layout constants
|
|
8
|
-
HEADER_HEIGHT =
|
|
9
|
+
HEADER_HEIGHT = 1 # Header占有行数
|
|
9
10
|
FOOTER_HEIGHT = 1 # Footer占有行数
|
|
10
|
-
HEADER_FOOTER_MARGIN =
|
|
11
|
+
HEADER_FOOTER_MARGIN = 3 # Header + Footer分のマージン
|
|
11
12
|
|
|
12
13
|
# Panel layout ratios
|
|
13
14
|
LEFT_PANEL_RATIO = 0.5 # 左パネルの幅比率
|
|
@@ -17,8 +18,6 @@ module Rufio
|
|
|
17
18
|
DEFAULT_SCREEN_WIDTH = 80 # デフォルト画面幅
|
|
18
19
|
DEFAULT_SCREEN_HEIGHT = 24 # デフォルト画面高さ
|
|
19
20
|
HEADER_PADDING = 2 # ヘッダーのパディング
|
|
20
|
-
BASE_INFO_RESERVED_WIDTH = 20 # ベースディレクトリ表示の予約幅
|
|
21
|
-
BASE_INFO_MIN_WIDTH = 10 # ベースディレクトリ表示の最小幅
|
|
22
21
|
FILTER_TEXT_RESERVED = 15 # フィルタテキスト表示の予約幅
|
|
23
22
|
|
|
24
23
|
# File display constants
|
|
@@ -31,7 +30,7 @@ module Rufio
|
|
|
31
30
|
GIGABYTE = MEGABYTE * 1024
|
|
32
31
|
|
|
33
32
|
# Line offsets
|
|
34
|
-
CONTENT_START_LINE =
|
|
33
|
+
CONTENT_START_LINE = 2 # コンテンツ開始行(ヘッダー1行スキップ)
|
|
35
34
|
|
|
36
35
|
def initialize
|
|
37
36
|
console = IO.console
|
|
@@ -125,9 +124,8 @@ module Rufio
|
|
|
125
124
|
return
|
|
126
125
|
end
|
|
127
126
|
|
|
128
|
-
# header (
|
|
127
|
+
# header (1 line)
|
|
129
128
|
draw_header
|
|
130
|
-
draw_base_directory_info
|
|
131
129
|
|
|
132
130
|
# main content (left: directory list, right: preview)
|
|
133
131
|
entries = get_display_entries
|
|
@@ -184,42 +182,6 @@ module Rufio
|
|
|
184
182
|
puts "\e[7m#{header.ljust(@screen_width)}\e[0m" # reverse display
|
|
185
183
|
end
|
|
186
184
|
|
|
187
|
-
def draw_base_directory_info
|
|
188
|
-
# 強制的に表示 - デバッグ用に安全チェックを緩和
|
|
189
|
-
if @keybind_handler && @keybind_handler.instance_variable_get(:@base_directory)
|
|
190
|
-
base_dir = @keybind_handler.instance_variable_get(:@base_directory)
|
|
191
|
-
selected_count = @keybind_handler.selected_items.length
|
|
192
|
-
base_info = "📋 Base Directory: #{base_dir}"
|
|
193
|
-
|
|
194
|
-
# 選択されたアイテム数を表示
|
|
195
|
-
if selected_count > 0
|
|
196
|
-
base_info += " | Selected: #{selected_count} item(s)"
|
|
197
|
-
end
|
|
198
|
-
else
|
|
199
|
-
# keybind_handlerがない場合、またはbase_directoryが設定されていない場合
|
|
200
|
-
base_info = "📋 Base Directory: #{Dir.pwd}"
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
# 長すぎる場合は省略
|
|
204
|
-
if base_info.length > @screen_width - HEADER_PADDING
|
|
205
|
-
if base_info.include?(" | Selected:")
|
|
206
|
-
selected_part = base_info.split(" | Selected:").last
|
|
207
|
-
available_length = @screen_width - BASE_INFO_RESERVED_WIDTH - " | Selected:#{selected_part}".length
|
|
208
|
-
else
|
|
209
|
-
available_length = @screen_width - BASE_INFO_RESERVED_WIDTH
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
if available_length > BASE_INFO_MIN_WIDTH
|
|
213
|
-
# パスの最後の部分を表示
|
|
214
|
-
dir_part = base_info.split(": ").last.split(" | ").first
|
|
215
|
-
short_base_dir = "...#{dir_part[-available_length..-1]}"
|
|
216
|
-
base_info = base_info.gsub(dir_part, short_base_dir)
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
# 2行目に確実に表示
|
|
221
|
-
print "\e[2;1H\e[44m\e[37m#{base_info.ljust(@screen_width)}\e[0m"
|
|
222
|
-
end
|
|
223
185
|
|
|
224
186
|
|
|
225
187
|
def draw_directory_list(entries, width, height)
|
|
@@ -352,7 +314,7 @@ module Rufio
|
|
|
352
314
|
elsif selected_entry && selected_entry[:type] == 'file' && i >= 2
|
|
353
315
|
# ファイルプレビュー(折り返し対応)
|
|
354
316
|
preview_content = get_preview_content(selected_entry)
|
|
355
|
-
wrapped_lines = wrap_preview_lines(preview_content, safe_width - 1) # スペース分を除く
|
|
317
|
+
wrapped_lines = TextUtils.wrap_preview_lines(preview_content, safe_width - 1) # スペース分を除く
|
|
356
318
|
display_line_index = i - 2
|
|
357
319
|
|
|
358
320
|
if display_line_index < wrapped_lines.length
|
|
@@ -370,16 +332,16 @@ module Rufio
|
|
|
370
332
|
if safe_width <= 0
|
|
371
333
|
# 表示スペースがない場合は何も出力しない
|
|
372
334
|
next
|
|
373
|
-
elsif display_width(content_to_print) > safe_width
|
|
335
|
+
elsif TextUtils.display_width(content_to_print) > safe_width
|
|
374
336
|
# 表示幅ベースで切り詰める
|
|
375
|
-
content_to_print = truncate_to_width(content_to_print, safe_width)
|
|
337
|
+
content_to_print = TextUtils.truncate_to_width(content_to_print, safe_width)
|
|
376
338
|
end
|
|
377
339
|
|
|
378
340
|
# 出力(パディングなし、はみ出し防止のため)
|
|
379
341
|
print content_to_print
|
|
380
342
|
|
|
381
343
|
# 残りのスペースを埋める(ただし安全な範囲内のみ)
|
|
382
|
-
remaining_space = safe_width - display_width(content_to_print)
|
|
344
|
+
remaining_space = safe_width - TextUtils.display_width(content_to_print)
|
|
383
345
|
print ' ' * remaining_space if remaining_space > 0
|
|
384
346
|
end
|
|
385
347
|
end
|
|
@@ -402,98 +364,6 @@ module Rufio
|
|
|
402
364
|
["(#{ConfigLoader.message('file.preview_error')})"]
|
|
403
365
|
end
|
|
404
366
|
|
|
405
|
-
def wrap_preview_lines(lines, max_width)
|
|
406
|
-
return [] if lines.empty? || max_width <= 0
|
|
407
|
-
|
|
408
|
-
wrapped_lines = []
|
|
409
|
-
|
|
410
|
-
lines.each do |line|
|
|
411
|
-
if display_width(line) <= max_width
|
|
412
|
-
# 短い行はそのまま追加
|
|
413
|
-
wrapped_lines << line
|
|
414
|
-
else
|
|
415
|
-
# 長い行は折り返し
|
|
416
|
-
remaining_line = line
|
|
417
|
-
while display_width(remaining_line) > max_width
|
|
418
|
-
# 単語境界で折り返すことを試みる
|
|
419
|
-
break_point = find_break_point(remaining_line, max_width)
|
|
420
|
-
wrapped_lines << remaining_line[0...break_point]
|
|
421
|
-
remaining_line = remaining_line[break_point..-1]
|
|
422
|
-
end
|
|
423
|
-
# 残りの部分を追加
|
|
424
|
-
wrapped_lines << remaining_line if remaining_line.length > 0
|
|
425
|
-
end
|
|
426
|
-
end
|
|
427
|
-
|
|
428
|
-
wrapped_lines
|
|
429
|
-
end
|
|
430
|
-
|
|
431
|
-
def display_width(string)
|
|
432
|
-
# 文字列の表示幅を計算する
|
|
433
|
-
# 日本語文字(全角)は幅2、ASCII文字(半角)は幅1として計算
|
|
434
|
-
width = 0
|
|
435
|
-
string.each_char do |char|
|
|
436
|
-
# 全角文字の判定
|
|
437
|
-
width += if char.ord > 127 || char.match?(/[あ-んア-ン一-龯]/)
|
|
438
|
-
2
|
|
439
|
-
else
|
|
440
|
-
1
|
|
441
|
-
end
|
|
442
|
-
end
|
|
443
|
-
width
|
|
444
|
-
end
|
|
445
|
-
|
|
446
|
-
def truncate_to_width(string, max_width)
|
|
447
|
-
# 表示幅を指定して文字列を切り詰める
|
|
448
|
-
return string if display_width(string) <= max_width
|
|
449
|
-
|
|
450
|
-
current_width = 0
|
|
451
|
-
result = ''
|
|
452
|
-
|
|
453
|
-
string.each_char do |char|
|
|
454
|
-
char_width = char.ord > 127 || char.match?(/[あ-んア-ン一-龯]/) ? 2 : 1
|
|
455
|
-
|
|
456
|
-
if current_width + char_width > max_width
|
|
457
|
-
# "..."を追加できるかチェック
|
|
458
|
-
result += '...' if max_width >= 3 && current_width <= max_width - 3
|
|
459
|
-
break
|
|
460
|
-
end
|
|
461
|
-
|
|
462
|
-
result += char
|
|
463
|
-
current_width += char_width
|
|
464
|
-
end
|
|
465
|
-
|
|
466
|
-
result
|
|
467
|
-
end
|
|
468
|
-
|
|
469
|
-
def find_break_point(line, max_width)
|
|
470
|
-
# 最大幅以内で適切な折り返し位置を見つける
|
|
471
|
-
return line.length if display_width(line) <= max_width
|
|
472
|
-
|
|
473
|
-
# 文字ごとに幅を計算しながら適切な位置を探す
|
|
474
|
-
current_width = 0
|
|
475
|
-
best_break_point = 0
|
|
476
|
-
space_break_point = nil
|
|
477
|
-
punct_break_point = nil
|
|
478
|
-
|
|
479
|
-
line.each_char.with_index do |char, index|
|
|
480
|
-
char_width = char.ord > 127 || char.match?(/[あ-んア-ン一-龯]/) ? 2 : 1
|
|
481
|
-
|
|
482
|
-
break if current_width + char_width > max_width
|
|
483
|
-
|
|
484
|
-
current_width += char_width
|
|
485
|
-
best_break_point = index + 1
|
|
486
|
-
|
|
487
|
-
# スペースで区切れる位置を記録
|
|
488
|
-
space_break_point = index + 1 if char == ' ' && current_width > max_width * 0.5
|
|
489
|
-
|
|
490
|
-
# 日本語の句読点で区切れる位置を記録
|
|
491
|
-
punct_break_point = index + 1 if char.match?(/[、。,.!?]/) && current_width > max_width * 0.5
|
|
492
|
-
end
|
|
493
|
-
|
|
494
|
-
# 最適な折り返し位置を選択
|
|
495
|
-
space_break_point || punct_break_point || best_break_point
|
|
496
|
-
end
|
|
497
367
|
|
|
498
368
|
def get_display_entries
|
|
499
369
|
if @keybind_handler.filter_active?
|
data/lib/rufio/text_utils.rb
CHANGED
|
@@ -104,5 +104,54 @@ module Rufio
|
|
|
104
104
|
|
|
105
105
|
space_break_point || punct_break_point || best_break_point
|
|
106
106
|
end
|
|
107
|
+
|
|
108
|
+
# Wrap preview lines to fit within max_width
|
|
109
|
+
# @param lines [Array<String>] Lines to wrap
|
|
110
|
+
# @param max_width [Integer] Maximum width for each line
|
|
111
|
+
# @return [Array<String>] Wrapped lines
|
|
112
|
+
def wrap_preview_lines(lines, max_width)
|
|
113
|
+
return lines if max_width <= 0
|
|
114
|
+
|
|
115
|
+
wrapped = []
|
|
116
|
+
lines.each do |line|
|
|
117
|
+
# Remove trailing whitespace
|
|
118
|
+
line = line.rstrip
|
|
119
|
+
|
|
120
|
+
# If line is empty, keep it
|
|
121
|
+
if line.empty?
|
|
122
|
+
wrapped << ''
|
|
123
|
+
next
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# If line fits within max_width, keep it as is
|
|
127
|
+
if display_width(line) <= max_width
|
|
128
|
+
wrapped << line
|
|
129
|
+
next
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Split long lines
|
|
133
|
+
current_line = []
|
|
134
|
+
current_width = 0
|
|
135
|
+
|
|
136
|
+
line.each_char do |char|
|
|
137
|
+
char_width = display_width(char)
|
|
138
|
+
|
|
139
|
+
if current_width + char_width > max_width
|
|
140
|
+
# Start a new line
|
|
141
|
+
wrapped << current_line.join
|
|
142
|
+
current_line = [char]
|
|
143
|
+
current_width = char_width
|
|
144
|
+
else
|
|
145
|
+
current_line << char
|
|
146
|
+
current_width += char_width
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Add remaining characters
|
|
151
|
+
wrapped << current_line.join unless current_line.empty?
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
wrapped
|
|
155
|
+
end
|
|
107
156
|
end
|
|
108
157
|
end
|
data/lib/rufio/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rufio
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.21.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- masisz
|
|
@@ -119,6 +119,7 @@ files:
|
|
|
119
119
|
- CHANGELOG.md
|
|
120
120
|
- CHANGELOG_v0.10.0.md
|
|
121
121
|
- CHANGELOG_v0.20.0.md
|
|
122
|
+
- CHANGELOG_v0.21.0.md
|
|
122
123
|
- CHANGELOG_v0.4.0.md
|
|
123
124
|
- CHANGELOG_v0.5.0.md
|
|
124
125
|
- CHANGELOG_v0.6.0.md
|