diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50e754160e..f918a73af1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 9086e1c228..8da8cf9981 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -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 diff --git a/.github/workflows/release-pkg.nu b/.github/workflows/release-pkg.nu index 0c65dcb44d..eee100425b 100755 --- a/.github/workflows/release-pkg.nu +++ b/.github/workflows/release-pkg.nu @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ab7d6b5cf0..6fcc71eebe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index eb57d8c471..1ed384c9c1 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 336fcb9f9e..630d07d58b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index 274e3c5273..3510ef4ef8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 8e37b97f51..ff5ffe4731 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 0909caf125..607bc5af94 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -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"] } diff --git a/crates/nu-cli/src/commands/history/history_import.rs b/crates/nu-cli/src/commands/history/history_import.rs index 80237acb87..bf394620c2 100644 --- a/crates/nu-cli/src/commands/history/history_import.rs +++ b/crates/nu-cli/src/commands/history/history_import.rs @@ -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. diff --git a/crates/nu-cli/src/completions/cell_path_completions.rs b/crates/nu-cli/src/completions/cell_path_completions.rs index 3a439bb790..34376b8ddb 100644 --- a/crates/nu-cli/src/completions/cell_path_completions.rs +++ b/crates/nu-cli/src/completions/cell_path_completions.rs @@ -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( diff --git a/crates/nu-cmd-base/Cargo.toml b/crates/nu-cmd-base/Cargo.toml index 6b771ee667..7049b261bb 100644 --- a/crates/nu-cmd-base/Cargo.toml +++ b/crates/nu-cmd-base/Cargo.toml @@ -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 } diff --git a/crates/nu-cmd-extra/Cargo.toml b/crates/nu-cmd-extra/Cargo.toml index c68724e429..b27925c25a 100644 --- a/crates/nu-cmd-extra/Cargo.toml +++ b/crates/nu-cmd-extra/Cargo.toml @@ -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" } diff --git a/crates/nu-cmd-extra/src/extra/strings/format/command.rs b/crates/nu-cmd-extra/src/extra/strings/format/command.rs index 18206acba6..e648c66298 100644 --- a/crates/nu-cmd-extra/src/extra/strings/format/command.rs +++ b/crates/nu-cmd-extra/src/extra/strings/format/command.rs @@ -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()) } } } diff --git a/crates/nu-cmd-lang/Cargo.toml b/crates/nu-cmd-lang/Cargo.toml index c580011981..ac9ce2a93a 100644 --- a/crates/nu-cmd-lang/Cargo.toml +++ b/crates/nu-cmd-lang/Cargo.toml @@ -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 } diff --git a/crates/nu-cmd-plugin/Cargo.toml b/crates/nu-cmd-plugin/Cargo.toml index 4a0360ee34..d091b0332e 100644 --- a/crates/nu-cmd-plugin/Cargo.toml +++ b/crates/nu-cmd-plugin/Cargo.toml @@ -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 } diff --git a/crates/nu-color-config/Cargo.toml b/crates/nu-color-config/Cargo.toml index f42585ceb7..e1b6fef488 100644 --- a/crates/nu-color-config/Cargo.toml +++ b/crates/nu-color-config/Cargo.toml @@ -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" } \ No newline at end of file +nu-test-support = { path = "../nu-test-support", version = "0.104.1" } \ No newline at end of file diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 3b995f9867..28858e2a85 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -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 } diff --git a/crates/nu-command/src/date/now.rs b/crates/nu-command/src/date/now.rs index b3fca4ade2..833ef0c310 100644 --- a/crates/nu-command/src/date/now.rs +++ b/crates/nu-command/src/date/now.rs @@ -38,10 +38,16 @@ impl Command for DateNow { fn examples(&self) -> Vec { 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, }, diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index d803225c36..f18e3eb369 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -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, diff --git a/crates/nu-command/src/experimental/job_flush.rs b/crates/nu-command/src/experimental/job_flush.rs new file mode 100644 index 0000000000..f717cb7bae --- /dev/null +++ b/crates/nu-command/src/experimental/job_flush.rs @@ -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 { + 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 { + vec![Example { + example: "job flush", + description: "Clear the mailbox of the current job.", + result: None, + }] + } +} diff --git a/crates/nu-command/src/experimental/job_id.rs b/crates/nu-command/src/experimental/job_id.rs new file mode 100644 index 0000000000..6f3be8a9eb --- /dev/null +++ b/crates/nu-command/src/experimental/job_id.rs @@ -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 { + let head = call.head; + + Ok(Value::int(engine_state.current_job.id.get() as i64, head).into_pipeline_data()) + } + + fn examples(&self) -> Vec { + vec![Example { + example: "job id", + description: "Get id of current job", + result: None, + }] + } +} diff --git a/crates/nu-command/src/experimental/job_recv.rs b/crates/nu-command/src/experimental/job_recv.rs new file mode 100644 index 0000000000..76a8dde2b3 --- /dev/null +++ b/crates/nu-command/src/experimental/job_recv.rs @@ -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 { + let head = call.head; + + let tag_arg: Option> = 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 = 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 { + 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, + signals: &Signals, + span: Span, +) -> Result { + 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, + span: Span, +) -> Result { + 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, + signals: &Signals, + span: Span, + timeout: Duration, +) -> Result { + 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 }); + } + } +} diff --git a/crates/nu-command/src/experimental/job_send.rs b/crates/nu-command/src/experimental/job_send.rs new file mode 100644 index 0000000000..08495d5261 --- /dev/null +++ b/crates/nu-command/src/experimental/job_send.rs @@ -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 { + let head = call.head; + + let id_arg: Spanned = call.req(engine_state, stack, 0)?; + let tag_arg: Option> = 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 { + 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, + }] + } +} diff --git a/crates/nu-command/src/experimental/job_spawn.rs b/crates/nu-command/src/experimental/job_spawn.rs index 37203aafb9..09ff929a8a 100644 --- a/crates/nu-command/src/experimental/job_spawn.rs +++ b/crates/nu-command/src/experimental/job_spawn.rs @@ -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 = 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() diff --git a/crates/nu-command/src/experimental/job_unfreeze.rs b/crates/nu-command/src/experimental/job_unfreeze.rs index 67fb3c96a1..3143b31184 100644 --- a/crates/nu-command/src/experimental/job_unfreeze.rs +++ b/crates/nu-command/src/experimental/job_unfreeze.rs @@ -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); } diff --git a/crates/nu-command/src/experimental/mod.rs b/crates/nu-command/src/experimental/mod.rs index f98d123218..9c695d9116 100644 --- a/crates/nu-command/src/experimental/mod.rs +++ b/crates/nu-command/src/experimental/mod.rs @@ -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; diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index cd91f43aad..9932c9c9f5 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -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, + } ] } } diff --git a/crates/nu-command/src/filesystem/save.rs b/crates/nu-command/src/filesystem/save.rs index 1cbf75373f..dc02e526f3 100644 --- a/crates/nu-command/src/filesystem/save.rs +++ b/crates/nu-command/src/filesystem/save.rs @@ -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, + }, ] } diff --git a/crates/nu-command/src/filters/empty.rs b/crates/nu-command/src/filters/empty.rs index 92a540fa46..b99289f6da 100644 --- a/crates/nu-command/src/filters/empty.rs +++ b/crates/nu-command/src/filters/empty.rs @@ -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()); } } } diff --git a/crates/nu-command/src/filters/get.rs b/crates/nu-command/src/filters/get.rs index 54f99386af..d681849448 100644 --- a/crates/nu-command/src/filters/get.rs +++ b/crates/nu-command/src/filters/get.rs @@ -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); diff --git a/crates/nu-command/src/filters/group_by.rs b/crates/nu-command/src/filters/group_by.rs index 0f56152834..a8da1cedf3 100644 --- a/crates/nu-command/src/filters/group_by.rs +++ b/crates/nu-command/src/filters/group_by.rs @@ -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 } diff --git a/crates/nu-command/src/filters/insert.rs b/crates/nu-command/src/filters/insert.rs index 97a087ec6e..e2801bf6ee 100644 --- a/crates/nu-command/src/filters/insert.rs +++ b/crates/nu-command/src/filters/insert.rs @@ -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() diff --git a/crates/nu-command/src/filters/select.rs b/crates/nu-command/src/filters/select.rs index 3e8e5cb614..b324a7e771 100644 --- a/crates/nu-command/src/filters/select.rs +++ b/crates/nu-command/src/filters/select.rs @@ -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()), } } diff --git a/crates/nu-command/src/filters/update.rs b/crates/nu-command/src/filters/update.rs index 9d1d152d1e..3fe63ce676 100644 --- a/crates/nu-command/src/filters/update.rs +++ b/crates/nu-command/src/filters/update.rs @@ -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) diff --git a/crates/nu-command/src/filters/upsert.rs b/crates/nu-command/src/filters/upsert.rs index 5bcd7b9a8a..750263f402 100644 --- a/crates/nu-command/src/filters/upsert.rs +++ b/crates/nu-command/src/filters/upsert.rs @@ -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); diff --git a/crates/nu-command/src/network/http/delete.rs b/crates/nu-command/src/network/http/delete.rs index f46c16b584..97bd681608 100644 --- a/crates/nu-command/src/network/http/delete.rs +++ b/crates/nu-command/src/network/http/delete.rs @@ -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 { diff --git a/crates/nu-command/src/network/http/get.rs b/crates/nu-command/src/network/http/get.rs index f317b4b080..ddc04f1f9f 100644 --- a/crates/nu-command/src/network/http/get.rs +++ b/crates/nu-command/src/network/http/get.rs @@ -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, }, diff --git a/crates/nu-command/src/network/http/head.rs b/crates/nu-command/src/network/http/head.rs index 5298fefda8..be94529420 100644 --- a/crates/nu-command/src/network/http/head.rs +++ b/crates/nu-command/src/network/http/head.rs @@ -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, }, ] diff --git a/crates/nu-command/src/network/http/options.rs b/crates/nu-command/src/network/http/options.rs index 75a459cb9b..396faee15a 100644 --- a/crates/nu-command/src/network/http/options.rs +++ b/crates/nu-command/src/network/http/options.rs @@ -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, }, diff --git a/crates/nu-command/src/network/http/patch.rs b/crates/nu-command/src/network/http/patch.rs index ee8143650a..eb0064e522 100644 --- a/crates/nu-command/src/network/http/patch.rs +++ b/crates/nu-command/src/network/http/patch.rs @@ -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 { diff --git a/crates/nu-command/src/network/http/post.rs b/crates/nu-command/src/network/http/post.rs index 8c2a3edb9d..41b316c65d 100644 --- a/crates/nu-command/src/network/http/post.rs +++ b/crates/nu-command/src/network/http/post.rs @@ -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 { diff --git a/crates/nu-command/src/network/http/put.rs b/crates/nu-command/src/network/http/put.rs index 69adaa9aba..53fb975f4a 100644 --- a/crates/nu-command/src/network/http/put.rs +++ b/crates/nu-command/src/network/http/put.rs @@ -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 { diff --git a/crates/nu-command/src/platform/input/list.rs b/crates/nu-command/src/platform/input/list.rs index 91a31aebd8..df605f4a27 100644 --- a/crates/nu-command/src/platform/input/list.rs +++ b/crates/nu-command/src/platform/input/list.rs @@ -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) diff --git a/crates/nu-command/src/sort_utils.rs b/crates/nu-command/src/sort_utils.rs index 951a93ead9..0511e7cddf 100644 --- a/crates/nu-command/src/sort_utils.rs +++ b/crates/nu-command/src/sort_utils.rs @@ -239,8 +239,8 @@ pub fn compare_cell_path( insensitive: bool, natural: bool, ) -> Result { - 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) } diff --git a/crates/nu-command/src/strings/format/date.rs b/crates/nu-command/src/strings/format/date.rs index ad668ca510..5c6fb61d83 100644 --- a/crates/nu-command/src/strings/format/date.rs +++ b/crates/nu-command/src/strings/format/date.rs @@ -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""#, diff --git a/crates/nu-command/src/strings/str_/expand.rs b/crates/nu-command/src/strings/str_/expand.rs index e6caa213b5..377ed8e757 100644 --- a/crates/nu-command/src/strings/str_/expand.rs +++ b/crates/nu-command/src/strings/str_/expand.rs @@ -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; diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 8331c9f284..180238f3f2 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -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( diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 147555558e..f71eea7f82 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -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::test_string("restructured"), Value::test_string("ascii_rounded"), Value::test_string("basic_compact"), + Value::test_string("single"), ] } diff --git a/crates/nu-command/tests/commands/job.rs b/crates/nu-command/tests/commands/job.rs index 10b8346edd..608ca318c8 100644 --- a/crates/nu-command/tests/commands/job.rs +++ b/crates/nu-command/tests/commands/job.rs @@ -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 diff --git a/crates/nu-command/tests/commands/table.rs b/crates/nu-command/tests/commands/table.rs index 19650a4f9f..956aab54c1 100644 --- a/crates/nu-command/tests/commands/table.rs +++ b/crates/nu-command/tests/commands/table.rs @@ -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 │\ + ╰───┴──────╯", + ) +} diff --git a/crates/nu-derive-value/Cargo.toml b/crates/nu-derive-value/Cargo.toml index df34048ef6..24e70abac0 100644 --- a/crates/nu-derive-value/Cargo.toml +++ b/crates/nu-derive-value/Cargo.toml @@ -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 diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index f351a11654..7d8b8a278a 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -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] diff --git a/crates/nu-engine/src/compile/operator.rs b/crates/nu-engine/src/compile/operator.rs index 6e127c8d5b..2a913f8836 100644 --- a/crates/nu-engine/src/compile/operator.rs +++ b/crates/nu-engine/src/compile/operator.rs @@ -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( diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 41d39241f8..c39dd0cf30 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -269,6 +269,7 @@ pub fn eval_expression_with_input( input = eval_subexpression::(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::(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 { diff --git a/crates/nu-engine/src/eval_ir.rs b/crates/nu-engine/src/eval_ir.rs index 6dfa913931..ffd0938079 100644 --- a/crates/nu-engine/src/eval_ir.rs +++ b/crates/nu-engine/src/eval_ir.rs @@ -694,9 +694,8 @@ fn eval_instruction( 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) diff --git a/crates/nu-explore/Cargo.toml b/crates/nu-explore/Cargo.toml index a5a14a0e1f..feed5e8bb0 100644 --- a/crates/nu-explore/Cargo.toml +++ b/crates/nu-explore/Cargo.toml @@ -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 } diff --git a/crates/nu-explore/src/commands/help.rs b/crates/nu-explore/src/commands/help.rs index 3083b83a9b..a0b6a4d97a 100644 --- a/crates/nu-explore/src/commands/help.rs +++ b/crates/nu-explore/src/commands/help.rs @@ -38,6 +38,7 @@ Drill down into records+tables: Press to select a cell, move around wit Expand (show all nested data): Press "e" Open this help page : Type ":help" then Open an interactive REPL: Type ":try" then + Run a Nushell command: Type ":nu " then . 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 , or Ctrl+D. Alternately, press or "q" until Explore exits diff --git a/crates/nu-glob/Cargo.toml b/crates/nu-glob/Cargo.toml index a6d797e4db..21c98d9880 100644 --- a/crates/nu-glob/Cargo.toml +++ b/crates/nu-glob/Cargo.toml @@ -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 = """ diff --git a/crates/nu-json/Cargo.toml b/crates/nu-json/Cargo.toml index 2b8ae829a1..c3f8ecb399 100644 --- a/crates/nu-json/Cargo.toml +++ b/crates/nu-json/Cargo.toml @@ -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" diff --git a/crates/nu-lsp/Cargo.toml b/crates/nu-lsp/Cargo.toml index be0d17ca7f..b1bf1fa677 100644 --- a/crates/nu-lsp/Cargo.toml +++ b/crates/nu-lsp/Cargo.toml @@ -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" diff --git a/crates/nu-lsp/src/goto.rs b/crates/nu-lsp/src/goto.rs index 1a232c5986..f8623ee0d7 100644 --- a/crates/nu-lsp/src/goto.rs +++ b/crates/nu-lsp/src/goto.rs @@ -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), diff --git a/crates/nu-lsp/src/hover.rs b/crates/nu-lsp/src/hover.rs index df6de49fc2..c75499f0c2 100644 --- a/crates/nu-lsp/src/hover.rs +++ b/crates/nu-lsp/src/hover.rs @@ -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()), ) diff --git a/crates/nu-parser/Cargo.toml b/crates/nu-parser/Cargo.toml index a1914b7561..f903d7b5a5 100644 --- a/crates/nu-parser/Cargo.toml +++ b/crates/nu-parser/Cargo.toml @@ -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 } diff --git a/crates/nu-path/Cargo.toml b/crates/nu-path/Cargo.toml index 320629258e..b429db9a19 100644 --- a/crates/nu-path/Cargo.toml +++ b/crates/nu-path/Cargo.toml @@ -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] diff --git a/crates/nu-plugin-core/Cargo.toml b/crates/nu-plugin-core/Cargo.toml index f220e648c4..32b82eb88c 100644 --- a/crates/nu-plugin-core/Cargo.toml +++ b/crates/nu-plugin-core/Cargo.toml @@ -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 } diff --git a/crates/nu-plugin-engine/Cargo.toml b/crates/nu-plugin-engine/Cargo.toml index 8b9eb17544..ed0686ac5e 100644 --- a/crates/nu-plugin-engine/Cargo.toml +++ b/crates/nu-plugin-engine/Cargo.toml @@ -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 } diff --git a/crates/nu-plugin-protocol/Cargo.toml b/crates/nu-plugin-protocol/Cargo.toml index a6c9e1ba0b..68537b872b 100644 --- a/crates/nu-plugin-protocol/Cargo.toml +++ b/crates/nu-plugin-protocol/Cargo.toml @@ -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"] } diff --git a/crates/nu-plugin-test-support/Cargo.toml b/crates/nu-plugin-test-support/Cargo.toml index 0d0fa83851..b3708668fd 100644 --- a/crates/nu-plugin-test-support/Cargo.toml +++ b/crates/nu-plugin-test-support/Cargo.toml @@ -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" diff --git a/crates/nu-plugin/Cargo.toml b/crates/nu-plugin/Cargo.toml index 6f81e9b3f0..31a748fdc9 100644 --- a/crates/nu-plugin/Cargo.toml +++ b/crates/nu-plugin/Cargo.toml @@ -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"] } \ No newline at end of file +nix = { workspace = true, default-features = false, features = ["process"] } diff --git a/crates/nu-pretty-hex/Cargo.toml b/crates/nu-pretty-hex/Cargo.toml index 8e0c2079f9..19e43782fd 100644 --- a/crates/nu-pretty-hex/Cargo.toml +++ b/crates/nu-pretty-hex/Cargo.toml @@ -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 diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index e6f85a8558..09cedc0aea 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -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 } diff --git a/crates/nu-protocol/src/config/table.rs b/crates/nu-protocol/src/config/table.rs index 5c6aaf07d6..74893cd9c7 100644 --- a/crates/nu-protocol/src/config/table.rs +++ b/crates/nu-protocol/src/config/table.rs @@ -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, 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), } } diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 89572c9a85..3099b33f20 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -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>>; #[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>, // The job being executed with this engine state, or None if main thread - pub current_thread_job: Option, + pub current_job: CurrentJob, + + pub root_job_sender: Sender, // 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::(); + 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() } } diff --git a/crates/nu-protocol/src/engine/jobs.rs b/crates/nu-protocol/src/engine/jobs.rs index 8e64e46f7f..71c18a4c83 100644 --- a/crates/nu-protocol/src/engine/jobs.rs +++ b/crates/nu-protocol/src/engine/jobs.rs @@ -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>>, tag: Option, + pub sender: Sender, } impl ThreadJob { - pub fn new(signals: Signals, tag: Option) -> Self { + pub fn new(signals: Signals, tag: Option, sender: Sender) -> 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, + + // note: although the mailbox is Mutex'd, it is only ever accessed + // by the current job's threads + pub mailbox: Arc>, +} + +// 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, + ignored_mail: IgnoredMail, +} + +impl Mailbox { + pub fn new(receiver: Receiver) -> Self { + Mailbox { + receiver, + ignored_mail: IgnoredMail::default(), + } + } + + #[cfg(not(target_family = "wasm"))] + pub fn recv_timeout( + &mut self, + filter_tag: Option, + timeout: Duration, + ) -> Result { + 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, + ) -> Result { + 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, + by_tag: HashMap>, +} + +pub type FilterTag = u64; +pub type Mail = (Option, 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) -> Option { + 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 { + 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 { + 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) + } +} diff --git a/crates/nu-protocol/src/errors/shell_error/mod.rs b/crates/nu-protocol/src/errors/shell_error/mod.rs index 84c23cd208..9ff49f36d8 100644 --- a/crates/nu-protocol/src/errors/shell_error/mod.rs +++ b/crates/nu-protocol/src/errors/shell_error/mod.rs @@ -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), diff --git a/crates/nu-protocol/src/eval_base.rs b/crates/nu-protocol/src/eval_base.rs index 15fc5105d3..65c9b5f82b 100644 --- a/crates/nu-protocol/src/eval_base.rs +++ b/crates/nu-protocol/src/eval_base.rs @@ -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) => { diff --git a/crates/nu-protocol/src/module.rs b/crates/nu-protocol/src/module.rs index b5913f58da..1b36b5a586 100644 --- a/crates/nu-protocol/src/module.rs +++ b/crates/nu-protocol/src/module.rs @@ -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, diff --git a/crates/nu-protocol/src/pipeline/pipeline_data.rs b/crates/nu-protocol/src/pipeline/pipeline_data.rs index ce688c18e3..a0ea603dc1 100644 --- a/crates/nu-protocol/src/pipeline/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline/pipeline_data.rs @@ -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, diff --git a/crates/nu-protocol/src/process/child.rs b/crates/nu-protocol/src/process/child.rs index 069cb71204..b431a11df8 100644 --- a/crates/nu-protocol/src/process/child.rs +++ b/crates/nu-protocol/src/process/child.rs @@ -194,7 +194,7 @@ impl PostWaitCallback { child_pid: Option, tag: Option, ) -> 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; diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index ad7f8fe546..7ff9218eea 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -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 { - let mut current = self; + ) -> Result, 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 ` - 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::>()?; - - 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 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 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>, ShellError> { + match member { + PathMember::Int { + val: count, + span: origin_span, + optional, + } => { + // Treat a numeric path member as `select ` + 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::>()?; + + 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 { diff --git a/crates/nu-std/Cargo.toml b/crates/nu-std/Cargo.toml index 8e2389ccca..8a46966059 100644 --- a/crates/nu-std/Cargo.toml +++ b/crates/nu-std/Cargo.toml @@ -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" diff --git a/crates/nu-std/std-rfc/kv/mod.nu b/crates/nu-std/std-rfc/kv/mod.nu index 31c990fd6a..95d90282e1 100644 --- a/crates/nu-std/std-rfc/kv/mod.nu +++ b/crates/nu-std/std-rfc/kv/mod.nu @@ -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) } diff --git a/crates/nu-std/std/help/mod.nu b/crates/nu-std/std/help/mod.nu index fcdcc67a35..6b754c96c9 100644 --- a/crates/nu-std/std/help/mod.nu +++ b/crates/nu-std/std/help/mod.nu @@ -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" } diff --git a/crates/nu-std/tests/test_std-rfc_kv.nu b/crates/nu-std/tests/test_std-rfc_kv.nu index f30a709dee..84467fceee 100644 --- a/crates/nu-std/tests/test_std-rfc_kv.nu +++ b/crates/nu-std/tests/test_std-rfc_kv.nu @@ -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" 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" 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 diff --git a/crates/nu-system/Cargo.toml b/crates/nu-system/Cargo.toml index ad29400e63..2d318ab85b 100644 --- a/crates/nu-system/Cargo.toml +++ b/crates/nu-system/Cargo.toml @@ -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" diff --git a/crates/nu-table/Cargo.toml b/crates/nu-table/Cargo.toml index 82c6c8cd34..e109472fa2 100644 --- a/crates/nu-table/Cargo.toml +++ b/crates/nu-table/Cargo.toml @@ -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" } \ No newline at end of file +# nu-test-support = { path="../nu-test-support", version = "0.104.1" } \ No newline at end of file diff --git a/crates/nu-table/examples/table_demo.rs b/crates/nu-table/examples/table_demo.rs index 581d8934ea..001b25e061 100644 --- a/crates/nu-table/examples/table_demo.rs +++ b/crates/nu-table/examples/table_demo.rs @@ -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))); diff --git a/crates/nu-table/src/common.rs b/crates/nu-table/src/common.rs index 2f08e8f966..6176cd2670 100644 --- a/crates/nu-table/src/common.rs +++ b/crates/nu-table/src/common.rs @@ -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(), } } diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index 8cc4291cfa..6fd40d3734 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -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; /// NuTable is a table rendering implementation. #[derive(Debug, Clone)] pub struct NuTable { - data: NuRecords, + data: Vec>, + widths: Vec, + 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) { - 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) { 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] { &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>>> for NuTable { fn from(value: Vec>>) -> 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; +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; - -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Hash)] struct CellConfiguration { - data: Value, index: Value, header: Value, - columns: HashMap, - cells: HashMap, + data: Value, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct Styles { + cfg: ColoredConfig, + colors: CellConfiguration, + alignments: CellConfiguration, } #[derive(Debug, Clone)] @@ -304,49 +364,72 @@ fn table_insert_footer_if(t: &mut NuTable) { } fn table_truncate(t: &mut NuTable, termwidth: usize) -> Option { - 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 = 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> = 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, 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 { 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, needed: Vec, total: usize, truncate: bool) -> Self { + fn new( + original: Vec, + needed: Vec, + 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, + 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, 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>, + widths: Vec, 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>, + widths: Vec, 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>, + widths: Vec, 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