mirror of
https://github.com/nushell/nushell.git
synced 2025-07-05 00:50:11 +02:00
Compare commits
396 Commits
Author | SHA1 | Date | |
---|---|---|---|
30f98f7e64 | |||
c9409a2edb | |||
b857064d65 | |||
a541382776 | |||
07ad24ab97 | |||
55db643048 | |||
8f9b198d48 | |||
6c7129cc0c | |||
919d55f3fc | |||
bdf63420d1 | |||
b7af715f6b | |||
b6eda33438 | |||
ab641d9f18 | |||
c7e128eed1 | |||
cc0259bbed | |||
23fba6d2ea | |||
3182adb6a0 | |||
d52ec65f18 | |||
b968376be9 | |||
90bd8c82b7 | |||
0955e8c5b6 | |||
ef55367224 | |||
a60f454154 | |||
7a7df3e635 | |||
62198a29c2 | |||
e87a35104a | |||
1e051e573d | |||
e172a621f3 | |||
9f09930834 | |||
20c2de9eed | |||
22ca5a6b8d | |||
8b19399b13 | |||
d289c773d0 | |||
a935e0720f | |||
1c3ff179bc | |||
ccab3d6b6e | |||
3e39fae6e1 | |||
d575fd1c3a | |||
0a2fb137af | |||
4907575d3d | |||
4200df21d3 | |||
e0bb5a2bd2 | |||
a6c2c685bc | |||
1e2fa68db0 | |||
599f16f15c | |||
91da168251 | |||
e104bccfb9 | |||
74bd0e32cc | |||
03015ed33f | |||
79ea70d4ec | |||
3ec76af96e | |||
b8efd2a347 | |||
9083157baa | |||
6cdc9e3b77 | |||
f8d4adfb7a | |||
719d9aa83c | |||
9ebaa737aa | |||
88b0982dac | |||
8c2e12ad79 | |||
2c31b3db07 | |||
eedf833b6f | |||
69d81cc065 | |||
af9c31152a | |||
abb6fca5e3 | |||
3ec1c40320 | |||
619211c1bf | |||
3a685049da | |||
ae54d05930 | |||
e7c4597ad0 | |||
09c9495015 | |||
e05f387632 | |||
9870c7c9a6 | |||
3f75b6b371 | |||
04fed82e5e | |||
f3a1dfef95 | |||
f738932bbd | |||
4968b6b9d0 | |||
ee97c00818 | |||
1dbd431117 | |||
09ab583f64 | |||
9ad6d13982 | |||
8d4426f2f8 | |||
8c8f795e9e | |||
7f2f67238f | |||
740fe942c1 | |||
7c5dcbb3fc | |||
7e055810b1 | |||
5758993e9f | |||
d7014e671d | |||
b0427ca9ff | |||
3af575cce7 | |||
f787d272e6 | |||
f061c9a30e | |||
8812072f06 | |||
e911ff4d67 | |||
28b6db115a | |||
e735bd475f | |||
299d199150 | |||
5e784d38eb | |||
868029f655 | |||
043d1ed9fb | |||
6230a62e9e | |||
71b49c3374 | |||
2eef42c6b9 | |||
0209992f6c | |||
c9d54f821b | |||
59d6dee3b3 | |||
9d25b2f29a | |||
91ff57faa7 | |||
b99affba4b | |||
639bd4fc2e | |||
a0f38f8845 | |||
a11c9e9d70 | |||
bdbcf82967 | |||
1f47d72e86 | |||
d83781ddec | |||
e32e55938b | |||
de08b68ba8 | |||
0e3a8c552c | |||
389e7d2502 | |||
fce6146576 | |||
02313e6819 | |||
df0a174802 | |||
bcb7ef48b6 | |||
9f714e62cb | |||
a95c2198a6 | |||
2df91e7f92 | |||
44be445b57 | |||
e43632fd95 | |||
69e4abad0f | |||
3bedbd0669 | |||
1d15bbc95b | |||
5002d87af4 | |||
2a3805c164 | |||
52f646d8db | |||
36c1073441 | |||
2979595cc5 | |||
d67120be19 | |||
ad31f1cf26 | |||
99798ace7d | |||
ba4becc61c | |||
397499b106 | |||
55c3fc9141 | |||
2830ec008c | |||
4c8b09eb97 | |||
98e0864be8 | |||
6dc71f5ad0 | |||
d6f4e4c4fe | |||
33ae71f300 | |||
abcca0897e | |||
b1379b2b14 | |||
27ebccce80 | |||
6964968f14 | |||
68377c176d | |||
baadaee016 | |||
199aa2ad3a | |||
29b176b719 | |||
6ce20675eb | |||
1e9967c3bf | |||
e0bc85d0dd | |||
e3fd4d3f81 | |||
00709fc5bd | |||
157494e803 | |||
f03ba6793e | |||
475aa4f1dd | |||
1d6ac16530 | |||
573a7e2c7b | |||
cebbc82322 | |||
cf5b2aeb88 | |||
52eb9c2ef3 | |||
702dcd8581 | |||
9e6ada6411 | |||
a38663ec90 | |||
02804ab537 | |||
b2d0d9cf13 | |||
46589faaca | |||
166d5fa4ff | |||
4bd38847c2 | |||
30a4187be4 | |||
f0c83a4459 | |||
fc61416c79 | |||
8200831b07 | |||
497954d84c | |||
bcaef8959c | |||
5bef81a059 | |||
d68c3ec89a | |||
0c72f881a6 | |||
8195e2d638 | |||
e8c20390e0 | |||
13df0af514 | |||
54e9aa92bc | |||
1afff777a6 | |||
071faae772 | |||
08a241f763 | |||
63f9e273b3 | |||
71d604067a | |||
66d0e18674 | |||
a940a8aa80 | |||
0d30550950 | |||
65bb0ff167 | |||
151767a5e3 | |||
a948ec6c2c | |||
28a7461057 | |||
6f47990a63 | |||
183c2221bb | |||
03ee54a4df | |||
2541a712e4 | |||
ee877607fb | |||
93351b889a | |||
5fa9d76500 | |||
cd0d0364ec | |||
cf5fec63c0 | |||
5c5cf418fb | |||
fb14008f50 | |||
18c8c16c5e | |||
299a218de7 | |||
1a081c09de | |||
6e1e824473 | |||
af77bc60e2 | |||
9ca0fb772d | |||
c535c24d03 | |||
c9cb62067c | |||
ebc7b80c23 | |||
aaaab8e070 | |||
a59477205d | |||
5101b5e306 | |||
fb34a4fc6c | |||
1bcceafd93 | |||
217eb4ed70 | |||
78af66f2ce | |||
f63cecc316 | |||
8d60c0d35d | |||
0c139c7411 | |||
c4bac90b35 | |||
2ab8751ff9 | |||
d192d854d6 | |||
6b5906613c | |||
493850b1bf | |||
6600b3edfb | |||
aff974552a | |||
2afc6a974e | |||
1e64f59220 | |||
3e074bc447 | |||
3d008e2c4e | |||
6c1c7f9509 | |||
f531cc2058 | |||
edd69aa283 | |||
3d20c53904 | |||
2d360fda7f | |||
d0c2adabf7 | |||
870eb2530c | |||
b2cab3274b | |||
92091599ff | |||
4d2d553cca | |||
dc53c20628 | |||
e7c5f83460 | |||
f611196373 | |||
abd230e12e | |||
4792328d0e | |||
63b94dbd28 | |||
eb0de25d19 | |||
ebe42241fe | |||
fa4f9b083e | |||
e7b7c7f39a | |||
6be458c686 | |||
348c59b740 | |||
2c827d2bf4 | |||
61544eecd6 | |||
c150af4279 | |||
39bda8986e | |||
ee997ef3dd | |||
e3f59910b8 | |||
f4940e115f | |||
3f31ca7b8e | |||
0119534f61 | |||
84e1ac27e5 | |||
644bebf4c6 | |||
f58a4b5017 | |||
ae0e13733d | |||
2c379cba71 | |||
7171c9b84a | |||
055d7e27e9 | |||
af76e11dd6 | |||
7dda39a89e | |||
4f822e263f | |||
a39e94de8a | |||
a88f46c6c9 | |||
4ca1f95b6c | |||
71ced35987 | |||
1128df2d29 | |||
da98c23ab3 | |||
e3efc8da9f | |||
3f332bef35 | |||
525eac1afd | |||
7003b007d5 | |||
dfdb2b5d31 | |||
822007dbbb | |||
0560826414 | |||
39b0f3bdda | |||
712fec166d | |||
43dcf19ac3 | |||
ffddee5678 | |||
95b78eee25 | |||
3ab9f0b90a | |||
9261c0c55a | |||
7a888c9e9b | |||
e211b7ba53 | |||
60769ac1ba | |||
34e7bd861c | |||
d667b3c0bc | |||
b63c3514c4 | |||
7e9d32d64e | |||
621fb25670 | |||
a80273bd7b | |||
5473def7ef | |||
04746b8e2d | |||
31b3104af7 | |||
803bc9c63f | |||
e690e7aac0 | |||
e841fce0f9 | |||
d7b0dc1275 | |||
5627c95916 | |||
f122065772 | |||
4732507f46 | |||
5f45f6c223 | |||
a55d172e52 | |||
48e401834d | |||
d5946a9667 | |||
a432bf94ec | |||
0eabbb88dd | |||
80c8edcfb4 | |||
059167ac96 | |||
4e205cd9a7 | |||
18772b73b3 | |||
983014cc40 | |||
4ff33933dd | |||
035308bb1d | |||
e530e7d654 | |||
7d4449f021 | |||
ec3e0e593d | |||
ff09c7964e | |||
ce13ecfd10 | |||
c18e6bfca0 | |||
bc6947cd09 | |||
faaa12838e | |||
edee2a3c15 | |||
2ced9e4d19 | |||
926331dbfb | |||
eca2975b3d | |||
1cd0544a3f | |||
73e8de9753 | |||
4e83ccdf86 | |||
6d36941e55 | |||
2f44801414 | |||
9172b22985 | |||
1c37f4b958 | |||
56ed532038 | |||
b974f8f7e3 | |||
802bfed173 | |||
07e7c8c81f | |||
20b53067cd | |||
f4c0d9d45b | |||
85b06b22d9 | |||
63f00e78d1 | |||
ff1ad77130 | |||
af34d5c062 | |||
ed82f9ee18 | |||
d081e3386f | |||
ca8eb856e8 | |||
168835ecd2 | |||
4157ca711d | |||
d34a24db33 | |||
2c6b1471e1 | |||
ae5fed41ed | |||
ca73d85c09 | |||
f82c43f850 | |||
3dc9691aaa | |||
42531e017c | |||
928c57db41 | |||
d880241102 | |||
813aac89bd | |||
d2bf82d22b | |||
3f12b14053 | |||
8e2917b9ae | |||
3c3ec7891c | |||
6b839c3c32 | |||
ea22c319b6 | |||
7432e67da1 | |||
0576794e74 | |||
12f57dbc62 | |||
fe57c5c22e | |||
18161e5707 | |||
466b3899e0 | |||
7b82c6b482 | |||
e3f78b8793 | |||
c31291753c |
16
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
16
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -13,7 +13,7 @@ body:
|
|||||||
id: repro
|
id: repro
|
||||||
attributes:
|
attributes:
|
||||||
label: How to reproduce
|
label: How to reproduce
|
||||||
description: Steps to reproduce the behavior
|
description: Steps to reproduce the behavior (including succinct code examples or screenshots of the observed behavior)
|
||||||
placeholder: |
|
placeholder: |
|
||||||
1.
|
1.
|
||||||
2.
|
2.
|
||||||
@ -28,13 +28,6 @@ body:
|
|||||||
placeholder: I expected nu to...
|
placeholder: I expected nu to...
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
|
||||||
id: screenshots
|
|
||||||
attributes:
|
|
||||||
label: Screenshots
|
|
||||||
description: Please add any relevant screenshots here, if any
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: config
|
id: config
|
||||||
attributes:
|
attributes:
|
||||||
@ -55,10 +48,3 @@ body:
|
|||||||
| installed_plugins | binaryview, chart bar, chart line, fetch, from bson, from sqlite, inc, match, post, ps, query json, s3, selector, start, sys, textview, to bson, to sqlite, tree, xpath |
|
| installed_plugins | binaryview, chart bar, chart line, fetch, from bson, from sqlite, inc, match, post, ps, query json, s3, selector, start, sys, textview, to bson, to sqlite, tree, xpath |
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
|
||||||
id: context
|
|
||||||
attributes:
|
|
||||||
label: Additional context
|
|
||||||
description: Add any other context about the problem here.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
2
.github/workflows/audit.yml
vendored
2
.github/workflows/audit.yml
vendored
@ -20,6 +20,6 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.7
|
- uses: actions/checkout@v4.1.7
|
||||||
- uses: rustsec/audit-check@v1.4.1
|
- uses: rustsec/audit-check@v2.0.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
15
.github/workflows/ci.yml
vendored
15
.github/workflows/ci.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4.1.7
|
- uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
||||||
|
|
||||||
- name: cargo fmt
|
- name: cargo fmt
|
||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
@ -57,11 +57,6 @@ jobs:
|
|||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
platform: [windows-latest, macos-latest, ubuntu-20.04]
|
platform: [windows-latest, macos-latest, ubuntu-20.04]
|
||||||
include:
|
|
||||||
- default-flags: ""
|
|
||||||
# linux CI cannot handle clipboard feature
|
|
||||||
- platform: ubuntu-20.04
|
|
||||||
default-flags: "--no-default-features --features=default-no-clipboard"
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
@ -69,10 +64,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.9.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.default-flags }}
|
run: cargo test --workspace --profile ci --exclude nu_plugin_*
|
||||||
- name: Check for clean repo
|
- name: Check for clean repo
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@ -98,7 +93,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.9.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
||||||
|
|
||||||
- name: Install Nushell
|
- name: Install Nushell
|
||||||
run: cargo install --path . --locked --no-default-features
|
run: cargo install --path . --locked --no-default-features
|
||||||
@ -149,7 +144,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4.1.7
|
- uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
||||||
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS
|
run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS
|
||||||
|
30
.github/workflows/milestone.yml
vendored
Normal file
30
.github/workflows/milestone.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Description:
|
||||||
|
# - Add milestone to a merged PR automatically
|
||||||
|
# - Add milestone to a closed issue that has a merged PR fix (if any)
|
||||||
|
|
||||||
|
name: Milestone Action
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [closed]
|
||||||
|
pull_request_target:
|
||||||
|
types: [closed]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-milestone:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Milestone Update
|
||||||
|
steps:
|
||||||
|
- name: Set Milestone for PR
|
||||||
|
uses: hustcer/milestone-action@main
|
||||||
|
if: github.event.pull_request.merged == true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
# Bind milestone to closed issue that has a merged PR fix
|
||||||
|
- name: Set Milestone for Issue
|
||||||
|
uses: hustcer/milestone-action@main
|
||||||
|
if: github.event.issue.state == 'closed'
|
||||||
|
with:
|
||||||
|
action: bind-issue
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
33
.github/workflows/nightly-build.yml
vendored
33
.github/workflows/nightly-build.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
|||||||
# if: github.repository == 'nushell/nightly'
|
# if: github.repository == 'nushell/nightly'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4
|
||||||
if: github.repository == 'nushell/nightly'
|
if: github.repository == 'nushell/nightly'
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
@ -36,10 +36,10 @@ jobs:
|
|||||||
token: ${{ secrets.WORKFLOW_TOKEN }}
|
token: ${{ secrets.WORKFLOW_TOKEN }}
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.12
|
uses: hustcer/setup-nu@v3
|
||||||
if: github.repository == 'nushell/nightly'
|
if: github.repository == 'nushell/nightly'
|
||||||
with:
|
with:
|
||||||
version: 0.95.0
|
version: 0.98.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
|
||||||
@ -65,7 +65,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
standard:
|
standard:
|
||||||
name: Std
|
name: Nu
|
||||||
needs: prepare
|
needs: prepare
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@ -78,8 +78,11 @@ jobs:
|
|||||||
- x86_64-unknown-linux-gnu
|
- x86_64-unknown-linux-gnu
|
||||||
- x86_64-unknown-linux-musl
|
- x86_64-unknown-linux-musl
|
||||||
- aarch64-unknown-linux-gnu
|
- aarch64-unknown-linux-gnu
|
||||||
|
- aarch64-unknown-linux-musl
|
||||||
- armv7-unknown-linux-gnueabihf
|
- armv7-unknown-linux-gnueabihf
|
||||||
|
- armv7-unknown-linux-musleabihf
|
||||||
- riscv64gc-unknown-linux-gnu
|
- riscv64gc-unknown-linux-gnu
|
||||||
|
- loongarch64-unknown-linux-gnu
|
||||||
extra: ['bin']
|
extra: ['bin']
|
||||||
include:
|
include:
|
||||||
- target: aarch64-apple-darwin
|
- target: aarch64-apple-darwin
|
||||||
@ -104,15 +107,21 @@ jobs:
|
|||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
- target: aarch64-unknown-linux-gnu
|
- target: aarch64-unknown-linux-gnu
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
|
- target: aarch64-unknown-linux-musl
|
||||||
|
os: ubuntu-22.04
|
||||||
- target: armv7-unknown-linux-gnueabihf
|
- target: armv7-unknown-linux-gnueabihf
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
|
- target: armv7-unknown-linux-musleabihf
|
||||||
|
os: ubuntu-22.04
|
||||||
- target: riscv64gc-unknown-linux-gnu
|
- target: riscv64gc-unknown-linux-gnu
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
|
- target: loongarch64-unknown-linux-gnu
|
||||||
|
os: ubuntu-22.04
|
||||||
|
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.7
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@ -122,15 +131,15 @@ jobs:
|
|||||||
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||||
|
|
||||||
- name: Setup Rust toolchain and cache
|
- name: Setup Rust toolchain and cache
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
||||||
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||||
with:
|
with:
|
||||||
rustflags: ''
|
rustflags: ''
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.12
|
uses: hustcer/setup-nu@v3
|
||||||
with:
|
with:
|
||||||
version: 0.95.0
|
version: 0.98.0
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
@ -161,7 +170,7 @@ jobs:
|
|||||||
# REF: https://github.com/marketplace/actions/gh-release
|
# REF: https://github.com/marketplace/actions/gh-release
|
||||||
# Create a release only in nushell/nightly repo
|
# Create a release only in nushell/nightly repo
|
||||||
- name: Publish Archive
|
- name: Publish Archive
|
||||||
uses: softprops/action-gh-release@v2.0.8
|
uses: softprops/action-gh-release@v2.0.9
|
||||||
if: ${{ startsWith(github.repository, 'nushell/nightly') }}
|
if: ${{ startsWith(github.repository, 'nushell/nightly') }}
|
||||||
with:
|
with:
|
||||||
prerelease: true
|
prerelease: true
|
||||||
@ -181,14 +190,14 @@ jobs:
|
|||||||
- name: Waiting for Release
|
- name: Waiting for Release
|
||||||
run: sleep 1800
|
run: sleep 1800
|
||||||
|
|
||||||
- uses: actions/checkout@v4.1.7
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.12
|
uses: hustcer/setup-nu@v3
|
||||||
with:
|
with:
|
||||||
version: 0.95.0
|
version: 0.98.0
|
||||||
|
|
||||||
# Keep the last a few releases
|
# Keep the last a few releases
|
||||||
- name: Delete Older Releases
|
- name: Delete Older Releases
|
||||||
|
21
.github/workflows/release-pkg.nu
vendored
21
.github/workflows/release-pkg.nu
vendored
@ -84,6 +84,27 @@ if $os in ['macos-latest'] or $USE_UBUNTU {
|
|||||||
$env.CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER = 'arm-linux-gnueabihf-gcc'
|
$env.CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER = 'arm-linux-gnueabihf-gcc'
|
||||||
cargo-build-nu
|
cargo-build-nu
|
||||||
}
|
}
|
||||||
|
'aarch64-unknown-linux-musl' => {
|
||||||
|
aria2c https://musl.cc/aarch64-linux-musl-cross.tgz
|
||||||
|
tar -xf aarch64-linux-musl-cross.tgz -C $env.HOME
|
||||||
|
$env.PATH = ($env.PATH | split row (char esep) | prepend $'($env.HOME)/aarch64-linux-musl-cross/bin')
|
||||||
|
$env.CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER = 'aarch64-linux-musl-gcc'
|
||||||
|
cargo-build-nu
|
||||||
|
}
|
||||||
|
'armv7-unknown-linux-musleabihf' => {
|
||||||
|
aria2c https://musl.cc/armv7r-linux-musleabihf-cross.tgz
|
||||||
|
tar -xf armv7r-linux-musleabihf-cross.tgz -C $env.HOME
|
||||||
|
$env.PATH = ($env.PATH | split row (char esep) | prepend $'($env.HOME)/armv7r-linux-musleabihf-cross/bin')
|
||||||
|
$env.CARGO_TARGET_ARMV7_UNKNOWN_LINUX_MUSLEABIHF_LINKER = 'armv7r-linux-musleabihf-gcc'
|
||||||
|
cargo-build-nu
|
||||||
|
}
|
||||||
|
'loongarch64-unknown-linux-gnu' => {
|
||||||
|
aria2c https://github.com/loongson/build-tools/releases/download/2024.08.08/x86_64-cross-tools-loongarch64-binutils_2.43-gcc_14.2.0-glibc_2.40.tar.xz
|
||||||
|
tar xf x86_64-cross-tools-loongarch64-*.tar.xz
|
||||||
|
$env.PATH = ($env.PATH | split row (char esep) | prepend $'($env.PWD)/cross-tools/bin')
|
||||||
|
$env.CARGO_TARGET_LOONGARCH64_UNKNOWN_LINUX_GNU_LINKER = 'loongarch64-unknown-linux-gnu-gcc'
|
||||||
|
cargo-build-nu
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
||||||
# Actually just for x86_64-unknown-linux-musl target
|
# Actually just for x86_64-unknown-linux-musl target
|
||||||
|
49
.github/workflows/release.yml
vendored
49
.github/workflows/release.yml
vendored
@ -14,8 +14,8 @@ defaults:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
standard:
|
release:
|
||||||
name: Std
|
name: Nu
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@ -28,8 +28,11 @@ jobs:
|
|||||||
- x86_64-unknown-linux-gnu
|
- x86_64-unknown-linux-gnu
|
||||||
- x86_64-unknown-linux-musl
|
- x86_64-unknown-linux-musl
|
||||||
- aarch64-unknown-linux-gnu
|
- aarch64-unknown-linux-gnu
|
||||||
|
- aarch64-unknown-linux-musl
|
||||||
- armv7-unknown-linux-gnueabihf
|
- armv7-unknown-linux-gnueabihf
|
||||||
|
- armv7-unknown-linux-musleabihf
|
||||||
- riscv64gc-unknown-linux-gnu
|
- riscv64gc-unknown-linux-gnu
|
||||||
|
- loongarch64-unknown-linux-gnu
|
||||||
extra: ['bin']
|
extra: ['bin']
|
||||||
include:
|
include:
|
||||||
- target: aarch64-apple-darwin
|
- target: aarch64-apple-darwin
|
||||||
@ -54,31 +57,37 @@ jobs:
|
|||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
- target: aarch64-unknown-linux-gnu
|
- target: aarch64-unknown-linux-gnu
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
|
- target: aarch64-unknown-linux-musl
|
||||||
|
os: ubuntu-22.04
|
||||||
- target: armv7-unknown-linux-gnueabihf
|
- target: armv7-unknown-linux-gnueabihf
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
|
- target: armv7-unknown-linux-musleabihf
|
||||||
|
os: ubuntu-22.04
|
||||||
- target: riscv64gc-unknown-linux-gnu
|
- target: riscv64gc-unknown-linux-gnu
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
|
- target: loongarch64-unknown-linux-gnu
|
||||||
|
os: ubuntu-22.04
|
||||||
|
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.7
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- 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.9.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.10.1
|
||||||
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
|
||||||
with:
|
with:
|
||||||
cache: false
|
cache: false
|
||||||
rustflags: ''
|
rustflags: ''
|
||||||
|
|
||||||
- name: Setup Nushell
|
- name: Setup Nushell
|
||||||
uses: hustcer/setup-nu@v3.12
|
uses: hustcer/setup-nu@v3
|
||||||
with:
|
with:
|
||||||
version: 0.95.0
|
version: 0.98.0
|
||||||
|
|
||||||
- name: Release Nu Binary
|
- name: Release Nu Binary
|
||||||
id: nu
|
id: nu
|
||||||
@ -89,12 +98,36 @@ jobs:
|
|||||||
TARGET: ${{ matrix.target }}
|
TARGET: ${{ matrix.target }}
|
||||||
_EXTRA_: ${{ matrix.extra }}
|
_EXTRA_: ${{ matrix.extra }}
|
||||||
|
|
||||||
# REF: https://github.com/marketplace/actions/gh-release
|
# WARN: Don't upgrade this action due to the release per asset issue.
|
||||||
|
# See: https://github.com/softprops/action-gh-release/issues/445
|
||||||
- name: Publish Archive
|
- name: Publish Archive
|
||||||
uses: softprops/action-gh-release@v2.0.8
|
uses: softprops/action-gh-release@v2.0.5
|
||||||
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.archive }}
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
sha256sum:
|
||||||
|
needs: release
|
||||||
|
name: Create Sha256sum
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download Release Archives
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: >-
|
||||||
|
gh release download ${{ github.ref_name }}
|
||||||
|
--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.5
|
||||||
|
with:
|
||||||
|
draft: true
|
||||||
|
files: SHA256SUMS
|
||||||
|
env:
|
||||||
|
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.23.3
|
uses: crate-ci/typos@v1.27.0
|
||||||
|
2034
Cargo.lock
generated
2034
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
119
Cargo.toml
119
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.77.2"
|
rust-version = "1.80.1"
|
||||||
version = "0.96.1"
|
version = "0.100.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -66,61 +66,59 @@ 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.2"
|
bracoxide = "0.1.4"
|
||||||
brotli = "5.0"
|
brotli = "6.0"
|
||||||
byteorder = "1.5"
|
byteorder = "1.5"
|
||||||
|
bytes = "1"
|
||||||
bytesize = "1.3"
|
bytesize = "1.3"
|
||||||
calamine = "0.24.0"
|
calamine = "0.24.0"
|
||||||
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"
|
||||||
chrono-tz = "0.8"
|
chrono-tz = "0.10"
|
||||||
convert_case = "0.6"
|
|
||||||
crossbeam-channel = "0.5.8"
|
crossbeam-channel = "0.5.8"
|
||||||
crossterm = "0.27"
|
crossterm = "0.28.1"
|
||||||
csv = "1.3"
|
csv = "1.3"
|
||||||
ctrlc = "3.4"
|
ctrlc = "3.4"
|
||||||
deunicode = "1.6.0"
|
|
||||||
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"
|
||||||
dirs-sys = "0.4"
|
dirs-sys = "0.4"
|
||||||
dtparse = "2.0"
|
dtparse = "2.0"
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
fancy-regex = "0.13"
|
fancy-regex = "0.14"
|
||||||
filesize = "0.2"
|
filesize = "0.2"
|
||||||
filetime = "0.2"
|
filetime = "0.2"
|
||||||
fs_extra = "1.3"
|
|
||||||
fuzzy-matcher = "0.3"
|
fuzzy-matcher = "0.3"
|
||||||
hamcrest2 = "0.3"
|
|
||||||
heck = "0.5.0"
|
heck = "0.5.0"
|
||||||
human-date-parser = "0.1.1"
|
human-date-parser = "0.2.0"
|
||||||
indexmap = "2.2"
|
indexmap = "2.6"
|
||||||
indicatif = "0.17"
|
indicatif = "0.17"
|
||||||
interprocess = "2.2.0"
|
interprocess = "2.2.0"
|
||||||
is_executable = "1.0"
|
is_executable = "1.0"
|
||||||
itertools = "0.12"
|
itertools = "0.13"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
libproc = "0.14"
|
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.5"
|
||||||
lsp-types = "0.95.0"
|
lsp-types = { version = "0.95.0", features = ["proposed"] }
|
||||||
mach2 = "0.4"
|
mach2 = "0.4"
|
||||||
md5 = { version = "0.10", package = "md-5" }
|
md5 = { version = "0.10", package = "md-5" }
|
||||||
miette = "7.2"
|
miette = "7.2"
|
||||||
mime = "0.3"
|
mime = "0.3.17"
|
||||||
mime_guess = "2.0"
|
mime_guess = "2.0"
|
||||||
mockito = { version = "1.4", default-features = false }
|
mockito = { version = "1.5", default-features = false }
|
||||||
|
multipart-rs = "0.1.11"
|
||||||
native-tls = "0.2"
|
native-tls = "0.2"
|
||||||
nix = { version = "0.28", default-features = false }
|
nix = { version = "0.29", default-features = false }
|
||||||
notify-debouncer-full = { version = "0.3", default-features = false }
|
notify-debouncer-full = { version = "0.3", default-features = false }
|
||||||
nu-ansi-term = "0.50.1"
|
nu-ansi-term = "0.50.1"
|
||||||
num-format = "0.4"
|
num-format = "0.4"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
|
oem_cp = "2.0.0"
|
||||||
omnipath = "0.1"
|
omnipath = "0.1"
|
||||||
once_cell = "1.18"
|
|
||||||
open = "5.3"
|
open = "5.3"
|
||||||
os_pipe = { version = "1.2", features = ["io_safety"] }
|
os_pipe = { version = "1.2", features = ["io_safety"] }
|
||||||
pathdiff = "0.2"
|
pathdiff = "0.2"
|
||||||
@ -131,14 +129,15 @@ proc-macro-error = { version = "1.0", default-features = false }
|
|||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
procfs = "0.16.0"
|
procfs = "0.16.0"
|
||||||
pwd = "1.3"
|
pwd = "1.3"
|
||||||
quick-xml = "0.31.0"
|
quick-xml = "0.32.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.8"
|
||||||
|
rand_chacha = "0.3.1"
|
||||||
ratatui = "0.26"
|
ratatui = "0.26"
|
||||||
rayon = "1.10"
|
rayon = "1.10"
|
||||||
reedline = "0.33.0"
|
reedline = "0.37.0"
|
||||||
regex = "1.9.5"
|
regex = "1.9.5"
|
||||||
rmp = "0.8"
|
rmp = "0.8"
|
||||||
rmp-serde = "1.3"
|
rmp-serde = "1.3"
|
||||||
@ -147,23 +146,22 @@ roxmltree = "0.19"
|
|||||||
rstest = { version = "0.18", default-features = false }
|
rstest = { version = "0.18", default-features = false }
|
||||||
rusqlite = "0.31"
|
rusqlite = "0.31"
|
||||||
rust-embed = "8.5.0"
|
rust-embed = "8.5.0"
|
||||||
same-file = "1.0"
|
serde = { version = "1.0" }
|
||||||
serde = { version = "1.0", default-features = false }
|
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_urlencoded = "0.7.1"
|
serde_urlencoded = "0.7.1"
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
strip-ansi-escapes = "0.2.0"
|
strip-ansi-escapes = "0.2.0"
|
||||||
syn = "2.0"
|
syn = "2.0"
|
||||||
sysinfo = "0.30"
|
sysinfo = "0.32"
|
||||||
tabled = { version = "0.14.0", default-features = false }
|
tabled = { version = "0.16.0", default-features = false }
|
||||||
tempfile = "3.10"
|
tempfile = "3.13"
|
||||||
terminal_size = "0.3"
|
terminal_size = "0.3"
|
||||||
titlecase = "2.0"
|
titlecase = "2.0"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
trash = "3.3"
|
trash = "5.2"
|
||||||
umask = "2.1"
|
umask = "2.1"
|
||||||
unicode-segmentation = "1.11"
|
unicode-segmentation = "1.12"
|
||||||
unicode-width = "0.1"
|
unicode-width = "0.1"
|
||||||
ureq = { version = "2.10", default-features = false }
|
ureq = { version = "2.10", default-features = false }
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
@ -174,31 +172,39 @@ uu_mv = "0.0.27"
|
|||||||
uu_whoami = "0.0.27"
|
uu_whoami = "0.0.27"
|
||||||
uu_uname = "0.0.27"
|
uu_uname = "0.0.27"
|
||||||
uucore = "0.0.27"
|
uucore = "0.0.27"
|
||||||
uuid = "1.10.0"
|
uuid = "1.11.0"
|
||||||
v_htmlescape = "0.15.0"
|
v_htmlescape = "0.15.0"
|
||||||
wax = "0.6"
|
wax = "0.6"
|
||||||
which = "6.0.0"
|
which = "6.0.0"
|
||||||
windows = "0.54"
|
windows = "0.56"
|
||||||
windows-sys = "0.48"
|
windows-sys = "0.48"
|
||||||
winreg = "0.52"
|
winreg = "0.52"
|
||||||
|
|
||||||
|
[workspace.lints.clippy]
|
||||||
|
# Warning: workspace lints affect library code as well as tests, so don't enable lints that would be too noisy in tests like that.
|
||||||
|
# todo = "warn"
|
||||||
|
unchecked_duration_subtraction = "warn"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cli = { path = "./crates/nu-cli", version = "0.96.1" }
|
nu-cli = { path = "./crates/nu-cli", version = "0.100.0" }
|
||||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.96.1" }
|
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.100.0" }
|
||||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.96.1" }
|
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.100.0" }
|
||||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.96.1", optional = true }
|
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.100.0", optional = true }
|
||||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.96.1" }
|
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.100.0" }
|
||||||
nu-command = { path = "./crates/nu-command", version = "0.96.1" }
|
nu-command = { path = "./crates/nu-command", version = "0.100.0" }
|
||||||
nu-engine = { path = "./crates/nu-engine", version = "0.96.1" }
|
nu-engine = { path = "./crates/nu-engine", version = "0.100.0" }
|
||||||
nu-explore = { path = "./crates/nu-explore", version = "0.96.1" }
|
nu-explore = { path = "./crates/nu-explore", version = "0.100.0" }
|
||||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.96.1" }
|
nu-lsp = { path = "./crates/nu-lsp/", version = "0.100.0" }
|
||||||
nu-parser = { path = "./crates/nu-parser", version = "0.96.1" }
|
nu-parser = { path = "./crates/nu-parser", version = "0.100.0" }
|
||||||
nu-path = { path = "./crates/nu-path", version = "0.96.1" }
|
nu-path = { path = "./crates/nu-path", version = "0.100.0" }
|
||||||
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.96.1" }
|
nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.100.0" }
|
||||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.96.1" }
|
nu-protocol = { path = "./crates/nu-protocol", version = "0.100.0" }
|
||||||
nu-std = { path = "./crates/nu-std", version = "0.96.1" }
|
nu-std = { path = "./crates/nu-std", version = "0.100.0" }
|
||||||
nu-system = { path = "./crates/nu-system", version = "0.96.1" }
|
nu-system = { path = "./crates/nu-system", version = "0.100.0" }
|
||||||
nu-utils = { path = "./crates/nu-utils", version = "0.96.1" }
|
nu-utils = { path = "./crates/nu-utils", version = "0.100.0" }
|
||||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
crossterm = { workspace = true }
|
crossterm = { workspace = true }
|
||||||
@ -207,6 +213,7 @@ 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 }
|
mimalloc = { version = "0.1.42", default-features = false, optional = true }
|
||||||
|
multipart-rs = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
simplelog = "0.12"
|
simplelog = "0.12"
|
||||||
time = "0.3"
|
time = "0.3"
|
||||||
@ -227,12 +234,12 @@ nix = { workspace = true, default-features = false, features = [
|
|||||||
] }
|
] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.96.1" }
|
nu-test-support = { path = "./crates/nu-test-support", version = "0.100.0" }
|
||||||
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.96.1" }
|
nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.100.0" }
|
||||||
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.96.1" }
|
nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.100.0" }
|
||||||
assert_cmd = "2.0"
|
assert_cmd = "2.0"
|
||||||
dirs = { workspace = true }
|
dirs = { workspace = true }
|
||||||
tango-bench = "0.5"
|
tango-bench = "0.6"
|
||||||
pretty_assertions = { workspace = true }
|
pretty_assertions = { workspace = true }
|
||||||
regex = { workspace = true }
|
regex = { workspace = true }
|
||||||
rstest = { workspace = true, default-features = false }
|
rstest = { workspace = true, default-features = false }
|
||||||
@ -249,10 +256,8 @@ plugin = [
|
|||||||
"nu-protocol/plugin",
|
"nu-protocol/plugin",
|
||||||
"nu-engine/plugin",
|
"nu-engine/plugin",
|
||||||
]
|
]
|
||||||
default = ["default-no-clipboard", "system-clipboard"]
|
|
||||||
# Enables convenient omitting of the system-clipboard feature, as it leads to problems in ci on linux
|
default = [
|
||||||
# See https://github.com/nushell/nushell/pull/11535
|
|
||||||
default-no-clipboard = [
|
|
||||||
"plugin",
|
"plugin",
|
||||||
"trash-support",
|
"trash-support",
|
||||||
"sqlite",
|
"sqlite",
|
||||||
@ -266,6 +271,8 @@ stable = ["default"]
|
|||||||
static-link-openssl = ["dep:openssl", "nu-cmd-lang/static-link-openssl"]
|
static-link-openssl = ["dep:openssl", "nu-cmd-lang/static-link-openssl"]
|
||||||
|
|
||||||
mimalloc = ["nu-cmd-lang/mimalloc", "dep:mimalloc"]
|
mimalloc = ["nu-cmd-lang/mimalloc", "dep:mimalloc"]
|
||||||
|
# Optional system clipboard support in `reedline`, this behavior has problematic compatibility with some systems.
|
||||||
|
# Missing X server/ Wayland can cause issues
|
||||||
system-clipboard = [
|
system-clipboard = [
|
||||||
"reedline/system_clipboard",
|
"reedline/system_clipboard",
|
||||||
"nu-cli/system-clipboard",
|
"nu-cli/system-clipboard",
|
||||||
@ -305,7 +312,7 @@ bench = false
|
|||||||
|
|
||||||
# To use a development version of a dependency please use a global override here
|
# To use a development version of a dependency please use a global override here
|
||||||
# changing versions in each sub-crate of the workspace is tedious
|
# changing versions in each sub-crate of the workspace is tedious
|
||||||
# [patch.crates-io]
|
[patch.crates-io]
|
||||||
# reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
# reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
||||||
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
|
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
|
||||||
|
|
||||||
@ -313,4 +320,4 @@ bench = false
|
|||||||
# Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
# Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "benchmarks"
|
name = "benchmarks"
|
||||||
harness = false
|
harness = false
|
||||||
|
@ -229,7 +229,7 @@ Please submit an issue or PR to be added to this list.
|
|||||||
See [Contributing](CONTRIBUTING.md) for details. Thanks to all the people who already contributed!
|
See [Contributing](CONTRIBUTING.md) for details. Thanks to all the people who already contributed!
|
||||||
|
|
||||||
<a href="https://github.com/nushell/nushell/graphs/contributors">
|
<a href="https://github.com/nushell/nushell/graphs/contributors">
|
||||||
<img src="https://contributors-img.web.app/image?repo=nushell/nushell&max=750" />
|
<img src="https://contributors-img.web.app/image?repo=nushell/nushell&max=750&columns=20" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
29
SECURITY.md
Normal file
29
SECURITY.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
As a shell and programming language Nushell provides you with great powers and the potential to do dangerous things to your computer and data. Whenever there is a risk that a malicious actor can abuse a bug or a violation of documented behavior/assumptions in Nushell to harm you this is a *security* risk.
|
||||||
|
We want to fix those issues without exposing our users to unnecessary risk. Thus we want to explain our security policy.
|
||||||
|
Additional issues may be part of *safety* where the behavior of Nushell as designed and implemented can cause unintended harm or a bug causes damage without the involvement of a third party.
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
As Nushell is still under very active pre-stable development, the only version the core team prioritizes for security and safety fixes is the [most recent version as published on GitHub](https://github.com/nushell/nushell/releases/latest).
|
||||||
|
Only if you provide a strong reasoning and the necessary resources, will we consider blessing a backported fix with an official patch release for a previous version.
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
If you suspect that a bug or behavior of Nushell can affect security or may be potentially exploitable, please report the issue to us in private.
|
||||||
|
Either reach out to the core team on [our Discord server](https://discord.gg/NtAbbGn) to arrange a private channel or use the [GitHub vulnerability reporting form](https://github.com/nushell/nushell/security/advisories/new).
|
||||||
|
Please try to answer the following questions:
|
||||||
|
- How can we reach you for further questions?
|
||||||
|
- What is the bug? Which system of Nushell may be affected?
|
||||||
|
- Do you have proof-of-concept for a potential exploit or have you observed an exploit in the wild?
|
||||||
|
- What is your assessment of the severity based on what could be impacted should the bug be exploited?
|
||||||
|
- Are additional people aware of the issue or deserve credit for identifying the issue?
|
||||||
|
|
||||||
|
We will try to get back to you within a week with:
|
||||||
|
- acknowledging the receipt of the report
|
||||||
|
- an initial plan of how we want to address this including the primary points of contact for further communication
|
||||||
|
- our preliminary assessment of how severe we judge the issue
|
||||||
|
- a proposal for how we can coordinate responsible disclosure (e.g. how we ship the bugfix, if we need to coordinate with distribution maintainers, when you can release a blog post if you want to etc.)
|
||||||
|
|
||||||
|
For purely *safety* related issues where the impact is severe by direct user action instead of malicious input or third parties, feel free to open a regular issue. If we deem that there may be an additional *security* risk on a *safety* issue we may continue discussions in a restricted forum.
|
@ -46,8 +46,8 @@ fn setup_stack_and_engine_from_command(command: &str) -> (Stack, EngineState) {
|
|||||||
|
|
||||||
let mut stack = Stack::new();
|
let mut stack = Stack::new();
|
||||||
|
|
||||||
// Support running benchmarks with IR mode
|
// Support running benchmarks without IR mode
|
||||||
stack.use_ir = std::env::var_os("NU_USE_IR").is_some();
|
stack.use_ir = std::env::var_os("NU_DISABLE_IR").is_none();
|
||||||
|
|
||||||
evaluate_commands(
|
evaluate_commands(
|
||||||
&commands,
|
&commands,
|
||||||
|
@ -5,27 +5,27 @@ 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.96.1"
|
version = "0.100.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.1" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.100.0" }
|
||||||
nu-command = { path = "../nu-command", version = "0.96.1" }
|
nu-command = { path = "../nu-command", version = "0.100.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.96.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.100.0" }
|
||||||
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.96.1" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.100.0" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.96.1" }
|
nu-engine = { path = "../nu-engine", version = "0.100.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.96.1" }
|
nu-path = { path = "../nu-path", version = "0.100.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.96.1" }
|
nu-parser = { path = "../nu-parser", version = "0.100.0" }
|
||||||
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.1", optional = true }
|
nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.100.0", optional = true }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.96.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.100.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.96.1" }
|
nu-utils = { path = "../nu-utils", version = "0.100.0" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.96.1" }
|
nu-color-config = { path = "../nu-color-config", version = "0.100.0" }
|
||||||
nu-ansi-term = { workspace = true }
|
nu-ansi-term = { workspace = true }
|
||||||
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||||
|
|
||||||
@ -37,7 +37,6 @@ is_executable = { workspace = true }
|
|||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
miette = { workspace = true, features = ["fancy-no-backtrace"] }
|
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"] }
|
||||||
once_cell = { workspace = true }
|
|
||||||
percent-encoding = { workspace = true }
|
percent-encoding = { workspace = true }
|
||||||
sysinfo = { workspace = true }
|
sysinfo = { workspace = true }
|
||||||
unicode-segmentation = { workspace = true }
|
unicode-segmentation = { workspace = true }
|
||||||
@ -46,4 +45,7 @@ which = { workspace = true }
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
plugin = ["nu-plugin-engine"]
|
plugin = ["nu-plugin-engine"]
|
||||||
system-clipboard = ["reedline/system_clipboard"]
|
system-clipboard = ["reedline/system_clipboard"]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
@ -14,7 +14,7 @@ impl Command for Commandline {
|
|||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"View the current command line input buffer."
|
"View the current command line input buffer."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ impl Command for SubCommand {
|
|||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Modify the current command line input buffer."
|
"Modify the current command line input buffer."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ impl Command for SubCommand {
|
|||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Get the current cursor position."
|
"Get the current cursor position."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ impl Command for SubCommand {
|
|||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Set the current cursor position."
|
"Set the current cursor position."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ pub fn add_cli_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
CommandlineGetCursor,
|
CommandlineGetCursor,
|
||||||
CommandlineSetCursor,
|
CommandlineSetCursor,
|
||||||
History,
|
History,
|
||||||
|
HistoryImport,
|
||||||
HistorySession,
|
HistorySession,
|
||||||
Keybindings,
|
Keybindings,
|
||||||
KeybindingsDefault,
|
KeybindingsDefault,
|
||||||
|
9
crates/nu-cli/src/commands/history/fields.rs
Normal file
9
crates/nu-cli/src/commands/history/fields.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Each const is named after a HistoryItem field, and the value is the field name to be displayed to
|
||||||
|
// the user (or accept during import).
|
||||||
|
pub const COMMAND_LINE: &str = "command";
|
||||||
|
pub const START_TIMESTAMP: &str = "start_timestamp";
|
||||||
|
pub const HOSTNAME: &str = "hostname";
|
||||||
|
pub const CWD: &str = "cwd";
|
||||||
|
pub const EXIT_STATUS: &str = "exit_status";
|
||||||
|
pub const DURATION: &str = "duration";
|
||||||
|
pub const SESSION_ID: &str = "session_id";
|
@ -5,6 +5,8 @@ use reedline::{
|
|||||||
SqliteBackedHistory,
|
SqliteBackedHistory,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::fields;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct History;
|
pub struct History;
|
||||||
|
|
||||||
@ -13,7 +15,7 @@ impl Command for History {
|
|||||||
"history"
|
"history"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Get the command history."
|
"Get the command history."
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,91 +44,76 @@ impl Command for History {
|
|||||||
let Some(history) = engine_state.history_config() else {
|
let Some(history) = engine_state.history_config() else {
|
||||||
return Ok(PipelineData::empty());
|
return Ok(PipelineData::empty());
|
||||||
};
|
};
|
||||||
|
|
||||||
// todo for sqlite history this command should be an alias to `open ~/.config/nushell/history.sqlite3 | get history`
|
// todo for sqlite history this command should be an alias to `open ~/.config/nushell/history.sqlite3 | get history`
|
||||||
if let Some(config_path) = nu_path::config_dir() {
|
let Some(history_path) = history.file_path() else {
|
||||||
let clear = call.has_flag(engine_state, stack, "clear")?;
|
return Err(ShellError::ConfigDirNotFound { span: Some(head) });
|
||||||
let long = call.has_flag(engine_state, stack, "long")?;
|
};
|
||||||
let signals = engine_state.signals().clone();
|
|
||||||
|
|
||||||
let mut history_path = config_path;
|
if call.has_flag(engine_state, stack, "clear")? {
|
||||||
history_path.push("nushell");
|
let _ = std::fs::remove_file(history_path);
|
||||||
match history.file_format {
|
// TODO: FIXME also clear the auxiliary files when using sqlite
|
||||||
HistoryFileFormat::Sqlite => {
|
return Ok(PipelineData::empty());
|
||||||
history_path.push("history.sqlite3");
|
}
|
||||||
}
|
|
||||||
HistoryFileFormat::PlainText => {
|
|
||||||
history_path.push("history.txt");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if clear {
|
let long = call.has_flag(engine_state, stack, "long")?;
|
||||||
let _ = std::fs::remove_file(history_path);
|
let signals = engine_state.signals().clone();
|
||||||
// TODO: FIXME also clear the auxiliary files when using sqlite
|
let history_reader: Option<Box<dyn ReedlineHistory>> = match history.file_format {
|
||||||
Ok(PipelineData::empty())
|
HistoryFileFormat::Sqlite => {
|
||||||
} else {
|
SqliteBackedHistory::with_file(history_path.clone(), None, None)
|
||||||
let history_reader: Option<Box<dyn ReedlineHistory>> = match history.file_format {
|
|
||||||
HistoryFileFormat::Sqlite => {
|
|
||||||
SqliteBackedHistory::with_file(history_path.clone(), None, None)
|
|
||||||
.map(|inner| {
|
|
||||||
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
|
||||||
boxed
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
HistoryFileFormat::PlainText => FileBackedHistory::with_file(
|
|
||||||
history.max_size as usize,
|
|
||||||
history_path.clone(),
|
|
||||||
)
|
|
||||||
.map(|inner| {
|
.map(|inner| {
|
||||||
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
||||||
boxed
|
boxed
|
||||||
})
|
})
|
||||||
.ok(),
|
.ok()
|
||||||
};
|
|
||||||
|
|
||||||
match history.file_format {
|
|
||||||
HistoryFileFormat::PlainText => Ok(history_reader
|
|
||||||
.and_then(|h| {
|
|
||||||
h.search(SearchQuery::everything(SearchDirection::Forward, None))
|
|
||||||
.ok()
|
|
||||||
})
|
|
||||||
.map(move |entries| {
|
|
||||||
entries.into_iter().enumerate().map(move |(idx, entry)| {
|
|
||||||
Value::record(
|
|
||||||
record! {
|
|
||||||
"command" => Value::string(entry.command_line, head),
|
|
||||||
"index" => Value::int(idx as i64, head),
|
|
||||||
},
|
|
||||||
head,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.ok_or(ShellError::FileNotFound {
|
|
||||||
file: history_path.display().to_string(),
|
|
||||||
span: head,
|
|
||||||
})?
|
|
||||||
.into_pipeline_data(head, signals)),
|
|
||||||
HistoryFileFormat::Sqlite => Ok(history_reader
|
|
||||||
.and_then(|h| {
|
|
||||||
h.search(SearchQuery::everything(SearchDirection::Forward, None))
|
|
||||||
.ok()
|
|
||||||
})
|
|
||||||
.map(move |entries| {
|
|
||||||
entries.into_iter().enumerate().map(move |(idx, entry)| {
|
|
||||||
create_history_record(idx, entry, long, head)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.ok_or(ShellError::FileNotFound {
|
|
||||||
file: history_path.display().to_string(),
|
|
||||||
span: head,
|
|
||||||
})?
|
|
||||||
.into_pipeline_data(head, signals)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
HistoryFileFormat::Plaintext => {
|
||||||
Err(ShellError::ConfigDirNotFound { span: Some(head) })
|
FileBackedHistory::with_file(history.max_size as usize, history_path.clone())
|
||||||
|
.map(|inner| {
|
||||||
|
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
||||||
|
boxed
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match history.file_format {
|
||||||
|
HistoryFileFormat::Plaintext => Ok(history_reader
|
||||||
|
.and_then(|h| {
|
||||||
|
h.search(SearchQuery::everything(SearchDirection::Forward, None))
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
.map(move |entries| {
|
||||||
|
entries.into_iter().enumerate().map(move |(idx, entry)| {
|
||||||
|
Value::record(
|
||||||
|
record! {
|
||||||
|
fields::COMMAND_LINE => Value::string(entry.command_line, head),
|
||||||
|
// TODO: This name is inconsistent with create_history_record.
|
||||||
|
"index" => Value::int(idx as i64, head),
|
||||||
|
},
|
||||||
|
head,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.ok_or(ShellError::FileNotFound {
|
||||||
|
file: history_path.display().to_string(),
|
||||||
|
span: head,
|
||||||
|
})?
|
||||||
|
.into_pipeline_data(head, signals)),
|
||||||
|
HistoryFileFormat::Sqlite => Ok(history_reader
|
||||||
|
.and_then(|h| {
|
||||||
|
h.search(SearchQuery::everything(SearchDirection::Forward, None))
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
.map(move |entries| {
|
||||||
|
entries
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(move |(idx, entry)| create_history_record(idx, entry, long, head))
|
||||||
|
})
|
||||||
|
.ok_or(ShellError::FileNotFound {
|
||||||
|
file: history_path.display().to_string(),
|
||||||
|
span: head,
|
||||||
|
})?
|
||||||
|
.into_pipeline_data(head, signals)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,58 +143,34 @@ fn create_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span)
|
|||||||
//2. Create a record of either short or long columns and values
|
//2. Create a record of either short or long columns and values
|
||||||
|
|
||||||
let item_id_value = Value::int(
|
let item_id_value = Value::int(
|
||||||
match entry.id {
|
entry
|
||||||
Some(id) => {
|
.id
|
||||||
let ids = id.to_string();
|
.and_then(|id| id.to_string().parse::<i64>().ok())
|
||||||
match ids.parse::<i64>() {
|
.unwrap_or_default(),
|
||||||
Ok(i) => i,
|
|
||||||
_ => 0i64,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => 0i64,
|
|
||||||
},
|
|
||||||
head,
|
head,
|
||||||
);
|
);
|
||||||
let start_timestamp_value = Value::string(
|
let start_timestamp_value = Value::string(
|
||||||
match entry.start_timestamp {
|
entry
|
||||||
Some(time) => time.to_string(),
|
.start_timestamp
|
||||||
None => "".into(),
|
.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);
|
||||||
let session_id_value = Value::int(
|
let session_id_value = Value::int(
|
||||||
match entry.session_id {
|
entry
|
||||||
Some(sid) => {
|
.session_id
|
||||||
let sids = sid.to_string();
|
.and_then(|id| id.to_string().parse::<i64>().ok())
|
||||||
match sids.parse::<i64>() {
|
.unwrap_or_default(),
|
||||||
Ok(i) => i,
|
|
||||||
_ => 0i64,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => 0i64,
|
|
||||||
},
|
|
||||||
head,
|
|
||||||
);
|
|
||||||
let hostname_value = Value::string(
|
|
||||||
match entry.hostname {
|
|
||||||
Some(host) => host,
|
|
||||||
None => "".into(),
|
|
||||||
},
|
|
||||||
head,
|
|
||||||
);
|
|
||||||
let cwd_value = Value::string(
|
|
||||||
match entry.cwd {
|
|
||||||
Some(cwd) => cwd,
|
|
||||||
None => "".into(),
|
|
||||||
},
|
|
||||||
head,
|
head,
|
||||||
);
|
);
|
||||||
|
let hostname_value = Value::string(entry.hostname.unwrap_or_default(), head);
|
||||||
|
let cwd_value = Value::string(entry.cwd.unwrap_or_default(), head);
|
||||||
let duration_value = Value::duration(
|
let duration_value = Value::duration(
|
||||||
match entry.duration {
|
entry
|
||||||
Some(d) => d.as_nanos().try_into().unwrap_or(0),
|
.duration
|
||||||
None => 0,
|
.and_then(|d| d.as_nanos().try_into().ok())
|
||||||
},
|
.unwrap_or(0),
|
||||||
head,
|
head,
|
||||||
);
|
);
|
||||||
let exit_status_value = Value::int(entry.exit_status.unwrap_or(0), head);
|
let exit_status_value = Value::int(entry.exit_status.unwrap_or(0), head);
|
||||||
@ -216,13 +179,13 @@ fn create_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span)
|
|||||||
Value::record(
|
Value::record(
|
||||||
record! {
|
record! {
|
||||||
"item_id" => item_id_value,
|
"item_id" => item_id_value,
|
||||||
"start_timestamp" => start_timestamp_value,
|
fields::START_TIMESTAMP => start_timestamp_value,
|
||||||
"command" => command_value,
|
fields::COMMAND_LINE => command_value,
|
||||||
"session_id" => session_id_value,
|
fields::SESSION_ID => session_id_value,
|
||||||
"hostname" => hostname_value,
|
fields::HOSTNAME => hostname_value,
|
||||||
"cwd" => cwd_value,
|
fields::CWD => cwd_value,
|
||||||
"duration" => duration_value,
|
fields::DURATION => duration_value,
|
||||||
"exit_status" => exit_status_value,
|
fields::EXIT_STATUS => exit_status_value,
|
||||||
"idx" => index_value,
|
"idx" => index_value,
|
||||||
},
|
},
|
||||||
head,
|
head,
|
||||||
@ -230,11 +193,11 @@ fn create_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span)
|
|||||||
} else {
|
} else {
|
||||||
Value::record(
|
Value::record(
|
||||||
record! {
|
record! {
|
||||||
"start_timestamp" => start_timestamp_value,
|
fields::START_TIMESTAMP => start_timestamp_value,
|
||||||
"command" => command_value,
|
fields::COMMAND_LINE => command_value,
|
||||||
"cwd" => cwd_value,
|
fields::CWD => cwd_value,
|
||||||
"duration" => duration_value,
|
fields::DURATION => duration_value,
|
||||||
"exit_status" => exit_status_value,
|
fields::EXIT_STATUS => exit_status_value,
|
||||||
},
|
},
|
||||||
head,
|
head,
|
||||||
)
|
)
|
||||||
|
415
crates/nu-cli/src/commands/history/history_import.rs
Normal file
415
crates/nu-cli/src/commands/history/history_import.rs
Normal file
@ -0,0 +1,415 @@
|
|||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
use nu_protocol::HistoryFileFormat;
|
||||||
|
|
||||||
|
use reedline::{
|
||||||
|
FileBackedHistory, History, HistoryItem, ReedlineError, SearchQuery, SqliteBackedHistory,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::fields;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct HistoryImport;
|
||||||
|
|
||||||
|
impl Command for HistoryImport {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"history import"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"Import command line history"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_description(&self) -> &str {
|
||||||
|
r#"Can import history from input, either successive command lines or more detailed records. If providing records, available fields are:
|
||||||
|
command_line, id, start_timestamp, hostname, cwd, duration, exit_status.
|
||||||
|
|
||||||
|
If no input is provided, will import all history items from existing history in the other format: if current history is stored in sqlite, it will store it in plain text and vice versa.
|
||||||
|
|
||||||
|
Note that history item IDs are ignored when importing from file."#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("history import")
|
||||||
|
.category(Category::History)
|
||||||
|
.input_output_types(vec![
|
||||||
|
(Type::Nothing, Type::Nothing),
|
||||||
|
(Type::List(Box::new(Type::String)), Type::Nothing),
|
||||||
|
(Type::table(), Type::Nothing),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
example: "history import",
|
||||||
|
description:
|
||||||
|
"Append all items from history in the other format to the current history",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
example: "echo foo | history import",
|
||||||
|
description: "Append `foo` to the current history",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
example: "[[ command_line cwd ]; [ foo /home ]] | history import",
|
||||||
|
description: "Append `foo` ran from `/home` to the current history",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let ok = Ok(Value::nothing(call.head).into_pipeline_data());
|
||||||
|
|
||||||
|
let Some(history) = engine_state.history_config() else {
|
||||||
|
return ok;
|
||||||
|
};
|
||||||
|
let Some(current_history_path) = history.file_path() else {
|
||||||
|
return Err(ShellError::ConfigDirNotFound {
|
||||||
|
span: Some(call.head),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if let Some(bak_path) = backup(¤t_history_path)? {
|
||||||
|
println!("Backed history to {}", bak_path.display());
|
||||||
|
}
|
||||||
|
match input {
|
||||||
|
PipelineData::Empty => {
|
||||||
|
let other_format = match history.file_format {
|
||||||
|
HistoryFileFormat::Sqlite => HistoryFileFormat::Plaintext,
|
||||||
|
HistoryFileFormat::Plaintext => HistoryFileFormat::Sqlite,
|
||||||
|
};
|
||||||
|
let src = new_backend(other_format, None)?;
|
||||||
|
let mut dst = new_backend(history.file_format, Some(current_history_path))?;
|
||||||
|
let items = src
|
||||||
|
.search(SearchQuery::everything(
|
||||||
|
reedline::SearchDirection::Forward,
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
.map_err(error_from_reedline)?
|
||||||
|
.into_iter()
|
||||||
|
.map(Ok);
|
||||||
|
import(dst.as_mut(), items)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let input = input.into_iter().map(item_from_value);
|
||||||
|
import(
|
||||||
|
new_backend(history.file_format, Some(current_history_path))?.as_mut(),
|
||||||
|
input,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
|
||||||
|
ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_backend(
|
||||||
|
format: HistoryFileFormat,
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
) -> Result<Box<dyn History>, ShellError> {
|
||||||
|
let path = match path {
|
||||||
|
Some(path) => path,
|
||||||
|
None => {
|
||||||
|
let Some(mut path) = nu_path::nu_config_dir() else {
|
||||||
|
return Err(ShellError::ConfigDirNotFound { span: None });
|
||||||
|
};
|
||||||
|
path.push(format.default_file_name());
|
||||||
|
path.into_std_path_buf()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn map(
|
||||||
|
result: Result<impl History + 'static, ReedlineError>,
|
||||||
|
) -> Result<Box<dyn History>, ShellError> {
|
||||||
|
result
|
||||||
|
.map(|x| Box::new(x) as Box<dyn History>)
|
||||||
|
.map_err(error_from_reedline)
|
||||||
|
}
|
||||||
|
match format {
|
||||||
|
// Use a reasonably large value for maximum capacity.
|
||||||
|
HistoryFileFormat::Plaintext => map(FileBackedHistory::with_file(0xfffffff, path)),
|
||||||
|
HistoryFileFormat::Sqlite => map(SqliteBackedHistory::with_file(path, None, None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import(
|
||||||
|
dst: &mut dyn History,
|
||||||
|
src: impl Iterator<Item = Result<HistoryItem, ShellError>>,
|
||||||
|
) -> Result<(), ShellError> {
|
||||||
|
for item in src {
|
||||||
|
let mut item = item?;
|
||||||
|
item.id = None;
|
||||||
|
dst.save(item).map_err(error_from_reedline)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_from_reedline(e: ReedlineError) -> ShellError {
|
||||||
|
// TODO: Should we add a new ShellError variant?
|
||||||
|
ShellError::GenericError {
|
||||||
|
error: "Reedline error".to_owned(),
|
||||||
|
msg: format!("{e}"),
|
||||||
|
span: None,
|
||||||
|
help: None,
|
||||||
|
inner: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn item_from_value(v: Value) -> Result<HistoryItem, ShellError> {
|
||||||
|
let span = v.span();
|
||||||
|
match v {
|
||||||
|
Value::Record { val, .. } => item_from_record(val.into_owned(), span),
|
||||||
|
Value::String { val, .. } => Ok(HistoryItem {
|
||||||
|
command_line: val,
|
||||||
|
id: None,
|
||||||
|
start_timestamp: None,
|
||||||
|
session_id: None,
|
||||||
|
hostname: None,
|
||||||
|
cwd: None,
|
||||||
|
duration: None,
|
||||||
|
exit_status: None,
|
||||||
|
more_info: None,
|
||||||
|
}),
|
||||||
|
_ => Err(ShellError::UnsupportedInput {
|
||||||
|
msg: "Only list and record inputs are supported".to_owned(),
|
||||||
|
input: v.get_type().to_string(),
|
||||||
|
msg_span: span,
|
||||||
|
input_span: span,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn item_from_record(mut rec: Record, span: Span) -> Result<HistoryItem, ShellError> {
|
||||||
|
let cmd = match rec.remove(fields::COMMAND_LINE) {
|
||||||
|
Some(v) => v.as_str()?.to_owned(),
|
||||||
|
None => {
|
||||||
|
return Err(ShellError::TypeMismatch {
|
||||||
|
err_message: format!("missing column: {}", fields::COMMAND_LINE),
|
||||||
|
span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn get<T>(
|
||||||
|
rec: &mut Record,
|
||||||
|
field: &'static str,
|
||||||
|
f: impl FnOnce(Value) -> Result<T, ShellError>,
|
||||||
|
) -> Result<Option<T>, ShellError> {
|
||||||
|
rec.remove(field).map(f).transpose()
|
||||||
|
}
|
||||||
|
|
||||||
|
let rec = &mut rec;
|
||||||
|
let item = HistoryItem {
|
||||||
|
command_line: cmd,
|
||||||
|
id: None,
|
||||||
|
start_timestamp: get(rec, fields::START_TIMESTAMP, |v| Ok(v.as_date()?.to_utc()))?,
|
||||||
|
hostname: get(rec, fields::HOSTNAME, |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())?,
|
||||||
|
duration: get(rec, fields::DURATION, duration_from_value)?,
|
||||||
|
more_info: None,
|
||||||
|
// TODO: Currently reedline doesn't let you create session IDs.
|
||||||
|
session_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !rec.is_empty() {
|
||||||
|
let cols = rec.columns().map(|s| s.as_str()).collect::<Vec<_>>();
|
||||||
|
return Err(ShellError::TypeMismatch {
|
||||||
|
err_message: format!("unsupported column names: {}", cols.join(", ")),
|
||||||
|
span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn duration_from_value(v: Value) -> Result<std::time::Duration, ShellError> {
|
||||||
|
chrono::Duration::nanoseconds(v.as_duration()?)
|
||||||
|
.to_std()
|
||||||
|
.map_err(|_| ShellError::IOError {
|
||||||
|
msg: "negative duration not supported".to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_backup_path(path: &Path) -> Result<PathBuf, ShellError> {
|
||||||
|
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.
|
||||||
|
return Err(ShellError::IOError {
|
||||||
|
msg: "History path mush be representable as UTF-8".to_string(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
bak_path.push_str(".bak");
|
||||||
|
if !Path::new(&bak_path).exists() {
|
||||||
|
return Ok(bak_path.into());
|
||||||
|
}
|
||||||
|
let base_len = bak_path.len();
|
||||||
|
for i in 1..100 {
|
||||||
|
use std::fmt::Write;
|
||||||
|
bak_path.truncate(base_len);
|
||||||
|
write!(&mut bak_path, ".{i}").unwrap();
|
||||||
|
if !Path::new(&bak_path).exists() {
|
||||||
|
return Ok(PathBuf::from(bak_path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(ShellError::IOError {
|
||||||
|
msg: "Too many existing backup files".to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn backup(path: &Path) -> Result<Option<PathBuf>, ShellError> {
|
||||||
|
match path.metadata() {
|
||||||
|
Ok(md) if md.is_file() => (),
|
||||||
|
Ok(_) => {
|
||||||
|
return Err(ShellError::IOError {
|
||||||
|
msg: "history path exists but is not a file".to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
|
||||||
|
Err(e) => return Err(e.into()),
|
||||||
|
}
|
||||||
|
let bak_path = find_backup_path(path)?;
|
||||||
|
std::fs::copy(path, &bak_path)?;
|
||||||
|
Ok(Some(bak_path))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use chrono::DateTime;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_item_from_value_string() -> Result<(), ShellError> {
|
||||||
|
let item = item_from_value(Value::string("foo", Span::unknown()))?;
|
||||||
|
assert_eq!(
|
||||||
|
item,
|
||||||
|
HistoryItem {
|
||||||
|
command_line: "foo".to_string(),
|
||||||
|
id: None,
|
||||||
|
start_timestamp: None,
|
||||||
|
session_id: None,
|
||||||
|
hostname: None,
|
||||||
|
cwd: None,
|
||||||
|
duration: None,
|
||||||
|
exit_status: None,
|
||||||
|
more_info: None
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_item_from_value_record() {
|
||||||
|
let span = Span::unknown();
|
||||||
|
let rec = new_record(&[
|
||||||
|
("command", Value::string("foo", span)),
|
||||||
|
(
|
||||||
|
"start_timestamp",
|
||||||
|
Value::date(
|
||||||
|
DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00").unwrap(),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("hostname", Value::string("localhost", span)),
|
||||||
|
("cwd", Value::string("/home/test", span)),
|
||||||
|
("duration", Value::duration(100_000_000, span)),
|
||||||
|
("exit_status", Value::int(42, span)),
|
||||||
|
]);
|
||||||
|
let item = item_from_value(rec).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
item,
|
||||||
|
HistoryItem {
|
||||||
|
command_line: "foo".to_string(),
|
||||||
|
id: None,
|
||||||
|
start_timestamp: Some(
|
||||||
|
DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00")
|
||||||
|
.unwrap()
|
||||||
|
.to_utc()
|
||||||
|
),
|
||||||
|
hostname: Some("localhost".to_string()),
|
||||||
|
cwd: Some("/home/test".to_string()),
|
||||||
|
duration: Some(std::time::Duration::from_nanos(100_000_000)),
|
||||||
|
exit_status: Some(42),
|
||||||
|
|
||||||
|
session_id: None,
|
||||||
|
more_info: None
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_item_from_value_record_extra_field() {
|
||||||
|
let span = Span::unknown();
|
||||||
|
let rec = new_record(&[
|
||||||
|
("command_line", Value::string("foo", span)),
|
||||||
|
("id_nonexistent", Value::int(1, span)),
|
||||||
|
]);
|
||||||
|
assert!(item_from_value(rec).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_item_from_value_record_bad_type() {
|
||||||
|
let span = Span::unknown();
|
||||||
|
let rec = new_record(&[
|
||||||
|
("command_line", Value::string("foo", span)),
|
||||||
|
("id", Value::string("one".to_string(), span)),
|
||||||
|
]);
|
||||||
|
assert!(item_from_value(rec).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_record(rec: &[(&'static str, Value)]) -> Value {
|
||||||
|
let span = Span::unknown();
|
||||||
|
let rec = Record::from_raw_cols_vals(
|
||||||
|
rec.iter().map(|(col, _)| col.to_string()).collect(),
|
||||||
|
rec.iter().map(|(_, val)| val.clone()).collect(),
|
||||||
|
span,
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
Value::record(rec, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case::no_backup(&["history.dat"], "history.dat.bak")]
|
||||||
|
#[case::backup_exists(&["history.dat", "history.dat.bak"], "history.dat.bak.1")]
|
||||||
|
#[case::multiple_backups_exists( &["history.dat", "history.dat.bak", "history.dat.bak.1"], "history.dat.bak.2")]
|
||||||
|
fn test_find_backup_path(#[case] existing: &[&str], #[case] want: &str) {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
for name in existing {
|
||||||
|
std::fs::File::create_new(dir.path().join(name)).unwrap();
|
||||||
|
}
|
||||||
|
let got = find_backup_path(&dir.path().join("history.dat")).unwrap();
|
||||||
|
assert_eq!(got, dir.path().join(want))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_backup() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let mut history = std::fs::File::create_new(dir.path().join("history.dat")).unwrap();
|
||||||
|
use std::io::Write;
|
||||||
|
write!(&mut history, "123").unwrap();
|
||||||
|
let want_bak_path = dir.path().join("history.dat.bak");
|
||||||
|
assert_eq!(
|
||||||
|
backup(&dir.path().join("history.dat")),
|
||||||
|
Ok(Some(want_bak_path.clone()))
|
||||||
|
);
|
||||||
|
let got_data = String::from_utf8(std::fs::read(want_bak_path).unwrap()).unwrap();
|
||||||
|
assert_eq!(got_data, "123");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_backup_no_file() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let bak_path = backup(&dir.path().join("history.dat")).unwrap();
|
||||||
|
assert!(bak_path.is_none());
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@ impl Command for HistorySession {
|
|||||||
"history session"
|
"history session"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Get the command history session."
|
"Get the command history session."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
mod fields;
|
||||||
mod history_;
|
mod history_;
|
||||||
|
mod history_import;
|
||||||
mod history_session;
|
mod history_session;
|
||||||
|
|
||||||
pub use history_::History;
|
pub use history_::History;
|
||||||
|
pub use history_import::HistoryImport;
|
||||||
pub use history_session::HistorySession;
|
pub use history_session::HistorySession;
|
||||||
|
@ -14,11 +14,11 @@ impl Command for Keybindings {
|
|||||||
.input_output_types(vec![(Type::Nothing, Type::String)])
|
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Keybindings related commands."
|
"Keybindings related commands."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_description(&self) -> &str {
|
||||||
r#"You must use one of the following subcommands. Using this command as-is will only produce this help message.
|
r#"You must use one of the following subcommands. Using this command as-is will only produce this help message.
|
||||||
|
|
||||||
For more information on input and keybindings, check:
|
For more information on input and keybindings, check:
|
||||||
|
@ -15,7 +15,7 @@ impl Command for KeybindingsDefault {
|
|||||||
.input_output_types(vec![(Type::Nothing, Type::table())])
|
.input_output_types(vec![(Type::Nothing, Type::table())])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"List default keybindings."
|
"List default keybindings."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ impl Command for KeybindingsList {
|
|||||||
.category(Category::Platform)
|
.category(Category::Platform)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"List available options that can be used to create keybindings."
|
"List available options that can be used to create keybindings."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,11 +12,11 @@ impl Command for KeybindingsListen {
|
|||||||
"keybindings listen"
|
"keybindings listen"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Get input from the user."
|
"Get input from the user."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_description(&self) -> &str {
|
||||||
"This is an internal debugging tool. For better output, try `input listen --types [key]`"
|
"This is an internal debugging tool. For better output, try `input listen --types [key]`"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ mod keybindings_list;
|
|||||||
mod keybindings_listen;
|
mod keybindings_listen;
|
||||||
|
|
||||||
pub use commandline::{Commandline, CommandlineEdit, CommandlineGetCursor, CommandlineSetCursor};
|
pub use commandline::{Commandline, CommandlineEdit, CommandlineGetCursor, CommandlineSetCursor};
|
||||||
pub use history::{History, HistorySession};
|
pub use history::{History, HistoryImport, HistorySession};
|
||||||
pub use keybindings::Keybindings;
|
pub use keybindings::Keybindings;
|
||||||
pub use keybindings_default::KeybindingsDefault;
|
pub use keybindings_default::KeybindingsDefault;
|
||||||
pub use keybindings_list::KeybindingsList;
|
pub use keybindings_list::KeybindingsList;
|
||||||
|
@ -12,7 +12,7 @@ pub trait Completer {
|
|||||||
&mut self,
|
&mut self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
prefix: Vec<u8>,
|
prefix: &[u8],
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
completions::{Completer, CompletionOptions, MatchAlgorithm, SortBy},
|
completions::{Completer, CompletionOptions, MatchAlgorithm},
|
||||||
SuggestionKind,
|
SuggestionKind,
|
||||||
};
|
};
|
||||||
use nu_parser::FlatShape;
|
use nu_parser::FlatShape;
|
||||||
@ -51,7 +51,9 @@ impl CommandCompletion {
|
|||||||
if working_set
|
if working_set
|
||||||
.permanent_state
|
.permanent_state
|
||||||
.config
|
.config
|
||||||
.max_external_completion_results
|
.completions
|
||||||
|
.external
|
||||||
|
.max_results
|
||||||
> executables.len() as i64
|
> executables.len() as i64
|
||||||
&& !executables.contains(
|
&& !executables.contains(
|
||||||
&item
|
&item
|
||||||
@ -99,10 +101,9 @@ impl CommandCompletion {
|
|||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: String::from_utf8_lossy(&x.0).to_string(),
|
value: String::from_utf8_lossy(&x.0).to_string(),
|
||||||
description: x.1,
|
description: x.1,
|
||||||
style: None,
|
|
||||||
extra: None,
|
|
||||||
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()
|
||||||
},
|
},
|
||||||
kind: Some(SuggestionKind::Command(x.2)),
|
kind: Some(SuggestionKind::Command(x.2)),
|
||||||
})
|
})
|
||||||
@ -118,11 +119,9 @@ impl CommandCompletion {
|
|||||||
.map(move |x| SemanticSuggestion {
|
.map(move |x| SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: x,
|
value: x,
|
||||||
description: None,
|
|
||||||
style: None,
|
|
||||||
extra: None,
|
|
||||||
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()
|
||||||
},
|
},
|
||||||
// TODO: is there a way to create a test?
|
// TODO: is there a way to create a test?
|
||||||
kind: None,
|
kind: None,
|
||||||
@ -136,11 +135,9 @@ impl CommandCompletion {
|
|||||||
results.push(SemanticSuggestion {
|
results.push(SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: format!("^{}", external.suggestion.value),
|
value: format!("^{}", external.suggestion.value),
|
||||||
description: None,
|
|
||||||
style: None,
|
|
||||||
extra: None,
|
|
||||||
span: external.suggestion.span,
|
span: external.suggestion.span,
|
||||||
append_whitespace: true,
|
append_whitespace: true,
|
||||||
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
kind: external.kind,
|
kind: external.kind,
|
||||||
})
|
})
|
||||||
@ -161,7 +158,7 @@ impl Completer for CommandCompletion {
|
|||||||
&mut self,
|
&mut self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
_stack: &Stack,
|
_stack: &Stack,
|
||||||
prefix: Vec<u8>,
|
prefix: &[u8],
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
@ -198,11 +195,7 @@ impl Completer for CommandCompletion {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if !subcommands.is_empty() {
|
if !subcommands.is_empty() {
|
||||||
return sort_suggestions(
|
return sort_suggestions(&String::from_utf8_lossy(prefix), subcommands, options);
|
||||||
&String::from_utf8_lossy(&prefix),
|
|
||||||
subcommands,
|
|
||||||
SortBy::LevenshteinDistance,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = working_set.get_config();
|
let config = working_set.get_config();
|
||||||
@ -220,18 +213,14 @@ impl Completer for CommandCompletion {
|
|||||||
working_set,
|
working_set,
|
||||||
span,
|
span,
|
||||||
offset,
|
offset,
|
||||||
config.enable_external_completion,
|
config.completions.external.enable,
|
||||||
options.match_algorithm,
|
options.match_algorithm,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
};
|
};
|
||||||
|
|
||||||
sort_suggestions(
|
sort_suggestions(&String::from_utf8_lossy(prefix), commands, options)
|
||||||
&String::from_utf8_lossy(&prefix),
|
|
||||||
commands,
|
|
||||||
SortBy::LevenshteinDistance,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::completions::{
|
use crate::completions::{
|
||||||
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
||||||
DotNuCompletion, FileCompletion, FlagCompletion, VariableCompletion,
|
DotNuCompletion, FileCompletion, FlagCompletion, OperatorCompletion, VariableCompletion,
|
||||||
};
|
};
|
||||||
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
|
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
|
||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
@ -25,7 +25,7 @@ impl NuCompleter {
|
|||||||
pub fn new(engine_state: Arc<EngineState>, stack: Arc<Stack>) -> Self {
|
pub fn new(engine_state: Arc<EngineState>, stack: Arc<Stack>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
engine_state,
|
engine_state,
|
||||||
stack: Stack::with_parent(stack).reset_out_dest().capture(),
|
stack: Stack::with_parent(stack).reset_out_dest().collect_value(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ impl NuCompleter {
|
|||||||
&self,
|
&self,
|
||||||
completer: &mut T,
|
completer: &mut T,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
prefix: Vec<u8>,
|
prefix: &[u8],
|
||||||
new_span: Span,
|
new_span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
@ -46,15 +46,16 @@ impl NuCompleter {
|
|||||||
let config = self.engine_state.get_config();
|
let config = self.engine_state.get_config();
|
||||||
|
|
||||||
let options = CompletionOptions {
|
let options = CompletionOptions {
|
||||||
case_sensitive: config.case_sensitive_completions,
|
case_sensitive: config.completions.case_sensitive,
|
||||||
match_algorithm: config.completion_algorithm.into(),
|
match_algorithm: config.completions.algorithm.into(),
|
||||||
|
sort: config.completions.sort,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
completer.fetch(
|
completer.fetch(
|
||||||
working_set,
|
working_set,
|
||||||
&self.stack,
|
&self.stack,
|
||||||
prefix.clone(),
|
prefix,
|
||||||
new_span,
|
new_span,
|
||||||
offset,
|
offset,
|
||||||
pos,
|
pos,
|
||||||
@ -169,23 +170,38 @@ impl NuCompleter {
|
|||||||
let new_span = Span::new(flat.0.start, flat.0.end - 1);
|
let new_span = Span::new(flat.0.start, flat.0.end - 1);
|
||||||
|
|
||||||
// Parses the prefix. Completion should look up to the cursor position, not after.
|
// Parses the prefix. Completion should look up to the cursor position, not after.
|
||||||
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
let mut prefix = working_set.get_span_contents(flat.0);
|
||||||
let index = pos - flat.0.start;
|
let index = pos - flat.0.start;
|
||||||
prefix.drain(index..);
|
prefix = &prefix[..index];
|
||||||
|
|
||||||
// Variables completion
|
// Variables completion
|
||||||
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
||||||
let mut completer =
|
let mut variable_names_completer =
|
||||||
VariableCompletion::new(most_left_var.unwrap_or((vec![], vec![])));
|
VariableCompletion::new(most_left_var.unwrap_or((vec![], vec![])));
|
||||||
|
|
||||||
return self.process_completion(
|
let mut variable_completions = self.process_completion(
|
||||||
&mut completer,
|
&mut variable_names_completer,
|
||||||
&working_set,
|
&working_set,
|
||||||
prefix,
|
prefix,
|
||||||
new_span,
|
new_span,
|
||||||
fake_offset,
|
fake_offset,
|
||||||
pos,
|
pos,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut variable_operations_completer =
|
||||||
|
OperatorCompletion::new(pipeline_element.expr.clone());
|
||||||
|
|
||||||
|
let mut variable_operations_completions = self.process_completion(
|
||||||
|
&mut variable_operations_completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
fake_offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
|
||||||
|
variable_completions.append(&mut variable_operations_completions);
|
||||||
|
return variable_completions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flags completion
|
// Flags completion
|
||||||
@ -195,7 +211,7 @@ impl NuCompleter {
|
|||||||
let result = self.process_completion(
|
let result = self.process_completion(
|
||||||
&mut completer,
|
&mut completer,
|
||||||
&working_set,
|
&working_set,
|
||||||
prefix.clone(),
|
prefix,
|
||||||
new_span,
|
new_span,
|
||||||
fake_offset,
|
fake_offset,
|
||||||
pos,
|
pos,
|
||||||
@ -207,7 +223,7 @@ impl NuCompleter {
|
|||||||
|
|
||||||
// We got no results for internal completion
|
// We got no results for internal completion
|
||||||
// now we can check if external completer is set and use it
|
// now we can check if external completer is set and use it
|
||||||
if let Some(closure) = config.external_completer.as_ref() {
|
if let Some(closure) = config.completions.external.completer.as_ref() {
|
||||||
if let Some(external_result) =
|
if let Some(external_result) =
|
||||||
self.external_completion(closure, &spans, fake_offset, new_span)
|
self.external_completion(closure, &spans, fake_offset, new_span)
|
||||||
{
|
{
|
||||||
@ -261,6 +277,26 @@ impl NuCompleter {
|
|||||||
} else if prev_expr_str == b"ls" {
|
} else if prev_expr_str == b"ls" {
|
||||||
let mut completer = FileCompletion::new();
|
let mut completer = FileCompletion::new();
|
||||||
|
|
||||||
|
return self.process_completion(
|
||||||
|
&mut completer,
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
new_span,
|
||||||
|
fake_offset,
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
} else if matches!(
|
||||||
|
previous_expr.1,
|
||||||
|
FlatShape::Float
|
||||||
|
| FlatShape::Int
|
||||||
|
| FlatShape::String
|
||||||
|
| FlatShape::List
|
||||||
|
| FlatShape::Bool
|
||||||
|
| FlatShape::Variable(_)
|
||||||
|
) {
|
||||||
|
let mut completer =
|
||||||
|
OperatorCompletion::new(pipeline_element.expr.clone());
|
||||||
|
|
||||||
return self.process_completion(
|
return self.process_completion(
|
||||||
&mut completer,
|
&mut completer,
|
||||||
&working_set,
|
&working_set,
|
||||||
@ -326,7 +362,7 @@ impl NuCompleter {
|
|||||||
let mut out: Vec<_> = self.process_completion(
|
let mut out: Vec<_> = self.process_completion(
|
||||||
&mut completer,
|
&mut completer,
|
||||||
&working_set,
|
&working_set,
|
||||||
prefix.clone(),
|
prefix,
|
||||||
new_span,
|
new_span,
|
||||||
fake_offset,
|
fake_offset,
|
||||||
pos,
|
pos,
|
||||||
@ -337,7 +373,9 @@ impl NuCompleter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to complete using an external completer (if set)
|
// Try to complete using an external completer (if set)
|
||||||
if let Some(closure) = config.external_completer.as_ref() {
|
if let Some(closure) =
|
||||||
|
config.completions.external.completer.as_ref()
|
||||||
|
{
|
||||||
if let Some(external_result) = self.external_completion(
|
if let Some(external_result) = self.external_completion(
|
||||||
closure,
|
closure,
|
||||||
&spans,
|
&spans,
|
||||||
@ -443,14 +481,11 @@ pub fn map_value_completions<'a>(
|
|||||||
return Some(SemanticSuggestion {
|
return Some(SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: s,
|
value: s,
|
||||||
description: None,
|
|
||||||
style: None,
|
|
||||||
extra: None,
|
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: span.start - offset,
|
start: span.start - offset,
|
||||||
end: span.end - offset,
|
end: span.end - offset,
|
||||||
},
|
},
|
||||||
append_whitespace: false,
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
kind: Some(SuggestionKind::Type(x.get_type())),
|
kind: Some(SuggestionKind::Type(x.get_type())),
|
||||||
});
|
});
|
||||||
@ -460,14 +495,11 @@ pub fn map_value_completions<'a>(
|
|||||||
if let Ok(record) = x.as_record() {
|
if let Ok(record) = x.as_record() {
|
||||||
let mut suggestion = Suggestion {
|
let mut suggestion = Suggestion {
|
||||||
value: String::from(""), // Initialize with empty string
|
value: String::from(""), // Initialize with empty string
|
||||||
description: None,
|
|
||||||
style: None,
|
|
||||||
extra: None,
|
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: span.start - offset,
|
start: span.start - offset,
|
||||||
end: span.end - offset,
|
end: span.end - offset,
|
||||||
},
|
},
|
||||||
append_whitespace: false,
|
..Suggestion::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Iterate the cols looking for `value` and `description`
|
// Iterate the cols looking for `value` and `description`
|
||||||
@ -536,6 +568,11 @@ mod completer_tests {
|
|||||||
|
|
||||||
let mut completer = NuCompleter::new(engine_state.into(), Arc::new(Stack::new()));
|
let mut completer = NuCompleter::new(engine_state.into(), Arc::new(Stack::new()));
|
||||||
let dataset = [
|
let dataset = [
|
||||||
|
("1 bit-sh", true, "b", vec!["bit-shl", "bit-shr"]),
|
||||||
|
("1.0 bit-sh", false, "b", vec![]),
|
||||||
|
("1 m", true, "m", vec!["mod"]),
|
||||||
|
("1.0 m", true, "m", vec!["mod"]),
|
||||||
|
("\"a\" s", true, "s", vec!["starts-with"]),
|
||||||
("sudo", false, "", Vec::new()),
|
("sudo", false, "", Vec::new()),
|
||||||
("sudo l", true, "l", vec!["ls", "let", "lines", "loop"]),
|
("sudo l", true, "l", vec!["ls", "let", "lines", "loop"]),
|
||||||
(" sudo", false, "", Vec::new()),
|
(" sudo", false, "", Vec::new()),
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
|
use super::MatchAlgorithm;
|
||||||
use crate::{
|
use crate::{
|
||||||
completions::{matches, CompletionOptions},
|
completions::{matches, CompletionOptions},
|
||||||
SemanticSuggestion,
|
SemanticSuggestion,
|
||||||
};
|
};
|
||||||
|
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
||||||
use nu_ansi_term::Style;
|
use nu_ansi_term::Style;
|
||||||
use nu_engine::env_to_string;
|
use nu_engine::env_to_string;
|
||||||
|
use nu_path::dots::expand_ndots;
|
||||||
use nu_path::{expand_to_real_path, home_dir};
|
use nu_path::{expand_to_real_path, home_dir};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
levenshtein_distance, Span,
|
CompletionSort, Span,
|
||||||
};
|
};
|
||||||
use nu_utils::get_ls_colors;
|
use nu_utils::get_ls_colors;
|
||||||
use std::path::{
|
use std::path::{is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP};
|
||||||
is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::SortBy;
|
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct PathBuiltFromString {
|
pub struct PathBuiltFromString {
|
||||||
@ -22,22 +21,31 @@ pub struct PathBuiltFromString {
|
|||||||
isdir: bool,
|
isdir: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete_rec(
|
/// Recursively goes through paths that match a given `partial`.
|
||||||
|
/// built: State struct for a valid matching path built so far.
|
||||||
|
///
|
||||||
|
/// `isdir`: whether the current partial path has a trailing slash.
|
||||||
|
/// Parsing a path string into a pathbuf loses that bit of information.
|
||||||
|
///
|
||||||
|
/// want_directory: Whether we want only directories as completion matches.
|
||||||
|
/// Some commands like `cd` can only be run on directories whereas others
|
||||||
|
/// like `ls` can be run on regular files as well.
|
||||||
|
pub fn complete_rec(
|
||||||
partial: &[&str],
|
partial: &[&str],
|
||||||
built: &PathBuiltFromString,
|
built: &PathBuiltFromString,
|
||||||
cwd: &Path,
|
cwd: &Path,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
dir: bool,
|
want_directory: bool,
|
||||||
isdir: bool,
|
isdir: bool,
|
||||||
) -> Vec<PathBuiltFromString> {
|
) -> Vec<PathBuiltFromString> {
|
||||||
let mut completions = vec![];
|
let mut completions = vec![];
|
||||||
|
|
||||||
if let Some((&base, rest)) = partial.split_first() {
|
if let Some((&base, rest)) = partial.split_first() {
|
||||||
if (base == "." || base == "..") && (isdir || !rest.is_empty()) {
|
if base.chars().all(|c| c == '.') && (isdir || !rest.is_empty()) {
|
||||||
let mut built = built.clone();
|
let mut built = built.clone();
|
||||||
built.parts.push(base.to_string());
|
built.parts.push(base.to_string());
|
||||||
built.isdir = true;
|
built.isdir = true;
|
||||||
return complete_rec(rest, &built, cwd, options, dir, isdir);
|
return complete_rec(rest, &built, cwd, options, want_directory, isdir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,24 +66,41 @@ fn complete_rec(
|
|||||||
built.parts.push(entry_name.clone());
|
built.parts.push(entry_name.clone());
|
||||||
built.isdir = entry_isdir;
|
built.isdir = entry_isdir;
|
||||||
|
|
||||||
if !dir || entry_isdir {
|
if !want_directory || entry_isdir {
|
||||||
entries.push((entry_name, built));
|
entries.push((entry_name, built));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let prefix = partial.first().unwrap_or(&"");
|
let prefix = partial.first().unwrap_or(&"");
|
||||||
let sorted_entries = sort_completions(prefix, entries, SortBy::Ascending, |(entry, _)| entry);
|
let sorted_entries = sort_completions(prefix, entries, options, |(entry, _)| entry);
|
||||||
|
|
||||||
for (entry_name, built) in sorted_entries {
|
for (entry_name, built) in sorted_entries {
|
||||||
match partial.split_first() {
|
match partial.split_first() {
|
||||||
Some((base, rest)) => {
|
Some((base, rest)) => {
|
||||||
if matches(base, &entry_name, options) {
|
if matches(base, &entry_name, options) {
|
||||||
|
// We use `isdir` to confirm that the current component has
|
||||||
|
// at least one next component or a slash.
|
||||||
|
// Serves as confirmation to ignore longer completions for
|
||||||
|
// components in between.
|
||||||
if !rest.is_empty() || isdir {
|
if !rest.is_empty() || isdir {
|
||||||
completions.extend(complete_rec(rest, &built, cwd, options, dir, isdir));
|
completions.extend(complete_rec(
|
||||||
|
rest,
|
||||||
|
&built,
|
||||||
|
cwd,
|
||||||
|
options,
|
||||||
|
want_directory,
|
||||||
|
isdir,
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
completions.push(built);
|
completions.push(built);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if entry_name.eq(base)
|
||||||
|
&& matches!(options.match_algorithm, MatchAlgorithm::Prefix)
|
||||||
|
&& isdir
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
completions.push(built);
|
completions.push(built);
|
||||||
@ -93,16 +118,16 @@ enum OriginalCwd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OriginalCwd {
|
impl OriginalCwd {
|
||||||
fn apply(&self, mut p: PathBuiltFromString) -> String {
|
fn apply(&self, mut p: PathBuiltFromString, path_separator: char) -> String {
|
||||||
match self {
|
match self {
|
||||||
Self::None => {}
|
Self::None => {}
|
||||||
Self::Home => p.parts.insert(0, "~".to_string()),
|
Self::Home => p.parts.insert(0, "~".to_string()),
|
||||||
Self::Prefix(s) => p.parts.insert(0, s.clone()),
|
Self::Prefix(s) => p.parts.insert(0, s.clone()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut ret = p.parts.join(MAIN_SEPARATOR_STR);
|
let mut ret = p.parts.join(&path_separator.to_string());
|
||||||
if p.isdir {
|
if p.isdir {
|
||||||
ret.push(SEP);
|
ret.push(path_separator);
|
||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
@ -131,14 +156,31 @@ pub fn complete_item(
|
|||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
) -> Vec<(nu_protocol::Span, String, Option<Style>)> {
|
) -> Vec<(nu_protocol::Span, String, Option<Style>)> {
|
||||||
let partial = surround_remove(partial);
|
let cleaned_partial = surround_remove(partial);
|
||||||
let isdir = partial.ends_with(is_separator);
|
let isdir = cleaned_partial.ends_with(is_separator);
|
||||||
|
let expanded_partial = expand_ndots(Path::new(&cleaned_partial));
|
||||||
|
let should_collapse_dots = expanded_partial != Path::new(&cleaned_partial);
|
||||||
|
let mut partial = expanded_partial.to_string_lossy().to_string();
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
let path_separator = SEP;
|
||||||
|
#[cfg(windows)]
|
||||||
|
let path_separator = cleaned_partial
|
||||||
|
.chars()
|
||||||
|
.rfind(|c: &char| is_separator(*c))
|
||||||
|
.unwrap_or(SEP);
|
||||||
|
|
||||||
|
// Handle the trailing dot case
|
||||||
|
if cleaned_partial.ends_with(&format!("{path_separator}.")) {
|
||||||
|
partial.push_str(&format!("{path_separator}."));
|
||||||
|
}
|
||||||
|
|
||||||
let cwd_pathbuf = Path::new(cwd).to_path_buf();
|
let cwd_pathbuf = Path::new(cwd).to_path_buf();
|
||||||
let ls_colors = (engine_state.config.use_ls_colors_completions
|
let ls_colors = (engine_state.config.completions.use_ls_colors
|
||||||
&& engine_state.config.use_ansi_coloring)
|
&& engine_state.config.use_ansi_coloring)
|
||||||
.then(|| {
|
.then(|| {
|
||||||
let ls_colors_env_str = match stack.get_env_var(engine_state, "LS_COLORS") {
|
let ls_colors_env_str = match stack.get_env_var(engine_state, "LS_COLORS") {
|
||||||
Some(v) => env_to_string("LS_COLORS", &v, engine_state, stack).ok(),
|
Some(v) => env_to_string("LS_COLORS", v, engine_state, stack).ok(),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
get_ls_colors(ls_colors_env_str)
|
get_ls_colors(ls_colors_env_str)
|
||||||
@ -152,16 +194,11 @@ pub fn complete_item(
|
|||||||
match components.peek().cloned() {
|
match components.peek().cloned() {
|
||||||
Some(c @ Component::Prefix(..)) => {
|
Some(c @ Component::Prefix(..)) => {
|
||||||
// windows only by definition
|
// windows only by definition
|
||||||
components.next();
|
|
||||||
if let Some(Component::RootDir) = components.peek().cloned() {
|
|
||||||
components.next();
|
|
||||||
};
|
|
||||||
cwd = [c, Component::RootDir].iter().collect();
|
cwd = [c, Component::RootDir].iter().collect();
|
||||||
prefix_len = c.as_os_str().len();
|
prefix_len = c.as_os_str().len();
|
||||||
original_cwd = OriginalCwd::Prefix(c.as_os_str().to_string_lossy().into_owned());
|
original_cwd = OriginalCwd::Prefix(c.as_os_str().to_string_lossy().into_owned());
|
||||||
}
|
}
|
||||||
Some(c @ Component::RootDir) => {
|
Some(c @ Component::RootDir) => {
|
||||||
components.next();
|
|
||||||
// This is kind of a hack. When joining an empty string with the rest,
|
// This is kind of a hack. When joining an empty string with the rest,
|
||||||
// we add the slash automagically
|
// we add the slash automagically
|
||||||
cwd = PathBuf::from(c.as_os_str());
|
cwd = PathBuf::from(c.as_os_str());
|
||||||
@ -169,8 +206,7 @@ pub fn complete_item(
|
|||||||
original_cwd = OriginalCwd::Prefix(String::new());
|
original_cwd = OriginalCwd::Prefix(String::new());
|
||||||
}
|
}
|
||||||
Some(Component::Normal(home)) if home.to_string_lossy() == "~" => {
|
Some(Component::Normal(home)) if home.to_string_lossy() == "~" => {
|
||||||
components.next();
|
cwd = home_dir().map(Into::into).unwrap_or(cwd_pathbuf);
|
||||||
cwd = home_dir().unwrap_or(cwd_pathbuf);
|
|
||||||
prefix_len = 1;
|
prefix_len = 1;
|
||||||
original_cwd = OriginalCwd::Home;
|
original_cwd = OriginalCwd::Home;
|
||||||
}
|
}
|
||||||
@ -194,8 +230,11 @@ pub fn complete_item(
|
|||||||
isdir,
|
isdir,
|
||||||
)
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|p| {
|
.map(|mut p| {
|
||||||
let path = original_cwd.apply(p);
|
if should_collapse_dots {
|
||||||
|
p = collapse_ndots(p);
|
||||||
|
}
|
||||||
|
let path = original_cwd.apply(p, path_separator);
|
||||||
let style = ls_colors.as_ref().map(|lsc| {
|
let style = ls_colors.as_ref().map(|lsc| {
|
||||||
lsc.style_for_path_with_metadata(
|
lsc.style_for_path_with_metadata(
|
||||||
&path,
|
&path,
|
||||||
@ -228,8 +267,10 @@ pub fn escape_path(path: String, dir: bool) -> String {
|
|||||||
let filename_contaminated = !dir && path.contains(['\'', '"', ' ', '#', '(', ')']);
|
let filename_contaminated = !dir && path.contains(['\'', '"', ' ', '#', '(', ')']);
|
||||||
let dirname_contaminated = dir && path.contains(['\'', '"', ' ', '#']);
|
let dirname_contaminated = dir && path.contains(['\'', '"', ' ', '#']);
|
||||||
let maybe_flag = path.starts_with('-');
|
let maybe_flag = path.starts_with('-');
|
||||||
|
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_number {
|
if filename_contaminated || dirname_contaminated || maybe_flag || maybe_variable || maybe_number
|
||||||
|
{
|
||||||
format!("`{path}`")
|
format!("`{path}`")
|
||||||
} else {
|
} else {
|
||||||
path
|
path
|
||||||
@ -273,33 +314,71 @@ pub fn adjust_if_intermediate(
|
|||||||
pub fn sort_suggestions(
|
pub fn sort_suggestions(
|
||||||
prefix: &str,
|
prefix: &str,
|
||||||
items: Vec<SemanticSuggestion>,
|
items: Vec<SemanticSuggestion>,
|
||||||
sort_by: SortBy,
|
options: &CompletionOptions,
|
||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
sort_completions(prefix, items, sort_by, |it| &it.suggestion.value)
|
sort_completions(prefix, items, options, |it| &it.suggestion.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `prefix` - What the user's typed, for sorting by Levenshtein distance
|
/// * `prefix` - What the user's typed, for sorting by fuzzy matcher score
|
||||||
pub fn sort_completions<T>(
|
pub fn sort_completions<T>(
|
||||||
prefix: &str,
|
prefix: &str,
|
||||||
mut items: Vec<T>,
|
mut items: Vec<T>,
|
||||||
sort_by: SortBy,
|
options: &CompletionOptions,
|
||||||
get_value: fn(&T) -> &str,
|
get_value: fn(&T) -> &str,
|
||||||
) -> Vec<T> {
|
) -> Vec<T> {
|
||||||
// Sort items
|
// Sort items
|
||||||
match sort_by {
|
if options.sort == CompletionSort::Smart && options.match_algorithm == MatchAlgorithm::Fuzzy {
|
||||||
SortBy::LevenshteinDistance => {
|
let mut matcher = SkimMatcherV2::default();
|
||||||
items.sort_by(|a, b| {
|
if options.case_sensitive {
|
||||||
let a_distance = levenshtein_distance(prefix, get_value(a));
|
matcher = matcher.respect_case();
|
||||||
let b_distance = levenshtein_distance(prefix, get_value(b));
|
} else {
|
||||||
a_distance.cmp(&b_distance)
|
matcher = matcher.ignore_case();
|
||||||
});
|
};
|
||||||
}
|
items.sort_unstable_by(|a, b| {
|
||||||
SortBy::Ascending => {
|
let a_str = get_value(a);
|
||||||
items.sort_by(|a, b| get_value(a).cmp(get_value(b)));
|
let b_str = get_value(b);
|
||||||
}
|
let a_score = matcher.fuzzy_match(a_str, prefix).unwrap_or_default();
|
||||||
SortBy::None => {}
|
let b_score = matcher.fuzzy_match(b_str, prefix).unwrap_or_default();
|
||||||
};
|
b_score.cmp(&a_score).then(a_str.cmp(b_str))
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
items.sort_unstable_by(|a, b| get_value(a).cmp(get_value(b)));
|
||||||
|
}
|
||||||
|
|
||||||
items
|
items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Collapse multiple ".." components into n-dots.
|
||||||
|
///
|
||||||
|
/// It performs the reverse operation of `expand_ndots`, collapsing sequences of ".." into n-dots,
|
||||||
|
/// such as "..." and "....".
|
||||||
|
///
|
||||||
|
/// The resulting path will use platform-specific path separators, regardless of what path separators were used in the input.
|
||||||
|
fn collapse_ndots(path: PathBuiltFromString) -> PathBuiltFromString {
|
||||||
|
let mut result = PathBuiltFromString {
|
||||||
|
parts: Vec::with_capacity(path.parts.len()),
|
||||||
|
isdir: path.isdir,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut dot_count = 0;
|
||||||
|
|
||||||
|
for part in path.parts {
|
||||||
|
if part == ".." {
|
||||||
|
dot_count += 1;
|
||||||
|
} else {
|
||||||
|
if dot_count > 0 {
|
||||||
|
result.parts.push(".".repeat(dot_count + 1));
|
||||||
|
dot_count = 0;
|
||||||
|
}
|
||||||
|
result.parts.push(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any remaining dots
|
||||||
|
if dot_count > 0 {
|
||||||
|
result.parts.push(".".repeat(dot_count + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
@ -1,17 +1,10 @@
|
|||||||
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
||||||
use nu_parser::trim_quotes_str;
|
use nu_parser::trim_quotes_str;
|
||||||
use nu_protocol::CompletionAlgorithm;
|
use nu_protocol::{CompletionAlgorithm, CompletionSort};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub enum SortBy {
|
|
||||||
LevenshteinDistance,
|
|
||||||
Ascending,
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes how suggestions should be matched.
|
/// Describes how suggestions should be matched.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum MatchAlgorithm {
|
pub enum MatchAlgorithm {
|
||||||
/// Only show suggestions which begin with the given input
|
/// Only show suggestions which begin with the given input
|
||||||
///
|
///
|
||||||
@ -96,6 +89,7 @@ pub struct CompletionOptions {
|
|||||||
pub case_sensitive: bool,
|
pub case_sensitive: bool,
|
||||||
pub positional: bool,
|
pub positional: bool,
|
||||||
pub match_algorithm: MatchAlgorithm,
|
pub match_algorithm: MatchAlgorithm,
|
||||||
|
pub sort: CompletionSort,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CompletionOptions {
|
impl Default for CompletionOptions {
|
||||||
@ -104,6 +98,7 @@ impl Default for CompletionOptions {
|
|||||||
case_sensitive: true,
|
case_sensitive: true,
|
||||||
positional: true,
|
positional: true,
|
||||||
match_algorithm: MatchAlgorithm::Prefix,
|
match_algorithm: MatchAlgorithm::Prefix,
|
||||||
|
sort: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
use crate::completions::{
|
use crate::completions::{
|
||||||
completer::map_value_completions, Completer, CompletionOptions, MatchAlgorithm,
|
completer::map_value_completions, Completer, CompletionOptions, MatchAlgorithm,
|
||||||
SemanticSuggestion, SortBy,
|
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::{Stack, StateWorkingSet},
|
||||||
PipelineData, Span, Type, Value,
|
CompletionSort, DeclId, PipelineData, Span, Type, Value,
|
||||||
};
|
};
|
||||||
use nu_utils::IgnoreCaseExt;
|
use nu_utils::IgnoreCaseExt;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -16,18 +16,16 @@ use super::completion_common::sort_suggestions;
|
|||||||
|
|
||||||
pub struct CustomCompletion {
|
pub struct CustomCompletion {
|
||||||
stack: Stack,
|
stack: Stack,
|
||||||
decl_id: usize,
|
decl_id: DeclId,
|
||||||
line: String,
|
line: String,
|
||||||
sort_by: SortBy,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CustomCompletion {
|
impl CustomCompletion {
|
||||||
pub fn new(stack: Stack, decl_id: usize, line: String) -> Self {
|
pub fn new(stack: Stack, decl_id: DeclId, line: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
stack,
|
stack,
|
||||||
decl_id,
|
decl_id,
|
||||||
line,
|
line,
|
||||||
sort_by: SortBy::None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -37,7 +35,7 @@ impl Completer for CustomCompletion {
|
|||||||
&mut self,
|
&mut self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
_stack: &Stack,
|
_stack: &Stack,
|
||||||
prefix: Vec<u8>,
|
prefix: &[u8],
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
@ -93,10 +91,6 @@ impl Completer for CustomCompletion {
|
|||||||
.and_then(|val| val.as_bool().ok())
|
.and_then(|val| val.as_bool().ok())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
if should_sort {
|
|
||||||
self.sort_by = SortBy::Ascending;
|
|
||||||
}
|
|
||||||
|
|
||||||
custom_completion_options = Some(CompletionOptions {
|
custom_completion_options = Some(CompletionOptions {
|
||||||
case_sensitive: options
|
case_sensitive: options
|
||||||
.get("case_sensitive")
|
.get("case_sensitive")
|
||||||
@ -114,6 +108,11 @@ impl Completer for CustomCompletion {
|
|||||||
.unwrap_or(MatchAlgorithm::Prefix),
|
.unwrap_or(MatchAlgorithm::Prefix),
|
||||||
None => completion_options.match_algorithm,
|
None => completion_options.match_algorithm,
|
||||||
},
|
},
|
||||||
|
sort: if should_sort {
|
||||||
|
CompletionSort::Alphabetical
|
||||||
|
} else {
|
||||||
|
CompletionSort::Smart
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,12 +123,11 @@ impl Completer for CustomCompletion {
|
|||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let suggestions = if let Some(custom_completion_options) = custom_completion_options {
|
let options = custom_completion_options
|
||||||
filter(&prefix, suggestions, &custom_completion_options)
|
.as_ref()
|
||||||
} else {
|
.unwrap_or(completion_options);
|
||||||
filter(&prefix, suggestions, completion_options)
|
let suggestions = filter(prefix, suggestions, options);
|
||||||
};
|
sort_suggestions(&String::from_utf8_lossy(prefix), suggestions, options)
|
||||||
sort_suggestions(&String::from_utf8_lossy(&prefix), suggestions, self.sort_by)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,13 +26,13 @@ impl Completer for DirectoryCompletion {
|
|||||||
&mut self,
|
&mut self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
prefix: Vec<u8>,
|
prefix: &[u8],
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_pos: 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, working_set, span);
|
||||||
|
|
||||||
// Filter only the folders
|
// Filter only the folders
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
@ -48,14 +48,12 @@ impl Completer for DirectoryCompletion {
|
|||||||
.map(move |x| SemanticSuggestion {
|
.map(move |x| SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: x.1,
|
value: x.1,
|
||||||
description: None,
|
|
||||||
style: x.2,
|
style: x.2,
|
||||||
extra: None,
|
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: x.0.start - offset,
|
start: x.0.start - offset,
|
||||||
end: x.0.end - offset,
|
end: x.0.end - offset,
|
||||||
},
|
},
|
||||||
append_whitespace: false,
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
// TODO????
|
// TODO????
|
||||||
kind: None,
|
kind: None,
|
||||||
|
@ -6,7 +6,7 @@ use nu_protocol::{
|
|||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::path::{is_separator, Path, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR};
|
use std::path::{is_separator, Path, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR};
|
||||||
|
|
||||||
use super::{completion_common::sort_suggestions, SemanticSuggestion, SortBy};
|
use super::{completion_common::sort_suggestions, SemanticSuggestion};
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct DotNuCompletion {}
|
pub struct DotNuCompletion {}
|
||||||
@ -22,13 +22,13 @@ impl Completer for DotNuCompletion {
|
|||||||
&mut self,
|
&mut self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
prefix: Vec<u8>,
|
prefix: &[u8],
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_pos: usize,
|
_pos: usize,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let prefix_str = String::from_utf8_lossy(&prefix).replace('`', "");
|
let prefix_str = String::from_utf8_lossy(prefix).replace('`', "");
|
||||||
let mut search_dirs: Vec<String> = vec![];
|
let mut search_dirs: Vec<String> = vec![];
|
||||||
|
|
||||||
// If prefix_str is only a word we want to search in the current dir
|
// If prefix_str is only a word we want to search in the current dir
|
||||||
@ -116,14 +116,13 @@ impl Completer for DotNuCompletion {
|
|||||||
.map(move |x| SemanticSuggestion {
|
.map(move |x| SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: x.1,
|
value: x.1,
|
||||||
description: None,
|
|
||||||
style: x.2,
|
style: x.2,
|
||||||
extra: None,
|
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: x.0.start - offset,
|
start: x.0.start - offset,
|
||||||
end: x.0.end - offset,
|
end: x.0.end - offset,
|
||||||
},
|
},
|
||||||
append_whitespace: true,
|
append_whitespace: true,
|
||||||
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
// TODO????
|
// TODO????
|
||||||
kind: None,
|
kind: None,
|
||||||
@ -131,6 +130,6 @@ impl Completer for DotNuCompletion {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
sort_suggestions(&prefix_str, output, SortBy::Ascending)
|
sort_suggestions(&prefix_str, output, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ impl Completer for FileCompletion {
|
|||||||
&mut self,
|
&mut self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
prefix: Vec<u8>,
|
prefix: &[u8],
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_pos: usize,
|
_pos: usize,
|
||||||
@ -37,7 +37,7 @@ impl Completer for FileCompletion {
|
|||||||
prefix,
|
prefix,
|
||||||
span,
|
span,
|
||||||
readjusted,
|
readjusted,
|
||||||
} = adjust_if_intermediate(&prefix, working_set, span);
|
} = adjust_if_intermediate(prefix, working_set, span);
|
||||||
|
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
let items: Vec<_> = complete_item(
|
let items: Vec<_> = complete_item(
|
||||||
@ -53,14 +53,12 @@ impl Completer for FileCompletion {
|
|||||||
.map(move |x| SemanticSuggestion {
|
.map(move |x| SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: x.1,
|
value: x.1,
|
||||||
description: None,
|
|
||||||
style: x.2,
|
style: x.2,
|
||||||
extra: None,
|
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: x.0.start - offset,
|
start: x.0.start - offset,
|
||||||
end: x.0.end - offset,
|
end: x.0.end - offset,
|
||||||
},
|
},
|
||||||
append_whitespace: false,
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
// TODO????
|
// TODO????
|
||||||
kind: None,
|
kind: None,
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
use crate::completions::{
|
use crate::completions::{completion_common::sort_suggestions, Completer, CompletionOptions};
|
||||||
completion_common::sort_suggestions, Completer, CompletionOptions, SortBy,
|
|
||||||
};
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Expr, Expression},
|
ast::{Expr, Expression},
|
||||||
engine::{Stack, StateWorkingSet},
|
engine::{Stack, StateWorkingSet},
|
||||||
@ -26,7 +24,7 @@ impl Completer for FlagCompletion {
|
|||||||
&mut self,
|
&mut self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
_stack: &Stack,
|
_stack: &Stack,
|
||||||
prefix: Vec<u8>,
|
prefix: &[u8],
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_pos: usize,
|
_pos: usize,
|
||||||
@ -46,18 +44,17 @@ impl Completer for FlagCompletion {
|
|||||||
short.encode_utf8(&mut named);
|
short.encode_utf8(&mut named);
|
||||||
named.insert(0, b'-');
|
named.insert(0, b'-');
|
||||||
|
|
||||||
if options.match_algorithm.matches_u8(&named, &prefix) {
|
if options.match_algorithm.matches_u8(&named, prefix) {
|
||||||
output.push(SemanticSuggestion {
|
output.push(SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: String::from_utf8_lossy(&named).to_string(),
|
value: String::from_utf8_lossy(&named).to_string(),
|
||||||
description: Some(flag_desc.to_string()),
|
description: Some(flag_desc.to_string()),
|
||||||
style: None,
|
|
||||||
extra: None,
|
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: span.start - offset,
|
start: span.start - offset,
|
||||||
end: span.end - offset,
|
end: span.end - offset,
|
||||||
},
|
},
|
||||||
append_whitespace: true,
|
append_whitespace: true,
|
||||||
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
// TODO????
|
// TODO????
|
||||||
kind: None,
|
kind: None,
|
||||||
@ -73,18 +70,17 @@ impl Completer for FlagCompletion {
|
|||||||
named.insert(0, b'-');
|
named.insert(0, b'-');
|
||||||
named.insert(0, b'-');
|
named.insert(0, b'-');
|
||||||
|
|
||||||
if options.match_algorithm.matches_u8(&named, &prefix) {
|
if options.match_algorithm.matches_u8(&named, prefix) {
|
||||||
output.push(SemanticSuggestion {
|
output.push(SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: String::from_utf8_lossy(&named).to_string(),
|
value: String::from_utf8_lossy(&named).to_string(),
|
||||||
description: Some(flag_desc.to_string()),
|
description: Some(flag_desc.to_string()),
|
||||||
style: None,
|
|
||||||
extra: None,
|
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: span.start - offset,
|
start: span.start - offset,
|
||||||
end: span.end - offset,
|
end: span.end - offset,
|
||||||
},
|
},
|
||||||
append_whitespace: true,
|
append_whitespace: true,
|
||||||
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
// TODO????
|
// TODO????
|
||||||
kind: None,
|
kind: None,
|
||||||
@ -92,7 +88,7 @@ impl Completer for FlagCompletion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sort_suggestions(&String::from_utf8_lossy(&prefix), output, SortBy::Ascending);
|
return sort_suggestions(&String::from_utf8_lossy(prefix), output, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
vec![]
|
vec![]
|
||||||
|
@ -8,15 +8,17 @@ mod directory_completions;
|
|||||||
mod dotnu_completions;
|
mod dotnu_completions;
|
||||||
mod file_completions;
|
mod file_completions;
|
||||||
mod flag_completions;
|
mod flag_completions;
|
||||||
|
mod operator_completions;
|
||||||
mod variable_completions;
|
mod variable_completions;
|
||||||
|
|
||||||
pub use base::{Completer, SemanticSuggestion, SuggestionKind};
|
pub use base::{Completer, SemanticSuggestion, SuggestionKind};
|
||||||
pub use command_completions::CommandCompletion;
|
pub use command_completions::CommandCompletion;
|
||||||
pub use completer::NuCompleter;
|
pub use completer::NuCompleter;
|
||||||
pub use completion_options::{CompletionOptions, MatchAlgorithm, SortBy};
|
pub use completion_options::{CompletionOptions, MatchAlgorithm};
|
||||||
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 file_completions::{file_path_completion, matches, FileCompletion};
|
pub use file_completions::{file_path_completion, matches, FileCompletion};
|
||||||
pub use flag_completions::FlagCompletion;
|
pub use flag_completions::FlagCompletion;
|
||||||
|
pub use operator_completions::OperatorCompletion;
|
||||||
pub use variable_completions::VariableCompletion;
|
pub use variable_completions::VariableCompletion;
|
||||||
|
182
crates/nu-cli/src/completions/operator_completions.rs
Normal file
182
crates/nu-cli/src/completions/operator_completions.rs
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
use crate::completions::{
|
||||||
|
Completer, CompletionOptions, MatchAlgorithm, SemanticSuggestion, SuggestionKind,
|
||||||
|
};
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::{Expr, Expression},
|
||||||
|
engine::{Stack, StateWorkingSet},
|
||||||
|
Span, Type,
|
||||||
|
};
|
||||||
|
use reedline::Suggestion;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct OperatorCompletion {
|
||||||
|
previous_expr: Expression,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OperatorCompletion {
|
||||||
|
pub fn new(previous_expr: Expression) -> Self {
|
||||||
|
OperatorCompletion { previous_expr }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Completer for OperatorCompletion {
|
||||||
|
fn fetch(
|
||||||
|
&mut self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
_stack: &Stack,
|
||||||
|
_prefix: &[u8],
|
||||||
|
span: Span,
|
||||||
|
offset: usize,
|
||||||
|
_pos: usize,
|
||||||
|
_options: &CompletionOptions,
|
||||||
|
) -> Vec<SemanticSuggestion> {
|
||||||
|
//Check if int, float, or string
|
||||||
|
let partial = std::str::from_utf8(working_set.get_span_contents(span)).unwrap_or("");
|
||||||
|
let op = match &self.previous_expr.expr {
|
||||||
|
Expr::BinaryOp(x, _, _) => &x.expr,
|
||||||
|
_ => {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let possible_operations = match op {
|
||||||
|
Expr::Int(_) => 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"),
|
||||||
|
("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)"),
|
||||||
|
(
|
||||||
|
"++",
|
||||||
|
"Appends two lists, a list and a value, two strings, or two binary values",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
Expr::String(_) => vec![
|
||||||
|
("=~", "Contains regex match"),
|
||||||
|
("like", "Contains regex match"),
|
||||||
|
("!~", "Does not contain regex match"),
|
||||||
|
("not-like", "Does not contain regex match"),
|
||||||
|
(
|
||||||
|
"++",
|
||||||
|
"Appends two lists, a list and a value, 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)"),
|
||||||
|
(
|
||||||
|
"++",
|
||||||
|
"Appends two lists, a list and a value, two strings, or two binary values",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
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)"),
|
||||||
|
(
|
||||||
|
"++",
|
||||||
|
"Appends two lists, a list and a value, two strings, or two binary values",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
Expr::FullCellPath(path) => match path.head.expr {
|
||||||
|
Expr::List(_) => vec![(
|
||||||
|
"++",
|
||||||
|
"Appends two lists, a list and a value, two strings, or two binary values",
|
||||||
|
)],
|
||||||
|
Expr::Var(id) => get_variable_completions(id, working_set),
|
||||||
|
_ => vec![],
|
||||||
|
},
|
||||||
|
_ => vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let match_algorithm = MatchAlgorithm::Prefix;
|
||||||
|
let input_fuzzy_search =
|
||||||
|
|(operator, _): &(&str, &str)| match_algorithm.matches_str(operator, partial);
|
||||||
|
|
||||||
|
possible_operations
|
||||||
|
.into_iter()
|
||||||
|
.filter(input_fuzzy_search)
|
||||||
|
.map(move |x| SemanticSuggestion {
|
||||||
|
suggestion: Suggestion {
|
||||||
|
value: x.0.to_string(),
|
||||||
|
description: Some(x.1.to_string()),
|
||||||
|
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||||
|
append_whitespace: true,
|
||||||
|
..Suggestion::default()
|
||||||
|
},
|
||||||
|
kind: Some(SuggestionKind::Command(
|
||||||
|
nu_protocol::engine::CommandType::Builtin,
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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![
|
||||||
|
(
|
||||||
|
"++=",
|
||||||
|
"Appends a list, a value, a string, or a binary value to a variable.",
|
||||||
|
),
|
||||||
|
("=", "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![],
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,7 @@ use nu_protocol::{
|
|||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
use super::{completion_common::sort_suggestions, SortBy};
|
use super::completion_common::sort_suggestions;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct VariableCompletion {
|
pub struct VariableCompletion {
|
||||||
@ -27,7 +27,7 @@ impl Completer for VariableCompletion {
|
|||||||
&mut self,
|
&mut self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
prefix: Vec<u8>,
|
prefix: &[u8],
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_pos: usize,
|
_pos: usize,
|
||||||
@ -42,7 +42,7 @@ impl Completer for VariableCompletion {
|
|||||||
end: span.end - offset,
|
end: span.end - offset,
|
||||||
};
|
};
|
||||||
let sublevels_count = self.var_context.1.len();
|
let sublevels_count = self.var_context.1.len();
|
||||||
let prefix_str = String::from_utf8_lossy(&prefix);
|
let prefix_str = String::from_utf8_lossy(prefix);
|
||||||
|
|
||||||
// Completions for the given variable
|
// Completions for the given variable
|
||||||
if !var_str.is_empty() {
|
if !var_str.is_empty() {
|
||||||
@ -66,13 +66,13 @@ impl Completer for VariableCompletion {
|
|||||||
if options.match_algorithm.matches_u8_insensitive(
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
options.case_sensitive,
|
options.case_sensitive,
|
||||||
suggestion.suggestion.value.as_bytes(),
|
suggestion.suggestion.value.as_bytes(),
|
||||||
&prefix,
|
prefix,
|
||||||
) {
|
) {
|
||||||
output.push(suggestion);
|
output.push(suggestion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
return sort_suggestions(&prefix_str, output, options);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No nesting provided, return all env vars
|
// No nesting provided, return all env vars
|
||||||
@ -80,23 +80,20 @@ impl Completer for VariableCompletion {
|
|||||||
if options.match_algorithm.matches_u8_insensitive(
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
options.case_sensitive,
|
options.case_sensitive,
|
||||||
env_var.0.as_bytes(),
|
env_var.0.as_bytes(),
|
||||||
&prefix,
|
prefix,
|
||||||
) {
|
) {
|
||||||
output.push(SemanticSuggestion {
|
output.push(SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: env_var.0,
|
value: env_var.0,
|
||||||
description: None,
|
|
||||||
style: None,
|
|
||||||
extra: None,
|
|
||||||
span: current_span,
|
span: current_span,
|
||||||
append_whitespace: false,
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
kind: Some(SuggestionKind::Type(env_var.1.get_type())),
|
kind: Some(SuggestionKind::Type(env_var.1.get_type())),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
return sort_suggestions(&prefix_str, output, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,13 +111,13 @@ impl Completer for VariableCompletion {
|
|||||||
if options.match_algorithm.matches_u8_insensitive(
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
options.case_sensitive,
|
options.case_sensitive,
|
||||||
suggestion.suggestion.value.as_bytes(),
|
suggestion.suggestion.value.as_bytes(),
|
||||||
&prefix,
|
prefix,
|
||||||
) {
|
) {
|
||||||
output.push(suggestion);
|
output.push(suggestion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
return sort_suggestions(&prefix_str, output, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,13 +133,13 @@ impl Completer for VariableCompletion {
|
|||||||
if options.match_algorithm.matches_u8_insensitive(
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
options.case_sensitive,
|
options.case_sensitive,
|
||||||
suggestion.suggestion.value.as_bytes(),
|
suggestion.suggestion.value.as_bytes(),
|
||||||
&prefix,
|
prefix,
|
||||||
) {
|
) {
|
||||||
output.push(suggestion);
|
output.push(suggestion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
return sort_suggestions(&prefix_str, output, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,16 +149,13 @@ impl Completer for VariableCompletion {
|
|||||||
if options.match_algorithm.matches_u8_insensitive(
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
options.case_sensitive,
|
options.case_sensitive,
|
||||||
builtin.as_bytes(),
|
builtin.as_bytes(),
|
||||||
&prefix,
|
prefix,
|
||||||
) {
|
) {
|
||||||
output.push(SemanticSuggestion {
|
output.push(SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: builtin.to_string(),
|
value: builtin.to_string(),
|
||||||
description: None,
|
|
||||||
style: None,
|
|
||||||
extra: None,
|
|
||||||
span: current_span,
|
span: current_span,
|
||||||
append_whitespace: false,
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
// TODO is there a way to get the VarId to get the type???
|
// TODO is there a way to get the VarId to get the type???
|
||||||
kind: None,
|
kind: None,
|
||||||
@ -179,16 +173,13 @@ impl Completer for VariableCompletion {
|
|||||||
if options.match_algorithm.matches_u8_insensitive(
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
options.case_sensitive,
|
options.case_sensitive,
|
||||||
v.0,
|
v.0,
|
||||||
&prefix,
|
prefix,
|
||||||
) {
|
) {
|
||||||
output.push(SemanticSuggestion {
|
output.push(SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: String::from_utf8_lossy(v.0).to_string(),
|
value: String::from_utf8_lossy(v.0).to_string(),
|
||||||
description: None,
|
|
||||||
style: None,
|
|
||||||
extra: None,
|
|
||||||
span: current_span,
|
span: current_span,
|
||||||
append_whitespace: false,
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
kind: Some(SuggestionKind::Type(
|
kind: Some(SuggestionKind::Type(
|
||||||
working_set.get_variable(*v.1).ty.clone(),
|
working_set.get_variable(*v.1).ty.clone(),
|
||||||
@ -210,16 +201,13 @@ impl Completer for VariableCompletion {
|
|||||||
if options.match_algorithm.matches_u8_insensitive(
|
if options.match_algorithm.matches_u8_insensitive(
|
||||||
options.case_sensitive,
|
options.case_sensitive,
|
||||||
v.0,
|
v.0,
|
||||||
&prefix,
|
prefix,
|
||||||
) {
|
) {
|
||||||
output.push(SemanticSuggestion {
|
output.push(SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: String::from_utf8_lossy(v.0).to_string(),
|
value: String::from_utf8_lossy(v.0).to_string(),
|
||||||
description: None,
|
|
||||||
style: None,
|
|
||||||
extra: None,
|
|
||||||
span: current_span,
|
span: current_span,
|
||||||
append_whitespace: false,
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
kind: Some(SuggestionKind::Type(
|
kind: Some(SuggestionKind::Type(
|
||||||
working_set.get_variable(*v.1).ty.clone(),
|
working_set.get_variable(*v.1).ty.clone(),
|
||||||
@ -229,7 +217,7 @@ impl Completer for VariableCompletion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
output = sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
output = sort_suggestions(&prefix_str, output, options);
|
||||||
|
|
||||||
output.dedup(); // TODO: Removes only consecutive duplicates, is it intended?
|
output.dedup(); // TODO: Removes only consecutive duplicates, is it intended?
|
||||||
|
|
||||||
@ -255,11 +243,8 @@ fn nested_suggestions(
|
|||||||
output.push(SemanticSuggestion {
|
output.push(SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: col.clone(),
|
value: col.clone(),
|
||||||
description: None,
|
|
||||||
style: None,
|
|
||||||
extra: None,
|
|
||||||
span: current_span,
|
span: current_span,
|
||||||
append_whitespace: false,
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
kind: Some(kind.clone()),
|
kind: Some(kind.clone()),
|
||||||
});
|
});
|
||||||
@ -272,11 +257,8 @@ fn nested_suggestions(
|
|||||||
output.push(SemanticSuggestion {
|
output.push(SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: column_name,
|
value: column_name,
|
||||||
description: None,
|
|
||||||
style: None,
|
|
||||||
extra: None,
|
|
||||||
span: current_span,
|
span: current_span,
|
||||||
append_whitespace: false,
|
..Suggestion::default()
|
||||||
},
|
},
|
||||||
kind: Some(kind.clone()),
|
kind: Some(kind.clone()),
|
||||||
});
|
});
|
||||||
|
@ -2,10 +2,10 @@ use crate::util::eval_source;
|
|||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
use nu_path::canonicalize_with;
|
use nu_path::canonicalize_with;
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
use nu_protocol::{engine::StateWorkingSet, report_error, ParseError, PluginRegistryFile, Spanned};
|
use nu_protocol::{engine::StateWorkingSet, ParseError, PluginRegistryFile, Spanned};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
report_error_new, HistoryFileFormat, PipelineData,
|
report_shell_error, PipelineData,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
use nu_utils::perf;
|
use nu_utils::perf;
|
||||||
@ -16,15 +16,8 @@ const PLUGIN_FILE: &str = "plugin.msgpackz";
|
|||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
const OLD_PLUGIN_FILE: &str = "plugin.nu";
|
const OLD_PLUGIN_FILE: &str = "plugin.nu";
|
||||||
|
|
||||||
const HISTORY_FILE_TXT: &str = "history.txt";
|
|
||||||
const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
|
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
pub fn read_plugin_file(
|
pub fn read_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Spanned<String>>) {
|
||||||
engine_state: &mut EngineState,
|
|
||||||
plugin_file: Option<Spanned<String>>,
|
|
||||||
storage_path: &str,
|
|
||||||
) {
|
|
||||||
use nu_protocol::ShellError;
|
use nu_protocol::ShellError;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@ -36,7 +29,7 @@ pub fn read_plugin_file(
|
|||||||
.and_then(|p| Path::new(&p.item).extension())
|
.and_then(|p| Path::new(&p.item).extension())
|
||||||
.is_some_and(|ext| ext == "nu")
|
.is_some_and(|ext| ext == "nu")
|
||||||
{
|
{
|
||||||
report_error_new(
|
report_shell_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
&ShellError::GenericError {
|
&ShellError::GenericError {
|
||||||
error: "Wrong plugin file format".into(),
|
error: "Wrong plugin file format".into(),
|
||||||
@ -52,7 +45,7 @@ pub fn read_plugin_file(
|
|||||||
let mut start_time = std::time::Instant::now();
|
let mut start_time = std::time::Instant::now();
|
||||||
// Reading signatures from plugin registry file
|
// Reading signatures from plugin registry file
|
||||||
// The plugin.msgpackz file stores the parsed signature collected from each registered plugin
|
// The plugin.msgpackz file stores the parsed signature collected from each registered plugin
|
||||||
add_plugin_file(engine_state, plugin_file.clone(), storage_path);
|
add_plugin_file(engine_state, plugin_file.clone());
|
||||||
perf!(
|
perf!(
|
||||||
"add plugin file to engine_state",
|
"add plugin file to engine_state",
|
||||||
start_time,
|
start_time,
|
||||||
@ -70,8 +63,7 @@ pub fn read_plugin_file(
|
|||||||
log::warn!("Plugin file not found: {}", plugin_path.display());
|
log::warn!("Plugin file not found: {}", plugin_path.display());
|
||||||
|
|
||||||
// Try migration of an old plugin file if this wasn't a custom plugin file
|
// Try migration of an old plugin file if this wasn't a custom plugin file
|
||||||
if plugin_file.is_none() && migrate_old_plugin_file(engine_state, storage_path)
|
if plugin_file.is_none() && migrate_old_plugin_file(engine_state) {
|
||||||
{
|
|
||||||
let Ok(file) = std::fs::File::open(&plugin_path) else {
|
let Ok(file) = std::fs::File::open(&plugin_path) else {
|
||||||
log::warn!("Failed to load newly migrated plugin file");
|
log::warn!("Failed to load newly migrated plugin file");
|
||||||
return;
|
return;
|
||||||
@ -81,7 +73,7 @@ pub fn read_plugin_file(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
report_error_new(
|
report_shell_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
&ShellError::GenericError {
|
&ShellError::GenericError {
|
||||||
error: format!(
|
error: format!(
|
||||||
@ -113,7 +105,7 @@ pub fn read_plugin_file(
|
|||||||
Ok(contents) => contents,
|
Ok(contents) => contents,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("Failed to read plugin registry file: {err:?}");
|
log::warn!("Failed to read plugin registry file: {err:?}");
|
||||||
report_error_new(
|
report_shell_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
&ShellError::GenericError {
|
&ShellError::GenericError {
|
||||||
error: format!(
|
error: format!(
|
||||||
@ -146,7 +138,7 @@ pub fn read_plugin_file(
|
|||||||
nu_plugin_engine::load_plugin_file(&mut working_set, &contents, span);
|
nu_plugin_engine::load_plugin_file(&mut working_set, &contents, span);
|
||||||
|
|
||||||
if let Err(err) = engine_state.merge_delta(working_set.render()) {
|
if let Err(err) = engine_state.merge_delta(working_set.render()) {
|
||||||
report_error_new(engine_state, &err);
|
report_shell_error(engine_state, &err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,14 +151,10 @@ pub fn read_plugin_file(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
pub fn add_plugin_file(
|
pub fn add_plugin_file(engine_state: &mut EngineState, plugin_file: Option<Spanned<String>>) {
|
||||||
engine_state: &mut EngineState,
|
|
||||||
plugin_file: Option<Spanned<String>>,
|
|
||||||
storage_path: &str,
|
|
||||||
) {
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
use nu_protocol::report_parse_error;
|
||||||
|
|
||||||
if let Ok(cwd) = engine_state.cwd_as_string(None) {
|
if let Ok(cwd) = engine_state.cwd_as_string(None) {
|
||||||
if let Some(plugin_file) = plugin_file {
|
if let Some(plugin_file) = plugin_file {
|
||||||
@ -181,18 +169,18 @@ pub fn add_plugin_file(
|
|||||||
engine_state.plugin_path = Some(path)
|
engine_state.plugin_path = Some(path)
|
||||||
} else {
|
} else {
|
||||||
// It's an error if the directory for the plugin file doesn't exist.
|
// It's an error if the directory for the plugin file doesn't exist.
|
||||||
report_error(
|
report_parse_error(
|
||||||
&working_set,
|
&StateWorkingSet::new(engine_state),
|
||||||
&ParseError::FileNotFound(
|
&ParseError::FileNotFound(
|
||||||
path_dir.to_string_lossy().into_owned(),
|
path_dir.to_string_lossy().into_owned(),
|
||||||
plugin_file.span,
|
plugin_file.span,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if let Some(mut plugin_path) = nu_path::config_dir() {
|
} else if let Some(plugin_path) = nu_path::nu_config_dir() {
|
||||||
// Path to store plugins signatures
|
// Path to store plugins signatures
|
||||||
plugin_path.push(storage_path);
|
let mut plugin_path =
|
||||||
let mut plugin_path = canonicalize_with(&plugin_path, &cwd).unwrap_or(plugin_path);
|
canonicalize_with(&plugin_path, &cwd).unwrap_or(plugin_path.into());
|
||||||
plugin_path.push(PLUGIN_FILE);
|
plugin_path.push(PLUGIN_FILE);
|
||||||
let plugin_path = canonicalize_with(&plugin_path, &cwd).unwrap_or(plugin_path);
|
let plugin_path = canonicalize_with(&plugin_path, &cwd).unwrap_or(plugin_path);
|
||||||
engine_state.plugin_path = Some(plugin_path);
|
engine_state.plugin_path = Some(plugin_path);
|
||||||
@ -213,7 +201,8 @@ pub fn eval_config_contents(
|
|||||||
let prev_file = engine_state.file.take();
|
let prev_file = engine_state.file.take();
|
||||||
engine_state.file = Some(config_path.clone());
|
engine_state.file = Some(config_path.clone());
|
||||||
|
|
||||||
eval_source(
|
// TODO: ignore this error?
|
||||||
|
let _ = eval_source(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
&contents,
|
&contents,
|
||||||
@ -226,33 +215,15 @@ pub fn eval_config_contents(
|
|||||||
engine_state.file = prev_file;
|
engine_state.file = prev_file;
|
||||||
|
|
||||||
// Merge the environment in case env vars changed in the config
|
// Merge the environment in case env vars changed in the config
|
||||||
match engine_state.cwd(Some(stack)) {
|
if let Err(e) = engine_state.merge_env(stack) {
|
||||||
Ok(cwd) => {
|
report_shell_error(engine_state, &e);
|
||||||
if let Err(e) = engine_state.merge_env(stack, cwd) {
|
|
||||||
report_error_new(engine_state, &e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
report_error_new(engine_state, &e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_history_path(storage_path: &str, mode: HistoryFileFormat) -> Option<PathBuf> {
|
|
||||||
nu_path::config_dir().map(|mut history_path| {
|
|
||||||
history_path.push(storage_path);
|
|
||||||
history_path.push(match mode {
|
|
||||||
HistoryFileFormat::PlainText => HISTORY_FILE_TXT,
|
|
||||||
HistoryFileFormat::Sqlite => HISTORY_FILE_SQLITE,
|
|
||||||
});
|
|
||||||
history_path
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) -> bool {
|
pub fn migrate_old_plugin_file(engine_state: &EngineState) -> bool {
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
PluginExample, PluginIdentity, PluginRegistryItem, PluginRegistryItemData, PluginSignature,
|
PluginExample, PluginIdentity, PluginRegistryItem, PluginRegistryItemData, PluginSignature,
|
||||||
ShellError,
|
ShellError,
|
||||||
@ -265,10 +236,9 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) -
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(config_dir) = nu_path::config_dir().and_then(|mut dir| {
|
let Some(config_dir) =
|
||||||
dir.push(storage_path);
|
nu_path::nu_config_dir().and_then(|dir| nu_path::canonicalize_with(dir, &cwd).ok())
|
||||||
nu_path::canonicalize_with(dir, &cwd).ok()
|
else {
|
||||||
}) else {
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -279,7 +249,7 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) -
|
|||||||
let old_contents = match std::fs::read(&old_plugin_file_path) {
|
let old_contents = match std::fs::read(&old_plugin_file_path) {
|
||||||
Ok(old_contents) => old_contents,
|
Ok(old_contents) => old_contents,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
report_error_new(
|
report_shell_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
&ShellError::GenericError {
|
&ShellError::GenericError {
|
||||||
error: "Can't read old plugin file to migrate".into(),
|
error: "Can't read old plugin file to migrate".into(),
|
||||||
@ -348,7 +318,7 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) -
|
|||||||
.map_err(|e| e.into())
|
.map_err(|e| e.into())
|
||||||
.and_then(|file| contents.write_to(file, None))
|
.and_then(|file| contents.write_to(file, None))
|
||||||
{
|
{
|
||||||
report_error_new(
|
report_shell_error(
|
||||||
&engine_state,
|
&engine_state,
|
||||||
&ShellError::GenericError {
|
&ShellError::GenericError {
|
||||||
error: "Failed to save migrated plugin file".into(),
|
error: "Failed to save migrated plugin file".into(),
|
||||||
|
@ -2,9 +2,10 @@ use log::info;
|
|||||||
use nu_engine::{convert_env_values, eval_block};
|
use nu_engine::{convert_env_values, eval_block};
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
|
cli_error::report_compile_error,
|
||||||
debugger::WithoutDebug,
|
debugger::WithoutDebug,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
report_error, PipelineData, ShellError, Spanned, Value,
|
report_parse_error, report_parse_warning, PipelineData, ShellError, Spanned, Value,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -53,7 +54,7 @@ pub fn evaluate_commands(
|
|||||||
// 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 {
|
||||||
Arc::make_mut(&mut engine_state.config).table_mode =
|
Arc::make_mut(&mut engine_state.config).table.mode =
|
||||||
t_mode.coerce_str()?.parse().unwrap_or_default();
|
t_mode.coerce_str()?.parse().unwrap_or_default();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,16 +62,16 @@ pub fn evaluate_commands(
|
|||||||
|
|
||||||
let output = parse(&mut working_set, None, commands.item.as_bytes(), false);
|
let output = parse(&mut working_set, None, commands.item.as_bytes(), false);
|
||||||
if let Some(warning) = working_set.parse_warnings.first() {
|
if let Some(warning) = working_set.parse_warnings.first() {
|
||||||
report_error(&working_set, warning);
|
report_parse_warning(&working_set, warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(err) = working_set.parse_errors.first() {
|
if let Some(err) = working_set.parse_errors.first() {
|
||||||
report_error(&working_set, err);
|
report_parse_error(&working_set, err);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(err) = working_set.compile_errors.first() {
|
if let Some(err) = working_set.compile_errors.first() {
|
||||||
report_error(&working_set, err);
|
report_compile_error(&working_set, err);
|
||||||
// Not a fatal error, for now
|
// Not a fatal error, for now
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,15 +89,11 @@ pub fn evaluate_commands(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(t_mode) = table_mode {
|
if let Some(t_mode) = table_mode {
|
||||||
Arc::make_mut(&mut engine_state.config).table_mode =
|
Arc::make_mut(&mut engine_state.config).table.mode =
|
||||||
t_mode.coerce_str()?.parse().unwrap_or_default();
|
t_mode.coerce_str()?.parse().unwrap_or_default();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(status) = pipeline.print(engine_state, stack, no_newline, false)? {
|
pipeline.print(engine_state, stack, no_newline, false)?;
|
||||||
if status.code() != 0 {
|
|
||||||
std::process::exit(status.code())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||||
|
|
||||||
|
@ -4,9 +4,10 @@ use nu_engine::{convert_env_values, 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,
|
||||||
debugger::WithoutDebug,
|
debugger::WithoutDebug,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
report_error, PipelineData, ShellError, Span, Value,
|
report_parse_error, report_parse_warning, PipelineData, ShellError, Span, Value,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -77,17 +78,17 @@ pub fn evaluate_file(
|
|||||||
let block = parse(&mut working_set, Some(file_path_str), &file, false);
|
let block = parse(&mut working_set, Some(file_path_str), &file, false);
|
||||||
|
|
||||||
if let Some(warning) = working_set.parse_warnings.first() {
|
if let Some(warning) = working_set.parse_warnings.first() {
|
||||||
report_error(&working_set, warning);
|
report_parse_warning(&working_set, warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If any parse errors were found, report the first error and exit.
|
// If any parse errors were found, report the first error and exit.
|
||||||
if let Some(err) = working_set.parse_errors.first() {
|
if let Some(err) = working_set.parse_errors.first() {
|
||||||
report_error(&working_set, err);
|
report_parse_error(&working_set, err);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(err) = working_set.compile_errors.first() {
|
if let Some(err) = working_set.compile_errors.first() {
|
||||||
report_error(&working_set, err);
|
report_compile_error(&working_set, err);
|
||||||
// Not a fatal error, for now
|
// Not a fatal error, for now
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,11 +119,7 @@ pub fn evaluate_file(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Print the pipeline output of the last command of the file.
|
// Print the pipeline output of the last command of the file.
|
||||||
if let Some(status) = pipeline.print(engine_state, stack, true, false)? {
|
pipeline.print(engine_state, stack, true, false)?;
|
||||||
if status.code() != 0 {
|
|
||||||
std::process::exit(status.code())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invoke the main command with arguments.
|
// Invoke the main command with arguments.
|
||||||
// Arguments with whitespaces are quoted, thus can be safely concatenated by whitespace.
|
// Arguments with whitespaces are quoted, thus can be safely concatenated by whitespace.
|
||||||
@ -140,7 +137,7 @@ pub fn evaluate_file(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if exit_code != 0 {
|
if exit_code != 0 {
|
||||||
std::process::exit(exit_code)
|
std::process::exit(exit_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||||
|
@ -21,7 +21,6 @@ pub use config_files::eval_config_contents;
|
|||||||
pub use eval_cmds::{evaluate_commands, EvaluateCommandsOpts};
|
pub use eval_cmds::{evaluate_commands, EvaluateCommandsOpts};
|
||||||
pub use eval_file::evaluate_file;
|
pub use eval_file::evaluate_file;
|
||||||
pub use menus::NuHelpCompleter;
|
pub use menus::NuHelpCompleter;
|
||||||
pub use nu_cmd_base::util::get_init_cwd;
|
|
||||||
pub use nu_highlight::NuHighlight;
|
pub use nu_highlight::NuHighlight;
|
||||||
pub use print::Print;
|
pub use print::Print;
|
||||||
pub use prompt::NushellPrompt;
|
pub use prompt::NushellPrompt;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use nu_engine::documentation::get_flags_section;
|
use nu_engine::documentation::{get_flags_section, HelpStyle};
|
||||||
use nu_protocol::{engine::EngineState, levenshtein_distance, Config};
|
use nu_protocol::{engine::EngineState, levenshtein_distance, Config};
|
||||||
use nu_utils::IgnoreCaseExt;
|
use nu_utils::IgnoreCaseExt;
|
||||||
use reedline::{Completer, Suggestion};
|
use reedline::{Completer, Suggestion};
|
||||||
@ -20,6 +20,9 @@ impl NuHelpCompleter {
|
|||||||
fn completion_helper(&self, line: &str, pos: usize) -> Vec<Suggestion> {
|
fn completion_helper(&self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||||
let folded_line = line.to_folded_case();
|
let folded_line = line.to_folded_case();
|
||||||
|
|
||||||
|
let mut help_style = HelpStyle::default();
|
||||||
|
help_style.update_from_config(&self.engine_state, &self.config);
|
||||||
|
|
||||||
let mut commands = self
|
let mut commands = self
|
||||||
.engine_state
|
.engine_state
|
||||||
.get_decls_sorted(false)
|
.get_decls_sorted(false)
|
||||||
@ -27,12 +30,15 @@ impl NuHelpCompleter {
|
|||||||
.filter_map(|(_, decl_id)| {
|
.filter_map(|(_, decl_id)| {
|
||||||
let decl = self.engine_state.get_decl(decl_id);
|
let decl = self.engine_state.get_decl(decl_id);
|
||||||
(decl.name().to_folded_case().contains(&folded_line)
|
(decl.name().to_folded_case().contains(&folded_line)
|
||||||
|| decl.usage().to_folded_case().contains(&folded_line)
|
|| decl.description().to_folded_case().contains(&folded_line)
|
||||||
|| decl
|
|| decl
|
||||||
.search_terms()
|
.search_terms()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.any(|term| term.to_folded_case().contains(&folded_line))
|
.any(|term| term.to_folded_case().contains(&folded_line))
|
||||||
|| decl.extra_usage().to_folded_case().contains(&folded_line))
|
|| decl
|
||||||
|
.extra_description()
|
||||||
|
.to_folded_case()
|
||||||
|
.contains(&folded_line))
|
||||||
.then_some(decl)
|
.then_some(decl)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@ -44,15 +50,15 @@ impl NuHelpCompleter {
|
|||||||
.map(|decl| {
|
.map(|decl| {
|
||||||
let mut long_desc = String::new();
|
let mut long_desc = String::new();
|
||||||
|
|
||||||
let usage = decl.usage();
|
let description = decl.description();
|
||||||
if !usage.is_empty() {
|
if !description.is_empty() {
|
||||||
long_desc.push_str(usage);
|
long_desc.push_str(description);
|
||||||
long_desc.push_str("\r\n\r\n");
|
long_desc.push_str("\r\n\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
let extra_usage = decl.extra_usage();
|
let extra_desc = decl.extra_description();
|
||||||
if !extra_usage.is_empty() {
|
if !extra_desc.is_empty() {
|
||||||
long_desc.push_str(extra_usage);
|
long_desc.push_str(extra_desc);
|
||||||
long_desc.push_str("\r\n\r\n");
|
long_desc.push_str("\r\n\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,12 +66,9 @@ impl NuHelpCompleter {
|
|||||||
let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature());
|
let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature());
|
||||||
|
|
||||||
if !sig.named.is_empty() {
|
if !sig.named.is_empty() {
|
||||||
long_desc.push_str(&get_flags_section(
|
long_desc.push_str(&get_flags_section(&sig, &help_style, |v| {
|
||||||
Some(&self.engine_state),
|
v.to_parsable_string(", ", &self.config)
|
||||||
Some(&self.config),
|
}))
|
||||||
&sig,
|
|
||||||
|v| v.to_parsable_string(", ", &self.config),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !sig.required_positional.is_empty()
|
if !sig.required_positional.is_empty()
|
||||||
@ -110,13 +113,12 @@ impl NuHelpCompleter {
|
|||||||
Suggestion {
|
Suggestion {
|
||||||
value: decl.name().into(),
|
value: decl.name().into(),
|
||||||
description: Some(long_desc),
|
description: Some(long_desc),
|
||||||
style: None,
|
|
||||||
extra: Some(extra),
|
extra: Some(extra),
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: pos - line.len(),
|
start: pos - line.len(),
|
||||||
end: pos,
|
end: pos,
|
||||||
},
|
},
|
||||||
append_whitespace: false,
|
..Suggestion::default()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -2,7 +2,7 @@ use nu_engine::eval_block;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
debugger::WithoutDebug,
|
debugger::WithoutDebug,
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
IntoPipelineData, Span, Value,
|
BlockId, IntoPipelineData, Span, Value,
|
||||||
};
|
};
|
||||||
use reedline::{menu_functions::parse_selection_char, Completer, Suggestion};
|
use reedline::{menu_functions::parse_selection_char, Completer, Suggestion};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -10,7 +10,7 @@ use std::sync::Arc;
|
|||||||
const SELECTION_CHAR: char = '!';
|
const SELECTION_CHAR: char = '!';
|
||||||
|
|
||||||
pub struct NuMenuCompleter {
|
pub struct NuMenuCompleter {
|
||||||
block_id: usize,
|
block_id: BlockId,
|
||||||
span: Span,
|
span: Span,
|
||||||
stack: Stack,
|
stack: Stack,
|
||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
@ -19,7 +19,7 @@ pub struct NuMenuCompleter {
|
|||||||
|
|
||||||
impl NuMenuCompleter {
|
impl NuMenuCompleter {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
block_id: usize,
|
block_id: BlockId,
|
||||||
span: Span,
|
span: Span,
|
||||||
stack: Stack,
|
stack: Stack,
|
||||||
engine_state: Arc<EngineState>,
|
engine_state: Arc<EngineState>,
|
||||||
@ -28,7 +28,7 @@ impl NuMenuCompleter {
|
|||||||
Self {
|
Self {
|
||||||
block_id,
|
block_id,
|
||||||
span,
|
span,
|
||||||
stack: stack.reset_out_dest().capture(),
|
stack: stack.reset_out_dest().collect_value(),
|
||||||
engine_state,
|
engine_state,
|
||||||
only_buffer_difference,
|
only_buffer_difference,
|
||||||
}
|
}
|
||||||
@ -142,10 +142,9 @@ fn convert_to_suggestions(
|
|||||||
vec![Suggestion {
|
vec![Suggestion {
|
||||||
value: text,
|
value: text,
|
||||||
description,
|
description,
|
||||||
style: None,
|
|
||||||
extra,
|
extra,
|
||||||
span,
|
span,
|
||||||
append_whitespace: false,
|
..Suggestion::default()
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
Value::List { vals, .. } => vals
|
Value::List { vals, .. } => vals
|
||||||
@ -154,9 +153,6 @@ fn convert_to_suggestions(
|
|||||||
.collect(),
|
.collect(),
|
||||||
_ => vec![Suggestion {
|
_ => vec![Suggestion {
|
||||||
value: format!("Not a record: {value:?}"),
|
value: format!("Not a record: {value:?}"),
|
||||||
description: None,
|
|
||||||
style: None,
|
|
||||||
extra: None,
|
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: if only_buffer_difference {
|
start: if only_buffer_difference {
|
||||||
pos - line.len()
|
pos - line.len()
|
||||||
@ -169,7 +165,7 @@ fn convert_to_suggestions(
|
|||||||
line.len()
|
line.len()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
append_whitespace: false,
|
..Suggestion::default()
|
||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ impl Command for NuHighlight {
|
|||||||
.input_output_types(vec![(Type::String, Type::String)])
|
.input_output_types(vec![(Type::String, Type::String)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Syntax highlight the input string."
|
"Syntax highlight the input string."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
|
use nu_protocol::ByteStreamSource;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Print;
|
pub struct Print;
|
||||||
@ -22,14 +23,19 @@ impl Command for Print {
|
|||||||
Some('n'),
|
Some('n'),
|
||||||
)
|
)
|
||||||
.switch("stderr", "print to stderr instead of stdout", Some('e'))
|
.switch("stderr", "print to stderr instead of stdout", Some('e'))
|
||||||
|
.switch(
|
||||||
|
"raw",
|
||||||
|
"print without formatting (including binary data)",
|
||||||
|
Some('r'),
|
||||||
|
)
|
||||||
.category(Category::Strings)
|
.category(Category::Strings)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Print the given values to stdout."
|
"Print the given values to stdout."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_description(&self) -> &str {
|
||||||
r#"Unlike `echo`, this command does not return any value (`print | describe` will return "nothing").
|
r#"Unlike `echo`, this command does not return any value (`print | describe` will return "nothing").
|
||||||
Since this command has no output, there is no point in piping it with other commands.
|
Since this command has no output, there is no point in piping it with other commands.
|
||||||
|
|
||||||
@ -45,20 +51,35 @@ Since this command has no output, there is no point in piping it with other comm
|
|||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
mut input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||||
let no_newline = call.has_flag(engine_state, stack, "no-newline")?;
|
let no_newline = call.has_flag(engine_state, stack, "no-newline")?;
|
||||||
let to_stderr = call.has_flag(engine_state, stack, "stderr")?;
|
let to_stderr = call.has_flag(engine_state, stack, "stderr")?;
|
||||||
|
let raw = call.has_flag(engine_state, stack, "raw")?;
|
||||||
|
|
||||||
// This will allow for easy printing of pipelines as well
|
// This will allow for easy printing of pipelines as well
|
||||||
if !args.is_empty() {
|
if !args.is_empty() {
|
||||||
for arg in args {
|
for arg in args {
|
||||||
arg.into_pipeline_data()
|
if raw {
|
||||||
.print(engine_state, stack, no_newline, to_stderr)?;
|
arg.into_pipeline_data()
|
||||||
|
.print_raw(engine_state, no_newline, to_stderr)?;
|
||||||
|
} else {
|
||||||
|
arg.into_pipeline_data()
|
||||||
|
.print(engine_state, stack, no_newline, to_stderr)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if !input.is_nothing() {
|
} else if !input.is_nothing() {
|
||||||
input.print(engine_state, stack, no_newline, to_stderr)?;
|
if let PipelineData::ByteStream(stream, _) = &mut input {
|
||||||
|
if let ByteStreamSource::Child(child) = stream.source_mut() {
|
||||||
|
child.ignore_error(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if raw {
|
||||||
|
input.print_raw(engine_state, no_newline, to_stderr)?;
|
||||||
|
} else {
|
||||||
|
input.print(engine_state, stack, no_newline, to_stderr)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
@ -76,6 +97,11 @@ Since this command has no output, there is no point in piping it with other comm
|
|||||||
example: r#"print (2 + 3)"#,
|
example: r#"print (2 + 3)"#,
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Print 'ABC' from binary data",
|
||||||
|
example: r#"0x[41 42 43] | print --raw"#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
use crate::prompt_update::{
|
use crate::prompt_update::{
|
||||||
POST_PROMPT_MARKER, PRE_PROMPT_MARKER, VSCODE_POST_PROMPT_MARKER, VSCODE_PRE_PROMPT_MARKER,
|
POST_PROMPT_MARKER, PRE_PROMPT_MARKER, VSCODE_POST_PROMPT_MARKER, VSCODE_PRE_PROMPT_MARKER,
|
||||||
};
|
};
|
||||||
use nu_protocol::{
|
use nu_protocol::engine::{EngineState, Stack};
|
||||||
engine::{EngineState, Stack},
|
|
||||||
Value,
|
|
||||||
};
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use nu_utils::enable_vt_processing;
|
use nu_utils::enable_vt_processing;
|
||||||
use reedline::{
|
use reedline::{
|
||||||
@ -124,8 +121,11 @@ impl Prompt for NushellPrompt {
|
|||||||
.replace('\n', "\r\n");
|
.replace('\n', "\r\n");
|
||||||
|
|
||||||
if self.shell_integration_osc633 {
|
if self.shell_integration_osc633 {
|
||||||
if self.stack.get_env_var(&self.engine_state, "TERM_PROGRAM")
|
if self
|
||||||
== Some(Value::test_string("vscode"))
|
.stack
|
||||||
|
.get_env_var(&self.engine_state, "TERM_PROGRAM")
|
||||||
|
.and_then(|v| v.as_str().ok())
|
||||||
|
== Some("vscode")
|
||||||
{
|
{
|
||||||
// We're in vscode and we have osc633 enabled
|
// We're in vscode and we have osc633 enabled
|
||||||
format!("{VSCODE_PRE_PROMPT_MARKER}{prompt}{VSCODE_POST_PROMPT_MARKER}").into()
|
format!("{VSCODE_PRE_PROMPT_MARKER}{prompt}{VSCODE_POST_PROMPT_MARKER}").into()
|
||||||
|
@ -3,7 +3,7 @@ use log::trace;
|
|||||||
use nu_engine::ClosureEvalOnce;
|
use nu_engine::ClosureEvalOnce;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
report_error_new, Config, PipelineData, Value,
|
report_shell_error, Config, PipelineData, Value,
|
||||||
};
|
};
|
||||||
use reedline::Prompt;
|
use reedline::Prompt;
|
||||||
|
|
||||||
@ -46,7 +46,10 @@ pub(crate) const VSCODE_POST_EXECUTION_MARKER_PREFIX: &str = "\x1b]633;D;";
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) const VSCODE_POST_EXECUTION_MARKER_SUFFIX: &str = "\x1b\\";
|
pub(crate) const VSCODE_POST_EXECUTION_MARKER_SUFFIX: &str = "\x1b\\";
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) const VSCODE_COMMANDLINE_MARKER: &str = "\x1b]633;E\x1b\\";
|
//"\x1b]633;E;{}\x1b\\"
|
||||||
|
pub(crate) const VSCODE_COMMANDLINE_MARKER_PREFIX: &str = "\x1b]633;E;";
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) const VSCODE_COMMANDLINE_MARKER_SUFFIX: &str = "\x1b\\";
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
// "\x1b]633;P;Cwd={}\x1b\\"
|
// "\x1b]633;P;Cwd={}\x1b\\"
|
||||||
pub(crate) const VSCODE_CWD_PROPERTY_MARKER_PREFIX: &str = "\x1b]633;P;Cwd=";
|
pub(crate) const VSCODE_CWD_PROPERTY_MARKER_PREFIX: &str = "\x1b]633;P;Cwd=";
|
||||||
@ -65,7 +68,7 @@ fn get_prompt_string(
|
|||||||
.get_env_var(engine_state, prompt)
|
.get_env_var(engine_state, prompt)
|
||||||
.and_then(|v| match v {
|
.and_then(|v| match v {
|
||||||
Value::Closure { val, .. } => {
|
Value::Closure { val, .. } => {
|
||||||
let result = ClosureEvalOnce::new(engine_state, stack, *val)
|
let result = ClosureEvalOnce::new(engine_state, stack, val.as_ref().clone())
|
||||||
.run_with_input(PipelineData::Empty);
|
.run_with_input(PipelineData::Empty);
|
||||||
|
|
||||||
trace!(
|
trace!(
|
||||||
@ -77,7 +80,7 @@ fn get_prompt_string(
|
|||||||
|
|
||||||
result
|
result
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
report_error_new(engine_state, &err);
|
report_shell_error(engine_state, &err);
|
||||||
})
|
})
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
@ -115,13 +118,17 @@ pub(crate) fn update_prompt(
|
|||||||
|
|
||||||
// Now that we have the prompt string lets ansify it.
|
// Now that we have the prompt string lets ansify it.
|
||||||
// <133 A><prompt><133 B><command><133 C><command output>
|
// <133 A><prompt><133 B><command><133 C><command output>
|
||||||
let left_prompt_string = if config.shell_integration_osc633 {
|
let left_prompt_string = if config.shell_integration.osc633 {
|
||||||
if stack.get_env_var(engine_state, "TERM_PROGRAM") == Some(Value::test_string("vscode")) {
|
if stack
|
||||||
|
.get_env_var(engine_state, "TERM_PROGRAM")
|
||||||
|
.and_then(|v| v.as_str().ok())
|
||||||
|
== Some("vscode")
|
||||||
|
{
|
||||||
// We're in vscode and we have osc633 enabled
|
// We're in vscode and we have osc633 enabled
|
||||||
Some(format!(
|
Some(format!(
|
||||||
"{VSCODE_PRE_PROMPT_MARKER}{configured_left_prompt_string}{VSCODE_POST_PROMPT_MARKER}"
|
"{VSCODE_PRE_PROMPT_MARKER}{configured_left_prompt_string}{VSCODE_POST_PROMPT_MARKER}"
|
||||||
))
|
))
|
||||||
} else if config.shell_integration_osc133 {
|
} else if config.shell_integration.osc133 {
|
||||||
// If we're in VSCode but we don't find the env var, but we have osc133 set, then use it
|
// If we're in VSCode but we don't find the env var, but we have osc133 set, then use it
|
||||||
Some(format!(
|
Some(format!(
|
||||||
"{PRE_PROMPT_MARKER}{configured_left_prompt_string}{POST_PROMPT_MARKER}"
|
"{PRE_PROMPT_MARKER}{configured_left_prompt_string}{POST_PROMPT_MARKER}"
|
||||||
@ -129,7 +136,7 @@ pub(crate) fn update_prompt(
|
|||||||
} else {
|
} else {
|
||||||
configured_left_prompt_string.into()
|
configured_left_prompt_string.into()
|
||||||
}
|
}
|
||||||
} else if config.shell_integration_osc133 {
|
} else if config.shell_integration.osc133 {
|
||||||
Some(format!(
|
Some(format!(
|
||||||
"{PRE_PROMPT_MARKER}{configured_left_prompt_string}{POST_PROMPT_MARKER}"
|
"{PRE_PROMPT_MARKER}{configured_left_prompt_string}{POST_PROMPT_MARKER}"
|
||||||
))
|
))
|
||||||
|
@ -5,11 +5,10 @@ use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
|
|||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
create_menus,
|
|
||||||
debugger::WithoutDebug,
|
debugger::WithoutDebug,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
extract_value, Config, EditBindings, ParsedKeybinding, ParsedMenu, PipelineData, Record,
|
extract_value, Config, EditBindings, FromValue, ParsedKeybinding, ParsedMenu, PipelineData,
|
||||||
ShellError, Span, Value,
|
Record, ShellError, Span, Type, Value,
|
||||||
};
|
};
|
||||||
use reedline::{
|
use reedline::{
|
||||||
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
|
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
|
||||||
@ -36,6 +35,41 @@ const DEFAULT_COMPLETION_MENU: &str = r#"
|
|||||||
}
|
}
|
||||||
}"#;
|
}"#;
|
||||||
|
|
||||||
|
const DEFAULT_IDE_COMPLETION_MENU: &str = r#"
|
||||||
|
{
|
||||||
|
name: ide_completion_menu
|
||||||
|
only_buffer_difference: false
|
||||||
|
marker: "| "
|
||||||
|
type: {
|
||||||
|
layout: ide
|
||||||
|
min_completion_width: 0,
|
||||||
|
max_completion_width: 50,
|
||||||
|
max_completion_height: 10, # will be limited by the available lines in the terminal
|
||||||
|
padding: 0,
|
||||||
|
border: true,
|
||||||
|
cursor_offset: 0,
|
||||||
|
description_mode: "prefer_right"
|
||||||
|
min_description_width: 0
|
||||||
|
max_description_width: 50
|
||||||
|
max_description_height: 10
|
||||||
|
description_offset: 1
|
||||||
|
# If true, the cursor pos will be corrected, so the suggestions match up with the typed text
|
||||||
|
#
|
||||||
|
# C:\> str
|
||||||
|
# str join
|
||||||
|
# str trim
|
||||||
|
# str split
|
||||||
|
correct_cursor_pos: false
|
||||||
|
}
|
||||||
|
style: {
|
||||||
|
text: green
|
||||||
|
selected_text: { attr: r }
|
||||||
|
description_text: yellow
|
||||||
|
match_text: { attr: u }
|
||||||
|
selected_match_text: { attr: ur }
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
const DEFAULT_HISTORY_MENU: &str = r#"
|
const DEFAULT_HISTORY_MENU: &str = r#"
|
||||||
{
|
{
|
||||||
name: history_menu
|
name: history_menu
|
||||||
@ -95,6 +129,7 @@ pub(crate) fn add_menus(
|
|||||||
// Checking if the default menus have been added from the config file
|
// Checking if the default menus have been added from the config file
|
||||||
let default_menus = [
|
let default_menus = [
|
||||||
("completion_menu", DEFAULT_COMPLETION_MENU),
|
("completion_menu", DEFAULT_COMPLETION_MENU),
|
||||||
|
("ide_completion_menu", DEFAULT_IDE_COMPLETION_MENU),
|
||||||
("history_menu", DEFAULT_HISTORY_MENU),
|
("history_menu", DEFAULT_HISTORY_MENU),
|
||||||
("help_menu", DEFAULT_HELP_MENU),
|
("help_menu", DEFAULT_HELP_MENU),
|
||||||
];
|
];
|
||||||
@ -122,7 +157,7 @@ pub(crate) fn add_menus(
|
|||||||
|
|
||||||
engine_state.merge_delta(delta)?;
|
engine_state.merge_delta(delta)?;
|
||||||
|
|
||||||
let mut temp_stack = Stack::new().capture();
|
let mut temp_stack = Stack::new().collect_value();
|
||||||
let input = PipelineData::Empty;
|
let input = PipelineData::Empty;
|
||||||
menu_eval_results.push(eval_block::<WithoutDebug>(
|
menu_eval_results.push(eval_block::<WithoutDebug>(
|
||||||
&engine_state,
|
&engine_state,
|
||||||
@ -137,15 +172,13 @@ pub(crate) fn add_menus(
|
|||||||
|
|
||||||
for res in menu_eval_results.into_iter() {
|
for res in menu_eval_results.into_iter() {
|
||||||
if let PipelineData::Value(value, None) = res {
|
if let PipelineData::Value(value, None) = res {
|
||||||
for menu in create_menus(&value)? {
|
line_editor = add_menu(
|
||||||
line_editor = add_menu(
|
line_editor,
|
||||||
line_editor,
|
&ParsedMenu::from_value(value)?,
|
||||||
&menu,
|
new_engine_state_ref.clone(),
|
||||||
new_engine_state_ref.clone(),
|
stack,
|
||||||
stack,
|
config.clone(),
|
||||||
config.clone(),
|
)?;
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,8 +192,8 @@ fn add_menu(
|
|||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
) -> Result<Reedline, ShellError> {
|
) -> Result<Reedline, ShellError> {
|
||||||
let span = menu.menu_type.span();
|
let span = menu.r#type.span();
|
||||||
if let Value::Record { val, .. } = &menu.menu_type {
|
if let Value::Record { val, .. } = &menu.r#type {
|
||||||
let layout = extract_value("layout", val, span)?.to_expanded_string("", &config);
|
let layout = extract_value("layout", val, span)?.to_expanded_string("", &config);
|
||||||
|
|
||||||
match layout.as_str() {
|
match layout.as_str() {
|
||||||
@ -168,22 +201,22 @@ fn add_menu(
|
|||||||
"list" => add_list_menu(line_editor, menu, engine_state, stack, config),
|
"list" => add_list_menu(line_editor, menu, engine_state, stack, config),
|
||||||
"ide" => add_ide_menu(line_editor, menu, engine_state, stack, config),
|
"ide" => add_ide_menu(line_editor, menu, engine_state, stack, config),
|
||||||
"description" => add_description_menu(line_editor, menu, engine_state, stack, config),
|
"description" => add_description_menu(line_editor, menu, engine_state, stack, config),
|
||||||
_ => Err(ShellError::UnsupportedConfigValue {
|
str => Err(ShellError::InvalidValue {
|
||||||
expected: "columnar, list, ide or description".to_string(),
|
valid: "'columnar', 'list', 'ide', or 'description'".into(),
|
||||||
value: menu.menu_type.to_abbreviated_string(&config),
|
actual: format!("'{str}'"),
|
||||||
span: menu.menu_type.span(),
|
span,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::UnsupportedConfigValue {
|
Err(ShellError::RuntimeTypeMismatch {
|
||||||
expected: "only record type".to_string(),
|
expected: Type::record(),
|
||||||
value: menu.menu_type.to_abbreviated_string(&config),
|
actual: menu.r#type.get_type(),
|
||||||
span: menu.menu_type.span(),
|
span,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_style(record: &Record, name: &str, span: Span) -> Option<Style> {
|
fn get_style(record: &Record, name: &'static str, span: Span) -> Option<Style> {
|
||||||
extract_value(name, record, span)
|
extract_value(name, record, span)
|
||||||
.ok()
|
.ok()
|
||||||
.map(|text| match text {
|
.map(|text| match text {
|
||||||
@ -193,6 +226,29 @@ fn get_style(record: &Record, name: &str, span: Span) -> Option<Style> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_menu_style<M: MenuBuilder>(mut menu: M, style: &Value) -> M {
|
||||||
|
let span = style.span();
|
||||||
|
let Value::Record { val, .. } = &style else {
|
||||||
|
return menu;
|
||||||
|
};
|
||||||
|
if let Some(style) = get_style(val, "text", span) {
|
||||||
|
menu = menu.with_text_style(style);
|
||||||
|
}
|
||||||
|
if let Some(style) = get_style(val, "selected_text", span) {
|
||||||
|
menu = menu.with_selected_text_style(style);
|
||||||
|
}
|
||||||
|
if let Some(style) = get_style(val, "description_text", span) {
|
||||||
|
menu = menu.with_description_text_style(style);
|
||||||
|
}
|
||||||
|
if let Some(style) = get_style(val, "match_text", span) {
|
||||||
|
menu = menu.with_match_text_style(style);
|
||||||
|
}
|
||||||
|
if let Some(style) = get_style(val, "selected_match_text", span) {
|
||||||
|
menu = menu.with_selected_match_text_style(style);
|
||||||
|
}
|
||||||
|
menu
|
||||||
|
}
|
||||||
|
|
||||||
// Adds a columnar menu to the editor engine
|
// Adds a columnar menu to the editor engine
|
||||||
pub(crate) fn add_columnar_menu(
|
pub(crate) fn add_columnar_menu(
|
||||||
line_editor: Reedline,
|
line_editor: Reedline,
|
||||||
@ -201,11 +257,11 @@ pub(crate) fn add_columnar_menu(
|
|||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<Reedline, ShellError> {
|
) -> Result<Reedline, ShellError> {
|
||||||
let span = menu.menu_type.span();
|
let span = menu.r#type.span();
|
||||||
let name = menu.name.to_expanded_string("", config);
|
let name = menu.name.to_expanded_string("", config);
|
||||||
let mut columnar_menu = ColumnarMenu::default().with_name(&name);
|
let mut columnar_menu = ColumnarMenu::default().with_name(&name);
|
||||||
|
|
||||||
if let Value::Record { val, .. } = &menu.menu_type {
|
if let Value::Record { val, .. } = &menu.r#type {
|
||||||
columnar_menu = match extract_value("columns", val, span) {
|
columnar_menu = match extract_value("columns", val, span) {
|
||||||
Ok(columns) => {
|
Ok(columns) => {
|
||||||
let columns = columns.as_int()?;
|
let columns = columns.as_int()?;
|
||||||
@ -231,24 +287,7 @@ pub(crate) fn add_columnar_menu(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let span = menu.style.span();
|
columnar_menu = set_menu_style(columnar_menu, &menu.style);
|
||||||
if let Value::Record { val, .. } = &menu.style {
|
|
||||||
if let Some(style) = get_style(val, "text", span) {
|
|
||||||
columnar_menu = columnar_menu.with_text_style(style);
|
|
||||||
}
|
|
||||||
if let Some(style) = get_style(val, "selected_text", span) {
|
|
||||||
columnar_menu = columnar_menu.with_selected_text_style(style);
|
|
||||||
}
|
|
||||||
if let Some(style) = get_style(val, "description_text", span) {
|
|
||||||
columnar_menu = columnar_menu.with_description_text_style(style);
|
|
||||||
}
|
|
||||||
if let Some(style) = get_style(val, "match_text", span) {
|
|
||||||
columnar_menu = columnar_menu.with_match_text_style(style);
|
|
||||||
}
|
|
||||||
if let Some(style) = get_style(val, "selected_match_text", span) {
|
|
||||||
columnar_menu = columnar_menu.with_selected_match_text_style(style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let marker = menu.marker.to_expanded_string("", config);
|
let marker = menu.marker.to_expanded_string("", config);
|
||||||
columnar_menu = columnar_menu.with_marker(&marker);
|
columnar_menu = columnar_menu.with_marker(&marker);
|
||||||
@ -256,30 +295,23 @@ pub(crate) fn add_columnar_menu(
|
|||||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||||
columnar_menu = columnar_menu.with_only_buffer_difference(only_buffer_difference);
|
columnar_menu = columnar_menu.with_only_buffer_difference(only_buffer_difference);
|
||||||
|
|
||||||
let span = menu.source.span();
|
let completer = if let Some(closure) = &menu.source {
|
||||||
match &menu.source {
|
let menu_completer = NuMenuCompleter::new(
|
||||||
Value::Nothing { .. } => {
|
closure.block_id,
|
||||||
Ok(line_editor.with_menu(ReedlineMenu::EngineCompleter(Box::new(columnar_menu))))
|
|
||||||
}
|
|
||||||
Value::Closure { val, .. } => {
|
|
||||||
let menu_completer = NuMenuCompleter::new(
|
|
||||||
val.block_id,
|
|
||||||
span,
|
|
||||||
stack.captures_to_stack(val.captures.clone()),
|
|
||||||
engine_state,
|
|
||||||
only_buffer_difference,
|
|
||||||
);
|
|
||||||
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
|
||||||
menu: Box::new(columnar_menu),
|
|
||||||
completer: Box::new(menu_completer),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
_ => Err(ShellError::UnsupportedConfigValue {
|
|
||||||
expected: "block or omitted value".to_string(),
|
|
||||||
value: menu.source.to_abbreviated_string(config),
|
|
||||||
span,
|
span,
|
||||||
}),
|
stack.captures_to_stack(closure.captures.clone()),
|
||||||
}
|
engine_state,
|
||||||
|
only_buffer_difference,
|
||||||
|
);
|
||||||
|
ReedlineMenu::WithCompleter {
|
||||||
|
menu: Box::new(columnar_menu),
|
||||||
|
completer: Box::new(menu_completer),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ReedlineMenu::EngineCompleter(Box::new(columnar_menu))
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(line_editor.with_menu(completer))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a search menu to the line editor
|
// Adds a search menu to the line editor
|
||||||
@ -293,8 +325,8 @@ pub(crate) fn add_list_menu(
|
|||||||
let name = menu.name.to_expanded_string("", &config);
|
let name = menu.name.to_expanded_string("", &config);
|
||||||
let mut list_menu = ListMenu::default().with_name(&name);
|
let mut list_menu = ListMenu::default().with_name(&name);
|
||||||
|
|
||||||
let span = menu.menu_type.span();
|
let span = menu.r#type.span();
|
||||||
if let Value::Record { val, .. } = &menu.menu_type {
|
if let Value::Record { val, .. } = &menu.r#type {
|
||||||
list_menu = match extract_value("page_size", val, span) {
|
list_menu = match extract_value("page_size", val, span) {
|
||||||
Ok(page_size) => {
|
Ok(page_size) => {
|
||||||
let page_size = page_size.as_int()?;
|
let page_size = page_size.as_int()?;
|
||||||
@ -304,18 +336,7 @@ pub(crate) fn add_list_menu(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let span = menu.style.span();
|
list_menu = set_menu_style(list_menu, &menu.style);
|
||||||
if let Value::Record { val, .. } = &menu.style {
|
|
||||||
if let Some(style) = get_style(val, "text", span) {
|
|
||||||
list_menu = list_menu.with_text_style(style);
|
|
||||||
}
|
|
||||||
if let Some(style) = get_style(val, "selected_text", span) {
|
|
||||||
list_menu = list_menu.with_selected_text_style(style);
|
|
||||||
}
|
|
||||||
if let Some(style) = get_style(val, "description_text", span) {
|
|
||||||
list_menu = list_menu.with_description_text_style(style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let marker = menu.marker.to_expanded_string("", &config);
|
let marker = menu.marker.to_expanded_string("", &config);
|
||||||
list_menu = list_menu.with_marker(&marker);
|
list_menu = list_menu.with_marker(&marker);
|
||||||
@ -323,30 +344,23 @@ pub(crate) fn add_list_menu(
|
|||||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||||
list_menu = list_menu.with_only_buffer_difference(only_buffer_difference);
|
list_menu = list_menu.with_only_buffer_difference(only_buffer_difference);
|
||||||
|
|
||||||
let span = menu.source.span();
|
let completer = if let Some(closure) = &menu.source {
|
||||||
match &menu.source {
|
let menu_completer = NuMenuCompleter::new(
|
||||||
Value::Nothing { .. } => {
|
closure.block_id,
|
||||||
Ok(line_editor.with_menu(ReedlineMenu::HistoryMenu(Box::new(list_menu))))
|
span,
|
||||||
|
stack.captures_to_stack(closure.captures.clone()),
|
||||||
|
engine_state,
|
||||||
|
only_buffer_difference,
|
||||||
|
);
|
||||||
|
ReedlineMenu::WithCompleter {
|
||||||
|
menu: Box::new(list_menu),
|
||||||
|
completer: Box::new(menu_completer),
|
||||||
}
|
}
|
||||||
Value::Closure { val, .. } => {
|
} else {
|
||||||
let menu_completer = NuMenuCompleter::new(
|
ReedlineMenu::HistoryMenu(Box::new(list_menu))
|
||||||
val.block_id,
|
};
|
||||||
span,
|
|
||||||
stack.captures_to_stack(val.captures.clone()),
|
Ok(line_editor.with_menu(completer))
|
||||||
engine_state,
|
|
||||||
only_buffer_difference,
|
|
||||||
);
|
|
||||||
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
|
||||||
menu: Box::new(list_menu),
|
|
||||||
completer: Box::new(menu_completer),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
_ => Err(ShellError::UnsupportedConfigValue {
|
|
||||||
expected: "block or omitted value".to_string(),
|
|
||||||
value: menu.source.to_abbreviated_string(&config),
|
|
||||||
span: menu.source.span(),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds an IDE menu to the line editor
|
// Adds an IDE menu to the line editor
|
||||||
@ -357,11 +371,11 @@ pub(crate) fn add_ide_menu(
|
|||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
) -> Result<Reedline, ShellError> {
|
) -> Result<Reedline, ShellError> {
|
||||||
let span = menu.menu_type.span();
|
let span = menu.r#type.span();
|
||||||
let name = menu.name.to_expanded_string("", &config);
|
let name = menu.name.to_expanded_string("", &config);
|
||||||
let mut ide_menu = IdeMenu::default().with_name(&name);
|
let mut ide_menu = IdeMenu::default().with_name(&name);
|
||||||
|
|
||||||
if let Value::Record { val, .. } = &menu.menu_type {
|
if let Value::Record { val, .. } = &menu.r#type {
|
||||||
ide_menu = match extract_value("min_completion_width", val, span) {
|
ide_menu = match extract_value("min_completion_width", val, span) {
|
||||||
Ok(min_completion_width) => {
|
Ok(min_completion_width) => {
|
||||||
let min_completion_width = min_completion_width.as_int()?;
|
let min_completion_width = min_completion_width.as_int()?;
|
||||||
@ -421,9 +435,9 @@ pub(crate) fn add_ide_menu(
|
|||||||
vertical,
|
vertical,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
return Err(ShellError::RuntimeTypeMismatch {
|
||||||
expected: "bool or record".to_string(),
|
expected: Type::custom("bool or record"),
|
||||||
value: border.to_abbreviated_string(&config),
|
actual: border.get_type(),
|
||||||
span: border.span(),
|
span: border.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -444,10 +458,10 @@ pub(crate) fn add_ide_menu(
|
|||||||
"left" => ide_menu.with_description_mode(DescriptionMode::Left),
|
"left" => ide_menu.with_description_mode(DescriptionMode::Left),
|
||||||
"right" => ide_menu.with_description_mode(DescriptionMode::Right),
|
"right" => ide_menu.with_description_mode(DescriptionMode::Right),
|
||||||
"prefer_right" => ide_menu.with_description_mode(DescriptionMode::PreferRight),
|
"prefer_right" => ide_menu.with_description_mode(DescriptionMode::PreferRight),
|
||||||
_ => {
|
str => {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
return Err(ShellError::InvalidValue {
|
||||||
expected: "\"left\", \"right\" or \"prefer_right\"".to_string(),
|
valid: "'left', 'right', or 'prefer_right'".into(),
|
||||||
value: description_mode.to_abbreviated_string(&config),
|
actual: format!("'{str}'"),
|
||||||
span: description_mode.span(),
|
span: description_mode.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -496,24 +510,7 @@ pub(crate) fn add_ide_menu(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let span = menu.style.span();
|
ide_menu = set_menu_style(ide_menu, &menu.style);
|
||||||
if let Value::Record { val, .. } = &menu.style {
|
|
||||||
if let Some(style) = get_style(val, "text", span) {
|
|
||||||
ide_menu = ide_menu.with_text_style(style);
|
|
||||||
}
|
|
||||||
if let Some(style) = get_style(val, "selected_text", span) {
|
|
||||||
ide_menu = ide_menu.with_selected_text_style(style);
|
|
||||||
}
|
|
||||||
if let Some(style) = get_style(val, "description_text", span) {
|
|
||||||
ide_menu = ide_menu.with_description_text_style(style);
|
|
||||||
}
|
|
||||||
if let Some(style) = get_style(val, "match_text", span) {
|
|
||||||
ide_menu = ide_menu.with_match_text_style(style);
|
|
||||||
}
|
|
||||||
if let Some(style) = get_style(val, "selected_match_text", span) {
|
|
||||||
ide_menu = ide_menu.with_selected_match_text_style(style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let marker = menu.marker.to_expanded_string("", &config);
|
let marker = menu.marker.to_expanded_string("", &config);
|
||||||
ide_menu = ide_menu.with_marker(&marker);
|
ide_menu = ide_menu.with_marker(&marker);
|
||||||
@ -521,30 +518,23 @@ pub(crate) fn add_ide_menu(
|
|||||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||||
ide_menu = ide_menu.with_only_buffer_difference(only_buffer_difference);
|
ide_menu = ide_menu.with_only_buffer_difference(only_buffer_difference);
|
||||||
|
|
||||||
let span = menu.source.span();
|
let completer = if let Some(closure) = &menu.source {
|
||||||
match &menu.source {
|
let menu_completer = NuMenuCompleter::new(
|
||||||
Value::Nothing { .. } => {
|
closure.block_id,
|
||||||
Ok(line_editor.with_menu(ReedlineMenu::EngineCompleter(Box::new(ide_menu))))
|
|
||||||
}
|
|
||||||
Value::Closure { val, .. } => {
|
|
||||||
let menu_completer = NuMenuCompleter::new(
|
|
||||||
val.block_id,
|
|
||||||
span,
|
|
||||||
stack.captures_to_stack(val.captures.clone()),
|
|
||||||
engine_state,
|
|
||||||
only_buffer_difference,
|
|
||||||
);
|
|
||||||
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
|
||||||
menu: Box::new(ide_menu),
|
|
||||||
completer: Box::new(menu_completer),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
_ => Err(ShellError::UnsupportedConfigValue {
|
|
||||||
expected: "block or omitted value".to_string(),
|
|
||||||
value: menu.source.to_abbreviated_string(&config),
|
|
||||||
span,
|
span,
|
||||||
}),
|
stack.captures_to_stack(closure.captures.clone()),
|
||||||
}
|
engine_state,
|
||||||
|
only_buffer_difference,
|
||||||
|
);
|
||||||
|
ReedlineMenu::WithCompleter {
|
||||||
|
menu: Box::new(ide_menu),
|
||||||
|
completer: Box::new(menu_completer),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ReedlineMenu::EngineCompleter(Box::new(ide_menu))
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(line_editor.with_menu(completer))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a description menu to the line editor
|
// Adds a description menu to the line editor
|
||||||
@ -558,8 +548,8 @@ pub(crate) fn add_description_menu(
|
|||||||
let name = menu.name.to_expanded_string("", &config);
|
let name = menu.name.to_expanded_string("", &config);
|
||||||
let mut description_menu = DescriptionMenu::default().with_name(&name);
|
let mut description_menu = DescriptionMenu::default().with_name(&name);
|
||||||
|
|
||||||
let span = menu.menu_type.span();
|
let span = menu.r#type.span();
|
||||||
if let Value::Record { val, .. } = &menu.menu_type {
|
if let Value::Record { val, .. } = &menu.r#type {
|
||||||
description_menu = match extract_value("columns", val, span) {
|
description_menu = match extract_value("columns", val, span) {
|
||||||
Ok(columns) => {
|
Ok(columns) => {
|
||||||
let columns = columns.as_int()?;
|
let columns = columns.as_int()?;
|
||||||
@ -601,18 +591,7 @@ pub(crate) fn add_description_menu(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let span = menu.style.span();
|
description_menu = set_menu_style(description_menu, &menu.style);
|
||||||
if let Value::Record { val, .. } = &menu.style {
|
|
||||||
if let Some(style) = get_style(val, "text", span) {
|
|
||||||
description_menu = description_menu.with_text_style(style);
|
|
||||||
}
|
|
||||||
if let Some(style) = get_style(val, "selected_text", span) {
|
|
||||||
description_menu = description_menu.with_selected_text_style(style);
|
|
||||||
}
|
|
||||||
if let Some(style) = get_style(val, "description_text", span) {
|
|
||||||
description_menu = description_menu.with_description_text_style(style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let marker = menu.marker.to_expanded_string("", &config);
|
let marker = menu.marker.to_expanded_string("", &config);
|
||||||
description_menu = description_menu.with_marker(&marker);
|
description_menu = description_menu.with_marker(&marker);
|
||||||
@ -620,34 +599,27 @@ pub(crate) fn add_description_menu(
|
|||||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||||
description_menu = description_menu.with_only_buffer_difference(only_buffer_difference);
|
description_menu = description_menu.with_only_buffer_difference(only_buffer_difference);
|
||||||
|
|
||||||
let span = menu.source.span();
|
let completer = if let Some(closure) = &menu.source {
|
||||||
match &menu.source {
|
let menu_completer = NuMenuCompleter::new(
|
||||||
Value::Nothing { .. } => {
|
closure.block_id,
|
||||||
let completer = Box::new(NuHelpCompleter::new(engine_state, config));
|
span,
|
||||||
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
stack.captures_to_stack(closure.captures.clone()),
|
||||||
menu: Box::new(description_menu),
|
engine_state,
|
||||||
completer,
|
only_buffer_difference,
|
||||||
}))
|
);
|
||||||
|
ReedlineMenu::WithCompleter {
|
||||||
|
menu: Box::new(description_menu),
|
||||||
|
completer: Box::new(menu_completer),
|
||||||
}
|
}
|
||||||
Value::Closure { val, .. } => {
|
} else {
|
||||||
let menu_completer = NuMenuCompleter::new(
|
let menu_completer = NuHelpCompleter::new(engine_state, config);
|
||||||
val.block_id,
|
ReedlineMenu::WithCompleter {
|
||||||
span,
|
menu: Box::new(description_menu),
|
||||||
stack.captures_to_stack(val.captures.clone()),
|
completer: Box::new(menu_completer),
|
||||||
engine_state,
|
|
||||||
only_buffer_difference,
|
|
||||||
);
|
|
||||||
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
|
||||||
menu: Box::new(description_menu),
|
|
||||||
completer: Box::new(menu_completer),
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
_ => Err(ShellError::UnsupportedConfigValue {
|
};
|
||||||
expected: "closure or omitted value".to_string(),
|
|
||||||
value: menu.source.to_abbreviated_string(&config),
|
Ok(line_editor.with_menu(completer))
|
||||||
span: menu.source.span(),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_menu_keybindings(keybindings: &mut Keybindings) {
|
fn add_menu_keybindings(keybindings: &mut Keybindings) {
|
||||||
@ -662,6 +634,16 @@ fn add_menu_keybindings(keybindings: &mut Keybindings) {
|
|||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
keybindings.add_binding(
|
||||||
|
KeyModifiers::CONTROL,
|
||||||
|
KeyCode::Char(' '),
|
||||||
|
ReedlineEvent::UntilFound(vec![
|
||||||
|
ReedlineEvent::Menu("ide_completion_menu".to_string()),
|
||||||
|
ReedlineEvent::MenuNext,
|
||||||
|
ReedlineEvent::Edit(vec![EditCommand::Complete]),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
keybindings.add_binding(
|
keybindings.add_binding(
|
||||||
KeyModifiers::SHIFT,
|
KeyModifiers::SHIFT,
|
||||||
KeyCode::BackTab,
|
KeyCode::BackTab,
|
||||||
@ -729,6 +711,7 @@ pub(crate) fn create_keybindings(config: &Config) -> Result<KeybindingsMode, She
|
|||||||
}
|
}
|
||||||
for keybinding in parsed_keybindings {
|
for keybinding in parsed_keybindings {
|
||||||
add_keybinding(
|
add_keybinding(
|
||||||
|
&keybinding.name,
|
||||||
&keybinding.mode,
|
&keybinding.mode,
|
||||||
keybinding,
|
keybinding,
|
||||||
config,
|
config,
|
||||||
@ -747,7 +730,9 @@ pub(crate) fn create_keybindings(config: &Config) -> Result<KeybindingsMode, She
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::only_used_in_recursion)]
|
||||||
fn add_keybinding(
|
fn add_keybinding(
|
||||||
|
name: &Option<Value>,
|
||||||
mode: &Value,
|
mode: &Value,
|
||||||
keybinding: &ParsedKeybinding,
|
keybinding: &ParsedKeybinding,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
@ -761,15 +746,16 @@ fn add_keybinding(
|
|||||||
"emacs" => add_parsed_keybinding(emacs_keybindings, keybinding, config),
|
"emacs" => add_parsed_keybinding(emacs_keybindings, keybinding, config),
|
||||||
"vi_insert" => add_parsed_keybinding(insert_keybindings, keybinding, config),
|
"vi_insert" => add_parsed_keybinding(insert_keybindings, keybinding, config),
|
||||||
"vi_normal" => add_parsed_keybinding(normal_keybindings, keybinding, config),
|
"vi_normal" => add_parsed_keybinding(normal_keybindings, keybinding, config),
|
||||||
m => Err(ShellError::UnsupportedConfigValue {
|
str => Err(ShellError::InvalidValue {
|
||||||
expected: "emacs, vi_insert or vi_normal".to_string(),
|
valid: "'emacs', 'vi_insert', or 'vi_normal'".into(),
|
||||||
value: m.to_string(),
|
actual: format!("'{str}'"),
|
||||||
span,
|
span,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
for inner_mode in vals {
|
for inner_mode in vals {
|
||||||
add_keybinding(
|
add_keybinding(
|
||||||
|
name,
|
||||||
inner_mode,
|
inner_mode,
|
||||||
keybinding,
|
keybinding,
|
||||||
config,
|
config,
|
||||||
@ -781,9 +767,9 @@ fn add_keybinding(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
v => Err(ShellError::UnsupportedConfigValue {
|
v => Err(ShellError::RuntimeTypeMismatch {
|
||||||
expected: "string or list of strings".to_string(),
|
expected: Type::custom("string or list<string>"),
|
||||||
value: v.to_abbreviated_string(config),
|
actual: v.get_type(),
|
||||||
span: v.span(),
|
span: v.span(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -794,91 +780,107 @@ fn add_parsed_keybinding(
|
|||||||
keybinding: &ParsedKeybinding,
|
keybinding: &ParsedKeybinding,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let modifier = match keybinding
|
let Ok(modifier_str) = keybinding.modifier.as_str() else {
|
||||||
.modifier
|
return Err(ShellError::RuntimeTypeMismatch {
|
||||||
.to_expanded_string("", config)
|
expected: Type::String,
|
||||||
.to_ascii_lowercase()
|
actual: keybinding.modifier.get_type(),
|
||||||
.as_str()
|
span: keybinding.modifier.span(),
|
||||||
{
|
});
|
||||||
"control" => KeyModifiers::CONTROL,
|
|
||||||
"shift" => KeyModifiers::SHIFT,
|
|
||||||
"alt" => KeyModifiers::ALT,
|
|
||||||
"none" => KeyModifiers::NONE,
|
|
||||||
"shift_alt" | "alt_shift" => KeyModifiers::SHIFT | KeyModifiers::ALT,
|
|
||||||
"control_shift" | "shift_control" => KeyModifiers::CONTROL | KeyModifiers::SHIFT,
|
|
||||||
"control_alt" | "alt_control" => KeyModifiers::CONTROL | KeyModifiers::ALT,
|
|
||||||
"control_alt_shift" | "control_shift_alt" => {
|
|
||||||
KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
|
||||||
expected: "CONTROL, SHIFT, ALT or NONE".to_string(),
|
|
||||||
value: keybinding.modifier.to_abbreviated_string(config),
|
|
||||||
span: keybinding.modifier.span(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let keycode = match keybinding
|
let mut modifier = KeyModifiers::NONE;
|
||||||
.keycode
|
if !str::eq_ignore_ascii_case(modifier_str, "none") {
|
||||||
.to_expanded_string("", config)
|
for part in modifier_str.split('_') {
|
||||||
.to_ascii_lowercase()
|
match part.to_ascii_lowercase().as_str() {
|
||||||
.as_str()
|
"control" => modifier |= KeyModifiers::CONTROL,
|
||||||
{
|
"shift" => modifier |= KeyModifiers::SHIFT,
|
||||||
"backspace" => KeyCode::Backspace,
|
"alt" => modifier |= KeyModifiers::ALT,
|
||||||
"enter" => KeyCode::Enter,
|
"super" => modifier |= KeyModifiers::SUPER,
|
||||||
c if c.starts_with("char_") => {
|
"hyper" => modifier |= KeyModifiers::HYPER,
|
||||||
let mut char_iter = c.chars().skip(5);
|
"meta" => modifier |= KeyModifiers::META,
|
||||||
let pos1 = char_iter.next();
|
_ => {
|
||||||
let pos2 = char_iter.next();
|
return Err(ShellError::InvalidValue {
|
||||||
|
valid: "'control', 'shift', 'alt', 'super', 'hyper', 'meta', or 'none'"
|
||||||
let char = if let (Some(char), None) = (pos1, pos2) {
|
.into(),
|
||||||
char
|
actual: format!("'{part}'"),
|
||||||
} else {
|
span: keybinding.modifier.span(),
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
});
|
||||||
expected: "char_<CHAR: unicode codepoint>".to_string(),
|
}
|
||||||
value: c.to_string(),
|
}
|
||||||
span: keybinding.keycode.span(),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
KeyCode::Char(char)
|
|
||||||
}
|
}
|
||||||
"space" => KeyCode::Char(' '),
|
}
|
||||||
"down" => KeyCode::Down,
|
|
||||||
"up" => KeyCode::Up,
|
let Ok(keycode) = keybinding.keycode.as_str() else {
|
||||||
"left" => KeyCode::Left,
|
return Err(ShellError::RuntimeTypeMismatch {
|
||||||
"right" => KeyCode::Right,
|
expected: Type::String,
|
||||||
"home" => KeyCode::Home,
|
actual: keybinding.keycode.get_type(),
|
||||||
"end" => KeyCode::End,
|
span: keybinding.keycode.span(),
|
||||||
"pageup" => KeyCode::PageUp,
|
});
|
||||||
"pagedown" => KeyCode::PageDown,
|
};
|
||||||
"tab" => KeyCode::Tab,
|
|
||||||
"backtab" => KeyCode::BackTab,
|
let keycode_lower = keycode.to_ascii_lowercase();
|
||||||
"delete" => KeyCode::Delete,
|
|
||||||
"insert" => KeyCode::Insert,
|
let keycode = if let Some(rest) = keycode_lower.strip_prefix("char_") {
|
||||||
c if c.starts_with('f') => {
|
let error = |valid: &str, actual: &str| ShellError::InvalidValue {
|
||||||
let fn_num: u8 = c[1..]
|
valid: valid.into(),
|
||||||
|
actual: actual.into(),
|
||||||
|
span: keybinding.keycode.span(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut char_iter = rest.chars();
|
||||||
|
let char = match (char_iter.next(), char_iter.next()) {
|
||||||
|
(Some(char), None) => char,
|
||||||
|
(Some('u'), Some(_)) => {
|
||||||
|
// This will never panic as we know there are at least two symbols
|
||||||
|
let Ok(code_point) = u32::from_str_radix(&rest[1..], 16) else {
|
||||||
|
return Err(error("a valid hex code", keycode));
|
||||||
|
};
|
||||||
|
|
||||||
|
char::from_u32(code_point).ok_or(error("a valid Unicode code point", keycode))?
|
||||||
|
}
|
||||||
|
_ => return Err(error("'char_<char>' or 'char_u<hex code>'", keycode)),
|
||||||
|
};
|
||||||
|
|
||||||
|
KeyCode::Char(char)
|
||||||
|
} else {
|
||||||
|
match keycode_lower.as_str() {
|
||||||
|
"backspace" => KeyCode::Backspace,
|
||||||
|
"enter" => KeyCode::Enter,
|
||||||
|
"space" => KeyCode::Char(' '),
|
||||||
|
"down" => KeyCode::Down,
|
||||||
|
"up" => KeyCode::Up,
|
||||||
|
"left" => KeyCode::Left,
|
||||||
|
"right" => KeyCode::Right,
|
||||||
|
"home" => KeyCode::Home,
|
||||||
|
"end" => KeyCode::End,
|
||||||
|
"pageup" => KeyCode::PageUp,
|
||||||
|
"pagedown" => KeyCode::PageDown,
|
||||||
|
"tab" => KeyCode::Tab,
|
||||||
|
"backtab" => KeyCode::BackTab,
|
||||||
|
"delete" => KeyCode::Delete,
|
||||||
|
"insert" => KeyCode::Insert,
|
||||||
|
c if c.starts_with('f') => c[1..]
|
||||||
.parse()
|
.parse()
|
||||||
.ok()
|
.ok()
|
||||||
.filter(|num| matches!(num, 1..=20))
|
.filter(|num| (1..=35).contains(num))
|
||||||
.ok_or(ShellError::UnsupportedConfigValue {
|
.map(KeyCode::F)
|
||||||
expected: "(f1|f2|...|f20)".to_string(),
|
.ok_or(ShellError::InvalidValue {
|
||||||
value: format!("unknown function key: {c}"),
|
valid: "'f1', 'f2', ..., or 'f35'".into(),
|
||||||
|
actual: format!("'{keycode}'"),
|
||||||
span: keybinding.keycode.span(),
|
span: keybinding.keycode.span(),
|
||||||
})?;
|
})?,
|
||||||
KeyCode::F(fn_num)
|
"null" => KeyCode::Null,
|
||||||
}
|
"esc" | "escape" => KeyCode::Esc,
|
||||||
"null" => KeyCode::Null,
|
_ => {
|
||||||
"esc" | "escape" => KeyCode::Esc,
|
return Err(ShellError::InvalidValue {
|
||||||
_ => {
|
valid: "a crossterm KeyCode".into(),
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
actual: format!("'{keycode}'"),
|
||||||
expected: "crossterm KeyCode".to_string(),
|
span: keybinding.keycode.span(),
|
||||||
value: keybinding.keycode.to_abbreviated_string(config),
|
});
|
||||||
span: keybinding.keycode.span(),
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(event) = parse_event(&keybinding.event, config)? {
|
if let Some(event) = parse_event(&keybinding.event, config)? {
|
||||||
keybindings.add_binding(modifier, keycode, event);
|
keybindings.add_binding(modifier, keycode, event);
|
||||||
} else {
|
} else {
|
||||||
@ -900,8 +902,8 @@ impl<'config> EventType<'config> {
|
|||||||
.map(Self::Send)
|
.map(Self::Send)
|
||||||
.or_else(|_| extract_value("edit", record, span).map(Self::Edit))
|
.or_else(|_| extract_value("edit", record, span).map(Self::Edit))
|
||||||
.or_else(|_| extract_value("until", record, span).map(Self::Until))
|
.or_else(|_| extract_value("until", record, span).map(Self::Until))
|
||||||
.map_err(|_| ShellError::MissingConfigValue {
|
.map_err(|_| ShellError::MissingRequiredColumn {
|
||||||
missing_value: "send, edit or until".to_string(),
|
column: "'send', 'edit', or 'until'",
|
||||||
span,
|
span,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -939,9 +941,9 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|value| match parse_event(value, config) {
|
.map(|value| match parse_event(value, config) {
|
||||||
Ok(inner) => match inner {
|
Ok(inner) => match inner {
|
||||||
None => Err(ShellError::UnsupportedConfigValue {
|
None => Err(ShellError::RuntimeTypeMismatch {
|
||||||
expected: "List containing valid events".to_string(),
|
expected: Type::custom("record or table"),
|
||||||
value: "Nothing value (null)".to_string(),
|
actual: value.get_type(),
|
||||||
span: value.span(),
|
span: value.span(),
|
||||||
}),
|
}),
|
||||||
Some(event) => Ok(event),
|
Some(event) => Ok(event),
|
||||||
@ -952,9 +954,9 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
|
|
||||||
Ok(Some(ReedlineEvent::UntilFound(events)))
|
Ok(Some(ReedlineEvent::UntilFound(events)))
|
||||||
}
|
}
|
||||||
v => Err(ShellError::UnsupportedConfigValue {
|
v => Err(ShellError::RuntimeTypeMismatch {
|
||||||
expected: "list of events".to_string(),
|
expected: Type::list(Type::Any),
|
||||||
value: v.to_abbreviated_string(config),
|
actual: v.get_type(),
|
||||||
span: v.span(),
|
span: v.span(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -964,9 +966,9 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|value| match parse_event(value, config) {
|
.map(|value| match parse_event(value, config) {
|
||||||
Ok(inner) => match inner {
|
Ok(inner) => match inner {
|
||||||
None => Err(ShellError::UnsupportedConfigValue {
|
None => Err(ShellError::RuntimeTypeMismatch {
|
||||||
expected: "List containing valid events".to_string(),
|
expected: Type::custom("record or table"),
|
||||||
value: "Nothing value (null)".to_string(),
|
actual: value.get_type(),
|
||||||
span: value.span(),
|
span: value.span(),
|
||||||
}),
|
}),
|
||||||
Some(event) => Ok(event),
|
Some(event) => Ok(event),
|
||||||
@ -978,9 +980,9 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
|||||||
Ok(Some(ReedlineEvent::Multiple(events)))
|
Ok(Some(ReedlineEvent::Multiple(events)))
|
||||||
}
|
}
|
||||||
Value::Nothing { .. } => Ok(None),
|
Value::Nothing { .. } => Ok(None),
|
||||||
v => Err(ShellError::UnsupportedConfigValue {
|
v => Err(ShellError::RuntimeTypeMismatch {
|
||||||
expected: "record or list of records, null to unbind key".to_string(),
|
expected: Type::custom("record, table, or nothing"),
|
||||||
value: v.to_abbreviated_string(config),
|
actual: v.get_type(),
|
||||||
span: v.span(),
|
span: v.span(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -1029,12 +1031,12 @@ fn event_from_record(
|
|||||||
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))
|
||||||
}
|
}
|
||||||
v => {
|
str => {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
return Err(ShellError::InvalidValue {
|
||||||
expected: "Reedline event".to_string(),
|
valid: "a reedline event".into(),
|
||||||
value: v.to_string(),
|
actual: format!("'{str}'"),
|
||||||
span,
|
span,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1127,7 +1129,7 @@ fn edit_from_record(
|
|||||||
}
|
}
|
||||||
"insertchar" => {
|
"insertchar" => {
|
||||||
let value = extract_value("value", record, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value)?;
|
||||||
EditCommand::InsertChar(char)
|
EditCommand::InsertChar(char)
|
||||||
}
|
}
|
||||||
"insertstring" => {
|
"insertstring" => {
|
||||||
@ -1164,17 +1166,17 @@ fn edit_from_record(
|
|||||||
"redo" => EditCommand::Redo,
|
"redo" => EditCommand::Redo,
|
||||||
"cutrightuntil" => {
|
"cutrightuntil" => {
|
||||||
let value = extract_value("value", record, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value)?;
|
||||||
EditCommand::CutRightUntil(char)
|
EditCommand::CutRightUntil(char)
|
||||||
}
|
}
|
||||||
"cutrightbefore" => {
|
"cutrightbefore" => {
|
||||||
let value = extract_value("value", record, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value)?;
|
||||||
EditCommand::CutRightBefore(char)
|
EditCommand::CutRightBefore(char)
|
||||||
}
|
}
|
||||||
"moverightuntil" => {
|
"moverightuntil" => {
|
||||||
let value = extract_value("value", record, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value)?;
|
||||||
let select = extract_value("select", record, span)
|
let select = extract_value("select", record, span)
|
||||||
.and_then(|value| value.as_bool())
|
.and_then(|value| value.as_bool())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
@ -1182,7 +1184,7 @@ fn edit_from_record(
|
|||||||
}
|
}
|
||||||
"moverightbefore" => {
|
"moverightbefore" => {
|
||||||
let value = extract_value("value", record, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value)?;
|
||||||
let select = extract_value("select", record, span)
|
let select = extract_value("select", record, span)
|
||||||
.and_then(|value| value.as_bool())
|
.and_then(|value| value.as_bool())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
@ -1190,17 +1192,17 @@ fn edit_from_record(
|
|||||||
}
|
}
|
||||||
"cutleftuntil" => {
|
"cutleftuntil" => {
|
||||||
let value = extract_value("value", record, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value)?;
|
||||||
EditCommand::CutLeftUntil(char)
|
EditCommand::CutLeftUntil(char)
|
||||||
}
|
}
|
||||||
"cutleftbefore" => {
|
"cutleftbefore" => {
|
||||||
let value = extract_value("value", record, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value)?;
|
||||||
EditCommand::CutLeftBefore(char)
|
EditCommand::CutLeftBefore(char)
|
||||||
}
|
}
|
||||||
"moveleftuntil" => {
|
"moveleftuntil" => {
|
||||||
let value = extract_value("value", record, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value)?;
|
||||||
let select = extract_value("select", record, span)
|
let select = extract_value("select", record, span)
|
||||||
.and_then(|value| value.as_bool())
|
.and_then(|value| value.as_bool())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
@ -1208,7 +1210,7 @@ fn edit_from_record(
|
|||||||
}
|
}
|
||||||
"moveleftbefore" => {
|
"moveleftbefore" => {
|
||||||
let value = extract_value("value", record, span)?;
|
let value = extract_value("value", record, span)?;
|
||||||
let char = extract_char(value, config)?;
|
let char = extract_char(value)?;
|
||||||
let select = extract_value("select", record, span)
|
let select = extract_value("select", record, span)
|
||||||
.and_then(|value| value.as_bool())
|
.and_then(|value| value.as_bool())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
@ -1225,28 +1227,36 @@ fn edit_from_record(
|
|||||||
#[cfg(feature = "system-clipboard")]
|
#[cfg(feature = "system-clipboard")]
|
||||||
"pastesystem" => EditCommand::PasteSystem,
|
"pastesystem" => EditCommand::PasteSystem,
|
||||||
"selectall" => EditCommand::SelectAll,
|
"selectall" => EditCommand::SelectAll,
|
||||||
e => {
|
str => {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
return Err(ShellError::InvalidValue {
|
||||||
expected: "reedline EditCommand".to_string(),
|
valid: "a reedline EditCommand".into(),
|
||||||
value: e.to_string(),
|
actual: format!("'{str}'"),
|
||||||
span,
|
span,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(edit)
|
Ok(edit)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_char(value: &Value, config: &Config) -> Result<char, ShellError> {
|
fn extract_char(value: &Value) -> Result<char, ShellError> {
|
||||||
let span = value.span();
|
if let Ok(str) = value.as_str() {
|
||||||
value
|
let mut chars = str.chars();
|
||||||
.to_expanded_string("", config)
|
match (chars.next(), chars.next()) {
|
||||||
.chars()
|
(Some(c), None) => Ok(c),
|
||||||
.next()
|
_ => Err(ShellError::InvalidValue {
|
||||||
.ok_or_else(|| ShellError::MissingConfigValue {
|
valid: "a single character".into(),
|
||||||
missing_value: "char to insert".to_string(),
|
actual: format!("'{str}'"),
|
||||||
span,
|
span: value.span(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(ShellError::RuntimeTypeMismatch {
|
||||||
|
expected: Type::String,
|
||||||
|
actual: value.get_type(),
|
||||||
|
span: value.span(),
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -1375,7 +1385,7 @@ mod test {
|
|||||||
|
|
||||||
let span = Span::test_data();
|
let span = Span::test_data();
|
||||||
let b = EventType::try_from_record(&event, span);
|
let b = EventType::try_from_record(&event, span);
|
||||||
assert!(matches!(b, Err(ShellError::MissingConfigValue { .. })));
|
assert!(matches!(b, Err(ShellError::MissingRequiredColumn { .. })));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::prompt_update::{
|
use crate::prompt_update::{
|
||||||
POST_EXECUTION_MARKER_PREFIX, POST_EXECUTION_MARKER_SUFFIX, PRE_EXECUTION_MARKER,
|
POST_EXECUTION_MARKER_PREFIX, POST_EXECUTION_MARKER_SUFFIX, PRE_EXECUTION_MARKER,
|
||||||
RESET_APPLICATION_MODE, VSCODE_CWD_PROPERTY_MARKER_PREFIX, VSCODE_CWD_PROPERTY_MARKER_SUFFIX,
|
RESET_APPLICATION_MODE, VSCODE_COMMANDLINE_MARKER_PREFIX, VSCODE_COMMANDLINE_MARKER_SUFFIX,
|
||||||
|
VSCODE_CWD_PROPERTY_MARKER_PREFIX, VSCODE_CWD_PROPERTY_MARKER_SUFFIX,
|
||||||
VSCODE_POST_EXECUTION_MARKER_PREFIX, VSCODE_POST_EXECUTION_MARKER_SUFFIX,
|
VSCODE_POST_EXECUTION_MARKER_PREFIX, VSCODE_POST_EXECUTION_MARKER_SUFFIX,
|
||||||
VSCODE_PRE_EXECUTION_MARKER,
|
VSCODE_PRE_EXECUTION_MARKER,
|
||||||
};
|
};
|
||||||
@ -15,10 +16,7 @@ use crate::{
|
|||||||
use crossterm::cursor::SetCursorStyle;
|
use crossterm::cursor::SetCursorStyle;
|
||||||
use log::{error, trace, warn};
|
use log::{error, trace, warn};
|
||||||
use miette::{ErrReport, IntoDiagnostic, Result};
|
use miette::{ErrReport, IntoDiagnostic, Result};
|
||||||
use nu_cmd_base::{
|
use nu_cmd_base::{hook::eval_hook, util::get_editor};
|
||||||
hook::eval_hook,
|
|
||||||
util::{get_editor, get_guaranteed_cwd},
|
|
||||||
};
|
|
||||||
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::{convert_env_values, current_dir_str, env_to_strings};
|
||||||
@ -26,7 +24,7 @@ use nu_parser::{lex, parse, trim_quotes_str};
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
config::NuCursorShape,
|
config::NuCursorShape,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
report_error_new, HistoryConfig, HistoryFileFormat, PipelineData, ShellError, Span, Spanned,
|
report_shell_error, HistoryConfig, HistoryFileFormat, PipelineData, ShellError, Span, Spanned,
|
||||||
Value,
|
Value,
|
||||||
};
|
};
|
||||||
use nu_utils::{
|
use nu_utils::{
|
||||||
@ -52,7 +50,6 @@ use sysinfo::System;
|
|||||||
pub fn evaluate_repl(
|
pub fn evaluate_repl(
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: Stack,
|
stack: Stack,
|
||||||
nushell_path: &str,
|
|
||||||
prerun_command: Option<Spanned<String>>,
|
prerun_command: Option<Spanned<String>>,
|
||||||
load_std_lib: Option<Spanned<String>>,
|
load_std_lib: Option<Spanned<String>>,
|
||||||
entire_start_time: Instant,
|
entire_start_time: Instant,
|
||||||
@ -71,11 +68,11 @@ pub fn evaluate_repl(
|
|||||||
let mut entry_num = 0;
|
let mut entry_num = 0;
|
||||||
|
|
||||||
// Let's grab the shell_integration configs
|
// Let's grab the shell_integration configs
|
||||||
let shell_integration_osc2 = config.shell_integration_osc2;
|
let shell_integration_osc2 = config.shell_integration.osc2;
|
||||||
let shell_integration_osc7 = config.shell_integration_osc7;
|
let shell_integration_osc7 = config.shell_integration.osc7;
|
||||||
let shell_integration_osc9_9 = config.shell_integration_osc9_9;
|
let shell_integration_osc9_9 = config.shell_integration.osc9_9;
|
||||||
let shell_integration_osc133 = config.shell_integration_osc133;
|
let shell_integration_osc133 = config.shell_integration.osc133;
|
||||||
let shell_integration_osc633 = config.shell_integration_osc633;
|
let shell_integration_osc633 = config.shell_integration.osc633;
|
||||||
|
|
||||||
let nu_prompt = NushellPrompt::new(
|
let nu_prompt = NushellPrompt::new(
|
||||||
shell_integration_osc133,
|
shell_integration_osc133,
|
||||||
@ -87,7 +84,7 @@ pub fn evaluate_repl(
|
|||||||
let start_time = std::time::Instant::now();
|
let start_time = std::time::Instant::now();
|
||||||
// Translate environment variables from Strings to Values
|
// Translate environment variables from Strings to Values
|
||||||
if let Err(e) = convert_env_values(engine_state, &unique_stack) {
|
if let Err(e) = convert_env_values(engine_state, &unique_stack) {
|
||||||
report_error_new(engine_state, &e);
|
report_shell_error(engine_state, &e);
|
||||||
}
|
}
|
||||||
perf!("translate env vars", start_time, use_color);
|
perf!("translate env vars", start_time, use_color);
|
||||||
|
|
||||||
@ -97,9 +94,9 @@ pub fn evaluate_repl(
|
|||||||
Value::string("0823", Span::unknown()),
|
Value::string("0823", Span::unknown()),
|
||||||
);
|
);
|
||||||
|
|
||||||
unique_stack.add_env_var("LAST_EXIT_CODE".into(), Value::int(0, Span::unknown()));
|
unique_stack.set_last_exit_code(0, Span::unknown());
|
||||||
|
|
||||||
let mut line_editor = get_line_editor(engine_state, nushell_path, use_color)?;
|
let mut line_editor = get_line_editor(engine_state, use_color)?;
|
||||||
let temp_file = temp_dir().join(format!("{}.nu", uuid::Uuid::new_v4()));
|
let temp_file = temp_dir().join(format!("{}.nu", uuid::Uuid::new_v4()));
|
||||||
|
|
||||||
if let Some(s) = prerun_command {
|
if let Some(s) = prerun_command {
|
||||||
@ -111,8 +108,7 @@ pub fn evaluate_repl(
|
|||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
let cwd = get_guaranteed_cwd(engine_state, &unique_stack);
|
engine_state.merge_env(&mut unique_stack)?;
|
||||||
engine_state.merge_env(&mut unique_stack, cwd)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let hostname = System::host_name();
|
let hostname = System::host_name();
|
||||||
@ -131,7 +127,18 @@ pub fn evaluate_repl(
|
|||||||
run_shell_integration_osc9_9(engine_state, &mut unique_stack, use_color);
|
run_shell_integration_osc9_9(engine_state, &mut unique_stack, use_color);
|
||||||
}
|
}
|
||||||
if shell_integration_osc633 {
|
if shell_integration_osc633 {
|
||||||
run_shell_integration_osc633(engine_state, &mut unique_stack, use_color);
|
// escape a few things because this says so
|
||||||
|
// https://code.visualstudio.com/docs/terminal/shell-integration#_vs-code-custom-sequences-osc-633-st
|
||||||
|
let cmd_text = line_editor.current_buffer_contents().to_string();
|
||||||
|
|
||||||
|
let replaced_cmd_text = escape_special_vscode_bytes(&cmd_text)?;
|
||||||
|
|
||||||
|
run_shell_integration_osc633(
|
||||||
|
engine_state,
|
||||||
|
&mut unique_stack,
|
||||||
|
use_color,
|
||||||
|
replaced_cmd_text,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
engine_state.set_startup_time(entire_start_time.elapsed().as_nanos() as i64);
|
engine_state.set_startup_time(entire_start_time.elapsed().as_nanos() as i64);
|
||||||
@ -143,7 +150,7 @@ pub fn evaluate_repl(
|
|||||||
eval_source(
|
eval_source(
|
||||||
engine_state,
|
engine_state,
|
||||||
&mut unique_stack,
|
&mut unique_stack,
|
||||||
r#"use std banner; banner"#.as_bytes(),
|
r#"banner"#.as_bytes(),
|
||||||
"show_banner",
|
"show_banner",
|
||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
false,
|
false,
|
||||||
@ -200,7 +207,7 @@ pub fn evaluate_repl(
|
|||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// line_editor is lost in the error case so reconstruct a new one
|
// line_editor is lost in the error case so reconstruct a new one
|
||||||
line_editor = get_line_editor(engine_state, nushell_path, use_color)?;
|
line_editor = get_line_editor(engine_state, use_color)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,11 +215,44 @@ pub fn evaluate_repl(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_line_editor(
|
fn escape_special_vscode_bytes(input: &str) -> Result<String, ShellError> {
|
||||||
engine_state: &mut EngineState,
|
let bytes = input
|
||||||
nushell_path: &str,
|
.chars()
|
||||||
use_color: bool,
|
.flat_map(|c| {
|
||||||
) -> Result<Reedline> {
|
let mut buf = [0; 4]; // Buffer to hold UTF-8 bytes of the character
|
||||||
|
let c_bytes = c.encode_utf8(&mut buf); // Get UTF-8 bytes for the character
|
||||||
|
|
||||||
|
if c_bytes.len() == 1 {
|
||||||
|
let byte = c_bytes.as_bytes()[0];
|
||||||
|
|
||||||
|
match byte {
|
||||||
|
// Escape bytes below 0x20
|
||||||
|
b if b < 0x20 => format!("\\x{:02X}", byte).into_bytes(),
|
||||||
|
// Escape semicolon as \x3B
|
||||||
|
b';' => "\\x3B".to_string().into_bytes(),
|
||||||
|
// Escape backslash as \\
|
||||||
|
b'\\' => "\\\\".to_string().into_bytes(),
|
||||||
|
// Otherwise, return the character unchanged
|
||||||
|
_ => vec![byte],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// pass through multi-byte characters unchanged
|
||||||
|
c_bytes.bytes().collect()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
String::from_utf8(bytes).map_err(|err| ShellError::CantConvert {
|
||||||
|
to_type: "string".to_string(),
|
||||||
|
from_type: "bytes".to_string(),
|
||||||
|
span: Span::unknown(),
|
||||||
|
help: Some(format!(
|
||||||
|
"Error {err}, Unable to convert {input} to escaped bytes"
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_line_editor(engine_state: &mut EngineState, use_color: bool) -> Result<Reedline> {
|
||||||
let mut start_time = std::time::Instant::now();
|
let mut start_time = std::time::Instant::now();
|
||||||
let mut line_editor = Reedline::create();
|
let mut line_editor = Reedline::create();
|
||||||
|
|
||||||
@ -223,7 +263,7 @@ fn get_line_editor(
|
|||||||
if let Some(history) = engine_state.history_config() {
|
if let Some(history) = engine_state.history_config() {
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
|
|
||||||
line_editor = setup_history(nushell_path, engine_state, line_editor, history)?;
|
line_editor = setup_history(engine_state, line_editor, history)?;
|
||||||
|
|
||||||
perf!("setup history", start_time, use_color);
|
perf!("setup history", start_time, use_color);
|
||||||
}
|
}
|
||||||
@ -260,17 +300,15 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
hostname,
|
hostname,
|
||||||
} = ctx;
|
} = ctx;
|
||||||
|
|
||||||
let cwd = get_guaranteed_cwd(engine_state, &stack);
|
|
||||||
|
|
||||||
let mut start_time = std::time::Instant::now();
|
let mut start_time = std::time::Instant::now();
|
||||||
// Before doing anything, merge the environment from the previous REPL iteration into the
|
// Before doing anything, merge the environment from the previous REPL iteration into the
|
||||||
// permanent state.
|
// permanent state.
|
||||||
if let Err(err) = engine_state.merge_env(&mut stack, cwd) {
|
if let Err(err) = engine_state.merge_env(&mut stack) {
|
||||||
report_error_new(engine_state, &err);
|
report_shell_error(engine_state, &err);
|
||||||
}
|
}
|
||||||
// Check whether $env.NU_USE_IR is set, so that the user can change it in the REPL
|
// Check whether $env.NU_DISABLE_IR is set, so that the user can change it in the REPL
|
||||||
// Temporary while IR eval is optional
|
// Temporary while IR eval is optional
|
||||||
stack.use_ir = stack.has_env_var(engine_state, "NU_USE_IR");
|
stack.use_ir = !stack.has_env_var(engine_state, "NU_DISABLE_IR");
|
||||||
perf!("merge env", start_time, use_color);
|
perf!("merge env", start_time, use_color);
|
||||||
|
|
||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
@ -282,7 +320,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
// fire the "pre_prompt" hook
|
// fire the "pre_prompt" hook
|
||||||
if let Some(hook) = engine_state.get_config().hooks.pre_prompt.clone() {
|
if let Some(hook) = engine_state.get_config().hooks.pre_prompt.clone() {
|
||||||
if let Err(err) = eval_hook(engine_state, &mut stack, None, vec![], &hook, "pre_prompt") {
|
if let Err(err) = eval_hook(engine_state, &mut stack, None, vec![], &hook, "pre_prompt") {
|
||||||
report_error_new(engine_state, &err);
|
report_shell_error(engine_state, &err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
perf!("pre-prompt hook", start_time, use_color);
|
perf!("pre-prompt hook", start_time, use_color);
|
||||||
@ -292,7 +330,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
// fire the "env_change" hook
|
// fire the "env_change" hook
|
||||||
let env_change = engine_state.get_config().hooks.env_change.clone();
|
let env_change = engine_state.get_config().hooks.env_change.clone();
|
||||||
if let Err(error) = hook::eval_env_change_hook(env_change, engine_state, &mut stack) {
|
if let Err(error) = hook::eval_env_change_hook(env_change, engine_state, &mut stack) {
|
||||||
report_error_new(engine_state, &error)
|
report_shell_error(engine_state, &error)
|
||||||
}
|
}
|
||||||
perf!("env-change hook", start_time, use_color);
|
perf!("env-change hook", start_time, use_color);
|
||||||
|
|
||||||
@ -302,9 +340,9 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
start_time = std::time::Instant::now();
|
start_time = std::time::Instant::now();
|
||||||
// Find the configured cursor shapes for each mode
|
// Find the configured cursor shapes for each mode
|
||||||
let cursor_config = CursorConfig {
|
let cursor_config = CursorConfig {
|
||||||
vi_insert: map_nucursorshape_to_cursorshape(config.cursor_shape_vi_insert),
|
vi_insert: map_nucursorshape_to_cursorshape(config.cursor_shape.vi_insert),
|
||||||
vi_normal: map_nucursorshape_to_cursorshape(config.cursor_shape_vi_normal),
|
vi_normal: map_nucursorshape_to_cursorshape(config.cursor_shape.vi_normal),
|
||||||
emacs: map_nucursorshape_to_cursorshape(config.cursor_shape_emacs),
|
emacs: map_nucursorshape_to_cursorshape(config.cursor_shape.emacs),
|
||||||
};
|
};
|
||||||
perf!("get config/cursor config", start_time, use_color);
|
perf!("get config/cursor config", start_time, use_color);
|
||||||
|
|
||||||
@ -332,8 +370,8 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
// STACK-REFERENCE 2
|
// STACK-REFERENCE 2
|
||||||
stack_arc.clone(),
|
stack_arc.clone(),
|
||||||
)))
|
)))
|
||||||
.with_quick_completions(config.quick_completions)
|
.with_quick_completions(config.completions.quick)
|
||||||
.with_partial_completions(config.partial_completions)
|
.with_partial_completions(config.completions.partial)
|
||||||
.with_ansi_colors(config.use_ansi_coloring)
|
.with_ansi_colors(config.use_ansi_coloring)
|
||||||
.with_cwd(Some(
|
.with_cwd(Some(
|
||||||
engine_state
|
engine_state
|
||||||
@ -343,7 +381,11 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_string(),
|
.to_string(),
|
||||||
))
|
))
|
||||||
.with_cursor_config(cursor_config);
|
.with_cursor_config(cursor_config)
|
||||||
|
.with_visual_selection_style(nu_ansi_term::Style {
|
||||||
|
is_reverse: true,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
perf!("reedline builder", start_time, use_color);
|
perf!("reedline builder", start_time, use_color);
|
||||||
|
|
||||||
@ -366,7 +408,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
trace!("adding menus");
|
trace!("adding menus");
|
||||||
line_editor =
|
line_editor =
|
||||||
add_menus(line_editor, engine_reference, &stack_arc, config).unwrap_or_else(|e| {
|
add_menus(line_editor, engine_reference, &stack_arc, config).unwrap_or_else(|e| {
|
||||||
report_error_new(engine_state, &e);
|
report_shell_error(engine_state, &e);
|
||||||
Reedline::create()
|
Reedline::create()
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -437,12 +479,12 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
.with_completer(Box::<DefaultCompleter>::default());
|
.with_completer(Box::<DefaultCompleter>::default());
|
||||||
|
|
||||||
// Let's grab the shell_integration configs
|
// Let's grab the shell_integration configs
|
||||||
let shell_integration_osc2 = config.shell_integration_osc2;
|
let shell_integration_osc2 = config.shell_integration.osc2;
|
||||||
let shell_integration_osc7 = config.shell_integration_osc7;
|
let shell_integration_osc7 = config.shell_integration.osc7;
|
||||||
let shell_integration_osc9_9 = config.shell_integration_osc9_9;
|
let shell_integration_osc9_9 = config.shell_integration.osc9_9;
|
||||||
let shell_integration_osc133 = config.shell_integration_osc133;
|
let shell_integration_osc133 = config.shell_integration.osc133;
|
||||||
let shell_integration_osc633 = config.shell_integration_osc633;
|
let shell_integration_osc633 = config.shell_integration.osc633;
|
||||||
let shell_integration_reset_application_mode = config.shell_integration_reset_application_mode;
|
let shell_integration_reset_application_mode = config.shell_integration.reset_application_mode;
|
||||||
|
|
||||||
// TODO: we may clone the stack, this can lead to major performance issues
|
// TODO: we may clone the stack, this can lead to major performance issues
|
||||||
// so we should avoid it or making stack cheaper to clone.
|
// so we should avoid it or making stack cheaper to clone.
|
||||||
@ -452,14 +494,19 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
|
|
||||||
let line_editor_input_time = std::time::Instant::now();
|
let line_editor_input_time = std::time::Instant::now();
|
||||||
match input {
|
match input {
|
||||||
Ok(Signal::Success(s)) => {
|
Ok(Signal::Success(repl_cmd_line_text)) => {
|
||||||
let history_supports_meta = matches!(
|
let history_supports_meta = matches!(
|
||||||
engine_state.history_config().map(|h| h.file_format),
|
engine_state.history_config().map(|h| h.file_format),
|
||||||
Some(HistoryFileFormat::Sqlite)
|
Some(HistoryFileFormat::Sqlite)
|
||||||
);
|
);
|
||||||
|
|
||||||
if history_supports_meta {
|
if history_supports_meta {
|
||||||
prepare_history_metadata(&s, hostname, engine_state, &mut line_editor);
|
prepare_history_metadata(
|
||||||
|
&repl_cmd_line_text,
|
||||||
|
hostname,
|
||||||
|
engine_state,
|
||||||
|
&mut line_editor,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For pre_exec_hook
|
// For pre_exec_hook
|
||||||
@ -470,7 +517,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
if let Some(hook) = config.hooks.pre_execution.clone() {
|
if let Some(hook) = config.hooks.pre_execution.clone() {
|
||||||
// Set the REPL buffer to the current command for the "pre_execution" hook
|
// Set the REPL buffer to the current command for the "pre_execution" hook
|
||||||
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
|
||||||
repl.buffer = s.to_string();
|
repl.buffer = repl_cmd_line_text.to_string();
|
||||||
drop(repl);
|
drop(repl);
|
||||||
|
|
||||||
if let Err(err) = eval_hook(
|
if let Err(err) = eval_hook(
|
||||||
@ -481,7 +528,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
&hook,
|
&hook,
|
||||||
"pre_execution",
|
"pre_execution",
|
||||||
) {
|
) {
|
||||||
report_error_new(engine_state, &err);
|
report_shell_error(engine_state, &err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -493,8 +540,10 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
drop(repl);
|
drop(repl);
|
||||||
|
|
||||||
if shell_integration_osc633 {
|
if shell_integration_osc633 {
|
||||||
if stack.get_env_var(engine_state, "TERM_PROGRAM")
|
if stack
|
||||||
== Some(Value::test_string("vscode"))
|
.get_env_var(engine_state, "TERM_PROGRAM")
|
||||||
|
.and_then(|v| v.as_str().ok())
|
||||||
|
== Some("vscode")
|
||||||
{
|
{
|
||||||
start_time = Instant::now();
|
start_time = Instant::now();
|
||||||
|
|
||||||
@ -531,7 +580,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
// Actual command execution logic starts from here
|
// Actual command execution logic starts from here
|
||||||
let cmd_execution_start_time = Instant::now();
|
let cmd_execution_start_time = Instant::now();
|
||||||
|
|
||||||
match parse_operation(s.clone(), engine_state, &stack) {
|
match parse_operation(repl_cmd_line_text.clone(), engine_state, &stack) {
|
||||||
Ok(operation) => match operation {
|
Ok(operation) => match operation {
|
||||||
ReplOperation::AutoCd { cwd, target, span } => {
|
ReplOperation::AutoCd { cwd, target, span } => {
|
||||||
do_auto_cd(target, cwd, &mut stack, engine_state, span);
|
do_auto_cd(target, cwd, &mut stack, engine_state, span);
|
||||||
@ -577,7 +626,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
|
|
||||||
if history_supports_meta {
|
if history_supports_meta {
|
||||||
if let Err(e) = fill_in_result_related_history_metadata(
|
if let Err(e) = fill_in_result_related_history_metadata(
|
||||||
&s,
|
&repl_cmd_line_text,
|
||||||
engine_state,
|
engine_state,
|
||||||
cmd_duration,
|
cmd_duration,
|
||||||
&mut stack,
|
&mut stack,
|
||||||
@ -597,7 +646,12 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||||||
run_shell_integration_osc9_9(engine_state, &mut stack, use_color);
|
run_shell_integration_osc9_9(engine_state, &mut stack, use_color);
|
||||||
}
|
}
|
||||||
if shell_integration_osc633 {
|
if shell_integration_osc633 {
|
||||||
run_shell_integration_osc633(engine_state, &mut stack, use_color);
|
run_shell_integration_osc633(
|
||||||
|
engine_state,
|
||||||
|
&mut stack,
|
||||||
|
use_color,
|
||||||
|
repl_cmd_line_text,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if shell_integration_reset_application_mode {
|
if shell_integration_reset_application_mode {
|
||||||
run_shell_integration_reset_application_mode();
|
run_shell_integration_reset_application_mode();
|
||||||
@ -706,7 +760,7 @@ fn fill_in_result_related_history_metadata(
|
|||||||
c.duration = Some(cmd_duration);
|
c.duration = Some(cmd_duration);
|
||||||
c.exit_status = stack
|
c.exit_status = stack
|
||||||
.get_env_var(engine_state, "LAST_EXIT_CODE")
|
.get_env_var(engine_state, "LAST_EXIT_CODE")
|
||||||
.and_then(|e| e.as_i64().ok());
|
.and_then(|e| e.as_int().ok());
|
||||||
c
|
c
|
||||||
})
|
})
|
||||||
.into_diagnostic()?; // todo: don't stop repl if error here?
|
.into_diagnostic()?; // todo: don't stop repl if error here?
|
||||||
@ -778,7 +832,7 @@ fn do_auto_cd(
|
|||||||
) {
|
) {
|
||||||
let path = {
|
let path = {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
report_error_new(
|
report_shell_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
&ShellError::DirectoryNotFound {
|
&ShellError::DirectoryNotFound {
|
||||||
dir: path.to_string_lossy().to_string(),
|
dir: path.to_string_lossy().to_string(),
|
||||||
@ -790,7 +844,7 @@ fn do_auto_cd(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let PermissionResult::PermissionDenied(reason) = have_permission(path.clone()) {
|
if let PermissionResult::PermissionDenied(reason) = have_permission(path.clone()) {
|
||||||
report_error_new(
|
report_shell_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
&ShellError::IOError {
|
&ShellError::IOError {
|
||||||
msg: format!("Cannot change directory to {path}: {reason}"),
|
msg: format!("Cannot change directory to {path}: {reason}"),
|
||||||
@ -804,14 +858,14 @@ fn do_auto_cd(
|
|||||||
//FIXME: this only changes the current scope, but instead this environment variable
|
//FIXME: this only changes the current scope, but instead this environment variable
|
||||||
//should probably be a block that loads the information from the state in the overlay
|
//should probably be a block that loads the information from the state in the overlay
|
||||||
if let Err(err) = stack.set_cwd(&path) {
|
if let Err(err) = stack.set_cwd(&path) {
|
||||||
report_error_new(engine_state, &err);
|
report_shell_error(engine_state, &err);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let cwd = Value::string(cwd, span);
|
let cwd = Value::string(cwd, span);
|
||||||
|
|
||||||
let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS");
|
let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS");
|
||||||
let mut shells = if let Some(v) = shells {
|
let mut shells = if let Some(v) = shells {
|
||||||
v.into_list().unwrap_or_else(|_| vec![cwd])
|
v.clone().into_list().unwrap_or_else(|_| vec![cwd])
|
||||||
} else {
|
} else {
|
||||||
vec![cwd]
|
vec![cwd]
|
||||||
};
|
};
|
||||||
@ -837,7 +891,7 @@ fn do_auto_cd(
|
|||||||
"NUSHELL_LAST_SHELL".into(),
|
"NUSHELL_LAST_SHELL".into(),
|
||||||
Value::int(last_shell as i64, span),
|
Value::int(last_shell as i64, span),
|
||||||
);
|
);
|
||||||
stack.add_env_var("LAST_EXIT_CODE".into(), Value::int(0, Span::unknown()));
|
stack.set_last_exit_code(0, Span::unknown());
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -911,7 +965,12 @@ fn run_shell_integration_osc2(
|
|||||||
|
|
||||||
// Try to abbreviate string for windows title
|
// Try to abbreviate string for windows title
|
||||||
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
||||||
path.replace(&p.as_path().display().to_string(), "~")
|
let home_dir_str = p.as_path().display().to_string();
|
||||||
|
if path.starts_with(&home_dir_str) {
|
||||||
|
path.replacen(&home_dir_str, "~", 1)
|
||||||
|
} else {
|
||||||
|
path
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
path
|
path
|
||||||
};
|
};
|
||||||
@ -988,12 +1047,21 @@ fn run_shell_integration_osc9_9(engine_state: &EngineState, stack: &mut Stack, u
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_shell_integration_osc633(engine_state: &EngineState, stack: &mut Stack, use_color: bool) {
|
fn run_shell_integration_osc633(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
use_color: bool,
|
||||||
|
repl_cmd_line_text: String,
|
||||||
|
) {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
if let Ok(path) = current_dir_str(engine_state, 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.get_env_var(engine_state, "TERM_PROGRAM") == Some(Value::test_string("vscode")) {
|
if stack
|
||||||
|
.get_env_var(engine_state, "TERM_PROGRAM")
|
||||||
|
.and_then(|v| v.as_str().ok())
|
||||||
|
== Some("vscode")
|
||||||
|
{
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
|
|
||||||
// If we're in vscode, run their specific ansi escape sequence.
|
// If we're in vscode, run their specific ansi escape sequence.
|
||||||
@ -1008,6 +1076,19 @@ fn run_shell_integration_osc633(engine_state: &EngineState, stack: &mut Stack, u
|
|||||||
start_time,
|
start_time,
|
||||||
use_color
|
use_color
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// escape a few things because this says so
|
||||||
|
// https://code.visualstudio.com/docs/terminal/shell-integration#_vs-code-custom-sequences-osc-633-st
|
||||||
|
let replaced_cmd_text =
|
||||||
|
escape_special_vscode_bytes(&repl_cmd_line_text).unwrap_or(repl_cmd_line_text);
|
||||||
|
|
||||||
|
//OSC 633 ; E ; <commandline> [; <nonce] ST - Explicitly set the command line with an optional nonce.
|
||||||
|
run_ansi_sequence(&format!(
|
||||||
|
"{}{}{}",
|
||||||
|
VSCODE_COMMANDLINE_MARKER_PREFIX,
|
||||||
|
replaced_cmd_text,
|
||||||
|
VSCODE_COMMANDLINE_MARKER_SUFFIX
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1037,7 +1118,6 @@ fn flush_engine_state_repl_buffer(engine_state: &mut EngineState, line_editor: &
|
|||||||
/// Setup history management for Reedline
|
/// Setup history management for Reedline
|
||||||
///
|
///
|
||||||
fn setup_history(
|
fn setup_history(
|
||||||
nushell_path: &str,
|
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
line_editor: Reedline,
|
line_editor: Reedline,
|
||||||
history: HistoryConfig,
|
history: HistoryConfig,
|
||||||
@ -1049,7 +1129,7 @@ fn setup_history(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(path) = crate::config_files::get_history_path(nushell_path, history.file_format) {
|
if let Some(path) = history.file_path() {
|
||||||
return update_line_editor_history(
|
return update_line_editor_history(
|
||||||
engine_state,
|
engine_state,
|
||||||
path,
|
path,
|
||||||
@ -1080,7 +1160,7 @@ fn setup_keybindings(engine_state: &EngineState, line_editor: Reedline) -> Reedl
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
report_error_new(engine_state, &e);
|
report_shell_error(engine_state, &e);
|
||||||
line_editor
|
line_editor
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1112,7 +1192,7 @@ fn update_line_editor_history(
|
|||||||
history_session_id: Option<HistorySessionId>,
|
history_session_id: Option<HistorySessionId>,
|
||||||
) -> Result<Reedline, ErrReport> {
|
) -> Result<Reedline, ErrReport> {
|
||||||
let history: Box<dyn reedline::History> = match history.file_format {
|
let history: Box<dyn reedline::History> = match history.file_format {
|
||||||
HistoryFileFormat::PlainText => Box::new(
|
HistoryFileFormat::Plaintext => Box::new(
|
||||||
FileBackedHistory::with_file(history.max_size as usize, history_path)
|
FileBackedHistory::with_file(history.max_size as usize, history_path)
|
||||||
.into_diagnostic()?,
|
.into_diagnostic()?,
|
||||||
),
|
),
|
||||||
@ -1150,10 +1230,10 @@ fn confirm_stdin_is_terminal() -> Result<()> {
|
|||||||
fn map_nucursorshape_to_cursorshape(shape: NuCursorShape) -> Option<SetCursorStyle> {
|
fn map_nucursorshape_to_cursorshape(shape: NuCursorShape) -> Option<SetCursorStyle> {
|
||||||
match shape {
|
match shape {
|
||||||
NuCursorShape::Block => Some(SetCursorStyle::SteadyBlock),
|
NuCursorShape::Block => Some(SetCursorStyle::SteadyBlock),
|
||||||
NuCursorShape::UnderScore => Some(SetCursorStyle::SteadyUnderScore),
|
NuCursorShape::Underscore => Some(SetCursorStyle::SteadyUnderScore),
|
||||||
NuCursorShape::Line => Some(SetCursorStyle::SteadyBar),
|
NuCursorShape::Line => Some(SetCursorStyle::SteadyBar),
|
||||||
NuCursorShape::BlinkBlock => Some(SetCursorStyle::BlinkingBlock),
|
NuCursorShape::BlinkBlock => Some(SetCursorStyle::BlinkingBlock),
|
||||||
NuCursorShape::BlinkUnderScore => Some(SetCursorStyle::BlinkingUnderScore),
|
NuCursorShape::BlinkUnderscore => Some(SetCursorStyle::BlinkingUnderScore),
|
||||||
NuCursorShape::BlinkLine => Some(SetCursorStyle::BlinkingBar),
|
NuCursorShape::BlinkLine => Some(SetCursorStyle::BlinkingBar),
|
||||||
NuCursorShape::Inherit => None,
|
NuCursorShape::Inherit => None,
|
||||||
}
|
}
|
||||||
@ -1167,10 +1247,14 @@ fn get_command_finished_marker(
|
|||||||
) -> String {
|
) -> String {
|
||||||
let exit_code = stack
|
let exit_code = stack
|
||||||
.get_env_var(engine_state, "LAST_EXIT_CODE")
|
.get_env_var(engine_state, "LAST_EXIT_CODE")
|
||||||
.and_then(|e| e.as_i64().ok());
|
.and_then(|e| e.as_int().ok());
|
||||||
|
|
||||||
if shell_integration_osc633 {
|
if shell_integration_osc633 {
|
||||||
if stack.get_env_var(engine_state, "TERM_PROGRAM") == Some(Value::test_string("vscode")) {
|
if stack
|
||||||
|
.get_env_var(engine_state, "TERM_PROGRAM")
|
||||||
|
.and_then(|v| v.as_str().ok())
|
||||||
|
== Some("vscode")
|
||||||
|
{
|
||||||
// We're in vscode and we have osc633 enabled
|
// We're in vscode and we have osc633 enabled
|
||||||
format!(
|
format!(
|
||||||
"{}{}{}",
|
"{}{}{}",
|
||||||
@ -1219,7 +1303,11 @@ fn run_finaliziation_ansi_sequence(
|
|||||||
) {
|
) {
|
||||||
if shell_integration_osc633 {
|
if shell_integration_osc633 {
|
||||||
// Only run osc633 if we are in vscode
|
// Only run osc633 if we are in vscode
|
||||||
if stack.get_env_var(engine_state, "TERM_PROGRAM") == Some(Value::test_string("vscode")) {
|
if stack
|
||||||
|
.get_env_var(engine_state, "TERM_PROGRAM")
|
||||||
|
.and_then(|v| v.as_str().ok())
|
||||||
|
== Some("vscode")
|
||||||
|
{
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
|
|
||||||
run_ansi_sequence(&get_command_finished_marker(
|
run_ansi_sequence(&get_command_finished_marker(
|
||||||
@ -1270,10 +1358,9 @@ fn run_finaliziation_ansi_sequence(
|
|||||||
|
|
||||||
// Absolute paths with a drive letter, like 'C:', 'D:\', 'E:\foo'
|
// Absolute paths with a drive letter, like 'C:', 'D:\', 'E:\foo'
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
static DRIVE_PATH_REGEX: once_cell::sync::Lazy<fancy_regex::Regex> =
|
static DRIVE_PATH_REGEX: std::sync::LazyLock<fancy_regex::Regex> = std::sync::LazyLock::new(|| {
|
||||||
once_cell::sync::Lazy::new(|| {
|
fancy_regex::Regex::new(r"^[a-zA-Z]:[/\\]?").expect("Internal error: regex creation")
|
||||||
fancy_regex::Regex::new(r"^[a-zA-Z]:[/\\]?").expect("Internal error: regex creation")
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// A best-effort "does this string look kinda like a path?" function to determine whether to auto-cd
|
// A best-effort "does this string look kinda like a path?" function to determine whether to auto-cd
|
||||||
fn looks_like_path(orig: &str) -> bool {
|
fn looks_like_path(orig: &str) -> bool {
|
||||||
@ -1317,8 +1404,7 @@ fn trailing_slash_looks_like_path() {
|
|||||||
fn are_session_ids_in_sync() {
|
fn are_session_ids_in_sync() {
|
||||||
let engine_state = &mut EngineState::new();
|
let engine_state = &mut EngineState::new();
|
||||||
let history = engine_state.history_config().unwrap();
|
let history = engine_state.history_config().unwrap();
|
||||||
let history_path =
|
let history_path = history.file_path().unwrap();
|
||||||
crate::config_files::get_history_path("nushell", history.file_format).unwrap();
|
|
||||||
let line_editor = reedline::Reedline::create();
|
let line_editor = reedline::Reedline::create();
|
||||||
let history_session_id = reedline::Reedline::create_history_session_id();
|
let history_session_id = reedline::Reedline::create_history_session_id();
|
||||||
let line_editor = update_line_editor_history(
|
let line_editor = update_line_editor_history(
|
||||||
@ -1336,21 +1422,27 @@ fn are_session_ids_in_sync() {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_auto_cd {
|
mod test_auto_cd {
|
||||||
use super::{do_auto_cd, parse_operation, ReplOperation};
|
use super::{do_auto_cd, escape_special_vscode_bytes, parse_operation, ReplOperation};
|
||||||
|
use nu_path::AbsolutePath;
|
||||||
use nu_protocol::engine::{EngineState, Stack};
|
use nu_protocol::engine::{EngineState, Stack};
|
||||||
use std::path::Path;
|
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
/// Create a symlink. Works on both Unix and Windows.
|
/// Create a symlink. Works on both Unix and Windows.
|
||||||
#[cfg(any(unix, windows))]
|
#[cfg(any(unix, windows))]
|
||||||
fn symlink(original: impl AsRef<Path>, link: impl AsRef<Path>) -> std::io::Result<()> {
|
fn symlink(
|
||||||
|
original: impl AsRef<AbsolutePath>,
|
||||||
|
link: impl AsRef<AbsolutePath>,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
let original = original.as_ref();
|
||||||
|
let link = link.as_ref();
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
std::os::unix::fs::symlink(original, link)
|
std::os::unix::fs::symlink(original, link)
|
||||||
}
|
}
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
if original.as_ref().is_dir() {
|
if original.is_dir() {
|
||||||
std::os::windows::fs::symlink_dir(original, link)
|
std::os::windows::fs::symlink_dir(original, link)
|
||||||
} else {
|
} else {
|
||||||
std::os::windows::fs::symlink_file(original, link)
|
std::os::windows::fs::symlink_file(original, link)
|
||||||
@ -1362,11 +1454,11 @@ mod test_auto_cd {
|
|||||||
/// `before`, and after `input` is parsed and evaluated, PWD should be
|
/// `before`, and after `input` is parsed and evaluated, PWD should be
|
||||||
/// changed to `after`.
|
/// changed to `after`.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn check(before: impl AsRef<Path>, input: &str, after: impl AsRef<Path>) {
|
fn check(before: impl AsRef<AbsolutePath>, input: &str, after: impl AsRef<AbsolutePath>) {
|
||||||
// Setup EngineState and Stack.
|
// Setup EngineState and Stack.
|
||||||
let mut engine_state = EngineState::new();
|
let mut engine_state = EngineState::new();
|
||||||
let mut stack = Stack::new();
|
let mut stack = Stack::new();
|
||||||
stack.set_cwd(before).unwrap();
|
stack.set_cwd(before.as_ref()).unwrap();
|
||||||
|
|
||||||
// Parse the input. It must be an auto-cd operation.
|
// Parse the input. It must be an auto-cd operation.
|
||||||
let op = parse_operation(input.to_string(), &engine_state, &stack).unwrap();
|
let op = parse_operation(input.to_string(), &engine_state, &stack).unwrap();
|
||||||
@ -1382,54 +1474,66 @@ mod test_auto_cd {
|
|||||||
// don't have to be byte-wise equal (on Windows, the 8.3 filename
|
// don't have to be byte-wise equal (on Windows, the 8.3 filename
|
||||||
// conversion messes things up),
|
// conversion messes things up),
|
||||||
let updated_cwd = std::fs::canonicalize(updated_cwd).unwrap();
|
let updated_cwd = std::fs::canonicalize(updated_cwd).unwrap();
|
||||||
let after = std::fs::canonicalize(after).unwrap();
|
let after = std::fs::canonicalize(after.as_ref()).unwrap();
|
||||||
assert_eq!(updated_cwd, after);
|
assert_eq!(updated_cwd, after);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn auto_cd_root() {
|
fn auto_cd_root() {
|
||||||
let tempdir = tempdir().unwrap();
|
let tempdir = tempdir().unwrap();
|
||||||
let root = if cfg!(windows) { r"C:\" } else { "/" };
|
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||||
check(&tempdir, root, root);
|
|
||||||
|
let input = if cfg!(windows) { r"C:\" } else { "/" };
|
||||||
|
let root = AbsolutePath::try_new(input).unwrap();
|
||||||
|
check(tempdir, input, root);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn auto_cd_tilde() {
|
fn auto_cd_tilde() {
|
||||||
let tempdir = tempdir().unwrap();
|
let tempdir = tempdir().unwrap();
|
||||||
|
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||||
|
|
||||||
let home = nu_path::home_dir().unwrap();
|
let home = nu_path::home_dir().unwrap();
|
||||||
check(&tempdir, "~", home);
|
check(tempdir, "~", home);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn auto_cd_dot() {
|
fn auto_cd_dot() {
|
||||||
let tempdir = tempdir().unwrap();
|
let tempdir = tempdir().unwrap();
|
||||||
check(&tempdir, ".", &tempdir);
|
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||||
|
|
||||||
|
check(tempdir, ".", tempdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn auto_cd_double_dot() {
|
fn auto_cd_double_dot() {
|
||||||
let tempdir = tempdir().unwrap();
|
let tempdir = tempdir().unwrap();
|
||||||
let dir = tempdir.path().join("foo");
|
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||||
|
|
||||||
|
let dir = tempdir.join("foo");
|
||||||
std::fs::create_dir_all(&dir).unwrap();
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
check(dir, "..", &tempdir);
|
check(dir, "..", tempdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn auto_cd_triple_dot() {
|
fn auto_cd_triple_dot() {
|
||||||
let tempdir = tempdir().unwrap();
|
let tempdir = tempdir().unwrap();
|
||||||
let dir = tempdir.path().join("foo").join("bar");
|
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||||
|
|
||||||
|
let dir = tempdir.join("foo").join("bar");
|
||||||
std::fs::create_dir_all(&dir).unwrap();
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
check(dir, "...", &tempdir);
|
check(dir, "...", tempdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn auto_cd_relative() {
|
fn auto_cd_relative() {
|
||||||
let tempdir = tempdir().unwrap();
|
let tempdir = tempdir().unwrap();
|
||||||
let foo = tempdir.path().join("foo");
|
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||||
let bar = tempdir.path().join("bar");
|
|
||||||
|
let foo = tempdir.join("foo");
|
||||||
|
let bar = tempdir.join("bar");
|
||||||
std::fs::create_dir_all(&foo).unwrap();
|
std::fs::create_dir_all(&foo).unwrap();
|
||||||
std::fs::create_dir_all(&bar).unwrap();
|
std::fs::create_dir_all(&bar).unwrap();
|
||||||
|
|
||||||
let input = if cfg!(windows) { r"..\bar" } else { "../bar" };
|
let input = if cfg!(windows) { r"..\bar" } else { "../bar" };
|
||||||
check(foo, input, bar);
|
check(foo, input, bar);
|
||||||
}
|
}
|
||||||
@ -1437,32 +1541,74 @@ mod test_auto_cd {
|
|||||||
#[test]
|
#[test]
|
||||||
fn auto_cd_trailing_slash() {
|
fn auto_cd_trailing_slash() {
|
||||||
let tempdir = tempdir().unwrap();
|
let tempdir = tempdir().unwrap();
|
||||||
let dir = tempdir.path().join("foo");
|
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||||
std::fs::create_dir_all(&dir).unwrap();
|
|
||||||
|
|
||||||
|
let dir = tempdir.join("foo");
|
||||||
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
let input = if cfg!(windows) { r"foo\" } else { "foo/" };
|
let input = if cfg!(windows) { r"foo\" } else { "foo/" };
|
||||||
check(&tempdir, input, dir);
|
check(tempdir, input, dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn auto_cd_symlink() {
|
fn auto_cd_symlink() {
|
||||||
let tempdir = tempdir().unwrap();
|
let tempdir = tempdir().unwrap();
|
||||||
let dir = tempdir.path().join("foo");
|
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||||
std::fs::create_dir_all(&dir).unwrap();
|
|
||||||
let link = tempdir.path().join("link");
|
|
||||||
symlink(&dir, &link).unwrap();
|
|
||||||
|
|
||||||
|
let dir = tempdir.join("foo");
|
||||||
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
|
let link = tempdir.join("link");
|
||||||
|
symlink(&dir, &link).unwrap();
|
||||||
let input = if cfg!(windows) { r".\link" } else { "./link" };
|
let input = if cfg!(windows) { r".\link" } else { "./link" };
|
||||||
check(&tempdir, input, link);
|
check(tempdir, input, link);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "was not parsed into an auto-cd operation")]
|
#[should_panic(expected = "was not parsed into an auto-cd operation")]
|
||||||
fn auto_cd_nonexistent_directory() {
|
fn auto_cd_nonexistent_directory() {
|
||||||
let tempdir = tempdir().unwrap();
|
let tempdir = tempdir().unwrap();
|
||||||
let dir = tempdir.path().join("foo");
|
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||||
|
|
||||||
|
let dir = tempdir.join("foo");
|
||||||
let input = if cfg!(windows) { r"foo\" } else { "foo/" };
|
let input = if cfg!(windows) { r"foo\" } else { "foo/" };
|
||||||
check(&tempdir, input, dir);
|
check(tempdir, input, dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn escape_vscode_semicolon_test() {
|
||||||
|
let input = r#"now;is"#;
|
||||||
|
let expected = r#"now\x3Bis"#;
|
||||||
|
let actual = escape_special_vscode_bytes(input).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn escape_vscode_backslash_test() {
|
||||||
|
let input = r#"now\is"#;
|
||||||
|
let expected = r#"now\\is"#;
|
||||||
|
let actual = escape_special_vscode_bytes(input).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn escape_vscode_linefeed_test() {
|
||||||
|
let input = "now\nis";
|
||||||
|
let expected = r#"now\x0Ais"#;
|
||||||
|
let actual = escape_special_vscode_bytes(input).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn escape_vscode_tab_null_cr_test() {
|
||||||
|
let input = "now\t\0\ris";
|
||||||
|
let expected = r#"now\x09\x00\x0Dis"#;
|
||||||
|
let actual = escape_special_vscode_bytes(input).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn escape_vscode_multibyte_ok() {
|
||||||
|
let input = "now🍪is";
|
||||||
|
let actual = escape_special_vscode_bytes(input).unwrap();
|
||||||
|
assert_eq!(input, actual);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
use nu_cmd_base::hook::eval_hook;
|
use nu_cmd_base::hook::eval_hook;
|
||||||
use nu_engine::{eval_block, eval_block_with_early_return};
|
use nu_engine::{eval_block, eval_block_with_early_return};
|
||||||
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
|
use nu_parser::{lex, parse, unescape_unquote_string, Token, TokenContents};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
|
cli_error::report_compile_error,
|
||||||
debugger::WithoutDebug,
|
debugger::WithoutDebug,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
report_error, report_error_new, PipelineData, ShellError, Span, Value,
|
report_parse_error, report_parse_warning, report_shell_error, PipelineData, ShellError, Span,
|
||||||
|
Value,
|
||||||
};
|
};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use nu_utils::enable_vt_processing;
|
use nu_utils::enable_vt_processing;
|
||||||
use nu_utils::perf;
|
use nu_utils::{escape_quote_string, perf};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
// This will collect environment variables from std::env and adds them to a stack.
|
// This will collect environment variables from std::env and adds them to a stack.
|
||||||
@ -39,7 +41,7 @@ fn gather_env_vars(
|
|||||||
init_cwd: &Path,
|
init_cwd: &Path,
|
||||||
) {
|
) {
|
||||||
fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) {
|
fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) {
|
||||||
report_error_new(
|
report_shell_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
&ShellError::GenericError {
|
&ShellError::GenericError {
|
||||||
error: format!("Environment variable was not captured: {env_str}"),
|
error: format!("Environment variable was not captured: {env_str}"),
|
||||||
@ -70,7 +72,7 @@ fn gather_env_vars(
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Could not capture current working directory
|
// Could not capture current working directory
|
||||||
report_error_new(
|
report_shell_error(
|
||||||
engine_state,
|
engine_state,
|
||||||
&ShellError::GenericError {
|
&ShellError::GenericError {
|
||||||
error: "Current directory is not a valid utf-8 path".into(),
|
error: "Current directory is not a valid utf-8 path".into(),
|
||||||
@ -210,18 +212,19 @@ pub fn eval_source(
|
|||||||
let start_time = std::time::Instant::now();
|
let start_time = std::time::Instant::now();
|
||||||
|
|
||||||
let exit_code = match evaluate_source(engine_state, stack, source, fname, input, allow_return) {
|
let exit_code = match evaluate_source(engine_state, stack, source, fname, input, allow_return) {
|
||||||
Ok(code) => code.unwrap_or(0),
|
Ok(failed) => {
|
||||||
|
let code = failed.into();
|
||||||
|
stack.set_last_exit_code(code, Span::unknown());
|
||||||
|
code
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
report_error_new(engine_state, &err);
|
report_shell_error(engine_state, &err);
|
||||||
1
|
let code = err.exit_code();
|
||||||
|
stack.set_last_error(&err);
|
||||||
|
code.unwrap_or(0)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
stack.add_env_var(
|
|
||||||
"LAST_EXIT_CODE".to_string(),
|
|
||||||
Value::int(exit_code.into(), Span::unknown()),
|
|
||||||
);
|
|
||||||
|
|
||||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
@ -244,7 +247,7 @@ fn evaluate_source(
|
|||||||
fname: &str,
|
fname: &str,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
allow_return: bool,
|
allow_return: bool,
|
||||||
) -> Result<Option<i32>, ShellError> {
|
) -> Result<bool, ShellError> {
|
||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
let output = parse(
|
let output = parse(
|
||||||
@ -254,16 +257,16 @@ fn evaluate_source(
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
if let Some(warning) = working_set.parse_warnings.first() {
|
if let Some(warning) = working_set.parse_warnings.first() {
|
||||||
report_error(&working_set, warning);
|
report_parse_warning(&working_set, warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(err) = working_set.parse_errors.first() {
|
if let Some(err) = working_set.parse_errors.first() {
|
||||||
report_error(&working_set, err);
|
report_parse_error(&working_set, err);
|
||||||
return Ok(Some(1));
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(err) = working_set.compile_errors.first() {
|
if let Some(err) = working_set.compile_errors.first() {
|
||||||
report_error(&working_set, err);
|
report_compile_error(&working_set, err);
|
||||||
// Not a fatal error, for now
|
// Not a fatal error, for now
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,27 +281,36 @@ fn evaluate_source(
|
|||||||
eval_block::<WithoutDebug>(engine_state, stack, &block, input)
|
eval_block::<WithoutDebug>(engine_state, stack, &block, input)
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
let status = if let PipelineData::ByteStream(..) = pipeline {
|
if let PipelineData::ByteStream(..) = pipeline {
|
||||||
pipeline.print(engine_state, stack, false, false)?
|
// run the display hook on bytestreams too
|
||||||
|
run_display_hook(engine_state, stack, pipeline, false)
|
||||||
} else {
|
} else {
|
||||||
if let Some(hook) = engine_state.get_config().hooks.display_output.clone() {
|
run_display_hook(engine_state, stack, pipeline, true)
|
||||||
let pipeline = eval_hook(
|
}?;
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
Some(pipeline),
|
|
||||||
vec![],
|
|
||||||
&hook,
|
|
||||||
"display_output",
|
|
||||||
)?;
|
|
||||||
pipeline.print(engine_state, stack, false, false)
|
|
||||||
} else {
|
|
||||||
pipeline.print(engine_state, stack, true, false)
|
|
||||||
}?
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(status.map(|status| status.code()))
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_display_hook(
|
||||||
|
engine_state: &mut EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
pipeline: PipelineData,
|
||||||
|
no_newline: bool,
|
||||||
|
) -> Result<(), ShellError> {
|
||||||
|
if let Some(hook) = engine_state.get_config().hooks.display_output.clone() {
|
||||||
|
let pipeline = eval_hook(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
Some(pipeline),
|
||||||
|
vec![],
|
||||||
|
&hook,
|
||||||
|
"display_output",
|
||||||
|
)?;
|
||||||
|
pipeline.print(engine_state, stack, no_newline, false)
|
||||||
|
} else {
|
||||||
|
pipeline.print(engine_state, stack, no_newline, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -321,16 +333,10 @@ mod test {
|
|||||||
|
|
||||||
let env = engine_state.render_env_vars();
|
let env = engine_state.render_env_vars();
|
||||||
|
|
||||||
assert!(
|
assert!(matches!(env.get("FOO"), Some(&Value::String { val, .. }) if val == "foo"));
|
||||||
matches!(env.get(&"FOO".to_string()), Some(&Value::String { val, .. }) if val == "foo")
|
assert!(matches!(env.get("SYMBOLS"), Some(&Value::String { val, .. }) if val == symbols));
|
||||||
);
|
assert!(matches!(env.get(symbols), Some(&Value::String { val, .. }) if val == "symbols"));
|
||||||
assert!(
|
assert!(env.contains_key("PWD"));
|
||||||
matches!(env.get(&"SYMBOLS".to_string()), Some(&Value::String { val, .. }) if val == symbols)
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
matches!(env.get(&symbols.to_string()), Some(&Value::String { val, .. }) if val == "symbols")
|
|
||||||
);
|
|
||||||
assert!(env.get(&"PWD".to_string()).is_some());
|
|
||||||
assert_eq!(env.len(), 4);
|
assert_eq!(env.len(), 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
296
crates/nu-cli/tests/commands/history_import.rs
Normal file
296
crates/nu-cli/tests/commands/history_import.rs
Normal file
@ -0,0 +1,296 @@
|
|||||||
|
use nu_protocol::HistoryFileFormat;
|
||||||
|
use nu_test_support::{nu, Outcome};
|
||||||
|
use reedline::{
|
||||||
|
FileBackedHistory, History, HistoryItem, HistoryItemId, ReedlineError, SearchQuery,
|
||||||
|
SqliteBackedHistory,
|
||||||
|
};
|
||||||
|
use rstest::rstest;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
struct Test {
|
||||||
|
cfg_dir: TempDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Test {
|
||||||
|
fn new(history_format: &'static str) -> Self {
|
||||||
|
let cfg_dir = tempfile::Builder::new()
|
||||||
|
.prefix("history_import_test")
|
||||||
|
.tempdir()
|
||||||
|
.unwrap();
|
||||||
|
// Assigning to $env.config.history.file_format seems to work only in startup
|
||||||
|
// configuration.
|
||||||
|
std::fs::write(
|
||||||
|
cfg_dir.path().join("env.nu"),
|
||||||
|
format!("$env.config.history.file_format = {history_format:?}"),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
Self { cfg_dir }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nu(&self, cmd: impl AsRef<str>) -> Outcome {
|
||||||
|
let env = [(
|
||||||
|
"XDG_CONFIG_HOME".to_string(),
|
||||||
|
self.cfg_dir.path().to_str().unwrap().to_string(),
|
||||||
|
)];
|
||||||
|
let env_config = self.cfg_dir.path().join("env.nu");
|
||||||
|
nu!(envs: env, env_config: env_config, cmd.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_plaintext(&self) -> Result<FileBackedHistory, ReedlineError> {
|
||||||
|
FileBackedHistory::with_file(
|
||||||
|
100,
|
||||||
|
self.cfg_dir
|
||||||
|
.path()
|
||||||
|
.join("nushell")
|
||||||
|
.join(HistoryFileFormat::Plaintext.default_file_name()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_sqlite(&self) -> Result<SqliteBackedHistory, ReedlineError> {
|
||||||
|
SqliteBackedHistory::with_file(
|
||||||
|
self.cfg_dir
|
||||||
|
.path()
|
||||||
|
.join("nushell")
|
||||||
|
.join(HistoryFileFormat::Sqlite.default_file_name()),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_backend(&self, format: HistoryFileFormat) -> Result<Box<dyn History>, ReedlineError> {
|
||||||
|
fn boxed(be: impl History + 'static) -> Box<dyn History> {
|
||||||
|
Box::new(be)
|
||||||
|
}
|
||||||
|
use HistoryFileFormat::*;
|
||||||
|
match format {
|
||||||
|
Plaintext => self.open_plaintext().map(boxed),
|
||||||
|
Sqlite => self.open_sqlite().map(boxed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum HistorySource {
|
||||||
|
Vec(Vec<HistoryItem>),
|
||||||
|
Command(&'static str),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestCase {
|
||||||
|
dst_format: HistoryFileFormat,
|
||||||
|
dst_history: Vec<HistoryItem>,
|
||||||
|
src_history: HistorySource,
|
||||||
|
want_history: Vec<HistoryItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const EMPTY_TEST_CASE: TestCase = TestCase {
|
||||||
|
dst_format: HistoryFileFormat::Plaintext,
|
||||||
|
dst_history: Vec::new(),
|
||||||
|
src_history: HistorySource::Vec(Vec::new()),
|
||||||
|
want_history: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
impl TestCase {
|
||||||
|
fn run(self) {
|
||||||
|
use HistoryFileFormat::*;
|
||||||
|
let test = Test::new(match self.dst_format {
|
||||||
|
Plaintext => "plaintext",
|
||||||
|
Sqlite => "sqlite",
|
||||||
|
});
|
||||||
|
save_all(
|
||||||
|
&mut *test.open_backend(self.dst_format).unwrap(),
|
||||||
|
self.dst_history,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let outcome = match self.src_history {
|
||||||
|
HistorySource::Vec(src_history) => {
|
||||||
|
let src_format = match self.dst_format {
|
||||||
|
Plaintext => Sqlite,
|
||||||
|
Sqlite => Plaintext,
|
||||||
|
};
|
||||||
|
save_all(&mut *test.open_backend(src_format).unwrap(), src_history).unwrap();
|
||||||
|
test.nu("history import")
|
||||||
|
}
|
||||||
|
HistorySource::Command(cmd) => {
|
||||||
|
let mut cmd = cmd.to_string();
|
||||||
|
cmd.push_str(" | history import");
|
||||||
|
test.nu(cmd)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assert!(outcome.status.success());
|
||||||
|
let got = query_all(&*test.open_backend(self.dst_format).unwrap()).unwrap();
|
||||||
|
|
||||||
|
// Compare just the commands first, for readability.
|
||||||
|
fn commands_only(items: &[HistoryItem]) -> Vec<&str> {
|
||||||
|
items
|
||||||
|
.iter()
|
||||||
|
.map(|item| item.command_line.as_str())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
assert_eq!(commands_only(&got), commands_only(&self.want_history));
|
||||||
|
// If commands match, compare full items.
|
||||||
|
assert_eq!(got, self.want_history);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query_all(history: &dyn History) -> Result<Vec<HistoryItem>, ReedlineError> {
|
||||||
|
history.search(SearchQuery::everything(
|
||||||
|
reedline::SearchDirection::Forward,
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_all(history: &mut dyn History, items: Vec<HistoryItem>) -> Result<(), ReedlineError> {
|
||||||
|
for item in items {
|
||||||
|
history.save(item)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const EMPTY_ITEM: HistoryItem = HistoryItem {
|
||||||
|
command_line: String::new(),
|
||||||
|
id: None,
|
||||||
|
start_timestamp: None,
|
||||||
|
session_id: None,
|
||||||
|
hostname: None,
|
||||||
|
cwd: None,
|
||||||
|
duration: None,
|
||||||
|
exit_status: None,
|
||||||
|
more_info: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn history_import_pipe_string() {
|
||||||
|
TestCase {
|
||||||
|
dst_format: HistoryFileFormat::Plaintext,
|
||||||
|
src_history: HistorySource::Command("echo bar"),
|
||||||
|
want_history: vec![HistoryItem {
|
||||||
|
id: Some(HistoryItemId::new(0)),
|
||||||
|
command_line: "bar".to_string(),
|
||||||
|
..EMPTY_ITEM
|
||||||
|
}],
|
||||||
|
..EMPTY_TEST_CASE
|
||||||
|
}
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn history_import_pipe_record() {
|
||||||
|
TestCase {
|
||||||
|
dst_format: HistoryFileFormat::Sqlite,
|
||||||
|
src_history: HistorySource::Command("[[cwd command]; [/tmp some_command]]"),
|
||||||
|
want_history: vec![HistoryItem {
|
||||||
|
id: Some(HistoryItemId::new(1)),
|
||||||
|
command_line: "some_command".to_string(),
|
||||||
|
cwd: Some("/tmp".to_string()),
|
||||||
|
..EMPTY_ITEM
|
||||||
|
}],
|
||||||
|
..EMPTY_TEST_CASE
|
||||||
|
}
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn to_empty_plaintext() {
|
||||||
|
TestCase {
|
||||||
|
dst_format: HistoryFileFormat::Plaintext,
|
||||||
|
src_history: HistorySource::Vec(vec![
|
||||||
|
HistoryItem {
|
||||||
|
command_line: "foo".to_string(),
|
||||||
|
..EMPTY_ITEM
|
||||||
|
},
|
||||||
|
HistoryItem {
|
||||||
|
command_line: "bar".to_string(),
|
||||||
|
..EMPTY_ITEM
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
want_history: vec![
|
||||||
|
HistoryItem {
|
||||||
|
id: Some(HistoryItemId::new(0)),
|
||||||
|
command_line: "foo".to_string(),
|
||||||
|
..EMPTY_ITEM
|
||||||
|
},
|
||||||
|
HistoryItem {
|
||||||
|
id: Some(HistoryItemId::new(1)),
|
||||||
|
command_line: "bar".to_string(),
|
||||||
|
..EMPTY_ITEM
|
||||||
|
},
|
||||||
|
],
|
||||||
|
..EMPTY_TEST_CASE
|
||||||
|
}
|
||||||
|
.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn to_empty_sqlite() {
|
||||||
|
TestCase {
|
||||||
|
dst_format: HistoryFileFormat::Sqlite,
|
||||||
|
src_history: HistorySource::Vec(vec![
|
||||||
|
HistoryItem {
|
||||||
|
command_line: "foo".to_string(),
|
||||||
|
..EMPTY_ITEM
|
||||||
|
},
|
||||||
|
HistoryItem {
|
||||||
|
command_line: "bar".to_string(),
|
||||||
|
..EMPTY_ITEM
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
want_history: vec![
|
||||||
|
HistoryItem {
|
||||||
|
id: Some(HistoryItemId::new(1)),
|
||||||
|
command_line: "foo".to_string(),
|
||||||
|
..EMPTY_ITEM
|
||||||
|
},
|
||||||
|
HistoryItem {
|
||||||
|
id: Some(HistoryItemId::new(2)),
|
||||||
|
command_line: "bar".to_string(),
|
||||||
|
..EMPTY_ITEM
|
||||||
|
},
|
||||||
|
],
|
||||||
|
..EMPTY_TEST_CASE
|
||||||
|
}
|
||||||
|
.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case::plaintext(HistoryFileFormat::Plaintext)]
|
||||||
|
#[case::sqlite(HistoryFileFormat::Sqlite)]
|
||||||
|
fn to_existing(#[case] dst_format: HistoryFileFormat) {
|
||||||
|
TestCase {
|
||||||
|
dst_format,
|
||||||
|
dst_history: vec![
|
||||||
|
HistoryItem {
|
||||||
|
id: Some(HistoryItemId::new(0)),
|
||||||
|
command_line: "original-1".to_string(),
|
||||||
|
..EMPTY_ITEM
|
||||||
|
},
|
||||||
|
HistoryItem {
|
||||||
|
id: Some(HistoryItemId::new(1)),
|
||||||
|
command_line: "original-2".to_string(),
|
||||||
|
..EMPTY_ITEM
|
||||||
|
},
|
||||||
|
],
|
||||||
|
src_history: HistorySource::Vec(vec![HistoryItem {
|
||||||
|
id: Some(HistoryItemId::new(1)),
|
||||||
|
command_line: "new".to_string(),
|
||||||
|
..EMPTY_ITEM
|
||||||
|
}]),
|
||||||
|
want_history: vec![
|
||||||
|
HistoryItem {
|
||||||
|
id: Some(HistoryItemId::new(0)),
|
||||||
|
command_line: "original-1".to_string(),
|
||||||
|
..EMPTY_ITEM
|
||||||
|
},
|
||||||
|
HistoryItem {
|
||||||
|
id: Some(HistoryItemId::new(1)),
|
||||||
|
command_line: "original-2".to_string(),
|
||||||
|
..EMPTY_ITEM
|
||||||
|
},
|
||||||
|
HistoryItem {
|
||||||
|
id: Some(HistoryItemId::new(2)),
|
||||||
|
command_line: "new".to_string(),
|
||||||
|
..EMPTY_ITEM
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
.run()
|
||||||
|
}
|
@ -1,2 +1,3 @@
|
|||||||
|
mod history_import;
|
||||||
mod keybindings_list;
|
mod keybindings_list;
|
||||||
mod nu_highlight;
|
mod nu_highlight;
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -63,7 +63,7 @@ pub fn new_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Merge environment into the permanent state
|
// Merge environment into the permanent state
|
||||||
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
let merge_result = engine_state.merge_env(&mut stack);
|
||||||
assert!(merge_result.is_ok());
|
assert!(merge_result.is_ok());
|
||||||
|
|
||||||
(dir, dir_str, engine_state, stack)
|
(dir, dir_str, engine_state, stack)
|
||||||
@ -109,7 +109,7 @@ pub fn new_dotnu_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Merge environment into the permanent state
|
// Merge environment into the permanent state
|
||||||
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
let merge_result = engine_state.merge_env(&mut stack);
|
||||||
assert!(merge_result.is_ok());
|
assert!(merge_result.is_ok());
|
||||||
|
|
||||||
(dir, dir_str, engine_state, stack)
|
(dir, dir_str, engine_state, stack)
|
||||||
@ -144,7 +144,7 @@ pub fn new_quote_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Merge environment into the permanent state
|
// Merge environment into the permanent state
|
||||||
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
let merge_result = engine_state.merge_env(&mut stack);
|
||||||
assert!(merge_result.is_ok());
|
assert!(merge_result.is_ok());
|
||||||
|
|
||||||
(dir, dir_str, engine_state, stack)
|
(dir, dir_str, engine_state, stack)
|
||||||
@ -179,14 +179,14 @@ pub fn new_partial_engine() -> (AbsolutePathBuf, String, EngineState, Stack) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Merge environment into the permanent state
|
// Merge environment into the permanent state
|
||||||
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
let merge_result = engine_state.merge_env(&mut stack);
|
||||||
assert!(merge_result.is_ok());
|
assert!(merge_result.is_ok());
|
||||||
|
|
||||||
(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<String>, suggestions: &Vec<Suggestion>) {
|
||||||
let expected_len = expected.len();
|
let expected_len = expected.len();
|
||||||
let suggestions_len = suggestions.len();
|
let suggestions_len = suggestions.len();
|
||||||
if expected_len != suggestions_len {
|
if expected_len != suggestions_len {
|
||||||
@ -196,13 +196,13 @@ pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
|
|||||||
Expected: {expected:#?}\n"
|
Expected: {expected:#?}\n"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
assert_eq!(
|
|
||||||
expected,
|
let suggestoins_str = suggestions
|
||||||
suggestions
|
.iter()
|
||||||
.into_iter()
|
.map(|it| it.value.clone())
|
||||||
.map(|it| it.value)
|
.collect::<Vec<_>>();
|
||||||
.collect::<Vec<_>>()
|
|
||||||
);
|
assert_eq!(expected, &suggestoins_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
// append the separator to the converted path
|
// append the separator to the converted path
|
||||||
@ -223,7 +223,6 @@ pub fn merge_input(
|
|||||||
input: &[u8],
|
input: &[u8],
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
dir: AbsolutePathBuf,
|
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
@ -246,5 +245,5 @@ pub fn merge_input(
|
|||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
// Merge environment into the permanent state
|
// Merge environment into the permanent state
|
||||||
engine_state.merge_env(stack, &dir)
|
engine_state.merge_env(stack)
|
||||||
}
|
}
|
||||||
|
@ -5,15 +5,18 @@ 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.96.1"
|
version = "0.100.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.96.1" }
|
nu-engine = { path = "../nu-engine", version = "0.100.0" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.96.1" }
|
nu-parser = { path = "../nu-parser", version = "0.100.0" }
|
||||||
nu-path = { path = "../nu-path", version = "0.96.1" }
|
nu-path = { path = "../nu-path", version = "0.100.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.96.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.100.0" }
|
||||||
|
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
miette = { workspace = true }
|
miette = { workspace = true }
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
use crate::util::get_guaranteed_cwd;
|
|
||||||
use miette::Result;
|
use miette::Result;
|
||||||
use nu_engine::{eval_block, eval_block_with_early_return};
|
use nu_engine::{eval_block, eval_block_with_early_return};
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
cli_error::{report_error, report_error_new},
|
cli_error::{report_parse_error, report_shell_error},
|
||||||
debugger::WithoutDebug,
|
debugger::WithoutDebug,
|
||||||
engine::{Closure, EngineState, Stack, StateWorkingSet},
|
engine::{Closure, EngineState, Stack, StateWorkingSet},
|
||||||
PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId,
|
PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId,
|
||||||
@ -19,17 +18,12 @@ pub fn eval_env_change_hook(
|
|||||||
match hook {
|
match hook {
|
||||||
Value::Record { val, .. } => {
|
Value::Record { val, .. } => {
|
||||||
for (env_name, hook_value) in &*val {
|
for (env_name, hook_value) in &*val {
|
||||||
let before = engine_state
|
let before = engine_state.previous_env_vars.get(env_name);
|
||||||
.previous_env_vars
|
let after = stack.get_env_var(engine_state, env_name);
|
||||||
.get(env_name)
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let after = stack
|
|
||||||
.get_env_var(engine_state, env_name)
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
if before != after {
|
if before != after {
|
||||||
|
let before = before.cloned().unwrap_or_default();
|
||||||
|
let after = after.cloned().unwrap_or_default();
|
||||||
|
|
||||||
eval_hook(
|
eval_hook(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
@ -40,7 +34,7 @@ pub fn eval_env_change_hook(
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
Arc::make_mut(&mut engine_state.previous_env_vars)
|
Arc::make_mut(&mut engine_state.previous_env_vars)
|
||||||
.insert(env_name.to_string(), after);
|
.insert(env_name.clone(), after);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,12 +85,13 @@ pub fn eval_hook(
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
if let Some(err) = working_set.parse_errors.first() {
|
if let Some(err) = working_set.parse_errors.first() {
|
||||||
report_error(&working_set, err);
|
report_parse_error(&working_set, err);
|
||||||
|
return Err(ShellError::GenericError {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
error: format!("Failed to run {hook_name} hook"),
|
||||||
expected: "valid source code".into(),
|
msg: "source code has errors".into(),
|
||||||
value: "source code with syntax errors".into(),
|
span: Some(span),
|
||||||
span,
|
help: None,
|
||||||
|
inner: Vec::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +118,7 @@ pub fn eval_hook(
|
|||||||
output = pipeline_data;
|
output = pipeline_data;
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
report_error_new(engine_state, &err);
|
report_shell_error(engine_state, &err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,10 +162,10 @@ pub fn eval_hook(
|
|||||||
{
|
{
|
||||||
val
|
val
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
return Err(ShellError::RuntimeTypeMismatch {
|
||||||
expected: "boolean output".to_string(),
|
expected: Type::Bool,
|
||||||
value: "other PipelineData variant".to_string(),
|
actual: pipeline_data.get_type(),
|
||||||
span: other_span,
|
span: pipeline_data.span().unwrap_or(other_span),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -179,9 +174,9 @@ pub fn eval_hook(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
return Err(ShellError::RuntimeTypeMismatch {
|
||||||
expected: "block".to_string(),
|
expected: Type::Closure,
|
||||||
value: format!("{}", condition.get_type()),
|
actual: condition.get_type(),
|
||||||
span: other_span,
|
span: other_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -223,12 +218,13 @@ pub fn eval_hook(
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
if let Some(err) = working_set.parse_errors.first() {
|
if let Some(err) = working_set.parse_errors.first() {
|
||||||
report_error(&working_set, err);
|
report_parse_error(&working_set, err);
|
||||||
|
return Err(ShellError::GenericError {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
error: format!("Failed to run {hook_name} hook"),
|
||||||
expected: "valid source code".into(),
|
msg: "source code has errors".into(),
|
||||||
value: "source code with syntax errors".into(),
|
span: Some(span),
|
||||||
span: source_span,
|
help: None,
|
||||||
|
inner: Vec::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,7 +247,7 @@ pub fn eval_hook(
|
|||||||
output = pipeline_data;
|
output = pipeline_data;
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
report_error_new(engine_state, &err);
|
report_shell_error(engine_state, &err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,9 +259,9 @@ pub fn eval_hook(
|
|||||||
run_hook(engine_state, stack, val, input, arguments, source_span)?;
|
run_hook(engine_state, stack, val, input, arguments, source_span)?;
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
return Err(ShellError::RuntimeTypeMismatch {
|
||||||
expected: "block or string".to_string(),
|
expected: Type::custom("string or closure"),
|
||||||
value: format!("{}", other.get_type()),
|
actual: other.get_type(),
|
||||||
span: source_span,
|
span: source_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -276,16 +272,15 @@ pub fn eval_hook(
|
|||||||
output = run_hook(engine_state, stack, val, input, arguments, span)?;
|
output = run_hook(engine_state, stack, val, input, arguments, span)?;
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
return Err(ShellError::UnsupportedConfigValue {
|
return Err(ShellError::RuntimeTypeMismatch {
|
||||||
expected: "string, block, record, or list of commands".into(),
|
expected: Type::custom("string, closure, record, or list"),
|
||||||
value: format!("{}", other.get_type()),
|
actual: other.get_type(),
|
||||||
span: other.span(),
|
span: other.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
engine_state.merge_env(stack)?;
|
||||||
engine_state.merge_env(stack, cwd)?;
|
|
||||||
|
|
||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,8 @@
|
|||||||
use nu_path::AbsolutePathBuf;
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
Range, ShellError, Span, Value,
|
Range, ShellError, Span, Value,
|
||||||
};
|
};
|
||||||
use std::{ops::Bound, path::PathBuf};
|
use std::ops::Bound;
|
||||||
|
|
||||||
pub fn get_init_cwd() -> PathBuf {
|
|
||||||
std::env::current_dir().unwrap_or_else(|_| {
|
|
||||||
std::env::var("PWD")
|
|
||||||
.map(Into::into)
|
|
||||||
.unwrap_or_else(|_| nu_path::home_dir().unwrap_or_default())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf {
|
|
||||||
engine_state
|
|
||||||
.cwd(Some(stack))
|
|
||||||
.map(AbsolutePathBuf::into_std_path_buf)
|
|
||||||
.unwrap_or(crate::util::get_init_cwd())
|
|
||||||
}
|
|
||||||
|
|
||||||
type MakeRangeError = fn(&str, Span) -> ShellError;
|
type MakeRangeError = fn(&str, Span) -> ShellError;
|
||||||
|
|
||||||
@ -94,10 +78,10 @@ pub fn get_editor(
|
|||||||
get_editor_commandline(&config.buffer_editor, "$env.config.buffer_editor")
|
get_editor_commandline(&config.buffer_editor, "$env.config.buffer_editor")
|
||||||
{
|
{
|
||||||
Ok(buff_editor)
|
Ok(buff_editor)
|
||||||
} else if let Some(value) = env_vars.get("EDITOR") {
|
|
||||||
get_editor_commandline(value, "$env.EDITOR")
|
|
||||||
} else if let Some(value) = env_vars.get("VISUAL") {
|
} else if let Some(value) = env_vars.get("VISUAL") {
|
||||||
get_editor_commandline(value, "$env.VISUAL")
|
get_editor_commandline(value, "$env.VISUAL")
|
||||||
|
} else if let Some(value) = env_vars.get("EDITOR") {
|
||||||
|
get_editor_commandline(value, "$env.EDITOR")
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::GenericError {
|
Err(ShellError::GenericError {
|
||||||
error: "No editor configured".into(),
|
error: "No editor configured".into(),
|
||||||
|
@ -5,21 +5,24 @@ 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.96.1"
|
version = "0.100.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.1" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.100.0" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.96.1" }
|
nu-engine = { path = "../nu-engine", version = "0.100.0" }
|
||||||
nu-json = { version = "0.96.1", path = "../nu-json" }
|
nu-json = { version = "0.100.0", path = "../nu-json" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.96.1" }
|
nu-parser = { path = "../nu-parser", version = "0.100.0" }
|
||||||
nu-pretty-hex = { version = "0.96.1", path = "../nu-pretty-hex" }
|
nu-pretty-hex = { version = "0.100.0", path = "../nu-pretty-hex" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.96.1" }
|
nu-protocol = { path = "../nu-protocol", version = "0.100.0" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.96.1" }
|
nu-utils = { path = "../nu-utils", version = "0.100.0" }
|
||||||
|
|
||||||
# Potential dependencies for extras
|
# Potential dependencies for extras
|
||||||
heck = { workspace = true }
|
heck = { workspace = true }
|
||||||
@ -33,6 +36,6 @@ v_htmlescape = { workspace = true }
|
|||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.1" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.100.0" }
|
||||||
nu-command = { path = "../nu-command", version = "0.96.1" }
|
nu-command = { path = "../nu-command", version = "0.100.0" }
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.96.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.100.0" }
|
@ -37,7 +37,7 @@ impl Command for BitsAnd {
|
|||||||
.category(Category::Bits)
|
.category(Category::Bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Performs bitwise and for ints or binary values."
|
"Performs bitwise and for ints or binary values."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,11 +14,11 @@ impl Command for Bits {
|
|||||||
.input_output_types(vec![(Type::Nothing, Type::String)])
|
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Various commands for working with bits."
|
"Various commands for working with bits."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_description(&self) -> &str {
|
||||||
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ impl Command for BitsInto {
|
|||||||
.category(Category::Conversions)
|
.category(Category::Conversions)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Convert value to a binary primitive."
|
"Convert value to a binary primitive."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,25 @@ enum InputNumType {
|
|||||||
SignedEight,
|
SignedEight,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl InputNumType {
|
||||||
|
fn num_bits(self) -> u32 {
|
||||||
|
match self {
|
||||||
|
InputNumType::One => 8,
|
||||||
|
InputNumType::Two => 16,
|
||||||
|
InputNumType::Four => 32,
|
||||||
|
InputNumType::Eight => 64,
|
||||||
|
InputNumType::SignedOne => 8,
|
||||||
|
InputNumType::SignedTwo => 16,
|
||||||
|
InputNumType::SignedFour => 32,
|
||||||
|
InputNumType::SignedEight => 64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_permitted_bit_shift(self, bits: u32) -> bool {
|
||||||
|
bits < self.num_bits()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_number_bytes(
|
fn get_number_bytes(
|
||||||
number_bytes: Option<Spanned<usize>>,
|
number_bytes: Option<Spanned<usize>>,
|
||||||
head: Span,
|
head: Span,
|
||||||
|
@ -51,7 +51,7 @@ impl Command for BitsNot {
|
|||||||
.category(Category::Bits)
|
.category(Category::Bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Performs logical negation on each bit."
|
"Performs logical negation on each bit."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ impl Command for BitsOr {
|
|||||||
.category(Category::Bits)
|
.category(Category::Bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Performs bitwise or for ints or binary values."
|
"Performs bitwise or for ints or binary values."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||||
use itertools::Itertools;
|
|
||||||
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::*;
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
signed: bool,
|
signed: bool,
|
||||||
bits: usize,
|
bits: Spanned<usize>,
|
||||||
number_size: NumberBytes,
|
number_size: NumberBytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +52,7 @@ impl Command for BitsRol {
|
|||||||
.category(Category::Bits)
|
.category(Category::Bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Bitwise rotate left for ints or binary values."
|
"Bitwise rotate left for ints or binary values."
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +68,7 @@ impl Command for BitsRol {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
let bits = call.req(engine_state, stack, 0)?;
|
||||||
let signed = call.has_flag(engine_state, stack, "signed")?;
|
let signed = call.has_flag(engine_state, stack, "signed")?;
|
||||||
let number_bytes: Option<Spanned<usize>> =
|
let number_bytes: Option<Spanned<usize>> =
|
||||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
@ -119,6 +118,8 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
number_size,
|
number_size,
|
||||||
bits,
|
bits,
|
||||||
} = *args;
|
} = *args;
|
||||||
|
let bits_span = bits.span;
|
||||||
|
let bits = bits.item;
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
Value::Int { val, .. } => {
|
Value::Int { val, .. } => {
|
||||||
@ -127,6 +128,19 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
let bits = bits as u32;
|
let bits = bits as u32;
|
||||||
let input_num_type = get_input_num_type(val, signed, number_size);
|
let input_num_type = get_input_num_type(val, signed, number_size);
|
||||||
|
|
||||||
|
if bits > input_num_type.num_bits() {
|
||||||
|
return Value::error(
|
||||||
|
ShellError::IncorrectValue {
|
||||||
|
msg: format!(
|
||||||
|
"Trying to rotate by more than the available bits ({})",
|
||||||
|
input_num_type.num_bits()
|
||||||
|
),
|
||||||
|
val_span: bits_span,
|
||||||
|
call_span: span,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
);
|
||||||
|
}
|
||||||
let int = match input_num_type {
|
let int = match input_num_type {
|
||||||
One => (val as u8).rotate_left(bits) as i64,
|
One => (val as u8).rotate_left(bits) as i64,
|
||||||
Two => (val as u16).rotate_left(bits) as i64,
|
Two => (val as u16).rotate_left(bits) as i64,
|
||||||
@ -157,16 +171,28 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
Value::int(int, span)
|
Value::int(int, span)
|
||||||
}
|
}
|
||||||
Value::Binary { val, .. } => {
|
Value::Binary { val, .. } => {
|
||||||
|
let len = val.len();
|
||||||
|
if bits > len * 8 {
|
||||||
|
return Value::error(
|
||||||
|
ShellError::IncorrectValue {
|
||||||
|
msg: format!(
|
||||||
|
"Trying to rotate by more than the available bits ({})",
|
||||||
|
len * 8
|
||||||
|
),
|
||||||
|
val_span: bits_span,
|
||||||
|
call_span: span,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
);
|
||||||
|
}
|
||||||
let byte_shift = bits / 8;
|
let byte_shift = bits / 8;
|
||||||
let bit_rotate = bits % 8;
|
let bit_rotate = bits % 8;
|
||||||
|
|
||||||
let mut bytes = val
|
let bytes = if bit_rotate == 0 {
|
||||||
.iter()
|
rotate_bytes_left(val, byte_shift)
|
||||||
.copied()
|
} else {
|
||||||
.circular_tuple_windows::<(u8, u8)>()
|
rotate_bytes_and_bits_left(val, byte_shift, bit_rotate)
|
||||||
.map(|(lhs, rhs)| (lhs << bit_rotate) | (rhs >> (8 - bit_rotate)))
|
};
|
||||||
.collect::<Vec<u8>>();
|
|
||||||
bytes.rotate_left(byte_shift);
|
|
||||||
|
|
||||||
Value::binary(bytes, span)
|
Value::binary(bytes, span)
|
||||||
}
|
}
|
||||||
@ -184,6 +210,34 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rotate_bytes_left(data: &[u8], byte_shift: usize) -> Vec<u8> {
|
||||||
|
let len = data.len();
|
||||||
|
let mut output = vec![0; len];
|
||||||
|
output[..len - byte_shift].copy_from_slice(&data[byte_shift..]);
|
||||||
|
output[len - byte_shift..].copy_from_slice(&data[..byte_shift]);
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate_bytes_and_bits_left(data: &[u8], byte_shift: usize, bit_shift: usize) -> Vec<u8> {
|
||||||
|
debug_assert!(byte_shift < data.len());
|
||||||
|
debug_assert!(
|
||||||
|
(1..8).contains(&bit_shift),
|
||||||
|
"Bit shifts of 0 can't be handled by this impl and everything else should be part of the byteshift");
|
||||||
|
let mut bytes = Vec::with_capacity(data.len());
|
||||||
|
let mut next_index = byte_shift;
|
||||||
|
for _ in 0..data.len() {
|
||||||
|
let curr_byte = data[next_index];
|
||||||
|
next_index += 1;
|
||||||
|
if next_index == data.len() {
|
||||||
|
next_index = 0;
|
||||||
|
}
|
||||||
|
let next_byte = data[next_index];
|
||||||
|
let new_byte = (curr_byte << bit_shift) | (next_byte >> (8 - bit_shift));
|
||||||
|
bytes.push(new_byte);
|
||||||
|
}
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||||
use itertools::Itertools;
|
|
||||||
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::*;
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
signed: bool,
|
signed: bool,
|
||||||
bits: usize,
|
bits: Spanned<usize>,
|
||||||
number_size: NumberBytes,
|
number_size: NumberBytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +52,7 @@ impl Command for BitsRor {
|
|||||||
.category(Category::Bits)
|
.category(Category::Bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Bitwise rotate right for ints or binary values."
|
"Bitwise rotate right for ints or binary values."
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +68,7 @@ impl Command for BitsRor {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
let bits = call.req(engine_state, stack, 0)?;
|
||||||
let signed = call.has_flag(engine_state, stack, "signed")?;
|
let signed = call.has_flag(engine_state, stack, "signed")?;
|
||||||
let number_bytes: Option<Spanned<usize>> =
|
let number_bytes: Option<Spanned<usize>> =
|
||||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
@ -123,6 +122,8 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
number_size,
|
number_size,
|
||||||
bits,
|
bits,
|
||||||
} = *args;
|
} = *args;
|
||||||
|
let bits_span = bits.span;
|
||||||
|
let bits = bits.item;
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
Value::Int { val, .. } => {
|
Value::Int { val, .. } => {
|
||||||
@ -131,6 +132,19 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
let bits = bits as u32;
|
let bits = bits as u32;
|
||||||
let input_num_type = get_input_num_type(val, signed, number_size);
|
let input_num_type = get_input_num_type(val, signed, number_size);
|
||||||
|
|
||||||
|
if bits > input_num_type.num_bits() {
|
||||||
|
return Value::error(
|
||||||
|
ShellError::IncorrectValue {
|
||||||
|
msg: format!(
|
||||||
|
"Trying to rotate by more than the available bits ({})",
|
||||||
|
input_num_type.num_bits()
|
||||||
|
),
|
||||||
|
val_span: bits_span,
|
||||||
|
call_span: span,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
);
|
||||||
|
}
|
||||||
let int = match input_num_type {
|
let int = match input_num_type {
|
||||||
One => (val as u8).rotate_right(bits) as i64,
|
One => (val as u8).rotate_right(bits) as i64,
|
||||||
Two => (val as u16).rotate_right(bits) as i64,
|
Two => (val as u16).rotate_right(bits) as i64,
|
||||||
@ -161,16 +175,28 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
Value::int(int, span)
|
Value::int(int, span)
|
||||||
}
|
}
|
||||||
Value::Binary { val, .. } => {
|
Value::Binary { val, .. } => {
|
||||||
|
let len = val.len();
|
||||||
|
if bits > len * 8 {
|
||||||
|
return Value::error(
|
||||||
|
ShellError::IncorrectValue {
|
||||||
|
msg: format!(
|
||||||
|
"Trying to rotate by more than the available bits ({})",
|
||||||
|
len * 8
|
||||||
|
),
|
||||||
|
val_span: bits_span,
|
||||||
|
call_span: span,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
);
|
||||||
|
}
|
||||||
let byte_shift = bits / 8;
|
let byte_shift = bits / 8;
|
||||||
let bit_rotate = bits % 8;
|
let bit_rotate = bits % 8;
|
||||||
|
|
||||||
let mut bytes = val
|
let bytes = if bit_rotate == 0 {
|
||||||
.iter()
|
rotate_bytes_right(val, byte_shift)
|
||||||
.copied()
|
} else {
|
||||||
.circular_tuple_windows::<(u8, u8)>()
|
rotate_bytes_and_bits_right(val, byte_shift, bit_rotate)
|
||||||
.map(|(lhs, rhs)| (lhs >> bit_rotate) | (rhs << (8 - bit_rotate)))
|
};
|
||||||
.collect::<Vec<u8>>();
|
|
||||||
bytes.rotate_right(byte_shift);
|
|
||||||
|
|
||||||
Value::binary(bytes, span)
|
Value::binary(bytes, span)
|
||||||
}
|
}
|
||||||
@ -188,6 +214,35 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rotate_bytes_right(data: &[u8], byte_shift: usize) -> Vec<u8> {
|
||||||
|
let len = data.len();
|
||||||
|
let mut output = vec![0; len];
|
||||||
|
output[byte_shift..].copy_from_slice(&data[..len - byte_shift]);
|
||||||
|
output[..byte_shift].copy_from_slice(&data[len - byte_shift..]);
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate_bytes_and_bits_right(data: &[u8], byte_shift: usize, bit_shift: usize) -> Vec<u8> {
|
||||||
|
debug_assert!(byte_shift < data.len());
|
||||||
|
debug_assert!(
|
||||||
|
(1..8).contains(&bit_shift),
|
||||||
|
"Bit shifts of 0 can't be handled by this impl and everything else should be part of the byteshift"
|
||||||
|
);
|
||||||
|
let mut bytes = Vec::with_capacity(data.len());
|
||||||
|
let mut previous_index = data.len() - byte_shift - 1;
|
||||||
|
for _ in 0..data.len() {
|
||||||
|
let previous_byte = data[previous_index];
|
||||||
|
previous_index += 1;
|
||||||
|
if previous_index == data.len() {
|
||||||
|
previous_index = 0;
|
||||||
|
}
|
||||||
|
let curr_byte = data[previous_index];
|
||||||
|
let rotated_byte = (curr_byte >> bit_shift) | (previous_byte << (8 - bit_shift));
|
||||||
|
bytes.push(rotated_byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes
|
||||||
|
}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -7,7 +7,7 @@ use std::iter;
|
|||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
signed: bool,
|
signed: bool,
|
||||||
bits: usize,
|
bits: Spanned<usize>,
|
||||||
number_size: NumberBytes,
|
number_size: NumberBytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ impl Command for BitsShl {
|
|||||||
.category(Category::Bits)
|
.category(Category::Bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Bitwise shift left for ints or binary values."
|
"Bitwise shift left for ints or binary values."
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +71,9 @@ impl Command for BitsShl {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
// This restricts to a positive shift value (our underlying operations do not
|
||||||
|
// permit them)
|
||||||
|
let bits: Spanned<usize> = call.req(engine_state, stack, 0)?;
|
||||||
let signed = call.has_flag(engine_state, stack, "signed")?;
|
let signed = call.has_flag(engine_state, stack, "signed")?;
|
||||||
let number_bytes: Option<Spanned<usize>> =
|
let number_bytes: Option<Spanned<usize>> =
|
||||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
@ -131,14 +133,29 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
number_size,
|
number_size,
|
||||||
bits,
|
bits,
|
||||||
} = *args;
|
} = *args;
|
||||||
|
let bits_span = bits.span;
|
||||||
|
let bits = bits.item;
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
Value::Int { val, .. } => {
|
Value::Int { val, .. } => {
|
||||||
use InputNumType::*;
|
use InputNumType::*;
|
||||||
let val = *val;
|
let val = *val;
|
||||||
let bits = bits as u64;
|
let bits = bits as u32;
|
||||||
|
|
||||||
let input_num_type = get_input_num_type(val, signed, number_size);
|
let input_num_type = get_input_num_type(val, signed, number_size);
|
||||||
|
if !input_num_type.is_permitted_bit_shift(bits) {
|
||||||
|
return Value::error(
|
||||||
|
ShellError::IncorrectValue {
|
||||||
|
msg: format!(
|
||||||
|
"Trying to shift by more than the available bits (permitted < {})",
|
||||||
|
input_num_type.num_bits()
|
||||||
|
),
|
||||||
|
val_span: bits_span,
|
||||||
|
call_span: span,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
);
|
||||||
|
}
|
||||||
let int = match input_num_type {
|
let int = match input_num_type {
|
||||||
One => ((val as u8) << bits) as i64,
|
One => ((val as u8) << bits) as i64,
|
||||||
Two => ((val as u16) << bits) as i64,
|
Two => ((val as u16) << bits) as i64,
|
||||||
@ -147,12 +164,14 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
let Ok(i) = i64::try_from((val as u64) << bits) else {
|
let Ok(i) = i64::try_from((val as u64) << bits) else {
|
||||||
return Value::error(
|
return Value::error(
|
||||||
ShellError::GenericError {
|
ShellError::GenericError {
|
||||||
error: "result out of range for specified number".into(),
|
error: "result out of range for int".into(),
|
||||||
msg: format!(
|
msg: format!(
|
||||||
"shifting left by {bits} is out of range for the value {val}"
|
"shifting left by {bits} is out of range for the value {val}"
|
||||||
),
|
),
|
||||||
span: Some(span),
|
span: Some(span),
|
||||||
help: None,
|
help: Some(
|
||||||
|
"Ensure the result fits in a 64-bit signed integer.".into(),
|
||||||
|
),
|
||||||
inner: vec![],
|
inner: vec![],
|
||||||
},
|
},
|
||||||
span,
|
span,
|
||||||
@ -172,19 +191,26 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
let byte_shift = bits / 8;
|
let byte_shift = bits / 8;
|
||||||
let bit_shift = bits % 8;
|
let bit_shift = bits % 8;
|
||||||
|
|
||||||
use itertools::Position::*;
|
// This is purely for symmetry with the int case and the fact that the
|
||||||
let bytes = val
|
// shift right implementation in its current form panicked with an overflow
|
||||||
.iter()
|
if bits > val.len() * 8 {
|
||||||
.copied()
|
return Value::error(
|
||||||
.skip(byte_shift)
|
ShellError::IncorrectValue {
|
||||||
.circular_tuple_windows::<(u8, u8)>()
|
msg: format!(
|
||||||
.with_position()
|
"Trying to shift by more than the available bits ({})",
|
||||||
.map(|(pos, (lhs, rhs))| match pos {
|
val.len() * 8
|
||||||
Last | Only => lhs << bit_shift,
|
),
|
||||||
_ => (lhs << bit_shift) | (rhs >> bit_shift),
|
val_span: bits_span,
|
||||||
})
|
call_span: span,
|
||||||
.chain(iter::repeat(0).take(byte_shift))
|
},
|
||||||
.collect::<Vec<u8>>();
|
span,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let bytes = if bit_shift == 0 {
|
||||||
|
shift_bytes_left(val, byte_shift)
|
||||||
|
} else {
|
||||||
|
shift_bytes_and_bits_left(val, byte_shift, bit_shift)
|
||||||
|
};
|
||||||
|
|
||||||
Value::binary(bytes, span)
|
Value::binary(bytes, span)
|
||||||
}
|
}
|
||||||
@ -202,6 +228,31 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn shift_bytes_left(data: &[u8], byte_shift: usize) -> Vec<u8> {
|
||||||
|
let len = data.len();
|
||||||
|
let mut output = vec![0; len];
|
||||||
|
output[..len - byte_shift].copy_from_slice(&data[byte_shift..]);
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shift_bytes_and_bits_left(data: &[u8], byte_shift: usize, bit_shift: usize) -> Vec<u8> {
|
||||||
|
use itertools::Position::*;
|
||||||
|
debug_assert!((1..8).contains(&bit_shift),
|
||||||
|
"Bit shifts of 0 can't be handled by this impl and everything else should be part of the byteshift"
|
||||||
|
);
|
||||||
|
data.iter()
|
||||||
|
.copied()
|
||||||
|
.skip(byte_shift)
|
||||||
|
.circular_tuple_windows::<(u8, u8)>()
|
||||||
|
.with_position()
|
||||||
|
.map(|(pos, (lhs, rhs))| match pos {
|
||||||
|
Last | Only => lhs << bit_shift,
|
||||||
|
_ => (lhs << bit_shift) | (rhs >> (8 - bit_shift)),
|
||||||
|
})
|
||||||
|
.chain(iter::repeat(0).take(byte_shift))
|
||||||
|
.collect::<Vec<u8>>()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
use super::{get_input_num_type, get_number_bytes, InputNumType, NumberBytes};
|
||||||
use itertools::Itertools;
|
|
||||||
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 std::iter;
|
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
signed: bool,
|
signed: bool,
|
||||||
bits: usize,
|
bits: Spanned<usize>,
|
||||||
number_size: NumberBytes,
|
number_size: NumberBytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +52,7 @@ impl Command for BitsShr {
|
|||||||
.category(Category::Bits)
|
.category(Category::Bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Bitwise shift right for ints or binary values."
|
"Bitwise shift right for ints or binary values."
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +68,9 @@ impl Command for BitsShr {
|
|||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let head = call.head;
|
let head = call.head;
|
||||||
let bits: usize = call.req(engine_state, stack, 0)?;
|
// This restricts to a positive shift value (our underlying operations do not
|
||||||
|
// permit them)
|
||||||
|
let bits: Spanned<usize> = call.req(engine_state, stack, 0)?;
|
||||||
let signed = call.has_flag(engine_state, stack, "signed")?;
|
let signed = call.has_flag(engine_state, stack, "signed")?;
|
||||||
let number_bytes: Option<Spanned<usize>> =
|
let number_bytes: Option<Spanned<usize>> =
|
||||||
call.get_flag(engine_state, stack, "number-bytes")?;
|
call.get_flag(engine_state, stack, "number-bytes")?;
|
||||||
@ -121,6 +120,8 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
number_size,
|
number_size,
|
||||||
bits,
|
bits,
|
||||||
} = *args;
|
} = *args;
|
||||||
|
let bits_span = bits.span;
|
||||||
|
let bits = bits.item;
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
Value::Int { val, .. } => {
|
Value::Int { val, .. } => {
|
||||||
@ -129,6 +130,19 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
let bits = bits as u32;
|
let bits = bits as u32;
|
||||||
let input_num_type = get_input_num_type(val, signed, number_size);
|
let input_num_type = get_input_num_type(val, signed, number_size);
|
||||||
|
|
||||||
|
if !input_num_type.is_permitted_bit_shift(bits) {
|
||||||
|
return Value::error(
|
||||||
|
ShellError::IncorrectValue {
|
||||||
|
msg: format!(
|
||||||
|
"Trying to shift by more than the available bits (permitted < {})",
|
||||||
|
input_num_type.num_bits()
|
||||||
|
),
|
||||||
|
val_span: bits_span,
|
||||||
|
call_span: span,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
);
|
||||||
|
}
|
||||||
let int = match input_num_type {
|
let int = match input_num_type {
|
||||||
One => ((val as u8) >> bits) as i64,
|
One => ((val as u8) >> bits) as i64,
|
||||||
Two => ((val as u16) >> bits) as i64,
|
Two => ((val as u16) >> bits) as i64,
|
||||||
@ -147,21 +161,27 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
let bit_shift = bits % 8;
|
let bit_shift = bits % 8;
|
||||||
|
|
||||||
let len = val.len();
|
let len = val.len();
|
||||||
use itertools::Position::*;
|
// This check is done for symmetry with the int case and the previous
|
||||||
let bytes = iter::repeat(0)
|
// implementation would overflow byte indices leading to unexpected output
|
||||||
.take(byte_shift)
|
// lengths
|
||||||
.chain(
|
if bits > len * 8 {
|
||||||
val.iter()
|
return Value::error(
|
||||||
.copied()
|
ShellError::IncorrectValue {
|
||||||
.circular_tuple_windows::<(u8, u8)>()
|
msg: format!(
|
||||||
.with_position()
|
"Trying to shift by more than the available bits ({})",
|
||||||
.map(|(pos, (lhs, rhs))| match pos {
|
len * 8
|
||||||
First | Only => lhs >> bit_shift,
|
),
|
||||||
_ => (lhs >> bit_shift) | (rhs << bit_shift),
|
val_span: bits_span,
|
||||||
})
|
call_span: span,
|
||||||
.take(len - byte_shift),
|
},
|
||||||
)
|
span,
|
||||||
.collect::<Vec<u8>>();
|
);
|
||||||
|
}
|
||||||
|
let bytes = if bit_shift == 0 {
|
||||||
|
shift_bytes_right(val, byte_shift)
|
||||||
|
} else {
|
||||||
|
shift_bytes_and_bits_right(val, byte_shift, bit_shift)
|
||||||
|
};
|
||||||
|
|
||||||
Value::binary(bytes, span)
|
Value::binary(bytes, span)
|
||||||
}
|
}
|
||||||
@ -178,6 +198,35 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn shift_bytes_right(data: &[u8], byte_shift: usize) -> Vec<u8> {
|
||||||
|
let len = data.len();
|
||||||
|
let mut output = vec![0; len];
|
||||||
|
output[byte_shift..].copy_from_slice(&data[..len - byte_shift]);
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shift_bytes_and_bits_right(data: &[u8], byte_shift: usize, bit_shift: usize) -> Vec<u8> {
|
||||||
|
debug_assert!(
|
||||||
|
bit_shift > 0 && bit_shift < 8,
|
||||||
|
"bit_shift should be in the range (0, 8)"
|
||||||
|
);
|
||||||
|
let len = data.len();
|
||||||
|
let mut output = vec![0; len];
|
||||||
|
|
||||||
|
for i in byte_shift..len {
|
||||||
|
let shifted_bits = data[i - byte_shift] >> bit_shift;
|
||||||
|
let carried_bits = if i > byte_shift {
|
||||||
|
data[i - byte_shift - 1] << (8 - bit_shift)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let shifted_byte = shifted_bits | carried_bits;
|
||||||
|
|
||||||
|
output[i] = shifted_byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
@ -38,7 +38,7 @@ impl Command for BitsXor {
|
|||||||
.category(Category::Bits)
|
.category(Category::Bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Performs bitwise xor for ints or binary values."
|
"Performs bitwise xor for ints or binary values."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ impl Command for Fmt {
|
|||||||
"fmt"
|
"fmt"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Format a number."
|
"Format a number."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ impl Command for EachWhile {
|
|||||||
"each while"
|
"each while"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Run a closure on each row of the input list until a null is found, then create a new list with the results."
|
"Run a closure on each row of the input list until a null is found, then create a new list with the results."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,11 +18,11 @@ impl Command for Roll {
|
|||||||
.input_output_types(vec![(Type::Nothing, Type::String)])
|
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Rolling commands for tables."
|
"Rolling commands for tables."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_description(&self) -> &str {
|
||||||
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ impl Command for RollDown {
|
|||||||
.category(Category::Filters)
|
.category(Category::Filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Roll table rows down."
|
"Roll table rows down."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ impl Command for RollLeft {
|
|||||||
.category(Category::Filters)
|
.category(Category::Filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Roll record or table columns left."
|
"Roll record or table columns left."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ impl Command for RollRight {
|
|||||||
.category(Category::Filters)
|
.category(Category::Filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Roll table columns right."
|
"Roll table columns right."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ impl Command for RollUp {
|
|||||||
.category(Category::Filters)
|
.category(Category::Filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Roll table rows up."
|
"Roll table rows up."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ impl Command for Rotate {
|
|||||||
.category(Category::Filters)
|
.category(Category::Filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Rotates a table or record clockwise (default) or counter-clockwise (use --ccw flag)."
|
"Rotates a table or record clockwise (default) or counter-clockwise (use --ccw flag)."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ impl Command for UpdateCells {
|
|||||||
.category(Category::Filters)
|
.category(Category::Filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Update the table cells."
|
"Update the table cells."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ impl Command for FromUrl {
|
|||||||
.category(Category::Formats)
|
.category(Category::Formats)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Parse url-encoded string as a record."
|
"Parse url-encoded string as a record."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,11 +138,11 @@ impl Command for ToHtml {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Convert table into simple HTML."
|
"Convert table into simple HTML."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_description(&self) -> &str {
|
||||||
"Screenshots of the themes can be browsed here: https://github.com/mbadolato/iTerm2-Color-Schemes."
|
"Screenshots of the themes can be browsed here: https://github.com/mbadolato/iTerm2-Color-Schemes."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ impl Command for SubCommand {
|
|||||||
.category(Category::Math)
|
.category(Category::Math)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Returns the arccosine of the number."
|
"Returns the arccosine of the number."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
|||||||
.category(Category::Math)
|
.category(Category::Math)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Returns the inverse of the hyperbolic cosine function."
|
"Returns the inverse of the hyperbolic cosine function."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ impl Command for SubCommand {
|
|||||||
.category(Category::Math)
|
.category(Category::Math)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Returns the arcsine of the number."
|
"Returns the arcsine of the number."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
|||||||
.category(Category::Math)
|
.category(Category::Math)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Returns the inverse of the hyperbolic sine function."
|
"Returns the inverse of the hyperbolic sine function."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ impl Command for SubCommand {
|
|||||||
.category(Category::Math)
|
.category(Category::Math)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Returns the arctangent of the number."
|
"Returns the arctangent of the number."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
|||||||
.category(Category::Math)
|
.category(Category::Math)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Returns the inverse of the hyperbolic tangent function."
|
"Returns the inverse of the hyperbolic tangent function."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
|||||||
.category(Category::Math)
|
.category(Category::Math)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Returns the cosine of the number."
|
"Returns the cosine of the number."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
|||||||
.category(Category::Math)
|
.category(Category::Math)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Returns the hyperbolic cosine of the number."
|
"Returns the hyperbolic cosine of the number."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
|||||||
.category(Category::Math)
|
.category(Category::Math)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Returns e raised to the power of x."
|
"Returns e raised to the power of x."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
|||||||
.category(Category::Math)
|
.category(Category::Math)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Returns the natural logarithm. Base: (math e)."
|
"Returns the natural logarithm. Base: (math e)."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
|||||||
.category(Category::Math)
|
.category(Category::Math)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Returns the sine of the number."
|
"Returns the sine of the number."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
|||||||
.category(Category::Math)
|
.category(Category::Math)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Returns the hyperbolic sine of the number."
|
"Returns the hyperbolic sine of the number."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
|||||||
.category(Category::Math)
|
.category(Category::Math)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Returns the tangent of the number."
|
"Returns the tangent of the number."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ impl Command for SubCommand {
|
|||||||
.category(Category::Math)
|
.category(Category::Math)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
"Returns the hyperbolic tangent of the number."
|
"Returns the hyperbolic tangent of the number."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,8 +45,6 @@ pub fn add_extra_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
|
|
||||||
bind_command!(
|
bind_command!(
|
||||||
strings::format::FormatPattern,
|
strings::format::FormatPattern,
|
||||||
strings::encode_decode::EncodeHex,
|
|
||||||
strings::encode_decode::DecodeHex,
|
|
||||||
strings::str_::case::Str,
|
strings::str_::case::Str,
|
||||||
strings::str_::case::StrCamelCase,
|
strings::str_::case::StrCamelCase,
|
||||||
strings::str_::case::StrKebabCase,
|
strings::str_::case::StrKebabCase,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user