SamovarGuidesCompletion

Completion

This guide explains how to add shell completion to commands built with samovar.

Samovar can complete command lines using the same grammar used for parsing. It can complete option flags, boolean flag variants, nested command names, option values, positional arguments, and split arguments.

Command Entry Point

Commands expose a completion entry point alongside the normal execution entry point:

Application.call(ARGV)     # Parse and execute the command.
Application.complete       # Print completion candidates.

complete expects the command-line arguments to be truncated to the cursor. The final argument is the token being completed. When completing after a space, pass an empty string as the final argument:

Application.complete(["serve", "--bind", ""])

Completion candidates are printed as tab-separated values:

type	value	description	key=value

The first three fields are always the completion type, value, and description. Additional fields are optional metadata entries encoded as key=value.

Static Completions

Option flags and nested command names are completed automatically. You can add static completions for option values and positional arguments with completions:.

require "samovar"

class Serve < Samovar::Command
	self.description = "Run the server."
	
	options do
		option "--format <name>", "The output format.", default: "text", completions: ["json", "text", "yaml"]
	end
end

class Application < Samovar::Command
	options do
		option "-h/--help", "Print help."
	end
	
	nested :command, {
		"serve" => Serve
	}, default: "serve"
end

Examples:

Application.complete(["ser"])
# command	serve	Run the server.

Application.complete(["serve", "--format", "j"])
# value	json

If an option has a default value, the default is offered before other value completions.

Dynamic Completions

Use a callable provider when completions depend on runtime state.

class Serve < Samovar::Command
	def self.host_completions(context)
		["localhost", "0.0.0.0"].select do |host|
			host.start_with?(context.current)
		end
	end
	
	options do
		option "--bind <host>", "The bind address.", completions: method(:host_completions)
	end
end

The provider receives a Samovar::Completion::Context with:

  • current: The token being completed.
  • arguments: The full truncated argument list.
  • environment: The environment hash passed to complete.
  • row: The parser row whose value is being completed. This can be an option or a positional argument.

Providers can return strings, hashes, or Samovar::Completion::Suggestion instances:

option "--mode <name>", "The mode.",
	completions: [
		{value: "development", description: "Local development", type: :value, suffix: " "},
		{value: "production", description: "Production", type: :value}
	]

Path Completion

For path-like arguments, let the shell do native path expansion by using one of the native completion providers:

class Process < Samovar::Command
	options do
		option "--output <path>", "The output path.", completions: :path
		option "--root <path>", "The root directory.", completions: :directory
	end
	
	one :input, "The input path.", completions: :file
end

Supported native providers:

  • :path: Complete files and directories using the shell.
  • :file: Alias for :path.
  • :directory: Complete directories using the shell.
  • :executable: Complete executable commands using the shell.

Samovar does not inspect the filesystem for these providers. It emits a typed completion request, and the shell adapter translates it to native shell path completion.

For split arguments, :executable completes the command immediately after the split marker. Once a command is present, Samovar emits a delegate completion with an index metadata field. The index is the zero-based argument index where delegated completion begins.

Dedicated Completion Executable

Shell adapters call a dedicated completion executable named completion-<command>. This avoids running the normal command during completion.

For a command named falcon, provide:

bin/falcon
bin/completion-falcon

The completion executable can be very small:

#!/usr/bin/env ruby
# frozen_string_literal: true

require_relative "../lib/my/application"

My::Application.complete

When the user completes a command by path, the shell adapter resolves the completion executable next to that command:

falcon          -> completion-falcon
bin/falcon      -> bin/completion-falcon
./bin/falcon    -> ./bin/completion-falcon
/path/falcon    -> /path/completion-falcon

Installing Shell Adapters

Shell adapter generation and installation is provided by the completion gem.

Generate an adapter script:

$ completion generate --shell zsh --command falcon

Install a generic adapter script into the default directory for the current shell:

$ completion install

The generic adapter checks whether a matching completion-<command> executable exists before handling a command. You can install an adapter for a specific command instead:

$ completion install --command falcon

You can specify the shell and directory explicitly:

$ completion install --shell fish --directory ~/.config/fish/completions --command falcon

The installed adapter calls the matching completion-<command> executable when completion is requested.

Testing Completion

You can test completion directly without involving a shell:

output = StringIO.new

Application.complete(["serve", "--format", "j"], output: output)

expect(output.string).to be == "value\tjson\t\n"

For a trailing-space completion, pass an empty final token:

Application.complete(["serve", "--format", ""], output: output)