rtfm-filemanager 8.2.1 → 8.2.2

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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/bin/rtfm +77 -65
  3. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eb02bbf2f12ccc66afd6d2fbbef68f6e7a1a05f3d932e48a8143ae8df1a70d39
4
- data.tar.gz: e1a1583ccab118308ecf16d898631dff1fcddedb3b46f7462ba2a8486cfad5d1
3
+ metadata.gz: 70a9b068989497362fa5d26c2a31cc1f839af5364ccf978b0f99f68707159255
4
+ data.tar.gz: f7e15cbab0c75bef8fc163eb179b60559ed08fc2f87244eb0e7ed703758c4581
5
5
  SHA512:
6
- metadata.gz: 8eaeb2806d03dd15601d705bc409b677cbbb3bbe08aa69a1cd8b9135bbf4b5ecfc95c8140b4c9d9e526eb4652fa9057f4102aec05058d6c94272b39027d3e33a
7
- data.tar.gz: f9cb7747b1c773a2c545bb1afaa600ceff1a04d13149d9a98cae029687a37cbeccb0b330951659463fd666206104234d5ae0f6298c5cef2ff01d3950be9063ae
6
+ metadata.gz: 664d2c0a068716ff76cd10bd525dbc6383b5e8073d696db8d268a2927eaf6050affdcd0c359fbb0be8544612c3591804975a9847594af09392f12de389c76b5b
7
+ data.tar.gz: c02dce8c6556406b65b0ff87b9062f4f45256d4feccc0fbcf1c8fc2404c84708acdaae8f87b2ddd929377494691accd61d519ca8b964481ed7d686d08ee7d6e1
data/bin/rtfm CHANGED
@@ -18,7 +18,7 @@
18
18
  # get a great understanding of the code itself by simply sending
19
19
  # or pasting this whole file into you favorite AI for coding with
20
20
  # a prompt like this: "Help me understand every part of this code".
21
- @version = '8.2.1' # Trash browser, right pane scroll fix, plugin improvements
21
+ @version = '8.2.2' # Replace bare rescues with typed exceptions
22
22
 
23
23
  # SAVE & STORE TERMINAL {{{1
24
24
  ORIG_STTY = `stty -g`.chomp
@@ -61,7 +61,7 @@ def check_image_redraw # {{{2
61
61
  end
62
62
  end
63
63
  # Kitty and Sixel protocols don't need redraw checks - images persist
64
- rescue
64
+ rescue StandardError # non-fatal image redraw check
65
65
  # Silently fail - we don't want focus checking to break anything
66
66
  end
67
67
  end
@@ -682,7 +682,7 @@ def restore_tab_state # {{{2
682
682
 
683
683
  begin
684
684
  Dir.chdir(tab[:directory]) if Dir.exist?(tab[:directory])
685
- rescue
685
+ rescue Errno::ENOENT, Errno::EACCES # permission denied or missing dir
686
686
  # If directory doesn't exist, go to home
687
687
  Dir.chdir
688
688
  tab[:directory] = Dir.pwd
@@ -910,7 +910,7 @@ if File.exist?(PREVIEW_FILE)
910
910
  @plugin_errors << "Invalid preview.rb line #{idx + 1}: #{line}"
911
911
  end
912
912
  end
913
- rescue => e
913
+ rescue StandardError => e # malformed preview config
914
914
  @plugin_errors << "Error loading preview.rb: #{e.class}: #{e.message}"
915
915
  end
916
916
  end
@@ -1075,7 +1075,7 @@ KEYMAP = { # {{{2
1075
1075
  if File.exist?(KEYS_FILE)
1076
1076
  begin
1077
1077
  load KEYS_FILE
1078
- rescue => e
1078
+ rescue StandardError => e # broken key binding config
1079
1079
  @plugin_errors << "Error loading keys.rb: #{e.class}: #{e.message}"
1080
1080
  end
1081
1081
  end
@@ -1114,7 +1114,7 @@ def load_plugin(name)
1114
1114
  snapshot.each { |k, v| changed[k] = v unless KEYMAP.key?(k) }
1115
1115
  p[:saved_keys] = changed
1116
1116
  p[:enabled] = true
1117
- rescue => e
1117
+ rescue StandardError => e # plugin load failure
1118
1118
  @plugin_errors << "Error loading plugin #{name}: #{e.class}: #{e.message}"
1119
1119
  end
1120
1120
  end
@@ -1248,7 +1248,7 @@ def refresh_all # {{{3
1248
1248
  if new_h && new_w && new_h >= 10 && new_w >= 20
1249
1249
  @h, @w = new_h, new_w
1250
1250
  end
1251
- rescue
1251
+ rescue StandardError # terminal size query failed
1252
1252
  # Keep current size if we can't read new size
1253
1253
  end
1254
1254
 
@@ -1346,7 +1346,7 @@ def change_width # {{{3
1346
1346
  @width = 2 if @width == 8
1347
1347
 
1348
1348
  # Persist width setting
1349
- File.write(File.join(RTFM_HOME, 'width'), @width.to_s) rescue nil
1349
+ File.write(File.join(RTFM_HOME, 'width'), @width.to_s) rescue nil # ignore write error
1350
1350
 
1351
1351
  if @dual_pane
1352
1352
  # Show width setting info for dual-pane mode using the new ratio calculation
@@ -1589,7 +1589,7 @@ def jump_to_mark # {{{3
1589
1589
  if m =~ /[\w']/ && @marks[m]
1590
1590
  @directory[Dir.pwd] = @index
1591
1591
  dir_before = Dir.pwd
1592
- begin; Dir.chdir(@marks[m]); track_directory_access(@marks[m]); rescue; @pB.say(' No such directory'); end
1592
+ begin; Dir.chdir(@marks[m]); track_directory_access(@marks[m]); rescue Errno::ENOENT, Errno::EACCES; @pB.say(' No such directory'); end
1593
1593
  mark_latest
1594
1594
  @marks["'"] = dir_before
1595
1595
  end
@@ -1692,7 +1692,7 @@ def follow_symlink # {{{3
1692
1692
 
1693
1693
  # Clear history after successful navigation
1694
1694
  @symlink_history.clear
1695
- rescue => e
1695
+ rescue StandardError => e # symlink resolution failure
1696
1696
  @pB.say("Error following symlink: #{e}")
1697
1697
  @symlink_history&.clear
1698
1698
  end
@@ -1755,7 +1755,7 @@ def tag_current # {{{3
1755
1755
  else
1756
1756
  @tagged.push(item); @tagsize += File.size(item) rescue 0
1757
1757
  end
1758
-
1758
+
1759
1759
  # Advance to next item in the active pane
1760
1760
  max_index = current_files.size - 1
1761
1761
  if @active_pane == :left
@@ -2124,7 +2124,7 @@ def show_file_properties # {{{3
2124
2124
  mime_output = `file --mime-type #{Shellwords.escape(@selected)} 2>/dev/null`.strip
2125
2125
  mime_type = mime_output.split(':')[1]&.strip || "Unknown"
2126
2126
  text << sprintf(" %-20s %s\n", "MIME Type:", mime_type.fg(156))
2127
- rescue
2127
+ rescue StandardError # file command failed
2128
2128
  text << sprintf(" %-20s %s\n", "MIME Type:", "Unknown".fg(240))
2129
2129
  end
2130
2130
 
@@ -2143,7 +2143,7 @@ def show_file_properties # {{{3
2143
2143
  owner = Etc.getpwuid(stat.uid).name rescue stat.uid.to_s
2144
2144
  group = Etc.getgrgid(stat.gid).name rescue stat.gid.to_s
2145
2145
  text << sprintf(" %-20s %s\n", "Owner:Group:", "#{owner}:#{group}".fg(156))
2146
- rescue
2146
+ rescue StandardError # uid/gid lookup failed
2147
2147
  text << sprintf(" %-20s %s\n", "Owner:Group:", "#{stat.uid}:#{stat.gid}".fg(156))
2148
2148
  end
2149
2149
 
@@ -2163,7 +2163,7 @@ def show_file_properties # {{{3
2163
2163
  target = File.readlink(@selected)
2164
2164
  text << sprintf(" %-20s %s\n", "Points to:", target.fg(156))
2165
2165
  text << sprintf(" %-20s %s\n", "Target exists:", File.exist?(target) ? "Yes".fg(156) : "No".fg(196))
2166
- rescue
2166
+ rescue Errno::ENOENT, Errno::EACCES # permission denied or missing symlink target
2167
2167
  text << sprintf(" %-20s %s\n", "Target:", "Cannot read link".fg(196))
2168
2168
  end
2169
2169
  end
@@ -2178,7 +2178,7 @@ def show_file_properties # {{{3
2178
2178
  dirs = entries.select { |e| File.directory?(File.join(@selected, e)) }
2179
2179
  files = entries.select { |e| File.file?(File.join(@selected, e)) }
2180
2180
  text << sprintf(" %-20s %s\n", "Breakdown:", "#{dirs.length} directories, #{files.length} files")
2181
- rescue
2181
+ rescue Errno::EACCES, Errno::ENOENT # permission denied or missing dir
2182
2182
  text << sprintf(" %-20s %s\n", "Contents:", "Cannot read directory".fg(196))
2183
2183
  end
2184
2184
  end
@@ -2194,7 +2194,7 @@ def show_file_properties # {{{3
2194
2194
  require 'digest'
2195
2195
  checksum = Digest::SHA256.file(@selected).hexdigest[0, 16]
2196
2196
  text << sprintf(" %-20s %s...\n", "SHA256 (partial):", checksum.fg(156))
2197
- rescue
2197
+ rescue Errno::EACCES, Errno::ENOENT # permission denied or missing file
2198
2198
  text << sprintf(" %-20s %s\n", "Checksum:", "Cannot calculate".fg(240))
2199
2199
  end
2200
2200
  else
@@ -2209,7 +2209,7 @@ def show_file_properties # {{{3
2209
2209
  (chunk.bytes.any? { |b| b < 32 && ![9, 10, 13].include?(b) })
2210
2210
  text << sprintf(" %-20s %s\n", "Content type:", is_binary ? "Binary".fg(196) : "Text".fg(156))
2211
2211
  end
2212
- rescue
2212
+ rescue Errno::EACCES, Errno::ENOENT # permission denied or missing file
2213
2213
  text << sprintf(" %-20s %s\n", "Content type:", "Unknown".fg(240))
2214
2214
  end
2215
2215
  end
@@ -2578,7 +2578,7 @@ def show_binary_comparison(file1, file2, stat1, stat2) # {{{3
2578
2578
  text << "\n File types:\n".fg(226)
2579
2579
  text << " #{File.basename(file1)}: #{type1.split(':')[1]&.strip || 'Unknown'}\n".fg(240)
2580
2580
  text << " #{File.basename(file2)}: #{type2.split(':')[1]&.strip || 'Unknown'}\n".fg(240)
2581
- rescue
2581
+ rescue StandardError # file type detection failed
2582
2582
  # Ignore file type detection errors
2583
2583
  end
2584
2584
 
@@ -2792,7 +2792,7 @@ def files_identical?(file1, file2) # {{{3
2792
2792
  end
2793
2793
 
2794
2794
  true
2795
- rescue
2795
+ rescue Errno::EACCES, Errno::ENOENT, Errno::EISDIR # permission denied or missing file
2796
2796
  false
2797
2797
  end
2798
2798
 
@@ -2808,7 +2808,7 @@ def binary_file?(file) # {{{3
2808
2808
 
2809
2809
  null_count > 0 || (non_printable.to_f / chunk.length) > 0.3
2810
2810
  end
2811
- rescue
2811
+ rescue Errno::EACCES, Errno::ENOENT, Errno::EISDIR # permission denied or missing file
2812
2812
  true # Assume binary if we can't read it
2813
2813
  end
2814
2814
 
@@ -2924,7 +2924,7 @@ def parse_archive_listing(archive_path) # {{{3
2924
2924
  else
2925
2925
  parse_tar_listing(raw, entries)
2926
2926
  end
2927
- rescue => e
2927
+ rescue StandardError => e # archive format or read error
2928
2928
  @pB.say("Error reading archive: #{e.message}".fg(196))
2929
2929
  end
2930
2930
  entries
@@ -3064,6 +3064,7 @@ end
3064
3064
  def enter_archive_mode(archive_path) # {{{3
3065
3065
  @archive_origin_dir = Dir.pwd
3066
3066
  @archive_origin_tagged = @tagged.dup
3067
+ @archive_origin_index = @index
3067
3068
  @archive_mode = true
3068
3069
  @archive_path = archive_path
3069
3070
  @archive_current_dir = ''
@@ -3093,7 +3094,8 @@ def exit_archive_mode # {{{3
3093
3094
  @tagged = @archive_origin_tagged || []
3094
3095
  @archive_origin_tagged = []
3095
3096
  @archive_origin_dir = nil
3096
- @index = 0
3097
+ @index = @archive_origin_index || 0
3098
+ @archive_origin_index = nil
3097
3099
  @pB.say("Returned to local browsing".fg(156))
3098
3100
  @pL.update = @pR.update = @pT.update = @pB.update = true
3099
3101
  dirlist
@@ -3645,7 +3647,7 @@ def upload_tagged_files # {{{3
3645
3647
  failed_count += 1
3646
3648
  @pB.say("✗ Failed to upload #{File.basename(local_file)}".fg(196))
3647
3649
  end
3648
- rescue => e
3650
+ rescue StandardError => e # upload failure
3649
3651
  failed_count += 1
3650
3652
  @pB.say("✗ Error uploading #{File.basename(local_file)}: #{e.message}".fg(196))
3651
3653
  end
@@ -4229,7 +4231,7 @@ def trash_browser # TRASH BROWSER {{{3
4229
4231
  begin
4230
4232
  size = File.directory?(f) ? (command("du -sb #{Shellwords.escape(f)} 2>/dev/null").split.first.to_i rescue 0) : File.size(f)
4231
4233
  mtime = File.mtime(f)
4232
- rescue
4234
+ rescue Errno::EACCES, Errno::ENOENT # permission denied or missing file
4233
4235
  size = 0
4234
4236
  mtime = Time.at(0)
4235
4237
  end
@@ -4300,7 +4302,7 @@ def trash_browser # TRASH BROWSER {{{3
4300
4302
  end
4301
4303
  @pB.say("Restored: #{File.basename(dest)}".fg(156))
4302
4304
  sel = [sel - 1, 0].max
4303
- rescue => e
4305
+ rescue StandardError => e # restore from trash failed
4304
4306
  @pB.say("Restore failed: #{e.message}".fg(196))
4305
4307
  end
4306
4308
  when 'd'
@@ -5002,9 +5004,9 @@ def system_info # {{{3
5002
5004
  text << sprintf(" %-15s %s\n", "Kernel:".fg(249), kernel_version.fg(156))
5003
5005
  text << sprintf(" %-15s %s\n", "Architecture:".fg(249), architecture.fg(156))
5004
5006
  text << "\n"
5005
- rescue # rubocop:disable Lint/SuppressedException
5007
+ rescue StandardError # OS info unavailable
5006
5008
  end
5007
-
5009
+
5008
5010
  begin
5009
5011
  # Hardware Information
5010
5012
  text << "Hardware".fg(226).b + "\n"
@@ -5033,11 +5035,11 @@ def system_info # {{{3
5033
5035
  temp_color = temp > 80 ? 196 : temp > 60 ? 220 : 156
5034
5036
  text << sprintf(" %-15s %s\n", "CPU Temp:".fg(249), "#{temp}°C".fg(temp_color))
5035
5037
  end
5036
-
5038
+
5037
5039
  text << "\n"
5038
- rescue # rubocop:disable Lint/SuppressedException
5040
+ rescue StandardError # hardware info unavailable
5039
5041
  end
5040
-
5042
+
5041
5043
  begin
5042
5044
  # Memory Information with visual bar
5043
5045
  text << "Memory".fg(226).b + "\n"
@@ -5079,11 +5081,11 @@ def system_info # {{{3
5079
5081
  swap_used = (parts[2].to_i / 1024.0 / 1024.0 / 1024.0).round(1)
5080
5082
  text << sprintf(" %-15s %s\n", "Swap:".fg(249), "#{swap_used}/#{swap_total} GB".fg(156))
5081
5083
  end
5082
-
5084
+
5083
5085
  text << "\n"
5084
- rescue # rubocop:disable Lint/SuppressedException
5086
+ rescue StandardError # memory info unavailable
5085
5087
  end
5086
-
5088
+
5087
5089
  begin
5088
5090
  # Storage Information with visual bars
5089
5091
  text << "Storage".fg(226).b + "\n"
@@ -5123,9 +5125,9 @@ def system_info # {{{3
5123
5125
  end
5124
5126
  end
5125
5127
  text << "\n"
5126
- rescue # rubocop:disable Lint/SuppressedException
5128
+ rescue StandardError # storage info unavailable
5127
5129
  end
5128
-
5130
+
5129
5131
  begin
5130
5132
  # Network Information
5131
5133
  text << "Network".fg(226).b + "\n"
@@ -5159,14 +5161,14 @@ def system_info # {{{3
5159
5161
  `curl -s ifconfig.me 2>/dev/null`.chomp
5160
5162
  end
5161
5163
  text << sprintf(" %-15s %s\n", "Public IP:".fg(249), public_ip.fg(156)) unless public_ip.empty?
5162
- rescue
5164
+ rescue StandardError # timeout or network error
5163
5165
  # Skip public IP if timeout or error
5164
5166
  end
5165
5167
 
5166
5168
  text << "\n"
5167
- rescue # rubocop:disable Lint/SuppressedException
5169
+ rescue StandardError # network info unavailable
5168
5170
  end
5169
-
5171
+
5170
5172
  begin
5171
5173
  # Environment Information
5172
5174
  text << "Environment".fg(226).b + "\n"
@@ -5201,9 +5203,9 @@ def system_info # {{{3
5201
5203
  end
5202
5204
 
5203
5205
  text << "\n"
5204
- rescue # rubocop:disable Lint/SuppressedException
5206
+ rescue StandardError # environment info unavailable
5205
5207
  end
5206
-
5208
+
5207
5209
  begin
5208
5210
  # Services & Processes
5209
5211
  text << "Services & Processes".fg(226).b + "\n"
@@ -5244,11 +5246,11 @@ def system_info # {{{3
5244
5246
  text << sprintf(" %-15s %s\n", "Users:".fg(249), "#{users} logged in".fg(156))
5245
5247
 
5246
5248
  text << "\n"
5247
- rescue # rubocop:disable Lint/SuppressedException
5249
+ rescue StandardError # services info unavailable
5248
5250
  end
5249
-
5251
+
5250
5252
  @pR.say(text)
5251
- rescue => e
5253
+ rescue StandardError => e # system info display failed
5252
5254
  @pR.say("Unable to show system info\n#{e.message}".fg(196))
5253
5255
  end
5254
5256
 
@@ -5261,7 +5263,7 @@ end
5261
5263
 
5262
5264
  def navi_invoke # {{{3
5263
5265
  @navi = `navi`
5264
- rescue
5266
+ rescue StandardError # navi command not found
5265
5267
  @pB.say(' navi not installed - see https://github.com/junegunn/fzf')
5266
5268
  end
5267
5269
 
@@ -5460,7 +5462,7 @@ def get_cached_dirlist(dir, ls_options, ls_options_with_long = nil) # {{{2
5460
5462
  dir_mtime = File.mtime(dir).to_i
5461
5463
  file_count = Dir.entries(dir).size
5462
5464
  cache_key = "#{dir}:#{ls_options_with_long}:#{dir_mtime}:#{file_count}"
5463
- rescue
5465
+ rescue Errno::EACCES, Errno::ENOENT # permission denied or missing dir
5464
5466
  return nil # Can't cache if we can't get mtime or file count
5465
5467
  end
5466
5468
 
@@ -5488,7 +5490,7 @@ def get_cached_dirlist(dir, ls_options, ls_options_with_long = nil) # {{{2
5488
5490
  @dir_cache.delete_if { |key, _| key.start_with?("#{dir}:") && key != cache_key }
5489
5491
 
5490
5492
  result
5491
- rescue => e
5493
+ rescue StandardError => e # ls command failed
5492
5494
  # Return empty result on error
5493
5495
  { purels: [], colorls: [] }
5494
5496
  end
@@ -5500,7 +5502,7 @@ def get_cached_file_metadata(file_path) # {{{2
5500
5502
  begin
5501
5503
  file_stat = File.stat(file_path)
5502
5504
  cache_key = "#{file_path}:#{file_stat.mtime.to_i}:#{file_stat.size}"
5503
- rescue
5505
+ rescue Errno::EACCES, Errno::ENOENT # permission denied or missing file
5504
5506
  return nil
5505
5507
  end
5506
5508
 
@@ -5536,7 +5538,7 @@ def get_cached_file_metadata(file_path) # {{{2
5536
5538
  @metadata_cache.delete_if { |key, _| key.start_with?("#{file_path}:") && key != cache_key }
5537
5539
 
5538
5540
  metadata
5539
- rescue
5541
+ rescue StandardError # metadata extraction failed
5540
5542
  nil
5541
5543
  end
5542
5544
  end
@@ -5701,7 +5703,7 @@ def dirlist(left: true, directory: nil) # LIST DIRECTORIES {{{2
5701
5703
  pure_output = command("ls #{Shellwords.escape(dir)} #{ls_options}")
5702
5704
  purels = pure_output.pure.split("\n")
5703
5705
  colorls = color_output.split("\n")
5704
- rescue => e
5706
+ rescue StandardError => e # ls command failed
5705
5707
  purels = []
5706
5708
  colorls = []
5707
5709
  end
@@ -5732,7 +5734,7 @@ def dirlist(left: true, directory: nil) # LIST DIRECTORIES {{{2
5732
5734
  @files = purels
5733
5735
  # Update @selected & @fileattr for left pane
5734
5736
  if purels[@index]
5735
- @selected = Dir.pwd + '/' + purels[@index]
5737
+ @selected = File.join(Dir.pwd, purels[@index])
5736
5738
  sfile = @selected.dup
5737
5739
  sfile += '/' if File.directory?(@selected)
5738
5740
  time_opt = @is_macos_bsd ? '-T' : '--time-style=long-iso'
@@ -6165,7 +6167,7 @@ def command(cmd, timeout: 5, return_both: false) # {{{2
6165
6167
  @pR.say(msg.fg(196))
6166
6168
  ''
6167
6169
  end
6168
- rescue => e
6170
+ rescue StandardError => e # command execution failed
6169
6171
  msg = "Error: #{e.message}\n#{e.backtrace.join("\n")}\n"
6170
6172
  if return_both
6171
6173
  ['', msg]
@@ -6241,7 +6243,7 @@ def copy_move_link(type) # COPY OR MOVE TAGGED ITEMS {{{2
6241
6243
  end
6242
6244
 
6243
6245
  @file_op_result = " #{type.capitalize} complete: #{operations.size} item(s)".fg(156)
6244
- rescue => e
6246
+ rescue StandardError => e # file operation failed
6245
6247
  @file_op_result = " #{type.capitalize} error: #{e.message}".fg(196)
6246
6248
  ensure
6247
6249
  @file_op_progress = nil
@@ -6274,7 +6276,7 @@ def copy_move_link_sync(type, items, dest_dir) # {{{3
6274
6276
  operations << { source_path: item, dest_path: dest }
6275
6277
  @pB.say(' Item(s) symlinked here.')
6276
6278
  end
6277
- rescue => e
6279
+ rescue StandardError => e # file operation failed
6278
6280
  @pB.say(e.to_s)
6279
6281
  end
6280
6282
  end
@@ -6403,7 +6405,7 @@ def get_interactive_program(file_path) # HELPER FOR OPEN_SELECTED TO USE @intera
6403
6405
  end
6404
6406
  end
6405
6407
  end
6406
- rescue
6408
+ rescue StandardError # desktop file detection failed
6407
6409
  # If detection fails, fall back to normal behavior
6408
6410
  end
6409
6411
  nil
@@ -6723,7 +6725,7 @@ def showcontent # SHOW CONTENTS IN THE RIGHT WINDOW {{{2
6723
6725
  end
6724
6726
  end
6725
6727
  end
6726
- rescue => e
6728
+ rescue StandardError => e # SVG render failed
6727
6729
  @pR.say("Error processing SVG: #{e}")
6728
6730
  end
6729
6731
  when /\.(?:png|jpe?g|bmp|gif|webp|tiff?)$/i
@@ -6736,7 +6738,7 @@ def showcontent # SHOW CONTENTS IN THE RIGHT WINDOW {{{2
6736
6738
  showimage(@selected)
6737
6739
  @image = true
6738
6740
  end
6739
- rescue => e
6741
+ rescue Errno::EACCES, Errno::ENOENT => e # permission denied or missing image
6740
6742
  @pR.say("Error checking image size: #{e}")
6741
6743
  end
6742
6744
  when /\.(?:mpg|mpeg|avi|mov|mkv|mp4|webm|flv|wmv|m4v)$/i
@@ -6765,6 +6767,7 @@ def showcontent # SHOW CONTENTS IN THE RIGHT WINDOW {{{2
6765
6767
  else
6766
6768
  # Enhanced text file preview with partial loading for large files
6767
6769
  begin
6770
+ Timeout.timeout(5) do # Prevent preview from hanging
6768
6771
  file_size = File.size(@selected)
6769
6772
 
6770
6773
  # For text files, we can preview them partially even if large
@@ -6779,6 +6782,7 @@ def showcontent # SHOW CONTENTS IN THE RIGHT WINDOW {{{2
6779
6782
  line_count = 0
6780
6783
 
6781
6784
  file.each_line do |line|
6785
+ line = line[0, 10_000] + "...\n" if line.length > 10_000
6782
6786
  lines << line
6783
6787
  line_count += 1
6784
6788
  break if line_count >= preview_lines
@@ -6819,25 +6823,33 @@ def showcontent # SHOW CONTENTS IN THE RIGHT WINDOW {{{2
6819
6823
  else
6820
6824
  # Small files - read entirely as before
6821
6825
  text = File.read(@selected).force_encoding('UTF-8') rescue ''
6826
+ # Truncate extremely long lines to prevent UI hang
6827
+ has_long_lines = text.lines.any? { |l| l.length > 2_000 }
6828
+ if has_long_lines
6829
+ text = text.lines.map { |l| l.length > 2_000 ? l[0, 10_000] + "...\n" : l }.join
6830
+ end
6822
6831
  if text.valid_encoding?
6823
- if @batuse
6824
- # Try bat first, with robust fallback
6832
+ if @batuse && !has_long_lines
6833
+ # Try bat on original file (safe, no long lines)
6825
6834
  bat_cmd = "#{@bat} -n --color=always #{Shellwords.escape(@selected)}"
6826
- bat_output = command(bat_cmd, timeout: 10)
6835
+ bat_output = command(bat_cmd, timeout: 5)
6827
6836
  if bat_output.empty?
6828
- # Bat failed or returned empty - try plain cat
6829
6837
  showcommand("cat #{Shellwords.escape(@selected)}")
6830
6838
  else
6831
6839
  @pR.say(bat_output)
6832
6840
  end
6833
6841
  else
6834
- showcommand("cat #{Shellwords.escape(@selected)}")
6842
+ # Use truncated text directly (long lines or no bat)
6843
+ @pR.say(text)
6835
6844
  end
6836
6845
  else
6837
6846
  @pR.say("No preview available for #{@selected}")
6838
6847
  end
6839
6848
  end
6840
- rescue => e
6849
+ end # Timeout.timeout
6850
+ rescue Timeout::Error
6851
+ @pR.say("Preview timed out (file too large or complex)")
6852
+ rescue StandardError => e # file preview failed
6841
6853
  @pR.say("Error previewing file: #{e}")
6842
6854
  end
6843
6855
  end
@@ -6894,7 +6906,7 @@ def showimage(image) # SHOW THE SELECTED IMAGE IN THE RIGHT WINDOW {{{2
6894
6906
  max_height: @pR.h - 1)
6895
6907
  @current_image_path = img_path
6896
6908
  end
6897
- rescue => e
6909
+ rescue StandardError => e # image display failed
6898
6910
  @pR.text = "Error showing image: #{e.message}"
6899
6911
  end
6900
6912
  end
@@ -7075,7 +7087,7 @@ loop do
7075
7087
  @pT.update = @pL.update = @pR.update = @pB.update = true
7076
7088
  refresh
7077
7089
  end
7078
- rescue; end
7090
+ rescue StandardError; end # terminal size query failed
7079
7091
  end
7080
7092
 
7081
7093
  # redraw, but ignore TTY‐focus errors
@@ -7093,7 +7105,7 @@ loop do
7093
7105
  # If cwd was deleted externally, jump home
7094
7106
  begin
7095
7107
  Dir.pwd
7096
- rescue
7108
+ rescue Errno::ENOENT, Errno::EACCES # cwd was deleted or became inaccessible
7097
7109
  Dir.chdir
7098
7110
  end
7099
7111
  # If selected file was removed externally, force pane refresh (skip in archive/remote mode)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rtfm-filemanager
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.2.1
4
+ version: 8.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geir Isene
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-16 00:00:00.000000000 Z
11
+ date: 2026-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rcurses