ruby-shell 3.6.15 → 3.6.17

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 +59 -44
  3. metadata +4 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b48490df2e369dfa33c4d92ce4c713573d7b1e54ff565a53b62a217db070ca6
4
- data.tar.gz: d79027f5999c4765f99b900d7d4bc9edd7dfd90f195799815e6715577ed2bcd8
3
+ metadata.gz: 47a8896d9151658c431c1f2302f18b0d143887c9fe67d410a64d9c18bd143347
4
+ data.tar.gz: 230a1b7784618c69c860d169e36be458a09509d66052cd3350e1bb6e16620908
5
5
  SHA512:
6
- metadata.gz: 81b35c1a4f9b1e30b29381ad3c563b4da9efdf5d4fee1157409c87e7fb256f096fa855f352b4cb7faa1d0d9bd60c9755326f818991a52b8fe2f2eb3365c16409
7
- data.tar.gz: ee3bffbbd08096c6651dc8abf6984acb17f1d373d5d2558ea4354c56e1e8d1f6659205be9b339deb1b8e502d875975ade197153e48a787266a0082c074ded8e7
6
+ metadata.gz: 1c841a0dc5675dbd5bf1bb9341b0f9edeead426cec47357a2e1655dc1d67465732822a596d71468a5972f511c2a6f0f2f983ee19ed24317567de6b05affef665
7
+ data.tar.gz: 61c8fb056e30ad6eba62bca87be4d4d60da8817fb69acee4924d672fa32dcd8f050250a7accae2af6bb288de43907b06c95b38956980af53cdf205c437952f28
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.6.15" # Fix config persistence bugs in .rshrc
11
+ @version = "3.6.17" # Optimize core input loop
12
12
 
13
13
  # MODULES, CLASSES AND EXTENSIONS
14
14
  class String # Add coloring to strings (with escaping for Readline)
@@ -162,6 +162,7 @@ begin # Initialization
162
162
  @history_dedup = 'smart' # History dedup mode: 'off', 'full', 'smart'
163
163
  @auto_correct = false # Auto-correct typos (default: off)
164
164
  @slow_command_threshold = 0 # Threshold for slow command alerts (0 = disabled)
165
+ @config_dirty = [] # Track which config vars were changed via :config
165
166
  @plugin_dir = Dir.home + '/.rsh/plugins' # Plugins directory
166
167
  @plugins = [] # Loaded plugin instances
167
168
  @plugin_enabled = [] # List of enabled plugin names (whitelist)
@@ -387,38 +388,39 @@ def getstr # A custom Readline-like function
387
388
  lift = false
388
389
  right = false
389
390
 
390
- # Clear from cmd_row down (handles wrapped lines from previous iteration)
391
- @c.row(@cmd_row)
392
- @c.col(1)
393
- @c.clear_screen_down
394
-
395
- # Always reprint prompt + command text
396
- print @prompt_last_line
397
- print cmd_check(@history[0])
391
+ # Cache syntax highlighting (skip re-highlighting on cursor-only moves)
392
+ cmd_text = @history[0].to_s
393
+ if cmd_text != @_hl_text
394
+ @_hl_text = cmd_text.dup
395
+ @_hl_result = cmd_check(@history[0])
396
+ end
398
397
 
399
- # Print history suggestion if available
400
- @ci = @history[1..].find_index {|e| e =~ /^#{Regexp.escape(@history[0].to_s)}./}
398
+ # History suggestion lookup
399
+ @ci = @history[1..].find_index {|e| e =~ /^#{Regexp.escape(cmd_text)}./}
401
400
  unless @ci == nil
402
401
  @ci += 1
403
- @ciprompt = @history[@ci][@history[0].to_s.length..].to_s
404
- end
405
- if @history[0].to_s.length > 1 and @ci
406
- print @ciprompt.c(@c_stamp)
407
- right = true
402
+ @ciprompt = @history[@ci][cmd_text.length..].to_s
408
403
  end
404
+ right = cmd_text.length > 1 && @ci
409
405
 
410
- # Detect if terminal scrolled due to wrapping near the bottom
411
- end_row, _ = @c.pos
412
- printed_len = @pos0 + @history[0].to_s.length
413
- printed_len += @ciprompt.to_s.length if right # Include suggestion text
406
+ # Compute scroll adjustment before drawing (avoids terminal round-trip)
407
+ printed_len = @pos0 + cmd_text.length
408
+ printed_len += @ciprompt.to_s.length if right
414
409
  expected_rows = printed_len > 0 ? (printed_len - 1) / @maxcol : 0
415
- actual_start = end_row - expected_rows
416
- if actual_start < @cmd_row
417
- @cmd_row = [actual_start, 1].max # Terminal scrolled; adjust (min row 1)
410
+ if @cmd_row + expected_rows > @maxrow
411
+ @cmd_row = [@maxrow - expected_rows, 1].max
418
412
  end
419
413
 
414
+ # Redraw prompt + command
415
+ @c.row(@cmd_row)
416
+ @c.col(1)
417
+ @c.clear_screen_down
418
+ print @prompt_last_line
419
+ print @_hl_result
420
+ print @ciprompt.c(@c_stamp) if right
421
+
420
422
  # Position cursor with wrapping math
421
- total_pos = @pos0 + @pos # 0-indexed total position
423
+ total_pos = @pos0 + @pos
422
424
  cursor_row = @cmd_row + total_pos / @maxcol
423
425
  cursor_col = total_pos % @maxcol + 1
424
426
  @c.row(cursor_row)
@@ -429,7 +431,7 @@ def getstr # A custom Readline-like function
429
431
  when 'C-G' # Ctrl-G opens command in $EDITOR
430
432
  temp_file = "/tmp/rsh_edit_#{Process.pid}.tmp"
431
433
  File.write(temp_file, @history[0] || "")
432
- system("#{ENV['EDITOR'] || 'vi'} #{temp_file}")
434
+ system(ENV['EDITOR'] || 'vi', temp_file)
433
435
  if File.exist?(temp_file)
434
436
  edited = File.read(temp_file).strip
435
437
  # Convert multi-line to single line with proper separators
@@ -561,7 +563,7 @@ def getstr # A custom Readline-like function
561
563
  end
562
564
  lift = true
563
565
  when 'C-Y' # Copy command line to primary selection
564
- system("echo -n '#{@history[0]}' | xclip")
566
+ IO.popen('xclip', 'w') { |io| io.write(@history[0]) }
565
567
  puts "\n#{Time.now.strftime("%H:%M:%S")}: Copied to primary selection (paste with middle buttoni)".c(@c_stamp)
566
568
  when 'C-Z' # Suspend current process (background job)
567
569
  if @current_pid
@@ -968,7 +970,7 @@ def tab(type)
968
970
  begin
969
971
  count = Dir.entries(clean_item).length - 2
970
972
  print " [#{count}]".c(244)
971
- rescue
973
+ rescue Errno::EACCES, Errno::ENOENT
972
974
  end
973
975
  else
974
976
  size = File.size(clean_item)
@@ -1004,7 +1006,7 @@ def tab(type)
1004
1006
  begin
1005
1007
  count = Dir.entries(clean_item).length - 2
1006
1008
  print " [#{count}]".c(244)
1007
- rescue
1009
+ rescue Errno::EACCES, Errno::ENOENT
1008
1010
  end
1009
1011
  else
1010
1012
  size = File.size(clean_item)
@@ -1088,6 +1090,7 @@ def tabend
1088
1090
  @c.col(@c_col)
1089
1091
  end
1090
1092
  def get_command_switches(command) # Helper function to extract switches from --help
1093
+ require 'shellwords' unless defined?(Shellwords)
1091
1094
  # Check cache first (cache expires after 1 hour)
1092
1095
  cache_key = command.to_s.strip
1093
1096
  return [] if cache_key.empty?
@@ -1098,9 +1101,10 @@ def get_command_switches(command) # Helper function to extract switches from --h
1098
1101
  end
1099
1102
 
1100
1103
  # Parse --help output
1101
- hlp = `#{command} --help 2>/dev/null`
1104
+ escaped = Shellwords.escape(command)
1105
+ hlp = `#{escaped} --help 2>/dev/null`
1102
1106
  # Try -h if --help didn't work
1103
- hlp = `#{command} -h 2>/dev/null` if hlp.empty?
1107
+ hlp = `#{escaped} -h 2>/dev/null` if hlp.empty?
1104
1108
  return [] if hlp.empty?
1105
1109
 
1106
1110
  switches = []
@@ -1242,6 +1246,7 @@ def config(*args) # Configure rsh settings
1242
1246
  when 'history_dedup'
1243
1247
  if %w[off full smart].include?(value)
1244
1248
  @history_dedup = value
1249
+ @config_dirty << '@history_dedup'
1245
1250
  puts "History deduplication set to '#{value}'"
1246
1251
  rshrc
1247
1252
  else
@@ -1249,30 +1254,37 @@ def config(*args) # Configure rsh settings
1249
1254
  end
1250
1255
  when 'session_autosave'
1251
1256
  @session_autosave = value.to_i
1257
+ @config_dirty << '@session_autosave'
1252
1258
  puts "Session auto-save set to #{value}s #{value.to_i > 0 ? '(enabled)' : '(disabled)'}"
1253
1259
  rshrc
1254
1260
  when 'auto_correct'
1255
1261
  @auto_correct = %w[on true yes 1].include?(value.to_s.downcase)
1262
+ @config_dirty << '@auto_correct'
1256
1263
  puts "Auto-correct #{@auto_correct ? 'enabled' : 'disabled'}"
1257
1264
  rshrc
1258
1265
  when 'slow_command_threshold'
1259
1266
  @slow_command_threshold = value.to_i
1267
+ @config_dirty << '@slow_command_threshold'
1260
1268
  puts "Slow command threshold set to #{value}s #{value.to_i > 0 ? '(enabled)' : '(disabled)'}"
1261
1269
  rshrc
1262
1270
  when 'completion_learning'
1263
1271
  @completion_learning = %w[on true yes 1].include?(value.to_s.downcase)
1272
+ @config_dirty << '@completion_learning'
1264
1273
  puts "Completion learning #{@completion_learning ? 'enabled' : 'disabled'}"
1265
1274
  rshrc
1266
1275
  when 'completion_limit'
1267
1276
  @completion_limit = value.to_i
1277
+ @config_dirty << '@completion_limit'
1268
1278
  puts "Completion limit set to #{value}"
1269
1279
  rshrc
1270
1280
  when 'completion_show_metadata'
1271
1281
  @completion_show_metadata = %w[on true yes 1].include?(value.to_s.downcase)
1282
+ @config_dirty << '@completion_show_metadata'
1272
1283
  puts "Completion metadata display #{@completion_show_metadata ? 'enabled' : 'disabled'}"
1273
1284
  rshrc
1274
1285
  when 'show_tips'
1275
1286
  @show_tips = %w[on true yes 1].include?(value.to_s.downcase)
1287
+ @config_dirty << '@show_tips'
1276
1288
  puts "Startup tips #{@show_tips ? 'enabled' : 'disabled'}"
1277
1289
  rshrc
1278
1290
  else
@@ -1432,7 +1444,7 @@ def format_tab_item(item, show_metadata: false) # Format tab item with color and
1432
1444
  begin
1433
1445
  count = Dir.entries(clean_name).length - 2 # Exclude . and ..
1434
1446
  meta = "[dir, #{count} items]"
1435
- rescue
1447
+ rescue Errno::EACCES, Errno::ENOENT
1436
1448
  meta = "[dir]"
1437
1449
  end
1438
1450
  else
@@ -1511,7 +1523,7 @@ def rshrc # Write user configuration to .rshrc (portable between machines)
1511
1523
  begin
1512
1524
  file_nicks = eval($1)
1513
1525
  @nick = file_nicks.merge(@nick) if file_nicks.is_a?(Hash)
1514
- rescue
1526
+ rescue SyntaxError, StandardError
1515
1527
  # If eval fails, keep current @nick
1516
1528
  end
1517
1529
  end
@@ -1521,7 +1533,7 @@ def rshrc # Write user configuration to .rshrc (portable between machines)
1521
1533
  begin
1522
1534
  file_gnicks = eval($1)
1523
1535
  @gnick = file_gnicks.merge(@gnick) if file_gnicks.is_a?(Hash)
1524
- rescue
1536
+ rescue SyntaxError, StandardError
1525
1537
  # If eval fails, keep current @gnick
1526
1538
  end
1527
1539
  end
@@ -1535,21 +1547,23 @@ def rshrc # Write user configuration to .rshrc (portable between machines)
1535
1547
  conf.sub!(/^#{Regexp.escape(var)} =.*\n?/, "")
1536
1548
  end
1537
1549
 
1538
- # Persist user-editable configuration using helper
1550
+ # Persist runtime data (nicks, bookmarks, etc. change during normal use)
1539
1551
  conf = persist_var(conf, '@nick', @nick)
1540
1552
  conf = persist_var(conf, '@gnick', @gnick)
1541
1553
  conf = persist_var(conf, '@bookmarks', @bookmarks, !@bookmarks.empty?)
1542
1554
  conf = persist_var(conf, '@defuns', @defuns, !@defuns.empty?)
1543
- conf = persist_var(conf, '@history_dedup', @history_dedup)
1544
- conf = persist_var(conf, '@session_autosave', @session_autosave)
1545
- conf = persist_var(conf, '@auto_correct', @auto_correct)
1546
- conf = persist_var(conf, '@slow_command_threshold', @slow_command_threshold)
1547
- conf = persist_var(conf, '@completion_learning', @completion_learning)
1548
- conf = persist_var(conf, '@show_tips', @show_tips)
1549
- conf = persist_var(conf, '@completion_show_metadata', @completion_show_metadata)
1550
1555
  conf = persist_var(conf, '@plugin_disabled', @plugin_disabled, !@plugin_disabled.empty?)
1551
1556
  conf = persist_var(conf, '@validation_rules', @validation_rules, !@validation_rules.empty?)
1552
1557
 
1558
+ # Only persist config vars that were explicitly changed via :config in this session
1559
+ conf = persist_var(conf, '@history_dedup', @history_dedup, @config_dirty.include?('@history_dedup'))
1560
+ conf = persist_var(conf, '@session_autosave', @session_autosave, @config_dirty.include?('@session_autosave'))
1561
+ conf = persist_var(conf, '@auto_correct', @auto_correct, @config_dirty.include?('@auto_correct'))
1562
+ conf = persist_var(conf, '@slow_command_threshold', @slow_command_threshold, @config_dirty.include?('@slow_command_threshold'))
1563
+ conf = persist_var(conf, '@completion_learning', @completion_learning, @config_dirty.include?('@completion_learning'))
1564
+ conf = persist_var(conf, '@show_tips', @show_tips, @config_dirty.include?('@show_tips'))
1565
+ conf = persist_var(conf, '@completion_show_metadata', @completion_show_metadata, @config_dirty.include?('@completion_show_metadata'))
1566
+
1553
1567
  File.write(Dir.home+'/.rshrc', conf)
1554
1568
  rshstate # Also save runtime state
1555
1569
  end
@@ -3850,12 +3864,13 @@ loop do
3850
3864
  puts "#{Time.now.strftime("%H:%M:%S")}: #{@cmd}".c(@c_stamp)
3851
3865
  # Only auto-open files if it's a single filename (no spaces = no command with args)
3852
3866
  if File.read(@cmd).force_encoding("UTF-8").valid_encoding?
3853
- system("#{ENV['EDITOR']} #{@cmd}") # Try open with user's editor
3867
+ system(ENV['EDITOR'] || 'vi', @cmd) # Try open with user's editor
3854
3868
  else
3869
+ require 'shellwords' unless defined?(Shellwords)
3855
3870
  if @runmailcap
3856
- Thread.new { system("run-mailcap #{@cmd} 2>/dev/null") }
3871
+ Thread.new { system("run-mailcap #{Shellwords.escape(@cmd)} 2>/dev/null") }
3857
3872
  else
3858
- Thread.new { system("xdg-open #{@cmd} 2>/dev/null") }
3873
+ Thread.new { system("xdg-open #{Shellwords.escape(@cmd)} 2>/dev/null") }
3859
3874
  end
3860
3875
  end
3861
3876
  else
metadata CHANGED
@@ -1,19 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-shell
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.6.15
4
+ version: 3.6.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geir Isene
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-02-27 00:00:00.000000000 Z
11
+ date: 2026-03-23 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.6.15: Fix config persistence bugs - scalar settings now always persist, runtime
16
- data stripped from .rshrc.'
15
+ v3.6.17: Optimize core input loop with cached syntax highlighting and math-based
16
+ scroll detection.'
17
17
  email: g@isene.com
18
18
  executables:
19
19
  - rsh