rtfm-filemanager 6.0.4 → 6.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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/bin/rtfm +324 -106
  3. metadata +4 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 485057b825685d870eba56b870704bdf27757f577a515a8bd734ef999cc0de21
4
- data.tar.gz: 99a1d71b6e97cd29c8dee500460870df8f97a2e13a0a40734a679bd2313b3fb3
3
+ metadata.gz: 88ef15c96d6cd2e481ea3d0741da517dc256f7790673769457c9b78686721391
4
+ data.tar.gz: c1db40c673a60aa766fb3a79623e7ac912f0cf6898798db4d6731029cde8c7bb
5
5
  SHA512:
6
- metadata.gz: af06a49dc63aaaa43deb3095aab75cf797dae88e4fd1c7c7ef71234b8ba5be74c26dc216382a7f9a305b0057237bc216dbe1472991a1b711e5d49f476f589a90
7
- data.tar.gz: 6c41d7940a9daef84173385807e910ed6110571add6d6cde88e4d5aae09f096d8cda62040f523b0cedd4877f51d1b023ec8aaef727c405d019cd764523646ccb
6
+ metadata.gz: 7dd8b024cc288a1cb6b8fe2119dfaff80e657e16cbe34c0a0c3418943854f9a3bc9d2539031ab21b4fa1219a96fbd84e41bd24a1dff2eefd1db1b1504480e138
7
+ data.tar.gz: d4da7c1a24813ae78d768369e83decd456c4bde4e2b711d3b476bbbfd168db1e58632834a0db905bda1041cd44e63a6b8c3465b3758ae7620953dfea6e09000d
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 = '6.0.4' # Fixed preview handler regex bug causing false matches
21
+ @version = '6.0.7' # Fixed preview update after deletion + enhanced System Info with visual indicators
22
22
 
23
23
  # SAVE & STORE TERMINAL {{{1
24
24
  ORIG_STTY = `stty -g`.chomp
@@ -1406,7 +1406,37 @@ def follow_symlink # {{{3
1406
1406
  @symlink_history.add(resolved_path)
1407
1407
  target = File.readlink(@selected)
1408
1408
  target = File.expand_path(target, File.dirname(@selected)) unless target.start_with?('/')
1409
- Dir.chdir(target)
1409
+
1410
+ # Check if target is a directory or file
1411
+ if File.directory?(target)
1412
+ Dir.chdir(target)
1413
+ elsif File.file?(target)
1414
+ # If it's a file, change to the directory containing the file
1415
+ target_dir = File.dirname(target)
1416
+ target_basename = File.basename(target)
1417
+ Dir.chdir(target_dir)
1418
+ # Clear the index first to force a full refresh
1419
+ @index = 0
1420
+ @selected = nil
1421
+ # Refresh the directory listing
1422
+ dirlist
1423
+ # Find and select the target file in the new directory
1424
+ file_index = @files.index(target_basename)
1425
+ if file_index
1426
+ @index = file_index
1427
+ @selected = @files[@index]
1428
+ # Update the directory memory with the new index so it's not reset by the main loop
1429
+ @directory[Dir.pwd] = @index
1430
+ else
1431
+ # If file not found in listing (possibly filtered), still try to select it
1432
+ @selected = target_basename
1433
+ @directory[Dir.pwd] = 0
1434
+ end
1435
+ else
1436
+ @pB.say("Error: Symlink target does not exist or is not accessible".fg(196))
1437
+ @symlink_history&.clear
1438
+ return
1439
+ end
1410
1440
 
1411
1441
  # Clear history after successful navigation
1412
1442
  @symlink_history.clear
@@ -3194,7 +3224,22 @@ def delete_items # {{{3
3194
3224
  # Cannot undo permanent deletion, so don't add to undo history
3195
3225
  end
3196
3226
  @tagged.clear
3197
- refresh_right
3227
+
3228
+ # Refresh the file list to reflect deletions
3229
+ @pL.update = true
3230
+ render # This will update @files and @selected
3231
+
3232
+ # After render, check if we still have files
3233
+ if @files.empty?
3234
+ # No files left in directory - clear the preview
3235
+ @pR.text = ""
3236
+ @pR.refresh
3237
+ else
3238
+ # Files exist - update preview for new selection
3239
+ @pR.update = true
3240
+ render # Update the preview pane
3241
+ end
3242
+
3198
3243
  @pB.say("#{action} #{paths.size} items#{@trash ? " to #{TRASH_DIR}" : ''}".fg(204))
3199
3244
  end
3200
3245
  else
@@ -3603,7 +3648,6 @@ end
3603
3648
 
3604
3649
  # RIGHT PANE CONTROLS {{{2
3605
3650
  def refresh_right # {{{3
3606
- render
3607
3651
  @pR.full_refresh
3608
3652
  @pR.update = @pB.update = true
3609
3653
  end
@@ -3704,141 +3748,302 @@ end
3704
3748
 
3705
3749
  # SYSTEM SHORTCUTS {{{2
3706
3750
  def system_info # {{{3
3707
- text = "System Information\n".b.fg(156)
3751
+ text = "SYSTEM INFORMATION".b.fg(156) + " - " + Time.now.strftime("%Y-%m-%d %H:%M:%S").fg(249) + "\n"
3708
3752
  text << "=" * 50 + "\n\n"
3709
3753
 
3710
3754
  begin
3711
- # Operating System Information
3712
- text << "Operating System:\n".fg(226)
3755
+ # System Overview
3756
+ text << "System Overview".fg(226).b + "\n"
3757
+ text << "─" * 20 + "\n"
3758
+
3759
+ # Hostname and uptime
3760
+ hostname = `hostname 2>/dev/null`.chomp
3761
+ uptime_raw = `uptime -p 2>/dev/null`.chomp.sub('up ', '')
3762
+ boot_time = `uptime -s 2>/dev/null`.chomp
3763
+
3764
+ text << sprintf(" %-15s %s\n", "Hostname:".fg(249), hostname.fg(156))
3765
+ text << sprintf(" %-15s %s\n", "Uptime:".fg(249), uptime_raw.fg(156))
3766
+ text << sprintf(" %-15s %s\n", "Boot time:".fg(249), boot_time.fg(156))
3767
+
3768
+ # OS Info
3713
3769
  os_name = `awk -F '"' '/PRETTY/ {print $2}' /etc/os-release 2>/dev/null`.chomp
3714
3770
  kernel_version = `uname -r 2>/dev/null`.chomp
3715
3771
  architecture = `uname -m 2>/dev/null`.chomp
3716
- text << sprintf(" %-15s %s\n", "Distribution:", os_name.fg(156))
3717
- text << sprintf(" %-15s %s\n", "Kernel:", kernel_version.fg(156))
3718
- text << sprintf(" %-15s %s\n", "Architecture:", architecture.fg(156))
3772
+
3773
+ text << sprintf(" %-15s %s\n", "OS:".fg(249), os_name.fg(156))
3774
+ text << sprintf(" %-15s %s\n", "Kernel:".fg(249), kernel_version.fg(156))
3775
+ text << sprintf(" %-15s %s\n", "Architecture:".fg(249), architecture.fg(156))
3719
3776
  text << "\n"
3720
3777
  rescue # rubocop:disable Lint/SuppressedException
3721
3778
  end
3722
3779
 
3723
3780
  begin
3724
- # Hardware Information
3725
- text << "Hardware:\n".fg(226)
3781
+ # Hardware Information
3782
+ text << "Hardware".fg(226).b + "\n"
3783
+ text << "─" * 20 + "\n"
3784
+
3785
+ # CPU Info
3726
3786
  cpu_count = `nproc 2>/dev/null`.chomp
3727
3787
  cpuinfo = `lscpu 2>/dev/null`
3728
- cpu_model = cpuinfo[/^.*Model name:\s*(.*)/, 1] || "Unknown"
3729
- cpu_max = cpuinfo[/^.*CPU max MHz:\s*(.*)/, 1]&.to_i || 0
3730
- cpu_min = cpuinfo[/^.*CPU min MHz:\s*(.*)/, 1]&.to_i || 0
3731
-
3732
- text << sprintf(" %-15s %s cores\n", "CPU Count:", cpu_count.fg(156))
3733
- text << sprintf(" %-15s %s\n", "CPU Model:", cpu_model.fg(156))
3734
- if cpu_max > 0 && cpu_min > 0
3735
- text << sprintf(" %-15s %d-%d MHz\n", "CPU Speed:", cpu_min, cpu_max)
3788
+ cpu_model = cpuinfo[/^.*Model name:\s*(.*)/, 1]&.strip || "Unknown"
3789
+ cpu_model = cpu_model[0..45] + "..." if cpu_model.length > 48
3790
+
3791
+ # Current CPU frequency
3792
+ cpu_freq = `grep "cpu MHz" /proc/cpuinfo 2>/dev/null | head -1 | awk '{print $4}'`.chomp.to_f.round
3793
+
3794
+ # Load average
3795
+ loadavg = `cat /proc/loadavg 2>/dev/null`.chomp.split[0..2].join(' ')
3796
+
3797
+ text << sprintf(" %-15s %s\n", "CPU Model:".fg(249), cpu_model.fg(156))
3798
+ text << sprintf(" %-15s %s\n", "CPU Cores:".fg(249), "#{cpu_count} cores".fg(156))
3799
+ text << sprintf(" %-15s %s\n", "Current Freq:".fg(249), "#{cpu_freq} MHz".fg(156)) if cpu_freq > 0
3800
+ text << sprintf(" %-15s %s\n", "Load Average:".fg(249), loadavg.fg(156))
3801
+
3802
+ # Temperature if available
3803
+ if File.exist?("/sys/class/thermal/thermal_zone0/temp")
3804
+ temp = (File.read("/sys/class/thermal/thermal_zone0/temp").to_i / 1000.0).round(1)
3805
+ temp_color = temp > 80 ? 196 : temp > 60 ? 220 : 156
3806
+ text << sprintf(" %-15s %s\n", "CPU Temp:".fg(249), "#{temp}°C".fg(temp_color))
3736
3807
  end
3808
+
3737
3809
  text << "\n"
3738
3810
  rescue # rubocop:disable Lint/SuppressedException
3739
3811
  end
3740
3812
 
3741
3813
  begin
3742
- # Memory Information
3743
- text << "Memory Usage:\n".fg(226)
3744
- mem_output = `free -h 2>/dev/null`
3745
- if mem_output && !mem_output.empty?
3746
- mem_lines = mem_output.lines
3747
- mem_lines.each_with_index do |line, i|
3748
- if i == 0 # Header
3749
- text << " " + line.strip.fg(240) + "\n"
3750
- else
3751
- text << " " + line.strip.fg(156) + "\n"
3752
- end
3753
- end
3814
+ # Memory Information with visual bar
3815
+ text << "Memory".fg(226).b + "\n"
3816
+ text << "─" * 20 + "\n"
3817
+
3818
+ mem_info = `free -b 2>/dev/null | grep Mem:`
3819
+ if mem_info && !mem_info.empty?
3820
+ parts = mem_info.split
3821
+ total = parts[1].to_i
3822
+ used = parts[2].to_i
3823
+ available = parts[6].to_i
3824
+
3825
+ # Convert to human readable
3826
+ total_h = (total / 1024.0 / 1024.0 / 1024.0).round(1)
3827
+ used_h = (used / 1024.0 / 1024.0 / 1024.0).round(1)
3828
+ available_h = (available / 1024.0 / 1024.0 / 1024.0).round(1)
3829
+ percent = ((used.to_f / total) * 100).round
3830
+
3831
+ # Create visual bar (40 chars wide)
3832
+ bar_width = 40
3833
+ filled = (percent * bar_width / 100.0).round
3834
+ bar = "█" * filled + "░" * (bar_width - filled)
3835
+
3836
+ # Color based on usage
3837
+ bar_color = percent > 90 ? 196 : percent > 70 ? 220 : 156
3838
+
3839
+ # Bar first
3840
+ text << " " + bar.fg(bar_color) + " " + "#{percent}%".fg(bar_color) + "\n"
3841
+ text << sprintf(" %-15s %s\n", "Total:".fg(249), "#{total_h} GB".fg(156))
3842
+ text << sprintf(" %-15s %s\n", "Used:".fg(249), "#{used_h} GB".fg(bar_color))
3843
+ text << sprintf(" %-15s %s\n", "Available:".fg(249), "#{available_h} GB".fg(156))
3844
+ end
3845
+
3846
+ # Swap info
3847
+ swap_info = `free -b 2>/dev/null | grep Swap:`
3848
+ if swap_info && !swap_info.empty? && swap_info.split[1].to_i > 0
3849
+ parts = swap_info.split
3850
+ swap_total = (parts[1].to_i / 1024.0 / 1024.0 / 1024.0).round(1)
3851
+ swap_used = (parts[2].to_i / 1024.0 / 1024.0 / 1024.0).round(1)
3852
+ text << sprintf(" %-15s %s\n", "Swap:".fg(249), "#{swap_used}/#{swap_total} GB".fg(156))
3754
3853
  end
3854
+
3755
3855
  text << "\n"
3756
3856
  rescue # rubocop:disable Lint/SuppressedException
3757
3857
  end
3758
3858
 
3759
3859
  begin
3760
- # Environment Information
3761
- text << "Environment:\n".fg(226)
3762
- shell = `echo $SHELL 2>/dev/null`.sub(%r{.*/}, '').chomp
3763
- terminal = `echo $TERM 2>/dev/null`.chomp
3764
- packages = `dpkg-query -l 2>/dev/null | grep -c '^.i'`.chomp
3765
- packages = `pacman -Q 2>/dev/null | wc -l`.chomp if packages == '0'
3766
- packages = "Unknown" if packages == '0'
3767
-
3768
- text << sprintf(" %-15s %s\n", "Shell:", shell.fg(156))
3769
- text << sprintf(" %-15s %s\n", "Terminal:", terminal.fg(156))
3770
- text << sprintf(" %-15s %s\n", "Packages:", packages.fg(156))
3860
+ # Storage Information with visual bars
3861
+ text << "Storage".fg(226).b + "\n"
3862
+ text << "─" * 20 + "\n"
3863
+
3864
+ # Get disk usage for main filesystems only
3865
+ disk_output = `df -BG 2>/dev/null | grep -E '^/dev/' | grep -vE '/snap/|/tmp\\.| /run|/dev/loop'`
3866
+ if disk_output && !disk_output.empty?
3867
+ disk_output.lines.each do |line|
3868
+ parts = line.split
3869
+ next if parts.length < 6
3870
+
3871
+ filesystem = parts[0].sub('/dev/', '')
3872
+ size = parts[1].sub('G', '').to_i
3873
+ used = parts[2].sub('G', '').to_i
3874
+ avail = parts[3].sub('G', '').to_i
3875
+ percent = parts[4].sub('%', '').to_i
3876
+ mount = parts[5]
3877
+
3878
+ # Skip if too small
3879
+ next if size < 1
3880
+
3881
+ # Shorten mount point if needed
3882
+ mount_display = mount.length > 15 ? "...#{mount[-12..]}" : mount
3883
+
3884
+ # Mini bar (10 chars)
3885
+ bar_width = 10
3886
+ filled = (percent * bar_width / 100.0).round
3887
+ bar = "█" * filled + "░" * (bar_width - filled)
3888
+
3889
+ # Color based on usage
3890
+ color = percent > 90 ? 196 : percent > 80 ? 220 : 156
3891
+
3892
+ info = sprintf(" %-15s %3dG/%3dG %s %3d%%",
3893
+ mount_display, used, size, bar, percent)
3894
+ text << info.fg(color) + "\n"
3895
+ end
3896
+ end
3771
3897
  text << "\n"
3772
3898
  rescue # rubocop:disable Lint/SuppressedException
3773
3899
  end
3774
3900
 
3775
3901
  begin
3776
- # Disk Usage
3777
- text << "Disk Usage:\n".fg(226)
3778
- disk_output = `df -h 2>/dev/null | head -8`
3779
- if disk_output && !disk_output.empty?
3780
- disk_lines = disk_output.lines
3781
- disk_lines.each_with_index do |line, i|
3782
- if i == 0 # Header
3783
- text << " " + line.strip.fg(240) + "\n"
3784
- else
3785
- # Highlight usage percentage
3786
- colored_line = line.gsub(/(\d+)%/) do |match|
3787
- percent = $1.to_i
3788
- color = percent > 90 ? 196 : percent > 80 ? 220 : 156
3789
- match.fg(color)
3790
- end
3791
- text << " " + colored_line.strip.fg(249) + "\n"
3792
- end
3902
+ # Network Information
3903
+ text << "Network".fg(226).b + "\n"
3904
+ text << "─" * 20 + "\n"
3905
+
3906
+ # Get active network interfaces
3907
+ interfaces = `ip -o link show 2>/dev/null | grep -E 'state UP|UNKNOWN' | awk '{print $2}' | sed 's/://'`.chomp.split("\n")
3908
+
3909
+ interfaces.each do |iface|
3910
+ next if iface.empty? || iface == "lo"
3911
+
3912
+ # Get IP address
3913
+ ip_addr = `ip -4 addr show #{iface} 2>/dev/null | grep -oP '(?<=inet\\s)\\d+(\\.\\d+){3}'`.chomp
3914
+ next if ip_addr.empty?
3915
+
3916
+ # Get RX/TX stats
3917
+ rx_bytes = File.read("/sys/class/net/#{iface}/statistics/rx_bytes").to_i rescue 0
3918
+ tx_bytes = File.read("/sys/class/net/#{iface}/statistics/tx_bytes").to_i rescue 0
3919
+
3920
+ rx_mb = (rx_bytes / 1024.0 / 1024.0).round(1)
3921
+ tx_mb = (tx_bytes / 1024.0 / 1024.0).round(1)
3922
+
3923
+ text << sprintf(" %-15s %s\n", "#{iface}:".fg(249), ip_addr.fg(156))
3924
+ text << sprintf(" %-15s %s\n", " Traffic:".fg(249), "↓#{rx_mb}MB ↑#{tx_mb}MB".fg(156))
3925
+ end
3926
+
3927
+ # Public IP if available (with timeout)
3928
+ begin
3929
+ require 'timeout'
3930
+ public_ip = Timeout.timeout(2) do
3931
+ `curl -s ifconfig.me 2>/dev/null`.chomp
3793
3932
  end
3933
+ text << sprintf(" %-15s %s\n", "Public IP:".fg(249), public_ip.fg(156)) unless public_ip.empty?
3934
+ rescue
3935
+ # Skip public IP if timeout or error
3794
3936
  end
3937
+
3795
3938
  text << "\n"
3796
3939
  rescue # rubocop:disable Lint/SuppressedException
3797
3940
  end
3798
3941
 
3799
3942
  begin
3800
- # Top Processes
3801
- text << "Top Processes (CPU & Memory):\n".fg(226)
3802
- ps_output = `ps -eo comm,pid,user,pcpu,pmem,stat --sort -pcpu,-pmem 2>/dev/null | head -8`
3803
- if ps_output && !ps_output.empty?
3804
- ps_lines = ps_output.lines
3805
- ps_lines.each_with_index do |line, i|
3806
- if i == 0 # Header
3807
- text << " " + line.strip.fg(240) + "\n"
3808
- else
3809
- text << " " + line.strip.fg(249) + "\n"
3810
- end
3811
- end
3943
+ # Environment Information
3944
+ text << "Environment".fg(226).b + "\n"
3945
+ text << "─" * 20 + "\n"
3946
+
3947
+ # Shell and terminal
3948
+ shell = ENV['SHELL']&.sub(%r{.*/}, '') || 'unknown'
3949
+ terminal = ENV['TERM'] || 'unknown'
3950
+ text << sprintf(" %-15s %s\n", "Shell:".fg(249), shell.fg(156))
3951
+ text << sprintf(" %-15s %s\n", "Terminal:".fg(249), terminal.fg(156))
3952
+
3953
+ # Package count
3954
+ if system("which dpkg >/dev/null 2>&1")
3955
+ packages = `dpkg-query -l 2>/dev/null | grep -c '^ii'`.chomp
3956
+ elsif system("which rpm >/dev/null 2>&1")
3957
+ packages = `rpm -qa 2>/dev/null | wc -l`.chomp
3958
+ elsif system("which pacman >/dev/null 2>&1")
3959
+ packages = `pacman -Q 2>/dev/null | wc -l`.chomp
3960
+ else
3961
+ packages = "unknown"
3962
+ end
3963
+ text << sprintf(" %-15s %s\n", "Packages:".fg(249), packages.fg(156))
3964
+
3965
+ # Desktop environment
3966
+ desktop = ENV['XDG_CURRENT_DESKTOP'] || ENV['DESKTOP_SESSION'] || 'none'
3967
+ text << sprintf(" %-15s %s\n", "Desktop:".fg(249), desktop.fg(156))
3968
+
3969
+ # GTK Theme if available
3970
+ if File.exist?("#{ENV['HOME']}/.config/gtk-3.0/settings.ini")
3971
+ gtk_theme = `grep gtk-theme-name ~/.config/gtk-3.0/settings.ini 2>/dev/null | cut -d= -f2`.chomp
3972
+ text << sprintf(" %-15s %s\n", "GTK Theme:".fg(249), gtk_theme.fg(156)) unless gtk_theme.empty?
3812
3973
  end
3974
+
3813
3975
  text << "\n"
3814
3976
  rescue # rubocop:disable Lint/SuppressedException
3815
3977
  end
3816
3978
 
3817
3979
  begin
3818
- # System Messages
3819
- text << "Recent System Messages:\n".fg(226)
3820
- dmesg_output = `dmesg 2>/dev/null | tail -5`
3821
- if dmesg_output && !dmesg_output.empty?
3822
- dmesg_output.lines.reverse.each do |line|
3823
- # Color code different message types
3824
- colored_line = case line
3825
- when /error|fail|critical/i then line.fg(196)
3826
- when /warn/i then line.fg(220)
3827
- when /info/i then line.fg(156)
3828
- else line.fg(249)
3829
- end
3830
- text << " " + colored_line.strip + "\n"
3980
+ # Services & Processes
3981
+ text << "Services & Processes".fg(226).b + "\n"
3982
+ text << "─" * 20 + "\n"
3983
+
3984
+ # Running services count
3985
+ services_count = `systemctl list-units --type=service --state=running 2>/dev/null | grep -c '\.service'`.chomp
3986
+ text << sprintf(" %-15s %s\n", "Services:".fg(249), "#{services_count} running".fg(156)) unless services_count == "0"
3987
+
3988
+ # Failed services
3989
+ failed_count = `systemctl list-units --type=service --state=failed 2>/dev/null | grep -c '\.service'`.chomp
3990
+ if failed_count.to_i > 0
3991
+ text << sprintf(" %-15s %s\n", "Failed:".fg(249), "#{failed_count} service#{failed_count.to_i > 1 ? 's' : ''}".fg(196))
3992
+ # Get the actual failed services (need to parse the full output)
3993
+ failed_output = `systemctl list-units --type=service --state=failed --no-legend 2>/dev/null`.chomp
3994
+ unless failed_output.empty?
3995
+ failed_output.lines.each do |line|
3996
+ # Skip the bullet point and extract service name (second field)
3997
+ parts = line.strip.split(/\s+/)
3998
+ if parts.length >= 2
3999
+ service_name = parts[1]
4000
+ if service_name && service_name.include?('.service')
4001
+ # Shorten long service names if needed
4002
+ display_name = service_name.length > 40 ? service_name[0..37] + "..." : service_name
4003
+ text << " → #{display_name}".fg(196) + "\n"
4004
+ end
4005
+ end
4006
+ end
3831
4007
  end
3832
- else
3833
- text << " " + "dmesg requires root access".fg(240) + "\n"
3834
- text << " " + "Run: sudo sysctl kernel.dmesg_restrict=0".fg(240) + "\n"
3835
4008
  end
4009
+
4010
+ # Process count
4011
+ proc_count = `ps aux | wc -l`.chomp.to_i - 1
4012
+ text << sprintf(" %-15s %s\n", "Processes:".fg(249), proc_count.to_s.fg(156))
4013
+
4014
+ # Users logged in
4015
+ users = `who | awk '{print $1}' | sort -u | wc -l`.chomp
4016
+ text << sprintf(" %-15s %s\n", "Users:".fg(249), "#{users} logged in".fg(156))
4017
+
4018
+ text << "\n"
3836
4019
  rescue # rubocop:disable Lint/SuppressedException
3837
4020
  end
3838
4021
 
4022
+ # Footer with system load average
4023
+ begin
4024
+ text << "─" * 50 + "\n"
4025
+
4026
+ # Get load average values
4027
+ loadavg = File.read("/proc/loadavg").split[0..2].map(&:to_f) rescue [0, 0, 0]
4028
+ cpu_count = `nproc 2>/dev/null`.chomp.to_i
4029
+ cpu_count = 1 if cpu_count == 0
4030
+
4031
+ # Format load values with color coding
4032
+ load_values = loadavg.map do |load|
4033
+ percent = (load / cpu_count * 100).round
4034
+ color = percent > 100 ? 196 : percent > 80 ? 220 : 156
4035
+ load.round(2).to_s.fg(color)
4036
+ end
4037
+
4038
+ text << "Load Average: " + load_values.join(" ") + " (1m 5m 15m)\n"
4039
+ text << "Updated: #{Time.now.strftime('%H:%M:%S')}".fg(240) + " - Press 'ENTER' to return".fg(249)
4040
+ rescue
4041
+ # Skip footer if error
4042
+ end
4043
+
3839
4044
  @pR.say(text)
3840
- rescue
3841
- @pR.say('Unable to show system info'.fg(196))
4045
+ rescue => e
4046
+ @pR.say("Unable to show system info\n#{e.message}".fg(196))
3842
4047
  end
3843
4048
 
3844
4049
  def make_directory # {{{3
@@ -3865,6 +4070,24 @@ def command_mode # {{{3
3865
4070
  sel = Shellwords.escape(@selected.to_s)
3866
4071
  tg = @tagged.map { |p| Shellwords.escape(p) }.join(' ')
3867
4072
  cmd = raw.gsub('@s', sel).gsub('@t', tg)
4073
+
4074
+ # Handle cd commands specially
4075
+ if cmd.match(/^\s*cd\s*(.*)$/)
4076
+ target = $1.strip
4077
+ target = ENV['HOME'] if target.empty? # Default to home directory
4078
+ target = File.expand_path(target)
4079
+
4080
+ if File.directory?(target)
4081
+ @directory[Dir.pwd] = @index; mark_latest
4082
+ Dir.chdir(target)
4083
+ @pB.update = @pR.update = true
4084
+ refresh
4085
+ return
4086
+ else
4087
+ @pB.say("cd: #{target}: No such directory".fg(196))
4088
+ return
4089
+ end
4090
+ end
3868
4091
  # Determine program name
3869
4092
  prog = Shellwords.split(cmd).first
3870
4093
  prog = File.basename(prog) if prog
@@ -4823,26 +5046,21 @@ def open_selected(html = nil) # OPEN SELECTED FILE {{{2
4823
5046
  end
4824
5047
  # Don't try to read large files or PDFs as text for validation
4825
5048
  if !@selected&.match(@pdffile) && File.size(@selected) < 1_000_000 && File.read(@selected).force_encoding('UTF-8').valid_encoding? # Pure text
4826
- # Flush any pending input before switching terminal modes
4827
- while IO.select([$stdin], nil, nil, 0)
4828
- begin
4829
- $stdin.read_nonblock(4096)
4830
- rescue IO::WaitReadable, EOFError
4831
- break
4832
- end
4833
- end
4834
- system("stty #{ORIG_STTY} < /dev/tty")
4835
- # Clear to top-left
5049
+ # Save terminal state before launching editor
5050
+ system("stty -g < /dev/tty > /tmp/rtfm_stty_$$")
5051
+ # Clear and reset terminal for editor
4836
5052
  system('clear < /dev/tty > /dev/tty')
4837
5053
  Cursor.show
4838
- editor = ENV.fetch('EDITOR', 'vi') # Launch $EDITOR on the real TTY
5054
+ # Launch editor
5055
+ editor = ENV.fetch('EDITOR', 'vi')
4839
5056
  system("#{editor} #{Shellwords.escape(@selected)}")
4840
- system('stty raw -echo isig < /dev/tty')
4841
- $stdin.raw!
4842
- $stdin.echo = false
4843
- Rcurses.init! # Reinitialize rcurses to fix input handling
5057
+ # Restore terminal state
5058
+ system("stty $(cat /tmp/rtfm_stty_$$) < /dev/tty")
5059
+ system("rm -f /tmp/rtfm_stty_$$")
5060
+ # Reinitialize rcurses
5061
+ Rcurses.init!
4844
5062
  Cursor.hide
4845
- Rcurses.clear_screen # Redraw RTFM
5063
+ Rcurses.clear_screen
4846
5064
  refresh
4847
5065
  render
4848
5066
  return
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: 6.0.4
4
+ version: 6.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geir Isene
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-05 00:00:00.000000000 Z
11
+ date: 2025-07-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rcurses
@@ -53,8 +53,8 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '7.4'
55
55
  description: |-
56
- Major release - RTFM v6.0: Remote SSH/SFTP browsing with seamless navigation, interactive SSH shell integration, enhanced help system with color-coded formatting, SSH connection comments, comprehensive undo system, and performance optimizations.
57
- A full featured terminal browser with syntax highlighted files, images shown in the terminal, videos thumbnailed, etc. You can bookmark and jump around easily, delete, rename, copy, symlink and move files. RTFM is one of the most feature-packed terminal file managers.
56
+ RTFM v6.0.7: Fixed preview updates after file deletion and significantly enhanced System Info display with visual indicators, environment details, and service monitoring.
57
+ A full featured terminal browser with syntax highlighted files, images shown in the terminal, videos thumbnailed, etc. Features include remote SSH/SFTP browsing, interactive SSH shell, comprehensive undo system, bookmarks, and much more. You can bookmark and jump around easily, delete, rename, copy, symlink and move files. RTFM is one of the most feature-packed terminal file managers.
58
58
  email: g@isene.com
59
59
  executables:
60
60
  - rtfm