ruby-shell 3.5.0 → 3.6.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 (3) hide show
  1. checksums.yaml +4 -4
  2. data/bin/rsh +162 -89
  3. metadata +5 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bac3cf4ea368be8c2991ee650e1977c7fafd7b1548645c1e3daaa94fd29bae2d
4
- data.tar.gz: 31b8ae6ffcf460c7a236c72c8188254243879ab8b83ac690fcf8e61fc0aa46ad
3
+ metadata.gz: 84b836c4727e19dc59d0e12f980ebb4437b729a474247d94288d017f39d735b3
4
+ data.tar.gz: 72a2ef18a3a12bce6c1081743801d0f996373199268c084ffdb39e5b6f4771e8
5
5
  SHA512:
6
- metadata.gz: bb8eb4cef6df5403bc751cf29d440a4289877fd9f7684f153e82196a3f24e425f88fabbe2ccae63c0ef108cae205feb241e84b52f138ad07975d03bd38834a7b
7
- data.tar.gz: 96eb9e448ffd6c20b469e1b0d0f11e7f8dc274e809c4cc543dea3aa7ff5b5a5121a1aae6de7522cb3af1b3ff59e8e655df7af68ed54322f96844c5df910b4904
6
+ metadata.gz: de67a854302e2855c5fc9fd74a34cf550a98e3e3827cc83862c6b3f8cb329b3da8e0c491bf4266c57f3c066c41d79f0858899f5d9578ce97641a6a2f3c56a15b
7
+ data.tar.gz: a3a84a2553228efcbb9e77bbca2c01f7e3b689b1570a02e19a65d2b6c3e85a0bded51519a2bb16b04654687d8e7926272d112e7b4c33029318e9c454e0b8952a
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.5.0" # Polish release: Nick/history export, default nicks, expanded completions, startup tips, validation templates
11
+ @version = "3.6.1" # Ctrl-L fixes: Position reset, visible length calculation, no duplicate prompts
12
12
 
13
13
  # MODULES, CLASSES AND EXTENSIONS
14
14
  class String # Add coloring to strings (with escaping for Readline)
@@ -185,67 +185,44 @@ end
185
185
  # HELP TEXT
186
186
  @info = <<~INFO
187
187
 
188
- Hello #{@user}, welcome to rsh v3.3 - the Ruby SHell.
189
-
190
- rsh does not attempt to compete with the grand old shells like bash and zsh.
191
- It serves the specific needs and wants of its author. If you like it, then feel free
192
- to ask for more or different features here: https://github.com/isene/rsh.
193
-
194
- Core Features:
195
- * Aliases (called nicks in rsh) - both for commands and general nicks
196
- * Syntax highlighting for nicks, bookmarks, commands, switches and valid dirs/files
197
- * Tab completions for nicks, system commands, command switches and dirs/files
198
- * Smart context-aware tab completion for git, apt, docker, systemctl, cargo, npm, gem, bundle
199
- * History with editing, search and repeat (use `!` or UP arrow)
200
- * Auto-suggestions from history (press RIGHT arrow to accept)
201
- * Ruby functions callable as shell commands (persistent across sessions)
202
- * AI integration: Use @ for text responses and @@ for command suggestions
203
-
204
- NEW in v3.0:
205
- * Command analytics - :stats shows usage patterns and performance metrics
206
- * Enhanced bookmarks with tags - :bm "name path #tag1,tag2" then just type name to jump
207
- * Session management - :save_session and :load_session preserve your entire shell state
208
- * Smart typo detection - "Did you mean...?" suggestions for misspelled commands
209
- * Switch caching - Faster TAB completion for command options
210
- * Option value completion - TAB complete values like --format=json
211
- * Syntax validation - Pre-execution warnings for dangerous or malformed commands
212
- * Unified command syntax - :nick, :gnick, :bm all work the same way (list/create/delete)
213
-
214
- NEW in v3.4:
215
- * Completion learning - Shell learns which TAB completions you use and ranks them higher
216
- * Context-aware - Separate learning for each command (git, ls, docker, etc.)
217
- * :completion_stats - View learned patterns with visual bar charts
218
- * Persistent - Learning data saves to .rshrc across sessions
219
-
220
- v3.3 Features:
221
- * Quote-less syntax - No more quotes! Use :nick la = ls -la
222
- * Parametrized nicks - :nick gp = git push origin {{branch}}, then: gp branch=main
223
- * Ctrl-G multi-line edit - Press Ctrl-G to edit command in $EDITOR
224
- * Custom validation - :validate rm -rf / = block prevents dangerous commands
225
- * Shell script support - for/while/if loops work with full bash syntax
226
-
227
- v3.2 Features:
228
- * Plugin system - Extensible architecture for custom commands, completions, and hooks
229
- * Auto-correct typos - :config auto_correct on (with confirmation prompt)
230
- * Command timing alerts - :config slow_command_threshold 5 warns on slow commands
231
- * Inline calculator - :calc 2 + 2, :calc "Math::PI", full Ruby Math library
232
- * Enhanced history - !!, !-2, !5:7 for repeat last, nth-to-last, and chaining
233
- * Stats visualization - :stats --graph for colorful ASCII bar charts
234
-
235
- v3.1 Features:
236
- * Multiple named sessions - :save_session "project" and :load_session "project"
237
- * Stats export - :stats --csv or :stats --json for data analysis
238
- * Session auto-save - Set @session_autosave = 300 in .rshrc for 5-min auto-save
239
- * Bookmark import/export - :bm --export file.json and :bm --import file.json
240
- * Bookmark statistics - :bm --stats shows usage patterns and tag distribution
241
- * Color themes - :theme solarized|dracula|gruvbox|nord|monokai
242
- * Config management - :config shows/sets history_dedup, session_autosave, etc.
243
- * Environment management - :env for listing/setting/exporting environment variables
244
-
245
- Config file (.rshrc) updates on exit (Ctrl-d) or not (Ctrl-e).
246
- All colors are themeable in .rshrc (see github link for possibilities).
247
-
248
- Use `:help` for complete command reference.
188
+ Hello #{@user}, welcome to rsh v3.6 - the Ruby SHell.
189
+
190
+ rsh is a feature-rich shell written in pure Ruby, designed for productivity and customization.
191
+
192
+ KEY FEATURES:
193
+ • Nicks (aliases) with {{parameters}} - :nick gp = git push origin {{branch}}
194
+ Completion learning - Shell learns and adapts to your patterns
195
+ Plugin system - Extend with custom commands and hooks
196
+ Command recording/replay - Automate workflows
197
+ Sessions & bookmarks - Save and restore your workspace
198
+ Multi-line prompts - Full support for prompts with newlines
199
+ Stats & analytics - :stats shows usage patterns and performance
200
+ Validation rules - :validate rm -rf / = block for safety
201
+ 6 color themes - :theme solarized|dracula|gruvbox|nord|monokai|default
202
+ Auto-correct - Smart typo detection with suggestions
203
+ • Shell scripts - Full bash support (for, while, if loops)
204
+ AI integration - @ for questions, @@ for command suggestions
205
+ Ruby functions - :defun for custom shell commands
206
+ 18 tool completions - git, docker, kubectl, terraform, aws, etc.
207
+
208
+ QUICK START:
209
+ :help - Command reference
210
+ :info --features - Feature list
211
+ :nick - Create alias
212
+ :bm - Bookmark directory
213
+ • :stats - View analytics
214
+ :theme - Change colors
215
+ :plugins - Extend functionality
216
+
217
+ CUSTOMIZATION:
218
+ Config file: ~/.rshrc (edit with :config or manually)
219
+ • Colors: All themeable (see :help)
220
+ Exit: Ctrl-D (save) or Ctrl-E (don't save)
221
+
222
+ MORE INFO:
223
+ GitHub: https://github.com/isene/rsh
224
+ Docs: Type :help for quick reference
225
+ Version: Type :version for current/latest
249
226
 
250
227
  INFO
251
228
 
@@ -356,19 +333,74 @@ def getstr # A custom Readline-like function
356
333
  @pos = @ai_suggestion.length
357
334
  @ai_suggestion = nil
358
335
  end
336
+
337
+ # Print prompt ONCE at start (optimization + multi-line support)
359
338
  @row0, p = @c.pos
339
+
340
+ # Clear any previous prompt
341
+ @c.row(@row0)
342
+ @c.clear_screen_down
343
+
344
+ print @display_prompt
345
+
346
+ # Calculate positions and save prompt parts
347
+ if @display_prompt.include?("\n")
348
+ # Multi-line prompt
349
+ newline_count = @display_prompt.count("\n")
350
+ @row0 += newline_count
351
+
352
+ # Extract last line and reapply color codes (rcurses pattern)
353
+ # Detect ANY color codes at start of full prompt
354
+ color_codes = ""
355
+ if @display_prompt =~ /^(\001\e\[[0-9;]+m\002)/
356
+ color_codes = $1
357
+ end
358
+
359
+ # Split and get last line
360
+ lines = @display_prompt.split("\n")
361
+ @prompt_last_line = lines.last
362
+
363
+ # If original had colors and last line doesn't, reapply them
364
+ if !color_codes.empty? && !@prompt_last_line.start_with?("\001")
365
+ @prompt_last_line = color_codes + @prompt_last_line + "\001\e[0m\002"
366
+ end
367
+
368
+ # Calculate visible length (strip ANSI for counting)
369
+ @pos0 = @prompt_last_line.gsub(/\001?\e\[[0-9;]*m\002?/, '').length
370
+ @cmd_row = @row0
371
+ @is_multiline = true
372
+ else
373
+ # Single-line prompt
374
+ row, col = @c.pos
375
+ @prompt_last_line = @display_prompt
376
+ # Calculate visible length (strip ANSI) - don't trust @c.pos with complex prompts
377
+ @pos0 = @display_prompt.gsub(/\001\e\[[0-9;]*m\002/, '').length
378
+ @cmd_row = @row0
379
+ @is_multiline = false
380
+ end
381
+
382
+ @skip_prompt_print = false # Track if prompt already printed (for Ctrl-L)
383
+
360
384
  while chr != "ENTER" # Keep going with readline until user presses ENTER
361
385
  @ci = nil
362
386
  lift = false
363
387
  right = false
364
- # The actual printing og the command line
365
- @c.row(@row0)
366
- @c.clear_line
367
- print @display_prompt
368
- @c.clear_screen_down
369
- row, @pos0 = @c.pos
370
- #@history[0] = "" if @history[0].nil?
388
+
389
+ # Update command line display (not the prompt!)
390
+ @c.row(@cmd_row)
391
+
392
+ # Reprint the prompt line (preserves colors) - skip if Ctrl-L just printed it
393
+ unless @skip_prompt_print
394
+ @c.col(1)
395
+ print @prompt_last_line
396
+ @c.clear_line_after
397
+ end
398
+ @skip_prompt_print = false # Reset flag
399
+
400
+ # Print command text with syntax highlighting
371
401
  print cmd_check(@history[0])
402
+
403
+ # Print history suggestion if available
372
404
  @ci = @history[1..].find_index {|e| e =~ /^#{Regexp.escape(@history[0].to_s)}./}
373
405
  unless @ci == nil
374
406
  @ci += 1
@@ -378,15 +410,14 @@ def getstr # A custom Readline-like function
378
410
  print @ciprompt.c(@c_stamp)
379
411
  right = true
380
412
  end
381
- c_col = @pos0 + @pos
382
- c_row = @row0 + c_col/(@maxcol)
383
- c_col == 0 ? @c.row(c_row + 1) : @c.row(c_row)
384
- if c_col.modulo(@maxcol) == 0
385
- @c.col(c_col)
386
- @c.row(@c.rowget - 1)
387
- else
388
- @c.col(c_col.modulo(@maxcol))
389
- end
413
+
414
+ # Position cursor (unified - both now use string length which is 0-indexed)
415
+ # @pos0 = visible length of prompt (14 for "geir@juba: ~/ ")
416
+ # @pos = position in command text (0 = start, 5 = after 5 chars)
417
+ # Terminal columns are 1-indexed, so add +1
418
+ cursor_col = @pos0 + @pos + 1
419
+ @c.row(@cmd_row)
420
+ @c.col(cursor_col)
390
421
  chr = getchr
391
422
  puts "DEBUG: Got char: '#{chr}' (length: #{chr.length})" if ENV['RSH_DEBUG']
392
423
  case chr
@@ -416,8 +447,37 @@ def getstr # A custom Readline-like function
416
447
  exit
417
448
  when 'C-L' # Clear screen and set position to top of the screen
418
449
  @c.row(1)
419
- @row0 = 1
450
+ @c.col(1) # Also reset column to 1
420
451
  @c.clear_screen_down
452
+ # Reprint prompt after clear
453
+ @row0 = 1
454
+ print @display_prompt
455
+ # Recalculate positions (match initial setup exactly)
456
+ if @display_prompt.include?("\n")
457
+ newline_count = @display_prompt.count("\n")
458
+ @row0 += newline_count
459
+ # Reextract color codes and last line (same as init)
460
+ color_codes = ""
461
+ if @display_prompt =~ /^(\001\e\[[0-9;]+m\002)/
462
+ color_codes = $1
463
+ end
464
+ lines = @display_prompt.split("\n")
465
+ @prompt_last_line = lines.last
466
+ if !color_codes.empty? && !@prompt_last_line.start_with?("\001")
467
+ @prompt_last_line = color_codes + @prompt_last_line + "\001\e[0m\002"
468
+ end
469
+ @pos0 = @prompt_last_line.gsub(/\001?\e\[[0-9;]*m\002?/, '').length
470
+ @cmd_row = @row0
471
+ @is_multiline = true
472
+ else
473
+ row, col = @c.pos
474
+ @prompt_last_line = @display_prompt
475
+ # Calculate visible length (match main init)
476
+ @pos0 = @display_prompt.gsub(/\001\e\[[0-9;]*m\002/, '').length
477
+ @cmd_row = @row0
478
+ @is_multiline = false
479
+ end
480
+ @skip_prompt_print = true # Skip reprinting on next loop iteration
421
481
  when 'UP' # Go up in history
422
482
  if @stk == 0 and @history[0].length > 0
423
483
  @tabsearch = @history[0]
@@ -436,8 +496,6 @@ def getstr # A custom Readline-like function
436
496
  end
437
497
  lift = false
438
498
  end
439
- @c.row(@row0)
440
- @c.clear_screen_down
441
499
  when 'DOWN' # Go down in history
442
500
  if lift
443
501
  @history.unshift("")
@@ -457,8 +515,6 @@ def getstr # A custom Readline-like function
457
515
  @pos = @history[0].length
458
516
  end
459
517
  lift = false
460
- @c.row(@row0)
461
- @c.clear_screen_down
462
518
  when 'RIGHT' # Move right on the readline
463
519
  if right
464
520
  if lift
@@ -633,10 +689,17 @@ def tab(type)
633
689
  completed = @tabstr + "/"
634
690
  @history[0] = @pretab + completed + @postab
635
691
  @pos = @pretab.length + completed.length
636
- @c_col = @pos0 + @pos
692
+ # Consistent cursor calc
693
+ if @is_multiline
694
+ @c_col = @pos0 + @pos + 1
695
+ else
696
+ @c_col = @pos0 + @pos
697
+ end
637
698
  @c.clear_line
638
699
  line_display = cmd_check(@history[0]).to_s
639
- print @display_prompt + line_display
700
+ # Use last line only for multi-line prompts
701
+ prompt_display = @is_multiline ? @display_prompt.split("\n").last : @display_prompt
702
+ print prompt_display + line_display
640
703
  @c.col(@c_col)
641
704
  return
642
705
  end
@@ -851,6 +914,9 @@ def tab(type)
851
914
  @newhist0 = @pretab + tabchoice + @postab # Remember now the new value to be given to @history[0]
852
915
  line1 = cmd_check(@pretab).to_s # Syntax highlight before @tabstr
853
916
  line2 = cmd_check(@postab).to_s # Syntax highlight after @tabstr
917
+
918
+ # For multi-line prompts, only show last line during tab completion
919
+ prompt_for_tab = @display_prompt.include?("\n") ? @display_prompt.split("\n").last : @display_prompt
854
920
  # Color and underline the current tabchoice on the commandline with file type color:
855
921
  display_choice = tabchoice.dup
856
922
  clean_choice = tabchoice.gsub(/['"]/, '').chomp('/')
@@ -861,9 +927,14 @@ def tab(type)
861
927
  # Escape regex special characters in @tabstr for pattern matching
862
928
  escaped_tabstr = Regexp.escape(@tabstr)
863
929
  tabline = display_choice.sub(/(.*)#{escaped_tabstr}(.*)/, '\1'.c(choice_color) + @tabstr.u.c(choice_color) + '\2'.c(choice_color))
864
- print @display_prompt + line1 + tabline + line2 # Print the commandline
930
+ print prompt_for_tab + line1 + tabline + line2 # Print the commandline (use last line only for multi-line)
865
931
  @pos = @pretab.length.to_i + tabchoice.length.to_i # Set the position on that commandline
866
- @c_col = @pos0 + @pos # The cursor position must include the prompt as well
932
+ # Consistent cursor positioning
933
+ if @is_multiline
934
+ @c_col = @pos0 + @pos + 1 # Multi-line: string length needs +1
935
+ else
936
+ @c_col = @pos0 + @pos # Single-line: already 1-indexed
937
+ end
867
938
  @c.col(@c_col) # Set the cursor position
868
939
  nextline # Then start showing the completion items
869
940
  tabline = @tabarray[i] # Get the next matching tabline
@@ -3541,9 +3612,11 @@ loop do
3541
3612
  @cmd = hi if hi
3542
3613
  end
3543
3614
  # Move cursor to end of line and print the full command before clearing
3544
- @c.row(@row0)
3615
+ @c.row(@cmd_row || @row0) # Use cmd_row if set (multi-line)
3545
3616
  @c.clear_line
3546
- print @display_prompt + cmd_check(@cmd)
3617
+ # For multi-line, only print last line; for single-line, print full prompt
3618
+ prompt_to_print = (@display_prompt.include?("\n") && @prompt_last_line) ? @prompt_last_line : @display_prompt
3619
+ print prompt_to_print + cmd_check(@cmd)
3547
3620
  print "\n"; @c.clear_screen_down
3548
3621
  if @cmd == "r" # Integration with rtfm (https://github.com/isene/RTFM)
3549
3622
  t = Time.now
metadata CHANGED
@@ -1,20 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-shell
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.5.0
4
+ version: 3.6.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: 2025-10-30 00:00:00.000000000 Z
11
+ date: 2025-11-05 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.5.0: Nick/history export, 9 new tool completions (kubectl, terraform, aws, brew,
16
- etc.), validation templates, startup tips, improved :info, aggressive suggest_command
17
- optimization for huge PATHs (18K+ executables), timestamp shows corrected commands!'
15
+ v3.6.0: MULTI-LINE PROMPT SUPPORT - Complete readline refactor with rcurses-inspired
16
+ ANSI handling. Define prompts with newlines! Plus nick/history export, 9 new completions,
17
+ validation templates, startup tips, and critical performance fixes for huge PATHs!'
18
18
  email: g@isene.com
19
19
  executables:
20
20
  - rsh