rtfm-filemanager 8.2.6 → 8.6.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 +21 -0
- data/bin/rtfm +190 -44
- metadata +3 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7d97437ca84138aaa2ad3fc03b54c2d607f39cda4e1d6fead69bef9d83278a9d
|
|
4
|
+
data.tar.gz: feb00468052819fdbb1c7c109406db875eba2be13c98273f0d16cbd90fb968ce
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 59981f04215c73a44f4821d1dbf3dddb74db5590521e746567c0970c157c96aca84b48c9c2ee3afa20037537734937e7b59c56b9a23c2cee8d947e67c0cefa2f
|
|
7
|
+
data.tar.gz: ac25468b6d11effefb11b5debd5e006c9d11aa689e623f638d5ee35a06e798b93343481b8b8b4f7f3950ae7acf046f117cdaecc37de43e050abc2b2a39100de2
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ 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.6.0] - 2026-05-19
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`Terminal=true` in `.desktop` files honored** - When opening a file, if the resolved `.desktop` file declares `Terminal=true`, RTFM treats the program as interactive even when it isn't in `@interactive`. Newly installed TUI tools now "just work" without editing config. The `@interactive` whitelist still wins for programs whose `.desktop` files lie about being terminal apps
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **LS_COLORS fallback via `dircolors -b`** - When `$LS_COLORS` is unset or empty (cron launches, minimal login envs, some macOS setups), RTFM now seeds it from `dircolors -b` at startup so `ls --color` still emits ANSI. Directory listings stay colored in environments that don't export LS_COLORS
|
|
15
|
+
|
|
16
|
+
## [8.5.0] - 2026-05-19
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **Crash log** - Unhandled exceptions now append to `~/.rtfm/crash.log` with timestamp, version, Ruby/platform info, cwd, exception class/message, and full backtrace. The `at_exit` handler skips `SystemExit`/`Interrupt` so normal quits stay silent. Makes bug reports actionable instead of guesswork from terminal scrollback
|
|
20
|
+
- **Persistent per-directory cursor** - The in-memory `@directory` hash that tracks selected index per directory is now saved to `~/.rtfm/conf` on quit (capped at 200 entries, prunes deleted dirs). Re-enter a directory in a later session and the cursor lands where you left it
|
|
21
|
+
- **`--fresh` CLI flag** - Launch with `rtfm --fresh` to ignore the saved cursor map for one session (useful when the saved positions are stale or you want a clean slate)
|
|
22
|
+
- **File-path argument** - `rtfm /etc/hosts` now cd's into `/etc` and places the cursor on `hosts`. Previously only directory arguments worked
|
|
23
|
+
- **Claude (Anthropic) support for AI features** - Set `@aimodel = "claude-sonnet-4-6"` (or any `claude-*` model) in `~/.rtfm/conf` and both `I` (file description) and `Ctrl-a` (chat) route to the Anthropic API via curl. OpenAI continues to work for non-`claude-*` model names. Same `@ai` config field holds either key
|
|
24
|
+
- **Non-blocking `:` commands** - Non-interactive shell commands now run in a background thread. Launching GUI apps (`xdg-open`, `firefox`, `evince`, etc.) no longer freezes RTFM until they exit. Output drops into the right pane when the command completes. The bottom pane shows running/done status
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- **Bootsnap optional** - Missing `bootsnap` gem no longer crashes RTFM on startup. It was always intended as a startup-speed optimization; now `require 'bootsnap/setup'` is wrapped in a `LoadError` rescue and RTFM continues without it
|
|
28
|
+
|
|
8
29
|
## [8.2.6] - 2026-04-21
|
|
9
30
|
|
|
10
31
|
### Added
|
data/bin/rtfm
CHANGED
|
@@ -18,21 +18,58 @@
|
|
|
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.
|
|
21
|
+
@version = '8.6.0' # LS_COLORS fallback (dircolors -b) + Terminal=true in .desktop honored
|
|
22
22
|
|
|
23
23
|
# SAVE & STORE TERMINAL {{{1
|
|
24
24
|
ORIG_STTY = `stty -g`.chomp
|
|
25
25
|
|
|
26
26
|
at_exit do
|
|
27
|
+
# Log unhandled exceptions to ~/.rtfm/crash.log so users can file bug reports
|
|
28
|
+
# without losing the backtrace to terminal scrollback.
|
|
29
|
+
if $! && !$!.is_a?(SystemExit) && !$!.is_a?(Interrupt)
|
|
30
|
+
begin
|
|
31
|
+
log = File.join(Dir.home, '.rtfm', 'crash.log')
|
|
32
|
+
require 'fileutils'
|
|
33
|
+
FileUtils.mkdir_p(File.dirname(log))
|
|
34
|
+
File.open(log, 'a') do |f|
|
|
35
|
+
f.puts '=' * 60
|
|
36
|
+
f.puts "RTFM v#{@version} crash at #{Time.now}"
|
|
37
|
+
f.puts "Ruby #{RUBY_VERSION} on #{RUBY_PLATFORM}"
|
|
38
|
+
f.puts "PWD: #{Dir.pwd rescue 'unknown'}"
|
|
39
|
+
f.puts "#{$!.class}: #{$!.message}"
|
|
40
|
+
f.puts($!.backtrace.join("\n")) if $!.backtrace
|
|
41
|
+
end
|
|
42
|
+
rescue StandardError
|
|
43
|
+
# never crash inside the crash handler
|
|
44
|
+
end
|
|
45
|
+
end
|
|
27
46
|
system("stty #{ORIG_STTY} < /dev/tty") rescue nil
|
|
28
47
|
end
|
|
29
48
|
|
|
30
49
|
# BOOTSNAP {{{1
|
|
50
|
+
# Optional startup speedup. If the gem isn't installed, RTFM just starts
|
|
51
|
+
# a bit slower instead of refusing to run.
|
|
31
52
|
cache_dir = ENV.fetch('BOOTSNAP_CACHE_DIR', File.join(Dir.home, '.rtfm', 'bootsnap-cache'))
|
|
32
53
|
ENV['BOOTSNAP_CACHE_DIR'] = cache_dir
|
|
33
54
|
require 'fileutils'
|
|
34
55
|
FileUtils.mkdir_p(cache_dir)
|
|
35
|
-
|
|
56
|
+
begin
|
|
57
|
+
require 'bootsnap/setup'
|
|
58
|
+
rescue LoadError
|
|
59
|
+
# bootsnap not installed — proceed without compile cache
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# LS_COLORS FALLBACK {{{1
|
|
63
|
+
# RTFM relies on `ls --color` ANSI output for left/right pane colors, and
|
|
64
|
+
# `ls` itself reads $LS_COLORS. If the user's shell never exports it (cron
|
|
65
|
+
# launches, minimal login environments, some macOS setups) the listing
|
|
66
|
+
# comes back colorless. Seed it from `dircolors -b` when available.
|
|
67
|
+
if (ENV['LS_COLORS'].nil? || ENV['LS_COLORS'].empty?) && !`which dircolors 2>/dev/null`.strip.empty?
|
|
68
|
+
dc = `dircolors -b 2>/dev/null`
|
|
69
|
+
if (m = dc.match(/LS_COLORS='([^']*)'/))
|
|
70
|
+
ENV['LS_COLORS'] = m[1]
|
|
71
|
+
end
|
|
72
|
+
end
|
|
36
73
|
|
|
37
74
|
# ENCODING {{{1
|
|
38
75
|
# encoding: utf-8
|
|
@@ -616,6 +653,13 @@ $stdin.set_encoding(Encoding::UTF_8)
|
|
|
616
653
|
@file_op_complete = false # Flag when operation finishes
|
|
617
654
|
@file_op_result = nil # Result message when operation finishes
|
|
618
655
|
|
|
656
|
+
## Async shell command (`:` mode, non-interactive branch)
|
|
657
|
+
@shell_cmd_thread = nil # Current background shell-command thread
|
|
658
|
+
@shell_cmd_running = false # True while a background `:` cmd is running
|
|
659
|
+
@shell_cmd_label = nil # Command string for status display
|
|
660
|
+
@shell_cmd_complete = false # Set by worker when output is ready
|
|
661
|
+
@shell_cmd_output = nil # Captured stdout+stderr
|
|
662
|
+
|
|
619
663
|
## Recently accessed files/directories
|
|
620
664
|
@recent_files = [] # Last 50 accessed files
|
|
621
665
|
@recent_dirs = [] # Last 20 accessed directories
|
|
@@ -860,11 +904,33 @@ if (pick_arg = ARGV.find { |a| a.start_with?('--pick=') })
|
|
|
860
904
|
ARGV.delete(pick_arg)
|
|
861
905
|
end
|
|
862
906
|
|
|
907
|
+
# --fresh: ignore any saved per-directory cursor positions for this session.
|
|
908
|
+
# @directory is still tracked in-memory, but startup restoration is skipped.
|
|
909
|
+
@fresh = false
|
|
910
|
+
if ARGV.delete('--fresh')
|
|
911
|
+
@fresh = true
|
|
912
|
+
@directory = {}
|
|
913
|
+
end
|
|
914
|
+
|
|
863
915
|
# Handle start dir {{{2
|
|
864
|
-
|
|
916
|
+
# Accept either a directory or a file path. With a file path, cd into its
|
|
917
|
+
# parent and queue the basename for cursor restoration after load_dir runs.
|
|
918
|
+
@startup_select = nil
|
|
919
|
+
if (arg = ARGV[0]) && !arg.start_with?('-')
|
|
920
|
+
if File.directory?(arg)
|
|
921
|
+
Dir.chdir(ARGV.shift)
|
|
922
|
+
elsif File.exist?(arg)
|
|
923
|
+
@startup_select = File.basename(arg)
|
|
924
|
+
Dir.chdir(File.dirname(File.expand_path(arg)))
|
|
925
|
+
ARGV.shift
|
|
926
|
+
end
|
|
927
|
+
end
|
|
865
928
|
|
|
866
929
|
# Initialize first tab {{{2
|
|
867
930
|
create_tab(Dir.pwd, "Main")
|
|
931
|
+
# Seed the first tab's per-tab directory_memory with the persisted @directory
|
|
932
|
+
# so switching tabs and back doesn't lose the saved cursor map.
|
|
933
|
+
@tabs[0][:directory_memory] = @directory.dup if @tabs[0]
|
|
868
934
|
|
|
869
935
|
# OPENAI SETUP {{{1
|
|
870
936
|
def chat_history # {{{2
|
|
@@ -880,6 +946,33 @@ def openai_client # {{{2
|
|
|
880
946
|
@openai_client ||= OpenAI::Client.new(access_token: @ai)
|
|
881
947
|
end
|
|
882
948
|
|
|
949
|
+
# AI dispatch: routes to Anthropic when @aimodel starts with 'claude-',
|
|
950
|
+
# otherwise OpenAI. Returns the assistant message text, or nil on failure.
|
|
951
|
+
# Anthropic uses curl (no extra gem dependency); OpenAI uses ruby-openai.
|
|
952
|
+
def ai_request(messages, max_tokens = 600) # {{{2
|
|
953
|
+
if @aimodel.to_s.start_with?('claude-')
|
|
954
|
+
require 'json'
|
|
955
|
+
# Anthropic expects "system" as a top-level field, not a message role.
|
|
956
|
+
sys = messages.find { |m| (m[:role] || m['role']).to_s == 'system' }
|
|
957
|
+
msgs = messages.reject { |m| (m[:role] || m['role']).to_s == 'system' }
|
|
958
|
+
body = { model: @aimodel, max_tokens: max_tokens, messages: msgs }
|
|
959
|
+
body[:system] = sys[:content] || sys['content'] if sys
|
|
960
|
+
out = `curl -sS -X POST https://api.anthropic.com/v1/messages \
|
|
961
|
+
-H 'content-type: application/json' \
|
|
962
|
+
-H 'anthropic-version: 2023-06-01' \
|
|
963
|
+
-H #{Shellwords.escape("x-api-key: #{@ai}")} \
|
|
964
|
+
-d #{Shellwords.escape(body.to_json)} 2>/dev/null`
|
|
965
|
+
return nil if out.empty?
|
|
966
|
+
json = JSON.parse(out) rescue nil
|
|
967
|
+
json && json.dig('content', 0, 'text')
|
|
968
|
+
else
|
|
969
|
+
resp = openai_client.chat(
|
|
970
|
+
parameters: { model: @aimodel, messages: messages, max_tokens: max_tokens }
|
|
971
|
+
) rescue nil
|
|
972
|
+
resp&.dig('choices', 0, 'message', 'content')
|
|
973
|
+
end
|
|
974
|
+
end
|
|
975
|
+
|
|
883
976
|
# SET UP VIEWER SYSTEM {{{1
|
|
884
977
|
# rubocop:disable Style/StringLiterals
|
|
885
978
|
preview_specs = {
|
|
@@ -4804,16 +4897,19 @@ end
|
|
|
4804
4897
|
|
|
4805
4898
|
def openai_description # {{{3
|
|
4806
4899
|
clear_image
|
|
4807
|
-
begin
|
|
4808
|
-
require 'ruby/openai'
|
|
4809
|
-
rescue LoadError
|
|
4810
|
-
@pB.say('To enable AI-descriptions: `gem install ruby-openai` and set @ai in ~/.rtfm/conf')
|
|
4811
|
-
return
|
|
4812
|
-
end
|
|
4813
4900
|
unless @ai && !@ai.empty?
|
|
4814
|
-
@pB.say("Set your API key in ~/.rtfm/conf: @ai = 'your-
|
|
4901
|
+
@pB.say("Set your API key in ~/.rtfm/conf: @ai = 'your-api-key'")
|
|
4815
4902
|
return
|
|
4816
4903
|
end
|
|
4904
|
+
# ruby-openai is only needed for OpenAI models; Anthropic uses curl.
|
|
4905
|
+
unless @aimodel.to_s.start_with?('claude-')
|
|
4906
|
+
begin
|
|
4907
|
+
require 'ruby/openai'
|
|
4908
|
+
rescue LoadError
|
|
4909
|
+
@pB.say('For OpenAI models: `gem install ruby-openai`. For Claude, set @aimodel to a claude-* model.')
|
|
4910
|
+
return
|
|
4911
|
+
end
|
|
4912
|
+
end
|
|
4817
4913
|
# Context
|
|
4818
4914
|
path = File.join(Dir.pwd, @selected.to_s)
|
|
4819
4915
|
is_dir = File.directory?(path)
|
|
@@ -4827,27 +4923,26 @@ def openai_description # {{{3
|
|
|
4827
4923
|
parts << "Git-Aware Diff Explanation: summarize the most recent `git diff` touching #{path}, explaining what changed." if Dir.exist?(File.join(Dir.pwd, '.git'))
|
|
4828
4924
|
parts << "Existing preview text: #{preview}" unless preview.empty?
|
|
4829
4925
|
prompt = parts.join(' ')
|
|
4830
|
-
# Send to OpenAI
|
|
4831
|
-
client = OpenAI::Client.new(access_token: @ai)
|
|
4832
4926
|
@pR.say('Thinking...'.fg(244))
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
model: @aimodel,
|
|
4836
|
-
messages: [{ role: 'user', content: prompt }],
|
|
4837
|
-
max_tokens: 600
|
|
4838
|
-
}
|
|
4839
|
-
) rescue nil
|
|
4840
|
-
answer = response&.dig('choices', 0, 'message', 'content') ||
|
|
4841
|
-
'⚠️ Error or empty response from OpenAI.'
|
|
4927
|
+
answer = ai_request([{ role: 'user', content: prompt }], 600) ||
|
|
4928
|
+
'⚠️ Error or empty response from AI provider.'
|
|
4842
4929
|
@pR.say(answer.fg(230))
|
|
4843
4930
|
end
|
|
4844
4931
|
|
|
4845
4932
|
def chat_mode # {{{3
|
|
4846
4933
|
clear_image
|
|
4847
|
-
unless
|
|
4848
|
-
@pB.say("
|
|
4934
|
+
unless @ai && !@ai.empty?
|
|
4935
|
+
@pB.say("Set your API key in ~/.rtfm/conf: @ai = 'your-api-key'")
|
|
4849
4936
|
return
|
|
4850
4937
|
end
|
|
4938
|
+
unless @aimodel.to_s.start_with?('claude-')
|
|
4939
|
+
begin
|
|
4940
|
+
require 'ruby/openai'
|
|
4941
|
+
rescue LoadError
|
|
4942
|
+
@pB.say('For OpenAI models: `gem install ruby-openai`. For Claude, set @aimodel to a claude-* model.')
|
|
4943
|
+
return
|
|
4944
|
+
end
|
|
4945
|
+
end
|
|
4851
4946
|
|
|
4852
4947
|
@pB.clear; @pB.update = true
|
|
4853
4948
|
question = @pAI.ask('Chat> ', '').strip
|
|
@@ -4855,15 +4950,7 @@ def chat_mode # {{{3
|
|
|
4855
4950
|
|
|
4856
4951
|
chat_history << { role: 'user', content: question }
|
|
4857
4952
|
@pR.say('Thinking...'.fg(230))
|
|
4858
|
-
|
|
4859
|
-
parameters: {
|
|
4860
|
-
model: @aimodel,
|
|
4861
|
-
messages: chat_history,
|
|
4862
|
-
max_tokens: 400
|
|
4863
|
-
}
|
|
4864
|
-
) rescue nil
|
|
4865
|
-
answer = reply&.dig('choices', 0, 'message', 'content') ||
|
|
4866
|
-
'⚠️ API error or empty response'
|
|
4953
|
+
answer = ai_request(chat_history, 400) || '⚠️ API error or empty response'
|
|
4867
4954
|
chat_history << { role: 'assistant', content: answer }
|
|
4868
4955
|
@pR.say(answer.fg(230))
|
|
4869
4956
|
@pB.clear; @pB.update = true
|
|
@@ -5385,15 +5472,33 @@ def command_mode # {{{3
|
|
|
5385
5472
|
refresh
|
|
5386
5473
|
render
|
|
5387
5474
|
else
|
|
5388
|
-
#
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
@
|
|
5475
|
+
# Run non-interactive commands in a background thread so GUI apps
|
|
5476
|
+
# (xdg-open, firefox, evince, …) and long-running tools don't freeze
|
|
5477
|
+
# the TUI. The main loop polls @shell_cmd_complete and drops output
|
|
5478
|
+
# into the right pane when ready.
|
|
5479
|
+
if @shell_cmd_running
|
|
5480
|
+
@pB.say("A background command is still running: #{@shell_cmd_label}".fg(214))
|
|
5481
|
+
return
|
|
5482
|
+
end
|
|
5483
|
+
@shell_cmd_running = true
|
|
5484
|
+
@shell_cmd_complete = false
|
|
5485
|
+
@shell_cmd_output = nil
|
|
5486
|
+
@shell_cmd_label = cmd
|
|
5487
|
+
cmd_str = cmd.dup
|
|
5488
|
+
@shell_cmd_thread = Thread.new do
|
|
5489
|
+
begin
|
|
5490
|
+
out, err, _status = Open3.capture3('sh', '-c', cmd_str)
|
|
5491
|
+
combined = out.dup
|
|
5492
|
+
combined << "\n" << err.fg(196) unless err.empty?
|
|
5493
|
+
@shell_cmd_output = combined
|
|
5494
|
+
rescue StandardError => e
|
|
5495
|
+
@shell_cmd_output = "Error: #{e.class}: #{e.message}".fg(196)
|
|
5496
|
+
ensure
|
|
5497
|
+
@shell_cmd_complete = true
|
|
5498
|
+
end
|
|
5499
|
+
end
|
|
5500
|
+
@pB.say(" Running: #{cmd}".fg(244))
|
|
5501
|
+
@pB.update = true
|
|
5397
5502
|
end
|
|
5398
5503
|
end
|
|
5399
5504
|
|
|
@@ -6415,11 +6520,18 @@ def get_interactive_program(file_path) # HELPER FOR OPEN_SELECTED TO USE @intera
|
|
|
6415
6520
|
if desktop_path
|
|
6416
6521
|
content = File.read(desktop_path)
|
|
6417
6522
|
exec_line = content[/^Exec=(.*)$/m, 1]
|
|
6523
|
+
# Honor Terminal=true in the .desktop file: any app declaring
|
|
6524
|
+
# itself a terminal program is treated as interactive even if
|
|
6525
|
+
# the user hasn't manually added it to @interactive. Lets newly
|
|
6526
|
+
# installed TUI tools "just work" without editing config.
|
|
6527
|
+
terminal_true = content =~ /^Terminal\s*=\s*true\b/i
|
|
6418
6528
|
if exec_line
|
|
6419
|
-
# Extract the program name (first word, remove path)
|
|
6420
6529
|
prog = exec_line.split.first
|
|
6421
6530
|
prog = File.basename(prog) if prog
|
|
6422
|
-
|
|
6531
|
+
if prog
|
|
6532
|
+
return prog if inter_list.include?(prog)
|
|
6533
|
+
return prog if terminal_true
|
|
6534
|
+
end
|
|
6423
6535
|
end
|
|
6424
6536
|
end
|
|
6425
6537
|
end
|
|
@@ -6574,7 +6686,8 @@ def conf_write(all: false) # WRITE TO ~/.rtfm/conf {{{2
|
|
|
6574
6686
|
'history' => "@history = #{@pCmd.history.reverse.uniq.reverse.last(40)}",
|
|
6575
6687
|
'rubyhistory' => "@rubyhistory = #{@pRuby.history.reverse.uniq.reverse.last(40)}",
|
|
6576
6688
|
'aihistory' => "@aihistory = #{@pAI.history.reverse.uniq.reverse.last(40)}",
|
|
6577
|
-
'sshhistory' => "@sshhistory = #{@pSsh.history.reverse.uniq.reverse.last(40)}"
|
|
6689
|
+
'sshhistory' => "@sshhistory = #{@pSsh.history.reverse.uniq.reverse.last(40)}",
|
|
6690
|
+
'directory' => "@directory = #{@directory.select { |d, _| Dir.exist?(d) }.to_a.last(200).to_h}"
|
|
6578
6691
|
}
|
|
6579
6692
|
if all
|
|
6580
6693
|
assignments.merge!(
|
|
@@ -7063,6 +7176,13 @@ end
|
|
|
7063
7176
|
@pSsh.record = true
|
|
7064
7177
|
@pSsh.history = @sshhistory
|
|
7065
7178
|
|
|
7179
|
+
# Restore startup cursor from saved per-directory positions (unless --fresh).
|
|
7180
|
+
# A file-path argument is applied after the first render in the main loop,
|
|
7181
|
+
# since @files isn't populated until render() runs.
|
|
7182
|
+
if !@fresh && @directory[Dir.pwd]
|
|
7183
|
+
@index = @directory[Dir.pwd]
|
|
7184
|
+
end
|
|
7185
|
+
|
|
7066
7186
|
# Report plugin errors {{{2
|
|
7067
7187
|
@pR.say("Plugin load errors:\n" + @plugin_errors.join("\n").fg(196)) if @plugin_errors.any?
|
|
7068
7188
|
|
|
@@ -7096,6 +7216,23 @@ loop do
|
|
|
7096
7216
|
@pB.update = false
|
|
7097
7217
|
end
|
|
7098
7218
|
|
|
7219
|
+
# Check async `:` shell command (non-interactive branch in command_mode)
|
|
7220
|
+
if @shell_cmd_complete
|
|
7221
|
+
@shell_cmd_complete = false
|
|
7222
|
+
@shell_cmd_running = false
|
|
7223
|
+
@shell_cmd_thread = nil
|
|
7224
|
+
if @shell_cmd_output && !@shell_cmd_output.empty?
|
|
7225
|
+
clear_image
|
|
7226
|
+
@pR.say(@shell_cmd_output)
|
|
7227
|
+
end
|
|
7228
|
+
@pB.say(" Done: #{@shell_cmd_label}".fg(156))
|
|
7229
|
+
@shell_cmd_label = nil
|
|
7230
|
+
@shell_cmd_output = nil
|
|
7231
|
+
# Directory may have changed (file created/removed); invalidate cache
|
|
7232
|
+
@dir_cache.delete_if { |key, _| key.start_with?("#{Dir.pwd}:") }
|
|
7233
|
+
@pL.update = @pR.update = @pB.update = true
|
|
7234
|
+
end
|
|
7235
|
+
|
|
7099
7236
|
# Handle pending terminal resize (debounced from SIGWINCH)
|
|
7100
7237
|
if @winch_pending && !@external_program_running
|
|
7101
7238
|
@winch_pending = false
|
|
@@ -7115,6 +7252,15 @@ loop do
|
|
|
7115
7252
|
rescue Errno::EIO
|
|
7116
7253
|
# Note: rcurses 4.9.0+ has enhanced error handling, reducing need for this
|
|
7117
7254
|
end
|
|
7255
|
+
# Apply file-argument cursor selection on first render (when @files is populated)
|
|
7256
|
+
if @startup_select && @files && !@files.empty?
|
|
7257
|
+
ix = @files.index(@startup_select)
|
|
7258
|
+
if ix
|
|
7259
|
+
@index = ix
|
|
7260
|
+
@pL.update = @pR.update = true
|
|
7261
|
+
end
|
|
7262
|
+
@startup_select = nil
|
|
7263
|
+
end
|
|
7118
7264
|
# read key, but ignore TTY-focus errors
|
|
7119
7265
|
begin
|
|
7120
7266
|
getkey
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rtfm-filemanager
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 8.
|
|
4
|
+
version: 8.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Geir Isene
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: rcurses
|
|
@@ -104,7 +103,6 @@ licenses:
|
|
|
104
103
|
- Unlicense
|
|
105
104
|
metadata:
|
|
106
105
|
source_code_uri: https://github.com/isene/RTFM
|
|
107
|
-
post_install_message:
|
|
108
106
|
rdoc_options: []
|
|
109
107
|
require_paths:
|
|
110
108
|
- lib
|
|
@@ -119,8 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
119
117
|
- !ruby/object:Gem::Version
|
|
120
118
|
version: '0'
|
|
121
119
|
requirements: []
|
|
122
|
-
rubygems_version: 3.
|
|
123
|
-
signing_key:
|
|
120
|
+
rubygems_version: 3.6.7
|
|
124
121
|
specification_version: 4
|
|
125
122
|
summary: RTFM - Ruby Terminal File Manager
|
|
126
123
|
test_files: []
|