Skip to content

Add smart tab-completion for scriptOrFile argument#2466

Draft
maxandersen wants to merge 23 commits into
mainfrom
feature/scriptref-completion
Draft

Add smart tab-completion for scriptOrFile argument#2466
maxandersen wants to merge 23 commits into
mainfrom
feature/scriptref-completion

Conversation

@maxandersen
Copy link
Copy Markdown
Collaborator

@maxandersen maxandersen commented May 17, 2026

Summary

Adds rich tab-completion for the scriptOrFile positional argument used by run, build, edit, alias add, and info commands. Completes from multiple sources depending on what the user is typing.

Completion Sources

1. Local files

Filters to JBang-supported extensions (.java, .jsh, .kt, .groovy, .md) plus directories. Hidden files excluded. Case-insensitive. Supports subdirectory navigation (src/A<TAB>).

2. Aliases

Completes alias names from the merged catalog (nearest + imported + implicit).

3. Catalog browsing (@catalog)

  • @<TAB> → lists all available catalog names
  • @jbanghub<TAB> → shows sub-catalogs (@jbanghub/h2, ...)
  • @jbanghub/h2<TAB> → resolves to h2@jbanghub/h2

4. Maven GAV (from local ~/.m2/repository)

  • com.google.<TAB> → groupId prefixes and available artifacts
  • com.google.guava:<TAB> → artifactIds
  • com.google.guava:guava:<TAB> → cached versions

Distinguishes GAV from filenames using a heuristic: 2+ dots without path separators triggers GAV mode.

5. GitHub URL navigation

  • github.com/owner/repo<TAB> → lists files at repo root (defaults to HEAD)
  • https://github.com/owner/repo/blob/main/src/<TAB> → subdirectory listing
  • Bare github.com/ (no https://) supported, candidates normalized to full URLs
  • In-memory LRU cache (50 entries, 5min TTL), 2s connect / 3s read timeout
  • Uses GITHUB_TOKEN / GH_TOKEN for auth if available

Bug Fixes

Fish completion script fix

The aesh-generated fish completion script uses commandline -cop which excludes the current token being typed. Applied a post-generation fix using commandline -ct. Upstream: aeshell/aesh#436

Known Limitation

Completion works on commands with @Argument only (build, info tools). Commands with both @Argument + @Arguments (run, edit, alias add) are blocked by an aesh bug where the @Argument completer is silently skipped. Upstream: aeshell/aesh#435

Demo

jbang-completions

(GIF shows all 5 completion types in action)

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 17, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a089d2d2-7a2d-4d43-b8c7-1af1da09ce36

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/scriptref-completion

Comment @coderabbitai help to get the list of available commands and usage tips.

stalep and others added 23 commits May 19, 2026 13:37
Migrate jbang's CLI framework from picocli to aesh, leveraging aesh's
compile-time annotation processor to eliminate runtime reflection and
improve startup performance.

Key changes:
- Replace all picocli annotations with aesh equivalents
- Convert Callable<Integer> commands to Command<CommandInvocation>
- Port custom type converters and parameter consumers to aesh
- Implement external plugin discovery, default value provider,
  deprecated flag detection, and grouped help sections
- Add usage examples header and branding footer via HelpSectionProvider
- Use enum types (Format, Source.Type) with aesh's native validation
- Add arity and paramLabel to @Argument/@arguments
- Rewrite docs generator (genadoc.java) for aesh command model
- Regenerate native-image configuration

Performance (vs picocli, median):
- Native image: 6ms vs 33ms (5.5x faster) for single commands
- JDK 25: 109ms vs 228ms (2.1x faster)
- JDK 11: 149ms vs 269ms (1.8x faster)
Replace reflection-based command tree walking with aesh's
CommandRegistry/ProcessedCommand API in three places:

- Config.ConfigList.gatherKeys: used getDeclaredFields() and
  @Option/@mixin annotations to discover configurable option names
- JBangDefaultValueProvider.buildPaths: used @GroupCommandDefinition
  and @CommandDefinition annotations to map classes to dotted paths
- BaseCommand.missingSubcommand: used @GroupCommandDefinition to
  list subcommands; now uses commandInvocation.getHelpInfo()

This eliminates all runtime reflection on command classes, fixing
native image errors (MissingReflectionRegistrationError for mixin
classes) without needing reachability-metadata.json entries.
- Fix Info.ScriptInfo.init(Project) where repositories was assigned
  twice: once inside the null-source branch and unconditionally after,
  making the first assignment dead code
- Fix RunMixin.opts() debug serialization to emit a single
  --debug=key=val,key=val entry instead of multiple -d flags that
  cannot round-trip through DebugOptionParser
- Fix Alias.AliasAdd.docs to use @OptionList instead of @option for
  List<String>, enabling correct multi-value accumulation in aesh
- Fix Info.ClassPath.doCall() NPE when resolvedDependencies is null
  due to BuildContext initialization failure
- Add catch-all for unchecked exceptions in BaseCommand.execute() to
  prevent aesh from converting arbitrary exceptions to exit code 4
- Add StrictOptionParser to Info.BaseInfoCommand.module for consistent
  --module=value syntax with Build/Run (was lost in picocli migration)
- Add helpGroup "Essentials" to Info @GroupCommandDefinition so it
  appears in the correct help group like other commands
- Remove unused getCommandPath(String) from JBangDefaultValueProvider
- Extract shared prefix-resolution logic from DebugOptionParser and
  StrictOptionParser into StrictOptionParser.fullPrefix()
Add a test that verifies the hardcoded subcommand name list in
Main.getSubcommandNames() stays in sync with the groupCommands
array in JBang's @GroupCommandDefinition annotation. This prevents
silent breakage of implicit-run detection when commands are added
or removed.

Make Main.getSubcommandNames() public so the test can access it
from the dev.jbang.cli test package.
- Replace System.exit() with ExitException in Main.handleDefaultRun()
  plugin dispatch path, preventing JVM termination when called from
  tests via JBang.execute() or JBang.parseCommand()
- Add verbose logging in BaseTest.checkedRun() when falling back from
  parseCommand to JBang.execute() on CommandLineParserException, to
  aid debugging when the fallback masks test misconfigurations
- Move realOut PrintStream from BaseCommand to Run where it is the
  only consumer, avoiding ~18 unnecessary PrintStream allocations per
  parseCommand call
Update genadoc.java to render option aliases (e.g. --ea for
--enableassertions, --class-path for --cp) and negatable options
(e.g. --[no-]cds, --[no-]integrations) matching the picocli doc
format.

Regenerate all CLI reference pages to reflect:
- Option aliases now visible in documentation
- Negatable options shown with --[no-] prefix
- AI option descriptions synced with AIOptions.java source
- Subcommand ordering updated from aesh parser traversal
- Rename Cache.CacheClear fields to match option names (e.g. urls->url,
  jars->jar, groovys->groovyc) for consistency as suggested by Max
- Add comment explaining FALLBACK_OPTIONS in JBangDefaultValueProvider
  to clarify why debug and jfr are excluded from default value lookup
- Move null-element filtering inside the args!=null guard in
  Main.handleDefaultRun() to avoid unnecessary work on null input
- Catch IllegalArgumentException in BaseCommand.execute() and return
  EXIT_INVALID_INPUT (2) instead of EXIT_INTERNAL_ERROR (4) for
  validation errors thrown from doCall() (e.g. invalid JDK path)
- Update genadoc.java to use paramLabel from @Argument/@arguments
  annotations, rendering semantic names like scriptOrFile, userParams,
  catalogName instead of generic 'arg' placeholders
- Regenerate all CLI docs with improved argument labels
Update genadoc.java to skip the =_<value>_ suffix for boolean-typed
options and the auto-generated --help option, so docs show '-h, --help'
instead of '-h, --help=_<help>_'.

Addresses aesh#444 (--help should be a boolean flag). The aesh-side
fix changes doGenerateHelp() to use OptionType.BOOLEAN, and this
genadoc change ensures the doc generator also handles it correctly
regardless of the aesh jar version.
Leverage aesh#445 to set DefaultValueProvider at the runtime/builder
level instead of repeating it on every @CommandDefinition. The provider
is now set once on AeshRuntimeRunner (Main.main, JBang.execute) and
AeshCommandRuntimeBuilder (JBang.parseCommand), removing the
annotation from all 60+ command definitions.

Use aesh#446 fallbackValue on --module to replace StrictOptionParser
for options where bare usage (--module) means 'enable with defaults'
and =value syntax (--module=name) provides an explicit value.
With the aesh#447 fix for fallbackValue token consumption, replace
all remaining StrictOptionParser usages with fallbackValue="":
- --jfr in RunMixin
- --code in Run
- --open in Edit

Delete StrictOptionParser.java entirely. Move fullPrefix() helper
into DebugOptionParser which is the only remaining custom parser
(needed for --debug's peek-ahead pattern matching).
Replace hand-rolled ANSI escape codes with constants from
org.aesh.terminal.utils.ANSI (YELLOW_TEXT, CYAN_TEXT, MAGENTA_TEXT,
BOLD, RESET) which is already on the classpath via terminal-api.

ANSI.FAINT is not yet available in terminal-api 3.7, so faint()
uses ANSI.START + "2m" until aesh-readline 3.8 is released.
Uses aesh's HelpSectionProvider to add an "Examples" section to the
root jbang help output, showing common usage patterns (init, edit,
run, alias, GAV).
Move usage examples from additional section to header (shown before
synopsis) and add Commonhaus Foundation branding footer, matching
the picocli help layout. Uses aesh 3.8-dev HelpSectionProvider
getHeader()/getFooter().
Introduce ScriptRefCompleter that provides shell completion candidates
for the scriptOrFile positional argument used by run, build, edit,
alias add, and info commands.

Phase 1 completes:
- Local files filtered to JBang-supported extensions (.java, .jsh, .kt,
  .groovy, .md) plus directories (with trailing slash)
- Alias names from the merged catalog (nearest + imported + implicit)

Hidden files are excluded. Completion is case-insensitive for file names
and supports subdirectory navigation (e.g. src/A<TAB>).

Works with aesh's dynamic completion (--aesh-complete) used by the
generated bash/zsh/fish completion scripts.
Typing @<TAB> lists all available catalog names. Typing @catalogName
lists aliases in that catalog as alias@catalogName candidates, so
selecting one replaces the @catalogName token with the fully-qualified
reference (e.g. h2@jbanghub/h2).

Also handles sub-catalog navigation: @jbanghub shows @jbanghub/h2,
@jbanghub/allure, etc. and drilling into @jbanghub/h2 shows
h2@jbanghub/h2.
The aesh-generated fish completion script uses 'commandline -cop'
which returns only completed tokens, excluding the token currently
being typed at the cursor. This means partial-word completion never
works (e.g. 'jbang build hel<TAB>' produces no results).

Apply a post-generation fix that replaces the function body to use
'commandline -ct' for the current token, which works correctly in
all cases.

Upstream issue: aeshell/aesh#436
Complete groupId, artifactId, and version from the local Maven
repository (~/.m2/repository or configured location).

- Typing a dotted prefix with 2+ dots triggers GAV mode (e.g. com.google.)
- Typing a colon always triggers GAV mode
- GroupId completion shows both deeper groups (trailing dot) and
  available artifacts (trailing colon) when both exist
- ArtifactId completion lists artifacts within the groupId
- Version completion lists locally cached versions

The heuristic distinguishes GAV from filenames: single-dot names like
hello.java stay in file mode, while com.google.guava switches to GAV.

Directory structure is introspected to distinguish groupId paths from
artifactId directories (which contain version subdirs with .pom files).
Complete files and directories when typing GitHub blob/tree URLs by
calling the GitHub Contents API.

Supports URLs like:
  https://github.com/owner/repo/blob/branch/<TAB>
  https://github.com/owner/repo/blob/branch/src/main/<TAB>
  https://github.com/owner/repo/blob/branch/hel<TAB>

Features:
- Lists directories (with trailing /) and files filtered to JBang
  extensions (.java, .jsh, .kt, .groovy, .md)
- In-memory LRU cache with 5-minute TTL (up to 50 entries)
- Uses GITHUB_TOKEN or GH_TOKEN env var for auth if available
- 2s connect timeout + 3s read timeout — fails silently on error
- Caches empty results on API errors to avoid repeated hammering
The GitHub Contents API accepts HEAD as a ref, which always resolves
to the repo's default branch. This avoids the two-request dance of
trying main then master.
- Accept github.com/owner/repo without https:// prefix
- Trailing slash is optional for repo URLs
- All completion candidates are normalized to https:// URLs
@maxandersen maxandersen force-pushed the feature/scriptref-completion branch from f58fa6a to 9365f8c Compare May 20, 2026 18:01
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