mirror of
https://github.com/nushell/nushell.git
synced 2025-07-03 08:03:32 +02:00
Compare commits
478 Commits
0.101.0
...
release/0.
Author | SHA1 | Date | |
---|---|---|---|
98521100ed | |||
985211a924 | |||
a3388b00ae | |||
eadb8da9f7 | |||
cda15d91dd | |||
651a8716fb | |||
a1b7574306 | |||
09f12b9c4a | |||
9ae74e3941 | |||
d8bec8668f | |||
12ccaf5e33 | |||
5fecf59f54 | |||
a3aae2d26c | |||
d1d6518ece | |||
2d868323b6 | |||
0389815137 | |||
11cdb94699 | |||
0ca5c2f135 | |||
715b0d90a9 | |||
05c36d1bc7 | |||
208ebeefab | |||
b33f4b7f55 | |||
f41b1460aa | |||
220858d641 | |||
db261e3ed9 | |||
82eb1c5584 | |||
6be291b00a | |||
7add38fe32 | |||
78903724f5 | |||
cb57f0a539 | |||
717081bd2f | |||
e1ffaf2548 | |||
1db4be12d1 | |||
6193679dfc | |||
a9657e17ad | |||
03d455a688 | |||
bae04352ca | |||
a1497716f1 | |||
b5b63d2bf9 | |||
5c59611083 | |||
1503ee09ba | |||
24dba9dc53 | |||
a2dc3e3b33 | |||
95998bdd53 | |||
bd5de023a1 | |||
38e761493d | |||
7fcebf37ec | |||
0e9927ea4d | |||
d273ce89df | |||
2dc5c19b71 | |||
669b44ad7d | |||
eff063822a | |||
9a5c4d36be | |||
cd4560e97a | |||
24cc2f9d87 | |||
8f81812ef9 | |||
2229370b13 | |||
a33650a69e | |||
56d7e4bb89 | |||
e5f589ccdd | |||
8c4d3eaa7e | |||
89322f59f2 | |||
4e307480e4 | |||
d601abaee0 | |||
ceaa0f9375 | |||
d31b7024d8 | |||
9dd30d7756 | |||
eff9305eb3 | |||
885b87a842 | |||
017daeed18 | |||
c8c018452f | |||
1a0778d77e | |||
d75aa7ed1b | |||
39edd7e080 | |||
61dbcf3de6 | |||
f8ed4b45fd | |||
7b57f132bb | |||
dfca117551 | |||
29eb109b1e | |||
70d8163181 | |||
e4cef8a154 | |||
15146e68ad | |||
b0f9cda9b5 | |||
173162df2e | |||
c0b944edb6 | |||
26699d96eb | |||
08940ba4f8 | |||
ecb9799b6a | |||
a886e30e04 | |||
147009a161 | |||
12a1eefe73 | |||
0f8f3bcf9a | |||
639f4bd499 | |||
e82df7c1c9 | |||
41f4d0dcbc | |||
eb2a91ea7c | |||
b81d46574c | |||
1c6c85d35d | |||
67ea25afca | |||
f25525be6c | |||
a72f94f452 | |||
210c6f1c43 | |||
0cd90e2388 | |||
7ca2a6f8ac | |||
237a685605 | |||
2bf0397d80 | |||
5ec823996a | |||
67b6188b19 | |||
df74a0c961 | |||
af6c4bdc9c | |||
d7f26b177a | |||
470d130289 | |||
a23e96c945 | |||
9ba16dbdaf | |||
43f9ec295f | |||
f39e5b3f37 | |||
6c0b65b570 | |||
1dcaffb792 | |||
ca4222277e | |||
5c2bcd068b | |||
9aba96604b | |||
7be90c2644 | |||
7e9e93cf82 | |||
6d1f7cb3e3 | |||
334cf1862a | |||
49d86855ce | |||
5fe97b8d59 | |||
2bad1371f0 | |||
3030608de0 | |||
5d32cd2c40 | |||
07be33c119 | |||
eaf522b41f | |||
e76586ede4 | |||
1979b61a92 | |||
02fcc485fb | |||
55e05be0d8 | |||
e10ac2ede6 | |||
bf1f2d5ebd | |||
6aed1b42ae | |||
f33a26123c | |||
7c160725ed | |||
5832823dff | |||
3fe355c4a6 | |||
dd56c813f9 | |||
7a6cfa24fc | |||
2ea2a904e8 | |||
dfba62da00 | |||
b241e9edd5 | |||
946cef77f1 | |||
c99c8119fe | |||
2b4914608e | |||
8b80ceac32 | |||
e89bb2ee96 | |||
862d53bb6e | |||
820d0c0959 | |||
968eb45fb2 | |||
2c1d261cca | |||
69d1c8e948 | |||
2c7ab6e898 | |||
c986426478 | |||
09674a0026 | |||
9cca4ec18b | |||
90c86e6cbf | |||
4cb195a998 | |||
f7f09292d6 | |||
2c35e07c2d | |||
c949d2e893 | |||
83de8560ee | |||
00e5e6d719 | |||
1dd861b10f | |||
42aa2ff5ba | |||
74f62305b2 | |||
8f634f4140 | |||
33001d1992 | |||
f4b7333dc8 | |||
3dde851381 | |||
029f3843d3 | |||
0f6996b70d | |||
9160f36ea5 | |||
7f346dbf4c | |||
03888b9d81 | |||
966cebec34 | |||
44b7cfd696 | |||
a17ffdfe56 | |||
430b2746b8 | |||
1e566adcfc | |||
789781665d | |||
e926919582 | |||
8d5d01bbc9 | |||
58f7cfd099 | |||
b432866dc9 | |||
81e496673e | |||
2dab65f852 | |||
95dcb2fd6c | |||
d97b2e3c60 | |||
4fe7865ad0 | |||
d122bc3d89 | |||
7d17c2eb5e | |||
0e6e9abc12 | |||
f3982278e8 | |||
b1e591f84c | |||
122bcff356 | |||
087fe484f6 | |||
551fecd10d | |||
88bbe4abaa | |||
49f92e9090 | |||
4779d69de6 | |||
de7b000505 | |||
9eaa8908d2 | |||
fc72aa6abe | |||
8e1385417e | |||
95f89a093a | |||
e9b677a9e9 | |||
7555743ccc | |||
93612974e0 | |||
52a35827c7 | |||
c5a14bb8ff | |||
48bdcc71f4 | |||
78c93e5ae0 | |||
96af27fb4c | |||
12b8b4580c | |||
1616acd124 | |||
0cb4281fdb | |||
6f6ad23072 | |||
1ab09256d7 | |||
ee14811912 | |||
7939fb05ea | |||
53d30ee7ea | |||
058ce0ed2d | |||
9bb7f0c7dc | |||
9521b209d1 | |||
f51a79181a | |||
938fa6ee55 | |||
1d0d91d5e5 | |||
252155bdb9 | |||
be508cbd7f | |||
fcd1d59abd | |||
083c534948 | |||
bda3245725 | |||
1d44843970 | |||
d16946c6e8 | |||
2f6b4c5e9b | |||
4a967d19a9 | |||
3d58c3f70e | |||
c504c93a1d | |||
8b46ba8b6b | |||
f8ac9db15b | |||
7636963732 | |||
5d1e2b1df1 | |||
273226d666 | |||
2b8fb4fe00 | |||
2cb059146b | |||
fb7b0a8c11 | |||
d4aeadbb44 | |||
2a8f92b709 | |||
453e294883 | |||
e1c5ae3cd5 | |||
a8a0c78a32 | |||
879258039c | |||
4ac4f71a37 | |||
62e56d3581 | |||
2e1b6acc0e | |||
3eae657121 | |||
e74ce72f09 | |||
d577074da9 | |||
f7d5162582 | |||
0430167f1c | |||
1128fa137f | |||
81243c48f0 | |||
442df9e39c | |||
a58d9b0b3a | |||
2a3d5a9d42 | |||
a5d7d6dd46 | |||
18e3a5d40b | |||
553c951a60 | |||
781c4bd1d7 | |||
a2e335dcd7 | |||
c6fc6bd5a7 | |||
a7830ac1fd | |||
00713c9339 | |||
7d7dbd8b2c | |||
d4675d9138 | |||
6e88b3f8d6 | |||
720813339f | |||
5b4dd775d4 | |||
bfe398ca36 | |||
31e1f49cb6 | |||
26897b287c | |||
5a7707cb52 | |||
4b0b4ddce1 | |||
9fa2f43d06 | |||
2891867de9 | |||
55c7246830 | |||
17246db38b | |||
e60dac8957 | |||
d007b10fbf | |||
942030199d | |||
fb8ac4198b | |||
2ce5de58e6 | |||
2f18b9c856 | |||
bdc767bf23 | |||
3770a5eed1 | |||
0705fb9cd1 | |||
1a1a960836 | |||
5be818b5ee | |||
c7d3014849 | |||
164a089656 | |||
0b2d1327d2 | |||
5f6f18076c | |||
81de8ecd70 | |||
30ed63667b | |||
a56906ca6d | |||
0f0e1e2068 | |||
192ee59c75 | |||
803a348f41 | |||
1aa2ed1947 | |||
f04db2a7a3 | |||
30b3c42b37 | |||
4424481487 | |||
8b431e3a2e | |||
c58b432c21 | |||
34c09d8b35 | |||
13d5a15f75 | |||
339c5b7c83 | |||
5291f978c2 | |||
63fa6a6df7 | |||
4540f3829e | |||
1349187e17 | |||
b55ed69c92 | |||
948965d42f | |||
f4205132c7 | |||
5eae08ac76 | |||
3836da0cf1 | |||
6be42d94d9 | |||
945e9511ce | |||
cce12efe48 | |||
aa62de78e6 | |||
08b5d5cce5 | |||
03bb144150 | |||
5fa79e6e5f | |||
080b501ba8 | |||
66bc0542e0 | |||
ec1f7deb23 | |||
45f9d03025 | |||
4bc28f1752 | |||
a2705f9eb5 | |||
c0b4d19761 | |||
b53271b86a | |||
5ca4e903c8 | |||
0ad5f4389c | |||
7ea4895513 | |||
f46f8b286b | |||
f88ed6ecd5 | |||
a011791631 | |||
c783b07d58 | |||
e3e2554b3d | |||
926b0407c5 | |||
22a01d7e76 | |||
299453ecb7 | |||
fd684a204c | |||
cdbb3ee7b9 | |||
f0f6b3a3e5 | |||
93e121782c | |||
befeddad59 | |||
73c08fcb2b | |||
9a0ae7c4c0 | |||
28ca0e7116 | |||
84c720daf5 | |||
cdb082e92d | |||
0666b3784f | |||
379d89369c | |||
2bd345c367 | |||
0e418688d4 | |||
b97d89adb6 | |||
ee84435a0e | |||
500cd35ad2 | |||
3f5ebd75b6 | |||
75105033b2 | |||
8759936636 | |||
4dcaf2a201 | |||
089c5221cc | |||
0587308684 | |||
6eff420e17 | |||
d66f8cca40 | |||
06938659d2 | |||
46566296c0 | |||
4e1b06cb51 | |||
b99a8c9d80 | |||
b34547334a | |||
d9bfcb4c09 | |||
8ce14a7c86 | |||
301d1370c4 | |||
306e305b65 | |||
e117706518 | |||
737ea3940e | |||
e5337b50a9 | |||
23dc1b600a | |||
f05162811c | |||
0b71eb201c | |||
707ab1df6a | |||
c811d86dbd | |||
902e6d7a27 | |||
827e31191d | |||
b9b3101bd9 | |||
8e8a60a432 | |||
72d50cf8b7 | |||
3a1601de8e | |||
3f8dd1b705 | |||
f360489f1e | |||
79f19f2fc7 | |||
5cf6dea997 | |||
214714e0ab | |||
d894c8befe | |||
cc4d4acc6b | |||
dc52a6fec5 | |||
16e174be7e | |||
8e41a308cd | |||
787f292ca7 | |||
dad956b2ee | |||
1f477c8eb1 | |||
6260fa9f07 | |||
88f44701a9 | |||
9ed944312f | |||
6eb14522b6 | |||
ac12b02437 | |||
9ed2ca792f | |||
ebabca575c | |||
b60f91f722 | |||
2b4c54d383 | |||
ed1381adc4 | |||
1b7fabd1fd | |||
87a562e24b | |||
b5ff46db6a | |||
8b086d3613 | |||
d702c4605a | |||
6325bc5e54 | |||
25d90fa603 | |||
86f7f53f85 | |||
461eb43d9d | |||
df3892f323 | |||
0d3f76ddef | |||
816b9a6953 | |||
80788636ee | |||
c46ca36bcd | |||
62bd6fe08b | |||
f69b22f00b | |||
c6523eb8d9 | |||
76afa74320 | |||
a0d4ae18ee | |||
4884894ddb | |||
e7877db078 | |||
1181349c22 | |||
378395c22c | |||
2bcf2389aa | |||
a65e5ab01d | |||
4ff4e3f93d | |||
d36514a323 | |||
4401924128 | |||
5314b31b12 | |||
b2b5b89a92 | |||
76bbd41e43 | |||
5f3c8d45d8 | |||
38694a9850 | |||
0a0475ebad | |||
38ffcaad7b | |||
1b01598840 | |||
45ff964cbd | |||
81baf53814 | |||
6ebc0fc3ff | |||
b1da50774a | |||
469e23cae4 | |||
23ba613b00 | |||
f2dcae570c | |||
f1ce0c98fd | |||
35d2750757 | |||
4b1f4e63c3 | |||
c29bcc94e7 | |||
d3cbcf401f |
40
.github/labeler.yml
vendored
Normal file
40
.github/labeler.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# A bot for automatically labelling pull requests
|
||||||
|
# See https://github.com/actions/labeler
|
||||||
|
|
||||||
|
dataframe:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- crates/nu_plugin_polars/**
|
||||||
|
|
||||||
|
std-library:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- crates/nu-std/**
|
||||||
|
|
||||||
|
ci:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- .github/workflows/**
|
||||||
|
|
||||||
|
|
||||||
|
LSP:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- crates/nu-lsp/**
|
||||||
|
|
||||||
|
parser:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- crates/nu-parser/**
|
||||||
|
|
||||||
|
pr:plugins:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
# plugins API
|
||||||
|
- crates/nu-plugin/**
|
||||||
|
- crates/nu-plugin-core/**
|
||||||
|
- crates/nu-plugin-engine/**
|
||||||
|
- crates/nu-plugin-protocol/**
|
||||||
|
- crates/nu-plugin-test-support/**
|
||||||
|
# specific plugins (like polars)
|
||||||
|
- crates/nu_plugin_*/**
|
52
.github/workflows/beta-test.yml
vendored
Normal file
52
.github/workflows/beta-test.yml
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
name: Test on Beta Toolchain
|
||||||
|
# This workflow is made to run our tests on the beta toolchain to validate that
|
||||||
|
# the beta toolchain works.
|
||||||
|
# We do not intend to test here that we are working correctly but rather that
|
||||||
|
# the beta toolchain works correctly.
|
||||||
|
# The ci.yml handles our actual testing with our guarantees.
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# If this workflow fails, GitHub notifications will go to the last person
|
||||||
|
# who edited this line.
|
||||||
|
# See: https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/monitoring-workflows/notifications-for-workflow-runs
|
||||||
|
- cron: '0 0 * * *' # Runs daily at midnight UTC
|
||||||
|
|
||||||
|
env:
|
||||||
|
NUSHELL_CARGO_PROFILE: ci
|
||||||
|
NU_LOG_LEVEL: DEBUG
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-test:
|
||||||
|
# this job is more for testing the beta toolchain and not our tests, so if
|
||||||
|
# this fails but the tests of the regular ci pass, then this is fine
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
platform: [windows-latest, macos-latest, ubuntu-22.04]
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- run: rustup update beta
|
||||||
|
|
||||||
|
- name: Tests
|
||||||
|
run: cargo +beta test --workspace --profile ci --exclude nu_plugin_*
|
||||||
|
- name: Check for clean repo
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [ -n "$(git status --porcelain)" ]; then
|
||||||
|
echo "there are changes";
|
||||||
|
git status --porcelain
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "no changes in working directory";
|
||||||
|
fi
|
81
.github/workflows/ci.yml
vendored
81
.github/workflows/ci.yml
vendored
@ -3,6 +3,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- 'patch-release-*'
|
||||||
|
|
||||||
name: continuous-integration
|
name: continuous-integration
|
||||||
|
|
||||||
@ -21,14 +22,14 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
# Pinning to Ubuntu 20.04 because building on newer Ubuntu versions causes linux-gnu
|
# Pinning to Ubuntu 22.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 22.04 is closer to EOL (June 2027)
|
||||||
#
|
#
|
||||||
# Using macOS 13 runner because 14 is based on the M1 and has half as much RAM (7 GB,
|
# Using macOS 13 runner because 14 is based on the M1 and has half as much RAM (7 GB,
|
||||||
# instead of 14 GB) which is too little for us right now. Revisit when `dfr` commands are
|
# instead of 14 GB) which is too little for us right now. Revisit when `dfr` commands are
|
||||||
# removed and we're only building the `polars` plugin instead
|
# removed and we're only building the `polars` plugin instead
|
||||||
platform: [windows-latest, macos-13, ubuntu-20.04]
|
platform: [windows-latest, macos-13, ubuntu-22.04]
|
||||||
|
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
@ -36,7 +37,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4.1.7
|
- uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||||
|
|
||||||
- name: cargo fmt
|
- name: cargo fmt
|
||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
@ -56,7 +57,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
platform: [windows-latest, macos-latest, ubuntu-20.04]
|
platform: [windows-latest, macos-latest, ubuntu-22.04]
|
||||||
|
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
@ -64,7 +65,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4.1.7
|
- uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
run: cargo test --workspace --profile ci --exclude nu_plugin_*
|
run: cargo test --workspace --profile ci --exclude nu_plugin_*
|
||||||
@ -83,7 +84,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
platform: [ubuntu-20.04, macos-latest, windows-latest]
|
platform: [ubuntu-22.04, macos-latest, windows-latest]
|
||||||
py:
|
py:
|
||||||
- py
|
- py
|
||||||
|
|
||||||
@ -93,10 +94,10 @@ jobs:
|
|||||||
- uses: actions/checkout@v4.1.7
|
- uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||||
|
|
||||||
- name: Install Nushell
|
- name: Install Nushell
|
||||||
run: cargo install --path . --locked --no-default-features
|
run: cargo install --path . --locked --force
|
||||||
|
|
||||||
- name: Standard library tests
|
- name: Standard library tests
|
||||||
run: nu -c 'use crates/nu-std/testing.nu; testing run-tests --path crates/nu-std'
|
run: nu -c 'use crates/nu-std/testing.nu; testing run-tests --path crates/nu-std'
|
||||||
@ -136,7 +137,7 @@ jobs:
|
|||||||
# instead of 14 GB) which is too little for us right now.
|
# instead of 14 GB) which is too little for us right now.
|
||||||
#
|
#
|
||||||
# Failure occurring with clippy for rust 1.77.2
|
# Failure occurring with clippy for rust 1.77.2
|
||||||
platform: [windows-latest, macos-13, ubuntu-20.04]
|
platform: [windows-latest, macos-13, ubuntu-22.04]
|
||||||
|
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
@ -144,7 +145,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4.1.7
|
- uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||||
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS
|
run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS
|
||||||
@ -163,33 +164,49 @@ jobs:
|
|||||||
echo "no changes in working directory";
|
echo "no changes in working directory";
|
||||||
fi
|
fi
|
||||||
|
|
||||||
build-wasm:
|
wasm:
|
||||||
|
env:
|
||||||
|
WASM_OPTIONS: --no-default-features --target wasm32-unknown-unknown
|
||||||
|
CLIPPY_CONF_DIR: ${{ github.workspace }}/clippy/wasm/
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
job:
|
||||||
|
- name: Build WASM
|
||||||
|
command: cargo build
|
||||||
|
args:
|
||||||
|
- name: Clippy WASM
|
||||||
|
command: cargo clippy
|
||||||
|
args: -- $CLIPPY_OPTIONS
|
||||||
|
|
||||||
|
name: ${{ matrix.job.name }}
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.7
|
- uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||||
|
|
||||||
- name: Add wasm32-unknown-unknown target
|
- name: Add wasm32-unknown-unknown target
|
||||||
run: rustup target add wasm32-unknown-unknown
|
run: rustup target add wasm32-unknown-unknown
|
||||||
|
|
||||||
- run: cargo build -p nu-cmd-base --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-cmd-base $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-cmd-extra --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-cmd-extra $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-cmd-lang --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-cmd-lang $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-color-config --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-color-config $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-command --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-command $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-derive-value --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-derive-value $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-engine --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-engine $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-glob --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-glob $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-json --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-json $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-parser --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-parser $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-path --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-path $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-pretty-hex --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-pretty-hex $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-protocol --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-protocol $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-std --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-std $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-system --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-system $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-table --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-table $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-term-grid --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-term-grid $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nu-utils --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nu-utils $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
- run: cargo build -p nuon --no-default-features --target wasm32-unknown-unknown
|
- run: ${{ matrix.job.command }} -p nuon $WASM_OPTIONS ${{ matrix.job.args }}
|
||||||
|
19
.github/workflows/labels.yml
vendored
Normal file
19
.github/workflows/labels.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Automatically labels PRs based on the configuration file
|
||||||
|
# you are probably looking for 👉 `.github/labeler.yml`
|
||||||
|
name: Label PRs
|
||||||
|
|
||||||
|
on:
|
||||||
|
- pull_request_target
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
triage:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository_owner == 'nushell'
|
||||||
|
steps:
|
||||||
|
- uses: actions/labeler@v5
|
||||||
|
with:
|
||||||
|
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
sync-labels: true
|
2
.github/workflows/milestone.yml
vendored
2
.github/workflows/milestone.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
|
|
||||||
# Bind milestone to closed issue that has a merged PR fix
|
# Bind milestone to closed issue that has a merged PR fix
|
||||||
- name: Set Milestone for Issue
|
- name: Set Milestone for Issue
|
||||||
uses: hustcer/milestone-action@main
|
uses: hustcer/milestone-action@v2
|
||||||
if: github.event.issue.state == 'closed'
|
if: github.event.issue.state == 'closed'
|
||||||
with:
|
with:
|
||||||
action: bind-issue
|
action: bind-issue
|
||||||
|
155
.github/workflows/nightly-build.yml
vendored
155
.github/workflows/nightly-build.yml
vendored
@ -4,6 +4,7 @@
|
|||||||
# 2. https://github.com/JasonEtco/create-an-issue
|
# 2. https://github.com/JasonEtco/create-an-issue
|
||||||
# 3. https://docs.github.com/en/actions/learn-github-actions/variables
|
# 3. https://docs.github.com/en/actions/learn-github-actions/variables
|
||||||
# 4. https://github.com/actions/github-script
|
# 4. https://github.com/actions/github-script
|
||||||
|
# 5. https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds
|
||||||
#
|
#
|
||||||
name: Nightly Build
|
name: Nightly Build
|
||||||
|
|
||||||
@ -14,6 +15,7 @@ on:
|
|||||||
# This schedule will run only from the default branch
|
# This schedule will run only from the default branch
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '15 0 * * *' # run at 00:15 AM UTC
|
- cron: '15 0 * * *' # run at 00:15 AM UTC
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@ -25,6 +27,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# This job is required by the release job, so we should make it run both from Nushell repo and nightly repo
|
# This job is required by the release job, so we should make it run both from Nushell repo and nightly repo
|
||||||
# if: github.repository == 'nushell/nightly'
|
# if: github.repository == 'nushell/nightly'
|
||||||
|
# Map a step output to a job output
|
||||||
|
outputs:
|
||||||
|
skip: ${{ steps.vars.outputs.skip }}
|
||||||
|
build_date: ${{ steps.vars.outputs.build_date }}
|
||||||
|
nightly_tag: ${{ steps.vars.outputs.nightly_tag }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@ -39,7 +46,7 @@ jobs:
|
|||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3
|
||||||
if: github.repository == 'nushell/nightly'
|
if: github.repository == 'nushell/nightly'
|
||||||
with:
|
with:
|
||||||
version: 0.98.0
|
version: 0.103.0
|
||||||
|
|
||||||
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
|
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
|
||||||
- name: Prepare for Nightly Release
|
- name: Prepare for Nightly Release
|
||||||
@ -57,16 +64,53 @@ jobs:
|
|||||||
# All the changes will be overwritten by the upstream main branch
|
# All the changes will be overwritten by the upstream main branch
|
||||||
git reset --hard src/main
|
git reset --hard src/main
|
||||||
git push origin main -f
|
git push origin main -f
|
||||||
let sha_short = (git rev-parse --short origin/main | str trim | str substring 0..7)
|
|
||||||
let tag_name = $'nightly-($sha_short)'
|
- name: Create Tag and Output Tag Name
|
||||||
if (git ls-remote --tags origin $tag_name | is-empty) {
|
if: github.repository == 'nushell/nightly'
|
||||||
git tag -a $tag_name -m $'Nightly build from ($sha_short)'
|
id: vars
|
||||||
|
shell: nu {0}
|
||||||
|
run: |
|
||||||
|
let date = date now | format date %m%d
|
||||||
|
let version = open Cargo.toml | get package.version
|
||||||
|
let sha_short = (git rev-parse --short origin/main | str trim | str substring 0..6)
|
||||||
|
let latest_meta = http get https://api.github.com/repos/nushell/nightly/releases
|
||||||
|
| sort-by -r created_at
|
||||||
|
| where tag_name =~ nightly
|
||||||
|
| get tag_name?.0? | default ''
|
||||||
|
| parse '{version}-nightly.{build}+{hash}'
|
||||||
|
if ($latest_meta.0?.hash? | default '') == $sha_short {
|
||||||
|
print $'(ansi g)Latest nightly build is up-to-date, skip rebuilding.(ansi reset)'
|
||||||
|
$'skip=true(char nl)' o>> $env.GITHUB_OUTPUT
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
let prev_ver = $latest_meta.0?.version? | default '0.0.0'
|
||||||
|
let build = if ($latest_meta | is-empty) or ($version != $prev_ver) { 1 } else {
|
||||||
|
($latest_meta | get build?.0? | default 0 | into int) + 1
|
||||||
|
}
|
||||||
|
let nightly_tag = $'($version)-nightly.($build)+($sha_short)'
|
||||||
|
$'build_date=($date)(char nl)' o>> $env.GITHUB_OUTPUT
|
||||||
|
$'nightly_tag=($nightly_tag)(char nl)' o>> $env.GITHUB_OUTPUT
|
||||||
|
if (git ls-remote --tags origin $nightly_tag | is-empty) {
|
||||||
|
ls **/Cargo.toml | each {|file|
|
||||||
|
open --raw $file.name
|
||||||
|
| str replace --all $'version = "($version)"' $'version = "($version)-nightly.($build)"'
|
||||||
|
| save --force $file.name
|
||||||
|
}
|
||||||
|
# Disable the following two workflows for the automatic committed changes
|
||||||
|
rm .github/workflows/ci.yml
|
||||||
|
rm .github/workflows/audit.yml
|
||||||
|
|
||||||
|
git add .
|
||||||
|
git commit -m $'Update version to ($version)-nightly.($build)'
|
||||||
|
git tag -a $nightly_tag -m $'Nightly build from ($sha_short)'
|
||||||
git push origin --tags
|
git push origin --tags
|
||||||
|
git push origin main -f
|
||||||
}
|
}
|
||||||
|
|
||||||
standard:
|
release:
|
||||||
name: Nu
|
name: Nu
|
||||||
needs: prepare
|
needs: prepare
|
||||||
|
if: needs.prepare.outputs.skip != 'true'
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@ -83,24 +127,15 @@ jobs:
|
|||||||
- armv7-unknown-linux-musleabihf
|
- armv7-unknown-linux-musleabihf
|
||||||
- riscv64gc-unknown-linux-gnu
|
- riscv64gc-unknown-linux-gnu
|
||||||
- loongarch64-unknown-linux-gnu
|
- loongarch64-unknown-linux-gnu
|
||||||
extra: ['bin']
|
|
||||||
include:
|
include:
|
||||||
- target: aarch64-apple-darwin
|
- target: aarch64-apple-darwin
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
- target: x86_64-apple-darwin
|
- target: x86_64-apple-darwin
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
- target: x86_64-pc-windows-msvc
|
- target: x86_64-pc-windows-msvc
|
||||||
extra: 'bin'
|
|
||||||
os: windows-latest
|
|
||||||
- target: x86_64-pc-windows-msvc
|
|
||||||
extra: msi
|
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
- target: aarch64-pc-windows-msvc
|
- target: aarch64-pc-windows-msvc
|
||||||
extra: 'bin'
|
os: windows-11-arm
|
||||||
os: windows-latest
|
|
||||||
- target: aarch64-pc-windows-msvc
|
|
||||||
extra: msi
|
|
||||||
os: windows-latest
|
|
||||||
- target: x86_64-unknown-linux-gnu
|
- target: x86_64-unknown-linux-gnu
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
- target: x86_64-unknown-linux-musl
|
- target: x86_64-unknown-linux-musl
|
||||||
@ -114,45 +149,69 @@ jobs:
|
|||||||
- target: armv7-unknown-linux-musleabihf
|
- target: armv7-unknown-linux-musleabihf
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
- target: riscv64gc-unknown-linux-gnu
|
- target: riscv64gc-unknown-linux-gnu
|
||||||
os: ubuntu-latest
|
os: ubuntu-22.04
|
||||||
- target: loongarch64-unknown-linux-gnu
|
- target: loongarch64-unknown-linux-gnu
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
|
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Install Wix Toolset 6 for Windows
|
||||||
|
shell: pwsh
|
||||||
|
if: ${{ startsWith(matrix.os, 'windows') }}
|
||||||
|
run: |
|
||||||
|
dotnet tool install --global wix --version 6.0.0
|
||||||
|
dotnet workload install wix
|
||||||
|
$wixPath = "$env:USERPROFILE\.dotnet\tools"
|
||||||
|
echo "$wixPath" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||||
|
$env:PATH = "$wixPath;$env:PATH"
|
||||||
|
wix --version
|
||||||
|
|
||||||
- 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 and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
# 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
|
uses: hustcer/setup-nu@v3
|
||||||
|
if: ${{ matrix.os != 'windows-11-arm' }}
|
||||||
with:
|
with:
|
||||||
version: 0.98.0
|
version: 0.103.0
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
|
if: ${{ matrix.os != 'windows-11-arm' }}
|
||||||
run: nu .github/workflows/release-pkg.nu
|
run: nu .github/workflows/release-pkg.nu
|
||||||
env:
|
env:
|
||||||
OS: ${{ matrix.os }}
|
OS: ${{ matrix.os }}
|
||||||
REF: ${{ github.ref }}
|
REF: ${{ github.ref }}
|
||||||
TARGET: ${{ matrix.target }}
|
TARGET: ${{ matrix.target }}
|
||||||
_EXTRA_: ${{ matrix.extra }}
|
|
||||||
|
- name: Build Nu for Windows ARM64
|
||||||
|
id: nu0
|
||||||
|
shell: pwsh
|
||||||
|
if: ${{ matrix.os == 'windows-11-arm' }}
|
||||||
|
run: |
|
||||||
|
$env:OS = 'windows'
|
||||||
|
$env:REF = '${{ github.ref }}'
|
||||||
|
$env:TARGET = '${{ matrix.target }}'
|
||||||
|
cargo build --release --all --target aarch64-pc-windows-msvc
|
||||||
|
cp ./target/${{ matrix.target }}/release/nu.exe .
|
||||||
|
./nu.exe -c 'version'
|
||||||
|
./nu.exe ${{github.workspace}}/.github/workflows/release-pkg.nu
|
||||||
|
|
||||||
- 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.2
|
uses: JasonEtco/create-an-issue@v2
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
@ -160,13 +219,6 @@ jobs:
|
|||||||
search_existing: open
|
search_existing: open
|
||||||
filename: .github/AUTO_ISSUE_TEMPLATE/nightly-build-fail.md
|
filename: .github/AUTO_ISSUE_TEMPLATE/nightly-build-fail.md
|
||||||
|
|
||||||
- name: Set Outputs of Short SHA
|
|
||||||
id: vars
|
|
||||||
run: |
|
|
||||||
echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
|
||||||
sha_short=$(git rev-parse --short HEAD)
|
|
||||||
echo "sha_short=${sha_short:0:7}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# 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
|
||||||
@ -174,9 +226,39 @@ jobs:
|
|||||||
if: ${{ startsWith(github.repository, 'nushell/nightly') }}
|
if: ${{ startsWith(github.repository, 'nushell/nightly') }}
|
||||||
with:
|
with:
|
||||||
prerelease: true
|
prerelease: true
|
||||||
files: ${{ steps.nu.outputs.archive }}
|
files: |
|
||||||
tag_name: nightly-${{ steps.vars.outputs.sha_short }}
|
${{ steps.nu.outputs.msi }}
|
||||||
name: Nu-nightly-${{ steps.vars.outputs.date }}-${{ steps.vars.outputs.sha_short }}
|
${{ steps.nu0.outputs.msi }}
|
||||||
|
${{ steps.nu.outputs.archive }}
|
||||||
|
${{ steps.nu0.outputs.archive }}
|
||||||
|
tag_name: ${{ needs.prepare.outputs.nightly_tag }}
|
||||||
|
name: ${{ needs.prepare.outputs.build_date }}-${{ needs.prepare.outputs.nightly_tag }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
sha256sum:
|
||||||
|
needs: [prepare, release]
|
||||||
|
name: Create Sha256sum
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository == 'nushell/nightly'
|
||||||
|
steps:
|
||||||
|
- name: Download Release Archives
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: >-
|
||||||
|
gh release download ${{ needs.prepare.outputs.nightly_tag }}
|
||||||
|
--repo ${{ github.repository }}
|
||||||
|
--pattern '*'
|
||||||
|
--dir release
|
||||||
|
- name: Create Checksums
|
||||||
|
run: cd release && shasum -a 256 * > ../SHA256SUMS
|
||||||
|
- name: Publish Checksums
|
||||||
|
uses: softprops/action-gh-release@v2.0.9
|
||||||
|
with:
|
||||||
|
draft: false
|
||||||
|
prerelease: true
|
||||||
|
files: SHA256SUMS
|
||||||
|
tag_name: ${{ needs.prepare.outputs.nightly_tag }}
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@ -184,12 +266,9 @@ jobs:
|
|||||||
name: Cleanup
|
name: Cleanup
|
||||||
# Should only run in nushell/nightly repo
|
# Should only run in nushell/nightly repo
|
||||||
if: github.repository == 'nushell/nightly'
|
if: github.repository == 'nushell/nightly'
|
||||||
|
needs: [release, sha256sum]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
# Sleep for 30 minutes, waiting for the release to be published
|
|
||||||
- name: Waiting for Release
|
|
||||||
run: sleep 1800
|
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
@ -197,14 +276,14 @@ jobs:
|
|||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3
|
||||||
with:
|
with:
|
||||||
version: 0.98.0
|
version: 0.103.0
|
||||||
|
|
||||||
# Keep the last a few releases
|
# Keep the last a few releases
|
||||||
- name: Delete Older Releases
|
- name: Delete Older Releases
|
||||||
shell: nu {0}
|
shell: nu {0}
|
||||||
run: |
|
run: |
|
||||||
let KEEP_COUNT = 10
|
let KEEP_COUNT = 10
|
||||||
let deprecated = (http get https://api.github.com/repos/nushell/nightly/releases | sort-by -r created_at | select tag_name id | range $KEEP_COUNT..)
|
let deprecated = (http get https://api.github.com/repos/nushell/nightly/releases | sort-by -r created_at | select tag_name id | slice $KEEP_COUNT..)
|
||||||
for release in $deprecated {
|
for release in $deprecated {
|
||||||
print $'Deleting tag ($release.tag_name)'
|
print $'Deleting tag ($release.tag_name)'
|
||||||
git push origin --delete $release.tag_name
|
git push origin --delete $release.tag_name
|
||||||
|
77
.github/workflows/release-pkg.nu
vendored
77
.github/workflows/release-pkg.nu
vendored
@ -117,14 +117,14 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
|
|||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
# Build for Windows without static-link-openssl feature
|
# Build for Windows without static-link-openssl feature
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
if $os in ['windows-latest'] {
|
if $os =~ 'windows' {
|
||||||
cargo-build-nu
|
cargo-build-nu
|
||||||
}
|
}
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
# Prepare for the release archive
|
# Prepare for the release archive
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
let suffix = if $os == 'windows-latest' { '.exe' }
|
let suffix = if $os =~ 'windows' { '.exe' }
|
||||||
# nu, nu_plugin_* were all included
|
# nu, nu_plugin_* were all included
|
||||||
let executable = $'target/($target)/release/($bin)*($suffix)'
|
let executable = $'target/($target)/release/($bin)*($suffix)'
|
||||||
print $'Current executable file: ($executable)'
|
print $'Current executable file: ($executable)'
|
||||||
@ -148,10 +148,10 @@ For more information, refer to https://www.nushell.sh/book/plugins.html
|
|||||||
[LICENSE ...(glob $executable)] | each {|it| cp -rv $it $dist } | flatten
|
[LICENSE ...(glob $executable)] | each {|it| cp -rv $it $dist } | flatten
|
||||||
|
|
||||||
print $'(char nl)Check binary release version detail:'; hr-line
|
print $'(char nl)Check binary release version detail:'; hr-line
|
||||||
let ver = if $os == 'windows-latest' {
|
let ver = if $os =~ 'windows' {
|
||||||
(do -i { .\output\nu.exe -c 'version' }) | str join
|
(do -i { .\output\nu.exe -c 'version' }) | default '' | str join
|
||||||
} else {
|
} else {
|
||||||
(do -i { ./output/nu -c 'version' }) | str join
|
(do -i { ./output/nu -c 'version' }) | default '' | str join
|
||||||
}
|
}
|
||||||
if ($ver | str trim | is-empty) {
|
if ($ver | str trim | is-empty) {
|
||||||
print $'(ansi r)Incompatible Nu binary: The binary cross compiled is not runnable on current arch...(ansi reset)'
|
print $'(ansi r)Incompatible Nu binary: The binary cross compiled is not runnable on current arch...(ansi reset)'
|
||||||
@ -175,37 +175,13 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
|
|||||||
tar -czf $archive $dest
|
tar -czf $archive $dest
|
||||||
print $'archive: ---> ($archive)'; ls $archive
|
print $'archive: ---> ($archive)'; ls $archive
|
||||||
# REF: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
|
# REF: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
|
||||||
echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT
|
echo $"archive=($archive)(char nl)" o>> $env.GITHUB_OUTPUT
|
||||||
|
|
||||||
} else if $os == 'windows-latest' {
|
} else if $os =~ 'windows' {
|
||||||
|
|
||||||
let releaseStem = $'($bin)-($version)-($target)'
|
let releaseStem = $'($bin)-($version)-($target)'
|
||||||
|
let arch = if $nu.os-info.arch =~ 'x86_64' { 'x64' } else { 'arm64' }
|
||||||
print $'(char nl)Download less related stuffs...'; hr-line
|
fetch-less $arch
|
||||||
# todo: less-v661 is out but is released as a zip file. maybe we should switch to that and extract it?
|
|
||||||
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v608/less.exe -o less.exe
|
|
||||||
# the below was renamed because it was failing to download for darren. it should work but it wasn't
|
|
||||||
# todo: maybe we should get rid of this aria2c dependency and just use http get?
|
|
||||||
#aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt
|
|
||||||
aria2c https://github.com/jftuga/less-Windows/blob/master/LICENSE -o LICENSE-for-less.txt
|
|
||||||
|
|
||||||
# Create Windows msi release package
|
|
||||||
if (get-env _EXTRA_) == 'msi' {
|
|
||||||
|
|
||||||
let wixRelease = $'($src)/target/wix/($releaseStem).msi'
|
|
||||||
print $'(char nl)Start creating Windows msi package with the following contents...'
|
|
||||||
cd $src; hr-line
|
|
||||||
# Wix need the binaries be stored in target/release/
|
|
||||||
cp -r ($'($dist)/*' | into glob) target/release/
|
|
||||||
ls target/release/* | print
|
|
||||||
cargo install cargo-wix --version 0.3.8
|
|
||||||
cargo wix --no-build --nocapture --package nu --output $wixRelease
|
|
||||||
# Workaround for https://github.com/softprops/action-gh-release/issues/280
|
|
||||||
let archive = ($wixRelease | str replace --all '\' '/')
|
|
||||||
print $'archive: ---> ($archive)';
|
|
||||||
echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
print $'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls | print
|
print $'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls | print
|
||||||
let archive = $'($dist)/($releaseStem).zip'
|
let archive = $'($dist)/($releaseStem).zip'
|
||||||
@ -215,13 +191,44 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
|
|||||||
# Workaround for https://github.com/softprops/action-gh-release/issues/280
|
# Workaround for https://github.com/softprops/action-gh-release/issues/280
|
||||||
let archive = ($pkg | get 0 | str replace --all '\' '/')
|
let archive = ($pkg | get 0 | str replace --all '\' '/')
|
||||||
print $'archive: ---> ($archive)'
|
print $'archive: ---> ($archive)'
|
||||||
echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT
|
echo $"archive=($archive)(char nl)" o>> $env.GITHUB_OUTPUT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Create extra Windows msi release package if dotnet and wix are available
|
||||||
|
let installed = [dotnet wix] | all { (which $in | length) > 0 }
|
||||||
|
if $installed and (wix --version | split row . | first | into int) >= 6 {
|
||||||
|
|
||||||
|
print $'(char nl)Start creating Windows msi package with the following contents...'
|
||||||
|
cd $src; cd wix; hr-line; mkdir nu
|
||||||
|
# Wix need the binaries be stored in nu folder
|
||||||
|
cp -r ($'($dist)/*' | into glob) nu/
|
||||||
|
cp $'($dist)/README.txt' .
|
||||||
|
ls -f nu/* | print
|
||||||
|
./nu/nu.exe -c $'NU_RELEASE_VERSION=($version) dotnet build -c Release -p:Platform=($arch)'
|
||||||
|
glob **/*.msi | print
|
||||||
|
# Workaround for https://github.com/softprops/action-gh-release/issues/280
|
||||||
|
let wixRelease = (glob **/*.msi | where $it =~ bin | get 0 | str replace --all '\' '/')
|
||||||
|
let msi = $'($wixRelease | path dirname)/nu-($version)-($target).msi'
|
||||||
|
mv $wixRelease $msi
|
||||||
|
print $'MSI archive: ---> ($msi)';
|
||||||
|
echo $"msi=($msi)(char nl)" o>> $env.GITHUB_OUTPUT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def fetch-less [
|
||||||
|
arch: string = 'x64' # The architecture to fetch
|
||||||
|
] {
|
||||||
|
let less_zip = $'less-($arch).zip'
|
||||||
|
print $'Fetching less archive: (ansi g)($less_zip)(ansi reset)'
|
||||||
|
let url = $'https://github.com/jftuga/less-Windows/releases/download/less-v668/($less_zip)'
|
||||||
|
http get https://github.com/jftuga/less-Windows/blob/master/LICENSE | save -rf LICENSE-for-less.txt
|
||||||
|
http get $url | save -rf $less_zip
|
||||||
|
unzip $less_zip
|
||||||
|
rm $less_zip lesskey.exe
|
||||||
|
}
|
||||||
|
|
||||||
def 'cargo-build-nu' [] {
|
def 'cargo-build-nu' [] {
|
||||||
if $os == 'windows-latest' {
|
if $os =~ 'windows' {
|
||||||
cargo build --release --all --target $target
|
cargo build --release --all --target $target
|
||||||
} else {
|
} else {
|
||||||
cargo build --release --all --target $target --features=static-link-openssl
|
cargo build --release --all --target $target --features=static-link-openssl
|
||||||
|
54
.github/workflows/release.yml
vendored
54
.github/workflows/release.yml
vendored
@ -7,7 +7,9 @@ name: Create Release Draft
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
tags: ["[0-9]+.[0-9]+.[0-9]+*"]
|
tags:
|
||||||
|
- '[0-9]+.[0-9]+.[0-9]+*'
|
||||||
|
- '!*nightly*' # Don't trigger release for nightly tags
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@ -33,24 +35,15 @@ jobs:
|
|||||||
- armv7-unknown-linux-musleabihf
|
- armv7-unknown-linux-musleabihf
|
||||||
- riscv64gc-unknown-linux-gnu
|
- riscv64gc-unknown-linux-gnu
|
||||||
- loongarch64-unknown-linux-gnu
|
- loongarch64-unknown-linux-gnu
|
||||||
extra: ['bin']
|
|
||||||
include:
|
include:
|
||||||
- target: aarch64-apple-darwin
|
- target: aarch64-apple-darwin
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
- target: x86_64-apple-darwin
|
- target: x86_64-apple-darwin
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
- target: x86_64-pc-windows-msvc
|
- target: x86_64-pc-windows-msvc
|
||||||
extra: 'bin'
|
|
||||||
os: windows-latest
|
|
||||||
- target: x86_64-pc-windows-msvc
|
|
||||||
extra: msi
|
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
- target: aarch64-pc-windows-msvc
|
- target: aarch64-pc-windows-msvc
|
||||||
extra: 'bin'
|
os: windows-11-arm
|
||||||
os: windows-latest
|
|
||||||
- target: aarch64-pc-windows-msvc
|
|
||||||
extra: msi
|
|
||||||
os: windows-latest
|
|
||||||
- target: x86_64-unknown-linux-gnu
|
- target: x86_64-unknown-linux-gnu
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
- target: x86_64-unknown-linux-musl
|
- target: x86_64-unknown-linux-musl
|
||||||
@ -64,7 +57,7 @@ jobs:
|
|||||||
- target: armv7-unknown-linux-musleabihf
|
- target: armv7-unknown-linux-musleabihf
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
- target: riscv64gc-unknown-linux-gnu
|
- target: riscv64gc-unknown-linux-gnu
|
||||||
os: ubuntu-latest
|
os: ubuntu-22.04
|
||||||
- target: loongarch64-unknown-linux-gnu
|
- target: loongarch64-unknown-linux-gnu
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
|
|
||||||
@ -73,12 +66,23 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Wix Toolset 6 for Windows
|
||||||
|
shell: pwsh
|
||||||
|
if: ${{ startsWith(matrix.os, 'windows') }}
|
||||||
|
run: |
|
||||||
|
dotnet tool install --global wix --version 6.0.0
|
||||||
|
dotnet workload install wix
|
||||||
|
$wixPath = "$env:USERPROFILE\.dotnet\tools"
|
||||||
|
echo "$wixPath" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||||
|
$env:PATH = "$wixPath;$env:PATH"
|
||||||
|
wix --version
|
||||||
|
|
||||||
- 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
|
- name: Setup Rust toolchain
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
uses: actions-rust-lang/setup-rust-toolchain@v1.12.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
|
cache: false
|
||||||
@ -86,17 +90,31 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3
|
uses: hustcer/setup-nu@v3
|
||||||
|
if: ${{ matrix.os != 'windows-11-arm' }}
|
||||||
with:
|
with:
|
||||||
version: 0.98.0
|
version: 0.103.0
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
|
if: ${{ matrix.os != 'windows-11-arm' }}
|
||||||
run: nu .github/workflows/release-pkg.nu
|
run: nu .github/workflows/release-pkg.nu
|
||||||
env:
|
env:
|
||||||
OS: ${{ matrix.os }}
|
OS: ${{ matrix.os }}
|
||||||
REF: ${{ github.ref }}
|
REF: ${{ github.ref }}
|
||||||
TARGET: ${{ matrix.target }}
|
TARGET: ${{ matrix.target }}
|
||||||
_EXTRA_: ${{ matrix.extra }}
|
|
||||||
|
- name: Build Nu for Windows ARM64
|
||||||
|
id: nu0
|
||||||
|
shell: pwsh
|
||||||
|
if: ${{ matrix.os == 'windows-11-arm' }}
|
||||||
|
run: |
|
||||||
|
$env:OS = 'windows'
|
||||||
|
$env:REF = '${{ github.ref }}'
|
||||||
|
$env:TARGET = '${{ matrix.target }}'
|
||||||
|
cargo build --release --all --target aarch64-pc-windows-msvc
|
||||||
|
cp ./target/${{ matrix.target }}/release/nu.exe .
|
||||||
|
./nu.exe -c 'version'
|
||||||
|
./nu.exe ${{github.workspace}}/.github/workflows/release-pkg.nu
|
||||||
|
|
||||||
# WARN: Don't upgrade this action due to the release per asset issue.
|
# WARN: Don't upgrade this action due to the release per asset issue.
|
||||||
# See: https://github.com/softprops/action-gh-release/issues/445
|
# See: https://github.com/softprops/action-gh-release/issues/445
|
||||||
@ -105,7 +123,11 @@ jobs:
|
|||||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
files: ${{ steps.nu.outputs.archive }}
|
files: |
|
||||||
|
${{ steps.nu.outputs.msi }}
|
||||||
|
${{ steps.nu0.outputs.msi }}
|
||||||
|
${{ steps.nu.outputs.archive }}
|
||||||
|
${{ steps.nu0.outputs.archive }}
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
@ -10,4 +10,4 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Check spelling
|
- name: Check spelling
|
||||||
uses: crate-ci/typos@v1.28.4
|
uses: crate-ci/typos@v1.31.1
|
||||||
|
2
.github/workflows/winget-submission.yml
vendored
2
.github/workflows/winget-submission.yml
vendored
@ -26,4 +26,4 @@ jobs:
|
|||||||
version: ${{ inputs.tag_name || github.event.release.tag_name }}
|
version: ${{ inputs.tag_name || github.event.release.tag_name }}
|
||||||
release-tag: ${{ inputs.tag_name || github.event.release.tag_name }}
|
release-tag: ${{ inputs.tag_name || github.event.release.tag_name }}
|
||||||
token: ${{ secrets.NUSHELL_PAT }}
|
token: ${{ secrets.NUSHELL_PAT }}
|
||||||
fork-user: fdncred
|
fork-user: nushell
|
||||||
|
2206
Cargo.lock
generated
2206
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
126
Cargo.toml
126
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.81.0"
|
rust-version = "1.84.1"
|
||||||
version = "0.101.0"
|
version = "0.104.1"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -66,12 +66,12 @@ alphanumeric-sort = "1.5"
|
|||||||
ansi-str = "0.8"
|
ansi-str = "0.8"
|
||||||
anyhow = "1.0.82"
|
anyhow = "1.0.82"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
bracoxide = "0.1.4"
|
bracoxide = "0.1.5"
|
||||||
brotli = "6.0"
|
brotli = "7.0"
|
||||||
byteorder = "1.5"
|
byteorder = "1.5"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
bytesize = "1.3"
|
bytesize = "1.3.3"
|
||||||
calamine = "0.26.1"
|
calamine = "0.27"
|
||||||
chardetng = "0.1.17"
|
chardetng = "0.1.17"
|
||||||
chrono = { default-features = false, version = "0.4.34" }
|
chrono = { default-features = false, version = "0.4.34" }
|
||||||
chrono-humanize = "0.2.3"
|
chrono-humanize = "0.2.3"
|
||||||
@ -80,6 +80,7 @@ crossbeam-channel = "0.5.8"
|
|||||||
crossterm = "0.28.1"
|
crossterm = "0.28.1"
|
||||||
csv = "1.3"
|
csv = "1.3"
|
||||||
ctrlc = "3.4"
|
ctrlc = "3.4"
|
||||||
|
devicons = "0.6.12"
|
||||||
dialoguer = { default-features = false, version = "0.11" }
|
dialoguer = { default-features = false, version = "0.11" }
|
||||||
digest = { default-features = false, version = "0.10" }
|
digest = { default-features = false, version = "0.10" }
|
||||||
dirs = "5.0"
|
dirs = "5.0"
|
||||||
@ -89,10 +90,9 @@ encoding_rs = "0.8"
|
|||||||
fancy-regex = "0.14"
|
fancy-regex = "0.14"
|
||||||
filesize = "0.2"
|
filesize = "0.2"
|
||||||
filetime = "0.2"
|
filetime = "0.2"
|
||||||
fuzzy-matcher = "0.3"
|
|
||||||
heck = "0.5.0"
|
heck = "0.5.0"
|
||||||
human-date-parser = "0.2.0"
|
human-date-parser = "0.3.0"
|
||||||
indexmap = "2.7"
|
indexmap = "2.9"
|
||||||
indicatif = "0.17"
|
indicatif = "0.17"
|
||||||
interprocess = "2.2.0"
|
interprocess = "2.2.0"
|
||||||
is_executable = "1.0"
|
is_executable = "1.0"
|
||||||
@ -102,19 +102,21 @@ libproc = "0.14"
|
|||||||
log = "0.4"
|
log = "0.4"
|
||||||
lru = "0.12"
|
lru = "0.12"
|
||||||
lscolors = { version = "0.17", default-features = false }
|
lscolors = { version = "0.17", default-features = false }
|
||||||
lsp-server = "0.7.5"
|
lsp-server = "0.7.8"
|
||||||
lsp-types = { version = "0.95.0", features = ["proposed"] }
|
lsp-types = { version = "0.97.0", features = ["proposed"] }
|
||||||
|
lsp-textdocument = "0.4.2"
|
||||||
mach2 = "0.4"
|
mach2 = "0.4"
|
||||||
md5 = { version = "0.10", package = "md-5" }
|
md5 = { version = "0.10", package = "md-5" }
|
||||||
miette = "7.3"
|
miette = "7.5"
|
||||||
mime = "0.3.17"
|
mime = "0.3.17"
|
||||||
mime_guess = "2.0"
|
mime_guess = "2.0"
|
||||||
mockito = { version = "1.6", default-features = false }
|
mockito = { version = "1.7", default-features = false }
|
||||||
multipart-rs = "0.1.13"
|
multipart-rs = "0.1.13"
|
||||||
native-tls = "0.2"
|
native-tls = "0.2"
|
||||||
nix = { version = "0.29", default-features = false }
|
nix = { version = "0.29", default-features = false }
|
||||||
notify-debouncer-full = { version = "0.3", default-features = false }
|
notify-debouncer-full = { version = "0.3", default-features = false }
|
||||||
nu-ansi-term = "0.50.1"
|
nu-ansi-term = "0.50.1"
|
||||||
|
nucleo-matcher = "0.3"
|
||||||
num-format = "0.4"
|
num-format = "0.4"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
oem_cp = "2.0.0"
|
oem_cp = "2.0.0"
|
||||||
@ -125,7 +127,7 @@ pathdiff = "0.2"
|
|||||||
percent-encoding = "2"
|
percent-encoding = "2"
|
||||||
pretty_assertions = "1.4"
|
pretty_assertions = "1.4"
|
||||||
print-positions = "0.6"
|
print-positions = "0.6"
|
||||||
proc-macro-error = { version = "1.0", default-features = false }
|
proc-macro-error2 = "2.0"
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
procfs = "0.17.0"
|
procfs = "0.17.0"
|
||||||
pwd = "1.3"
|
pwd = "1.3"
|
||||||
@ -133,55 +135,58 @@ quick-xml = "0.37.0"
|
|||||||
quickcheck = "1.0"
|
quickcheck = "1.0"
|
||||||
quickcheck_macros = "1.0"
|
quickcheck_macros = "1.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
rand = "0.8"
|
rand = "0.9"
|
||||||
getrandom = "0.2" # pick same version that rand requires
|
getrandom = "0.2" # pick same version that rand requires
|
||||||
rand_chacha = "0.3.1"
|
rand_chacha = "0.9"
|
||||||
ratatui = "0.26"
|
ratatui = "0.29"
|
||||||
rayon = "1.10"
|
rayon = "1.10"
|
||||||
reedline = "0.38.0"
|
reedline = "0.40.0"
|
||||||
regex = "1.9.5"
|
|
||||||
rmp = "0.8"
|
rmp = "0.8"
|
||||||
rmp-serde = "1.3"
|
rmp-serde = "1.3"
|
||||||
ropey = "1.6.1"
|
|
||||||
roxmltree = "0.20"
|
roxmltree = "0.20"
|
||||||
rstest = { version = "0.23", default-features = false }
|
rstest = { version = "0.23", default-features = false }
|
||||||
|
rstest_reuse = "0.7"
|
||||||
rusqlite = "0.31"
|
rusqlite = "0.31"
|
||||||
rust-embed = "8.5.0"
|
rust-embed = "8.7.0"
|
||||||
scopeguard = { version = "1.2.0" }
|
scopeguard = { version = "1.2.0" }
|
||||||
serde = { version = "1.0" }
|
serde = { version = "1.0" }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0.97"
|
||||||
serde_urlencoded = "0.7.1"
|
serde_urlencoded = "0.7.1"
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9.33"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
strip-ansi-escapes = "0.2.0"
|
strip-ansi-escapes = "0.2.0"
|
||||||
|
strum = "0.26"
|
||||||
|
strum_macros = "0.26"
|
||||||
syn = "2.0"
|
syn = "2.0"
|
||||||
sysinfo = "0.32"
|
sysinfo = "0.33"
|
||||||
tabled = { version = "0.16.0", default-features = false }
|
tabled = { version = "0.17.0", default-features = false }
|
||||||
tempfile = "3.14"
|
tempfile = "3.15"
|
||||||
terminal_size = "0.4"
|
titlecase = "3.5"
|
||||||
titlecase = "3.0"
|
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
trash = "5.2"
|
trash = "5.2"
|
||||||
|
update-informer = { version = "1.2.0", default-features = false, features = ["github", "native-tls", "ureq"] }
|
||||||
umask = "2.1"
|
umask = "2.1"
|
||||||
unicode-segmentation = "1.12"
|
unicode-segmentation = "1.12"
|
||||||
unicode-width = "0.2"
|
unicode-width = "0.2"
|
||||||
ureq = { version = "2.12", default-features = false }
|
ureq = { version = "2.12", default-features = false, features = ["socks-proxy"] }
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
uu_cp = "0.0.28"
|
uu_cp = "0.0.30"
|
||||||
uu_mkdir = "0.0.28"
|
uu_mkdir = "0.0.30"
|
||||||
uu_mktemp = "0.0.28"
|
uu_mktemp = "0.0.30"
|
||||||
uu_mv = "0.0.28"
|
uu_mv = "0.0.30"
|
||||||
uu_touch = "0.0.28"
|
uu_touch = "0.0.30"
|
||||||
uu_whoami = "0.0.28"
|
uu_whoami = "0.0.30"
|
||||||
uu_uname = "0.0.28"
|
uu_uname = "0.0.30"
|
||||||
uucore = "0.0.28"
|
uucore = "0.0.30"
|
||||||
uuid = "1.11.0"
|
uuid = "1.16.0"
|
||||||
v_htmlescape = "0.15.0"
|
v_htmlescape = "0.15.0"
|
||||||
wax = "0.6"
|
wax = "0.6"
|
||||||
|
web-time = "1.1.0"
|
||||||
which = "7.0.0"
|
which = "7.0.0"
|
||||||
windows = "0.56"
|
windows = "0.56"
|
||||||
windows-sys = "0.48"
|
windows-sys = "0.48"
|
||||||
winreg = "0.52"
|
winreg = "0.52"
|
||||||
|
memchr = "2.7.4"
|
||||||
|
|
||||||
[workspace.lints.clippy]
|
[workspace.lints.clippy]
|
||||||
# Warning: workspace lints affect library code as well as tests, so don't enable lints that would be too noisy in tests like that.
|
# Warning: workspace lints affect library code as well as tests, so don't enable lints that would be too noisy in tests like that.
|
||||||
@ -192,22 +197,22 @@ unchecked_duration_subtraction = "warn"
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cli = { path = "./crates/nu-cli", version = "0.101.0" }
|
nu-cli = { path = "./crates/nu-cli", version = "0.104.1" }
|
||||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.101.0" }
|
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.104.1" }
|
||||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.101.0" }
|
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.104.1" }
|
||||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.101.0", optional = true }
|
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.104.1", optional = true }
|
||||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.101.0" }
|
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.104.1" }
|
||||||
nu-command = { path = "./crates/nu-command", version = "0.101.0" }
|
nu-command = { path = "./crates/nu-command", version = "0.104.1" }
|
||||||
nu-engine = { path = "./crates/nu-engine", version = "0.101.0" }
|
nu-engine = { path = "./crates/nu-engine", version = "0.104.1" }
|
||||||
nu-explore = { path = "./crates/nu-explore", version = "0.101.0" }
|
nu-explore = { path = "./crates/nu-explore", version = "0.104.1" }
|
||||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.101.0" }
|
nu-lsp = { path = "./crates/nu-lsp/", version = "0.104.1" }
|
||||||
nu-parser = { path = "./crates/nu-parser", version = "0.101.0" }
|
nu-parser = { path = "./crates/nu-parser", version = "0.104.1" }
|
||||||
nu-path = { path = "./crates/nu-path", version = "0.101.0" }
|
nu-path = { path = "./crates/nu-path", version = "0.104.1" }
|
||||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.101.0" }
|
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.104.1" }
|
||||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.101.0" }
|
nu-protocol = { path = "./crates/nu-protocol", version = "0.104.1" }
|
||||||
nu-std = { path = "./crates/nu-std", version = "0.101.0" }
|
nu-std = { path = "./crates/nu-std", version = "0.104.1" }
|
||||||
nu-system = { path = "./crates/nu-system", version = "0.101.0" }
|
nu-system = { path = "./crates/nu-system", version = "0.104.1" }
|
||||||
nu-utils = { path = "./crates/nu-utils", version = "0.101.0" }
|
nu-utils = { path = "./crates/nu-utils", version = "0.104.1" }
|
||||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
crossterm = { workspace = true }
|
crossterm = { workspace = true }
|
||||||
@ -215,7 +220,6 @@ ctrlc = { workspace = true }
|
|||||||
dirs = { workspace = true }
|
dirs = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] }
|
miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] }
|
||||||
mimalloc = { version = "0.1.42", default-features = false, optional = true }
|
|
||||||
multipart-rs = { workspace = true }
|
multipart-rs = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
simplelog = "0.12"
|
simplelog = "0.12"
|
||||||
@ -237,14 +241,14 @@ nix = { workspace = true, default-features = false, features = [
|
|||||||
] }
|
] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.101.0" }
|
nu-test-support = { path = "./crates/nu-test-support", version = "0.104.1" }
|
||||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.101.0" }
|
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.104.1" }
|
||||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.101.0" }
|
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.104.1" }
|
||||||
assert_cmd = "2.0"
|
assert_cmd = "2.0"
|
||||||
dirs = { workspace = true }
|
dirs = { workspace = true }
|
||||||
tango-bench = "0.6"
|
tango-bench = "0.6"
|
||||||
pretty_assertions = { workspace = true }
|
pretty_assertions = { workspace = true }
|
||||||
regex = { workspace = true }
|
fancy-regex = { workspace = true }
|
||||||
rstest = { workspace = true, default-features = false }
|
rstest = { workspace = true, default-features = false }
|
||||||
serial_test = "3.2"
|
serial_test = "3.2"
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
@ -269,7 +273,6 @@ default = [
|
|||||||
"plugin",
|
"plugin",
|
||||||
"trash-support",
|
"trash-support",
|
||||||
"sqlite",
|
"sqlite",
|
||||||
"mimalloc",
|
|
||||||
]
|
]
|
||||||
stable = ["default"]
|
stable = ["default"]
|
||||||
# NOTE: individual features are also passed to `nu-cmd-lang` that uses them to generate the feature matrix in the `version` command
|
# NOTE: individual features are also passed to `nu-cmd-lang` that uses them to generate the feature matrix in the `version` command
|
||||||
@ -278,7 +281,6 @@ stable = ["default"]
|
|||||||
# otherwise the system version will be used. Not enabled by default because it takes a while to build
|
# otherwise the system version will be used. Not enabled by default because it takes a while to build
|
||||||
static-link-openssl = ["dep:openssl", "nu-cmd-lang/static-link-openssl"]
|
static-link-openssl = ["dep:openssl", "nu-cmd-lang/static-link-openssl"]
|
||||||
|
|
||||||
mimalloc = ["nu-cmd-lang/mimalloc", "dep:mimalloc"]
|
|
||||||
# Optional system clipboard support in `reedline`, this behavior has problematic compatibility with some systems.
|
# Optional system clipboard support in `reedline`, this behavior has problematic compatibility with some systems.
|
||||||
# Missing X server/ Wayland can cause issues
|
# Missing X server/ Wayland can cause issues
|
||||||
system-clipboard = [
|
system-clipboard = [
|
||||||
@ -291,7 +293,7 @@ system-clipboard = [
|
|||||||
trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"]
|
trash-support = ["nu-command/trash-support", "nu-cmd-lang/trash-support"]
|
||||||
|
|
||||||
# SQLite commands for nushell
|
# SQLite commands for nushell
|
||||||
sqlite = ["nu-command/sqlite", "nu-cmd-lang/sqlite"]
|
sqlite = ["nu-command/sqlite", "nu-cmd-lang/sqlite", "nu-std/sqlite"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = "s" # Optimize for size
|
opt-level = "s" # Optimize for size
|
||||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2019 - 2023 The Nushell Project Developers
|
Copyright (c) 2019 - 2025 The Nushell Project Developers
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
115
README.md
115
README.md
@ -4,7 +4,6 @@
|
|||||||
[](https://github.com/nushell/nushell/actions/workflows/nightly-build.yml)
|
[](https://github.com/nushell/nushell/actions/workflows/nightly-build.yml)
|
||||||
[](https://discord.gg/NtAbbGn)
|
[](https://discord.gg/NtAbbGn)
|
||||||
[](https://changelog.com/podcast/363)
|
[](https://changelog.com/podcast/363)
|
||||||
[](https://twitter.com/nu_shell)
|
|
||||||
[](https://github.com/nushell/nushell/graphs/commit-activity)
|
[](https://github.com/nushell/nushell/graphs/commit-activity)
|
||||||
[](https://github.com/nushell/nushell/graphs/contributors)
|
[](https://github.com/nushell/nushell/graphs/contributors)
|
||||||
|
|
||||||
@ -35,7 +34,7 @@ This project has reached a minimum-viable-product level of quality. Many people
|
|||||||
|
|
||||||
The [Nushell book](https://www.nushell.sh/book/) is the primary source of Nushell documentation. You can find [a full list of Nu commands in the book](https://www.nushell.sh/commands/), and we have many examples of using Nu in our [cookbook](https://www.nushell.sh/cookbook/).
|
The [Nushell book](https://www.nushell.sh/book/) is the primary source of Nushell documentation. You can find [a full list of Nu commands in the book](https://www.nushell.sh/commands/), and we have many examples of using Nu in our [cookbook](https://www.nushell.sh/cookbook/).
|
||||||
|
|
||||||
We're also active on [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell); come and chat with us!
|
We're also active on [Discord](https://discord.gg/NtAbbGn); come and chat with us!
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@ -95,44 +94,44 @@ Commands that work in the pipeline fit into one of three categories:
|
|||||||
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> ls | where type == "dir" | table
|
ls | where type == "dir" | table
|
||||||
╭────┬──────────┬──────┬─────────┬───────────────╮
|
# => ╭────┬──────────┬──────┬─────────┬───────────────╮
|
||||||
│ # │ name │ type │ size │ modified │
|
# => │ # │ name │ type │ size │ modified │
|
||||||
├────┼──────────┼──────┼─────────┼───────────────┤
|
# => ├────┼──────────┼──────┼─────────┼───────────────┤
|
||||||
│ 0 │ .cargo │ dir │ 0 B │ 9 minutes ago │
|
# => │ 0 │ .cargo │ dir │ 0 B │ 9 minutes ago │
|
||||||
│ 1 │ assets │ dir │ 0 B │ 2 weeks ago │
|
# => │ 1 │ assets │ dir │ 0 B │ 2 weeks ago │
|
||||||
│ 2 │ crates │ dir │ 4.0 KiB │ 2 weeks ago │
|
# => │ 2 │ crates │ dir │ 4.0 KiB │ 2 weeks ago │
|
||||||
│ 3 │ docker │ dir │ 0 B │ 2 weeks ago │
|
# => │ 3 │ docker │ dir │ 0 B │ 2 weeks ago │
|
||||||
│ 4 │ docs │ dir │ 0 B │ 2 weeks ago │
|
# => │ 4 │ docs │ dir │ 0 B │ 2 weeks ago │
|
||||||
│ 5 │ images │ dir │ 0 B │ 2 weeks ago │
|
# => │ 5 │ images │ dir │ 0 B │ 2 weeks ago │
|
||||||
│ 6 │ pkg_mgrs │ dir │ 0 B │ 2 weeks ago │
|
# => │ 6 │ pkg_mgrs │ dir │ 0 B │ 2 weeks ago │
|
||||||
│ 7 │ samples │ dir │ 0 B │ 2 weeks ago │
|
# => │ 7 │ samples │ dir │ 0 B │ 2 weeks ago │
|
||||||
│ 8 │ src │ dir │ 4.0 KiB │ 2 weeks ago │
|
# => │ 8 │ src │ dir │ 4.0 KiB │ 2 weeks ago │
|
||||||
│ 9 │ target │ dir │ 0 B │ a day ago │
|
# => │ 9 │ target │ dir │ 0 B │ a day ago │
|
||||||
│ 10 │ tests │ dir │ 4.0 KiB │ 2 weeks ago │
|
# => │ 10 │ tests │ dir │ 4.0 KiB │ 2 weeks ago │
|
||||||
│ 11 │ wix │ dir │ 0 B │ 2 weeks ago │
|
# => │ 11 │ wix │ dir │ 0 B │ 2 weeks ago │
|
||||||
╰────┴──────────┴──────┴─────────┴───────────────╯
|
# => ╰────┴──────────┴──────┴─────────┴───────────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
Because most of the time you'll want to see the output of a pipeline, `table` is assumed.
|
Because most of the time you'll want to see the output of a pipeline, `table` is assumed.
|
||||||
We could have also written the above:
|
We could have also written the above:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> ls | where type == "dir"
|
ls | where type == "dir"
|
||||||
```
|
```
|
||||||
|
|
||||||
Being able to use the same commands and compose them differently is an important philosophy in Nu.
|
Being able to use the same commands and compose them differently is an important philosophy in Nu.
|
||||||
For example, we could use the built-in `ps` command to get a list of the running processes, using the same `where` as above.
|
For example, we could use the built-in `ps` command to get a list of the running processes, using the same `where` as above.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> ps | where cpu > 0
|
ps | where cpu > 0
|
||||||
╭───┬───────┬───────────┬───────┬───────────┬───────────╮
|
# => ╭───┬───────┬───────────┬───────┬───────────┬───────────╮
|
||||||
│ # │ pid │ name │ cpu │ mem │ virtual │
|
# => │ # │ pid │ name │ cpu │ mem │ virtual │
|
||||||
├───┼───────┼───────────┼───────┼───────────┼───────────┤
|
# => ├───┼───────┼───────────┼───────┼───────────┼───────────┤
|
||||||
│ 0 │ 2240 │ Slack.exe │ 16.40 │ 178.3 MiB │ 232.6 MiB │
|
# => │ 0 │ 2240 │ Slack.exe │ 16.40 │ 178.3 MiB │ 232.6 MiB │
|
||||||
│ 1 │ 16948 │ Slack.exe │ 16.32 │ 205.0 MiB │ 197.9 MiB │
|
# => │ 1 │ 16948 │ Slack.exe │ 16.32 │ 205.0 MiB │ 197.9 MiB │
|
||||||
│ 2 │ 17700 │ nu.exe │ 3.77 │ 26.1 MiB │ 8.8 MiB │
|
# => │ 2 │ 17700 │ nu.exe │ 3.77 │ 26.1 MiB │ 8.8 MiB │
|
||||||
╰───┴───────┴───────────┴───────┴───────────┴───────────╯
|
# => ╰───┴───────┴───────────┴───────┴───────────┴───────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
### Opening files
|
### Opening files
|
||||||
@ -141,46 +140,46 @@ Nu can load file and URL contents as raw text or structured data (if it recogniz
|
|||||||
For example, you can load a .toml file as structured data and explore it:
|
For example, you can load a .toml file as structured data and explore it:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> open Cargo.toml
|
open Cargo.toml
|
||||||
╭──────────────────┬────────────────────╮
|
# => ╭──────────────────┬────────────────────╮
|
||||||
│ bin │ [table 1 row] │
|
# => │ bin │ [table 1 row] │
|
||||||
│ dependencies │ {record 25 fields} │
|
# => │ dependencies │ {record 25 fields} │
|
||||||
│ dev-dependencies │ {record 8 fields} │
|
# => │ dev-dependencies │ {record 8 fields} │
|
||||||
│ features │ {record 10 fields} │
|
# => │ features │ {record 10 fields} │
|
||||||
│ package │ {record 13 fields} │
|
# => │ package │ {record 13 fields} │
|
||||||
│ patch │ {record 1 field} │
|
# => │ patch │ {record 1 field} │
|
||||||
│ profile │ {record 3 fields} │
|
# => │ profile │ {record 3 fields} │
|
||||||
│ target │ {record 3 fields} │
|
# => │ target │ {record 3 fields} │
|
||||||
│ workspace │ {record 1 field} │
|
# => │ workspace │ {record 1 field} │
|
||||||
╰──────────────────┴────────────────────╯
|
# => ╰──────────────────┴────────────────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
We can pipe this into a command that gets the contents of one of the columns:
|
We can pipe this into a command that gets the contents of one of the columns:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> open Cargo.toml | get package
|
open Cargo.toml | get package
|
||||||
╭───────────────┬────────────────────────────────────╮
|
# => ╭───────────────┬────────────────────────────────────╮
|
||||||
│ authors │ [list 1 item] │
|
# => │ authors │ [list 1 item] │
|
||||||
│ default-run │ nu │
|
# => │ default-run │ nu │
|
||||||
│ description │ A new type of shell │
|
# => │ description │ A new type of shell │
|
||||||
│ documentation │ https://www.nushell.sh/book/ │
|
# => │ documentation │ https://www.nushell.sh/book/ │
|
||||||
│ edition │ 2018 │
|
# => │ edition │ 2018 │
|
||||||
│ exclude │ [list 1 item] │
|
# => │ exclude │ [list 1 item] │
|
||||||
│ homepage │ https://www.nushell.sh │
|
# => │ homepage │ https://www.nushell.sh │
|
||||||
│ license │ MIT │
|
# => │ license │ MIT │
|
||||||
│ metadata │ {record 1 field} │
|
# => │ metadata │ {record 1 field} │
|
||||||
│ name │ nu │
|
# => │ name │ nu │
|
||||||
│ repository │ https://github.com/nushell/nushell │
|
# => │ repository │ https://github.com/nushell/nushell │
|
||||||
│ rust-version │ 1.60 │
|
# => │ rust-version │ 1.60 │
|
||||||
│ version │ 0.72.0 │
|
# => │ version │ 0.72.0 │
|
||||||
╰───────────────┴────────────────────────────────────╯
|
# => ╰───────────────┴────────────────────────────────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
And if needed we can drill down further:
|
And if needed we can drill down further:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
> open Cargo.toml | get package.version
|
open Cargo.toml | get package.version
|
||||||
0.72.0
|
# => 0.72.0
|
||||||
```
|
```
|
||||||
|
|
||||||
### Plugins
|
### Plugins
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use nu_cli::{eval_source, evaluate_commands};
|
use nu_cli::{eval_source, evaluate_commands};
|
||||||
use nu_plugin_core::{Encoder, EncodingType};
|
use nu_plugin_core::{Encoder, EncodingType};
|
||||||
use nu_plugin_protocol::{PluginCallResponse, PluginOutput};
|
use nu_plugin_protocol::{PluginCallResponse, PluginOutput};
|
||||||
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
PipelineData, Signals, Span, Spanned, Value,
|
PipelineData, Signals, Span, Spanned, Value,
|
||||||
@ -9,12 +8,11 @@ use nu_protocol::{
|
|||||||
use nu_std::load_standard_library;
|
use nu_std::load_standard_library;
|
||||||
use nu_utils::{get_default_config, get_default_env};
|
use nu_utils::{get_default_config, get_default_env};
|
||||||
use std::{
|
use std::{
|
||||||
|
fmt::Write,
|
||||||
|
hint::black_box,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::hint::black_box;
|
|
||||||
|
|
||||||
use tango_bench::{benchmark_fn, tango_benchmarks, tango_main, IntoBenchmarks};
|
use tango_bench::{benchmark_fn, tango_benchmarks, tango_main, IntoBenchmarks};
|
||||||
|
|
||||||
fn load_bench_commands() -> EngineState {
|
fn load_bench_commands() -> EngineState {
|
||||||
@ -141,19 +139,16 @@ fn bench_load_standard_lib() -> impl IntoBenchmarks {
|
|||||||
})]
|
})]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_flat_record_string(n: i32) -> String {
|
fn create_flat_record_string(n: usize) -> String {
|
||||||
let mut s = String::from("let record = {");
|
let mut s = String::from("let record = { ");
|
||||||
for i in 0..n {
|
for i in 0..n {
|
||||||
s.push_str(&format!("col_{}: {}", i, i));
|
write!(s, "col_{i}: {i}, ").unwrap();
|
||||||
if i < n - 1 {
|
|
||||||
s.push_str(", ");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
s.push('}');
|
s.push('}');
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_nested_record_string(depth: i32) -> String {
|
fn create_nested_record_string(depth: usize) -> String {
|
||||||
let mut s = String::from("let record = {");
|
let mut s = String::from("let record = {");
|
||||||
for _ in 0..depth {
|
for _ in 0..depth {
|
||||||
s.push_str("col: {");
|
s.push_str("col: {");
|
||||||
@ -166,7 +161,7 @@ fn create_nested_record_string(depth: i32) -> String {
|
|||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_example_table_nrows(n: i32) -> String {
|
fn create_example_table_nrows(n: usize) -> String {
|
||||||
let mut s = String::from("let table = [[foo bar baz]; ");
|
let mut s = String::from("let table = [[foo bar baz]; ");
|
||||||
for i in 0..n {
|
for i in 0..n {
|
||||||
s.push_str(&format!("[0, 1, {i}]"));
|
s.push_str(&format!("[0, 1, {i}]"));
|
||||||
@ -178,7 +173,7 @@ fn create_example_table_nrows(n: i32) -> String {
|
|||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_record_create(n: i32) -> impl IntoBenchmarks {
|
fn bench_record_create(n: usize) -> impl IntoBenchmarks {
|
||||||
bench_command(
|
bench_command(
|
||||||
&format!("record_create_{n}"),
|
&format!("record_create_{n}"),
|
||||||
&create_flat_record_string(n),
|
&create_flat_record_string(n),
|
||||||
@ -187,7 +182,7 @@ fn bench_record_create(n: i32) -> impl IntoBenchmarks {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_record_flat_access(n: i32) -> impl IntoBenchmarks {
|
fn bench_record_flat_access(n: usize) -> impl IntoBenchmarks {
|
||||||
let setup_command = create_flat_record_string(n);
|
let setup_command = create_flat_record_string(n);
|
||||||
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
|
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
|
||||||
bench_command(
|
bench_command(
|
||||||
@ -198,10 +193,10 @@ fn bench_record_flat_access(n: i32) -> impl IntoBenchmarks {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_record_nested_access(n: i32) -> impl IntoBenchmarks {
|
fn bench_record_nested_access(n: usize) -> impl IntoBenchmarks {
|
||||||
let setup_command = create_nested_record_string(n);
|
let setup_command = create_nested_record_string(n);
|
||||||
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
|
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
|
||||||
let nested_access = ".col".repeat(n as usize);
|
let nested_access = ".col".repeat(n);
|
||||||
bench_command(
|
bench_command(
|
||||||
&format!("record_nested_access_{n}"),
|
&format!("record_nested_access_{n}"),
|
||||||
&format!("$record{} | ignore", nested_access),
|
&format!("$record{} | ignore", nested_access),
|
||||||
@ -210,7 +205,18 @@ fn bench_record_nested_access(n: i32) -> impl IntoBenchmarks {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_table_create(n: i32) -> impl IntoBenchmarks {
|
fn bench_record_insert(n: usize, m: usize) -> impl IntoBenchmarks {
|
||||||
|
let setup_command = create_flat_record_string(n);
|
||||||
|
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
|
||||||
|
let mut insert = String::from("$record");
|
||||||
|
for i in n..(n + m) {
|
||||||
|
write!(insert, " | insert col_{i} {i}").unwrap();
|
||||||
|
}
|
||||||
|
insert.push_str(" | ignore");
|
||||||
|
bench_command(&format!("record_insert_{n}_{m}"), &insert, stack, engine)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_table_create(n: usize) -> impl IntoBenchmarks {
|
||||||
bench_command(
|
bench_command(
|
||||||
&format!("table_create_{n}"),
|
&format!("table_create_{n}"),
|
||||||
&create_example_table_nrows(n),
|
&create_example_table_nrows(n),
|
||||||
@ -219,7 +225,7 @@ fn bench_table_create(n: i32) -> impl IntoBenchmarks {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_table_get(n: i32) -> impl IntoBenchmarks {
|
fn bench_table_get(n: usize) -> impl IntoBenchmarks {
|
||||||
let setup_command = create_example_table_nrows(n);
|
let setup_command = create_example_table_nrows(n);
|
||||||
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
|
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
|
||||||
bench_command(
|
bench_command(
|
||||||
@ -230,7 +236,7 @@ fn bench_table_get(n: i32) -> impl IntoBenchmarks {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_table_select(n: i32) -> impl IntoBenchmarks {
|
fn bench_table_select(n: usize) -> impl IntoBenchmarks {
|
||||||
let setup_command = create_example_table_nrows(n);
|
let setup_command = create_example_table_nrows(n);
|
||||||
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
|
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
|
||||||
bench_command(
|
bench_command(
|
||||||
@ -241,7 +247,29 @@ fn bench_table_select(n: i32) -> impl IntoBenchmarks {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_eval_interleave(n: i32) -> impl IntoBenchmarks {
|
fn bench_table_insert_row(n: usize, m: usize) -> impl IntoBenchmarks {
|
||||||
|
let setup_command = create_example_table_nrows(n);
|
||||||
|
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
|
||||||
|
let mut insert = String::from("$table");
|
||||||
|
for i in n..(n + m) {
|
||||||
|
write!(insert, " | insert {i} {{ foo: 0, bar: 1, baz: {i} }}").unwrap();
|
||||||
|
}
|
||||||
|
insert.push_str(" | ignore");
|
||||||
|
bench_command(&format!("table_insert_row_{n}_{m}"), &insert, stack, engine)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_table_insert_col(n: usize, m: usize) -> impl IntoBenchmarks {
|
||||||
|
let setup_command = create_example_table_nrows(n);
|
||||||
|
let (stack, engine) = setup_stack_and_engine_from_command(&setup_command);
|
||||||
|
let mut insert = String::from("$table");
|
||||||
|
for i in 0..m {
|
||||||
|
write!(insert, " | insert col_{i} {i}").unwrap();
|
||||||
|
}
|
||||||
|
insert.push_str(" | ignore");
|
||||||
|
bench_command(&format!("table_insert_col_{n}_{m}"), &insert, stack, engine)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_eval_interleave(n: usize) -> impl IntoBenchmarks {
|
||||||
let engine = setup_engine();
|
let engine = setup_engine();
|
||||||
let stack = Stack::new();
|
let stack = Stack::new();
|
||||||
bench_command(
|
bench_command(
|
||||||
@ -252,7 +280,7 @@ fn bench_eval_interleave(n: i32) -> impl IntoBenchmarks {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_eval_interleave_with_interrupt(n: i32) -> impl IntoBenchmarks {
|
fn bench_eval_interleave_with_interrupt(n: usize) -> impl IntoBenchmarks {
|
||||||
let mut engine = setup_engine();
|
let mut engine = setup_engine();
|
||||||
engine.set_signals(Signals::new(Arc::new(AtomicBool::new(false))));
|
engine.set_signals(Signals::new(Arc::new(AtomicBool::new(false))));
|
||||||
let stack = Stack::new();
|
let stack = Stack::new();
|
||||||
@ -264,7 +292,7 @@ fn bench_eval_interleave_with_interrupt(n: i32) -> impl IntoBenchmarks {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_eval_for(n: i32) -> impl IntoBenchmarks {
|
fn bench_eval_for(n: usize) -> impl IntoBenchmarks {
|
||||||
let engine = setup_engine();
|
let engine = setup_engine();
|
||||||
let stack = Stack::new();
|
let stack = Stack::new();
|
||||||
bench_command(
|
bench_command(
|
||||||
@ -275,7 +303,7 @@ fn bench_eval_for(n: i32) -> impl IntoBenchmarks {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_eval_each(n: i32) -> impl IntoBenchmarks {
|
fn bench_eval_each(n: usize) -> impl IntoBenchmarks {
|
||||||
let engine = setup_engine();
|
let engine = setup_engine();
|
||||||
let stack = Stack::new();
|
let stack = Stack::new();
|
||||||
bench_command(
|
bench_command(
|
||||||
@ -286,7 +314,7 @@ fn bench_eval_each(n: i32) -> impl IntoBenchmarks {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_eval_par_each(n: i32) -> impl IntoBenchmarks {
|
fn bench_eval_par_each(n: usize) -> impl IntoBenchmarks {
|
||||||
let engine = setup_engine();
|
let engine = setup_engine();
|
||||||
let stack = Stack::new();
|
let stack = Stack::new();
|
||||||
bench_command(
|
bench_command(
|
||||||
@ -427,6 +455,14 @@ tango_benchmarks!(
|
|||||||
bench_record_nested_access(32),
|
bench_record_nested_access(32),
|
||||||
bench_record_nested_access(64),
|
bench_record_nested_access(64),
|
||||||
bench_record_nested_access(128),
|
bench_record_nested_access(128),
|
||||||
|
bench_record_insert(1, 1),
|
||||||
|
bench_record_insert(10, 1),
|
||||||
|
bench_record_insert(100, 1),
|
||||||
|
bench_record_insert(1000, 1),
|
||||||
|
bench_record_insert(1, 10),
|
||||||
|
bench_record_insert(10, 10),
|
||||||
|
bench_record_insert(100, 10),
|
||||||
|
bench_record_insert(1000, 10),
|
||||||
// Table
|
// Table
|
||||||
bench_table_create(1),
|
bench_table_create(1),
|
||||||
bench_table_create(10),
|
bench_table_create(10),
|
||||||
@ -440,6 +476,22 @@ tango_benchmarks!(
|
|||||||
bench_table_select(10),
|
bench_table_select(10),
|
||||||
bench_table_select(100),
|
bench_table_select(100),
|
||||||
bench_table_select(1_000),
|
bench_table_select(1_000),
|
||||||
|
bench_table_insert_row(1, 1),
|
||||||
|
bench_table_insert_row(10, 1),
|
||||||
|
bench_table_insert_row(100, 1),
|
||||||
|
bench_table_insert_row(1000, 1),
|
||||||
|
bench_table_insert_row(1, 10),
|
||||||
|
bench_table_insert_row(10, 10),
|
||||||
|
bench_table_insert_row(100, 10),
|
||||||
|
bench_table_insert_row(1000, 10),
|
||||||
|
bench_table_insert_col(1, 1),
|
||||||
|
bench_table_insert_col(10, 1),
|
||||||
|
bench_table_insert_col(100, 1),
|
||||||
|
bench_table_insert_col(1000, 1),
|
||||||
|
bench_table_insert_col(1, 10),
|
||||||
|
bench_table_insert_col(10, 10),
|
||||||
|
bench_table_insert_col(100, 10),
|
||||||
|
bench_table_insert_col(1000, 10),
|
||||||
// Eval
|
// Eval
|
||||||
// Interleave
|
// Interleave
|
||||||
bench_eval_interleave(100),
|
bench_eval_interleave(100),
|
||||||
|
3
clippy/wasm/clippy.toml
Normal file
3
clippy/wasm/clippy.toml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[[disallowed-types]]
|
||||||
|
path = "std::time::Instant"
|
||||||
|
reason = "WASM panics if used, use `web_time::Instant` instead"
|
@ -5,40 +5,43 @@ 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.101.0"
|
version = "0.104.1"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.101.0" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.104.1" }
|
||||||
nu-command = { path = "../nu-command", version = "0.101.0" }
|
nu-command = { path = "../nu-command", version = "0.104.1" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.101.0" }
|
nu-std = { path = "../nu-std", version = "0.104.1" }
|
||||||
|
nu-test-support = { path = "../nu-test-support", version = "0.104.1" }
|
||||||
rstest = { workspace = true, default-features = false }
|
rstest = { workspace = true, default-features = false }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.101.0" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.104.1" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.101.0", features = ["os"] }
|
nu-engine = { path = "../nu-engine", version = "0.104.1", features = ["os"] }
|
||||||
nu-path = { path = "../nu-path", version = "0.101.0" }
|
nu-glob = { path = "../nu-glob", version = "0.104.1" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.101.0" }
|
nu-path = { path = "../nu-path", version = "0.104.1" }
|
||||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.101.0", optional = true }
|
nu-parser = { path = "../nu-parser", version = "0.104.1" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.101.0", features = ["os"] }
|
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.104.1", optional = true }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.101.0" }
|
nu-protocol = { path = "../nu-protocol", version = "0.104.1", features = ["os"] }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.101.0" }
|
nu-utils = { path = "../nu-utils", version = "0.104.1" }
|
||||||
|
nu-color-config = { path = "../nu-color-config", version = "0.104.1" }
|
||||||
nu-ansi-term = { workspace = true }
|
nu-ansi-term = { workspace = true }
|
||||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
chrono = { default-features = false, features = ["std"], workspace = true }
|
chrono = { default-features = false, features = ["std"], workspace = true }
|
||||||
crossterm = { workspace = true }
|
crossterm = { workspace = true }
|
||||||
fancy-regex = { workspace = true }
|
fancy-regex = { workspace = true }
|
||||||
fuzzy-matcher = { workspace = true }
|
|
||||||
is_executable = { workspace = true }
|
is_executable = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
miette = { workspace = true, features = ["fancy-no-backtrace"] }
|
|
||||||
lscolors = { workspace = true, default-features = false, features = ["nu-ansi-term"] }
|
lscolors = { workspace = true, default-features = false, features = ["nu-ansi-term"] }
|
||||||
|
miette = { workspace = true, features = ["fancy-no-backtrace"] }
|
||||||
|
nucleo-matcher = { workspace = true }
|
||||||
percent-encoding = { workspace = true }
|
percent-encoding = { workspace = true }
|
||||||
sysinfo = { workspace = true }
|
sysinfo = { workspace = true }
|
||||||
|
strum = { workspace = true }
|
||||||
unicode-segmentation = { workspace = true }
|
unicode-segmentation = { workspace = true }
|
||||||
uuid = { workspace = true, features = ["v4"] }
|
uuid = { workspace = true, features = ["v4"] }
|
||||||
which = { workspace = true }
|
which = { workspace = true }
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct CommandlineEdit;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for CommandlineEdit {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"commandline edit"
|
"commandline edit"
|
||||||
}
|
}
|
||||||
@ -29,7 +29,7 @@ impl Command for SubCommand {
|
|||||||
.required(
|
.required(
|
||||||
"str",
|
"str",
|
||||||
SyntaxShape::String,
|
SyntaxShape::String,
|
||||||
"the string to perform the operation with",
|
"The string to perform the operation with.",
|
||||||
)
|
)
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,9 @@ use nu_engine::command_prelude::*;
|
|||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct CommandlineGetCursor;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for CommandlineGetCursor {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"commandline get-cursor"
|
"commandline get-cursor"
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,6 @@ mod get_cursor;
|
|||||||
mod set_cursor;
|
mod set_cursor;
|
||||||
|
|
||||||
pub use commandline_::Commandline;
|
pub use commandline_::Commandline;
|
||||||
pub use edit::SubCommand as CommandlineEdit;
|
pub use edit::CommandlineEdit;
|
||||||
pub use get_cursor::SubCommand as CommandlineGetCursor;
|
pub use get_cursor::CommandlineGetCursor;
|
||||||
pub use set_cursor::SubCommand as CommandlineSetCursor;
|
pub use set_cursor::CommandlineSetCursor;
|
||||||
|
@ -3,9 +3,9 @@ use nu_engine::command_prelude::*;
|
|||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct CommandlineSetCursor;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for CommandlineSetCursor {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"commandline set-cursor"
|
"commandline set-cursor"
|
||||||
}
|
}
|
||||||
@ -18,7 +18,7 @@ impl Command for SubCommand {
|
|||||||
"set the current cursor position to the end of the buffer",
|
"set the current cursor position to the end of the buffer",
|
||||||
Some('e'),
|
Some('e'),
|
||||||
)
|
)
|
||||||
.optional("pos", SyntaxShape::Int, "Cursor position to be set")
|
.optional("pos", SyntaxShape::Int, "Cursor position to be set.")
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::HistoryFileFormat;
|
use nu_protocol::{shell_error::io::IoError, HistoryFileFormat};
|
||||||
use reedline::{
|
use reedline::{
|
||||||
FileBackedHistory, History as ReedlineHistory, HistoryItem, SearchDirection, SearchQuery,
|
FileBackedHistory, History as ReedlineHistory, HistoryItem, SearchDirection, SearchQuery,
|
||||||
SqliteBackedHistory,
|
SqliteBackedHistory,
|
||||||
@ -93,10 +93,11 @@ impl Command for History {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.ok_or(ShellError::FileNotFound {
|
.ok_or(IoError::new(
|
||||||
file: history_path.display().to_string(),
|
std::io::ErrorKind::NotFound,
|
||||||
span: head,
|
head,
|
||||||
})?
|
history_path,
|
||||||
|
))?
|
||||||
.into_pipeline_data(head, signals)),
|
.into_pipeline_data(head, signals)),
|
||||||
HistoryFileFormat::Sqlite => Ok(history_reader
|
HistoryFileFormat::Sqlite => Ok(history_reader
|
||||||
.and_then(|h| {
|
.and_then(|h| {
|
||||||
@ -104,15 +105,15 @@ impl Command for History {
|
|||||||
.ok()
|
.ok()
|
||||||
})
|
})
|
||||||
.map(move |entries| {
|
.map(move |entries| {
|
||||||
entries
|
entries.into_iter().enumerate().map(move |(idx, entry)| {
|
||||||
.into_iter()
|
create_sqlite_history_record(idx, entry, long, head)
|
||||||
.enumerate()
|
|
||||||
.map(move |(idx, entry)| create_history_record(idx, entry, long, head))
|
|
||||||
})
|
})
|
||||||
.ok_or(ShellError::FileNotFound {
|
})
|
||||||
file: history_path.display().to_string(),
|
.ok_or(IoError::new(
|
||||||
span: head,
|
std::io::ErrorKind::NotFound,
|
||||||
})?
|
head,
|
||||||
|
history_path,
|
||||||
|
))?
|
||||||
.into_pipeline_data(head, signals)),
|
.into_pipeline_data(head, signals)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,7 +139,7 @@ impl Command for History {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span) -> Value {
|
fn create_sqlite_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span) -> Value {
|
||||||
//1. Format all the values
|
//1. Format all the values
|
||||||
//2. Create a record of either short or long columns and values
|
//2. Create a record of either short or long columns and values
|
||||||
|
|
||||||
@ -149,11 +150,8 @@ fn create_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span)
|
|||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
head,
|
head,
|
||||||
);
|
);
|
||||||
let start_timestamp_value = Value::string(
|
let start_timestamp_value = Value::date(
|
||||||
entry
|
entry.start_timestamp.unwrap_or_default().fixed_offset(),
|
||||||
.start_timestamp
|
|
||||||
.map(|time| time.to_string())
|
|
||||||
.unwrap_or_default(),
|
|
||||||
head,
|
head,
|
||||||
);
|
);
|
||||||
let command_value = Value::string(entry.command_line, head);
|
let command_value = Value::string(entry.command_line, head);
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::HistoryFileFormat;
|
use nu_protocol::{
|
||||||
|
shell_error::{self, io::IoError},
|
||||||
|
HistoryFileFormat,
|
||||||
|
};
|
||||||
|
|
||||||
use reedline::{
|
use reedline::{
|
||||||
FileBackedHistory, History, HistoryItem, ReedlineError, SearchQuery, SqliteBackedHistory,
|
FileBackedHistory, History, HistoryItem, ReedlineError, SearchQuery, SqliteBackedHistory,
|
||||||
@ -18,7 +21,7 @@ impl Command for HistoryImport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Import command line history"
|
"Import command line history."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_description(&self) -> &str {
|
fn extra_description(&self) -> &str {
|
||||||
@ -35,6 +38,7 @@ Note that history item IDs are ignored when importing from file."#
|
|||||||
.category(Category::History)
|
.category(Category::History)
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::Nothing, Type::Nothing),
|
(Type::Nothing, Type::Nothing),
|
||||||
|
(Type::String, Type::Nothing),
|
||||||
(Type::List(Box::new(Type::String)), Type::Nothing),
|
(Type::List(Box::new(Type::String)), Type::Nothing),
|
||||||
(Type::table(), Type::Nothing),
|
(Type::table(), Type::Nothing),
|
||||||
])
|
])
|
||||||
@ -68,17 +72,16 @@ Note that history item IDs are ignored when importing from file."#
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let span = call.head;
|
||||||
let ok = Ok(Value::nothing(call.head).into_pipeline_data());
|
let ok = Ok(Value::nothing(call.head).into_pipeline_data());
|
||||||
|
|
||||||
let Some(history) = engine_state.history_config() else {
|
let Some(history) = engine_state.history_config() else {
|
||||||
return ok;
|
return ok;
|
||||||
};
|
};
|
||||||
let Some(current_history_path) = history.file_path() else {
|
let Some(current_history_path) = history.file_path() else {
|
||||||
return Err(ShellError::ConfigDirNotFound {
|
return Err(ShellError::ConfigDirNotFound { span: span.into() });
|
||||||
span: Some(call.head),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
if let Some(bak_path) = backup(¤t_history_path)? {
|
if let Some(bak_path) = backup(¤t_history_path, span)? {
|
||||||
println!("Backed history to {}", bak_path.display());
|
println!("Backed history to {}", bak_path.display());
|
||||||
}
|
}
|
||||||
match input {
|
match input {
|
||||||
@ -215,7 +218,7 @@ fn item_from_record(mut rec: Record, span: Span) -> Result<HistoryItem, ShellErr
|
|||||||
hostname: get(rec, fields::HOSTNAME, |v| Ok(v.as_str()?.to_owned()))?,
|
hostname: get(rec, fields::HOSTNAME, |v| Ok(v.as_str()?.to_owned()))?,
|
||||||
cwd: get(rec, fields::CWD, |v| Ok(v.as_str()?.to_owned()))?,
|
cwd: get(rec, fields::CWD, |v| Ok(v.as_str()?.to_owned()))?,
|
||||||
exit_status: get(rec, fields::EXIT_STATUS, |v| v.as_int())?,
|
exit_status: get(rec, fields::EXIT_STATUS, |v| v.as_int())?,
|
||||||
duration: get(rec, fields::DURATION, duration_from_value)?,
|
duration: get(rec, fields::DURATION, |v| duration_from_value(v, span))?,
|
||||||
more_info: None,
|
more_info: None,
|
||||||
// TODO: Currently reedline doesn't let you create session IDs.
|
// TODO: Currently reedline doesn't let you create session IDs.
|
||||||
session_id: None,
|
session_id: None,
|
||||||
@ -231,19 +234,21 @@ fn item_from_record(mut rec: Record, span: Span) -> Result<HistoryItem, ShellErr
|
|||||||
Ok(item)
|
Ok(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn duration_from_value(v: Value) -> Result<std::time::Duration, ShellError> {
|
fn duration_from_value(v: Value, span: Span) -> Result<std::time::Duration, ShellError> {
|
||||||
chrono::Duration::nanoseconds(v.as_duration()?)
|
chrono::Duration::nanoseconds(v.as_duration()?)
|
||||||
.to_std()
|
.to_std()
|
||||||
.map_err(|_| ShellError::IOError {
|
.map_err(|_| ShellError::NeedsPositiveValue { span })
|
||||||
msg: "negative duration not supported".to_string(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_backup_path(path: &Path) -> Result<PathBuf, ShellError> {
|
fn find_backup_path(path: &Path, span: Span) -> Result<PathBuf, ShellError> {
|
||||||
let Ok(mut bak_path) = path.to_path_buf().into_os_string().into_string() else {
|
let Ok(mut bak_path) = path.to_path_buf().into_os_string().into_string() else {
|
||||||
// This isn't fundamentally problem, but trying to work with OsString is a nightmare.
|
// This isn't fundamentally problem, but trying to work with OsString is a nightmare.
|
||||||
return Err(ShellError::IOError {
|
return Err(ShellError::GenericError {
|
||||||
msg: "History path mush be representable as UTF-8".to_string(),
|
error: "History path not UTF-8".to_string(),
|
||||||
|
msg: "History path must be representable as UTF-8".to_string(),
|
||||||
|
span: Some(span),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
bak_path.push_str(".bak");
|
bak_path.push_str(".bak");
|
||||||
@ -259,24 +264,45 @@ fn find_backup_path(path: &Path) -> Result<PathBuf, ShellError> {
|
|||||||
return Ok(PathBuf::from(bak_path));
|
return Ok(PathBuf::from(bak_path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(ShellError::IOError {
|
Err(ShellError::GenericError {
|
||||||
msg: "Too many existing backup files".to_string(),
|
error: "Too many backup files".to_string(),
|
||||||
|
msg: "Found too many existing backup files".to_string(),
|
||||||
|
span: Some(span),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn backup(path: &Path) -> Result<Option<PathBuf>, ShellError> {
|
fn backup(path: &Path, span: Span) -> Result<Option<PathBuf>, ShellError> {
|
||||||
match path.metadata() {
|
match path.metadata() {
|
||||||
Ok(md) if md.is_file() => (),
|
Ok(md) if md.is_file() => (),
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
return Err(ShellError::IOError {
|
return Err(IoError::new_with_additional_context(
|
||||||
msg: "history path exists but is not a file".to_string(),
|
shell_error::io::ErrorKind::NotAFile,
|
||||||
})
|
span,
|
||||||
|
PathBuf::from(path),
|
||||||
|
"history path exists but is not a file",
|
||||||
|
)
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => {
|
||||||
|
return Err(IoError::new_internal(
|
||||||
|
e.kind(),
|
||||||
|
"Could not get metadata",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
)
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
let bak_path = find_backup_path(path)?;
|
}
|
||||||
std::fs::copy(path, &bak_path)?;
|
let bak_path = find_backup_path(path, span)?;
|
||||||
|
std::fs::copy(path, &bak_path).map_err(|err| {
|
||||||
|
IoError::new_internal(
|
||||||
|
err.kind(),
|
||||||
|
"Could not copy backup",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
Ok(Some(bak_path))
|
Ok(Some(bak_path))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,7 +413,7 @@ mod tests {
|
|||||||
for name in existing {
|
for name in existing {
|
||||||
std::fs::File::create_new(dir.path().join(name)).unwrap();
|
std::fs::File::create_new(dir.path().join(name)).unwrap();
|
||||||
}
|
}
|
||||||
let got = find_backup_path(&dir.path().join("history.dat")).unwrap();
|
let got = find_backup_path(&dir.path().join("history.dat"), Span::test_data()).unwrap();
|
||||||
assert_eq!(got, dir.path().join(want))
|
assert_eq!(got, dir.path().join(want))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,7 +425,7 @@ mod tests {
|
|||||||
write!(&mut history, "123").unwrap();
|
write!(&mut history, "123").unwrap();
|
||||||
let want_bak_path = dir.path().join("history.dat.bak");
|
let want_bak_path = dir.path().join("history.dat.bak");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
backup(&dir.path().join("history.dat")),
|
backup(&dir.path().join("history.dat"), Span::test_data()),
|
||||||
Ok(Some(want_bak_path.clone()))
|
Ok(Some(want_bak_path.clone()))
|
||||||
);
|
);
|
||||||
let got_data = String::from_utf8(std::fs::read(want_bak_path).unwrap()).unwrap();
|
let got_data = String::from_utf8(std::fs::read(want_bak_path).unwrap()).unwrap();
|
||||||
@ -409,7 +435,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_backup_no_file() {
|
fn test_backup_no_file() {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
let dir = tempfile::tempdir().unwrap();
|
||||||
let bak_path = backup(&dir.path().join("history.dat")).unwrap();
|
let bak_path = backup(&dir.path().join("history.dat"), Span::test_data()).unwrap();
|
||||||
assert!(bak_path.is_none());
|
assert!(bak_path.is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ use crossterm::{
|
|||||||
event::Event, event::KeyCode, event::KeyEvent, execute, terminal, QueueableCommand,
|
event::Event, event::KeyCode, event::KeyEvent, execute, terminal, QueueableCommand,
|
||||||
};
|
};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
use nu_protocol::shell_error::io::IoError;
|
||||||
use std::io::{stdout, Write};
|
use std::io::{stdout, Write};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -39,7 +40,13 @@ impl Command for KeybindingsListen {
|
|||||||
match print_events(engine_state) {
|
match print_events(engine_state) {
|
||||||
Ok(v) => Ok(v.into_pipeline_data()),
|
Ok(v) => Ok(v.into_pipeline_data()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
terminal::disable_raw_mode()?;
|
terminal::disable_raw_mode().map_err(|err| {
|
||||||
|
IoError::new_internal(
|
||||||
|
err.kind(),
|
||||||
|
"Could not disable raw mode",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
Err(ShellError::GenericError {
|
Err(ShellError::GenericError {
|
||||||
error: "Error with input".into(),
|
error: "Error with input".into(),
|
||||||
msg: "".into(),
|
msg: "".into(),
|
||||||
@ -63,8 +70,20 @@ impl Command for KeybindingsListen {
|
|||||||
pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
|
pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
|
|
||||||
stdout().flush()?;
|
stdout().flush().map_err(|err| {
|
||||||
terminal::enable_raw_mode()?;
|
IoError::new_internal(
|
||||||
|
err.kind(),
|
||||||
|
"Could not flush stdout",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
terminal::enable_raw_mode().map_err(|err| {
|
||||||
|
IoError::new_internal(
|
||||||
|
err.kind(),
|
||||||
|
"Could not enable raw mode",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if config.use_kitty_protocol {
|
if config.use_kitty_protocol {
|
||||||
if let Ok(false) = crossterm::terminal::supports_keyboard_enhancement() {
|
if let Ok(false) = crossterm::terminal::supports_keyboard_enhancement() {
|
||||||
@ -94,7 +113,9 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
|
|||||||
let mut stdout = std::io::BufWriter::new(std::io::stderr());
|
let mut stdout = std::io::BufWriter::new(std::io::stderr());
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let event = crossterm::event::read()?;
|
let event = crossterm::event::read().map_err(|err| {
|
||||||
|
IoError::new_internal(err.kind(), "Could not read event", nu_protocol::location!())
|
||||||
|
})?;
|
||||||
if event == Event::Key(KeyCode::Esc.into()) {
|
if event == Event::Key(KeyCode::Esc.into()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -113,9 +134,25 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
|
|||||||
|
|
||||||
_ => "".to_string(),
|
_ => "".to_string(),
|
||||||
};
|
};
|
||||||
stdout.queue(crossterm::style::Print(o))?;
|
stdout.queue(crossterm::style::Print(o)).map_err(|err| {
|
||||||
stdout.queue(crossterm::style::Print("\r\n"))?;
|
IoError::new_internal(
|
||||||
stdout.flush()?;
|
err.kind(),
|
||||||
|
"Could not print output record",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
stdout
|
||||||
|
.queue(crossterm::style::Print("\r\n"))
|
||||||
|
.map_err(|err| {
|
||||||
|
IoError::new_internal(
|
||||||
|
err.kind(),
|
||||||
|
"Could not print linebreak",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
stdout.flush().map_err(|err| {
|
||||||
|
IoError::new_internal(err.kind(), "Could not flush", nu_protocol::location!())
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.use_kitty_protocol {
|
if config.use_kitty_protocol {
|
||||||
@ -125,7 +162,13 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
terminal::disable_raw_mode()?;
|
terminal::disable_raw_mode().map_err(|err| {
|
||||||
|
IoError::new_internal(
|
||||||
|
err.kind(),
|
||||||
|
"Could not disable raw mode",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(Value::nothing(Span::unknown()))
|
Ok(Value::nothing(Span::unknown()))
|
||||||
}
|
}
|
||||||
|
87
crates/nu-cli/src/completions/attribute_completions.rs
Normal file
87
crates/nu-cli/src/completions/attribute_completions.rs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
use super::{completion_options::NuMatcher, SemanticSuggestion};
|
||||||
|
use crate::{
|
||||||
|
completions::{Completer, CompletionOptions},
|
||||||
|
SuggestionKind,
|
||||||
|
};
|
||||||
|
use nu_protocol::{
|
||||||
|
engine::{Stack, StateWorkingSet},
|
||||||
|
Span,
|
||||||
|
};
|
||||||
|
use reedline::Suggestion;
|
||||||
|
|
||||||
|
pub struct AttributeCompletion;
|
||||||
|
pub struct AttributableCompletion;
|
||||||
|
|
||||||
|
impl Completer for AttributeCompletion {
|
||||||
|
fn fetch(
|
||||||
|
&mut self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
_stack: &Stack,
|
||||||
|
prefix: impl AsRef<str>,
|
||||||
|
span: Span,
|
||||||
|
offset: usize,
|
||||||
|
options: &CompletionOptions,
|
||||||
|
) -> Vec<SemanticSuggestion> {
|
||||||
|
let mut matcher = NuMatcher::new(prefix, options);
|
||||||
|
|
||||||
|
let attr_commands =
|
||||||
|
working_set.find_commands_by_predicate(|s| s.starts_with(b"attr "), true);
|
||||||
|
|
||||||
|
for (decl_id, name, desc, ty) in attr_commands {
|
||||||
|
let name = name.strip_prefix(b"attr ").unwrap_or(&name);
|
||||||
|
matcher.add_semantic_suggestion(SemanticSuggestion {
|
||||||
|
suggestion: Suggestion {
|
||||||
|
value: String::from_utf8_lossy(name).into_owned(),
|
||||||
|
description: desc,
|
||||||
|
style: None,
|
||||||
|
extra: None,
|
||||||
|
span: reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
},
|
||||||
|
append_whitespace: false,
|
||||||
|
},
|
||||||
|
kind: Some(SuggestionKind::Command(ty, Some(decl_id))),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
matcher.results()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Completer for AttributableCompletion {
|
||||||
|
fn fetch(
|
||||||
|
&mut self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
_stack: &Stack,
|
||||||
|
prefix: impl AsRef<str>,
|
||||||
|
span: Span,
|
||||||
|
offset: usize,
|
||||||
|
options: &CompletionOptions,
|
||||||
|
) -> Vec<SemanticSuggestion> {
|
||||||
|
let mut matcher = NuMatcher::new(prefix, options);
|
||||||
|
|
||||||
|
for s in ["def", "extern", "export def", "export extern"] {
|
||||||
|
let decl_id = working_set
|
||||||
|
.find_decl(s.as_bytes())
|
||||||
|
.expect("internal error, builtin declaration not found");
|
||||||
|
let cmd = working_set.get_decl(decl_id);
|
||||||
|
matcher.add_semantic_suggestion(SemanticSuggestion {
|
||||||
|
suggestion: Suggestion {
|
||||||
|
value: cmd.name().into(),
|
||||||
|
description: Some(cmd.description().into()),
|
||||||
|
style: None,
|
||||||
|
extra: None,
|
||||||
|
span: reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
},
|
||||||
|
append_whitespace: false,
|
||||||
|
},
|
||||||
|
kind: Some(SuggestionKind::Command(cmd.command_type(), None)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
matcher.results()
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
use crate::completions::CompletionOptions;
|
use crate::completions::CompletionOptions;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{Stack, StateWorkingSet},
|
engine::{Stack, StateWorkingSet},
|
||||||
Span,
|
DeclId, Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
|
|
||||||
@ -12,10 +12,9 @@ pub trait Completer {
|
|||||||
&mut self,
|
&mut self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
prefix: &[u8],
|
prefix: impl AsRef<str>,
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
pos: usize,
|
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<SemanticSuggestion>;
|
) -> Vec<SemanticSuggestion>;
|
||||||
}
|
}
|
||||||
@ -29,8 +28,15 @@ pub struct SemanticSuggestion {
|
|||||||
// TODO: think about name: maybe suggestion context?
|
// TODO: think about name: maybe suggestion context?
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum SuggestionKind {
|
pub enum SuggestionKind {
|
||||||
Command(nu_protocol::engine::CommandType),
|
Command(nu_protocol::engine::CommandType, Option<DeclId>),
|
||||||
Type(nu_protocol::Type),
|
Value(nu_protocol::Type),
|
||||||
|
CellPath,
|
||||||
|
Directory,
|
||||||
|
File,
|
||||||
|
Flag,
|
||||||
|
Module,
|
||||||
|
Operator,
|
||||||
|
Variable,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Suggestion> for SemanticSuggestion {
|
impl From<Suggestion> for SemanticSuggestion {
|
||||||
|
149
crates/nu-cli/src/completions/cell_path_completions.rs
Normal file
149
crates/nu-cli/src/completions/cell_path_completions.rs
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
use crate::completions::{Completer, CompletionOptions, SemanticSuggestion, SuggestionKind};
|
||||||
|
use nu_engine::{column::get_columns, eval_variable};
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::{Expr, Expression, FullCellPath, PathMember},
|
||||||
|
engine::{Stack, StateWorkingSet},
|
||||||
|
eval_const::eval_constant,
|
||||||
|
ShellError, Span, Value,
|
||||||
|
};
|
||||||
|
use reedline::Suggestion;
|
||||||
|
|
||||||
|
use super::completion_options::NuMatcher;
|
||||||
|
|
||||||
|
pub struct CellPathCompletion<'a> {
|
||||||
|
pub full_cell_path: &'a FullCellPath,
|
||||||
|
pub position: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prefix_from_path_member(member: &PathMember, pos: usize) -> (String, Span) {
|
||||||
|
let (prefix_str, start) = match member {
|
||||||
|
PathMember::String { val, span, .. } => (val, span.start),
|
||||||
|
PathMember::Int { val, span, .. } => (&val.to_string(), span.start),
|
||||||
|
};
|
||||||
|
let prefix_str = prefix_str.get(..pos + 1 - start).unwrap_or(prefix_str);
|
||||||
|
// strip wrapping quotes
|
||||||
|
let quotations = ['"', '\'', '`'];
|
||||||
|
let prefix_str = prefix_str.strip_prefix(quotations).unwrap_or(prefix_str);
|
||||||
|
(prefix_str.to_string(), Span::new(start, pos + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Completer for CellPathCompletion<'_> {
|
||||||
|
fn fetch(
|
||||||
|
&mut self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
stack: &Stack,
|
||||||
|
_prefix: impl AsRef<str>,
|
||||||
|
_span: Span,
|
||||||
|
offset: usize,
|
||||||
|
options: &CompletionOptions,
|
||||||
|
) -> Vec<SemanticSuggestion> {
|
||||||
|
let mut prefix_str = String::new();
|
||||||
|
// position at dots, e.g. `$env.config.<TAB>`
|
||||||
|
let mut span = Span::new(self.position + 1, self.position + 1);
|
||||||
|
let mut path_member_num_before_pos = 0;
|
||||||
|
for member in self.full_cell_path.tail.iter() {
|
||||||
|
if member.span().end <= self.position {
|
||||||
|
path_member_num_before_pos += 1;
|
||||||
|
} else if member.span().contains(self.position) {
|
||||||
|
(prefix_str, span) = prefix_from_path_member(member, self.position);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_span = reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut matcher = NuMatcher::new(prefix_str, options);
|
||||||
|
let path_members = self
|
||||||
|
.full_cell_path
|
||||||
|
.tail
|
||||||
|
.get(0..path_member_num_before_pos)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let value = eval_cell_path(
|
||||||
|
working_set,
|
||||||
|
stack,
|
||||||
|
&self.full_cell_path.head,
|
||||||
|
path_members,
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
for suggestion in get_suggestions_by_value(&value, current_span) {
|
||||||
|
matcher.add_semantic_suggestion(suggestion);
|
||||||
|
}
|
||||||
|
matcher.results()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Follow cell path to get the value
|
||||||
|
/// NOTE: This is a relatively lightweight implementation,
|
||||||
|
/// so it may fail to get the exact value when the expression is complicated.
|
||||||
|
/// One failing example would be `[$foo].0`
|
||||||
|
pub(crate) fn eval_cell_path(
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
stack: &Stack,
|
||||||
|
head: &Expression,
|
||||||
|
path_members: &[PathMember],
|
||||||
|
span: Span,
|
||||||
|
) -> Result<Value, ShellError> {
|
||||||
|
// evaluate the head expression to get its value
|
||||||
|
let head_value = if let Expr::Var(var_id) = head.expr {
|
||||||
|
working_set
|
||||||
|
.get_variable(var_id)
|
||||||
|
.const_val
|
||||||
|
.to_owned()
|
||||||
|
.map_or_else(
|
||||||
|
|| eval_variable(working_set.permanent_state, stack, var_id, span),
|
||||||
|
Ok,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
eval_constant(working_set, head)
|
||||||
|
}?;
|
||||||
|
head_value.follow_cell_path(path_members, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_suggestions_by_value(
|
||||||
|
value: &Value,
|
||||||
|
current_span: reedline::Span,
|
||||||
|
) -> Vec<SemanticSuggestion> {
|
||||||
|
let to_suggestion = |s: String, v: Option<&Value>| {
|
||||||
|
// Check if the string needs quoting
|
||||||
|
let value = if s.is_empty()
|
||||||
|
|| s.chars()
|
||||||
|
.any(|c: char| !(c.is_ascii_alphabetic() || ['_', '-'].contains(&c)))
|
||||||
|
{
|
||||||
|
format!("{:?}", s)
|
||||||
|
} else {
|
||||||
|
s
|
||||||
|
};
|
||||||
|
|
||||||
|
SemanticSuggestion {
|
||||||
|
suggestion: Suggestion {
|
||||||
|
value,
|
||||||
|
span: current_span,
|
||||||
|
description: v.map(|v| v.get_type().to_string()),
|
||||||
|
..Suggestion::default()
|
||||||
|
},
|
||||||
|
kind: Some(SuggestionKind::CellPath),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match value {
|
||||||
|
Value::Record { val, .. } => val
|
||||||
|
.columns()
|
||||||
|
.map(|s| to_suggestion(s.to_string(), val.get(s)))
|
||||||
|
.collect(),
|
||||||
|
Value::List { vals, .. } => get_columns(vals.as_slice())
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| {
|
||||||
|
let sub_val = vals
|
||||||
|
.first()
|
||||||
|
.and_then(|v| v.as_record().ok())
|
||||||
|
.and_then(|rv| rv.get(&s));
|
||||||
|
to_suggestion(s, sub_val)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
_ => vec![],
|
||||||
|
}
|
||||||
|
}
|
@ -4,9 +4,8 @@ use crate::{
|
|||||||
completions::{Completer, CompletionOptions},
|
completions::{Completer, CompletionOptions},
|
||||||
SuggestionKind,
|
SuggestionKind,
|
||||||
};
|
};
|
||||||
use nu_parser::FlatShape;
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{CachedFile, Stack, StateWorkingSet},
|
engine::{CommandType, Stack, StateWorkingSet},
|
||||||
Span,
|
Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
@ -14,24 +13,13 @@ use reedline::Suggestion;
|
|||||||
use super::{completion_options::NuMatcher, SemanticSuggestion};
|
use super::{completion_options::NuMatcher, SemanticSuggestion};
|
||||||
|
|
||||||
pub struct CommandCompletion {
|
pub struct CommandCompletion {
|
||||||
flattened: Vec<(Span, FlatShape)>,
|
/// Whether to include internal commands
|
||||||
flat_shape: FlatShape,
|
pub internals: bool,
|
||||||
force_completion_after_space: bool,
|
/// Whether to include external commands
|
||||||
|
pub externals: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandCompletion {
|
impl CommandCompletion {
|
||||||
pub fn new(
|
|
||||||
flattened: Vec<(Span, FlatShape)>,
|
|
||||||
flat_shape: FlatShape,
|
|
||||||
force_completion_after_space: bool,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
flattened,
|
|
||||||
flat_shape,
|
|
||||||
force_completion_after_space,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn external_command_completion(
|
fn external_command_completion(
|
||||||
&self,
|
&self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
@ -43,7 +31,7 @@ impl CommandCompletion {
|
|||||||
|
|
||||||
let paths = working_set.permanent_state.get_env_var_insensitive("path");
|
let paths = working_set.permanent_state.get_env_var_insensitive("path");
|
||||||
|
|
||||||
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.coerce_str().unwrap_or_default();
|
let path = path.coerce_str().unwrap_or_default();
|
||||||
@ -71,6 +59,9 @@ impl CommandCompletion {
|
|||||||
if suggs.contains_key(&value) {
|
if suggs.contains_key(&value) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// TODO: check name matching before a relative heavy IO involved
|
||||||
|
// `is_executable` for performance consideration, should avoid
|
||||||
|
// duplicated `match_aux` call for matched items in the future
|
||||||
if matcher.matches(&name) && is_executable::is_executable(item.path()) {
|
if matcher.matches(&name) && is_executable::is_executable(item.path()) {
|
||||||
// If there's an internal command with the same name, adds ^cmd to the
|
// If there's an internal command with the same name, adds ^cmd to the
|
||||||
// matcher so that both the internal and external command are included
|
// matcher so that both the internal and external command are included
|
||||||
@ -84,8 +75,10 @@ impl CommandCompletion {
|
|||||||
append_whitespace: true,
|
append_whitespace: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// TODO: is there a way to create a test?
|
kind: Some(SuggestionKind::Command(
|
||||||
kind: None,
|
CommandType::External,
|
||||||
|
None,
|
||||||
|
)),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -97,21 +90,24 @@ impl CommandCompletion {
|
|||||||
|
|
||||||
suggs
|
suggs
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn complete_commands(
|
impl Completer for CommandCompletion {
|
||||||
&self,
|
fn fetch(
|
||||||
|
&mut self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
|
_stack: &Stack,
|
||||||
|
prefix: impl AsRef<str>,
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
find_externals: bool,
|
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let partial = working_set.get_span_contents(span);
|
let mut matcher = NuMatcher::new(prefix, options);
|
||||||
let mut matcher = NuMatcher::new(String::from_utf8_lossy(partial), options.clone());
|
|
||||||
|
|
||||||
let sugg_span = reedline::Span::new(span.start - offset, span.end - offset);
|
let sugg_span = reedline::Span::new(span.start - offset, span.end - offset);
|
||||||
|
|
||||||
let mut internal_suggs = HashMap::new();
|
let mut internal_suggs = HashMap::new();
|
||||||
|
if self.internals {
|
||||||
let filtered_commands = working_set.find_commands_by_predicate(
|
let filtered_commands = working_set.find_commands_by_predicate(
|
||||||
|name| {
|
|name| {
|
||||||
let name = String::from_utf8_lossy(name);
|
let name = String::from_utf8_lossy(name);
|
||||||
@ -119,7 +115,7 @@ impl CommandCompletion {
|
|||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
for (name, description, typ) in filtered_commands {
|
for (decl_id, name, description, typ) in filtered_commands {
|
||||||
let name = String::from_utf8_lossy(&name);
|
let name = String::from_utf8_lossy(&name);
|
||||||
internal_suggs.insert(
|
internal_suggs.insert(
|
||||||
name.to_string(),
|
name.to_string(),
|
||||||
@ -131,12 +127,13 @@ impl CommandCompletion {
|
|||||||
append_whitespace: true,
|
append_whitespace: true,
|
||||||
..Suggestion::default()
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
kind: Some(SuggestionKind::Command(typ)),
|
kind: Some(SuggestionKind::Command(typ, Some(decl_id))),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut external_suggs = if find_externals {
|
let mut external_suggs = if self.externals {
|
||||||
self.external_command_completion(
|
self.external_command_completion(
|
||||||
working_set,
|
working_set,
|
||||||
sugg_span,
|
sugg_span,
|
||||||
@ -159,179 +156,3 @@ impl CommandCompletion {
|
|||||||
res
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Completer for CommandCompletion {
|
|
||||||
fn fetch(
|
|
||||||
&mut self,
|
|
||||||
working_set: &StateWorkingSet,
|
|
||||||
_stack: &Stack,
|
|
||||||
_prefix: &[u8],
|
|
||||||
span: Span,
|
|
||||||
offset: usize,
|
|
||||||
pos: usize,
|
|
||||||
options: &CompletionOptions,
|
|
||||||
) -> Vec<SemanticSuggestion> {
|
|
||||||
let last = self
|
|
||||||
.flattened
|
|
||||||
.iter()
|
|
||||||
.rev()
|
|
||||||
.skip_while(|x| x.0.end > pos)
|
|
||||||
.take_while(|x| {
|
|
||||||
matches!(
|
|
||||||
x.1,
|
|
||||||
FlatShape::InternalCall(_)
|
|
||||||
| FlatShape::External
|
|
||||||
| FlatShape::ExternalArg
|
|
||||||
| FlatShape::Literal
|
|
||||||
| FlatShape::String
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.last();
|
|
||||||
|
|
||||||
// The last item here would be the earliest shape that could possible by part of this subcommand
|
|
||||||
let subcommands = if let Some(last) = last {
|
|
||||||
self.complete_commands(
|
|
||||||
working_set,
|
|
||||||
Span::new(last.0.start, pos),
|
|
||||||
offset,
|
|
||||||
false,
|
|
||||||
options,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
};
|
|
||||||
|
|
||||||
if !subcommands.is_empty() {
|
|
||||||
return subcommands;
|
|
||||||
}
|
|
||||||
|
|
||||||
let config = working_set.get_config();
|
|
||||||
if matches!(self.flat_shape, nu_parser::FlatShape::External)
|
|
||||||
|| matches!(self.flat_shape, nu_parser::FlatShape::InternalCall(_))
|
|
||||||
|| ((span.end - span.start) == 0)
|
|
||||||
|| is_passthrough_command(working_set.delta.get_file_contents())
|
|
||||||
{
|
|
||||||
// we're in a gap or at a command
|
|
||||||
if working_set.get_span_contents(span).is_empty() && !self.force_completion_after_space
|
|
||||||
{
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
self.complete_commands(
|
|
||||||
working_set,
|
|
||||||
span,
|
|
||||||
offset,
|
|
||||||
config.completions.external.enable,
|
|
||||||
options,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_non_whitespace_index(contents: &[u8], start: usize) -> usize {
|
|
||||||
match contents.get(start..) {
|
|
||||||
Some(contents) => {
|
|
||||||
contents
|
|
||||||
.iter()
|
|
||||||
.take_while(|x| x.is_ascii_whitespace())
|
|
||||||
.count()
|
|
||||||
+ start
|
|
||||||
}
|
|
||||||
None => start,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_passthrough_command(working_set_file_contents: &[CachedFile]) -> bool {
|
|
||||||
for cached_file in working_set_file_contents {
|
|
||||||
let contents = &cached_file.content;
|
|
||||||
let last_pipe_pos_rev = contents.iter().rev().position(|x| x == &b'|');
|
|
||||||
let last_pipe_pos = last_pipe_pos_rev.map(|x| contents.len() - x).unwrap_or(0);
|
|
||||||
|
|
||||||
let cur_pos = find_non_whitespace_index(contents, last_pipe_pos);
|
|
||||||
|
|
||||||
let result = match contents.get(cur_pos..) {
|
|
||||||
Some(contents) => contents.starts_with(b"sudo ") || contents.starts_with(b"doas "),
|
|
||||||
None => false,
|
|
||||||
};
|
|
||||||
if result {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod command_completions_tests {
|
|
||||||
use super::*;
|
|
||||||
use nu_protocol::engine::EngineState;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_find_non_whitespace_index() {
|
|
||||||
let commands = [
|
|
||||||
(" hello", 4),
|
|
||||||
("sudo ", 0),
|
|
||||||
(" sudo ", 2),
|
|
||||||
(" sudo ", 2),
|
|
||||||
(" hello ", 1),
|
|
||||||
(" hello ", 3),
|
|
||||||
(" hello | sudo ", 4),
|
|
||||||
(" sudo|sudo", 5),
|
|
||||||
("sudo | sudo ", 0),
|
|
||||||
(" hello sud", 1),
|
|
||||||
];
|
|
||||||
for (idx, ele) in commands.iter().enumerate() {
|
|
||||||
let index = find_non_whitespace_index(ele.0.as_bytes(), 0);
|
|
||||||
assert_eq!(index, ele.1, "Failed on index {}", idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_is_last_command_passthrough() {
|
|
||||||
let commands = [
|
|
||||||
(" hello", false),
|
|
||||||
(" sudo ", true),
|
|
||||||
("sudo ", true),
|
|
||||||
(" hello", false),
|
|
||||||
(" sudo", false),
|
|
||||||
(" sudo ", true),
|
|
||||||
(" sudo ", true),
|
|
||||||
(" sudo ", true),
|
|
||||||
(" hello ", false),
|
|
||||||
(" hello | sudo ", true),
|
|
||||||
(" sudo|sudo", false),
|
|
||||||
("sudo | sudo ", true),
|
|
||||||
(" hello sud", false),
|
|
||||||
(" sudo | sud ", false),
|
|
||||||
(" sudo|sudo ", true),
|
|
||||||
(" sudo | sudo ls | sudo ", true),
|
|
||||||
];
|
|
||||||
for (idx, ele) in commands.iter().enumerate() {
|
|
||||||
let input = ele.0.as_bytes();
|
|
||||||
|
|
||||||
let mut engine_state = EngineState::new();
|
|
||||||
engine_state.add_file("test.nu".into(), Arc::new([]));
|
|
||||||
|
|
||||||
let delta = {
|
|
||||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
||||||
let _ = working_set.add_file("child.nu".into(), input);
|
|
||||||
working_set.render()
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = engine_state.merge_delta(delta);
|
|
||||||
assert!(
|
|
||||||
result.is_ok(),
|
|
||||||
"Merge delta has failed: {}",
|
|
||||||
result.err().unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
let is_passthrough_command = is_passthrough_command(engine_state.get_file_contents());
|
|
||||||
assert_eq!(
|
|
||||||
is_passthrough_command, ele.1,
|
|
||||||
"index for '{}': {}",
|
|
||||||
ele.0, idx
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -22,18 +22,22 @@ pub struct PathBuiltFromString {
|
|||||||
/// Recursively goes through paths that match a given `partial`.
|
/// Recursively goes through paths that match a given `partial`.
|
||||||
/// built: State struct for a valid matching path built so far.
|
/// built: State struct for a valid matching path built so far.
|
||||||
///
|
///
|
||||||
|
/// `want_directory`: Whether we want only directories as completion matches.
|
||||||
|
/// Some commands like `cd` can only be run on directories whereas others
|
||||||
|
/// like `ls` can be run on regular files as well.
|
||||||
|
///
|
||||||
/// `isdir`: whether the current partial path has a trailing slash.
|
/// `isdir`: whether the current partial path has a trailing slash.
|
||||||
/// Parsing a path string into a pathbuf loses that bit of information.
|
/// Parsing a path string into a pathbuf loses that bit of information.
|
||||||
///
|
///
|
||||||
/// want_directory: Whether we want only directories as completion matches.
|
/// `enable_exact_match`: Whether match algorithm is Prefix and all previous components
|
||||||
/// Some commands like `cd` can only be run on directories whereas others
|
/// of the path matched a directory exactly.
|
||||||
/// like `ls` can be run on regular files as well.
|
|
||||||
fn complete_rec(
|
fn complete_rec(
|
||||||
partial: &[&str],
|
partial: &[&str],
|
||||||
built_paths: &[PathBuiltFromString],
|
built_paths: &[PathBuiltFromString],
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
want_directory: bool,
|
want_directory: bool,
|
||||||
isdir: bool,
|
isdir: bool,
|
||||||
|
enable_exact_match: bool,
|
||||||
) -> Vec<PathBuiltFromString> {
|
) -> Vec<PathBuiltFromString> {
|
||||||
if let Some((&base, rest)) = partial.split_first() {
|
if let Some((&base, rest)) = partial.split_first() {
|
||||||
if base.chars().all(|c| c == '.') && (isdir || !rest.is_empty()) {
|
if base.chars().all(|c| c == '.') && (isdir || !rest.is_empty()) {
|
||||||
@ -46,12 +50,19 @@ fn complete_rec(
|
|||||||
built
|
built
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
return complete_rec(rest, &built_paths, options, want_directory, isdir);
|
return complete_rec(
|
||||||
|
rest,
|
||||||
|
&built_paths,
|
||||||
|
options,
|
||||||
|
want_directory,
|
||||||
|
isdir,
|
||||||
|
enable_exact_match,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let prefix = partial.first().unwrap_or(&"");
|
let prefix = partial.first().unwrap_or(&"");
|
||||||
let mut matcher = NuMatcher::new(prefix, options.clone());
|
let mut matcher = NuMatcher::new(prefix, options);
|
||||||
|
|
||||||
for built in built_paths {
|
for built in built_paths {
|
||||||
let mut path = built.cwd.clone();
|
let mut path = built.cwd.clone();
|
||||||
@ -68,7 +79,8 @@ fn complete_rec(
|
|||||||
let entry_isdir = entry.path().is_dir();
|
let entry_isdir = entry.path().is_dir();
|
||||||
let mut built = built.clone();
|
let mut built = built.clone();
|
||||||
built.parts.push(entry_name.clone());
|
built.parts.push(entry_name.clone());
|
||||||
built.isdir = entry_isdir;
|
// Symlinks to directories shouldn't have a trailing slash (#13275)
|
||||||
|
built.isdir = entry_isdir && !entry.path().is_symlink();
|
||||||
|
|
||||||
if !want_directory || entry_isdir {
|
if !want_directory || entry_isdir {
|
||||||
matcher.add(entry_name.clone(), (entry_name, built));
|
matcher.add(entry_name.clone(), (entry_name, built));
|
||||||
@ -85,27 +97,26 @@ fn complete_rec(
|
|||||||
// Serves as confirmation to ignore longer completions for
|
// Serves as confirmation to ignore longer completions for
|
||||||
// components in between.
|
// components in between.
|
||||||
if !rest.is_empty() || isdir {
|
if !rest.is_empty() || isdir {
|
||||||
|
// Don't show longer completions if we have an exact match (#13204, #14794)
|
||||||
|
let exact_match = enable_exact_match
|
||||||
|
&& (if options.case_sensitive {
|
||||||
|
entry_name.eq(base)
|
||||||
|
} else {
|
||||||
|
entry_name.eq_ignore_case(base)
|
||||||
|
});
|
||||||
completions.extend(complete_rec(
|
completions.extend(complete_rec(
|
||||||
rest,
|
rest,
|
||||||
&[built],
|
&[built],
|
||||||
options,
|
options,
|
||||||
want_directory,
|
want_directory,
|
||||||
isdir,
|
isdir,
|
||||||
|
exact_match,
|
||||||
));
|
));
|
||||||
} else {
|
|
||||||
completions.push(built);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For https://github.com/nushell/nushell/issues/13204
|
|
||||||
if isdir && options.match_algorithm == MatchAlgorithm::Prefix {
|
|
||||||
let exact_match = if options.case_sensitive {
|
|
||||||
entry_name.eq(base)
|
|
||||||
} else {
|
|
||||||
entry_name.to_folded_case().eq(&base.to_folded_case())
|
|
||||||
};
|
|
||||||
if exact_match {
|
if exact_match {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
completions.push(built);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
@ -139,7 +150,7 @@ impl OriginalCwd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn surround_remove(partial: &str) -> String {
|
pub fn surround_remove(partial: &str) -> String {
|
||||||
for c in ['`', '"', '\''] {
|
for c in ['`', '"', '\''] {
|
||||||
if partial.starts_with(c) {
|
if partial.starts_with(c) {
|
||||||
let ret = partial.strip_prefix(c).unwrap_or(partial);
|
let ret = partial.strip_prefix(c).unwrap_or(partial);
|
||||||
@ -157,7 +168,7 @@ pub struct FileSuggestion {
|
|||||||
pub span: nu_protocol::Span,
|
pub span: nu_protocol::Span,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
pub style: Option<Style>,
|
pub style: Option<Style>,
|
||||||
pub cwd: PathBuf,
|
pub is_dir: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
@ -196,12 +207,11 @@ pub fn complete_item(
|
|||||||
.map(|cwd| Path::new(cwd.as_ref()).to_path_buf())
|
.map(|cwd| Path::new(cwd.as_ref()).to_path_buf())
|
||||||
.collect();
|
.collect();
|
||||||
let ls_colors = (engine_state.config.completions.use_ls_colors
|
let ls_colors = (engine_state.config.completions.use_ls_colors
|
||||||
&& engine_state.config.use_ansi_coloring)
|
&& engine_state.config.use_ansi_coloring.get(engine_state))
|
||||||
.then(|| {
|
.then(|| {
|
||||||
let ls_colors_env_str = match stack.get_env_var(engine_state, "LS_COLORS") {
|
let ls_colors_env_str = stack
|
||||||
Some(v) => env_to_string("LS_COLORS", v, engine_state, stack).ok(),
|
.get_env_var(engine_state, "LS_COLORS")
|
||||||
None => None,
|
.and_then(|v| env_to_string("LS_COLORS", v, engine_state, stack).ok());
|
||||||
};
|
|
||||||
get_ls_colors(ls_colors_env_str)
|
get_ls_colors(ls_colors_env_str)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -255,59 +265,58 @@ pub fn complete_item(
|
|||||||
options,
|
options,
|
||||||
want_directory,
|
want_directory,
|
||||||
isdir,
|
isdir,
|
||||||
|
options.match_algorithm == MatchAlgorithm::Prefix,
|
||||||
)
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mut p| {
|
.map(|mut p| {
|
||||||
if should_collapse_dots {
|
if should_collapse_dots {
|
||||||
p = collapse_ndots(p);
|
p = collapse_ndots(p);
|
||||||
}
|
}
|
||||||
let cwd = p.cwd.clone();
|
let is_dir = p.isdir;
|
||||||
let path = original_cwd.apply(p, path_separator);
|
let path = original_cwd.apply(p, path_separator);
|
||||||
|
let real_path = expand_to_real_path(&path);
|
||||||
|
let metadata = std::fs::symlink_metadata(&real_path).ok();
|
||||||
let style = ls_colors.as_ref().map(|lsc| {
|
let style = ls_colors.as_ref().map(|lsc| {
|
||||||
lsc.style_for_path_with_metadata(
|
lsc.style_for_path_with_metadata(&real_path, metadata.as_ref())
|
||||||
&path,
|
|
||||||
std::fs::symlink_metadata(expand_to_real_path(&path))
|
|
||||||
.ok()
|
|
||||||
.as_ref(),
|
|
||||||
)
|
|
||||||
.map(lscolors::Style::to_nu_ansi_term_style)
|
.map(lscolors::Style::to_nu_ansi_term_style)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
});
|
});
|
||||||
FileSuggestion {
|
FileSuggestion {
|
||||||
span,
|
span,
|
||||||
path: escape_path(path, want_directory),
|
path: escape_path(path),
|
||||||
style,
|
style,
|
||||||
cwd,
|
is_dir,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix files or folders with quotes or hashes
|
// Fix files or folders with quotes or hashes
|
||||||
pub fn escape_path(path: String, dir: bool) -> String {
|
pub fn escape_path(path: String) -> String {
|
||||||
// make glob pattern have the highest priority.
|
// make glob pattern have the highest priority.
|
||||||
let glob_contaminated = path.contains(['[', '*', ']', '?']);
|
if nu_glob::is_glob(path.as_str()) || path.contains('`') {
|
||||||
if glob_contaminated {
|
// expand home `~` for https://github.com/nushell/nushell/issues/13905
|
||||||
return if path.contains('\'') {
|
let pathbuf = nu_path::expand_tilde(path);
|
||||||
// decide to use double quote, also need to escape `"` in path
|
let path = pathbuf.to_string_lossy();
|
||||||
// or else users can't do anything with completed path either.
|
if path.contains('\'') {
|
||||||
format!("\"{}\"", path.replace('"', r#"\""#))
|
// decide to use double quotes
|
||||||
|
// Path as Debug will do the escaping for `"`, `\`
|
||||||
|
format!("{:?}", path)
|
||||||
} else {
|
} else {
|
||||||
format!("'{path}'")
|
format!("'{path}'")
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
let filename_contaminated = !dir && path.contains(['\'', '"', ' ', '#', '(', ')']);
|
let contaminated =
|
||||||
let dirname_contaminated = dir && path.contains(['\'', '"', ' ', '#']);
|
path.contains(['\'', '"', ' ', '#', '(', ')', '{', '}', '[', ']', '|', ';']);
|
||||||
let maybe_flag = path.starts_with('-');
|
let maybe_flag = path.starts_with('-');
|
||||||
let maybe_variable = path.starts_with('$');
|
let maybe_variable = path.starts_with('$');
|
||||||
let maybe_number = path.parse::<f64>().is_ok();
|
let maybe_number = path.parse::<f64>().is_ok();
|
||||||
if filename_contaminated || dirname_contaminated || maybe_flag || maybe_variable || maybe_number
|
if contaminated || maybe_flag || maybe_variable || maybe_number {
|
||||||
{
|
|
||||||
format!("`{path}`")
|
format!("`{path}`")
|
||||||
} else {
|
} else {
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AdjustView {
|
pub struct AdjustView {
|
||||||
@ -317,12 +326,12 @@ pub struct AdjustView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn adjust_if_intermediate(
|
pub fn adjust_if_intermediate(
|
||||||
prefix: &[u8],
|
prefix: &str,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
mut span: nu_protocol::Span,
|
mut span: nu_protocol::Span,
|
||||||
) -> AdjustView {
|
) -> AdjustView {
|
||||||
let span_contents = String::from_utf8_lossy(working_set.get_span_contents(span)).to_string();
|
let span_contents = String::from_utf8_lossy(working_set.get_span_contents(span)).to_string();
|
||||||
let mut prefix = String::from_utf8_lossy(prefix).to_string();
|
let mut prefix = prefix.to_string();
|
||||||
|
|
||||||
// A difference of 1 because of the cursor's unicode code point in between.
|
// A difference of 1 because of the cursor's unicode code point in between.
|
||||||
// Using .chars().count() because unicode and Windows.
|
// Using .chars().count() because unicode and Windows.
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
|
||||||
use nu_parser::trim_quotes_str;
|
use nu_parser::trim_quotes_str;
|
||||||
use nu_protocol::{CompletionAlgorithm, CompletionSort};
|
use nu_protocol::{CompletionAlgorithm, CompletionSort};
|
||||||
use nu_utils::IgnoreCaseExt;
|
use nu_utils::IgnoreCaseExt;
|
||||||
|
use nucleo_matcher::{
|
||||||
|
pattern::{Atom, AtomKind, CaseMatching, Normalization},
|
||||||
|
Config, Matcher, Utf32Str,
|
||||||
|
};
|
||||||
use std::{borrow::Cow, fmt::Display};
|
use std::{borrow::Cow, fmt::Display};
|
||||||
|
|
||||||
use super::SemanticSuggestion;
|
use super::SemanticSuggestion;
|
||||||
@ -15,6 +18,12 @@ pub enum MatchAlgorithm {
|
|||||||
/// "git switch" is matched by "git sw"
|
/// "git switch" is matched by "git sw"
|
||||||
Prefix,
|
Prefix,
|
||||||
|
|
||||||
|
/// Only show suggestions which have a substring matching with the given input
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// "git checkout" is matched by "checkout"
|
||||||
|
Substring,
|
||||||
|
|
||||||
/// Only show suggestions which contain the input chars at any place
|
/// Only show suggestions which contain the input chars at any place
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
@ -22,8 +31,8 @@ pub enum MatchAlgorithm {
|
|||||||
Fuzzy,
|
Fuzzy,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NuMatcher<T> {
|
pub struct NuMatcher<'a, T> {
|
||||||
options: CompletionOptions,
|
options: &'a CompletionOptions,
|
||||||
needle: String,
|
needle: String,
|
||||||
state: State<T>,
|
state: State<T>,
|
||||||
}
|
}
|
||||||
@ -33,43 +42,68 @@ enum State<T> {
|
|||||||
/// Holds (haystack, item)
|
/// Holds (haystack, item)
|
||||||
items: Vec<(String, T)>,
|
items: Vec<(String, T)>,
|
||||||
},
|
},
|
||||||
|
Substring {
|
||||||
|
/// Holds (haystack, item)
|
||||||
|
items: Vec<(String, T)>,
|
||||||
|
},
|
||||||
Fuzzy {
|
Fuzzy {
|
||||||
matcher: Box<SkimMatcherV2>,
|
matcher: Matcher,
|
||||||
|
atom: Atom,
|
||||||
/// Holds (haystack, item, score)
|
/// Holds (haystack, item, score)
|
||||||
items: Vec<(String, T, i64)>,
|
items: Vec<(String, T, u16)>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Filters and sorts suggestions
|
/// Filters and sorts suggestions
|
||||||
impl<T> NuMatcher<T> {
|
impl<T> NuMatcher<'_, T> {
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `needle` - The text to search for
|
/// * `needle` - The text to search for
|
||||||
pub fn new(needle: impl AsRef<str>, options: CompletionOptions) -> NuMatcher<T> {
|
pub fn new(needle: impl AsRef<str>, options: &CompletionOptions) -> NuMatcher<T> {
|
||||||
let orig_needle = trim_quotes_str(needle.as_ref());
|
let needle = trim_quotes_str(needle.as_ref());
|
||||||
let lowercase_needle = if options.case_sensitive {
|
|
||||||
orig_needle.to_owned()
|
|
||||||
} else {
|
|
||||||
orig_needle.to_folded_case()
|
|
||||||
};
|
|
||||||
match options.match_algorithm {
|
match options.match_algorithm {
|
||||||
MatchAlgorithm::Prefix => NuMatcher {
|
MatchAlgorithm::Prefix => {
|
||||||
options,
|
let lowercase_needle = if options.case_sensitive {
|
||||||
needle: lowercase_needle,
|
needle.to_owned()
|
||||||
state: State::Prefix { items: Vec::new() },
|
|
||||||
},
|
|
||||||
MatchAlgorithm::Fuzzy => {
|
|
||||||
let mut matcher = SkimMatcherV2::default();
|
|
||||||
if options.case_sensitive {
|
|
||||||
matcher = matcher.respect_case();
|
|
||||||
} else {
|
} else {
|
||||||
matcher = matcher.ignore_case();
|
needle.to_folded_case()
|
||||||
};
|
};
|
||||||
NuMatcher {
|
NuMatcher {
|
||||||
options,
|
options,
|
||||||
needle: orig_needle.to_owned(),
|
needle: lowercase_needle,
|
||||||
|
state: State::Prefix { items: Vec::new() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MatchAlgorithm::Substring => {
|
||||||
|
let lowercase_needle = if options.case_sensitive {
|
||||||
|
needle.to_owned()
|
||||||
|
} else {
|
||||||
|
needle.to_folded_case()
|
||||||
|
};
|
||||||
|
NuMatcher {
|
||||||
|
options,
|
||||||
|
needle: lowercase_needle,
|
||||||
|
state: State::Substring { items: Vec::new() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MatchAlgorithm::Fuzzy => {
|
||||||
|
let atom = Atom::new(
|
||||||
|
needle,
|
||||||
|
if options.case_sensitive {
|
||||||
|
CaseMatching::Respect
|
||||||
|
} else {
|
||||||
|
CaseMatching::Ignore
|
||||||
|
},
|
||||||
|
Normalization::Smart,
|
||||||
|
AtomKind::Fuzzy,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
NuMatcher {
|
||||||
|
options,
|
||||||
|
needle: needle.to_owned(),
|
||||||
state: State::Fuzzy {
|
state: State::Fuzzy {
|
||||||
matcher: Box::new(matcher),
|
matcher: Matcher::new(Config::DEFAULT),
|
||||||
|
atom,
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -90,11 +124,7 @@ impl<T> NuMatcher<T> {
|
|||||||
} else {
|
} else {
|
||||||
Cow::Owned(haystack.to_folded_case())
|
Cow::Owned(haystack.to_folded_case())
|
||||||
};
|
};
|
||||||
let matches = if self.options.positional {
|
let matches = haystack_folded.starts_with(self.needle.as_str());
|
||||||
haystack_folded.starts_with(self.needle.as_str())
|
|
||||||
} else {
|
|
||||||
haystack_folded.contains(self.needle.as_str())
|
|
||||||
};
|
|
||||||
if matches {
|
if matches {
|
||||||
if let Some(item) = item {
|
if let Some(item) = item {
|
||||||
items.push((haystack.to_string(), item));
|
items.push((haystack.to_string(), item));
|
||||||
@ -102,8 +132,29 @@ impl<T> NuMatcher<T> {
|
|||||||
}
|
}
|
||||||
matches
|
matches
|
||||||
}
|
}
|
||||||
State::Fuzzy { items, matcher } => {
|
State::Substring { items } => {
|
||||||
let Some(score) = matcher.fuzzy_match(haystack, &self.needle) else {
|
let haystack_folded = if self.options.case_sensitive {
|
||||||
|
Cow::Borrowed(haystack)
|
||||||
|
} else {
|
||||||
|
Cow::Owned(haystack.to_folded_case())
|
||||||
|
};
|
||||||
|
let matches = haystack_folded.contains(self.needle.as_str());
|
||||||
|
if matches {
|
||||||
|
if let Some(item) = item {
|
||||||
|
items.push((haystack.to_string(), item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matches
|
||||||
|
}
|
||||||
|
State::Fuzzy {
|
||||||
|
matcher,
|
||||||
|
atom,
|
||||||
|
items,
|
||||||
|
} => {
|
||||||
|
let mut haystack_buf = Vec::new();
|
||||||
|
let haystack_utf32 = Utf32Str::new(trim_quotes_str(haystack), &mut haystack_buf);
|
||||||
|
let mut indices = Vec::new();
|
||||||
|
let Some(score) = atom.indices(haystack_utf32, matcher, &mut indices) else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
if let Some(item) = item {
|
if let Some(item) = item {
|
||||||
@ -129,7 +180,7 @@ impl<T> NuMatcher<T> {
|
|||||||
/// Get all the items that matched (sorted)
|
/// Get all the items that matched (sorted)
|
||||||
pub fn results(self) -> Vec<T> {
|
pub fn results(self) -> Vec<T> {
|
||||||
match self.state {
|
match self.state {
|
||||||
State::Prefix { mut items, .. } => {
|
State::Prefix { mut items, .. } | State::Substring { mut items, .. } => {
|
||||||
items.sort_by(|(haystack1, _), (haystack2, _)| {
|
items.sort_by(|(haystack1, _), (haystack2, _)| {
|
||||||
let cmp_sensitive = haystack1.cmp(haystack2);
|
let cmp_sensitive = haystack1.cmp(haystack2);
|
||||||
if self.options.case_sensitive {
|
if self.options.case_sensitive {
|
||||||
@ -165,7 +216,7 @@ impl<T> NuMatcher<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NuMatcher<SemanticSuggestion> {
|
impl NuMatcher<'_, SemanticSuggestion> {
|
||||||
pub fn add_semantic_suggestion(&mut self, sugg: SemanticSuggestion) -> bool {
|
pub fn add_semantic_suggestion(&mut self, sugg: SemanticSuggestion) -> bool {
|
||||||
let value = sugg.suggestion.value.to_string();
|
let value = sugg.suggestion.value.to_string();
|
||||||
self.add(value, sugg)
|
self.add(value, sugg)
|
||||||
@ -176,6 +227,7 @@ impl From<CompletionAlgorithm> for MatchAlgorithm {
|
|||||||
fn from(value: CompletionAlgorithm) -> Self {
|
fn from(value: CompletionAlgorithm) -> Self {
|
||||||
match value {
|
match value {
|
||||||
CompletionAlgorithm::Prefix => MatchAlgorithm::Prefix,
|
CompletionAlgorithm::Prefix => MatchAlgorithm::Prefix,
|
||||||
|
CompletionAlgorithm::Substring => MatchAlgorithm::Substring,
|
||||||
CompletionAlgorithm::Fuzzy => MatchAlgorithm::Fuzzy,
|
CompletionAlgorithm::Fuzzy => MatchAlgorithm::Fuzzy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -187,6 +239,7 @@ impl TryFrom<String> for MatchAlgorithm {
|
|||||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
match value.as_str() {
|
match value.as_str() {
|
||||||
"prefix" => Ok(Self::Prefix),
|
"prefix" => Ok(Self::Prefix),
|
||||||
|
"substring" => Ok(Self::Substring),
|
||||||
"fuzzy" => Ok(Self::Fuzzy),
|
"fuzzy" => Ok(Self::Fuzzy),
|
||||||
_ => Err(InvalidMatchAlgorithm::Unknown),
|
_ => Err(InvalidMatchAlgorithm::Unknown),
|
||||||
}
|
}
|
||||||
@ -211,7 +264,6 @@ impl std::error::Error for InvalidMatchAlgorithm {}
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CompletionOptions {
|
pub struct CompletionOptions {
|
||||||
pub case_sensitive: bool,
|
pub case_sensitive: bool,
|
||||||
pub positional: bool,
|
|
||||||
pub match_algorithm: MatchAlgorithm,
|
pub match_algorithm: MatchAlgorithm,
|
||||||
pub sort: CompletionSort,
|
pub sort: CompletionSort,
|
||||||
}
|
}
|
||||||
@ -220,7 +272,6 @@ impl Default for CompletionOptions {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
case_sensitive: true,
|
case_sensitive: true,
|
||||||
positional: true,
|
|
||||||
match_algorithm: MatchAlgorithm::Prefix,
|
match_algorithm: MatchAlgorithm::Prefix,
|
||||||
sort: Default::default(),
|
sort: Default::default(),
|
||||||
}
|
}
|
||||||
@ -237,6 +288,9 @@ mod test {
|
|||||||
#[case(MatchAlgorithm::Prefix, "example text", "", true)]
|
#[case(MatchAlgorithm::Prefix, "example text", "", true)]
|
||||||
#[case(MatchAlgorithm::Prefix, "example text", "examp", true)]
|
#[case(MatchAlgorithm::Prefix, "example text", "examp", true)]
|
||||||
#[case(MatchAlgorithm::Prefix, "example text", "text", false)]
|
#[case(MatchAlgorithm::Prefix, "example text", "text", false)]
|
||||||
|
#[case(MatchAlgorithm::Substring, "example text", "", true)]
|
||||||
|
#[case(MatchAlgorithm::Substring, "example text", "text", true)]
|
||||||
|
#[case(MatchAlgorithm::Substring, "example text", "mplxt", false)]
|
||||||
#[case(MatchAlgorithm::Fuzzy, "example text", "", true)]
|
#[case(MatchAlgorithm::Fuzzy, "example text", "", true)]
|
||||||
#[case(MatchAlgorithm::Fuzzy, "example text", "examp", true)]
|
#[case(MatchAlgorithm::Fuzzy, "example text", "examp", true)]
|
||||||
#[case(MatchAlgorithm::Fuzzy, "example text", "ext", true)]
|
#[case(MatchAlgorithm::Fuzzy, "example text", "ext", true)]
|
||||||
@ -252,7 +306,7 @@ mod test {
|
|||||||
match_algorithm,
|
match_algorithm,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let mut matcher = NuMatcher::new(needle, options);
|
let mut matcher = NuMatcher::new(needle, &options);
|
||||||
matcher.add(haystack, haystack);
|
matcher.add(haystack, haystack);
|
||||||
if should_match {
|
if should_match {
|
||||||
assert_eq!(vec![haystack], matcher.results());
|
assert_eq!(vec![haystack], matcher.results());
|
||||||
@ -267,11 +321,29 @@ mod test {
|
|||||||
match_algorithm: MatchAlgorithm::Fuzzy,
|
match_algorithm: MatchAlgorithm::Fuzzy,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let mut matcher = NuMatcher::new("fob", options);
|
let mut matcher = NuMatcher::new("fob", &options);
|
||||||
for item in ["foo/bar", "fob", "foo bar"] {
|
for item in ["foo/bar", "fob", "foo bar"] {
|
||||||
matcher.add(item, item);
|
matcher.add(item, item);
|
||||||
}
|
}
|
||||||
// Sort by score, then in alphabetical order
|
// Sort by score, then in alphabetical order
|
||||||
assert_eq!(vec!["fob", "foo bar", "foo/bar"], matcher.results());
|
assert_eq!(vec!["fob", "foo bar", "foo/bar"], matcher.results());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn match_algorithm_fuzzy_sort_strip() {
|
||||||
|
let options = CompletionOptions {
|
||||||
|
match_algorithm: MatchAlgorithm::Fuzzy,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mut matcher = NuMatcher::new("'love spaces' ", &options);
|
||||||
|
for item in [
|
||||||
|
"'i love spaces'",
|
||||||
|
"'i love spaces' so much",
|
||||||
|
"'lovespaces' ",
|
||||||
|
] {
|
||||||
|
matcher.add(item, item);
|
||||||
|
}
|
||||||
|
// Make sure the spaces are respected
|
||||||
|
assert_eq!(vec!["'i love spaces' so much"], matcher.results());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,51 +1,52 @@
|
|||||||
use crate::completions::{
|
use crate::completions::{
|
||||||
completer::map_value_completions, Completer, CompletionOptions, SemanticSuggestion,
|
completer::map_value_completions, Completer, CompletionOptions, MatchAlgorithm,
|
||||||
|
SemanticSuggestion,
|
||||||
};
|
};
|
||||||
use nu_engine::eval_call;
|
use nu_engine::eval_call;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Argument, Call, Expr, Expression},
|
ast::{Argument, Call, Expr, Expression},
|
||||||
debugger::WithoutDebug,
|
debugger::WithoutDebug,
|
||||||
engine::{Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
DeclId, PipelineData, Span, Type, Value,
|
DeclId, PipelineData, Span, Type, Value,
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use super::completion_options::NuMatcher;
|
use super::completion_options::NuMatcher;
|
||||||
|
|
||||||
pub struct CustomCompletion {
|
pub struct CustomCompletion<T: Completer> {
|
||||||
stack: Stack,
|
|
||||||
decl_id: DeclId,
|
decl_id: DeclId,
|
||||||
line: String,
|
line: String,
|
||||||
|
line_pos: usize,
|
||||||
|
fallback: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CustomCompletion {
|
impl<T: Completer> CustomCompletion<T> {
|
||||||
pub fn new(stack: Stack, decl_id: DeclId, line: String) -> Self {
|
pub fn new(decl_id: DeclId, line: String, line_pos: usize, fallback: T) -> Self {
|
||||||
Self {
|
Self {
|
||||||
stack,
|
|
||||||
decl_id,
|
decl_id,
|
||||||
line,
|
line,
|
||||||
|
line_pos,
|
||||||
|
fallback,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Completer for CustomCompletion {
|
impl<T: Completer> Completer for CustomCompletion<T> {
|
||||||
fn fetch(
|
fn fetch(
|
||||||
&mut self,
|
&mut self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
_stack: &Stack,
|
stack: &Stack,
|
||||||
prefix: &[u8],
|
prefix: impl AsRef<str>,
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
pos: usize,
|
orig_options: &CompletionOptions,
|
||||||
completion_options: &CompletionOptions,
|
|
||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
// Line position
|
|
||||||
let line_pos = pos - offset;
|
|
||||||
|
|
||||||
// Call custom declaration
|
// Call custom declaration
|
||||||
let result = eval_call::<WithoutDebug>(
|
let mut stack_mut = stack.clone();
|
||||||
working_set.permanent_state,
|
let mut eval = |engine_state: &EngineState| {
|
||||||
&mut self.stack,
|
eval_call::<WithoutDebug>(
|
||||||
|
engine_state,
|
||||||
|
&mut stack_mut,
|
||||||
&Call {
|
&Call {
|
||||||
decl_id: self.decl_id,
|
decl_id: self.decl_id,
|
||||||
head: span,
|
head: span,
|
||||||
@ -56,7 +57,7 @@ impl Completer for CustomCompletion {
|
|||||||
Type::String,
|
Type::String,
|
||||||
)),
|
)),
|
||||||
Argument::Positional(Expression::new_unknown(
|
Argument::Positional(Expression::new_unknown(
|
||||||
Expr::Int(line_pos as i64),
|
Expr::Int(self.line_pos as i64),
|
||||||
Span::unknown(),
|
Span::unknown(),
|
||||||
Type::Int,
|
Type::Int,
|
||||||
)),
|
)),
|
||||||
@ -64,15 +65,22 @@ impl Completer for CustomCompletion {
|
|||||||
parser_info: HashMap::new(),
|
parser_info: HashMap::new(),
|
||||||
},
|
},
|
||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
);
|
)
|
||||||
|
};
|
||||||
|
let result = if self.decl_id.get() < working_set.permanent_state.num_decls() {
|
||||||
|
eval(working_set.permanent_state)
|
||||||
|
} else {
|
||||||
|
let mut engine_state = working_set.permanent_state.clone();
|
||||||
|
let _ = engine_state.merge_delta(working_set.delta.clone());
|
||||||
|
eval(&engine_state)
|
||||||
|
};
|
||||||
|
|
||||||
let mut custom_completion_options = None;
|
let mut completion_options = orig_options.clone();
|
||||||
let mut should_sort = true;
|
let mut should_sort = true;
|
||||||
|
|
||||||
// Parse result
|
// Parse result
|
||||||
let suggestions = result
|
let suggestions = match result.and_then(|data| data.into_value(span)) {
|
||||||
.and_then(|data| data.into_value(span))
|
Ok(value) => match &value {
|
||||||
.map(|value| match &value {
|
|
||||||
Value::Record { val, .. } => {
|
Value::Record { val, .. } => {
|
||||||
let completions = val
|
let completions = val
|
||||||
.get("completions")
|
.get("completions")
|
||||||
@ -89,36 +97,59 @@ impl Completer for CustomCompletion {
|
|||||||
should_sort = sort;
|
should_sort = sort;
|
||||||
}
|
}
|
||||||
|
|
||||||
custom_completion_options = Some(CompletionOptions {
|
if let Some(case_sensitive) = options
|
||||||
case_sensitive: options
|
|
||||||
.get("case_sensitive")
|
.get("case_sensitive")
|
||||||
.and_then(|val| val.as_bool().ok())
|
.and_then(|val| val.as_bool().ok())
|
||||||
.unwrap_or(true),
|
{
|
||||||
positional: options
|
completion_options.case_sensitive = case_sensitive;
|
||||||
.get("positional")
|
}
|
||||||
.and_then(|val| val.as_bool().ok())
|
let positional =
|
||||||
.unwrap_or(completion_options.positional),
|
options.get("positional").and_then(|val| val.as_bool().ok());
|
||||||
match_algorithm: match options.get("completion_algorithm") {
|
if positional.is_some() {
|
||||||
Some(option) => option
|
log::warn!("Use of the positional option is deprecated. Use the substring match algorithm instead.");
|
||||||
.coerce_string()
|
}
|
||||||
.ok()
|
if let Some(algorithm) = options
|
||||||
|
.get("completion_algorithm")
|
||||||
|
.and_then(|option| option.coerce_string().ok())
|
||||||
.and_then(|option| option.try_into().ok())
|
.and_then(|option| option.try_into().ok())
|
||||||
.unwrap_or(completion_options.match_algorithm),
|
{
|
||||||
None => completion_options.match_algorithm,
|
completion_options.match_algorithm = algorithm;
|
||||||
},
|
if let Some(false) = positional {
|
||||||
sort: completion_options.sort,
|
if completion_options.match_algorithm == MatchAlgorithm::Prefix {
|
||||||
});
|
completion_options.match_algorithm = MatchAlgorithm::Substring
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
completions
|
completions
|
||||||
}
|
}
|
||||||
Value::List { vals, .. } => map_value_completions(vals.iter(), span, offset),
|
Value::List { vals, .. } => map_value_completions(vals.iter(), span, offset),
|
||||||
_ => vec![],
|
Value::Nothing { .. } => {
|
||||||
})
|
return self.fallback.fetch(
|
||||||
.unwrap_or_default();
|
working_set,
|
||||||
|
stack,
|
||||||
|
prefix,
|
||||||
|
span,
|
||||||
|
offset,
|
||||||
|
orig_options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
log::error!(
|
||||||
|
"Custom completer returned invalid value of type {}",
|
||||||
|
value.get_type().to_string()
|
||||||
|
);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Error getting custom completions: {e}");
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let options = custom_completion_options.unwrap_or(completion_options.clone());
|
let mut matcher = NuMatcher::new(prefix, &completion_options);
|
||||||
let mut matcher = NuMatcher::new(String::from_utf8_lossy(prefix), options);
|
|
||||||
|
|
||||||
if should_sort {
|
if should_sort {
|
||||||
for sugg in suggestions {
|
for sugg in suggestions {
|
||||||
|
@ -9,29 +9,22 @@ use nu_protocol::{
|
|||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use super::{completion_common::FileSuggestion, SemanticSuggestion};
|
use super::{completion_common::FileSuggestion, SemanticSuggestion, SuggestionKind};
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
pub struct DirectoryCompletion;
|
||||||
pub struct DirectoryCompletion {}
|
|
||||||
|
|
||||||
impl DirectoryCompletion {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Completer for DirectoryCompletion {
|
impl Completer for DirectoryCompletion {
|
||||||
fn fetch(
|
fn fetch(
|
||||||
&mut self,
|
&mut self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
prefix: &[u8],
|
prefix: impl AsRef<str>,
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_pos: usize,
|
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let AdjustView { prefix, span, .. } = adjust_if_intermediate(prefix, working_set, span);
|
let AdjustView { prefix, span, .. } =
|
||||||
|
adjust_if_intermediate(prefix.as_ref(), working_set, span);
|
||||||
|
|
||||||
// Filter only the folders
|
// Filter only the folders
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
@ -54,8 +47,7 @@ impl Completer for DirectoryCompletion {
|
|||||||
},
|
},
|
||||||
..Suggestion::default()
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
// TODO????
|
kind: Some(SuggestionKind::Directory),
|
||||||
kind: None,
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
use crate::completions::{file_path_completion, Completer, CompletionOptions};
|
use crate::completions::{
|
||||||
|
completion_common::{surround_remove, FileSuggestion},
|
||||||
|
completion_options::NuMatcher,
|
||||||
|
file_path_completion, Completer, CompletionOptions, SemanticSuggestion, SuggestionKind,
|
||||||
|
};
|
||||||
|
use nu_path::expand_tilde;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{Stack, StateWorkingSet},
|
engine::{Stack, StateWorkingSet, VirtualPath},
|
||||||
Span,
|
Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::path::{is_separator, Path, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR};
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
path::{is_separator, PathBuf, MAIN_SEPARATOR_STR},
|
||||||
|
};
|
||||||
|
|
||||||
use super::SemanticSuggestion;
|
pub struct DotNuCompletion {
|
||||||
|
/// e.g. use std/a<tab>
|
||||||
#[derive(Clone, Default)]
|
pub std_virtual_path: bool,
|
||||||
pub struct DotNuCompletion {}
|
|
||||||
|
|
||||||
impl DotNuCompletion {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Completer for DotNuCompletion {
|
impl Completer for DotNuCompletion {
|
||||||
@ -22,108 +24,184 @@ impl Completer for DotNuCompletion {
|
|||||||
&mut self,
|
&mut self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
prefix: &[u8],
|
prefix: impl AsRef<str>,
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_pos: usize,
|
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let prefix_str = String::from_utf8_lossy(prefix).replace('`', "");
|
let prefix_str = prefix.as_ref();
|
||||||
let mut search_dirs: Vec<String> = vec![];
|
let start_with_backquote = prefix_str.starts_with('`');
|
||||||
|
let end_with_backquote = prefix_str.ends_with('`');
|
||||||
|
let prefix_str = prefix_str.replace('`', "");
|
||||||
|
// e.g. `./`, `..\`, `/`
|
||||||
|
let not_lib_dirs = prefix_str
|
||||||
|
.chars()
|
||||||
|
.find(|c| *c != '.')
|
||||||
|
.is_some_and(is_separator);
|
||||||
|
let mut search_dirs: Vec<PathBuf> = vec![];
|
||||||
|
|
||||||
// If prefix_str is only a word we want to search in the current dir
|
let (base, partial) = if let Some((parent, remain)) = prefix_str.rsplit_once(is_separator) {
|
||||||
let (base, partial) = prefix_str
|
// If prefix_str is only a word we want to search in the current dir.
|
||||||
.rsplit_once(is_separator)
|
// "/xx" should be split to "/" and "xx".
|
||||||
.unwrap_or((".", &prefix_str));
|
if parent.is_empty() {
|
||||||
|
(MAIN_SEPARATOR_STR, remain)
|
||||||
|
} else {
|
||||||
|
(parent, remain)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(".", prefix_str.as_str())
|
||||||
|
};
|
||||||
let base_dir = base.replace(is_separator, MAIN_SEPARATOR_STR);
|
let base_dir = base.replace(is_separator, MAIN_SEPARATOR_STR);
|
||||||
let mut partial = partial.to_string();
|
|
||||||
// On windows, this standardizes paths to use \
|
|
||||||
let mut is_current_folder = false;
|
|
||||||
|
|
||||||
// Fetch the lib dirs
|
// Fetch the lib dirs
|
||||||
let lib_dirs: Vec<String> = if let Some(lib_dirs) = working_set.get_env_var("NU_LIB_DIRS") {
|
// NOTE: 2 ways to setup `NU_LIB_DIRS`
|
||||||
|
// 1. `const NU_LIB_DIRS = [paths]`, equal to `nu -I paths`
|
||||||
|
// 2. `$env.NU_LIB_DIRS = [paths]`
|
||||||
|
let const_lib_dirs = working_set
|
||||||
|
.find_variable(b"$NU_LIB_DIRS")
|
||||||
|
.and_then(|vid| working_set.get_variable(vid).const_val.as_ref());
|
||||||
|
let env_lib_dirs = working_set.get_env_var("NU_LIB_DIRS");
|
||||||
|
let lib_dirs: HashSet<PathBuf> = [const_lib_dirs, env_lib_dirs]
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.flat_map(|lib_dirs| {
|
||||||
lib_dirs
|
lib_dirs
|
||||||
.as_list()
|
.as_list()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|it| {
|
.flat_map(|it| it.iter().filter_map(|x| x.to_path().ok()))
|
||||||
it.iter().map(|x| {
|
.map(expand_tilde)
|
||||||
x.to_path()
|
|
||||||
.expect("internal error: failed to convert lib path")
|
|
||||||
})
|
})
|
||||||
})
|
.collect();
|
||||||
.map(|it| {
|
|
||||||
it.into_os_string()
|
|
||||||
.into_string()
|
|
||||||
.expect("internal error: failed to convert OS path")
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if the base_dir is a folder
|
// Check if the base_dir is a folder
|
||||||
// rsplit_once removes the separator
|
let cwd = working_set.permanent_state.cwd(None);
|
||||||
if base_dir != "." {
|
if base_dir != "." {
|
||||||
// Add the base dir into the directories to be searched
|
let expanded_base_dir = expand_tilde(&base_dir);
|
||||||
search_dirs.push(base_dir.clone());
|
let is_base_dir_relative = expanded_base_dir.is_relative();
|
||||||
|
// Search in base_dir as well as lib_dirs.
|
||||||
// Reset the partial adding the basic dir back
|
// After expanded, base_dir can be a relative path or absolute path.
|
||||||
// in order to make the span replace work properly
|
// If relative, we join "current working dir" with it to get subdirectory and add to search_dirs.
|
||||||
let mut base_dir_partial = base_dir;
|
// If absolute, we add it to search_dirs.
|
||||||
base_dir_partial.push_str(&partial);
|
if let Ok(mut cwd) = cwd {
|
||||||
|
if is_base_dir_relative {
|
||||||
partial = base_dir_partial;
|
cwd.push(&base_dir);
|
||||||
|
search_dirs.push(cwd.into_std_path_buf());
|
||||||
} else {
|
} else {
|
||||||
// Fetch the current folder
|
search_dirs.push(expanded_base_dir);
|
||||||
#[allow(deprecated)]
|
}
|
||||||
let current_folder = working_set.permanent_state.current_work_dir();
|
}
|
||||||
is_current_folder = true;
|
if !not_lib_dirs {
|
||||||
|
search_dirs.extend(lib_dirs.into_iter().map(|mut dir| {
|
||||||
// Add the current folder and the lib dirs into the
|
dir.push(&base_dir);
|
||||||
// directories to be searched
|
dir
|
||||||
search_dirs.push(current_folder);
|
}));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Ok(cwd) = cwd {
|
||||||
|
search_dirs.push(cwd.into_std_path_buf());
|
||||||
|
}
|
||||||
|
if !not_lib_dirs {
|
||||||
search_dirs.extend(lib_dirs);
|
search_dirs.extend(lib_dirs);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch the files filtering the ones that ends with .nu
|
// Fetch the files filtering the ones that ends with .nu
|
||||||
// and transform them into suggestions
|
// and transform them into suggestions
|
||||||
|
let mut completions = file_path_completion(
|
||||||
let completions = file_path_completion(
|
|
||||||
span,
|
span,
|
||||||
&partial,
|
partial,
|
||||||
&search_dirs.iter().map(|d| d.as_str()).collect::<Vec<_>>(),
|
&search_dirs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|d| d.to_str())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
options,
|
options,
|
||||||
working_set.permanent_state,
|
working_set.permanent_state,
|
||||||
stack,
|
stack,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if self.std_virtual_path {
|
||||||
|
let mut matcher = NuMatcher::new(partial, options);
|
||||||
|
let base_dir = surround_remove(&base_dir);
|
||||||
|
if base_dir == "." {
|
||||||
|
let surround_prefix = partial
|
||||||
|
.chars()
|
||||||
|
.take_while(|c| "`'\"".contains(*c))
|
||||||
|
.collect::<String>();
|
||||||
|
for path in ["std", "std-rfc"] {
|
||||||
|
let path = format!("{}{}", surround_prefix, path);
|
||||||
|
matcher.add(
|
||||||
|
path.clone(),
|
||||||
|
FileSuggestion {
|
||||||
|
span,
|
||||||
|
path,
|
||||||
|
style: None,
|
||||||
|
is_dir: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if let Some(VirtualPath::Dir(sub_paths)) =
|
||||||
|
working_set.find_virtual_path(&base_dir)
|
||||||
|
{
|
||||||
|
for sub_vp_id in sub_paths {
|
||||||
|
let (path, sub_vp) = working_set.get_virtual_path(*sub_vp_id);
|
||||||
|
let path = path
|
||||||
|
.strip_prefix(&format!("{}/", base_dir))
|
||||||
|
.unwrap_or(path)
|
||||||
|
.to_string();
|
||||||
|
matcher.add(
|
||||||
|
path.clone(),
|
||||||
|
FileSuggestion {
|
||||||
|
path,
|
||||||
|
span,
|
||||||
|
style: None,
|
||||||
|
is_dir: matches!(sub_vp, VirtualPath::Dir(_)),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
completions.extend(matcher.results());
|
||||||
|
}
|
||||||
|
|
||||||
completions
|
completions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(move |it| {
|
|
||||||
// Different base dir, so we list the .nu files or folders
|
// Different base dir, so we list the .nu files or folders
|
||||||
if !is_current_folder {
|
.filter(|it| {
|
||||||
it.path.ends_with(".nu") || it.path.ends_with(SEP)
|
// for paths with spaces in them
|
||||||
} else {
|
let path = it.path.trim_end_matches('`');
|
||||||
// Lib dirs, so we filter only the .nu files or directory modules
|
path.ends_with(".nu") || it.is_dir
|
||||||
if it.path.ends_with(SEP) {
|
|
||||||
Path::new(&it.cwd).join(&it.path).join("mod.nu").exists()
|
|
||||||
} else {
|
|
||||||
it.path.ends_with(".nu")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.map(move |x| SemanticSuggestion {
|
.map(|x| {
|
||||||
|
let append_whitespace = !x.is_dir && (!start_with_backquote || end_with_backquote);
|
||||||
|
// Re-calculate the span to replace
|
||||||
|
let mut span_offset = 0;
|
||||||
|
let mut value = x.path.to_string();
|
||||||
|
// Complete only the last path component
|
||||||
|
if base_dir == MAIN_SEPARATOR_STR {
|
||||||
|
span_offset = base_dir.len()
|
||||||
|
} else if base_dir != "." {
|
||||||
|
span_offset = base_dir.len() + 1
|
||||||
|
}
|
||||||
|
// Retain only one '`'
|
||||||
|
if start_with_backquote {
|
||||||
|
value = value.trim_start_matches('`').to_string();
|
||||||
|
span_offset += 1;
|
||||||
|
}
|
||||||
|
// Add the backquote back
|
||||||
|
if end_with_backquote && !value.ends_with('`') {
|
||||||
|
value.push('`');
|
||||||
|
}
|
||||||
|
let end = x.span.end - offset;
|
||||||
|
let start = std::cmp::min(end, x.span.start - offset + span_offset);
|
||||||
|
SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: x.path,
|
value,
|
||||||
style: x.style,
|
style: x.style,
|
||||||
span: reedline::Span {
|
span: reedline::Span { start, end },
|
||||||
start: x.span.start - offset,
|
append_whitespace,
|
||||||
end: x.span.end - offset,
|
|
||||||
},
|
|
||||||
append_whitespace: true,
|
|
||||||
..Suggestion::default()
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
// TODO????
|
kind: Some(SuggestionKind::Module),
|
||||||
kind: None,
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
112
crates/nu-cli/src/completions/exportable_completions.rs
Normal file
112
crates/nu-cli/src/completions/exportable_completions.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
use crate::completions::{
|
||||||
|
completion_common::surround_remove, completion_options::NuMatcher, Completer,
|
||||||
|
CompletionOptions, SemanticSuggestion, SuggestionKind,
|
||||||
|
};
|
||||||
|
use nu_protocol::{
|
||||||
|
engine::{Stack, StateWorkingSet},
|
||||||
|
ModuleId, Span,
|
||||||
|
};
|
||||||
|
use reedline::Suggestion;
|
||||||
|
|
||||||
|
pub struct ExportableCompletion<'a> {
|
||||||
|
pub module_id: ModuleId,
|
||||||
|
pub temp_working_set: Option<StateWorkingSet<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If name contains space, wrap it in quotes
|
||||||
|
fn wrapped_name(name: String) -> String {
|
||||||
|
if !name.contains(' ') {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
if name.contains('\'') {
|
||||||
|
format!("\"{}\"", name.replace('"', r#"\""#))
|
||||||
|
} else {
|
||||||
|
format!("'{name}'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Completer for ExportableCompletion<'_> {
|
||||||
|
fn fetch(
|
||||||
|
&mut self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
_stack: &Stack,
|
||||||
|
prefix: impl AsRef<str>,
|
||||||
|
span: Span,
|
||||||
|
offset: usize,
|
||||||
|
options: &CompletionOptions,
|
||||||
|
) -> Vec<SemanticSuggestion> {
|
||||||
|
let mut matcher = NuMatcher::<()>::new(surround_remove(prefix.as_ref()), options);
|
||||||
|
let mut results = Vec::new();
|
||||||
|
let span = reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
};
|
||||||
|
// TODO: use matcher.add_lazy to lazy evaluate an item if it matches the prefix
|
||||||
|
let mut add_suggestion = |value: String,
|
||||||
|
description: Option<String>,
|
||||||
|
extra: Option<Vec<String>>,
|
||||||
|
kind: SuggestionKind| {
|
||||||
|
results.push(SemanticSuggestion {
|
||||||
|
suggestion: Suggestion {
|
||||||
|
value,
|
||||||
|
span,
|
||||||
|
description,
|
||||||
|
extra,
|
||||||
|
..Suggestion::default()
|
||||||
|
},
|
||||||
|
kind: Some(kind),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let working_set = self.temp_working_set.as_ref().unwrap_or(working_set);
|
||||||
|
let module = working_set.get_module(self.module_id);
|
||||||
|
|
||||||
|
for (name, decl_id) in &module.decls {
|
||||||
|
let name = String::from_utf8_lossy(name).to_string();
|
||||||
|
if matcher.matches(&name) {
|
||||||
|
let cmd = working_set.get_decl(*decl_id);
|
||||||
|
add_suggestion(
|
||||||
|
wrapped_name(name),
|
||||||
|
Some(cmd.description().to_string()),
|
||||||
|
None,
|
||||||
|
// `None` here avoids arguments being expanded by snippet edit style for lsp
|
||||||
|
SuggestionKind::Command(cmd.command_type(), None),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (name, module_id) in &module.submodules {
|
||||||
|
let name = String::from_utf8_lossy(name).to_string();
|
||||||
|
if matcher.matches(&name) {
|
||||||
|
let comments = working_set.get_module_comments(*module_id).map(|spans| {
|
||||||
|
spans
|
||||||
|
.iter()
|
||||||
|
.map(|sp| {
|
||||||
|
String::from_utf8_lossy(working_set.get_span_contents(*sp)).into()
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
});
|
||||||
|
add_suggestion(
|
||||||
|
wrapped_name(name),
|
||||||
|
Some("Submodule".into()),
|
||||||
|
comments,
|
||||||
|
SuggestionKind::Module,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (name, var_id) in &module.constants {
|
||||||
|
let name = String::from_utf8_lossy(name).to_string();
|
||||||
|
if matcher.matches(&name) {
|
||||||
|
let var = working_set.get_variable(*var_id);
|
||||||
|
add_suggestion(
|
||||||
|
wrapped_name(name),
|
||||||
|
var.const_val
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|v| v.clone().coerce_into_string().ok()),
|
||||||
|
None,
|
||||||
|
SuggestionKind::Variable,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results
|
||||||
|
}
|
||||||
|
}
|
@ -9,33 +9,25 @@ use nu_protocol::{
|
|||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use super::{completion_common::FileSuggestion, SemanticSuggestion};
|
use super::{completion_common::FileSuggestion, SemanticSuggestion, SuggestionKind};
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
pub struct FileCompletion;
|
||||||
pub struct FileCompletion {}
|
|
||||||
|
|
||||||
impl FileCompletion {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Completer for FileCompletion {
|
impl Completer for FileCompletion {
|
||||||
fn fetch(
|
fn fetch(
|
||||||
&mut self,
|
&mut self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
prefix: &[u8],
|
prefix: impl AsRef<str>,
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_pos: usize,
|
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let AdjustView {
|
let AdjustView {
|
||||||
prefix,
|
prefix,
|
||||||
span,
|
span,
|
||||||
readjusted,
|
readjusted,
|
||||||
} = adjust_if_intermediate(prefix, working_set, span);
|
} = adjust_if_intermediate(prefix.as_ref(), working_set, span);
|
||||||
|
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
let items: Vec<_> = complete_item(
|
let items: Vec<_> = complete_item(
|
||||||
@ -58,8 +50,11 @@ impl Completer for FileCompletion {
|
|||||||
},
|
},
|
||||||
..Suggestion::default()
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
// TODO????
|
kind: Some(if x.is_dir {
|
||||||
kind: None,
|
SuggestionKind::Directory
|
||||||
|
} else {
|
||||||
|
SuggestionKind::File
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -1,22 +1,15 @@
|
|||||||
use crate::completions::{completion_options::NuMatcher, Completer, CompletionOptions};
|
use crate::completions::{
|
||||||
|
completion_options::NuMatcher, Completer, CompletionOptions, SemanticSuggestion, SuggestionKind,
|
||||||
|
};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Expr, Expression},
|
|
||||||
engine::{Stack, StateWorkingSet},
|
engine::{Stack, StateWorkingSet},
|
||||||
Span,
|
DeclId, Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
|
|
||||||
use super::SemanticSuggestion;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FlagCompletion {
|
pub struct FlagCompletion {
|
||||||
expression: Expression,
|
pub decl_id: DeclId,
|
||||||
}
|
|
||||||
|
|
||||||
impl FlagCompletion {
|
|
||||||
pub fn new(expression: Expression) -> Self {
|
|
||||||
Self { expression }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Completer for FlagCompletion {
|
impl Completer for FlagCompletion {
|
||||||
@ -24,30 +17,17 @@ impl Completer for FlagCompletion {
|
|||||||
&mut self,
|
&mut self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
_stack: &Stack,
|
_stack: &Stack,
|
||||||
prefix: &[u8],
|
prefix: impl AsRef<str>,
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_pos: usize,
|
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
// Check if it's a flag
|
let mut matcher = NuMatcher::new(prefix, options);
|
||||||
if let Expr::Call(call) = &self.expression.expr {
|
let mut add_suggestion = |value: String, description: String| {
|
||||||
let decl = working_set.get_decl(call.decl_id);
|
|
||||||
let sig = decl.signature();
|
|
||||||
|
|
||||||
let mut matcher = NuMatcher::new(String::from_utf8_lossy(prefix), options.clone());
|
|
||||||
|
|
||||||
for named in &sig.named {
|
|
||||||
let flag_desc = &named.desc;
|
|
||||||
if let Some(short) = named.short {
|
|
||||||
let mut named = vec![0; short.len_utf8()];
|
|
||||||
short.encode_utf8(&mut named);
|
|
||||||
named.insert(0, b'-');
|
|
||||||
|
|
||||||
matcher.add_semantic_suggestion(SemanticSuggestion {
|
matcher.add_semantic_suggestion(SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: String::from_utf8_lossy(&named).to_string(),
|
value,
|
||||||
description: Some(flag_desc.to_string()),
|
description: Some(description),
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: span.start - offset,
|
start: span.start - offset,
|
||||||
end: span.end - offset,
|
end: span.end - offset,
|
||||||
@ -55,38 +35,24 @@ impl Completer for FlagCompletion {
|
|||||||
append_whitespace: true,
|
append_whitespace: true,
|
||||||
..Suggestion::default()
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
// TODO????
|
kind: Some(SuggestionKind::Flag),
|
||||||
kind: None,
|
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let decl = working_set.get_decl(self.decl_id);
|
||||||
|
let sig = decl.signature();
|
||||||
|
for named in &sig.named {
|
||||||
|
if let Some(short) = named.short {
|
||||||
|
let mut name = String::from("-");
|
||||||
|
name.push(short);
|
||||||
|
add_suggestion(name, named.desc.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
if named.long.is_empty() {
|
if named.long.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
add_suggestion(format!("--{}", named.long), named.desc.clone());
|
||||||
let mut named = named.long.as_bytes().to_vec();
|
|
||||||
named.insert(0, b'-');
|
|
||||||
named.insert(0, b'-');
|
|
||||||
|
|
||||||
matcher.add_semantic_suggestion(SemanticSuggestion {
|
|
||||||
suggestion: Suggestion {
|
|
||||||
value: String::from_utf8_lossy(&named).to_string(),
|
|
||||||
description: Some(flag_desc.to_string()),
|
|
||||||
span: reedline::Span {
|
|
||||||
start: span.start - offset,
|
|
||||||
end: span.end - offset,
|
|
||||||
},
|
|
||||||
append_whitespace: true,
|
|
||||||
..Suggestion::default()
|
|
||||||
},
|
|
||||||
// TODO????
|
|
||||||
kind: None,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
matcher.results()
|
||||||
return matcher.results();
|
|
||||||
}
|
|
||||||
|
|
||||||
vec![]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
mod attribute_completions;
|
||||||
mod base;
|
mod base;
|
||||||
|
mod cell_path_completions;
|
||||||
mod command_completions;
|
mod command_completions;
|
||||||
mod completer;
|
mod completer;
|
||||||
mod completion_common;
|
mod completion_common;
|
||||||
@ -6,18 +8,22 @@ mod completion_options;
|
|||||||
mod custom_completions;
|
mod custom_completions;
|
||||||
mod directory_completions;
|
mod directory_completions;
|
||||||
mod dotnu_completions;
|
mod dotnu_completions;
|
||||||
|
mod exportable_completions;
|
||||||
mod file_completions;
|
mod file_completions;
|
||||||
mod flag_completions;
|
mod flag_completions;
|
||||||
mod operator_completions;
|
mod operator_completions;
|
||||||
mod variable_completions;
|
mod variable_completions;
|
||||||
|
|
||||||
|
pub use attribute_completions::{AttributableCompletion, AttributeCompletion};
|
||||||
pub use base::{Completer, SemanticSuggestion, SuggestionKind};
|
pub use base::{Completer, SemanticSuggestion, SuggestionKind};
|
||||||
|
pub use cell_path_completions::CellPathCompletion;
|
||||||
pub use command_completions::CommandCompletion;
|
pub use command_completions::CommandCompletion;
|
||||||
pub use completer::NuCompleter;
|
pub use completer::NuCompleter;
|
||||||
pub use completion_options::{CompletionOptions, MatchAlgorithm};
|
pub use completion_options::{CompletionOptions, MatchAlgorithm};
|
||||||
pub use custom_completions::CustomCompletion;
|
pub use custom_completions::CustomCompletion;
|
||||||
pub use directory_completions::DirectoryCompletion;
|
pub use directory_completions::DirectoryCompletion;
|
||||||
pub use dotnu_completions::DotNuCompletion;
|
pub use dotnu_completions::DotNuCompletion;
|
||||||
|
pub use exportable_completions::ExportableCompletion;
|
||||||
pub use file_completions::{file_path_completion, FileCompletion};
|
pub use file_completions::{file_path_completion, FileCompletion};
|
||||||
pub use flag_completions::FlagCompletion;
|
pub use flag_completions::FlagCompletion;
|
||||||
pub use operator_completions::OperatorCompletion;
|
pub use operator_completions::OperatorCompletion;
|
||||||
|
@ -2,165 +2,276 @@ use crate::completions::{
|
|||||||
completion_options::NuMatcher, Completer, CompletionOptions, SemanticSuggestion, SuggestionKind,
|
completion_options::NuMatcher, Completer, CompletionOptions, SemanticSuggestion, SuggestionKind,
|
||||||
};
|
};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Expr, Expression},
|
ast::{self, Comparison, Expr, Expression},
|
||||||
engine::{Stack, StateWorkingSet},
|
engine::{Stack, StateWorkingSet},
|
||||||
Span, Type,
|
Span, Type, Value, ENV_VARIABLE_ID,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
|
use strum::{EnumMessage, IntoEnumIterator};
|
||||||
|
|
||||||
|
use super::cell_path_completions::eval_cell_path;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct OperatorCompletion {
|
pub struct OperatorCompletion<'a> {
|
||||||
previous_expr: Expression,
|
pub left_hand_side: &'a Expression,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OperatorCompletion {
|
struct OperatorItem {
|
||||||
pub fn new(previous_expr: Expression) -> Self {
|
pub symbols: String,
|
||||||
OperatorCompletion { previous_expr }
|
pub description: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operator_to_item<T: EnumMessage + AsRef<str>>(op: T) -> OperatorItem {
|
||||||
|
OperatorItem {
|
||||||
|
symbols: op.as_ref().into(),
|
||||||
|
description: op.get_message().unwrap_or_default().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Completer for OperatorCompletion {
|
fn common_comparison_ops() -> Vec<OperatorItem> {
|
||||||
|
vec![
|
||||||
|
operator_to_item(Comparison::In),
|
||||||
|
operator_to_item(Comparison::NotIn),
|
||||||
|
operator_to_item(Comparison::Equal),
|
||||||
|
operator_to_item(Comparison::NotEqual),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn all_ops_for_immutable() -> Vec<OperatorItem> {
|
||||||
|
ast::Comparison::iter()
|
||||||
|
.map(operator_to_item)
|
||||||
|
.chain(ast::Math::iter().map(operator_to_item))
|
||||||
|
.chain(ast::Boolean::iter().map(operator_to_item))
|
||||||
|
.chain(ast::Bits::iter().map(operator_to_item))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collection_comparison_ops() -> Vec<OperatorItem> {
|
||||||
|
let mut ops = common_comparison_ops();
|
||||||
|
ops.push(operator_to_item(Comparison::Has));
|
||||||
|
ops.push(operator_to_item(Comparison::NotHas));
|
||||||
|
ops
|
||||||
|
}
|
||||||
|
|
||||||
|
fn number_comparison_ops() -> Vec<OperatorItem> {
|
||||||
|
Comparison::iter()
|
||||||
|
.filter(|op| {
|
||||||
|
!matches!(
|
||||||
|
op,
|
||||||
|
Comparison::RegexMatch
|
||||||
|
| Comparison::NotRegexMatch
|
||||||
|
| Comparison::StartsWith
|
||||||
|
| Comparison::EndsWith
|
||||||
|
| Comparison::Has
|
||||||
|
| Comparison::NotHas
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map(operator_to_item)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn math_ops() -> Vec<OperatorItem> {
|
||||||
|
ast::Math::iter()
|
||||||
|
.filter(|op| !matches!(op, ast::Math::Concatenate | ast::Math::Pow))
|
||||||
|
.map(operator_to_item)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bit_ops() -> Vec<OperatorItem> {
|
||||||
|
ast::Bits::iter().map(operator_to_item).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn all_assignment_ops() -> Vec<OperatorItem> {
|
||||||
|
ast::Assignment::iter().map(operator_to_item).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn numeric_assignment_ops() -> Vec<OperatorItem> {
|
||||||
|
ast::Assignment::iter()
|
||||||
|
.filter(|op| !matches!(op, ast::Assignment::ConcatenateAssign))
|
||||||
|
.map(operator_to_item)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn concat_assignment_ops() -> Vec<OperatorItem> {
|
||||||
|
vec![
|
||||||
|
operator_to_item(ast::Assignment::Assign),
|
||||||
|
operator_to_item(ast::Assignment::ConcatenateAssign),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_int_ops() -> Vec<OperatorItem> {
|
||||||
|
let mut ops = valid_float_ops();
|
||||||
|
ops.extend(bit_ops());
|
||||||
|
ops
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_float_ops() -> Vec<OperatorItem> {
|
||||||
|
let mut ops = valid_value_with_unit_ops();
|
||||||
|
ops.push(operator_to_item(ast::Math::Pow));
|
||||||
|
ops
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_string_ops() -> Vec<OperatorItem> {
|
||||||
|
let mut ops: Vec<OperatorItem> = Comparison::iter().map(operator_to_item).collect();
|
||||||
|
ops.push(operator_to_item(ast::Math::Concatenate));
|
||||||
|
ops.push(OperatorItem {
|
||||||
|
symbols: "like".into(),
|
||||||
|
description: Comparison::RegexMatch
|
||||||
|
.get_message()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into(),
|
||||||
|
});
|
||||||
|
ops.push(OperatorItem {
|
||||||
|
symbols: "not-like".into(),
|
||||||
|
description: Comparison::NotRegexMatch
|
||||||
|
.get_message()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into(),
|
||||||
|
});
|
||||||
|
ops
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_list_ops() -> Vec<OperatorItem> {
|
||||||
|
let mut ops = collection_comparison_ops();
|
||||||
|
ops.push(operator_to_item(ast::Math::Concatenate));
|
||||||
|
ops
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_binary_ops() -> Vec<OperatorItem> {
|
||||||
|
let mut ops = number_comparison_ops();
|
||||||
|
ops.extend(bit_ops());
|
||||||
|
ops.push(operator_to_item(ast::Math::Concatenate));
|
||||||
|
ops
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_bool_ops() -> Vec<OperatorItem> {
|
||||||
|
let mut ops: Vec<OperatorItem> = ast::Boolean::iter().map(operator_to_item).collect();
|
||||||
|
ops.extend(common_comparison_ops());
|
||||||
|
ops
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_value_with_unit_ops() -> Vec<OperatorItem> {
|
||||||
|
let mut ops = number_comparison_ops();
|
||||||
|
ops.extend(math_ops());
|
||||||
|
ops
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ops_by_value(value: &Value, mutable: bool) -> Vec<OperatorItem> {
|
||||||
|
let mut ops = match value {
|
||||||
|
Value::Int { .. } => valid_int_ops(),
|
||||||
|
Value::Float { .. } => valid_float_ops(),
|
||||||
|
Value::String { .. } => valid_string_ops(),
|
||||||
|
Value::Binary { .. } => valid_binary_ops(),
|
||||||
|
Value::Bool { .. } => valid_bool_ops(),
|
||||||
|
Value::Date { .. } => number_comparison_ops(),
|
||||||
|
Value::Filesize { .. } | Value::Duration { .. } => valid_value_with_unit_ops(),
|
||||||
|
Value::Range { .. } | Value::Record { .. } => collection_comparison_ops(),
|
||||||
|
Value::List { .. } => valid_list_ops(),
|
||||||
|
_ => all_ops_for_immutable(),
|
||||||
|
};
|
||||||
|
if mutable {
|
||||||
|
ops.extend(match value {
|
||||||
|
Value::Int { .. }
|
||||||
|
| Value::Float { .. }
|
||||||
|
| Value::Filesize { .. }
|
||||||
|
| Value::Duration { .. } => numeric_assignment_ops(),
|
||||||
|
Value::String { .. } | Value::Binary { .. } | Value::List { .. } => {
|
||||||
|
concat_assignment_ops()
|
||||||
|
}
|
||||||
|
Value::Bool { .. }
|
||||||
|
| Value::Date { .. }
|
||||||
|
| Value::Range { .. }
|
||||||
|
| Value::Record { .. } => vec![operator_to_item(ast::Assignment::Assign)],
|
||||||
|
_ => all_assignment_ops(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ops
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_expression_mutable(expr: &Expr, working_set: &StateWorkingSet) -> bool {
|
||||||
|
let Expr::FullCellPath(path) = expr else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Expr::Var(id) = path.head.expr else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if id == ENV_VARIABLE_ID {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let var = working_set.get_variable(id);
|
||||||
|
var.mutable
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Completer for OperatorCompletion<'_> {
|
||||||
fn fetch(
|
fn fetch(
|
||||||
&mut self,
|
&mut self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
_stack: &Stack,
|
stack: &Stack,
|
||||||
_prefix: &[u8],
|
prefix: impl AsRef<str>,
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_pos: usize,
|
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
//Check if int, float, or string
|
let mut needs_assignment_ops = true;
|
||||||
let partial = std::str::from_utf8(working_set.get_span_contents(span)).unwrap_or("");
|
// Complete according expression type
|
||||||
let op = match &self.previous_expr.expr {
|
// TODO: type inference on self.left_hand_side to get more accurate completions
|
||||||
Expr::BinaryOp(x, _, _) => &x.expr,
|
let mut possible_operations: Vec<OperatorItem> = match &self.left_hand_side.ty {
|
||||||
_ => {
|
Type::Int | Type::Number => valid_int_ops(),
|
||||||
|
Type::Float => valid_float_ops(),
|
||||||
|
Type::String => valid_string_ops(),
|
||||||
|
Type::Binary => valid_binary_ops(),
|
||||||
|
Type::Bool => valid_bool_ops(),
|
||||||
|
Type::Date => number_comparison_ops(),
|
||||||
|
Type::Filesize | Type::Duration => valid_value_with_unit_ops(),
|
||||||
|
Type::Record(_) | Type::Range => collection_comparison_ops(),
|
||||||
|
Type::List(_) | Type::Table(_) => valid_list_ops(),
|
||||||
|
// Unknown type, resort to evaluated values
|
||||||
|
Type::Any => match &self.left_hand_side.expr {
|
||||||
|
Expr::FullCellPath(path) => {
|
||||||
|
// for `$ <tab>`
|
||||||
|
if matches!(path.head.expr, Expr::Garbage) {
|
||||||
return vec![];
|
return vec![];
|
||||||
}
|
}
|
||||||
};
|
let value =
|
||||||
let possible_operations = match op {
|
eval_cell_path(working_set, stack, &path.head, &path.tail, path.head.span)
|
||||||
Expr::Int(_) => vec![
|
.unwrap_or_default();
|
||||||
("+", "Add (Plus)"),
|
let mutable = is_expression_mutable(&self.left_hand_side.expr, working_set);
|
||||||
("-", "Subtract (Minus)"),
|
// to avoid duplication
|
||||||
("*", "Multiply"),
|
needs_assignment_ops = false;
|
||||||
("/", "Divide"),
|
ops_by_value(&value, mutable)
|
||||||
("==", "Equal to"),
|
}
|
||||||
("!=", "Not equal to"),
|
_ => all_ops_for_immutable(),
|
||||||
("//", "Floor division"),
|
|
||||||
("<", "Less than"),
|
|
||||||
(">", "Greater than"),
|
|
||||||
("<=", "Less than or equal to"),
|
|
||||||
(">=", "Greater than or equal to"),
|
|
||||||
("mod", "Floor division remainder (Modulo)"),
|
|
||||||
("**", "Power of"),
|
|
||||||
("bit-or", "Bitwise OR"),
|
|
||||||
("bit-xor", "Bitwise exclusive OR"),
|
|
||||||
("bit-and", "Bitwise AND"),
|
|
||||||
("bit-shl", "Bitwise shift left"),
|
|
||||||
("bit-shr", "Bitwise shift right"),
|
|
||||||
("in", "Is a member of (doesn't use regex)"),
|
|
||||||
("not-in", "Is not a member of (doesn't use regex)"),
|
|
||||||
],
|
|
||||||
Expr::String(_) => vec![
|
|
||||||
("=~", "Contains regex match"),
|
|
||||||
("like", "Contains regex match"),
|
|
||||||
("!~", "Does not contain regex match"),
|
|
||||||
("not-like", "Does not contain regex match"),
|
|
||||||
(
|
|
||||||
"++",
|
|
||||||
"Concatenates two lists, two strings, or two binary values",
|
|
||||||
),
|
|
||||||
("in", "Is a member of (doesn't use regex)"),
|
|
||||||
("not-in", "Is not a member of (doesn't use regex)"),
|
|
||||||
("starts-with", "Starts with"),
|
|
||||||
("ends-with", "Ends with"),
|
|
||||||
],
|
|
||||||
Expr::Float(_) => vec![
|
|
||||||
("+", "Add (Plus)"),
|
|
||||||
("-", "Subtract (Minus)"),
|
|
||||||
("*", "Multiply"),
|
|
||||||
("/", "Divide"),
|
|
||||||
("==", "Equal to"),
|
|
||||||
("!=", "Not equal to"),
|
|
||||||
("//", "Floor division"),
|
|
||||||
("<", "Less than"),
|
|
||||||
(">", "Greater than"),
|
|
||||||
("<=", "Less than or equal to"),
|
|
||||||
(">=", "Greater than or equal to"),
|
|
||||||
("mod", "Floor division remainder (Modulo)"),
|
|
||||||
("**", "Power of"),
|
|
||||||
("in", "Is a member of (doesn't use regex)"),
|
|
||||||
("not-in", "Is not a member of (doesn't use regex)"),
|
|
||||||
],
|
|
||||||
Expr::Bool(_) => vec![
|
|
||||||
(
|
|
||||||
"and",
|
|
||||||
"Both values are true (short-circuits when first value is false)",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"or",
|
|
||||||
"Either value is true (short-circuits when first value is true)",
|
|
||||||
),
|
|
||||||
("xor", "One value is true and the other is false"),
|
|
||||||
("not", "Negates a value or expression"),
|
|
||||||
("in", "Is a member of (doesn't use regex)"),
|
|
||||||
("not-in", "Is not a member of (doesn't use regex)"),
|
|
||||||
],
|
|
||||||
Expr::FullCellPath(path) => match path.head.expr {
|
|
||||||
Expr::List(_) => vec![(
|
|
||||||
"++",
|
|
||||||
"Concatenates two lists, two strings, or two binary values",
|
|
||||||
)],
|
|
||||||
Expr::Var(id) => get_variable_completions(id, working_set),
|
|
||||||
_ => vec![],
|
|
||||||
},
|
},
|
||||||
_ => vec![],
|
_ => common_comparison_ops(),
|
||||||
};
|
};
|
||||||
|
// If the left hand side is a variable, add assignment operators if mutable
|
||||||
|
if needs_assignment_ops && is_expression_mutable(&self.left_hand_side.expr, working_set) {
|
||||||
|
possible_operations.extend(match &self.left_hand_side.ty {
|
||||||
|
Type::Int | Type::Float | Type::Number => numeric_assignment_ops(),
|
||||||
|
Type::Filesize | Type::Duration => numeric_assignment_ops(),
|
||||||
|
Type::String | Type::Binary | Type::List(_) => concat_assignment_ops(),
|
||||||
|
Type::Any => all_assignment_ops(),
|
||||||
|
_ => vec![operator_to_item(ast::Assignment::Assign)],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let mut matcher = NuMatcher::new(partial, options.clone());
|
let mut matcher = NuMatcher::new(prefix, options);
|
||||||
for (symbol, desc) in possible_operations.into_iter() {
|
for OperatorItem {
|
||||||
|
symbols,
|
||||||
|
description,
|
||||||
|
} in possible_operations
|
||||||
|
{
|
||||||
matcher.add_semantic_suggestion(SemanticSuggestion {
|
matcher.add_semantic_suggestion(SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: symbol.to_string(),
|
value: symbols.to_owned(),
|
||||||
description: Some(desc.to_string()),
|
description: Some(description.to_owned()),
|
||||||
span: reedline::Span::new(span.start - offset, span.end - offset),
|
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||||
append_whitespace: true,
|
append_whitespace: true,
|
||||||
..Suggestion::default()
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
kind: Some(SuggestionKind::Command(
|
kind: Some(SuggestionKind::Operator),
|
||||||
nu_protocol::engine::CommandType::Builtin,
|
|
||||||
)),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
matcher.results()
|
matcher.results()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_variable_completions<'a>(
|
|
||||||
id: nu_protocol::Id<nu_protocol::marker::Var>,
|
|
||||||
working_set: &StateWorkingSet,
|
|
||||||
) -> Vec<(&'a str, &'a str)> {
|
|
||||||
let var = working_set.get_variable(id);
|
|
||||||
if !var.mutable {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
|
|
||||||
match var.ty {
|
|
||||||
Type::List(_) | Type::String | Type::Binary => vec![
|
|
||||||
(
|
|
||||||
"++=",
|
|
||||||
"Concatenates two lists, two strings, or two binary values",
|
|
||||||
),
|
|
||||||
("=", "Assigns a value to a variable."),
|
|
||||||
],
|
|
||||||
|
|
||||||
Type::Int | Type::Float => vec![
|
|
||||||
("=", "Assigns a value to a variable."),
|
|
||||||
("+=", "Adds a value to a variable."),
|
|
||||||
("-=", "Subtracts a value from a variable."),
|
|
||||||
("*=", "Multiplies a variable by a value"),
|
|
||||||
("/=", "Divides a variable by a value."),
|
|
||||||
],
|
|
||||||
_ => vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,157 +1,67 @@
|
|||||||
use crate::completions::{Completer, CompletionOptions, SemanticSuggestion, SuggestionKind};
|
use crate::completions::{Completer, CompletionOptions, SemanticSuggestion, SuggestionKind};
|
||||||
use nu_engine::{column::get_columns, eval_variable};
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{Stack, StateWorkingSet},
|
engine::{Stack, StateWorkingSet},
|
||||||
Span, Value,
|
Span, VarId,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::str;
|
|
||||||
|
|
||||||
use super::completion_options::NuMatcher;
|
use super::completion_options::NuMatcher;
|
||||||
|
|
||||||
#[derive(Clone)]
|
pub struct VariableCompletion;
|
||||||
pub struct VariableCompletion {
|
|
||||||
var_context: (Vec<u8>, Vec<Vec<u8>>), // tuple with $var and the sublevels (.b.c.d)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VariableCompletion {
|
|
||||||
pub fn new(var_context: (Vec<u8>, Vec<Vec<u8>>)) -> Self {
|
|
||||||
Self { var_context }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Completer for VariableCompletion {
|
impl Completer for VariableCompletion {
|
||||||
fn fetch(
|
fn fetch(
|
||||||
&mut self,
|
&mut self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
stack: &Stack,
|
_stack: &Stack,
|
||||||
prefix: &[u8],
|
prefix: impl AsRef<str>,
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_pos: usize,
|
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let builtins = ["$nu", "$in", "$env"];
|
let mut matcher = NuMatcher::new(prefix, options);
|
||||||
let var_str = std::str::from_utf8(&self.var_context.0).unwrap_or("");
|
|
||||||
let var_id = working_set.find_variable(&self.var_context.0);
|
|
||||||
let current_span = reedline::Span {
|
let current_span = reedline::Span {
|
||||||
start: span.start - offset,
|
start: span.start - offset,
|
||||||
end: span.end - offset,
|
end: span.end - offset,
|
||||||
};
|
};
|
||||||
let sublevels_count = self.var_context.1.len();
|
|
||||||
let prefix_str = String::from_utf8_lossy(prefix);
|
|
||||||
let mut matcher = NuMatcher::new(prefix_str, options.clone());
|
|
||||||
|
|
||||||
// Completions for the given variable
|
|
||||||
if !var_str.is_empty() {
|
|
||||||
// Completion for $env.<tab>
|
|
||||||
if var_str == "$env" {
|
|
||||||
let env_vars = stack.get_env_vars(working_set.permanent_state);
|
|
||||||
|
|
||||||
// Return nested values
|
|
||||||
if sublevels_count > 0 {
|
|
||||||
// Extract the target var ($env.<target-var>)
|
|
||||||
let target_var = self.var_context.1[0].clone();
|
|
||||||
let target_var_str =
|
|
||||||
str::from_utf8(&target_var).unwrap_or_default().to_string();
|
|
||||||
|
|
||||||
// Everything after the target var is the nested level ($env.<target-var>.<nested_levels>...)
|
|
||||||
let nested_levels: Vec<Vec<u8>> =
|
|
||||||
self.var_context.1.clone().into_iter().skip(1).collect();
|
|
||||||
|
|
||||||
if let Some(val) = env_vars.get(&target_var_str) {
|
|
||||||
for suggestion in nested_suggestions(val, &nested_levels, current_span) {
|
|
||||||
matcher.add_semantic_suggestion(suggestion);
|
|
||||||
}
|
|
||||||
|
|
||||||
return matcher.results();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No nesting provided, return all env vars
|
|
||||||
for env_var in env_vars {
|
|
||||||
matcher.add_semantic_suggestion(SemanticSuggestion {
|
|
||||||
suggestion: Suggestion {
|
|
||||||
value: env_var.0,
|
|
||||||
span: current_span,
|
|
||||||
..Suggestion::default()
|
|
||||||
},
|
|
||||||
kind: Some(SuggestionKind::Type(env_var.1.get_type())),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return matcher.results();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Completions for $nu.<tab>
|
|
||||||
if var_str == "$nu" {
|
|
||||||
// Eval nu var
|
|
||||||
if let Ok(nuval) = eval_variable(
|
|
||||||
working_set.permanent_state,
|
|
||||||
stack,
|
|
||||||
nu_protocol::NU_VARIABLE_ID,
|
|
||||||
nu_protocol::Span::new(current_span.start, current_span.end),
|
|
||||||
) {
|
|
||||||
for suggestion in nested_suggestions(&nuval, &self.var_context.1, current_span)
|
|
||||||
{
|
|
||||||
matcher.add_semantic_suggestion(suggestion);
|
|
||||||
}
|
|
||||||
|
|
||||||
return matcher.results();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Completion other variable types
|
|
||||||
if let Some(var_id) = var_id {
|
|
||||||
// Extract the variable value from the stack
|
|
||||||
let var = stack.get_var(var_id, Span::new(span.start, span.end));
|
|
||||||
|
|
||||||
// If the value exists and it's of type Record
|
|
||||||
if let Ok(value) = var {
|
|
||||||
for suggestion in nested_suggestions(&value, &self.var_context.1, current_span)
|
|
||||||
{
|
|
||||||
matcher.add_semantic_suggestion(suggestion);
|
|
||||||
}
|
|
||||||
|
|
||||||
return matcher.results();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variable completion (e.g: $en<tab> to complete $env)
|
// Variable completion (e.g: $en<tab> to complete $env)
|
||||||
|
let builtins = ["$nu", "$in", "$env"];
|
||||||
for builtin in builtins {
|
for builtin in builtins {
|
||||||
matcher.add_semantic_suggestion(SemanticSuggestion {
|
matcher.add_semantic_suggestion(SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: builtin.to_string(),
|
value: builtin.to_string(),
|
||||||
span: current_span,
|
span: current_span,
|
||||||
|
description: Some("reserved".into()),
|
||||||
..Suggestion::default()
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
// TODO is there a way to get the VarId to get the type???
|
kind: Some(SuggestionKind::Variable),
|
||||||
kind: None,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut add_candidate = |name, var_id: &VarId| {
|
||||||
|
matcher.add_semantic_suggestion(SemanticSuggestion {
|
||||||
|
suggestion: Suggestion {
|
||||||
|
value: String::from_utf8_lossy(name).to_string(),
|
||||||
|
span: current_span,
|
||||||
|
description: Some(working_set.get_variable(*var_id).ty.to_string()),
|
||||||
|
..Suggestion::default()
|
||||||
|
},
|
||||||
|
kind: Some(SuggestionKind::Variable),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: The following can be refactored (see find_commands_by_predicate() used in
|
// TODO: The following can be refactored (see find_commands_by_predicate() used in
|
||||||
// command_completions).
|
// command_completions).
|
||||||
let mut removed_overlays = vec![];
|
let mut removed_overlays = vec![];
|
||||||
// Working set scope vars
|
// Working set scope vars
|
||||||
for scope_frame in working_set.delta.scope.iter().rev() {
|
for scope_frame in working_set.delta.scope.iter().rev() {
|
||||||
for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() {
|
for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() {
|
||||||
for v in &overlay_frame.vars {
|
for (name, var_id) in &overlay_frame.vars {
|
||||||
matcher.add_semantic_suggestion(SemanticSuggestion {
|
add_candidate(name, var_id);
|
||||||
suggestion: Suggestion {
|
|
||||||
value: String::from_utf8_lossy(v.0).to_string(),
|
|
||||||
span: current_span,
|
|
||||||
..Suggestion::default()
|
|
||||||
},
|
|
||||||
kind: Some(SuggestionKind::Type(
|
|
||||||
working_set.get_variable(*v.1).ty.clone(),
|
|
||||||
)),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Permanent state vars
|
// Permanent state vars
|
||||||
// for scope in &self.engine_state.scope {
|
// for scope in &self.engine_state.scope {
|
||||||
for overlay_frame in working_set
|
for overlay_frame in working_set
|
||||||
@ -159,98 +69,11 @@ impl Completer for VariableCompletion {
|
|||||||
.active_overlays(&removed_overlays)
|
.active_overlays(&removed_overlays)
|
||||||
.rev()
|
.rev()
|
||||||
{
|
{
|
||||||
for v in &overlay_frame.vars {
|
for (name, var_id) in &overlay_frame.vars {
|
||||||
matcher.add_semantic_suggestion(SemanticSuggestion {
|
add_candidate(name, var_id);
|
||||||
suggestion: Suggestion {
|
|
||||||
value: String::from_utf8_lossy(v.0).to_string(),
|
|
||||||
span: current_span,
|
|
||||||
..Suggestion::default()
|
|
||||||
},
|
|
||||||
kind: Some(SuggestionKind::Type(
|
|
||||||
working_set.get_variable(*v.1).ty.clone(),
|
|
||||||
)),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
matcher.results()
|
matcher.results()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find recursively the values for sublevels
|
|
||||||
// if no sublevels are set it returns the current value
|
|
||||||
fn nested_suggestions(
|
|
||||||
val: &Value,
|
|
||||||
sublevels: &[Vec<u8>],
|
|
||||||
current_span: reedline::Span,
|
|
||||||
) -> Vec<SemanticSuggestion> {
|
|
||||||
let mut output: Vec<SemanticSuggestion> = vec![];
|
|
||||||
let value = recursive_value(val, sublevels).unwrap_or_else(Value::nothing);
|
|
||||||
|
|
||||||
let kind = SuggestionKind::Type(value.get_type());
|
|
||||||
match value {
|
|
||||||
Value::Record { val, .. } => {
|
|
||||||
// Add all the columns as completion
|
|
||||||
for col in val.columns() {
|
|
||||||
output.push(SemanticSuggestion {
|
|
||||||
suggestion: Suggestion {
|
|
||||||
value: col.clone(),
|
|
||||||
span: current_span,
|
|
||||||
..Suggestion::default()
|
|
||||||
},
|
|
||||||
kind: Some(kind.clone()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
Value::List { vals, .. } => {
|
|
||||||
for column_name in get_columns(vals.as_slice()) {
|
|
||||||
output.push(SemanticSuggestion {
|
|
||||||
suggestion: Suggestion {
|
|
||||||
value: column_name,
|
|
||||||
span: current_span,
|
|
||||||
..Suggestion::default()
|
|
||||||
},
|
|
||||||
kind: Some(kind.clone()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
_ => output,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extracts the recursive value (e.g: $var.a.b.c)
|
|
||||||
fn recursive_value(val: &Value, sublevels: &[Vec<u8>]) -> Result<Value, Span> {
|
|
||||||
// Go to next sublevel
|
|
||||||
if let Some((sublevel, next_sublevels)) = sublevels.split_first() {
|
|
||||||
let span = val.span();
|
|
||||||
match val {
|
|
||||||
Value::Record { val, .. } => {
|
|
||||||
if let Some((_, value)) = val.iter().find(|(key, _)| key.as_bytes() == sublevel) {
|
|
||||||
// If matches try to fetch recursively the next
|
|
||||||
recursive_value(value, next_sublevels)
|
|
||||||
} else {
|
|
||||||
// Current sublevel value not found
|
|
||||||
Err(span)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::List { vals, .. } => {
|
|
||||||
for col in get_columns(vals.as_slice()) {
|
|
||||||
if col.as_bytes() == *sublevel {
|
|
||||||
let val = val.get_data_by_key(&col).ok_or(span)?;
|
|
||||||
return recursive_value(&val, next_sublevels);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Current sublevel value not found
|
|
||||||
Err(span)
|
|
||||||
}
|
|
||||||
_ => Ok(val.clone()),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(val.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -18,7 +18,7 @@ const OLD_PLUGIN_FILE: &str = "plugin.nu";
|
|||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Spanned<String>>) {
|
pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Spanned<String>>) {
|
||||||
use nu_protocol::ShellError;
|
use nu_protocol::{shell_error::io::IoError, ShellError};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
let span = plugin_file.as_ref().map(|s| s.span);
|
let span = plugin_file.as_ref().map(|s| s.span);
|
||||||
@ -49,7 +49,10 @@ pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Span
|
|||||||
perf!(
|
perf!(
|
||||||
"add plugin file to engine_state",
|
"add plugin file to engine_state",
|
||||||
start_time,
|
start_time,
|
||||||
engine_state.get_config().use_ansi_coloring
|
engine_state
|
||||||
|
.get_config()
|
||||||
|
.use_ansi_coloring
|
||||||
|
.get(engine_state)
|
||||||
);
|
);
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
@ -75,16 +78,12 @@ pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Span
|
|||||||
} else {
|
} else {
|
||||||
report_shell_error(
|
report_shell_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
&ShellError::GenericError {
|
&ShellError::Io(IoError::new_internal_with_path(
|
||||||
error: format!(
|
err.kind(),
|
||||||
"Error while opening plugin registry file: {}",
|
"Could not open plugin registry file",
|
||||||
plugin_path.display()
|
nu_protocol::location!(),
|
||||||
),
|
plugin_path,
|
||||||
msg: "plugin path defined here".into(),
|
)),
|
||||||
span,
|
|
||||||
help: None,
|
|
||||||
inner: vec![err.into()],
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -129,7 +128,10 @@ pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Span
|
|||||||
perf!(
|
perf!(
|
||||||
&format!("read plugin file {}", plugin_path.display()),
|
&format!("read plugin file {}", plugin_path.display()),
|
||||||
start_time,
|
start_time,
|
||||||
engine_state.get_config().use_ansi_coloring
|
engine_state
|
||||||
|
.get_config()
|
||||||
|
.use_ansi_coloring
|
||||||
|
.get(engine_state)
|
||||||
);
|
);
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
|
|
||||||
@ -145,7 +147,10 @@ pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Span
|
|||||||
perf!(
|
perf!(
|
||||||
&format!("load plugin file {}", plugin_path.display()),
|
&format!("load plugin file {}", plugin_path.display()),
|
||||||
start_time,
|
start_time,
|
||||||
engine_state.get_config().use_ansi_coloring
|
engine_state
|
||||||
|
.get_config()
|
||||||
|
.use_ansi_coloring
|
||||||
|
.get(engine_state)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,8 +230,8 @@ pub fn eval_config_contents(
|
|||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
pub fn migrate_old_plugin_file(engine_state: &EngineState) -> bool {
|
pub fn migrate_old_plugin_file(engine_state: &EngineState) -> bool {
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
PluginExample, PluginIdentity, PluginRegistryItem, PluginRegistryItemData, PluginSignature,
|
shell_error::io::IoError, PluginExample, PluginIdentity, PluginRegistryItem,
|
||||||
ShellError,
|
PluginRegistryItemData, PluginSignature, ShellError,
|
||||||
};
|
};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
@ -315,7 +320,15 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState) -> bool {
|
|||||||
// Write the new file
|
// Write the new file
|
||||||
let new_plugin_file_path = config_dir.join(PLUGIN_FILE);
|
let new_plugin_file_path = config_dir.join(PLUGIN_FILE);
|
||||||
if let Err(err) = std::fs::File::create(&new_plugin_file_path)
|
if let Err(err) = std::fs::File::create(&new_plugin_file_path)
|
||||||
.map_err(|e| e.into())
|
.map_err(|err| {
|
||||||
|
IoError::new_internal_with_path(
|
||||||
|
err.kind(),
|
||||||
|
"Could not create new plugin file",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
new_plugin_file_path.clone(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map_err(ShellError::from)
|
||||||
.and_then(|file| contents.write_to(file, None))
|
.and_then(|file| contents.write_to(file, None))
|
||||||
{
|
{
|
||||||
report_shell_error(
|
report_shell_error(
|
||||||
@ -345,7 +358,10 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState) -> bool {
|
|||||||
perf!(
|
perf!(
|
||||||
"migrate old plugin file",
|
"migrate old plugin file",
|
||||||
start_time,
|
start_time,
|
||||||
engine_state.get_config().use_ansi_coloring
|
engine_state
|
||||||
|
.get_config()
|
||||||
|
.use_ansi_coloring
|
||||||
|
.get(&engine_state)
|
||||||
);
|
);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use log::info;
|
use log::info;
|
||||||
use nu_engine::{convert_env_values, eval_block};
|
use nu_engine::eval_block;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
cli_error::report_compile_error,
|
cli_error::report_compile_error,
|
||||||
@ -50,9 +50,6 @@ pub fn evaluate_commands(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate environment variables from Strings to Values
|
|
||||||
convert_env_values(engine_state, stack)?;
|
|
||||||
|
|
||||||
// Parse the source code
|
// Parse the source code
|
||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
if let Some(ref t_mode) = table_mode {
|
if let Some(ref t_mode) = table_mode {
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
use crate::util::{eval_source, print_pipeline};
|
use crate::util::{eval_source, print_pipeline};
|
||||||
use log::{info, trace};
|
use log::{info, trace};
|
||||||
use nu_engine::{convert_env_values, eval_block};
|
use nu_engine::eval_block;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_path::canonicalize_with;
|
use nu_path::canonicalize_with;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
cli_error::report_compile_error,
|
cli_error::report_compile_error,
|
||||||
debugger::WithoutDebug,
|
debugger::WithoutDebug,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
report_parse_error, report_parse_warning, PipelineData, ShellError, Span, Value,
|
report_parse_error, report_parse_warning,
|
||||||
|
shell_error::io::*,
|
||||||
|
PipelineData, ShellError, Span, Value,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
/// Entry point for evaluating a file.
|
/// Entry point for evaluating a file.
|
||||||
///
|
///
|
||||||
@ -22,15 +24,15 @@ pub fn evaluate_file(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
// Convert environment variables from Strings to Values and store them in the engine state.
|
|
||||||
convert_env_values(engine_state, stack)?;
|
|
||||||
|
|
||||||
let cwd = engine_state.cwd_as_string(Some(stack))?;
|
let cwd = engine_state.cwd_as_string(Some(stack))?;
|
||||||
|
|
||||||
let file_path =
|
let file_path = canonicalize_with(&path, cwd).map_err(|err| {
|
||||||
canonicalize_with(&path, cwd).map_err(|err| ShellError::FileNotFoundCustom {
|
IoError::new_internal_with_path(
|
||||||
msg: format!("Could not access file '{path}': {err}"),
|
err.kind().not_found_as(NotFound::File),
|
||||||
span: Span::unknown(),
|
"Could not access file",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
PathBuf::from(&path),
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let file_path_str = file_path
|
let file_path_str = file_path
|
||||||
@ -43,17 +45,23 @@ pub fn evaluate_file(
|
|||||||
span: Span::unknown(),
|
span: Span::unknown(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let file = std::fs::read(&file_path).map_err(|err| ShellError::FileNotFoundCustom {
|
let file = std::fs::read(&file_path).map_err(|err| {
|
||||||
msg: format!("Could not read file '{file_path_str}': {err}"),
|
IoError::new_internal_with_path(
|
||||||
span: Span::unknown(),
|
err.kind().not_found_as(NotFound::File),
|
||||||
|
"Could not read file",
|
||||||
|
nu_protocol::location!(),
|
||||||
|
file_path.clone(),
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
engine_state.file = Some(file_path.clone());
|
engine_state.file = Some(file_path.clone());
|
||||||
|
|
||||||
let parent = file_path
|
let parent = file_path.parent().ok_or_else(|| {
|
||||||
.parent()
|
IoError::new_internal_with_path(
|
||||||
.ok_or_else(|| ShellError::FileNotFoundCustom {
|
ErrorKind::DirectoryNotFound,
|
||||||
msg: format!("The file path '{file_path_str}' does not have a parent"),
|
"The file path does not have a parent",
|
||||||
span: Span::unknown(),
|
nu_protocol::location!(),
|
||||||
|
file_path.clone(),
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
|
@ -87,14 +87,6 @@ fn get_prompt_string(
|
|||||||
x.insert_str(0, "\x1b[0m")
|
x.insert_str(0, "\x1b[0m")
|
||||||
};
|
};
|
||||||
|
|
||||||
// Just remove the very last newline.
|
|
||||||
if x.ends_with('\n') {
|
|
||||||
x.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if x.ends_with('\r') {
|
|
||||||
x.pop();
|
|
||||||
}
|
|
||||||
x
|
x
|
||||||
});
|
});
|
||||||
// Let's keep this for debugging purposes with nu --log-level warn
|
// Let's keep this for debugging purposes with nu --log-level warn
|
||||||
|
@ -740,9 +740,15 @@ fn add_keybinding(
|
|||||||
let span = mode.span();
|
let span = mode.span();
|
||||||
match &mode {
|
match &mode {
|
||||||
Value::String { val, .. } => match val.as_str() {
|
Value::String { val, .. } => match val.as_str() {
|
||||||
"emacs" => add_parsed_keybinding(emacs_keybindings, keybinding, config),
|
str if str.eq_ignore_ascii_case("emacs") => {
|
||||||
"vi_insert" => add_parsed_keybinding(insert_keybindings, keybinding, config),
|
add_parsed_keybinding(emacs_keybindings, keybinding, config)
|
||||||
"vi_normal" => add_parsed_keybinding(normal_keybindings, keybinding, config),
|
}
|
||||||
|
str if str.eq_ignore_ascii_case("vi_insert") => {
|
||||||
|
add_parsed_keybinding(insert_keybindings, keybinding, config)
|
||||||
|
}
|
||||||
|
str if str.eq_ignore_ascii_case("vi_normal") => {
|
||||||
|
add_parsed_keybinding(normal_keybindings, keybinding, config)
|
||||||
|
}
|
||||||
str => Err(ShellError::InvalidValue {
|
str => Err(ShellError::InvalidValue {
|
||||||
valid: "'emacs', 'vi_insert', or 'vi_normal'".into(),
|
valid: "'emacs', 'vi_insert', or 'vi_normal'".into(),
|
||||||
actual: format!("'{str}'"),
|
actual: format!("'{str}'"),
|
||||||
@ -992,41 +998,54 @@ fn event_from_record(
|
|||||||
) -> Result<ReedlineEvent, ShellError> {
|
) -> Result<ReedlineEvent, ShellError> {
|
||||||
let event = match name {
|
let event = match name {
|
||||||
"none" => ReedlineEvent::None,
|
"none" => ReedlineEvent::None,
|
||||||
"clearscreen" => ReedlineEvent::ClearScreen,
|
|
||||||
"clearscrollback" => ReedlineEvent::ClearScrollback,
|
|
||||||
"historyhintcomplete" => ReedlineEvent::HistoryHintComplete,
|
"historyhintcomplete" => ReedlineEvent::HistoryHintComplete,
|
||||||
"historyhintwordcomplete" => ReedlineEvent::HistoryHintWordComplete,
|
"historyhintwordcomplete" => ReedlineEvent::HistoryHintWordComplete,
|
||||||
"ctrld" => ReedlineEvent::CtrlD,
|
"ctrld" => ReedlineEvent::CtrlD,
|
||||||
"ctrlc" => ReedlineEvent::CtrlC,
|
"ctrlc" => ReedlineEvent::CtrlC,
|
||||||
|
"clearscreen" => ReedlineEvent::ClearScreen,
|
||||||
|
"clearscrollback" => ReedlineEvent::ClearScrollback,
|
||||||
"enter" => ReedlineEvent::Enter,
|
"enter" => ReedlineEvent::Enter,
|
||||||
"submit" => ReedlineEvent::Submit,
|
"submit" => ReedlineEvent::Submit,
|
||||||
"submitornewline" => ReedlineEvent::SubmitOrNewline,
|
"submitornewline" => ReedlineEvent::SubmitOrNewline,
|
||||||
"esc" | "escape" => ReedlineEvent::Esc,
|
"esc" | "escape" => ReedlineEvent::Esc,
|
||||||
|
// Non-sensical for user configuration:
|
||||||
|
//
|
||||||
|
// `ReedlineEvent::Mouse` - itself a no-op
|
||||||
|
// `ReedlineEvent::Resize` - requires size info specifically from the ANSI resize
|
||||||
|
// event
|
||||||
|
//
|
||||||
|
// Handled above in `parse_event`:
|
||||||
|
//
|
||||||
|
// `ReedlineEvent::Edit`
|
||||||
|
"repaint" => ReedlineEvent::Repaint,
|
||||||
|
"previoushistory" => ReedlineEvent::PreviousHistory,
|
||||||
"up" => ReedlineEvent::Up,
|
"up" => ReedlineEvent::Up,
|
||||||
"down" => ReedlineEvent::Down,
|
"down" => ReedlineEvent::Down,
|
||||||
"right" => ReedlineEvent::Right,
|
"right" => ReedlineEvent::Right,
|
||||||
"left" => ReedlineEvent::Left,
|
"left" => ReedlineEvent::Left,
|
||||||
"searchhistory" => ReedlineEvent::SearchHistory,
|
|
||||||
"nexthistory" => ReedlineEvent::NextHistory,
|
"nexthistory" => ReedlineEvent::NextHistory,
|
||||||
"previoushistory" => ReedlineEvent::PreviousHistory,
|
"searchhistory" => ReedlineEvent::SearchHistory,
|
||||||
"repaint" => ReedlineEvent::Repaint,
|
// Handled above in `parse_event`:
|
||||||
"menudown" => ReedlineEvent::MenuDown,
|
//
|
||||||
"menuup" => ReedlineEvent::MenuUp,
|
// `ReedlineEvent::Multiple`
|
||||||
"menuleft" => ReedlineEvent::MenuLeft,
|
// `ReedlineEvent::UntilFound`
|
||||||
"menuright" => ReedlineEvent::MenuRight,
|
|
||||||
"menunext" => ReedlineEvent::MenuNext,
|
|
||||||
"menuprevious" => ReedlineEvent::MenuPrevious,
|
|
||||||
"menupagenext" => ReedlineEvent::MenuPageNext,
|
|
||||||
"menupageprevious" => ReedlineEvent::MenuPagePrevious,
|
|
||||||
"openeditor" => ReedlineEvent::OpenEditor,
|
|
||||||
"menu" => {
|
"menu" => {
|
||||||
let menu = extract_value("name", record, span)?;
|
let menu = extract_value("name", record, span)?;
|
||||||
ReedlineEvent::Menu(menu.to_expanded_string("", config))
|
ReedlineEvent::Menu(menu.to_expanded_string("", config))
|
||||||
}
|
}
|
||||||
|
"menunext" => ReedlineEvent::MenuNext,
|
||||||
|
"menuprevious" => ReedlineEvent::MenuPrevious,
|
||||||
|
"menuup" => ReedlineEvent::MenuUp,
|
||||||
|
"menudown" => ReedlineEvent::MenuDown,
|
||||||
|
"menuleft" => ReedlineEvent::MenuLeft,
|
||||||
|
"menuright" => ReedlineEvent::MenuRight,
|
||||||
|
"menupagenext" => ReedlineEvent::MenuPageNext,
|
||||||
|
"menupageprevious" => ReedlineEvent::MenuPagePrevious,
|
||||||
"executehostcommand" => {
|
"executehostcommand" => {
|
||||||
let cmd = extract_value("cmd", record, span)?;
|
let cmd = extract_value("cmd", record, span)?;
|
||||||
ReedlineEvent::ExecuteHostCommand(cmd.to_expanded_string("", config))
|
ReedlineEvent::ExecuteHostCommand(cmd.to_expanded_string("", config))
|
||||||
}
|
}
|
||||||
|
"openeditor" => ReedlineEvent::OpenEditor,
|
||||||
str => {
|
str => {
|
||||||
return Err(ShellError::InvalidValue {
|
return Err(ShellError::InvalidValue {
|
||||||
valid: "a reedline event".into(),
|
valid: "a reedline event".into(),
|
||||||
@ -1056,7 +1075,6 @@ fn edit_from_record(
|
|||||||
.and_then(|value| value.as_bool())
|
.and_then(|value| value.as_bool())
|
||||||
.unwrap_or(false),
|
.unwrap_or(false),
|
||||||
},
|
},
|
||||||
|
|
||||||
"movetoend" => EditCommand::MoveToEnd {
|
"movetoend" => EditCommand::MoveToEnd {
|
||||||
select: extract_value("select", record, span)
|
select: extract_value("select", record, span)
|
||||||
.and_then(|value| value.as_bool())
|
.and_then(|value| value.as_bool())
|
||||||
@ -1092,16 +1110,6 @@ fn edit_from_record(
|
|||||||
.and_then(|value| value.as_bool())
|
.and_then(|value| value.as_bool())
|
||||||
.unwrap_or(false),
|
.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 {
|
"movewordrightstart" => EditCommand::MoveWordRightStart {
|
||||||
select: extract_value("select", record, span)
|
select: extract_value("select", record, span)
|
||||||
.and_then(|value| value.as_bool())
|
.and_then(|value| value.as_bool())
|
||||||
@ -1112,6 +1120,16 @@ fn edit_from_record(
|
|||||||
.and_then(|value| value.as_bool())
|
.and_then(|value| value.as_bool())
|
||||||
.unwrap_or(false),
|
.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),
|
||||||
|
},
|
||||||
"movetoposition" => {
|
"movetoposition" => {
|
||||||
let value = extract_value("value", record, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let select = extract_value("select", record, span)
|
let select = extract_value("select", record, span)
|
||||||
@ -1133,6 +1151,13 @@ fn edit_from_record(
|
|||||||
EditCommand::InsertString(value.to_expanded_string("", config))
|
EditCommand::InsertString(value.to_expanded_string("", config))
|
||||||
}
|
}
|
||||||
"insertnewline" => EditCommand::InsertNewline,
|
"insertnewline" => EditCommand::InsertNewline,
|
||||||
|
"replacechar" => {
|
||||||
|
let value = extract_value("value", record, span)?;
|
||||||
|
let char = extract_char(value)?;
|
||||||
|
EditCommand::ReplaceChar(char)
|
||||||
|
}
|
||||||
|
// `EditCommand::ReplaceChars` - Internal hack not sanely implementable as a
|
||||||
|
// standalone binding
|
||||||
"backspace" => EditCommand::Backspace,
|
"backspace" => EditCommand::Backspace,
|
||||||
"delete" => EditCommand::Delete,
|
"delete" => EditCommand::Delete,
|
||||||
"cutchar" => EditCommand::CutChar,
|
"cutchar" => EditCommand::CutChar,
|
||||||
@ -1140,6 +1165,7 @@ fn edit_from_record(
|
|||||||
"deleteword" => EditCommand::DeleteWord,
|
"deleteword" => EditCommand::DeleteWord,
|
||||||
"clear" => EditCommand::Clear,
|
"clear" => EditCommand::Clear,
|
||||||
"cleartolineend" => EditCommand::ClearToLineEnd,
|
"cleartolineend" => EditCommand::ClearToLineEnd,
|
||||||
|
"complete" => EditCommand::Complete,
|
||||||
"cutcurrentline" => EditCommand::CutCurrentLine,
|
"cutcurrentline" => EditCommand::CutCurrentLine,
|
||||||
"cutfromstart" => EditCommand::CutFromStart,
|
"cutfromstart" => EditCommand::CutFromStart,
|
||||||
"cutfromlinestart" => EditCommand::CutFromLineStart,
|
"cutfromlinestart" => EditCommand::CutFromLineStart,
|
||||||
@ -1156,6 +1182,7 @@ fn edit_from_record(
|
|||||||
"uppercaseword" => EditCommand::UppercaseWord,
|
"uppercaseword" => EditCommand::UppercaseWord,
|
||||||
"lowercaseword" => EditCommand::LowercaseWord,
|
"lowercaseword" => EditCommand::LowercaseWord,
|
||||||
"capitalizechar" => EditCommand::CapitalizeChar,
|
"capitalizechar" => EditCommand::CapitalizeChar,
|
||||||
|
"switchcasechar" => EditCommand::SwitchcaseChar,
|
||||||
"swapwords" => EditCommand::SwapWords,
|
"swapwords" => EditCommand::SwapWords,
|
||||||
"swapgraphemes" => EditCommand::SwapGraphemes,
|
"swapgraphemes" => EditCommand::SwapGraphemes,
|
||||||
"undo" => EditCommand::Undo,
|
"undo" => EditCommand::Undo,
|
||||||
@ -1212,17 +1239,64 @@ fn edit_from_record(
|
|||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
EditCommand::MoveLeftBefore { c: char, select }
|
EditCommand::MoveLeftBefore { c: char, select }
|
||||||
}
|
}
|
||||||
"complete" => EditCommand::Complete,
|
"selectall" => EditCommand::SelectAll,
|
||||||
"cutselection" => EditCommand::CutSelection,
|
"cutselection" => EditCommand::CutSelection,
|
||||||
|
"copyselection" => EditCommand::CopySelection,
|
||||||
|
"paste" => EditCommand::Paste,
|
||||||
|
"copyfromstart" => EditCommand::CopyFromStart,
|
||||||
|
"copyfromlinestart" => EditCommand::CopyFromLineStart,
|
||||||
|
"copytoend" => EditCommand::CopyToEnd,
|
||||||
|
"copytolineend" => EditCommand::CopyToLineEnd,
|
||||||
|
"copycurrentline" => EditCommand::CopyCurrentLine,
|
||||||
|
"copywordleft" => EditCommand::CopyWordLeft,
|
||||||
|
"copybigwordleft" => EditCommand::CopyBigWordLeft,
|
||||||
|
"copywordright" => EditCommand::CopyWordRight,
|
||||||
|
"copybigwordright" => EditCommand::CopyBigWordRight,
|
||||||
|
"copywordrighttonext" => EditCommand::CopyWordRightToNext,
|
||||||
|
"copybigwordrighttonext" => EditCommand::CopyBigWordRightToNext,
|
||||||
|
"copyleft" => EditCommand::CopyLeft,
|
||||||
|
"copyright" => EditCommand::CopyRight,
|
||||||
|
"copyrightuntil" => {
|
||||||
|
let value = extract_value("value", record, span)?;
|
||||||
|
let char = extract_char(value)?;
|
||||||
|
EditCommand::CopyRightUntil(char)
|
||||||
|
}
|
||||||
|
"copyrightbefore" => {
|
||||||
|
let value = extract_value("value", record, span)?;
|
||||||
|
let char = extract_char(value)?;
|
||||||
|
EditCommand::CopyRightBefore(char)
|
||||||
|
}
|
||||||
|
"copyleftuntil" => {
|
||||||
|
let value = extract_value("value", record, span)?;
|
||||||
|
let char = extract_char(value)?;
|
||||||
|
EditCommand::CopyLeftUntil(char)
|
||||||
|
}
|
||||||
|
"copyleftbefore" => {
|
||||||
|
let value = extract_value("value", record, span)?;
|
||||||
|
let char = extract_char(value)?;
|
||||||
|
EditCommand::CopyLeftBefore(char)
|
||||||
|
}
|
||||||
|
"swapcursorandanchor" => EditCommand::SwapCursorAndAnchor,
|
||||||
#[cfg(feature = "system-clipboard")]
|
#[cfg(feature = "system-clipboard")]
|
||||||
"cutselectionsystem" => EditCommand::CutSelectionSystem,
|
"cutselectionsystem" => EditCommand::CutSelectionSystem,
|
||||||
"copyselection" => EditCommand::CopySelection,
|
|
||||||
#[cfg(feature = "system-clipboard")]
|
#[cfg(feature = "system-clipboard")]
|
||||||
"copyselectionsystem" => EditCommand::CopySelectionSystem,
|
"copyselectionsystem" => EditCommand::CopySelectionSystem,
|
||||||
"paste" => EditCommand::Paste,
|
|
||||||
#[cfg(feature = "system-clipboard")]
|
#[cfg(feature = "system-clipboard")]
|
||||||
"pastesystem" => EditCommand::PasteSystem,
|
"pastesystem" => EditCommand::PasteSystem,
|
||||||
"selectall" => EditCommand::SelectAll,
|
"cutinside" => {
|
||||||
|
let value = extract_value("left", record, span)?;
|
||||||
|
let left = extract_char(value)?;
|
||||||
|
let value = extract_value("right", record, span)?;
|
||||||
|
let right = extract_char(value)?;
|
||||||
|
EditCommand::CutInside { left, right }
|
||||||
|
}
|
||||||
|
"yankinside" => {
|
||||||
|
let value = extract_value("left", record, span)?;
|
||||||
|
let left = extract_char(value)?;
|
||||||
|
let value = extract_value("right", record, span)?;
|
||||||
|
let right = extract_char(value)?;
|
||||||
|
EditCommand::YankInside { left, right }
|
||||||
|
}
|
||||||
str => {
|
str => {
|
||||||
return Err(ShellError::InvalidValue {
|
return Err(ShellError::InvalidValue {
|
||||||
valid: "a reedline EditCommand".into(),
|
valid: "a reedline EditCommand".into(),
|
||||||
|
@ -19,8 +19,10 @@ use miette::{ErrReport, IntoDiagnostic, Result};
|
|||||||
use nu_cmd_base::util::get_editor;
|
use nu_cmd_base::util::get_editor;
|
||||||
use nu_color_config::StyleComputer;
|
use nu_color_config::StyleComputer;
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
use nu_engine::{convert_env_values, current_dir_str, env_to_strings};
|
use nu_engine::env_to_strings;
|
||||||
|
use nu_engine::exit::cleanup_exit;
|
||||||
use nu_parser::{lex, parse, trim_quotes_str};
|
use nu_parser::{lex, parse, trim_quotes_str};
|
||||||
|
use nu_protocol::shell_error::io::IoError;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
config::NuCursorShape,
|
config::NuCursorShape,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
@ -35,6 +37,7 @@ use reedline::{
|
|||||||
CursorConfig, CwdAwareHinter, DefaultCompleter, EditCommand, Emacs, FileBackedHistory,
|
CursorConfig, CwdAwareHinter, DefaultCompleter, EditCommand, Emacs, FileBackedHistory,
|
||||||
HistorySessionId, Reedline, SqliteBackedHistory, Vi,
|
HistorySessionId, Reedline, SqliteBackedHistory, Vi,
|
||||||
};
|
};
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
env::temp_dir,
|
env::temp_dir,
|
||||||
@ -61,9 +64,7 @@ pub fn evaluate_repl(
|
|||||||
// from the Arc. This lets us avoid copying stack variables needlessly
|
// from the Arc. This lets us avoid copying stack variables needlessly
|
||||||
let mut unique_stack = stack.clone();
|
let mut unique_stack = stack.clone();
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
let use_color = config.use_ansi_coloring;
|
let use_color = config.use_ansi_coloring.get(engine_state);
|
||||||
|
|
||||||
confirm_stdin_is_terminal()?;
|
|
||||||
|
|
||||||
let mut entry_num = 0;
|
let mut entry_num = 0;
|
||||||
|
|
||||||
@ -81,13 +82,6 @@ pub fn evaluate_repl(
|
|||||||
stack.clone(),
|
stack.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let start_time = std::time::Instant::now();
|
|
||||||
// Translate environment variables from Strings to Values
|
|
||||||
if let Err(e) = convert_env_values(engine_state, &unique_stack) {
|
|
||||||
report_shell_error(engine_state, &e);
|
|
||||||
}
|
|
||||||
perf!("translate env vars", start_time, use_color);
|
|
||||||
|
|
||||||
// seed env vars
|
// seed env vars
|
||||||
unique_stack.add_env_var(
|
unique_stack.add_env_var(
|
||||||
"CMD_DURATION_MS".into(),
|
"CMD_DURATION_MS".into(),
|
||||||
@ -111,6 +105,8 @@ pub fn evaluate_repl(
|
|||||||
engine_state.merge_env(&mut unique_stack)?;
|
engine_state.merge_env(&mut unique_stack)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
confirm_stdin_is_terminal()?;
|
||||||
|
|
||||||
let hostname = System::host_name();
|
let hostname = System::host_name();
|
||||||
if shell_integration_osc2 {
|
if shell_integration_osc2 {
|
||||||
run_shell_integration_osc2(None, engine_state, &mut unique_stack, use_color);
|
run_shell_integration_osc2(None, engine_state, &mut unique_stack, use_color);
|
||||||
@ -146,7 +142,20 @@ pub fn evaluate_repl(
|
|||||||
// Regenerate the $nu constant to contain the startup time and any other potential updates
|
// Regenerate the $nu constant to contain the startup time and any other potential updates
|
||||||
engine_state.generate_nu_constant();
|
engine_state.generate_nu_constant();
|
||||||
|
|
||||||
if load_std_lib.is_none() && engine_state.get_config().show_banner {
|
if load_std_lib.is_none() {
|
||||||
|
match engine_state.get_config().show_banner {
|
||||||
|
Value::Bool { val: false, .. } => {}
|
||||||
|
Value::String { ref val, .. } if val == "short" => {
|
||||||
|
eval_source(
|
||||||
|
engine_state,
|
||||||
|
&mut unique_stack,
|
||||||
|
r#"banner --short"#.as_bytes(),
|
||||||
|
"show short banner",
|
||||||
|
PipelineData::empty(),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
eval_source(
|
eval_source(
|
||||||
engine_state,
|
engine_state,
|
||||||
&mut unique_stack,
|
&mut unique_stack,
|
||||||
@ -156,6 +165,8 @@ pub fn evaluate_repl(
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
kitty_protocol_healthcheck(engine_state);
|
kitty_protocol_healthcheck(engine_state);
|
||||||
|
|
||||||
@ -375,7 +386,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
)))
|
)))
|
||||||
.with_quick_completions(config.completions.quick)
|
.with_quick_completions(config.completions.quick)
|
||||||
.with_partial_completions(config.completions.partial)
|
.with_partial_completions(config.completions.partial)
|
||||||
.with_ansi_colors(config.use_ansi_coloring)
|
.with_ansi_colors(config.use_ansi_coloring.get(engine_state))
|
||||||
.with_cwd(Some(
|
.with_cwd(Some(
|
||||||
engine_state
|
engine_state
|
||||||
.cwd(None)
|
.cwd(None)
|
||||||
@ -395,7 +406,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
let style_computer = StyleComputer::from_config(engine_state, &stack_arc);
|
let style_computer = StyleComputer::from_config(engine_state, &stack_arc);
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
line_editor = if config.use_ansi_coloring {
|
line_editor = if config.use_ansi_coloring.get(engine_state) {
|
||||||
line_editor.with_hinter(Box::new({
|
line_editor.with_hinter(Box::new({
|
||||||
// As of Nov 2022, "hints" color_config closures only get `null` passed in.
|
// As of Nov 2022, "hints" color_config closures only get `null` passed in.
|
||||||
let style = style_computer.compute("hints", &Value::nothing(Span::unknown()));
|
let style = style_computer.compute("hints", &Value::nothing(Span::unknown()));
|
||||||
@ -683,7 +694,11 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
return (false, stack, line_editor);
|
|
||||||
|
cleanup_exit((), engine_state, 0);
|
||||||
|
|
||||||
|
// if cleanup_exit didn't exit, we should keep running
|
||||||
|
return (true, stack, line_editor);
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let message = err.to_string();
|
let message = err.to_string();
|
||||||
@ -801,8 +816,10 @@ fn parse_operation(
|
|||||||
) -> Result<ReplOperation, ErrReport> {
|
) -> Result<ReplOperation, ErrReport> {
|
||||||
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
|
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
|
||||||
// Check if this is a single call to a directory, if so auto-cd
|
// Check if this is a single call to a directory, if so auto-cd
|
||||||
#[allow(deprecated)]
|
let cwd = engine_state
|
||||||
let cwd = nu_engine::env::current_dir_str(engine_state, stack).unwrap_or_default();
|
.cwd(Some(stack))
|
||||||
|
.map(|p| p.to_string_lossy().to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
let mut orig = s.clone();
|
let mut orig = s.clone();
|
||||||
if orig.starts_with('`') {
|
if orig.starts_with('`') {
|
||||||
orig = trim_quotes_str(&orig).to_string()
|
orig = trim_quotes_str(&orig).to_string()
|
||||||
@ -836,21 +853,26 @@ fn do_auto_cd(
|
|||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
report_shell_error(
|
report_shell_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
&ShellError::DirectoryNotFound {
|
&ShellError::Io(IoError::new_with_additional_context(
|
||||||
dir: path.to_string_lossy().to_string(),
|
std::io::ErrorKind::NotFound,
|
||||||
span,
|
span,
|
||||||
},
|
PathBuf::from(&path),
|
||||||
|
"Cannot change directory",
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
path.to_string_lossy().to_string()
|
path.to_string_lossy().to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
if let PermissionResult::PermissionDenied(reason) = have_permission(path.clone()) {
|
if let PermissionResult::PermissionDenied = have_permission(path.clone()) {
|
||||||
report_shell_error(
|
report_shell_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
&ShellError::IOError {
|
&ShellError::Io(IoError::new_with_additional_context(
|
||||||
msg: format!("Cannot change directory to {path}: {reason}"),
|
std::io::ErrorKind::PermissionDenied,
|
||||||
},
|
span,
|
||||||
|
PathBuf::from(path),
|
||||||
|
"Cannot change directory",
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -914,6 +936,9 @@ fn do_run_cmd(
|
|||||||
trace!("eval source: {}", s);
|
trace!("eval source: {}", s);
|
||||||
|
|
||||||
let mut cmds = s.split_whitespace();
|
let mut cmds = s.split_whitespace();
|
||||||
|
|
||||||
|
let had_warning_before = engine_state.exit_warning_given.load(Ordering::SeqCst);
|
||||||
|
|
||||||
if let Some("exit") = cmds.next() {
|
if let Some("exit") = cmds.next() {
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
let _ = parse(&mut working_set, None, s.as_bytes(), false);
|
let _ = parse(&mut working_set, None, s.as_bytes(), false);
|
||||||
@ -922,13 +947,11 @@ fn do_run_cmd(
|
|||||||
match cmds.next() {
|
match cmds.next() {
|
||||||
Some(s) => {
|
Some(s) => {
|
||||||
if let Ok(n) = s.parse::<i32>() {
|
if let Ok(n) = s.parse::<i32>() {
|
||||||
drop(line_editor);
|
return cleanup_exit(line_editor, engine_state, n);
|
||||||
std::process::exit(n);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
drop(line_editor);
|
return cleanup_exit(line_editor, engine_state, 0);
|
||||||
std::process::exit(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -947,6 +970,14 @@ fn do_run_cmd(
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// if there was a warning before, and we got to this point, it means
|
||||||
|
// the possible call to cleanup_exit did not occur.
|
||||||
|
if had_warning_before && engine_state.is_interactive {
|
||||||
|
engine_state
|
||||||
|
.exit_warning_given
|
||||||
|
.store(false, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
line_editor
|
line_editor
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -961,8 +992,7 @@ fn run_shell_integration_osc2(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
use_color: bool,
|
use_color: bool,
|
||||||
) {
|
) {
|
||||||
#[allow(deprecated)]
|
if let Ok(path) = engine_state.cwd_as_string(Some(stack)) {
|
||||||
if let Ok(path) = current_dir_str(engine_state, stack) {
|
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
|
|
||||||
// Try to abbreviate string for windows title
|
// Try to abbreviate string for windows title
|
||||||
@ -1006,8 +1036,7 @@ fn run_shell_integration_osc7(
|
|||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
use_color: bool,
|
use_color: bool,
|
||||||
) {
|
) {
|
||||||
#[allow(deprecated)]
|
if let Ok(path) = engine_state.cwd_as_string(Some(stack)) {
|
||||||
if let Ok(path) = current_dir_str(engine_state, stack) {
|
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
|
|
||||||
// Otherwise, communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
|
// Otherwise, communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
|
||||||
@ -1030,8 +1059,7 @@ fn run_shell_integration_osc7(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_shell_integration_osc9_9(engine_state: &EngineState, stack: &mut Stack, use_color: bool) {
|
fn run_shell_integration_osc9_9(engine_state: &EngineState, stack: &mut Stack, use_color: bool) {
|
||||||
#[allow(deprecated)]
|
if let Ok(path) = engine_state.cwd_as_string(Some(stack)) {
|
||||||
if let Ok(path) = current_dir_str(engine_state, stack) {
|
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
|
|
||||||
// Otherwise, communicate the path as OSC 9;9 from ConEmu (often used for spawning new tabs in the same dir)
|
// Otherwise, communicate the path as OSC 9;9 from ConEmu (often used for spawning new tabs in the same dir)
|
||||||
@ -1055,8 +1083,7 @@ fn run_shell_integration_osc633(
|
|||||||
use_color: bool,
|
use_color: bool,
|
||||||
repl_cmd_line_text: String,
|
repl_cmd_line_text: String,
|
||||||
) {
|
) {
|
||||||
#[allow(deprecated)]
|
if let Ok(path) = engine_state.cwd_as_string(Some(stack)) {
|
||||||
if let Ok(path) = current_dir_str(engine_state, stack) {
|
|
||||||
// Supported escape sequences of Microsoft's Visual Studio Code (vscode)
|
// Supported escape sequences of Microsoft's Visual Studio Code (vscode)
|
||||||
// https://code.visualstudio.com/docs/terminal/shell-integration#_supported-escape-sequences
|
// https://code.visualstudio.com/docs/terminal/shell-integration#_supported-escape-sequences
|
||||||
if stack
|
if stack
|
||||||
@ -1147,7 +1174,7 @@ fn setup_history(
|
|||||||
/// Setup Reedline keybindingds based on the provided config
|
/// Setup Reedline keybindingds based on the provided config
|
||||||
///
|
///
|
||||||
fn setup_keybindings(engine_state: &EngineState, line_editor: Reedline) -> Reedline {
|
fn setup_keybindings(engine_state: &EngineState, line_editor: Reedline) -> Reedline {
|
||||||
return match create_keybindings(engine_state.get_config()) {
|
match create_keybindings(engine_state.get_config()) {
|
||||||
Ok(keybindings) => match keybindings {
|
Ok(keybindings) => match keybindings {
|
||||||
KeybindingsMode::Emacs(keybindings) => {
|
KeybindingsMode::Emacs(keybindings) => {
|
||||||
let edit_mode = Box::new(Emacs::new(keybindings));
|
let edit_mode = Box::new(Emacs::new(keybindings));
|
||||||
@ -1165,7 +1192,7 @@ fn setup_keybindings(engine_state: &EngineState, line_editor: Reedline) -> Reedl
|
|||||||
report_shell_error(engine_state, &e);
|
report_shell_error(engine_state, &e);
|
||||||
line_editor
|
line_editor
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -1562,6 +1589,13 @@ mod test_auto_cd {
|
|||||||
symlink(&dir, &link).unwrap();
|
symlink(&dir, &link).unwrap();
|
||||||
let input = if cfg!(windows) { r".\link" } else { "./link" };
|
let input = if cfg!(windows) { r".\link" } else { "./link" };
|
||||||
check(tempdir, input, link);
|
check(tempdir, input, link);
|
||||||
|
|
||||||
|
let dir = tempdir.join("foo").join("bar");
|
||||||
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
|
let link = tempdir.join("link2");
|
||||||
|
symlink(&dir, &link).unwrap();
|
||||||
|
let input = "..";
|
||||||
|
check(link, input, tempdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -309,6 +309,7 @@ fn find_matching_block_end_in_expr(
|
|||||||
.unwrap_or(expression.span.start);
|
.unwrap_or(expression.span.start);
|
||||||
|
|
||||||
return match &expression.expr {
|
return match &expression.expr {
|
||||||
|
// TODO: Can't these be handled with an `_ => None` branch? Refactor
|
||||||
Expr::Bool(_) => None,
|
Expr::Bool(_) => None,
|
||||||
Expr::Int(_) => None,
|
Expr::Int(_) => None,
|
||||||
Expr::Float(_) => None,
|
Expr::Float(_) => None,
|
||||||
@ -335,6 +336,28 @@ fn find_matching_block_end_in_expr(
|
|||||||
Expr::Nothing => None,
|
Expr::Nothing => None,
|
||||||
Expr::Garbage => None,
|
Expr::Garbage => None,
|
||||||
|
|
||||||
|
Expr::AttributeBlock(ab) => ab
|
||||||
|
.attributes
|
||||||
|
.iter()
|
||||||
|
.find_map(|attr| {
|
||||||
|
find_matching_block_end_in_expr(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
&attr.expr,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
find_matching_block_end_in_expr(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
&ab.item,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
|
||||||
Expr::Table(table) => {
|
Expr::Table(table) => {
|
||||||
if expr_last == global_cursor_offset {
|
if expr_last == global_cursor_offset {
|
||||||
// cursor is at table end
|
// cursor is at table end
|
||||||
|
@ -132,7 +132,7 @@ fn gather_env_vars(
|
|||||||
working_set.error(err);
|
working_set.error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if working_set.parse_errors.first().is_some() {
|
if !working_set.parse_errors.is_empty() {
|
||||||
report_capture_error(
|
report_capture_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
&String::from_utf8_lossy(contents),
|
&String::from_utf8_lossy(contents),
|
||||||
@ -176,7 +176,7 @@ fn gather_env_vars(
|
|||||||
working_set.error(err);
|
working_set.error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if working_set.parse_errors.first().is_some() {
|
if !working_set.parse_errors.is_empty() {
|
||||||
report_capture_error(
|
report_capture_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
&String::from_utf8_lossy(contents),
|
&String::from_utf8_lossy(contents),
|
||||||
@ -265,7 +265,10 @@ pub fn eval_source(
|
|||||||
perf!(
|
perf!(
|
||||||
&format!("eval_source {}", &fname),
|
&format!("eval_source {}", &fname),
|
||||||
start_time,
|
start_time,
|
||||||
engine_state.get_config().use_ansi_coloring
|
engine_state
|
||||||
|
.get_config()
|
||||||
|
.use_ansi_coloring
|
||||||
|
.get(engine_state)
|
||||||
);
|
);
|
||||||
|
|
||||||
exit_code
|
exit_code
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -14,7 +14,7 @@ fn create_default_context() -> EngineState {
|
|||||||
nu_command::add_shell_command_context(nu_cmd_lang::create_default_context())
|
nu_command::add_shell_command_context(nu_cmd_lang::create_default_context())
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates a new engine with the current path into the completions fixtures folder
|
/// creates a new engine with the current path into the completions fixtures folder
|
||||||
pub fn new_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
|
pub fn new_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
|
||||||
// Target folder inside assets
|
// Target folder inside assets
|
||||||
let dir = fs::fixtures().join("completions");
|
let dir = fs::fixtures().join("completions");
|
||||||
@ -69,7 +69,26 @@ pub fn new_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
|
|||||||
(dir, dir_str, engine_state, stack)
|
(dir, dir_str, engine_state, stack)
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates a new engine with the current path into the completions fixtures folder
|
/// Adds pseudo PATH env for external completion tests
|
||||||
|
pub fn new_external_engine() -> EngineState {
|
||||||
|
let mut engine = create_default_context();
|
||||||
|
let dir = fs::fixtures().join("external_completions").join("path");
|
||||||
|
let dir_str = dir.to_string_lossy().to_string();
|
||||||
|
let internal_span = nu_protocol::Span::new(0, dir_str.len());
|
||||||
|
engine.add_env_var(
|
||||||
|
"PATH".to_string(),
|
||||||
|
Value::List {
|
||||||
|
vals: vec![Value::String {
|
||||||
|
val: dir_str,
|
||||||
|
internal_span,
|
||||||
|
}],
|
||||||
|
internal_span,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
engine
|
||||||
|
}
|
||||||
|
|
||||||
|
/// creates a new engine with the current path into the completions fixtures folder
|
||||||
pub fn new_dotnu_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
|
pub fn new_dotnu_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
|
||||||
// Target folder inside assets
|
// Target folder inside assets
|
||||||
let dir = fs::fixtures().join("dotnu_completions");
|
let dir = fs::fixtures().join("dotnu_completions");
|
||||||
@ -86,6 +105,23 @@ pub fn new_dotnu_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
|
|||||||
// Add $nu
|
// Add $nu
|
||||||
engine_state.generate_nu_constant();
|
engine_state.generate_nu_constant();
|
||||||
|
|
||||||
|
// const $NU_LIB_DIRS
|
||||||
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
let var_id = working_set.add_variable(
|
||||||
|
b"$NU_LIB_DIRS".into(),
|
||||||
|
Span::unknown(),
|
||||||
|
nu_protocol::Type::List(Box::new(nu_protocol::Type::String)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
working_set.set_variable_const_val(
|
||||||
|
var_id,
|
||||||
|
Value::test_list(vec![
|
||||||
|
Value::string(file(dir.join("lib-dir1")), dir_span),
|
||||||
|
Value::string(file(dir.join("lib-dir3")), dir_span),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
let _ = engine_state.merge_delta(working_set.render());
|
||||||
|
|
||||||
// New stack
|
// New stack
|
||||||
let mut stack = Stack::new();
|
let mut stack = Stack::new();
|
||||||
|
|
||||||
@ -95,17 +131,12 @@ pub fn new_dotnu_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
|
|||||||
"TEST".to_string(),
|
"TEST".to_string(),
|
||||||
Value::string("NUSHELL".to_string(), dir_span),
|
Value::string("NUSHELL".to_string(), dir_span),
|
||||||
);
|
);
|
||||||
|
|
||||||
stack.add_env_var(
|
stack.add_env_var(
|
||||||
"NU_LIB_DIRS".to_string(),
|
"NU_LIB_DIRS".into(),
|
||||||
Value::List {
|
Value::test_list(vec![
|
||||||
vals: vec![
|
|
||||||
Value::string(file(dir.join("lib-dir1")), dir_span),
|
|
||||||
Value::string(file(dir.join("lib-dir2")), dir_span),
|
Value::string(file(dir.join("lib-dir2")), dir_span),
|
||||||
Value::string(file(dir.join("lib-dir3")), dir_span),
|
Value::string(file(dir.join("lib-dir3")), dir_span),
|
||||||
],
|
]),
|
||||||
internal_span: dir_span,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Merge environment into the permanent state
|
// Merge environment into the permanent state
|
||||||
@ -185,8 +216,8 @@ pub fn new_partial_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
|
|||||||
(dir, dir_str, engine_state, stack)
|
(dir, dir_str, engine_state, stack)
|
||||||
}
|
}
|
||||||
|
|
||||||
// match a list of suggestions with the expected values
|
/// match a list of suggestions with the expected values
|
||||||
pub fn match_suggestions(expected: &Vec<String>, suggestions: &Vec<Suggestion>) {
|
pub fn match_suggestions(expected: &Vec<&str>, suggestions: &Vec<Suggestion>) {
|
||||||
let expected_len = expected.len();
|
let expected_len = expected.len();
|
||||||
let suggestions_len = suggestions.len();
|
let suggestions_len = suggestions.len();
|
||||||
if expected_len != suggestions_len {
|
if expected_len != suggestions_len {
|
||||||
@ -197,28 +228,34 @@ pub fn match_suggestions(expected: &Vec<String>, suggestions: &Vec<Suggestion>)
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let suggestoins_str = suggestions
|
let suggestions_str = suggestions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|it| it.value.clone())
|
.map(|it| it.value.as_str())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
assert_eq!(expected, &suggestoins_str);
|
assert_eq!(expected, &suggestions_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
// append the separator to the converted path
|
/// match a list of suggestions with the expected values
|
||||||
|
pub fn match_suggestions_by_string(expected: &[String], suggestions: &Vec<Suggestion>) {
|
||||||
|
let expected = expected.iter().map(|it| it.as_str()).collect::<Vec<_>>();
|
||||||
|
match_suggestions(&expected, suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// append the separator to the converted path
|
||||||
pub fn folder(path: impl Into<PathBuf>) -> String {
|
pub fn folder(path: impl Into<PathBuf>) -> String {
|
||||||
let mut converted_path = file(path);
|
let mut converted_path = file(path);
|
||||||
converted_path.push(MAIN_SEPARATOR);
|
converted_path.push(MAIN_SEPARATOR);
|
||||||
converted_path
|
converted_path
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert a given path to string
|
/// convert a given path to string
|
||||||
pub fn file(path: impl Into<PathBuf>) -> String {
|
pub fn file(path: impl Into<PathBuf>) -> String {
|
||||||
path.into().into_os_string().into_string().unwrap()
|
path.into().into_os_string().into_string().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
// merge_input executes the given input into the engine
|
/// merge_input executes the given input into the engine
|
||||||
// and merges the state
|
/// and merges the state
|
||||||
pub fn merge_input(
|
pub fn merge_input(
|
||||||
input: &[u8],
|
input: &[u8],
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
pub mod completions_helpers;
|
pub mod completions_helpers;
|
||||||
|
|
||||||
pub use completions_helpers::{file, folder, match_suggestions, merge_input, new_engine};
|
pub use completions_helpers::{
|
||||||
|
file, folder, match_suggestions, match_suggestions_by_string, merge_input, new_engine,
|
||||||
|
};
|
||||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-base"
|
name = "nu-cmd-base"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
||||||
version = "0.101.0"
|
version = "0.104.1"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -13,10 +13,10 @@ version = "0.101.0"
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.101.0", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.104.1", default-features = false }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.101.0" }
|
nu-parser = { path = "../nu-parser", version = "0.104.1" }
|
||||||
nu-path = { path = "../nu-path", version = "0.101.0" }
|
nu-path = { path = "../nu-path", version = "0.104.1" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.101.0", default-features = false }
|
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false }
|
||||||
|
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
miette = { workspace = true }
|
miette = { workspace = true }
|
||||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-extra"
|
name = "nu-cmd-extra"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
|
||||||
version = "0.101.0"
|
version = "0.104.1"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -16,13 +16,13 @@ bench = false
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.101.0" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.104.1" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.101.0", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.104.1", default-features = false }
|
||||||
nu-json = { version = "0.101.0", path = "../nu-json" }
|
nu-json = { version = "0.104.1", path = "../nu-json" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.101.0" }
|
nu-parser = { path = "../nu-parser", version = "0.104.1" }
|
||||||
nu-pretty-hex = { version = "0.101.0", path = "../nu-pretty-hex" }
|
nu-pretty-hex = { version = "0.104.1", path = "../nu-pretty-hex" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.101.0", default-features = false }
|
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.101.0", default-features = false }
|
nu-utils = { path = "../nu-utils", version = "0.104.1", default-features = false }
|
||||||
|
|
||||||
# Potential dependencies for extras
|
# Potential dependencies for extras
|
||||||
heck = { workspace = true }
|
heck = { workspace = true }
|
||||||
@ -34,8 +34,9 @@ serde = { workspace = true }
|
|||||||
serde_urlencoded = { workspace = true }
|
serde_urlencoded = { workspace = true }
|
||||||
v_htmlescape = { workspace = true }
|
v_htmlescape = { workspace = true }
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
|
mime = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.101.0" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.104.1" }
|
||||||
nu-command = { path = "../nu-command", version = "0.101.0" }
|
nu-command = { path = "../nu-command", version = "0.104.1" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.101.0" }
|
nu-test-support = { path = "../nu-test-support", version = "0.104.1" }
|
||||||
|
@ -43,7 +43,12 @@ mod test_examples {
|
|||||||
signature.operates_on_cell_paths(),
|
signature.operates_on_cell_paths(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
check_example_evaluates_to_expected_output(&example, cwd.as_path(), &mut engine_state);
|
check_example_evaluates_to_expected_output(
|
||||||
|
cmd.name(),
|
||||||
|
&example,
|
||||||
|
cwd.as_path(),
|
||||||
|
&mut engine_state,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
check_all_signature_input_output_types_entries_have_examples(
|
check_all_signature_input_output_types_entries_have_examples(
|
||||||
|
@ -26,7 +26,7 @@ impl Command for BitsAnd {
|
|||||||
.required(
|
.required(
|
||||||
"target",
|
"target",
|
||||||
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]),
|
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]),
|
||||||
"right-hand side of the operation",
|
"Right-hand side of the operation.",
|
||||||
)
|
)
|
||||||
.named(
|
.named(
|
||||||
"endian",
|
"endian",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
mod and;
|
mod and;
|
||||||
mod bits_;
|
mod bits_;
|
||||||
mod into;
|
|
||||||
mod not;
|
mod not;
|
||||||
mod or;
|
mod or;
|
||||||
mod rotate_left;
|
mod rotate_left;
|
||||||
@ -11,7 +10,6 @@ mod xor;
|
|||||||
|
|
||||||
pub use and::BitsAnd;
|
pub use and::BitsAnd;
|
||||||
pub use bits_::Bits;
|
pub use bits_::Bits;
|
||||||
pub use into::BitsInto;
|
|
||||||
pub use not::BitsNot;
|
pub use not::BitsNot;
|
||||||
pub use or::BitsOr;
|
pub use or::BitsOr;
|
||||||
pub use rotate_left::BitsRol;
|
pub use rotate_left::BitsRol;
|
||||||
@ -137,7 +135,7 @@ where
|
|||||||
(min, max) => (rhs, lhs, max, min),
|
(min, max) => (rhs, lhs, max, min),
|
||||||
};
|
};
|
||||||
|
|
||||||
let pad = iter::repeat(0).take(max_len - min_len);
|
let pad = iter::repeat_n(0, max_len - min_len);
|
||||||
|
|
||||||
let mut a;
|
let mut a;
|
||||||
let mut b;
|
let mut b;
|
||||||
@ -161,9 +159,10 @@ where
|
|||||||
}
|
}
|
||||||
(Value::Binary { .. }, Value::Int { .. }) | (Value::Int { .. }, Value::Binary { .. }) => {
|
(Value::Binary { .. }, Value::Int { .. }) | (Value::Int { .. }, Value::Binary { .. }) => {
|
||||||
Value::error(
|
Value::error(
|
||||||
ShellError::PipelineMismatch {
|
ShellError::OnlySupportsThisInputType {
|
||||||
exp_input_type: "input, and argument, to be both int or both binary"
|
exp_input_type: "input, and argument, to be both int or both binary"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
|
wrong_type: "int and binary".to_string(),
|
||||||
dst_span: rhs.span(),
|
dst_span: rhs.span(),
|
||||||
src_span: span,
|
src_span: span,
|
||||||
},
|
},
|
||||||
|
@ -27,7 +27,7 @@ impl Command for BitsOr {
|
|||||||
.required(
|
.required(
|
||||||
"target",
|
"target",
|
||||||
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]),
|
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]),
|
||||||
"right-hand side of the operation",
|
"Right-hand side of the operation.",
|
||||||
)
|
)
|
||||||
.named(
|
.named(
|
||||||
"endian",
|
"endian",
|
||||||
|
@ -37,7 +37,7 @@ impl Command for BitsRol {
|
|||||||
),
|
),
|
||||||
])
|
])
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.required("bits", SyntaxShape::Int, "number of bits to rotate left")
|
.required("bits", SyntaxShape::Int, "Number of bits to rotate left.")
|
||||||
.switch(
|
.switch(
|
||||||
"signed",
|
"signed",
|
||||||
"always treat input number as a signed number",
|
"always treat input number as a signed number",
|
||||||
|
@ -37,7 +37,7 @@ impl Command for BitsRor {
|
|||||||
),
|
),
|
||||||
])
|
])
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.required("bits", SyntaxShape::Int, "number of bits to rotate right")
|
.required("bits", SyntaxShape::Int, "Number of bits to rotate right.")
|
||||||
.switch(
|
.switch(
|
||||||
"signed",
|
"signed",
|
||||||
"always treat input number as a signed number",
|
"always treat input number as a signed number",
|
||||||
|
@ -40,7 +40,7 @@ impl Command for BitsShl {
|
|||||||
),
|
),
|
||||||
])
|
])
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.required("bits", SyntaxShape::Int, "number of bits to shift left")
|
.required("bits", SyntaxShape::Int, "Number of bits to shift left.")
|
||||||
.switch(
|
.switch(
|
||||||
"signed",
|
"signed",
|
||||||
"always treat input number as a signed number",
|
"always treat input number as a signed number",
|
||||||
@ -249,7 +249,7 @@ fn shift_bytes_and_bits_left(data: &[u8], byte_shift: usize, bit_shift: usize) -
|
|||||||
Last | Only => lhs << bit_shift,
|
Last | Only => lhs << bit_shift,
|
||||||
_ => (lhs << bit_shift) | (rhs >> (8 - bit_shift)),
|
_ => (lhs << bit_shift) | (rhs >> (8 - bit_shift)),
|
||||||
})
|
})
|
||||||
.chain(iter::repeat(0).take(byte_shift))
|
.chain(iter::repeat_n(0, byte_shift))
|
||||||
.collect::<Vec<u8>>()
|
.collect::<Vec<u8>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ impl Command for BitsShr {
|
|||||||
),
|
),
|
||||||
])
|
])
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.required("bits", SyntaxShape::Int, "number of bits to shift right")
|
.required("bits", SyntaxShape::Int, "Number of bits to shift right.")
|
||||||
.switch(
|
.switch(
|
||||||
"signed",
|
"signed",
|
||||||
"always treat input number as a signed number",
|
"always treat input number as a signed number",
|
||||||
|
@ -27,7 +27,7 @@ impl Command for BitsXor {
|
|||||||
.required(
|
.required(
|
||||||
"target",
|
"target",
|
||||||
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]),
|
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::Int]),
|
||||||
"right-hand side of the operation",
|
"Right-hand side of the operation.",
|
||||||
)
|
)
|
||||||
.named(
|
.named(
|
||||||
"endian",
|
"endian",
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
mod fmt;
|
|
||||||
|
|
||||||
pub(crate) use fmt::Fmt;
|
|
@ -26,7 +26,7 @@ impl Command for EachWhile {
|
|||||||
.required(
|
.required(
|
||||||
"closure",
|
"closure",
|
||||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
||||||
"the closure to run",
|
"The closure to run.",
|
||||||
)
|
)
|
||||||
.category(Category::Filters)
|
.category(Category::Filters)
|
||||||
}
|
}
|
||||||
|
@ -13,14 +13,17 @@ impl Command for Rotate {
|
|||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::record(), Type::table()),
|
(Type::record(), Type::table()),
|
||||||
(Type::table(), Type::table()),
|
(Type::table(), Type::table()),
|
||||||
|
(Type::list(Type::Any), Type::table()),
|
||||||
|
(Type::String, Type::table()),
|
||||||
])
|
])
|
||||||
.switch("ccw", "rotate counter clockwise", None)
|
.switch("ccw", "rotate counter clockwise", None)
|
||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::String,
|
SyntaxShape::String,
|
||||||
"the names to give columns once rotated",
|
"The names to give columns once rotated.",
|
||||||
)
|
)
|
||||||
.category(Category::Filters)
|
.category(Category::Filters)
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
|
@ -16,7 +16,7 @@ impl Command for UpdateCells {
|
|||||||
.required(
|
.required(
|
||||||
"closure",
|
"closure",
|
||||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
||||||
"the closure to run an update for each cell",
|
"The closure to run an update for each cell.",
|
||||||
)
|
)
|
||||||
.named(
|
.named(
|
||||||
"columns",
|
"columns",
|
||||||
|
@ -330,7 +330,12 @@ fn to_html(
|
|||||||
output_string = run_regexes(®ex_hm, &output_string);
|
output_string = run_regexes(®ex_hm, &output_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::string(output_string, head).into_pipeline_data())
|
let metadata = PipelineMetadata {
|
||||||
|
data_source: nu_protocol::DataSource::None,
|
||||||
|
content_type: Some(mime::TEXT_HTML_UTF_8.to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Value::string(output_string, head).into_pipeline_data_with_metadata(metadata))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme_demo(span: Span) -> PipelineData {
|
fn theme_demo(span: Span) -> PipelineData {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct MathArcCos;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for MathArcCos {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"math arccos"
|
"math arccos"
|
||||||
}
|
}
|
||||||
@ -114,6 +114,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(MathArcCos {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct MathArcCosH;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for MathArcCosH {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"math arccosh"
|
"math arccosh"
|
||||||
}
|
}
|
||||||
@ -100,6 +100,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(MathArcCosH {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct MathArcSin;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for MathArcSin {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"math arcsin"
|
"math arcsin"
|
||||||
}
|
}
|
||||||
@ -115,6 +115,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(MathArcSin {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct MathArcSinH;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for MathArcSinH {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"math arcsinh"
|
"math arcsinh"
|
||||||
}
|
}
|
||||||
@ -88,6 +88,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(MathArcSinH {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct MathArcTan;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for MathArcTan {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"math arctan"
|
"math arctan"
|
||||||
}
|
}
|
||||||
@ -102,6 +102,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(MathArcTan {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct MathArcTanH;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for MathArcTanH {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"math arctanh"
|
"math arctanh"
|
||||||
}
|
}
|
||||||
@ -101,6 +101,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(MathArcTanH {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct MathCos;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for MathCos {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"math cos"
|
"math cos"
|
||||||
}
|
}
|
||||||
@ -108,6 +108,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(MathCos {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct MathCosH;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for MathCosH {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"math cosh"
|
"math cosh"
|
||||||
}
|
}
|
||||||
@ -88,6 +88,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(MathCosH {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct MathExp;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for MathExp {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"math exp"
|
"math exp"
|
||||||
}
|
}
|
||||||
@ -93,6 +93,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(MathExp {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct MathLn;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for MathLn {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"math ln"
|
"math ln"
|
||||||
}
|
}
|
||||||
@ -100,6 +100,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(MathLn {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,19 +15,19 @@ mod arcsinh;
|
|||||||
mod arctan;
|
mod arctan;
|
||||||
mod arctanh;
|
mod arctanh;
|
||||||
|
|
||||||
pub use cos::SubCommand as MathCos;
|
pub use cos::MathCos;
|
||||||
pub use cosh::SubCommand as MathCosH;
|
pub use cosh::MathCosH;
|
||||||
pub use sin::SubCommand as MathSin;
|
pub use sin::MathSin;
|
||||||
pub use sinh::SubCommand as MathSinH;
|
pub use sinh::MathSinH;
|
||||||
pub use tan::SubCommand as MathTan;
|
pub use tan::MathTan;
|
||||||
pub use tanh::SubCommand as MathTanH;
|
pub use tanh::MathTanH;
|
||||||
|
|
||||||
pub use exp::SubCommand as MathExp;
|
pub use exp::MathExp;
|
||||||
pub use ln::SubCommand as MathLn;
|
pub use ln::MathLn;
|
||||||
|
|
||||||
pub use arccos::SubCommand as MathArcCos;
|
pub use arccos::MathArcCos;
|
||||||
pub use arccosh::SubCommand as MathArcCosH;
|
pub use arccosh::MathArcCosH;
|
||||||
pub use arcsin::SubCommand as MathArcSin;
|
pub use arcsin::MathArcSin;
|
||||||
pub use arcsinh::SubCommand as MathArcSinH;
|
pub use arcsinh::MathArcSinH;
|
||||||
pub use arctan::SubCommand as MathArcTan;
|
pub use arctan::MathArcTan;
|
||||||
pub use arctanh::SubCommand as MathArcTanH;
|
pub use arctanh::MathArcTanH;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct MathSin;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for MathSin {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"math sin"
|
"math sin"
|
||||||
}
|
}
|
||||||
@ -108,6 +108,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(MathSin {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct MathSinH;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for MathSinH {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"math sinh"
|
"math sinh"
|
||||||
}
|
}
|
||||||
@ -87,6 +87,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(MathSinH {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct MathTan;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for MathTan {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"math tan"
|
"math tan"
|
||||||
}
|
}
|
||||||
@ -106,6 +106,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(MathTan {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct MathTanH;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for MathTanH {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"math tanh"
|
"math tanh"
|
||||||
}
|
}
|
||||||
@ -86,6 +86,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(MathTanH {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
mod bits;
|
mod bits;
|
||||||
mod conversions;
|
|
||||||
mod filters;
|
mod filters;
|
||||||
mod formats;
|
mod formats;
|
||||||
mod math;
|
mod math;
|
||||||
mod platform;
|
mod platform;
|
||||||
mod strings;
|
mod strings;
|
||||||
|
|
||||||
pub use bits::{
|
pub use bits::{Bits, BitsAnd, BitsNot, BitsOr, BitsRol, BitsRor, BitsShl, BitsShr, BitsXor};
|
||||||
Bits, BitsAnd, BitsInto, BitsNot, BitsOr, BitsRol, BitsRor, BitsShl, BitsShr, BitsXor,
|
|
||||||
};
|
|
||||||
pub use formats::ToHtml;
|
pub use formats::ToHtml;
|
||||||
pub use math::{MathArcCos, MathArcCosH, MathArcSin, MathArcSinH, MathArcTan, MathArcTanH};
|
pub use math::{MathArcCos, MathArcCosH, MathArcSin, MathArcSinH, MathArcTan, MathArcTanH};
|
||||||
pub use math::{MathCos, MathCosH, MathSin, MathSinH, MathTan, MathTanH};
|
pub use math::{MathCos, MathCosH, MathSin, MathSinH, MathTan, MathTanH};
|
||||||
@ -29,8 +26,6 @@ pub fn add_extra_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
bind_command!(conversions::Fmt);
|
|
||||||
|
|
||||||
bind_command!(
|
bind_command!(
|
||||||
filters::UpdateCells,
|
filters::UpdateCells,
|
||||||
filters::EachWhile,
|
filters::EachWhile,
|
||||||
@ -46,6 +41,8 @@ pub fn add_extra_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
|
|
||||||
bind_command!(
|
bind_command!(
|
||||||
strings::format::FormatPattern,
|
strings::format::FormatPattern,
|
||||||
|
strings::format::FormatBits,
|
||||||
|
strings::format::FormatNumber,
|
||||||
strings::str_::case::Str,
|
strings::str_::case::Str,
|
||||||
strings::str_::case::StrCamelCase,
|
strings::str_::case::StrCamelCase,
|
||||||
strings::str_::case::StrKebabCase,
|
strings::str_::case::StrKebabCase,
|
||||||
@ -61,7 +58,6 @@ pub fn add_extra_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
bind_command! {
|
bind_command! {
|
||||||
Bits,
|
Bits,
|
||||||
BitsAnd,
|
BitsAnd,
|
||||||
BitsInto,
|
|
||||||
BitsNot,
|
BitsNot,
|
||||||
BitsOr,
|
BitsOr,
|
||||||
BitsRol,
|
BitsRol,
|
||||||
|
@ -38,7 +38,7 @@ impl Command for SubCommand {
|
|||||||
.rest(
|
.rest(
|
||||||
"cell path",
|
"cell path",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"for a data structure input, add a gradient to strings at the given cell paths",
|
"For a data structure input, add a gradient to strings at the given cell paths.",
|
||||||
)
|
)
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::String, Type::String),
|
(Type::String, Type::String),
|
||||||
|
@ -3,10 +3,10 @@ use std::io::{self, Read, Write};
|
|||||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
use nu_protocol::Signals;
|
use nu_protocol::{shell_error::io::IoError, Signals};
|
||||||
use num_traits::ToPrimitive;
|
use num_traits::ToPrimitive;
|
||||||
|
|
||||||
pub struct Arguments {
|
struct Arguments {
|
||||||
cell_paths: Option<Vec<CellPath>>,
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,15 +17,15 @@ impl CmdArgument for Arguments {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BitsInto;
|
pub struct FormatBits;
|
||||||
|
|
||||||
impl Command for BitsInto {
|
impl Command for FormatBits {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"into bits"
|
"format bits"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("into bits")
|
Signature::build("format bits")
|
||||||
.input_output_types(vec![
|
.input_output_types(vec![
|
||||||
(Type::Binary, Type::String),
|
(Type::Binary, Type::String),
|
||||||
(Type::Int, Type::String),
|
(Type::Int, Type::String),
|
||||||
@ -40,17 +40,17 @@ impl Command for BitsInto {
|
|||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"for a data structure input, convert data at the given cell paths",
|
"For a data structure input, convert data at the given cell paths.",
|
||||||
)
|
)
|
||||||
.category(Category::Conversions)
|
.category(Category::Conversions)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Convert value to a binary primitive."
|
"Convert value to a string of binary data represented by 0 and 1."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["convert", "cast"]
|
vec!["convert", "cast", "binary"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -60,49 +60,49 @@ impl Command for BitsInto {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
into_bits(engine_state, stack, call, input)
|
format_bits(engine_state, stack, call, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
Example {
|
Example {
|
||||||
description: "convert a binary value into a string, padded to 8 places with 0s",
|
description: "convert a binary value into a string, padded to 8 places with 0s",
|
||||||
example: "0x[1] | into bits",
|
example: "0x[1] | format bits",
|
||||||
result: Some(Value::string("00000001",
|
result: Some(Value::string("00000001",
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "convert an int into a string, padded to 8 places with 0s",
|
description: "convert an int into a string, padded to 8 places with 0s",
|
||||||
example: "1 | into bits",
|
example: "1 | format bits",
|
||||||
result: Some(Value::string("00000001",
|
result: Some(Value::string("00000001",
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "convert a filesize value into a string, padded to 8 places with 0s",
|
description: "convert a filesize value into a string, padded to 8 places with 0s",
|
||||||
example: "1b | into bits",
|
example: "1b | format bits",
|
||||||
result: Some(Value::string("00000001",
|
result: Some(Value::string("00000001",
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "convert a duration value into a string, padded to 8 places with 0s",
|
description: "convert a duration value into a string, padded to 8 places with 0s",
|
||||||
example: "1ns | into bits",
|
example: "1ns | format bits",
|
||||||
result: Some(Value::string("00000001",
|
result: Some(Value::string("00000001",
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "convert a boolean value into a string, padded to 8 places with 0s",
|
description: "convert a boolean value into a string, padded to 8 places with 0s",
|
||||||
example: "true | into bits",
|
example: "true | format bits",
|
||||||
result: Some(Value::string("00000001",
|
result: Some(Value::string("00000001",
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "convert a string into a raw binary string, padded with 0s to 8 places",
|
description: "convert a string into a raw binary string, padded with 0s to 8 places",
|
||||||
example: "'nushell.sh' | into bits",
|
example: "'nushell.sh' | format bits",
|
||||||
result: Some(Value::string("01101110 01110101 01110011 01101000 01100101 01101100 01101100 00101110 01110011 01101000",
|
result: Some(Value::string("01101110 01110101 01110011 01101000 01100101 01101100 01101100 00101110 01110011 01101000",
|
||||||
Span::test_data(),
|
Span::test_data(),
|
||||||
)),
|
)),
|
||||||
@ -111,7 +111,7 @@ impl Command for BitsInto {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_bits(
|
fn format_bits(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
@ -141,7 +141,11 @@ fn byte_stream_to_bits(stream: ByteStream, head: Span) -> ByteStream {
|
|||||||
ByteStreamType::String,
|
ByteStreamType::String,
|
||||||
move |buffer| {
|
move |buffer| {
|
||||||
let mut byte = [0];
|
let mut byte = [0];
|
||||||
if reader.read(&mut byte[..]).err_span(head)? > 0 {
|
if reader
|
||||||
|
.read(&mut byte[..])
|
||||||
|
.map_err(|err| IoError::new(err.kind(), head, None))?
|
||||||
|
> 0
|
||||||
|
{
|
||||||
// Format the byte as bits
|
// Format the byte as bits
|
||||||
if is_first {
|
if is_first {
|
||||||
is_first = false;
|
is_first = false;
|
||||||
@ -193,7 +197,7 @@ fn convert_to_smallest_number_type(num: i64, span: Span) -> Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::Binary { val, .. } => {
|
Value::Binary { val, .. } => {
|
||||||
let mut raw_string = "".to_string();
|
let mut raw_string = "".to_string();
|
||||||
@ -239,6 +243,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(BitsInto {})
|
test_examples(FormatBits {})
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,7 +18,7 @@ impl Command for FormatPattern {
|
|||||||
.required(
|
.required(
|
||||||
"pattern",
|
"pattern",
|
||||||
SyntaxShape::String,
|
SyntaxShape::String,
|
||||||
"the pattern to output. e.g.) \"{foo}: {bar}\"",
|
"The pattern to output. e.g.) \"{foo}: {bar}\".",
|
||||||
)
|
)
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.category(Category::Strings)
|
.category(Category::Strings)
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
mod bits;
|
||||||
mod command;
|
mod command;
|
||||||
|
mod number;
|
||||||
|
|
||||||
|
pub(crate) use bits::FormatBits;
|
||||||
pub(crate) use command::FormatPattern;
|
pub(crate) use command::FormatPattern;
|
||||||
|
pub(crate) use number::FormatNumber;
|
||||||
|
@ -2,11 +2,11 @@ use nu_cmd_base::input_handler::{operate, CellPathOnlyArgs};
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Fmt;
|
pub struct FormatNumber;
|
||||||
|
|
||||||
impl Command for Fmt {
|
impl Command for FormatNumber {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"fmt"
|
"format number"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
@ -14,19 +14,19 @@ impl Command for Fmt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("fmt")
|
Signature::build("format number")
|
||||||
.input_output_types(vec![(Type::Number, Type::record())])
|
.input_output_types(vec![(Type::Number, Type::record())])
|
||||||
.category(Category::Conversions)
|
.category(Category::Conversions)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["display", "render", "format"]
|
vec!["display", "render", "fmt"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![Example {
|
||||||
description: "Get a record containing multiple formats for the number 42",
|
description: "Get a record containing multiple formats for the number 42",
|
||||||
example: "42 | fmt",
|
example: "42 | format number",
|
||||||
result: Some(Value::test_record(record! {
|
result: Some(Value::test_record(record! {
|
||||||
"binary" => Value::test_string("0b101010"),
|
"binary" => Value::test_string("0b101010"),
|
||||||
"debug" => Value::test_string("42"),
|
"debug" => Value::test_string("42"),
|
||||||
@ -47,11 +47,11 @@ impl Command for Fmt {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
fmt(engine_state, stack, call, input)
|
format_number(engine_state, stack, call, input)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fmt(
|
pub(crate) fn format_number(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
@ -64,9 +64,9 @@ fn fmt(
|
|||||||
|
|
||||||
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::Float { val, .. } => fmt_it_64(*val, span),
|
Value::Float { val, .. } => format_f64(*val, span),
|
||||||
Value::Int { val, .. } => fmt_it(*val, span),
|
Value::Int { val, .. } => format_i64(*val, span),
|
||||||
Value::Filesize { val, .. } => fmt_it(val.get(), span),
|
Value::Filesize { val, .. } => format_i64(val.get(), span),
|
||||||
// Propagate errors by explicitly matching them before the final case.
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
Value::Error { .. } => input.clone(),
|
Value::Error { .. } => input.clone(),
|
||||||
other => Value::error(
|
other => Value::error(
|
||||||
@ -81,7 +81,7 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fmt_it(num: i64, span: Span) -> Value {
|
fn format_i64(num: i64, span: Span) -> Value {
|
||||||
Value::record(
|
Value::record(
|
||||||
record! {
|
record! {
|
||||||
"binary" => Value::string(format!("{num:#b}"), span),
|
"binary" => Value::string(format!("{num:#b}"), span),
|
||||||
@ -97,7 +97,7 @@ fn fmt_it(num: i64, span: Span) -> Value {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fmt_it_64(num: f64, span: Span) -> Value {
|
fn format_f64(num: f64, span: Span) -> Value {
|
||||||
Value::record(
|
Value::record(
|
||||||
record! {
|
record! {
|
||||||
"binary" => Value::string(format!("{:b}", num.to_bits()), span),
|
"binary" => Value::string(format!("{:b}", num.to_bits()), span),
|
||||||
@ -121,6 +121,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(Fmt {})
|
test_examples(FormatNumber {})
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,9 +3,9 @@ use heck::ToLowerCamelCase;
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct StrCamelCase;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for StrCamelCase {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"str camel-case"
|
"str camel-case"
|
||||||
}
|
}
|
||||||
@ -25,7 +25,7 @@ impl Command for SubCommand {
|
|||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"For a data structure input, convert strings at the given cell paths",
|
"For a data structure input, convert strings at the given cell paths.",
|
||||||
)
|
)
|
||||||
.category(Category::Strings)
|
.category(Category::Strings)
|
||||||
}
|
}
|
||||||
@ -91,6 +91,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(StrCamelCase {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,9 @@ use heck::ToKebabCase;
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct StrKebabCase;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for StrKebabCase {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"str kebab-case"
|
"str kebab-case"
|
||||||
}
|
}
|
||||||
@ -25,7 +25,7 @@ impl Command for SubCommand {
|
|||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"For a data structure input, convert strings at the given cell paths",
|
"For a data structure input, convert strings at the given cell paths.",
|
||||||
)
|
)
|
||||||
.category(Category::Strings)
|
.category(Category::Strings)
|
||||||
}
|
}
|
||||||
@ -90,6 +90,6 @@ mod tests {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(StrKebabCase {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,13 @@ mod snake_case;
|
|||||||
mod str_;
|
mod str_;
|
||||||
mod title_case;
|
mod title_case;
|
||||||
|
|
||||||
pub use camel_case::SubCommand as StrCamelCase;
|
pub use camel_case::StrCamelCase;
|
||||||
pub use kebab_case::SubCommand as StrKebabCase;
|
pub use kebab_case::StrKebabCase;
|
||||||
pub use pascal_case::SubCommand as StrPascalCase;
|
pub use pascal_case::StrPascalCase;
|
||||||
pub use screaming_snake_case::SubCommand as StrScreamingSnakeCase;
|
pub use screaming_snake_case::StrScreamingSnakeCase;
|
||||||
pub use snake_case::SubCommand as StrSnakeCase;
|
pub use snake_case::StrSnakeCase;
|
||||||
pub use str_::Str;
|
pub use str_::Str;
|
||||||
pub use title_case::SubCommand as StrTitleCase;
|
pub use title_case::StrTitleCase;
|
||||||
|
|
||||||
use nu_cmd_base::input_handler::{operate as general_operate, CmdArgument};
|
use nu_cmd_base::input_handler::{operate as general_operate, CmdArgument};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
@ -3,9 +3,9 @@ use heck::ToUpperCamelCase;
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct StrPascalCase;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for StrPascalCase {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"str pascal-case"
|
"str pascal-case"
|
||||||
}
|
}
|
||||||
@ -25,7 +25,7 @@ impl Command for SubCommand {
|
|||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"For a data structure input, convert strings at the given cell paths",
|
"For a data structure input, convert strings at the given cell paths.",
|
||||||
)
|
)
|
||||||
.category(Category::Strings)
|
.category(Category::Strings)
|
||||||
}
|
}
|
||||||
@ -91,6 +91,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(StrPascalCase {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,9 @@ use heck::ToShoutySnakeCase;
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct StrScreamingSnakeCase;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for StrScreamingSnakeCase {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"str screaming-snake-case"
|
"str screaming-snake-case"
|
||||||
}
|
}
|
||||||
@ -25,7 +25,7 @@ impl Command for SubCommand {
|
|||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"For a data structure input, convert strings at the given cell paths",
|
"For a data structure input, convert strings at the given cell paths.",
|
||||||
)
|
)
|
||||||
.category(Category::Strings)
|
.category(Category::Strings)
|
||||||
}
|
}
|
||||||
@ -91,6 +91,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(StrScreamingSnakeCase {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,9 @@ use heck::ToSnakeCase;
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct StrSnakeCase;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for StrSnakeCase {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"str snake-case"
|
"str snake-case"
|
||||||
}
|
}
|
||||||
@ -25,7 +25,7 @@ impl Command for SubCommand {
|
|||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"For a data structure input, convert strings at the given cell paths",
|
"For a data structure input, convert strings at the given cell paths.",
|
||||||
)
|
)
|
||||||
.category(Category::Strings)
|
.category(Category::Strings)
|
||||||
}
|
}
|
||||||
@ -91,6 +91,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(StrSnakeCase {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,9 @@ use heck::ToTitleCase;
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct StrTitleCase;
|
||||||
|
|
||||||
impl Command for SubCommand {
|
impl Command for StrTitleCase {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"str title-case"
|
"str title-case"
|
||||||
}
|
}
|
||||||
@ -25,7 +25,7 @@ impl Command for SubCommand {
|
|||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::CellPath,
|
SyntaxShape::CellPath,
|
||||||
"For a data structure input, convert strings at the given cell paths",
|
"For a data structure input, convert strings at the given cell paths.",
|
||||||
)
|
)
|
||||||
.category(Category::Strings)
|
.category(Category::Strings)
|
||||||
}
|
}
|
||||||
@ -86,6 +86,6 @@ mod test {
|
|||||||
fn test_examples() {
|
fn test_examples() {
|
||||||
use crate::test_examples;
|
use crate::test_examples;
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(StrTitleCase {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,12 @@ use nu_test_support::nu;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn byte_stream_into_bits() {
|
fn byte_stream_into_bits() {
|
||||||
let result = nu!("[0x[01] 0x[02 03]] | bytes collect | into bits");
|
let result = nu!("[0x[01] 0x[02 03]] | bytes collect | format bits");
|
||||||
assert_eq!("00000001 00000010 00000011", result.out);
|
assert_eq!("00000001 00000010 00000011", result.out);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn byte_stream_into_bits_is_stream() {
|
fn byte_stream_into_bits_is_stream() {
|
||||||
let result = nu!("[0x[01] 0x[02 03]] | bytes collect | into bits | describe");
|
let result = nu!("[0x[01] 0x[02 03]] | bytes collect | format bits | describe");
|
||||||
assert_eq!("string (stream)", result.out);
|
assert_eq!("string (stream)", result.out);
|
||||||
}
|
}
|
@ -1 +1 @@
|
|||||||
mod into;
|
mod format;
|
||||||
|
@ -6,7 +6,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-cmd-lang"
|
name = "nu-cmd-lang"
|
||||||
version = "0.101.0"
|
version = "0.104.1"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
@ -15,16 +15,20 @@ bench = false
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.101.0", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.104.1", default-features = false }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.101.0" }
|
nu-parser = { path = "../nu-parser", version = "0.104.1" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.101.0", default-features = false }
|
nu-protocol = { path = "../nu-protocol", version = "0.104.1", default-features = false }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.101.0", default-features = false }
|
nu-utils = { path = "../nu-utils", version = "0.104.1", default-features = false }
|
||||||
|
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
shadow-rs = { version = "0.37", default-features = false }
|
shadow-rs = { version = "1.1", default-features = false }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
shadow-rs = { version = "0.37", default-features = false }
|
shadow-rs = { version = "1.1", default-features = false, features = ["build"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
quickcheck = { workspace = true }
|
||||||
|
quickcheck_macros = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["os"]
|
default = ["os"]
|
||||||
@ -38,7 +42,6 @@ plugin = [
|
|||||||
"os",
|
"os",
|
||||||
]
|
]
|
||||||
|
|
||||||
mimalloc = []
|
|
||||||
trash-support = []
|
trash-support = []
|
||||||
sqlite = []
|
sqlite = []
|
||||||
static-link-openssl = []
|
static-link-openssl = []
|
||||||
|
61
crates/nu-cmd-lang/src/core_commands/attr/category.rs
Normal file
61
crates/nu-cmd-lang/src/core_commands/attr/category.rs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AttrCategory;
|
||||||
|
|
||||||
|
impl Command for AttrCategory {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"attr category"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("attr category")
|
||||||
|
.input_output_type(Type::Nothing, Type::list(Type::String))
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
.required(
|
||||||
|
"category",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"Category of the custom command.",
|
||||||
|
)
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"Attribute for adding a category to custom commands."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let arg: String = call.req(engine_state, stack, 0)?;
|
||||||
|
Ok(Value::string(arg, call.head).into_pipeline_data())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_const(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let arg: String = call.req_const(working_set, 0)?;
|
||||||
|
Ok(Value::string(arg, call.head).into_pipeline_data())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Add a category to a custom command",
|
||||||
|
example: r###"# Double numbers
|
||||||
|
@category math
|
||||||
|
def double []: [number -> number] { $in * 2 }"###,
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
159
crates/nu-cmd-lang/src/core_commands/attr/example.rs
Normal file
159
crates/nu-cmd-lang/src/core_commands/attr/example.rs
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AttrExample;
|
||||||
|
|
||||||
|
impl Command for AttrExample {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"attr example"
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: When const closure are available, switch to using them for the `example` argument
|
||||||
|
// rather than a block. That should remove the need for `requires_ast_for_arguments` to be true
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("attr example")
|
||||||
|
.input_output_types(vec![(
|
||||||
|
Type::Nothing,
|
||||||
|
Type::Record(
|
||||||
|
[
|
||||||
|
("description".into(), Type::String),
|
||||||
|
("example".into(), Type::String),
|
||||||
|
]
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
)])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
.required(
|
||||||
|
"description",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"Description of the example.",
|
||||||
|
)
|
||||||
|
.required(
|
||||||
|
"example",
|
||||||
|
SyntaxShape::OneOf(vec![SyntaxShape::Block, SyntaxShape::String]),
|
||||||
|
"Example code snippet.",
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"result",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"Expected output of example.",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"Attribute for adding examples to custom commands."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let description: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||||
|
let result: Option<Value> = call.get_flag(engine_state, stack, "result")?;
|
||||||
|
|
||||||
|
let example_string: Result<String, _> = call.req(engine_state, stack, 1);
|
||||||
|
let example_expr = call
|
||||||
|
.positional_nth(stack, 1)
|
||||||
|
.ok_or(ShellError::MissingParameter {
|
||||||
|
param_name: "example".into(),
|
||||||
|
span: call.head,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
|
attr_example_impl(
|
||||||
|
example_expr,
|
||||||
|
example_string,
|
||||||
|
&working_set,
|
||||||
|
call,
|
||||||
|
description,
|
||||||
|
result,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_const(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let description: Spanned<String> = call.req_const(working_set, 0)?;
|
||||||
|
let result: Option<Value> = call.get_flag_const(working_set, "result")?;
|
||||||
|
|
||||||
|
let example_string: Result<String, _> = call.req_const(working_set, 1);
|
||||||
|
let example_expr =
|
||||||
|
call.assert_ast_call()?
|
||||||
|
.positional_nth(1)
|
||||||
|
.ok_or(ShellError::MissingParameter {
|
||||||
|
param_name: "example".into(),
|
||||||
|
span: call.head,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
attr_example_impl(
|
||||||
|
example_expr,
|
||||||
|
example_string,
|
||||||
|
working_set,
|
||||||
|
call,
|
||||||
|
description,
|
||||||
|
result,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn requires_ast_for_arguments(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Add examples to custom command",
|
||||||
|
example: r###"# Double numbers
|
||||||
|
@example "double an int" { 2 | double } --result 4
|
||||||
|
@example "double a float" { 0.25 | double } --result 0.5
|
||||||
|
def double []: [number -> number] { $in * 2 }"###,
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attr_example_impl(
|
||||||
|
example_expr: &nu_protocol::ast::Expression,
|
||||||
|
example_string: Result<String, ShellError>,
|
||||||
|
working_set: &StateWorkingSet<'_>,
|
||||||
|
call: &Call<'_>,
|
||||||
|
description: Spanned<String>,
|
||||||
|
result: Option<Value>,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let example_content = match example_expr.as_block() {
|
||||||
|
Some(block_id) => {
|
||||||
|
let block = working_set.get_block(block_id);
|
||||||
|
let contents =
|
||||||
|
working_set.get_span_contents(block.span.expect("a block must have a span"));
|
||||||
|
let contents = contents
|
||||||
|
.strip_prefix(b"{")
|
||||||
|
.and_then(|x| x.strip_suffix(b"}"))
|
||||||
|
.unwrap_or(contents)
|
||||||
|
.trim_ascii();
|
||||||
|
String::from_utf8_lossy(contents).into_owned()
|
||||||
|
}
|
||||||
|
None => example_string?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut rec = record! {
|
||||||
|
"description" => Value::string(description.item, description.span),
|
||||||
|
"example" => Value::string(example_content, example_expr.span),
|
||||||
|
};
|
||||||
|
if let Some(result) = result {
|
||||||
|
rec.push("result", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::record(rec, call.head).into_pipeline_data())
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user