rtfm-filemanager 8.0.2 → 8.1.1

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 +10 -0
  3. data/README.md +8 -0
  4. data/bin/rtfm +87 -24
  5. metadata +4 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2268b6df0d2f7e4c4c3b16441b1d38c60596271205935b7718d85c279ce583d0
4
- data.tar.gz: 6900af4ed1b6b7ff9be6d6d62dc688c62f937dd752bc9a2e164b8eddca243c1b
3
+ metadata.gz: 65a65b423798385788eb367824c259a5c06afe6fdb758236c1251392d25a3a9c
4
+ data.tar.gz: 85ffe85ecc519a3ed161895a1c3acbb75abca292a406a91006867beb2094aca0
5
5
  SHA512:
6
- metadata.gz: 45bc4528912e89d2002139c15c80b214b4824aaa0113f49269ed390bafb15c8a065a4602346abef6bd7163876b8e9d432784d5678d22b88184c48de5fbe33398
7
- data.tar.gz: cb124815c4ace418b44d82f2bfadca9b1d7dec1b65d46beddc9c77009ccb5d21eea9b0e88fc2d98522935c7622d7187fe8ce28472ff3af1cc13e636cb5a80f12
6
+ metadata.gz: 42e385ba69a21ab5f4460c83033c1fa78f18fd0bbf2cbd9efb08288481ea3a11ea6cd79211e5299b2a09acf0c35b19c8d1ed661c354c3c28651e99063e5b66a7
7
+ data.tar.gz: e3d90244aa51ec34abbfabf044eb5cb51326ee3c264884dc9effbfbe4cc2d4ac99cc0f6174824600c55e02dc3173f2ec229c23889b5e39fd74578abd2a922399
data/CHANGELOG.md CHANGED
@@ -5,6 +5,16 @@ 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.1] - 2026-03-12
9
+
10
+ ### Fixed
11
+ - **Right pane text not showing over images or file previews** - Fixed monkey-patched `say` method so pane refresh always draws, regardless of prior update state. Also added `clear_image` calls to marks, recent files, file properties, AI description, chat mode, command history, and SSH history
12
+
13
+ ## [8.1.0] - 2026-03-12
14
+
15
+ ### Added
16
+ - **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]`
17
+
8
18
  ## [8.0.1] - 2026-03-07
9
19
 
10
20
  ### 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.2' # 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
@@ -134,6 +134,7 @@ module Rcurses
134
134
  # Restore original say method override for compatibility
135
135
  alias original_say say
136
136
  def say(text)
137
+ self.update = true
137
138
  original_say(text)
138
139
  self.update = false
139
140
  end
@@ -825,9 +826,22 @@ rescue StandardError => e
825
826
  errormsg("⚠ Errors while loading #{CONFIG_FILE}\nCheck your config file or delete it to remake in a fresh RTFM start.", e)
826
827
  end
827
828
  load_config
829
+ # Load persisted width setting if it exists
830
+ width_file = File.join(RTFM_HOME, 'width')
831
+ if File.exist?(width_file)
832
+ saved_width = File.read(width_file).strip.to_i
833
+ @width = saved_width if saved_width >= 2 && saved_width <= 7
834
+ end
828
835
  @marks['0'] = Dir.pwd # Original dir
829
836
  @marks["'"] = Dir.pwd # Initial mark
830
837
 
838
+ # File picker mode: --pick=/path/to/output writes tagged files on quit
839
+ @pick_output = nil
840
+ if (pick_arg = ARGV.find { |a| a.start_with?('--pick=') })
841
+ @pick_output = pick_arg.split('=', 2).last
842
+ ARGV.delete(pick_arg)
843
+ end
844
+
831
845
  # Handle start dir {{{2
832
846
  Dir.chdir(ARGV.shift) if ARGV[0] && File.directory?(ARGV[0])
833
847
 
@@ -1203,7 +1217,10 @@ end
1203
1217
  def change_width # {{{3
1204
1218
  @width += 1
1205
1219
  @width = 2 if @width == 8
1206
-
1220
+
1221
+ # Persist width setting
1222
+ File.write(File.join(RTFM_HOME, 'width'), @width.to_s) rescue nil
1223
+
1207
1224
  if @dual_pane
1208
1225
  # Show width setting info for dual-pane mode using the new ratio calculation
1209
1226
  dir_panes_ratio = [0.5 - (@width - 2) * 0.034, 0.33].max
@@ -1212,7 +1229,7 @@ def change_width # {{{3
1212
1229
  else
1213
1230
  @pB.say("Width: #{@width}")
1214
1231
  end
1215
-
1232
+
1216
1233
  refresh
1217
1234
  @pL.update = @pR.update = @pT.update = @pB.update = true
1218
1235
  # Also update dual-pane objects if they exist
@@ -1886,6 +1903,7 @@ def track_directory_access(dir_path) # {{{3
1886
1903
  end
1887
1904
 
1888
1905
  def show_recent_files # {{{3
1906
+ clear_image
1889
1907
  text = "Recently Accessed Files and Directories\n".b.fg(156)
1890
1908
  text << "=" * 40 + "\n\n"
1891
1909
 
@@ -1959,8 +1977,9 @@ end
1959
1977
 
1960
1978
  # FILE PROPERTIES {{{2
1961
1979
  def show_file_properties # {{{3
1980
+ clear_image
1962
1981
  return unless @selected && File.exist?(@selected)
1963
-
1982
+
1964
1983
  begin
1965
1984
  stat = File.stat(@selected)
1966
1985
  text = "File Properties: #{File.basename(@selected)}\n".b.fg(156)
@@ -4502,6 +4521,7 @@ def hash_directory # {{{3
4502
4521
  end
4503
4522
 
4504
4523
  def openai_description # {{{3
4524
+ clear_image
4505
4525
  begin
4506
4526
  require 'ruby/openai'
4507
4527
  rescue LoadError
@@ -4541,6 +4561,7 @@ def openai_description # {{{3
4541
4561
  end
4542
4562
 
4543
4563
  def chat_mode # {{{3
4564
+ clear_image
4544
4565
  unless defined?(OpenAI) && @ai && !@ai.empty?
4545
4566
  @pB.say("To make OpenAI work in RTFM, run `gem install ruby-openai` and add to ~/.rtfm/conf:\n @ai = 'your-secret-openai-key'")
4546
4567
  return
@@ -5092,11 +5113,13 @@ def command_mode # {{{3
5092
5113
  end
5093
5114
 
5094
5115
  def show_history # {{{3
5116
+ clear_image
5095
5117
  @pR.say("Command history:\n\n" + @pCmd.history.reverse.join("\n"))
5096
5118
  @pB.update = true
5097
5119
  end
5098
5120
 
5099
5121
  def show_ssh_history # {{{3
5122
+ clear_image
5100
5123
  history_text = "SSH Connection History\n".b.fg(156)
5101
5124
  history_text << "=" * 50 + "\n\n"
5102
5125
 
@@ -6038,6 +6061,42 @@ def mark_latest # UPDATE MARKS LIST {{{2
6038
6061
  @marks["'"] = Dir.pwd
6039
6062
  end
6040
6063
 
6064
+ def mailcap_command(file_path) # FIND VIEWER VIA ~/.mailcap {{{2
6065
+ # Detect MIME type
6066
+ mime_type = IO.popen(['file', '--brief', '--mime-type', file_path]) { |f| f.read.strip }
6067
+ return nil if mime_type.nil? || mime_type.empty?
6068
+
6069
+ # Search mailcap files in order of preference
6070
+ mailcap_files = [File.join(Dir.home, '.mailcap'), '/etc/mailcap']
6071
+ mailcap_files.each do |mc_file|
6072
+ next unless File.exist?(mc_file)
6073
+ File.foreach(mc_file) do |line|
6074
+ line = line.strip
6075
+ next if line.empty? || line.start_with?('#')
6076
+ # Handle continuation lines (trailing backslash)
6077
+ # For simplicity, skip multi-line entries
6078
+ next if line.end_with?('\\')
6079
+ parts = line.split(';').map(&:strip)
6080
+ next if parts.length < 2
6081
+ pattern = parts[0]
6082
+ command = parts[1]
6083
+ flags = parts[2..].join(';')
6084
+ # Skip entries with copiousoutput (meant for text dump, not viewing)
6085
+ next if flags =~ /copiousoutput/i
6086
+ # Check if MIME type matches (supports wildcards like "image/*")
6087
+ pattern_re = Regexp.new('^' + Regexp.escape(pattern).gsub('\\*', '.*') + '$', Regexp::IGNORECASE)
6088
+ next unless mime_type.match?(pattern_re)
6089
+ # Replace %s with the file path in the command
6090
+ if command.include?('%s')
6091
+ return command.gsub('%s', Shellwords.escape(file_path))
6092
+ else
6093
+ return "#{command} #{Shellwords.escape(file_path)}"
6094
+ end
6095
+ end
6096
+ end
6097
+ nil # No mailcap match found
6098
+ end
6099
+
6041
6100
  def get_interactive_program(file_path) # HELPER FOR OPEN_SELECTED TO USE @interactive {{{2
6042
6101
  begin
6043
6102
  inter_list = (@interactive || '').split(',').map(&:strip)
@@ -6146,16 +6205,11 @@ def open_selected(html = nil) # OPEN SELECTED FILE {{{2
6146
6205
  # Use tagged items if any exist, otherwise use selected item
6147
6206
  paths = @tagged.empty? ? [@selected] : @tagged
6148
6207
  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)
6208
+ paths.each do |p|
6209
+ pid = Process.spawn('xdg-open', p, [:out, :err] => '/dev/null')
6210
+ Process.detach(pid)
6158
6211
  end
6212
+ Rcurses.clear_screen; refresh; render
6159
6213
  return
6160
6214
  end
6161
6215
  # Check if file is text (UTF-8, UTF-16, or other text encodings)
@@ -6209,21 +6263,22 @@ def open_selected(html = nil) # OPEN SELECTED FILE {{{2
6209
6263
  render
6210
6264
  return
6211
6265
  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)
6266
+ # Try mailcap first, then fall back to run-mailcap or xdg-open
6267
+ mc_cmd = mailcap_command(@selected)
6268
+ if mc_cmd
6269
+ pid = Process.spawn(mc_cmd, :in => '/dev/null', [:out, :err] => '/dev/null')
6270
+ Process.detach(pid)
6271
+ elsif @runmailcap
6272
+ paths.each do |p|
6273
+ pid = Process.spawn('run-mailcap', p, :in => '/dev/null', [:out, :err] => '/dev/null')
6274
+ Process.detach(pid)
6275
+ end
6215
6276
  else
6216
- shell("xdg-open #{Shellwords.escape(@selected)} &", err: tmpfile)
6277
+ pid = Process.spawn('xdg-open', @selected, :in => '/dev/null', [:out, :err] => '/dev/null')
6278
+ Process.detach(pid)
6217
6279
  end
6218
6280
  # Clean up
6219
6281
  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
6282
  end
6228
6283
 
6229
6284
  def conf_write(all: false) # WRITE TO ~/.rtfm/conf {{{2
@@ -6264,6 +6319,13 @@ def conf_write(all: false) # WRITE TO ~/.rtfm/conf {{{2
6264
6319
  end
6265
6320
 
6266
6321
  def exit_rtfm # CLEAN EXIT {{{2
6322
+ # Pick mode: write tagged file paths to output file
6323
+ if @pick_output
6324
+ begin
6325
+ File.write(@pick_output, @tagged.join("\n") + "\n")
6326
+ rescue StandardError
6327
+ end
6328
+ end
6267
6329
  # If invoked with a stub filename, write out our cwd
6268
6330
  if ARGV[0]
6269
6331
  begin
@@ -6574,6 +6636,7 @@ def showimage(image) # SHOW THE SELECTED IMAGE IN THE RIGHT WINDOW {{{2
6574
6636
  end
6575
6637
 
6576
6638
  def marks_info # SHOW MARKS IN RIGHT WINDOW {{{2
6639
+ clear_image
6577
6640
  @marks = @marks.sort.to_h
6578
6641
  info = "Directory Marks".b.fg(156) + "\n"
6579
6642
  info << "=" * 50 + "\n\n"
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.2
4
+ version: 8.1.1
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-08 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