Merge branch 'main' of github.com:nushell/nushell into path/fix-wrap-emojie-issue

This commit is contained in:
Maxim Zhiburt 2025-05-06 20:17:44 +03:00
commit 99fb49fc2c
125 changed files with 3142 additions and 1506 deletions

View File

@ -37,7 +37,7 @@ jobs:
- uses: actions/checkout@v4.1.7
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
- name: cargo fmt
run: cargo fmt --all -- --check
@ -65,7 +65,7 @@ jobs:
- uses: actions/checkout@v4.1.7
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
- name: Tests
run: cargo test --workspace --profile ci --exclude nu_plugin_*
@ -94,7 +94,7 @@ jobs:
- uses: actions/checkout@v4.1.7
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
- name: Install Nushell
run: cargo install --path . --locked --force
@ -145,7 +145,7 @@ jobs:
- uses: actions/checkout@v4.1.7
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
- name: Clippy
run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS
@ -186,7 +186,7 @@ jobs:
- uses: actions/checkout@v4.1.7
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
- name: Add wasm32-unknown-unknown target
run: rustup target add wasm32-unknown-unknown

View File

@ -8,6 +8,7 @@
name: Nightly Build
on:
workflow_dispatch:
push:
branches:
- nightly # Just for test purpose only with the nightly repo
@ -39,7 +40,7 @@ jobs:
uses: hustcer/setup-nu@v3
if: github.repository == 'nushell/nightly'
with:
version: 0.101.0
version: 0.103.0
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
- name: Prepare for Nightly Release
@ -131,7 +132,7 @@ jobs:
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
with:
rustflags: ''
@ -139,7 +140,7 @@ jobs:
- name: Setup Nushell
uses: hustcer/setup-nu@v3
with:
version: 0.101.0
version: 0.103.0
- name: Release Nu Binary
id: nu
@ -197,7 +198,7 @@ jobs:
- name: Setup Nushell
uses: hustcer/setup-nu@v3
with:
version: 0.101.0
version: 0.103.0
# Keep the last a few releases
- name: Delete Older Releases

View File

@ -117,14 +117,14 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
# ----------------------------------------------------------------------------
# Build for Windows without static-link-openssl feature
# ----------------------------------------------------------------------------
if $os in ['windows-latest'] {
if $os =~ 'windows' {
cargo-build-nu
}
# ----------------------------------------------------------------------------
# Prepare for the release archive
# ----------------------------------------------------------------------------
let suffix = if $os == 'windows-latest' { '.exe' }
let suffix = if $os =~ 'windows' { '.exe' }
# nu, nu_plugin_* were all included
let executable = $'target/($target)/release/($bin)*($suffix)'
print $'Current executable file: ($executable)'
@ -148,10 +148,10 @@ For more information, refer to https://www.nushell.sh/book/plugins.html
[LICENSE ...(glob $executable)] | each {|it| cp -rv $it $dist } | flatten
print $'(char nl)Check binary release version detail:'; hr-line
let ver = if $os == 'windows-latest' {
(do -i { .\output\nu.exe -c 'version' }) | str join
let ver = if $os =~ 'windows' {
(do -i { .\output\nu.exe -c 'version' }) | default '' | str join
} else {
(do -i { ./output/nu -c 'version' }) | str join
(do -i { ./output/nu -c 'version' }) | default '' | str join
}
if ($ver | str trim | is-empty) {
print $'(ansi r)Incompatible Nu binary: The binary cross compiled is not runnable on current arch...(ansi reset)'
@ -177,7 +177,7 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
# REF: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT
} else if $os == 'windows-latest' {
} else if $os =~ 'windows' {
let releaseStem = $'($bin)-($version)-($target)'
@ -221,7 +221,7 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
}
def 'cargo-build-nu' [] {
if $os == 'windows-latest' {
if $os =~ 'windows' {
cargo build --release --all --target $target
} else {
cargo build --release --all --target $target --features=static-link-openssl

View File

@ -80,7 +80,7 @@ jobs:
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
with:
cache: false
@ -89,7 +89,7 @@ jobs:
- name: Setup Nushell
uses: hustcer/setup-nu@v3
with:
version: 0.101.0
version: 0.103.0
- name: Release Nu Binary
id: nu

View File

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

143
Cargo.lock generated
View File

@ -781,9 +781,9 @@ dependencies = [
[[package]]
name = "bracoxide"
version = "0.1.5"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f52991c481aa9d7518254cfb6ce5726d24ff8c5d383d6422cd3793729b0962a"
checksum = "3572b24445a122332bb25a2637248d62ca8b567351d98b1194ca4132c61810bd"
[[package]]
name = "brotli"
@ -882,16 +882,18 @@ checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659"
[[package]]
name = "calamine"
version = "0.26.1"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138646b9af2c5d7f1804ea4bf93afc597737d2bd4f7341d67c48b03316976eb1"
checksum = "6d80f81ba5c68206b9027e62346d49dc26fb32ffc4fe6ef7022a8ae21d348ccb"
dependencies = [
"atoi_simd",
"byteorder",
"chrono",
"codepage",
"encoding_rs",
"fast-float2",
"log",
"quick-xml 0.31.0",
"quick-xml 0.37.1",
"serde",
"zip",
]
@ -3489,7 +3491,7 @@ dependencies = [
[[package]]
name = "nu"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"assert_cmd",
"crossterm",
@ -3543,7 +3545,7 @@ dependencies = [
[[package]]
name = "nu-cli"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"chrono",
"crossterm",
@ -3580,7 +3582,7 @@ dependencies = [
[[package]]
name = "nu-cmd-base"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"indexmap",
"miette",
@ -3592,7 +3594,7 @@ dependencies = [
[[package]]
name = "nu-cmd-extra"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"fancy-regex",
"heck",
@ -3618,7 +3620,7 @@ dependencies = [
[[package]]
name = "nu-cmd-lang"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"itertools 0.13.0",
"nu-engine",
@ -3632,7 +3634,7 @@ dependencies = [
[[package]]
name = "nu-cmd-plugin"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"itertools 0.13.0",
"nu-engine",
@ -3643,7 +3645,7 @@ dependencies = [
[[package]]
name = "nu-color-config"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"nu-ansi-term",
"nu-engine",
@ -3655,7 +3657,7 @@ dependencies = [
[[package]]
name = "nu-command"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"alphanumeric-sort",
"base64 0.22.1",
@ -3768,7 +3770,7 @@ dependencies = [
[[package]]
name = "nu-derive-value"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"heck",
"proc-macro-error2",
@ -3779,7 +3781,7 @@ dependencies = [
[[package]]
name = "nu-engine"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"log",
"nu-glob",
@ -3790,7 +3792,7 @@ dependencies = [
[[package]]
name = "nu-explore"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"ansi-str 0.8.0",
"anyhow",
@ -3814,14 +3816,14 @@ dependencies = [
[[package]]
name = "nu-glob"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"doc-comment",
]
[[package]]
name = "nu-json"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"fancy-regex",
"linked-hash-map",
@ -3834,7 +3836,7 @@ dependencies = [
[[package]]
name = "nu-lsp"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"assert-json-diff",
"crossbeam-channel",
@ -3861,7 +3863,7 @@ dependencies = [
[[package]]
name = "nu-parser"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"bytesize",
"chrono",
@ -3878,7 +3880,7 @@ dependencies = [
[[package]]
name = "nu-path"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"dirs",
"omnipath",
@ -3888,7 +3890,7 @@ dependencies = [
[[package]]
name = "nu-plugin"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"log",
"nix 0.29.0",
@ -3898,13 +3900,13 @@ dependencies = [
"nu-protocol",
"nu-utils",
"serde",
"thiserror 2.0.6",
"thiserror 2.0.12",
"typetag",
]
[[package]]
name = "nu-plugin-core"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"interprocess",
"log",
@ -3918,7 +3920,7 @@ dependencies = [
[[package]]
name = "nu-plugin-engine"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"log",
"nu-engine",
@ -3934,7 +3936,7 @@ dependencies = [
[[package]]
name = "nu-plugin-protocol"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"nu-protocol",
"nu-utils",
@ -3946,7 +3948,7 @@ dependencies = [
[[package]]
name = "nu-plugin-test-support"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"nu-ansi-term",
"nu-cmd-lang",
@ -3964,7 +3966,7 @@ dependencies = [
[[package]]
name = "nu-pretty-hex"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"heapless",
"nu-ansi-term",
@ -3973,7 +3975,7 @@ dependencies = [
[[package]]
name = "nu-protocol"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"brotli",
"bytes",
@ -4005,7 +4007,7 @@ dependencies = [
"strum",
"strum_macros",
"tempfile",
"thiserror 2.0.6",
"thiserror 2.0.12",
"typetag",
"web-time",
"windows-sys 0.48.0",
@ -4013,7 +4015,7 @@ dependencies = [
[[package]]
name = "nu-std"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"log",
"miette",
@ -4024,7 +4026,7 @@ dependencies = [
[[package]]
name = "nu-system"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"chrono",
"itertools 0.13.0",
@ -4042,7 +4044,7 @@ dependencies = [
[[package]]
name = "nu-table"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"fancy-regex",
"nu-ansi-term",
@ -4055,7 +4057,7 @@ dependencies = [
[[package]]
name = "nu-term-grid"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"nu-utils",
"unicode-width 0.2.0",
@ -4063,7 +4065,7 @@ dependencies = [
[[package]]
name = "nu-test-support"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"nu-glob",
"nu-path",
@ -4075,7 +4077,7 @@ dependencies = [
[[package]]
name = "nu-utils"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"crossterm",
"crossterm_winapi",
@ -4104,7 +4106,7 @@ dependencies = [
[[package]]
name = "nu_plugin_example"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"nu-cmd-lang",
"nu-plugin",
@ -4114,7 +4116,7 @@ dependencies = [
[[package]]
name = "nu_plugin_formats"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"chrono",
"eml-parser",
@ -4129,7 +4131,7 @@ dependencies = [
[[package]]
name = "nu_plugin_gstat"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"git2",
"nu-plugin",
@ -4138,7 +4140,7 @@ dependencies = [
[[package]]
name = "nu_plugin_inc"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"nu-plugin",
"nu-protocol",
@ -4147,7 +4149,7 @@ dependencies = [
[[package]]
name = "nu_plugin_polars"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"aws-config",
"aws-credential-types",
@ -4186,7 +4188,7 @@ dependencies = [
[[package]]
name = "nu_plugin_query"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"gjson",
"nu-plugin",
@ -4201,7 +4203,7 @@ dependencies = [
[[package]]
name = "nu_plugin_stress_internals"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"interprocess",
"serde",
@ -4325,7 +4327,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]]
name = "nuon"
version = "0.103.1"
version = "0.104.1"
dependencies = [
"chrono",
"nu-engine",
@ -4694,7 +4696,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc"
dependencies = [
"memchr",
"thiserror 2.0.6",
"thiserror 2.0.12",
"ucd-trie",
]
@ -5009,7 +5011,7 @@ dependencies = [
"serde",
"serde_json",
"strum_macros",
"thiserror 2.0.6",
"thiserror 2.0.12",
"version_check",
"xxhash-rust",
]
@ -5025,7 +5027,7 @@ dependencies = [
"polars-arrow-format",
"regex",
"simdutf8",
"thiserror 2.0.6",
"thiserror 2.0.12",
]
[[package]]
@ -5624,16 +5626,6 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
dependencies = [
"encoding_rs",
"memchr",
]
[[package]]
name = "quick-xml"
version = "0.32.0"
@ -5658,6 +5650,7 @@ version = "0.37.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f22f29bdff3987b4d8632ef95fd6424ec7e4e0a57e2f4fc63e489e75357f6a03"
dependencies = [
"encoding_rs",
"memchr",
"serde",
]
@ -5697,7 +5690,7 @@ dependencies = [
"rustc-hash 2.1.0",
"rustls 0.23.20",
"socket2",
"thiserror 2.0.6",
"thiserror 2.0.12",
"tokio",
"tracing",
]
@ -5716,7 +5709,7 @@ dependencies = [
"rustls 0.23.20",
"rustls-pki-types",
"slab",
"thiserror 2.0.6",
"thiserror 2.0.12",
"tinyvec",
"tracing",
"web-time",
@ -5919,9 +5912,9 @@ dependencies = [
[[package]]
name = "reedline"
version = "0.39.0"
version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd4728ee71d2aa3a364ee64470d1aa64b3f0467b2d28b73df15259d005dec64a"
checksum = "b5cdfab7494d13ebfb6ce64828648518205d3ce8541ef1f94a27887f29d2d50b"
dependencies = [
"arboard",
"chrono",
@ -5935,9 +5928,9 @@ dependencies = [
"strip-ansi-escapes",
"strum",
"strum_macros",
"thiserror 1.0.69",
"thiserror 2.0.12",
"unicode-segmentation",
"unicode-width 0.1.11",
"unicode-width 0.2.0",
]
[[package]]
@ -6061,7 +6054,7 @@ dependencies = [
"chumsky",
"memchr",
"quoted_printable",
"thiserror 2.0.6",
"thiserror 2.0.12",
]
[[package]]
@ -7171,11 +7164,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.6"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl 2.0.6",
"thiserror-impl 2.0.12",
]
[[package]]
@ -7191,9 +7184,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.6"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
@ -7747,7 +7740,7 @@ dependencies = [
"clap",
"rand 0.9.0",
"tempfile",
"thiserror 2.0.6",
"thiserror 2.0.12",
"uucore",
]
@ -7761,7 +7754,7 @@ dependencies = [
"fs_extra",
"indicatif",
"libc",
"thiserror 2.0.6",
"thiserror 2.0.12",
"uucore",
"windows-sys 0.59.0",
]
@ -7776,7 +7769,7 @@ dependencies = [
"clap",
"filetime",
"parse_datetime",
"thiserror 2.0.6",
"thiserror 2.0.12",
"uucore",
"windows-sys 0.59.0",
]
@ -8771,18 +8764,16 @@ dependencies = [
[[package]]
name = "zip"
version = "2.4.1"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "938cc23ac49778ac8340e366ddc422b2227ea176edb447e23fc0627608dddadd"
checksum = "27c03817464f64e23f6f37574b4fdc8cf65925b5bfd2b0f2aedf959791941f88"
dependencies = [
"arbitrary",
"crc32fast",
"crossbeam-utils",
"displaydoc",
"flate2",
"indexmap",
"memchr",
"thiserror 2.0.6",
"zopfli",
]

View File

@ -11,7 +11,7 @@ license = "MIT"
name = "nu"
repository = "https://github.com/nushell/nushell"
rust-version = "1.84.1"
version = "0.103.1"
version = "0.104.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -66,12 +66,12 @@ alphanumeric-sort = "1.5"
ansi-str = "0.8"
anyhow = "1.0.82"
base64 = "0.22.1"
bracoxide = "0.1.5"
bracoxide = "0.1.6"
brotli = "7.0"
byteorder = "1.5"
bytes = "1"
bytesize = "1.3.3"
calamine = "0.26.1"
calamine = "0.27"
chardetng = "0.1.17"
chrono = { default-features = false, version = "0.4.34" }
chrono-humanize = "0.2.3"
@ -140,7 +140,7 @@ getrandom = "0.2" # pick same version that rand requires
rand_chacha = "0.9"
ratatui = "0.29"
rayon = "1.10"
reedline = "0.39.0"
reedline = "0.40.0"
rmp = "0.8"
rmp-serde = "1.3"
roxmltree = "0.20"
@ -197,22 +197,22 @@ unchecked_duration_subtraction = "warn"
workspace = true
[dependencies]
nu-cli = { path = "./crates/nu-cli", version = "0.103.1" }
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.103.1" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.103.1" }
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.103.1", optional = true }
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.103.1" }
nu-command = { path = "./crates/nu-command", version = "0.103.1" }
nu-engine = { path = "./crates/nu-engine", version = "0.103.1" }
nu-explore = { path = "./crates/nu-explore", version = "0.103.1" }
nu-lsp = { path = "./crates/nu-lsp/", version = "0.103.1" }
nu-parser = { path = "./crates/nu-parser", version = "0.103.1" }
nu-path = { path = "./crates/nu-path", version = "0.103.1" }
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.103.1" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.103.1" }
nu-std = { path = "./crates/nu-std", version = "0.103.1" }
nu-system = { path = "./crates/nu-system", version = "0.103.1" }
nu-utils = { path = "./crates/nu-utils", version = "0.103.1" }
nu-cli = { path = "./crates/nu-cli", version = "0.104.1" }
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.104.1" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.104.1" }
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.104.1", optional = true }
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.104.1" }
nu-command = { path = "./crates/nu-command", version = "0.104.1" }
nu-engine = { path = "./crates/nu-engine", version = "0.104.1" }
nu-explore = { path = "./crates/nu-explore", version = "0.104.1" }
nu-lsp = { path = "./crates/nu-lsp/", version = "0.104.1" }
nu-parser = { path = "./crates/nu-parser", version = "0.104.1" }
nu-path = { path = "./crates/nu-path", version = "0.104.1" }
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.104.1" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.104.1" }
nu-std = { path = "./crates/nu-std", version = "0.104.1" }
nu-system = { path = "./crates/nu-system", version = "0.104.1" }
nu-utils = { path = "./crates/nu-utils", version = "0.104.1" }
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
crossterm = { workspace = true }
@ -241,9 +241,9 @@ nix = { workspace = true, default-features = false, features = [
] }
[dev-dependencies]
nu-test-support = { path = "./crates/nu-test-support", version = "0.103.1" }
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.103.1" }
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.103.1" }
nu-test-support = { path = "./crates/nu-test-support", version = "0.104.1" }
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.104.1" }
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.104.1" }
assert_cmd = "2.0"
dirs = { workspace = true }
tango-bench = "0.6"

View File

@ -222,6 +222,7 @@ Please submit an issue or PR to be added to this list.
- [Dorothy](http://github.com/bevry/dorothy)
- [Direnv](https://github.com/direnv/direnv/blob/master/docs/hook.md#nushell)
- [x-cmd](https://x-cmd.com/mod/nu)
- [vfox](https://github.com/version-fox/vfox)
## Contributing

View File

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

View File

@ -26,7 +26,7 @@ impl Command for HistoryImport {
fn extra_description(&self) -> &str {
r#"Can import history from input, either successive command lines or more detailed records. If providing records, available fields are:
command_line, id, start_timestamp, hostname, cwd, duration, exit_status.
command, start_timestamp, hostname, cwd, duration, exit_status.
If no input is provided, will import all history items from existing history in the other format: if current history is stored in sqlite, it will store it in plain text and vice versa.

View File

@ -1,3 +1,5 @@
use std::borrow::Cow;
use crate::completions::{Completer, CompletionOptions, SemanticSuggestion, SuggestionKind};
use nu_engine::{column::get_columns, eval_variable};
use nu_protocol::{
@ -101,7 +103,9 @@ pub(crate) fn eval_cell_path(
} else {
eval_constant(working_set, head)
}?;
head_value.follow_cell_path(path_members, false)
head_value
.follow_cell_path(path_members, false)
.map(Cow::into_owned)
}
fn get_suggestions_by_value(

View File

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

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT"
name = "nu-cmd-extra"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
version = "0.103.1"
version = "0.104.1"
# 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.103.1" }
nu-engine = { path = "../nu-engine", version = "0.103.1", default-features = false }
nu-json = { version = "0.103.1", path = "../nu-json" }
nu-parser = { path = "../nu-parser", version = "0.103.1" }
nu-pretty-hex = { version = "0.103.1", path = "../nu-pretty-hex" }
nu-protocol = { path = "../nu-protocol", version = "0.103.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.103.1", default-features = false }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.104.1" }
nu-engine = { path = "../nu-engine", version = "0.104.1", default-features = false }
nu-json = { version = "0.104.1", path = "../nu-json" }
nu-parser = { path = "../nu-parser", version = "0.104.1" }
nu-pretty-hex = { version = "0.104.1", path = "../nu-pretty-hex" }
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.104.1", 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.103.1" }
nu-command = { path = "../nu-command", version = "0.103.1" }
nu-test-support = { path = "../nu-test-support", version = "0.103.1" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.104.1" }
nu-command = { path = "../nu-command", version = "0.104.1" }
nu-test-support = { path = "../nu-test-support", version = "0.104.1" }

View File

@ -253,12 +253,11 @@ fn format_record(
optional: false,
})
.collect();
match data_as_value.clone().follow_cell_path(&path_members, false) {
Ok(value_at_column) => {
output.push_str(value_at_column.to_expanded_string(", ", config).as_str())
}
Err(se) => return Err(se),
}
let expanded_string = data_as_value
.follow_cell_path(&path_members, false)?
.to_expanded_string(", ", config);
output.push_str(expanded_string.as_str())
}
}
}

View File

@ -6,7 +6,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
edition = "2021"
license = "MIT"
name = "nu-cmd-lang"
version = "0.103.1"
version = "0.104.1"
[lib]
bench = false
@ -15,10 +15,10 @@ bench = false
workspace = true
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.103.1", default-features = false }
nu-parser = { path = "../nu-parser", version = "0.103.1" }
nu-protocol = { path = "../nu-protocol", version = "0.103.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.103.1", default-features = false }
nu-engine = { path = "../nu-engine", version = "0.104.1", default-features = false }
nu-parser = { path = "../nu-parser", version = "0.104.1" }
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.104.1", default-features = false }
itertools = { workspace = true }
shadow-rs = { version = "1.1", default-features = false }

View File

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

View File

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

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT"
name = "nu-command"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
version = "0.103.1"
version = "0.104.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -16,21 +16,21 @@ bench = false
workspace = true
[dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.103.1" }
nu-color-config = { path = "../nu-color-config", version = "0.103.1" }
nu-engine = { path = "../nu-engine", version = "0.103.1", default-features = false }
nu-glob = { path = "../nu-glob", version = "0.103.1" }
nu-json = { path = "../nu-json", version = "0.103.1" }
nu-parser = { path = "../nu-parser", version = "0.103.1" }
nu-path = { path = "../nu-path", version = "0.103.1" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.103.1" }
nu-protocol = { path = "../nu-protocol", version = "0.103.1", default-features = false }
nu-system = { path = "../nu-system", version = "0.103.1" }
nu-table = { path = "../nu-table", version = "0.103.1" }
nu-term-grid = { path = "../nu-term-grid", version = "0.103.1" }
nu-utils = { path = "../nu-utils", version = "0.103.1", default-features = false }
nu-cmd-base = { path = "../nu-cmd-base", version = "0.104.1" }
nu-color-config = { path = "../nu-color-config", version = "0.104.1" }
nu-engine = { path = "../nu-engine", version = "0.104.1", default-features = false }
nu-glob = { path = "../nu-glob", version = "0.104.1" }
nu-json = { path = "../nu-json", version = "0.104.1" }
nu-parser = { path = "../nu-parser", version = "0.104.1" }
nu-path = { path = "../nu-path", version = "0.104.1" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.104.1" }
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false }
nu-system = { path = "../nu-system", version = "0.104.1" }
nu-table = { path = "../nu-table", version = "0.104.1" }
nu-term-grid = { path = "../nu-term-grid", version = "0.104.1" }
nu-utils = { path = "../nu-utils", version = "0.104.1", default-features = false }
nu-ansi-term = { workspace = true }
nuon = { path = "../nuon", version = "0.103.1" }
nuon = { path = "../nuon", version = "0.104.1" }
alphanumeric-sort = { workspace = true }
base64 = { workspace = true }
@ -209,8 +209,8 @@ sqlite = ["rusqlite"]
trash-support = ["trash"]
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.103.1" }
nu-test-support = { path = "../nu-test-support", version = "0.103.1" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.104.1" }
nu-test-support = { path = "../nu-test-support", version = "0.104.1" }
dirs = { workspace = true }
mockito = { workspace = true, default-features = false }

View File

@ -38,10 +38,16 @@ impl Command for DateNow {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get the current date and display it in a given format string.",
description: "Get the current date and format it in a given format string.",
example: r#"date now | format date "%Y-%m-%d %H:%M:%S""#,
result: None,
},
Example {
description:
"Get the current date and format it according to the RFC 3339 standard.",
example: r#"date now | format date "%+""#,
result: None,
},
Example {
description: "Get the time duration since 2019-04-30.",
example: r#"(date now) - 2019-05-01"#,
@ -53,7 +59,8 @@ impl Command for DateNow {
result: None,
},
Example {
description: "Get current time in full RFC 3339 format with time zone.",
description:
"Get current time and format it in the debug format (RFC 2822 with timezone)",
example: r#"date now | debug"#,
result: None,
},

View File

@ -452,10 +452,18 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
JobSpawn,
JobList,
JobKill,
JobId,
JobTag,
Job,
};
#[cfg(not(target_family = "wasm"))]
bind_command! {
JobSend,
JobRecv,
JobFlush,
}
#[cfg(all(unix, feature = "os"))]
bind_command! {
JobUnfreeze,

View File

@ -0,0 +1,58 @@
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct JobFlush;
impl Command for JobFlush {
fn name(&self) -> &str {
"job flush"
}
fn description(&self) -> &str {
"Clear this job's mailbox."
}
fn extra_description(&self) -> &str {
r#"
This command removes all messages in the mailbox of the current job.
If a message is received while this command is executing, it may also be discarded.
"#
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("job flush")
.category(Category::Experimental)
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.allow_variants_without_examples(true)
}
fn search_terms(&self) -> Vec<&str> {
vec![]
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let mut mailbox = engine_state
.current_job
.mailbox
.lock()
.expect("failed to acquire lock");
mailbox.clear();
Ok(Value::nothing(call.head).into_pipeline_data())
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "job flush",
description: "Clear the mailbox of the current job.",
result: None,
}]
}
}

View File

@ -0,0 +1,50 @@
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct JobId;
impl Command for JobId {
fn name(&self) -> &str {
"job id"
}
fn description(&self) -> &str {
"Get id of current job."
}
fn extra_description(&self) -> &str {
"This command returns the job id for the current background job.
The special id 0 indicates that this command was not called from a background job thread, and
was instead spawned by main nushell execution thread."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("job id")
.category(Category::Experimental)
.input_output_types(vec![(Type::Nothing, Type::Int)])
}
fn search_terms(&self) -> Vec<&str> {
vec!["self", "this", "my-id", "this-id"]
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
Ok(Value::int(engine_state.current_job.id.get() as i64, head).into_pipeline_data())
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "job id",
description: "Get id of current job",
result: None,
}]
}
}

View File

@ -0,0 +1,181 @@
use std::{
sync::mpsc::{RecvTimeoutError, TryRecvError},
time::{Duration, Instant},
};
use nu_engine::command_prelude::*;
use nu_protocol::{
engine::{FilterTag, Mailbox},
Signals,
};
#[derive(Clone)]
pub struct JobRecv;
const CTRL_C_CHECK_INTERVAL: Duration = Duration::from_millis(100);
impl Command for JobRecv {
fn name(&self) -> &str {
"job recv"
}
fn description(&self) -> &str {
"Read a message from the mailbox."
}
fn extra_description(&self) -> &str {
r#"When messages are sent to the current process, they get stored in what is called the "mailbox".
This commands reads and returns a message from the mailbox, in a first-in-first-out fashion.
Messages may have numeric flags attached to them. This commands supports filtering out messages that do not satisfy a given tag, by using the `tag` flag.
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.
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.
In the case of two or more threads running this command, they will wait until other threads are done using it,
in no particular order, regardless of the specified timeout parameter.
"#
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("job recv")
.category(Category::Experimental)
.named("tag", SyntaxShape::Int, "A tag for the message", None)
.named(
"timeout",
SyntaxShape::Duration,
"The maximum time duration to wait for.",
None,
)
.input_output_types(vec![(Type::Nothing, Type::Any)])
.allow_variants_without_examples(true)
}
fn search_terms(&self) -> Vec<&str> {
vec!["receive"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let tag_arg: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "tag")?;
if let Some(tag) = tag_arg {
if tag.item < 0 {
return Err(ShellError::NeedsPositiveValue { span: tag.span });
}
}
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 mut mailbox = engine_state
.current_job
.mailbox
.lock()
.expect("failed to acquire lock");
if let Some(timeout) = timeout {
if timeout == Duration::ZERO {
recv_instantly(&mut mailbox, tag, head)
} else {
recv_with_time_limit(&mut mailbox, tag, engine_state.signals(), head, timeout)
}
} else {
recv_without_time_limit(&mut mailbox, tag, engine_state.signals(), head)
}
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "job recv",
description: "Block the current thread while no message arrives",
result: None,
},
Example {
example: "job recv --timeout 10sec",
description: "Receive a message, wait for at most 10 seconds.",
result: None,
},
Example {
example: "job recv --timeout 0sec",
description: "Get a message or fail if no message is available immediately",
result: None,
},
]
}
}
fn recv_without_time_limit(
mailbox: &mut Mailbox,
tag: Option<FilterTag>,
signals: &Signals,
span: Span,
) -> Result<PipelineData, ShellError> {
loop {
if signals.interrupted() {
return Err(ShellError::Interrupted { span });
}
match mailbox.recv_timeout(tag, CTRL_C_CHECK_INTERVAL) {
Ok(value) => return Ok(value),
Err(RecvTimeoutError::Timeout) => {} // try again
Err(RecvTimeoutError::Disconnected) => return Err(ShellError::Interrupted { span }),
}
}
}
fn recv_instantly(
mailbox: &mut Mailbox,
tag: Option<FilterTag>,
span: Span,
) -> Result<PipelineData, ShellError> {
match mailbox.try_recv(tag) {
Ok(value) => Ok(value),
Err(TryRecvError::Empty) => Err(ShellError::RecvTimeout { span }),
Err(TryRecvError::Disconnected) => Err(ShellError::Interrupted { span }),
}
}
fn recv_with_time_limit(
mailbox: &mut Mailbox,
tag: Option<FilterTag>,
signals: &Signals,
span: Span,
timeout: Duration,
) -> Result<PipelineData, ShellError> {
let deadline = Instant::now() + timeout;
loop {
if signals.interrupted() {
return Err(ShellError::Interrupted { span });
}
let time_until_deadline = deadline.saturating_duration_since(Instant::now());
let time_to_sleep = time_until_deadline.min(CTRL_C_CHECK_INTERVAL);
match mailbox.recv_timeout(tag, time_to_sleep) {
Ok(value) => return Ok(value),
Err(RecvTimeoutError::Timeout) => {} // try again
Err(RecvTimeoutError::Disconnected) => return Err(ShellError::Interrupted { span }),
}
if time_until_deadline.is_zero() {
return Err(ShellError::RecvTimeout { span });
}
}
}

View File

@ -0,0 +1,112 @@
use nu_engine::command_prelude::*;
use nu_protocol::{engine::FilterTag, JobId};
#[derive(Clone)]
pub struct JobSend;
impl Command for JobSend {
fn name(&self) -> &str {
"job send"
}
fn description(&self) -> &str {
"Send a message to the mailbox of a job."
}
fn extra_description(&self) -> &str {
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 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.
This command never blocks.
"#
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("job send")
.category(Category::Experimental)
.required(
"id",
SyntaxShape::Int,
"The id of the job to send the message to.",
)
.named("tag", SyntaxShape::Int, "A tag for the message", None)
.input_output_types(vec![(Type::Any, Type::Nothing)])
.allow_variants_without_examples(true)
}
fn search_terms(&self) -> Vec<&str> {
vec![]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let id_arg: Spanned<i64> = call.req(engine_state, stack, 0)?;
let tag_arg: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "tag")?;
let id = id_arg.item;
if id < 0 {
return Err(ShellError::NeedsPositiveValue { span: id_arg.span });
}
if let Some(tag) = tag_arg {
if tag.item < 0 {
return Err(ShellError::NeedsPositiveValue { span: tag.span });
}
}
let tag = tag_arg.map(|it| it.item as FilterTag);
if id == 0 {
engine_state
.root_job_sender
.send((tag, input))
.expect("this should NEVER happen.");
} else {
let jobs = engine_state.jobs.lock().expect("failed to acquire lock");
if let Some(job) = jobs.lookup(JobId::new(id as usize)) {
match job {
nu_protocol::engine::Job::Thread(thread_job) => {
// it is ok to send this value while holding the lock, because
// mail channels are always unbounded, so this send never blocks
let _ = thread_job.sender.send((tag, input));
}
nu_protocol::engine::Job::Frozen(_) => {
return Err(ShellError::JobIsFrozen {
id: id as usize,
span: id_arg.span,
});
}
}
} else {
return Err(ShellError::JobNotFound {
id: id as usize,
span: id_arg.span,
});
}
}
Ok(Value::nothing(head).into_pipeline_data())
}
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,
}]
}
}

View File

@ -1,14 +1,14 @@
use std::{
sync::{
atomic::{AtomicBool, AtomicU32},
Arc,
mpsc, Arc, Mutex,
},
thread,
};
use nu_engine::{command_prelude::*, ClosureEvalOnce};
use nu_protocol::{
engine::{Closure, Job, Redirection, ThreadJob},
engine::{Closure, CurrentJob, Job, Mailbox, Redirection, ThreadJob},
report_shell_error, OutDest, Signals,
};
@ -57,12 +57,11 @@ impl Command for JobSpawn {
let closure: Closure = call.req(engine_state, stack, 0)?;
let tag: Option<String> = call.get_flag(engine_state, stack, "tag")?;
let job_stack = stack.clone();
let mut job_state = engine_state.clone();
job_state.is_interactive = false;
let job_stack = stack.clone();
// the new job should have its ctrl-c independent of foreground
let job_signals = Signals::new(Arc::new(AtomicBool::new(false)));
job_state.set_signals(job_signals.clone());
@ -75,10 +74,20 @@ impl Command for JobSpawn {
let jobs = job_state.jobs.clone();
let mut jobs = jobs.lock().expect("jobs lock is poisoned!");
let (send, recv) = mpsc::channel();
let id = {
let thread_job = ThreadJob::new(job_signals, tag);
job_state.current_thread_job = Some(thread_job.clone());
jobs.add_job(Job::Thread(thread_job))
let thread_job = ThreadJob::new(job_signals, tag, send);
let id = jobs.add_job(Job::Thread(thread_job.clone()));
job_state.current_job = CurrentJob {
id,
background_thread_job: Some(thread_job),
mailbox: Arc::new(Mutex::new(Mailbox::new(recv))),
};
id
};
let result = thread::Builder::new()

View File

@ -118,7 +118,7 @@ fn unfreeze_job(
}) => {
let pid = handle.pid();
if let Some(thread_job) = &state.current_thread_job {
if let Some(thread_job) = &state.current_thread_job() {
if !thread_job.try_add_pid(pid) {
kill_by_pid(pid.into()).map_err(|err| {
ShellError::Io(IoError::new_internal(
@ -136,7 +136,7 @@ fn unfreeze_job(
.then(|| state.pipeline_externals_state.clone()),
);
if let Some(thread_job) = &state.current_thread_job {
if let Some(thread_job) = &state.current_thread_job() {
thread_job.remove_pid(pid);
}

View File

@ -1,5 +1,6 @@
mod is_admin;
mod job;
mod job_id;
mod job_kill;
mod job_list;
mod job_spawn;
@ -8,12 +9,27 @@ mod job_tag;
#[cfg(all(unix, feature = "os"))]
mod job_unfreeze;
#[cfg(not(target_family = "wasm"))]
mod job_flush;
#[cfg(not(target_family = "wasm"))]
mod job_recv;
#[cfg(not(target_family = "wasm"))]
mod job_send;
pub use is_admin::IsAdmin;
pub use job::Job;
pub use job_id::JobId;
pub use job_kill::JobKill;
pub use job_list::JobList;
pub use job_spawn::JobSpawn;
pub use job_tag::JobTag;
#[cfg(not(target_family = "wasm"))]
pub use job_flush::JobFlush;
#[cfg(not(target_family = "wasm"))]
pub use job_recv::JobRecv;
#[cfg(not(target_family = "wasm"))]
pub use job_send::JobSend;
#[cfg(all(unix, feature = "os"))]
pub use job_unfreeze::JobUnfreeze;

View File

@ -34,7 +34,14 @@ impl Command for Open {
}
fn search_terms(&self) -> Vec<&str> {
vec!["load", "read", "load_file", "read_file"]
vec![
"load",
"read",
"load_file",
"read_file",
"cat",
"get-content",
]
}
fn signature(&self) -> nu_protocol::Signature {
@ -274,6 +281,16 @@ impl Command for Open {
example: r#"def "from ndjson" [] { from json -o }; open myfile.ndjson"#,
result: None,
},
Example {
description: "Show the extensions for which the `open` command will automatically parse",
example: r#"scope commands
| where name starts-with "from "
| insert extension { get name | str replace -r "^from " "" | $"*.($in)" }
| select extension name
| rename extension command
"#,
result: None,
}
]
}
}

View File

@ -263,6 +263,17 @@ impl Command for Save {
example: r#"do -i {} | save foo.txt --stderr bar.txt"#,
result: None,
},
Example {
description:
"Show the extensions for which the `save` command will automatically serialize",
example: r#"scope commands
| where name starts-with "to "
| insert extension { get name | str replace -r "^to " "" | $"*.($in)" }
| select extension name
| rename extension command
"#,
result: None,
},
]
}

View File

@ -15,17 +15,8 @@ pub fn empty(
if !columns.is_empty() {
for val in input {
for column in &columns {
let val = val.clone();
match val.follow_cell_path(&column.members, false) {
Ok(Value::Nothing { .. }) => {}
Ok(_) => {
if negate {
return Ok(Value::bool(true, head).into_pipeline_data());
} else {
return Ok(Value::bool(false, head).into_pipeline_data());
}
}
Err(err) => return Err(err),
if !val.follow_cell_path(&column.members, false)?.is_nothing() {
return Ok(Value::bool(negate, head).into_pipeline_data());
}
}
}

View File

@ -1,3 +1,5 @@
use std::borrow::Cow;
use nu_engine::command_prelude::*;
use nu_protocol::{ast::PathMember, Signals};
@ -188,9 +190,11 @@ fn action(
let input = input.into_value(span)?;
for path in paths {
let val = input.clone().follow_cell_path(&path.members, !sensitive);
output.push(val?);
output.push(
input
.follow_cell_path(&path.members, !sensitive)?
.into_owned(),
);
}
Ok(output.into_iter().into_pipeline_data(span, signals))
@ -223,10 +227,10 @@ pub fn follow_cell_path_into_stream(
.map(move |value| {
let span = value.span();
match value.follow_cell_path(&cell_path, insensitive) {
Ok(v) => v,
Err(error) => Value::error(error, span),
}
value
.follow_cell_path(&cell_path, insensitive)
.map(Cow::into_owned)
.unwrap_or_else(|error| Value::error(error, span))
})
.into_pipeline_data(head, signals);

View File

@ -322,11 +322,9 @@ fn group_cell_path(
let mut groups = IndexMap::<_, Vec<_>>::new();
for value in values.into_iter() {
let key = value
.clone()
.follow_cell_path(&column_name.members, false)?;
let key = value.follow_cell_path(&column_name.members, false)?;
if matches!(key, Value::Nothing { .. }) {
if key.is_nothing() {
continue; // likely the result of a failed optional access, ignore this value
}

View File

@ -1,3 +1,5 @@
use std::borrow::Cow;
use nu_engine::{command_prelude::*, ClosureEval, ClosureEvalOnce};
use nu_protocol::ast::PathMember;
@ -299,8 +301,8 @@ fn insert_value_by_closure(
) -> Result<(), ShellError> {
let value_at_path = if first_path_member_int {
value
.clone()
.follow_cell_path(cell_path, false)
.map(Cow::into_owned)
.unwrap_or(Value::nothing(span))
} else {
value.clone()
@ -319,8 +321,8 @@ fn insert_single_value_by_closure(
) -> Result<(), ShellError> {
let value_at_path = if first_path_member_int {
value
.clone()
.follow_cell_path(cell_path, false)
.map(Cow::into_owned)
.unwrap_or(Value::nothing(span))
} else {
value.clone()

View File

@ -229,45 +229,37 @@ fn select(
match v {
Value::List {
vals: input_vals, ..
} => {
Ok(input_vals
.into_iter()
.map(move |input_val| {
if !columns.is_empty() {
let mut record = Record::new();
for path in &columns {
//FIXME: improve implementation to not clone
match input_val.clone().follow_cell_path(&path.members, false) {
Ok(fetcher) => {
record.push(path.to_column_name(), fetcher);
}
Err(e) => return Value::error(e, call_span),
} => Ok(input_vals
.into_iter()
.map(move |input_val| {
if !columns.is_empty() {
let mut record = Record::new();
for path in &columns {
match input_val.follow_cell_path(&path.members, false) {
Ok(fetcher) => {
record.push(path.to_column_name(), fetcher.into_owned());
}
Err(e) => return Value::error(e, call_span),
}
Value::record(record, span)
} else {
input_val.clone()
}
})
.into_pipeline_data_with_metadata(
call_span,
engine_state.signals().clone(),
metadata,
))
}
Value::record(record, span)
} else {
input_val.clone()
}
})
.into_pipeline_data_with_metadata(
call_span,
engine_state.signals().clone(),
metadata,
)),
_ => {
if !columns.is_empty() {
let mut record = Record::new();
for cell_path in columns {
// FIXME: remove clone
match v.clone().follow_cell_path(&cell_path.members, false) {
Ok(result) => {
record.push(cell_path.to_column_name(), result);
}
Err(e) => return Err(e),
}
let result = v.follow_cell_path(&cell_path.members, false)?;
record.push(cell_path.to_column_name(), result.into_owned());
}
Ok(Value::record(record, call_span)
@ -278,31 +270,24 @@ fn select(
}
}
}
PipelineData::ListStream(stream, metadata, ..) => {
Ok(stream
.map(move |x| {
if !columns.is_empty() {
let mut record = Record::new();
for path in &columns {
//FIXME: improve implementation to not clone
match x.clone().follow_cell_path(&path.members, false) {
Ok(value) => {
record.push(path.to_column_name(), value);
}
Err(e) => return Value::error(e, call_span),
PipelineData::ListStream(stream, metadata, ..) => Ok(stream
.map(move |x| {
if !columns.is_empty() {
let mut record = Record::new();
for path in &columns {
match x.follow_cell_path(&path.members, false) {
Ok(value) => {
record.push(path.to_column_name(), value.into_owned());
}
Err(e) => return Value::error(e, call_span),
}
Value::record(record, call_span)
} else {
x
}
})
.into_pipeline_data_with_metadata(
call_span,
engine_state.signals().clone(),
metadata,
))
}
Value::record(record, call_span)
} else {
x
}
})
.into_pipeline_data_with_metadata(call_span, engine_state.signals().clone(), metadata)),
_ => Ok(PipelineData::empty()),
}
}

View File

@ -243,17 +243,17 @@ fn update_value_by_closure(
cell_path: &[PathMember],
first_path_member_int: bool,
) -> Result<(), ShellError> {
let value_at_path = value.clone().follow_cell_path(cell_path, false)?;
let value_at_path = value.follow_cell_path(cell_path, false)?;
let arg = if first_path_member_int {
&value_at_path
value_at_path.as_ref()
} else {
&*value
};
let new_value = closure
.add_arg(arg.clone())
.run_with_input(value_at_path.into_pipeline_data())?
.run_with_input(value_at_path.into_owned().into_pipeline_data())?
.into_value(span)?;
value.update_data_at_cell_path(cell_path, new_value)
@ -266,17 +266,17 @@ fn update_single_value_by_closure(
cell_path: &[PathMember],
first_path_member_int: bool,
) -> Result<(), ShellError> {
let value_at_path = value.clone().follow_cell_path(cell_path, false)?;
let value_at_path = value.follow_cell_path(cell_path, false)?;
let arg = if first_path_member_int {
&value_at_path
value_at_path.as_ref()
} else {
&*value
};
let new_value = closure
.add_arg(arg.clone())
.run_with_input(value_at_path.into_pipeline_data())?
.run_with_input(value_at_path.into_owned().into_pipeline_data())?
.into_value(span)?;
value.update_data_at_cell_path(cell_path, new_value)

View File

@ -1,3 +1,5 @@
use std::borrow::Cow;
use nu_engine::{command_prelude::*, ClosureEval, ClosureEvalOnce};
use nu_protocol::ast::PathMember;
@ -319,15 +321,19 @@ fn upsert_value_by_closure(
cell_path: &[PathMember],
first_path_member_int: bool,
) -> Result<(), ShellError> {
let value_at_path = value.clone().follow_cell_path(cell_path, false);
let value_at_path = value.follow_cell_path(cell_path, false);
let arg = if first_path_member_int {
value_at_path.clone().unwrap_or(Value::nothing(span))
value_at_path
.as_deref()
.cloned()
.unwrap_or(Value::nothing(span))
} else {
value.clone()
};
let input = value_at_path
.map(Cow::into_owned)
.map(IntoPipelineData::into_pipeline_data)
.unwrap_or(PipelineData::Empty);
@ -346,15 +352,19 @@ fn upsert_single_value_by_closure(
cell_path: &[PathMember],
first_path_member_int: bool,
) -> Result<(), ShellError> {
let value_at_path = value.clone().follow_cell_path(cell_path, false);
let value_at_path = value.follow_cell_path(cell_path, false);
let arg = if first_path_member_int {
value_at_path.clone().unwrap_or(Value::nothing(span))
value_at_path
.as_deref()
.cloned()
.unwrap_or(Value::nothing(span))
} else {
value.clone()
};
let input = value_at_path
.map(Cow::into_owned)
.map(IntoPipelineData::into_pipeline_data)
.unwrap_or(PipelineData::Empty);

View File

@ -117,8 +117,13 @@ impl Command for HttpDelete {
result: None,
},
Example {
description: "http delete from example.com, with custom header",
example: "http delete --headers [my-header-key my-header-value] https://www.example.com",
description: "http delete from example.com, with custom header using a record",
example: "http delete --headers {my-header-key: my-header-value} https://www.example.com",
result: None,
},
Example {
description: "http delete from example.com, with custom header using a list",
example: "http delete --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
result: None,
},
Example {

View File

@ -115,12 +115,12 @@ impl Command for HttpGet {
result: None,
},
Example {
description: "Get content from example.com, with custom header",
example: "http get --headers [my-header-key my-header-value] https://www.example.com",
description: "Get content from example.com, with custom header using a record",
example: "http get --headers {my-header-key: my-header-value} https://www.example.com",
result: None,
},
Example {
description: "Get content from example.com, with custom headers",
description: "Get content from example.com, with custom headers using a list",
example: "http get --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
result: None,
},

View File

@ -97,9 +97,15 @@ impl Command for HttpHead {
result: None,
},
Example {
description: "Get headers from example.com, with custom header",
description: "Get headers from example.com, with custom header using a record",
example:
"http head --headers [my-header-key my-header-value] https://www.example.com",
"http head --headers {my-header-key: my-header-value} https://www.example.com",
result: None,
},
Example {
description: "Get headers from example.com, with custom header using a list",
example:
"http head --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
result: None,
},
]

View File

@ -96,12 +96,12 @@ impl Command for HttpOptions {
result: None,
},
Example {
description: "Get options from example.com, with custom header",
example: "http options --headers [my-header-key my-header-value] https://www.example.com",
description: "Get options from example.com, with custom header using a record",
example: "http options --headers {my-header-key: my-header-value} https://www.example.com",
result: None,
},
Example {
description: "Get options from example.com, with custom headers",
description: "Get options from example.com, with custom headers using a list",
example: "http options --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
result: None,
},

View File

@ -114,9 +114,15 @@ impl Command for HttpPatch {
result: None,
},
Example {
description: "Patch content to example.com, with custom header",
description: "Patch content to example.com, with custom header using a record",
example:
"http patch --headers [my-header-key my-header-value] https://www.example.com",
"http patch --headers {my-header-key: my-header-value} https://www.example.com",
result: None,
},
Example {
description: "Patch content to example.com, with custom header using a list",
example:
"http patch --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
result: None,
},
Example {

View File

@ -113,8 +113,13 @@ impl Command for HttpPost {
result: None,
},
Example {
description: "Post content to example.com, with custom header",
example: "http post --headers [my-header-key my-header-value] https://www.example.com",
description: "Post content to example.com, with custom header using a record",
example: "http post --headers {my-header-key: my-header-value} https://www.example.com",
result: None,
},
Example {
description: "Post content to example.com, with custom header using a list",
example: "http post --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
result: None,
},
Example {

View File

@ -113,8 +113,13 @@ impl Command for HttpPut {
result: None,
},
Example {
description: "Put content to example.com, with custom header",
example: "http put --headers [my-header-key my-header-value] https://www.example.com",
description: "Put content to example.com, with custom header using a record",
example: "http put --headers {my-header-key: my-header-value} https://www.example.com",
result: None,
},
Example {
description: "Put content to example.com, with custom header using a list",
example: "http put --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
result: None,
},
Example {

View File

@ -89,8 +89,7 @@ impl Command for InputList {
.into_iter()
.map(move |val| {
let display_value = if let Some(ref cellpath) = display_path {
val.clone()
.follow_cell_path(&cellpath.members, false)?
val.follow_cell_path(&cellpath.members, false)?
.to_expanded_string(", ", &config)
} else {
val.to_expanded_string(", ", &config)

View File

@ -239,8 +239,8 @@ pub fn compare_cell_path(
insensitive: bool,
natural: bool,
) -> Result<Ordering, ShellError> {
let left = left.clone().follow_cell_path(&cell_path.members, false)?;
let right = right.clone().follow_cell_path(&cell_path.members, false)?;
let left = left.follow_cell_path(&cell_path.members, false)?;
let right = right.follow_cell_path(&cell_path.members, false)?;
compare_values(&left, &right, insensitive, natural)
}

View File

@ -61,6 +61,14 @@ impl Command for FormatDate {
Span::test_data(),
)),
},
Example {
description: "Format a given date-time according to the RFC 3339 standard.",
example: r#"'2021-10-22 20:00:12 +01:00' | into datetime | format date "%+""#,
result: Some(Value::string(
"2021-10-22T20:00:12+01:00".to_string(),
Span::test_data(),
)),
},
Example {
description: "Format the current date-time using a given format string.",
example: r#"date now | format date "%Y-%m-%d %H:%M:%S""#,

View File

@ -376,6 +376,20 @@ mod tests {
);
}
#[test]
fn test_numbers_proceeding_escape_char_not_ignored() {
assert_eq!(
str_expand("1\\\\{a,b}", Span::test_data(), Span::test_data()),
Value::list(
vec![
Value::string(String::from("1\\a"), Span::test_data(),),
Value::string(String::from("1\\b"), Span::test_data(),)
],
Span::test_data(),
)
);
}
#[test]
fn test_examples() {
use crate::test_examples;

View File

@ -285,7 +285,7 @@ impl Command for External {
)
})?;
if let Some(thread_job) = &engine_state.current_thread_job {
if let Some(thread_job) = engine_state.current_thread_job() {
if !thread_job.try_add_pid(child.pid()) {
kill_by_pid(child.pid().into()).map_err(|err| {
ShellError::Io(IoError::new_internal(

View File

@ -5,6 +5,7 @@
use std::{collections::VecDeque, io::Read, path::PathBuf, str::FromStr};
use lscolors::{LsColors, Style};
use tabled::grid::config::Position;
use url::Url;
use web_time::Instant;
@ -17,8 +18,8 @@ use nu_protocol::{
Signals, TableMode, ValueIterator,
};
use nu_table::{
common::configure_table, CollapsedTable, ExpandedTable, JustTable, NuRecordsValue, NuTable,
StringResult, TableOpts, TableOutput,
common::configure_table, CollapsedTable, ExpandedTable, JustTable, NuTable, StringResult,
TableOpts, TableOutput,
};
use nu_utils::{get_ls_colors, terminal_size};
@ -609,7 +610,7 @@ fn build_table_kv(
span: Span,
) -> StringResult {
match table_view {
TableView::General => JustTable::kv_table(&record, opts),
TableView::General => JustTable::kv_table(record, opts),
TableView::Expanded {
limit,
flatten,
@ -645,7 +646,7 @@ fn build_table_batch(
}
match view {
TableView::General => JustTable::table(&vals, opts),
TableView::General => JustTable::table(vals, opts),
TableView::Expanded {
limit,
flatten,
@ -1090,9 +1091,9 @@ fn create_empty_placeholder(
return String::new();
}
let cell = NuRecordsValue::new(format!("empty {}", value_type_name));
let data = vec![vec![cell]];
let mut table = NuTable::from(data);
let cell = format!("empty {}", value_type_name);
let mut table = NuTable::new(1, 1);
table.insert(Position::new(0, 0), cell);
table.set_data_style(TextStyle::default().dimmed());
let mut out = TableOutput::from_table(table, false, false);
@ -1153,6 +1154,7 @@ fn supported_table_modes() -> Vec<Value> {
Value::test_string("restructured"),
Value::test_string("ascii_rounded"),
Value::test_string("basic_compact"),
Value::test_string("single"),
]
}

View File

@ -1,22 +1,188 @@
use nu_test_support::{nu, playground::Playground};
use nu_test_support::nu;
#[test]
fn jobs_do_run() {
Playground::setup("job_test_1", |dirs, sandbox| {
sandbox.with_files(&[]);
fn job_send_root_job_works() {
let actual = nu!(r#"
job spawn { 'beep' | job send 0 }
job recv --timeout 10sec"#);
let actual = nu!(
cwd: dirs.root(),
r#"
rm -f a.txt;
job spawn { sleep 200ms; 'a' | save a.txt };
let before = 'a.txt' | path exists;
sleep 400ms;
let after = 'a.txt' | path exists;
[$before, $after] | to nuon"#
);
assert_eq!(actual.out, "[false, true]");
})
assert_eq!(actual.out, "beep");
}
#[test]
fn job_send_background_job_works() {
let actual = nu!(r#"
let job = job spawn { job recv | job send 0 }
'boop' | job send $job
job recv --timeout 10sec"#);
assert_eq!(actual.out, "boop");
}
#[test]
fn job_send_to_self_works() {
let actual = nu!(r#"
"meep" | job send 0
job recv"#);
assert_eq!(actual.out, "meep");
}
#[test]
fn job_send_to_self_from_background_works() {
let actual = nu!(r#"
job spawn {
'beep' | job send (job id)
job recv | job send 0
}
job recv --timeout 10sec"#);
assert_eq!(actual.out, "beep");
}
#[test]
fn job_id_of_root_job_is_zero() {
let actual = nu!(r#"job id"#);
assert_eq!(actual.out, "0");
}
#[test]
fn job_id_of_background_jobs_works() {
let actual = nu!(r#"
let job1 = job spawn { job id | job send 0 }
let id1 = job recv --timeout 5sec
let job2 = job spawn { job id | job send 0 }
let id2 = job recv --timeout 5sec
let job3 = job spawn { job id | job send 0 }
let id3 = job recv --timeout 5sec
[($job1 == $id1) ($job2 == $id2) ($job3 == $id3)] | to nuon
"#);
assert_eq!(actual.out, "[true, true, true]");
}
#[test]
fn untagged_job_recv_accepts_tagged_messages() {
let actual = nu!(r#"
job spawn { "boop" | job send 0 --tag 123 }
job recv --timeout 10sec
"#);
assert_eq!(actual.out, "boop");
}
#[test]
fn tagged_job_recv_filters_untagged_messages() {
let actual = nu!(r#"
job spawn { "boop" | job send 0 }
job recv --tag 123 --timeout 1sec
"#);
assert_eq!(actual.out, "");
assert!(actual.err.contains("timeout"));
}
#[test]
fn tagged_job_recv_filters_badly_tagged_messages() {
let actual = nu!(r#"
job spawn { "boop" | job send 0 --tag 321 }
job recv --tag 123 --timeout 1sec
"#);
assert_eq!(actual.out, "");
assert!(actual.err.contains("timeout"));
}
#[test]
fn tagged_job_recv_accepts_properly_tagged_messages() {
let actual = nu!(r#"
job spawn { "boop" | job send 0 --tag 123 }
job recv --tag 123 --timeout 5sec
"#);
assert_eq!(actual.out, "boop");
}
#[test]
fn filtered_messages_are_not_erased() {
let actual = nu!(r#"
"msg1" | job send 0 --tag 123
"msg2" | job send 0 --tag 456
"msg3" | job send 0 --tag 789
let first = job recv --tag 789 --timeout 5sec
let second = job recv --timeout 1sec
let third = job recv --timeout 1sec
[($first) ($second) ($third)] | to nuon
"#);
assert_eq!(actual.out, r#"["msg3", "msg1", "msg2"]"#);
}
#[test]
fn job_recv_timeout_works() {
let actual = nu!(r#"
job spawn {
sleep 2sec
"boop" | job send 0
}
job recv --timeout 1sec
"#);
assert_eq!(actual.out, "");
assert!(actual.err.contains("timeout"));
}
#[test]
fn job_recv_timeout_zero_works() {
let actual = nu!(r#"
"hi there" | job send 0
job recv --timeout 0sec
"#);
assert_eq!(actual.out, "hi there");
}
#[test]
fn job_flush_clears_messages() {
let actual = nu!(r#"
"SALE!!!" | job send 0
"[HYPERLINK BLOCKED]" | job send 0
job flush
job recv --timeout 1sec
"#);
assert_eq!(actual.out, "");
assert!(actual.err.contains("timeout"));
}
#[test]
fn job_flush_clears_filtered_messages() {
let actual = nu!(r#"
"msg1" | job send 0 --tag 123
"msg2" | job send 0 --tag 456
"msg3" | job send 0 --tag 789
job recv --tag 789 --timeout 1sec
job flush
job recv --timeout 1sec
"#);
assert_eq!(actual.out, "");
assert!(actual.err.contains("timeout"));
}
#[test]
@ -31,11 +197,11 @@ fn job_list_adds_jobs_correctly() {
let actual = nu!(format!(
r#"
let list0 = job list | get id;
let job1 = job spawn {{ sleep 20ms }};
let job1 = job spawn {{ job recv }};
let list1 = job list | get id;
let job2 = job spawn {{ sleep 20ms }};
let job2 = job spawn {{ job recv }};
let list2 = job list | get id;
let job3 = job spawn {{ sleep 20ms }};
let job3 = job spawn {{ job recv }};
let list3 = job list | get id;
[({}), ({}), ({}), ({})] | to nuon
"#,
@ -52,11 +218,13 @@ fn job_list_adds_jobs_correctly() {
fn jobs_get_removed_from_list_after_termination() {
let actual = nu!(format!(
r#"
let job = job spawn {{ sleep 0.5sec }};
let job = job spawn {{ job recv }};
let list0 = job list | get id;
sleep 1sec
"die!" | job send $job
sleep 0.2sec
let list1 = job list | get id;
@ -68,6 +236,8 @@ fn jobs_get_removed_from_list_after_termination() {
assert_eq!(actual.out, "[true, true]");
}
// TODO: find way to communicate between process in windows
// so these tests can fail less often
#[test]
fn job_list_shows_pids() {
let actual = nu!(format!(
@ -89,9 +259,9 @@ fn job_list_shows_pids() {
fn killing_job_removes_it_from_table() {
let actual = nu!(format!(
r#"
let job1 = job spawn {{ sleep 100ms }}
let job2 = job spawn {{ sleep 100ms }}
let job3 = job spawn {{ sleep 100ms }}
let job1 = job spawn {{ job recv }}
let job2 = job spawn {{ job recv }}
let job3 = job spawn {{ job recv }}
let list_before = job list | get id

View File

@ -1337,15 +1337,17 @@ fn test_expand_big_0() {
"│ target │ {record 3 fields} │",
"│ dev-dependencies │ {record 9 fields} │",
"│ features │ {record 8 fields} │",
"│ │ ╭───┬─────┬─────╮ │",
"│ bin │ │ # │ nam │ pat │ │",
"│ │ │ │ e │ h │ │",
"│ │ ├───┼─────┼─────┤ │",
"│ │ │ 0 │ nu │ src │ │",
"│ │ │ │ │ /ma │ │",
"│ │ │ │ │ in. │ │",
"│ │ │ │ │ rs │ │",
"│ │ ╰───┴─────┴─────╯ │",
"│ │ ╭───┬──────┬────╮ │",
"│ bin │ │ # │ name │ pa │ │",
"│ │ │ │ │ th │ │",
"│ │ ├───┼──────┼────┤ │",
"│ │ │ 0 │ nu │ sr │ │",
"│ │ │ │ │ c/ │ │",
"│ │ │ │ │ ma │ │",
"│ │ │ │ │ in │ │",
"│ │ │ │ │ .r │ │",
"│ │ │ │ │ s │ │",
"│ │ ╰───┴──────┴────╯ │",
"│ │ ╭───────────┬───╮ │",
"│ patch │ │ crates-io │ { │ │",
"│ │ │ │ r │ │",
@ -1364,16 +1366,16 @@ fn test_expand_big_0() {
"│ │ │ │ d │ │",
"│ │ │ │ } │ │",
"│ │ ╰───────────┴───╯ │",
"│ │ ╭───┬──────────╮ │",
"│ bench │ │ # │ nam │ har │ │",
"│ │ │ │ e │ nes │ │",
"│ │ │ │ │ s │ │",
"│ │ ├───┼─────┼─────┤",
"│ │ │ 0 │ ben │ fal │",
"│ │ │ │ chm │ se │ │",
"│ │ │ │ ark │ │ │",
"│ │ │ │ s │ │ │",
"│ │ ╰───┴──────────╯ │",
"│ │ ╭───┬─────────╮ │",
"│ bench │ │ # │ name │ ha │ │",
"│ │ │ │ │ rn │ │",
"│ │ │ │ es │ │",
"│ │ │ │ │ s │",
"│ │ ├───┼──────┼────┤",
"│ │ │ 0 │ benc │ fa │ │",
"│ │ │ │ hmar │ ls │ │",
"│ │ │ │ ks │ e │ │",
"│ │ ╰───┴─────────╯ │",
"╰──────────────────┴───────────────────╯",
]);
@ -1555,190 +1557,114 @@ fn table_expande_with_no_header_internally_0() {
"│ │ │ │ │ │ ╰─────┴──────────╯ │ │ │",
"│ │ │ │ │ display_output │ │ │ │",
"│ │ │ │ ╰────────────────┴────────────────────╯ │ │",
"│ │ │ │ ╭───┬───────────────────────────┬────────────────────────┬────────┬───┬─────╮ │ │",
"│ │ │ menus │ │ # │ name │ only_buffer_difference │ marker │ t │ ... │ │ │",
"│ │ │ │ │ │ │ │ │ y │ │ │ │",
"│ │ │ │ │ │ │ │ │ p │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ ├───┼───────────────────────────┼────────────────────────┼────────┼───┼─────┤ │ │",
"│ │ │ │ │ 0 │ completion_menu │ false │ | │ { │ ... │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ c │ │ │ │",
"│ │ │ │ │ │ │ │ │ o │ │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ 4 │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ f │ │ │ │",
"│ │ │ │ │ │ │ │ │ i │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ l │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ s │ │ │ │",
"│ │ │ │ │ │ │ │ │ } │ │ │ │",
"│ │ │ │ │ 1 │ history_menu │ true │ ? │ { │ ... │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ c │ │ │ │",
"│ │ │ │ │ │ │ │ │ o │ │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ 2 │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ f │ │ │ │",
"│ │ │ │ │ │ │ │ │ i │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ l │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ s │ │ │ │",
"│ │ │ │ │ │ │ │ │ } │ │ │ │",
"│ │ │ │ │ 2 │ help_menu │ true │ ? │ { │ ... │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ c │ │ │ │",
"│ │ │ │ │ │ │ │ │ o │ │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ 6 │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ f │ │ │ │",
"│ │ │ │ │ │ │ │ │ i │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ l │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ s │ │ │ │",
"│ │ │ │ │ │ │ │ │ } │ │ │ │",
"│ │ │ │ │ 3 │ commands_menu │ false │ # │ { │ ... │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ c │ │ │ │",
"│ │ │ │ │ │ │ │ │ o │ │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ 4 │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ f │ │ │ │",
"│ │ │ │ │ │ │ │ │ i │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ l │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ s │ │ │ │",
"│ │ │ │ │ │ │ │ │ } │ │ │ │",
"│ │ │ │ │ 4 │ vars_menu │ true │ # │ { │ ... │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ c │ │ │ │",
"│ │ │ │ │ │ │ │ │ o │ │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ 2 │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ f │ │ │ │",
"│ │ │ │ │ │ │ │ │ i │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ l │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ s │ │ │ │",
"│ │ │ │ │ │ │ │ │ } │ │ │ │",
"│ │ │ │ │ 5 │ commands_with_description │ true │ # │ { │ ... │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ c │ │ │ │",
"│ │ │ │ │ │ │ │ │ o │ │ │ │",
"│ │ │ │ │ │ │ │ │ r │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ 6 │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ f │ │ │ │",
"│ │ │ │ │ │ │ │ │ i │ │ │ │",
"│ │ │ │ │ │ │ │ │ e │ │ │ │",
"│ │ │ │ │ │ │ │ │ l │ │ │ │",
"│ │ │ │ │ │ │ │ │ d │ │ │ │",
"│ │ │ │ │ │ │ │ │ s │ │ │ │",
"│ │ │ │ │ │ │ │ │ } │ │ │ │",
"│ │ │ │ ╰───┴───────────────────────────┴────────────────────────┴────────┴───┴─────╯ │ │",
"│ │ │ │ ╭────┬───────────────────────────┬──────────┬─────────┬───────────────┬─────╮ │ │",
"│ │ │ keybindings │ │ # │ name │ modifier │ keycode │ mode │ eve │ │ │",
"│ │ │ │ │ │ │ │ │ │ nt │ │ │",
"│ │ │ │ ├────┼───────────────────────────┼──────────┼─────────┼───────────────┼─────┤ │ │",
"│ │ │ │ │ 0 │ completion_menu │ none │ tab │ ╭───┬───────╮ │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ ld} │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
"│ │ │ │ │ 1 │ completion_previous │ shift │ backtab │ ╭───┬───────╮ │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ ld} │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
"│ │ │ │ │ 2 │ history_menu │ control │ char_r │ emacs │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ lds │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 3 │ next_page │ control │ char_x │ emacs │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ ld} │ │ │",
"│ │ │ │ │ 4 │ undo_or_previous_page │ control │ char_z │ emacs │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ ld} │ │ │",
"│ │ │ │ │ 5 │ yank │ control │ char_y │ emacs │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ ld} │ │ │",
"│ │ │ │ │ 6 │ unix-line-discard │ control │ char_u │ ╭───┬───────╮ │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ ld} │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
"│ │ │ │ │ 7 │ kill-line │ control │ char_k │ ╭───┬───────╮ │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ ld} │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
"│ │ │ │ │ 8 │ commands_menu │ control │ char_t │ ╭───┬───────╮ │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ lds │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ } │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
"│ │ │ │ │ 9 │ vars_menu │ alt │ char_o │ ╭───┬───────╮ │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ lds │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ } │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
"│ │ │ │ │ 10 │ commands_with_description │ control │ char_s │ ╭───┬───────╮ │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_no │ │ d 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ rmal │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_in │ │ lds │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ sert │ │ } │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │",
"│ │ │ │ ╰────┴───────────────────────────┴──────────┴─────────┴───────────────┴─────╯ │ │",
"│ │ │ │ ╭───┬───────────────────────────┬────────────────────────┬────────┬─────╮ │ │",
"│ │ │ menus │ │ # │ name │ only_buffer_difference │ marker │ ... │ │ │",
"│ │ │ │ ├───┼───────────────────────────┼────────────────────────┼────────┼─────┤ │ │",
"│ │ │ │ │ 0 │ completion_menu │ false │ | │ ... │ │ │",
"│ │ │ │ │ 1 │ history_menu │ true │ ? │ ... │ │ │",
"│ │ │ │ │ 2 │ help_menu │ true │ ? │ ... │ │ │",
"│ │ │ │ │ 3 │ commands_menu │ false │ # │ ... │ │ │",
"│ │ │ │ │ 4 │ vars_menu │ true │ # │ ... │ │ │",
"│ │ │ │ │ 5 │ commands_with_description │ true │ # │ ... │ │ │",
"│ │ │ │ ╰───┴───────────────────────────┴────────────────────────┴────────┴─────╯ │ │",
"│ │ │ │ ╭────┬───────────────────────────┬──────────┬─────────┬────────────────┬────╮ │ │",
"│ │ │ keybindings │ │ # │ name │ modifier │ keycode │ mode │ ev │ │ │",
"│ │ │ │ │ │ │ │ │ │ en │ │ │",
"│ │ │ │ │ │ │ │ │ │ t │ │ │",
"│ │ │ │ ├────┼───────────────────────────┼──────────┼─────────┼────────────────┼────┤ │ │",
"│ │ │ │ │ 0 │ completion_menu │ none │ tab │ ╭───┬────────╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 1 │ completion_previous │ shift │ backtab │ ╭───┬────────╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 2 │ history_menu │ control │ char_r │ emacs │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ ds │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 3 │ next_page │ control │ char_x │ emacs │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 4 │ undo_or_previous_page │ control │ char_z │ emacs │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 5 │ yank │ control │ char_y │ emacs │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 6 │ unix-line-discard │ control │ char_u │ ╭───┬────────╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 7 │ kill-line │ control │ char_k │ ╭───┬────────╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 8 │ commands_menu │ control │ char_t │ ╭───┬────────╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ ds │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 9 │ vars_menu │ alt │ char_o │ ╭───┬────────╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ ds │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 10 │ commands_with_description │ control │ char_s │ ╭───┬────────╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ emacs │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ vi_nor │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ mal │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ vi_ins │ │ 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ ert │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴────────╯ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ ds │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ ╰────┴───────────────────────────┴──────────┴─────────┴────────────────┴────╯ │ │",
"│ │ ╰──────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────╯ │",
"╰────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯",
])
@ -1930,70 +1856,220 @@ fn table_expande_with_no_header_internally_1() {
"│ │ │ │ │ 4 │ vars_menu │ true │ # │ ... │ │ │",
"│ │ │ │ │ 5 │ commands_with_description │ true │ # │ ... │ │ │",
"│ │ │ │ ╰───┴───────────────────────────┴────────────────────────┴───────┴─────╯ │ │",
"│ │ │ │ ╭────┬───────────────────────────┬──────────┬─────────┬──────────┬─────╮ │ │",
"│ │ │ keybindings │ │ # │ name │ modifier │ keycode │ mode │ eve │ │ │",
"│ │ │ │ │ │ │ │ │ │ nt │ │ │",
"│ │ │ │ ├────┼───────────────────────────┼──────────┼─────────┼──────────┼─────┤ │ │",
"│ │ │ │ │ 0 │ completion_menu │ none │ tab │ [list 3 │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ ld} │ │ │",
"│ │ │ │ │ 1 │ completion_previous │ shift │ backtab │ [list 3 │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ ld} │ │ │",
"│ │ │ │ │ 2 │ history_menu │ control │ char_r │ emacs │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ lds │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 3 │ next_page │ control │ char_x │ emacs │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ ld} │ │ │",
"│ │ │ │ │ 4 │ undo_or_previous_page │ control │ char_z │ emacs │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ ld} │ │ │",
"│ │ │ │ │ 5 │ yank │ control │ char_y │ emacs │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ ld} │ │ │",
"│ │ │ │ │ 6 │ unix-line-discard │ control │ char_u │ [list 3 │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ ld} │ │ │",
"│ │ │ │ │ 7 │ kill-line │ control │ char_k │ [list 3 │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ ld} │ │ │",
"│ │ │ │ │ 8 │ commands_menu │ control │ char_t │ [list 3 │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ lds │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 9 │ vars_menu │ alt │ char_o │ [list 3 │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ lds │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 10 │ commands_with_description │ control │ char_s │ [list 3 │ {re │ │ │",
"│ │ │ │ │ │ │ │ │ items] │ cor │ │ │",
"│ │ │ │ │ │ │ │ │ │ d 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fie │ │ │",
"│ │ │ │ │ │ │ │ │ │ lds │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ ╰────┴───────────────────────────┴──────────┴─────────┴──────────┴─────╯ │ │",
"│ │ │ │ ╭────┬───────────────────────────┬──────────┬─────────┬───────────┬────╮ │ │",
"│ │ │ keybindings │ │ # │ name │ modifier │ keycode │ mode │ ev │ │ │",
"│ │ │ │ │ │ │ │ │ │ en │ │ │",
"│ │ │ │ │ │ │ │ │ │ t │ │ │",
"│ │ │ │ ├────┼───────────────────────────┼──────────┼─────────┼───────────┼────┤ │ │",
"│ │ │ │ │ 0 │ completion_menu │ none │ tab │ ╭───┬───╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ d} │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
"│ │ │ │ │ 1 │ completion_previous │ shift │ backtab │ ╭───┬───╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ d} │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
"│ │ │ │ │ 2 │ history_menu │ control │ char_r │ emacs │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ ds │ │ │",
"│ │ │ │ │ │ │ │ │ │ } │ │ │",
"│ │ │ │ │ 3 │ next_page │ control │ char_x │ emacs │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 4 │ undo_or_previous_page │ control │ char_z │ emacs │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 5 │ yank │ control │ char_y │ emacs │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ d} │ │ │",
"│ │ │ │ │ 6 │ unix-line-discard │ control │ char_u │ ╭───┬───╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ d} │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
"│ │ │ │ │ 7 │ kill-line │ control │ char_k │ ╭───┬───╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 1 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ d} │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
"│ │ │ │ │ 8 │ commands_menu │ control │ char_t │ ╭───┬───╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ ds │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ } │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
"│ │ │ │ │ 9 │ vars_menu │ alt │ char_o │ ╭───┬───╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ ds │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ } │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
"│ │ │ │ │ 10 │ commands_with_description │ control │ char_s │ ╭───┬───╮ │ {r │ │ │",
"│ │ │ │ │ │ │ │ │ │ 0 │ e │ │ ec │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ or │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ d │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ c │ │ 2 │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ fi │ │ │",
"│ │ │ │ │ │ │ │ │ │ 1 │ v │ │ el │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ ds │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ } │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ o │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ m │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ a │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ l │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ 2 │ v │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ _ │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ i │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ n │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ s │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ e │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ r │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ │ │ t │ │ │ │ │",
"│ │ │ │ │ │ │ │ │ ╰───┴───╯ │ │ │ │",
"│ │ │ │ ╰────┴───────────────────────────┴──────────┴─────────┴───────────┴────╯ │ │",
"│ │ ╰──────────────────────────────────┴──────────────────────────────────────────────────────────────────────────╯ │",
"╰────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯",
])
@ -3103,13 +3179,13 @@ fn table_expand_index_arg() {
#[test]
fn table_list() {
let actual = nu!("table --list");
assert_eq!(actual.out, "╭────┬────────────────╮│ 0 │ basic ││ 1 │ compact ││ 2 │ compact_double ││ 3 │ default ││ 4 │ heavy ││ 5 │ light ││ 6 │ none ││ 7 │ reinforced ││ 8 │ rounded ││ 9 │ thin ││ 10 │ with_love ││ 11 │ psql ││ 12 │ markdown ││ 13 │ dots ││ 14 │ restructured ││ 15 │ ascii_rounded ││ 16 │ basic_compact │╰────┴────────────────╯");
assert_eq!(actual.out, "╭────┬────────────────╮│ 0 │ basic ││ 1 │ compact ││ 2 │ compact_double ││ 3 │ default ││ 4 │ heavy ││ 5 │ light ││ 6 │ none ││ 7 │ reinforced ││ 8 │ rounded ││ 9 │ thin ││ 10 │ with_love ││ 11 │ psql ││ 12 │ markdown ││ 13 │ dots ││ 14 │ restructured ││ 15 │ ascii_rounded ││ 16 │ basic_compact ││ 17 │ single │╰────┴────────────────╯");
let actual = nu!("ls | table --list");
assert_eq!(actual.out, "╭────┬────────────────╮│ 0 │ basic ││ 1 │ compact ││ 2 │ compact_double ││ 3 │ default ││ 4 │ heavy ││ 5 │ light ││ 6 │ none ││ 7 │ reinforced ││ 8 │ rounded ││ 9 │ thin ││ 10 │ with_love ││ 11 │ psql ││ 12 │ markdown ││ 13 │ dots ││ 14 │ restructured ││ 15 │ ascii_rounded ││ 16 │ basic_compact │╰────┴────────────────╯");
assert_eq!(actual.out, "╭────┬────────────────╮│ 0 │ basic ││ 1 │ compact ││ 2 │ compact_double ││ 3 │ default ││ 4 │ heavy ││ 5 │ light ││ 6 │ none ││ 7 │ reinforced ││ 8 │ rounded ││ 9 │ thin ││ 10 │ with_love ││ 11 │ psql ││ 12 │ markdown ││ 13 │ dots ││ 14 │ restructured ││ 15 │ ascii_rounded ││ 16 │ basic_compact ││ 17 │ single │╰────┴────────────────╯");
let actual = nu!("table --list --theme basic");
assert_eq!(actual.out, "╭────┬────────────────╮│ 0 │ basic ││ 1 │ compact ││ 2 │ compact_double ││ 3 │ default ││ 4 │ heavy ││ 5 │ light ││ 6 │ none ││ 7 │ reinforced ││ 8 │ rounded ││ 9 │ thin ││ 10 │ with_love ││ 11 │ psql ││ 12 │ markdown ││ 13 │ dots ││ 14 │ restructured ││ 15 │ ascii_rounded ││ 16 │ basic_compact │╰────┴────────────────╯");
assert_eq!(actual.out, "╭────┬────────────────╮│ 0 │ basic ││ 1 │ compact ││ 2 │ compact_double ││ 3 │ default ││ 4 │ heavy ││ 5 │ light ││ 6 │ none ││ 7 │ reinforced ││ 8 │ rounded ││ 9 │ thin ││ 10 │ with_love ││ 11 │ psql ││ 12 │ markdown ││ 13 │ dots ││ 14 │ restructured ││ 15 │ ascii_rounded ││ 16 │ basic_compact ││ 17 │ single │╰────┴────────────────╯");
}
#[test]
@ -3352,3 +3428,36 @@ fn table_expand_big_header() {
"
);
}
#[test]
fn table_missing_value() {
let actual = nu!(r###"[{foo: null} {} {}] | table"###);
assert_eq!(
actual.out,
"╭───┬─────╮\
# foo \
\
0 \
1 \
2 \
",
)
}
#[test]
fn table_missing_value_custom() {
let actual = nu!(r###"
$env.config.table.missing_value_symbol = "NULL";
[{foo: null} {} {}] | table
"###);
assert_eq!(
actual.out,
"╭───┬──────╮\
# foo \
\
0 \
1 NULL \
2 NULL \
",
)
}

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT"
name = "nu-derive-value"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-derive-value"
version = "0.103.1"
version = "0.104.1"
[lib]
proc-macro = true

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-engine"
edition = "2021"
license = "MIT"
name = "nu-engine"
version = "0.103.1"
version = "0.104.1"
[lib]
bench = false
@ -14,10 +14,10 @@ bench = false
workspace = true
[dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.103.1", default-features = false }
nu-path = { path = "../nu-path", version = "0.103.1" }
nu-glob = { path = "../nu-glob", version = "0.103.1" }
nu-utils = { path = "../nu-utils", version = "0.103.1", default-features = false }
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false }
nu-path = { path = "../nu-path", version = "0.104.1" }
nu-glob = { path = "../nu-glob", version = "0.104.1" }
nu-utils = { path = "../nu-utils", version = "0.104.1", default-features = false }
log = { workspace = true }
[features]

View File

@ -70,6 +70,8 @@ pub(crate) fn compile_binary_op(
Boolean::Xor => unreachable!(),
};
// Before match against lhs_reg, it's important to collect it first to get a concrete value if there is a subexpression.
builder.push(Instruction::Collect { src_dst: lhs_reg }.into_spanned(lhs.span))?;
// Short-circuit to return `lhs_reg`. `match` op does not consume `lhs_reg`.
let short_circuit_label = builder.label(None);
builder.r#match(

View File

@ -269,6 +269,7 @@ pub fn eval_expression_with_input<D: DebugContext>(
input = eval_subexpression::<D>(engine_state, stack, block, input)?
.into_value(*span)?
.follow_cell_path(&full_cell_path.tail, false)?
.into_owned()
.into_pipeline_data()
} else {
input = eval_subexpression::<D>(engine_state, stack, block, input)?;
@ -604,7 +605,7 @@ impl Eval for EvalRuntime {
let is_config = original_key == "config";
stack.add_env_var(original_key, value);
stack.add_env_var(original_key, value.into_owned());
// Trigger the update to config, if we modified that.
if is_config {

View File

@ -694,9 +694,8 @@ fn eval_instruction<D: DebugContext>(
let value = ctx.clone_reg_value(*src, *span)?;
let path = ctx.take_reg(*path);
if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path {
// TODO: make follow_cell_path() not have to take ownership, probably using Cow
let value = value.follow_cell_path(&path.members, true)?;
ctx.put_reg(*dst, value.into_pipeline_data());
ctx.put_reg(*dst, value.into_owned().into_pipeline_data());
Ok(Continue)
} else if let PipelineData::Value(Value::Error { error, .. }, _) = path {
Err(*error)

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-explore"
edition = "2021"
license = "MIT"
name = "nu-explore"
version = "0.103.1"
version = "0.104.1"
[lib]
bench = false
@ -14,16 +14,16 @@ bench = false
workspace = true
[dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.103.1" }
nu-parser = { path = "../nu-parser", version = "0.103.1" }
nu-path = { path = "../nu-path", version = "0.103.1" }
nu-color-config = { path = "../nu-color-config", version = "0.103.1" }
nu-engine = { path = "../nu-engine", version = "0.103.1" }
nu-table = { path = "../nu-table", version = "0.103.1" }
nu-json = { path = "../nu-json", version = "0.103.1" }
nu-utils = { path = "../nu-utils", version = "0.103.1" }
nu-protocol = { path = "../nu-protocol", version = "0.104.1" }
nu-parser = { path = "../nu-parser", version = "0.104.1" }
nu-path = { path = "../nu-path", version = "0.104.1" }
nu-color-config = { path = "../nu-color-config", version = "0.104.1" }
nu-engine = { path = "../nu-engine", version = "0.104.1" }
nu-table = { path = "../nu-table", version = "0.104.1" }
nu-json = { path = "../nu-json", version = "0.104.1" }
nu-utils = { path = "../nu-utils", version = "0.104.1" }
nu-ansi-term = { workspace = true }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.103.1" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.104.1" }
anyhow = { workspace = true }
log = { workspace = true }

View File

@ -38,6 +38,7 @@ Drill down into records+tables: Press <Enter> to select a cell, move around wit
Expand (show all nested data): Press "e"
Open this help page : Type ":help" then <Enter>
Open an interactive REPL: Type ":try" then <Enter>
Run a Nushell command: Type ":nu <command>" then <Enter>. The data currently being explored is piped into it.
Scroll up: Press "Page Up", Ctrl+B, or Alt+V
Scroll down: Press "Page Down", Ctrl+F, or Ctrl+V
Exit Explore: Type ":q" then <Enter>, or Ctrl+D. Alternately, press <Esc> or "q" until Explore exits

View File

@ -1,6 +1,6 @@
[package]
name = "nu-glob"
version = "0.103.1"
version = "0.104.1"
authors = ["The Nushell Project Developers", "The Rust Project Developers"]
license = "MIT/Apache-2.0"
description = """

View File

@ -8,7 +8,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-json"
edition = "2021"
license = "MIT"
name = "nu-json"
version = "0.103.1"
version = "0.104.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -26,8 +26,8 @@ serde = { workspace = true }
serde_json = { workspace = true }
[dev-dependencies]
nu-test-support = { path = "../nu-test-support", version = "0.103.1" }
nu-path = { path = "../nu-path", version = "0.103.1" }
nu-test-support = { path = "../nu-test-support", version = "0.104.1" }
nu-path = { path = "../nu-path", version = "0.104.1" }
serde_json = "1.0"
fancy-regex = "0.14.0"

View File

@ -3,16 +3,16 @@ authors = ["The Nushell Project Developers"]
description = "Nushell's integrated LSP server"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-lsp"
name = "nu-lsp"
version = "0.103.1"
version = "0.104.1"
edition = "2021"
license = "MIT"
[dependencies]
nu-cli = { path = "../nu-cli", version = "0.103.1" }
nu-glob = { path = "../nu-glob", version = "0.103.1" }
nu-parser = { path = "../nu-parser", version = "0.103.1" }
nu-protocol = { path = "../nu-protocol", version = "0.103.1" }
nu-utils = { path = "../nu-utils", version = "0.103.1" }
nu-cli = { path = "../nu-cli", version = "0.104.1" }
nu-glob = { path = "../nu-glob", version = "0.104.1" }
nu-parser = { path = "../nu-parser", version = "0.104.1" }
nu-protocol = { path = "../nu-protocol", version = "0.104.1" }
nu-utils = { path = "../nu-utils", version = "0.104.1" }
crossbeam-channel = { workspace = true }
lsp-server = { workspace = true }
@ -26,11 +26,11 @@ serde_json = { workspace = true }
url = { workspace = true }
[dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.103.1" }
nu-command = { path = "../nu-command", version = "0.103.1" }
nu-engine = { path = "../nu-engine", version = "0.103.1" }
nu-std = { path = "../nu-std", version = "0.103.1" }
nu-test-support = { path = "../nu-test-support", version = "0.103.1" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.104.1" }
nu-command = { path = "../nu-command", version = "0.104.1" }
nu-engine = { path = "../nu-engine", version = "0.104.1" }
nu-std = { path = "../nu-std", version = "0.104.1" }
nu-test-support = { path = "../nu-test-support", version = "0.104.1" }
assert-json-diff = "2.0"

View File

@ -63,7 +63,7 @@ impl LanguageServer {
let var = working_set.get_variable(*var_id);
Some(
var.const_val
.clone()
.as_ref()
.and_then(|val| val.follow_cell_path(cell_path, false).ok())
.map(|val| val.span())
.unwrap_or(var.declaration_span),

View File

@ -160,16 +160,15 @@ impl LanguageServer {
let var = working_set.get_variable(var_id);
markdown_hover(
var.const_val
.clone()
.as_ref()
.and_then(|val| val.follow_cell_path(&cell_path, false).ok())
.map(|val| {
let ty = val.get_type().clone();
let value_string = val
.coerce_into_string()
.ok()
.map(|s| format!("\n---\n{}", s))
.unwrap_or_default();
format!("```\n{}\n```{}", ty, value_string)
let ty = val.get_type();
if let Ok(s) = val.coerce_str() {
format!("```\n{}\n```\n---\n{}", ty, s)
} else {
format!("```\n{}\n```", ty)
}
})
.unwrap_or("`unknown`".into()),
)

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-parser"
edition = "2021"
license = "MIT"
name = "nu-parser"
version = "0.103.1"
version = "0.104.1"
exclude = ["/fuzz"]
[lib]
@ -15,11 +15,11 @@ bench = false
workspace = true
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.103.1", default-features = false }
nu-path = { path = "../nu-path", version = "0.103.1" }
nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.103.1" }
nu-protocol = { path = "../nu-protocol", version = "0.103.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.103.1", default-features = false }
nu-engine = { path = "../nu-engine", version = "0.104.1", default-features = false }
nu-path = { path = "../nu-path", version = "0.104.1" }
nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.104.1" }
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.104.1", default-features = false }
bytesize = { workspace = true }
chrono = { default-features = false, features = ['std'], workspace = true }

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-path"
edition = "2021"
license = "MIT"
name = "nu-path"
version = "0.103.1"
version = "0.104.1"
exclude = ["/fuzz"]
[lib]

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-core
edition = "2021"
license = "MIT"
name = "nu-plugin-core"
version = "0.103.1"
version = "0.104.1"
[lib]
bench = false
@ -14,8 +14,8 @@ bench = false
workspace = true
[dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.103.1" }
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.103.1", default-features = false }
nu-protocol = { path = "../nu-protocol", version = "0.104.1" }
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.104.1", default-features = false }
rmp-serde = { workspace = true }
serde = { workspace = true }

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-engi
edition = "2021"
license = "MIT"
name = "nu-plugin-engine"
version = "0.103.1"
version = "0.104.1"
[lib]
bench = false
@ -14,12 +14,12 @@ bench = false
workspace = true
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.103.1" }
nu-protocol = { path = "../nu-protocol", version = "0.103.1" }
nu-system = { path = "../nu-system", version = "0.103.1" }
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.103.1" }
nu-plugin-core = { path = "../nu-plugin-core", version = "0.103.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.103.1" }
nu-engine = { path = "../nu-engine", version = "0.104.1" }
nu-protocol = { path = "../nu-protocol", version = "0.104.1" }
nu-system = { path = "../nu-system", version = "0.104.1" }
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.104.1" }
nu-plugin-core = { path = "../nu-plugin-core", version = "0.104.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.104.1" }
serde = { workspace = true }
log = { workspace = true }

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-prot
edition = "2021"
license = "MIT"
name = "nu-plugin-protocol"
version = "0.103.1"
version = "0.104.1"
[lib]
bench = false
@ -14,8 +14,8 @@ bench = false
workspace = true
[dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.103.1", features = ["plugin"] }
nu-utils = { path = "../nu-utils", version = "0.103.1" }
nu-protocol = { path = "../nu-protocol", version = "0.104.1", features = ["plugin"] }
nu-utils = { path = "../nu-utils", version = "0.104.1" }
rmp-serde = { workspace = true }
serde = { workspace = true, features = ["derive"] }

View File

@ -1,6 +1,6 @@
[package]
name = "nu-plugin-test-support"
version = "0.103.1"
version = "0.104.1"
edition = "2021"
license = "MIT"
description = "Testing support for Nushell plugins"
@ -15,14 +15,14 @@ workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.103.1", features = ["plugin"] }
nu-protocol = { path = "../nu-protocol", version = "0.103.1", features = ["plugin"] }
nu-parser = { path = "../nu-parser", version = "0.103.1", features = ["plugin"] }
nu-plugin = { path = "../nu-plugin", version = "0.103.1" }
nu-plugin-core = { path = "../nu-plugin-core", version = "0.103.1" }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.103.1" }
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.103.1" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.103.1" }
nu-engine = { path = "../nu-engine", version = "0.104.1", features = ["plugin"] }
nu-protocol = { path = "../nu-protocol", version = "0.104.1", features = ["plugin"] }
nu-parser = { path = "../nu-parser", version = "0.104.1", features = ["plugin"] }
nu-plugin = { path = "../nu-plugin", version = "0.104.1" }
nu-plugin-core = { path = "../nu-plugin-core", version = "0.104.1" }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.104.1" }
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.104.1" }
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.104.1" }
nu-ansi-term = { workspace = true }
similar = "2.7"

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin"
edition = "2021"
license = "MIT"
name = "nu-plugin"
version = "0.103.1"
version = "0.104.1"
[lib]
bench = false
@ -14,14 +14,14 @@ bench = false
workspace = true
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.103.1", features = ["plugin"] }
nu-protocol = { path = "../nu-protocol", version = "0.103.1", features = ["plugin"] }
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.103.1" }
nu-plugin-core = { path = "../nu-plugin-core", version = "0.103.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.103.1" }
nu-engine = { path = "../nu-engine", version = "0.104.1", features = ["plugin"] }
nu-protocol = { path = "../nu-protocol", version = "0.104.1", features = ["plugin"] }
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.104.1" }
nu-plugin-core = { path = "../nu-plugin-core", version = "0.104.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.104.1" }
log = { workspace = true }
thiserror = "2.0"
thiserror = "2.0.12"
[dev-dependencies]
serde = { workspace = true }
@ -33,4 +33,4 @@ local-socket = ["nu-plugin-core/local-socket"]
[target.'cfg(target_family = "unix")'.dependencies]
# For setting the process group ID (EnterForeground / LeaveForeground)
nix = { workspace = true, default-features = false, features = ["process"] }
nix = { workspace = true, default-features = false, features = ["process"] }

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-pretty-hex"
edition = "2021"
license = "MIT"
name = "nu-pretty-hex"
version = "0.103.1"
version = "0.104.1"
[lib]
doctest = false

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-protocol"
edition = "2021"
license = "MIT"
name = "nu-protocol"
version = "0.103.1"
version = "0.104.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -16,29 +16,29 @@ bench = false
workspace = true
[dependencies]
nu-utils = { path = "../nu-utils", version = "0.103.1", default-features = false }
nu-glob = { path = "../nu-glob", version = "0.103.1" }
nu-path = { path = "../nu-path", version = "0.103.1" }
nu-system = { path = "../nu-system", version = "0.103.1" }
nu-derive-value = { path = "../nu-derive-value", version = "0.103.1" }
nu-utils = { path = "../nu-utils", version = "0.104.1", default-features = false }
nu-glob = { path = "../nu-glob", version = "0.104.1" }
nu-path = { path = "../nu-path", version = "0.104.1" }
nu-system = { path = "../nu-system", version = "0.104.1" }
nu-derive-value = { path = "../nu-derive-value", version = "0.104.1" }
brotli = { workspace = true, optional = true }
bytes = { workspace = true }
chrono = { workspace = true, features = [ "serde", "std", "unstable-locales" ], default-features = false }
chrono = { workspace = true, features = ["serde", "std", "unstable-locales"], default-features = false }
chrono-humanize = { workspace = true }
dirs = { workspace = true }
fancy-regex = { workspace = true }
heck = { workspace = true }
indexmap = { workspace = true }
lru = { workspace = true }
miette = { workspace = true, features = ["fancy-no-backtrace"]}
miette = { workspace = true, features = ["fancy-no-backtrace"] }
num-format = { workspace = true }
rmp-serde = { workspace = true, optional = true }
serde = { workspace = true }
serde_json = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
thiserror = "2.0"
thiserror = "2.0.12"
typetag = "0.2"
os_pipe = { workspace = true, optional = true, features = ["io_safety"] }
log = { workspace = true }
@ -67,8 +67,8 @@ plugin = [
[dev-dependencies]
serde_json = { workspace = true }
nu-test-support = { path = "../nu-test-support", version = "0.103.1" }
nu-utils = { path = "../nu-utils", version = "0.103.1" }
nu-test-support = { path = "../nu-test-support", version = "0.104.1" }
nu-utils = { path = "../nu-utils", version = "0.104.1" }
pretty_assertions = { workspace = true }
rstest = { workspace = true }
tempfile = { workspace = true }

View File

@ -20,6 +20,7 @@ pub enum TableMode {
Restructured,
AsciiRounded,
BasicCompact,
Single,
}
impl FromStr for TableMode {
@ -44,7 +45,8 @@ impl FromStr for TableMode {
"restructured" => Ok(Self::Restructured),
"ascii_rounded" => Ok(Self::AsciiRounded),
"basic_compact" => Ok(Self::BasicCompact),
_ => Err("'basic', 'thin', 'light', 'compact', 'with_love', 'compact_double', 'rounded', 'reinforced', 'heavy', 'none', 'psql', 'markdown', 'dots', 'restructured', 'ascii_rounded', or 'basic_compact'"),
"single" => Ok(Self::Single),
_ => Err("'basic', 'thin', 'light', 'compact', 'with_love', 'compact_double', 'rounded', 'reinforced', 'heavy', 'none', 'psql', 'markdown', 'dots', 'restructured', 'ascii_rounded', 'basic_compact' or 'single'"),
}
}
}
@ -340,6 +342,7 @@ pub struct TableConfig {
pub header_on_separator: bool,
pub abbreviated_row_count: Option<usize>,
pub footer_inheritance: bool,
pub missing_value_symbol: String,
}
impl IntoValue for TableConfig {
@ -358,6 +361,7 @@ impl IntoValue for TableConfig {
"header_on_separator" => self.header_on_separator.into_value(span),
"abbreviated_row_count" => abbv_count,
"footer_inheritance" => self.footer_inheritance.into_value(span),
"missing_value_symbol" => self.missing_value_symbol.into_value(span),
}
.into_value(span)
}
@ -374,6 +378,7 @@ impl Default for TableConfig {
padding: TableIndent::default(),
abbreviated_row_count: None,
footer_inheritance: false,
missing_value_symbol: "".into(),
}
}
}
@ -411,6 +416,10 @@ impl UpdateFromValue for TableConfig {
_ => errors.type_mismatch(path, Type::custom("int or nothing"), val),
},
"footer_inheritance" => self.footer_inheritance.update(val, path, errors),
"missing_value_symbol" => match val.as_str() {
Ok(val) => self.missing_value_symbol = val.to_string(),
Err(_) => errors.type_mismatch(path, Type::String, val),
},
_ => errors.unknown_option(path, val),
}
}

View File

@ -8,9 +8,9 @@ use crate::{
},
eval_const::create_nu_constant,
shell_error::io::IoError,
BlockId, Category, Config, DeclId, FileId, GetSpan, Handlers, HistoryConfig, Module, ModuleId,
OverlayId, ShellError, SignalAction, Signals, Signature, Span, SpanId, Type, Value, VarId,
VirtualPathId,
BlockId, Category, Config, DeclId, FileId, GetSpan, Handlers, HistoryConfig, JobId, Module,
ModuleId, OverlayId, ShellError, SignalAction, Signals, Signature, Span, SpanId, Type, Value,
VarId, VirtualPathId,
};
use fancy_regex::Regex;
use lru::LruCache;
@ -22,6 +22,8 @@ use std::{
path::PathBuf,
sync::{
atomic::{AtomicBool, AtomicU32, Ordering},
mpsc::channel,
mpsc::Sender,
Arc, Mutex, MutexGuard, PoisonError,
},
};
@ -31,7 +33,7 @@ type PoisonDebuggerError<'a> = PoisonError<MutexGuard<'a, Box<dyn Debugger>>>;
#[cfg(feature = "plugin")]
use crate::{PluginRegistryFile, PluginRegistryItem, RegisteredPlugin};
use super::{Jobs, ThreadJob};
use super::{CurrentJob, Jobs, Mail, Mailbox, ThreadJob};
#[derive(Clone, Debug)]
pub enum VirtualPath {
@ -117,7 +119,9 @@ pub struct EngineState {
pub jobs: Arc<Mutex<Jobs>>,
// The job being executed with this engine state, or None if main thread
pub current_thread_job: Option<ThreadJob>,
pub current_job: CurrentJob,
pub root_job_sender: Sender<Mail>,
// When there are background jobs running, the interactive behavior of `exit` changes depending on
// the value of this flag:
@ -141,6 +145,8 @@ pub const UNKNOWN_SPAN_ID: SpanId = SpanId::new(0);
impl EngineState {
pub fn new() -> Self {
let (send, recv) = channel::<Mail>();
Self {
files: vec![],
virtual_paths: vec![],
@ -196,7 +202,12 @@ impl EngineState {
is_debugging: IsDebugging::new(false),
debugger: Arc::new(Mutex::new(Box::new(NoopDebugger))),
jobs: Arc::new(Mutex::new(Jobs::default())),
current_thread_job: None,
current_job: CurrentJob {
id: JobId::new(0),
background_thread_job: None,
mailbox: Arc::new(Mutex::new(Mailbox::new(recv))),
},
root_job_sender: send,
exit_warning_given: Arc::new(AtomicBool::new(false)),
}
}
@ -1081,7 +1092,12 @@ impl EngineState {
// Determines whether the current state is being held by a background job
pub fn is_background_job(&self) -> bool {
self.current_thread_job.is_some()
self.current_job.background_thread_job.is_some()
}
// Gets the thread job entry
pub fn current_thread_job(&self) -> Option<&ThreadJob> {
self.current_job.background_thread_job.as_ref()
}
}

View File

@ -1,11 +1,17 @@
use std::{
collections::{HashMap, HashSet},
sync::{Arc, Mutex},
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
sync::{
mpsc::{Receiver, RecvTimeoutError, Sender, TryRecvError},
Arc, Mutex,
},
};
#[cfg(not(target_family = "wasm"))]
use std::time::{Duration, Instant};
use nu_system::{kill_by_pid, UnfreezeHandle};
use crate::Signals;
use crate::{PipelineData, Signals};
use crate::JobId;
@ -139,13 +145,15 @@ pub struct ThreadJob {
signals: Signals,
pids: Arc<Mutex<HashSet<u32>>>,
tag: Option<String>,
pub sender: Sender<Mail>,
}
impl ThreadJob {
pub fn new(signals: Signals, tag: Option<String>) -> Self {
pub fn new(signals: Signals, tag: Option<String>, sender: Sender<Mail>) -> Self {
ThreadJob {
signals,
pids: Arc::new(Mutex::new(HashSet::default())),
sender,
tag,
}
}
@ -238,3 +246,160 @@ impl FrozenJob {
}
}
}
/// Stores the information about the background job currently being executed by this thread, if any
#[derive(Clone)]
pub struct CurrentJob {
pub id: JobId,
// The background thread job associated with this thread.
// If None, it indicates this thread is currently the main job
pub background_thread_job: Option<ThreadJob>,
// note: although the mailbox is Mutex'd, it is only ever accessed
// by the current job's threads
pub mailbox: Arc<Mutex<Mailbox>>,
}
// The storage for unread messages
//
// Messages are initially sent over a mpsc channel,
// and may then be stored in a IgnoredMail struct when
// filtered out by a tag.
pub struct Mailbox {
receiver: Receiver<Mail>,
ignored_mail: IgnoredMail,
}
impl Mailbox {
pub fn new(receiver: Receiver<Mail>) -> Self {
Mailbox {
receiver,
ignored_mail: IgnoredMail::default(),
}
}
#[cfg(not(target_family = "wasm"))]
pub fn recv_timeout(
&mut self,
filter_tag: Option<FilterTag>,
timeout: Duration,
) -> Result<PipelineData, RecvTimeoutError> {
if let Some(value) = self.ignored_mail.pop(filter_tag) {
Ok(value)
} else {
let mut waited_so_far = Duration::ZERO;
let mut before = Instant::now();
while waited_so_far < timeout {
let (tag, value) = self.receiver.recv_timeout(timeout - waited_so_far)?;
if filter_tag.is_none() || filter_tag == tag {
return Ok(value);
} else {
self.ignored_mail.add((tag, value));
let now = Instant::now();
waited_so_far += now - before;
before = now;
}
}
Err(RecvTimeoutError::Timeout)
}
}
#[cfg(not(target_family = "wasm"))]
pub fn try_recv(
&mut self,
filter_tag: Option<FilterTag>,
) -> Result<PipelineData, TryRecvError> {
if let Some(value) = self.ignored_mail.pop(filter_tag) {
Ok(value)
} else {
loop {
let (tag, value) = self.receiver.try_recv()?;
if filter_tag.is_none() || filter_tag == tag {
return Ok(value);
} else {
self.ignored_mail.add((tag, value));
}
}
}
}
pub fn clear(&mut self) {
self.ignored_mail.clear();
while self.receiver.try_recv().is_ok() {}
}
}
// A data structure used to store messages which were received, but currently ignored by a tag filter
// messages are added and popped in a first-in-first-out matter.
#[derive(Default)]
struct IgnoredMail {
next_id: usize,
messages: BTreeMap<usize, Mail>,
by_tag: HashMap<FilterTag, BTreeSet<usize>>,
}
pub type FilterTag = u64;
pub type Mail = (Option<FilterTag>, PipelineData);
impl IgnoredMail {
pub fn add(&mut self, (tag, value): Mail) {
let id = self.next_id;
self.next_id += 1;
self.messages.insert(id, (tag, value));
if let Some(tag) = tag {
self.by_tag.entry(tag).or_default().insert(id);
}
}
pub fn pop(&mut self, tag: Option<FilterTag>) -> Option<PipelineData> {
if let Some(tag) = tag {
self.pop_oldest_with_tag(tag)
} else {
self.pop_oldest()
}
}
pub fn clear(&mut self) {
self.messages.clear();
self.by_tag.clear();
}
fn pop_oldest(&mut self) -> Option<PipelineData> {
let (id, (tag, value)) = self.messages.pop_first()?;
if let Some(tag) = tag {
let needs_cleanup = if let Some(ids) = self.by_tag.get_mut(&tag) {
ids.remove(&id);
ids.is_empty()
} else {
false
};
if needs_cleanup {
self.by_tag.remove(&tag);
}
}
Some(value)
}
fn pop_oldest_with_tag(&mut self, tag: FilterTag) -> Option<PipelineData> {
let ids = self.by_tag.get_mut(&tag)?;
let id = ids.pop_first()?;
if ids.is_empty() {
self.by_tag.remove(&tag);
}
Some(self.messages.remove(&id)?.1)
}
}

View File

@ -1370,7 +1370,7 @@ On Windows, this would be %USERPROFILE%\AppData\Roaming"#
#[error("Job {id} is not frozen")]
#[diagnostic(
code(nu::shell::os_disabled),
code(nu::shell::job_not_frozen),
help("You tried to unfreeze a job which is not frozen")
)]
JobNotFrozen {
@ -1379,6 +1379,27 @@ On Windows, this would be %USERPROFILE%\AppData\Roaming"#
span: Span,
},
#[error("The job {id} is frozen")]
#[diagnostic(
code(nu::shell::job_is_frozen),
help("This operation cannot be performed because the job is frozen")
)]
JobIsFrozen {
id: usize,
#[label = "This job is frozen"]
span: Span,
},
#[error("No message was received in the requested time interval")]
#[diagnostic(
code(nu::shell::recv_timeout),
help("No message arrived within the specified time limit")
)]
RecvTimeout {
#[label = "timeout"]
span: Span,
},
#[error(transparent)]
#[diagnostic(transparent)]
ChainedError(ChainedError),

View File

@ -7,7 +7,7 @@ use crate::{
debugger::DebugContext,
BlockId, Config, GetSpan, Range, Record, ShellError, Span, Value, VarId, ENV_VARIABLE_ID,
};
use std::{collections::HashMap, sync::Arc};
use std::{borrow::Cow, collections::HashMap, sync::Arc};
/// To share implementations for regular eval and const eval
pub trait Eval {
@ -43,11 +43,8 @@ pub trait Eval {
// Cell paths are usually case-sensitive, but we give $env
// special treatment.
if cell_path.head.expr == Expr::Var(ENV_VARIABLE_ID) {
value.follow_cell_path(&cell_path.tail, true)
} else {
value.follow_cell_path(&cell_path.tail, false)
}
let insensitive = cell_path.head.expr == Expr::Var(ENV_VARIABLE_ID);
value.follow_cell_path(&cell_path.tail, insensitive).map(Cow::into_owned)
}
Expr::DateTime(dt) => Ok(Value::date(*dt, expr_span)),
Expr::List(list) => {

View File

@ -128,7 +128,6 @@ impl Module {
} else {
// Import pattern was just name without any members
let mut decls = vec![];
let mut const_vids = vec![];
let mut const_rows = vec![];
let mut errors = vec![];
@ -154,7 +153,6 @@ impl Module {
decls.push((new_name, sub_decl_id));
}
const_vids.extend(sub_results.constants);
const_rows.extend(sub_results.constant_values);
}
@ -162,10 +160,7 @@ impl Module {
for (name, var_id) in self.consts() {
match working_set.get_constant(var_id) {
Ok(const_val) => {
const_vids.push((name.clone(), var_id));
const_rows.push((name, const_val.clone()))
}
Ok(const_val) => const_rows.push((name, const_val.clone())),
Err(err) => errors.push(err),
}
}
@ -192,7 +187,7 @@ impl Module {
ResolvedImportPattern::new(
decls,
vec![(final_name.clone(), self_id)],
const_vids,
vec![],
constant_values,
),
errors,

View File

@ -6,7 +6,7 @@ use crate::{
ByteStream, ByteStreamType, Config, ListStream, OutDest, PipelineMetadata, Range, ShellError,
Signals, Span, Type, Value,
};
use std::io::Write;
use std::{borrow::Cow, io::Write};
const LINE_ENDING_PATTERN: &[char] = &['\r', '\n'];
@ -416,8 +416,11 @@ impl PipelineData {
match self {
// FIXME: there are probably better ways of doing this
PipelineData::ListStream(stream, ..) => Value::list(stream.into_iter().collect(), head)
.follow_cell_path(cell_path, insensitive),
PipelineData::Value(v, ..) => v.follow_cell_path(cell_path, insensitive),
.follow_cell_path(cell_path, insensitive)
.map(Cow::into_owned),
PipelineData::Value(v, ..) => v
.follow_cell_path(cell_path, insensitive)
.map(Cow::into_owned),
PipelineData::Empty => Err(ShellError::IncompatiblePathAccess {
type_name: "empty pipeline".to_string(),
span: head,

View File

@ -194,7 +194,7 @@ impl PostWaitCallback {
child_pid: Option<u32>,
tag: Option<String>,
) -> Self {
let this_job = engine_state.current_thread_job.clone();
let this_job = engine_state.current_thread_job().cloned();
let jobs = engine_state.jobs.clone();
let is_interactive = engine_state.is_interactive;

View File

@ -38,7 +38,7 @@ use std::{
borrow::Cow,
cmp::Ordering,
fmt::{Debug, Display, Write},
ops::Bound,
ops::{Bound, ControlFlow, Deref},
path::PathBuf,
};
@ -1080,224 +1080,83 @@ impl Value {
}
/// Follow a given cell path into the value: for example accessing select elements in a stream or list
pub fn follow_cell_path(
self,
pub fn follow_cell_path<'out>(
&'out self,
cell_path: &[PathMember],
insensitive: bool,
) -> Result<Value, ShellError> {
let mut current = self;
) -> Result<Cow<'out, Value>, ShellError> {
enum MultiLife<'out, 'local, T>
where
'out: 'local,
T: ?Sized,
{
Out(&'out T),
Local(&'local T),
}
for member in cell_path {
match member {
PathMember::Int {
val: count,
span: origin_span,
optional,
} => {
// Treat a numeric path member as `select <val>`
match current {
Value::List { mut vals, .. } => {
if *count < vals.len() {
// `vals` is owned and will be dropped right after this,
// so we can `swap_remove` the value at index `count`
// without worrying about preserving order.
current = vals.swap_remove(*count);
} else if *optional {
return Ok(Value::nothing(*origin_span)); // short-circuit
} else if vals.is_empty() {
return Err(ShellError::AccessEmptyContent { span: *origin_span });
} else {
return Err(ShellError::AccessBeyondEnd {
max_idx: vals.len() - 1,
span: *origin_span,
});
}
}
Value::Binary { val, .. } => {
if let Some(item) = val.get(*count) {
current = Value::int(*item as i64, *origin_span);
} else if *optional {
return Ok(Value::nothing(*origin_span)); // short-circuit
} else if val.is_empty() {
return Err(ShellError::AccessEmptyContent { span: *origin_span });
} else {
return Err(ShellError::AccessBeyondEnd {
max_idx: val.len() - 1,
span: *origin_span,
});
}
}
Value::Range { ref val, .. } => {
if let Some(item) = val
.into_range_iter(current.span(), Signals::empty())
.nth(*count)
{
current = item;
} else if *optional {
return Ok(Value::nothing(*origin_span)); // short-circuit
} else {
return Err(ShellError::AccessBeyondEndOfStream {
span: *origin_span,
});
}
}
Value::Custom { ref val, .. } => {
current =
match val.follow_path_int(current.span(), *count, *origin_span) {
Ok(val) => val,
Err(err) => {
if *optional {
return Ok(Value::nothing(*origin_span));
// short-circuit
} else {
return Err(err);
}
}
};
}
Value::Nothing { .. } if *optional => {
return Ok(Value::nothing(*origin_span)); // short-circuit
}
// Records (and tables) are the only built-in which support column names,
// so only use this message for them.
Value::Record { .. } => {
return Err(ShellError::TypeMismatch {
err_message:"Can't access record values with a row index. Try specifying a column name instead".into(),
span: *origin_span,
});
}
Value::Error { error, .. } => return Err(*error),
x => {
return Err(ShellError::IncompatiblePathAccess {
type_name: format!("{}", x.get_type()),
span: *origin_span,
});
}
}
}
PathMember::String {
val: column_name,
span: origin_span,
optional,
} => {
let span = current.span();
impl<'out, 'local, T> Deref for MultiLife<'out, 'local, T>
where
'out: 'local,
T: ?Sized,
{
type Target = T;
match current {
Value::Record { mut val, .. } => {
// Make reverse iterate to avoid duplicate column leads to first value, actually last value is expected.
if let Some(found) = val.to_mut().iter_mut().rev().find(|x| {
if insensitive {
x.0.eq_ignore_case(column_name)
} else {
x.0 == column_name
}
}) {
current = std::mem::take(found.1);
} else if *optional {
return Ok(Value::nothing(*origin_span)); // short-circuit
} else if let Some(suggestion) =
did_you_mean(val.columns(), column_name)
{
return Err(ShellError::DidYouMean {
suggestion,
span: *origin_span,
});
} else {
return Err(ShellError::CantFindColumn {
col_name: column_name.clone(),
span: Some(*origin_span),
src_span: span,
});
}
}
// String access of Lists always means Table access.
// Create a List which contains each matching value for contained
// records in the source list.
Value::List { vals, .. } => {
let list = vals
.into_iter()
.map(|val| {
let val_span = val.span();
match val {
Value::Record { mut val, .. } => {
if let Some(found) =
val.to_mut().iter_mut().rev().find(|x| {
if insensitive {
x.0.eq_ignore_case(column_name)
} else {
x.0 == column_name
}
})
{
Ok(std::mem::take(found.1))
} else if *optional {
Ok(Value::nothing(*origin_span))
} else if let Some(suggestion) =
did_you_mean(val.columns(), column_name)
{
Err(ShellError::DidYouMean {
suggestion,
span: *origin_span,
})
} else {
Err(ShellError::CantFindColumn {
col_name: column_name.clone(),
span: Some(*origin_span),
src_span: val_span,
})
}
}
Value::Nothing { .. } if *optional => {
Ok(Value::nothing(*origin_span))
}
_ => Err(ShellError::CantFindColumn {
col_name: column_name.clone(),
span: Some(*origin_span),
src_span: val_span,
}),
}
})
.collect::<Result<_, _>>()?;
current = Value::list(list, span);
}
Value::Custom { ref val, .. } => {
current = match val.follow_path_string(
current.span(),
column_name.clone(),
*origin_span,
) {
Ok(val) => val,
Err(err) => {
if *optional {
return Ok(Value::nothing(*origin_span));
// short-circuit
} else {
return Err(err);
}
}
}
}
Value::Nothing { .. } if *optional => {
return Ok(Value::nothing(*origin_span)); // short-circuit
}
Value::Error { error, .. } => return Err(*error),
x => {
return Err(ShellError::IncompatiblePathAccess {
type_name: format!("{}", x.get_type()),
span: *origin_span,
});
}
}
fn deref(&self) -> &Self::Target {
match *self {
MultiLife::Out(x) => x,
MultiLife::Local(x) => x,
}
}
}
// A dummy value is required, otherwise rust doesn't allow references, which we need for
// the `std::ptr::eq` comparison
let mut store: Value = Value::test_nothing();
let mut current: MultiLife<'out, '_, Value> = MultiLife::Out(self);
for member in cell_path {
current = match current {
MultiLife::Out(current) => match get_value_member(current, member, insensitive)? {
ControlFlow::Break(span) => return Ok(Cow::Owned(Value::nothing(span))),
ControlFlow::Continue(x) => match x {
Cow::Borrowed(x) => MultiLife::Out(x),
Cow::Owned(x) => {
store = x;
MultiLife::Local(&store)
}
},
},
MultiLife::Local(current) => {
match get_value_member(current, member, insensitive)? {
ControlFlow::Break(span) => return Ok(Cow::Owned(Value::nothing(span))),
ControlFlow::Continue(x) => match x {
Cow::Borrowed(x) => MultiLife::Local(x),
Cow::Owned(x) => {
store = x;
MultiLife::Local(&store)
}
},
}
}
};
}
// If a single Value::Error was produced by the above (which won't happen if nullify_errors is true), unwrap it now.
// Note that Value::Errors inside Lists remain as they are, so that the rest of the list can still potentially be used.
if let Value::Error { error, .. } = current {
Err(*error)
if let Value::Error { error, .. } = &*current {
Err(error.as_ref().clone())
} else {
Ok(current)
Ok(match current {
MultiLife::Out(x) => Cow::Borrowed(x),
MultiLife::Local(x) => {
let x = if std::ptr::eq(x, &store) {
store
} else {
x.clone()
};
Cow::Owned(x)
}
})
}
}
@ -1307,9 +1166,7 @@ impl Value {
cell_path: &[PathMember],
callback: Box<dyn FnOnce(&Value) -> Value>,
) -> Result<(), ShellError> {
let orig = self.clone();
let new_val = callback(&orig.follow_cell_path(cell_path, false)?);
let new_val = callback(self.follow_cell_path(cell_path, false)?.as_ref());
match new_val {
Value::Error { error, .. } => Err(*error),
@ -1409,9 +1266,7 @@ impl Value {
cell_path: &[PathMember],
callback: Box<dyn FnOnce(&Value) -> Value + 'a>,
) -> Result<(), ShellError> {
let orig = self.clone();
let new_val = callback(&orig.follow_cell_path(cell_path, false)?);
let new_val = callback(self.follow_cell_path(cell_path, false)?.as_ref());
match new_val {
Value::Error { error, .. } => Err(*error),
@ -2147,6 +2002,198 @@ impl Value {
}
}
fn get_value_member<'a>(
current: &'a Value,
member: &PathMember,
insensitive: bool,
) -> Result<ControlFlow<Span, Cow<'a, Value>>, ShellError> {
match member {
PathMember::Int {
val: count,
span: origin_span,
optional,
} => {
// Treat a numeric path member as `select <val>`
match current {
Value::List { vals, .. } => {
if *count < vals.len() {
Ok(ControlFlow::Continue(Cow::Borrowed(&vals[*count])))
} else if *optional {
Ok(ControlFlow::Break(*origin_span))
// short-circuit
} else if vals.is_empty() {
Err(ShellError::AccessEmptyContent { span: *origin_span })
} else {
Err(ShellError::AccessBeyondEnd {
max_idx: vals.len() - 1,
span: *origin_span,
})
}
}
Value::Binary { val, .. } => {
if let Some(item) = val.get(*count) {
Ok(ControlFlow::Continue(Cow::Owned(Value::int(
*item as i64,
*origin_span,
))))
} else if *optional {
Ok(ControlFlow::Break(*origin_span))
// short-circuit
} else if val.is_empty() {
Err(ShellError::AccessEmptyContent { span: *origin_span })
} else {
Err(ShellError::AccessBeyondEnd {
max_idx: val.len() - 1,
span: *origin_span,
})
}
}
Value::Range { ref val, .. } => {
if let Some(item) = val
.into_range_iter(current.span(), Signals::empty())
.nth(*count)
{
Ok(ControlFlow::Continue(Cow::Owned(item)))
} else if *optional {
Ok(ControlFlow::Break(*origin_span))
// short-circuit
} else {
Err(ShellError::AccessBeyondEndOfStream {
span: *origin_span,
})
}
}
Value::Custom { ref val, .. } => {
match val.follow_path_int(current.span(), *count, *origin_span)
{
Ok(val) => Ok(ControlFlow::Continue(Cow::Owned(val))),
Err(err) => {
if *optional {
Ok(ControlFlow::Break(*origin_span))
// short-circuit
} else {
Err(err)
}
}
}
}
Value::Nothing { .. } if *optional => Ok(ControlFlow::Break(*origin_span)),
// Records (and tables) are the only built-in which support column names,
// so only use this message for them.
Value::Record { .. } => Err(ShellError::TypeMismatch {
err_message:"Can't access record values with a row index. Try specifying a column name instead".into(),
span: *origin_span,
}),
Value::Error { error, .. } => Err(*error.clone()),
x => Err(ShellError::IncompatiblePathAccess { type_name: format!("{}", x.get_type()), span: *origin_span }),
}
}
PathMember::String {
val: column_name,
span: origin_span,
optional,
} => {
let span = current.span();
match current {
Value::Record { val, .. } => {
if let Some(found) = val.iter().rev().find(|x| {
if insensitive {
x.0.eq_ignore_case(column_name)
} else {
x.0 == column_name
}
}) {
Ok(ControlFlow::Continue(Cow::Borrowed(found.1)))
} else if *optional {
Ok(ControlFlow::Break(*origin_span))
// short-circuit
} else if let Some(suggestion) = did_you_mean(val.columns(), column_name) {
Err(ShellError::DidYouMean {
suggestion,
span: *origin_span,
})
} else {
Err(ShellError::CantFindColumn {
col_name: column_name.clone(),
span: Some(*origin_span),
src_span: span,
})
}
}
// String access of Lists always means Table access.
// Create a List which contains each matching value for contained
// records in the source list.
Value::List { vals, .. } => {
let list = vals
.iter()
.map(|val| {
let val_span = val.span();
match val {
Value::Record { val, .. } => {
if let Some(found) = val.iter().rev().find(|x| {
if insensitive {
x.0.eq_ignore_case(column_name)
} else {
x.0 == column_name
}
}) {
Ok(found.1.clone())
} else if *optional {
Ok(Value::nothing(*origin_span))
} else if let Some(suggestion) =
did_you_mean(val.columns(), column_name)
{
Err(ShellError::DidYouMean {
suggestion,
span: *origin_span,
})
} else {
Err(ShellError::CantFindColumn {
col_name: column_name.clone(),
span: Some(*origin_span),
src_span: val_span,
})
}
}
Value::Nothing { .. } if *optional => {
Ok(Value::nothing(*origin_span))
}
_ => Err(ShellError::CantFindColumn {
col_name: column_name.clone(),
span: Some(*origin_span),
src_span: val_span,
}),
}
})
.collect::<Result<_, _>>()?;
Ok(ControlFlow::Continue(Cow::Owned(Value::list(list, span))))
}
Value::Custom { ref val, .. } => {
match val.follow_path_string(current.span(), column_name.clone(), *origin_span)
{
Ok(val) => Ok(ControlFlow::Continue(Cow::Owned(val))),
Err(err) => {
if *optional {
Ok(ControlFlow::Break(*origin_span))
// short-circuit
} else {
Err(err)
}
}
}
}
Value::Nothing { .. } if *optional => Ok(ControlFlow::Break(*origin_span)),
Value::Error { error, .. } => Err(error.as_ref().clone()),
x => Err(ShellError::IncompatiblePathAccess {
type_name: format!("{}", x.get_type()),
span: *origin_span,
}),
}
}
}
}
impl Default for Value {
fn default() -> Self {
Value::Nothing {

View File

@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-std"
edition = "2021"
license = "MIT"
name = "nu-std"
version = "0.103.1"
version = "0.104.1"
[dependencies]
nu-parser = { version = "0.103.1", path = "../nu-parser" }
nu-protocol = { version = "0.103.1", path = "../nu-protocol", default-features = false }
nu-engine = { version = "0.103.1", path = "../nu-engine", default-features = false }
nu-parser = { version = "0.104.1", path = "../nu-parser" }
nu-protocol = { version = "0.104.1", path = "../nu-protocol", default-features = false }
nu-engine = { version = "0.104.1", path = "../nu-engine", default-features = false }
miette = { workspace = true, features = ["fancy-no-backtrace"] }
log = "0.4"

View File

@ -38,7 +38,7 @@ export def "kv set" [
# If passed a closure, execute it
let arg_type = ($value_or_closure | describe)
let value = match $arg_type {
closure => { kv get $key --universal=$universal | do $value_or_closure }
closure => { $input | do $value_or_closure }
_ => ($value_or_closure | default $input)
}

View File

@ -484,6 +484,62 @@ export def operators [
}
}
def get-extension-by-prefix [prefix: string] {
scope commands
| where name starts-with $prefix
| insert extension { get name | parse $"($prefix){ext}" | get ext.0 | $"*.($in)" }
| select extension name
| rename --column { name: command }
}
def get-command-extensions [command: string] {
# low-tech version of `nu-highlight`, which produces suboptimal results with unknown commands
def hl [shape: string] {
let color = $env.config.color_config | get $"shape_($shape)"
$"(ansi $color)($in)(ansi reset)"
}
let extensions = {
"open": {||
[
(
$"('open' | hl internalcall) will attempt to automatically parse the file according to its extension,"
+ $" by calling ('from ext' | hl internalcall) on the file contents. For example,"
+ $" ('open' | hl internalcall) ('file.json' | hl globpattern) will call"
+ $" ('from json' | hl internalcall). If the file is not a supported type, its content will be returned"
+ $" as a binary stream instead."
)
""
"The following extensions are recognized:"
(get-extension-by-prefix "from " | table --index false)
]
}
"save": {||
[
(
$"('save' | hl internalcall) will attempt to automatically serialize its input into the format"
+ $" determined by the file extension, by calling ('to ext' | hl internalcall) before writing the data"
+ $" to the file. For example, ('save' | hl internalcall) ('file.json' | hl globpattern)"
+ $" will call ('to json' | hl internalcall)."
)
""
"The following extensions are recognized:"
(get-extension-by-prefix "to " | table --index false)
]
}
}
if $command in $extensions {
$extensions
| get $command
| do $in
| each { lines | each { $" ($in)" } | str join "\n" }
} else {
[]
}
}
def build-command-page [command: record] {
let description = (if not ($command.description? | is-empty) {[
$command.description
@ -653,6 +709,18 @@ def build-command-page [command: record] {
] | flatten)
} else { [] })
# This section documents how the command can be extended
# E.g. `open` can be extended by adding more `from ...` commands
let extensions = (
get-command-extensions $command.name
| if ($in | is-not-empty) {
prepend [
""
(build-help-header -n "Extensions")
]
} else {}
)
let examples = (if not ($command.examples | is-empty) {[
""
(build-help-header -n "Examples")
@ -683,6 +751,7 @@ def build-command-page [command: record] {
$cli_usage
$subcommands
$rest
$extensions
$examples
] | flatten | str join "\n"
}

View File

@ -83,7 +83,7 @@ def local-transpose_to_record [] {
}
@test
def local-using_cellpaths [] {
def local-using_closure [] {
if ('sqlite' not-in (version).features) { return }
let key = (random uuid)
@ -91,8 +91,8 @@ def local-using_cellpaths [] {
let size_key = (random uuid)
ls
| kv set $name_key $in.name
| kv set $size_key $in.size
| kv set $name_key { get name }
| kv set $size_key { get size }
let expected = "list<string>"
let actual = (kv get $name_key | describe)
@ -106,22 +106,6 @@ def local-using_cellpaths [] {
kv drop $size_key
}
@test
def local-using_closure [] {
if ('sqlite' not-in (version).features) { return }
let key = (random uuid)
kv set $key 5
kv set $key { $in + 1 }
let expected = 6
let actual = (kv get $key)
assert equal $actual $expected
kv drop $key
}
@test
def local-return-entire-list [] {
if ('sqlite' not-in (version).features) { return }
@ -153,7 +137,7 @@ def local-return_value_only [] {
let key = (random uuid)
let expected = 'VALUE'
let actual = ('value' | kv set -r v $key ($in | str upcase))
let actual = ('value' | kv set -r v $key {str upcase})
assert equal $actual $expected
@ -249,7 +233,7 @@ def universal-transpose_to_record [] {
}
@test
def universal-using_cellpaths [] {
def universal-using_closure [] {
if ('sqlite' not-in (version).features) { return }
let key = (random uuid)
@ -259,8 +243,8 @@ def universal-using_cellpaths [] {
let size_key = (random uuid)
ls
| kv set -u $name_key $in.name
| kv set -u $size_key $in.size
| kv set -u $name_key { get name }
| kv set -u $size_key { get size }
let expected = "list<string>"
let actual = (kv get -u $name_key | describe)
@ -275,24 +259,6 @@ def universal-using_cellpaths [] {
rm $env.NU_UNIVERSAL_KV_PATH
}
@test
def universal-using_closure [] {
if ('sqlite' not-in (version).features) { return }
let key = (random uuid)
$env.NU_UNIVERSAL_KV_PATH = (mktemp -t --suffix .sqlite3)
kv set -u $key 5
kv set -u $key { $in + 1 }
let expected = 6
let actual = (kv get -u $key)
assert equal $actual $expected
kv drop -u $key
rm $env.NU_UNIVERSAL_KV_PATH
}
@test
def universal-return-entire-list [] {
if ('sqlite' not-in (version).features) { return }
@ -329,7 +295,7 @@ def universal-return_value_only [] {
let key = (random uuid)
let expected = 'VALUE'
let actual = ('value' | kv set --universal -r v $key ($in | str upcase))
let actual = ('value' | kv set --universal -r v $key {str upcase})
assert equal $actual $expected

View File

@ -3,7 +3,7 @@ authors = ["The Nushell Project Developers", "procs creators"]
description = "Nushell system querying"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-system"
name = "nu-system"
version = "0.103.1"
version = "0.104.1"
edition = "2021"
license = "MIT"

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-table"
edition = "2021"
license = "MIT"
name = "nu-table"
version = "0.103.1"
version = "0.104.1"
[lib]
bench = false
@ -14,13 +14,13 @@ bench = false
workspace = true
[dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.103.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.103.1", default-features = false }
nu-engine = { path = "../nu-engine", version = "0.103.1", default-features = false }
nu-color-config = { path = "../nu-color-config", version = "0.103.1" }
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.104.1", default-features = false }
nu-engine = { path = "../nu-engine", version = "0.104.1", default-features = false }
nu-color-config = { path = "../nu-color-config", version = "0.104.1" }
nu-ansi-term = { workspace = true }
fancy-regex = { workspace = true }
tabled = { workspace = true, features = ["ansi"], default-features = false }
[dev-dependencies]
# nu-test-support = { path="../nu-test-support", version = "0.103.1" }
# nu-test-support = { path="../nu-test-support", version = "0.104.1" }

View File

@ -21,10 +21,12 @@ fn main() {
let headers = to_cell_info_vec(&table_headers);
let rows = to_cell_info_vec(&row_data);
let mut rows = vec![rows; 3];
rows.insert(0, headers);
let mut table = NuTable::new(4, 3);
table.set_row(0, headers);
let mut table = NuTable::from(rows);
for i in 0..3 {
table.set_row(i + 1, rows.clone());
}
table.set_data_style(TextStyle::basic_left());
table.set_header_style(TextStyle::basic_center().style(Style::new().on(Color::Blue)));

View File

@ -71,10 +71,9 @@ pub fn nu_value_to_string_clean(val: &Value, cfg: &Config, style_comp: &StyleCom
(text, style)
}
pub fn error_sign(style_computer: &StyleComputer) -> (String, TextStyle) {
pub fn error_sign(text: String, style_computer: &StyleComputer) -> (String, TextStyle) {
// Though holes are not the same as null, the closure for "empty" is passed a null anyway.
let text = String::from("");
let style = style_computer.compute("empty", &Value::nothing(Span::unknown()));
(text, TextStyle::with_style(Alignment::Center, style))
}
@ -122,9 +121,9 @@ pub fn get_value_style(value: &Value, config: &Config, style_computer: &StyleCom
}
}
pub fn get_empty_style(style_computer: &StyleComputer) -> NuText {
pub fn get_empty_style(text: String, style_computer: &StyleComputer) -> NuText {
(
String::from(""),
text,
TextStyle::with_style(
Alignment::Right,
style_computer.compute("empty", &Value::nothing(Span::unknown())),
@ -187,6 +186,7 @@ pub fn load_theme(mode: TableMode) -> TableTheme {
TableMode::Restructured => TableTheme::restructured(),
TableMode::AsciiRounded => TableTheme::ascii_rounded(),
TableMode::BasicCompact => TableTheme::basic_compact(),
TableMode::Single => TableTheme::single(),
}
}

View File

@ -3,7 +3,7 @@
// NOTE: TODO the above we could expose something like [`WidthCtrl`] in which case we could also laverage the width list build right away.
// currently it seems like we do recacalculate it for `table -e`?
use std::{cmp::min, collections::HashMap};
use std::cmp::{max, min};
use nu_ansi_term::Style;
use nu_color_config::TextStyle;
@ -13,13 +13,14 @@ use tabled::{
builder::Builder,
grid::{
ansi::ANSIBuf,
colors::Colors,
config::{
AlignmentHorizontal, ColoredConfig, Entity, Indent, Position, Sides, SpannedConfig,
},
dimension::{CompleteDimension, Dimension, PeekableGridDimension},
dimension::{CompleteDimension, Dimension, IterGridDimension},
records::{
vec_records::{Text, VecRecords},
ExactRecords, Records,
vec_records::{Cell, Text, VecRecords},
IntoRecords, IterRecords, Records,
},
},
settings::{
@ -43,24 +44,30 @@ pub type NuRecordsValue = Text<String>;
/// NuTable is a table rendering implementation.
#[derive(Debug, Clone)]
pub struct NuTable {
data: NuRecords,
data: Vec<Vec<NuRecordsValue>>,
widths: Vec<usize>,
count_rows: usize,
count_cols: usize,
styles: Styles,
alignments: Alignments,
config: TableConfig,
}
impl NuTable {
/// Creates an empty [`NuTable`] instance.
pub fn new(count_rows: usize, count_columns: usize) -> Self {
pub fn new(count_rows: usize, count_cols: usize) -> Self {
Self {
data: VecRecords::new(vec![vec![Text::default(); count_columns]; count_rows]),
styles: Styles::default(),
alignments: Alignments {
data: AlignmentHorizontal::Left,
index: AlignmentHorizontal::Right,
header: AlignmentHorizontal::Center,
columns: HashMap::default(),
cells: HashMap::default(),
data: vec![vec![Text::default(); count_cols]; count_rows],
widths: vec![2; count_cols],
count_rows,
count_cols,
styles: Styles {
cfg: ColoredConfig::default(),
alignments: CellConfiguration {
data: AlignmentHorizontal::Left,
index: AlignmentHorizontal::Right,
header: AlignmentHorizontal::Center,
},
colors: CellConfiguration::default(),
},
config: TableConfig {
theme: TableTheme::basic(),
@ -76,84 +83,125 @@ impl NuTable {
/// Return amount of rows.
pub fn count_rows(&self) -> usize {
self.data.count_rows()
self.count_rows
}
/// Return amount of columns.
pub fn count_columns(&self) -> usize {
self.data.count_columns()
self.count_cols
}
pub fn insert(&mut self, pos: Position, text: String) {
self.data[pos.row][pos.col] = Text::new(text);
}
pub fn insert_row(&mut self, index: usize, row: Vec<String>) {
let data = &mut self.data[index];
for (col, text) in row.into_iter().enumerate() {
data[col] = Text::new(text);
}
let text = Text::new(text);
self.widths[pos.col] = max(
self.widths[pos.col],
text.width() + indent_sum(self.config.indent),
);
self.data[pos.row][pos.col] = text;
}
pub fn set_row(&mut self, index: usize, row: Vec<NuRecordsValue>) {
assert_eq!(self.data[index].len(), row.len());
for (i, text) in row.iter().enumerate() {
self.widths[i] = max(
self.widths[i],
text.width() + indent_sum(self.config.indent),
);
}
self.data[index] = row;
}
pub fn set_column_style(&mut self, column: usize, style: TextStyle) {
if let Some(style) = style.color_style {
let style = convert_style(style);
self.styles.columns.insert(column, style);
pub fn pop_column(&mut self, count: usize) {
self.count_cols -= count;
self.widths.truncate(self.count_cols);
for row in &mut self.data[..] {
row.truncate(self.count_cols);
}
let alignment = convert_alignment(style.alignment);
if alignment != self.alignments.data {
self.alignments.columns.insert(column, alignment);
// set to default styles of the popped columns
for i in 0..count {
let col = self.count_cols + i;
for row in 0..self.count_rows {
self.styles
.cfg
.set_alignment_horizontal(Entity::Cell(row, col), self.styles.alignments.data);
self.styles
.cfg
.set_color(Entity::Cell(row, col), ANSIBuf::default());
}
}
}
pub fn push_column(&mut self, text: String) {
let value = Text::new(text);
self.widths
.push(value.width() + indent_sum(self.config.indent));
for row in &mut self.data[..] {
row.push(value.clone());
}
self.count_cols += 1;
}
pub fn insert_style(&mut self, pos: Position, style: TextStyle) {
if let Some(style) = style.color_style {
let style = convert_style(style);
self.styles.cells.insert(pos, style);
self.styles.cfg.set_color(pos.into(), style.into());
}
let alignment = convert_alignment(style.alignment);
if alignment != self.alignments.data {
self.alignments.cells.insert(pos, alignment);
if alignment != self.styles.alignments.data {
self.styles
.cfg
.set_alignment_horizontal(pos.into(), alignment);
}
}
pub fn set_header_style(&mut self, style: TextStyle) {
if let Some(style) = style.color_style {
let style = convert_style(style);
self.styles.header = style;
self.styles.colors.header = style;
}
self.alignments.header = convert_alignment(style.alignment);
self.styles.alignments.header = convert_alignment(style.alignment);
}
pub fn set_index_style(&mut self, style: TextStyle) {
if let Some(style) = style.color_style {
let style = convert_style(style);
self.styles.index = style;
self.styles.colors.index = style;
}
self.alignments.index = convert_alignment(style.alignment);
self.styles.alignments.index = convert_alignment(style.alignment);
}
pub fn set_data_style(&mut self, style: TextStyle) {
if let Some(style) = style.color_style {
let style = convert_style(style);
self.styles.data = style;
if !style.is_plain() {
let style = convert_style(style);
self.styles.cfg.set_color(Entity::Global, style.into());
}
}
self.alignments.data = convert_alignment(style.alignment);
let alignment = convert_alignment(style.alignment);
self.styles
.cfg
.set_alignment_horizontal(Entity::Global, alignment);
self.styles.alignments.data = alignment;
}
// NOTE: Crusial to be called before data changes (todo fix interface)
pub fn set_indent(&mut self, indent: TableIndent) {
self.config.indent = indent;
let pad = indent_sum(indent);
for w in &mut self.widths {
*w = pad;
}
}
pub fn set_theme(&mut self, theme: TableTheme) {
@ -180,7 +228,9 @@ impl NuTable {
self.config.border_color = (!color.is_plain()).then_some(color);
}
pub fn get_records_mut(&mut self) -> &mut NuRecords {
// NOTE: BE CAREFUL TO KEEP WIDTH UNCHANGED
// TODO: fix interface
pub fn get_records_mut(&mut self) -> &mut [Vec<NuRecordsValue>] {
&mut self.data
}
@ -194,32 +244,42 @@ impl NuTable {
/// Return a total table width.
pub fn total_width(&self) -> usize {
let config = create_config(&self.config.theme, false, None);
let pad = indent_sum(self.config.indent);
let widths = build_width(&self.data, pad);
get_total_width2(&widths, &config)
get_total_width2(&self.widths, &config)
}
}
impl From<Vec<Vec<Text<String>>>> for NuTable {
fn from(value: Vec<Vec<Text<String>>>) -> Self {
let mut nutable = Self::new(0, 0);
nutable.data = VecRecords::new(value);
let count_rows = value.len();
let count_cols = if value.is_empty() { 0 } else { value[0].len() };
nutable
let mut t = Self::new(count_rows, count_cols);
t.data = value;
table_recalculate_widths(&mut t);
t
}
}
type Alignments = CellConfiguration<AlignmentHorizontal>;
fn table_recalculate_widths(t: &mut NuTable) {
let pad = indent_sum(t.config.indent);
let records = IterRecords::new(&t.data, t.count_cols, Some(t.count_rows));
let widths = build_width(records, pad);
t.widths = widths;
}
type Styles = CellConfiguration<Color>;
#[derive(Debug, Default, Clone)]
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Hash)]
struct CellConfiguration<Value> {
data: Value,
index: Value,
header: Value,
columns: HashMap<usize, Value>,
cells: HashMap<Position, Value>,
data: Value,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Styles {
cfg: ColoredConfig,
colors: CellConfiguration<Color>,
alignments: CellConfiguration<AlignmentHorizontal>,
}
#[derive(Debug, Clone)]
@ -304,49 +364,72 @@ fn table_insert_footer_if(t: &mut NuTable) {
}
fn table_truncate(t: &mut NuTable, termwidth: usize) -> Option<WidthEstimation> {
let widths = maybe_truncate_columns(&mut t.data, &t.config, termwidth);
let widths = maybe_truncate_columns(&mut t.data, t.widths.clone(), &t.config, termwidth);
if widths.needed.is_empty() {
return None;
}
// reset style for last column which is a trail one
if widths.trail {
let col = widths.needed.len() - 1;
for row in 0..t.count_rows {
t.styles
.cfg
.set_alignment_horizontal(Entity::Cell(row, col), t.styles.alignments.data);
t.styles
.cfg
.set_color(Entity::Cell(row, col), ANSIBuf::default());
}
}
Some(widths)
}
fn remove_header(t: &mut NuTable) -> HeadInfo {
let head: Vec<String> = t
// move settings by one row down
for row in 1..t.data.len() {
for col in 0..t.count_cols {
let alignment = *t
.styles
.cfg
.get_alignment_horizontal(Position::new(row, col));
if alignment != t.styles.alignments.data {
t.styles
.cfg
.set_alignment_horizontal(Entity::Cell(row - 1, col), alignment);
}
// TODO: use get_color from upstream (when released)
let color = t
.styles
.cfg
.get_colors()
.get_color(Position::new(row, col))
.cloned();
if let Some(color) = color {
t.styles.cfg.set_color(Entity::Cell(row - 1, col), color);
}
}
}
let head = t
.data
.remove(0)
.into_iter()
.map(|s| s.to_string())
.collect();
let align = t.alignments.header;
let color = if is_color_empty(&t.styles.header) {
None
} else {
Some(t.styles.header.clone())
};
// move settings by one row down
t.alignments.cells = t
.alignments
.cells
.drain()
.filter(|(k, _)| k.row != 0)
.map(|(k, v)| (k - (1, 0), v))
.collect();
t.alignments.header = AlignmentHorizontal::Center;
// WE NEED TO RELCULATE WIDTH.
// TODO: cause we have configuration beforehand we can just not calculate it in?
table_recalculate_widths(t);
// move settings by one row down
t.styles.cells = t
.styles
.cells
.drain()
.filter(|(k, _)| k.row != 0)
.map(|(k, v)| (k - (1, 0), v))
.collect();
t.styles.header = Color::empty();
let alignment = t.styles.alignments.header;
let color = get_color_if_exists(&t.styles.colors.header);
HeadInfo::new(head, align, color)
t.styles.alignments.header = AlignmentHorizontal::Center;
t.styles.colors.header = Color::empty();
HeadInfo::new(head, alignment, color)
}
fn draw_table(
@ -359,19 +442,24 @@ fn draw_table(
structure.with_footer = structure.with_footer && head.is_none();
let sep_color = t.config.border_color;
let data: Vec<Vec<_>> = t.data.into();
let data = t.data;
let mut table = Builder::from_vec(data).build();
set_styles(&mut table, t.styles, &structure);
set_indent(&mut table, t.config.indent);
load_theme(&mut table, &t.config.theme, &structure, sep_color);
align_table(&mut table, t.alignments, &structure);
colorize_table(&mut table, t.styles, &structure);
truncate_table(&mut table, &t.config, width, termwidth);
table_set_border_header(&mut table, head, &t.config);
table_to_string(table, termwidth)
}
fn set_styles(table: &mut Table, styles: Styles, structure: &TableStructure) {
table.with(styles.cfg);
align_table(table, styles.alignments, structure);
colorize_table(table, styles.colors, structure);
}
fn table_set_border_header(table: &mut Table, head: Option<HeadInfo>, cfg: &TableConfig) {
let head = match head {
Some(head) => head,
@ -420,7 +508,6 @@ fn set_indent(table: &mut Table, indent: TableIndent) {
fn table_to_string(table: Table, termwidth: usize) -> Option<String> {
let total_width = table.total_width();
if total_width > termwidth {
None
} else {
@ -462,15 +549,23 @@ struct WidthEstimation {
#[allow(dead_code)]
total: usize,
truncate: bool,
trail: bool,
}
impl WidthEstimation {
fn new(original: Vec<usize>, needed: Vec<usize>, total: usize, truncate: bool) -> Self {
fn new(
original: Vec<usize>,
needed: Vec<usize>,
total: usize,
truncate: bool,
trail: bool,
) -> Self {
Self {
original,
needed,
total,
truncate,
trail,
}
}
}
@ -546,18 +641,12 @@ fn width_ctrl_truncate(
dims.set_widths(ctrl.width.needed);
}
fn align_table(table: &mut Table, alignments: Alignments, structure: &TableStructure) {
// note: IDK why it was originally here. Is it desirable? I maybe PerCell better?
fn align_table(
table: &mut Table,
alignments: CellConfiguration<AlignmentHorizontal>,
structure: &TableStructure,
) {
table.with(AlignmentStrategy::PerLine);
table.with(Alignment::from(alignments.data));
for (column, alignment) in alignments.columns {
table.modify(Columns::single(column), Alignment::from(alignment));
}
for (pos, alignment) in alignments.cells {
table.modify(pos, Alignment::from(alignment));
}
if structure.with_header {
table.modify(Rows::first(), AlignmentStrategy::PerCell);
@ -574,23 +663,7 @@ fn align_table(table: &mut Table, alignments: Alignments, structure: &TableStruc
}
}
fn colorize_table(table: &mut Table, styles: Styles, structure: &TableStructure) {
if !is_color_empty(&styles.data) {
table.with(styles.data);
}
for (column, color) in styles.columns {
if !is_color_empty(&color) {
table.modify(Columns::single(column), color);
}
}
for (pos, color) in styles.cells {
if !is_color_empty(&color) {
table.modify(pos, color);
}
}
fn colorize_table(table: &mut Table, styles: CellConfiguration<Color>, structure: &TableStructure) {
if structure.with_index && !is_color_empty(&styles.index) {
table.modify(Columns::first(), styles.index);
}
@ -632,7 +705,8 @@ fn load_theme(
}
fn maybe_truncate_columns(
data: &mut NuRecords,
data: &mut Vec<Vec<NuRecordsValue>>,
widths: Vec<usize>,
cfg: &TableConfig,
termwidth: usize,
) -> WidthEstimation {
@ -642,15 +716,16 @@ fn maybe_truncate_columns(
let preserve_content = termwidth > TERMWIDTH_THRESHOLD;
if preserve_content {
truncate_columns_by_columns(data, &cfg.theme, pad, termwidth)
truncate_columns_by_columns(data, widths, &cfg.theme, pad, termwidth)
} else {
truncate_columns_by_content(data, &cfg.theme, pad, termwidth)
truncate_columns_by_content(data, widths, &cfg.theme, pad, termwidth)
}
}
// VERSION where we are showing AS LITTLE COLUMNS AS POSSIBLE but WITH AS MUCH CONTENT AS POSSIBLE.
fn truncate_columns_by_content(
data: &mut NuRecords,
data: &mut Vec<Vec<NuRecordsValue>>,
widths: Vec<usize>,
theme: &TableTheme,
pad: usize,
termwidth: usize,
@ -661,13 +736,14 @@ fn truncate_columns_by_content(
let trailing_column_width = TRAILING_COLUMN_WIDTH + pad;
let min_column_width = MIN_ACCEPTABLE_WIDTH + pad;
let count_columns = data[0].len();
let config = create_config(theme, false, None);
let widths_original = build_width(data, pad);
let widths_original = widths;
let mut widths = vec![];
let borders = config.get_borders();
let vertical = borders.has_vertical() as usize;
let count_columns = data.count_columns();
let mut width = borders.has_left() as usize + borders.has_right() as usize;
let mut truncate_pos = 0;
@ -688,7 +764,7 @@ fn truncate_columns_by_content(
}
if truncate_pos == count_columns {
return WidthEstimation::new(widths_original, widths, width, false);
return WidthEstimation::new(widths_original, widths, width, false, false);
}
if truncate_pos == 0 {
@ -705,11 +781,11 @@ fn truncate_columns_by_content(
widths.push(trailing_column_width);
width += trailing_column_width + vertical;
return WidthEstimation::new(widths_original, widths, width, true);
return WidthEstimation::new(widths_original, widths, width, true, true);
}
}
return WidthEstimation::new(widths_original, widths, width, false);
return WidthEstimation::new(widths_original, widths, width, false, false);
}
let available = termwidth - width;
@ -721,7 +797,7 @@ fn truncate_columns_by_content(
widths.push(w);
width += w + vertical;
return WidthEstimation::new(widths_original, widths, width, true);
return WidthEstimation::new(widths_original, widths, width, true, false);
}
// special case where the last column is smaller then a trailing column
@ -739,7 +815,7 @@ fn truncate_columns_by_content(
widths.push(next_column_width);
width += next_column_width + vertical;
return WidthEstimation::new(widths_original, widths, width, true);
return WidthEstimation::new(widths_original, widths, width, true, false);
}
}
@ -756,7 +832,7 @@ fn truncate_columns_by_content(
widths.push(trailing_column_width);
width += trailing_column_width + vertical;
return WidthEstimation::new(widths_original, widths, width, true);
return WidthEstimation::new(widths_original, widths, width, true, true);
}
if available >= trailing_column_width + vertical {
@ -766,7 +842,7 @@ fn truncate_columns_by_content(
widths.push(trailing_column_width);
width += trailing_column_width + vertical;
return WidthEstimation::new(widths_original, widths, width, false);
return WidthEstimation::new(widths_original, widths, width, false, true);
}
let last_width = widths.last().cloned().expect("ok");
@ -790,7 +866,7 @@ fn truncate_columns_by_content(
widths.push(trailing_column_width);
width += trailing_column_width + vertical;
return WidthEstimation::new(widths_original, widths, width, true);
return WidthEstimation::new(widths_original, widths, width, true, true);
}
}
@ -804,10 +880,10 @@ fn truncate_columns_by_content(
if widths.len() == 1 {
// nothing to show anyhow
return WidthEstimation::new(widths_original, vec![], width, false);
return WidthEstimation::new(widths_original, vec![], width, false, true);
}
WidthEstimation::new(widths_original, widths, width, false)
WidthEstimation::new(widths_original, widths, width, false, true)
}
// VERSION where we are showing AS MANY COLUMNS AS POSSIBLE but as a side affect they MIGHT CONTAIN AS LITTLE CONTENT AS POSSIBLE
@ -821,7 +897,8 @@ fn truncate_columns_by_content(
// Point being of the column needs more space we do can give it a little more based on it's distance from the start.
// Percentage wise.
fn truncate_columns_by_columns(
data: &mut NuRecords,
data: &mut Vec<Vec<NuRecordsValue>>,
widths: Vec<usize>,
theme: &TableTheme,
pad: usize,
termwidth: usize,
@ -832,13 +909,14 @@ fn truncate_columns_by_columns(
let trailing_column_width = TRAILING_COLUMN_WIDTH + pad;
let min_column_width = MIN_ACCEPTABLE_WIDTH + pad;
let count_columns = data[0].len();
let config = create_config(theme, false, None);
let widths_original = build_width(data, pad);
let widths_original = widths;
let mut widths = vec![];
let borders = config.get_borders();
let vertical = borders.has_vertical() as usize;
let count_columns = data.count_columns();
let mut width = borders.has_left() as usize + borders.has_right() as usize;
let mut truncate_pos = 0;
@ -860,7 +938,7 @@ fn truncate_columns_by_columns(
}
if truncate_pos == 0 {
return WidthEstimation::new(widths_original, widths, width, false);
return WidthEstimation::new(widths_original, widths, width, false, false);
}
let mut available = termwidth - width;
@ -885,7 +963,7 @@ fn truncate_columns_by_columns(
}
if truncate_pos == count_columns {
return WidthEstimation::new(widths_original, widths, width, true);
return WidthEstimation::new(widths_original, widths, width, true, false);
}
if available >= trailing_column_width + vertical {
@ -895,7 +973,7 @@ fn truncate_columns_by_columns(
widths.push(trailing_column_width);
width += trailing_column_width + vertical;
return WidthEstimation::new(widths_original, widths, width, true);
return WidthEstimation::new(widths_original, widths, width, true, true);
}
truncate_rows(data, truncate_pos - 1);
@ -906,7 +984,7 @@ fn truncate_columns_by_columns(
widths.push(trailing_column_width);
width += trailing_column_width;
WidthEstimation::new(widths_original, widths, width, true)
WidthEstimation::new(widths_original, widths, width, true, true)
}
fn get_total_width2(widths: &[usize], cfg: &ColoredConfig) -> usize {
@ -924,37 +1002,22 @@ fn create_config(theme: &TableTheme, with_header: bool, color: Option<Style>) ->
table.get_config().clone()
}
fn push_empty_column(data: &mut NuRecords) {
let records = std::mem::take(data);
let mut inner: Vec<Vec<_>> = records.into();
fn push_empty_column(data: &mut Vec<Vec<NuRecordsValue>>) {
let empty_cell = Text::new(String::from(EMPTY_COLUMN_TEXT));
for row in &mut inner {
for row in data {
row.push(empty_cell.clone());
}
*data = VecRecords::new(inner);
}
fn duplicate_row(data: &mut NuRecords, row: usize) {
let records = std::mem::take(data);
let mut inner: Vec<Vec<_>> = records.into();
let duplicate = inner[row].clone();
inner.push(duplicate);
*data = VecRecords::new(inner);
fn duplicate_row(data: &mut Vec<Vec<NuRecordsValue>>, row: usize) {
let duplicate = data[row].clone();
data.push(duplicate);
}
fn truncate_rows(data: &mut NuRecords, count: usize) {
let records = std::mem::take(data);
let mut inner: Vec<Vec<_>> = records.into();
for row in &mut inner {
fn truncate_rows(data: &mut Vec<Vec<NuRecordsValue>>, count: usize) {
for row in data {
row.truncate(count);
}
*data = VecRecords::new(inner);
}
fn convert_alignment(alignment: nu_color_config::Alignment) -> AlignmentHorizontal {
@ -974,7 +1037,11 @@ impl<R> TableOption<R, ColoredConfig, CompleteDimension<'_>> for SetDimensions {
}
}
fn build_width(records: &NuRecords, pad: usize) -> Vec<usize> {
fn build_width<R>(records: R, pad: usize) -> Vec<usize>
where
R: Records,
<R::Iter as IntoRecords>::Cell: AsRef<str>,
{
// TODO: Expose not spaned version (could be optimized).
let mut cfg = SpannedConfig::default();
let padding = Sides {
@ -984,7 +1051,7 @@ fn build_width(records: &NuRecords, pad: usize) -> Vec<usize> {
cfg.set_padding(Entity::Global, padding);
PeekableGridDimension::width(records, &cfg)
IterGridDimension::width(records, &cfg)
}
// It's laverages a use of guuaranted cached widths before hand
@ -1043,3 +1110,11 @@ fn theme_copy_horizontal_line(theme: &mut tabled::settings::Theme, from: usize,
theme.insert_horizontal_line(to, *line);
}
}
pub fn get_color_if_exists(c: &Color) -> Option<Color> {
if !is_color_empty(c) {
Some(c.clone())
} else {
None
}
}

View File

@ -156,6 +156,16 @@ impl TableTheme {
Self::new(theme, full)
}
pub fn single() -> TableTheme {
let full = Style::modern()
.corner_top_left('┌')
.corner_top_right('┐')
.corner_bottom_left('└')
.corner_bottom_right('┘');
Self::new(Style::sharp(), full)
}
pub fn none() -> TableTheme {
Self::new(Style::blank(), Style::blank())
}

View File

@ -1,9 +1,8 @@
use std::{cmp::max, collections::HashMap};
use std::cmp::max;
use nu_color_config::{Alignment, StyleComputer, TextStyle};
use nu_engine::column::get_columns;
use nu_protocol::{Config, Record, ShellError, Span, Value};
use tabled::grid::config::Position;
use crate::{
@ -14,7 +13,7 @@ use crate::{
},
string_width,
types::has_index,
NuRecordsValue, NuTable, TableOpts, TableOutput,
NuTable, TableOpts, TableOutput,
};
#[derive(Debug, Clone)]
@ -106,7 +105,7 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
const PADDING_SPACE: usize = 2;
const SPLIT_LINE_SPACE: usize = 1;
const ADDITIONAL_CELL_SPACE: usize = PADDING_SPACE + SPLIT_LINE_SPACE;
const MIN_CELL_CONTENT_WIDTH: usize = 1;
const MIN_CELL_CONTENT_WIDTH: usize = 3;
const TRUNCATE_CONTENT_WIDTH: usize = 3;
const TRUNCATE_CELL_WIDTH: usize = TRUNCATE_CONTENT_WIDTH + PADDING_SPACE;
@ -124,10 +123,7 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
}
let headers = get_columns(input);
let with_index = has_index(&cfg.opts, &headers);
let row_offset = cfg.opts.index_offset;
let mut rows_count = 0usize;
// The header with the INDEX is removed from the table headers since
// it is added to the natural table index
@ -135,165 +131,171 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
.into_iter()
.filter(|header| header != INDEX_COLUMN_NAME)
.collect();
let with_header = !headers.is_empty();
let row_offset = cfg.opts.index_offset;
let mut data = vec![vec![]; input.len() + with_header as usize];
let mut data_styles = HashMap::new();
let mut total_rows = 0usize;
if with_index {
if with_header {
data[0].push(NuRecordsValue::exact(String::from("#"), 1, vec![]));
}
for (row, item) in input.iter().enumerate() {
cfg.opts.signals.check(cfg.opts.span)?;
check_value(item)?;
let index = row + row_offset;
let text = item
.as_record()
.ok()
.and_then(|val| val.get(INDEX_COLUMN_NAME))
.map(|value| value.to_expanded_string("", cfg.opts.config))
.unwrap_or_else(|| index.to_string());
let row = row + with_header as usize;
let value = NuRecordsValue::new(text);
data[row].push(value);
}
let column_width = string_width(data[data.len() - 1][0].as_ref());
if column_width + ADDITIONAL_CELL_SPACE > available_width {
available_width = 0;
} else {
available_width -= column_width + ADDITIONAL_CELL_SPACE;
}
}
if !with_header {
if available_width > ADDITIONAL_CELL_SPACE {
available_width -= PADDING_SPACE;
} else {
if !with_index && !with_header {
if available_width <= ADDITIONAL_CELL_SPACE {
// it means we have no space left for actual content;
// which means there's no point in index itself if it was even used.
// so we do not print it.
return Ok(None);
}
available_width -= PADDING_SPACE;
let mut table = NuTable::new(input.len(), 1);
table.set_index_style(get_index_style(&cfg.opts.style_computer));
table.set_header_style(get_header_style(&cfg.opts.style_computer));
table.set_indent(cfg.opts.config.table.padding);
for (row, item) in input.iter().enumerate() {
cfg.opts.signals.check(cfg.opts.span)?;
check_value(item)?;
let inner_cfg = cfg_expand_reset_table(cfg.clone(), available_width);
let mut cell = expand_entry(item, inner_cfg);
let cell = expand_entry(item, inner_cfg);
let value_width = string_width(&cell.text);
if value_width > available_width {
// it must only happen when a string is produced, so we can safely wrap it.
// (it might be string table representation as well) (I guess I mean default { table ...} { list ...})
//
// todo: Maybe convert_to_table2_entry could do for strings to not mess caller code?
table.insert(Position::new(row, 0), cell.text);
table.insert_style(Position::new(row, 0), cell.style);
cell.text = wrap_text(&cell.text, available_width, cfg.opts.config);
}
let value = NuRecordsValue::new(cell.text);
data[row].push(value);
data_styles.insert(Position::new(row, with_index as usize), cell.style);
rows_count = rows_count.saturating_add(cell.size);
total_rows = total_rows.saturating_add(cell.size);
}
let mut table = NuTable::from(data);
table.set_indent(cfg.opts.config.table.padding);
table.set_index_style(get_index_style(&cfg.opts.style_computer));
set_data_styles(&mut table, data_styles);
return Ok(Some(TableOutput::new(table, false, with_index, rows_count)));
return Ok(Some(TableOutput::new(table, false, false, total_rows)));
}
if !headers.is_empty() {
let mut pad_space = PADDING_SPACE;
if headers.len() > 1 {
pad_space += SPLIT_LINE_SPACE;
if !with_header && with_index {
let mut table = NuTable::new(input.len(), 2);
table.set_index_style(get_index_style(&cfg.opts.style_computer));
table.set_header_style(get_header_style(&cfg.opts.style_computer));
table.set_indent(cfg.opts.config.table.padding);
let mut index_column_width = 0;
for (row, item) in input.iter().enumerate() {
cfg.opts.signals.check(cfg.opts.span)?;
check_value(item)?;
let index = row + row_offset;
let index_value = item
.as_record()
.ok()
.and_then(|val| val.get(INDEX_COLUMN_NAME))
.map(|value| value.to_expanded_string("", cfg.opts.config))
.unwrap_or_else(|| index.to_string());
let index_width = string_width(&index_value);
if available_width <= index_width + ADDITIONAL_CELL_SPACE + PADDING_SPACE {
// NOTE: we don't wanna wrap index; so we return
return Ok(None);
}
table.insert(Position::new(row, 0), index_value);
index_column_width = max(index_column_width, index_width);
}
if available_width < pad_space {
// there's no space for actual data so we don't return index if it's present.
// (also see the comment after the loop)
available_width -= index_column_width + ADDITIONAL_CELL_SPACE + PADDING_SPACE;
for (row, item) in input.iter().enumerate() {
cfg.opts.signals.check(cfg.opts.span)?;
check_value(item)?;
let inner_cfg = cfg_expand_reset_table(cfg.clone(), available_width);
let cell = expand_entry(item, inner_cfg);
table.insert(Position::new(row, 1), cell.text);
table.insert_style(Position::new(row, 1), cell.style);
total_rows = total_rows.saturating_add(cell.size);
}
return Ok(Some(TableOutput::new(table, false, true, total_rows)));
}
// NOTE: redefine to not break above logic (fixme)
let mut available_width = cfg.opts.width - SPLIT_LINE_SPACE;
let mut table = NuTable::new(input.len() + 1, headers.len() + with_index as usize);
table.set_index_style(get_index_style(&cfg.opts.style_computer));
table.set_header_style(get_header_style(&cfg.opts.style_computer));
table.set_indent(cfg.opts.config.table.padding);
let mut widths = Vec::new();
if with_index {
table.insert(Position::new(0, 0), String::from("#"));
let mut index_column_width = 1;
for (row, item) in input.iter().enumerate() {
cfg.opts.signals.check(cfg.opts.span)?;
check_value(item)?;
let index = row + row_offset;
let index_value = item
.as_record()
.ok()
.and_then(|val| val.get(INDEX_COLUMN_NAME))
.map(|value| value.to_expanded_string("", cfg.opts.config))
.unwrap_or_else(|| index.to_string());
let index_width = string_width(&index_value);
table.insert(Position::new(row + 1, 0), index_value);
index_column_width = max(index_column_width, index_width);
}
if available_width <= index_column_width + ADDITIONAL_CELL_SPACE {
// NOTE: we don't wanna wrap index; so we return
return Ok(None);
}
available_width -= index_column_width + ADDITIONAL_CELL_SPACE;
widths.push(index_column_width);
}
let count_columns = headers.len();
let mut widths = Vec::new();
let mut truncate = false;
let mut rendered_column = 0;
for (col, header) in headers.into_iter().enumerate() {
let column = col + with_index as usize;
let extra_space = PADDING_SPACE + SPLIT_LINE_SPACE;
if available_width <= extra_space {
table.pop_column(table.count_columns() - column);
widths.pop();
truncate = true;
break;
}
let mut available = available_width - extra_space;
// We want to reserver some space for next column
// If we can't fit it in it will be popped anyhow.
let is_last_column = col + 1 == count_columns;
let mut pad_space = PADDING_SPACE;
if !is_last_column {
pad_space += SPLIT_LINE_SPACE;
if !is_last_column && available > TRUNCATE_CELL_WIDTH {
available -= TRUNCATE_CELL_WIDTH;
}
let mut available = available_width - pad_space;
let mut total_column_rows = 0usize;
let mut column_width = 0;
if !is_last_column {
// we need to make sure that we have a space for a next column if we use available width
// so we might need to decrease a bit it.
// we consider a header width be a minimum width
let pad_space = PADDING_SPACE + TRUNCATE_CONTENT_WIDTH;
if available > pad_space {
// In we have no space for a next column,
// We consider showing something better then nothing,
// So we try to decrease the width to show at least a truncution column
available -= pad_space;
} else {
truncate = true;
break;
}
if available < column_width {
truncate = true;
break;
}
}
let mut column_rows = 0usize;
for (row, item) in input.iter().enumerate() {
cfg.opts.signals.check(cfg.opts.span)?;
check_value(item)?;
let inner_cfg = cfg_expand_reset_table(cfg.clone(), available);
let mut cell = expand_entry_with_header(item, &header, inner_cfg);
let mut value_width = string_width(&cell.text);
if value_width > available {
// it must only happen when a string is produced, so we can safely wrap it.
// (it might be string table representation as well)
cell.text = wrap_text(&cell.text, available, cfg.opts.config);
value_width = available;
}
let cell = expand_entry_with_header(item, &header, inner_cfg);
let value_width = string_width(&cell.text); // TODO: optimize cause when we expand we alrready know the width (most of the time or all)
column_width = max(column_width, value_width);
let value = NuRecordsValue::new(cell.text);
data[row + 1].push(value);
data_styles.insert(
Position::new(row + 1, col + with_index as usize),
cell.style,
);
table.insert(Position::new(row + 1, column), cell.text);
table.insert_style(Position::new(row + 1, column), cell.style);
column_rows = column_rows.saturating_add(cell.size);
total_column_rows = total_column_rows.saturating_add(cell.size);
}
let mut head_width = string_width(&header);
@ -303,50 +305,37 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
head_width = available;
}
let head_cell = NuRecordsValue::new(header);
data[0].push(head_cell);
table.insert(Position::new(0, column), header);
column_width = max(column_width, head_width);
if column_width > available {
// remove the column we just inserted
for row in &mut data {
row.pop();
}
truncate = true;
break;
}
assert!(column_width <= available);
widths.push(column_width);
available_width -= pad_space + column_width;
available_width -= column_width + extra_space;
rendered_column += 1;
rows_count = std::cmp::max(rows_count, column_rows);
}
if truncate && rendered_column == 0 {
// it means that no actual data was rendered, there might be only index present,
// so there's no point in rendering the table.
//
// It's actually quite important in case it's called recursively,
// cause we will back up to the basic table view as a string e.g. '[table 123 columns]'.
//
// But potentially if its reached as a 1st called function we might would love to see the index.
return Ok(None);
total_rows = std::cmp::max(total_rows, total_column_rows);
}
if truncate {
if rendered_column == 0 {
// it means that no actual data was rendered, there might be only index present,
// so there's no point in rendering the table.
//
// It's actually quite important in case it's called recursively,
// cause we will back up to the basic table view as a string e.g. '[table 123 columns]'.
//
// But potentially if its reached as a 1st called function we might would love to see the index.
return Ok(None);
}
if available_width < TRUNCATE_CELL_WIDTH {
// back up by removing last column.
// it's LIKELY that removing only 1 column will leave us enough space for a shift column.
while let Some(width) = widths.pop() {
for row in &mut data {
row.pop();
}
table.pop_column(1);
available_width += width + PADDING_SPACE;
if !widths.is_empty() {
@ -367,22 +356,12 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
let is_last_column = widths.len() == count_columns;
if !is_last_column {
let shift = NuRecordsValue::exact(String::from("..."), 3, vec![]);
for row in &mut data {
row.push(shift.clone());
}
table.push_column(String::from("..."));
widths.push(3);
}
}
let mut table = NuTable::from(data);
table.set_index_style(get_index_style(&cfg.opts.style_computer));
table.set_header_style(get_header_style(&cfg.opts.style_computer));
table.set_indent(cfg.opts.config.table.padding);
set_data_styles(&mut table, data_styles);
Ok(Some(TableOutput::new(table, true, with_index, rows_count)))
Ok(Some(TableOutput::new(table, true, with_index, total_rows)))
}
fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
@ -403,10 +382,13 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
let value_width = cfg.opts.width - key_width - count_borders - padding - padding;
let mut count_rows = 0usize;
let mut total_rows = 0usize;
let mut data = Vec::with_capacity(record.len());
for (key, value) in record {
let mut table = NuTable::new(record.len(), 2);
table.set_index_style(get_key_style(&cfg));
table.set_indent(cfg.opts.config.table.padding);
for (i, (key, value)) in record.iter().enumerate() {
cfg.opts.signals.check(cfg.opts.span)?;
let cell = match expand_value(value, value_width, &cfg)? {
@ -414,29 +396,24 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
None => return Ok(None),
};
let value = cell.text;
let mut key = key.to_owned();
// we want to have a key being aligned to 2nd line,
// we could use Padding for it but,
// the easiest way to do so is just push a new_line char before
let mut key = key.to_owned();
let is_key_on_next_line = !key.is_empty() && cell.is_expanded && theme.borders_has_top();
if is_key_on_next_line {
key.insert(0, '\n');
}
let key = NuRecordsValue::new(key);
let val = NuRecordsValue::new(cell.text);
let row = vec![key, val];
table.insert(Position::new(i, 0), key);
table.insert(Position::new(i, 1), value);
data.push(row);
count_rows = count_rows.saturating_add(cell.size);
total_rows = total_rows.saturating_add(cell.size);
}
let mut table = NuTable::from(data);
table.set_index_style(get_key_style(&cfg));
table.set_indent(cfg.opts.config.table.padding);
let mut out = TableOutput::new(table, false, true, count_rows);
let mut out = TableOutput::new(table, false, true, total_rows);
configure_table(
&mut out,
@ -446,7 +423,7 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
);
maybe_expand_table(out, cfg.opts.width)
.map(|value| value.map(|value| CellOutput::clean(value, count_rows, false)))
.map(|value| value.map(|value| CellOutput::clean(value, total_rows, false)))
}
// the flag is used as an optimization to not do `value.lines().count()` search.
@ -510,7 +487,10 @@ fn expand_entry_with_header(item: &Value, header: &str, cfg: Cfg<'_>) -> CellOut
match item {
Value::Record { val, .. } => match val.get(header) {
Some(val) => expand_entry(val, cfg),
None => CellOutput::styled(error_sign(&cfg.opts.style_computer)),
None => CellOutput::styled(error_sign(
cfg.opts.config.table.missing_value_symbol.clone(),
&cfg.opts.style_computer,
)),
},
_ => expand_entry(item, cfg),
}
@ -519,6 +499,7 @@ fn expand_entry_with_header(item: &Value, header: &str, cfg: Cfg<'_>) -> CellOut
fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
if is_limit_reached(&cfg) {
let value = nu_value_to_string_clean(item, cfg.opts.config, &cfg.opts.style_computer);
let value = nutext_wrap(value, &cfg);
return CellOutput::styled(value);
}
@ -527,6 +508,7 @@ fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
Value::Record { val: record, .. } => {
if record.is_empty() {
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
let value = nutext_wrap(value, &cfg);
return CellOutput::styled(value);
}
@ -538,6 +520,7 @@ fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
Ok(Some(table)) => table,
_ => {
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
let value = nutext_wrap(value, &cfg);
CellOutput::styled(value)
}
}
@ -560,6 +543,7 @@ fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
Ok(Some(out)) => out,
_ => {
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
let value = nutext_wrap(value, &cfg);
return CellOutput::styled(value);
}
};
@ -571,17 +555,28 @@ fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
Some(table) => CellOutput::clean(table, out.count_rows, false),
None => {
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
let value = nutext_wrap(value, &cfg);
CellOutput::styled(value)
}
}
}
_ => {
let value = nu_value_to_string_clean(item, cfg.opts.config, &cfg.opts.style_computer);
let value = nutext_wrap(value, &cfg);
CellOutput::styled(value)
}
}
}
fn nutext_wrap(mut text: NuText, cfg: &Cfg<'_>) -> NuText {
let width = string_width(&text.0);
if width > cfg.opts.width {
text.0 = wrap_text(&text.0, cfg.opts.width, cfg.opts.config);
}
text
}
fn is_limit_reached(cfg: &Cfg<'_>) -> bool {
matches!(cfg.format.expand_limit, Some(0))
}
@ -626,12 +621,6 @@ fn maybe_expand_table(mut out: TableOutput, term_width: usize) -> StringResult {
Ok(table)
}
fn set_data_styles(table: &mut NuTable, styles: HashMap<Position, TextStyle>) {
for (pos, style) in styles {
table.insert_style(pos, style);
}
}
fn table_apply_config(out: &mut TableOutput, cfg: &Cfg<'_>) {
configure_table(
out,

View File

@ -16,23 +16,23 @@ use crate::{
pub struct JustTable;
impl JustTable {
pub fn table(input: &[Value], opts: TableOpts<'_>) -> StringResult {
pub fn table(input: Vec<Value>, opts: TableOpts<'_>) -> StringResult {
list_table(input, opts)
}
pub fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
pub fn kv_table(record: Record, opts: TableOpts<'_>) -> StringResult {
kv_table(record, opts)
}
}
fn list_table(input: &[Value], opts: TableOpts<'_>) -> Result<Option<String>, ShellError> {
let mut out = match create_table(input, &opts)? {
fn list_table(input: Vec<Value>, opts: TableOpts<'_>) -> Result<Option<String>, ShellError> {
let output = create_table(input, &opts)?;
let mut out = match output {
Some(out) => out,
None => return Ok(None),
};
out.table.set_indent(opts.config.table.padding);
// TODO: It would be WAY more effitient to do right away instead of second pass over the data.
colorize_space(out.table.get_records_mut(), &opts.style_computer);
configure_table(&mut out, opts.config, &opts.style_computer, opts.mode);
@ -41,25 +41,20 @@ fn list_table(input: &[Value], opts: TableOpts<'_>) -> Result<Option<String>, Sh
Ok(table)
}
fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
let mut data = vec![Vec::with_capacity(2); record.len()];
for ((column, value), row) in record.iter().zip(data.iter_mut()) {
opts.signals.check(opts.span)?;
let key = NuRecordsValue::new(column.to_string());
let value = nu_value_to_string_colored(value, opts.config, &opts.style_computer);
let value = NuRecordsValue::new(value);
row.push(key);
row.push(value);
}
let mut table = NuTable::from(data);
fn kv_table(record: Record, opts: TableOpts<'_>) -> StringResult {
let mut table = NuTable::new(record.len(), 2);
table.set_index_style(TextStyle::default_field());
table.set_indent(opts.config.table.padding);
for (i, (key, value)) in record.into_iter().enumerate() {
opts.signals.check(opts.span)?;
let value = nu_value_to_string_colored(&value, opts.config, &opts.style_computer);
table.insert(Position::new(i, 0), key);
table.insert(Position::new(i, 1), value);
}
let mut out = TableOutput::from_table(table, false, true);
configure_table(&mut out, opts.config, &opts.style_computer, opts.mode);
let table = out.table.draw(opts.width);
@ -67,12 +62,12 @@ fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
Ok(table)
}
fn create_table(input: &[Value], opts: &TableOpts<'_>) -> TableResult {
fn create_table(input: Vec<Value>, opts: &TableOpts<'_>) -> TableResult {
if input.is_empty() {
return Ok(None);
}
let headers = get_columns(input);
let headers = get_columns(&input);
let with_index = has_index(opts, &headers);
let with_header = !headers.is_empty();
let row_offset = opts.index_offset;
@ -90,27 +85,23 @@ fn create_table(input: &[Value], opts: &TableOpts<'_>) -> TableResult {
}
fn create_table_with_header(
input: &[Value],
input: Vec<Value>,
headers: Vec<String>,
opts: &TableOpts<'_>,
) -> Result<Option<NuTable>, ShellError> {
let headers = collect_headers(headers, false);
let count_rows = input.len() + 1;
let count_columns = headers.len();
let mut table = NuTable::new(count_rows, count_columns);
table.set_header_style(get_header_style(&opts.style_computer));
table.set_index_style(get_index_style(&opts.style_computer));
table.set_indent(opts.config.table.padding);
table.set_row(0, headers.clone());
for (row, item) in input.iter().enumerate() {
for (row, item) in input.into_iter().enumerate() {
opts.signals.check(opts.span)?;
check_value(item)?;
check_value(&item)?;
for (col, header) in headers.iter().enumerate() {
let (text, style) = get_string_value_with_header(item, header.as_ref(), opts);
let (text, style) = get_string_value_with_header(&item, header, opts);
let pos = Position::new(row + 1, col);
table.insert(pos, text);
@ -118,35 +109,39 @@ fn create_table_with_header(
}
}
let headers = collect_headers(headers, false);
table.set_row(0, headers);
Ok(Some(table))
}
fn create_table_with_header_and_index(
input: &[Value],
input: Vec<Value>,
headers: Vec<String>,
row_offset: usize,
opts: &TableOpts<'_>,
) -> Result<Option<NuTable>, ShellError> {
let headers = collect_headers(headers, true);
let head = collect_headers(headers, true);
let count_rows = input.len() + 1;
let count_columns = headers.len();
let mut table = NuTable::new(count_rows, count_columns);
let count_columns = head.len();
let mut table = NuTable::new(count_rows, count_columns);
table.set_header_style(get_header_style(&opts.style_computer));
table.set_index_style(get_index_style(&opts.style_computer));
table.set_indent(opts.config.table.padding);
table.set_row(0, headers.clone());
table.set_row(0, head.clone());
for (row, item) in input.iter().enumerate() {
for (row, item) in input.into_iter().enumerate() {
opts.signals.check(opts.span)?;
check_value(item)?;
check_value(&item)?;
let text = get_table_row_index(item, opts.config, row, row_offset);
let text = get_table_row_index(&item, opts.config, row, row_offset);
table.insert(Position::new(row + 1, 0), text);
for (col, header) in headers.iter().enumerate().skip(1) {
let (text, style) = get_string_value_with_header(item, header.as_ref(), opts);
for (col, head) in head.iter().enumerate().skip(1) {
let (text, style) = get_string_value_with_header(&item, head.as_ref(), opts);
let pos = Position::new(row + 1, col);
table.insert(pos, text);
@ -158,46 +153,45 @@ fn create_table_with_header_and_index(
}
fn create_table_with_no_header(
input: &[Value],
input: Vec<Value>,
opts: &TableOpts<'_>,
) -> Result<Option<NuTable>, ShellError> {
let mut table = NuTable::new(input.len(), 1);
table.set_index_style(get_index_style(&opts.style_computer));
table.set_indent(opts.config.table.padding);
for (row, item) in input.iter().enumerate() {
for (row, item) in input.into_iter().enumerate() {
opts.signals.check(opts.span)?;
check_value(item)?;
check_value(&item)?;
let (text, style) = get_string_value(item, opts);
let (text, style) = get_string_value(&item, opts);
let pos = Position::new(row, 0);
table.insert(pos, text);
table.insert_style(pos, style);
table.insert(Position::new(row, 0), text);
table.insert_style(Position::new(row, 0), style);
}
Ok(Some(table))
}
fn create_table_with_no_header_and_index(
input: &[Value],
input: Vec<Value>,
row_offset: usize,
opts: &TableOpts<'_>,
) -> Result<Option<NuTable>, ShellError> {
let mut table = NuTable::new(input.len(), 1 + 1);
table.set_index_style(get_index_style(&opts.style_computer));
table.set_indent(opts.config.table.padding);
for (row, item) in input.iter().enumerate() {
for (row, item) in input.into_iter().enumerate() {
opts.signals.check(opts.span)?;
check_value(item)?;
check_value(&item)?;
let text = get_table_row_index(item, opts.config, row, row_offset);
table.insert(Position::new(row, 0), text);
let index = get_table_row_index(&item, opts.config, row, row_offset);
let (value, style) = get_string_value(&item, opts);
let (text, style) = get_string_value(item, opts);
let pos = Position::new(row, 1);
table.insert(pos, text);
table.insert_style(pos, style);
table.insert(Position::new(row, 0), index);
table.insert(Position::new(row, 1), value);
table.insert_style(Position::new(row, 1), style);
}
Ok(Some(table))
@ -207,7 +201,10 @@ fn get_string_value_with_header(item: &Value, header: &str, opts: &TableOpts) ->
match item {
Value::Record { val, .. } => match val.get(header) {
Some(value) => get_string_value(value, opts),
None => get_empty_style(&opts.style_computer),
None => get_empty_style(
opts.config.table.missing_value_symbol.clone(),
&opts.style_computer,
),
},
value => get_string_value(value, opts),
}

View File

@ -88,7 +88,14 @@ where
}
pub fn create_table(data: Data, case: TestCase) -> Option<String> {
let mut table = NuTable::from(data);
let count_rows = data.len();
let count_cols = data[0].len();
let mut table = NuTable::new(count_rows, count_cols);
for (i, row) in data.into_iter().enumerate() {
table.set_row(i, row);
}
table.set_theme(case.theme);
table.set_structure(case.with_index, case.with_header, case.with_footer);
table.set_trim(case.strategy);

View File

@ -451,6 +451,52 @@ fn test_with_love() {
assert_eq!(create_table_with_size(vec![], true, theme::with_love()), "");
}
#[test]
fn test_single() {
assert_eq!(
create_table(vec![row(4); 3], true, theme::single()),
"┌───┬───┬───┬───┐\n\
0 1 2 3 \n\
\n\
0 1 2 3 \n\
0 1 2 3 \n\
"
);
assert_eq!(
create_table(vec![row(4); 2], true, theme::single()),
"┌───┬───┬───┬───┐\n\
0 1 2 3 \n\
\n\
0 1 2 3 \n\
"
);
assert_eq!(
create_table(vec![row(4); 1], true, theme::single()),
"┌───┬───┬───┬───┐\n\
0 1 2 3 \n\
"
);
assert_eq!(
create_table(vec![row(4); 1], false, theme::single()),
"┌───┬───┬───┬───┐\n\
0 1 2 3 \n\
"
);
assert_eq!(
create_table(vec![row(4); 2], false, theme::single()),
"┌───┬───┬───┬───┐\n\
0 1 2 3 \n\
0 1 2 3 \n\
"
);
assert_eq!(create_table_with_size(vec![], true, theme::single()), "");
}
fn create_table(data: Vec<Vec<Text<String>>>, with_header: bool, theme: theme) -> String {
let mut case = TestCase::new(usize::MAX).theme(theme);
if with_header {

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-term-grid"
edition = "2021"
license = "MIT"
name = "nu-term-grid"
version = "0.103.1"
version = "0.104.1"
[lib]
bench = false
@ -14,6 +14,6 @@ bench = false
workspace = true
[dependencies]
nu-utils = { path = "../nu-utils", version = "0.103.1", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.104.1", default-features = false }
unicode-width = { workspace = true }

View File

@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-test-suppor
edition = "2021"
license = "MIT"
name = "nu-test-support"
version = "0.103.1"
version = "0.104.1"
[lib]
doctest = false
@ -15,9 +15,9 @@ bench = false
workspace = true
[dependencies]
nu-path = { path = "../nu-path", version = "0.103.1" }
nu-glob = { path = "../nu-glob", version = "0.103.1" }
nu-utils = { path = "../nu-utils", version = "0.103.1" }
nu-path = { path = "../nu-path", version = "0.104.1" }
nu-glob = { path = "../nu-glob", version = "0.104.1" }
nu-utils = { path = "../nu-utils", version = "0.104.1" }
num-format = { workspace = true }
which = { workspace = true }

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT"
name = "nu-utils"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-utils"
version = "0.103.1"
version = "0.104.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]

View File

@ -1,6 +1,6 @@
# Nushell Config File
#
# version = "0.103.1"
# version = "0.104.1"
$env.config.color_config = {
separator: white
leading_trailing_space_bg: { attr: n }

View File

@ -1,7 +1,7 @@
# Default Nushell Environment Config File
# These "sensible defaults" are set before the user's `env.nu` is loaded
#
# version = "0.103.1"
# version = "0.104.1"
$env.PROMPT_COMMAND = {||
let dir = match (do -i { $env.PWD | path relative-to $nu.home-path }) {

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