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.
- checksums.yaml +4 -4
- data/bin/rtfm +324 -106
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 88ef15c96d6cd2e481ea3d0741da517dc256f7790673769457c9b78686721391
|
4
|
+
data.tar.gz: c1db40c673a60aa766fb3a79623e7ac912f0cf6898798db4d6731029cde8c7bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
-
|
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
|
-
|
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 = "
|
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
|
-
#
|
3712
|
-
text << "
|
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
|
-
|
3717
|
-
text << sprintf(" %-15s %s\n", "
|
3718
|
-
text << sprintf(" %-15s %s\n", "
|
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
|
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
|
-
|
3730
|
-
|
3731
|
-
|
3732
|
-
|
3733
|
-
|
3734
|
-
|
3735
|
-
|
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
|
3744
|
-
|
3745
|
-
|
3746
|
-
|
3747
|
-
|
3748
|
-
|
3749
|
-
|
3750
|
-
|
3751
|
-
|
3752
|
-
|
3753
|
-
|
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
|
-
#
|
3761
|
-
text << "
|
3762
|
-
|
3763
|
-
|
3764
|
-
|
3765
|
-
|
3766
|
-
|
3767
|
-
|
3768
|
-
|
3769
|
-
|
3770
|
-
|
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
|
-
#
|
3777
|
-
text << "
|
3778
|
-
|
3779
|
-
|
3780
|
-
|
3781
|
-
|
3782
|
-
|
3783
|
-
|
3784
|
-
|
3785
|
-
|
3786
|
-
|
3787
|
-
|
3788
|
-
|
3789
|
-
|
3790
|
-
|
3791
|
-
|
3792
|
-
|
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
|
-
#
|
3801
|
-
text << "
|
3802
|
-
|
3803
|
-
|
3804
|
-
|
3805
|
-
|
3806
|
-
|
3807
|
-
|
3808
|
-
|
3809
|
-
|
3810
|
-
|
3811
|
-
|
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
|
-
#
|
3819
|
-
text << "
|
3820
|
-
|
3821
|
-
|
3822
|
-
|
3823
|
-
|
3824
|
-
|
3825
|
-
|
3826
|
-
|
3827
|
-
|
3828
|
-
|
3829
|
-
|
3830
|
-
|
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(
|
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
|
-
#
|
4827
|
-
|
4828
|
-
|
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
|
-
|
5054
|
+
# Launch editor
|
5055
|
+
editor = ENV.fetch('EDITOR', 'vi')
|
4839
5056
|
system("#{editor} #{Shellwords.escape(@selected)}")
|
4840
|
-
|
4841
|
-
$
|
4842
|
-
|
4843
|
-
|
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
|
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
|
+
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-
|
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
|
-
|
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
|