forked from extern/nushell
Compare commits
558 Commits
Author | SHA1 | Date | |
---|---|---|---|
3b220e07e3 | |||
16799a1d78 | |||
38bf3f6e1b | |||
e83123dcca | |||
648486400c | |||
59ee96c70d | |||
365f5954ee | |||
24ecb84d97 | |||
0c4d5330ee | |||
884d5312bb | |||
76d1d70e83 | |||
02de69de92 | |||
533603b72c | |||
1ecbb3e09f | |||
d23a3737c0 | |||
822c434c12 | |||
d126793290 | |||
adf38c7c76 | |||
79ebf0c0a9 | |||
f234a0ea33 | |||
2466a39574 | |||
1239e86320 | |||
5b0546cfce | |||
b6d765a2d8 | |||
530162b4c4 | |||
25cbcb511d | |||
9996e4a1f8 | |||
c52884b3c8 | |||
0f645b3bb6 | |||
1e453020b6 | |||
06c72df672 | |||
9d65c47313 | |||
b576123b0a | |||
1633004643 | |||
fbb4fad7ac | |||
c9bc0c7d3e | |||
eec8645b9c | |||
1f4131532d | |||
5c7f7883c8 | |||
b0acc1d890 | |||
bed236362a | |||
797a90520c | |||
20bf3c587f | |||
bae6d694ca | |||
83720a9f30 | |||
a60381a932 | |||
aad3ac11da | |||
8b7696f4c1 | |||
a900166e27 | |||
2595f31541 | |||
6cba7c6b40 | |||
3b1d405b96 | |||
5fd34320e9 | |||
5e52bd77e0 | |||
cf8fcef9bf | |||
47867a58df | |||
b274ec19fd | |||
be5ed3290c | |||
187b87c61c | |||
f2169c8022 | |||
9fb59a6f43 | |||
55edef5dda | |||
fac2f43aa4 | |||
6d2cb4382a | |||
351bff8233 | |||
7fe2e60af7 | |||
999dfdf936 | |||
cc7b5c5a26 | |||
9a265847e2 | |||
b088f395dc | |||
6ccd547d81 | |||
57b0c722c6 | |||
13160b3ec3 | |||
410f3c5c8a | |||
b296d6ee3c | |||
9a739d9f0d | |||
8cba6678b3 | |||
055aae9f5c | |||
a67dad3d15 | |||
43814dcb0f | |||
cc781a1ecd | |||
a7a5ec31be | |||
48e4448e55 | |||
e6fbf7d01d | |||
62555c997e | |||
03317ff92e | |||
1661bb68f9 | |||
c9e9b138eb | |||
5f818eaefe | |||
078ba5aabe | |||
c06ef201b7 | |||
67e7eec7da | |||
af72a18785 | |||
50b2dac8d7 | |||
2ae9ad8676 | |||
b508d1028c | |||
040f10e19d | |||
10a9a17b8c | |||
b9dd47ebb7 | |||
211d9c685c | |||
0110345755 | |||
56cdee1fd8 | |||
1bded8572c | |||
f975c9923a | |||
b7fb0af967 | |||
3a11b9d69d | |||
50fb8243c8 | |||
741e3c3d8f | |||
3eb9c2a565 | |||
de41345bf2 | |||
b9c2f9ee56 | |||
872945ae8e | |||
cd8f04041c | |||
3a39e3df22 | |||
39156930f5 | |||
83674909f1 | |||
18ddf95d44 | |||
81c61f3243 | |||
efc1cfa939 | |||
cbbccaa722 | |||
d735607ac8 | |||
d7ba8872bf | |||
6536fa5ff7 | |||
14b0ff3f05 | |||
00b3a07efe | |||
9a2a6ab52c | |||
40f72e80c3 | |||
56c9587cc3 | |||
625a9144cf | |||
773dafa8ac | |||
e234f3ea7b | |||
67c8b0db69 | |||
6b4cbe7a98 | |||
70520000d2 | |||
01c79bbefc | |||
db1dccc762 | |||
1c5bd21ebc | |||
fe99729cdb | |||
03667bdf8c | |||
eb36dbb091 | |||
e211e96d33 | |||
7a7d43344e | |||
75fedcc8dd | |||
ed4927441f | |||
6fba5f5ec7 | |||
12b897b149 | |||
16cbed7d6e | |||
0e36c43c64 | |||
2562e306b6 | |||
c82dfce246 | |||
82b7548c0c | |||
0884d1a5ce | |||
00b576b7f1 | |||
88ff622b16 | |||
394487b3a7 | |||
acc3ca9de7 | |||
c00a05a762 | |||
51aa66fef7 | |||
25b90744b7 | |||
e810995cf8 | |||
f0a073b397 | |||
cd00a489af | |||
cbf7feef22 | |||
a6afc89338 | |||
c7b9183e47 | |||
b6c537d782 | |||
c3428b891a | |||
50ad729e45 | |||
aaefc5e110 | |||
3bc0b332f4 | |||
0cf7de598b | |||
3b8258ae57 | |||
9444020f26 | |||
251599c507 | |||
714a0ccd24 | |||
cc39069e13 | |||
ce581a80a6 | |||
e889679d42 | |||
ff2aba7ae3 | |||
cf923fc44c | |||
3857e368ff | |||
04531357b4 | |||
8e763a2fd6 | |||
442faa5576 | |||
e97368433b | |||
910e3b52a7 | |||
968926a327 | |||
e5a52aad9d | |||
b19da158d5 | |||
dfbbacfdf8 | |||
bf8de9d1ea | |||
01d30a416b | |||
03b5e9d853 | |||
6542f23ad1 | |||
39ca734873 | |||
c747ec75c9 | |||
f8c1e3ac61 | |||
8499f7b4f8 | |||
b752fdb0dc | |||
b07e206b36 | |||
592dc4bbfa | |||
b70766e6f5 | |||
24d2c8dd8e | |||
e8bcfbaed1 | |||
a15462fd00 | |||
2ae4408ced | |||
efe1c99a3b | |||
b2c5dc204a | |||
2ad68e3702 | |||
838fc7e098 | |||
87c5f6e455 | |||
e7bdd08a04 | |||
d1a8992590 | |||
b3721a24fa | |||
4ddc35cdad | |||
544e7bcb5e | |||
78be67f0c6 | |||
c79c43d2f8 | |||
ff41cf91ef | |||
cc8f2b6419 | |||
6e2c41a5b5 | |||
bf799ac3ba | |||
02a920395c | |||
1d418030e1 | |||
ef05d1419d | |||
efe25e3f58 | |||
8237d15683 | |||
fdf7f28d07 | |||
ec528c0626 | |||
63335e99ae | |||
2046912160 | |||
c810f5e3da | |||
dcf2e8ce9a | |||
f8c1e03ea7 | |||
f56070cbcd | |||
b267a9c1ba | |||
cf321ab510 | |||
a29efe28f7 | |||
02551c416c | |||
6795ad7e33 | |||
931f522616 | |||
6e37ad0275 | |||
127c4a9e63 | |||
992359a191 | |||
a5d03b1d6e | |||
1cb5221f01 | |||
c7e0d4b1e5 | |||
f6faf73e02 | |||
687fbc49c8 | |||
c950269575 | |||
9cf2e873b5 | |||
b6c7656194 | |||
e2907e7e3a | |||
64bab4b6a6 | |||
8abc7e6d5e | |||
3c4ae7b1a6 | |||
d3f22588f0 | |||
bb1fe958b4 | |||
ad2fd520ca | |||
be841d88d7 | |||
0ff36dfe42 | |||
8725bd3112 | |||
32529433fb | |||
a404af8ce9 | |||
96b4b91b2d | |||
e1cfc96ee8 | |||
cd71372ea9 | |||
b95cdd9705 | |||
bb265c5310 | |||
ad4ca61860 | |||
13cbfd3cee | |||
390a7e3f0b | |||
37a9f21b2a | |||
73f3c0b60b | |||
8a250d2e08 | |||
f3a4f10b46 | |||
26786a759e | |||
77379d7b3d | |||
f6853fd636 | |||
afce380530 | |||
5596190377 | |||
27edef4874 | |||
3a983bb5db | |||
f695ba408a | |||
067ceedf79 | |||
a7b281292d | |||
1d14d29408 | |||
bc19be25b1 | |||
430fb1fcb6 | |||
c6d4e4f890 | |||
d05f94e5fc | |||
5e937ca1af | |||
d8f13b36b1 | |||
c90640411d | |||
af98b0219d | |||
9e5f4c3b82 | |||
71ffd04ae7 | |||
14d1c67863 | |||
a9ddc58f21 | |||
8822750048 | |||
bff7124393 | |||
65af572761 | |||
229e8c5fd7 | |||
b8181c5cae | |||
89b3fb92aa | |||
b2b4562376 | |||
979a97c455 | |||
93188b3eda | |||
ce116b5d5f | |||
48fca1c151 | |||
dfe072fd30 | |||
87fa86c60e | |||
f08145a23a | |||
7cb13ee734 | |||
a18de999c2 | |||
3740b50eab | |||
9c00757a5e | |||
85fb3e1ff3 | |||
2ee3538de4 | |||
fe2761c7a6 | |||
e5f086cfb4 | |||
3016d7a64c | |||
9a07b41c9d | |||
565c6409d9 | |||
27793e7452 | |||
7066cc5004 | |||
71aacf5032 | |||
3ee2fc60f9 | |||
669659f974 | |||
4cda183103 | |||
9a9fdd7a35 | |||
626d597527 | |||
8c112c9efd | |||
98525043ed | |||
872aa78373 | |||
8948c350d4 | |||
9ff92c6878 | |||
38a42905ae | |||
39cf43ef06 | |||
262914cf92 | |||
4c4609d646 | |||
65e5abaa3e | |||
f24877ba08 | |||
ab08328a30 | |||
345edbbe10 | |||
e69a02d379 | |||
0126620c19 | |||
387328fe73 | |||
eaedb30a8c | |||
c6cb406a53 | |||
d3895d71db | |||
7b95e37bbe | |||
bf425874b8 | |||
f6e248f343 | |||
ecaed7f0ae | |||
0aae485395 | |||
523d57b2d8 | |||
2a721bad52 | |||
f4d9ddd3ad | |||
daeb56fe90 | |||
43687207b4 | |||
c0ff0f12f0 | |||
2697ea9a25 | |||
55b67e17bb | |||
123547444c | |||
88f1f386bb | |||
461f69ac5d | |||
e09b4817e1 | |||
96744e3155 | |||
4372d00ea9 | |||
7884de1941 | |||
098527b263 | |||
995989dad4 | |||
67a63162b2 | |||
6be91a68f3 | |||
f7d647ac3c | |||
a2a1c1656f | |||
99ba365c4a | |||
28f58057b6 | |||
f17f857b1f | |||
cf68334fa0 | |||
6ff3a4180b | |||
1058707a29 | |||
6e590fe0a2 | |||
b23fe30530 | |||
123bf2d736 | |||
7c1eb6b0c8 | |||
63ccc62a24 | |||
c0bac5a440 | |||
68a3d7c430 | |||
d11080e9ab | |||
a4ef7c1ac4 | |||
6a1691f378 | |||
a71abdaf15 | |||
1350f1eff7 | |||
752d25b004 | |||
68fcd71898 | |||
fb4251aba7 | |||
28f0f32ae7 | |||
06c590d894 | |||
0487e9ffcb | |||
1c49ca503a | |||
360ebeb0bc | |||
7f1e025cc9 | |||
1b220815b9 | |||
671bd08bcd | |||
bce2627e45 | |||
903afda6d9 | |||
317653d5d2 | |||
a20b24a712 | |||
74d62581b9 | |||
c68324762d | |||
525acf9d9e | |||
cb67de675e | |||
fd7eef1499 | |||
a603b067e5 | |||
b0600449e5 | |||
4073783d6e | |||
779a3c075e | |||
d77eeeb5dd | |||
7a181960b7 | |||
1bf016bae3 | |||
da4c918392 | |||
db4b3a561d | |||
d74c59eace | |||
06b0bea77f | |||
3ec0655577 | |||
005b8b02b2 | |||
5042f19d1b | |||
bacc1f6317 | |||
5536c9c7bc | |||
58c6fea60b | |||
e7f1bf8535 | |||
84517138bc | |||
fb7f6fc08b | |||
3633772d52 | |||
4de9a3eb34 | |||
b2092df27e | |||
09f513bb53 | |||
857c522808 | |||
9fa2b77611 | |||
f8a8eca836 | |||
20aa59085b | |||
4b91ed57dd | |||
366348dea0 | |||
b6b36e00c6 | |||
c79432f33c | |||
08931e976e | |||
c2992d5d8b | |||
342907c04d | |||
f5f21aca2d | |||
9342ad6ae1 | |||
cb4ca157fd | |||
d1c807230b | |||
4e5d3db952 | |||
b8d37a7541 | |||
c7a8aac883 | |||
0d518bf813 | |||
e083b75aef | |||
2e5a857983 | |||
0b6f15b69e | |||
16f3d9b4e1 | |||
3e0fa8ff85 | |||
d40019a141 | |||
d9b324c5d3 | |||
00176c5666 | |||
f16ac886a8 | |||
c77ec0b667 | |||
6779b248e4 | |||
0a355db5c0 | |||
cf9813cbf8 | |||
c371d1a535 | |||
4e0a65c822 | |||
6530403ff8 | |||
c08f46f836 | |||
175dab4898 | |||
798ae7b251 | |||
f879c00f9d | |||
d03ad6a257 | |||
14e4d05a9f | |||
86dd045554 | |||
0e023eaa84 | |||
427857a78e | |||
eea3f79c3c | |||
b004e80f77 | |||
25b62c2ac3 | |||
859f7b3dc7 | |||
39b020037d | |||
1334de72b0 | |||
56acebb826 | |||
d646903161 | |||
e43d893ea3 | |||
f03d81b0c8 | |||
7f1fd7699e | |||
a3f1116ea8 | |||
ef1d70eb67 | |||
cffce7f4b2 | |||
a4809d2f08 | |||
f286286510 | |||
2a65d43c13 | |||
0aabe84460 | |||
323207ca1d | |||
e35376f1e7 | |||
7e37e4bb5f | |||
a44ad949f1 | |||
4105255a5a | |||
ff5815c0a3 | |||
3af4f34f11 | |||
092d496ff5 | |||
415ebf207f | |||
aaac273cd0 | |||
90d65bb987 | |||
4c5a8c1804 | |||
188aca8fe6 | |||
c59d6d31bc | |||
64f34e0287 | |||
6edf91dcae | |||
bc872a1a2a | |||
ea1bd9f8f9 | |||
4458aae3d4 | |||
e7a4af14cd | |||
90095c72f6 | |||
f12f590d82 | |||
c8f30fa3bf | |||
ff290a5c3d | |||
56067da39c | |||
5d63f47c85 | |||
ee6547dbb7 | |||
19e76332fa | |||
55bf4d847f | |||
a4199ea312 | |||
afb7e1cf66 | |||
61d5aed0a2 | |||
41a7c351cb | |||
7ac3e97bfe | |||
924986576d | |||
7071617f18 | |||
e72a4116ec | |||
a109283118 | |||
42b03917fb | |||
12a07052f9 | |||
0cd927d634 | |||
e4c2c123ab | |||
d25be66929 | |||
6eb6086823 | |||
e88a531945 | |||
a093e66822 | |||
387c5462e9 | |||
724818030d | |||
8cad12a05c | |||
41119d3f88 | |||
0ebbc8f71c | |||
bd07f7b302 | |||
1867bb1a88 | |||
62272975f2 | |||
7bb9ee55c4 | |||
2c1560e281 | |||
d5206cbd68 |
@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
name: standard library bug or feature report
|
|
||||||
about: Used to submit issues related to the nu standard library
|
|
||||||
title: ''
|
|
||||||
labels: ['needs-triage', 'std-library']
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug or feature**
|
|
||||||
A clear and concise description of what the bug is.
|
|
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
@ -11,6 +11,10 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
|
# We release on Tuesdays and open dependabot PRs will rebase after the
|
||||||
|
# version bump and thus consume unnecessary workers during release, thus
|
||||||
|
# let's open new ones on Wednesday
|
||||||
|
day: "wednesday"
|
||||||
ignore:
|
ignore:
|
||||||
- dependency-name: "*"
|
- dependency-name: "*"
|
||||||
update-types: ["version-update:semver-patch"]
|
update-types: ["version-update:semver-patch"]
|
||||||
@ -18,3 +22,4 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
|
day: "wednesday"
|
||||||
|
2
.github/workflows/audit.yml
vendored
2
.github/workflows/audit.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
|||||||
# Prevent sudden announcement of a new advisory from failing ci:
|
# Prevent sudden announcement of a new advisory from failing ci:
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4.1.3
|
||||||
- uses: rustsec/audit-check@v1.4.1
|
- uses: rustsec/audit-check@v1.4.1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
12
.github/workflows/check-msrv.nu
vendored
Normal file
12
.github/workflows/check-msrv.nu
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
let toolchain_spec = open rust-toolchain.toml | get toolchain.channel
|
||||||
|
let msrv_spec = open Cargo.toml | get package.rust-version
|
||||||
|
|
||||||
|
# This check is conservative in the sense that we use `rust-toolchain.toml`'s
|
||||||
|
# override to ensure that this is the upper-bound for the minimum supported
|
||||||
|
# rust version
|
||||||
|
if $toolchain_spec != $msrv_spec {
|
||||||
|
print -e "Mismatching rust compiler versions specified in `Cargo.toml` and `rust-toolchain.toml`"
|
||||||
|
print -e $"Cargo.toml: ($msrv_spec)"
|
||||||
|
print -e $"rust-toolchain.toml: ($toolchain_spec)"
|
||||||
|
exit 1
|
||||||
|
}
|
64
.github/workflows/ci.yml
vendored
64
.github/workflows/ci.yml
vendored
@ -10,7 +10,11 @@ env:
|
|||||||
NUSHELL_CARGO_PROFILE: ci
|
NUSHELL_CARGO_PROFILE: ci
|
||||||
NU_LOG_LEVEL: DEBUG
|
NU_LOG_LEVEL: DEBUG
|
||||||
# If changing these settings also change toolkit.nu
|
# If changing these settings also change toolkit.nu
|
||||||
CLIPPY_OPTIONS: "-D warnings -D clippy::unwrap_used"
|
CLIPPY_OPTIONS: "-D warnings -D clippy::unwrap_used -D clippy::unchecked_duration_subtraction"
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
fmt-clippy:
|
fmt-clippy:
|
||||||
@ -20,28 +24,30 @@ jobs:
|
|||||||
# Pinning to Ubuntu 20.04 because building on newer Ubuntu versions causes linux-gnu
|
# Pinning to Ubuntu 20.04 because building on newer Ubuntu versions causes linux-gnu
|
||||||
# builds to link against a too-new-for-many-Linux-installs glibc version. Consider
|
# builds to link against a too-new-for-many-Linux-installs glibc version. Consider
|
||||||
# revisiting this when 20.04 is closer to EOL (April 2025)
|
# revisiting this when 20.04 is closer to EOL (April 2025)
|
||||||
platform: [windows-latest, macos-latest, ubuntu-20.04]
|
#
|
||||||
feature: [default, dataframe, extra]
|
# Using macOS 13 runner because 14 is based on the M1 and has half as much RAM (7 GB,
|
||||||
|
# instead of 14 GB) which is too little for us right now. Revisit when `dfr` commands are
|
||||||
|
# removed and we're only building the `polars` plugin instead
|
||||||
|
platform: [windows-latest, macos-13, ubuntu-20.04]
|
||||||
|
feature: [default, dataframe]
|
||||||
include:
|
include:
|
||||||
- feature: default
|
- feature: default
|
||||||
flags: ""
|
flags: ""
|
||||||
- feature: dataframe
|
- feature: dataframe
|
||||||
flags: "--features=dataframe"
|
flags: "--features=dataframe"
|
||||||
- feature: extra
|
|
||||||
flags: "--features=extra"
|
|
||||||
exclude:
|
exclude:
|
||||||
- platform: windows-latest
|
- platform: windows-latest
|
||||||
feature: dataframe
|
feature: dataframe
|
||||||
- platform: macos-latest
|
- platform: macos-13
|
||||||
feature: dataframe
|
feature: dataframe
|
||||||
|
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4.1.3
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||||
with:
|
with:
|
||||||
rustflags: ""
|
rustflags: ""
|
||||||
|
|
||||||
@ -56,41 +62,42 @@ jobs:
|
|||||||
- name: Clippy of tests
|
- name: Clippy of tests
|
||||||
run: cargo clippy --tests --workspace ${{ matrix.flags }} --exclude nu_plugin_* -- -D warnings
|
run: cargo clippy --tests --workspace ${{ matrix.flags }} --exclude nu_plugin_* -- -D warnings
|
||||||
|
|
||||||
|
- name: Clippy of benchmarks
|
||||||
|
run: cargo clippy --benches --workspace ${{ matrix.flags }} --exclude nu_plugin_* -- -D warnings
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
platform: [windows-latest, macos-latest, ubuntu-20.04]
|
platform: [windows-latest, macos-latest, ubuntu-20.04]
|
||||||
feature: [default, dataframe, extra]
|
feature: [default, dataframe]
|
||||||
include:
|
include:
|
||||||
|
# linux CI cannot handle clipboard feature
|
||||||
|
- default-flags: ""
|
||||||
|
- platform: ubuntu-20.04
|
||||||
|
default-flags: "--no-default-features --features=default-no-clipboard"
|
||||||
- feature: default
|
- feature: default
|
||||||
flags: ""
|
flags: ""
|
||||||
- feature: dataframe
|
- feature: dataframe
|
||||||
flags: "--features=dataframe"
|
flags: "--features=dataframe"
|
||||||
- feature: extra
|
|
||||||
flags: "--features=extra"
|
|
||||||
exclude:
|
exclude:
|
||||||
- platform: windows-latest
|
- platform: windows-latest
|
||||||
feature: dataframe
|
feature: dataframe
|
||||||
- platform: macos-latest
|
- platform: macos-latest
|
||||||
feature: dataframe
|
feature: dataframe
|
||||||
- platform: windows-latest
|
|
||||||
feature: extra
|
|
||||||
- platform: macos-latest
|
|
||||||
feature: extra
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4.1.3
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||||
with:
|
with:
|
||||||
rustflags: ""
|
rustflags: ""
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
|
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.default-flags }} ${{ matrix.flags }}
|
||||||
|
|
||||||
- name: Check for clean repo
|
- name: Check for clean repo
|
||||||
shell: bash
|
shell: bash
|
||||||
@ -114,10 +121,10 @@ jobs:
|
|||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4.1.3
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||||
with:
|
with:
|
||||||
rustflags: ""
|
rustflags: ""
|
||||||
|
|
||||||
@ -125,7 +132,10 @@ jobs:
|
|||||||
run: cargo install --path . --locked --no-default-features
|
run: cargo install --path . --locked --no-default-features
|
||||||
|
|
||||||
- name: Standard library tests
|
- name: Standard library tests
|
||||||
run: nu -c 'use std testing; testing run-tests --path crates/nu-std'
|
run: nu -c 'use crates/nu-std/testing.nu; testing run-tests --path crates/nu-std'
|
||||||
|
|
||||||
|
- name: Ensure that Cargo.toml MSRV and rust-toolchain.toml use the same version
|
||||||
|
run: nu .github/workflows/check-msrv.nu
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
@ -155,20 +165,24 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
platform: [windows-latest, macos-latest, ubuntu-20.04]
|
# Using macOS 13 runner because 14 is based on the M1 and has half as much RAM (7 GB,
|
||||||
|
# instead of 14 GB) which is too little for us right now.
|
||||||
|
#
|
||||||
|
# Failure occuring with clippy for rust 1.77.2
|
||||||
|
platform: [windows-latest, macos-13, ubuntu-20.04]
|
||||||
|
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4.1.3
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||||
with:
|
with:
|
||||||
rustflags: ""
|
rustflags: ""
|
||||||
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy --package nu_plugin_* ${{ matrix.flags }} -- $CLIPPY_OPTIONS
|
run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
run: cargo test --profile ci --package nu_plugin_*
|
run: cargo test --profile ci --package nu_plugin_*
|
||||||
|
62
.github/workflows/nightly-build.yml
vendored
62
.github/workflows/nightly-build.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
|||||||
# if: github.repository == 'nushell/nightly'
|
# if: github.repository == 'nushell/nightly'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4.1.3
|
||||||
if: github.repository == 'nushell/nightly'
|
if: github.repository == 'nushell/nightly'
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
@ -36,12 +36,10 @@ jobs:
|
|||||||
token: ${{ secrets.WORKFLOW_TOKEN }}
|
token: ${{ secrets.WORKFLOW_TOKEN }}
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.8
|
uses: hustcer/setup-nu@v3.10
|
||||||
if: github.repository == 'nushell/nightly'
|
if: github.repository == 'nushell/nightly'
|
||||||
with:
|
with:
|
||||||
version: 0.86.0
|
version: 0.91.0
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
|
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
|
||||||
- name: Prepare for Nightly Release
|
- name: Prepare for Nightly Release
|
||||||
@ -125,7 +123,7 @@ jobs:
|
|||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4.1.3
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@ -135,17 +133,15 @@ jobs:
|
|||||||
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||||
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||||
with:
|
with:
|
||||||
rustflags: ''
|
rustflags: ''
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.8
|
uses: hustcer/setup-nu@v3.10
|
||||||
with:
|
with:
|
||||||
version: 0.86.0
|
version: 0.91.0
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
@ -160,7 +156,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create an Issue for Release Failure
|
- name: Create an Issue for Release Failure
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
uses: JasonEtco/create-an-issue@v2.9.1
|
uses: JasonEtco/create-an-issue@v2.9.2
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
@ -178,7 +174,7 @@ jobs:
|
|||||||
# REF: https://github.com/marketplace/actions/gh-release
|
# REF: https://github.com/marketplace/actions/gh-release
|
||||||
# Create a release only in nushell/nightly repo
|
# Create a release only in nushell/nightly repo
|
||||||
- name: Publish Archive
|
- name: Publish Archive
|
||||||
uses: softprops/action-gh-release@v0.1.15
|
uses: softprops/action-gh-release@v2.0.4
|
||||||
if: ${{ startsWith(github.repository, 'nushell/nightly') }}
|
if: ${{ startsWith(github.repository, 'nushell/nightly') }}
|
||||||
with:
|
with:
|
||||||
prerelease: true
|
prerelease: true
|
||||||
@ -206,40 +202,40 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- target: aarch64-apple-darwin
|
- target: aarch64-apple-darwin
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
target_rustflags: '--features=dataframe,extra'
|
target_rustflags: '--features=dataframe'
|
||||||
- target: x86_64-apple-darwin
|
- target: x86_64-apple-darwin
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
target_rustflags: '--features=dataframe,extra'
|
target_rustflags: '--features=dataframe'
|
||||||
- target: x86_64-pc-windows-msvc
|
- target: x86_64-pc-windows-msvc
|
||||||
extra: 'bin'
|
extra: 'bin'
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
target_rustflags: '--features=dataframe,extra'
|
target_rustflags: '--features=dataframe'
|
||||||
- target: x86_64-pc-windows-msvc
|
- target: x86_64-pc-windows-msvc
|
||||||
extra: msi
|
extra: msi
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
target_rustflags: '--features=dataframe,extra'
|
target_rustflags: '--features=dataframe'
|
||||||
- target: aarch64-pc-windows-msvc
|
- target: aarch64-pc-windows-msvc
|
||||||
extra: 'bin'
|
extra: 'bin'
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
target_rustflags: '--features=dataframe,extra'
|
target_rustflags: '--features=dataframe'
|
||||||
- target: aarch64-pc-windows-msvc
|
- target: aarch64-pc-windows-msvc
|
||||||
extra: msi
|
extra: msi
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
target_rustflags: '--features=dataframe,extra'
|
target_rustflags: '--features=dataframe'
|
||||||
- target: x86_64-unknown-linux-gnu
|
- target: x86_64-unknown-linux-gnu
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
target_rustflags: '--features=dataframe,extra'
|
target_rustflags: '--features=dataframe'
|
||||||
- target: x86_64-unknown-linux-musl
|
- target: x86_64-unknown-linux-musl
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
target_rustflags: '--features=dataframe,extra'
|
target_rustflags: '--features=dataframe'
|
||||||
- target: aarch64-unknown-linux-gnu
|
- target: aarch64-unknown-linux-gnu
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
target_rustflags: '--features=dataframe,extra'
|
target_rustflags: '--features=dataframe'
|
||||||
|
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4.1.3
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@ -249,17 +245,15 @@ jobs:
|
|||||||
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||||
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||||
with:
|
with:
|
||||||
rustflags: ''
|
rustflags: ''
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.8
|
uses: hustcer/setup-nu@v3.10
|
||||||
with:
|
with:
|
||||||
version: 0.86.0
|
version: 0.91.0
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
@ -274,7 +268,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create an Issue for Release Failure
|
- name: Create an Issue for Release Failure
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
uses: JasonEtco/create-an-issue@v2.9.1
|
uses: JasonEtco/create-an-issue@v2.9.2
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
@ -292,7 +286,7 @@ jobs:
|
|||||||
# REF: https://github.com/marketplace/actions/gh-release
|
# REF: https://github.com/marketplace/actions/gh-release
|
||||||
# Create a release only in nushell/nightly repo
|
# Create a release only in nushell/nightly repo
|
||||||
- name: Publish Archive
|
- name: Publish Archive
|
||||||
uses: softprops/action-gh-release@v0.1.15
|
uses: softprops/action-gh-release@v2.0.4
|
||||||
if: ${{ startsWith(github.repository, 'nushell/nightly') }}
|
if: ${{ startsWith(github.repository, 'nushell/nightly') }}
|
||||||
with:
|
with:
|
||||||
draft: false
|
draft: false
|
||||||
@ -316,16 +310,14 @@ jobs:
|
|||||||
- name: Waiting for Release
|
- name: Waiting for Release
|
||||||
run: sleep 1800
|
run: sleep 1800
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4.1.3
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.8
|
uses: hustcer/setup-nu@v3.10
|
||||||
with:
|
with:
|
||||||
version: 0.86.0
|
version: 0.91.0
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
# Keep the last a few releases
|
# Keep the last a few releases
|
||||||
- name: Delete Older Releases
|
- name: Delete Older Releases
|
||||||
|
39
.github/workflows/release-pkg.nu
vendored
39
.github/workflows/release-pkg.nu
vendored
@ -71,7 +71,7 @@ const FULL_RLS_NAMING = {
|
|||||||
|
|
||||||
# $env
|
# $env
|
||||||
|
|
||||||
let USE_UBUNTU = 'ubuntu-20.04'
|
let USE_UBUNTU = $os starts-with ubuntu
|
||||||
let FULL_NAME = $FULL_RLS_NAMING | get -i $target | default 'unknown-target-full'
|
let FULL_NAME = $FULL_RLS_NAMING | get -i $target | default 'unknown-target-full'
|
||||||
|
|
||||||
print $'(char nl)Packaging ($bin) v($version) for ($target) in ($src)...'; hr-line -b
|
print $'(char nl)Packaging ($bin) v($version) for ($target) in ($src)...'; hr-line -b
|
||||||
@ -82,8 +82,8 @@ print $'Start building ($bin)...'; hr-line
|
|||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
# Build for Ubuntu and macOS
|
# Build for Ubuntu and macOS
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
if $os in [$USE_UBUNTU, 'macos-latest', 'ubuntu-latest'] {
|
if $os in ['macos-latest'] or $USE_UBUNTU {
|
||||||
if $os starts-with ubuntu {
|
if $USE_UBUNTU {
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt-get install libxcb-composite0-dev -y
|
sudo apt-get install libxcb-composite0-dev -y
|
||||||
}
|
}
|
||||||
@ -106,7 +106,7 @@ if $os in [$USE_UBUNTU, 'macos-latest', 'ubuntu-latest'] {
|
|||||||
_ => {
|
_ => {
|
||||||
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
||||||
# Actually just for x86_64-unknown-linux-musl target
|
# Actually just for x86_64-unknown-linux-musl target
|
||||||
if $os starts-with ubuntu { sudo apt install musl-tools -y }
|
if $USE_UBUNTU { sudo apt install musl-tools -y }
|
||||||
cargo-build-nu $flags
|
cargo-build-nu $flags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,16 +128,22 @@ let executable = $'target/($target)/release/($bin)*($suffix)'
|
|||||||
print $'Current executable file: ($executable)'
|
print $'Current executable file: ($executable)'
|
||||||
|
|
||||||
cd $src; mkdir $dist;
|
cd $src; mkdir $dist;
|
||||||
rm -rf $'target/($target)/release/*.d' $'target/($target)/release/nu_pretty_hex*'
|
rm -rf ...(glob $'target/($target)/release/*.d') ...(glob $'target/($target)/release/nu_pretty_hex*')
|
||||||
print $'(char nl)All executable files:'; hr-line
|
print $'(char nl)All executable files:'; hr-line
|
||||||
# We have to use `print` here to make sure the command output is displayed
|
# We have to use `print` here to make sure the command output is displayed
|
||||||
print (ls -f $executable); sleep 1sec
|
print (ls -f ($executable | into glob)); sleep 1sec
|
||||||
|
|
||||||
print $'(char nl)Copying release files...'; hr-line
|
print $'(char nl)Copying release files...'; hr-line
|
||||||
"To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
|
"To use the included Nushell plugins, register the binaries with the `plugin add` command to tell Nu where to find the plugin.
|
||||||
|
Then you can use `plugin use` to load the plugin into your session.
|
||||||
|
For example:
|
||||||
|
|
||||||
> register ./nu_plugin_query" | save $'($dist)/README.txt' -f
|
> plugin add ./nu_plugin_query
|
||||||
[LICENSE $executable] | each {|it| cp -rv $it $dist } | flatten
|
> plugin use query
|
||||||
|
|
||||||
|
For more information, refer to https://www.nushell.sh/book/plugins.html
|
||||||
|
" | save $'($dist)/README.txt' -f
|
||||||
|
[LICENSE ...(glob $executable)] | each {|it| cp -rv $it $dist } | flatten
|
||||||
|
|
||||||
print $'(char nl)Check binary release version detail:'; hr-line
|
print $'(char nl)Check binary release version detail:'; hr-line
|
||||||
let ver = if $os == 'windows-latest' {
|
let ver = if $os == 'windows-latest' {
|
||||||
@ -153,16 +159,16 @@ if ($ver | str trim | is-empty) {
|
|||||||
# Create a release archive and send it to output for the following steps
|
# Create a release archive and send it to output for the following steps
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
cd $dist; print $'(char nl)Creating release archive...'; hr-line
|
cd $dist; print $'(char nl)Creating release archive...'; hr-line
|
||||||
if $os in [$USE_UBUNTU, 'macos-latest', 'ubuntu-latest'] {
|
if $os in ['macos-latest'] or $USE_UBUNTU {
|
||||||
|
|
||||||
let files = (ls | get name)
|
let files = (ls | get name)
|
||||||
let dest = if $env.RELEASE_TYPE == 'full' { $'($bin)-($version)-($FULL_NAME)' } else { $'($bin)-($version)-($target)' }
|
let dest = if $env.RELEASE_TYPE == 'full' { $'($bin)-($version)-($FULL_NAME)' } else { $'($bin)-($version)-($target)' }
|
||||||
let archive = $'($dist)/($dest).tar.gz'
|
let archive = $'($dist)/($dest).tar.gz'
|
||||||
|
|
||||||
mkdir $dest
|
mkdir $dest
|
||||||
$files | each {|it| mv $it $dest } | ignore
|
$files | each {|it| cp -v $it $dest }
|
||||||
|
|
||||||
print $'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls $dest
|
print $'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls $dest | print
|
||||||
|
|
||||||
tar -czf $archive $dest
|
tar -czf $archive $dest
|
||||||
print $'archive: ---> ($archive)'; ls $archive
|
print $'archive: ---> ($archive)'; ls $archive
|
||||||
@ -181,10 +187,11 @@ if $os in [$USE_UBUNTU, 'macos-latest', 'ubuntu-latest'] {
|
|||||||
if (get-env _EXTRA_) == 'msi' {
|
if (get-env _EXTRA_) == 'msi' {
|
||||||
|
|
||||||
let wixRelease = $'($src)/target/wix/($releaseStem).msi'
|
let wixRelease = $'($src)/target/wix/($releaseStem).msi'
|
||||||
print $'(char nl)Start creating Windows msi package...'
|
print $'(char nl)Start creating Windows msi package with the following contents...'
|
||||||
cd $src; hr-line
|
cd $src; hr-line
|
||||||
# Wix need the binaries be stored in target/release/
|
# Wix need the binaries be stored in target/release/
|
||||||
cp -r $'($dist)/*' target/release/
|
cp -r ($'($dist)/*' | into glob) target/release/
|
||||||
|
ls target/release/* | print
|
||||||
cargo install cargo-wix --version 0.3.4
|
cargo install cargo-wix --version 0.3.4
|
||||||
cargo wix --no-build --nocapture --package nu --output $wixRelease
|
cargo wix --no-build --nocapture --package nu --output $wixRelease
|
||||||
# Workaround for https://github.com/softprops/action-gh-release/issues/280
|
# Workaround for https://github.com/softprops/action-gh-release/issues/280
|
||||||
@ -194,9 +201,9 @@ if $os in [$USE_UBUNTU, 'macos-latest', 'ubuntu-latest'] {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
print $'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls
|
print $'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls | print
|
||||||
let archive = $'($dist)/($releaseStem).zip'
|
let archive = $'($dist)/($releaseStem).zip'
|
||||||
7z a $archive *
|
7z a $archive ...(glob *)
|
||||||
let pkg = (ls -f $archive | get name)
|
let pkg = (ls -f $archive | get name)
|
||||||
if not ($pkg | is-empty) {
|
if not ($pkg | is-empty) {
|
||||||
# Workaround for https://github.com/softprops/action-gh-release/issues/280
|
# Workaround for https://github.com/softprops/action-gh-release/issues/280
|
||||||
|
49
.github/workflows/release.yml
vendored
49
.github/workflows/release.yml
vendored
@ -18,6 +18,7 @@ jobs:
|
|||||||
name: Std
|
name: Std
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
target:
|
target:
|
||||||
- aarch64-apple-darwin
|
- aarch64-apple-darwin
|
||||||
@ -72,24 +73,23 @@ jobs:
|
|||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4.1.3
|
||||||
|
|
||||||
- name: Update Rust Toolchain Target
|
- name: Update Rust Toolchain Target
|
||||||
run: |
|
run: |
|
||||||
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||||
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||||
with:
|
with:
|
||||||
|
cache: false
|
||||||
rustflags: ''
|
rustflags: ''
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.8
|
uses: hustcer/setup-nu@v3.10
|
||||||
with:
|
with:
|
||||||
version: 0.86.0
|
version: 0.91.0
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
@ -104,7 +104,7 @@ jobs:
|
|||||||
|
|
||||||
# REF: https://github.com/marketplace/actions/gh-release
|
# REF: https://github.com/marketplace/actions/gh-release
|
||||||
- name: Publish Archive
|
- name: Publish Archive
|
||||||
uses: softprops/action-gh-release@v0.1.15
|
uses: softprops/action-gh-release@v2.0.4
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
@ -130,57 +130,56 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- target: aarch64-apple-darwin
|
- target: aarch64-apple-darwin
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
target_rustflags: '--features=dataframe,extra'
|
target_rustflags: '--features=dataframe'
|
||||||
- target: x86_64-apple-darwin
|
- target: x86_64-apple-darwin
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
target_rustflags: '--features=dataframe,extra'
|
target_rustflags: '--features=dataframe'
|
||||||
- target: x86_64-pc-windows-msvc
|
- target: x86_64-pc-windows-msvc
|
||||||
extra: 'bin'
|
extra: 'bin'
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
target_rustflags: '--features=dataframe,extra'
|
target_rustflags: '--features=dataframe'
|
||||||
- target: x86_64-pc-windows-msvc
|
- target: x86_64-pc-windows-msvc
|
||||||
extra: msi
|
extra: msi
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
target_rustflags: '--features=dataframe,extra'
|
target_rustflags: '--features=dataframe'
|
||||||
- target: aarch64-pc-windows-msvc
|
- target: aarch64-pc-windows-msvc
|
||||||
extra: 'bin'
|
extra: 'bin'
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
target_rustflags: '--features=dataframe,extra'
|
target_rustflags: '--features=dataframe'
|
||||||
- target: aarch64-pc-windows-msvc
|
- target: aarch64-pc-windows-msvc
|
||||||
extra: msi
|
extra: msi
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
target_rustflags: '--features=dataframe,extra'
|
target_rustflags: '--features=dataframe'
|
||||||
- target: x86_64-unknown-linux-gnu
|
- target: x86_64-unknown-linux-gnu
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
target_rustflags: '--features=dataframe,extra'
|
target_rustflags: '--features=dataframe'
|
||||||
- target: x86_64-unknown-linux-musl
|
- target: x86_64-unknown-linux-musl
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
target_rustflags: '--features=dataframe,extra'
|
target_rustflags: '--features=dataframe'
|
||||||
- target: aarch64-unknown-linux-gnu
|
- target: aarch64-unknown-linux-gnu
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
target_rustflags: '--features=dataframe,extra'
|
target_rustflags: '--features=dataframe'
|
||||||
|
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4.1.3
|
||||||
|
|
||||||
- name: Update Rust Toolchain Target
|
- name: Update Rust Toolchain Target
|
||||||
run: |
|
run: |
|
||||||
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||||
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||||
with:
|
with:
|
||||||
|
cache: false
|
||||||
rustflags: ''
|
rustflags: ''
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.8
|
uses: hustcer/setup-nu@v3.10
|
||||||
with:
|
with:
|
||||||
version: 0.86.0
|
version: 0.91.0
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
@ -195,7 +194,7 @@ jobs:
|
|||||||
|
|
||||||
# REF: https://github.com/marketplace/actions/gh-release
|
# REF: https://github.com/marketplace/actions/gh-release
|
||||||
- name: Publish Archive
|
- name: Publish Archive
|
||||||
uses: softprops/action-gh-release@v0.1.15
|
uses: softprops/action-gh-release@v2.0.4
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
|
6
.github/workflows/typos.yml
vendored
6
.github/workflows/typos.yml
vendored
@ -7,9 +7,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Actions Repository
|
- name: Checkout Actions Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4.1.3
|
||||||
|
|
||||||
- name: Check spelling
|
- name: Check spelling
|
||||||
uses: crate-ci/typos@v1.17.0
|
uses: crate-ci/typos@v1.20.10
|
||||||
with:
|
|
||||||
config: ./.github/.typos.toml
|
|
||||||
|
@ -16,8 +16,8 @@ Welcome to Nushell and thank you for considering contributing!
|
|||||||
|
|
||||||
More resources can be found in the nascent [developer documentation](devdocs/README.md) in this repo.
|
More resources can be found in the nascent [developer documentation](devdocs/README.md) in this repo.
|
||||||
|
|
||||||
- [Developer FAQ](FAQ.md)
|
- [Developer FAQ](devdocs/FAQ.md)
|
||||||
- [Platform support policy](PLATFORM_SUPPORT.md)
|
- [Platform support policy](devdocs/PLATFORM_SUPPORT.md)
|
||||||
- [Our Rust style](devdocs/rust_style.md)
|
- [Our Rust style](devdocs/rust_style.md)
|
||||||
|
|
||||||
## Proposing design changes
|
## Proposing design changes
|
||||||
|
2903
Cargo.lock
generated
2903
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
293
Cargo.toml
293
Cargo.toml
@ -10,8 +10,8 @@ homepage = "https://www.nushell.sh"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu"
|
name = "nu"
|
||||||
repository = "https://github.com/nushell/nushell"
|
repository = "https://github.com/nushell/nushell"
|
||||||
rust-version = "1.72.1"
|
rust-version = "1.77.2"
|
||||||
version = "0.88.2"
|
version = "0.93.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -24,106 +24,235 @@ pkg-fmt = "zip"
|
|||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"crates/nu-cli",
|
"crates/nu-cli",
|
||||||
"crates/nu-engine",
|
"crates/nu-engine",
|
||||||
"crates/nu-parser",
|
"crates/nu-parser",
|
||||||
"crates/nu-system",
|
"crates/nu-system",
|
||||||
"crates/nu-cmd-base",
|
"crates/nu-cmd-base",
|
||||||
"crates/nu-cmd-extra",
|
"crates/nu-cmd-extra",
|
||||||
"crates/nu-cmd-lang",
|
"crates/nu-cmd-lang",
|
||||||
"crates/nu-cmd-dataframe",
|
"crates/nu-cmd-plugin",
|
||||||
"crates/nu-command",
|
"crates/nu-cmd-dataframe",
|
||||||
"crates/nu-color-config",
|
"crates/nu-command",
|
||||||
"crates/nu-explore",
|
"crates/nu-color-config",
|
||||||
"crates/nu-json",
|
"crates/nu-explore",
|
||||||
"crates/nu-lsp",
|
"crates/nu-json",
|
||||||
"crates/nu-pretty-hex",
|
"crates/nu-lsp",
|
||||||
"crates/nu-protocol",
|
"crates/nu-pretty-hex",
|
||||||
"crates/nu-plugin",
|
"crates/nu-protocol",
|
||||||
"crates/nu_plugin_inc",
|
"crates/nu-plugin",
|
||||||
"crates/nu_plugin_gstat",
|
"crates/nu-plugin-core",
|
||||||
"crates/nu_plugin_example",
|
"crates/nu-plugin-engine",
|
||||||
"crates/nu_plugin_query",
|
"crates/nu-plugin-protocol",
|
||||||
"crates/nu_plugin_custom_values",
|
"crates/nu-plugin-test-support",
|
||||||
"crates/nu_plugin_formats",
|
"crates/nu_plugin_inc",
|
||||||
"crates/nu-std",
|
"crates/nu_plugin_gstat",
|
||||||
"crates/nu-table",
|
"crates/nu_plugin_example",
|
||||||
"crates/nu-term-grid",
|
"crates/nu_plugin_query",
|
||||||
"crates/nu-utils",
|
"crates/nu_plugin_custom_values",
|
||||||
|
"crates/nu_plugin_formats",
|
||||||
|
"crates/nu_plugin_polars",
|
||||||
|
"crates/nu_plugin_stress_internals",
|
||||||
|
"crates/nu-std",
|
||||||
|
"crates/nu-table",
|
||||||
|
"crates/nu-term-grid",
|
||||||
|
"crates/nu-test-support",
|
||||||
|
"crates/nu-utils",
|
||||||
|
"crates/nuon",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[workspace.dependencies]
|
||||||
nu-cli = { path = "./crates/nu-cli", version = "0.88.2" }
|
alphanumeric-sort = "1.5"
|
||||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.88.2" }
|
ansi-str = "0.8"
|
||||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.88.2" }
|
base64 = "0.22"
|
||||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.88.2" }
|
bracoxide = "0.1.2"
|
||||||
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.88.2", features = ["dataframe"], optional = true }
|
brotli = "5.0"
|
||||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.88.2", optional = true }
|
byteorder = "1.5"
|
||||||
nu-command = { path = "./crates/nu-command", version = "0.88.2" }
|
bytesize = "1.3"
|
||||||
nu-engine = { path = "./crates/nu-engine", version = "0.88.2" }
|
calamine = "0.24.0"
|
||||||
nu-explore = { path = "./crates/nu-explore", version = "0.88.2" }
|
chardetng = "0.1.17"
|
||||||
nu-json = { path = "./crates/nu-json", version = "0.88.2" }
|
chrono = { default-features = false, version = "0.4" }
|
||||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.88.2" }
|
chrono-humanize = "0.2.3"
|
||||||
nu-parser = { path = "./crates/nu-parser", version = "0.88.2" }
|
chrono-tz = "0.8"
|
||||||
nu-path = { path = "./crates/nu-path", version = "0.88.2" }
|
crossbeam-channel = "0.5.8"
|
||||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.88.2" }
|
|
||||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.88.2" }
|
|
||||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.88.2" }
|
|
||||||
nu-system = { path = "./crates/nu-system", version = "0.88.2" }
|
|
||||||
nu-table = { path = "./crates/nu-table", version = "0.88.2" }
|
|
||||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.88.2" }
|
|
||||||
nu-std = { path = "./crates/nu-std", version = "0.88.2" }
|
|
||||||
nu-utils = { path = "./crates/nu-utils", version = "0.88.2" }
|
|
||||||
|
|
||||||
nu-ansi-term = "0.49.0"
|
|
||||||
reedline = { version = "0.27.0", features = ["bashisms", "sqlite"] }
|
|
||||||
|
|
||||||
crossterm = "0.27"
|
crossterm = "0.27"
|
||||||
|
csv = "1.3"
|
||||||
ctrlc = "3.4"
|
ctrlc = "3.4"
|
||||||
|
dialoguer = { default-features = false, version = "0.11" }
|
||||||
|
digest = { default-features = false, version = "0.10" }
|
||||||
|
dirs-next = "2.0"
|
||||||
|
dtparse = "2.0"
|
||||||
|
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"
|
||||||
|
indicatif = "0.17"
|
||||||
|
interprocess = "1.2.1"
|
||||||
|
is_executable = "1.0"
|
||||||
|
itertools = "0.12"
|
||||||
|
libc = "0.2"
|
||||||
|
libproc = "0.14"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
miette = { version = "5.10", features = ["fancy-no-backtrace"] }
|
lru = "0.12"
|
||||||
mimalloc = { version = "0.1.37", default-features = false, optional = true }
|
lscolors = { version = "0.17", default-features = false }
|
||||||
|
lsp-server = "0.7.5"
|
||||||
|
lsp-types = "0.95.0"
|
||||||
|
mach2 = "0.4"
|
||||||
|
md5 = { version = "0.10", package = "md-5" }
|
||||||
|
miette = "7.2"
|
||||||
|
mime = "0.3"
|
||||||
|
mime_guess = "2.0"
|
||||||
|
mockito = { version = "1.4", default-features = false }
|
||||||
|
native-tls = "0.2"
|
||||||
|
nix = { version = "0.28", default-features = false }
|
||||||
|
notify-debouncer-full = { version = "0.3", default-features = false }
|
||||||
|
nu-ansi-term = "0.50.0"
|
||||||
|
num-format = "0.4"
|
||||||
|
num-traits = "0.2"
|
||||||
|
omnipath = "0.1"
|
||||||
|
once_cell = "1.18"
|
||||||
|
open = "5.1"
|
||||||
|
os_pipe = "1.1"
|
||||||
|
pathdiff = "0.2"
|
||||||
|
percent-encoding = "2"
|
||||||
|
pretty_assertions = "1.4"
|
||||||
|
print-positions = "0.6"
|
||||||
|
procfs = "0.16.0"
|
||||||
|
pwd = "1.3"
|
||||||
|
quick-xml = "0.31.0"
|
||||||
|
quickcheck = "1.0"
|
||||||
|
quickcheck_macros = "1.0"
|
||||||
|
rand = "0.8"
|
||||||
|
ratatui = "0.26"
|
||||||
|
rayon = "1.10"
|
||||||
|
reedline = "0.32.0"
|
||||||
|
regex = "1.9.5"
|
||||||
|
rmp = "0.8"
|
||||||
|
rmp-serde = "1.2"
|
||||||
|
ropey = "1.6.1"
|
||||||
|
roxmltree = "0.19"
|
||||||
|
rstest = { version = "0.18", default-features = false }
|
||||||
|
rusqlite = "0.31"
|
||||||
|
rust-embed = "8.3.0"
|
||||||
|
same-file = "1.0"
|
||||||
|
serde = { version = "1.0", default-features = false }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
serde_urlencoded = "0.7.1"
|
||||||
|
serde_yaml = "0.9"
|
||||||
|
sha2 = "0.10"
|
||||||
|
strip-ansi-escapes = "0.2.0"
|
||||||
|
sysinfo = "0.30"
|
||||||
|
tabled = { version = "0.14.0", default-features = false }
|
||||||
|
tempfile = "3.10"
|
||||||
|
terminal_size = "0.3"
|
||||||
|
titlecase = "2.0"
|
||||||
|
toml = "0.8"
|
||||||
|
trash = "3.3"
|
||||||
|
umask = "2.1"
|
||||||
|
unicode-segmentation = "1.11"
|
||||||
|
unicode-width = "0.1"
|
||||||
|
ureq = { version = "2.9", default-features = false }
|
||||||
|
url = "2.2"
|
||||||
|
uu_cp = "0.0.25"
|
||||||
|
uu_mkdir = "0.0.25"
|
||||||
|
uu_mktemp = "0.0.25"
|
||||||
|
uu_mv = "0.0.25"
|
||||||
|
uu_whoami = "0.0.25"
|
||||||
|
uu_uname = "0.0.25"
|
||||||
|
uucore = "0.0.25"
|
||||||
|
uuid = "1.8.0"
|
||||||
|
v_htmlescape = "0.15.0"
|
||||||
|
wax = "0.6"
|
||||||
|
which = "6.0.0"
|
||||||
|
windows = "0.54"
|
||||||
|
winreg = "0.52"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nu-cli = { path = "./crates/nu-cli", version = "0.93.0" }
|
||||||
|
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.93.0" }
|
||||||
|
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.93.0" }
|
||||||
|
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.93.0", optional = true }
|
||||||
|
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.93.0", features = [
|
||||||
|
"dataframe",
|
||||||
|
], optional = true }
|
||||||
|
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.93.0" }
|
||||||
|
nu-command = { path = "./crates/nu-command", version = "0.93.0" }
|
||||||
|
nu-engine = { path = "./crates/nu-engine", version = "0.93.0" }
|
||||||
|
nu-explore = { path = "./crates/nu-explore", version = "0.93.0" }
|
||||||
|
nu-lsp = { path = "./crates/nu-lsp/", version = "0.93.0" }
|
||||||
|
nu-parser = { path = "./crates/nu-parser", version = "0.93.0" }
|
||||||
|
nu-path = { path = "./crates/nu-path", version = "0.93.0" }
|
||||||
|
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.93.0" }
|
||||||
|
nu-protocol = { path = "./crates/nu-protocol", version = "0.93.0" }
|
||||||
|
nu-std = { path = "./crates/nu-std", version = "0.93.0" }
|
||||||
|
nu-system = { path = "./crates/nu-system", version = "0.93.0" }
|
||||||
|
nu-utils = { path = "./crates/nu-utils", version = "0.93.0" }
|
||||||
|
|
||||||
|
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
|
crossterm = { workspace = true }
|
||||||
|
ctrlc = { workspace = true }
|
||||||
|
log = { workspace = true }
|
||||||
|
miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] }
|
||||||
|
mimalloc = { version = "0.1.37", default-features = false, optional = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
simplelog = "0.12"
|
simplelog = "0.12"
|
||||||
time = "0.3"
|
time = "0.3"
|
||||||
|
|
||||||
[target.'cfg(not(target_os = "windows"))'.dependencies]
|
[target.'cfg(not(target_os = "windows"))'.dependencies]
|
||||||
# Our dependencies don't use OpenSSL on Windows
|
# Our dependencies don't use OpenSSL on Windows
|
||||||
openssl = { version = "0.10", features = ["vendored"], optional = true }
|
openssl = { version = "0.10", features = ["vendored"], optional = true }
|
||||||
signal-hook = { version = "0.3", default-features = false }
|
|
||||||
|
|
||||||
[target.'cfg(windows)'.build-dependencies]
|
[target.'cfg(windows)'.build-dependencies]
|
||||||
winresource = "0.1"
|
winresource = "0.1"
|
||||||
|
|
||||||
[target.'cfg(target_family = "unix")'.dependencies]
|
[target.'cfg(target_family = "unix")'.dependencies]
|
||||||
nix = { version = "0.27", default-features = false, features = [
|
nix = { workspace = true, default-features = false, features = [
|
||||||
"signal",
|
"signal",
|
||||||
"process",
|
"process",
|
||||||
"fs",
|
"fs",
|
||||||
"term",
|
"term",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.88.2" }
|
nu-test-support = { path = "./crates/nu-test-support", version = "0.93.0" }
|
||||||
|
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.93.0" }
|
||||||
|
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.93.0" }
|
||||||
assert_cmd = "2.0"
|
assert_cmd = "2.0"
|
||||||
criterion = "0.5"
|
dirs-next = { workspace = true }
|
||||||
pretty_assertions = "1.4"
|
divan = "0.1.14"
|
||||||
rstest = { version = "0.18", default-features = false }
|
pretty_assertions = { workspace = true }
|
||||||
serial_test = "2.0"
|
rstest = { workspace = true, default-features = false }
|
||||||
tempfile = "3.8"
|
serial_test = "3.1"
|
||||||
|
tempfile = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = [
|
plugin = [
|
||||||
"nu-plugin",
|
"nu-plugin-engine",
|
||||||
"nu-cli/plugin",
|
"nu-cmd-plugin",
|
||||||
"nu-parser/plugin",
|
"nu-cli/plugin",
|
||||||
"nu-command/plugin",
|
"nu-parser/plugin",
|
||||||
"nu-protocol/plugin",
|
"nu-command/plugin",
|
||||||
"nu-engine/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 = [
|
||||||
|
"plugin",
|
||||||
|
"which-support",
|
||||||
|
"trash-support",
|
||||||
|
"sqlite",
|
||||||
|
"mimalloc",
|
||||||
]
|
]
|
||||||
default = ["plugin", "which-support", "trash-support", "sqlite", "mimalloc"]
|
|
||||||
stable = ["default"]
|
stable = ["default"]
|
||||||
wasi = ["nu-cmd-lang/wasi"]
|
|
||||||
# NOTE: individual features are also passed to `nu-cmd-lang` that uses them to generate the feature matrix in the `version` command
|
# NOTE: individual features are also passed to `nu-cmd-lang` that uses them to generate the feature matrix in the `version` command
|
||||||
|
|
||||||
# Enable to statically link OpenSSL (perl is required, to build OpenSSL https://docs.rs/openssl/latest/openssl/);
|
# Enable to statically link OpenSSL (perl is required, to build OpenSSL https://docs.rs/openssl/latest/openssl/);
|
||||||
@ -131,14 +260,16 @@ wasi = ["nu-cmd-lang/wasi"]
|
|||||||
static-link-openssl = ["dep:openssl", "nu-cmd-lang/static-link-openssl"]
|
static-link-openssl = ["dep:openssl", "nu-cmd-lang/static-link-openssl"]
|
||||||
|
|
||||||
mimalloc = ["nu-cmd-lang/mimalloc", "dep:mimalloc"]
|
mimalloc = ["nu-cmd-lang/mimalloc", "dep:mimalloc"]
|
||||||
|
system-clipboard = [
|
||||||
|
"reedline/system_clipboard",
|
||||||
|
"nu-cli/system-clipboard",
|
||||||
|
"nu-cmd-lang/system-clipboard",
|
||||||
|
]
|
||||||
|
|
||||||
# Stable (Default)
|
# Stable (Default)
|
||||||
which-support = ["nu-command/which-support", "nu-cmd-lang/which-support"]
|
which-support = ["nu-command/which-support", "nu-cmd-lang/which-support"]
|
||||||
trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"]
|
trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"]
|
||||||
|
|
||||||
# Extra feature for nushell
|
|
||||||
extra = ["dep:nu-cmd-extra", "nu-cmd-lang/extra"]
|
|
||||||
|
|
||||||
# Dataframe feature for nushell
|
# Dataframe feature for nushell
|
||||||
dataframe = ["dep:nu-cmd-dataframe", "nu-cmd-lang/dataframe"]
|
dataframe = ["dep:nu-cmd-dataframe", "nu-cmd-lang/dataframe"]
|
||||||
|
|
||||||
@ -173,11 +304,9 @@ bench = false
|
|||||||
# To use a development version of a dependency please use a global override here
|
# 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
|
# changing versions in each sub-crate of the workspace is tedious
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
reedline = { git = "https://github.com/nushell/reedline.git", branch = "main" }
|
# reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
||||||
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
|
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
|
||||||
# uu_cp = { git = "https://github.com/uutils/coreutils.git", branch = "main" }
|
|
||||||
|
|
||||||
# Criterion benchmarking setup
|
|
||||||
# Run all benchmarks with `cargo bench`
|
# Run all benchmarks with `cargo bench`
|
||||||
# Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
# Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
||||||
[[bench]]
|
[[bench]]
|
||||||
|
@ -228,7 +228,7 @@ Please submit an issue or PR to be added to this list.
|
|||||||
See [Contributing](CONTRIBUTING.md) for details. Thanks to all the people who already contributed!
|
See [Contributing](CONTRIBUTING.md) for details. Thanks to all the people who already contributed!
|
||||||
|
|
||||||
<a href="https://github.com/nushell/nushell/graphs/contributors">
|
<a href="https://github.com/nushell/nushell/graphs/contributors">
|
||||||
<img src="https://contributors-img.web.app/image?repo=nushell/nushell&max=600" />
|
<img src="https://contributors-img.web.app/image?repo=nushell/nushell&max=750" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Criterion benchmarks
|
# Divan benchmarks
|
||||||
|
|
||||||
These are benchmarks using [Criterion](https://github.com/bheisler/criterion.rs), a microbenchmarking tool for Rust.
|
These are benchmarks using [Divan](https://github.com/nvzqz/divan), a microbenchmarking tool for Rust.
|
||||||
|
|
||||||
Run all benchmarks with `cargo bench`
|
Run all benchmarks with `cargo bench`
|
||||||
|
|
||||||
|
@ -1,11 +1,21 @@
|
|||||||
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
use nu_cli::{eval_source, evaluate_commands};
|
||||||
use nu_cli::eval_source;
|
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_plugin::{EncodingType, PluginResponse};
|
use nu_plugin_core::{Encoder, EncodingType};
|
||||||
use nu_protocol::{engine::EngineState, PipelineData, Span, Value};
|
use nu_plugin_protocol::{PluginCallResponse, PluginOutput};
|
||||||
|
use nu_protocol::{
|
||||||
|
engine::{EngineState, Stack},
|
||||||
|
eval_const::create_nu_constant,
|
||||||
|
PipelineData, Span, Spanned, Value, NU_VARIABLE_ID,
|
||||||
|
};
|
||||||
|
use nu_std::load_standard_library;
|
||||||
use nu_utils::{get_default_config, get_default_env};
|
use nu_utils::{get_default_config, get_default_env};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Run registered benchmarks.
|
||||||
|
divan::main();
|
||||||
|
}
|
||||||
|
|
||||||
fn load_bench_commands() -> EngineState {
|
fn load_bench_commands() -> EngineState {
|
||||||
nu_command::add_shell_command_context(nu_cmd_lang::create_default_context())
|
nu_command::add_shell_command_context(nu_cmd_lang::create_default_context())
|
||||||
}
|
}
|
||||||
@ -29,105 +39,333 @@ fn get_home_path(engine_state: &EngineState) -> PathBuf {
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setup_engine() -> EngineState {
|
||||||
|
let mut engine_state = load_bench_commands();
|
||||||
|
let home_path = get_home_path(&engine_state);
|
||||||
|
|
||||||
|
// parsing config.nu breaks without PWD set, so set a valid path
|
||||||
|
engine_state.add_env_var(
|
||||||
|
"PWD".into(),
|
||||||
|
Value::string(home_path.to_string_lossy(), Span::test_data()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let nu_const = create_nu_constant(&engine_state, Span::unknown())
|
||||||
|
.expect("Failed to create nushell constant.");
|
||||||
|
engine_state.set_variable_const_val(NU_VARIABLE_ID, nu_const);
|
||||||
|
|
||||||
|
engine_state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_command(bencher: divan::Bencher, scaled_command: String) {
|
||||||
|
bench_command_with_custom_stack_and_engine(
|
||||||
|
bencher,
|
||||||
|
scaled_command,
|
||||||
|
Stack::new(),
|
||||||
|
setup_engine(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_command_with_custom_stack_and_engine(
|
||||||
|
bencher: divan::Bencher,
|
||||||
|
scaled_command: String,
|
||||||
|
stack: nu_protocol::engine::Stack,
|
||||||
|
mut engine: EngineState,
|
||||||
|
) {
|
||||||
|
load_standard_library(&mut engine).unwrap();
|
||||||
|
let commands = Spanned {
|
||||||
|
span: Span::unknown(),
|
||||||
|
item: scaled_command,
|
||||||
|
};
|
||||||
|
|
||||||
|
bencher
|
||||||
|
.with_inputs(|| engine.clone())
|
||||||
|
.bench_values(|mut engine| {
|
||||||
|
evaluate_commands(
|
||||||
|
&commands,
|
||||||
|
&mut engine,
|
||||||
|
&mut stack.clone(),
|
||||||
|
PipelineData::empty(),
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_stack_and_engine_from_command(command: &str) -> (Stack, EngineState) {
|
||||||
|
let mut engine = setup_engine();
|
||||||
|
let commands = Spanned {
|
||||||
|
span: Span::unknown(),
|
||||||
|
item: command.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut stack = Stack::new();
|
||||||
|
evaluate_commands(
|
||||||
|
&commands,
|
||||||
|
&mut engine,
|
||||||
|
&mut stack,
|
||||||
|
PipelineData::empty(),
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
(stack, engine)
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: All benchmarks live in this 1 file to speed up build times when benchmarking.
|
// FIXME: All benchmarks live in this 1 file to speed up build times when benchmarking.
|
||||||
// When the *_benchmarks functions were in different files, `cargo bench` would build
|
// When the *_benchmarks functions were in different files, `cargo bench` would build
|
||||||
// an executable for every single one - incredibly slowly. Would be nice to figure out
|
// an executable for every single one - incredibly slowly. Would be nice to figure out
|
||||||
// a way to split things up again.
|
// a way to split things up again.
|
||||||
|
|
||||||
fn parser_benchmarks(c: &mut Criterion) {
|
#[divan::bench]
|
||||||
let mut engine_state = load_bench_commands();
|
fn load_standard_lib(bencher: divan::Bencher) {
|
||||||
let home_path = get_home_path(&engine_state);
|
let engine = setup_engine();
|
||||||
|
bencher
|
||||||
// parsing config.nu breaks without PWD set, so set a valid path
|
.with_inputs(|| engine.clone())
|
||||||
engine_state.add_env_var(
|
.bench_values(|mut engine| {
|
||||||
"PWD".into(),
|
load_standard_library(&mut engine).unwrap();
|
||||||
Value::string(home_path.to_string_lossy(), Span::test_data()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let default_env = get_default_env().as_bytes();
|
|
||||||
c.bench_function("parse_default_env_file", |b| {
|
|
||||||
b.iter_batched(
|
|
||||||
|| nu_protocol::engine::StateWorkingSet::new(&engine_state),
|
|
||||||
|mut working_set| parse(&mut working_set, None, default_env, false),
|
|
||||||
BatchSize::SmallInput,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let default_config = get_default_config().as_bytes();
|
|
||||||
c.bench_function("parse_default_config_file", |b| {
|
|
||||||
b.iter_batched(
|
|
||||||
|| nu_protocol::engine::StateWorkingSet::new(&engine_state),
|
|
||||||
|mut working_set| parse(&mut working_set, None, default_config, false),
|
|
||||||
BatchSize::SmallInput,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
c.bench_function("eval default_env.nu", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut stack = nu_protocol::engine::Stack::new();
|
|
||||||
eval_source(
|
|
||||||
&mut engine_state,
|
|
||||||
&mut stack,
|
|
||||||
get_default_env().as_bytes(),
|
|
||||||
"default_env.nu",
|
|
||||||
PipelineData::empty(),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
});
|
|
||||||
|
|
||||||
c.bench_function("eval default_config.nu", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut stack = nu_protocol::engine::Stack::new();
|
|
||||||
eval_source(
|
|
||||||
&mut engine_state,
|
|
||||||
&mut stack,
|
|
||||||
get_default_config().as_bytes(),
|
|
||||||
"default_config.nu",
|
|
||||||
PipelineData::empty(),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_benchmarks(c: &mut Criterion) {
|
#[divan::bench_group]
|
||||||
let mut engine_state = load_bench_commands();
|
mod record {
|
||||||
let home_path = get_home_path(&engine_state);
|
|
||||||
|
|
||||||
// parsing config.nu breaks without PWD set, so set a valid path
|
use super::*;
|
||||||
engine_state.add_env_var(
|
|
||||||
"PWD".into(),
|
|
||||||
Value::string(home_path.to_string_lossy(), Span::test_data()),
|
|
||||||
);
|
|
||||||
|
|
||||||
c.bench_function("eval default_env.nu", |b| {
|
fn create_flat_record_string(n: i32) -> String {
|
||||||
b.iter(|| {
|
let mut s = String::from("let record = {");
|
||||||
let mut stack = nu_protocol::engine::Stack::new();
|
for i in 0..n {
|
||||||
eval_source(
|
s.push_str(&format!("col_{}: {}", i, i));
|
||||||
&mut engine_state,
|
if i < n - 1 {
|
||||||
&mut stack,
|
s.push_str(", ");
|
||||||
get_default_env().as_bytes(),
|
}
|
||||||
"default_env.nu",
|
}
|
||||||
PipelineData::empty(),
|
s.push('}');
|
||||||
false,
|
s
|
||||||
)
|
}
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
c.bench_function("eval default_config.nu", |b| {
|
fn create_nested_record_string(depth: i32) -> String {
|
||||||
b.iter(|| {
|
let mut s = String::from("let record = {");
|
||||||
let mut stack = nu_protocol::engine::Stack::new();
|
for _ in 0..depth {
|
||||||
eval_source(
|
s.push_str("col: {");
|
||||||
&mut engine_state,
|
}
|
||||||
&mut stack,
|
s.push_str("col_final: 0");
|
||||||
get_default_config().as_bytes(),
|
for _ in 0..depth {
|
||||||
"default_config.nu",
|
s.push('}');
|
||||||
PipelineData::empty(),
|
}
|
||||||
false,
|
s.push('}');
|
||||||
)
|
s
|
||||||
})
|
}
|
||||||
});
|
|
||||||
|
#[divan::bench(args = [1, 10, 100, 1000])]
|
||||||
|
fn create(bencher: divan::Bencher, n: i32) {
|
||||||
|
bench_command(bencher, create_flat_record_string(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench(args = [1, 10, 100, 1000])]
|
||||||
|
fn flat_access(bencher: divan::Bencher, n: i32) {
|
||||||
|
let (stack, engine) = setup_stack_and_engine_from_command(&create_flat_record_string(n));
|
||||||
|
bench_command_with_custom_stack_and_engine(
|
||||||
|
bencher,
|
||||||
|
"$record.col_0 | ignore".to_string(),
|
||||||
|
stack,
|
||||||
|
engine,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench(args = [1, 2, 4, 8, 16, 32, 64, 128])]
|
||||||
|
fn nest_access(bencher: divan::Bencher, depth: i32) {
|
||||||
|
let (stack, engine) =
|
||||||
|
setup_stack_and_engine_from_command(&create_nested_record_string(depth));
|
||||||
|
let nested_access = ".col".repeat(depth as usize);
|
||||||
|
bench_command_with_custom_stack_and_engine(
|
||||||
|
bencher,
|
||||||
|
format!("$record{} | ignore", nested_access),
|
||||||
|
stack,
|
||||||
|
engine,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench_group]
|
||||||
|
mod table {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn create_example_table_nrows(n: i32) -> String {
|
||||||
|
let mut s = String::from("let table = [[foo bar baz]; ");
|
||||||
|
for i in 0..n {
|
||||||
|
s.push_str(&format!("[0, 1, {i}]"));
|
||||||
|
if i < n - 1 {
|
||||||
|
s.push_str(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.push(']');
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench(args = [1, 10, 100, 1000])]
|
||||||
|
fn create(bencher: divan::Bencher, n: i32) {
|
||||||
|
bench_command(bencher, create_example_table_nrows(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench(args = [1, 10, 100, 1000])]
|
||||||
|
fn get(bencher: divan::Bencher, n: i32) {
|
||||||
|
let (stack, engine) = setup_stack_and_engine_from_command(&create_example_table_nrows(n));
|
||||||
|
bench_command_with_custom_stack_and_engine(
|
||||||
|
bencher,
|
||||||
|
"$table | get bar | math sum | ignore".to_string(),
|
||||||
|
stack,
|
||||||
|
engine,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench(args = [1, 10, 100, 1000])]
|
||||||
|
fn select(bencher: divan::Bencher, n: i32) {
|
||||||
|
let (stack, engine) = setup_stack_and_engine_from_command(&create_example_table_nrows(n));
|
||||||
|
bench_command_with_custom_stack_and_engine(
|
||||||
|
bencher,
|
||||||
|
"$table | select foo baz | ignore".to_string(),
|
||||||
|
stack,
|
||||||
|
engine,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench_group]
|
||||||
|
mod eval_commands {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[divan::bench(args = [100, 1_000, 10_000])]
|
||||||
|
fn interleave(bencher: divan::Bencher, n: i32) {
|
||||||
|
bench_command(
|
||||||
|
bencher,
|
||||||
|
format!("seq 1 {n} | wrap a | interleave {{ seq 1 {n} | wrap b }} | ignore"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench(args = [100, 1_000, 10_000])]
|
||||||
|
fn interleave_with_ctrlc(bencher: divan::Bencher, n: i32) {
|
||||||
|
let mut engine = setup_engine();
|
||||||
|
engine.ctrlc = Some(std::sync::Arc::new(std::sync::atomic::AtomicBool::new(
|
||||||
|
false,
|
||||||
|
)));
|
||||||
|
load_standard_library(&mut engine).unwrap();
|
||||||
|
let commands = Spanned {
|
||||||
|
span: Span::unknown(),
|
||||||
|
item: format!("seq 1 {n} | wrap a | interleave {{ seq 1 {n} | wrap b }} | ignore"),
|
||||||
|
};
|
||||||
|
|
||||||
|
bencher
|
||||||
|
.with_inputs(|| engine.clone())
|
||||||
|
.bench_values(|mut engine| {
|
||||||
|
evaluate_commands(
|
||||||
|
&commands,
|
||||||
|
&mut engine,
|
||||||
|
&mut nu_protocol::engine::Stack::new(),
|
||||||
|
PipelineData::empty(),
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench(args = [1, 5, 10, 100, 1_000])]
|
||||||
|
fn for_range(bencher: divan::Bencher, n: i32) {
|
||||||
|
bench_command(bencher, format!("(for $x in (1..{}) {{ sleep 50ns }})", n))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench(args = [1, 5, 10, 100, 1_000])]
|
||||||
|
fn each(bencher: divan::Bencher, n: i32) {
|
||||||
|
bench_command(
|
||||||
|
bencher,
|
||||||
|
format!("(1..{}) | each {{|_| sleep 50ns }} | ignore", n),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench(args = [1, 5, 10, 100, 1_000])]
|
||||||
|
fn par_each_1t(bencher: divan::Bencher, n: i32) {
|
||||||
|
bench_command(
|
||||||
|
bencher,
|
||||||
|
format!("(1..{}) | par-each -t 1 {{|_| sleep 50ns }} | ignore", n),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench(args = [1, 5, 10, 100, 1_000])]
|
||||||
|
fn par_each_2t(bencher: divan::Bencher, n: i32) {
|
||||||
|
bench_command(
|
||||||
|
bencher,
|
||||||
|
format!("(1..{}) | par-each -t 2 {{|_| sleep 50ns }} | ignore", n),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench_group()]
|
||||||
|
mod parser_benchmarks {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[divan::bench()]
|
||||||
|
fn parse_default_config_file(bencher: divan::Bencher) {
|
||||||
|
let engine_state = setup_engine();
|
||||||
|
let default_env = get_default_config().as_bytes();
|
||||||
|
|
||||||
|
bencher
|
||||||
|
.with_inputs(|| nu_protocol::engine::StateWorkingSet::new(&engine_state))
|
||||||
|
.bench_refs(|working_set| parse(working_set, None, default_env, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench()]
|
||||||
|
fn parse_default_env_file(bencher: divan::Bencher) {
|
||||||
|
let engine_state = setup_engine();
|
||||||
|
let default_env = get_default_env().as_bytes();
|
||||||
|
|
||||||
|
bencher
|
||||||
|
.with_inputs(|| nu_protocol::engine::StateWorkingSet::new(&engine_state))
|
||||||
|
.bench_refs(|working_set| parse(working_set, None, default_env, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench_group()]
|
||||||
|
mod eval_benchmarks {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[divan::bench()]
|
||||||
|
fn eval_default_env(bencher: divan::Bencher) {
|
||||||
|
let default_env = get_default_env().as_bytes();
|
||||||
|
let fname = "default_env.nu";
|
||||||
|
bencher
|
||||||
|
.with_inputs(|| (setup_engine(), nu_protocol::engine::Stack::new()))
|
||||||
|
.bench_values(|(mut engine_state, mut stack)| {
|
||||||
|
eval_source(
|
||||||
|
&mut engine_state,
|
||||||
|
&mut stack,
|
||||||
|
default_env,
|
||||||
|
fname,
|
||||||
|
PipelineData::empty(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench()]
|
||||||
|
fn eval_default_config(bencher: divan::Bencher) {
|
||||||
|
let default_env = get_default_config().as_bytes();
|
||||||
|
let fname = "default_config.nu";
|
||||||
|
bencher
|
||||||
|
.with_inputs(|| (setup_engine(), nu_protocol::engine::Stack::new()))
|
||||||
|
.bench_values(|(mut engine_state, mut stack)| {
|
||||||
|
eval_source(
|
||||||
|
&mut engine_state,
|
||||||
|
&mut stack,
|
||||||
|
default_env,
|
||||||
|
fname,
|
||||||
|
PipelineData::empty(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate a new table data with `row_cnt` rows, `col_cnt` columns.
|
// generate a new table data with `row_cnt` rows, `col_cnt` columns.
|
||||||
@ -141,50 +379,76 @@ fn encoding_test_data(row_cnt: usize, col_cnt: usize) -> Value {
|
|||||||
Value::list(vec![record; row_cnt], Span::test_data())
|
Value::list(vec![record; row_cnt], Span::test_data())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encoding_benchmarks(c: &mut Criterion) {
|
#[divan::bench_group()]
|
||||||
let mut group = c.benchmark_group("Encoding");
|
mod encoding_benchmarks {
|
||||||
let test_cnt_pairs = [(100, 5), (100, 15), (10000, 5), (10000, 15)];
|
use super::*;
|
||||||
for (row_cnt, col_cnt) in test_cnt_pairs.into_iter() {
|
|
||||||
for fmt in ["json", "msgpack"] {
|
#[divan::bench(args = [(100, 5), (10000, 15)])]
|
||||||
group.bench_function(&format!("{fmt} encode {row_cnt} * {col_cnt}"), |b| {
|
fn json_encode(bencher: divan::Bencher, (row_cnt, col_cnt): (usize, usize)) {
|
||||||
let mut res = vec![];
|
let test_data = PluginOutput::CallResponse(
|
||||||
let test_data =
|
0,
|
||||||
PluginResponse::Value(Box::new(encoding_test_data(row_cnt, col_cnt)));
|
PluginCallResponse::value(encoding_test_data(row_cnt, col_cnt)),
|
||||||
let encoder = EncodingType::try_from_bytes(fmt.as_bytes()).unwrap();
|
);
|
||||||
b.iter(|| encoder.encode_response(&test_data, &mut res))
|
let encoder = EncodingType::try_from_bytes(b"json").unwrap();
|
||||||
});
|
bencher
|
||||||
}
|
.with_inputs(Vec::new)
|
||||||
|
.bench_values(|mut res| encoder.encode(&test_data, &mut res))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench(args = [(100, 5), (10000, 15)])]
|
||||||
|
fn msgpack_encode(bencher: divan::Bencher, (row_cnt, col_cnt): (usize, usize)) {
|
||||||
|
let test_data = PluginOutput::CallResponse(
|
||||||
|
0,
|
||||||
|
PluginCallResponse::value(encoding_test_data(row_cnt, col_cnt)),
|
||||||
|
);
|
||||||
|
let encoder = EncodingType::try_from_bytes(b"msgpack").unwrap();
|
||||||
|
bencher
|
||||||
|
.with_inputs(Vec::new)
|
||||||
|
.bench_values(|mut res| encoder.encode(&test_data, &mut res))
|
||||||
}
|
}
|
||||||
group.finish();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decoding_benchmarks(c: &mut Criterion) {
|
#[divan::bench_group()]
|
||||||
let mut group = c.benchmark_group("Decoding");
|
mod decoding_benchmarks {
|
||||||
let test_cnt_pairs = [(100, 5), (100, 15), (10000, 5), (10000, 15)];
|
use super::*;
|
||||||
for (row_cnt, col_cnt) in test_cnt_pairs.into_iter() {
|
|
||||||
for fmt in ["json", "msgpack"] {
|
|
||||||
group.bench_function(&format!("{fmt} decode for {row_cnt} * {col_cnt}"), |b| {
|
|
||||||
let mut res = vec![];
|
|
||||||
let test_data =
|
|
||||||
PluginResponse::Value(Box::new(encoding_test_data(row_cnt, col_cnt)));
|
|
||||||
let encoder = EncodingType::try_from_bytes(fmt.as_bytes()).unwrap();
|
|
||||||
encoder.encode_response(&test_data, &mut res).unwrap();
|
|
||||||
let mut binary_data = std::io::Cursor::new(res);
|
|
||||||
b.iter(|| {
|
|
||||||
binary_data.set_position(0);
|
|
||||||
encoder.decode_response(&mut binary_data)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
group.finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
criterion_group!(
|
#[divan::bench(args = [(100, 5), (10000, 15)])]
|
||||||
benches,
|
fn json_decode(bencher: divan::Bencher, (row_cnt, col_cnt): (usize, usize)) {
|
||||||
parser_benchmarks,
|
let test_data = PluginOutput::CallResponse(
|
||||||
eval_benchmarks,
|
0,
|
||||||
encoding_benchmarks,
|
PluginCallResponse::value(encoding_test_data(row_cnt, col_cnt)),
|
||||||
decoding_benchmarks
|
);
|
||||||
);
|
let encoder = EncodingType::try_from_bytes(b"json").unwrap();
|
||||||
criterion_main!(benches);
|
let mut res = vec![];
|
||||||
|
encoder.encode(&test_data, &mut res).unwrap();
|
||||||
|
bencher
|
||||||
|
.with_inputs(|| {
|
||||||
|
let mut binary_data = std::io::Cursor::new(res.clone());
|
||||||
|
binary_data.set_position(0);
|
||||||
|
binary_data
|
||||||
|
})
|
||||||
|
.bench_values(|mut binary_data| -> Result<Option<PluginOutput>, _> {
|
||||||
|
encoder.decode(&mut binary_data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench(args = [(100, 5), (10000, 15)])]
|
||||||
|
fn msgpack_decode(bencher: divan::Bencher, (row_cnt, col_cnt): (usize, usize)) {
|
||||||
|
let test_data = PluginOutput::CallResponse(
|
||||||
|
0,
|
||||||
|
PluginCallResponse::value(encoding_test_data(row_cnt, col_cnt)),
|
||||||
|
);
|
||||||
|
let encoder = EncodingType::try_from_bytes(b"msgpack").unwrap();
|
||||||
|
let mut res = vec![];
|
||||||
|
encoder.encode(&test_data, &mut res).unwrap();
|
||||||
|
bencher
|
||||||
|
.with_inputs(|| {
|
||||||
|
let mut binary_data = std::io::Cursor::new(res.clone());
|
||||||
|
binary_data.set_position(0);
|
||||||
|
binary_data
|
||||||
|
})
|
||||||
|
.bench_values(|mut binary_data| -> Result<Option<PluginOutput>, _> {
|
||||||
|
encoder.decode(&mut binary_data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,42 +5,45 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cli"
|
name = "nu-cli"
|
||||||
version = "0.88.2"
|
version = "0.93.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.88.2" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.93.0" }
|
||||||
nu-command = { path = "../nu-command", version = "0.88.2" }
|
nu-command = { path = "../nu-command", version = "0.93.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.88.2" }
|
nu-test-support = { path = "../nu-test-support", version = "0.93.0" }
|
||||||
rstest = { version = "0.18.1", default-features = false }
|
rstest = { workspace = true, default-features = false }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.88.2" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.93.0" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.88.2" }
|
nu-engine = { path = "../nu-engine", version = "0.93.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.88.2" }
|
nu-path = { path = "../nu-path", version = "0.93.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.88.2" }
|
nu-parser = { path = "../nu-parser", version = "0.93.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.88.2" }
|
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.93.0", optional = true }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.88.2" }
|
nu-protocol = { path = "../nu-protocol", version = "0.93.0" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.88.2" }
|
nu-utils = { path = "../nu-utils", version = "0.93.0" }
|
||||||
nu-ansi-term = "0.49.0"
|
nu-color-config = { path = "../nu-color-config", version = "0.93.0" }
|
||||||
reedline = { version = "0.27.0", features = ["bashisms", "sqlite"] }
|
nu-ansi-term = { workspace = true }
|
||||||
|
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
chrono = { default-features = false, features = ["std"], version = "0.4" }
|
chrono = { default-features = false, features = ["std"], workspace = true }
|
||||||
crossterm = "0.27"
|
crossterm = { workspace = true }
|
||||||
fancy-regex = "0.12"
|
fancy-regex = { workspace = true }
|
||||||
fuzzy-matcher = "0.3"
|
fuzzy-matcher = { workspace = true }
|
||||||
is_executable = "1.0"
|
is_executable = { workspace = true }
|
||||||
log = "0.4"
|
log = { workspace = true }
|
||||||
miette = { version = "5.10", features = ["fancy-no-backtrace"] }
|
miette = { workspace = true, features = ["fancy-no-backtrace"] }
|
||||||
once_cell = "1.18"
|
lscolors = { workspace = true, default-features = false, features = ["nu-ansi-term"] }
|
||||||
percent-encoding = "2"
|
once_cell = { workspace = true }
|
||||||
pathdiff = "0.2"
|
percent-encoding = { workspace = true }
|
||||||
sysinfo = "0.30"
|
pathdiff = { workspace = true }
|
||||||
unicode-segmentation = "1.10"
|
sysinfo = { workspace = true }
|
||||||
uuid = { version = "1.6.0", features = ["v4"] }
|
unicode-segmentation = { workspace = true }
|
||||||
which = "5.0.0"
|
uuid = { workspace = true, features = ["v4"] }
|
||||||
|
which = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = []
|
plugin = ["nu-plugin-engine"]
|
||||||
|
system-clipboard = ["reedline/system_clipboard"]
|
||||||
|
@ -1,129 +0,0 @@
|
|||||||
use nu_engine::CallExt;
|
|
||||||
use nu_protocol::{
|
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Commandline;
|
|
||||||
|
|
||||||
impl Command for Commandline {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"commandline"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("commandline")
|
|
||||||
.input_output_types(vec![
|
|
||||||
(Type::Nothing, Type::Nothing),
|
|
||||||
(Type::String, Type::String),
|
|
||||||
])
|
|
||||||
.switch(
|
|
||||||
"cursor",
|
|
||||||
"Set or get the current cursor position",
|
|
||||||
Some('c'),
|
|
||||||
)
|
|
||||||
.switch(
|
|
||||||
"cursor-end",
|
|
||||||
"Set the current cursor position to the end of the buffer",
|
|
||||||
Some('e'),
|
|
||||||
)
|
|
||||||
.switch(
|
|
||||||
"append",
|
|
||||||
"appends the string to the end of the buffer",
|
|
||||||
Some('a'),
|
|
||||||
)
|
|
||||||
.switch(
|
|
||||||
"insert",
|
|
||||||
"inserts the string into the buffer at the cursor position",
|
|
||||||
Some('i'),
|
|
||||||
)
|
|
||||||
.switch(
|
|
||||||
"replace",
|
|
||||||
"replaces the current contents of the buffer (default)",
|
|
||||||
Some('r'),
|
|
||||||
)
|
|
||||||
.optional(
|
|
||||||
"cmd",
|
|
||||||
SyntaxShape::String,
|
|
||||||
"the string to perform the operation with",
|
|
||||||
)
|
|
||||||
.category(Category::Core)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"View or modify the current command line input buffer."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
|
||||||
vec!["repl", "interactive"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
_input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? {
|
|
||||||
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
|
||||||
|
|
||||||
if call.has_flag("cursor") {
|
|
||||||
let cmd_str = cmd.as_string()?;
|
|
||||||
match cmd_str.parse::<i64>() {
|
|
||||||
Ok(n) => {
|
|
||||||
repl.cursor_pos = if n <= 0 {
|
|
||||||
0usize
|
|
||||||
} else {
|
|
||||||
repl.buffer
|
|
||||||
.grapheme_indices(true)
|
|
||||||
.map(|(i, _c)| i)
|
|
||||||
.nth(n as usize)
|
|
||||||
.unwrap_or(repl.buffer.len())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
return Err(ShellError::CantConvert {
|
|
||||||
to_type: "int".to_string(),
|
|
||||||
from_type: "string".to_string(),
|
|
||||||
span: cmd.span(),
|
|
||||||
help: Some(format!(
|
|
||||||
r#"string "{cmd_str}" does not represent a valid int"#
|
|
||||||
)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if call.has_flag("append") {
|
|
||||||
repl.buffer.push_str(&cmd.as_string()?);
|
|
||||||
} else if call.has_flag("insert") {
|
|
||||||
let cmd_str = cmd.as_string()?;
|
|
||||||
let cursor_pos = repl.cursor_pos;
|
|
||||||
repl.buffer.insert_str(cursor_pos, &cmd_str);
|
|
||||||
repl.cursor_pos += cmd_str.len();
|
|
||||||
} else {
|
|
||||||
repl.buffer = cmd.as_string()?;
|
|
||||||
repl.cursor_pos = repl.buffer.len();
|
|
||||||
}
|
|
||||||
Ok(Value::nothing(call.head).into_pipeline_data())
|
|
||||||
} else {
|
|
||||||
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
|
||||||
if call.has_flag("cursor-end") {
|
|
||||||
repl.cursor_pos = repl.buffer.graphemes(true).count();
|
|
||||||
Ok(Value::nothing(call.head).into_pipeline_data())
|
|
||||||
} else if call.has_flag("cursor") {
|
|
||||||
let char_pos = repl
|
|
||||||
.buffer
|
|
||||||
.grapheme_indices(true)
|
|
||||||
.chain(std::iter::once((repl.buffer.len(), "")))
|
|
||||||
.position(|(i, _c)| i == repl.cursor_pos)
|
|
||||||
.expect("Cursor position isn't on a grapheme boundary");
|
|
||||||
Ok(Value::string(char_pos.to_string(), call.head).into_pipeline_data())
|
|
||||||
} else {
|
|
||||||
Ok(Value::string(repl.buffer.to_string(), call.head).into_pipeline_data())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
35
crates/nu-cli/src/commands/commandline/commandline_.rs
Normal file
35
crates/nu-cli/src/commands/commandline/commandline_.rs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Commandline;
|
||||||
|
|
||||||
|
impl Command for Commandline {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"commandline"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("commandline")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"View the current command line input buffer."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["repl", "interactive"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||||
|
Ok(Value::string(repl.buffer.clone(), call.head).into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
66
crates/nu-cli/src/commands/commandline/edit.rs
Normal file
66
crates/nu-cli/src/commands/commandline/edit.rs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"commandline edit"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.switch(
|
||||||
|
"append",
|
||||||
|
"appends the string to the end of the buffer",
|
||||||
|
Some('a'),
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"insert",
|
||||||
|
"inserts the string into the buffer at the cursor position",
|
||||||
|
Some('i'),
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"replace",
|
||||||
|
"replaces the current contents of the buffer (default)",
|
||||||
|
Some('r'),
|
||||||
|
)
|
||||||
|
.required(
|
||||||
|
"str",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the string to perform the operation with",
|
||||||
|
)
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Modify the current command line input buffer."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["repl", "interactive"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let str: String = call.req(engine_state, stack, 0)?;
|
||||||
|
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||||
|
if call.has_flag(engine_state, stack, "append")? {
|
||||||
|
repl.buffer.push_str(&str);
|
||||||
|
} else if call.has_flag(engine_state, stack, "insert")? {
|
||||||
|
let cursor_pos = repl.cursor_pos;
|
||||||
|
repl.buffer.insert_str(cursor_pos, &str);
|
||||||
|
repl.cursor_pos += str.len();
|
||||||
|
} else {
|
||||||
|
repl.buffer = str;
|
||||||
|
repl.cursor_pos = repl.buffer.len();
|
||||||
|
}
|
||||||
|
Ok(Value::nothing(call.head).into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
52
crates/nu-cli/src/commands/commandline/get_cursor.rs
Normal file
52
crates/nu-cli/src/commands/commandline/get_cursor.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"commandline get-cursor"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Int)])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Get the current cursor position."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["repl", "interactive"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||||
|
let char_pos = repl
|
||||||
|
.buffer
|
||||||
|
.grapheme_indices(true)
|
||||||
|
.chain(std::iter::once((repl.buffer.len(), "")))
|
||||||
|
.position(|(i, _c)| i == repl.cursor_pos)
|
||||||
|
.expect("Cursor position isn't on a grapheme boundary");
|
||||||
|
match i64::try_from(char_pos) {
|
||||||
|
Ok(pos) => Ok(Value::int(pos, call.head).into_pipeline_data()),
|
||||||
|
Err(e) => Err(ShellError::GenericError {
|
||||||
|
error: "Failed to convert cursor position to int".to_string(),
|
||||||
|
msg: e.to_string(),
|
||||||
|
span: None,
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
crates/nu-cli/src/commands/commandline/mod.rs
Normal file
9
crates/nu-cli/src/commands/commandline/mod.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
mod commandline_;
|
||||||
|
mod edit;
|
||||||
|
mod get_cursor;
|
||||||
|
mod set_cursor;
|
||||||
|
|
||||||
|
pub use commandline_::Commandline;
|
||||||
|
pub use edit::SubCommand as CommandlineEdit;
|
||||||
|
pub use get_cursor::SubCommand as CommandlineGetCursor;
|
||||||
|
pub use set_cursor::SubCommand as CommandlineSetCursor;
|
65
crates/nu-cli/src/commands/commandline/set_cursor.rs
Normal file
65
crates/nu-cli/src/commands/commandline/set_cursor.rs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"commandline set-cursor"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.switch(
|
||||||
|
"end",
|
||||||
|
"set the current cursor position to the end of the buffer",
|
||||||
|
Some('e'),
|
||||||
|
)
|
||||||
|
.optional("pos", SyntaxShape::Int, "Cursor position to be set")
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Set the current cursor position."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["repl", "interactive"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||||
|
if let Some(pos) = call.opt::<i64>(engine_state, stack, 0)? {
|
||||||
|
repl.cursor_pos = if pos <= 0 {
|
||||||
|
0usize
|
||||||
|
} else {
|
||||||
|
repl.buffer
|
||||||
|
.grapheme_indices(true)
|
||||||
|
.map(|(i, _c)| i)
|
||||||
|
.nth(pos as usize)
|
||||||
|
.unwrap_or(repl.buffer.len())
|
||||||
|
};
|
||||||
|
Ok(Value::nothing(call.head).into_pipeline_data())
|
||||||
|
} else if call.has_flag(engine_state, stack, "end")? {
|
||||||
|
repl.cursor_pos = repl.buffer.len();
|
||||||
|
Ok(Value::nothing(call.head).into_pipeline_data())
|
||||||
|
} else {
|
||||||
|
Err(ShellError::GenericError {
|
||||||
|
error: "Required a positional argument or a flag".to_string(),
|
||||||
|
msg: "".to_string(),
|
||||||
|
span: None,
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
|
||||||
|
|
||||||
use crate::commands::*;
|
use crate::commands::*;
|
||||||
|
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
||||||
|
|
||||||
pub fn add_cli_context(mut engine_state: EngineState) -> EngineState {
|
pub fn add_cli_context(mut engine_state: EngineState) -> EngineState {
|
||||||
let delta = {
|
let delta = {
|
||||||
@ -14,6 +13,9 @@ pub fn add_cli_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
|
|
||||||
bind_command! {
|
bind_command! {
|
||||||
Commandline,
|
Commandline,
|
||||||
|
CommandlineEdit,
|
||||||
|
CommandlineGetCursor,
|
||||||
|
CommandlineSetCursor,
|
||||||
History,
|
History,
|
||||||
HistorySession,
|
HistorySession,
|
||||||
Keybindings,
|
Keybindings,
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
use nu_protocol::ast::Call;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::HistoryFileFormat;
|
||||||
use nu_protocol::{
|
|
||||||
record, Category, Example, HistoryFileFormat, IntoInterruptiblePipelineData, PipelineData,
|
|
||||||
ShellError, Signature, Span, Type, Value,
|
|
||||||
};
|
|
||||||
use reedline::{
|
use reedline::{
|
||||||
FileBackedHistory, History as ReedlineHistory, HistoryItem, SearchDirection, SearchQuery,
|
FileBackedHistory, History as ReedlineHistory, HistoryItem, SearchDirection, SearchQuery,
|
||||||
SqliteBackedHistory,
|
SqliteBackedHistory,
|
||||||
@ -23,10 +19,7 @@ impl Command for History {
|
|||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("history")
|
Signature::build("history")
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![(Type::Nothing, Type::Any)])
|
||||||
(Type::Nothing, Type::Table(vec![])),
|
|
||||||
(Type::Nothing, Type::Nothing),
|
|
||||||
])
|
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.switch("clear", "Clears out the history entries", Some('c'))
|
.switch("clear", "Clears out the history entries", Some('c'))
|
||||||
.switch(
|
.switch(
|
||||||
@ -40,21 +33,25 @@ impl Command for History {
|
|||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
_stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
|
|
||||||
|
let Some(history) = engine_state.history_config() else {
|
||||||
|
return Ok(PipelineData::empty());
|
||||||
|
};
|
||||||
|
|
||||||
// todo for sqlite history this command should be an alias to `open ~/.config/nushell/history.sqlite3 | get history`
|
// todo for sqlite history this command should be an alias to `open ~/.config/nushell/history.sqlite3 | get history`
|
||||||
if let Some(config_path) = nu_path::config_dir() {
|
if let Some(config_path) = nu_path::config_dir() {
|
||||||
let clear = call.has_flag("clear");
|
let clear = call.has_flag(engine_state, stack, "clear")?;
|
||||||
let long = call.has_flag("long");
|
let long = call.has_flag(engine_state, stack, "long")?;
|
||||||
let ctrlc = engine_state.ctrlc.clone();
|
let ctrlc = engine_state.ctrlc.clone();
|
||||||
|
|
||||||
let mut history_path = config_path;
|
let mut history_path = config_path;
|
||||||
history_path.push("nushell");
|
history_path.push("nushell");
|
||||||
match engine_state.config.history_file_format {
|
match history.file_format {
|
||||||
HistoryFileFormat::Sqlite => {
|
HistoryFileFormat::Sqlite => {
|
||||||
history_path.push("history.sqlite3");
|
history_path.push("history.sqlite3");
|
||||||
}
|
}
|
||||||
@ -68,29 +65,28 @@ impl Command for History {
|
|||||||
// TODO: FIXME also clear the auxiliary files when using sqlite
|
// TODO: FIXME also clear the auxiliary files when using sqlite
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
} else {
|
} else {
|
||||||
let history_reader: Option<Box<dyn ReedlineHistory>> =
|
let history_reader: Option<Box<dyn ReedlineHistory>> = match history.file_format {
|
||||||
match engine_state.config.history_file_format {
|
HistoryFileFormat::Sqlite => {
|
||||||
HistoryFileFormat::Sqlite => {
|
SqliteBackedHistory::with_file(history_path.clone(), None, None)
|
||||||
SqliteBackedHistory::with_file(history_path, None, None)
|
.map(|inner| {
|
||||||
.map(|inner| {
|
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
||||||
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
boxed
|
||||||
boxed
|
})
|
||||||
})
|
.ok()
|
||||||
.ok()
|
}
|
||||||
}
|
|
||||||
|
|
||||||
HistoryFileFormat::PlainText => FileBackedHistory::with_file(
|
HistoryFileFormat::PlainText => FileBackedHistory::with_file(
|
||||||
engine_state.config.max_history_size as usize,
|
history.max_size as usize,
|
||||||
history_path,
|
history_path.clone(),
|
||||||
)
|
)
|
||||||
.map(|inner| {
|
.map(|inner| {
|
||||||
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
||||||
boxed
|
boxed
|
||||||
})
|
})
|
||||||
.ok(),
|
.ok(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match engine_state.config.history_file_format {
|
match history.file_format {
|
||||||
HistoryFileFormat::PlainText => Ok(history_reader
|
HistoryFileFormat::PlainText => Ok(history_reader
|
||||||
.and_then(|h| {
|
.and_then(|h| {
|
||||||
h.search(SearchQuery::everything(SearchDirection::Forward, None))
|
h.search(SearchQuery::everything(SearchDirection::Forward, None))
|
||||||
@ -107,7 +103,10 @@ impl Command for History {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.ok_or(ShellError::FileNotFound { span: head })?
|
.ok_or(ShellError::FileNotFound {
|
||||||
|
file: history_path.display().to_string(),
|
||||||
|
span: head,
|
||||||
|
})?
|
||||||
.into_pipeline_data(ctrlc)),
|
.into_pipeline_data(ctrlc)),
|
||||||
HistoryFileFormat::Sqlite => Ok(history_reader
|
HistoryFileFormat::Sqlite => Ok(history_reader
|
||||||
.and_then(|h| {
|
.and_then(|h| {
|
||||||
@ -119,12 +118,15 @@ impl Command for History {
|
|||||||
create_history_record(idx, entry, long, head)
|
create_history_record(idx, entry, long, head)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.ok_or(ShellError::FileNotFound { span: head })?
|
.ok_or(ShellError::FileNotFound {
|
||||||
|
file: history_path.display().to_string(),
|
||||||
|
span: head,
|
||||||
|
})?
|
||||||
.into_pipeline_data(ctrlc)),
|
.into_pipeline_data(ctrlc)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::FileNotFound { span: head })
|
Err(ShellError::ConfigDirNotFound { span: Some(head) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
use nu_protocol::ast::Call;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
|
||||||
use nu_protocol::{
|
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct HistorySession;
|
pub struct HistorySession;
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
use nu_engine::get_full_help;
|
use nu_engine::{command_prelude::*, get_full_help};
|
||||||
use nu_protocol::{
|
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Keybindings;
|
pub struct Keybindings;
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
use nu_protocol::{
|
use nu_engine::command_prelude::*;
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
|
||||||
};
|
|
||||||
use reedline::get_reedline_default_keybindings;
|
use reedline::get_reedline_default_keybindings;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -16,7 +12,7 @@ impl Command for KeybindingsDefault {
|
|||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build(self.name())
|
Signature::build(self.name())
|
||||||
.category(Category::Platform)
|
.category(Category::Platform)
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
.input_output_types(vec![(Type::Nothing, Type::table())])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
use nu_protocol::{
|
use nu_engine::command_prelude::*;
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type,
|
|
||||||
Value,
|
|
||||||
};
|
|
||||||
use reedline::{
|
use reedline::{
|
||||||
get_reedline_edit_commands, get_reedline_keybinding_modifiers, get_reedline_keycodes,
|
get_reedline_edit_commands, get_reedline_keybinding_modifiers, get_reedline_keycodes,
|
||||||
get_reedline_prompt_edit_modes, get_reedline_reedline_events,
|
get_reedline_prompt_edit_modes, get_reedline_reedline_events,
|
||||||
@ -19,7 +14,7 @@ impl Command for KeybindingsList {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build(self.name())
|
Signature::build(self.name())
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
.input_output_types(vec![(Type::Nothing, Type::table())])
|
||||||
.switch("modifiers", "list of modifiers", Some('m'))
|
.switch("modifiers", "list of modifiers", Some('m'))
|
||||||
.switch("keycodes", "list of keycodes", Some('k'))
|
.switch("keycodes", "list of keycodes", Some('k'))
|
||||||
.switch("modes", "list of edit modes", Some('o'))
|
.switch("modes", "list of edit modes", Some('o'))
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
use crossterm::execute;
|
use crossterm::{
|
||||||
use crossterm::QueueableCommand;
|
event::Event, event::KeyCode, event::KeyEvent, execute, terminal, QueueableCommand,
|
||||||
use crossterm::{event::Event, event::KeyCode, event::KeyEvent, terminal};
|
|
||||||
use nu_protocol::ast::Call;
|
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
|
||||||
use nu_protocol::{
|
|
||||||
record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type,
|
|
||||||
Value,
|
|
||||||
};
|
};
|
||||||
|
use nu_engine::command_prelude::*;
|
||||||
use std::io::{stdout, Write};
|
use std::io::{stdout, Write};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -112,7 +107,7 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
|
|||||||
let o = match v {
|
let o = match v {
|
||||||
Value::Record { val, .. } => val
|
Value::Record { val, .. } => val
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(x, y)| format!("{}: {}", x, y.into_string("", config)))
|
.map(|(x, y)| format!("{}: {}", x, y.to_expanded_string("", config)))
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join(", "),
|
.join(", "),
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ mod keybindings_default;
|
|||||||
mod keybindings_list;
|
mod keybindings_list;
|
||||||
mod keybindings_listen;
|
mod keybindings_listen;
|
||||||
|
|
||||||
pub use commandline::Commandline;
|
pub use commandline::{Commandline, CommandlineEdit, CommandlineGetCursor, CommandlineSetCursor};
|
||||||
pub use history::{History, HistorySession};
|
pub use history::{History, HistorySession};
|
||||||
pub use keybindings::Keybindings;
|
pub use keybindings::Keybindings;
|
||||||
pub use keybindings_default::KeybindingsDefault;
|
pub use keybindings_default::KeybindingsDefault;
|
||||||
|
@ -13,13 +13,13 @@ pub trait Completer {
|
|||||||
offset: usize,
|
offset: usize,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion>;
|
) -> Vec<SemanticSuggestion>;
|
||||||
|
|
||||||
fn get_sort_by(&self) -> SortBy {
|
fn get_sort_by(&self) -> SortBy {
|
||||||
SortBy::Ascending
|
SortBy::Ascending
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sort(&self, items: Vec<Suggestion>, prefix: Vec<u8>) -> Vec<Suggestion> {
|
fn sort(&self, items: Vec<SemanticSuggestion>, prefix: Vec<u8>) -> Vec<SemanticSuggestion> {
|
||||||
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
||||||
let mut filtered_items = items;
|
let mut filtered_items = items;
|
||||||
|
|
||||||
@ -27,13 +27,13 @@ pub trait Completer {
|
|||||||
match self.get_sort_by() {
|
match self.get_sort_by() {
|
||||||
SortBy::LevenshteinDistance => {
|
SortBy::LevenshteinDistance => {
|
||||||
filtered_items.sort_by(|a, b| {
|
filtered_items.sort_by(|a, b| {
|
||||||
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
let a_distance = levenshtein_distance(&prefix_str, &a.suggestion.value);
|
||||||
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
let b_distance = levenshtein_distance(&prefix_str, &b.suggestion.value);
|
||||||
a_distance.cmp(&b_distance)
|
a_distance.cmp(&b_distance)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
SortBy::Ascending => {
|
SortBy::Ascending => {
|
||||||
filtered_items.sort_by(|a, b| a.value.cmp(&b.value));
|
filtered_items.sort_by(|a, b| a.suggestion.value.cmp(&b.suggestion.value));
|
||||||
}
|
}
|
||||||
SortBy::None => {}
|
SortBy::None => {}
|
||||||
};
|
};
|
||||||
@ -41,3 +41,25 @@ pub trait Completer {
|
|||||||
filtered_items
|
filtered_items
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, PartialEq)]
|
||||||
|
pub struct SemanticSuggestion {
|
||||||
|
pub suggestion: Suggestion,
|
||||||
|
pub kind: Option<SuggestionKind>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: think about name: maybe suggestion context?
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum SuggestionKind {
|
||||||
|
Command(nu_protocol::engine::CommandType),
|
||||||
|
Type(nu_protocol::Type),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Suggestion> for SemanticSuggestion {
|
||||||
|
fn from(suggestion: Suggestion) -> Self {
|
||||||
|
Self {
|
||||||
|
suggestion,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
use crate::completions::{Completer, CompletionOptions, MatchAlgorithm, SortBy};
|
use crate::{
|
||||||
|
completions::{Completer, CompletionOptions, MatchAlgorithm, SortBy},
|
||||||
|
SuggestionKind,
|
||||||
|
};
|
||||||
use nu_parser::FlatShape;
|
use nu_parser::FlatShape;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{CachedFile, EngineState, StateWorkingSet},
|
||||||
Span,
|
Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::SemanticSuggestion;
|
||||||
|
|
||||||
pub struct CommandCompletion {
|
pub struct CommandCompletion {
|
||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
flattened: Vec<(Span, FlatShape)>,
|
flattened: Vec<(Span, FlatShape)>,
|
||||||
@ -43,9 +48,9 @@ impl CommandCompletion {
|
|||||||
if let Some(paths) = paths {
|
if let Some(paths) = paths {
|
||||||
if let Ok(paths) = paths.as_list() {
|
if let Ok(paths) = paths.as_list() {
|
||||||
for path in paths {
|
for path in paths {
|
||||||
let path = path.as_string().unwrap_or_default();
|
let path = path.coerce_str().unwrap_or_default();
|
||||||
|
|
||||||
if let Ok(mut contents) = std::fs::read_dir(path) {
|
if let Ok(mut contents) = std::fs::read_dir(path.as_ref()) {
|
||||||
while let Some(Ok(item)) = contents.next() {
|
while let Some(Ok(item)) = contents.next() {
|
||||||
if self.engine_state.config.max_external_completion_results
|
if self.engine_state.config.max_external_completion_results
|
||||||
> executables.len() as i64
|
> executables.len() as i64
|
||||||
@ -83,7 +88,7 @@ impl CommandCompletion {
|
|||||||
offset: usize,
|
offset: usize,
|
||||||
find_externals: bool,
|
find_externals: bool,
|
||||||
match_algorithm: MatchAlgorithm,
|
match_algorithm: MatchAlgorithm,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let partial = working_set.get_span_contents(span);
|
let partial = working_set.get_span_contents(span);
|
||||||
|
|
||||||
let filter_predicate = |command: &[u8]| match_algorithm.matches_u8(command, partial);
|
let filter_predicate = |command: &[u8]| match_algorithm.matches_u8(command, partial);
|
||||||
@ -91,12 +96,16 @@ impl CommandCompletion {
|
|||||||
let mut results = working_set
|
let mut results = working_set
|
||||||
.find_commands_by_predicate(filter_predicate, true)
|
.find_commands_by_predicate(filter_predicate, true)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |x| Suggestion {
|
.map(move |x| SemanticSuggestion {
|
||||||
value: String::from_utf8_lossy(&x.0).to_string(),
|
suggestion: Suggestion {
|
||||||
description: x.1,
|
value: String::from_utf8_lossy(&x.0).to_string(),
|
||||||
extra: None,
|
description: x.1,
|
||||||
span: reedline::Span::new(span.start - offset, span.end - offset),
|
style: None,
|
||||||
append_whitespace: true,
|
extra: None,
|
||||||
|
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||||
|
append_whitespace: true,
|
||||||
|
},
|
||||||
|
kind: Some(SuggestionKind::Command(x.2)),
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
@ -107,25 +116,34 @@ impl CommandCompletion {
|
|||||||
let results_external = self
|
let results_external = self
|
||||||
.external_command_completion(&partial, match_algorithm)
|
.external_command_completion(&partial, match_algorithm)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |x| Suggestion {
|
.map(move |x| SemanticSuggestion {
|
||||||
value: x,
|
suggestion: Suggestion {
|
||||||
description: None,
|
value: x,
|
||||||
extra: None,
|
description: None,
|
||||||
span: reedline::Span::new(span.start - offset, span.end - offset),
|
style: None,
|
||||||
append_whitespace: true,
|
extra: None,
|
||||||
|
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||||
|
append_whitespace: true,
|
||||||
|
},
|
||||||
|
// TODO: is there a way to create a test?
|
||||||
|
kind: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
let results_strings: Vec<String> =
|
let results_strings: Vec<String> =
|
||||||
results.clone().into_iter().map(|x| x.value).collect();
|
results.iter().map(|x| x.suggestion.value.clone()).collect();
|
||||||
|
|
||||||
for external in results_external {
|
for external in results_external {
|
||||||
if results_strings.contains(&external.value) {
|
if results_strings.contains(&external.suggestion.value) {
|
||||||
results.push(Suggestion {
|
results.push(SemanticSuggestion {
|
||||||
value: format!("^{}", external.value),
|
suggestion: Suggestion {
|
||||||
description: None,
|
value: format!("^{}", external.suggestion.value),
|
||||||
extra: None,
|
description: None,
|
||||||
span: external.span,
|
style: None,
|
||||||
append_whitespace: true,
|
extra: None,
|
||||||
|
span: external.suggestion.span,
|
||||||
|
append_whitespace: true,
|
||||||
|
},
|
||||||
|
kind: external.kind,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
results.push(external)
|
results.push(external)
|
||||||
@ -148,7 +166,7 @@ impl Completer for CommandCompletion {
|
|||||||
offset: usize,
|
offset: usize,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let last = self
|
let last = self
|
||||||
.flattened
|
.flattened
|
||||||
.iter()
|
.iter()
|
||||||
@ -226,8 +244,9 @@ pub fn find_non_whitespace_index(contents: &[u8], start: usize) -> usize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_passthrough_command(working_set_file_contents: &[(Vec<u8>, usize, usize)]) -> bool {
|
pub fn is_passthrough_command(working_set_file_contents: &[CachedFile]) -> bool {
|
||||||
for (contents, _, _) in working_set_file_contents {
|
for cached_file in working_set_file_contents {
|
||||||
|
let contents = &cached_file.content;
|
||||||
let last_pipe_pos_rev = contents.iter().rev().position(|x| x == &b'|');
|
let last_pipe_pos_rev = contents.iter().rev().position(|x| x == &b'|');
|
||||||
let last_pipe_pos = last_pipe_pos_rev.map(|x| contents.len() - x).unwrap_or(0);
|
let last_pipe_pos = last_pipe_pos_rev.map(|x| contents.len() - x).unwrap_or(0);
|
||||||
|
|
||||||
@ -250,7 +269,7 @@ mod command_completions_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_find_non_whitespace_index() {
|
fn test_find_non_whitespace_index() {
|
||||||
let commands = vec![
|
let commands = [
|
||||||
(" hello", 4),
|
(" hello", 4),
|
||||||
("sudo ", 0),
|
("sudo ", 0),
|
||||||
(" sudo ", 2),
|
(" sudo ", 2),
|
||||||
@ -270,7 +289,7 @@ mod command_completions_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_last_command_passthrough() {
|
fn test_is_last_command_passthrough() {
|
||||||
let commands = vec![
|
let commands = [
|
||||||
(" hello", false),
|
(" hello", false),
|
||||||
(" sudo ", true),
|
(" sudo ", true),
|
||||||
("sudo ", true),
|
("sudo ", true),
|
||||||
@ -292,7 +311,7 @@ mod command_completions_tests {
|
|||||||
let input = ele.0.as_bytes();
|
let input = ele.0.as_bytes();
|
||||||
|
|
||||||
let mut engine_state = EngineState::new();
|
let mut engine_state = EngineState::new();
|
||||||
engine_state.add_file("test.nu".into(), vec![]);
|
engine_state.add_file("test.nu".into(), Arc::new([]));
|
||||||
|
|
||||||
let delta = {
|
let delta = {
|
||||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
@ -2,16 +2,18 @@ use crate::completions::{
|
|||||||
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
||||||
DotNuCompletion, FileCompletion, FlagCompletion, VariableCompletion,
|
DotNuCompletion, FileCompletion, FlagCompletion, VariableCompletion,
|
||||||
};
|
};
|
||||||
|
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
|
||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_parser::{flatten_expression, parse, FlatShape};
|
use nu_parser::{flatten_pipeline_element, parse, FlatShape};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::PipelineElement,
|
debugger::WithoutDebug,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{Closure, EngineState, Stack, StateWorkingSet},
|
||||||
BlockId, PipelineData, Span, Value,
|
PipelineData, Span, Value,
|
||||||
};
|
};
|
||||||
use reedline::{Completer as ReedlineCompleter, Suggestion};
|
use reedline::{Completer as ReedlineCompleter, Suggestion};
|
||||||
use std::str;
|
use std::{str, sync::Arc};
|
||||||
use std::sync::Arc;
|
|
||||||
|
use super::base::{SemanticSuggestion, SuggestionKind};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct NuCompleter {
|
pub struct NuCompleter {
|
||||||
@ -23,10 +25,14 @@ impl NuCompleter {
|
|||||||
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
|
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
|
||||||
Self {
|
Self {
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack: stack.reset_out_dest().capture(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn fetch_completions_at(&mut self, line: &str, pos: usize) -> Vec<SemanticSuggestion> {
|
||||||
|
self.completion_helper(line, pos)
|
||||||
|
}
|
||||||
|
|
||||||
// Process the completion for a given completer
|
// Process the completion for a given completer
|
||||||
fn process_completion<T: Completer>(
|
fn process_completion<T: Completer>(
|
||||||
&self,
|
&self,
|
||||||
@ -36,7 +42,7 @@ impl NuCompleter {
|
|||||||
new_span: Span,
|
new_span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let config = self.engine_state.get_config();
|
let config = self.engine_state.get_config();
|
||||||
|
|
||||||
let options = CompletionOptions {
|
let options = CompletionOptions {
|
||||||
@ -57,14 +63,15 @@ impl NuCompleter {
|
|||||||
|
|
||||||
fn external_completion(
|
fn external_completion(
|
||||||
&self,
|
&self,
|
||||||
block_id: BlockId,
|
closure: &Closure,
|
||||||
spans: &[String],
|
spans: &[String],
|
||||||
offset: usize,
|
offset: usize,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> Option<Vec<Suggestion>> {
|
) -> Option<Vec<SemanticSuggestion>> {
|
||||||
let stack = self.stack.clone();
|
let block = self.engine_state.get_block(closure.block_id);
|
||||||
let block = self.engine_state.get_block(block_id);
|
let mut callee_stack = self
|
||||||
let mut callee_stack = stack.gather_captures(&self.engine_state, &block.captures);
|
.stack
|
||||||
|
.captures_to_stack_preserve_out_dest(closure.captures.clone());
|
||||||
|
|
||||||
// Line
|
// Line
|
||||||
if let Some(pos_arg) = block.signature.required_positional.first() {
|
if let Some(pos_arg) = block.signature.required_positional.first() {
|
||||||
@ -82,13 +89,11 @@ impl NuCompleter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = eval_block(
|
let result = eval_block::<WithoutDebug>(
|
||||||
&self.engine_state,
|
&self.engine_state,
|
||||||
&mut callee_stack,
|
&mut callee_stack,
|
||||||
block,
|
block,
|
||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
true,
|
|
||||||
true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
@ -107,276 +112,281 @@ impl NuCompleter {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<SemanticSuggestion> {
|
||||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||||
let offset = working_set.next_span_start();
|
let offset = working_set.next_span_start();
|
||||||
|
// TODO: Callers should be trimming the line themselves
|
||||||
|
let line = if line.len() > pos { &line[..pos] } else { line };
|
||||||
|
// Adjust offset so that the spans of the suggestions will start at the right
|
||||||
|
// place even with `only_buffer_difference: true`
|
||||||
|
let fake_offset = offset + line.len() - pos;
|
||||||
|
let pos = offset + line.len();
|
||||||
let initial_line = line.to_string();
|
let initial_line = line.to_string();
|
||||||
let mut line = line.to_string();
|
let mut line = line.to_string();
|
||||||
line.insert(pos, 'a');
|
line.push('a');
|
||||||
let pos = offset + pos;
|
|
||||||
let config = self.engine_state.get_config();
|
let config = self.engine_state.get_config();
|
||||||
|
|
||||||
let output = parse(&mut working_set, Some("completer"), line.as_bytes(), false);
|
let output = parse(&mut working_set, Some("completer"), line.as_bytes(), false);
|
||||||
|
|
||||||
for pipeline in output.pipelines.into_iter() {
|
for pipeline in &output.pipelines {
|
||||||
for pipeline_element in pipeline.elements {
|
for pipeline_element in &pipeline.elements {
|
||||||
match pipeline_element {
|
let flattened = flatten_pipeline_element(&working_set, pipeline_element);
|
||||||
PipelineElement::Expression(_, expr)
|
let mut spans: Vec<String> = vec![];
|
||||||
| PipelineElement::Redirection(_, _, expr, _)
|
|
||||||
| PipelineElement::And(_, expr)
|
|
||||||
| PipelineElement::Or(_, expr)
|
|
||||||
| PipelineElement::SameTargetRedirection { cmd: (_, expr), .. }
|
|
||||||
| PipelineElement::SeparateRedirection {
|
|
||||||
out: (_, expr, _), ..
|
|
||||||
} => {
|
|
||||||
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
|
||||||
let mut spans: Vec<String> = vec![];
|
|
||||||
|
|
||||||
for (flat_idx, flat) in flattened.iter().enumerate() {
|
for (flat_idx, flat) in flattened.iter().enumerate() {
|
||||||
let is_passthrough_command = spans
|
let is_passthrough_command = spans
|
||||||
.first()
|
.first()
|
||||||
.filter(|content| {
|
.filter(|content| content.as_str() == "sudo" || content.as_str() == "doas")
|
||||||
content.as_str() == "sudo" || content.as_str() == "doas"
|
.is_some();
|
||||||
})
|
// Read the current spam to string
|
||||||
.is_some();
|
let current_span = working_set.get_span_contents(flat.0).to_vec();
|
||||||
// Read the current spam to string
|
let current_span_str = String::from_utf8_lossy(¤t_span);
|
||||||
let current_span = working_set.get_span_contents(flat.0).to_vec();
|
|
||||||
let current_span_str = String::from_utf8_lossy(¤t_span);
|
|
||||||
|
|
||||||
let is_last_span = pos >= flat.0.start && pos < flat.0.end;
|
let is_last_span = pos >= flat.0.start && pos < flat.0.end;
|
||||||
|
|
||||||
// Skip the last 'a' as span item
|
// Skip the last 'a' as span item
|
||||||
if is_last_span {
|
if is_last_span {
|
||||||
let offset = pos - flat.0.start;
|
let offset = pos - flat.0.start;
|
||||||
if offset == 0 {
|
if offset == 0 {
|
||||||
spans.push(String::new())
|
spans.push(String::new())
|
||||||
} else {
|
} else {
|
||||||
let mut current_span_str = current_span_str.to_string();
|
let mut current_span_str = current_span_str.to_string();
|
||||||
current_span_str.remove(offset);
|
current_span_str.remove(offset);
|
||||||
spans.push(current_span_str);
|
spans.push(current_span_str);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
spans.push(current_span_str.to_string());
|
spans.push(current_span_str.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete based on the last span
|
||||||
|
if is_last_span {
|
||||||
|
// Context variables
|
||||||
|
let most_left_var =
|
||||||
|
most_left_variable(flat_idx, &working_set, flattened.clone());
|
||||||
|
|
||||||
|
// Create a new span
|
||||||
|
let new_span = Span::new(flat.0.start, flat.0.end - 1);
|
||||||
|
|
||||||
|
// Parses the prefix. Completion should look up to the cursor position, not after.
|
||||||
|
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
||||||
|
let index = pos - flat.0.start;
|
||||||
|
prefix.drain(index..);
|
||||||
|
|
||||||
|
// Variables completion
|
||||||
|
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
||||||
|
let mut completer = VariableCompletion::new(
|
||||||
|
self.engine_state.clone(),
|
||||||
|
self.stack.clone(),
|
||||||
|
most_left_var.unwrap_or((vec![], vec![])),
|
||||||
|
);
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
fake_offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flags completion
|
||||||
|
if prefix.starts_with(b"-") {
|
||||||
|
// Try to complete flag internally
|
||||||
|
let mut completer = FlagCompletion::new(pipeline_element.expr.clone());
|
||||||
|
let result = self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix.clone(),
|
||||||
|
new_span,
|
||||||
|
fake_offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !result.is_empty() {
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complete based on the last span
|
// We got no results for internal completion
|
||||||
if is_last_span {
|
// now we can check if external completer is set and use it
|
||||||
// Context variables
|
if let Some(closure) = config.external_completer.as_ref() {
|
||||||
let most_left_var =
|
if let Some(external_result) =
|
||||||
most_left_variable(flat_idx, &working_set, flattened.clone());
|
self.external_completion(closure, &spans, fake_offset, new_span)
|
||||||
|
|
||||||
// Create a new span
|
|
||||||
let new_span = Span::new(flat.0.start, flat.0.end - 1);
|
|
||||||
|
|
||||||
// Parses the prefix. Completion should look up to the cursor position, not after.
|
|
||||||
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
|
||||||
let index = pos - flat.0.start;
|
|
||||||
prefix.drain(index..);
|
|
||||||
|
|
||||||
// Variables completion
|
|
||||||
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
|
||||||
let mut completer = VariableCompletion::new(
|
|
||||||
self.engine_state.clone(),
|
|
||||||
self.stack.clone(),
|
|
||||||
most_left_var.unwrap_or((vec![], vec![])),
|
|
||||||
);
|
|
||||||
|
|
||||||
return self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flags completion
|
|
||||||
if prefix.starts_with(b"-") {
|
|
||||||
// Try to complete flag internally
|
|
||||||
let mut completer = FlagCompletion::new(expr.clone());
|
|
||||||
let result = self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix.clone(),
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
|
|
||||||
if !result.is_empty() {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We got no results for internal completion
|
|
||||||
// now we can check if external completer is set and use it
|
|
||||||
if let Some(block_id) = config.external_completer {
|
|
||||||
if let Some(external_result) = self
|
|
||||||
.external_completion(block_id, &spans, offset, new_span)
|
|
||||||
{
|
|
||||||
return external_result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// specially check if it is currently empty - always complete commands
|
|
||||||
if (is_passthrough_command && flat_idx == 1)
|
|
||||||
|| (flat_idx == 0
|
|
||||||
&& working_set.get_span_contents(new_span).is_empty())
|
|
||||||
{
|
{
|
||||||
let mut completer = CommandCompletion::new(
|
return external_result;
|
||||||
self.engine_state.clone(),
|
|
||||||
&working_set,
|
|
||||||
flattened.clone(),
|
|
||||||
// flat_idx,
|
|
||||||
FlatShape::String,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
return self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Completions that depends on the previous expression (e.g: use, source-env)
|
|
||||||
if (is_passthrough_command && flat_idx > 1) || flat_idx > 0 {
|
|
||||||
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
|
|
||||||
// Read the content for the previous expression
|
|
||||||
let prev_expr_str =
|
|
||||||
working_set.get_span_contents(previous_expr.0).to_vec();
|
|
||||||
|
|
||||||
// Completion for .nu files
|
|
||||||
if prev_expr_str == b"use"
|
|
||||||
|| prev_expr_str == b"overlay use"
|
|
||||||
|| prev_expr_str == b"source-env"
|
|
||||||
{
|
|
||||||
let mut completer =
|
|
||||||
DotNuCompletion::new(self.engine_state.clone());
|
|
||||||
|
|
||||||
return self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
} else if prev_expr_str == b"ls" {
|
|
||||||
let mut completer =
|
|
||||||
FileCompletion::new(self.engine_state.clone());
|
|
||||||
|
|
||||||
return self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match other types
|
|
||||||
match &flat.1 {
|
|
||||||
FlatShape::Custom(decl_id) => {
|
|
||||||
let mut completer = CustomCompletion::new(
|
|
||||||
self.engine_state.clone(),
|
|
||||||
self.stack.clone(),
|
|
||||||
*decl_id,
|
|
||||||
initial_line,
|
|
||||||
);
|
|
||||||
|
|
||||||
return self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
FlatShape::Directory => {
|
|
||||||
let mut completer =
|
|
||||||
DirectoryCompletion::new(self.engine_state.clone());
|
|
||||||
|
|
||||||
return self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
FlatShape::Filepath | FlatShape::GlobPattern => {
|
|
||||||
let mut completer =
|
|
||||||
FileCompletion::new(self.engine_state.clone());
|
|
||||||
|
|
||||||
return self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
flat_shape => {
|
|
||||||
let mut completer = CommandCompletion::new(
|
|
||||||
self.engine_state.clone(),
|
|
||||||
&working_set,
|
|
||||||
flattened.clone(),
|
|
||||||
// flat_idx,
|
|
||||||
flat_shape.clone(),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut out: Vec<_> = self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix.clone(),
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
|
|
||||||
if !out.is_empty() {
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to complete using an external completer (if set)
|
|
||||||
if let Some(block_id) = config.external_completer {
|
|
||||||
if let Some(external_result) = self.external_completion(
|
|
||||||
block_id, &spans, offset, new_span,
|
|
||||||
) {
|
|
||||||
return external_result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for file completion
|
|
||||||
let mut completer =
|
|
||||||
FileCompletion::new(self.engine_state.clone());
|
|
||||||
out = self.process_completion(
|
|
||||||
&mut completer,
|
|
||||||
&working_set,
|
|
||||||
prefix,
|
|
||||||
new_span,
|
|
||||||
offset,
|
|
||||||
pos,
|
|
||||||
);
|
|
||||||
|
|
||||||
if !out.is_empty() {
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// specially check if it is currently empty - always complete commands
|
||||||
|
if (is_passthrough_command && flat_idx == 1)
|
||||||
|
|| (flat_idx == 0 && working_set.get_span_contents(new_span).is_empty())
|
||||||
|
{
|
||||||
|
let mut completer = CommandCompletion::new(
|
||||||
|
self.engine_state.clone(),
|
||||||
|
&working_set,
|
||||||
|
flattened.clone(),
|
||||||
|
// flat_idx,
|
||||||
|
FlatShape::String,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
fake_offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Completions that depends on the previous expression (e.g: use, source-env)
|
||||||
|
if (is_passthrough_command && flat_idx > 1) || flat_idx > 0 {
|
||||||
|
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
|
||||||
|
// Read the content for the previous expression
|
||||||
|
let prev_expr_str =
|
||||||
|
working_set.get_span_contents(previous_expr.0).to_vec();
|
||||||
|
|
||||||
|
// Completion for .nu files
|
||||||
|
if prev_expr_str == b"use"
|
||||||
|
|| prev_expr_str == b"overlay use"
|
||||||
|
|| prev_expr_str == b"source-env"
|
||||||
|
{
|
||||||
|
let mut completer = DotNuCompletion::new(
|
||||||
|
self.engine_state.clone(),
|
||||||
|
self.stack.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
fake_offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
} else if prev_expr_str == b"ls" {
|
||||||
|
let mut completer = FileCompletion::new(
|
||||||
|
self.engine_state.clone(),
|
||||||
|
self.stack.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
fake_offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match other types
|
||||||
|
match &flat.1 {
|
||||||
|
FlatShape::Custom(decl_id) => {
|
||||||
|
let mut completer = CustomCompletion::new(
|
||||||
|
self.engine_state.clone(),
|
||||||
|
self.stack.clone(),
|
||||||
|
*decl_id,
|
||||||
|
initial_line,
|
||||||
|
);
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
fake_offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
FlatShape::Directory => {
|
||||||
|
let mut completer = DirectoryCompletion::new(
|
||||||
|
self.engine_state.clone(),
|
||||||
|
self.stack.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
fake_offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
FlatShape::Filepath | FlatShape::GlobPattern => {
|
||||||
|
let mut completer = FileCompletion::new(
|
||||||
|
self.engine_state.clone(),
|
||||||
|
self.stack.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
fake_offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
flat_shape => {
|
||||||
|
let mut completer = CommandCompletion::new(
|
||||||
|
self.engine_state.clone(),
|
||||||
|
&working_set,
|
||||||
|
flattened.clone(),
|
||||||
|
// flat_idx,
|
||||||
|
flat_shape.clone(),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut out: Vec<_> = self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix.clone(),
|
||||||
|
new_span,
|
||||||
|
fake_offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !out.is_empty() {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to complete using an external completer (if set)
|
||||||
|
if let Some(closure) = config.external_completer.as_ref() {
|
||||||
|
if let Some(external_result) = self.external_completion(
|
||||||
|
closure,
|
||||||
|
&spans,
|
||||||
|
fake_offset,
|
||||||
|
new_span,
|
||||||
|
) {
|
||||||
|
return external_result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for file completion
|
||||||
|
let mut completer = FileCompletion::new(
|
||||||
|
self.engine_state.clone(),
|
||||||
|
self.stack.clone(),
|
||||||
|
);
|
||||||
|
out = self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
fake_offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !out.is_empty() {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -389,6 +399,9 @@ impl NuCompleter {
|
|||||||
impl ReedlineCompleter for NuCompleter {
|
impl ReedlineCompleter for NuCompleter {
|
||||||
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||||
self.completion_helper(line, pos)
|
self.completion_helper(line, pos)
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| s.suggestion)
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,19 +459,23 @@ pub fn map_value_completions<'a>(
|
|||||||
list: impl Iterator<Item = &'a Value>,
|
list: impl Iterator<Item = &'a Value>,
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
list.filter_map(move |x| {
|
list.filter_map(move |x| {
|
||||||
// Match for string values
|
// Match for string values
|
||||||
if let Ok(s) = x.as_string() {
|
if let Ok(s) = x.coerce_string() {
|
||||||
return Some(Suggestion {
|
return Some(SemanticSuggestion {
|
||||||
value: s,
|
suggestion: Suggestion {
|
||||||
description: None,
|
value: s,
|
||||||
extra: None,
|
description: None,
|
||||||
span: reedline::Span {
|
style: None,
|
||||||
start: span.start - offset,
|
extra: None,
|
||||||
end: span.end - offset,
|
span: reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
},
|
||||||
|
append_whitespace: false,
|
||||||
},
|
},
|
||||||
append_whitespace: false,
|
kind: Some(SuggestionKind::Type(x.get_type())),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -467,6 +484,7 @@ pub fn map_value_completions<'a>(
|
|||||||
let mut suggestion = Suggestion {
|
let mut suggestion = Suggestion {
|
||||||
value: String::from(""), // Initialize with empty string
|
value: String::from(""), // Initialize with empty string
|
||||||
description: None,
|
description: None,
|
||||||
|
style: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: span.start - offset,
|
start: span.start - offset,
|
||||||
@ -480,7 +498,7 @@ pub fn map_value_completions<'a>(
|
|||||||
// Match `value` column
|
// Match `value` column
|
||||||
if it.0 == "value" {
|
if it.0 == "value" {
|
||||||
// Convert the value to string
|
// Convert the value to string
|
||||||
if let Ok(val_str) = it.1.as_string() {
|
if let Ok(val_str) = it.1.coerce_string() {
|
||||||
// Update the suggestion value
|
// Update the suggestion value
|
||||||
suggestion.value = val_str;
|
suggestion.value = val_str;
|
||||||
}
|
}
|
||||||
@ -489,14 +507,27 @@ pub fn map_value_completions<'a>(
|
|||||||
// Match `description` column
|
// Match `description` column
|
||||||
if it.0 == "description" {
|
if it.0 == "description" {
|
||||||
// Convert the value to string
|
// Convert the value to string
|
||||||
if let Ok(desc_str) = it.1.as_string() {
|
if let Ok(desc_str) = it.1.coerce_string() {
|
||||||
// Update the suggestion value
|
// Update the suggestion value
|
||||||
suggestion.description = Some(desc_str);
|
suggestion.description = Some(desc_str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Match `style` column
|
||||||
|
if it.0 == "style" {
|
||||||
|
// Convert the value to string
|
||||||
|
suggestion.style = match it.1 {
|
||||||
|
Value::String { val, .. } => Some(lookup_ansi_color_style(val)),
|
||||||
|
Value::Record { .. } => Some(color_record_to_nustyle(it.1)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return Some(suggestion);
|
return Some(SemanticSuggestion {
|
||||||
|
suggestion,
|
||||||
|
kind: Some(SuggestionKind::Type(x.get_type())),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
@ -527,7 +558,7 @@ mod completer_tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let mut completer = NuCompleter::new(engine_state.into(), Stack::new());
|
let mut completer = NuCompleter::new(engine_state.into(), Stack::new());
|
||||||
let dataset = vec![
|
let dataset = [
|
||||||
("sudo", false, "", Vec::new()),
|
("sudo", false, "", Vec::new()),
|
||||||
("sudo l", true, "l", vec!["ls", "let", "lines", "loop"]),
|
("sudo l", true, "l", vec!["ls", "let", "lines", "loop"]),
|
||||||
(" sudo", false, "", Vec::new()),
|
(" sudo", false, "", Vec::new()),
|
||||||
@ -548,13 +579,13 @@ mod completer_tests {
|
|||||||
// Test whether the result begins with the expected value
|
// Test whether the result begins with the expected value
|
||||||
result
|
result
|
||||||
.iter()
|
.iter()
|
||||||
.for_each(|x| assert!(x.value.starts_with(begins_with)));
|
.for_each(|x| assert!(x.suggestion.value.starts_with(begins_with)));
|
||||||
|
|
||||||
// Test whether the result contains all the expected values
|
// Test whether the result contains all the expected values
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result
|
result
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| expected_values.contains(&x.value.as_str()))
|
.map(|x| expected_values.contains(&x.suggestion.value.as_str()))
|
||||||
.filter(|x| *x)
|
.filter(|x| *x)
|
||||||
.count(),
|
.count(),
|
||||||
expected_values.len(),
|
expected_values.len(),
|
||||||
|
@ -1,7 +1,16 @@
|
|||||||
use crate::completions::{matches, CompletionOptions};
|
use crate::completions::{matches, CompletionOptions};
|
||||||
|
use nu_ansi_term::Style;
|
||||||
|
use nu_engine::env_to_string;
|
||||||
use nu_path::home_dir;
|
use nu_path::home_dir;
|
||||||
use nu_protocol::{engine::StateWorkingSet, Span};
|
use nu_protocol::{
|
||||||
use std::path::{is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP};
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
|
Span,
|
||||||
|
};
|
||||||
|
use nu_utils::get_ls_colors;
|
||||||
|
use std::{
|
||||||
|
ffi::OsStr,
|
||||||
|
path::{is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP},
|
||||||
|
};
|
||||||
|
|
||||||
fn complete_rec(
|
fn complete_rec(
|
||||||
partial: &[String],
|
partial: &[String],
|
||||||
@ -92,12 +101,31 @@ pub fn complete_item(
|
|||||||
partial: &str,
|
partial: &str,
|
||||||
cwd: &str,
|
cwd: &str,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<(nu_protocol::Span, String)> {
|
engine_state: &EngineState,
|
||||||
|
stack: &Stack,
|
||||||
|
) -> Vec<(nu_protocol::Span, String, Option<Style>)> {
|
||||||
let partial = surround_remove(partial);
|
let partial = surround_remove(partial);
|
||||||
let isdir = partial.ends_with(is_separator);
|
let isdir = partial.ends_with(is_separator);
|
||||||
let cwd_pathbuf = Path::new(cwd).to_path_buf();
|
let cwd_pathbuf = Path::new(cwd).to_path_buf();
|
||||||
|
let ls_colors = (engine_state.config.use_ls_colors_completions
|
||||||
|
&& engine_state.config.use_ansi_coloring)
|
||||||
|
.then(|| {
|
||||||
|
let ls_colors_env_str = match stack.get_env_var(engine_state, "LS_COLORS") {
|
||||||
|
Some(v) => env_to_string("LS_COLORS", &v, engine_state, stack).ok(),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
get_ls_colors(ls_colors_env_str)
|
||||||
|
});
|
||||||
let mut original_cwd = OriginalCwd::None;
|
let mut original_cwd = OriginalCwd::None;
|
||||||
let mut components = Path::new(&partial).components().peekable();
|
let mut components_vec: Vec<Component> = Path::new(&partial).components().collect();
|
||||||
|
|
||||||
|
// Path components that end with a single "." get normalized away,
|
||||||
|
// so if the partial path ends in a literal "." we must add it back in manually
|
||||||
|
if partial.ends_with('.') && partial.len() > 1 {
|
||||||
|
components_vec.push(Component::Normal(OsStr::new(".")));
|
||||||
|
};
|
||||||
|
let mut components = components_vec.into_iter().peekable();
|
||||||
|
|
||||||
let mut cwd = match components.peek().cloned() {
|
let mut cwd = match components.peek().cloned() {
|
||||||
Some(c @ Component::Prefix(..)) => {
|
Some(c @ Component::Prefix(..)) => {
|
||||||
// windows only by definition
|
// windows only by definition
|
||||||
@ -148,12 +176,35 @@ pub fn complete_item(
|
|||||||
|
|
||||||
complete_rec(partial.as_slice(), &cwd, options, want_directory, isdir)
|
complete_rec(partial.as_slice(), &cwd, options, want_directory, isdir)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|p| (span, escape_path(original_cwd.apply(&p), want_directory)))
|
.map(|p| {
|
||||||
|
let path = original_cwd.apply(&p);
|
||||||
|
let style = ls_colors.as_ref().map(|lsc| {
|
||||||
|
lsc.style_for_path_with_metadata(
|
||||||
|
&path,
|
||||||
|
std::fs::symlink_metadata(&path).ok().as_ref(),
|
||||||
|
)
|
||||||
|
.map(lscolors::Style::to_nu_ansi_term_style)
|
||||||
|
.unwrap_or_default()
|
||||||
|
});
|
||||||
|
(span, escape_path(path, want_directory), style)
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix files or folders with quotes or hashes
|
// Fix files or folders with quotes or hashes
|
||||||
pub fn escape_path(path: String, dir: bool) -> String {
|
pub fn escape_path(path: String, dir: bool) -> String {
|
||||||
|
// make glob pattern have the highest priority.
|
||||||
|
let glob_contaminated = path.contains(['[', '*', ']', '?']);
|
||||||
|
if glob_contaminated {
|
||||||
|
return if path.contains('\'') {
|
||||||
|
// decide to use double quote, also need to escape `"` in path
|
||||||
|
// or else users can't do anything with completed path either.
|
||||||
|
format!("\"{}\"", path.replace('"', r#"\""#))
|
||||||
|
} else {
|
||||||
|
format!("'{path}'")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let filename_contaminated = !dir && path.contains(['\'', '"', ' ', '#', '(', ')']);
|
let filename_contaminated = !dir && path.contains(['\'', '"', ' ', '#', '(', ')']);
|
||||||
let dirname_contaminated = dir && path.contains(['\'', '"', ' ', '#']);
|
let dirname_contaminated = dir && path.contains(['\'', '"', ' ', '#']);
|
||||||
let maybe_flag = path.starts_with('-');
|
let maybe_flag = path.starts_with('-');
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
||||||
use nu_parser::trim_quotes_str;
|
use nu_parser::trim_quotes_str;
|
||||||
use nu_protocol::CompletionAlgorithm;
|
use nu_protocol::CompletionAlgorithm;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum SortBy {
|
pub enum SortBy {
|
||||||
@ -96,7 +95,6 @@ impl std::error::Error for InvalidMatchAlgorithm {}
|
|||||||
pub struct CompletionOptions {
|
pub struct CompletionOptions {
|
||||||
pub case_sensitive: bool,
|
pub case_sensitive: bool,
|
||||||
pub positional: bool,
|
pub positional: bool,
|
||||||
pub sort_by: SortBy,
|
|
||||||
pub match_algorithm: MatchAlgorithm,
|
pub match_algorithm: MatchAlgorithm,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +103,6 @@ impl Default for CompletionOptions {
|
|||||||
Self {
|
Self {
|
||||||
case_sensitive: true,
|
case_sensitive: true,
|
||||||
positional: true,
|
positional: true,
|
||||||
sort_by: SortBy::Ascending,
|
|
||||||
match_algorithm: MatchAlgorithm::Prefix,
|
match_algorithm: MatchAlgorithm::Prefix,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
use crate::completions::{Completer, CompletionOptions, MatchAlgorithm, SortBy};
|
use crate::completions::{
|
||||||
|
completer::map_value_completions, Completer, CompletionOptions, MatchAlgorithm,
|
||||||
|
SemanticSuggestion, SortBy,
|
||||||
|
};
|
||||||
use nu_engine::eval_call;
|
use nu_engine::eval_call;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Argument, Call, Expr, Expression},
|
ast::{Argument, Call, Expr, Expression},
|
||||||
|
debugger::WithoutDebug,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
PipelineData, Span, Type, Value,
|
PipelineData, Span, Type, Value,
|
||||||
};
|
};
|
||||||
use nu_utils::IgnoreCaseExt;
|
use nu_utils::IgnoreCaseExt;
|
||||||
use reedline::Suggestion;
|
use std::{collections::HashMap, sync::Arc};
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use super::completer::map_value_completions;
|
|
||||||
|
|
||||||
pub struct CustomCompletion {
|
pub struct CustomCompletion {
|
||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
@ -24,7 +24,7 @@ impl CustomCompletion {
|
|||||||
pub fn new(engine_state: Arc<EngineState>, stack: Stack, decl_id: usize, line: String) -> Self {
|
pub fn new(engine_state: Arc<EngineState>, stack: Stack, decl_id: usize, line: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack: stack.reset_out_dest().capture(),
|
||||||
decl_id,
|
decl_id,
|
||||||
line,
|
line,
|
||||||
sort_by: SortBy::None,
|
sort_by: SortBy::None,
|
||||||
@ -41,12 +41,12 @@ impl Completer for CustomCompletion {
|
|||||||
offset: usize,
|
offset: usize,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
completion_options: &CompletionOptions,
|
completion_options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
// Line position
|
// Line position
|
||||||
let line_pos = pos - offset;
|
let line_pos = pos - offset;
|
||||||
|
|
||||||
// Call custom declaration
|
// Call custom declaration
|
||||||
let result = eval_call(
|
let result = eval_call::<WithoutDebug>(
|
||||||
&self.engine_state,
|
&self.engine_state,
|
||||||
&mut self.stack,
|
&mut self.stack,
|
||||||
&Call {
|
&Call {
|
||||||
@ -66,8 +66,6 @@ impl Completer for CustomCompletion {
|
|||||||
custom_completion: None,
|
custom_completion: None,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
redirect_stdout: true,
|
|
||||||
redirect_stderr: true,
|
|
||||||
parser_info: HashMap::new(),
|
parser_info: HashMap::new(),
|
||||||
},
|
},
|
||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
@ -110,14 +108,9 @@ impl Completer for CustomCompletion {
|
|||||||
.get("positional")
|
.get("positional")
|
||||||
.and_then(|val| val.as_bool().ok())
|
.and_then(|val| val.as_bool().ok())
|
||||||
.unwrap_or(true),
|
.unwrap_or(true),
|
||||||
sort_by: if should_sort {
|
|
||||||
SortBy::Ascending
|
|
||||||
} else {
|
|
||||||
SortBy::None
|
|
||||||
},
|
|
||||||
match_algorithm: match options.get("completion_algorithm") {
|
match_algorithm: match options.get("completion_algorithm") {
|
||||||
Some(option) => option
|
Some(option) => option
|
||||||
.as_string()
|
.coerce_string()
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|option| option.try_into().ok())
|
.and_then(|option| option.try_into().ok())
|
||||||
.unwrap_or(MatchAlgorithm::Prefix),
|
.unwrap_or(MatchAlgorithm::Prefix),
|
||||||
@ -146,15 +139,22 @@ impl Completer for CustomCompletion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn filter(prefix: &[u8], items: Vec<Suggestion>, options: &CompletionOptions) -> Vec<Suggestion> {
|
fn filter(
|
||||||
|
prefix: &[u8],
|
||||||
|
items: Vec<SemanticSuggestion>,
|
||||||
|
options: &CompletionOptions,
|
||||||
|
) -> Vec<SemanticSuggestion> {
|
||||||
items
|
items
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|it| match options.match_algorithm {
|
.filter(|it| match options.match_algorithm {
|
||||||
MatchAlgorithm::Prefix => match (options.case_sensitive, options.positional) {
|
MatchAlgorithm::Prefix => match (options.case_sensitive, options.positional) {
|
||||||
(true, true) => it.value.as_bytes().starts_with(prefix),
|
(true, true) => it.suggestion.value.as_bytes().starts_with(prefix),
|
||||||
(true, false) => it.value.contains(std::str::from_utf8(prefix).unwrap_or("")),
|
(true, false) => it
|
||||||
|
.suggestion
|
||||||
|
.value
|
||||||
|
.contains(std::str::from_utf8(prefix).unwrap_or("")),
|
||||||
(false, positional) => {
|
(false, positional) => {
|
||||||
let value = it.value.to_folded_case();
|
let value = it.suggestion.value.to_folded_case();
|
||||||
let prefix = std::str::from_utf8(prefix).unwrap_or("").to_folded_case();
|
let prefix = std::str::from_utf8(prefix).unwrap_or("").to_folded_case();
|
||||||
if positional {
|
if positional {
|
||||||
value.starts_with(&prefix)
|
value.starts_with(&prefix)
|
||||||
@ -165,7 +165,7 @@ fn filter(prefix: &[u8], items: Vec<Suggestion>, options: &CompletionOptions) ->
|
|||||||
},
|
},
|
||||||
MatchAlgorithm::Fuzzy => options
|
MatchAlgorithm::Fuzzy => options
|
||||||
.match_algorithm
|
.match_algorithm
|
||||||
.matches_u8(it.value.as_bytes(), prefix),
|
.matches_u8(it.suggestion.value.as_bytes(), prefix),
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -2,22 +2,31 @@ use crate::completions::{
|
|||||||
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
|
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
|
||||||
Completer, CompletionOptions, SortBy,
|
Completer, CompletionOptions, SortBy,
|
||||||
};
|
};
|
||||||
|
use nu_ansi_term::Style;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
levenshtein_distance, Span,
|
levenshtein_distance, Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::path::{Path, MAIN_SEPARATOR as SEP};
|
use std::{
|
||||||
use std::sync::Arc;
|
path::{Path, MAIN_SEPARATOR as SEP},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::SemanticSuggestion;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DirectoryCompletion {
|
pub struct DirectoryCompletion {
|
||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
|
stack: Stack,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirectoryCompletion {
|
impl DirectoryCompletion {
|
||||||
pub fn new(engine_state: Arc<EngineState>) -> Self {
|
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
|
||||||
Self { engine_state }
|
Self {
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,7 +39,7 @@ impl Completer for DirectoryCompletion {
|
|||||||
offset: usize,
|
offset: usize,
|
||||||
_: usize,
|
_: usize,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let AdjustView { prefix, span, .. } = adjust_if_intermediate(&prefix, working_set, span);
|
let AdjustView { prefix, span, .. } = adjust_if_intermediate(&prefix, working_set, span);
|
||||||
|
|
||||||
// Filter only the folders
|
// Filter only the folders
|
||||||
@ -39,17 +48,24 @@ impl Completer for DirectoryCompletion {
|
|||||||
&prefix,
|
&prefix,
|
||||||
&self.engine_state.current_work_dir(),
|
&self.engine_state.current_work_dir(),
|
||||||
options,
|
options,
|
||||||
|
self.engine_state.as_ref(),
|
||||||
|
&self.stack,
|
||||||
)
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |x| Suggestion {
|
.map(move |x| SemanticSuggestion {
|
||||||
value: x.1,
|
suggestion: Suggestion {
|
||||||
description: None,
|
value: x.1,
|
||||||
extra: None,
|
description: None,
|
||||||
span: reedline::Span {
|
style: x.2,
|
||||||
start: x.0.start - offset,
|
extra: None,
|
||||||
end: x.0.end - offset,
|
span: reedline::Span {
|
||||||
|
start: x.0.start - offset,
|
||||||
|
end: x.0.end - offset,
|
||||||
|
},
|
||||||
|
append_whitespace: false,
|
||||||
},
|
},
|
||||||
append_whitespace: false,
|
// TODO????
|
||||||
|
kind: None,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@ -57,7 +73,7 @@ impl Completer for DirectoryCompletion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort results prioritizing the non hidden folders
|
// Sort results prioritizing the non hidden folders
|
||||||
fn sort(&self, items: Vec<Suggestion>, prefix: Vec<u8>) -> Vec<Suggestion> {
|
fn sort(&self, items: Vec<SemanticSuggestion>, prefix: Vec<u8>) -> Vec<SemanticSuggestion> {
|
||||||
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
||||||
|
|
||||||
// Sort items
|
// Sort items
|
||||||
@ -67,15 +83,16 @@ impl Completer for DirectoryCompletion {
|
|||||||
SortBy::Ascending => {
|
SortBy::Ascending => {
|
||||||
sorted_items.sort_by(|a, b| {
|
sorted_items.sort_by(|a, b| {
|
||||||
// Ignore trailing slashes in folder names when sorting
|
// Ignore trailing slashes in folder names when sorting
|
||||||
a.value
|
a.suggestion
|
||||||
|
.value
|
||||||
.trim_end_matches(SEP)
|
.trim_end_matches(SEP)
|
||||||
.cmp(b.value.trim_end_matches(SEP))
|
.cmp(b.suggestion.value.trim_end_matches(SEP))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
SortBy::LevenshteinDistance => {
|
SortBy::LevenshteinDistance => {
|
||||||
sorted_items.sort_by(|a, b| {
|
sorted_items.sort_by(|a, b| {
|
||||||
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
let a_distance = levenshtein_distance(&prefix_str, &a.suggestion.value);
|
||||||
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
let b_distance = levenshtein_distance(&prefix_str, &b.suggestion.value);
|
||||||
a_distance.cmp(&b_distance)
|
a_distance.cmp(&b_distance)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -83,11 +100,11 @@ impl Completer for DirectoryCompletion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Separate the results between hidden and non hidden
|
// Separate the results between hidden and non hidden
|
||||||
let mut hidden: Vec<Suggestion> = vec![];
|
let mut hidden: Vec<SemanticSuggestion> = vec![];
|
||||||
let mut non_hidden: Vec<Suggestion> = vec![];
|
let mut non_hidden: Vec<SemanticSuggestion> = vec![];
|
||||||
|
|
||||||
for item in sorted_items.into_iter() {
|
for item in sorted_items.into_iter() {
|
||||||
let item_path = Path::new(&item.value);
|
let item_path = Path::new(&item.suggestion.value);
|
||||||
|
|
||||||
if let Some(value) = item_path.file_name() {
|
if let Some(value) = item_path.file_name() {
|
||||||
if let Some(value) = value.to_str() {
|
if let Some(value) = value.to_str() {
|
||||||
@ -112,6 +129,8 @@ pub fn directory_completion(
|
|||||||
partial: &str,
|
partial: &str,
|
||||||
cwd: &str,
|
cwd: &str,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<(nu_protocol::Span, String)> {
|
engine_state: &EngineState,
|
||||||
complete_item(true, span, partial, cwd, options)
|
stack: &Stack,
|
||||||
|
) -> Vec<(nu_protocol::Span, String, Option<Style>)> {
|
||||||
|
complete_item(true, span, partial, cwd, options, engine_state, stack)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::completions::{file_path_completion, Completer, CompletionOptions, SortBy};
|
use crate::completions::{file_path_completion, Completer, CompletionOptions, SortBy};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
Span,
|
Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
@ -9,14 +9,20 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::SemanticSuggestion;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DotNuCompletion {
|
pub struct DotNuCompletion {
|
||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
|
stack: Stack,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DotNuCompletion {
|
impl DotNuCompletion {
|
||||||
pub fn new(engine_state: Arc<EngineState>) -> Self {
|
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
|
||||||
Self { engine_state }
|
Self {
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +35,7 @@ impl Completer for DotNuCompletion {
|
|||||||
offset: usize,
|
offset: usize,
|
||||||
_: usize,
|
_: usize,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let prefix_str = String::from_utf8_lossy(&prefix).replace('`', "");
|
let prefix_str = String::from_utf8_lossy(&prefix).replace('`', "");
|
||||||
let mut search_dirs: Vec<String> = vec![];
|
let mut search_dirs: Vec<String> = vec![];
|
||||||
|
|
||||||
@ -50,7 +56,7 @@ impl Completer for DotNuCompletion {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|it| {
|
.flat_map(|it| {
|
||||||
it.iter().map(|x| {
|
it.iter().map(|x| {
|
||||||
x.as_path()
|
x.to_path()
|
||||||
.expect("internal error: failed to convert lib path")
|
.expect("internal error: failed to convert lib path")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -89,10 +95,17 @@ impl Completer for DotNuCompletion {
|
|||||||
|
|
||||||
// Fetch the files filtering the ones that ends with .nu
|
// Fetch the files filtering the ones that ends with .nu
|
||||||
// and transform them into suggestions
|
// and transform them into suggestions
|
||||||
let output: Vec<Suggestion> = search_dirs
|
let output: Vec<SemanticSuggestion> = search_dirs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|search_dir| {
|
.flat_map(|search_dir| {
|
||||||
let completions = file_path_completion(span, &partial, &search_dir, options);
|
let completions = file_path_completion(
|
||||||
|
span,
|
||||||
|
&partial,
|
||||||
|
&search_dir,
|
||||||
|
options,
|
||||||
|
self.engine_state.as_ref(),
|
||||||
|
&self.stack,
|
||||||
|
);
|
||||||
completions
|
completions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(move |it| {
|
.filter(move |it| {
|
||||||
@ -108,15 +121,20 @@ impl Completer for DotNuCompletion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(move |x| Suggestion {
|
.map(move |x| SemanticSuggestion {
|
||||||
value: x.1,
|
suggestion: Suggestion {
|
||||||
description: None,
|
value: x.1,
|
||||||
extra: None,
|
description: None,
|
||||||
span: reedline::Span {
|
style: x.2,
|
||||||
start: x.0.start - offset,
|
extra: None,
|
||||||
end: x.0.end - offset,
|
span: reedline::Span {
|
||||||
|
start: x.0.start - offset,
|
||||||
|
end: x.0.end - offset,
|
||||||
|
},
|
||||||
|
append_whitespace: true,
|
||||||
},
|
},
|
||||||
append_whitespace: true,
|
// TODO????
|
||||||
|
kind: None,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -2,23 +2,32 @@ use crate::completions::{
|
|||||||
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
|
completion_common::{adjust_if_intermediate, complete_item, AdjustView},
|
||||||
Completer, CompletionOptions, SortBy,
|
Completer, CompletionOptions, SortBy,
|
||||||
};
|
};
|
||||||
|
use nu_ansi_term::Style;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
levenshtein_distance, Span,
|
levenshtein_distance, Span,
|
||||||
};
|
};
|
||||||
use nu_utils::IgnoreCaseExt;
|
use nu_utils::IgnoreCaseExt;
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::path::{Path, MAIN_SEPARATOR as SEP};
|
use std::{
|
||||||
use std::sync::Arc;
|
path::{Path, MAIN_SEPARATOR as SEP},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::SemanticSuggestion;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FileCompletion {
|
pub struct FileCompletion {
|
||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
|
stack: Stack,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileCompletion {
|
impl FileCompletion {
|
||||||
pub fn new(engine_state: Arc<EngineState>) -> Self {
|
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
|
||||||
Self { engine_state }
|
Self {
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,7 +40,7 @@ impl Completer for FileCompletion {
|
|||||||
offset: usize,
|
offset: usize,
|
||||||
_: usize,
|
_: usize,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let AdjustView {
|
let AdjustView {
|
||||||
prefix,
|
prefix,
|
||||||
span,
|
span,
|
||||||
@ -44,17 +53,24 @@ impl Completer for FileCompletion {
|
|||||||
&prefix,
|
&prefix,
|
||||||
&self.engine_state.current_work_dir(),
|
&self.engine_state.current_work_dir(),
|
||||||
options,
|
options,
|
||||||
|
self.engine_state.as_ref(),
|
||||||
|
&self.stack,
|
||||||
)
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |x| Suggestion {
|
.map(move |x| SemanticSuggestion {
|
||||||
value: x.1,
|
suggestion: Suggestion {
|
||||||
description: None,
|
value: x.1,
|
||||||
extra: None,
|
description: None,
|
||||||
span: reedline::Span {
|
style: x.2,
|
||||||
start: x.0.start - offset,
|
extra: None,
|
||||||
end: x.0.end - offset,
|
span: reedline::Span {
|
||||||
|
start: x.0.start - offset,
|
||||||
|
end: x.0.end - offset,
|
||||||
|
},
|
||||||
|
append_whitespace: false,
|
||||||
},
|
},
|
||||||
append_whitespace: false,
|
// TODO????
|
||||||
|
kind: None,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@ -62,7 +78,7 @@ impl Completer for FileCompletion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort results prioritizing the non hidden folders
|
// Sort results prioritizing the non hidden folders
|
||||||
fn sort(&self, items: Vec<Suggestion>, prefix: Vec<u8>) -> Vec<Suggestion> {
|
fn sort(&self, items: Vec<SemanticSuggestion>, prefix: Vec<u8>) -> Vec<SemanticSuggestion> {
|
||||||
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
||||||
|
|
||||||
// Sort items
|
// Sort items
|
||||||
@ -72,15 +88,16 @@ impl Completer for FileCompletion {
|
|||||||
SortBy::Ascending => {
|
SortBy::Ascending => {
|
||||||
sorted_items.sort_by(|a, b| {
|
sorted_items.sort_by(|a, b| {
|
||||||
// Ignore trailing slashes in folder names when sorting
|
// Ignore trailing slashes in folder names when sorting
|
||||||
a.value
|
a.suggestion
|
||||||
|
.value
|
||||||
.trim_end_matches(SEP)
|
.trim_end_matches(SEP)
|
||||||
.cmp(b.value.trim_end_matches(SEP))
|
.cmp(b.suggestion.value.trim_end_matches(SEP))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
SortBy::LevenshteinDistance => {
|
SortBy::LevenshteinDistance => {
|
||||||
sorted_items.sort_by(|a, b| {
|
sorted_items.sort_by(|a, b| {
|
||||||
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
let a_distance = levenshtein_distance(&prefix_str, &a.suggestion.value);
|
||||||
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
let b_distance = levenshtein_distance(&prefix_str, &b.suggestion.value);
|
||||||
a_distance.cmp(&b_distance)
|
a_distance.cmp(&b_distance)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -88,11 +105,11 @@ impl Completer for FileCompletion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Separate the results between hidden and non hidden
|
// Separate the results between hidden and non hidden
|
||||||
let mut hidden: Vec<Suggestion> = vec![];
|
let mut hidden: Vec<SemanticSuggestion> = vec![];
|
||||||
let mut non_hidden: Vec<Suggestion> = vec![];
|
let mut non_hidden: Vec<SemanticSuggestion> = vec![];
|
||||||
|
|
||||||
for item in sorted_items.into_iter() {
|
for item in sorted_items.into_iter() {
|
||||||
let item_path = Path::new(&item.value);
|
let item_path = Path::new(&item.suggestion.value);
|
||||||
|
|
||||||
if let Some(value) = item_path.file_name() {
|
if let Some(value) = item_path.file_name() {
|
||||||
if let Some(value) = value.to_str() {
|
if let Some(value) = value.to_str() {
|
||||||
@ -117,8 +134,10 @@ pub fn file_path_completion(
|
|||||||
partial: &str,
|
partial: &str,
|
||||||
cwd: &str,
|
cwd: &str,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<(nu_protocol::Span, String)> {
|
engine_state: &EngineState,
|
||||||
complete_item(false, span, partial, cwd, options)
|
stack: &Stack,
|
||||||
|
) -> Vec<(nu_protocol::Span, String, Option<Style>)> {
|
||||||
|
complete_item(false, span, partial, cwd, options, engine_state, stack)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn matches(partial: &str, from: &str, options: &CompletionOptions) -> bool {
|
pub fn matches(partial: &str, from: &str, options: &CompletionOptions) -> bool {
|
||||||
|
@ -4,9 +4,10 @@ use nu_protocol::{
|
|||||||
engine::StateWorkingSet,
|
engine::StateWorkingSet,
|
||||||
Span,
|
Span,
|
||||||
};
|
};
|
||||||
|
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
|
|
||||||
|
use super::SemanticSuggestion;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FlagCompletion {
|
pub struct FlagCompletion {
|
||||||
expression: Expression,
|
expression: Expression,
|
||||||
@ -27,7 +28,7 @@ impl Completer for FlagCompletion {
|
|||||||
offset: usize,
|
offset: usize,
|
||||||
_: usize,
|
_: usize,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
// Check if it's a flag
|
// Check if it's a flag
|
||||||
if let Expr::Call(call) = &self.expression.expr {
|
if let Expr::Call(call) = &self.expression.expr {
|
||||||
let decl = working_set.get_decl(call.decl_id);
|
let decl = working_set.get_decl(call.decl_id);
|
||||||
@ -43,15 +44,20 @@ impl Completer for FlagCompletion {
|
|||||||
named.insert(0, b'-');
|
named.insert(0, b'-');
|
||||||
|
|
||||||
if options.match_algorithm.matches_u8(&named, &prefix) {
|
if options.match_algorithm.matches_u8(&named, &prefix) {
|
||||||
output.push(Suggestion {
|
output.push(SemanticSuggestion {
|
||||||
value: String::from_utf8_lossy(&named).to_string(),
|
suggestion: Suggestion {
|
||||||
description: Some(flag_desc.to_string()),
|
value: String::from_utf8_lossy(&named).to_string(),
|
||||||
extra: None,
|
description: Some(flag_desc.to_string()),
|
||||||
span: reedline::Span {
|
style: None,
|
||||||
start: span.start - offset,
|
extra: None,
|
||||||
end: span.end - offset,
|
span: reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
},
|
||||||
|
append_whitespace: true,
|
||||||
},
|
},
|
||||||
append_whitespace: true,
|
// TODO????
|
||||||
|
kind: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,15 +71,20 @@ impl Completer for FlagCompletion {
|
|||||||
named.insert(0, b'-');
|
named.insert(0, b'-');
|
||||||
|
|
||||||
if options.match_algorithm.matches_u8(&named, &prefix) {
|
if options.match_algorithm.matches_u8(&named, &prefix) {
|
||||||
output.push(Suggestion {
|
output.push(SemanticSuggestion {
|
||||||
value: String::from_utf8_lossy(&named).to_string(),
|
suggestion: Suggestion {
|
||||||
description: Some(flag_desc.to_string()),
|
value: String::from_utf8_lossy(&named).to_string(),
|
||||||
extra: None,
|
description: Some(flag_desc.to_string()),
|
||||||
span: reedline::Span {
|
style: None,
|
||||||
start: span.start - offset,
|
extra: None,
|
||||||
end: span.end - offset,
|
span: reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
},
|
||||||
|
append_whitespace: true,
|
||||||
},
|
},
|
||||||
append_whitespace: true,
|
// TODO????
|
||||||
|
kind: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ mod file_completions;
|
|||||||
mod flag_completions;
|
mod flag_completions;
|
||||||
mod variable_completions;
|
mod variable_completions;
|
||||||
|
|
||||||
pub use base::Completer;
|
pub use base::{Completer, SemanticSuggestion, SuggestionKind};
|
||||||
pub use command_completions::CommandCompletion;
|
pub use command_completions::CommandCompletion;
|
||||||
pub use completer::NuCompleter;
|
pub use completer::NuCompleter;
|
||||||
pub use completion_options::{CompletionOptions, MatchAlgorithm, SortBy};
|
pub use completion_options::{CompletionOptions, MatchAlgorithm, SortBy};
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
use crate::completions::{Completer, CompletionOptions};
|
use crate::completions::{
|
||||||
|
Completer, CompletionOptions, MatchAlgorithm, SemanticSuggestion, SuggestionKind,
|
||||||
|
};
|
||||||
use nu_engine::{column::get_columns, eval_variable};
|
use nu_engine::{column::get_columns, eval_variable};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
Span, Value,
|
Span, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::str;
|
use std::{str, sync::Arc};
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use super::MatchAlgorithm;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct VariableCompletion {
|
pub struct VariableCompletion {
|
||||||
@ -41,7 +39,7 @@ impl Completer for VariableCompletion {
|
|||||||
offset: usize,
|
offset: usize,
|
||||||
_: usize,
|
_: usize,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
let builtins = ["$nu", "$in", "$env"];
|
let builtins = ["$nu", "$in", "$env"];
|
||||||
let var_str = std::str::from_utf8(&self.var_context.0).unwrap_or("");
|
let var_str = std::str::from_utf8(&self.var_context.0).unwrap_or("");
|
||||||
@ -70,12 +68,10 @@ impl Completer for VariableCompletion {
|
|||||||
self.var_context.1.clone().into_iter().skip(1).collect();
|
self.var_context.1.clone().into_iter().skip(1).collect();
|
||||||
|
|
||||||
if let Some(val) = env_vars.get(&target_var_str) {
|
if let Some(val) = env_vars.get(&target_var_str) {
|
||||||
for suggestion in
|
for suggestion in nested_suggestions(val, &nested_levels, current_span) {
|
||||||
nested_suggestions(val.clone(), nested_levels, current_span)
|
|
||||||
{
|
|
||||||
if options.match_algorithm.matches_u8_insensitive(
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
options.case_sensitive,
|
options.case_sensitive,
|
||||||
suggestion.value.as_bytes(),
|
suggestion.suggestion.value.as_bytes(),
|
||||||
&prefix,
|
&prefix,
|
||||||
) {
|
) {
|
||||||
output.push(suggestion);
|
output.push(suggestion);
|
||||||
@ -92,12 +88,16 @@ impl Completer for VariableCompletion {
|
|||||||
env_var.0.as_bytes(),
|
env_var.0.as_bytes(),
|
||||||
&prefix,
|
&prefix,
|
||||||
) {
|
) {
|
||||||
output.push(Suggestion {
|
output.push(SemanticSuggestion {
|
||||||
value: env_var.0,
|
suggestion: Suggestion {
|
||||||
description: None,
|
value: env_var.0,
|
||||||
extra: None,
|
description: None,
|
||||||
span: current_span,
|
style: None,
|
||||||
append_whitespace: false,
|
extra: None,
|
||||||
|
span: current_span,
|
||||||
|
append_whitespace: false,
|
||||||
|
},
|
||||||
|
kind: Some(SuggestionKind::Type(env_var.1.get_type())),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,12 +115,11 @@ impl Completer for VariableCompletion {
|
|||||||
nu_protocol::NU_VARIABLE_ID,
|
nu_protocol::NU_VARIABLE_ID,
|
||||||
nu_protocol::Span::new(current_span.start, current_span.end),
|
nu_protocol::Span::new(current_span.start, current_span.end),
|
||||||
) {
|
) {
|
||||||
for suggestion in
|
for suggestion in nested_suggestions(&nuval, &self.var_context.1, current_span)
|
||||||
nested_suggestions(nuval, self.var_context.1.clone(), current_span)
|
|
||||||
{
|
{
|
||||||
if options.match_algorithm.matches_u8_insensitive(
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
options.case_sensitive,
|
options.case_sensitive,
|
||||||
suggestion.value.as_bytes(),
|
suggestion.suggestion.value.as_bytes(),
|
||||||
&prefix,
|
&prefix,
|
||||||
) {
|
) {
|
||||||
output.push(suggestion);
|
output.push(suggestion);
|
||||||
@ -138,12 +137,11 @@ impl Completer for VariableCompletion {
|
|||||||
|
|
||||||
// If the value exists and it's of type Record
|
// If the value exists and it's of type Record
|
||||||
if let Ok(value) = var {
|
if let Ok(value) = var {
|
||||||
for suggestion in
|
for suggestion in nested_suggestions(&value, &self.var_context.1, current_span)
|
||||||
nested_suggestions(value, self.var_context.1.clone(), current_span)
|
|
||||||
{
|
{
|
||||||
if options.match_algorithm.matches_u8_insensitive(
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
options.case_sensitive,
|
options.case_sensitive,
|
||||||
suggestion.value.as_bytes(),
|
suggestion.suggestion.value.as_bytes(),
|
||||||
&prefix,
|
&prefix,
|
||||||
) {
|
) {
|
||||||
output.push(suggestion);
|
output.push(suggestion);
|
||||||
@ -162,12 +160,17 @@ impl Completer for VariableCompletion {
|
|||||||
builtin.as_bytes(),
|
builtin.as_bytes(),
|
||||||
&prefix,
|
&prefix,
|
||||||
) {
|
) {
|
||||||
output.push(Suggestion {
|
output.push(SemanticSuggestion {
|
||||||
value: builtin.to_string(),
|
suggestion: Suggestion {
|
||||||
description: None,
|
value: builtin.to_string(),
|
||||||
extra: None,
|
description: None,
|
||||||
span: current_span,
|
style: None,
|
||||||
append_whitespace: false,
|
extra: None,
|
||||||
|
span: current_span,
|
||||||
|
append_whitespace: false,
|
||||||
|
},
|
||||||
|
// TODO is there a way to get the VarId to get the type???
|
||||||
|
kind: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,12 +187,18 @@ impl Completer for VariableCompletion {
|
|||||||
v.0,
|
v.0,
|
||||||
&prefix,
|
&prefix,
|
||||||
) {
|
) {
|
||||||
output.push(Suggestion {
|
output.push(SemanticSuggestion {
|
||||||
value: String::from_utf8_lossy(v.0).to_string(),
|
suggestion: Suggestion {
|
||||||
description: None,
|
value: String::from_utf8_lossy(v.0).to_string(),
|
||||||
extra: None,
|
description: None,
|
||||||
span: current_span,
|
style: None,
|
||||||
append_whitespace: false,
|
extra: None,
|
||||||
|
span: current_span,
|
||||||
|
append_whitespace: false,
|
||||||
|
},
|
||||||
|
kind: Some(SuggestionKind::Type(
|
||||||
|
working_set.get_variable(*v.1).ty.clone(),
|
||||||
|
)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,12 +214,18 @@ impl Completer for VariableCompletion {
|
|||||||
v.0,
|
v.0,
|
||||||
&prefix,
|
&prefix,
|
||||||
) {
|
) {
|
||||||
output.push(Suggestion {
|
output.push(SemanticSuggestion {
|
||||||
value: String::from_utf8_lossy(v.0).to_string(),
|
suggestion: Suggestion {
|
||||||
description: None,
|
value: String::from_utf8_lossy(v.0).to_string(),
|
||||||
extra: None,
|
description: None,
|
||||||
span: current_span,
|
style: None,
|
||||||
append_whitespace: false,
|
extra: None,
|
||||||
|
span: current_span,
|
||||||
|
append_whitespace: false,
|
||||||
|
},
|
||||||
|
kind: Some(SuggestionKind::Type(
|
||||||
|
working_set.get_variable(*v.1).ty.clone(),
|
||||||
|
)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,23 +240,28 @@ impl Completer for VariableCompletion {
|
|||||||
// Find recursively the values for sublevels
|
// Find recursively the values for sublevels
|
||||||
// if no sublevels are set it returns the current value
|
// if no sublevels are set it returns the current value
|
||||||
fn nested_suggestions(
|
fn nested_suggestions(
|
||||||
val: Value,
|
val: &Value,
|
||||||
sublevels: Vec<Vec<u8>>,
|
sublevels: &[Vec<u8>],
|
||||||
current_span: reedline::Span,
|
current_span: reedline::Span,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let mut output: Vec<Suggestion> = vec![];
|
let mut output: Vec<SemanticSuggestion> = vec![];
|
||||||
let value = recursive_value(val, sublevels);
|
let value = recursive_value(val, sublevels).unwrap_or_else(Value::nothing);
|
||||||
|
|
||||||
|
let kind = SuggestionKind::Type(value.get_type());
|
||||||
match value {
|
match value {
|
||||||
Value::Record { val, .. } => {
|
Value::Record { val, .. } => {
|
||||||
// Add all the columns as completion
|
// Add all the columns as completion
|
||||||
for (col, _) in val.into_iter() {
|
for col in val.columns() {
|
||||||
output.push(Suggestion {
|
output.push(SemanticSuggestion {
|
||||||
value: col,
|
suggestion: Suggestion {
|
||||||
description: None,
|
value: col.clone(),
|
||||||
extra: None,
|
description: None,
|
||||||
span: current_span,
|
style: None,
|
||||||
append_whitespace: false,
|
extra: None,
|
||||||
|
span: current_span,
|
||||||
|
append_whitespace: false,
|
||||||
|
},
|
||||||
|
kind: Some(kind.clone()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,12 +270,16 @@ fn nested_suggestions(
|
|||||||
Value::LazyRecord { val, .. } => {
|
Value::LazyRecord { val, .. } => {
|
||||||
// Add all the columns as completion
|
// Add all the columns as completion
|
||||||
for column_name in val.column_names() {
|
for column_name in val.column_names() {
|
||||||
output.push(Suggestion {
|
output.push(SemanticSuggestion {
|
||||||
value: column_name.to_string(),
|
suggestion: Suggestion {
|
||||||
description: None,
|
value: column_name.to_string(),
|
||||||
extra: None,
|
description: None,
|
||||||
span: current_span,
|
style: None,
|
||||||
append_whitespace: false,
|
extra: None,
|
||||||
|
span: current_span,
|
||||||
|
append_whitespace: false,
|
||||||
|
},
|
||||||
|
kind: Some(kind.clone()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,12 +287,16 @@ fn nested_suggestions(
|
|||||||
}
|
}
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
for column_name in get_columns(vals.as_slice()) {
|
for column_name in get_columns(vals.as_slice()) {
|
||||||
output.push(Suggestion {
|
output.push(SemanticSuggestion {
|
||||||
value: column_name,
|
suggestion: Suggestion {
|
||||||
description: None,
|
value: column_name,
|
||||||
extra: None,
|
description: None,
|
||||||
span: current_span,
|
style: None,
|
||||||
append_whitespace: false,
|
extra: None,
|
||||||
|
span: current_span,
|
||||||
|
append_whitespace: false,
|
||||||
|
},
|
||||||
|
kind: Some(kind.clone()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,56 +307,47 @@ fn nested_suggestions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extracts the recursive value (e.g: $var.a.b.c)
|
// Extracts the recursive value (e.g: $var.a.b.c)
|
||||||
fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
|
fn recursive_value(val: &Value, sublevels: &[Vec<u8>]) -> Result<Value, Span> {
|
||||||
// Go to next sublevel
|
// Go to next sublevel
|
||||||
if let Some(next_sublevel) = sublevels.clone().into_iter().next() {
|
if let Some((sublevel, next_sublevels)) = sublevels.split_first() {
|
||||||
let span = val.span();
|
let span = val.span();
|
||||||
match val {
|
match val {
|
||||||
Value::Record { val, .. } => {
|
Value::Record { val, .. } => {
|
||||||
for item in val {
|
if let Some((_, value)) = val.iter().find(|(key, _)| key.as_bytes() == sublevel) {
|
||||||
// Check if index matches with sublevel
|
// If matches try to fetch recursively the next
|
||||||
if item.0.as_bytes().to_vec() == next_sublevel {
|
recursive_value(value, next_sublevels)
|
||||||
// If matches try to fetch recursively the next
|
} else {
|
||||||
return recursive_value(item.1, sublevels.into_iter().skip(1).collect());
|
// Current sublevel value not found
|
||||||
}
|
Err(span)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current sublevel value not found
|
|
||||||
return Value::nothing(span);
|
|
||||||
}
|
}
|
||||||
Value::LazyRecord { val, .. } => {
|
Value::LazyRecord { val, .. } => {
|
||||||
for col in val.column_names() {
|
for col in val.column_names() {
|
||||||
if col.as_bytes().to_vec() == next_sublevel {
|
if col.as_bytes() == *sublevel {
|
||||||
return recursive_value(
|
let val = val.get_column_value(col).map_err(|_| span)?;
|
||||||
val.get_column_value(col).unwrap_or_default(),
|
return recursive_value(&val, next_sublevels);
|
||||||
sublevels.into_iter().skip(1).collect(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current sublevel value not found
|
// Current sublevel value not found
|
||||||
return Value::nothing(span);
|
Err(span)
|
||||||
}
|
}
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
for col in get_columns(vals.as_slice()) {
|
for col in get_columns(vals.as_slice()) {
|
||||||
if col.as_bytes().to_vec() == next_sublevel {
|
if col.as_bytes() == *sublevel {
|
||||||
return recursive_value(
|
let val = val.get_data_by_key(&col).ok_or(span)?;
|
||||||
Value::list(vals, span)
|
return recursive_value(&val, next_sublevels);
|
||||||
.get_data_by_key(&col)
|
|
||||||
.unwrap_or_default(),
|
|
||||||
sublevels.into_iter().skip(1).collect(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current sublevel value not found
|
// Current sublevel value not found
|
||||||
return Value::nothing(span);
|
Err(span)
|
||||||
}
|
}
|
||||||
_ => return val,
|
_ => Ok(val.clone()),
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Ok(val.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
val
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MatchAlgorithm {
|
impl MatchAlgorithm {
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
use crate::util::eval_source;
|
use crate::util::eval_source;
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
use nu_path::canonicalize_with;
|
use nu_path::canonicalize_with;
|
||||||
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
use nu_protocol::{
|
||||||
use nu_protocol::report_error;
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
use nu_protocol::{HistoryFileFormat, PipelineData};
|
report_error, HistoryFileFormat, PipelineData,
|
||||||
|
};
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
use nu_protocol::{ParseError, Spanned};
|
use nu_protocol::{ParseError, PluginRegistryFile, Spanned};
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
use nu_utils::utils::perf;
|
use nu_utils::utils::perf;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
const PLUGIN_FILE: &str = "plugin.nu";
|
const PLUGIN_FILE: &str = "plugin.msgpackz";
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
const OLD_PLUGIN_FILE: &str = "plugin.nu";
|
||||||
|
|
||||||
const HISTORY_FILE_TXT: &str = "history.txt";
|
const HISTORY_FILE_TXT: &str = "history.txt";
|
||||||
const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
|
const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
|
||||||
@ -19,40 +22,150 @@ const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
|
|||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
pub fn read_plugin_file(
|
pub fn read_plugin_file(
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
|
||||||
plugin_file: Option<Spanned<String>>,
|
plugin_file: Option<Spanned<String>>,
|
||||||
storage_path: &str,
|
storage_path: &str,
|
||||||
) {
|
) {
|
||||||
let start_time = std::time::Instant::now();
|
use std::path::Path;
|
||||||
let mut plug_path = String::new();
|
|
||||||
// Reading signatures from signature file
|
|
||||||
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
|
||||||
add_plugin_file(engine_state, plugin_file, storage_path);
|
|
||||||
|
|
||||||
let plugin_path = engine_state.plugin_signatures.clone();
|
use nu_protocol::{report_error_new, ShellError};
|
||||||
if let Some(plugin_path) = plugin_path {
|
|
||||||
let plugin_filename = plugin_path.to_string_lossy();
|
let span = plugin_file.as_ref().map(|s| s.span);
|
||||||
plug_path = plugin_filename.to_string();
|
|
||||||
if let Ok(contents) = std::fs::read(&plugin_path) {
|
// Check and warn + abort if this is a .nu plugin file
|
||||||
eval_source(
|
if plugin_file
|
||||||
engine_state,
|
.as_ref()
|
||||||
stack,
|
.and_then(|p| Path::new(&p.item).extension())
|
||||||
&contents,
|
.is_some_and(|ext| ext == "nu")
|
||||||
&plugin_filename,
|
{
|
||||||
PipelineData::empty(),
|
report_error_new(
|
||||||
false,
|
engine_state,
|
||||||
);
|
&ShellError::GenericError {
|
||||||
}
|
error: "Wrong plugin file format".into(),
|
||||||
|
msg: ".nu plugin files are no longer supported".into(),
|
||||||
|
span,
|
||||||
|
help: Some("please recreate this file in the new .msgpackz format".into()),
|
||||||
|
inner: vec![],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut start_time = std::time::Instant::now();
|
||||||
|
// Reading signatures from plugin registry file
|
||||||
|
// The plugin.msgpackz file stores the parsed signature collected from each registered plugin
|
||||||
|
add_plugin_file(engine_state, plugin_file.clone(), storage_path);
|
||||||
perf(
|
perf(
|
||||||
&format!("read_plugin_file {}", &plug_path),
|
"add plugin file to engine_state",
|
||||||
start_time,
|
start_time,
|
||||||
file!(),
|
file!(),
|
||||||
line!(),
|
line!(),
|
||||||
column!(),
|
column!(),
|
||||||
engine_state.get_config().use_ansi_coloring,
|
engine_state.get_config().use_ansi_coloring,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
start_time = std::time::Instant::now();
|
||||||
|
let plugin_path = engine_state.plugin_path.clone();
|
||||||
|
if let Some(plugin_path) = plugin_path {
|
||||||
|
// Open the plugin file
|
||||||
|
let mut file = match std::fs::File::open(&plugin_path) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(err) => {
|
||||||
|
if err.kind() == std::io::ErrorKind::NotFound {
|
||||||
|
log::warn!("Plugin file not found: {}", plugin_path.display());
|
||||||
|
|
||||||
|
// Try migration of an old plugin file if this wasn't a custom plugin file
|
||||||
|
if plugin_file.is_none() && migrate_old_plugin_file(engine_state, storage_path)
|
||||||
|
{
|
||||||
|
let Ok(file) = std::fs::File::open(&plugin_path) else {
|
||||||
|
log::warn!("Failed to load newly migrated plugin file");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
file
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
report_error_new(
|
||||||
|
engine_state,
|
||||||
|
&ShellError::GenericError {
|
||||||
|
error: format!(
|
||||||
|
"Error while opening plugin registry file: {}",
|
||||||
|
plugin_path.display()
|
||||||
|
),
|
||||||
|
msg: "plugin path defined here".into(),
|
||||||
|
span,
|
||||||
|
help: None,
|
||||||
|
inner: vec![err.into()],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Abort if the file is empty.
|
||||||
|
if file.metadata().is_ok_and(|m| m.len() == 0) {
|
||||||
|
log::warn!(
|
||||||
|
"Not reading plugin file because it's empty: {}",
|
||||||
|
plugin_path.display()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the contents of the plugin file
|
||||||
|
let contents = match PluginRegistryFile::read_from(&mut file, span) {
|
||||||
|
Ok(contents) => contents,
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("Failed to read plugin registry file: {err:?}");
|
||||||
|
report_error_new(
|
||||||
|
engine_state,
|
||||||
|
&ShellError::GenericError {
|
||||||
|
error: format!(
|
||||||
|
"Error while reading plugin registry file: {}",
|
||||||
|
plugin_path.display()
|
||||||
|
),
|
||||||
|
msg: "plugin path defined here".into(),
|
||||||
|
span,
|
||||||
|
help: Some(
|
||||||
|
"you might try deleting the file and registering all of your \
|
||||||
|
plugins again"
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
inner: vec![],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
perf(
|
||||||
|
&format!("read plugin file {}", plugin_path.display()),
|
||||||
|
start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
engine_state.get_config().use_ansi_coloring,
|
||||||
|
);
|
||||||
|
start_time = std::time::Instant::now();
|
||||||
|
|
||||||
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
|
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);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
perf(
|
||||||
|
&format!("load plugin file {}", plugin_path.display()),
|
||||||
|
start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
engine_state.get_config().use_ansi_coloring,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
@ -61,21 +174,38 @@ pub fn add_plugin_file(
|
|||||||
plugin_file: Option<Spanned<String>>,
|
plugin_file: Option<Spanned<String>>,
|
||||||
storage_path: &str,
|
storage_path: &str,
|
||||||
) {
|
) {
|
||||||
if let Some(plugin_file) = plugin_file {
|
use std::path::Path;
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
let cwd = working_set.get_cwd();
|
|
||||||
|
|
||||||
if let Ok(path) = canonicalize_with(&plugin_file.item, cwd) {
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
engine_state.plugin_signatures = Some(path)
|
let cwd = working_set.get_cwd();
|
||||||
|
|
||||||
|
if let Some(plugin_file) = plugin_file {
|
||||||
|
let path = Path::new(&plugin_file.item);
|
||||||
|
let path_dir = path.parent().unwrap_or(path);
|
||||||
|
// Just try to canonicalize the directory of the plugin file first.
|
||||||
|
if let Ok(path_dir) = canonicalize_with(path_dir, &cwd) {
|
||||||
|
// Try to canonicalize the actual filename, but it's ok if that fails. The file doesn't
|
||||||
|
// have to exist.
|
||||||
|
let path = path_dir.join(path.file_name().unwrap_or(path.as_os_str()));
|
||||||
|
let path = canonicalize_with(&path, &cwd).unwrap_or(path);
|
||||||
|
engine_state.plugin_path = Some(path)
|
||||||
} else {
|
} else {
|
||||||
let e = ParseError::FileNotFound(plugin_file.item, plugin_file.span);
|
// It's an error if the directory for the plugin file doesn't exist.
|
||||||
report_error(&working_set, &e);
|
report_error(
|
||||||
|
&working_set,
|
||||||
|
&ParseError::FileNotFound(
|
||||||
|
path_dir.to_string_lossy().into_owned(),
|
||||||
|
plugin_file.span,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if let Some(mut plugin_path) = nu_path::config_dir() {
|
} else if let Some(mut plugin_path) = nu_path::config_dir() {
|
||||||
// Path to store plugins signatures
|
// Path to store plugins signatures
|
||||||
plugin_path.push(storage_path);
|
plugin_path.push(storage_path);
|
||||||
|
let mut plugin_path = canonicalize_with(&plugin_path, &cwd).unwrap_or(plugin_path);
|
||||||
plugin_path.push(PLUGIN_FILE);
|
plugin_path.push(PLUGIN_FILE);
|
||||||
engine_state.plugin_signatures = Some(plugin_path.clone());
|
let plugin_path = canonicalize_with(&plugin_path, &cwd).unwrap_or(plugin_path);
|
||||||
|
engine_state.plugin_path = Some(plugin_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,6 +218,10 @@ pub fn eval_config_contents(
|
|||||||
let config_filename = config_path.to_string_lossy();
|
let config_filename = config_path.to_string_lossy();
|
||||||
|
|
||||||
if let Ok(contents) = std::fs::read(&config_path) {
|
if let Ok(contents) = std::fs::read(&config_path) {
|
||||||
|
// Set the current active file to the config file.
|
||||||
|
let prev_file = engine_state.file.take();
|
||||||
|
engine_state.file = Some(config_path.clone());
|
||||||
|
|
||||||
eval_source(
|
eval_source(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
@ -97,6 +231,9 @@ pub fn eval_config_contents(
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Restore the current active file.
|
||||||
|
engine_state.file = prev_file;
|
||||||
|
|
||||||
// Merge the environment in case env vars changed in the config
|
// Merge the environment in case env vars changed in the config
|
||||||
match nu_engine::env::current_dir(engine_state, stack) {
|
match nu_engine::env::current_dir(engine_state, stack) {
|
||||||
Ok(cwd) => {
|
Ok(cwd) => {
|
||||||
@ -124,3 +261,129 @@ pub(crate) fn get_history_path(storage_path: &str, mode: HistoryFileFormat) -> O
|
|||||||
history_path
|
history_path
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
|
pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) -> bool {
|
||||||
|
use nu_protocol::{
|
||||||
|
report_error_new, PluginExample, PluginIdentity, PluginRegistryItem,
|
||||||
|
PluginRegistryItemData, PluginSignature, ShellError,
|
||||||
|
};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
let start_time = std::time::Instant::now();
|
||||||
|
|
||||||
|
let cwd = engine_state.current_work_dir();
|
||||||
|
|
||||||
|
let Some(config_dir) = nu_path::config_dir().and_then(|mut dir| {
|
||||||
|
dir.push(storage_path);
|
||||||
|
nu_path::canonicalize_with(dir, &cwd).ok()
|
||||||
|
}) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(old_plugin_file_path) = nu_path::canonicalize_with(OLD_PLUGIN_FILE, &config_dir) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let old_contents = match std::fs::read(&old_plugin_file_path) {
|
||||||
|
Ok(old_contents) => old_contents,
|
||||||
|
Err(err) => {
|
||||||
|
report_error_new(
|
||||||
|
engine_state,
|
||||||
|
&ShellError::GenericError {
|
||||||
|
error: "Can't read old plugin file to migrate".into(),
|
||||||
|
msg: "".into(),
|
||||||
|
span: None,
|
||||||
|
help: Some(err.to_string()),
|
||||||
|
inner: vec![],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make a copy of the engine state, because we'll read the newly generated file
|
||||||
|
let mut engine_state = engine_state.clone();
|
||||||
|
let mut stack = Stack::new();
|
||||||
|
|
||||||
|
if !eval_source(
|
||||||
|
&mut engine_state,
|
||||||
|
&mut stack,
|
||||||
|
&old_contents,
|
||||||
|
&old_plugin_file_path.to_string_lossy(),
|
||||||
|
PipelineData::Empty,
|
||||||
|
false,
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that the plugin commands are loaded, we just have to generate the file
|
||||||
|
let mut contents = PluginRegistryFile::new();
|
||||||
|
|
||||||
|
let mut groups = BTreeMap::<PluginIdentity, Vec<PluginSignature>>::new();
|
||||||
|
|
||||||
|
for decl in engine_state.plugin_decls() {
|
||||||
|
if let Some(identity) = decl.plugin_identity() {
|
||||||
|
groups
|
||||||
|
.entry(identity.clone())
|
||||||
|
.or_default()
|
||||||
|
.push(PluginSignature {
|
||||||
|
sig: decl.signature(),
|
||||||
|
examples: decl
|
||||||
|
.examples()
|
||||||
|
.into_iter()
|
||||||
|
.map(PluginExample::from)
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (identity, commands) in groups {
|
||||||
|
contents.upsert_plugin(PluginRegistryItem {
|
||||||
|
name: identity.name().to_owned(),
|
||||||
|
filename: identity.filename().to_owned(),
|
||||||
|
shell: identity.shell().map(|p| p.to_owned()),
|
||||||
|
data: PluginRegistryItemData::Valid { commands },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the new file
|
||||||
|
let new_plugin_file_path = config_dir.join(PLUGIN_FILE);
|
||||||
|
if let Err(err) = std::fs::File::create(&new_plugin_file_path)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
.and_then(|file| contents.write_to(file, None))
|
||||||
|
{
|
||||||
|
report_error_new(
|
||||||
|
&engine_state,
|
||||||
|
&ShellError::GenericError {
|
||||||
|
error: "Failed to save migrated plugin file".into(),
|
||||||
|
msg: "".into(),
|
||||||
|
span: None,
|
||||||
|
help: Some("ensure `$nu.plugin-path` is writable".into()),
|
||||||
|
inner: vec![err],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if engine_state.is_interactive {
|
||||||
|
eprintln!(
|
||||||
|
"Your old plugin.nu file has been migrated to the new format: {}",
|
||||||
|
new_plugin_file_path.display()
|
||||||
|
);
|
||||||
|
eprintln!(
|
||||||
|
"The plugin.nu file has not been removed. If `plugin list` looks okay, \
|
||||||
|
you may do so manually."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
perf(
|
||||||
|
"migrate old plugin file",
|
||||||
|
start_time,
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!(),
|
||||||
|
engine_state.get_config().use_ansi_coloring,
|
||||||
|
);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
@ -2,11 +2,10 @@ use log::info;
|
|||||||
use miette::Result;
|
use miette::Result;
|
||||||
use nu_engine::{convert_env_values, eval_block};
|
use nu_engine::{convert_env_values, eval_block};
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::engine::Stack;
|
|
||||||
use nu_protocol::report_error;
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
debugger::WithoutDebug,
|
||||||
PipelineData, Spanned, Value,
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
|
report_error, PipelineData, Spanned, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Run a command (or commands) given to us by the user
|
/// Run a command (or commands) given to us by the user
|
||||||
@ -16,6 +15,7 @@ pub fn evaluate_commands(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
table_mode: Option<Value>,
|
table_mode: Option<Value>,
|
||||||
|
no_newline: bool,
|
||||||
) -> Result<Option<i64>> {
|
) -> Result<Option<i64>> {
|
||||||
// Translate environment variables from Strings to Values
|
// Translate environment variables from Strings to Values
|
||||||
if let Some(e) = convert_env_values(engine_state, stack) {
|
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||||
@ -28,7 +28,7 @@ pub fn evaluate_commands(
|
|||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
if let Some(ref t_mode) = table_mode {
|
if let Some(ref t_mode) = table_mode {
|
||||||
let mut config = engine_state.get_config().clone();
|
let mut config = engine_state.get_config().clone();
|
||||||
config.table_mode = t_mode.as_string()?.parse().unwrap_or_default();
|
config.table_mode = t_mode.coerce_str()?.parse().unwrap_or_default();
|
||||||
engine_state.set_config(config);
|
engine_state.set_config(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,13 +55,19 @@ pub fn evaluate_commands(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run the block
|
// Run the block
|
||||||
let exit_code = match eval_block(engine_state, stack, &block, input, false, false) {
|
let exit_code = match eval_block::<WithoutDebug>(engine_state, stack, &block, input) {
|
||||||
Ok(pipeline_data) => {
|
Ok(pipeline_data) => {
|
||||||
let mut config = engine_state.get_config().clone();
|
let mut config = engine_state.get_config().clone();
|
||||||
if let Some(t_mode) = table_mode {
|
if let Some(t_mode) = table_mode {
|
||||||
config.table_mode = t_mode.as_string()?.parse().unwrap_or_default();
|
config.table_mode = t_mode.coerce_str()?.parse().unwrap_or_default();
|
||||||
}
|
}
|
||||||
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
|
crate::eval_file::print_table_or_error(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
pipeline_data,
|
||||||
|
&mut config,
|
||||||
|
no_newline,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
use crate::util::eval_source;
|
use crate::util::eval_source;
|
||||||
use log::info;
|
use log::{info, trace};
|
||||||
use log::trace;
|
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use nu_engine::eval_block;
|
use nu_engine::{convert_env_values, current_dir, eval_block};
|
||||||
use nu_engine::{convert_env_values, current_dir};
|
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_path::canonicalize_with;
|
use nu_path::canonicalize_with;
|
||||||
use nu_protocol::report_error;
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
debugger::WithoutDebug,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
Config, PipelineData, ShellError, Span, Value,
|
report_error, Config, PipelineData, ShellError, Span, Value,
|
||||||
};
|
};
|
||||||
use nu_utils::stdout_write_all_and_flush;
|
use std::{io::Write, sync::Arc};
|
||||||
|
|
||||||
/// Main function used when a file path is found as argument for nu
|
/// Entry point for evaluating a file.
|
||||||
|
///
|
||||||
|
/// If the file contains a main command, it is invoked with `args` and the pipeline data from `input`;
|
||||||
|
/// otherwise, the pipeline data is forwarded to the first command in the file, and `args` are ignored.
|
||||||
pub fn evaluate_file(
|
pub fn evaluate_file(
|
||||||
path: String,
|
path: String,
|
||||||
args: &[String],
|
args: &[String],
|
||||||
@ -22,7 +22,7 @@ pub fn evaluate_file(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Translate environment variables from Strings to Values
|
// Convert environment variables from Strings to Values and store them in the engine state.
|
||||||
if let Some(e) = convert_env_values(engine_state, stack) {
|
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(&working_set, &e);
|
report_error(&working_set, &e);
|
||||||
@ -75,8 +75,7 @@ pub fn evaluate_file(
|
|||||||
);
|
);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
});
|
});
|
||||||
|
engine_state.file = Some(file_path.clone());
|
||||||
engine_state.start_in_file(Some(file_path_str));
|
|
||||||
|
|
||||||
let parent = file_path.parent().unwrap_or_else(|| {
|
let parent = file_path.parent().unwrap_or_else(|| {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
@ -105,18 +104,20 @@ pub fn evaluate_file(
|
|||||||
|
|
||||||
let source_filename = file_path
|
let source_filename = file_path
|
||||||
.file_name()
|
.file_name()
|
||||||
.expect("internal error: script missing filename");
|
.expect("internal error: missing filename");
|
||||||
|
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
trace!("parsing file: {}", file_path_str);
|
trace!("parsing file: {}", file_path_str);
|
||||||
let block = parse(&mut working_set, Some(file_path_str), &file, false);
|
let block = parse(&mut working_set, Some(file_path_str), &file, false);
|
||||||
|
|
||||||
|
// If any parse errors were found, report the first error and exit.
|
||||||
if let Some(err) = working_set.parse_errors.first() {
|
if let Some(err) = working_set.parse_errors.first() {
|
||||||
report_error(&working_set, err);
|
report_error(&working_set, err);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
for block in &mut working_set.delta.blocks {
|
// Look for blocks whose name starts with "main" and replace it with the filename.
|
||||||
|
for block in working_set.delta.blocks.iter_mut().map(Arc::make_mut) {
|
||||||
if block.signature.name == "main" {
|
if block.signature.name == "main" {
|
||||||
block.signature.name = source_filename.to_string_lossy().to_string();
|
block.signature.name = source_filename.to_string_lossy().to_string();
|
||||||
} else if block.signature.name.starts_with("main ") {
|
} else if block.signature.name.starts_with("main ") {
|
||||||
@ -125,25 +126,21 @@ pub fn evaluate_file(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = engine_state.merge_delta(working_set.delta);
|
// Merge the changes into the engine state.
|
||||||
|
engine_state
|
||||||
|
.merge_delta(working_set.delta)
|
||||||
|
.expect("merging delta into engine_state should succeed");
|
||||||
|
|
||||||
|
// Check if the file contains a main command.
|
||||||
if engine_state.find_decl(b"main", &[]).is_some() {
|
if engine_state.find_decl(b"main", &[]).is_some() {
|
||||||
let args = format!("main {}", args.join(" "));
|
// Evaluate the file, but don't run main yet.
|
||||||
|
let pipeline_data =
|
||||||
let pipeline_data = eval_block(
|
eval_block::<WithoutDebug>(engine_state, stack, &block, PipelineData::empty());
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
&block,
|
|
||||||
PipelineData::empty(),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
let pipeline_data = match pipeline_data {
|
let pipeline_data = match pipeline_data {
|
||||||
Err(ShellError::Return { .. }) => {
|
Err(ShellError::Return { .. }) => {
|
||||||
// allows early exists before `main` is run.
|
// Allow early return before main is run.
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
x => x,
|
x => x,
|
||||||
}
|
}
|
||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(|e| {
|
||||||
@ -152,12 +149,12 @@ pub fn evaluate_file(
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Print the pipeline output of the file.
|
||||||
|
// The pipeline output of a file is the pipeline output of its last command.
|
||||||
let result = pipeline_data.print(engine_state, stack, true, false);
|
let result = pipeline_data.print(engine_state, stack, true, false);
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
report_error(&working_set, &err);
|
report_error(&working_set, &err);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
@ -168,6 +165,9 @@ pub fn evaluate_file(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invoke the main command with arguments.
|
||||||
|
// Arguments with whitespaces are quoted, thus can be safely concatenated by whitespace.
|
||||||
|
let args = format!("main {}", args.join(" "));
|
||||||
if !eval_source(
|
if !eval_source(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
@ -192,6 +192,7 @@ pub(crate) fn print_table_or_error(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
mut pipeline_data: PipelineData,
|
mut pipeline_data: PipelineData,
|
||||||
config: &mut Config,
|
config: &mut Config,
|
||||||
|
no_newline: bool,
|
||||||
) -> Option<i64> {
|
) -> Option<i64> {
|
||||||
let exit_code = match &mut pipeline_data {
|
let exit_code = match &mut pipeline_data {
|
||||||
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
|
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
|
||||||
@ -207,30 +208,8 @@ pub(crate) fn print_table_or_error(
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(decl_id) = engine_state.find_decl("table".as_bytes(), &[]) {
|
// We don't need to do anything special to print a table because print() handles it
|
||||||
let command = engine_state.get_decl(decl_id);
|
print_or_exit(pipeline_data, engine_state, stack, no_newline);
|
||||||
if command.get_block_id().is_some() {
|
|
||||||
print_or_exit(pipeline_data, engine_state, config);
|
|
||||||
} else {
|
|
||||||
// The final call on table command, it's ok to set redirect_output to false.
|
|
||||||
let mut call = Call::new(Span::new(0, 0));
|
|
||||||
call.redirect_stdout = false;
|
|
||||||
let table = command.run(engine_state, stack, &call, pipeline_data);
|
|
||||||
|
|
||||||
match table {
|
|
||||||
Ok(table) => {
|
|
||||||
print_or_exit(table, engine_state, config);
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
report_error(&working_set, &error);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print_or_exit(pipeline_data, engine_state, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure everything has finished
|
// Make sure everything has finished
|
||||||
if let Some(exit_code) = exit_code {
|
if let Some(exit_code) = exit_code {
|
||||||
@ -246,17 +225,21 @@ pub(crate) fn print_table_or_error(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, config: &Config) {
|
fn print_or_exit(
|
||||||
for item in pipeline_data {
|
pipeline_data: PipelineData,
|
||||||
if let Value::Error { error, .. } = item {
|
engine_state: &EngineState,
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
stack: &mut Stack,
|
||||||
|
no_newline: bool,
|
||||||
|
) {
|
||||||
|
let result = pipeline_data.print(engine_state, stack, no_newline, false);
|
||||||
|
|
||||||
report_error(&working_set, &*error);
|
let _ = std::io::stdout().flush();
|
||||||
|
let _ = std::io::stderr().flush();
|
||||||
|
|
||||||
std::process::exit(1);
|
if let Err(error) = result {
|
||||||
}
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
report_error(&working_set, &error);
|
||||||
let out = item.into_string("\n", config) + "\n";
|
let _ = std::io::stderr().flush();
|
||||||
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{err}"));
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,11 @@ mod util;
|
|||||||
mod validation;
|
mod validation;
|
||||||
|
|
||||||
pub use commands::add_cli_context;
|
pub use commands::add_cli_context;
|
||||||
pub use completions::{FileCompletion, NuCompleter};
|
pub use completions::{FileCompletion, NuCompleter, SemanticSuggestion, SuggestionKind};
|
||||||
pub use config_files::eval_config_contents;
|
pub use config_files::eval_config_contents;
|
||||||
pub use eval_cmds::evaluate_commands;
|
pub use eval_cmds::evaluate_commands;
|
||||||
pub use eval_file::evaluate_file;
|
pub use eval_file::evaluate_file;
|
||||||
pub use menus::{DescriptionMenu, NuHelpCompleter};
|
pub use menus::NuHelpCompleter;
|
||||||
pub use nu_cmd_base::util::get_init_cwd;
|
pub use nu_cmd_base::util::get_init_cwd;
|
||||||
pub use nu_highlight::NuHighlight;
|
pub use nu_highlight::NuHighlight;
|
||||||
pub use print::Print;
|
pub use print::Print;
|
||||||
@ -32,4 +32,6 @@ pub use validation::NuValidator;
|
|||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
pub use config_files::add_plugin_file;
|
pub use config_files::add_plugin_file;
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
|
pub use config_files::migrate_old_plugin_file;
|
||||||
|
#[cfg(feature = "plugin")]
|
||||||
pub use config_files::read_plugin_file;
|
pub use config_files::read_plugin_file;
|
||||||
|
@ -1,730 +0,0 @@
|
|||||||
use {
|
|
||||||
nu_ansi_term::{ansi::RESET, Style},
|
|
||||||
reedline::{
|
|
||||||
menu_functions::string_difference, Completer, Editor, Menu, MenuEvent, MenuTextStyle,
|
|
||||||
Painter, Suggestion, UndoBehavior,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Default values used as reference for the menu. These values are set during
|
|
||||||
/// the initial declaration of the menu and are always kept as reference for the
|
|
||||||
/// changeable [`WorkingDetails`]
|
|
||||||
struct DefaultMenuDetails {
|
|
||||||
/// Number of columns that the menu will have
|
|
||||||
pub columns: u16,
|
|
||||||
/// Column width
|
|
||||||
pub col_width: Option<usize>,
|
|
||||||
/// Column padding
|
|
||||||
pub col_padding: usize,
|
|
||||||
/// Number of rows for commands
|
|
||||||
pub selection_rows: u16,
|
|
||||||
/// Number of rows allowed to display the description
|
|
||||||
pub description_rows: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for DefaultMenuDetails {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
columns: 4,
|
|
||||||
col_width: None,
|
|
||||||
col_padding: 2,
|
|
||||||
selection_rows: 4,
|
|
||||||
description_rows: 10,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the actual column conditions of the menu. These conditions change
|
|
||||||
/// since they need to accommodate possible different line sizes for the column values
|
|
||||||
#[derive(Default)]
|
|
||||||
struct WorkingDetails {
|
|
||||||
/// Number of columns that the menu will have
|
|
||||||
pub columns: u16,
|
|
||||||
/// Column width
|
|
||||||
pub col_width: usize,
|
|
||||||
/// Number of rows for description
|
|
||||||
pub description_rows: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Completion menu definition
|
|
||||||
pub struct DescriptionMenu {
|
|
||||||
/// Menu name
|
|
||||||
name: String,
|
|
||||||
/// Menu status
|
|
||||||
active: bool,
|
|
||||||
/// Menu coloring
|
|
||||||
color: MenuTextStyle,
|
|
||||||
/// Default column details that are set when creating the menu
|
|
||||||
/// These values are the reference for the working details
|
|
||||||
default_details: DefaultMenuDetails,
|
|
||||||
/// Number of minimum rows that are displayed when
|
|
||||||
/// the required lines is larger than the available lines
|
|
||||||
min_rows: u16,
|
|
||||||
/// Working column details keep changing based on the collected values
|
|
||||||
working_details: WorkingDetails,
|
|
||||||
/// Menu cached values
|
|
||||||
values: Vec<Suggestion>,
|
|
||||||
/// column position of the cursor. Starts from 0
|
|
||||||
col_pos: u16,
|
|
||||||
/// row position in the menu. Starts from 0
|
|
||||||
row_pos: u16,
|
|
||||||
/// Menu marker when active
|
|
||||||
marker: String,
|
|
||||||
/// Event sent to the menu
|
|
||||||
event: Option<MenuEvent>,
|
|
||||||
/// String collected after the menu is activated
|
|
||||||
input: Option<String>,
|
|
||||||
/// Examples to select
|
|
||||||
examples: Vec<String>,
|
|
||||||
/// Example index
|
|
||||||
example_index: Option<usize>,
|
|
||||||
/// Examples may not be shown if there is not enough space in the screen
|
|
||||||
show_examples: bool,
|
|
||||||
/// Skipped description rows
|
|
||||||
skipped_rows: usize,
|
|
||||||
/// Calls the completer using only the line buffer difference difference
|
|
||||||
/// after the menu was activated
|
|
||||||
only_buffer_difference: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for DescriptionMenu {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
name: "description_menu".to_string(),
|
|
||||||
active: false,
|
|
||||||
color: MenuTextStyle::default(),
|
|
||||||
default_details: DefaultMenuDetails::default(),
|
|
||||||
min_rows: 3,
|
|
||||||
working_details: WorkingDetails::default(),
|
|
||||||
values: Vec::new(),
|
|
||||||
col_pos: 0,
|
|
||||||
row_pos: 0,
|
|
||||||
marker: "? ".to_string(),
|
|
||||||
event: None,
|
|
||||||
input: None,
|
|
||||||
examples: Vec::new(),
|
|
||||||
example_index: None,
|
|
||||||
show_examples: true,
|
|
||||||
skipped_rows: 0,
|
|
||||||
only_buffer_difference: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Menu configuration
|
|
||||||
impl DescriptionMenu {
|
|
||||||
/// Menu builder with new name
|
|
||||||
pub fn with_name(mut self, name: &str) -> Self {
|
|
||||||
self.name = name.into();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Menu builder with new value for text style
|
|
||||||
pub fn with_text_style(mut self, text_style: Style) -> Self {
|
|
||||||
self.color.text_style = text_style;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Menu builder with new value for text style
|
|
||||||
pub fn with_selected_text_style(mut self, selected_text_style: Style) -> Self {
|
|
||||||
self.color.selected_text_style = selected_text_style;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Menu builder with new value for text style
|
|
||||||
pub fn with_description_text_style(mut self, description_text_style: Style) -> Self {
|
|
||||||
self.color.description_style = description_text_style;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Menu builder with new columns value
|
|
||||||
pub fn with_columns(mut self, columns: u16) -> Self {
|
|
||||||
self.default_details.columns = columns;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Menu builder with new column width value
|
|
||||||
pub fn with_column_width(mut self, col_width: Option<usize>) -> Self {
|
|
||||||
self.default_details.col_width = col_width;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Menu builder with new column width value
|
|
||||||
pub fn with_column_padding(mut self, col_padding: usize) -> Self {
|
|
||||||
self.default_details.col_padding = col_padding;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Menu builder with new selection rows value
|
|
||||||
pub fn with_selection_rows(mut self, selection_rows: u16) -> Self {
|
|
||||||
self.default_details.selection_rows = selection_rows;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Menu builder with new description rows value
|
|
||||||
pub fn with_description_rows(mut self, description_rows: usize) -> Self {
|
|
||||||
self.default_details.description_rows = description_rows;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Menu builder with marker
|
|
||||||
pub fn with_marker(mut self, marker: String) -> Self {
|
|
||||||
self.marker = marker;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Menu builder with new only buffer difference
|
|
||||||
pub fn with_only_buffer_difference(mut self, only_buffer_difference: bool) -> Self {
|
|
||||||
self.only_buffer_difference = only_buffer_difference;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Menu functionality
|
|
||||||
impl DescriptionMenu {
|
|
||||||
/// Move menu cursor to the next element
|
|
||||||
fn move_next(&mut self) {
|
|
||||||
let mut new_col = self.col_pos + 1;
|
|
||||||
let mut new_row = self.row_pos;
|
|
||||||
|
|
||||||
if new_col >= self.get_cols() {
|
|
||||||
new_row += 1;
|
|
||||||
new_col = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if new_row >= self.get_rows() {
|
|
||||||
new_row = 0;
|
|
||||||
new_col = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let position = new_row * self.get_cols() + new_col;
|
|
||||||
if position >= self.get_values().len() as u16 {
|
|
||||||
self.reset_position();
|
|
||||||
} else {
|
|
||||||
self.col_pos = new_col;
|
|
||||||
self.row_pos = new_row;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Move menu cursor to the previous element
|
|
||||||
fn move_previous(&mut self) {
|
|
||||||
let new_col = self.col_pos.checked_sub(1);
|
|
||||||
|
|
||||||
let (new_col, new_row) = match new_col {
|
|
||||||
Some(col) => (col, self.row_pos),
|
|
||||||
None => match self.row_pos.checked_sub(1) {
|
|
||||||
Some(row) => (self.get_cols().saturating_sub(1), row),
|
|
||||||
None => (
|
|
||||||
self.get_cols().saturating_sub(1),
|
|
||||||
self.get_rows().saturating_sub(1),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let position = new_row * self.get_cols() + new_col;
|
|
||||||
if position >= self.get_values().len() as u16 {
|
|
||||||
self.col_pos = (self.get_values().len() as u16 % self.get_cols()).saturating_sub(1);
|
|
||||||
self.row_pos = self.get_rows().saturating_sub(1);
|
|
||||||
} else {
|
|
||||||
self.col_pos = new_col;
|
|
||||||
self.row_pos = new_row;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Menu index based on column and row position
|
|
||||||
fn index(&self) -> usize {
|
|
||||||
let index = self.row_pos * self.get_cols() + self.col_pos;
|
|
||||||
index as usize
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get selected value from the menu
|
|
||||||
fn get_value(&self) -> Option<Suggestion> {
|
|
||||||
self.get_values().get(self.index()).cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculates how many rows the Menu will use
|
|
||||||
fn get_rows(&self) -> u16 {
|
|
||||||
let values = self.get_values().len() as u16;
|
|
||||||
|
|
||||||
if values == 0 {
|
|
||||||
// When the values are empty the no_records_msg is shown, taking 1 line
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let rows = values / self.get_cols();
|
|
||||||
if values % self.get_cols() != 0 {
|
|
||||||
rows + 1
|
|
||||||
} else {
|
|
||||||
rows
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns working details col width
|
|
||||||
fn get_width(&self) -> usize {
|
|
||||||
self.working_details.col_width
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset menu position
|
|
||||||
fn reset_position(&mut self) {
|
|
||||||
self.col_pos = 0;
|
|
||||||
self.row_pos = 0;
|
|
||||||
self.skipped_rows = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn no_records_msg(&self, use_ansi_coloring: bool) -> String {
|
|
||||||
let msg = "TYPE TO START SEARCH";
|
|
||||||
if use_ansi_coloring {
|
|
||||||
format!(
|
|
||||||
"{}{}{}",
|
|
||||||
self.color.selected_text_style.prefix(),
|
|
||||||
msg,
|
|
||||||
RESET
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
msg.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns working details columns
|
|
||||||
fn get_cols(&self) -> u16 {
|
|
||||||
self.working_details.columns.max(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// End of line for menu
|
|
||||||
fn end_of_line(&self, column: u16, index: usize) -> &str {
|
|
||||||
let is_last = index == self.values.len().saturating_sub(1);
|
|
||||||
if column == self.get_cols().saturating_sub(1) || is_last {
|
|
||||||
"\r\n"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update list of examples from the actual value
|
|
||||||
fn update_examples(&mut self) {
|
|
||||||
self.examples = self
|
|
||||||
.get_value()
|
|
||||||
.and_then(|suggestion| suggestion.extra)
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
self.example_index = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates default string that represents one suggestion from the menu
|
|
||||||
fn create_entry_string(
|
|
||||||
&self,
|
|
||||||
suggestion: &Suggestion,
|
|
||||||
index: usize,
|
|
||||||
column: u16,
|
|
||||||
empty_space: usize,
|
|
||||||
use_ansi_coloring: bool,
|
|
||||||
) -> String {
|
|
||||||
if use_ansi_coloring {
|
|
||||||
if index == self.index() {
|
|
||||||
format!(
|
|
||||||
"{}{}{}{:>empty$}{}",
|
|
||||||
self.color.selected_text_style.prefix(),
|
|
||||||
&suggestion.value,
|
|
||||||
RESET,
|
|
||||||
"",
|
|
||||||
self.end_of_line(column, index),
|
|
||||||
empty = empty_space,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
format!(
|
|
||||||
"{}{}{}{:>empty$}{}",
|
|
||||||
self.color.text_style.prefix(),
|
|
||||||
&suggestion.value,
|
|
||||||
RESET,
|
|
||||||
"",
|
|
||||||
self.end_of_line(column, index),
|
|
||||||
empty = empty_space,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If no ansi coloring is found, then the selection word is
|
|
||||||
// the line in uppercase
|
|
||||||
let (marker, empty_space) = if index == self.index() {
|
|
||||||
(">", empty_space.saturating_sub(1))
|
|
||||||
} else {
|
|
||||||
("", empty_space)
|
|
||||||
};
|
|
||||||
|
|
||||||
let line = format!(
|
|
||||||
"{}{}{:>empty$}{}",
|
|
||||||
marker,
|
|
||||||
&suggestion.value,
|
|
||||||
"",
|
|
||||||
self.end_of_line(column, index),
|
|
||||||
empty = empty_space,
|
|
||||||
);
|
|
||||||
|
|
||||||
if index == self.index() {
|
|
||||||
line.to_uppercase()
|
|
||||||
} else {
|
|
||||||
line
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Description string with color
|
|
||||||
fn create_description_string(&self, use_ansi_coloring: bool) -> String {
|
|
||||||
let description = self
|
|
||||||
.get_value()
|
|
||||||
.and_then(|suggestion| suggestion.description)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.lines()
|
|
||||||
.skip(self.skipped_rows)
|
|
||||||
.take(self.working_details.description_rows)
|
|
||||||
.collect::<Vec<&str>>()
|
|
||||||
.join("\r\n");
|
|
||||||
|
|
||||||
if use_ansi_coloring && !description.is_empty() {
|
|
||||||
format!(
|
|
||||||
"{}{}{}",
|
|
||||||
self.color.description_style.prefix(),
|
|
||||||
description,
|
|
||||||
RESET,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
description
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Selectable list of examples from the actual value
|
|
||||||
fn create_example_string(&self, use_ansi_coloring: bool) -> String {
|
|
||||||
if !self.show_examples {
|
|
||||||
return "".into();
|
|
||||||
}
|
|
||||||
|
|
||||||
let examples: String = self
|
|
||||||
.examples
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(index, example)| {
|
|
||||||
if let Some(example_index) = self.example_index {
|
|
||||||
if index == example_index {
|
|
||||||
format!(
|
|
||||||
" {}{}{}\r\n",
|
|
||||||
self.color.selected_text_style.prefix(),
|
|
||||||
example,
|
|
||||||
RESET
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
format!(" {example}\r\n")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
format!(" {example}\r\n")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if examples.is_empty() {
|
|
||||||
"".into()
|
|
||||||
} else if use_ansi_coloring {
|
|
||||||
format!(
|
|
||||||
"{}\r\n\r\nExamples:\r\n{}{}",
|
|
||||||
self.color.description_style.prefix(),
|
|
||||||
RESET,
|
|
||||||
examples,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
format!("\r\n\r\nExamples:\r\n{examples}",)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Menu for DescriptionMenu {
|
|
||||||
/// Menu name
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
self.name.as_str()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Menu indicator
|
|
||||||
fn indicator(&self) -> &str {
|
|
||||||
self.marker.as_str()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deactivates context menu
|
|
||||||
fn is_active(&self) -> bool {
|
|
||||||
self.active
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The menu stays active even with one record
|
|
||||||
fn can_quick_complete(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The menu does not need to partially complete
|
|
||||||
fn can_partially_complete(
|
|
||||||
&mut self,
|
|
||||||
_values_updated: bool,
|
|
||||||
_editor: &mut Editor,
|
|
||||||
_completer: &mut dyn Completer,
|
|
||||||
) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Selects what type of event happened with the menu
|
|
||||||
fn menu_event(&mut self, event: MenuEvent) {
|
|
||||||
match &event {
|
|
||||||
MenuEvent::Activate(_) => self.active = true,
|
|
||||||
MenuEvent::Deactivate => {
|
|
||||||
self.active = false;
|
|
||||||
self.input = None;
|
|
||||||
self.values = Vec::new();
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.event = Some(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates menu values
|
|
||||||
fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer) {
|
|
||||||
if self.only_buffer_difference {
|
|
||||||
if let Some(old_string) = &self.input {
|
|
||||||
let (start, input) = string_difference(editor.get_buffer(), old_string);
|
|
||||||
if !input.is_empty() {
|
|
||||||
self.reset_position();
|
|
||||||
self.values = completer.complete(input, start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let trimmed_buffer = editor.get_buffer().replace('\n', " ");
|
|
||||||
self.values = completer.complete(
|
|
||||||
trimmed_buffer.as_str(),
|
|
||||||
editor.line_buffer().insertion_point(),
|
|
||||||
);
|
|
||||||
self.reset_position();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The working details for the menu changes based on the size of the lines
|
|
||||||
/// collected from the completer
|
|
||||||
fn update_working_details(
|
|
||||||
&mut self,
|
|
||||||
editor: &mut Editor,
|
|
||||||
completer: &mut dyn Completer,
|
|
||||||
painter: &Painter,
|
|
||||||
) {
|
|
||||||
if let Some(event) = self.event.take() {
|
|
||||||
// Updating all working parameters from the menu before executing any of the
|
|
||||||
// possible event
|
|
||||||
let max_width = self.get_values().iter().fold(0, |acc, suggestion| {
|
|
||||||
let str_len = suggestion.value.len() + self.default_details.col_padding;
|
|
||||||
if str_len > acc {
|
|
||||||
str_len
|
|
||||||
} else {
|
|
||||||
acc
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// If no default width is found, then the total screen width is used to estimate
|
|
||||||
// the column width based on the default number of columns
|
|
||||||
let default_width = if let Some(col_width) = self.default_details.col_width {
|
|
||||||
col_width
|
|
||||||
} else {
|
|
||||||
let col_width = painter.screen_width() / self.default_details.columns;
|
|
||||||
col_width as usize
|
|
||||||
};
|
|
||||||
|
|
||||||
// Adjusting the working width of the column based the max line width found
|
|
||||||
// in the menu values
|
|
||||||
if max_width > default_width {
|
|
||||||
self.working_details.col_width = max_width;
|
|
||||||
} else {
|
|
||||||
self.working_details.col_width = default_width;
|
|
||||||
};
|
|
||||||
|
|
||||||
// The working columns is adjusted based on possible number of columns
|
|
||||||
// that could be fitted in the screen with the calculated column width
|
|
||||||
let possible_cols = painter.screen_width() / self.working_details.col_width as u16;
|
|
||||||
if possible_cols > self.default_details.columns {
|
|
||||||
self.working_details.columns = self.default_details.columns.max(1);
|
|
||||||
} else {
|
|
||||||
self.working_details.columns = possible_cols;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updating the working rows to display the description
|
|
||||||
if self.menu_required_lines(painter.screen_width()) <= painter.remaining_lines() {
|
|
||||||
self.working_details.description_rows = self.default_details.description_rows;
|
|
||||||
self.show_examples = true;
|
|
||||||
} else {
|
|
||||||
self.working_details.description_rows = painter
|
|
||||||
.remaining_lines()
|
|
||||||
.saturating_sub(self.default_details.selection_rows + 1)
|
|
||||||
as usize;
|
|
||||||
|
|
||||||
self.show_examples = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
match event {
|
|
||||||
MenuEvent::Activate(_) => {
|
|
||||||
self.reset_position();
|
|
||||||
self.input = Some(editor.get_buffer().to_string());
|
|
||||||
self.update_values(editor, completer);
|
|
||||||
}
|
|
||||||
MenuEvent::Deactivate => self.active = false,
|
|
||||||
MenuEvent::Edit(_) => {
|
|
||||||
self.reset_position();
|
|
||||||
self.update_values(editor, completer);
|
|
||||||
self.update_examples()
|
|
||||||
}
|
|
||||||
MenuEvent::NextElement => {
|
|
||||||
self.skipped_rows = 0;
|
|
||||||
self.move_next();
|
|
||||||
self.update_examples();
|
|
||||||
}
|
|
||||||
MenuEvent::PreviousElement => {
|
|
||||||
self.skipped_rows = 0;
|
|
||||||
self.move_previous();
|
|
||||||
self.update_examples();
|
|
||||||
}
|
|
||||||
MenuEvent::MoveUp => {
|
|
||||||
if let Some(example_index) = self.example_index {
|
|
||||||
if let Some(index) = example_index.checked_sub(1) {
|
|
||||||
self.example_index = Some(index);
|
|
||||||
} else {
|
|
||||||
self.example_index = Some(self.examples.len().saturating_sub(1));
|
|
||||||
}
|
|
||||||
} else if !self.examples.is_empty() {
|
|
||||||
self.example_index = Some(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MenuEvent::MoveDown => {
|
|
||||||
if let Some(example_index) = self.example_index {
|
|
||||||
let index = example_index + 1;
|
|
||||||
if index < self.examples.len() {
|
|
||||||
self.example_index = Some(index);
|
|
||||||
} else {
|
|
||||||
self.example_index = Some(0);
|
|
||||||
}
|
|
||||||
} else if !self.examples.is_empty() {
|
|
||||||
self.example_index = Some(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MenuEvent::MoveLeft => self.skipped_rows = self.skipped_rows.saturating_sub(1),
|
|
||||||
MenuEvent::MoveRight => {
|
|
||||||
let skipped = self.skipped_rows + 1;
|
|
||||||
let description_rows = self
|
|
||||||
.get_value()
|
|
||||||
.and_then(|suggestion| suggestion.description)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.lines()
|
|
||||||
.count();
|
|
||||||
|
|
||||||
let allowed_skips =
|
|
||||||
description_rows.saturating_sub(self.working_details.description_rows);
|
|
||||||
|
|
||||||
if skipped < allowed_skips {
|
|
||||||
self.skipped_rows = skipped;
|
|
||||||
} else {
|
|
||||||
self.skipped_rows = allowed_skips;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MenuEvent::PreviousPage | MenuEvent::NextPage => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The buffer gets replaced in the Span location
|
|
||||||
fn replace_in_buffer(&self, editor: &mut Editor) {
|
|
||||||
if let Some(Suggestion { value, span, .. }) = self.get_value() {
|
|
||||||
let start = span.start.min(editor.line_buffer().len());
|
|
||||||
let end = span.end.min(editor.line_buffer().len());
|
|
||||||
|
|
||||||
let replacement = if let Some(example_index) = self.example_index {
|
|
||||||
self.examples
|
|
||||||
.get(example_index)
|
|
||||||
.expect("the example index is always checked")
|
|
||||||
} else {
|
|
||||||
&value
|
|
||||||
};
|
|
||||||
|
|
||||||
editor.edit_buffer(
|
|
||||||
|lb| {
|
|
||||||
lb.replace_range(start..end, replacement);
|
|
||||||
let mut offset = lb.insertion_point();
|
|
||||||
offset += lb
|
|
||||||
.len()
|
|
||||||
.saturating_sub(end.saturating_sub(start))
|
|
||||||
.saturating_sub(start);
|
|
||||||
lb.set_insertion_point(offset);
|
|
||||||
},
|
|
||||||
UndoBehavior::CreateUndoPoint,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Minimum rows that should be displayed by the menu
|
|
||||||
fn min_rows(&self) -> u16 {
|
|
||||||
self.get_rows().min(self.min_rows)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets values from filler that will be displayed in the menu
|
|
||||||
fn get_values(&self) -> &[Suggestion] {
|
|
||||||
&self.values
|
|
||||||
}
|
|
||||||
|
|
||||||
fn menu_required_lines(&self, _terminal_columns: u16) -> u16 {
|
|
||||||
let example_lines = self
|
|
||||||
.examples
|
|
||||||
.iter()
|
|
||||||
.fold(0, |acc, example| example.lines().count() + acc);
|
|
||||||
|
|
||||||
self.default_details.selection_rows
|
|
||||||
+ self.default_details.description_rows as u16
|
|
||||||
+ example_lines as u16
|
|
||||||
+ 3
|
|
||||||
}
|
|
||||||
|
|
||||||
fn menu_string(&self, _available_lines: u16, use_ansi_coloring: bool) -> String {
|
|
||||||
if self.get_values().is_empty() {
|
|
||||||
self.no_records_msg(use_ansi_coloring)
|
|
||||||
} else {
|
|
||||||
// The skip values represent the number of lines that should be skipped
|
|
||||||
// while printing the menu
|
|
||||||
let available_lines = self.default_details.selection_rows;
|
|
||||||
let skip_values = if self.row_pos >= available_lines {
|
|
||||||
let skip_lines = self.row_pos.saturating_sub(available_lines) + 1;
|
|
||||||
(skip_lines * self.get_cols()) as usize
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
// It seems that crossterm prefers to have a complete string ready to be printed
|
|
||||||
// rather than looping through the values and printing multiple things
|
|
||||||
// This reduces the flickering when printing the menu
|
|
||||||
let available_values = (available_lines * self.get_cols()) as usize;
|
|
||||||
let selection_values: String = self
|
|
||||||
.get_values()
|
|
||||||
.iter()
|
|
||||||
.skip(skip_values)
|
|
||||||
.take(available_values)
|
|
||||||
.enumerate()
|
|
||||||
.map(|(index, suggestion)| {
|
|
||||||
// Correcting the enumerate index based on the number of skipped values
|
|
||||||
let index = index + skip_values;
|
|
||||||
let column = index as u16 % self.get_cols();
|
|
||||||
let empty_space = self.get_width().saturating_sub(suggestion.value.len());
|
|
||||||
|
|
||||||
self.create_entry_string(
|
|
||||||
suggestion,
|
|
||||||
index,
|
|
||||||
column,
|
|
||||||
empty_space,
|
|
||||||
use_ansi_coloring,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
format!(
|
|
||||||
"{}{}{}",
|
|
||||||
selection_values,
|
|
||||||
self.create_description_string(use_ansi_coloring),
|
|
||||||
self.create_example_string(use_ansi_coloring)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,8 +2,7 @@ use nu_engine::documentation::get_flags_section;
|
|||||||
use nu_protocol::{engine::EngineState, levenshtein_distance};
|
use nu_protocol::{engine::EngineState, levenshtein_distance};
|
||||||
use nu_utils::IgnoreCaseExt;
|
use nu_utils::IgnoreCaseExt;
|
||||||
use reedline::{Completer, Suggestion};
|
use reedline::{Completer, Suggestion};
|
||||||
use std::fmt::Write;
|
use std::{fmt::Write, sync::Arc};
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub struct NuHelpCompleter(Arc<EngineState>);
|
pub struct NuHelpCompleter(Arc<EngineState>);
|
||||||
|
|
||||||
@ -57,7 +56,7 @@ impl NuHelpCompleter {
|
|||||||
|
|
||||||
if !sig.named.is_empty() {
|
if !sig.named.is_empty() {
|
||||||
long_desc.push_str(&get_flags_section(Some(&*self.0.clone()), sig, |v| {
|
long_desc.push_str(&get_flags_section(Some(&*self.0.clone()), sig, |v| {
|
||||||
v.into_string_parsable(", ", &self.0.config)
|
v.to_parsable_string(", ", &self.0.config)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +72,7 @@ impl NuHelpCompleter {
|
|||||||
let opt_suffix = if let Some(value) = &positional.default_value {
|
let opt_suffix = if let Some(value) = &positional.default_value {
|
||||||
format!(
|
format!(
|
||||||
" (optional, default: {})",
|
" (optional, default: {})",
|
||||||
&value.into_string_parsable(", ", &self.0.config),
|
&value.to_parsable_string(", ", &self.0.config),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(" (optional)").to_string()
|
(" (optional)").to_string()
|
||||||
@ -102,10 +101,11 @@ impl NuHelpCompleter {
|
|||||||
Suggestion {
|
Suggestion {
|
||||||
value: sig.name.clone(),
|
value: sig.name.clone(),
|
||||||
description: Some(long_desc),
|
description: Some(long_desc),
|
||||||
|
style: None,
|
||||||
extra: Some(extra),
|
extra: Some(extra),
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: pos,
|
start: pos - line.len(),
|
||||||
end: pos + line.len(),
|
end: pos,
|
||||||
},
|
},
|
||||||
append_whitespace: false,
|
append_whitespace: false,
|
||||||
}
|
}
|
||||||
@ -119,3 +119,42 @@ impl Completer for NuHelpCompleter {
|
|||||||
self.completion_helper(line, pos)
|
self.completion_helper(line, pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case("who", 5, 8, &["whoami"])]
|
||||||
|
#[case("hash", 1, 5, &["hash", "hash md5", "hash sha256"])]
|
||||||
|
#[case("into f", 0, 6, &["into float", "into filesize"])]
|
||||||
|
#[case("into nonexistent", 0, 16, &[])]
|
||||||
|
fn test_help_completer(
|
||||||
|
#[case] line: &str,
|
||||||
|
#[case] start: usize,
|
||||||
|
#[case] end: usize,
|
||||||
|
#[case] expected: &[&str],
|
||||||
|
) {
|
||||||
|
let engine_state =
|
||||||
|
nu_command::add_shell_command_context(nu_cmd_lang::create_default_context());
|
||||||
|
let mut completer = NuHelpCompleter::new(engine_state.into());
|
||||||
|
let suggestions = completer.complete(line, end);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
expected.len(),
|
||||||
|
suggestions.len(),
|
||||||
|
"expected {:?}, got {:?}",
|
||||||
|
expected,
|
||||||
|
suggestions
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.value.clone())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
|
||||||
|
for (exp, actual) in expected.iter().zip(suggestions) {
|
||||||
|
assert_eq!(exp, &actual.value);
|
||||||
|
assert_eq!(reedline::Span::new(start, end), actual.span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
|
debugger::WithoutDebug,
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
IntoPipelineData, Span, Value,
|
IntoPipelineData, Span, Value,
|
||||||
};
|
};
|
||||||
@ -27,7 +28,7 @@ impl NuMenuCompleter {
|
|||||||
Self {
|
Self {
|
||||||
block_id,
|
block_id,
|
||||||
span,
|
span,
|
||||||
stack,
|
stack: stack.reset_out_dest().capture(),
|
||||||
engine_state,
|
engine_state,
|
||||||
only_buffer_difference,
|
only_buffer_difference,
|
||||||
}
|
}
|
||||||
@ -55,14 +56,8 @@ impl Completer for NuMenuCompleter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let input = Value::nothing(self.span).into_pipeline_data();
|
let input = Value::nothing(self.span).into_pipeline_data();
|
||||||
let res = eval_block(
|
|
||||||
&self.engine_state,
|
let res = eval_block::<WithoutDebug>(&self.engine_state, &mut self.stack, block, input);
|
||||||
&mut self.stack,
|
|
||||||
block,
|
|
||||||
input,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Ok(values) = res {
|
if let Ok(values) = res {
|
||||||
let values = values.into_value(self.span);
|
let values = values.into_value(self.span);
|
||||||
@ -83,10 +78,12 @@ fn convert_to_suggestions(
|
|||||||
Value::Record { val, .. } => {
|
Value::Record { val, .. } => {
|
||||||
let text = val
|
let text = val
|
||||||
.get("value")
|
.get("value")
|
||||||
.and_then(|val| val.as_string().ok())
|
.and_then(|val| val.coerce_string().ok())
|
||||||
.unwrap_or_else(|| "No value key".to_string());
|
.unwrap_or_else(|| "No value key".to_string());
|
||||||
|
|
||||||
let description = val.get("description").and_then(|val| val.as_string().ok());
|
let description = val
|
||||||
|
.get("description")
|
||||||
|
.and_then(|val| val.coerce_string().ok());
|
||||||
|
|
||||||
let span = match val.get("span") {
|
let span = match val.get("span") {
|
||||||
Some(Value::Record { val: span, .. }) => {
|
Some(Value::Record { val: span, .. }) => {
|
||||||
@ -101,9 +98,13 @@ fn convert_to_suggestions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => reedline::Span {
|
_ => reedline::Span {
|
||||||
start: if only_buffer_difference { pos } else { 0 },
|
start: if only_buffer_difference {
|
||||||
|
pos - line.len()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
end: if only_buffer_difference {
|
end: if only_buffer_difference {
|
||||||
pos + line.len()
|
pos
|
||||||
} else {
|
} else {
|
||||||
line.len()
|
line.len()
|
||||||
},
|
},
|
||||||
@ -111,9 +112,13 @@ fn convert_to_suggestions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => reedline::Span {
|
_ => reedline::Span {
|
||||||
start: if only_buffer_difference { pos } else { 0 },
|
start: if only_buffer_difference {
|
||||||
|
pos - line.len()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
end: if only_buffer_difference {
|
end: if only_buffer_difference {
|
||||||
pos + line.len()
|
pos
|
||||||
} else {
|
} else {
|
||||||
line.len()
|
line.len()
|
||||||
},
|
},
|
||||||
@ -138,6 +143,7 @@ fn convert_to_suggestions(
|
|||||||
vec![Suggestion {
|
vec![Suggestion {
|
||||||
value: text,
|
value: text,
|
||||||
description,
|
description,
|
||||||
|
style: None,
|
||||||
extra,
|
extra,
|
||||||
span,
|
span,
|
||||||
append_whitespace: false,
|
append_whitespace: false,
|
||||||
@ -150,10 +156,19 @@ fn convert_to_suggestions(
|
|||||||
_ => vec![Suggestion {
|
_ => vec![Suggestion {
|
||||||
value: format!("Not a record: {value:?}"),
|
value: format!("Not a record: {value:?}"),
|
||||||
description: None,
|
description: None,
|
||||||
|
style: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: 0,
|
start: if only_buffer_difference {
|
||||||
end: line.len(),
|
pos - line.len()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
|
end: if only_buffer_difference {
|
||||||
|
pos
|
||||||
|
} else {
|
||||||
|
line.len()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
append_whitespace: false,
|
append_whitespace: false,
|
||||||
}],
|
}],
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
mod description_menu;
|
|
||||||
mod help_completions;
|
mod help_completions;
|
||||||
mod menu_completions;
|
mod menu_completions;
|
||||||
|
|
||||||
pub use description_menu::DescriptionMenu;
|
|
||||||
pub use help_completions::NuHelpCompleter;
|
pub use help_completions::NuHelpCompleter;
|
||||||
pub use menu_completions::NuMenuCompleter;
|
pub use menu_completions::NuMenuCompleter;
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
use nu_protocol::ast::Call;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use reedline::{Highlighter, StyledText};
|
||||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Type, Value};
|
|
||||||
use reedline::Highlighter;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct NuHighlight;
|
pub struct NuHighlight;
|
||||||
@ -28,7 +26,7 @@ impl Command for NuHighlight {
|
|||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
_stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
@ -40,14 +38,14 @@ impl Command for NuHighlight {
|
|||||||
|
|
||||||
let highlighter = crate::NuHighlighter {
|
let highlighter = crate::NuHighlighter {
|
||||||
engine_state,
|
engine_state,
|
||||||
|
stack: std::sync::Arc::new(stack.clone()),
|
||||||
config,
|
config,
|
||||||
};
|
};
|
||||||
|
|
||||||
input.map(
|
input.map(
|
||||||
move |x| match x.as_string() {
|
move |x| match x.coerce_into_string() {
|
||||||
Ok(line) => {
|
Ok(line) => {
|
||||||
let highlights = highlighter.highlight(&line, line.len());
|
let highlights = highlighter.highlight(&line, line.len());
|
||||||
|
|
||||||
Value::string(highlights.render_simple(), head)
|
Value::string(highlights.render_simple(), head)
|
||||||
}
|
}
|
||||||
Err(err) => Value::error(err, head),
|
Err(err) => Value::error(err, head),
|
||||||
@ -64,3 +62,16 @@ impl Command for NuHighlight {
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A highlighter that does nothing
|
||||||
|
///
|
||||||
|
/// Used to remove highlighting from a reedline instance
|
||||||
|
/// (letting NuHighlighter structs be dropped)
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct NoOpHighlighter {}
|
||||||
|
|
||||||
|
impl Highlighter for NoOpHighlighter {
|
||||||
|
fn highlight(&self, _line: &str, _cursor: usize) -> reedline::StyledText {
|
||||||
|
StyledText::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,10 +1,4 @@
|
|||||||
use nu_engine::CallExt;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::ast::Call;
|
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
|
||||||
use nu_protocol::{
|
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type,
|
|
||||||
Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Print;
|
pub struct Print;
|
||||||
@ -54,8 +48,8 @@ Since this command has no output, there is no point in piping it with other comm
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||||
let no_newline = call.has_flag("no-newline");
|
let no_newline = call.has_flag(engine_state, stack, "no-newline")?;
|
||||||
let to_stderr = call.has_flag("stderr");
|
let to_stderr = call.has_flag(engine_state, stack, "stderr")?;
|
||||||
|
|
||||||
// This will allow for easy printing of pipelines as well
|
// This will allow for easy printing of pipelines as well
|
||||||
if !args.is_empty() {
|
if !args.is_empty() {
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
use crate::prompt_update::{POST_PROMPT_MARKER, PRE_PROMPT_MARKER};
|
use crate::prompt_update::{POST_PROMPT_MARKER, PRE_PROMPT_MARKER};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use nu_utils::enable_vt_processing;
|
use nu_utils::enable_vt_processing;
|
||||||
use reedline::DefaultPrompt;
|
use reedline::{
|
||||||
use {
|
DefaultPrompt, Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus,
|
||||||
reedline::{
|
PromptViMode,
|
||||||
Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode,
|
|
||||||
},
|
|
||||||
std::borrow::Cow,
|
|
||||||
};
|
};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// Nushell prompt definition
|
/// Nushell prompt definition
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
use crate::NushellPrompt;
|
use crate::NushellPrompt;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use nu_engine::eval_subexpression;
|
use nu_engine::ClosureEvalOnce;
|
||||||
use nu_protocol::report_error;
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
Config, PipelineData, Value,
|
report_error, Config, PipelineData, Value,
|
||||||
};
|
};
|
||||||
use reedline::Prompt;
|
use reedline::Prompt;
|
||||||
|
|
||||||
@ -39,11 +38,9 @@ fn get_prompt_string(
|
|||||||
.get_env_var(engine_state, prompt)
|
.get_env_var(engine_state, prompt)
|
||||||
.and_then(|v| match v {
|
.and_then(|v| match v {
|
||||||
Value::Closure { val, .. } => {
|
Value::Closure { val, .. } => {
|
||||||
let block = engine_state.get_block(val.block_id);
|
let result = ClosureEvalOnce::new(engine_state, stack, val)
|
||||||
let mut stack = stack.captures_to_stack(val.captures);
|
.run_with_input(PipelineData::Empty);
|
||||||
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
|
|
||||||
let ret_val =
|
|
||||||
eval_subexpression(engine_state, &mut stack, block, PipelineData::empty());
|
|
||||||
trace!(
|
trace!(
|
||||||
"get_prompt_string (block) {}:{}:{}",
|
"get_prompt_string (block) {}:{}:{}",
|
||||||
file!(),
|
file!(),
|
||||||
@ -51,25 +48,7 @@ fn get_prompt_string(
|
|||||||
column!()
|
column!()
|
||||||
);
|
);
|
||||||
|
|
||||||
ret_val
|
result
|
||||||
.map_err(|err| {
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
report_error(&working_set, &err);
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
Value::Block { val: block_id, .. } => {
|
|
||||||
let block = engine_state.get_block(block_id);
|
|
||||||
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
|
|
||||||
let ret_val = eval_subexpression(engine_state, stack, block, PipelineData::empty());
|
|
||||||
trace!(
|
|
||||||
"get_prompt_string (block) {}:{}:{}",
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!()
|
|
||||||
);
|
|
||||||
|
|
||||||
ret_val
|
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
report_error(&working_set, &err);
|
report_error(&working_set, &err);
|
||||||
@ -99,12 +78,10 @@ fn get_prompt_string(
|
|||||||
pub(crate) fn update_prompt(
|
pub(crate) fn update_prompt(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &Stack,
|
stack: &mut Stack,
|
||||||
nu_prompt: &mut NushellPrompt,
|
nu_prompt: &mut NushellPrompt,
|
||||||
) {
|
) {
|
||||||
let mut stack = stack.clone();
|
let left_prompt_string = get_prompt_string(PROMPT_COMMAND, config, engine_state, stack);
|
||||||
|
|
||||||
let left_prompt_string = get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack);
|
|
||||||
|
|
||||||
// Now that we have the prompt string lets ansify it.
|
// Now that we have the prompt string lets ansify it.
|
||||||
// <133 A><prompt><133 B><command><133 C><command output>
|
// <133 A><prompt><133 B><command><133 C><command output>
|
||||||
@ -120,20 +97,18 @@ pub(crate) fn update_prompt(
|
|||||||
left_prompt_string
|
left_prompt_string
|
||||||
};
|
};
|
||||||
|
|
||||||
let right_prompt_string =
|
let right_prompt_string = get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, stack);
|
||||||
get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, &mut stack);
|
|
||||||
|
|
||||||
let prompt_indicator_string =
|
let prompt_indicator_string = get_prompt_string(PROMPT_INDICATOR, config, engine_state, stack);
|
||||||
get_prompt_string(PROMPT_INDICATOR, config, engine_state, &mut stack);
|
|
||||||
|
|
||||||
let prompt_multiline_string =
|
let prompt_multiline_string =
|
||||||
get_prompt_string(PROMPT_MULTILINE_INDICATOR, config, engine_state, &mut stack);
|
get_prompt_string(PROMPT_MULTILINE_INDICATOR, config, engine_state, stack);
|
||||||
|
|
||||||
let prompt_vi_insert_string =
|
let prompt_vi_insert_string =
|
||||||
get_prompt_string(PROMPT_INDICATOR_VI_INSERT, config, engine_state, &mut stack);
|
get_prompt_string(PROMPT_INDICATOR_VI_INSERT, config, engine_state, stack);
|
||||||
|
|
||||||
let prompt_vi_normal_string =
|
let prompt_vi_normal_string =
|
||||||
get_prompt_string(PROMPT_INDICATOR_VI_NORMAL, config, engine_state, &mut stack);
|
get_prompt_string(PROMPT_INDICATOR_VI_NORMAL, config, engine_state, stack);
|
||||||
|
|
||||||
// apply the other indicators
|
// apply the other indicators
|
||||||
nu_prompt.update_all_prompt_strings(
|
nu_prompt.update_all_prompt_strings(
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
use super::DescriptionMenu;
|
|
||||||
use crate::{menus::NuMenuCompleter, NuHelpCompleter};
|
use crate::{menus::NuMenuCompleter, NuHelpCompleter};
|
||||||
use crossterm::event::{KeyCode, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyModifiers};
|
||||||
|
use log::trace;
|
||||||
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
|
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
|
||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
create_menus,
|
create_menus,
|
||||||
|
debugger::WithoutDebug,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
extract_value, Config, EditBindings, ParsedKeybinding, ParsedMenu, PipelineData, Record,
|
extract_value, Config, EditBindings, ParsedKeybinding, ParsedMenu, PipelineData, Record,
|
||||||
ShellError, Span, Value,
|
ShellError, Span, Value,
|
||||||
};
|
};
|
||||||
use reedline::{
|
use reedline::{
|
||||||
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
|
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
|
||||||
ColumnarMenu, EditCommand, Keybindings, ListMenu, Reedline, ReedlineEvent, ReedlineMenu,
|
ColumnarMenu, DescriptionMenu, DescriptionMode, EditCommand, IdeMenu, Keybindings, ListMenu,
|
||||||
|
MenuBuilder, Reedline, ReedlineEvent, ReedlineMenu,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -77,6 +79,7 @@ pub(crate) fn add_menus(
|
|||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<Reedline, ShellError> {
|
) -> Result<Reedline, ShellError> {
|
||||||
|
trace!("add_menus: config: {:#?}", &config);
|
||||||
line_editor = line_editor.clear_menus();
|
line_editor = line_editor.clear_menus();
|
||||||
|
|
||||||
for menu in &config.menus {
|
for menu in &config.menus {
|
||||||
@ -84,7 +87,7 @@ pub(crate) fn add_menus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Checking if the default menus have been added from the config file
|
// Checking if the default menus have been added from the config file
|
||||||
let default_menus = vec![
|
let default_menus = [
|
||||||
("completion_menu", DEFAULT_COMPLETION_MENU),
|
("completion_menu", DEFAULT_COMPLETION_MENU),
|
||||||
("history_menu", DEFAULT_HISTORY_MENU),
|
("history_menu", DEFAULT_HISTORY_MENU),
|
||||||
("help_menu", DEFAULT_HELP_MENU),
|
("help_menu", DEFAULT_HELP_MENU),
|
||||||
@ -94,7 +97,7 @@ pub(crate) fn add_menus(
|
|||||||
if !config
|
if !config
|
||||||
.menus
|
.menus
|
||||||
.iter()
|
.iter()
|
||||||
.any(|menu| menu.name.into_string("", config) == name)
|
.any(|menu| menu.name.to_expanded_string("", config) == name)
|
||||||
{
|
{
|
||||||
let (block, _) = {
|
let (block, _) = {
|
||||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
@ -108,9 +111,9 @@ pub(crate) fn add_menus(
|
|||||||
(output, working_set.render())
|
(output, working_set.render())
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut temp_stack = Stack::new();
|
let mut temp_stack = Stack::new().capture();
|
||||||
let input = PipelineData::Empty;
|
let input = PipelineData::Empty;
|
||||||
let res = eval_block(&engine_state, &mut temp_stack, &block, input, false, false)?;
|
let res = eval_block::<WithoutDebug>(&engine_state, &mut temp_stack, &block, input)?;
|
||||||
|
|
||||||
if let PipelineData::Value(value, None) = res {
|
if let PipelineData::Value(value, None) = res {
|
||||||
for menu in create_menus(&value)? {
|
for menu in create_menus(&value)? {
|
||||||
@ -133,22 +136,23 @@ fn add_menu(
|
|||||||
) -> Result<Reedline, ShellError> {
|
) -> Result<Reedline, ShellError> {
|
||||||
let span = menu.menu_type.span();
|
let span = menu.menu_type.span();
|
||||||
if let Value::Record { val, .. } = &menu.menu_type {
|
if let Value::Record { val, .. } = &menu.menu_type {
|
||||||
let layout = extract_value("layout", val, span)?.into_string("", config);
|
let layout = extract_value("layout", val, span)?.to_expanded_string("", config);
|
||||||
|
|
||||||
match layout.as_str() {
|
match layout.as_str() {
|
||||||
"columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config),
|
"columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config),
|
||||||
"list" => add_list_menu(line_editor, menu, engine_state, stack, config),
|
"list" => add_list_menu(line_editor, menu, engine_state, stack, config),
|
||||||
|
"ide" => add_ide_menu(line_editor, menu, engine_state, stack, config),
|
||||||
"description" => add_description_menu(line_editor, menu, engine_state, stack, config),
|
"description" => add_description_menu(line_editor, menu, engine_state, stack, config),
|
||||||
_ => Err(ShellError::UnsupportedConfigValue {
|
_ => Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "columnar, list or description".to_string(),
|
expected: "columnar, list, ide or description".to_string(),
|
||||||
value: menu.menu_type.into_abbreviated_string(config),
|
value: menu.menu_type.to_abbreviated_string(config),
|
||||||
span: menu.menu_type.span(),
|
span: menu.menu_type.span(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::UnsupportedConfigValue {
|
Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "only record type".to_string(),
|
expected: "only record type".to_string(),
|
||||||
value: menu.menu_type.into_abbreviated_string(config),
|
value: menu.menu_type.to_abbreviated_string(config),
|
||||||
span: menu.menu_type.span(),
|
span: menu.menu_type.span(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -180,7 +184,7 @@ pub(crate) fn add_columnar_menu(
|
|||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<Reedline, ShellError> {
|
) -> Result<Reedline, ShellError> {
|
||||||
let span = menu.menu_type.span();
|
let span = menu.menu_type.span();
|
||||||
let name = menu.name.into_string("", config);
|
let name = menu.name.to_expanded_string("", config);
|
||||||
let mut columnar_menu = ColumnarMenu::default().with_name(&name);
|
let mut columnar_menu = ColumnarMenu::default().with_name(&name);
|
||||||
|
|
||||||
if let Value::Record { val, .. } = &menu.menu_type {
|
if let Value::Record { val, .. } = &menu.menu_type {
|
||||||
@ -235,10 +239,26 @@ pub(crate) fn add_columnar_menu(
|
|||||||
columnar_menu,
|
columnar_menu,
|
||||||
ColumnarMenu::with_description_text_style
|
ColumnarMenu::with_description_text_style
|
||||||
);
|
);
|
||||||
|
add_style!(
|
||||||
|
"match_text",
|
||||||
|
val,
|
||||||
|
span,
|
||||||
|
config,
|
||||||
|
columnar_menu,
|
||||||
|
ColumnarMenu::with_match_text_style
|
||||||
|
);
|
||||||
|
add_style!(
|
||||||
|
"selected_match_text",
|
||||||
|
val,
|
||||||
|
span,
|
||||||
|
config,
|
||||||
|
columnar_menu,
|
||||||
|
ColumnarMenu::with_selected_match_text_style
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let marker = menu.marker.into_string("", config);
|
let marker = menu.marker.to_expanded_string("", config);
|
||||||
columnar_menu = columnar_menu.with_marker(marker);
|
columnar_menu = columnar_menu.with_marker(&marker);
|
||||||
|
|
||||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||||
columnar_menu = columnar_menu.with_only_buffer_difference(only_buffer_difference);
|
columnar_menu = columnar_menu.with_only_buffer_difference(only_buffer_difference);
|
||||||
@ -263,7 +283,7 @@ pub(crate) fn add_columnar_menu(
|
|||||||
}
|
}
|
||||||
_ => Err(ShellError::UnsupportedConfigValue {
|
_ => Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "block or omitted value".to_string(),
|
expected: "block or omitted value".to_string(),
|
||||||
value: menu.source.into_abbreviated_string(config),
|
value: menu.source.to_abbreviated_string(config),
|
||||||
span,
|
span,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -277,7 +297,7 @@ pub(crate) fn add_list_menu(
|
|||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<Reedline, ShellError> {
|
) -> Result<Reedline, ShellError> {
|
||||||
let name = menu.name.into_string("", config);
|
let name = menu.name.to_expanded_string("", config);
|
||||||
let mut list_menu = ListMenu::default().with_name(&name);
|
let mut list_menu = ListMenu::default().with_name(&name);
|
||||||
|
|
||||||
let span = menu.menu_type.span();
|
let span = menu.menu_type.span();
|
||||||
@ -319,8 +339,8 @@ pub(crate) fn add_list_menu(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let marker = menu.marker.into_string("", config);
|
let marker = menu.marker.to_expanded_string("", config);
|
||||||
list_menu = list_menu.with_marker(marker);
|
list_menu = list_menu.with_marker(&marker);
|
||||||
|
|
||||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||||
list_menu = list_menu.with_only_buffer_difference(only_buffer_difference);
|
list_menu = list_menu.with_only_buffer_difference(only_buffer_difference);
|
||||||
@ -345,12 +365,235 @@ pub(crate) fn add_list_menu(
|
|||||||
}
|
}
|
||||||
_ => Err(ShellError::UnsupportedConfigValue {
|
_ => Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "block or omitted value".to_string(),
|
expected: "block or omitted value".to_string(),
|
||||||
value: menu.source.into_abbreviated_string(config),
|
value: menu.source.to_abbreviated_string(config),
|
||||||
span: menu.source.span(),
|
span: menu.source.span(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adds an IDE menu to the line editor
|
||||||
|
pub(crate) fn add_ide_menu(
|
||||||
|
line_editor: Reedline,
|
||||||
|
menu: &ParsedMenu,
|
||||||
|
engine_state: Arc<EngineState>,
|
||||||
|
stack: &Stack,
|
||||||
|
config: &Config,
|
||||||
|
) -> Result<Reedline, ShellError> {
|
||||||
|
let span = menu.menu_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 {
|
||||||
|
ide_menu = match extract_value("min_completion_width", val, span) {
|
||||||
|
Ok(min_completion_width) => {
|
||||||
|
let min_completion_width = min_completion_width.as_int()?;
|
||||||
|
ide_menu.with_min_completion_width(min_completion_width as u16)
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("max_completion_width", val, span) {
|
||||||
|
Ok(max_completion_width) => {
|
||||||
|
let max_completion_width = max_completion_width.as_int()?;
|
||||||
|
ide_menu.with_max_completion_width(max_completion_width as u16)
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("max_completion_height", val, span) {
|
||||||
|
Ok(max_completion_height) => {
|
||||||
|
let max_completion_height = max_completion_height.as_int()?;
|
||||||
|
ide_menu.with_max_completion_height(max_completion_height as u16)
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu.with_max_completion_height(10u16),
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("padding", val, span) {
|
||||||
|
Ok(padding) => {
|
||||||
|
let padding = padding.as_int()?;
|
||||||
|
ide_menu.with_padding(padding as u16)
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("border", val, span) {
|
||||||
|
Ok(border) => {
|
||||||
|
if let Ok(border) = border.as_bool() {
|
||||||
|
if border {
|
||||||
|
ide_menu.with_default_border()
|
||||||
|
} else {
|
||||||
|
ide_menu
|
||||||
|
}
|
||||||
|
} else if let Ok(border_chars) = border.as_record() {
|
||||||
|
let top_right = extract_value("top_right", border_chars, span)?.as_char()?;
|
||||||
|
let top_left = extract_value("top_left", border_chars, span)?.as_char()?;
|
||||||
|
let bottom_right =
|
||||||
|
extract_value("bottom_right", border_chars, span)?.as_char()?;
|
||||||
|
let bottom_left =
|
||||||
|
extract_value("bottom_left", border_chars, span)?.as_char()?;
|
||||||
|
let horizontal = extract_value("horizontal", border_chars, span)?.as_char()?;
|
||||||
|
let vertical = extract_value("vertical", border_chars, span)?.as_char()?;
|
||||||
|
|
||||||
|
ide_menu.with_border(
|
||||||
|
top_right,
|
||||||
|
top_left,
|
||||||
|
bottom_right,
|
||||||
|
bottom_left,
|
||||||
|
horizontal,
|
||||||
|
vertical,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
|
expected: "bool or record".to_string(),
|
||||||
|
value: border.to_abbreviated_string(config),
|
||||||
|
span: border.span(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu.with_default_border(),
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("cursor_offset", val, span) {
|
||||||
|
Ok(cursor_offset) => {
|
||||||
|
let cursor_offset = cursor_offset.as_int()?;
|
||||||
|
ide_menu.with_cursor_offset(cursor_offset as i16)
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("description_mode", val, span) {
|
||||||
|
Ok(description_mode) => match description_mode.coerce_str()?.as_ref() {
|
||||||
|
"left" => ide_menu.with_description_mode(DescriptionMode::Left),
|
||||||
|
"right" => ide_menu.with_description_mode(DescriptionMode::Right),
|
||||||
|
"prefer_right" => ide_menu.with_description_mode(DescriptionMode::PreferRight),
|
||||||
|
_ => {
|
||||||
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
|
expected: "\"left\", \"right\" or \"prefer_right\"".to_string(),
|
||||||
|
value: description_mode.to_abbreviated_string(config),
|
||||||
|
span: description_mode.span(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("min_description_width", val, span) {
|
||||||
|
Ok(min_description_width) => {
|
||||||
|
let min_description_width = min_description_width.as_int()?;
|
||||||
|
ide_menu.with_min_description_width(min_description_width as u16)
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("max_description_width", val, span) {
|
||||||
|
Ok(max_description_width) => {
|
||||||
|
let max_description_width = max_description_width.as_int()?;
|
||||||
|
ide_menu.with_max_description_width(max_description_width as u16)
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("max_description_height", val, span) {
|
||||||
|
Ok(max_description_height) => {
|
||||||
|
let max_description_height = max_description_height.as_int()?;
|
||||||
|
ide_menu.with_max_description_height(max_description_height as u16)
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("description_offset", val, span) {
|
||||||
|
Ok(description_padding) => {
|
||||||
|
let description_padding = description_padding.as_int()?;
|
||||||
|
ide_menu.with_description_offset(description_padding as u16)
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
ide_menu = match extract_value("correct_cursor_pos", val, span) {
|
||||||
|
Ok(correct_cursor_pos) => {
|
||||||
|
let correct_cursor_pos = correct_cursor_pos.as_bool()?;
|
||||||
|
ide_menu.with_correct_cursor_pos(correct_cursor_pos)
|
||||||
|
}
|
||||||
|
Err(_) => ide_menu,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let span = menu.style.span();
|
||||||
|
if let Value::Record { val, .. } = &menu.style {
|
||||||
|
add_style!(
|
||||||
|
"text",
|
||||||
|
val,
|
||||||
|
span,
|
||||||
|
config,
|
||||||
|
ide_menu,
|
||||||
|
IdeMenu::with_text_style
|
||||||
|
);
|
||||||
|
add_style!(
|
||||||
|
"selected_text",
|
||||||
|
val,
|
||||||
|
span,
|
||||||
|
config,
|
||||||
|
ide_menu,
|
||||||
|
IdeMenu::with_selected_text_style
|
||||||
|
);
|
||||||
|
add_style!(
|
||||||
|
"description_text",
|
||||||
|
val,
|
||||||
|
span,
|
||||||
|
config,
|
||||||
|
ide_menu,
|
||||||
|
IdeMenu::with_description_text_style
|
||||||
|
);
|
||||||
|
add_style!(
|
||||||
|
"match_text",
|
||||||
|
val,
|
||||||
|
span,
|
||||||
|
config,
|
||||||
|
ide_menu,
|
||||||
|
IdeMenu::with_match_text_style
|
||||||
|
);
|
||||||
|
add_style!(
|
||||||
|
"selected_match_text",
|
||||||
|
val,
|
||||||
|
span,
|
||||||
|
config,
|
||||||
|
ide_menu,
|
||||||
|
IdeMenu::with_selected_match_text_style
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let marker = menu.marker.to_expanded_string("", config);
|
||||||
|
ide_menu = ide_menu.with_marker(&marker);
|
||||||
|
|
||||||
|
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||||
|
ide_menu = ide_menu.with_only_buffer_difference(only_buffer_difference);
|
||||||
|
|
||||||
|
let span = menu.source.span();
|
||||||
|
match &menu.source {
|
||||||
|
Value::Nothing { .. } => {
|
||||||
|
Ok(line_editor.with_menu(ReedlineMenu::EngineCompleter(Box::new(ide_menu))))
|
||||||
|
}
|
||||||
|
Value::Closure { val, .. } => {
|
||||||
|
let menu_completer = NuMenuCompleter::new(
|
||||||
|
val.block_id,
|
||||||
|
span,
|
||||||
|
stack.captures_to_stack(val.captures.clone()),
|
||||||
|
engine_state,
|
||||||
|
only_buffer_difference,
|
||||||
|
);
|
||||||
|
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
||||||
|
menu: Box::new(ide_menu),
|
||||||
|
completer: Box::new(menu_completer),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
_ => Err(ShellError::UnsupportedConfigValue {
|
||||||
|
expected: "block or omitted value".to_string(),
|
||||||
|
value: menu.source.to_abbreviated_string(config),
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Adds a description menu to the line editor
|
// Adds a description menu to the line editor
|
||||||
pub(crate) fn add_description_menu(
|
pub(crate) fn add_description_menu(
|
||||||
line_editor: Reedline,
|
line_editor: Reedline,
|
||||||
@ -359,7 +602,7 @@ pub(crate) fn add_description_menu(
|
|||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<Reedline, ShellError> {
|
) -> Result<Reedline, ShellError> {
|
||||||
let name = menu.name.into_string("", config);
|
let name = menu.name.to_expanded_string("", config);
|
||||||
let mut description_menu = DescriptionMenu::default().with_name(&name);
|
let mut description_menu = DescriptionMenu::default().with_name(&name);
|
||||||
|
|
||||||
let span = menu.menu_type.span();
|
let span = menu.menu_type.span();
|
||||||
@ -433,8 +676,8 @@ pub(crate) fn add_description_menu(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let marker = menu.marker.into_string("", config);
|
let marker = menu.marker.to_expanded_string("", config);
|
||||||
description_menu = description_menu.with_marker(marker);
|
description_menu = description_menu.with_marker(&marker);
|
||||||
|
|
||||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||||
description_menu = description_menu.with_only_buffer_difference(only_buffer_difference);
|
description_menu = description_menu.with_only_buffer_difference(only_buffer_difference);
|
||||||
@ -463,7 +706,7 @@ pub(crate) fn add_description_menu(
|
|||||||
}
|
}
|
||||||
_ => Err(ShellError::UnsupportedConfigValue {
|
_ => Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "closure or omitted value".to_string(),
|
expected: "closure or omitted value".to_string(),
|
||||||
value: menu.source.into_abbreviated_string(config),
|
value: menu.source.to_abbreviated_string(config),
|
||||||
span: menu.source.span(),
|
span: menu.source.span(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -602,7 +845,7 @@ fn add_keybinding(
|
|||||||
}
|
}
|
||||||
v => Err(ShellError::UnsupportedConfigValue {
|
v => Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "string or list of strings".to_string(),
|
expected: "string or list of strings".to_string(),
|
||||||
value: v.into_abbreviated_string(config),
|
value: v.to_abbreviated_string(config),
|
||||||
span: v.span(),
|
span: v.span(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -615,7 +858,7 @@ fn add_parsed_keybinding(
|
|||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let modifier = match keybinding
|
let modifier = match keybinding
|
||||||
.modifier
|
.modifier
|
||||||
.into_string("", config)
|
.to_expanded_string("", config)
|
||||||
.to_ascii_lowercase()
|
.to_ascii_lowercase()
|
||||||
.as_str()
|
.as_str()
|
||||||
{
|
{
|
||||||
@ -632,7 +875,7 @@ fn add_parsed_keybinding(
|
|||||||
_ => {
|
_ => {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "CONTROL, SHIFT, ALT or NONE".to_string(),
|
expected: "CONTROL, SHIFT, ALT or NONE".to_string(),
|
||||||
value: keybinding.modifier.into_abbreviated_string(config),
|
value: keybinding.modifier.to_abbreviated_string(config),
|
||||||
span: keybinding.modifier.span(),
|
span: keybinding.modifier.span(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -640,7 +883,7 @@ fn add_parsed_keybinding(
|
|||||||
|
|
||||||
let keycode = match keybinding
|
let keycode = match keybinding
|
||||||
.keycode
|
.keycode
|
||||||
.into_string("", config)
|
.to_expanded_string("", config)
|
||||||
.to_ascii_lowercase()
|
.to_ascii_lowercase()
|
||||||
.as_str()
|
.as_str()
|
||||||
{
|
{
|
||||||
@ -693,7 +936,7 @@ fn add_parsed_keybinding(
|
|||||||
_ => {
|
_ => {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "crossterm KeyCode".to_string(),
|
expected: "crossterm KeyCode".to_string(),
|
||||||
value: keybinding.keycode.into_abbreviated_string(config),
|
value: keybinding.keycode.to_abbreviated_string(config),
|
||||||
span: keybinding.keycode.span(),
|
span: keybinding.keycode.span(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -731,7 +974,10 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
match value {
|
match value {
|
||||||
Value::Record { val: record, .. } => match EventType::try_from_record(record, span)? {
|
Value::Record { val: record, .. } => match EventType::try_from_record(record, span)? {
|
||||||
EventType::Send(value) => event_from_record(
|
EventType::Send(value) => event_from_record(
|
||||||
value.into_string("", config).to_ascii_lowercase().as_str(),
|
value
|
||||||
|
.to_expanded_string("", config)
|
||||||
|
.to_ascii_lowercase()
|
||||||
|
.as_str(),
|
||||||
record,
|
record,
|
||||||
config,
|
config,
|
||||||
span,
|
span,
|
||||||
@ -739,7 +985,10 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
.map(Some),
|
.map(Some),
|
||||||
EventType::Edit(value) => {
|
EventType::Edit(value) => {
|
||||||
let edit = edit_from_record(
|
let edit = edit_from_record(
|
||||||
value.into_string("", config).to_ascii_lowercase().as_str(),
|
value
|
||||||
|
.to_expanded_string("", config)
|
||||||
|
.to_ascii_lowercase()
|
||||||
|
.as_str(),
|
||||||
record,
|
record,
|
||||||
config,
|
config,
|
||||||
span,
|
span,
|
||||||
@ -767,7 +1016,7 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
}
|
}
|
||||||
v => Err(ShellError::UnsupportedConfigValue {
|
v => Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "list of events".to_string(),
|
expected: "list of events".to_string(),
|
||||||
value: v.into_abbreviated_string(config),
|
value: v.to_abbreviated_string(config),
|
||||||
span: v.span(),
|
span: v.span(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -793,7 +1042,7 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
Value::Nothing { .. } => Ok(None),
|
Value::Nothing { .. } => Ok(None),
|
||||||
v => Err(ShellError::UnsupportedConfigValue {
|
v => Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "record or list of records, null to unbind key".to_string(),
|
expected: "record or list of records, null to unbind key".to_string(),
|
||||||
value: v.into_abbreviated_string(config),
|
value: v.to_abbreviated_string(config),
|
||||||
span: v.span(),
|
span: v.span(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -836,11 +1085,11 @@ fn event_from_record(
|
|||||||
"openeditor" => ReedlineEvent::OpenEditor,
|
"openeditor" => ReedlineEvent::OpenEditor,
|
||||||
"menu" => {
|
"menu" => {
|
||||||
let menu = extract_value("name", record, span)?;
|
let menu = extract_value("name", record, span)?;
|
||||||
ReedlineEvent::Menu(menu.into_string("", config))
|
ReedlineEvent::Menu(menu.to_expanded_string("", config))
|
||||||
}
|
}
|
||||||
"executehostcommand" => {
|
"executehostcommand" => {
|
||||||
let cmd = extract_value("cmd", record, span)?;
|
let cmd = extract_value("cmd", record, span)?;
|
||||||
ReedlineEvent::ExecuteHostCommand(cmd.into_string("", config))
|
ReedlineEvent::ExecuteHostCommand(cmd.to_expanded_string("", config))
|
||||||
}
|
}
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
@ -861,22 +1110,82 @@ fn edit_from_record(
|
|||||||
span: Span,
|
span: Span,
|
||||||
) -> Result<EditCommand, ShellError> {
|
) -> Result<EditCommand, ShellError> {
|
||||||
let edit = match name {
|
let edit = match name {
|
||||||
"movetostart" => EditCommand::MoveToStart,
|
"movetostart" => EditCommand::MoveToStart {
|
||||||
"movetolinestart" => EditCommand::MoveToLineStart,
|
select: extract_value("select", record, span)
|
||||||
"movetoend" => EditCommand::MoveToEnd,
|
.and_then(|value| value.as_bool())
|
||||||
"movetolineend" => EditCommand::MoveToLineEnd,
|
.unwrap_or(false),
|
||||||
"moveleft" => EditCommand::MoveLeft,
|
},
|
||||||
"moveright" => EditCommand::MoveRight,
|
"movetolinestart" => EditCommand::MoveToLineStart {
|
||||||
"movewordleft" => EditCommand::MoveWordLeft,
|
select: extract_value("select", record, span)
|
||||||
"movebigwordleft" => EditCommand::MoveBigWordLeft,
|
.and_then(|value| value.as_bool())
|
||||||
"movewordright" => EditCommand::MoveWordRight,
|
.unwrap_or(false),
|
||||||
"movewordrightend" => EditCommand::MoveWordRightEnd,
|
},
|
||||||
"movebigwordrightend" => EditCommand::MoveBigWordRightEnd,
|
|
||||||
"movewordrightstart" => EditCommand::MoveWordRightStart,
|
"movetoend" => EditCommand::MoveToEnd {
|
||||||
"movebigwordrightstart" => EditCommand::MoveBigWordRightStart,
|
select: extract_value("select", record, span)
|
||||||
|
.and_then(|value| value.as_bool())
|
||||||
|
.unwrap_or(false),
|
||||||
|
},
|
||||||
|
"movetolineend" => EditCommand::MoveToLineEnd {
|
||||||
|
select: extract_value("select", record, span)
|
||||||
|
.and_then(|value| value.as_bool())
|
||||||
|
.unwrap_or(false),
|
||||||
|
},
|
||||||
|
"moveleft" => EditCommand::MoveLeft {
|
||||||
|
select: extract_value("select", record, span)
|
||||||
|
.and_then(|value| value.as_bool())
|
||||||
|
.unwrap_or(false),
|
||||||
|
},
|
||||||
|
"moveright" => EditCommand::MoveRight {
|
||||||
|
select: extract_value("select", record, span)
|
||||||
|
.and_then(|value| value.as_bool())
|
||||||
|
.unwrap_or(false),
|
||||||
|
},
|
||||||
|
"movewordleft" => EditCommand::MoveWordLeft {
|
||||||
|
select: extract_value("select", record, span)
|
||||||
|
.and_then(|value| value.as_bool())
|
||||||
|
.unwrap_or(false),
|
||||||
|
},
|
||||||
|
"movebigwordleft" => EditCommand::MoveBigWordLeft {
|
||||||
|
select: extract_value("select", record, span)
|
||||||
|
.and_then(|value| value.as_bool())
|
||||||
|
.unwrap_or(false),
|
||||||
|
},
|
||||||
|
"movewordright" => EditCommand::MoveWordRight {
|
||||||
|
select: extract_value("select", record, span)
|
||||||
|
.and_then(|value| value.as_bool())
|
||||||
|
.unwrap_or(false),
|
||||||
|
},
|
||||||
|
"movewordrightend" => EditCommand::MoveWordRightEnd {
|
||||||
|
select: extract_value("select", record, span)
|
||||||
|
.and_then(|value| value.as_bool())
|
||||||
|
.unwrap_or(false),
|
||||||
|
},
|
||||||
|
"movebigwordrightend" => EditCommand::MoveBigWordRightEnd {
|
||||||
|
select: extract_value("select", record, span)
|
||||||
|
.and_then(|value| value.as_bool())
|
||||||
|
.unwrap_or(false),
|
||||||
|
},
|
||||||
|
"movewordrightstart" => EditCommand::MoveWordRightStart {
|
||||||
|
select: extract_value("select", record, span)
|
||||||
|
.and_then(|value| value.as_bool())
|
||||||
|
.unwrap_or(false),
|
||||||
|
},
|
||||||
|
"movebigwordrightstart" => EditCommand::MoveBigWordRightStart {
|
||||||
|
select: extract_value("select", record, span)
|
||||||
|
.and_then(|value| value.as_bool())
|
||||||
|
.unwrap_or(false),
|
||||||
|
},
|
||||||
"movetoposition" => {
|
"movetoposition" => {
|
||||||
let value = extract_value("value", record, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
EditCommand::MoveToPosition(value.as_int()? as usize)
|
let select = extract_value("select", record, span)
|
||||||
|
.and_then(|value| value.as_bool())
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
EditCommand::MoveToPosition {
|
||||||
|
position: value.as_int()? as usize,
|
||||||
|
select,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"insertchar" => {
|
"insertchar" => {
|
||||||
let value = extract_value("value", record, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
@ -885,7 +1194,7 @@ fn edit_from_record(
|
|||||||
}
|
}
|
||||||
"insertstring" => {
|
"insertstring" => {
|
||||||
let value = extract_value("value", record, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
EditCommand::InsertString(value.into_string("", config))
|
EditCommand::InsertString(value.to_expanded_string("", config))
|
||||||
}
|
}
|
||||||
"insertnewline" => EditCommand::InsertNewline,
|
"insertnewline" => EditCommand::InsertNewline,
|
||||||
"backspace" => EditCommand::Backspace,
|
"backspace" => EditCommand::Backspace,
|
||||||
@ -928,12 +1237,18 @@ fn edit_from_record(
|
|||||||
"moverightuntil" => {
|
"moverightuntil" => {
|
||||||
let value = extract_value("value", record, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value, config)?;
|
||||||
EditCommand::MoveRightUntil(char)
|
let select = extract_value("select", record, span)
|
||||||
|
.and_then(|value| value.as_bool())
|
||||||
|
.unwrap_or(false);
|
||||||
|
EditCommand::MoveRightUntil { c: char, select }
|
||||||
}
|
}
|
||||||
"moverightbefore" => {
|
"moverightbefore" => {
|
||||||
let value = extract_value("value", record, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value, config)?;
|
||||||
EditCommand::MoveRightBefore(char)
|
let select = extract_value("select", record, span)
|
||||||
|
.and_then(|value| value.as_bool())
|
||||||
|
.unwrap_or(false);
|
||||||
|
EditCommand::MoveRightBefore { c: char, select }
|
||||||
}
|
}
|
||||||
"cutleftuntil" => {
|
"cutleftuntil" => {
|
||||||
let value = extract_value("value", record, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
@ -948,14 +1263,30 @@ fn edit_from_record(
|
|||||||
"moveleftuntil" => {
|
"moveleftuntil" => {
|
||||||
let value = extract_value("value", record, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value, config)?;
|
||||||
EditCommand::MoveLeftUntil(char)
|
let select = extract_value("select", record, span)
|
||||||
|
.and_then(|value| value.as_bool())
|
||||||
|
.unwrap_or(false);
|
||||||
|
EditCommand::MoveLeftUntil { c: char, select }
|
||||||
}
|
}
|
||||||
"moveleftbefore" => {
|
"moveleftbefore" => {
|
||||||
let value = extract_value("value", record, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value, config)?;
|
||||||
EditCommand::MoveLeftBefore(char)
|
let select = extract_value("select", record, span)
|
||||||
|
.and_then(|value| value.as_bool())
|
||||||
|
.unwrap_or(false);
|
||||||
|
EditCommand::MoveLeftBefore { c: char, select }
|
||||||
}
|
}
|
||||||
"complete" => EditCommand::Complete,
|
"complete" => EditCommand::Complete,
|
||||||
|
"cutselection" => EditCommand::CutSelection,
|
||||||
|
#[cfg(feature = "system-clipboard")]
|
||||||
|
"cutselectionsystem" => EditCommand::CutSelectionSystem,
|
||||||
|
"copyselection" => EditCommand::CopySelection,
|
||||||
|
#[cfg(feature = "system-clipboard")]
|
||||||
|
"copyselectionsystem" => EditCommand::CopySelectionSystem,
|
||||||
|
"paste" => EditCommand::Paste,
|
||||||
|
#[cfg(feature = "system-clipboard")]
|
||||||
|
"pastesystem" => EditCommand::PasteSystem,
|
||||||
|
"selectall" => EditCommand::SelectAll,
|
||||||
e => {
|
e => {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
expected: "reedline EditCommand".to_string(),
|
expected: "reedline EditCommand".to_string(),
|
||||||
@ -971,7 +1302,7 @@ fn edit_from_record(
|
|||||||
fn extract_char(value: &Value, config: &Config) -> Result<char, ShellError> {
|
fn extract_char(value: &Value, config: &Config) -> Result<char, ShellError> {
|
||||||
let span = value.span();
|
let span = value.span();
|
||||||
value
|
value
|
||||||
.into_string("", config)
|
.to_expanded_string("", config)
|
||||||
.chars()
|
.chars()
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| ShellError::MissingConfigValue {
|
.ok_or_else(|| ShellError::MissingConfigValue {
|
||||||
@ -982,9 +1313,8 @@ fn extract_char(value: &Value, config: &Config) -> Result<char, ShellError> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use nu_protocol::record;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use nu_protocol::record;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_send_event() {
|
fn test_send_event() {
|
||||||
@ -1109,4 +1439,57 @@ mod test {
|
|||||||
let b = EventType::try_from_record(&event, span);
|
let b = EventType::try_from_record(&event, span);
|
||||||
assert!(matches!(b, Err(ShellError::MissingConfigValue { .. })));
|
assert!(matches!(b, Err(ShellError::MissingConfigValue { .. })));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_move_without_optional_select() {
|
||||||
|
let event = record! {
|
||||||
|
"edit" => Value::test_string("moveleft")
|
||||||
|
};
|
||||||
|
let event = Value::test_record(event);
|
||||||
|
let config = Config::default();
|
||||||
|
|
||||||
|
let parsed_event = parse_event(&event, &config).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
parsed_event,
|
||||||
|
Some(ReedlineEvent::Edit(vec![EditCommand::MoveLeft {
|
||||||
|
select: false
|
||||||
|
}]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_move_with_select_false() {
|
||||||
|
let event = record! {
|
||||||
|
"edit" => Value::test_string("moveleft"),
|
||||||
|
"select" => Value::test_bool(false)
|
||||||
|
};
|
||||||
|
let event = Value::test_record(event);
|
||||||
|
let config = Config::default();
|
||||||
|
|
||||||
|
let parsed_event = parse_event(&event, &config).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
parsed_event,
|
||||||
|
Some(ReedlineEvent::Edit(vec![EditCommand::MoveLeft {
|
||||||
|
select: false
|
||||||
|
}]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_move_with_select_true() {
|
||||||
|
let event = record! {
|
||||||
|
"edit" => Value::test_string("moveleft"),
|
||||||
|
"select" => Value::test_bool(true)
|
||||||
|
};
|
||||||
|
let event = Value::test_record(event);
|
||||||
|
let config = Config::default();
|
||||||
|
|
||||||
|
let parsed_event = parse_event(&event, &config).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
parsed_event,
|
||||||
|
Some(ReedlineEvent::Edit(vec![EditCommand::MoveLeft {
|
||||||
|
select: true
|
||||||
|
}]))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,15 +1,19 @@
|
|||||||
use log::trace;
|
use log::trace;
|
||||||
use nu_ansi_term::Style;
|
use nu_ansi_term::Style;
|
||||||
use nu_color_config::{get_matching_brackets_style, get_shape_color};
|
use nu_color_config::{get_matching_brackets_style, get_shape_color};
|
||||||
|
use nu_engine::env;
|
||||||
use nu_parser::{flatten_block, parse, FlatShape};
|
use nu_parser::{flatten_block, parse, FlatShape};
|
||||||
use nu_protocol::ast::{Argument, Block, Expr, Expression, PipelineElement, RecordItem};
|
use nu_protocol::{
|
||||||
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
ast::{Argument, Block, Expr, Expression, PipelineRedirection, RecordItem},
|
||||||
use nu_protocol::{Config, Span};
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
|
Config, Span,
|
||||||
|
};
|
||||||
use reedline::{Highlighter, StyledText};
|
use reedline::{Highlighter, StyledText};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct NuHighlighter {
|
pub struct NuHighlighter {
|
||||||
pub engine_state: Arc<EngineState>,
|
pub engine_state: Arc<EngineState>,
|
||||||
|
pub stack: Arc<Stack>,
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +36,17 @@ impl Highlighter for NuHighlighter {
|
|||||||
working_set.get_span_contents(Span::new(span.start, span.end));
|
working_set.get_span_contents(Span::new(span.start, span.end));
|
||||||
|
|
||||||
let str_word = String::from_utf8_lossy(str_contents).to_string();
|
let str_word = String::from_utf8_lossy(str_contents).to_string();
|
||||||
if which::which(str_word).ok().is_some() {
|
let paths = env::path_str(&self.engine_state, &self.stack, *span).ok();
|
||||||
|
let res = if let Ok(cwd) =
|
||||||
|
env::current_dir_str(&self.engine_state, &self.stack)
|
||||||
|
{
|
||||||
|
which::which_in(str_word, paths.as_ref(), cwd).ok()
|
||||||
|
} else {
|
||||||
|
which::which_in_global(str_word, paths.as_ref())
|
||||||
|
.ok()
|
||||||
|
.and_then(|mut i| i.next())
|
||||||
|
};
|
||||||
|
if res.is_some() {
|
||||||
*shape = FlatShape::ExternalResolved;
|
*shape = FlatShape::ExternalResolved;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -250,24 +264,38 @@ fn find_matching_block_end_in_block(
|
|||||||
) -> Option<usize> {
|
) -> Option<usize> {
|
||||||
for p in &block.pipelines {
|
for p in &block.pipelines {
|
||||||
for e in &p.elements {
|
for e in &p.elements {
|
||||||
match e {
|
if e.expr.span.contains(global_cursor_offset) {
|
||||||
PipelineElement::Expression(_, e)
|
if let Some(pos) = find_matching_block_end_in_expr(
|
||||||
| PipelineElement::Redirection(_, _, e, _)
|
line,
|
||||||
| PipelineElement::And(_, e)
|
working_set,
|
||||||
| PipelineElement::Or(_, e)
|
&e.expr,
|
||||||
| PipelineElement::SameTargetRedirection { cmd: (_, e), .. }
|
global_span_offset,
|
||||||
| PipelineElement::SeparateRedirection { out: (_, e, _), .. } => {
|
global_cursor_offset,
|
||||||
if e.span.contains(global_cursor_offset) {
|
) {
|
||||||
if let Some(pos) = find_matching_block_end_in_expr(
|
return Some(pos);
|
||||||
line,
|
}
|
||||||
working_set,
|
}
|
||||||
e,
|
|
||||||
global_span_offset,
|
if let Some(redirection) = e.redirection.as_ref() {
|
||||||
global_cursor_offset,
|
match redirection {
|
||||||
) {
|
PipelineRedirection::Single { target, .. }
|
||||||
|
| PipelineRedirection::Separate { out: target, .. }
|
||||||
|
| PipelineRedirection::Separate { err: target, .. }
|
||||||
|
if target.span().contains(global_cursor_offset) =>
|
||||||
|
{
|
||||||
|
if let Some(pos) = target.expr().and_then(|expr| {
|
||||||
|
find_matching_block_end_in_expr(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
expr,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
)
|
||||||
|
}) {
|
||||||
return Some(pos);
|
return Some(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -321,9 +349,9 @@ fn find_matching_block_end_in_expr(
|
|||||||
Expr::Keyword(..) => None,
|
Expr::Keyword(..) => None,
|
||||||
Expr::ValueWithUnit(..) => None,
|
Expr::ValueWithUnit(..) => None,
|
||||||
Expr::DateTime(_) => None,
|
Expr::DateTime(_) => None,
|
||||||
Expr::Filepath(_) => None,
|
Expr::Filepath(_, _) => None,
|
||||||
Expr::Directory(_) => None,
|
Expr::Directory(_, _) => None,
|
||||||
Expr::GlobPattern(_) => None,
|
Expr::GlobPattern(_, _) => None,
|
||||||
Expr::String(_) => None,
|
Expr::String(_) => None,
|
||||||
Expr::CellPath(_) => None,
|
Expr::CellPath(_) => None,
|
||||||
Expr::ImportPattern(_) => None,
|
Expr::ImportPattern(_) => None,
|
||||||
@ -332,9 +360,8 @@ fn find_matching_block_end_in_expr(
|
|||||||
Expr::MatchBlock(_) => None,
|
Expr::MatchBlock(_) => None,
|
||||||
Expr::Nothing => None,
|
Expr::Nothing => None,
|
||||||
Expr::Garbage => None,
|
Expr::Garbage => None,
|
||||||
Expr::Spread(_) => None,
|
|
||||||
|
|
||||||
Expr::Table(hdr, rows) => {
|
Expr::Table(table) => {
|
||||||
if expr_last == global_cursor_offset {
|
if expr_last == global_cursor_offset {
|
||||||
// cursor is at table end
|
// cursor is at table end
|
||||||
Some(expr_first)
|
Some(expr_first)
|
||||||
@ -343,11 +370,11 @@ fn find_matching_block_end_in_expr(
|
|||||||
Some(expr_last)
|
Some(expr_last)
|
||||||
} else {
|
} else {
|
||||||
// cursor is inside table
|
// cursor is inside table
|
||||||
for inner_expr in hdr {
|
for inner_expr in table.columns.as_ref() {
|
||||||
find_in_expr_or_continue!(inner_expr);
|
find_in_expr_or_continue!(inner_expr);
|
||||||
}
|
}
|
||||||
for row in rows {
|
for row in table.rows.as_ref() {
|
||||||
for inner_expr in row {
|
for inner_expr in row.as_ref() {
|
||||||
find_in_expr_or_continue!(inner_expr);
|
find_in_expr_or_continue!(inner_expr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -440,7 +467,7 @@ fn find_matching_block_end_in_expr(
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr::List(inner_expr) => {
|
Expr::List(list) => {
|
||||||
if expr_last == global_cursor_offset {
|
if expr_last == global_cursor_offset {
|
||||||
// cursor is at list end
|
// cursor is at list end
|
||||||
Some(expr_first)
|
Some(expr_first)
|
||||||
@ -449,8 +476,9 @@ fn find_matching_block_end_in_expr(
|
|||||||
Some(expr_last)
|
Some(expr_last)
|
||||||
} else {
|
} else {
|
||||||
// cursor is inside list
|
// cursor is inside list
|
||||||
for inner_expr in inner_expr {
|
for item in list {
|
||||||
find_in_expr_or_continue!(inner_expr);
|
let expr = item.expr();
|
||||||
|
find_in_expr_or_continue!(expr);
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
use nu_cmd_base::hook::eval_hook;
|
use nu_cmd_base::hook::eval_hook;
|
||||||
use nu_engine::{eval_block, eval_block_with_early_return};
|
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_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
|
||||||
use nu_protocol::engine::StateWorkingSet;
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
debugger::WithoutDebug,
|
||||||
print_if_stream, PipelineData, ShellError, Span, Value,
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
|
print_if_stream, report_error, report_error_new, PipelineData, ShellError, Span, Value,
|
||||||
};
|
};
|
||||||
use nu_protocol::{report_error, report_error_new};
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use nu_utils::enable_vt_processing;
|
use nu_utils::enable_vt_processing;
|
||||||
use nu_utils::utils::perf;
|
use nu_utils::utils::perf;
|
||||||
@ -93,8 +92,8 @@ fn gather_env_vars(
|
|||||||
let span_offset = engine_state.next_span_start();
|
let span_offset = engine_state.next_span_start();
|
||||||
|
|
||||||
engine_state.add_file(
|
engine_state.add_file(
|
||||||
"Host Environment Variables".to_string(),
|
"Host Environment Variables".into(),
|
||||||
fake_env_file.as_bytes().to_vec(),
|
fake_env_file.as_bytes().into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let (tokens, _) = lex(fake_env_file.as_bytes(), span_offset, &[], &[], true);
|
let (tokens, _) = lex(fake_env_file.as_bytes(), span_offset, &[], &[], true);
|
||||||
@ -240,9 +239,9 @@ pub fn eval_source(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let b = if allow_return {
|
let b = if allow_return {
|
||||||
eval_block_with_early_return(engine_state, stack, &block, input, false, false)
|
eval_block_with_early_return::<WithoutDebug>(engine_state, stack, &block, input)
|
||||||
} else {
|
} else {
|
||||||
eval_block(engine_state, stack, &block, input, false, false)
|
eval_block::<WithoutDebug>(engine_state, stack, &block, input)
|
||||||
};
|
};
|
||||||
|
|
||||||
match b {
|
match b {
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
pub mod support;
|
pub mod support;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use nu_cli::NuCompleter;
|
use nu_cli::NuCompleter;
|
||||||
|
use nu_engine::eval_block;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::engine::StateWorkingSet;
|
use nu_protocol::{debugger::WithoutDebug, engine::StateWorkingSet, PipelineData};
|
||||||
use reedline::{Completer, Suggestion};
|
use reedline::{Completer, Suggestion};
|
||||||
use rstest::{fixture, rstest};
|
use rstest::{fixture, rstest};
|
||||||
|
use std::path::PathBuf;
|
||||||
use support::{
|
use support::{
|
||||||
completions_helpers::{new_partial_engine, new_quote_engine},
|
completions_helpers::{new_partial_engine, new_quote_engine},
|
||||||
file, folder, match_suggestions, new_engine,
|
file, folder, match_suggestions, new_engine,
|
||||||
@ -66,7 +66,7 @@ fn custom_completer() -> NuCompleter {
|
|||||||
|
|
||||||
// Add record value as example
|
// Add record value as example
|
||||||
let record = r#"
|
let record = r#"
|
||||||
let external_completer = {|spans|
|
let external_completer = {|spans|
|
||||||
$spans
|
$spans
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ fn dotnu_completions() {
|
|||||||
#[ignore]
|
#[ignore]
|
||||||
fn external_completer_trailing_space() {
|
fn external_completer_trailing_space() {
|
||||||
// https://github.com/nushell/nushell/issues/6378
|
// https://github.com/nushell/nushell/issues/6378
|
||||||
let block = "let external_completer = {|spans| $spans}";
|
let block = "{|spans| $spans}";
|
||||||
let input = "gh alias ".to_string();
|
let input = "gh alias ".to_string();
|
||||||
|
|
||||||
let suggestions = run_external_completion(block, &input);
|
let suggestions = run_external_completion(block, &input);
|
||||||
@ -247,6 +247,16 @@ fn file_completions() {
|
|||||||
|
|
||||||
// Match the results
|
// 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 suggestions = completer.complete(&target_dir, target_dir.len());
|
||||||
|
|
||||||
|
let expected_paths: Vec<String> =
|
||||||
|
vec![file(dir.join(".hidden_folder").join(".hidden_subfile"))];
|
||||||
|
|
||||||
|
// Match the results
|
||||||
|
match_suggestions(expected_paths, suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -584,6 +594,7 @@ fn file_completion_quoted() {
|
|||||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||||
|
|
||||||
let expected_paths: Vec<String> = vec![
|
let expected_paths: Vec<String> = vec![
|
||||||
|
"\'[a] bc.txt\'".to_string(),
|
||||||
"`--help`".to_string(),
|
"`--help`".to_string(),
|
||||||
"`-42`".to_string(),
|
"`-42`".to_string(),
|
||||||
"`-inf`".to_string(),
|
"`-inf`".to_string(),
|
||||||
@ -684,13 +695,14 @@ fn variables_completions() {
|
|||||||
// Test completions for $nu
|
// Test completions for $nu
|
||||||
let suggestions = completer.complete("$nu.", 4);
|
let suggestions = completer.complete("$nu.", 4);
|
||||||
|
|
||||||
assert_eq!(14, suggestions.len());
|
assert_eq!(15, suggestions.len());
|
||||||
|
|
||||||
let expected: Vec<String> = vec![
|
let expected: Vec<String> = vec![
|
||||||
"config-path".into(),
|
"config-path".into(),
|
||||||
"current-exe".into(),
|
"current-exe".into(),
|
||||||
"default-config-dir".into(),
|
"default-config-dir".into(),
|
||||||
"env-path".into(),
|
"env-path".into(),
|
||||||
|
"history-enabled".into(),
|
||||||
"history-path".into(),
|
"history-path".into(),
|
||||||
"home-path".into(),
|
"home-path".into(),
|
||||||
"is-interactive".into(),
|
"is-interactive".into(),
|
||||||
@ -709,9 +721,13 @@ fn variables_completions() {
|
|||||||
// Test completions for $nu.h (filter)
|
// Test completions for $nu.h (filter)
|
||||||
let suggestions = completer.complete("$nu.h", 5);
|
let suggestions = completer.complete("$nu.h", 5);
|
||||||
|
|
||||||
assert_eq!(2, suggestions.len());
|
assert_eq!(3, suggestions.len());
|
||||||
|
|
||||||
let expected: Vec<String> = vec!["history-path".into(), "home-path".into()];
|
let expected: Vec<String> = vec![
|
||||||
|
"history-enabled".into(),
|
||||||
|
"history-path".into(),
|
||||||
|
"home-path".into(),
|
||||||
|
];
|
||||||
|
|
||||||
// Match results
|
// Match results
|
||||||
match_suggestions(expected, suggestions);
|
match_suggestions(expected, suggestions);
|
||||||
@ -832,12 +848,14 @@ fn alias_of_another_alias() {
|
|||||||
match_suggestions(expected_paths, suggestions)
|
match_suggestions(expected_paths, suggestions)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
|
fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> {
|
||||||
|
let completer = format!("$env.config.completions.external.completer = {completer}");
|
||||||
|
|
||||||
// Create a new engine
|
// Create a new engine
|
||||||
let (dir, _, mut engine_state, mut stack) = new_engine();
|
let (dir, _, mut engine_state, mut stack) = new_engine();
|
||||||
let (_, delta) = {
|
let (block, delta) = {
|
||||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
let block = parse(&mut working_set, None, block.as_bytes(), false);
|
let block = parse(&mut working_set, None, completer.as_bytes(), false);
|
||||||
assert!(working_set.parse_errors.is_empty());
|
assert!(working_set.parse_errors.is_empty());
|
||||||
|
|
||||||
(block, working_set.render())
|
(block, working_set.render())
|
||||||
@ -845,16 +863,13 @@ fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
|
|||||||
|
|
||||||
assert!(engine_state.merge_delta(delta).is_ok());
|
assert!(engine_state.merge_delta(delta).is_ok());
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
eval_block::<WithoutDebug>(&engine_state, &mut stack, &block, PipelineData::Empty).is_ok()
|
||||||
|
);
|
||||||
|
|
||||||
// Merge environment into the permanent state
|
// Merge environment into the permanent state
|
||||||
assert!(engine_state.merge_env(&mut stack, &dir).is_ok());
|
assert!(engine_state.merge_env(&mut stack, &dir).is_ok());
|
||||||
|
|
||||||
let latest_block_id = engine_state.num_blocks() - 1;
|
|
||||||
|
|
||||||
// Change config adding the external completer
|
|
||||||
let mut config = engine_state.get_config().clone();
|
|
||||||
config.external_completer = Some(latest_block_id);
|
|
||||||
engine_state.set_config(config);
|
|
||||||
|
|
||||||
// Instantiate a new completer
|
// Instantiate a new completer
|
||||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine_state), stack);
|
let mut completer = NuCompleter::new(std::sync::Arc::new(engine_state), stack);
|
||||||
|
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
|
debugger::WithoutDebug,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
eval_const::create_nu_constant,
|
eval_const::create_nu_constant,
|
||||||
PipelineData, ShellError, Span, Value, NU_VARIABLE_ID,
|
PipelineData, ShellError, Span, Value, NU_VARIABLE_ID,
|
||||||
};
|
};
|
||||||
use nu_test_support::fs;
|
use nu_test_support::fs;
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||||
|
|
||||||
fn create_default_context() -> EngineState {
|
fn create_default_context() -> EngineState {
|
||||||
@ -194,13 +195,11 @@ pub fn merge_input(
|
|||||||
|
|
||||||
engine_state.merge_delta(delta)?;
|
engine_state.merge_delta(delta)?;
|
||||||
|
|
||||||
assert!(eval_block(
|
assert!(eval_block::<WithoutDebug>(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
&block,
|
&block,
|
||||||
PipelineData::Value(Value::nothing(Span::unknown(),), None),
|
PipelineData::Value(Value::nothing(Span::unknown()), None),
|
||||||
false,
|
|
||||||
false
|
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
|
@ -5,21 +5,17 @@ edition = "2021"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-base"
|
name = "nu-cmd-base"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
||||||
version = "0.88.2"
|
version = "0.93.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.88.2" }
|
nu-engine = { path = "../nu-engine", version = "0.93.0" }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.88.2" }
|
nu-parser = { path = "../nu-parser", version = "0.93.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.88.2" }
|
nu-path = { path = "../nu-path", version = "0.93.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.88.2" }
|
nu-protocol = { path = "../nu-protocol", version = "0.93.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.88.2" }
|
|
||||||
nu-utils = { path = "../nu-utils", version = "0.88.2" }
|
|
||||||
|
|
||||||
indexmap = "2.1"
|
indexmap = { workspace = true }
|
||||||
miette = "5.10.0"
|
miette = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.88.2" }
|
|
||||||
rstest = "0.18.2"
|
|
||||||
|
@ -1,205 +0,0 @@
|
|||||||
// utilities for expanding globs in command arguments
|
|
||||||
|
|
||||||
use nu_glob::{glob_with_parent, MatchOptions, Paths};
|
|
||||||
use nu_protocol::{ShellError, Spanned};
|
|
||||||
use std::fs;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
// standard glob options to use for filesystem command arguments
|
|
||||||
|
|
||||||
const GLOB_PARAMS: MatchOptions = MatchOptions {
|
|
||||||
case_sensitive: true,
|
|
||||||
require_literal_separator: false,
|
|
||||||
require_literal_leading_dot: false,
|
|
||||||
recursive_match_hidden_dir: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// handle an argument that could be a literal path or a glob.
|
|
||||||
// if literal path, return just that (whether user can access it or not).
|
|
||||||
// if glob, expand into matching paths, using GLOB_PARAMS options.
|
|
||||||
pub fn arg_glob(
|
|
||||||
pattern: &Spanned<String>, // alleged path or glob
|
|
||||||
cwd: &Path, // current working directory
|
|
||||||
) -> Result<Paths, ShellError> {
|
|
||||||
arg_glob_opt(pattern, cwd, GLOB_PARAMS)
|
|
||||||
}
|
|
||||||
|
|
||||||
// variant of [arg_glob] that requires literal dot prefix in pattern to match dot-prefixed path.
|
|
||||||
pub fn arg_glob_leading_dot(pattern: &Spanned<String>, cwd: &Path) -> Result<Paths, ShellError> {
|
|
||||||
arg_glob_opt(
|
|
||||||
pattern,
|
|
||||||
cwd,
|
|
||||||
MatchOptions {
|
|
||||||
require_literal_leading_dot: true,
|
|
||||||
..GLOB_PARAMS
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn arg_glob_opt(
|
|
||||||
pattern: &Spanned<String>,
|
|
||||||
cwd: &Path,
|
|
||||||
options: MatchOptions,
|
|
||||||
) -> Result<Paths, ShellError> {
|
|
||||||
// remove ansi coloring (?)
|
|
||||||
let pattern = {
|
|
||||||
Spanned {
|
|
||||||
item: nu_utils::strip_ansi_string_unlikely(pattern.item.clone()),
|
|
||||||
span: pattern.span,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// if there's a file with same path as the pattern, just return that.
|
|
||||||
let pp = cwd.join(&pattern.item);
|
|
||||||
let md = fs::metadata(pp);
|
|
||||||
#[allow(clippy::single_match)]
|
|
||||||
match md {
|
|
||||||
Ok(_metadata) => {
|
|
||||||
return Ok(Paths::single(&PathBuf::from(pattern.item), cwd));
|
|
||||||
}
|
|
||||||
// file not found, but also "invalid chars in file" (e.g * on Windows). Fall through and glob
|
|
||||||
Err(_) => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// user wasn't referring to a specific thing in filesystem, try to glob it.
|
|
||||||
match glob_with_parent(&pattern.item, options, cwd) {
|
|
||||||
Ok(p) => Ok(p),
|
|
||||||
Err(pat_err) => Err(ShellError::InvalidGlobPattern {
|
|
||||||
msg: pat_err.msg.into(),
|
|
||||||
span: pattern.span,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use nu_glob::GlobResult;
|
|
||||||
use nu_protocol::{Span, Spanned};
|
|
||||||
use nu_test_support::fs::Stub::EmptyFile;
|
|
||||||
use nu_test_support::playground::Playground;
|
|
||||||
use rstest::rstest;
|
|
||||||
|
|
||||||
fn spanned_string(str: &str) -> Spanned<String> {
|
|
||||||
Spanned {
|
|
||||||
item: str.to_string(),
|
|
||||||
span: Span::test_data(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn does_something() {
|
|
||||||
let act = arg_glob(&spanned_string("*"), &PathBuf::from("."));
|
|
||||||
assert!(act.is_ok());
|
|
||||||
for f in act.expect("checked ok") {
|
|
||||||
match f {
|
|
||||||
Ok(p) => {
|
|
||||||
assert!(!p.to_str().unwrap().is_empty());
|
|
||||||
}
|
|
||||||
Err(e) => panic!("unexpected error {:?}", e),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn glob_format_error() {
|
|
||||||
let act = arg_glob(&spanned_string(r#"ab]c[def"#), &PathBuf::from("."));
|
|
||||||
assert!(act.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case("*", 4, "no dirs")]
|
|
||||||
#[case("**/*", 7, "incl dirs")]
|
|
||||||
fn glob_subdirs(#[case] pat: &str, #[case] exp_count: usize, #[case] case: &str) {
|
|
||||||
Playground::setup("glob_subdirs", |dirs, sandbox| {
|
|
||||||
sandbox.with_files(vec![
|
|
||||||
EmptyFile("yehuda.txt"),
|
|
||||||
EmptyFile("jttxt"),
|
|
||||||
EmptyFile("andres.txt"),
|
|
||||||
]);
|
|
||||||
sandbox.mkdir(".children");
|
|
||||||
sandbox.within(".children").with_files(vec![
|
|
||||||
EmptyFile("timothy.txt"),
|
|
||||||
EmptyFile("tiffany.txt"),
|
|
||||||
EmptyFile("trish.txt"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let p: Vec<GlobResult> = arg_glob(&spanned_string(pat), &dirs.test)
|
|
||||||
.expect("no error")
|
|
||||||
.collect();
|
|
||||||
assert_eq!(
|
|
||||||
exp_count,
|
|
||||||
p.iter().filter(|i| i.is_ok()).count(),
|
|
||||||
" case: {case} ",
|
|
||||||
);
|
|
||||||
|
|
||||||
// expected behavior -- that directories are included in results (if name matches pattern)
|
|
||||||
let t = p
|
|
||||||
.iter()
|
|
||||||
.any(|i| i.as_ref().unwrap().to_string_lossy().contains(".children"));
|
|
||||||
assert!(t, "check for dir, case {case}");
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case("yehuda.txt", true, 1, "matches literal path")]
|
|
||||||
#[case("*", false, 3, "matches glob")]
|
|
||||||
#[case(r#"bad[glob.foo"#, true, 1, "matches literal, would be bad glob pat")]
|
|
||||||
fn exact_vs_glob(
|
|
||||||
#[case] pat: &str,
|
|
||||||
#[case] exp_matches_input: bool,
|
|
||||||
#[case] exp_count: usize,
|
|
||||||
#[case] case: &str,
|
|
||||||
) {
|
|
||||||
Playground::setup("exact_vs_glob", |dirs, sandbox| {
|
|
||||||
sandbox.with_files(vec![
|
|
||||||
EmptyFile("yehuda.txt"),
|
|
||||||
EmptyFile("jttxt"),
|
|
||||||
EmptyFile("bad[glob.foo"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let res = arg_glob(&spanned_string(pat), &dirs.test)
|
|
||||||
.expect("no error")
|
|
||||||
.collect::<Vec<GlobResult>>();
|
|
||||||
|
|
||||||
eprintln!("res: {:?}", res);
|
|
||||||
if exp_matches_input {
|
|
||||||
assert_eq!(
|
|
||||||
exp_count,
|
|
||||||
res.len(),
|
|
||||||
" case {case}: matches input, but count not 1? "
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
&res[0].as_ref().unwrap().to_string_lossy(),
|
|
||||||
pat, // todo: is it OK for glob to return relative paths (not to current cwd, but to arg cwd of arg_glob)?
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
assert_eq!(exp_count, res.len(), " case: {}: matched glob", case);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case(r#"realbad[glob.foo"#, true, 1, "error, bad glob")]
|
|
||||||
fn exact_vs_bad_glob(
|
|
||||||
// if path doesn't exist but pattern is not valid glob, should get error.
|
|
||||||
#[case] pat: &str,
|
|
||||||
#[case] _exp_matches_input: bool,
|
|
||||||
#[case] _exp_count: usize,
|
|
||||||
#[case] _tag: &str,
|
|
||||||
) {
|
|
||||||
Playground::setup("exact_vs_bad_glob", |dirs, sandbox| {
|
|
||||||
sandbox.with_files(vec![
|
|
||||||
EmptyFile("yehuda.txt"),
|
|
||||||
EmptyFile("jttxt"),
|
|
||||||
EmptyFile("bad[glob.foo"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let res = arg_glob(&spanned_string(pat), &dirs.test);
|
|
||||||
assert!(res
|
|
||||||
.expect_err("expected error")
|
|
||||||
.to_string()
|
|
||||||
.contains("Invalid glob pattern"));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,9 +2,13 @@ use crate::util::get_guaranteed_cwd;
|
|||||||
use miette::Result;
|
use miette::Result;
|
||||||
use nu_engine::{eval_block, eval_block_with_early_return};
|
use nu_engine::{eval_block, eval_block_with_early_return};
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::cli_error::{report_error, report_error_new};
|
use nu_protocol::{
|
||||||
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
cli_error::{report_error, report_error_new},
|
||||||
use nu_protocol::{BlockId, PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId};
|
debugger::WithoutDebug,
|
||||||
|
engine::{Closure, EngineState, Stack, StateWorkingSet},
|
||||||
|
PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub fn eval_env_change_hook(
|
pub fn eval_env_change_hook(
|
||||||
env_change_hook: Option<Value>,
|
env_change_hook: Option<Value>,
|
||||||
@ -14,7 +18,7 @@ pub fn eval_env_change_hook(
|
|||||||
if let Some(hook) = env_change_hook {
|
if let Some(hook) = env_change_hook {
|
||||||
match hook {
|
match hook {
|
||||||
Value::Record { val, .. } => {
|
Value::Record { val, .. } => {
|
||||||
for (env_name, hook_value) in &val {
|
for (env_name, hook_value) in &*val {
|
||||||
let before = engine_state
|
let before = engine_state
|
||||||
.previous_env_vars
|
.previous_env_vars
|
||||||
.get(env_name)
|
.get(env_name)
|
||||||
@ -35,8 +39,7 @@ pub fn eval_env_change_hook(
|
|||||||
"env_change",
|
"env_change",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
engine_state
|
Arc::make_mut(&mut engine_state.previous_env_vars)
|
||||||
.previous_env_vars
|
|
||||||
.insert(env_name.to_string(), after);
|
.insert(env_name.to_string(), after);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,7 +118,7 @@ pub fn eval_hook(
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
match eval_block::<WithoutDebug>(engine_state, stack, &block, input) {
|
||||||
Ok(pipeline_data) => {
|
Ok(pipeline_data) => {
|
||||||
output = pipeline_data;
|
output = pipeline_data;
|
||||||
}
|
}
|
||||||
@ -150,11 +153,11 @@ pub fn eval_hook(
|
|||||||
// If it returns true (the default if a condition block is not specified), the hook should be run.
|
// If it returns true (the default if a condition block is not specified), the hook should be run.
|
||||||
let do_run_hook = if let Some(condition) = val.get("condition") {
|
let do_run_hook = if let Some(condition) = val.get("condition") {
|
||||||
let other_span = condition.span();
|
let other_span = condition.span();
|
||||||
if let Ok(block_id) = condition.as_block() {
|
if let Ok(closure) = condition.as_closure() {
|
||||||
match run_hook_block(
|
match run_hook(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
block_id,
|
closure,
|
||||||
None,
|
None,
|
||||||
arguments.clone(),
|
arguments.clone(),
|
||||||
other_span,
|
other_span,
|
||||||
@ -243,7 +246,7 @@ pub fn eval_hook(
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
match eval_block::<WithoutDebug>(engine_state, stack, &block, input) {
|
||||||
Ok(pipeline_data) => {
|
Ok(pipeline_data) => {
|
||||||
output = pipeline_data;
|
output = pipeline_data;
|
||||||
}
|
}
|
||||||
@ -256,25 +259,8 @@ pub fn eval_hook(
|
|||||||
stack.remove_var(*var_id);
|
stack.remove_var(*var_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Block { val: block_id, .. } => {
|
|
||||||
run_hook_block(
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
*block_id,
|
|
||||||
input,
|
|
||||||
arguments,
|
|
||||||
source_span,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
Value::Closure { val, .. } => {
|
Value::Closure { val, .. } => {
|
||||||
run_hook_block(
|
run_hook(engine_state, stack, val, input, arguments, source_span)?;
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
val.block_id,
|
|
||||||
input,
|
|
||||||
arguments,
|
|
||||||
source_span,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
@ -286,11 +272,8 @@ pub fn eval_hook(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Block { val: block_id, .. } => {
|
|
||||||
output = run_hook_block(engine_state, stack, *block_id, input, arguments, span)?;
|
|
||||||
}
|
|
||||||
Value::Closure { val, .. } => {
|
Value::Closure { val, .. } => {
|
||||||
output = run_hook_block(engine_state, stack, val.block_id, input, arguments, span)?;
|
output = run_hook(engine_state, stack, val, input, arguments, span)?;
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
return Err(ShellError::UnsupportedConfigValue {
|
||||||
@ -307,19 +290,21 @@ pub fn eval_hook(
|
|||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_hook_block(
|
fn run_hook(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
block_id: BlockId,
|
closure: &Closure,
|
||||||
optional_input: Option<PipelineData>,
|
optional_input: Option<PipelineData>,
|
||||||
arguments: Vec<(String, Value)>,
|
arguments: Vec<(String, Value)>,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let block = engine_state.get_block(block_id);
|
let block = engine_state.get_block(closure.block_id);
|
||||||
|
|
||||||
let input = optional_input.unwrap_or_else(PipelineData::empty);
|
let input = optional_input.unwrap_or_else(PipelineData::empty);
|
||||||
|
|
||||||
let mut callee_stack = stack.gather_captures(engine_state, &block.captures);
|
let mut callee_stack = stack
|
||||||
|
.captures_to_stack_preserve_out_dest(closure.captures.clone())
|
||||||
|
.reset_pipes();
|
||||||
|
|
||||||
for (idx, PositionalArg { var_id, .. }) in
|
for (idx, PositionalArg { var_id, .. }) in
|
||||||
block.signature.required_positional.iter().enumerate()
|
block.signature.required_positional.iter().enumerate()
|
||||||
@ -336,8 +321,12 @@ fn run_hook_block(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let pipeline_data =
|
let pipeline_data = eval_block_with_early_return::<WithoutDebug>(
|
||||||
eval_block_with_early_return(engine_state, &mut callee_stack, block, input, false, false)?;
|
engine_state,
|
||||||
|
&mut callee_stack,
|
||||||
|
block,
|
||||||
|
input,
|
||||||
|
)?;
|
||||||
|
|
||||||
if let PipelineData::Value(Value::Error { error, .. }, _) = pipeline_data {
|
if let PipelineData::Value(Value::Error { error, .. }, _) = pipeline_data {
|
||||||
return Err(*error);
|
return Err(*error);
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
use nu_protocol::ast::CellPath;
|
use nu_protocol::{ast::CellPath, PipelineData, ShellError, Span, Value};
|
||||||
use nu_protocol::{PipelineData, ShellError, Span, Value};
|
use std::sync::{atomic::AtomicBool, Arc};
|
||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub trait CmdArgument {
|
pub trait CmdArgument {
|
||||||
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>>;
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>>;
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
mod arg_glob;
|
|
||||||
pub mod formats;
|
pub mod formats;
|
||||||
pub mod hook;
|
pub mod hook;
|
||||||
pub mod input_handler;
|
pub mod input_handler;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub use arg_glob::arg_glob;
|
|
||||||
pub use arg_glob::arg_glob_leading_dot;
|
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
use nu_protocol::report_error;
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::RangeInclusion,
|
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
Range, ShellError, Span, Value,
|
report_error, Range, ShellError, Span, Value,
|
||||||
};
|
};
|
||||||
use std::path::PathBuf;
|
use std::{ops::Bound, path::PathBuf};
|
||||||
|
|
||||||
pub fn get_init_cwd() -> PathBuf {
|
pub fn get_init_cwd() -> PathBuf {
|
||||||
std::env::current_dir().unwrap_or_else(|_| {
|
std::env::current_dir().unwrap_or_else(|_| {
|
||||||
@ -25,35 +23,21 @@ pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf
|
|||||||
type MakeRangeError = fn(&str, Span) -> ShellError;
|
type MakeRangeError = fn(&str, Span) -> ShellError;
|
||||||
|
|
||||||
pub fn process_range(range: &Range) -> Result<(isize, isize), MakeRangeError> {
|
pub fn process_range(range: &Range) -> Result<(isize, isize), MakeRangeError> {
|
||||||
let start = match &range.from {
|
match range {
|
||||||
Value::Int { val, .. } => isize::try_from(*val).unwrap_or_default(),
|
Range::IntRange(range) => {
|
||||||
Value::Nothing { .. } => 0,
|
let start = range.start().try_into().unwrap_or(0);
|
||||||
_ => {
|
let end = match range.end() {
|
||||||
return Err(|msg, span| ShellError::TypeMismatch {
|
Bound::Included(v) => v as isize,
|
||||||
err_message: msg.to_string(),
|
Bound::Excluded(v) => (v - 1) as isize,
|
||||||
span,
|
Bound::Unbounded => isize::MAX,
|
||||||
})
|
};
|
||||||
|
Ok((start, end))
|
||||||
}
|
}
|
||||||
};
|
Range::FloatRange(_) => Err(|msg, span| ShellError::TypeMismatch {
|
||||||
|
err_message: msg.to_string(),
|
||||||
let end = match &range.to {
|
span,
|
||||||
Value::Int { val, .. } => {
|
}),
|
||||||
if matches!(range.inclusion, RangeInclusion::Inclusive) {
|
}
|
||||||
isize::try_from(*val).unwrap_or(isize::max_value())
|
|
||||||
} else {
|
|
||||||
isize::try_from(*val).unwrap_or(isize::max_value()) - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Nothing { .. } => isize::max_value(),
|
|
||||||
_ => {
|
|
||||||
return Err(|msg, span| ShellError::TypeMismatch {
|
|
||||||
err_message: msg.to_string(),
|
|
||||||
span,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((start, end))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const HELP_MSG: &str = "Nushell's config file can be found with the command: $nu.config-path. \
|
const HELP_MSG: &str = "Nushell's config file can be found with the command: $nu.config-path. \
|
||||||
@ -66,7 +50,7 @@ fn get_editor_commandline(
|
|||||||
match value {
|
match value {
|
||||||
Value::String { val, .. } if !val.is_empty() => Ok((val.to_string(), Vec::new())),
|
Value::String { val, .. } if !val.is_empty() => Ok((val.to_string(), Vec::new())),
|
||||||
Value::List { vals, .. } if !vals.is_empty() => {
|
Value::List { vals, .. } if !vals.is_empty() => {
|
||||||
let mut editor_cmd = vals.iter().map(|l| l.as_string());
|
let mut editor_cmd = vals.iter().map(|l| l.coerce_string());
|
||||||
match editor_cmd.next().transpose()? {
|
match editor_cmd.next().transpose()? {
|
||||||
Some(editor) if !editor.is_empty() => {
|
Some(editor) if !editor.is_empty() => {
|
||||||
let params = editor_cmd.collect::<Result<_, ShellError>>()?;
|
let params = editor_cmd.collect::<Result<_, ShellError>>()?;
|
||||||
@ -99,7 +83,7 @@ fn get_editor_commandline(
|
|||||||
|
|
||||||
pub fn get_editor(
|
pub fn get_editor(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &Stack,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> Result<(String, Vec<String>), ShellError> {
|
) -> Result<(String, Vec<String>), ShellError> {
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-dataframe"
|
name = "nu-cmd-dataframe"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-dataframe"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-dataframe"
|
||||||
version = "0.88.2"
|
version = "0.93.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -13,22 +13,24 @@ version = "0.88.2"
|
|||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.88.2" }
|
nu-engine = { path = "../nu-engine", version = "0.93.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.88.2" }
|
nu-parser = { path = "../nu-parser", version = "0.93.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.88.2" }
|
nu-protocol = { path = "../nu-protocol", version = "0.93.0" }
|
||||||
|
|
||||||
# Potential dependencies for extras
|
# Potential dependencies for extras
|
||||||
chrono = { version = "0.4", features = ["std", "unstable-locales"], default-features = false }
|
chrono = { workspace = true, features = ["std", "unstable-locales"], default-features = false }
|
||||||
chrono-tz = "0.8"
|
chrono-tz = { workspace = true }
|
||||||
fancy-regex = "0.12"
|
fancy-regex = { workspace = true }
|
||||||
indexmap = { version = "2.1" }
|
indexmap = { workspace = true }
|
||||||
num = { version = "0.4", optional = true }
|
num = { version = "0.4", optional = true }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
sqlparser = { version = "0.39", optional = true }
|
# keep sqlparser at 0.39.0 until we can update polars
|
||||||
polars-io = { version = "0.35", features = ["avro"], optional = true }
|
sqlparser = { version = "0.45", optional = true }
|
||||||
polars-arrow = { version = "0.35", optional = true }
|
polars-io = { version = "0.39", features = ["avro"], optional = true }
|
||||||
polars-ops = { version = "0.35", optional = true }
|
polars-arrow = { version = "0.39", optional = true }
|
||||||
polars-plan = { version = "0.35", optional = true }
|
polars-ops = { version = "0.39", optional = true }
|
||||||
|
polars-plan = { version = "0.39", features = ["regex"], optional = true }
|
||||||
|
polars-utils = { version = "0.39", optional = true }
|
||||||
|
|
||||||
[dependencies.polars]
|
[dependencies.polars]
|
||||||
features = [
|
features = [
|
||||||
@ -38,7 +40,6 @@ features = [
|
|||||||
"cross_join",
|
"cross_join",
|
||||||
"csv",
|
"csv",
|
||||||
"cum_agg",
|
"cum_agg",
|
||||||
"default",
|
|
||||||
"dtype-categorical",
|
"dtype-categorical",
|
||||||
"dtype-datetime",
|
"dtype-datetime",
|
||||||
"dtype-struct",
|
"dtype-struct",
|
||||||
@ -59,15 +60,16 @@ features = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde-lazy",
|
"serde-lazy",
|
||||||
"strings",
|
"strings",
|
||||||
|
"temporal",
|
||||||
"to_dummies",
|
"to_dummies",
|
||||||
]
|
]
|
||||||
|
default-features = false
|
||||||
optional = true
|
optional = true
|
||||||
version = "0.35"
|
version = "0.39"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
dataframe = ["num", "polars", "polars-io", "polars-arrow", "polars-ops", "polars-plan", "sqlparser"]
|
dataframe = ["num", "polars", "polars-io", "polars-arrow", "polars-ops", "polars-plan", "polars-utils", "sqlparser"]
|
||||||
default = []
|
default = []
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.88.2" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.93.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.88.2" }
|
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
use nu_engine::CallExt;
|
use crate::dataframe::values::{Axis, Column, NuDataFrame};
|
||||||
use nu_protocol::{
|
use nu_engine::command_prelude::*;
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::super::values::{Axis, Column, NuDataFrame};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppendDF;
|
pub struct AppendDF;
|
||||||
@ -37,24 +31,27 @@ impl Command for AppendDF {
|
|||||||
example: r#"let a = ([[a b]; [1 2] [3 4]] | dfr into-df);
|
example: r#"let a = ([[a b]; [1 2] [3 4]] | dfr into-df);
|
||||||
$a | dfr append $a"#,
|
$a | dfr append $a"#,
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new(
|
vec![
|
||||||
"a".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(1), Value::test_int(3)],
|
"a".to_string(),
|
||||||
),
|
vec![Value::test_int(1), Value::test_int(3)],
|
||||||
Column::new(
|
),
|
||||||
"b".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(2), Value::test_int(4)],
|
"b".to_string(),
|
||||||
),
|
vec![Value::test_int(2), Value::test_int(4)],
|
||||||
Column::new(
|
),
|
||||||
"a_x".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(1), Value::test_int(3)],
|
"a_x".to_string(),
|
||||||
),
|
vec![Value::test_int(1), Value::test_int(3)],
|
||||||
Column::new(
|
),
|
||||||
"b_x".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(2), Value::test_int(4)],
|
"b_x".to_string(),
|
||||||
),
|
vec![Value::test_int(2), Value::test_int(4)],
|
||||||
])
|
),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
@ -64,26 +61,29 @@ impl Command for AppendDF {
|
|||||||
example: r#"let a = ([[a b]; [1 2] [3 4]] | dfr into-df);
|
example: r#"let a = ([[a b]; [1 2] [3 4]] | dfr into-df);
|
||||||
$a | dfr append $a --col"#,
|
$a | dfr append $a --col"#,
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new(
|
vec![
|
||||||
"a".to_string(),
|
Column::new(
|
||||||
vec![
|
"a".to_string(),
|
||||||
Value::test_int(1),
|
vec![
|
||||||
Value::test_int(3),
|
Value::test_int(1),
|
||||||
Value::test_int(1),
|
Value::test_int(3),
|
||||||
Value::test_int(3),
|
Value::test_int(1),
|
||||||
],
|
Value::test_int(3),
|
||||||
),
|
],
|
||||||
Column::new(
|
),
|
||||||
"b".to_string(),
|
Column::new(
|
||||||
vec![
|
"b".to_string(),
|
||||||
Value::test_int(2),
|
vec![
|
||||||
Value::test_int(4),
|
Value::test_int(2),
|
||||||
Value::test_int(2),
|
Value::test_int(4),
|
||||||
Value::test_int(4),
|
Value::test_int(2),
|
||||||
],
|
Value::test_int(4),
|
||||||
),
|
],
|
||||||
])
|
),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
@ -110,7 +110,7 @@ fn command(
|
|||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let other: Value = call.req(engine_state, stack, 0)?;
|
let other: Value = call.req(engine_state, stack, 0)?;
|
||||||
|
|
||||||
let axis = if call.has_flag("col") {
|
let axis = if call.has_flag(engine_state, stack, "col")? {
|
||||||
Axis::Column
|
Axis::Column
|
||||||
} else {
|
} else {
|
||||||
Axis::Row
|
Axis::Row
|
||||||
|
195
crates/nu-cmd-dataframe/src/dataframe/eager/cast.rs
Normal file
195
crates/nu-cmd-dataframe/src/dataframe/eager/cast.rs
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
use crate::dataframe::values::{str_to_dtype, NuDataFrame, NuExpression, NuLazyFrame};
|
||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
|
use polars::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CastDF;
|
||||||
|
|
||||||
|
impl Command for CastDF {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"dfr cast"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Cast a column to a different dtype."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.input_output_types(vec![
|
||||||
|
(
|
||||||
|
Type::Custom("expression".into()),
|
||||||
|
Type::Custom("expression".into()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Type::Custom("dataframe".into()),
|
||||||
|
Type::Custom("dataframe".into()),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.required(
|
||||||
|
"dtype",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"The dtype to cast the column to",
|
||||||
|
)
|
||||||
|
.optional(
|
||||||
|
"column",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"The column to cast. Required when used with a dataframe.",
|
||||||
|
)
|
||||||
|
.category(Category::Custom("dataframe".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Cast a column in a dataframe to a different dtype",
|
||||||
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr cast u8 a | dfr schema",
|
||||||
|
result: Some(Value::record(
|
||||||
|
record! {
|
||||||
|
"a" => Value::string("u8", Span::test_data()),
|
||||||
|
"b" => Value::string("i64", Span::test_data()),
|
||||||
|
},
|
||||||
|
Span::test_data(),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Cast a column in a lazy dataframe to a different dtype",
|
||||||
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr into-lazy | dfr cast u8 a | dfr schema",
|
||||||
|
result: Some(Value::record(
|
||||||
|
record! {
|
||||||
|
"a" => Value::string("u8", Span::test_data()),
|
||||||
|
"b" => Value::string("i64", Span::test_data()),
|
||||||
|
},
|
||||||
|
Span::test_data(),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Cast a column in a expression to a different dtype",
|
||||||
|
example: r#"[[a b]; [1 2] [1 4]] | dfr into-df | dfr group-by a | dfr agg [ (dfr col b | dfr cast u8 | dfr min | dfr as "b_min") ] | dfr schema"#,
|
||||||
|
result: None
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let value = input.into_value(call.head);
|
||||||
|
if NuLazyFrame::can_downcast(&value) {
|
||||||
|
let (dtype, column_nm) = df_args(engine_state, stack, call)?;
|
||||||
|
let df = NuLazyFrame::try_from_value(value)?;
|
||||||
|
command_lazy(call, column_nm, dtype, df)
|
||||||
|
} else if NuDataFrame::can_downcast(&value) {
|
||||||
|
let (dtype, column_nm) = df_args(engine_state, stack, call)?;
|
||||||
|
let df = NuDataFrame::try_from_value(value)?;
|
||||||
|
command_eager(call, column_nm, dtype, df)
|
||||||
|
} else {
|
||||||
|
let dtype: String = call.req(engine_state, stack, 0)?;
|
||||||
|
let dtype = str_to_dtype(&dtype, call.head)?;
|
||||||
|
|
||||||
|
let expr = NuExpression::try_from_value(value)?;
|
||||||
|
let expr: NuExpression = expr.into_polars().cast(dtype).into();
|
||||||
|
|
||||||
|
Ok(PipelineData::Value(
|
||||||
|
NuExpression::into_value(expr, call.head),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn df_args(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<(DataType, String), ShellError> {
|
||||||
|
let dtype = dtype_arg(engine_state, stack, call)?;
|
||||||
|
let column_nm: String =
|
||||||
|
call.opt(engine_state, stack, 1)?
|
||||||
|
.ok_or(ShellError::MissingParameter {
|
||||||
|
param_name: "column_name".into(),
|
||||||
|
span: call.head,
|
||||||
|
})?;
|
||||||
|
Ok((dtype, column_nm))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dtype_arg(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<DataType, ShellError> {
|
||||||
|
let dtype: String = call.req(engine_state, stack, 0)?;
|
||||||
|
str_to_dtype(&dtype, call.head)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_lazy(
|
||||||
|
call: &Call,
|
||||||
|
column_nm: String,
|
||||||
|
dtype: DataType,
|
||||||
|
lazy: NuLazyFrame,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let column = col(&column_nm).cast(dtype);
|
||||||
|
let lazy = lazy.into_polars().with_columns(&[column]);
|
||||||
|
let lazy = NuLazyFrame::new(false, lazy);
|
||||||
|
|
||||||
|
Ok(PipelineData::Value(
|
||||||
|
NuLazyFrame::into_value(lazy, call.head)?,
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_eager(
|
||||||
|
call: &Call,
|
||||||
|
column_nm: String,
|
||||||
|
dtype: DataType,
|
||||||
|
nu_df: NuDataFrame,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let mut df = nu_df.df;
|
||||||
|
let column = df
|
||||||
|
.column(&column_nm)
|
||||||
|
.map_err(|e| ShellError::GenericError {
|
||||||
|
error: format!("{e}"),
|
||||||
|
msg: "".into(),
|
||||||
|
span: Some(call.head),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let casted = column.cast(&dtype).map_err(|e| ShellError::GenericError {
|
||||||
|
error: format!("{e}"),
|
||||||
|
msg: "".into(),
|
||||||
|
span: Some(call.head),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let _ = df
|
||||||
|
.with_column(casted)
|
||||||
|
.map_err(|e| ShellError::GenericError {
|
||||||
|
error: format!("{e}"),
|
||||||
|
msg: "".into(),
|
||||||
|
span: Some(call.head),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let df = NuDataFrame::new(false, df);
|
||||||
|
Ok(PipelineData::Value(df.into_value(call.head), None))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
|
||||||
|
use super::super::super::test_dataframe::test_dataframe;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
test_dataframe(vec![Box::new(CastDF {})])
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,5 @@
|
|||||||
use super::super::values::NuDataFrame;
|
use crate::dataframe::values::NuDataFrame;
|
||||||
use nu_protocol::{
|
use nu_engine::command_prelude::*;
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ColumnsDF;
|
pub struct ColumnsDF;
|
||||||
|
@ -1,12 +1,5 @@
|
|||||||
use nu_engine::CallExt;
|
use crate::dataframe::values::{utils::convert_columns, Column, NuDataFrame};
|
||||||
use nu_protocol::{
|
use nu_engine::command_prelude::*;
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::super::values::utils::convert_columns;
|
|
||||||
use super::super::values::{Column, NuDataFrame};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DropDF;
|
pub struct DropDF;
|
||||||
@ -35,10 +28,13 @@ impl Command for DropDF {
|
|||||||
description: "drop column a",
|
description: "drop column a",
|
||||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr drop a",
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr drop a",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![Column::new(
|
NuDataFrame::try_from_columns(
|
||||||
"b".to_string(),
|
vec![Column::new(
|
||||||
vec![Value::test_int(2), Value::test_int(4)],
|
"b".to_string(),
|
||||||
)])
|
vec![Value::test_int(2), Value::test_int(4)],
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
|
@ -1,13 +1,7 @@
|
|||||||
use nu_engine::CallExt;
|
use crate::dataframe::values::{utils::convert_columns_string, Column, NuDataFrame};
|
||||||
use nu_protocol::{
|
use nu_engine::command_prelude::*;
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
use polars::prelude::UniqueKeepStrategy;
|
|
||||||
|
|
||||||
use super::super::values::utils::convert_columns_string;
|
use polars::prelude::UniqueKeepStrategy;
|
||||||
use super::super::values::{Column, NuDataFrame};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DropDuplicates;
|
pub struct DropDuplicates;
|
||||||
@ -46,16 +40,19 @@ impl Command for DropDuplicates {
|
|||||||
description: "drop duplicates",
|
description: "drop duplicates",
|
||||||
example: "[[a b]; [1 2] [3 4] [1 2]] | dfr into-df | dfr drop-duplicates",
|
example: "[[a b]; [1 2] [3 4] [1 2]] | dfr into-df | dfr drop-duplicates",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new(
|
vec![
|
||||||
"a".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(3), Value::test_int(1)],
|
"a".to_string(),
|
||||||
),
|
vec![Value::test_int(3), Value::test_int(1)],
|
||||||
Column::new(
|
),
|
||||||
"b".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(4), Value::test_int(2)],
|
"b".to_string(),
|
||||||
),
|
vec![Value::test_int(4), Value::test_int(2)],
|
||||||
])
|
),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
@ -92,7 +89,7 @@ fn command(
|
|||||||
|
|
||||||
let subset_slice = subset.as_ref().map(|cols| &cols[..]);
|
let subset_slice = subset.as_ref().map(|cols| &cols[..]);
|
||||||
|
|
||||||
let keep_strategy = if call.has_flag("last") {
|
let keep_strategy = if call.has_flag(engine_state, stack, "last")? {
|
||||||
UniqueKeepStrategy::Last
|
UniqueKeepStrategy::Last
|
||||||
} else {
|
} else {
|
||||||
UniqueKeepStrategy::First
|
UniqueKeepStrategy::First
|
||||||
|
@ -1,12 +1,5 @@
|
|||||||
use nu_engine::CallExt;
|
use crate::dataframe::values::{utils::convert_columns_string, Column, NuDataFrame};
|
||||||
use nu_protocol::{
|
use nu_engine::command_prelude::*;
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::super::values::utils::convert_columns_string;
|
|
||||||
use super::super::values::{Column, NuDataFrame};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DropNulls;
|
pub struct DropNulls;
|
||||||
@ -43,20 +36,23 @@ impl Command for DropNulls {
|
|||||||
let a = ($df | dfr with-column $res --name res);
|
let a = ($df | dfr with-column $res --name res);
|
||||||
$a | dfr drop-nulls"#,
|
$a | dfr drop-nulls"#,
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new(
|
vec![
|
||||||
"a".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(1), Value::test_int(1)],
|
"a".to_string(),
|
||||||
),
|
vec![Value::test_int(1), Value::test_int(1)],
|
||||||
Column::new(
|
),
|
||||||
"b".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(2), Value::test_int(2)],
|
"b".to_string(),
|
||||||
),
|
vec![Value::test_int(2), Value::test_int(2)],
|
||||||
Column::new(
|
),
|
||||||
"res".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(1), Value::test_int(1)],
|
"res".to_string(),
|
||||||
),
|
vec![Value::test_int(1), Value::test_int(1)],
|
||||||
])
|
),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
@ -66,15 +62,18 @@ impl Command for DropNulls {
|
|||||||
example: r#"let s = ([1 2 0 0 3 4] | dfr into-df);
|
example: r#"let s = ([1 2 0 0 3 4] | dfr into-df);
|
||||||
($s / $s) | dfr drop-nulls"#,
|
($s / $s) | dfr drop-nulls"#,
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![Column::new(
|
NuDataFrame::try_from_columns(
|
||||||
"div_0_0".to_string(),
|
vec![Column::new(
|
||||||
vec![
|
"div_0_0".to_string(),
|
||||||
Value::test_int(1),
|
vec![
|
||||||
Value::test_int(1),
|
Value::test_int(1),
|
||||||
Value::test_int(1),
|
Value::test_int(1),
|
||||||
Value::test_int(1),
|
Value::test_int(1),
|
||||||
],
|
Value::test_int(1),
|
||||||
)])
|
],
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
use super::super::values::{Column, NuDataFrame};
|
use crate::dataframe::values::{Column, NuDataFrame};
|
||||||
use nu_protocol::{
|
use nu_engine::command_prelude::*;
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DataTypes;
|
pub struct DataTypes;
|
||||||
@ -31,16 +27,19 @@ impl Command for DataTypes {
|
|||||||
description: "Dataframe dtypes",
|
description: "Dataframe dtypes",
|
||||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr dtypes",
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr dtypes",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new(
|
vec![
|
||||||
"column".to_string(),
|
Column::new(
|
||||||
vec![Value::test_string("a"), Value::test_string("b")],
|
"column".to_string(),
|
||||||
),
|
vec![Value::test_string("a"), Value::test_string("b")],
|
||||||
Column::new(
|
),
|
||||||
"dtype".to_string(),
|
Column::new(
|
||||||
vec![Value::test_string("i64"), Value::test_string("i64")],
|
"dtype".to_string(),
|
||||||
),
|
vec![Value::test_string("i64"), Value::test_string("i64")],
|
||||||
])
|
),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
@ -79,6 +78,7 @@ fn command(
|
|||||||
.dtype();
|
.dtype();
|
||||||
|
|
||||||
let dtype_str = dtype.to_string();
|
let dtype_str = dtype.to_string();
|
||||||
|
|
||||||
dtypes.push(Value::string(dtype_str, call.head));
|
dtypes.push(Value::string(dtype_str, call.head));
|
||||||
|
|
||||||
Value::string(*v, call.head)
|
Value::string(*v, call.head)
|
||||||
@ -88,7 +88,7 @@ fn command(
|
|||||||
let names_col = Column::new("column".to_string(), names);
|
let names_col = Column::new("column".to_string(), names);
|
||||||
let dtypes_col = Column::new("dtype".to_string(), dtypes);
|
let dtypes_col = Column::new("dtype".to_string(), dtypes);
|
||||||
|
|
||||||
NuDataFrame::try_from_columns(vec![names_col, dtypes_col])
|
NuDataFrame::try_from_columns(vec![names_col, dtypes_col], None)
|
||||||
.map(|df| PipelineData::Value(df.into_value(call.head), None))
|
.map(|df| PipelineData::Value(df.into_value(call.head), None))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
use super::super::values::NuDataFrame;
|
use crate::dataframe::values::NuDataFrame;
|
||||||
use nu_protocol::{
|
use nu_engine::command_prelude::*;
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Type,
|
|
||||||
};
|
|
||||||
use polars::{prelude::*, series::Series};
|
use polars::{prelude::*, series::Series};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -78,12 +75,12 @@ impl Command for Dummies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn command(
|
fn command(
|
||||||
_engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
_stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let drop_first: bool = call.has_flag("drop-first");
|
let drop_first: bool = call.has_flag(engine_state, stack, "drop-first")?;
|
||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
|
|
||||||
df.as_ref()
|
df.as_ref()
|
||||||
|
@ -1,15 +1,8 @@
|
|||||||
use nu_engine::CallExt;
|
use crate::dataframe::values::{Column, NuDataFrame, NuExpression, NuLazyFrame};
|
||||||
use nu_protocol::{
|
use nu_engine::command_prelude::*;
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
use polars::prelude::LazyFrame;
|
use polars::prelude::LazyFrame;
|
||||||
|
|
||||||
use crate::dataframe::values::{NuExpression, NuLazyFrame};
|
|
||||||
|
|
||||||
use super::super::values::{Column, NuDataFrame};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FilterWith;
|
pub struct FilterWith;
|
||||||
|
|
||||||
@ -43,10 +36,13 @@ impl Command for FilterWith {
|
|||||||
example: r#"let mask = ([true false] | dfr into-df);
|
example: r#"let mask = ([true false] | dfr into-df);
|
||||||
[[a b]; [1 2] [3 4]] | dfr into-df | dfr filter-with $mask"#,
|
[[a b]; [1 2] [3 4]] | dfr into-df | dfr filter-with $mask"#,
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new("a".to_string(), vec![Value::test_int(1)]),
|
vec![
|
||||||
Column::new("b".to_string(), vec![Value::test_int(2)]),
|
Column::new("a".to_string(), vec![Value::test_int(1)]),
|
||||||
])
|
Column::new("b".to_string(), vec![Value::test_int(2)]),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
@ -55,10 +51,13 @@ impl Command for FilterWith {
|
|||||||
description: "Filter dataframe using an expression",
|
description: "Filter dataframe using an expression",
|
||||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr filter-with ((dfr col a) > 1)",
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr filter-with ((dfr col a) > 1)",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new("a".to_string(), vec![Value::test_int(3)]),
|
vec![
|
||||||
Column::new("b".to_string(), vec![Value::test_int(4)]),
|
Column::new("a".to_string(), vec![Value::test_int(3)]),
|
||||||
])
|
Column::new("b".to_string(), vec![Value::test_int(4)]),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
use super::super::values::{Column, NuDataFrame, NuExpression};
|
use crate::dataframe::values::{Column, NuDataFrame, NuExpression};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::{
|
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FirstDF;
|
pub struct FirstDF;
|
||||||
@ -44,10 +39,13 @@ impl Command for FirstDF {
|
|||||||
description: "Return the first row of a dataframe",
|
description: "Return the first row of a dataframe",
|
||||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr first",
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr first",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new("a".to_string(), vec![Value::test_int(1)]),
|
vec![
|
||||||
Column::new("b".to_string(), vec![Value::test_int(2)]),
|
Column::new("a".to_string(), vec![Value::test_int(1)]),
|
||||||
])
|
Column::new("b".to_string(), vec![Value::test_int(2)]),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("should not fail")
|
.expect("should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
@ -56,16 +54,19 @@ impl Command for FirstDF {
|
|||||||
description: "Return the first two rows of a dataframe",
|
description: "Return the first two rows of a dataframe",
|
||||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr first 2",
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr first 2",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new(
|
vec![
|
||||||
"a".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(1), Value::test_int(3)],
|
"a".to_string(),
|
||||||
),
|
vec![Value::test_int(1), Value::test_int(3)],
|
||||||
Column::new(
|
),
|
||||||
"b".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(2), Value::test_int(4)],
|
"b".to_string(),
|
||||||
),
|
vec![Value::test_int(2), Value::test_int(4)],
|
||||||
])
|
),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("should not fail")
|
.expect("should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
|
@ -1,13 +1,5 @@
|
|||||||
use nu_engine::CallExt;
|
use crate::dataframe::values::{utils::convert_columns_string, Column, NuDataFrame};
|
||||||
use nu_protocol::{
|
use nu_engine::command_prelude::*;
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::dataframe::values::utils::convert_columns_string;
|
|
||||||
|
|
||||||
use super::super::values::{Column, NuDataFrame};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct GetDF;
|
pub struct GetDF;
|
||||||
@ -36,10 +28,13 @@ impl Command for GetDF {
|
|||||||
description: "Returns the selected column",
|
description: "Returns the selected column",
|
||||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr get a",
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr get a",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![Column::new(
|
NuDataFrame::try_from_columns(
|
||||||
"a".to_string(),
|
vec![Column::new(
|
||||||
vec![Value::test_int(1), Value::test_int(3)],
|
"a".to_string(),
|
||||||
)])
|
vec![Value::test_int(1), Value::test_int(3)],
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
use super::super::values::{utils::DEFAULT_ROWS, Column, NuDataFrame, NuExpression};
|
use crate::dataframe::values::{utils::DEFAULT_ROWS, Column, NuDataFrame, NuExpression};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::{
|
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LastDF;
|
pub struct LastDF;
|
||||||
@ -40,10 +35,13 @@ impl Command for LastDF {
|
|||||||
description: "Create new dataframe with last rows",
|
description: "Create new dataframe with last rows",
|
||||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr last 1",
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr last 1",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new("a".to_string(), vec![Value::test_int(3)]),
|
vec![
|
||||||
Column::new("b".to_string(), vec![Value::test_int(4)]),
|
Column::new("a".to_string(), vec![Value::test_int(3)]),
|
||||||
])
|
Column::new("b".to_string(), vec![Value::test_int(4)]),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
use nu_protocol::{
|
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::dataframe::values::NuDataFrame;
|
use crate::dataframe::values::NuDataFrame;
|
||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ListDF;
|
pub struct ListDF;
|
||||||
|
@ -1,14 +1,5 @@
|
|||||||
use nu_engine::CallExt;
|
use crate::dataframe::values::{utils::convert_columns_string, Column, NuDataFrame};
|
||||||
use nu_protocol::{
|
use nu_engine::command_prelude::*;
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type,
|
|
||||||
Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::dataframe::values::utils::convert_columns_string;
|
|
||||||
|
|
||||||
use super::super::values::{Column, NuDataFrame};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct MeltDF;
|
pub struct MeltDF;
|
||||||
@ -106,7 +97,7 @@ impl Command for MeltDF {
|
|||||||
Value::test_string("c"),
|
Value::test_string("c"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
])
|
], None)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
mod append;
|
mod append;
|
||||||
|
mod cast;
|
||||||
mod columns;
|
mod columns;
|
||||||
mod drop;
|
mod drop;
|
||||||
mod drop_duplicates;
|
mod drop_duplicates;
|
||||||
@ -15,6 +16,7 @@ mod open;
|
|||||||
mod query_df;
|
mod query_df;
|
||||||
mod rename;
|
mod rename;
|
||||||
mod sample;
|
mod sample;
|
||||||
|
mod schema;
|
||||||
mod shape;
|
mod shape;
|
||||||
mod slice;
|
mod slice;
|
||||||
mod sql_context;
|
mod sql_context;
|
||||||
@ -34,6 +36,7 @@ use nu_protocol::engine::StateWorkingSet;
|
|||||||
|
|
||||||
pub use self::open::OpenDataFrame;
|
pub use self::open::OpenDataFrame;
|
||||||
pub use append::AppendDF;
|
pub use append::AppendDF;
|
||||||
|
pub use cast::CastDF;
|
||||||
pub use columns::ColumnsDF;
|
pub use columns::ColumnsDF;
|
||||||
pub use drop::DropDF;
|
pub use drop::DropDF;
|
||||||
pub use drop_duplicates::DropDuplicates;
|
pub use drop_duplicates::DropDuplicates;
|
||||||
@ -49,10 +52,10 @@ pub use melt::MeltDF;
|
|||||||
pub use query_df::QueryDf;
|
pub use query_df::QueryDf;
|
||||||
pub use rename::RenameDF;
|
pub use rename::RenameDF;
|
||||||
pub use sample::SampleDF;
|
pub use sample::SampleDF;
|
||||||
|
pub use schema::SchemaDF;
|
||||||
pub use shape::ShapeDF;
|
pub use shape::ShapeDF;
|
||||||
pub use slice::SliceDF;
|
pub use slice::SliceDF;
|
||||||
pub use sql_context::SQLContext;
|
pub use sql_context::SQLContext;
|
||||||
pub use sql_expr::parse_sql_expr;
|
|
||||||
pub use summary::Summary;
|
pub use summary::Summary;
|
||||||
pub use take::TakeDF;
|
pub use take::TakeDF;
|
||||||
pub use to_arrow::ToArrow;
|
pub use to_arrow::ToArrow;
|
||||||
@ -77,6 +80,7 @@ pub fn add_eager_decls(working_set: &mut StateWorkingSet) {
|
|||||||
// Dataframe commands
|
// Dataframe commands
|
||||||
bind_command!(
|
bind_command!(
|
||||||
AppendDF,
|
AppendDF,
|
||||||
|
CastDF,
|
||||||
ColumnsDF,
|
ColumnsDF,
|
||||||
DataTypes,
|
DataTypes,
|
||||||
Summary,
|
Summary,
|
||||||
@ -94,6 +98,7 @@ pub fn add_eager_decls(working_set: &mut StateWorkingSet) {
|
|||||||
QueryDf,
|
QueryDf,
|
||||||
RenameDF,
|
RenameDF,
|
||||||
SampleDF,
|
SampleDF,
|
||||||
|
SchemaDF,
|
||||||
ShapeDF,
|
ShapeDF,
|
||||||
SliceDF,
|
SliceDF,
|
||||||
TakeDF,
|
TakeDF,
|
||||||
|
@ -1,19 +1,12 @@
|
|||||||
use super::super::values::{NuDataFrame, NuLazyFrame};
|
use crate::dataframe::values::{NuDataFrame, NuLazyFrame, NuSchema};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::{
|
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::{fs::File, io::BufReader, path::PathBuf};
|
|
||||||
|
|
||||||
use polars::prelude::{
|
use polars::prelude::{
|
||||||
CsvEncoding, CsvReader, IpcReader, JsonFormat, JsonReader, LazyCsvReader, LazyFileListReader,
|
CsvEncoding, CsvReader, IpcReader, JsonFormat, JsonReader, LazyCsvReader, LazyFileListReader,
|
||||||
LazyFrame, ParallelStrategy, ParquetReader, ScanArgsIpc, ScanArgsParquet, SerReader,
|
LazyFrame, ParallelStrategy, ParquetReader, ScanArgsIpc, ScanArgsParquet, SerReader,
|
||||||
};
|
};
|
||||||
|
use polars_io::{avro::AvroReader, HiveOptions};
|
||||||
use polars_io::avro::AvroReader;
|
use std::{fs::File, io::BufReader, path::PathBuf};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct OpenDataFrame;
|
pub struct OpenDataFrame;
|
||||||
@ -70,6 +63,12 @@ impl Command for OpenDataFrame {
|
|||||||
"Columns to be selected from csv file. CSV and Parquet file",
|
"Columns to be selected from csv file. CSV and Parquet file",
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
.named(
|
||||||
|
"schema",
|
||||||
|
SyntaxShape::Record(vec![]),
|
||||||
|
r#"Polars Schema in format [{name: str}]. CSV, JSON, and JSONL files"#,
|
||||||
|
Some('s')
|
||||||
|
)
|
||||||
.input_output_type(Type::Any, Type::Custom("dataframe".into()))
|
.input_output_type(Type::Any, Type::Custom("dataframe".into()))
|
||||||
.category(Category::Custom("dataframe".into()))
|
.category(Category::Custom("dataframe".into()))
|
||||||
}
|
}
|
||||||
@ -122,7 +121,9 @@ fn command(
|
|||||||
"jsonl" => from_jsonl(engine_state, stack, call),
|
"jsonl" => from_jsonl(engine_state, stack, call),
|
||||||
"avro" => from_avro(engine_state, stack, call),
|
"avro" => from_avro(engine_state, stack, call),
|
||||||
_ => Err(ShellError::FileNotFoundCustom {
|
_ => Err(ShellError::FileNotFoundCustom {
|
||||||
msg: format!("{msg}. Supported values: csv, tsv, parquet, ipc, arrow, json"),
|
msg: format!(
|
||||||
|
"{msg}. Supported values: csv, tsv, parquet, ipc, arrow, json, jsonl, avro"
|
||||||
|
),
|
||||||
span: blamed,
|
span: blamed,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -139,18 +140,18 @@ fn from_parquet(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
if call.has_flag("lazy") {
|
if call.has_flag(engine_state, stack, "lazy")? {
|
||||||
let file: String = call.req(engine_state, stack, 0)?;
|
let file: String = call.req(engine_state, stack, 0)?;
|
||||||
let args = ScanArgsParquet {
|
let args = ScanArgsParquet {
|
||||||
n_rows: None,
|
n_rows: None,
|
||||||
cache: true,
|
cache: true,
|
||||||
parallel: ParallelStrategy::Auto,
|
parallel: ParallelStrategy::Auto,
|
||||||
rechunk: false,
|
rechunk: false,
|
||||||
row_count: None,
|
row_index: None,
|
||||||
low_memory: false,
|
low_memory: false,
|
||||||
cloud_options: None,
|
cloud_options: None,
|
||||||
use_statistics: false,
|
use_statistics: false,
|
||||||
hive_partitioning: false,
|
hive_options: HiveOptions::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let df: NuLazyFrame = LazyFrame::scan_parquet(file, args)
|
let df: NuLazyFrame = LazyFrame::scan_parquet(file, args)
|
||||||
@ -238,14 +239,15 @@ fn from_ipc(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
if call.has_flag("lazy") {
|
if call.has_flag(engine_state, stack, "lazy")? {
|
||||||
let file: String = call.req(engine_state, stack, 0)?;
|
let file: String = call.req(engine_state, stack, 0)?;
|
||||||
let args = ScanArgsIpc {
|
let args = ScanArgsIpc {
|
||||||
n_rows: None,
|
n_rows: None,
|
||||||
cache: true,
|
cache: true,
|
||||||
rechunk: false,
|
rechunk: false,
|
||||||
row_count: None,
|
row_index: None,
|
||||||
memmap: true,
|
memory_map: true,
|
||||||
|
cloud_options: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let df: NuLazyFrame = LazyFrame::scan_ipc(file, args)
|
let df: NuLazyFrame = LazyFrame::scan_ipc(file, args)
|
||||||
@ -305,10 +307,19 @@ fn from_json(
|
|||||||
help: None,
|
help: None,
|
||||||
inner: vec![],
|
inner: vec![],
|
||||||
})?;
|
})?;
|
||||||
|
let maybe_schema = call
|
||||||
|
.get_flag(engine_state, stack, "schema")?
|
||||||
|
.map(|schema| NuSchema::try_from(&schema))
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
let buf_reader = BufReader::new(file);
|
let buf_reader = BufReader::new(file);
|
||||||
let reader = JsonReader::new(buf_reader);
|
let reader = JsonReader::new(buf_reader);
|
||||||
|
|
||||||
|
let reader = match maybe_schema {
|
||||||
|
Some(schema) => reader.with_schema(schema.into()),
|
||||||
|
None => reader,
|
||||||
|
};
|
||||||
|
|
||||||
let df: NuDataFrame = reader
|
let df: NuDataFrame = reader
|
||||||
.finish()
|
.finish()
|
||||||
.map_err(|e| ShellError::GenericError {
|
.map_err(|e| ShellError::GenericError {
|
||||||
@ -329,6 +340,10 @@ fn from_jsonl(
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
let infer_schema: Option<usize> = call.get_flag(engine_state, stack, "infer-schema")?;
|
let infer_schema: Option<usize> = call.get_flag(engine_state, stack, "infer-schema")?;
|
||||||
|
let maybe_schema = call
|
||||||
|
.get_flag(engine_state, stack, "schema")?
|
||||||
|
.map(|schema| NuSchema::try_from(&schema))
|
||||||
|
.transpose()?;
|
||||||
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
||||||
let file = File::open(&file.item).map_err(|e| ShellError::GenericError {
|
let file = File::open(&file.item).map_err(|e| ShellError::GenericError {
|
||||||
error: "Error opening file".into(),
|
error: "Error opening file".into(),
|
||||||
@ -343,6 +358,11 @@ fn from_jsonl(
|
|||||||
.with_json_format(JsonFormat::JsonLines)
|
.with_json_format(JsonFormat::JsonLines)
|
||||||
.infer_schema_len(infer_schema);
|
.infer_schema_len(infer_schema);
|
||||||
|
|
||||||
|
let reader = match maybe_schema {
|
||||||
|
Some(schema) => reader.with_schema(schema.into()),
|
||||||
|
None => reader,
|
||||||
|
};
|
||||||
|
|
||||||
let df: NuDataFrame = reader
|
let df: NuDataFrame = reader
|
||||||
.finish()
|
.finish()
|
||||||
.map_err(|e| ShellError::GenericError {
|
.map_err(|e| ShellError::GenericError {
|
||||||
@ -363,12 +383,17 @@ fn from_csv(
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
let delimiter: Option<Spanned<String>> = call.get_flag(engine_state, stack, "delimiter")?;
|
let delimiter: Option<Spanned<String>> = call.get_flag(engine_state, stack, "delimiter")?;
|
||||||
let no_header: bool = call.has_flag("no-header");
|
let no_header: bool = call.has_flag(engine_state, stack, "no-header")?;
|
||||||
let infer_schema: Option<usize> = call.get_flag(engine_state, stack, "infer-schema")?;
|
let infer_schema: Option<usize> = call.get_flag(engine_state, stack, "infer-schema")?;
|
||||||
let skip_rows: Option<usize> = call.get_flag(engine_state, stack, "skip-rows")?;
|
let skip_rows: Option<usize> = call.get_flag(engine_state, stack, "skip-rows")?;
|
||||||
let columns: Option<Vec<String>> = call.get_flag(engine_state, stack, "columns")?;
|
let columns: Option<Vec<String>> = call.get_flag(engine_state, stack, "columns")?;
|
||||||
|
|
||||||
if call.has_flag("lazy") {
|
let maybe_schema = call
|
||||||
|
.get_flag(engine_state, stack, "schema")?
|
||||||
|
.map(|schema| NuSchema::try_from(&schema))
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
|
if call.has_flag(engine_state, stack, "lazy")? {
|
||||||
let file: String = call.req(engine_state, stack, 0)?;
|
let file: String = call.req(engine_state, stack, 0)?;
|
||||||
let csv_reader = LazyCsvReader::new(file);
|
let csv_reader = LazyCsvReader::new(file);
|
||||||
|
|
||||||
@ -395,6 +420,11 @@ fn from_csv(
|
|||||||
|
|
||||||
let csv_reader = csv_reader.has_header(!no_header);
|
let csv_reader = csv_reader.has_header(!no_header);
|
||||||
|
|
||||||
|
let csv_reader = match maybe_schema {
|
||||||
|
Some(schema) => csv_reader.with_schema(Some(schema.into())),
|
||||||
|
None => csv_reader,
|
||||||
|
};
|
||||||
|
|
||||||
let csv_reader = match infer_schema {
|
let csv_reader = match infer_schema {
|
||||||
None => csv_reader,
|
None => csv_reader,
|
||||||
Some(r) => csv_reader.with_infer_schema_length(Some(r)),
|
Some(r) => csv_reader.with_infer_schema_length(Some(r)),
|
||||||
@ -452,6 +482,11 @@ fn from_csv(
|
|||||||
|
|
||||||
let csv_reader = csv_reader.has_header(!no_header);
|
let csv_reader = csv_reader.has_header(!no_header);
|
||||||
|
|
||||||
|
let csv_reader = match maybe_schema {
|
||||||
|
Some(schema) => csv_reader.with_schema(Some(schema.into())),
|
||||||
|
None => csv_reader,
|
||||||
|
};
|
||||||
|
|
||||||
let csv_reader = match infer_schema {
|
let csv_reader = match infer_schema {
|
||||||
None => csv_reader,
|
None => csv_reader,
|
||||||
Some(r) => csv_reader.infer_schema(Some(r)),
|
Some(r) => csv_reader.infer_schema(Some(r)),
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
use super::super::values::NuDataFrame;
|
use crate::dataframe::{
|
||||||
use crate::dataframe::values::Column;
|
eager::SQLContext,
|
||||||
use crate::dataframe::{eager::SQLContext, values::NuLazyFrame};
|
values::{Column, NuDataFrame, NuLazyFrame},
|
||||||
use nu_engine::CallExt;
|
|
||||||
use nu_protocol::{
|
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
|
||||||
};
|
};
|
||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
// attribution:
|
// attribution:
|
||||||
// sql_context.rs, and sql_expr.rs were copied from polars-sql. thank you.
|
// sql_context.rs, and sql_expr.rs were copied from polars-sql. thank you.
|
||||||
@ -44,10 +40,13 @@ impl Command for QueryDf {
|
|||||||
description: "Query dataframe using SQL",
|
description: "Query dataframe using SQL",
|
||||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr query 'select a from df'",
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr query 'select a from df'",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![Column::new(
|
NuDataFrame::try_from_columns(
|
||||||
"a".to_string(),
|
vec![Column::new(
|
||||||
vec![Value::test_int(1), Value::test_int(3)],
|
"a".to_string(),
|
||||||
)])
|
vec![Value::test_int(1), Value::test_int(3)],
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
@ -88,7 +87,7 @@ fn command(
|
|||||||
let lazy = NuLazyFrame::new(false, df_sql);
|
let lazy = NuLazyFrame::new(false, df_sql);
|
||||||
|
|
||||||
let eager = lazy.collect(call.head)?;
|
let eager = lazy.collect(call.head)?;
|
||||||
let value = Value::custom_value(Box::new(eager), call.head);
|
let value = Value::custom(Box::new(eager), call.head);
|
||||||
|
|
||||||
Ok(PipelineData::Value(value, None))
|
Ok(PipelineData::Value(value, None))
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
use nu_engine::CallExt;
|
use crate::dataframe::{
|
||||||
use nu_protocol::{
|
utils::extract_strings,
|
||||||
ast::Call,
|
values::{Column, NuDataFrame, NuLazyFrame},
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
|
||||||
};
|
};
|
||||||
|
use nu_engine::command_prelude::*;
|
||||||
use crate::dataframe::{utils::extract_strings, values::NuLazyFrame};
|
|
||||||
|
|
||||||
use super::super::values::{Column, NuDataFrame};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct RenameDF;
|
pub struct RenameDF;
|
||||||
@ -46,15 +41,18 @@ impl Command for RenameDF {
|
|||||||
description: "Renames a series",
|
description: "Renames a series",
|
||||||
example: "[5 6 7 8] | dfr into-df | dfr rename '0' new_name",
|
example: "[5 6 7 8] | dfr into-df | dfr rename '0' new_name",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![Column::new(
|
NuDataFrame::try_from_columns(
|
||||||
"new_name".to_string(),
|
vec![Column::new(
|
||||||
vec![
|
"new_name".to_string(),
|
||||||
Value::test_int(5),
|
vec![
|
||||||
Value::test_int(6),
|
Value::test_int(5),
|
||||||
Value::test_int(7),
|
Value::test_int(6),
|
||||||
Value::test_int(8),
|
Value::test_int(7),
|
||||||
],
|
Value::test_int(8),
|
||||||
)])
|
],
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
@ -63,16 +61,19 @@ impl Command for RenameDF {
|
|||||||
description: "Renames a dataframe column",
|
description: "Renames a dataframe column",
|
||||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr rename a a_new",
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr rename a a_new",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new(
|
vec![
|
||||||
"a_new".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(1), Value::test_int(3)],
|
"a_new".to_string(),
|
||||||
),
|
vec![Value::test_int(1), Value::test_int(3)],
|
||||||
Column::new(
|
),
|
||||||
"b".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(2), Value::test_int(4)],
|
"b".to_string(),
|
||||||
),
|
vec![Value::test_int(2), Value::test_int(4)],
|
||||||
])
|
),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
@ -81,16 +82,19 @@ impl Command for RenameDF {
|
|||||||
description: "Renames two dataframe columns",
|
description: "Renames two dataframe columns",
|
||||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr rename [a b] [a_new b_new]",
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr rename [a b] [a_new b_new]",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new(
|
vec![
|
||||||
"a_new".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(1), Value::test_int(3)],
|
"a_new".to_string(),
|
||||||
),
|
vec![Value::test_int(1), Value::test_int(3)],
|
||||||
Column::new(
|
),
|
||||||
"b_new".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(2), Value::test_int(4)],
|
"b_new".to_string(),
|
||||||
),
|
vec![Value::test_int(2), Value::test_int(4)],
|
||||||
])
|
),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
|
@ -1,13 +1,7 @@
|
|||||||
use nu_engine::CallExt;
|
use crate::dataframe::values::NuDataFrame;
|
||||||
use nu_protocol::{
|
use nu_engine::command_prelude::*;
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type,
|
|
||||||
};
|
|
||||||
use polars::prelude::NamedFrom;
|
|
||||||
use polars::series::Series;
|
|
||||||
|
|
||||||
use super::super::values::NuDataFrame;
|
use polars::{prelude::NamedFrom, series::Series};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SampleDF;
|
pub struct SampleDF;
|
||||||
@ -88,8 +82,8 @@ fn command(
|
|||||||
let seed: Option<u64> = call
|
let seed: Option<u64> = call
|
||||||
.get_flag::<i64>(engine_state, stack, "seed")?
|
.get_flag::<i64>(engine_state, stack, "seed")?
|
||||||
.map(|val| val as u64);
|
.map(|val| val as u64);
|
||||||
let replace: bool = call.has_flag("replace");
|
let replace: bool = call.has_flag(engine_state, stack, "replace")?;
|
||||||
let shuffle: bool = call.has_flag("shuffle");
|
let shuffle: bool = call.has_flag(engine_state, stack, "shuffle")?;
|
||||||
|
|
||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
|
|
||||||
|
112
crates/nu-cmd-dataframe/src/dataframe/eager/schema.rs
Normal file
112
crates/nu-cmd-dataframe/src/dataframe/eager/schema.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
use crate::dataframe::values::NuDataFrame;
|
||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SchemaDF;
|
||||||
|
|
||||||
|
impl Command for SchemaDF {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"dfr schema"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Show schema for a dataframe."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.switch("datatype-list", "creates a lazy dataframe", Some('l'))
|
||||||
|
.input_output_type(
|
||||||
|
Type::Custom("dataframe".into()),
|
||||||
|
Type::Custom("dataframe".into()),
|
||||||
|
)
|
||||||
|
.category(Category::Custom("dataframe".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Dataframe schema",
|
||||||
|
example: r#"[[a b]; [1 "foo"] [3 "bar"]] | dfr into-df | dfr schema"#,
|
||||||
|
result: Some(Value::record(
|
||||||
|
record! {
|
||||||
|
"a" => Value::string("i64", Span::test_data()),
|
||||||
|
"b" => Value::string("str", Span::test_data()),
|
||||||
|
},
|
||||||
|
Span::test_data(),
|
||||||
|
)),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
if call.has_flag(engine_state, stack, "datatype-list")? {
|
||||||
|
Ok(PipelineData::Value(datatype_list(Span::unknown()), None))
|
||||||
|
} else {
|
||||||
|
command(engine_state, stack, call, input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command(
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
|
let schema = df.schema();
|
||||||
|
let value: Value = schema.into();
|
||||||
|
Ok(PipelineData::Value(value, None))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn datatype_list(span: Span) -> Value {
|
||||||
|
let types: Vec<Value> = [
|
||||||
|
("null", ""),
|
||||||
|
("bool", ""),
|
||||||
|
("u8", ""),
|
||||||
|
("u16", ""),
|
||||||
|
("u32", ""),
|
||||||
|
("u64", ""),
|
||||||
|
("i8", ""),
|
||||||
|
("i16", ""),
|
||||||
|
("i32", ""),
|
||||||
|
("i64", ""),
|
||||||
|
("f32", ""),
|
||||||
|
("f64", ""),
|
||||||
|
("str", ""),
|
||||||
|
("binary", ""),
|
||||||
|
("date", ""),
|
||||||
|
("datetime<time_unit: (ms, us, ns) timezone (optional)>", "Time Unit can be: milliseconds: ms, microseconds: us, nanoseconds: ns. Timezone wildcard is *. Other Timezone examples: UTC, America/Los_Angeles."),
|
||||||
|
("duration<time_unit: (ms, us, ns)>", "Time Unit can be: milliseconds: ms, microseconds: us, nanoseconds: ns."),
|
||||||
|
("time", ""),
|
||||||
|
("object", ""),
|
||||||
|
("unknown", ""),
|
||||||
|
("list<dtype>", ""),
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.map(|(dtype, note)| {
|
||||||
|
Value::record(record! {
|
||||||
|
"dtype" => Value::string(*dtype, span),
|
||||||
|
"note" => Value::string(*note, span),
|
||||||
|
},
|
||||||
|
span)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Value::list(types, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::super::super::test_dataframe::test_dataframe;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
test_dataframe(vec![Box::new(SchemaDF {})])
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,5 @@
|
|||||||
use nu_protocol::{
|
use crate::dataframe::values::{Column, NuDataFrame};
|
||||||
ast::Call,
|
use nu_engine::command_prelude::*;
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::dataframe::values::Column;
|
|
||||||
|
|
||||||
use super::super::values::NuDataFrame;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ShapeDF;
|
pub struct ShapeDF;
|
||||||
@ -34,10 +27,13 @@ impl Command for ShapeDF {
|
|||||||
description: "Shows row and column shape",
|
description: "Shows row and column shape",
|
||||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr shape",
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr shape",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new("rows".to_string(), vec![Value::test_int(2)]),
|
vec![
|
||||||
Column::new("columns".to_string(), vec![Value::test_int(2)]),
|
Column::new("rows".to_string(), vec![Value::test_int(2)]),
|
||||||
])
|
Column::new("columns".to_string(), vec![Value::test_int(2)]),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
@ -70,7 +66,7 @@ fn command(
|
|||||||
let rows_col = Column::new("rows".to_string(), vec![rows]);
|
let rows_col = Column::new("rows".to_string(), vec![rows]);
|
||||||
let cols_col = Column::new("columns".to_string(), vec![cols]);
|
let cols_col = Column::new("columns".to_string(), vec![cols]);
|
||||||
|
|
||||||
NuDataFrame::try_from_columns(vec![rows_col, cols_col])
|
NuDataFrame::try_from_columns(vec![rows_col, cols_col], None)
|
||||||
.map(|df| PipelineData::Value(df.into_value(call.head), None))
|
.map(|df| PipelineData::Value(df.into_value(call.head), None))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,5 @@
|
|||||||
use nu_engine::CallExt;
|
use crate::dataframe::values::{Column, NuDataFrame};
|
||||||
use nu_protocol::{
|
use nu_engine::command_prelude::*;
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::dataframe::values::Column;
|
|
||||||
|
|
||||||
use super::super::values::NuDataFrame;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SliceDF;
|
pub struct SliceDF;
|
||||||
@ -37,10 +29,13 @@ impl Command for SliceDF {
|
|||||||
description: "Create new dataframe from a slice of the rows",
|
description: "Create new dataframe from a slice of the rows",
|
||||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr slice 0 1",
|
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr slice 0 1",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new("a".to_string(), vec![Value::test_int(1)]),
|
vec![
|
||||||
Column::new("b".to_string(), vec![Value::test_int(2)]),
|
Column::new("a".to_string(), vec![Value::test_int(1)]),
|
||||||
])
|
Column::new("b".to_string(), vec![Value::test_int(2)]),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
|
@ -13,7 +13,7 @@ fn map_sql_polars_datatype(data_type: &SQLDataType) -> Result<DataType> {
|
|||||||
| SQLDataType::Uuid
|
| SQLDataType::Uuid
|
||||||
| SQLDataType::Clob(_)
|
| SQLDataType::Clob(_)
|
||||||
| SQLDataType::Text
|
| SQLDataType::Text
|
||||||
| SQLDataType::String(_) => DataType::Utf8,
|
| SQLDataType::String(_) => DataType::String,
|
||||||
SQLDataType::Float(_) => DataType::Float32,
|
SQLDataType::Float(_) => DataType::Float32,
|
||||||
SQLDataType::Real => DataType::Float32,
|
SQLDataType::Real => DataType::Float32,
|
||||||
SQLDataType::Double => DataType::Float64,
|
SQLDataType::Double => DataType::Float64,
|
||||||
@ -62,7 +62,9 @@ fn binary_op_(left: Expr, right: Expr, op: &SQLBinaryOperator) -> Result<Expr> {
|
|||||||
SQLBinaryOperator::Multiply => left * right,
|
SQLBinaryOperator::Multiply => left * right,
|
||||||
SQLBinaryOperator::Divide => left / right,
|
SQLBinaryOperator::Divide => left / right,
|
||||||
SQLBinaryOperator::Modulo => left % right,
|
SQLBinaryOperator::Modulo => left % right,
|
||||||
SQLBinaryOperator::StringConcat => left.cast(DataType::Utf8) + right.cast(DataType::Utf8),
|
SQLBinaryOperator::StringConcat => {
|
||||||
|
left.cast(DataType::String) + right.cast(DataType::String)
|
||||||
|
}
|
||||||
SQLBinaryOperator::Gt => left.gt(right),
|
SQLBinaryOperator::Gt => left.gt(right),
|
||||||
SQLBinaryOperator::Lt => left.lt(right),
|
SQLBinaryOperator::Lt => left.lt(right),
|
||||||
SQLBinaryOperator::GtEq => left.gt_eq(right),
|
SQLBinaryOperator::GtEq => left.gt_eq(right),
|
||||||
|
@ -1,16 +1,11 @@
|
|||||||
use super::super::values::{Column, NuDataFrame};
|
use crate::dataframe::values::{Column, NuDataFrame};
|
||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
use nu_engine::CallExt;
|
|
||||||
use nu_protocol::{
|
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
use polars::{
|
use polars::{
|
||||||
chunked_array::ChunkedArray,
|
chunked_array::ChunkedArray,
|
||||||
prelude::{
|
prelude::{
|
||||||
AnyValue, DataFrame, DataType, Float64Type, IntoSeries, NewChunkedArray,
|
AnyValue, DataFrame, DataType, Float64Type, IntoSeries, NewChunkedArray,
|
||||||
QuantileInterpolOptions, Series, Utf8Type,
|
QuantileInterpolOptions, Series, StringType,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -46,53 +41,56 @@ impl Command for Summary {
|
|||||||
description: "list dataframe descriptives",
|
description: "list dataframe descriptives",
|
||||||
example: "[[a b]; [1 1] [1 1]] | dfr into-df | dfr summary",
|
example: "[[a b]; [1 1] [1 1]] | dfr into-df | dfr summary",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new(
|
vec![
|
||||||
"descriptor".to_string(),
|
Column::new(
|
||||||
vec![
|
"descriptor".to_string(),
|
||||||
Value::test_string("count"),
|
vec![
|
||||||
Value::test_string("sum"),
|
Value::test_string("count"),
|
||||||
Value::test_string("mean"),
|
Value::test_string("sum"),
|
||||||
Value::test_string("median"),
|
Value::test_string("mean"),
|
||||||
Value::test_string("std"),
|
Value::test_string("median"),
|
||||||
Value::test_string("min"),
|
Value::test_string("std"),
|
||||||
Value::test_string("25%"),
|
Value::test_string("min"),
|
||||||
Value::test_string("50%"),
|
Value::test_string("25%"),
|
||||||
Value::test_string("75%"),
|
Value::test_string("50%"),
|
||||||
Value::test_string("max"),
|
Value::test_string("75%"),
|
||||||
],
|
Value::test_string("max"),
|
||||||
),
|
],
|
||||||
Column::new(
|
),
|
||||||
"a (i64)".to_string(),
|
Column::new(
|
||||||
vec![
|
"a (i64)".to_string(),
|
||||||
Value::test_float(2.0),
|
vec![
|
||||||
Value::test_float(2.0),
|
Value::test_float(2.0),
|
||||||
Value::test_float(1.0),
|
Value::test_float(2.0),
|
||||||
Value::test_float(1.0),
|
Value::test_float(1.0),
|
||||||
Value::test_float(0.0),
|
Value::test_float(1.0),
|
||||||
Value::test_float(1.0),
|
Value::test_float(0.0),
|
||||||
Value::test_float(1.0),
|
Value::test_float(1.0),
|
||||||
Value::test_float(1.0),
|
Value::test_float(1.0),
|
||||||
Value::test_float(1.0),
|
Value::test_float(1.0),
|
||||||
Value::test_float(1.0),
|
Value::test_float(1.0),
|
||||||
],
|
Value::test_float(1.0),
|
||||||
),
|
],
|
||||||
Column::new(
|
),
|
||||||
"b (i64)".to_string(),
|
Column::new(
|
||||||
vec![
|
"b (i64)".to_string(),
|
||||||
Value::test_float(2.0),
|
vec![
|
||||||
Value::test_float(2.0),
|
Value::test_float(2.0),
|
||||||
Value::test_float(1.0),
|
Value::test_float(2.0),
|
||||||
Value::test_float(1.0),
|
Value::test_float(1.0),
|
||||||
Value::test_float(0.0),
|
Value::test_float(1.0),
|
||||||
Value::test_float(1.0),
|
Value::test_float(0.0),
|
||||||
Value::test_float(1.0),
|
Value::test_float(1.0),
|
||||||
Value::test_float(1.0),
|
Value::test_float(1.0),
|
||||||
Value::test_float(1.0),
|
Value::test_float(1.0),
|
||||||
Value::test_float(1.0),
|
Value::test_float(1.0),
|
||||||
],
|
Value::test_float(1.0),
|
||||||
),
|
],
|
||||||
])
|
),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
@ -171,7 +169,7 @@ fn command(
|
|||||||
|
|
||||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
|
|
||||||
let names = ChunkedArray::<Utf8Type>::from_slice_options("descriptor", &labels).into_series();
|
let names = ChunkedArray::<StringType>::from_slice_options("descriptor", &labels).into_series();
|
||||||
|
|
||||||
let head = std::iter::once(names);
|
let head = std::iter::once(names);
|
||||||
|
|
||||||
@ -179,42 +177,50 @@ fn command(
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.get_columns()
|
.get_columns()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|col| col.dtype() != &DataType::Object("object"))
|
.filter(|col| !matches!(col.dtype(), &DataType::Object("object", _)))
|
||||||
.map(|col| {
|
.map(|col| {
|
||||||
let count = col.len() as f64;
|
let count = col.len() as f64;
|
||||||
|
|
||||||
let sum = col
|
let sum = col.sum_as_series().ok().and_then(|series| {
|
||||||
.sum_as_series()
|
series
|
||||||
.cast(&DataType::Float64)
|
.cast(&DataType::Float64)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|ca| match ca.get(0) {
|
.and_then(|ca| match ca.get(0) {
|
||||||
Ok(AnyValue::Float64(v)) => Some(v),
|
Ok(AnyValue::Float64(v)) => Some(v),
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
})
|
||||||
|
});
|
||||||
|
|
||||||
let mean = match col.mean_as_series().get(0) {
|
let mean = match col.mean_as_series().get(0) {
|
||||||
Ok(AnyValue::Float64(v)) => Some(v),
|
Ok(AnyValue::Float64(v)) => Some(v),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let median = match col.median_as_series().get(0) {
|
let median = match col.median_as_series() {
|
||||||
Ok(AnyValue::Float64(v)) => Some(v),
|
Ok(v) => match v.get(0) {
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let std = match col.std_as_series(0).get(0) {
|
|
||||||
Ok(AnyValue::Float64(v)) => Some(v),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let min = col
|
|
||||||
.min_as_series()
|
|
||||||
.cast(&DataType::Float64)
|
|
||||||
.ok()
|
|
||||||
.and_then(|ca| match ca.get(0) {
|
|
||||||
Ok(AnyValue::Float64(v)) => Some(v),
|
Ok(AnyValue::Float64(v)) => Some(v),
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
},
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let std = match col.std_as_series(0) {
|
||||||
|
Ok(v) => match v.get(0) {
|
||||||
|
Ok(AnyValue::Float64(v)) => Some(v),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let min = col.min_as_series().ok().and_then(|series| {
|
||||||
|
series
|
||||||
|
.cast(&DataType::Float64)
|
||||||
|
.ok()
|
||||||
|
.and_then(|ca| match ca.get(0) {
|
||||||
|
Ok(AnyValue::Float64(v)) => Some(v),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
let mut quantiles = quantiles
|
let mut quantiles = quantiles
|
||||||
.clone()
|
.clone()
|
||||||
@ -230,14 +236,15 @@ fn command(
|
|||||||
})
|
})
|
||||||
.collect::<Vec<Option<f64>>>();
|
.collect::<Vec<Option<f64>>>();
|
||||||
|
|
||||||
let max = col
|
let max = col.max_as_series().ok().and_then(|series| {
|
||||||
.max_as_series()
|
series
|
||||||
.cast(&DataType::Float64)
|
.cast(&DataType::Float64)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|ca| match ca.get(0) {
|
.and_then(|ca| match ca.get(0) {
|
||||||
Ok(AnyValue::Float64(v)) => Some(v),
|
Ok(AnyValue::Float64(v)) => Some(v),
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
})
|
||||||
|
});
|
||||||
|
|
||||||
let mut descriptors = vec![Some(count), sum, mean, median, std, min];
|
let mut descriptors = vec![Some(count), sum, mean, median, std, min];
|
||||||
descriptors.append(&mut quantiles);
|
descriptors.append(&mut quantiles);
|
||||||
|
@ -1,15 +1,8 @@
|
|||||||
use nu_engine::CallExt;
|
use crate::dataframe::values::{Column, NuDataFrame};
|
||||||
use nu_protocol::{
|
use nu_engine::command_prelude::*;
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
use polars::prelude::DataType;
|
use polars::prelude::DataType;
|
||||||
|
|
||||||
use crate::dataframe::values::Column;
|
|
||||||
|
|
||||||
use super::super::values::NuDataFrame;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TakeDF;
|
pub struct TakeDF;
|
||||||
|
|
||||||
@ -44,16 +37,19 @@ impl Command for TakeDF {
|
|||||||
let indices = ([0 2] | dfr into-df);
|
let indices = ([0 2] | dfr into-df);
|
||||||
$df | dfr take $indices"#,
|
$df | dfr take $indices"#,
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new(
|
vec![
|
||||||
"a".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(4), Value::test_int(4)],
|
"a".to_string(),
|
||||||
),
|
vec![Value::test_int(4), Value::test_int(4)],
|
||||||
Column::new(
|
),
|
||||||
"b".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(1), Value::test_int(3)],
|
"b".to_string(),
|
||||||
),
|
vec![Value::test_int(1), Value::test_int(3)],
|
||||||
])
|
),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
@ -64,10 +60,13 @@ impl Command for TakeDF {
|
|||||||
let indices = ([0 2] | dfr into-df);
|
let indices = ([0 2] | dfr into-df);
|
||||||
$series | dfr take $indices"#,
|
$series | dfr take $indices"#,
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![Column::new(
|
NuDataFrame::try_from_columns(
|
||||||
"0".to_string(),
|
vec![Column::new(
|
||||||
vec![Value::test_int(4), Value::test_int(5)],
|
"0".to_string(),
|
||||||
)])
|
vec![Value::test_int(4), Value::test_int(5)],
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
|
@ -1,14 +1,8 @@
|
|||||||
use std::{fs::File, path::PathBuf};
|
use crate::dataframe::values::NuDataFrame;
|
||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
use nu_engine::CallExt;
|
|
||||||
use nu_protocol::{
|
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
use polars::prelude::{IpcWriter, SerWriter};
|
use polars::prelude::{IpcWriter, SerWriter};
|
||||||
|
use std::{fs::File, path::PathBuf};
|
||||||
use super::super::values::NuDataFrame;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ToArrow;
|
pub struct ToArrow;
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
use std::{fs::File, path::PathBuf};
|
use crate::dataframe::values::NuDataFrame;
|
||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
use nu_engine::CallExt;
|
use polars_io::{
|
||||||
use nu_protocol::{
|
avro::{AvroCompression, AvroWriter},
|
||||||
ast::Call,
|
SerWriter,
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
|
||||||
};
|
};
|
||||||
use polars_io::avro::{AvroCompression, AvroWriter};
|
use std::{fs::File, path::PathBuf};
|
||||||
use polars_io::SerWriter;
|
|
||||||
|
|
||||||
use super::super::values::NuDataFrame;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ToAvro;
|
pub struct ToAvro;
|
||||||
|
@ -1,14 +1,8 @@
|
|||||||
use std::{fs::File, path::PathBuf};
|
use crate::dataframe::values::NuDataFrame;
|
||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
use nu_engine::CallExt;
|
|
||||||
use nu_protocol::{
|
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
use polars::prelude::{CsvWriter, SerWriter};
|
use polars::prelude::{CsvWriter, SerWriter};
|
||||||
|
use std::{fs::File, path::PathBuf};
|
||||||
use super::super::values::NuDataFrame;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ToCSV;
|
pub struct ToCSV;
|
||||||
@ -70,7 +64,7 @@ fn command(
|
|||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let file_name: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
let file_name: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
||||||
let delimiter: Option<Spanned<String>> = call.get_flag(engine_state, stack, "delimiter")?;
|
let delimiter: Option<Spanned<String>> = call.get_flag(engine_state, stack, "delimiter")?;
|
||||||
let no_header: bool = call.has_flag("no-header");
|
let no_header: bool = call.has_flag(engine_state, stack, "no-header")?;
|
||||||
|
|
||||||
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||||
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
use super::super::values::{Column, NuDataFrame};
|
use crate::dataframe::values::{Column, NuDataFrame, NuSchema};
|
||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
use nu_protocol::{
|
use polars::prelude::*;
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ToDataFrame;
|
pub struct ToDataFrame;
|
||||||
@ -20,6 +17,12 @@ impl Command for ToDataFrame {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build(self.name())
|
Signature::build(self.name())
|
||||||
|
.named(
|
||||||
|
"schema",
|
||||||
|
SyntaxShape::Record(vec![]),
|
||||||
|
r#"Polars Schema in format [{name: str}]. CSV, JSON, and JSONL files"#,
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
.input_output_type(Type::Any, Type::Custom("dataframe".into()))
|
.input_output_type(Type::Any, Type::Custom("dataframe".into()))
|
||||||
.category(Category::Custom("dataframe".into()))
|
.category(Category::Custom("dataframe".into()))
|
||||||
}
|
}
|
||||||
@ -30,16 +33,19 @@ impl Command for ToDataFrame {
|
|||||||
description: "Takes a dictionary and creates a dataframe",
|
description: "Takes a dictionary and creates a dataframe",
|
||||||
example: "[[a b];[1 2] [3 4]] | dfr into-df",
|
example: "[[a b];[1 2] [3 4]] | dfr into-df",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new(
|
vec![
|
||||||
"a".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(1), Value::test_int(3)],
|
"a".to_string(),
|
||||||
),
|
vec![Value::test_int(1), Value::test_int(3)],
|
||||||
Column::new(
|
),
|
||||||
"b".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(2), Value::test_int(4)],
|
"b".to_string(),
|
||||||
),
|
vec![Value::test_int(2), Value::test_int(4)],
|
||||||
])
|
),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
@ -48,24 +54,27 @@ impl Command for ToDataFrame {
|
|||||||
description: "Takes a list of tables and creates a dataframe",
|
description: "Takes a list of tables and creates a dataframe",
|
||||||
example: "[[1 2 a] [3 4 b] [5 6 c]] | dfr into-df",
|
example: "[[1 2 a] [3 4 b] [5 6 c]] | dfr into-df",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new(
|
vec![
|
||||||
"0".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(1), Value::test_int(3), Value::test_int(5)],
|
"0".to_string(),
|
||||||
),
|
vec![Value::test_int(1), Value::test_int(3), Value::test_int(5)],
|
||||||
Column::new(
|
),
|
||||||
"1".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(2), Value::test_int(4), Value::test_int(6)],
|
"1".to_string(),
|
||||||
),
|
vec![Value::test_int(2), Value::test_int(4), Value::test_int(6)],
|
||||||
Column::new(
|
),
|
||||||
"2".to_string(),
|
Column::new(
|
||||||
vec![
|
"2".to_string(),
|
||||||
Value::test_string("a"),
|
vec![
|
||||||
Value::test_string("b"),
|
Value::test_string("a"),
|
||||||
Value::test_string("c"),
|
Value::test_string("b"),
|
||||||
],
|
Value::test_string("c"),
|
||||||
),
|
],
|
||||||
])
|
),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
@ -74,14 +83,17 @@ impl Command for ToDataFrame {
|
|||||||
description: "Takes a list and creates a dataframe",
|
description: "Takes a list and creates a dataframe",
|
||||||
example: "[a b c] | dfr into-df",
|
example: "[a b c] | dfr into-df",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![Column::new(
|
NuDataFrame::try_from_columns(
|
||||||
"0".to_string(),
|
vec![Column::new(
|
||||||
vec![
|
"0".to_string(),
|
||||||
Value::test_string("a"),
|
vec![
|
||||||
Value::test_string("b"),
|
Value::test_string("a"),
|
||||||
Value::test_string("c"),
|
Value::test_string("b"),
|
||||||
],
|
Value::test_string("c"),
|
||||||
)])
|
],
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
@ -90,30 +102,78 @@ impl Command for ToDataFrame {
|
|||||||
description: "Takes a list of booleans and creates a dataframe",
|
description: "Takes a list of booleans and creates a dataframe",
|
||||||
example: "[true true false] | dfr into-df",
|
example: "[true true false] | dfr into-df",
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![Column::new(
|
NuDataFrame::try_from_columns(
|
||||||
"0".to_string(),
|
vec![Column::new(
|
||||||
vec![
|
"0".to_string(),
|
||||||
Value::test_bool(true),
|
vec![
|
||||||
Value::test_bool(true),
|
Value::test_bool(true),
|
||||||
Value::test_bool(false),
|
Value::test_bool(true),
|
||||||
],
|
Value::test_bool(false),
|
||||||
)])
|
],
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert to a dataframe and provide a schema",
|
||||||
|
example: "{a: 1, b: {a: [1 2 3]}, c: [a b c]}| dfr into-df -s {a: u8, b: {a: list<u64>}, c: list<str>}",
|
||||||
|
result: Some(
|
||||||
|
NuDataFrame::try_from_series(vec![
|
||||||
|
Series::new("a", &[1u8]),
|
||||||
|
{
|
||||||
|
let dtype = DataType::Struct(vec![Field::new("a", DataType::List(Box::new(DataType::UInt64)))]);
|
||||||
|
let vals = vec![AnyValue::StructOwned(
|
||||||
|
Box::new((vec![AnyValue::List(Series::new("a", &[1u64, 2, 3]))], vec![Field::new("a", DataType::String)]))); 1];
|
||||||
|
Series::from_any_values_and_dtype("b", &vals, &dtype, false)
|
||||||
|
.expect("Struct series should not fail")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
let dtype = DataType::List(Box::new(DataType::String));
|
||||||
|
let vals = vec![AnyValue::List(Series::new("c", &["a", "b", "c"]))];
|
||||||
|
Series::from_any_values_and_dtype("c", &vals, &dtype, false)
|
||||||
|
.expect("List series should not fail")
|
||||||
|
}
|
||||||
|
], Span::test_data())
|
||||||
|
.expect("simple df for test should not fail")
|
||||||
|
.into_value(Span::test_data()),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert to a dataframe and provide a schema that adds a new column",
|
||||||
|
example: r#"[[a b]; [1 "foo"] [2 "bar"]] | dfr into-df -s {a: u8, b:str, c:i64} | dfr fill-null 3"#,
|
||||||
|
result: Some(NuDataFrame::try_from_series(vec![
|
||||||
|
Series::new("a", [1u8, 2]),
|
||||||
|
Series::new("b", ["foo", "bar"]),
|
||||||
|
Series::new("c", [3i64, 3]),
|
||||||
|
], Span::test_data())
|
||||||
|
.expect("simple df for test should not fail")
|
||||||
|
.into_value(Span::test_data()),
|
||||||
|
),
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
_engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
_stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
NuDataFrame::try_from_iter(input.into_iter())
|
let maybe_schema = call
|
||||||
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
|
.get_flag(engine_state, stack, "schema")?
|
||||||
|
.map(|schema| NuSchema::try_from(&schema))
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
|
let df = NuDataFrame::try_from_iter(input.into_iter(), maybe_schema.clone())?;
|
||||||
|
|
||||||
|
Ok(PipelineData::Value(
|
||||||
|
NuDataFrame::into_value(df, call.head),
|
||||||
|
None,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,8 @@
|
|||||||
use std::{fs::File, io::BufWriter, path::PathBuf};
|
use crate::dataframe::values::NuDataFrame;
|
||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
use nu_engine::CallExt;
|
|
||||||
use nu_protocol::{
|
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
use polars::prelude::{JsonWriter, SerWriter};
|
use polars::prelude::{JsonWriter, SerWriter};
|
||||||
|
use std::{fs::File, io::BufWriter, path::PathBuf};
|
||||||
use super::super::values::NuDataFrame;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ToJsonLines;
|
pub struct ToJsonLines;
|
||||||
|
@ -1,13 +1,5 @@
|
|||||||
use nu_engine::CallExt;
|
use crate::dataframe::values::{NuDataFrame, NuExpression};
|
||||||
use nu_protocol::{
|
use nu_engine::command_prelude::*;
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
record, Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::dataframe::values::NuExpression;
|
|
||||||
|
|
||||||
use super::super::values::NuDataFrame;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ToNu;
|
pub struct ToNu;
|
||||||
@ -32,7 +24,7 @@ impl Command for ToNu {
|
|||||||
.switch("tail", "shows tail rows", Some('t'))
|
.switch("tail", "shows tail rows", Some('t'))
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::Custom("expression".into()), Type::Any),
|
(Type::Custom("expression".into()), Type::Any),
|
||||||
(Type::Custom("dataframe".into()), Type::Table(vec![])),
|
(Type::Custom("dataframe".into()), Type::table()),
|
||||||
])
|
])
|
||||||
//.input_output_type(Type::Any, Type::Any)
|
//.input_output_type(Type::Any, Type::Any)
|
||||||
.category(Category::Custom("dataframe".into()))
|
.category(Category::Custom("dataframe".into()))
|
||||||
@ -100,7 +92,7 @@ fn dataframe_command(
|
|||||||
input: Value,
|
input: Value,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let rows: Option<usize> = call.get_flag(engine_state, stack, "rows")?;
|
let rows: Option<usize> = call.get_flag(engine_state, stack, "rows")?;
|
||||||
let tail: bool = call.has_flag("tail");
|
let tail: bool = call.has_flag(engine_state, stack, "tail")?;
|
||||||
|
|
||||||
let df = NuDataFrame::try_from_value(input)?;
|
let df = NuDataFrame::try_from_value(input)?;
|
||||||
|
|
||||||
|
@ -1,14 +1,8 @@
|
|||||||
use std::{fs::File, path::PathBuf};
|
use crate::dataframe::values::NuDataFrame;
|
||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
use nu_engine::CallExt;
|
|
||||||
use nu_protocol::{
|
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
use polars::prelude::ParquetWriter;
|
use polars::prelude::ParquetWriter;
|
||||||
|
use std::{fs::File, path::PathBuf};
|
||||||
use super::super::values::NuDataFrame;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ToParquet;
|
pub struct ToParquet;
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
use super::super::values::{Column, NuDataFrame};
|
use crate::dataframe::values::{Column, NuDataFrame, NuExpression, NuLazyFrame};
|
||||||
use crate::dataframe::values::{NuExpression, NuLazyFrame};
|
use nu_engine::command_prelude::*;
|
||||||
use nu_engine::CallExt;
|
|
||||||
use nu_protocol::{
|
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WithColumn;
|
pub struct WithColumn;
|
||||||
@ -42,20 +36,23 @@ impl Command for WithColumn {
|
|||||||
| dfr into-df
|
| dfr into-df
|
||||||
| dfr with-column ([5 6] | dfr into-df) --name c"#,
|
| dfr with-column ([5 6] | dfr into-df) --name c"#,
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new(
|
vec![
|
||||||
"a".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(1), Value::test_int(3)],
|
"a".to_string(),
|
||||||
),
|
vec![Value::test_int(1), Value::test_int(3)],
|
||||||
Column::new(
|
),
|
||||||
"b".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(2), Value::test_int(4)],
|
"b".to_string(),
|
||||||
),
|
vec![Value::test_int(2), Value::test_int(4)],
|
||||||
Column::new(
|
),
|
||||||
"c".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(5), Value::test_int(6)],
|
"c".to_string(),
|
||||||
),
|
vec![Value::test_int(5), Value::test_int(6)],
|
||||||
])
|
),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
@ -70,24 +67,27 @@ impl Command for WithColumn {
|
|||||||
]
|
]
|
||||||
| dfr collect"#,
|
| dfr collect"#,
|
||||||
result: Some(
|
result: Some(
|
||||||
NuDataFrame::try_from_columns(vec![
|
NuDataFrame::try_from_columns(
|
||||||
Column::new(
|
vec![
|
||||||
"a".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(1), Value::test_int(3)],
|
"a".to_string(),
|
||||||
),
|
vec![Value::test_int(1), Value::test_int(3)],
|
||||||
Column::new(
|
),
|
||||||
"b".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(2), Value::test_int(4)],
|
"b".to_string(),
|
||||||
),
|
vec![Value::test_int(2), Value::test_int(4)],
|
||||||
Column::new(
|
),
|
||||||
"c".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(2), Value::test_int(6)],
|
"c".to_string(),
|
||||||
),
|
vec![Value::test_int(2), Value::test_int(6)],
|
||||||
Column::new(
|
),
|
||||||
"d".to_string(),
|
Column::new(
|
||||||
vec![Value::test_int(3), Value::test_int(9)],
|
"d".to_string(),
|
||||||
),
|
vec![Value::test_int(3), Value::test_int(9)],
|
||||||
])
|
),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
)
|
||||||
.expect("simple df for test should not fail")
|
.expect("simple df for test should not fail")
|
||||||
.into_value(Span::test_data()),
|
.into_value(Span::test_data()),
|
||||||
),
|
),
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user