Compare commits

...

178 Commits

Author SHA1 Message Date
36427a7434 update to rust version 1.87.0 (#16437)
The PR upgrades nushell to rust version 1.87.0.

## Dev overview from clippy
- I added `result_large_err` to clippy in the root Cargo.toml to avoid
the warnings (and a few places in plugins). At some point a more proper
fix, perhaps boxing these, will need to be performed. This PR is to just
get us over the hump.
- I boxed a couple areas in some commands
- I changed `rdr.bytes()` to `BufReader::new(rdr).bytes()` in nu-json

## Release notes summary - What our users need to know
Users can use rust version 1.87.0 to compile nushell now

## Tasks after submitting
N/A
2025-08-14 11:27:34 -05:00
daf52ba5c8 refactor: run env_change hooks before pre_prompt hooks (#16356)
Change the order of hook evaluations, run `env_change` before `pre_prompt`.
New order of execution is: `env_change` -> `pre_prompt` -> `PROMPT_COMMAND`
2025-08-14 02:11:05 +03:00
2f7f00001d feat(std/bench) add osc 9;4 progress bar (#16245)
# Description
Using `osc 9;4`, `bench` shows a progress bar or circle on supported
terminals, every 10 timing rounds to not degrade performance too much.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-08-13 16:49:39 -04:00
ee7334a772 feat(get,select,reject): add --ignore-case which interprets cell-paths case insensitively, analogous to --optional (#16401)
# Description
Follow up to #16007

Also added some examples for existing flags which were previously
missing.

After the deprecation period of `--ignore-errors (-i)`, `-i` will be
used as the short form of this flag.

# User-Facing Changes
`get`, `select`, `reject` commands now have a `--ignore-case` flag,
which makes the commands interpret all cell-path arguments as completely
case insensitive.

# Tests + Formatting
+1

# After Submitting
Set a reminder for the `--ignore-errors` deprecation and using `-i` as
the short flag. Maybe we can make PRs in advance for future versions and
mark them with GitHub's milestones.
2025-08-13 16:45:33 -04:00
3fe9c7c00c feat(each): noop on single null input, map-null equivalent (#16396)
# Description
Basically, now `null | each { "something" }` will be `null` instead of
`"something"`. Thanks to this, `each` can be used to map values similar
to `map-null` custom commands, for example:
- Before
```nu
let args = if $delay != null {
    ["--delay" ($delay | format duration sec | parse '{secs} {_}' | get 0.secs)]
} else {
    []
}
```
- After
```nu
let args = (
    $delay
    | each { ["--delay" ($in | format duration sec | parse '{secs} {_}' | get 0.secs)] }
    | default []
)
```

Please let me know if this change messes something up I'm not seeing.
# User-Facing Changes
- Before
```nu
→ null | each { "something" }
something
```
- After
```nu
→ null | each { "something" }
null
```
# Tests + Formatting
Added a test to check for this.

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->

---------

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
2025-08-13 15:22:24 -05:00
43992f5b6f Rework PR template (#16412) 2025-08-13 22:10:47 +02:00
91e72ae8b4 Validate std/random dice args (#16430)
Fixes #16429
2025-08-13 21:50:49 +02:00
da54ed8ea1 docs: document undocumented Signature methods (#16417)
Just a small PR which documents methods. I got confused what `rest` did
while working on a plugin, so I decided to document all of them.
2025-08-13 20:58:30 +02:00
3eabc83c61 build(deps): bump crate-ci/typos from 1.35.1 to 1.35.4 (#16424) 2025-08-13 18:55:36 +00:00
cc4a4a11f0 build(deps): bump sysinfo from 0.36.0 to 0.36.1 (#16423) 2025-08-13 18:55:11 +00:00
d53e16748d build(deps): bump rayon from 1.10.0 to 1.11.0 (#16422) 2025-08-13 18:54:22 +00:00
4ead4ce4d6 feat: move random dice to std (#16420)
# Description

As per the suggestion in #16350, this PR moves `random dice` to std.
It's basically a thin wrapper over `random int` already.

# User-Facing Changes (deprecations)

## `random dice` moved to `std`

The `random dice` command has been rewritten in Nushell and moved to the
standard library. The `random dice` built-in is still available with a
deprecation error, but will be removed in 0.108. The new command can be
used as follows:

```nushell
use std/random

random dice
```

It's behavior, parameters, and defaults are the same.


# After Submitting

Update documentation to reflect the change.

Closes #16350
2025-08-13 20:45:45 +02:00
31606a8fe1 Kill background jobs on interrupt (#16285)
# Description
This PR kills all background jobs on interrupt, as a fix for
https://github.com/nushell/nushell/issues/15947.

# User-Facing Changes
If you run the following: `job spawn { print "job spawned"; ^sleep
infinity }; ^sleep infinity`, then hit ctrl-c, the current behavior is
that the `sleep` process from the job will not be killed, it will
reparented to init. With this change, the process will be killed on
ctrl-c.

# Tests + Formatting
I was unsure of the best way to write a test for this.

# After Submitting

---------

Co-authored-by: 132ikl <132@ikl.sh>
2025-08-13 13:11:40 -04:00
7133a04e2f Improve wrong flag help (#16427)
# Description

Currently, when Nushell encounters an unknown flag, it prints all
options in the help string. This is pretty verbose and uses the
`formatted_flags` signature method, which isn't used anywhere else. This
commit refactors the parser to use `did_you_mean` instead, which only
suggest one closest option or sends the user to `help` if nothing close
is found.


# User-Facing Changes (Bug fixes and other changes)

## Improved error messages for misspelled flags

Previously, the help text for a missing flag would list all of them,
which could get verbose on a single line:

```nushell
~> ls --full-path
Error: nu::parser::unknown_flag

  × The `ls` command doesn't have flag `full-path`.
   ╭─[entry #8:1:4]
 1 │ ls --full-path
   ·    ─────┬─────
   ·         ╰── unknown flag
   ╰────
  help: Available flags: --help(-h), --all(-a), --long(-l), --short-names(-s), --full-paths(-f), --du(-d), --directory(-D), --mime-type(-m), --threads(-t). Use
        `--help` for more information.
```

The new error message only suggests the closest flag:

```nushell
> ls --full-path
Error: nu::parser::unknown_flag

  × The `ls` command doesn't have flag `full-path`.
   ╭─[entry #23:1:4]
 1 │ ls --full-path
   ·    ─────┬─────
   ·         ╰── unknown flag
   ╰────
  help: Did you mean: `--full-paths`?
```


---

Closes #16418
2025-08-13 06:25:18 -05:00
79a6c78032 implement FromValue for std::time::Duration and refactor relevant commands to utilize that (#16414)
# Description

- Implemented `FromValue` for `std::time::Duration`.
- It only converts positive `Value::Duration` values, negative ones
raise `ShellError::NeedsPositiveValue`.
- Refactor `job recv` and `watch` commands to use this implementation
rather than handling it ad-hoc.
- Simplified `watch`'s `debounce` & `debounce-ms` and factored it to a
function. Should make removing `debounce-ms` after its deprecation
period ends.
- `job recv` previously used a numeric cast (`i64 as u64`) which would
result in extremely long duration values rather than raising an error
when negative duration arguments were given.

# User-Facing Changes

Changes in error messages:
- Providing the wrong type (bypassing parse time type checking):
  - Before
    ```
    Error: nu:🐚:type_mismatch

      × Type mismatch.
       ╭─[entry #40:1:9]
     1 │ watch . --debounce (1 | $in) {|| }
       ·         ──────────┬─────────
       ·                   ╰── Debounce duration must be a duration
       ╰────
    ```
  - After
    ```
    Error: nu:🐚:cant_convert

      × Can't convert to duration.
       ╭─[entry #2:1:9]
     1 │ watch . --debounce (1 | $in) {|| }
       ·         ──────────┬─────────
       ·                   ╰── can't convert int to duration
       ╰────
    ```
- Providing a negative duration value:
  - Before
    ```
    Error: nu:🐚:type_mismatch

      × Type mismatch.
       ╭─[entry #41:1:9]
     1 │ watch . --debounce -100ms {|| }
       ·         ────────┬────────
       ·                 ╰── Debounce duration is invalid
       ╰────
    ```
  - After
    ```
    Error: nu:🐚:needs_positive_value

      × Negative value passed when positive one is required
       ╭─[entry #4:1:9]
     1 │ watch . --debounce -100ms {|| }
       ·         ────────┬────────
       ·                 ╰── use a positive value
       ╰────
    ```
2025-08-12 22:25:23 +02:00
5478bdff0e feat: null values can be spread as if they are empty lists or records. (#16399)
# Description
Spread operator `...` treats `null` values as empty collections,
, whichever of list or record is appropriate.

# User-Facing Changes
`null` values can be used with the spread operator (`...`)
2025-08-11 23:47:31 +03:00
a4711af952 feat: impl<B> for (From/Into)Value for Cow<'_, B> where B::Owned: (From/Into)Value (#16380)
# Description
Implements `FromValue` and `IntoValue` for `Cow`, which makes it easy to
use it in types that need to implement these traits.

I don't think it will have a significant impact, but it can let us avoid
allocations where we can use static values.

# Tests + Formatting
No need, the implementations are delegated to `B::Owned`.

Co-authored-by: Bahex <Bahex@users.noreply.github.com>
2025-08-11 19:47:50 +02:00
751ef6e8da [parser] Improve type errors for boolean literals (#16408)
Currently, strings `true` and `false` are intercepted early in parsing.
Previously, if they didn't match the expected shape, the parse error
said "expected non-boolean value". This PR changes the message to say
"expected <shape>".

Because the parsing error requires a `&'static str` I had to add a
`to_str(&self) -> &'static str` method on `SyntaxShape`. It's a bit
crude for more complex shapes: it simply says `keyword`, `list`,
`table`, and so on for them, without exposing the underlying structure.

Fixes #16406
2025-08-11 11:59:27 -05:00
038f8f85ed Fix missing $env.PATH unhelpful error and breaking CMD builtins (#16410)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->
Fixes #16409 

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->
After running `hide-env PATH`, there is a more helpful error and CMD
builtins still work.
<img width="503" height="217" alt="image"
src="https://github.com/user-attachments/assets/a43180f9-5bc2-43bd-9773-aa9ad1818386"
/>
<img width="779" height="253" alt="image"
src="https://github.com/user-attachments/assets/03b59209-f9a9-4c61-9ea2-8fbdc27b8d4b"
/>


# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->

---------

Co-authored-by: 132ikl <132@ikl.sh>
2025-08-11 12:55:22 -04:00
4245c67ce3 Add --raw flag to to html (#16373)
Works towards #16347 however more work may be required first

# Description
Adds a `--raw` flag to `to html`. This stops the resulting html content
being escaped
# User-Facing Changes

# Tests + Formatting
# After Submitting
2025-08-11 21:31:24 +08:00
e56879e588 fix(parser): missing span of short flag that requires a value (#16376)
Fixes #16375

# Description

# User-Facing Changes

Bug fix

# Tests + Formatting

+0.5
2025-08-09 23:50:55 +02:00
c75e7bfbd3 Fix watch return type (#16400)
Refs
https://discord.com/channels/601130461678272522/615329862395101194/1403760147985207487

# Description

Currently `watch` doesn't normally return, ever. The only way to stop it
is to abort with `Ctrl+C` (or some internal error happens), so it never
produces a usable pipeline output. Since nu doesn't have `never` type
yet, `nothing` is the closest thing we can use.

# User-Facing Changes

Users may start to get type errors if they used `watch .... | something`
and the `something` did not accept `nothing`.

# Tests + Formatting

All pass.
2025-08-09 23:43:51 +02:00
e8579a9268 Add examples for receiving data from a job (#16372)
# Description

The existing examples cover how to send data to a job, but I think it
will be much more common to want to receive data from a job.

# User-Facing Changes

Just documentation, though it may be worth highlighting anyway. I really
thought for a while that this was not possible yet. See also my book PR
https://github.com/nushell/nushell.github.io/pull/2006 (`job send` and
`job recv` were not documented in the book at all).
2025-08-09 07:39:44 +08:00
3dead9a001 Respect $env.LC_ALL and $env.LANG in format date (#16369)
Refs
https://github.com/nushell/nushell/issues/16368#issuecomment-3160728758

# Description

Respect user preference for date/time formats, in a more compatible way.
Environment variable order is taken from
https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html.
Previously, only `$env.LC_TIME` was consulted to select the locale.

# User-Facing Changes

Users will be able to specify the format preference via `$env.LANG` and
override it with `$env.LC_ALL`.

# Tests + Formatting

All pass.
2025-08-09 07:36:34 +08:00
0b106789a7 build(deps): bump mach2 from 0.4.2 to 0.4.3 (#16363)
Bumps [mach2](https://github.com/JohnTitor/mach2) from 0.4.2 to 0.4.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/JohnTitor/mach2/releases">mach2's
releases</a>.</em></p>
<blockquote>
<h2>0.4.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Add <code>time_value</code> by <a
href="https://github.com/ldm0"><code>@​ldm0</code></a> in <a
href="https://redirect.github.com/JohnTitor/mach2/pull/25">JohnTitor/mach2#25</a></li>
<li>Add <code>mach-o/dyld.h</code> items by <a
href="https://github.com/JohnTitor"><code>@​JohnTitor</code></a> in <a
href="https://redirect.github.com/JohnTitor/mach2/pull/32">JohnTitor/mach2#32</a></li>
<li>Add <code>struct</code> prefix on all the structs in C by <a
href="https://github.com/JohnTitor"><code>@​JohnTitor</code></a> in <a
href="https://redirect.github.com/JohnTitor/mach2/pull/33">JohnTitor/mach2#33</a></li>
<li>chore(ci): Update macOS version by <a
href="https://github.com/JohnTitor"><code>@​JohnTitor</code></a> in <a
href="https://redirect.github.com/JohnTitor/mach2/pull/37">JohnTitor/mach2#37</a></li>
<li>Configure Renovate by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/JohnTitor/mach2/pull/36">JohnTitor/mach2#36</a></li>
<li>chore(deps): pin dependencies by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/JohnTitor/mach2/pull/39">JohnTitor/mach2#39</a></li>
<li>chore: Use original ctest by <a
href="https://github.com/JohnTitor"><code>@​JohnTitor</code></a> in <a
href="https://redirect.github.com/JohnTitor/mach2/pull/40">JohnTitor/mach2#40</a></li>
<li>chore: Prepare 0.4.3 release by <a
href="https://github.com/JohnTitor"><code>@​JohnTitor</code></a> in <a
href="https://redirect.github.com/JohnTitor/mach2/pull/41">JohnTitor/mach2#41</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/ldm0"><code>@​ldm0</code></a> made their
first contribution in <a
href="https://redirect.github.com/JohnTitor/mach2/pull/25">JohnTitor/mach2#25</a></li>
<li><a href="https://github.com/renovate"><code>@​renovate</code></a>
made their first contribution in <a
href="https://redirect.github.com/JohnTitor/mach2/pull/36">JohnTitor/mach2#36</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/JohnTitor/mach2/compare/0.4.2...0.4.3">https://github.com/JohnTitor/mach2/compare/0.4.2...0.4.3</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ed16d350ab"><code>ed16d35</code></a>
Merge pull request <a
href="https://redirect.github.com/JohnTitor/mach2/issues/41">#41</a>
from JohnTitor/chore/0.4.3</li>
<li><a
href="86b3f278c4"><code>86b3f27</code></a>
chore: Prepare 0.4.3 release</li>
<li><a
href="6b9a8bcdca"><code>6b9a8bc</code></a>
Merge pull request <a
href="https://redirect.github.com/JohnTitor/mach2/issues/40">#40</a>
from JohnTitor/chore/ctest</li>
<li><a
href="ae47e1935b"><code>ae47e19</code></a>
chore: Use original ctest</li>
<li><a
href="52f3edbfaf"><code>52f3edb</code></a>
Merge pull request <a
href="https://redirect.github.com/JohnTitor/mach2/issues/39">#39</a>
from JohnTitor/renovate/pin-dependencies</li>
<li><a
href="ceee600d4b"><code>ceee600</code></a>
chore(deps): pin dependencies</li>
<li><a
href="ac9f277587"><code>ac9f277</code></a>
chore(renovate): Enable GHA digests helper</li>
<li><a
href="3f3769660c"><code>3f37696</code></a>
Merge pull request <a
href="https://redirect.github.com/JohnTitor/mach2/issues/36">#36</a>
from JohnTitor/renovate/configure</li>
<li><a
href="ab4d126d28"><code>ab4d126</code></a>
Add renovate.json</li>
<li><a
href="d156e25e99"><code>d156e25</code></a>
Merge pull request <a
href="https://redirect.github.com/JohnTitor/mach2/issues/37">#37</a>
from JohnTitor/chore/update-macos-version</li>
<li>Additional commits viewable in <a
href="https://github.com/JohnTitor/mach2/compare/0.4.2...0.4.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=mach2&package-manager=cargo&previous-version=0.4.2&new-version=0.4.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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-08-09 07:30:56 +08:00
bf83756562 Fix example result span (#16395)
Refs
https://discord.com/channels/601130461678272522/614593951969574961/1403435414416654427

# Description

Previously Example result values would have a test span, which would
cause hard to understand errors for the code that uses `scope commands`.

Now they will have the span that points to `scope commands` invocation.

# User-Facing Changes

Errors referencing example results will get slightly better.

# Tests + Formatting

All pass.
2025-08-09 07:29:27 +08:00
06fa1784c1 Clarify that input's history feature uses reedline (#16334)
Follow up to this commit @sholderbach made on my PR #16329:
f21350ec88 (diff-5cab4dac5ced236548db9fbf6cd0e9d250ba12317bb916ec26603054ce9144a7)
2025-08-08 22:20:42 +02:00
e6d673c39e fix (std/help): fix bug and use is-not-empty (#16394)
# Description
In #16354, I introduced a bug thinking that `table -e` would always
return a string, so I fix it here restoring the `to text`. While I was
at it, I changed all instances of `if not ($var | is-empty)` to `if
($var | is-not-empty)` to improve readability.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-08-08 13:58:33 -05:00
c4fcd54573 nu-table: Fix header on border index coloring (#16377)
Sorry for delay

close #16345

<img width="519" height="263" alt="image"
src="https://github.com/user-attachments/assets/e5fe2a23-5a47-4c18-933f-6cf936ea702c"
/>

About the incorrect alignment of the index header `#` it shall be fixed
with the next `tabled` release.

cc: @fdncred
2025-08-07 08:19:39 -05:00
4e56cd5fc4 fix(parser): external argument with subexpressions (#16346)
Fixes: #16040

# Description

TBH, I not a fan of this whole `parse_external_string` idea.
Maybe I lack some of the background knowledge here, but I don't see why
we choose not to
1. parse external arguments the same way as internal ones
2. treat them literally at runtime if necessary

Tests: +1
2025-08-06 22:17:58 +03:00
0e3ca7b355 build(deps): bump fancy-regex from 0.14.0 to 0.16.1 (#16365) 2025-08-06 05:26:49 +00:00
2b69bd9b6d build(deps): bump crate-ci/typos from 1.34.0 to 1.35.1 (#16360) 2025-08-06 12:27:52 +08:00
3a82c6c88d Fix panic in unit parsing with non-UTF8 code (#16355)
# Description
Trying to parse non-UTF8 data as a value with unit (part of every
literal parse) introduced a replacement character which shifted the span
information so the indices where incorrect and triggered
a panic.

This has been resolved by removing a bad `String::from_utf8_lossy`

# User-Facing Changes
One less possible panic

# Tests + Formatting
Added a test with the original reproducer from fuzzing:

File with `0\xffB` where the `\xff` represents the non utf-8 char `FF`
run as a script to trigger
2025-08-05 22:08:33 +02:00
61a89c1834 Fully qualify the sqlite path for into sqlite (#16349)
- related #16258

# Description


In #16258 we had some trouble getting tests to work properly. After some
investigation with @WindSoilder we figured out that `Table::new` inside
`into_sqlite.rs` did not respect the `$env.PWD`. The underlying
`open_sqlite_db` and in that `Connection::open` respects the current
working directory. That one is updated via `cd` but not necessarily in
tests (and we should not try that). In this PR I join the `$env.PWD`
with the path passed to `into sqlite`.

This PR also adds a test for the auto conversion from #16258.

# User-Facing Changes

Should be none, some edge cases might be fixed now.
2025-08-05 22:02:33 +02:00
fcdc7f3d83 fix(std/help): trim example results and fix binary examples (#16354)
# Description
Added trimming to the example results like in the normal `help`, and
also changed the logic for binary examples to remove the weird spacing.
- Before:
<img width="998" height="919" alt="image"
src="https://github.com/user-attachments/assets/03f18f45-5b12-41bc-b495-232bcf899964"
/>

- After:
<img width="959" height="720" alt="image"
src="https://github.com/user-attachments/assets/894dc622-c603-467c-8904-aef582a82b0a"
/>

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-08-05 13:21:23 -05:00
2b70d27cdf fix(help): don't trim example result beginning (#16353)
# Description
The `help` command, when printing the examples with results, trims the
first line and it appears unindented compared to the following lines.

- Before:
<img width="1110" height="346" alt="image"
src="https://github.com/user-attachments/assets/3487381d-3631-49c9-bb0e-f7ad958b7291"
/>

- After:
<img width="1123" height="339" alt="image"
src="https://github.com/user-attachments/assets/acb45afd-0492-49d2-a5cb-5130bbb4cf94"
/>

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-08-05 13:20:37 -05:00
8c2af9941c feat(std-rfc/str): add str align (#16062) 2025-08-06 00:21:23 +08:00
f015409253 [nu-std] std-rfc/random: add random choice (#16270)
# Description

Adds `random choice` suggested in #16241.

# User-Facing Changes

New `random` module in `std-rfc` with the `choice` subcommand.

# Tests + Formatting

Unsure how do to do tests. Sampling and a histogram should be enough,
but they'll be non-deterministic.

# After Submitting

<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->

---------

Co-authored-by: sholderbach <sholderbach@users.noreply.github.com>
2025-08-04 20:36:28 +02:00
f33d952adf 'stor create/insert/open' & 'query db' now support JSON columns (#16258)
Co-authored-by: Tim 'Piepmatz' Hesse <git+github@cptpiepmatz.de>
2025-08-04 16:58:21 +02:00
9f4c3a1d10 Fix path relative-to for case-insensitive filesystems (#16310)
fixes #16205 

# Description

1. **Adds fallback**: On case-insensitive filesystems (Windows, macOS),
falls back to case-insensitive comparison when the standard comparison
fails
2. **Maintains filesystem semantics**: Only uses case-insensitive
comparison on platforms where it's appropriate

## Before:
```console
$> "/etc" | path relative-to "/Etc"
Error: nu:🐚:cant_convert

  × Can't convert to prefix not found.
   ╭─[entry #33:1:1]
 1 │ "/etc" | path relative-to "/Etc"
   · ───┬──
   ·    ╰── can't convert string to prefix not found
   ╰────
```

## After:
For Windows and macOS:
```console
$> "/etc" | path relative-to "/Etc" | debug -v
""
```
2025-08-04 22:32:31 +08:00
4f9c0775d9 Change the behavior of --ignore-case and --multiline options for find (#16323)
# Description

Changes the behavior of `--ignore-case` and `--multiline` options for
`find`, to make them more consistent between regex mode and search term
mode, and to enable more options for using find.

# User-Facing Changes

Search term mode is now case-sensitive by default.

`--ignore-case` will make the search case-insensitive in search term
mode. In regex mode, the previous behavior of adding a (?i) flag to the
regex is preserved.

`--multiline` will no longer add a (?m) flag in regex mode. Instead, it
will make the search not split multi-line strings into lists of lines.

closes #16317
closes #16022
2025-08-04 22:27:00 +08:00
d528bb713b Fix UTF-8 multibyte handling in explore inputs (#16325)
# Description
`explore` was reading the byte length for computing:
- `:` command input cursor positions
- `/`/`?` search mode cursor positions
- `:try` input box cursor positions
- search highlighting

Fixed this for the majority of cases by using `unicode_width`, this is
only best effort as terminals don't need to follow those expectations
but for the most cases (traditional language scripts etc.) this should
lead to better result. The only way around the uncertainty would be to
perform the highlighting/cursor marking as we go, but this may not be as
compatible with the `ratatui` framework.

Closes #16312

# User-Facing Changes
Fixed cursor position and search highlighting for non-ASCII characters,
with the caveat mentioned above.

# Tests + Formatting
Manually tested
2025-08-03 21:20:35 +02:00
7cc1a86459 typo: help format filesize has a wrong example (#16336)
Just a small one letter typo in the help command. It should be `kB`

<img width="943" height="688" alt="image"
src="https://github.com/user-attachments/assets/fcca3978-cc0d-483f-b74e-465743213b76"
/>
2025-08-03 13:27:03 +02:00
dfbd98013d Add send: vichangemode to reedline config (#16327)
# Description
Allows custom bindings (non-chord) to send a `edit_mode: vi` mode change
via the new `ReedlineEvent::ViChangeMode`
Takes https://github.com/nushell/reedline/pull/932

# User-Facing Changes
You can now set bindings which change the Vi mode. (This still has the
same rules for defining the key-combination: Only modifiers and single
keys are supported)
To do so send a `vichangemode` event with the `mode` field to set
`normal`, `insert`, or `visual`

```nushell
$env.config.keybindings ++=
	[{
	    name: modechangetest
	    modifier: control
	    keycode: "char_["
	    mode: [vi_normal, vi_insert]
	    event: {send: vichangemode, mode: normal}
	}]

```
2025-08-03 13:23:55 +02:00
2c9f6acc03 Forgo full build in the cargo hack wf (#16328)
We ran out of disk space on Github actions with the build part of this
workflow. We hope that `check` should catch the worst offenders.
2025-08-02 21:04:43 +02:00
007d15ed9f Add multiline example for input command (#16329)
# Description

Adds an example that documents how to use `input --reedline` to collect
multiple lines of input from the user

I also removed an extraneous and inconsistent space in the following
example.

# User-Facing Changes

Documentation addition

# Tests + Formatting

I did not run any tests or autoformatters because of the docs-only
nature of the change, and the fact that I copy-pasted the format from an
existing example. If the autoformatter is unhappy, I apologize.

# After Submitting

This PR should automatically update the docs site at the next release,
so no need to do anything there.

---------

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2025-08-02 21:04:20 +02:00
3e37922537 Bump ureq, get redirect history. (#16078) 2025-08-02 13:55:37 +02:00
1274d1f7e3 Add -h/--help flag to testbin (#16196)
# Description
As title, this pr introduce `-h` flag to testbin, so if I want to see
which testbin I should use, I don't need to look into source code.

### About the change
I don't know if there is any way to get docstring of a function inside
rust code. So I created a trait, and put docstring into it's `help`
method:
```rust
pub trait TestBin {
    // the docstring of original functions are moved here.
    fn help(&self) -> &'static str;
    fn run(&self);
}
```
Take `cococo` testbin as example, the changes are:
```
original cococo function --> Cococo struct, then
1. put the body of `cococo` function into `run` method
2. put the docstring of `cococo` function into `help` method
```

# User-Facing Changes

`-h/--help` flag in testbin is enabled.
```
> nu --testbin -h
Usage: nu --testbin <bin>
<bin>:
chop -> With no parameters, will chop a character off the end of each line
cococo -> Cross platform echo using println!()(e.g: nu --testbin cococo a b c)
echo_env -> Echo's value of env keys from args(e.g: nu --testbin echo_env FOO BAR)
echo_env_mixed -> Mix echo of env keys from input(e.g: nu --testbin echo_env_mixed out-err FOO BAR; nu --testbin echo_env_mixed err-out FOO BAR)
echo_env_stderr -> Echo's value of env keys from args to stderr(e.g: nu --testbin echo_env_stderr FOO BAR)
echo_env_stderr_fail -> Echo's value of env keys from args to stderr, and exit with failure(e.g: nu --testbin echo_env_stderr_fail FOO BAR)
fail -> Exits with failure code 1(e.g: nu --testbin fail)
iecho -> Another type of echo that outputs a parameter per line, looping infinitely(e.g: nu --testbin iecho 3)
input_bytes_length -> Prints the number of bytes received on stdin(e.g: 0x[deadbeef] | nu --testbin input_bytes_length)
meow -> Cross platform cat (open a file, print the contents) using read_to_string and println!()(e.g: nu --testbin meow file.txt)
meowb -> Cross platform cat (open a file, print the contents) using read() and write_all() / binary(e.g: nu --testbin meowb sample.db)
nonu -> Cross platform echo but concats arguments without space and NO newline(e.g: nu --testbin nonu a b c)
nu_repl -> Run a REPL with the given source lines
relay -> Relays anything received on stdin to stdout(e.g: 0x[beef] | nu --testbin relay)
repeat_bytes -> A version of repeater that can output binary data, even null bytes(e.g: nu --testbin repeat_bytes 003d9fbf 10)
repeater -> Repeat a string or char N times(e.g: nu --testbin repeater a 5)
```

# Tests + Formatting
None, all existed tests can guarantee the behavior of testbins doesn't
change.

# After Submitting
NaN
2025-08-02 10:48:07 +08:00
da9615f971 Fix parse-time pipeline type checking to support multiple output types for same input type (#16111)
# Description
Fixes #15485

This PR changes pipeline checking to keep track of all possible output
types instead of only first type matching input type which appears in
the input/output types. For example, in this command:
```nushell
def foo []: [int -> string, int -> record] {
  # ...
}
```
An `int` input to the command may result in a string or a record to be
output. Before this PR, Nushell would always assume that an `int` input
would cause a `string` output because it's the first matching
input/output type pair. This would cause issues during type checking
where the parser would incorrectly determine the output type. After this
PR, Nushell considers the command to output either a string or a record.

# User-Facing Changes
* Parse-time pipeline type checking now properly supports commands with
multiple pipeline output types for the same pipeline input type

# Tests + Formatting
Added a couple tests

# After Submitting
N/A

---------

Co-authored-by: Bahex <Bahex@users.noreply.github.com>
2025-08-02 09:35:25 +08:00
eb8d2d3206 Refactor: introduce 2 associated functions to PipelineData (#16233)
# Description
As title: this pr is try to introduce 2 functions to `PipelineData`:
1. PipelineData::list_stream --> create a PipelineData::ListStream
2. PipelineData::byte_stream -> create a PipelineData::ByteStream
And use these functions everywhere.

### Reason behind this change
I tried to implement `pipefail` feature, but this would required to
change `PipelineData` from enum to struct. So use these functions can
reduce diff if I finally change to struct. [Discord message
here](https://discord.com/channels/601130461678272522/615962413203718156/1396999539000479784)
is my plan.

# User-Facing Changes
NaN

# Tests + Formatting
NaN

# After Submitting
NaN
2025-08-02 09:30:30 +08:00
ee5b5bd39e fix(input list): don't leak ansi styling, fuzzy match indicator preserves styles (#16276)
- fixes #16200

# Description

|    | Select           | Fuzzy           |
| -- | ---------------- | --------------- |
|  | ![select-before] | ![fuzzy-before] |
|  | ![select-fixed]  | ![fuzzy-fixed]  |

[select-before]:
8fe9136472/select-before.svg
[select-fixed]:
8fe9136472/select-after.svg
[fuzzy-before]:
8fe9136472/fuzzy-before.svg
[fuzzy-fixed]:
8fe9136472/fuzzy-after.svg

Using a custom `dialoguer::theme::Theme` implementation, how `input
list` renders items are overridden.

Unfortunately, implementing one of the methods requires
`fuzzy_matcher::skim::SkimMatcherV2` which `dialoguer` does not export
by itself.
Had to add an explicit dependency to `fuzzy_matcher`, which we already
depend on through `dialoguer`. Version specification is copied from
`dialoguer`.

# Tests + Formatting
No tests added.
Couldn't find existing tests, not sure how to test this.

---------

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
2025-07-31 22:42:10 +02:00
d565c9ed01 Fix commit ID hex formatting in gstat (#16309)
Fix commit ID hex formatting in gstat, closes #16307 

Leading zeros are preserved, maintaining the correct hex representation

This issue is relatively easy to fix, but it's not very easy to verify.
However, I have already tested several scenarios, it works for commit
sha like `000baeef`, `000003c7` and `00000002` etc.
2025-07-31 09:59:14 -05:00
18d5d8aae1 Fixup pre-release checkup workflow (#16305)
Avoid going out of disk by running cargo clean between each build:
https://github.com/taiki-e/cargo-hack#--clean-per-run

Also rename to something shorter for the overview
2025-07-30 23:46:18 +02:00
89c0e325fa fix panic when ..= syntax is used in stepped ranges (#16231)
Fixes #16185

# Description

Stepped range literals where `..=` precedes the second value no longer
cause a parser panic:

```diff
random int 1..=3..5
-Error:   x Main thread panicked.
-  |-> at crates/nu-protocol/src/engine/state_working_set.rs:400:48
-  `-> slice index starts at 8 but ends at 7
+Error: nu::parser::parse_mismatch
+
+  × Parse mismatch during operation.
+   ╭─[entry #1:1:15]
+ 1 │ random int 1..=3..5
+   ·               ─┬
+   ·                ╰── expected number
```
2025-07-30 23:38:59 +02:00
7f2beb49db Manual GH workflow for cargo hack before release (#16304)
Currently we run this locally on whatever machine the person doing it is
using. With this we have all three major platforms covered and don't
block a personal machine before the release. Also any failures are
visible to everyone if we need to fix something.
2025-07-30 23:05:33 +02:00
7203138880 Bump version to 0.106.2 (#16295) 2025-07-30 01:36:35 +02:00
459f3c0c28 feat(watch): implement --debounce flag with duration (#16187)
- fixes #16178
- `watch --debounce-ms` deprecated

Co-authored-by: Luca Scherzer <luca.scherzer@de.clara.net>
2025-07-29 17:10:40 +03:00
2e4900f085 Check type of row conditions at parse-time (#16175)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

As a bonus to #16174, I realized it would be trivial to add a similar
check to where.

Before:
```nushell
1..100 | where 1
# => no output...
```

After:
```nushell
1..100 | where 1
# => Error: nu::parser::type_mismatch
# => 
# =>   × Type mismatch.
# =>    ╭─[entry #3:1:16]
# =>  1 │ 1..100 | where 1
# =>    ·                ┬
# =>    ·                ╰── expected bool, found int
# =>    ╰────
```

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
* `where` should now error on row condition expressions which are not
booleans
  
# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
Added test

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
N/A
2025-07-28 22:24:12 -04:00
c921eadc6a Don't import IoError on nu-plugin-core without local-socket (#16279)
When you use `nu-plugin-core` without the `local-socket` feature, you
got the warning:
<img width="1182" height="353" alt="image"
src="https://github.com/user-attachments/assets/dd80af11-4963-4d48-8c93-43e6c2dabafa"
/>

This PR fixes that warning.
2025-07-28 20:10:17 +02:00
00ac34d716 Port unsafe_op_in_unsafe_fn fix to FreeBSD (#16275)
Same general idea as https://github.com/nushell/nushell/pull/16266

Fixes the 2024 edition
[`unsafe_op_in_unsafe_fn`](https://doc.rust-lang.org/nightly/edition-guide/rust-2024/unsafe-op-in-unsafe-fn.html)
lint for FreeBSD as well

Add safety comments to both implementations and an assertion before
`MaybeUninit::assume_init`
2025-07-28 08:42:23 +02:00
pin
28a796d5cb Fix #16261 (#16266)
- this PR should close #16261 
- fixes #16261

Confirmed to build with Rust-1.86 and to yield a working binary.
2025-07-27 21:27:35 +02:00
f8698a6c24 fix(get): run_const uses --optional flag (#16268)
`Get::run_const()` was not update along `Get::run()` in #16007.

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
2025-07-27 18:17:33 +03:00
48bca0a058 Reapply "refactor(completion, parser): move custom_completion info from Expression to Signature" (#16250) (#16259)
This reverts commit aeb517867e. The
Nushell version has bumped, so it's okay to reapply the changes from
https://github.com/nushell/nushell/pull/15613.
2025-07-25 19:57:00 -04:00
f3d92e3fa1 fix bare interpolation regression (#16235)
Regression from #16204 

Before:

![](f1995bc71f/before.svg)

After:

![](f1995bc71f/after.svg)

# Tests + Formatting
+1

---------

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
2025-07-25 08:19:15 +03:00
57dce8a386 Bump patch version (#16236)
Bump patch version
2025-07-25 03:28:24 +08:00
aeb517867e Revert "refactor(completion, parser): move custom_completion info from Expression to Signature" (#16250)
Reverts nushell/nushell#15613 because we haven't bumped to the 106.1 dev
version yet
2025-07-24 15:10:47 -04:00
71baeff287 refactor(completion, parser): move custom_completion info from Expression to Signature (#15613)
Restricts custom completion from universal to internal arguments only.

Pros:
1. Less memory
2. More flexible for later customizations, e.g. #14923 

Cons:
1. limited customization capabilities, but at least covers all currently
existing features in nushell.

# Description

Mostly vibe coded by [Zed AI](https://zed.dev/ai) with a single prompt.
LGTM, but I'm not so sure @ysthakur 

# User-Facing Changes

Hopefully none.

# Tests + Formatting

+3

# After Submitting

---------

Co-authored-by: Yash Thakur <45539777+ysthakur@users.noreply.github.com>
2025-07-24 14:21:58 -04:00
1b01625e1e Bump to 0.106.0 (#16222)
Bump to 0.106.0
2025-07-23 22:54:21 +08:00
51265b262d fix: highlighting a where command with invalid arguments can duplicate text (#16192)
- fixes #16129

# Description

## Problem
Parsing a multi span value as RowCondition always produces a RowCondition
expression that covers all the spans.

Which is problematic inside OneOf and can produce multiple expressions with
overlapping spans.
Which results in funky highlighting that duplicates text.

## Solution
Only reason for including `SyntaxShape::Closure` in the signature (#15697) was
for documentation purposes, making it clear in `help` texts that a closure can
be used as the argument.

As our current parser is shape directed, simplifying the command signature
means simplifies the parsing, so using a RowCondition on its own, and instead
making it always look like a union with `closure(any)` solves the issue without
any changes in the parser.

Also, RowCondition always accepts closure values anyway, so its textual
representation should indicate that without the need to wrap it in OneOf.

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
2025-07-22 17:24:21 +03:00
889fe7f97b Add KillLine to the EditCommand parser (#16221)
# Description
Follow-up to https://github.com/nushell/reedline/pull/901
Closes https://github.com/nushell/reedline/issues/935

# User-Facing Changes
You can now use `{edit: KillLine}` to mimic emacs `Ctrl-K` behavior`
2025-07-22 14:12:24 +02:00
93b407fa88 Fix the nu-path fuzz-target (#16188)
This code didn't compile, fixed provisionally by always running the
codepath with tilde expansion.

@IanManske worth discussing what we may want to fuzz here.
2025-07-22 14:10:25 +02:00
b0687606f7 group by to table empty (#16219)
- fixes #16217

# Description

> `group-by --to-table` does not always return a table (a list)
> ```nushell
> [] | group-by --to-table something
> #=> ╭──────────────╮
> #=> │ empty record │
> #=> ╰──────────────╯
> ```

Fixed:
```nushell
[] | group-by --to-table something
#=> ╭────────────╮
#=> │ empty list │
#=> ╰────────────╯
```

# Tests + Formatting
+1 test

---------

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
2025-07-22 14:07:55 +02:00
324aaef0af Bump reedline to 0.41.0 (#16220) 2025-07-22 13:57:19 +02:00
30c38b8c49 fix(parser): repeated ( / parenthesis / opened sub-expressions causes memory leak (#16204)
- fixes #16186

# Description

Don't attempt further parsing when there is no complete sub-expression.

---------

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
2025-07-21 08:14:35 +03:00
16167a25ec Fix typo in glob example description ("files for folders" → "files or folders") (#16206)
This pull request fixes a typo in the description of a `glob` example:

**Before:**
```
  Search for files for folders that do not begin with c, C, b, M, or s
  > glob "[!cCbMs]*"
```
**After:**
```
  Search for files or folders that do not begin with c, C, b, M, or s
  > glob "[!cCbMs]*"
```
2025-07-18 18:49:50 -05:00
38009c714c feat(completion): enable nucleo's prefer_prefix option (#16183)
cc: @blindFS @ysthakur

# Description
Enable `nucleo`'s `prefer_prefix` configuration option.
Ranks suggestions with matches closer to the start higher than
suggestions that have matches further from the start.

Example: suggestions based on `reverse`:
<table>
<tr>
<td width=200>Before</td>
<td width=200>After</td>
</tr>
<tr>
<td>

```
bytes reverse
polars reverse
reverse
str reverse
```

</td>
<td>


```
reverse
str reverse
polars reverse
bytes reverse
```

</td>
</tr>
</table>

# User-Facing Changes
More relevant suggestions with fuzzy matching algorithm.
(`$env.config.completions.algorithm = "fuzzy"`)

# Tests + Formatting
We might want to add tests to make sure this option keeps working in the
future.

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
2025-07-17 22:16:55 -04:00
AUX
e263991448 deps: bump sysinfo to v0.36 to improve sys temp command (#16195)
# Description
This PR improves `sys temp` command with component names showing up in
the unit column. See related [`sysinfo`
commit](6a5520459e)
for details.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

An example of running `sys temp` on my Linux system with the old
version:

```sh
$ nu -c 'sys temp'
╭────┬──────────────────────┬───────┬───────┬──────────╮
│  # │         unit         │ temp  │ high  │ critical │
├────┼──────────────────────┼───────┼───────┼──────────┤
│  0 │ r8169_0_500:00 temp1 │ 47.50 │ 47.50 │        │
│  1 │ Sensor 2             │ 54.85 │ 54.85 │        │
│  2 │ Composite            │ 47.85 │ 51.85 │    87.85 │
│  3 │ Sensor 1             │ 47.85 │ 72.85 │        │
│  4 │ acpitz temp1         │ 16.80 │ 16.80 │        │
│  5 │ acpitz temp2         │ 16.80 │ 16.80 │        │
│  6 │ junction             │ 66.00 │ 66.00 │   110.00 │
│  7 │ mem                  │ 60.00 │ 60.00 │   100.00 │
│  8 │ edge                 │ 57.00 │ 57.00 │   100.00 │
│  9 │ gigabyte_wmi temp4   │ 47.00 │ 47.00 │        │
│ 10 │ gigabyte_wmi temp3   │ 62.00 │ 62.00 │        │
│ 11 │ gigabyte_wmi temp1   │ 40.00 │ 40.00 │        │
│ 12 │ gigabyte_wmi temp6   │ 51.00 │ 51.00 │        │
│ 13 │ gigabyte_wmi temp5   │ 45.00 │ 45.00 │        │
│ 14 │ gigabyte_wmi temp2   │ 40.00 │ 40.00 │        │
│ 15 │ Tccd2                │ 47.00 │ 47.00 │        │
│ 16 │ Tctl                 │ 62.00 │ 62.00 │        │
╰────┴──────────────────────┴───────┴───────┴──────────╯
```
After this PR you can see the name of the components/devices:
```
$ target/debug/nu -c 'sys temp'
╭────┬──────────────────────────────────────────┬───────┬───────┬──────────╮
│  # │                   unit                   │ temp  │ high  │ critical │
├────┼──────────────────────────────────────────┼───────┼───────┼──────────┤
│  0 │ r8169_0_500:00 temp1                     │ 48.50 │ 48.50 │        │
│  1 │ nvme Sensor 1 WD Blue SN5000 1TB         │ 72.85 │ 72.85 │        │
│  2 │ nvme Sensor 2 WD Blue SN5000 1TB         │ 46.85 │ 46.85 │        │
│  3 │ nvme Composite WD Blue SN5000 1TB        │ 51.85 │ 51.85 │    87.85 │
│  4 │ acpitz temp2                             │ 16.80 │ 16.80 │        │
│  5 │ acpitz temp1                             │ 16.80 │ 16.80 │        │
│  6 │ amdgpu edge                              │ 56.00 │ 56.00 │   100.00 │
│  7 │ amdgpu junction                          │ 66.00 │ 66.00 │   110.00 │
│  8 │ amdgpu mem                               │ 58.00 │ 58.00 │   100.00 │
│  9 │ gigabyte_wmi temp4                       │ 47.00 │ 47.00 │        │
│ 10 │ gigabyte_wmi temp6                       │ 51.00 │ 51.00 │        │
│ 11 │ gigabyte_wmi temp3                       │ 48.00 │ 48.00 │        │
│ 12 │ gigabyte_wmi temp5                       │ 44.00 │ 44.00 │        │
│ 13 │ gigabyte_wmi temp2                       │ 40.00 │ 40.00 │        │
│ 14 │ gigabyte_wmi temp1                       │ 40.00 │ 40.00 │        │
│ 15 │ k10temp Tccd2                            │ 48.50 │ 48.50 │        │
│ 16 │ k10temp Tctl                             │ 49.38 │ 49.38 │        │
│ 17 │ nvme Sensor 2 Samsung SSD 970 EVO 500GB  │ 54.85 │ 54.85 │        │
│ 18 │ nvme Sensor 1 Samsung SSD 970 EVO 500GB  │ 47.85 │ 47.85 │        │
│ 19 │ nvme Composite Samsung SSD 970 EVO 500GB │ 47.85 │ 47.85 │    84.85 │
╰────┴──────────────────────────────────────────┴───────┴───────┴──────────╯
```
2025-07-18 07:17:59 +08:00
0b7c246bf4 Check for interrupt before each IR instruction (#16134)
# Description
Fixes #15308. Surprisingly, I can't find any more issues that bring this
up.

This PR adds a check for interrupt before evaluating each IR
instruction. This makes it possible to ctrl-c out of things that were
difficult/impossible previously, like:
```nushell
loop {}
```

@devyn also [mentioned
previously](https://discord.com/channels/601130461678272522/615329862395101194/1268674828492083327)
that this might be a feasible option, but mentioned some performance
concerns.

I did some benchmarking previously but it turns out tango isn't the
greatest for this. I don't have hard numbers anymore but based on some
experimenting I shared in the Discord it seems like no experimental
option and limiting this to specific instructions should keep the
performance impact minimal.
<details>
<summary>Old benchmarking info</summary>

I did some benchmarks against main. It seems like most benchmarks are
between 1-6% slower, with some oddball exceptions.

The worst case seems to a giant loop, which makes sense since it's
mostly just jumping back to the beginning, meaning we hit the check over
and over again.

With `bench --pretty -n 100 { for i in 0..1000000 { 1 } }`, it seems
like the `ir-interrupt` branch is ~10% slower for this stress case:
```
main: 94ms 323µs 29ns +/- 1ms 291µs 759ns 
ir-interrupt: 103ms 54µs 708ns +/- 1ms 606µs 571ns

(103ms + 54µs + 708ns) / (94ms + 323µs + 29ns) = 1.0925720801438639
```

The performance numbers here aren't great, but they're not terrible
either, and I think the usability improvement is probably worth it here.

<details>
<summary>Tango benchmarks</summary>

```
load_standard_lib                                  [   2.8 ms ...   2.9 ms ]      +3.53%*
record_create_1                                    [ 101.8 us ... 107.0 us ]      +5.12%*
record_create_10                                   [ 132.3 us ... 135.6 us ]      +2.48%*
record_create_100                                  [ 349.7 us ... 354.6 us ]      +1.39%*
record_create_1000                                 [   3.7 ms ...   3.6 ms ]      -1.30%
record_flat_access_1                               [  94.8 us ...  99.9 us ]      +5.35%*
record_flat_access_10                              [  96.2 us ... 101.3 us ]      +5.33%*
record_flat_access_100                             [ 106.0 us ... 111.4 us ]      +5.07%*
record_flat_access_1000                            [ 203.0 us ... 208.5 us ]      +2.69%
record_nested_access_1                             [  95.3 us ... 100.1 us ]      +5.03%*
record_nested_access_2                             [  98.4 us ... 104.6 us ]      +6.26%*
record_nested_access_4                             [ 100.8 us ... 105.4 us ]      +4.56%*
record_nested_access_8                             [ 104.7 us ... 108.1 us ]      +3.23%
record_nested_access_16                            [ 110.6 us ... 115.8 us ]      +4.71%*
record_nested_access_32                            [ 119.0 us ... 124.0 us ]      +4.20%*
record_nested_access_64                            [ 137.4 us ... 142.2 us ]      +3.47%*
record_nested_access_128                           [ 176.7 us ... 181.8 us ]      +2.87%*
record_insert_1_1                                  [ 106.2 us ... 111.9 us ]      +5.35%*
record_insert_10_1                                 [ 111.2 us ... 116.0 us ]      +4.28%*
record_insert_100_1                                [ 134.3 us ... 139.8 us ]      +4.06%*
record_insert_1000_1                               [ 387.8 us ... 417.1 us ]      +7.55%
record_insert_1_10                                 [ 162.9 us ... 171.5 us ]      +5.23%*
record_insert_10_10                                [ 159.5 us ... 166.2 us ]      +4.23%*
record_insert_100_10                               [ 183.3 us ... 190.7 us ]      +4.04%*
record_insert_1000_10                              [ 411.7 us ... 418.7 us ]      +1.71%*
table_create_1                                     [ 117.2 us ... 120.3 us ]      +2.66%
table_create_10                                    [ 144.9 us ... 152.2 us ]      +5.02%*
table_create_100                                   [ 476.0 us ... 484.8 us ]      +1.86%*
table_create_1000                                  [   3.7 ms ...   3.7 ms ]      +1.55%
table_get_1                                        [ 121.6 us ... 127.0 us ]      +4.40%*
table_get_10                                       [ 112.1 us ... 117.4 us ]      +4.71%*
table_get_100                                      [ 124.0 us ... 129.5 us ]      +4.41%*
table_get_1000                                     [ 229.9 us ... 245.5 us ]      +6.75%*
table_select_1                                     [ 111.1 us ... 115.6 us ]      +4.03%*
table_select_10                                    [ 112.1 us ... 118.4 us ]      +5.65%*
table_select_100                                   [ 142.2 us ... 147.2 us ]      +3.53%*
table_select_1000                                  [ 383.5 us ... 370.3 us ]      -3.43%*
table_insert_row_1_1                               [ 122.5 us ... 127.8 us ]      +4.35%*
table_insert_row_10_1                              [ 123.6 us ... 128.6 us ]      +3.99%*
table_insert_row_100_1                             [ 124.9 us ... 131.2 us ]      +5.05%*
table_insert_row_1000_1                            [ 177.3 us ... 182.7 us ]      +3.07%*
table_insert_row_1_10                              [ 225.7 us ... 234.5 us ]      +3.90%*
table_insert_row_10_10                             [ 229.2 us ... 235.4 us ]      +2.69%*
table_insert_row_100_10                            [ 253.3 us ... 257.6 us ]      +1.69%
table_insert_row_1000_10                           [ 311.1 us ... 318.3 us ]      +2.32%*
table_insert_col_1_1                               [ 107.6 us ... 113.3 us ]      +5.35%*
table_insert_col_10_1                              [ 109.6 us ... 115.3 us ]      +5.27%*
table_insert_col_100_1                             [ 155.5 us ... 159.8 us ]      +2.71%*
table_insert_col_1000_1                            [ 476.6 us ... 480.8 us ]      +0.88%*
table_insert_col_1_10                              [ 167.7 us ... 177.4 us ]      +5.75%
table_insert_col_10_10                             [ 178.5 us ... 194.8 us ]      +9.16%*
table_insert_col_100_10                            [ 314.4 us ... 322.3 us ]      +2.53%*
table_insert_col_1000_10                           [   1.7 ms ...   1.7 ms ]      +1.35%*
eval_interleave_100                                [ 485.8 us ... 506.5 us ]      +4.27%
eval_interleave_1000                               [   3.3 ms ...   3.2 ms ]      -1.51%
eval_interleave_10000                              [  31.5 ms ...  31.0 ms ]      -1.80%
eval_interleave_with_interrupt_100                 [ 473.2 us ... 479.6 us ]      +1.35%
eval_interleave_with_interrupt_1000                [   3.2 ms ...   3.2 ms ]      -1.26%
eval_interleave_with_interrupt_10000               [  32.3 ms ...  31.1 ms ]      -3.81%
eval_for_1                                         [ 124.4 us ... 130.1 us ]      +4.60%*
eval_for_10                                        [ 124.4 us ... 130.3 us ]      +4.80%*
eval_for_100                                       [ 134.5 us ... 141.5 us ]      +5.18%*
eval_for_1000                                      [ 222.7 us ... 244.0 us ]      +9.59%*
eval_for_10000                                     [   1.0 ms ...   1.2 ms ]     +13.86%*
eval_each_1                                        [ 146.9 us ... 153.0 us ]      +4.15%*
eval_each_10                                       [ 152.3 us ... 158.8 us ]      +4.26%*
eval_each_100                                      [ 169.3 us ... 175.6 us ]      +3.76%*
eval_each_1000                                     [ 346.8 us ... 357.4 us ]      +3.06%*
eval_each_10000                                    [   2.1 ms ...   2.2 ms ]      +2.37%*
eval_par_each_1                                    [ 194.3 us ... 203.5 us ]      +4.73%*
eval_par_each_10                                   [ 186.4 us ... 193.1 us ]      +3.59%*
eval_par_each_100                                  [ 213.5 us ... 222.2 us ]      +4.08%*
eval_par_each_1000                                 [ 433.4 us ... 437.4 us ]      +0.93%
eval_par_each_10000                                [   2.4 ms ...   2.4 ms ]      -0.40%
eval_default_config                                [ 406.3 us ... 414.9 us ]      +2.12%*
eval_default_env                                   [ 475.7 us ... 495.1 us ]      +4.08%*
encode_json_100_5                                  [ 132.7 us ... 128.9 us ]      -2.88%*
encode_json_10000_15                               [  35.5 ms ...  34.0 ms ]      -4.15%*
encode_msgpack_100_5                               [  85.0 us ...  83.9 us ]      -1.20%*
encode_msgpack_10000_15                            [  22.2 ms ...  21.7 ms ]      -2.17%*
decode_json_100_5                                  [ 431.3 us ... 421.0 us ]      -2.40%*
decode_json_10000_15                               [ 119.7 ms ... 117.6 ms ]      -1.76%*
decode_msgpack_100_5                               [ 160.6 us ... 151.1 us ]      -5.90%*
decode_msgpack_10000_15                            [  44.0 ms ...  43.1 ms ]      -2.12%*
```

</details>

</details>

# User-Facing Changes
* It should be possible to ctrl-c in situations where it was not
previously possible

# Tests + Formatting
N/A

# After Submitting
N/A
2025-07-17 21:41:54 +08:00
7ce66a9b77 Also type-check optional arguments (#16194)
# Description
Type-check all closure arguments, not just required arguments.

Not doing so looks like an oversight.

# User-Facing Changes

Previously, passing an argument of the wrong type to a closure would
fail if the argument is required, but be accepted (ignoring the type
annotation) if the argument is optional:

```
> do {|x: string| $x} 4
Error: nu:🐚:cant_convert

  × Can't convert to string.
   ╭─[entry #13:1:21]
 1 │ do {|x: string| $x} 4
   ·                     ┬
   ·                     ╰── can't convert int to string
   ╰────
> do {|x?: string| $x} 4
4
> do {|x?: string| $x} 4 | describe
int
```

It now fails the same way in both cases.

# Tests + Formatting
Added tests, the existing tests still pass.

Please let me know if I added the wrong type of test or added them in
the wrong place (I didn't spot similar tests in the nu-cmd-lang crate,
so I put them next to the most-related existing tests I could find...

# After Submitting
I think this is minor enough it doesn't need a doc update, but please
point me in the right direction if not.
2025-07-17 21:38:08 +08:00
c2622589ef fix quotation rules (#16089)
# Description
This script as example for demonstration
```nushell
def miku [] { print "Hiii world! 初音ミクはみんなのことが大好きだよ!" }

def main [leek: int, fn: closure] {
  print $"Miku has ($leek) leeks 🩵"
  do $fn
}
```
---

`escape_for_string_arg` quoting strings misses, where `|` and `;` did
split the command into 2+ commands
```console
~:►  nu ./miku.nu '32' '{miku}'
Miku has 32 leeks 🩵
Hiii world! 初音ミクはみんなのことが大好きだよ!
~:►  nu ./miku.nu '32' '{miku};ls|' where type == dir 
Miku has 32 leeks 🩵
Hiii world! 初音ミクはみんなのことが大好きだよ!
╭────┬─────────────┬──────┬────────┬──────────────╮
│  # │    name     │ type │  size  │   modified   │
├────┼─────────────┼──────┼────────┼──────────────┤
│  0 │ Desktop     │ dir  │ 4.0 kB │ 5 months ago │
│  1 │ Documents   │ dir  │ 4.0 kB │ a day ago    │
│  2 │ Downloads   │ dir  │ 4.0 kB │ a day ago    │
│  3 │ Music       │ dir  │ 4.0 kB │ 9 months ago │
│  4 │ Pictures    │ dir  │ 4.0 kB │ 3 weeks ago  │
│  5 │ Public      │ dir  │ 4.0 kB │ 9 months ago │
│  6 │ Templates   │ dir  │ 4.0 kB │ 3 months ago │
│  7 │ Videos      │ dir  │ 4.0 kB │ 9 months ago │
│  8 │ __pycache__ │ dir  │ 4.0 kB │ 3 weeks ago  │
│  9 │ bin         │ dir  │ 4.0 kB │ 3 days ago   │
│ 10 │ repos       │ dir  │ 4.0 kB │ a day ago    │
│ 11 │ trash       │ dir  │ 4.0 kB │ 3 days ago   │
╰────┴─────────────┴──────┴────────┴──────────────╯
```

# User-Facing Changes

This adjustment won't need a change, aside from clarifying the following
behavior, which is unintuitive and potentially ambiguous

```console
~:►  nu ./miku.nu '32' {miku}
Miku has 32 leeks 🩵
Hiii world! 初音ミクはみんなのことが大好きだよ!
~:►  nu ./miku.nu '32' { miku }
Error: nu::parser::parse_mismatch

  × Parse mismatch during operation.
   ╭─[<commandline>:1:9]
 1 │ main 32 "{ miku }"
   ·         ─────┬────
   ·              ╰── expected block, closure or record
   ╰────

~:►  nu ./miku.nu '32' '{' miku '}'
Miku has 32 leeks 🩵
Hiii world! 初音ミクはみんなのことが大好きだよ!
```
2025-07-17 21:36:06 +08:00
1ba4fe0aac Use correct column name in history import -h example (#16190)
Closes #16189

See that issue for details.
2025-07-16 14:04:56 -05:00
5aa1ccea10 fix rest parameter spans (#16176)
Closes #16071

# Description

Rest parameter variables are now fully spanned, instead of just the
first value:

```diff
def foo [...rest] {
  metadata $rest | view span $in.span.start $in.span.end
}
foo 1 2

-1
+1 2
```
2025-07-16 13:02:48 +03:00
00e9e0e6a9 fix(overlay use): report errors in export-env (#16184)
- fixes #10242

# Tests + Formatting
Added 2 tests to confirm `use` and `overlay use` report errors in
`export-env` blocks.

---------

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
2025-07-16 10:32:02 +03:00
db1ffe57d3 Panic when converting other I/O errors into our I/O errors (#16160)
# Description
I added a debug-only check that makes sure only non-other I/O errors get
converted.
Since tests run in debug mode, that should help us catch these errors early.

I left the check out in release mode on purpose so we don't crash users who
have nu as their main shell. It's similar to the debug assertions in the same
file that check for unknown spans.
2025-07-16 01:55:02 +03:00
2df00ff498 Revert "Add extern for nu command" (#16180)
Reverts nushell/nushell#16119
2025-07-15 19:18:44 +03:00
c4e8e040ce Add warning when using history isolation with non-SQLite history format (#16151)
# Description
This PR depends on #16147, use `git diff 132ikl/shell-warning
132ikl/isolation-warn` to see only changes from this PR

People seem to get tripped up by this a lot, and it's not exactly
intuitive, so I added a warning if you try to set
`$env.config.history.isolation = true` when using the plaintext file
format:

    Warning: nu:🐚:invalid_config

      ⚠ Encountered 1 warnings(s) when updating config

    Warning: nu:🐚:incompatible_options

      ⚠ Incompatible options
       ╭─[source:1:33]
     1 │ $env.config.history.isolation = true
       ·                                 ──┬─
       ·                                   ╰── history isolation only compatible with SQLite format
       ╰────
      help: disable history isolation, or set $env.config.history.file_format = "sqlite"


# User-Facing Changes
* Added a warning when using history isolation without using SQLite
history.

# Tests + Formatting
Added a test
2025-07-15 16:40:32 +03:00
59ad605e22 Add ShellWarning (#16147)
# Description
Adds a proper `ShellWarning` enum which has the same functionality as
`ParseWarning`.

Also moves the deprecation from #15806 into `ShellWarning::Deprecated`
with `ReportMode::FirstUse`, so that warning will only pop up once now.

# User-Facing Changes
Technically the change to the deprecation warning from #15806 is user
facing but it's really not worth listing in the changelog
2025-07-15 15:30:18 +03:00
5569f5beff Print errors during stdlib testing (#16128)
# Description
This PR adds error output display for stdlib tests. This makes it easier
to understand why a test is failing. Here's what an example error output
looks like:


![image](https://github.com/user-attachments/assets/ed06b53e-c3f9-4c56-a646-7e6486ce7ec6)


# User-Facing Changes
N/A

# Tests + Formatting
N/A

# After Submitting
N/A
2025-07-15 19:28:13 +08:00
4ed522db93 Add default error codes (#16166)
# Description
Before this PR, errors without error codes are printed somewhat
strangely, with the `×` and the description being printed on the same
line as the `Error:` text:

    Error:   × Invalid literal
       ╭─[entry #1:1:2]
     1 │ "\z"
       ·  ─┬─
       ·   ╰── unrecognized escape after '\' in string
       ╰────


This PR adds a default error code for the different error types:

    Error: nu::parser::error

      × Invalid literal
       ╭─[entry #1:1:2]
     1 │ "\z"
       ·  ─┬─
       ·   ╰── unrecognized escape after '\' in string
       ╰────

While maybe not as informative as a proper error code, it makes
`GenericError`s and other things which don't have error codes look a lot
nicer.

It would be nicer if we could just set `diagnostic(code:
"nu:🐚:error")` at the top of `ShellError`, but unfortunately you
can't set a "default" at the `enum` level and then override it in the
variants. @cptpiepmatz mentioned he might change miette's derive macro
to accommodate this, in that case we can switch the approach here.

# User-Facing Changes
* Errors without error codes now have a default error code corresponding
to from which part of Nushell the error occurred (shell, parser,
compile, etc)

---------

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
2025-07-15 13:42:10 +03:00
beb3ec6a49 refactor(get,select,reject)!: deprecate --ignore-errors in favor of --optional (#16007)
# Description
As decided on the team meeting on 2025-06-19, rename `--ignore-errors
(-i)` to `--optional (-o)` with a (currently) indefinite grace period.

After `--ignore-errors (-i)` is removed, the short flag `-i` can be used
for `--ignore-case` (not implemented as of this PR)

# User-Facing Changes
`get`/`select`/`reject`: rename `--ignore-errors (-i)` to `--optional
(-o)` to better reflect its behavior.

# Tests + Formatting
- 🟢 toolkit fmt
- 🟢 toolkit clippy
- 🟢 toolkit test
- 🟢 toolkit test stdlib

# After Submitting
Update docs and inform third parties that integrate with nushell.

---------

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
2025-07-15 00:26:41 -04:00
a506d3f9b5 fix(completion): put argument completion results before commands (#16112)
#16075

# Description

# User-Facing Changes

Improved experience: more meaningful items on top of the completion
menu.
Mostly for the fuzzy/substring matcher.

# Tests + Formatting

Adjusted

# After Submitting
2025-07-14 21:35:36 -04:00
316d2d6af2 Add extern for nu command (#16119)
# Description
Adds an `extern` definition (`KnownExternal`) for the `nu` command. This
way you can do `help nu` and get tab completions for flags on the `nu`
executable

# User-Facing Changes
* You can now view the flags for Nushell itself with `help nu`, and tab
completion for flags on `nu` works
2025-07-15 02:26:40 +03:00
202d3b2d11 Polars limit housekeeping (#16173)
# Description
Removed a todo and fixed an example.

---------

Co-authored-by: Jack Wright <jack.wright@nike.com>
2025-07-14 15:32:04 -07:00
288f419f7b print to a value should be a SIGPIPE (#16171)
# Description
Partially handles #14799 
It's difficult to fix all cases there, but I think it's good to improve
one of this with a small step

# User-Facing Changes
Given the following code in c:
```C
// write.c
#include <stdio.h>

int main() {
  while (1) {
    printf("a\n");
  }
}
```
After this pr, `./write | 0` will exit immediately, in that case,
`./write` will receive `SIGPIPE` in unix. Before this pr, `./write | 0`
runs indefinitely.

-----------------------

Maybe it's easier to see what happened internally by the different
output of `view ir { ./write | 0 }` command.
### Before
```
# 2 registers, 6 instructions, 7 bytes of data
   0: load-literal           %1, glob-pattern("./write", no_expand = false)
   1: push-positional        %1
   2: redirect-out           null
   3: call                   decl 135 "run-external", %0
   4: load-literal           %0, int(0)
   5: return                 %0
```

### After
```
# 2 registers, 6 instructions, 7 bytes of data
   0: load-literal           %1, glob-pattern("./write", no_expand = false)
   1: push-positional        %1
   2: redirect-out           pipe      # changed, the command's output is a pipe rather than null
   3: call                   decl 136 "run-external", %0
   4: load-literal           %0, int(0)
   5: return                 %0
```
# Tests + Formatting
Added 1 test.

# After Submitting
NaN
2025-07-14 13:26:51 -04:00
c272fa2b0a Update default (scaffold) config blurb (#16165)
Updates the header for the default generated config file to point people
towards `config nu --doc`, adapting some text from that command.
2025-07-14 11:48:06 -04:00
bfe9d8699d fix: unwrap ShellErrorBridge in ByteStream::into_bytes (#16161)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

- closes #16159 

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

The setup in #16159 produced a `ByteStream` that held the `ShellError`
inside an `std::io::Error`. For exactly this setup is the
`ShellErrorBridge` designed, while figuring out what happened I found
out that inside `BytesStream::into_bytes` a sneaky `ShellErrorBridge`
was hiding:
<img width="1034" height="580" alt="image"
src="https://github.com/user-attachments/assets/a0d16ca6-1a60-4c3c-a4cb-5e4b0695f20c"
/>

To fix this, I try to unwrap the bridge and if that is possible, return
the original `ShellError`. This is done at multiple occasions inside
`byte_stream.rs` already.

With this fix, we get the original error that looks like this:
<img width="1439" height="733" alt="image"
src="https://github.com/user-attachments/assets/73f4dee7-f986-4f68-9c2c-0140a6e9e2b2"
/>


# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

None, just a better error.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->

To have less situations with other I/O errors, I opened #16160 to find
out faster what other error was passed around.
2025-07-13 12:53:55 -04:00
60ca889443 fix(overlay): overlay use and overlay hide now update config state (#16154)
- fixes #5986
- fixes #7760
- fixes #8856
- fixes #10592
- fixes #11082

# Description
Unconditionally update the config state after each `overlay use` and
`overlay hide`.

The fix looks simple, but only because of the constant improvements and
refactors to the codebase that have taken place over time made it
possible.

Fixing these issue when they were initially filed would have been much
harder.

# User-Facing Changes
Overlays can add hooks, change color_config, update the config in
general.

# Tests + Formatting
No tests added as I still haven't figured out how to simulate the repl
in tests.

# After Submitting
N/A

---------

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
2025-07-13 02:31:16 +03:00
f48656e18b Bump update-informer to v1.3.0 (#16157)
# Description

This PR bumps `update-informer` to 1.3.0 and gets rid of
`NativeTlsHttpClient`.
2025-07-11 12:11:12 -05:00
172a0c44bd fix(get): to be consistent with regular cell-path access on null values (#16155)
# Description
Cell-path accesses on `null` values throw an error, unless the initial
cell-path member is optional:
```nushell
">"; (null).a
# => Error: nu:🐚:incompatible_path_access
# => 
# =>   x Data cannot be accessed with a cell path
# =>    ,-[source:1:8]
# =>  1 | (null).a
# =>    :        |
# =>    :        `-- nothing doesn't support cell paths
# =>    `----

">"; (null).a? | describe
# => nothing
```

`get` throws an error on `null` even when the cell-path is optional, and
only returns `null` quietly with the `--ignore-errors` flag
```nushell
">"; null | get a?
# => Error: nu:🐚:only_supports_this_input_type
# => 
# =>   x Input type not supported.
# =>    ,-[source:1:1]
# =>  1 | null | get a?
# =>    : ^^|^   ^|^
# =>    :   |     `-- only table or record input data is supported
# =>    :   `-- input type: nothing
# =>    `----

">"; null | get -i a? | describe
# => nothing
```

# Tests + Formatting
No breakage.

# After Submitting
N/A

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
2025-07-11 12:32:31 -04:00
8979e3f5bf update to latest reedline (#16156)
# Description

This updates to the latest reedline 405299b to include
https://github.com/nushell/reedline/pull/898 for dogfooding.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-07-11 06:51:23 -05:00
1fcce4cffc fix(gstat): make state entry lowercase (#16153)
A follow up to #15965 based on [this
comment](https://github.com/nushell/nushell/pull/15965#issuecomment-3058106114).

# Description

Make Git state values lowercase instead of title case (as in
[upstream](https://docs.rs/git2/latest/git2/enum.RepositoryState.html)).

# User-Facing Changes

Different values.
2025-07-10 16:14:45 -05:00
8cf9efafbb build(deps): bump indicatif from 0.17.9 to 0.17.11 (#16136) 2025-07-10 19:22:29 +00:00
8943fcf78d Use Severity::Warning for miette warnings (#16146)
# Description


Changes miette warnings to use `Severity::Warning` instead of the
default `Severity::Error`. This shows them in a different color and with
a different icon:

<img width="650" height="266" alt="image"
src="https://github.com/user-attachments/assets/3ff0d3cf-ab1e-47f2-aff7-586ecea5a32a"
/>


# User-Facing Changes

* Warnings now look less like errors
2025-07-10 20:50:12 +02:00
33a2b98f66 fix typo on documentation about pipes (#16152)
these documentations say ">" when what they mean is ">>"

# Description

this documentation is about the use of ">>", but the documentation
wrongly says ">". it has been updated to say ">>" instead.

# User-Facing Changes

the user will see ">>" in the documentation instead of ">".

# Tests + Formatting

there is nothing to test. this is a documentation change.

# After Submitting
2025-07-10 12:16:02 -04:00
05e570aa71 polars: fix datetime type conversion (#16133)
# Description

Conversion from `AnyType::DatetimeOwned` was missing, so datetime
objects could not be represented in Nushell Values. This adds the
conversion.

A slight refactor of the `datetime_from_epoch_nanos` was needed.


# User-Facing Changes

All datetime types can be represented in Nushell.
2025-07-08 12:42:55 -07:00
d8255040f1 Added flag limit for polars arg-sort (#16132)
# Description
Exposes the polars sort option limit for the arg-sort command.

```nu
> ❯ : [1 2 2 3 3] | polars into-df | polars arg-sort --limit 2
╭───┬──────────╮
│ # │ arg_sort │
├───┼──────────┤
│ 0 │        0 │
│ 1 │        1 │
╰───┴──────────╯
```

 
# User-Facing Changes
- The `--limit` flag is now available for `polars arg-sort`

Co-authored-by: Jack Wright <jack.wright@nike.com>
2025-07-08 12:24:47 -05:00
4da755895d fix: panic of if command as a constant expr by bringing back Type::Block (#16122)
Fixes #16110. Alternative to #16120 

# Description

# User-Facing Changes

no more panic

# Tests + Formatting

+1

# After Submitting
2025-07-08 20:45:35 +08:00
a674ce2dbc Update winget default INSTALLDIR for per-machine install path (#16026)
# Description

- Update `winget` default INSTALLDIR for per-machine install path to `
C:\Program Files\nu`

# User-Facing Changes

These changes, along with the manifest update PR (such as [this
example](https://github.com/microsoft/winget-pkgs/pull/266009/files)),
will resolve [issue
#15949](https://github.com/nushell/nushell/issues/15949) and add
`--scope` flag support for `winget install`.

After these changes are merged, the following should occur:

1. When no `nu` is installed, `winget install --id Nushell.Nushell
--scope machine` will install Nushell to `C:\Program Files\nu`.
2. `winget update --id Nushell.Nushell` will upgrade Nushell to the
latest version with machine scope.
3. When no `nu` is installed, `winget install --id Nushell.Nushell` will
install Nushell to `%LOCALAPPDATA%\Programs\nu`.
4. Due to [winget-cli issue
#3011](https://github.com/microsoft/winget-cli/issues/3011), `winget
update --id Nushell.Nushell` will unexpectedly install the latest
version to `C:\Program Files\nu`. The workaround is to run `winget
install --id Nushell.Nushell` again to install the latest version for
user scope.

# Tests + Formatting

1. Nushell install from MSI tests:
https://github.com/nushell/integrations/actions/runs/16088967701
2. Nushell install by Winget tests:
https://github.com/nushell/integrations/actions/runs/16088814557
3. Nushell Upgrade by Winget tests:
https://github.com/nushell/integrations/actions/runs/16088967705 and
test script:
https://github.com/nushell/integrations/blob/main/tests/test-all.nu

# After Submitting

The manifest files need to be updated manually for the next release, and
I will do that.
2025-07-07 08:10:07 +08:00
71d78b41c4 nu-table: optimize table creation and width functions (#15900)
> Further tests are welcomed.

It was already implemented that we precalculate widths, but nothing
stops to do heights as well.
Because before all the calculus were wasted (literally).

It affects `table` and `table --expand`.
The only case when it does not work (even makes things slightly less
optimal in case of `table` when `truncation` is used)

Sadly my tests are not showing the clear benefit.
I have no idea why I was expecting something 😞 
But it must be there :)

Running `scope commands` + `$env.CMD_DURATION_MS`:

```log
# patch (release)
2355 2462 2210 2356 2303

# main (release)
2375 2240 2202 2297 2385
```

PS: as once mentioned all this stuff ought to be moved out `nu-table`

---------

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2025-07-06 12:56:42 -05:00
647a740c11 Add all to enable all active experimental options (#16121)
- closes #16118 

<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

This PR adds the option to pass `all` to the experimental options
parser. This will enable all active (not deprecated) experimental
options to ease with dogfooding.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

A new valid value for `--experimental-options` and
`NU_EXPERIMENTAL_OPTIONS`.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-07-06 02:34:27 -04:00
a317284db6 fix ansi --list missing new items (#16113)
# Description

This fixes an oversight where not all items show in `ansi --list`.

### Before

![image](https://github.com/user-attachments/assets/2fd60744-28e1-4769-b491-7ac92b8b96c6)

### After

![image](https://github.com/user-attachments/assets/422993a5-5f29-4c24-8d90-66b642d72fa4)



# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-07-04 22:05:35 -05:00
a4bd51a11d Fix type checking for assignment operators (#16107)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

Rel: #14429, #16079

Finishes up a TODO in the assignment type checking. 

- For regular assignment operations (only applies to `mut`), type
checking is now done using `type_compatible` (which is what `let` uses)
- This allows some mutable assignments to work which weren't allowed
before

Before:
```nushell
let x: glob = "" 
# => ok, no error
mut x: glob = ""; $x = ""
# => Error: nu::parser::operator_incompatible_types
# => 
# =>   × Types 'glob' and 'string' are not compatible for the '=' operator.
# =>    ╭─[entry #6:1:19]
# =>  1 │ mut x: glob = ""; $x = ""
# =>    ·                   ─┬ ┬ ─┬
# =>    ·                    │ │  ╰── string
# =>    ·                    │ ╰── does not operate between 'glob' and 'string'
# =>    ·                    ╰── glob
# =>    ╰────

let x: number = 1
# ok, no error
mut x: number = 1; $x = 2
# => Error: nu::parser::operator_incompatible_types
# => 
# =>   × Types 'number' and 'int' are not compatible for the '=' operator.
# =>    ╭─[source:1:20]
# =>  1 │ mut x: number = 1; $x = 2
# =>    ·                    ─┬ ┬ ┬
# =>    ·                     │ │ ╰── int
# =>    ·                     │ ╰── does not operate between 'number' and 'int'
# =>    ·                     ╰── number
# =>    ╰────
```

After:
```nushell
let x: glob = ""
# ok, no error (same as before)
mut x: glob = ""; $x = ""
# ok, no error

let x: number = 1
# ok, no error (same as before)
mut x: number = 1; $x = 2
# ok, no error
```

- Properly type check compound operations. First checks if the operation
(eg. `+` for `+=`) type checks successfully, and then checks if the
assignment type checks successfully (also using `type_compatible`)
- This fixes some issues where the "long version" of a compound
assignment operator would error, but the compound assignment operator
itself would not

Before:
```nushell
mut x = 1; $x = $x / 2
# => Error: nu::parser::operator_incompatible_types
# => 
# =>   × Types 'int' and 'float' are not compatible for the '=' operator.
# =>    ╭─[entry #15:1:12]
# =>  1 │ mut x = 1; $x = $x / 2
# =>    ·            ─┬ ┬ ───┬──
# =>    ·             │ │    ╰── float
# =>    ·             │ ╰── does not operate between 'int' and 'float'
# =>    ·             ╰── int
# =>    ╰────

mut x = 1; $x /= 2
# uh oh, no error...

mut x = (date now); $x = $x - 2019-05-10
# => Error: nu::parser::operator_incompatible_types
# => 
# =>   × Types 'datetime' and 'duration' are not compatible for the '=' operator.
# =>    ╭─[entry #1:1:21]
# =>  1 │ mut x = (date now); $x = $x - 2019-05-10
# =>    ·                     ─┬ ┬ ───────┬───────
# =>    ·                      │ │        ╰── duration
# =>    ·                      │ ╰── does not operate between 'datetime' and 'duration'
# =>    ·                      ╰── datetime
# =>    ╰────

mut x = (date now); $x -= 2019-05-10
# uh oh, no error... (the result of this is a duration, not a datetime)
```

After:
```nushell
mut x = 1; $x = $x / 2
# => Error: nu::parser::operator_incompatible_types
# => 
# =>   × Types 'int' and 'float' are not compatible for the '=' operator.
# =>    ╭─[entry #5:1:12]
# =>  1 │ mut x = 1; $x = $x / 2
# =>    ·            ─┬ ┬ ───┬──
# =>    ·             │ │    ╰── float
# =>    ·             │ ╰── does not operate between 'int' and 'float'
# =>    ·             ╰── int
# =>    ╰────

mut x = (date now); $x -= 2019-05-10
# => Error: nu::parser::operator_incompatible_types
# => 
# =>   × Types 'datetime' and 'datetime' are not compatible for the '-=' operator.
# =>    ╭─[entry #11:1:21]
# =>  1 │ mut x = (date now); $x -= 2019-05-10
# =>    ·                     ─┬ ─┬ ─────┬────
# =>    ·                      │  │      ╰── datetime
# =>    ·                      │  ╰── does not operate between 'datetime' and 'datetime'
# =>    ·                      ╰── datetime
# =>    ╰────
# =>   help: The result type of this operation is not compatible with the type of the variable.
```

This is technically a breaking change if you relied on the old behavior
(for example, there was a test that broke after this change because it
relied on `/=` improperly type checking)

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
* Mutable assignment operations now use the same type checking rules as
normal assignments
* For example, `$x = 123` now uses the same type checking rules as `let
x = 123` or `mut x = 123`
* Compound assignment operations now type check using the same rules as
the operation they use
* Assignment errors will also now highlight the invalid assignment
operator in red


# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
Adds some tests for the examples given above

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
N/A
2025-07-04 02:48:49 -04:00
f6d807bf36 'find --columns .. --regex ..' works. Change help message to match. (#16103)
# Description
find's help message for '--columns' claims incompatibility with
'--regex'. However, both a review of the code and a test shows that this
is not true, and the two flags are compatible.
This commit removes the incompatibility claim.

# User-Facing Changes
  * Help message of '--columns' flag in 'find' command is changed

# Tests + Formatting
Tested using 'toolkit check pr'. The stdlib tests don't pass on my
system, but they didn't before this commit either.

# After Submitting
This command isn't mentioned in the main text, but is mentioned in the
command reference. Should I rerun the command reference generator for
the website? or is that done automatically before releases?
2025-07-03 11:13:40 -05:00
020d1b17c5 feat: add ansi style reset codes (#16099)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
This is so the styling can be set and reset without resetting the color.

NB. I don't have any domain or language knowledge here so am trying to
stick with existing patterns but it might be good to find out why code
like `Style::new().bold().prefix()` was used instead of just the raw SGR
(Select Graphic Rendition) codes (eg. `"\x1b[1m"`)

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-07-03 10:37:21 -05:00
118857aedc fix(metadata set): return error when both --datasource-filepath and -datasource-ls are used (#16049)
# Description

This PR improves the `metadata set` command by returning a clear error
when both `--datasource-filepath` and `--datasource-ls` flags are used
together. These flags are meant to be mutually exclusive, and previously
this conflicting usage was silently ignored.

# User-Facing Changes

* Users will now see an error message if they use both
`--datasource-filepath` and `--datasource-ls` together in `metadata
set`.

# Tests + Formatting

* [x] Added test at
`crates/nu-command/tests/commands/debug/metadata_set.rs` to verify the
error behavior.
* [x] Ran `cargo fmt --all -- --check`
* [x] Ran `cargo clippy --workspace -- -D warnings -D
clippy::unwrap_used`
* [x] Ran `cargo test --workspace`


# After Submitting

N/A
2025-07-02 19:40:34 +02:00
a340e965e8 refactor(nu-command/parse)!: Return null for unmatched capture groups, rather than empty string (#16094)
Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
2025-07-02 20:32:52 +03:00
25a5e8d8e8 Allow enabling deprecated experimental options (#16096)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

This PR changes the behavior of #16028 to allow enabling experimental
options even if they are marked as deprecated.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`
2025-07-02 16:46:53 +02:00
2fe84bd197 Forward experimental options in toolkit run (#16095)
# Description
I use `toolkit run` to test PRs or my own code. Passing experimental
options to it makes this nicer if you're trying to test that out.

# User-Facing Changes


You can pass `--experimental-options` to `toolkit run`.

# Tests + Formatting


- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting
2025-07-02 14:20:11 +02:00
c95c1e845c perf: reorder cell-path member accesses to avoid clones (#15682)
Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
Co-authored-by: Piepmatz <git+github@cptpiepmatz.de>
2025-07-02 12:55:46 +02:00
105ec0c89f Allow dashes in experimental option identifiers (#16093)
# Description

In #16028 I also added a test to check that identifiers are valid to
ensure that we have consistency there. But I only checked for
alphanumeric strings as identifiers. It doesn't allow underscores or
dashes. @Bahex used in his PR about #15682 a dash to separate words. So
expanded the test to allow that.

# User-Facing Changes

None.

# Tests + Formatting

The `assert_identifiers_are_valid` now allows dashes.

# After Submitting

The tests in #15682 should work then.
2025-07-02 13:31:01 +03:00
2c1b787db5 build(deps): bump indexmap from 2.9.0 to 2.10.0 (#16087) 2025-07-02 07:23:08 +00:00
fdb677e932 build(deps): bump quick-xml from 0.37.1 to 0.37.5 (#16086) 2025-07-02 07:22:37 +00:00
a18ff1d3a2 build(deps): bump crate-ci/typos from 1.33.1 to 1.34.0 (#16088)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.33.1 to
1.34.0.
<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.34.0</h2>
<h2>[1.34.0] - 2025-06-30</h2>
<h3>Features</h3>
<ul>
<li>Updated the dictionary with the <a
href="https://redirect.github.com/crate-ci/typos/issues/1309">June
2025</a> changes</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/crate-ci/typos/blob/master/CHANGELOG.md">crate-ci/typos's
changelog</a>.</em></p>
<blockquote>
<h2>[1.34.0] - 2025-06-30</h2>
<h3>Features</h3>
<ul>
<li>Updated the dictionary with the <a
href="https://redirect.github.com/crate-ci/typos/issues/1309">June
2025</a> changes</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="392b78fe18"><code>392b78f</code></a>
chore: Release</li>
<li><a
href="34b60f1f88"><code>34b60f1</code></a>
chore: Release</li>
<li><a
href="8b9670a614"><code>8b9670a</code></a>
docs: Update changelog</li>
<li><a
href="a6e61180eb"><code>a6e6118</code></a>
Merge pull request <a
href="https://redirect.github.com/crate-ci/typos/issues/1332">#1332</a>
from epage/juune</li>
<li><a
href="92f481e38a"><code>92f481e</code></a>
feat(dict): June 2025 updates</li>
<li><a
href="fb1f645959"><code>fb1f645</code></a>
chore(deps): Update Rust Stable to v1.88 (<a
href="https://redirect.github.com/crate-ci/typos/issues/1330">#1330</a>)</li>
<li><a
href="ebc6aac34e"><code>ebc6aac</code></a>
Merge pull request <a
href="https://redirect.github.com/crate-ci/typos/issues/1327">#1327</a>
from not-my-profile/fix-typo-in-error</li>
<li><a
href="e359d71a7f"><code>e359d71</code></a>
fix(cli): Correct config field reference in error message</li>
<li><a
href="022bdbe8ce"><code>022bdbe</code></a>
chore(ci): Update from windows-2019</li>
<li><a
href="ed74f4ebbb"><code>ed74f4e</code></a>
chore(ci): Update from windows-2019</li>
<li>Additional commits viewable in <a
href="https://github.com/crate-ci/typos/compare/v1.33.1...v1.34.0">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.33.1&new-version=1.34.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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-07-02 14:24:45 +08:00
a86a0dd16e Add infrastructure for experimental options (#16028)
Co-authored-by: Bahex <Bahex@users.noreply.github.com>
2025-07-01 18:36:51 +02:00
f4136aa3f4 Add pipeline span to metadata (#16014)
# Description

This PR makes the span of a pipeline accessible through `metadata`,
meaning it's possible to get the span of a pipeline without collecting
it.

Examples:
```nushell
ls | metadata
# => ╭────────┬────────────────────╮
# => │        │ ╭───────┬────────╮ │
# => │ span   │ │ start │ 170218 │ │
# => │        │ │ end   │ 170220 │ │
# => │        │ ╰───────┴────────╯ │
# => │ source │ ls                 │
# => ╰────────┴────────────────────╯
```

```nushell
ls | metadata access {|meta|
  error make {msg: "error", label: {text: "here", span: $meta.span}}
}
# => Error:   × error
# =>    ╭─[entry #7:1:1]
# =>  1 │ ls | metadata access {|meta|
# =>    · ─┬
# =>    ·  ╰── here
# =>  2 │   error make {msg: "error", label: {text: "here", span: $meta.span}}
# =>    ╰────
```

Here's an example that wouldn't be possible before, since you would have
to use `metadata $in` to get the span, collecting the (infinite) stream

```nushell
generate {|x=0| {out: 0, next: 0} } | metadata access {|meta|
  # do whatever with stream
  error make {msg: "error", label: {text: "here", span: $meta.span}}
}
# => Error:   × error
# =>    ╭─[entry #16:1:1]
# =>  1 │ generate {|x=0| {out: 0, next: 0} } | metadata access {|meta|
# =>    · ────┬───
# =>    ·     ╰── here
# =>  2 │   # do whatever with stream
# =>    ╰────
```

I haven't done the tests or anything yet since I'm not sure how we feel
about having this as part of the normal metadata, rather than a new
command like `metadata span` or something. We could also have a
`metadata access` like functionality for that with an optional closure
argument potentially.

# User-Facing Changes

* The span of a pipeline is now available through `metadata` and
`metadata access` without collecting a stream.

# Tests + Formatting

TODO

# After Submitting

N/A
2025-06-30 23:17:43 +02:00
082e8d0de8 update rust version 1.86.0 (#16077)
# Description

This PR updates nushell to use rust version 1.86.0
2025-06-30 15:28:38 +02:00
9da0f41ebb Fix easy clippy lints from latest stable (#16053)
1.88.0 was released today, clippy now lints (machine-applicable)
against:
- format strings with empty braces that could be inlined
  - easy win
- `manual_abs_diff`
- returning of a stored result of the last expression.
  - this can be somewhat contentious but touched only a few places
2025-06-29 17:37:17 +02:00
372d576846 fix(std/help): add debug -v to string default parameters (#16063)
# Description
Added `debug -v` in case the default parameter is a string so that it
will be not be printed literally:
- Before
```nu
  --char: <string> (default:  )
```
```nu
  --char: <string> (default:
)
```
```nu
  --char: <string> (default: abc)
```
- After
```nu
  --char: <string> (default: " ")
```
```nu
  --char: <string> (default: "\n")
```
```nu
  --char: <string> (default: "abc")
```
Other types like `int` remain unaffected.
# User-Facing Changes

# Tests + Formatting

# After Submitting
2025-06-29 17:35:25 +02:00
c795f16143 If save-ing with non-existing parent dir, return directory_not_found (#15961) 2025-06-29 17:15:15 +02:00
a4a3c514ba Bump strip-ansi-escapes to deduplicate vte (#16054)
Updating here deduplicates `vte` which is also depended on by `ansitok`
from the `tabled`/zhiburt-cinematic-universe
2025-06-26 23:31:07 +02:00
5478ec44bb to <format>: preserve round float numbers' type (#16016)
- fixes #16011

# Description
`Display` implementation for `f64` omits the decimal part for round
numbers, and by using it we did the same.
This affected:
- conversions to delimited formats: `csv`, `tsv`
- textual formats: `html`, `md`, `text`
- pretty printed `json` (`--raw` was unaffected)
- how single float values are displayed in the REPL

> [!TIP]
> This PR fixes our existing json pretty printing implementation.
> We can likely switch to using serde_json's impl using its
PrettyFormatter which allows arbitrary indent strings.

# User-Facing Changes
- Round trips through `csv`, `tsv`, and `json` preserve the type of
round floats.
- It's always clear whether a number is an integer or a float in the
REPL
  ```nushell
  4 / 2
  # => 2  # before: is this an int or a float?

  4 / 2
  # => 2.0  # after: clearly a float
  ``` 

# Tests + Formatting
Adjusted tests for the new behavior.

- 🟢 toolkit fmt
- 🟢 toolkit clippy
- 🟢 toolkit test
- 🟢 toolkit test stdlib

# After Submitting
N/A

---------

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
2025-06-26 15:15:19 -05:00
6902bbe547 Add tango folder to .gitignore (#16052)
This is the folder used by `toolkit.nu` when invoking the tango commands
2025-06-26 21:43:07 +02:00
4e5da8cd91 default config: add note for figuring out datetime escape sequences (#16051)
# Description

There was no hint as to what datetime escape sequences are supported,
previously. Looked into the source code to figure this out, which is not
great ux hehehe

# User-Facing Changes

# Tests + Formatting


# After Submitting


---------

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2025-06-26 21:42:06 +02:00
d248451428 Update which from 7.0.3 to 8.0.0 (#16045)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->
This simply updates the `which` dependency from 7.0.3 to 8.0.0, with no
code changes. See
https://github.com/harryfei/which-rs/releases/tag/8.0.0 for release
notes.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

N/A

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

Tested with `cargo test --workspace` and `cargo run -- -c "use
toolkit.nu; toolkit test stdlib"`.

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->

N/A
2025-06-25 19:24:31 -05:00
3e758e899f update nushell to latest reedline e4221b9 (#16044)
# Description

This PR just updates nushell to the latest reedline commit e4221b9 so we
can dogfood the most recent changes.
2025-06-25 23:49:26 +02:00
f69a812055 fix(hooks): updating $env.config now correctly updates config state. (#16021)
- fixes #14946

- related #15227
- > [When I run nushell with the hook, the hook itself works as
expected, correctly detects system theme and changes
$env.config.color_config. However, it seems that the change to
$env.config.color_config is not propagated outside the
hook](https://github.com/nushell/nushell/issues/15227#issuecomment-2695287318)
- > [But it suffers from the same problem - modifications made to the
$env.config variable are not visible outside of the hook (which I'm not
sure if is correct behavior or
bug).](https://github.com/nushell/nushell/issues/15227#issuecomment-2695741542)
- > [I also managed to get it working with def --env, but there was one
more issue, I had to change $env.config.hooks.pre_prompt = [{
switch_theme }] into $env.config.hooks.pre_execution = ([ switch_theme
])](https://github.com/nushell/nushell/issues/15227#issuecomment-2704537565)
(having to use a string hook rather than a closure)

- related #11082
  > Might be possible solve or at least mitigate using a similar method

# Description

Recently realized that changes made to `$env.config` in closure hooks
don't take effect whereas string hooks don't have that problem.

After some investigation:
- Hooks' environment was not preserved prior to #5982 >
[2309601](2309601dd4/crates/nu-cli/src/repl.rs (L823-L840))
- `redirect_env` which properly updates the config state was implemented
afterwards in #6355 >
[ea8b0e8](ea8b0e8a1d/crates/nu-engine/src/eval.rs (L174-L190))

Simply using `nu_engine::eval::redirect_env` for the environment update
was enough to fix the issue.

# User-Facing Changes
Hooks can update `$env.config` and the configuration change will work as
expected.

# Tests + Formatting

- 🟢 toolkit fmt
- 🟢 toolkit clippy
- 🟢 toolkit test
- 🟢 toolkit test stdlib

# After Submitting
N/A

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
2025-06-25 23:22:43 +02:00
6fba4b409e Add backtick code formatting to help (#15892)
# Description
Adds formatting for code in backticks in `help` output. If it's possible
to highlight syntax (`nu-highlight` is available and there's no invalid
syntax) then it's highlighted. If the syntax is invalid or not an
internal command, then it's dimmed and italicized. like some of the
output from `std/help`. If `use_ansi_coloring` is `false`, then we leave
the backticks alone. Here's a couple examples:


![image](https://github.com/user-attachments/assets/57eed1dd-b38c-48ef-92c6-3f805392487c)


![image](https://github.com/user-attachments/assets/a0efa0d7-fc11-4702-973b-a0b448c383e0)

(note on this one: usually we can highlight partial commands, like `get`
in the `select` help page which is invalid according to `nu-check` but
is still properly highlighted, however `where` is special cased and just
typing `where` with no row condition is highlighted with the garbage
style so `where` alone isn't highlighted here)

![image](https://github.com/user-attachments/assets/28c110c9-16c4-4890-bc74-6de0f2e6d1b8)

here's the `where` page with `$env.config.use_ansi_coloring = false`:

![image](https://github.com/user-attachments/assets/57871cc8-d509-4719-9dd4-e6f24f9d891c)


Technically, some syntax is valid but isn't really "Nushell code". For
example, the `select` help page has a line that says "Select just the
\`name\` column". If you just type `name` in the REPL, Nushell treats it
as an external command, but for the purposes of highlighted we actually
want this to fall back to the generic dimmed/italic style. This is
accomplished by temporarily setting the `shape_external` and
`shape_externalarg` color config to the generic/fallback style, and then
restoring the color config after highlighting. This is a bit hack-ish
but it seems to work pretty well.


# User-Facing Changes

- `help` command now supports code backtick formatting. Code will be
highlighted using `nu-highlight` if possible, otherwise it will fall
back to a generic format.
- Adds `--reject-garbage` flag to `nu-highlight` which will return an
error on invalid syntax (which would otherwise be highlighted with
`$env.config.color_config.shape_garbage`)

# Tests + Formatting

Added tests for the regex. I don't think tests for the actual
highlighting are very necessary since the failure mode is graceful and
it would be difficult to meaningfully test.

# After Submitting

N/A

---------

Co-authored-by: Piepmatz <git+github@cptpiepmatz.de>
2025-06-25 21:26:52 +02:00
cb7ac9199d Stream lazy default output (#15955)
It was brought up in the Discord that `default { open -r foo.txt }`
results in a string instead of streaming output. This changes `default`
such that closures now stream when given simple input.

# Description
If the value isn't expected to be cached, `default` just runs the
closure without caching the value, which allows its output to be
streamed

# User-Facing Changes


# Tests + Formatting
👍 

# After Submitting
2025-06-24 19:17:33 -04:00
a6b8e2f95c Update the behaviour how paths are interpreted in start (#16033)
Closes: https://github.com/nushell/nushell/issues/13127

# Description

This PR updates the behaviour of `start` in the following ways:
Instead of joining the path with CWD, we expand the path.

Behaviour on `origin/main`:
```
nushell> ls ~/nushell-test
test.txt

nushell> start ~/nushell-test/test.txt
Error:   × Cannot find file or URL: ~/nushell-test/test.txt
...
help: Ensure the path or URL is correct and try again.
```

Behaviour in this PR:
```
nushell> ls ~/nushell-test
test.txt

nushell> start ~/nushell-test/test.txt
<opens text editor>
```

# User-Facing Changes

`start` now treats the input path differently. This is a breaking
change, I believe. Although I'm not sure how breaking it would be in the
perspective of the user.

# Tests + Formatting

I've manually tested this. The test suite for `start` is broken. And
even if I fix it, I'm not sure how to test it.
I'll need to override the default command list for `start` in the
sandbox for testing.

# After Submitting

I don't think the documentation needs to be updated.
2025-06-24 17:29:10 -05:00
0b202d55f0 Add only command to std-rfc/iter (#16015)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

This PR adds the `only` command to `std-rfc/iter`, which is a command I
wrote a while ago that I've found so useful that I think it could have a
place in the standard library. It acts similarly to `get 0`, but ensures
that the value actually exists, and there aren't additional values. I
find this most useful when chained with `where`, when you want to be
certain that no additional elements are accidentally selected when you
only mean to get a single element.

I'll copy the help page here for additional explanation:

> Get the only element of a list or table, ensuring it exists and there
are no extra elements.
> 
> Similar to `first` with no arguments, but errors if there are no
additional
> items when there should only be one item. This can help avoid issues
when more
> than one row than expected matches some criteria.
> 
> This command is useful when chained with `where` to ensure that only
one row
> meets the given condition.
> 
> If a cell path is provided as an argument, it will be accessed after
the first
> element. For example, `only foo` is roughly equivalent to `get 0.foo`,
with
> the guarantee that there are no additional elements.
> 
> Note that this command currently collects streams.

> Examples:
>  
> Get the only item in a list, ensuring it exists and there's no
additional items
> ```nushell
> [5] | only
> # => 5
> ```
> 
> Get the `name` column of the only row in a table
> ```nushell
> [{name: foo, id: 5}] | only name
> # => foo
> ```
> 
> Get the modification time of the file named foo.txt
> ```nushell
> ls | where name == "foo.txt" | only modified
> ```

Here's some additional examples showing the errors:

![image](https://github.com/user-attachments/assets/d5e6f202-db52-42e4-a2ba-fb7c4f1d530a)


![image](https://github.com/user-attachments/assets/b080da2a-7aff-48a9-a523-55c638fdcce3)

Most of the time I chain this with a simple `where`, but here's a couple
other real world examples of how I've used this:

[With `parse`, which outputs a
table](https://git.ikl.sh/132ikl/dotfiles/src/branch/main/.scripts/manage-nu#L53):
```nushell
let commit = $selection | parse "{start}.g{commit}-{end}" | only commit
```

[Ensuring that only one row in a table has a name that ends with a
certain
suffix](https://git.ikl.sh/132ikl/dotfiles/src/branch/main/.scripts/btconnect):
```nushell
$devices | where ($chosen_name ends-with $it.name) | only
```


Unfortunately to get these nice errors I had to collect the stream (and
I think the errors are more useful for this). This should be to be
mitigated with (something like) #16014.


Putting this in `std/iter` might be pushing it, but it seems *just*
close enough that I can't really justify putting it in a different/new
module.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
* Adds the `only` command to `std-rfc/iter`, which can be used to ensure
that a table or list only has a single element.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

Added a few tests for `only` including error cases

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
N/A

---------

Co-authored-by: Bahex <Bahex@users.noreply.github.com>
2025-06-23 16:29:58 -05:00
e88a6bff60 polars 0.49 upgrade (#16031)
# Description
Polars 0.49 upgrade

Co-authored-by: Jack Wright <jack.wright@nike.com>
2025-06-23 16:17:39 -05:00
a234e6ff51 feat(std/help): add is_const information (#16032)
# Description

I wanted to know if `version` is a const command and thought that it
would be in the "This command" section but it wasn't, so I added it.
```
→ help version
Display Nu version, and its build configuration.

Category: core

This command:
 Creates scope         | 
 Is built-in           | 
 Is const              | 
 Is a subcommand       | 
 Is a part of a plugin | 
 Is a custom command   | 
 Is a keyword          | 
```
2025-06-23 23:22:58 +03:00
ae0cf8780d fix(random dice): gracefully handle --sides 0 using NonZeroUsize (#16001) 2025-06-23 14:47:50 +02:00
680a2fa2aa Add loongarch64-unknown-linux-musl build target (#16020) 2025-06-23 06:22:25 +08:00
70277cc2ba fix(std/help): collect windows --help output for gui programs (#16019)
# Description
Adding to #15962, I have realized that there are windows gui programs
like `prismlauncher` or `firefox` that do accept the `--help` flag but
won't output on the terminal unless `collect`ed, so now it collects the
output on windows.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-06-21 20:54:31 -05:00
574106bc03 allow update cells to work on single records (#16018)
# Description
The `update cells` command used to "work" on records by converting them
into single-row tables in the past, but strengthened input type checking
made it so that no longer worked. This commit introduces correct record
-> record functionality.

# User-Facing Changes
Users can now pipe records into `update cells`. An example inspired by a
conversation in the Discord:
```nushell
> version | update cells { split row ', ' } -c [features, installed_plugins]
╭────────────────────┬──────────────────────────────────────────╮
│ version            │ 0.105.2                                  │
│ major              │ 0                                        │
│ minor              │ 105                                      │
│ patch              │ 2                                        │
│ branch             │ update-cells-record                      │
│ commit_hash        │ 4f7e9aac62 │
│ build_os           │ macos-x86_64                             │
│ build_target       │ x86_64-apple-darwin                      │
│ rust_version       │ rustc 1.85.1 (4eb161250 2025-03-15)      │
│ rust_channel       │ 1.85.1-x86_64-apple-darwin               │
│ cargo_version      │ cargo 1.85.1 (d73d2caf9 2024-12-31)      │
│ build_time         │ 2025-06-21 12:02:06 -04:00               │
│ build_rust_channel │ debug                                    │
│ allocator          │ standard                                 │
│                    │ ╭───┬───────────────╮                    │
│ features           │ │ 0 │ default       │                    │
│                    │ │ 1 │ plugin        │                    │
│                    │ │ 2 │ rustls-tls    │                    │
│                    │ │ 3 │ sqlite        │                    │
│                    │ │ 4 │ trash-support │                    │
│                    │ ╰───┴───────────────╯                    │
│                    │ ╭───┬─────────────────╮                  │
│ installed_plugins  │ │ 0 │ formats 0.104.0 │                  │
│                    │ │ 1 │ polars 0.104.0  │                  │
│                    │ │ 2 │ query 0.104.0   │                  │
│                    │ │ 3 │ todotxt 0.3.0   │                  │
│                    │ ╰───┴─────────────────╯                  │
╰────────────────────┴──────────────────────────────────────────╯
```

# Tests + Formatting
👍. Let me know if more tests besides the new example are needed.

# After Submitting
2025-06-21 20:53:45 -05:00
2a8364d259 drop nth command supports spreadable arguments (#15897)
##  Improve `drop nth` command to support spreadable arguments

### Summary

This PR updates the `drop nth` command to support **spreadable
arguments** in a way consistent with other commands like `which`,
enabling:

```nu
[1 2 3 4 5] | drop nth 0 2 4
```

### What's Changed

* **Previously**: only a single index or a single range was accepted as
the first argument, with rest arguments ignored for ranges.

* **Now**: the command accepts any combination of:

  * Integers: to drop individual rows
  * Ranges: to drop slices of rows
  * Unbounded ranges: like `3..`, to drop from index onward

Example:

```nu
[one two three four five six] | drop nth 0 2 4..5
# drops "one", "three", "five", and "six"
```

### Test 

Manual Test:

![nu-dron_n](https://github.com/user-attachments/assets/02f3988c-ac02-4245-967c-16a9604be406)


### Notes

As per feedback:

* We **only collect the list of indices** to drop, not the input stream.
* Unbounded ranges are handled by terminating the stream early.

Let me know if you'd like further changes

---------

Co-authored-by: Kumar Ujjawal <kumar.ujjawal@greenpista.com>
Co-authored-by: Kumar Ujjawal <kumarujjawal@Kumars-MacBook-Air.local>
2025-06-21 15:57:14 -04:00
760c9ef2e9 fix(completion): invalid prefix for external path argument with spaces (#15998)
Fixes the default behavior of #15790 

# Description

As for the mentioned carapace version: `cat ~"/Downloads/Obsidian
Vault/"`, the problem lies in the unexpanded home directory `~`. Either
we encourage users to manually expand that in
`$env.config.completions.external.completer` or open an issue on the
carapace project.

# User-Facing Changes

bug fix

# Tests + Formatting

Adjusted

# After Submitting
2025-06-20 21:33:01 -04:00
c3079a14d9 feat(table): add 'double' table mode (#16013)
# Description

Add 'double' table mode, that is similar to `compact_double` but with
left and right border lines. This is similar to how there exist both
`single` and `compact`, but there is no `double` to compliment
`compact_double`. Printing `[ { a: 1, b: 11 }, { a: 2, b:12 } ]` looks
like this:

```
╔═══╦═══╦════╗
║ # ║ a ║ b  ║
╠═══╬═══╬════╣
║ 0 ║ 1 ║ 11 ║
║ 1 ║ 2 ║ 12 ║
╚═══╩═══╩════╝
```

The implementation is mostly a one-to-one of #15672 and #15681.

# User-Facing Changes

New value `double` to set as `$env.config.table.mode`.

# Tests + Formatting

Tests are added following the example of adding 'single' mode.

# After Submitting
2025-06-20 21:09:55 +02:00
4f7e9aac62 fix LS_COLORS fi=0 coloring (#16012)
# Description

fixes #16010

When `$env.LS_COLORS = 'fi=0' and `$env.config.color_config.string =
'red'` were set, regular files without file extensions would be colored
red. Now they're colored based on the LS_COLORS definition which, in
this case, means use default colors.

This is done by checking if a style was applied from ls_colors and if
none was applied, create a default nu_ansi_term style with
'Color::Default' for foreground and background.

### Before

![image](https://github.com/user-attachments/assets/ff245ee9-3299-4362-9df7-95613e8972ed)

### After

![image](https://github.com/user-attachments/assets/7c3f1178-6e6b-446d-b88c-1a5b0747345d)



# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->

---------

Co-authored-by: Bahex <Bahex@users.noreply.github.com>
2025-06-20 08:09:59 -05:00
7ee8aa78cc perf: better scalability of get_columns (#15780)
# Description

Use hashset for existence checking.
Still needs a vector collection to keep the column order for tables.

# User-Facing Changes

Should be None
2025-06-20 05:07:23 -05:00
d9d022733f feat(std/help): Add --help for external-commands (#15962)
# Description
I have just discovered the `std/help` command and that it can use `man`
or other programs for externals. Coming from windows, I don't have `man`
so what I want is just to run `external_program --help` in most cases.
This pr adds that option, if you set `$env.NU_HELPER = "--help"`, it
will run the command you passed with `--help` added as the last
argument.


![image](https://github.com/user-attachments/assets/60d25dda-718b-4cb5-b540-808de000b221)

# User-Facing Changes
None

# Tests + Formatting


# After Submitting
2025-06-20 05:06:27 -05:00
1d032ce80c Support namespaces in query xml (#16008)
Refs #15992
Refs #14457 

# Description

This PR introduces a new switch for `query xml`, `--namespaces`,
and thus allows people to use namespace prefixes in the XPath query
to query namespaced XML.

Example:
```nushell
r#'
   <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
      <rdf:Description rdf:about=""
            xmlns:dc="http://purl.org/dc/elements/1.1/"
         <dc:title>Black-breasted buzzard_AEB_IMG_7158</dc:title>
      </rdf:Description>
   </rdf:RDF>
'# | query xml --namespaces {dublincore: "http://purl.org/dc/elements/1.1/"} "//dublincore:title/text()"
```

# User-Facing Changes

New switch added to `query xml`: `query xml --namespaces {....}`

# Tests + Formatting

Pass.

# After Submitting

IIRC the commands docs on the website are automatically generated, so
nothing to do here.
2025-06-19 17:58:26 -05:00
975a89269e repect color_config.header color for record key (#16006)
# Description

This PR fixes an oversight where the record key value was not being
colored as the color_config.header color when used with the `table`
command in some circumstances. It respected it with `table -e` but just
not `table`.

### Before

![image](https://github.com/user-attachments/assets/a41e609f-9b3a-415b-af90-037e6ee47318)

### After

![image](https://github.com/user-attachments/assets/c3afb293-ebb3-4cb3-8ee6-4f7e2e96723b)


# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-06-19 11:22:18 -05:00
db5b6c790f Fix: missing installed_plugins in version (#16004)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

- related #15972

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

In #15972 I was very eager removing fowarded features from `nu` to
`nu-cmd-lang`. By accident I also removed `nu-cmd-lang/plugin` too. This
removed `installed_plugins` from `version`. By adding the feature again,
it works again.
2025-06-19 07:48:20 -05:00
2bed202b82 Add backtrack named flag to parse (issue #15997) (#16000)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

Addresses #15997

Adds a `--backtrack` or `-b` named flag to the `parse` command. Allows a
user to specify a max backtrack limit for fancy-regex other than the
default 1,000,000 limit.

Uses a RegexBuilder to add the manual config.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

Adds a new named flag `backtrack` to the `parse` command. The flag is
optional and defaults to 1,000,000.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

Added an example test to the parse command using `--backtrack 1500000`.
2025-06-19 06:42:30 -05:00
8a0f2ca9f9 Bump calamine to 0.28 (#16003) 2025-06-19 13:37:36 +02:00
24ab294cda Use CARGO_CFG_FEATURE to get feature list in version (#15972) 2025-06-19 12:58:37 +02:00
bfa95bbd24 Clearer help section for command attributes (#15999)
Refs
https://discord.com/channels/601130461678272522/615329862395101194/1385021428314800148

# Description

Clearer command attribute section (`This command:`).


![image](https://github.com/user-attachments/assets/7f26c015-1f00-4a86-a334-c87f7756ee82)


# User-Facing Changes

This is a cosmetic change to how `std/help` shows the command
attributes.

# Tests + Formatting

Pass.
2025-06-18 20:54:50 -05:00
3f700f03ad Generalize nu_protocol::format_shell_error (#15996) 2025-06-18 22:16:01 +02:00
f0e90a3733 Move nu_command::platform::ansi to nu_command::strings::ansi (#15995) 2025-06-18 21:51:16 +02:00
cde8a629c5 Restrict config.show_banner to valid options (#15985) 2025-06-18 10:49:40 +02:00
70aa7ad993 Disallow clippy::used_underscore_binding lint (#15988) 2025-06-18 10:19:57 +02:00
29b3512494 build(deps): bump shadow-rs from 1.1.1 to 1.2.0 (#15989)
Bumps [shadow-rs](https://github.com/baoyachi/shadow-rs) from 1.1.1 to
1.2.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/baoyachi/shadow-rs/releases">shadow-rs's
releases</a>.</em></p>
<blockquote>
<h2>v1.2.0</h2>
<h2>What's Changed</h2>
<ul>
<li>add cargo_metadata crate unit test by <a
href="https://github.com/baoyachi"><code>@​baoyachi</code></a> in <a
href="https://redirect.github.com/baoyachi/shadow-rs/pull/231">baoyachi/shadow-rs#231</a></li>
<li>Update cargo_metadata requirement from 0.19.1 to 0.20.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/baoyachi/shadow-rs/pull/229">baoyachi/shadow-rs#229</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/baoyachi/shadow-rs/compare/v1.1.1...v1.2.0">https://github.com/baoyachi/shadow-rs/compare/v1.1.1...v1.2.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f0d180ac92"><code>f0d180a</code></a>
Update Cargo.toml</li>
<li><a
href="d106a172ad"><code>d106a17</code></a>
Merge pull request <a
href="https://redirect.github.com/baoyachi/shadow-rs/issues/229">#229</a>
from baoyachi/dependabot/cargo/cargo_metadata-0.20.0</li>
<li><a
href="7861af1dd0"><code>7861af1</code></a>
Merge branch 'master' into dependabot/cargo/cargo_metadata-0.20.0</li>
<li><a
href="ab73c01cd1"><code>ab73c01</code></a>
Merge pull request <a
href="https://redirect.github.com/baoyachi/shadow-rs/issues/231">#231</a>
from baoyachi/cargo_metadata</li>
<li><a
href="ff1a1dcf27"><code>ff1a1dc</code></a>
fix cargo clippy check</li>
<li><a
href="f59bceaf92"><code>f59bcea</code></a>
add cargo_metadata crate unit test</li>
<li><a
href="5c5b556400"><code>5c5b556</code></a>
Update cargo_metadata requirement from 0.19.1 to 0.20.0</li>
<li>See full diff in <a
href="https://github.com/baoyachi/shadow-rs/compare/v1.1.1...v1.2.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=shadow-rs&package-manager=cargo&previous-version=1.1.1&new-version=1.2.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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-06-18 11:34:35 +08:00
d961ea19cc Add automatic reminder for doc_config.nu (#15984)
Inspired by https://github.com/nushell/nushell/pull/15979 a small Github
actions bot that detects when you make a change to the `nu-protocol`
bits of the config and reminds to consider making a change to the
Nushell version in `doc_config.nu` as well.
2025-06-16 23:51:15 +02:00
3db9c81958 Search nested structures recursively in find command (#15850)
# Description

Instead of converting nested structures into strings and
pattern-matching the strings, the `find` command will recursively search
the nested structures for matches.

- fixes #15618 

# User-Facing Changes

Text in nested structures will now be highlighted as well.

Error values will always passed on instead of testing them against the
search term

There will be slight changes in match behavior, such as characters that
are part of the string representations of data structures no longer
matching all nested data structures.
2025-06-16 15:29:41 -05:00
55240d98a5 Update config nu --doc to represent OSC 7 and 9;9 better (#15979)
- fixes #15975

# Description

This changes the `config nu --doc` output for OSC 7 and 9;9 to represent
better what happens on Windows machines.

This is the current behavior internally:

5be8717fe8/crates/nu-protocol/src/config/shell_integration.rs (L18-L27)

And with this PR the `config nu --doc` better reflects that behavior,
thanks to @fdncred for that idea.

# User-Facing Changes

None

# Tests + Formatting


- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting


---------

Co-authored-by: Bahex <Bahex@users.noreply.github.com>
2025-06-16 21:42:07 +02:00
fda181d566 Adjust std-rfc/clip deprecation window (#15981)
Follow-up to #15877. That PR was created before 0.105, but merged after
it was released. This PR adjusts the deprecation window from
0.105.0-0.107.0 to 0.106.0-0.108.0
2025-06-16 21:40:37 +02:00
2e484156e0 Use internal find.rs code for help --find (#15982)
# Description

Currently, `help --find` uses it's own code for looking for the keyword
in a string and highlighting it. This code duplicates a lot of the logic
found in the code of `find`.

This commit re-uses the code of `find` in `help` commands instead.

# User-Facing Changes

This should not affect the behavior of `help`.
2025-06-16 21:29:32 +02:00
52604f8b00 fix(std/log): Don't assume env variables are set (#15980)
# Description
Commands in `std/log` assume the `export-env` has been run and the
relevant environment variables are set.
However, when modules/libraries import `std/log` without defining their
own `export-env` block to run `std/log`'s, logging commands will fail at
runtime.

While it's on the author of the modules to include `export-env { use
std/log [] }` in their modules, this is a very simple issue to solve and
would make the user experience smoother.

# User-Facing Changes
`std/log` work without problem when their env vars are not set.

---------

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
Co-authored-by: 132ikl <132@ikl.sh>
2025-06-16 12:31:17 -04:00
2fed1f5967 Update Nu for release and nightly workflow (#15969)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

1. Upgrade Nushell to 0.105.1 for release and nightly workflow
2. Use `hustcer/setup-nu` Action for `windows-11-arm` runner to simplify
the workflow

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

None

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

Looks fine here:
https://github.com/hustcer/nushell/actions/runs/15657383788/job/44110173668#step:7:1357
2025-06-15 22:25:18 +08:00
5be8717fe8 Add full feature as an alternative to --all-features (#15971)
- closes #15967 

# Description


In 0.105 we introduced the feature `rustls-tls` which is enabled by
default and uses `rustls` instead of `openssl` on linux machines. Since
both `native-tls` and `rustls-tls` cannot be enabled at the same did
this break the `--all-features` flag. To provide an easy alternative, I
introduced the `full` feature here.

# User-Facing Changes

Instead of `cargo install nu --all-features`, you now can do `cargo
install nu --features full`.

# Tests + Formatting


No new tests, this is just a feature collection.
2025-06-15 13:49:58 +02:00
091d14f085 Fix table --expand case with wrapping of emojie (#15948)
close #15940
2025-06-14 18:39:39 -05:00
4c19242c0d feat(gstat): add state entry (like "Clean", "Merge", "Rebase", etc.) (#15965)
# Description

This PR adds a new `state` key to the output of `gstat` that shows the
current repo state state. Like "Clean", "Merge", "Rebase", etc. The full
list of possible values can be seen
[here](https://docs.rs/git2/latest/git2/enum.RepositoryState.html).

This information is somewhat useful when shown in prompt. Not often
needed, but sometimes really useful.

# User-Facing Changes

New key added to `gstat` output. I don't think it should cause issues to
`gstat` users.

# Tests + Formatting

I couldn't find any tests for `nu_plugin_gstat`.

# After Submitting

I couldn't find any documentation about the output of `gstat`, so I
don't think there is anything to be done here either.
2025-06-14 07:50:29 -05:00
3df0177ba5 feat: use get request by default, post if payload (#15862)
Hello, this PR resolves the second request of the issue
https://github.com/nushell/nushell/issues/10957, which involves using a
default verb based on the request. If a URL is provided, the command
will default to GET, and if data is provided, it will default to POST.
This means that the following pairs of commands are equivalent:

```
http --content-type application/json http://localhost:8000 {a:1}
http post --content-type application/json http://localhost:8000 {a:1}
```
```
http http://localhost:8000 "username"
http post http://localhost:8000 "username"
```
```
http http://localhost:8000
http get http://localhost:8000
```

The `http` command now accepts all flags of the `post` and `get`
commands. It will still display the help message if no subcommand is
provided, and the description has been updated accordingly. The logic in
the `http` command is minimal to delegate error management
responsibilities to the specific `run_get` and `run_post` functions.
2025-06-14 15:22:37 +08:00
f7888fce83 fix stor insert/delete collision (#15838)
# Description

Based on some testing in
[Discord](https://discord.com/channels/601130461678272522/1349836000804995196/1353138803640111135)
we were able to find that `insert` and `delete` happening at the same
time caused problems in the `stor` command. So, I added `conn.is_busy()`
with a sleep to try and avoid that problem.


![image](https://github.com/user-attachments/assets/e01bccab-0aaa-40ab-b0bf-25e3c72aa037)

/cc @NotTheDr01ds @132ikl 

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2025-06-13 16:29:07 -05:00
cf1a53143c feat(ansi): use _ in short name and rst -> reset (#15907)
# Description
I've noticed that unlike everything else in nushell the output of `ansi
--list` has a column named `short name` instead of `short_name`, so I
changed it. While I was at it, I also added a shortname `rst` to `reset`
since it is often used.

# User-Facing Changes
Changed the column name of `ansi --list` from `short name` to
`short_name`
2025-06-13 16:24:40 -05:00
28a94048c5 feat(format number): add --no-prefix flag (#15960)
# Description
I have added a `--no-prefix` flag to the `format number` command to not
include the `0b`, `0x` and `0o` prefixes in the output. Also, I've
changed the order in which the formats are displayed to one I thinks
makes it easier to read, with the upper and lower alternatives next to
each other.


![image](https://github.com/user-attachments/assets/cd50631d-1b27-40d4-84d9-f2ac125586d4)

# User-Facing Changes
The formatting of floats previously did not include prefixes while
integers did. Now prefixes are on by default for both, while including
the new flag removes them. Changing the order of the record shouldn't
have any effect on previous code.

# Tests + Formatting
I have added an additional example that test this behavior.

# After Submitting

---------

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2025-06-13 16:13:26 -05:00
503 changed files with 8856 additions and 4272 deletions

View File

@ -1,40 +1,16 @@
<!--
if this PR closes one or more issues, you can automatically link the PR with
them by using one of the [*linking keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), e.g.
- this PR should close #xxxx
- fixes #xxxx
Thank you for improving Nushell!
Please, read our contributing guide: https://github.com/nushell/nushell/blob/main/CONTRIBUTING.md
you can also mention related issues, PRs or discussions!
Use the following space to include the motivation and any technical details behind this PR.
-->
# Description
## Release notes summary - What our users need to know
<!--
Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes.
Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience.
This section will be included as part of our release notes. See the contributing guide for more details.
If you're not confident about this, a core team member would be glad to help!
-->
# User-Facing Changes
<!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. -->
# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make sure to [enable developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the tests for the standard library
> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it automatically
> toolkit check pr
> ```
-->
# After Submitting
<!-- If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. -->
## Tasks after submitting
<!-- Remove any tasks which aren't relevant for your PR, or add your own -->
- [ ] Update the [documentation](https://github.com/nushell/nushell.github.io)

View File

@ -0,0 +1,25 @@
name: Comment on changes to the config
on:
pull_request_target:
paths:
- 'crates/nu-protocol/src/config/**'
jobs:
comment:
runs-on: ubuntu-latest
steps:
- name: Check if there is already a bot comment
uses: peter-evans/find-comment@v3
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: Hey, just a bot checking in!
- name: Create comment if there is not
if: steps.fc.outputs.comment-id == ''
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.pull_request.number }}
body: |
Hey, just a bot checking in! You edited files related to the configuration.
If you changed any of the default values or added a new config option, don't forget to update the [`doc_config.nu`](https://github.com/nushell/nushell/blob/main/crates/nu-utils/src/default_files/doc_config.nu) which documents the options for our users including the defaults provided by the Rust implementation.
If you didn't make a change here, you can just ignore me.

View File

@ -46,7 +46,7 @@ jobs:
uses: hustcer/setup-nu@v3
if: github.repository == 'nushell/nightly'
with:
version: 0.103.0
version: 0.105.1
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
- name: Prepare for Nightly Release
@ -127,6 +127,7 @@ jobs:
- armv7-unknown-linux-musleabihf
- riscv64gc-unknown-linux-gnu
- loongarch64-unknown-linux-gnu
- loongarch64-unknown-linux-musl
include:
- target: aarch64-apple-darwin
os: macos-latest
@ -152,6 +153,8 @@ jobs:
os: ubuntu-22.04
- target: loongarch64-unknown-linux-gnu
os: ubuntu-22.04
- target: loongarch64-unknown-linux-musl
os: ubuntu-22.04
runs-on: ${{matrix.os}}
steps:
@ -179,36 +182,22 @@ jobs:
uses: actions-rust-lang/setup-rust-toolchain@v1
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
with:
cache: false
rustflags: ''
- name: Setup Nushell
uses: hustcer/setup-nu@v3
if: ${{ matrix.os != 'windows-11-arm' }}
with:
version: 0.103.0
version: 0.105.1
- name: Release Nu Binary
id: nu
if: ${{ matrix.os != 'windows-11-arm' }}
run: nu .github/workflows/release-pkg.nu
env:
OS: ${{ matrix.os }}
REF: ${{ github.ref }}
TARGET: ${{ matrix.target }}
- name: Build Nu for Windows ARM64
id: nu0
shell: pwsh
if: ${{ matrix.os == 'windows-11-arm' }}
run: |
$env:OS = 'windows'
$env:REF = '${{ github.ref }}'
$env:TARGET = '${{ matrix.target }}'
cargo build --release --all --target aarch64-pc-windows-msvc
cp ./target/${{ matrix.target }}/release/nu.exe .
./nu.exe -c 'version'
./nu.exe ${{github.workspace}}/.github/workflows/release-pkg.nu
- name: Create an Issue for Release Failure
if: ${{ failure() }}
uses: JasonEtco/create-an-issue@v2
@ -228,9 +217,7 @@ jobs:
prerelease: true
files: |
${{ steps.nu.outputs.msi }}
${{ steps.nu0.outputs.msi }}
${{ steps.nu.outputs.archive }}
${{ steps.nu0.outputs.archive }}
tag_name: ${{ needs.prepare.outputs.nightly_tag }}
name: ${{ needs.prepare.outputs.build_date }}-${{ needs.prepare.outputs.nightly_tag }}
env:
@ -276,7 +263,7 @@ jobs:
- name: Setup Nushell
uses: hustcer/setup-nu@v3
with:
version: 0.103.0
version: 0.105.1
# Keep the last a few releases
- name: Delete Older Releases

View File

@ -0,0 +1,44 @@
name: Checks to perform pre-release (manual)
on:
- workflow_dispatch
env:
NUSHELL_CARGO_PROFILE: ci
NU_LOG_LEVEL: DEBUG
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
jobs:
build-and-test:
strategy:
fail-fast: true
matrix:
platform: [windows-latest, macos-latest, ubuntu-22.04]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- uses: taiki-e/install-action@cargo-hack
- name: Feature power set
run: |
cargo hack --all --feature-powerset --at-least-one-of rustls-tls,native-tls --mutually-exclusive-features rustls-tls,native-tls --mutually-exclusive-features rustls-tls,static-link-openssl --skip default-no-clipboard,stable,mimalloc check
# Don't build fully for now as it will run out of disk space
# - name: Build all crates
# run: cargo hack --all build --clean-per-run
- name: Check for clean repo
shell: bash
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "there are changes";
git status --porcelain
exit 1
else
echo "no changes in working directory";
fi

View File

@ -58,7 +58,7 @@ jobs:
- name: Setup Nushell
uses: hustcer/setup-nu@v3
with:
version: nightly
version: 0.105.1
- name: Release MSI Packages
id: nu

View File

@ -99,6 +99,14 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
$env.CARGO_TARGET_LOONGARCH64_UNKNOWN_LINUX_GNU_LINKER = 'loongarch64-unknown-linux-gnu-gcc'
cargo-build-nu
}
'loongarch64-unknown-linux-musl' => {
print $"(ansi g)Downloading LoongArch64 musl cross-compilation toolchain...(ansi reset)"
aria2c -q https://github.com/LoongsonLab/oscomp-toolchains-for-oskernel/releases/download/loongarch64-linux-musl-cross-gcc-13.2.0/loongarch64-linux-musl-cross.tgz
tar -xf loongarch64-linux-musl-cross.tgz
$env.PATH = ($env.PATH | split row (char esep) | prepend $'($env.PWD)/loongarch64-linux-musl-cross/bin')
$env.CARGO_TARGET_LOONGARCH64_UNKNOWN_LINUX_MUSL_LINKER = "loongarch64-linux-musl-gcc"
cargo-build-nu
}
_ => {
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
# Actually just for x86_64-unknown-linux-musl target

View File

@ -35,6 +35,7 @@ jobs:
- armv7-unknown-linux-musleabihf
- riscv64gc-unknown-linux-gnu
- loongarch64-unknown-linux-gnu
- loongarch64-unknown-linux-musl
include:
- target: aarch64-apple-darwin
os: macos-latest
@ -60,6 +61,8 @@ jobs:
os: ubuntu-22.04
- target: loongarch64-unknown-linux-gnu
os: ubuntu-22.04
- target: loongarch64-unknown-linux-musl
os: ubuntu-22.04
runs-on: ${{matrix.os}}
@ -90,32 +93,17 @@ jobs:
- name: Setup Nushell
uses: hustcer/setup-nu@v3
if: ${{ matrix.os != 'windows-11-arm' }}
with:
version: 0.103.0
version: 0.105.1
- name: Release Nu Binary
id: nu
if: ${{ matrix.os != 'windows-11-arm' }}
run: nu .github/workflows/release-pkg.nu
env:
OS: ${{ matrix.os }}
REF: ${{ github.ref }}
TARGET: ${{ matrix.target }}
- name: Build Nu for Windows ARM64
id: nu0
shell: pwsh
if: ${{ matrix.os == 'windows-11-arm' }}
run: |
$env:OS = 'windows'
$env:REF = '${{ github.ref }}'
$env:TARGET = '${{ matrix.target }}'
cargo build --release --all --target aarch64-pc-windows-msvc
cp ./target/${{ matrix.target }}/release/nu.exe .
./nu.exe -c 'version'
./nu.exe ${{github.workspace}}/.github/workflows/release-pkg.nu
# WARN: Don't upgrade this action due to the release per asset issue.
# See: https://github.com/softprops/action-gh-release/issues/445
- name: Publish Archive
@ -125,9 +113,7 @@ jobs:
draft: true
files: |
${{ steps.nu.outputs.msi }}
${{ steps.nu0.outputs.msi }}
${{ steps.nu.outputs.archive }}
${{ steps.nu0.outputs.archive }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -10,4 +10,4 @@ jobs:
uses: actions/checkout@v4.1.7
- name: Check spelling
uses: crate-ci/typos@v1.33.1
uses: crate-ci/typos@v1.35.4

6
.gitignore vendored
View File

@ -32,11 +32,17 @@ unstable_cargo_features.txt
# Helix configuration folder
.helix/*
.helix
wix/bin/
wix/obj/
wix/nu/
# Coverage tools
lcov.info
tarpaulin-report.html
# benchmarking
/tango
# Visual Studio
.vs/*
*.rsproj

View File

@ -3,6 +3,7 @@
Welcome to Nushell and thank you for considering contributing!
## Table of contents
- [Tips for submitting PRs](#tips-for-submitting-prs)
- [Proposing design changes](#proposing-design-changes)
- [Developing](#developing)
- [Setup](#setup)
@ -20,6 +21,51 @@ More resources can be found in the nascent [developer documentation](devdocs/REA
- [Platform support policy](devdocs/PLATFORM_SUPPORT.md)
- [Our Rust style](devdocs/rust_style.md)
## Tips for submitting PRs
Thank you for improving Nushell! We are always glad to see contributions, and we are absolutely willing to talk through the design or implementation of your PR. Come talk with us in [Discord](https://discordapp.com/invite/NtAbbGn), or create a GitHub discussion or draft PR and we can help you work out the details from there.
**Please talk to the core team before making major changes!** See the [proposing design changes](#proposing-design-changes) for more details.
### Release notes section
In our PR template, we have a "Release notes summary" section which will be included in our release notes for our blog.
This section should include all information about your change which is relevant to a user of Nushell. You should try to keep it **brief and simple to understand**, and focus on the ways your change directly impacts the user experience. We highly encourage adding examples and, when relevant, screenshots in this section.
Please make sure to consider both the *intended changes*, such as additions or deliberate breaking changes **and** possible *side effects* that might change how users interact with a command or feature. It's important to think carefully about the ways that your PR might affect any aspect of the user experience, and to document these changes even if they seem minor or aren't directly related to the main purpose of the PR.
This section might not be relevant for all PRs. If your PR is a work in progress, feel free to write "WIP"/"TODO"/etc in this section. You can also write "N/A" if this is a technical change which doesn't impact the user experience.
If you're not sure what to put here, or need some help, **a core team member would be glad to help you out**. We may also makes some tweaks to your release notes section. Please don't take it personally, we just want to make sure our release notes are polished and easy to understand. Once the release notes section is ready, we'll add the (TODO label name) label to indicate that the release notes section is ready to be included in the actual release notes.
### Tests and formatting checks
Our CI system automatically checks formatting and runs our tests. If you're running into an issue, or just want to make sure everything is ready to go before creating your PR, you can run the checks yourself:
```nushell
use toolkit.nu # or use an `env_change` hook to activate it automatically
toolkit check pr
```
Furthermore, you can also runs these checks individually with the subcommands of `toolkit`, or run the underlying commands yourself:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make sure to enable [developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the tests for the standard library
If the checks are passing on your local system, but CI just won't pass, feel free to ask for help from the core team.
### Linking and mentioning issues
If your PR closes one or more issues, you can automatically link the PR with them by using one of the [linking keywords](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword):
- This PR should close #xxxx
- Fixes #xxxx
You can also mention related issues, PRs or discussions!
## Proposing design changes
First of all, before diving into the code, if you want to create a new feature, change something significantly, and especially if the change is user-facing, it is a good practice to first get an approval from the core team before starting to work on it.

800
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -10,8 +10,8 @@ homepage = "https://www.nushell.sh"
license = "MIT"
name = "nu"
repository = "https://github.com/nushell/nushell"
rust-version = "1.85.1"
version = "0.105.2"
rust-version = "1.87.0"
version = "0.106.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -24,36 +24,37 @@ pkg-fmt = "zip"
[workspace]
members = [
"crates/nu_plugin_custom_values",
"crates/nu_plugin_example",
"crates/nu_plugin_formats",
"crates/nu_plugin_gstat",
"crates/nu_plugin_inc",
"crates/nu_plugin_polars",
"crates/nu_plugin_query",
"crates/nu_plugin_stress_internals",
"crates/nu-cli",
"crates/nu-engine",
"crates/nu-parser",
"crates/nu-system",
"crates/nu-cmd-base",
"crates/nu-cmd-extra",
"crates/nu-cmd-lang",
"crates/nu-cmd-plugin",
"crates/nu-command",
"crates/nu-color-config",
"crates/nu-command",
"crates/nu-derive-value",
"crates/nu-engine",
"crates/nu-experimental",
"crates/nu-explore",
"crates/nu-json",
"crates/nu-lsp",
"crates/nu-pretty-hex",
"crates/nu-protocol",
"crates/nu-derive-value",
"crates/nu-plugin",
"crates/nu-parser",
"crates/nu-plugin-core",
"crates/nu-plugin-engine",
"crates/nu-plugin-protocol",
"crates/nu-plugin-test-support",
"crates/nu_plugin_inc",
"crates/nu_plugin_gstat",
"crates/nu_plugin_example",
"crates/nu_plugin_query",
"crates/nu_plugin_custom_values",
"crates/nu_plugin_formats",
"crates/nu_plugin_polars",
"crates/nu_plugin_stress_internals",
"crates/nu-plugin",
"crates/nu-pretty-hex",
"crates/nu-protocol",
"crates/nu-std",
"crates/nu-system",
"crates/nu-table",
"crates/nu-term-grid",
"crates/nu-test-support",
@ -71,7 +72,7 @@ brotli = "7.0"
byteorder = "1.5"
bytes = "1"
bytesize = "1.3.3"
calamine = "0.26"
calamine = "0.28"
chardetng = "0.1.17"
chrono = { default-features = false, version = "0.4.34" }
chrono-humanize = "0.2.3"
@ -82,17 +83,19 @@ csv = "1.3"
ctrlc = "3.4"
devicons = "0.6.12"
dialoguer = { default-features = false, version = "0.11" }
fuzzy-matcher = { version = "^0.3.7" }
digest = { default-features = false, version = "0.10" }
dirs = "5.0"
dirs-sys = "0.4"
dtparse = "2.0"
encoding_rs = "0.8"
fancy-regex = "0.14"
fancy-regex = "0.16"
filesize = "0.2"
filetime = "0.2"
heck = "0.5.0"
http = "1.3.1"
human-date-parser = "0.3.0"
indexmap = "2.9"
indexmap = "2.10"
indicatif = "0.17"
interprocess = "2.2.0"
is_executable = "1.0"
@ -131,7 +134,7 @@ proc-macro-error2 = "2.0"
proc-macro2 = "1.0"
procfs = "0.17.0"
pwd = "1.3"
quick-xml = "0.37.0"
quick-xml = "0.37.5"
quickcheck = "1.0"
quickcheck_macros = "1.1"
quote = "1.0"
@ -139,8 +142,8 @@ rand = "0.9"
getrandom = "0.2" # pick same version that rand requires
rand_chacha = "0.9"
ratatui = "0.29"
rayon = "1.10"
reedline = "0.40.0"
rayon = "1.11"
reedline = "0.41.0"
rmp = "0.8"
rmp-serde = "1.3"
roxmltree = "0.20"
@ -148,7 +151,10 @@ rstest = { version = "0.23", default-features = false }
rstest_reuse = "0.7"
rusqlite = "0.31"
rust-embed = "8.7.0"
rustls = { version = "0.23", default-features = false, features = ["std", "tls12"] }
# We have to fix rustls and ureq versions
# because we use unversioned api to allow users set up their own
# crypto providers (grep for "unversioned")
rustls = { version = "=0.23.28", default-features = false, features = ["std", "tls12"] }
rustls-native-certs = "0.8"
scopeguard = { version = "1.2.0" }
serde = { version = "1.0" }
@ -156,21 +162,22 @@ serde_json = "1.0.97"
serde_urlencoded = "0.7.1"
serde_yaml = "0.9.33"
sha2 = "0.10"
strip-ansi-escapes = "0.2.0"
strip-ansi-escapes = "0.2.1"
strum = "0.26"
strum_macros = "0.26"
syn = "2.0"
sysinfo = "0.33"
sysinfo = "0.36"
tabled = { version = "0.20", default-features = false }
tempfile = "3.20"
thiserror = "2.0.12"
titlecase = "3.6"
toml = "0.8"
trash = "5.2"
update-informer = { version = "1.2.0", default-features = false, features = ["github", "ureq"] }
update-informer = { version = "1.3.0", default-features = false, features = ["github", "ureq"] }
umask = "2.1"
unicode-segmentation = "1.12"
unicode-width = "0.2"
ureq = { version = "2.12", default-features = false, features = ["socks-proxy"] }
ureq = { version = "=3.0.12", default-features = false, features = ["socks-proxy"] }
url = "2.2"
uu_cp = "0.0.30"
uu_mkdir = "0.0.30"
@ -184,7 +191,7 @@ uuid = "1.16.0"
v_htmlescape = "0.15.0"
wax = "0.6"
web-time = "1.1.0"
which = "7.0.3"
which = "8.0.0"
windows = "0.56"
windows-sys = "0.48"
winreg = "0.52"
@ -195,27 +202,30 @@ webpki-roots = "1.0"
# Warning: workspace lints affect library code as well as tests, so don't enable lints that would be too noisy in tests like that.
# todo = "warn"
unchecked_duration_subtraction = "warn"
used_underscore_binding = "warn"
result_large_err = "allow"
[lints]
workspace = true
[dependencies]
nu-cli = { path = "./crates/nu-cli", version = "0.105.2" }
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.105.2" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.105.2" }
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.105.2", optional = true }
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.105.2" }
nu-command = { path = "./crates/nu-command", version = "0.105.2", default-features = false, features = ["os"] }
nu-engine = { path = "./crates/nu-engine", version = "0.105.2" }
nu-explore = { path = "./crates/nu-explore", version = "0.105.2" }
nu-lsp = { path = "./crates/nu-lsp/", version = "0.105.2" }
nu-parser = { path = "./crates/nu-parser", version = "0.105.2" }
nu-path = { path = "./crates/nu-path", version = "0.105.2" }
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.105.2" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.105.2" }
nu-std = { path = "./crates/nu-std", version = "0.105.2" }
nu-system = { path = "./crates/nu-system", version = "0.105.2" }
nu-utils = { path = "./crates/nu-utils", version = "0.105.2" }
nu-cli = { path = "./crates/nu-cli", version = "0.106.2" }
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.106.2" }
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.106.2" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.106.2" }
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.106.2", optional = true }
nu-command = { path = "./crates/nu-command", version = "0.106.2", default-features = false, features = ["os"] }
nu-engine = { path = "./crates/nu-engine", version = "0.106.2" }
nu-experimental = { path = "./crates/nu-experimental", version = "0.106.2" }
nu-explore = { path = "./crates/nu-explore", version = "0.106.2" }
nu-lsp = { path = "./crates/nu-lsp/", version = "0.106.2" }
nu-parser = { path = "./crates/nu-parser", version = "0.106.2" }
nu-path = { path = "./crates/nu-path", version = "0.106.2" }
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.106.2" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.106.2" }
nu-std = { path = "./crates/nu-std", version = "0.106.2" }
nu-system = { path = "./crates/nu-system", version = "0.106.2" }
nu-utils = { path = "./crates/nu-utils", version = "0.106.2" }
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
crossterm = { workspace = true }
@ -244,9 +254,9 @@ nix = { workspace = true, default-features = false, features = [
] }
[dev-dependencies]
nu-test-support = { path = "./crates/nu-test-support", version = "0.105.2" }
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.105.2" }
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.105.2" }
nu-test-support = { path = "./crates/nu-test-support", version = "0.106.2" }
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.106.2" }
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.106.2" }
assert_cmd = "2.0"
dirs = { workspace = true }
tango-bench = "0.6"
@ -257,10 +267,14 @@ serial_test = "3.2"
tempfile = { workspace = true }
[features]
# Enable all features while still avoiding mutually exclusive features.
# Use this if `--all-features` fails.
full = ["plugin", "rustls-tls", "system-clipboard", "trash-support", "sqlite"]
plugin = [
# crates
"nu-cmd-plugin",
"nu-plugin-engine",
"dep:nu-cmd-plugin",
"dep:nu-plugin-engine",
# features
"nu-cli/plugin",
@ -286,21 +300,20 @@ stable = ["default"]
# Enable to statically link OpenSSL (perl is required, to build OpenSSL https://docs.rs/openssl/latest/openssl/);
# otherwise the system version will be used. Not enabled by default because it takes a while to build
static-link-openssl = ["dep:openssl", "nu-cmd-lang/static-link-openssl"]
static-link-openssl = ["dep:openssl"]
# Optional system clipboard support in `reedline`, this behavior has problematic compatibility with some systems.
# Missing X server/ Wayland can cause issues
system-clipboard = [
"reedline/system_clipboard",
"nu-cli/system-clipboard",
"nu-cmd-lang/system-clipboard",
]
# Stable (Default)
trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"]
trash-support = ["nu-command/trash-support"]
# SQLite commands for nushell
sqlite = ["nu-command/sqlite", "nu-cmd-lang/sqlite", "nu-std/sqlite"]
sqlite = ["nu-command/sqlite", "nu-std/sqlite"]
[profile.release]
opt-level = "s" # Optimize for size
@ -330,7 +343,7 @@ bench = false
# To use a development version of a dependency please use a global override here
# changing versions in each sub-crate of the workspace is tedious
[patch.crates-io]
# reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
# Run all benchmarks with `cargo bench`

View File

@ -1,6 +1,6 @@
# Security Policy
As a shell and programming language Nushell provides you with great powers and the potential to do dangerous things to your computer and data. Whenever there is a risk that a malicious actor can abuse a bug or a violation of documented behavior/assumptions in Nushell to harm you this is a *security* risk.
As a shell and programming language Nushell provides you with great powers and the potential to do dangerous things to your computer and data. Whenever there is a risk that a malicious actor can abuse a bug or a violation of documented behavior/assumptions in Nushell to harm you this is a *security* risk.
We want to fix those issues without exposing our users to unnecessary risk. Thus we want to explain our security policy.
Additional issues may be part of *safety* where the behavior of Nushell as designed and implemented can cause unintended harm or a bug causes damage without the involvement of a third party.
@ -11,7 +11,7 @@ Only if you provide a strong reasoning and the necessary resources, will we cons
## Reporting a Vulnerability
If you suspect that a bug or behavior of Nushell can affect security or may be potentially exploitable, please report the issue to us in private.
If you suspect that a bug or behavior of Nushell can affect security or may be potentially exploitable, please report the issue to us in private.
Either reach out to the core team on [our Discord server](https://discord.gg/NtAbbGn) to arrange a private channel or use the [GitHub vulnerability reporting form](https://github.com/nushell/nushell/security/advisories/new).
Please try to answer the following questions:
- How can we reach you for further questions?

View File

@ -199,7 +199,7 @@ fn bench_record_nested_access(n: usize) -> impl IntoBenchmarks {
let nested_access = ".col".repeat(n);
bench_command(
format!("record_nested_access_{n}"),
format!("$record{} | ignore", nested_access),
format!("$record{nested_access} | ignore"),
stack,
engine,
)
@ -319,7 +319,7 @@ fn bench_eval_par_each(n: usize) -> impl IntoBenchmarks {
let stack = Stack::new();
bench_command(
format!("eval_par_each_{n}"),
format!("(1..{}) | par-each -t 2 {{|_| 1 }} | ignore", n),
format!("(1..{n}) | par-each -t 2 {{|_| 1 }} | ignore"),
stack,
engine,
)
@ -357,7 +357,7 @@ fn encode_json(row_cnt: usize, col_cnt: usize) -> impl IntoBenchmarks {
let encoder = Rc::new(EncodingType::try_from_bytes(b"json").unwrap());
[benchmark_fn(
format!("encode_json_{}_{}", row_cnt, col_cnt),
format!("encode_json_{row_cnt}_{col_cnt}"),
move |b| {
let encoder = encoder.clone();
let test_data = test_data.clone();
@ -377,7 +377,7 @@ fn encode_msgpack(row_cnt: usize, col_cnt: usize) -> impl IntoBenchmarks {
let encoder = Rc::new(EncodingType::try_from_bytes(b"msgpack").unwrap());
[benchmark_fn(
format!("encode_msgpack_{}_{}", row_cnt, col_cnt),
format!("encode_msgpack_{row_cnt}_{col_cnt}"),
move |b| {
let encoder = encoder.clone();
let test_data = test_data.clone();
@ -399,7 +399,7 @@ fn decode_json(row_cnt: usize, col_cnt: usize) -> impl IntoBenchmarks {
encoder.encode(&test_data, &mut res).unwrap();
[benchmark_fn(
format!("decode_json_{}_{}", row_cnt, col_cnt),
format!("decode_json_{row_cnt}_{col_cnt}"),
move |b| {
let res = res.clone();
b.iter(move || {
@ -422,7 +422,7 @@ fn decode_msgpack(row_cnt: usize, col_cnt: usize) -> impl IntoBenchmarks {
encoder.encode(&test_data, &mut res).unwrap();
[benchmark_fn(
format!("decode_msgpack_{}_{}", row_cnt, col_cnt),
format!("decode_msgpack_{row_cnt}_{col_cnt}"),
move |b| {
let res = res.clone();
b.iter(move || {

View File

@ -5,29 +5,29 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
edition = "2024"
license = "MIT"
name = "nu-cli"
version = "0.105.2"
version = "0.106.2"
[lib]
bench = false
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.105.2" }
nu-command = { path = "../nu-command", version = "0.105.2" }
nu-std = { path = "../nu-std", version = "0.105.2" }
nu-test-support = { path = "../nu-test-support", version = "0.105.2" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.106.2" }
nu-command = { path = "../nu-command", version = "0.106.2" }
nu-std = { path = "../nu-std", version = "0.106.2" }
nu-test-support = { path = "../nu-test-support", version = "0.106.2" }
rstest = { workspace = true, default-features = false }
tempfile = { workspace = true }
[dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.2" }
nu-engine = { path = "../nu-engine", version = "0.105.2", features = ["os"] }
nu-glob = { path = "../nu-glob", version = "0.105.2" }
nu-path = { path = "../nu-path", version = "0.105.2" }
nu-parser = { path = "../nu-parser", version = "0.105.2" }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.105.2", optional = true }
nu-protocol = { path = "../nu-protocol", version = "0.105.2", features = ["os"] }
nu-utils = { path = "../nu-utils", version = "0.105.2" }
nu-color-config = { path = "../nu-color-config", version = "0.105.2" }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.106.2" }
nu-engine = { path = "../nu-engine", version = "0.106.2", features = ["os"] }
nu-glob = { path = "../nu-glob", version = "0.106.2" }
nu-path = { path = "../nu-path", version = "0.106.2" }
nu-parser = { path = "../nu-parser", version = "0.106.2" }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.106.2", optional = true }
nu-protocol = { path = "../nu-protocol", version = "0.106.2", features = ["os"] }
nu-utils = { path = "../nu-utils", version = "0.106.2" }
nu-color-config = { path = "../nu-color-config", version = "0.106.2" }
nu-ansi-term = { workspace = true }
reedline = { workspace = true, features = ["bashisms", "sqlite"] }

View File

@ -57,7 +57,7 @@ Note that history item IDs are ignored when importing from file."#
result: None,
},
Example {
example: "[[ command_line cwd ]; [ foo /home ]] | history import",
example: "[[ command cwd ]; [ foo /home ]] | history import",
description: "Append `foo` ran from `/home` to the current history",
result: None,
},

View File

@ -118,7 +118,7 @@ fn get_suggestions_by_value(
|| s.chars()
.any(|c: char| !(c.is_ascii_alphabetic() || ['_', '-'].contains(&c)))
{
format!("{:?}", s)
format!("{s:?}")
} else {
s
};

View File

@ -52,7 +52,7 @@ impl CommandCompletion {
continue;
};
let value = if matched_internal(&name) {
format!("^{}", name)
format!("^{name}")
} else {
name.clone()
};

View File

@ -176,7 +176,7 @@ impl NuCompleter {
&mut working_set,
Some("completer"),
// Add a placeholder `a` to the end
format!("{}a", line).as_bytes(),
format!("{line}a").as_bytes(),
false,
);
self.fetch_completions_by_block(block, &working_set, pos, offset, line, true)
@ -348,8 +348,43 @@ impl NuCompleter {
for (arg_idx, arg) in call.arguments.iter().enumerate() {
let span = arg.span();
if span.contains(pos) {
// if customized completion specified, it has highest priority
if let Some(decl_id) = arg.expr().and_then(|e| e.custom_completion) {
// Get custom completion from PositionalArg or Flag
let custom_completion_decl_id = {
// Check PositionalArg or Flag from Signature
let signature = working_set.get_decl(call.decl_id).signature();
match arg {
// For named arguments, check Flag
Argument::Named((name, short, value)) => {
if value.as_ref().is_none_or(|e| !e.span.contains(pos)) {
None
} else {
// If we're completing the value of the flag,
// search for the matching custom completion decl_id (long or short)
let flag =
signature.get_long_flag(&name.item).or_else(|| {
short.as_ref().and_then(|s| {
signature.get_short_flag(
s.item.chars().next().unwrap_or('_'),
)
})
});
flag.and_then(|f| f.custom_completion)
}
}
// For positional arguments, check PositionalArg
Argument::Positional(_) => {
// Find the right positional argument by index
let arg_pos = positional_arg_indices.len();
signature
.get_positional(arg_pos)
.and_then(|pos_arg| pos_arg.custom_completion)
}
_ => None,
}
};
if let Some(decl_id) = custom_completion_decl_id {
// for `--foo <tab>` and `--foo=<tab>`, the arg span should be trimmed
let (new_span, prefix) = if matches!(arg, Argument::Named(_)) {
strip_placeholder_with_rsplit(
@ -370,7 +405,8 @@ impl NuCompleter {
FileCompletion,
);
suggestions.extend(self.process_completion(&mut completer, &ctx));
// Prioritize argument completions over (sub)commands
suggestions.splice(0..0, self.process_completion(&mut completer, &ctx));
break;
}
@ -384,33 +420,39 @@ impl NuCompleter {
};
self.process_completion(&mut flag_completions, &ctx)
};
suggestions.extend(match arg {
// flags
Argument::Named(_) | Argument::Unknown(_)
if prefix.starts_with(b"-") =>
{
flag_completion_helper()
}
// only when `strip` == false
Argument::Positional(_) if prefix == b"-" => flag_completion_helper(),
// complete according to expression type and command head
Argument::Positional(expr) => {
let command_head = working_set.get_decl(call.decl_id).name();
positional_arg_indices.push(arg_idx);
self.argument_completion_helper(
PositionalArguments {
command_head,
positional_arg_indices,
arguments: &call.arguments,
expr,
},
pos,
&ctx,
suggestions.is_empty(),
)
}
_ => vec![],
});
// Prioritize argument completions over (sub)commands
suggestions.splice(
0..0,
match arg {
// flags
Argument::Named(_) | Argument::Unknown(_)
if prefix.starts_with(b"-") =>
{
flag_completion_helper()
}
// only when `strip` == false
Argument::Positional(_) if prefix == b"-" => {
flag_completion_helper()
}
// complete according to expression type and command head
Argument::Positional(expr) => {
let command_head = working_set.get_decl(call.decl_id).name();
positional_arg_indices.push(arg_idx);
self.argument_completion_helper(
PositionalArguments {
command_head,
positional_arg_indices,
arguments: &call.arguments,
expr,
},
pos,
&ctx,
suggestions.is_empty(),
)
}
_ => vec![],
},
);
break;
} else if !matches!(arg, Argument::Named(_)) {
positional_arg_indices.push(arg_idx);
@ -462,10 +504,18 @@ impl NuCompleter {
if let Some(external_result) =
self.external_completion(closure, &text_spans, offset, new_span)
{
suggestions.extend(external_result);
// Prioritize external results over (sub)commands
suggestions.splice(0..0, external_result);
return suggestions;
}
}
// for external path arguments with spaces, please check issue #15790
if suggestions.is_empty() {
let (new_span, prefix) =
strip_placeholder_if_any(working_set, &span, strip);
let ctx = Context::new(working_set, new_span, prefix, offset);
return self.process_completion(&mut FileCompletion, &ctx);
}
break;
}
}
@ -703,7 +753,7 @@ impl NuCompleter {
Ok(value) => {
log::error!(
"External completer returned invalid value of type {}",
value.get_type().to_string()
value.get_type()
);
Some(vec![])
}
@ -842,7 +892,7 @@ mod completer_tests {
for (line, has_result, begins_with, expected_values) in dataset {
let result = completer.fetch_completions_at(line, line.len());
// Test whether the result is empty or not
assert_eq!(!result.is_empty(), has_result, "line: {}", line);
assert_eq!(!result.is_empty(), has_result, "line: {line}");
// Test whether the result begins with the expected value
result
@ -857,8 +907,7 @@ mod completer_tests {
.filter(|x| *x)
.count(),
expected_values.len(),
"line: {}",
line
"line: {line}"
);
}
}

View File

@ -314,7 +314,7 @@ pub fn escape_path(path: String) -> String {
if path.contains('\'') {
// decide to use double quotes
// Path as Debug will do the escaping for `"`, `\`
format!("{:?}", path)
format!("{path:?}")
} else {
format!("'{path}'")
}

View File

@ -102,7 +102,11 @@ impl<T> NuMatcher<'_, T> {
options,
needle: needle.to_owned(),
state: State::Fuzzy {
matcher: Matcher::new(Config::DEFAULT),
matcher: Matcher::new({
let mut cfg = Config::DEFAULT;
cfg.prefer_prefix = true;
cfg
}),
atom,
items: Vec::new(),
},

View File

@ -140,7 +140,7 @@ impl<T: Completer> Completer for CustomCompletion<T> {
_ => {
log::error!(
"Custom completer returned invalid value of type {}",
value.get_type().to_string()
value.get_type()
);
return vec![];
}

View File

@ -129,7 +129,7 @@ impl Completer for DotNuCompletion {
.take_while(|c| "`'\"".contains(*c))
.collect::<String>();
for path in ["std", "std-rfc"] {
let path = format!("{}{}", surround_prefix, path);
let path = format!("{surround_prefix}{path}");
matcher.add(
path.clone(),
FileSuggestion {
@ -146,7 +146,7 @@ impl Completer for DotNuCompletion {
for sub_vp_id in sub_paths {
let (path, sub_vp) = working_set.get_virtual_path(*sub_vp_id);
let path = path
.strip_prefix(&format!("{}/", base_dir))
.strip_prefix(&format!("{base_dir}/"))
.unwrap_or(path)
.to_string();
matcher.add(

View File

@ -278,7 +278,7 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState) -> bool {
&mut stack,
&old_contents,
&old_plugin_file_path.to_string_lossy(),
PipelineData::Empty,
PipelineData::empty(),
false,
) != 0
{

View File

@ -3,9 +3,9 @@ use nu_engine::eval_block;
use nu_parser::parse;
use nu_protocol::{
PipelineData, ShellError, Spanned, Value,
cli_error::report_compile_error,
debugger::WithoutDebug,
engine::{EngineState, Stack, StateWorkingSet},
report_error::report_compile_error,
report_parse_error, report_parse_warning,
};
use std::sync::Arc;

View File

@ -5,9 +5,9 @@ use nu_parser::parse;
use nu_path::canonicalize_with;
use nu_protocol::{
PipelineData, ShellError, Span, Value,
cli_error::report_compile_error,
debugger::WithoutDebug,
engine::{EngineState, Stack, StateWorkingSet},
report_error::report_compile_error,
report_parse_error, report_parse_warning,
shell_error::io::*,
};

View File

@ -1,4 +1,4 @@
use nu_engine::documentation::{HelpStyle, get_flags_section};
use nu_engine::documentation::{FormatterValue, HelpStyle, get_flags_section};
use nu_protocol::{Config, engine::EngineState, levenshtein_distance};
use nu_utils::IgnoreCaseExt;
use reedline::{Completer, Suggestion};
@ -66,8 +66,11 @@ impl NuHelpCompleter {
let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature());
if !sig.named.is_empty() {
long_desc.push_str(&get_flags_section(&sig, &help_style, |v| {
v.to_parsable_string(", ", &self.config)
long_desc.push_str(&get_flags_section(&sig, &help_style, |v| match v {
FormatterValue::DefaultValue(value) => {
value.to_parsable_string(", ", &self.config)
}
FormatterValue::CodeString(text) => text.to_string(),
}))
}

View File

@ -3,6 +3,8 @@ use std::sync::Arc;
use nu_engine::command_prelude::*;
use reedline::{Highlighter, StyledText};
use crate::syntax_highlight::highlight_syntax;
#[derive(Clone)]
pub struct NuHighlight;
@ -14,6 +16,11 @@ impl Command for NuHighlight {
fn signature(&self) -> Signature {
Signature::build("nu-highlight")
.category(Category::Strings)
.switch(
"reject-garbage",
"Return an error if invalid syntax (garbage) was encountered",
Some('r'),
)
.input_output_types(vec![(Type::String, Type::String)])
}
@ -32,19 +39,33 @@ impl Command for NuHighlight {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let reject_garbage = call.has_flag(engine_state, stack, "reject-garbage")?;
let head = call.head;
let signals = engine_state.signals();
let highlighter = crate::NuHighlighter {
engine_state: Arc::new(engine_state.clone()),
stack: Arc::new(stack.clone()),
};
let engine_state = Arc::new(engine_state.clone());
let stack = Arc::new(stack.clone());
input.map(
move |x| match x.coerce_into_string() {
Ok(line) => {
let highlights = highlighter.highlight(&line, line.len());
let result = highlight_syntax(&engine_state, &stack, &line, line.len());
let highlights = match (reject_garbage, result.found_garbage) {
(false, _) => result.text,
(true, None) => result.text,
(true, Some(span)) => {
let error = ShellError::OutsideSpannedLabeledError {
src: line,
error: "encountered invalid syntax while highlighting".into(),
msg: "invalid syntax".into(),
span,
};
return Value::error(error, head);
}
};
Value::string(highlights.render_simple(), head)
}
Err(err) => Value::error(err, head),

View File

@ -61,7 +61,7 @@ fn get_prompt_string(
.and_then(|v| match v {
Value::Closure { val, .. } => {
let result = ClosureEvalOnce::new(engine_state, stack, val.as_ref().clone())
.run_with_input(PipelineData::Empty);
.run_with_input(PipelineData::empty());
trace!(
"get_prompt_string (block) {}:{}:{}",
@ -76,7 +76,7 @@ fn get_prompt_string(
})
.ok()
}
Value::String { .. } => Some(PipelineData::Value(v.clone(), None)),
Value::String { .. } => Some(PipelineData::value(v.clone(), None)),
_ => None,
})
.and_then(|pipeline_data| {

View File

@ -159,7 +159,7 @@ pub(crate) fn add_menus(
engine_state.merge_delta(delta)?;
let mut temp_stack = Stack::new().collect_value();
let input = PipelineData::Empty;
let input = PipelineData::empty();
menu_eval_results.push(eval_block::<WithoutDebug>(
&engine_state,
&mut temp_stack,
@ -1047,6 +1047,10 @@ fn event_from_record(
ReedlineEvent::ExecuteHostCommand(cmd.to_expanded_string("", config))
}
"openeditor" => ReedlineEvent::OpenEditor,
"vichangemode" => {
let mode = extract_value("mode", record, span)?;
ReedlineEvent::ViChangeMode(mode.as_str()?.to_owned())
}
str => {
return Err(ShellError::InvalidValue {
valid: "a reedline event".into(),
@ -1172,6 +1176,7 @@ fn edit_from_record(
"cutfromlinestart" => EditCommand::CutFromLineStart,
"cuttoend" => EditCommand::CutToEnd,
"cuttolineend" => EditCommand::CutToLineEnd,
"killline" => EditCommand::KillLine,
"cutwordleft" => EditCommand::CutWordLeft,
"cutbigwordleft" => EditCommand::CutBigWordLeft,
"cutwordright" => EditCommand::CutWordRight,

View File

@ -22,8 +22,8 @@ use nu_color_config::StyleComputer;
use nu_engine::env_to_strings;
use nu_engine::exit::cleanup_exit;
use nu_parser::{lex, parse, trim_quotes_str};
use nu_protocol::shell_error;
use nu_protocol::shell_error::io::IoError;
use nu_protocol::{BannerKind, shell_error};
use nu_protocol::{
HistoryConfig, HistoryFileFormat, PipelineData, ShellError, Span, Spanned, Value,
config::NuCursorShape,
@ -145,8 +145,8 @@ pub fn evaluate_repl(
if load_std_lib.is_none() {
match engine_state.get_config().show_banner {
Value::Bool { val: false, .. } => {}
Value::String { ref val, .. } if val == "short" => {
BannerKind::None => {}
BannerKind::Short => {
eval_source(
engine_state,
&mut unique_stack,
@ -156,7 +156,7 @@ pub fn evaluate_repl(
false,
);
}
_ => {
BannerKind::Full => {
eval_source(
engine_state,
&mut unique_stack,
@ -239,7 +239,7 @@ fn escape_special_vscode_bytes(input: &str) -> Result<String, ShellError> {
match byte {
// Escape bytes below 0x20
b if b < 0x20 => format!("\\x{:02X}", byte).into_bytes(),
b if b < 0x20 => format!("\\x{byte:02X}").into_bytes(),
// Escape semicolon as \x3B
b';' => "\\x3B".to_string().into_bytes(),
// Escape backslash as \\
@ -325,7 +325,19 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
perf!("reset signals", start_time, use_color);
start_time = std::time::Instant::now();
// Right before we start our prompt and take input from the user, fire the "pre_prompt" hook
// Check all the environment variables they ask for
// fire the "env_change" hook
if let Err(error) = hook::eval_env_change_hook(
&engine_state.get_config().hooks.env_change.clone(),
engine_state,
&mut stack,
) {
report_shell_error(engine_state, &error)
}
perf!("env-change hook", start_time, use_color);
start_time = std::time::Instant::now();
// Next, right before we start our prompt and take input from the user, fire the "pre_prompt" hook
if let Err(err) = hook::eval_hooks(
engine_state,
&mut stack,
@ -337,18 +349,6 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
}
perf!("pre-prompt hook", start_time, use_color);
start_time = std::time::Instant::now();
// Next, check all the environment variables they ask for
// fire the "env_change" hook
if let Err(error) = hook::eval_env_change_hook(
&engine_state.get_config().hooks.env_change.clone(),
engine_state,
&mut stack,
) {
report_shell_error(engine_state, &error)
}
perf!("env-change hook", start_time, use_color);
let engine_reference = Arc::new(engine_state.clone());
let config = stack.get_config(engine_state);
@ -1097,8 +1097,7 @@ fn run_shell_integration_osc633(
// If we're in vscode, run their specific ansi escape sequence.
// This is helpful for ctrl+g to change directories in the terminal.
run_ansi_sequence(&format!(
"{}{}{}",
VSCODE_CWD_PROPERTY_MARKER_PREFIX, path, VSCODE_CWD_PROPERTY_MARKER_SUFFIX
"{VSCODE_CWD_PROPERTY_MARKER_PREFIX}{path}{VSCODE_CWD_PROPERTY_MARKER_SUFFIX}"
));
perf!(
@ -1114,10 +1113,7 @@ fn run_shell_integration_osc633(
//OSC 633 ; E ; <commandline> [; <nonce] ST - Explicitly set the command line with an optional nonce.
run_ansi_sequence(&format!(
"{}{}{}",
VSCODE_COMMANDLINE_MARKER_PREFIX,
replaced_cmd_text,
VSCODE_COMMANDLINE_MARKER_SUFFIX
"{VSCODE_COMMANDLINE_MARKER_PREFIX}{replaced_cmd_text}{VSCODE_COMMANDLINE_MARKER_SUFFIX}"
));
}
}
@ -1493,7 +1489,7 @@ mod test_auto_cd {
// Parse the input. It must be an auto-cd operation.
let op = parse_operation(input.to_string(), &engine_state, &stack).unwrap();
let ReplOperation::AutoCd { cwd, target, span } = op else {
panic!("'{}' was not parsed into an auto-cd operation", input)
panic!("'{input}' was not parsed into an auto-cd operation")
};
// Perform the auto-cd operation.

View File

@ -17,147 +17,173 @@ pub struct NuHighlighter {
}
impl Highlighter for NuHighlighter {
fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
trace!("highlighting: {}", line);
fn highlight(&self, line: &str, cursor: usize) -> StyledText {
let result = highlight_syntax(&self.engine_state, &self.stack, line, cursor);
result.text
}
}
let config = self.stack.get_config(&self.engine_state);
let highlight_resolved_externals = config.highlight_resolved_externals;
let mut working_set = StateWorkingSet::new(&self.engine_state);
let block = parse(&mut working_set, None, line.as_bytes(), false);
let (shapes, global_span_offset) = {
let mut shapes = flatten_block(&working_set, &block);
// Highlighting externals has a config point because of concerns that using which to resolve
// externals may slow down things too much.
if highlight_resolved_externals {
for (span, shape) in shapes.iter_mut() {
if *shape == FlatShape::External {
let str_contents =
working_set.get_span_contents(Span::new(span.start, span.end));
/// Result of a syntax highlight operation
#[derive(Default)]
pub(crate) struct HighlightResult {
/// The highlighted text
pub(crate) text: StyledText,
/// The span of any garbage that was highlighted
pub(crate) found_garbage: Option<Span>,
}
let str_word = String::from_utf8_lossy(str_contents).to_string();
let paths = env::path_str(&self.engine_state, &self.stack, *span).ok();
#[allow(deprecated)]
let res = if let Ok(cwd) =
env::current_dir_str(&self.engine_state, &self.stack)
{
which::which_in(str_word, paths.as_ref(), cwd).ok()
} else {
which::which_in_global(str_word, paths.as_ref())
.ok()
.and_then(|mut i| i.next())
};
if res.is_some() {
*shape = FlatShape::ExternalResolved;
}
pub(crate) fn highlight_syntax(
engine_state: &EngineState,
stack: &Stack,
line: &str,
cursor: usize,
) -> HighlightResult {
trace!("highlighting: {}", line);
let config = stack.get_config(engine_state);
let highlight_resolved_externals = config.highlight_resolved_externals;
let mut working_set = StateWorkingSet::new(engine_state);
let block = parse(&mut working_set, None, line.as_bytes(), false);
let (shapes, global_span_offset) = {
let mut shapes = flatten_block(&working_set, &block);
// Highlighting externals has a config point because of concerns that using which to resolve
// externals may slow down things too much.
if highlight_resolved_externals {
for (span, shape) in shapes.iter_mut() {
if *shape == FlatShape::External {
let str_contents =
working_set.get_span_contents(Span::new(span.start, span.end));
let str_word = String::from_utf8_lossy(str_contents).to_string();
let paths = env::path_str(engine_state, stack, *span).ok();
let res = if let Ok(cwd) = engine_state.cwd(Some(stack)) {
which::which_in(str_word, paths.as_ref(), cwd).ok()
} else {
which::which_in_global(str_word, paths.as_ref())
.ok()
.and_then(|mut i| i.next())
};
if res.is_some() {
*shape = FlatShape::ExternalResolved;
}
}
}
(shapes, self.engine_state.next_span_start())
}
(shapes, engine_state.next_span_start())
};
let mut result = HighlightResult::default();
let mut last_seen_span = global_span_offset;
let global_cursor_offset = cursor + global_span_offset;
let matching_brackets_pos = find_matching_brackets(
line,
&working_set,
&block,
global_span_offset,
global_cursor_offset,
);
for shape in &shapes {
if shape.0.end <= last_seen_span
|| last_seen_span < global_span_offset
|| shape.0.start < global_span_offset
{
// We've already output something for this span
// so just skip this one
continue;
}
if shape.0.start > last_seen_span {
let gap = line
[(last_seen_span - global_span_offset)..(shape.0.start - global_span_offset)]
.to_string();
result.text.push((Style::new(), gap));
}
let next_token = line
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
.to_string();
let mut add_colored_token = |shape: &FlatShape, text: String| {
result
.text
.push((get_shape_color(shape.as_str(), &config), text));
};
let mut output = StyledText::default();
let mut last_seen_span = global_span_offset;
let global_cursor_offset = _cursor + global_span_offset;
let matching_brackets_pos = find_matching_brackets(
line,
&working_set,
&block,
global_span_offset,
global_cursor_offset,
);
for shape in &shapes {
if shape.0.end <= last_seen_span
|| last_seen_span < global_span_offset
|| shape.0.start < global_span_offset
{
// We've already output something for this span
// so just skip this one
continue;
match shape.1 {
FlatShape::Garbage => {
result.found_garbage.get_or_insert_with(|| {
Span::new(
shape.0.start - global_span_offset,
shape.0.end - global_span_offset,
)
});
add_colored_token(&shape.1, next_token)
}
if shape.0.start > last_seen_span {
let gap = line
[(last_seen_span - global_span_offset)..(shape.0.start - global_span_offset)]
.to_string();
output.push((Style::new(), gap));
}
let next_token = line
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
.to_string();
let mut add_colored_token = |shape: &FlatShape, text: String| {
output.push((get_shape_color(shape.as_str(), &config), text));
};
match shape.1 {
FlatShape::Garbage => add_colored_token(&shape.1, next_token),
FlatShape::Nothing => add_colored_token(&shape.1, next_token),
FlatShape::Binary => add_colored_token(&shape.1, next_token),
FlatShape::Bool => add_colored_token(&shape.1, next_token),
FlatShape::Int => add_colored_token(&shape.1, next_token),
FlatShape::Float => add_colored_token(&shape.1, next_token),
FlatShape::Range => add_colored_token(&shape.1, next_token),
FlatShape::InternalCall(_) => add_colored_token(&shape.1, next_token),
FlatShape::External => add_colored_token(&shape.1, next_token),
FlatShape::ExternalArg => add_colored_token(&shape.1, next_token),
FlatShape::ExternalResolved => add_colored_token(&shape.1, next_token),
FlatShape::Keyword => add_colored_token(&shape.1, next_token),
FlatShape::Literal => add_colored_token(&shape.1, next_token),
FlatShape::Operator => add_colored_token(&shape.1, next_token),
FlatShape::Signature => add_colored_token(&shape.1, next_token),
FlatShape::String => add_colored_token(&shape.1, next_token),
FlatShape::RawString => add_colored_token(&shape.1, next_token),
FlatShape::StringInterpolation => add_colored_token(&shape.1, next_token),
FlatShape::DateTime => add_colored_token(&shape.1, next_token),
FlatShape::List
| FlatShape::Table
| FlatShape::Record
| FlatShape::Block
| FlatShape::Closure => {
let span = shape.0;
let shape = &shape.1;
let spans = split_span_by_highlight_positions(
line,
span,
&matching_brackets_pos,
global_span_offset,
);
for (part, highlight) in spans {
let start = part.start - span.start;
let end = part.end - span.start;
let text = next_token[start..end].to_string();
let mut style = get_shape_color(shape.as_str(), &config);
if highlight {
style = get_matching_brackets_style(style, &config);
}
output.push((style, text));
FlatShape::Nothing => add_colored_token(&shape.1, next_token),
FlatShape::Binary => add_colored_token(&shape.1, next_token),
FlatShape::Bool => add_colored_token(&shape.1, next_token),
FlatShape::Int => add_colored_token(&shape.1, next_token),
FlatShape::Float => add_colored_token(&shape.1, next_token),
FlatShape::Range => add_colored_token(&shape.1, next_token),
FlatShape::InternalCall(_) => add_colored_token(&shape.1, next_token),
FlatShape::External => add_colored_token(&shape.1, next_token),
FlatShape::ExternalArg => add_colored_token(&shape.1, next_token),
FlatShape::ExternalResolved => add_colored_token(&shape.1, next_token),
FlatShape::Keyword => add_colored_token(&shape.1, next_token),
FlatShape::Literal => add_colored_token(&shape.1, next_token),
FlatShape::Operator => add_colored_token(&shape.1, next_token),
FlatShape::Signature => add_colored_token(&shape.1, next_token),
FlatShape::String => add_colored_token(&shape.1, next_token),
FlatShape::RawString => add_colored_token(&shape.1, next_token),
FlatShape::StringInterpolation => add_colored_token(&shape.1, next_token),
FlatShape::DateTime => add_colored_token(&shape.1, next_token),
FlatShape::List
| FlatShape::Table
| FlatShape::Record
| FlatShape::Block
| FlatShape::Closure => {
let span = shape.0;
let shape = &shape.1;
let spans = split_span_by_highlight_positions(
line,
span,
&matching_brackets_pos,
global_span_offset,
);
for (part, highlight) in spans {
let start = part.start - span.start;
let end = part.end - span.start;
let text = next_token[start..end].to_string();
let mut style = get_shape_color(shape.as_str(), &config);
if highlight {
style = get_matching_brackets_style(style, &config);
}
result.text.push((style, text));
}
FlatShape::Filepath => add_colored_token(&shape.1, next_token),
FlatShape::Directory => add_colored_token(&shape.1, next_token),
FlatShape::GlobInterpolation => add_colored_token(&shape.1, next_token),
FlatShape::GlobPattern => add_colored_token(&shape.1, next_token),
FlatShape::Variable(_) | FlatShape::VarDecl(_) => {
add_colored_token(&shape.1, next_token)
}
FlatShape::Flag => add_colored_token(&shape.1, next_token),
FlatShape::Pipe => add_colored_token(&shape.1, next_token),
FlatShape::Redirection => add_colored_token(&shape.1, next_token),
FlatShape::Custom(..) => add_colored_token(&shape.1, next_token),
FlatShape::MatchPattern => add_colored_token(&shape.1, next_token),
}
last_seen_span = shape.0.end;
}
let remainder = line[(last_seen_span - global_span_offset)..].to_string();
if !remainder.is_empty() {
output.push((Style::new(), remainder));
FlatShape::Filepath => add_colored_token(&shape.1, next_token),
FlatShape::Directory => add_colored_token(&shape.1, next_token),
FlatShape::GlobInterpolation => add_colored_token(&shape.1, next_token),
FlatShape::GlobPattern => add_colored_token(&shape.1, next_token),
FlatShape::Variable(_) | FlatShape::VarDecl(_) => {
add_colored_token(&shape.1, next_token)
}
FlatShape::Flag => add_colored_token(&shape.1, next_token),
FlatShape::Pipe => add_colored_token(&shape.1, next_token),
FlatShape::Redirection => add_colored_token(&shape.1, next_token),
FlatShape::Custom(..) => add_colored_token(&shape.1, next_token),
FlatShape::MatchPattern => add_colored_token(&shape.1, next_token),
}
output
last_seen_span = shape.0.end;
}
let remainder = line[(last_seen_span - global_span_offset)..].to_string();
if !remainder.is_empty() {
result.text.push((Style::new(), remainder));
}
result
}
fn split_span_by_highlight_positions(

View File

@ -5,9 +5,9 @@ use nu_engine::{eval_block, eval_block_with_early_return};
use nu_parser::{Token, TokenContents, lex, parse, unescape_unquote_string};
use nu_protocol::{
PipelineData, ShellError, Span, Value,
cli_error::report_compile_error,
debugger::WithoutDebug,
engine::{EngineState, Stack, StateWorkingSet},
report_error::report_compile_error,
report_parse_error, report_parse_warning, report_shell_error,
};
#[cfg(windows)]

View File

@ -5,3 +5,9 @@ fn nu_highlight_not_expr() {
let actual = nu!("'not false' | nu-highlight | ansi strip");
assert_eq!(actual.out, "not false");
}
#[test]
fn nu_highlight_where_row_condition() {
let actual = nu!("'ls | where a b 12345(' | nu-highlight | ansi strip");
assert_eq!(actual.out, "ls | where a b 12345(");
}

View File

@ -9,14 +9,16 @@ use std::{
use nu_cli::NuCompleter;
use nu_engine::eval_block;
use nu_parser::parse;
use nu_path::expand_tilde;
use nu_path::{AbsolutePathBuf, expand_tilde};
use nu_protocol::{Config, PipelineData, debugger::WithoutDebug, engine::StateWorkingSet};
use nu_std::load_standard_library;
use nu_test_support::fs;
use reedline::{Completer, Suggestion};
use rstest::{fixture, rstest};
use support::{
completions_helpers::{
new_dotnu_engine, new_external_engine, new_partial_engine, new_quote_engine,
new_dotnu_engine, new_engine_helper, new_external_engine, new_partial_engine,
new_quote_engine,
},
file, folder, match_suggestions, match_suggestions_by_string, new_engine,
};
@ -95,8 +97,11 @@ fn extern_completer() -> NuCompleter {
// Add record value as example
let record = r#"
def animals [] { [ "cat", "dog", "eel" ] }
def fruits [] { [ "apple", "banana" ] }
extern spam [
animal: string@animals
fruit?: string@fruits
...rest: string@animals
--foo (-f): string@animals
-b: string@animals
]
@ -123,7 +128,7 @@ fn custom_completer_with_options(
global_opts,
completions
.iter()
.map(|comp| format!("'{}'", comp))
.map(|comp| format!("'{comp}'"))
.collect::<Vec<_>>()
.join(", "),
completer_opts,
@ -307,10 +312,10 @@ fn custom_arguments_and_subcommands() {
let suggestions = completer.complete(completion_str, completion_str.len());
// including both subcommand and directory completions
let expected = [
"foo test bar".into(),
folder("test_a"),
file("test_a_symlink"),
folder("test_b"),
"foo test bar".into(),
];
match_suggestions_by_string(&expected, &suggestions);
}
@ -328,7 +333,7 @@ fn custom_flags_and_subcommands() {
let completion_str = "foo --test";
let suggestions = completer.complete(completion_str, completion_str.len());
// including both flag and directory completions
let expected: Vec<_> = vec!["foo --test bar", "--test"];
let expected: Vec<_> = vec!["--test", "foo --test bar"];
match_suggestions(&expected, &suggestions);
}
@ -716,6 +721,16 @@ fn external_completer_fallback() {
let expected = [folder("test_a"), file("test_a_symlink"), folder("test_b")];
let suggestions = run_external_completion(block, input);
match_suggestions_by_string(&expected, &suggestions);
// issue #15790
let input = "foo `dir with space/`";
let expected = vec!["`dir with space/bar baz`", "`dir with space/foo`"];
let suggestions = run_external_completion_within_pwd(
block,
input,
fs::fixtures().join("external_completions"),
);
match_suggestions(&expected, &suggestions);
}
/// Fallback to external completions for flags of `sudo`
@ -1468,11 +1483,12 @@ fn command_watch_with_filecompletion() {
match_suggestions(&expected_paths, &suggestions)
}
#[rstest]
fn subcommand_completions() {
#[test]
fn subcommand_vs_external_completer() {
let (_, _, mut engine, mut stack) = new_engine();
let commands = r#"
$env.config.completions.algorithm = "fuzzy"
$env.config.completions.external.completer = {|spans| ["external"]}
def foo-test-command [] {}
def "foo-test-command bar" [] {}
def "foo-test-command aagap bcr" [] {}
@ -1485,6 +1501,7 @@ fn subcommand_completions() {
let suggestions = subcommand_completer.complete(prefix, prefix.len());
match_suggestions(
&vec![
"external",
"food bar",
"foo-test-command bar",
"foo-test-command aagap bcr",
@ -1494,7 +1511,7 @@ fn subcommand_completions() {
let prefix = "foot bar";
let suggestions = subcommand_completer.complete(prefix, prefix.len());
match_suggestions(&vec!["foo-test-command bar"], &suggestions);
match_suggestions(&vec!["external", "foo-test-command bar"], &suggestions);
}
#[test]
@ -1546,9 +1563,7 @@ fn flag_completions() {
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
// Test completions for the 'ls' flags
let suggestions = completer.complete("ls -", 4);
assert_eq!(18, suggestions.len());
let expected: Vec<_> = vec![
"--all",
"--directory",
@ -1569,9 +1584,12 @@ fn flag_completions() {
"-s",
"-t",
];
// Match results
match_suggestions(&expected, &suggestions);
// https://github.com/nushell/nushell/issues/16375
let suggestions = completer.complete("table -", 7);
assert_eq!(20, suggestions.len());
}
#[test]
@ -2103,11 +2121,15 @@ fn alias_of_another_alias() {
match_suggestions(&expected_paths, &suggestions)
}
fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> {
fn run_external_completion_within_pwd(
completer: &str,
input: &str,
pwd: AbsolutePathBuf,
) -> Vec<Suggestion> {
let completer = format!("$env.config.completions.external.completer = {completer}");
// Create a new engine
let (_, _, mut engine_state, mut stack) = new_engine();
let (_, _, mut engine_state, mut stack) = new_engine_helper(pwd);
let (block, delta) = {
let mut working_set = StateWorkingSet::new(&engine_state);
let block = parse(&mut working_set, None, completer.as_bytes(), false);
@ -2119,7 +2141,8 @@ fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> {
assert!(engine_state.merge_delta(delta).is_ok());
assert!(
eval_block::<WithoutDebug>(&engine_state, &mut stack, &block, PipelineData::Empty).is_ok()
eval_block::<WithoutDebug>(&engine_state, &mut stack, &block, PipelineData::empty())
.is_ok()
);
// Merge environment into the permanent state
@ -2131,6 +2154,10 @@ fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> {
completer.complete(input, input.len())
}
fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> {
run_external_completion_within_pwd(completer, input, fs::fixtures().join("completions"))
}
#[test]
fn unknown_command_completion() {
let (_, _, engine, stack) = new_engine();
@ -2239,6 +2266,22 @@ fn extern_custom_completion_positional(mut extern_completer: NuCompleter) {
match_suggestions(&expected, &suggestions);
}
#[rstest]
fn extern_custom_completion_optional(mut extern_completer: NuCompleter) {
let suggestions = extern_completer.complete("spam foo -f bar ", 16);
let expected: Vec<_> = vec!["apple", "banana"];
match_suggestions(&expected, &suggestions);
}
#[rstest]
fn extern_custom_completion_rest(mut extern_completer: NuCompleter) {
let suggestions = extern_completer.complete("spam foo -f bar baz ", 20);
let expected: Vec<_> = vec!["cat", "dog", "eel"];
match_suggestions(&expected, &suggestions);
let suggestions = extern_completer.complete("spam foo -f bar baz qux ", 24);
match_suggestions(&expected, &suggestions);
}
#[rstest]
fn extern_custom_completion_long_flag_1(mut extern_completer: NuCompleter) {
let suggestions = extern_completer.complete("spam --foo=", 11);
@ -2267,6 +2310,17 @@ fn extern_custom_completion_short_flag(mut extern_completer: NuCompleter) {
match_suggestions(&expected, &suggestions);
}
/// When we're completing the flag name itself, not its value,
/// custom completions should not be used
#[rstest]
fn custom_completion_flag_name_not_value(mut extern_completer: NuCompleter) {
let suggestions = extern_completer.complete("spam --f", 8);
match_suggestions(&vec!["--foo"], &suggestions);
// Also test with partial short flag
let suggestions = extern_completer.complete("spam -f", 7);
match_suggestions(&vec!["-f"], &suggestions);
}
#[rstest]
fn extern_complete_flags(mut extern_completer: NuCompleter) {
let suggestions = extern_completer.complete("spam -", 6);

View File

@ -199,7 +199,7 @@ pub fn merge_input(
engine_state,
stack,
&block,
PipelineData::Value(Value::nothing(Span::unknown()), None),
PipelineData::value(Value::nothing(Span::unknown()), None),
)
.is_ok()
);

View File

@ -5,7 +5,7 @@ edition = "2024"
license = "MIT"
name = "nu-cmd-base"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
version = "0.105.2"
version = "0.106.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -13,10 +13,10 @@ version = "0.105.2"
workspace = true
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
nu-parser = { path = "../nu-parser", version = "0.105.2" }
nu-path = { path = "../nu-path", version = "0.105.2" }
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
nu-engine = { path = "../nu-engine", version = "0.106.2", default-features = false }
nu-parser = { path = "../nu-parser", version = "0.106.2" }
nu-path = { path = "../nu-path", version = "0.106.2" }
nu-protocol = { path = "../nu-protocol", version = "0.106.2", default-features = false }
indexmap = { workspace = true }
miette = { workspace = true }

View File

@ -1,11 +1,11 @@
use miette::Result;
use nu_engine::{eval_block, eval_block_with_early_return};
use nu_engine::{eval_block, eval_block_with_early_return, redirect_env};
use nu_parser::parse;
use nu_protocol::{
PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId,
cli_error::{report_parse_error, report_shell_error},
debugger::WithoutDebug,
engine::{Closure, EngineState, Stack, StateWorkingSet},
report_error::{report_parse_error, report_shell_error},
};
use std::{collections::HashMap, sync::Arc};
@ -325,19 +325,7 @@ fn run_hook(
}
// If all went fine, preserve the environment of the called block
let caller_env_vars = stack.get_env_var_names(engine_state);
redirect_env(engine_state, stack, &callee_stack);
// remove env vars that are present in the caller but not in the callee
// (the callee hid them)
for var in caller_env_vars.iter() {
if !callee_stack.has_env_var(engine_state, var) {
stack.remove_env_var(engine_state, var);
}
}
// add new env vars from callee to caller
for (var, value) in callee_stack.get_stack_env_vars() {
stack.add_env_var(var, value);
}
Ok(pipeline_data)
}

View File

@ -12,7 +12,7 @@ use nu_protocol::{
/// ```rust
/// # use nu_engine::command_prelude::*;
/// # use nu_cmd_base::WrapCall;
/// # fn do_command_logic(call: WrapCall) -> Result<PipelineData, ShellError> { Ok(PipelineData::Empty) }
/// # fn do_command_logic(call: WrapCall) -> Result<PipelineData, ShellError> { Ok(PipelineData::empty()) }
///
/// # struct Command {}
/// # impl Command {

View File

@ -5,7 +5,7 @@ edition = "2024"
license = "MIT"
name = "nu-cmd-extra"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
version = "0.105.2"
version = "0.106.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -16,13 +16,13 @@ bench = false
workspace = true
[dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.2" }
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
nu-json = { version = "0.105.2", path = "../nu-json" }
nu-parser = { path = "../nu-parser", version = "0.105.2" }
nu-pretty-hex = { version = "0.105.2", path = "../nu-pretty-hex" }
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.105.2", default-features = false }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.106.2" }
nu-engine = { path = "../nu-engine", version = "0.106.2", default-features = false }
nu-json = { version = "0.106.2", path = "../nu-json" }
nu-parser = { path = "../nu-parser", version = "0.106.2" }
nu-pretty-hex = { version = "0.106.2", path = "../nu-pretty-hex" }
nu-protocol = { path = "../nu-protocol", version = "0.106.2", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.106.2", default-features = false }
# Potential dependencies for extras
heck = { workspace = true }
@ -37,6 +37,6 @@ itertools = { workspace = true }
mime = { workspace = true }
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.105.2" }
nu-command = { path = "../nu-command", version = "0.105.2" }
nu-test-support = { path = "../nu-test-support", version = "0.105.2" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.106.2" }
nu-command = { path = "../nu-command", version = "0.106.2" }
nu-test-support = { path = "../nu-test-support", version = "0.106.2" }

View File

@ -3,7 +3,12 @@ use nu_protocol::engine::Command;
#[cfg(test)]
pub fn test_examples(cmd: impl Command + 'static) {
test_examples::test_examples(cmd);
test_examples::test_examples(cmd, &[]);
}
#[cfg(test)]
pub fn test_examples_with_commands(cmd: impl Command + 'static, commands: &[&dyn Command]) {
test_examples::test_examples(cmd, commands);
}
#[cfg(test)]
@ -21,10 +26,10 @@ mod test_examples {
};
use std::collections::HashSet;
pub fn test_examples(cmd: impl Command + 'static) {
pub fn test_examples(cmd: impl Command + 'static, commands: &[&dyn Command]) {
let examples = cmd.examples();
let signature = cmd.signature();
let mut engine_state = make_engine_state(cmd.clone_box());
let mut engine_state = make_engine_state(cmd.clone_box(), commands);
let cwd = std::env::current_dir().expect("Could not get current working directory.");
@ -38,7 +43,7 @@ mod test_examples {
check_example_input_and_output_types_match_command_signature(
&example,
&cwd,
&mut make_engine_state(cmd.clone_box()),
&mut make_engine_state(cmd.clone_box(), commands),
&signature.input_output_types,
signature.operates_on_cell_paths(),
),
@ -57,7 +62,7 @@ mod test_examples {
);
}
fn make_engine_state(cmd: Box<dyn Command>) -> Box<EngineState> {
fn make_engine_state(cmd: Box<dyn Command>, commands: &[&dyn Command]) -> Box<EngineState> {
let mut engine_state = Box::new(EngineState::new());
let delta = {
@ -69,6 +74,10 @@ mod test_examples {
working_set.add_decl(Box::new(nu_cmd_lang::If));
working_set.add_decl(Box::new(nu_command::MathRound));
for command in commands {
working_set.add_decl(command.clone_box());
}
// Adding the command that is being tested to the working set
working_set.add_decl(cmd);
working_set.render()

View File

@ -72,7 +72,7 @@ impl Command for EachWhile {
let metadata = input.metadata();
match input {
PipelineData::Empty => Ok(PipelineData::Empty),
PipelineData::Empty => Ok(PipelineData::empty()),
PipelineData::Value(Value::Range { .. }, ..)
| PipelineData::Value(Value::List { .. }, ..)
| PipelineData::ListStream(..) => {
@ -109,7 +109,7 @@ impl Command for EachWhile {
.fuse()
.into_pipeline_data(head, engine_state.signals().clone()))
} else {
Ok(PipelineData::Empty)
Ok(PipelineData::empty())
}
}
// This match allows non-iterables to be accepted,

View File

@ -12,7 +12,10 @@ impl Command for UpdateCells {
fn signature(&self) -> Signature {
Signature::build("update cells")
.input_output_types(vec![(Type::table(), Type::table())])
.input_output_types(vec![
(Type::table(), Type::table()),
(Type::record(), Type::record()),
])
.required(
"closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
@ -77,6 +80,15 @@ impl Command for UpdateCells {
"2021-11-18" => Value::test_string(""),
})])),
},
Example {
example: r#"{a: 1, b: 2, c: 3} | update cells { $in + 10 }"#,
description: "Update each value in a record.",
result: Some(Value::test_record(record! {
"a" => Value::test_int(11),
"b" => Value::test_int(12),
"c" => Value::test_int(13),
})),
},
]
}
@ -85,7 +97,7 @@ impl Command for UpdateCells {
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
mut input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let closure: Closure = call.req(engine_state, stack, 0)?;
@ -102,14 +114,51 @@ impl Command for UpdateCells {
let metadata = input.metadata();
Ok(UpdateCellIterator {
iter: input.into_iter(),
closure: ClosureEval::new(engine_state, stack, closure),
columns,
span: head,
match input {
PipelineData::Value(
Value::Record {
ref mut val,
internal_span,
},
..,
) => {
let val = val.to_mut();
update_record(
val,
&mut ClosureEval::new(engine_state, stack, closure),
internal_span,
columns.as_ref(),
);
Ok(input)
}
_ => Ok(UpdateCellIterator {
iter: input.into_iter(),
closure: ClosureEval::new(engine_state, stack, closure),
columns,
span: head,
}
.into_pipeline_data(head, engine_state.signals().clone())
.set_metadata(metadata)),
}
}
}
fn update_record(
record: &mut Record,
closure: &mut ClosureEval,
span: Span,
cols: Option<&HashSet<String>>,
) {
if let Some(columns) = cols {
for (col, val) in record.iter_mut() {
if columns.contains(col) {
*val = eval_value(closure, span, std::mem::take(val));
}
}
} else {
for (_, val) in record.iter_mut() {
*val = eval_value(closure, span, std::mem::take(val))
}
.into_pipeline_data(head, engine_state.signals().clone())
.set_metadata(metadata))
}
}
@ -128,18 +177,7 @@ impl Iterator for UpdateCellIterator {
let value = if let Value::Record { val, .. } = &mut value {
let val = val.to_mut();
if let Some(columns) = &self.columns {
for (col, val) in val.iter_mut() {
if columns.contains(col) {
*val = eval_value(&mut self.closure, self.span, std::mem::take(val));
}
}
} else {
for (_, val) in val.iter_mut() {
*val = eval_value(&mut self.closure, self.span, std::mem::take(val))
}
}
update_record(val, &mut self.closure, self.span, self.columns.as_ref());
value
} else {
eval_value(&mut self.closure, self.span, value)

View File

@ -55,7 +55,7 @@ fn from_url(input: PipelineData, head: Span) -> Result<PipelineData, ShellError>
.map(|(k, v)| (k, Value::string(v, head)))
.collect();
Ok(PipelineData::Value(Value::record(record, head), metadata))
Ok(PipelineData::value(Value::record(record, head), metadata))
}
_ => Err(ShellError::UnsupportedInput {
msg: "String not compatible with URL encoding".to_string(),

View File

@ -109,18 +109,26 @@ impl Command for ToHtml {
"produce a color table of all available themes",
Some('l'),
)
.switch("raw", "do not escape html tags", Some('r'))
.category(Category::Formats)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Outputs an HTML string representing the contents of this table",
description: "Outputs an HTML string representing the contents of this table",
example: "[[foo bar]; [1 2]] | to html",
result: Some(Value::test_string(
r#"<html><style>body { background-color:white;color:black; }</style><body><table><thead><tr><th>foo</th><th>bar</th></tr></thead><tbody><tr><td>1</td><td>2</td></tr></tbody></table></body></html>"#,
)),
},
Example {
description: "Outputs an HTML string using a record of xml data",
example: r#"{tag: a attributes: { style: "color: red" } content: ["hello!"] } | to xml | to html --raw"#,
result: Some(Value::test_string(
r#"<html><style>body { background-color:white;color:black; }</style><body><a style="color: red">hello!</a></body></html>"#,
)),
},
Example {
description: "Optionally, only output the html for the content itself",
example: "[[foo bar]; [1 2]] | to html --partial",
@ -188,7 +196,7 @@ fn get_theme_from_asset_file(
Some(t) => t,
None => {
return Err(ShellError::TypeMismatch {
err_message: format!("Unknown HTML theme '{}'", theme_name),
err_message: format!("Unknown HTML theme '{theme_name}'"),
span: theme_span,
});
}
@ -254,6 +262,7 @@ fn to_html(
let dark = call.has_flag(engine_state, stack, "dark")?;
let partial = call.has_flag(engine_state, stack, "partial")?;
let list = call.has_flag(engine_state, stack, "list")?;
let raw = call.has_flag(engine_state, stack, "raw")?;
let theme: Option<Spanned<String>> = call.get_flag(engine_state, stack, "theme")?;
let config = &stack.get_config(engine_state);
@ -319,15 +328,15 @@ fn to_html(
let inner_value = match vec_of_values.len() {
0 => String::default(),
1 => match headers {
Some(headers) => html_table(vec_of_values, headers, config),
Some(headers) => html_table(vec_of_values, headers, raw, config),
None => {
let value = &vec_of_values[0];
html_value(value.clone(), config)
html_value(value.clone(), raw, config)
}
},
_ => match headers {
Some(headers) => html_table(vec_of_values, headers, config),
None => html_list(vec_of_values, config),
Some(headers) => html_table(vec_of_values, headers, raw, config),
None => html_list(vec_of_values, raw, config),
},
};
@ -395,19 +404,19 @@ fn theme_demo(span: Span) -> PipelineData {
})
}
fn html_list(list: Vec<Value>, config: &Config) -> String {
fn html_list(list: Vec<Value>, raw: bool, config: &Config) -> String {
let mut output_string = String::new();
output_string.push_str("<ol>");
for value in list {
output_string.push_str("<li>");
output_string.push_str(&html_value(value, config));
output_string.push_str(&html_value(value, raw, config));
output_string.push_str("</li>");
}
output_string.push_str("</ol>");
output_string
}
fn html_table(table: Vec<Value>, headers: Vec<String>, config: &Config) -> String {
fn html_table(table: Vec<Value>, headers: Vec<String>, raw: bool, config: &Config) -> String {
let mut output_string = String::new();
output_string.push_str("<table>");
@ -430,7 +439,7 @@ fn html_table(table: Vec<Value>, headers: Vec<String>, config: &Config) -> Strin
.cloned()
.unwrap_or_else(|| Value::nothing(span));
output_string.push_str("<td>");
output_string.push_str(&html_value(data, config));
output_string.push_str(&html_value(data, raw, config));
output_string.push_str("</td>");
}
output_string.push_str("</tr>");
@ -441,7 +450,7 @@ fn html_table(table: Vec<Value>, headers: Vec<String>, config: &Config) -> Strin
output_string
}
fn html_value(value: Value, config: &Config) -> String {
fn html_value(value: Value, raw: bool, config: &Config) -> String {
let mut output_string = String::new();
match value {
Value::Binary { val, .. } => {
@ -450,11 +459,22 @@ fn html_value(value: Value, config: &Config) -> String {
output_string.push_str(&output);
output_string.push_str("</pre>");
}
other => output_string.push_str(
&v_htmlescape::escape(&other.to_abbreviated_string(config))
.to_string()
.replace('\n', "<br>"),
),
other => {
if raw {
output_string.push_str(
&other
.to_abbreviated_string(config)
.to_string()
.replace('\n', "<br>"),
)
} else {
output_string.push_str(
&v_htmlescape::escape(&other.to_abbreviated_string(config))
.to_string()
.replace('\n', "<br>"),
)
}
}
}
output_string
}
@ -717,9 +737,10 @@ mod tests {
#[test]
fn test_examples() {
use crate::test_examples;
use crate::test_examples_with_commands;
use nu_command::ToXml;
test_examples(ToHtml {})
test_examples_with_commands(ToHtml {}, &[&ToXml])
}
#[test]
@ -774,8 +795,7 @@ mod tests {
for key in required_keys {
assert!(
theme_map.contains_key(key),
"Expected theme to contain key '{}'",
key
"Expected theme to contain key '{key}'"
);
}
}
@ -792,15 +812,13 @@ mod tests {
if let Err(err) = result {
assert!(
matches!(err, ShellError::TypeMismatch { .. }),
"Expected TypeMismatch error, got: {:?}",
err
"Expected TypeMismatch error, got: {err:?}"
);
if let ShellError::TypeMismatch { err_message, span } = err {
assert!(
err_message.contains("doesnt-exist"),
"Error message should mention theme name, got: {}",
err_message
"Error message should mention theme name, got: {err_message}"
);
assert_eq!(span.start, 0);
assert_eq!(span.end, 13);

View File

@ -113,7 +113,7 @@ fn format_bits(
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
if let PipelineData::ByteStream(stream, metadata) = input {
Ok(PipelineData::ByteStream(
Ok(PipelineData::byte_stream(
byte_stream_to_bits(stream, head),
metadata,
))
@ -161,28 +161,28 @@ fn convert_to_smallest_number_type(num: i64, span: Span) -> Value {
let bytes = v.to_ne_bytes();
let mut raw_string = "".to_string();
for ch in bytes {
raw_string.push_str(&format!("{:08b} ", ch));
raw_string.push_str(&format!("{ch:08b} "));
}
Value::string(raw_string.trim(), span)
} else if let Some(v) = num.to_i16() {
let bytes = v.to_ne_bytes();
let mut raw_string = "".to_string();
for ch in bytes {
raw_string.push_str(&format!("{:08b} ", ch));
raw_string.push_str(&format!("{ch:08b} "));
}
Value::string(raw_string.trim(), span)
} else if let Some(v) = num.to_i32() {
let bytes = v.to_ne_bytes();
let mut raw_string = "".to_string();
for ch in bytes {
raw_string.push_str(&format!("{:08b} ", ch));
raw_string.push_str(&format!("{ch:08b} "));
}
Value::string(raw_string.trim(), span)
} else {
let bytes = num.to_ne_bytes();
let mut raw_string = "".to_string();
for ch in bytes {
raw_string.push_str(&format!("{:08b} ", ch));
raw_string.push_str(&format!("{ch:08b} "));
}
Value::string(raw_string.trim(), span)
}
@ -193,7 +193,7 @@ fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
Value::Binary { val, .. } => {
let mut raw_string = "".to_string();
for ch in val {
raw_string.push_str(&format!("{:08b} ", ch));
raw_string.push_str(&format!("{ch:08b} "));
}
Value::string(raw_string.trim(), span)
}
@ -204,7 +204,7 @@ fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
let raw_bytes = val.as_bytes();
let mut raw_string = "".to_string();
for ch in raw_bytes {
raw_string.push_str(&format!("{:08b} ", ch));
raw_string.push_str(&format!("{ch:08b} "));
}
Value::string(raw_string.trim(), span)
}

View File

@ -191,7 +191,7 @@ fn format(
// We can only handle a Record or a List of Records
match data_as_value {
Value::Record { .. } => match format_record(format_operations, &data_as_value, config) {
Ok(value) => Ok(PipelineData::Value(Value::string(value, head_span), None)),
Ok(value) => Ok(PipelineData::value(Value::string(value, head_span), None)),
Err(value) => Err(value),
},

View File

@ -16,6 +16,11 @@ impl Command for FormatNumber {
fn signature(&self) -> nu_protocol::Signature {
Signature::build("format number")
.input_output_types(vec![(Type::Number, Type::record())])
.switch(
"no-prefix",
"don't include the binary, hex or octal prefixes",
Some('n'),
)
.category(Category::Conversions)
}
@ -24,20 +29,36 @@ impl Command for FormatNumber {
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get a record containing multiple formats for the number 42",
example: "42 | format number",
result: Some(Value::test_record(record! {
"binary" => Value::test_string("0b101010"),
"debug" => Value::test_string("42"),
"display" => Value::test_string("42"),
"lowerexp" => Value::test_string("4.2e1"),
"lowerhex" => Value::test_string("0x2a"),
"octal" => Value::test_string("0o52"),
"upperexp" => Value::test_string("4.2E1"),
"upperhex" => Value::test_string("0x2A"),
})),
}]
vec![
Example {
description: "Get a record containing multiple formats for the number 42",
example: "42 | format number",
result: Some(Value::test_record(record! {
"debug" => Value::test_string("42"),
"display" => Value::test_string("42"),
"binary" => Value::test_string("0b101010"),
"lowerexp" => Value::test_string("4.2e1"),
"upperexp" => Value::test_string("4.2E1"),
"lowerhex" => Value::test_string("0x2a"),
"upperhex" => Value::test_string("0x2A"),
"octal" => Value::test_string("0o52"),
})),
},
Example {
description: "Format float without prefixes",
example: "3.14 | format number --no-prefix",
result: Some(Value::test_record(record! {
"debug" => Value::test_string("3.14"),
"display" => Value::test_string("3.14"),
"binary" => Value::test_string("100000000001001000111101011100001010001111010111000010100011111"),
"lowerexp" => Value::test_string("3.14e0"),
"upperexp" => Value::test_string("3.14E0"),
"lowerhex" => Value::test_string("40091eb851eb851f"),
"upperhex" => Value::test_string("40091EB851EB851F"),
"octal" => Value::test_string("400110753412172702437"),
})),
},
]
}
fn run(
@ -59,14 +80,24 @@ pub(crate) fn format_number(
) -> Result<PipelineData, ShellError> {
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let args = CellPathOnlyArgs::from(cell_paths);
operate(action, args, input, call.head, engine_state.signals())
if call.has_flag(engine_state, stack, "no-prefix")? {
operate(
action_no_prefix,
args,
input,
call.head,
engine_state.signals(),
)
} else {
operate(action, args, input, call.head, engine_state.signals())
}
}
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
match input {
Value::Float { val, .. } => format_f64(*val, span),
Value::Int { val, .. } => format_i64(*val, span),
Value::Filesize { val, .. } => format_i64(val.get(), span),
Value::Float { val, .. } => format_f64(*val, false, span),
Value::Int { val, .. } => format_i64(*val, false, span),
Value::Filesize { val, .. } => format_i64(val.get(), false, span),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::error(
@ -81,33 +112,80 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
}
}
fn format_i64(num: i64, span: Span) -> Value {
fn action_no_prefix(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
match input {
Value::Float { val, .. } => format_f64(*val, true, span),
Value::Int { val, .. } => format_i64(*val, true, span),
Value::Filesize { val, .. } => format_i64(val.get(), true, span),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "float, int, or filesize".into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: other.span(),
},
span,
),
}
}
fn format_i64(num: i64, no_prefix: bool, span: Span) -> Value {
Value::record(
record! {
"binary" => Value::string(format!("{num:#b}"), span),
"debug" => Value::string(format!("{num:#?}"), span),
"display" => Value::string(format!("{num}"), span),
"binary" => Value::string(
if no_prefix { format!("{num:b}") } else { format!("{num:#b}") },
span,
),
"lowerexp" => Value::string(format!("{num:#e}"), span),
"lowerhex" => Value::string(format!("{num:#x}"), span),
"octal" => Value::string(format!("{num:#o}"), span),
"upperexp" => Value::string(format!("{num:#E}"), span),
"upperhex" => Value::string(format!("{num:#X}"), span),
"lowerhex" => Value::string(
if no_prefix { format!("{num:x}") } else { format!("{num:#x}") },
span,
),
"upperhex" => Value::string(
if no_prefix { format!("{num:X}") } else { format!("{num:#X}") },
span,
),
"octal" => Value::string(
if no_prefix { format!("{num:o}") } else { format!("{num:#o}") },
span,
)
},
span,
)
}
fn format_f64(num: f64, span: Span) -> Value {
fn format_f64(num: f64, no_prefix: bool, span: Span) -> Value {
Value::record(
record! {
"binary" => Value::string(format!("{:b}", num.to_bits()), span),
"debug" => Value::string(format!("{num:#?}"), span),
"display" => Value::string(format!("{num}"), span),
"binary" => Value::string(
if no_prefix {
format!("{:b}", num.to_bits())
} else {
format!("{:#b}", num.to_bits())
},
span,
),
"lowerexp" => Value::string(format!("{num:#e}"), span),
"lowerhex" => Value::string(format!("{:0x}", num.to_bits()), span),
"octal" => Value::string(format!("{:0o}", num.to_bits()), span),
"upperexp" => Value::string(format!("{num:#E}"), span),
"upperhex" => Value::string(format!("{:0X}", num.to_bits()), span),
"lowerhex" => Value::string(
if no_prefix { format!("{:x}", num.to_bits()) } else { format!("{:#x}", num.to_bits()) },
span,
),
"upperhex" => Value::string(
if no_prefix { format!("{:X}", num.to_bits()) } else { format!("{:#X}", num.to_bits()) },
span,
),
"octal" => Value::string(
if no_prefix { format!("{:o}", num.to_bits()) } else { format!("{:#o}", num.to_bits()) },
span,
)
},
span,
)

View File

@ -4,4 +4,4 @@ pub mod extra;
pub use extra::*;
#[cfg(test)]
pub use example_test::test_examples;
pub use example_test::{test_examples, test_examples_with_commands};

View File

@ -6,7 +6,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
edition = "2024"
license = "MIT"
name = "nu-cmd-lang"
version = "0.105.2"
version = "0.106.2"
[lib]
bench = false
@ -15,17 +15,18 @@ bench = false
workspace = true
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
nu-parser = { path = "../nu-parser", version = "0.105.2" }
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.105.2", default-features = false }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.2" }
nu-engine = { path = "../nu-engine", version = "0.106.2", default-features = false }
nu-experimental = { path = "../nu-experimental", version = "0.106.2" }
nu-parser = { path = "../nu-parser", version = "0.106.2" }
nu-protocol = { path = "../nu-protocol", version = "0.106.2", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.106.2", default-features = false }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.106.2" }
itertools = { workspace = true }
shadow-rs = { version = "1.1", default-features = false }
shadow-rs = { version = "1.2", default-features = false }
[build-dependencies]
shadow-rs = { version = "1.1", default-features = false, features = ["build"] }
shadow-rs = { version = "1.2", default-features = false, features = ["build"] }
[dev-dependencies]
quickcheck = { workspace = true }
@ -43,8 +44,3 @@ plugin = [
"nu-protocol/plugin",
"os",
]
trash-support = []
sqlite = []
static-link-openssl = []
system-clipboard = []

View File

@ -296,7 +296,7 @@ fn run(
} else {
let value = stream.into_value();
let base_description = value.get_type().to_string();
Value::string(format!("{} (stream)", base_description), head)
Value::string(format!("{base_description} (stream)"), head)
}
}
PipelineData::Value(value, ..) => {

View File

@ -157,12 +157,12 @@ impl Command for Do {
if !stderr_msg.is_empty() {
child.stderr = Some(ChildPipe::Tee(Box::new(Cursor::new(stderr_msg))));
}
Ok(PipelineData::ByteStream(
Ok(PipelineData::byte_stream(
ByteStream::child(child, span),
metadata,
))
}
Err(stream) => Ok(PipelineData::ByteStream(stream, metadata)),
Err(stream) => Ok(PipelineData::byte_stream(stream, metadata)),
}
}
Ok(PipelineData::ByteStream(mut stream, metadata))
@ -176,7 +176,7 @@ impl Command for Do {
if let ByteStreamSource::Child(child) = stream.source_mut() {
child.ignore_error(true);
}
Ok(PipelineData::ByteStream(stream, metadata))
Ok(PipelineData::byte_stream(stream, metadata))
}
Ok(PipelineData::Value(Value::Error { .. }, ..)) | Err(_) if ignore_all_errors => {
Ok(PipelineData::empty())
@ -189,7 +189,7 @@ impl Command for Do {
value
}
});
Ok(PipelineData::ListStream(stream, metadata))
Ok(PipelineData::list_stream(stream, metadata))
}
r => r,
}
@ -264,7 +264,7 @@ fn bind_args_to(
.expect("internal error: all custom parameters must have var_ids");
if let Some(result) = val_iter.next() {
let param_type = param.shape.to_type();
if required && !result.is_subtype_of(&param_type) {
if !result.is_subtype_of(&param_type) {
return Err(ShellError::CantConvert {
to_type: param.shape.to_type().to_string(),
from_type: result.get_type().to_string(),

View File

@ -229,7 +229,7 @@ fn make_other_error(value: &Value, throw_span: Option<Span>) -> ShellError {
error: "invalid error format.".into(),
msg: "`$.label.start` should be smaller than `$.label.end`".into(),
span: Some(label_span),
help: Some(format!("{} > {}", span_start, span_end)),
help: Some(format!("{span_start} > {span_end}")),
inner: vec![],
};
}

View File

@ -60,11 +60,13 @@ impl Command for If {
) -> Result<PipelineData, ShellError> {
let call = call.assert_ast_call()?;
let cond = call.positional_nth(0).expect("checked through parser");
let then_block = call
.positional_nth(1)
.expect("checked through parser")
let then_expr = call.positional_nth(1).expect("checked through parser");
let then_block = then_expr
.as_block()
.expect("internal error: missing block");
.ok_or_else(|| ShellError::TypeMismatch {
err_message: "expected block".into(),
span: then_expr.span,
})?;
let else_case = call.positional_nth(2);
if eval_constant(working_set, cond)?.as_bool()? {

View File

@ -69,5 +69,5 @@ pub use return_::Return;
pub use scope::*;
pub use try_::Try;
pub use use_::Use;
pub use version::Version;
pub use version::{VERSION_NU_FEATURES, Version};
pub use while_::While;

View File

@ -96,6 +96,7 @@ impl Command for OverlayHide {
for (name, val) in env_vars_to_keep {
stack.add_env_var(name, val);
}
stack.update_config(engine_state)?;
Ok(PipelineData::empty())
}

View File

@ -158,7 +158,7 @@ impl Command for OverlayUse {
}
let eval_block = get_eval_block(engine_state);
let _ = eval_block(engine_state, &mut callee_stack, block, input);
let _ = eval_block(engine_state, &mut callee_stack, block, input)?;
// The export-env block should see the env vars *before* activating this overlay
caller_stack.add_overlay(overlay_name);
@ -178,6 +178,7 @@ impl Command for OverlayUse {
}
} else {
caller_stack.add_overlay(overlay_name);
caller_stack.update_config(engine_state)?;
}
Ok(PipelineData::empty())

View File

@ -1,11 +1,48 @@
use std::sync::OnceLock;
use std::{borrow::Cow, sync::OnceLock};
use itertools::Itertools;
use nu_engine::command_prelude::*;
use nu_protocol::engine::StateWorkingSet;
use shadow_rs::shadow;
shadow!(build);
/// Static container for the cargo features used by the `version` command.
///
/// This `OnceLock` holds the features from `nu`.
/// When you build `nu_cmd_lang`, Cargo doesn't pass along the same features that `nu` itself uses.
/// By setting this static before calling `version`, you make it show `nu`'s features instead
/// of `nu_cmd_lang`'s.
///
/// Embedders can set this to any feature list they need, but in most cases you'll probably want to
/// pass the cargo features of your host binary.
///
/// # How to get cargo features in your build script
///
/// In your binary's build script:
/// ```rust,ignore
/// // Re-export CARGO_CFG_FEATURE to the main binary.
/// // It holds all the features that cargo sets for your binary as a comma-separated list.
/// println!(
/// "cargo:rustc-env=NU_FEATURES={}",
/// std::env::var("CARGO_CFG_FEATURE").expect("set by cargo")
/// );
/// ```
///
/// Then, before you call `version`:
/// ```rust,ignore
/// // This uses static strings, but since we're using `Cow`, you can also pass owned strings.
/// let features = env!("NU_FEATURES")
/// .split(',')
/// .map(Cow::Borrowed)
/// .collect();
///
/// nu_cmd_lang::VERSION_NU_FEATURES
/// .set(features)
/// .expect("couldn't set VERSION_NU_FEATURES");
/// ```
pub static VERSION_NU_FEATURES: OnceLock<Vec<Cow<'static, str>>> = OnceLock::new();
#[derive(Clone)]
pub struct Version;
@ -113,7 +150,17 @@ pub fn version(engine_state: &EngineState, span: Span) -> Result<PipelineData, S
record.push(
"features",
Value::string(features_enabled().join(", "), span),
Value::string(
VERSION_NU_FEATURES
.get()
.as_ref()
.map(|v| v.as_slice())
.unwrap_or_default()
.iter()
.filter(|f| !f.starts_with("dep:"))
.join(", "),
span,
),
);
#[cfg(not(feature = "plugin"))]
@ -141,6 +188,17 @@ pub fn version(engine_state: &EngineState, span: Span) -> Result<PipelineData, S
);
}
record.push(
"experimental_options",
Value::string(
nu_experimental::ALL
.iter()
.map(|option| format!("{}={}", option.identifier(), option.get()))
.join(", "),
span,
),
);
Ok(Value::record(record, span).into_pipeline_data())
}
@ -164,42 +222,12 @@ fn global_allocator() -> &'static str {
"standard"
}
fn features_enabled() -> Vec<String> {
let mut names = vec!["default".to_string()];
// NOTE: There should be another way to know features on.
#[cfg(feature = "trash-support")]
{
names.push("trash".to_string());
}
#[cfg(feature = "sqlite")]
{
names.push("sqlite".to_string());
}
#[cfg(feature = "static-link-openssl")]
{
names.push("static-link-openssl".to_string());
}
#[cfg(feature = "system-clipboard")]
{
names.push("system-clipboard".to_string());
}
names.sort();
names
}
#[cfg(test)]
mod test {
#[test]
fn test_examples() {
use super::Version;
use crate::test_examples;
test_examples(Version {})
test_examples(Version)
}
}

View File

@ -221,23 +221,23 @@ impl std::fmt::Debug for DebuggableValue<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0 {
Value::Bool { val, .. } => {
write!(f, "{:?}", val)
write!(f, "{val:?}")
}
Value::Int { val, .. } => {
write!(f, "{:?}", val)
write!(f, "{val:?}")
}
Value::Float { val, .. } => {
write!(f, "{:?}f", val)
write!(f, "{val:?}f")
}
Value::Filesize { val, .. } => {
write!(f, "Filesize({:?})", val)
write!(f, "Filesize({val:?})")
}
Value::Duration { val, .. } => {
let duration = std::time::Duration::from_nanos(*val as u64);
write!(f, "Duration({:?})", duration)
write!(f, "Duration({duration:?})")
}
Value::Date { val, .. } => {
write!(f, "Date({:?})", val)
write!(f, "Date({val:?})")
}
Value::Range { val, .. } => match **val {
Range::IntRange(range) => match range.end() {
@ -280,7 +280,7 @@ impl std::fmt::Debug for DebuggableValue<'_> {
},
},
Value::String { val, .. } | Value::Glob { val, .. } => {
write!(f, "{:?}", val)
write!(f, "{val:?}")
}
Value::Record { val, .. } => {
write!(f, "{{")?;
@ -305,22 +305,22 @@ impl std::fmt::Debug for DebuggableValue<'_> {
write!(f, "]")
}
Value::Closure { val, .. } => {
write!(f, "Closure({:?})", val)
write!(f, "Closure({val:?})")
}
Value::Nothing { .. } => {
write!(f, "Nothing")
}
Value::Error { error, .. } => {
write!(f, "Error({:?})", error)
write!(f, "Error({error:?})")
}
Value::Binary { val, .. } => {
write!(f, "Binary({:?})", val)
write!(f, "Binary({val:?})")
}
Value::CellPath { val, .. } => {
write!(f, "CellPath({:?})", val.to_string())
}
Value::Custom { val, .. } => {
write!(f, "CustomValue({:?})", val)
write!(f, "CustomValue({val:?})")
}
}
}

View File

@ -5,7 +5,7 @@ edition = "2024"
license = "MIT"
name = "nu-cmd-plugin"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin"
version = "0.105.2"
version = "0.106.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -13,10 +13,10 @@ version = "0.105.2"
workspace = true
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.105.2" }
nu-path = { path = "../nu-path", version = "0.105.2" }
nu-protocol = { path = "../nu-protocol", version = "0.105.2", features = ["plugin"] }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.105.2" }
nu-engine = { path = "../nu-engine", version = "0.106.2" }
nu-path = { path = "../nu-path", version = "0.106.2" }
nu-protocol = { path = "../nu-protocol", version = "0.106.2", features = ["plugin"] }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.106.2" }
itertools = { workspace = true }

View File

@ -66,7 +66,7 @@ impl Command for PluginStop {
}
if found {
Ok(PipelineData::Empty)
Ok(PipelineData::empty())
} else {
Err(ShellError::GenericError {
error: format!("Failed to stop the `{}` plugin", name.item),

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
edition = "2024"
license = "MIT"
name = "nu-color-config"
version = "0.105.2"
version = "0.106.2"
[lib]
bench = false
@ -14,12 +14,12 @@ bench = false
workspace = true
[dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
nu-json = { path = "../nu-json", version = "0.105.2" }
nu-protocol = { path = "../nu-protocol", version = "0.106.2", default-features = false }
nu-engine = { path = "../nu-engine", version = "0.106.2", default-features = false }
nu-json = { path = "../nu-json", version = "0.106.2" }
nu-ansi-term = { workspace = true }
serde = { workspace = true, features = ["derive"] }
[dev-dependencies]
nu-test-support = { path = "../nu-test-support", version = "0.105.2" }
nu-test-support = { path = "../nu-test-support", version = "0.106.2" }

View File

@ -5,7 +5,7 @@ edition = "2024"
license = "MIT"
name = "nu-command"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
version = "0.105.2"
version = "0.106.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -16,21 +16,22 @@ bench = false
workspace = true
[dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.2" }
nu-color-config = { path = "../nu-color-config", version = "0.105.2" }
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
nu-glob = { path = "../nu-glob", version = "0.105.2" }
nu-json = { path = "../nu-json", version = "0.105.2" }
nu-parser = { path = "../nu-parser", version = "0.105.2" }
nu-path = { path = "../nu-path", version = "0.105.2" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.105.2" }
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
nu-system = { path = "../nu-system", version = "0.105.2" }
nu-table = { path = "../nu-table", version = "0.105.2" }
nu-term-grid = { path = "../nu-term-grid", version = "0.105.2" }
nu-utils = { path = "../nu-utils", version = "0.105.2", default-features = false }
nu-ansi-term = { workspace = true }
nuon = { path = "../nuon", version = "0.105.2" }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.106.2" }
nu-color-config = { path = "../nu-color-config", version = "0.106.2" }
nu-engine = { path = "../nu-engine", version = "0.106.2", default-features = false }
nu-experimental = { path = "../nu-experimental", version = "0.106.2" }
nu-glob = { path = "../nu-glob", version = "0.106.2" }
nu-json = { path = "../nu-json", version = "0.106.2" }
nu-parser = { path = "../nu-parser", version = "0.106.2" }
nu-path = { path = "../nu-path", version = "0.106.2" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.106.2" }
nu-protocol = { path = "../nu-protocol", version = "0.106.2", default-features = false }
nu-system = { path = "../nu-system", version = "0.106.2" }
nu-table = { path = "../nu-table", version = "0.106.2" }
nu-term-grid = { path = "../nu-term-grid", version = "0.106.2" }
nu-utils = { path = "../nu-utils", version = "0.106.2", default-features = false }
nuon = { path = "../nuon", version = "0.106.2" }
alphanumeric-sort = { workspace = true }
base64 = { workspace = true }
@ -53,12 +54,14 @@ devicons = { workspace = true }
dialoguer = { workspace = true, default-features = false, features = [
"fuzzy-select",
] }
fuzzy-matcher = { workspace = true }
digest = { workspace = true, default-features = false }
dtparse = { workspace = true }
encoding_rs = { workspace = true }
fancy-regex = { workspace = true }
filesize = { workspace = true }
filetime = { workspace = true }
http = {workspace = true}
human-date-parser = { workspace = true }
indexmap = { workspace = true }
indicatif = { workspace = true }
@ -91,6 +94,7 @@ rusqlite = { workspace = true, features = [
"bundled",
"backup",
"chrono",
"column_decltype",
], optional = true }
rustls = { workspace = true, optional = true, features = ["ring"] }
rustls-native-certs = { workspace = true, optional = true }
@ -190,6 +194,7 @@ os = [
"uu_uname",
"uu_whoami",
"which",
"ureq/platform-verifier"
]
# The dependencies listed below need 'getrandom'.
@ -218,7 +223,7 @@ rustls-tls = [
"dep:rustls-native-certs",
"dep:webpki-roots",
"update-informer/rustls-tls",
"ureq/tls", # ureq 3 will has the feature rustls instead
"ureq/rustls",
]
plugin = ["nu-parser/plugin", "os"]
@ -226,8 +231,8 @@ sqlite = ["rusqlite"]
trash-support = ["trash"]
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.105.2" }
nu-test-support = { path = "../nu-test-support", version = "0.105.2" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.106.2" }
nu-test-support = { path = "../nu-test-support", version = "0.106.2" }
dirs = { workspace = true }
mockito = { workspace = true, default-features = false }

View File

@ -76,7 +76,7 @@ impl Command for BytesAt {
if let PipelineData::ByteStream(stream, metadata) = input {
let stream = stream.slice(call.head, call.arguments_span(), range)?;
Ok(PipelineData::ByteStream(stream, metadata))
Ok(PipelineData::byte_stream(stream, metadata))
} else {
operate(
map_value,

View File

@ -67,7 +67,7 @@ impl Command for BytesCollect {
ByteStreamType::Binary,
);
Ok(PipelineData::ByteStream(output, metadata))
Ok(PipelineData::byte_stream(output, metadata))
}
fn examples(&self) -> Vec<Example> {

View File

@ -89,7 +89,7 @@ impl Command for Histogram {
"frequency-column-name can't be {}",
forbidden_column_names
.iter()
.map(|val| format!("'{}'", val))
.map(|val| format!("'{val}'"))
.collect::<Vec<_>>()
.join(", ")
),

View File

@ -129,7 +129,7 @@ fn into_binary(
if let PipelineData::ByteStream(stream, metadata) = input {
// Just set the type - that should be good enough
Ok(PipelineData::ByteStream(
Ok(PipelineData::byte_stream(
stream.with_type(ByteStreamType::Binary),
metadata,
))
@ -142,7 +142,7 @@ fn into_binary(
}
}
fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
let value = match input {
Value::Binary { .. } => input.clone(),
Value::Int { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span),
@ -168,7 +168,7 @@ fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
),
};
if _args.compact {
if args.compact {
let val_span = value.span();
if let Value::Binary { val, .. } = value {
let val = if cfg!(target_endian = "little") {

View File

@ -82,9 +82,6 @@ impl Command for IntoDatetime {
(Type::List(Box::new(Type::String)), Type::List(Box::new(Type::Date))),
(Type::table(), Type::table()),
(Type::Nothing, Type::table()),
// FIXME: https://github.com/nushell/nushell/issues/15485
// 'record -> any' was added as a temporary workaround to avoid type inference issues. The Any arm needs to be appear first.
(Type::record(), Type::Any),
(Type::record(), Type::record()),
(Type::record(), Type::Date),
// FIXME Type::Any input added to disable pipeline input type checking, as run-time checks can raise undesirable type errors
@ -678,7 +675,7 @@ fn parse_value_from_record_as_u32(
Value::Int { val, .. } => {
if *val < 0 || *val > u32::MAX as i64 {
return Err(ShellError::IncorrectValue {
msg: format!("incorrect value for {}", col),
msg: format!("incorrect value for {col}"),
val_span: *head,
call_span: *span,
});

View File

@ -53,9 +53,6 @@ impl Command for IntoDuration {
(Type::Float, Type::Duration),
(Type::String, Type::Duration),
(Type::Duration, Type::Duration),
// FIXME: https://github.com/nushell/nushell/issues/15485
// 'record -> any' was added as a temporary workaround to avoid type inference issues. The Any arm needs to be appear first.
(Type::record(), Type::Any),
(Type::record(), Type::record()),
(Type::record(), Type::Duration),
(Type::table(), Type::table()),
@ -368,8 +365,7 @@ fn merge_record(record: &Record, head: Span, span: Span) -> Result<Value, ShellE
if !ALLOWED_SIGNS.contains(&val.as_str()) {
let allowed_signs = ALLOWED_SIGNS.join(", ");
return Err(ShellError::IncorrectValue {
msg: format!("Invalid sign. Allowed signs are {}", allowed_signs)
.to_string(),
msg: format!("Invalid sign. Allowed signs are {allowed_signs}").to_string(),
val_span: sign.span(),
call_span: head,
});

View File

@ -170,7 +170,7 @@ fn string_helper(
// within a string stream is actually valid UTF-8. But refuse to do it if it was already set
// to binary
if stream.type_().is_string_coercible() {
Ok(PipelineData::ByteStream(
Ok(PipelineData::byte_stream(
stream.with_type(ByteStreamType::String),
metadata,
))

View File

@ -1,9 +1,12 @@
use crate::database::values::sqlite::{open_sqlite_db, values_to_sql};
use crate::{
MEMORY_DB,
database::values::sqlite::{open_sqlite_db, values_to_sql},
};
use nu_engine::command_prelude::*;
use itertools::Itertools;
use nu_protocol::Signals;
use std::path::Path;
use std::{borrow::Cow, path::Path};
pub const DEFAULT_TABLE_NAME: &str = "main";
@ -76,6 +79,17 @@ impl Command for IntoSqliteDb {
example: "{ foo: bar, baz: quux } | into sqlite filename.db",
result: None,
},
Example {
description: "Insert data that contains records, lists or tables, that will be stored as JSONB columns
These columns will be automatically turned back into nu objects when read directly via cell-path",
example: "{a_record: {foo: bar, baz: quux}, a_list: [1 2 3], a_table: [[a b]; [0 1] [2 3]]} | into sqlite filename.db -t my_table
(open filename.db).my_table.0.a_list",
result: Some(Value::test_list(vec![
Value::test_int(1),
Value::test_int(2),
Value::test_int(3)
]))
}
]
}
}
@ -89,15 +103,25 @@ impl Table {
pub fn new(
db_path: &Spanned<String>,
table_name: Option<Spanned<String>>,
engine_state: &EngineState,
stack: &Stack,
) -> Result<Self, nu_protocol::ShellError> {
let table_name = if let Some(table_name) = table_name {
table_name.item
} else {
DEFAULT_TABLE_NAME.to_string()
let table_name = table_name
.map(|table_name| table_name.item)
.unwrap_or_else(|| DEFAULT_TABLE_NAME.to_string());
let span = db_path.span;
let db_path: Cow<'_, Path> = match db_path.item.as_str() {
MEMORY_DB => Cow::Borrowed(Path::new(&db_path.item)),
item => engine_state
.cwd(Some(stack))?
.join(item)
.to_std_path_buf()
.into(),
};
// create the sqlite database table
let conn = open_sqlite_db(Path::new(&db_path.item), db_path.span)?;
let conn = open_sqlite_db(&db_path, span)?;
Ok(Self { conn, table_name })
}
@ -122,8 +146,8 @@ impl Table {
.conn
.query_row(&table_exists_query, [], |row| row.get(0))
.map_err(|err| ShellError::GenericError {
error: format!("{:#?}", err),
msg: format!("{:#?}", err),
error: format!("{err:#?}"),
msg: format!("{err:#?}"),
span: None,
help: None,
inner: Vec::new(),
@ -182,11 +206,12 @@ fn operate(
let span = call.head;
let file_name: Spanned<String> = call.req(engine_state, stack, 0)?;
let table_name: Option<Spanned<String>> = call.get_flag(engine_state, stack, "table-name")?;
let table = Table::new(&file_name, table_name)?;
Ok(action(input, table, span, engine_state.signals())?.into_pipeline_data())
let table = Table::new(&file_name, table_name, engine_state, stack)?;
Ok(action(engine_state, input, table, span, engine_state.signals())?.into_pipeline_data())
}
fn action(
engine_state: &EngineState,
input: PipelineData,
table: Table,
span: Span,
@ -194,17 +219,17 @@ fn action(
) -> Result<Value, ShellError> {
match input {
PipelineData::ListStream(stream, _) => {
insert_in_transaction(stream.into_iter(), span, table, signals)
insert_in_transaction(engine_state, stream.into_iter(), span, table, signals)
}
PipelineData::Value(value @ Value::List { .. }, _) => {
let span = value.span();
let vals = value
.into_list()
.expect("Value matched as list above, but is not a list");
insert_in_transaction(vals.into_iter(), span, table, signals)
insert_in_transaction(engine_state, vals.into_iter(), span, table, signals)
}
PipelineData::Value(val, _) => {
insert_in_transaction(std::iter::once(val), span, table, signals)
insert_in_transaction(engine_state, std::iter::once(val), span, table, signals)
}
_ => Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "list".into(),
@ -216,6 +241,7 @@ fn action(
}
fn insert_in_transaction(
engine_state: &EngineState,
stream: impl Iterator<Item = Value>,
span: Span,
mut table: Table,
@ -241,7 +267,7 @@ fn insert_in_transaction(
let tx = table.try_init(&first_val)?;
for stream_value in stream {
if let Err(err) = signals.check(span) {
if let Err(err) = signals.check(&span) {
tx.rollback().map_err(|e| ShellError::GenericError {
error: "Failed to rollback SQLite transaction".into(),
msg: e.to_string(),
@ -257,7 +283,7 @@ fn insert_in_transaction(
let insert_statement = format!(
"INSERT INTO [{}] ({}) VALUES ({})",
table_name,
Itertools::intersperse(val.columns().map(|c| format!("`{}`", c)), ", ".to_string())
Itertools::intersperse(val.columns().map(|c| format!("`{c}`")), ", ".to_string())
.collect::<String>(),
Itertools::intersperse(itertools::repeat_n("?", val.len()), ", ").collect::<String>(),
);
@ -272,7 +298,7 @@ fn insert_in_transaction(
inner: Vec::new(),
})?;
let result = insert_value(stream_value, &mut insert_statement);
let result = insert_value(engine_state, stream_value, span, &mut insert_statement);
insert_statement
.finalize()
@ -299,13 +325,15 @@ fn insert_in_transaction(
}
fn insert_value(
engine_state: &EngineState,
stream_value: Value,
call_span: Span,
insert_statement: &mut rusqlite::Statement<'_>,
) -> Result<(), ShellError> {
match stream_value {
// map each column value into its SQL representation
Value::Record { val, .. } => {
let sql_vals = values_to_sql(val.values().cloned())?;
let sql_vals = values_to_sql(engine_state, val.values().cloned(), call_span)?;
insert_statement
.execute(rusqlite::params_from_iter(sql_vals))
@ -345,6 +373,7 @@ fn nu_value_to_sqlite_type(val: &Value) -> Result<&'static str, ShellError> {
Type::Date => Ok("DATETIME"),
Type::Duration => Ok("BIGINT"),
Type::Filesize => Ok("INTEGER"),
Type::List(_) | Type::Record(_) | Type::Table(_) => Ok("JSONB"),
// [NOTE] On null values, we just assume TEXT. This could end up
// creating a table where the column type is wrong in the table schema.
@ -353,15 +382,13 @@ fn nu_value_to_sqlite_type(val: &Value) -> Result<&'static str, ShellError> {
// intentionally enumerated so that any future types get handled
Type::Any
| Type::Block
| Type::CellPath
| Type::Closure
| Type::Custom(_)
| Type::Error
| Type::List(_)
| Type::Range
| Type::Record(_)
| Type::Glob
| Type::Table(_) => Err(ShellError::OnlySupportsThisInputType {
| Type::Glob => Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "sql".into(),
wrong_type: val.get_type().to_string(),
dst_span: Span::unknown(),
@ -381,23 +408,9 @@ fn get_columns_with_sqlite_types(
.map(|name| (format!("`{}`", name.0), name.1))
.any(|(name, _)| name == *c)
{
columns.push((format!("`{}`", c), nu_value_to_sqlite_type(v)?));
columns.push((format!("`{c}`"), nu_value_to_sqlite_type(v)?));
}
}
Ok(columns)
}
#[cfg(test)]
mod tests {
use super::*;
// use super::{action, IntoSqliteDb};
// use nu_protocol::Type::Error;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(IntoSqliteDb {})
}
}

View File

@ -41,7 +41,7 @@ impl Command for QueryDb {
Example {
description: "Execute a SQL statement with parameters",
example: r#"stor create -t my_table -c { first: str, second: int }
stor open | query db "INSERT INTO my_table VALUES (?, ?)" -p [hello 123]"#,
stor open | query db "INSERT INTO my_table VALUES (?, ?)" -p [hello 123]"#,
result: None,
},
Example {
@ -54,6 +54,36 @@ stor open | query db "SELECT * FROM my_table WHERE second = :search_second" -p {
"second" => Value::test_int(123)
})])),
},
Example {
description: "Execute a SQL query, selecting a declared JSON(B) column that will automatically be parsed",
example: r#"stor create -t my_table -c {data: jsonb}
[{data: {name: Albert, age: 40}} {data: {name: Barnaby, age: 54}}] | stor insert -t my_table
stor open | query db "SELECT data FROM my_table WHERE data->>'age' < 45""#,
result: Some(Value::test_list(vec![Value::test_record(record! {
"data" => Value::test_record(
record! {
"name" => Value::test_string("Albert"),
"age" => Value::test_int(40),
}
)})])),
},
Example {
description: "Execute a SQL query selecting a sub-field of a JSON(B) column.
In this case, results must be parsed afterwards because SQLite does not
return declaration types when a JSON(B) column is not directly selected",
example: r#"stor create -t my_table -c {data: jsonb}
stor insert -t my_table -d {data: {foo: foo, bar: 12, baz: [0 1 2]}}
stor open | query db "SELECT data->'baz' AS baz FROM my_table" | update baz {from json}"#,
result: Some(Value::test_list(vec![Value::test_record(
record! { "baz" =>
Value::test_list(vec![
Value::test_int(0),
Value::test_int(1),
Value::test_int(2),
])
},
)])),
},
]
}
@ -73,7 +103,7 @@ stor open | query db "SELECT * FROM my_table WHERE second = :search_second" -p {
.get_flag(engine_state, stack, "params")?
.unwrap_or_else(|| Value::nothing(Span::unknown()));
let params = nu_value_to_params(params_value)?;
let params = nu_value_to_params(engine_state, params_value, call.head)?;
let db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
db.query(&sql, params, call.head)

View File

@ -79,7 +79,7 @@ impl Command for SchemaDb {
// TODO: add views and triggers
Ok(PipelineData::Value(Value::record(record, span), None))
Ok(PipelineData::value(Value::record(record, span), None))
}
}

View File

@ -4,7 +4,7 @@ use super::definitions::{
};
use nu_protocol::{
CustomValue, PipelineData, Record, ShellError, Signals, Span, Spanned, Value,
shell_error::io::IoError,
engine::EngineState, shell_error::io::IoError,
};
use rusqlite::{
Connection, DatabaseName, Error as SqliteError, OpenFlags, Row, Statement, ToSql,
@ -112,16 +112,31 @@ impl SQLiteDatabase {
if self.path == PathBuf::from(MEMORY_DB) {
open_connection_in_memory_custom()
} else {
Connection::open(&self.path).map_err(|e| ShellError::GenericError {
let conn = Connection::open(&self.path).map_err(|e| ShellError::GenericError {
error: "Failed to open SQLite database from open_connection".into(),
msg: e.to_string(),
span: None,
help: None,
inner: vec![],
})
})?;
conn.busy_handler(Some(SQLiteDatabase::sleeper))
.map_err(|e| ShellError::GenericError {
error: "Failed to set busy handler for SQLite database".into(),
msg: e.to_string(),
span: None,
help: None,
inner: vec![],
})?;
Ok(conn)
}
}
fn sleeper(attempts: i32) -> bool {
log::warn!("SQLITE_BUSY, retrying after 250ms (attempt {})", attempts);
std::thread::sleep(std::time::Duration::from_millis(250));
true
}
pub fn get_tables(&self, conn: &Connection) -> Result<Vec<DbTable>, SqliteError> {
let mut table_names =
conn.prepare("SELECT name FROM sqlite_master WHERE type = 'table'")?;
@ -158,7 +173,7 @@ impl SQLiteDatabase {
filename: String,
) -> Result<(), SqliteError> {
//vacuum main into 'c:\\temp\\foo.db'
conn.execute(&format!("vacuum main into '{}'", filename), [])?;
conn.execute(&format!("vacuum main into '{filename}'"), [])?;
Ok(())
}
@ -416,35 +431,44 @@ fn run_sql_query(
}
// This is taken from to text local_into_string but tweaks it a bit so that certain formatting does not happen
pub fn value_to_sql(value: Value) -> Result<Box<dyn rusqlite::ToSql>, ShellError> {
Ok(match value {
Value::Bool { val, .. } => Box::new(val),
Value::Int { val, .. } => Box::new(val),
Value::Float { val, .. } => Box::new(val),
Value::Filesize { val, .. } => Box::new(val.get()),
Value::Duration { val, .. } => Box::new(val),
Value::Date { val, .. } => Box::new(val),
Value::String { val, .. } => Box::new(val),
Value::Binary { val, .. } => Box::new(val),
Value::Nothing { .. } => Box::new(rusqlite::types::Null),
pub fn value_to_sql(
engine_state: &EngineState,
value: Value,
call_span: Span,
) -> Result<Box<dyn rusqlite::ToSql>, ShellError> {
match value {
Value::Bool { val, .. } => Ok(Box::new(val)),
Value::Int { val, .. } => Ok(Box::new(val)),
Value::Float { val, .. } => Ok(Box::new(val)),
Value::Filesize { val, .. } => Ok(Box::new(val.get())),
Value::Duration { val, .. } => Ok(Box::new(val)),
Value::Date { val, .. } => Ok(Box::new(val)),
Value::String { val, .. } => Ok(Box::new(val)),
Value::Binary { val, .. } => Ok(Box::new(val)),
Value::Nothing { .. } => Ok(Box::new(rusqlite::types::Null)),
val => {
return Err(ShellError::OnlySupportsThisInputType {
exp_input_type:
"bool, int, float, filesize, duration, date, string, nothing, binary".into(),
wrong_type: val.get_type().to_string(),
dst_span: Span::unknown(),
src_span: val.span(),
});
let json_value = crate::value_to_json_value(engine_state, &val, call_span, false)?;
match nu_json::to_string_raw(&json_value) {
Ok(s) => Ok(Box::new(s)),
Err(err) => Err(ShellError::CantConvert {
to_type: "JSON".into(),
from_type: val.get_type().to_string(),
span: val.span(),
help: Some(err.to_string()),
}),
}
}
})
}
}
pub fn values_to_sql(
engine_state: &EngineState,
values: impl IntoIterator<Item = Value>,
call_span: Span,
) -> Result<Vec<Box<dyn rusqlite::ToSql>>, ShellError> {
values
.into_iter()
.map(value_to_sql)
.map(|v| value_to_sql(engine_state, v, call_span))
.collect::<Result<Vec<_>, _>>()
}
@ -459,13 +483,17 @@ impl Default for NuSqlParams {
}
}
pub fn nu_value_to_params(value: Value) -> Result<NuSqlParams, ShellError> {
pub fn nu_value_to_params(
engine_state: &EngineState,
value: Value,
call_span: Span,
) -> Result<NuSqlParams, ShellError> {
match value {
Value::Record { val, .. } => {
let mut params = Vec::with_capacity(val.len());
for (mut column, value) in val.into_owned().into_iter() {
let sql_type_erased = value_to_sql(value)?;
let sql_type_erased = value_to_sql(engine_state, value, call_span)?;
if !column.starts_with([':', '@', '$']) {
column.insert(0, ':');
@ -480,7 +508,7 @@ pub fn nu_value_to_params(value: Value) -> Result<NuSqlParams, ShellError> {
let mut params = Vec::with_capacity(vals.len());
for value in vals.into_iter() {
let sql_type_erased = value_to_sql(value)?;
let sql_type_erased = value_to_sql(engine_state, value, call_span)?;
params.push(sql_type_erased);
}
@ -542,17 +570,49 @@ fn read_single_table(
prepared_statement_to_nu_list(stmt, NuSqlParams::default(), call_span, signals)
}
/// The SQLite type behind a query column returned as some raw type (e.g. 'text')
#[derive(Clone, Copy)]
pub enum DeclType {
Json,
Jsonb,
}
impl DeclType {
pub fn from_str(s: &str) -> Option<Self> {
match s.to_uppercase().as_str() {
"JSON" => Some(DeclType::Json),
"JSONB" => Some(DeclType::Jsonb),
_ => None, // We are only special-casing JSON(B) columns for now
}
}
}
/// A column out of an SQLite query, together with its type
pub struct TypedColumn {
pub name: String,
pub decl_type: Option<DeclType>,
}
impl TypedColumn {
pub fn from_rusqlite_column(c: &rusqlite::Column) -> Self {
Self {
name: c.name().to_owned(),
decl_type: c.decl_type().and_then(DeclType::from_str),
}
}
}
fn prepared_statement_to_nu_list(
mut stmt: Statement,
params: NuSqlParams,
call_span: Span,
signals: &Signals,
) -> Result<Value, SqliteOrShellError> {
let column_names = stmt
.column_names()
.into_iter()
.map(String::from)
.collect::<Vec<String>>();
let columns: Vec<TypedColumn> = stmt
.columns()
.iter()
.map(TypedColumn::from_rusqlite_column)
.collect();
// I'm very sorry for this repetition
// I tried scoping the match arms to the query_map alone, but lifetime and closure reference escapes
@ -562,18 +622,14 @@ fn prepared_statement_to_nu_list(
let refs: Vec<&dyn ToSql> = params.iter().map(|value| (&**value)).collect();
let row_results = stmt.query_map(refs.as_slice(), |row| {
Ok(convert_sqlite_row_to_nu_value(
row,
call_span,
&column_names,
))
Ok(convert_sqlite_row_to_nu_value(row, call_span, &columns))
})?;
// we collect all rows before returning them. Not ideal but it's hard/impossible to return a stream from a CustomValue
let mut row_values = vec![];
for row_result in row_results {
signals.check(call_span)?;
signals.check(&call_span)?;
if let Ok(row_value) = row_result {
row_values.push(row_value);
}
@ -588,18 +644,14 @@ fn prepared_statement_to_nu_list(
.collect();
let row_results = stmt.query_map(refs.as_slice(), |row| {
Ok(convert_sqlite_row_to_nu_value(
row,
call_span,
&column_names,
))
Ok(convert_sqlite_row_to_nu_value(row, call_span, &columns))
})?;
// we collect all rows before returning them. Not ideal but it's hard/impossible to return a stream from a CustomValue
let mut row_values = vec![];
for row_result in row_results {
signals.check(call_span)?;
signals.check(&call_span)?;
if let Ok(row_value) = row_result {
row_values.push(row_value);
}
@ -635,14 +687,14 @@ fn read_entire_sqlite_db(
Ok(Value::record(tables, call_span))
}
pub fn convert_sqlite_row_to_nu_value(row: &Row, span: Span, column_names: &[String]) -> Value {
let record = column_names
pub fn convert_sqlite_row_to_nu_value(row: &Row, span: Span, columns: &[TypedColumn]) -> Value {
let record = columns
.iter()
.enumerate()
.map(|(i, col)| {
(
col.clone(),
convert_sqlite_value_to_nu_value(row.get_ref_unwrap(i), span),
col.name.clone(),
convert_sqlite_value_to_nu_value(row.get_ref_unwrap(i), col.decl_type, span),
)
})
.collect();
@ -650,31 +702,48 @@ pub fn convert_sqlite_row_to_nu_value(row: &Row, span: Span, column_names: &[Str
Value::record(record, span)
}
pub fn convert_sqlite_value_to_nu_value(value: ValueRef, span: Span) -> Value {
pub fn convert_sqlite_value_to_nu_value(
value: ValueRef,
decl_type: Option<DeclType>,
span: Span,
) -> Value {
match value {
ValueRef::Null => Value::nothing(span),
ValueRef::Integer(i) => Value::int(i, span),
ValueRef::Real(f) => Value::float(f, span),
ValueRef::Text(buf) => {
let s = match std::str::from_utf8(buf) {
Ok(v) => v,
Err(_) => return Value::error(ShellError::NonUtf8 { span }, span),
};
Value::string(s.to_string(), span)
}
ValueRef::Text(buf) => match (std::str::from_utf8(buf), decl_type) {
(Ok(txt), Some(DeclType::Json | DeclType::Jsonb)) => {
match crate::convert_json_string_to_value(txt, span) {
Ok(val) => val,
Err(err) => Value::error(err, span),
}
}
(Ok(txt), _) => Value::string(txt.to_string(), span),
(Err(_), _) => Value::error(ShellError::NonUtf8 { span }, span),
},
ValueRef::Blob(u) => Value::binary(u.to_vec(), span),
}
}
pub fn open_connection_in_memory_custom() -> Result<Connection, ShellError> {
let flags = OpenFlags::default();
Connection::open_with_flags(MEMORY_DB, flags).map_err(|e| ShellError::GenericError {
error: "Failed to open SQLite custom connection in memory".into(),
msg: e.to_string(),
span: Some(Span::test_data()),
help: None,
inner: vec![],
})
let conn =
Connection::open_with_flags(MEMORY_DB, flags).map_err(|e| ShellError::GenericError {
error: "Failed to open SQLite custom connection in memory".into(),
msg: e.to_string(),
span: Some(Span::test_data()),
help: None,
inner: vec![],
})?;
conn.busy_handler(Some(SQLiteDatabase::sleeper))
.map_err(|e| ShellError::GenericError {
error: "Failed to set busy handler for SQLite custom connection in memory".into(),
msg: e.to_string(),
span: Some(Span::test_data()),
help: None,
inner: vec![],
})?;
Ok(conn)
}
pub fn open_connection_in_memory() -> Result<Connection, ShellError> {

View File

@ -42,7 +42,7 @@ impl Command for Debug {
let raw = call.has_flag(engine_state, stack, "raw")?;
let raw_value = call.has_flag(engine_state, stack, "raw-value")?;
// Should PipelineData::Empty result in an error here?
// Should PipelineData::empty() result in an error here?
input.map(
move |x| {

View File

@ -25,7 +25,7 @@ impl Command for DebugEnv {
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
Ok(PipelineData::Value(
Ok(PipelineData::value(
env_to_strings(engine_state, stack)?.into_value(call.head),
None,
))

View File

@ -0,0 +1,64 @@
use nu_engine::command_prelude::*;
use nu_experimental::Status;
#[derive(Clone)]
pub struct DebugExperimentalOptions;
impl Command for DebugExperimentalOptions {
fn name(&self) -> &str {
"debug experimental-options"
}
fn signature(&self) -> Signature {
Signature::new(self.name())
.input_output_type(
Type::Nothing,
Type::Table(Box::from([
(String::from("identifier"), Type::String),
(String::from("enabled"), Type::Bool),
(String::from("status"), Type::String),
(String::from("description"), Type::String),
])),
)
.add_help()
.category(Category::Debug)
}
fn description(&self) -> &str {
"Show all experimental options."
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
Ok(PipelineData::value(
Value::list(
nu_experimental::ALL
.iter()
.map(|option| {
Value::record(
nu_protocol::record! {
"identifier" => Value::string(option.identifier(), call.head),
"enabled" => Value::bool(option.get(), call.head),
"status" => Value::string(match option.status() {
Status::OptIn => "opt-in",
Status::OptOut => "opt-out",
Status::DeprecatedDiscard => "deprecated-discard",
Status::DeprecatedDefault => "deprecated-default"
}, call.head),
"description" => Value::string(option.description(), call.head),
},
call.head,
)
})
.collect(),
call.head,
),
None,
))
}
}

View File

@ -43,6 +43,17 @@ impl Command for Metadata {
let arg = call.positional_nth(stack, 0);
let head = call.head;
if !matches!(input, PipelineData::Empty) {
if let Some(arg_expr) = arg {
return Err(ShellError::IncompatibleParameters {
left_message: "pipeline input was provided".into(),
left_span: head,
right_message: "but a positional metadata expression was also given".into(),
right_span: arg_expr.span,
});
}
}
match arg {
Some(Expression {
expr: Expr::FullCellPath(full_cell_path),
@ -56,7 +67,6 @@ impl Command for Metadata {
..
} => {
let origin = stack.get_var_with_origin(*var_id, *span)?;
Ok(build_metadata_record_value(
&origin,
input.metadata().as_ref(),
@ -87,10 +97,9 @@ impl Command for Metadata {
.into_pipeline_data(),
)
}
None => Ok(
Value::record(build_metadata_record(input.metadata().as_ref(), head), head)
.into_pipeline_data(),
),
None => {
Ok(Value::record(build_metadata_record(&input, head), head).into_pipeline_data())
}
}
}
@ -116,19 +125,7 @@ fn build_metadata_record_value(
head: Span,
) -> Value {
let mut record = Record::new();
let span = arg.span();
record.push(
"span",
Value::record(
record! {
"start" => Value::int(span.start as i64,span),
"end" => Value::int(span.end as i64, span),
},
head,
),
);
record.push("span", arg.span().into_value(head));
Value::record(extend_record_with_metadata(record, metadata, head), head)
}

View File

@ -42,10 +42,7 @@ impl Command for MetadataAccess {
// `ClosureEvalOnce` is not used as it uses `Stack::captures_to_stack` rather than
// `Stack::captures_to_stack_preserve_out_dest`. This command shouldn't collect streams
let mut callee_stack = caller_stack.captures_to_stack_preserve_out_dest(closure.captures);
let metadata_record = Value::record(
build_metadata_record(input.metadata().as_ref(), call.head),
call.head,
);
let metadata_record = Value::record(build_metadata_record(&input, call.head), call.head);
if let Some(var_id) = block.signature.get_positional(0).and_then(|var| var.var_id) {
callee_stack.add_var(var_id, metadata_record)
@ -58,12 +55,10 @@ impl Command for MetadataAccess {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Access metadata and data from a stream together",
example: r#"{foo: bar} | to json --raw | metadata access {|meta| {in: $in, meta: $meta}}"#,
example: r#"{foo: bar} | to json --raw | metadata access {|meta| {in: $in, content: $meta.content_type}}"#,
result: Some(Value::test_record(record! {
"in" => Value::test_string(r#"{"foo":"bar"}"#),
"meta" => Value::test_record(record! {
"content_type" => Value::test_string(r#"application/json"#)
})
"content" => Value::test_string(r#"application/json"#)
})),
}]
}

View File

@ -63,7 +63,18 @@ impl Command for MetadataSet {
match (ds_fp, ds_ls) {
(Some(path), false) => metadata.data_source = DataSource::FilePath(path.into()),
(None, true) => metadata.data_source = DataSource::Ls,
(Some(_), true) => (), // TODO: error here
(Some(_), true) => {
return Err(ShellError::IncompatibleParameters {
left_message: "cannot use `--datasource-filepath`".into(),
left_span: call
.get_flag_span(stack, "datasource-filepath")
.expect("has flag"),
right_message: "with `--datasource-ls`".into(),
right_span: call
.get_flag_span(stack, "datasource-ls")
.expect("has flag"),
});
}
(None, false) => (),
}
@ -79,15 +90,13 @@ impl Command for MetadataSet {
},
Example {
description: "Set the metadata of a file path",
example: "'crates' | metadata set --datasource-filepath $'(pwd)/crates' | metadata",
example: "'crates' | metadata set --datasource-filepath $'(pwd)/crates'",
result: None,
},
Example {
description: "Set the metadata of a file path",
example: "'crates' | metadata set --content-type text/plain | metadata",
result: Some(Value::test_record(record! {
"content_type" => Value::test_string("text/plain"),
})),
example: "'crates' | metadata set --content-type text/plain | metadata | get content_type",
result: Some(Value::test_string("text/plain")),
},
]
}

View File

@ -1,6 +1,7 @@
mod ast;
mod debug_;
mod env;
mod experimental_options;
mod explain;
mod info;
mod inspect;
@ -21,6 +22,7 @@ mod view_span;
pub use ast::Ast;
pub use debug_::Debug;
pub use env::DebugEnv;
pub use experimental_options::DebugExperimentalOptions;
pub use explain::Explain;
pub use info::DebugInfo;
pub use inspect::Inspect;

View File

@ -1,4 +1,4 @@
use nu_protocol::{DataSource, PipelineMetadata, Record, Span, Value};
use nu_protocol::{DataSource, IntoValue, PipelineData, PipelineMetadata, Record, Span, Value};
pub fn extend_record_with_metadata(
mut record: Record,
@ -29,6 +29,10 @@ pub fn extend_record_with_metadata(
record
}
pub fn build_metadata_record(metadata: Option<&PipelineMetadata>, head: Span) -> Record {
extend_record_with_metadata(Record::new(), metadata, head)
pub fn build_metadata_record(pipeline: &PipelineData, head: Span) -> Record {
let mut record = Record::new();
if let Some(span) = pipeline.span() {
record.insert("span", span.into_value(head));
}
extend_record_with_metadata(record, pipeline.metadata().as_ref(), head)
}

View File

@ -126,10 +126,10 @@ impl Command for ViewSource {
}
let _ = write!(&mut final_contents, "--{}", n.long);
if let Some(short) = n.short {
let _ = write!(&mut final_contents, "(-{})", short);
let _ = write!(&mut final_contents, "(-{short})");
}
if let Some(arg) = &n.arg {
let _ = write!(&mut final_contents, ": {}", arg);
let _ = write!(&mut final_contents, ": {arg}");
}
final_contents.push(' ');
}
@ -146,7 +146,7 @@ impl Command for ViewSource {
let mut c = 0;
for (insig, outsig) in type_signatures {
c += 1;
let s = format!("{} -> {}", insig, outsig);
let s = format!("{insig} -> {outsig}");
final_contents.push_str(&s);
if c != len {
final_contents.push_str(", ")

View File

@ -153,6 +153,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
Ast,
Debug,
DebugEnv,
DebugExperimentalOptions,
DebugInfo,
DebugProfile,
Explain,
@ -188,6 +189,9 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
// Strings
bind_command! {
Ansi,
AnsiLink,
AnsiStrip,
Char,
Decode,
Encode,
@ -250,9 +254,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
// Platform
#[cfg(feature = "os")]
bind_command! {
Ansi,
AnsiLink,
AnsiStrip,
Clear,
Du,
Input,

View File

@ -124,7 +124,7 @@ pub(super) fn start_editor(
let post_wait_callback = PostWaitCallback::for_job_control(engine_state, None, None);
// Wrap the output into a `PipelineData::ByteStream`.
// Wrap the output into a `PipelineData::byte_stream`.
let child = nu_protocol::process::ChildProcess::new(
child,
None,
@ -133,7 +133,7 @@ pub(super) fn start_editor(
Some(post_wait_callback),
)?;
Ok(PipelineData::ByteStream(
Ok(PipelineData::byte_stream(
ByteStream::child(child, call.head),
None,
))

View File

@ -33,7 +33,7 @@ impl Command for ConfigUseColors {
.get_config()
.use_ansi_coloring
.get(engine_state);
Ok(PipelineData::Value(
Ok(PipelineData::value(
Value::bool(use_ansi_coloring, call.head),
None,
))

View File

@ -32,7 +32,7 @@ Messages may have numeric flags attached to them. This commands supports filteri
If no tag is specified, this command will accept any message.
If no message with the specified tag (if any) is available in the mailbox, this command will block the current thread until one arrives.
By default this command block indefinitely until a matching message arrives, but a timeout duration can be specified.
By default this command block indefinitely until a matching message arrives, but a timeout duration can be specified.
If a timeout duration of zero is specified, it will succeed only if there already is a message in the mailbox.
Note: When using par-each, only one thread at a time can utilize this command.
@ -78,9 +78,7 @@ in no particular order, regardless of the specified timeout parameter.
let tag = tag_arg.map(|it| it.item as FilterTag);
let duration: Option<i64> = call.get_flag(engine_state, stack, "timeout")?;
let timeout = duration.map(|it| Duration::from_nanos(it as u64));
let timeout: Option<Duration> = call.get_flag(engine_state, stack, "timeout")?;
let mut mailbox = engine_state
.current_job
@ -116,6 +114,11 @@ in no particular order, regardless of the specified timeout parameter.
description: "Get a message or fail if no message is available immediately",
result: None,
},
Example {
example: "job spawn { sleep 1sec; 'hi' | job send 0 }; job recv",
description: "Receive a message from a newly-spawned job",
result: None,
},
]
}
}

View File

@ -17,7 +17,7 @@ impl Command for JobSend {
r#"
This command sends a message to a background job, which can then read sent messages
in a first-in-first-out fashion with `job recv`. When it does so, it may additionally specify a numeric filter tag,
in which case it will only read messages sent with the exact same filter tag.
in which case it will only read messages sent with the exact same filter tag.
In particular, the id 0 refers to the main/initial nushell thread.
A message can be any nushell value, and streams are always collected before being sent.
@ -101,10 +101,17 @@ This command never blocks.
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "let id = job spawn { job recv | save sent.txt }; 'hi' | job send $id",
description: "Send a message to a newly spawned job",
result: None,
}]
vec![
Example {
example: "let id = job spawn { job recv | save sent.txt }; 'hi' | job send $id",
description: "Send a message from the main thread to a newly-spawned job",
result: None,
},
Example {
example: "job spawn { sleep 1sec; 'hi' | job send 0 }; job recv",
description: "Send a message from a newly-spawned job to the main thread (which always has an ID of 0)",
result: None,
},
]
}
}

View File

@ -85,7 +85,7 @@ impl Command for Glob {
result: None,
},
Example {
description: "Search for files for folders that do not begin with c, C, b, M, or s",
description: "Search for files or folders that do not begin with c, C, b, M, or s",
example: r#"glob "[!cCbMs]*""#,
result: None,
},
@ -329,7 +329,7 @@ fn glob_to_value(
) -> ListStream {
let map_signals = signals.clone();
let result = glob_results.filter_map(move |entry| {
if let Err(err) = map_signals.check(span) {
if let Err(err) = map_signals.check(&span) {
return Some(Value::error(err, span));
};
let file_type = entry.file_type();

View File

@ -341,7 +341,7 @@ fn ls_for_one_pattern(
let mut paths_peek = paths.peekable();
let no_matches = paths_peek.peek().is_none();
signals.check(call_span)?;
signals.check(&call_span)?;
if no_matches {
return Err(ShellError::GenericError {
error: format!("No matches found for {:?}", path.item),
@ -979,14 +979,14 @@ fn read_dir(
.read_dir()
.map_err(|err| IoError::new(err, span, f.clone()))?
.map(move |d| {
signals_clone.check(span)?;
signals_clone.check(&span)?;
d.map(|r| r.path())
.map_err(|err| IoError::new(err, span, f.clone()))
.map_err(ShellError::from)
});
if !use_threads {
let mut collected = items.collect::<Vec<_>>();
signals.check(span)?;
signals.check(&span)?;
collected.sort_by(|a, b| match (a, b) {
(Ok(a), Ok(b)) => a.cmp(b),
(Ok(_), Err(_)) => Ordering::Greater,

View File

@ -112,14 +112,14 @@ impl Command for Mktemp {
.map_err(|_| ShellError::NonUtf8 { span })?,
Err(e) => {
return Err(ShellError::GenericError {
error: format!("{}", e),
msg: format!("{}", e),
error: format!("{e}"),
msg: format!("{e}"),
span: None,
help: None,
inner: vec![],
});
}
};
Ok(PipelineData::Value(Value::string(res, span), None))
Ok(PipelineData::value(Value::string(res, span), None))
}
}

View File

@ -176,7 +176,7 @@ impl Command for Open {
.map_err(|err| IoError::new(err, arg_span, PathBuf::from(path)))?;
// No content_type by default - Is added later if no converter is found
let stream = PipelineData::ByteStream(
let stream = PipelineData::byte_stream(
ByteStream::file(file, call_span, engine_state.signals().clone()),
Some(PipelineMetadata {
data_source: DataSource::FilePath(path.to_path_buf()),
@ -198,7 +198,7 @@ impl Command for Open {
let converter = exts_opt.and_then(|exts| {
exts.iter().find_map(|ext| {
engine_state
.find_decl(format!("from {}", ext).as_bytes(), &[])
.find_decl(format!("from {ext}").as_bytes(), &[])
.map(|id| (id, ext.to_string()))
})
});
@ -246,7 +246,7 @@ impl Command for Open {
}
if output.is_empty() {
Ok(PipelineData::Empty)
Ok(PipelineData::empty())
} else if output.len() == 1 {
Ok(output.remove(0))
} else {
@ -314,7 +314,7 @@ fn extract_extensions(filename: &str) -> Vec<String> {
if current_extension.is_empty() {
current_extension.push_str(part);
} else {
current_extension = format!("{}.{}", part, current_extension);
current_extension = format!("{part}.{current_extension}");
}
extensions.push(current_extension.clone());
}

View File

@ -339,7 +339,7 @@ fn rm(
inner: vec![],
});
} else if !confirmed {
return Ok(PipelineData::Empty);
return Ok(PipelineData::empty());
}
}
@ -454,7 +454,7 @@ fn rm(
});
for result in iter {
engine_state.signals().check(call.head)?;
engine_state.signals().check(&call.head)?;
match result {
Ok(None) => {}
Ok(Some(msg)) => eprintln!("{msg}"),

View File

@ -91,7 +91,8 @@ impl Command for Save {
PipelineData::ByteStream(stream, metadata) => {
check_saving_to_source_file(metadata.as_ref(), &path, stderr_path.as_ref())?;
let (file, stderr_file) = get_files(&path, stderr_path.as_ref(), append, force)?;
let (file, stderr_file) =
get_files(engine_state, &path, stderr_path.as_ref(), append, force)?;
let size = stream.known_size();
let signals = engine_state.signals();
@ -190,7 +191,7 @@ impl Command for Save {
}
}
Ok(PipelineData::Empty)
Ok(PipelineData::empty())
}
PipelineData::ListStream(ls, pipeline_metadata)
if raw || prepare_path(&path, append, force)?.0.extension().is_none() =>
@ -201,7 +202,8 @@ impl Command for Save {
stderr_path.as_ref(),
)?;
let (mut file, _) = get_files(&path, stderr_path.as_ref(), append, force)?;
let (mut file, _) =
get_files(engine_state, &path, stderr_path.as_ref(), append, force)?;
for val in ls {
file.write_all(&value_to_bytes(val)?)
.map_err(&from_io_error)?;
@ -226,7 +228,8 @@ impl Command for Save {
input_to_bytes(input, Path::new(&path.item), raw, engine_state, stack, span)?;
// Only open file after successful conversion
let (mut file, _) = get_files(&path, stderr_path.as_ref(), append, force)?;
let (mut file, _) =
get_files(engine_state, &path, stderr_path.as_ref(), append, force)?;
file.write_all(&bytes).map_err(&from_io_error)?;
file.flush().map_err(&from_io_error)?;
@ -422,13 +425,14 @@ fn prepare_path(
}
}
fn open_file(path: &Path, span: Span, append: bool) -> Result<File, ShellError> {
let file: Result<File, nu_protocol::shell_error::io::ErrorKind> = match (append, path.exists())
{
(true, true) => std::fs::OpenOptions::new()
.append(true)
.open(path)
.map_err(|err| err.into()),
fn open_file(
engine_state: &EngineState,
path: &Path,
span: Span,
append: bool,
) -> Result<File, ShellError> {
let file: std::io::Result<File> = match (append, path.exists()) {
(true, true) => std::fs::OpenOptions::new().append(true).open(path),
_ => {
// This is a temporary solution until `std::fs::File::create` is fixed on Windows (rust-lang/rust#134893)
// A TOCTOU problem exists here, which may cause wrong error message to be shown
@ -438,22 +442,51 @@ fn open_file(path: &Path, span: Span, append: bool) -> Result<File, ShellError>
deprecated,
reason = "we don't get a IsADirectory error, so we need to provide it"
)]
Err(nu_protocol::shell_error::io::ErrorKind::from_std(
std::io::ErrorKind::IsADirectory,
))
Err(std::io::ErrorKind::IsADirectory.into())
} else {
std::fs::File::create(path).map_err(|err| err.into())
std::fs::File::create(path)
}
#[cfg(not(target_os = "windows"))]
std::fs::File::create(path).map_err(|err| err.into())
std::fs::File::create(path)
}
};
file.map_err(|err_kind| ShellError::Io(IoError::new(err_kind, span, PathBuf::from(path))))
match file {
Ok(file) => Ok(file),
Err(err) => {
// In caase of NotFound, search for the missing parent directory.
// This also presents a TOCTOU (or TOUTOC, technically?)
if err.kind() == std::io::ErrorKind::NotFound {
if let Some(missing_component) =
path.ancestors().skip(1).filter(|dir| !dir.exists()).last()
{
// By looking at the postfix to remove, rather than the prefix
// to keep, we are able to handle relative paths too.
let components_to_remove = path
.strip_prefix(missing_component)
.expect("Stripping ancestor from a path should never fail")
.as_os_str()
.as_encoded_bytes();
return Err(ShellError::Io(IoError::new(
ErrorKind::DirectoryNotFound,
engine_state
.span_match_postfix(span, components_to_remove)
.map(|(pre, _post)| pre)
.unwrap_or(span),
PathBuf::from(missing_component),
)));
}
}
Err(ShellError::Io(IoError::new(err, span, PathBuf::from(path))))
}
}
}
/// Get output file and optional stderr file
fn get_files(
engine_state: &EngineState,
path: &Spanned<PathBuf>,
stderr_path: Option<&Spanned<PathBuf>>,
append: bool,
@ -467,7 +500,7 @@ fn get_files(
.transpose()?;
// Only if both files can be used open and possibly truncate them
let file = open_file(path, path_span, append)?;
let file = open_file(engine_state, path, path_span, append)?;
let stderr_file = stderr_path_and_span
.map(|(stderr_path, stderr_path_span)| {
@ -480,7 +513,7 @@ fn get_files(
inner: vec![],
})
} else {
open_file(stderr_path, stderr_path_span, append)
open_file(engine_state, stderr_path, stderr_path_span, append)
}
})
.transpose()?;
@ -510,7 +543,7 @@ fn stream_to_file(
let mut reader = BufReader::new(source);
let res = loop {
if let Err(err) = signals.check(span) {
if let Err(err) = signals.check(&span) {
bar.abandoned_msg("# Cancelled #".to_owned());
return Err(err);
}

View File

@ -45,15 +45,16 @@ impl Command for Start {
// Attempt to parse the input as a URL
if let Ok(url) = url::Url::parse(path_no_whitespace) {
open_path(url.as_str(), engine_state, stack, path.span)?;
return Ok(PipelineData::Empty);
return Ok(PipelineData::empty());
}
// If it's not a URL, treat it as a file path
let cwd = engine_state.cwd(Some(stack))?;
let full_path = cwd.join(path_no_whitespace);
let full_path = nu_path::expand_path_with(path_no_whitespace, &cwd, true);
// Check if the path exists or if it's a valid file/directory
if full_path.exists() {
open_path(full_path, engine_state, stack, path.span)?;
return Ok(PipelineData::Empty);
return Ok(PipelineData::empty());
}
// If neither file nor URL, return an error
Err(ShellError::GenericError {

View File

@ -272,8 +272,8 @@ impl Command for UCp {
uu_cp::Error::NotAllFilesCopied => {}
_ => {
return Err(ShellError::GenericError {
error: format!("{}", error),
msg: format!("{}", error),
error: format!("{error}"),
msg: format!("{error}"),
span: None,
help: None,
inner: vec![],
@ -373,7 +373,7 @@ fn parse_and_set_attribute(
"xattr" => &mut attribute.xattr,
_ => {
return Err(ShellError::IncompatibleParametersSingle {
msg: format!("--preserve flag got an unexpected attribute \"{}\"", val),
msg: format!("--preserve flag got an unexpected attribute \"{val}\""),
span: value.span(),
});
}

View File

@ -77,8 +77,8 @@ impl Command for UMkdir {
for dir in directories {
if let Err(error) = mkdir(&dir, IS_RECURSIVE, get_mode(), is_verbose) {
return Err(ShellError::GenericError {
error: format!("{}", error),
msg: format!("{}", error),
error: format!("{error}"),
msg: format!("{error}"),
span: None,
help: None,
inner: vec![],

View File

@ -195,8 +195,8 @@ impl Command for UMv {
};
if let Err(error) = uu_mv::mv(&files, &options) {
return Err(ShellError::GenericError {
error: format!("{}", error),
msg: format!("{}", error),
error: format!("{error}"),
msg: format!("{error}"),
span: None,
help: None,
inner: Vec::new(),

View File

@ -220,7 +220,7 @@ impl Command for UTouch {
inner: Vec::new(),
},
TouchError::InvalidDateFormat(date) => ShellError::IncorrectValue {
msg: format!("Invalid date: {}", date),
msg: format!("Invalid date: {date}"),
val_span: date_span.expect("touch should've been given a date"),
call_span: call.head,
},

View File

@ -7,10 +7,10 @@ use notify_debouncer_full::{
};
use nu_engine::{ClosureEval, command_prelude::*};
use nu_protocol::{
engine::{Closure, StateWorkingSet},
format_shell_error,
DeprecationEntry, DeprecationType, ReportMode, engine::Closure, report_shell_error,
shell_error::io::IoError,
};
use std::{
path::PathBuf,
sync::mpsc::{RecvTimeoutError, channel},
@ -37,9 +37,20 @@ impl Command for Watch {
vec!["watcher", "reload", "filesystem"]
}
fn deprecation_info(&self) -> Vec<DeprecationEntry> {
vec![DeprecationEntry {
ty: DeprecationType::Flag("--debounce-ms".into()),
report_mode: ReportMode::FirstUse,
since: Some("0.107.0".into()),
expected_removal: Some("0.109.0".into()),
help: Some("`--debounce-ms` will be removed in favour of `--debounce`".into()),
}]
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("watch")
.input_output_types(vec![(Type::Nothing, Type::table())])
// actually `watch` never returns normally, but we don't have `noreturn` / `never` type yet
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.required("path", SyntaxShape::Filepath, "The path to watch. Can be a file or directory.")
.required("closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::String, SyntaxShape::String, SyntaxShape::String])),
@ -47,7 +58,13 @@ impl Command for Watch {
.named(
"debounce-ms",
SyntaxShape::Int,
"Debounce changes for this many milliseconds (default: 100). Adjust if you find that single writes are reported as multiple events",
"Debounce changes for this many milliseconds (default: 100). Adjust if you find that single writes are reported as multiple events (deprecated)",
None,
)
.named(
"debounce",
SyntaxShape::Duration,
"Debounce changes for this duration (default: 100ms). Adjust if you find that single writes are reported as multiple events",
Some('d'),
)
.named(
@ -99,20 +116,14 @@ impl Command for Watch {
let quiet = call.has_flag(engine_state, stack, "quiet")?;
let debounce_duration_flag: Option<Spanned<i64>> =
let debounce_duration_flag_ms: Option<Spanned<i64>> =
call.get_flag(engine_state, stack, "debounce-ms")?;
let debounce_duration = match debounce_duration_flag {
Some(val) => match u64::try_from(val.item) {
Ok(val) => Duration::from_millis(val),
Err(_) => {
return Err(ShellError::TypeMismatch {
err_message: "Debounce duration is invalid".to_string(),
span: val.span,
});
}
},
None => DEFAULT_WATCH_DEBOUNCE_DURATION,
};
let debounce_duration_flag: Option<Spanned<Duration>> =
call.get_flag(engine_state, stack, "debounce")?;
let debounce_duration: Duration =
resolve_duration_arguments(debounce_duration_flag_ms, debounce_duration_flag)?;
let glob_flag: Option<Spanned<String>> = call.get_flag(engine_state, stack, "glob")?;
let glob_pattern = match glob_flag {
@ -200,17 +211,12 @@ impl Command for Watch {
new_path.unwrap_or_else(|| "".into()).to_string_lossy(),
head,
))
.run_with_input(PipelineData::Empty);
.run_with_input(PipelineData::empty());
match result {
Ok(val) => {
val.print_table(engine_state, stack, false, false)?;
}
Err(err) => {
let working_set = StateWorkingSet::new(engine_state);
eprintln!("{}", format_shell_error(&working_set, &err));
}
}
Ok(val) => val.print_table(engine_state, stack, false, false)?,
Err(err) => report_shell_error(engine_state, &err),
};
}
Ok(())
@ -303,6 +309,11 @@ impl Command for Watch {
example: r#"watch /foo/bar { |op, path| $"($op) - ($path)(char nl)" | save --append changes_in_bar.log }"#,
result: None,
},
Example {
description: "Print file changes with a debounce time of 5 minutes",
example: r#"watch /foo/bar --debounce 5min { |op, path| $"Registered ($op) on ($path)" | print }"#,
result: None,
},
Example {
description: "Note: if you are looking to run a command every N units of time, this can be accomplished with a loop and sleep",
example: r#"loop { command; sleep duration }"#,
@ -311,3 +322,26 @@ impl Command for Watch {
]
}
}
fn resolve_duration_arguments(
debounce_duration_flag_ms: Option<Spanned<i64>>,
debounce_duration_flag: Option<Spanned<Duration>>,
) -> Result<Duration, ShellError> {
match (debounce_duration_flag, debounce_duration_flag_ms) {
(None, None) => Ok(DEFAULT_WATCH_DEBOUNCE_DURATION),
(Some(l), Some(r)) => Err(ShellError::IncompatibleParameters {
left_message: "Here".to_string(),
left_span: l.span,
right_message: "and here".to_string(),
right_span: r.span,
}),
(None, Some(val)) => match u64::try_from(val.item) {
Ok(v) => Ok(Duration::from_millis(v)),
Err(_) => Err(ShellError::TypeMismatch {
err_message: "Debounce duration is invalid".to_string(),
span: val.span,
}),
},
(Some(v), None) => Ok(v.item),
}
}

Some files were not shown because too many files have changed in this diff Show More