sergeant 1.0.2 → 1.0.4
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 +50 -0
- data/README.md +12 -13
- data/lib/sergeant/modals/file_operations.rb +88 -3
- data/lib/sergeant/modals/help.rb +6 -3
- data/lib/sergeant/modals/navigation.rb +124 -0
- data/lib/sergeant/rendering.rb +19 -4
- data/lib/sergeant/version.rb +1 -1
- data/lib/sergeant.rb +28 -6
- data/sergeant.gemspec +2 -0
- metadata +1 -4
- data/lib/.DS_Store +0 -0
- data/lib/sergeant/.DS_Store +0 -0
- data/lib/sergeant/modals/.DS_Store +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9a87751fe5e98dee814b470aa90818bd0f798cbdb4143f55522ffcf610d8de0c
|
|
4
|
+
data.tar.gz: d1c47033ac959cc1bbd50af0fe96e526df4d2f72077efc914d0570b8df6e0e7b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 77dac789c948ee83d3bfeda13cdda4fa0c3f03779d2e6cfc3ec85126dd97465c309d55cfa2500008656be3b65367e961c7af8b639c4d237a0716572d2cfa82be
|
|
7
|
+
data.tar.gz: e0e1222dba1be26d9a3da7611aa8c5289ea422f58f9c1f5d3813d7f7581c05f860e61144e7623c1f66ccbb127ba37d27eaded1b9c5633f6e9e5cfa7422df8c0b
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,56 @@ All notable changes to Sergeant will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.3] - 2024-12-26
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Total size display for marked items**
|
|
12
|
+
- Status bar now shows total size of all marked files
|
|
13
|
+
- Helps users understand the size of operations before copying/cutting
|
|
14
|
+
- Automatically formatted with appropriate units (B, K, M, G)
|
|
15
|
+
- **Quick filter feature** (f key)
|
|
16
|
+
- Filter current directory view without changing directories
|
|
17
|
+
- Case-insensitive real-time filtering as you type
|
|
18
|
+
- Status bar shows active filter and filtered item count
|
|
19
|
+
- Press ESC to clear filter, Enter to apply
|
|
20
|
+
- **Archive peek support** (v key on archives)
|
|
21
|
+
- Preview contents of archive files without extracting
|
|
22
|
+
- Supports: .zip, .tar, .tar.gz/.tgz, .tar.bz2/.tbz, .tar.xz/.txz, .7z, .rar
|
|
23
|
+
- Uses native tools (unzip, tar, 7z, unrar) for listing contents
|
|
24
|
+
- Falls back gracefully if archive tools not installed
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- Updated help modal to reflect new features
|
|
28
|
+
- Reorganized help modal with "View & Search" section for better clarity
|
|
29
|
+
|
|
30
|
+
### Performance
|
|
31
|
+
- **Optimized directory refresh**
|
|
32
|
+
- Only fetch owner info and permissions when ownership display is enabled
|
|
33
|
+
- Use `stat.directory?` instead of `File.directory?()` to avoid duplicate syscalls
|
|
34
|
+
- Track ownership toggle changes to refresh only when needed
|
|
35
|
+
- **Added comprehensive test coverage** for performance optimizations (14 test cases)
|
|
36
|
+
|
|
37
|
+
## [1.0.2] - 2024-12-26
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
- **Display issue on Arch Linux**: Added terminal color support checking
|
|
41
|
+
- Prevents crashes on terminals without color support
|
|
42
|
+
- Gracefully degrades when `start_color` is unavailable
|
|
43
|
+
- Fixes blank screen issue with Ruby version managers (mise, rbenv, asdf)
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
- **Installation troubleshooting**
|
|
47
|
+
- Comprehensive troubleshooting documentation in README
|
|
48
|
+
- Simple alias solution for Arch Linux display issues: `alias sgt='ruby "$(which sgt)"'`
|
|
49
|
+
- **Better error handling**
|
|
50
|
+
- Terminal initialization now shows helpful error messages on failure
|
|
51
|
+
- Displays environment information (TERM, TTY status) to aid debugging
|
|
52
|
+
|
|
53
|
+
### Technical
|
|
54
|
+
- Improved terminal initialization with `has_colors?` checks before calling `start_color`
|
|
55
|
+
- Added error recovery for curses screen initialization failures
|
|
56
|
+
- Better compatibility with different ncurses implementations
|
|
57
|
+
|
|
8
58
|
## [1.0.1] - 2024-12-24
|
|
9
59
|
|
|
10
60
|
### Fixed
|
data/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# 🎖️ Sergeant (sgt)
|
|
2
2
|
|
|
3
3
|

|
|
4
|
+

|
|
4
5
|
|
|
5
6
|
**Interactive TUI Directory Navigator for Terminal - "Leave it to the Sarge!"**
|
|
6
7
|
|
|
@@ -19,14 +20,17 @@ Simple, fast, and elegant.
|
|
|
19
20
|
- 🔍 **Git Branch Display** - Shows current git branch in header
|
|
20
21
|
- 👤 **Ownership Toggle** - View file permissions and ownership (press 'o')
|
|
21
22
|
- 📑 **Bookmarks** - Save and quickly navigate to favorite directories
|
|
23
|
+
- 🔎 **Quick Filter** - Filter current directory view in real-time (press 'f')
|
|
22
24
|
|
|
23
25
|
### File Operations
|
|
24
26
|
- 📋 **Copy/Cut/Paste** - Mark files with spacebar, copy (c), cut (x), and paste (p)
|
|
25
27
|
- ✂️ **Multi-file Selection** - Mark multiple files/folders for batch operations
|
|
28
|
+
- 📏 **Size Display** - See total size of marked items in status bar
|
|
26
29
|
- 🗑️ **Delete with Confirmation** - Safe deletion with confirmation dialog
|
|
27
30
|
- ✏️ **Rename** - Rename files and folders with pre-filled input
|
|
28
31
|
- 🔄 **Conflict Resolution** - Smart handling of file conflicts (skip/overwrite/rename)
|
|
29
|
-
- 📄 **File Preview** - View markdown
|
|
32
|
+
- 📄 **File Preview** - View markdown with glow, code with vim/nano, peek inside archives
|
|
33
|
+
- 📦 **Archive Peek** - Preview contents of .zip, .tar.gz, .7z, .rar files without extracting
|
|
30
34
|
|
|
31
35
|
### Search & Productivity
|
|
32
36
|
- 🔎 **Fuzzy Search** - Integrate with fzf for fast file finding
|
|
@@ -65,6 +69,7 @@ sudo dnf install ncurses-devel ruby-devel
|
|
|
65
69
|
### Optional Tools
|
|
66
70
|
- **glow** - For beautiful markdown preview (`brew install glow` or `go install github.com/charmbracelet/glow@latest`)
|
|
67
71
|
- **fzf** - For fuzzy file search (`brew install fzf` or `sudo apt-get install fzf`)
|
|
72
|
+
- **Archive tools** - For archive preview: `unzip`, `tar`, `7z`, `unrar` (usually pre-installed on most systems)
|
|
68
73
|
|
|
69
74
|
## 🚀 Installation
|
|
70
75
|
|
|
@@ -113,16 +118,6 @@ sgt
|
|
|
113
118
|
# Run with explicit ruby (temporary fix)
|
|
114
119
|
ruby $(which sgt)
|
|
115
120
|
```
|
|
116
|
-
|
|
117
|
-
**Alternative - Automated fix script:**
|
|
118
|
-
```bash
|
|
119
|
-
# Creates a wrapper script (requires cloning repo)
|
|
120
|
-
cd Sergeant
|
|
121
|
-
bash arch_fix.sh
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
**For detailed troubleshooting**, see [INSTALL_TROUBLESHOOTING.md](./INSTALL_TROUBLESHOOTING.md)
|
|
125
|
-
|
|
126
121
|
### Development Installation
|
|
127
122
|
|
|
128
123
|
If you want to work on the gem:
|
|
@@ -165,7 +160,9 @@ sgt
|
|
|
165
160
|
| `d` | Delete marked items (with confirmation) |
|
|
166
161
|
| `r` | Rename current item |
|
|
167
162
|
| `u` | Unmark all items |
|
|
168
|
-
| `
|
|
163
|
+
| `n` | Create new file or directory |
|
|
164
|
+
| `e` | Edit file with $EDITOR (or nano/nvim/vim) |
|
|
165
|
+
| `v` | Preview file or archive contents |
|
|
169
166
|
|
|
170
167
|
### Other Commands
|
|
171
168
|
|
|
@@ -175,9 +172,11 @@ sgt
|
|
|
175
172
|
| `↓/j` | Move down |
|
|
176
173
|
| `Enter/→/l` | Open directory or preview file |
|
|
177
174
|
| `←/h` | Go to parent directory |
|
|
175
|
+
| `f` | Filter current directory view |
|
|
176
|
+
| `/` | Search files (requires fzf) |
|
|
177
|
+
| `:` | Execute terminal command in current directory |
|
|
178
178
|
| `o` | Toggle ownership/permissions display |
|
|
179
179
|
| `b` | Go to bookmark |
|
|
180
|
-
| `/` | Search files (requires fzf) |
|
|
181
180
|
| `m` | Show help modal with all key mappings |
|
|
182
181
|
| `q/ESC` | Quit and cd to current directory |
|
|
183
182
|
|
|
@@ -23,6 +23,9 @@ module Sergeant
|
|
|
23
23
|
if editor
|
|
24
24
|
# Use user's preferred editor
|
|
25
25
|
system("#{editor} \"#{file_path}\"")
|
|
26
|
+
elsif Gem.win_platform?
|
|
27
|
+
# Windows: use notepad (always available)
|
|
28
|
+
system("notepad \"#{file_path}\"")
|
|
26
29
|
elsif nvim_available?
|
|
27
30
|
# Second fallback: nvim (modern vim)
|
|
28
31
|
system("nvim \"#{file_path}\"")
|
|
@@ -65,6 +68,13 @@ module Sergeant
|
|
|
65
68
|
return unless item && item[:type] == :file
|
|
66
69
|
|
|
67
70
|
file_path = item[:path]
|
|
71
|
+
file_ext = File.extname(file_path).downcase
|
|
72
|
+
|
|
73
|
+
# Check if it's an archive file
|
|
74
|
+
if archive_file?(file_ext)
|
|
75
|
+
preview_archive(file_path, file_ext)
|
|
76
|
+
return
|
|
77
|
+
end
|
|
68
78
|
|
|
69
79
|
# Check if it's a text file
|
|
70
80
|
unless text_file?(file_path)
|
|
@@ -76,10 +86,11 @@ module Sergeant
|
|
|
76
86
|
close_screen
|
|
77
87
|
|
|
78
88
|
begin
|
|
79
|
-
|
|
80
|
-
|
|
89
|
+
if Gem.win_platform?
|
|
90
|
+
# Windows: use notepad for preview (simpler and always works)
|
|
91
|
+
system("notepad \"#{file_path}\"")
|
|
81
92
|
# Use glow for markdown files if available, otherwise fall back to less
|
|
82
|
-
|
|
93
|
+
elsif file_ext == '.md' && glow_available?
|
|
83
94
|
system("glow -p \"#{file_path}\"")
|
|
84
95
|
elsif file_ext == '.md'
|
|
85
96
|
system("less -R -F -X \"#{file_path}\"")
|
|
@@ -651,6 +662,80 @@ module Sergeant
|
|
|
651
662
|
# Force refresh to show any changes from the command
|
|
652
663
|
force_refresh
|
|
653
664
|
end
|
|
665
|
+
|
|
666
|
+
private
|
|
667
|
+
|
|
668
|
+
def archive_file?(ext)
|
|
669
|
+
%w[.zip .tar .gz .bz2 .xz .7z .rar .tar.gz .tar.bz2 .tar.xz .tgz .tbz .txz].include?(ext) ||
|
|
670
|
+
ext.end_with?('.tar.gz', '.tar.bz2', '.tar.xz')
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
def preview_archive(file_path, file_ext)
|
|
674
|
+
close_screen
|
|
675
|
+
|
|
676
|
+
puts "Archive Preview: #{File.basename(file_path)}"
|
|
677
|
+
puts '─' * 80
|
|
678
|
+
puts
|
|
679
|
+
|
|
680
|
+
begin
|
|
681
|
+
# Detect archive type and use appropriate command
|
|
682
|
+
case file_ext
|
|
683
|
+
when '.zip'
|
|
684
|
+
system("unzip -l \"#{file_path}\" | less -R -F -X")
|
|
685
|
+
when '.tar'
|
|
686
|
+
system("tar -tvf \"#{file_path}\" | less -R -F -X")
|
|
687
|
+
when '.tar.gz', '.tgz', '.gz'
|
|
688
|
+
if file_path.end_with?('.tar.gz') || file_path.end_with?('.tgz')
|
|
689
|
+
system("tar -tzf \"#{file_path}\" | less -R -F -X")
|
|
690
|
+
else
|
|
691
|
+
system("gzip -l \"#{file_path}\"")
|
|
692
|
+
puts
|
|
693
|
+
puts 'Press Enter to continue...'
|
|
694
|
+
gets
|
|
695
|
+
end
|
|
696
|
+
when '.tar.bz2', '.tbz', '.bz2'
|
|
697
|
+
if file_path.end_with?('.tar.bz2') || file_path.end_with?('.tbz')
|
|
698
|
+
system("tar -tjf \"#{file_path}\" | less -R -F -X")
|
|
699
|
+
else
|
|
700
|
+
system("bzip2 -l \"#{file_path}\" 2>/dev/null || echo 'bzip2 does not support -l flag'")
|
|
701
|
+
puts
|
|
702
|
+
puts 'Press Enter to continue...'
|
|
703
|
+
gets
|
|
704
|
+
end
|
|
705
|
+
when '.tar.xz', '.txz', '.xz'
|
|
706
|
+
if file_path.end_with?('.tar.xz') || file_path.end_with?('.txz')
|
|
707
|
+
system("tar -tJf \"#{file_path}\" | less -R -F -X")
|
|
708
|
+
else
|
|
709
|
+
system("xz -l \"#{file_path}\"")
|
|
710
|
+
puts
|
|
711
|
+
puts 'Press Enter to continue...'
|
|
712
|
+
gets
|
|
713
|
+
end
|
|
714
|
+
when '.7z'
|
|
715
|
+
system("7z l \"#{file_path}\" | less -R -F -X")
|
|
716
|
+
when '.rar'
|
|
717
|
+
system("unrar l \"#{file_path}\" | less -R -F -X")
|
|
718
|
+
else
|
|
719
|
+
puts 'Archive type detected but no preview command available'
|
|
720
|
+
puts 'Press Enter to continue...'
|
|
721
|
+
gets
|
|
722
|
+
end
|
|
723
|
+
rescue StandardError => e
|
|
724
|
+
puts "Error previewing archive: #{e.message}"
|
|
725
|
+
puts 'Press Enter to continue...'
|
|
726
|
+
gets
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
# Restore curses screen
|
|
730
|
+
init_screen
|
|
731
|
+
if has_colors?
|
|
732
|
+
start_color
|
|
733
|
+
apply_color_theme
|
|
734
|
+
end
|
|
735
|
+
curs_set(0)
|
|
736
|
+
noecho
|
|
737
|
+
stdscr.keypad(true)
|
|
738
|
+
end
|
|
654
739
|
end
|
|
655
740
|
end
|
|
656
741
|
end
|
data/lib/sergeant/modals/help.rb
CHANGED
|
@@ -59,13 +59,16 @@ module Sergeant
|
|
|
59
59
|
' u - Unmark all items',
|
|
60
60
|
' n - Create new file or directory',
|
|
61
61
|
'',
|
|
62
|
+
'View & Search:',
|
|
63
|
+
' e - Edit file ($EDITOR, nano, nvim, vim)',
|
|
64
|
+
' v - Preview file or archive contents',
|
|
65
|
+
' f - Filter current directory view',
|
|
66
|
+
' / - Search files (with fzf if available)',
|
|
67
|
+
'',
|
|
62
68
|
'Other:',
|
|
63
69
|
' : - Execute terminal command',
|
|
64
|
-
' e - Edit file ($EDITOR, nano, nvim, vim)',
|
|
65
|
-
' v - Preview file (read-only)',
|
|
66
70
|
' o - Toggle ownership display',
|
|
67
71
|
' b - Go to bookmark',
|
|
68
|
-
' / - Search files (with fzf if available)',
|
|
69
72
|
' q / ESC - Quit and cd to current directory'
|
|
70
73
|
]
|
|
71
74
|
|
|
@@ -240,6 +240,130 @@ module Sergeant
|
|
|
240
240
|
getch
|
|
241
241
|
true
|
|
242
242
|
end
|
|
243
|
+
|
|
244
|
+
def filter_current_view
|
|
245
|
+
max_y = lines
|
|
246
|
+
max_x = cols
|
|
247
|
+
|
|
248
|
+
modal_height = 8
|
|
249
|
+
modal_width = [70, max_x - 4].min
|
|
250
|
+
modal_y = (max_y - modal_height) / 2
|
|
251
|
+
modal_x = (max_x - modal_width) / 2
|
|
252
|
+
|
|
253
|
+
# Draw modal
|
|
254
|
+
(modal_y..(modal_y + modal_height)).each do |y|
|
|
255
|
+
setpos(y, modal_x)
|
|
256
|
+
attron(color_pair(3)) do
|
|
257
|
+
addstr(' ' * modal_width)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
setpos(modal_y, modal_x)
|
|
262
|
+
attron(color_pair(4) | Curses::A_BOLD) do
|
|
263
|
+
addstr("\u250C#{'─' * (modal_width - 2)}\u2510")
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
setpos(modal_y + 1, modal_x)
|
|
267
|
+
attron(color_pair(4) | Curses::A_BOLD) do
|
|
268
|
+
addstr('│')
|
|
269
|
+
end
|
|
270
|
+
attron(color_pair(5) | Curses::A_BOLD) do
|
|
271
|
+
addstr(' Filter Current View '.center(modal_width - 2))
|
|
272
|
+
end
|
|
273
|
+
attron(color_pair(4) | Curses::A_BOLD) do
|
|
274
|
+
addstr('│')
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
setpos(modal_y + 2, modal_x)
|
|
278
|
+
attron(color_pair(4)) do
|
|
279
|
+
addstr("\u251C#{'─' * (modal_width - 2)}\u2524")
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
setpos(modal_y + 3, modal_x)
|
|
283
|
+
attron(color_pair(4)) do
|
|
284
|
+
addstr('│ ')
|
|
285
|
+
end
|
|
286
|
+
addstr('Enter text to filter files/folders (ESC to clear):'.ljust(modal_width - 4))
|
|
287
|
+
attron(color_pair(4)) do
|
|
288
|
+
addstr(' │')
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
setpos(modal_y + 4, modal_x)
|
|
292
|
+
attron(color_pair(4)) do
|
|
293
|
+
addstr('│ ')
|
|
294
|
+
end
|
|
295
|
+
prompt = 'Filter: '
|
|
296
|
+
attron(color_pair(5)) do
|
|
297
|
+
addstr(prompt)
|
|
298
|
+
end
|
|
299
|
+
addstr(' ' * (modal_width - 4 - prompt.length))
|
|
300
|
+
attron(color_pair(4)) do
|
|
301
|
+
addstr(' │')
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
setpos(modal_y + 6, modal_x)
|
|
305
|
+
attron(color_pair(4)) do
|
|
306
|
+
addstr('│')
|
|
307
|
+
end
|
|
308
|
+
attron(color_pair(4) | Curses::A_DIM) do
|
|
309
|
+
count = @all_items.length - (@all_items.any? { |i| i[:name] == '..' } ? 1 : 0)
|
|
310
|
+
addstr(" #{count} items in current directory ".center(modal_width - 2))
|
|
311
|
+
end
|
|
312
|
+
attron(color_pair(4)) do
|
|
313
|
+
addstr('│')
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
setpos(modal_y + modal_height - 1, modal_x)
|
|
317
|
+
attron(color_pair(4) | Curses::A_BOLD) do
|
|
318
|
+
addstr("\u2514#{'─' * (modal_width - 2)}\u2518")
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Input handling
|
|
322
|
+
curs_set(1)
|
|
323
|
+
echo
|
|
324
|
+
input_width = modal_width - 6 - prompt.length
|
|
325
|
+
filter_input = @filter_text.dup
|
|
326
|
+
|
|
327
|
+
setpos(modal_y + 4, modal_x + 2 + prompt.length)
|
|
328
|
+
addstr(filter_input.ljust(input_width))
|
|
329
|
+
setpos(modal_y + 4, modal_x + 2 + prompt.length + filter_input.length)
|
|
330
|
+
|
|
331
|
+
loop do
|
|
332
|
+
refresh
|
|
333
|
+
ch = getch
|
|
334
|
+
|
|
335
|
+
case ch
|
|
336
|
+
when 10, 13 # Enter
|
|
337
|
+
break
|
|
338
|
+
when 27 # ESC - clear filter
|
|
339
|
+
filter_input = ''
|
|
340
|
+
break
|
|
341
|
+
when 127, Curses::Key::BACKSPACE
|
|
342
|
+
if filter_input.length.positive?
|
|
343
|
+
filter_input = filter_input[0...-1]
|
|
344
|
+
setpos(modal_y + 4, modal_x + 2 + prompt.length)
|
|
345
|
+
addstr(filter_input.ljust(input_width))
|
|
346
|
+
setpos(modal_y + 4, modal_x + 2 + prompt.length + filter_input.length)
|
|
347
|
+
end
|
|
348
|
+
else
|
|
349
|
+
if ch.is_a?(String) && filter_input.length < input_width
|
|
350
|
+
filter_input += ch
|
|
351
|
+
setpos(modal_y + 4, modal_x + 2 + prompt.length)
|
|
352
|
+
addstr(filter_input.ljust(input_width))
|
|
353
|
+
setpos(modal_y + 4, modal_x + 2 + prompt.length + filter_input.length)
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
noecho
|
|
359
|
+
curs_set(0)
|
|
360
|
+
|
|
361
|
+
# Apply filter
|
|
362
|
+
@filter_text = filter_input.strip
|
|
363
|
+
@selected_index = 0
|
|
364
|
+
@scroll_offset = 0
|
|
365
|
+
force_refresh # Force refresh to apply filter
|
|
366
|
+
end
|
|
243
367
|
end
|
|
244
368
|
end
|
|
245
369
|
end
|
data/lib/sergeant/rendering.rb
CHANGED
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
module Sergeant
|
|
6
6
|
module Rendering
|
|
7
|
+
# Use ASCII icons on Windows for better terminal compatibility
|
|
8
|
+
WINDOWS = Gem.win_platform?
|
|
9
|
+
ICON_DIR = WINDOWS ? '[D] ' : '📁 '
|
|
10
|
+
ICON_FILE = WINDOWS ? '[F] ' : '📄 '
|
|
11
|
+
ICON_MARK = WINDOWS ? '* ' : '✓ '
|
|
12
|
+
ICON_SELECT = WINDOWS ? '> ' : '▶ '
|
|
13
|
+
|
|
7
14
|
def draw_screen
|
|
8
15
|
clear
|
|
9
16
|
|
|
@@ -20,11 +27,19 @@ module Sergeant
|
|
|
20
27
|
|
|
21
28
|
# Build status info
|
|
22
29
|
status_parts = []
|
|
23
|
-
|
|
30
|
+
unless @marked_items.empty?
|
|
31
|
+
total_size = @marked_items.sum { |path| File.size(path) rescue 0 }
|
|
32
|
+
size_str = format_size(total_size)
|
|
33
|
+
status_parts << "Marked: #{@marked_items.length} (#{size_str.strip})"
|
|
34
|
+
end
|
|
24
35
|
unless @copied_items.empty?
|
|
25
36
|
mode_text = @cut_mode ? 'Cut' : 'Copied'
|
|
26
37
|
status_parts << "#{mode_text}: #{@copied_items.length}"
|
|
27
38
|
end
|
|
39
|
+
unless @filter_text.empty?
|
|
40
|
+
filtered_count = @items.length - (@items.any? { |i| i[:name] == '..' } ? 1 : 0)
|
|
41
|
+
status_parts << "Filter: '#{@filter_text}' (#{filtered_count})"
|
|
42
|
+
end
|
|
28
43
|
status_text = status_parts.empty? ? '' : " | #{status_parts.join(' | ')}"
|
|
29
44
|
|
|
30
45
|
if branch
|
|
@@ -119,13 +134,13 @@ module Sergeant
|
|
|
119
134
|
end
|
|
120
135
|
|
|
121
136
|
def draw_item(item, max_x, is_selected)
|
|
122
|
-
icon = item[:type] == :directory ?
|
|
137
|
+
icon = item[:type] == :directory ? ICON_DIR : ICON_FILE
|
|
123
138
|
|
|
124
139
|
# Check if item is marked
|
|
125
140
|
is_marked = @marked_items.include?(item[:path])
|
|
126
|
-
mark_indicator = is_marked ?
|
|
141
|
+
mark_indicator = is_marked ? ICON_MARK : ' '
|
|
127
142
|
|
|
128
|
-
prefix = is_selected ?
|
|
143
|
+
prefix = is_selected ? ICON_SELECT : ' '
|
|
129
144
|
|
|
130
145
|
size_str = format_size(item[:size])
|
|
131
146
|
date_str = format_date(item[:mtime])
|
data/lib/sergeant/version.rb
CHANGED
data/lib/sergeant.rb
CHANGED
|
@@ -26,6 +26,7 @@ class SergeantApp
|
|
|
26
26
|
@selected_index = 0
|
|
27
27
|
@scroll_offset = 0
|
|
28
28
|
@show_ownership = false
|
|
29
|
+
@last_show_ownership = false
|
|
29
30
|
@config = Sergeant::Config.load_config
|
|
30
31
|
@bookmarks = Sergeant::Config.load_bookmarks
|
|
31
32
|
@marked_items = []
|
|
@@ -33,6 +34,8 @@ class SergeantApp
|
|
|
33
34
|
@cut_mode = false
|
|
34
35
|
@last_refreshed_dir = nil
|
|
35
36
|
@items = []
|
|
37
|
+
@filter_text = ''
|
|
38
|
+
@all_items = []
|
|
36
39
|
end
|
|
37
40
|
|
|
38
41
|
def run
|
|
@@ -99,6 +102,8 @@ class SergeantApp
|
|
|
99
102
|
execute_terminal_command
|
|
100
103
|
when '/'
|
|
101
104
|
search_files
|
|
105
|
+
when 'f'
|
|
106
|
+
filter_current_view
|
|
102
107
|
when 'q', 27
|
|
103
108
|
close_screen
|
|
104
109
|
puts @current_dir
|
|
@@ -194,9 +199,10 @@ class SergeantApp
|
|
|
194
199
|
def refresh_items_if_needed
|
|
195
200
|
# Only refresh if directory has changed, or if showing ownership toggle changed
|
|
196
201
|
# This prevents expensive file system operations on every keystroke
|
|
197
|
-
if @current_dir != @last_refreshed_dir
|
|
202
|
+
if @current_dir != @last_refreshed_dir || @show_ownership != @last_show_ownership
|
|
198
203
|
refresh_items
|
|
199
204
|
@last_refreshed_dir = @current_dir
|
|
205
|
+
@last_show_ownership = @show_ownership
|
|
200
206
|
end
|
|
201
207
|
end
|
|
202
208
|
|
|
@@ -206,8 +212,7 @@ class SergeantApp
|
|
|
206
212
|
end
|
|
207
213
|
|
|
208
214
|
def refresh_items
|
|
209
|
-
entries = Dir.entries(@current_dir).reject { |e| e == '.' }
|
|
210
|
-
|
|
215
|
+
entries = Dir.entries(@current_dir).reject { |e| e == '.' || e == '..' }
|
|
211
216
|
@items = []
|
|
212
217
|
|
|
213
218
|
unless @current_dir == '/'
|
|
@@ -229,9 +234,9 @@ class SergeantApp
|
|
|
229
234
|
full_path = File.join(@current_dir, entry)
|
|
230
235
|
begin
|
|
231
236
|
stat = File.stat(full_path)
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
perms = format_permissions(stat.mode, is_dir)
|
|
237
|
+
is_dir = stat.directory? # Use stat instead of File.directory? (saves syscall)
|
|
238
|
+
owner_info = @show_ownership ? get_owner_info(stat) : nil # Only fetch if needed
|
|
239
|
+
perms = @show_ownership ? format_permissions(stat.mode, is_dir) : nil
|
|
235
240
|
|
|
236
241
|
if is_dir
|
|
237
242
|
directories << {
|
|
@@ -262,15 +267,32 @@ class SergeantApp
|
|
|
262
267
|
files.sort_by! { |f| f[:name].downcase }
|
|
263
268
|
|
|
264
269
|
@items += directories + files
|
|
270
|
+
@all_items = @items.dup # Store all items for filtering
|
|
271
|
+
|
|
272
|
+
# Apply filter if active
|
|
273
|
+
apply_filter if @filter_text && !@filter_text.empty?
|
|
265
274
|
|
|
266
275
|
@selected_index = [@selected_index, @items.length - 1].min
|
|
267
276
|
@selected_index = 0 if @selected_index.negative?
|
|
268
277
|
end
|
|
269
278
|
|
|
279
|
+
def apply_filter
|
|
280
|
+
return if @filter_text.empty?
|
|
281
|
+
|
|
282
|
+
# Filter items by name (case-insensitive), keep '..' entry
|
|
283
|
+
@items = @all_items.select do |item|
|
|
284
|
+
item[:name] == '..' || item[:name].downcase.include?(@filter_text.downcase)
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
270
288
|
def move_selection(delta)
|
|
271
289
|
return if @items.empty?
|
|
272
290
|
|
|
273
291
|
@selected_index = (@selected_index + delta).clamp(0, @items.length - 1)
|
|
292
|
+
|
|
293
|
+
# Flush input buffer to prevent lag on Windows when holding arrow keys
|
|
294
|
+
# This clears any queued key-repeat events that accumulated during processing
|
|
295
|
+
Curses.flushinp
|
|
274
296
|
end
|
|
275
297
|
|
|
276
298
|
def toggle_mark
|
data/sergeant.gemspec
CHANGED
|
@@ -24,6 +24,8 @@ Gem::Specification.new do |spec|
|
|
|
24
24
|
`git ls-files -z`.split("\x0").reject do |f|
|
|
25
25
|
f.match(%r{\A(?:test|spec|features)/}) ||
|
|
26
26
|
f.match(%r{\A\.}) ||
|
|
27
|
+
f.match(%r{\.DS_Store$}) ||
|
|
28
|
+
f.match(%r{\.(gif|png|jpg|jpeg|mp4|webm)$}) || # Exclude media files
|
|
27
29
|
f == 'build.rb' ||
|
|
28
30
|
f == 'install.sh' ||
|
|
29
31
|
f == 'sgt.rb'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sergeant
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mateusz Grotha
|
|
@@ -66,12 +66,9 @@ files:
|
|
|
66
66
|
- LICENSE
|
|
67
67
|
- README.md
|
|
68
68
|
- bin/sgt
|
|
69
|
-
- lib/.DS_Store
|
|
70
69
|
- lib/sergeant.rb
|
|
71
|
-
- lib/sergeant/.DS_Store
|
|
72
70
|
- lib/sergeant/config.rb
|
|
73
71
|
- lib/sergeant/modals.rb
|
|
74
|
-
- lib/sergeant/modals/.DS_Store
|
|
75
72
|
- lib/sergeant/modals/dialogs.rb
|
|
76
73
|
- lib/sergeant/modals/file_operations.rb
|
|
77
74
|
- lib/sergeant/modals/help.rb
|
data/lib/.DS_Store
DELETED
|
Binary file
|
data/lib/sergeant/.DS_Store
DELETED
|
Binary file
|
|
Binary file
|