docs/plugins
RTFM Plugin Development Guide
Extend RTFM with custom preview handlers and key bindings.
Plugin System Overview
RTFM supports two types of plugins:
- Preview Handlers (
~/.rtfm/plugins/preview.rb) - Custom file type previews - Key Bindings (
~/.rtfm/plugins/keys.rb) - Custom commands and key mappings
Both are Ruby files evaluated in RTFM’s context, giving you full access to RTFM’s internals.
Preview Handlers
Location
~/.rtfm/plugins/preview.rb
Syntax
# extension1, extension2, extension3 = command with @s placeholder # # @s is replaced with shell-escaped filename # Examples: txt, log = bat -n --color=always @s md = pandoc @s -t plain pdf = pdftotext -f 1 -l 4 @s - json = jq . @s xml = xmllint --format @s
How It Works
- RTFM matches file extension
- Replaces
@swith escaped filename - Executes command
- Displays output in right pane
Examples
Syntax Highlighting
# Programming languages rb, py, js = bat -n --color=always @s c, cpp, h = highlight -O ansi --force --line-numbers @s
Document Formats
# Markdown md, markdown = pandoc @s -t plain # PDF pdf = pdftotext -f 1 -l 10 @s - # LibreOffice odt = odt2txt @s ods = ssconvert --export-type=Gnumeric_stf:stf_csv @s fd://1 # MS Office docx = docx2txt @s xlsx = ssconvert --export-type=Gnumeric_stf:stf_csv @s fd://1
Data Formats
# JSON with syntax highlighting json = jq -C . @s # YAML yaml, yml = bat -l yaml @s # XML xml = xmllint --format @s | bat -l xml
Media Info
# Video metadata mp4, mkv, avi = ffprobe -hide_banner @s 2>&1 # Audio metadata mp3, flac = mediainfo @s # Image metadata (already built-in, but you can override) # png, jpg = identify -verbose @s
Archives (Preview Contents)
Built-in support for: zip, tar, gz, bz2, xz, rar, 7z
Override if needed:
zip = unzip -l @s tar = tar -tvf @s
Complex Preview Handlers
For more complex logic, use Ruby in preview.rb:
# Define handler as Ruby code instead of shell command PREVIEW_HANDLERS << [/\.log$/i, -> { # Custom Ruby handler content = File.read(@selected).lines.last(50).join @pR.say("Last 50 lines:\n" + content) }]
Key Bindings
Location
~/.rtfm/plugins/keys.rb
Basic Syntax
# Add or override key binding KEYMAP['X'] = :my_handler # Define handler method def my_handler(_chr) @pB.say("You pressed X!") end
Available Panes
| Variable | Description |
|---|---|
@pT |
Top pane (path/metadata) |
@pL |
Left pane (file list) |
@pR |
Right pane (preview) |
@pB |
Bottom pane (status) |
@pCmd |
Command prompt pane |
@pSearch |
Search prompt pane |
@pAI |
AI chat pane |
@pRuby |
Ruby debug pane |
Pane Methods
# Display text @pR.say("Hello world") @pB.say("Status message") # Ask for input answer = @pCmd.ask('Enter value: ', 'default') # Clear pane @pR.clear # Update pane (mark for refresh) @pR.update = true # Force immediate refresh @pR.refresh @pR.full_refresh # Complete redraw
Available Variables
| Variable | Type | Description |
|---|---|---|
@selected |
String | Currently selected file/dir path |
@tagged |
Array | Paths of tagged items |
@marks |
Hash | Bookmarks {‘a’ => ‘/path’, …} |
@files |
Array | Current directory file list |
@index |
Integer | Selected item index |
@w / @h |
Integer | Terminal width/height |
@preview |
Boolean | Preview enabled? |
@showimage |
Boolean | Image preview enabled? |
@trash |
Boolean | Trash bin enabled? |
Helper Functions
Execute Commands
# Capture output (auto-shows errors in right pane) output = command("ls -la", timeout: 5) @pR.say(output) # Fire-and-forget (shows errors if any) shell("mv file1 file2", background: false) # Show both stdout and stderr in right pane shellexec("grep -r pattern .")
File Operations
# Check if file exists File.exist?(@selected) # Get file size File.size(@selected) # Read file content = File.read(@selected) # Write file File.write('/tmp/output.txt', content)
Example Plugins
Example 1: Git Commit Shortcut
# ~/.rtfm/plugins/keys.rb KEYMAP['C-G'] = :git_quick_commit def git_quick_commit message = @pCmd.ask('Commit message: ', '') return if message.strip.empty? shellexec("git add . && git commit -m '#{message}' && git push") @pB.say("Git commit and push completed") end
Usage: Press Ctrl-g, enter message, done!
Example 2: Quick Note Taker
KEYMAP['C-N'] = :quick_note def quick_note note = @pCmd.ask('Note: ', '') return if note.strip.empty? File.open("#{Dir.home}/notes.txt", 'a') do |f| f.puts "[#{Time.now}] #{note}" end @pB.say("Note saved to ~/notes.txt") end
Example 3: Batch File Converter
KEYMAP['C-C'] = :convert_images def convert_images return @pB.say("Tag images first!") if @tagged.empty? format = @pCmd.ask('Convert to (png/jpg/webp): ', 'png') @tagged.each do |file| next unless file.match(/\.(jpg|png|gif|bmp)$/i) output = file.sub(/\.\w+$/, ".#{format}") command("convert #{Shellwords.escape(file)} #{Shellwords.escape(output)}") end @pB.say("Converted #{@tagged.size} images to #{format}") @tagged.clear @pL.update = true end
Example 4: Custom File Opener
KEYMAP['O'] = :open_with def open_with program = @pCmd.ask('Open with: ', 'vim') return if program.strip.empty? escaped = Shellwords.escape(@selected) # Set flag to prevent SIGWINCH redrawing over program @external_program_running = true system("stty sane < /dev/tty") system("clear < /dev/tty > /dev/tty") Cursor.show system("#{program} #{escaped}") @external_program_running = false # Restore terminal for RTFM system('stty raw -echo isig < /dev/tty') $stdin.raw! $stdin.echo = false Cursor.hide Rcurses.clear_screen refresh render end
Example 5: Directory Size Calculator
KEYMAP['#'] = :calc_dir_size def calc_dir_size return @pB.say("Select a directory") unless File.directory?(@selected) @pR.say("Calculating size...") output = command("du -sh #{Shellwords.escape(@selected)}") size = output.split("\t").first @pR.say("Directory Size\n\n#{@selected}\n\n#{size}") end
Advanced Techniques
Launching External TUI Programs
For full-screen terminal programs (vim, htop, etc.):
def launch_external_program(cmd) @external_program_running = true # Save and restore terminal state system("stty -g < /dev/tty > /tmp/rtfm_stty_$$") system('stty sane < /dev/tty') system('clear < /dev/tty > /dev/tty') Cursor.show # Spawn on real tty pid = Process.spawn(cmd, in: '/dev/tty', out: '/dev/tty', err: '/dev/tty') begin Process.wait(pid) rescue Interrupt Process.kill('TERM', pid) rescue nil retry ensure @external_program_running = false end # Restore RTFM terminal state system('stty raw -echo isig < /dev/tty') $stdin.raw! $stdin.echo = false Cursor.hide Rcurses.clear_screen refresh render end
Working with Tagged Items
def process_tagged_items if @tagged.empty? @pB.say("No items tagged") return end @tagged.each do |item| # Process each item if File.file?(item) # Handle file elsif File.directory?(item) # Handle directory end end # Clear tags after processing @tagged.clear @pL.update = true end
Interactive Prompts
def interactive_handler # Text input text = @pCmd.ask('Enter text: ', 'default value') # Number input num = @pCmd.ask('Enter number: ', '10').to_i # Yes/no confirmation confirm = @pCmd.ask('Proceed? (y/n): ', 'y') return unless confirm =~ /^y/i # Process... end
Updating Display
def custom_display # Update right pane @pR.clear @pR.say("Custom content here") @pR.update = true # Update bottom status @pB.say("Operation complete") @pB.update = true # Trigger render render end
Plugin Best Practices
1. Check Prerequisites
def my_handler unless cmd?('required-program') @pB.say("Error: required-program not installed") return end # Continue... end
2. Handle Errors Gracefully
def safe_handler begin # Your code rescue => e @pB.say("Error: #{e.message}") end end
3. Provide Feedback
def verbose_handler @pB.say("Processing...") # Long operation result = command("slow-command") @pB.say("Completed!") @pR.say(result) end
4. Use Shellwords for Safety
require 'shellwords' escaped = Shellwords.escape(@selected) command("program #{escaped}")
5. Respect Image Display
def text_display_handler # Clear image before showing text clear_image @pR.say("Your text content") end
Debugging Plugins
Test in Ruby Mode
- Press
@to enter Ruby mode - Test your code:
ruby my_handler(nil) - Check for errors in right pane
Reload Plugins
# In Ruby mode load '~/.rtfm/plugins/keys.rb'
Or restart RTFM.
Check Variables
# In Ruby mode puts KEYMAP.keys.sort puts @selected puts defined?(my_handler)
Plugin Ideas
Workflow Automation
- Git workflow shortcuts
- Deployment scripts
- Backup automation
- File organization rules
File Processing
- Batch image conversion
- Document generation
- Log analysis
- Data extraction
Integration
- Integration with other tools
- API calls
- Database queries
- Cloud storage sync
Information Display
- Custom file info
- Directory statistics
- Metadata extraction
- Health checks
Sharing Plugins
Consider sharing useful plugins:
- Post as GitHub gist
- Share in RTFM issues/discussions
- Create plugin collection repository
Plugin Template
# ~/.rtfm/plugins/keys.rb # # Plugin: [Name] # Description: [What it does] # Author: [Your name] # Dependencies: [Required programs] KEYMAP['[KEY]'] = :plugin_name def plugin_name(_chr) # Check prerequisites return @pB.say("Error: dependency missing") unless cmd?('program') # Get input if needed input = @pCmd.ask('Prompt: ', 'default') return if input.strip.empty? # Show progress @pB.say("Processing...") # Do work begin result = command("your-command #{Shellwords.escape(input)}") # Display result @pR.say(result) @pB.say("Completed!") rescue => e @pB.say("Error: #{e.message}") end end
Reference
All Available Methods
# Command execution command(cmd, timeout: 5, return_both: false) shell(cmd, background: false, err: nil) shellexec(cmd, timeout: 10) # Utilities cmd?(program) # Check if program exists dirlist(left: false) # Get directory listing mark_latest # Update directory marks track_file_access(path) # Track file access track_directory_access(path) # Track directory access # Display refresh # Refresh layout render # Render all panes clear_image # Clear displayed image showimage(path) # Display image # Operations add_undo_operation(info) # Add to undo history copy_to_clipboard(text, 'primary' or 'clipboard')
Global Variables
# File system Dir.pwd # Current directory @selected # Selected item path @files # Current dir file list @tagged # Tagged items array # Configuration @preview # Preview enabled? @showimage # Images enabled? @trash # Trash enabled? @lsall / @lslong / @lsorder / @lsinvert # State @index # Selected item index @marks # Bookmarks hash @history # Command history array @remote_mode # In SSH mode? # Display @w / @h # Terminal dimensions @pL / @pR / @pT / @pB # Panes