rtfm-filemanager 8.0.1 → 8.1.0
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/CHANGELOG.md +5 -0
- data/README.md +8 -0
- data/bin/rtfm +82 -27
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f1ec26a45d5adbaeb2aeb253b7078c255ae40bdc6aebb45f26234f0ba28c89de
|
|
4
|
+
data.tar.gz: 4aeaf423db737eb9956e22f53eea0f6e8b02cbff48c62b92b13834932074bc54
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7df8b94d058bd3c169cf08d59b81a1cb1ceb0c26f21617d57e04c89083317200f88f6aae9164fc7a1dc1d546a08a07c49861dc2fcdf7bf54ca30aaa868d42602
|
|
7
|
+
data.tar.gz: 4cbd8be47ef2ee06285e1fcf89a03d01a44524070c5019c6c6491a50416677a053d42476769919702462b0c4780245a4d579fc72003b61bebeca15fa3f144328
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,11 @@ All notable changes to RTFM will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [8.1.0] - 2026-03-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **File picker mode** (`--pick`) - Launch RTFM as a file picker for other applications. Tag files with `t`, quit with `q`, and the tagged file paths are written to the specified output file. Usage: `rtfm --pick=/path/to/output.txt [start_directory]`
|
|
12
|
+
|
|
8
13
|
## [8.0.1] - 2026-03-07
|
|
9
14
|
|
|
10
15
|
### Changed
|
data/README.md
CHANGED
|
@@ -22,6 +22,9 @@ rtfm
|
|
|
22
22
|
# Or start in specific directory
|
|
23
23
|
rtfm ~/Documents
|
|
24
24
|
|
|
25
|
+
# Use as file picker (tag files with t, quit with q)
|
|
26
|
+
rtfm --pick=/tmp/selected_files.txt
|
|
27
|
+
|
|
25
28
|
# Press ? for help
|
|
26
29
|
```
|
|
27
30
|
|
|
@@ -84,6 +87,7 @@ After first run, use `r` command to launch RTFM and exit into your current direc
|
|
|
84
87
|
- **Cryptographic hashing** - Directory tree verification
|
|
85
88
|
- **OpenAI integration** - File descriptions and interactive chat
|
|
86
89
|
- **Tab management** - Multiple tabs with duplication and renaming
|
|
90
|
+
- **File picker mode** - Use RTFM as a file selector for other applications (`--pick`)
|
|
87
91
|
- **Fuzzy search** - fzf integration
|
|
88
92
|
- **Navi integration** - Interactive command cheatsheets
|
|
89
93
|
|
|
@@ -646,6 +650,10 @@ Best image experience with: kitty, urxvt, xterm, mlterm, foot
|
|
|
646
650
|
|
|
647
651
|
## Latest Updates
|
|
648
652
|
|
|
653
|
+
### Version 8.1 Highlights
|
|
654
|
+
|
|
655
|
+
- **File picker mode** - `rtfm --pick=/path/to/output.txt` launches RTFM as a file selector. Browse and tag files normally with `t`, then quit with `q`. Tagged file paths are written one per line to the output file. Enables integration with email clients, upload tools, and other applications that need a file selection dialog.
|
|
656
|
+
|
|
649
657
|
### Version 7.3 Highlights
|
|
650
658
|
|
|
651
659
|
- **Modern image display** with termpix gem (Sixel + w3m protocols)
|
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.0
|
|
21
|
+
@version = '8.1.0' # File picker mode (--pick) for integration with other tools
|
|
22
22
|
|
|
23
23
|
# SAVE & STORE TERMINAL {{{1
|
|
24
24
|
ORIG_STTY = `stty -g`.chomp
|
|
@@ -825,9 +825,22 @@ rescue StandardError => e
|
|
|
825
825
|
errormsg("⚠ Errors while loading #{CONFIG_FILE}\nCheck your config file or delete it to remake in a fresh RTFM start.", e)
|
|
826
826
|
end
|
|
827
827
|
load_config
|
|
828
|
+
# Load persisted width setting if it exists
|
|
829
|
+
width_file = File.join(RTFM_HOME, 'width')
|
|
830
|
+
if File.exist?(width_file)
|
|
831
|
+
saved_width = File.read(width_file).strip.to_i
|
|
832
|
+
@width = saved_width if saved_width >= 2 && saved_width <= 7
|
|
833
|
+
end
|
|
828
834
|
@marks['0'] = Dir.pwd # Original dir
|
|
829
835
|
@marks["'"] = Dir.pwd # Initial mark
|
|
830
836
|
|
|
837
|
+
# File picker mode: --pick=/path/to/output writes tagged files on quit
|
|
838
|
+
@pick_output = nil
|
|
839
|
+
if (pick_arg = ARGV.find { |a| a.start_with?('--pick=') })
|
|
840
|
+
@pick_output = pick_arg.split('=', 2).last
|
|
841
|
+
ARGV.delete(pick_arg)
|
|
842
|
+
end
|
|
843
|
+
|
|
831
844
|
# Handle start dir {{{2
|
|
832
845
|
Dir.chdir(ARGV.shift) if ARGV[0] && File.directory?(ARGV[0])
|
|
833
846
|
|
|
@@ -1203,7 +1216,10 @@ end
|
|
|
1203
1216
|
def change_width # {{{3
|
|
1204
1217
|
@width += 1
|
|
1205
1218
|
@width = 2 if @width == 8
|
|
1206
|
-
|
|
1219
|
+
|
|
1220
|
+
# Persist width setting
|
|
1221
|
+
File.write(File.join(RTFM_HOME, 'width'), @width.to_s) rescue nil
|
|
1222
|
+
|
|
1207
1223
|
if @dual_pane
|
|
1208
1224
|
# Show width setting info for dual-pane mode using the new ratio calculation
|
|
1209
1225
|
dir_panes_ratio = [0.5 - (@width - 2) * 0.034, 0.33].max
|
|
@@ -1212,7 +1228,7 @@ def change_width # {{{3
|
|
|
1212
1228
|
else
|
|
1213
1229
|
@pB.say("Width: #{@width}")
|
|
1214
1230
|
end
|
|
1215
|
-
|
|
1231
|
+
|
|
1216
1232
|
refresh
|
|
1217
1233
|
@pL.update = @pR.update = @pT.update = @pB.update = true
|
|
1218
1234
|
# Also update dual-pane objects if they exist
|
|
@@ -2564,9 +2580,9 @@ end
|
|
|
2564
2580
|
def render_side_by_side(lines1, lines2, name1, name2, offset, page_height) # {{{3
|
|
2565
2581
|
half_width = (@pR.w - 7) / 2
|
|
2566
2582
|
half_width = [half_width, 10].max
|
|
2567
|
-
text = "\n"
|
|
2568
|
-
text << sprintf(" %-#{half_width}s
|
|
2569
|
-
text << " " + "
|
|
2583
|
+
text = +"\n"
|
|
2584
|
+
text << sprintf(" %-#{half_width}s \u2502 %s\n", name1[0, half_width], name2[0, half_width]).fg(226)
|
|
2585
|
+
text << " " + "\u2500" * half_width + "\u2500\u253C\u2500" + "\u2500" * half_width + "\n"
|
|
2570
2586
|
|
|
2571
2587
|
max_lines = [lines1.size, lines2.size].max
|
|
2572
2588
|
end_line = [offset + page_height, max_lines].min
|
|
@@ -2577,7 +2593,7 @@ def render_side_by_side(lines1, lines2, name1, name2, offset, page_height) # {{{
|
|
|
2577
2593
|
same = lines1[i] == lines2[i]
|
|
2578
2594
|
color = same ? 240 : (lines1[i].nil? ? 156 : (lines2[i].nil? ? 196 : 226))
|
|
2579
2595
|
|
|
2580
|
-
text << sprintf(" %-#{half_width}s
|
|
2596
|
+
text << sprintf(" %-#{half_width}s \u2502 %s\n", l1, l2).fg(color)
|
|
2581
2597
|
end
|
|
2582
2598
|
|
|
2583
2599
|
text
|
|
@@ -6038,6 +6054,42 @@ def mark_latest # UPDATE MARKS LIST {{{2
|
|
|
6038
6054
|
@marks["'"] = Dir.pwd
|
|
6039
6055
|
end
|
|
6040
6056
|
|
|
6057
|
+
def mailcap_command(file_path) # FIND VIEWER VIA ~/.mailcap {{{2
|
|
6058
|
+
# Detect MIME type
|
|
6059
|
+
mime_type = IO.popen(['file', '--brief', '--mime-type', file_path]) { |f| f.read.strip }
|
|
6060
|
+
return nil if mime_type.nil? || mime_type.empty?
|
|
6061
|
+
|
|
6062
|
+
# Search mailcap files in order of preference
|
|
6063
|
+
mailcap_files = [File.join(Dir.home, '.mailcap'), '/etc/mailcap']
|
|
6064
|
+
mailcap_files.each do |mc_file|
|
|
6065
|
+
next unless File.exist?(mc_file)
|
|
6066
|
+
File.foreach(mc_file) do |line|
|
|
6067
|
+
line = line.strip
|
|
6068
|
+
next if line.empty? || line.start_with?('#')
|
|
6069
|
+
# Handle continuation lines (trailing backslash)
|
|
6070
|
+
# For simplicity, skip multi-line entries
|
|
6071
|
+
next if line.end_with?('\\')
|
|
6072
|
+
parts = line.split(';').map(&:strip)
|
|
6073
|
+
next if parts.length < 2
|
|
6074
|
+
pattern = parts[0]
|
|
6075
|
+
command = parts[1]
|
|
6076
|
+
flags = parts[2..].join(';')
|
|
6077
|
+
# Skip entries with copiousoutput (meant for text dump, not viewing)
|
|
6078
|
+
next if flags =~ /copiousoutput/i
|
|
6079
|
+
# Check if MIME type matches (supports wildcards like "image/*")
|
|
6080
|
+
pattern_re = Regexp.new('^' + Regexp.escape(pattern).gsub('\\*', '.*') + '$', Regexp::IGNORECASE)
|
|
6081
|
+
next unless mime_type.match?(pattern_re)
|
|
6082
|
+
# Replace %s with the file path in the command
|
|
6083
|
+
if command.include?('%s')
|
|
6084
|
+
return command.gsub('%s', Shellwords.escape(file_path))
|
|
6085
|
+
else
|
|
6086
|
+
return "#{command} #{Shellwords.escape(file_path)}"
|
|
6087
|
+
end
|
|
6088
|
+
end
|
|
6089
|
+
end
|
|
6090
|
+
nil # No mailcap match found
|
|
6091
|
+
end
|
|
6092
|
+
|
|
6041
6093
|
def get_interactive_program(file_path) # HELPER FOR OPEN_SELECTED TO USE @interactive {{{2
|
|
6042
6094
|
begin
|
|
6043
6095
|
inter_list = (@interactive || '').split(',').map(&:strip)
|
|
@@ -6146,16 +6198,11 @@ def open_selected(html = nil) # OPEN SELECTED FILE {{{2
|
|
|
6146
6198
|
# Use tagged items if any exist, otherwise use selected item
|
|
6147
6199
|
paths = @tagged.empty? ? [@selected] : @tagged
|
|
6148
6200
|
if html # html mode - open in HTML-browser
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
if File.exist?(tmpfile)
|
|
6153
|
-
sleep 0.5
|
|
6154
|
-
err = File.read(tmpfile)
|
|
6155
|
-
showimage('clear') if @image
|
|
6156
|
-
@pR.say(err.fg(196))
|
|
6157
|
-
File.delete(tmpfile)
|
|
6201
|
+
paths.each do |p|
|
|
6202
|
+
pid = Process.spawn('xdg-open', p, [:out, :err] => '/dev/null')
|
|
6203
|
+
Process.detach(pid)
|
|
6158
6204
|
end
|
|
6205
|
+
Rcurses.clear_screen; refresh; render
|
|
6159
6206
|
return
|
|
6160
6207
|
end
|
|
6161
6208
|
# Check if file is text (UTF-8, UTF-16, or other text encodings)
|
|
@@ -6209,21 +6256,22 @@ def open_selected(html = nil) # OPEN SELECTED FILE {{{2
|
|
|
6209
6256
|
render
|
|
6210
6257
|
return
|
|
6211
6258
|
end
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6259
|
+
# Try mailcap first, then fall back to run-mailcap or xdg-open
|
|
6260
|
+
mc_cmd = mailcap_command(@selected)
|
|
6261
|
+
if mc_cmd
|
|
6262
|
+
pid = Process.spawn(mc_cmd, :in => '/dev/null', [:out, :err] => '/dev/null')
|
|
6263
|
+
Process.detach(pid)
|
|
6264
|
+
elsif @runmailcap
|
|
6265
|
+
paths.each do |p|
|
|
6266
|
+
pid = Process.spawn('run-mailcap', p, :in => '/dev/null', [:out, :err] => '/dev/null')
|
|
6267
|
+
Process.detach(pid)
|
|
6268
|
+
end
|
|
6215
6269
|
else
|
|
6216
|
-
|
|
6270
|
+
pid = Process.spawn('xdg-open', @selected, :in => '/dev/null', [:out, :err] => '/dev/null')
|
|
6271
|
+
Process.detach(pid)
|
|
6217
6272
|
end
|
|
6218
6273
|
# Clean up
|
|
6219
6274
|
Rcurses.clear_screen; refresh; render
|
|
6220
|
-
if File.exist?(tmpfile)
|
|
6221
|
-
sleep 0.5
|
|
6222
|
-
err = File.read(tmpfile)
|
|
6223
|
-
showimage('clear') if @image
|
|
6224
|
-
@pR.say(err.fg(196))
|
|
6225
|
-
File.delete(tmpfile)
|
|
6226
|
-
end
|
|
6227
6275
|
end
|
|
6228
6276
|
|
|
6229
6277
|
def conf_write(all: false) # WRITE TO ~/.rtfm/conf {{{2
|
|
@@ -6264,6 +6312,13 @@ def conf_write(all: false) # WRITE TO ~/.rtfm/conf {{{2
|
|
|
6264
6312
|
end
|
|
6265
6313
|
|
|
6266
6314
|
def exit_rtfm # CLEAN EXIT {{{2
|
|
6315
|
+
# Pick mode: write tagged file paths to output file
|
|
6316
|
+
if @pick_output
|
|
6317
|
+
begin
|
|
6318
|
+
File.write(@pick_output, @tagged.join("\n") + "\n")
|
|
6319
|
+
rescue StandardError
|
|
6320
|
+
end
|
|
6321
|
+
end
|
|
6267
6322
|
# If invoked with a stub filename, write out our cwd
|
|
6268
6323
|
if ARGV[0]
|
|
6269
6324
|
begin
|
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.0
|
|
4
|
+
version: 8.1.0
|
|
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-
|
|
11
|
+
date: 2026-03-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rcurses
|
|
@@ -71,7 +71,8 @@ description: 'RTFM v8.0: Browse and modify archives as virtual directories (extr
|
|
|
71
71
|
side-by-side mode. A full featured terminal browser with syntax highlighted files,
|
|
72
72
|
images shown in the terminal, videos thumbnailed, etc. Features include remote SSH/SFTP
|
|
73
73
|
browsing, interactive SSH shell, comprehensive undo system, OpenAI integration,
|
|
74
|
-
bookmarks, and much more. RTFM is one of the most feature-packed terminal file managers.
|
|
74
|
+
bookmarks, and much more. RTFM is one of the most feature-packed terminal file managers.
|
|
75
|
+
v8.1: File picker mode (--pick) for integration with other tools.'
|
|
75
76
|
email: g@isene.com
|
|
76
77
|
executables:
|
|
77
78
|
- rtfm
|