Add smart tab-completion for scriptOrFile argument#2466
Draft
maxandersen wants to merge 23 commits into
Draft
Conversation
Contributor
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
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
f58fa6a to
9365f8c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds rich tab-completion for the
scriptOrFilepositional argument used byrun,build,edit,alias add, andinfocommands. 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 toh2@jbanghub/h24. Maven GAV (from local
~/.m2/repository)com.google.<TAB>→ groupId prefixes and available artifactscom.google.guava:<TAB>→ artifactIdscom.google.guava:guava:<TAB>→ cached versionsDistinguishes 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 listinggithub.com/(nohttps://) supported, candidates normalized to full URLsGITHUB_TOKEN/GH_TOKENfor auth if availableBug Fixes
Fish completion script fix
The aesh-generated fish completion script uses
commandline -copwhich excludes the current token being typed. Applied a post-generation fix usingcommandline -ct. Upstream: aeshell/aesh#436Known Limitation
Completion works on commands with
@Argumentonly (build,info tools). Commands with both@Argument+@Arguments(run,edit,alias add) are blocked by an aesh bug where the@Argumentcompleter is silently skipped. Upstream: aeshell/aesh#435Demo
(GIF shows all 5 completion types in action)