ruby-shell 3.0.0 → 3.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.
- checksums.yaml +4 -4
- data/README.md +26 -6
- data/bin/rsh +443 -30
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 318b51625674f856dfa297d3b8fc8a3bf0e2fd2bb87e4f95d4184bae6e94be72
|
|
4
|
+
data.tar.gz: 03cfd00609fc1f2a011458d3a75837adfe2ac3975c38718b8fcca9998ffa22b6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 982ec206a79d08fed5b95324b356edd4069c158bef62b52cf0205906bef39f38c8e44031821cf7c4f7eec8f9162df61e186589dee240a6074ae8dc592f090393
|
|
7
|
+
data.tar.gz: b0d025d2b2a0df814b3a9239f2151bcecc66a35ca71f0f020aa360ee2b0081cecbe963e6a2124fa35993f3595652e4797760219e88a423390f6b6825e77d8207
|
data/README.md
CHANGED
|
@@ -33,7 +33,19 @@ Or simply `gem install ruby-shell`.
|
|
|
33
33
|
* All colors are themeable in .rshrc (see github link for possibilities)
|
|
34
34
|
* Copy current command line to primary selection (paste w/middle button) with `Ctrl-y`
|
|
35
35
|
|
|
36
|
-
## NEW in v3.
|
|
36
|
+
## NEW in v3.1.0 - Quick Wins & Polish ⭐
|
|
37
|
+
* **Multiple Named Sessions**: Save/load different sessions - `:save_session "project"`, `:load_session "project"`
|
|
38
|
+
* **Stats Export**: Export analytics to CSV/JSON - `:stats --csv` or `:stats --json`
|
|
39
|
+
* **Session Auto-save**: Set `@session_autosave = 300` in .rshrc for automatic 5-minute saves
|
|
40
|
+
* **Bookmark Import/Export**: Share bookmarks - `:bm --export bookmarks.json`, `:bm --import bookmarks.json`
|
|
41
|
+
* **Bookmark Statistics**: See usage patterns - `:bm --stats` shows tag distribution and analytics
|
|
42
|
+
* **Color Themes**: 6 preset themes - `:theme solarized|dracula|gruvbox|nord|monokai|default`
|
|
43
|
+
* **Config Management**: `:config` shows/sets history_dedup, session_autosave, completion settings
|
|
44
|
+
* **Environment Variables**: `:env` lists/sets/exports environment variables
|
|
45
|
+
* **Bookmark TAB Completion**: Bookmarks appear in TAB completion alongside commands
|
|
46
|
+
* **List Sessions**: `:list_sessions` shows all saved sessions with timestamps and paths
|
|
47
|
+
|
|
48
|
+
## v3.0.0 - Major Feature Release ⭐⭐⭐
|
|
37
49
|
* **Persistent Ruby Functions**: defun functions now save to .rshrc and persist across sessions
|
|
38
50
|
* **Smart Command Suggestions**: Typo detection with "Did you mean...?" suggestions using Levenshtein distance
|
|
39
51
|
* **Command Analytics**: New `:stats` command shows usage statistics, performance metrics, and most-used commands
|
|
@@ -94,8 +106,8 @@ Special commands:
|
|
|
94
106
|
* `:bm "name"` or `:bookmark "name"` bookmark current directory, `:bm "name path #tags"` with tags (NEW in v3.0)
|
|
95
107
|
* `:bm` lists all bookmarks, just type bookmark name to jump (e.g., `work`) (NEW in v3.0)
|
|
96
108
|
* `:bm "-name"` delete bookmark, `:bm "?tag"` search by tag (NEW in v3.0)
|
|
97
|
-
* `:save_session` saves
|
|
98
|
-
* `:
|
|
109
|
+
* `:save_session "name"` saves named session, `:load_session "name"` loads session (NEW in v3.0)
|
|
110
|
+
* `:list_sessions` shows all saved sessions, `:rmsession "name"` or `:rmsession "*"` deletes (NEW in v3.1)
|
|
99
111
|
* `:info` shows introduction and feature overview
|
|
100
112
|
* `:version` Shows the rsh version number and the last published gem file version
|
|
101
113
|
* `:help` will display a compact command reference in two columns
|
|
@@ -125,12 +137,19 @@ Add to your `.rshrc`:
|
|
|
125
137
|
While you `cd` around to different directories, you can see the last 10 directories visited via the command `:dirs` or the convenient shortcut `#`. Entering the number in the list (like `6` and ENTER) will jump you to that directory. Entering `-` will jump you back to the previous dir (equivalent of `1`. Entering `~` will get you to your home dir. If you want to bookmark a special directory, you can do that via a general nick like this: `:gnick "x = /path/to/a/dir/"` - this would bookmark the directory to the single letter `x`.
|
|
126
138
|
|
|
127
139
|
## Nicks
|
|
128
|
-
Add command nicks (aliases) with `:nick "some_nick = some_command"`, e.g. `:nick "ls = ls --color"`. Add general nicks that will substitute anything on a command line (not just commands) like this `:gnick "some_gnick = some_command"`, e.g. `:gnick "x = /home/user/somewhere"`. List
|
|
140
|
+
Add command nicks (aliases) with `:nick "some_nick = some_command"`, e.g. `:nick "ls = ls --color"`. Add general nicks that will substitute anything on a command line (not just commands) like this `:gnick "some_gnick = some_command"`, e.g. `:gnick "x = /home/user/somewhere"`. List nicks with `:nick`, list gnicks with `:gnick`. Remove a nick with `:nick "-some_command"`, e.g. `:nick "-ls"` to remove an `ls` nick. Same for gnicks.
|
|
129
141
|
|
|
130
142
|
## Tab completion
|
|
131
|
-
You can tab complete almost anything. Hitting `TAB` will try to complete in this priority: nicks, gnicks, commands, dirs/files.
|
|
143
|
+
You can tab complete almost anything. Hitting `TAB` will try to complete in this priority: nicks, gnicks, commands, dirs/files. Special completions:
|
|
144
|
+
- `ls -<TAB>` lists command switches from --help with descriptions
|
|
145
|
+
- `:st<TAB>` completes colon commands (:stats, etc.)
|
|
146
|
+
- `$HO<TAB>` completes environment variables ($HOME, etc.)
|
|
147
|
+
- `git <TAB>` shows git subcommands (add, commit, push, etc.)
|
|
148
|
+
- `--format=<TAB>` completes option values (json, yaml, xml, etc.)
|
|
132
149
|
|
|
133
|
-
|
|
150
|
+
You can add to (or subtract from) the search criteria while selecting matches - hit any letter to refine the search, backspace removes a letter from the criteria.
|
|
151
|
+
|
|
152
|
+
Hitting Shift-TAB will search through the command history with fuzzy matching.
|
|
134
153
|
|
|
135
154
|
## Open files
|
|
136
155
|
If you press `ENTER` after writing or tab-completing to a file, rsh will try to open the file in the user's EDITOR of choice (if it is a valid text file) or use `xdg-open` to open the file using the correct program. If you, for some reason want to use `run-mailcap` instead of `xdg-open` as the file opener, simply add `@runmailcap = true` to your `.rshrc`.
|
|
@@ -215,6 +234,7 @@ Variable | Description
|
|
|
215
234
|
`@c_path` | Color for valid path
|
|
216
235
|
`@c_switch` | Color for command switches/options
|
|
217
236
|
`@c_bookmark` | Color for bookmarks (NEW in v3.0)
|
|
237
|
+
`@c_colon` | Color for colon commands (NEW in v3.1)
|
|
218
238
|
`@c_tabselect` | Color for selected tabcompleted item
|
|
219
239
|
`@c_taboption` | Color for unselected tabcompleted item
|
|
220
240
|
`@c_stamp` | Color for time stamp/command
|
data/bin/rsh
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
# Web_site: http://isene.com/
|
|
9
9
|
# Github: https://github.com/isene/rsh
|
|
10
10
|
# License: Public domain
|
|
11
|
-
@version = "3.
|
|
11
|
+
@version = "3.1.0" # Quick wins: Multiple sessions, stats export, bookmark features, themes, config, env management
|
|
12
12
|
|
|
13
13
|
# MODULES, CLASSES AND EXTENSIONS
|
|
14
14
|
class String # Add coloring to strings (with escaping for Readline)
|
|
@@ -93,6 +93,7 @@ begin # Initialization
|
|
|
93
93
|
@c_path = 3 # Color for valid path
|
|
94
94
|
@c_switch = 6 # Color for switches/options
|
|
95
95
|
@c_bookmark = 13 # Color for bookmarks
|
|
96
|
+
@c_colon = 4 # Color for colon commands
|
|
96
97
|
@c_tabselect = 5 # Color for selected tabcompleted item
|
|
97
98
|
@c_taboption = 244 # Color for unselected tabcompleted item
|
|
98
99
|
@c_stamp = 244 # Color for time stamp/command
|
|
@@ -138,7 +139,13 @@ begin # Initialization
|
|
|
138
139
|
@bookmarks = {} # Enhanced bookmarks with tags
|
|
139
140
|
@defuns = {} # Store defun definitions for persistence
|
|
140
141
|
@cmd_stats = {} # Command execution statistics
|
|
141
|
-
@
|
|
142
|
+
@session_dir = Dir.home + '/.rsh/sessions' # Sessions directory
|
|
143
|
+
@session_file = @session_dir + '/default.json' # Default session file
|
|
144
|
+
@session_autosave = 0 # Auto-save interval (0 = disabled)
|
|
145
|
+
@session_last_save = Time.now.to_i # Last auto-save timestamp
|
|
146
|
+
@history_dedup = 'smart' # History dedup mode: 'off', 'full', 'smart'
|
|
147
|
+
Dir.mkdir(Dir.home + '/.rsh') unless Dir.exist?(Dir.home + '/.rsh')
|
|
148
|
+
Dir.mkdir(@session_dir) unless Dir.exist?(@session_dir)
|
|
142
149
|
def pre_cmd; end # User-defined function to be run BEFORE command execution
|
|
143
150
|
def post_cmd; end # User-defined function to be run AFTER command execution
|
|
144
151
|
end
|
|
@@ -146,7 +153,7 @@ end
|
|
|
146
153
|
# HELP TEXT
|
|
147
154
|
@info = <<~INFO
|
|
148
155
|
|
|
149
|
-
Hello #{@user}, welcome to rsh v3.
|
|
156
|
+
Hello #{@user}, welcome to rsh v3.1 - the Ruby SHell.
|
|
150
157
|
|
|
151
158
|
rsh does not attempt to compete with the grand old shells like bash and zsh.
|
|
152
159
|
It serves the specific needs and wants of its author. If you like it, then feel free
|
|
@@ -172,6 +179,18 @@ end
|
|
|
172
179
|
* Syntax validation - Pre-execution warnings for dangerous or malformed commands
|
|
173
180
|
* Unified command syntax - :nick, :gnick, :bm all work the same way (list/create/delete)
|
|
174
181
|
|
|
182
|
+
NEW in v3.1:
|
|
183
|
+
* Multiple named sessions - :save_session "project" and :load_session "project"
|
|
184
|
+
* Stats export - :stats --csv or :stats --json for data analysis
|
|
185
|
+
* Session auto-save - Set @session_autosave = 300 in .rshrc for 5-min auto-save
|
|
186
|
+
* Bookmark import/export - :bm --export file.json and :bm --import file.json
|
|
187
|
+
* Bookmark statistics - :bm --stats shows usage patterns and tag distribution
|
|
188
|
+
* Color themes - :theme solarized|dracula|gruvbox|nord|monokai
|
|
189
|
+
* Config management - :config shows/sets history_dedup, session_autosave, etc.
|
|
190
|
+
* Environment management - :env for listing/setting/exporting environment variables
|
|
191
|
+
* Bookmark TAB completion - Bookmarks appear in TAB completion list
|
|
192
|
+
* List sessions - :list_sessions shows all saved sessions with timestamps
|
|
193
|
+
|
|
175
194
|
Config file (.rshrc) updates on exit (Ctrl-d) or not (Ctrl-e).
|
|
176
195
|
All colors are themeable in .rshrc (see github link for possibilities).
|
|
177
196
|
|
|
@@ -206,6 +225,7 @@ def firstrun
|
|
|
206
225
|
@c_path = 208 # Color for valid path
|
|
207
226
|
@c_switch = 148 # Color for switches/options
|
|
208
227
|
@c_bookmark = 13 # Color for bookmarks
|
|
228
|
+
@c_colon = 4 # Color for colon commands
|
|
209
229
|
@c_tabselect = 207 # Color for selected tabcompleted item
|
|
210
230
|
@c_taboption = 244 # Color for unselected tabcompleted item
|
|
211
231
|
@c_stamp = 244 # Color for time stamp/command
|
|
@@ -485,6 +505,7 @@ def tab(type)
|
|
|
485
505
|
type = "switch" if @tabstr && @tabstr[0] == "-" && !@tabstr.include?("=")
|
|
486
506
|
type = "option_value" if @tabstr && @tabstr =~ /^--?[\w-]+=/
|
|
487
507
|
type = "env_vars" if @tabstr && @tabstr[0] == "$"
|
|
508
|
+
type = "colon_commands" if @tabstr && @tabstr[0] == ":"
|
|
488
509
|
|
|
489
510
|
# Debug output when RSH_DEBUG is set
|
|
490
511
|
if ENV['RSH_DEBUG']
|
|
@@ -576,12 +597,25 @@ def tab(type)
|
|
|
576
597
|
env_vars = ENV.keys.map { |k| "$#{k}" }
|
|
577
598
|
regex_flags = @completion_case_sensitive ? 0 : Regexp::IGNORECASE
|
|
578
599
|
@tabarray = env_vars.select { |var| var =~ Regexp.new(@tabstr, regex_flags) }
|
|
600
|
+
when "colon_commands" # Ruby/rsh commands starting with :
|
|
601
|
+
colon_cmds = %w[
|
|
602
|
+
:nick :gnick :bm :bookmark :stats :defun :defun?
|
|
603
|
+
:history :rmhistory :jobs :fg :bg
|
|
604
|
+
:save_session :load_session :list_sessions :delete_session :rmsession
|
|
605
|
+
:config :env :theme
|
|
606
|
+
:info :version :help
|
|
607
|
+
]
|
|
608
|
+
search_str = @tabstr[1..-1] || "" # Remove leading :
|
|
609
|
+
regex_flags = @completion_case_sensitive ? 0 : Regexp::IGNORECASE
|
|
610
|
+
matches = colon_cmds.select { |cmd| cmd[1..-1] =~ Regexp.new("^#{search_str}", regex_flags) }
|
|
611
|
+
@tabarray = matches
|
|
579
612
|
when "all" # Handle all other tab completions
|
|
580
613
|
ex = []
|
|
581
614
|
ex += @exe
|
|
582
615
|
ex.sort!
|
|
583
616
|
ex.prepend(*@nick.keys) # Add nicks
|
|
584
617
|
ex.prepend(*@gnick.keys) # Add gnicks
|
|
618
|
+
ex.prepend(*@bookmarks.keys) if @bookmarks # Add bookmarks
|
|
585
619
|
|
|
586
620
|
# Enhanced matching with case sensitivity and fuzzy support
|
|
587
621
|
regex_flags = @completion_case_sensitive ? 0 : Regexp::IGNORECASE
|
|
@@ -829,9 +863,96 @@ def suggest_command(cmd) # Smart command suggestions for typos
|
|
|
829
863
|
candidates.first(3)
|
|
830
864
|
end
|
|
831
865
|
def hist_clean # Clean up @history
|
|
832
|
-
@history.uniq!
|
|
833
866
|
@history.compact!
|
|
834
867
|
@history.delete("")
|
|
868
|
+
|
|
869
|
+
# Apply deduplication based on mode
|
|
870
|
+
case @history_dedup
|
|
871
|
+
when 'off'
|
|
872
|
+
# No deduplication
|
|
873
|
+
when 'full', 'smart'
|
|
874
|
+
# Remove duplicates, keeping first (most recent) occurrence
|
|
875
|
+
@history.uniq!
|
|
876
|
+
else
|
|
877
|
+
# Default to smart
|
|
878
|
+
@history.uniq!
|
|
879
|
+
end
|
|
880
|
+
end
|
|
881
|
+
def config(*args) # Configure rsh settings
|
|
882
|
+
setting = args[0]
|
|
883
|
+
value = args[1]
|
|
884
|
+
|
|
885
|
+
if setting.nil?
|
|
886
|
+
# Show current configuration
|
|
887
|
+
puts "\n Current Configuration:".c(@c_prompt).b
|
|
888
|
+
puts " history_dedup: #{@history_dedup}"
|
|
889
|
+
puts " session_autosave: #{@session_autosave}s #{@session_autosave > 0 ? '(enabled)' : '(disabled)'}"
|
|
890
|
+
puts " completion_limit: #{@completion_limit}"
|
|
891
|
+
puts " completion_fuzzy: #{@completion_fuzzy}"
|
|
892
|
+
puts " completion_case_sensitive: #{@completion_case_sensitive}"
|
|
893
|
+
puts
|
|
894
|
+
return
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
case setting
|
|
898
|
+
when 'history_dedup'
|
|
899
|
+
if %w[off full smart].include?(value)
|
|
900
|
+
@history_dedup = value
|
|
901
|
+
puts "History deduplication set to '#{value}'"
|
|
902
|
+
rshrc
|
|
903
|
+
else
|
|
904
|
+
puts "Invalid value. Use: off, full, or smart"
|
|
905
|
+
end
|
|
906
|
+
when 'session_autosave'
|
|
907
|
+
@session_autosave = value.to_i
|
|
908
|
+
puts "Session auto-save set to #{value}s #{value.to_i > 0 ? '(enabled)' : '(disabled)'}"
|
|
909
|
+
rshrc
|
|
910
|
+
when 'completion_limit'
|
|
911
|
+
@completion_limit = value.to_i
|
|
912
|
+
puts "Completion limit set to #{value}"
|
|
913
|
+
rshrc
|
|
914
|
+
else
|
|
915
|
+
puts "Unknown setting '#{setting}'"
|
|
916
|
+
puts "Available: history_dedup, session_autosave, completion_limit"
|
|
917
|
+
end
|
|
918
|
+
end
|
|
919
|
+
def env(*args) # Environment variable management
|
|
920
|
+
arg_str = args.join(' ')
|
|
921
|
+
|
|
922
|
+
if args.empty?
|
|
923
|
+
# List all environment variables
|
|
924
|
+
puts "\n Environment Variables:".c(@c_prompt).b
|
|
925
|
+
ENV.sort.first(20).each do |key, value|
|
|
926
|
+
value_display = value.length > 50 ? value[0..47] + '...' : value
|
|
927
|
+
puts " #{key.c(@c_gnick).ljust(25)} = #{value_display}"
|
|
928
|
+
end
|
|
929
|
+
puts " ... (#{ENV.length} total, showing first 20)"
|
|
930
|
+
puts "\n Use :env \"VARNAME\" to see specific variable"
|
|
931
|
+
puts
|
|
932
|
+
elsif arg_str =~ /^set\s+(\w+)\s+(.+)$/
|
|
933
|
+
# Set environment variable
|
|
934
|
+
var_name, var_value = $1, $2
|
|
935
|
+
ENV[var_name] = var_value
|
|
936
|
+
puts "#{var_name} = #{var_value}"
|
|
937
|
+
elsif arg_str =~ /^unset\s+(\w+)$/
|
|
938
|
+
# Unset environment variable
|
|
939
|
+
var_name = $1
|
|
940
|
+
ENV.delete(var_name)
|
|
941
|
+
puts "#{var_name} unset"
|
|
942
|
+
elsif arg_str =~ /^export\s+(.+)$/
|
|
943
|
+
# Export to shell script
|
|
944
|
+
filename = $1
|
|
945
|
+
File.write(filename, ENV.map { |k,v| "export #{k}=\"#{v}\"" }.join("\n"))
|
|
946
|
+
puts "Environment exported to #{filename}"
|
|
947
|
+
else
|
|
948
|
+
# Show specific variable
|
|
949
|
+
var_name = arg_str.strip
|
|
950
|
+
if ENV[var_name]
|
|
951
|
+
puts "#{var_name} = #{ENV[var_name]}"
|
|
952
|
+
else
|
|
953
|
+
puts "Environment variable '#{var_name}' not set"
|
|
954
|
+
end
|
|
955
|
+
end
|
|
835
956
|
end
|
|
836
957
|
def cmd_check(str) # Check if each element on the readline matches commands, nicks, paths; color them
|
|
837
958
|
return if str.nil?
|
|
@@ -840,7 +961,14 @@ def cmd_check(str) # Check if each element on the readline matches commands, nic
|
|
|
840
961
|
if str =~ /^(@@?)\s+(.*)$/
|
|
841
962
|
prefix = $1
|
|
842
963
|
rest = $2
|
|
843
|
-
return prefix.c(
|
|
964
|
+
return prefix.c(@c_colon) + " " + rest # Color @ or @@ in colon color
|
|
965
|
+
end
|
|
966
|
+
|
|
967
|
+
# Special handling for : commands
|
|
968
|
+
if str =~ /^(:[\w?_]+)/
|
|
969
|
+
colon_cmd = $1
|
|
970
|
+
rest = str.sub(/^:[\w?_]+/, '')
|
|
971
|
+
return colon_cmd.c(@c_colon) + rest # Color colon commands
|
|
844
972
|
end
|
|
845
973
|
|
|
846
974
|
str.gsub(/(?:\S'[^']*'|[^ '])+/) do |el|
|
|
@@ -892,6 +1020,10 @@ def rshrc # Write updates to .rshrc
|
|
|
892
1020
|
conf += "@bookmarks = #{@bookmarks}\n" unless @bookmarks.empty?
|
|
893
1021
|
conf.sub!(/^@defuns.*(\n|$)/, "")
|
|
894
1022
|
conf += "@defuns = #{@defuns}\n" unless @defuns.empty?
|
|
1023
|
+
conf.sub!(/^@history_dedup.*(\n|$)/, "")
|
|
1024
|
+
conf += "@history_dedup = '#{@history_dedup}'\n" if @history_dedup && @history_dedup != 'smart'
|
|
1025
|
+
conf.sub!(/^@session_autosave.*(\n|$)/, "")
|
|
1026
|
+
conf += "@session_autosave = #{@session_autosave}\n" if @session_autosave && @session_autosave > 0
|
|
895
1027
|
# Only write @cmd_completions if user has customized it
|
|
896
1028
|
unless conf =~ /^@cmd_completions\s*=/
|
|
897
1029
|
# Don't write default completions to avoid cluttering .rshrc
|
|
@@ -928,6 +1060,7 @@ def help
|
|
|
928
1060
|
left_col << "RIGHT/Ctrl-F Accept suggestion"
|
|
929
1061
|
left_col << "UP/DOWN Navigate history"
|
|
930
1062
|
left_col << "TAB Tab complete"
|
|
1063
|
+
left_col << "Shift-TAB Search history"
|
|
931
1064
|
left_col << "Ctrl-Y Copy to clipboard"
|
|
932
1065
|
left_col << "Ctrl-D Exit + save .rshrc"
|
|
933
1066
|
left_col << "Ctrl-E Exit without save"
|
|
@@ -964,15 +1097,20 @@ def help
|
|
|
964
1097
|
right_col << ":fg [id] Foreground job"
|
|
965
1098
|
right_col << ":bg [id] Resume in bg"
|
|
966
1099
|
right_col << ""
|
|
967
|
-
right_col << "v3.0
|
|
968
|
-
right_col << ":stats
|
|
1100
|
+
right_col << "v3.0/3.1 FEATURES:".c(@c_prompt).b
|
|
1101
|
+
right_col << ":stats [--csv|--json] Analytics"
|
|
969
1102
|
right_col << ":bm \"name\" Create bookmark"
|
|
970
1103
|
right_col << "name Jump to bookmark"
|
|
971
|
-
right_col << ":bm
|
|
972
|
-
right_col << ":bm
|
|
973
|
-
right_col << ":bm
|
|
974
|
-
right_col << ":save_session
|
|
975
|
-
right_col << ":load_session
|
|
1104
|
+
right_col << ":bm --stats Bookmark stats"
|
|
1105
|
+
right_col << ":bm --export file Export bookmarks"
|
|
1106
|
+
right_col << ":bm --import file Import bookmarks"
|
|
1107
|
+
right_col << ":save_session [nm] Save session"
|
|
1108
|
+
right_col << ":load_session [nm] Load session"
|
|
1109
|
+
right_col << ":list_sessions List all sessions"
|
|
1110
|
+
right_col << ":rmsession name|* Delete session(s)"
|
|
1111
|
+
right_col << ":theme [name] Color presets"
|
|
1112
|
+
right_col << ":config [set val] Settings"
|
|
1113
|
+
right_col << ":env [VARNAME] Env management"
|
|
976
1114
|
right_col << ""
|
|
977
1115
|
right_col << "INTEGRATIONS:".c(@c_prompt).b
|
|
978
1116
|
right_col << "r Launch rtfm"
|
|
@@ -1176,7 +1314,26 @@ def defun? # Show all user-defined functions
|
|
|
1176
1314
|
end
|
|
1177
1315
|
end
|
|
1178
1316
|
end
|
|
1179
|
-
def stats # Show command execution statistics and analytics
|
|
1317
|
+
def stats(*args) # Show command execution statistics and analytics
|
|
1318
|
+
format = args[0]
|
|
1319
|
+
filename = args[1]
|
|
1320
|
+
|
|
1321
|
+
if format == "--export"
|
|
1322
|
+
# Export to file
|
|
1323
|
+
fname = filename || "rsh_stats.json"
|
|
1324
|
+
export_stats(fname)
|
|
1325
|
+
return
|
|
1326
|
+
elsif format == "--json"
|
|
1327
|
+
fname = filename || "rsh_stats.json"
|
|
1328
|
+
export_stats_json(fname)
|
|
1329
|
+
return
|
|
1330
|
+
elsif format == "--csv"
|
|
1331
|
+
fname = filename || "rsh_stats.csv"
|
|
1332
|
+
export_stats_csv(fname)
|
|
1333
|
+
return
|
|
1334
|
+
end
|
|
1335
|
+
|
|
1336
|
+
# Display stats (existing code)
|
|
1180
1337
|
puts "\n Command Execution Statistics".c(@c_prompt).b
|
|
1181
1338
|
puts " " + "="*50
|
|
1182
1339
|
|
|
@@ -1216,11 +1373,62 @@ def stats # Show command execution statistics and analytics
|
|
|
1216
1373
|
puts "\n Last command exit status: #{@last_exit == 0 ? 'Success'.c(@c_path) : "Failed (#{@last_exit})".c(196)}"
|
|
1217
1374
|
puts
|
|
1218
1375
|
end
|
|
1219
|
-
def
|
|
1220
|
-
if
|
|
1376
|
+
def export_stats(filename) # Export stats to file (JSON or CSV based on extension)
|
|
1377
|
+
if filename.end_with?('.csv')
|
|
1378
|
+
export_stats_csv(filename)
|
|
1379
|
+
else
|
|
1380
|
+
filename += '.json' unless filename.end_with?('.json')
|
|
1381
|
+
export_stats_json(filename)
|
|
1382
|
+
end
|
|
1383
|
+
end
|
|
1384
|
+
def export_stats_json(filename = 'rsh_stats.json') # Export stats to JSON
|
|
1385
|
+
stats_data = {
|
|
1386
|
+
generated: Time.now.to_i,
|
|
1387
|
+
cmd_frequency: @cmd_frequency,
|
|
1388
|
+
cmd_stats: @cmd_stats,
|
|
1389
|
+
history: {
|
|
1390
|
+
total: @history.length,
|
|
1391
|
+
unique: @history.uniq.length
|
|
1392
|
+
},
|
|
1393
|
+
last_exit: @last_exit
|
|
1394
|
+
}
|
|
1395
|
+
begin
|
|
1396
|
+
require 'json'
|
|
1397
|
+
File.write(filename, JSON.pretty_generate(stats_data))
|
|
1398
|
+
puts "Stats exported to #{filename}"
|
|
1399
|
+
rescue => e
|
|
1400
|
+
puts "Error exporting stats: #{e.message}"
|
|
1401
|
+
end
|
|
1402
|
+
end
|
|
1403
|
+
def export_stats_csv(filename = 'rsh_stats.csv') # Export stats to CSV
|
|
1404
|
+
begin
|
|
1405
|
+
lines = []
|
|
1406
|
+
lines << "command,frequency,count,total_time,avg_time"
|
|
1407
|
+
|
|
1408
|
+
# Merge frequency and performance data
|
|
1409
|
+
all_cmds = (@cmd_frequency.keys + @cmd_stats.keys).uniq
|
|
1410
|
+
all_cmds.sort.each do |cmd|
|
|
1411
|
+
freq = @cmd_frequency[cmd] || 0
|
|
1412
|
+
count = @cmd_stats.dig(cmd, :count) || 0
|
|
1413
|
+
total = @cmd_stats.dig(cmd, :total_time) || 0.0
|
|
1414
|
+
avg = @cmd_stats.dig(cmd, :avg_time) || 0.0
|
|
1415
|
+
lines << "#{cmd},#{freq},#{count},#{'%.3f' % total},#{'%.3f' % avg}"
|
|
1416
|
+
end
|
|
1417
|
+
|
|
1418
|
+
File.write(filename, lines.join("\n"))
|
|
1419
|
+
puts "Stats exported to #{filename}"
|
|
1420
|
+
rescue => e
|
|
1421
|
+
puts "Error exporting stats: #{e.message}"
|
|
1422
|
+
end
|
|
1423
|
+
end
|
|
1424
|
+
def bm(*args) # Enhanced bookmark management with tags
|
|
1425
|
+
# Handle variadic arguments
|
|
1426
|
+
arg_str = args.join(' ')
|
|
1427
|
+
|
|
1428
|
+
if args.empty?
|
|
1221
1429
|
# List all bookmarks
|
|
1222
1430
|
if @bookmarks.empty?
|
|
1223
|
-
puts "No bookmarks defined. Use :
|
|
1431
|
+
puts "No bookmarks defined. Use :bm \"name\" to bookmark current directory"
|
|
1224
1432
|
return
|
|
1225
1433
|
end
|
|
1226
1434
|
puts "\n Bookmarks:".c(@c_prompt).b
|
|
@@ -1230,7 +1438,18 @@ def bm(args = nil) # Enhanced bookmark management with tags
|
|
|
1230
1438
|
puts " #{name.c(@c_nick)} → #{path}#{tags.c(@c_stamp)}"
|
|
1231
1439
|
end
|
|
1232
1440
|
puts
|
|
1233
|
-
elsif args
|
|
1441
|
+
elsif args[0] == '--export'
|
|
1442
|
+
# Export bookmarks to file
|
|
1443
|
+
filename = args[1] || 'bookmarks.json'
|
|
1444
|
+
export_bookmarks(filename)
|
|
1445
|
+
elsif args[0] == '--import'
|
|
1446
|
+
# Import bookmarks from file
|
|
1447
|
+
filename = args[1]
|
|
1448
|
+
import_bookmarks(filename) if filename
|
|
1449
|
+
elsif args[0] == '--stats'
|
|
1450
|
+
# Show bookmark statistics
|
|
1451
|
+
bookmark_stats
|
|
1452
|
+
elsif arg_str =~ /^(\w+)\s+(.+)$/
|
|
1234
1453
|
# Set bookmark with optional tags
|
|
1235
1454
|
name, rest = $1, $2
|
|
1236
1455
|
if rest.include?('#')
|
|
@@ -1244,7 +1463,7 @@ def bm(args = nil) # Enhanced bookmark management with tags
|
|
|
1244
1463
|
end
|
|
1245
1464
|
puts "Bookmark '#{name}' set to #{@bookmarks[name][:path]}"
|
|
1246
1465
|
rshrc
|
|
1247
|
-
elsif
|
|
1466
|
+
elsif arg_str =~ /^-(\w+)$/
|
|
1248
1467
|
# Delete bookmark
|
|
1249
1468
|
name = $1
|
|
1250
1469
|
if @bookmarks.delete(name)
|
|
@@ -1253,7 +1472,7 @@ def bm(args = nil) # Enhanced bookmark management with tags
|
|
|
1253
1472
|
else
|
|
1254
1473
|
puts "Bookmark '#{name}' not found"
|
|
1255
1474
|
end
|
|
1256
|
-
elsif
|
|
1475
|
+
elsif arg_str =~ /^\?(\w*)$/
|
|
1257
1476
|
# Search bookmarks by tag
|
|
1258
1477
|
tag = $1
|
|
1259
1478
|
if tag.empty?
|
|
@@ -1273,17 +1492,82 @@ def bm(args = nil) # Enhanced bookmark management with tags
|
|
|
1273
1492
|
end
|
|
1274
1493
|
else
|
|
1275
1494
|
# Bookmark current directory
|
|
1276
|
-
name =
|
|
1495
|
+
name = arg_str.strip
|
|
1277
1496
|
@bookmarks[name] = {path: Dir.pwd, tags: []}
|
|
1278
1497
|
puts "Bookmark '#{name}' set to #{Dir.pwd}"
|
|
1279
1498
|
rshrc
|
|
1280
1499
|
end
|
|
1281
1500
|
end
|
|
1282
|
-
def bookmark(args
|
|
1283
|
-
bm(args)
|
|
1501
|
+
def bookmark(*args) # Alias for bm
|
|
1502
|
+
bm(*args)
|
|
1503
|
+
end
|
|
1504
|
+
def export_bookmarks(filename = 'bookmarks.json') # Export bookmarks to JSON
|
|
1505
|
+
begin
|
|
1506
|
+
require 'json'
|
|
1507
|
+
File.write(filename, JSON.pretty_generate(@bookmarks))
|
|
1508
|
+
puts "Bookmarks exported to #{filename}"
|
|
1509
|
+
rescue => e
|
|
1510
|
+
puts "Error exporting bookmarks: #{e.message}"
|
|
1511
|
+
end
|
|
1512
|
+
end
|
|
1513
|
+
def import_bookmarks(filename) # Import bookmarks from JSON
|
|
1514
|
+
unless File.exist?(filename)
|
|
1515
|
+
puts "File '#{filename}' not found"
|
|
1516
|
+
return
|
|
1517
|
+
end
|
|
1518
|
+
begin
|
|
1519
|
+
require 'json'
|
|
1520
|
+
imported = JSON.parse(File.read(filename))
|
|
1521
|
+
imported.each do |name, data|
|
|
1522
|
+
# Convert to proper format
|
|
1523
|
+
if data.is_a?(Hash)
|
|
1524
|
+
@bookmarks[name] = {
|
|
1525
|
+
path: data['path'] || data[:path],
|
|
1526
|
+
tags: data['tags'] || data[:tags] || []
|
|
1527
|
+
}.transform_keys(&:to_sym)
|
|
1528
|
+
else
|
|
1529
|
+
@bookmarks[name] = {path: data.to_s, tags: []}
|
|
1530
|
+
end
|
|
1531
|
+
end
|
|
1532
|
+
puts "Imported #{imported.length} bookmarks from #{filename}"
|
|
1533
|
+
rshrc
|
|
1534
|
+
rescue => e
|
|
1535
|
+
puts "Error importing bookmarks: #{e.message}"
|
|
1536
|
+
end
|
|
1537
|
+
end
|
|
1538
|
+
def bookmark_stats # Show bookmark usage statistics
|
|
1539
|
+
if @bookmarks.empty?
|
|
1540
|
+
puts "No bookmarks defined"
|
|
1541
|
+
return
|
|
1542
|
+
end
|
|
1543
|
+
|
|
1544
|
+
puts "\n Bookmark Statistics".c(@c_prompt).b
|
|
1545
|
+
puts " " + "="*50
|
|
1546
|
+
puts "\n Total bookmarks: #{@bookmarks.length}"
|
|
1547
|
+
|
|
1548
|
+
# Count by tags
|
|
1549
|
+
all_tags = @bookmarks.values.flat_map { |d| d.is_a?(Hash) ? (d[:tags] || []) : [] }
|
|
1550
|
+
unless all_tags.empty?
|
|
1551
|
+
puts "\n Tags Distribution:".c(@c_nick)
|
|
1552
|
+
tag_counts = all_tags.group_by(&:itself).transform_values(&:count)
|
|
1553
|
+
tag_counts.sort_by { |_, count| -count }.each do |tag, count|
|
|
1554
|
+
puts " #{tag.ljust(15)} #{count}x"
|
|
1555
|
+
end
|
|
1556
|
+
end
|
|
1557
|
+
|
|
1558
|
+
# Bookmarks by directory depth
|
|
1559
|
+
puts "\n Path Analysis:".c(@c_nick)
|
|
1560
|
+
paths = @bookmarks.values.map { |d| d.is_a?(Hash) ? d[:path] : d }
|
|
1561
|
+
avg_depth = paths.map { |p| p.split('/').length }.sum / paths.length
|
|
1562
|
+
puts " Average path depth: #{avg_depth}"
|
|
1563
|
+
puts
|
|
1284
1564
|
end
|
|
1285
|
-
def save_session # Save current session state
|
|
1565
|
+
def save_session(*args) # Save current session state
|
|
1566
|
+
session_name = args[0] || 'default'
|
|
1567
|
+
session_path = @session_dir + "/#{session_name}.json"
|
|
1568
|
+
|
|
1286
1569
|
session = {
|
|
1570
|
+
name: session_name,
|
|
1287
1571
|
pwd: Dir.pwd,
|
|
1288
1572
|
history: @history.first(50),
|
|
1289
1573
|
bookmarks: @bookmarks,
|
|
@@ -1292,20 +1576,24 @@ def save_session # Save current session state
|
|
|
1292
1576
|
}
|
|
1293
1577
|
begin
|
|
1294
1578
|
require 'json'
|
|
1295
|
-
File.write(
|
|
1296
|
-
puts "Session saved to #{
|
|
1579
|
+
File.write(session_path, JSON.pretty_generate(session))
|
|
1580
|
+
puts "Session '#{session_name}' saved to #{session_path}"
|
|
1297
1581
|
rescue => e
|
|
1298
1582
|
puts "Error saving session: #{e.message}"
|
|
1299
1583
|
end
|
|
1300
1584
|
end
|
|
1301
|
-
def load_session # Restore previous session
|
|
1302
|
-
|
|
1303
|
-
|
|
1585
|
+
def load_session(*args) # Restore previous session
|
|
1586
|
+
session_name = args[0] || 'default'
|
|
1587
|
+
session_path = @session_dir + "/#{session_name}.json"
|
|
1588
|
+
|
|
1589
|
+
unless File.exist?(session_path)
|
|
1590
|
+
puts "Session '#{session_name}' not found"
|
|
1591
|
+
list_sessions
|
|
1304
1592
|
return
|
|
1305
1593
|
end
|
|
1306
1594
|
begin
|
|
1307
1595
|
require 'json'
|
|
1308
|
-
session = JSON.parse(File.read(
|
|
1596
|
+
session = JSON.parse(File.read(session_path), symbolize_names: true)
|
|
1309
1597
|
|
|
1310
1598
|
# Restore state
|
|
1311
1599
|
Dir.chdir(session[:pwd]) if session[:pwd] && Dir.exist?(session[:pwd])
|
|
@@ -1338,12 +1626,124 @@ def load_session # Restore previous session
|
|
|
1338
1626
|
end
|
|
1339
1627
|
|
|
1340
1628
|
saved_time = Time.at(session[:timestamp] || 0).strftime("%Y-%m-%d %H:%M:%S")
|
|
1341
|
-
puts "Session restored from #{saved_time}"
|
|
1629
|
+
puts "Session '#{session_name}' restored from #{saved_time}"
|
|
1342
1630
|
rshrc
|
|
1343
1631
|
rescue => e
|
|
1344
1632
|
puts "Error loading session: #{e.message}"
|
|
1345
1633
|
end
|
|
1346
1634
|
end
|
|
1635
|
+
def list_sessions # List all saved sessions
|
|
1636
|
+
unless Dir.exist?(@session_dir)
|
|
1637
|
+
puts "No sessions directory found"
|
|
1638
|
+
return
|
|
1639
|
+
end
|
|
1640
|
+
|
|
1641
|
+
sessions = Dir.glob(@session_dir + '/*.json').map { |f| File.basename(f, '.json') }
|
|
1642
|
+
|
|
1643
|
+
if sessions.empty?
|
|
1644
|
+
puts "No saved sessions found. Use :save_session \"name\" to create one"
|
|
1645
|
+
return
|
|
1646
|
+
end
|
|
1647
|
+
|
|
1648
|
+
puts "\n Saved Sessions:".c(@c_prompt).b
|
|
1649
|
+
sessions.sort.each do |name|
|
|
1650
|
+
session_path = @session_dir + "/#{name}.json"
|
|
1651
|
+
begin
|
|
1652
|
+
require 'json'
|
|
1653
|
+
session = JSON.parse(File.read(session_path), symbolize_names: true)
|
|
1654
|
+
timestamp = Time.at(session[:timestamp] || 0).strftime("%Y-%m-%d %H:%M")
|
|
1655
|
+
pwd = session[:pwd] || '?'
|
|
1656
|
+
puts " #{name.c(@c_bookmark).ljust(20)} #{timestamp.c(@c_stamp)} #{pwd.c(@c_path)}"
|
|
1657
|
+
rescue => e
|
|
1658
|
+
puts " #{name.c(@c_bookmark).ljust(20)} [corrupted]".c(196)
|
|
1659
|
+
end
|
|
1660
|
+
end
|
|
1661
|
+
puts
|
|
1662
|
+
end
|
|
1663
|
+
def delete_session(*args) # Delete a saved session
|
|
1664
|
+
name = args[0]
|
|
1665
|
+
|
|
1666
|
+
if name == '*'
|
|
1667
|
+
# Delete all sessions except default
|
|
1668
|
+
sessions = Dir.glob(@session_dir + '/*.json').map { |f| File.basename(f, '.json') }
|
|
1669
|
+
sessions.reject! { |s| s == 'default' || s == 'autosave' }
|
|
1670
|
+
|
|
1671
|
+
if sessions.empty?
|
|
1672
|
+
puts "No sessions to delete (keeping default and autosave)"
|
|
1673
|
+
return
|
|
1674
|
+
end
|
|
1675
|
+
|
|
1676
|
+
sessions.each do |session_name|
|
|
1677
|
+
session_path = @session_dir + "/#{session_name}.json"
|
|
1678
|
+
File.delete(session_path)
|
|
1679
|
+
end
|
|
1680
|
+
puts "Deleted #{sessions.length} sessions: #{sessions.join(', ')}"
|
|
1681
|
+
return
|
|
1682
|
+
end
|
|
1683
|
+
|
|
1684
|
+
return puts "Cannot delete default session" if name == 'default'
|
|
1685
|
+
return puts "Cannot delete autosave session (use * to delete all)" if name == 'autosave'
|
|
1686
|
+
|
|
1687
|
+
session_path = @session_dir + "/#{name}.json"
|
|
1688
|
+
unless File.exist?(session_path)
|
|
1689
|
+
puts "Session '#{name}' not found"
|
|
1690
|
+
return
|
|
1691
|
+
end
|
|
1692
|
+
|
|
1693
|
+
File.delete(session_path)
|
|
1694
|
+
puts "Session '#{name}' deleted"
|
|
1695
|
+
end
|
|
1696
|
+
def rmsession(*args) # Alias for delete_session
|
|
1697
|
+
delete_session(*args)
|
|
1698
|
+
end
|
|
1699
|
+
def theme(*args) # Apply color scheme presets
|
|
1700
|
+
name = args[0]
|
|
1701
|
+
|
|
1702
|
+
if name.nil?
|
|
1703
|
+
puts "\n Available themes:".c(@c_prompt).b
|
|
1704
|
+
puts " default, solarized, dracula, gruvbox, nord, monokai"
|
|
1705
|
+
puts "\n Current theme colors:"
|
|
1706
|
+
puts " prompt:#{' '*5}#{@c_prompt} cmd:#{' '*8}#{@c_cmd} nick:#{' '*7}#{@c_nick}"
|
|
1707
|
+
puts " gnick:#{' '*6}#{@c_gnick} path:#{' '*7}#{@c_path} switch:#{' '*5}#{@c_switch}"
|
|
1708
|
+
puts " bookmark:#{' '*3}#{@c_bookmark} colon:#{' '*6}#{@c_colon} tabselect:#{' '*2}#{@c_tabselect}"
|
|
1709
|
+
puts " taboption:#{' '*2}#{@c_taboption} stamp:#{' '*6}#{@c_stamp}"
|
|
1710
|
+
puts
|
|
1711
|
+
return
|
|
1712
|
+
end
|
|
1713
|
+
|
|
1714
|
+
case name.downcase
|
|
1715
|
+
when 'default'
|
|
1716
|
+
@c_prompt, @c_cmd, @c_nick, @c_gnick = 10, 2, 6, 14
|
|
1717
|
+
@c_path, @c_switch, @c_bookmark, @c_colon = 3, 6, 13, 4
|
|
1718
|
+
@c_tabselect, @c_taboption, @c_stamp = 5, 244, 244
|
|
1719
|
+
when 'solarized'
|
|
1720
|
+
@c_prompt, @c_cmd, @c_nick, @c_gnick = 33, 64, 37, 117
|
|
1721
|
+
@c_path, @c_switch, @c_bookmark, @c_colon = 136, 125, 61, 33
|
|
1722
|
+
@c_tabselect, @c_taboption, @c_stamp = 166, 240, 240
|
|
1723
|
+
when 'dracula'
|
|
1724
|
+
@c_prompt, @c_cmd, @c_nick, @c_gnick = 141, 84, 117, 212
|
|
1725
|
+
@c_path, @c_switch, @c_bookmark, @c_colon = 228, 215, 141, 141
|
|
1726
|
+
@c_tabselect, @c_taboption, @c_stamp = 212, 238, 238
|
|
1727
|
+
when 'gruvbox'
|
|
1728
|
+
@c_prompt, @c_cmd, @c_nick, @c_gnick = 214, 142, 109, 175
|
|
1729
|
+
@c_path, @c_switch, @c_bookmark, @c_colon = 208, 142, 167, 214
|
|
1730
|
+
@c_tabselect, @c_taboption, @c_stamp = 208, 243, 243
|
|
1731
|
+
when 'nord'
|
|
1732
|
+
@c_prompt, @c_cmd, @c_nick, @c_gnick = 110, 109, 116, 152
|
|
1733
|
+
@c_path, @c_switch, @c_bookmark, @c_colon = 180, 109, 139, 110
|
|
1734
|
+
@c_tabselect, @c_taboption, @c_stamp = 143, 240, 240
|
|
1735
|
+
when 'monokai'
|
|
1736
|
+
@c_prompt, @c_cmd, @c_nick, @c_gnick = 197, 112, 81, 141
|
|
1737
|
+
@c_path, @c_switch, @c_bookmark, @c_colon = 228, 208, 141, 197
|
|
1738
|
+
@c_tabselect, @c_taboption, @c_stamp = 197, 238, 238
|
|
1739
|
+
else
|
|
1740
|
+
puts "Unknown theme '#{name}'. Available: default, solarized, dracula, gruvbox, nord, monokai"
|
|
1741
|
+
return
|
|
1742
|
+
end
|
|
1743
|
+
|
|
1744
|
+
puts "Theme '#{name}' applied"
|
|
1745
|
+
puts "Add this to .rshrc to make it permanent: :theme \"#{name}\""
|
|
1746
|
+
end
|
|
1347
1747
|
def validate_command(cmd) # Syntax validation before execution
|
|
1348
1748
|
return nil if cmd.nil? || cmd.empty?
|
|
1349
1749
|
warnings = []
|
|
@@ -1653,6 +2053,8 @@ def load_rshrc_safe
|
|
|
1653
2053
|
@cmd_stats = {} unless @cmd_stats.is_a?(Hash)
|
|
1654
2054
|
@bookmarks = {} unless @bookmarks.is_a?(Hash)
|
|
1655
2055
|
@defuns = {} unless @defuns.is_a?(Hash)
|
|
2056
|
+
@history_dedup = 'smart' unless @history_dedup.is_a?(String)
|
|
2057
|
+
@session_autosave = 0 unless @session_autosave.is_a?(Integer)
|
|
1656
2058
|
|
|
1657
2059
|
# Restore defuns from .rshrc
|
|
1658
2060
|
if @defuns && !@defuns.empty?
|
|
@@ -1787,6 +2189,8 @@ def load_defaults
|
|
|
1787
2189
|
@defuns ||= {}
|
|
1788
2190
|
@switch_cache ||= {}
|
|
1789
2191
|
@switch_cache_time ||= {}
|
|
2192
|
+
@history_dedup ||= 'smart'
|
|
2193
|
+
@session_autosave ||= 0
|
|
1790
2194
|
puts "Loaded with default configuration."
|
|
1791
2195
|
end
|
|
1792
2196
|
|
|
@@ -1872,6 +2276,14 @@ loop do
|
|
|
1872
2276
|
system("printf \"\033]0;rsh: #{Dir.pwd}\007\"") # Set Window title to path
|
|
1873
2277
|
@history[0] = "" unless @history[0]
|
|
1874
2278
|
cache_executables # Use cached executable lookup
|
|
2279
|
+
# Auto-save session if enabled and interval elapsed
|
|
2280
|
+
if @session_autosave && @session_autosave > 0
|
|
2281
|
+
current_time = Time.now.to_i
|
|
2282
|
+
if (current_time - @session_last_save) >= @session_autosave
|
|
2283
|
+
save_session('autosave')
|
|
2284
|
+
@session_last_save = current_time
|
|
2285
|
+
end
|
|
2286
|
+
end
|
|
1875
2287
|
getstr # Main work is here
|
|
1876
2288
|
@cmd = @history[0]
|
|
1877
2289
|
@dirs.unshift(Dir.pwd)
|
|
@@ -1976,6 +2388,7 @@ loop do
|
|
|
1976
2388
|
elsif @bookmarks && @bookmarks[@cmd]
|
|
1977
2389
|
bookmark_data = @bookmarks[@cmd]
|
|
1978
2390
|
bm_dir = bookmark_data.is_a?(Hash) ? bookmark_data[:path] : bookmark_data
|
|
2391
|
+
bm_dir = bm_dir.sub(/^~/, Dir.home) # Expand tilde
|
|
1979
2392
|
if Dir.exist?(bm_dir)
|
|
1980
2393
|
Dir.chdir(bm_dir)
|
|
1981
2394
|
puts "Jumped to bookmark '#{@cmd}' → #{bm_dir}".c(@c_path)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby-shell
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Geir Isene
|
|
@@ -12,9 +12,9 @@ date: 2025-10-22 00:00:00.000000000 Z
|
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: 'A shell written in Ruby with extensive tab completions, aliases/nicks,
|
|
14
14
|
history, syntax highlighting, theming, auto-cd, auto-opening files and more. UPDATE
|
|
15
|
-
v3.
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
v3.1.0: Multiple named sessions, stats export (CSV/JSON), session auto-save, bookmark
|
|
16
|
+
import/export, bookmark statistics, 6 color themes, config management, environment
|
|
17
|
+
variable tools, bookmark TAB completion, and much more!'
|
|
18
18
|
email: g@isene.com
|
|
19
19
|
executables:
|
|
20
20
|
- rsh
|