sergeant 1.0.6 → 1.0.7

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: 8121e483349ea026f5d4abec53dea0fc9a08b9201d43904c3a31e2a6667b457a
4
- data.tar.gz: e3067dbdafba6fe79d606662ea3ee27d85dcba40a290413a9c43c1c3195e2976
3
+ metadata.gz: 2c874d9cc43e11d9c891bf372f1324f188c7a1b5aebe5ee4ddcb71bb5db68c66
4
+ data.tar.gz: 2a47b52f0ae9fe27c7e02b13a74338b34c0db9a768bf36216048e838445843e3
5
5
  SHA512:
6
- metadata.gz: 52c9ede72d02a8d22f6ad98d5d7b8590c16adbfe4c5e3d907ea6e4dc52e90b6abf609a2a01de11d3c9d3556bc537f7adad40206ee0823f105a98bf53fc3418f0
7
- data.tar.gz: 8cf1a0e2467cd455513385bef1beceea416b0d347129eff30ef61162c4370623034031e8b623ce966d078db4075d79beac7003c0aae2586b3da1b0e93682b6e0
6
+ metadata.gz: d9b9b02b04e41563f2a623f64228b07f2de2a551480d1547a731e4d09d577033c2bb29ceb1c8c5b0b56d22000d6c8bb51c267fc6e415f07190badcde85b631fc
7
+ data.tar.gz: 7a23e6671b4e1c77231060e3dde2cde017d859ce8cfacff23c449166cc7b62b1c3df0bcec53f3243ab5c193d6c82de7b5c73dfe684f671c3db1f3679aeed32b6
data/CHANGELOG.md CHANGED
@@ -5,6 +5,15 @@ 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
+
9
+ ## [1.0.7] - 2026-03-09
10
+
11
+ ### Added
12
+ - **Progress bar for file operations**
13
+ - Copy, move, and delete now show a live progress modal
14
+ - Displays current filename, filled bar (█░), and X/N (%) counter
15
+ - Prevents UI from appearing frozen during large or multi-file operations
16
+
8
17
  ## [1.0.6] - 2026-01-16
9
18
 
10
19
  ### Added
@@ -348,6 +348,93 @@ module Sergeant
348
348
  end
349
349
  end
350
350
 
351
+ def draw_progress_modal(title, total)
352
+ max_y = lines
353
+ max_x = cols
354
+
355
+ modal_width = [60, max_x - 4].min
356
+ modal_height = 7
357
+ modal_y = (max_y - modal_height) / 2
358
+ modal_x = (max_x - modal_width) / 2
359
+
360
+ @progress_modal = { y: modal_y, x: modal_x, width: modal_width, total: total }
361
+
362
+ # Draw background
363
+ (modal_y..(modal_y + modal_height)).each do |y|
364
+ setpos(y, modal_x)
365
+ attron(color_pair(3)) { addstr(' ' * modal_width) }
366
+ end
367
+
368
+ # Top border
369
+ setpos(modal_y, modal_x)
370
+ attron(color_pair(4) | Curses::A_BOLD) do
371
+ addstr("\u250C#{'─' * (modal_width - 2)}\u2510")
372
+ end
373
+
374
+ # Title row
375
+ setpos(modal_y + 1, modal_x)
376
+ attron(color_pair(4) | Curses::A_BOLD) { addstr('│') }
377
+ attron(color_pair(5) | Curses::A_BOLD) { addstr(" #{title} ".center(modal_width - 2)) }
378
+ attron(color_pair(4) | Curses::A_BOLD) { addstr('│') }
379
+
380
+ # Separator
381
+ setpos(modal_y + 2, modal_x)
382
+ attron(color_pair(4)) { addstr("\u251C#{'─' * (modal_width - 2)}\u2524") }
383
+
384
+ # Filename row (blank initially)
385
+ setpos(modal_y + 3, modal_x)
386
+ attron(color_pair(4)) { addstr('│ ') }
387
+ addstr(' ' * (modal_width - 4))
388
+ attron(color_pair(4)) { addstr(' │') }
389
+
390
+ # Spacer row
391
+ setpos(modal_y + 4, modal_x)
392
+ attron(color_pair(4)) { addstr("\u2502#{' ' * (modal_width - 2)}\u2502") }
393
+
394
+ # Progress bar row (blank initially)
395
+ setpos(modal_y + 5, modal_x)
396
+ attron(color_pair(4)) { addstr('│ ') }
397
+ addstr(' ' * (modal_width - 4))
398
+ attron(color_pair(4)) { addstr(' │') }
399
+
400
+ # Bottom border
401
+ setpos(modal_y + 6, modal_x)
402
+ attron(color_pair(4) | Curses::A_BOLD) do
403
+ addstr("\u2514#{'─' * (modal_width - 2)}\u2518")
404
+ end
405
+
406
+ refresh
407
+ end
408
+
409
+ def update_progress_modal(current, total, filename)
410
+ return unless @progress_modal
411
+
412
+ modal_y = @progress_modal[:y]
413
+ modal_x = @progress_modal[:x]
414
+ modal_width = @progress_modal[:width]
415
+ inner_width = modal_width - 4 # between '│ ' and ' │'
416
+
417
+ # Update filename line
418
+ setpos(modal_y + 3, modal_x + 2)
419
+ display_name = filename.to_s
420
+ display_name = "...#{display_name[-(inner_width - 3)..]}" if display_name.length > inner_width
421
+ addstr(display_name.ljust(inner_width))
422
+
423
+ # Build progress bar
424
+ percent = total > 0 ? (current.to_f / total * 100).round : 0
425
+ count_str = "#{current}/#{total} (#{percent}%)"
426
+ bar_outer = [inner_width - count_str.length - 1, 3].max # 1 space between bar and count
427
+ bar_inner = bar_outer - 2 # subtract [ and ]
428
+ filled = total > 0 ? (current.to_f / total * bar_inner).round : 0
429
+ empty = bar_inner - filled
430
+
431
+ bar = "[#{'█' * filled}#{'░' * empty}] #{count_str}"
432
+ setpos(modal_y + 5, modal_x + 2)
433
+ addstr(bar.ljust(inner_width))
434
+
435
+ refresh
436
+ end
437
+
351
438
  def show_error_with_retry(filepath, error_message)
352
439
  max_y = lines
353
440
  max_x = cols
@@ -131,11 +131,18 @@ module Sergeant
131
131
  error_count = 0
132
132
  errors = []
133
133
 
134
+ total = @copied_items.count { |p| File.exist?(p) }
135
+ operation = @cut_mode ? 'Moving' : 'Copying'
136
+ draw_progress_modal("#{operation} Files", total)
137
+ current = 0
138
+
134
139
  @copied_items.each do |source_path|
135
140
  next unless File.exist?(source_path)
136
141
 
137
142
  filename = File.basename(source_path)
138
143
  dest_path = File.join(@current_dir, filename)
144
+ current += 1
145
+ update_progress_modal(current, total, filename)
139
146
 
140
147
  begin
141
148
  if File.exist?(dest_path)
@@ -167,6 +174,8 @@ module Sergeant
167
174
  end
168
175
  end
169
176
 
177
+ @progress_modal = nil
178
+
170
179
  # Clean up after operation
171
180
  @marked_items.clear
172
181
  @copied_items.clear
@@ -204,19 +213,28 @@ module Sergeant
204
213
  error_count = 0
205
214
  errors = []
206
215
 
216
+ total = @marked_items.count { |p| File.exist?(p) }
217
+ draw_progress_modal('Deleting Files', total)
218
+ current = 0
219
+
207
220
  @marked_items.each do |item_path|
208
221
  next unless File.exist?(item_path)
209
222
 
223
+ filename = File.basename(item_path)
224
+ current += 1
225
+ update_progress_modal(current, total, filename)
226
+
210
227
  begin
211
228
  FileUtils.rm_rf(item_path)
212
229
  success_count += 1
213
230
  rescue StandardError => e
214
231
  error_count += 1
215
- filename = File.basename(item_path)
216
232
  errors << "#{filename}: #{e.message}"
217
233
  end
218
234
  end
219
235
 
236
+ @progress_modal = nil
237
+
220
238
  # Clear marked items after deletion
221
239
  @marked_items.clear
222
240
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sergeant
4
- VERSION = '1.0.6'
4
+ VERSION = '1.0.7'
5
5
  end
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.6
4
+ version: 1.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mateusz Grotha