Skip to content

Proposal to add common prefix completion for autocompletion mode#880

Open
amatsuda wants to merge 1 commit into
ruby:masterfrom
amatsuda:common_prefix_completion_with_first_tab
Open

Proposal to add common prefix completion for autocompletion mode#880
amatsuda wants to merge 1 commit into
ruby:masterfrom
amatsuda:common_prefix_completion_with_first_tab

Conversation

@amatsuda

@amatsuda amatsuda commented Jan 26, 2026

Copy link
Copy Markdown
Member

Let me propose to add the "common prefix completion" for the first tab hit on autocompletion mode.
The behavior is similar to the tab hit on non-autocompletion mode, and then it cycles through the candidates just as the current autocompletion mode behaves. I think this is how most of the modern shells e.g., bash, zsh, etc. behave by default.

As a realistic example, when having candidates

instance_of?
instance_variable_defined?
instance_variable_get
instance_variable_set
instance_variables

now we can get to "instance_variable_get" with the following three steps:

  1. User hits Tab: Complete to common prefix, "instance_" with menu displayed
  2. User types "v" + Tab: Complete to common prefix, "instance_variable"
  3. User types "_g" + Tab: Complete "instance_variable_get"

You can confirm this exact behavior with this one liner on this branch:
ruby -Ilib -rreline -e "Reline.autocompletion = true; Reline.completion_proc = proc { |s| %w[instance_of? instance_variable_defined? instance_variable_get instance_variable_set instance_variables].select { |i| i.start_with?(s) } }; puts Reline.readline('> ')"

Now the first tab press on autocompletion mode performs common prefix
completion, like the tab press on non-autocompletion mode.

For instance, when having candidates %w[ab1 ab2 ab3 c]:

1. User inputs "a"
2. First Tab: Complete to common prefix (e.g., "a" → "ab" with menu displayed)
3. Second Tab: Select first candidate (e.g., "ab" → "ab1")
4. Subsequent Tabs: Cycle through candidates + common prefix ("ab")
Comment thread lib/reline/line_editor.rb
else
# First tab: complete to common prefix
pre, target, post, quote = retrieve_completion_block
result = call_completion_proc(pre, target, post, quote)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use:

@completion_journey_state = retrieve_completion_journey_state

The computed result can be re-used if there is no common prefix, and fallback to move_completed_list(:down)

Comment thread lib/reline/line_editor.rb
result = call_completion_proc(pre, target, post, quote)
if result.is_a?(Array)
@completion_occurs = true
perform_completion(pre, target, post, quote, result)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perform_completion is for Readline-compatible tab completion, not for autocompletion.

irb(main):001> 1.a
1.abs       1.abs2      1.allbits?  1.angle     1.anybits?  1.arg

It may unintentionally set @completion_state to CompletionState::PERFECT_MATCH or CompletionState::MENU which should be only used in noautocomplete mode, especially when set show-all-if-ambiguous on is set in .inputrc.

irb(main):001> 1.a
1.abs       1.a1.abs     .allbits?  1.angle     1.anybits?  1.arg
               1.abs2    
               1.allbits?
               1.angle   
               1.anybits?
               1.arg     

Comment thread lib/reline/line_editor.rb
post_part = post.split("\n", -1).first || ''
@completion_journey_state = CompletionJourneyState.new(
@line_index, pre_part, completed, post_part, [completed] + candidates, 0
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about extract completion applying part from move_completed_list,
calculate common prefix, apply the completion common prefix with the extracted method if exist, and fallback to move_completed_list(:down)?

Comment thread lib/reline/line_editor.rb
if @completion_journey_state && @completion_journey_state.pointer > 0
# Already cycling (third+ tab): move to next candidate
@completion_occurs = move_completed_list(:down)
elsif @completion_journey_state && @completion_journey_state.pointer == 0 && @autocompletion_first_tab_completed

@tompng tompng Jul 25, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if pointer == 0, I think we want to apply common prefix completion only on the first TAB press.
Not on SHIFT-TAB (move to the end of completion candidate list) and then press TAB again.
I think @autocompletion_first_tab_completed can be renamed to started. if completion is started (move_completion_list is called), don't apply common prefix completion.
And this parameter can be moved to @completion_journey_state.started
Perhaps, common prefix can also be a pre-computed property of @completion_journey_state too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants