Compare commits

..

No commits in common. "main" and "0.102.0" have entirely different histories.

700 changed files with 12088 additions and 29836 deletions

40
.github/labeler.yml vendored
View File

@ -1,40 +0,0 @@
# A bot for automatically labelling pull requests
# See https://github.com/actions/labeler
dataframe:
- changed-files:
- any-glob-to-any-file:
- crates/nu_plugin_polars/**
std-library:
- changed-files:
- any-glob-to-any-file:
- crates/nu-std/**
ci:
- changed-files:
- any-glob-to-any-file:
- .github/workflows/**
LSP:
- changed-files:
- any-glob-to-any-file:
- crates/nu-lsp/**
parser:
- changed-files:
- any-glob-to-any-file:
- crates/nu-parser/**
pr:plugins:
- changed-files:
- any-glob-to-any-file:
# plugins API
- crates/nu-plugin/**
- crates/nu-plugin-core/**
- crates/nu-plugin-engine/**
- crates/nu-plugin-protocol/**
- crates/nu-plugin-test-support/**
# specific plugins (like polars)
- crates/nu_plugin_*/**

View File

@ -1,52 +0,0 @@
name: Test on Beta Toolchain
# This workflow is made to run our tests on the beta toolchain to validate that
# the beta toolchain works.
# We do not intend to test here that we are working correctly but rather that
# the beta toolchain works correctly.
# The ci.yml handles our actual testing with our guarantees.
on:
schedule:
# If this workflow fails, GitHub notifications will go to the last person
# who edited this line.
# See: https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/monitoring-workflows/notifications-for-workflow-runs
- cron: '0 0 * * *' # Runs daily at midnight UTC
env:
NUSHELL_CARGO_PROFILE: ci
NU_LOG_LEVEL: DEBUG
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
cancel-in-progress: true
jobs:
build-and-test:
# this job is more for testing the beta toolchain and not our tests, so if
# this fails but the tests of the regular ci pass, then this is fine
continue-on-error: true
strategy:
fail-fast: true
matrix:
platform: [windows-latest, macos-latest, ubuntu-22.04]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- run: rustup update beta
- name: Tests
run: cargo +beta test --workspace --profile ci --exclude nu_plugin_*
- name: Check for clean repo
shell: bash
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "there are changes";
git status --porcelain
exit 1
else
echo "no changes in working directory";
fi

View File

@ -3,7 +3,6 @@ on:
push: push:
branches: branches:
- main - main
- 'patch-release-*'
name: continuous-integration name: continuous-integration
@ -22,14 +21,14 @@ jobs:
strategy: strategy:
fail-fast: true fail-fast: true
matrix: matrix:
# Pinning to Ubuntu 22.04 because building on newer Ubuntu versions causes linux-gnu # Pinning to Ubuntu 20.04 because building on newer Ubuntu versions causes linux-gnu
# builds to link against a too-new-for-many-Linux-installs glibc version. Consider # builds to link against a too-new-for-many-Linux-installs glibc version. Consider
# revisiting this when 22.04 is closer to EOL (June 2027) # revisiting this when 20.04 is closer to EOL (April 2025)
# #
# Using macOS 13 runner because 14 is based on the M1 and has half as much RAM (7 GB, # Using macOS 13 runner because 14 is based on the M1 and has half as much RAM (7 GB,
# instead of 14 GB) which is too little for us right now. Revisit when `dfr` commands are # instead of 14 GB) which is too little for us right now. Revisit when `dfr` commands are
# removed and we're only building the `polars` plugin instead # removed and we're only building the `polars` plugin instead
platform: [windows-latest, macos-13, ubuntu-22.04] platform: [windows-latest, macos-13, ubuntu-20.04]
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
@ -37,7 +36,7 @@ jobs:
- uses: actions/checkout@v4.1.7 - uses: actions/checkout@v4.1.7
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0 uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
- name: cargo fmt - name: cargo fmt
run: cargo fmt --all -- --check run: cargo fmt --all -- --check
@ -57,7 +56,7 @@ jobs:
strategy: strategy:
fail-fast: true fail-fast: true
matrix: matrix:
platform: [windows-latest, macos-latest, ubuntu-22.04] platform: [windows-latest, macos-latest, ubuntu-20.04]
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
@ -65,7 +64,7 @@ jobs:
- uses: actions/checkout@v4.1.7 - uses: actions/checkout@v4.1.7
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0 uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
- name: Tests - name: Tests
run: cargo test --workspace --profile ci --exclude nu_plugin_* run: cargo test --workspace --profile ci --exclude nu_plugin_*
@ -84,7 +83,7 @@ jobs:
strategy: strategy:
fail-fast: true fail-fast: true
matrix: matrix:
platform: [ubuntu-22.04, macos-latest, windows-latest] platform: [ubuntu-20.04, macos-latest, windows-latest]
py: py:
- py - py
@ -94,10 +93,10 @@ jobs:
- uses: actions/checkout@v4.1.7 - uses: actions/checkout@v4.1.7
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0 uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
- name: Install Nushell - name: Install Nushell
run: cargo install --path . --locked --force run: cargo install --path . --locked --no-default-features --force
- name: Standard library tests - name: Standard library tests
run: nu -c 'use crates/nu-std/testing.nu; testing run-tests --path crates/nu-std' run: nu -c 'use crates/nu-std/testing.nu; testing run-tests --path crates/nu-std'
@ -137,7 +136,7 @@ jobs:
# instead of 14 GB) which is too little for us right now. # instead of 14 GB) which is too little for us right now.
# #
# Failure occurring with clippy for rust 1.77.2 # Failure occurring with clippy for rust 1.77.2
platform: [windows-latest, macos-13, ubuntu-22.04] platform: [windows-latest, macos-13, ubuntu-20.04]
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
@ -145,7 +144,7 @@ jobs:
- uses: actions/checkout@v4.1.7 - uses: actions/checkout@v4.1.7
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0 uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
- name: Clippy - name: Clippy
run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS
@ -186,7 +185,7 @@ jobs:
- uses: actions/checkout@v4.1.7 - uses: actions/checkout@v4.1.7
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0 uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
- name: Add wasm32-unknown-unknown target - name: Add wasm32-unknown-unknown target
run: rustup target add wasm32-unknown-unknown run: rustup target add wasm32-unknown-unknown

View File

@ -1,19 +0,0 @@
# Automatically labels PRs based on the configuration file
# you are probably looking for 👉 `.github/labeler.yml`
name: Label PRs
on:
- pull_request_target
jobs:
triage:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
if: github.repository_owner == 'nushell'
steps:
- uses: actions/labeler@v5
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
sync-labels: true

View File

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

View File

@ -117,14 +117,14 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# Build for Windows without static-link-openssl feature # Build for Windows without static-link-openssl feature
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
if $os =~ 'windows' { if $os in ['windows-latest'] {
cargo-build-nu cargo-build-nu
} }
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# Prepare for the release archive # Prepare for the release archive
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
let suffix = if $os =~ 'windows' { '.exe' } let suffix = if $os == 'windows-latest' { '.exe' }
# nu, nu_plugin_* were all included # nu, nu_plugin_* were all included
let executable = $'target/($target)/release/($bin)*($suffix)' let executable = $'target/($target)/release/($bin)*($suffix)'
print $'Current executable file: ($executable)' 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 [LICENSE ...(glob $executable)] | each {|it| cp -rv $it $dist } | flatten
print $'(char nl)Check binary release version detail:'; hr-line print $'(char nl)Check binary release version detail:'; hr-line
let ver = if $os =~ 'windows' { let ver = if $os == 'windows-latest' {
(do -i { .\output\nu.exe -c 'version' }) | default '' | str join (do -i { .\output\nu.exe -c 'version' }) | str join
} else { } else {
(do -i { ./output/nu -c 'version' }) | default '' | str join (do -i { ./output/nu -c 'version' }) | str join
} }
if ($ver | str trim | is-empty) { if ($ver | str trim | is-empty) {
print $'(ansi r)Incompatible Nu binary: The binary cross compiled is not runnable on current arch...(ansi reset)' 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/ # 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 echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT
} else if $os =~ 'windows' { } else if $os == 'windows-latest' {
let releaseStem = $'($bin)-($version)-($target)' let releaseStem = $'($bin)-($version)-($target)'
@ -221,7 +221,7 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
} }
def 'cargo-build-nu' [] { def 'cargo-build-nu' [] {
if $os =~ 'windows' { if $os == 'windows-latest' {
cargo build --release --all --target $target cargo build --release --all --target $target
} else { } else {
cargo build --release --all --target $target --features=static-link-openssl cargo build --release --all --target $target --features=static-link-openssl

View File

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

View File

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

756
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -10,8 +10,8 @@ homepage = "https://www.nushell.sh"
license = "MIT" license = "MIT"
name = "nu" name = "nu"
repository = "https://github.com/nushell/nushell" repository = "https://github.com/nushell/nushell"
rust-version = "1.84.1" rust-version = "1.82.0"
version = "0.104.1" version = "0.102.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # 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" ansi-str = "0.8"
anyhow = "1.0.82" anyhow = "1.0.82"
base64 = "0.22.1" base64 = "0.22.1"
bracoxide = "0.1.6" bracoxide = "0.1.4"
brotli = "7.0" brotli = "7.0"
byteorder = "1.5" byteorder = "1.5"
bytes = "1" bytes = "1"
bytesize = "1.3.3" bytesize = "1.3"
calamine = "0.27" calamine = "0.26.1"
chardetng = "0.1.17" chardetng = "0.1.17"
chrono = { default-features = false, version = "0.4.34" } chrono = { default-features = false, version = "0.4.34" }
chrono-humanize = "0.2.3" chrono-humanize = "0.2.3"
@ -91,8 +91,8 @@ fancy-regex = "0.14"
filesize = "0.2" filesize = "0.2"
filetime = "0.2" filetime = "0.2"
heck = "0.5.0" heck = "0.5.0"
human-date-parser = "0.3.0" human-date-parser = "0.2.0"
indexmap = "2.9" indexmap = "2.7"
indicatif = "0.17" indicatif = "0.17"
interprocess = "2.2.0" interprocess = "2.2.0"
is_executable = "1.0" is_executable = "1.0"
@ -104,13 +104,13 @@ lru = "0.12"
lscolors = { version = "0.17", default-features = false } lscolors = { version = "0.17", default-features = false }
lsp-server = "0.7.8" lsp-server = "0.7.8"
lsp-types = { version = "0.97.0", features = ["proposed"] } lsp-types = { version = "0.97.0", features = ["proposed"] }
lsp-textdocument = "0.4.2" lsp-textdocument = "0.4.1"
mach2 = "0.4" mach2 = "0.4"
md5 = { version = "0.10", package = "md-5" } md5 = { version = "0.10", package = "md-5" }
miette = "7.5" miette = "7.3"
mime = "0.3.17" mime = "0.3.17"
mime_guess = "2.0" mime_guess = "2.0"
mockito = { version = "1.7", default-features = false } mockito = { version = "1.6", default-features = false }
multipart-rs = "0.1.13" multipart-rs = "0.1.13"
native-tls = "0.2" native-tls = "0.2"
nix = { version = "0.29", default-features = false } nix = { version = "0.29", default-features = false }
@ -127,7 +127,7 @@ pathdiff = "0.2"
percent-encoding = "2" percent-encoding = "2"
pretty_assertions = "1.4" pretty_assertions = "1.4"
print-positions = "0.6" print-positions = "0.6"
proc-macro-error2 = "2.0" proc-macro-error = { version = "1.0", default-features = false }
proc-macro2 = "1.0" proc-macro2 = "1.0"
procfs = "0.17.0" procfs = "0.17.0"
pwd = "1.3" pwd = "1.3"
@ -135,50 +135,48 @@ quick-xml = "0.37.0"
quickcheck = "1.0" quickcheck = "1.0"
quickcheck_macros = "1.0" quickcheck_macros = "1.0"
quote = "1.0" quote = "1.0"
rand = "0.9" rand = "0.8"
getrandom = "0.2" # pick same version that rand requires getrandom = "0.2" # pick same version that rand requires
rand_chacha = "0.9" rand_chacha = "0.3.1"
ratatui = "0.29" ratatui = "0.26"
rayon = "1.10" rayon = "1.10"
reedline = "0.40.0" reedline = "0.38.0"
rmp = "0.8" rmp = "0.8"
rmp-serde = "1.3" rmp-serde = "1.3"
roxmltree = "0.20" roxmltree = "0.20"
rstest = { version = "0.23", default-features = false } rstest = { version = "0.23", default-features = false }
rstest_reuse = "0.7" rstest_reuse = "0.7"
rusqlite = "0.31" rusqlite = "0.31"
rust-embed = "8.7.0" rust-embed = "8.5.0"
scopeguard = { version = "1.2.0" } scopeguard = { version = "1.2.0" }
serde = { version = "1.0" } serde = { version = "1.0" }
serde_json = "1.0.97" serde_json = "1.0"
serde_urlencoded = "0.7.1" serde_urlencoded = "0.7.1"
serde_yaml = "0.9.33" serde_yaml = "0.9.33"
sha2 = "0.10" sha2 = "0.10"
strip-ansi-escapes = "0.2.0" strip-ansi-escapes = "0.2.0"
strum = "0.26"
strum_macros = "0.26"
syn = "2.0" syn = "2.0"
sysinfo = "0.33" sysinfo = "0.33"
tabled = { version = "0.17.0", default-features = false } tabled = { version = "0.17.0", default-features = false }
tempfile = "3.15" tempfile = "3.15"
titlecase = "3.5" titlecase = "3.0"
toml = "0.8" toml = "0.8"
trash = "5.2" trash = "5.2"
update-informer = { version = "1.2.0", default-features = false, features = ["github", "native-tls", "ureq"] } update-informer = { version = "1.2.0", default-features = false, features = ["github", "native-tls", "ureq"] }
umask = "2.1" umask = "2.1"
unicode-segmentation = "1.12" unicode-segmentation = "1.12"
unicode-width = "0.2" unicode-width = "0.2"
ureq = { version = "2.12", default-features = false, features = ["socks-proxy"] } ureq = { version = "2.12", default-features = false }
url = "2.2" url = "2.2"
uu_cp = "0.0.30" uu_cp = "0.0.29"
uu_mkdir = "0.0.30" uu_mkdir = "0.0.29"
uu_mktemp = "0.0.30" uu_mktemp = "0.0.29"
uu_mv = "0.0.30" uu_mv = "0.0.29"
uu_touch = "0.0.30" uu_touch = "0.0.29"
uu_whoami = "0.0.30" uu_whoami = "0.0.29"
uu_uname = "0.0.30" uu_uname = "0.0.29"
uucore = "0.0.30" uucore = "0.0.29"
uuid = "1.16.0" uuid = "1.12.0"
v_htmlescape = "0.15.0" v_htmlescape = "0.15.0"
wax = "0.6" wax = "0.6"
web-time = "1.1.0" web-time = "1.1.0"
@ -197,22 +195,22 @@ unchecked_duration_subtraction = "warn"
workspace = true workspace = true
[dependencies] [dependencies]
nu-cli = { path = "./crates/nu-cli", version = "0.104.1" } nu-cli = { path = "./crates/nu-cli", version = "0.102.0" }
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.104.1" } nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.102.0" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.104.1" } nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.102.0" }
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.104.1", optional = true } nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.102.0", optional = true }
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.104.1" } nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.102.0" }
nu-command = { path = "./crates/nu-command", version = "0.104.1" } nu-command = { path = "./crates/nu-command", version = "0.102.0" }
nu-engine = { path = "./crates/nu-engine", version = "0.104.1" } nu-engine = { path = "./crates/nu-engine", version = "0.102.0" }
nu-explore = { path = "./crates/nu-explore", version = "0.104.1" } nu-explore = { path = "./crates/nu-explore", version = "0.102.0" }
nu-lsp = { path = "./crates/nu-lsp/", version = "0.104.1" } nu-lsp = { path = "./crates/nu-lsp/", version = "0.102.0" }
nu-parser = { path = "./crates/nu-parser", version = "0.104.1" } nu-parser = { path = "./crates/nu-parser", version = "0.102.0" }
nu-path = { path = "./crates/nu-path", version = "0.104.1" } nu-path = { path = "./crates/nu-path", version = "0.102.0" }
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.104.1" } nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.102.0" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.104.1" } nu-protocol = { path = "./crates/nu-protocol", version = "0.102.0" }
nu-std = { path = "./crates/nu-std", version = "0.104.1" } nu-std = { path = "./crates/nu-std", version = "0.102.0" }
nu-system = { path = "./crates/nu-system", version = "0.104.1" } nu-system = { path = "./crates/nu-system", version = "0.102.0" }
nu-utils = { path = "./crates/nu-utils", version = "0.104.1" } nu-utils = { path = "./crates/nu-utils", version = "0.102.0" }
reedline = { workspace = true, features = ["bashisms", "sqlite"] } reedline = { workspace = true, features = ["bashisms", "sqlite"] }
crossterm = { workspace = true } crossterm = { workspace = true }
@ -220,6 +218,7 @@ ctrlc = { workspace = true }
dirs = { workspace = true } dirs = { workspace = true }
log = { workspace = true } log = { workspace = true }
miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] } miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] }
mimalloc = { version = "0.1.42", default-features = false, optional = true }
multipart-rs = { workspace = true } multipart-rs = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
simplelog = "0.12" simplelog = "0.12"
@ -241,9 +240,9 @@ nix = { workspace = true, default-features = false, features = [
] } ] }
[dev-dependencies] [dev-dependencies]
nu-test-support = { path = "./crates/nu-test-support", version = "0.104.1" } nu-test-support = { path = "./crates/nu-test-support", version = "0.102.0" }
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.104.1" } nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.102.0" }
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.104.1" } nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.102.0" }
assert_cmd = "2.0" assert_cmd = "2.0"
dirs = { workspace = true } dirs = { workspace = true }
tango-bench = "0.6" tango-bench = "0.6"
@ -273,6 +272,7 @@ default = [
"plugin", "plugin",
"trash-support", "trash-support",
"sqlite", "sqlite",
"mimalloc",
] ]
stable = ["default"] stable = ["default"]
# NOTE: individual features are also passed to `nu-cmd-lang` that uses them to generate the feature matrix in the `version` command # NOTE: individual features are also passed to `nu-cmd-lang` that uses them to generate the feature matrix in the `version` command
@ -281,6 +281,7 @@ stable = ["default"]
# otherwise the system version will be used. Not enabled by default because it takes a while to build # otherwise the system version will be used. Not enabled by default because it takes a while to build
static-link-openssl = ["dep:openssl", "nu-cmd-lang/static-link-openssl"] static-link-openssl = ["dep:openssl", "nu-cmd-lang/static-link-openssl"]
mimalloc = ["nu-cmd-lang/mimalloc", "dep:mimalloc"]
# Optional system clipboard support in `reedline`, this behavior has problematic compatibility with some systems. # Optional system clipboard support in `reedline`, this behavior has problematic compatibility with some systems.
# Missing X server/ Wayland can cause issues # Missing X server/ Wayland can cause issues
system-clipboard = [ system-clipboard = [
@ -293,7 +294,7 @@ system-clipboard = [
trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"] trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"]
# SQLite commands for nushell # SQLite commands for nushell
sqlite = ["nu-command/sqlite", "nu-cmd-lang/sqlite", "nu-std/sqlite"] sqlite = ["nu-command/sqlite", "nu-cmd-lang/sqlite"]
[profile.release] [profile.release]
opt-level = "s" # Optimize for size opt-level = "s" # Optimize for size

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2019 - 2025 The Nushell Project Developers Copyright (c) 2019 - 2023 The Nushell Project Developers
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -4,6 +4,7 @@
[![Nightly Build](https://github.com/nushell/nushell/actions/workflows/nightly-build.yml/badge.svg)](https://github.com/nushell/nushell/actions/workflows/nightly-build.yml) [![Nightly Build](https://github.com/nushell/nushell/actions/workflows/nightly-build.yml/badge.svg)](https://github.com/nushell/nushell/actions/workflows/nightly-build.yml)
[![Discord](https://img.shields.io/discord/601130461678272522.svg?logo=discord)](https://discord.gg/NtAbbGn) [![Discord](https://img.shields.io/discord/601130461678272522.svg?logo=discord)](https://discord.gg/NtAbbGn)
[![The Changelog #363](https://img.shields.io/badge/The%20Changelog-%23363-61c192.svg)](https://changelog.com/podcast/363) [![The Changelog #363](https://img.shields.io/badge/The%20Changelog-%23363-61c192.svg)](https://changelog.com/podcast/363)
[![@nu_shell](https://img.shields.io/badge/twitter-@nu_shell-1DA1F3?style=flat-square)](https://twitter.com/nu_shell)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/nushell/nushell)](https://github.com/nushell/nushell/graphs/commit-activity) [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/nushell/nushell)](https://github.com/nushell/nushell/graphs/commit-activity)
[![GitHub contributors](https://img.shields.io/github/contributors/nushell/nushell)](https://github.com/nushell/nushell/graphs/contributors) [![GitHub contributors](https://img.shields.io/github/contributors/nushell/nushell)](https://github.com/nushell/nushell/graphs/contributors)
@ -34,7 +35,7 @@ This project has reached a minimum-viable-product level of quality. Many people
The [Nushell book](https://www.nushell.sh/book/) is the primary source of Nushell documentation. You can find [a full list of Nu commands in the book](https://www.nushell.sh/commands/), and we have many examples of using Nu in our [cookbook](https://www.nushell.sh/cookbook/). The [Nushell book](https://www.nushell.sh/book/) is the primary source of Nushell documentation. You can find [a full list of Nu commands in the book](https://www.nushell.sh/commands/), and we have many examples of using Nu in our [cookbook](https://www.nushell.sh/cookbook/).
We're also active on [Discord](https://discord.gg/NtAbbGn); come and chat with us! We're also active on [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell); come and chat with us!
## Installation ## Installation
@ -222,7 +223,6 @@ Please submit an issue or PR to be added to this list.
- [Dorothy](http://github.com/bevry/dorothy) - [Dorothy](http://github.com/bevry/dorothy)
- [Direnv](https://github.com/direnv/direnv/blob/master/docs/hook.md#nushell) - [Direnv](https://github.com/direnv/direnv/blob/master/docs/hook.md#nushell)
- [x-cmd](https://x-cmd.com/mod/nu) - [x-cmd](https://x-cmd.com/mod/nu)
- [vfox](https://github.com/version-fox/vfox)
## Contributing ## Contributing

View File

@ -1,6 +1,7 @@
use nu_cli::{eval_source, evaluate_commands}; use nu_cli::{eval_source, evaluate_commands};
use nu_plugin_core::{Encoder, EncodingType}; use nu_plugin_core::{Encoder, EncodingType};
use nu_plugin_protocol::{PluginCallResponse, PluginOutput}; use nu_plugin_protocol::{PluginCallResponse, PluginOutput};
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, Stack}, engine::{EngineState, Stack},
PipelineData, Signals, Span, Spanned, Value, PipelineData, Signals, Span, Spanned, Value,
@ -8,11 +9,12 @@ use nu_protocol::{
use nu_std::load_standard_library; use nu_std::load_standard_library;
use nu_utils::{get_default_config, get_default_env}; use nu_utils::{get_default_config, get_default_env};
use std::{ use std::{
fmt::Write,
hint::black_box,
rc::Rc, rc::Rc,
sync::{atomic::AtomicBool, Arc}, sync::{atomic::AtomicBool, Arc},
}; };
use std::hint::black_box;
use tango_bench::{benchmark_fn, tango_benchmarks, tango_main, IntoBenchmarks}; use tango_bench::{benchmark_fn, tango_benchmarks, tango_main, IntoBenchmarks};
fn load_bench_commands() -> EngineState { fn load_bench_commands() -> EngineState {
@ -139,16 +141,19 @@ fn bench_load_standard_lib() -> impl IntoBenchmarks {
})] })]
} }
fn create_flat_record_string(n: usize) -> String { fn create_flat_record_string(n: i32) -> String {
let mut s = String::from("let record = { "); let mut s = String::from("let record = {");
for i in 0..n { for i in 0..n {
write!(s, "col_{i}: {i}, ").unwrap(); s.push_str(&format!("col_{}: {}", i, i));
if i < n - 1 {
s.push_str(", ");
}
} }
s.push('}'); s.push('}');
s s
} }
fn create_nested_record_string(depth: usize) -> String { fn create_nested_record_string(depth: i32) -> String {
let mut s = String::from("let record = {"); let mut s = String::from("let record = {");
for _ in 0..depth { for _ in 0..depth {
s.push_str("col: {"); s.push_str("col: {");
@ -161,7 +166,7 @@ fn create_nested_record_string(depth: usize) -> String {
s s
} }
fn create_example_table_nrows(n: usize) -> String { fn create_example_table_nrows(n: i32) -> String {
let mut s = String::from("let table = [[foo bar baz]; "); let mut s = String::from("let table = [[foo bar baz]; ");
for i in 0..n { for i in 0..n {
s.push_str(&format!("[0, 1, {i}]")); s.push_str(&format!("[0, 1, {i}]"));
@ -173,7 +178,7 @@ fn create_example_table_nrows(n: usize) -> String {
s s
} }
fn bench_record_create(n: usize) -> impl IntoBenchmarks { fn bench_record_create(n: i32) -> impl IntoBenchmarks {
bench_command( bench_command(
&format!("record_create_{n}"), &format!("record_create_{n}"),
&create_flat_record_string(n), &create_flat_record_string(n),
@ -182,7 +187,7 @@ fn bench_record_create(n: usize) -> impl IntoBenchmarks {
) )
} }
fn bench_record_flat_access(n: usize) -> impl IntoBenchmarks { fn bench_record_flat_access(n: i32) -> impl IntoBenchmarks {
let setup_command = create_flat_record_string(n); let setup_command = create_flat_record_string(n);
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command); let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
bench_command( bench_command(
@ -193,10 +198,10 @@ fn bench_record_flat_access(n: usize) -> impl IntoBenchmarks {
) )
} }
fn bench_record_nested_access(n: usize) -> impl IntoBenchmarks { fn bench_record_nested_access(n: i32) -> impl IntoBenchmarks {
let setup_command = create_nested_record_string(n); let setup_command = create_nested_record_string(n);
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command); let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
let nested_access = ".col".repeat(n); let nested_access = ".col".repeat(n as usize);
bench_command( bench_command(
&format!("record_nested_access_{n}"), &format!("record_nested_access_{n}"),
&format!("$record{} | ignore", nested_access), &format!("$record{} | ignore", nested_access),
@ -205,18 +210,7 @@ fn bench_record_nested_access(n: usize) -> impl IntoBenchmarks {
) )
} }
fn bench_record_insert(n: usize, m: usize) -> impl IntoBenchmarks { fn bench_table_create(n: i32) -> impl IntoBenchmarks {
let setup_command = create_flat_record_string(n);
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
let mut insert = String::from("$record");
for i in n..(n + m) {
write!(insert, " | insert col_{i} {i}").unwrap();
}
insert.push_str(" | ignore");
bench_command(&format!("record_insert_{n}_{m}"), &insert, stack, engine)
}
fn bench_table_create(n: usize) -> impl IntoBenchmarks {
bench_command( bench_command(
&format!("table_create_{n}"), &format!("table_create_{n}"),
&create_example_table_nrows(n), &create_example_table_nrows(n),
@ -225,7 +219,7 @@ fn bench_table_create(n: usize) -> impl IntoBenchmarks {
) )
} }
fn bench_table_get(n: usize) -> impl IntoBenchmarks { fn bench_table_get(n: i32) -> impl IntoBenchmarks {
let setup_command = create_example_table_nrows(n); let setup_command = create_example_table_nrows(n);
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command); let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
bench_command( bench_command(
@ -236,7 +230,7 @@ fn bench_table_get(n: usize) -> impl IntoBenchmarks {
) )
} }
fn bench_table_select(n: usize) -> impl IntoBenchmarks { fn bench_table_select(n: i32) -> impl IntoBenchmarks {
let setup_command = create_example_table_nrows(n); let setup_command = create_example_table_nrows(n);
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command); let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
bench_command( bench_command(
@ -247,29 +241,7 @@ fn bench_table_select(n: usize) -> impl IntoBenchmarks {
) )
} }
fn bench_table_insert_row(n: usize, m: usize) -> impl IntoBenchmarks { fn bench_eval_interleave(n: i32) -> impl IntoBenchmarks {
let setup_command = create_example_table_nrows(n);
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
let mut insert = String::from("$table");
for i in n..(n + m) {
write!(insert, " | insert {i} {{ foo: 0, bar: 1, baz: {i} }}").unwrap();
}
insert.push_str(" | ignore");
bench_command(&format!("table_insert_row_{n}_{m}"), &insert, stack, engine)
}
fn bench_table_insert_col(n: usize, m: usize) -> impl IntoBenchmarks {
let setup_command = create_example_table_nrows(n);
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
let mut insert = String::from("$table");
for i in 0..m {
write!(insert, " | insert col_{i} {i}").unwrap();
}
insert.push_str(" | ignore");
bench_command(&format!("table_insert_col_{n}_{m}"), &insert, stack, engine)
}
fn bench_eval_interleave(n: usize) -> impl IntoBenchmarks {
let engine = setup_engine(); let engine = setup_engine();
let stack = Stack::new(); let stack = Stack::new();
bench_command( bench_command(
@ -280,7 +252,7 @@ fn bench_eval_interleave(n: usize) -> impl IntoBenchmarks {
) )
} }
fn bench_eval_interleave_with_interrupt(n: usize) -> impl IntoBenchmarks { fn bench_eval_interleave_with_interrupt(n: i32) -> impl IntoBenchmarks {
let mut engine = setup_engine(); let mut engine = setup_engine();
engine.set_signals(Signals::new(Arc::new(AtomicBool::new(false)))); engine.set_signals(Signals::new(Arc::new(AtomicBool::new(false))));
let stack = Stack::new(); let stack = Stack::new();
@ -292,7 +264,7 @@ fn bench_eval_interleave_with_interrupt(n: usize) -> impl IntoBenchmarks {
) )
} }
fn bench_eval_for(n: usize) -> impl IntoBenchmarks { fn bench_eval_for(n: i32) -> impl IntoBenchmarks {
let engine = setup_engine(); let engine = setup_engine();
let stack = Stack::new(); let stack = Stack::new();
bench_command( bench_command(
@ -303,7 +275,7 @@ fn bench_eval_for(n: usize) -> impl IntoBenchmarks {
) )
} }
fn bench_eval_each(n: usize) -> impl IntoBenchmarks { fn bench_eval_each(n: i32) -> impl IntoBenchmarks {
let engine = setup_engine(); let engine = setup_engine();
let stack = Stack::new(); let stack = Stack::new();
bench_command( bench_command(
@ -314,7 +286,7 @@ fn bench_eval_each(n: usize) -> impl IntoBenchmarks {
) )
} }
fn bench_eval_par_each(n: usize) -> impl IntoBenchmarks { fn bench_eval_par_each(n: i32) -> impl IntoBenchmarks {
let engine = setup_engine(); let engine = setup_engine();
let stack = Stack::new(); let stack = Stack::new();
bench_command( bench_command(
@ -455,14 +427,6 @@ tango_benchmarks!(
bench_record_nested_access(32), bench_record_nested_access(32),
bench_record_nested_access(64), bench_record_nested_access(64),
bench_record_nested_access(128), bench_record_nested_access(128),
bench_record_insert(1, 1),
bench_record_insert(10, 1),
bench_record_insert(100, 1),
bench_record_insert(1000, 1),
bench_record_insert(1, 10),
bench_record_insert(10, 10),
bench_record_insert(100, 10),
bench_record_insert(1000, 10),
// Table // Table
bench_table_create(1), bench_table_create(1),
bench_table_create(10), bench_table_create(10),
@ -476,22 +440,6 @@ tango_benchmarks!(
bench_table_select(10), bench_table_select(10),
bench_table_select(100), bench_table_select(100),
bench_table_select(1_000), bench_table_select(1_000),
bench_table_insert_row(1, 1),
bench_table_insert_row(10, 1),
bench_table_insert_row(100, 1),
bench_table_insert_row(1000, 1),
bench_table_insert_row(1, 10),
bench_table_insert_row(10, 10),
bench_table_insert_row(100, 10),
bench_table_insert_row(1000, 10),
bench_table_insert_col(1, 1),
bench_table_insert_col(10, 1),
bench_table_insert_col(100, 1),
bench_table_insert_col(1000, 1),
bench_table_insert_col(1, 10),
bench_table_insert_col(10, 10),
bench_table_insert_col(100, 10),
bench_table_insert_col(1000, 10),
// Eval // Eval
// Interleave // Interleave
bench_eval_interleave(100), bench_eval_interleave(100),

View File

@ -5,29 +5,28 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-cli" name = "nu-cli"
version = "0.104.1" version = "0.102.0"
[lib] [lib]
bench = false bench = false
[dev-dependencies] [dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.104.1" } nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.102.0" }
nu-command = { path = "../nu-command", version = "0.104.1" } nu-command = { path = "../nu-command", version = "0.102.0" }
nu-std = { path = "../nu-std", version = "0.104.1" } nu-test-support = { path = "../nu-test-support", version = "0.102.0" }
nu-test-support = { path = "../nu-test-support", version = "0.104.1" }
rstest = { workspace = true, default-features = false } rstest = { workspace = true, default-features = false }
tempfile = { workspace = true } tempfile = { workspace = true }
[dependencies] [dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.104.1" } nu-cmd-base = { path = "../nu-cmd-base", version = "0.102.0" }
nu-engine = { path = "../nu-engine", version = "0.104.1", features = ["os"] } nu-engine = { path = "../nu-engine", version = "0.102.0", features = ["os"] }
nu-glob = { path = "../nu-glob", version = "0.104.1" } nu-glob = { path = "../nu-glob", version = "0.102.0" }
nu-path = { path = "../nu-path", version = "0.104.1" } nu-path = { path = "../nu-path", version = "0.102.0" }
nu-parser = { path = "../nu-parser", version = "0.104.1" } nu-parser = { path = "../nu-parser", version = "0.102.0" }
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.104.1", optional = true } nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.102.0", optional = true }
nu-protocol = { path = "../nu-protocol", version = "0.104.1", features = ["os"] } nu-protocol = { path = "../nu-protocol", version = "0.102.0", features = ["os"] }
nu-utils = { path = "../nu-utils", version = "0.104.1" } nu-utils = { path = "../nu-utils", version = "0.102.0" }
nu-color-config = { path = "../nu-color-config", version = "0.104.1" } nu-color-config = { path = "../nu-color-config", version = "0.102.0" }
nu-ansi-term = { workspace = true } nu-ansi-term = { workspace = true }
reedline = { workspace = true, features = ["bashisms", "sqlite"] } reedline = { workspace = true, features = ["bashisms", "sqlite"] }
@ -41,7 +40,6 @@ miette = { workspace = true, features = ["fancy-no-backtrace"] }
nucleo-matcher = { workspace = true } nucleo-matcher = { workspace = true }
percent-encoding = { workspace = true } percent-encoding = { workspace = true }
sysinfo = { workspace = true } sysinfo = { workspace = true }
strum = { workspace = true }
unicode-segmentation = { workspace = true } unicode-segmentation = { workspace = true }
uuid = { workspace = true, features = ["v4"] } uuid = { workspace = true, features = ["v4"] }
which = { workspace = true } which = { workspace = true }

View File

@ -1,9 +1,9 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct CommandlineEdit; pub struct SubCommand;
impl Command for CommandlineEdit { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"commandline edit" "commandline edit"
} }
@ -29,7 +29,7 @@ impl Command for CommandlineEdit {
.required( .required(
"str", "str",
SyntaxShape::String, SyntaxShape::String,
"The string to perform the operation with.", "the string to perform the operation with",
) )
.category(Category::Core) .category(Category::Core)
} }

View File

@ -2,9 +2,9 @@ use nu_engine::command_prelude::*;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
#[derive(Clone)] #[derive(Clone)]
pub struct CommandlineGetCursor; pub struct SubCommand;
impl Command for CommandlineGetCursor { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"commandline get-cursor" "commandline get-cursor"
} }

View File

@ -4,6 +4,6 @@ mod get_cursor;
mod set_cursor; mod set_cursor;
pub use commandline_::Commandline; pub use commandline_::Commandline;
pub use edit::CommandlineEdit; pub use edit::SubCommand as CommandlineEdit;
pub use get_cursor::CommandlineGetCursor; pub use get_cursor::SubCommand as CommandlineGetCursor;
pub use set_cursor::CommandlineSetCursor; pub use set_cursor::SubCommand as CommandlineSetCursor;

View File

@ -3,9 +3,9 @@ use nu_engine::command_prelude::*;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
#[derive(Clone)] #[derive(Clone)]
pub struct CommandlineSetCursor; pub struct SubCommand;
impl Command for CommandlineSetCursor { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"commandline set-cursor" "commandline set-cursor"
} }
@ -18,7 +18,7 @@ impl Command for CommandlineSetCursor {
"set the current cursor position to the end of the buffer", "set the current cursor position to the end of the buffer",
Some('e'), Some('e'),
) )
.optional("pos", SyntaxShape::Int, "Cursor position to be set.") .optional("pos", SyntaxShape::Int, "Cursor position to be set")
.category(Category::Core) .category(Category::Core)
} }

View File

@ -105,9 +105,10 @@ impl Command for History {
.ok() .ok()
}) })
.map(move |entries| { .map(move |entries| {
entries.into_iter().enumerate().map(move |(idx, entry)| { entries
create_sqlite_history_record(idx, entry, long, head) .into_iter()
}) .enumerate()
.map(move |(idx, entry)| create_history_record(idx, entry, long, head))
}) })
.ok_or(IoError::new( .ok_or(IoError::new(
std::io::ErrorKind::NotFound, std::io::ErrorKind::NotFound,
@ -139,7 +140,7 @@ impl Command for History {
} }
} }
fn create_sqlite_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span) -> Value { fn create_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span) -> Value {
//1. Format all the values //1. Format all the values
//2. Create a record of either short or long columns and values //2. Create a record of either short or long columns and values
@ -150,8 +151,11 @@ fn create_sqlite_history_record(idx: usize, entry: HistoryItem, long: bool, head
.unwrap_or_default(), .unwrap_or_default(),
head, head,
); );
let start_timestamp_value = Value::date( let start_timestamp_value = Value::string(
entry.start_timestamp.unwrap_or_default().fixed_offset(), entry
.start_timestamp
.map(|time| time.to_string())
.unwrap_or_default(),
head, head,
); );
let command_value = Value::string(entry.command_line, head); let command_value = Value::string(entry.command_line, head);

View File

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

View File

@ -1,87 +0,0 @@
use super::{completion_options::NuMatcher, SemanticSuggestion};
use crate::{
completions::{Completer, CompletionOptions},
SuggestionKind,
};
use nu_protocol::{
engine::{Stack, StateWorkingSet},
Span,
};
use reedline::Suggestion;
pub struct AttributeCompletion;
pub struct AttributableCompletion;
impl Completer for AttributeCompletion {
fn fetch(
&mut self,
working_set: &StateWorkingSet,
_stack: &Stack,
prefix: impl AsRef<str>,
span: Span,
offset: usize,
options: &CompletionOptions,
) -> Vec<SemanticSuggestion> {
let mut matcher = NuMatcher::new(prefix, options);
let attr_commands =
working_set.find_commands_by_predicate(|s| s.starts_with(b"attr "), true);
for (decl_id, name, desc, ty) in attr_commands {
let name = name.strip_prefix(b"attr ").unwrap_or(&name);
matcher.add_semantic_suggestion(SemanticSuggestion {
suggestion: Suggestion {
value: String::from_utf8_lossy(name).into_owned(),
description: desc,
style: None,
extra: None,
span: reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
append_whitespace: false,
},
kind: Some(SuggestionKind::Command(ty, Some(decl_id))),
});
}
matcher.results()
}
}
impl Completer for AttributableCompletion {
fn fetch(
&mut self,
working_set: &StateWorkingSet,
_stack: &Stack,
prefix: impl AsRef<str>,
span: Span,
offset: usize,
options: &CompletionOptions,
) -> Vec<SemanticSuggestion> {
let mut matcher = NuMatcher::new(prefix, options);
for s in ["def", "extern", "export def", "export extern"] {
let decl_id = working_set
.find_decl(s.as_bytes())
.expect("internal error, builtin declaration not found");
let cmd = working_set.get_decl(decl_id);
matcher.add_semantic_suggestion(SemanticSuggestion {
suggestion: Suggestion {
value: cmd.name().into(),
description: Some(cmd.description().into()),
style: None,
extra: None,
span: reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
append_whitespace: false,
},
kind: Some(SuggestionKind::Command(cmd.command_type(), None)),
});
}
matcher.results()
}
}

View File

@ -1,7 +1,7 @@
use crate::completions::CompletionOptions; use crate::completions::CompletionOptions;
use nu_protocol::{ use nu_protocol::{
engine::{Stack, StateWorkingSet}, engine::{Stack, StateWorkingSet},
DeclId, Span, Span,
}; };
use reedline::Suggestion; use reedline::Suggestion;
@ -12,9 +12,10 @@ pub trait Completer {
&mut self, &mut self,
working_set: &StateWorkingSet, working_set: &StateWorkingSet,
stack: &Stack, stack: &Stack,
prefix: impl AsRef<str>, prefix: &[u8],
span: Span, span: Span,
offset: usize, offset: usize,
pos: usize,
options: &CompletionOptions, options: &CompletionOptions,
) -> Vec<SemanticSuggestion>; ) -> Vec<SemanticSuggestion>;
} }
@ -28,15 +29,9 @@ pub struct SemanticSuggestion {
// TODO: think about name: maybe suggestion context? // TODO: think about name: maybe suggestion context?
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum SuggestionKind { pub enum SuggestionKind {
Command(nu_protocol::engine::CommandType, Option<DeclId>), Command(nu_protocol::engine::CommandType),
Value(nu_protocol::Type), Type(nu_protocol::Type),
CellPath,
Directory,
File,
Flag,
Module, Module,
Operator,
Variable,
} }
impl From<Suggestion> for SemanticSuggestion { impl From<Suggestion> for SemanticSuggestion {

View File

@ -1,153 +0,0 @@
use std::borrow::Cow;
use crate::completions::{Completer, CompletionOptions, SemanticSuggestion, SuggestionKind};
use nu_engine::{column::get_columns, eval_variable};
use nu_protocol::{
ast::{Expr, Expression, FullCellPath, PathMember},
engine::{Stack, StateWorkingSet},
eval_const::eval_constant,
ShellError, Span, Value,
};
use reedline::Suggestion;
use super::completion_options::NuMatcher;
pub struct CellPathCompletion<'a> {
pub full_cell_path: &'a FullCellPath,
pub position: usize,
}
fn prefix_from_path_member(member: &PathMember, pos: usize) -> (String, Span) {
let (prefix_str, start) = match member {
PathMember::String { val, span, .. } => (val, span.start),
PathMember::Int { val, span, .. } => (&val.to_string(), span.start),
};
let prefix_str = prefix_str.get(..pos + 1 - start).unwrap_or(prefix_str);
// strip wrapping quotes
let quotations = ['"', '\'', '`'];
let prefix_str = prefix_str.strip_prefix(quotations).unwrap_or(prefix_str);
(prefix_str.to_string(), Span::new(start, pos + 1))
}
impl Completer for CellPathCompletion<'_> {
fn fetch(
&mut self,
working_set: &StateWorkingSet,
stack: &Stack,
_prefix: impl AsRef<str>,
_span: Span,
offset: usize,
options: &CompletionOptions,
) -> Vec<SemanticSuggestion> {
let mut prefix_str = String::new();
// position at dots, e.g. `$env.config.<TAB>`
let mut span = Span::new(self.position + 1, self.position + 1);
let mut path_member_num_before_pos = 0;
for member in self.full_cell_path.tail.iter() {
if member.span().end <= self.position {
path_member_num_before_pos += 1;
} else if member.span().contains(self.position) {
(prefix_str, span) = prefix_from_path_member(member, self.position);
break;
}
}
let current_span = reedline::Span {
start: span.start - offset,
end: span.end - offset,
};
let mut matcher = NuMatcher::new(prefix_str, options);
let path_members = self
.full_cell_path
.tail
.get(0..path_member_num_before_pos)
.unwrap_or_default();
let value = eval_cell_path(
working_set,
stack,
&self.full_cell_path.head,
path_members,
span,
)
.unwrap_or_default();
for suggestion in get_suggestions_by_value(&value, current_span) {
matcher.add_semantic_suggestion(suggestion);
}
matcher.results()
}
}
/// Follow cell path to get the value
/// NOTE: This is a relatively lightweight implementation,
/// so it may fail to get the exact value when the expression is complicated.
/// One failing example would be `[$foo].0`
pub(crate) fn eval_cell_path(
working_set: &StateWorkingSet,
stack: &Stack,
head: &Expression,
path_members: &[PathMember],
span: Span,
) -> Result<Value, ShellError> {
// evaluate the head expression to get its value
let head_value = if let Expr::Var(var_id) = head.expr {
working_set
.get_variable(var_id)
.const_val
.to_owned()
.map_or_else(
|| eval_variable(working_set.permanent_state, stack, var_id, span),
Ok,
)
} else {
eval_constant(working_set, head)
}?;
head_value
.follow_cell_path(path_members, false)
.map(Cow::into_owned)
}
fn get_suggestions_by_value(
value: &Value,
current_span: reedline::Span,
) -> Vec<SemanticSuggestion> {
let to_suggestion = |s: String, v: Option<&Value>| {
// Check if the string needs quoting
let value = if s.is_empty()
|| s.chars()
.any(|c: char| !(c.is_ascii_alphabetic() || ['_', '-'].contains(&c)))
{
format!("{:?}", s)
} else {
s
};
SemanticSuggestion {
suggestion: Suggestion {
value,
span: current_span,
description: v.map(|v| v.get_type().to_string()),
..Suggestion::default()
},
kind: Some(SuggestionKind::CellPath),
}
};
match value {
Value::Record { val, .. } => val
.columns()
.map(|s| to_suggestion(s.to_string(), val.get(s)))
.collect(),
Value::List { vals, .. } => get_columns(vals.as_slice())
.into_iter()
.map(|s| {
let sub_val = vals
.first()
.and_then(|v| v.as_record().ok())
.and_then(|rv| rv.get(&s));
to_suggestion(s, sub_val)
})
.collect(),
_ => vec![],
}
}

View File

@ -4,8 +4,9 @@ use crate::{
completions::{Completer, CompletionOptions}, completions::{Completer, CompletionOptions},
SuggestionKind, SuggestionKind,
}; };
use nu_parser::FlatShape;
use nu_protocol::{ use nu_protocol::{
engine::{CommandType, Stack, StateWorkingSet}, engine::{CachedFile, Stack, StateWorkingSet},
Span, Span,
}; };
use reedline::Suggestion; use reedline::Suggestion;
@ -13,13 +14,24 @@ use reedline::Suggestion;
use super::{completion_options::NuMatcher, SemanticSuggestion}; use super::{completion_options::NuMatcher, SemanticSuggestion};
pub struct CommandCompletion { pub struct CommandCompletion {
/// Whether to include internal commands flattened: Vec<(Span, FlatShape)>,
pub internals: bool, flat_shape: FlatShape,
/// Whether to include external commands force_completion_after_space: bool,
pub externals: bool,
} }
impl CommandCompletion { impl CommandCompletion {
pub fn new(
flattened: Vec<(Span, FlatShape)>,
flat_shape: FlatShape,
force_completion_after_space: bool,
) -> Self {
Self {
flattened,
flat_shape,
force_completion_after_space,
}
}
fn external_command_completion( fn external_command_completion(
&self, &self,
working_set: &StateWorkingSet, working_set: &StateWorkingSet,
@ -59,9 +71,6 @@ impl CommandCompletion {
if suggs.contains_key(&value) { if suggs.contains_key(&value) {
continue; continue;
} }
// TODO: check name matching before a relative heavy IO involved
// `is_executable` for performance consideration, should avoid
// duplicated `match_aux` call for matched items in the future
if matcher.matches(&name) && is_executable::is_executable(item.path()) { if matcher.matches(&name) && is_executable::is_executable(item.path()) {
// If there's an internal command with the same name, adds ^cmd to the // If there's an internal command with the same name, adds ^cmd to the
// matcher so that both the internal and external command are included // matcher so that both the internal and external command are included
@ -75,10 +84,8 @@ impl CommandCompletion {
append_whitespace: true, append_whitespace: true,
..Default::default() ..Default::default()
}, },
kind: Some(SuggestionKind::Command( // TODO: is there a way to create a test?
CommandType::External, kind: None,
None,
)),
}, },
); );
} }
@ -90,24 +97,21 @@ impl CommandCompletion {
suggs suggs
} }
}
impl Completer for CommandCompletion { fn complete_commands(
fn fetch( &self,
&mut self,
working_set: &StateWorkingSet, working_set: &StateWorkingSet,
_stack: &Stack,
prefix: impl AsRef<str>,
span: Span, span: Span,
offset: usize, offset: usize,
find_externals: bool,
options: &CompletionOptions, options: &CompletionOptions,
) -> Vec<SemanticSuggestion> { ) -> Vec<SemanticSuggestion> {
let mut matcher = NuMatcher::new(prefix, options); let partial = working_set.get_span_contents(span);
let mut matcher = NuMatcher::new(String::from_utf8_lossy(partial), options.clone());
let sugg_span = reedline::Span::new(span.start - offset, span.end - offset); let sugg_span = reedline::Span::new(span.start - offset, span.end - offset);
let mut internal_suggs = HashMap::new(); let mut internal_suggs = HashMap::new();
if self.internals {
let filtered_commands = working_set.find_commands_by_predicate( let filtered_commands = working_set.find_commands_by_predicate(
|name| { |name| {
let name = String::from_utf8_lossy(name); let name = String::from_utf8_lossy(name);
@ -115,7 +119,7 @@ impl Completer for CommandCompletion {
}, },
true, true,
); );
for (decl_id, name, description, typ) in filtered_commands { for (name, description, typ) in filtered_commands {
let name = String::from_utf8_lossy(&name); let name = String::from_utf8_lossy(&name);
internal_suggs.insert( internal_suggs.insert(
name.to_string(), name.to_string(),
@ -127,13 +131,12 @@ impl Completer for CommandCompletion {
append_whitespace: true, append_whitespace: true,
..Suggestion::default() ..Suggestion::default()
}, },
kind: Some(SuggestionKind::Command(typ, Some(decl_id))), kind: Some(SuggestionKind::Command(typ)),
}, },
); );
} }
}
let mut external_suggs = if self.externals { let mut external_suggs = if find_externals {
self.external_command_completion( self.external_command_completion(
working_set, working_set,
sugg_span, sugg_span,
@ -156,3 +159,179 @@ impl Completer for CommandCompletion {
res res
} }
} }
impl Completer for CommandCompletion {
fn fetch(
&mut self,
working_set: &StateWorkingSet,
_stack: &Stack,
_prefix: &[u8],
span: Span,
offset: usize,
pos: usize,
options: &CompletionOptions,
) -> Vec<SemanticSuggestion> {
let last = self
.flattened
.iter()
.rev()
.skip_while(|x| x.0.end > pos)
.take_while(|x| {
matches!(
x.1,
FlatShape::InternalCall(_)
| FlatShape::External
| FlatShape::ExternalArg
| FlatShape::Literal
| FlatShape::String
)
})
.last();
// The last item here would be the earliest shape that could possible by part of this subcommand
let subcommands = if let Some(last) = last {
self.complete_commands(
working_set,
Span::new(last.0.start, pos),
offset,
false,
options,
)
} else {
vec![]
};
if !subcommands.is_empty() {
return subcommands;
}
let config = working_set.get_config();
if matches!(self.flat_shape, nu_parser::FlatShape::External)
|| matches!(self.flat_shape, nu_parser::FlatShape::InternalCall(_))
|| ((span.end - span.start) == 0)
|| is_passthrough_command(working_set.delta.get_file_contents())
{
// we're in a gap or at a command
if working_set.get_span_contents(span).is_empty() && !self.force_completion_after_space
{
return vec![];
}
self.complete_commands(
working_set,
span,
offset,
config.completions.external.enable,
options,
)
} else {
vec![]
}
}
}
pub fn find_non_whitespace_index(contents: &[u8], start: usize) -> usize {
match contents.get(start..) {
Some(contents) => {
contents
.iter()
.take_while(|x| x.is_ascii_whitespace())
.count()
+ start
}
None => start,
}
}
pub fn is_passthrough_command(working_set_file_contents: &[CachedFile]) -> bool {
for cached_file in working_set_file_contents {
let contents = &cached_file.content;
let last_pipe_pos_rev = contents.iter().rev().position(|x| x == &b'|');
let last_pipe_pos = last_pipe_pos_rev.map(|x| contents.len() - x).unwrap_or(0);
let cur_pos = find_non_whitespace_index(contents, last_pipe_pos);
let result = match contents.get(cur_pos..) {
Some(contents) => contents.starts_with(b"sudo ") || contents.starts_with(b"doas "),
None => false,
};
if result {
return true;
}
}
false
}
#[cfg(test)]
mod command_completions_tests {
use super::*;
use nu_protocol::engine::EngineState;
use std::sync::Arc;
#[test]
fn test_find_non_whitespace_index() {
let commands = [
(" hello", 4),
("sudo ", 0),
(" sudo ", 2),
(" sudo ", 2),
(" hello ", 1),
(" hello ", 3),
(" hello | sudo ", 4),
(" sudo|sudo", 5),
("sudo | sudo ", 0),
(" hello sud", 1),
];
for (idx, ele) in commands.iter().enumerate() {
let index = find_non_whitespace_index(ele.0.as_bytes(), 0);
assert_eq!(index, ele.1, "Failed on index {}", idx);
}
}
#[test]
fn test_is_last_command_passthrough() {
let commands = [
(" hello", false),
(" sudo ", true),
("sudo ", true),
(" hello", false),
(" sudo", false),
(" sudo ", true),
(" sudo ", true),
(" sudo ", true),
(" hello ", false),
(" hello | sudo ", true),
(" sudo|sudo", false),
("sudo | sudo ", true),
(" hello sud", false),
(" sudo | sud ", false),
(" sudo|sudo ", true),
(" sudo | sudo ls | sudo ", true),
];
for (idx, ele) in commands.iter().enumerate() {
let input = ele.0.as_bytes();
let mut engine_state = EngineState::new();
engine_state.add_file("test.nu".into(), Arc::new([]));
let delta = {
let mut working_set = StateWorkingSet::new(&engine_state);
let _ = working_set.add_file("child.nu".into(), input);
working_set.render()
};
let result = engine_state.merge_delta(delta);
assert!(
result.is_ok(),
"Merge delta has failed: {}",
result.err().unwrap()
);
let is_passthrough_command = is_passthrough_command(engine_state.get_file_contents());
assert_eq!(
is_passthrough_command, ele.1,
"index for '{}': {}",
ele.0, idx
);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -22,22 +22,18 @@ pub struct PathBuiltFromString {
/// Recursively goes through paths that match a given `partial`. /// Recursively goes through paths that match a given `partial`.
/// built: State struct for a valid matching path built so far. /// built: State struct for a valid matching path built so far.
/// ///
/// `want_directory`: Whether we want only directories as completion matches.
/// Some commands like `cd` can only be run on directories whereas others
/// like `ls` can be run on regular files as well.
///
/// `isdir`: whether the current partial path has a trailing slash. /// `isdir`: whether the current partial path has a trailing slash.
/// Parsing a path string into a pathbuf loses that bit of information. /// Parsing a path string into a pathbuf loses that bit of information.
/// ///
/// `enable_exact_match`: Whether match algorithm is Prefix and all previous components /// want_directory: Whether we want only directories as completion matches.
/// of the path matched a directory exactly. /// Some commands like `cd` can only be run on directories whereas others
/// like `ls` can be run on regular files as well.
fn complete_rec( fn complete_rec(
partial: &[&str], partial: &[&str],
built_paths: &[PathBuiltFromString], built_paths: &[PathBuiltFromString],
options: &CompletionOptions, options: &CompletionOptions,
want_directory: bool, want_directory: bool,
isdir: bool, isdir: bool,
enable_exact_match: bool,
) -> Vec<PathBuiltFromString> { ) -> Vec<PathBuiltFromString> {
if let Some((&base, rest)) = partial.split_first() { if let Some((&base, rest)) = partial.split_first() {
if base.chars().all(|c| c == '.') && (isdir || !rest.is_empty()) { if base.chars().all(|c| c == '.') && (isdir || !rest.is_empty()) {
@ -50,19 +46,12 @@ fn complete_rec(
built built
}) })
.collect(); .collect();
return complete_rec( return complete_rec(rest, &built_paths, options, want_directory, isdir);
rest,
&built_paths,
options,
want_directory,
isdir,
enable_exact_match,
);
} }
} }
let prefix = partial.first().unwrap_or(&""); let prefix = partial.first().unwrap_or(&"");
let mut matcher = NuMatcher::new(prefix, options); let mut matcher = NuMatcher::new(prefix, options.clone());
for built in built_paths { for built in built_paths {
let mut path = built.cwd.clone(); let mut path = built.cwd.clone();
@ -76,11 +65,10 @@ fn complete_rec(
for entry in result.filter_map(|e| e.ok()) { for entry in result.filter_map(|e| e.ok()) {
let entry_name = entry.file_name().to_string_lossy().into_owned(); let entry_name = entry.file_name().to_string_lossy().into_owned();
let entry_isdir = entry.path().is_dir(); let entry_isdir = entry.path().is_dir() && !entry.path().is_symlink();
let mut built = built.clone(); let mut built = built.clone();
built.parts.push(entry_name.clone()); built.parts.push(entry_name.clone());
// Symlinks to directories shouldn't have a trailing slash (#13275) built.isdir = entry_isdir;
built.isdir = entry_isdir && !entry.path().is_symlink();
if !want_directory || entry_isdir { if !want_directory || entry_isdir {
matcher.add(entry_name.clone(), (entry_name, built)); matcher.add(entry_name.clone(), (entry_name, built));
@ -97,26 +85,27 @@ fn complete_rec(
// Serves as confirmation to ignore longer completions for // Serves as confirmation to ignore longer completions for
// components in between. // components in between.
if !rest.is_empty() || isdir { if !rest.is_empty() || isdir {
// Don't show longer completions if we have an exact match (#13204, #14794)
let exact_match = enable_exact_match
&& (if options.case_sensitive {
entry_name.eq(base)
} else {
entry_name.eq_ignore_case(base)
});
completions.extend(complete_rec( completions.extend(complete_rec(
rest, rest,
&[built], &[built],
options, options,
want_directory, want_directory,
isdir, isdir,
exact_match,
)); ));
} else {
completions.push(built);
}
// For https://github.com/nushell/nushell/issues/13204
if isdir && options.match_algorithm == MatchAlgorithm::Prefix {
let exact_match = if options.case_sensitive {
entry_name.eq(base)
} else {
entry_name.to_folded_case().eq(&base.to_folded_case())
};
if exact_match { if exact_match {
break; break;
} }
} else {
completions.push(built);
} }
} }
None => { None => {
@ -150,7 +139,7 @@ impl OriginalCwd {
} }
} }
pub fn surround_remove(partial: &str) -> String { fn surround_remove(partial: &str) -> String {
for c in ['`', '"', '\''] { for c in ['`', '"', '\''] {
if partial.starts_with(c) { if partial.starts_with(c) {
let ret = partial.strip_prefix(c).unwrap_or(partial); let ret = partial.strip_prefix(c).unwrap_or(partial);
@ -168,7 +157,6 @@ pub struct FileSuggestion {
pub span: nu_protocol::Span, pub span: nu_protocol::Span,
pub path: String, pub path: String,
pub style: Option<Style>, pub style: Option<Style>,
pub is_dir: bool,
} }
/// # Parameters /// # Parameters
@ -209,9 +197,10 @@ pub fn complete_item(
let ls_colors = (engine_state.config.completions.use_ls_colors let ls_colors = (engine_state.config.completions.use_ls_colors
&& engine_state.config.use_ansi_coloring.get(engine_state)) && engine_state.config.use_ansi_coloring.get(engine_state))
.then(|| { .then(|| {
let ls_colors_env_str = stack let ls_colors_env_str = match stack.get_env_var(engine_state, "LS_COLORS") {
.get_env_var(engine_state, "LS_COLORS") Some(v) => env_to_string("LS_COLORS", v, engine_state, stack).ok(),
.and_then(|v| env_to_string("LS_COLORS", v, engine_state, stack).ok()); None => None,
};
get_ls_colors(ls_colors_env_str) get_ls_colors(ls_colors_env_str)
}); });
@ -265,58 +254,58 @@ pub fn complete_item(
options, options,
want_directory, want_directory,
isdir, isdir,
options.match_algorithm == MatchAlgorithm::Prefix,
) )
.into_iter() .into_iter()
.map(|mut p| { .map(|mut p| {
if should_collapse_dots { if should_collapse_dots {
p = collapse_ndots(p); p = collapse_ndots(p);
} }
let is_dir = p.isdir;
let path = original_cwd.apply(p, path_separator); let path = original_cwd.apply(p, path_separator);
let real_path = expand_to_real_path(&path);
let metadata = std::fs::symlink_metadata(&real_path).ok();
let style = ls_colors.as_ref().map(|lsc| { let style = ls_colors.as_ref().map(|lsc| {
lsc.style_for_path_with_metadata(&real_path, metadata.as_ref()) lsc.style_for_path_with_metadata(
&path,
std::fs::symlink_metadata(expand_to_real_path(&path))
.ok()
.as_ref(),
)
.map(lscolors::Style::to_nu_ansi_term_style) .map(lscolors::Style::to_nu_ansi_term_style)
.unwrap_or_default() .unwrap_or_default()
}); });
FileSuggestion { FileSuggestion {
span, span,
path: escape_path(path), path: escape_path(path, want_directory),
style, style,
is_dir,
} }
}) })
.collect() .collect()
} }
// Fix files or folders with quotes or hashes // Fix files or folders with quotes or hashes
pub fn escape_path(path: String) -> String { pub fn escape_path(path: String, dir: bool) -> String {
// make glob pattern have the highest priority. // make glob pattern have the highest priority.
if nu_glob::is_glob(path.as_str()) || path.contains('`') { if nu_glob::is_glob(path.as_str()) {
// expand home `~` for https://github.com/nushell/nushell/issues/13905
let pathbuf = nu_path::expand_tilde(path); let pathbuf = nu_path::expand_tilde(path);
let path = pathbuf.to_string_lossy(); let path = pathbuf.to_string_lossy();
if path.contains('\'') { return if path.contains('\'') {
// decide to use double quotes // decide to use double quote, also need to escape `"` in path
// Path as Debug will do the escaping for `"`, `\` // or else users can't do anything with completed path either.
format!("{:?}", path) format!("\"{}\"", path.replace('"', r#"\""#))
} else { } else {
format!("'{path}'") format!("'{path}'")
};
} }
} else {
let contaminated = let filename_contaminated = !dir && path.contains(['\'', '"', ' ', '#', '(', ')']);
path.contains(['\'', '"', ' ', '#', '(', ')', '{', '}', '[', ']', '|', ';']); let dirname_contaminated = dir && path.contains(['\'', '"', ' ', '#']);
let maybe_flag = path.starts_with('-'); let maybe_flag = path.starts_with('-');
let maybe_variable = path.starts_with('$'); let maybe_variable = path.starts_with('$');
let maybe_number = path.parse::<f64>().is_ok(); let maybe_number = path.parse::<f64>().is_ok();
if contaminated || maybe_flag || maybe_variable || maybe_number { if filename_contaminated || dirname_contaminated || maybe_flag || maybe_variable || maybe_number
{
format!("`{path}`") format!("`{path}`")
} else { } else {
path path
} }
}
} }
pub struct AdjustView { pub struct AdjustView {
@ -326,12 +315,12 @@ pub struct AdjustView {
} }
pub fn adjust_if_intermediate( pub fn adjust_if_intermediate(
prefix: &str, prefix: &[u8],
working_set: &StateWorkingSet, working_set: &StateWorkingSet,
mut span: nu_protocol::Span, mut span: nu_protocol::Span,
) -> AdjustView { ) -> AdjustView {
let span_contents = String::from_utf8_lossy(working_set.get_span_contents(span)).to_string(); let span_contents = String::from_utf8_lossy(working_set.get_span_contents(span)).to_string();
let mut prefix = prefix.to_string(); let mut prefix = String::from_utf8_lossy(prefix).to_string();
// A difference of 1 because of the cursor's unicode code point in between. // A difference of 1 because of the cursor's unicode code point in between.
// Using .chars().count() because unicode and Windows. // Using .chars().count() because unicode and Windows.

View File

@ -18,12 +18,6 @@ pub enum MatchAlgorithm {
/// "git switch" is matched by "git sw" /// "git switch" is matched by "git sw"
Prefix, Prefix,
/// Only show suggestions which have a substring matching with the given input
///
/// Example:
/// "git checkout" is matched by "checkout"
Substring,
/// Only show suggestions which contain the input chars at any place /// Only show suggestions which contain the input chars at any place
/// ///
/// Example: /// Example:
@ -31,8 +25,8 @@ pub enum MatchAlgorithm {
Fuzzy, Fuzzy,
} }
pub struct NuMatcher<'a, T> { pub struct NuMatcher<T> {
options: &'a CompletionOptions, options: CompletionOptions,
needle: String, needle: String,
state: State<T>, state: State<T>,
} }
@ -42,10 +36,6 @@ enum State<T> {
/// Holds (haystack, item) /// Holds (haystack, item)
items: Vec<(String, T)>, items: Vec<(String, T)>,
}, },
Substring {
/// Holds (haystack, item)
items: Vec<(String, T)>,
},
Fuzzy { Fuzzy {
matcher: Matcher, matcher: Matcher,
atom: Atom, atom: Atom,
@ -55,11 +45,11 @@ enum State<T> {
} }
/// Filters and sorts suggestions /// Filters and sorts suggestions
impl<T> NuMatcher<'_, T> { impl<T> NuMatcher<T> {
/// # Arguments /// # Arguments
/// ///
/// * `needle` - The text to search for /// * `needle` - The text to search for
pub fn new(needle: impl AsRef<str>, options: &CompletionOptions) -> NuMatcher<T> { pub fn new(needle: impl AsRef<str>, options: CompletionOptions) -> NuMatcher<T> {
let needle = trim_quotes_str(needle.as_ref()); let needle = trim_quotes_str(needle.as_ref());
match options.match_algorithm { match options.match_algorithm {
MatchAlgorithm::Prefix => { MatchAlgorithm::Prefix => {
@ -74,18 +64,6 @@ impl<T> NuMatcher<'_, T> {
state: State::Prefix { items: Vec::new() }, state: State::Prefix { items: Vec::new() },
} }
} }
MatchAlgorithm::Substring => {
let lowercase_needle = if options.case_sensitive {
needle.to_owned()
} else {
needle.to_folded_case()
};
NuMatcher {
options,
needle: lowercase_needle,
state: State::Substring { items: Vec::new() },
}
}
MatchAlgorithm::Fuzzy => { MatchAlgorithm::Fuzzy => {
let atom = Atom::new( let atom = Atom::new(
needle, needle,
@ -124,21 +102,11 @@ impl<T> NuMatcher<'_, T> {
} else { } else {
Cow::Owned(haystack.to_folded_case()) Cow::Owned(haystack.to_folded_case())
}; };
let matches = haystack_folded.starts_with(self.needle.as_str()); let matches = if self.options.positional {
if matches { haystack_folded.starts_with(self.needle.as_str())
if let Some(item) = item {
items.push((haystack.to_string(), item));
}
}
matches
}
State::Substring { items } => {
let haystack_folded = if self.options.case_sensitive {
Cow::Borrowed(haystack)
} else { } else {
Cow::Owned(haystack.to_folded_case()) haystack_folded.contains(self.needle.as_str())
}; };
let matches = haystack_folded.contains(self.needle.as_str());
if matches { if matches {
if let Some(item) = item { if let Some(item) = item {
items.push((haystack.to_string(), item)); items.push((haystack.to_string(), item));
@ -180,7 +148,7 @@ impl<T> NuMatcher<'_, T> {
/// Get all the items that matched (sorted) /// Get all the items that matched (sorted)
pub fn results(self) -> Vec<T> { pub fn results(self) -> Vec<T> {
match self.state { match self.state {
State::Prefix { mut items, .. } | State::Substring { mut items, .. } => { State::Prefix { mut items, .. } => {
items.sort_by(|(haystack1, _), (haystack2, _)| { items.sort_by(|(haystack1, _), (haystack2, _)| {
let cmp_sensitive = haystack1.cmp(haystack2); let cmp_sensitive = haystack1.cmp(haystack2);
if self.options.case_sensitive { if self.options.case_sensitive {
@ -216,7 +184,7 @@ impl<T> NuMatcher<'_, T> {
} }
} }
impl NuMatcher<'_, SemanticSuggestion> { impl NuMatcher<SemanticSuggestion> {
pub fn add_semantic_suggestion(&mut self, sugg: SemanticSuggestion) -> bool { pub fn add_semantic_suggestion(&mut self, sugg: SemanticSuggestion) -> bool {
let value = sugg.suggestion.value.to_string(); let value = sugg.suggestion.value.to_string();
self.add(value, sugg) self.add(value, sugg)
@ -227,7 +195,6 @@ impl From<CompletionAlgorithm> for MatchAlgorithm {
fn from(value: CompletionAlgorithm) -> Self { fn from(value: CompletionAlgorithm) -> Self {
match value { match value {
CompletionAlgorithm::Prefix => MatchAlgorithm::Prefix, CompletionAlgorithm::Prefix => MatchAlgorithm::Prefix,
CompletionAlgorithm::Substring => MatchAlgorithm::Substring,
CompletionAlgorithm::Fuzzy => MatchAlgorithm::Fuzzy, CompletionAlgorithm::Fuzzy => MatchAlgorithm::Fuzzy,
} }
} }
@ -239,7 +206,6 @@ impl TryFrom<String> for MatchAlgorithm {
fn try_from(value: String) -> Result<Self, Self::Error> { fn try_from(value: String) -> Result<Self, Self::Error> {
match value.as_str() { match value.as_str() {
"prefix" => Ok(Self::Prefix), "prefix" => Ok(Self::Prefix),
"substring" => Ok(Self::Substring),
"fuzzy" => Ok(Self::Fuzzy), "fuzzy" => Ok(Self::Fuzzy),
_ => Err(InvalidMatchAlgorithm::Unknown), _ => Err(InvalidMatchAlgorithm::Unknown),
} }
@ -264,6 +230,7 @@ impl std::error::Error for InvalidMatchAlgorithm {}
#[derive(Clone)] #[derive(Clone)]
pub struct CompletionOptions { pub struct CompletionOptions {
pub case_sensitive: bool, pub case_sensitive: bool,
pub positional: bool,
pub match_algorithm: MatchAlgorithm, pub match_algorithm: MatchAlgorithm,
pub sort: CompletionSort, pub sort: CompletionSort,
} }
@ -272,6 +239,7 @@ impl Default for CompletionOptions {
fn default() -> Self { fn default() -> Self {
Self { Self {
case_sensitive: true, case_sensitive: true,
positional: true,
match_algorithm: MatchAlgorithm::Prefix, match_algorithm: MatchAlgorithm::Prefix,
sort: Default::default(), sort: Default::default(),
} }
@ -288,9 +256,6 @@ mod test {
#[case(MatchAlgorithm::Prefix, "example text", "", true)] #[case(MatchAlgorithm::Prefix, "example text", "", true)]
#[case(MatchAlgorithm::Prefix, "example text", "examp", true)] #[case(MatchAlgorithm::Prefix, "example text", "examp", true)]
#[case(MatchAlgorithm::Prefix, "example text", "text", false)] #[case(MatchAlgorithm::Prefix, "example text", "text", false)]
#[case(MatchAlgorithm::Substring, "example text", "", true)]
#[case(MatchAlgorithm::Substring, "example text", "text", true)]
#[case(MatchAlgorithm::Substring, "example text", "mplxt", false)]
#[case(MatchAlgorithm::Fuzzy, "example text", "", true)] #[case(MatchAlgorithm::Fuzzy, "example text", "", true)]
#[case(MatchAlgorithm::Fuzzy, "example text", "examp", true)] #[case(MatchAlgorithm::Fuzzy, "example text", "examp", true)]
#[case(MatchAlgorithm::Fuzzy, "example text", "ext", true)] #[case(MatchAlgorithm::Fuzzy, "example text", "ext", true)]
@ -306,7 +271,7 @@ mod test {
match_algorithm, match_algorithm,
..Default::default() ..Default::default()
}; };
let mut matcher = NuMatcher::new(needle, &options); let mut matcher = NuMatcher::new(needle, options);
matcher.add(haystack, haystack); matcher.add(haystack, haystack);
if should_match { if should_match {
assert_eq!(vec![haystack], matcher.results()); assert_eq!(vec![haystack], matcher.results());
@ -321,7 +286,7 @@ mod test {
match_algorithm: MatchAlgorithm::Fuzzy, match_algorithm: MatchAlgorithm::Fuzzy,
..Default::default() ..Default::default()
}; };
let mut matcher = NuMatcher::new("fob", &options); let mut matcher = NuMatcher::new("fob", options);
for item in ["foo/bar", "fob", "foo bar"] { for item in ["foo/bar", "fob", "foo bar"] {
matcher.add(item, item); matcher.add(item, item);
} }
@ -335,7 +300,7 @@ mod test {
match_algorithm: MatchAlgorithm::Fuzzy, match_algorithm: MatchAlgorithm::Fuzzy,
..Default::default() ..Default::default()
}; };
let mut matcher = NuMatcher::new("'love spaces' ", &options); let mut matcher = NuMatcher::new("'love spaces' ", options);
for item in [ for item in [
"'i love spaces'", "'i love spaces'",
"'i love spaces' so much", "'i love spaces' so much",

View File

@ -1,12 +1,11 @@
use crate::completions::{ use crate::completions::{
completer::map_value_completions, Completer, CompletionOptions, MatchAlgorithm, completer::map_value_completions, Completer, CompletionOptions, SemanticSuggestion,
SemanticSuggestion,
}; };
use nu_engine::eval_call; use nu_engine::eval_call;
use nu_protocol::{ use nu_protocol::{
ast::{Argument, Call, Expr, Expression}, ast::{Argument, Call, Expr, Expression},
debugger::WithoutDebug, debugger::WithoutDebug,
engine::{EngineState, Stack, StateWorkingSet}, engine::{Stack, StateWorkingSet},
DeclId, PipelineData, Span, Type, Value, DeclId, PipelineData, Span, Type, Value,
}; };
use std::collections::HashMap; use std::collections::HashMap;
@ -14,18 +13,18 @@ use std::collections::HashMap;
use super::completion_options::NuMatcher; use super::completion_options::NuMatcher;
pub struct CustomCompletion<T: Completer> { pub struct CustomCompletion<T: Completer> {
stack: Stack,
decl_id: DeclId, decl_id: DeclId,
line: String, line: String,
line_pos: usize,
fallback: T, fallback: T,
} }
impl<T: Completer> CustomCompletion<T> { impl<T: Completer> CustomCompletion<T> {
pub fn new(decl_id: DeclId, line: String, line_pos: usize, fallback: T) -> Self { pub fn new(stack: Stack, decl_id: DeclId, line: String, fallback: T) -> Self {
Self { Self {
stack,
decl_id, decl_id,
line, line,
line_pos,
fallback, fallback,
} }
} }
@ -36,17 +35,19 @@ impl<T: Completer> Completer for CustomCompletion<T> {
&mut self, &mut self,
working_set: &StateWorkingSet, working_set: &StateWorkingSet,
stack: &Stack, stack: &Stack,
prefix: impl AsRef<str>, prefix: &[u8],
span: Span, span: Span,
offset: usize, offset: usize,
pos: usize,
orig_options: &CompletionOptions, orig_options: &CompletionOptions,
) -> Vec<SemanticSuggestion> { ) -> Vec<SemanticSuggestion> {
// Line position
let line_pos = pos - offset;
// Call custom declaration // Call custom declaration
let mut stack_mut = stack.clone(); let result = eval_call::<WithoutDebug>(
let mut eval = |engine_state: &EngineState| { working_set.permanent_state,
eval_call::<WithoutDebug>( &mut self.stack,
engine_state,
&mut stack_mut,
&Call { &Call {
decl_id: self.decl_id, decl_id: self.decl_id,
head: span, head: span,
@ -57,7 +58,7 @@ impl<T: Completer> Completer for CustomCompletion<T> {
Type::String, Type::String,
)), )),
Argument::Positional(Expression::new_unknown( Argument::Positional(Expression::new_unknown(
Expr::Int(self.line_pos as i64), Expr::Int(line_pos as i64),
Span::unknown(), Span::unknown(),
Type::Int, Type::Int,
)), )),
@ -65,15 +66,7 @@ impl<T: Completer> Completer for CustomCompletion<T> {
parser_info: HashMap::new(), parser_info: HashMap::new(),
}, },
PipelineData::empty(), PipelineData::empty(),
) );
};
let result = if self.decl_id.get() < working_set.permanent_state.num_decls() {
eval(working_set.permanent_state)
} else {
let mut engine_state = working_set.permanent_state.clone();
let _ = engine_state.merge_delta(working_set.delta.clone());
eval(&engine_state)
};
let mut completion_options = orig_options.clone(); let mut completion_options = orig_options.clone();
let mut should_sort = true; let mut should_sort = true;
@ -103,10 +96,10 @@ impl<T: Completer> Completer for CustomCompletion<T> {
{ {
completion_options.case_sensitive = case_sensitive; completion_options.case_sensitive = case_sensitive;
} }
let positional = if let Some(positional) =
options.get("positional").and_then(|val| val.as_bool().ok()); options.get("positional").and_then(|val| val.as_bool().ok())
if positional.is_some() { {
log::warn!("Use of the positional option is deprecated. Use the substring match algorithm instead."); completion_options.positional = positional;
} }
if let Some(algorithm) = options if let Some(algorithm) = options
.get("completion_algorithm") .get("completion_algorithm")
@ -114,11 +107,6 @@ impl<T: Completer> Completer for CustomCompletion<T> {
.and_then(|option| option.try_into().ok()) .and_then(|option| option.try_into().ok())
{ {
completion_options.match_algorithm = algorithm; completion_options.match_algorithm = algorithm;
if let Some(false) = positional {
if completion_options.match_algorithm == MatchAlgorithm::Prefix {
completion_options.match_algorithm = MatchAlgorithm::Substring
}
}
} }
} }
@ -132,6 +120,7 @@ impl<T: Completer> Completer for CustomCompletion<T> {
prefix, prefix,
span, span,
offset, offset,
pos,
orig_options, orig_options,
); );
} }
@ -149,7 +138,7 @@ impl<T: Completer> Completer for CustomCompletion<T> {
} }
}; };
let mut matcher = NuMatcher::new(prefix, &completion_options); let mut matcher = NuMatcher::new(String::from_utf8_lossy(prefix), completion_options);
if should_sort { if should_sort {
for sugg in suggestions { for sugg in suggestions {

View File

@ -9,22 +9,29 @@ use nu_protocol::{
use reedline::Suggestion; use reedline::Suggestion;
use std::path::Path; use std::path::Path;
use super::{completion_common::FileSuggestion, SemanticSuggestion, SuggestionKind}; use super::{completion_common::FileSuggestion, SemanticSuggestion};
pub struct DirectoryCompletion; #[derive(Clone, Default)]
pub struct DirectoryCompletion {}
impl DirectoryCompletion {
pub fn new() -> Self {
Self::default()
}
}
impl Completer for DirectoryCompletion { impl Completer for DirectoryCompletion {
fn fetch( fn fetch(
&mut self, &mut self,
working_set: &StateWorkingSet, working_set: &StateWorkingSet,
stack: &Stack, stack: &Stack,
prefix: impl AsRef<str>, prefix: &[u8],
span: Span, span: Span,
offset: usize, offset: usize,
_pos: usize,
options: &CompletionOptions, options: &CompletionOptions,
) -> Vec<SemanticSuggestion> { ) -> Vec<SemanticSuggestion> {
let AdjustView { prefix, span, .. } = let AdjustView { prefix, span, .. } = adjust_if_intermediate(prefix, working_set, span);
adjust_if_intermediate(prefix.as_ref(), working_set, span);
// Filter only the folders // Filter only the folders
#[allow(deprecated)] #[allow(deprecated)]
@ -47,7 +54,8 @@ impl Completer for DirectoryCompletion {
}, },
..Suggestion::default() ..Suggestion::default()
}, },
kind: Some(SuggestionKind::Directory), // TODO????
kind: None,
}) })
.collect(); .collect();

View File

@ -1,22 +1,21 @@
use crate::completions::{ use crate::completions::{file_path_completion, Completer, CompletionOptions};
completion_common::{surround_remove, FileSuggestion},
completion_options::NuMatcher,
file_path_completion, Completer, CompletionOptions, SemanticSuggestion, SuggestionKind,
};
use nu_path::expand_tilde; use nu_path::expand_tilde;
use nu_protocol::{ use nu_protocol::{
engine::{Stack, StateWorkingSet, VirtualPath}, engine::{Stack, StateWorkingSet},
Span, Span,
}; };
use reedline::Suggestion; use reedline::Suggestion;
use std::{ use std::path::{is_separator, PathBuf, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR};
collections::HashSet,
path::{is_separator, PathBuf, MAIN_SEPARATOR_STR},
};
pub struct DotNuCompletion { use super::{SemanticSuggestion, SuggestionKind};
/// e.g. use std/a<tab>
pub std_virtual_path: bool, #[derive(Clone, Default)]
pub struct DotNuCompletion {}
impl DotNuCompletion {
pub fn new() -> Self {
Self::default()
}
} }
impl Completer for DotNuCompletion { impl Completer for DotNuCompletion {
@ -24,90 +23,62 @@ impl Completer for DotNuCompletion {
&mut self, &mut self,
working_set: &StateWorkingSet, working_set: &StateWorkingSet,
stack: &Stack, stack: &Stack,
prefix: impl AsRef<str>, prefix: &[u8],
span: Span, span: Span,
offset: usize, offset: usize,
_pos: usize,
options: &CompletionOptions, options: &CompletionOptions,
) -> Vec<SemanticSuggestion> { ) -> Vec<SemanticSuggestion> {
let prefix_str = prefix.as_ref(); let prefix_str = String::from_utf8_lossy(prefix);
let start_with_backquote = prefix_str.starts_with('`'); let start_with_backquote = prefix_str.starts_with('`');
let end_with_backquote = prefix_str.ends_with('`'); let end_with_backquote = prefix_str.ends_with('`');
let prefix_str = prefix_str.replace('`', ""); let prefix_str = prefix_str.replace('`', "");
// e.g. `./`, `..\`, `/`
let not_lib_dirs = prefix_str
.chars()
.find(|c| *c != '.')
.is_some_and(is_separator);
let mut search_dirs: Vec<PathBuf> = vec![]; let mut search_dirs: Vec<PathBuf> = vec![];
let (base, partial) = if let Some((parent, remain)) = prefix_str.rsplit_once(is_separator) { // If prefix_str is only a word we want to search in the current dir
// If prefix_str is only a word we want to search in the current dir. let (base, partial) = prefix_str
// "/xx" should be split to "/" and "xx". .rsplit_once(is_separator)
if parent.is_empty() { .unwrap_or((".", &prefix_str));
(MAIN_SEPARATOR_STR, remain)
} else {
(parent, remain)
}
} else {
(".", prefix_str.as_str())
};
let base_dir = base.replace(is_separator, MAIN_SEPARATOR_STR); let base_dir = base.replace(is_separator, MAIN_SEPARATOR_STR);
// Fetch the lib dirs // Fetch the lib dirs
// NOTE: 2 ways to setup `NU_LIB_DIRS` let lib_dirs: Vec<PathBuf> = working_set
// 1. `const NU_LIB_DIRS = [paths]`, equal to `nu -I paths`
// 2. `$env.NU_LIB_DIRS = [paths]`
let const_lib_dirs = working_set
.find_variable(b"$NU_LIB_DIRS") .find_variable(b"$NU_LIB_DIRS")
.and_then(|vid| working_set.get_variable(vid).const_val.as_ref()); .and_then(|vid| working_set.get_variable(vid).const_val.as_ref())
let env_lib_dirs = working_set.get_env_var("NU_LIB_DIRS"); .or(working_set.get_env_var("NU_LIB_DIRS"))
let lib_dirs: HashSet<PathBuf> = [const_lib_dirs, env_lib_dirs] .map(|lib_dirs| {
.into_iter()
.flatten()
.flat_map(|lib_dirs| {
lib_dirs lib_dirs
.as_list() .as_list()
.into_iter() .into_iter()
.flat_map(|it| it.iter().filter_map(|x| x.to_path().ok())) .flat_map(|it| it.iter().filter_map(|x| x.to_path().ok()))
.map(expand_tilde) .map(expand_tilde)
.collect()
}) })
.collect(); .unwrap_or_default();
// Check if the base_dir is a folder // Check if the base_dir is a folder
// rsplit_once removes the separator
let cwd = working_set.permanent_state.cwd(None); let cwd = working_set.permanent_state.cwd(None);
if base_dir != "." { if base_dir != "." {
let expanded_base_dir = expand_tilde(&base_dir); // Search in base_dir as well as lib_dirs
let is_base_dir_relative = expanded_base_dir.is_relative();
// Search in base_dir as well as lib_dirs.
// After expanded, base_dir can be a relative path or absolute path.
// If relative, we join "current working dir" with it to get subdirectory and add to search_dirs.
// If absolute, we add it to search_dirs.
if let Ok(mut cwd) = cwd { if let Ok(mut cwd) = cwd {
if is_base_dir_relative {
cwd.push(&base_dir); cwd.push(&base_dir);
search_dirs.push(cwd.into_std_path_buf()); search_dirs.push(cwd.into_std_path_buf());
} else {
search_dirs.push(expanded_base_dir);
} }
}
if !not_lib_dirs {
search_dirs.extend(lib_dirs.into_iter().map(|mut dir| { search_dirs.extend(lib_dirs.into_iter().map(|mut dir| {
dir.push(&base_dir); dir.push(&base_dir);
dir dir
})); }));
}
} else { } else {
if let Ok(cwd) = cwd { if let Ok(cwd) = cwd {
search_dirs.push(cwd.into_std_path_buf()); search_dirs.push(cwd.into_std_path_buf());
} }
if !not_lib_dirs {
search_dirs.extend(lib_dirs); search_dirs.extend(lib_dirs);
} }
}
// Fetch the files filtering the ones that ends with .nu // Fetch the files filtering the ones that ends with .nu
// and transform them into suggestions // and transform them into suggestions
let mut completions = file_path_completion( let completions = file_path_completion(
span, span,
partial, partial,
&search_dirs &search_dirs
@ -118,67 +89,22 @@ impl Completer for DotNuCompletion {
working_set.permanent_state, working_set.permanent_state,
stack, stack,
); );
if self.std_virtual_path {
let mut matcher = NuMatcher::new(partial, options);
let base_dir = surround_remove(&base_dir);
if base_dir == "." {
let surround_prefix = partial
.chars()
.take_while(|c| "`'\"".contains(*c))
.collect::<String>();
for path in ["std", "std-rfc"] {
let path = format!("{}{}", surround_prefix, path);
matcher.add(
path.clone(),
FileSuggestion {
span,
path,
style: None,
is_dir: true,
},
);
}
} else if let Some(VirtualPath::Dir(sub_paths)) =
working_set.find_virtual_path(&base_dir)
{
for sub_vp_id in sub_paths {
let (path, sub_vp) = working_set.get_virtual_path(*sub_vp_id);
let path = path
.strip_prefix(&format!("{}/", base_dir))
.unwrap_or(path)
.to_string();
matcher.add(
path.clone(),
FileSuggestion {
path,
span,
style: None,
is_dir: matches!(sub_vp, VirtualPath::Dir(_)),
},
);
}
}
completions.extend(matcher.results());
}
completions completions
.into_iter() .into_iter()
// Different base dir, so we list the .nu files or folders // Different base dir, so we list the .nu files or folders
.filter(|it| { .filter(|it| {
// for paths with spaces in them // for paths with spaces in them
let path = it.path.trim_end_matches('`'); let path = it.path.trim_end_matches('`');
path.ends_with(".nu") || it.is_dir path.ends_with(".nu") || path.ends_with(SEP)
}) })
.map(|x| { .map(|x| {
let append_whitespace = !x.is_dir && (!start_with_backquote || end_with_backquote); let append_whitespace =
x.path.ends_with(".nu") && (!start_with_backquote || end_with_backquote);
// Re-calculate the span to replace // Re-calculate the span to replace
let mut span_offset = 0; let mut span_offset = 0;
let mut value = x.path.to_string(); let mut value = x.path.to_string();
// Complete only the last path component // Complete only the last path component
if base_dir == MAIN_SEPARATOR_STR { if base_dir != "." {
span_offset = base_dir.len()
} else if base_dir != "." {
span_offset = base_dir.len() + 1 span_offset = base_dir.len() + 1
} }
// Retain only one '`' // Retain only one '`'

View File

@ -1,112 +0,0 @@
use crate::completions::{
completion_common::surround_remove, completion_options::NuMatcher, Completer,
CompletionOptions, SemanticSuggestion, SuggestionKind,
};
use nu_protocol::{
engine::{Stack, StateWorkingSet},
ModuleId, Span,
};
use reedline::Suggestion;
pub struct ExportableCompletion<'a> {
pub module_id: ModuleId,
pub temp_working_set: Option<StateWorkingSet<'a>>,
}
/// If name contains space, wrap it in quotes
fn wrapped_name(name: String) -> String {
if !name.contains(' ') {
return name;
}
if name.contains('\'') {
format!("\"{}\"", name.replace('"', r#"\""#))
} else {
format!("'{name}'")
}
}
impl Completer for ExportableCompletion<'_> {
fn fetch(
&mut self,
working_set: &StateWorkingSet,
_stack: &Stack,
prefix: impl AsRef<str>,
span: Span,
offset: usize,
options: &CompletionOptions,
) -> Vec<SemanticSuggestion> {
let mut matcher = NuMatcher::<()>::new(surround_remove(prefix.as_ref()), options);
let mut results = Vec::new();
let span = reedline::Span {
start: span.start - offset,
end: span.end - offset,
};
// TODO: use matcher.add_lazy to lazy evaluate an item if it matches the prefix
let mut add_suggestion = |value: String,
description: Option<String>,
extra: Option<Vec<String>>,
kind: SuggestionKind| {
results.push(SemanticSuggestion {
suggestion: Suggestion {
value,
span,
description,
extra,
..Suggestion::default()
},
kind: Some(kind),
});
};
let working_set = self.temp_working_set.as_ref().unwrap_or(working_set);
let module = working_set.get_module(self.module_id);
for (name, decl_id) in &module.decls {
let name = String::from_utf8_lossy(name).to_string();
if matcher.matches(&name) {
let cmd = working_set.get_decl(*decl_id);
add_suggestion(
wrapped_name(name),
Some(cmd.description().to_string()),
None,
// `None` here avoids arguments being expanded by snippet edit style for lsp
SuggestionKind::Command(cmd.command_type(), None),
);
}
}
for (name, module_id) in &module.submodules {
let name = String::from_utf8_lossy(name).to_string();
if matcher.matches(&name) {
let comments = working_set.get_module_comments(*module_id).map(|spans| {
spans
.iter()
.map(|sp| {
String::from_utf8_lossy(working_set.get_span_contents(*sp)).into()
})
.collect::<Vec<String>>()
});
add_suggestion(
wrapped_name(name),
Some("Submodule".into()),
comments,
SuggestionKind::Module,
);
}
}
for (name, var_id) in &module.constants {
let name = String::from_utf8_lossy(name).to_string();
if matcher.matches(&name) {
let var = working_set.get_variable(*var_id);
add_suggestion(
wrapped_name(name),
var.const_val
.as_ref()
.and_then(|v| v.clone().coerce_into_string().ok()),
None,
SuggestionKind::Variable,
);
}
}
results
}
}

View File

@ -9,25 +9,33 @@ use nu_protocol::{
use reedline::Suggestion; use reedline::Suggestion;
use std::path::Path; use std::path::Path;
use super::{completion_common::FileSuggestion, SemanticSuggestion, SuggestionKind}; use super::{completion_common::FileSuggestion, SemanticSuggestion};
pub struct FileCompletion; #[derive(Clone, Default)]
pub struct FileCompletion {}
impl FileCompletion {
pub fn new() -> Self {
Self::default()
}
}
impl Completer for FileCompletion { impl Completer for FileCompletion {
fn fetch( fn fetch(
&mut self, &mut self,
working_set: &StateWorkingSet, working_set: &StateWorkingSet,
stack: &Stack, stack: &Stack,
prefix: impl AsRef<str>, prefix: &[u8],
span: Span, span: Span,
offset: usize, offset: usize,
_pos: usize,
options: &CompletionOptions, options: &CompletionOptions,
) -> Vec<SemanticSuggestion> { ) -> Vec<SemanticSuggestion> {
let AdjustView { let AdjustView {
prefix, prefix,
span, span,
readjusted, readjusted,
} = adjust_if_intermediate(prefix.as_ref(), working_set, span); } = adjust_if_intermediate(prefix, working_set, span);
#[allow(deprecated)] #[allow(deprecated)]
let items: Vec<_> = complete_item( let items: Vec<_> = complete_item(
@ -50,11 +58,8 @@ impl Completer for FileCompletion {
}, },
..Suggestion::default() ..Suggestion::default()
}, },
kind: Some(if x.is_dir { // TODO????
SuggestionKind::Directory kind: None,
} else {
SuggestionKind::File
}),
}) })
.collect(); .collect();

View File

@ -1,15 +1,22 @@
use crate::completions::{ use crate::completions::{completion_options::NuMatcher, Completer, CompletionOptions};
completion_options::NuMatcher, Completer, CompletionOptions, SemanticSuggestion, SuggestionKind,
};
use nu_protocol::{ use nu_protocol::{
ast::{Expr, Expression},
engine::{Stack, StateWorkingSet}, engine::{Stack, StateWorkingSet},
DeclId, Span, Span,
}; };
use reedline::Suggestion; use reedline::Suggestion;
use super::SemanticSuggestion;
#[derive(Clone)] #[derive(Clone)]
pub struct FlagCompletion { pub struct FlagCompletion {
pub decl_id: DeclId, expression: Expression,
}
impl FlagCompletion {
pub fn new(expression: Expression) -> Self {
Self { expression }
}
} }
impl Completer for FlagCompletion { impl Completer for FlagCompletion {
@ -17,17 +24,30 @@ impl Completer for FlagCompletion {
&mut self, &mut self,
working_set: &StateWorkingSet, working_set: &StateWorkingSet,
_stack: &Stack, _stack: &Stack,
prefix: impl AsRef<str>, prefix: &[u8],
span: Span, span: Span,
offset: usize, offset: usize,
_pos: usize,
options: &CompletionOptions, options: &CompletionOptions,
) -> Vec<SemanticSuggestion> { ) -> Vec<SemanticSuggestion> {
let mut matcher = NuMatcher::new(prefix, options); // Check if it's a flag
let mut add_suggestion = |value: String, description: String| { if let Expr::Call(call) = &self.expression.expr {
let decl = working_set.get_decl(call.decl_id);
let sig = decl.signature();
let mut matcher = NuMatcher::new(String::from_utf8_lossy(prefix), options.clone());
for named in &sig.named {
let flag_desc = &named.desc;
if let Some(short) = named.short {
let mut named = vec![0; short.len_utf8()];
short.encode_utf8(&mut named);
named.insert(0, b'-');
matcher.add_semantic_suggestion(SemanticSuggestion { matcher.add_semantic_suggestion(SemanticSuggestion {
suggestion: Suggestion { suggestion: Suggestion {
value, value: String::from_utf8_lossy(&named).to_string(),
description: Some(description), description: Some(flag_desc.to_string()),
span: reedline::Span { span: reedline::Span {
start: span.start - offset, start: span.start - offset,
end: span.end - offset, end: span.end - offset,
@ -35,24 +55,38 @@ impl Completer for FlagCompletion {
append_whitespace: true, append_whitespace: true,
..Suggestion::default() ..Suggestion::default()
}, },
kind: Some(SuggestionKind::Flag), // TODO????
kind: None,
}); });
};
let decl = working_set.get_decl(self.decl_id);
let sig = decl.signature();
for named in &sig.named {
if let Some(short) = named.short {
let mut name = String::from("-");
name.push(short);
add_suggestion(name, named.desc.clone());
} }
if named.long.is_empty() { if named.long.is_empty() {
continue; continue;
} }
add_suggestion(format!("--{}", named.long), named.desc.clone());
let mut named = named.long.as_bytes().to_vec();
named.insert(0, b'-');
named.insert(0, b'-');
matcher.add_semantic_suggestion(SemanticSuggestion {
suggestion: Suggestion {
value: String::from_utf8_lossy(&named).to_string(),
description: Some(flag_desc.to_string()),
span: reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
append_whitespace: true,
..Suggestion::default()
},
// TODO????
kind: None,
});
} }
matcher.results()
return matcher.results();
}
vec![]
} }
} }

View File

@ -1,6 +1,4 @@
mod attribute_completions;
mod base; mod base;
mod cell_path_completions;
mod command_completions; mod command_completions;
mod completer; mod completer;
mod completion_common; mod completion_common;
@ -8,22 +6,18 @@ mod completion_options;
mod custom_completions; mod custom_completions;
mod directory_completions; mod directory_completions;
mod dotnu_completions; mod dotnu_completions;
mod exportable_completions;
mod file_completions; mod file_completions;
mod flag_completions; mod flag_completions;
mod operator_completions; mod operator_completions;
mod variable_completions; mod variable_completions;
pub use attribute_completions::{AttributableCompletion, AttributeCompletion};
pub use base::{Completer, SemanticSuggestion, SuggestionKind}; pub use base::{Completer, SemanticSuggestion, SuggestionKind};
pub use cell_path_completions::CellPathCompletion;
pub use command_completions::CommandCompletion; pub use command_completions::CommandCompletion;
pub use completer::NuCompleter; pub use completer::NuCompleter;
pub use completion_options::{CompletionOptions, MatchAlgorithm}; pub use completion_options::{CompletionOptions, MatchAlgorithm};
pub use custom_completions::CustomCompletion; pub use custom_completions::CustomCompletion;
pub use directory_completions::DirectoryCompletion; pub use directory_completions::DirectoryCompletion;
pub use dotnu_completions::DotNuCompletion; pub use dotnu_completions::DotNuCompletion;
pub use exportable_completions::ExportableCompletion;
pub use file_completions::{file_path_completion, FileCompletion}; pub use file_completions::{file_path_completion, FileCompletion};
pub use flag_completions::FlagCompletion; pub use flag_completions::FlagCompletion;
pub use operator_completions::OperatorCompletion; pub use operator_completions::OperatorCompletion;

View File

@ -2,276 +2,169 @@ use crate::completions::{
completion_options::NuMatcher, Completer, CompletionOptions, SemanticSuggestion, SuggestionKind, completion_options::NuMatcher, Completer, CompletionOptions, SemanticSuggestion, SuggestionKind,
}; };
use nu_protocol::{ use nu_protocol::{
ast::{self, Comparison, Expr, Expression}, ast::{Expr, Expression},
engine::{Stack, StateWorkingSet}, engine::{Stack, StateWorkingSet},
Span, Type, Value, ENV_VARIABLE_ID, Span, Type,
}; };
use reedline::Suggestion; use reedline::Suggestion;
use strum::{EnumMessage, IntoEnumIterator};
use super::cell_path_completions::eval_cell_path;
#[derive(Clone)] #[derive(Clone)]
pub struct OperatorCompletion<'a> { pub struct OperatorCompletion {
pub left_hand_side: &'a Expression, previous_expr: Expression,
} }
struct OperatorItem { impl OperatorCompletion {
pub symbols: String, pub fn new(previous_expr: Expression) -> Self {
pub description: String, OperatorCompletion { previous_expr }
}
fn operator_to_item<T: EnumMessage + AsRef<str>>(op: T) -> OperatorItem {
OperatorItem {
symbols: op.as_ref().into(),
description: op.get_message().unwrap_or_default().into(),
} }
} }
fn common_comparison_ops() -> Vec<OperatorItem> { impl Completer for OperatorCompletion {
vec![
operator_to_item(Comparison::In),
operator_to_item(Comparison::NotIn),
operator_to_item(Comparison::Equal),
operator_to_item(Comparison::NotEqual),
]
}
fn all_ops_for_immutable() -> Vec<OperatorItem> {
ast::Comparison::iter()
.map(operator_to_item)
.chain(ast::Math::iter().map(operator_to_item))
.chain(ast::Boolean::iter().map(operator_to_item))
.chain(ast::Bits::iter().map(operator_to_item))
.collect()
}
fn collection_comparison_ops() -> Vec<OperatorItem> {
let mut ops = common_comparison_ops();
ops.push(operator_to_item(Comparison::Has));
ops.push(operator_to_item(Comparison::NotHas));
ops
}
fn number_comparison_ops() -> Vec<OperatorItem> {
Comparison::iter()
.filter(|op| {
!matches!(
op,
Comparison::RegexMatch
| Comparison::NotRegexMatch
| Comparison::StartsWith
| Comparison::EndsWith
| Comparison::Has
| Comparison::NotHas
)
})
.map(operator_to_item)
.collect()
}
fn math_ops() -> Vec<OperatorItem> {
ast::Math::iter()
.filter(|op| !matches!(op, ast::Math::Concatenate | ast::Math::Pow))
.map(operator_to_item)
.collect()
}
fn bit_ops() -> Vec<OperatorItem> {
ast::Bits::iter().map(operator_to_item).collect()
}
fn all_assignment_ops() -> Vec<OperatorItem> {
ast::Assignment::iter().map(operator_to_item).collect()
}
fn numeric_assignment_ops() -> Vec<OperatorItem> {
ast::Assignment::iter()
.filter(|op| !matches!(op, ast::Assignment::ConcatenateAssign))
.map(operator_to_item)
.collect()
}
fn concat_assignment_ops() -> Vec<OperatorItem> {
vec![
operator_to_item(ast::Assignment::Assign),
operator_to_item(ast::Assignment::ConcatenateAssign),
]
}
fn valid_int_ops() -> Vec<OperatorItem> {
let mut ops = valid_float_ops();
ops.extend(bit_ops());
ops
}
fn valid_float_ops() -> Vec<OperatorItem> {
let mut ops = valid_value_with_unit_ops();
ops.push(operator_to_item(ast::Math::Pow));
ops
}
fn valid_string_ops() -> Vec<OperatorItem> {
let mut ops: Vec<OperatorItem> = Comparison::iter().map(operator_to_item).collect();
ops.push(operator_to_item(ast::Math::Concatenate));
ops.push(OperatorItem {
symbols: "like".into(),
description: Comparison::RegexMatch
.get_message()
.unwrap_or_default()
.into(),
});
ops.push(OperatorItem {
symbols: "not-like".into(),
description: Comparison::NotRegexMatch
.get_message()
.unwrap_or_default()
.into(),
});
ops
}
fn valid_list_ops() -> Vec<OperatorItem> {
let mut ops = collection_comparison_ops();
ops.push(operator_to_item(ast::Math::Concatenate));
ops
}
fn valid_binary_ops() -> Vec<OperatorItem> {
let mut ops = number_comparison_ops();
ops.extend(bit_ops());
ops.push(operator_to_item(ast::Math::Concatenate));
ops
}
fn valid_bool_ops() -> Vec<OperatorItem> {
let mut ops: Vec<OperatorItem> = ast::Boolean::iter().map(operator_to_item).collect();
ops.extend(common_comparison_ops());
ops
}
fn valid_value_with_unit_ops() -> Vec<OperatorItem> {
let mut ops = number_comparison_ops();
ops.extend(math_ops());
ops
}
fn ops_by_value(value: &Value, mutable: bool) -> Vec<OperatorItem> {
let mut ops = match value {
Value::Int { .. } => valid_int_ops(),
Value::Float { .. } => valid_float_ops(),
Value::String { .. } => valid_string_ops(),
Value::Binary { .. } => valid_binary_ops(),
Value::Bool { .. } => valid_bool_ops(),
Value::Date { .. } => number_comparison_ops(),
Value::Filesize { .. } | Value::Duration { .. } => valid_value_with_unit_ops(),
Value::Range { .. } | Value::Record { .. } => collection_comparison_ops(),
Value::List { .. } => valid_list_ops(),
_ => all_ops_for_immutable(),
};
if mutable {
ops.extend(match value {
Value::Int { .. }
| Value::Float { .. }
| Value::Filesize { .. }
| Value::Duration { .. } => numeric_assignment_ops(),
Value::String { .. } | Value::Binary { .. } | Value::List { .. } => {
concat_assignment_ops()
}
Value::Bool { .. }
| Value::Date { .. }
| Value::Range { .. }
| Value::Record { .. } => vec![operator_to_item(ast::Assignment::Assign)],
_ => all_assignment_ops(),
})
}
ops
}
fn is_expression_mutable(expr: &Expr, working_set: &StateWorkingSet) -> bool {
let Expr::FullCellPath(path) = expr else {
return false;
};
let Expr::Var(id) = path.head.expr else {
return false;
};
if id == ENV_VARIABLE_ID {
return true;
}
let var = working_set.get_variable(id);
var.mutable
}
impl Completer for OperatorCompletion<'_> {
fn fetch( fn fetch(
&mut self, &mut self,
working_set: &StateWorkingSet, working_set: &StateWorkingSet,
stack: &Stack, _stack: &Stack,
prefix: impl AsRef<str>, _prefix: &[u8],
span: Span, span: Span,
offset: usize, offset: usize,
_pos: usize,
options: &CompletionOptions, options: &CompletionOptions,
) -> Vec<SemanticSuggestion> { ) -> Vec<SemanticSuggestion> {
let mut needs_assignment_ops = true; //Check if int, float, or string
// Complete according expression type let partial = std::str::from_utf8(working_set.get_span_contents(span)).unwrap_or("");
// TODO: type inference on self.left_hand_side to get more accurate completions let op = match &self.previous_expr.expr {
let mut possible_operations: Vec<OperatorItem> = match &self.left_hand_side.ty { Expr::BinaryOp(x, _, _) => &x.expr,
Type::Int | Type::Number => valid_int_ops(), _ => {
Type::Float => valid_float_ops(),
Type::String => valid_string_ops(),
Type::Binary => valid_binary_ops(),
Type::Bool => valid_bool_ops(),
Type::Date => number_comparison_ops(),
Type::Filesize | Type::Duration => valid_value_with_unit_ops(),
Type::Record(_) | Type::Range => collection_comparison_ops(),
Type::List(_) | Type::Table(_) => valid_list_ops(),
// Unknown type, resort to evaluated values
Type::Any => match &self.left_hand_side.expr {
Expr::FullCellPath(path) => {
// for `$ <tab>`
if matches!(path.head.expr, Expr::Garbage) {
return vec![]; return vec![];
} }
let value =
eval_cell_path(working_set, stack, &path.head, &path.tail, path.head.span)
.unwrap_or_default();
let mutable = is_expression_mutable(&self.left_hand_side.expr, working_set);
// to avoid duplication
needs_assignment_ops = false;
ops_by_value(&value, mutable)
}
_ => all_ops_for_immutable(),
},
_ => common_comparison_ops(),
}; };
// If the left hand side is a variable, add assignment operators if mutable let possible_operations = match op {
if needs_assignment_ops && is_expression_mutable(&self.left_hand_side.expr, working_set) { Expr::Int(_) => vec![
possible_operations.extend(match &self.left_hand_side.ty { ("+", "Add (Plus)"),
Type::Int | Type::Float | Type::Number => numeric_assignment_ops(), ("-", "Subtract (Minus)"),
Type::Filesize | Type::Duration => numeric_assignment_ops(), ("*", "Multiply"),
Type::String | Type::Binary | Type::List(_) => concat_assignment_ops(), ("/", "Divide"),
Type::Any => all_assignment_ops(), ("==", "Equal to"),
_ => vec![operator_to_item(ast::Assignment::Assign)], ("!=", "Not equal to"),
}); ("//", "Floor division"),
} ("<", "Less than"),
(">", "Greater than"),
("<=", "Less than or equal to"),
(">=", "Greater than or equal to"),
("mod", "Floor division remainder (Modulo)"),
("**", "Power of"),
("bit-or", "Bitwise OR"),
("bit-xor", "Bitwise exclusive OR"),
("bit-and", "Bitwise AND"),
("bit-shl", "Bitwise shift left"),
("bit-shr", "Bitwise shift right"),
("in", "Is a member of (doesn't use regex)"),
("not-in", "Is not a member of (doesn't use regex)"),
],
Expr::String(_) => vec![
("=~", "Contains regex match"),
("like", "Contains regex match"),
("!~", "Does not contain regex match"),
("not-like", "Does not contain regex match"),
(
"++",
"Concatenates two lists, two strings, or two binary values",
),
("in", "Is a member of (doesn't use regex)"),
("not-in", "Is not a member of (doesn't use regex)"),
("starts-with", "Starts with"),
("ends-with", "Ends with"),
],
Expr::Float(_) => vec![
("+", "Add (Plus)"),
("-", "Subtract (Minus)"),
("*", "Multiply"),
("/", "Divide"),
("==", "Equal to"),
("!=", "Not equal to"),
("//", "Floor division"),
("<", "Less than"),
(">", "Greater than"),
("<=", "Less than or equal to"),
(">=", "Greater than or equal to"),
("mod", "Floor division remainder (Modulo)"),
("**", "Power of"),
("in", "Is a member of (doesn't use regex)"),
("not-in", "Is not a member of (doesn't use regex)"),
],
Expr::Bool(_) => vec![
(
"and",
"Both values are true (short-circuits when first value is false)",
),
(
"or",
"Either value is true (short-circuits when first value is true)",
),
("xor", "One value is true and the other is false"),
("not", "Negates a value or expression"),
("in", "Is a member of (doesn't use regex)"),
("not-in", "Is not a member of (doesn't use regex)"),
],
Expr::FullCellPath(path) => match path.head.expr {
Expr::List(_) => vec![
(
"++",
"Concatenates two lists, two strings, or two binary values",
),
("has", "Contains a value of (doesn't use regex)"),
("not-has", "Does not contain a value of (doesn't use regex)"),
],
Expr::Var(id) => get_variable_completions(id, working_set),
_ => vec![],
},
_ => vec![],
};
let mut matcher = NuMatcher::new(prefix, options); let mut matcher = NuMatcher::new(partial, options.clone());
for OperatorItem { for (symbol, desc) in possible_operations.into_iter() {
symbols,
description,
} in possible_operations
{
matcher.add_semantic_suggestion(SemanticSuggestion { matcher.add_semantic_suggestion(SemanticSuggestion {
suggestion: Suggestion { suggestion: Suggestion {
value: symbols.to_owned(), value: symbol.to_string(),
description: Some(description.to_owned()), description: Some(desc.to_string()),
span: reedline::Span::new(span.start - offset, span.end - offset), span: reedline::Span::new(span.start - offset, span.end - offset),
append_whitespace: true, append_whitespace: true,
..Suggestion::default() ..Suggestion::default()
}, },
kind: Some(SuggestionKind::Operator), kind: Some(SuggestionKind::Command(
nu_protocol::engine::CommandType::Builtin,
)),
}); });
} }
matcher.results() matcher.results()
} }
} }
pub fn get_variable_completions<'a>(
id: nu_protocol::Id<nu_protocol::marker::Var>,
working_set: &StateWorkingSet,
) -> Vec<(&'a str, &'a str)> {
let var = working_set.get_variable(id);
if !var.mutable {
return vec![];
}
match var.ty {
Type::List(_) | Type::String | Type::Binary => vec![
(
"++=",
"Concatenates two lists, two strings, or two binary values",
),
("=", "Assigns a value to a variable."),
],
Type::Int | Type::Float => vec![
("=", "Assigns a value to a variable."),
("+=", "Adds a value to a variable."),
("-=", "Subtracts a value from a variable."),
("*=", "Multiplies a variable by a value"),
("/=", "Divides a variable by a value."),
],
_ => vec![],
}
}

View File

@ -1,67 +1,157 @@
use crate::completions::{Completer, CompletionOptions, SemanticSuggestion, SuggestionKind}; use crate::completions::{Completer, CompletionOptions, SemanticSuggestion, SuggestionKind};
use nu_engine::{column::get_columns, eval_variable};
use nu_protocol::{ use nu_protocol::{
engine::{Stack, StateWorkingSet}, engine::{Stack, StateWorkingSet},
Span, VarId, Span, Value,
}; };
use reedline::Suggestion; use reedline::Suggestion;
use std::str;
use super::completion_options::NuMatcher; use super::completion_options::NuMatcher;
pub struct VariableCompletion; #[derive(Clone)]
pub struct VariableCompletion {
var_context: (Vec<u8>, Vec<Vec<u8>>), // tuple with $var and the sublevels (.b.c.d)
}
impl VariableCompletion {
pub fn new(var_context: (Vec<u8>, Vec<Vec<u8>>)) -> Self {
Self { var_context }
}
}
impl Completer for VariableCompletion { impl Completer for VariableCompletion {
fn fetch( fn fetch(
&mut self, &mut self,
working_set: &StateWorkingSet, working_set: &StateWorkingSet,
_stack: &Stack, stack: &Stack,
prefix: impl AsRef<str>, prefix: &[u8],
span: Span, span: Span,
offset: usize, offset: usize,
_pos: usize,
options: &CompletionOptions, options: &CompletionOptions,
) -> Vec<SemanticSuggestion> { ) -> Vec<SemanticSuggestion> {
let mut matcher = NuMatcher::new(prefix, options); let builtins = ["$nu", "$in", "$env"];
let var_str = std::str::from_utf8(&self.var_context.0).unwrap_or("");
let var_id = working_set.find_variable(&self.var_context.0);
let current_span = reedline::Span { let current_span = reedline::Span {
start: span.start - offset, start: span.start - offset,
end: span.end - offset, end: span.end - offset,
}; };
let sublevels_count = self.var_context.1.len();
let prefix_str = String::from_utf8_lossy(prefix);
let mut matcher = NuMatcher::new(prefix_str, options.clone());
// Completions for the given variable
if !var_str.is_empty() {
// Completion for $env.<tab>
if var_str == "$env" {
let env_vars = stack.get_env_vars(working_set.permanent_state);
// Return nested values
if sublevels_count > 0 {
// Extract the target var ($env.<target-var>)
let target_var = self.var_context.1[0].clone();
let target_var_str =
str::from_utf8(&target_var).unwrap_or_default().to_string();
// Everything after the target var is the nested level ($env.<target-var>.<nested_levels>...)
let nested_levels: Vec<Vec<u8>> =
self.var_context.1.clone().into_iter().skip(1).collect();
if let Some(val) = env_vars.get(&target_var_str) {
for suggestion in nested_suggestions(val, &nested_levels, current_span) {
matcher.add_semantic_suggestion(suggestion);
}
return matcher.results();
}
} else {
// No nesting provided, return all env vars
for env_var in env_vars {
matcher.add_semantic_suggestion(SemanticSuggestion {
suggestion: Suggestion {
value: env_var.0,
span: current_span,
..Suggestion::default()
},
kind: Some(SuggestionKind::Type(env_var.1.get_type())),
});
}
return matcher.results();
}
}
// Completions for $nu.<tab>
if var_str == "$nu" {
// Eval nu var
if let Ok(nuval) = eval_variable(
working_set.permanent_state,
stack,
nu_protocol::NU_VARIABLE_ID,
nu_protocol::Span::new(current_span.start, current_span.end),
) {
for suggestion in nested_suggestions(&nuval, &self.var_context.1, current_span)
{
matcher.add_semantic_suggestion(suggestion);
}
return matcher.results();
}
}
// Completion other variable types
if let Some(var_id) = var_id {
// Extract the variable value from the stack
let var = stack.get_var(var_id, Span::new(span.start, span.end));
// If the value exists and it's of type Record
if let Ok(value) = var {
for suggestion in nested_suggestions(&value, &self.var_context.1, current_span)
{
matcher.add_semantic_suggestion(suggestion);
}
return matcher.results();
}
}
}
// Variable completion (e.g: $en<tab> to complete $env) // Variable completion (e.g: $en<tab> to complete $env)
let builtins = ["$nu", "$in", "$env"];
for builtin in builtins { for builtin in builtins {
matcher.add_semantic_suggestion(SemanticSuggestion { matcher.add_semantic_suggestion(SemanticSuggestion {
suggestion: Suggestion { suggestion: Suggestion {
value: builtin.to_string(), value: builtin.to_string(),
span: current_span, span: current_span,
description: Some("reserved".into()),
..Suggestion::default() ..Suggestion::default()
}, },
kind: Some(SuggestionKind::Variable), // TODO is there a way to get the VarId to get the type???
kind: None,
}); });
} }
let mut add_candidate = |name, var_id: &VarId| {
matcher.add_semantic_suggestion(SemanticSuggestion {
suggestion: Suggestion {
value: String::from_utf8_lossy(name).to_string(),
span: current_span,
description: Some(working_set.get_variable(*var_id).ty.to_string()),
..Suggestion::default()
},
kind: Some(SuggestionKind::Variable),
})
};
// TODO: The following can be refactored (see find_commands_by_predicate() used in // TODO: The following can be refactored (see find_commands_by_predicate() used in
// command_completions). // command_completions).
let mut removed_overlays = vec![]; let mut removed_overlays = vec![];
// Working set scope vars // Working set scope vars
for scope_frame in working_set.delta.scope.iter().rev() { for scope_frame in working_set.delta.scope.iter().rev() {
for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() { for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() {
for (name, var_id) in &overlay_frame.vars { for v in &overlay_frame.vars {
add_candidate(name, var_id); matcher.add_semantic_suggestion(SemanticSuggestion {
suggestion: Suggestion {
value: String::from_utf8_lossy(v.0).to_string(),
span: current_span,
..Suggestion::default()
},
kind: Some(SuggestionKind::Type(
working_set.get_variable(*v.1).ty.clone(),
)),
});
} }
} }
} }
// Permanent state vars // Permanent state vars
// for scope in &self.engine_state.scope { // for scope in &self.engine_state.scope {
for overlay_frame in working_set for overlay_frame in working_set
@ -69,11 +159,98 @@ impl Completer for VariableCompletion {
.active_overlays(&removed_overlays) .active_overlays(&removed_overlays)
.rev() .rev()
{ {
for (name, var_id) in &overlay_frame.vars { for v in &overlay_frame.vars {
add_candidate(name, var_id); matcher.add_semantic_suggestion(SemanticSuggestion {
suggestion: Suggestion {
value: String::from_utf8_lossy(v.0).to_string(),
span: current_span,
..Suggestion::default()
},
kind: Some(SuggestionKind::Type(
working_set.get_variable(*v.1).ty.clone(),
)),
});
} }
} }
matcher.results() matcher.results()
} }
} }
// Find recursively the values for sublevels
// if no sublevels are set it returns the current value
fn nested_suggestions(
val: &Value,
sublevels: &[Vec<u8>],
current_span: reedline::Span,
) -> Vec<SemanticSuggestion> {
let mut output: Vec<SemanticSuggestion> = vec![];
let value = recursive_value(val, sublevels).unwrap_or_else(Value::nothing);
let kind = SuggestionKind::Type(value.get_type());
match value {
Value::Record { val, .. } => {
// Add all the columns as completion
for col in val.columns() {
output.push(SemanticSuggestion {
suggestion: Suggestion {
value: col.clone(),
span: current_span,
..Suggestion::default()
},
kind: Some(kind.clone()),
});
}
output
}
Value::List { vals, .. } => {
for column_name in get_columns(vals.as_slice()) {
output.push(SemanticSuggestion {
suggestion: Suggestion {
value: column_name,
span: current_span,
..Suggestion::default()
},
kind: Some(kind.clone()),
});
}
output
}
_ => output,
}
}
// Extracts the recursive value (e.g: $var.a.b.c)
fn recursive_value(val: &Value, sublevels: &[Vec<u8>]) -> Result<Value, Span> {
// Go to next sublevel
if let Some((sublevel, next_sublevels)) = sublevels.split_first() {
let span = val.span();
match val {
Value::Record { val, .. } => {
if let Some((_, value)) = val.iter().find(|(key, _)| key.as_bytes() == sublevel) {
// If matches try to fetch recursively the next
recursive_value(value, next_sublevels)
} else {
// Current sublevel value not found
Err(span)
}
}
Value::List { vals, .. } => {
for col in get_columns(vals.as_slice()) {
if col.as_bytes() == *sublevel {
let val = val.get_data_by_key(&col).ok_or(span)?;
return recursive_value(&val, next_sublevels);
}
}
// Current sublevel value not found
Err(span)
}
_ => Ok(val.clone()),
}
} else {
Ok(val.clone())
}
}

View File

@ -8,7 +8,7 @@ use nu_protocol::{
debugger::WithoutDebug, debugger::WithoutDebug,
engine::{EngineState, Stack, StateWorkingSet}, engine::{EngineState, Stack, StateWorkingSet},
report_parse_error, report_parse_warning, report_parse_error, report_parse_warning,
shell_error::io::*, shell_error::io::IoError,
PipelineData, ShellError, Span, Value, PipelineData, ShellError, Span, Value,
}; };
use std::{path::PathBuf, sync::Arc}; use std::{path::PathBuf, sync::Arc};
@ -27,11 +27,11 @@ pub fn evaluate_file(
let cwd = engine_state.cwd_as_string(Some(stack))?; let cwd = engine_state.cwd_as_string(Some(stack))?;
let file_path = canonicalize_with(&path, cwd).map_err(|err| { let file_path = canonicalize_with(&path, cwd).map_err(|err| {
IoError::new_internal_with_path( IoError::new_with_additional_context(
err.kind().not_found_as(NotFound::File), err.kind(),
"Could not access file", Span::unknown(),
nu_protocol::location!(),
PathBuf::from(&path), PathBuf::from(&path),
"Could not access file",
) )
})?; })?;
@ -46,21 +46,21 @@ pub fn evaluate_file(
})?; })?;
let file = std::fs::read(&file_path).map_err(|err| { let file = std::fs::read(&file_path).map_err(|err| {
IoError::new_internal_with_path( IoError::new_with_additional_context(
err.kind().not_found_as(NotFound::File), err.kind(),
"Could not read file", Span::unknown(),
nu_protocol::location!(),
file_path.clone(), file_path.clone(),
"Could not read file",
) )
})?; })?;
engine_state.file = Some(file_path.clone()); engine_state.file = Some(file_path.clone());
let parent = file_path.parent().ok_or_else(|| { let parent = file_path.parent().ok_or_else(|| {
IoError::new_internal_with_path( IoError::new_with_additional_context(
ErrorKind::DirectoryNotFound, std::io::ErrorKind::NotFound,
"The file path does not have a parent", Span::unknown(),
nu_protocol::location!(),
file_path.clone(), file_path.clone(),
"The file path does not have a parent",
) )
})?; })?;

View File

@ -740,15 +740,9 @@ fn add_keybinding(
let span = mode.span(); let span = mode.span();
match &mode { match &mode {
Value::String { val, .. } => match val.as_str() { Value::String { val, .. } => match val.as_str() {
str if str.eq_ignore_ascii_case("emacs") => { "emacs" => add_parsed_keybinding(emacs_keybindings, keybinding, config),
add_parsed_keybinding(emacs_keybindings, keybinding, config) "vi_insert" => add_parsed_keybinding(insert_keybindings, keybinding, config),
} "vi_normal" => add_parsed_keybinding(normal_keybindings, keybinding, config),
str if str.eq_ignore_ascii_case("vi_insert") => {
add_parsed_keybinding(insert_keybindings, keybinding, config)
}
str if str.eq_ignore_ascii_case("vi_normal") => {
add_parsed_keybinding(normal_keybindings, keybinding, config)
}
str => Err(ShellError::InvalidValue { str => Err(ShellError::InvalidValue {
valid: "'emacs', 'vi_insert', or 'vi_normal'".into(), valid: "'emacs', 'vi_insert', or 'vi_normal'".into(),
actual: format!("'{str}'"), actual: format!("'{str}'"),
@ -998,54 +992,41 @@ fn event_from_record(
) -> Result<ReedlineEvent, ShellError> { ) -> Result<ReedlineEvent, ShellError> {
let event = match name { let event = match name {
"none" => ReedlineEvent::None, "none" => ReedlineEvent::None,
"clearscreen" => ReedlineEvent::ClearScreen,
"clearscrollback" => ReedlineEvent::ClearScrollback,
"historyhintcomplete" => ReedlineEvent::HistoryHintComplete, "historyhintcomplete" => ReedlineEvent::HistoryHintComplete,
"historyhintwordcomplete" => ReedlineEvent::HistoryHintWordComplete, "historyhintwordcomplete" => ReedlineEvent::HistoryHintWordComplete,
"ctrld" => ReedlineEvent::CtrlD, "ctrld" => ReedlineEvent::CtrlD,
"ctrlc" => ReedlineEvent::CtrlC, "ctrlc" => ReedlineEvent::CtrlC,
"clearscreen" => ReedlineEvent::ClearScreen,
"clearscrollback" => ReedlineEvent::ClearScrollback,
"enter" => ReedlineEvent::Enter, "enter" => ReedlineEvent::Enter,
"submit" => ReedlineEvent::Submit, "submit" => ReedlineEvent::Submit,
"submitornewline" => ReedlineEvent::SubmitOrNewline, "submitornewline" => ReedlineEvent::SubmitOrNewline,
"esc" | "escape" => ReedlineEvent::Esc, "esc" | "escape" => ReedlineEvent::Esc,
// Non-sensical for user configuration:
//
// `ReedlineEvent::Mouse` - itself a no-op
// `ReedlineEvent::Resize` - requires size info specifically from the ANSI resize
// event
//
// Handled above in `parse_event`:
//
// `ReedlineEvent::Edit`
"repaint" => ReedlineEvent::Repaint,
"previoushistory" => ReedlineEvent::PreviousHistory,
"up" => ReedlineEvent::Up, "up" => ReedlineEvent::Up,
"down" => ReedlineEvent::Down, "down" => ReedlineEvent::Down,
"right" => ReedlineEvent::Right, "right" => ReedlineEvent::Right,
"left" => ReedlineEvent::Left, "left" => ReedlineEvent::Left,
"nexthistory" => ReedlineEvent::NextHistory,
"searchhistory" => ReedlineEvent::SearchHistory, "searchhistory" => ReedlineEvent::SearchHistory,
// Handled above in `parse_event`: "nexthistory" => ReedlineEvent::NextHistory,
// "previoushistory" => ReedlineEvent::PreviousHistory,
// `ReedlineEvent::Multiple` "repaint" => ReedlineEvent::Repaint,
// `ReedlineEvent::UntilFound` "menudown" => ReedlineEvent::MenuDown,
"menuup" => ReedlineEvent::MenuUp,
"menuleft" => ReedlineEvent::MenuLeft,
"menuright" => ReedlineEvent::MenuRight,
"menunext" => ReedlineEvent::MenuNext,
"menuprevious" => ReedlineEvent::MenuPrevious,
"menupagenext" => ReedlineEvent::MenuPageNext,
"menupageprevious" => ReedlineEvent::MenuPagePrevious,
"openeditor" => ReedlineEvent::OpenEditor,
"menu" => { "menu" => {
let menu = extract_value("name", record, span)?; let menu = extract_value("name", record, span)?;
ReedlineEvent::Menu(menu.to_expanded_string("", config)) ReedlineEvent::Menu(menu.to_expanded_string("", config))
} }
"menunext" => ReedlineEvent::MenuNext,
"menuprevious" => ReedlineEvent::MenuPrevious,
"menuup" => ReedlineEvent::MenuUp,
"menudown" => ReedlineEvent::MenuDown,
"menuleft" => ReedlineEvent::MenuLeft,
"menuright" => ReedlineEvent::MenuRight,
"menupagenext" => ReedlineEvent::MenuPageNext,
"menupageprevious" => ReedlineEvent::MenuPagePrevious,
"executehostcommand" => { "executehostcommand" => {
let cmd = extract_value("cmd", record, span)?; let cmd = extract_value("cmd", record, span)?;
ReedlineEvent::ExecuteHostCommand(cmd.to_expanded_string("", config)) ReedlineEvent::ExecuteHostCommand(cmd.to_expanded_string("", config))
} }
"openeditor" => ReedlineEvent::OpenEditor,
str => { str => {
return Err(ShellError::InvalidValue { return Err(ShellError::InvalidValue {
valid: "a reedline event".into(), valid: "a reedline event".into(),
@ -1075,6 +1056,7 @@ fn edit_from_record(
.and_then(|value| value.as_bool()) .and_then(|value| value.as_bool())
.unwrap_or(false), .unwrap_or(false),
}, },
"movetoend" => EditCommand::MoveToEnd { "movetoend" => EditCommand::MoveToEnd {
select: extract_value("select", record, span) select: extract_value("select", record, span)
.and_then(|value| value.as_bool()) .and_then(|value| value.as_bool())
@ -1110,16 +1092,6 @@ fn edit_from_record(
.and_then(|value| value.as_bool()) .and_then(|value| value.as_bool())
.unwrap_or(false), .unwrap_or(false),
}, },
"movewordrightstart" => EditCommand::MoveWordRightStart {
select: extract_value("select", record, span)
.and_then(|value| value.as_bool())
.unwrap_or(false),
},
"movebigwordrightstart" => EditCommand::MoveBigWordRightStart {
select: extract_value("select", record, span)
.and_then(|value| value.as_bool())
.unwrap_or(false),
},
"movewordrightend" => EditCommand::MoveWordRightEnd { "movewordrightend" => EditCommand::MoveWordRightEnd {
select: extract_value("select", record, span) select: extract_value("select", record, span)
.and_then(|value| value.as_bool()) .and_then(|value| value.as_bool())
@ -1130,6 +1102,16 @@ fn edit_from_record(
.and_then(|value| value.as_bool()) .and_then(|value| value.as_bool())
.unwrap_or(false), .unwrap_or(false),
}, },
"movewordrightstart" => EditCommand::MoveWordRightStart {
select: extract_value("select", record, span)
.and_then(|value| value.as_bool())
.unwrap_or(false),
},
"movebigwordrightstart" => EditCommand::MoveBigWordRightStart {
select: extract_value("select", record, span)
.and_then(|value| value.as_bool())
.unwrap_or(false),
},
"movetoposition" => { "movetoposition" => {
let value = extract_value("value", record, span)?; let value = extract_value("value", record, span)?;
let select = extract_value("select", record, span) let select = extract_value("select", record, span)
@ -1151,13 +1133,6 @@ fn edit_from_record(
EditCommand::InsertString(value.to_expanded_string("", config)) EditCommand::InsertString(value.to_expanded_string("", config))
} }
"insertnewline" => EditCommand::InsertNewline, "insertnewline" => EditCommand::InsertNewline,
"replacechar" => {
let value = extract_value("value", record, span)?;
let char = extract_char(value)?;
EditCommand::ReplaceChar(char)
}
// `EditCommand::ReplaceChars` - Internal hack not sanely implementable as a
// standalone binding
"backspace" => EditCommand::Backspace, "backspace" => EditCommand::Backspace,
"delete" => EditCommand::Delete, "delete" => EditCommand::Delete,
"cutchar" => EditCommand::CutChar, "cutchar" => EditCommand::CutChar,
@ -1165,7 +1140,6 @@ fn edit_from_record(
"deleteword" => EditCommand::DeleteWord, "deleteword" => EditCommand::DeleteWord,
"clear" => EditCommand::Clear, "clear" => EditCommand::Clear,
"cleartolineend" => EditCommand::ClearToLineEnd, "cleartolineend" => EditCommand::ClearToLineEnd,
"complete" => EditCommand::Complete,
"cutcurrentline" => EditCommand::CutCurrentLine, "cutcurrentline" => EditCommand::CutCurrentLine,
"cutfromstart" => EditCommand::CutFromStart, "cutfromstart" => EditCommand::CutFromStart,
"cutfromlinestart" => EditCommand::CutFromLineStart, "cutfromlinestart" => EditCommand::CutFromLineStart,
@ -1182,7 +1156,6 @@ fn edit_from_record(
"uppercaseword" => EditCommand::UppercaseWord, "uppercaseword" => EditCommand::UppercaseWord,
"lowercaseword" => EditCommand::LowercaseWord, "lowercaseword" => EditCommand::LowercaseWord,
"capitalizechar" => EditCommand::CapitalizeChar, "capitalizechar" => EditCommand::CapitalizeChar,
"switchcasechar" => EditCommand::SwitchcaseChar,
"swapwords" => EditCommand::SwapWords, "swapwords" => EditCommand::SwapWords,
"swapgraphemes" => EditCommand::SwapGraphemes, "swapgraphemes" => EditCommand::SwapGraphemes,
"undo" => EditCommand::Undo, "undo" => EditCommand::Undo,
@ -1239,64 +1212,17 @@ fn edit_from_record(
.unwrap_or(false); .unwrap_or(false);
EditCommand::MoveLeftBefore { c: char, select } EditCommand::MoveLeftBefore { c: char, select }
} }
"selectall" => EditCommand::SelectAll, "complete" => EditCommand::Complete,
"cutselection" => EditCommand::CutSelection, "cutselection" => EditCommand::CutSelection,
"copyselection" => EditCommand::CopySelection,
"paste" => EditCommand::Paste,
"copyfromstart" => EditCommand::CopyFromStart,
"copyfromlinestart" => EditCommand::CopyFromLineStart,
"copytoend" => EditCommand::CopyToEnd,
"copytolineend" => EditCommand::CopyToLineEnd,
"copycurrentline" => EditCommand::CopyCurrentLine,
"copywordleft" => EditCommand::CopyWordLeft,
"copybigwordleft" => EditCommand::CopyBigWordLeft,
"copywordright" => EditCommand::CopyWordRight,
"copybigwordright" => EditCommand::CopyBigWordRight,
"copywordrighttonext" => EditCommand::CopyWordRightToNext,
"copybigwordrighttonext" => EditCommand::CopyBigWordRightToNext,
"copyleft" => EditCommand::CopyLeft,
"copyright" => EditCommand::CopyRight,
"copyrightuntil" => {
let value = extract_value("value", record, span)?;
let char = extract_char(value)?;
EditCommand::CopyRightUntil(char)
}
"copyrightbefore" => {
let value = extract_value("value", record, span)?;
let char = extract_char(value)?;
EditCommand::CopyRightBefore(char)
}
"copyleftuntil" => {
let value = extract_value("value", record, span)?;
let char = extract_char(value)?;
EditCommand::CopyLeftUntil(char)
}
"copyleftbefore" => {
let value = extract_value("value", record, span)?;
let char = extract_char(value)?;
EditCommand::CopyLeftBefore(char)
}
"swapcursorandanchor" => EditCommand::SwapCursorAndAnchor,
#[cfg(feature = "system-clipboard")] #[cfg(feature = "system-clipboard")]
"cutselectionsystem" => EditCommand::CutSelectionSystem, "cutselectionsystem" => EditCommand::CutSelectionSystem,
"copyselection" => EditCommand::CopySelection,
#[cfg(feature = "system-clipboard")] #[cfg(feature = "system-clipboard")]
"copyselectionsystem" => EditCommand::CopySelectionSystem, "copyselectionsystem" => EditCommand::CopySelectionSystem,
"paste" => EditCommand::Paste,
#[cfg(feature = "system-clipboard")] #[cfg(feature = "system-clipboard")]
"pastesystem" => EditCommand::PasteSystem, "pastesystem" => EditCommand::PasteSystem,
"cutinside" => { "selectall" => EditCommand::SelectAll,
let value = extract_value("left", record, span)?;
let left = extract_char(value)?;
let value = extract_value("right", record, span)?;
let right = extract_char(value)?;
EditCommand::CutInside { left, right }
}
"yankinside" => {
let value = extract_value("left", record, span)?;
let left = extract_char(value)?;
let value = extract_value("right", record, span)?;
let right = extract_char(value)?;
EditCommand::YankInside { left, right }
}
str => { str => {
return Err(ShellError::InvalidValue { return Err(ShellError::InvalidValue {
valid: "a reedline EditCommand".into(), valid: "a reedline EditCommand".into(),

View File

@ -20,7 +20,6 @@ use nu_cmd_base::util::get_editor;
use nu_color_config::StyleComputer; use nu_color_config::StyleComputer;
#[allow(deprecated)] #[allow(deprecated)]
use nu_engine::env_to_strings; use nu_engine::env_to_strings;
use nu_engine::exit::cleanup_exit;
use nu_parser::{lex, parse, trim_quotes_str}; use nu_parser::{lex, parse, trim_quotes_str};
use nu_protocol::shell_error::io::IoError; use nu_protocol::shell_error::io::IoError;
use nu_protocol::{ use nu_protocol::{
@ -37,7 +36,6 @@ use reedline::{
CursorConfig, CwdAwareHinter, DefaultCompleter, EditCommand, Emacs, FileBackedHistory, CursorConfig, CwdAwareHinter, DefaultCompleter, EditCommand, Emacs, FileBackedHistory,
HistorySessionId, Reedline, SqliteBackedHistory, Vi, HistorySessionId, Reedline, SqliteBackedHistory, Vi,
}; };
use std::sync::atomic::Ordering;
use std::{ use std::{
collections::HashMap, collections::HashMap,
env::temp_dir, env::temp_dir,
@ -694,11 +692,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
); );
println!(); println!();
return (false, stack, line_editor);
cleanup_exit((), engine_state, 0);
// if cleanup_exit didn't exit, we should keep running
return (true, stack, line_editor);
} }
Err(err) => { Err(err) => {
let message = err.to_string(); let message = err.to_string();
@ -864,7 +858,7 @@ fn do_auto_cd(
path.to_string_lossy().to_string() path.to_string_lossy().to_string()
}; };
if let PermissionResult::PermissionDenied = have_permission(path.clone()) { if let PermissionResult::PermissionDenied(_) = have_permission(path.clone()) {
report_shell_error( report_shell_error(
engine_state, engine_state,
&ShellError::Io(IoError::new_with_additional_context( &ShellError::Io(IoError::new_with_additional_context(
@ -936,9 +930,6 @@ fn do_run_cmd(
trace!("eval source: {}", s); trace!("eval source: {}", s);
let mut cmds = s.split_whitespace(); let mut cmds = s.split_whitespace();
let had_warning_before = engine_state.exit_warning_given.load(Ordering::SeqCst);
if let Some("exit") = cmds.next() { if let Some("exit") = cmds.next() {
let mut working_set = StateWorkingSet::new(engine_state); let mut working_set = StateWorkingSet::new(engine_state);
let _ = parse(&mut working_set, None, s.as_bytes(), false); let _ = parse(&mut working_set, None, s.as_bytes(), false);
@ -947,11 +938,13 @@ fn do_run_cmd(
match cmds.next() { match cmds.next() {
Some(s) => { Some(s) => {
if let Ok(n) = s.parse::<i32>() { if let Ok(n) = s.parse::<i32>() {
return cleanup_exit(line_editor, engine_state, n); drop(line_editor);
std::process::exit(n);
} }
} }
None => { None => {
return cleanup_exit(line_editor, engine_state, 0); drop(line_editor);
std::process::exit(0);
} }
} }
} }
@ -970,14 +963,6 @@ fn do_run_cmd(
false, false,
); );
// if there was a warning before, and we got to this point, it means
// the possible call to cleanup_exit did not occur.
if had_warning_before && engine_state.is_interactive {
engine_state
.exit_warning_given
.store(false, Ordering::SeqCst);
}
line_editor line_editor
} }

View File

@ -309,7 +309,6 @@ fn find_matching_block_end_in_expr(
.unwrap_or(expression.span.start); .unwrap_or(expression.span.start);
return match &expression.expr { return match &expression.expr {
// TODO: Can't these be handled with an `_ => None` branch? Refactor
Expr::Bool(_) => None, Expr::Bool(_) => None,
Expr::Int(_) => None, Expr::Int(_) => None,
Expr::Float(_) => None, Expr::Float(_) => None,
@ -336,28 +335,6 @@ fn find_matching_block_end_in_expr(
Expr::Nothing => None, Expr::Nothing => None,
Expr::Garbage => None, Expr::Garbage => None,
Expr::AttributeBlock(ab) => ab
.attributes
.iter()
.find_map(|attr| {
find_matching_block_end_in_expr(
line,
working_set,
&attr.expr,
global_span_offset,
global_cursor_offset,
)
})
.or_else(|| {
find_matching_block_end_in_expr(
line,
working_set,
&ab.item,
global_span_offset,
global_cursor_offset,
)
}),
Expr::Table(table) => { Expr::Table(table) => {
if expr_last == global_cursor_offset { if expr_last == global_cursor_offset {
// cursor is at table end // cursor is at table end

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@ fn create_default_context() -> EngineState {
nu_command::add_shell_command_context(nu_cmd_lang::create_default_context()) nu_command::add_shell_command_context(nu_cmd_lang::create_default_context())
} }
/// creates a new engine with the current path into the completions fixtures folder // creates a new engine with the current path into the completions fixtures folder
pub fn new_engine() -> (AbsolutePathBuf, String, EngineState, Stack) { pub fn new_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
// Target folder inside assets // Target folder inside assets
let dir = fs::fixtures().join("completions"); let dir = fs::fixtures().join("completions");
@ -69,26 +69,7 @@ pub fn new_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
(dir, dir_str, engine_state, stack) (dir, dir_str, engine_state, stack)
} }
/// Adds pseudo PATH env for external completion tests // creates a new engine with the current path into the completions fixtures folder
pub fn new_external_engine() -> EngineState {
let mut engine = create_default_context();
let dir = fs::fixtures().join("external_completions").join("path");
let dir_str = dir.to_string_lossy().to_string();
let internal_span = nu_protocol::Span::new(0, dir_str.len());
engine.add_env_var(
"PATH".to_string(),
Value::List {
vals: vec![Value::String {
val: dir_str,
internal_span,
}],
internal_span,
},
);
engine
}
/// creates a new engine with the current path into the completions fixtures folder
pub fn new_dotnu_engine() -> (AbsolutePathBuf, String, EngineState, Stack) { pub fn new_dotnu_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
// Target folder inside assets // Target folder inside assets
let dir = fs::fixtures().join("dotnu_completions"); let dir = fs::fixtures().join("dotnu_completions");
@ -105,23 +86,6 @@ pub fn new_dotnu_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
// Add $nu // Add $nu
engine_state.generate_nu_constant(); engine_state.generate_nu_constant();
// const $NU_LIB_DIRS
let mut working_set = StateWorkingSet::new(&engine_state);
let var_id = working_set.add_variable(
b"$NU_LIB_DIRS".into(),
Span::unknown(),
nu_protocol::Type::List(Box::new(nu_protocol::Type::String)),
false,
);
working_set.set_variable_const_val(
var_id,
Value::test_list(vec![
Value::string(file(dir.join("lib-dir1")), dir_span),
Value::string(file(dir.join("lib-dir3")), dir_span),
]),
);
let _ = engine_state.merge_delta(working_set.render());
// New stack // New stack
let mut stack = Stack::new(); let mut stack = Stack::new();
@ -131,12 +95,17 @@ pub fn new_dotnu_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
"TEST".to_string(), "TEST".to_string(),
Value::string("NUSHELL".to_string(), dir_span), Value::string("NUSHELL".to_string(), dir_span),
); );
stack.add_env_var( stack.add_env_var(
"NU_LIB_DIRS".into(), "NU_LIB_DIRS".to_string(),
Value::test_list(vec![ Value::list(
vec![
Value::string(file(dir.join("lib-dir1")), dir_span),
Value::string(file(dir.join("lib-dir2")), dir_span), Value::string(file(dir.join("lib-dir2")), dir_span),
Value::string(file(dir.join("lib-dir3")), dir_span), Value::string(file(dir.join("lib-dir3")), dir_span),
]), ],
dir_span,
),
); );
// Merge environment into the permanent state // Merge environment into the permanent state
@ -216,8 +185,8 @@ pub fn new_partial_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
(dir, dir_str, engine_state, stack) (dir, dir_str, engine_state, stack)
} }
/// match a list of suggestions with the expected values // match a list of suggestions with the expected values
pub fn match_suggestions(expected: &Vec<&str>, suggestions: &Vec<Suggestion>) { pub fn match_suggestions(expected: &Vec<String>, suggestions: &Vec<Suggestion>) {
let expected_len = expected.len(); let expected_len = expected.len();
let suggestions_len = suggestions.len(); let suggestions_len = suggestions.len();
if expected_len != suggestions_len { if expected_len != suggestions_len {
@ -228,34 +197,28 @@ pub fn match_suggestions(expected: &Vec<&str>, suggestions: &Vec<Suggestion>) {
) )
} }
let suggestions_str = suggestions let suggestoins_str = suggestions
.iter() .iter()
.map(|it| it.value.as_str()) .map(|it| it.value.clone())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!(expected, &suggestions_str); assert_eq!(expected, &suggestoins_str);
} }
/// match a list of suggestions with the expected values // append the separator to the converted path
pub fn match_suggestions_by_string(expected: &[String], suggestions: &Vec<Suggestion>) {
let expected = expected.iter().map(|it| it.as_str()).collect::<Vec<_>>();
match_suggestions(&expected, suggestions);
}
/// append the separator to the converted path
pub fn folder(path: impl Into<PathBuf>) -> String { pub fn folder(path: impl Into<PathBuf>) -> String {
let mut converted_path = file(path); let mut converted_path = file(path);
converted_path.push(MAIN_SEPARATOR); converted_path.push(MAIN_SEPARATOR);
converted_path converted_path
} }
/// convert a given path to string // convert a given path to string
pub fn file(path: impl Into<PathBuf>) -> String { pub fn file(path: impl Into<PathBuf>) -> String {
path.into().into_os_string().into_string().unwrap() path.into().into_os_string().into_string().unwrap()
} }
/// merge_input executes the given input into the engine // merge_input executes the given input into the engine
/// and merges the state // and merges the state
pub fn merge_input( pub fn merge_input(
input: &[u8], input: &[u8],
engine_state: &mut EngineState, engine_state: &mut EngineState,

View File

@ -1,5 +1,3 @@
pub mod completions_helpers; pub mod completions_helpers;
pub use completions_helpers::{ pub use completions_helpers::{file, folder, match_suggestions, merge_input, new_engine};
file, folder, match_suggestions, match_suggestions_by_string, merge_input, new_engine,
};

View File

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

View File

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

View File

@ -26,7 +26,7 @@ impl Command for BitsAnd {
.required( .required(
"target", "target",
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]), SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]),
"Right-hand side of the operation.", "right-hand side of the operation",
) )
.named( .named(
"endian", "endian",

View File

@ -0,0 +1,120 @@
use nu_engine::command_prelude::*;
use nu_protocol::{report_parse_warning, ParseWarning};
#[derive(Clone)]
pub struct BitsInto;
impl Command for BitsInto {
fn name(&self) -> &str {
"into bits"
}
fn signature(&self) -> Signature {
Signature::build("into bits")
.input_output_types(vec![
(Type::Binary, Type::String),
(Type::Int, Type::String),
(Type::Filesize, Type::String),
(Type::Duration, Type::String),
(Type::String, Type::String),
(Type::Bool, Type::String),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
])
.allow_variants_without_examples(true) // TODO: supply exhaustive examples
.rest(
"rest",
SyntaxShape::CellPath,
"for a data structure input, convert data at the given cell paths",
)
.category(Category::Deprecated)
}
fn description(&self) -> &str {
"Convert value to a binary string."
}
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;
report_parse_warning(
&StateWorkingSet::new(engine_state),
&ParseWarning::DeprecatedWarning {
old_command: "into bits".into(),
new_suggestion: "use `format bits`".into(),
span: head,
url: "`help format bits`".into(),
},
);
crate::extra::strings::format::format_bits(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "convert a binary value into a string, padded to 8 places with 0s",
example: "0x[1] | into bits",
result: Some(Value::string("00000001",
Span::test_data(),
)),
},
Example {
description: "convert an int into a string, padded to 8 places with 0s",
example: "1 | into bits",
result: Some(Value::string("00000001",
Span::test_data(),
)),
},
Example {
description: "convert a filesize value into a string, padded to 8 places with 0s",
example: "1b | into bits",
result: Some(Value::string("00000001",
Span::test_data(),
)),
},
Example {
description: "convert a duration value into a string, padded to 8 places with 0s",
example: "1ns | into bits",
result: Some(Value::string("00000001",
Span::test_data(),
)),
},
Example {
description: "convert a boolean value into a string, padded to 8 places with 0s",
example: "true | into bits",
result: Some(Value::string("00000001",
Span::test_data(),
)),
},
Example {
description: "convert a string into a raw binary string, padded with 0s to 8 places",
example: "'nushell.sh' | into bits",
result: Some(Value::string("01101110 01110101 01110011 01101000 01100101 01101100 01101100 00101110 01110011 01101000",
Span::test_data(),
)),
},
]
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BitsInto {})
}
}

View File

@ -1,5 +1,6 @@
mod and; mod and;
mod bits_; mod bits_;
mod into;
mod not; mod not;
mod or; mod or;
mod rotate_left; mod rotate_left;
@ -10,6 +11,7 @@ mod xor;
pub use and::BitsAnd; pub use and::BitsAnd;
pub use bits_::Bits; pub use bits_::Bits;
pub use into::BitsInto;
pub use not::BitsNot; pub use not::BitsNot;
pub use or::BitsOr; pub use or::BitsOr;
pub use rotate_left::BitsRol; pub use rotate_left::BitsRol;
@ -135,7 +137,7 @@ where
(min, max) => (rhs, lhs, max, min), (min, max) => (rhs, lhs, max, min),
}; };
let pad = iter::repeat_n(0, max_len - min_len); let pad = iter::repeat(0).take(max_len - min_len);
let mut a; let mut a;
let mut b; let mut b;
@ -159,10 +161,9 @@ where
} }
(Value::Binary { .. }, Value::Int { .. }) | (Value::Int { .. }, Value::Binary { .. }) => { (Value::Binary { .. }, Value::Int { .. }) | (Value::Int { .. }, Value::Binary { .. }) => {
Value::error( Value::error(
ShellError::OnlySupportsThisInputType { ShellError::PipelineMismatch {
exp_input_type: "input, and argument, to be both int or both binary" exp_input_type: "input, and argument, to be both int or both binary"
.to_string(), .to_string(),
wrong_type: "int and binary".to_string(),
dst_span: rhs.span(), dst_span: rhs.span(),
src_span: span, src_span: span,
}, },

View File

@ -27,7 +27,7 @@ impl Command for BitsOr {
.required( .required(
"target", "target",
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]), SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]),
"Right-hand side of the operation.", "right-hand side of the operation",
) )
.named( .named(
"endian", "endian",

View File

@ -37,7 +37,7 @@ impl Command for BitsRol {
), ),
]) ])
.allow_variants_without_examples(true) .allow_variants_without_examples(true)
.required("bits", SyntaxShape::Int, "Number of bits to rotate left.") .required("bits", SyntaxShape::Int, "number of bits to rotate left")
.switch( .switch(
"signed", "signed",
"always treat input number as a signed number", "always treat input number as a signed number",

View File

@ -37,7 +37,7 @@ impl Command for BitsRor {
), ),
]) ])
.allow_variants_without_examples(true) .allow_variants_without_examples(true)
.required("bits", SyntaxShape::Int, "Number of bits to rotate right.") .required("bits", SyntaxShape::Int, "number of bits to rotate right")
.switch( .switch(
"signed", "signed",
"always treat input number as a signed number", "always treat input number as a signed number",

View File

@ -40,7 +40,7 @@ impl Command for BitsShl {
), ),
]) ])
.allow_variants_without_examples(true) .allow_variants_without_examples(true)
.required("bits", SyntaxShape::Int, "Number of bits to shift left.") .required("bits", SyntaxShape::Int, "number of bits to shift left")
.switch( .switch(
"signed", "signed",
"always treat input number as a signed number", "always treat input number as a signed number",
@ -249,7 +249,7 @@ fn shift_bytes_and_bits_left(data: &[u8], byte_shift: usize, bit_shift: usize) -
Last | Only => lhs << bit_shift, Last | Only => lhs << bit_shift,
_ => (lhs << bit_shift) | (rhs >> (8 - bit_shift)), _ => (lhs << bit_shift) | (rhs >> (8 - bit_shift)),
}) })
.chain(iter::repeat_n(0, byte_shift)) .chain(iter::repeat(0).take(byte_shift))
.collect::<Vec<u8>>() .collect::<Vec<u8>>()
} }

View File

@ -37,7 +37,7 @@ impl Command for BitsShr {
), ),
]) ])
.allow_variants_without_examples(true) .allow_variants_without_examples(true)
.required("bits", SyntaxShape::Int, "Number of bits to shift right.") .required("bits", SyntaxShape::Int, "number of bits to shift right")
.switch( .switch(
"signed", "signed",
"always treat input number as a signed number", "always treat input number as a signed number",

View File

@ -27,7 +27,7 @@ impl Command for BitsXor {
.required( .required(
"target", "target",
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]), SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]),
"Right-hand side of the operation.", "right-hand side of the operation",
) )
.named( .named(
"endian", "endian",

View File

@ -0,0 +1,74 @@
use nu_engine::command_prelude::*;
use nu_protocol::{report_parse_warning, ParseWarning};
#[derive(Clone)]
pub struct Fmt;
impl Command for Fmt {
fn name(&self) -> &str {
"fmt"
}
fn description(&self) -> &str {
"Format a number."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("fmt")
.input_output_types(vec![(Type::Number, Type::record())])
.category(Category::Deprecated)
}
fn search_terms(&self) -> Vec<&str> {
vec![]
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get a record containing multiple formats for the number 42",
example: "42 | fmt",
result: Some(Value::test_record(record! {
"binary" => Value::test_string("0b101010"),
"debug" => Value::test_string("42"),
"display" => Value::test_string("42"),
"lowerexp" => Value::test_string("4.2e1"),
"lowerhex" => Value::test_string("0x2a"),
"octal" => Value::test_string("0o52"),
"upperexp" => Value::test_string("4.2E1"),
"upperhex" => Value::test_string("0x2A"),
})),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
report_parse_warning(
&StateWorkingSet::new(engine_state),
&ParseWarning::DeprecatedWarning {
old_command: "fmt".into(),
new_suggestion: "use `format number`".into(),
span: head,
url: "`help format number`".into(),
},
);
crate::extra::strings::format::format_number(engine_state, stack, call, input)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Fmt {})
}
}

View File

@ -0,0 +1,3 @@
mod fmt;
pub(crate) use fmt::Fmt;

View File

@ -26,7 +26,7 @@ impl Command for EachWhile {
.required( .required(
"closure", "closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"The closure to run.", "the closure to run",
) )
.category(Category::Filters) .category(Category::Filters)
} }

View File

@ -20,7 +20,7 @@ impl Command for Rotate {
.rest( .rest(
"rest", "rest",
SyntaxShape::String, SyntaxShape::String,
"The names to give columns once rotated.", "the names to give columns once rotated",
) )
.category(Category::Filters) .category(Category::Filters)
.allow_variants_without_examples(true) .allow_variants_without_examples(true)

View File

@ -16,7 +16,7 @@ impl Command for UpdateCells {
.required( .required(
"closure", "closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"The closure to run an update for each cell.", "the closure to run an update for each cell",
) )
.named( .named(
"columns", "columns",

View File

@ -1,9 +1,9 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct MathArcCos; pub struct SubCommand;
impl Command for MathArcCos { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"math arccos" "math arccos"
} }
@ -114,6 +114,6 @@ mod test {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(MathArcCos {}) test_examples(SubCommand {})
} }
} }

View File

@ -1,9 +1,9 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct MathArcCosH; pub struct SubCommand;
impl Command for MathArcCosH { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"math arccosh" "math arccosh"
} }
@ -100,6 +100,6 @@ mod test {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(MathArcCosH {}) test_examples(SubCommand {})
} }
} }

View File

@ -1,9 +1,9 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct MathArcSin; pub struct SubCommand;
impl Command for MathArcSin { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"math arcsin" "math arcsin"
} }
@ -115,6 +115,6 @@ mod test {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(MathArcSin {}) test_examples(SubCommand {})
} }
} }

View File

@ -1,9 +1,9 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct MathArcSinH; pub struct SubCommand;
impl Command for MathArcSinH { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"math arcsinh" "math arcsinh"
} }
@ -88,6 +88,6 @@ mod test {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(MathArcSinH {}) test_examples(SubCommand {})
} }
} }

View File

@ -1,9 +1,9 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct MathArcTan; pub struct SubCommand;
impl Command for MathArcTan { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"math arctan" "math arctan"
} }
@ -102,6 +102,6 @@ mod test {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(MathArcTan {}) test_examples(SubCommand {})
} }
} }

View File

@ -1,9 +1,9 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct MathArcTanH; pub struct SubCommand;
impl Command for MathArcTanH { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"math arctanh" "math arctanh"
} }
@ -101,6 +101,6 @@ mod test {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(MathArcTanH {}) test_examples(SubCommand {})
} }
} }

View File

@ -1,9 +1,9 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct MathCos; pub struct SubCommand;
impl Command for MathCos { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"math cos" "math cos"
} }
@ -108,6 +108,6 @@ mod test {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(MathCos {}) test_examples(SubCommand {})
} }
} }

View File

@ -1,9 +1,9 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct MathCosH; pub struct SubCommand;
impl Command for MathCosH { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"math cosh" "math cosh"
} }
@ -88,6 +88,6 @@ mod test {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(MathCosH {}) test_examples(SubCommand {})
} }
} }

View File

@ -1,9 +1,9 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct MathExp; pub struct SubCommand;
impl Command for MathExp { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"math exp" "math exp"
} }
@ -93,6 +93,6 @@ mod test {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(MathExp {}) test_examples(SubCommand {})
} }
} }

View File

@ -1,9 +1,9 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct MathLn; pub struct SubCommand;
impl Command for MathLn { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"math ln" "math ln"
} }
@ -100,6 +100,6 @@ mod test {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(MathLn {}) test_examples(SubCommand {})
} }
} }

View File

@ -15,19 +15,19 @@ mod arcsinh;
mod arctan; mod arctan;
mod arctanh; mod arctanh;
pub use cos::MathCos; pub use cos::SubCommand as MathCos;
pub use cosh::MathCosH; pub use cosh::SubCommand as MathCosH;
pub use sin::MathSin; pub use sin::SubCommand as MathSin;
pub use sinh::MathSinH; pub use sinh::SubCommand as MathSinH;
pub use tan::MathTan; pub use tan::SubCommand as MathTan;
pub use tanh::MathTanH; pub use tanh::SubCommand as MathTanH;
pub use exp::MathExp; pub use exp::SubCommand as MathExp;
pub use ln::MathLn; pub use ln::SubCommand as MathLn;
pub use arccos::MathArcCos; pub use arccos::SubCommand as MathArcCos;
pub use arccosh::MathArcCosH; pub use arccosh::SubCommand as MathArcCosH;
pub use arcsin::MathArcSin; pub use arcsin::SubCommand as MathArcSin;
pub use arcsinh::MathArcSinH; pub use arcsinh::SubCommand as MathArcSinH;
pub use arctan::MathArcTan; pub use arctan::SubCommand as MathArcTan;
pub use arctanh::MathArcTanH; pub use arctanh::SubCommand as MathArcTanH;

View File

@ -1,9 +1,9 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct MathSin; pub struct SubCommand;
impl Command for MathSin { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"math sin" "math sin"
} }
@ -108,6 +108,6 @@ mod test {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(MathSin {}) test_examples(SubCommand {})
} }
} }

View File

@ -1,9 +1,9 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct MathSinH; pub struct SubCommand;
impl Command for MathSinH { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"math sinh" "math sinh"
} }
@ -87,6 +87,6 @@ mod test {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(MathSinH {}) test_examples(SubCommand {})
} }
} }

View File

@ -1,9 +1,9 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct MathTan; pub struct SubCommand;
impl Command for MathTan { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"math tan" "math tan"
} }
@ -106,6 +106,6 @@ mod test {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(MathTan {}) test_examples(SubCommand {})
} }
} }

View File

@ -1,9 +1,9 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct MathTanH; pub struct SubCommand;
impl Command for MathTanH { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"math tanh" "math tanh"
} }
@ -86,6 +86,6 @@ mod test {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(MathTanH {}) test_examples(SubCommand {})
} }
} }

View File

@ -1,11 +1,14 @@
mod bits; mod bits;
mod conversions;
mod filters; mod filters;
mod formats; mod formats;
mod math; mod math;
mod platform; mod platform;
mod strings; mod strings;
pub use bits::{Bits, BitsAnd, BitsNot, BitsOr, BitsRol, BitsRor, BitsShl, BitsShr, BitsXor}; pub use bits::{
Bits, BitsAnd, BitsInto, BitsNot, BitsOr, BitsRol, BitsRor, BitsShl, BitsShr, BitsXor,
};
pub use formats::ToHtml; pub use formats::ToHtml;
pub use math::{MathArcCos, MathArcCosH, MathArcSin, MathArcSinH, MathArcTan, MathArcTanH}; pub use math::{MathArcCos, MathArcCosH, MathArcSin, MathArcSinH, MathArcTan, MathArcTanH};
pub use math::{MathCos, MathCosH, MathSin, MathSinH, MathTan, MathTanH}; pub use math::{MathCos, MathCosH, MathSin, MathSinH, MathTan, MathTanH};
@ -26,6 +29,8 @@ pub fn add_extra_command_context(mut engine_state: EngineState) -> EngineState {
}; };
} }
bind_command!(conversions::Fmt);
bind_command!( bind_command!(
filters::UpdateCells, filters::UpdateCells,
filters::EachWhile, filters::EachWhile,
@ -58,6 +63,7 @@ pub fn add_extra_command_context(mut engine_state: EngineState) -> EngineState {
bind_command! { bind_command! {
Bits, Bits,
BitsAnd, BitsAnd,
BitsInto,
BitsNot, BitsNot,
BitsOr, BitsOr,
BitsRol, BitsRol,

View File

@ -38,7 +38,7 @@ impl Command for SubCommand {
.rest( .rest(
"cell path", "cell path",
SyntaxShape::CellPath, SyntaxShape::CellPath,
"For a data structure input, add a gradient to strings at the given cell paths.", "for a data structure input, add a gradient to strings at the given cell paths",
) )
.input_output_types(vec![ .input_output_types(vec![
(Type::String, Type::String), (Type::String, Type::String),

View File

@ -40,7 +40,7 @@ impl Command for FormatBits {
.rest( .rest(
"rest", "rest",
SyntaxShape::CellPath, SyntaxShape::CellPath,
"For a data structure input, convert data at the given cell paths.", "for a data structure input, convert data at the given cell paths",
) )
.category(Category::Conversions) .category(Category::Conversions)
} }
@ -111,7 +111,8 @@ impl Command for FormatBits {
} }
} }
fn format_bits( // TODO: crate public only during deprecation
pub(crate) fn format_bits(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,

View File

@ -18,7 +18,7 @@ impl Command for FormatPattern {
.required( .required(
"pattern", "pattern",
SyntaxShape::String, SyntaxShape::String,
"The pattern to output. e.g.) \"{foo}: {bar}\".", "the pattern to output. e.g.) \"{foo}: {bar}\"",
) )
.allow_variants_without_examples(true) .allow_variants_without_examples(true)
.category(Category::Strings) .category(Category::Strings)
@ -253,11 +253,12 @@ fn format_record(
optional: false, optional: false,
}) })
.collect(); .collect();
match data_as_value.clone().follow_cell_path(&path_members, false) {
let expanded_string = data_as_value Ok(value_at_column) => {
.follow_cell_path(&path_members, false)? output.push_str(value_at_column.to_expanded_string(", ", config).as_str())
.to_expanded_string(", ", config); }
output.push_str(expanded_string.as_str()) Err(se) => return Err(se),
}
} }
} }
} }

View File

@ -2,6 +2,8 @@ mod bits;
mod command; mod command;
mod number; mod number;
pub(crate) use bits::FormatBits;
pub(crate) use command::FormatPattern; pub(crate) use command::FormatPattern;
pub(crate) use number::FormatNumber; // TODO remove `format_bits` visibility after removal of into bits
pub(crate) use bits::{format_bits, FormatBits};
// TODO remove `format_number` visibility after removal of into bits
pub(crate) use number::{format_number, FormatNumber};

View File

@ -20,7 +20,7 @@ impl Command for FormatNumber {
} }
fn search_terms(&self) -> Vec<&str> { fn search_terms(&self) -> Vec<&str> {
vec!["display", "render", "fmt"] vec!["display", "render", "format"]
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {

View File

@ -3,9 +3,9 @@ use heck::ToLowerCamelCase;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct StrCamelCase; pub struct SubCommand;
impl Command for StrCamelCase { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"str camel-case" "str camel-case"
} }
@ -25,7 +25,7 @@ impl Command for StrCamelCase {
.rest( .rest(
"rest", "rest",
SyntaxShape::CellPath, SyntaxShape::CellPath,
"For a data structure input, convert strings at the given cell paths.", "For a data structure input, convert strings at the given cell paths",
) )
.category(Category::Strings) .category(Category::Strings)
} }
@ -91,6 +91,6 @@ mod test {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(StrCamelCase {}) test_examples(SubCommand {})
} }
} }

View File

@ -3,9 +3,9 @@ use heck::ToKebabCase;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct StrKebabCase; pub struct SubCommand;
impl Command for StrKebabCase { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"str kebab-case" "str kebab-case"
} }
@ -25,7 +25,7 @@ impl Command for StrKebabCase {
.rest( .rest(
"rest", "rest",
SyntaxShape::CellPath, SyntaxShape::CellPath,
"For a data structure input, convert strings at the given cell paths.", "For a data structure input, convert strings at the given cell paths",
) )
.category(Category::Strings) .category(Category::Strings)
} }
@ -90,6 +90,6 @@ mod tests {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(StrKebabCase {}) test_examples(SubCommand {})
} }
} }

View File

@ -6,13 +6,13 @@ mod snake_case;
mod str_; mod str_;
mod title_case; mod title_case;
pub use camel_case::StrCamelCase; pub use camel_case::SubCommand as StrCamelCase;
pub use kebab_case::StrKebabCase; pub use kebab_case::SubCommand as StrKebabCase;
pub use pascal_case::StrPascalCase; pub use pascal_case::SubCommand as StrPascalCase;
pub use screaming_snake_case::StrScreamingSnakeCase; pub use screaming_snake_case::SubCommand as StrScreamingSnakeCase;
pub use snake_case::StrSnakeCase; pub use snake_case::SubCommand as StrSnakeCase;
pub use str_::Str; pub use str_::Str;
pub use title_case::StrTitleCase; pub use title_case::SubCommand as StrTitleCase;
use nu_cmd_base::input_handler::{operate as general_operate, CmdArgument}; use nu_cmd_base::input_handler::{operate as general_operate, CmdArgument};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;

View File

@ -3,9 +3,9 @@ use heck::ToUpperCamelCase;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct StrPascalCase; pub struct SubCommand;
impl Command for StrPascalCase { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"str pascal-case" "str pascal-case"
} }
@ -25,7 +25,7 @@ impl Command for StrPascalCase {
.rest( .rest(
"rest", "rest",
SyntaxShape::CellPath, SyntaxShape::CellPath,
"For a data structure input, convert strings at the given cell paths.", "For a data structure input, convert strings at the given cell paths",
) )
.category(Category::Strings) .category(Category::Strings)
} }
@ -91,6 +91,6 @@ mod test {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(StrPascalCase {}) test_examples(SubCommand {})
} }
} }

View File

@ -3,9 +3,9 @@ use heck::ToShoutySnakeCase;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct StrScreamingSnakeCase; pub struct SubCommand;
impl Command for StrScreamingSnakeCase { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"str screaming-snake-case" "str screaming-snake-case"
} }
@ -25,7 +25,7 @@ impl Command for StrScreamingSnakeCase {
.rest( .rest(
"rest", "rest",
SyntaxShape::CellPath, SyntaxShape::CellPath,
"For a data structure input, convert strings at the given cell paths.", "For a data structure input, convert strings at the given cell paths",
) )
.category(Category::Strings) .category(Category::Strings)
} }
@ -91,6 +91,6 @@ mod test {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(StrScreamingSnakeCase {}) test_examples(SubCommand {})
} }
} }

View File

@ -3,9 +3,9 @@ use heck::ToSnakeCase;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct StrSnakeCase; pub struct SubCommand;
impl Command for StrSnakeCase { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"str snake-case" "str snake-case"
} }
@ -25,7 +25,7 @@ impl Command for StrSnakeCase {
.rest( .rest(
"rest", "rest",
SyntaxShape::CellPath, SyntaxShape::CellPath,
"For a data structure input, convert strings at the given cell paths.", "For a data structure input, convert strings at the given cell paths",
) )
.category(Category::Strings) .category(Category::Strings)
} }
@ -91,6 +91,6 @@ mod test {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(StrSnakeCase {}) test_examples(SubCommand {})
} }
} }

View File

@ -3,9 +3,9 @@ use heck::ToTitleCase;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct StrTitleCase; pub struct SubCommand;
impl Command for StrTitleCase { impl Command for SubCommand {
fn name(&self) -> &str { fn name(&self) -> &str {
"str title-case" "str title-case"
} }
@ -25,7 +25,7 @@ impl Command for StrTitleCase {
.rest( .rest(
"rest", "rest",
SyntaxShape::CellPath, SyntaxShape::CellPath,
"For a data structure input, convert strings at the given cell paths.", "For a data structure input, convert strings at the given cell paths",
) )
.category(Category::Strings) .category(Category::Strings)
} }
@ -86,6 +86,6 @@ mod test {
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(StrTitleCase {}) test_examples(SubCommand {})
} }
} }

View File

@ -6,7 +6,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-cmd-lang" name = "nu-cmd-lang"
version = "0.104.1" version = "0.102.0"
[lib] [lib]
bench = false bench = false
@ -15,20 +15,16 @@ bench = false
workspace = true workspace = true
[dependencies] [dependencies]
nu-engine = { path = "../nu-engine", version = "0.104.1", default-features = false } nu-engine = { path = "../nu-engine", version = "0.102.0", default-features = false }
nu-parser = { path = "../nu-parser", version = "0.104.1" } nu-parser = { path = "../nu-parser", version = "0.102.0" }
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false } nu-protocol = { path = "../nu-protocol", version = "0.102.0", default-features = false }
nu-utils = { path = "../nu-utils", version = "0.104.1", default-features = false } nu-utils = { path = "../nu-utils", version = "0.102.0", default-features = false }
itertools = { workspace = true } itertools = { workspace = true }
shadow-rs = { version = "1.1", default-features = false } shadow-rs = { version = "0.38", default-features = false }
[build-dependencies] [build-dependencies]
shadow-rs = { version = "1.1", default-features = false, features = ["build"] } shadow-rs = { version = "0.38", default-features = false }
[dev-dependencies]
quickcheck = { workspace = true }
quickcheck_macros = { workspace = true }
[features] [features]
default = ["os"] default = ["os"]
@ -42,6 +38,7 @@ plugin = [
"os", "os",
] ]
mimalloc = []
trash-support = [] trash-support = []
sqlite = [] sqlite = []
static-link-openssl = [] static-link-openssl = []

View File

@ -1,61 +0,0 @@
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct AttrCategory;
impl Command for AttrCategory {
fn name(&self) -> &str {
"attr category"
}
fn signature(&self) -> Signature {
Signature::build("attr category")
.input_output_type(Type::Nothing, Type::list(Type::String))
.allow_variants_without_examples(true)
.required(
"category",
SyntaxShape::String,
"Category of the custom command.",
)
.category(Category::Core)
}
fn description(&self) -> &str {
"Attribute for adding a category to custom commands."
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let arg: String = call.req(engine_state, stack, 0)?;
Ok(Value::string(arg, call.head).into_pipeline_data())
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let arg: String = call.req_const(working_set, 0)?;
Ok(Value::string(arg, call.head).into_pipeline_data())
}
fn is_const(&self) -> bool {
true
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Add a category to a custom command",
example: r###"# Double numbers
@category math
def double []: [number -> number] { $in * 2 }"###,
result: None,
}]
}
}

View File

@ -1,159 +0,0 @@
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct AttrExample;
impl Command for AttrExample {
fn name(&self) -> &str {
"attr example"
}
// TODO: When const closure are available, switch to using them for the `example` argument
// rather than a block. That should remove the need for `requires_ast_for_arguments` to be true
fn signature(&self) -> Signature {
Signature::build("attr example")
.input_output_types(vec![(
Type::Nothing,
Type::Record(
[
("description".into(), Type::String),
("example".into(), Type::String),
]
.into(),
),
)])
.allow_variants_without_examples(true)
.required(
"description",
SyntaxShape::String,
"Description of the example.",
)
.required(
"example",
SyntaxShape::OneOf(vec![SyntaxShape::Block, SyntaxShape::String]),
"Example code snippet.",
)
.named(
"result",
SyntaxShape::Any,
"Expected output of example.",
None,
)
.category(Category::Core)
}
fn description(&self) -> &str {
"Attribute for adding examples to custom commands."
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let description: Spanned<String> = call.req(engine_state, stack, 0)?;
let result: Option<Value> = call.get_flag(engine_state, stack, "result")?;
let example_string: Result<String, _> = call.req(engine_state, stack, 1);
let example_expr = call
.positional_nth(stack, 1)
.ok_or(ShellError::MissingParameter {
param_name: "example".into(),
span: call.head,
})?;
let working_set = StateWorkingSet::new(engine_state);
attr_example_impl(
example_expr,
example_string,
&working_set,
call,
description,
result,
)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let description: Spanned<String> = call.req_const(working_set, 0)?;
let result: Option<Value> = call.get_flag_const(working_set, "result")?;
let example_string: Result<String, _> = call.req_const(working_set, 1);
let example_expr =
call.assert_ast_call()?
.positional_nth(1)
.ok_or(ShellError::MissingParameter {
param_name: "example".into(),
span: call.head,
})?;
attr_example_impl(
example_expr,
example_string,
working_set,
call,
description,
result,
)
}
fn is_const(&self) -> bool {
true
}
fn requires_ast_for_arguments(&self) -> bool {
true
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Add examples to custom command",
example: r###"# Double numbers
@example "double an int" { 2 | double } --result 4
@example "double a float" { 0.25 | double } --result 0.5
def double []: [number -> number] { $in * 2 }"###,
result: None,
}]
}
}
fn attr_example_impl(
example_expr: &nu_protocol::ast::Expression,
example_string: Result<String, ShellError>,
working_set: &StateWorkingSet<'_>,
call: &Call<'_>,
description: Spanned<String>,
result: Option<Value>,
) -> Result<PipelineData, ShellError> {
let example_content = match example_expr.as_block() {
Some(block_id) => {
let block = working_set.get_block(block_id);
let contents =
working_set.get_span_contents(block.span.expect("a block must have a span"));
let contents = contents
.strip_prefix(b"{")
.and_then(|x| x.strip_suffix(b"}"))
.unwrap_or(contents)
.trim_ascii();
String::from_utf8_lossy(contents).into_owned()
}
None => example_string?,
};
let mut rec = record! {
"description" => Value::string(description.item, description.span),
"example" => Value::string(example_content, example_expr.span),
};
if let Some(result) = result {
rec.push("result", result);
}
Ok(Value::record(rec, call.head).into_pipeline_data())
}

View File

@ -1,7 +0,0 @@
mod category;
mod example;
mod search_terms;
pub use category::AttrCategory;
pub use example::AttrExample;
pub use search_terms::AttrSearchTerms;

View File

@ -1,57 +0,0 @@
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct AttrSearchTerms;
impl Command for AttrSearchTerms {
fn name(&self) -> &str {
"attr search-terms"
}
fn signature(&self) -> Signature {
Signature::build("attr search-terms")
.input_output_type(Type::Nothing, Type::list(Type::String))
.allow_variants_without_examples(true)
.rest("terms", SyntaxShape::String, "Search terms.")
.category(Category::Core)
}
fn description(&self) -> &str {
"Attribute for adding search terms to custom commands."
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let args = call.rest(engine_state, stack, 0)?;
Ok(Value::list(args, call.head).into_pipeline_data())
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let args = call.rest_const(working_set, 0)?;
Ok(Value::list(args, call.head).into_pipeline_data())
}
fn is_const(&self) -> bool {
true
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Add search terms to a custom command",
example: r###"# Double numbers
@search-terms multiply times
def double []: [number -> number] { $in * 2 }"###,
result: None,
}]
}
}

View File

@ -72,19 +72,6 @@ impl Command for Const {
} }
} }
fn run_const(
&self,
_working_set: &StateWorkingSet,
_call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
Ok(PipelineData::empty())
}
fn is_const(&self) -> bool {
true
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -1,9 +1,6 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::{ use nu_protocol::{engine::StateWorkingSet, ByteStreamSource, PipelineMetadata};
engine::{Closure, StateWorkingSet},
BlockId, ByteStreamSource, Category, PipelineMetadata, Signature,
};
use std::any::type_name;
#[derive(Clone)] #[derive(Clone)]
pub struct Describe; pub struct Describe;
@ -76,116 +73,39 @@ impl Command for Describe {
"{shell:'true', uwu:true, features: {bugs:false, multiplatform:true, speed: 10}, fib: [1 1 2 3 5 8], on_save: {|x| $'Saving ($x)'}, first_commit: 2019-05-10, my_duration: (4min + 20sec)} | describe -d", "{shell:'true', uwu:true, features: {bugs:false, multiplatform:true, speed: 10}, fib: [1 1 2 3 5 8], on_save: {|x| $'Saving ($x)'}, first_commit: 2019-05-10, my_duration: (4min + 20sec)} | describe -d",
result: Some(Value::test_record(record!( result: Some(Value::test_record(record!(
"type" => Value::test_string("record"), "type" => Value::test_string("record"),
"detailed_type" => Value::test_string("record<shell: string, uwu: bool, features: record<bugs: bool, multiplatform: bool, speed: int>, fib: list<int>, on_save: closure, first_commit: datetime, my_duration: duration>"),
"columns" => Value::test_record(record!( "columns" => Value::test_record(record!(
"shell" => Value::test_record(record!( "shell" => Value::test_string("string"),
"type" => Value::test_string("string"), "uwu" => Value::test_string("bool"),
"detailed_type" => Value::test_string("string"),
"rust_type" => Value::test_string("&alloc::string::String"),
"value" => Value::test_string("true"),
)),
"uwu" => Value::test_record(record!(
"type" => Value::test_string("bool"),
"detailed_type" => Value::test_string("bool"),
"rust_type" => Value::test_string("bool"),
"value" => Value::test_bool(true),
)),
"features" => Value::test_record(record!( "features" => Value::test_record(record!(
"type" => Value::test_string("record"), "type" => Value::test_string("record"),
"detailed_type" => Value::test_string("record<bugs: bool, multiplatform: bool, speed: int>"),
"columns" => Value::test_record(record!( "columns" => Value::test_record(record!(
"bugs" => Value::test_record(record!( "bugs" => Value::test_string("bool"),
"type" => Value::test_string("bool"), "multiplatform" => Value::test_string("bool"),
"detailed_type" => Value::test_string("bool"), "speed" => Value::test_string("int"),
"rust_type" => Value::test_string("bool"),
"value" => Value::test_bool(false),
)), )),
"multiplatform" => Value::test_record(record!(
"type" => Value::test_string("bool"),
"detailed_type" => Value::test_string("bool"),
"rust_type" => Value::test_string("bool"),
"value" => Value::test_bool(true),
)),
"speed" => Value::test_record(record!(
"type" => Value::test_string("int"),
"detailed_type" => Value::test_string("int"),
"rust_type" => Value::test_string("i64"),
"value" => Value::test_int(10),
)),
)),
"rust_type" => Value::test_string("&nu_utils::shared_cow::SharedCow<nu_protocol::value::record::Record>"),
)), )),
"fib" => Value::test_record(record!( "fib" => Value::test_record(record!(
"type" => Value::test_string("list"), "type" => Value::test_string("list"),
"detailed_type" => Value::test_string("list<int>"),
"length" => Value::test_int(6), "length" => Value::test_int(6),
"rust_type" => Value::test_string("&mut alloc::vec::Vec<nu_protocol::value::Value>"), "values" => Value::test_list(vec![
"value" => Value::test_list(vec![ Value::test_string("int"),
Value::test_record(record!( Value::test_string("int"),
"type" => Value::test_string("int"), Value::test_string("int"),
"detailed_type" => Value::test_string("int"), Value::test_string("int"),
"rust_type" => Value::test_string("i64"), Value::test_string("int"),
"value" => Value::test_int(1), Value::test_string("int"),
)), ]),
Value::test_record(record!(
"type" => Value::test_string("int"),
"detailed_type" => Value::test_string("int"),
"rust_type" => Value::test_string("i64"),
"value" => Value::test_int(1),
)),
Value::test_record(record!(
"type" => Value::test_string("int"),
"detailed_type" => Value::test_string("int"),
"rust_type" => Value::test_string("i64"),
"value" => Value::test_int(2),
)),
Value::test_record(record!(
"type" => Value::test_string("int"),
"detailed_type" => Value::test_string("int"),
"rust_type" => Value::test_string("i64"),
"value" => Value::test_int(3),
)),
Value::test_record(record!(
"type" => Value::test_string("int"),
"detailed_type" => Value::test_string("int"),
"rust_type" => Value::test_string("i64"),
"value" => Value::test_int(5),
)),
Value::test_record(record!(
"type" => Value::test_string("int"),
"detailed_type" => Value::test_string("int"),
"rust_type" => Value::test_string("i64"),
"value" => Value::test_int(8),
))]
),
)), )),
"on_save" => Value::test_record(record!( "on_save" => Value::test_record(record!(
"type" => Value::test_string("closure"), "type" => Value::test_string("closure"),
"detailed_type" => Value::test_string("closure"),
"rust_type" => Value::test_string("&alloc::boxed::Box<nu_protocol::engine::closure::Closure>"),
"value" => Value::test_closure(Closure {
block_id: BlockId::new(1),
captures: vec![],
}),
"signature" => Value::test_record(record!( "signature" => Value::test_record(record!(
"name" => Value::test_string(""), "name" => Value::test_string(""),
"category" => Value::test_string("default"), "category" => Value::test_string("default"),
)), )),
)), )),
"first_commit" => Value::test_record(record!( "first_commit" => Value::test_string("date"),
"type" => Value::test_string("datetime"), "my_duration" => Value::test_string("duration"),
"detailed_type" => Value::test_string("datetime"),
"rust_type" => Value::test_string("chrono::datetime::DateTime<chrono::offset::fixed::FixedOffset>"),
"value" => Value::test_date("2019-05-10 00:00:00Z".parse().unwrap_or_default()),
)), )),
"my_duration" => Value::test_record(record!(
"type" => Value::test_string("duration"),
"detailed_type" => Value::test_string("duration"),
"rust_type" => Value::test_string("i64"),
"value" => Value::test_duration(260_000_000_000),
))
)),
"rust_type" => Value::test_string("&nu_utils::shared_cow::SharedCow<nu_protocol::value::record::Record>"),
))), ))),
}, },
Example { Example {
@ -255,9 +175,7 @@ fn run(
Value::record( Value::record(
record! { record! {
"type" => Value::string("bytestream", head), "type" => Value::string(type_, head),
"detailed_type" => Value::string(type_, head),
"rust_type" => Value::string(type_of(&stream), head),
"origin" => Value::string(origin, head), "origin" => Value::string(origin, head),
"metadata" => metadata_to_value(metadata, head), "metadata" => metadata_to_value(metadata, head),
}, },
@ -274,7 +192,6 @@ fn run(
description description
} }
PipelineData::ListStream(stream, ..) => { PipelineData::ListStream(stream, ..) => {
let type_ = type_of(&stream);
if options.detailed { if options.detailed {
let subtype = if options.no_collect { let subtype = if options.no_collect {
Value::string("any", head) Value::string("any", head)
@ -284,8 +201,6 @@ fn run(
Value::record( Value::record(
record! { record! {
"type" => Value::string("stream", head), "type" => Value::string("stream", head),
"detailed_type" => Value::string("list stream", head),
"rust_type" => Value::string(type_, head),
"origin" => Value::string("nushell", head), "origin" => Value::string("nushell", head),
"subtype" => subtype, "subtype" => subtype,
"metadata" => metadata_to_value(metadata, head), "metadata" => metadata_to_value(metadata, head),
@ -314,95 +229,45 @@ fn run(
} }
enum Description { enum Description {
String(String),
Record(Record), Record(Record),
} }
impl Description { impl Description {
fn into_value(self, span: Span) -> Value { fn into_value(self, span: Span) -> Value {
match self { match self {
Description::String(ty) => Value::string(ty, span),
Description::Record(record) => Value::record(record, span), Description::Record(record) => Value::record(record, span),
} }
} }
} }
fn describe_value(value: Value, head: Span, engine_state: Option<&EngineState>) -> Value { fn describe_value(value: Value, head: Span, engine_state: Option<&EngineState>) -> Value {
let Description::Record(record) = describe_value_inner(value, head, engine_state); let record = match describe_value_inner(value, head, engine_state) {
Description::String(ty) => record! { "type" => Value::string(ty, head) },
Description::Record(record) => record,
};
Value::record(record, head) Value::record(record, head)
} }
fn type_of<T>(_: &T) -> String {
type_name::<T>().to_string()
}
fn describe_value_inner( fn describe_value_inner(
mut value: Value, value: Value,
head: Span, head: Span,
engine_state: Option<&EngineState>, engine_state: Option<&EngineState>,
) -> Description { ) -> Description {
let value_type = value.get_type().to_string();
match value { match value {
Value::Bool { val, .. } => Description::Record(record! { Value::Bool { .. }
"type" => Value::string("bool", head), | Value::Int { .. }
"detailed_type" => Value::string(value_type, head), | Value::Float { .. }
"rust_type" => Value::string(type_of(&val), head), | Value::Filesize { .. }
"value" => value, | Value::Duration { .. }
}), | Value::Date { .. }
Value::Int { val, .. } => Description::Record(record! { | Value::Range { .. }
"type" => Value::string("int", head), | Value::String { .. }
"detailed_type" => Value::string(value_type, head), | Value::Glob { .. }
"rust_type" => Value::string(type_of(&val), head), | Value::Nothing { .. } => Description::String(value.get_type().to_string()),
"value" => value, Value::Record { val, .. } => {
}), let mut columns = val.into_owned();
Value::Float { val, .. } => Description::Record(record! {
"type" => Value::string("float", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
}),
Value::Filesize { val, .. } => Description::Record(record! {
"type" => Value::string("filesize", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
}),
Value::Duration { val, .. } => Description::Record(record! {
"type" => Value::string("duration", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
}),
Value::Date { val, .. } => Description::Record(record! {
"type" => Value::string("datetime", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
}),
Value::Range { ref val, .. } => Description::Record(record! {
"type" => Value::string("range", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
}),
Value::String { ref val, .. } => Description::Record(record! {
"type" => Value::string("string", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
}),
Value::Glob { ref val, .. } => Description::Record(record! {
"type" => Value::string("glob", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
}),
Value::Nothing { .. } => Description::Record(record! {
"type" => Value::string("nothing", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string("", head),
"value" => value,
}),
Value::Record { ref val, .. } => {
let mut columns = val.clone().into_owned();
for (_, val) in &mut columns { for (_, val) in &mut columns {
*val = *val =
describe_value_inner(std::mem::take(val), head, engine_state).into_value(head); describe_value_inner(std::mem::take(val), head, engine_state).into_value(head);
@ -410,34 +275,25 @@ fn describe_value_inner(
Description::Record(record! { Description::Record(record! {
"type" => Value::string("record", head), "type" => Value::string("record", head),
"detailed_type" => Value::string(value_type, head), "columns" => Value::record(columns, head),
"columns" => Value::record(columns.clone(), head),
"rust_type" => Value::string(type_of(&val), head),
}) })
} }
Value::List { ref mut vals, .. } => { Value::List { mut vals, .. } => {
for val in &mut *vals { for val in &mut vals {
*val = *val =
describe_value_inner(std::mem::take(val), head, engine_state).into_value(head); describe_value_inner(std::mem::take(val), head, engine_state).into_value(head);
} }
Description::Record(record! { Description::Record(record! {
"type" => Value::string("list", head), "type" => Value::string("list", head),
"detailed_type" => Value::string(value_type, head),
"length" => Value::int(vals.len() as i64, head), "length" => Value::int(vals.len() as i64, head),
"rust_type" => Value::string(type_of(&vals), head), "values" => Value::list(vals, head),
"value" => value,
}) })
} }
Value::Closure { ref val, .. } => { Value::Closure { val, .. } => {
let block = engine_state.map(|engine_state| engine_state.get_block(val.block_id)); let block = engine_state.map(|engine_state| engine_state.get_block(val.block_id));
let mut record = record! { let mut record = record! { "type" => Value::string("closure", head) };
"type" => Value::string("closure", head),
"detailed_type" => Value::string(value_type, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
};
if let Some(block) = block { if let Some(block) = block {
record.push( record.push(
"signature", "signature",
@ -452,37 +308,21 @@ fn describe_value_inner(
} }
Description::Record(record) Description::Record(record)
} }
Value::Error { ref error, .. } => Description::Record(record! { Value::Error { error, .. } => Description::Record(record! {
"type" => Value::string("error", head), "type" => Value::string("error", head),
"detailed_type" => Value::string(value_type, head),
"subtype" => Value::string(error.to_string(), head), "subtype" => Value::string(error.to_string(), head),
"rust_type" => Value::string(type_of(&error), head),
"value" => value,
}), }),
Value::Binary { ref val, .. } => Description::Record(record! { Value::Binary { val, .. } => Description::Record(record! {
"type" => Value::string("binary", head), "type" => Value::string("binary", head),
"detailed_type" => Value::string(value_type, head),
"length" => Value::int(val.len() as i64, head), "length" => Value::int(val.len() as i64, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value,
}), }),
Value::CellPath { ref val, .. } => Description::Record(record! { Value::CellPath { val, .. } => Description::Record(record! {
"type" => Value::string("cell-path", head), "type" => Value::string("cell-path", head),
"detailed_type" => Value::string(value_type, head),
"length" => Value::int(val.members.len() as i64, head), "length" => Value::int(val.members.len() as i64, head),
"rust_type" => Value::string(type_of(&val), head),
"value" => value
}), }),
Value::Custom { ref val, .. } => Description::Record(record! { Value::Custom { val, .. } => Description::Record(record! {
"type" => Value::string("custom", head), "type" => Value::string("custom", head),
"detailed_type" => Value::string(value_type, head),
"subtype" => Value::string(val.type_name(), head), "subtype" => Value::string(val.type_name(), head),
"rust_type" => Value::string(type_of(&val), head),
"value" =>
match val.to_base_value(head) {
Ok(base_value) => base_value,
Err(err) => Value::error(err, head),
}
}), }),
} }
} }

View File

@ -31,6 +31,16 @@ impl Command for Do {
"ignore errors as the closure runs", "ignore errors as the closure runs",
Some('i'), Some('i'),
) )
.switch(
"ignore-shell-errors",
"ignore shell errors as the closure runs",
Some('s'),
)
.switch(
"ignore-program-errors",
"ignore external program errors as the closure runs",
Some('p'),
)
.switch( .switch(
"capture-errors", "capture-errors",
"catch errors as the closure runs, and return them", "catch errors as the closure runs, and return them",
@ -61,6 +71,36 @@ impl Command for Do {
let rest: Vec<Value> = call.rest(engine_state, caller_stack, 1)?; let rest: Vec<Value> = call.rest(engine_state, caller_stack, 1)?;
let ignore_all_errors = call.has_flag(engine_state, caller_stack, "ignore-errors")?; let ignore_all_errors = call.has_flag(engine_state, caller_stack, "ignore-errors")?;
if call.has_flag(engine_state, caller_stack, "ignore-shell-errors")? {
nu_protocol::report_shell_warning(
engine_state,
&ShellError::GenericError {
error: "Deprecated option".into(),
msg: "`--ignore-shell-errors` is deprecated and will be removed in 0.102.0."
.into(),
span: Some(call.head),
help: Some("Please use the `--ignore-errors(-i)`".into()),
inner: vec![],
},
);
}
if call.has_flag(engine_state, caller_stack, "ignore-program-errors")? {
nu_protocol::report_shell_warning(
engine_state,
&ShellError::GenericError {
error: "Deprecated option".into(),
msg: "`--ignore-program-errors` is deprecated and will be removed in 0.102.0."
.into(),
span: Some(call.head),
help: Some("Please use the `--ignore-errors(-i)`".into()),
inner: vec![],
},
);
}
let ignore_shell_errors = ignore_all_errors
|| call.has_flag(engine_state, caller_stack, "ignore-shell-errors")?;
let ignore_program_errors = ignore_all_errors
|| call.has_flag(engine_state, caller_stack, "ignore-program-errors")?;
let capture_errors = call.has_flag(engine_state, caller_stack, "capture-errors")?; let capture_errors = call.has_flag(engine_state, caller_stack, "capture-errors")?;
let has_env = call.has_flag(engine_state, caller_stack, "env")?; let has_env = call.has_flag(engine_state, caller_stack, "env")?;
@ -166,7 +206,7 @@ impl Command for Do {
} }
} }
Ok(PipelineData::ByteStream(mut stream, metadata)) Ok(PipelineData::ByteStream(mut stream, metadata))
if ignore_all_errors if ignore_program_errors
&& !matches!( && !matches!(
caller_stack.stdout(), caller_stack.stdout(),
OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value
@ -178,10 +218,10 @@ impl Command for Do {
} }
Ok(PipelineData::ByteStream(stream, metadata)) Ok(PipelineData::ByteStream(stream, metadata))
} }
Ok(PipelineData::Value(Value::Error { .. }, ..)) | Err(_) if ignore_all_errors => { Ok(PipelineData::Value(Value::Error { .. }, ..)) | Err(_) if ignore_shell_errors => {
Ok(PipelineData::empty()) Ok(PipelineData::empty())
} }
Ok(PipelineData::ListStream(stream, metadata)) if ignore_all_errors => { Ok(PipelineData::ListStream(stream, metadata)) if ignore_shell_errors => {
let stream = stream.map(move |value| { let stream = stream.map(move |value| {
if let Value::Error { .. } = value { if let Value::Error { .. } = value {
Value::nothing(head) Value::nothing(head)

View File

@ -34,22 +34,13 @@ little reason to use this over just writing the values as-is."#
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let args = call.rest(engine_state, stack, 0)?; let mut args = call.rest(engine_state, stack, 0)?;
echo_impl(args, call.head) let value = match args.len() {
} 0 => Value::string("", call.head),
1 => args.pop().expect("one element"),
fn run_const( _ => Value::list(args, call.head),
&self, };
working_set: &StateWorkingSet, Ok(value.into_pipeline_data())
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let args = call.rest_const(working_set, 0)?;
echo_impl(args, call.head)
}
fn is_const(&self) -> bool {
true
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -72,15 +63,6 @@ little reason to use this over just writing the values as-is."#
} }
} }
fn echo_impl(mut args: Vec<Value>, head: Span) -> Result<PipelineData, ShellError> {
let value = match args.len() {
0 => Value::string("", head),
1 => args.pop().expect("one element"),
_ => Value::list(args, head),
};
Ok(value.into_pipeline_data())
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
#[test] #[test]

View File

@ -32,10 +32,6 @@ This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nu.html"# https://www.nushell.sh/book/thinking_in_nu.html"#
} }
fn search_terms(&self) -> Vec<&str> {
vec!["unset"]
}
fn command_type(&self) -> CommandType { fn command_type(&self) -> CommandType {
CommandType::Keyword CommandType::Keyword
} }

View File

@ -29,10 +29,6 @@ impl Command for HideEnv {
"Hide environment variables in the current scope." "Hide environment variables in the current scope."
} }
fn search_terms(&self) -> Vec<&str> {
vec!["unset", "drop"]
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,

View File

@ -1,5 +1,4 @@
mod alias; mod alias;
mod attr;
mod break_; mod break_;
mod collect; mod collect;
mod const_; mod const_;
@ -36,7 +35,6 @@ mod version;
mod while_; mod while_;
pub use alias::Alias; pub use alias::Alias;
pub use attr::*;
pub use break_::Break; pub use break_::Break;
pub use collect::Collect; pub use collect::Collect;
pub use const_::Const; pub use const_::Const;

View File

@ -116,45 +116,34 @@ impl Command for OverlayUse {
// b) refreshing an active overlay (the origin module changed) // b) refreshing an active overlay (the origin module changed)
let module = engine_state.get_module(module_id); let module = engine_state.get_module(module_id);
// in such case, should also make sure that PWD is not restored in old overlays.
let cwd = caller_stack.get_env_var(engine_state, "PWD").cloned();
// Evaluate the export-env block (if any) and keep its environment // Evaluate the export-env block (if any) and keep its environment
if let Some(block_id) = module.env_block { if let Some(block_id) = module.env_block {
let maybe_file_path_or_dir = find_in_dirs_env( let maybe_path = find_in_dirs_env(
&name_arg.item, &name_arg.item,
engine_state, engine_state,
caller_stack, caller_stack,
get_dirs_var_from_call(caller_stack, call), get_dirs_var_from_call(caller_stack, call),
)?; )?;
let block = engine_state.get_block(block_id); let block = engine_state.get_block(block_id);
let mut callee_stack = caller_stack let mut callee_stack = caller_stack
.gather_captures(engine_state, &block.captures) .gather_captures(engine_state, &block.captures)
.reset_pipes(); .reset_pipes();
if let Some(path) = &maybe_file_path_or_dir { if let Some(path) = &maybe_path {
// Set the currently evaluated directory, if the argument is a valid path // Set the currently evaluated directory, if the argument is a valid path
let parent = if path.is_dir() {
path.clone()
} else {
let mut parent = path.clone(); let mut parent = path.clone();
parent.pop(); parent.pop();
parent
};
let file_pwd = Value::string(parent.to_string_lossy(), call.head); let file_pwd = Value::string(parent.to_string_lossy(), call.head);
callee_stack.add_env_var("FILE_PWD".to_string(), file_pwd); callee_stack.add_env_var("FILE_PWD".to_string(), file_pwd);
} }
if let Some(path) = &maybe_file_path_or_dir { if let Some(file_path) = &maybe_path {
let module_file_path = if path.is_dir() { let file_path = Value::string(file_path.to_string_lossy(), call.head);
// the existence of `mod.nu` is verified in parsing time callee_stack.add_env_var("CURRENT_FILE".to_string(), file_path);
// so it's safe to use it here.
Value::string(path.join("mod.nu").to_string_lossy(), call.head)
} else {
Value::string(path.to_string_lossy(), call.head)
};
callee_stack.add_env_var("CURRENT_FILE".to_string(), module_file_path);
} }
let eval_block = get_eval_block(engine_state); let eval_block = get_eval_block(engine_state);
@ -162,19 +151,11 @@ impl Command for OverlayUse {
// The export-env block should see the env vars *before* activating this overlay // The export-env block should see the env vars *before* activating this overlay
caller_stack.add_overlay(overlay_name); caller_stack.add_overlay(overlay_name);
// make sure that PWD is not restored in old overlays.
if let Some(cwd) = cwd {
caller_stack.add_env_var("PWD".to_string(), cwd);
}
// Merge the block's environment to the current stack // Merge the block's environment to the current stack
redirect_env(engine_state, caller_stack, &callee_stack); redirect_env(engine_state, caller_stack, &callee_stack);
} else { } else {
caller_stack.add_overlay(overlay_name); caller_stack.add_overlay(overlay_name);
// make sure that PWD is not restored in old overlays.
if let Some(cwd) = cwd {
caller_stack.add_env_var("PWD".to_string(), cwd);
}
} }
} else { } else {
caller_stack.add_overlay(overlay_name); caller_stack.add_overlay(overlay_name);

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