irb-autosuggestions 0.2.0 → 0.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48eb1b9edb63b8d65e26cb9917c5631ec58ccac72e05ab0c47efa7c2521e84c0
4
- data.tar.gz: 7369bbf6293a3ff1c99f733b5026d3d0d6a68305af4330af8ebe5feff8944041
3
+ metadata.gz: df8665e8ded51b93cb8c175133f17a82bdf369ffe8dfc191740d65958821ab9c
4
+ data.tar.gz: 8c37639ad88f22439a243b5f59e4794feb1edd744dcfaf0caa65c59e662eefb7
5
5
  SHA512:
6
- metadata.gz: d362044adf712df1f2f36a7305554c837263001d557fe6d999614f51cfd43901b336402326c3008d20c2191c89c60fb54618e79dd785626ceb0abd11eb7d43f4
7
- data.tar.gz: d7aee55ff2cd6ec1c49c0b4adeeb890010cda94ffe0b354acb7d63c50081bb6c74c79f8553f820564b592c2ca47cdf8239a492ac1f1a97754be60190ab641635
6
+ metadata.gz: eefd9feddd378c75bc6052223b9873460dcc4cf52dc07762eab5c7541bf9bd34d9efb37ed9ceedb74d4dc5acac7ee734cf547a52dd659021e3df00139d97da0c
7
+ data.tar.gz: 1ff38d6e5db43910299127bd891780b6e9a82c781af7d9febd7627eda9acfecae578fcbe1dc655fa25ffb006a602d1051c59fe42b4eeb666689e8ca8eea890da
data/CHANGELOG.md ADDED
@@ -0,0 +1,71 @@
1
+ # Changelog
2
+
3
+ ## [0.2.2] - 2026-07-08
4
+
5
+ ### Added
6
+
7
+ - Alternative key bindings to accept ghost suggestion: Tab, Ctrl+F, Ctrl+E (configurable via
8
+ `IRB.conf[:AUTOSUGGESTION_ACCEPT_KEYS]`) ([#15], [#157])
9
+ - Configurable ghost color/style via `IRB.conf[:AUTOSUGGESTION_GHOST_COLOR]` (ANSI string) and
10
+ `IRB.conf[:AUTOSUGGESTION_GHOST_STYLE]` (declarative hash) ([#16], [#163])
11
+ - GitHub issue templates (bug report, feature request) and pull request template
12
+
13
+ ### Fixed
14
+
15
+ - Ghost text artifacts and cursor misalignment after accepting multiline (10+ lines) suggestions ([#19], [#256])
16
+ - Auto-execute guard — verified Enter always fires exactly once regardless of ghost visibility ([#18], [#159])
17
+
18
+ ## [0.2.1] - 2026-07-05
19
+
20
+ ### Fixed
21
+
22
+ - Ruby 4.0 compatibility: arrow (`ed_next_char`) not erasing after suggestion accept ([#13])
23
+
24
+ ## [0.2.0] - 2026-06-30
25
+
26
+ ### Added
27
+
28
+ - Syntax-colored ghost text via `IRB::Color.colorize_code` (dimmed)
29
+ - Prefix-filtered history navigation (up/down arrows match typed prefix)
30
+ - Ruby 2.7 support
31
+
32
+ ### Changed
33
+
34
+ - Ghost text now uses dim syntax colors when `IRB.conf[:USE_COLORIZE]` is enabled
35
+
36
+ ## [0.1.1] - 2026-06-25
37
+
38
+ ### Added
39
+
40
+ - `IRB.conf[:USE_AUTOSUGGESTIONS]` toggle
41
+ - Environment variable `IRB_AUTOSUGGESTIONS` override
42
+ - RBS type signatures
43
+
44
+ ### Fixed
45
+
46
+ - Gem metadata (licenses, homepage)
47
+
48
+ ## [0.1.0] - 2026-06-20
49
+
50
+ ### Added
51
+
52
+ - Initial release: fish-like autosuggestions from `Reline::HISTORY`
53
+ - Right arrow key accepts ghost suggestion
54
+ - Gray ghost text (`\e[90m`)
55
+ - Configuration via `IRB.conf[:USE_AUTOSUGGESTIONS]`
56
+
57
+ [#15]: https://github.com/unurgunite/irb-autosuggestions/pull/15
58
+
59
+ [#16]: https://github.com/unurgunite/irb-autosuggestions/pull/16
60
+
61
+ [#18]: https://github.com/unurgunite/irb-autosuggestions/pull/18
62
+
63
+ [#19]: https://github.com/unurgunite/irb-autosuggestions/pull/19
64
+
65
+ [#157]: https://github.com/unurgunite/irb-autosuggestions/issues/157
66
+
67
+ [#163]: https://github.com/unurgunite/irb-autosuggestions/issues/163
68
+
69
+ [#159]: https://github.com/unurgunite/irb-autosuggestions/issues/159
70
+
71
+ [#256]: https://github.com/unurgunite/irb-autosuggestions/issues/256
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- irb-autosuggestions (0.2.0)
4
+ irb-autosuggestions (0.2.2)
5
5
  reline
6
6
 
7
7
  GEM
@@ -9,11 +9,11 @@ GEM
9
9
  specs:
10
10
  ast (2.4.3)
11
11
  diff-lcs (1.6.2)
12
- docscribe (1.3.3)
12
+ docscribe (1.5.1)
13
13
  parser (>= 3.3)
14
14
  prism (~> 1.8)
15
15
  io-console (0.8.2)
16
- json (2.19.5)
16
+ json (2.20.0)
17
17
  language_server-protocol (3.17.0.5)
18
18
  lint_roller (1.1.0)
19
19
  logger (1.7.0)
@@ -43,7 +43,7 @@ GEM
43
43
  diff-lcs (>= 1.2.0, < 2.0)
44
44
  rspec-support (~> 3.13.0)
45
45
  rspec-support (3.13.7)
46
- rubocop (1.86.2)
46
+ rubocop (1.88.1)
47
47
  json (~> 2.3)
48
48
  language_server-protocol (~> 3.17.0.2)
49
49
  lint_roller (~> 1.1.0)
@@ -60,10 +60,11 @@ GEM
60
60
  rubocop-rake (0.7.1)
61
61
  lint_roller (~> 1.1)
62
62
  rubocop (>= 1.72.1)
63
- rubocop-rspec (3.9.0)
63
+ rubocop-rspec (3.10.2)
64
64
  lint_roller (~> 1.1)
65
- rubocop (~> 1.81)
66
- rubocop-sorted_methods_by_call (1.2.2)
65
+ regexp_parser (>= 2.0)
66
+ rubocop (~> 1.86, >= 1.86.2)
67
+ rubocop-sorted_methods_by_call (1.2.3)
67
68
  lint_roller
68
69
  rubocop (>= 1.72.0)
69
70
  ruby-progressbar (1.13.0)
@@ -75,6 +76,7 @@ GEM
75
76
  PLATFORMS
76
77
  arm64-darwin-24
77
78
  arm64-darwin-25
79
+ ruby
78
80
  x86_64-linux
79
81
 
80
82
  DEPENDENCIES
@@ -90,4 +92,4 @@ DEPENDENCIES
90
92
  yard (>= 0.9.38)
91
93
 
92
94
  BUNDLED WITH
93
- 4.0.12
95
+ 2.4.22
data/Gemfile_2_7.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- irb-autosuggestions (0.2.0)
4
+ irb-autosuggestions (0.2.2)
5
5
  reline
6
6
 
7
7
  GEM
@@ -9,11 +9,11 @@ GEM
9
9
  specs:
10
10
  ast (2.4.3)
11
11
  diff-lcs (1.6.2)
12
- docscribe (1.3.3)
12
+ docscribe (1.5.1)
13
13
  parser (>= 3.3)
14
14
  prism (~> 1.8)
15
15
  io-console (0.8.2)
16
- json (2.19.5)
16
+ json (2.20.0)
17
17
  language_server-protocol (3.17.0.5)
18
18
  lint_roller (1.1.0)
19
19
  parallel (1.28.0)
@@ -40,7 +40,7 @@ GEM
40
40
  diff-lcs (>= 1.2.0, < 2.0)
41
41
  rspec-support (~> 3.13.0)
42
42
  rspec-support (3.13.7)
43
- rubocop (1.86.2)
43
+ rubocop (1.88.1)
44
44
  json (~> 2.3)
45
45
  language_server-protocol (~> 3.17.0.2)
46
46
  lint_roller (~> 1.1.0)
@@ -57,10 +57,11 @@ GEM
57
57
  rubocop-rake (0.7.1)
58
58
  lint_roller (~> 1.1)
59
59
  rubocop (>= 1.72.1)
60
- rubocop-rspec (3.9.0)
60
+ rubocop-rspec (3.10.2)
61
61
  lint_roller (~> 1.1)
62
- rubocop (~> 1.81)
63
- rubocop-sorted_methods_by_call (1.2.2)
62
+ regexp_parser (>= 2.0)
63
+ rubocop (~> 1.86, >= 1.86.2)
64
+ rubocop-sorted_methods_by_call (1.2.3)
64
65
  lint_roller
65
66
  rubocop (>= 1.72.0)
66
67
  ruby-progressbar (1.13.0)
@@ -70,8 +71,8 @@ GEM
70
71
  yard (0.9.44)
71
72
 
72
73
  PLATFORMS
73
- x86_64-linux
74
74
  arm64-darwin-25
75
+ x86_64-linux
75
76
 
76
77
  DEPENDENCIES
77
78
  docscribe
data/README.md CHANGED
@@ -18,7 +18,9 @@ No need to explain. Fish-like autosuggestions for IRB — ghost text from histor
18
18
  * [Usage](#usage)
19
19
  * [Prefix-filtered history navigation](#prefix-filtered-history-navigation)
20
20
  * [Configuration](#configuration)
21
+ * [Ghost color](#ghost-color)
21
22
  * [Colors](#colors)
23
+ * [Accept keys](#accept-keys)
22
24
  * [How it works](#how-it-works)
23
25
  * [Development](#development)
24
26
  * [License](#license)
@@ -48,7 +50,8 @@ irb(main):002* el.succ <- "cc" in gray
48
50
  irb(main):003> end <- "d" in gray
49
51
  ```
50
52
 
51
- Press **right arrow** (`->`) to accept the full multiline suggestion.
53
+ Press **right arrow** (`->`), **Tab**, **Ctrl+F**, or **Ctrl+E** to accept the full multiline suggestion. Tab falls
54
+ back to normal completion when no suggestion is visible. Accept keys are configurable (see below).
52
55
 
53
56
  ### Prefix-filtered history navigation
54
57
 
@@ -96,6 +99,29 @@ Or via environment variable:
96
99
  export IRB_AUTOSUGGESTIONS=0
97
100
  ```
98
101
 
102
+ ### Ghost color
103
+
104
+ The plain gray fallback color can be customized. Syntax-colored ghost text (when `IRB::Color` is available) always
105
+ takes precedence over these settings.
106
+
107
+ Set a raw ANSI escape code:
108
+
109
+ ```ruby
110
+ IRB.conf[:AUTOSUGGESTION_GHOST_COLOR] = "\e[32m" # green
111
+ ```
112
+
113
+ Or use the declarative style hash:
114
+
115
+ ```ruby
116
+ IRB.conf[:AUTOSUGGESTION_GHOST_STYLE] = { fg: :bright_black, italic: true }
117
+ ```
118
+
119
+ Supported colors: `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`, `default`,
120
+ `bright_black`, `bright_red`, `bright_green`, `bright_yellow`, `bright_blue`, `bright_magenta`,
121
+ `bright_cyan`, `bright_white`.
122
+
123
+ Supported attributes: `bold`, `dim`, `italic`, `underline`, `blink`, `reverse`.
124
+
99
125
  ### Colors
100
126
 
101
127
  Syntax-colored ghost text is **enabled by default** when `IRB::Color` is available and `IRB.conf[:USE_COLORIZE]` is
@@ -113,10 +139,24 @@ Or from command line:
113
139
  irb --nocolorize
114
140
  ```
115
141
 
116
- > [!NOTE] Colorized ghost rendering may behave differently across terminal emulators, Ruby versions, and IRB color
142
+ > [!NOTE]
143
+ > Colorized ghost rendering may behave differently across terminal emulators, Ruby versions, and IRB color
117
144
  > schemes. If you notice visual artifacts (e.g., wrong colors, underlines, or unusual brightness), try disabling the
118
145
  > feature or switch to the gray fallback or create new issue.
119
146
 
147
+ ### Accept keys
148
+
149
+ Configure which keys accept the ghost suggestion:
150
+
151
+ ```ruby
152
+ IRB.conf[:AUTOSUGGESTION_ACCEPT_KEYS] = %i[ed_next_char tab ed_end_of_line]
153
+ ```
154
+
155
+ Defaults: `[:ed_next_char, :ed_end_of_line, :tab]` (right arrow/Ctrl+F, Ctrl+E, Tab).
156
+
157
+ Available symbols: `:ed_next_char` (right arrow/Ctrl+F), `:ed_end_of_line` (Ctrl+E), `:tab`, or
158
+ any Reline method symbol. When no suggestion is visible, the key falls through to its normal behavior.
159
+
120
160
  ### How it works
121
161
 
122
162
  - Each keystroke queries `Reline::HISTORY` for the most recent entry whose prefix matches the current buffer.
@@ -126,7 +166,7 @@ irb --nocolorize
126
166
  - Extra ghost lines (for multiline history entries) are drawn below the prompt.
127
167
  - Ghost artifacts are cleared each frame using cursor save/restore (`\e[s`/`\e[u`) and per-line clearing (`\e[2K`),
128
168
  avoiding `\e[J` which interfered with Reline's cursor tracking.
129
- - Right arrow triggers `ed_next_char`, which replaces the buffer with the ghost text.
169
+ - Configurable accept keys (right arrow, Tab, Ctrl+F, Ctrl+E by default) replace the buffer with the ghost text.
130
170
  - Up/down arrows use prefix-filtered navigation when the buffer is non-empty, falling back to unfiltered browsing
131
171
  when nothing was typed.
132
172
 
@@ -14,8 +14,22 @@ module Irb
14
14
  ENV_KEY = 'IRB_AUTOSUGGESTIONS'
15
15
  CONFIG_NAV_KEY = :USE_PREFIX_HISTORY_NAVIGATION
16
16
  ENV_NAV_KEY = 'IRB_PREFIX_HISTORY_NAVIGATION'
17
-
18
- # Intercepts key input to accept autosuggestions on right arrow
17
+ ACCEPT_KEYS_CONFIG = :AUTOSUGGESTION_ACCEPT_KEYS
18
+ DEFAULT_ACCEPT_KEYS = %i[ed_next_char ed_end_of_line tab].freeze
19
+ GHOST_COLOR_CONFIG = :AUTOSUGGESTION_GHOST_COLOR
20
+ GHOST_STYLE_CONFIG = :AUTOSUGGESTION_GHOST_STYLE
21
+ DEFAULT_GHOST_COLOR = "\e[90m"
22
+ FG_MAP = {
23
+ black: 30, red: 31, green: 32, yellow: 33, blue: 34, magenta: 35, cyan: 36, white: 37,
24
+ default: 39,
25
+ bright_black: 90, bright_red: 91, bright_green: 92, bright_yellow: 93,
26
+ bright_blue: 94, bright_magenta: 95, bright_cyan: 96, bright_white: 97
27
+ }.freeze
28
+ ATTR_MAP = {
29
+ bold: 1, dim: 2, italic: 3, underline: 4, blink: 5, reverse: 7
30
+ }.freeze
31
+
32
+ # Intercepts key input to accept autosuggestions on configured keys
19
33
  # and clears the prefix navigation anchor on non-history keys.
20
34
  #
21
35
  # @param [Object] key A Reline key event.
@@ -23,7 +37,7 @@ module Irb
23
37
  def input_key(key)
24
38
  clear_prefix_anchor if navigation_enabled? && !history_navigation_key?(key)
25
39
 
26
- if enabled? && right_arrow?(key)
40
+ if enabled? && accept_key?(key)
27
41
  buffer = whole_buffer
28
42
  suggestion = find_suggestion(buffer)
29
43
 
@@ -36,23 +50,27 @@ module Irb
36
50
  super
37
51
  end
38
52
 
39
- private
40
-
41
53
  # Injects ghost text into terminal output after Reline finishes rendering.
42
- # Clears any previous ghost text (inline and multi-line) first,
43
- # then renders the new ghost suggestion.
54
+ # Must be public because Reline::Core calls +rerender+ externally.
55
+ # Works on both Reline 0.3.x (rerender is the main rendering entry) and
56
+ # Reline 0.4+ (rerender calls render internally).
57
+ #
58
+ # Ghost is cleared BEFORE super to avoid cursor-position-dependent
59
+ # escapes targeting wrong lines after accept_suggestion changes buffer.
44
60
  #
45
- # @private
46
61
  # @return [Object] The result of +super+.
47
- def render(...)
62
+ def rerender
63
+ clear_previous_ghost if enabled?
64
+
48
65
  result = super
49
- if enabled?
50
- clear_previous_ghost
51
- render_ghost_suggestion
52
- end
66
+
67
+ render_ghost_suggestion if enabled?
68
+
53
69
  result
54
70
  end
55
71
 
72
+ private
73
+
56
74
  # Prefix-filtered up-arrow history navigation.
57
75
  #
58
76
  # @private
@@ -109,8 +127,8 @@ module Irb
109
127
  end
110
128
 
111
129
  # Clears ghost from the previous frame: inline text from buffer end
112
- # to end of line, and each extra line below. Uses cursor save/restore
113
- # so it does not interfere with Reline's cursor tracking.
130
+ # to end of line, and each extra line below. Restores cursor column
131
+ # afterwards so Reline's cursor tracking is not disturbed.
114
132
  #
115
133
  # @private
116
134
  # @return [void]
@@ -118,10 +136,11 @@ module Irb
118
136
  clear_inline_ghost
119
137
  return unless @ghost_line_count&.positive?
120
138
 
139
+ origin_column = cursor_column_at_byte_pointer
121
140
  output = Reline.core.instance_variable_get(:@output)
122
- output.write("\e[s")
123
141
  @ghost_line_count.times { output.write("\e[1B\e[2K") }
124
- output.write("\e[u")
142
+ output.write("\e[#{@ghost_line_count}A")
143
+ output.write("\e[0G\e[#{origin_column}C")
125
144
  end
126
145
 
127
146
  # Clears inline ghost text from the buffer end to end of line.
@@ -132,12 +151,10 @@ module Irb
132
151
  return unless @has_inline_ghost
133
152
 
134
153
  output = Reline.core.instance_variable_get(:@output)
135
- prompt_width = @prompt ? Reline::Unicode.calculate_width(@prompt) : 0
136
- current_line = @buffer_of_lines[@line_index] || ''
137
- buf_end = prompt_width + Reline::Unicode.calculate_width(current_line)
138
- output.write("\e[s")
139
- output.write("\e[0G\e[#{buf_end}C\e[K")
140
- output.write("\e[u")
154
+ buf_end = move_to_buffer_end
155
+ origin_column = cursor_column_at_byte_pointer
156
+ output.write("#{buf_end}\e[K")
157
+ output.write("\e[0G\e[#{origin_column}C")
141
158
  @has_inline_ghost = false
142
159
  end
143
160
 
@@ -184,6 +201,21 @@ module Irb
184
201
  remove_instance_variable(:@prefix_buffer)
185
202
  end
186
203
 
204
+ # Whether prefix-filtered history navigation is enabled.
205
+ # Falls back to the value of +enabled?+ when not explicitly set.
206
+ #
207
+ # @private
208
+ # @return [Boolean]
209
+ def navigation_enabled?
210
+ case ENV.fetch(ENV_NAV_KEY, nil)
211
+ when '0' then false
212
+ when '1' then true
213
+ else
214
+ val = IRB.conf[CONFIG_NAV_KEY]
215
+ val.nil? ? enabled? : val
216
+ end
217
+ end
218
+
187
219
  # Checks whether autosuggestions are enabled via IRB.conf or env var.
188
220
  #
189
221
  # @private
@@ -198,29 +230,34 @@ module Irb
198
230
  end
199
231
  end
200
232
 
201
- # Whether prefix-filtered history navigation is enabled.
202
- # Falls back to the value of +enabled?+ when not explicitly set.
233
+ # Checks if a key event matches any configured accept key.
234
+ # Defaults to right arrow (:ed_next_char).
235
+ # Tab is matched specially — method_symbol == :ed_insert + char == "\t".
203
236
  #
204
237
  # @private
238
+ # @param [Object] key A Reline key event.
205
239
  # @return [Boolean]
206
- def navigation_enabled?
207
- case ENV.fetch(ENV_NAV_KEY, nil)
208
- when '0' then false
209
- when '1' then true
210
- else
211
- val = IRB.conf[CONFIG_NAV_KEY]
212
- val.nil? ? enabled? : val
213
- end
240
+ def accept_key?(key)
241
+ accept_keys = IRB.conf.fetch(ACCEPT_KEYS_CONFIG, DEFAULT_ACCEPT_KEYS)
242
+ accept_keys.any? { |k| key_match?(key, k) }
214
243
  end
215
244
 
216
- # Checks if a key event is a right arrow press.
245
+ # Checks if a key matches a given config symbol.
217
246
  #
218
247
  # @private
219
248
  # @param [Object] key A Reline key event.
249
+ # @param [Symbol] config_symbol One of :ed_next_char, :ed_end_of_line, :tab, etc.
220
250
  # @return [Boolean]
221
- def right_arrow?(key)
222
- key.respond_to?(:method_symbol) &&
223
- key.method_symbol == :ed_next_char
251
+ def key_match?(key, config_symbol)
252
+ return false unless key.respond_to?(:method_symbol)
253
+
254
+ if config_symbol == :tab
255
+ key.method_symbol == :ed_insert &&
256
+ key.respond_to?(:char) &&
257
+ key.char == "\t"
258
+ else
259
+ key.method_symbol == config_symbol
260
+ end
224
261
  end
225
262
 
226
263
  # Finds the index of the previous (older) history entry starting with +buffer+.
@@ -325,6 +362,8 @@ module Irb
325
362
  end
326
363
 
327
364
  # Writes the ghost text (inline + extra lines) to terminal output.
365
+ # Saves and restores cursor column so Reline's cursor tracking
366
+ # (used for left/right arrow positioning) is not disturbed.
328
367
  #
329
368
  # If +suggestion+ is provided and colorization is enabled, the ghost
330
369
  # is rendered with syntax highlighting via IRB::Color.
@@ -333,16 +372,19 @@ module Irb
333
372
  # @param [String] ghost The ghost text (suffix of the suggestion).
334
373
  # @param [String, nil] suggestion The full matching history entry.
335
374
  # @return [void]
336
- def render_ghost(ghost, suggestion = nil)
375
+ def render_ghost(ghost, suggestion = nil) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
337
376
  output = Reline.core.instance_variable_get(:@output)
338
377
  display_lines = ghost_display_lines(ghost, suggestion)
339
378
  @ghost_line_count = display_lines.size - 1
340
379
  @has_inline_ghost = true
341
380
 
381
+ output.write(move_to_buffer_end)
342
382
  first_line = display_lines.first
343
383
  output.write(first_line) if first_line && !first_line.empty?
344
384
  write_extra_ghost_lines(display_lines.drop(1))
345
- restore_cursor_after(display_lines)
385
+ origin_column = cursor_column_at_byte_pointer
386
+ output.write("\e[#{@ghost_line_count}A") if @ghost_line_count.positive?
387
+ output.write("\e[0G\e[#{origin_column}C")
346
388
  output.flush
347
389
  end
348
390
 
@@ -350,7 +392,6 @@ module Irb
350
392
  #
351
393
  # When colorization is enabled, the full suggestion is colorized via
352
394
  # IRB::Color and the ghost portion is extracted from the colored output.
353
- # Otherwise, each line is wrapped in GRAY/RESET.
354
395
  #
355
396
  # @private
356
397
  # @param [String] ghost The ghost text (suffix of the suggestion).
@@ -361,10 +402,35 @@ module Irb
361
402
  if suggestion && use_colorize?
362
403
  colorize_ghost_lines(ghost, suggestion)
363
404
  else
364
- ghost.split("\n").map { |line| "#{GRAY}#{line}#{RESET}" }
405
+ ghost.split("\n").map { |line| "#{ghost_color}#{line}#{RESET}" }
365
406
  end
366
407
  rescue StandardError
367
- ghost.split("\n").map { |line| "#{GRAY}#{line}#{RESET}" }
408
+ ghost.split("\n").map { |line| "#{ghost_color}#{line}#{RESET}" }
409
+ end
410
+
411
+ # Returns the effective ANSI color code for ghost text.
412
+ # Checks GHOST_STYLE_CONFIG first, then GHOST_COLOR_CONFIG, then default.
413
+ #
414
+ # @private
415
+ # @return [String]
416
+ def ghost_color
417
+ style = IRB.conf[GHOST_STYLE_CONFIG]
418
+ return resolve_ghost_style(style) if style.is_a?(Hash)
419
+
420
+ IRB.conf.fetch(GHOST_COLOR_CONFIG, DEFAULT_GHOST_COLOR)
421
+ end
422
+
423
+ # Converts a style hash like +{ fg: :bright_black, italic: true }+
424
+ # to an ANSI escape sequence.
425
+ #
426
+ # @private
427
+ # @param [Hash] hash Style hash with +:fg+ color and optional attribute keys.
428
+ # @return [String]
429
+ def resolve_ghost_style(hash)
430
+ codes = []
431
+ codes << FG_MAP[hash[:fg]] if hash[:fg]
432
+ ATTR_MAP.each { |attr, code| codes << code if hash[attr] }
433
+ "\e[#{codes.join(';')}m"
368
434
  end
369
435
 
370
436
  # Checks whether syntax coloring is available and enabled.
@@ -456,20 +522,26 @@ module Irb
456
522
  end
457
523
  end
458
524
 
459
- # Restores the cursor to the end of the buffer after ghost rendering.
525
+ # Returns the terminal column where the cursor should sit (after the
526
+ # prompt and currently typed text at +@byte_pointer+).
460
527
  #
461
528
  # @private
462
- # @param [Array<String>] lines The ghost split into lines.
463
- # @return [void]
464
- def restore_cursor_after(lines)
465
- extra_count = lines.size - 1
529
+ # @return [Integer]
530
+ def cursor_column_at_byte_pointer
466
531
  prompt_width = @prompt ? Reline::Unicode.calculate_width(@prompt) : 0
467
- pos = prompt_width + (@buffer_of_lines[@line_index] || '').length
468
- output = Reline.core.instance_variable_get(:@output)
532
+ current_line = @buffer_of_lines[@line_index] || ''
533
+ prompt_width + Reline::Unicode.calculate_width(current_line[0...@byte_pointer])
534
+ end
469
535
 
470
- output.write("\e[#{extra_count}A") if extra_count.positive?
471
- output.write("\e[0G")
472
- output.write("\e[#{pos}C")
536
+ # Moves cursor to the end of the buffer line (for writing inline ghost).
537
+ #
538
+ # @private
539
+ # @return [String]
540
+ def move_to_buffer_end
541
+ prompt_width = @prompt ? Reline::Unicode.calculate_width(@prompt) : 0
542
+ current_line = @buffer_of_lines[@line_index] || ''
543
+ buf_end = prompt_width + Reline::Unicode.calculate_width(current_line)
544
+ "\e[0G\e[#{buf_end}C"
473
545
  end
474
546
  end
475
547
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Irb
4
4
  module Autosuggestions
5
- VERSION = '0.2.0'
5
+ VERSION = '0.2.2'
6
6
  end
7
7
  end
data/readme.png CHANGED
Binary file
@@ -10,24 +10,39 @@ module Irb
10
10
  CONFIG_KEY: Symbol
11
11
  ENV_KEY: String
12
12
  RESET_COLOR: String
13
+ ACCEPT_KEYS_CONFIG: Symbol
14
+ DEFAULT_ACCEPT_KEYS: Array[Symbol]
15
+ GHOST_COLOR_CONFIG: Symbol
16
+ GHOST_STYLE_CONFIG: Symbol
17
+ DEFAULT_GHOST_COLOR: String
18
+ FG_MAP: Hash[Symbol, Integer]
19
+ ATTR_MAP: Hash[Symbol, Integer]
13
20
 
14
21
  @buffer_of_lines: Array[String]
15
22
  @byte_pointer: Integer
16
23
  @line_index: Integer
17
24
 
18
25
  def input_key: (untyped key) -> untyped
26
+ def rerender: () -> untyped
19
27
  def render: (*untyped) -> untyped
20
28
  def enabled?: () -> bool
29
+ def navigation_enabled?: () -> bool
21
30
  def use_colorize?: () -> bool
22
- def right_arrow?: (untyped key) -> bool
31
+ def accept_key?: (untyped key) -> bool
32
+ def key_match?: (untyped key, Symbol config_symbol) -> bool
33
+ def ghost_color: () -> String
34
+ def resolve_ghost_style: (Hash[Symbol, untyped] hash) -> String
35
+ def render_ghost_suggestion: () -> void
23
36
  def render_ghost: (String ghost, ?String suggestion) -> void
24
37
  def ghost_display_lines: (String ghost, ?String suggestion) -> Array[String]
25
38
  def colorize_ghost_lines: (String ghost, String suggestion) -> Array[String]
39
+ def dim_line: (String line) -> String
26
40
  def extract_ansi_colored_suffix: (String colored_text, Integer visible_byte_offset) -> String
27
41
  def write_extra_ghost_lines: (Array[String] lines) -> void
28
- def restore_cursor_after: (Array[String] lines) -> void
29
42
  def find_suggestion: (String buffer) -> (String | nil)
30
43
  def accept_suggestion: (String suggestion) -> void
44
+ def find_prev_match: (String buffer, Integer? from_pointer) -> Integer?
45
+ def find_next_match: (String buffer, Integer? from_pointer) -> Integer?
31
46
  end
32
47
  end
33
48
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: irb-autosuggestions
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - unurgunite
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-27 00:00:00.000000000 Z
11
+ date: 2026-07-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: reline
@@ -145,6 +145,7 @@ files:
145
145
  - ".rspec"
146
146
  - ".rubocop.yml"
147
147
  - ".ruby-version"
148
+ - CHANGELOG.md
148
149
  - CODE_OF_CONDUCT.md
149
150
  - Gemfile
150
151
  - Gemfile.lock