12071 Commits
Author SHA1 Message Date
Piepmatz 7e36e4a148 After patch release (#19156) 2026-10-04 17:47:01 +02:00
Piepmatz b632ef0994 Prep patch release (#19154) 0.116.1 2026-10-04 16:31:48 +02:00
Darren Schroeder bd30843743 fix(startup): time nushell from main like bash and zsh, fix exec (#19136)
## Description

This PR fixes two problems with `$nu.startup-time` reported on Discord.

1. **It grew after every `exec nu`.** While probably more accurate,
#19049 counted the time before `main` by reading what the kernel records
for the process, but it turned out to be a problem since `exec` replaces
the program but keeps the process, so the time would carry over, getting
larger with each `exec` which made it no longer correct.

2. **It was `-1ns` in many places.** The value started at `-1` and was
only set when the REPL drew its first prompt, or when `-c` commands or a
script started running.
 
So, I did some research to figure out how other shells, namely bash,
zsh, and fish, count startup time.

### How other shells time themselves

None that I checked reads the process creation time. Each starts its
clock inside the shell:

- **bash** sets `shellstart` with `gettimeofday` near the top of `main`
(`shell.c`). That is before option parsing and before it takes control
of the terminal for job control.
- **zsh** 5.9 sets `shtimer` with `gettimeofday` in `setupvals()`, after
`init_io()` has taken control of the terminal. `$SECONDS` counts from
it.
- **fish** has no startup-time value. `--profile-startup` times each
startup command with its own wall clock.

Nushell now does the same as bash: the clock starts on the first line of
`main` with the monotonic `Instant`. It leaves out the time the OS
spends starting the executable, and `exec nu` starts a new count. Like
bash, nushell counts the time an interactive `nu &` waits to be brought
to the foreground before it can take the terminal; zsh doesn't.

## User-facing changes (Release notes)

- `$nu.startup-time` is now measured from the moment Nushell starts
running, the same way bash and zsh time themselves. It no longer
includes the time the operating system spends loading the executable and
its libraries (about 7–9ms on macOS), on all platforms.
- Fixed `$nu.startup-time` growing every time `exec nu` replaced the
current shell on macOS and Linux.
- Fixed `$nu.startup-time` showing `-1ns` in config files, `-e`
commands, `--mcp`, `--lsp`, `--dap` and `--ide-check`. Before startup
finishes, it now shows the startup time so far.
- `--ide-hover`, `--ide-complete`, `--ide-goto-def` and `--ide-ast` now
see `$nu`, so hovering a constant built from it shows its type and
`$nu.` completes its columns.
- Fixed `$nu.is-mcp` being `false` in config files loaded by `nu --mcp`.
- `--log-level perf` no longer prints a "before main" line. Its final
REPL line is now "startup (main to first prompt)", and the
`--mcp`/`--lsp` start lines no longer include the whole session.

## Additional notes

- API changes for embedders:
`EngineState::get_startup_time`/`set_startup_time` are replaced by
`startup_time`, `set_startup_start` and `finish_startup`, and
`nu_cli::evaluate_repl` no longer takes a start time.
2026-10-02 09:25:12 -05:00
Martin Kronberger 1f2dd1f54a fix(completions): keep menus open on no match, let cancelled pickers close and reopen (#19129)
## Description

Since #18791 every completion menu closes as soon as the typed text
matches nothing, with `persistent_menus` on too (#19096).
`SourcedMenu::abandon_if_empty` closes on any settled empty answer, and
#18791 wrapped the default columnar and ide menus in it as well.
The abandon exists for cancelled pickers: an `@interactive` completer or
menu source cancelled with Esc answers empty, and an open empty menu
would relaunch it on the next keystroke.
A cancelled picker is always the first answer after activation though,
while typing past all matches comes after the menu has shown values.
So `SourcedMenu` now remembers whether it has shown values since the
last `Activate`, and only abandons when the first answer is empty.

Testing the picker path turned up two older bugs, both also on `main`:
- **Stray indicator.** With `quick` off, the opening
`can_partially_complete` probe is the first fetch, but the wrapper
skipped the abandon check there. The menu only closed on the next
repaint, after the prompt had drawn its indicator, so a `| ` stayed
until the next keypress. The probe now checks too; a probe that fetched
nothing is still covered by `is_awaiting_first_answer`.
- **Picker would not reopen.** `NuCompleter` kept the cancelled picker's
empty answer in `latest`, so Tab on the same line served it again
instead of running the picker. Interactive answers are now kept only
when non-empty, which still dedupes the repeated asks of one keystroke.

## User-facing changes (Release notes)
### Completion menus stay open when nothing matches

An open completion menu no longer closes when the typed text stops
matching.
It shows `NO RECORDS FOUND` and fills again once the text matches, as it
did before 0.116.
Cancelling an `@interactive` picker with Esc now closes its menu right
away, and Tab on the same line opens the picker again.

## Additional notes

fixes #19096
2026-10-01 06:10:42 -05:00
Martin Kronberger 3100c9d764 fix(completions): run a wrapped rest completer on flag-like tokens (#19130)
## Description

In a `def --wrapped` command, `what --<Tab>` never called the completer
on `...args` (#19097).
Both `trailing_slot` and `resolve_argument_site` classify any token
starting with `-` as `SiteKind::FlagName`, which dispatches to flag-name
completion only, even though a wrapped command binds an unknown `--x` to
its rest parameter.
This is a regression from #18761: before it, only a bare `-` routed to
flag completion, so `--x` on a wrapped command reached the rest
completer, which took precedence over `@complete` as it does again now.

`SiteKind::FlagName` now carries the positional the token would bind to.
On a wrapped command whose positional declares a completer, the flag
names are merged with that positional's completion, through the same
`complete_argument_value` chain a plain positional takes.
`site_completer` resolves the same completer, so an `@interactive` one
still runs inline.

An undeclared rest parameter keeps flag-name completion only, since its
shape fallback would list files for every `-` typed.
`place.kind` stays `flag-name`.

## User-facing changes (Release notes)

Fixed a regression since 0.115 where custom completers on a `def
--wrapped` rest parameter were not called for arguments starting with
`-`, so `what --<Tab>` only offered declared flags.

## Additional notes

fixes #19097
2026-10-01 06:09:41 -05:00
Martin Kronberger d69f333122 chore: bump reedline to d7bcb12 (#19123) 2026-09-30 16:01:49 +02:00
dependabot[bot] 6b2f93a52b build(deps): bump mq-markdown from 0.8.4 to 0.9.1 (#19119)
Bumps [mq-markdown](https://github.com/harehare/mq) from 0.8.4 to 0.9.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/harehare/mq/releases">mq-markdown's
releases</a>.</em></p>
<blockquote>
<h2>v0.9.1</h2>
<h2>What's Changed</h2>
<h3>✨ Features</h3>
<ul>
<li>✨ feat(mq-lang): add extract_urls by <a
href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2420">harehare/mq#2420</a></li>
<li>✨ feat(mq-playground): run mq scripts in a Web Worker by <a
href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2424">harehare/mq#2424</a></li>
</ul>
<h3>🐛 Fixes</h3>
<ul>
<li>fix(wasm): use browser clock for query timeouts by <a
href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2425">harehare/mq#2425</a></li>
</ul>
<!-- raw HTML omitted -->
<ul>
<li>build(deps): bump codecov/codecov-action from 7.1.0 to 7.1.1 in the
dependencies group by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/harehare/mq/pull/2430">harehare/mq#2430</a></li>
<li>build(deps-dev): bump the dependencies group in /editors/vscode with
2 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/harehare/mq/pull/2427">harehare/mq#2427</a></li>
<li>build(deps-dev): bump <code>@​babel/core</code> from 7.29.7 to 8.0.5
in /packages/mq-playground in the dependencies group by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/harehare/mq/pull/2429">harehare/mq#2429</a></li>
<li>build(deps-dev): bump <code>@​types/node</code> from 26.6.0 to
26.6.1 in /packages/mq-web in the dependencies group by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/harehare/mq/pull/2428">harehare/mq#2428</a></li>
</ul>
<!-- raw HTML omitted -->
<p>Full Changelog: <a
href="https://github.com/harehare/mq/compare/v0.9.0...v0.9.1">https://github.com/harehare/mq/compare/v0.9.0...v0.9.1</a></p>
<h2>v0.9.0</h2>
<h2>What's Changed</h2>
<h3>Features</h3>
<ul>
<li>✨ feat(bench): add mq-bench runner for pure-mq bench_ functions by
<a href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2367">harehare/mq#2367</a></li>
<li>✨ feat(cli): add --no-clobber/--append for -o output by <a
href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2306">harehare/mq#2306</a></li>
<li>feat(cli): add quiet execution mode by <a
href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2324">harehare/mq#2324</a></li>
<li>✨ feat(editors): add recent query history to Execute Query command
by <a href="https://github.com/harehare"><code>@​harehare</code></a> in
<a
href="https://redirect.github.com/harehare/mq/pull/2350">harehare/mq#2350</a></li>
<li>✨ feat(json): add JSON Pointer (RFC 6901) functions by <a
href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2386">harehare/mq#2386</a></li>
<li>✨ feat(json): add json_diff and json_patch (RFC 6902) by <a
href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2402">harehare/mq#2402</a></li>
<li>✨ feat(json): add json_merge_patch and merge3 by <a
href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2404">harehare/mq#2404</a></li>
<li>✨ feat(lang): add yield/generator support on the Tarn VM by <a
href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2322">harehare/mq#2322</a></li>
<li>✨ feat(md): add h1-h6, highlight, comment, wikilink, embed, escape,
hard_break builders by <a
href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2379">harehare/mq#2379</a></li>
<li>✨ feat(mq-lang): add casefold, unicode_normalize, graphemes,
grapheme_len builtins by <a
href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2315">harehare/mq#2315</a></li>
<li>✨ feat(mq-lang): add md::reference_index and md::resolve_references
by <a href="https://github.com/harehare"><code>@​harehare</code></a> in
<a
href="https://redirect.github.com/harehare/mq/pull/2317">harehare/mq#2317</a></li>
<li>✨ feat(mq-lang): add file_info builtin for file metadata by <a
href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2327">harehare/mq#2327</a></li>
<li>✨ feat(mq-lang): add dedent and common_indent builtins by <a
href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2332">harehare/mq#2332</a></li>
<li>✨ feat(mq-lang): support hyphen ranges and || (OR) in semver ranges
by <a href="https://github.com/harehare"><code>@​harehare</code></a> in
<a
href="https://redirect.github.com/harehare/mq/pull/2333">harehare/mq#2333</a></li>
<li>✨ feat(mq-lang): add walk_files builtin for generic file enumeration
by <a href="https://github.com/harehare"><code>@​harehare</code></a> in
<a
href="https://redirect.github.com/harehare/mq/pull/2334">harehare/mq#2334</a></li>
<li>✨ feat(mq-lang): add find builtin with predicate, stops at first
match by <a
href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2337">harehare/mq#2337</a></li>
<li>✨ feat(mq-lang): add is_blank, normalize_newlines, and safe_filename
builtins by <a
href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2346">harehare/mq#2346</a></li>
<li>✨ feat(mq-lang): add section::find/by_title/title_is/containing by
<a href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2351">harehare/mq#2351</a></li>
<li>✨ feat(mq-lang): add markdown_escape builtin for context-aware
Markdown escaping by <a
href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2352">harehare/mq#2352</a></li>
<li>✨ feat(mq-lang): add regex_escape builtin for literal-safe regex
patterns by <a
href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2354">harehare/mq#2354</a></li>
<li>✨ feat(mq-lang): add callback-based regex_replace by <a
href="https://github.com/harehare"><code>@​harehare</code></a> in <a
href="https://redirect.github.com/harehare/mq/pull/2365">harehare/mq#2365</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/harehare/mq/commit/d90aacf05b01e67250dbcdaa329e2ff3919ad0d9"><code>d90aacf</code></a>
chore: bump version to 0.9.1</li>
<li><a
href="https://github.com/harehare/mq/commit/e18830b3ffb7d9c676c96bf1db8a05de4d294f81"><code>e18830b</code></a>
Merge pull request <a
href="https://redirect.github.com/harehare/mq/issues/2427">#2427</a>
from harehare/dependabot/npm_and_yarn/editors/vscode...</li>
<li><a
href="https://github.com/harehare/mq/commit/c6575cf0a5c62a63bd8ffbf24636dc8924f09d0c"><code>c6575cf</code></a>
Merge pull request <a
href="https://redirect.github.com/harehare/mq/issues/2428">#2428</a>
from harehare/dependabot/npm_and_yarn/packages/mq-we...</li>
<li><a
href="https://github.com/harehare/mq/commit/43f50d70205c64976150e95d718ddbeaa00d99d5"><code>43f50d7</code></a>
Merge pull request <a
href="https://redirect.github.com/harehare/mq/issues/2429">#2429</a>
from harehare/dependabot/npm_and_yarn/packages/mq-pl...</li>
<li><a
href="https://github.com/harehare/mq/commit/5be9423e91ff220cedff76cbc46a364aa468e319"><code>5be9423</code></a>
Merge pull request <a
href="https://redirect.github.com/harehare/mq/issues/2430">#2430</a>
from harehare/dependabot/github_actions/dependencies...</li>
<li><a
href="https://github.com/harehare/mq/commit/8e6f117033b6c839fb06a639c5ab6dbcee0d6e27"><code>8e6f117</code></a>
build(deps): bump codecov/codecov-action in the dependencies group</li>
<li><a
href="https://github.com/harehare/mq/commit/aa762fcebf8c7dafa07eb57739650dc64514be5b"><code>aa762fc</code></a>
build(deps-dev): bump <code>@​babel/core</code></li>
<li><a
href="https://github.com/harehare/mq/commit/ec0243f802587f25a965d9f5e09e3398e8a53067"><code>ec0243f</code></a>
build(deps-dev): bump <code>@​types/node</code></li>
<li><a
href="https://github.com/harehare/mq/commit/2f1355c6dbb7315de90c4837c6718ae7ce121529"><code>2f1355c</code></a>
build(deps-dev): bump the dependencies group</li>
<li><a
href="https://github.com/harehare/mq/commit/9a0ebd9236e61bc65dfeaf070531a90ce67eb9ae"><code>9a0ebd9</code></a>
Merge pull request <a
href="https://redirect.github.com/harehare/mq/issues/2420">#2420</a>
from harehare/feat/extract-urls</li>
<li>Additional commits viewable in <a
href="https://github.com/harehare/mq/compare/v0.8.4...v0.9.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=mq-markdown&package-manager=cargo&previous-version=0.8.4&new-version=0.9.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-30 20:41:05 +08:00
dependabot[bot] cbb8e89f9d build(deps): bump crate-ci/typos from 1.50.2 to 1.50.3 (#19118)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.50.2 to
1.50.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/crate-ci/typos/releases">crate-ci/typos's
releases</a>.</em></p>
<blockquote>
<h2>v1.50.3</h2>
<h2>[1.50.3] - 2026-09-25</h2>
<h3>Fixes</h3>
<ul>
<li>Don'y panicwhen case-converting non-ASCII corrections</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/crate-ci/typos/blob/main/CHANGELOG.md">crate-ci/typos's
changelog</a>.</em></p>
<blockquote>
<h1>Change Log</h1>
<p>All notable changes to this project will be documented in this
file.</p>
<p>The format is based on <a href="https://keepachangelog.com/">Keep a
Changelog</a>
and this project adheres to <a href="https://semver.org/">Semantic
Versioning</a>.</p>
<!-- raw HTML omitted -->
<h2>[Unreleased] - ReleaseDate</h2>
<h2>[1.50.3] - 2026-09-25</h2>
<h3>Fixes</h3>
<ul>
<li>Don'y panicwhen case-converting non-ASCII corrections</li>
</ul>
<h2>[1.50.2] - 2026-09-15</h2>
<h3>Fixes</h3>
<ul>
<li>Don't panic when files being examined are removed</li>
</ul>
<h2>[1.50.1] - 2026-09-01</h2>
<h3>Fixes</h3>
<ul>
<li>Don't correct <code>asend</code> in Python code</li>
</ul>
<h2>[1.50.0] - 2026-08-28</h2>
<h3>Features</h3>
<ul>
<li>Updated the dictionary with the <a
href="https://redirect.github.com/crate-ci/typos/issues/1587">August
2026</a> changes</li>
</ul>
<h2>[1.49.1] - 2026-08-27</h2>
<h3>Fixes</h3>
<ul>
<li>Don't correct the brand name <code>HashiCorp</code></li>
</ul>
<h2>[1.49.0] - 2026-08-03</h2>
<h3>Features</h3>
<ul>
<li>Updated the dictionary with the <a
href="https://redirect.github.com/crate-ci/typos/issues/1573">July
2026</a> changes</li>
</ul>
<h2>[1.48.0] - 2026-06-30</h2>
<h3>Features</h3>
<ul>
<li>Updated the dictionary with the <a
href="https://redirect.github.com/crate-ci/typos/issues/1562">June
2026</a> changes</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/crate-ci/typos/commit/00f422f3b19c57bc6338715ebfe3316d38768461"><code>00f422f</code></a>
chore: Release</li>
<li><a
href="https://github.com/crate-ci/typos/commit/98bbed78fd67676aa2142a4610cc2765d38065b1"><code>98bbed7</code></a>
docs: Update changelog</li>
<li><a
href="https://github.com/crate-ci/typos/commit/ee31b0612a32db0dae40912b4887b81bac062d32"><code>ee31b06</code></a>
Merge pull request <a
href="https://redirect.github.com/crate-ci/typos/issues/1621">#1621</a>
from antonkesy/fix-case-correct-panic</li>
<li><a
href="https://github.com/crate-ci/typos/commit/8aa61dbd1954540b95bfc58701a021421643d6f5"><code>8aa61db</code></a>
Merge pull request <a
href="https://redirect.github.com/crate-ci/typos/issues/1625">#1625</a>
from epage/maturin</li>
<li><a
href="https://github.com/crate-ci/typos/commit/3a5d9f60c2eeb5e4204d21048a248b8cdc4a0102"><code>3a5d9f6</code></a>
chore(ci): Update maturin</li>
<li><a
href="https://github.com/crate-ci/typos/commit/d4bd872c54325f63d1aeeebf45ac87a5bc14b3ce"><code>d4bd872</code></a>
fix(cli): Don't panic on non-ASCII corrections</li>
<li><a
href="https://github.com/crate-ci/typos/commit/85949cbe0d5583635f56363749f17d6ae4f21d35"><code>85949cb</code></a>
Merge pull request <a
href="https://redirect.github.com/crate-ci/typos/issues/1620">#1620</a>
from epage/test</li>
<li><a
href="https://github.com/crate-ci/typos/commit/70517c75e45231834820170bfa60bc967f515aa9"><code>70517c7</code></a>
chore(ci): Clean up test action</li>
<li><a
href="https://github.com/crate-ci/typos/commit/fb4f6a6bc5240be5610738566a80ada25f3a2659"><code>fb4f6a6</code></a>
Merge pull request <a
href="https://redirect.github.com/crate-ci/typos/issues/1619">#1619</a>
from szepeviktor/fix-wf</li>
<li><a
href="https://github.com/crate-ci/typos/commit/0c7bff0d1e90cc511d1114d172d01223bce09c46"><code>0c7bff0</code></a>
Fix merge conflict in rust-next workflow</li>
<li>Additional commits viewable in <a
href="https://github.com/crate-ci/typos/compare/512fc24f32f44ab01972217aaaf3dc86ec234d53...00f422f3b19c57bc6338715ebfe3316d38768461">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=crate-ci/typos&package-manager=github_actions&previous-version=1.50.2&new-version=1.50.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-30 20:40:42 +08:00
dependabot[bot] 7857aade69 build(deps): bump taiki-e/install-action from 2.87.17 to 2.87.21 (#19117)
Bumps
[taiki-e/install-action](https://github.com/taiki-e/install-action) from
2.87.17 to 2.87.21.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/taiki-e/install-action/releases">taiki-e/install-action's
releases</a>.</em></p>
<blockquote>
<h2>2.87.21</h2>
<ul>
<li>
<p>Update <code>wasm-bindgen@latest</code> to 0.2.129.</p>
</li>
<li>
<p>Update <code>protoc-gen-connect-openapi@latest</code> to 0.27.3.</p>
</li>
<li>
<p>Update <code>wasmtime@latest</code> to 49.0.1.</p>
</li>
<li>
<p>Update <code>uv@latest</code> to 0.12.19.</p>
</li>
<li>
<p>Update <code>mise@latest</code> to 2026.9.13.</p>
</li>
<li>
<p>Update <code>kingfisher@latest</code> to 2.7.0.</p>
</li>
</ul>
<h2>2.87.20</h2>
<ul>
<li>
<p>Update <code>uv@latest</code> to 0.12.18.</p>
</li>
<li>
<p>Update <code>cargo-shear@latest</code> to 1.14.0.</p>
</li>
</ul>
<h2>2.87.19</h2>
<ul>
<li>
<p>Update <code>wasmtime@latest</code> to 49.0.0.</p>
</li>
<li>
<p>Update <code>cargo-shear@latest</code> to 1.13.5.</p>
</li>
<li>
<p>Update <code>cargo-nextest@latest</code> to 0.9.146.</p>
</li>
</ul>
<h2>2.87.18</h2>
<ul>
<li>
<p>Update <code>oxfmt@latest</code> to 1.84.0.</p>
</li>
<li>
<p>Update <code>mise@latest</code> to 2026.9.12.</p>
</li>
<li>
<p>Update <code>kache@latest</code> to 0.26.3.</p>
</li>
<li>
<p>Update <code>cargo-tarpaulin@latest</code> to 0.37.4.</p>
</li>
<li>
<p>Update <code>cargo-rdme@latest</code> to 2.2.3.</p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md">taiki-e/install-action's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<p>All notable changes to this project will be documented in this
file.</p>
<p>This project adheres to <a href="https://semver.org">Semantic
Versioning</a>.</p>
<!-- raw HTML omitted -->
<h2>[Unreleased]</h2>
<h2>[2.87.22] - 2026-09-29</h2>
<ul>
<li>
<p>Update <code>typos@latest</code> to 1.50.3.</p>
</li>
<li>
<p>Update <code>tombi@latest</code> to 1.5.8.</p>
</li>
<li>
<p>Update <code>prek@latest</code> to 0.5.4.</p>
</li>
<li>
<p>Update <code>mise@latest</code> to 2026.9.16.</p>
</li>
<li>
<p>Update <code>kache@latest</code> to 0.28.0.</p>
</li>
<li>
<p>Update <code>gungraun-runner@latest</code> to 0.20.0.</p>
</li>
<li>
<p>Update <code>cargo-tarpaulin@latest</code> to 0.37.5.</p>
</li>
<li>
<p>Update <code>cargo-leptos@latest</code> to 0.3.10.</p>
</li>
<li>
<p>Update <code>cargo-binstall@latest</code> to 1.24.0.</p>
</li>
</ul>
<h2>[2.87.21] - 2026-09-26</h2>
<ul>
<li>
<p>Update <code>wasm-bindgen@latest</code> to 0.2.129.</p>
</li>
<li>
<p>Update <code>protoc-gen-connect-openapi@latest</code> to 0.27.3.</p>
</li>
<li>
<p>Update <code>wasmtime@latest</code> to 49.0.1.</p>
</li>
<li>
<p>Update <code>uv@latest</code> to 0.12.19.</p>
</li>
<li>
<p>Update <code>mise@latest</code> to 2026.9.13.</p>
</li>
<li>
<p>Update <code>kingfisher@latest</code> to 2.7.0.</p>
</li>
</ul>
<h2>[2.87.20] - 2026-09-24</h2>
<ul>
<li>Update <code>uv@latest</code> to 0.12.18.</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/taiki-e/install-action/commit/4cef1412cce204788f482e778a0b9187f9626a29"><code>4cef141</code></a>
Release 2.87.21</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/133135e7fd1570baff8964a044bcf0d2147fc57a"><code>133135e</code></a>
Update <code>wasm-bindgen@latest</code> to 0.2.129</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/8589241798155a8c0c86c7c0ae4f642a90a698e1"><code>8589241</code></a>
Update <code>protoc-gen-connect-openapi@latest</code> to 0.27.3</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/95e5bbc4a58633e12ebefd904f798c1eb76c18f7"><code>95e5bbc</code></a>
Update wasmtime manifest</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/48ce11d4f97676e64a76a3dc4b83a4f59245b702"><code>48ce11d</code></a>
Update <code>wasmtime@latest</code> to 49.0.1</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/368e6617a899743920fa751bb5d414b6f0352cc0"><code>368e661</code></a>
Update wasm-bindgen manifest</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/57f22a457e27d10a13bfcaee43e03dff1c20dedd"><code>57f22a4</code></a>
Update <code>uv@latest</code> to 0.12.19</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/888634e2727c1021a94ebfac9a2d266109a366b8"><code>888634e</code></a>
Update typos manifest</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/3bcab01fb9790f533b19647e60396514a9d08f96"><code>3bcab01</code></a>
Update protoc-gen-connect-openapi manifest</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/947e90eb285641d8325cc2d55ae24bd51d5c6b8a"><code>947e90e</code></a>
Update <code>mise@latest</code> to 2026.9.13</li>
<li>Additional commits viewable in <a
href="https://github.com/taiki-e/install-action/compare/94c31af3204a9f15ab40b35ad084410b905bbc73...4cef1412cce204788f482e778a0b9187f9626a29">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=taiki-e/install-action&package-manager=github_actions&previous-version=2.87.17&new-version=2.87.21)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-30 20:40:25 +08:00
Darren Schroeder 087077bdff Update cell_path.rs (#19112)
<!--
Thanks for contributing to Nushell!

Before submitting, please read the contributing guide:
https://github.com/nushell/nushell/blob/main/CONTRIBUTING.md

This template helps reviewers understand your changes and allows us to
generate high-quality release notes.
-->

## Description
Two typo bugs in cell paths `0x` instead of `Ox` and `'A'..='F'` instead
of `'A'..='Z'`
<!--
Explain what this PR does and why.

This section is intentionally flexible:
- Describe the problem
- Explain your approach
- Include technical details if relevant

Good examples:
- "In this PR, I fixed..."
- "In this PR, I added support for..."
- "This change improves X by..."

Write as much or as little as needed for reviewers to understand your
changes.
-->

## User-facing changes (Release notes)
More correct cell path parsing
<!--
This section is used (mostly as-is) for https://www.nushell.sh/blog/

Describe how Nushell behavior changes from a user's perspective.
Do NOT describe internal Rust changes here.

Write in a release note style, for example:
- "Added support for..."
- "Fixed an issue where..."
- "Nushell now supports..."
- "Improved performance of..."

If your changes do NOT affect users (internal refactors, cleanup, etc.),
just write:
- "n/a"
- "nan"
- or similar

This tells us the change should not appear in the changelog.

Tips:
- Focus on observable behavior
- Include examples if helpful
- Keep it concise

You can:
- Write a short paragraph (will appear as a bullet point), OR
- Use headings (###) if your change needs more structure

Avoid writing things like:
- "In this PR, I refactored..."
- "This updates internal code..."

You may leave this blank until the PR is ready.
-->

## Additional notes
<!--
Optional.

Examples:
- fixes #123
- closes #456
- related #789

Anything else reviewers should know.
Remove this section if not needed.
-->
2026-09-29 15:48:24 -05:00
Darren Schroeder 6a8f2a3cd3 feat(tui): --border and $env.config.tui.border_type pick border lines by table --theme name (#19100)
## Description

watashi_san asked for a way to change the border style of `tui` widgets,
using the same names as `table --theme`. Every border used to be
ratatui's plain `┌─┐` lines.

This PR adds two ways to choose the lines:

- **`--border <name>`** on every widget that draws a border: `tui
table`, `tui tree`, `tui select`, `tui log`, `tui preview`, `tui
search`, `tui textbox`, `tui box`, and `tui menu` (for its dropdowns).
The flag completes the names like `table --theme` does.
- **`$env.config.tui.border_type`** sets the default for every border,
including the menu dropdown and the `--dialog` frame. It defaults to
`"single"`, which draws the same lines as before.

Each name draws the closest outline of that table theme, using ratatui
border glyphs:

| name | outline |
|---|---|
| `single`, `thin` | `┌─┐ │ └─┘` (the old look) |
| `rounded` | `╭─╮ │ ╰─╯` |
| `double` | `╔═╗ ║ ╚═╝` |
| `heavy` | `┏━┓ ┃ ┗━┛` |
| `reinforced` | `┏─┓ │ ┗─┛` |
| `basic`, `basic_compact` | `+-+ \| +-+` |
| `ascii_rounded` | `.-. \| '-'` |
| `dots` | `.` top, `:` sides, `:..:` bottom |
| `compact` / `compact_double` / `restructured` / `with_love` | `─` /
`═` / `=` / `❤` lines on top and bottom, blank sides |
| `markdown` | `\|` sides, blank top and bottom |
| `frameless`, `light`, `psql` | blank all around |


## User-facing changes (Release notes)

### Choose `tui` border lines with `--border` or
`$env.config.tui.border_type`

Bordered `tui` widgets (`tui table`, `tui tree`, `tui select`, `tui
log`, `tui preview`, `tui search`, `tui textbox`, `tui box` and the `tui
menu` dropdowns) accept `--border <name>`, where the name is a `table
--theme` name such as `rounded`, `double` or `heavy`.
`$env.config.tui.border_type` sets the default for every border. It
defaults to `single`, the existing look.

```nushell
ls | tui table --border rounded | tui run
$env.config.tui.border_type = "double"
```

`none` isn't accepted, because a tui border always takes one cell. Use
`frameless` for a border that draws nothing.


## Additional notes
<!--
Optional.

Examples:
- fixes #123
- closes #456
- related #789

Anything else reviewers should know.
Remove this section if not needed.
-->
2026-09-29 09:53:20 -05:00
Darren Schroeder bc55559a7a feat(tui): --title renames a widget's border; tui label --title becomes --titlebar (#19102)
## Description

watashi_san asked for a way to change the title a `tui` widget draws in
its border. Until now the border titles were fixed, like `table (12)`,
`log (live)` and `input`.

`tui label` already had a `--title` switch that meant "put this text on
the top bar". Adding a `--title <string>` flag to the other widgets
would have given one flag two meanings in the same command family, so
the label switch is renamed to `--titlebar`. That frees `--title` for
the border title. `tui label --title` still works for one release. It
prints a deprecation warning and is due to go in 0.118.0.

The new flag is on every widget that draws a titled border. That's `tui
table`, `tui tree`, `tui select`, `tui log`, `tui preview`, `tui search`
and `tui textbox`. `--title` replaces only the widget's name. Anything
after the name that shows state stays.

| widget | default | `--title files` |
|---|---|---|
| table | `table (12)`, `table (12, 3 checked)` | `files (12)`, `files
(12, 3 checked)` |
| select, tree | `select (4)`, `tree (40)` | `files (4)`, `files (40)` |
| log | `log`, `log (live)`, `log (paused)` | `files`, `files (live)`,
`files (paused)` |
| search | `search`, `search (fuzzy)` | `files`, `files (fuzzy)` |
| textbox | `input` | `files` |
| preview | `preview`, or the previewed file's name | `files`, or `files
(Cargo.toml)` |

Preview is the odd one out. It has no fixed name, since its border shows
the file being previewed, so `--title` goes in front and the file name
moves into parentheses. `help tui preview` says so.

## User-facing changes (Release notes)

### Widgets can set their border title with `--title`

`tui table`, `tui tree`, `tui select`, `tui log`, `tui preview`, `tui
search` and `tui textbox` accept `--title <string>`. It replaces the
widget's name in the border. Counts or state after the name stay.

```nushell
ls | tui table --title files | tui run   # border reads "files (12)" instead of "table (12)"
```

On `tui preview` the file name follows the title in parentheses, as in
`files (Cargo.toml)`. On `tui search` the title is also the default
placeholder.

### Deprecation: `tui label --title` is now `tui label --titlebar`

The switch that puts a label on the top bar is renamed. `tui label
--title` keeps working until 0.118.0 and warns on first use. Replace
`tui label --title "My app"` with `tui label --titlebar "My app"`.

### Changed `tui` records

- A title-bar label's `slot` in the `widgets` record is `titlebar`, not
`title`.
- `values.<preview id>.title` is an empty string when nothing is
previewed. It used to be the placeholder `preview`.


## Additional notes
<!--
Optional.

Examples:
- fixes #123
- closes #456
- related #789

Anything else reviewers should know.
Remove this section if not needed.
-->
2026-09-29 09:08:48 -05:00
Darren Schroeder 780cb719de fix(tui): arrow keys resize tui split panes in both directions (#19101)
## Description

Resizing a `tui split` pane with the arrow keys (or `h`/`j`/`k`/`l`)
only worked in one direction, and on short splits it barely worked at
all. Reported by watashi_san.

## User-facing changes (Release notes)

- Fixed `tui split` resizing from the keyboard. The arrow keys and
`h`/`j`/`k`/`l` now move the divider one cell per press in both
directions, with or without `--vertical`, and never shrink a pane to
nothing. Dragging any divider with the mouse now lands under the
pointer, as far as the panes on either side have room. A split of
buttons can no longer be resized by accident.

### Examples
```nushell
ls | tui split [(tui table) (tui preview)] | tui run
ls | tui split [(tui table) (tui preview)] --vertical | tui run
```

## Additional notes
<!--
Optional.

Examples:
- fixes #123
- closes #456
- related #789

Anything else reviewers should know.
Remove this section if not needed.
-->
2026-09-29 08:31:18 -05:00
Darren Schroeder f48edc35d9 fix(tui): builders no longer read piped streams, so slow streams show live (#19099)
## Description

@Bahex reported that `tui` blocked on stream input, "ranges like `1..`
are fine, but just passing it through `each {}` makes tui block. It
sometimes works, but if there is a sleep it always blocks."

Both shipped stream examples had this problem:

- `1.. | each {|n| sleep 100ms; $"tick ($n)"} | tui label --title
"ticks" | tui log | tui run` never painted.
- `1..100 | each {|n| sleep 50ms; $n } | tui progress --total 100 | tui
run` painted only after the whole stream had run, so the bar started at
100% and never moved.

This should be fixed now.

## User-facing changes (Release notes)

- Fixed `tui` commands blocking on slow or endless streams. A builder no
longer reads what is piped into it. The stream is read while `tui run`
is showing, so `1.. | each {|n| sleep 1sec; $n} | tui log | tui run`
shows each row as it arrives, and `tui progress` fed by a stream fills
in as the stream runs. This also works for streams piped into a child
list, such as `tui split [(tail -f app.log | tui log) (ls | tui
table)]`.
- Builders now always output a `tui` value. Piping a builder into
something other than another `tui` command (for example `1.. | tui table
| first`) no longer returns the stream's rows.
- A `tui` value saved with `let` reads its stream the first time it
runs. If that run read the stream to the end, later runs show the same
rows. A stream that was stopped early shows nothing on later runs.
- When a `tui run` closure, `tui bind`, button or menu hook returns
data, that data replaces the piped data for the rest of the run, and a
piped stream no longer adds rows to it.
- A stream keeps its newest 100,000 rows, dropping older rows 12,500 at
a time. Before, rows arriving after the first 100,000 were trimmed to
10,000.
- Piping data into a child list's builder when every widget in it has
`--data` is now an error instead of being ignored.

## Additional notes
<!--
Optional.

Examples:
- fixes #123
- closes #456
- related #789

Anything else reviewers should know.
Remove this section if not needed.
-->
2026-09-29 08:30:45 -05:00
Piepmatz 2459fdd134 Bump minor version (#19090) 0.116.0 2026-09-26 18:02:22 +02:00
Martin Kronberger ac000bbbae fix(completions): expand alias heads in place.command, keep completer warnings off the menu (#19085)
## Description

`place.command` (#19054) reads argv off the line, and the parser keeps
only the alias name there.
So `alias gco = git checkout` handed a completer `[gco, ma]`, while
0.115 handed `[git, checkout, ma]`; the carapace example in
`doc_config.nu` and the legacy `spans` input break the same way.
`Line::head` now expands an alias head from its definition, with the
lookup off inside the definition since `alias ls = ls -l` names itself.

Also: `commandline complete` no longer flushes completer warnings while
a completion source is running, which printed the `{|buffer, position|}`
deprecation over an open menu.
And the `arg_pair` splitting gets tests.

## User-facing changes (Release notes)

`$place.command` is the argv of the command the cursor is in, one shell
word per argument, even after a pipe, inside a closure or after a `;`:

```nu
$env.config.completions.external.completer = {|place|
  carapace $place.command.0 nushell ...$place.command | from json
}
```

An alias head is expanded, so `gco ma` hands the completer `[git,
checkout, ma]`.
The legacy `spans` input carries the same list, thus `foo [a b` is
`[foo, "[a b"]`, no longer `[foo, "[", a, " ", b]`.
Menu `source` deprecation warnings show after the line is handed back
instead of over the menu.

## Additional notes

Follow-up to #19054, whose release note this covers too. The reedline
side of #19053 is at nushell/reedline#1228
2026-09-25 16:35:40 +02:00
pickx 7de074cfe9 char: add forward slash/backslash. (#19082)
## Description
super simple PR that just adds `/` and `\` to `char` (and `char
--list`).

`(char whatever)` is sometimes more clear when used in string
interpolation (I needed backslash a few times).

however on a UNIX-like OS, `/` is available as the path separator, `\`
is absent from `char --list`. and other way around on Windows. so this
decouples path separators (which are, by definition, not portable) from
slashes

feel free to suggest shorter names, because these are kinda long.

(sidenote: many chars have multiple entries in `char --list`, one for
each alias, maybe aliases should be joined to a single one?)

## User-facing changes (Release notes)
Added forward slash and backslash to `char`, independent of the
underlying operating system.
2026-09-25 06:29:09 -05:00
John Costa bcefe6d654 fix(config): keep XDG config when there is no home directory (#19083)
## Description

Since the config reorganization in #18510, `resolve_paths` returned
`ConfigError::NoHomeDir` whenever the platform home lookup came back
empty, even after `XDG_CONFIG_HOME` had already resolved `config_home`.
`main.rs` then fell back to `NushellConfigDirs::empty()`, so a user
without a home directory lost their XDG config entirely.

A missing home is no longer fatal: `home_dir` is left empty (the same
"unset" value `NushellConfigDirs::empty()` already uses), and
`$nu.home-dir` becomes an error value in that case, which is how it
behaved before #18510 ("setting $nu.home-dir failed" / "Could not get
home directory"). `ConfigError::NoHomeDir` had no remaining users, so it
and its arm in `main.rs` are removed.

Tests:
- `test_resolve_paths_missing_home_keeps_xdg_config` replaces
`test_resolve_paths_missing_home_errors`: XDG set, no home, no platform
config dir; `config_home` resolves under XDG and `home_dir` is empty.
- `nu_home_dir_is_an_error_without_home` in
`tests/repl/test_config_path.rs` checks `$nu.home-dir` with an empty
home.

Both fail without the fix. `cargo fmt --all -- --check`, `cargo clippy
--workspace -- -D warnings -D clippy::unwrap_used` and `cargo test
--workspace` pass locally (macOS). I did not run it end to end as a
Linux user with no home directory, since macOS always resolves a home
through `getpwuid`.

## User-facing changes (Release notes)

Fixed an issue where Nushell ignored `XDG_CONFIG_HOME` for users without
a home directory. Config loads from `XDG_CONFIG_HOME` again, and
`$nu.home-dir` reports an error when there is no home directory.

## Additional notes

fixes #18902
2026-09-25 06:24:47 -05:00
Justin Ma 1a4cc5ad0c Fix nushell command docs build error (#19084)
Fix nushell command docs build error here:


https://github.com/nushell/nushell.github.io/actions/runs/36083011191/job/107908815597?pr=2246#step:5:32
2026-09-25 10:13:53 +08:00
Miles Wirht d7cb4e589e fixes for unified/completions in general (#19054)
<!--
Thanks for contributing to Nushell!

Before submitting, please read the contributing guide:
https://github.com/nushell/nushell/blob/main/CONTRIBUTING.md

This template helps reviewers understand your changes and allows us to
generate high-quality release notes.
-->

## Description

Fixes #19016 and #19053
<!--
Explain what this PR does and why.



This section is intentionally flexible:
- Describe the problem
- Explain your approach
- Include technical details if relevant

Good examples:
- "In this PR, I fixed..."
- "In this PR, I added support for..."
- "This change improves X by..."

Write as much or as little as needed for reviewers to understand your
changes.
-->

## User-facing changes (Release notes)
<!--
This section is used (mostly as-is) for https://www.nushell.sh/blog/

Describe how Nushell behavior changes from a user's perspective.
Do NOT describe internal Rust changes here.

Write in a release note style, for example:
- "Added support for..."
- "Fixed an issue where..."
- "Nushell now supports..."
- "Improved performance of..."

If your changes do NOT affect users (internal refactors, cleanup, etc.),
just write:
- "n/a"
- "nan"
- or similar

This tells us the change should not appear in the changelog.

Tips:
- Focus on observable behavior
- Include examples if helpful
- Keep it concise

You can:
- Write a short paragraph (will appear as a bullet point), OR
- Use headings (###) if your change needs more structure

Avoid writing things like:
- "In this PR, I refactored..."
- "This updates internal code..."

You may leave this blank until the PR is ready.
-->

## Additional notes
<!--
Optional.

Examples:
- fixes #123
- closes #456
- related #789

Anything else reviewers should know.
Remove this section if not needed.
-->
2026-09-23 19:48:35 -05:00
Darren Schroeder 3d91726885 fix lsp hover tooltip markdown (#19080)
<!--
Thanks for contributing to Nushell!

Before submitting, please read the contributing guide:
https://github.com/nushell/nushell/blob/main/CONTRIBUTING.md

This template helps reviewers understand your changes and allows us to
generate high-quality release notes.
-->

## Description

This PR fixes the lsp hover tooltip markdown so text isn't bolded
inappropriately


<!--
Explain what this PR does and why.

This section is intentionally flexible:
- Describe the problem
- Explain your approach
- Include technical details if relevant

Good examples:
- "In this PR, I fixed..."
- "In this PR, I added support for..."
- "This change improves X by..."

Write as much or as little as needed for reviewers to understand your
changes.
-->

## User-facing changes (Release notes)

Fix the lsp hover tooltip markdown

### Before
<img width="2090" height="1416" alt="Screenshot 2026-09-23 at 14-30-45"
src="https://github.com/user-attachments/assets/7158dcfa-6372-43e7-95f3-9d9f616cbde5"
/>

### After
<img width="2143" height="1195" alt="Screenshot 2026-09-23 at 14-43-38"
src="https://github.com/user-attachments/assets/92d44896-5b9e-437d-88a0-659cd7989507"
/>
<!--
This section is used (mostly as-is) for https://www.nushell.sh/blog/

Describe how Nushell behavior changes from a user's perspective.
Do NOT describe internal Rust changes here.

Write in a release note style, for example:
- "Added support for..."
- "Fixed an issue where..."
- "Nushell now supports..."
- "Improved performance of..."

If your changes do NOT affect users (internal refactors, cleanup, etc.),
just write:
- "n/a"
- "nan"
- or similar

This tells us the change should not appear in the changelog.

Tips:
- Focus on observable behavior
- Include examples if helpful
- Keep it concise

You can:
- Write a short paragraph (will appear as a bullet point), OR
- Use headings (###) if your change needs more structure

Avoid writing things like:
- "In this PR, I refactored..."
- "This updates internal code..."

You may leave this blank until the PR is ready.
-->

## Additional notes
<!--
Optional.

Examples:
- fixes #123
- closes #456
- related #789

Anything else reviewers should know.
Remove this section if not needed.
-->
2026-09-23 16:11:18 -05:00
mrhard9090andClaude Opus 5.5 0190232461 fix(idx search): don't cap matches at 200 per file (#19078)
`idx search` returns at most 200 matches per file, and `--limit` is
rounded up to whole files (`--limit 10` returns 200 rows, `--limit
100000` on one file with 1000 matches returns 200).

`stream_grep` builds `GrepSearchOptions` with `..Default::default()`, so
FFF's `max_matches_per_file` stays at its default of 200. FFF also only
checks `page_limit` after adding all matches of a file, so the result
grows in whole-file steps.

This PR sets `max_matches_per_file` to the requested limit, so a single
file can give all requested matches, and truncates the result to
`--limit`.

## User-facing changes (Release notes)

Fixed `idx search` returning at most 200 matches per file and ignoring
the exact `--limit` value.

## Tests + Formatting

- Added `idx_search_limit_is_not_capped_per_file` (250 matches in one
file, `--limit 1000` returns 250) and `idx_search_limit_is_exact`
(`--limit 10` returns 10). Both fail on `main` with 200 and pass with
this change.
- `cargo test -p nu-command --test tests -- commands::idx::`: 27 passed
- `cargo fmt --all -- --check`
- `cargo clippy -p nu-command -- -D warnings -D clippy::unwrap_used`

## Additional notes

Fixes #18930

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 12:55:45 -05:00
dependabot[bot]andDarren Schroeder fcd3512496 build(deps): bump serde-saphyr from 1.2.0 to 1.3.0 (#19071)
Bumps [serde-saphyr](https://github.com/bourumir-wyngs/serde-saphyr)
from 1.2.0 to 1.3.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/bourumir-wyngs/serde-saphyr/releases">serde-saphyr's
releases</a>.</em></p>
<blockquote>
<h2>1.3.0 Maintenance and performance release</h2>
<ul>
<li>Integer mapping keys now compare by their parsed numeric value for
duplicate detection and merge
resolution, including inside composite keys (<code>0xB</code> and
<code>11</code> compare equal). Deserializing to strings
preserves the original spelling; <code>FirstWins</code> and
<code>LastWins</code> retain the selected key's spelling
and associated value.</li>
<li>Composite keys containing null-like strings now preserve both the
key and its associated value;
for example, <code>{{&quot;null&quot;: 1}: 2}</code> now round-trips
correctly.</li>
<li>Null mapping keys now remain distinct from null-like string keys
during duplicate detection
and merge resolution, including inside composite keys.</li>
<li>Null detection now respects string tags and scalar styles,
preserving null-like strings such as
<code>!!str null</code>, including when deserializing to
<code>Option&lt;String&gt;</code>.</li>
<li>Explicit numeric tags (<code>!!int</code> and <code>!!float</code>)
now deserialize correctly through <code>deserialize_any</code>,
including quoted and block scalars.</li>
<li>Floating-point deserialization now rejects incompatible core tags;
for example,
<code>from_str::&lt;f64&gt;(&quot;!!str 1.5&quot;)</code> now returns an
error.</li>
<li>Serialization now escapes U+FFFE and U+FFFF as <code>\uFFFE</code>
and <code>\uFFFF</code>.</li>
<li>Serialization now preserves newline-only strings in literal block
scalars, including
<code>LitStr</code> and <code>LitString</code>, by using keep chomping
and retaining every empty line.</li>
<li>Updated the granit-parser revision to preserve document boundaries
after zero-indented root
literal and folded block scalars, including empty strings.</li>
<li>Preserve string scalars across YAML 1.1 readers (PR <a
href="https://redirect.github.com/bourumir-wyngs/serde-saphyr/issues/90">#90</a>,
thanks <a
href="https://github.com/NiklasRosenstein"><code>@​NiklasRosenstein</code></a>)</li>
<li>The version increased to 1.3 because it uses granit-parser 1.3, and
that is because granit-parser
added two methods to <code>Input</code> for performance improvements. As
serde-saphyr re-exports it,
the version number must be increased to 1.3 as well, even if no new
features are added to
this crate itself.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/bourumir-wyngs/serde-saphyr/blob/master/CHANGELOG.md">serde-saphyr's
changelog</a>.</em></p>
<blockquote>
<h2>1.3.0 Maintenance and performance release</h2>
<h3>Fixed</h3>
<ul>
<li>Integer mapping keys now compare by their parsed numeric value for
duplicate detection and merge
resolution, including inside composite keys (<code>0xB</code> and
<code>11</code> compare equal). Deserializing to strings
preserves the original spelling; <code>FirstWins</code> and
<code>LastWins</code> retain the selected key's spelling
and associated value.</li>
<li>Composite keys containing null-like strings now preserve both the
key and its associated value;
for example, <code>{{&quot;null&quot;: 1}: 2}</code> now round-trips
correctly.</li>
<li>Null mapping keys now remain distinct from null-like string keys
during duplicate detection
and merge resolution, including inside composite keys.</li>
<li>Null detection now respects string tags and scalar styles,
preserving null-like strings such as
<code>!!str null</code>, including when deserializing to
<code>Option&lt;String&gt;</code>.</li>
<li>Explicit numeric tags (<code>!!int</code> and <code>!!float</code>)
now deserialize correctly through <code>deserialize_any</code>,
including quoted and block scalars.</li>
<li>Floating-point deserialization now rejects incompatible core tags;
for example,
<code>from_str::&lt;f64&gt;(&quot;!!str 1.5&quot;)</code> now returns an
error.</li>
<li>Serialization now escapes U+FFFE and U+FFFF as <code>\uFFFE</code>
and <code>\uFFFF</code>.</li>
<li>Serialization now preserves newline-only strings in literal block
scalars, including
<code>LitStr</code> and <code>LitString</code>, by using keep chomping
and retaining every empty line.</li>
<li>Updated the granit-parser revision to preserve document boundaries
after zero-indented root
literal and folded block scalars, including empty strings.</li>
<li>Preserve string scalars across YAML 1.1 readers (PR <a
href="https://redirect.github.com/bourumir-wyngs/serde-saphyr/issues/90">#90</a>,
thanks <a
href="https://github.com/NiklasRosenstein"><code>@​NiklasRosenstein</code></a>)</li>
<li>The version increased to 1.3 because it uses granit-parser 1.3, and
that is because granit-parser
added two methods to <code>Input</code> for performance improvements. As
serde-saphyr re-exports it,
the version number must be increased to 1.3 as well, even if no new
features are added to
this crate itself.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/bourumir-wyngs/serde-saphyr/commit/93e634c680c4ae4bff6a0b8f841077868725547d"><code>93e634c</code></a>
Switching to 1.3.0</li>
<li><a
href="https://github.com/bourumir-wyngs/serde-saphyr/commit/b94505a24aaabb972401852ad06627c4d0d41dc2"><code>b94505a</code></a>
Miri fix</li>
<li><a
href="https://github.com/bourumir-wyngs/serde-saphyr/commit/aa4adada822e29cae1313a98b4e3e06d3146d3f6"><code>aa4adad</code></a>
We will need to go 1.3</li>
<li><a
href="https://github.com/bourumir-wyngs/serde-saphyr/commit/7a1646e4e87cb1e48bd7b3befbe6998f0261a8f1"><code>7a1646e</code></a>
Update CHANGELOG.md</li>
<li><a
href="https://github.com/bourumir-wyngs/serde-saphyr/commit/b17fc43fb5a266285dc0885b03703feeeac1d6e6"><code>b17fc43</code></a>
Extend the quoting check to signed lowercase 0o forms</li>
<li><a
href="https://github.com/bourumir-wyngs/serde-saphyr/commit/8aae8fe5cab41881440ca821d37b5a70a5662bb6"><code>8aae8fe</code></a>
Preserve string scalars across YAML 1.1 readers (<a
href="https://redirect.github.com/bourumir-wyngs/serde-saphyr/issues/190">#190</a>)</li>
<li><a
href="https://github.com/bourumir-wyngs/serde-saphyr/commit/de014b0b0b677128947d73edd2f24c2036e19712"><code>de014b0</code></a>
Bump rstest from 0.26.1 to 0.27.0 (<a
href="https://redirect.github.com/bourumir-wyngs/serde-saphyr/issues/191">#191</a>)</li>
<li><a
href="https://github.com/bourumir-wyngs/serde-saphyr/commit/bbd93a8a06eec4b61854178d0601e5582aa03137"><code>bbd93a8</code></a>
Switch to released granit parser.</li>
<li><a
href="https://github.com/bourumir-wyngs/serde-saphyr/commit/49ecf1b8d8086b1ec476453fe1264443673c37c6"><code>49ecf1b</code></a>
Next release start automatically on tag push</li>
<li><a
href="https://github.com/bourumir-wyngs/serde-saphyr/commit/6bcee83d22a8f4fc604e2ab05b8ce1727769895c"><code>6bcee83</code></a>
Fix pirate formatter test</li>
<li>Additional commits viewable in <a
href="https://github.com/bourumir-wyngs/serde-saphyr/compare/1.2.0...1.3.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=serde-saphyr&package-manager=cargo&previous-version=1.2.0&new-version=1.3.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2026-09-23 11:19:25 -05:00
dependabot[bot] de7c4d2f2f build(deps): bump taiki-e/install-action from 2.87.12 to 2.87.17 (#19068)
Bumps
[taiki-e/install-action](https://github.com/taiki-e/install-action) from
2.87.12 to 2.87.17.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/taiki-e/install-action/releases">taiki-e/install-action's
releases</a>.</em></p>
<blockquote>
<h2>2.87.17</h2>
<ul>
<li>
<p>Update <code>uv@latest</code> to 0.12.17.</p>
</li>
<li>
<p>Update <code>release-plz@latest</code> to 0.3.169.</p>
</li>
<li>
<p>Update <code>kingfisher@latest</code> to 2.5.0.</p>
</li>
<li>
<p>Update <code>kache@latest</code> to 0.25.0.</p>
</li>
<li>
<p>Update <code>git-cliff@latest</code> to 2.14.2.</p>
</li>
<li>
<p>Update <code>cargo-tarpaulin@latest</code> to 0.37.3.</p>
</li>
<li>
<p>Update <code>cargo-leptos@latest</code> to 0.3.9.</p>
</li>
</ul>
<h2>2.87.16</h2>
<ul>
<li>
<p>Update <code>uv@latest</code> to 0.12.16.</p>
</li>
<li>
<p>Update <code>release-plz@latest</code> to 0.3.168.</p>
</li>
<li>
<p>Update <code>protoc@latest</code> to 3.36.2.</p>
</li>
<li>
<p>Update <code>mise@latest</code> to 2026.9.11.</p>
</li>
</ul>
<h2>2.87.15</h2>
<ul>
<li>
<p>Update <code>syft@latest</code> to 1.52.0.</p>
</li>
<li>
<p>Update <code>mise@latest</code> to 2026.9.10.</p>
</li>
<li>
<p>Update <code>kingfisher@latest</code> to 2.4.0.</p>
</li>
<li>
<p>Update <code>kache@latest</code> to 0.23.1.</p>
</li>
<li>
<p>Update <code>coreutils@latest</code> to 0.12.0.</p>
</li>
<li>
<p>Update <code>cargo-nextest@latest</code> to 0.9.145.</p>
</li>
</ul>
<h2>2.87.14</h2>
<ul>
<li>
<p>Update <code>vacuum@latest</code> to 0.30.6.</p>
</li>
<li>
<p>Update <code>uv@latest</code> to 0.12.15.</p>
</li>
<li>
<p>Update <code>typos@latest</code> to 1.50.2.</p>
</li>
<li>
<p>Update <code>sccache@latest</code> to 0.18.0.</p>
</li>
<li>
<p>Update <code>oxfmt@latest</code> to 1.83.0.</p>
</li>
<li>
<p>Update <code>mise@latest</code> to 2026.9.9.</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md">taiki-e/install-action's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<p>All notable changes to this project will be documented in this
file.</p>
<p>This project adheres to <a href="https://semver.org">Semantic
Versioning</a>.</p>
<!-- raw HTML omitted -->
<h2>[Unreleased]</h2>
<h2>[2.87.18] - 2026-09-22</h2>
<ul>
<li>
<p>Update <code>oxfmt@latest</code> to 1.84.0.</p>
</li>
<li>
<p>Update <code>mise@latest</code> to 2026.9.12.</p>
</li>
<li>
<p>Update <code>kache@latest</code> to 0.26.3.</p>
</li>
<li>
<p>Update <code>cargo-tarpaulin@latest</code> to 0.37.4.</p>
</li>
<li>
<p>Update <code>cargo-rdme@latest</code> to 2.2.3.</p>
</li>
</ul>
<h2>[2.87.17] - 2026-09-20</h2>
<ul>
<li>
<p>Update <code>uv@latest</code> to 0.12.17.</p>
</li>
<li>
<p>Update <code>release-plz@latest</code> to 0.3.169.</p>
</li>
<li>
<p>Update <code>kingfisher@latest</code> to 2.5.0.</p>
</li>
<li>
<p>Update <code>kache@latest</code> to 0.25.0.</p>
</li>
<li>
<p>Update <code>git-cliff@latest</code> to 2.14.2.</p>
</li>
<li>
<p>Update <code>cargo-tarpaulin@latest</code> to 0.37.3.</p>
</li>
<li>
<p>Update <code>cargo-leptos@latest</code> to 0.3.9.</p>
</li>
</ul>
<h2>[2.87.16] - 2026-09-19</h2>
<ul>
<li>
<p>Update <code>uv@latest</code> to 0.12.16.</p>
</li>
<li>
<p>Update <code>release-plz@latest</code> to 0.3.168.</p>
</li>
<li>
<p>Update <code>protoc@latest</code> to 3.36.2.</p>
</li>
<li>
<p>Update <code>mise@latest</code> to 2026.9.11.</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/taiki-e/install-action/commit/94c31af3204a9f15ab40b35ad084410b905bbc73"><code>94c31af</code></a>
Release 2.87.17</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/475671fc114659712ce2ff4c1d94a927df34acaa"><code>475671f</code></a>
Update <code>uv@latest</code> to 0.12.17</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/eed0e36340db98284aecb38cec932175e66e08ea"><code>eed0e36</code></a>
Update <code>release-plz@latest</code> to 0.3.169</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/28baa71610ff9c6ca6f9c2238420167a40febe2d"><code>28baa71</code></a>
Update <code>kingfisher@latest</code> to 2.5.0</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/2588b3fa536b03dd80a635cd335014a5a7fbd06c"><code>2588b3f</code></a>
Update <code>kache@latest</code> to 0.25.0</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/0d4ca4ed781961f5ab749f0c9f822ff33177e1bf"><code>0d4ca4e</code></a>
Update <code>git-cliff@latest</code> to 2.14.2</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/b96f11eb0c9f8820d621c4b53579f5ad6045195e"><code>b96f11e</code></a>
Update <code>cargo-tarpaulin@latest</code> to 0.37.3</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/18f77226f9e7299acf999113898461dc73e12132"><code>18f7722</code></a>
Update <code>cargo-leptos@latest</code> to 0.3.9</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/9114bf4d891761788c546334fd37538eae1bf8b3"><code>9114bf4</code></a>
Release 2.87.16</li>
<li><a
href="https://github.com/taiki-e/install-action/commit/bef0ddf2a7e45573907045d6d2e764dc0b552ec4"><code>bef0ddf</code></a>
Update <code>uv@latest</code> to 0.12.16</li>
<li>Additional commits viewable in <a
href="https://github.com/taiki-e/install-action/compare/3f74d7c16a4242f1c95561e98edc25d36adb4375...94c31af3204a9f15ab40b35ad084410b905bbc73">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=taiki-e/install-action&package-manager=github_actions&previous-version=2.87.12&new-version=2.87.17)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-23 07:24:06 -05:00
mrhard9090andClaude Opus 5.5 facdb3bc25 fix(rm): answer one --interactive prompt per key press on Windows (#19067)
On Windows, `rm --interactive` consumes one key press for two prompts:
answering `n` for one file also answers `n` for the next.

The confirmation prompt (`get_interactive_confirmation` in
`filesystem/util.rs`) reads crossterm key events but never checks
`KeyEvent::kind`. On Windows, crossterm reports both a `Press` and a
`Release` event for every key, so the prompt handles the press, returns,
and the leftover `Release` of the same key answers the next file's
prompt. Other platforms only report presses, which is why this is
Windows-only. The raw-mode prompt came in with #17420, which added the
same release filtering to `input list` but not here.

This PR only handles `KeyEventKind::Press`, like `nu-explore` and `input
list` already do. `Repeat` is ignored too, so holding a key can't answer
several prompts.

Still reproducible on current `main` (9c9f93c72). I checked it on
Windows 11 by running `rm --interactive a.txt b.txt c.txt` in a console
and injecting real key-down/key-up events (`WriteConsoleInput`) for `y`,
`n`, `n`:

- before: 2 files deleted (the release of `y` answered the second
prompt)
- after: 1 file deleted, one answer per key press

(same result on repeated runs)

## User-facing changes (Release notes)

Fixed `rm --interactive` on Windows answering two prompts with a single
key press.

## Tests + Formatting

- No automated test: the bug needs real console key events, which the
test harness can't produce. Verified manually as described above.
- `cargo test -p nu-command --test tests -- commands::rm::`: 34 passed
- `cargo fmt --all -- --check`
- `cargo clippy -p nu-command --all-targets -- -D warnings -D
clippy::unwrap_used`

## Additional notes

Fixes #19062

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 07:23:46 -05:00
mrhard9090andClaude Opus 5.5 356e1c8734 fix(table): keep markdown and restructured headers off the separator (#19066)
With `$env.config.table.header_on_separator = true`, the `markdown` and
`restructured` themes drew the header inside the top border, which
produces invalid markup:

```
|-#-|-a-|-b-|
| 0 | 1 | 2 |
```

```
=#= =a= =b=
 0   1   2
=== === ===
```

These two themes exist to emit markup that other tools parse, so as
suggested in #13161, `configure_table` now ignores `header_on_separator`
for them. Every table goes through `configure_table` (general and
`--expand`), so this covers both. Other themes are unchanged.

After:

```
| # | a | b |
|---|---|---|
| 0 | 1 | 2 |
```

`wrap_and_truncate_differ_for_every_theme_with_header_on_separator`
(from #18979) no longer lists `markdown` and `restructured`. With the
header in its own row, wrapping and truncating give identical output for
this data on every theme (e.g. `rounded` with `header_on_separator =
false`), because overflowing columns are replaced with `...`. The two
only differ in the header-on-border layout, which these themes no longer
use.

## User-facing changes (Release notes)

`table --theme markdown` and `table --theme restructured` (and the
matching `$env.config.table.mode` values) now always produce valid
Markdown / reStructuredText, even when
`$env.config.table.header_on_separator` is enabled.

## Tests + Formatting

- Added `table_markup_themes_ignore_header_on_separator`. It fails
without this change (`|-#-|-a-|-b-|`) and passes with it.
- `cargo test -p nu-command --test tests -- commands::table::`: 492
passed
- `cargo fmt --all -- --check`
- `cargo clippy -p nu-table -p nu-command --all-targets -- -D warnings
-D clippy::unwrap_used`

## Additional notes

Fixes #13161

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 06:57:15 -05:00
mrhard9090andClaude Opus 5.5 6b3b6a45ff fix(rm): skip . and .. glob matches on Windows (#19065)
`rm` filters out the special `.` and `..` entries that a leading-dot
glob such as `.*` matches, but the check only compared against `/`:

```rust
if name.ends_with("/.") || name.ends_with("/..") {
```

On Windows, glob results use `\` (e.g. `C:\work\parent\child\..`), so
the guard never matched. As a result, `rm -r parent/child/.*` deleted
`child` (via `child\.`) and `parent` (via `child\..`), including
unrelated files. Running `rm -r .*` inside a directory also tried to
delete the current and parent directories and failed with "being used by
another process" errors.

The check now looks at the final path component, splitting on every
platform separator (`std::path::is_separator`), so it matches both `/`
and `\` on Windows. `Path::components()` can't be used here because it
normalizes away a trailing `.`.

Verified on Windows:
- Before: `rm -r parent/child/.*` removed all of `parent`.
- After: only the dotfiles in `child` are removed. `child`, `parent` and
their other contents are kept.

## User-facing changes (Release notes)

Fixed `rm` with leading-dot globs such as `rm -r dir/.*` on Windows
deleting `dir` and its parent directory. The `.` and `..` entries are
now skipped on all platforms.

## Tests + Formatting

- Added `rm_leading_dot_glob_skips_current_and_parent_dirs`. It fails on
Windows without this change and passes with it.
- `cargo test -p nu-command --test tests -- commands::rm::`: 35 passed
- `cargo fmt --all -- --check`
- `cargo clippy -p nu-command --all-targets -- -D warnings -D
clippy::unwrap_used`

## Additional notes

Fixes #13494

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 06:56:56 -05:00
Martin Kronberger fcbb8c0ff3 feat(reedline): bump to db34d84 (#19077)
## Description

Bumps reedline to `db34d84`, seven commits past the previous pin
`c9e7035`:

- nushell/reedline#1214 `feat(engine)!: switch between edit modes with
one SwitchMode event`
- nushell/reedline#1226 `fix(engine): Up and Down report whether they
moved anything`
- nushell/reedline#1225 `refactor(editor): give paste-after the same
shape as paste-before`
- nushell/reedline#1223 `chore(deps): drop fd-lock, itertools, thiserror
and unicase`
- nushell/reedline#1209 `feat(menu): let a menu close at the end of the
word it was opened for`
- nushell/reedline#1206 `fix(painter): trust the measured cursor row
over a short terminal size`
- nushell/reedline#1222 `fix(ci): change typos to v1`

#1214 replaces `ViChangeMode` and `HelixChangeMode` with one
`SwitchMode` event and gives vi visual mode its own keybinding table and
cursor slot.
Both old names still parse and mean the same, so existing configs keep
working.

## User-facing changes (Release notes)

- Added the `SwitchMode` keybinding event, `{ send: SwitchMode, mode:
vi_normal }`, taking the same mode names `mode` takes on a keybinding. A
switch into the mode already active reports itself inapplicable, so an
`until` list falls through to its next event where it used to stop;
`ViChangeMode` and `HelixChangeMode` share that.
- Added `vi_visual` as a keybinding mode and
`$env.config.cursor_shape.vi_visual`, which follows `vi_normal` while
left on `inherit`. Bindings for `vi_normal` no longer apply in visual
mode.
2026-09-23 06:50:36 -05:00
dependabot[bot] 9d553e1f7d build(deps): bump rstest from 0.26.1 to 0.27.0 (#19070)
Bumps [rstest](https://github.com/la10736/rstest) from 0.26.1 to 0.27.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/la10736/rstest/releases">rstest's
releases</a>.</em></p>
<blockquote>
<h2>v0.27.0</h2>
<h2>[0.27.0] 2026/9/6</h2>
<h3>Changed</h3>
<ul>
<li>Bump msrv to 1.85.0 both for <code>rstest</code> and
<code>rstest_reuse</code></li>
<li>Disabled default features of <code>futures-util</code></li>
</ul>
<h3>Added</h3>
<ul>
<li>Doc comments before <code>#[values(...)]</code> entries can be used
to override the generated matrix
test names (both for the legacy <code>arg =&gt; [..]</code> syntax and
the new attribute form).
See <a
href="https://redirect.github.com/la10736/rstest/pull/321">#321</a>
thanks to <a
href="https://github.com/orhun"><code>@​orhun</code></a>.</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Use fully-qualified <code>core</code> import. See <a
href="https://redirect.github.com/la10736/rstest/pull/336">#336</a>.</li>
<li>Fix <code>mut</code> arguments failing to compile with
<code>#[trace]</code>. See <a
href="https://redirect.github.com/la10736/rstest/pull/345">#345</a>
thanks to <a
href="https://github.com/super-cooper"><code>@​super-cooper</code></a>.</li>
<li>Fix compilation under bazel by upgrading proc-macro-crate to
3.4.0.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/la10736/rstest/blob/master/CHANGELOG.md">rstest's
changelog</a>.</em></p>
<blockquote>
<h2>[0.27.0] 2026/9/6</h2>
<h3>Changed</h3>
<ul>
<li>Bump msrv to 1.85.0 both for <code>rstest</code> and
<code>rstest_reuse</code></li>
<li>Disabled default features of <code>futures-util</code></li>
</ul>
<h3>Added</h3>
<ul>
<li>Doc comments before <code>#[values(...)]</code> entries can be used
to override the generated matrix
test names (both for the legacy <code>arg =&gt; [..]</code> syntax and
the new attribute form).
See <a
href="https://redirect.github.com/la10736/rstest/pull/321">#321</a>
thanks to <a
href="https://github.com/orhun"><code>@​orhun</code></a>.</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Use fully-qualified <code>core</code> import. See <a
href="https://redirect.github.com/la10736/rstest/pull/336">#336</a>.</li>
<li>Fix <code>mut</code> arguments failing to compile with
<code>#[trace]</code>. See <a
href="https://redirect.github.com/la10736/rstest/pull/345">#345</a>
thanks to <a
href="https://github.com/super-cooper"><code>@​super-cooper</code></a>.</li>
<li>Fix compilation under bazel by upgrading proc-macro-crate to
3.4.0.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/la10736/rstest/commit/59cd3d1919fc471922297606b8819af4e5bfd472"><code>59cd3d1</code></a>
Release 0.27.0</li>
<li><a
href="https://github.com/la10736/rstest/commit/0821ebc65d0c8434f0d9b7773a23043549313426"><code>0821ebc</code></a>
test: Add E2E test for mut arguments with #[trace]</li>
<li><a
href="https://github.com/la10736/rstest/commit/509ffef32538324bd75d0addf49ee7e0296bff81"><code>509ffef</code></a>
fix: <code>mut</code> arguments with <code>#[trace]</code></li>
<li><a
href="https://github.com/la10736/rstest/commit/043437d63956f1c9e3f6284c4463169cb484cd3b"><code>043437d</code></a>
fix: Resolve clippy warnings and truncate long test project names</li>
<li><a
href="https://github.com/la10736/rstest/commit/9aa8d1a3c51627ec87d0620abda9707488a6f8fe"><code>9aa8d1a</code></a>
fix: Suppress nightly cargo lints in test scaffolding</li>
<li><a
href="https://github.com/la10736/rstest/commit/3d3c76c4fb7215aae92008e390b7fb618ecd5522"><code>3d3c76c</code></a>
fix: Bump rstest_test MSRV to 1.85 and mark as unpublished</li>
<li><a
href="https://github.com/la10736/rstest/commit/d9ae990e323b6910d39364d94876a86b003b4729"><code>d9ae990</code></a>
chore: Add changelog entry</li>
<li><a
href="https://github.com/la10736/rstest/commit/05d4b1a817c1ee4684e1cff728d805f3a647fc0b"><code>05d4b1a</code></a>
fix: Use fully-qualified core import</li>
<li><a
href="https://github.com/la10736/rstest/commit/6da56a112e65b96685e06be41f8d98a84a6e7452"><code>6da56a1</code></a>
Bump msrv to 1.85 also for rstest_reuse (<a
href="https://redirect.github.com/la10736/rstest/issues/342">#342</a>)</li>
<li><a
href="https://github.com/la10736/rstest/commit/1e9963bbc1a0feff27afdd1c6cec25d00c96d607"><code>1e9963b</code></a>
Add CLAUDE.md for Claude Code guidance (<a
href="https://redirect.github.com/la10736/rstest/issues/340">#340</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/la10736/rstest/compare/v0.26.1...v0.27.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rstest&package-manager=cargo&previous-version=0.26.1&new-version=0.27.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-23 06:40:25 -05:00
dependabot[bot] 28a43fda41 build(deps): bump crate-ci/typos from 1.50.1 to 1.50.2 (#19069)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.50.1 to
1.50.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/crate-ci/typos/releases">crate-ci/typos's
releases</a>.</em></p>
<blockquote>
<h2>v1.50.2</h2>
<h2>[1.50.2] - 2026-09-15</h2>
<h3>Fixes</h3>
<ul>
<li>Don't panic when files being examined are removed</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/crate-ci/typos/blob/main/CHANGELOG.md">crate-ci/typos's
changelog</a>.</em></p>
<blockquote>
<h1>Change Log</h1>
<p>All notable changes to this project will be documented in this
file.</p>
<p>The format is based on <a href="https://keepachangelog.com/">Keep a
Changelog</a>
and this project adheres to <a href="https://semver.org/">Semantic
Versioning</a>.</p>
<!-- raw HTML omitted -->
<h2>[Unreleased] - ReleaseDate</h2>
<h2>[1.50.2] - 2026-09-15</h2>
<h3>Fixes</h3>
<ul>
<li>Don't panic when files being examined are removed</li>
</ul>
<h2>[1.50.1] - 2026-09-01</h2>
<h3>Fixes</h3>
<ul>
<li>Don't correct <code>asend</code> in Python code</li>
</ul>
<h2>[1.50.0] - 2026-08-28</h2>
<h3>Features</h3>
<ul>
<li>Updated the dictionary with the <a
href="https://redirect.github.com/crate-ci/typos/issues/1587">August
2026</a> changes</li>
</ul>
<h2>[1.49.1] - 2026-08-27</h2>
<h3>Fixes</h3>
<ul>
<li>Don't correct the brand name <code>HashiCorp</code></li>
</ul>
<h2>[1.49.0] - 2026-08-03</h2>
<h3>Features</h3>
<ul>
<li>Updated the dictionary with the <a
href="https://redirect.github.com/crate-ci/typos/issues/1573">July
2026</a> changes</li>
</ul>
<h2>[1.48.0] - 2026-06-30</h2>
<h3>Features</h3>
<ul>
<li>Updated the dictionary with the <a
href="https://redirect.github.com/crate-ci/typos/issues/1562">June
2026</a> changes</li>
</ul>
<h2>[1.47.2] - 2026-06-04</h2>
<h3>Fixes</h3>
<ul>
<li>Don't correct <code>inferrable</code></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/crate-ci/typos/commit/512fc24f32f44ab01972217aaaf3dc86ec234d53"><code>512fc24</code></a>
chore: Release</li>
<li><a
href="https://github.com/crate-ci/typos/commit/982cd9f770e6a9c97bfc285127eb85a6f438fca3"><code>982cd9f</code></a>
docs: Update changelog</li>
<li><a
href="https://github.com/crate-ci/typos/commit/6edd83eb2e896de329eba6b6aef70bd227b982c2"><code>6edd83e</code></a>
Merge pull request <a
href="https://redirect.github.com/crate-ci/typos/issues/1617">#1617</a>
from nightah/fix-1444-walk-canonicalize-panic</li>
<li><a
href="https://github.com/crate-ci/typos/commit/160d79280571abaefdec19d48d108457d85b45fd"><code>160d792</code></a>
fix(cli): Don't panic when canonicalize fails mid-walk</li>
<li><a
href="https://github.com/crate-ci/typos/commit/25761c7bf3ea578c9b0ce80fdea933caa3134ee2"><code>25761c7</code></a>
test(cli): Show panic when canonicalize fails mid-walk</li>
<li>See full diff in <a
href="https://github.com/crate-ci/typos/compare/d43b6c087ac471e2ea7b8af622ff15f05c0c365b...512fc24f32f44ab01972217aaaf3dc86ec234d53">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=crate-ci/typos&package-manager=github_actions&previous-version=1.50.1&new-version=1.50.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-23 06:39:47 -05:00
Charlie 9c9f93c727 fix(parser): reject unexpected semicolons in list literals and patterns (#19063)
## Description

List literals such as `[1; 2]` previously evaluated to `[1]`, silently
discarding content after the semicolon. The same issue affected `match`
list patterns and could cause incorrect matches.

In this PR, I added an error check that reports parse error for
unexpected semicolon separators in both list literals and list patterns,
with guidance to use commas or whitespace instead.

It also aligns the NUON table grammar with the documented requirement
for at least one data row and marks the header-only table parsing bug as
fixed.

Note: this version also rejects `[;1]`, `[1;]`, and `[;]`. I can't find
rules in the NUON spec that allow leading or trailing semicolons. The
parser now rejects all semicolon separators in list literals but I think
this should be discussed further in #11631 before merging, since it may
break existing code that relies on the current behavior.

## User-facing changes (Release notes)

List literals and `match` list patterns now reject unexpected semicolon
separators instead of silently accepting malformed input or discarding
subsequent content.

Header-only table syntax such as `[[a b];]` now also produces a parse
error.

## Additional notes

Fixes #11631.

Related: the semicolon discussion in #14188 and the NUON specification
added in #18925.

AI assistance (GPT-6 Astra) was used to investigate related issues and
add regression tests. The final review and writing of this PR were done
by myself.
2026-09-22 12:21:42 -05:00
3e080262cc Add a nu-dap crate and nu --dap: a Debug Adapter Protocol server for Nushell (#18738)
## Description

This PR adds **`crates/nu-dap`**, a [Debug Adapter Protocol][dap] (DAP)
server
for Nushell, and wires it into the CLI as **`nu --dap`** (behind a new
`dap`
feature, enabled by default like `lsp`/`mcp`). It embeds the engine and
speaks
DAP over stdio, so any DAP-capable editor — VS Code, Zed, Neovim — can
debug a
`.nu` script.

This is the follow-up the team suggested to #18708 ("a separate PR … to
add a
nu-dap crate … `nu --dap` or something similar"). It builds directly on
that
change: because the `Debugger` callbacks now receive `&Stack`, the
adapter reads
locals and `$env` straight from the real stack rather than
reconstructing them.

What it supports today:
- breakpoints, conditional breakpoints, logpoints, exception
breakpoints;
- stepping (`continue`/`next`/`stepIn`/`stepOut`/`pause`), including
stepping
  through pipeline stages;
- variable inspection across scopes (Locals, Pipeline, Globals,
Registers,
  Process) with lazy hydration and no depth cap;
- watch/`evaluate` expressions (via a separate scratch engine);
- entry points: `def main`, a chosen function for `main`-less scripts,
or
  top-level;
- recorded time-travel (step-back / reverse-continue).

Design notes for reviewers:
- The public API is intentionally tiny: `run_stdio()` (what `nu --dap`
calls),
`serve(reader, writer)` (a transport-agnostic seam), and the `dap`
wire-format
  module. Everything else is `pub(crate)`.
- The CLI wiring mirrors `--lsp`/`--mcp` exactly (feature-gated flag,
plumbed
through the arg parser); `main.rs` hands off to `run_stdio()` early and
  returns.
- The adapter runs a server thread and the eval thread over a shared
`DebugState`; the server never touches `EngineState.debugger` (deadlock
rule).
  See `crates/nu-dap/README.md` for the architecture.

[dap]: https://microsoft.github.io/debug-adapter-protocol/

## User-facing changes (Release notes)

Added a Debug Adapter Protocol server to Nushell. Running `nu --dap`
starts a
DAP server over stdio, so any DAP-capable editor (VS Code, Zed, Neovim,
…) can
debug `.nu` scripts — set breakpoints (including conditional breakpoints
and
logpoints), step through code and pipelines, inspect variables and
`$env`,
evaluate watch expressions, and even step backward through a recorded
timeline.

## Additional notes

- Related: #18708 (the `&Stack`-in-callbacks change this builds on).
- The `dap` feature is in the `default` set (like `lsp`/`mcp`). Happy to
make it
  opt-in instead if you'd prefer — it's a one-line change.
- Tests: `cargo test -p nu-dap` runs 19 unit + 25 DAP integration tests
(the
integration tests spawn the built binary and drive it over the real
protocol).
  `toolkit check pr` is clean.
- The adapter is launch-only (no `attach`); `crates/nu-dap/README.md`
has a full
  capability table (supported / not-yet).
- User docs for nushell.github.io will follow after merge.
- A VS Code extension that drives `nu --dap` is available for trying it
out
  (happy to link it in a comment).

<!-- gk-ai-analysis-start:7392a93a775934859a0a3c070f9ce7c7e3b4bc04 -->
<!--
gk-ai-analysis-data:eyJzdW1tYXJ5IjoiQWRkcyBhIERlYnVnIEFkYXB0ZXIgUHJvdG9jb2wgKERBUCkgc2VydmVyIHRvIE51c2hlbGwgdmlhIGEgbmV3IGBudS1kYXBgIGNyYXRlIGFuZCB3aXJlcyBpdCBpbnRvIHRoZSBDTEkgYXMgYG51IC0tZGFwYC4iLCJrZXlJbnNpZ2h0cyI6W3sidGl0bGUiOiJOZXcgYGRhcGAgY3JhdGUgYW5kIGZlYXR1cmUgYWRkZWQiLCJkZXNjcmlwdGlvbiI6IlRoZSBgbnUtZGFwYCBjcmF0ZSBpcyBpbnRyb2R1Y2VkIGFzIGFuIG9wdGlvbmFsIGRlcGVuZGVuY3kgYW5kIGFkZGVkIHRvIHRoZSBgZGVmYXVsdGAgYW5kIGBmdWxsYCBmZWF0dXJlIHNldHMsIG1pcnJvcmluZyB0aGUgZXhpc3RpbmcgYGxzcGAgYW5kIGBtY3BgIGZlYXR1cmVzLiIsInNldmVyaXR5IjoibWVkaXVtIiwiZmlsZVBhdGgiOiJDYXJnby50b21sIiwibGluZVN0YXJ0Ijo0MTUsImxpbmVFbmQiOjQxNX0seyJ0aXRsZSI6IkNMSSBmbGFnIGZvciBEZWJ1ZyBBZGFwdGVyIFByb3RvY29sIiwiZGVzY3JpcHRpb24iOiJBZGRlZCBhIG5ldyBgLS1kYXBgIHN3aXRjaCB0byB0aGUgQ0xJLCBmYWxsaW5nIHVuZGVyIHRoZSBgSWRlYCBjYXRlZ29yeSwgYWxsb3dpbmcgdXNlcnMgdG8gc3RhcnQgdGhlIERBUCBzZXJ2ZXIgb3ZlciBzdGRpby4iLCJzZXZlcml0eSI6Im1lZGl1bSIsImZpbGVQYXRoIjoic3JjL2NvbW1hbmQucnMiLCJsaW5lU3RhcnQiOjM0MywibGluZUVuZCI6MzUwfSx7InRpdGxlIjoiRWFybHkgZXhpdCBmb3IgREFQIHNlcnZlciBpbiBtYWluIiwiZGVzY3JpcHRpb24iOiJJZiBgbnUgLS1kYXBgIGlzIGNhbGxlZCwgdGhlIHNoZWxsIGhhbmRzIG9mZiBleGVjdXRpb24gdG8gdGhlIERBUCBzZXJ2ZXIncyBgcnVuX3N0ZGlvKClgIGFuZCByZXR1cm5zIGltbWVkaWF0ZWx5LCBwcmV2ZW50aW5nIG5vcm1hbCBzaGVsbCBleGVjdXRpb24gYW5kIHByZXNlcnZpbmcgcHJvY2VzcyBzdGRpbyBmb3IgdGhlIHByb3RvY29sLiIsInNldmVyaXR5IjoiaGlnaCIsImZpbGVQYXRoIjoic3JjL21haW4ucnMiLCJsaW5lU3RhcnQiOjE0MiwibGluZUVuZCI6MTUwfSx7InRpdGxlIjoiU3RhbmRhbG9uZSBzdGRpbyBydW5uZXIgYW5kIG91dHB1dCBjYXB0dXJlIiwiZGVzY3JpcHRpb24iOiJUaGUgYHJ1bl9zdGRpb2AgZnVuY3Rpb24gc2V0cyB1cCB0aGUgcnVzdGxzIGNyeXB0byBwcm92aWRlciBhbmQgZGV0YWNoZXMgY2hpbGQgc3RkaW4gd2hpbGUgY2FwdHVyaW5nIHNjcmlwdCBzdGRvdXQvc3RkZXJyIHRvIHByZXZlbnQgc2NyaXB0IG91dHB1dCBmcm9tIGNvcnJ1cHRpbmcgdGhlIERBUCBKU09OIHN0cmVhbS4iLCJzZXZlcml0eSI6Im1lZGl1bSIsImZpbGVQYXRoIjoiY3JhdGVzL251LWRhcC9zcmMvbGliLnJzIiwibGluZVN0YXJ0Ijo3MSwibGluZUVuZCI6ODl9XSwiaXNzdWVzIjpbXSwic3VnZ2VzdGlvbnMiOltdLCJzZWN1cml0eSI6W119
-->
<!-- gk-ai-analysis-end:7392a93a775934859a0a3c070f9ce7c7e3b4bc04 -->

<!-- gitkraken-review-badge-begin -->
---
<a
href="https://gitkraken.dev/review/github/nushell/nushell/pull/18738?source=pr_review_chip">
  <picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://gitkraken.dev/images/figures/gitkraken-review-badge-dark.svg">
<img
src="https://gitkraken.dev/images/figures/gitkraken-review-badge-light.svg"
alt="Open with GitKraken">
  </picture>
</a>
<!-- gitkraken-review-badge-end -->

---------

Co-authored-by: Ronald van Helden <ronald.van.helden@wortell.nl>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Tim 'Piepmatz' Hesse <git+github@cptpiepmatz.de>
2026-09-22 09:56:25 -05:00
mikehasaandClaude Opus 4.8 c4dad02d69 fix(std/dt): correct datetime-diff day count when borrowing a month (#19064)
## Description

Fixes a day-count error in `std dt datetime-diff`. When the subtraction
needs to borrow a month, `borrow-month` added the number of days in the
**later** date's own month instead of the month actually being crossed,
derived February's length from a year *difference* rather than a real
calendar year, and only reached the February branch when the later date
itself was in February (not March).

```nushell
# before
dt datetime-diff 2021-03-01T00:00:00Z 2021-01-15T00:00:00Z
# => { month: 1, day: 17, ... }   ("1month 17days")

# after
# => { month: 1, day: 14, ... }   ("1month 14days")
```

The span is 45 days = 1 month (Jan 15 → Feb 15) + 14 days; the old code
added March's 31 days where February's 28 belonged, overstating the
count by 3. `borrow-month` now keys off the month *before* the later
date (wrapping `1 → 12` and decrementing the year), so the February
branch uses the crossed month's real calendar year. Every corrected
value round-trips exactly (`earlier + diff == later`).

## User-facing changes (Release notes)

- Fixed an issue where `std dt datetime-diff` reported an incorrect day
count when the difference crossed a month boundary — for example, the
difference between `2021-01-15` and `2021-03-01` was `1month 17days`
instead of the correct `1month 14days`. This also corrects the
standard-library startup banner, which formats its uptime with
`datetime-diff` via `pretty-print-duration`.

## Additional notes

Tests: updated the two `@example` outputs in `std/dt/mod.nu` and the
`carry_ripples` / `banner_math_with_ms_us_ns` assertions in
`test_dt.nu`, and added regression tests
`borrow_month_uses_crossed_month` (non-leap February) and
`borrow_february_uses_real_leap_year` (leap February). Verified with the
full `nu-std` test suite (`testing run-tests --path crates/nu-std`, 235
tests, all passing) plus a direct sanity check.

Developed with assistance from Claude Code; reviewed & validated by me.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-22 09:03:48 -05:00
dependabot[bot] 40c9172017 build(deps): bump aws-config from 1.10.0 to 1.12.0 (#18994)
Bumps [aws-config](https://github.com/smithy-lang/smithy-rs) from 1.10.0
to 1.12.0.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/smithy-lang/smithy-rs/commits">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=aws-config&package-manager=cargo&previous-version=1.10.0&new-version=1.12.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-22 09:41:12 +08:00
Darren Schroeder af40010969 feat(update cells): add --recursive flag to visit nested leaf values (#19060)
## Description

`update cells` runs its closure once per cell. That is the right default
for a table, where a cell holding a record or list is a value in its own
right. It is less helpful for the other common input, a nested record
from `open config.toml` or `http get`, where "do X to every value in
here" means every leaf:

```nushell
# before: the closure sees {c: 2, d: [3 4]} as one value
{a: 1, b: {c: 2, d: [3 4]}} | update cells { $in * 10 }
# => error, or a hand-written recursive def
```

In this PR I added `--recursive` (`-r`). With the flag, nested records
and lists are descended into and the closure runs on each leaf value;
without it, nothing changes.

```nushell
# after
{a: 1, b: {c: 2, d: [3 4]}} | update cells -r { $in * 10 }
# => {a: 10, b: {c: 20, d: [30, 40]}}

# --columns still selects top-level columns; recursion happens under them
[[a b]; [1 {c: 2}] [3 {c: 4}]] | update cells -r -c [b] { $in * 10 }
# => [[a, b]; [1, {c: 20}], [3, {c: 40}]]
```


## User-facing changes (Release notes)

`update cells` has a new `--recursive` (`-r`) flag. With it, the closure
runs on every leaf value inside nested records and lists instead of on
the containing cell as a whole. The default behavior is unchanged.

## Additional notes
<!--
Optional.

Examples:
- fixes #123
- closes #456
- related #789

Anything else reviewers should know.
Remove this section if not needed.
-->
2026-09-21 10:56:35 -05:00
Darren Schroeder a3af2e300b fix(flatten): keep nested fields that collide with top-level columns (#19059)
## Description

`flatten` lifts the fields of a nested record up to the top level and,
when a lifted field has the same name as an existing top-level column,
is supposed to rename it to `<parent>_<field>`. That rename only worked
in one field order.

```nushell
# before
{a: {d: 1}, d: 3} | flatten    # => [[d]; [3]]        nested d is lost
{d: 3, a: {d: 1}} | flatten    # => [[d, a_d]; [3, 1]] correct
```
In this PR the set is computed unconditionally.

```nushell
# after
{a: {d: 1}, d: 3} | flatten    # => [[a_d, d]; [1, 3]]
{d: 3, a: {d: 1}} | flatten    # => [[d, a_d]; [3, 1]]
```

## User-facing changes (Release notes)

Fixed an issue where `flatten` silently dropped a nested record field
when a top-level column of the same name came later in the record. The
nested field is now renamed to `<parent>_<field>`, as it already was for
the opposite field order. The `flatten` help text now describes how
record input is handled.

## Additional notes
<!--
Optional.

Examples:
- fixes #123
- closes #456
- related #789

Anything else reviewers should know.
Remove this section if not needed.
-->
2026-09-21 10:56:16 -05:00
Darren Schroeder 6dceb6edef fix(default): accept cell paths for column arguments (#19058)
## Description

`default` declared its column arguments as `SyntaxShape::String`, so a
dotted argument was treated as a literal key rather than a path. The
signature even carried a TODO asking whether it should declare cell
paths, since users kept assuming it did.

```nushell
# before
{a: {}} | default 5 a.b
# => {a: {}, "a.b": 5}
```

In this PR the rest arguments become `SyntaxShape::CellPath`.
`fill_record` now works on a `Value` instead of a `Record`.

```nushell
# after
{a: {}} | default 5 a.b        # => {a: {b: 5}}
{} | default 5 a.b             # => {a: {b: 5}}
{a: {b: null}} | default 5 a.b # => {a: {b: 5}}
{a: [1 null]} | default 5 a.1  # => {a: [1, 5]}
{"a.b": null} | default 5 "a.b" # => {"a.b": 5}   (quoted segment still targets the literal key)
{a: 1} | default 5 a.b         # => error: Cannot find column 'b'  (was silently {a: 1, "a.b": 5})
[{a: {}} {a: {c: 1}}] | default 5 a.c  # => [{a: {c: 5}}, {a: {c: 1}}]
```

## User-facing changes (Release notes)

`default` now accepts cell paths for its column arguments, so `default 5
a.b` fills the nested field `b` inside `a` (creating `a` if needed)
instead of adding a literal `"a.b"` key. Quote the argument, as in
`default 5 "a.b"`, to target a key that actually contains a dot.

## Additional notes
<!--
Optional.

Examples:
- fixes #123
- closes #456
- related #789

Anything else reviewers should know.
Remove this section if not needed.
-->
2026-09-21 10:55:56 -05:00
David Bateman dc1d81a4f0 Merge commit from fork
* fix: reject relative vendor autoload paths

* refactor: strengthen vendor autoload path validation

* fix(config): validate resolved vendor autoload paths

* fix(config): log rejected autoload directories
2026-09-21 07:31:00 -05:00
Darren Schroeder 8795d809c4 bump fff-search to 0.11.0 (#19057)
<!--
Thanks for contributing to Nushell!

Before submitting, please read the contributing guide:
https://github.com/nushell/nushell/blob/main/CONTRIBUTING.md

This template helps reviewers understand your changes and allows us to
generate high-quality release notes.
-->

## Description
This PR bumps the fff-search crate used in `idx` to 0.11.0, the latest
version.
<!--
Explain what this PR does and why.

This section is intentionally flexible:
- Describe the problem
- Explain your approach
- Include technical details if relevant

Good examples:
- "In this PR, I fixed..."
- "In this PR, I added support for..."
- "This change improves X by..."

Write as much or as little as needed for reviewers to understand your
changes.
-->

## User-facing changes (Release notes)
<!--
This section is used (mostly as-is) for https://www.nushell.sh/blog/

Describe how Nushell behavior changes from a user's perspective.
Do NOT describe internal Rust changes here.

Write in a release note style, for example:
- "Added support for..."
- "Fixed an issue where..."
- "Nushell now supports..."
- "Improved performance of..."

If your changes do NOT affect users (internal refactors, cleanup, etc.),
just write:
- "n/a"
- "nan"
- or similar

This tells us the change should not appear in the changelog.

Tips:
- Focus on observable behavior
- Include examples if helpful
- Keep it concise

You can:
- Write a short paragraph (will appear as a bullet point), OR
- Use headings (###) if your change needs more structure

Avoid writing things like:
- "In this PR, I refactored..."
- "This updates internal code..."

You may leave this blank until the PR is ready.
-->

## Additional notes
<!--
Optional.

Examples:
- fixes #123
- closes #456
- related #789

Anything else reviewers should know.
Remove this section if not needed.
-->
2026-09-21 06:15:43 -05:00
Darren Schroeder 9fc5f8d10e fix(engine): run every finally on the way out of nested try blocks, and unwind break/continue through them (#19052)
## Description

`catch` handlers and `finally` handlers lived on two separate stacks in
`Stack` (`error_handlers` and `finally_run_handlers`). We found a few
other bugs too.

| Code | Before | Expected |
|---|---|---|
| `try { error make {msg: a} } finally { print f }; print AFTER` |
prints `f`, then `AFTER`, *then* raises the error | `f`, then the error
|
| `def foo [] { try { return 1 } finally { print f }; print AFTER; 2 }`
| prints `f`, `AFTER`, returns 1 | `f`, returns 1 |
| `try { try { err } catch { error make {msg: b} } finally { print f } }
catch { … }` | `f` never printed | `f`, then outer catch |
| `try { try { err } finally { error make {msg: b} } } catch {\|e\|
print $e.msg }` | prints `a` (finally never ran) | prints `b` |
| `try { try { try { err } finally { print 1 } } finally { print 2 } }
catch { print 3 }` | only `3` | `1`, `2`, `3` |
| `for x in [1 2] { try { break } finally { print f } }` | `f` never
printed | `f` |
| `try { for x in [1] { try { break } finally {} }; error make {msg: x}
} catch { print caught }` | `break` popped the **outer catch** instead,
error escapes | `caught` |
| `for x in [1 2] { try { break } finally { print f } }; try { error
make {msg: x} } catch {}` | the stale `finally` fires on the later,
unrelated error | nothing extra |

### The fix

There is now a single handler stack and a single unwinding path.

**`nu-protocol`** — `ErrorHandlerStack` holds `TryHandler` entries
instead of bare `ErrorHandler`s.

### Examples after the fix

```nushell
> try { try { error make {msg: bad} } finally { print "inner finally" } } catch { print "outer catch" }
inner finally
outer catch

> try { try { try { error make {msg: a} } finally { print 1 } } finally { print 2 } } catch { print 3 }
1
2
3

> try { try { error make {msg: a} } finally { error make {msg: b} } } catch {|e| print $e.msg }
b

> for x in [1 2] { try { break } finally { print "finally" } }; print "done"
finally
done

> for x in [1 2] { try { continue } finally { print $"finally ($x)" } }; print "done"
finally 1
finally 2
done

> def foo [] { try { return 1 } finally { print "f" }; print "AFTER"; 2 }; foo
f
1

> try { error make {msg: a} } finally { print "f" }; print "AFTER"
f
Error: nu::shell::error
  × a
```


## User-facing changes (Release notes)

### `finally` now always runs, in the right order, however a `try` block
is left

- Fixed an issue where a `finally` block inside a nested `try` was
skipped when an outer `try { } catch { }` caught the error (#19051).
Every `finally` between the error and the `catch` that handles it now
runs, innermost first.
- Fixed an issue where, after an error or `return` inside `try { }
finally { }`, the statements following the `try` expression kept
executing before the error or return took effect.
- Fixed `finally` being skipped when the error was raised inside a
`catch` block or inside another `finally` block and then caught further
out. An error raised in `finally` replaces the original error, as
before.
- `break` and `continue` inside `try { } finally { }` now run the
`finally` block on their way to the loop. Previously they skipped it,
could pop an enclosing `catch` handler by mistake, and left a handler
behind that fired on a later, unrelated error in the same block.
- `$in` inside a `finally` block is now always defined: the value
produced by `try`/`catch` on success, the error record on the error
path, and `nothing` for `return`, `exit`, `break` and `continue`. This
matches the block's optional parameter (`finally {|x| ... }`).
- `view ir` shows new instructions for this: `begin-finally`,
`end-finally` and `unwind-jump` replace `pop-finally`; the register-less
`finally` instruction is gone.

## Additional notes
closes https://github.com/nushell/nushell/issues/19051
2026-09-21 09:52:21 +08:00
Doru 394a9ec849 Don't go to tmp if --directory (#18996)
<!--
Thanks for contributing to Nushell!

Before submitting, please read the contributing guide:
https://github.com/nushell/nushell/blob/main/CONTRIBUTING.md

This template helps reviewers understand your changes and allows us to
generate high-quality release notes.
-->

## Description
Fixes #18995

## User-facing changes (Release notes)
<!--
This section is used (mostly as-is) for https://www.nushell.sh/blog/

Describe how Nushell behavior changes from a user's perspective.
Do NOT describe internal Rust changes here.

Write in a release note style, for example:
- "Added support for..."
- "Fixed an issue where..."
- "Nushell now supports..."
- "Improved performance of..."

If your changes do NOT affect users (internal refactors, cleanup, etc.),
just write:
- "n/a"
- "nan"
- or similar

This tells us the change should not appear in the changelog.

Tips:
- Focus on observable behavior
- Include examples if helpful
- Keep it concise

You can:
- Write a short paragraph (will appear as a bullet point), OR
- Use headings (###) if your change needs more structure

Avoid writing things like:
- "In this PR, I refactored..."
- "This updates internal code..."

You may leave this blank until the PR is ready.
-->

mktemp --directory is now relative to the working directory if a
template is passed

## Additional notes
<!--
Optional.

Examples:
- fixes #123
- closes #456
- related #789

Anything else reviewers should know.
Remove this section if not needed.
-->
This is a breaking change.

The alternative: Fix the docs, and use `-p .`. It will stay inconsistent
between --directory and file, though
2026-09-20 16:49:00 -05:00
Surma 2df2d92f3a feat(save): create parent directories with --force (#19039)
## Description

`save --force` currently fails when the destination's parent directory
does not exist.

This change makes `--force` create missing parent directories before
opening the destination. The existing `-f` alias provides the same
behavior.

Without `--force`, `save` retains its existing behavior.

```nushell
'hello' | save -f path/to/output/file.txt
```

## User-facing changes (Release notes)

`save --force` now creates missing parent directories before writing the
destination file.

## Additional notes

Fixes #15924
2026-09-20 15:19:17 -05:00
Darren Schroeder b2ddf2e300 perf(parser): reduce parse time by 30% - 50% (#19055)
## Description

This PR is meant to squeeze every bit of parser performance we can out
of the current engine.

Before Nushell can run a script, it has to *read* it: split the text
into words, figure out which words are commands, check that each command
gets the arguments it expects, and turn the result into something the
engine can execute. This PR makes those tasks about a third faster.

This PR addresses these findings.
1. Signature cache in `StateWorkingSet` (`nu-protocol`)
2. Lexer fast paths
3. No allocations in speculative literal parsing
4. Cheaper declaration lookup
5. Small removals of per-call copies

### Benchmarks

Paired comparison with tango (`benchmarks compare <baseline> -f
'parser_*' -o`).

| Benchmark | Baseline | This PR | Change |
| --- | ---: | ---: | ---: |
| `parser_lex_small` (82 B) | 849 ns | 571 ns | -32.7% |
| `parser_lex_medium` (4.3 KB) | 30.4 µs | 19.4 µs | -36.1% |
| `parser_lex_large` (100 KB, all of `std`) | 497 µs | 232 µs | -53.2% |
| `parser_lite_small` | 1.5 µs | 1.2 µs | -19.6% |
| `parser_lite_medium` | 37.2 µs | 26.2 µs | -29.6% |
| `parser_lite_real_world` (`toolkit/mod.nu`) | 7.3 µs | 4.3 µs | -40.4%
|
| `parser_parse_block_small` | 4.9 µs | 3.4 µs | -31.2% |
| `parser_parse_block_medium` | 107 µs | 65.4 µs | -39.0% |
| `parser_parse_block_real_world` | 19.2 µs | 11.1 µs | -42.3% |
| `parser_parse_small` (bare engine state) | 8.2 µs | 6.3 µs | -22.8% |
| `parser_parse_medium` (bare engine state) | 85.5 µs | 59.8 µs | -30.1%
|
| `parser_parse_large` (bare engine state) | 1.2 ms | 797 µs | -33.2% |
| `parser_parse_ctx_small` (full context) | 21.4 µs | 19.9 µs | -7.3% |
| `parser_parse_ctx_medium` (full context) | 1.0 ms | 758 µs | -27.4% |
| `parser_parse_ctx_real_world` (`toolkit/mod.nu`) | 163 µs | 135 µs |
-17.1% |
| `parser_parse_ctx_std_log` (`std/log/mod.nu`, 9.6 KB) | 1.3 ms | 907
µs | -29.9% |
| `parser_parse_ctx_std_help` (`std/help/mod.nu`, 30 KB) | 4.7 ms | 3.3
ms | -30.5% |
| `parser_parse_ctx_doc_config` (63 KB) | 1.1 ms | 844 µs | -21.8% |
| `parser_parse_ctx_large` (100 KB, all of `std`) | 13.2 ms | 8.9 ms |
-32.6% |

All differences are statistically significant at tango's default
threshold (100 samples, outlier filtering on).

Harness numbers (full command context, median of repeated parses, same
machine, same profile) for the inputs that drove the work:

| Input | Baseline | This PR | Change |
| --- | ---: | ---: | ---: |
| `small.nu` (82 B) | 23.3 µs | 18.7 µs | -20% |
| `medium.nu` (4.3 KB, 80 `let` pipelines) | 996 µs | 707 µs | -29% |
| `std/help/mod.nu` (30 KB) | 4531 µs | 3044 µs | -33% |
| `std/log/mod.nu` (9.6 KB) | 1251 µs | 837 µs | -33% |
| `doc_config.nu` (63 KB) | 1043 µs | 804 µs | -23% |
| `toolkit/checks.nu` (11 KB) | 845 µs | 570 µs | -33% |
| all of `std` concatenated (100 KB) | 13058 µs | 8645 µs | -34% |

## User-facing changes (Release notes)

Improved parsing performance: scripts, modules and `source`d files parse
about 1.4-1.5x faster, and the REPL's per-keystroke parse benefits
accordingly. No change to what is accepted, rejected, or reported.

## Additional notes
2026-09-20 15:16:58 -05:00
Louis Deconinck de33b5407d fix(table): don't panic when the terminal is too narrow to draw (#19045)
## Description

On a zero-column pty (which some CI systems like CircleCI provide),
printing an empty list or record panicked with `Could not create empty
table placeholder`, since a bordered table can't be drawn in 0 columns.
Separately, `truncate_columns_by_content` could panic on
`widths.last().expect("ok")` when the terminal was too narrow to fit any
column.

In this PR, `NuTable::draw` now reports that the table can't be drawn in
these cases instead of panicking:

- `create_empty_placeholder` falls back to the bare `empty <type>` text
when the bordered placeholder can't be drawn.
- `truncate_columns_by_content` returns an empty `needed` widths when
the border overhead alone already exceeds `termwidth`, or when no column
was fitted and there's no room for a trailing column. This makes
`table_truncate` return `None`, so `draw` returns `None` and the caller
emits the existing `Couldn't fit table into N columns!` warning.

Added a `termwidth_too_small_single_column` test covering truncate/wrap
strategies below the minimum drawable width.

## User-facing changes (Release notes)

- Fixed a panic when printing empty lists/records or narrow-fitting
tables on a zero-column pty (e.g. some CI environments). Nushell now
falls back to plain text output or a warning instead of crashing.

## Additional notes

- fixes #15328
2026-09-19 06:21:57 -05:00
Darren Schroeder 19bdc5f432 the tui family of commands (#19025)
## Description

This PR introduces a new family of nushell commands, `tui`, that allow
one to use these composable commands to create TUIs and popup dialog
boxes.

**I'm not sure this PR will ever land so let's talk about it and hear
what you think.** If you want this PR landed and would find these
commands useful please indicate that by giving a thumbs up 👍🏻 on this
PR.

## How it is meant to be used

Compose widgets, then `tui run`:

```nushell
ls | tui label --title "files" | tui table | tui run
```

Each `tui *` command except `tui run` and `tui debug` returns a `tui`
custom value. Inspect it before running:

```nushell
tui label --title "App" | tui table | describe        # tui
tui label --title "App" | tui table | get widgets.type
```

Layout is nested. `tui split`, `tui box`, and `tui tab` take a list of
child widgets, built inside parentheses. A child can carry its own rows:

```nushell
ls | tui split [(tui table) (tui preview)] | tui run
tui split [(ps | tui table) (ls | tui table)] | tui run
```

Every closure that reacts to the user (`tui bind`, menu actions, `tui
button`, `--on-select`, the `tui run` refresh closure) follows one
contract: it receives the state record and returns nothing, a new data
list, or `{action: submit|quit, ...}`. Preview and source closures also
see their pane's inner size as `$env.TUI_WIDTH` and `$env.TUI_HEIGHT`,
so `table -w $env.TUI_WIDTH` fits the pane.

## User-facing changes (Release notes)

This PR adds a `tui` command family so a Nushell user can build an
interactive TUI or popup from a pipeline, without writing Rust. The
commands live in a new `nu-tui` crate; the ratatui style conversions are
shared with `explore`.

Interactive `tui run` always uses the alternate screen, so the shell
comes back intact when you quit. `--dialog` is the same session in a
smaller floating window. `tui debug` paints the same UI to a string and
reports how it was laid out, for scripts and tests.

`tui run` returns one record, `{action, focused, selected, page, values,
rows, live}`, where `values` holds every widget's state by id. Colors
come from a new `$env.config.tui` section (documented in `config nu
--doc`).

Also in this PR: `nu-utils` now reads the OS locale once per process.
`sys_locale` goes through CoreFoundation on macOS and its autoreleased
objects were never collected on a plain Rust thread, so any loop that
formats filesizes (the TUI redraws per frame) grew memory without bound.

## Commands

Every builder takes `--id` and `--focus` (start with that widget
focused). Data widgets (table, tree, log, select, progress, label) also
take `--data` and `--from`; list widgets (table, tree, select) also take
`--on-select` and `--multi`. The remaining flags are listed.

| Command | Positional | Flags | Role |
|---|---|---|---|
| `tui` | | | Help listing subcommands |
| `tui label` | text or closure | `--title`, `--status` | Static text:
inline, on the title bar, or on the status bar. A closure follows a row
|
| `tui menu` | `[items]` | | Menu bar with mnemonics, dropdowns, and
hook actions |
| `tui textbox` | | `--placeholder`, `--value` | Editable field |
| `tui table` | source closure | `--columns`, `--capture-keys`,
`--index` | Navigable rows. Scalars show as one `item` column |
| `tui select` | `[items]` | `--display`, `--index` | Radio list, or
checkbox list with `--multi` |
| `tui button` | label, hook | | Runs a hook, or submits its label.
Adjacent buttons share a row |
| `tui progress` | source closure | `--value`, `--total`, `--label` |
Gauge fed by a value, the data, or a source row |
| `tui log` | source closure | `--max-lines` | Append-only log. Follows
the tail of a stream |
| `tui tree` | source closure | `--walk`, `--column` | Nested records,
or a directory walk |
| `tui search` | | `--placeholder`, `--bind`, `--fuzzy`,
`--case-sensitive`, `--columns` | Search box. Filters the lists in its
scope |
| `tui preview` | closure | `--from`, `--max-bytes` | Text for the
selected row: a file, or whatever the closure returns |
| `tui split` | `[children]` | `--vertical`, `--sizes`, `--ratio` |
Side-by-side or stacked panes with draggable dividers |
| `tui box` | title, `[children]` | | A titled, bordered group |
| `tui tab` | title, `[children]` | | A page in the tab bar |
| `tui bind` | key, hook | | Run a hook when a key is pressed anywhere |
| `tui run` | hook | `--dialog`, `--size`, `--refresh`, `--no-mouse` |
Event loop |
| `tui debug` | hook | `--keys`, `--until`, `--size`, `--dialog` |
Headless render, key replay, and layout/focus introspection |

## Layout

Widgets in the outer pipeline stack top to bottom, one row each. The
exception is buttons: adjacent `tui button`s pack onto one row, left to
right (§12). `tui split`, `tui box`, and `tui tab` take a list of child
widgets and arrange them. Children are built inside parentheses:

```nushell
ls
| tui label --title "files"
| tui split --sizes [60% 1fr] [
    (tui box "list" [ (tui search --bind /) (tui table --columns [name type size]) ])
    (tui preview { nu-highlight })
  ]
| tui label --status "enter: pick  /: filter  q: quit"
| tui run
```

Chrome, that is `tui label --title`, `tui label --status`, and `tui
menu`, goes in the outer pipeline only and stays visible on every page.
`tui tab` is also outer-pipeline only. `tui search` can go in either
place; its position decides what it filters (§3).

Auto ids are `table-0`, `search-0`, …, numbering widgets of each kind in
tree order. When two children both contain a `table-0`, the second
becomes `table-1` and a `--from table-0` inside that child follows it.
An explicit `--id` is never renumbered; two of the same explicit id is
an error. Pass `--id` when another widget needs a stable name; `tui
debug` shows the resolved ids.

## Data

Each widget shows the first of these that exists:

1. its own `--data`, or the value piped into it inside a child list
(`[(ls | tui table)]`);
2. what its `--from` source produced (§4);
3. the nearest ancestor container's data;
4. the outer pipeline's data.

So one pipeline can feed every list, or each pane can have its own rows:

```nushell
ls | tui split [ (tui table) (tui tree) ] | tui run           # both read ls
tui split [ (ps | tui table) (ls | tui table) ] | tui run      # each has its own
tui split [ (tui table --data (ps)) (tui table --data (ls)) ]  # the same, spelled out
```

A builder reads a stream for a quarter of a second. A producer that
finishes in that time (`ls`, `ps`, `open`) is collected into the
widget's data; one that keeps going (`1..`, `tail -f`) stays live and
flows on to `tui run` (§9). A child list can only hold collected values,
so keep unbounded producers in the outer pipeline.

## Hooks

One contract covers every closure that reacts to the user: `tui bind`,
menu `action`s, `tui button`, `--on-select`, and the `tui run` closure.
The hook receives the state record (the same record `tui run` returns,
§Result record) as `$in`, and as its first parameter when it declares
one. Its output decides what happens:

| Returns | Effect |
|---|---|
| nothing | nothing changes |
| `{action: submit, selected: ...}` | the TUI closes with that selection
|
| `{action: quit}` | the TUI closes without a selection |
| anything else | it replaces the shared data list; every widget reading
it redraws |

An error goes to the status bar as `error:…` and the TUI stays open.

```nushell
ls
| tui table
| tui bind ctrl+r {|| ls }                                              # reload
| tui bind s {|state| {action: submit, selected: $state.selected.name} } # submit the name
| tui run
```

## Additional notes

I realize this section is crazy verbose but I think it's important to
know how to use these commands. So, consider this documentation and a
brief tutorial from my favorite clanker. I've been through these
examples several times but I figure there are bugs, edge cases, and
missing functionality that still exist.

### How-to

`tui debug` variants check the screen without grabbing the terminal.

#### 1. Render something

```nushell
tui label --title "hello" | tui label --status "ready" | tui debug | get screen
```

`tui run` takes over the terminal. `q` or Esc leaves. `tui debug`
returns a record; `screen` is the painted buffer.

A title can also come from a scalar when it is the first widget:

```nushell
"Files" | tui label --title | tui debug | get screen
```

#### 2. Put pipeline rows in a table

```nushell
ls | tui label --title "files" | tui table --columns [name type size] | tui run
```

Up/Down or j/k move. Enter returns a record:

```nushell
let picked = ls | tui table | tui run
if $picked.action == "submit" {
    $picked.selected
}
```

`tui debug --keys` replays that path in CI:

```nushell
[{name: a}, {name: b}]
| tui table
| tui debug --keys [down enter]
| get selected.name
# b
```

Give the table `--id files` as soon as anything else needs to point at
it. Missing `--columns` uses the record columns. Scalars and ranges get
a single `item` column, one row each:

```nushell
1..20 | tui table | tui run
```

`--multi` turns the table into a checklist. Space checks the highlighted
row and Enter submits the checked rows (or the highlighted one when
nothing is checked). `--index` submits positions instead of rows:

```nushell
[a b c] | tui table --multi | tui debug --keys [space down space enter] | get selected
# [a, b]
[a b c] | tui table --index | tui debug --keys [down enter] | get selected
# 1
```

#### 3. Filter those rows

```nushell
ls
| tui label --title "files"
| tui search --placeholder "filter" --bind /
| tui table --columns [name type size]
| tui label --status "enter: pick  /: filter  q: quit"
| tui run
```

Press `/` (or whatever you passed to `--bind`) to focus search. Type.
The table shrinks. Give the search box `--focus` to start there, so
typing filters immediately. Enter submits the table's selected row. Esc
clears the query; Esc again leaves the field. `q` typed in search is the
letter q.

Matching is a case-insensitive substring over every field. `--fuzzy`
uses the same matcher as `input list --fuzzy`, `--case-sensitive`
respects case, and `--columns [name]` matches only those columns:

```nushell
[{name: alpha}, {name: gamma}]
| tui search --fuzzy --columns [name] --bind /
| tui table
| tui debug --keys "/,type:gm,enter"
| get selected.name
# gamma
```

Scope comes from position. A search in the outer pipeline filters every
table, log, tree, and select. A search inside a `tui split` or `tui box`
filters only the widgets in that container, and each search box has its
own query:

```nushell
ls
| tui split [
    (tui box "files" [ (tui search --bind /) (tui table --id files) ])   # / filters only `files`
    (tui table --id other)                                               # untouched
  ]
| tui run
```

`--bind` is ignored while you are typing in a text box, so it cannot
steal keys from a form.

#### 4. Follow a row: preview, detail views, labels

```nushell
ls
| tui split [ (tui table --columns [name type]) (tui preview) ]
| tui run
```

Down/up stay on the table. The right pane follows the selected row: the
preview uses the focused list, else the nearest one in the same
container, else the first visible one. `--from files` names one
explicitly. Directories and binary files show a short note. Mouse wheel
over the preview scrolls the text. `--max-bytes` defaults to 65536. `tui
preview` uses the `name` column for what to preview because it is
currently hard coded that way.

A closure with no parameters transforms the file text. `$in` is the
text; `metadata access` sees `content_type`:

```nushell
ls
| tui split [ (tui table) (tui preview { nu-highlight }) ]
| tui run
```

A closure with one parameter is the source. It receives the selected
row, nothing is read from disk, and whatever it returns is shown:

```nushell
$env.config.keybindings
| tui split [ (tui table) (tui preview {|row| $row.event | to nuon }) ]
| tui run
```

ANSI from `nu-highlight` or `bat` is drawn as color.

Any data widget can follow a row the same way. `--from <id>` names the
source and the positional closure turns its highlighted row into this
widget's data. A table becomes a detail view, a label a caption:

```nushell
[{name: a, kids: [1 2]}, {name: b, kids: [3 4 5]}]
| tui split [ (tui table --id src --columns [name]) (tui table --from src {|r| $r.kids }) ]
| tui run

ls
| tui table --columns [name]
| tui label {|row| $"size: ($row.size)" }
| tui run
```

Without `--from`, a widget with a source closure follows the nearest
list like a preview does. Without a closure, the row itself is the
widget's data. A source closure re-runs only when the highlighted row
changes or the pane is resized. `--from` must name a widget that exists;
otherwise `tui run` and `tui debug` stop with an error listing the ids.

Preview and source closures see their pane's inner size as
`$env.TUI_WIDTH` and `$env.TUI_HEIGHT`, so output can be fitted instead
of guessed: `tui preview {|row| $row | table -e -w $env.TUI_WIDTH }`.
`table` without `-w` sizes itself to the whole terminal, which overflows
a pane.

#### 5. Tabs and boxes

```nushell
ls
| tui tab "table" [ (tui table --columns [name type size]) ]
| tui tab "tree"  [ (tui split [ (tui tree --walk) (tui preview) ]) ]
| tui run
```

Both tabs read the same rows. Click a tab, press `]` / `[`, Ctrl+Tab /
Ctrl+Shift+Tab, Ctrl+Left / Ctrl+Right, or press `2` to jump. The tab
bar shows whenever any top-level `tui tab` exists, even a single one.
Tabs cannot be nested.

`tui box` draws a titled border around its children and can sit
anywhere, including inside a split:

```nushell
ls | tui split [ (tui box "list" [ (tui search) (tui table) ]) (tui preview) ] | tui run
```

#### 6. A tree

Nested records. Top-level keys are the roots. Leaves show as `key:
value`. Right/l expands, Left/h collapses, Enter submits the node:

```nushell
{a: {b: 1, c: 2}, d: [3, 4]} | tui tree | tui run
```

With both roots expanded, that paints:

```
▾ a
    b: 1
    c: 2
▾ d
    3
    4
```

Any record expands into its fields, so `ls` rows open to show `name`,
`type`, `size`, and `modified`:

```nushell
ls | tui split [ (tui table) (tui tree) ] | tui run
```

Directory walk. `--walk` treats `type == dir` rows as folders and lists
their contents on expand; file rows still open to their fields. Relative
string paths resolve against the session cwd:

```nushell
ls
| tui split [ (tui tree --walk) (tui preview) ]
| tui run
```

`--column` defaults to `name`. Search filters visible labels. The
preview follows the selected node. `--multi` checks nodes with Space.

#### 7. Refresh on a timer

```nushell
ls | tui table | tui run --refresh 1sec { ls }
```

The closure is a hook (§Hooks): it runs once before the first frame,
then on every interval, and its output replaces the shared data list.
Every widget that reads that list redraws and the preview follows the
new selection. A failing closure keeps the last good rows and puts
`error:…` on the status bar.

Without `--refresh` the closure runs once at start. `tui debug` runs it
once before painting:

```nushell
[{name: old}]
| tui table --columns [name]
| tui debug { [{name: new}] }
| get screen
```

#### 8. Splits

`tui split` divides its area between its children: side by side by
default, stacked with `--vertical`. `--sizes` gives one size per child;
missing entries are `1fr`, `fr` as in `fraction`:

| Size | Meaning |
|---|---|
| `20` | 20 cells |
| `"30%"` | 30 percent of the split |
| `"1fr"`, `"2fr"` | a share of what is left; `2fr` takes twice `1fr` |
| `"min:10"`, `"max:40"` | at least / at most that many cells |

`--ratio 60` is shorthand for `--sizes [60% 1fr]`. Splits nest.

```nushell
ls
| tui split --sizes [30 1fr] [
    (tui tree --walk)
    (tui split --vertical --ratio 70 [ (tui preview) (tui log) ])
  ]
| tui run
```

A one-cell handle sits between every pair of children. Drag it with the
mouse; Tab to the split and use arrows or hjkl to move the first divider
by one percent. `tui debug` reports the resulting sizes under `values`.

A split whose children are all fixed-height leaves (buttons, labels,
text boxes, progress bars) is not a set of panes: it takes exactly their
height, draws no handle, and is the way to stack buttons or put a text
box next to a button on one line (§12). A `tui box` of fixed leaves
shrinks to fit the same way.

Without a split, widgets stack vertically in pipeline order.

#### 9. Live streams

A stream that outlives the builders' quarter-second collect budget
(§Data) is not collected. `tui run` reads it on a background thread
while the UI is open. Ranges are read the same way, so an open-ended
range keeps producing rows:

```nushell
1..
| each {|n| sleep 100ms; {n: $n, at: (date now)}}
| tui label --title "stream"
| tui table
| tui run
```

A table keeps its highlight while rows arrive. `tui log` follows the
tail until you scroll up; the title becomes `log (paused)`, and
scrolling to the bottom (or pressing End) resumes follow. Up/Down,
PageUp/PageDown, and the wheel all scroll:

```nushell
1..
| each {|n| sleep 100ms; $"tick ($n)"}
| tui label --title "ticks"
| tui log --max-lines 20000
| tui run
```

`--max-lines` defaults to 10000 and is clamped to 10…1000000. Streamed
rows share one store, capped at `max(10000, --max-lines)`.

`1.. | tui label --title "x" | tui table | first 1` returns `1` without
waiting for the infinite range. The stream is still a stream after the
builders; only the first builder pays the collect budget, later ones
pass a live stream straight through.

`tui debug` drains a finite stream (5 second cap) then paints. Do not
pipe an infinite stream into it without a bound.

#### 10. Floating dialog

```nushell
ls
| tui label --title "files"
| tui table --columns [name type size]
| tui run --dialog --size [70 20]
```

`--dialog` (`-d`) is a smaller window on the same alternate screen.
Without `--size` it is about three quarters of the terminal, at least
40×12. Drag the title bar to move, drag the bottom-right `┘` to resize,
click `x` to quit. Widget keys inside the dialog are unchanged.

#### 11. Menu bar

Each entry is a string, or a record `{name, items?, action?}`. `items`
opens as a dropdown; `action` is a hook.

```nushell
ls
| tui label --title "Files"
| tui menu [
    {name: "&File", items: [
        {name: "&Reload",  action: {|| ls }}
        {name: "&Parent",  action: {|| ls .. }}
        "&Quit"
    ]}
    {name: "&Sort", items: [
        {name: "by &Name", action: {|| ls | sort-by name }}
        {name: "by &Size", action: {|| ls | sort-by size }}
        {name: "by &Modified", action: {|| ls | sort-by modified }}
    ]}
    "&Help"
  ]
| tui table --columns [name type size modified]
| tui label --status "alt+f: file  alt+s: sort  q: quit"
| tui run
```

Mnemonics. `&` before a letter picks it and that letter is drawn
underlined: `"&File"` is File with F underlined, `"by &Size"` is by Size
with S underlined. Without `&`, the first letter is used. Alt+F opens
File from anywhere except a text field. Inside an open dropdown the
letter alone picks the entry: Alt+S then N sorts by name.

Keys. Left/Right or h/l move along the bar. Down or Enter opens the
dropdown under the current item, or activates it if it has none. Up/Down
move inside the dropdown; Left/Right move to the neighbouring bar item,
opening its dropdown if it has one; Esc closes the dropdown. Click a bar
item to open it (or activate it if it has no dropdown); click a dropdown
row to activate it.

Actions. An entry with an `action` runs it as a hook when activated: a
returned value replaces the data list (`Reload` and the `Sort` entries
above re-run `ls`), `nothing` leaves the data alone, `{action: quit}`
closes the TUI, and an error goes to the status bar.

Submit. An entry without an action submits when activated and the TUI
closes. For a dropdown entry `selected` is `{menu: "File", item: "Open",
row: {...}}`, where `row` is the row highlighted in the table, so the
entry can act on it. For a bar item with no dropdown `selected` is its
name (`"Help"`):

```nushell
let r = ls | tui menu [{name: "&File", items: ["&Open" "&Quit"]}] | tui table | tui run
if $r.selected.item? == "Open" { open $r.selected.row.name }
```

Headless:

```nushell
tui menu ["&File" "&Edit" "&View"] | tui table | tui debug --keys "alt+e,enter" | get selected
# Edit

[a b]
| tui menu [{name: "&File", items: [{name: "&Reload", action: {|| [x y z] }} "&Quit"]}]
| tui table
| tui debug --keys "alt+f,r"
| get rows
# 3
```

#### 12. Form: text boxes and buttons

```nushell
tui label --title "rename"
| tui label "new name"
| tui textbox --id name --placeholder "type a name"
| tui button Save {|s| $s.values.name | save name.txt; {action: quit} }
| tui button Cancel {|| {action: quit} }
| tui label --status "tab: next  enter: activate  esc: cancel"
| tui run
```

While the text box is focused, `q` is a character. Esc leaves the field.
Enter submits with the text as `selected`; the text is also
`values.name`. `--value` sets the initial text.

`tui button` is a focusable label. Tab to it and press Enter or Space,
or click it. With a hook it behaves like `tui bind`; without one it
submits its label, so two plain buttons make a confirm dialog:

```nushell
tui label "Delete everything?" | tui button Yes | tui button No | tui run | get selected
```

Adjacent buttons share one row, so that paints as:

```
Delete everything?
 [ Yes ]  [ No ]
```

Left/Right (or h/l) move between the buttons on a row; Tab still walks
the whole page. Anything else between two buttons, such as a label or a
text box, starts a new row. To stack buttons on purpose, put them in a
vertical split; to place a text box and a button on one line, put them
in a horizontal split. Splits of fixed-height leaves take only that
height and draw no divider (§8):

```nushell
tui split --vertical [ (tui button Yes) (tui button No) ]          # one under the other
tui label "name" | tui split [ (tui textbox --id name) (tui button Go) ]   # a form line
```

Headless check that `q` is typed, not treated as quit, and that Right
reaches the second button:

```nushell
tui textbox | tui debug --keys "type:q,enter" | get selected
# q
tui label "Delete?" | tui button Yes | tui button No | tui debug --keys [right enter] | get selected
# No
```

#### 13. Pick from a list

`tui select` is a radio list of items from its argument, `--data`, or
the pipeline. `--multi` makes it a checkbox list. `--display` picks the
label: a column name for records, or a closure over the item. The
original item is what gets selected, as with `input list --display`:

```nushell
tui select [small medium large] | tui debug --keys [down enter] | get selected
# medium

ls | tui select --multi --display name | tui run | get selected.name
```

With `--multi`, the highlighted line paints as `[x] name` once checked;
a plain select paints `(•) name`.

#### 14. Keybinding explorer

```nushell
def "keys ui" [] {
    $env.config.keybindings
    | tui label --title "keybindings"
    | tui search --placeholder "type to filter, or focus the list and press a chord" --bind /
    | tui table --capture-keys --columns [name modifier keycode]
    | tui label --status "/: search  chord: filter list  enter: pick  q: quit"
    | tui run
}
```

Press `/` and type `history`. Or, with the table focused, press Ctrl+R:
with `--capture-keys` the chord becomes the query. It lands in the
search box that scopes the table, or on the table itself when there is
no search box. Rows with `modifier`/`keycode` fields match
reedline-style chords, so config `control`+`char_r` matches `ctrl+r`.
Tab, Enter, Esc, `q`, `[`, `]`, and the page digits keep their normal
meaning.

```nushell
[
    {name: "history", modifier: "control", keycode: "char_r", mode: "emacs"}
    {name: "clear", modifier: "control", keycode: "char_l", mode: "emacs"}
]
| tui table --capture-keys
| tui debug --keys "ctrl+r,enter"
| get selected.name
# history
```

#### 15. Key bindings and selection hooks

`tui bind` runs a hook when a key is pressed anywhere. The key is a
chord string (`ctrl+s`, `alt+r`, `f5`, `x`) or a reedline-style record
`{modifier: control, keycode: char_s}` as in `$env.config.keybindings`.
Binds take precedence over the built-in keys, except Ctrl+C. A bind
without a modifier does not fire while typing in a search or text box.

```nushell
[a b]
| tui table
| tui bind ctrl+r {|| [x y z] }
| tui debug --keys ctrl+r
| get rows
# 3

[a b]
| tui table
| tui bind {modifier: control, keycode: char_q} {|| {action: quit} }
| tui debug --keys ctrl+q
| get action
# quit
```

`--on-select` on a table, tree, or select runs a hook whenever the
highlight moves. The hook sees the new selection in the state record.
Use it for side effects outside the UI; printing from a hook would draw
over the screen:

```nushell
ls | tui table --on-select {|s| $s.selected.name | save -f last-picked.txt; null } | tui run
```

To show something about the selection on screen, use a widget that
follows the row instead (§4):

```nushell
ls | tui table | tui label {|row| $"selected: ($row.name)  ($row.size)" } | tui run
```

#### 16. Progress

`tui progress` shows `--value`, or otherwise reads its data: a number, a
`{value, total}` record's `value`, or the last number in a list, so a
streamed counter drives it live. Values above 1 are read out of
`--total` (default 100):

```nushell
tui progress --value 0.4 --label copying | tui debug | get screen     # copying 40%
1..100 | each {|n| sleep 50ms; $n } | tui progress --total 100 | tui run
```

With `--from` and a closure the bar follows another widget's highlighted
row.

#### 17. Theme

`$env.config.tui` holds one slot per surface. Each value takes the same
forms as `color_config`: a color name, `#RRGGBB`, or `{fg?, bg?,
attr?}`. `config nu --doc` documents every key with its default.

| Key | Paints |
|---|---|
| `title_bar`, `status_bar` | the bars from `tui label --title` /
`--status` |
| `border`, `border_focused` | widget borders, and the focused widget's
border |
| `selected` | the highlighted row in tables, trees, selects, and menus
|
| `header` | table column headers |
| `muted` | placeholders and empty-state text |
| `highlight` | search query text and check marks |
| `tab_active`, `tab_inactive` | the tab bar |
| `progress`, `button` | the gauge fill and button labels |
| `surface`, `backdrop` | the fill behind widgets, and behind a
`--dialog` (only `bg` is used) |

```nushell
$env.config.tui.title_bar = { fg: black, bg: yellow, attr: b }
$env.config.tui.border_focused = { fg: "#ff8800" }
```

Table and tree cells match `table` / `ls`: values are colored by type
through `color_config`, and path columns (`ls` metadata `path_columns`,
or a `name` column next to `type`) use `LS_COLORS`, following
`$env.config.ls.use_ls_colors`. Regular files with `fi=0` stay
uncolored. With `use_ansi_coloring` off the TUI draws without colors;
the selection is reverse video.

The color conversions are shared with `explore`, so the two commands
paint nushell styles the same way.

#### 18. Inspect before you run

The custom value shows the tree as written:

```nushell
tui label --title "App"
| tui split [ (tui table) (tui preview) ]
| get widgets
```

Each widget record has `id`, `type`, and `children` for containers.
Extra fields depend on the kind (`text`, `slot`, `columns`, `items`,
`walk`, `placeholder`, `sizes`, …). Widgets with a source show `from`
and `has_source_closure`; widgets with their own data show `has_data`.

`tui debug` shows the tree as resolved:

```nushell
ls | tui split --ratio 60 [ (tui table) (tui preview) ] | tui debug | get widgets.0.children
```

Every visible widget has `rect` (`x y width height`); widgets on a
hidden tab have none. Every widget has `focusable`. Tables add
`resolved_columns` and filtered `rows`; logs, trees, and selects add
`rows`; searches add `query`; previews and `--from` widgets add `source`
(the id they follow); lists add `search_scope` (the search that filters
them, or nothing). The record also has `focus` (`order`, `default`) and
`pages`.

#### 19. Scripted keys

`tui debug --keys` replays tokens and returns the result record,
including `screen`. Pass a comma-separated string or a list of tokens.
In the string form `click:10,5` keeps its comma.

| Token | Meaning |
|---|---|
| `enter`, `esc`, `tab`, `shift+tab` | Keys |
| `up`, `down`, `left`, `right`, `home`, `end`, `pageup`, `pagedown` |
Motion |
| `backspace`, `delete`, `space`, `insert` | Editing |
| `ctrl+c`, `ctrl+r`, `alt+f`, `f1` | Modifiers and function keys |
| a single character, or `/` | That key |
| `type:hello` | Type those characters |
| `click:COL,ROW` | Left click |
| `scroll-up`, `scroll-down` | Wheel |
| `drag:COL,ROW` | Left-button drag |

```nushell
[{name: a}, {name: b}]
| tui table
| tui debug --keys [down enter] --size [80 16]
| get selected.name
# b
```

`--until {|state| ...}` stops the replay as soon as the closure returns
true for the state record:

```nushell
[a b c d]
| tui table
| tui debug --keys [down down down] --until {|s| $s.values.table-0.index == 1 }
| get values.table-0.index
# 1
```

Default canvas is 80×24. `--size [w h]` clamps to 16…400 and 4…200.
`--dialog` paints the popup frame. `--no-mouse` on `tui run` turns off
mouse capture.

### Interaction

`tui run` owns the terminal until you submit or quit.

- **Tab / Shift+Tab** moves focus. The focused widget gets the
`border_focused` color. Focus starts on the widget built with `--focus`,
else on the first list, input, or button.
- **Typing** goes to a focused search box or text box. `q` is then a
character, not quit.
- **Enter** submits and returns a record. `selected` is the focused
widget's selection: a table row (or checked rows, or an index), a tree
node, a select item, a text box's text, a button's label, or the row
highlighted in the list a search box filters. On a menu it opens the
dropdown or activates the item.
- **Space** checks the highlighted row in a `--multi` list, or activates
a button.
- **Esc** in a search box clears the query; again, leaves the field. In
a text box it leaves the field. With a dropdown open it closes the
dropdown. Otherwise it quits.
- **Arrows, hjkl, PageUp/PageDown, Home/End** move in tables, trees, and
selects. On a tree, Right/l expands and Left/h collapses. On a log they
scroll. On a menu, Left/Right or h/l change the item and Down opens it.
On a button row, Left/Right move between buttons. On a split they move
the first divider 1%.
- **Alt+letter** opens the menu-bar item with that mnemonic. Inside a
dropdown the bare letter picks an entry.
- **q** (when not typing) or **Ctrl+C** quits. `selected` is then
nothing.
- **[ / ]**, **Ctrl+Left / Ctrl+Right**, **Ctrl+Tab / Ctrl+Shift+Tab**,
or **1-9** switch pages.
- **Mouse**. Click to focus and select. Wheel scrolls the widget under
the cursor. Drag a split handle to resize. Click a menu item to open it
and a dropdown row to activate it. Click a button to activate it. With
`--dialog`, drag the title bar to move, drag the bottom-right corner to
resize, click `x` to close.
- A search `--bind` (for example `/` or `ctrl+r`) focuses that search
from anywhere except a text field. `tui bind` keys run their hook before
any of the above, except Ctrl+C.

Focusable widgets: menu, table, select, tree, log, search, text box,
button, split handle. Labels, previews, progress bars, boxes, and tabs
are not.

### Result record

Returned by `tui run` and by `tui debug`; also what every hook receives:

```nushell
{
  action: "submit" | "quit" | "render"
  focused: "table-0"
  selected: { name: "foo", ... }   # row, checked rows, index, tree node, {menu, item, row}, text, or label
  page: 0
  values: {                        # every widget, by id
    "search-0": "foo"              # search and textbox: the text
    "table-0": { index: 2, row: {...} }          # table and select; --multi adds checked: [...]
    "tree-0": { index: 0, path: "a/b", value: 1 }
    "log-0": { rows: 12, follow: true }
    "preview-0": { title: "foo.txt", text: "..." }
    "split-0": ["60%", "1fr"]
    "progress-0": 0.4
    "button-0": "Save"
  }
  rows: 12
  live: false
  screen?: "<painted buffer, tui debug only>"
  widgets?: [...]                  # tui debug only: resolved tree
  focus?: { order: [...], default: "table-0" }
  pages?: [{ title: "files", id: "tab-0" }]
}
```

`action` is `render` when `tui debug` finished without Enter or quit.
`rows` is the length of the shared data list. `live` is true while a
stream is still producing. Read a widget by the id you gave it: `tui
textbox --id name | tui run | get values.name`.

### Recipes

#### File picker

```nushell
ls
| tui label --title "files"
| tui search --placeholder "filter" --bind /
| tui table --columns [name type size modified]
| tui label --status "enter: pick  /: filter  q: quit"
| tui run
```

#### File list plus preview plus search

```nushell
ls
| tui search --bind /
| tui split [ (tui table --columns [name type size]) (tui preview { nu-highlight }) ]
| tui run
```

#### Master and detail

```nushell
ls
| tui split --sizes [30 1fr] [
    (tui tree --walk --id dirs)
    (tui split --vertical [
        (tui table --from dirs {|node| ls $node.name } --columns [name size modified])
        (tui preview)
    ])
  ]
| tui run
```

The table lists whatever directory is highlighted in the tree; the
preview follows the focused list.

#### File browser with a menu and shortcuts

```nushell
ls
| tui label --title "browse"
| tui menu [
    {name: "&File", items: [{name: "&Reload", action: {|| ls }} "&Open" "&Quit"]}
    {name: "&Sort", items: [
        {name: "&Name", action: {|| ls | sort-by name }}
        {name: "&Size", action: {|| ls | sort-by size -r }}
    ]}
  ]
| tui search --bind /
| tui split [ (tui table --columns [name type size]) (tui preview { nu-highlight }) ]
| tui bind ctrl+r {|| ls }
| tui bind ctrl+o {|s| {action: submit, selected: $s.selected.name} }
| tui label --status "alt+f alt+s: menus  /: filter  ctrl+o: open  q: quit"
| tui run
```

#### Confirm dialog

```nushell
def confirm [question: string] {
    let r = tui label $question | tui button Yes | tui button No | tui run --dialog --size [40 8]
    $r.action == "submit" and $r.selected == "Yes"
}
if (confirm "Delete build output?") { print "rm -rf target" } # print and in quotes for safety
```

#### Content-type highlighting

```nushell
ls
| tui split [
    (tui table --columns [name size])
    (tui preview {
        metadata access {|meta|
            match $meta.content_type? {
                "application/x-nuscript" | "application/x-nuon" | "text/x-nushell" => {
                    if (config use-colors) { nu-highlight } else { $in }
                }
                "application/json" => { ^bat --language=json --color=always --style=plain --paging=never }
                "application/xml" => { ^bat -Ppf --language=xml }
                "application/yaml" => { ^bat -Ppf --language=yaml }
                "text/csv" => { ^bat -Ppf --language=csv }
                "text/tab-separated-values" => { ^bat -Ppf --language=tsv }
                "text/x-toml" => { ^bat -Ppf --language=toml }
                "text/markdown" => { ^bat -Ppf --language=markdown }
                _ => { $in }
            }
        }
    })
  ]
| tui run
```

#### History picker on a key

Fuzzy-find a past command and put it on the command line. Bind it to a
chord in `$env.config.keybindings`; the definition itself is plain
nushell. (Ctrl+H is the backspace byte on terminals without the kitty
keyboard protocol; pick another chord there.)

```nushell
def "history ui" [] {
    let r = history
        | get command
        | reverse
        | uniq
        | wrap command
        | tui label --title "history"
        | tui search --focus --bind / --fuzzy --columns [command] --placeholder "fuzzy filter"
        | tui table --columns [command]
        | tui label --status "type to filter  enter: put on the command line  esc esc: cancel"
        | tui run --dialog --size [100 24]
    if $r.action == "submit" {
        commandline edit --replace $r.selected.command
    }
}

$env.config.keybindings ++= [{
    name: history_ui
    modifier: control
    keycode: char_h
    mode: [emacs vi_insert vi_normal]
    event: { send: executehostcommand, cmd: "history ui" }
}]
```

Newest commands come first, duplicates are folded, and the popup leaves
the prompt as it was. `--focus` puts the cursor in the search box, so
typing goes straight to the fuzzy filter; Enter replaces the current
line with the top match. Esc clears the filter, Esc again moves to the
list, and a third Esc (or Ctrl+C at any time) cancels.

#### Git branch switcher

Branches on the left with their age and last subject, the branch's
recent log on the right, Enter to switch:

```nushell
def "git switch-ui" [] {
    let branches = ^git branch --format="%(refname:short)|%(committerdate:relative)|%(subject)"
        | lines
        | parse "{name}|{when}|{subject}"
    let r = $branches
        | tui label --title "branches"
        | tui search --bind / --fuzzy --columns [name]
        | tui split --sizes [45% 1fr] [
            (tui table --columns [name when subject])
            (tui preview {|b| ^git log --color=always --oneline -20 $b.name })
          ]
        | tui label --status "/: filter  enter: switch  q: cancel"
        | tui run
    if $r.action == "submit" {
        ^git switch $r.selected.name
    }
}
```

The preview closure takes the highlighted row, so the log pane changes
as you move; `--color=always` keeps git's colors. Swap `^git switch` for
`^git branch -d` and you have a branch cleaner.

The caret matters: if you load git completions, `git branch` is an
`extern` with its own flag list, and nushell then parses `--format=`
against that signature (in the stock completions it is a boolean switch,
so the call fails with "expected bool"). `^git` always runs the binary.

#### Process monitor

A `ps` view that refreshes every two seconds, filters by name, shows the
full record of the highlighted process, and kills on Ctrl+K:

```nushell
def "ps ui" [] {
    let snapshot = {|| ps | sort-by cpu -r | update cpu { math round -p 1 } | first 200 }
    do $snapshot
    | tui label --title "processes"
    | tui search --bind / --columns [name]
    | tui split --sizes [1fr 44] [
        (tui table --columns [pid name cpu mem])
        (tui preview {|p| $p | transpose field value | table -w $env.TUI_WIDTH })
      ]
    | tui bind ctrl+k {|s| kill $s.selected.pid; do $snapshot }
    | tui label --status "/: filter  ctrl+k: kill  enter: pick  q: quit"
    | tui run --refresh 2sec $snapshot
    | get selected?
}
```

The same closure feeds the first frame, the timer, and the reload after
a kill, so the list is always sorted the same way. Each refresh replaces
the data while the highlight and the filter stay put. Enter returns the
highlighted process record; `q` returns nothing.

#### Explore any data file

Open a JSON, TOML, YAML, or NUON file as a tree, filter its keys, and
see the highlighted node rendered as a table:

```nushell
def "explore-data" [file: path] {
    open $file
    | tui label --title ($file | path basename)
    | tui search --bind /
    | tui split --sizes [40% 1fr] [
        (tui tree)
        (tui preview {|node| $node | table -e -w $env.TUI_WIDTH })
      ]
    | tui label --status "right/left: open and close  /: filter  enter: pick  q: quit"
    | tui run
    | get selected?
}

explore-data Cargo.toml
explore-data $nu.config-path
```

Right opens a record or list, Left closes it, and the search box narrows
the tree to matching labels. `-w $env.TUI_WIDTH` makes the rendered
table fill the preview pane exactly, whatever the terminal size. Enter
returns the node's value, so `explore-data settings.json | get port`
reads a nested field by pointing at it.

#### Pick several files, then act on them

A checklist over `ls`: Space checks, Enter returns the checked rows. Put
a confirm in front of anything destructive:

```nushell
def "pick files" [] {
    let r = ls
        | tui label --title "pick files"
        | tui search --bind / --columns [name]
        | tui table --multi --columns [name type size modified]
        | tui label --status "space: check  /: filter  enter: accept  q: cancel"
        | tui run
    if $r.action == "submit" { $r.selected } else { [] }
}

def confirm [question: string] {
    let r = tui label $question | tui button Yes | tui button No | tui run --dialog --size [44 8]
    $r.action == "submit" and $r.selected == "Yes"
}

let files = pick files
if ($files | is-not-empty) and (confirm $"Delete ($files | length) files?") {
    $files | each {|f| print "rm $f.name" } # print and in quotes for safety
}
```

`pick files | each {|f| cp $f.name backup/ }` copies instead. With
nothing checked, Enter returns just the highlighted row, so the same
picker works for a single file.
2026-09-18 18:57:48 -05:00
Darren Schroeder 31b085859a perf(startup): make $nu.startup-time accurate and speed up config loading, plugin registry, and gstat (#19049)
## Description

`$nu.startup-time` was not measuring startup accurately. It was in
`evaluate_repl` before the `env_change` and `pre_prompt` hooks, the
prompt closures, and the line editor setup ran, and it started at the
top of `main`, so it also missed the loader and runtime work before
`main`. On my config it reported `~132ms` while the real time to a
usable prompt was `~1.1s`.

This PR fixes the measurement first, then uses the resulting profile to
fix the largest startup costs that are Nushell's own.

There were several areas that were addressed to make startup faster and
report more accurately.

1. Accurately measure `$nu.startup-time` from process creation time
until the repl is ready
2. The banner is split so the number it shows is the final one without
reordering the welcome
3. Parser - `find_decl` merged visibility maps on every lookup
4. Plugin registry - single-pass decode
5. gstat - bounded nearest-tag lookup
6. Terminal size - get size without spawning `tput`
7. Kitty protocol health check - It now only runs when warn-level
logging is enabled.

Here's stats from my system demonstrating the performance changes

| Measurement | Before | After |
|---|---|---|
| `$nu.startup-time` reported (my config, REPL) | 132ms (wrong) | 156ms
|
| Real time to first prompt (my config, REPL) | ~1100ms | 156ms |
| `setup_config` (env.nu, config.nu, autoloads, plugins) | 125ms | 55ms
|
| `config.nu` parse + eval (52KB, ~150KB of `use`d modules) | 72ms |
39ms |
| `_fzf_integration.nu` autoload (28KB) | 23ms | 5ms |
| Plugin registry load (20 plugins, 289 commands) | 10.5ms | 2.2ms |
| `gstat` per call (nushell repo, with tag) | 275ms | 68ms |
| `nu -c null` wall clock (config loaded) | 25.5ms | 18.0ms |
| `nu -n --no-std-lib -c null` wall clock | 9.7ms | 9.7ms |
| `nu -n --no-std-lib` REPL to first prompt | ~12ms (est.) | 12ms |
| `$nu.startup-time` in a script | -1ns | real value |

## User-facing changes (Release notes)

- `$nu.startup-time` now measures from process creation (on macOS and
Windows; from `main` on other platforms) to the moment the first prompt
is ready, so it includes config files, plugins, `env_change` and
`pre_prompt` hooks, and prompt evaluation. Hooks that run before the
first prompt see a provisional value; the final value is available from
the first command on.
- The banner's "Startup Time" line is now printed right before the first
prompt, after the startup hooks, so it shows the final value. The
welcome message still appears first; output from
`env_change`/`pre_prompt` hooks now lands between the welcome message
and the startup time line. `banner` gained a `--no-startup-time` flag.
- `$nu.startup-time` is now set when running a script (it was `-1ns`).
- Improved performance of parsing config files and modules: command
lookups no longer rebuild overlay visibility maps, which roughly halves
config load time for configs that `use` many modules.
- Improved performance of loading the plugin registry file (about 5x on
a 20-plugin registry).
- `gstat` computes the nearest tag much faster on large repositories
(about 4x on the nushell repo); output is unchanged.
- `table` and `term size` no longer spawn `tput` when no terminal is
attached; they use `COLUMNS`/`LINES`, or 80x24, instead.
- `--log-level perf` now reports the time spent before `main`, engine
setup, standard library loading, and a final "startup (process start to
first prompt)" line.

## Additional notes
<!--
Optional.

Examples:
- fixes #123
- closes #456
- related #789

Anything else reviewers should know.
Remove this section if not needed.
-->
2026-09-18 18:56:09 -05:00
Martin Kronberger c2c56fe88e fix(ci): run ready_for_review CI on the PR head, not on main (#19047)
#18586 runs CI on `ready_for_review` through `pull_request_target`.
That event checks out the base branch, thus those runs built `main` and
reported the result on the PR.
41 runs since, none of them tested the PR.
#19042 got its green check that way ([this
run](https://github.com/nushell/nushell/actions/runs/35272087069)
checked out `6359cda37`) and broke `main` on merge.

This moves `ready_for_review` into the `types` of the `pull_request`
trigger instead.
Keeping `pull_request_target` and checking out the PR head is not an
option, since it would build fork code with base-repo permissions,
partly on the self-hosted runners.
2026-09-18 21:53:52 +02:00
Martin Kronberger 8eed1489d6 fix(reedline): forward Menu::settings through SourcedMenu (#19043)
## Description

Fixes the broken build on `main` after #19042.
nushell/reedline#1215 made `Menu::settings` a required method, and
`SourcedMenu` never forwarded it, so `nu-cli` stopped compiling.
This adds the forward and bumps reedline to c9e7035, which includes
nushell/reedline#1220 exporting `MenuSettings`, since without that
export the return type could not be named here.

## User-facing changes (Release notes)

## Additional notes
2026-09-18 00:48:09 +02:00
Martin Kronberger ab6dafdbf1 chore(reedline): bump reedline to e0f1c0b (#19042) 2026-09-17 23:01:48 +02:00
Louis Deconinck 1e600005f3 fix(query web): parse headerless tables row by row (#19041)
## Description

Fixes headerless HTML tables with multiple rows being accumulated into a
single `Record`, which produced duplicate `column0`, `column1`, ... keys
and a `nu::shell::plugin_failed_to_decode` error ("duplicate keys are
not allowed for `Record`").

In `retrieve_table()`, the `cols.is_empty()` branch (no `<th>` headers
anywhere, `-t []` passed) created one `Record` outside the loop over
HTML rows and pushed every row's cells into it. With more than one row
the generated `columnN` keys repeated within the same record. A fresh
`Record` is now created per row so each `<tr>` becomes its own record —
a natural row-by-row reading, as discussed in the issue.

Minor observable change: a completely empty `<table></table>` now
returns an empty list instead of a list containing one empty record.

Tables with `<th>` headers, tables with row headers, and explicit `-t
[columns]` selections are unchanged.

## User-facing changes (Release notes)

Fixed `query web -t []` failing with `plugin_failed_to_decode` on HTML
tables that have no `<th>` headers and more than one row. Such tables
now produce one record per row with generated `column0`, `column1`, ...
column names.

## Additional notes

Fixes #18471

Tests: added `test_table_without_headers_multiple_rows` (regression —
fails on the old code with the duplicated-keys record),
`test_table_without_headers_one_row`, and `test_table_with_headers`,
exercising `retrieve_tables` directly with embedded HTML fixtures. Ran
`cargo test -p nu_plugin_query` (56 passed), `cargo fmt --all --
--check`, and `cargo clippy --workspace -- -D warnings -D
clippy::unwrap_used`, all clean. Also verified end-to-end with a locally
built `nu` + `nu_plugin_query` that the issue's reproduction now outputs
a 3-row table.
2026-09-17 15:57:42 -05:00