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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f8e44aa17efd4cddd56d88d910edc0082e94488cd3d25cf6def3b832a579c3d
4
- data.tar.gz: c66899a4748192ded5481c0497a81ebb81edad956a932d6253beab34ab834a67
3
+ metadata.gz: 9a87751fe5e98dee814b470aa90818bd0f798cbdb4143f55522ffcf610d8de0c
4
+ data.tar.gz: d1c47033ac959cc1bbd50af0fe96e526df4d2f72077efc914d0570b8df6e0e7b
5
5
  SHA512:
6
- metadata.gz: 1590f36daa589471edc9f65f150b24b9764ef192b77d46cc51ffc1884e01dafa2fd9a4832a5fef635dfef8bca12f3d16f49b305a295a4fd8e6159e50529d9127
7
- data.tar.gz: 8ee3a3f6eacd45f3a91c42b7a96e94ccc0961c12cbd3b363ee8a92553efe4db4db73be8499d5304d54702b82333c50fa29b27ad0f87a6f2e141a0a9ca4dad3f2
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
  ![Sergeant Logo](./logo.svg)
4
+ ![highlight](./highlight.gif)
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 files with glow, code files with vim/nano
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
- | `v` | Preview file (markdown/code) |
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
- file_ext = File.extname(file_path).downcase
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
- if file_ext == '.md' && glow_available?
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
@@ -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
@@ -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
- status_parts << "Marked: #{@marked_items.length}" unless @marked_items.empty?
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])
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sergeant
4
- VERSION = '1.0.2'
4
+ VERSION = '1.0.4'
5
5
  end
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
- owner_info = get_owner_info(stat)
233
- is_dir = File.directory?(full_path)
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.2
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
Binary file
Binary file