mirror of
https://github.com/nushell/nushell.git
synced 2025-05-31 23:26:11 +02:00
Merge branch 'main' of github.com:nushell/nushell into path/fix-wrap-emojie-issue
This commit is contained in:
commit
99fb49fc2c
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@ -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
|
||||
|
9
.github/workflows/nightly-build.yml
vendored
9
.github/workflows/nightly-build.yml
vendored
@ -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
|
||||
|
14
.github/workflows/release-pkg.nu
vendored
14
.github/workflows/release-pkg.nu
vendored
@ -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
|
||||
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -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
|
||||
|
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
@ -10,4 +10,4 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Check spelling
|
||||
uses: crate-ci/typos@v1.31.1
|
||||
uses: crate-ci/typos@v1.31.2
|
||||
|
143
Cargo.lock
generated
143
Cargo.lock
generated
@ -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",
|
||||
]
|
||||
|
||||
|
46
Cargo.toml
46
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"
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"] }
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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 }
|
||||
|
@ -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" }
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 }
|
||||
|
@ -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 }
|
||||
|
||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-color-config"
|
||||
version = "0.103.1"
|
||||
version = "0.104.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
@ -14,12 +14,12 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.103.1", default-features = false }
|
||||
nu-engine = { path = "../nu-engine", version = "0.103.1", default-features = false }
|
||||
nu-json = { path = "../nu-json", version = "0.103.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false }
|
||||
nu-engine = { path = "../nu-engine", version = "0.104.1", default-features = false }
|
||||
nu-json = { path = "../nu-json", version = "0.104.1" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.103.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.104.1" }
|
@ -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 }
|
||||
|
@ -38,10 +38,16 @@ impl Command for DateNow {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get the current date and display it in a given format string.",
|
||||
description: "Get the current date and format it in a given format string.",
|
||||
example: r#"date now | format date "%Y-%m-%d %H:%M:%S""#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Get the current date and format it according to the RFC 3339 standard.",
|
||||
example: r#"date now | format date "%+""#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get the time duration since 2019-04-30.",
|
||||
example: r#"(date now) - 2019-05-01"#,
|
||||
@ -53,7 +59,8 @@ impl Command for DateNow {
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get current time in full RFC 3339 format with time zone.",
|
||||
description:
|
||||
"Get current time and format it in the debug format (RFC 2822 with timezone)",
|
||||
example: r#"date now | debug"#,
|
||||
result: None,
|
||||
},
|
||||
|
@ -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,
|
||||
|
58
crates/nu-command/src/experimental/job_flush.rs
Normal file
58
crates/nu-command/src/experimental/job_flush.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct JobFlush;
|
||||
|
||||
impl Command for JobFlush {
|
||||
fn name(&self) -> &str {
|
||||
"job flush"
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Clear this job's mailbox."
|
||||
}
|
||||
|
||||
fn extra_description(&self) -> &str {
|
||||
r#"
|
||||
This command removes all messages in the mailbox of the current job.
|
||||
If a message is received while this command is executing, it may also be discarded.
|
||||
"#
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("job flush")
|
||||
.category(Category::Experimental)
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.allow_variants_without_examples(true)
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let mut mailbox = engine_state
|
||||
.current_job
|
||||
.mailbox
|
||||
.lock()
|
||||
.expect("failed to acquire lock");
|
||||
|
||||
mailbox.clear();
|
||||
|
||||
Ok(Value::nothing(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
example: "job flush",
|
||||
description: "Clear the mailbox of the current job.",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
50
crates/nu-command/src/experimental/job_id.rs
Normal file
50
crates/nu-command/src/experimental/job_id.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct JobId;
|
||||
|
||||
impl Command for JobId {
|
||||
fn name(&self) -> &str {
|
||||
"job id"
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Get id of current job."
|
||||
}
|
||||
|
||||
fn extra_description(&self) -> &str {
|
||||
"This command returns the job id for the current background job.
|
||||
The special id 0 indicates that this command was not called from a background job thread, and
|
||||
was instead spawned by main nushell execution thread."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("job id")
|
||||
.category(Category::Experimental)
|
||||
.input_output_types(vec![(Type::Nothing, Type::Int)])
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["self", "this", "my-id", "this-id"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
|
||||
Ok(Value::int(engine_state.current_job.id.get() as i64, head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
example: "job id",
|
||||
description: "Get id of current job",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
181
crates/nu-command/src/experimental/job_recv.rs
Normal file
181
crates/nu-command/src/experimental/job_recv.rs
Normal file
@ -0,0 +1,181 @@
|
||||
use std::{
|
||||
sync::mpsc::{RecvTimeoutError, TryRecvError},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
use nu_protocol::{
|
||||
engine::{FilterTag, Mailbox},
|
||||
Signals,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct JobRecv;
|
||||
|
||||
const CTRL_C_CHECK_INTERVAL: Duration = Duration::from_millis(100);
|
||||
|
||||
impl Command for JobRecv {
|
||||
fn name(&self) -> &str {
|
||||
"job recv"
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Read a message from the mailbox."
|
||||
}
|
||||
|
||||
fn extra_description(&self) -> &str {
|
||||
r#"When messages are sent to the current process, they get stored in what is called the "mailbox".
|
||||
This commands reads and returns a message from the mailbox, in a first-in-first-out fashion.
|
||||
|
||||
Messages may have numeric flags attached to them. This commands supports filtering out messages that do not satisfy a given tag, by using the `tag` flag.
|
||||
If no tag is specified, this command will accept any message.
|
||||
|
||||
If no message with the specified tag (if any) is available in the mailbox, this command will block the current thread until one arrives.
|
||||
By default this command block indefinitely until a matching message arrives, but a timeout duration can be specified.
|
||||
If a timeout duration of zero is specified, it will succeed only if there already is a message in the mailbox.
|
||||
|
||||
Note: When using par-each, only one thread at a time can utilize this command.
|
||||
In the case of two or more threads running this command, they will wait until other threads are done using it,
|
||||
in no particular order, regardless of the specified timeout parameter.
|
||||
"#
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("job recv")
|
||||
.category(Category::Experimental)
|
||||
.named("tag", SyntaxShape::Int, "A tag for the message", None)
|
||||
.named(
|
||||
"timeout",
|
||||
SyntaxShape::Duration,
|
||||
"The maximum time duration to wait for.",
|
||||
None,
|
||||
)
|
||||
.input_output_types(vec![(Type::Nothing, Type::Any)])
|
||||
.allow_variants_without_examples(true)
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["receive"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
|
||||
let tag_arg: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "tag")?;
|
||||
|
||||
if let Some(tag) = tag_arg {
|
||||
if tag.item < 0 {
|
||||
return Err(ShellError::NeedsPositiveValue { span: tag.span });
|
||||
}
|
||||
}
|
||||
|
||||
let tag = tag_arg.map(|it| it.item as FilterTag);
|
||||
|
||||
let duration: Option<i64> = call.get_flag(engine_state, stack, "timeout")?;
|
||||
|
||||
let timeout = duration.map(|it| Duration::from_nanos(it as u64));
|
||||
|
||||
let mut mailbox = engine_state
|
||||
.current_job
|
||||
.mailbox
|
||||
.lock()
|
||||
.expect("failed to acquire lock");
|
||||
|
||||
if let Some(timeout) = timeout {
|
||||
if timeout == Duration::ZERO {
|
||||
recv_instantly(&mut mailbox, tag, head)
|
||||
} else {
|
||||
recv_with_time_limit(&mut mailbox, tag, engine_state.signals(), head, timeout)
|
||||
}
|
||||
} else {
|
||||
recv_without_time_limit(&mut mailbox, tag, engine_state.signals(), head)
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
example: "job recv",
|
||||
description: "Block the current thread while no message arrives",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
example: "job recv --timeout 10sec",
|
||||
description: "Receive a message, wait for at most 10 seconds.",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
example: "job recv --timeout 0sec",
|
||||
description: "Get a message or fail if no message is available immediately",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn recv_without_time_limit(
|
||||
mailbox: &mut Mailbox,
|
||||
tag: Option<FilterTag>,
|
||||
signals: &Signals,
|
||||
span: Span,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
loop {
|
||||
if signals.interrupted() {
|
||||
return Err(ShellError::Interrupted { span });
|
||||
}
|
||||
match mailbox.recv_timeout(tag, CTRL_C_CHECK_INTERVAL) {
|
||||
Ok(value) => return Ok(value),
|
||||
Err(RecvTimeoutError::Timeout) => {} // try again
|
||||
Err(RecvTimeoutError::Disconnected) => return Err(ShellError::Interrupted { span }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn recv_instantly(
|
||||
mailbox: &mut Mailbox,
|
||||
tag: Option<FilterTag>,
|
||||
span: Span,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
match mailbox.try_recv(tag) {
|
||||
Ok(value) => Ok(value),
|
||||
Err(TryRecvError::Empty) => Err(ShellError::RecvTimeout { span }),
|
||||
Err(TryRecvError::Disconnected) => Err(ShellError::Interrupted { span }),
|
||||
}
|
||||
}
|
||||
|
||||
fn recv_with_time_limit(
|
||||
mailbox: &mut Mailbox,
|
||||
tag: Option<FilterTag>,
|
||||
signals: &Signals,
|
||||
span: Span,
|
||||
timeout: Duration,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let deadline = Instant::now() + timeout;
|
||||
|
||||
loop {
|
||||
if signals.interrupted() {
|
||||
return Err(ShellError::Interrupted { span });
|
||||
}
|
||||
|
||||
let time_until_deadline = deadline.saturating_duration_since(Instant::now());
|
||||
|
||||
let time_to_sleep = time_until_deadline.min(CTRL_C_CHECK_INTERVAL);
|
||||
|
||||
match mailbox.recv_timeout(tag, time_to_sleep) {
|
||||
Ok(value) => return Ok(value),
|
||||
Err(RecvTimeoutError::Timeout) => {} // try again
|
||||
Err(RecvTimeoutError::Disconnected) => return Err(ShellError::Interrupted { span }),
|
||||
}
|
||||
|
||||
if time_until_deadline.is_zero() {
|
||||
return Err(ShellError::RecvTimeout { span });
|
||||
}
|
||||
}
|
||||
}
|
112
crates/nu-command/src/experimental/job_send.rs
Normal file
112
crates/nu-command/src/experimental/job_send.rs
Normal file
@ -0,0 +1,112 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::{engine::FilterTag, JobId};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct JobSend;
|
||||
|
||||
impl Command for JobSend {
|
||||
fn name(&self) -> &str {
|
||||
"job send"
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Send a message to the mailbox of a job."
|
||||
}
|
||||
|
||||
fn extra_description(&self) -> &str {
|
||||
r#"
|
||||
This command sends a message to a background job, which can then read sent messages
|
||||
in a first-in-first-out fashion with `job recv`. When it does so, it may additionally specify a numeric filter tag,
|
||||
in which case it will only read messages sent with the exact same filter tag.
|
||||
In particular, the id 0 refers to the main/initial nushell thread.
|
||||
|
||||
A message can be any nushell value, and streams are always collected before being sent.
|
||||
|
||||
This command never blocks.
|
||||
"#
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("job send")
|
||||
.category(Category::Experimental)
|
||||
.required(
|
||||
"id",
|
||||
SyntaxShape::Int,
|
||||
"The id of the job to send the message to.",
|
||||
)
|
||||
.named("tag", SyntaxShape::Int, "A tag for the message", None)
|
||||
.input_output_types(vec![(Type::Any, Type::Nothing)])
|
||||
.allow_variants_without_examples(true)
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
|
||||
let id_arg: Spanned<i64> = call.req(engine_state, stack, 0)?;
|
||||
let tag_arg: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "tag")?;
|
||||
|
||||
let id = id_arg.item;
|
||||
|
||||
if id < 0 {
|
||||
return Err(ShellError::NeedsPositiveValue { span: id_arg.span });
|
||||
}
|
||||
|
||||
if let Some(tag) = tag_arg {
|
||||
if tag.item < 0 {
|
||||
return Err(ShellError::NeedsPositiveValue { span: tag.span });
|
||||
}
|
||||
}
|
||||
|
||||
let tag = tag_arg.map(|it| it.item as FilterTag);
|
||||
|
||||
if id == 0 {
|
||||
engine_state
|
||||
.root_job_sender
|
||||
.send((tag, input))
|
||||
.expect("this should NEVER happen.");
|
||||
} else {
|
||||
let jobs = engine_state.jobs.lock().expect("failed to acquire lock");
|
||||
|
||||
if let Some(job) = jobs.lookup(JobId::new(id as usize)) {
|
||||
match job {
|
||||
nu_protocol::engine::Job::Thread(thread_job) => {
|
||||
// it is ok to send this value while holding the lock, because
|
||||
// mail channels are always unbounded, so this send never blocks
|
||||
let _ = thread_job.sender.send((tag, input));
|
||||
}
|
||||
nu_protocol::engine::Job::Frozen(_) => {
|
||||
return Err(ShellError::JobIsFrozen {
|
||||
id: id as usize,
|
||||
span: id_arg.span,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(ShellError::JobNotFound {
|
||||
id: id as usize,
|
||||
span: id_arg.span,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::nothing(head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
example: "let id = job spawn { job recv | save sent.txt }; 'hi' | job send $id",
|
||||
description: "Send a message to a newly spawned job",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicU32},
|
||||
Arc,
|
||||
mpsc, Arc, Mutex,
|
||||
},
|
||||
thread,
|
||||
};
|
||||
|
||||
use nu_engine::{command_prelude::*, ClosureEvalOnce};
|
||||
use nu_protocol::{
|
||||
engine::{Closure, Job, Redirection, ThreadJob},
|
||||
engine::{Closure, CurrentJob, Job, Mailbox, Redirection, ThreadJob},
|
||||
report_shell_error, OutDest, Signals,
|
||||
};
|
||||
|
||||
@ -57,12 +57,11 @@ impl Command for JobSpawn {
|
||||
let closure: Closure = call.req(engine_state, stack, 0)?;
|
||||
|
||||
let tag: Option<String> = call.get_flag(engine_state, stack, "tag")?;
|
||||
let job_stack = stack.clone();
|
||||
|
||||
let mut job_state = engine_state.clone();
|
||||
job_state.is_interactive = false;
|
||||
|
||||
let job_stack = stack.clone();
|
||||
|
||||
// the new job should have its ctrl-c independent of foreground
|
||||
let job_signals = Signals::new(Arc::new(AtomicBool::new(false)));
|
||||
job_state.set_signals(job_signals.clone());
|
||||
@ -75,10 +74,20 @@ impl Command for JobSpawn {
|
||||
let jobs = job_state.jobs.clone();
|
||||
let mut jobs = jobs.lock().expect("jobs lock is poisoned!");
|
||||
|
||||
let (send, recv) = mpsc::channel();
|
||||
|
||||
let id = {
|
||||
let thread_job = ThreadJob::new(job_signals, tag);
|
||||
job_state.current_thread_job = Some(thread_job.clone());
|
||||
jobs.add_job(Job::Thread(thread_job))
|
||||
let thread_job = ThreadJob::new(job_signals, tag, send);
|
||||
|
||||
let id = jobs.add_job(Job::Thread(thread_job.clone()));
|
||||
|
||||
job_state.current_job = CurrentJob {
|
||||
id,
|
||||
background_thread_job: Some(thread_job),
|
||||
mailbox: Arc::new(Mutex::new(Mailbox::new(recv))),
|
||||
};
|
||||
|
||||
id
|
||||
};
|
||||
|
||||
let result = thread::Builder::new()
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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,
|
||||
},
|
||||
]
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -239,8 +239,8 @@ pub fn compare_cell_path(
|
||||
insensitive: bool,
|
||||
natural: bool,
|
||||
) -> Result<Ordering, ShellError> {
|
||||
let left = left.clone().follow_cell_path(&cell_path.members, false)?;
|
||||
let right = right.clone().follow_cell_path(&cell_path.members, false)?;
|
||||
let left = left.follow_cell_path(&cell_path.members, false)?;
|
||||
let right = right.follow_cell_path(&cell_path.members, false)?;
|
||||
compare_values(&left, &right, insensitive, natural)
|
||||
}
|
||||
|
||||
|
@ -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""#,
|
||||
|
@ -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;
|
||||
|
@ -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(
|
||||
|
@ -5,6 +5,7 @@
|
||||
use std::{collections::VecDeque, io::Read, path::PathBuf, str::FromStr};
|
||||
|
||||
use lscolors::{LsColors, Style};
|
||||
use tabled::grid::config::Position;
|
||||
use url::Url;
|
||||
use web_time::Instant;
|
||||
|
||||
@ -17,8 +18,8 @@ use nu_protocol::{
|
||||
Signals, TableMode, ValueIterator,
|
||||
};
|
||||
use nu_table::{
|
||||
common::configure_table, CollapsedTable, ExpandedTable, JustTable, NuRecordsValue, NuTable,
|
||||
StringResult, TableOpts, TableOutput,
|
||||
common::configure_table, CollapsedTable, ExpandedTable, JustTable, NuTable, StringResult,
|
||||
TableOpts, TableOutput,
|
||||
};
|
||||
use nu_utils::{get_ls_colors, terminal_size};
|
||||
|
||||
@ -609,7 +610,7 @@ fn build_table_kv(
|
||||
span: Span,
|
||||
) -> StringResult {
|
||||
match table_view {
|
||||
TableView::General => JustTable::kv_table(&record, opts),
|
||||
TableView::General => JustTable::kv_table(record, opts),
|
||||
TableView::Expanded {
|
||||
limit,
|
||||
flatten,
|
||||
@ -645,7 +646,7 @@ fn build_table_batch(
|
||||
}
|
||||
|
||||
match view {
|
||||
TableView::General => JustTable::table(&vals, opts),
|
||||
TableView::General => JustTable::table(vals, opts),
|
||||
TableView::Expanded {
|
||||
limit,
|
||||
flatten,
|
||||
@ -1090,9 +1091,9 @@ fn create_empty_placeholder(
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let cell = NuRecordsValue::new(format!("empty {}", value_type_name));
|
||||
let data = vec![vec![cell]];
|
||||
let mut table = NuTable::from(data);
|
||||
let cell = format!("empty {}", value_type_name);
|
||||
let mut table = NuTable::new(1, 1);
|
||||
table.insert(Position::new(0, 0), cell);
|
||||
table.set_data_style(TextStyle::default().dimmed());
|
||||
let mut out = TableOutput::from_table(table, false, false);
|
||||
|
||||
@ -1153,6 +1154,7 @@ fn supported_table_modes() -> Vec<Value> {
|
||||
Value::test_string("restructured"),
|
||||
Value::test_string("ascii_rounded"),
|
||||
Value::test_string("basic_compact"),
|
||||
Value::test_string("single"),
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 │\
|
||||
╰───┴──────╯",
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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(
|
||||
|
@ -269,6 +269,7 @@ pub fn eval_expression_with_input<D: DebugContext>(
|
||||
input = eval_subexpression::<D>(engine_state, stack, block, input)?
|
||||
.into_value(*span)?
|
||||
.follow_cell_path(&full_cell_path.tail, false)?
|
||||
.into_owned()
|
||||
.into_pipeline_data()
|
||||
} else {
|
||||
input = eval_subexpression::<D>(engine_state, stack, block, input)?;
|
||||
@ -604,7 +605,7 @@ impl Eval for EvalRuntime {
|
||||
|
||||
let is_config = original_key == "config";
|
||||
|
||||
stack.add_env_var(original_key, value);
|
||||
stack.add_env_var(original_key, value.into_owned());
|
||||
|
||||
// Trigger the update to config, if we modified that.
|
||||
if is_config {
|
||||
|
@ -694,9 +694,8 @@ fn eval_instruction<D: DebugContext>(
|
||||
let value = ctx.clone_reg_value(*src, *span)?;
|
||||
let path = ctx.take_reg(*path);
|
||||
if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path {
|
||||
// TODO: make follow_cell_path() not have to take ownership, probably using Cow
|
||||
let value = value.follow_cell_path(&path.members, true)?;
|
||||
ctx.put_reg(*dst, value.into_pipeline_data());
|
||||
ctx.put_reg(*dst, value.into_owned().into_pipeline_data());
|
||||
Ok(Continue)
|
||||
} else if let PipelineData::Value(Value::Error { error, .. }, _) = path {
|
||||
Err(*error)
|
||||
|
@ -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 }
|
||||
|
@ -38,6 +38,7 @@ Drill down into records+tables: Press <Enter> to select a cell, move around wit
|
||||
Expand (show all nested data): Press "e"
|
||||
Open this help page : Type ":help" then <Enter>
|
||||
Open an interactive REPL: Type ":try" then <Enter>
|
||||
Run a Nushell command: Type ":nu <command>" then <Enter>. The data currently being explored is piped into it.
|
||||
Scroll up: Press "Page Up", Ctrl+B, or Alt+V
|
||||
Scroll down: Press "Page Down", Ctrl+F, or Ctrl+V
|
||||
Exit Explore: Type ":q" then <Enter>, or Ctrl+D. Alternately, press <Esc> or "q" until Explore exits
|
||||
|
@ -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 = """
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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()),
|
||||
)
|
||||
|
@ -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 }
|
||||
|
@ -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]
|
||||
|
@ -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 }
|
||||
|
@ -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 }
|
||||
|
@ -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"] }
|
||||
|
@ -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"
|
||||
|
||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-plugin"
|
||||
version = "0.103.1"
|
||||
version = "0.104.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
@ -14,14 +14,14 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.103.1", features = ["plugin"] }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.103.1", features = ["plugin"] }
|
||||
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.103.1" }
|
||||
nu-plugin-core = { path = "../nu-plugin-core", version = "0.103.1", default-features = false }
|
||||
nu-utils = { path = "../nu-utils", version = "0.103.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.104.1", features = ["plugin"] }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.104.1", features = ["plugin"] }
|
||||
nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.104.1" }
|
||||
nu-plugin-core = { path = "../nu-plugin-core", version = "0.104.1", default-features = false }
|
||||
nu-utils = { path = "../nu-utils", version = "0.104.1" }
|
||||
|
||||
log = { workspace = true }
|
||||
thiserror = "2.0"
|
||||
thiserror = "2.0.12"
|
||||
|
||||
[dev-dependencies]
|
||||
serde = { workspace = true }
|
||||
@ -33,4 +33,4 @@ local-socket = ["nu-plugin-core/local-socket"]
|
||||
|
||||
[target.'cfg(target_family = "unix")'.dependencies]
|
||||
# For setting the process group ID (EnterForeground / LeaveForeground)
|
||||
nix = { workspace = true, default-features = false, features = ["process"] }
|
||||
nix = { workspace = true, default-features = false, features = ["process"] }
|
||||
|
@ -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
|
||||
|
@ -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 }
|
||||
|
@ -20,6 +20,7 @@ pub enum TableMode {
|
||||
Restructured,
|
||||
AsciiRounded,
|
||||
BasicCompact,
|
||||
Single,
|
||||
}
|
||||
|
||||
impl FromStr for TableMode {
|
||||
@ -44,7 +45,8 @@ impl FromStr for TableMode {
|
||||
"restructured" => Ok(Self::Restructured),
|
||||
"ascii_rounded" => Ok(Self::AsciiRounded),
|
||||
"basic_compact" => Ok(Self::BasicCompact),
|
||||
_ => Err("'basic', 'thin', 'light', 'compact', 'with_love', 'compact_double', 'rounded', 'reinforced', 'heavy', 'none', 'psql', 'markdown', 'dots', 'restructured', 'ascii_rounded', or 'basic_compact'"),
|
||||
"single" => Ok(Self::Single),
|
||||
_ => Err("'basic', 'thin', 'light', 'compact', 'with_love', 'compact_double', 'rounded', 'reinforced', 'heavy', 'none', 'psql', 'markdown', 'dots', 'restructured', 'ascii_rounded', 'basic_compact' or 'single'"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -340,6 +342,7 @@ pub struct TableConfig {
|
||||
pub header_on_separator: bool,
|
||||
pub abbreviated_row_count: Option<usize>,
|
||||
pub footer_inheritance: bool,
|
||||
pub missing_value_symbol: String,
|
||||
}
|
||||
|
||||
impl IntoValue for TableConfig {
|
||||
@ -358,6 +361,7 @@ impl IntoValue for TableConfig {
|
||||
"header_on_separator" => self.header_on_separator.into_value(span),
|
||||
"abbreviated_row_count" => abbv_count,
|
||||
"footer_inheritance" => self.footer_inheritance.into_value(span),
|
||||
"missing_value_symbol" => self.missing_value_symbol.into_value(span),
|
||||
}
|
||||
.into_value(span)
|
||||
}
|
||||
@ -374,6 +378,7 @@ impl Default for TableConfig {
|
||||
padding: TableIndent::default(),
|
||||
abbreviated_row_count: None,
|
||||
footer_inheritance: false,
|
||||
missing_value_symbol: "❎".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -411,6 +416,10 @@ impl UpdateFromValue for TableConfig {
|
||||
_ => errors.type_mismatch(path, Type::custom("int or nothing"), val),
|
||||
},
|
||||
"footer_inheritance" => self.footer_inheritance.update(val, path, errors),
|
||||
"missing_value_symbol" => match val.as_str() {
|
||||
Ok(val) => self.missing_value_symbol = val.to_string(),
|
||||
Err(_) => errors.type_mismatch(path, Type::String, val),
|
||||
},
|
||||
_ => errors.unknown_option(path, val),
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,9 @@ use crate::{
|
||||
},
|
||||
eval_const::create_nu_constant,
|
||||
shell_error::io::IoError,
|
||||
BlockId, Category, Config, DeclId, FileId, GetSpan, Handlers, HistoryConfig, Module, ModuleId,
|
||||
OverlayId, ShellError, SignalAction, Signals, Signature, Span, SpanId, Type, Value, VarId,
|
||||
VirtualPathId,
|
||||
BlockId, Category, Config, DeclId, FileId, GetSpan, Handlers, HistoryConfig, JobId, Module,
|
||||
ModuleId, OverlayId, ShellError, SignalAction, Signals, Signature, Span, SpanId, Type, Value,
|
||||
VarId, VirtualPathId,
|
||||
};
|
||||
use fancy_regex::Regex;
|
||||
use lru::LruCache;
|
||||
@ -22,6 +22,8 @@ use std::{
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicU32, Ordering},
|
||||
mpsc::channel,
|
||||
mpsc::Sender,
|
||||
Arc, Mutex, MutexGuard, PoisonError,
|
||||
},
|
||||
};
|
||||
@ -31,7 +33,7 @@ type PoisonDebuggerError<'a> = PoisonError<MutexGuard<'a, Box<dyn Debugger>>>;
|
||||
#[cfg(feature = "plugin")]
|
||||
use crate::{PluginRegistryFile, PluginRegistryItem, RegisteredPlugin};
|
||||
|
||||
use super::{Jobs, ThreadJob};
|
||||
use super::{CurrentJob, Jobs, Mail, Mailbox, ThreadJob};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum VirtualPath {
|
||||
@ -117,7 +119,9 @@ pub struct EngineState {
|
||||
pub jobs: Arc<Mutex<Jobs>>,
|
||||
|
||||
// The job being executed with this engine state, or None if main thread
|
||||
pub current_thread_job: Option<ThreadJob>,
|
||||
pub current_job: CurrentJob,
|
||||
|
||||
pub root_job_sender: Sender<Mail>,
|
||||
|
||||
// When there are background jobs running, the interactive behavior of `exit` changes depending on
|
||||
// the value of this flag:
|
||||
@ -141,6 +145,8 @@ pub const UNKNOWN_SPAN_ID: SpanId = SpanId::new(0);
|
||||
|
||||
impl EngineState {
|
||||
pub fn new() -> Self {
|
||||
let (send, recv) = channel::<Mail>();
|
||||
|
||||
Self {
|
||||
files: vec![],
|
||||
virtual_paths: vec![],
|
||||
@ -196,7 +202,12 @@ impl EngineState {
|
||||
is_debugging: IsDebugging::new(false),
|
||||
debugger: Arc::new(Mutex::new(Box::new(NoopDebugger))),
|
||||
jobs: Arc::new(Mutex::new(Jobs::default())),
|
||||
current_thread_job: None,
|
||||
current_job: CurrentJob {
|
||||
id: JobId::new(0),
|
||||
background_thread_job: None,
|
||||
mailbox: Arc::new(Mutex::new(Mailbox::new(recv))),
|
||||
},
|
||||
root_job_sender: send,
|
||||
exit_warning_given: Arc::new(AtomicBool::new(false)),
|
||||
}
|
||||
}
|
||||
@ -1081,7 +1092,12 @@ impl EngineState {
|
||||
|
||||
// Determines whether the current state is being held by a background job
|
||||
pub fn is_background_job(&self) -> bool {
|
||||
self.current_thread_job.is_some()
|
||||
self.current_job.background_thread_job.is_some()
|
||||
}
|
||||
|
||||
// Gets the thread job entry
|
||||
pub fn current_thread_job(&self) -> Option<&ThreadJob> {
|
||||
self.current_job.background_thread_job.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,17 @@
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::{Arc, Mutex},
|
||||
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
|
||||
sync::{
|
||||
mpsc::{Receiver, RecvTimeoutError, Sender, TryRecvError},
|
||||
Arc, Mutex,
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use nu_system::{kill_by_pid, UnfreezeHandle};
|
||||
|
||||
use crate::Signals;
|
||||
use crate::{PipelineData, Signals};
|
||||
|
||||
use crate::JobId;
|
||||
|
||||
@ -139,13 +145,15 @@ pub struct ThreadJob {
|
||||
signals: Signals,
|
||||
pids: Arc<Mutex<HashSet<u32>>>,
|
||||
tag: Option<String>,
|
||||
pub sender: Sender<Mail>,
|
||||
}
|
||||
|
||||
impl ThreadJob {
|
||||
pub fn new(signals: Signals, tag: Option<String>) -> Self {
|
||||
pub fn new(signals: Signals, tag: Option<String>, sender: Sender<Mail>) -> Self {
|
||||
ThreadJob {
|
||||
signals,
|
||||
pids: Arc::new(Mutex::new(HashSet::default())),
|
||||
sender,
|
||||
tag,
|
||||
}
|
||||
}
|
||||
@ -238,3 +246,160 @@ impl FrozenJob {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the information about the background job currently being executed by this thread, if any
|
||||
#[derive(Clone)]
|
||||
pub struct CurrentJob {
|
||||
pub id: JobId,
|
||||
|
||||
// The background thread job associated with this thread.
|
||||
// If None, it indicates this thread is currently the main job
|
||||
pub background_thread_job: Option<ThreadJob>,
|
||||
|
||||
// note: although the mailbox is Mutex'd, it is only ever accessed
|
||||
// by the current job's threads
|
||||
pub mailbox: Arc<Mutex<Mailbox>>,
|
||||
}
|
||||
|
||||
// The storage for unread messages
|
||||
//
|
||||
// Messages are initially sent over a mpsc channel,
|
||||
// and may then be stored in a IgnoredMail struct when
|
||||
// filtered out by a tag.
|
||||
pub struct Mailbox {
|
||||
receiver: Receiver<Mail>,
|
||||
ignored_mail: IgnoredMail,
|
||||
}
|
||||
|
||||
impl Mailbox {
|
||||
pub fn new(receiver: Receiver<Mail>) -> Self {
|
||||
Mailbox {
|
||||
receiver,
|
||||
ignored_mail: IgnoredMail::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub fn recv_timeout(
|
||||
&mut self,
|
||||
filter_tag: Option<FilterTag>,
|
||||
timeout: Duration,
|
||||
) -> Result<PipelineData, RecvTimeoutError> {
|
||||
if let Some(value) = self.ignored_mail.pop(filter_tag) {
|
||||
Ok(value)
|
||||
} else {
|
||||
let mut waited_so_far = Duration::ZERO;
|
||||
let mut before = Instant::now();
|
||||
|
||||
while waited_so_far < timeout {
|
||||
let (tag, value) = self.receiver.recv_timeout(timeout - waited_so_far)?;
|
||||
|
||||
if filter_tag.is_none() || filter_tag == tag {
|
||||
return Ok(value);
|
||||
} else {
|
||||
self.ignored_mail.add((tag, value));
|
||||
let now = Instant::now();
|
||||
waited_so_far += now - before;
|
||||
before = now;
|
||||
}
|
||||
}
|
||||
|
||||
Err(RecvTimeoutError::Timeout)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub fn try_recv(
|
||||
&mut self,
|
||||
filter_tag: Option<FilterTag>,
|
||||
) -> Result<PipelineData, TryRecvError> {
|
||||
if let Some(value) = self.ignored_mail.pop(filter_tag) {
|
||||
Ok(value)
|
||||
} else {
|
||||
loop {
|
||||
let (tag, value) = self.receiver.try_recv()?;
|
||||
|
||||
if filter_tag.is_none() || filter_tag == tag {
|
||||
return Ok(value);
|
||||
} else {
|
||||
self.ignored_mail.add((tag, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.ignored_mail.clear();
|
||||
|
||||
while self.receiver.try_recv().is_ok() {}
|
||||
}
|
||||
}
|
||||
|
||||
// A data structure used to store messages which were received, but currently ignored by a tag filter
|
||||
// messages are added and popped in a first-in-first-out matter.
|
||||
#[derive(Default)]
|
||||
struct IgnoredMail {
|
||||
next_id: usize,
|
||||
messages: BTreeMap<usize, Mail>,
|
||||
by_tag: HashMap<FilterTag, BTreeSet<usize>>,
|
||||
}
|
||||
|
||||
pub type FilterTag = u64;
|
||||
pub type Mail = (Option<FilterTag>, PipelineData);
|
||||
|
||||
impl IgnoredMail {
|
||||
pub fn add(&mut self, (tag, value): Mail) {
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
|
||||
self.messages.insert(id, (tag, value));
|
||||
|
||||
if let Some(tag) = tag {
|
||||
self.by_tag.entry(tag).or_default().insert(id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pop(&mut self, tag: Option<FilterTag>) -> Option<PipelineData> {
|
||||
if let Some(tag) = tag {
|
||||
self.pop_oldest_with_tag(tag)
|
||||
} else {
|
||||
self.pop_oldest()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.messages.clear();
|
||||
self.by_tag.clear();
|
||||
}
|
||||
|
||||
fn pop_oldest(&mut self) -> Option<PipelineData> {
|
||||
let (id, (tag, value)) = self.messages.pop_first()?;
|
||||
|
||||
if let Some(tag) = tag {
|
||||
let needs_cleanup = if let Some(ids) = self.by_tag.get_mut(&tag) {
|
||||
ids.remove(&id);
|
||||
ids.is_empty()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if needs_cleanup {
|
||||
self.by_tag.remove(&tag);
|
||||
}
|
||||
}
|
||||
|
||||
Some(value)
|
||||
}
|
||||
|
||||
fn pop_oldest_with_tag(&mut self, tag: FilterTag) -> Option<PipelineData> {
|
||||
let ids = self.by_tag.get_mut(&tag)?;
|
||||
|
||||
let id = ids.pop_first()?;
|
||||
|
||||
if ids.is_empty() {
|
||||
self.by_tag.remove(&tag);
|
||||
}
|
||||
|
||||
Some(self.messages.remove(&id)?.1)
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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) => {
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -194,7 +194,7 @@ impl PostWaitCallback {
|
||||
child_pid: Option<u32>,
|
||||
tag: Option<String>,
|
||||
) -> Self {
|
||||
let this_job = engine_state.current_thread_job.clone();
|
||||
let this_job = engine_state.current_thread_job().cloned();
|
||||
let jobs = engine_state.jobs.clone();
|
||||
let is_interactive = engine_state.is_interactive;
|
||||
|
||||
|
@ -38,7 +38,7 @@ use std::{
|
||||
borrow::Cow,
|
||||
cmp::Ordering,
|
||||
fmt::{Debug, Display, Write},
|
||||
ops::Bound,
|
||||
ops::{Bound, ControlFlow, Deref},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
@ -1080,224 +1080,83 @@ impl Value {
|
||||
}
|
||||
|
||||
/// Follow a given cell path into the value: for example accessing select elements in a stream or list
|
||||
pub fn follow_cell_path(
|
||||
self,
|
||||
pub fn follow_cell_path<'out>(
|
||||
&'out self,
|
||||
cell_path: &[PathMember],
|
||||
insensitive: bool,
|
||||
) -> Result<Value, ShellError> {
|
||||
let mut current = self;
|
||||
) -> Result<Cow<'out, Value>, ShellError> {
|
||||
enum MultiLife<'out, 'local, T>
|
||||
where
|
||||
'out: 'local,
|
||||
T: ?Sized,
|
||||
{
|
||||
Out(&'out T),
|
||||
Local(&'local T),
|
||||
}
|
||||
|
||||
for member in cell_path {
|
||||
match member {
|
||||
PathMember::Int {
|
||||
val: count,
|
||||
span: origin_span,
|
||||
optional,
|
||||
} => {
|
||||
// Treat a numeric path member as `select <val>`
|
||||
match current {
|
||||
Value::List { mut vals, .. } => {
|
||||
if *count < vals.len() {
|
||||
// `vals` is owned and will be dropped right after this,
|
||||
// so we can `swap_remove` the value at index `count`
|
||||
// without worrying about preserving order.
|
||||
current = vals.swap_remove(*count);
|
||||
} else if *optional {
|
||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||
} else if vals.is_empty() {
|
||||
return Err(ShellError::AccessEmptyContent { span: *origin_span });
|
||||
} else {
|
||||
return Err(ShellError::AccessBeyondEnd {
|
||||
max_idx: vals.len() - 1,
|
||||
span: *origin_span,
|
||||
});
|
||||
}
|
||||
}
|
||||
Value::Binary { val, .. } => {
|
||||
if let Some(item) = val.get(*count) {
|
||||
current = Value::int(*item as i64, *origin_span);
|
||||
} else if *optional {
|
||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||
} else if val.is_empty() {
|
||||
return Err(ShellError::AccessEmptyContent { span: *origin_span });
|
||||
} else {
|
||||
return Err(ShellError::AccessBeyondEnd {
|
||||
max_idx: val.len() - 1,
|
||||
span: *origin_span,
|
||||
});
|
||||
}
|
||||
}
|
||||
Value::Range { ref val, .. } => {
|
||||
if let Some(item) = val
|
||||
.into_range_iter(current.span(), Signals::empty())
|
||||
.nth(*count)
|
||||
{
|
||||
current = item;
|
||||
} else if *optional {
|
||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||
} else {
|
||||
return Err(ShellError::AccessBeyondEndOfStream {
|
||||
span: *origin_span,
|
||||
});
|
||||
}
|
||||
}
|
||||
Value::Custom { ref val, .. } => {
|
||||
current =
|
||||
match val.follow_path_int(current.span(), *count, *origin_span) {
|
||||
Ok(val) => val,
|
||||
Err(err) => {
|
||||
if *optional {
|
||||
return Ok(Value::nothing(*origin_span));
|
||||
// short-circuit
|
||||
} else {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Value::Nothing { .. } if *optional => {
|
||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||
}
|
||||
// Records (and tables) are the only built-in which support column names,
|
||||
// so only use this message for them.
|
||||
Value::Record { .. } => {
|
||||
return Err(ShellError::TypeMismatch {
|
||||
err_message:"Can't access record values with a row index. Try specifying a column name instead".into(),
|
||||
span: *origin_span,
|
||||
});
|
||||
}
|
||||
Value::Error { error, .. } => return Err(*error),
|
||||
x => {
|
||||
return Err(ShellError::IncompatiblePathAccess {
|
||||
type_name: format!("{}", x.get_type()),
|
||||
span: *origin_span,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
PathMember::String {
|
||||
val: column_name,
|
||||
span: origin_span,
|
||||
optional,
|
||||
} => {
|
||||
let span = current.span();
|
||||
impl<'out, 'local, T> Deref for MultiLife<'out, 'local, T>
|
||||
where
|
||||
'out: 'local,
|
||||
T: ?Sized,
|
||||
{
|
||||
type Target = T;
|
||||
|
||||
match current {
|
||||
Value::Record { mut val, .. } => {
|
||||
// Make reverse iterate to avoid duplicate column leads to first value, actually last value is expected.
|
||||
if let Some(found) = val.to_mut().iter_mut().rev().find(|x| {
|
||||
if insensitive {
|
||||
x.0.eq_ignore_case(column_name)
|
||||
} else {
|
||||
x.0 == column_name
|
||||
}
|
||||
}) {
|
||||
current = std::mem::take(found.1);
|
||||
} else if *optional {
|
||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||
} else if let Some(suggestion) =
|
||||
did_you_mean(val.columns(), column_name)
|
||||
{
|
||||
return Err(ShellError::DidYouMean {
|
||||
suggestion,
|
||||
span: *origin_span,
|
||||
});
|
||||
} else {
|
||||
return Err(ShellError::CantFindColumn {
|
||||
col_name: column_name.clone(),
|
||||
span: Some(*origin_span),
|
||||
src_span: span,
|
||||
});
|
||||
}
|
||||
}
|
||||
// String access of Lists always means Table access.
|
||||
// Create a List which contains each matching value for contained
|
||||
// records in the source list.
|
||||
Value::List { vals, .. } => {
|
||||
let list = vals
|
||||
.into_iter()
|
||||
.map(|val| {
|
||||
let val_span = val.span();
|
||||
match val {
|
||||
Value::Record { mut val, .. } => {
|
||||
if let Some(found) =
|
||||
val.to_mut().iter_mut().rev().find(|x| {
|
||||
if insensitive {
|
||||
x.0.eq_ignore_case(column_name)
|
||||
} else {
|
||||
x.0 == column_name
|
||||
}
|
||||
})
|
||||
{
|
||||
Ok(std::mem::take(found.1))
|
||||
} else if *optional {
|
||||
Ok(Value::nothing(*origin_span))
|
||||
} else if let Some(suggestion) =
|
||||
did_you_mean(val.columns(), column_name)
|
||||
{
|
||||
Err(ShellError::DidYouMean {
|
||||
suggestion,
|
||||
span: *origin_span,
|
||||
})
|
||||
} else {
|
||||
Err(ShellError::CantFindColumn {
|
||||
col_name: column_name.clone(),
|
||||
span: Some(*origin_span),
|
||||
src_span: val_span,
|
||||
})
|
||||
}
|
||||
}
|
||||
Value::Nothing { .. } if *optional => {
|
||||
Ok(Value::nothing(*origin_span))
|
||||
}
|
||||
_ => Err(ShellError::CantFindColumn {
|
||||
col_name: column_name.clone(),
|
||||
span: Some(*origin_span),
|
||||
src_span: val_span,
|
||||
}),
|
||||
}
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
current = Value::list(list, span);
|
||||
}
|
||||
Value::Custom { ref val, .. } => {
|
||||
current = match val.follow_path_string(
|
||||
current.span(),
|
||||
column_name.clone(),
|
||||
*origin_span,
|
||||
) {
|
||||
Ok(val) => val,
|
||||
Err(err) => {
|
||||
if *optional {
|
||||
return Ok(Value::nothing(*origin_span));
|
||||
// short-circuit
|
||||
} else {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Nothing { .. } if *optional => {
|
||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||
}
|
||||
Value::Error { error, .. } => return Err(*error),
|
||||
x => {
|
||||
return Err(ShellError::IncompatiblePathAccess {
|
||||
type_name: format!("{}", x.get_type()),
|
||||
span: *origin_span,
|
||||
});
|
||||
}
|
||||
}
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match *self {
|
||||
MultiLife::Out(x) => x,
|
||||
MultiLife::Local(x) => x,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A dummy value is required, otherwise rust doesn't allow references, which we need for
|
||||
// the `std::ptr::eq` comparison
|
||||
let mut store: Value = Value::test_nothing();
|
||||
let mut current: MultiLife<'out, '_, Value> = MultiLife::Out(self);
|
||||
|
||||
for member in cell_path {
|
||||
current = match current {
|
||||
MultiLife::Out(current) => match get_value_member(current, member, insensitive)? {
|
||||
ControlFlow::Break(span) => return Ok(Cow::Owned(Value::nothing(span))),
|
||||
ControlFlow::Continue(x) => match x {
|
||||
Cow::Borrowed(x) => MultiLife::Out(x),
|
||||
Cow::Owned(x) => {
|
||||
store = x;
|
||||
MultiLife::Local(&store)
|
||||
}
|
||||
},
|
||||
},
|
||||
MultiLife::Local(current) => {
|
||||
match get_value_member(current, member, insensitive)? {
|
||||
ControlFlow::Break(span) => return Ok(Cow::Owned(Value::nothing(span))),
|
||||
ControlFlow::Continue(x) => match x {
|
||||
Cow::Borrowed(x) => MultiLife::Local(x),
|
||||
Cow::Owned(x) => {
|
||||
store = x;
|
||||
MultiLife::Local(&store)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// If a single Value::Error was produced by the above (which won't happen if nullify_errors is true), unwrap it now.
|
||||
// Note that Value::Errors inside Lists remain as they are, so that the rest of the list can still potentially be used.
|
||||
if let Value::Error { error, .. } = current {
|
||||
Err(*error)
|
||||
if let Value::Error { error, .. } = &*current {
|
||||
Err(error.as_ref().clone())
|
||||
} else {
|
||||
Ok(current)
|
||||
Ok(match current {
|
||||
MultiLife::Out(x) => Cow::Borrowed(x),
|
||||
MultiLife::Local(x) => {
|
||||
let x = if std::ptr::eq(x, &store) {
|
||||
store
|
||||
} else {
|
||||
x.clone()
|
||||
};
|
||||
Cow::Owned(x)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1307,9 +1166,7 @@ impl Value {
|
||||
cell_path: &[PathMember],
|
||||
callback: Box<dyn FnOnce(&Value) -> Value>,
|
||||
) -> Result<(), ShellError> {
|
||||
let orig = self.clone();
|
||||
|
||||
let new_val = callback(&orig.follow_cell_path(cell_path, false)?);
|
||||
let new_val = callback(self.follow_cell_path(cell_path, false)?.as_ref());
|
||||
|
||||
match new_val {
|
||||
Value::Error { error, .. } => Err(*error),
|
||||
@ -1409,9 +1266,7 @@ impl Value {
|
||||
cell_path: &[PathMember],
|
||||
callback: Box<dyn FnOnce(&Value) -> Value + 'a>,
|
||||
) -> Result<(), ShellError> {
|
||||
let orig = self.clone();
|
||||
|
||||
let new_val = callback(&orig.follow_cell_path(cell_path, false)?);
|
||||
let new_val = callback(self.follow_cell_path(cell_path, false)?.as_ref());
|
||||
|
||||
match new_val {
|
||||
Value::Error { error, .. } => Err(*error),
|
||||
@ -2147,6 +2002,198 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_value_member<'a>(
|
||||
current: &'a Value,
|
||||
member: &PathMember,
|
||||
insensitive: bool,
|
||||
) -> Result<ControlFlow<Span, Cow<'a, Value>>, ShellError> {
|
||||
match member {
|
||||
PathMember::Int {
|
||||
val: count,
|
||||
span: origin_span,
|
||||
optional,
|
||||
} => {
|
||||
// Treat a numeric path member as `select <val>`
|
||||
match current {
|
||||
Value::List { vals, .. } => {
|
||||
if *count < vals.len() {
|
||||
Ok(ControlFlow::Continue(Cow::Borrowed(&vals[*count])))
|
||||
} else if *optional {
|
||||
Ok(ControlFlow::Break(*origin_span))
|
||||
// short-circuit
|
||||
} else if vals.is_empty() {
|
||||
Err(ShellError::AccessEmptyContent { span: *origin_span })
|
||||
} else {
|
||||
Err(ShellError::AccessBeyondEnd {
|
||||
max_idx: vals.len() - 1,
|
||||
span: *origin_span,
|
||||
})
|
||||
}
|
||||
}
|
||||
Value::Binary { val, .. } => {
|
||||
if let Some(item) = val.get(*count) {
|
||||
Ok(ControlFlow::Continue(Cow::Owned(Value::int(
|
||||
*item as i64,
|
||||
*origin_span,
|
||||
))))
|
||||
} else if *optional {
|
||||
Ok(ControlFlow::Break(*origin_span))
|
||||
// short-circuit
|
||||
} else if val.is_empty() {
|
||||
Err(ShellError::AccessEmptyContent { span: *origin_span })
|
||||
} else {
|
||||
Err(ShellError::AccessBeyondEnd {
|
||||
max_idx: val.len() - 1,
|
||||
span: *origin_span,
|
||||
})
|
||||
}
|
||||
}
|
||||
Value::Range { ref val, .. } => {
|
||||
if let Some(item) = val
|
||||
.into_range_iter(current.span(), Signals::empty())
|
||||
.nth(*count)
|
||||
{
|
||||
Ok(ControlFlow::Continue(Cow::Owned(item)))
|
||||
} else if *optional {
|
||||
Ok(ControlFlow::Break(*origin_span))
|
||||
// short-circuit
|
||||
} else {
|
||||
Err(ShellError::AccessBeyondEndOfStream {
|
||||
span: *origin_span,
|
||||
})
|
||||
}
|
||||
}
|
||||
Value::Custom { ref val, .. } => {
|
||||
match val.follow_path_int(current.span(), *count, *origin_span)
|
||||
{
|
||||
Ok(val) => Ok(ControlFlow::Continue(Cow::Owned(val))),
|
||||
Err(err) => {
|
||||
if *optional {
|
||||
Ok(ControlFlow::Break(*origin_span))
|
||||
// short-circuit
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Nothing { .. } if *optional => Ok(ControlFlow::Break(*origin_span)),
|
||||
// Records (and tables) are the only built-in which support column names,
|
||||
// so only use this message for them.
|
||||
Value::Record { .. } => Err(ShellError::TypeMismatch {
|
||||
err_message:"Can't access record values with a row index. Try specifying a column name instead".into(),
|
||||
span: *origin_span,
|
||||
}),
|
||||
Value::Error { error, .. } => Err(*error.clone()),
|
||||
x => Err(ShellError::IncompatiblePathAccess { type_name: format!("{}", x.get_type()), span: *origin_span }),
|
||||
}
|
||||
}
|
||||
PathMember::String {
|
||||
val: column_name,
|
||||
span: origin_span,
|
||||
optional,
|
||||
} => {
|
||||
let span = current.span();
|
||||
match current {
|
||||
Value::Record { val, .. } => {
|
||||
if let Some(found) = val.iter().rev().find(|x| {
|
||||
if insensitive {
|
||||
x.0.eq_ignore_case(column_name)
|
||||
} else {
|
||||
x.0 == column_name
|
||||
}
|
||||
}) {
|
||||
Ok(ControlFlow::Continue(Cow::Borrowed(found.1)))
|
||||
} else if *optional {
|
||||
Ok(ControlFlow::Break(*origin_span))
|
||||
// short-circuit
|
||||
} else if let Some(suggestion) = did_you_mean(val.columns(), column_name) {
|
||||
Err(ShellError::DidYouMean {
|
||||
suggestion,
|
||||
span: *origin_span,
|
||||
})
|
||||
} else {
|
||||
Err(ShellError::CantFindColumn {
|
||||
col_name: column_name.clone(),
|
||||
span: Some(*origin_span),
|
||||
src_span: span,
|
||||
})
|
||||
}
|
||||
}
|
||||
// String access of Lists always means Table access.
|
||||
// Create a List which contains each matching value for contained
|
||||
// records in the source list.
|
||||
Value::List { vals, .. } => {
|
||||
let list = vals
|
||||
.iter()
|
||||
.map(|val| {
|
||||
let val_span = val.span();
|
||||
match val {
|
||||
Value::Record { val, .. } => {
|
||||
if let Some(found) = val.iter().rev().find(|x| {
|
||||
if insensitive {
|
||||
x.0.eq_ignore_case(column_name)
|
||||
} else {
|
||||
x.0 == column_name
|
||||
}
|
||||
}) {
|
||||
Ok(found.1.clone())
|
||||
} else if *optional {
|
||||
Ok(Value::nothing(*origin_span))
|
||||
} else if let Some(suggestion) =
|
||||
did_you_mean(val.columns(), column_name)
|
||||
{
|
||||
Err(ShellError::DidYouMean {
|
||||
suggestion,
|
||||
span: *origin_span,
|
||||
})
|
||||
} else {
|
||||
Err(ShellError::CantFindColumn {
|
||||
col_name: column_name.clone(),
|
||||
span: Some(*origin_span),
|
||||
src_span: val_span,
|
||||
})
|
||||
}
|
||||
}
|
||||
Value::Nothing { .. } if *optional => {
|
||||
Ok(Value::nothing(*origin_span))
|
||||
}
|
||||
_ => Err(ShellError::CantFindColumn {
|
||||
col_name: column_name.clone(),
|
||||
span: Some(*origin_span),
|
||||
src_span: val_span,
|
||||
}),
|
||||
}
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(ControlFlow::Continue(Cow::Owned(Value::list(list, span))))
|
||||
}
|
||||
Value::Custom { ref val, .. } => {
|
||||
match val.follow_path_string(current.span(), column_name.clone(), *origin_span)
|
||||
{
|
||||
Ok(val) => Ok(ControlFlow::Continue(Cow::Owned(val))),
|
||||
Err(err) => {
|
||||
if *optional {
|
||||
Ok(ControlFlow::Break(*origin_span))
|
||||
// short-circuit
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Nothing { .. } if *optional => Ok(ControlFlow::Break(*origin_span)),
|
||||
Value::Error { error, .. } => Err(error.as_ref().clone()),
|
||||
x => Err(ShellError::IncompatiblePathAccess {
|
||||
type_name: format!("{}", x.get_type()),
|
||||
span: *origin_span,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Value {
|
||||
fn default() -> Self {
|
||||
Value::Nothing {
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ def local-transpose_to_record [] {
|
||||
}
|
||||
|
||||
@test
|
||||
def local-using_cellpaths [] {
|
||||
def local-using_closure [] {
|
||||
if ('sqlite' not-in (version).features) { return }
|
||||
|
||||
let key = (random uuid)
|
||||
@ -91,8 +91,8 @@ def local-using_cellpaths [] {
|
||||
let size_key = (random uuid)
|
||||
|
||||
ls
|
||||
| kv set $name_key $in.name
|
||||
| kv set $size_key $in.size
|
||||
| kv set $name_key { get name }
|
||||
| kv set $size_key { get size }
|
||||
|
||||
let expected = "list<string>"
|
||||
let actual = (kv get $name_key | describe)
|
||||
@ -106,22 +106,6 @@ def local-using_cellpaths [] {
|
||||
kv drop $size_key
|
||||
}
|
||||
|
||||
@test
|
||||
def local-using_closure [] {
|
||||
if ('sqlite' not-in (version).features) { return }
|
||||
|
||||
let key = (random uuid)
|
||||
|
||||
kv set $key 5
|
||||
kv set $key { $in + 1 }
|
||||
|
||||
let expected = 6
|
||||
let actual = (kv get $key)
|
||||
assert equal $actual $expected
|
||||
|
||||
kv drop $key
|
||||
}
|
||||
|
||||
@test
|
||||
def local-return-entire-list [] {
|
||||
if ('sqlite' not-in (version).features) { return }
|
||||
@ -153,7 +137,7 @@ def local-return_value_only [] {
|
||||
let key = (random uuid)
|
||||
|
||||
let expected = 'VALUE'
|
||||
let actual = ('value' | kv set -r v $key ($in | str upcase))
|
||||
let actual = ('value' | kv set -r v $key {str upcase})
|
||||
|
||||
assert equal $actual $expected
|
||||
|
||||
@ -249,7 +233,7 @@ def universal-transpose_to_record [] {
|
||||
}
|
||||
|
||||
@test
|
||||
def universal-using_cellpaths [] {
|
||||
def universal-using_closure [] {
|
||||
if ('sqlite' not-in (version).features) { return }
|
||||
|
||||
let key = (random uuid)
|
||||
@ -259,8 +243,8 @@ def universal-using_cellpaths [] {
|
||||
let size_key = (random uuid)
|
||||
|
||||
ls
|
||||
| kv set -u $name_key $in.name
|
||||
| kv set -u $size_key $in.size
|
||||
| kv set -u $name_key { get name }
|
||||
| kv set -u $size_key { get size }
|
||||
|
||||
let expected = "list<string>"
|
||||
let actual = (kv get -u $name_key | describe)
|
||||
@ -275,24 +259,6 @@ def universal-using_cellpaths [] {
|
||||
rm $env.NU_UNIVERSAL_KV_PATH
|
||||
}
|
||||
|
||||
@test
|
||||
def universal-using_closure [] {
|
||||
if ('sqlite' not-in (version).features) { return }
|
||||
|
||||
let key = (random uuid)
|
||||
$env.NU_UNIVERSAL_KV_PATH = (mktemp -t --suffix .sqlite3)
|
||||
|
||||
kv set -u $key 5
|
||||
kv set -u $key { $in + 1 }
|
||||
|
||||
let expected = 6
|
||||
let actual = (kv get -u $key)
|
||||
assert equal $actual $expected
|
||||
|
||||
kv drop -u $key
|
||||
rm $env.NU_UNIVERSAL_KV_PATH
|
||||
}
|
||||
|
||||
@test
|
||||
def universal-return-entire-list [] {
|
||||
if ('sqlite' not-in (version).features) { return }
|
||||
@ -329,7 +295,7 @@ def universal-return_value_only [] {
|
||||
let key = (random uuid)
|
||||
|
||||
let expected = 'VALUE'
|
||||
let actual = ('value' | kv set --universal -r v $key ($in | str upcase))
|
||||
let actual = ('value' | kv set --universal -r v $key {str upcase})
|
||||
|
||||
assert equal $actual $expected
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-table"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-table"
|
||||
version = "0.103.1"
|
||||
version = "0.104.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
@ -14,13 +14,13 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.103.1", default-features = false }
|
||||
nu-utils = { path = "../nu-utils", version = "0.103.1", default-features = false }
|
||||
nu-engine = { path = "../nu-engine", version = "0.103.1", default-features = false }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.103.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false }
|
||||
nu-utils = { path = "../nu-utils", version = "0.104.1", default-features = false }
|
||||
nu-engine = { path = "../nu-engine", version = "0.104.1", default-features = false }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.104.1" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
fancy-regex = { workspace = true }
|
||||
tabled = { workspace = true, features = ["ansi"], default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
# nu-test-support = { path="../nu-test-support", version = "0.103.1" }
|
||||
# nu-test-support = { path="../nu-test-support", version = "0.104.1" }
|
@ -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)));
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
// NOTE: TODO the above we could expose something like [`WidthCtrl`] in which case we could also laverage the width list build right away.
|
||||
// currently it seems like we do recacalculate it for `table -e`?
|
||||
|
||||
use std::{cmp::min, collections::HashMap};
|
||||
use std::cmp::{max, min};
|
||||
|
||||
use nu_ansi_term::Style;
|
||||
use nu_color_config::TextStyle;
|
||||
@ -13,13 +13,14 @@ use tabled::{
|
||||
builder::Builder,
|
||||
grid::{
|
||||
ansi::ANSIBuf,
|
||||
colors::Colors,
|
||||
config::{
|
||||
AlignmentHorizontal, ColoredConfig, Entity, Indent, Position, Sides, SpannedConfig,
|
||||
},
|
||||
dimension::{CompleteDimension, Dimension, PeekableGridDimension},
|
||||
dimension::{CompleteDimension, Dimension, IterGridDimension},
|
||||
records::{
|
||||
vec_records::{Text, VecRecords},
|
||||
ExactRecords, Records,
|
||||
vec_records::{Cell, Text, VecRecords},
|
||||
IntoRecords, IterRecords, Records,
|
||||
},
|
||||
},
|
||||
settings::{
|
||||
@ -43,24 +44,30 @@ pub type NuRecordsValue = Text<String>;
|
||||
/// NuTable is a table rendering implementation.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NuTable {
|
||||
data: NuRecords,
|
||||
data: Vec<Vec<NuRecordsValue>>,
|
||||
widths: Vec<usize>,
|
||||
count_rows: usize,
|
||||
count_cols: usize,
|
||||
styles: Styles,
|
||||
alignments: Alignments,
|
||||
config: TableConfig,
|
||||
}
|
||||
|
||||
impl NuTable {
|
||||
/// Creates an empty [`NuTable`] instance.
|
||||
pub fn new(count_rows: usize, count_columns: usize) -> Self {
|
||||
pub fn new(count_rows: usize, count_cols: usize) -> Self {
|
||||
Self {
|
||||
data: VecRecords::new(vec![vec![Text::default(); count_columns]; count_rows]),
|
||||
styles: Styles::default(),
|
||||
alignments: Alignments {
|
||||
data: AlignmentHorizontal::Left,
|
||||
index: AlignmentHorizontal::Right,
|
||||
header: AlignmentHorizontal::Center,
|
||||
columns: HashMap::default(),
|
||||
cells: HashMap::default(),
|
||||
data: vec![vec![Text::default(); count_cols]; count_rows],
|
||||
widths: vec![2; count_cols],
|
||||
count_rows,
|
||||
count_cols,
|
||||
styles: Styles {
|
||||
cfg: ColoredConfig::default(),
|
||||
alignments: CellConfiguration {
|
||||
data: AlignmentHorizontal::Left,
|
||||
index: AlignmentHorizontal::Right,
|
||||
header: AlignmentHorizontal::Center,
|
||||
},
|
||||
colors: CellConfiguration::default(),
|
||||
},
|
||||
config: TableConfig {
|
||||
theme: TableTheme::basic(),
|
||||
@ -76,84 +83,125 @@ impl NuTable {
|
||||
|
||||
/// Return amount of rows.
|
||||
pub fn count_rows(&self) -> usize {
|
||||
self.data.count_rows()
|
||||
self.count_rows
|
||||
}
|
||||
|
||||
/// Return amount of columns.
|
||||
pub fn count_columns(&self) -> usize {
|
||||
self.data.count_columns()
|
||||
self.count_cols
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, pos: Position, text: String) {
|
||||
self.data[pos.row][pos.col] = Text::new(text);
|
||||
}
|
||||
|
||||
pub fn insert_row(&mut self, index: usize, row: Vec<String>) {
|
||||
let data = &mut self.data[index];
|
||||
|
||||
for (col, text) in row.into_iter().enumerate() {
|
||||
data[col] = Text::new(text);
|
||||
}
|
||||
let text = Text::new(text);
|
||||
self.widths[pos.col] = max(
|
||||
self.widths[pos.col],
|
||||
text.width() + indent_sum(self.config.indent),
|
||||
);
|
||||
self.data[pos.row][pos.col] = text;
|
||||
}
|
||||
|
||||
pub fn set_row(&mut self, index: usize, row: Vec<NuRecordsValue>) {
|
||||
assert_eq!(self.data[index].len(), row.len());
|
||||
|
||||
for (i, text) in row.iter().enumerate() {
|
||||
self.widths[i] = max(
|
||||
self.widths[i],
|
||||
text.width() + indent_sum(self.config.indent),
|
||||
);
|
||||
}
|
||||
|
||||
self.data[index] = row;
|
||||
}
|
||||
|
||||
pub fn set_column_style(&mut self, column: usize, style: TextStyle) {
|
||||
if let Some(style) = style.color_style {
|
||||
let style = convert_style(style);
|
||||
self.styles.columns.insert(column, style);
|
||||
pub fn pop_column(&mut self, count: usize) {
|
||||
self.count_cols -= count;
|
||||
self.widths.truncate(self.count_cols);
|
||||
for row in &mut self.data[..] {
|
||||
row.truncate(self.count_cols);
|
||||
}
|
||||
|
||||
let alignment = convert_alignment(style.alignment);
|
||||
if alignment != self.alignments.data {
|
||||
self.alignments.columns.insert(column, alignment);
|
||||
// set to default styles of the popped columns
|
||||
for i in 0..count {
|
||||
let col = self.count_cols + i;
|
||||
for row in 0..self.count_rows {
|
||||
self.styles
|
||||
.cfg
|
||||
.set_alignment_horizontal(Entity::Cell(row, col), self.styles.alignments.data);
|
||||
self.styles
|
||||
.cfg
|
||||
.set_color(Entity::Cell(row, col), ANSIBuf::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_column(&mut self, text: String) {
|
||||
let value = Text::new(text);
|
||||
|
||||
self.widths
|
||||
.push(value.width() + indent_sum(self.config.indent));
|
||||
|
||||
for row in &mut self.data[..] {
|
||||
row.push(value.clone());
|
||||
}
|
||||
|
||||
self.count_cols += 1;
|
||||
}
|
||||
|
||||
pub fn insert_style(&mut self, pos: Position, style: TextStyle) {
|
||||
if let Some(style) = style.color_style {
|
||||
let style = convert_style(style);
|
||||
self.styles.cells.insert(pos, style);
|
||||
self.styles.cfg.set_color(pos.into(), style.into());
|
||||
}
|
||||
|
||||
let alignment = convert_alignment(style.alignment);
|
||||
if alignment != self.alignments.data {
|
||||
self.alignments.cells.insert(pos, alignment);
|
||||
if alignment != self.styles.alignments.data {
|
||||
self.styles
|
||||
.cfg
|
||||
.set_alignment_horizontal(pos.into(), alignment);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_header_style(&mut self, style: TextStyle) {
|
||||
if let Some(style) = style.color_style {
|
||||
let style = convert_style(style);
|
||||
self.styles.header = style;
|
||||
self.styles.colors.header = style;
|
||||
}
|
||||
|
||||
self.alignments.header = convert_alignment(style.alignment);
|
||||
self.styles.alignments.header = convert_alignment(style.alignment);
|
||||
}
|
||||
|
||||
pub fn set_index_style(&mut self, style: TextStyle) {
|
||||
if let Some(style) = style.color_style {
|
||||
let style = convert_style(style);
|
||||
self.styles.index = style;
|
||||
self.styles.colors.index = style;
|
||||
}
|
||||
|
||||
self.alignments.index = convert_alignment(style.alignment);
|
||||
self.styles.alignments.index = convert_alignment(style.alignment);
|
||||
}
|
||||
|
||||
pub fn set_data_style(&mut self, style: TextStyle) {
|
||||
if let Some(style) = style.color_style {
|
||||
let style = convert_style(style);
|
||||
self.styles.data = style;
|
||||
if !style.is_plain() {
|
||||
let style = convert_style(style);
|
||||
self.styles.cfg.set_color(Entity::Global, style.into());
|
||||
}
|
||||
}
|
||||
|
||||
self.alignments.data = convert_alignment(style.alignment);
|
||||
let alignment = convert_alignment(style.alignment);
|
||||
self.styles
|
||||
.cfg
|
||||
.set_alignment_horizontal(Entity::Global, alignment);
|
||||
self.styles.alignments.data = alignment;
|
||||
}
|
||||
|
||||
// NOTE: Crusial to be called before data changes (todo fix interface)
|
||||
pub fn set_indent(&mut self, indent: TableIndent) {
|
||||
self.config.indent = indent;
|
||||
|
||||
let pad = indent_sum(indent);
|
||||
for w in &mut self.widths {
|
||||
*w = pad;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_theme(&mut self, theme: TableTheme) {
|
||||
@ -180,7 +228,9 @@ impl NuTable {
|
||||
self.config.border_color = (!color.is_plain()).then_some(color);
|
||||
}
|
||||
|
||||
pub fn get_records_mut(&mut self) -> &mut NuRecords {
|
||||
// NOTE: BE CAREFUL TO KEEP WIDTH UNCHANGED
|
||||
// TODO: fix interface
|
||||
pub fn get_records_mut(&mut self) -> &mut [Vec<NuRecordsValue>] {
|
||||
&mut self.data
|
||||
}
|
||||
|
||||
@ -194,32 +244,42 @@ impl NuTable {
|
||||
/// Return a total table width.
|
||||
pub fn total_width(&self) -> usize {
|
||||
let config = create_config(&self.config.theme, false, None);
|
||||
let pad = indent_sum(self.config.indent);
|
||||
let widths = build_width(&self.data, pad);
|
||||
get_total_width2(&widths, &config)
|
||||
get_total_width2(&self.widths, &config)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Vec<Text<String>>>> for NuTable {
|
||||
fn from(value: Vec<Vec<Text<String>>>) -> Self {
|
||||
let mut nutable = Self::new(0, 0);
|
||||
nutable.data = VecRecords::new(value);
|
||||
let count_rows = value.len();
|
||||
let count_cols = if value.is_empty() { 0 } else { value[0].len() };
|
||||
|
||||
nutable
|
||||
let mut t = Self::new(count_rows, count_cols);
|
||||
t.data = value;
|
||||
table_recalculate_widths(&mut t);
|
||||
|
||||
t
|
||||
}
|
||||
}
|
||||
|
||||
type Alignments = CellConfiguration<AlignmentHorizontal>;
|
||||
fn table_recalculate_widths(t: &mut NuTable) {
|
||||
let pad = indent_sum(t.config.indent);
|
||||
let records = IterRecords::new(&t.data, t.count_cols, Some(t.count_rows));
|
||||
let widths = build_width(records, pad);
|
||||
t.widths = widths;
|
||||
}
|
||||
|
||||
type Styles = CellConfiguration<Color>;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Hash)]
|
||||
struct CellConfiguration<Value> {
|
||||
data: Value,
|
||||
index: Value,
|
||||
header: Value,
|
||||
columns: HashMap<usize, Value>,
|
||||
cells: HashMap<Position, Value>,
|
||||
data: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct Styles {
|
||||
cfg: ColoredConfig,
|
||||
colors: CellConfiguration<Color>,
|
||||
alignments: CellConfiguration<AlignmentHorizontal>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -304,49 +364,72 @@ fn table_insert_footer_if(t: &mut NuTable) {
|
||||
}
|
||||
|
||||
fn table_truncate(t: &mut NuTable, termwidth: usize) -> Option<WidthEstimation> {
|
||||
let widths = maybe_truncate_columns(&mut t.data, &t.config, termwidth);
|
||||
let widths = maybe_truncate_columns(&mut t.data, t.widths.clone(), &t.config, termwidth);
|
||||
if widths.needed.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// reset style for last column which is a trail one
|
||||
if widths.trail {
|
||||
let col = widths.needed.len() - 1;
|
||||
for row in 0..t.count_rows {
|
||||
t.styles
|
||||
.cfg
|
||||
.set_alignment_horizontal(Entity::Cell(row, col), t.styles.alignments.data);
|
||||
t.styles
|
||||
.cfg
|
||||
.set_color(Entity::Cell(row, col), ANSIBuf::default());
|
||||
}
|
||||
}
|
||||
|
||||
Some(widths)
|
||||
}
|
||||
|
||||
fn remove_header(t: &mut NuTable) -> HeadInfo {
|
||||
let head: Vec<String> = t
|
||||
// move settings by one row down
|
||||
for row in 1..t.data.len() {
|
||||
for col in 0..t.count_cols {
|
||||
let alignment = *t
|
||||
.styles
|
||||
.cfg
|
||||
.get_alignment_horizontal(Position::new(row, col));
|
||||
if alignment != t.styles.alignments.data {
|
||||
t.styles
|
||||
.cfg
|
||||
.set_alignment_horizontal(Entity::Cell(row - 1, col), alignment);
|
||||
}
|
||||
|
||||
// TODO: use get_color from upstream (when released)
|
||||
let color = t
|
||||
.styles
|
||||
.cfg
|
||||
.get_colors()
|
||||
.get_color(Position::new(row, col))
|
||||
.cloned();
|
||||
if let Some(color) = color {
|
||||
t.styles.cfg.set_color(Entity::Cell(row - 1, col), color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let head = t
|
||||
.data
|
||||
.remove(0)
|
||||
.into_iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
let align = t.alignments.header;
|
||||
let color = if is_color_empty(&t.styles.header) {
|
||||
None
|
||||
} else {
|
||||
Some(t.styles.header.clone())
|
||||
};
|
||||
|
||||
// move settings by one row down
|
||||
t.alignments.cells = t
|
||||
.alignments
|
||||
.cells
|
||||
.drain()
|
||||
.filter(|(k, _)| k.row != 0)
|
||||
.map(|(k, v)| (k - (1, 0), v))
|
||||
.collect();
|
||||
t.alignments.header = AlignmentHorizontal::Center;
|
||||
// WE NEED TO RELCULATE WIDTH.
|
||||
// TODO: cause we have configuration beforehand we can just not calculate it in?
|
||||
table_recalculate_widths(t);
|
||||
|
||||
// move settings by one row down
|
||||
t.styles.cells = t
|
||||
.styles
|
||||
.cells
|
||||
.drain()
|
||||
.filter(|(k, _)| k.row != 0)
|
||||
.map(|(k, v)| (k - (1, 0), v))
|
||||
.collect();
|
||||
t.styles.header = Color::empty();
|
||||
let alignment = t.styles.alignments.header;
|
||||
let color = get_color_if_exists(&t.styles.colors.header);
|
||||
|
||||
HeadInfo::new(head, align, color)
|
||||
t.styles.alignments.header = AlignmentHorizontal::Center;
|
||||
t.styles.colors.header = Color::empty();
|
||||
|
||||
HeadInfo::new(head, alignment, color)
|
||||
}
|
||||
|
||||
fn draw_table(
|
||||
@ -359,19 +442,24 @@ fn draw_table(
|
||||
structure.with_footer = structure.with_footer && head.is_none();
|
||||
let sep_color = t.config.border_color;
|
||||
|
||||
let data: Vec<Vec<_>> = t.data.into();
|
||||
let data = t.data;
|
||||
let mut table = Builder::from_vec(data).build();
|
||||
|
||||
set_styles(&mut table, t.styles, &structure);
|
||||
set_indent(&mut table, t.config.indent);
|
||||
load_theme(&mut table, &t.config.theme, &structure, sep_color);
|
||||
align_table(&mut table, t.alignments, &structure);
|
||||
colorize_table(&mut table, t.styles, &structure);
|
||||
truncate_table(&mut table, &t.config, width, termwidth);
|
||||
table_set_border_header(&mut table, head, &t.config);
|
||||
|
||||
table_to_string(table, termwidth)
|
||||
}
|
||||
|
||||
fn set_styles(table: &mut Table, styles: Styles, structure: &TableStructure) {
|
||||
table.with(styles.cfg);
|
||||
align_table(table, styles.alignments, structure);
|
||||
colorize_table(table, styles.colors, structure);
|
||||
}
|
||||
|
||||
fn table_set_border_header(table: &mut Table, head: Option<HeadInfo>, cfg: &TableConfig) {
|
||||
let head = match head {
|
||||
Some(head) => head,
|
||||
@ -420,7 +508,6 @@ fn set_indent(table: &mut Table, indent: TableIndent) {
|
||||
|
||||
fn table_to_string(table: Table, termwidth: usize) -> Option<String> {
|
||||
let total_width = table.total_width();
|
||||
|
||||
if total_width > termwidth {
|
||||
None
|
||||
} else {
|
||||
@ -462,15 +549,23 @@ struct WidthEstimation {
|
||||
#[allow(dead_code)]
|
||||
total: usize,
|
||||
truncate: bool,
|
||||
trail: bool,
|
||||
}
|
||||
|
||||
impl WidthEstimation {
|
||||
fn new(original: Vec<usize>, needed: Vec<usize>, total: usize, truncate: bool) -> Self {
|
||||
fn new(
|
||||
original: Vec<usize>,
|
||||
needed: Vec<usize>,
|
||||
total: usize,
|
||||
truncate: bool,
|
||||
trail: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
original,
|
||||
needed,
|
||||
total,
|
||||
truncate,
|
||||
trail,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -546,18 +641,12 @@ fn width_ctrl_truncate(
|
||||
dims.set_widths(ctrl.width.needed);
|
||||
}
|
||||
|
||||
fn align_table(table: &mut Table, alignments: Alignments, structure: &TableStructure) {
|
||||
// note: IDK why it was originally here. Is it desirable? I maybe PerCell better?
|
||||
fn align_table(
|
||||
table: &mut Table,
|
||||
alignments: CellConfiguration<AlignmentHorizontal>,
|
||||
structure: &TableStructure,
|
||||
) {
|
||||
table.with(AlignmentStrategy::PerLine);
|
||||
table.with(Alignment::from(alignments.data));
|
||||
|
||||
for (column, alignment) in alignments.columns {
|
||||
table.modify(Columns::single(column), Alignment::from(alignment));
|
||||
}
|
||||
|
||||
for (pos, alignment) in alignments.cells {
|
||||
table.modify(pos, Alignment::from(alignment));
|
||||
}
|
||||
|
||||
if structure.with_header {
|
||||
table.modify(Rows::first(), AlignmentStrategy::PerCell);
|
||||
@ -574,23 +663,7 @@ fn align_table(table: &mut Table, alignments: Alignments, structure: &TableStruc
|
||||
}
|
||||
}
|
||||
|
||||
fn colorize_table(table: &mut Table, styles: Styles, structure: &TableStructure) {
|
||||
if !is_color_empty(&styles.data) {
|
||||
table.with(styles.data);
|
||||
}
|
||||
|
||||
for (column, color) in styles.columns {
|
||||
if !is_color_empty(&color) {
|
||||
table.modify(Columns::single(column), color);
|
||||
}
|
||||
}
|
||||
|
||||
for (pos, color) in styles.cells {
|
||||
if !is_color_empty(&color) {
|
||||
table.modify(pos, color);
|
||||
}
|
||||
}
|
||||
|
||||
fn colorize_table(table: &mut Table, styles: CellConfiguration<Color>, structure: &TableStructure) {
|
||||
if structure.with_index && !is_color_empty(&styles.index) {
|
||||
table.modify(Columns::first(), styles.index);
|
||||
}
|
||||
@ -632,7 +705,8 @@ fn load_theme(
|
||||
}
|
||||
|
||||
fn maybe_truncate_columns(
|
||||
data: &mut NuRecords,
|
||||
data: &mut Vec<Vec<NuRecordsValue>>,
|
||||
widths: Vec<usize>,
|
||||
cfg: &TableConfig,
|
||||
termwidth: usize,
|
||||
) -> WidthEstimation {
|
||||
@ -642,15 +716,16 @@ fn maybe_truncate_columns(
|
||||
let preserve_content = termwidth > TERMWIDTH_THRESHOLD;
|
||||
|
||||
if preserve_content {
|
||||
truncate_columns_by_columns(data, &cfg.theme, pad, termwidth)
|
||||
truncate_columns_by_columns(data, widths, &cfg.theme, pad, termwidth)
|
||||
} else {
|
||||
truncate_columns_by_content(data, &cfg.theme, pad, termwidth)
|
||||
truncate_columns_by_content(data, widths, &cfg.theme, pad, termwidth)
|
||||
}
|
||||
}
|
||||
|
||||
// VERSION where we are showing AS LITTLE COLUMNS AS POSSIBLE but WITH AS MUCH CONTENT AS POSSIBLE.
|
||||
fn truncate_columns_by_content(
|
||||
data: &mut NuRecords,
|
||||
data: &mut Vec<Vec<NuRecordsValue>>,
|
||||
widths: Vec<usize>,
|
||||
theme: &TableTheme,
|
||||
pad: usize,
|
||||
termwidth: usize,
|
||||
@ -661,13 +736,14 @@ fn truncate_columns_by_content(
|
||||
let trailing_column_width = TRAILING_COLUMN_WIDTH + pad;
|
||||
let min_column_width = MIN_ACCEPTABLE_WIDTH + pad;
|
||||
|
||||
let count_columns = data[0].len();
|
||||
|
||||
let config = create_config(theme, false, None);
|
||||
let widths_original = build_width(data, pad);
|
||||
let widths_original = widths;
|
||||
let mut widths = vec![];
|
||||
|
||||
let borders = config.get_borders();
|
||||
let vertical = borders.has_vertical() as usize;
|
||||
let count_columns = data.count_columns();
|
||||
|
||||
let mut width = borders.has_left() as usize + borders.has_right() as usize;
|
||||
let mut truncate_pos = 0;
|
||||
@ -688,7 +764,7 @@ fn truncate_columns_by_content(
|
||||
}
|
||||
|
||||
if truncate_pos == count_columns {
|
||||
return WidthEstimation::new(widths_original, widths, width, false);
|
||||
return WidthEstimation::new(widths_original, widths, width, false, false);
|
||||
}
|
||||
|
||||
if truncate_pos == 0 {
|
||||
@ -705,11 +781,11 @@ fn truncate_columns_by_content(
|
||||
widths.push(trailing_column_width);
|
||||
width += trailing_column_width + vertical;
|
||||
|
||||
return WidthEstimation::new(widths_original, widths, width, true);
|
||||
return WidthEstimation::new(widths_original, widths, width, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
return WidthEstimation::new(widths_original, widths, width, false);
|
||||
return WidthEstimation::new(widths_original, widths, width, false, false);
|
||||
}
|
||||
|
||||
let available = termwidth - width;
|
||||
@ -721,7 +797,7 @@ fn truncate_columns_by_content(
|
||||
widths.push(w);
|
||||
width += w + vertical;
|
||||
|
||||
return WidthEstimation::new(widths_original, widths, width, true);
|
||||
return WidthEstimation::new(widths_original, widths, width, true, false);
|
||||
}
|
||||
|
||||
// special case where the last column is smaller then a trailing column
|
||||
@ -739,7 +815,7 @@ fn truncate_columns_by_content(
|
||||
widths.push(next_column_width);
|
||||
width += next_column_width + vertical;
|
||||
|
||||
return WidthEstimation::new(widths_original, widths, width, true);
|
||||
return WidthEstimation::new(widths_original, widths, width, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -756,7 +832,7 @@ fn truncate_columns_by_content(
|
||||
widths.push(trailing_column_width);
|
||||
width += trailing_column_width + vertical;
|
||||
|
||||
return WidthEstimation::new(widths_original, widths, width, true);
|
||||
return WidthEstimation::new(widths_original, widths, width, true, true);
|
||||
}
|
||||
|
||||
if available >= trailing_column_width + vertical {
|
||||
@ -766,7 +842,7 @@ fn truncate_columns_by_content(
|
||||
widths.push(trailing_column_width);
|
||||
width += trailing_column_width + vertical;
|
||||
|
||||
return WidthEstimation::new(widths_original, widths, width, false);
|
||||
return WidthEstimation::new(widths_original, widths, width, false, true);
|
||||
}
|
||||
|
||||
let last_width = widths.last().cloned().expect("ok");
|
||||
@ -790,7 +866,7 @@ fn truncate_columns_by_content(
|
||||
widths.push(trailing_column_width);
|
||||
width += trailing_column_width + vertical;
|
||||
|
||||
return WidthEstimation::new(widths_original, widths, width, true);
|
||||
return WidthEstimation::new(widths_original, widths, width, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -804,10 +880,10 @@ fn truncate_columns_by_content(
|
||||
|
||||
if widths.len() == 1 {
|
||||
// nothing to show anyhow
|
||||
return WidthEstimation::new(widths_original, vec![], width, false);
|
||||
return WidthEstimation::new(widths_original, vec![], width, false, true);
|
||||
}
|
||||
|
||||
WidthEstimation::new(widths_original, widths, width, false)
|
||||
WidthEstimation::new(widths_original, widths, width, false, true)
|
||||
}
|
||||
|
||||
// VERSION where we are showing AS MANY COLUMNS AS POSSIBLE but as a side affect they MIGHT CONTAIN AS LITTLE CONTENT AS POSSIBLE
|
||||
@ -821,7 +897,8 @@ fn truncate_columns_by_content(
|
||||
// Point being of the column needs more space we do can give it a little more based on it's distance from the start.
|
||||
// Percentage wise.
|
||||
fn truncate_columns_by_columns(
|
||||
data: &mut NuRecords,
|
||||
data: &mut Vec<Vec<NuRecordsValue>>,
|
||||
widths: Vec<usize>,
|
||||
theme: &TableTheme,
|
||||
pad: usize,
|
||||
termwidth: usize,
|
||||
@ -832,13 +909,14 @@ fn truncate_columns_by_columns(
|
||||
let trailing_column_width = TRAILING_COLUMN_WIDTH + pad;
|
||||
let min_column_width = MIN_ACCEPTABLE_WIDTH + pad;
|
||||
|
||||
let count_columns = data[0].len();
|
||||
|
||||
let config = create_config(theme, false, None);
|
||||
let widths_original = build_width(data, pad);
|
||||
let widths_original = widths;
|
||||
let mut widths = vec![];
|
||||
|
||||
let borders = config.get_borders();
|
||||
let vertical = borders.has_vertical() as usize;
|
||||
let count_columns = data.count_columns();
|
||||
|
||||
let mut width = borders.has_left() as usize + borders.has_right() as usize;
|
||||
let mut truncate_pos = 0;
|
||||
@ -860,7 +938,7 @@ fn truncate_columns_by_columns(
|
||||
}
|
||||
|
||||
if truncate_pos == 0 {
|
||||
return WidthEstimation::new(widths_original, widths, width, false);
|
||||
return WidthEstimation::new(widths_original, widths, width, false, false);
|
||||
}
|
||||
|
||||
let mut available = termwidth - width;
|
||||
@ -885,7 +963,7 @@ fn truncate_columns_by_columns(
|
||||
}
|
||||
|
||||
if truncate_pos == count_columns {
|
||||
return WidthEstimation::new(widths_original, widths, width, true);
|
||||
return WidthEstimation::new(widths_original, widths, width, true, false);
|
||||
}
|
||||
|
||||
if available >= trailing_column_width + vertical {
|
||||
@ -895,7 +973,7 @@ fn truncate_columns_by_columns(
|
||||
widths.push(trailing_column_width);
|
||||
width += trailing_column_width + vertical;
|
||||
|
||||
return WidthEstimation::new(widths_original, widths, width, true);
|
||||
return WidthEstimation::new(widths_original, widths, width, true, true);
|
||||
}
|
||||
|
||||
truncate_rows(data, truncate_pos - 1);
|
||||
@ -906,7 +984,7 @@ fn truncate_columns_by_columns(
|
||||
widths.push(trailing_column_width);
|
||||
width += trailing_column_width;
|
||||
|
||||
WidthEstimation::new(widths_original, widths, width, true)
|
||||
WidthEstimation::new(widths_original, widths, width, true, true)
|
||||
}
|
||||
|
||||
fn get_total_width2(widths: &[usize], cfg: &ColoredConfig) -> usize {
|
||||
@ -924,37 +1002,22 @@ fn create_config(theme: &TableTheme, with_header: bool, color: Option<Style>) ->
|
||||
table.get_config().clone()
|
||||
}
|
||||
|
||||
fn push_empty_column(data: &mut NuRecords) {
|
||||
let records = std::mem::take(data);
|
||||
let mut inner: Vec<Vec<_>> = records.into();
|
||||
|
||||
fn push_empty_column(data: &mut Vec<Vec<NuRecordsValue>>) {
|
||||
let empty_cell = Text::new(String::from(EMPTY_COLUMN_TEXT));
|
||||
for row in &mut inner {
|
||||
for row in data {
|
||||
row.push(empty_cell.clone());
|
||||
}
|
||||
|
||||
*data = VecRecords::new(inner);
|
||||
}
|
||||
|
||||
fn duplicate_row(data: &mut NuRecords, row: usize) {
|
||||
let records = std::mem::take(data);
|
||||
let mut inner: Vec<Vec<_>> = records.into();
|
||||
|
||||
let duplicate = inner[row].clone();
|
||||
inner.push(duplicate);
|
||||
|
||||
*data = VecRecords::new(inner);
|
||||
fn duplicate_row(data: &mut Vec<Vec<NuRecordsValue>>, row: usize) {
|
||||
let duplicate = data[row].clone();
|
||||
data.push(duplicate);
|
||||
}
|
||||
|
||||
fn truncate_rows(data: &mut NuRecords, count: usize) {
|
||||
let records = std::mem::take(data);
|
||||
let mut inner: Vec<Vec<_>> = records.into();
|
||||
|
||||
for row in &mut inner {
|
||||
fn truncate_rows(data: &mut Vec<Vec<NuRecordsValue>>, count: usize) {
|
||||
for row in data {
|
||||
row.truncate(count);
|
||||
}
|
||||
|
||||
*data = VecRecords::new(inner);
|
||||
}
|
||||
|
||||
fn convert_alignment(alignment: nu_color_config::Alignment) -> AlignmentHorizontal {
|
||||
@ -974,7 +1037,11 @@ impl<R> TableOption<R, ColoredConfig, CompleteDimension<'_>> for SetDimensions {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_width(records: &NuRecords, pad: usize) -> Vec<usize> {
|
||||
fn build_width<R>(records: R, pad: usize) -> Vec<usize>
|
||||
where
|
||||
R: Records,
|
||||
<R::Iter as IntoRecords>::Cell: AsRef<str>,
|
||||
{
|
||||
// TODO: Expose not spaned version (could be optimized).
|
||||
let mut cfg = SpannedConfig::default();
|
||||
let padding = Sides {
|
||||
@ -984,7 +1051,7 @@ fn build_width(records: &NuRecords, pad: usize) -> Vec<usize> {
|
||||
|
||||
cfg.set_padding(Entity::Global, padding);
|
||||
|
||||
PeekableGridDimension::width(records, &cfg)
|
||||
IterGridDimension::width(records, &cfg)
|
||||
}
|
||||
|
||||
// It's laverages a use of guuaranted cached widths before hand
|
||||
@ -1043,3 +1110,11 @@ fn theme_copy_horizontal_line(theme: &mut tabled::settings::Theme, from: usize,
|
||||
theme.insert_horizontal_line(to, *line);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_color_if_exists(c: &Color) -> Option<Color> {
|
||||
if !is_color_empty(c) {
|
||||
Some(c.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -156,6 +156,16 @@ impl TableTheme {
|
||||
Self::new(theme, full)
|
||||
}
|
||||
|
||||
pub fn single() -> TableTheme {
|
||||
let full = Style::modern()
|
||||
.corner_top_left('┌')
|
||||
.corner_top_right('┐')
|
||||
.corner_bottom_left('└')
|
||||
.corner_bottom_right('┘');
|
||||
|
||||
Self::new(Style::sharp(), full)
|
||||
}
|
||||
|
||||
pub fn none() -> TableTheme {
|
||||
Self::new(Style::blank(), Style::blank())
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
use std::{cmp::max, collections::HashMap};
|
||||
use std::cmp::max;
|
||||
|
||||
use nu_color_config::{Alignment, StyleComputer, TextStyle};
|
||||
use nu_engine::column::get_columns;
|
||||
use nu_protocol::{Config, Record, ShellError, Span, Value};
|
||||
|
||||
use tabled::grid::config::Position;
|
||||
|
||||
use crate::{
|
||||
@ -14,7 +13,7 @@ use crate::{
|
||||
},
|
||||
string_width,
|
||||
types::has_index,
|
||||
NuRecordsValue, NuTable, TableOpts, TableOutput,
|
||||
NuTable, TableOpts, TableOutput,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -106,7 +105,7 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
||||
const PADDING_SPACE: usize = 2;
|
||||
const SPLIT_LINE_SPACE: usize = 1;
|
||||
const ADDITIONAL_CELL_SPACE: usize = PADDING_SPACE + SPLIT_LINE_SPACE;
|
||||
const MIN_CELL_CONTENT_WIDTH: usize = 1;
|
||||
const MIN_CELL_CONTENT_WIDTH: usize = 3;
|
||||
const TRUNCATE_CONTENT_WIDTH: usize = 3;
|
||||
const TRUNCATE_CELL_WIDTH: usize = TRUNCATE_CONTENT_WIDTH + PADDING_SPACE;
|
||||
|
||||
@ -124,10 +123,7 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
||||
}
|
||||
|
||||
let headers = get_columns(input);
|
||||
|
||||
let with_index = has_index(&cfg.opts, &headers);
|
||||
let row_offset = cfg.opts.index_offset;
|
||||
let mut rows_count = 0usize;
|
||||
|
||||
// The header with the INDEX is removed from the table headers since
|
||||
// it is added to the natural table index
|
||||
@ -135,165 +131,171 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
||||
.into_iter()
|
||||
.filter(|header| header != INDEX_COLUMN_NAME)
|
||||
.collect();
|
||||
|
||||
let with_header = !headers.is_empty();
|
||||
let row_offset = cfg.opts.index_offset;
|
||||
|
||||
let mut data = vec![vec![]; input.len() + with_header as usize];
|
||||
let mut data_styles = HashMap::new();
|
||||
let mut total_rows = 0usize;
|
||||
|
||||
if with_index {
|
||||
if with_header {
|
||||
data[0].push(NuRecordsValue::exact(String::from("#"), 1, vec![]));
|
||||
}
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
cfg.opts.signals.check(cfg.opts.span)?;
|
||||
check_value(item)?;
|
||||
|
||||
let index = row + row_offset;
|
||||
let text = item
|
||||
.as_record()
|
||||
.ok()
|
||||
.and_then(|val| val.get(INDEX_COLUMN_NAME))
|
||||
.map(|value| value.to_expanded_string("", cfg.opts.config))
|
||||
.unwrap_or_else(|| index.to_string());
|
||||
|
||||
let row = row + with_header as usize;
|
||||
let value = NuRecordsValue::new(text);
|
||||
data[row].push(value);
|
||||
}
|
||||
|
||||
let column_width = string_width(data[data.len() - 1][0].as_ref());
|
||||
|
||||
if column_width + ADDITIONAL_CELL_SPACE > available_width {
|
||||
available_width = 0;
|
||||
} else {
|
||||
available_width -= column_width + ADDITIONAL_CELL_SPACE;
|
||||
}
|
||||
}
|
||||
|
||||
if !with_header {
|
||||
if available_width > ADDITIONAL_CELL_SPACE {
|
||||
available_width -= PADDING_SPACE;
|
||||
} else {
|
||||
if !with_index && !with_header {
|
||||
if available_width <= ADDITIONAL_CELL_SPACE {
|
||||
// it means we have no space left for actual content;
|
||||
// which means there's no point in index itself if it was even used.
|
||||
// so we do not print it.
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
available_width -= PADDING_SPACE;
|
||||
|
||||
let mut table = NuTable::new(input.len(), 1);
|
||||
table.set_index_style(get_index_style(&cfg.opts.style_computer));
|
||||
table.set_header_style(get_header_style(&cfg.opts.style_computer));
|
||||
table.set_indent(cfg.opts.config.table.padding);
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
cfg.opts.signals.check(cfg.opts.span)?;
|
||||
check_value(item)?;
|
||||
|
||||
let inner_cfg = cfg_expand_reset_table(cfg.clone(), available_width);
|
||||
let mut cell = expand_entry(item, inner_cfg);
|
||||
let cell = expand_entry(item, inner_cfg);
|
||||
|
||||
let value_width = string_width(&cell.text);
|
||||
if value_width > available_width {
|
||||
// it must only happen when a string is produced, so we can safely wrap it.
|
||||
// (it might be string table representation as well) (I guess I mean default { table ...} { list ...})
|
||||
//
|
||||
// todo: Maybe convert_to_table2_entry could do for strings to not mess caller code?
|
||||
table.insert(Position::new(row, 0), cell.text);
|
||||
table.insert_style(Position::new(row, 0), cell.style);
|
||||
|
||||
cell.text = wrap_text(&cell.text, available_width, cfg.opts.config);
|
||||
}
|
||||
|
||||
let value = NuRecordsValue::new(cell.text);
|
||||
data[row].push(value);
|
||||
data_styles.insert(Position::new(row, with_index as usize), cell.style);
|
||||
|
||||
rows_count = rows_count.saturating_add(cell.size);
|
||||
total_rows = total_rows.saturating_add(cell.size);
|
||||
}
|
||||
|
||||
let mut table = NuTable::from(data);
|
||||
table.set_indent(cfg.opts.config.table.padding);
|
||||
table.set_index_style(get_index_style(&cfg.opts.style_computer));
|
||||
set_data_styles(&mut table, data_styles);
|
||||
|
||||
return Ok(Some(TableOutput::new(table, false, with_index, rows_count)));
|
||||
return Ok(Some(TableOutput::new(table, false, false, total_rows)));
|
||||
}
|
||||
|
||||
if !headers.is_empty() {
|
||||
let mut pad_space = PADDING_SPACE;
|
||||
if headers.len() > 1 {
|
||||
pad_space += SPLIT_LINE_SPACE;
|
||||
if !with_header && with_index {
|
||||
let mut table = NuTable::new(input.len(), 2);
|
||||
table.set_index_style(get_index_style(&cfg.opts.style_computer));
|
||||
table.set_header_style(get_header_style(&cfg.opts.style_computer));
|
||||
table.set_indent(cfg.opts.config.table.padding);
|
||||
|
||||
let mut index_column_width = 0;
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
cfg.opts.signals.check(cfg.opts.span)?;
|
||||
check_value(item)?;
|
||||
|
||||
let index = row + row_offset;
|
||||
let index_value = item
|
||||
.as_record()
|
||||
.ok()
|
||||
.and_then(|val| val.get(INDEX_COLUMN_NAME))
|
||||
.map(|value| value.to_expanded_string("", cfg.opts.config))
|
||||
.unwrap_or_else(|| index.to_string());
|
||||
let index_width = string_width(&index_value);
|
||||
if available_width <= index_width + ADDITIONAL_CELL_SPACE + PADDING_SPACE {
|
||||
// NOTE: we don't wanna wrap index; so we return
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
table.insert(Position::new(row, 0), index_value);
|
||||
|
||||
index_column_width = max(index_column_width, index_width);
|
||||
}
|
||||
|
||||
if available_width < pad_space {
|
||||
// there's no space for actual data so we don't return index if it's present.
|
||||
// (also see the comment after the loop)
|
||||
available_width -= index_column_width + ADDITIONAL_CELL_SPACE + PADDING_SPACE;
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
cfg.opts.signals.check(cfg.opts.span)?;
|
||||
check_value(item)?;
|
||||
|
||||
let inner_cfg = cfg_expand_reset_table(cfg.clone(), available_width);
|
||||
let cell = expand_entry(item, inner_cfg);
|
||||
|
||||
table.insert(Position::new(row, 1), cell.text);
|
||||
table.insert_style(Position::new(row, 1), cell.style);
|
||||
|
||||
total_rows = total_rows.saturating_add(cell.size);
|
||||
}
|
||||
|
||||
return Ok(Some(TableOutput::new(table, false, true, total_rows)));
|
||||
}
|
||||
|
||||
// NOTE: redefine to not break above logic (fixme)
|
||||
let mut available_width = cfg.opts.width - SPLIT_LINE_SPACE;
|
||||
|
||||
let mut table = NuTable::new(input.len() + 1, headers.len() + with_index as usize);
|
||||
table.set_index_style(get_index_style(&cfg.opts.style_computer));
|
||||
table.set_header_style(get_header_style(&cfg.opts.style_computer));
|
||||
table.set_indent(cfg.opts.config.table.padding);
|
||||
|
||||
let mut widths = Vec::new();
|
||||
|
||||
if with_index {
|
||||
table.insert(Position::new(0, 0), String::from("#"));
|
||||
|
||||
let mut index_column_width = 1;
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
cfg.opts.signals.check(cfg.opts.span)?;
|
||||
check_value(item)?;
|
||||
|
||||
let index = row + row_offset;
|
||||
let index_value = item
|
||||
.as_record()
|
||||
.ok()
|
||||
.and_then(|val| val.get(INDEX_COLUMN_NAME))
|
||||
.map(|value| value.to_expanded_string("", cfg.opts.config))
|
||||
.unwrap_or_else(|| index.to_string());
|
||||
let index_width = string_width(&index_value);
|
||||
|
||||
table.insert(Position::new(row + 1, 0), index_value);
|
||||
index_column_width = max(index_column_width, index_width);
|
||||
}
|
||||
|
||||
if available_width <= index_column_width + ADDITIONAL_CELL_SPACE {
|
||||
// NOTE: we don't wanna wrap index; so we return
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
available_width -= index_column_width + ADDITIONAL_CELL_SPACE;
|
||||
widths.push(index_column_width);
|
||||
}
|
||||
|
||||
let count_columns = headers.len();
|
||||
let mut widths = Vec::new();
|
||||
let mut truncate = false;
|
||||
let mut rendered_column = 0;
|
||||
for (col, header) in headers.into_iter().enumerate() {
|
||||
let column = col + with_index as usize;
|
||||
let extra_space = PADDING_SPACE + SPLIT_LINE_SPACE;
|
||||
|
||||
if available_width <= extra_space {
|
||||
table.pop_column(table.count_columns() - column);
|
||||
widths.pop();
|
||||
truncate = true;
|
||||
break;
|
||||
}
|
||||
|
||||
let mut available = available_width - extra_space;
|
||||
|
||||
// We want to reserver some space for next column
|
||||
// If we can't fit it in it will be popped anyhow.
|
||||
let is_last_column = col + 1 == count_columns;
|
||||
let mut pad_space = PADDING_SPACE;
|
||||
if !is_last_column {
|
||||
pad_space += SPLIT_LINE_SPACE;
|
||||
if !is_last_column && available > TRUNCATE_CELL_WIDTH {
|
||||
available -= TRUNCATE_CELL_WIDTH;
|
||||
}
|
||||
|
||||
let mut available = available_width - pad_space;
|
||||
let mut total_column_rows = 0usize;
|
||||
let mut column_width = 0;
|
||||
|
||||
if !is_last_column {
|
||||
// we need to make sure that we have a space for a next column if we use available width
|
||||
// so we might need to decrease a bit it.
|
||||
|
||||
// we consider a header width be a minimum width
|
||||
let pad_space = PADDING_SPACE + TRUNCATE_CONTENT_WIDTH;
|
||||
|
||||
if available > pad_space {
|
||||
// In we have no space for a next column,
|
||||
// We consider showing something better then nothing,
|
||||
// So we try to decrease the width to show at least a truncution column
|
||||
|
||||
available -= pad_space;
|
||||
} else {
|
||||
truncate = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if available < column_width {
|
||||
truncate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut column_rows = 0usize;
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
cfg.opts.signals.check(cfg.opts.span)?;
|
||||
check_value(item)?;
|
||||
|
||||
let inner_cfg = cfg_expand_reset_table(cfg.clone(), available);
|
||||
let mut cell = expand_entry_with_header(item, &header, inner_cfg);
|
||||
|
||||
let mut value_width = string_width(&cell.text);
|
||||
if value_width > available {
|
||||
// it must only happen when a string is produced, so we can safely wrap it.
|
||||
// (it might be string table representation as well)
|
||||
|
||||
cell.text = wrap_text(&cell.text, available, cfg.opts.config);
|
||||
value_width = available;
|
||||
}
|
||||
let cell = expand_entry_with_header(item, &header, inner_cfg);
|
||||
let value_width = string_width(&cell.text); // TODO: optimize cause when we expand we alrready know the width (most of the time or all)
|
||||
|
||||
column_width = max(column_width, value_width);
|
||||
|
||||
let value = NuRecordsValue::new(cell.text);
|
||||
data[row + 1].push(value);
|
||||
data_styles.insert(
|
||||
Position::new(row + 1, col + with_index as usize),
|
||||
cell.style,
|
||||
);
|
||||
table.insert(Position::new(row + 1, column), cell.text);
|
||||
table.insert_style(Position::new(row + 1, column), cell.style);
|
||||
|
||||
column_rows = column_rows.saturating_add(cell.size);
|
||||
total_column_rows = total_column_rows.saturating_add(cell.size);
|
||||
}
|
||||
|
||||
let mut head_width = string_width(&header);
|
||||
@ -303,50 +305,37 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
||||
head_width = available;
|
||||
}
|
||||
|
||||
let head_cell = NuRecordsValue::new(header);
|
||||
data[0].push(head_cell);
|
||||
table.insert(Position::new(0, column), header);
|
||||
|
||||
column_width = max(column_width, head_width);
|
||||
|
||||
if column_width > available {
|
||||
// remove the column we just inserted
|
||||
for row in &mut data {
|
||||
row.pop();
|
||||
}
|
||||
|
||||
truncate = true;
|
||||
break;
|
||||
}
|
||||
assert!(column_width <= available);
|
||||
|
||||
widths.push(column_width);
|
||||
|
||||
available_width -= pad_space + column_width;
|
||||
available_width -= column_width + extra_space;
|
||||
rendered_column += 1;
|
||||
|
||||
rows_count = std::cmp::max(rows_count, column_rows);
|
||||
}
|
||||
|
||||
if truncate && rendered_column == 0 {
|
||||
// it means that no actual data was rendered, there might be only index present,
|
||||
// so there's no point in rendering the table.
|
||||
//
|
||||
// It's actually quite important in case it's called recursively,
|
||||
// cause we will back up to the basic table view as a string e.g. '[table 123 columns]'.
|
||||
//
|
||||
// But potentially if its reached as a 1st called function we might would love to see the index.
|
||||
|
||||
return Ok(None);
|
||||
total_rows = std::cmp::max(total_rows, total_column_rows);
|
||||
}
|
||||
|
||||
if truncate {
|
||||
if rendered_column == 0 {
|
||||
// it means that no actual data was rendered, there might be only index present,
|
||||
// so there's no point in rendering the table.
|
||||
//
|
||||
// It's actually quite important in case it's called recursively,
|
||||
// cause we will back up to the basic table view as a string e.g. '[table 123 columns]'.
|
||||
//
|
||||
// But potentially if its reached as a 1st called function we might would love to see the index.
|
||||
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if available_width < TRUNCATE_CELL_WIDTH {
|
||||
// back up by removing last column.
|
||||
// it's LIKELY that removing only 1 column will leave us enough space for a shift column.
|
||||
|
||||
while let Some(width) = widths.pop() {
|
||||
for row in &mut data {
|
||||
row.pop();
|
||||
}
|
||||
table.pop_column(1);
|
||||
|
||||
available_width += width + PADDING_SPACE;
|
||||
if !widths.is_empty() {
|
||||
@ -367,22 +356,12 @@ fn expand_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
|
||||
|
||||
let is_last_column = widths.len() == count_columns;
|
||||
if !is_last_column {
|
||||
let shift = NuRecordsValue::exact(String::from("..."), 3, vec![]);
|
||||
for row in &mut data {
|
||||
row.push(shift.clone());
|
||||
}
|
||||
|
||||
table.push_column(String::from("..."));
|
||||
widths.push(3);
|
||||
}
|
||||
}
|
||||
|
||||
let mut table = NuTable::from(data);
|
||||
table.set_index_style(get_index_style(&cfg.opts.style_computer));
|
||||
table.set_header_style(get_header_style(&cfg.opts.style_computer));
|
||||
table.set_indent(cfg.opts.config.table.padding);
|
||||
set_data_styles(&mut table, data_styles);
|
||||
|
||||
Ok(Some(TableOutput::new(table, true, with_index, rows_count)))
|
||||
Ok(Some(TableOutput::new(table, true, with_index, total_rows)))
|
||||
}
|
||||
|
||||
fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
|
||||
@ -403,10 +382,13 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
|
||||
|
||||
let value_width = cfg.opts.width - key_width - count_borders - padding - padding;
|
||||
|
||||
let mut count_rows = 0usize;
|
||||
let mut total_rows = 0usize;
|
||||
|
||||
let mut data = Vec::with_capacity(record.len());
|
||||
for (key, value) in record {
|
||||
let mut table = NuTable::new(record.len(), 2);
|
||||
table.set_index_style(get_key_style(&cfg));
|
||||
table.set_indent(cfg.opts.config.table.padding);
|
||||
|
||||
for (i, (key, value)) in record.iter().enumerate() {
|
||||
cfg.opts.signals.check(cfg.opts.span)?;
|
||||
|
||||
let cell = match expand_value(value, value_width, &cfg)? {
|
||||
@ -414,29 +396,24 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let value = cell.text;
|
||||
let mut key = key.to_owned();
|
||||
|
||||
// we want to have a key being aligned to 2nd line,
|
||||
// we could use Padding for it but,
|
||||
// the easiest way to do so is just push a new_line char before
|
||||
let mut key = key.to_owned();
|
||||
let is_key_on_next_line = !key.is_empty() && cell.is_expanded && theme.borders_has_top();
|
||||
if is_key_on_next_line {
|
||||
key.insert(0, '\n');
|
||||
}
|
||||
|
||||
let key = NuRecordsValue::new(key);
|
||||
let val = NuRecordsValue::new(cell.text);
|
||||
let row = vec![key, val];
|
||||
table.insert(Position::new(i, 0), key);
|
||||
table.insert(Position::new(i, 1), value);
|
||||
|
||||
data.push(row);
|
||||
|
||||
count_rows = count_rows.saturating_add(cell.size);
|
||||
total_rows = total_rows.saturating_add(cell.size);
|
||||
}
|
||||
|
||||
let mut table = NuTable::from(data);
|
||||
table.set_index_style(get_key_style(&cfg));
|
||||
table.set_indent(cfg.opts.config.table.padding);
|
||||
|
||||
let mut out = TableOutput::new(table, false, true, count_rows);
|
||||
let mut out = TableOutput::new(table, false, true, total_rows);
|
||||
|
||||
configure_table(
|
||||
&mut out,
|
||||
@ -446,7 +423,7 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult {
|
||||
);
|
||||
|
||||
maybe_expand_table(out, cfg.opts.width)
|
||||
.map(|value| value.map(|value| CellOutput::clean(value, count_rows, false)))
|
||||
.map(|value| value.map(|value| CellOutput::clean(value, total_rows, false)))
|
||||
}
|
||||
|
||||
// the flag is used as an optimization to not do `value.lines().count()` search.
|
||||
@ -510,7 +487,10 @@ fn expand_entry_with_header(item: &Value, header: &str, cfg: Cfg<'_>) -> CellOut
|
||||
match item {
|
||||
Value::Record { val, .. } => match val.get(header) {
|
||||
Some(val) => expand_entry(val, cfg),
|
||||
None => CellOutput::styled(error_sign(&cfg.opts.style_computer)),
|
||||
None => CellOutput::styled(error_sign(
|
||||
cfg.opts.config.table.missing_value_symbol.clone(),
|
||||
&cfg.opts.style_computer,
|
||||
)),
|
||||
},
|
||||
_ => expand_entry(item, cfg),
|
||||
}
|
||||
@ -519,6 +499,7 @@ fn expand_entry_with_header(item: &Value, header: &str, cfg: Cfg<'_>) -> CellOut
|
||||
fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
|
||||
if is_limit_reached(&cfg) {
|
||||
let value = nu_value_to_string_clean(item, cfg.opts.config, &cfg.opts.style_computer);
|
||||
let value = nutext_wrap(value, &cfg);
|
||||
return CellOutput::styled(value);
|
||||
}
|
||||
|
||||
@ -527,6 +508,7 @@ fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
|
||||
Value::Record { val: record, .. } => {
|
||||
if record.is_empty() {
|
||||
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
|
||||
let value = nutext_wrap(value, &cfg);
|
||||
return CellOutput::styled(value);
|
||||
}
|
||||
|
||||
@ -538,6 +520,7 @@ fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
|
||||
Ok(Some(table)) => table,
|
||||
_ => {
|
||||
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
|
||||
let value = nutext_wrap(value, &cfg);
|
||||
CellOutput::styled(value)
|
||||
}
|
||||
}
|
||||
@ -560,6 +543,7 @@ fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
|
||||
Ok(Some(out)) => out,
|
||||
_ => {
|
||||
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
|
||||
let value = nutext_wrap(value, &cfg);
|
||||
return CellOutput::styled(value);
|
||||
}
|
||||
};
|
||||
@ -571,17 +555,28 @@ fn expand_entry(item: &Value, cfg: Cfg<'_>) -> CellOutput {
|
||||
Some(table) => CellOutput::clean(table, out.count_rows, false),
|
||||
None => {
|
||||
let value = nu_value_to_string(item, cfg.opts.config, &cfg.opts.style_computer);
|
||||
let value = nutext_wrap(value, &cfg);
|
||||
CellOutput::styled(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let value = nu_value_to_string_clean(item, cfg.opts.config, &cfg.opts.style_computer);
|
||||
let value = nutext_wrap(value, &cfg);
|
||||
CellOutput::styled(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn nutext_wrap(mut text: NuText, cfg: &Cfg<'_>) -> NuText {
|
||||
let width = string_width(&text.0);
|
||||
if width > cfg.opts.width {
|
||||
text.0 = wrap_text(&text.0, cfg.opts.width, cfg.opts.config);
|
||||
}
|
||||
|
||||
text
|
||||
}
|
||||
|
||||
fn is_limit_reached(cfg: &Cfg<'_>) -> bool {
|
||||
matches!(cfg.format.expand_limit, Some(0))
|
||||
}
|
||||
@ -626,12 +621,6 @@ fn maybe_expand_table(mut out: TableOutput, term_width: usize) -> StringResult {
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
fn set_data_styles(table: &mut NuTable, styles: HashMap<Position, TextStyle>) {
|
||||
for (pos, style) in styles {
|
||||
table.insert_style(pos, style);
|
||||
}
|
||||
}
|
||||
|
||||
fn table_apply_config(out: &mut TableOutput, cfg: &Cfg<'_>) {
|
||||
configure_table(
|
||||
out,
|
||||
|
@ -16,23 +16,23 @@ use crate::{
|
||||
pub struct JustTable;
|
||||
|
||||
impl JustTable {
|
||||
pub fn table(input: &[Value], opts: TableOpts<'_>) -> StringResult {
|
||||
pub fn table(input: Vec<Value>, opts: TableOpts<'_>) -> StringResult {
|
||||
list_table(input, opts)
|
||||
}
|
||||
|
||||
pub fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
|
||||
pub fn kv_table(record: Record, opts: TableOpts<'_>) -> StringResult {
|
||||
kv_table(record, opts)
|
||||
}
|
||||
}
|
||||
|
||||
fn list_table(input: &[Value], opts: TableOpts<'_>) -> Result<Option<String>, ShellError> {
|
||||
let mut out = match create_table(input, &opts)? {
|
||||
fn list_table(input: Vec<Value>, opts: TableOpts<'_>) -> Result<Option<String>, ShellError> {
|
||||
let output = create_table(input, &opts)?;
|
||||
let mut out = match output {
|
||||
Some(out) => out,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
out.table.set_indent(opts.config.table.padding);
|
||||
|
||||
// TODO: It would be WAY more effitient to do right away instead of second pass over the data.
|
||||
colorize_space(out.table.get_records_mut(), &opts.style_computer);
|
||||
|
||||
configure_table(&mut out, opts.config, &opts.style_computer, opts.mode);
|
||||
@ -41,25 +41,20 @@ fn list_table(input: &[Value], opts: TableOpts<'_>) -> Result<Option<String>, Sh
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
|
||||
let mut data = vec![Vec::with_capacity(2); record.len()];
|
||||
|
||||
for ((column, value), row) in record.iter().zip(data.iter_mut()) {
|
||||
opts.signals.check(opts.span)?;
|
||||
|
||||
let key = NuRecordsValue::new(column.to_string());
|
||||
|
||||
let value = nu_value_to_string_colored(value, opts.config, &opts.style_computer);
|
||||
let value = NuRecordsValue::new(value);
|
||||
|
||||
row.push(key);
|
||||
row.push(value);
|
||||
}
|
||||
|
||||
let mut table = NuTable::from(data);
|
||||
fn kv_table(record: Record, opts: TableOpts<'_>) -> StringResult {
|
||||
let mut table = NuTable::new(record.len(), 2);
|
||||
table.set_index_style(TextStyle::default_field());
|
||||
table.set_indent(opts.config.table.padding);
|
||||
|
||||
for (i, (key, value)) in record.into_iter().enumerate() {
|
||||
opts.signals.check(opts.span)?;
|
||||
|
||||
let value = nu_value_to_string_colored(&value, opts.config, &opts.style_computer);
|
||||
|
||||
table.insert(Position::new(i, 0), key);
|
||||
table.insert(Position::new(i, 1), value);
|
||||
}
|
||||
|
||||
let mut out = TableOutput::from_table(table, false, true);
|
||||
configure_table(&mut out, opts.config, &opts.style_computer, opts.mode);
|
||||
let table = out.table.draw(opts.width);
|
||||
@ -67,12 +62,12 @@ fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult {
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
fn create_table(input: &[Value], opts: &TableOpts<'_>) -> TableResult {
|
||||
fn create_table(input: Vec<Value>, opts: &TableOpts<'_>) -> TableResult {
|
||||
if input.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let headers = get_columns(input);
|
||||
let headers = get_columns(&input);
|
||||
let with_index = has_index(opts, &headers);
|
||||
let with_header = !headers.is_empty();
|
||||
let row_offset = opts.index_offset;
|
||||
@ -90,27 +85,23 @@ fn create_table(input: &[Value], opts: &TableOpts<'_>) -> TableResult {
|
||||
}
|
||||
|
||||
fn create_table_with_header(
|
||||
input: &[Value],
|
||||
input: Vec<Value>,
|
||||
headers: Vec<String>,
|
||||
opts: &TableOpts<'_>,
|
||||
) -> Result<Option<NuTable>, ShellError> {
|
||||
let headers = collect_headers(headers, false);
|
||||
|
||||
let count_rows = input.len() + 1;
|
||||
let count_columns = headers.len();
|
||||
let mut table = NuTable::new(count_rows, count_columns);
|
||||
|
||||
table.set_header_style(get_header_style(&opts.style_computer));
|
||||
table.set_index_style(get_index_style(&opts.style_computer));
|
||||
table.set_indent(opts.config.table.padding);
|
||||
|
||||
table.set_row(0, headers.clone());
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
for (row, item) in input.into_iter().enumerate() {
|
||||
opts.signals.check(opts.span)?;
|
||||
check_value(item)?;
|
||||
check_value(&item)?;
|
||||
|
||||
for (col, header) in headers.iter().enumerate() {
|
||||
let (text, style) = get_string_value_with_header(item, header.as_ref(), opts);
|
||||
let (text, style) = get_string_value_with_header(&item, header, opts);
|
||||
|
||||
let pos = Position::new(row + 1, col);
|
||||
table.insert(pos, text);
|
||||
@ -118,35 +109,39 @@ fn create_table_with_header(
|
||||
}
|
||||
}
|
||||
|
||||
let headers = collect_headers(headers, false);
|
||||
table.set_row(0, headers);
|
||||
|
||||
Ok(Some(table))
|
||||
}
|
||||
|
||||
fn create_table_with_header_and_index(
|
||||
input: &[Value],
|
||||
input: Vec<Value>,
|
||||
headers: Vec<String>,
|
||||
row_offset: usize,
|
||||
opts: &TableOpts<'_>,
|
||||
) -> Result<Option<NuTable>, ShellError> {
|
||||
let headers = collect_headers(headers, true);
|
||||
let head = collect_headers(headers, true);
|
||||
|
||||
let count_rows = input.len() + 1;
|
||||
let count_columns = headers.len();
|
||||
let mut table = NuTable::new(count_rows, count_columns);
|
||||
let count_columns = head.len();
|
||||
|
||||
let mut table = NuTable::new(count_rows, count_columns);
|
||||
table.set_header_style(get_header_style(&opts.style_computer));
|
||||
table.set_index_style(get_index_style(&opts.style_computer));
|
||||
table.set_indent(opts.config.table.padding);
|
||||
|
||||
table.set_row(0, headers.clone());
|
||||
table.set_row(0, head.clone());
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
for (row, item) in input.into_iter().enumerate() {
|
||||
opts.signals.check(opts.span)?;
|
||||
check_value(item)?;
|
||||
check_value(&item)?;
|
||||
|
||||
let text = get_table_row_index(item, opts.config, row, row_offset);
|
||||
let text = get_table_row_index(&item, opts.config, row, row_offset);
|
||||
table.insert(Position::new(row + 1, 0), text);
|
||||
|
||||
for (col, header) in headers.iter().enumerate().skip(1) {
|
||||
let (text, style) = get_string_value_with_header(item, header.as_ref(), opts);
|
||||
for (col, head) in head.iter().enumerate().skip(1) {
|
||||
let (text, style) = get_string_value_with_header(&item, head.as_ref(), opts);
|
||||
|
||||
let pos = Position::new(row + 1, col);
|
||||
table.insert(pos, text);
|
||||
@ -158,46 +153,45 @@ fn create_table_with_header_and_index(
|
||||
}
|
||||
|
||||
fn create_table_with_no_header(
|
||||
input: &[Value],
|
||||
input: Vec<Value>,
|
||||
opts: &TableOpts<'_>,
|
||||
) -> Result<Option<NuTable>, ShellError> {
|
||||
let mut table = NuTable::new(input.len(), 1);
|
||||
table.set_index_style(get_index_style(&opts.style_computer));
|
||||
table.set_indent(opts.config.table.padding);
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
for (row, item) in input.into_iter().enumerate() {
|
||||
opts.signals.check(opts.span)?;
|
||||
check_value(item)?;
|
||||
check_value(&item)?;
|
||||
|
||||
let (text, style) = get_string_value(item, opts);
|
||||
let (text, style) = get_string_value(&item, opts);
|
||||
|
||||
let pos = Position::new(row, 0);
|
||||
table.insert(pos, text);
|
||||
table.insert_style(pos, style);
|
||||
table.insert(Position::new(row, 0), text);
|
||||
table.insert_style(Position::new(row, 0), style);
|
||||
}
|
||||
|
||||
Ok(Some(table))
|
||||
}
|
||||
|
||||
fn create_table_with_no_header_and_index(
|
||||
input: &[Value],
|
||||
input: Vec<Value>,
|
||||
row_offset: usize,
|
||||
opts: &TableOpts<'_>,
|
||||
) -> Result<Option<NuTable>, ShellError> {
|
||||
let mut table = NuTable::new(input.len(), 1 + 1);
|
||||
table.set_index_style(get_index_style(&opts.style_computer));
|
||||
table.set_indent(opts.config.table.padding);
|
||||
|
||||
for (row, item) in input.iter().enumerate() {
|
||||
for (row, item) in input.into_iter().enumerate() {
|
||||
opts.signals.check(opts.span)?;
|
||||
check_value(item)?;
|
||||
check_value(&item)?;
|
||||
|
||||
let text = get_table_row_index(item, opts.config, row, row_offset);
|
||||
table.insert(Position::new(row, 0), text);
|
||||
let index = get_table_row_index(&item, opts.config, row, row_offset);
|
||||
let (value, style) = get_string_value(&item, opts);
|
||||
|
||||
let (text, style) = get_string_value(item, opts);
|
||||
|
||||
let pos = Position::new(row, 1);
|
||||
table.insert(pos, text);
|
||||
table.insert_style(pos, style);
|
||||
table.insert(Position::new(row, 0), index);
|
||||
table.insert(Position::new(row, 1), value);
|
||||
table.insert_style(Position::new(row, 1), style);
|
||||
}
|
||||
|
||||
Ok(Some(table))
|
||||
@ -207,7 +201,10 @@ fn get_string_value_with_header(item: &Value, header: &str, opts: &TableOpts) ->
|
||||
match item {
|
||||
Value::Record { val, .. } => match val.get(header) {
|
||||
Some(value) => get_string_value(value, opts),
|
||||
None => get_empty_style(&opts.style_computer),
|
||||
None => get_empty_style(
|
||||
opts.config.table.missing_value_symbol.clone(),
|
||||
&opts.style_computer,
|
||||
),
|
||||
},
|
||||
value => get_string_value(value, opts),
|
||||
}
|
||||
|
@ -88,7 +88,14 @@ where
|
||||
}
|
||||
|
||||
pub fn create_table(data: Data, case: TestCase) -> Option<String> {
|
||||
let mut table = NuTable::from(data);
|
||||
let count_rows = data.len();
|
||||
let count_cols = data[0].len();
|
||||
|
||||
let mut table = NuTable::new(count_rows, count_cols);
|
||||
for (i, row) in data.into_iter().enumerate() {
|
||||
table.set_row(i, row);
|
||||
}
|
||||
|
||||
table.set_theme(case.theme);
|
||||
table.set_structure(case.with_index, case.with_header, case.with_footer);
|
||||
table.set_trim(case.strategy);
|
||||
|
@ -451,6 +451,52 @@ fn test_with_love() {
|
||||
assert_eq!(create_table_with_size(vec![], true, theme::with_love()), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single() {
|
||||
assert_eq!(
|
||||
create_table(vec![row(4); 3], true, theme::single()),
|
||||
"┌───┬───┬───┬───┐\n\
|
||||
│ 0 │ 1 │ 2 │ 3 │\n\
|
||||
├───┼───┼───┼───┤\n\
|
||||
│ 0 │ 1 │ 2 │ 3 │\n\
|
||||
│ 0 │ 1 │ 2 │ 3 │\n\
|
||||
└───┴───┴───┴───┘"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
create_table(vec![row(4); 2], true, theme::single()),
|
||||
"┌───┬───┬───┬───┐\n\
|
||||
│ 0 │ 1 │ 2 │ 3 │\n\
|
||||
├───┼───┼───┼───┤\n\
|
||||
│ 0 │ 1 │ 2 │ 3 │\n\
|
||||
└───┴───┴───┴───┘"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
create_table(vec![row(4); 1], true, theme::single()),
|
||||
"┌───┬───┬───┬───┐\n\
|
||||
│ 0 │ 1 │ 2 │ 3 │\n\
|
||||
└───┴───┴───┴───┘"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
create_table(vec![row(4); 1], false, theme::single()),
|
||||
"┌───┬───┬───┬───┐\n\
|
||||
│ 0 │ 1 │ 2 │ 3 │\n\
|
||||
└───┴───┴───┴───┘"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
create_table(vec![row(4); 2], false, theme::single()),
|
||||
"┌───┬───┬───┬───┐\n\
|
||||
│ 0 │ 1 │ 2 │ 3 │\n\
|
||||
│ 0 │ 1 │ 2 │ 3 │\n\
|
||||
└───┴───┴───┴───┘"
|
||||
);
|
||||
|
||||
assert_eq!(create_table_with_size(vec![], true, theme::single()), "");
|
||||
}
|
||||
|
||||
fn create_table(data: Vec<Vec<Text<String>>>, with_header: bool, theme: theme) -> String {
|
||||
let mut case = TestCase::new(usize::MAX).theme(theme);
|
||||
if with_header {
|
||||
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-term-grid"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-term-grid"
|
||||
version = "0.103.1"
|
||||
version = "0.104.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
@ -14,6 +14,6 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-utils = { path = "../nu-utils", version = "0.103.1", default-features = false }
|
||||
nu-utils = { path = "../nu-utils", version = "0.104.1", default-features = false }
|
||||
|
||||
unicode-width = { workspace = true }
|
@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-test-suppor
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-test-support"
|
||||
version = "0.103.1"
|
||||
version = "0.104.1"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
@ -15,9 +15,9 @@ bench = false
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-path = { path = "../nu-path", version = "0.103.1" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.103.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.103.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.104.1" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.104.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.104.1" }
|
||||
|
||||
num-format = { workspace = true }
|
||||
which = { workspace = true }
|
||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-utils"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-utils"
|
||||
version = "0.103.1"
|
||||
version = "0.104.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[[bin]]
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Nushell Config File
|
||||
#
|
||||
# version = "0.103.1"
|
||||
# version = "0.104.1"
|
||||
$env.config.color_config = {
|
||||
separator: white
|
||||
leading_trailing_space_bg: { attr: n }
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Default Nushell Environment Config File
|
||||
# These "sensible defaults" are set before the user's `env.nu` is loaded
|
||||
#
|
||||
# version = "0.103.1"
|
||||
# version = "0.104.1"
|
||||
|
||||
$env.PROMPT_COMMAND = {||
|
||||
let dir = match (do -i { $env.PWD | path relative-to $nu.home-path }) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user