rtfm-filemanager 8.2.1 → 8.2.3

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 (5) hide show
  1. checksums.yaml +4 -4
  2. data/bin/rtfm +86 -71
  3. data/img/rtfm-kb.svg +341 -0
  4. metadata +3 -3
  5. data/img/rtfm-kb.png +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eb02bbf2f12ccc66afd6d2fbbef68f6e7a1a05f3d932e48a8143ae8df1a70d39
4
- data.tar.gz: e1a1583ccab118308ecf16d898631dff1fcddedb3b46f7462ba2a8486cfad5d1
3
+ metadata.gz: 4ca25cd86bf46067b73bf66b33635f6e220d937ba528ab25434da99655a04e52
4
+ data.tar.gz: 58418e13253d8063d73114f361764cf20e04f62649ddea41ec0163abbf224af4
5
5
  SHA512:
6
- metadata.gz: 8eaeb2806d03dd15601d705bc409b677cbbb3bbe08aa69a1cd8b9135bbf4b5ecfc95c8140b4c9d9e526eb4652fa9057f4102aec05058d6c94272b39027d3e33a
7
- data.tar.gz: f9cb7747b1c773a2c545bb1afaa600ceff1a04d13149d9a98cae029687a37cbeccb0b330951659463fd666206104234d5ae0f6298c5cef2ff01d3950be9063ae
6
+ metadata.gz: 70397c9a79033585cdd6dcb7ff1bd99d5fc6f973a223e3f91cbd7c23f2534ed0c738f9ed2be6432a37e993fe115d872c36617c444844bb9f09e84510541afade
7
+ data.tar.gz: b346e1b4d9cadd7657eb0649e0111a83b0f2e5332c5195b13b9e82afb3a34703b293c74b934e3e1876f916780b25d085daed25306c18c23a692c4e2bb526c488
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.3' # 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'
@@ -4880,7 +4882,8 @@ def copy_path_primary # {{{3
4880
4882
  path = get_selected_full_path
4881
4883
  if path
4882
4884
  copy_to_clipboard(path, 'primary')
4883
- @pB.say(' Path copied to primary selection (middle-click to paste)')
4885
+ copy_to_clipboard(path, 'clipboard')
4886
+ @pB.say(' Path copied')
4884
4887
  else
4885
4888
  @pB.say(' No selected item path to copy')
4886
4889
  end
@@ -4895,7 +4898,8 @@ def copy_path_clipboard # {{{3
4895
4898
  path = get_selected_full_path
4896
4899
  if path
4897
4900
  copy_to_clipboard(path, 'clipboard')
4898
- @pB.say(' Path copied to clipboard (Ctrl+V to paste)')
4901
+ copy_to_clipboard(path, 'primary')
4902
+ @pB.say(' Path copied')
4899
4903
  else
4900
4904
  @pB.say(' No selected item path to copy')
4901
4905
  end
@@ -4966,10 +4970,11 @@ def copy_right # {{{3
4966
4970
  @pB.say(' Failed to copy image - xclip may not be installed')
4967
4971
  end
4968
4972
  else
4969
- # Copy right pane text content
4970
- clip = 'xclip -selection clipboard'
4971
- @pB.say(' Right pane text copied to clipboard')
4972
- shell("echo -n #{Shellwords.escape(@pR.text.pure)} | #{clip} > /dev/null 2>&1", background: true)
4973
+ # Copy right pane text content to both selections
4974
+ escaped = Shellwords.escape(@pR.text.pure)
4975
+ shell("echo -n #{escaped} | xclip -selection clipboard > /dev/null 2>&1", background: true)
4976
+ shell("echo -n #{escaped} | xclip -selection primary > /dev/null 2>&1", background: true)
4977
+ @pB.say(' Right pane text copied')
4973
4978
  end
4974
4979
  end
4975
4980
 
@@ -5002,9 +5007,9 @@ def system_info # {{{3
5002
5007
  text << sprintf(" %-15s %s\n", "Kernel:".fg(249), kernel_version.fg(156))
5003
5008
  text << sprintf(" %-15s %s\n", "Architecture:".fg(249), architecture.fg(156))
5004
5009
  text << "\n"
5005
- rescue # rubocop:disable Lint/SuppressedException
5010
+ rescue StandardError # OS info unavailable
5006
5011
  end
5007
-
5012
+
5008
5013
  begin
5009
5014
  # Hardware Information
5010
5015
  text << "Hardware".fg(226).b + "\n"
@@ -5033,11 +5038,11 @@ def system_info # {{{3
5033
5038
  temp_color = temp > 80 ? 196 : temp > 60 ? 220 : 156
5034
5039
  text << sprintf(" %-15s %s\n", "CPU Temp:".fg(249), "#{temp}°C".fg(temp_color))
5035
5040
  end
5036
-
5041
+
5037
5042
  text << "\n"
5038
- rescue # rubocop:disable Lint/SuppressedException
5043
+ rescue StandardError # hardware info unavailable
5039
5044
  end
5040
-
5045
+
5041
5046
  begin
5042
5047
  # Memory Information with visual bar
5043
5048
  text << "Memory".fg(226).b + "\n"
@@ -5079,11 +5084,11 @@ def system_info # {{{3
5079
5084
  swap_used = (parts[2].to_i / 1024.0 / 1024.0 / 1024.0).round(1)
5080
5085
  text << sprintf(" %-15s %s\n", "Swap:".fg(249), "#{swap_used}/#{swap_total} GB".fg(156))
5081
5086
  end
5082
-
5087
+
5083
5088
  text << "\n"
5084
- rescue # rubocop:disable Lint/SuppressedException
5089
+ rescue StandardError # memory info unavailable
5085
5090
  end
5086
-
5091
+
5087
5092
  begin
5088
5093
  # Storage Information with visual bars
5089
5094
  text << "Storage".fg(226).b + "\n"
@@ -5123,9 +5128,9 @@ def system_info # {{{3
5123
5128
  end
5124
5129
  end
5125
5130
  text << "\n"
5126
- rescue # rubocop:disable Lint/SuppressedException
5131
+ rescue StandardError # storage info unavailable
5127
5132
  end
5128
-
5133
+
5129
5134
  begin
5130
5135
  # Network Information
5131
5136
  text << "Network".fg(226).b + "\n"
@@ -5159,14 +5164,14 @@ def system_info # {{{3
5159
5164
  `curl -s ifconfig.me 2>/dev/null`.chomp
5160
5165
  end
5161
5166
  text << sprintf(" %-15s %s\n", "Public IP:".fg(249), public_ip.fg(156)) unless public_ip.empty?
5162
- rescue
5167
+ rescue StandardError # timeout or network error
5163
5168
  # Skip public IP if timeout or error
5164
5169
  end
5165
5170
 
5166
5171
  text << "\n"
5167
- rescue # rubocop:disable Lint/SuppressedException
5172
+ rescue StandardError # network info unavailable
5168
5173
  end
5169
-
5174
+
5170
5175
  begin
5171
5176
  # Environment Information
5172
5177
  text << "Environment".fg(226).b + "\n"
@@ -5201,9 +5206,9 @@ def system_info # {{{3
5201
5206
  end
5202
5207
 
5203
5208
  text << "\n"
5204
- rescue # rubocop:disable Lint/SuppressedException
5209
+ rescue StandardError # environment info unavailable
5205
5210
  end
5206
-
5211
+
5207
5212
  begin
5208
5213
  # Services & Processes
5209
5214
  text << "Services & Processes".fg(226).b + "\n"
@@ -5244,11 +5249,11 @@ def system_info # {{{3
5244
5249
  text << sprintf(" %-15s %s\n", "Users:".fg(249), "#{users} logged in".fg(156))
5245
5250
 
5246
5251
  text << "\n"
5247
- rescue # rubocop:disable Lint/SuppressedException
5252
+ rescue StandardError # services info unavailable
5248
5253
  end
5249
-
5254
+
5250
5255
  @pR.say(text)
5251
- rescue => e
5256
+ rescue StandardError => e # system info display failed
5252
5257
  @pR.say("Unable to show system info\n#{e.message}".fg(196))
5253
5258
  end
5254
5259
 
@@ -5261,7 +5266,7 @@ end
5261
5266
 
5262
5267
  def navi_invoke # {{{3
5263
5268
  @navi = `navi`
5264
- rescue
5269
+ rescue StandardError # navi command not found
5265
5270
  @pB.say(' navi not installed - see https://github.com/junegunn/fzf')
5266
5271
  end
5267
5272
 
@@ -5460,7 +5465,7 @@ def get_cached_dirlist(dir, ls_options, ls_options_with_long = nil) # {{{2
5460
5465
  dir_mtime = File.mtime(dir).to_i
5461
5466
  file_count = Dir.entries(dir).size
5462
5467
  cache_key = "#{dir}:#{ls_options_with_long}:#{dir_mtime}:#{file_count}"
5463
- rescue
5468
+ rescue Errno::EACCES, Errno::ENOENT # permission denied or missing dir
5464
5469
  return nil # Can't cache if we can't get mtime or file count
5465
5470
  end
5466
5471
 
@@ -5488,7 +5493,7 @@ def get_cached_dirlist(dir, ls_options, ls_options_with_long = nil) # {{{2
5488
5493
  @dir_cache.delete_if { |key, _| key.start_with?("#{dir}:") && key != cache_key }
5489
5494
 
5490
5495
  result
5491
- rescue => e
5496
+ rescue StandardError => e # ls command failed
5492
5497
  # Return empty result on error
5493
5498
  { purels: [], colorls: [] }
5494
5499
  end
@@ -5500,7 +5505,7 @@ def get_cached_file_metadata(file_path) # {{{2
5500
5505
  begin
5501
5506
  file_stat = File.stat(file_path)
5502
5507
  cache_key = "#{file_path}:#{file_stat.mtime.to_i}:#{file_stat.size}"
5503
- rescue
5508
+ rescue Errno::EACCES, Errno::ENOENT # permission denied or missing file
5504
5509
  return nil
5505
5510
  end
5506
5511
 
@@ -5536,7 +5541,7 @@ def get_cached_file_metadata(file_path) # {{{2
5536
5541
  @metadata_cache.delete_if { |key, _| key.start_with?("#{file_path}:") && key != cache_key }
5537
5542
 
5538
5543
  metadata
5539
- rescue
5544
+ rescue StandardError # metadata extraction failed
5540
5545
  nil
5541
5546
  end
5542
5547
  end
@@ -5701,7 +5706,7 @@ def dirlist(left: true, directory: nil) # LIST DIRECTORIES {{{2
5701
5706
  pure_output = command("ls #{Shellwords.escape(dir)} #{ls_options}")
5702
5707
  purels = pure_output.pure.split("\n")
5703
5708
  colorls = color_output.split("\n")
5704
- rescue => e
5709
+ rescue StandardError => e # ls command failed
5705
5710
  purels = []
5706
5711
  colorls = []
5707
5712
  end
@@ -5732,7 +5737,7 @@ def dirlist(left: true, directory: nil) # LIST DIRECTORIES {{{2
5732
5737
  @files = purels
5733
5738
  # Update @selected & @fileattr for left pane
5734
5739
  if purels[@index]
5735
- @selected = Dir.pwd + '/' + purels[@index]
5740
+ @selected = File.join(Dir.pwd, purels[@index])
5736
5741
  sfile = @selected.dup
5737
5742
  sfile += '/' if File.directory?(@selected)
5738
5743
  time_opt = @is_macos_bsd ? '-T' : '--time-style=long-iso'
@@ -6165,7 +6170,7 @@ def command(cmd, timeout: 5, return_both: false) # {{{2
6165
6170
  @pR.say(msg.fg(196))
6166
6171
  ''
6167
6172
  end
6168
- rescue => e
6173
+ rescue StandardError => e # command execution failed
6169
6174
  msg = "Error: #{e.message}\n#{e.backtrace.join("\n")}\n"
6170
6175
  if return_both
6171
6176
  ['', msg]
@@ -6241,7 +6246,7 @@ def copy_move_link(type) # COPY OR MOVE TAGGED ITEMS {{{2
6241
6246
  end
6242
6247
 
6243
6248
  @file_op_result = " #{type.capitalize} complete: #{operations.size} item(s)".fg(156)
6244
- rescue => e
6249
+ rescue StandardError => e # file operation failed
6245
6250
  @file_op_result = " #{type.capitalize} error: #{e.message}".fg(196)
6246
6251
  ensure
6247
6252
  @file_op_progress = nil
@@ -6274,7 +6279,7 @@ def copy_move_link_sync(type, items, dest_dir) # {{{3
6274
6279
  operations << { source_path: item, dest_path: dest }
6275
6280
  @pB.say(' Item(s) symlinked here.')
6276
6281
  end
6277
- rescue => e
6282
+ rescue StandardError => e # file operation failed
6278
6283
  @pB.say(e.to_s)
6279
6284
  end
6280
6285
  end
@@ -6403,7 +6408,7 @@ def get_interactive_program(file_path) # HELPER FOR OPEN_SELECTED TO USE @intera
6403
6408
  end
6404
6409
  end
6405
6410
  end
6406
- rescue
6411
+ rescue StandardError # desktop file detection failed
6407
6412
  # If detection fails, fall back to normal behavior
6408
6413
  end
6409
6414
  nil
@@ -6723,7 +6728,7 @@ def showcontent # SHOW CONTENTS IN THE RIGHT WINDOW {{{2
6723
6728
  end
6724
6729
  end
6725
6730
  end
6726
- rescue => e
6731
+ rescue StandardError => e # SVG render failed
6727
6732
  @pR.say("Error processing SVG: #{e}")
6728
6733
  end
6729
6734
  when /\.(?:png|jpe?g|bmp|gif|webp|tiff?)$/i
@@ -6736,7 +6741,7 @@ def showcontent # SHOW CONTENTS IN THE RIGHT WINDOW {{{2
6736
6741
  showimage(@selected)
6737
6742
  @image = true
6738
6743
  end
6739
- rescue => e
6744
+ rescue Errno::EACCES, Errno::ENOENT => e # permission denied or missing image
6740
6745
  @pR.say("Error checking image size: #{e}")
6741
6746
  end
6742
6747
  when /\.(?:mpg|mpeg|avi|mov|mkv|mp4|webm|flv|wmv|m4v)$/i
@@ -6765,6 +6770,7 @@ def showcontent # SHOW CONTENTS IN THE RIGHT WINDOW {{{2
6765
6770
  else
6766
6771
  # Enhanced text file preview with partial loading for large files
6767
6772
  begin
6773
+ Timeout.timeout(5) do # Prevent preview from hanging
6768
6774
  file_size = File.size(@selected)
6769
6775
 
6770
6776
  # For text files, we can preview them partially even if large
@@ -6779,6 +6785,7 @@ def showcontent # SHOW CONTENTS IN THE RIGHT WINDOW {{{2
6779
6785
  line_count = 0
6780
6786
 
6781
6787
  file.each_line do |line|
6788
+ line = line[0, 10_000] + "...\n" if line.length > 10_000
6782
6789
  lines << line
6783
6790
  line_count += 1
6784
6791
  break if line_count >= preview_lines
@@ -6819,25 +6826,33 @@ def showcontent # SHOW CONTENTS IN THE RIGHT WINDOW {{{2
6819
6826
  else
6820
6827
  # Small files - read entirely as before
6821
6828
  text = File.read(@selected).force_encoding('UTF-8') rescue ''
6829
+ # Truncate extremely long lines to prevent UI hang
6830
+ has_long_lines = text.lines.any? { |l| l.length > 2_000 }
6831
+ if has_long_lines
6832
+ text = text.lines.map { |l| l.length > 2_000 ? l[0, 10_000] + "...\n" : l }.join
6833
+ end
6822
6834
  if text.valid_encoding?
6823
- if @batuse
6824
- # Try bat first, with robust fallback
6835
+ if @batuse && !has_long_lines
6836
+ # Try bat on original file (safe, no long lines)
6825
6837
  bat_cmd = "#{@bat} -n --color=always #{Shellwords.escape(@selected)}"
6826
- bat_output = command(bat_cmd, timeout: 10)
6838
+ bat_output = command(bat_cmd, timeout: 5)
6827
6839
  if bat_output.empty?
6828
- # Bat failed or returned empty - try plain cat
6829
6840
  showcommand("cat #{Shellwords.escape(@selected)}")
6830
6841
  else
6831
6842
  @pR.say(bat_output)
6832
6843
  end
6833
6844
  else
6834
- showcommand("cat #{Shellwords.escape(@selected)}")
6845
+ # Use truncated text directly (long lines or no bat)
6846
+ @pR.say(text)
6835
6847
  end
6836
6848
  else
6837
6849
  @pR.say("No preview available for #{@selected}")
6838
6850
  end
6839
6851
  end
6840
- rescue => e
6852
+ end # Timeout.timeout
6853
+ rescue Timeout::Error
6854
+ @pR.say("Preview timed out (file too large or complex)")
6855
+ rescue StandardError => e # file preview failed
6841
6856
  @pR.say("Error previewing file: #{e}")
6842
6857
  end
6843
6858
  end
@@ -6894,7 +6909,7 @@ def showimage(image) # SHOW THE SELECTED IMAGE IN THE RIGHT WINDOW {{{2
6894
6909
  max_height: @pR.h - 1)
6895
6910
  @current_image_path = img_path
6896
6911
  end
6897
- rescue => e
6912
+ rescue StandardError => e # image display failed
6898
6913
  @pR.text = "Error showing image: #{e.message}"
6899
6914
  end
6900
6915
  end
@@ -7075,7 +7090,7 @@ loop do
7075
7090
  @pT.update = @pL.update = @pR.update = @pB.update = true
7076
7091
  refresh
7077
7092
  end
7078
- rescue; end
7093
+ rescue StandardError; end # terminal size query failed
7079
7094
  end
7080
7095
 
7081
7096
  # redraw, but ignore TTY‐focus errors
@@ -7093,7 +7108,7 @@ loop do
7093
7108
  # If cwd was deleted externally, jump home
7094
7109
  begin
7095
7110
  Dir.pwd
7096
- rescue
7111
+ rescue Errno::ENOENT, Errno::EACCES # cwd was deleted or became inaccessible
7097
7112
  Dir.chdir
7098
7113
  end
7099
7114
  # If selected file was removed externally, force pane refresh (skip in archive/remote mode)
data/img/rtfm-kb.svg ADDED
@@ -0,0 +1,341 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1500 560" width="1500" height="560" font-family="monospace, Courier New, Courier">
2
+ <defs>
3
+ <style>
4
+ .key { fill: #333; stroke: #555; stroke-width: 1; rx: 5; ry: 5; }
5
+ .kw { fill: #2a2a2a; stroke: #555; stroke-width: 1; rx: 5; ry: 5; }
6
+ .kl { font-size: 12px; fill: #bbb; font-weight: bold; text-anchor: middle; }
7
+ .lo { font-size: 8.5px; fill: #FF8888; text-anchor: middle; }
8
+ .up { font-size: 8.5px; fill: #88BBFF; text-anchor: middle; }
9
+ .ct { font-size: 10px; fill: #88FF88; }
10
+ .sp { font-size: 8.5px; fill: #DDAAFF; text-anchor: middle; }
11
+ .nv { font-size: 8.5px; fill: #bbb; text-anchor: middle; }
12
+ </style>
13
+ </defs>
14
+ <rect width="1500" height="560" fill="#1a1a1a" rx="8"/>
15
+ <text x="470" y="22" text-anchor="middle" font-size="15" font-weight="bold" fill="#fff">RTFM Keyboard Reference</text>
16
+
17
+ <!-- Row 0: Esc -->
18
+ <rect x="10" y="34" width="58" height="55" class="key"/>
19
+ <text x="39" y="54" class="kl">Esc</text>
20
+ <text x="39" y="80" class="lo">quit</text>
21
+
22
+ <!-- Row 1: Number row kw=62 gap=4 -->
23
+ <rect x="10" y="97" width="58" height="55" class="key"/>
24
+ <text x="39" y="117" class="kl">` ~</text>
25
+ <text x="39" y="143" class="lo">~ home</text>
26
+
27
+ <rect x="72" y="97" width="62" height="55" class="key"/>
28
+ <text x="103" y="117" class="kl">1</text>
29
+ <text x="103" y="143" class="lo">tab 1</text>
30
+
31
+ <rect x="138" y="97" width="62" height="55" class="key"/>
32
+ <text x="169" y="117" class="kl">2</text>
33
+ <text x="169" y="143" class="lo">tab 2</text>
34
+
35
+ <rect x="204" y="97" width="62" height="55" class="key"/>
36
+ <text x="235" y="117" class="kl">3</text>
37
+ <text x="235" y="143" class="lo">tab 3</text>
38
+
39
+ <rect x="270" y="97" width="62" height="55" class="key"/>
40
+ <text x="301" y="117" class="kl">4</text>
41
+ <text x="301" y="143" class="lo">tab 4</text>
42
+
43
+ <rect x="336" y="97" width="62" height="55" class="key"/>
44
+ <text x="367" y="117" class="kl">5</text>
45
+ <text x="367" y="143" class="lo">tab 5</text>
46
+
47
+ <rect x="402" y="97" width="62" height="55" class="key"/>
48
+ <text x="433" y="117" class="kl">6</text>
49
+ <text x="433" y="143" class="lo">tab 6</text>
50
+
51
+ <rect x="468" y="97" width="62" height="55" class="key"/>
52
+ <text x="499" y="117" class="kl">7</text>
53
+ <text x="499" y="143" class="lo">tab 7</text>
54
+
55
+ <rect x="534" y="97" width="62" height="55" class="key"/>
56
+ <text x="565" y="117" class="kl">8</text>
57
+ <text x="565" y="143" class="lo">tab 8</text>
58
+
59
+ <rect x="600" y="97" width="62" height="55" class="key"/>
60
+ <text x="631" y="117" class="kl">9</text>
61
+ <text x="631" y="143" class="lo">tab 9</text>
62
+
63
+ <rect x="666" y="97" width="62" height="55" class="key"/>
64
+ <text x="697" y="127" class="kl">0</text>
65
+
66
+ <rect x="732" y="97" width="62" height="55" class="key"/>
67
+ <text x="763" y="115" class="kl">- _</text>
68
+ <text x="763" y="133" class="sp">_ image</text>
69
+ <text x="763" y="146" class="lo">- preview</text>
70
+
71
+ <rect x="798" y="97" width="62" height="55" class="key"/>
72
+ <text x="829" y="115" class="kl">= +</text>
73
+ <text x="829" y="133" class="sp">+ interact</text>
74
+ <text x="829" y="146" class="lo">= mkdir</text>
75
+
76
+ <rect x="864" y="97" width="76" height="55" class="kw"/>
77
+ <text x="902" y="130" class="nv">Bksp</text>
78
+
79
+ <!-- Row 2: QWERTY start x=10+80=90 offset, kw=62 -->
80
+ <rect x="10" y="160" width="76" height="55" class="kw"/>
81
+ <text x="48" y="183" class="nv">Tab</text>
82
+ <text x="48" y="206" class="nv">pgdn R</text>
83
+
84
+ <rect x="90" y="160" width="62" height="55" class="key"/>
85
+ <text x="121" y="176" class="kl">Q</text>
86
+ <text x="121" y="194" class="up">Q nosave</text>
87
+ <text x="121" y="207" class="lo">q save</text>
88
+
89
+ <rect x="156" y="160" width="62" height="55" class="key"/>
90
+ <text x="187" y="176" class="kl">W</text>
91
+ <text x="187" y="194" class="up">Write cfg</text>
92
+ <text x="187" y="207" class="lo">width</text>
93
+
94
+ <rect x="222" y="160" width="62" height="55" class="key"/>
95
+ <text x="253" y="176" class="kl">E</text>
96
+ <text x="253" y="194" class="up">Bulk ren</text>
97
+ <text x="253" y="207" class="lo">props</text>
98
+
99
+ <rect x="288" y="160" width="62" height="55" class="key"/>
100
+ <text x="319" y="176" class="kl">R</text>
101
+ <text x="319" y="194" class="up">Load cfg</text>
102
+ <text x="319" y="207" class="lo">refresh</text>
103
+
104
+ <rect x="354" y="160" width="62" height="55" class="key"/>
105
+ <text x="385" y="176" class="kl">T</text>
106
+ <text x="385" y="194" class="up">Tag list</text>
107
+ <text x="385" y="207" class="lo">tag</text>
108
+
109
+ <rect x="420" y="160" width="62" height="55" class="key"/>
110
+ <text x="451" y="176" class="kl">Y</text>
111
+ <text x="451" y="194" class="up">Cp clipbd</text>
112
+ <text x="451" y="207" class="lo">cp primary</text>
113
+
114
+ <rect x="486" y="160" width="62" height="55" class="key"/>
115
+ <text x="517" y="176" class="kl">U</text>
116
+ <text x="517" y="194" class="up">Undo</text>
117
+ <text x="517" y="207" class="lo">untag all</text>
118
+
119
+ <rect x="552" y="160" width="62" height="55" class="key"/>
120
+ <text x="583" y="176" class="kl">I</text>
121
+ <text x="583" y="194" class="up">AI descr</text>
122
+ <text x="583" y="207" class="lo">invert</text>
123
+
124
+ <rect x="618" y="160" width="62" height="55" class="key"/>
125
+ <text x="649" y="176" class="kl">O</text>
126
+ <text x="649" y="194" class="up">ls cmd</text>
127
+ <text x="649" y="207" class="lo">order</text>
128
+
129
+ <rect x="684" y="160" width="62" height="55" class="key"/>
130
+ <text x="715" y="176" class="kl">P</text>
131
+ <text x="715" y="194" class="up">Move here</text>
132
+ <text x="715" y="207" class="lo">copy here</text>
133
+
134
+ <rect x="750" y="160" width="62" height="55" class="key"/>
135
+ <text x="781" y="176" class="kl">[ {</text>
136
+ <text x="781" y="194" class="sp">{ ren tab</text>
137
+ <text x="781" y="207" class="sp">[ cls tab</text>
138
+
139
+ <rect x="816" y="160" width="62" height="55" class="key"/>
140
+ <text x="847" y="176" class="kl">] }</text>
141
+ <text x="847" y="194" class="sp">} dup tab</text>
142
+ <text x="847" y="207" class="sp">] new tab</text>
143
+
144
+ <rect x="882" y="160" width="58" height="55" class="key"/>
145
+ <text x="911" y="176" class="kl">\</text>
146
+ <text x="911" y="200" class="sp">clr srch</text>
147
+
148
+ <!-- Row 3: ASDF start x=10+90=100 offset -->
149
+ <rect x="10" y="223" width="86" height="55" class="kw"/>
150
+ <text x="53" y="255" class="nv">Ctrl</text>
151
+
152
+ <rect x="100" y="223" width="62" height="55" class="key"/>
153
+ <text x="131" y="239" class="kl">A</text>
154
+ <text x="131" y="257" class="up">Long list</text>
155
+ <text x="131" y="270" class="lo">all/none</text>
156
+
157
+ <rect x="166" y="223" width="62" height="55" class="key"/>
158
+ <text x="197" y="239" class="kl">S</text>
159
+ <text x="197" y="257" class="up">Sys info</text>
160
+ <text x="197" y="270" class="lo">symlink</text>
161
+
162
+ <rect x="232" y="223" width="62" height="55" class="key"/>
163
+ <text x="263" y="239" class="kl">D</text>
164
+ <text x="263" y="257" class="up">Emp trash</text>
165
+ <text x="263" y="270" class="lo">delete</text>
166
+
167
+ <rect x="298" y="223" width="62" height="55" class="key"/>
168
+ <text x="329" y="239" class="kl">F</text>
169
+ <text x="329" y="257" class="up">Filt regex</text>
170
+ <text x="329" y="270" class="lo">filt type</text>
171
+
172
+ <rect x="364" y="223" width="62" height="55" class="key"/>
173
+ <text x="395" y="239" class="kl">G</text>
174
+ <text x="395" y="257" class="up">Git status</text>
175
+ <text x="395" y="270" class="lo">grep</text>
176
+
177
+ <rect x="430" y="223" width="62" height="55" class="key"/>
178
+ <text x="461" y="239" class="kl">H</text>
179
+ <text x="461" y="257" class="up">Hash dir</text>
180
+ <text x="461" y="270" class="nv">h = LEFT</text>
181
+
182
+ <rect x="496" y="223" width="62" height="55" class="key"/>
183
+ <text x="527" y="239" class="kl">J</text>
184
+ <text x="527" y="257" class="up">Prev tab</text>
185
+ <text x="527" y="270" class="nv">j = DOWN</text>
186
+
187
+ <rect x="562" y="223" width="62" height="55" class="key"/>
188
+ <text x="593" y="239" class="kl">K</text>
189
+ <text x="593" y="257" class="up">Next tab</text>
190
+ <text x="593" y="270" class="nv">k = UP</text>
191
+
192
+ <rect x="628" y="223" width="62" height="55" class="key"/>
193
+ <text x="659" y="239" class="kl">L</text>
194
+ <text x="659" y="257" class="up">Locate</text>
195
+ <text x="659" y="270" class="nv">l = RIGHT</text>
196
+
197
+ <rect x="694" y="223" width="62" height="55" class="key"/>
198
+ <text x="725" y="239" class="kl">; :</text>
199
+ <text x="725" y="257" class="sp">: command</text>
200
+ <text x="725" y="270" class="sp">; history</text>
201
+
202
+ <rect x="760" y="223" width="62" height="55" class="key"/>
203
+ <text x="791" y="239" class="kl">'</text>
204
+ <text x="791" y="262" class="sp">jump mark</text>
205
+
206
+ <rect x="826" y="223" width="114" height="55" class="kw"/>
207
+ <text x="883" y="248" class="nv">Enter</text>
208
+ <text x="883" y="268" class="nv">open/refresh</text>
209
+
210
+ <!-- Row 4: ZXCV start x=10+100=110 offset -->
211
+ <rect x="10" y="286" width="96" height="55" class="kw"/>
212
+ <text x="58" y="318" class="nv">Shift</text>
213
+
214
+ <rect x="110" y="286" width="62" height="55" class="key"/>
215
+ <text x="141" y="302" class="kl">Z</text>
216
+ <text x="141" y="320" class="up">Zip</text>
217
+ <text x="141" y="333" class="lo">unzip</text>
218
+
219
+ <rect x="176" y="286" width="62" height="55" class="key"/>
220
+ <text x="207" y="302" class="kl">X</text>
221
+ <text x="207" y="320" class="up">Compare</text>
222
+ <text x="207" y="333" class="lo">open force</text>
223
+
224
+ <rect x="242" y="286" width="62" height="55" class="key"/>
225
+ <text x="273" y="302" class="kl">C</text>
226
+ <text x="273" y="320" class="up">Config</text>
227
+ <text x="273" y="333" class="lo">rename</text>
228
+
229
+ <rect x="308" y="286" width="62" height="55" class="key"/>
230
+ <text x="339" y="302" class="kl">V</text>
231
+ <text x="339" y="320" class="up">Plugins</text>
232
+ <text x="339" y="333" class="lo">version</text>
233
+
234
+ <rect x="374" y="286" width="62" height="55" class="key"/>
235
+ <text x="405" y="302" class="kl">B</text>
236
+ <text x="405" y="320" class="up">Border</text>
237
+ <text x="405" y="333" class="lo">syntax hl</text>
238
+
239
+ <rect x="440" y="286" width="62" height="55" class="key"/>
240
+ <text x="471" y="302" class="kl">N</text>
241
+ <text x="471" y="320" class="up">Srch prev</text>
242
+ <text x="471" y="333" class="lo">srch next</text>
243
+
244
+ <rect x="506" y="286" width="62" height="55" class="key"/>
245
+ <text x="537" y="302" class="kl">M</text>
246
+ <text x="537" y="320" class="up">Show mrks</text>
247
+ <text x="537" y="333" class="lo">set mark</text>
248
+
249
+ <rect x="572" y="286" width="62" height="55" class="key"/>
250
+ <text x="603" y="315" class="kl">, &lt;</text>
251
+
252
+ <rect x="638" y="286" width="62" height="55" class="key"/>
253
+ <text x="669" y="302" class="kl">. &gt;</text>
254
+ <text x="669" y="333" class="sp">&gt; follow lnk</text>
255
+
256
+ <rect x="704" y="286" width="62" height="55" class="key"/>
257
+ <text x="735" y="302" class="kl">/ ?</text>
258
+ <text x="735" y="320" class="sp">? help</text>
259
+ <text x="735" y="333" class="sp">/ search</text>
260
+
261
+ <rect x="770" y="286" width="170" height="55" class="kw"/>
262
+ <text x="855" y="318" class="nv">Shift</text>
263
+
264
+ <!-- Row 5: Bottom -->
265
+ <rect x="10" y="349" width="70" height="50" class="kw"/>
266
+ <text x="45" y="378" class="nv">Ctrl</text>
267
+
268
+ <rect x="84" y="349" width="58" height="50" class="kw"/>
269
+ <text x="113" y="378" class="nv">Alt</text>
270
+
271
+ <rect x="146" y="349" width="330" height="50" class="key"/>
272
+ <text x="311" y="378" class="nv">Space</text>
273
+
274
+ <rect x="480" y="349" width="62" height="50" class="key"/>
275
+ <text x="511" y="366" class="kl"># @</text>
276
+ <text x="511" y="382" class="sp"># locate</text>
277
+ <text x="511" y="394" class="sp">@ ruby dbg</text>
278
+
279
+ <!-- Nav cluster -->
280
+ <rect x="990" y="97" width="64" height="55" class="key"/>
281
+ <text x="1022" y="120" class="nv">Home</text>
282
+ <text x="1022" y="143" class="nv">g first</text>
283
+
284
+ <rect x="1058" y="97" width="64" height="55" class="key"/>
285
+ <text x="1090" y="120" class="nv">End</text>
286
+ <text x="1090" y="143" class="nv">G last</text>
287
+
288
+ <rect x="990" y="160" width="64" height="55" class="key"/>
289
+ <text x="1022" y="183" class="nv">PgUp</text>
290
+ <text x="1022" y="206" class="nv">page up</text>
291
+
292
+ <rect x="1058" y="160" width="64" height="55" class="key"/>
293
+ <text x="1090" y="183" class="nv">PgDn</text>
294
+ <text x="1090" y="206" class="nv">page dn</text>
295
+
296
+ <rect x="1022" y="286" width="60" height="55" class="key"/>
297
+ <text x="1052" y="310" class="nv">UP</text>
298
+ <text x="1052" y="333" class="nv">k</text>
299
+
300
+ <rect x="958" y="349" width="60" height="50" class="key"/>
301
+ <text x="988" y="370" class="nv">LEFT</text>
302
+ <text x="988" y="390" class="nv">h</text>
303
+
304
+ <rect x="1022" y="349" width="60" height="50" class="key"/>
305
+ <text x="1052" y="370" class="nv">DOWN</text>
306
+ <text x="1052" y="390" class="nv">j</text>
307
+
308
+ <rect x="1086" y="349" width="60" height="50" class="key"/>
309
+ <text x="1116" y="370" class="nv">RIGHT</text>
310
+ <text x="1116" y="390" class="nv">l/enter</text>
311
+
312
+ <!-- Right panel: Ctrl combos -->
313
+ <rect x="1180" y="34" width="300" height="340" rx="6" fill="#1a2a1a" stroke="#4a6a4a" stroke-width="1"/>
314
+ <text x="1330" y="56" text-anchor="middle" font-size="13" font-weight="bold" fill="#88FF88">Ctrl Combos</text>
315
+ <text x="1194" y="80" class="ct">C-A AI chat mode</text>
316
+ <text x="1194" y="100" class="ct">C-D Toggle trash view</text>
317
+ <text x="1194" y="120" class="ct">C-E Browse remote</text>
318
+ <text x="1194" y="140" class="ct">C-F Clear filter</text>
319
+ <text x="1194" y="160" class="ct">C-L fzf jump</text>
320
+ <text x="1194" y="180" class="ct">C-N Navi cheatsheet</text>
321
+ <text x="1194" y="200" class="ct">C-O Change ownership</text>
322
+ <text x="1194" y="220" class="ct">C-P Change permissions</text>
323
+ <text x="1194" y="240" class="ct">C-R Recent files</text>
324
+ <text x="1194" y="260" class="ct">C-T Tag by pattern</text>
325
+ <text x="1194" y="280" class="ct">C-Y Copy pane to clipboard</text>
326
+ <text x="1194" y="300" class="ct">C-; SSH history</text>
327
+ <text x="1194" y="328" class="ct">C-Arrows Move/copy items</text>
328
+ <text x="1194" y="348" class="ct">S-Arrows Scroll right pane</text>
329
+ <text x="1194" y="368" class="ct">S-Tab Page up right pane</text>
330
+
331
+ <!-- Legend -->
332
+ <rect x="20" y="470" width="14" height="14" fill="#442222" rx="2" stroke="#663333" stroke-width="0.5"/>
333
+ <text x="40" y="482" font-size="10" fill="#FF8888">lowercase</text>
334
+ <rect x="120" y="470" width="14" height="14" fill="#222244" rx="2" stroke="#333366" stroke-width="0.5"/>
335
+ <text x="140" y="482" font-size="10" fill="#88BBFF">UPPERCASE</text>
336
+ <rect x="240" y="470" width="14" height="14" fill="#224422" rx="2" stroke="#336633" stroke-width="0.5"/>
337
+ <text x="260" y="482" font-size="10" fill="#88FF88">Ctrl+key</text>
338
+ <rect x="350" y="470" width="14" height="14" fill="#332244" rx="2" stroke="#553366" stroke-width="0.5"/>
339
+ <text x="370" y="482" font-size="10" fill="#DDAAFF">special</text>
340
+ <text x="460" y="482" font-size="10" fill="#bbb">gray = navigation</text>
341
+ </svg>
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.3
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-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rcurses
@@ -97,7 +97,7 @@ files:
97
97
  - examples/rtfm.conf
98
98
  - examples/settings.rb
99
99
  - img/logo.png
100
- - img/rtfm-kb.png
100
+ - img/rtfm-kb.svg
101
101
  - man/rtfm.1
102
102
  homepage: https://isene.com/
103
103
  licenses:
data/img/rtfm-kb.png DELETED
Binary file