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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/README.md +8 -0
  4. data/bin/rtfm +82 -27
  5. metadata +4 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34e1069fa8b9c2a587f6216f06a98ae9a68feb1290898bf45a8a991063cef65f
4
- data.tar.gz: d90988e37f5bd7910929e66207d8862af8ba2307b2ab0abc214f5317faf3907a
3
+ metadata.gz: f1ec26a45d5adbaeb2aeb253b7078c255ae40bdc6aebb45f26234f0ba28c89de
4
+ data.tar.gz: 4aeaf423db737eb9956e22f53eea0f6e8b02cbff48c62b92b13834932074bc54
5
5
  SHA512:
6
- metadata.gz: 2096032a084f6851ae1ffdd87a72a96469b3239e605af345f8a8e4a4aa08e8b781640a25465518c1139127e764b9d78e504b581e137810124a6d952739a3a5e0
7
- data.tar.gz: 6a07bab823af11d303d7ef8ff92699f6145f68350dcf0471107c9f9ffb4e844614485d4e80c573680533c3800b0018712c8a6c2e4692aba30232ff24cf567a63
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.1' # Archive browsing, async file ops, scrollable diff viewer
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 | %s\n", name1[0, half_width], name2[0, half_width]).fg(226)
2569
- text << " " + "-" * half_width + "-+-" + "-" * half_width + "\n"
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 | %s\n", l1, l2).fg(color)
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
- esc = paths.map { |p| Shellwords.escape(p) }.join(' ')
6150
- shell("xdg-open #{esc} &", err: tmpfile)
6151
- Rcurses.clear_screen; refresh; render
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
- if @runmailcap # Open with run-mailcap or xdg-open
6213
- arg = paths.map { |p| Shellwords.escape(p) }.join(' ')
6214
- shell("run-mailcap #{arg} &", err: tmpfile)
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
- shell("xdg-open #{Shellwords.escape(@selected)} &", err: tmpfile)
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.1
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-07 00:00:00.000000000 Z
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