mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 07:00:37 +02:00
Compare commits
191 Commits
Author | SHA1 | Date | |
---|---|---|---|
6e1e824473 | |||
af77bc60e2 | |||
9ca0fb772d | |||
c535c24d03 | |||
c9cb62067c | |||
ebc7b80c23 | |||
aaaab8e070 | |||
a59477205d | |||
5101b5e306 | |||
fb34a4fc6c | |||
1bcceafd93 | |||
217eb4ed70 | |||
78af66f2ce | |||
f63cecc316 | |||
8d60c0d35d | |||
0c139c7411 | |||
c4bac90b35 | |||
2ab8751ff9 | |||
d192d854d6 | |||
6b5906613c | |||
493850b1bf | |||
6600b3edfb | |||
aff974552a | |||
2afc6a974e | |||
1e64f59220 | |||
3e074bc447 | |||
3d008e2c4e | |||
6c1c7f9509 | |||
f531cc2058 | |||
edd69aa283 | |||
3d20c53904 | |||
2d360fda7f | |||
d0c2adabf7 | |||
870eb2530c | |||
b2cab3274b | |||
92091599ff | |||
4d2d553cca | |||
dc53c20628 | |||
e7c5f83460 | |||
f611196373 | |||
abd230e12e | |||
4792328d0e | |||
63b94dbd28 | |||
eb0de25d19 | |||
ebe42241fe | |||
fa4f9b083e | |||
e7b7c7f39a | |||
6be458c686 | |||
348c59b740 | |||
2c827d2bf4 | |||
61544eecd6 | |||
c150af4279 | |||
39bda8986e | |||
ee997ef3dd | |||
e3f59910b8 | |||
f4940e115f | |||
3f31ca7b8e | |||
0119534f61 | |||
84e1ac27e5 | |||
644bebf4c6 | |||
f58a4b5017 | |||
ae0e13733d | |||
2c379cba71 | |||
7171c9b84a | |||
055d7e27e9 | |||
af76e11dd6 | |||
7dda39a89e | |||
4f822e263f | |||
a39e94de8a | |||
a88f46c6c9 | |||
4ca1f95b6c | |||
71ced35987 | |||
1128df2d29 | |||
da98c23ab3 | |||
e3efc8da9f | |||
3f332bef35 | |||
525eac1afd | |||
7003b007d5 | |||
dfdb2b5d31 | |||
822007dbbb | |||
0560826414 | |||
39b0f3bdda | |||
712fec166d | |||
43dcf19ac3 | |||
ffddee5678 | |||
95b78eee25 | |||
3ab9f0b90a | |||
9261c0c55a | |||
7a888c9e9b | |||
e211b7ba53 | |||
60769ac1ba | |||
34e7bd861c | |||
d667b3c0bc | |||
b63c3514c4 | |||
7e9d32d64e | |||
621fb25670 | |||
a80273bd7b | |||
5473def7ef | |||
04746b8e2d | |||
31b3104af7 | |||
803bc9c63f | |||
e690e7aac0 | |||
e841fce0f9 | |||
d7b0dc1275 | |||
5627c95916 | |||
f122065772 | |||
4732507f46 | |||
5f45f6c223 | |||
a55d172e52 | |||
48e401834d | |||
d5946a9667 | |||
a432bf94ec | |||
0eabbb88dd | |||
80c8edcfb4 | |||
059167ac96 | |||
4e205cd9a7 | |||
18772b73b3 | |||
983014cc40 | |||
4ff33933dd | |||
035308bb1d | |||
e530e7d654 | |||
7d4449f021 | |||
ec3e0e593d | |||
ff09c7964e | |||
ce13ecfd10 | |||
c18e6bfca0 | |||
bc6947cd09 | |||
faaa12838e | |||
edee2a3c15 | |||
2ced9e4d19 | |||
926331dbfb | |||
eca2975b3d | |||
1cd0544a3f | |||
73e8de9753 | |||
4e83ccdf86 | |||
6d36941e55 | |||
2f44801414 | |||
9172b22985 | |||
1c37f4b958 | |||
56ed532038 | |||
b974f8f7e3 | |||
802bfed173 | |||
07e7c8c81f | |||
20b53067cd | |||
f4c0d9d45b | |||
85b06b22d9 | |||
63f00e78d1 | |||
ff1ad77130 | |||
af34d5c062 | |||
ed82f9ee18 | |||
d081e3386f | |||
ca8eb856e8 | |||
168835ecd2 | |||
4157ca711d | |||
d34a24db33 | |||
2c6b1471e1 | |||
ae5fed41ed | |||
ca73d85c09 | |||
f82c43f850 | |||
3dc9691aaa | |||
42531e017c | |||
928c57db41 | |||
d880241102 | |||
813aac89bd | |||
d2bf82d22b | |||
3f12b14053 | |||
8e2917b9ae | |||
3c3ec7891c | |||
6b839c3c32 | |||
ea22c319b6 | |||
7432e67da1 | |||
0576794e74 | |||
12f57dbc62 | |||
fe57c5c22e | |||
18161e5707 | |||
466b3899e0 | |||
7b82c6b482 | |||
e3f78b8793 | |||
c31291753c | |||
f7d6c28a00 | |||
d618fd0527 | |||
d80de68665 | |||
5f7afafe51 | |||
53fbf62493 | |||
e68f744dda | |||
e2d0514bb5 | |||
4a7d4401b8 | |||
6446f26283 | |||
9f90d611e1 | |||
a88c3f48e2 | |||
5c2439abc0 |
16
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
16
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -13,7 +13,7 @@ body:
|
||||
id: repro
|
||||
attributes:
|
||||
label: How to reproduce
|
||||
description: Steps to reproduce the behavior
|
||||
description: Steps to reproduce the behavior (including succinct code examples or screenshots of the observed behavior)
|
||||
placeholder: |
|
||||
1.
|
||||
2.
|
||||
@ -28,13 +28,6 @@ body:
|
||||
placeholder: I expected nu to...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: Please add any relevant screenshots here, if any
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: config
|
||||
attributes:
|
||||
@ -55,10 +48,3 @@ body:
|
||||
| installed_plugins | binaryview, chart bar, chart line, fetch, from bson, from sqlite, inc, match, post, ps, query json, s3, selector, start, sys, textview, to bson, to sqlite, tree, xpath |
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
||||
validations:
|
||||
required: false
|
||||
|
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@ -57,11 +57,6 @@ jobs:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
platform: [windows-latest, macos-latest, ubuntu-20.04]
|
||||
include:
|
||||
- default-flags: ""
|
||||
# linux CI cannot handle clipboard feature
|
||||
- platform: ubuntu-20.04
|
||||
default-flags: "--no-default-features --features=default-no-clipboard"
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
@ -72,7 +67,7 @@ jobs:
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
|
||||
- name: Tests
|
||||
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.default-flags }}
|
||||
run: cargo test --workspace --profile ci --exclude nu_plugin_*
|
||||
- name: Check for clean repo
|
||||
shell: bash
|
||||
run: |
|
||||
|
20
.github/workflows/nightly-build.yml
vendored
20
.github/workflows/nightly-build.yml
vendored
@ -36,10 +36,10 @@ jobs:
|
||||
token: ${{ secrets.WORKFLOW_TOKEN }}
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3.12
|
||||
uses: hustcer/setup-nu@v3.13
|
||||
if: github.repository == 'nushell/nightly'
|
||||
with:
|
||||
version: 0.95.0
|
||||
version: 0.97.1
|
||||
|
||||
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
|
||||
- name: Prepare for Nightly Release
|
||||
@ -78,7 +78,9 @@ jobs:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
- aarch64-unknown-linux-gnu
|
||||
- aarch64-unknown-linux-musl
|
||||
- armv7-unknown-linux-gnueabihf
|
||||
- armv7-unknown-linux-musleabihf
|
||||
- riscv64gc-unknown-linux-gnu
|
||||
extra: ['bin']
|
||||
include:
|
||||
@ -104,8 +106,12 @@ jobs:
|
||||
os: ubuntu-22.04
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-22.04
|
||||
- target: aarch64-unknown-linux-musl
|
||||
os: ubuntu-22.04
|
||||
- target: armv7-unknown-linux-gnueabihf
|
||||
os: ubuntu-22.04
|
||||
- target: armv7-unknown-linux-musleabihf
|
||||
os: ubuntu-22.04
|
||||
- target: riscv64gc-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
|
||||
@ -128,9 +134,9 @@ jobs:
|
||||
rustflags: ''
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3.12
|
||||
uses: hustcer/setup-nu@v3.13
|
||||
with:
|
||||
version: 0.95.0
|
||||
version: 0.97.1
|
||||
|
||||
- name: Release Nu Binary
|
||||
id: nu
|
||||
@ -161,7 +167,7 @@ jobs:
|
||||
# REF: https://github.com/marketplace/actions/gh-release
|
||||
# Create a release only in nushell/nightly repo
|
||||
- name: Publish Archive
|
||||
uses: softprops/action-gh-release@v2.0.6
|
||||
uses: softprops/action-gh-release@v2.0.8
|
||||
if: ${{ startsWith(github.repository, 'nushell/nightly') }}
|
||||
with:
|
||||
prerelease: true
|
||||
@ -186,9 +192,9 @@ jobs:
|
||||
ref: main
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3.12
|
||||
uses: hustcer/setup-nu@v3.13
|
||||
with:
|
||||
version: 0.95.0
|
||||
version: 0.97.1
|
||||
|
||||
# Keep the last a few releases
|
||||
- name: Delete Older Releases
|
||||
|
20
.github/workflows/release-pkg.nu
vendored
20
.github/workflows/release-pkg.nu
vendored
@ -84,6 +84,20 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
|
||||
$env.CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER = 'arm-linux-gnueabihf-gcc'
|
||||
cargo-build-nu
|
||||
}
|
||||
'aarch64-unknown-linux-musl' => {
|
||||
aria2c https://musl.cc/aarch64-linux-musl-cross.tgz
|
||||
tar -xf aarch64-linux-musl-cross.tgz -C $env.HOME
|
||||
$env.PATH = ($env.PATH | split row (char esep) | prepend $'($env.HOME)/aarch64-linux-musl-cross/bin')
|
||||
$env.CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER = 'aarch64-linux-musl-gcc'
|
||||
cargo-build-nu
|
||||
}
|
||||
'armv7-unknown-linux-musleabihf' => {
|
||||
aria2c https://musl.cc/armv7r-linux-musleabihf-cross.tgz
|
||||
tar -xf armv7r-linux-musleabihf-cross.tgz -C $env.HOME
|
||||
$env.PATH = ($env.PATH | split row (char esep) | prepend $'($env.HOME)/armv7r-linux-musleabihf-cross/bin')
|
||||
$env.CARGO_TARGET_ARMV7_UNKNOWN_LINUX_MUSLEABIHF_LINKER = 'armv7r-linux-musleabihf-gcc'
|
||||
cargo-build-nu
|
||||
}
|
||||
_ => {
|
||||
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
||||
# Actually just for x86_64-unknown-linux-musl target
|
||||
@ -161,8 +175,12 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
|
||||
let releaseStem = $'($bin)-($version)-($target)'
|
||||
|
||||
print $'(char nl)Download less related stuffs...'; hr-line
|
||||
# todo: less-v661 is out but is released as a zip file. maybe we should switch to that and extract it?
|
||||
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v608/less.exe -o less.exe
|
||||
aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt
|
||||
# the below was renamed because it was failing to download for darren. it should work but it wasn't
|
||||
# todo: maybe we should get rid of this aria2c dependency and just use http get?
|
||||
#aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt
|
||||
aria2c https://github.com/jftuga/less-Windows/blob/master/LICENSE -o LICENSE-for-less.txt
|
||||
|
||||
# Create Windows msi release package
|
||||
if (get-env _EXTRA_) == 'msi' {
|
||||
|
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@ -28,7 +28,9 @@ jobs:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
- aarch64-unknown-linux-gnu
|
||||
- aarch64-unknown-linux-musl
|
||||
- armv7-unknown-linux-gnueabihf
|
||||
- armv7-unknown-linux-musleabihf
|
||||
- riscv64gc-unknown-linux-gnu
|
||||
extra: ['bin']
|
||||
include:
|
||||
@ -54,8 +56,12 @@ jobs:
|
||||
os: ubuntu-22.04
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-22.04
|
||||
- target: aarch64-unknown-linux-musl
|
||||
os: ubuntu-22.04
|
||||
- target: armv7-unknown-linux-gnueabihf
|
||||
os: ubuntu-22.04
|
||||
- target: armv7-unknown-linux-musleabihf
|
||||
os: ubuntu-22.04
|
||||
- target: riscv64gc-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
|
||||
@ -76,9 +82,9 @@ jobs:
|
||||
rustflags: ''
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3.12
|
||||
uses: hustcer/setup-nu@v3.13
|
||||
with:
|
||||
version: 0.95.0
|
||||
version: 0.97.1
|
||||
|
||||
- name: Release Nu Binary
|
||||
id: nu
|
||||
@ -91,7 +97,7 @@ jobs:
|
||||
|
||||
# REF: https://github.com/marketplace/actions/gh-release
|
||||
- name: Publish Archive
|
||||
uses: softprops/action-gh-release@v2.0.6
|
||||
uses: softprops/action-gh-release@v2.0.8
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
with:
|
||||
draft: true
|
||||
|
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
@ -10,4 +10,4 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Check spelling
|
||||
uses: crate-ci/typos@v1.23.2
|
||||
uses: crate-ci/typos@v1.24.5
|
||||
|
1659
Cargo.lock
generated
1659
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
91
Cargo.toml
91
Cargo.toml
@ -10,8 +10,8 @@ homepage = "https://www.nushell.sh"
|
||||
license = "MIT"
|
||||
name = "nu"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
rust-version = "1.77.2"
|
||||
version = "0.96.0"
|
||||
rust-version = "1.79.0"
|
||||
version = "0.98.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -69,18 +69,17 @@ base64 = "0.22.1"
|
||||
bracoxide = "0.1.2"
|
||||
brotli = "5.0"
|
||||
byteorder = "1.5"
|
||||
bytes = "1"
|
||||
bytesize = "1.3"
|
||||
calamine = "0.24.0"
|
||||
chardetng = "0.1.17"
|
||||
chrono = { default-features = false, version = "0.4.34" }
|
||||
chrono-humanize = "0.2.3"
|
||||
chrono-tz = "0.8"
|
||||
convert_case = "0.6"
|
||||
crossbeam-channel = "0.5.8"
|
||||
crossterm = "0.27"
|
||||
crossterm = "0.28.1"
|
||||
csv = "1.3"
|
||||
ctrlc = "3.4"
|
||||
deunicode = "1.6.0"
|
||||
dialoguer = { default-features = false, version = "0.11" }
|
||||
digest = { default-features = false, version = "0.10" }
|
||||
dirs = "5.0"
|
||||
@ -90,16 +89,14 @@ encoding_rs = "0.8"
|
||||
fancy-regex = "0.13"
|
||||
filesize = "0.2"
|
||||
filetime = "0.2"
|
||||
fs_extra = "1.3"
|
||||
fuzzy-matcher = "0.3"
|
||||
hamcrest2 = "0.3"
|
||||
heck = "0.5.0"
|
||||
human-date-parser = "0.1.1"
|
||||
indexmap = "2.2"
|
||||
indexmap = "2.5"
|
||||
indicatif = "0.17"
|
||||
interprocess = "2.2.0"
|
||||
is_executable = "1.0"
|
||||
itertools = "0.12"
|
||||
itertools = "0.13"
|
||||
libc = "0.2"
|
||||
libproc = "0.14"
|
||||
log = "0.4"
|
||||
@ -110,11 +107,12 @@ lsp-types = "0.95.0"
|
||||
mach2 = "0.4"
|
||||
md5 = { version = "0.10", package = "md-5" }
|
||||
miette = "7.2"
|
||||
mime = "0.3"
|
||||
mime = "0.3.17"
|
||||
mime_guess = "2.0"
|
||||
mockito = { version = "1.4", default-features = false }
|
||||
mockito = { version = "1.5", default-features = false }
|
||||
multipart-rs = "0.1.11"
|
||||
native-tls = "0.2"
|
||||
nix = { version = "0.28", default-features = false }
|
||||
nix = { version = "0.29", default-features = false }
|
||||
notify-debouncer-full = { version = "0.3", default-features = false }
|
||||
nu-ansi-term = "0.50.1"
|
||||
num-format = "0.4"
|
||||
@ -131,14 +129,15 @@ proc-macro-error = { version = "1.0", default-features = false }
|
||||
proc-macro2 = "1.0"
|
||||
procfs = "0.16.0"
|
||||
pwd = "1.3"
|
||||
quick-xml = "0.31.0"
|
||||
quick-xml = "0.32.0"
|
||||
quickcheck = "1.0"
|
||||
quickcheck_macros = "1.0"
|
||||
quote = "1.0"
|
||||
rand = "0.8"
|
||||
rand_chacha = "0.3.1"
|
||||
ratatui = "0.26"
|
||||
rayon = "1.10"
|
||||
reedline = "0.33.0"
|
||||
reedline = "0.35.0"
|
||||
regex = "1.9.5"
|
||||
rmp = "0.8"
|
||||
rmp-serde = "1.3"
|
||||
@ -147,8 +146,7 @@ roxmltree = "0.19"
|
||||
rstest = { version = "0.18", default-features = false }
|
||||
rusqlite = "0.31"
|
||||
rust-embed = "8.5.0"
|
||||
same-file = "1.0"
|
||||
serde = { version = "1.0", default-features = false }
|
||||
serde = { version = "1.0" }
|
||||
serde_json = "1.0"
|
||||
serde_urlencoded = "0.7.1"
|
||||
serde_yaml = "0.9"
|
||||
@ -156,7 +154,7 @@ sha2 = "0.10"
|
||||
strip-ansi-escapes = "0.2.0"
|
||||
syn = "2.0"
|
||||
sysinfo = "0.30"
|
||||
tabled = { version = "0.14.0", default-features = false }
|
||||
tabled = { version = "0.16.0", default-features = false }
|
||||
tempfile = "3.10"
|
||||
terminal_size = "0.3"
|
||||
titlecase = "2.0"
|
||||
@ -182,23 +180,31 @@ windows = "0.54"
|
||||
windows-sys = "0.48"
|
||||
winreg = "0.52"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
# Warning: workspace lints affect library code as well as tests, so don't enable lints that would be too noisy in tests like that.
|
||||
# todo = "warn"
|
||||
unchecked_duration_subtraction = "warn"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.96.0" }
|
||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.96.0" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.96.0" }
|
||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.96.0", optional = true }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.96.0" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.96.0" }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.96.0" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.96.0" }
|
||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.96.0" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.96.0" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.96.0" }
|
||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.96.0" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.96.0" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.96.0" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.96.0" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.96.0" }
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.98.0" }
|
||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.98.0" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.98.0" }
|
||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.98.0", optional = true }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.98.0" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.98.0" }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.98.0" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.98.0" }
|
||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.98.0" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.98.0" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.98.0" }
|
||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.98.0" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.98.0" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.98.0" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.98.0" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.98.0" }
|
||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||
|
||||
crossterm = { workspace = true }
|
||||
@ -207,6 +213,7 @@ dirs = { workspace = true }
|
||||
log = { workspace = true }
|
||||
miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] }
|
||||
mimalloc = { version = "0.1.42", default-features = false, optional = true }
|
||||
multipart-rs = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
simplelog = "0.12"
|
||||
time = "0.3"
|
||||
@ -227,9 +234,9 @@ nix = { workspace = true, default-features = false, features = [
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.96.0" }
|
||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.96.0" }
|
||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.96.0" }
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.98.0" }
|
||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.98.0" }
|
||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.98.0" }
|
||||
assert_cmd = "2.0"
|
||||
dirs = { workspace = true }
|
||||
tango-bench = "0.5"
|
||||
@ -249,10 +256,8 @@ plugin = [
|
||||
"nu-protocol/plugin",
|
||||
"nu-engine/plugin",
|
||||
]
|
||||
default = ["default-no-clipboard", "system-clipboard"]
|
||||
# Enables convenient omitting of the system-clipboard feature, as it leads to problems in ci on linux
|
||||
# See https://github.com/nushell/nushell/pull/11535
|
||||
default-no-clipboard = [
|
||||
|
||||
default = [
|
||||
"plugin",
|
||||
"trash-support",
|
||||
"sqlite",
|
||||
@ -266,6 +271,8 @@ stable = ["default"]
|
||||
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.
|
||||
# Missing X server/ Wayland can cause issues
|
||||
system-clipboard = [
|
||||
"reedline/system_clipboard",
|
||||
"nu-cli/system-clipboard",
|
||||
@ -305,7 +312,7 @@ bench = false
|
||||
|
||||
# To use a development version of a dependency please use a global override here
|
||||
# changing versions in each sub-crate of the workspace is tedious
|
||||
# [patch.crates-io]
|
||||
[patch.crates-io]
|
||||
# reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
||||
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
|
||||
|
||||
@ -313,4 +320,4 @@ bench = false
|
||||
# Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
harness = false
|
||||
harness = false
|
||||
|
29
SECURITY.md
Normal file
29
SECURITY.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Security Policy
|
||||
|
||||
As a shell and programming language Nushell provides you with great powers and the potential to do dangerous things to your computer and data. Whenever there is a risk that a malicious actor can abuse a bug or a violation of documented behavior/assumptions in Nushell to harm you this is a *security* risk.
|
||||
We want to fix those issues without exposing our users to unnecessary risk. Thus we want to explain our security policy.
|
||||
Additional issues may be part of *safety* where the behavior of Nushell as designed and implemented can cause unintended harm or a bug causes damage without the involvement of a third party.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
As Nushell is still under very active pre-stable development, the only version the core team prioritizes for security and safety fixes is the [most recent version as published on GitHub](https://github.com/nushell/nushell/releases/latest).
|
||||
Only if you provide a strong reasoning and the necessary resources, will we consider blessing a backported fix with an official patch release for a previous version.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you suspect that a bug or behavior of Nushell can affect security or may be potentially exploitable, please report the issue to us in private.
|
||||
Either reach out to the core team on [our Discord server](https://discord.gg/NtAbbGn) to arrange a private channel or use the [GitHub vulnerability reporting form](https://github.com/nushell/nushell/security/advisories/new).
|
||||
Please try to answer the following questions:
|
||||
- How can we reach you for further questions?
|
||||
- What is the bug? Which system of Nushell may be affected?
|
||||
- Do you have proof-of-concept for a potential exploit or have you observed an exploit in the wild?
|
||||
- What is your assessment of the severity based on what could be impacted should the bug be exploited?
|
||||
- Are additional people aware of the issue or deserve credit for identifying the issue?
|
||||
|
||||
We will try to get back to you within a week with:
|
||||
- acknowledging the receipt of the report
|
||||
- an initial plan of how we want to address this including the primary points of contact for further communication
|
||||
- our preliminary assessment of how severe we judge the issue
|
||||
- a proposal for how we can coordinate responsible disclosure (e.g. how we ship the bugfix, if we need to coordinate with distribution maintainers, when you can release a blog post if you want to etc.)
|
||||
|
||||
For purely *safety* related issues where the impact is severe by direct user action instead of malicious input or third parties, feel free to open a regular issue. If we deem that there may be an additional *security* risk on a *safety* issue we may continue discussions in a restricted forum.
|
@ -46,8 +46,8 @@ fn setup_stack_and_engine_from_command(command: &str) -> (Stack, EngineState) {
|
||||
|
||||
let mut stack = Stack::new();
|
||||
|
||||
// Support running benchmarks with IR mode
|
||||
stack.use_ir = std::env::var_os("NU_USE_IR").is_some();
|
||||
// Support running benchmarks without IR mode
|
||||
stack.use_ir = std::env::var_os("NU_DISABLE_IR").is_none();
|
||||
|
||||
evaluate_commands(
|
||||
&commands,
|
||||
|
@ -5,27 +5,27 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.96.0"
|
||||
version = "0.98.0"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.96.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.96.0" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.98.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.98.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.98.0" }
|
||||
rstest = { workspace = true, default-features = false }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.96.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.96.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.96.0" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.0", optional = true }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.96.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.96.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.96.0" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.98.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.98.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.98.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.98.0" }
|
||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.98.0", optional = true }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.98.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.98.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.98.0" }
|
||||
nu-ansi-term = { workspace = true }
|
||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||
|
||||
@ -46,4 +46,7 @@ which = { workspace = true }
|
||||
|
||||
[features]
|
||||
plugin = ["nu-plugin-engine"]
|
||||
system-clipboard = ["reedline/system_clipboard"]
|
||||
system-clipboard = ["reedline/system_clipboard"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
@ -14,7 +14,7 @@ impl Command for Commandline {
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"View the current command line input buffer."
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Modify the current command line input buffer."
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Get the current cursor position."
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Set the current cursor position."
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ impl Command for History {
|
||||
"history"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Get the command history."
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ impl Command for History {
|
||||
HistoryFileFormat::Sqlite => {
|
||||
history_path.push("history.sqlite3");
|
||||
}
|
||||
HistoryFileFormat::PlainText => {
|
||||
HistoryFileFormat::Plaintext => {
|
||||
history_path.push("history.txt");
|
||||
}
|
||||
}
|
||||
@ -67,7 +67,7 @@ impl Command for History {
|
||||
} else {
|
||||
let history_reader: Option<Box<dyn ReedlineHistory>> = match history.file_format {
|
||||
HistoryFileFormat::Sqlite => {
|
||||
SqliteBackedHistory::with_file(history_path.clone(), None, None)
|
||||
SqliteBackedHistory::with_file(history_path.clone().into(), None, None)
|
||||
.map(|inner| {
|
||||
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
||||
boxed
|
||||
@ -75,9 +75,9 @@ impl Command for History {
|
||||
.ok()
|
||||
}
|
||||
|
||||
HistoryFileFormat::PlainText => FileBackedHistory::with_file(
|
||||
HistoryFileFormat::Plaintext => FileBackedHistory::with_file(
|
||||
history.max_size as usize,
|
||||
history_path.clone(),
|
||||
history_path.clone().into(),
|
||||
)
|
||||
.map(|inner| {
|
||||
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
||||
@ -87,7 +87,7 @@ impl Command for History {
|
||||
};
|
||||
|
||||
match history.file_format {
|
||||
HistoryFileFormat::PlainText => Ok(history_reader
|
||||
HistoryFileFormat::Plaintext => Ok(history_reader
|
||||
.and_then(|h| {
|
||||
h.search(SearchQuery::everything(SearchDirection::Forward, None))
|
||||
.ok()
|
||||
@ -156,58 +156,34 @@ fn create_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span)
|
||||
//2. Create a record of either short or long columns and values
|
||||
|
||||
let item_id_value = Value::int(
|
||||
match entry.id {
|
||||
Some(id) => {
|
||||
let ids = id.to_string();
|
||||
match ids.parse::<i64>() {
|
||||
Ok(i) => i,
|
||||
_ => 0i64,
|
||||
}
|
||||
}
|
||||
None => 0i64,
|
||||
},
|
||||
entry
|
||||
.id
|
||||
.and_then(|id| id.to_string().parse::<i64>().ok())
|
||||
.unwrap_or_default(),
|
||||
head,
|
||||
);
|
||||
let start_timestamp_value = Value::string(
|
||||
match entry.start_timestamp {
|
||||
Some(time) => time.to_string(),
|
||||
None => "".into(),
|
||||
},
|
||||
entry
|
||||
.start_timestamp
|
||||
.map(|time| time.to_string())
|
||||
.unwrap_or_default(),
|
||||
head,
|
||||
);
|
||||
let command_value = Value::string(entry.command_line, head);
|
||||
let session_id_value = Value::int(
|
||||
match entry.session_id {
|
||||
Some(sid) => {
|
||||
let sids = sid.to_string();
|
||||
match sids.parse::<i64>() {
|
||||
Ok(i) => i,
|
||||
_ => 0i64,
|
||||
}
|
||||
}
|
||||
None => 0i64,
|
||||
},
|
||||
head,
|
||||
);
|
||||
let hostname_value = Value::string(
|
||||
match entry.hostname {
|
||||
Some(host) => host,
|
||||
None => "".into(),
|
||||
},
|
||||
head,
|
||||
);
|
||||
let cwd_value = Value::string(
|
||||
match entry.cwd {
|
||||
Some(cwd) => cwd,
|
||||
None => "".into(),
|
||||
},
|
||||
entry
|
||||
.session_id
|
||||
.and_then(|id| id.to_string().parse::<i64>().ok())
|
||||
.unwrap_or_default(),
|
||||
head,
|
||||
);
|
||||
let hostname_value = Value::string(entry.hostname.unwrap_or_default(), head);
|
||||
let cwd_value = Value::string(entry.cwd.unwrap_or_default(), head);
|
||||
let duration_value = Value::duration(
|
||||
match entry.duration {
|
||||
Some(d) => d.as_nanos().try_into().unwrap_or(0),
|
||||
None => 0,
|
||||
},
|
||||
entry
|
||||
.duration
|
||||
.and_then(|d| d.as_nanos().try_into().ok())
|
||||
.unwrap_or(0),
|
||||
head,
|
||||
);
|
||||
let exit_status_value = Value::int(entry.exit_status.unwrap_or(0), head);
|
||||
|
@ -8,7 +8,7 @@ impl Command for HistorySession {
|
||||
"history session"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Get the command history session."
|
||||
}
|
||||
|
||||
|
@ -14,11 +14,11 @@ impl Command for Keybindings {
|
||||
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Keybindings related commands."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
fn extra_description(&self) -> &str {
|
||||
r#"You must use one of the following subcommands. Using this command as-is will only produce this help message.
|
||||
|
||||
For more information on input and keybindings, check:
|
||||
|
@ -15,7 +15,7 @@ impl Command for KeybindingsDefault {
|
||||
.input_output_types(vec![(Type::Nothing, Type::table())])
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"List default keybindings."
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ impl Command for KeybindingsList {
|
||||
.category(Category::Platform)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"List available options that can be used to create keybindings."
|
||||
}
|
||||
|
||||
@ -61,10 +61,12 @@ impl Command for KeybindingsList {
|
||||
.map(|option| call.has_flag(engine_state, stack, option))
|
||||
.collect::<Result<Vec<_>, ShellError>>()?;
|
||||
|
||||
let no_option_specified = presence.iter().all(|present| !*present);
|
||||
|
||||
let records = all_options
|
||||
.iter()
|
||||
.zip(presence)
|
||||
.filter(|(_, present)| *present)
|
||||
.filter(|(_, present)| no_option_specified || *present)
|
||||
.flat_map(|(option, _)| get_records(option, call.head))
|
||||
.collect();
|
||||
|
||||
|
@ -12,11 +12,11 @@ impl Command for KeybindingsListen {
|
||||
"keybindings listen"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Get input from the user."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
fn extra_description(&self) -> &str {
|
||||
"This is an internal debugging tool. For better output, try `input listen --types [key]`"
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
completions::{Completer, CompletionOptions, MatchAlgorithm, SortBy},
|
||||
completions::{Completer, CompletionOptions, MatchAlgorithm},
|
||||
SuggestionKind,
|
||||
};
|
||||
use nu_parser::FlatShape;
|
||||
@ -51,7 +51,9 @@ impl CommandCompletion {
|
||||
if working_set
|
||||
.permanent_state
|
||||
.config
|
||||
.max_external_completion_results
|
||||
.completions
|
||||
.external
|
||||
.max_results
|
||||
> executables.len() as i64
|
||||
&& !executables.contains(
|
||||
&item
|
||||
@ -99,10 +101,9 @@ impl CommandCompletion {
|
||||
suggestion: Suggestion {
|
||||
value: String::from_utf8_lossy(&x.0).to_string(),
|
||||
description: x.1,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||
append_whitespace: true,
|
||||
..Suggestion::default()
|
||||
},
|
||||
kind: Some(SuggestionKind::Command(x.2)),
|
||||
})
|
||||
@ -118,11 +119,9 @@ impl CommandCompletion {
|
||||
.map(move |x| SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: x,
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||
append_whitespace: true,
|
||||
..Suggestion::default()
|
||||
},
|
||||
// TODO: is there a way to create a test?
|
||||
kind: None,
|
||||
@ -136,11 +135,9 @@ impl CommandCompletion {
|
||||
results.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: format!("^{}", external.suggestion.value),
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: external.suggestion.span,
|
||||
append_whitespace: true,
|
||||
..Suggestion::default()
|
||||
},
|
||||
kind: external.kind,
|
||||
})
|
||||
@ -198,11 +195,7 @@ impl Completer for CommandCompletion {
|
||||
};
|
||||
|
||||
if !subcommands.is_empty() {
|
||||
return sort_suggestions(
|
||||
&String::from_utf8_lossy(&prefix),
|
||||
subcommands,
|
||||
SortBy::LevenshteinDistance,
|
||||
);
|
||||
return sort_suggestions(&String::from_utf8_lossy(&prefix), subcommands, options);
|
||||
}
|
||||
|
||||
let config = working_set.get_config();
|
||||
@ -220,18 +213,14 @@ impl Completer for CommandCompletion {
|
||||
working_set,
|
||||
span,
|
||||
offset,
|
||||
config.enable_external_completion,
|
||||
config.completions.external.enable,
|
||||
options.match_algorithm,
|
||||
)
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
sort_suggestions(
|
||||
&String::from_utf8_lossy(&prefix),
|
||||
commands,
|
||||
SortBy::LevenshteinDistance,
|
||||
)
|
||||
sort_suggestions(&String::from_utf8_lossy(&prefix), commands, options)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,8 +46,9 @@ impl NuCompleter {
|
||||
let config = self.engine_state.get_config();
|
||||
|
||||
let options = CompletionOptions {
|
||||
case_sensitive: config.case_sensitive_completions,
|
||||
match_algorithm: config.completion_algorithm.into(),
|
||||
case_sensitive: config.completions.case_sensitive,
|
||||
match_algorithm: config.completions.algorithm.into(),
|
||||
sort: config.completions.sort,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@ -207,7 +208,7 @@ impl NuCompleter {
|
||||
|
||||
// We got no results for internal completion
|
||||
// now we can check if external completer is set and use it
|
||||
if let Some(closure) = config.external_completer.as_ref() {
|
||||
if let Some(closure) = config.completions.external.completer.as_ref() {
|
||||
if let Some(external_result) =
|
||||
self.external_completion(closure, &spans, fake_offset, new_span)
|
||||
{
|
||||
@ -337,7 +338,9 @@ impl NuCompleter {
|
||||
}
|
||||
|
||||
// Try to complete using an external completer (if set)
|
||||
if let Some(closure) = config.external_completer.as_ref() {
|
||||
if let Some(closure) =
|
||||
config.completions.external.completer.as_ref()
|
||||
{
|
||||
if let Some(external_result) = self.external_completion(
|
||||
closure,
|
||||
&spans,
|
||||
@ -443,14 +446,11 @@ pub fn map_value_completions<'a>(
|
||||
return Some(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: s,
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: false,
|
||||
..Suggestion::default()
|
||||
},
|
||||
kind: Some(SuggestionKind::Type(x.get_type())),
|
||||
});
|
||||
@ -460,14 +460,11 @@ pub fn map_value_completions<'a>(
|
||||
if let Ok(record) = x.as_record() {
|
||||
let mut suggestion = Suggestion {
|
||||
value: String::from(""), // Initialize with empty string
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: false,
|
||||
..Suggestion::default()
|
||||
};
|
||||
|
||||
// Iterate the cols looking for `value` and `description`
|
||||
|
@ -1,20 +1,19 @@
|
||||
use super::MatchAlgorithm;
|
||||
use crate::{
|
||||
completions::{matches, CompletionOptions},
|
||||
SemanticSuggestion,
|
||||
};
|
||||
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
||||
use nu_ansi_term::Style;
|
||||
use nu_engine::env_to_string;
|
||||
use nu_path::dots::expand_ndots;
|
||||
use nu_path::{expand_to_real_path, home_dir};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
levenshtein_distance, Span,
|
||||
CompletionSort, Span,
|
||||
};
|
||||
use nu_utils::get_ls_colors;
|
||||
use std::path::{
|
||||
is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR,
|
||||
};
|
||||
|
||||
use super::SortBy;
|
||||
use std::path::{is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PathBuiltFromString {
|
||||
@ -22,22 +21,31 @@ pub struct PathBuiltFromString {
|
||||
isdir: bool,
|
||||
}
|
||||
|
||||
fn complete_rec(
|
||||
/// Recursively goes through paths that match a given `partial`.
|
||||
/// built: State struct for a valid matching path built so far.
|
||||
///
|
||||
/// `isdir`: whether the current partial path has a trailing slash.
|
||||
/// Parsing a path string into a pathbuf loses that bit of information.
|
||||
///
|
||||
/// 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.
|
||||
pub fn complete_rec(
|
||||
partial: &[&str],
|
||||
built: &PathBuiltFromString,
|
||||
cwd: &Path,
|
||||
options: &CompletionOptions,
|
||||
dir: bool,
|
||||
want_directory: bool,
|
||||
isdir: bool,
|
||||
) -> Vec<PathBuiltFromString> {
|
||||
let mut completions = vec![];
|
||||
|
||||
if let Some((&base, rest)) = partial.split_first() {
|
||||
if (base == "." || base == "..") && (isdir || !rest.is_empty()) {
|
||||
if base.chars().all(|c| c == '.') && (isdir || !rest.is_empty()) {
|
||||
let mut built = built.clone();
|
||||
built.parts.push(base.to_string());
|
||||
built.isdir = true;
|
||||
return complete_rec(rest, &built, cwd, options, dir, isdir);
|
||||
return complete_rec(rest, &built, cwd, options, want_directory, isdir);
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,24 +66,41 @@ fn complete_rec(
|
||||
built.parts.push(entry_name.clone());
|
||||
built.isdir = entry_isdir;
|
||||
|
||||
if !dir || entry_isdir {
|
||||
if !want_directory || entry_isdir {
|
||||
entries.push((entry_name, built));
|
||||
}
|
||||
}
|
||||
|
||||
let prefix = partial.first().unwrap_or(&"");
|
||||
let sorted_entries = sort_completions(prefix, entries, SortBy::Ascending, |(entry, _)| entry);
|
||||
let sorted_entries = sort_completions(prefix, entries, options, |(entry, _)| entry);
|
||||
|
||||
for (entry_name, built) in sorted_entries {
|
||||
match partial.split_first() {
|
||||
Some((base, rest)) => {
|
||||
if matches(base, &entry_name, options) {
|
||||
// We use `isdir` to confirm that the current component has
|
||||
// at least one next component or a slash.
|
||||
// Serves as confirmation to ignore longer completions for
|
||||
// components in between.
|
||||
if !rest.is_empty() || isdir {
|
||||
completions.extend(complete_rec(rest, &built, cwd, options, dir, isdir));
|
||||
completions.extend(complete_rec(
|
||||
rest,
|
||||
&built,
|
||||
cwd,
|
||||
options,
|
||||
want_directory,
|
||||
isdir,
|
||||
));
|
||||
} else {
|
||||
completions.push(built);
|
||||
}
|
||||
}
|
||||
if entry_name.eq(base)
|
||||
&& matches!(options.match_algorithm, MatchAlgorithm::Prefix)
|
||||
&& isdir
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
completions.push(built);
|
||||
@ -93,16 +118,16 @@ enum OriginalCwd {
|
||||
}
|
||||
|
||||
impl OriginalCwd {
|
||||
fn apply(&self, mut p: PathBuiltFromString) -> String {
|
||||
fn apply(&self, mut p: PathBuiltFromString, path_separator: char) -> String {
|
||||
match self {
|
||||
Self::None => {}
|
||||
Self::Home => p.parts.insert(0, "~".to_string()),
|
||||
Self::Prefix(s) => p.parts.insert(0, s.clone()),
|
||||
};
|
||||
|
||||
let mut ret = p.parts.join(MAIN_SEPARATOR_STR);
|
||||
let mut ret = p.parts.join(&path_separator.to_string());
|
||||
if p.isdir {
|
||||
ret.push(SEP);
|
||||
ret.push(path_separator);
|
||||
}
|
||||
ret
|
||||
}
|
||||
@ -131,10 +156,27 @@ pub fn complete_item(
|
||||
engine_state: &EngineState,
|
||||
stack: &Stack,
|
||||
) -> Vec<(nu_protocol::Span, String, Option<Style>)> {
|
||||
let partial = surround_remove(partial);
|
||||
let isdir = partial.ends_with(is_separator);
|
||||
let cleaned_partial = surround_remove(partial);
|
||||
let isdir = cleaned_partial.ends_with(is_separator);
|
||||
let expanded_partial = expand_ndots(Path::new(&cleaned_partial));
|
||||
let should_collapse_dots = expanded_partial != Path::new(&cleaned_partial);
|
||||
let mut partial = expanded_partial.to_string_lossy().to_string();
|
||||
|
||||
#[cfg(unix)]
|
||||
let path_separator = SEP;
|
||||
#[cfg(windows)]
|
||||
let path_separator = cleaned_partial
|
||||
.chars()
|
||||
.rfind(|c: &char| is_separator(*c))
|
||||
.unwrap_or(SEP);
|
||||
|
||||
// Handle the trailing dot case
|
||||
if cleaned_partial.ends_with(&format!("{path_separator}.")) {
|
||||
partial.push_str(&format!("{path_separator}."));
|
||||
}
|
||||
|
||||
let cwd_pathbuf = Path::new(cwd).to_path_buf();
|
||||
let ls_colors = (engine_state.config.use_ls_colors_completions
|
||||
let ls_colors = (engine_state.config.completions.use_ls_colors
|
||||
&& engine_state.config.use_ansi_coloring)
|
||||
.then(|| {
|
||||
let ls_colors_env_str = match stack.get_env_var(engine_state, "LS_COLORS") {
|
||||
@ -152,16 +194,11 @@ pub fn complete_item(
|
||||
match components.peek().cloned() {
|
||||
Some(c @ Component::Prefix(..)) => {
|
||||
// windows only by definition
|
||||
components.next();
|
||||
if let Some(Component::RootDir) = components.peek().cloned() {
|
||||
components.next();
|
||||
};
|
||||
cwd = [c, Component::RootDir].iter().collect();
|
||||
prefix_len = c.as_os_str().len();
|
||||
original_cwd = OriginalCwd::Prefix(c.as_os_str().to_string_lossy().into_owned());
|
||||
}
|
||||
Some(c @ Component::RootDir) => {
|
||||
components.next();
|
||||
// This is kind of a hack. When joining an empty string with the rest,
|
||||
// we add the slash automagically
|
||||
cwd = PathBuf::from(c.as_os_str());
|
||||
@ -169,8 +206,7 @@ pub fn complete_item(
|
||||
original_cwd = OriginalCwd::Prefix(String::new());
|
||||
}
|
||||
Some(Component::Normal(home)) if home.to_string_lossy() == "~" => {
|
||||
components.next();
|
||||
cwd = home_dir().unwrap_or(cwd_pathbuf);
|
||||
cwd = home_dir().map(Into::into).unwrap_or(cwd_pathbuf);
|
||||
prefix_len = 1;
|
||||
original_cwd = OriginalCwd::Home;
|
||||
}
|
||||
@ -194,8 +230,11 @@ pub fn complete_item(
|
||||
isdir,
|
||||
)
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
let path = original_cwd.apply(p);
|
||||
.map(|mut p| {
|
||||
if should_collapse_dots {
|
||||
p = collapse_ndots(p);
|
||||
}
|
||||
let path = original_cwd.apply(p, path_separator);
|
||||
let style = ls_colors.as_ref().map(|lsc| {
|
||||
lsc.style_for_path_with_metadata(
|
||||
&path,
|
||||
@ -273,33 +312,71 @@ pub fn adjust_if_intermediate(
|
||||
pub fn sort_suggestions(
|
||||
prefix: &str,
|
||||
items: Vec<SemanticSuggestion>,
|
||||
sort_by: SortBy,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<SemanticSuggestion> {
|
||||
sort_completions(prefix, items, sort_by, |it| &it.suggestion.value)
|
||||
sort_completions(prefix, items, options, |it| &it.suggestion.value)
|
||||
}
|
||||
|
||||
/// # Arguments
|
||||
/// * `prefix` - What the user's typed, for sorting by Levenshtein distance
|
||||
/// * `prefix` - What the user's typed, for sorting by fuzzy matcher score
|
||||
pub fn sort_completions<T>(
|
||||
prefix: &str,
|
||||
mut items: Vec<T>,
|
||||
sort_by: SortBy,
|
||||
options: &CompletionOptions,
|
||||
get_value: fn(&T) -> &str,
|
||||
) -> Vec<T> {
|
||||
// Sort items
|
||||
match sort_by {
|
||||
SortBy::LevenshteinDistance => {
|
||||
items.sort_by(|a, b| {
|
||||
let a_distance = levenshtein_distance(prefix, get_value(a));
|
||||
let b_distance = levenshtein_distance(prefix, get_value(b));
|
||||
a_distance.cmp(&b_distance)
|
||||
});
|
||||
}
|
||||
SortBy::Ascending => {
|
||||
items.sort_by(|a, b| get_value(a).cmp(get_value(b)));
|
||||
}
|
||||
SortBy::None => {}
|
||||
};
|
||||
if options.sort == CompletionSort::Smart && options.match_algorithm == MatchAlgorithm::Fuzzy {
|
||||
let mut matcher = SkimMatcherV2::default();
|
||||
if options.case_sensitive {
|
||||
matcher = matcher.respect_case();
|
||||
} else {
|
||||
matcher = matcher.ignore_case();
|
||||
};
|
||||
items.sort_by(|a, b| {
|
||||
let a_str = get_value(a);
|
||||
let b_str = get_value(b);
|
||||
let a_score = matcher.fuzzy_match(a_str, prefix).unwrap_or_default();
|
||||
let b_score = matcher.fuzzy_match(b_str, prefix).unwrap_or_default();
|
||||
b_score.cmp(&a_score).then(a_str.cmp(b_str))
|
||||
});
|
||||
} else {
|
||||
items.sort_by(|a, b| get_value(a).cmp(get_value(b)));
|
||||
}
|
||||
|
||||
items
|
||||
}
|
||||
|
||||
/// Collapse multiple ".." components into n-dots.
|
||||
///
|
||||
/// It performs the reverse operation of `expand_ndots`, collapsing sequences of ".." into n-dots,
|
||||
/// such as "..." and "....".
|
||||
///
|
||||
/// The resulting path will use platform-specific path separators, regardless of what path separators were used in the input.
|
||||
fn collapse_ndots(path: PathBuiltFromString) -> PathBuiltFromString {
|
||||
let mut result = PathBuiltFromString {
|
||||
parts: Vec::with_capacity(path.parts.len()),
|
||||
isdir: path.isdir,
|
||||
};
|
||||
|
||||
let mut dot_count = 0;
|
||||
|
||||
for part in path.parts {
|
||||
if part == ".." {
|
||||
dot_count += 1;
|
||||
} else {
|
||||
if dot_count > 0 {
|
||||
result.parts.push(".".repeat(dot_count + 1));
|
||||
dot_count = 0;
|
||||
}
|
||||
result.parts.push(part);
|
||||
}
|
||||
}
|
||||
|
||||
// Add any remaining dots
|
||||
if dot_count > 0 {
|
||||
result.parts.push(".".repeat(dot_count + 1));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
@ -1,17 +1,10 @@
|
||||
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
||||
use nu_parser::trim_quotes_str;
|
||||
use nu_protocol::CompletionAlgorithm;
|
||||
use nu_protocol::{CompletionAlgorithm, CompletionSort};
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum SortBy {
|
||||
LevenshteinDistance,
|
||||
Ascending,
|
||||
None,
|
||||
}
|
||||
|
||||
/// Describes how suggestions should be matched.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum MatchAlgorithm {
|
||||
/// Only show suggestions which begin with the given input
|
||||
///
|
||||
@ -96,6 +89,7 @@ pub struct CompletionOptions {
|
||||
pub case_sensitive: bool,
|
||||
pub positional: bool,
|
||||
pub match_algorithm: MatchAlgorithm,
|
||||
pub sort: CompletionSort,
|
||||
}
|
||||
|
||||
impl Default for CompletionOptions {
|
||||
@ -104,6 +98,7 @@ impl Default for CompletionOptions {
|
||||
case_sensitive: true,
|
||||
positional: true,
|
||||
match_algorithm: MatchAlgorithm::Prefix,
|
||||
sort: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
use crate::completions::{
|
||||
completer::map_value_completions, Completer, CompletionOptions, MatchAlgorithm,
|
||||
SemanticSuggestion, SortBy,
|
||||
SemanticSuggestion,
|
||||
};
|
||||
use nu_engine::eval_call;
|
||||
use nu_protocol::{
|
||||
ast::{Argument, Call, Expr, Expression},
|
||||
debugger::WithoutDebug,
|
||||
engine::{Stack, StateWorkingSet},
|
||||
PipelineData, Span, Type, Value,
|
||||
CompletionSort, PipelineData, Span, Type, Value,
|
||||
};
|
||||
use nu_utils::IgnoreCaseExt;
|
||||
use std::collections::HashMap;
|
||||
@ -18,7 +18,6 @@ pub struct CustomCompletion {
|
||||
stack: Stack,
|
||||
decl_id: usize,
|
||||
line: String,
|
||||
sort_by: SortBy,
|
||||
}
|
||||
|
||||
impl CustomCompletion {
|
||||
@ -27,7 +26,6 @@ impl CustomCompletion {
|
||||
stack,
|
||||
decl_id,
|
||||
line,
|
||||
sort_by: SortBy::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -93,10 +91,6 @@ impl Completer for CustomCompletion {
|
||||
.and_then(|val| val.as_bool().ok())
|
||||
.unwrap_or(false);
|
||||
|
||||
if should_sort {
|
||||
self.sort_by = SortBy::Ascending;
|
||||
}
|
||||
|
||||
custom_completion_options = Some(CompletionOptions {
|
||||
case_sensitive: options
|
||||
.get("case_sensitive")
|
||||
@ -114,6 +108,11 @@ impl Completer for CustomCompletion {
|
||||
.unwrap_or(MatchAlgorithm::Prefix),
|
||||
None => completion_options.match_algorithm,
|
||||
},
|
||||
sort: if should_sort {
|
||||
CompletionSort::Alphabetical
|
||||
} else {
|
||||
CompletionSort::Smart
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -124,12 +123,11 @@ impl Completer for CustomCompletion {
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let suggestions = if let Some(custom_completion_options) = custom_completion_options {
|
||||
filter(&prefix, suggestions, &custom_completion_options)
|
||||
} else {
|
||||
filter(&prefix, suggestions, completion_options)
|
||||
};
|
||||
sort_suggestions(&String::from_utf8_lossy(&prefix), suggestions, self.sort_by)
|
||||
let options = custom_completion_options
|
||||
.as_ref()
|
||||
.unwrap_or(completion_options);
|
||||
let suggestions = filter(&prefix, suggestions, options);
|
||||
sort_suggestions(&String::from_utf8_lossy(&prefix), suggestions, options)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,14 +48,12 @@ impl Completer for DirectoryCompletion {
|
||||
.map(move |x| SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: x.1,
|
||||
description: None,
|
||||
style: x.2,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: x.0.start - offset,
|
||||
end: x.0.end - offset,
|
||||
},
|
||||
append_whitespace: false,
|
||||
..Suggestion::default()
|
||||
},
|
||||
// TODO????
|
||||
kind: None,
|
||||
|
@ -6,7 +6,7 @@ use nu_protocol::{
|
||||
use reedline::Suggestion;
|
||||
use std::path::{is_separator, Path, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR};
|
||||
|
||||
use super::{completion_common::sort_suggestions, SemanticSuggestion, SortBy};
|
||||
use super::{completion_common::sort_suggestions, SemanticSuggestion};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct DotNuCompletion {}
|
||||
@ -116,14 +116,13 @@ impl Completer for DotNuCompletion {
|
||||
.map(move |x| SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: x.1,
|
||||
description: None,
|
||||
style: x.2,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: x.0.start - offset,
|
||||
end: x.0.end - offset,
|
||||
},
|
||||
append_whitespace: true,
|
||||
..Suggestion::default()
|
||||
},
|
||||
// TODO????
|
||||
kind: None,
|
||||
@ -131,6 +130,6 @@ impl Completer for DotNuCompletion {
|
||||
})
|
||||
.collect();
|
||||
|
||||
sort_suggestions(&prefix_str, output, SortBy::Ascending)
|
||||
sort_suggestions(&prefix_str, output, options)
|
||||
}
|
||||
}
|
||||
|
@ -53,14 +53,12 @@ impl Completer for FileCompletion {
|
||||
.map(move |x| SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: x.1,
|
||||
description: None,
|
||||
style: x.2,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: x.0.start - offset,
|
||||
end: x.0.end - offset,
|
||||
},
|
||||
append_whitespace: false,
|
||||
..Suggestion::default()
|
||||
},
|
||||
// TODO????
|
||||
kind: None,
|
||||
|
@ -1,6 +1,4 @@
|
||||
use crate::completions::{
|
||||
completion_common::sort_suggestions, Completer, CompletionOptions, SortBy,
|
||||
};
|
||||
use crate::completions::{completion_common::sort_suggestions, Completer, CompletionOptions};
|
||||
use nu_protocol::{
|
||||
ast::{Expr, Expression},
|
||||
engine::{Stack, StateWorkingSet},
|
||||
@ -51,13 +49,12 @@ impl Completer for FlagCompletion {
|
||||
suggestion: Suggestion {
|
||||
value: String::from_utf8_lossy(&named).to_string(),
|
||||
description: Some(flag_desc.to_string()),
|
||||
style: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: true,
|
||||
..Suggestion::default()
|
||||
},
|
||||
// TODO????
|
||||
kind: None,
|
||||
@ -78,13 +75,12 @@ impl Completer for FlagCompletion {
|
||||
suggestion: Suggestion {
|
||||
value: String::from_utf8_lossy(&named).to_string(),
|
||||
description: Some(flag_desc.to_string()),
|
||||
style: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: true,
|
||||
..Suggestion::default()
|
||||
},
|
||||
// TODO????
|
||||
kind: None,
|
||||
@ -92,7 +88,7 @@ impl Completer for FlagCompletion {
|
||||
}
|
||||
}
|
||||
|
||||
return sort_suggestions(&String::from_utf8_lossy(&prefix), output, SortBy::Ascending);
|
||||
return sort_suggestions(&String::from_utf8_lossy(&prefix), output, options);
|
||||
}
|
||||
|
||||
vec![]
|
||||
|
@ -13,7 +13,7 @@ mod variable_completions;
|
||||
pub use base::{Completer, SemanticSuggestion, SuggestionKind};
|
||||
pub use command_completions::CommandCompletion;
|
||||
pub use completer::NuCompleter;
|
||||
pub use completion_options::{CompletionOptions, MatchAlgorithm, SortBy};
|
||||
pub use completion_options::{CompletionOptions, MatchAlgorithm};
|
||||
pub use custom_completions::CustomCompletion;
|
||||
pub use directory_completions::DirectoryCompletion;
|
||||
pub use dotnu_completions::DotNuCompletion;
|
||||
|
@ -9,7 +9,7 @@ use nu_protocol::{
|
||||
use reedline::Suggestion;
|
||||
use std::str;
|
||||
|
||||
use super::{completion_common::sort_suggestions, SortBy};
|
||||
use super::completion_common::sort_suggestions;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VariableCompletion {
|
||||
@ -72,7 +72,7 @@ impl Completer for VariableCompletion {
|
||||
}
|
||||
}
|
||||
|
||||
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
||||
return sort_suggestions(&prefix_str, output, options);
|
||||
}
|
||||
} else {
|
||||
// No nesting provided, return all env vars
|
||||
@ -85,18 +85,15 @@ impl Completer for VariableCompletion {
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: env_var.0,
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
..Suggestion::default()
|
||||
},
|
||||
kind: Some(SuggestionKind::Type(env_var.1.get_type())),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
||||
return sort_suggestions(&prefix_str, output, options);
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,7 +117,7 @@ impl Completer for VariableCompletion {
|
||||
}
|
||||
}
|
||||
|
||||
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
||||
return sort_suggestions(&prefix_str, output, options);
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,7 +139,7 @@ impl Completer for VariableCompletion {
|
||||
}
|
||||
}
|
||||
|
||||
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
||||
return sort_suggestions(&prefix_str, output, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -157,11 +154,8 @@ impl Completer for VariableCompletion {
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: builtin.to_string(),
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
..Suggestion::default()
|
||||
},
|
||||
// TODO is there a way to get the VarId to get the type???
|
||||
kind: None,
|
||||
@ -184,11 +178,8 @@ impl Completer for VariableCompletion {
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: String::from_utf8_lossy(v.0).to_string(),
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
..Suggestion::default()
|
||||
},
|
||||
kind: Some(SuggestionKind::Type(
|
||||
working_set.get_variable(*v.1).ty.clone(),
|
||||
@ -215,11 +206,8 @@ impl Completer for VariableCompletion {
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: String::from_utf8_lossy(v.0).to_string(),
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
..Suggestion::default()
|
||||
},
|
||||
kind: Some(SuggestionKind::Type(
|
||||
working_set.get_variable(*v.1).ty.clone(),
|
||||
@ -229,7 +217,7 @@ impl Completer for VariableCompletion {
|
||||
}
|
||||
}
|
||||
|
||||
output = sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
||||
output = sort_suggestions(&prefix_str, output, options);
|
||||
|
||||
output.dedup(); // TODO: Removes only consecutive duplicates, is it intended?
|
||||
|
||||
@ -255,11 +243,8 @@ fn nested_suggestions(
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: col.clone(),
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
..Suggestion::default()
|
||||
},
|
||||
kind: Some(kind.clone()),
|
||||
});
|
||||
@ -272,11 +257,8 @@ fn nested_suggestions(
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: column_name,
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
..Suggestion::default()
|
||||
},
|
||||
kind: Some(kind.clone()),
|
||||
});
|
||||
|
@ -2,10 +2,10 @@ use crate::util::eval_source;
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_path::canonicalize_with;
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_protocol::{engine::StateWorkingSet, report_error, ParseError, PluginRegistryFile, Spanned};
|
||||
use nu_protocol::{engine::StateWorkingSet, ParseError, PluginRegistryFile, Spanned};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
report_error_new, HistoryFileFormat, PipelineData,
|
||||
report_shell_error, HistoryFileFormat, PipelineData,
|
||||
};
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_utils::perf;
|
||||
@ -36,7 +36,7 @@ pub fn read_plugin_file(
|
||||
.and_then(|p| Path::new(&p.item).extension())
|
||||
.is_some_and(|ext| ext == "nu")
|
||||
{
|
||||
report_error_new(
|
||||
report_shell_error(
|
||||
engine_state,
|
||||
&ShellError::GenericError {
|
||||
error: "Wrong plugin file format".into(),
|
||||
@ -81,7 +81,7 @@ pub fn read_plugin_file(
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
report_error_new(
|
||||
report_shell_error(
|
||||
engine_state,
|
||||
&ShellError::GenericError {
|
||||
error: format!(
|
||||
@ -113,7 +113,7 @@ pub fn read_plugin_file(
|
||||
Ok(contents) => contents,
|
||||
Err(err) => {
|
||||
log::warn!("Failed to read plugin registry file: {err:?}");
|
||||
report_error_new(
|
||||
report_shell_error(
|
||||
engine_state,
|
||||
&ShellError::GenericError {
|
||||
error: format!(
|
||||
@ -146,7 +146,7 @@ pub fn read_plugin_file(
|
||||
nu_plugin_engine::load_plugin_file(&mut working_set, &contents, span);
|
||||
|
||||
if let Err(err) = engine_state.merge_delta(working_set.render()) {
|
||||
report_error_new(engine_state, &err);
|
||||
report_shell_error(engine_state, &err);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -166,7 +166,7 @@ pub fn add_plugin_file(
|
||||
) {
|
||||
use std::path::Path;
|
||||
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
use nu_protocol::report_parse_error;
|
||||
|
||||
if let Ok(cwd) = engine_state.cwd_as_string(None) {
|
||||
if let Some(plugin_file) = plugin_file {
|
||||
@ -181,8 +181,8 @@ pub fn add_plugin_file(
|
||||
engine_state.plugin_path = Some(path)
|
||||
} else {
|
||||
// It's an error if the directory for the plugin file doesn't exist.
|
||||
report_error(
|
||||
&working_set,
|
||||
report_parse_error(
|
||||
&StateWorkingSet::new(engine_state),
|
||||
&ParseError::FileNotFound(
|
||||
path_dir.to_string_lossy().into_owned(),
|
||||
plugin_file.span,
|
||||
@ -192,7 +192,8 @@ pub fn add_plugin_file(
|
||||
} else if let Some(mut plugin_path) = nu_path::config_dir() {
|
||||
// Path to store plugins signatures
|
||||
plugin_path.push(storage_path);
|
||||
let mut plugin_path = canonicalize_with(&plugin_path, &cwd).unwrap_or(plugin_path);
|
||||
let mut plugin_path =
|
||||
canonicalize_with(&plugin_path, &cwd).unwrap_or(plugin_path.into());
|
||||
plugin_path.push(PLUGIN_FILE);
|
||||
let plugin_path = canonicalize_with(&plugin_path, &cwd).unwrap_or(plugin_path);
|
||||
engine_state.plugin_path = Some(plugin_path);
|
||||
@ -213,7 +214,8 @@ pub fn eval_config_contents(
|
||||
let prev_file = engine_state.file.take();
|
||||
engine_state.file = Some(config_path.clone());
|
||||
|
||||
eval_source(
|
||||
// TODO: ignore this error?
|
||||
let _ = eval_source(
|
||||
engine_state,
|
||||
stack,
|
||||
&contents,
|
||||
@ -229,11 +231,11 @@ pub fn eval_config_contents(
|
||||
match engine_state.cwd(Some(stack)) {
|
||||
Ok(cwd) => {
|
||||
if let Err(e) = engine_state.merge_env(stack, cwd) {
|
||||
report_error_new(engine_state, &e);
|
||||
report_shell_error(engine_state, &e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
report_error_new(engine_state, &e);
|
||||
report_shell_error(engine_state, &e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -244,10 +246,10 @@ pub(crate) fn get_history_path(storage_path: &str, mode: HistoryFileFormat) -> O
|
||||
nu_path::config_dir().map(|mut history_path| {
|
||||
history_path.push(storage_path);
|
||||
history_path.push(match mode {
|
||||
HistoryFileFormat::PlainText => HISTORY_FILE_TXT,
|
||||
HistoryFileFormat::Plaintext => HISTORY_FILE_TXT,
|
||||
HistoryFileFormat::Sqlite => HISTORY_FILE_SQLITE,
|
||||
});
|
||||
history_path
|
||||
history_path.into()
|
||||
})
|
||||
}
|
||||
|
||||
@ -279,7 +281,7 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) -
|
||||
let old_contents = match std::fs::read(&old_plugin_file_path) {
|
||||
Ok(old_contents) => old_contents,
|
||||
Err(err) => {
|
||||
report_error_new(
|
||||
report_shell_error(
|
||||
engine_state,
|
||||
&ShellError::GenericError {
|
||||
error: "Can't read old plugin file to migrate".into(),
|
||||
@ -348,7 +350,7 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) -
|
||||
.map_err(|e| e.into())
|
||||
.and_then(|file| contents.write_to(file, None))
|
||||
{
|
||||
report_error_new(
|
||||
report_shell_error(
|
||||
&engine_state,
|
||||
&ShellError::GenericError {
|
||||
error: "Failed to save migrated plugin file".into(),
|
||||
|
@ -2,9 +2,10 @@ use log::info;
|
||||
use nu_engine::{convert_env_values, eval_block};
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
cli_error::report_compile_error,
|
||||
debugger::WithoutDebug,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
report_error, PipelineData, ShellError, Spanned, Value,
|
||||
report_parse_error, report_parse_warning, PipelineData, ShellError, Spanned, Value,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -53,7 +54,7 @@ pub fn evaluate_commands(
|
||||
// Parse the source code
|
||||
let (block, delta) = {
|
||||
if let Some(ref t_mode) = table_mode {
|
||||
Arc::make_mut(&mut engine_state.config).table_mode =
|
||||
Arc::make_mut(&mut engine_state.config).table.mode =
|
||||
t_mode.coerce_str()?.parse().unwrap_or_default();
|
||||
}
|
||||
|
||||
@ -61,16 +62,16 @@ pub fn evaluate_commands(
|
||||
|
||||
let output = parse(&mut working_set, None, commands.item.as_bytes(), false);
|
||||
if let Some(warning) = working_set.parse_warnings.first() {
|
||||
report_error(&working_set, warning);
|
||||
report_parse_warning(&working_set, warning);
|
||||
}
|
||||
|
||||
if let Some(err) = working_set.parse_errors.first() {
|
||||
report_error(&working_set, err);
|
||||
report_parse_error(&working_set, err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if let Some(err) = working_set.compile_errors.first() {
|
||||
report_error(&working_set, err);
|
||||
report_compile_error(&working_set, err);
|
||||
// Not a fatal error, for now
|
||||
}
|
||||
|
||||
@ -88,15 +89,11 @@ pub fn evaluate_commands(
|
||||
}
|
||||
|
||||
if let Some(t_mode) = table_mode {
|
||||
Arc::make_mut(&mut engine_state.config).table_mode =
|
||||
Arc::make_mut(&mut engine_state.config).table.mode =
|
||||
t_mode.coerce_str()?.parse().unwrap_or_default();
|
||||
}
|
||||
|
||||
if let Some(status) = pipeline.print(engine_state, stack, no_newline, false)? {
|
||||
if status.code() != 0 {
|
||||
std::process::exit(status.code())
|
||||
}
|
||||
}
|
||||
pipeline.print(engine_state, stack, no_newline, false)?;
|
||||
|
||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||
|
||||
|
@ -4,9 +4,10 @@ use nu_engine::{convert_env_values, eval_block};
|
||||
use nu_parser::parse;
|
||||
use nu_path::canonicalize_with;
|
||||
use nu_protocol::{
|
||||
cli_error::report_compile_error,
|
||||
debugger::WithoutDebug,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
report_error, PipelineData, ShellError, Span, Value,
|
||||
report_parse_error, report_parse_warning, PipelineData, ShellError, Span, Value,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -77,17 +78,17 @@ pub fn evaluate_file(
|
||||
let block = parse(&mut working_set, Some(file_path_str), &file, false);
|
||||
|
||||
if let Some(warning) = working_set.parse_warnings.first() {
|
||||
report_error(&working_set, warning);
|
||||
report_parse_warning(&working_set, warning);
|
||||
}
|
||||
|
||||
// If any parse errors were found, report the first error and exit.
|
||||
if let Some(err) = working_set.parse_errors.first() {
|
||||
report_error(&working_set, err);
|
||||
report_parse_error(&working_set, err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if let Some(err) = working_set.compile_errors.first() {
|
||||
report_error(&working_set, err);
|
||||
report_compile_error(&working_set, err);
|
||||
// Not a fatal error, for now
|
||||
}
|
||||
|
||||
@ -118,11 +119,7 @@ pub fn evaluate_file(
|
||||
};
|
||||
|
||||
// Print the pipeline output of the last command of the file.
|
||||
if let Some(status) = pipeline.print(engine_state, stack, true, false)? {
|
||||
if status.code() != 0 {
|
||||
std::process::exit(status.code())
|
||||
}
|
||||
}
|
||||
pipeline.print(engine_state, stack, true, false)?;
|
||||
|
||||
// Invoke the main command with arguments.
|
||||
// Arguments with whitespaces are quoted, thus can be safely concatenated by whitespace.
|
||||
@ -140,7 +137,7 @@ pub fn evaluate_file(
|
||||
};
|
||||
|
||||
if exit_code != 0 {
|
||||
std::process::exit(exit_code)
|
||||
std::process::exit(exit_code);
|
||||
}
|
||||
|
||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||
|
@ -1,4 +1,4 @@
|
||||
use nu_engine::documentation::get_flags_section;
|
||||
use nu_engine::documentation::{get_flags_section, HelpStyle};
|
||||
use nu_protocol::{engine::EngineState, levenshtein_distance, Config};
|
||||
use nu_utils::IgnoreCaseExt;
|
||||
use reedline::{Completer, Suggestion};
|
||||
@ -20,6 +20,9 @@ impl NuHelpCompleter {
|
||||
fn completion_helper(&self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
let folded_line = line.to_folded_case();
|
||||
|
||||
let mut help_style = HelpStyle::default();
|
||||
help_style.update_from_config(&self.engine_state, &self.config);
|
||||
|
||||
let mut commands = self
|
||||
.engine_state
|
||||
.get_decls_sorted(false)
|
||||
@ -27,12 +30,15 @@ impl NuHelpCompleter {
|
||||
.filter_map(|(_, decl_id)| {
|
||||
let decl = self.engine_state.get_decl(decl_id);
|
||||
(decl.name().to_folded_case().contains(&folded_line)
|
||||
|| decl.usage().to_folded_case().contains(&folded_line)
|
||||
|| decl.description().to_folded_case().contains(&folded_line)
|
||||
|| decl
|
||||
.search_terms()
|
||||
.into_iter()
|
||||
.any(|term| term.to_folded_case().contains(&folded_line))
|
||||
|| decl.extra_usage().to_folded_case().contains(&folded_line))
|
||||
|| decl
|
||||
.extra_description()
|
||||
.to_folded_case()
|
||||
.contains(&folded_line))
|
||||
.then_some(decl)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@ -44,15 +50,15 @@ impl NuHelpCompleter {
|
||||
.map(|decl| {
|
||||
let mut long_desc = String::new();
|
||||
|
||||
let usage = decl.usage();
|
||||
if !usage.is_empty() {
|
||||
long_desc.push_str(usage);
|
||||
let description = decl.description();
|
||||
if !description.is_empty() {
|
||||
long_desc.push_str(description);
|
||||
long_desc.push_str("\r\n\r\n");
|
||||
}
|
||||
|
||||
let extra_usage = decl.extra_usage();
|
||||
if !extra_usage.is_empty() {
|
||||
long_desc.push_str(extra_usage);
|
||||
let extra_desc = decl.extra_description();
|
||||
if !extra_desc.is_empty() {
|
||||
long_desc.push_str(extra_desc);
|
||||
long_desc.push_str("\r\n\r\n");
|
||||
}
|
||||
|
||||
@ -60,12 +66,9 @@ impl NuHelpCompleter {
|
||||
let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature());
|
||||
|
||||
if !sig.named.is_empty() {
|
||||
long_desc.push_str(&get_flags_section(
|
||||
Some(&self.engine_state),
|
||||
Some(&self.config),
|
||||
&sig,
|
||||
|v| v.to_parsable_string(", ", &self.config),
|
||||
))
|
||||
long_desc.push_str(&get_flags_section(&sig, &help_style, |v| {
|
||||
v.to_parsable_string(", ", &self.config)
|
||||
}))
|
||||
}
|
||||
|
||||
if !sig.required_positional.is_empty()
|
||||
@ -110,13 +113,12 @@ impl NuHelpCompleter {
|
||||
Suggestion {
|
||||
value: decl.name().into(),
|
||||
description: Some(long_desc),
|
||||
style: None,
|
||||
extra: Some(extra),
|
||||
span: reedline::Span {
|
||||
start: pos - line.len(),
|
||||
end: pos,
|
||||
},
|
||||
append_whitespace: false,
|
||||
..Suggestion::default()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
@ -142,10 +142,9 @@ fn convert_to_suggestions(
|
||||
vec![Suggestion {
|
||||
value: text,
|
||||
description,
|
||||
style: None,
|
||||
extra,
|
||||
span,
|
||||
append_whitespace: false,
|
||||
..Suggestion::default()
|
||||
}]
|
||||
}
|
||||
Value::List { vals, .. } => vals
|
||||
@ -154,9 +153,6 @@ fn convert_to_suggestions(
|
||||
.collect(),
|
||||
_ => vec![Suggestion {
|
||||
value: format!("Not a record: {value:?}"),
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: if only_buffer_difference {
|
||||
pos - line.len()
|
||||
@ -169,7 +165,7 @@ fn convert_to_suggestions(
|
||||
line.len()
|
||||
},
|
||||
},
|
||||
append_whitespace: false,
|
||||
..Suggestion::default()
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ impl Command for NuHighlight {
|
||||
.input_output_types(vec![(Type::String, Type::String)])
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Syntax highlight the input string."
|
||||
}
|
||||
|
||||
|
@ -22,14 +22,19 @@ impl Command for Print {
|
||||
Some('n'),
|
||||
)
|
||||
.switch("stderr", "print to stderr instead of stdout", Some('e'))
|
||||
.switch(
|
||||
"raw",
|
||||
"print without formatting (including binary data)",
|
||||
Some('r'),
|
||||
)
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Print the given values to stdout."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
fn extra_description(&self) -> &str {
|
||||
r#"Unlike `echo`, this command does not return any value (`print | describe` will return "nothing").
|
||||
Since this command has no output, there is no point in piping it with other commands.
|
||||
|
||||
@ -50,15 +55,25 @@ Since this command has no output, there is no point in piping it with other comm
|
||||
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||
let no_newline = call.has_flag(engine_state, stack, "no-newline")?;
|
||||
let to_stderr = call.has_flag(engine_state, stack, "stderr")?;
|
||||
let raw = call.has_flag(engine_state, stack, "raw")?;
|
||||
|
||||
// This will allow for easy printing of pipelines as well
|
||||
if !args.is_empty() {
|
||||
for arg in args {
|
||||
arg.into_pipeline_data()
|
||||
.print(engine_state, stack, no_newline, to_stderr)?;
|
||||
if raw {
|
||||
arg.into_pipeline_data()
|
||||
.print_raw(engine_state, no_newline, to_stderr)?;
|
||||
} else {
|
||||
arg.into_pipeline_data()
|
||||
.print(engine_state, stack, no_newline, to_stderr)?;
|
||||
}
|
||||
}
|
||||
} else if !input.is_nothing() {
|
||||
input.print(engine_state, stack, no_newline, to_stderr)?;
|
||||
if raw {
|
||||
input.print_raw(engine_state, no_newline, to_stderr)?;
|
||||
} else {
|
||||
input.print(engine_state, stack, no_newline, to_stderr)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PipelineData::empty())
|
||||
@ -76,6 +91,11 @@ Since this command has no output, there is no point in piping it with other comm
|
||||
example: r#"print (2 + 3)"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Print 'ABC' from binary data",
|
||||
example: r#"0x[41 42 43] | print --raw"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use log::trace;
|
||||
use nu_engine::ClosureEvalOnce;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
report_error_new, Config, PipelineData, Value,
|
||||
report_shell_error, Config, PipelineData, Value,
|
||||
};
|
||||
use reedline::Prompt;
|
||||
|
||||
@ -46,7 +46,10 @@ pub(crate) const VSCODE_POST_EXECUTION_MARKER_PREFIX: &str = "\x1b]633;D;";
|
||||
#[allow(dead_code)]
|
||||
pub(crate) const VSCODE_POST_EXECUTION_MARKER_SUFFIX: &str = "\x1b\\";
|
||||
#[allow(dead_code)]
|
||||
pub(crate) const VSCODE_COMMANDLINE_MARKER: &str = "\x1b]633;E\x1b\\";
|
||||
//"\x1b]633;E;{}\x1b\\"
|
||||
pub(crate) const VSCODE_COMMANDLINE_MARKER_PREFIX: &str = "\x1b]633;E;";
|
||||
#[allow(dead_code)]
|
||||
pub(crate) const VSCODE_COMMANDLINE_MARKER_SUFFIX: &str = "\x1b\\";
|
||||
#[allow(dead_code)]
|
||||
// "\x1b]633;P;Cwd={}\x1b\\"
|
||||
pub(crate) const VSCODE_CWD_PROPERTY_MARKER_PREFIX: &str = "\x1b]633;P;Cwd=";
|
||||
@ -77,7 +80,7 @@ fn get_prompt_string(
|
||||
|
||||
result
|
||||
.map_err(|err| {
|
||||
report_error_new(engine_state, &err);
|
||||
report_shell_error(engine_state, &err);
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
@ -115,13 +118,13 @@ pub(crate) fn update_prompt(
|
||||
|
||||
// Now that we have the prompt string lets ansify it.
|
||||
// <133 A><prompt><133 B><command><133 C><command output>
|
||||
let left_prompt_string = if config.shell_integration_osc633 {
|
||||
let left_prompt_string = if config.shell_integration.osc633 {
|
||||
if stack.get_env_var(engine_state, "TERM_PROGRAM") == Some(Value::test_string("vscode")) {
|
||||
// We're in vscode and we have osc633 enabled
|
||||
Some(format!(
|
||||
"{VSCODE_PRE_PROMPT_MARKER}{configured_left_prompt_string}{VSCODE_POST_PROMPT_MARKER}"
|
||||
))
|
||||
} else if config.shell_integration_osc133 {
|
||||
} else if config.shell_integration.osc133 {
|
||||
// If we're in VSCode but we don't find the env var, but we have osc133 set, then use it
|
||||
Some(format!(
|
||||
"{PRE_PROMPT_MARKER}{configured_left_prompt_string}{POST_PROMPT_MARKER}"
|
||||
@ -129,7 +132,7 @@ pub(crate) fn update_prompt(
|
||||
} else {
|
||||
configured_left_prompt_string.into()
|
||||
}
|
||||
} else if config.shell_integration_osc133 {
|
||||
} else if config.shell_integration.osc133 {
|
||||
Some(format!(
|
||||
"{PRE_PROMPT_MARKER}{configured_left_prompt_string}{POST_PROMPT_MARKER}"
|
||||
))
|
||||
|
@ -159,8 +159,8 @@ fn add_menu(
|
||||
stack: &Stack,
|
||||
config: Arc<Config>,
|
||||
) -> Result<Reedline, ShellError> {
|
||||
let span = menu.menu_type.span();
|
||||
if let Value::Record { val, .. } = &menu.menu_type {
|
||||
let span = menu.r#type.span();
|
||||
if let Value::Record { val, .. } = &menu.r#type {
|
||||
let layout = extract_value("layout", val, span)?.to_expanded_string("", &config);
|
||||
|
||||
match layout.as_str() {
|
||||
@ -170,15 +170,15 @@ fn add_menu(
|
||||
"description" => add_description_menu(line_editor, menu, engine_state, stack, config),
|
||||
_ => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "columnar, list, ide or description".to_string(),
|
||||
value: menu.menu_type.to_abbreviated_string(&config),
|
||||
span: menu.menu_type.span(),
|
||||
value: menu.r#type.to_abbreviated_string(&config),
|
||||
span: menu.r#type.span(),
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "only record type".to_string(),
|
||||
value: menu.menu_type.to_abbreviated_string(&config),
|
||||
span: menu.menu_type.span(),
|
||||
value: menu.r#type.to_abbreviated_string(&config),
|
||||
span: menu.r#type.span(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -193,6 +193,29 @@ fn get_style(record: &Record, name: &str, span: Span) -> Option<Style> {
|
||||
})
|
||||
}
|
||||
|
||||
fn set_menu_style<M: MenuBuilder>(mut menu: M, style: &Value) -> M {
|
||||
let span = style.span();
|
||||
let Value::Record { val, .. } = &style else {
|
||||
return menu;
|
||||
};
|
||||
if let Some(style) = get_style(val, "text", span) {
|
||||
menu = menu.with_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "selected_text", span) {
|
||||
menu = menu.with_selected_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "description_text", span) {
|
||||
menu = menu.with_description_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "match_text", span) {
|
||||
menu = menu.with_match_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "selected_match_text", span) {
|
||||
menu = menu.with_selected_match_text_style(style);
|
||||
}
|
||||
menu
|
||||
}
|
||||
|
||||
// Adds a columnar menu to the editor engine
|
||||
pub(crate) fn add_columnar_menu(
|
||||
line_editor: Reedline,
|
||||
@ -201,11 +224,11 @@ pub(crate) fn add_columnar_menu(
|
||||
stack: &Stack,
|
||||
config: &Config,
|
||||
) -> Result<Reedline, ShellError> {
|
||||
let span = menu.menu_type.span();
|
||||
let span = menu.r#type.span();
|
||||
let name = menu.name.to_expanded_string("", config);
|
||||
let mut columnar_menu = ColumnarMenu::default().with_name(&name);
|
||||
|
||||
if let Value::Record { val, .. } = &menu.menu_type {
|
||||
if let Value::Record { val, .. } = &menu.r#type {
|
||||
columnar_menu = match extract_value("columns", val, span) {
|
||||
Ok(columns) => {
|
||||
let columns = columns.as_int()?;
|
||||
@ -231,24 +254,7 @@ pub(crate) fn add_columnar_menu(
|
||||
};
|
||||
}
|
||||
|
||||
let span = menu.style.span();
|
||||
if let Value::Record { val, .. } = &menu.style {
|
||||
if let Some(style) = get_style(val, "text", span) {
|
||||
columnar_menu = columnar_menu.with_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "selected_text", span) {
|
||||
columnar_menu = columnar_menu.with_selected_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "description_text", span) {
|
||||
columnar_menu = columnar_menu.with_description_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "match_text", span) {
|
||||
columnar_menu = columnar_menu.with_match_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "selected_match_text", span) {
|
||||
columnar_menu = columnar_menu.with_selected_match_text_style(style);
|
||||
}
|
||||
}
|
||||
columnar_menu = set_menu_style(columnar_menu, &menu.style);
|
||||
|
||||
let marker = menu.marker.to_expanded_string("", config);
|
||||
columnar_menu = columnar_menu.with_marker(&marker);
|
||||
@ -293,8 +299,8 @@ pub(crate) fn add_list_menu(
|
||||
let name = menu.name.to_expanded_string("", &config);
|
||||
let mut list_menu = ListMenu::default().with_name(&name);
|
||||
|
||||
let span = menu.menu_type.span();
|
||||
if let Value::Record { val, .. } = &menu.menu_type {
|
||||
let span = menu.r#type.span();
|
||||
if let Value::Record { val, .. } = &menu.r#type {
|
||||
list_menu = match extract_value("page_size", val, span) {
|
||||
Ok(page_size) => {
|
||||
let page_size = page_size.as_int()?;
|
||||
@ -304,18 +310,7 @@ pub(crate) fn add_list_menu(
|
||||
};
|
||||
}
|
||||
|
||||
let span = menu.style.span();
|
||||
if let Value::Record { val, .. } = &menu.style {
|
||||
if let Some(style) = get_style(val, "text", span) {
|
||||
list_menu = list_menu.with_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "selected_text", span) {
|
||||
list_menu = list_menu.with_selected_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "description_text", span) {
|
||||
list_menu = list_menu.with_description_text_style(style);
|
||||
}
|
||||
}
|
||||
list_menu = set_menu_style(list_menu, &menu.style);
|
||||
|
||||
let marker = menu.marker.to_expanded_string("", &config);
|
||||
list_menu = list_menu.with_marker(&marker);
|
||||
@ -357,11 +352,11 @@ pub(crate) fn add_ide_menu(
|
||||
stack: &Stack,
|
||||
config: Arc<Config>,
|
||||
) -> Result<Reedline, ShellError> {
|
||||
let span = menu.menu_type.span();
|
||||
let span = menu.r#type.span();
|
||||
let name = menu.name.to_expanded_string("", &config);
|
||||
let mut ide_menu = IdeMenu::default().with_name(&name);
|
||||
|
||||
if let Value::Record { val, .. } = &menu.menu_type {
|
||||
if let Value::Record { val, .. } = &menu.r#type {
|
||||
ide_menu = match extract_value("min_completion_width", val, span) {
|
||||
Ok(min_completion_width) => {
|
||||
let min_completion_width = min_completion_width.as_int()?;
|
||||
@ -496,24 +491,7 @@ pub(crate) fn add_ide_menu(
|
||||
};
|
||||
}
|
||||
|
||||
let span = menu.style.span();
|
||||
if let Value::Record { val, .. } = &menu.style {
|
||||
if let Some(style) = get_style(val, "text", span) {
|
||||
ide_menu = ide_menu.with_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "selected_text", span) {
|
||||
ide_menu = ide_menu.with_selected_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "description_text", span) {
|
||||
ide_menu = ide_menu.with_description_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "match_text", span) {
|
||||
ide_menu = ide_menu.with_match_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "selected_match_text", span) {
|
||||
ide_menu = ide_menu.with_selected_match_text_style(style);
|
||||
}
|
||||
}
|
||||
ide_menu = set_menu_style(ide_menu, &menu.style);
|
||||
|
||||
let marker = menu.marker.to_expanded_string("", &config);
|
||||
ide_menu = ide_menu.with_marker(&marker);
|
||||
@ -558,8 +536,8 @@ pub(crate) fn add_description_menu(
|
||||
let name = menu.name.to_expanded_string("", &config);
|
||||
let mut description_menu = DescriptionMenu::default().with_name(&name);
|
||||
|
||||
let span = menu.menu_type.span();
|
||||
if let Value::Record { val, .. } = &menu.menu_type {
|
||||
let span = menu.r#type.span();
|
||||
if let Value::Record { val, .. } = &menu.r#type {
|
||||
description_menu = match extract_value("columns", val, span) {
|
||||
Ok(columns) => {
|
||||
let columns = columns.as_int()?;
|
||||
@ -601,18 +579,7 @@ pub(crate) fn add_description_menu(
|
||||
};
|
||||
}
|
||||
|
||||
let span = menu.style.span();
|
||||
if let Value::Record { val, .. } = &menu.style {
|
||||
if let Some(style) = get_style(val, "text", span) {
|
||||
description_menu = description_menu.with_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "selected_text", span) {
|
||||
description_menu = description_menu.with_selected_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "description_text", span) {
|
||||
description_menu = description_menu.with_description_text_style(style);
|
||||
}
|
||||
}
|
||||
description_menu = set_menu_style(description_menu, &menu.style);
|
||||
|
||||
let marker = menu.marker.to_expanded_string("", &config);
|
||||
description_menu = description_menu.with_marker(&marker);
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::prompt_update::{
|
||||
POST_EXECUTION_MARKER_PREFIX, POST_EXECUTION_MARKER_SUFFIX, PRE_EXECUTION_MARKER,
|
||||
RESET_APPLICATION_MODE, VSCODE_CWD_PROPERTY_MARKER_PREFIX, VSCODE_CWD_PROPERTY_MARKER_SUFFIX,
|
||||
RESET_APPLICATION_MODE, VSCODE_COMMANDLINE_MARKER_PREFIX, VSCODE_COMMANDLINE_MARKER_SUFFIX,
|
||||
VSCODE_CWD_PROPERTY_MARKER_PREFIX, VSCODE_CWD_PROPERTY_MARKER_SUFFIX,
|
||||
VSCODE_POST_EXECUTION_MARKER_PREFIX, VSCODE_POST_EXECUTION_MARKER_SUFFIX,
|
||||
VSCODE_PRE_EXECUTION_MARKER,
|
||||
};
|
||||
@ -26,7 +27,7 @@ use nu_parser::{lex, parse, trim_quotes_str};
|
||||
use nu_protocol::{
|
||||
config::NuCursorShape,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
report_error_new, HistoryConfig, HistoryFileFormat, PipelineData, ShellError, Span, Spanned,
|
||||
report_shell_error, HistoryConfig, HistoryFileFormat, PipelineData, ShellError, Span, Spanned,
|
||||
Value,
|
||||
};
|
||||
use nu_utils::{
|
||||
@ -71,11 +72,11 @@ pub fn evaluate_repl(
|
||||
let mut entry_num = 0;
|
||||
|
||||
// Let's grab the shell_integration configs
|
||||
let shell_integration_osc2 = config.shell_integration_osc2;
|
||||
let shell_integration_osc7 = config.shell_integration_osc7;
|
||||
let shell_integration_osc9_9 = config.shell_integration_osc9_9;
|
||||
let shell_integration_osc133 = config.shell_integration_osc133;
|
||||
let shell_integration_osc633 = config.shell_integration_osc633;
|
||||
let shell_integration_osc2 = config.shell_integration.osc2;
|
||||
let shell_integration_osc7 = config.shell_integration.osc7;
|
||||
let shell_integration_osc9_9 = config.shell_integration.osc9_9;
|
||||
let shell_integration_osc133 = config.shell_integration.osc133;
|
||||
let shell_integration_osc633 = config.shell_integration.osc633;
|
||||
|
||||
let nu_prompt = NushellPrompt::new(
|
||||
shell_integration_osc133,
|
||||
@ -87,7 +88,7 @@ pub fn evaluate_repl(
|
||||
let start_time = std::time::Instant::now();
|
||||
// Translate environment variables from Strings to Values
|
||||
if let Err(e) = convert_env_values(engine_state, &unique_stack) {
|
||||
report_error_new(engine_state, &e);
|
||||
report_shell_error(engine_state, &e);
|
||||
}
|
||||
perf!("translate env vars", start_time, use_color);
|
||||
|
||||
@ -97,7 +98,7 @@ pub fn evaluate_repl(
|
||||
Value::string("0823", Span::unknown()),
|
||||
);
|
||||
|
||||
unique_stack.add_env_var("LAST_EXIT_CODE".into(), Value::int(0, Span::unknown()));
|
||||
unique_stack.set_last_exit_code(0, Span::unknown());
|
||||
|
||||
let mut line_editor = get_line_editor(engine_state, nushell_path, use_color)?;
|
||||
let temp_file = temp_dir().join(format!("{}.nu", uuid::Uuid::new_v4()));
|
||||
@ -131,7 +132,26 @@ pub fn evaluate_repl(
|
||||
run_shell_integration_osc9_9(engine_state, &mut unique_stack, use_color);
|
||||
}
|
||||
if shell_integration_osc633 {
|
||||
run_shell_integration_osc633(engine_state, &mut unique_stack, use_color);
|
||||
// escape a few things because this says so
|
||||
// https://code.visualstudio.com/docs/terminal/shell-integration#_vs-code-custom-sequences-osc-633-st
|
||||
let cmd_text = line_editor.current_buffer_contents().to_string();
|
||||
|
||||
let replaced_cmd_text = cmd_text
|
||||
.chars()
|
||||
.map(|c| match c {
|
||||
'\n' => '\x0a',
|
||||
'\r' => '\x0d',
|
||||
'\x1b' => '\x1b',
|
||||
_ => c,
|
||||
})
|
||||
.collect();
|
||||
|
||||
run_shell_integration_osc633(
|
||||
engine_state,
|
||||
&mut unique_stack,
|
||||
use_color,
|
||||
replaced_cmd_text,
|
||||
);
|
||||
}
|
||||
|
||||
engine_state.set_startup_time(entire_start_time.elapsed().as_nanos() as i64);
|
||||
@ -266,11 +286,11 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
// Before doing anything, merge the environment from the previous REPL iteration into the
|
||||
// permanent state.
|
||||
if let Err(err) = engine_state.merge_env(&mut stack, cwd) {
|
||||
report_error_new(engine_state, &err);
|
||||
report_shell_error(engine_state, &err);
|
||||
}
|
||||
// Check whether $env.NU_USE_IR is set, so that the user can change it in the REPL
|
||||
// Check whether $env.NU_DISABLE_IR is set, so that the user can change it in the REPL
|
||||
// Temporary while IR eval is optional
|
||||
stack.use_ir = stack.has_env_var(engine_state, "NU_USE_IR");
|
||||
stack.use_ir = !stack.has_env_var(engine_state, "NU_DISABLE_IR");
|
||||
perf!("merge env", start_time, use_color);
|
||||
|
||||
start_time = std::time::Instant::now();
|
||||
@ -282,7 +302,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
// fire the "pre_prompt" hook
|
||||
if let Some(hook) = engine_state.get_config().hooks.pre_prompt.clone() {
|
||||
if let Err(err) = eval_hook(engine_state, &mut stack, None, vec![], &hook, "pre_prompt") {
|
||||
report_error_new(engine_state, &err);
|
||||
report_shell_error(engine_state, &err);
|
||||
}
|
||||
}
|
||||
perf!("pre-prompt hook", start_time, use_color);
|
||||
@ -292,7 +312,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
// fire the "env_change" hook
|
||||
let env_change = engine_state.get_config().hooks.env_change.clone();
|
||||
if let Err(error) = hook::eval_env_change_hook(env_change, engine_state, &mut stack) {
|
||||
report_error_new(engine_state, &error)
|
||||
report_shell_error(engine_state, &error)
|
||||
}
|
||||
perf!("env-change hook", start_time, use_color);
|
||||
|
||||
@ -302,9 +322,9 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
start_time = std::time::Instant::now();
|
||||
// Find the configured cursor shapes for each mode
|
||||
let cursor_config = CursorConfig {
|
||||
vi_insert: map_nucursorshape_to_cursorshape(config.cursor_shape_vi_insert),
|
||||
vi_normal: map_nucursorshape_to_cursorshape(config.cursor_shape_vi_normal),
|
||||
emacs: map_nucursorshape_to_cursorshape(config.cursor_shape_emacs),
|
||||
vi_insert: map_nucursorshape_to_cursorshape(config.cursor_shape.vi_insert),
|
||||
vi_normal: map_nucursorshape_to_cursorshape(config.cursor_shape.vi_normal),
|
||||
emacs: map_nucursorshape_to_cursorshape(config.cursor_shape.emacs),
|
||||
};
|
||||
perf!("get config/cursor config", start_time, use_color);
|
||||
|
||||
@ -332,8 +352,8 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
// STACK-REFERENCE 2
|
||||
stack_arc.clone(),
|
||||
)))
|
||||
.with_quick_completions(config.quick_completions)
|
||||
.with_partial_completions(config.partial_completions)
|
||||
.with_quick_completions(config.completions.quick)
|
||||
.with_partial_completions(config.completions.partial)
|
||||
.with_ansi_colors(config.use_ansi_coloring)
|
||||
.with_cwd(Some(
|
||||
engine_state
|
||||
@ -366,7 +386,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
trace!("adding menus");
|
||||
line_editor =
|
||||
add_menus(line_editor, engine_reference, &stack_arc, config).unwrap_or_else(|e| {
|
||||
report_error_new(engine_state, &e);
|
||||
report_shell_error(engine_state, &e);
|
||||
Reedline::create()
|
||||
});
|
||||
|
||||
@ -437,12 +457,12 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
.with_completer(Box::<DefaultCompleter>::default());
|
||||
|
||||
// Let's grab the shell_integration configs
|
||||
let shell_integration_osc2 = config.shell_integration_osc2;
|
||||
let shell_integration_osc7 = config.shell_integration_osc7;
|
||||
let shell_integration_osc9_9 = config.shell_integration_osc9_9;
|
||||
let shell_integration_osc133 = config.shell_integration_osc133;
|
||||
let shell_integration_osc633 = config.shell_integration_osc633;
|
||||
let shell_integration_reset_application_mode = config.shell_integration_reset_application_mode;
|
||||
let shell_integration_osc2 = config.shell_integration.osc2;
|
||||
let shell_integration_osc7 = config.shell_integration.osc7;
|
||||
let shell_integration_osc9_9 = config.shell_integration.osc9_9;
|
||||
let shell_integration_osc133 = config.shell_integration.osc133;
|
||||
let shell_integration_osc633 = config.shell_integration.osc633;
|
||||
let shell_integration_reset_application_mode = config.shell_integration.reset_application_mode;
|
||||
|
||||
// TODO: we may clone the stack, this can lead to major performance issues
|
||||
// so we should avoid it or making stack cheaper to clone.
|
||||
@ -452,14 +472,19 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
|
||||
let line_editor_input_time = std::time::Instant::now();
|
||||
match input {
|
||||
Ok(Signal::Success(s)) => {
|
||||
Ok(Signal::Success(repl_cmd_line_text)) => {
|
||||
let history_supports_meta = matches!(
|
||||
engine_state.history_config().map(|h| h.file_format),
|
||||
Some(HistoryFileFormat::Sqlite)
|
||||
);
|
||||
|
||||
if history_supports_meta {
|
||||
prepare_history_metadata(&s, hostname, engine_state, &mut line_editor);
|
||||
prepare_history_metadata(
|
||||
&repl_cmd_line_text,
|
||||
hostname,
|
||||
engine_state,
|
||||
&mut line_editor,
|
||||
);
|
||||
}
|
||||
|
||||
// For pre_exec_hook
|
||||
@ -470,7 +495,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
if let Some(hook) = config.hooks.pre_execution.clone() {
|
||||
// Set the REPL buffer to the current command for the "pre_execution" hook
|
||||
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||
repl.buffer = s.to_string();
|
||||
repl.buffer = repl_cmd_line_text.to_string();
|
||||
drop(repl);
|
||||
|
||||
if let Err(err) = eval_hook(
|
||||
@ -481,7 +506,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
&hook,
|
||||
"pre_execution",
|
||||
) {
|
||||
report_error_new(engine_state, &err);
|
||||
report_shell_error(engine_state, &err);
|
||||
}
|
||||
}
|
||||
|
||||
@ -531,7 +556,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
// Actual command execution logic starts from here
|
||||
let cmd_execution_start_time = Instant::now();
|
||||
|
||||
match parse_operation(s.clone(), engine_state, &stack) {
|
||||
match parse_operation(repl_cmd_line_text.clone(), engine_state, &stack) {
|
||||
Ok(operation) => match operation {
|
||||
ReplOperation::AutoCd { cwd, target, span } => {
|
||||
do_auto_cd(target, cwd, &mut stack, engine_state, span);
|
||||
@ -577,7 +602,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
|
||||
if history_supports_meta {
|
||||
if let Err(e) = fill_in_result_related_history_metadata(
|
||||
&s,
|
||||
&repl_cmd_line_text,
|
||||
engine_state,
|
||||
cmd_duration,
|
||||
&mut stack,
|
||||
@ -597,7 +622,12 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
run_shell_integration_osc9_9(engine_state, &mut stack, use_color);
|
||||
}
|
||||
if shell_integration_osc633 {
|
||||
run_shell_integration_osc633(engine_state, &mut stack, use_color);
|
||||
run_shell_integration_osc633(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
use_color,
|
||||
repl_cmd_line_text,
|
||||
);
|
||||
}
|
||||
if shell_integration_reset_application_mode {
|
||||
run_shell_integration_reset_application_mode();
|
||||
@ -778,7 +808,7 @@ fn do_auto_cd(
|
||||
) {
|
||||
let path = {
|
||||
if !path.exists() {
|
||||
report_error_new(
|
||||
report_shell_error(
|
||||
engine_state,
|
||||
&ShellError::DirectoryNotFound {
|
||||
dir: path.to_string_lossy().to_string(),
|
||||
@ -790,7 +820,7 @@ fn do_auto_cd(
|
||||
};
|
||||
|
||||
if let PermissionResult::PermissionDenied(reason) = have_permission(path.clone()) {
|
||||
report_error_new(
|
||||
report_shell_error(
|
||||
engine_state,
|
||||
&ShellError::IOError {
|
||||
msg: format!("Cannot change directory to {path}: {reason}"),
|
||||
@ -804,7 +834,7 @@ fn do_auto_cd(
|
||||
//FIXME: this only changes the current scope, but instead this environment variable
|
||||
//should probably be a block that loads the information from the state in the overlay
|
||||
if let Err(err) = stack.set_cwd(&path) {
|
||||
report_error_new(engine_state, &err);
|
||||
report_shell_error(engine_state, &err);
|
||||
return;
|
||||
};
|
||||
let cwd = Value::string(cwd, span);
|
||||
@ -837,7 +867,7 @@ fn do_auto_cd(
|
||||
"NUSHELL_LAST_SHELL".into(),
|
||||
Value::int(last_shell as i64, span),
|
||||
);
|
||||
stack.add_env_var("LAST_EXIT_CODE".into(), Value::int(0, Span::unknown()));
|
||||
stack.set_last_exit_code(0, Span::unknown());
|
||||
}
|
||||
|
||||
///
|
||||
@ -911,7 +941,12 @@ fn run_shell_integration_osc2(
|
||||
|
||||
// Try to abbreviate string for windows title
|
||||
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
||||
path.replace(&p.as_path().display().to_string(), "~")
|
||||
let home_dir_str = p.as_path().display().to_string();
|
||||
if path.starts_with(&home_dir_str) {
|
||||
path.replacen(&home_dir_str, "~", 1)
|
||||
} else {
|
||||
path
|
||||
}
|
||||
} else {
|
||||
path
|
||||
};
|
||||
@ -988,7 +1023,12 @@ fn run_shell_integration_osc9_9(engine_state: &EngineState, stack: &mut Stack, u
|
||||
}
|
||||
}
|
||||
|
||||
fn run_shell_integration_osc633(engine_state: &EngineState, stack: &mut Stack, use_color: bool) {
|
||||
fn run_shell_integration_osc633(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
use_color: bool,
|
||||
repl_cmd_line_text: String,
|
||||
) {
|
||||
#[allow(deprecated)]
|
||||
if let Ok(path) = current_dir_str(engine_state, stack) {
|
||||
// Supported escape sequences of Microsoft's Visual Studio Code (vscode)
|
||||
@ -1008,6 +1048,27 @@ fn run_shell_integration_osc633(engine_state: &EngineState, stack: &mut Stack, u
|
||||
start_time,
|
||||
use_color
|
||||
);
|
||||
|
||||
// escape a few things because this says so
|
||||
// https://code.visualstudio.com/docs/terminal/shell-integration#_vs-code-custom-sequences-osc-633-st
|
||||
|
||||
let replaced_cmd_text: String = repl_cmd_line_text
|
||||
.chars()
|
||||
.map(|c| match c {
|
||||
'\n' => '\x0a',
|
||||
'\r' => '\x0d',
|
||||
'\x1b' => '\x1b',
|
||||
_ => c,
|
||||
})
|
||||
.collect();
|
||||
|
||||
//OSC 633 ; E ; <commandline> [; <nonce] ST - Explicitly set the command line with an optional nonce.
|
||||
run_ansi_sequence(&format!(
|
||||
"{}{}{}",
|
||||
VSCODE_COMMANDLINE_MARKER_PREFIX,
|
||||
replaced_cmd_text,
|
||||
VSCODE_COMMANDLINE_MARKER_SUFFIX
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1080,7 +1141,7 @@ fn setup_keybindings(engine_state: &EngineState, line_editor: Reedline) -> Reedl
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
report_error_new(engine_state, &e);
|
||||
report_shell_error(engine_state, &e);
|
||||
line_editor
|
||||
}
|
||||
};
|
||||
@ -1112,7 +1173,7 @@ fn update_line_editor_history(
|
||||
history_session_id: Option<HistorySessionId>,
|
||||
) -> Result<Reedline, ErrReport> {
|
||||
let history: Box<dyn reedline::History> = match history.file_format {
|
||||
HistoryFileFormat::PlainText => Box::new(
|
||||
HistoryFileFormat::Plaintext => Box::new(
|
||||
FileBackedHistory::with_file(history.max_size as usize, history_path)
|
||||
.into_diagnostic()?,
|
||||
),
|
||||
@ -1150,10 +1211,10 @@ fn confirm_stdin_is_terminal() -> Result<()> {
|
||||
fn map_nucursorshape_to_cursorshape(shape: NuCursorShape) -> Option<SetCursorStyle> {
|
||||
match shape {
|
||||
NuCursorShape::Block => Some(SetCursorStyle::SteadyBlock),
|
||||
NuCursorShape::UnderScore => Some(SetCursorStyle::SteadyUnderScore),
|
||||
NuCursorShape::Underscore => Some(SetCursorStyle::SteadyUnderScore),
|
||||
NuCursorShape::Line => Some(SetCursorStyle::SteadyBar),
|
||||
NuCursorShape::BlinkBlock => Some(SetCursorStyle::BlinkingBlock),
|
||||
NuCursorShape::BlinkUnderScore => Some(SetCursorStyle::BlinkingUnderScore),
|
||||
NuCursorShape::BlinkUnderscore => Some(SetCursorStyle::BlinkingUnderScore),
|
||||
NuCursorShape::BlinkLine => Some(SetCursorStyle::BlinkingBar),
|
||||
NuCursorShape::Inherit => None,
|
||||
}
|
||||
@ -1337,20 +1398,26 @@ fn are_session_ids_in_sync() {
|
||||
#[cfg(test)]
|
||||
mod test_auto_cd {
|
||||
use super::{do_auto_cd, parse_operation, ReplOperation};
|
||||
use nu_path::AbsolutePath;
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use std::path::Path;
|
||||
use tempfile::tempdir;
|
||||
|
||||
/// Create a symlink. Works on both Unix and Windows.
|
||||
#[cfg(any(unix, windows))]
|
||||
fn symlink(original: impl AsRef<Path>, link: impl AsRef<Path>) -> std::io::Result<()> {
|
||||
fn symlink(
|
||||
original: impl AsRef<AbsolutePath>,
|
||||
link: impl AsRef<AbsolutePath>,
|
||||
) -> std::io::Result<()> {
|
||||
let original = original.as_ref();
|
||||
let link = link.as_ref();
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
std::os::unix::fs::symlink(original, link)
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if original.as_ref().is_dir() {
|
||||
if original.is_dir() {
|
||||
std::os::windows::fs::symlink_dir(original, link)
|
||||
} else {
|
||||
std::os::windows::fs::symlink_file(original, link)
|
||||
@ -1362,11 +1429,11 @@ mod test_auto_cd {
|
||||
/// `before`, and after `input` is parsed and evaluated, PWD should be
|
||||
/// changed to `after`.
|
||||
#[track_caller]
|
||||
fn check(before: impl AsRef<Path>, input: &str, after: impl AsRef<Path>) {
|
||||
fn check(before: impl AsRef<AbsolutePath>, input: &str, after: impl AsRef<AbsolutePath>) {
|
||||
// Setup EngineState and Stack.
|
||||
let mut engine_state = EngineState::new();
|
||||
let mut stack = Stack::new();
|
||||
stack.set_cwd(before).unwrap();
|
||||
stack.set_cwd(before.as_ref()).unwrap();
|
||||
|
||||
// Parse the input. It must be an auto-cd operation.
|
||||
let op = parse_operation(input.to_string(), &engine_state, &stack).unwrap();
|
||||
@ -1382,54 +1449,66 @@ mod test_auto_cd {
|
||||
// don't have to be byte-wise equal (on Windows, the 8.3 filename
|
||||
// conversion messes things up),
|
||||
let updated_cwd = std::fs::canonicalize(updated_cwd).unwrap();
|
||||
let after = std::fs::canonicalize(after).unwrap();
|
||||
let after = std::fs::canonicalize(after.as_ref()).unwrap();
|
||||
assert_eq!(updated_cwd, after);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_cd_root() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let root = if cfg!(windows) { r"C:\" } else { "/" };
|
||||
check(&tempdir, root, root);
|
||||
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||
|
||||
let input = if cfg!(windows) { r"C:\" } else { "/" };
|
||||
let root = AbsolutePath::try_new(input).unwrap();
|
||||
check(tempdir, input, root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_cd_tilde() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||
|
||||
let home = nu_path::home_dir().unwrap();
|
||||
check(&tempdir, "~", home);
|
||||
check(tempdir, "~", home);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_cd_dot() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
check(&tempdir, ".", &tempdir);
|
||||
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||
|
||||
check(tempdir, ".", tempdir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_cd_double_dot() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let dir = tempdir.path().join("foo");
|
||||
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||
|
||||
let dir = tempdir.join("foo");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
check(dir, "..", &tempdir);
|
||||
check(dir, "..", tempdir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_cd_triple_dot() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let dir = tempdir.path().join("foo").join("bar");
|
||||
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||
|
||||
let dir = tempdir.join("foo").join("bar");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
check(dir, "...", &tempdir);
|
||||
check(dir, "...", tempdir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_cd_relative() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let foo = tempdir.path().join("foo");
|
||||
let bar = tempdir.path().join("bar");
|
||||
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||
|
||||
let foo = tempdir.join("foo");
|
||||
let bar = tempdir.join("bar");
|
||||
std::fs::create_dir_all(&foo).unwrap();
|
||||
std::fs::create_dir_all(&bar).unwrap();
|
||||
|
||||
let input = if cfg!(windows) { r"..\bar" } else { "../bar" };
|
||||
check(foo, input, bar);
|
||||
}
|
||||
@ -1437,32 +1516,35 @@ mod test_auto_cd {
|
||||
#[test]
|
||||
fn auto_cd_trailing_slash() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let dir = tempdir.path().join("foo");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||
|
||||
let dir = tempdir.join("foo");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let input = if cfg!(windows) { r"foo\" } else { "foo/" };
|
||||
check(&tempdir, input, dir);
|
||||
check(tempdir, input, dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_cd_symlink() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let dir = tempdir.path().join("foo");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let link = tempdir.path().join("link");
|
||||
symlink(&dir, &link).unwrap();
|
||||
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||
|
||||
let dir = tempdir.join("foo");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let link = tempdir.join("link");
|
||||
symlink(&dir, &link).unwrap();
|
||||
let input = if cfg!(windows) { r".\link" } else { "./link" };
|
||||
check(&tempdir, input, link);
|
||||
check(tempdir, input, link);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "was not parsed into an auto-cd operation")]
|
||||
fn auto_cd_nonexistent_directory() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let dir = tempdir.path().join("foo");
|
||||
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||
|
||||
let dir = tempdir.join("foo");
|
||||
let input = if cfg!(windows) { r"foo\" } else { "foo/" };
|
||||
check(&tempdir, input, dir);
|
||||
check(tempdir, input, dir);
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,11 @@ use nu_cmd_base::hook::eval_hook;
|
||||
use nu_engine::{eval_block, eval_block_with_early_return};
|
||||
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
|
||||
use nu_protocol::{
|
||||
cli_error::report_compile_error,
|
||||
debugger::WithoutDebug,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
report_error, report_error_new, PipelineData, ShellError, Span, Value,
|
||||
report_parse_error, report_parse_warning, report_shell_error, PipelineData, ShellError, Span,
|
||||
Value,
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use nu_utils::enable_vt_processing;
|
||||
@ -39,7 +41,7 @@ fn gather_env_vars(
|
||||
init_cwd: &Path,
|
||||
) {
|
||||
fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) {
|
||||
report_error_new(
|
||||
report_shell_error(
|
||||
engine_state,
|
||||
&ShellError::GenericError {
|
||||
error: format!("Environment variable was not captured: {env_str}"),
|
||||
@ -70,7 +72,7 @@ fn gather_env_vars(
|
||||
}
|
||||
None => {
|
||||
// Could not capture current working directory
|
||||
report_error_new(
|
||||
report_shell_error(
|
||||
engine_state,
|
||||
&ShellError::GenericError {
|
||||
error: "Current directory is not a valid utf-8 path".into(),
|
||||
@ -210,18 +212,19 @@ pub fn eval_source(
|
||||
let start_time = std::time::Instant::now();
|
||||
|
||||
let exit_code = match evaluate_source(engine_state, stack, source, fname, input, allow_return) {
|
||||
Ok(code) => code.unwrap_or(0),
|
||||
Ok(failed) => {
|
||||
let code = failed.into();
|
||||
stack.set_last_exit_code(code, Span::unknown());
|
||||
code
|
||||
}
|
||||
Err(err) => {
|
||||
report_error_new(engine_state, &err);
|
||||
1
|
||||
report_shell_error(engine_state, &err);
|
||||
let code = err.exit_code();
|
||||
stack.set_last_error(&err);
|
||||
code
|
||||
}
|
||||
};
|
||||
|
||||
stack.add_env_var(
|
||||
"LAST_EXIT_CODE".to_string(),
|
||||
Value::int(exit_code.into(), Span::unknown()),
|
||||
);
|
||||
|
||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||
#[cfg(windows)]
|
||||
{
|
||||
@ -244,7 +247,7 @@ fn evaluate_source(
|
||||
fname: &str,
|
||||
input: PipelineData,
|
||||
allow_return: bool,
|
||||
) -> Result<Option<i32>, ShellError> {
|
||||
) -> Result<bool, ShellError> {
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
let output = parse(
|
||||
@ -254,16 +257,16 @@ fn evaluate_source(
|
||||
false,
|
||||
);
|
||||
if let Some(warning) = working_set.parse_warnings.first() {
|
||||
report_error(&working_set, warning);
|
||||
report_parse_warning(&working_set, warning);
|
||||
}
|
||||
|
||||
if let Some(err) = working_set.parse_errors.first() {
|
||||
report_error(&working_set, err);
|
||||
return Ok(Some(1));
|
||||
report_parse_error(&working_set, err);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if let Some(err) = working_set.compile_errors.first() {
|
||||
report_error(&working_set, err);
|
||||
report_compile_error(&working_set, err);
|
||||
// Not a fatal error, for now
|
||||
}
|
||||
|
||||
@ -278,25 +281,23 @@ fn evaluate_source(
|
||||
eval_block::<WithoutDebug>(engine_state, stack, &block, input)
|
||||
}?;
|
||||
|
||||
let status = if let PipelineData::ByteStream(..) = pipeline {
|
||||
pipeline.print(engine_state, stack, false, false)?
|
||||
if let PipelineData::ByteStream(..) = pipeline {
|
||||
pipeline.print(engine_state, stack, false, false)
|
||||
} else if let Some(hook) = engine_state.get_config().hooks.display_output.clone() {
|
||||
let pipeline = eval_hook(
|
||||
engine_state,
|
||||
stack,
|
||||
Some(pipeline),
|
||||
vec![],
|
||||
&hook,
|
||||
"display_output",
|
||||
)?;
|
||||
pipeline.print(engine_state, stack, false, false)
|
||||
} else {
|
||||
if let Some(hook) = engine_state.get_config().hooks.display_output.clone() {
|
||||
let pipeline = eval_hook(
|
||||
engine_state,
|
||||
stack,
|
||||
Some(pipeline),
|
||||
vec![],
|
||||
&hook,
|
||||
"display_output",
|
||||
)?;
|
||||
pipeline.print(engine_state, stack, false, false)
|
||||
} else {
|
||||
pipeline.print(engine_state, stack, true, false)
|
||||
}?
|
||||
};
|
||||
pipeline.print(engine_state, stack, true, false)
|
||||
}?;
|
||||
|
||||
Ok(status.map(|status| status.code()))
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -321,16 +322,10 @@ mod test {
|
||||
|
||||
let env = engine_state.render_env_vars();
|
||||
|
||||
assert!(
|
||||
matches!(env.get(&"FOO".to_string()), Some(&Value::String { val, .. }) if val == "foo")
|
||||
);
|
||||
assert!(
|
||||
matches!(env.get(&"SYMBOLS".to_string()), Some(&Value::String { val, .. }) if val == symbols)
|
||||
);
|
||||
assert!(
|
||||
matches!(env.get(&symbols.to_string()), Some(&Value::String { val, .. }) if val == "symbols")
|
||||
);
|
||||
assert!(env.get(&"PWD".to_string()).is_some());
|
||||
assert!(matches!(env.get("FOO"), Some(&Value::String { val, .. }) if val == "foo"));
|
||||
assert!(matches!(env.get("SYMBOLS"), Some(&Value::String { val, .. }) if val == symbols));
|
||||
assert!(matches!(env.get(symbols), Some(&Value::String { val, .. }) if val == "symbols"));
|
||||
assert!(env.contains_key("PWD"));
|
||||
assert_eq!(env.len(), 4);
|
||||
}
|
||||
}
|
||||
|
7
crates/nu-cli/tests/commands/keybindings_list.rs
Normal file
7
crates/nu-cli/tests/commands/keybindings_list.rs
Normal file
@ -0,0 +1,7 @@
|
||||
use nu_test_support::nu;
|
||||
|
||||
#[test]
|
||||
fn not_empty() {
|
||||
let result = nu!("keybindings list | is-not-empty");
|
||||
assert_eq!(result.out, "true");
|
||||
}
|
@ -1 +1,2 @@
|
||||
mod keybindings_list;
|
||||
mod nu_highlight;
|
||||
|
@ -32,7 +32,6 @@ fn completer() -> NuCompleter {
|
||||
fn completer_strings() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Add record value as example
|
||||
let record = r#"def animals [] { ["cat", "dog", "eel" ] }
|
||||
def my-command [animal: string@animals] { print $animal }"#;
|
||||
@ -62,6 +61,32 @@ fn extern_completer() -> NuCompleter {
|
||||
NuCompleter::new(Arc::new(engine), Arc::new(stack))
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
fn completer_strings_with_options() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
// Add record value as example
|
||||
let record = r#"
|
||||
# To test that the config setting has no effect on the custom completions
|
||||
$env.config.completions.algorithm = "fuzzy"
|
||||
def animals [] {
|
||||
{
|
||||
# Very rare and totally real animals
|
||||
completions: ["Abcdef", "Foo Abcdef", "Acd Bar" ],
|
||||
options: {
|
||||
completion_algorithm: "prefix",
|
||||
positional: false,
|
||||
case_sensitive: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
def my-command [animal: string@animals] { print $animal }"#;
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
// Instantiate a new completer
|
||||
NuCompleter::new(Arc::new(engine), Arc::new(stack))
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
fn custom_completer() -> NuCompleter {
|
||||
// Create a new engine
|
||||
@ -90,14 +115,12 @@ fn subcommand_completer() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Use fuzzy matching, because subcommands are sorted by Levenshtein distance,
|
||||
// and that's not very useful with prefix matching
|
||||
let commands = r#"
|
||||
$env.config.completions.algorithm = "fuzzy"
|
||||
def foo [] {}
|
||||
def "foo bar" [] {}
|
||||
def "foo abaz" [] {}
|
||||
def "foo aabrr" [] {}
|
||||
def "foo aabcrr" [] {}
|
||||
def food [] {}
|
||||
"#;
|
||||
assert!(support::merge_input(commands.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
@ -106,6 +129,22 @@ fn subcommand_completer() -> NuCompleter {
|
||||
NuCompleter::new(Arc::new(engine), Arc::new(stack))
|
||||
}
|
||||
|
||||
/// Use fuzzy completions but sort in alphabetical order
|
||||
#[fixture]
|
||||
fn fuzzy_alpha_sort_completer() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
let config = r#"
|
||||
$env.config.completions.algorithm = "fuzzy"
|
||||
$env.config.completions.sort = "alphabetical"
|
||||
"#;
|
||||
assert!(support::merge_input(config.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
// Instantiate a new completer
|
||||
NuCompleter::new(Arc::new(engine), Arc::new(stack))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variables_dollar_sign_with_variablecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
@ -123,28 +162,28 @@ fn variables_double_dash_argument_with_flagcompletion(mut completer: NuCompleter
|
||||
let suggestions = completer.complete("tst --", 6);
|
||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into()];
|
||||
// dbg!(&expected, &suggestions);
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_single_dash_argument_with_flagcompletion(mut completer: NuCompleter) {
|
||||
let suggestions = completer.complete("tst -", 5);
|
||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_command_with_commandcompletion(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-c ", 4);
|
||||
let expected: Vec<String> = vec!["my-command".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_subcommands_with_customcompletion(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-command ", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@ -153,7 +192,21 @@ fn variables_customcompletion_subcommands_with_customcompletion_2(
|
||||
) {
|
||||
let suggestions = completer_strings.complete("my-command ", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn customcompletions_substring_matching(mut completer_strings_with_options: NuCompleter) {
|
||||
let suggestions = completer_strings_with_options.complete("my-command Abcd", 15);
|
||||
let expected: Vec<String> = vec!["Abcdef".into(), "Foo Abcdef".into()];
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn customcompletions_case_insensitive(mut completer_strings_with_options: NuCompleter) {
|
||||
let suggestions = completer_strings_with_options.complete("my-command foo", 14);
|
||||
let expected: Vec<String> = vec!["Foo Abcdef".into()];
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -182,19 +235,19 @@ fn dotnu_completions() {
|
||||
let completion_str = "source-env ".to_string();
|
||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||
|
||||
match_suggestions(expected.clone(), suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
|
||||
// Test use completion
|
||||
let completion_str = "use ".to_string();
|
||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||
|
||||
match_suggestions(expected.clone(), suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
|
||||
// Test overlay use completion
|
||||
let completion_str = "overlay use ".to_string();
|
||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -258,8 +311,22 @@ fn file_completions() {
|
||||
folder(dir.join(".hidden_folder")),
|
||||
];
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let separator = '/';
|
||||
let target_dir = format!("cp {dir_str}{separator}");
|
||||
let slash_suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
let expected_slash_paths: Vec<String> = expected_paths
|
||||
.iter()
|
||||
.map(|s| s.replace('\\', "/"))
|
||||
.collect();
|
||||
|
||||
match_suggestions(&expected_slash_paths, &slash_suggestions);
|
||||
}
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
|
||||
// Test completions for a file
|
||||
let target_dir = format!("cp {}", folder(dir.join("another")));
|
||||
@ -269,17 +336,91 @@ fn file_completions() {
|
||||
let expected_paths: Vec<String> = vec![file(dir.join("another").join("newfile"))];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
|
||||
// Test completions for hidden files
|
||||
let target_dir = format!("ls {}/.", folder(dir.join(".hidden_folder")));
|
||||
let target_dir = format!("ls {}", file(dir.join(".hidden_folder").join(".")));
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
let expected_paths: Vec<String> =
|
||||
vec![file(dir.join(".hidden_folder").join(".hidden_subfile"))];
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let target_dir = format!("ls {}/.", folder(dir.join(".hidden_folder")));
|
||||
let slash_suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
let expected_slash: Vec<String> = expected_paths
|
||||
.iter()
|
||||
.map(|s| s.replace('\\', "/"))
|
||||
.collect();
|
||||
|
||||
match_suggestions(&expected_slash, &slash_suggestions);
|
||||
}
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn file_completions_with_mixed_separators() {
|
||||
// Create a new engine
|
||||
let (dir, dir_str, engine, stack) = new_dotnu_engine();
|
||||
|
||||
// Instantiate a new completer
|
||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||
|
||||
// Create Expected values
|
||||
let expected_paths: Vec<String> = vec![
|
||||
file(dir.join("lib-dir1").join("bar.nu")),
|
||||
file(dir.join("lib-dir1").join("baz.nu")),
|
||||
file(dir.join("lib-dir1").join("xyzzy.nu")),
|
||||
];
|
||||
let expected_slash_paths: Vec<String> = expected_paths
|
||||
.iter()
|
||||
.map(|s| s.replace(MAIN_SEPARATOR, "/"))
|
||||
.collect();
|
||||
|
||||
let target_dir = format!("ls {dir_str}/lib-dir1/");
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
match_suggestions(&expected_slash_paths, &suggestions);
|
||||
|
||||
let target_dir = format!("cp {dir_str}\\lib-dir1/");
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
match_suggestions(&expected_slash_paths, &suggestions);
|
||||
|
||||
let target_dir = format!("ls {dir_str}/lib-dir1\\/");
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
match_suggestions(&expected_slash_paths, &suggestions);
|
||||
|
||||
let target_dir = format!("ls {dir_str}\\lib-dir1\\/");
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
match_suggestions(&expected_slash_paths, &suggestions);
|
||||
|
||||
let target_dir = format!("ls {dir_str}\\lib-dir1\\");
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
|
||||
let target_dir = format!("ls {dir_str}/lib-dir1\\");
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
|
||||
let target_dir = format!("ls {dir_str}/lib-dir1/\\");
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
|
||||
let target_dir = format!("ls {dir_str}\\lib-dir1/\\");
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -303,7 +444,7 @@ fn partial_completions() {
|
||||
];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
|
||||
// Test completions for the files whose name begin with "h"
|
||||
// and are present under directories whose names begin with "pa"
|
||||
@ -324,7 +465,7 @@ fn partial_completions() {
|
||||
];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
|
||||
// Test completion for all files under directories whose names begin with "pa"
|
||||
let dir_str = folder(dir.join("pa"));
|
||||
@ -345,7 +486,7 @@ fn partial_completions() {
|
||||
];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
|
||||
// Test completion for a single file
|
||||
let dir_str = file(dir.join("fi").join("so"));
|
||||
@ -356,7 +497,7 @@ fn partial_completions() {
|
||||
let expected_paths: Vec<String> = vec![file(dir.join("final_partial").join("somefile"))];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
|
||||
// Test completion where there is a sneaky `..` in the path
|
||||
let dir_str = file(dir.join("par").join("..").join("fi").join("so"));
|
||||
@ -392,7 +533,7 @@ fn partial_completions() {
|
||||
];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
|
||||
// Test completion for all files under directories whose names begin with "pa"
|
||||
let file_str = file(dir.join("partial-a").join("have"));
|
||||
@ -406,7 +547,7 @@ fn partial_completions() {
|
||||
];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
|
||||
// Test completion for all files under directories whose names begin with "pa"
|
||||
let file_str = file(dir.join("partial-a").join("have_ext."));
|
||||
@ -420,7 +561,59 @@ fn partial_completions() {
|
||||
];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_completion_with_dot_expansions() {
|
||||
let (dir, _, engine, stack) = new_partial_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||
|
||||
let dir_str = file(
|
||||
dir.join("par")
|
||||
.join("...")
|
||||
.join("par")
|
||||
.join("fi")
|
||||
.join("so"),
|
||||
);
|
||||
let target_dir = format!("rm {dir_str}");
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
let expected_paths: Vec<String> = vec![
|
||||
file(
|
||||
dir.join("partial")
|
||||
.join("...")
|
||||
.join("partial_completions")
|
||||
.join("final_partial")
|
||||
.join("somefile"),
|
||||
),
|
||||
file(
|
||||
dir.join("partial-a")
|
||||
.join("...")
|
||||
.join("partial_completions")
|
||||
.join("final_partial")
|
||||
.join("somefile"),
|
||||
),
|
||||
file(
|
||||
dir.join("partial-b")
|
||||
.join("...")
|
||||
.join("partial_completions")
|
||||
.join("final_partial")
|
||||
.join("somefile"),
|
||||
),
|
||||
file(
|
||||
dir.join("partial-c")
|
||||
.join("...")
|
||||
.join("partial_completions")
|
||||
.join("final_partial")
|
||||
.join("somefile"),
|
||||
),
|
||||
];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -455,15 +648,16 @@ fn command_ls_with_filecompletion() {
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
|
||||
let target_dir = "ls custom_completion.";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
let expected_paths: Vec<String> = vec!["custom_completion.nu".to_string()];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_open_with_filecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
@ -496,14 +690,14 @@ fn command_open_with_filecompletion() {
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
|
||||
let target_dir = "open custom_completion.";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
let expected_paths: Vec<String> = vec!["custom_completion.nu".to_string()];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -538,7 +732,7 @@ fn command_rm_with_globcompletion() {
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
match_suggestions(&expected_paths, &suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -573,7 +767,7 @@ fn command_cp_with_globcompletion() {
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
match_suggestions(&expected_paths, &suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -608,7 +802,7 @@ fn command_save_with_filecompletion() {
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
match_suggestions(&expected_paths, &suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -643,7 +837,7 @@ fn command_touch_with_filecompletion() {
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
match_suggestions(&expected_paths, &suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -678,7 +872,7 @@ fn command_watch_with_filecompletion() {
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
match_suggestions(&expected_paths, &suggestions)
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@ -686,19 +880,19 @@ fn subcommand_completions(mut subcommand_completer: NuCompleter) {
|
||||
let prefix = "foo br";
|
||||
let suggestions = subcommand_completer.complete(prefix, prefix.len());
|
||||
match_suggestions(
|
||||
vec!["foo bar".to_string(), "foo aabrr".to_string()],
|
||||
suggestions,
|
||||
&vec!["foo bar".to_string(), "foo aabcrr".to_string()],
|
||||
&suggestions,
|
||||
);
|
||||
|
||||
let prefix = "foo b";
|
||||
let suggestions = subcommand_completer.complete(prefix, prefix.len());
|
||||
match_suggestions(
|
||||
vec![
|
||||
&vec![
|
||||
"foo bar".to_string(),
|
||||
"foo aabcrr".to_string(),
|
||||
"foo abaz".to_string(),
|
||||
"foo aabrr".to_string(),
|
||||
],
|
||||
suggestions,
|
||||
&suggestions,
|
||||
);
|
||||
}
|
||||
|
||||
@ -724,7 +918,7 @@ fn file_completion_quoted() {
|
||||
format!("`{}`", folder("test dir")),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
|
||||
let dir: PathBuf = "test dir".into();
|
||||
let target_dir = format!("open '{}'", folder(dir.clone()));
|
||||
@ -735,7 +929,7 @@ fn file_completion_quoted() {
|
||||
format!("`{}`", file(dir.join("single quote"))),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
match_suggestions(&expected_paths, &suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -770,7 +964,7 @@ fn flag_completions() {
|
||||
];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -794,8 +988,207 @@ fn folder_with_directorycompletions() {
|
||||
folder(dir.join(".hidden_folder")),
|
||||
];
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let target_dir = format!("cd {dir_str}/");
|
||||
let slash_suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
let expected_slash_paths: Vec<String> = expected_paths
|
||||
.iter()
|
||||
.map(|s| s.replace('\\', "/"))
|
||||
.collect();
|
||||
|
||||
match_suggestions(&expected_slash_paths, &slash_suggestions);
|
||||
}
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folder_with_directorycompletions_with_dots() {
|
||||
// Create a new engine
|
||||
let (dir, _, engine, stack) = new_engine();
|
||||
let dir_str = dir
|
||||
.join("directory_completion")
|
||||
.join("folder_inside_folder")
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap();
|
||||
|
||||
// Instantiate a new completer
|
||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||
|
||||
// Test completions for the current folder
|
||||
let target_dir = format!("cd {dir_str}{MAIN_SEPARATOR}..{MAIN_SEPARATOR}");
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
let expected_paths: Vec<String> = vec![folder(
|
||||
dir.join("directory_completion")
|
||||
.join("folder_inside_folder")
|
||||
.join("..")
|
||||
.join("folder_inside_folder"),
|
||||
)];
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let target_dir = format!("cd {dir_str}/../");
|
||||
let slash_suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
let expected_slash_paths: Vec<String> = expected_paths
|
||||
.iter()
|
||||
.map(|s| s.replace('\\', "/"))
|
||||
.collect();
|
||||
|
||||
match_suggestions(&expected_slash_paths, &slash_suggestions);
|
||||
}
|
||||
|
||||
// Match the results
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folder_with_directorycompletions_with_three_trailing_dots() {
|
||||
// Create a new engine
|
||||
let (dir, _, engine, stack) = new_engine();
|
||||
let dir_str = dir
|
||||
.join("directory_completion")
|
||||
.join("folder_inside_folder")
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap();
|
||||
|
||||
// Instantiate a new completer
|
||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||
|
||||
// Test completions for the current folder
|
||||
let target_dir = format!("cd {dir_str}{MAIN_SEPARATOR}...{MAIN_SEPARATOR}");
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
let expected_paths: Vec<String> = vec![
|
||||
folder(
|
||||
dir.join("directory_completion")
|
||||
.join("folder_inside_folder")
|
||||
.join("...")
|
||||
.join("another"),
|
||||
),
|
||||
folder(
|
||||
dir.join("directory_completion")
|
||||
.join("folder_inside_folder")
|
||||
.join("...")
|
||||
.join("directory_completion"),
|
||||
),
|
||||
folder(
|
||||
dir.join("directory_completion")
|
||||
.join("folder_inside_folder")
|
||||
.join("...")
|
||||
.join("test_a"),
|
||||
),
|
||||
folder(
|
||||
dir.join("directory_completion")
|
||||
.join("folder_inside_folder")
|
||||
.join("...")
|
||||
.join("test_b"),
|
||||
),
|
||||
folder(
|
||||
dir.join("directory_completion")
|
||||
.join("folder_inside_folder")
|
||||
.join("...")
|
||||
.join(".hidden_folder"),
|
||||
),
|
||||
];
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let target_dir = format!("cd {dir_str}/.../");
|
||||
let slash_suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
let expected_slash_paths: Vec<String> = expected_paths
|
||||
.iter()
|
||||
.map(|s| s.replace('\\', "/"))
|
||||
.collect();
|
||||
|
||||
match_suggestions(&expected_slash_paths, &slash_suggestions);
|
||||
}
|
||||
|
||||
// Match the results
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folder_with_directorycompletions_do_not_collapse_dots() {
|
||||
// Create a new engine
|
||||
let (dir, _, engine, stack) = new_engine();
|
||||
let dir_str = dir
|
||||
.join("directory_completion")
|
||||
.join("folder_inside_folder")
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap();
|
||||
|
||||
// Instantiate a new completer
|
||||
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
|
||||
|
||||
// Test completions for the current folder
|
||||
let target_dir = format!("cd {dir_str}{MAIN_SEPARATOR}..{MAIN_SEPARATOR}..{MAIN_SEPARATOR}");
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
let expected_paths: Vec<String> = vec![
|
||||
folder(
|
||||
dir.join("directory_completion")
|
||||
.join("folder_inside_folder")
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("another"),
|
||||
),
|
||||
folder(
|
||||
dir.join("directory_completion")
|
||||
.join("folder_inside_folder")
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("directory_completion"),
|
||||
),
|
||||
folder(
|
||||
dir.join("directory_completion")
|
||||
.join("folder_inside_folder")
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("test_a"),
|
||||
),
|
||||
folder(
|
||||
dir.join("directory_completion")
|
||||
.join("folder_inside_folder")
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("test_b"),
|
||||
),
|
||||
folder(
|
||||
dir.join("directory_completion")
|
||||
.join("folder_inside_folder")
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join(".hidden_folder"),
|
||||
),
|
||||
];
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let target_dir = format!("cd {dir_str}/../../");
|
||||
let slash_suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
let expected_slash_paths: Vec<String> = expected_paths
|
||||
.iter()
|
||||
.map(|s| s.replace('\\', "/"))
|
||||
.collect();
|
||||
|
||||
match_suggestions(&expected_slash_paths, &slash_suggestions);
|
||||
}
|
||||
|
||||
// Match the results
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -837,7 +1230,7 @@ fn variables_completions() {
|
||||
];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
|
||||
// Test completions for $nu.h (filter)
|
||||
let suggestions = completer.complete("$nu.h", 5);
|
||||
@ -851,7 +1244,7 @@ fn variables_completions() {
|
||||
];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
|
||||
// Test completions for $nu.os-info
|
||||
let suggestions = completer.complete("$nu.os-info.", 12);
|
||||
@ -863,7 +1256,7 @@ fn variables_completions() {
|
||||
"name".into(),
|
||||
];
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
|
||||
// Test completions for custom var
|
||||
let suggestions = completer.complete("$actor.", 7);
|
||||
@ -873,7 +1266,7 @@ fn variables_completions() {
|
||||
let expected: Vec<String> = vec!["age".into(), "name".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
|
||||
// Test completions for custom var (filtering)
|
||||
let suggestions = completer.complete("$actor.n", 8);
|
||||
@ -883,7 +1276,7 @@ fn variables_completions() {
|
||||
let expected: Vec<String> = vec!["name".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
|
||||
// Test completions for $env
|
||||
let suggestions = completer.complete("$env.", 5);
|
||||
@ -896,7 +1289,7 @@ fn variables_completions() {
|
||||
let expected: Vec<String> = vec!["PATH".into(), "PWD".into(), "TEST".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
|
||||
// Test completions for $env
|
||||
let suggestions = completer.complete("$env.T", 6);
|
||||
@ -906,12 +1299,12 @@ fn variables_completions() {
|
||||
let expected: Vec<String> = vec!["TEST".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
|
||||
let suggestions = completer.complete("$", 1);
|
||||
let expected: Vec<String> = vec!["$actor".into(), "$env".into(), "$in".into(), "$nu".into()];
|
||||
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -930,7 +1323,7 @@ fn alias_of_command_and_flags() {
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
match_suggestions(&expected_paths, &suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -949,7 +1342,7 @@ fn alias_of_basic_command() {
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
match_suggestions(&expected_paths, &suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -971,7 +1364,7 @@ fn alias_of_another_alias() {
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
match_suggestions(&expected_paths, &suggestions)
|
||||
}
|
||||
|
||||
fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> {
|
||||
@ -1034,35 +1427,35 @@ fn unknown_command_completion() {
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
match_suggestions(&expected_paths, &suggestions)
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn flagcompletion_triggers_after_cursor(mut completer: NuCompleter) {
|
||||
let suggestions = completer.complete("tst -h", 5);
|
||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn customcompletion_triggers_after_cursor(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-command c", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn customcompletion_triggers_after_cursor_piped(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-command c | ls", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn flagcompletion_triggers_after_cursor_piped(mut completer: NuCompleter) {
|
||||
let suggestions = completer.complete("tst -h | ls", 5);
|
||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1096,77 +1489,88 @@ fn filecompletions_triggers_after_cursor() {
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
match_suggestions(&expected_paths, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_positional(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam ", 5);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_long_flag_1(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam --foo=", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_long_flag_2(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam --foo ", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_long_flag_short(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam -f ", 8);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_short_flag(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam -b ", 8);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_complete_flags(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam -", 6);
|
||||
let expected: Vec<String> = vec!["--foo".into(), "-b".into(), "-f".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn custom_completer_triggers_cursor_before_word(mut custom_completer: NuCompleter) {
|
||||
let suggestions = custom_completer.complete("cmd foo bar", 8);
|
||||
let expected: Vec<String> = vec!["cmd".into(), "foo".into(), "".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn custom_completer_triggers_cursor_on_word_left_boundary(mut custom_completer: NuCompleter) {
|
||||
let suggestions = custom_completer.complete("cmd foo bar", 8);
|
||||
let expected: Vec<String> = vec!["cmd".into(), "foo".into(), "".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn custom_completer_triggers_cursor_next_to_word(mut custom_completer: NuCompleter) {
|
||||
let suggestions = custom_completer.complete("cmd foo bar", 11);
|
||||
let expected: Vec<String> = vec!["cmd".into(), "foo".into(), "bar".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn custom_completer_triggers_cursor_after_word(mut custom_completer: NuCompleter) {
|
||||
let suggestions = custom_completer.complete("cmd foo bar ", 12);
|
||||
let expected: Vec<String> = vec!["cmd".into(), "foo".into(), "bar".into(), "".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn sort_fuzzy_completions_in_alphabetical_order(mut fuzzy_alpha_sort_completer: NuCompleter) {
|
||||
let suggestions = fuzzy_alpha_sort_completer.complete("ls nu", 5);
|
||||
// Even though "nushell" is a better match, it should come second because
|
||||
// the completions should be sorted in alphabetical order
|
||||
match_suggestions(
|
||||
&vec!["custom_completion.nu".into(), "nushell".into()],
|
||||
&suggestions,
|
||||
);
|
||||
}
|
||||
|
||||
#[ignore = "was reverted, still needs fixing"]
|
||||
|
@ -186,7 +186,7 @@ pub fn new_partial_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
|
||||
}
|
||||
|
||||
// match a list of suggestions with the expected values
|
||||
pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
|
||||
pub fn match_suggestions(expected: &Vec<String>, suggestions: &Vec<Suggestion>) {
|
||||
let expected_len = expected.len();
|
||||
let suggestions_len = suggestions.len();
|
||||
if expected_len != suggestions_len {
|
||||
@ -196,13 +196,13 @@ pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
|
||||
Expected: {expected:#?}\n"
|
||||
)
|
||||
}
|
||||
assert_eq!(
|
||||
expected,
|
||||
suggestions
|
||||
.into_iter()
|
||||
.map(|it| it.value)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
let suggestoins_str = suggestions
|
||||
.iter()
|
||||
.map(|it| it.value.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(expected, &suggestoins_str);
|
||||
}
|
||||
|
||||
// append the separator to the converted path
|
||||
|
@ -5,15 +5,18 @@ edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-base"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
||||
version = "0.96.0"
|
||||
version = "0.98.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.96.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.96.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.96.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.96.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.98.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.98.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.98.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.98.0" }
|
||||
|
||||
indexmap = { workspace = true }
|
||||
miette = { workspace = true }
|
||||
|
@ -3,7 +3,7 @@ use miette::Result;
|
||||
use nu_engine::{eval_block, eval_block_with_early_return};
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
cli_error::{report_error, report_error_new},
|
||||
cli_error::{report_parse_error, report_shell_error},
|
||||
debugger::WithoutDebug,
|
||||
engine::{Closure, EngineState, Stack, StateWorkingSet},
|
||||
PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId,
|
||||
@ -91,7 +91,7 @@ pub fn eval_hook(
|
||||
false,
|
||||
);
|
||||
if let Some(err) = working_set.parse_errors.first() {
|
||||
report_error(&working_set, err);
|
||||
report_parse_error(&working_set, err);
|
||||
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "valid source code".into(),
|
||||
@ -123,7 +123,7 @@ pub fn eval_hook(
|
||||
output = pipeline_data;
|
||||
}
|
||||
Err(err) => {
|
||||
report_error_new(engine_state, &err);
|
||||
report_shell_error(engine_state, &err);
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,7 +223,7 @@ pub fn eval_hook(
|
||||
false,
|
||||
);
|
||||
if let Some(err) = working_set.parse_errors.first() {
|
||||
report_error(&working_set, err);
|
||||
report_parse_error(&working_set, err);
|
||||
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "valid source code".into(),
|
||||
@ -251,7 +251,7 @@ pub fn eval_hook(
|
||||
output = pipeline_data;
|
||||
}
|
||||
Err(err) => {
|
||||
report_error_new(engine_state, &err);
|
||||
report_shell_error(engine_state, &err);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,21 +3,26 @@ use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
Range, ShellError, Span, Value,
|
||||
};
|
||||
use std::{ops::Bound, path::PathBuf};
|
||||
use std::ops::Bound;
|
||||
|
||||
pub fn get_init_cwd() -> PathBuf {
|
||||
std::env::current_dir().unwrap_or_else(|_| {
|
||||
std::env::var("PWD")
|
||||
.map(Into::into)
|
||||
.unwrap_or_else(|_| nu_path::home_dir().unwrap_or_default())
|
||||
})
|
||||
pub fn get_init_cwd() -> AbsolutePathBuf {
|
||||
std::env::current_dir()
|
||||
.ok()
|
||||
.and_then(|path| AbsolutePathBuf::try_from(path).ok())
|
||||
.or_else(|| {
|
||||
std::env::var("PWD")
|
||||
.ok()
|
||||
.and_then(|path| AbsolutePathBuf::try_from(path).ok())
|
||||
})
|
||||
.or_else(nu_path::home_dir)
|
||||
.expect("Failed to get current working directory")
|
||||
}
|
||||
|
||||
pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf {
|
||||
pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> AbsolutePathBuf {
|
||||
engine_state
|
||||
.cwd(Some(stack))
|
||||
.map(AbsolutePathBuf::into_std_path_buf)
|
||||
.unwrap_or(crate::util::get_init_cwd())
|
||||
.ok()
|
||||
.unwrap_or_else(get_init_cwd)
|
||||
}
|
||||
|
||||
type MakeRangeError = fn(&str, Span) -> ShellError;
|
||||
|
@ -5,21 +5,24 @@ edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-extra"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
|
||||
version = "0.96.0"
|
||||
version = "0.98.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.96.0" }
|
||||
nu-json = { version = "0.96.0", path = "../nu-json" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.96.0" }
|
||||
nu-pretty-hex = { version = "0.96.0", path = "../nu-pretty-hex" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.96.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.96.0" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.98.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.98.0" }
|
||||
nu-json = { version = "0.98.0", path = "../nu-json" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.98.0" }
|
||||
nu-pretty-hex = { version = "0.98.0", path = "../nu-pretty-hex" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.98.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.98.0" }
|
||||
|
||||
# Potential dependencies for extras
|
||||
heck = { workspace = true }
|
||||
@ -33,6 +36,6 @@ v_htmlescape = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.96.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.96.0" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.98.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.98.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.98.0" }
|
@ -37,7 +37,7 @@ impl Command for BitsAnd {
|
||||
.category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Performs bitwise and for ints or binary values."
|
||||
}
|
||||
|
||||
|
@ -14,11 +14,11 @@ impl Command for Bits {
|
||||
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Various commands for working with bits."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
fn extra_description(&self) -> &str {
|
||||
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ impl Command for BitsInto {
|
||||
.category(Category::Conversions)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Convert value to a binary primitive."
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,25 @@ enum InputNumType {
|
||||
SignedEight,
|
||||
}
|
||||
|
||||
impl InputNumType {
|
||||
fn num_bits(self) -> u32 {
|
||||
match self {
|
||||
InputNumType::One => 8,
|
||||
InputNumType::Two => 16,
|
||||
InputNumType::Four => 32,
|
||||
InputNumType::Eight => 64,
|
||||
InputNumType::SignedOne => 8,
|
||||
InputNumType::SignedTwo => 16,
|
||||
InputNumType::SignedFour => 32,
|
||||
InputNumType::SignedEight => 64,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_permitted_bit_shift(self, bits: u32) -> bool {
|
||||
bits < self.num_bits()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_number_bytes(
|
||||
number_bytes: Option<Spanned<usize>>,
|
||||
head: Span,
|
||||
|
@ -51,7 +51,7 @@ impl Command for BitsNot {
|
||||
.category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Performs logical negation on each bit."
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ impl Command for BitsOr {
|
||||
.category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Performs bitwise or for ints or binary values."
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||
use itertools::Itertools;
|
||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
struct Arguments {
|
||||
signed: bool,
|
||||
bits: usize,
|
||||
bits: Spanned<usize>,
|
||||
number_size: NumberBytes,
|
||||
}
|
||||
|
||||
@ -53,7 +52,7 @@ impl Command for BitsRol {
|
||||
.category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Bitwise rotate left for ints or binary values."
|
||||
}
|
||||
|
||||
@ -69,7 +68,7 @@ impl Command for BitsRol {
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||
let bits = call.req(engine_state, stack, 0)?;
|
||||
let signed = call.has_flag(engine_state, stack, "signed")?;
|
||||
let number_bytes: Option<Spanned<usize>> =
|
||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||
@ -119,6 +118,8 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
number_size,
|
||||
bits,
|
||||
} = *args;
|
||||
let bits_span = bits.span;
|
||||
let bits = bits.item;
|
||||
|
||||
match input {
|
||||
Value::Int { val, .. } => {
|
||||
@ -127,6 +128,19 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
let bits = bits as u32;
|
||||
let input_num_type = get_input_num_type(val, signed, number_size);
|
||||
|
||||
if bits > input_num_type.num_bits() {
|
||||
return Value::error(
|
||||
ShellError::IncorrectValue {
|
||||
msg: format!(
|
||||
"Trying to rotate by more than the available bits ({})",
|
||||
input_num_type.num_bits()
|
||||
),
|
||||
val_span: bits_span,
|
||||
call_span: span,
|
||||
},
|
||||
span,
|
||||
);
|
||||
}
|
||||
let int = match input_num_type {
|
||||
One => (val as u8).rotate_left(bits) as i64,
|
||||
Two => (val as u16).rotate_left(bits) as i64,
|
||||
@ -157,16 +171,28 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
Value::int(int, span)
|
||||
}
|
||||
Value::Binary { val, .. } => {
|
||||
let len = val.len();
|
||||
if bits > len * 8 {
|
||||
return Value::error(
|
||||
ShellError::IncorrectValue {
|
||||
msg: format!(
|
||||
"Trying to rotate by more than the available bits ({})",
|
||||
len * 8
|
||||
),
|
||||
val_span: bits_span,
|
||||
call_span: span,
|
||||
},
|
||||
span,
|
||||
);
|
||||
}
|
||||
let byte_shift = bits / 8;
|
||||
let bit_rotate = bits % 8;
|
||||
|
||||
let mut bytes = val
|
||||
.iter()
|
||||
.copied()
|
||||
.circular_tuple_windows::<(u8, u8)>()
|
||||
.map(|(lhs, rhs)| (lhs << bit_rotate) | (rhs >> (8 - bit_rotate)))
|
||||
.collect::<Vec<u8>>();
|
||||
bytes.rotate_left(byte_shift);
|
||||
let bytes = if bit_rotate == 0 {
|
||||
rotate_bytes_left(val, byte_shift)
|
||||
} else {
|
||||
rotate_bytes_and_bits_left(val, byte_shift, bit_rotate)
|
||||
};
|
||||
|
||||
Value::binary(bytes, span)
|
||||
}
|
||||
@ -184,6 +210,34 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
}
|
||||
}
|
||||
|
||||
fn rotate_bytes_left(data: &[u8], byte_shift: usize) -> Vec<u8> {
|
||||
let len = data.len();
|
||||
let mut output = vec![0; len];
|
||||
output[..len - byte_shift].copy_from_slice(&data[byte_shift..]);
|
||||
output[len - byte_shift..].copy_from_slice(&data[..byte_shift]);
|
||||
output
|
||||
}
|
||||
|
||||
fn rotate_bytes_and_bits_left(data: &[u8], byte_shift: usize, bit_shift: usize) -> Vec<u8> {
|
||||
debug_assert!(byte_shift < data.len());
|
||||
debug_assert!(
|
||||
(1..8).contains(&bit_shift),
|
||||
"Bit shifts of 0 can't be handled by this impl and everything else should be part of the byteshift");
|
||||
let mut bytes = Vec::with_capacity(data.len());
|
||||
let mut next_index = byte_shift;
|
||||
for _ in 0..data.len() {
|
||||
let curr_byte = data[next_index];
|
||||
next_index += 1;
|
||||
if next_index == data.len() {
|
||||
next_index = 0;
|
||||
}
|
||||
let next_byte = data[next_index];
|
||||
let new_byte = (curr_byte << bit_shift) | (next_byte >> (8 - bit_shift));
|
||||
bytes.push(new_byte);
|
||||
}
|
||||
bytes
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
@ -1,11 +1,10 @@
|
||||
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||
use itertools::Itertools;
|
||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
struct Arguments {
|
||||
signed: bool,
|
||||
bits: usize,
|
||||
bits: Spanned<usize>,
|
||||
number_size: NumberBytes,
|
||||
}
|
||||
|
||||
@ -53,7 +52,7 @@ impl Command for BitsRor {
|
||||
.category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Bitwise rotate right for ints or binary values."
|
||||
}
|
||||
|
||||
@ -69,7 +68,7 @@ impl Command for BitsRor {
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||
let bits = call.req(engine_state, stack, 0)?;
|
||||
let signed = call.has_flag(engine_state, stack, "signed")?;
|
||||
let number_bytes: Option<Spanned<usize>> =
|
||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||
@ -123,6 +122,8 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
number_size,
|
||||
bits,
|
||||
} = *args;
|
||||
let bits_span = bits.span;
|
||||
let bits = bits.item;
|
||||
|
||||
match input {
|
||||
Value::Int { val, .. } => {
|
||||
@ -131,6 +132,19 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
let bits = bits as u32;
|
||||
let input_num_type = get_input_num_type(val, signed, number_size);
|
||||
|
||||
if bits > input_num_type.num_bits() {
|
||||
return Value::error(
|
||||
ShellError::IncorrectValue {
|
||||
msg: format!(
|
||||
"Trying to rotate by more than the available bits ({})",
|
||||
input_num_type.num_bits()
|
||||
),
|
||||
val_span: bits_span,
|
||||
call_span: span,
|
||||
},
|
||||
span,
|
||||
);
|
||||
}
|
||||
let int = match input_num_type {
|
||||
One => (val as u8).rotate_right(bits) as i64,
|
||||
Two => (val as u16).rotate_right(bits) as i64,
|
||||
@ -161,16 +175,28 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
Value::int(int, span)
|
||||
}
|
||||
Value::Binary { val, .. } => {
|
||||
let len = val.len();
|
||||
if bits > len * 8 {
|
||||
return Value::error(
|
||||
ShellError::IncorrectValue {
|
||||
msg: format!(
|
||||
"Trying to rotate by more than the available bits ({})",
|
||||
len * 8
|
||||
),
|
||||
val_span: bits_span,
|
||||
call_span: span,
|
||||
},
|
||||
span,
|
||||
);
|
||||
}
|
||||
let byte_shift = bits / 8;
|
||||
let bit_rotate = bits % 8;
|
||||
|
||||
let mut bytes = val
|
||||
.iter()
|
||||
.copied()
|
||||
.circular_tuple_windows::<(u8, u8)>()
|
||||
.map(|(lhs, rhs)| (lhs >> bit_rotate) | (rhs << (8 - bit_rotate)))
|
||||
.collect::<Vec<u8>>();
|
||||
bytes.rotate_right(byte_shift);
|
||||
let bytes = if bit_rotate == 0 {
|
||||
rotate_bytes_right(val, byte_shift)
|
||||
} else {
|
||||
rotate_bytes_and_bits_right(val, byte_shift, bit_rotate)
|
||||
};
|
||||
|
||||
Value::binary(bytes, span)
|
||||
}
|
||||
@ -188,6 +214,35 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
}
|
||||
}
|
||||
|
||||
fn rotate_bytes_right(data: &[u8], byte_shift: usize) -> Vec<u8> {
|
||||
let len = data.len();
|
||||
let mut output = vec![0; len];
|
||||
output[byte_shift..].copy_from_slice(&data[..len - byte_shift]);
|
||||
output[..byte_shift].copy_from_slice(&data[len - byte_shift..]);
|
||||
output
|
||||
}
|
||||
|
||||
fn rotate_bytes_and_bits_right(data: &[u8], byte_shift: usize, bit_shift: usize) -> Vec<u8> {
|
||||
debug_assert!(byte_shift < data.len());
|
||||
debug_assert!(
|
||||
(1..8).contains(&bit_shift),
|
||||
"Bit shifts of 0 can't be handled by this impl and everything else should be part of the byteshift"
|
||||
);
|
||||
let mut bytes = Vec::with_capacity(data.len());
|
||||
let mut previous_index = data.len() - byte_shift - 1;
|
||||
for _ in 0..data.len() {
|
||||
let previous_byte = data[previous_index];
|
||||
previous_index += 1;
|
||||
if previous_index == data.len() {
|
||||
previous_index = 0;
|
||||
}
|
||||
let curr_byte = data[previous_index];
|
||||
let rotated_byte = (curr_byte >> bit_shift) | (previous_byte << (8 - bit_shift));
|
||||
bytes.push(rotated_byte);
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
@ -7,7 +7,7 @@ use std::iter;
|
||||
|
||||
struct Arguments {
|
||||
signed: bool,
|
||||
bits: usize,
|
||||
bits: Spanned<usize>,
|
||||
number_size: NumberBytes,
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ impl Command for BitsShl {
|
||||
.category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Bitwise shift left for ints or binary values."
|
||||
}
|
||||
|
||||
@ -71,7 +71,9 @@ impl Command for BitsShl {
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||
// This restricts to a positive shift value (our underlying operations do not
|
||||
// permit them)
|
||||
let bits: Spanned<usize> = call.req(engine_state, stack, 0)?;
|
||||
let signed = call.has_flag(engine_state, stack, "signed")?;
|
||||
let number_bytes: Option<Spanned<usize>> =
|
||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||
@ -131,14 +133,29 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
number_size,
|
||||
bits,
|
||||
} = *args;
|
||||
let bits_span = bits.span;
|
||||
let bits = bits.item;
|
||||
|
||||
match input {
|
||||
Value::Int { val, .. } => {
|
||||
use InputNumType::*;
|
||||
let val = *val;
|
||||
let bits = bits as u64;
|
||||
let bits = bits as u32;
|
||||
|
||||
let input_num_type = get_input_num_type(val, signed, number_size);
|
||||
if !input_num_type.is_permitted_bit_shift(bits) {
|
||||
return Value::error(
|
||||
ShellError::IncorrectValue {
|
||||
msg: format!(
|
||||
"Trying to shift by more than the available bits (permitted < {})",
|
||||
input_num_type.num_bits()
|
||||
),
|
||||
val_span: bits_span,
|
||||
call_span: span,
|
||||
},
|
||||
span,
|
||||
);
|
||||
}
|
||||
let int = match input_num_type {
|
||||
One => ((val as u8) << bits) as i64,
|
||||
Two => ((val as u16) << bits) as i64,
|
||||
@ -147,12 +164,14 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
let Ok(i) = i64::try_from((val as u64) << bits) else {
|
||||
return Value::error(
|
||||
ShellError::GenericError {
|
||||
error: "result out of range for specified number".into(),
|
||||
error: "result out of range for int".into(),
|
||||
msg: format!(
|
||||
"shifting left by {bits} is out of range for the value {val}"
|
||||
),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
help: Some(
|
||||
"Ensure the result fits in a 64-bit signed integer.".into(),
|
||||
),
|
||||
inner: vec![],
|
||||
},
|
||||
span,
|
||||
@ -172,19 +191,26 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
let byte_shift = bits / 8;
|
||||
let bit_shift = bits % 8;
|
||||
|
||||
use itertools::Position::*;
|
||||
let bytes = val
|
||||
.iter()
|
||||
.copied()
|
||||
.skip(byte_shift)
|
||||
.circular_tuple_windows::<(u8, u8)>()
|
||||
.with_position()
|
||||
.map(|(pos, (lhs, rhs))| match pos {
|
||||
Last | Only => lhs << bit_shift,
|
||||
_ => (lhs << bit_shift) | (rhs >> bit_shift),
|
||||
})
|
||||
.chain(iter::repeat(0).take(byte_shift))
|
||||
.collect::<Vec<u8>>();
|
||||
// This is purely for symmetry with the int case and the fact that the
|
||||
// shift right implementation in its current form panicked with an overflow
|
||||
if bits > val.len() * 8 {
|
||||
return Value::error(
|
||||
ShellError::IncorrectValue {
|
||||
msg: format!(
|
||||
"Trying to shift by more than the available bits ({})",
|
||||
val.len() * 8
|
||||
),
|
||||
val_span: bits_span,
|
||||
call_span: span,
|
||||
},
|
||||
span,
|
||||
);
|
||||
}
|
||||
let bytes = if bit_shift == 0 {
|
||||
shift_bytes_left(val, byte_shift)
|
||||
} else {
|
||||
shift_bytes_and_bits_left(val, byte_shift, bit_shift)
|
||||
};
|
||||
|
||||
Value::binary(bytes, span)
|
||||
}
|
||||
@ -202,6 +228,31 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
}
|
||||
}
|
||||
|
||||
fn shift_bytes_left(data: &[u8], byte_shift: usize) -> Vec<u8> {
|
||||
let len = data.len();
|
||||
let mut output = vec![0; len];
|
||||
output[..len - byte_shift].copy_from_slice(&data[byte_shift..]);
|
||||
output
|
||||
}
|
||||
|
||||
fn shift_bytes_and_bits_left(data: &[u8], byte_shift: usize, bit_shift: usize) -> Vec<u8> {
|
||||
use itertools::Position::*;
|
||||
debug_assert!((1..8).contains(&bit_shift),
|
||||
"Bit shifts of 0 can't be handled by this impl and everything else should be part of the byteshift"
|
||||
);
|
||||
data.iter()
|
||||
.copied()
|
||||
.skip(byte_shift)
|
||||
.circular_tuple_windows::<(u8, u8)>()
|
||||
.with_position()
|
||||
.map(|(pos, (lhs, rhs))| match pos {
|
||||
Last | Only => lhs << bit_shift,
|
||||
_ => (lhs << bit_shift) | (rhs >> (8 - bit_shift)),
|
||||
})
|
||||
.chain(iter::repeat(0).take(byte_shift))
|
||||
.collect::<Vec<u8>>()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
@ -1,13 +1,10 @@
|
||||
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||
use itertools::Itertools;
|
||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
use std::iter;
|
||||
|
||||
struct Arguments {
|
||||
signed: bool,
|
||||
bits: usize,
|
||||
bits: Spanned<usize>,
|
||||
number_size: NumberBytes,
|
||||
}
|
||||
|
||||
@ -55,7 +52,7 @@ impl Command for BitsShr {
|
||||
.category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Bitwise shift right for ints or binary values."
|
||||
}
|
||||
|
||||
@ -71,7 +68,9 @@ impl Command for BitsShr {
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
||||
// This restricts to a positive shift value (our underlying operations do not
|
||||
// permit them)
|
||||
let bits: Spanned<usize> = call.req(engine_state, stack, 0)?;
|
||||
let signed = call.has_flag(engine_state, stack, "signed")?;
|
||||
let number_bytes: Option<Spanned<usize>> =
|
||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||
@ -121,6 +120,8 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
number_size,
|
||||
bits,
|
||||
} = *args;
|
||||
let bits_span = bits.span;
|
||||
let bits = bits.item;
|
||||
|
||||
match input {
|
||||
Value::Int { val, .. } => {
|
||||
@ -129,6 +130,19 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
let bits = bits as u32;
|
||||
let input_num_type = get_input_num_type(val, signed, number_size);
|
||||
|
||||
if !input_num_type.is_permitted_bit_shift(bits) {
|
||||
return Value::error(
|
||||
ShellError::IncorrectValue {
|
||||
msg: format!(
|
||||
"Trying to shift by more than the available bits (permitted < {})",
|
||||
input_num_type.num_bits()
|
||||
),
|
||||
val_span: bits_span,
|
||||
call_span: span,
|
||||
},
|
||||
span,
|
||||
);
|
||||
}
|
||||
let int = match input_num_type {
|
||||
One => ((val as u8) >> bits) as i64,
|
||||
Two => ((val as u16) >> bits) as i64,
|
||||
@ -147,21 +161,27 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
let bit_shift = bits % 8;
|
||||
|
||||
let len = val.len();
|
||||
use itertools::Position::*;
|
||||
let bytes = iter::repeat(0)
|
||||
.take(byte_shift)
|
||||
.chain(
|
||||
val.iter()
|
||||
.copied()
|
||||
.circular_tuple_windows::<(u8, u8)>()
|
||||
.with_position()
|
||||
.map(|(pos, (lhs, rhs))| match pos {
|
||||
First | Only => lhs >> bit_shift,
|
||||
_ => (lhs >> bit_shift) | (rhs << bit_shift),
|
||||
})
|
||||
.take(len - byte_shift),
|
||||
)
|
||||
.collect::<Vec<u8>>();
|
||||
// This check is done for symmetry with the int case and the previous
|
||||
// implementation would overflow byte indices leading to unexpected output
|
||||
// lengths
|
||||
if bits > len * 8 {
|
||||
return Value::error(
|
||||
ShellError::IncorrectValue {
|
||||
msg: format!(
|
||||
"Trying to shift by more than the available bits ({})",
|
||||
len * 8
|
||||
),
|
||||
val_span: bits_span,
|
||||
call_span: span,
|
||||
},
|
||||
span,
|
||||
);
|
||||
}
|
||||
let bytes = if bit_shift == 0 {
|
||||
shift_bytes_right(val, byte_shift)
|
||||
} else {
|
||||
shift_bytes_and_bits_right(val, byte_shift, bit_shift)
|
||||
};
|
||||
|
||||
Value::binary(bytes, span)
|
||||
}
|
||||
@ -178,6 +198,35 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
),
|
||||
}
|
||||
}
|
||||
fn shift_bytes_right(data: &[u8], byte_shift: usize) -> Vec<u8> {
|
||||
let len = data.len();
|
||||
let mut output = vec![0; len];
|
||||
output[byte_shift..].copy_from_slice(&data[..len - byte_shift]);
|
||||
output
|
||||
}
|
||||
|
||||
fn shift_bytes_and_bits_right(data: &[u8], byte_shift: usize, bit_shift: usize) -> Vec<u8> {
|
||||
debug_assert!(
|
||||
bit_shift > 0 && bit_shift < 8,
|
||||
"bit_shift should be in the range (0, 8)"
|
||||
);
|
||||
let len = data.len();
|
||||
let mut output = vec![0; len];
|
||||
|
||||
for i in byte_shift..len {
|
||||
let shifted_bits = data[i - byte_shift] >> bit_shift;
|
||||
let carried_bits = if i > byte_shift {
|
||||
data[i - byte_shift - 1] << (8 - bit_shift)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let shifted_byte = shifted_bits | carried_bits;
|
||||
|
||||
output[i] = shifted_byte;
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
@ -38,7 +38,7 @@ impl Command for BitsXor {
|
||||
.category(Category::Bits)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Performs bitwise xor for ints or binary values."
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ impl Command for Fmt {
|
||||
"fmt"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Format a number."
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ impl Command for EachWhile {
|
||||
"each while"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Run a closure on each row of the input list until a null is found, then create a new list with the results."
|
||||
}
|
||||
|
||||
|
@ -18,11 +18,11 @@ impl Command for Roll {
|
||||
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Rolling commands for tables."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
fn extra_description(&self) -> &str {
|
||||
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ impl Command for RollDown {
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Roll table rows down."
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ impl Command for RollLeft {
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Roll record or table columns left."
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ impl Command for RollRight {
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Roll table columns right."
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ impl Command for RollUp {
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Roll table rows up."
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ impl Command for Rotate {
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Rotates a table or record clockwise (default) or counter-clockwise (use --ccw flag)."
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ impl Command for UpdateCells {
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Update the table cells."
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ impl Command for FromUrl {
|
||||
.category(Category::Formats)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Parse url-encoded string as a record."
|
||||
}
|
||||
|
||||
|
@ -138,11 +138,11 @@ impl Command for ToHtml {
|
||||
]
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Convert table into simple HTML."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
fn extra_description(&self) -> &str {
|
||||
"Screenshots of the themes can be browsed here: https://github.com/mbadolato/iTerm2-Color-Schemes."
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Math)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Returns the arccosine of the number."
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Math)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Returns the inverse of the hyperbolic cosine function."
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Math)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Returns the arcsine of the number."
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Math)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Returns the inverse of the hyperbolic sine function."
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Math)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Returns the arctangent of the number."
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Math)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Returns the inverse of the hyperbolic tangent function."
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Math)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Returns the cosine of the number."
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Math)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Returns the hyperbolic cosine of the number."
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Math)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Returns e raised to the power of x."
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Math)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Returns the natural logarithm. Base: (math e)."
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Math)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Returns the sine of the number."
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Math)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Returns the hyperbolic sine of the number."
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Math)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Returns the tangent of the number."
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Math)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Returns the hyperbolic tangent of the number."
|
||||
}
|
||||
|
||||
|
@ -45,8 +45,6 @@ pub fn add_extra_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
|
||||
bind_command!(
|
||||
strings::format::FormatPattern,
|
||||
strings::encode_decode::EncodeHex,
|
||||
strings::encode_decode::DecodeHex,
|
||||
strings::str_::case::Str,
|
||||
strings::str_::case::StrCamelCase,
|
||||
strings::str_::case::StrKebabCase,
|
||||
|
@ -53,7 +53,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Platform)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Add a color gradient (using ANSI color codes) to the given string."
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ impl Command for DecodeHex {
|
||||
.category(Category::Formats)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Hex decode a value."
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ impl Command for EncodeHex {
|
||||
.category(Category::Formats)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Encode a binary value using hex."
|
||||
}
|
||||
|
||||
|
@ -1,192 +0,0 @@
|
||||
use nu_cmd_base::input_handler::{operate as general_operate, CmdArgument};
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
enum HexDecodingError {
|
||||
InvalidLength(usize),
|
||||
InvalidDigit(usize, char),
|
||||
}
|
||||
|
||||
fn hex_decode(value: &str) -> Result<Vec<u8>, HexDecodingError> {
|
||||
let mut digits = value
|
||||
.chars()
|
||||
.enumerate()
|
||||
.filter(|(_, c)| !c.is_whitespace());
|
||||
|
||||
let mut res = Vec::with_capacity(value.len() / 2);
|
||||
loop {
|
||||
let c1 = match digits.next() {
|
||||
Some((ind, c)) => match c.to_digit(16) {
|
||||
Some(d) => d,
|
||||
None => return Err(HexDecodingError::InvalidDigit(ind, c)),
|
||||
},
|
||||
None => return Ok(res),
|
||||
};
|
||||
let c2 = match digits.next() {
|
||||
Some((ind, c)) => match c.to_digit(16) {
|
||||
Some(d) => d,
|
||||
None => return Err(HexDecodingError::InvalidDigit(ind, c)),
|
||||
},
|
||||
None => {
|
||||
return Err(HexDecodingError::InvalidLength(value.len()));
|
||||
}
|
||||
};
|
||||
res.push((c1 << 4 | c2) as u8);
|
||||
}
|
||||
}
|
||||
|
||||
fn hex_digit(num: u8) -> char {
|
||||
match num {
|
||||
0..=9 => (num + b'0') as char,
|
||||
10..=15 => (num - 10 + b'A') as char,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn hex_encode(bytes: &[u8]) -> String {
|
||||
let mut res = String::with_capacity(bytes.len() * 2);
|
||||
for byte in bytes {
|
||||
res.push(hex_digit(byte >> 4));
|
||||
res.push(hex_digit(byte & 0b1111));
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HexConfig {
|
||||
pub action_type: ActionType,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ActionType {
|
||||
Encode,
|
||||
Decode,
|
||||
}
|
||||
|
||||
struct Arguments {
|
||||
cell_paths: Option<Vec<CellPath>>,
|
||||
encoding_config: HexConfig,
|
||||
}
|
||||
|
||||
impl CmdArgument for Arguments {
|
||||
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.cell_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn operate(
|
||||
action_type: ActionType,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||
|
||||
let args = Arguments {
|
||||
encoding_config: HexConfig { action_type },
|
||||
cell_paths,
|
||||
};
|
||||
|
||||
general_operate(action, args, input, call.head, engine_state.signals())
|
||||
}
|
||||
|
||||
fn action(
|
||||
input: &Value,
|
||||
// only used for `decode` action
|
||||
args: &Arguments,
|
||||
command_span: Span,
|
||||
) -> Value {
|
||||
let hex_config = &args.encoding_config;
|
||||
|
||||
match input {
|
||||
// Propagate existing errors.
|
||||
Value::Error { .. } => input.clone(),
|
||||
Value::Binary { val, .. } => match hex_config.action_type {
|
||||
ActionType::Encode => Value::string(hex_encode(val.as_ref()), command_span),
|
||||
ActionType::Decode => Value::error(
|
||||
ShellError::UnsupportedInput { msg: "Binary data can only be encoded".to_string(), input: "value originates from here".into(), msg_span: command_span, input_span: input.span() },
|
||||
command_span,
|
||||
),
|
||||
},
|
||||
Value::String { val, .. } => {
|
||||
match hex_config.action_type {
|
||||
ActionType::Encode => Value::error(
|
||||
ShellError::UnsupportedInput { msg: "String value can only be decoded".to_string(), input: "value originates from here".into(), msg_span: command_span, input_span: input.span() },
|
||||
command_span,
|
||||
),
|
||||
|
||||
ActionType::Decode => match hex_decode(val.as_ref()) {
|
||||
Ok(decoded_value) => Value::binary(decoded_value, command_span),
|
||||
Err(HexDecodingError::InvalidLength(len)) => Value::error(ShellError::GenericError {
|
||||
error: "value could not be hex decoded".into(),
|
||||
msg: format!("invalid hex input length: {len}. The length should be even"),
|
||||
span: Some(command_span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
command_span,
|
||||
),
|
||||
Err(HexDecodingError::InvalidDigit(index, digit)) => Value::error(ShellError::GenericError {
|
||||
error: "value could not be hex decoded".into(),
|
||||
msg: format!("invalid hex digit: '{digit}' at index {index}. Only 0-9, A-F, a-f are allowed in hex encoding"),
|
||||
span: Some(command_span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
command_span,
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
other => Value::error(
|
||||
ShellError::TypeMismatch {
|
||||
err_message: format!("string or binary, not {}", other.get_type()),
|
||||
span: other.span(),
|
||||
},
|
||||
other.span(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{action, ActionType, Arguments, HexConfig};
|
||||
use nu_protocol::{Span, Value};
|
||||
|
||||
#[test]
|
||||
fn hex_encode() {
|
||||
let word = Value::binary([77, 97, 110], Span::test_data());
|
||||
let expected = Value::test_string("4D616E");
|
||||
|
||||
let actual = action(
|
||||
&word,
|
||||
&Arguments {
|
||||
encoding_config: HexConfig {
|
||||
action_type: ActionType::Encode,
|
||||
},
|
||||
cell_paths: None,
|
||||
},
|
||||
Span::test_data(),
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_decode() {
|
||||
let word = Value::test_string("4D 61\r\n\n6E");
|
||||
let expected = Value::binary([77, 97, 110], Span::test_data());
|
||||
|
||||
let actual = action(
|
||||
&word,
|
||||
&Arguments {
|
||||
encoding_config: HexConfig {
|
||||
action_type: ActionType::Decode,
|
||||
},
|
||||
cell_paths: None,
|
||||
},
|
||||
Span::test_data(),
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
mod decode_hex;
|
||||
mod encode_hex;
|
||||
mod hex;
|
||||
|
||||
pub(crate) use decode_hex::DecodeHex;
|
||||
pub(crate) use encode_hex::EncodeHex;
|
@ -24,7 +24,7 @@ impl Command for FormatPattern {
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Format columns into a string using a simple pattern."
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,2 @@
|
||||
pub(crate) mod encode_decode;
|
||||
pub(crate) mod format;
|
||||
pub(crate) mod str_;
|
||||
|
@ -30,7 +30,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Convert a string to camelCase."
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Convert a string to kebab-case."
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Convert a string to PascalCase."
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Convert a string to SCREAMING_SNAKE_CASE."
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ impl Command for SubCommand {
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
fn description(&self) -> &str {
|
||||
"Convert a string to snake_case."
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user