rtfm-filemanager 7.3.6 → 7.4.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.
data/docs/plugins.md ADDED
@@ -0,0 +1,649 @@
1
+ # RTFM Plugin Development Guide
2
+
3
+ Extend RTFM with custom preview handlers and key bindings.
4
+
5
+ ## Plugin System Overview
6
+
7
+ RTFM supports two types of plugins:
8
+
9
+ 1. **Preview Handlers** (`~/.rtfm/plugins/preview.rb`) - Custom file type previews
10
+ 2. **Key Bindings** (`~/.rtfm/plugins/keys.rb`) - Custom commands and key mappings
11
+
12
+ Both are Ruby files evaluated in RTFM's context, giving you full access to RTFM's internals.
13
+
14
+ ## Preview Handlers
15
+
16
+ ### Location
17
+
18
+ `~/.rtfm/plugins/preview.rb`
19
+
20
+ ### Syntax
21
+
22
+ ```ruby
23
+ # extension1, extension2, extension3 = command with @s placeholder
24
+ #
25
+ # @s is replaced with shell-escaped filename
26
+
27
+ # Examples:
28
+ txt, log = bat -n --color=always @s
29
+ md = pandoc @s -t plain
30
+ pdf = pdftotext -f 1 -l 4 @s -
31
+ json = jq . @s
32
+ xml = xmllint --format @s
33
+ ```
34
+
35
+ ### How It Works
36
+
37
+ 1. RTFM matches file extension
38
+ 2. Replaces `@s` with escaped filename
39
+ 3. Executes command
40
+ 4. Displays output in right pane
41
+
42
+ ### Examples
43
+
44
+ #### Syntax Highlighting
45
+
46
+ ```ruby
47
+ # Programming languages
48
+ rb, py, js = bat -n --color=always @s
49
+ c, cpp, h = highlight -O ansi --force --line-numbers @s
50
+ ```
51
+
52
+ #### Document Formats
53
+
54
+ ```ruby
55
+ # Markdown
56
+ md, markdown = pandoc @s -t plain
57
+
58
+ # PDF
59
+ pdf = pdftotext -f 1 -l 10 @s -
60
+
61
+ # LibreOffice
62
+ odt = odt2txt @s
63
+ ods = ssconvert --export-type=Gnumeric_stf:stf_csv @s fd://1
64
+
65
+ # MS Office
66
+ docx = docx2txt @s
67
+ xlsx = ssconvert --export-type=Gnumeric_stf:stf_csv @s fd://1
68
+ ```
69
+
70
+ #### Data Formats
71
+
72
+ ```ruby
73
+ # JSON with syntax highlighting
74
+ json = jq -C . @s
75
+
76
+ # YAML
77
+ yaml, yml = bat -l yaml @s
78
+
79
+ # XML
80
+ xml = xmllint --format @s | bat -l xml
81
+ ```
82
+
83
+ #### Media Info
84
+
85
+ ```ruby
86
+ # Video metadata
87
+ mp4, mkv, avi = ffprobe -hide_banner @s 2>&1
88
+
89
+ # Audio metadata
90
+ mp3, flac = mediainfo @s
91
+
92
+ # Image metadata (already built-in, but you can override)
93
+ # png, jpg = identify -verbose @s
94
+ ```
95
+
96
+ #### Archives (Preview Contents)
97
+
98
+ Built-in support for: zip, tar, gz, bz2, xz, rar, 7z
99
+
100
+ Override if needed:
101
+ ```ruby
102
+ zip = unzip -l @s
103
+ tar = tar -tvf @s
104
+ ```
105
+
106
+ ### Complex Preview Handlers
107
+
108
+ For more complex logic, use Ruby in preview.rb:
109
+
110
+ ```ruby
111
+ # Define handler as Ruby code instead of shell command
112
+ PREVIEW_HANDLERS << [/\.log$/i, -> {
113
+ # Custom Ruby handler
114
+ content = File.read(@selected).lines.last(50).join
115
+ @pR.say("Last 50 lines:\n" + content)
116
+ }]
117
+ ```
118
+
119
+ ## Key Bindings
120
+
121
+ ### Location
122
+
123
+ `~/.rtfm/plugins/keys.rb`
124
+
125
+ ### Basic Syntax
126
+
127
+ ```ruby
128
+ # Add or override key binding
129
+ KEYMAP['X'] = :my_handler
130
+
131
+ # Define handler method
132
+ def my_handler(_chr)
133
+ @pB.say("You pressed X!")
134
+ end
135
+ ```
136
+
137
+ ### Available Panes
138
+
139
+ | Variable | Description |
140
+ |----------|-------------|
141
+ | `@pT` | Top pane (path/metadata) |
142
+ | `@pL` | Left pane (file list) |
143
+ | `@pR` | Right pane (preview) |
144
+ | `@pB` | Bottom pane (status) |
145
+ | `@pCmd` | Command prompt pane |
146
+ | `@pSearch` | Search prompt pane |
147
+ | `@pAI` | AI chat pane |
148
+ | `@pRuby` | Ruby debug pane |
149
+
150
+ ### Pane Methods
151
+
152
+ ```ruby
153
+ # Display text
154
+ @pR.say("Hello world")
155
+ @pB.say("Status message")
156
+
157
+ # Ask for input
158
+ answer = @pCmd.ask('Enter value: ', 'default')
159
+
160
+ # Clear pane
161
+ @pR.clear
162
+
163
+ # Update pane (mark for refresh)
164
+ @pR.update = true
165
+
166
+ # Force immediate refresh
167
+ @pR.refresh
168
+ @pR.full_refresh # Complete redraw
169
+ ```
170
+
171
+ ### Available Variables
172
+
173
+ | Variable | Type | Description |
174
+ |----------|------|-------------|
175
+ | `@selected` | String | Currently selected file/dir path |
176
+ | `@tagged` | Array | Paths of tagged items |
177
+ | `@marks` | Hash | Bookmarks {'a' => '/path', ...} |
178
+ | `@files` | Array | Current directory file list |
179
+ | `@index` | Integer | Selected item index |
180
+ | `@w` / `@h` | Integer | Terminal width/height |
181
+ | `@preview` | Boolean | Preview enabled? |
182
+ | `@showimage` | Boolean | Image preview enabled? |
183
+ | `@trash` | Boolean | Trash bin enabled? |
184
+
185
+ ### Helper Functions
186
+
187
+ #### Execute Commands
188
+
189
+ ```ruby
190
+ # Capture output (auto-shows errors in right pane)
191
+ output = command("ls -la", timeout: 5)
192
+ @pR.say(output)
193
+
194
+ # Fire-and-forget (shows errors if any)
195
+ shell("mv file1 file2", background: false)
196
+
197
+ # Show both stdout and stderr in right pane
198
+ shellexec("grep -r pattern .")
199
+ ```
200
+
201
+ #### File Operations
202
+
203
+ ```ruby
204
+ # Check if file exists
205
+ File.exist?(@selected)
206
+
207
+ # Get file size
208
+ File.size(@selected)
209
+
210
+ # Read file
211
+ content = File.read(@selected)
212
+
213
+ # Write file
214
+ File.write('/tmp/output.txt', content)
215
+ ```
216
+
217
+ ## Example Plugins
218
+
219
+ ### Example 1: Git Commit Shortcut
220
+
221
+ ```ruby
222
+ # ~/.rtfm/plugins/keys.rb
223
+
224
+ KEYMAP['C-G'] = :git_quick_commit
225
+
226
+ def git_quick_commit
227
+ message = @pCmd.ask('Commit message: ', '')
228
+ return if message.strip.empty?
229
+
230
+ shellexec("git add . && git commit -m '#{message}' && git push")
231
+ @pB.say("Git commit and push completed")
232
+ end
233
+ ```
234
+
235
+ **Usage:** Press `Ctrl-g`, enter message, done!
236
+
237
+ ### Example 2: Quick Note Taker
238
+
239
+ ```ruby
240
+ KEYMAP['C-N'] = :quick_note
241
+
242
+ def quick_note
243
+ note = @pCmd.ask('Note: ', '')
244
+ return if note.strip.empty?
245
+
246
+ File.open("#{Dir.home}/notes.txt", 'a') do |f|
247
+ f.puts "[#{Time.now}] #{note}"
248
+ end
249
+
250
+ @pB.say("Note saved to ~/notes.txt")
251
+ end
252
+ ```
253
+
254
+ ### Example 3: Batch File Converter
255
+
256
+ ```ruby
257
+ KEYMAP['C-C'] = :convert_images
258
+
259
+ def convert_images
260
+ return @pB.say("Tag images first!") if @tagged.empty?
261
+
262
+ format = @pCmd.ask('Convert to (png/jpg/webp): ', 'png')
263
+
264
+ @tagged.each do |file|
265
+ next unless file.match(/\.(jpg|png|gif|bmp)$/i)
266
+
267
+ output = file.sub(/\.\w+$/, ".#{format}")
268
+ command("convert #{Shellwords.escape(file)} #{Shellwords.escape(output)}")
269
+ end
270
+
271
+ @pB.say("Converted #{@tagged.size} images to #{format}")
272
+ @tagged.clear
273
+ @pL.update = true
274
+ end
275
+ ```
276
+
277
+ ### Example 4: Custom File Opener
278
+
279
+ ```ruby
280
+ KEYMAP['O'] = :open_with
281
+
282
+ def open_with
283
+ program = @pCmd.ask('Open with: ', 'vim')
284
+ return if program.strip.empty?
285
+
286
+ escaped = Shellwords.escape(@selected)
287
+
288
+ # Set flag to prevent SIGWINCH redrawing over program
289
+ @external_program_running = true
290
+
291
+ system("stty sane < /dev/tty")
292
+ system("clear < /dev/tty > /dev/tty")
293
+ Cursor.show
294
+
295
+ system("#{program} #{escaped}")
296
+
297
+ @external_program_running = false
298
+
299
+ # Restore terminal for RTFM
300
+ system('stty raw -echo isig < /dev/tty')
301
+ $stdin.raw!
302
+ $stdin.echo = false
303
+ Cursor.hide
304
+ Rcurses.clear_screen
305
+ refresh
306
+ render
307
+ end
308
+ ```
309
+
310
+ ### Example 5: Directory Size Calculator
311
+
312
+ ```ruby
313
+ KEYMAP['#'] = :calc_dir_size
314
+
315
+ def calc_dir_size
316
+ return @pB.say("Select a directory") unless File.directory?(@selected)
317
+
318
+ @pR.say("Calculating size...")
319
+
320
+ output = command("du -sh #{Shellwords.escape(@selected)}")
321
+ size = output.split("\t").first
322
+
323
+ @pR.say("Directory Size\n\n#{@selected}\n\n#{size}")
324
+ end
325
+ ```
326
+
327
+ ## Advanced Techniques
328
+
329
+ ### Launching External TUI Programs
330
+
331
+ For full-screen terminal programs (vim, htop, etc.):
332
+
333
+ ```ruby
334
+ def launch_external_program(cmd)
335
+ @external_program_running = true
336
+
337
+ # Save and restore terminal state
338
+ system("stty -g < /dev/tty > /tmp/rtfm_stty_$$")
339
+ system('stty sane < /dev/tty')
340
+ system('clear < /dev/tty > /dev/tty')
341
+ Cursor.show
342
+
343
+ # Spawn on real tty
344
+ pid = Process.spawn(cmd,
345
+ in: '/dev/tty',
346
+ out: '/dev/tty',
347
+ err: '/dev/tty')
348
+
349
+ begin
350
+ Process.wait(pid)
351
+ rescue Interrupt
352
+ Process.kill('TERM', pid) rescue nil
353
+ retry
354
+ ensure
355
+ @external_program_running = false
356
+ end
357
+
358
+ # Restore RTFM terminal state
359
+ system('stty raw -echo isig < /dev/tty')
360
+ $stdin.raw!
361
+ $stdin.echo = false
362
+ Cursor.hide
363
+ Rcurses.clear_screen
364
+ refresh
365
+ render
366
+ end
367
+ ```
368
+
369
+ ### Working with Tagged Items
370
+
371
+ ```ruby
372
+ def process_tagged_items
373
+ if @tagged.empty?
374
+ @pB.say("No items tagged")
375
+ return
376
+ end
377
+
378
+ @tagged.each do |item|
379
+ # Process each item
380
+ if File.file?(item)
381
+ # Handle file
382
+ elsif File.directory?(item)
383
+ # Handle directory
384
+ end
385
+ end
386
+
387
+ # Clear tags after processing
388
+ @tagged.clear
389
+ @pL.update = true
390
+ end
391
+ ```
392
+
393
+ ### Interactive Prompts
394
+
395
+ ```ruby
396
+ def interactive_handler
397
+ # Text input
398
+ text = @pCmd.ask('Enter text: ', 'default value')
399
+
400
+ # Number input
401
+ num = @pCmd.ask('Enter number: ', '10').to_i
402
+
403
+ # Yes/no confirmation
404
+ confirm = @pCmd.ask('Proceed? (y/n): ', 'y')
405
+ return unless confirm =~ /^y/i
406
+
407
+ # Process...
408
+ end
409
+ ```
410
+
411
+ ### Updating Display
412
+
413
+ ```ruby
414
+ def custom_display
415
+ # Update right pane
416
+ @pR.clear
417
+ @pR.say("Custom content here")
418
+ @pR.update = true
419
+
420
+ # Update bottom status
421
+ @pB.say("Operation complete")
422
+ @pB.update = true
423
+
424
+ # Trigger render
425
+ render
426
+ end
427
+ ```
428
+
429
+ ## Plugin Best Practices
430
+
431
+ ### 1. Check Prerequisites
432
+
433
+ ```ruby
434
+ def my_handler
435
+ unless cmd?('required-program')
436
+ @pB.say("Error: required-program not installed")
437
+ return
438
+ end
439
+
440
+ # Continue...
441
+ end
442
+ ```
443
+
444
+ ### 2. Handle Errors Gracefully
445
+
446
+ ```ruby
447
+ def safe_handler
448
+ begin
449
+ # Your code
450
+ rescue => e
451
+ @pB.say("Error: #{e.message}")
452
+ end
453
+ end
454
+ ```
455
+
456
+ ### 3. Provide Feedback
457
+
458
+ ```ruby
459
+ def verbose_handler
460
+ @pB.say("Processing...")
461
+
462
+ # Long operation
463
+ result = command("slow-command")
464
+
465
+ @pB.say("Completed!")
466
+ @pR.say(result)
467
+ end
468
+ ```
469
+
470
+ ### 4. Use Shellwords for Safety
471
+
472
+ ```ruby
473
+ require 'shellwords'
474
+
475
+ escaped = Shellwords.escape(@selected)
476
+ command("program #{escaped}")
477
+ ```
478
+
479
+ ### 5. Respect Image Display
480
+
481
+ ```ruby
482
+ def text_display_handler
483
+ # Clear image before showing text
484
+ clear_image
485
+
486
+ @pR.say("Your text content")
487
+ end
488
+ ```
489
+
490
+ ## Debugging Plugins
491
+
492
+ ### Test in Ruby Mode
493
+
494
+ 1. Press `@` to enter Ruby mode
495
+ 2. Test your code:
496
+ ```ruby
497
+ my_handler(nil)
498
+ ```
499
+ 3. Check for errors in right pane
500
+
501
+ ### Reload Plugins
502
+
503
+ ```ruby
504
+ # In Ruby mode
505
+ load '~/.rtfm/plugins/keys.rb'
506
+ ```
507
+
508
+ Or restart RTFM.
509
+
510
+ ### Check Variables
511
+
512
+ ```ruby
513
+ # In Ruby mode
514
+ puts KEYMAP.keys.sort
515
+ puts @selected
516
+ puts defined?(my_handler)
517
+ ```
518
+
519
+ ## Plugin Ideas
520
+
521
+ ### Workflow Automation
522
+
523
+ - Git workflow shortcuts
524
+ - Deployment scripts
525
+ - Backup automation
526
+ - File organization rules
527
+
528
+ ### File Processing
529
+
530
+ - Batch image conversion
531
+ - Document generation
532
+ - Log analysis
533
+ - Data extraction
534
+
535
+ ### Integration
536
+
537
+ - Integration with other tools
538
+ - API calls
539
+ - Database queries
540
+ - Cloud storage sync
541
+
542
+ ### Information Display
543
+
544
+ - Custom file info
545
+ - Directory statistics
546
+ - Metadata extraction
547
+ - Health checks
548
+
549
+ ## Sharing Plugins
550
+
551
+ Consider sharing useful plugins:
552
+ 1. Post as GitHub gist
553
+ 2. Share in RTFM issues/discussions
554
+ 3. Create plugin collection repository
555
+
556
+ ## Plugin Template
557
+
558
+ ```ruby
559
+ # ~/.rtfm/plugins/keys.rb
560
+ #
561
+ # Plugin: [Name]
562
+ # Description: [What it does]
563
+ # Author: [Your name]
564
+ # Dependencies: [Required programs]
565
+
566
+ KEYMAP['[KEY]'] = :plugin_name
567
+
568
+ def plugin_name(_chr)
569
+ # Check prerequisites
570
+ return @pB.say("Error: dependency missing") unless cmd?('program')
571
+
572
+ # Get input if needed
573
+ input = @pCmd.ask('Prompt: ', 'default')
574
+ return if input.strip.empty?
575
+
576
+ # Show progress
577
+ @pB.say("Processing...")
578
+
579
+ # Do work
580
+ begin
581
+ result = command("your-command #{Shellwords.escape(input)}")
582
+
583
+ # Display result
584
+ @pR.say(result)
585
+ @pB.say("Completed!")
586
+
587
+ rescue => e
588
+ @pB.say("Error: #{e.message}")
589
+ end
590
+ end
591
+ ```
592
+
593
+ ## Reference
594
+
595
+ ### All Available Methods
596
+
597
+ ```ruby
598
+ # Command execution
599
+ command(cmd, timeout: 5, return_both: false)
600
+ shell(cmd, background: false, err: nil)
601
+ shellexec(cmd, timeout: 10)
602
+
603
+ # Utilities
604
+ cmd?(program) # Check if program exists
605
+ dirlist(left: false) # Get directory listing
606
+ mark_latest # Update directory marks
607
+ track_file_access(path) # Track file access
608
+ track_directory_access(path) # Track directory access
609
+
610
+ # Display
611
+ refresh # Refresh layout
612
+ render # Render all panes
613
+ clear_image # Clear displayed image
614
+ showimage(path) # Display image
615
+
616
+ # Operations
617
+ add_undo_operation(info) # Add to undo history
618
+ copy_to_clipboard(text, 'primary' or 'clipboard')
619
+ ```
620
+
621
+ ### Global Variables
622
+
623
+ ```ruby
624
+ # File system
625
+ Dir.pwd # Current directory
626
+ @selected # Selected item path
627
+ @files # Current dir file list
628
+ @tagged # Tagged items array
629
+
630
+ # Configuration
631
+ @preview # Preview enabled?
632
+ @showimage # Images enabled?
633
+ @trash # Trash enabled?
634
+ @lsall / @lslong / @lsorder / @lsinvert
635
+
636
+ # State
637
+ @index # Selected item index
638
+ @marks # Bookmarks hash
639
+ @history # Command history array
640
+ @remote_mode # In SSH mode?
641
+
642
+ # Display
643
+ @w / @h # Terminal dimensions
644
+ @pL / @pR / @pT / @pB # Panes
645
+ ```
646
+
647
+ ---
648
+
649
+ [← Keyboard Reference](keyboard-reference.md) | [Back to README](../README.md)