diff options
Diffstat (limited to 'CHANGELOG.md')
| -rw-r--r-- | CHANGELOG.md | 133 |
1 files changed, 132 insertions, 1 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index c954123..f21cf86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,7 +57,45 @@ None #### Fixes: -None +1. **[`comps:zsh`]** Fixed zsh completion script to properly escape colons in completion descriptions. The zsh completion script generated for the `zsh` output format now escapes colon characters in completion items (`${item//:/\\:}`) and description parts (`${match[1]//:/\\:}`) so that descriptions containing colons don't break the `_describe` command's parsing. Additionally, fixed the simple-completions branch to iterate over the original `completions` array (with the colon-escaped format matching) rather than the already-parsed `parsed_completions` array, correctly extracting the completion item when no description is present. + +2. **[`comps:bash`]** Reworked the bash completion script template to fix word-index tracking when the cursor is in the middle of a word and to add colon-ltrimming for completion descriptions. The script now computes the current word (`cur`) using `COMP_LINE` truncated to `COMP_POINT` (taking the last whitespace-delimited token) rather than relying on `COMP_WORDS[COMP_CWORD]`, which fails when the cursor is in the middle of a word. The word index is derived from the count of words preceding the current cursor position (`before_words`), and `prev` is the last word in that preceding set. The option flags to the underlying completion engine are now passed as `-f value`/`-C value`/etc. (space-separated) instead of `-f=value`/`-C=value`/etc. (equals-separated), since the engine expects space-delimited argument pairs. Additionally, when the current word contains a colon and `COMP_WORDBREAKS` includes `:`, the completion items are trimmed of the colon prefix before being inserted into `COMPREPLY` — this prevents completions like `foo:bar` from being double-prefixed with the literal `foo:bar` when bash would otherwise append the full word. + +3. **[`core:comp`]** Fixed the completion engine to correctly resolve command nodes when global parameters (flags and their values) precede the subcommand. Previously, the engine matched the command tree starting from the first argument after the program name, treating every leading argument as part of the command path — so `prog [PARAM]... <subcommand>` style invocations would fail to match any node. Now: + + - A new helper `first_command_arg_index::<P>(args)` scans the argument list and returns the index of the first argument that matches the head of a registered command node (excluding node names starting with `_`). Everything before that index is treated as global parameters and skipped during command tree matching. + - In `CompletionHelper::complete`, the dispatch args are sliced to start at the first command-node match (`all_args[start..]`); if no node match is found, the args are empty (`Vec::new()`). + - In `default_completion`, the input path resolution skips the leading global-parameter arguments the same way, so `prog -v hello` correctly suggests the `hello` node even though `-v` precedes it. + - In the unmatched-dispatcher branch: when a command node _has_ been matched (`first_cmd_match.is_some()`), the `EntryFallback` handler is **skipped** and only `default_completion` runs, since global parameters do not warrant invoking the fallback. When no node was matched, the previous behavior is retained (fallback combined with default completion). + + This enables `prog [PARAM]... <subcommand>` style invocations to resolve the subcommand correctly, while a "broken" path such as `prog -v hello -a someone` still fails to match a `hello someone` node (since `-a someone` lies _after_ the first matched node and participates normally). + +4. **[`core:comp`]** Added `add_prefix()` and `add_suffix()` methods to `Suggest` for batch-transforming suggestion text: + + - **`add_prefix(self, prefix: impl Into<String>) -> Suggest`** — Takes the current `Suggest` value and prepends the given prefix to the suggestion text of every item. If the `Suggest` value is `Suggest::FileCompletion`, it is returned unchanged. For example, `["foo", "bar"]` with prefix `"--"` becomes `["--foo", "--bar"]`. + - **`add_suffix(self, suffix: impl Into<String>) -> Suggest`** — Takes the current `Suggest` value and appends the given suffix to the suggestion text of every item. If the `Suggest` value is `Suggest::FileCompletion`, it is returned unchanged. For example, `["foo", "bar"]` with suffix `"="` becomes `["foo=", "bar="]`. + + Both methods consume the original `Suggest` value and return a new one, enabling ergonomic chaining with the existing `combine()` method for transforming completion suggestion sets. + +5. **[`core:dispatch_tree`]** Refactored `build_dispatch_body` in `mingling_macros/src/systems/dispatch_tree_gen.rs` to use a caller-provided `no_match` fallback token stream instead of hardcoding `return Ok(Self::build_entry_fallback(raw.to_vec()));` at every level of the generated trie. + + - The root call from `gen_dispatch_args_trie` passes the current fallback token stream as `no_match`. + - The `no_match` parameter is threaded down through recursive calls; when a subtree has no nodes, it returns the caller's `no_match` code instead of unconditionally emitting the fallback. + - For each node, `level_no_match` is computed as the exact-endpoint checks for that node followed by the caller's `no_match` code. This ensures exact endpoints at the current level are tried before giving up and bubbling up to the parent. + - When a node has children, the arm for each child runs the child's body first; if the child subtree fails to match, control falls through to `level_no_match` (exact endpoints here, then the parent's fallback), so longer registered paths are preferred over the exact endpoint at the same depth. + - When a node has no children, the generated code runs the exact-endpoint checks followed by `no_match`. + + Behavioral result: the static trie dispatcher now follows the same "longest registered prefix wins" rule as the dynamic dispatcher — a child (longer) path is preferred over an exact endpoint at the same depth, and only when every descendant fails to match is the exact endpoint at the current depth dispatched. + +6. **[`core:comp`]** Changed the `CompletionHelper::complete` method so that when a concrete entry is dispatched (i.e., a custom completion handler produces a `Suggest` value), the custom completion is now **merged with the default completion suggestions** rather than replacing them entirely. + + Previously, when an explicit completion handler produced a `Suggest`, that value was returned as-is and the default subcommand suggestions (leaf nodes under the current path) were never consulted. Now: + + - The default completion (`default_completion::<P>(ctx)`) is always computed and merged via `Suggest::combine()` when the custom suggestion is not `Suggest::FileCompletion`. + - If the custom completion is `Suggest::FileCompletion`, the default completion is used instead (since `FileCompletion` cannot be meaningfully merged with subcommand suggestions). + - Concrete entry completions and default subcommand suggestions coexist — e.g., `thanks <tab>` now suggests both the leaf nodes (`bob`, `alice`) and the `thanks` entry's own completion. + +7. **[`core:render`]** Fixed the `RenderResult::eprintln` method to actually write to stderr. Previously, this method shamefully used `println!` (which writes to stdout) when `immediate_output` was enabled, rather than `eprintln!` (which writes to stderr). Yes, you read that right — a method literally named `eprintln` was printing to stdout. Talk about a identity crisis. The output has been corrected to use `eprintln!`, ensuring that error-level render output is properly separated from standard output streams. Whoever wrote that deserves a wet noodle slap — the entire point of an `e`-prefixed method is that it goes to standard _error_, not standard _out_. At least the bug is dead now, and we can all sleep a little easier knowing "error" output goes where error output belongs. #### Optimizations: @@ -168,6 +206,91 @@ None Also reorganized the `[features]` section of `mingling/Cargo.toml` into logical subsections (Presets, Core, Special features, Features, LEGACY) for improved maintainability and documentation. +8. **[`core`]** **[`macros`]** Added the `#[completion(EntryFallback)]` syntax to the `#[completion]` macro, mirroring the `#[help(EntryFallback)]` pattern. When the `EntryFallback` identifier is passed (either as a bare path in the attribute arguments), the macro generates a chain handler for the `EntryFallback` type (the program's fallback entry pack type generated by `gen_program!()`). This handler is invoked during the default-completion path when no explicit dispatcher match is found. + + When the program's dispatcher fails to find a match in the completion pipeline, the `CompletionHelper::complete` method now: + + - Checks for an explicit completion handler via the chain pipeline for the `EntryFallback` type (calling `P::do_comp(&P::build_entry_fallback(vec![]), ctx)`). + - If that produces suggestions, they are combined with the default completion suggestions via `Suggest::combine()` (which merges `BTreeSet`s for `Suggest::Suggest` values). + - Falls back to the standard `default_completion::<P>(ctx)` behavior when no explicit fallback handler yields results. + + This enables users to provide custom completion suggestions for the fallback/unmatched case: + + ```rust,ignore + #[completion(EntryFallback)] + fn complete_fallback(_ctx: &ShellContext) -> Suggest { + suggest! { "fallback" } + } + ``` + + The generated chain handler for `EntryFallback` is identical to how `#[chain]`-annotated functions targeting entry pack types work — the `EntryFallback` type name is resolved through the same `chain` code-generation path used for `Entry{Type}` types, allowing users to write a dedicated completion function for the fallback entry point without manually registering it in the program's dispatcher. + + Internal changes: + - `mingling_macros/src/attr/completion.rs` updated to detect the `EntryFallback` identifier in attribute arguments. + - `CompletionHelper::complete` in `mingling_core/src/comp.rs` now invokes the fallback completion handler via `P::do_comp(&P::build_entry_fallback(vec![]), ctx)` and merges results with `Suggest::combine()`. + +9. **[`core`]** **[`macros`]** Added a compile-time **entry metadata** system that allows attaching arbitrary, compile-time-typed metadata to entries and retrieving it at runtime. + + - **`Metadata<B>` trait** — Added to `mingling_core::asset::metadata` and re-exported from `mingling_core` / `mingling`. A type implementing `Metadata<B>` for an entry variant `E` provides `init_metadata() -> B`, defining the metadata value for that entry. + + - **`#[metadata(EntryVariant)]` attribute macro** — Added `mingling_macros::metadata`, which converts a zero-argument function into: + - an `impl ::mingling::Metadata<ReturnType> for EntryVariant` whose `init_metadata()` calls the original function, + - a `register_metadata!(EntryVariant, ReturnType)` invocation that populates the global `METADATA` registry, + - the preserved original function unchanged (including attributes, visibility, and return signature). + + Requirements: the function must take no parameters, must have an explicit return type, and cannot be async. + + - **`register_metadata!(EntryVariant, MetadataType)` macro** — Added `mingling_macros::register_metadata` (doc-hidden) which parses the two type arguments and stores a match-arm-style string entry in the `METADATA` global registry for later consumption by `gen_program!`. + + - **`ProgramCollect::get_metadata<T>(member_id) -> Option<T>`** — Added a default method on `ProgramCollect` that returns `None`. The `gen_program!` macro now overrides it: if the `METADATA` registry is non-empty, it generates a `get_metadata` implementation that matches on the enum member, compares the requested `TypeId::of::<T>()` against each registered metadata type's `TypeId`, and downcasts the boxed `Any` to `T`. + + - **`pathf` integration** — Added `MetadataPattern` to `mingling_pathf` that matches functions annotated `#[metadata(BindType)]`, extracting both the `BindType` (attribute argument, always a local in-crate entry type) and the `DataType` (the function's return type — resolved as local or foreign via `use` imports) so `pathf` emits the appropriate `use` statements for `gen_program!`. + + Usage: + + ```rust,ignore + #[metadata(EntryGreet)] + pub fn greet_desc() -> Description { + Description { desc: "ok".to_string() } + } + + // Later, at runtime: + let desc = ThisProgram::get_metadata::<Description>(ThisProgram::EntryGreet); + ``` + + The `#[metadata]` attribute and `Metadata` trait are re-exported as `mingling::macros::metadata` and `mingling::Metadata` respectively. + +10. **[`metadata:description`]** Added the `mingling::metadata` module and the `Description` convention metadata type. The `Description` type provides a human-readable description for any `Grouped` type, designed to be attached via the `#[metadata]` attribute macro introduced in item 9 above. + + The `Description` struct wraps a `String` and provides: + + - **`Description::new<S: Into<String>>(desc: S) -> Description`** — Constructs a new `Description` from any value convertible to `String`. + - **`From<String>`** / **`From<&str>`** — Constructs a `Description` from an owned `String` or a string slice. + - **`From<Description> for String`** / **`From<&Description> for String`** — Extracts the inner `String` (or a clone) from a `Description` value. + - **`Deref<Target = str>`** / **`DerefMut`** — Allows `Description` to be used transparently as a `str`, so string methods (`len()`, `contains()`, etc.) work directly on it. + - **`Display`** — Formats the description as its inner string, so `Description` can be used directly with `format!`, `print!`, and `String::from`-style operations. + + Usage: + + ```rust,ignore + use mingling::metadata::Description; + + #[metadata(EntryGreet)] + pub fn greet_desc() -> Description { + Description::new("Greets the user by name.") + } + + // Later, at runtime: + let desc = ThisProgram::get_metadata::<Description>(ThisProgram::EntryGreet); + println!("{desc}"); // "Greets the user by name." + ``` + + The module is gated behind the `core` feature and re-exported as `mingling::metadata`. This type is designed to work hand-in-hand with the compile-time entry metadata system from item 9, providing a first-party convention metadata for describing entries in generated documentation and help output. + +11. **[`core:comp`]** Enhanced `default_completion` to attach each suggestion's owning entry's `Description` metadata as a completion description. A new helper `entry_description::<P>(node)` resolves a space-separated command node path to its owning entry's member id (via the dispatch trie under `dispatch_tree`, or `match_user_input` + dispatcher `begin` otherwise), then retrieves the `Description` via `ProgramCollect::get_metadata::<Description>`. If found, suggestions for that node are built as `SuggestItem::new_with_desc(token, desc)` instead of plain `SuggestItem::new(token)`. + + The suggestion collection was also reworked: it now uses a `BTreeSet<SuggestItem>` for natural ordering and deduplication (replacing the previous `Vec<String>` + manual `sort()`/`dedup()`), carries both the suggested token and the fully-qualified owner node path used for the description lookup, and returns `Suggest::Suggest(suggestions)` directly. `entry_description` returns `None` for intermediate trie segments that have no entry of their own or entries without a registered `Description`, in which case suggestions fall back to plain `SuggestItem::new(token)`. The final empty-suggestions fallback to `file_suggest()` is unchanged. + #### **BREAKING CHANGES** (API CHANGES): 1. **[`macros`]** **[BREAKING]** Renamed the `extra_macros` feature to `extras`. All feature-gated macro re-exports in `mingling/src/lib.rs` (and throughout the codebase) have been updated from `#[cfg(feature = "extra_macros")]` to `#[cfg(feature = "extras")]`. @@ -212,6 +335,14 @@ None _No behavioral changes — this is a pure rename of the internal fallback type and its associated `ProgramCollect` methods. The type's semantics, shape (wrapping `Vec<String>`), and rendering behavior are unchanged. +3. **[`picker:global`]** **[`setups:picker`]** Refactored the global picker utility functions into a trait-based API. The standalone functions `pick_global_flag(program, flag)` and `pick_global_argument(program, arg)` in `mingling::picker` have been replaced by the `PickerHelper<C>` trait, implemented for `Program<C>`. This trait provides `pick_flag(&mut self, flag: &PickerArg<Flag>) -> bool` and `pick_argument<A>(&mut self, arg: &PickerArg<A>) -> Option<A>`, and is backed by the `take_args` / `replace_args` methods on `Program`. + + **Migration guide:** + + - `pick_global_flag(program, flag)` → `program.pick_flag(flag)` + - `pick_global_argument(program, arg)` → `program.pick_argument(arg)` + - Import `mingling::picker::PickerHelper` instead of `mingling::picker::{pick_global_flag, pick_global_argument}` + --- ### Release 0.3.0 (2026-07-27) |
