mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 07:00:37 +02:00
Compare commits
1167 Commits
Author | SHA1 | Date | |
---|---|---|---|
0120e4040d | |||
716c144e24 | |||
fd09609b44 | |||
c7583ecdb7 | |||
4eec4a27c7 | |||
86faf753bd | |||
79d0735864 | |||
808e523adc | |||
a52386e837 | |||
c26d91fb61 | |||
0a5f8f05da | |||
a13946e3ef | |||
2e01bf9cba | |||
7e82f8d9b5 | |||
2bef85a913 | |||
e435196956 | |||
af1ab39851 | |||
baddc86d9d | |||
de6bab59bf | |||
0ff1cb1ea6 | |||
3e9bb4028a | |||
878e08cfa4 | |||
4e78f3649b | |||
ccd72fa64a | |||
03e688ea7b | |||
7e949595bd | |||
d31a51e3bc | |||
0df847da15 | |||
6af59cb0ea | |||
2ad0fcb377 | |||
f34034ae58 | |||
0e2167884d | |||
e445c41454 | |||
454d1a995c | |||
62575c9a4f | |||
4898750fc1 | |||
48b4471382 | |||
f7b8f97873 | |||
4ae1b1cc26 | |||
b7a34498e3 | |||
10fd3115c2 | |||
df60793e3b | |||
a4952bc029 | |||
f6ca62384e | |||
d6141881f2 | |||
f93033c20b | |||
33fb17776a | |||
b864a455f2 | |||
b9c78a05aa | |||
26c36e932e | |||
bd096430cb | |||
6148314dcd | |||
a7b5bd18ba | |||
e01eb42e74 | |||
c1d76bfac7 | |||
12483fac92 | |||
1a62d87a42 | |||
2ccbefe01e | |||
438062d7fc | |||
0a1af85200 | |||
5bf077d64f | |||
dec0a2517f | |||
324d625324 | |||
e22b70acff | |||
a5c604c283 | |||
644164fab3 | |||
592e677caf | |||
cc7bdebc1c | |||
27d798270b | |||
50f1e33965 | |||
ffc3727a1e | |||
b093d5d10d | |||
9e589a9d93 | |||
f8d2bff283 | |||
0a2e711351 | |||
ba5258d716 | |||
c358400351 | |||
a3f817d71b | |||
c6e2607868 | |||
a29da8c95b | |||
a09aaf3495 | |||
ffc8e752a5 | |||
6ca07b87b9 | |||
49e45915f0 | |||
96e3a3de68 | |||
2aa5c2c41f | |||
4b3e3a37a3 | |||
2492165fcb | |||
c602b5a1e8 | |||
9bbb9711e4 | |||
680405e527 | |||
44595b44c5 | |||
b27c7702f9 | |||
378a3ae05f | |||
836a56b347 | |||
b36ac8f2f8 | |||
b1e7bb899a | |||
7c285750c7 | |||
e93a8b1d32 | |||
42f0b55de0 | |||
253b223e65 | |||
85bfdca578 | |||
4dd9d0d46b | |||
fd1ac5106d | |||
b572b4ecbd | |||
585e104608 | |||
d0aefa99eb | |||
728e95c52b | |||
83087e0f9d | |||
3fb1e37473 | |||
9890966fa4 | |||
aba0fb0000 | |||
0e86ba4b63 | |||
fc23c6721a | |||
f4a129a792 | |||
8deecc0137 | |||
c7966e81c2 | |||
2659c359e9 | |||
e389e51b2b | |||
0ab6b66d8f | |||
8608d8d873 | |||
d34a2c353f | |||
150b0b6b86 | |||
d0e0701a88 | |||
28b20c5ec3 | |||
9088ef182e | |||
c4d1aa452d | |||
66e52b7cfc | |||
e761954cf0 | |||
58829e3560 | |||
62652cf8c1 | |||
4482862a40 | |||
d80ba00590 | |||
81dd4a8450 | |||
101ed629a4 | |||
95ec2fcce7 | |||
73bc3389e5 | |||
bc38a6a795 | |||
e89866bedb | |||
ca09dbbbee | |||
fa4531fd17 | |||
d17c970f8c | |||
6ca62ef131 | |||
8e84e33638 | |||
b9be416937 | |||
0a8c9b22b0 | |||
527c44ed84 | |||
8ee015a847 | |||
e8cabd16d5 | |||
88e07b5ea4 | |||
f3ee8b50e3 | |||
68ad854b0d | |||
789b2e603a | |||
66398fbf77 | |||
daeb3e5187 | |||
1fd1a3a456 | |||
30ac2d220c | |||
ade7bde813 | |||
cba3e100a0 | |||
664d8d3573 | |||
d5ce509e3a | |||
2f10d19c98 | |||
4c787af26d | |||
8136170431 | |||
007916c2c1 | |||
208ffdc1da | |||
4468dc835c | |||
90a2352337 | |||
0a9d14fcb3 | |||
7863fb1087 | |||
072d2a919d | |||
ccbdc9f6d8 | |||
0f5ea16605 | |||
1cd70d7505 | |||
b0775b3f1e | |||
2894668b3e | |||
1096e653b0 | |||
9777d755d5 | |||
58529aa0b2 | |||
64b6c02a22 | |||
0780300fb3 | |||
b9106b633b | |||
23dfaa2933 | |||
cfd2cc4970 | |||
710349768f | |||
f4bd78b86d | |||
ddb7e4e179 | |||
00601f1835 | |||
c31225fdcf | |||
16b99ed0ba | |||
3b6d340603 | |||
99aea0c71c | |||
023e244958 | |||
659d890ecf | |||
8e9ed14b89 | |||
f4bf7316fe | |||
0527f9bf0d | |||
055edd886d | |||
5e70d4121a | |||
f9b5d8bc5e | |||
2917c045fb | |||
6e6ef862c5 | |||
a7fdca05c6 | |||
ddc33dc74a | |||
a562f492e3 | |||
58f0d0b945 | |||
67d1249b2b | |||
66e5e42fb1 | |||
1f01b6438f | |||
bea7ec33c1 | |||
b5561f35b9 | |||
b796cda060 | |||
4c308b7f2f | |||
d50eb9b41b | |||
9168301369 | |||
e8d930f659 | |||
aef88aa03e | |||
ec4370069a | |||
c79ece2b21 | |||
99076af18b | |||
a0e3ad2b70 | |||
e89e734ca2 | |||
9945241b77 | |||
215ed141e7 | |||
f189ee67a1 | |||
babc7d3baf | |||
8f4807020f | |||
31e1410191 | |||
24d7227e27 | |||
c130ca1bc6 | |||
4db960c0a6 | |||
d13ce2aec9 | |||
5e957ecda6 | |||
17a265b197 | |||
3fabc8e1e6 | |||
517ef7cde7 | |||
ad14b763f9 | |||
f74694d5a3 | |||
1ea39abcff | |||
7402589775 | |||
e0cd5a714a | |||
c6eea5de6b | |||
809416e3f0 | |||
040d812343 | |||
72465e6724 | |||
ab480856a5 | |||
6ae497eedc | |||
421bc828ef | |||
ed65886ae5 | |||
8c7e2dbdf9 | |||
afb4209f10 | |||
1d8775d237 | |||
8787ec9fe8 | |||
1f810cd26a | |||
f4d7d19370 | |||
e616b2e247 | |||
2a39332d51 | |||
3c6b10c6b2 | |||
2a9226a55c | |||
3d65fd7cc4 | |||
36ddbfdc85 | |||
76292ef10c | |||
9ae2e528c5 | |||
2849e28c2b | |||
9d0e52b94d | |||
731f5f8523 | |||
9d6d43ee55 | |||
f9e99048c4 | |||
e1df8d14b4 | |||
b9419e0f36 | |||
e03c354e89 | |||
2e44e4d33c | |||
5cbaabeeab | |||
d64e381085 | |||
41306aa7e0 | |||
0bb2e47c98 | |||
ef660be285 | |||
4bac90a3b2 | |||
4182fc203e | |||
10e36c4233 | |||
bef397228f | |||
5cf47767d7 | |||
8f2d2535dc | |||
2aae8e6382 | |||
ba12b0de0d | |||
3552d03f6c | |||
8d5165c449 | |||
d8027656b5 | |||
4f57c5d56e | |||
2c5c81815a | |||
b97bfe9297 | |||
db07657e40 | |||
2d98d0fcc2 | |||
e6f6f17c6d | |||
166a927c20 | |||
625fe8866c | |||
69e7aa9fc9 | |||
a775cfe177 | |||
9e4a2ab824 | |||
24aa1f312a | |||
cde56741fb | |||
bbe694a622 | |||
7e575a718b | |||
ea9ca8b4ed | |||
0fe2884397 | |||
2982a2c963 | |||
6a43e1a64d | |||
3b5172a8fa | |||
be32aeee70 | |||
adcc74ab8d | |||
8acced56b2 | |||
f823c7cb5d | |||
26e6516626 | |||
5979e0cd0c | |||
3ba1bfc369 | |||
efa0e6eb62 | |||
159b4bd7dc | |||
2611c9525e | |||
0353eb4a12 | |||
92c4097f8d | |||
a909c60f05 | |||
56a9eab7eb | |||
7221eb7f39 | |||
b0b0482d71 | |||
49ab559992 | |||
3dd21c635a | |||
835bbb2e44 | |||
2ee2370a71 | |||
54dd65cfe1 | |||
b004aacd69 | |||
48b7b415e2 | |||
544cea95e1 | |||
8aa2632661 | |||
5419e8ae9d | |||
d4d28ab796 | |||
b8db928c58 | |||
bf45a5860e | |||
ca543fc8af | |||
57cf805e12 | |||
1ae9157985 | |||
206a6ae6c9 | |||
82ac590412 | |||
9a274128ce | |||
5664ee7bda | |||
9a56665c6b | |||
8044fb2db0 | |||
3a59ab9f14 | |||
f609a4f26a | |||
80463d12fb | |||
cef05d3553 | |||
5879b0df99 | |||
95cd9dd2b2 | |||
424d5611a5 | |||
9bff68a4f6 | |||
a9bdc655c1 | |||
9b617de6f0 | |||
771270d526 | |||
26d1307476 | |||
86707b9972 | |||
52cb865c5c | |||
3ea027a136 | |||
00469de93e | |||
9bc4e6794d | |||
429127793f | |||
75cb3fcc5f | |||
f0e87da830 | |||
c5639cd9fa | |||
95d4922e44 | |||
bdd52f0111 | |||
7bd07cb351 | |||
249afc5df4 | |||
d7af461173 | |||
b17e9f4ed0 | |||
6862734580 | |||
9e1f645428 | |||
c4818d79f3 | |||
d1a78a58cd | |||
65d0b5b9d9 | |||
614bc2a943 | |||
27b06358ea | |||
e56c01d0e2 | |||
ececca7ad2 | |||
e9cc417fd5 | |||
81a7d17b33 | |||
9382dd6d55 | |||
7aa2a57434 | |||
9b88ea5b60 | |||
8bfcea8054 | |||
f3d2be7a56 | |||
be31182969 | |||
b543063749 | |||
ce0060e6b0 | |||
35b12fe5ec | |||
6ac26094da | |||
8c6a0f68d4 | |||
f5d6672ccf | |||
db06edc5d3 | |||
568927349d | |||
4f812a7f34 | |||
38fc42d352 | |||
b4c5693ac6 | |||
79000aa5e0 | |||
2415381682 | |||
b499e7c682 | |||
d8cde2ae89 | |||
ddc00014be | |||
9ffa3e55c2 | |||
45fe3be83e | |||
dd6fe6a04a | |||
e76b38882c | |||
11bdab7e61 | |||
3d682fe957 | |||
a43e66ef92 | |||
3be7996e79 | |||
5041a4ffa3 | |||
b16b3c0b7f | |||
852ec3f9a0 | |||
dd7b7311b3 | |||
9364bad625 | |||
6fc5244439 | |||
8e1112c1dd | |||
9d1cb1bfaf | |||
216d7d035f | |||
ead6fbdf9c | |||
5b616770df | |||
23a5c5dc09 | |||
74656bf976 | |||
d8a2e0e9a3 | |||
046e46b962 | |||
ec08e4bc6d | |||
05e07ddf5c | |||
757d7479af | |||
440feaf74a | |||
22c50185b5 | |||
3a2c7900d6 | |||
fa8629300f | |||
37dc226996 | |||
4e1f94026c | |||
d27263af97 | |||
215f1af1da | |||
1291b647ae | |||
91df6c236f | |||
58cea7e8b4 | |||
b01f50bd70 | |||
5f48452e3b | |||
dae1b9a996 | |||
a21af0ade4 | |||
28123841ba | |||
183be911d0 | |||
1966809502 | |||
c3c41a61b0 | |||
90849a067f | |||
705f12c1d9 | |||
0826e66fe0 | |||
774769a7ad | |||
e72cecf457 | |||
9c1a3aa244 | |||
2d07c6eedb | |||
fdd92b2dda | |||
8c70189422 | |||
075c83b3a1 | |||
d3a19c5ac7 | |||
080874df10 | |||
24848a1e35 | |||
e215fbbd08 | |||
33aea56ccd | |||
b6683a3010 | |||
578ef04988 | |||
735a7a21bd | |||
80a69224f7 | |||
e0bf17930b | |||
db3177a5aa | |||
98b9839e3d | |||
0db4d89838 | |||
52278f8562 | |||
c19d9597fd | |||
d9d9916ccc | |||
26759c4af2 | |||
e529746294 | |||
0c4d4632ef | |||
e2c1216c1b | |||
d83dbc3670 | |||
0242b30027 | |||
0c656fd276 | |||
b7a3e5989d | |||
5b5f1d1b92 | |||
35bea5e044 | |||
7917cf9f00 | |||
5036672a58 | |||
585ab56ea4 | |||
9009f68e09 | |||
2bacc29d30 | |||
4d7d97e0e6 | |||
f1000a17b4 | |||
f43edbccdc | |||
6b4282eadf | |||
7e2781a2af | |||
32a53450a6 | |||
ce78817f41 | |||
f0e93c2fa9 | |||
fa6bb147ea | |||
220b105efb | |||
b56ad92e25 | |||
fc5fe4b445 | |||
c01d44e37d | |||
5a0e86aa70 | |||
b4529a20e8 | |||
b938adefaa | |||
b39d797c1f | |||
4240bfb7b1 | |||
b7572f107f | |||
379e3d70ca | |||
6fc87fad72 | |||
fa15a2856a | |||
eaec480f42 | |||
d18587330a | |||
5114dfca7d | |||
3395beaa56 | |||
df66d9fcdf | |||
1af1e0b5a3 | |||
9b41f9ecb8 | |||
48ade4993d | |||
4ecc807dbb | |||
86b69cc5d1 | |||
017a13fa3f | |||
ca12b2e30e | |||
db6c804b17 | |||
57ff668d2e | |||
9fb9b16b38 | |||
41178dff90 | |||
12deff5d1b | |||
21a645b1a9 | |||
e8a55aa647 | |||
6295b20545 | |||
850ecf648a | |||
e6cf18ea43 | |||
d28624796c | |||
380c216d77 | |||
ee5a387300 | |||
3ac36879e0 | |||
bc0c9ab698 | |||
5762489070 | |||
fcdc474731 | |||
f491d3e1e1 | |||
94c89eb623 | |||
cf0a18be51 | |||
0621ab6652 | |||
64a028cc76 | |||
f71a45235a | |||
718ee3d545 | |||
e92678ea2c | |||
1f175d4c98 | |||
4d6ccf2540 | |||
aa6c3936d2 | |||
4f05994b36 | |||
b27d6b2cb1 | |||
64f226f7da | |||
5c1606ed82 | |||
11977759ce | |||
bc3dc98b34 | |||
6fadc72553 | |||
a9e6b1ec6b | |||
cbc7b94b02 | |||
45c66e2090 | |||
11b2423544 | |||
1f9907d2ff | |||
fd503fceaf | |||
b7e5790cd1 | |||
3caab5de36 | |||
fa97e819eb | |||
bdc4bf97a7 | |||
cfb0f3961b | |||
8fa965118c | |||
9c800bcb2c | |||
ea4d8c5f49 | |||
5d2abdd1c3 | |||
7d5333db3b | |||
2a8a628b72 | |||
c4d2b787aa | |||
a4e11726cf | |||
9850fbd77d | |||
2ccb91dc6a | |||
3e76ed9122 | |||
2223fd663a | |||
3f960012cd | |||
0b094e2bf2 | |||
2388e1e80b | |||
62e34b69b3 | |||
fd68767216 | |||
93202d4529 | |||
ed1f0eb231 | |||
04612809ab | |||
8cca447e8c | |||
651e86a3c0 | |||
c3c3481ef5 | |||
0732d8bbba | |||
bdca31cc2d | |||
ed7aea8dd3 | |||
e813e44501 | |||
f46c45343a | |||
b12ffb8888 | |||
cf96677c78 | |||
ce03d8eb12 | |||
21dedef7f6 | |||
da7f77867a | |||
3415594877 | |||
0c38729735 | |||
a0b3a48e8b | |||
b662c2eb96 | |||
8cda641350 | |||
efdfeac55e | |||
e0577e15f2 | |||
bb0b0870ea | |||
74a73f9838 | |||
c9f9078726 | |||
eb875ea949 | |||
b4a0e4c0dc | |||
88a0705df1 | |||
7bcd96fc65 | |||
833825ae9a | |||
899383c30c | |||
d9d6cea5a9 | |||
d01ccd5a54 | |||
a896892ac9 | |||
d89d1894d0 | |||
587536ddcc | |||
ced5e1065f | |||
7479173811 | |||
4b83a2d27a | |||
41f72b1236 | |||
c98a6705e6 | |||
6454bf69aa | |||
bd30ea723e | |||
2dd4cb9f7d | |||
1784b4bf50 | |||
8e4b85e29b | |||
7098e56ccf | |||
02ad491dea | |||
708fee535c | |||
7636cc7fe4 | |||
f856e64fb3 | |||
a783a084d4 | |||
81b12d02ec | |||
336df6c65e | |||
35f9299fc6 | |||
649c8319e6 | |||
99cf5871aa | |||
da04e9d801 | |||
2db2d98ef0 | |||
817eacccd8 | |||
ce6d3c6eb2 | |||
ef32e1ce1a | |||
c1105e945e | |||
69b089845c | |||
75556f6c5f | |||
b650d1ef79 | |||
099b571e8f | |||
cb926f7b49 | |||
13515c5eb0 | |||
58d960d914 | |||
4a83bb6c93 | |||
3e56e81d06 | |||
312e9bf5d6 | |||
18d7e64660 | |||
f1118020a1 | |||
63433f1bc8 | |||
921a66554e | |||
bb0d08a721 | |||
fe14e52e77 | |||
24d72ca43c | |||
457f7889df | |||
7b0c0692dc | |||
c4cb3a77cb | |||
aed8d3800b | |||
53a9264b67 | |||
e18fb13616 | |||
2201bd9b09 | |||
da8f6c5682 | |||
14d7ba5cc9 | |||
5ee096232c | |||
c259ef41bd | |||
2c238aea6a | |||
c600c1ebe7 | |||
df94052180 | |||
f878276de7 | |||
cd89304706 | |||
2b9f258126 | |||
85587c0c2a | |||
6cc4ef6c70 | |||
517173bb8c | |||
e415be6c0e | |||
59332562bb | |||
3d8d7787de | |||
611fe41788 | |||
a6118eed8d | |||
d4798d6ee1 | |||
5ea245badf | |||
5ee7847035 | |||
8a812cf03c | |||
9a1cedfd08 | |||
766d1ef374 | |||
beec658872 | |||
b90d701f89 | |||
be5d71ea47 | |||
f1bde69131 | |||
36ae384fb3 | |||
2c4048eb43 | |||
b9195c2668 | |||
bb968304da | |||
ca9bf19041 | |||
ecfee4c542 | |||
acb34561eb | |||
43aec8cdbe | |||
e46d610f77 | |||
1d95861a09 | |||
f48de73236 | |||
0b4daa66b0 | |||
412952182f | |||
014d36b17a | |||
f44f3a8af1 | |||
457514590d | |||
843d8c2242 | |||
ce4ae00a6f | |||
4f7f6a2932 | |||
7039602e4d | |||
acb7aff6fb | |||
8940ee6c3f | |||
3b26b4355e | |||
b56e603c58 | |||
66c2a36123 | |||
8838815737 | |||
834522d002 | |||
f281cd5aa3 | |||
5add5cbd12 | |||
902aad6016 | |||
13f87857cf | |||
e0cc2c9112 | |||
92ab8b831b | |||
6a7a60429f | |||
79fd7d54b2 | |||
ebca840d91 | |||
17b2bcc125 | |||
24a98f8999 | |||
e49b359848 | |||
c9fb381d69 | |||
8224ec49bc | |||
fe7e87ee02 | |||
a3dce8ff19 | |||
89f3cbf318 | |||
3f555a6836 | |||
4fdf5c663c | |||
1ec41a0ab4 | |||
ab0a6b6ca6 | |||
e3bf6fdfc0 | |||
46c0d29c08 | |||
76ccd5668a | |||
c6436eb32f | |||
b2c29117d9 | |||
ffb1dfb012 | |||
88c6fa9933 | |||
60df45a390 | |||
10aa86272b | |||
d37e6ba3b5 | |||
5eee33c7e4 | |||
5e748ae8fc | |||
50e53e788a | |||
7336e1df1a | |||
a724a8fe7d | |||
c731a4e275 | |||
9ef65dcd69 | |||
f99c002426 | |||
f0420c5a6c | |||
46eec5e3a2 | |||
378248341e | |||
803f9d4daf | |||
ce809881eb | |||
1a99893e2d | |||
ec8e57cde9 | |||
a498234f1d | |||
de77cb0cc4 | |||
7d5d53cf85 | |||
1572808adb | |||
9d77e3fc7c | |||
e22f2e9f13 | |||
4ffa4ac42a | |||
da6f548dfd | |||
7532991544 | |||
b7f47317c2 | |||
5849e4f6e3 | |||
868d94f573 | |||
1344ae3a65 | |||
804b155035 | |||
9446e3960b | |||
90ba39184a | |||
d40a73aafe | |||
5815f122ed | |||
0bbb3a20df | |||
1998bce19f | |||
2f1711f783 | |||
34c8b276ab | |||
fde56cfe99 | |||
118033e4a5 | |||
7910d20e50 | |||
e1d5180e6d | |||
79ce13abef | |||
5921c19bc0 | |||
e629ef203a | |||
5959d1366a | |||
530ff3893e | |||
6f59167960 | |||
ca715bb929 | |||
4af0a6a3fa | |||
6486364610 | |||
6aa8a0073b | |||
f5e1b08e6a | |||
7b9ad9d2e5 | |||
1a3762b905 | |||
32fbcf39cc | |||
dd578926c3 | |||
5c99921e15 | |||
d2e4f03d19 | |||
23bba9935f | |||
8a5abc7afc | |||
ec711cb79d | |||
f2ad7fae1f | |||
13a4474512 | |||
b746d8427c | |||
3beaca0d06 | |||
f7647584a3 | |||
f44473d510 | |||
d66a5398d1 | |||
43905caa46 | |||
b47bd22b37 | |||
7f21b7fd7e | |||
0ab4e5af2a | |||
848550771a | |||
d323ac3edc | |||
2e23d4d734 | |||
d9d14b38de | |||
03b7dd2725 | |||
ad0c6bf7d5 | |||
9aed95408d | |||
71844755e5 | |||
0b9dd87ca8 | |||
d704b05b7a | |||
15ebf45f46 | |||
b086f34fa2 | |||
5491634dda | |||
e7bf89b311 | |||
4fdfd3d15e | |||
35a521d762 | |||
f0ae6ffe12 | |||
10b9c65cb7 | |||
02e3f49bce | |||
f6c791f199 | |||
cc62e4db26 | |||
56bb9e92cb | |||
2791251268 | |||
b159bf2c28 | |||
12a0fe39f7 | |||
df6a7b6f5c | |||
8564c5371f | |||
d08212409f | |||
4490e97a13 | |||
2bb367f570 | |||
367f79cb4f | |||
4926865c4e | |||
9ee4086dfa | |||
3e0655cdba | |||
e76b3d61de | |||
1adebefc3e | |||
d1e1d0ac3e | |||
b398448cd9 | |||
02f92fa527 | |||
773d167449 | |||
aa92141ad7 | |||
80624267fd | |||
2030e25ddc | |||
247fff424d | |||
c902d8bc0c | |||
b0e5723a68 | |||
9273bb3f72 | |||
f7d3ccfc70 | |||
d86350af80 | |||
14512988ba | |||
33e1120add | |||
3278d290be | |||
daec3fc3d3 | |||
a6ba58ec41 | |||
65327e0e7e | |||
3ed3712fdc | |||
f46962d236 | |||
aa4778ff07 | |||
e81689f2c0 | |||
4656310a1c | |||
34e58bc5d6 | |||
fbe9d6f529 | |||
e266590813 | |||
b27148d14b | |||
4858a9a817 | |||
3ec53e544c | |||
c52d45cb97 | |||
11531b7630 | |||
a098a27837 | |||
2591bd8c63 | |||
a03fb946d9 | |||
9c58f2a522 | |||
3cb9147f22 | |||
f1d72e2670 | |||
f1e7a01b2e | |||
b88ace4cde | |||
34d7c17e78 | |||
3f1824111d | |||
fbae137442 | |||
9850424251 | |||
918ec9daa8 | |||
7b502a4c7f | |||
7b07e976b8 | |||
e45b169cba | |||
5ebfa10495 | |||
3f93dc2f1d | |||
a43514deb2 | |||
ab77bf3289 | |||
0afe1e4e67 | |||
ef26d539a7 | |||
fce8581321 | |||
ba6abd77c9 | |||
a7295c8f1b | |||
2a310ef187 | |||
530e250573 | |||
6fbc76bc0f | |||
884382bac4 | |||
d97975e9fa | |||
839b264261 | |||
7ef4e5f940 | |||
646aace05b | |||
772ad896c8 | |||
9c4bbe3c63 | |||
c5ca839294 | |||
5337a6dffa | |||
56ce10347e | |||
37bc90c62a | |||
ad7522bba0 | |||
bbcf374886 | |||
99c42582fe | |||
5a56d47f25 | |||
529c98085a | |||
2b955f82b7 | |||
1843fdc060 | |||
ec4e3a6d5c | |||
4ab468e65f | |||
1d18f6947e | |||
df3b6d9d26 | |||
4bbdb73668 | |||
62d3497bbb | |||
e614970c08 | |||
d931331b57 | |||
f18da2609a | |||
2ef9cc118e | |||
33674d3a98 | |||
0167649e6f | |||
cc263ee15d | |||
a4809f2e68 | |||
21770367e2 | |||
6145f734b7 | |||
9d8d305e9d | |||
eb55fd2383 | |||
613d2fb8df | |||
8783742060 | |||
20528e96c7 | |||
3b6c4c1bb5 | |||
cb18dd5200 | |||
ccebdd7a7f | |||
c3efb12733 | |||
d885258dc7 | |||
2da915d0c7 | |||
ae64c58f59 | |||
ff6868b329 | |||
47ef193600 | |||
c2f4969d4f | |||
08c98967e0 | |||
8d091f6f83 | |||
58094987ff | |||
ce26ef97e4 | |||
8f9bd4a299 | |||
45dd7d8770 | |||
0f10d984c3 | |||
2e5d981a09 | |||
c74254c2cb | |||
271fda7c91 | |||
0e5886ace1 | |||
4b89c5f900 | |||
dcab255d59 | |||
fc8512be39 | |||
e10ef4aaae | |||
0b70ca8451 | |||
555d9ee763 | |||
121b801baa | |||
9adcecbbf1 | |||
9f131d998d | |||
cd0a04f02a | |||
aaf5684f9c | |||
2f0cb044a5 | |||
8b55757a0b | |||
84fae6e07e | |||
63e220a763 | |||
a96fc21f88 | |||
1ba5b25b29 | |||
a871f2344a | |||
a217bc0715 | |||
34ab4d8360 | |||
48f1c3a49e | |||
692376e830 | |||
cc99df5ef1 | |||
d255a2a050 | |||
c07835f3ad | |||
78a5067434 | |||
cdeb8de75d | |||
606547ecb4 | |||
3b809b38e8 | |||
7c49a42b68 | |||
87823b0cb5 | |||
ebf845f431 | |||
ce6df93d05 | |||
e7958bebac | |||
233afebdf0 | |||
56069af42d | |||
376d22e331 | |||
7fc8ff60fd | |||
1f4791a191 | |||
2ac7a4d48d | |||
01386f4d58 | |||
1086fbe9b5 | |||
a83bd4ab20 | |||
26caf7e1b2 | |||
dd2a0e35f4 | |||
6a4eabf5c7 | |||
0e2c888f73 | |||
c140da5740 | |||
586c0ea3d8 | |||
d6f4189c7b | |||
7a820b1304 | |||
767201c40d | |||
3c3614a120 | |||
9e24e452a5 | |||
2cffff0c1b | |||
cf2e9cf481 | |||
6b2c7a4c86 | |||
98e199f7b5 | |||
10e463180e | |||
c9d0003818 | |||
e2a21afca8 | |||
2ea209bcc0 | |||
e049ca8ebf | |||
9037a9467b | |||
4c6cf36aa5 | |||
c92211c016 | |||
8bd6b5b913 | |||
b67fe31544 | |||
c8adb06ca7 | |||
9695331eed | |||
d42cfab6ef | |||
2b7c811402 | |||
c6cb491e77 | |||
e2a4632159 | |||
65f0edd14b | |||
b2c466bca6 | |||
6b4e577032 | |||
b12a3dd0e5 | |||
d856ac92f4 | |||
f5856b0914 | |||
8c675a0d31 | |||
86a0e77065 | |||
72c27bd095 | |||
e4e27b6e11 | |||
475d32045f | |||
3643ee6dfd | |||
32e4535f24 | |||
daa2148136 | |||
9097e865ca | |||
894d3e7452 | |||
5a5c65ee4b | |||
8b35239bce | |||
87e2fa137a | |||
46f64c6fdc | |||
10536f70f3 | |||
0812a08bfb | |||
5706eddee3 | |||
0b429fde24 | |||
388ff78a26 | |||
7d46177cf3 | |||
8a0bd20e84 | |||
a1a5a3646b | |||
453c11b4b5 | |||
c66b97126f | |||
0646f1118c | |||
0bcfa12e0d | |||
b2ec32fdf0 | |||
8f00848ff9 | |||
604025fe34 | |||
98126e2981 | |||
db9b88089e | |||
a35a71fd82 | |||
558cd58d09 | |||
410f3ef0f0 | |||
ae765c71fd | |||
e5684bc34c | |||
b4a7e7e6e9 | |||
41669e60c8 | |||
eeaca50dee | |||
d8d88cd395 | |||
9aabafeb41 | |||
9ced5915ff | |||
9d0be7d96f | |||
57a6465ba0 | |||
5cc6505512 | |||
3d45f77692 | |||
e01974b7ab | |||
1f01677b7b | |||
58ee2bf06a | |||
7bf09559a6 | |||
8dea08929a | |||
26f31da711 | |||
d95a065e3d | |||
ed50210832 | |||
ceafe434b5 | |||
89b374cb16 | |||
47c1f475bf | |||
61e027b227 | |||
58ab5aa887 | |||
2b2117173c | |||
f2a79cf381 | |||
ad9449bf00 | |||
c2f8f4bd9b | |||
8b6232ac87 | |||
93a965e3e2 | |||
217c2bae99 | |||
b9bbf0c10f | |||
a54f9719e5 | |||
a5470b2362 | |||
c1bf9fd897 | |||
f3036b8cfd | |||
9b6b817276 | |||
9e3c64aa84 | |||
920e0acb85 | |||
b7d3623e53 | |||
3676a8a48d | |||
f85a1d003c | |||
121e8678b6 | |||
e4c512e33d | |||
81df42d63b | |||
6802a4ee21 | |||
c0ce78f892 | |||
221f36ca65 | |||
125e60d06a | |||
83458510a9 | |||
eac5f62959 | |||
b19cc799aa | |||
efa56d0147 | |||
47f6d20131 | |||
e0b4ab09eb | |||
8abf28093a | |||
d1687df067 | |||
e77219a59f | |||
22edb37162 | |||
1ac87715ff | |||
de162c9aea |
@ -12,3 +12,17 @@ rustflags = ["-C", "link-args=-stack:10000000", "-C", "target-feature=+crt-stati
|
||||
# set a 2 gb stack size (0x80000000 = 2147483648 bytes = 2 GB)
|
||||
# [target.x86_64-apple-darwin]
|
||||
# rustflags = ["-C", "link-args=-Wl,-stack_size,0x80000000"]
|
||||
|
||||
# How to use mold in linux and mac
|
||||
|
||||
# [target.x86_64-unknown-linux-gnu]
|
||||
# linker = "clang"
|
||||
# rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/mold"]
|
||||
|
||||
# [target.x86_64-apple-darwin]
|
||||
# linker = "clang"
|
||||
# rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
|
||||
# [target.aarch64-apple-darwin]
|
||||
# linker = "clang"
|
||||
# rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
|
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -53,7 +53,7 @@ body:
|
||||
| features | clipboard-cli, ctrlc, dataframe, default, rustyline, term, trash, uuid, which, zip |
|
||||
| installed_plugins | binaryview, chart bar, chart line, fetch, from bson, from sqlite, inc, match, post, ps, query json, s3, selector, start, sys, textview, to bson, to sqlite, tree, xpath |
|
||||
validations:
|
||||
required: false
|
||||
required: true
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
|
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -1,5 +1,6 @@
|
||||
name: Feature Request
|
||||
description: "When you want a new feature for something that doesn't already exist"
|
||||
labels: "enhancement"
|
||||
body:
|
||||
- type: textarea
|
||||
id: problem
|
||||
|
21
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
21
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
name: Question
|
||||
description: "When you have a question to ask"
|
||||
labels: "question"
|
||||
body:
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Question
|
||||
description: Leave your question here
|
||||
placeholder: |
|
||||
A clear and concise question
|
||||
Example: Is there any equivalent of bash's $CDPATH in Nu?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Additional context and details
|
||||
description: Add any other context, screenshots or other media that will help us understand your question here, if needed.
|
||||
validations:
|
||||
required: false
|
20
.github/dependabot.yml
vendored
Normal file
20
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
# docs
|
||||
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
ignore:
|
||||
- dependency-name: "*"
|
||||
update-types: ["version-update:semver-patch"]
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
32
.github/pull_request_template.md
vendored
32
.github/pull_request_template.md
vendored
@ -1,17 +1,31 @@
|
||||
|
||||
# Description
|
||||
|
||||
(description of your pull request here)
|
||||
_(Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes.)_
|
||||
|
||||
# Tests
|
||||
_(Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience.)_
|
||||
|
||||
Make sure you've done the following:
|
||||
# User-Facing Changes
|
||||
|
||||
- [ ] Add tests that cover your changes, either in the command examples, the crate/tests folder, or in the /tests folder.
|
||||
- [ ] Try to think about corner cases and various ways how your changes could break. Cover them with tests.
|
||||
- [ ] If adding tests is not possible, please document in the PR body a minimal example with steps on how to reproduce so one can verify your change works.
|
||||
_(List of all changes that impact the user experience here. This helps us keep track of breaking changes.)_
|
||||
|
||||
# Tests + Formatting
|
||||
|
||||
Don't forget to add tests that cover your changes.
|
||||
|
||||
Make sure you've run and fixed any issues with these commands:
|
||||
|
||||
- [ ] `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
||||
- [ ] `cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
|
||||
- [ ] `cargo test --workspace --features=extra` to check that all the tests pass
|
||||
- `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
||||
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
|
||||
- `cargo test --workspace` to check that all tests pass
|
||||
|
||||
> **Note**
|
||||
> from `nushell` you can also use the `toolkit` as follows
|
||||
> ```bash
|
||||
> use toolkit.nu # or use an `env_change` hook to activate it automatically
|
||||
> toolkit check pr
|
||||
> ```
|
||||
|
||||
# After Submitting
|
||||
|
||||
If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
|
||||
|
177
.github/workflows/ci.yml
vendored
177
.github/workflows/ci.yml
vendored
@ -11,40 +11,42 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
platform: [windows-latest, macos-latest, ubuntu-latest]
|
||||
# Pinning to Ubuntu 20.04 because building on newer Ubuntu versions causes linux-gnu
|
||||
# builds to link against a too-new-for-many-Linux-installs glibc version. Consider
|
||||
# revisiting this when 20.04 is closer to EOL (April 2025)
|
||||
platform: [windows-latest, macos-latest, ubuntu-20.04]
|
||||
style: [default, dataframe]
|
||||
rust:
|
||||
- stable
|
||||
include:
|
||||
- style: default
|
||||
flags: ""
|
||||
- style: dataframe
|
||||
flags: "--features=dataframe "
|
||||
exclude:
|
||||
# only test dataframes on Ubuntu (the fastest platform)
|
||||
- platform: windows-latest
|
||||
style: dataframe
|
||||
- platform: macos-latest
|
||||
style: dataframe
|
||||
|
||||
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
NUSHELL_CARGO_TARGET: ci
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.4.3
|
||||
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
with:
|
||||
key: "v2" # increment this to bust the cache if needed
|
||||
|
||||
- name: Rustfmt
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
- name: cargo fmt
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
- name: Clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --features=extra --workspace --exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||
run: cargo clippy --workspace ${{ matrix.flags }}--exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||
|
||||
nu-tests:
|
||||
env:
|
||||
@ -53,44 +55,32 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
platform: [windows-latest, macos-latest, ubuntu-latest]
|
||||
style: [extra, default]
|
||||
platform: [windows-latest, macos-latest, ubuntu-20.04]
|
||||
style: [default, dataframe]
|
||||
rust:
|
||||
- stable
|
||||
include:
|
||||
- style: extra
|
||||
flags: "--features=extra"
|
||||
- style: default
|
||||
flags: ""
|
||||
- style: dataframe
|
||||
flags: "--features=dataframe"
|
||||
exclude:
|
||||
# only test dataframes on Ubuntu (the fastest platform)
|
||||
- platform: windows-latest
|
||||
style: default
|
||||
style: dataframe
|
||||
- platform: macos-latest
|
||||
style: default
|
||||
style: dataframe
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
# Temporarily disabled; the cache was getting huge (2.6GB compressed) on Windows and causing issues.
|
||||
# TODO: investigate why the cache was so big
|
||||
# - uses: Swatinem/rust-cache@v1
|
||||
# with:
|
||||
# key: ${{ matrix.style }}v3 # increment this to bust the cache if needed
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.4.3
|
||||
|
||||
- name: Tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
|
||||
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
|
||||
|
||||
python-virtualenv:
|
||||
env:
|
||||
@ -99,7 +89,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
platform: [ubuntu-20.04, macos-latest, windows-latest]
|
||||
rust:
|
||||
- stable
|
||||
py:
|
||||
@ -108,41 +98,35 @@ jobs:
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
with:
|
||||
key: "2" # increment this to bust the cache if needed
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.4.3
|
||||
|
||||
- name: Install Nushell
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: --path=. --profile ci --no-default-features
|
||||
run: cargo install --locked --path=. --profile ci --no-default-features
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- run: python -m pip install tox
|
||||
|
||||
# Get only the latest tagged version for stability reasons
|
||||
- name: Install virtualenv
|
||||
run: |
|
||||
git clone https://github.com/kubouch/virtualenv.git && \
|
||||
cd virtualenv && \
|
||||
git checkout engine-q-update
|
||||
run: git clone https://github.com/pypa/virtualenv.git
|
||||
shell: bash
|
||||
|
||||
- name: Test Nushell in virtualenv
|
||||
run: cd virtualenv && tox -e ${{ matrix.py }} -- -k nushell
|
||||
run: |
|
||||
cd virtualenv
|
||||
# if we encounter problems with bleeding edge tests pin to the latest tag
|
||||
# git checkout $(git describe --tags | cut -d - -f 1)
|
||||
# We need to disable failing on coverage levels.
|
||||
nu -c "open pyproject.toml | upsert tool.coverage.report.fail_under 1 | save patchproject.toml"
|
||||
mv patchproject.toml pyproject.toml
|
||||
tox -e ${{ matrix.py }} -- -k nushell
|
||||
shell: bash
|
||||
|
||||
# Build+test plugins on their own, without the rest of Nu. This helps with CI parallelization and
|
||||
@ -154,30 +138,59 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
platform: [windows-latest, macos-latest, ubuntu-latest]
|
||||
platform: [windows-latest, macos-latest, ubuntu-20.04]
|
||||
rust:
|
||||
- stable
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.4.3
|
||||
|
||||
- name: Clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||
run: cargo clippy --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||
|
||||
- name: Tests
|
||||
uses: actions-rs/cargo@v1
|
||||
run: cargo test --profile ci --package nu_plugin_*
|
||||
|
||||
|
||||
nu-coverage:
|
||||
needs: nu-tests
|
||||
env:
|
||||
NUSHELL_CARGO_TARGET: ci
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
# disabled mac due to problems with merging coverage and similarity to linux
|
||||
# disabled windows due to running out of disk space when having too many crates or tests
|
||||
platform: [ubuntu-20.04] # windows-latest
|
||||
rust:
|
||||
- stable
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.4.3
|
||||
- name: Install cargo-llvm-cov
|
||||
uses: taiki-e/install-action@cargo-llvm-cov
|
||||
|
||||
- name: Tests
|
||||
shell: bash
|
||||
run: |
|
||||
source <(cargo llvm-cov show-env --export-prefix) # Set the environment variables needed to get coverage.
|
||||
cargo llvm-cov clean --workspace # Remove artifacts that may affect the coverage results.
|
||||
cargo build --workspace --profile ci
|
||||
cargo test --workspace --profile ci
|
||||
cargo llvm-cov report --profile ci --lcov --output-path lcov.info
|
||||
|
||||
- name: Upload coverage reports to Codecov with GitHub Action
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
command: test
|
||||
args: --profile ci --package nu_plugin_*
|
||||
files: lcov.info
|
||||
|
98
.github/workflows/release-pkg.nu
vendored
98
.github/workflows/release-pkg.nu
vendored
@ -6,6 +6,40 @@
|
||||
# REF:
|
||||
# 1. https://github.com/volks73/cargo-wix
|
||||
|
||||
# Instructions for manually creating an MSI for Winget Releases when they fail
|
||||
# Added 2022-11-29 when Windows packaging wouldn't work
|
||||
# Updated again on 2023-02-23 because msis are still failing validation
|
||||
# To run this manual for windows here are the steps I take
|
||||
# checkout the release you want to publish
|
||||
# 1. git checkout 0.76.0
|
||||
# unset CARGO_TARGET_DIR if set (I have to do this in the parent shell to get it to work)
|
||||
# 2. $env:CARGO_TARGET_DIR = ""
|
||||
# 2. hide-env CARGO_TARGET_DIR
|
||||
# 3. let-env TARGET = 'x86_64-pc-windows-msvc'
|
||||
# 4. let-env TARGET_RUSTFLAGS = ''
|
||||
# 5. let-env GITHUB_WORKSPACE = 'C:\Users\dschroeder\source\repos\forks\nushell'
|
||||
# 6. let-env GITHUB_OUTPUT = 'C:\Users\dschroeder\source\repos\forks\nushell\output\out.txt'
|
||||
# 7. let-env OS = 'windows-latest'
|
||||
# make sure 7z.exe is in your path https://www.7-zip.org/download.html
|
||||
# 8. let-env Path = ($env.Path | append 'c:\apps\7-zip')
|
||||
# make sure aria2c.exe is in your path https://github.com/aria2/aria2
|
||||
# 9. let-env Path = ($env.Path | append 'c:\path\to\aria2c')
|
||||
# make sure you have the wixtools installed https://wixtoolset.org/
|
||||
# 10. let-env Path = ($env.Path | append 'C:\Users\dschroeder\AppData\Local\tauri\WixTools')
|
||||
# You need to run the release-pkg twice. The first pass, with _EXTRA_ as 'bin', makes the output
|
||||
# folder and builds everything. The second pass, that generates the msi file, with _EXTRA_ as 'msi'
|
||||
# 11. let-env _EXTRA_ = 'bin'
|
||||
# 12. source .github\workflows\release-pkg.nu
|
||||
# 13. cd ..
|
||||
# 14. let-env _EXTRA_ = 'msi'
|
||||
# 15. source .github\workflows\release-pkg.nu
|
||||
# After msi is generated, you have to update winget-pkgs repo, you'll need to patch the release
|
||||
# by deleting the existing msi and uploading this new msi. Then you'll need to update the hash
|
||||
# on the winget-pkgs PR. To generate the hash, run this command
|
||||
# 16. open target\wix\nu-0.74.0-x86_64-pc-windows-msvc.msi | hash sha256
|
||||
# Then, just take the output and put it in the winget-pkgs PR for the hash on the msi
|
||||
|
||||
|
||||
# The main binary file to be released
|
||||
let bin = 'nu'
|
||||
let os = $env.OS
|
||||
@ -16,8 +50,13 @@ let flags = $env.TARGET_RUSTFLAGS
|
||||
let dist = $'($env.GITHUB_WORKSPACE)/output'
|
||||
let version = (open Cargo.toml | get package.version)
|
||||
|
||||
$'Debugging info:'
|
||||
print { version: $version, bin: $bin, os: $os, target: $target, src: $src, flags: $flags, dist: $dist }; hr-line -b
|
||||
|
||||
# $env
|
||||
|
||||
let USE_UBUNTU = 'ubuntu-20.04'
|
||||
|
||||
$'(char nl)Packaging ($bin) v($version) for ($target) in ($src)...'; hr-line -b
|
||||
if not ('Cargo.lock' | path exists) { cargo generate-lockfile }
|
||||
|
||||
@ -26,8 +65,9 @@ $'Start building ($bin)...'; hr-line
|
||||
# ----------------------------------------------------------------------------
|
||||
# Build for Ubuntu and macOS
|
||||
# ----------------------------------------------------------------------------
|
||||
if $os in ['ubuntu-latest', 'macos-latest'] {
|
||||
if $os == 'ubuntu-latest' {
|
||||
if $os in [$USE_UBUNTU, 'macos-latest'] {
|
||||
if $os == $USE_UBUNTU {
|
||||
sudo apt update
|
||||
sudo apt-get install libxcb-composite0-dev -y
|
||||
}
|
||||
if $target == 'aarch64-unknown-linux-gnu' {
|
||||
@ -38,10 +78,14 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
||||
sudo apt-get install pkg-config gcc-arm-linux-gnueabihf -y
|
||||
let-env CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER = 'arm-linux-gnueabihf-gcc'
|
||||
cargo-build-nu $flags
|
||||
} else if $target == 'riscv64gc-unknown-linux-gnu' {
|
||||
sudo apt-get install gcc-riscv64-linux-gnu -y
|
||||
let-env CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER = 'riscv64-linux-gnu-gcc'
|
||||
cargo-build-nu $flags
|
||||
} else {
|
||||
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
|
||||
# Actually just for x86_64-unknown-linux-musl target
|
||||
sudo apt install musl-tools -y
|
||||
if $os == $USE_UBUNTU { sudo apt install musl-tools -y }
|
||||
cargo-build-nu $flags
|
||||
}
|
||||
}
|
||||
@ -50,10 +94,10 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
||||
# Build for Windows without static-link-openssl feature
|
||||
# ----------------------------------------------------------------------------
|
||||
if $os in ['windows-latest'] {
|
||||
if ($flags | str trim | empty?) {
|
||||
cargo build --release --all --target $target --features=extra
|
||||
if ($flags | str trim | is-empty) {
|
||||
cargo build --release --all --target $target
|
||||
} else {
|
||||
cargo build --release --all --target $target --features=extra $flags
|
||||
cargo build --release --all --target $target $flags
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,11 +120,11 @@ cp -v README.release.txt $'($dist)/README.txt'
|
||||
|
||||
$'(char nl)Check binary release version detail:'; hr-line
|
||||
let ver = if $os == 'windows-latest' {
|
||||
(do -i { ./output/nu.exe -c 'version' }) | str collect
|
||||
(do -i { ./output/nu.exe -c 'version' }) | str join
|
||||
} else {
|
||||
(do -i { ./output/nu -c 'version' }) | str collect
|
||||
(do -i { ./output/nu -c 'version' }) | str join
|
||||
}
|
||||
if ($ver | str trim | empty?) {
|
||||
if ($ver | str trim | is-empty) {
|
||||
$'(ansi r)Incompatible nu binary...(ansi reset)'
|
||||
} else { $ver }
|
||||
|
||||
@ -88,21 +132,28 @@ if ($ver | str trim | empty?) {
|
||||
# Create a release archive and send it to output for the following steps
|
||||
# ----------------------------------------------------------------------------
|
||||
cd $dist; $'(char nl)Creating release archive...'; hr-line
|
||||
if $os in ['ubuntu-latest', 'macos-latest'] {
|
||||
if $os in [$USE_UBUNTU, 'macos-latest'] {
|
||||
|
||||
$'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls
|
||||
let files = (ls | get name)
|
||||
let dest = $'($bin)-($version)-($target)'
|
||||
let archive = $'($dist)/($dest).tar.gz'
|
||||
|
||||
let archive = $'($dist)/($bin)-($version)-($target).tar.gz'
|
||||
tar czf $archive *
|
||||
mkdir $dest
|
||||
$files | each {|it| mv $it $dest } | ignore
|
||||
|
||||
$'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls $dest
|
||||
|
||||
tar -czf $archive $dest
|
||||
print $'archive: ---> ($archive)'; ls $archive
|
||||
echo $'::set-output name=archive::($archive)'
|
||||
# REF: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
|
||||
echo $"archive=($archive)" | save --append $env.GITHUB_OUTPUT
|
||||
|
||||
} else if $os == 'windows-latest' {
|
||||
|
||||
let releaseStem = $'($bin)-($version)-($target)'
|
||||
|
||||
$'(char nl)Download less related stuffs...'; hr-line
|
||||
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v590/less.exe -o less.exe
|
||||
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v608/less.exe -o less.exe
|
||||
aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt
|
||||
|
||||
# Create Windows msi release package
|
||||
@ -113,9 +164,10 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
||||
cd $src; hr-line
|
||||
# Wix need the binaries be stored in target/release/
|
||||
cp -r $'($dist)/*' target/release/
|
||||
cargo install cargo-wix --version 0.3.2
|
||||
cargo install cargo-wix --version 0.3.4
|
||||
cargo wix --no-build --nocapture --package nu --output $wixRelease
|
||||
echo $'::set-output name=archive::($wixRelease)'
|
||||
print $'archive: ---> ($wixRelease)';
|
||||
echo $"archive=($wixRelease)" | save --append $env.GITHUB_OUTPUT
|
||||
|
||||
} else {
|
||||
|
||||
@ -124,17 +176,17 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
||||
7z a $archive *
|
||||
print $'archive: ---> ($archive)';
|
||||
let pkg = (ls -f $archive | get name)
|
||||
if not ($pkg | empty?) {
|
||||
echo $'::set-output name=archive::($pkg | get 0)'
|
||||
if not ($pkg | is-empty) {
|
||||
echo $"archive=($pkg | get 0)" | save --append $env.GITHUB_OUTPUT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def 'cargo-build-nu' [ options: string ] {
|
||||
if ($options | str trim | empty?) {
|
||||
cargo build --release --all --target $target --features=extra,static-link-openssl
|
||||
if ($options | str trim | is-empty) {
|
||||
cargo build --release --all --target $target --features=static-link-openssl
|
||||
} else {
|
||||
cargo build --release --all --target $target --features=extra,static-link-openssl $options
|
||||
cargo build --release --all --target $target --features=static-link-openssl $options
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,7 +195,7 @@ def 'hr-line' [
|
||||
--blank-line(-b): bool
|
||||
] {
|
||||
print $'(ansi g)---------------------------------------------------------------------------->(ansi reset)'
|
||||
if $blank-line { char nl }
|
||||
if $blank_line { char nl }
|
||||
}
|
||||
|
||||
# Get the specified env key's value or ''
|
||||
|
33
.github/workflows/release.yml
vendored
33
.github/workflows/release.yml
vendored
@ -27,6 +27,7 @@ jobs:
|
||||
- x86_64-unknown-linux-musl
|
||||
- aarch64-unknown-linux-gnu
|
||||
- armv7-unknown-linux-gnueabihf
|
||||
- riscv64gc-unknown-linux-gnu
|
||||
extra: ['bin']
|
||||
include:
|
||||
- target: aarch64-apple-darwin
|
||||
@ -44,35 +45,37 @@ jobs:
|
||||
os: windows-latest
|
||||
target_rustflags: ''
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
os: ubuntu-20.04
|
||||
target_rustflags: ''
|
||||
- target: x86_64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
os: ubuntu-20.04
|
||||
target_rustflags: ''
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
os: ubuntu-20.04
|
||||
target_rustflags: ''
|
||||
- target: armv7-unknown-linux-gnueabihf
|
||||
os: ubuntu-latest
|
||||
os: ubuntu-20.04
|
||||
target_rustflags: ''
|
||||
- target: riscv64gc-unknown-linux-gnu
|
||||
os: ubuntu-20.04
|
||||
target_rustflags: ''
|
||||
|
||||
runs-on: ${{matrix.os}}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.0.2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust Toolchain Components
|
||||
uses: actions-rs/toolchain@v1.0.6
|
||||
with:
|
||||
override: true
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
target: ${{ matrix.target }}
|
||||
- name: Update Rust Toolchain Target
|
||||
run: |
|
||||
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||
|
||||
- name: Setup Rust toolchain and cache
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.4.3
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v1
|
||||
uses: hustcer/setup-nu@v3
|
||||
with:
|
||||
version: 0.63.0
|
||||
version: 0.72.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@ -88,7 +91,7 @@ jobs:
|
||||
|
||||
# REF: https://github.com/marketplace/actions/gh-release
|
||||
- name: Publish Archive
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v0.1.13
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
with:
|
||||
draft: true
|
||||
|
28
.github/workflows/stale.yml
vendored
28
.github/workflows/stale.yml
vendored
@ -1,28 +0,0 @@
|
||||
name: 'Close stale issues and PRs'
|
||||
#on: [workflow_dispatch]
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
with:
|
||||
#debug-only: true
|
||||
ascending: true
|
||||
operations-per-run: 520
|
||||
enable-statistics: true
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
close-issue-message: 'This issue has been marked stale for more than 100000 days without activity. Closing this issue, but if you find that the issue is still valid, please reopen.'
|
||||
close-pr-message: 'This PR has been marked stale for more than 100 days without activity. Closing this PR, but if you are still working on it, please reopen.'
|
||||
days-before-issue-stale: 90
|
||||
days-before-pr-stale: 45
|
||||
days-before-issue-close: 100000
|
||||
days-before-pr-close: 100
|
||||
exempt-issue-labels: 'exempt,keep'
|
13
.github/workflows/typos.yml
vendored
Normal file
13
.github/workflows/typos.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
name: Typos
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
run:
|
||||
name: Spell Check with Typos
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check spelling
|
||||
uses: crate-ci/typos@master
|
21
.github/workflows/winget-submission.yml
vendored
21
.github/workflows/winget-submission.yml
vendored
@ -1,9 +1,14 @@
|
||||
name: Submit Nushell package to Windows Package Manager Community Repository
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
types: [released]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag_name:
|
||||
description: 'Specific tag name'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
|
||||
@ -12,8 +17,10 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Submit package to Windows Package Manager Community Repository
|
||||
run: |
|
||||
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
|
||||
$github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json
|
||||
$installerUrl = $github.release.assets | Where-Object -Property name -match 'windows-msvc.msi' | Select -ExpandProperty browser_download_url -First 1
|
||||
.\wingetcreate.exe update Nushell.Nushell -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.NUSHELL_PAT }}
|
||||
uses: vedantmgoyal2009/winget-releaser@v2
|
||||
with:
|
||||
identifier: Nushell.Nushell
|
||||
version: ${{ inputs.tag_name || github.event.release.tag_name }}
|
||||
release-tag: ${{ inputs.tag_name || github.event.release.tag_name }}
|
||||
token: ${{ secrets.NUSHELL_PAT }}
|
||||
fork-user: fdncred
|
||||
|
17
.gitignore
vendored
17
.gitignore
vendored
@ -22,6 +22,10 @@ debian/nu/
|
||||
# VSCode's IDE items
|
||||
.vscode/*
|
||||
|
||||
# Visual Studio Extension SourceGear Rust items
|
||||
VSWorkspaceSettings.json
|
||||
unstable_cargo_features.txt
|
||||
|
||||
# Helix configuration folder
|
||||
.helix/*
|
||||
.helix
|
||||
@ -29,3 +33,16 @@ debian/nu/
|
||||
# Coverage tools
|
||||
lcov.info
|
||||
tarpaulin-report.html
|
||||
|
||||
# Visual Studio
|
||||
.vs/*
|
||||
*.rsproj
|
||||
*.rsproj.user
|
||||
*.sln
|
||||
|
||||
# direnv
|
||||
.direnv/
|
||||
.envrc
|
||||
|
||||
# pre-commit-hooks
|
||||
.pre-commit-config.yaml
|
||||
|
12
.typos.toml
Normal file
12
.typos.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[files]
|
||||
extend-exclude = ["crates/nu-command/tests/commands/table.rs", "*.tsv", "*.json", "*.txt"]
|
||||
|
||||
[default.extend-words]
|
||||
# Ignore false-positives
|
||||
nd = "nd"
|
||||
fo = "fo"
|
||||
ons = "ons"
|
||||
ba = "ba"
|
||||
Plasticos = "Plasticos"
|
||||
IIF = "IIF"
|
||||
numer = "numer"
|
@ -1,8 +1,23 @@
|
||||
# Contributing
|
||||
|
||||
Welcome to Nushell!
|
||||
Welcome to Nushell and thank you for considering contributing!
|
||||
|
||||
To get live support from the community see our [Discord](https://discordapp.com/invite/NtAbbGn), [Twitter](https://twitter.com/nu_shell) or file an issue or feature request here on [GitHub](https://github.com/nushell/nushell/issues/new/choose)!
|
||||
## Review Process
|
||||
|
||||
First of all, before diving into the code, if you want to create a new feature, change something significantly, and especially if the change is user-facing, it is a good practice to first get an approval from the core team before starting to work on it.
|
||||
This saves both your and our time if we realize the change needs to go another direction before spending time on it.
|
||||
So, please, reach out and tell us what you want to do.
|
||||
This will significantly increase the chance of your PR being accepted.
|
||||
|
||||
The review process can be summarized as follows:
|
||||
1. You want to make some change to Nushell that is more involved than simple bug-fixing.
|
||||
2. Go to [Discord](https://discordapp.com/invite/NtAbbGn) or a [GitHub issue](https://github.com/nushell/nushell/issues/new/choose) and chat with some core team members and/or other contributors about it.
|
||||
3. After getting a green light from the core team, implement the feature, open a pull request (PR) and write a concise but comprehensive description of the change.
|
||||
4. If your PR includes any use-facing features (such as adding a flag to a command), clearly list them in the PR description.
|
||||
5. Then, core team members and other regular contributors will review the PR and suggest changes.
|
||||
6. When we all agree, the PR will be merged.
|
||||
7. If your PR includes any user-facing features, make sure the changes are also reflected in [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged.
|
||||
8. Congratulate yourself, you just improved Nushell! :-)
|
||||
|
||||
## Developing
|
||||
|
||||
@ -16,6 +31,18 @@ cd nushell
|
||||
cargo build
|
||||
```
|
||||
|
||||
### Tests
|
||||
|
||||
It is a good practice to cover your changes with a test. Also, try to think about corner cases and various ways how your changes could break. Cover those in the tests as well.
|
||||
|
||||
Tests can be found in different places:
|
||||
* `/tests`
|
||||
* `src/tests`
|
||||
* command examples
|
||||
* crate-specific tests
|
||||
|
||||
The most comprehensive test suite we have is the `nu-test-support` crate. For testing specific features, such as running Nushell in a REPL mode, we have so called "testbins". For simple tests, you can find `run_test()` and `fail_test()` functions.
|
||||
|
||||
### Useful Commands
|
||||
|
||||
- Build and run Nushell:
|
||||
@ -24,21 +51,21 @@ cargo build
|
||||
cargo run
|
||||
```
|
||||
|
||||
- Build and run with extra features. Currently extra features include dataframes and sqlite database support.
|
||||
- Build and run with dataframe support.
|
||||
```shell
|
||||
cargo run --features=extra
|
||||
cargo run --features=dataframe
|
||||
```
|
||||
|
||||
- Run Clippy on Nushell:
|
||||
|
||||
```shell
|
||||
cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||
cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||
```
|
||||
|
||||
- Run all tests:
|
||||
|
||||
```shell
|
||||
cargo test --workspace --features=extra
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
- Run all tests for a specific command
|
||||
@ -64,5 +91,11 @@ cargo build
|
||||
- To view verbose logs when developing, enable the `trace` log level.
|
||||
|
||||
```shell
|
||||
cargo run --release --features=extra -- --log-level trace
|
||||
cargo run --release -- --log-level trace
|
||||
```
|
||||
|
||||
- To redirect trace logs to a file, enable the `--log-target file` switch.
|
||||
```shell
|
||||
cargo run --release -- --log-level trace --log-target file
|
||||
open $"($nu.temp-path)/nu-($nu.pid).log"
|
||||
```
|
||||
|
3512
Cargo.lock
generated
3512
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
125
Cargo.toml
125
Cargo.toml
@ -3,24 +3,31 @@ authors = ["The Nushell Project Developers"]
|
||||
default-run = "nu"
|
||||
description = "A new type of shell"
|
||||
documentation = "https://www.nushell.sh/book/"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
exclude = ["images"]
|
||||
homepage = "https://www.nushell.sh"
|
||||
license = "MIT"
|
||||
name = "nu"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
rust-version = "1.60"
|
||||
version = "0.65.0"
|
||||
version = "0.77.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[package.metadata.binstall]
|
||||
pkg-url = "{ repo }/releases/download/{ version }/{ name }-{ version }-{ target }.{ archive-format }"
|
||||
pkg-fmt = "tgz"
|
||||
|
||||
[package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
|
||||
pkg-fmt = "zip"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/nu-cli",
|
||||
"crates/nu-engine",
|
||||
"crates/nu-parser",
|
||||
"crates/nu-system",
|
||||
"crates/nu-cmd-lang",
|
||||
"crates/nu-command",
|
||||
"crates/nu-protocol",
|
||||
"crates/nu-plugin",
|
||||
@ -28,59 +35,85 @@ members = [
|
||||
"crates/nu_plugin_gstat",
|
||||
"crates/nu_plugin_example",
|
||||
"crates/nu_plugin_query",
|
||||
"crates/nu_plugin_custom_values",
|
||||
"crates/nu_plugin_formats",
|
||||
"crates/nu-utils",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
crossterm = "0.23.0"
|
||||
chrono = { version = "0.4.23", features = ["serde"] }
|
||||
crossterm = "0.24.0"
|
||||
ctrlc = "3.2.1"
|
||||
log = "0.4"
|
||||
miette = "5.1.0"
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-cli = { path="./crates/nu-cli", version = "0.65.0" }
|
||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.65.0" }
|
||||
nu-command = { path="./crates/nu-command", version = "0.65.0" }
|
||||
nu-engine = { path="./crates/nu-engine", version = "0.65.0" }
|
||||
nu-json = { path="./crates/nu-json", version = "0.65.0" }
|
||||
nu-parser = { path="./crates/nu-parser", version = "0.65.0" }
|
||||
nu-path = { path="./crates/nu-path", version = "0.65.0" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.65.0" }
|
||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.65.0" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.65.0" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.65.0" }
|
||||
nu-table = { path = "./crates/nu-table", version = "0.65.0" }
|
||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.65.0" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.65.0" }
|
||||
reedline = { version = "0.8.0", features = ["bashisms", "sqlite"]}
|
||||
pretty_env_logger = "0.4.0"
|
||||
rayon = "1.5.1"
|
||||
miette = { version = "5.5.0", features = ["fancy-no-backtrace"] }
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.77.1" }
|
||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.77.1" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.77.1" }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.77.1" }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.77.1" }
|
||||
nu-json = { path = "./crates/nu-json", version = "0.77.1" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.77.1" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.77.1" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.77.1" }
|
||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.77.1" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.77.1" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.77.1" }
|
||||
nu-table = { path = "./crates/nu-table", version = "0.77.1" }
|
||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.77.1" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.77.1" }
|
||||
|
||||
nu-ansi-term = "0.47.0"
|
||||
reedline = { version = "0.17.0", features = ["bashisms", "sqlite"] }
|
||||
|
||||
rayon = "1.7.0"
|
||||
is_executable = "1.0.1"
|
||||
simplelog = "0.12.0"
|
||||
time = "0.3.12"
|
||||
|
||||
[target.'cfg(not(target_os = "windows"))'.dependencies]
|
||||
# Our dependencies don't use OpenSSL on Windows
|
||||
openssl = { version = "0.10.38", features = ["vendored"], optional = true }
|
||||
signal-hook = { version = "0.3.14", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="./crates/nu-test-support", version = "0.65.0" }
|
||||
tempfile = "3.2.0"
|
||||
assert_cmd = "2.0.2"
|
||||
pretty_assertions = "1.0.0"
|
||||
serial_test = "0.5.1"
|
||||
hamcrest2 = "0.3.0"
|
||||
rstest = "0.12.0"
|
||||
itertools = "0.10.3"
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
winres = "0.1"
|
||||
|
||||
[target.'cfg(target_family = "unix")'.dependencies]
|
||||
nix = { version = "0.26", default-features = false, features = [
|
||||
"signal",
|
||||
"process",
|
||||
"fs",
|
||||
"term",
|
||||
] }
|
||||
atty = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.77.1" }
|
||||
tempfile = "3.4.0"
|
||||
assert_cmd = "2.0.2"
|
||||
criterion = "0.4"
|
||||
pretty_assertions = "1.0.0"
|
||||
serial_test = "1.0.0"
|
||||
hamcrest2 = "0.3.0"
|
||||
rstest = { version = "0.16.0", default-features = false }
|
||||
itertools = "0.10.3"
|
||||
|
||||
[features]
|
||||
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
|
||||
default = ["plugin", "which-support", "trash-support"]
|
||||
plugin = [
|
||||
"nu-plugin",
|
||||
"nu-cli/plugin",
|
||||
"nu-parser/plugin",
|
||||
"nu-command/plugin",
|
||||
"nu-protocol/plugin",
|
||||
"nu-engine/plugin",
|
||||
]
|
||||
# extra used to be more useful but now it's the same as default. Leaving it in for backcompat with existing build scripts
|
||||
extra = ["default"]
|
||||
default = ["plugin", "which-support", "trash-support", "sqlite"]
|
||||
stable = ["default"]
|
||||
extra = ["default", "dataframe", "database"]
|
||||
wasi = []
|
||||
|
||||
# Enable to statically link OpenSSL; otherwise the system version will be used. Not enabled by default because it takes a while to build
|
||||
static-link-openssl = ["dep:openssl"]
|
||||
|
||||
@ -93,11 +126,11 @@ trash-support = ["nu-command/trash-support"]
|
||||
# Dataframe feature for nushell
|
||||
dataframe = ["nu-command/dataframe"]
|
||||
|
||||
# Database commands for nushell
|
||||
database = ["nu-command/database"]
|
||||
# SQLite commands for nushell
|
||||
sqlite = ["nu-command/sqlite"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = "s" # Optimize for size
|
||||
opt-level = "s" # Optimize for size
|
||||
strip = "debuginfo"
|
||||
lto = "thin"
|
||||
|
||||
@ -119,3 +152,17 @@ debug = false
|
||||
[[bin]]
|
||||
name = "nu"
|
||||
path = "src/main.rs"
|
||||
bench = false
|
||||
|
||||
# To use a development version of a dependency please use a global override here
|
||||
# changing versions in each sub-crate of the workspace is tedious
|
||||
[patch.crates-io]
|
||||
# reedline = { git = "https://github.com/nushell/reedline.git", branch = "main" }
|
||||
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
|
||||
|
||||
# Criterion benchmarking setup
|
||||
# Run all benchmarks with `cargo bench`
|
||||
# Run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
harness = false
|
||||
|
9
Cross.toml
Normal file
9
Cross.toml
Normal file
@ -0,0 +1,9 @@
|
||||
# Configuration for cross-rs: https://github.com/cross-rs/cross
|
||||
# Run cross-rs like this:
|
||||
# cross build --target aarch64-unknown-linux-musl --release
|
||||
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
dockerfile = "./docker/cross-rs/aarch64-unknown-linux-gnu.dockerfile"
|
||||
|
||||
[target.aarch64-unknown-linux-musl]
|
||||
dockerfile = "./docker/cross-rs/aarch64-unknown-linux-musl.dockerfile"
|
28
README.md
28
README.md
@ -1,11 +1,12 @@
|
||||
# Nushell <!-- omit in toc -->
|
||||
[](https://crates.io/crates/nu)
|
||||

|
||||
[](https://github.com/nushell/nushell/actions)
|
||||
[](https://discord.gg/NtAbbGn)
|
||||
[](https://changelog.com/podcast/363)
|
||||
[](https://twitter.com/nu_shell)
|
||||

|
||||

|
||||
[](https://github.com/nushell/nushell/graphs/commit-activity)
|
||||
[](https://github.com/nushell/nushell/graphs/contributors)
|
||||
[](https://codecov.io/gh/nushell/nushell)
|
||||
|
||||
A new type of shell.
|
||||
|
||||
@ -32,7 +33,7 @@ This project has reached a minimum-viable-product level of quality. Many people
|
||||
|
||||
## Learning About Nu
|
||||
|
||||
The [Nushell book](https://www.nushell.sh/book/) is the primary source of Nushell documentation. You can find [a full list of Nu commands in the book](https://www.nushell.sh/book/command_reference.html), and we have many examples of using Nu in our [cookbook](https://www.nushell.sh/cookbook/).
|
||||
The [Nushell book](https://www.nushell.sh/book/) is the primary source of Nushell documentation. You can find [a full list of Nu commands in the book](https://www.nushell.sh/commands/), and we have many examples of using Nu in our [cookbook](https://www.nushell.sh/cookbook/).
|
||||
|
||||
We're also active on [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell); come and chat with us!
|
||||
|
||||
@ -47,7 +48,7 @@ brew install nushell
|
||||
winget install nushell
|
||||
```
|
||||
|
||||
To use `Nu` in Github Action, check [setup-nu](https://github.com/marketplace/actions/setup-nu) for more detail.
|
||||
To use `Nu` in GitHub Action, check [setup-nu](https://github.com/marketplace/actions/setup-nu) for more detail.
|
||||
|
||||
Detailed installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). Nu is available via many package managers:
|
||||
|
||||
@ -71,7 +72,7 @@ Additionally, commands can output structured data (you can think of this as a th
|
||||
Commands that work in the pipeline fit into one of three categories:
|
||||
|
||||
- Commands that produce a stream (e.g., `ls`)
|
||||
- Commands that filter a stream (eg, `where type == "dir"`)
|
||||
- Commands that filter a stream (e.g., `where type == "dir"`)
|
||||
- Commands that consume the output of the pipeline (e.g., `table`)
|
||||
|
||||
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
|
||||
@ -126,12 +127,13 @@ For example, you can load a .toml file as structured data and explore it:
|
||||
> open Cargo.toml
|
||||
╭──────────────────┬────────────────────╮
|
||||
│ bin │ [table 1 row] │
|
||||
│ dependencies │ {record 24 fields} │
|
||||
│ dependencies │ {record 25 fields} │
|
||||
│ dev-dependencies │ {record 8 fields} │
|
||||
│ features │ {record 10 fields} │
|
||||
│ package │ {record 13 fields} │
|
||||
│ patch │ {record 1 field} │
|
||||
│ profile │ {record 3 fields} │
|
||||
│ target │ {record 2 fields} │
|
||||
│ target │ {record 3 fields} │
|
||||
│ workspace │ {record 1 field} │
|
||||
╰──────────────────┴────────────────────╯
|
||||
```
|
||||
@ -149,11 +151,11 @@ We can pipe this into a command that gets the contents of one of the columns:
|
||||
│ exclude │ [list 1 item] │
|
||||
│ homepage │ https://www.nushell.sh │
|
||||
│ license │ MIT │
|
||||
│ metadata │ {record 1 field} │
|
||||
│ name │ nu │
|
||||
│ readme │ README.md │
|
||||
│ repository │ https://github.com/nushell/nushell │
|
||||
│ rust-version │ 1.60 │
|
||||
│ version │ 0.63.1 │
|
||||
│ version │ 0.72.0 │
|
||||
╰───────────────┴────────────────────────────────────╯
|
||||
```
|
||||
|
||||
@ -161,7 +163,7 @@ And if needed we can drill down further:
|
||||
|
||||
```shell
|
||||
> open Cargo.toml | get package.version
|
||||
0.63.1
|
||||
0.72.0
|
||||
```
|
||||
|
||||
### Plugins
|
||||
@ -173,6 +175,8 @@ These binaries interact with nu via a simple JSON-RPC protocol where the command
|
||||
If the plugin is a filter, data streams to it one element at a time, and it can stream data back in return via stdin/stdout.
|
||||
If the plugin is a sink, it is given the full vector of final data and is given free reign over stdin/stdout to use as it pleases.
|
||||
|
||||
The [awesome-nu repo](https://github.com/nushell/awesome-nu#plugins) lists a variety of nu-plugins.
|
||||
|
||||
## Goals
|
||||
|
||||
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
|
||||
@ -206,7 +210,7 @@ Nu is under heavy development and will naturally change as it matures. The chart
|
||||
| Functions | | | | X | | Functions and aliases are supported |
|
||||
| Variables | | | | X | | Nu supports variables and environment variables |
|
||||
| Completions | | | | X | | Completions for filepaths |
|
||||
| Type-checking | | | X | | | Commands check basic types, but input/output isn't checked |
|
||||
| Type-checking | | | | x | | Commands check basic types, and input/output types |
|
||||
|
||||
## Officially Supported By
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
To use Nu plugins, use the register command to tell Nu where to find the plugin. For example:
|
||||
|
||||
> register -e json ./nu_plugin_query
|
||||
> register ./nu_plugin_query
|
||||
|
7
benches/README.md
Normal file
7
benches/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Criterion benchmarks
|
||||
|
||||
These are benchmarks using [Criterion](https://github.com/bheisler/criterion.rs), a microbenchmarking tool for Rust.
|
||||
|
||||
Run all benchmarks with `cargo bench`
|
||||
|
||||
Or run individual benchmarks like `cargo bench -- <regex>` e.g. `cargo bench -- parse`
|
191
benches/benchmarks.rs
Normal file
191
benches/benchmarks.rs
Normal file
@ -0,0 +1,191 @@
|
||||
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
||||
use nu_cli::eval_source;
|
||||
use nu_parser::parse;
|
||||
use nu_plugin::{EncodingType, PluginResponse};
|
||||
use nu_protocol::{PipelineData, Span, Value};
|
||||
use nu_utils::{get_default_config, get_default_env};
|
||||
|
||||
// FIXME: All benchmarks live in this 1 file to speed up build times when benchmarking.
|
||||
// When the *_benchmarks functions were in different files, `cargo bench` would build
|
||||
// an executable for every single one - incredibly slowly. Would be nice to figure out
|
||||
// a way to split things up again.
|
||||
|
||||
fn parser_benchmarks(c: &mut Criterion) {
|
||||
let mut engine_state = nu_command::create_default_context();
|
||||
// parsing config.nu breaks without PWD set
|
||||
engine_state.add_env_var(
|
||||
"PWD".into(),
|
||||
Value::string("/some/dir".to_string(), Span::test_data()),
|
||||
);
|
||||
|
||||
let default_env = get_default_env().as_bytes();
|
||||
c.bench_function("parse_default_env_file", |b| {
|
||||
b.iter_batched(
|
||||
|| nu_protocol::engine::StateWorkingSet::new(&engine_state),
|
||||
|mut working_set| parse(&mut working_set, None, default_env, false, &[]),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
let default_config = get_default_config().as_bytes();
|
||||
c.bench_function("parse_default_config_file", |b| {
|
||||
b.iter_batched(
|
||||
|| nu_protocol::engine::StateWorkingSet::new(&engine_state),
|
||||
|mut working_set| parse(&mut working_set, None, default_config, false, &[]),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
c.bench_function("eval default_env.nu", |b| {
|
||||
b.iter(|| {
|
||||
let mut engine_state = nu_command::create_default_context();
|
||||
let mut stack = nu_protocol::engine::Stack::new();
|
||||
eval_source(
|
||||
&mut engine_state,
|
||||
&mut stack,
|
||||
get_default_env().as_bytes(),
|
||||
"default_env.nu",
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
c.bench_function("eval default_config.nu", |b| {
|
||||
b.iter(|| {
|
||||
let mut engine_state = nu_command::create_default_context();
|
||||
// parsing config.nu breaks without PWD set
|
||||
engine_state.add_env_var(
|
||||
"PWD".into(),
|
||||
Value::string("/some/dir".to_string(), Span::test_data()),
|
||||
);
|
||||
let mut stack = nu_protocol::engine::Stack::new();
|
||||
eval_source(
|
||||
&mut engine_state,
|
||||
&mut stack,
|
||||
get_default_config().as_bytes(),
|
||||
"default_config.nu",
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn eval_benchmarks(c: &mut Criterion) {
|
||||
c.bench_function("eval default_env.nu", |b| {
|
||||
b.iter(|| {
|
||||
let mut engine_state = nu_command::create_default_context();
|
||||
let mut stack = nu_protocol::engine::Stack::new();
|
||||
eval_source(
|
||||
&mut engine_state,
|
||||
&mut stack,
|
||||
get_default_env().as_bytes(),
|
||||
"default_env.nu",
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
c.bench_function("eval default_config.nu", |b| {
|
||||
b.iter(|| {
|
||||
let mut engine_state = nu_command::create_default_context();
|
||||
// parsing config.nu breaks without PWD set
|
||||
engine_state.add_env_var(
|
||||
"PWD".into(),
|
||||
Value::string("/some/dir".to_string(), Span::test_data()),
|
||||
);
|
||||
let mut stack = nu_protocol::engine::Stack::new();
|
||||
eval_source(
|
||||
&mut engine_state,
|
||||
&mut stack,
|
||||
get_default_config().as_bytes(),
|
||||
"default_config.nu",
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// generate a new table data with `row_cnt` rows, `col_cnt` columns.
|
||||
fn encoding_test_data(row_cnt: usize, col_cnt: usize) -> Value {
|
||||
let columns: Vec<String> = (0..col_cnt).map(|x| format!("col_{x}")).collect();
|
||||
let vals: Vec<Value> = (0..col_cnt as i64).map(Value::test_int).collect();
|
||||
|
||||
Value::List {
|
||||
vals: (0..row_cnt)
|
||||
.map(|_| Value::test_record(columns.clone(), vals.clone()))
|
||||
.collect(),
|
||||
span: Span::test_data(),
|
||||
}
|
||||
}
|
||||
|
||||
fn encoding_benchmarks(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Encoding");
|
||||
let test_cnt_pairs = [
|
||||
(100, 5),
|
||||
(100, 10),
|
||||
(100, 15),
|
||||
(1000, 5),
|
||||
(1000, 10),
|
||||
(1000, 15),
|
||||
(10000, 5),
|
||||
(10000, 10),
|
||||
(10000, 15),
|
||||
];
|
||||
for (row_cnt, col_cnt) in test_cnt_pairs.into_iter() {
|
||||
for fmt in ["json", "msgpack"] {
|
||||
group.bench_function(&format!("{fmt} encode {row_cnt} * {col_cnt}"), |b| {
|
||||
let mut res = vec![];
|
||||
let test_data =
|
||||
PluginResponse::Value(Box::new(encoding_test_data(row_cnt, col_cnt)));
|
||||
let encoder = EncodingType::try_from_bytes(fmt.as_bytes()).unwrap();
|
||||
b.iter(|| encoder.encode_response(&test_data, &mut res))
|
||||
});
|
||||
}
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn decoding_benchmarks(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Decoding");
|
||||
let test_cnt_pairs = [
|
||||
(100, 5),
|
||||
(100, 10),
|
||||
(100, 15),
|
||||
(1000, 5),
|
||||
(1000, 10),
|
||||
(1000, 15),
|
||||
(10000, 5),
|
||||
(10000, 10),
|
||||
(10000, 15),
|
||||
];
|
||||
for (row_cnt, col_cnt) in test_cnt_pairs.into_iter() {
|
||||
for fmt in ["json", "msgpack"] {
|
||||
group.bench_function(&format!("{fmt} decode for {row_cnt} * {col_cnt}"), |b| {
|
||||
let mut res = vec![];
|
||||
let test_data =
|
||||
PluginResponse::Value(Box::new(encoding_test_data(row_cnt, col_cnt)));
|
||||
let encoder = EncodingType::try_from_bytes(fmt.as_bytes()).unwrap();
|
||||
encoder.encode_response(&test_data, &mut res).unwrap();
|
||||
let mut binary_data = std::io::Cursor::new(res);
|
||||
b.iter(|| {
|
||||
binary_data.set_position(0);
|
||||
encoder.decode_response(&mut binary_data)
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
parser_benchmarks,
|
||||
eval_benchmarks,
|
||||
encoding_benchmarks,
|
||||
decoding_benchmarks
|
||||
);
|
||||
criterion_main!(benches);
|
@ -1,7 +1,8 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "---------------------------------------------------------------"
|
||||
echo "Building nushell (nu) with --features=extra and all the plugins"
|
||||
echo "Building nushell (nu) with dataframes and all the plugins"
|
||||
echo "---------------------------------------------------------------"
|
||||
echo ""
|
||||
|
||||
@ -10,13 +11,14 @@ NU_PLUGINS=(
|
||||
'nu_plugin_gstat'
|
||||
'nu_plugin_inc'
|
||||
'nu_plugin_query'
|
||||
'nu_plugin_custom_values'
|
||||
)
|
||||
|
||||
echo "Building nushell"
|
||||
cargo build --features=extra
|
||||
cargo build --features=dataframe
|
||||
for plugin in "${NU_PLUGINS[@]}"
|
||||
do
|
||||
echo '' && cd crates/$plugin
|
||||
echo '' && cd crates/"$plugin"
|
||||
echo "Building $plugin..."
|
||||
echo "-----------------------------"
|
||||
cargo build && cd ../..
|
||||
|
@ -1,11 +1,11 @@
|
||||
@echo off
|
||||
@echo -------------------------------------------------------------------
|
||||
@echo Building nushell (nu.exe) with --features=extra and all the plugins
|
||||
@echo Building nushell (nu.exe) with dataframes and all the plugins
|
||||
@echo -------------------------------------------------------------------
|
||||
@echo.
|
||||
|
||||
echo Building nushell.exe
|
||||
cargo build --features=extra
|
||||
cargo build --features=dataframe
|
||||
@echo.
|
||||
|
||||
@cd crates\nu_plugin_example
|
||||
@ -24,9 +24,13 @@ cargo build
|
||||
@echo.
|
||||
|
||||
@cd ..\..\crates\nu_plugin_query
|
||||
|
||||
echo Building nu_plugin_query.exe
|
||||
cargo build
|
||||
@echo.
|
||||
|
||||
@cd ..\..\crates\nu_plugin_custom_values
|
||||
echo Building nu_plugin_custom_values.exe
|
||||
cargo build
|
||||
@echo.
|
||||
|
||||
@cd ..\..
|
@ -1,16 +1,18 @@
|
||||
echo '-------------------------------------------------------------------'
|
||||
echo 'Building nushell (nu) with --features=extra and all the plugins'
|
||||
echo 'Building nushell (nu) with dataframes and all the plugins'
|
||||
echo '-------------------------------------------------------------------'
|
||||
|
||||
echo $'(char nl)Building nushell'
|
||||
echo '----------------------------'
|
||||
cargo build --features=extra
|
||||
cargo build --features=dataframe
|
||||
|
||||
let plugins = [
|
||||
nu_plugin_inc,
|
||||
nu_plugin_gstat,
|
||||
nu_plugin_query,
|
||||
nu_plugin_example,
|
||||
nu_plugin_custom_values,
|
||||
nu_plugin_formats,
|
||||
]
|
||||
|
||||
for plugin in $plugins {
|
||||
|
17
codecov.yml
Normal file
17
codecov.yml
Normal file
@ -0,0 +1,17 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 55%
|
||||
threshold: 2%
|
||||
patch:
|
||||
default:
|
||||
informational: true
|
||||
|
||||
comment:
|
||||
layout: reach, diff, files
|
||||
behavior: default
|
||||
require_base: yes
|
||||
require_head: yes
|
||||
after_n_builds: 1 # Disabled windows else: 2
|
||||
|
54
coverage-local.nu
Executable file
54
coverage-local.nu
Executable file
@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env nu
|
||||
|
||||
let start = (date now)
|
||||
# Script to generate coverage locally
|
||||
#
|
||||
# Output: `lcov.info` file
|
||||
#
|
||||
# Relies on `cargo-llvm-cov`. Install via `cargo install cargo-llvm-cov`
|
||||
# https://github.com/taiki-e/cargo-llvm-cov
|
||||
|
||||
# You probably have to run `cargo llvm-cov clean` once manually,
|
||||
# as you have to confirm to install additional tooling for your rustup toolchain.
|
||||
# Else the script might stall waiting for your `y<ENTER>`
|
||||
|
||||
# Some of the internal tests rely on the exact cargo profile
|
||||
# (This is somewhat criminal itself)
|
||||
# but we have to signal to the tests that we use the `ci` `--profile`
|
||||
let-env NUSHELL_CARGO_TARGET = "ci"
|
||||
|
||||
# Manual gathering of coverage to catch invocation of the `nu` binary.
|
||||
# This is relevant for tests using the `nu!` macro from `nu-test-support`
|
||||
# see: https://github.com/taiki-e/cargo-llvm-cov#get-coverage-of-external-tests
|
||||
|
||||
print "Setting up environment variables for coverage"
|
||||
# Enable LLVM coverage tracking through environment variables
|
||||
# show env outputs .ini/.toml style description of the variables
|
||||
# In order to use from toml, we need to make sure our string literals are single quoted
|
||||
# This is especially important when running on Windows since "C:\blah" is treated as an escape
|
||||
cargo llvm-cov show-env | str replace (char dq) (char sq) -a | from toml | load-env
|
||||
|
||||
print "Cleaning up coverage data"
|
||||
cargo llvm-cov clean --workspace
|
||||
|
||||
print "Building with workspace and profile=ci"
|
||||
# Apparently we need to explicitly build the necessary parts
|
||||
# using the `--profile=ci` is basically `debug` build with unnecessary symbols stripped
|
||||
# leads to smaller binaries and potential savings when compiling and running
|
||||
cargo build --workspace --profile=ci
|
||||
|
||||
print "Running tests with --workspace and profile=ci"
|
||||
cargo test --workspace --profile=ci
|
||||
|
||||
# You need to provide the used profile to find the raw data
|
||||
print "Generating coverage report as lcov.info"
|
||||
cargo llvm-cov report --lcov --output-path lcov.info --profile=ci
|
||||
|
||||
let end = (date now)
|
||||
$"Coverage generation took ($end - $start)."
|
||||
|
||||
# To display the coverage in your editor see:
|
||||
#
|
||||
# - https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters
|
||||
# - https://github.com/umaumax/vim-lcov
|
||||
# - https://github.com/andythigpen/nvim-coverage (probably needs some additional config)
|
38
coverage-local.sh
Executable file
38
coverage-local.sh
Executable file
@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Script to generate coverage locally
|
||||
#
|
||||
# Output: `lcov.info` file
|
||||
#
|
||||
# Relies on `cargo-llvm-cov`. Install via `cargo install cargo-llvm-cov`
|
||||
# https://github.com/taiki-e/cargo-llvm-cov
|
||||
|
||||
# You probably have to run `cargo llvm-cov clean` once manually,
|
||||
# as you have to confirm to install additional tooling for your rustup toolchain.
|
||||
# Else the script might stall waiting for your `y<ENTER>`
|
||||
|
||||
# Some of the internal tests rely on the exact cargo profile
|
||||
# (This is somewhat criminal itself)
|
||||
# but we have to signal to the tests that we use the `ci` `--profile`
|
||||
export NUSHELL_CARGO_TARGET=ci
|
||||
|
||||
# Manual gathering of coverage to catch invocation of the `nu` binary.
|
||||
# This is relevant for tests using the `nu!` macro from `nu-test-support`
|
||||
# see: https://github.com/taiki-e/cargo-llvm-cov#get-coverage-of-external-tests
|
||||
|
||||
# Enable LLVM coverage tracking through environment variables
|
||||
source <(cargo llvm-cov show-env --export-prefix)
|
||||
cargo llvm-cov clean --workspace
|
||||
# Apparently we need to explicitly build the necessary parts
|
||||
# using the `--profile=ci` is basically `debug` build with unnecessary symbols stripped
|
||||
# leads to smaller binaries and potential savings when compiling and running
|
||||
cargo build --workspace --profile=ci
|
||||
cargo test --workspace --profile=ci
|
||||
# You need to provide the used profile to find the raw data
|
||||
cargo llvm-cov report --lcov --output-path lcov.info --profile=ci
|
||||
|
||||
# To display the coverage in your editor see:
|
||||
#
|
||||
# - https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters
|
||||
# - https://github.com/umaumax/vim-lcov
|
||||
# - https://github.com/andythigpen/nvim-coverage (probably needs some additional config)
|
@ -1,33 +1,43 @@
|
||||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "CLI-related functionality for Nushell"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.65.0"
|
||||
version = "0.77.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="../nu-test-support", version = "0.65.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.65.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.77.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.77.1" }
|
||||
rstest = { version = "0.16.0", default-features = false }
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.65.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.65.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.65.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.65.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.65.0" }
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.65.0" }
|
||||
reedline = { version = "0.8.0", features = ["bashisms", "sqlite"]}
|
||||
crossterm = "0.23.0"
|
||||
miette = { version = "5.1.0", features = ["fancy"] }
|
||||
thiserror = "1.0.31"
|
||||
fuzzy-matcher = "0.3.7"
|
||||
nu-engine = { path = "../nu-engine", version = "0.77.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.77.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.77.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.77.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.77.1" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.77.1" }
|
||||
|
||||
log = "0.4"
|
||||
nu-ansi-term = "0.47.0"
|
||||
reedline = { version = "0.17.0", features = ["bashisms", "sqlite"] }
|
||||
|
||||
atty = "0.2.14"
|
||||
chrono = { default-features = false, features = ["std"], version = "0.4.23" }
|
||||
crossterm = "0.24.0"
|
||||
fancy-regex = "0.11.0"
|
||||
fuzzy-matcher = "0.3.7"
|
||||
is_executable = "1.0.1"
|
||||
chrono = "0.4.19"
|
||||
sysinfo = "0.24.1"
|
||||
once_cell = "1.17.0"
|
||||
log = "0.4"
|
||||
miette = { version = "5.5.0", features = ["fancy-no-backtrace"] }
|
||||
percent-encoding = "2"
|
||||
sysinfo = "0.28.2"
|
||||
thiserror = "1.0.31"
|
||||
|
||||
[features]
|
||||
plugin = []
|
||||
|
@ -5,21 +5,26 @@ use nu_engine::{convert_env_values, eval_block};
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::engine::Stack;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateDelta, StateWorkingSet},
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
PipelineData, Spanned, Value,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
/// Run a command (or commands) given to us by the user
|
||||
pub fn evaluate_commands(
|
||||
commands: &Spanned<String>,
|
||||
init_cwd: &Path,
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
input: PipelineData,
|
||||
is_perf_true: bool,
|
||||
table_mode: Option<Value>,
|
||||
) -> Result<Option<i64>> {
|
||||
// Run a command (or commands) given to us by the user
|
||||
// Translate environment variables from Strings to Values
|
||||
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Parse the source code
|
||||
let (block, delta) = {
|
||||
if let Some(ref t_mode) = table_mode {
|
||||
let mut config = engine_state.get_config().clone();
|
||||
@ -39,43 +44,19 @@ pub fn evaluate_commands(
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
if let Err(err) = engine_state.merge_delta(delta, None, init_cwd) {
|
||||
// Update permanent state
|
||||
if let Err(err) = engine_state.merge_delta(delta) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
}
|
||||
|
||||
let mut config = engine_state.get_config().clone();
|
||||
if let Some(t_mode) = table_mode {
|
||||
config.table_mode = t_mode.as_string()?;
|
||||
}
|
||||
|
||||
// Merge the delta in case env vars changed in the config
|
||||
match nu_engine::env::current_dir(engine_state, stack) {
|
||||
Ok(cwd) => {
|
||||
if let Err(e) =
|
||||
engine_state.merge_delta(StateDelta::new(engine_state), Some(stack), cwd)
|
||||
{
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Translate environment variables from Strings to Values
|
||||
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Run the block
|
||||
let exit_code = match eval_block(engine_state, stack, &block, input, false, false) {
|
||||
Ok(pipeline_data) => {
|
||||
let mut config = engine_state.get_config().clone();
|
||||
if let Some(t_mode) = table_mode {
|
||||
config.table_mode = t_mode.as_string()?;
|
||||
}
|
||||
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
|
||||
}
|
||||
Err(err) => {
|
||||
@ -86,9 +67,7 @@ pub fn evaluate_commands(
|
||||
}
|
||||
};
|
||||
|
||||
if is_perf_true {
|
||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||
|
||||
Ok(exit_code)
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ pub struct CommandCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
flattened: Vec<(Span, FlatShape)>,
|
||||
flat_shape: FlatShape,
|
||||
force_completion_after_space: bool,
|
||||
}
|
||||
|
||||
impl CommandCompletion {
|
||||
@ -19,11 +20,13 @@ impl CommandCompletion {
|
||||
_: &StateWorkingSet,
|
||||
flattened: Vec<(Span, FlatShape)>,
|
||||
flat_shape: FlatShape,
|
||||
force_completion_after_space: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
engine_state,
|
||||
flattened,
|
||||
flat_shape,
|
||||
force_completion_after_space,
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,7 +37,8 @@ impl CommandCompletion {
|
||||
) -> Vec<String> {
|
||||
let mut executables = vec![];
|
||||
|
||||
let paths = self.engine_state.get_env_var("PATH");
|
||||
// os agnostic way to get the PATH env var
|
||||
let paths = self.engine_state.get_path_env_var();
|
||||
|
||||
if let Some(paths) = paths {
|
||||
if let Ok(paths) = paths.as_list() {
|
||||
@ -43,19 +47,21 @@ impl CommandCompletion {
|
||||
|
||||
if let Ok(mut contents) = std::fs::read_dir(path) {
|
||||
while let Some(Ok(item)) = contents.next() {
|
||||
if !executables.contains(
|
||||
&item
|
||||
.path()
|
||||
.file_name()
|
||||
.map(|x| x.to_string_lossy().to_string())
|
||||
.unwrap_or_default(),
|
||||
) && matches!(
|
||||
item.path()
|
||||
.file_name()
|
||||
.map(|x| match_algorithm
|
||||
if self.engine_state.config.max_external_completion_results
|
||||
> executables.len() as i64
|
||||
&& !executables.contains(
|
||||
&item
|
||||
.path()
|
||||
.file_name()
|
||||
.map(|x| x.to_string_lossy().to_string())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
&& matches!(
|
||||
item.path().file_name().map(|x| match_algorithm
|
||||
.matches_str(&x.to_string_lossy(), prefix)),
|
||||
Some(true)
|
||||
) && is_executable::is_executable(&item.path())
|
||||
Some(true)
|
||||
)
|
||||
&& is_executable::is_executable(item.path())
|
||||
{
|
||||
if let Ok(name) = item.file_name().into_string() {
|
||||
executables.push(name);
|
||||
@ -89,10 +95,7 @@ impl CommandCompletion {
|
||||
value: String::from_utf8_lossy(&x.0).to_string(),
|
||||
description: x.1,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||
append_whitespace: true,
|
||||
});
|
||||
|
||||
@ -103,10 +106,7 @@ impl CommandCompletion {
|
||||
value: String::from_utf8_lossy(&x).to_string(),
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||
append_whitespace: true,
|
||||
});
|
||||
|
||||
@ -114,7 +114,8 @@ impl CommandCompletion {
|
||||
|
||||
let partial = working_set.get_span_contents(span);
|
||||
let partial = String::from_utf8_lossy(partial).to_string();
|
||||
let results = if find_externals {
|
||||
|
||||
if find_externals {
|
||||
let results_external = self
|
||||
.external_command_completion(&partial, match_algorithm)
|
||||
.into_iter()
|
||||
@ -122,15 +123,15 @@ impl CommandCompletion {
|
||||
value: x,
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
span: reedline::Span::new(span.start - offset, span.end - offset),
|
||||
append_whitespace: true,
|
||||
});
|
||||
|
||||
let results_strings: Vec<String> =
|
||||
results.clone().into_iter().map(|x| x.value).collect();
|
||||
|
||||
for external in results_external {
|
||||
if results.contains(&external) {
|
||||
if results_strings.contains(&external.value) {
|
||||
results.push(Suggestion {
|
||||
value: format!("^{}", external.value),
|
||||
description: None,
|
||||
@ -146,9 +147,7 @@ impl CommandCompletion {
|
||||
results
|
||||
} else {
|
||||
results
|
||||
};
|
||||
|
||||
results
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,10 +182,7 @@ impl Completer for CommandCompletion {
|
||||
let subcommands = if let Some(last) = last {
|
||||
self.complete_commands(
|
||||
working_set,
|
||||
Span {
|
||||
start: last.0.start,
|
||||
end: pos,
|
||||
},
|
||||
Span::new(last.0.start, pos),
|
||||
offset,
|
||||
false,
|
||||
options.match_algorithm,
|
||||
@ -203,8 +199,13 @@ impl Completer for CommandCompletion {
|
||||
let commands = if matches!(self.flat_shape, nu_parser::FlatShape::External)
|
||||
|| matches!(self.flat_shape, nu_parser::FlatShape::InternalCall)
|
||||
|| ((span.end - span.start) == 0)
|
||||
|| is_passthrough_command(working_set.delta.get_file_contents())
|
||||
{
|
||||
// we're in a gap or at a command
|
||||
if working_set.get_span_contents(span).is_empty() && !self.force_completion_after_space
|
||||
{
|
||||
return vec![];
|
||||
}
|
||||
self.complete_commands(
|
||||
working_set,
|
||||
span,
|
||||
@ -226,3 +227,107 @@ impl Completer for CommandCompletion {
|
||||
SortBy::LevenshteinDistance
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_non_whitespace_index(contents: &[u8], start: usize) -> usize {
|
||||
match contents.get(start..) {
|
||||
Some(contents) => {
|
||||
contents
|
||||
.iter()
|
||||
.take_while(|x| x.is_ascii_whitespace())
|
||||
.count()
|
||||
+ start
|
||||
}
|
||||
None => start,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_passthrough_command(working_set_file_contents: &[(Vec<u8>, usize, usize)]) -> bool {
|
||||
for (contents, _, _) in working_set_file_contents {
|
||||
let last_pipe_pos_rev = contents.iter().rev().position(|x| x == &b'|');
|
||||
let last_pipe_pos = last_pipe_pos_rev.map(|x| contents.len() - x).unwrap_or(0);
|
||||
|
||||
let cur_pos = find_non_whitespace_index(contents, last_pipe_pos);
|
||||
|
||||
let result = match contents.get(cur_pos..) {
|
||||
Some(contents) => contents.starts_with(b"sudo "),
|
||||
None => false,
|
||||
};
|
||||
if result {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod command_completions_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_find_non_whitespace_index() {
|
||||
let commands = vec![
|
||||
(" hello", 4),
|
||||
("sudo ", 0),
|
||||
(" sudo ", 2),
|
||||
(" sudo ", 2),
|
||||
(" hello ", 1),
|
||||
(" hello ", 3),
|
||||
(" hello | sudo ", 4),
|
||||
(" sudo|sudo", 5),
|
||||
("sudo | sudo ", 0),
|
||||
(" hello sud", 1),
|
||||
];
|
||||
for (idx, ele) in commands.iter().enumerate() {
|
||||
let index = find_non_whitespace_index(&Vec::from(ele.0.as_bytes()), 0);
|
||||
assert_eq!(index, ele.1, "Failed on index {}", idx);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_last_command_passthrough() {
|
||||
let commands = vec![
|
||||
(" hello", false),
|
||||
(" sudo ", true),
|
||||
("sudo ", true),
|
||||
(" hello", false),
|
||||
(" sudo", false),
|
||||
(" sudo ", true),
|
||||
(" sudo ", true),
|
||||
(" sudo ", true),
|
||||
(" hello ", false),
|
||||
(" hello | sudo ", true),
|
||||
(" sudo|sudo", false),
|
||||
("sudo | sudo ", true),
|
||||
(" hello sud", false),
|
||||
(" sudo | sud ", false),
|
||||
(" sudo|sudo ", true),
|
||||
(" sudo | sudo ls | sudo ", true),
|
||||
];
|
||||
for (idx, ele) in commands.iter().enumerate() {
|
||||
let input = ele.0.as_bytes();
|
||||
|
||||
let mut engine_state = EngineState::new();
|
||||
engine_state.add_file("test.nu".into(), vec![]);
|
||||
|
||||
let delta = {
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
working_set.add_file("child.nu".into(), input);
|
||||
working_set.render()
|
||||
};
|
||||
|
||||
let result = engine_state.merge_delta(delta);
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Merge delta has failed: {}",
|
||||
result.err().unwrap()
|
||||
);
|
||||
|
||||
let is_passthrough_command = is_passthrough_command(engine_state.get_file_contents());
|
||||
assert_eq!(
|
||||
is_passthrough_command, ele.1,
|
||||
"index for '{}': {}",
|
||||
ele.0, idx
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,12 @@ use crate::completions::{
|
||||
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
||||
DotNuCompletion, FileCompletion, FlagCompletion, MatchAlgorithm, VariableCompletion,
|
||||
};
|
||||
use nu_engine::eval_block;
|
||||
use nu_parser::{flatten_expression, parse, FlatShape};
|
||||
use nu_protocol::{
|
||||
ast::PipelineElement,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Span,
|
||||
BlockId, PipelineData, Span, Value,
|
||||
};
|
||||
use reedline::{Completer as ReedlineCompleter, Suggestion};
|
||||
use std::str;
|
||||
@ -56,176 +58,129 @@ impl NuCompleter {
|
||||
suggestions
|
||||
}
|
||||
|
||||
fn external_completion(
|
||||
&self,
|
||||
block_id: BlockId,
|
||||
spans: &[String],
|
||||
offset: usize,
|
||||
span: Span,
|
||||
) -> Option<Vec<Suggestion>> {
|
||||
let stack = self.stack.clone();
|
||||
let block = self.engine_state.get_block(block_id);
|
||||
let mut callee_stack = stack.gather_captures(&block.captures);
|
||||
|
||||
// Line
|
||||
if let Some(pos_arg) = block.signature.required_positional.get(0) {
|
||||
if let Some(var_id) = pos_arg.var_id {
|
||||
callee_stack.add_var(
|
||||
var_id,
|
||||
Value::List {
|
||||
vals: spans
|
||||
.iter()
|
||||
.map(|it| Value::string(it, Span::unknown()))
|
||||
.collect(),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let result = eval_block(
|
||||
&self.engine_state,
|
||||
&mut callee_stack,
|
||||
block,
|
||||
PipelineData::empty(),
|
||||
true,
|
||||
true,
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(pd) => {
|
||||
let value = pd.into_value(span);
|
||||
if let Value::List { vals, span: _ } = value {
|
||||
let result =
|
||||
map_value_completions(vals.iter(), Span::new(span.start, span.end), offset);
|
||||
|
||||
return Some(result);
|
||||
}
|
||||
}
|
||||
Err(err) => println!("failed to eval completer block: {err}"),
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||
let offset = working_set.next_span_start();
|
||||
let (mut new_line, alias_offset) = try_find_alias(line.as_bytes(), &working_set);
|
||||
let initial_line = line.to_string();
|
||||
new_line.push(b'a');
|
||||
let alias_total_offset: usize = alias_offset.iter().sum();
|
||||
new_line.insert(alias_total_offset + pos, b'a');
|
||||
let pos = offset + pos;
|
||||
let config = self.engine_state.get_config();
|
||||
|
||||
let (output, _err) = parse(&mut working_set, Some("completer"), &new_line, false, &[]);
|
||||
|
||||
for pipeline in output.pipelines.into_iter() {
|
||||
for expr in pipeline.expressions {
|
||||
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
||||
let span_offset: usize = alias_offset.iter().sum();
|
||||
for pipeline_element in pipeline.elements {
|
||||
match pipeline_element {
|
||||
PipelineElement::Expression(_, expr)
|
||||
| PipelineElement::Redirection(_, _, expr)
|
||||
| PipelineElement::And(_, expr)
|
||||
| PipelineElement::Or(_, expr)
|
||||
| PipelineElement::SeparateRedirection { out: (_, expr), .. } => {
|
||||
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
||||
let span_offset: usize = alias_offset.iter().sum();
|
||||
let mut spans: Vec<String> = vec![];
|
||||
|
||||
for (flat_idx, flat) in flattened.iter().enumerate() {
|
||||
if pos + span_offset >= flat.0.start && pos + span_offset < flat.0.end {
|
||||
// Context variables
|
||||
let most_left_var =
|
||||
most_left_variable(flat_idx, &working_set, flattened.clone());
|
||||
for (flat_idx, flat) in flattened.iter().enumerate() {
|
||||
let is_passthrough_command = spans
|
||||
.first()
|
||||
.filter(|content| *content == &String::from("sudo"))
|
||||
.is_some();
|
||||
// Read the current spam to string
|
||||
let current_span = working_set.get_span_contents(flat.0).to_vec();
|
||||
let current_span_str = String::from_utf8_lossy(¤t_span);
|
||||
|
||||
// Create a new span
|
||||
let new_span = if flat_idx == 0 {
|
||||
Span {
|
||||
start: flat.0.start,
|
||||
end: flat.0.end - 1 - span_offset,
|
||||
// Skip the last 'a' as span item
|
||||
if flat_idx == flattened.len() - 1 {
|
||||
let mut chars = current_span_str.chars();
|
||||
chars.next_back();
|
||||
let current_span_str = chars.as_str().to_owned();
|
||||
spans.push(current_span_str.to_string());
|
||||
} else {
|
||||
spans.push(current_span_str.to_string());
|
||||
}
|
||||
} else {
|
||||
Span {
|
||||
start: flat.0.start - span_offset,
|
||||
end: flat.0.end - 1 - span_offset,
|
||||
}
|
||||
};
|
||||
|
||||
// Parses the prefix
|
||||
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
||||
prefix.remove(pos - (flat.0.start - span_offset));
|
||||
// Complete based on the last span
|
||||
if pos + span_offset >= flat.0.start && pos + span_offset < flat.0.end {
|
||||
// Context variables
|
||||
let most_left_var =
|
||||
most_left_variable(flat_idx, &working_set, flattened.clone());
|
||||
|
||||
// Variables completion
|
||||
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
||||
let mut completer = VariableCompletion::new(
|
||||
self.engine_state.clone(),
|
||||
self.stack.clone(),
|
||||
most_left_var.unwrap_or((vec![], vec![])),
|
||||
);
|
||||
// Create a new span
|
||||
let new_span = if flat_idx == 0 {
|
||||
Span::new(flat.0.start, flat.0.end - 1 - span_offset)
|
||||
} else {
|
||||
Span::new(
|
||||
flat.0.start - span_offset,
|
||||
flat.0.end - 1 - span_offset,
|
||||
)
|
||||
};
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
// Parses the prefix. Completion should look up to the cursor position, not after.
|
||||
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
||||
let index = pos - (flat.0.start - span_offset);
|
||||
prefix.drain(index..);
|
||||
|
||||
// Flags completion
|
||||
if prefix.starts_with(b"-") {
|
||||
let mut completer = FlagCompletion::new(expr);
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
|
||||
// Completions that depends on the previous expression (e.g: use, source)
|
||||
if flat_idx > 0 {
|
||||
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
|
||||
// Read the content for the previous expression
|
||||
let prev_expr_str =
|
||||
working_set.get_span_contents(previous_expr.0).to_vec();
|
||||
|
||||
// Completion for .nu files
|
||||
if prev_expr_str == b"use" || prev_expr_str == b"source" {
|
||||
let mut completer =
|
||||
DotNuCompletion::new(self.engine_state.clone());
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
// Variables completion
|
||||
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
||||
let mut completer = VariableCompletion::new(
|
||||
self.engine_state.clone(),
|
||||
self.stack.clone(),
|
||||
most_left_var.unwrap_or((vec![], vec![])),
|
||||
);
|
||||
} else if prev_expr_str == b"ls" {
|
||||
let mut completer =
|
||||
FileCompletion::new(self.engine_state.clone());
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Match other types
|
||||
match &flat.1 {
|
||||
FlatShape::Custom(decl_id) => {
|
||||
let mut completer = CustomCompletion::new(
|
||||
self.engine_state.clone(),
|
||||
self.stack.clone(),
|
||||
*decl_id,
|
||||
initial_line,
|
||||
);
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
FlatShape::Directory => {
|
||||
let mut completer =
|
||||
DirectoryCompletion::new(self.engine_state.clone());
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
FlatShape::Filepath | FlatShape::GlobPattern => {
|
||||
let mut completer = FileCompletion::new(self.engine_state.clone());
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
flat_shape => {
|
||||
let mut completer = CommandCompletion::new(
|
||||
self.engine_state.clone(),
|
||||
&working_set,
|
||||
flattened.clone(),
|
||||
// flat_idx,
|
||||
flat_shape.clone(),
|
||||
);
|
||||
|
||||
let out: Vec<_> = self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix.clone(),
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
|
||||
if out.is_empty() {
|
||||
let mut completer =
|
||||
FileCompletion::new(self.engine_state.clone());
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
@ -237,15 +192,196 @@ impl NuCompleter {
|
||||
);
|
||||
}
|
||||
|
||||
return out;
|
||||
// Flags completion
|
||||
if prefix.starts_with(b"-") {
|
||||
// Try to complete flag internally
|
||||
let mut completer = FlagCompletion::new(expr.clone());
|
||||
let result = self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix.clone(),
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
|
||||
if !result.is_empty() {
|
||||
return result;
|
||||
}
|
||||
|
||||
// We got no results for internal completion
|
||||
// now we can check if external completer is set and use it
|
||||
if let Some(block_id) = config.external_completer {
|
||||
if let Some(external_result) = self
|
||||
.external_completion(block_id, &spans, offset, new_span)
|
||||
{
|
||||
return external_result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// specially check if it is currently empty - always complete commands
|
||||
if (is_passthrough_command && flat_idx == 1)
|
||||
|| (flat_idx == 0
|
||||
&& working_set.get_span_contents(new_span).is_empty())
|
||||
{
|
||||
let mut completer = CommandCompletion::new(
|
||||
self.engine_state.clone(),
|
||||
&working_set,
|
||||
flattened.clone(),
|
||||
// flat_idx,
|
||||
FlatShape::String,
|
||||
true,
|
||||
);
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
|
||||
// Completions that depends on the previous expression (e.g: use, source-env)
|
||||
if (is_passthrough_command && flat_idx > 1) || flat_idx > 0 {
|
||||
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
|
||||
// Read the content for the previous expression
|
||||
let prev_expr_str =
|
||||
working_set.get_span_contents(previous_expr.0).to_vec();
|
||||
|
||||
// Completion for .nu files
|
||||
if prev_expr_str == b"use" || prev_expr_str == b"source-env"
|
||||
{
|
||||
let mut completer =
|
||||
DotNuCompletion::new(self.engine_state.clone());
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
} else if prev_expr_str == b"ls" {
|
||||
let mut completer =
|
||||
FileCompletion::new(self.engine_state.clone());
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Match other types
|
||||
match &flat.1 {
|
||||
FlatShape::Custom(decl_id) => {
|
||||
let mut completer = CustomCompletion::new(
|
||||
self.engine_state.clone(),
|
||||
self.stack.clone(),
|
||||
*decl_id,
|
||||
initial_line,
|
||||
);
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
FlatShape::Directory => {
|
||||
let mut completer =
|
||||
DirectoryCompletion::new(self.engine_state.clone());
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
FlatShape::Filepath | FlatShape::GlobPattern => {
|
||||
let mut completer =
|
||||
FileCompletion::new(self.engine_state.clone());
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
flat_shape => {
|
||||
let mut completer = CommandCompletion::new(
|
||||
self.engine_state.clone(),
|
||||
&working_set,
|
||||
flattened.clone(),
|
||||
// flat_idx,
|
||||
flat_shape.clone(),
|
||||
false,
|
||||
);
|
||||
|
||||
let mut out: Vec<_> = self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix.clone(),
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
|
||||
if !out.is_empty() {
|
||||
return out;
|
||||
}
|
||||
|
||||
// Try to complete using an external completer (if set)
|
||||
if let Some(block_id) = config.external_completer {
|
||||
if let Some(external_result) = self.external_completion(
|
||||
block_id, &spans, offset, new_span,
|
||||
) {
|
||||
return external_result;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for file completion
|
||||
let mut completer =
|
||||
FileCompletion::new(self.engine_state.clone());
|
||||
out = self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
|
||||
if !out.is_empty() {
|
||||
return out;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vec![];
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,7 +438,7 @@ fn search_alias(input: &[u8], working_set: &StateWorkingSet) -> Option<MatchedAl
|
||||
}
|
||||
// Push the rest to names vector.
|
||||
if pos < input.len() {
|
||||
vec_names.push((&input[pos..]).to_owned());
|
||||
vec_names.push(input[pos..].to_owned());
|
||||
}
|
||||
|
||||
for name in &vec_names {
|
||||
@ -383,3 +519,125 @@ fn most_left_variable(
|
||||
|
||||
Some((var, sublevels))
|
||||
}
|
||||
|
||||
pub fn map_value_completions<'a>(
|
||||
list: impl Iterator<Item = &'a Value>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
) -> Vec<Suggestion> {
|
||||
list.filter_map(move |x| {
|
||||
// Match for string values
|
||||
if let Ok(s) = x.as_string() {
|
||||
return Some(Suggestion {
|
||||
value: s,
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
|
||||
// Match for record values
|
||||
if let Ok((cols, vals)) = x.as_record() {
|
||||
let mut suggestion = Suggestion {
|
||||
value: String::from(""), // Initialize with empty string
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: false,
|
||||
};
|
||||
|
||||
// Iterate the cols looking for `value` and `description`
|
||||
cols.iter().zip(vals).for_each(|it| {
|
||||
// Match `value` column
|
||||
if it.0 == "value" {
|
||||
// Convert the value to string
|
||||
if let Ok(val_str) = it.1.as_string() {
|
||||
// Update the suggestion value
|
||||
suggestion.value = val_str;
|
||||
}
|
||||
}
|
||||
|
||||
// Match `description` column
|
||||
if it.0 == "description" {
|
||||
// Convert the value to string
|
||||
if let Ok(desc_str) = it.1.as_string() {
|
||||
// Update the suggestion value
|
||||
suggestion.description = Some(desc_str);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Some(suggestion);
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod completer_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_completion_helper() {
|
||||
let mut engine_state = nu_command::create_default_context();
|
||||
|
||||
// Custom additions
|
||||
let delta = {
|
||||
let working_set = nu_protocol::engine::StateWorkingSet::new(&engine_state);
|
||||
working_set.render()
|
||||
};
|
||||
|
||||
let result = engine_state.merge_delta(delta);
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Error merging delta: {:?}",
|
||||
result.err().unwrap()
|
||||
);
|
||||
|
||||
let mut completer = NuCompleter::new(engine_state.into(), Stack::new());
|
||||
let dataset = vec![
|
||||
("sudo", false, "", Vec::new()),
|
||||
("sudo l", true, "l", vec!["ls", "let", "lines", "loop"]),
|
||||
(" sudo", false, "", Vec::new()),
|
||||
(" sudo le", true, "le", vec!["let", "length"]),
|
||||
(
|
||||
"ls | c",
|
||||
true,
|
||||
"c",
|
||||
vec!["cd", "config", "const", "cp", "cal"],
|
||||
),
|
||||
("ls | sudo m", true, "m", vec!["mv", "mut", "move"]),
|
||||
];
|
||||
for (line, has_result, begins_with, expected_values) in dataset {
|
||||
let result = completer.completion_helper(line, line.len());
|
||||
// Test whether the result is empty or not
|
||||
assert_eq!(!result.is_empty(), has_result, "line: {}", line);
|
||||
|
||||
// Test whether the result begins with the expected value
|
||||
result
|
||||
.iter()
|
||||
.for_each(|x| assert!(x.value.starts_with(begins_with)));
|
||||
|
||||
// Test whether the result contains all the expected values
|
||||
assert_eq!(
|
||||
result
|
||||
.iter()
|
||||
.map(|x| expected_values.contains(&x.value.as_str()))
|
||||
.filter(|x| *x)
|
||||
.count(),
|
||||
expected_values.len(),
|
||||
"line: {}",
|
||||
line
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ use nu_protocol::{
|
||||
use reedline::Suggestion;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::completer::map_value_completions;
|
||||
|
||||
pub struct CustomCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
stack: Stack,
|
||||
@ -26,69 +28,6 @@ impl CustomCompletion {
|
||||
sort_by: SortBy::None,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_completions<'a>(
|
||||
&self,
|
||||
list: impl Iterator<Item = &'a Value>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
) -> Vec<Suggestion> {
|
||||
list.filter_map(move |x| {
|
||||
// Match for string values
|
||||
if let Ok(s) = x.as_string() {
|
||||
return Some(Suggestion {
|
||||
value: s,
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
|
||||
// Match for record values
|
||||
if let Ok((cols, vals)) = x.as_record() {
|
||||
let mut suggestion = Suggestion {
|
||||
value: String::from(""), // Initialize with empty string
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: false,
|
||||
};
|
||||
|
||||
// Iterate the cols looking for `value` and `description`
|
||||
cols.iter().zip(vals).for_each(|it| {
|
||||
// Match `value` column
|
||||
if it.0 == "value" {
|
||||
// Convert the value to string
|
||||
if let Ok(val_str) = it.1.as_string() {
|
||||
// Update the suggestion value
|
||||
suggestion.value = val_str;
|
||||
}
|
||||
}
|
||||
|
||||
// Match `description` column
|
||||
if it.0 == "description" {
|
||||
// Convert the value to string
|
||||
if let Ok(desc_str) = it.1.as_string() {
|
||||
// Update the suggestion value
|
||||
suggestion.description = Some(desc_str);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Some(suggestion);
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for CustomCompletion {
|
||||
@ -113,13 +52,13 @@ impl Completer for CustomCompletion {
|
||||
head: span,
|
||||
arguments: vec![
|
||||
Argument::Positional(Expression {
|
||||
span: Span { start: 0, end: 0 },
|
||||
span: Span::unknown(),
|
||||
ty: Type::String,
|
||||
expr: Expr::String(self.line.clone()),
|
||||
custom_completion: None,
|
||||
}),
|
||||
Argument::Positional(Expression {
|
||||
span: Span { start: 0, end: 0 },
|
||||
span: Span::unknown(),
|
||||
ty: Type::Int,
|
||||
expr: Expr::Int(line_pos as i64),
|
||||
custom_completion: None,
|
||||
@ -127,15 +66,16 @@ impl Completer for CustomCompletion {
|
||||
],
|
||||
redirect_stdout: true,
|
||||
redirect_stderr: true,
|
||||
parser_info: vec![],
|
||||
},
|
||||
PipelineData::new(span),
|
||||
PipelineData::empty(),
|
||||
);
|
||||
|
||||
let mut custom_completion_options = None;
|
||||
|
||||
// Parse result
|
||||
let suggestions = match result {
|
||||
Ok(pd) => {
|
||||
let suggestions = result
|
||||
.map(|pd| {
|
||||
let value = pd.into_value(span);
|
||||
match &value {
|
||||
Value::Record { .. } => {
|
||||
@ -144,7 +84,7 @@ impl Completer for CustomCompletion {
|
||||
.and_then(|val| {
|
||||
val.as_list()
|
||||
.ok()
|
||||
.map(|it| self.map_completions(it.iter(), span, offset))
|
||||
.map(|it| map_value_completions(it.iter(), span, offset))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let options = value.get_data_by_key("options");
|
||||
@ -189,12 +129,11 @@ impl Completer for CustomCompletion {
|
||||
|
||||
completions
|
||||
}
|
||||
Value::List { vals, .. } => self.map_completions(vals.iter(), span, offset),
|
||||
Value::List { vals, .. } => map_value_completions(vals.iter(), span, offset),
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
_ => vec![],
|
||||
};
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(custom_completion_options) = custom_completion_options {
|
||||
filter(&prefix, suggestions, &custom_completion_options)
|
||||
|
@ -8,7 +8,7 @@ use std::fs;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{partial_from, prepend_base_dir};
|
||||
use super::{partial_from, prepend_base_dir, SortBy};
|
||||
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
@ -33,14 +33,7 @@ impl Completer for DirectoryCompletion {
|
||||
_: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
let cwd = if let Some(d) = self.engine_state.get_env_var("PWD") {
|
||||
match d.as_string() {
|
||||
Ok(s) => s,
|
||||
Err(_) => "".to_string(),
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let cwd = self.engine_state.current_work_dir();
|
||||
let partial = String::from_utf8_lossy(&prefix).to_string();
|
||||
|
||||
// Filter only the folders
|
||||
@ -67,12 +60,20 @@ impl Completer for DirectoryCompletion {
|
||||
|
||||
// Sort items
|
||||
let mut sorted_items = items;
|
||||
sorted_items.sort_by(|a, b| a.value.cmp(&b.value));
|
||||
sorted_items.sort_by(|a, b| {
|
||||
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
||||
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
||||
a_distance.cmp(&b_distance)
|
||||
});
|
||||
|
||||
match self.get_sort_by() {
|
||||
SortBy::Ascending => {
|
||||
sorted_items.sort_by(|a, b| a.value.cmp(&b.value));
|
||||
}
|
||||
SortBy::LevenshteinDistance => {
|
||||
sorted_items.sort_by(|a, b| {
|
||||
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
||||
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
||||
a_distance.cmp(&b_distance)
|
||||
});
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// Separate the results between hidden and non hidden
|
||||
let mut hidden: Vec<Suggestion> = vec![];
|
||||
@ -126,7 +127,7 @@ pub fn directory_completion(
|
||||
let mut file_name = entry.file_name().to_string_lossy().into_owned();
|
||||
if matches(&partial, &file_name, options) {
|
||||
let mut path = if prepend_base_dir(original_input, &base_dir_name) {
|
||||
format!("{}{}", base_dir_name, file_name)
|
||||
format!("{base_dir_name}{file_name}")
|
||||
} else {
|
||||
file_name.to_string()
|
||||
};
|
||||
@ -136,9 +137,13 @@ pub fn directory_completion(
|
||||
file_name.push(SEP);
|
||||
}
|
||||
|
||||
// Fix files or folders with quotes
|
||||
if path.contains('\'') || path.contains('"') || path.contains(' ') {
|
||||
path = format!("`{}`", path);
|
||||
// Fix files or folders with quotes or hash
|
||||
if path.contains('\'')
|
||||
|| path.contains('"')
|
||||
|| path.contains(' ')
|
||||
|| path.contains('#')
|
||||
{
|
||||
path = format!("`{path}`");
|
||||
}
|
||||
|
||||
Some((span, path))
|
||||
|
@ -58,7 +58,7 @@ impl Completer for DotNuCompletion {
|
||||
};
|
||||
|
||||
// Check if the base_dir is a folder
|
||||
if base_dir != format!(".{}", SEP) {
|
||||
if base_dir != format!(".{SEP}") {
|
||||
// Add the base dir into the directories to be searched
|
||||
search_dirs.push(base_dir.clone());
|
||||
|
||||
@ -70,14 +70,7 @@ impl Completer for DotNuCompletion {
|
||||
partial = base_dir_partial;
|
||||
} else {
|
||||
// Fetch the current folder
|
||||
let current_folder = if let Some(d) = self.engine_state.get_env_var("PWD") {
|
||||
match d.as_string() {
|
||||
Ok(s) => s,
|
||||
Err(_) => "".to_string(),
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let current_folder = self.engine_state.current_work_dir();
|
||||
is_current_folder = true;
|
||||
|
||||
// Add the current folder and the lib dirs into the
|
||||
|
@ -7,6 +7,8 @@ use reedline::Suggestion;
|
||||
use std::path::{is_separator, Path};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::SortBy;
|
||||
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -30,14 +32,7 @@ impl Completer for FileCompletion {
|
||||
_: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
let cwd = if let Some(d) = self.engine_state.get_env_var("PWD") {
|
||||
match d.as_string() {
|
||||
Ok(s) => s,
|
||||
Err(_) => "".to_string(),
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let cwd = self.engine_state.current_work_dir();
|
||||
let prefix = String::from_utf8_lossy(&prefix).to_string();
|
||||
let output: Vec<_> = file_path_completion(span, &prefix, &cwd, options)
|
||||
.into_iter()
|
||||
@ -62,12 +57,20 @@ impl Completer for FileCompletion {
|
||||
|
||||
// Sort items
|
||||
let mut sorted_items = items;
|
||||
sorted_items.sort_by(|a, b| a.value.cmp(&b.value));
|
||||
sorted_items.sort_by(|a, b| {
|
||||
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
||||
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
||||
a_distance.cmp(&b_distance)
|
||||
});
|
||||
|
||||
match self.get_sort_by() {
|
||||
SortBy::Ascending => {
|
||||
sorted_items.sort_by(|a, b| a.value.cmp(&b.value));
|
||||
}
|
||||
SortBy::LevenshteinDistance => {
|
||||
sorted_items.sort_by(|a, b| {
|
||||
let a_distance = levenshtein_distance(&prefix_str, &a.value);
|
||||
let b_distance = levenshtein_distance(&prefix_str, &b.value);
|
||||
a_distance.cmp(&b_distance)
|
||||
});
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// Separate the results between hidden and non hidden
|
||||
let mut hidden: Vec<Suggestion> = vec![];
|
||||
@ -131,7 +134,7 @@ pub fn file_path_completion(
|
||||
let mut file_name = entry.file_name().to_string_lossy().into_owned();
|
||||
if matches(&partial, &file_name, options) {
|
||||
let mut path = if prepend_base_dir(original_input, &base_dir_name) {
|
||||
format!("{}{}", base_dir_name, file_name)
|
||||
format!("{base_dir_name}{file_name}")
|
||||
} else {
|
||||
file_name.to_string()
|
||||
};
|
||||
@ -141,9 +144,25 @@ pub fn file_path_completion(
|
||||
file_name.push(SEP);
|
||||
}
|
||||
|
||||
// Fix files or folders with quotes
|
||||
if path.contains('\'') || path.contains('"') || path.contains(' ') {
|
||||
path = format!("`{}`", path);
|
||||
// Fix files or folders with quotes or hashes
|
||||
if path.contains('\'')
|
||||
|| path.contains('"')
|
||||
|| path.contains(' ')
|
||||
|| path.contains('#')
|
||||
|| path.contains('(')
|
||||
|| path.contains(')')
|
||||
|| path.starts_with('0')
|
||||
|| path.starts_with('1')
|
||||
|| path.starts_with('2')
|
||||
|| path.starts_with('3')
|
||||
|| path.starts_with('4')
|
||||
|| path.starts_with('5')
|
||||
|| path.starts_with('6')
|
||||
|| path.starts_with('7')
|
||||
|| path.starts_with('8')
|
||||
|| path.starts_with('9')
|
||||
{
|
||||
path = format!("`{path}`");
|
||||
}
|
||||
|
||||
Some((span, path))
|
||||
@ -171,7 +190,7 @@ pub fn matches(partial: &str, from: &str, options: &CompletionOptions) -> bool {
|
||||
|
||||
/// Returns whether the base_dir should be prepended to the file path
|
||||
pub fn prepend_base_dir(input: &str, base_dir: &str) -> bool {
|
||||
if base_dir == format!(".{}", SEP) {
|
||||
if base_dir == format!(".{SEP}") {
|
||||
// if the current base_dir path is the local folder we only add a "./" prefix if the user
|
||||
// input already includes a local folder prefix.
|
||||
let manually_entered = {
|
||||
|
@ -9,6 +9,8 @@ use reedline::Suggestion;
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::MatchAlgorithm;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VariableCompletion {
|
||||
engine_state: Arc<EngineState>, // TODO: Is engine state necessary? It's already a part of working set in fetch()
|
||||
@ -73,10 +75,11 @@ impl Completer for VariableCompletion {
|
||||
for suggestion in
|
||||
nested_suggestions(val.clone(), nested_levels, current_span)
|
||||
{
|
||||
if options
|
||||
.match_algorithm
|
||||
.matches_u8(suggestion.value.as_bytes(), &prefix)
|
||||
{
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
suggestion.value.as_bytes(),
|
||||
&prefix,
|
||||
) {
|
||||
output.push(suggestion);
|
||||
}
|
||||
}
|
||||
@ -86,10 +89,11 @@ impl Completer for VariableCompletion {
|
||||
} else {
|
||||
// No nesting provided, return all env vars
|
||||
for env_var in env_vars {
|
||||
if options
|
||||
.match_algorithm
|
||||
.matches_u8(env_var.0.as_bytes(), &prefix)
|
||||
{
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
env_var.0.as_bytes(),
|
||||
&prefix,
|
||||
) {
|
||||
output.push(Suggestion {
|
||||
value: env_var.0,
|
||||
description: None,
|
||||
@ -111,18 +115,16 @@ impl Completer for VariableCompletion {
|
||||
&self.engine_state,
|
||||
&self.stack,
|
||||
nu_protocol::NU_VARIABLE_ID,
|
||||
nu_protocol::Span {
|
||||
start: current_span.start,
|
||||
end: current_span.end,
|
||||
},
|
||||
nu_protocol::Span::new(current_span.start, current_span.end),
|
||||
) {
|
||||
for suggestion in
|
||||
nested_suggestions(nuval, self.var_context.1.clone(), current_span)
|
||||
{
|
||||
if options
|
||||
.match_algorithm
|
||||
.matches_u8(suggestion.value.as_bytes(), &prefix)
|
||||
{
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
suggestion.value.as_bytes(),
|
||||
&prefix,
|
||||
) {
|
||||
output.push(suggestion);
|
||||
}
|
||||
}
|
||||
@ -134,23 +136,18 @@ impl Completer for VariableCompletion {
|
||||
// Completion other variable types
|
||||
if let Some(var_id) = var_id {
|
||||
// Extract the variable value from the stack
|
||||
let var = self.stack.get_var(
|
||||
var_id,
|
||||
Span {
|
||||
start: span.start,
|
||||
end: span.end,
|
||||
},
|
||||
);
|
||||
let var = self.stack.get_var(var_id, Span::new(span.start, span.end));
|
||||
|
||||
// If the value exists and it's of type Record
|
||||
if let Ok(value) = var {
|
||||
for suggestion in
|
||||
nested_suggestions(value, self.var_context.1.clone(), current_span)
|
||||
{
|
||||
if options
|
||||
.match_algorithm
|
||||
.matches_u8(suggestion.value.as_bytes(), &prefix)
|
||||
{
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
suggestion.value.as_bytes(),
|
||||
&prefix,
|
||||
) {
|
||||
output.push(suggestion);
|
||||
}
|
||||
}
|
||||
@ -162,10 +159,11 @@ impl Completer for VariableCompletion {
|
||||
|
||||
// Variable completion (e.g: $en<tab> to complete $env)
|
||||
for builtin in builtins {
|
||||
if options
|
||||
.match_algorithm
|
||||
.matches_u8(builtin.as_bytes(), &prefix)
|
||||
{
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
builtin.as_bytes(),
|
||||
&prefix,
|
||||
) {
|
||||
output.push(Suggestion {
|
||||
value: builtin.to_string(),
|
||||
description: None,
|
||||
@ -187,7 +185,11 @@ impl Completer for VariableCompletion {
|
||||
.rev()
|
||||
{
|
||||
for v in &overlay_frame.vars {
|
||||
if options.match_algorithm.matches_u8(v.0, &prefix) {
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
v.0,
|
||||
&prefix,
|
||||
) {
|
||||
output.push(Suggestion {
|
||||
value: String::from_utf8_lossy(v.0).to_string(),
|
||||
description: None,
|
||||
@ -209,7 +211,11 @@ impl Completer for VariableCompletion {
|
||||
.rev()
|
||||
{
|
||||
for v in &overlay_frame.vars {
|
||||
if options.match_algorithm.matches_u8(v.0, &prefix) {
|
||||
if options.match_algorithm.matches_u8_insensitive(
|
||||
options.case_sensitive,
|
||||
v.0,
|
||||
&prefix,
|
||||
) {
|
||||
output.push(Suggestion {
|
||||
value: String::from_utf8_lossy(v.0).to_string(),
|
||||
description: None,
|
||||
@ -256,6 +262,20 @@ fn nested_suggestions(
|
||||
|
||||
output
|
||||
}
|
||||
Value::LazyRecord { val, .. } => {
|
||||
// Add all the columns as completion
|
||||
for column_name in val.column_names() {
|
||||
output.push(Suggestion {
|
||||
value: column_name.to_string(),
|
||||
description: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
_ => output,
|
||||
}
|
||||
@ -281,7 +301,7 @@ fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
|
||||
|
||||
// Current sublevel value not found
|
||||
return Value::Nothing {
|
||||
span: Span { start: 0, end: 0 },
|
||||
span: Span::unknown(),
|
||||
};
|
||||
}
|
||||
_ => return val,
|
||||
@ -290,3 +310,13 @@ fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
|
||||
|
||||
val
|
||||
}
|
||||
|
||||
impl MatchAlgorithm {
|
||||
pub fn matches_u8_insensitive(&self, sensitive: bool, haystack: &[u8], needle: &[u8]) -> bool {
|
||||
if sensitive {
|
||||
self.matches_u8(haystack, needle)
|
||||
} else {
|
||||
self.matches_u8(&haystack.to_ascii_lowercase(), &needle.to_ascii_lowercase())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,14 @@
|
||||
use crate::util::{eval_source, report_error};
|
||||
#[cfg(feature = "plugin")]
|
||||
use log::info;
|
||||
use nu_protocol::engine::{EngineState, Stack, StateDelta, StateWorkingSet};
|
||||
use nu_protocol::{HistoryFileFormat, PipelineData, Span};
|
||||
use nu_parser::ParseError;
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_path::canonicalize_with;
|
||||
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_protocol::Spanned;
|
||||
use nu_protocol::{HistoryFileFormat, PipelineData};
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_utils::utils::perf;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
@ -15,36 +21,58 @@ const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
|
||||
pub fn read_plugin_file(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
plugin_file: Option<Spanned<String>>,
|
||||
storage_path: &str,
|
||||
is_perf_true: bool,
|
||||
) {
|
||||
let start_time = std::time::Instant::now();
|
||||
let mut plug_path = String::new();
|
||||
// Reading signatures from signature file
|
||||
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
||||
add_plugin_file(engine_state, storage_path);
|
||||
add_plugin_file(engine_state, plugin_file, storage_path);
|
||||
|
||||
let plugin_path = engine_state.plugin_signatures.clone();
|
||||
if let Some(plugin_path) = plugin_path {
|
||||
let plugin_filename = plugin_path.to_string_lossy().to_owned();
|
||||
|
||||
let plugin_filename = plugin_path.to_string_lossy();
|
||||
plug_path = plugin_filename.to_string();
|
||||
if let Ok(contents) = std::fs::read(&plugin_path) {
|
||||
eval_source(
|
||||
engine_state,
|
||||
stack,
|
||||
&contents,
|
||||
&plugin_filename,
|
||||
PipelineData::new(Span::new(0, 0)),
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if is_perf_true {
|
||||
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
perf(
|
||||
&format!("read_plugin_file {}", &plug_path),
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
engine_state.get_config().use_ansi_coloring,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
pub fn add_plugin_file(engine_state: &mut EngineState, storage_path: &str) {
|
||||
if let Some(mut plugin_path) = nu_path::config_dir() {
|
||||
pub fn add_plugin_file(
|
||||
engine_state: &mut EngineState,
|
||||
plugin_file: Option<Spanned<String>>,
|
||||
storage_path: &str,
|
||||
) {
|
||||
if let Some(plugin_file) = plugin_file {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
let cwd = working_set.get_cwd();
|
||||
|
||||
if let Ok(path) = canonicalize_with(&plugin_file.item, cwd) {
|
||||
engine_state.plugin_signatures = Some(path)
|
||||
} else {
|
||||
let e = ParseError::FileNotFound(plugin_file.item, plugin_file.span);
|
||||
report_error(&working_set, &e);
|
||||
}
|
||||
} else if let Some(mut plugin_path) = nu_path::config_dir() {
|
||||
// Path to store plugins signatures
|
||||
plugin_path.push(storage_path);
|
||||
plugin_path.push(PLUGIN_FILE);
|
||||
@ -58,7 +86,7 @@ pub fn eval_config_contents(
|
||||
stack: &mut Stack,
|
||||
) {
|
||||
if config_path.exists() & config_path.is_file() {
|
||||
let config_filename = config_path.to_string_lossy().to_owned();
|
||||
let config_filename = config_path.to_string_lossy();
|
||||
|
||||
if let Ok(contents) = std::fs::read(&config_path) {
|
||||
eval_source(
|
||||
@ -66,15 +94,14 @@ pub fn eval_config_contents(
|
||||
stack,
|
||||
&contents,
|
||||
&config_filename,
|
||||
PipelineData::new(Span::new(0, 0)),
|
||||
PipelineData::empty(),
|
||||
false,
|
||||
);
|
||||
|
||||
// Merge the delta in case env vars changed in the config
|
||||
// Merge the environment in case env vars changed in the config
|
||||
match nu_engine::env::current_dir(engine_state, stack) {
|
||||
Ok(cwd) => {
|
||||
if let Err(e) =
|
||||
engine_state.merge_delta(StateDelta::new(engine_state), Some(stack), cwd)
|
||||
{
|
||||
if let Err(e) = engine_state.merge_env(stack, cwd) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
}
|
||||
|
@ -2,13 +2,13 @@ use crate::util::{eval_source, report_error};
|
||||
use log::info;
|
||||
use log::trace;
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use nu_engine::convert_env_values;
|
||||
use nu_engine::{convert_env_values, current_dir};
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::Type;
|
||||
use nu_path::canonicalize_with;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Config, PipelineData, Span, Value,
|
||||
Config, PipelineData, ShellError, Span, Type, Value,
|
||||
};
|
||||
use nu_utils::stdout_write_all_and_flush;
|
||||
|
||||
@ -19,7 +19,6 @@ pub fn evaluate_file(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
input: PipelineData,
|
||||
is_perf_true: bool,
|
||||
) -> Result<()> {
|
||||
// Translate environment variables from Strings to Values
|
||||
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||
@ -28,12 +27,75 @@ pub fn evaluate_file(
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let file = std::fs::read(&path).into_diagnostic()?;
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
|
||||
let file_path = canonicalize_with(&path, cwd).unwrap_or_else(|e| {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::FileNotFoundCustom(
|
||||
format!("Could not access file '{}': {:?}", path, e.to_string()),
|
||||
Span::unknown(),
|
||||
),
|
||||
);
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
let file_path_str = file_path.to_str().unwrap_or_else(|| {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::NonUtf8Custom(
|
||||
format!(
|
||||
"Input file name '{}' is not valid UTF8",
|
||||
file_path.to_string_lossy()
|
||||
),
|
||||
Span::unknown(),
|
||||
),
|
||||
);
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
let file = std::fs::read(&file_path)
|
||||
.into_diagnostic()
|
||||
.unwrap_or_else(|e| {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::FileNotFoundCustom(
|
||||
format!(
|
||||
"Could not read file '{}': {:?}",
|
||||
file_path_str,
|
||||
e.to_string()
|
||||
),
|
||||
Span::unknown(),
|
||||
),
|
||||
);
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
engine_state.start_in_file(Some(file_path_str));
|
||||
|
||||
let parent = file_path.parent().unwrap_or_else(|| {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::FileNotFoundCustom(
|
||||
format!("The file path '{file_path_str}' does not have a parent"),
|
||||
Span::unknown(),
|
||||
),
|
||||
);
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
stack.add_env_var(
|
||||
"FILE_PWD".to_string(),
|
||||
Value::string(parent.to_string_lossy(), Span::unknown()),
|
||||
);
|
||||
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
trace!("parsing file: {}", path);
|
||||
|
||||
let _ = parse(&mut working_set, Some(&path), &file, false, &[]);
|
||||
trace!("parsing file: {}", file_path_str);
|
||||
let _ = parse(&mut working_set, Some(file_path_str), &file, false, &[]);
|
||||
|
||||
if working_set.find_decl(b"main", &Type::Any).is_some() {
|
||||
let args = format!("main {}", args.join(" "));
|
||||
@ -42,26 +104,32 @@ pub fn evaluate_file(
|
||||
engine_state,
|
||||
stack,
|
||||
&file,
|
||||
&path,
|
||||
PipelineData::new(Span::new(0, 0)),
|
||||
file_path_str,
|
||||
PipelineData::empty(),
|
||||
true,
|
||||
) {
|
||||
std::process::exit(1);
|
||||
}
|
||||
if !eval_source(engine_state, stack, args.as_bytes(), "<commandline>", input) {
|
||||
if !eval_source(
|
||||
engine_state,
|
||||
stack,
|
||||
args.as_bytes(),
|
||||
"<commandline>",
|
||||
input,
|
||||
true,
|
||||
) {
|
||||
std::process::exit(1);
|
||||
}
|
||||
} else if !eval_source(engine_state, stack, &file, &path, input) {
|
||||
} else if !eval_source(engine_state, stack, &file, file_path_str, input, true) {
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if is_perf_true {
|
||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn print_table_or_error(
|
||||
pub(crate) fn print_table_or_error(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
mut pipeline_data: PipelineData,
|
||||
@ -75,58 +143,36 @@ pub fn print_table_or_error(
|
||||
// Change the engine_state config to use the passed in configuration
|
||||
engine_state.set_config(config);
|
||||
|
||||
match engine_state.find_decl("table".as_bytes(), &[]) {
|
||||
Some(decl_id) => {
|
||||
let table = engine_state.get_decl(decl_id).run(
|
||||
engine_state,
|
||||
stack,
|
||||
&Call::new(Span::new(0, 0)),
|
||||
pipeline_data,
|
||||
);
|
||||
if let PipelineData::Value(Value::Error { error }, ..) = &pipeline_data {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &**error);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if let Some(decl_id) = engine_state.find_decl("table".as_bytes(), &[]) {
|
||||
let command = engine_state.get_decl(decl_id);
|
||||
if command.get_block_id().is_some() {
|
||||
print_or_exit(pipeline_data, engine_state, config);
|
||||
} else {
|
||||
// The final call on table command, it's ok to set redirect_output to false.
|
||||
let mut call = Call::new(Span::new(0, 0));
|
||||
call.redirect_stdout = false;
|
||||
let table = command.run(engine_state, stack, &call, pipeline_data);
|
||||
|
||||
match table {
|
||||
Ok(table) => {
|
||||
for item in table {
|
||||
if let Value::Error { error } = item {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &error);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let mut out = item.into_string("\n", config);
|
||||
out.push('\n');
|
||||
|
||||
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
|
||||
}
|
||||
print_or_exit(table, engine_state, config);
|
||||
}
|
||||
Err(error) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &error);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
for item in pipeline_data {
|
||||
if let Value::Error { error } = item {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &error);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let mut out = item.into_string("\n", config);
|
||||
out.push('\n');
|
||||
|
||||
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
print_or_exit(pipeline_data, engine_state, config);
|
||||
}
|
||||
|
||||
// Make sure everything has finished
|
||||
if let Some(exit_code) = exit_code {
|
||||
@ -141,3 +187,18 @@ pub fn print_table_or_error(
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, config: &Config) {
|
||||
for item in pipeline_data {
|
||||
if let Value::Error { error } = item {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &*error);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let out = item.into_string("\n", config) + "\n";
|
||||
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{err}"));
|
||||
}
|
||||
}
|
||||
|
@ -22,8 +22,9 @@ pub use nu_highlight::NuHighlight;
|
||||
pub use print::Print;
|
||||
pub use prompt::NushellPrompt;
|
||||
pub use repl::evaluate_repl;
|
||||
pub use repl::{eval_env_change_hook, eval_hook};
|
||||
pub use syntax_highlight::NuHighlighter;
|
||||
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error};
|
||||
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error, report_error_new};
|
||||
pub use validation::NuValidator;
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
|
@ -1,8 +1,8 @@
|
||||
use {
|
||||
nu_ansi_term::{ansi::RESET, Style},
|
||||
reedline::{
|
||||
menu_functions::string_difference, Completer, LineBuffer, Menu, MenuEvent, MenuTextStyle,
|
||||
Painter, Suggestion,
|
||||
menu_functions::string_difference, Completer, Editor, Menu, MenuEvent, MenuTextStyle,
|
||||
Painter, Suggestion, UndoBehavior,
|
||||
},
|
||||
};
|
||||
|
||||
@ -372,7 +372,7 @@ impl DescriptionMenu {
|
||||
let description = self
|
||||
.get_value()
|
||||
.and_then(|suggestion| suggestion.description)
|
||||
.unwrap_or_else(|| "".to_string())
|
||||
.unwrap_or_default()
|
||||
.lines()
|
||||
.skip(self.skipped_rows)
|
||||
.take(self.working_details.description_rows)
|
||||
@ -411,10 +411,10 @@ impl DescriptionMenu {
|
||||
RESET
|
||||
)
|
||||
} else {
|
||||
format!(" {}\r\n", example)
|
||||
format!(" {example}\r\n")
|
||||
}
|
||||
} else {
|
||||
format!(" {}\r\n", example)
|
||||
format!(" {example}\r\n")
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@ -429,7 +429,7 @@ impl DescriptionMenu {
|
||||
examples,
|
||||
)
|
||||
} else {
|
||||
format!("\r\n\r\nExamples:\r\n{}", examples,)
|
||||
format!("\r\n\r\nExamples:\r\n{examples}",)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -459,7 +459,7 @@ impl Menu for DescriptionMenu {
|
||||
fn can_partially_complete(
|
||||
&mut self,
|
||||
_values_updated: bool,
|
||||
_line_buffer: &mut LineBuffer,
|
||||
_editor: &mut Editor,
|
||||
_completer: &mut dyn Completer,
|
||||
) -> bool {
|
||||
false
|
||||
@ -481,19 +481,21 @@ impl Menu for DescriptionMenu {
|
||||
}
|
||||
|
||||
/// Updates menu values
|
||||
fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &mut dyn Completer) {
|
||||
fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer) {
|
||||
if self.only_buffer_difference {
|
||||
if let Some(old_string) = &self.input {
|
||||
let (start, input) = string_difference(line_buffer.get_buffer(), old_string);
|
||||
let (start, input) = string_difference(editor.get_buffer(), old_string);
|
||||
if !input.is_empty() {
|
||||
self.reset_position();
|
||||
self.values = completer.complete(input, start);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let trimmed_buffer = line_buffer.get_buffer().replace('\n', " ");
|
||||
self.values =
|
||||
completer.complete(trimmed_buffer.as_str(), line_buffer.insertion_point());
|
||||
let trimmed_buffer = editor.get_buffer().replace('\n', " ");
|
||||
self.values = completer.complete(
|
||||
trimmed_buffer.as_str(),
|
||||
editor.line_buffer().insertion_point(),
|
||||
);
|
||||
self.reset_position();
|
||||
}
|
||||
}
|
||||
@ -502,7 +504,7 @@ impl Menu for DescriptionMenu {
|
||||
/// collected from the completer
|
||||
fn update_working_details(
|
||||
&mut self,
|
||||
line_buffer: &mut LineBuffer,
|
||||
editor: &mut Editor,
|
||||
completer: &mut dyn Completer,
|
||||
painter: &Painter,
|
||||
) {
|
||||
@ -560,13 +562,13 @@ impl Menu for DescriptionMenu {
|
||||
match event {
|
||||
MenuEvent::Activate(_) => {
|
||||
self.reset_position();
|
||||
self.input = Some(line_buffer.get_buffer().to_string());
|
||||
self.update_values(line_buffer, completer);
|
||||
self.input = Some(editor.get_buffer().to_string());
|
||||
self.update_values(editor, completer);
|
||||
}
|
||||
MenuEvent::Deactivate => self.active = false,
|
||||
MenuEvent::Edit(_) => {
|
||||
self.reset_position();
|
||||
self.update_values(line_buffer, completer);
|
||||
self.update_values(editor, completer);
|
||||
self.update_examples()
|
||||
}
|
||||
MenuEvent::NextElement => {
|
||||
@ -608,7 +610,7 @@ impl Menu for DescriptionMenu {
|
||||
let description_rows = self
|
||||
.get_value()
|
||||
.and_then(|suggestion| suggestion.description)
|
||||
.unwrap_or_else(|| "".to_string())
|
||||
.unwrap_or_default()
|
||||
.lines()
|
||||
.count();
|
||||
|
||||
@ -627,27 +629,28 @@ impl Menu for DescriptionMenu {
|
||||
}
|
||||
|
||||
/// The buffer gets replaced in the Span location
|
||||
fn replace_in_buffer(&self, line_buffer: &mut LineBuffer) {
|
||||
fn replace_in_buffer(&self, editor: &mut Editor) {
|
||||
if let Some(Suggestion { value, span, .. }) = self.get_value() {
|
||||
let start = span.start.min(line_buffer.len());
|
||||
let end = span.end.min(line_buffer.len());
|
||||
let start = span.start.min(editor.line_buffer().len());
|
||||
let end = span.end.min(editor.line_buffer().len());
|
||||
|
||||
let string_len = if let Some(example_index) = self.example_index {
|
||||
let example = self
|
||||
.examples
|
||||
let replacement = if let Some(example_index) = self.example_index {
|
||||
self.examples
|
||||
.get(example_index)
|
||||
.expect("the example index is always checked");
|
||||
|
||||
line_buffer.replace(start..end, example);
|
||||
example.len()
|
||||
.expect("the example index is always checked")
|
||||
} else {
|
||||
line_buffer.replace(start..end, &value);
|
||||
value.len()
|
||||
&value
|
||||
};
|
||||
|
||||
let mut offset = line_buffer.insertion_point();
|
||||
offset += string_len.saturating_sub(end.saturating_sub(start));
|
||||
line_buffer.set_insertion_point(offset);
|
||||
editor.edit_buffer(
|
||||
|lb| {
|
||||
lb.replace_range(start..end, replacement);
|
||||
let mut offset = lb.insertion_point();
|
||||
offset += lb.len().saturating_sub(end.saturating_sub(start));
|
||||
lb.set_insertion_point(offset);
|
||||
},
|
||||
UndoBehavior::CreateUndoPoint,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ impl NuHelpCompleter {
|
||||
//Vec<(Signature, Vec<Example>, bool, bool)> {
|
||||
let mut commands = full_commands
|
||||
.iter()
|
||||
.filter(|(sig, _, _, _)| {
|
||||
.filter(|(sig, _, _, _, _)| {
|
||||
sig.name.to_lowercase().contains(&line.to_lowercase())
|
||||
|| sig.usage.to_lowercase().contains(&line.to_lowercase())
|
||||
|| sig
|
||||
@ -31,7 +31,7 @@ impl NuHelpCompleter {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
commands.sort_by(|(a, _, _, _), (b, _, _, _)| {
|
||||
commands.sort_by(|(a, _, _, _, _), (b, _, _, _, _)| {
|
||||
let a_distance = levenshtein_distance(line, &a.name);
|
||||
let b_distance = levenshtein_distance(line, &b.name);
|
||||
a_distance.cmp(&b_distance)
|
||||
@ -39,7 +39,7 @@ impl NuHelpCompleter {
|
||||
|
||||
commands
|
||||
.into_iter()
|
||||
.map(|(sig, examples, _, _)| {
|
||||
.map(|(sig, examples, _, _, _)| {
|
||||
let mut long_desc = String::new();
|
||||
|
||||
let usage = &sig.usage;
|
||||
|
@ -42,20 +42,14 @@ impl Completer for NuMenuCompleter {
|
||||
|
||||
if let Some(buffer) = block.signature.get_positional(0) {
|
||||
if let Some(buffer_id) = &buffer.var_id {
|
||||
let line_buffer = Value::String {
|
||||
val: parsed.remainder.to_string(),
|
||||
span: self.span,
|
||||
};
|
||||
let line_buffer = Value::string(parsed.remainder, self.span);
|
||||
self.stack.add_var(*buffer_id, line_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(position) = block.signature.get_positional(1) {
|
||||
if let Some(position_id) = &position.var_id {
|
||||
let line_buffer = Value::Int {
|
||||
val: pos as i64,
|
||||
span: self.span,
|
||||
};
|
||||
let line_buffer = Value::int(pos as i64, self.span);
|
||||
self.stack.add_var(*position_id, line_buffer);
|
||||
}
|
||||
}
|
||||
@ -87,13 +81,10 @@ fn convert_to_suggestions(
|
||||
) -> Vec<Suggestion> {
|
||||
match value {
|
||||
Value::Record { .. } => {
|
||||
let text = match value
|
||||
let text = value
|
||||
.get_data_by_key("value")
|
||||
.and_then(|val| val.as_string().ok())
|
||||
{
|
||||
Some(val) => val,
|
||||
None => "No value key".to_string(),
|
||||
};
|
||||
.unwrap_or_else(|| "No value key".to_string());
|
||||
|
||||
let description = value
|
||||
.get_data_by_key("description")
|
||||
@ -163,7 +154,7 @@ fn convert_to_suggestions(
|
||||
.flat_map(|val| convert_to_suggestions(val, line, pos, only_buffer_difference))
|
||||
.collect(),
|
||||
_ => vec![Suggestion {
|
||||
value: format!("Not a record: {:?}", value),
|
||||
value: format!("Not a record: {value:?}"),
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Value};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Type, Value};
|
||||
use reedline::Highlighter;
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -12,7 +12,9 @@ impl Command for NuHighlight {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("nu-highlight").category(Category::Strings)
|
||||
Signature::build("nu-highlight")
|
||||
.category(Category::Strings)
|
||||
.input_output_types(vec![(Type::String, Type::String)])
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -33,7 +35,7 @@ impl Command for NuHighlight {
|
||||
let head = call.head;
|
||||
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let engine_state = engine_state.clone();
|
||||
let engine_state = std::sync::Arc::new(engine_state.clone());
|
||||
let config = engine_state.get_config().clone();
|
||||
|
||||
let highlighter = crate::NuHighlighter {
|
||||
@ -51,7 +53,9 @@ impl Command for NuHighlight {
|
||||
span: head,
|
||||
}
|
||||
}
|
||||
Err(err) => Value::Error { error: err },
|
||||
Err(err) => Value::Error {
|
||||
error: Box::new(err),
|
||||
},
|
||||
},
|
||||
ctrlc,
|
||||
)
|
||||
|
@ -2,7 +2,8 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type,
|
||||
Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -15,6 +16,7 @@ impl Command for Print {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("print")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.rest("rest", SyntaxShape::Any, "the values to print")
|
||||
.switch(
|
||||
"no-newline",
|
||||
@ -26,7 +28,7 @@ impl Command for Print {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Print the given values to stdout"
|
||||
"Print the given values to stdout."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
@ -45,19 +47,23 @@ Since this command has no output, there is no point in piping it with other comm
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||
let no_newline = call.has_flag("no-newline");
|
||||
let to_stderr = call.has_flag("stderr");
|
||||
let head = call.head;
|
||||
|
||||
for arg in args {
|
||||
arg.into_pipeline_data()
|
||||
.print(engine_state, stack, no_newline, to_stderr)?;
|
||||
// This will allow for easy printing of pipelines as well
|
||||
if !args.is_empty() {
|
||||
for arg in args {
|
||||
arg.into_pipeline_data()
|
||||
.print(engine_state, stack, no_newline, to_stderr)?;
|
||||
}
|
||||
} else if !input.is_nothing() {
|
||||
input.print(engine_state, stack, no_newline, to_stderr)?;
|
||||
}
|
||||
|
||||
Ok(PipelineData::new(head))
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -1,5 +1,6 @@
|
||||
#[cfg(windows)]
|
||||
use nu_utils::enable_vt_processing;
|
||||
use reedline::DefaultPrompt;
|
||||
|
||||
use {
|
||||
reedline::{
|
||||
Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode,
|
||||
@ -16,6 +17,7 @@ pub struct NushellPrompt {
|
||||
default_vi_insert_prompt_indicator: Option<String>,
|
||||
default_vi_normal_prompt_indicator: Option<String>,
|
||||
default_multiline_indicator: Option<String>,
|
||||
render_right_prompt_on_last_line: bool,
|
||||
}
|
||||
|
||||
impl Default for NushellPrompt {
|
||||
@ -33,6 +35,7 @@ impl NushellPrompt {
|
||||
default_vi_insert_prompt_indicator: None,
|
||||
default_vi_normal_prompt_indicator: None,
|
||||
default_multiline_indicator: None,
|
||||
render_right_prompt_on_last_line: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,8 +43,13 @@ impl NushellPrompt {
|
||||
self.left_prompt_string = prompt_string;
|
||||
}
|
||||
|
||||
pub fn update_prompt_right(&mut self, prompt_string: Option<String>) {
|
||||
pub fn update_prompt_right(
|
||||
&mut self,
|
||||
prompt_string: Option<String>,
|
||||
render_right_prompt_on_last_line: bool,
|
||||
) {
|
||||
self.right_prompt_string = prompt_string;
|
||||
self.render_right_prompt_on_last_line = render_right_prompt_on_last_line;
|
||||
}
|
||||
|
||||
pub fn update_prompt_indicator(&mut self, prompt_indicator_string: Option<String>) {
|
||||
@ -67,6 +75,7 @@ impl NushellPrompt {
|
||||
prompt_indicator_string: Option<String>,
|
||||
prompt_multiline_indicator_string: Option<String>,
|
||||
prompt_vi: (Option<String>, Option<String>),
|
||||
render_right_prompt_on_last_line: bool,
|
||||
) {
|
||||
let (prompt_vi_insert_string, prompt_vi_normal_string) = prompt_vi;
|
||||
|
||||
@ -77,19 +86,26 @@ impl NushellPrompt {
|
||||
|
||||
self.default_vi_insert_prompt_indicator = prompt_vi_insert_string;
|
||||
self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
|
||||
|
||||
self.render_right_prompt_on_last_line = render_right_prompt_on_last_line;
|
||||
}
|
||||
|
||||
fn default_wrapped_custom_string(&self, str: String) -> String {
|
||||
format!("({})", str)
|
||||
format!("({str})")
|
||||
}
|
||||
}
|
||||
|
||||
impl Prompt for NushellPrompt {
|
||||
fn render_prompt_left(&self) -> Cow<str> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = enable_vt_processing();
|
||||
}
|
||||
|
||||
if let Some(prompt_string) = &self.left_prompt_string {
|
||||
prompt_string.replace('\n', "\r\n").into()
|
||||
} else {
|
||||
let default = DefaultPrompt::new();
|
||||
let default = DefaultPrompt::default();
|
||||
default
|
||||
.render_prompt_left()
|
||||
.to_string()
|
||||
@ -102,7 +118,7 @@ impl Prompt for NushellPrompt {
|
||||
if let Some(prompt_string) = &self.right_prompt_string {
|
||||
prompt_string.replace('\n', "\r\n").into()
|
||||
} else {
|
||||
let default = DefaultPrompt::new();
|
||||
let default = DefaultPrompt::default();
|
||||
default
|
||||
.render_prompt_right()
|
||||
.to_string()
|
||||
@ -114,32 +130,36 @@ impl Prompt for NushellPrompt {
|
||||
fn render_prompt_indicator(&self, edit_mode: PromptEditMode) -> Cow<str> {
|
||||
match edit_mode {
|
||||
PromptEditMode::Default => match &self.default_prompt_indicator {
|
||||
Some(indicator) => indicator.as_str().into(),
|
||||
None => "〉".into(),
|
||||
},
|
||||
Some(indicator) => indicator,
|
||||
None => "> ",
|
||||
}
|
||||
.into(),
|
||||
PromptEditMode::Emacs => match &self.default_prompt_indicator {
|
||||
Some(indicator) => indicator.as_str().into(),
|
||||
None => "〉".into(),
|
||||
},
|
||||
Some(indicator) => indicator,
|
||||
None => "> ",
|
||||
}
|
||||
.into(),
|
||||
PromptEditMode::Vi(vi_mode) => match vi_mode {
|
||||
PromptViMode::Normal => match &self.default_vi_normal_prompt_indicator {
|
||||
Some(indicator) => indicator.as_str().into(),
|
||||
None => ": ".into(),
|
||||
Some(indicator) => indicator,
|
||||
None => ": ",
|
||||
},
|
||||
PromptViMode::Insert => match &self.default_vi_insert_prompt_indicator {
|
||||
Some(indicator) => indicator.as_str().into(),
|
||||
None => "〉".into(),
|
||||
Some(indicator) => indicator,
|
||||
None => "> ",
|
||||
},
|
||||
},
|
||||
}
|
||||
.into(),
|
||||
PromptEditMode::Custom(str) => self.default_wrapped_custom_string(str).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
|
||||
match &self.default_multiline_indicator {
|
||||
Some(indicator) => indicator.as_str().into(),
|
||||
None => "::: ".into(),
|
||||
Some(indicator) => indicator,
|
||||
None => "::: ",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
fn render_prompt_history_search_indicator(
|
||||
@ -156,4 +176,8 @@ impl Prompt for NushellPrompt {
|
||||
prefix, history_search.term
|
||||
))
|
||||
}
|
||||
|
||||
fn right_prompt_on_last_line(&self) -> bool {
|
||||
self.render_right_prompt_on_last_line
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::util::report_error;
|
||||
use crate::NushellPrompt;
|
||||
use log::info;
|
||||
use log::trace;
|
||||
use nu_engine::eval_subexpression;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Config, PipelineData, Span, Value,
|
||||
Config, PipelineData, Value,
|
||||
};
|
||||
use reedline::Prompt;
|
||||
|
||||
@ -15,18 +15,21 @@ pub(crate) const PROMPT_INDICATOR: &str = "PROMPT_INDICATOR";
|
||||
pub(crate) const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT";
|
||||
pub(crate) const PROMPT_INDICATOR_VI_NORMAL: &str = "PROMPT_INDICATOR_VI_NORMAL";
|
||||
pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR";
|
||||
// According to Daniel Imms @Tyriar, we need to do these this way:
|
||||
// <133 A><prompt><133 B><command><133 C><command output>
|
||||
const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
|
||||
const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
|
||||
|
||||
fn get_prompt_string(
|
||||
prompt: &str,
|
||||
config: &Config,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
is_perf_true: bool,
|
||||
) -> Option<String> {
|
||||
stack
|
||||
.get_env_var(engine_state, prompt)
|
||||
.and_then(|v| match v {
|
||||
Value::Block {
|
||||
Value::Closure {
|
||||
val: block_id,
|
||||
captures,
|
||||
..
|
||||
@ -34,29 +37,39 @@ fn get_prompt_string(
|
||||
let block = engine_state.get_block(block_id);
|
||||
let mut stack = stack.captures_to_stack(&captures);
|
||||
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
|
||||
let ret_val = eval_subexpression(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
block,
|
||||
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
|
||||
let ret_val =
|
||||
eval_subexpression(engine_state, &mut stack, block, PipelineData::empty());
|
||||
trace!(
|
||||
"get_prompt_string (block) {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"get_prompt_string (block) {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
}
|
||||
|
||||
match ret_val {
|
||||
Ok(ret_val) => Some(ret_val),
|
||||
Err(err) => {
|
||||
ret_val
|
||||
.map_err(|err| {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
Value::Block { val: block_id, .. } => {
|
||||
let block = engine_state.get_block(block_id);
|
||||
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
|
||||
let ret_val = eval_subexpression(engine_state, stack, block, PipelineData::empty());
|
||||
trace!(
|
||||
"get_prompt_string (block) {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
|
||||
ret_val
|
||||
.map_err(|err| {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
Value::String { .. } => Some(PipelineData::Value(v.clone(), None)),
|
||||
_ => None,
|
||||
@ -64,20 +77,17 @@ fn get_prompt_string(
|
||||
.and_then(|pipeline_data| {
|
||||
let output = pipeline_data.collect_string("", config).ok();
|
||||
|
||||
match output {
|
||||
Some(mut x) => {
|
||||
// Just remove the very last newline.
|
||||
if x.ends_with('\n') {
|
||||
x.pop();
|
||||
}
|
||||
|
||||
if x.ends_with('\r') {
|
||||
x.pop();
|
||||
}
|
||||
Some(x)
|
||||
output.map(|mut x| {
|
||||
// Just remove the very last newline.
|
||||
if x.ends_with('\n') {
|
||||
x.pop();
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
|
||||
if x.ends_with('\r') {
|
||||
x.pop();
|
||||
}
|
||||
x
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -86,57 +96,39 @@ pub(crate) fn update_prompt<'prompt>(
|
||||
engine_state: &EngineState,
|
||||
stack: &Stack,
|
||||
nu_prompt: &'prompt mut NushellPrompt,
|
||||
is_perf_true: bool,
|
||||
) -> &'prompt dyn Prompt {
|
||||
let mut stack = stack.clone();
|
||||
|
||||
let left_prompt_string = get_prompt_string(
|
||||
PROMPT_COMMAND,
|
||||
config,
|
||||
engine_state,
|
||||
&mut stack,
|
||||
is_perf_true,
|
||||
);
|
||||
let left_prompt_string = get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack);
|
||||
|
||||
let right_prompt_string = get_prompt_string(
|
||||
PROMPT_COMMAND_RIGHT,
|
||||
config,
|
||||
engine_state,
|
||||
&mut stack,
|
||||
is_perf_true,
|
||||
);
|
||||
// Now that we have the prompt string lets ansify it.
|
||||
// <133 A><prompt><133 B><command><133 C><command output>
|
||||
let left_prompt_string = if config.shell_integration {
|
||||
if let Some(prompt_string) = left_prompt_string {
|
||||
Some(format!(
|
||||
"{PRE_PROMPT_MARKER}{prompt_string}{POST_PROMPT_MARKER}"
|
||||
))
|
||||
} else {
|
||||
left_prompt_string
|
||||
}
|
||||
} else {
|
||||
left_prompt_string
|
||||
};
|
||||
|
||||
let prompt_indicator_string = get_prompt_string(
|
||||
PROMPT_INDICATOR,
|
||||
config,
|
||||
engine_state,
|
||||
&mut stack,
|
||||
is_perf_true,
|
||||
);
|
||||
let right_prompt_string =
|
||||
get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, &mut stack);
|
||||
|
||||
let prompt_multiline_string = get_prompt_string(
|
||||
PROMPT_MULTILINE_INDICATOR,
|
||||
config,
|
||||
engine_state,
|
||||
&mut stack,
|
||||
is_perf_true,
|
||||
);
|
||||
let prompt_indicator_string =
|
||||
get_prompt_string(PROMPT_INDICATOR, config, engine_state, &mut stack);
|
||||
|
||||
let prompt_vi_insert_string = get_prompt_string(
|
||||
PROMPT_INDICATOR_VI_INSERT,
|
||||
config,
|
||||
engine_state,
|
||||
&mut stack,
|
||||
is_perf_true,
|
||||
);
|
||||
let prompt_multiline_string =
|
||||
get_prompt_string(PROMPT_MULTILINE_INDICATOR, config, engine_state, &mut stack);
|
||||
|
||||
let prompt_vi_normal_string = get_prompt_string(
|
||||
PROMPT_INDICATOR_VI_NORMAL,
|
||||
config,
|
||||
engine_state,
|
||||
&mut stack,
|
||||
is_perf_true,
|
||||
);
|
||||
let prompt_vi_insert_string =
|
||||
get_prompt_string(PROMPT_INDICATOR_VI_INSERT, config, engine_state, &mut stack);
|
||||
|
||||
let prompt_vi_normal_string =
|
||||
get_prompt_string(PROMPT_INDICATOR_VI_NORMAL, config, engine_state, &mut stack);
|
||||
|
||||
// apply the other indicators
|
||||
nu_prompt.update_all_prompt_strings(
|
||||
@ -145,12 +137,11 @@ pub(crate) fn update_prompt<'prompt>(
|
||||
prompt_indicator_string,
|
||||
prompt_multiline_string,
|
||||
(prompt_vi_insert_string, prompt_vi_normal_string),
|
||||
config.render_right_prompt_on_last_line,
|
||||
);
|
||||
|
||||
let ret_val = nu_prompt as &dyn Prompt;
|
||||
if is_perf_true {
|
||||
info!("update_prompt {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
trace!("update_prompt {}:{}:{}", file!(), line!(), column!());
|
||||
|
||||
ret_val
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
use super::DescriptionMenu;
|
||||
use crate::{menus::NuMenuCompleter, NuHelpCompleter};
|
||||
use crossterm::event::{KeyCode, KeyModifiers};
|
||||
use nu_color_config::lookup_ansi_color_style;
|
||||
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
|
||||
use nu_engine::eval_block;
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
color_value_string, create_menus,
|
||||
create_menus,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
extract_value, Config, IntoPipelineData, ParsedKeybinding, ParsedMenu, PipelineData,
|
||||
ShellError, Span, Value,
|
||||
extract_value, Config, ParsedKeybinding, ParsedMenu, PipelineData, ShellError, Span, Value,
|
||||
};
|
||||
use reedline::{
|
||||
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
|
||||
@ -110,11 +109,11 @@ pub(crate) fn add_menus(
|
||||
};
|
||||
|
||||
let mut temp_stack = Stack::new();
|
||||
let input = Value::nothing(Span::test_data()).into_pipeline_data();
|
||||
let input = PipelineData::Empty;
|
||||
let res = eval_block(&engine_state, &mut temp_stack, &block, input, false, false)?;
|
||||
|
||||
if let PipelineData::Value(value, None) = res {
|
||||
for menu in create_menus(&value, config)? {
|
||||
for menu in create_menus(&value)? {
|
||||
line_editor =
|
||||
add_menu(line_editor, &menu, engine_state.clone(), stack, config)?;
|
||||
}
|
||||
@ -159,14 +158,11 @@ macro_rules! add_style {
|
||||
($name:expr, $cols: expr, $vals:expr, $span:expr, $config: expr, $menu:expr, $f:expr) => {
|
||||
$menu = match extract_value($name, $cols, $vals, $span) {
|
||||
Ok(text) => {
|
||||
let text = match text {
|
||||
Value::String { val, .. } => val.clone(),
|
||||
Value::Record { cols, vals, span } => {
|
||||
color_value_string(span, cols, vals, $config).into_string("", $config)
|
||||
}
|
||||
_ => "green".to_string(),
|
||||
let style = match text {
|
||||
Value::String { val, .. } => lookup_ansi_color_style(&val),
|
||||
Value::Record { .. } => color_record_to_nustyle(&text),
|
||||
_ => lookup_ansi_color_style("green"),
|
||||
};
|
||||
let style = lookup_ansi_color_style(&text);
|
||||
$f($menu, style)
|
||||
}
|
||||
Err(_) => $menu,
|
||||
@ -251,7 +247,7 @@ pub(crate) fn add_columnar_menu(
|
||||
Value::Nothing { .. } => {
|
||||
Ok(line_editor.with_menu(ReedlineMenu::EngineCompleter(Box::new(columnar_menu))))
|
||||
}
|
||||
Value::Block {
|
||||
Value::Closure {
|
||||
val,
|
||||
captures,
|
||||
span,
|
||||
@ -337,7 +333,7 @@ pub(crate) fn add_list_menu(
|
||||
Value::Nothing { .. } => {
|
||||
Ok(line_editor.with_menu(ReedlineMenu::HistoryMenu(Box::new(list_menu))))
|
||||
}
|
||||
Value::Block {
|
||||
Value::Closure {
|
||||
val,
|
||||
captures,
|
||||
span,
|
||||
@ -459,7 +455,7 @@ pub(crate) fn add_description_menu(
|
||||
completer,
|
||||
}))
|
||||
}
|
||||
Value::Block {
|
||||
Value::Closure {
|
||||
val,
|
||||
captures,
|
||||
span,
|
||||
@ -477,7 +473,7 @@ pub(crate) fn add_description_menu(
|
||||
}))
|
||||
}
|
||||
_ => Err(ShellError::UnsupportedConfigValue(
|
||||
"block or omitted value".to_string(),
|
||||
"closure or omitted value".to_string(),
|
||||
menu.source.into_abbreviated_string(config),
|
||||
menu.source.span()?,
|
||||
)),
|
||||
@ -491,7 +487,7 @@ fn add_menu_keybindings(keybindings: &mut Keybindings) {
|
||||
KeyCode::Tab,
|
||||
ReedlineEvent::UntilFound(vec![
|
||||
ReedlineEvent::Menu("completion_menu".to_string()),
|
||||
ReedlineEvent::MenuNext,
|
||||
ReedlineEvent::Edit(vec![EditCommand::Complete]),
|
||||
]),
|
||||
);
|
||||
|
||||
@ -655,17 +651,19 @@ fn add_parsed_keybinding(
|
||||
let pos1 = char_iter.next();
|
||||
let pos2 = char_iter.next();
|
||||
|
||||
let char = match (pos1, pos2) {
|
||||
(Some(char), None) => Ok(char),
|
||||
_ => Err(ShellError::UnsupportedConfigValue(
|
||||
let char = if let (Some(char), None) = (pos1, pos2) {
|
||||
char
|
||||
} else {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"char_<CHAR: unicode codepoint>".to_string(),
|
||||
c.to_string(),
|
||||
keybinding.keycode.span()?,
|
||||
)),
|
||||
}?;
|
||||
));
|
||||
};
|
||||
|
||||
KeyCode::Char(char)
|
||||
}
|
||||
"space" => KeyCode::Char(' '),
|
||||
"down" => KeyCode::Down,
|
||||
"up" => KeyCode::Up,
|
||||
"left" => KeyCode::Left,
|
||||
@ -682,10 +680,10 @@ fn add_parsed_keybinding(
|
||||
let fn_num: u8 = c[1..]
|
||||
.parse()
|
||||
.ok()
|
||||
.filter(|num| matches!(num, 1..=12))
|
||||
.filter(|num| matches!(num, 1..=20))
|
||||
.ok_or(ShellError::UnsupportedConfigValue(
|
||||
"(f1|f2|...|f12)".to_string(),
|
||||
format!("unknown function key: {}", c),
|
||||
"(f1|f2|...|f20)".to_string(),
|
||||
format!("unknown function key: {c}"),
|
||||
keybinding.keycode.span()?,
|
||||
))?;
|
||||
KeyCode::F(fn_num)
|
||||
@ -814,7 +812,6 @@ fn event_from_record(
|
||||
) -> Result<ReedlineEvent, ShellError> {
|
||||
let event = match name {
|
||||
"none" => ReedlineEvent::None,
|
||||
"actionhandler" => ReedlineEvent::ActionHandler,
|
||||
"clearscreen" => ReedlineEvent::ClearScreen,
|
||||
"clearscrollback" => ReedlineEvent::ClearScrollback,
|
||||
"historyhintcomplete" => ReedlineEvent::HistoryHintComplete,
|
||||
@ -822,6 +819,8 @@ fn event_from_record(
|
||||
"ctrld" => ReedlineEvent::CtrlD,
|
||||
"ctrlc" => ReedlineEvent::CtrlC,
|
||||
"enter" => ReedlineEvent::Enter,
|
||||
"submit" => ReedlineEvent::Submit,
|
||||
"submitornewline" => ReedlineEvent::SubmitOrNewline,
|
||||
"esc" | "escape" => ReedlineEvent::Esc,
|
||||
"up" => ReedlineEvent::Up,
|
||||
"down" => ReedlineEvent::Down,
|
||||
@ -962,6 +961,7 @@ fn edit_from_record(
|
||||
let char = extract_char(value, config)?;
|
||||
EditCommand::MoveLeftBefore(char)
|
||||
}
|
||||
"complete" => EditCommand::Complete,
|
||||
e => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"reedline EditCommand".to_string(),
|
||||
@ -990,10 +990,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_send_event() {
|
||||
let cols = vec!["send".to_string()];
|
||||
let vals = vec![Value::String {
|
||||
val: "Enter".to_string(),
|
||||
span: Span::test_data(),
|
||||
}];
|
||||
let vals = vec![Value::test_string("Enter")];
|
||||
|
||||
let span = Span::test_data();
|
||||
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
||||
@ -1013,10 +1010,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_edit_event() {
|
||||
let cols = vec!["edit".to_string()];
|
||||
let vals = vec![Value::String {
|
||||
val: "Clear".to_string(),
|
||||
span: Span::test_data(),
|
||||
}];
|
||||
let vals = vec![Value::test_string("Clear")];
|
||||
|
||||
let span = Span::test_data();
|
||||
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
||||
@ -1040,14 +1034,8 @@ mod test {
|
||||
fn test_send_menu() {
|
||||
let cols = vec!["send".to_string(), "name".to_string()];
|
||||
let vals = vec![
|
||||
Value::String {
|
||||
val: "Menu".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "history_menu".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::test_string("Menu"),
|
||||
Value::test_string("history_menu"),
|
||||
];
|
||||
|
||||
let span = Span::test_data();
|
||||
@ -1073,14 +1061,8 @@ mod test {
|
||||
// Menu event
|
||||
let cols = vec!["send".to_string(), "name".to_string()];
|
||||
let vals = vec![
|
||||
Value::String {
|
||||
val: "Menu".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "history_menu".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::test_string("Menu"),
|
||||
Value::test_string("history_menu"),
|
||||
];
|
||||
|
||||
let menu_event = Value::Record {
|
||||
@ -1091,10 +1073,7 @@ mod test {
|
||||
|
||||
// Enter event
|
||||
let cols = vec!["send".to_string()];
|
||||
let vals = vec![Value::String {
|
||||
val: "Enter".to_string(),
|
||||
span: Span::test_data(),
|
||||
}];
|
||||
let vals = vec![Value::test_string("Enter")];
|
||||
|
||||
let enter_event = Value::Record {
|
||||
cols,
|
||||
@ -1135,14 +1114,8 @@ mod test {
|
||||
// Menu event
|
||||
let cols = vec!["send".to_string(), "name".to_string()];
|
||||
let vals = vec![
|
||||
Value::String {
|
||||
val: "Menu".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "history_menu".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::test_string("Menu"),
|
||||
Value::test_string("history_menu"),
|
||||
];
|
||||
|
||||
let menu_event = Value::Record {
|
||||
@ -1153,10 +1126,7 @@ mod test {
|
||||
|
||||
// Enter event
|
||||
let cols = vec!["send".to_string()];
|
||||
let vals = vec![Value::String {
|
||||
val: "Enter".to_string(),
|
||||
span: Span::test_data(),
|
||||
}];
|
||||
let vals = vec![Value::test_string("Enter")];
|
||||
|
||||
let enter_event = Value::Record {
|
||||
cols,
|
||||
@ -1184,10 +1154,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_error() {
|
||||
let cols = vec!["not_exist".to_string()];
|
||||
let vals = vec![Value::String {
|
||||
val: "Enter".to_string(),
|
||||
span: Span::test_data(),
|
||||
}];
|
||||
let vals = vec![Value::test_string("Enter")];
|
||||
|
||||
let span = Span::test_data();
|
||||
let b = EventType::try_from_columns(&cols, &vals, &span);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,13 +1,15 @@
|
||||
use log::trace;
|
||||
use nu_ansi_term::Style;
|
||||
use nu_color_config::get_shape_color;
|
||||
use nu_color_config::{get_matching_brackets_style, get_shape_color};
|
||||
use nu_parser::{flatten_block, parse, FlatShape};
|
||||
use nu_protocol::ast::{Argument, Block, Expr, Expression, PipelineElement};
|
||||
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
||||
use nu_protocol::Config;
|
||||
use nu_protocol::{Config, Span};
|
||||
use reedline::{Highlighter, StyledText};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct NuHighlighter {
|
||||
pub engine_state: EngineState,
|
||||
pub engine_state: Arc<EngineState>,
|
||||
pub config: Config,
|
||||
}
|
||||
|
||||
@ -15,10 +17,12 @@ impl Highlighter for NuHighlighter {
|
||||
fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
|
||||
trace!("highlighting: {}", line);
|
||||
|
||||
let (shapes, global_span_offset) = {
|
||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||
let block = {
|
||||
let (block, _) = parse(&mut working_set, None, line.as_bytes(), false, &[]);
|
||||
|
||||
block
|
||||
};
|
||||
let (shapes, global_span_offset) = {
|
||||
let shapes = flatten_block(&working_set, &block);
|
||||
(shapes, self.engine_state.next_span_start())
|
||||
};
|
||||
@ -26,6 +30,15 @@ impl Highlighter for NuHighlighter {
|
||||
let mut output = StyledText::default();
|
||||
let mut last_seen_span = global_span_offset;
|
||||
|
||||
let global_cursor_offset = _cursor + global_span_offset;
|
||||
let matching_brackets_pos = find_matching_brackets(
|
||||
line,
|
||||
&working_set,
|
||||
&block,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
);
|
||||
|
||||
for shape in &shapes {
|
||||
if shape.0.end <= last_seen_span
|
||||
|| last_seen_span < global_span_offset
|
||||
@ -44,166 +57,75 @@ impl Highlighter for NuHighlighter {
|
||||
let next_token = line
|
||||
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
|
||||
.to_string();
|
||||
|
||||
macro_rules! add_colored_token_with_bracket_highlight {
|
||||
($shape:expr, $span:expr, $text:expr) => {{
|
||||
let spans = split_span_by_highlight_positions(
|
||||
line,
|
||||
&$span,
|
||||
&matching_brackets_pos,
|
||||
global_span_offset,
|
||||
);
|
||||
spans.iter().for_each(|(part, highlight)| {
|
||||
let start = part.start - $span.start;
|
||||
let end = part.end - $span.start;
|
||||
let text = (&next_token[start..end]).to_string();
|
||||
let mut style = get_shape_color($shape.to_string(), &self.config);
|
||||
if *highlight {
|
||||
style = get_matching_brackets_style(style, &self.config);
|
||||
}
|
||||
output.push((style, text));
|
||||
});
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! add_colored_token {
|
||||
($shape:expr, $text:expr) => {
|
||||
output.push((get_shape_color($shape.to_string(), &self.config), $text))
|
||||
};
|
||||
}
|
||||
|
||||
match shape.1 {
|
||||
FlatShape::Garbage => output.push((
|
||||
// nushell Garbage
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::Nothing => output.push((
|
||||
// nushell Nothing
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::Binary => {
|
||||
// nushell ?
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
}
|
||||
FlatShape::Bool => {
|
||||
// nushell ?
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
}
|
||||
FlatShape::Int => {
|
||||
// nushell Int
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
}
|
||||
FlatShape::Float => {
|
||||
// nushell Decimal
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
}
|
||||
FlatShape::Range => output.push((
|
||||
// nushell DotDot ?
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::InternalCall => output.push((
|
||||
// nushell InternalCommand
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::External => {
|
||||
// nushell ExternalCommand
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
}
|
||||
FlatShape::ExternalArg => {
|
||||
// nushell ExternalWord
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
}
|
||||
FlatShape::Literal => {
|
||||
// nushell ?
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
}
|
||||
FlatShape::Operator => output.push((
|
||||
// nushell Operator
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::Signature => output.push((
|
||||
// nushell ?
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::String => {
|
||||
// nushell String
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
}
|
||||
FlatShape::StringInterpolation => {
|
||||
// nushell ???
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
}
|
||||
FlatShape::DateTime => {
|
||||
// nushell ???
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
}
|
||||
FlatShape::Garbage => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Nothing => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Binary => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Bool => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Int => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Float => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Range => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::InternalCall => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::External => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::ExternalArg => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Literal => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Operator => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Signature => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::String => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::StringInterpolation => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::DateTime => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::List => {
|
||||
// nushell ???
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||
}
|
||||
FlatShape::Table => {
|
||||
// nushell ???
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||
}
|
||||
FlatShape::Record => {
|
||||
// nushell ???
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||
}
|
||||
|
||||
FlatShape::Block => {
|
||||
// nushell ???
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||
}
|
||||
FlatShape::Filepath => output.push((
|
||||
// nushell Path
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::Directory => output.push((
|
||||
// nushell Directory
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::GlobPattern => output.push((
|
||||
// nushell GlobPattern
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::Variable => output.push((
|
||||
// nushell Variable
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
)),
|
||||
FlatShape::Flag => {
|
||||
// nushell Flag
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
}
|
||||
FlatShape::Custom(..) => output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
)),
|
||||
|
||||
FlatShape::Filepath => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Directory => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::GlobPattern => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Variable => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Flag => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Pipe => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::And => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Or => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Redirection => add_colored_token!(shape.1, next_token),
|
||||
FlatShape::Custom(..) => add_colored_token!(shape.1, next_token),
|
||||
}
|
||||
last_seen_span = shape.0.end;
|
||||
}
|
||||
@ -216,3 +138,301 @@ impl Highlighter for NuHighlighter {
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
fn split_span_by_highlight_positions(
|
||||
line: &str,
|
||||
span: &Span,
|
||||
highlight_positions: &Vec<usize>,
|
||||
global_span_offset: usize,
|
||||
) -> Vec<(Span, bool)> {
|
||||
let mut start = span.start;
|
||||
let mut result: Vec<(Span, bool)> = Vec::new();
|
||||
for pos in highlight_positions {
|
||||
if start <= *pos && pos < &span.end {
|
||||
if start < *pos {
|
||||
result.push((Span::new(start, *pos), false));
|
||||
}
|
||||
let span_str = &line[pos - global_span_offset..span.end - global_span_offset];
|
||||
let end = span_str
|
||||
.chars()
|
||||
.next()
|
||||
.map(|c| pos + get_char_length(c))
|
||||
.unwrap_or(pos + 1);
|
||||
result.push((Span::new(*pos, end), true));
|
||||
start = end;
|
||||
}
|
||||
}
|
||||
if start < span.end {
|
||||
result.push((Span::new(start, span.end), false));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn find_matching_brackets(
|
||||
line: &str,
|
||||
working_set: &StateWorkingSet,
|
||||
block: &Block,
|
||||
global_span_offset: usize,
|
||||
global_cursor_offset: usize,
|
||||
) -> Vec<usize> {
|
||||
const BRACKETS: &str = "{}[]()";
|
||||
|
||||
// calculate first bracket position
|
||||
let global_end_offset = line.len() + global_span_offset;
|
||||
let global_bracket_pos =
|
||||
if global_cursor_offset == global_end_offset && global_end_offset > global_span_offset {
|
||||
// cursor is at the end of a non-empty string -- find block end at the previous position
|
||||
if let Some(last_char) = line.chars().last() {
|
||||
global_cursor_offset - get_char_length(last_char)
|
||||
} else {
|
||||
global_cursor_offset
|
||||
}
|
||||
} else {
|
||||
// cursor is in the middle of a string -- find block end at the current position
|
||||
global_cursor_offset
|
||||
};
|
||||
|
||||
// check that position contains bracket
|
||||
let match_idx = global_bracket_pos - global_span_offset;
|
||||
if match_idx >= line.len()
|
||||
|| !BRACKETS.contains(get_char_at_index(line, match_idx).unwrap_or_default())
|
||||
{
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
// find matching bracket by finding matching block end
|
||||
let matching_block_end = find_matching_block_end_in_block(
|
||||
line,
|
||||
working_set,
|
||||
block,
|
||||
global_span_offset,
|
||||
global_bracket_pos,
|
||||
);
|
||||
if let Some(pos) = matching_block_end {
|
||||
let matching_idx = pos - global_span_offset;
|
||||
if BRACKETS.contains(get_char_at_index(line, matching_idx).unwrap_or_default()) {
|
||||
return if global_bracket_pos < pos {
|
||||
vec![global_bracket_pos, pos]
|
||||
} else {
|
||||
vec![pos, global_bracket_pos]
|
||||
};
|
||||
}
|
||||
}
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn find_matching_block_end_in_block(
|
||||
line: &str,
|
||||
working_set: &StateWorkingSet,
|
||||
block: &Block,
|
||||
global_span_offset: usize,
|
||||
global_cursor_offset: usize,
|
||||
) -> Option<usize> {
|
||||
for p in &block.pipelines {
|
||||
for e in &p.elements {
|
||||
match e {
|
||||
PipelineElement::Expression(_, e)
|
||||
| PipelineElement::Redirection(_, _, e)
|
||||
| PipelineElement::And(_, e)
|
||||
| PipelineElement::Or(_, e)
|
||||
| PipelineElement::SeparateRedirection { out: (_, e), .. } => {
|
||||
if e.span.contains(global_cursor_offset) {
|
||||
if let Some(pos) = find_matching_block_end_in_expr(
|
||||
line,
|
||||
working_set,
|
||||
e,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
) {
|
||||
return Some(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn find_matching_block_end_in_expr(
|
||||
line: &str,
|
||||
working_set: &StateWorkingSet,
|
||||
expression: &Expression,
|
||||
global_span_offset: usize,
|
||||
global_cursor_offset: usize,
|
||||
) -> Option<usize> {
|
||||
macro_rules! find_in_expr_or_continue {
|
||||
($inner_expr:ident) => {
|
||||
if let Some(pos) = find_matching_block_end_in_expr(
|
||||
line,
|
||||
working_set,
|
||||
$inner_expr,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
) {
|
||||
return Some(pos);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if expression.span.contains(global_cursor_offset) && expression.span.start >= global_span_offset
|
||||
{
|
||||
let expr_first = expression.span.start;
|
||||
let span_str = &line
|
||||
[expression.span.start - global_span_offset..expression.span.end - global_span_offset];
|
||||
let expr_last = span_str
|
||||
.chars()
|
||||
.last()
|
||||
.map(|c| expression.span.end - get_char_length(c))
|
||||
.unwrap_or(expression.span.start);
|
||||
|
||||
return match &expression.expr {
|
||||
Expr::Bool(_) => None,
|
||||
Expr::Int(_) => None,
|
||||
Expr::Float(_) => None,
|
||||
Expr::Binary(_) => None,
|
||||
Expr::Range(..) => None,
|
||||
Expr::Var(_) => None,
|
||||
Expr::VarDecl(_) => None,
|
||||
Expr::ExternalCall(..) => None,
|
||||
Expr::Operator(_) => None,
|
||||
Expr::UnaryNot(_) => None,
|
||||
Expr::Keyword(..) => None,
|
||||
Expr::ValueWithUnit(..) => None,
|
||||
Expr::DateTime(_) => None,
|
||||
Expr::Filepath(_) => None,
|
||||
Expr::Directory(_) => None,
|
||||
Expr::GlobPattern(_) => None,
|
||||
Expr::String(_) => None,
|
||||
Expr::CellPath(_) => None,
|
||||
Expr::ImportPattern(_) => None,
|
||||
Expr::Overlay(_) => None,
|
||||
Expr::Signature(_) => None,
|
||||
Expr::Nothing => None,
|
||||
Expr::Garbage => None,
|
||||
|
||||
Expr::Table(hdr, rows) => {
|
||||
if expr_last == global_cursor_offset {
|
||||
// cursor is at table end
|
||||
Some(expr_first)
|
||||
} else if expr_first == global_cursor_offset {
|
||||
// cursor is at table start
|
||||
Some(expr_last)
|
||||
} else {
|
||||
// cursor is inside table
|
||||
for inner_expr in hdr {
|
||||
find_in_expr_or_continue!(inner_expr);
|
||||
}
|
||||
for row in rows {
|
||||
for inner_expr in row {
|
||||
find_in_expr_or_continue!(inner_expr);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
Expr::Record(exprs) => {
|
||||
if expr_last == global_cursor_offset {
|
||||
// cursor is at record end
|
||||
Some(expr_first)
|
||||
} else if expr_first == global_cursor_offset {
|
||||
// cursor is at record start
|
||||
Some(expr_last)
|
||||
} else {
|
||||
// cursor is inside record
|
||||
for (k, v) in exprs {
|
||||
find_in_expr_or_continue!(k);
|
||||
find_in_expr_or_continue!(v);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
Expr::Call(call) => {
|
||||
for arg in &call.arguments {
|
||||
let opt_expr = match arg {
|
||||
Argument::Named((_, _, opt_expr)) => opt_expr.as_ref(),
|
||||
Argument::Positional(inner_expr) => Some(inner_expr),
|
||||
Argument::Unknown(inner_expr) => Some(inner_expr),
|
||||
};
|
||||
|
||||
if let Some(inner_expr) = opt_expr {
|
||||
find_in_expr_or_continue!(inner_expr);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
Expr::FullCellPath(b) => find_matching_block_end_in_expr(
|
||||
line,
|
||||
working_set,
|
||||
&b.head,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
),
|
||||
|
||||
Expr::BinaryOp(lhs, op, rhs) => {
|
||||
find_in_expr_or_continue!(lhs);
|
||||
find_in_expr_or_continue!(op);
|
||||
find_in_expr_or_continue!(rhs);
|
||||
None
|
||||
}
|
||||
|
||||
Expr::Block(block_id)
|
||||
| Expr::Closure(block_id)
|
||||
| Expr::RowCondition(block_id)
|
||||
| Expr::Subexpression(block_id) => {
|
||||
if expr_last == global_cursor_offset {
|
||||
// cursor is at block end
|
||||
Some(expr_first)
|
||||
} else if expr_first == global_cursor_offset {
|
||||
// cursor is at block start
|
||||
Some(expr_last)
|
||||
} else {
|
||||
// cursor is inside block
|
||||
let nested_block = working_set.get_block(*block_id);
|
||||
find_matching_block_end_in_block(
|
||||
line,
|
||||
working_set,
|
||||
nested_block,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Expr::StringInterpolation(inner_expr) => {
|
||||
for inner_expr in inner_expr {
|
||||
find_in_expr_or_continue!(inner_expr);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
Expr::List(inner_expr) => {
|
||||
if expr_last == global_cursor_offset {
|
||||
// cursor is at list end
|
||||
Some(expr_first)
|
||||
} else if expr_first == global_cursor_offset {
|
||||
// cursor is at list start
|
||||
Some(expr_last)
|
||||
} else {
|
||||
// cursor is inside list
|
||||
for inner_expr in inner_expr {
|
||||
find_in_expr_or_continue!(inner_expr);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn get_char_at_index(s: &str, index: usize) -> Option<char> {
|
||||
s[index..].chars().next()
|
||||
}
|
||||
|
||||
fn get_char_length(c: char) -> usize {
|
||||
c.to_string().len()
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
use log::trace;
|
||||
use nu_engine::eval_block;
|
||||
use crate::repl::eval_hook;
|
||||
use nu_engine::{eval_block, eval_block_with_early_return};
|
||||
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
use nu_protocol::CliError;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
PipelineData, ShellError, Span, Value,
|
||||
print_if_stream, PipelineData, ShellError, Span, Value,
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use nu_utils::enable_vt_processing;
|
||||
use nu_utils::utils::perf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
// This will collect environment variables from std::env and adds them to a stack.
|
||||
@ -43,7 +44,7 @@ fn gather_env_vars(
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::GenericError(
|
||||
format!("Environment variable was not captured: {}", env_str),
|
||||
format!("Environment variable was not captured: {env_str}"),
|
||||
"".to_string(),
|
||||
None,
|
||||
Some(msg.into()),
|
||||
@ -79,8 +80,7 @@ fn gather_env_vars(
|
||||
"".to_string(),
|
||||
None,
|
||||
Some(format!(
|
||||
"Retrieving current directory failed: {:?} not a valid utf-8 path",
|
||||
init_cwd
|
||||
"Retrieving current directory failed: {init_cwd:?} not a valid utf-8 path"
|
||||
)),
|
||||
Vec::new(),
|
||||
),
|
||||
@ -203,8 +203,9 @@ pub fn eval_source(
|
||||
source: &[u8],
|
||||
fname: &str,
|
||||
input: PipelineData,
|
||||
allow_return: bool,
|
||||
) -> bool {
|
||||
trace!("eval_source");
|
||||
let start_time = std::time::Instant::now();
|
||||
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
@ -224,35 +225,54 @@ pub fn eval_source(
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
let cwd = match nu_engine::env::current_dir(engine_state, stack) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
get_init_cwd()
|
||||
}
|
||||
if let Err(err) = engine_state.merge_delta(delta) {
|
||||
set_last_exit_code(stack, 1);
|
||||
report_error_new(engine_state, &err);
|
||||
return false;
|
||||
}
|
||||
|
||||
let b = if allow_return {
|
||||
eval_block_with_early_return(engine_state, stack, &block, input, false, false)
|
||||
} else {
|
||||
eval_block(engine_state, stack, &block, input, false, false)
|
||||
};
|
||||
|
||||
let _ = engine_state.merge_delta(delta, Some(stack), &cwd);
|
||||
|
||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||
Ok(mut pipeline_data) => {
|
||||
if let PipelineData::ExternalStream { exit_code, .. } = &mut pipeline_data {
|
||||
if let Some(exit_code) = exit_code.take().and_then(|it| it.last()) {
|
||||
stack.add_env_var("LAST_EXIT_CODE".to_string(), exit_code);
|
||||
} else {
|
||||
set_last_exit_code(stack, 0);
|
||||
match b {
|
||||
Ok(pipeline_data) => {
|
||||
let config = engine_state.get_config();
|
||||
let result;
|
||||
if let PipelineData::ExternalStream {
|
||||
stdout: stream,
|
||||
stderr: stderr_stream,
|
||||
exit_code,
|
||||
..
|
||||
} = pipeline_data
|
||||
{
|
||||
result = print_if_stream(stream, stderr_stream, false, exit_code);
|
||||
} else if let Some(hook) = config.hooks.display_output.clone() {
|
||||
match eval_hook(engine_state, stack, Some(pipeline_data), vec![], &hook) {
|
||||
Err(err) => {
|
||||
result = Err(err);
|
||||
}
|
||||
Ok(val) => {
|
||||
result = val.print(engine_state, stack, false, false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
set_last_exit_code(stack, 0);
|
||||
result = pipeline_data.print(engine_state, stack, true, false);
|
||||
}
|
||||
|
||||
if let Err(err) = pipeline_data.print(engine_state, stack, false, false) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
match result {
|
||||
Err(err) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &err);
|
||||
report_error(&working_set, &err);
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
Ok(exit_code) => {
|
||||
set_last_exit_code(stack, exit_code);
|
||||
}
|
||||
}
|
||||
|
||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||
@ -271,6 +291,14 @@ pub fn eval_source(
|
||||
return false;
|
||||
}
|
||||
}
|
||||
perf(
|
||||
&format!("eval_source {}", &fname),
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
engine_state.get_config().use_ansi_coloring,
|
||||
);
|
||||
|
||||
true
|
||||
}
|
||||
@ -278,10 +306,7 @@ pub fn eval_source(
|
||||
fn set_last_exit_code(stack: &mut Stack, exit_code: i64) {
|
||||
stack.add_env_var(
|
||||
"LAST_EXIT_CODE".to_string(),
|
||||
Value::Int {
|
||||
val: exit_code,
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
Value::int(exit_code, Span::unknown()),
|
||||
);
|
||||
}
|
||||
|
||||
@ -297,17 +322,29 @@ pub fn report_error(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_error_new(
|
||||
engine_state: &EngineState,
|
||||
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
|
||||
) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, error);
|
||||
}
|
||||
|
||||
pub fn get_init_cwd() -> PathBuf {
|
||||
match std::env::current_dir() {
|
||||
Ok(cwd) => cwd,
|
||||
Err(_) => match std::env::var("PWD") {
|
||||
Ok(cwd) => PathBuf::from(cwd),
|
||||
Err(_) => match nu_path::home_dir() {
|
||||
Some(cwd) => cwd,
|
||||
None => PathBuf::new(),
|
||||
},
|
||||
},
|
||||
}
|
||||
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 {
|
||||
nu_engine::env::current_dir(engine_state, stack).unwrap_or_else(|e| {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
get_init_cwd()
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,9 +1,10 @@
|
||||
use nu_parser::{parse, ParseError};
|
||||
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
||||
use reedline::{ValidationResult, Validator};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct NuValidator {
|
||||
pub engine_state: EngineState,
|
||||
pub engine_state: Arc<EngineState>,
|
||||
}
|
||||
|
||||
impl Validator for NuValidator {
|
||||
|
@ -1,65 +0,0 @@
|
||||
pub mod support;
|
||||
|
||||
use nu_cli::NuCompleter;
|
||||
use reedline::Completer;
|
||||
use support::{match_suggestions, new_engine};
|
||||
|
||||
#[test]
|
||||
fn alias_of_command_and_flags() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ll = ls -l"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let suggestions = completer.complete("ll t", 4);
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_of_basic_command() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ll = ls "#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let suggestions = completer.complete("ll t", 4);
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_of_another_alias() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ll = ls -la"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir.clone()).is_ok());
|
||||
// Create the second alias
|
||||
let alias = r#"alias lf = ll -f"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let suggestions = completer.complete("lf t", 4);
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
870
crates/nu-cli/tests/completions.rs
Normal file
870
crates/nu-cli/tests/completions.rs
Normal file
@ -0,0 +1,870 @@
|
||||
pub mod support;
|
||||
|
||||
use nu_cli::NuCompleter;
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
use reedline::{Completer, Suggestion};
|
||||
use rstest::{fixture, rstest};
|
||||
use support::{completions_helpers::new_quote_engine, file, folder, match_suggestions, new_engine};
|
||||
|
||||
#[fixture]
|
||||
fn completer() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Add record value as example
|
||||
let record = "def tst [--mod -s] {}";
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
// Instantiate a new completer
|
||||
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
fn completer_strings() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Add record value as example
|
||||
let record = r#"def animals [] { ["cat", "dog", "eel" ] }
|
||||
def my-command [animal: string@animals] { print $animal }"#;
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
// Instantiate a new completer
|
||||
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
fn extern_completer() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Add record value as example
|
||||
let record = r#"
|
||||
def animals [] { [ "cat", "dog", "eel" ] }
|
||||
extern spam [
|
||||
animal: string@animals
|
||||
--foo (-f): string@animals
|
||||
-b: string@animals
|
||||
]
|
||||
"#;
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
// Instantiate a new completer
|
||||
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variables_dollar_sign_with_varialblecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "$ ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
assert_eq!(7, suggestions.len());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_double_dash_argument_with_flagcompletion(mut completer: NuCompleter) {
|
||||
let suggestions = completer.complete("tst --", 6);
|
||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into()];
|
||||
// dbg!(&expected, &suggestions);
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_single_dash_argument_with_flagcompletion(mut completer: NuCompleter) {
|
||||
let suggestions = completer.complete("tst -", 5);
|
||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_command_with_commandcompletion(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-c ", 4);
|
||||
let expected: Vec<String> = vec!["my-command".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_subcommands_with_customcompletion(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-command ", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_customcompletion_subcommands_with_customcompletion_2(
|
||||
mut completer_strings: NuCompleter,
|
||||
) {
|
||||
let suggestions = completer_strings.complete("my-command ", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dotnu_completions() {
|
||||
// Create a new engine
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
// Instantiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Test source completion
|
||||
let completion_str = "source-env ".to_string();
|
||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||
|
||||
assert_eq!(1, suggestions.len());
|
||||
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
|
||||
|
||||
// Test use completion
|
||||
let completion_str = "use ".to_string();
|
||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||
|
||||
assert_eq!(1, suggestions.len());
|
||||
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn external_completer_trailing_space() {
|
||||
// https://github.com/nushell/nushell/issues/6378
|
||||
let block = "let external_completer = {|spans| $spans}";
|
||||
let input = "gh alias ".to_string();
|
||||
|
||||
let suggestions = run_external_completion(block, &input);
|
||||
assert_eq!(3, suggestions.len());
|
||||
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
||||
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
||||
assert_eq!("", suggestions.get(2).unwrap().value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_completer_no_trailing_space() {
|
||||
let block = "let external_completer = {|spans| $spans}";
|
||||
let input = "gh alias".to_string();
|
||||
|
||||
let suggestions = run_external_completion(block, &input);
|
||||
assert_eq!(2, suggestions.len());
|
||||
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
||||
assert_eq!("alias", suggestions.get(1).unwrap().value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_completer_pass_flags() {
|
||||
let block = "let external_completer = {|spans| $spans}";
|
||||
let input = "gh api --".to_string();
|
||||
|
||||
let suggestions = run_external_completion(block, &input);
|
||||
assert_eq!(3, suggestions.len());
|
||||
assert_eq!("gh", suggestions.get(0).unwrap().value);
|
||||
assert_eq!("api", suggestions.get(1).unwrap().value);
|
||||
assert_eq!("--", suggestions.get(2).unwrap().value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_completions() {
|
||||
// Create a new engine
|
||||
let (dir, dir_str, engine, stack) = new_engine();
|
||||
|
||||
// Instantiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Test completions for the current folder
|
||||
let target_dir = format!("cp {dir_str}");
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
let expected_paths: Vec<String> = vec![
|
||||
folder(dir.join("another")),
|
||||
file(dir.join("custom_completion.nu")),
|
||||
file(dir.join("nushell")),
|
||||
folder(dir.join("test_a")),
|
||||
folder(dir.join("test_b")),
|
||||
file(dir.join(".hidden_file")),
|
||||
folder(dir.join(".hidden_folder")),
|
||||
];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
|
||||
// Test completions for a file
|
||||
let target_dir = format!("cp {}", folder(dir.join("another")));
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
let expected_paths: Vec<String> = vec![file(dir.join("another").join("newfile"))];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_ls_with_filecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "ls ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
#[test]
|
||||
fn command_open_with_filecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "open ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_rm_with_globcompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "rm ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_cp_with_globcompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "cp ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_save_with_filecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "save ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_touch_with_filecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "touch ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_watch_with_filecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "watch ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_completion_quoted() {
|
||||
let (_, _, engine, stack) = new_quote_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "open ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"`te st.txt`".to_string(),
|
||||
"`te#st.txt`".to_string(),
|
||||
"`te'st.txt`".to_string(),
|
||||
"`te(st).txt`".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_completions() {
|
||||
// Create a new engine
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
// Instantiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
// Test completions for the 'ls' flags
|
||||
let suggestions = completer.complete("ls -", 4);
|
||||
|
||||
assert_eq!(16, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec![
|
||||
"--all".into(),
|
||||
"--directory".into(),
|
||||
"--du".into(),
|
||||
"--full-paths".into(),
|
||||
"--help".into(),
|
||||
"--long".into(),
|
||||
"--mime-type".into(),
|
||||
"--short-names".into(),
|
||||
"-D".into(),
|
||||
"-a".into(),
|
||||
"-d".into(),
|
||||
"-f".into(),
|
||||
"-h".into(),
|
||||
"-l".into(),
|
||||
"-m".into(),
|
||||
"-s".into(),
|
||||
];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folder_with_directorycompletions() {
|
||||
// Create a new engine
|
||||
let (dir, dir_str, engine, stack) = new_engine();
|
||||
|
||||
// Instantiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Test completions for the current folder
|
||||
let target_dir = format!("cd {dir_str}");
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
let expected_paths: Vec<String> = vec![
|
||||
folder(dir.join("another")),
|
||||
folder(dir.join("test_a")),
|
||||
folder(dir.join("test_b")),
|
||||
folder(dir.join(".hidden_folder")),
|
||||
];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variables_completions() {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Add record value as example
|
||||
let record = "let actor = { name: 'Tom Hardy', age: 44 }";
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
// Instantiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Test completions for $nu
|
||||
let suggestions = completer.complete("$nu.", 4);
|
||||
|
||||
assert_eq!(12, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec![
|
||||
"config-path".into(),
|
||||
"env-path".into(),
|
||||
"history-path".into(),
|
||||
"home-path".into(),
|
||||
"is-interactive".into(),
|
||||
"is-login".into(),
|
||||
"loginshell-path".into(),
|
||||
"os-info".into(),
|
||||
"pid".into(),
|
||||
"scope".into(),
|
||||
"startup-time".into(),
|
||||
"temp-path".into(),
|
||||
];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for $nu.h (filter)
|
||||
let suggestions = completer.complete("$nu.h", 5);
|
||||
|
||||
assert_eq!(2, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec!["history-path".into(), "home-path".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for custom var
|
||||
let suggestions = completer.complete("$actor.", 7);
|
||||
|
||||
assert_eq!(2, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec!["age".into(), "name".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for custom var (filtering)
|
||||
let suggestions = completer.complete("$actor.n", 8);
|
||||
|
||||
assert_eq!(1, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec!["name".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for $env
|
||||
let suggestions = completer.complete("$env.", 5);
|
||||
|
||||
assert_eq!(3, suggestions.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected: Vec<String> = vec!["PWD".into(), "Path".into(), "TEST".into()];
|
||||
#[cfg(not(windows))]
|
||||
let expected: Vec<String> = vec!["PATH".into(), "PWD".into(), "TEST".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for $env
|
||||
let suggestions = completer.complete("$env.T", 6);
|
||||
|
||||
assert_eq!(1, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec!["TEST".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_of_command_and_flags() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ll = ls -l"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let suggestions = completer.complete("ll t", 4);
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_of_basic_command() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ll = ls "#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let suggestions = completer.complete("ll t", 4);
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_of_another_alias() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ll = ls -la"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir.clone()).is_ok());
|
||||
// Create the second alias
|
||||
let alias = r#"alias lf = ll -f"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let suggestions = completer.complete("lf t", 4);
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
fn run_external_completion(block: &str, input: &str) -> Vec<Suggestion> {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine_state, mut stack) = new_engine();
|
||||
let (_, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
let (block, err) = parse(&mut working_set, None, block.as_bytes(), false, &[]);
|
||||
assert!(err.is_none());
|
||||
|
||||
(block, working_set.render())
|
||||
};
|
||||
|
||||
assert!(engine_state.merge_delta(delta).is_ok());
|
||||
|
||||
// Merge environment into the permanent state
|
||||
assert!(engine_state.merge_env(&mut stack, &dir).is_ok());
|
||||
|
||||
let latest_block_id = engine_state.num_blocks() - 1;
|
||||
|
||||
// Change config adding the external completer
|
||||
let mut config = engine_state.get_config().clone();
|
||||
config.external_completer = Some(latest_block_id);
|
||||
engine_state.set_config(&config);
|
||||
|
||||
// Instantiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine_state), stack);
|
||||
|
||||
completer.complete(input, input.len())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_command_completion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "thiscommanddoesnotexist ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn flagcompletion_triggers_after_cursor(mut completer: NuCompleter) {
|
||||
let suggestions = completer.complete("tst -h", 5);
|
||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn customcompletion_triggers_after_cursor(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-command c", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn customcompletion_triggers_after_cursor_piped(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-command c | ls", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn flagcompletion_triggers_after_cursor_piped(mut completer: NuCompleter) {
|
||||
let suggestions = completer.complete("tst -h | ls", 5);
|
||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filecompletions_triggers_after_cursor() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let suggestions = completer.complete("cp test_c", 3);
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_positional(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam ", 5);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_long_flag_1(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam --foo=", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_long_flag_2(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam --foo ", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_long_flag_short(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam -f ", 8);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_short_flag(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam -b ", 8);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_complete_flags(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam -", 6);
|
||||
let expected: Vec<String> = vec!["--foo".into(), "-b".into(), "-f".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[ignore = "was reverted, still needs fixing"]
|
||||
#[rstest]
|
||||
fn alias_offset_bug_7648() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ea = ^$env.EDITOR /tmp/test.s"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Issue #7648
|
||||
// Nushell crashes when an alias name is shorter than the alias command
|
||||
// and the alias command is a external command
|
||||
// This happens because of offset is not correct.
|
||||
// This crashes before PR #7779
|
||||
let _suggestions = completer.complete("e", 1);
|
||||
}
|
||||
|
||||
#[ignore = "was reverted, still needs fixing"]
|
||||
#[rstest]
|
||||
fn alias_offset_bug_7754() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ll = ls -l"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Issue #7754
|
||||
// Nushell crashes when an alias name is shorter than the alias command
|
||||
// and the alias command contains pipes.
|
||||
// This crashes before PR #7756
|
||||
let _suggestions = completer.complete("ll -a | c", 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_path_env_var_8003() {
|
||||
// Create a new engine
|
||||
let (_, _, engine, _) = new_engine();
|
||||
// Get the path env var in a platform agnostic way
|
||||
let the_path = engine.get_path_env_var();
|
||||
// Make sure it's not empty
|
||||
assert!(the_path.is_some());
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
pub mod support;
|
||||
|
||||
use nu_cli::NuCompleter;
|
||||
use reedline::Completer;
|
||||
use support::{match_suggestions, new_engine};
|
||||
|
||||
#[test]
|
||||
fn variables_completions() {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Add record value as example
|
||||
let record = r#"def animals [] { ["cat", "dog", "eel" ] }
|
||||
def my-command [animal: string@animals] { print $animal }"#;
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
// Instantiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Test completions for $nu
|
||||
let suggestions = completer.complete("my-command ", 11);
|
||||
|
||||
assert_eq!(3, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
pub mod support;
|
||||
|
||||
use nu_cli::NuCompleter;
|
||||
use reedline::Completer;
|
||||
use support::new_engine;
|
||||
|
||||
#[test]
|
||||
fn dotnu_completions() {
|
||||
// Create a new engine
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
// Instatiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Test source completion
|
||||
let completion_str = "source ".to_string();
|
||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||
|
||||
assert_eq!(1, suggestions.len());
|
||||
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
|
||||
|
||||
// Test use completion
|
||||
let completion_str = "use ".to_string();
|
||||
let suggestions = completer.complete(&completion_str, completion_str.len());
|
||||
|
||||
assert_eq!(1, suggestions.len());
|
||||
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
|
||||
}
|
@ -1,272 +0,0 @@
|
||||
pub mod support;
|
||||
|
||||
use nu_cli::NuCompleter;
|
||||
use reedline::Completer;
|
||||
use support::{file, folder, match_suggestions, new_engine};
|
||||
|
||||
#[test]
|
||||
fn file_completions() {
|
||||
// Create a new engine
|
||||
let (dir, dir_str, engine, stack) = new_engine();
|
||||
|
||||
// Instatiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Test completions for the current folder
|
||||
let target_dir = format!("cp {}", dir_str);
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
let expected_paths: Vec<String> = vec![
|
||||
file(dir.join("nushell")),
|
||||
folder(dir.join("test_a")),
|
||||
folder(dir.join("test_b")),
|
||||
folder(dir.join("another")),
|
||||
file(dir.join("custom_completion.nu")),
|
||||
file(dir.join(".hidden_file")),
|
||||
folder(dir.join(".hidden_folder")),
|
||||
];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
|
||||
// Test completions for the completions/another folder
|
||||
let target_dir = format!("cd {}", folder(dir.join("another")));
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
let expected_paths: Vec<String> = vec![file(dir.join("another").join("newfile"))];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_ls_completion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "ls ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
#[test]
|
||||
fn command_open_completion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "open ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_rm_completion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "rm ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_cp_completion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "cp ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_save_completion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "save ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_touch_completion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "touch ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_watch_completion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "watch ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
pub mod support;
|
||||
|
||||
use nu_cli::NuCompleter;
|
||||
use reedline::Completer;
|
||||
use support::{match_suggestions, new_engine};
|
||||
|
||||
#[test]
|
||||
fn flag_completions() {
|
||||
// Create a new engine
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
// Instatiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
// Test completions for the 'ls' flags
|
||||
let suggestions = completer.complete("ls -", 4);
|
||||
|
||||
assert_eq!(12, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec![
|
||||
"--all".into(),
|
||||
"--du".into(),
|
||||
"--full-paths".into(),
|
||||
"--help".into(),
|
||||
"--long".into(),
|
||||
"--short-names".into(),
|
||||
"-a".into(),
|
||||
"-d".into(),
|
||||
"-f".into(),
|
||||
"-h".into(),
|
||||
"-l".into(),
|
||||
"-s".into(),
|
||||
];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
pub mod support;
|
||||
|
||||
use nu_cli::NuCompleter;
|
||||
use reedline::Completer;
|
||||
use support::{folder, match_suggestions, new_engine};
|
||||
|
||||
#[test]
|
||||
fn folder_completions() {
|
||||
// Create a new engine
|
||||
let (dir, dir_str, engine, stack) = new_engine();
|
||||
|
||||
// Instatiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Test completions for the current folder
|
||||
let target_dir = format!("cd {}", dir_str);
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
let expected_paths: Vec<String> = vec![
|
||||
folder(dir.join("test_a")),
|
||||
folder(dir.join("test_b")),
|
||||
folder(dir.join("another")),
|
||||
folder(dir.join(".hidden_folder")),
|
||||
];
|
||||
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
}
|
@ -4,7 +4,7 @@ use nu_command::create_default_context;
|
||||
use nu_engine::eval_block;
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateDelta, StateWorkingSet},
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
PipelineData, ShellError, Span, Value,
|
||||
};
|
||||
use nu_test_support::fs;
|
||||
@ -23,38 +23,84 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||
dir_str.push(SEP);
|
||||
|
||||
// Create a new engine with default context
|
||||
let mut engine_state = create_default_context(&dir);
|
||||
let mut engine_state = create_default_context();
|
||||
|
||||
// New stack
|
||||
let mut stack = Stack::new();
|
||||
|
||||
// New delta state
|
||||
let delta = StateDelta::new(&engine_state);
|
||||
|
||||
// Add pwd as env var
|
||||
stack.add_env_var(
|
||||
"PWD".to_string(),
|
||||
Value::String {
|
||||
val: dir_str.clone(),
|
||||
span: nu_protocol::Span {
|
||||
start: 0,
|
||||
end: dir_str.len(),
|
||||
},
|
||||
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||
},
|
||||
);
|
||||
stack.add_env_var(
|
||||
"TEST".to_string(),
|
||||
Value::String {
|
||||
val: "NUSHELL".to_string(),
|
||||
span: nu_protocol::Span {
|
||||
start: 0,
|
||||
end: dir_str.len(),
|
||||
},
|
||||
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||
},
|
||||
);
|
||||
#[cfg(windows)]
|
||||
stack.add_env_var(
|
||||
"Path".to_string(),
|
||||
Value::String {
|
||||
val: "c:\\some\\path;c:\\some\\other\\path".to_string(),
|
||||
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||
},
|
||||
);
|
||||
#[cfg(not(windows))]
|
||||
stack.add_env_var(
|
||||
"PATH".to_string(),
|
||||
Value::String {
|
||||
val: "/some/path:/some/other/path".to_string(),
|
||||
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||
},
|
||||
);
|
||||
|
||||
// Merge delta
|
||||
let merge_result = engine_state.merge_delta(delta, Some(&mut stack), &dir);
|
||||
// Merge environment into the permanent state
|
||||
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
||||
assert!(merge_result.is_ok());
|
||||
|
||||
(dir, dir_str, engine_state, stack)
|
||||
}
|
||||
|
||||
pub fn new_quote_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||
// Target folder inside assets
|
||||
let dir = fs::fixtures().join("quoted_completions");
|
||||
let mut dir_str = dir
|
||||
.clone()
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap_or_default();
|
||||
dir_str.push(SEP);
|
||||
|
||||
// Create a new engine with default context
|
||||
let mut engine_state = create_default_context();
|
||||
|
||||
// New stack
|
||||
let mut stack = Stack::new();
|
||||
|
||||
// Add pwd as env var
|
||||
stack.add_env_var(
|
||||
"PWD".to_string(),
|
||||
Value::String {
|
||||
val: dir_str.clone(),
|
||||
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||
},
|
||||
);
|
||||
stack.add_env_var(
|
||||
"TEST".to_string(),
|
||||
Value::String {
|
||||
val: "NUSHELL".to_string(),
|
||||
span: nu_protocol::Span::new(0, dir_str.len()),
|
||||
},
|
||||
);
|
||||
|
||||
// Merge environment into the permanent state
|
||||
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
||||
assert!(merge_result.is_ok());
|
||||
|
||||
(dir, dir_str, engine_state, stack)
|
||||
@ -62,6 +108,15 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||
|
||||
// match a list of suggestions with the expected values
|
||||
pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
|
||||
let expected_len = expected.len();
|
||||
let suggestions_len = suggestions.len();
|
||||
if expected_len != suggestions_len {
|
||||
panic!(
|
||||
"\nexpected {expected_len} suggestions but got {suggestions_len}: \n\
|
||||
Suggestions: {suggestions:#?} \n\
|
||||
Expected: {expected:#?}\n"
|
||||
)
|
||||
}
|
||||
expected.iter().zip(suggestions).for_each(|it| {
|
||||
assert_eq!(it.0, &it.1.value);
|
||||
});
|
||||
@ -97,13 +152,16 @@ pub fn merge_input(
|
||||
|
||||
(block, working_set.render())
|
||||
};
|
||||
|
||||
engine_state.merge_delta(delta)?;
|
||||
|
||||
assert!(eval_block(
|
||||
engine_state,
|
||||
stack,
|
||||
&block,
|
||||
PipelineData::Value(
|
||||
Value::Nothing {
|
||||
span: Span { start: 0, end: 0 },
|
||||
span: Span::unknown(),
|
||||
},
|
||||
None
|
||||
),
|
||||
@ -112,6 +170,6 @@ pub fn merge_input(
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
// Merge delta
|
||||
engine_state.merge_delta(delta, Some(stack), &dir)
|
||||
// Merge environment into the permanent state
|
||||
engine_state.merge_env(stack, &dir)
|
||||
}
|
||||
|
@ -1,88 +0,0 @@
|
||||
pub mod support;
|
||||
|
||||
use nu_cli::NuCompleter;
|
||||
use reedline::Completer;
|
||||
use support::{match_suggestions, new_engine};
|
||||
|
||||
#[test]
|
||||
fn variables_completions() {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Add record value as example
|
||||
let record = "let actor = { name: 'Tom Hardy', age: 44 }";
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
// Instatiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Test completions for $nu
|
||||
let suggestions = completer.complete("$nu.", 4);
|
||||
|
||||
assert_eq!(9, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec![
|
||||
"config-path".into(),
|
||||
"env-path".into(),
|
||||
"history-path".into(),
|
||||
"home-path".into(),
|
||||
"loginshell-path".into(),
|
||||
"os-info".into(),
|
||||
"pid".into(),
|
||||
"scope".into(),
|
||||
"temp-path".into(),
|
||||
];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for $nu.h (filter)
|
||||
let suggestions = completer.complete("$nu.h", 5);
|
||||
|
||||
assert_eq!(2, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec!["history-path".into(), "home-path".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for custom var
|
||||
let suggestions = completer.complete("$actor.", 7);
|
||||
|
||||
assert_eq!(2, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec!["age".into(), "name".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for custom var (filtering)
|
||||
let suggestions = completer.complete("$actor.n", 8);
|
||||
|
||||
assert_eq!(1, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec!["name".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for $env
|
||||
let suggestions = completer.complete("$env.", 5);
|
||||
|
||||
assert_eq!(2, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec!["PWD".into(), "TEST".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
|
||||
// Test completions for $env
|
||||
let suggestions = completer.complete("$env.T", 6);
|
||||
|
||||
assert_eq!(1, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec!["TEST".into()];
|
||||
|
||||
// Match results
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
32
crates/nu-cmd-lang/Cargo.toml
Normal file
32
crates/nu-cmd-lang/Cargo.toml
Normal file
@ -0,0 +1,32 @@
|
||||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
build = "build.rs"
|
||||
description = "Nushell's core language commands"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-lang"
|
||||
version = "0.77.1"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.77.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.77.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.77.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.77.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.77.1" }
|
||||
|
||||
nu-ansi-term = "0.47.0"
|
||||
|
||||
fancy-regex = "0.11.0"
|
||||
itertools = "0.10.0"
|
||||
log = "0.4.14"
|
||||
shadow-rs = { version = "0.21.0", default-features = false }
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = { version = "0.21.0", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="../nu-test-support", version = "0.77.1" }
|
21
crates/nu-cmd-lang/LICENSE
Normal file
21
crates/nu-cmd-lang/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 - 2022 The Nushell Project Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
20
crates/nu-cmd-lang/build.rs
Normal file
20
crates/nu-cmd-lang/build.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use std::process::Command;
|
||||
|
||||
fn main() -> shadow_rs::SdResult<()> {
|
||||
// Look up the current Git commit ourselves instead of relying on shadow_rs,
|
||||
// because shadow_rs does it in a really slow-to-compile way (it builds libgit2)
|
||||
let hash = get_git_hash().unwrap_or_default();
|
||||
println!("cargo:rustc-env=NU_COMMIT_HASH={hash}");
|
||||
|
||||
shadow_rs::new()
|
||||
}
|
||||
|
||||
fn get_git_hash() -> Option<String> {
|
||||
Command::new("git")
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.output()
|
||||
.ok()
|
||||
.filter(|output| output.status.success())
|
||||
.and_then(|output| String::from_utf8(output.stdout).ok())
|
||||
.map(|hash| hash.trim().to_string())
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Alias;
|
||||
@ -11,11 +13,12 @@ impl Command for Alias {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Alias a command (with optional flags) to a new name"
|
||||
"Alias a command (with optional flags) to a new name."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("alias")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("name", SyntaxShape::String, "name of the alias")
|
||||
.required(
|
||||
"initial_value",
|
||||
@ -27,7 +30,7 @@ impl Command for Alias {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -42,17 +45,17 @@ impl Command for Alias {
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
Ok(PipelineData::new(call.head))
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Alias ll to ls -l",
|
||||
example: "alias ll = ls -l",
|
||||
result: None,
|
||||
result: Some(Value::nothing(Span::test_data())),
|
||||
}]
|
||||
}
|
||||
}
|
49
crates/nu-cmd-lang/src/core_commands/break_.rs
Normal file
49
crates/nu-cmd-lang/src/core_commands/break_.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Break;
|
||||
|
||||
impl Command for Break {
|
||||
fn name(&self) -> &str {
|
||||
"break"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Break a loop."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("break")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Err(ShellError::Break(call.head))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Break out of a loop",
|
||||
example: r#"loop { break }"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
85
crates/nu-cmd-lang/src/core_commands/commandline.rs
Normal file
85
crates/nu-cmd-lang/src/core_commands/commandline.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::ReplOperation;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::Category;
|
||||
use nu_protocol::IntoPipelineData;
|
||||
use nu_protocol::{PipelineData, ShellError, Signature, SyntaxShape, Type, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Commandline;
|
||||
|
||||
impl Command for Commandline {
|
||||
fn name(&self) -> &str {
|
||||
"commandline"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("commandline")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.switch(
|
||||
"append",
|
||||
"appends the string to the end of the buffer",
|
||||
Some('a'),
|
||||
)
|
||||
.switch(
|
||||
"insert",
|
||||
"inserts the string into the buffer at the cursor position",
|
||||
Some('i'),
|
||||
)
|
||||
.switch(
|
||||
"replace",
|
||||
"replaces the current contents of the buffer (default)",
|
||||
Some('r'),
|
||||
)
|
||||
.optional(
|
||||
"cmd",
|
||||
SyntaxShape::String,
|
||||
"the string to perform the operation with",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"View or modify the current command line input buffer."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["repl", "interactive"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? {
|
||||
let mut ops = engine_state
|
||||
.repl_operation_queue
|
||||
.lock()
|
||||
.expect("repl op queue mutex");
|
||||
ops.push_back(if call.has_flag("append") {
|
||||
ReplOperation::Append(cmd.as_string()?)
|
||||
} else if call.has_flag("insert") {
|
||||
ReplOperation::Insert(cmd.as_string()?)
|
||||
} else {
|
||||
ReplOperation::Replace(cmd.as_string()?)
|
||||
});
|
||||
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
|
||||
} else if let Some(ref cmd) = *engine_state
|
||||
.repl_buffer_state
|
||||
.lock()
|
||||
.expect("repl buffer state mutex")
|
||||
{
|
||||
Ok(Value::String {
|
||||
val: cmd.clone(),
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
} else {
|
||||
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
}
|
104
crates/nu-cmd-lang/src/core_commands/const_.rs
Normal file
104
crates/nu-cmd-lang/src/core_commands/const_.rs
Normal file
@ -0,0 +1,104 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Const;
|
||||
|
||||
impl Command for Const {
|
||||
fn name(&self) -> &str {
|
||||
"const"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Create a parse-time constant."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("const")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.allow_variants_without_examples(true)
|
||||
.required("const_name", SyntaxShape::VarWithOptType, "constant name")
|
||||
.required(
|
||||
"initial_value",
|
||||
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
|
||||
"equals sign followed by constant value",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["set", "let"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let var_id = call
|
||||
.positional_nth(0)
|
||||
.expect("checked through parser")
|
||||
.as_var()
|
||||
.expect("internal error: missing variable");
|
||||
|
||||
if let Some(constval) = engine_state.find_constant(var_id, &[]) {
|
||||
// Instead of creating a second copy of the value in the stack, we could change
|
||||
// stack.get_var() to check engine_state.find_constant().
|
||||
stack.add_var(var_id, constval.clone());
|
||||
|
||||
Ok(PipelineData::empty())
|
||||
} else {
|
||||
Err(ShellError::NushellFailedSpanned {
|
||||
msg: "Missing Constant".to_string(),
|
||||
label: "constant not added by the parser".to_string(),
|
||||
span: call.head,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Create a new parse-time constant.",
|
||||
example: "const x = 10",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Create a composite constant value",
|
||||
example: "const x = { a: 10, b: 20 }",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(Const {})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_command_type() {
|
||||
assert!(matches!(Const.command_type(), CommandType::Keyword));
|
||||
}
|
||||
}
|
49
crates/nu-cmd-lang/src/core_commands/continue_.rs
Normal file
49
crates/nu-cmd-lang/src/core_commands/continue_.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Continue;
|
||||
|
||||
impl Command for Continue {
|
||||
fn name(&self) -> &str {
|
||||
"continue"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Continue a loop from the next iteration."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("continue")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Err(ShellError::Continue(call.head))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Continue a loop from the next iteration",
|
||||
example: r#"for i in 1..10 { if $i == 5 { continue }; print $i }"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Value};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Def;
|
||||
@ -11,24 +13,21 @@ impl Command for Def {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Define a custom command"
|
||||
"Define a custom command."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("def")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("def_name", SyntaxShape::String, "definition name")
|
||||
.required("params", SyntaxShape::Signature, "parameters")
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block(Some(vec![])),
|
||||
"body of the definition",
|
||||
)
|
||||
.required("body", SyntaxShape::Closure(None), "body of the definition")
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -39,10 +38,10 @@ impl Command for Def {
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
Ok(PipelineData::new(call.head))
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
@ -1,6 +1,8 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Value};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DefEnv;
|
||||
@ -11,50 +13,22 @@ impl Command for DefEnv {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Define a custom command, which participates in the caller environment"
|
||||
"Define a custom command, which participates in the caller environment."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("def-env")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("def_name", SyntaxShape::String, "definition name")
|
||||
.required("params", SyntaxShape::Signature, "parameters")
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block(Some(vec![])),
|
||||
"body of the definition",
|
||||
)
|
||||
.required("block", SyntaxShape::Block, "body of the definition")
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html
|
||||
|
||||
=== EXTRA NOTE ===
|
||||
All blocks are scoped, including variable definition and environment variable changes.
|
||||
|
||||
Because of this, the following doesn't work:
|
||||
|
||||
def-env cd_with_fallback [arg = ""] {
|
||||
let fall_back_path = "/tmp"
|
||||
if $arg != "" {
|
||||
cd $arg
|
||||
} else {
|
||||
cd $fall_back_path
|
||||
}
|
||||
}
|
||||
|
||||
Instead, you have to use cd in the top level scope:
|
||||
|
||||
def-env cd_with_fallback [arg = ""] {
|
||||
let fall_back_path = "/tmp"
|
||||
let path = if $arg != "" {
|
||||
$arg
|
||||
} else {
|
||||
$fall_back_path
|
||||
}
|
||||
cd $path
|
||||
}"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html
|
||||
"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -65,20 +39,17 @@ def-env cd_with_fallback [arg = ""] {
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
Ok(PipelineData::new(call.head))
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Set environment variable by call a custom command",
|
||||
example: r#"def-env foo [] { let-env BAR = "BAZ" }; foo; $env.BAR"#,
|
||||
result: Some(Value::String {
|
||||
val: "BAZ".to_string(),
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
result: Some(Value::test_string("BAZ")),
|
||||
}]
|
||||
}
|
||||
}
|
107
crates/nu-cmd-lang/src/core_commands/describe.rs
Normal file
107
crates/nu-cmd-lang/src/core_commands/describe.rs
Normal file
@ -0,0 +1,107 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Describe;
|
||||
|
||||
impl Command for Describe {
|
||||
fn name(&self) -> &str {
|
||||
"describe"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Describe the type and structure of the value(s) piped in."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("describe")
|
||||
.input_output_types(vec![(Type::Any, Type::String)])
|
||||
.switch(
|
||||
"no-collect",
|
||||
"do not collect streams of structured data",
|
||||
Some('n'),
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
|
||||
let no_collect: bool = call.has_flag("no-collect");
|
||||
|
||||
let description = match input {
|
||||
PipelineData::ExternalStream { .. } => "raw input".into(),
|
||||
PipelineData::ListStream(_, _) => {
|
||||
if no_collect {
|
||||
"stream".into()
|
||||
} else {
|
||||
let value = input.into_value(head);
|
||||
let base_description = match value {
|
||||
Value::CustomValue { val, .. } => val.value_string(),
|
||||
_ => value.get_type().to_string(),
|
||||
};
|
||||
|
||||
format!("{base_description} (stream)")
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let value = input.into_value(head);
|
||||
match value {
|
||||
Value::CustomValue { val, .. } => val.value_string(),
|
||||
_ => value.get_type().to_string(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Value::String {
|
||||
val: description,
|
||||
span: head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Describe the type of a string",
|
||||
example: "'hello' | describe",
|
||||
result: Some(Value::test_string("string")),
|
||||
},
|
||||
/*
|
||||
Example {
|
||||
description: "Describe a stream of data, collecting it first",
|
||||
example: "[1 2 3] | each {|i| $i} | describe",
|
||||
result: Some(Value::test_string("list<int> (stream)")),
|
||||
},
|
||||
Example {
|
||||
description: "Describe the input but do not collect streams",
|
||||
example: "[1 2 3] | each {|i| $i} | describe --no-collect",
|
||||
result: Some(Value::test_string("stream")),
|
||||
},
|
||||
*/
|
||||
]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["type", "typeof", "info", "structure"]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use super::Describe;
|
||||
use crate::test_examples;
|
||||
test_examples(Describe {})
|
||||
}
|
||||
}
|
305
crates/nu-cmd-lang/src/core_commands/do_.rs
Normal file
305
crates/nu-cmd-lang/src/core_commands/do_.rs
Normal file
@ -0,0 +1,305 @@
|
||||
use std::thread;
|
||||
|
||||
use nu_engine::{eval_block_with_early_return, CallExt};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, ListStream, PipelineData, RawStream, ShellError, Signature, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Do;
|
||||
|
||||
impl Command for Do {
|
||||
fn name(&self) -> &str {
|
||||
"do"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Run a closure, providing it with the pipeline input."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("do")
|
||||
.required("closure", SyntaxShape::Any, "the closure to run")
|
||||
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||
.switch(
|
||||
"ignore-errors",
|
||||
"ignore errors as the closure runs",
|
||||
Some('i'),
|
||||
)
|
||||
.switch(
|
||||
"ignore-shell-errors",
|
||||
"ignore shell errors as the closure runs",
|
||||
Some('s'),
|
||||
)
|
||||
.switch(
|
||||
"ignore-program-errors",
|
||||
"ignore external program errors as the closure runs",
|
||||
Some('p'),
|
||||
)
|
||||
.switch(
|
||||
"capture-errors",
|
||||
"catch errors as the closure runs, and return them",
|
||||
Some('c'),
|
||||
)
|
||||
.rest("rest", SyntaxShape::Any, "the parameter(s) for the closure")
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let block: Closure = call.req(engine_state, stack, 0)?;
|
||||
let rest: Vec<Value> = call.rest(engine_state, stack, 1)?;
|
||||
let ignore_all_errors = call.has_flag("ignore-errors");
|
||||
let ignore_shell_errors = ignore_all_errors || call.has_flag("ignore-shell-errors");
|
||||
let ignore_program_errors = ignore_all_errors || call.has_flag("ignore-program-errors");
|
||||
let capture_errors = call.has_flag("capture-errors");
|
||||
|
||||
let mut stack = stack.captures_to_stack(&block.captures);
|
||||
let block = engine_state.get_block(block.block_id);
|
||||
|
||||
let params: Vec<_> = block
|
||||
.signature
|
||||
.required_positional
|
||||
.iter()
|
||||
.chain(block.signature.optional_positional.iter())
|
||||
.collect();
|
||||
|
||||
for param in params.iter().zip(&rest) {
|
||||
if let Some(var_id) = param.0.var_id {
|
||||
stack.add_var(var_id, param.1.clone())
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(param) = &block.signature.rest_positional {
|
||||
if rest.len() > params.len() {
|
||||
let mut rest_items = vec![];
|
||||
|
||||
for r in rest.into_iter().skip(params.len()) {
|
||||
rest_items.push(r);
|
||||
}
|
||||
|
||||
let span = if let Some(rest_item) = rest_items.first() {
|
||||
rest_item.span()?
|
||||
} else {
|
||||
call.head
|
||||
};
|
||||
|
||||
stack.add_var(
|
||||
param
|
||||
.var_id
|
||||
.expect("Internal error: rest positional parameter lacks var_id"),
|
||||
Value::List {
|
||||
vals: rest_items,
|
||||
span,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
let result = eval_block_with_early_return(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
block,
|
||||
input,
|
||||
call.redirect_stdout,
|
||||
call.redirect_stdout,
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(PipelineData::ExternalStream {
|
||||
stdout,
|
||||
stderr,
|
||||
exit_code,
|
||||
span,
|
||||
metadata,
|
||||
trim_end_newline,
|
||||
}) if capture_errors => {
|
||||
// Use a thread to receive stdout message.
|
||||
// Or we may get a deadlock if child process sends out too much bytes to stderr.
|
||||
//
|
||||
// For example: in normal linux system, stderr pipe's limit is 65535 bytes.
|
||||
// if child process sends out 65536 bytes, the process will be hanged because no consumer
|
||||
// consumes the first 65535 bytes
|
||||
// So we need a thread to receive stdout message, then the current thread can continue to consume
|
||||
// stderr messages.
|
||||
let stdout_handler = stdout.map(|stdout_stream| {
|
||||
thread::Builder::new()
|
||||
.name("stderr redirector".to_string())
|
||||
.spawn(move || {
|
||||
let ctrlc = stdout_stream.ctrlc.clone();
|
||||
let span = stdout_stream.span;
|
||||
RawStream::new(
|
||||
Box::new(
|
||||
vec![stdout_stream.into_bytes().map(|s| s.item)].into_iter(),
|
||||
),
|
||||
ctrlc,
|
||||
span,
|
||||
None,
|
||||
)
|
||||
})
|
||||
.expect("Failed to create thread")
|
||||
});
|
||||
|
||||
// Intercept stderr so we can return it in the error if the exit code is non-zero.
|
||||
// The threading issues mentioned above dictate why we also need to intercept stdout.
|
||||
let mut stderr_ctrlc = None;
|
||||
let stderr_msg = match stderr {
|
||||
None => "".to_string(),
|
||||
Some(stderr_stream) => {
|
||||
stderr_ctrlc = stderr_stream.ctrlc.clone();
|
||||
stderr_stream.into_string().map(|s| s.item)?
|
||||
}
|
||||
};
|
||||
|
||||
let stdout = if let Some(handle) = stdout_handler {
|
||||
match handle.join() {
|
||||
Err(err) => {
|
||||
return Err(ShellError::ExternalCommand {
|
||||
label: "Fail to receive external commands stdout message"
|
||||
.to_string(),
|
||||
help: format!("{err:?}"),
|
||||
span,
|
||||
});
|
||||
}
|
||||
Ok(res) => Some(res),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut exit_code_ctrlc = None;
|
||||
let exit_code: Vec<Value> = match exit_code {
|
||||
None => vec![],
|
||||
Some(exit_code_stream) => {
|
||||
exit_code_ctrlc = exit_code_stream.ctrlc.clone();
|
||||
exit_code_stream.into_iter().collect()
|
||||
}
|
||||
};
|
||||
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
|
||||
if *code != 0 {
|
||||
return Err(ShellError::ExternalCommand {
|
||||
label: "External command failed".to_string(),
|
||||
help: stderr_msg,
|
||||
span,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PipelineData::ExternalStream {
|
||||
stdout,
|
||||
stderr: Some(RawStream::new(
|
||||
Box::new(vec![Ok(stderr_msg.into_bytes())].into_iter()),
|
||||
stderr_ctrlc,
|
||||
span,
|
||||
None,
|
||||
)),
|
||||
exit_code: Some(ListStream::from_stream(
|
||||
exit_code.into_iter(),
|
||||
exit_code_ctrlc,
|
||||
)),
|
||||
span,
|
||||
metadata,
|
||||
trim_end_newline,
|
||||
})
|
||||
}
|
||||
Ok(PipelineData::ExternalStream {
|
||||
stdout,
|
||||
stderr,
|
||||
exit_code: _,
|
||||
span,
|
||||
metadata,
|
||||
trim_end_newline,
|
||||
}) if ignore_program_errors && !call.redirect_stdout => {
|
||||
Ok(PipelineData::ExternalStream {
|
||||
stdout,
|
||||
stderr,
|
||||
exit_code: None,
|
||||
span,
|
||||
metadata,
|
||||
trim_end_newline,
|
||||
})
|
||||
}
|
||||
Ok(PipelineData::Value(Value::Error { .. }, ..)) | Err(_) if ignore_shell_errors => {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
Ok(PipelineData::ListStream(ls, metadata)) if ignore_shell_errors => {
|
||||
// check if there is a `Value::Error` in given list stream first.
|
||||
let mut values = vec![];
|
||||
let ctrlc = ls.ctrlc.clone();
|
||||
for v in ls {
|
||||
if let Value::Error { .. } = v {
|
||||
values.push(Value::nothing(call.head));
|
||||
} else {
|
||||
values.push(v)
|
||||
}
|
||||
}
|
||||
Ok(PipelineData::ListStream(
|
||||
ListStream::from_stream(values.into_iter(), ctrlc),
|
||||
metadata,
|
||||
))
|
||||
}
|
||||
r => r,
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Run the closure",
|
||||
example: r#"do { echo hello }"#,
|
||||
result: Some(Value::test_string("hello")),
|
||||
},
|
||||
Example {
|
||||
description: "Run a stored first-class closure",
|
||||
example: r#"let text = "I am enclosed"; let hello = {|| echo $text}; do $hello"#,
|
||||
result: Some(Value::test_string("I am enclosed")),
|
||||
},
|
||||
Example {
|
||||
description: "Run the closure and ignore both shell and external program errors",
|
||||
example: r#"do -i { thisisnotarealcommand }"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Run the closure and ignore shell errors",
|
||||
example: r#"do -s { thisisnotarealcommand }"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Run the closure and ignore external program errors",
|
||||
example: r#"do -p { nu -c 'exit 1' }; echo "I'll still run""#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Abort the pipeline if a program returns a non-zero exit code",
|
||||
example: r#"do -c { nu -c 'exit 1' } | myscarycommand"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Run the closure, with a positional parameter",
|
||||
example: r#"do {|x| 100 + $x } 77"#,
|
||||
result: Some(Value::test_int(177)),
|
||||
},
|
||||
Example {
|
||||
description: "Run the closure, with input",
|
||||
example: r#"77 | do {|x| 100 + $in }"#,
|
||||
result: None, // TODO: returns 177
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use super::Do;
|
||||
use crate::test_examples;
|
||||
test_examples(Do {})
|
||||
}
|
||||
}
|
@ -2,7 +2,8 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
||||
Category, Example, ListStream, PipelineData, ShellError, Signature, Span, SyntaxShape, Type,
|
||||
Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -14,17 +15,20 @@ impl Command for Echo {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Echo the arguments back to the user."
|
||||
"Returns its arguments, ignoring the piped-in value."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("echo")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Any)])
|
||||
.rest("rest", SyntaxShape::Any, "the values to echo")
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
"Unlike `print`, this command returns an actual value that will be passed to the next command of the pipeline."
|
||||
r#"When given no arguments, it returns an empty string. When given one argument,
|
||||
it returns it. Otherwise, it returns a list of the arguments. There is usually
|
||||
little reason to use this over just writing the values as-is."#
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -47,13 +51,7 @@ impl Command for Echo {
|
||||
std::cmp::Ordering::Equal => PipelineData::Value(to_be_echoed[0].clone(), None),
|
||||
|
||||
// When there are no elements, we echo the empty string
|
||||
std::cmp::Ordering::Less => PipelineData::Value(
|
||||
Value::String {
|
||||
val: "".to_string(),
|
||||
span: call.head,
|
||||
},
|
||||
None,
|
||||
),
|
||||
std::cmp::Ordering::Less => PipelineData::Value(Value::string("", call.head), None),
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -61,13 +59,17 @@ impl Command for Echo {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Put a hello message in the pipeline",
|
||||
example: "echo 'hello'",
|
||||
result: Some(Value::test_string("hello")),
|
||||
description: "Put a list of numbers in the pipeline. This is the same as [1 2 3].",
|
||||
example: "echo 1 2 3",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Print the value of the special '$nu' variable",
|
||||
example: "echo $nu",
|
||||
description:
|
||||
"Returns the piped-in value, by using the special $in variable to obtain it.",
|
||||
example: "echo $in",
|
||||
result: None,
|
||||
},
|
||||
]
|
@ -2,7 +2,7 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -15,7 +15,13 @@ impl Command for ErrorMake {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("error make")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Error)])
|
||||
.required("error_struct", SyntaxShape::Record, "the error to create")
|
||||
.switch(
|
||||
"unspanned",
|
||||
"remove the origin label from the error",
|
||||
Some('u'),
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
@ -24,7 +30,7 @@ impl Command for ErrorMake {
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["err", "panic", "crash", "throw"]
|
||||
vec!["panic", "crash", "throw"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -33,19 +39,32 @@ impl Command for ErrorMake {
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let span = call.head;
|
||||
let arg: Value = call.req(engine_state, stack, 0)?;
|
||||
let unspanned = call.has_flag("unspanned");
|
||||
|
||||
Err(make_error(&arg, span).unwrap_or_else(|| {
|
||||
ShellError::GenericError(
|
||||
"Creating error value not supported.".into(),
|
||||
"unsupported error format".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
}))
|
||||
if unspanned {
|
||||
Err(make_error(&arg, None).unwrap_or_else(|| {
|
||||
ShellError::GenericError(
|
||||
"Creating error value not supported.".into(),
|
||||
"unsupported error format".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
}))
|
||||
} else {
|
||||
Err(make_error(&arg, Some(span)).unwrap_or_else(|| {
|
||||
ShellError::GenericError(
|
||||
"Creating error value not supported.".into(),
|
||||
"unsupported error format".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -53,15 +72,15 @@ impl Command for ErrorMake {
|
||||
Example {
|
||||
description: "Create a custom error for a custom command",
|
||||
example: r#"def foo [x] {
|
||||
let span = (metadata $x).span;
|
||||
error make {msg: "this is fishy", label: {text: "fish right here", start: $span.start, end: $span.end } }
|
||||
let span = (metadata $x).span;
|
||||
error make {msg: "this is fishy", label: {text: "fish right here", start: $span.start, end: $span.end } }
|
||||
}"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Create a simple custom error for a custom command",
|
||||
example: r#"def foo [x] {
|
||||
error make {msg: "this is fishy"}
|
||||
error make {msg: "this is fishy"}
|
||||
}"#,
|
||||
result: None,
|
||||
},
|
||||
@ -69,7 +88,7 @@ impl Command for ErrorMake {
|
||||
}
|
||||
}
|
||||
|
||||
fn make_error(value: &Value, throw_span: Span) -> Option<ShellError> {
|
||||
fn make_error(value: &Value, throw_span: Option<Span>) -> Option<ShellError> {
|
||||
if let Value::Record { .. } = &value {
|
||||
let msg = value.get_data_by_key("msg");
|
||||
let label = value.get_data_by_key("label");
|
||||
@ -90,10 +109,7 @@ fn make_error(value: &Value, throw_span: Span) -> Option<ShellError> {
|
||||
) => Some(ShellError::GenericError(
|
||||
message,
|
||||
label_text,
|
||||
Some(Span {
|
||||
start: start as usize,
|
||||
end: end as usize,
|
||||
}),
|
||||
Some(Span::new(start as usize, end as usize)),
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
@ -106,7 +122,7 @@ fn make_error(value: &Value, throw_span: Span) -> Option<ShellError> {
|
||||
) => Some(ShellError::GenericError(
|
||||
message,
|
||||
label_text,
|
||||
Some(throw_span),
|
||||
throw_span,
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
@ -116,7 +132,7 @@ fn make_error(value: &Value, throw_span: Span) -> Option<ShellError> {
|
||||
(Some(Value::String { val: message, .. }), None) => Some(ShellError::GenericError(
|
||||
message,
|
||||
"originates from here".to_string(),
|
||||
Some(throw_span),
|
||||
throw_span,
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
@ -2,7 +2,7 @@ use nu_engine::get_full_help;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, Signature, Span, Value,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -14,16 +14,18 @@ impl Command for ExportCommand {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("export").category(Category::Core)
|
||||
Signature::build("export")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Export custom commands or environment variables from a module."
|
||||
"Export definitions or environment variables from a module."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -36,13 +38,14 @@ impl Command for ExportCommand {
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(Value::String {
|
||||
val: get_full_help(
|
||||
&ExportCommand.signature(),
|
||||
&ExportCommand.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
),
|
||||
span: call.head,
|
||||
}
|
||||
@ -53,10 +56,11 @@ impl Command for ExportCommand {
|
||||
vec![Example {
|
||||
description: "Export a definition from a module",
|
||||
example: r#"module utils { export def my-command [] { "hello" } }; use utils my-command; my-command"#,
|
||||
result: Some(Value::String {
|
||||
val: "hello".to_string(),
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
result: Some(Value::test_string("hello")),
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["module"]
|
||||
}
|
||||
}
|
61
crates/nu-cmd-lang/src/core_commands/export_alias.rs
Normal file
61
crates/nu-cmd-lang/src/core_commands/export_alias.rs
Normal file
@ -0,0 +1,61 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportAlias;
|
||||
|
||||
impl Command for ExportAlias {
|
||||
fn name(&self) -> &str {
|
||||
"export alias"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Alias a command (with optional flags) to a new name and export it from a module."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("export alias")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("name", SyntaxShape::String, "name of the alias")
|
||||
.required(
|
||||
"initial_value",
|
||||
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
|
||||
"equals sign followed by value",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["abbr", "aka", "fn", "func", "function"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Alias ll to ls -l and export it from a module",
|
||||
example: "module spam { export alias ll = ls -l }",
|
||||
result: Some(Value::nothing(Span::test_data())),
|
||||
}]
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Value};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportDef;
|
||||
@ -11,24 +13,21 @@ impl Command for ExportDef {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Define a custom command and export it from a module"
|
||||
"Define a custom command and export it from a module."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("export def")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("name", SyntaxShape::String, "definition name")
|
||||
.required("params", SyntaxShape::Signature, "parameters")
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block(Some(vec![])),
|
||||
"body of the definition",
|
||||
)
|
||||
.required("block", SyntaxShape::Block, "body of the definition")
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -39,20 +38,21 @@ impl Command for ExportDef {
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
Ok(PipelineData::new(call.head))
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Define a custom command in a module and call it",
|
||||
example: r#"module spam { export def foo [] { "foo" } }; use spam foo; foo"#,
|
||||
result: Some(Value::String {
|
||||
val: "foo".to_string(),
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
result: Some(Value::test_string("foo")),
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["module"]
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Value};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportDefEnv;
|
||||
@ -11,24 +13,21 @@ impl Command for ExportDefEnv {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Define a custom command that participates in the environment and export it from a module"
|
||||
"Define a custom command that participates in the environment and export it from a module."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("export def-env")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("name", SyntaxShape::String, "definition name")
|
||||
.required("params", SyntaxShape::Signature, "parameters")
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block(Some(vec![])),
|
||||
"body of the definition",
|
||||
)
|
||||
.required("block", SyntaxShape::Block, "body of the definition")
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html
|
||||
https://www.nushell.sh/book/thinking_in_nu.html
|
||||
|
||||
=== EXTRA NOTE ===
|
||||
All blocks are scoped, including variable definition and environment variable changes.
|
||||
@ -65,20 +64,21 @@ export def-env cd_with_fallback [arg = ""] {
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
Ok(PipelineData::new(call.head))
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Define a custom command that participates in the environment in a module and call it",
|
||||
example: r#"module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR"#,
|
||||
result: Some(Value::String {
|
||||
val: "BAZ".to_string(),
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
result: Some(Value::test_string("BAZ")),
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["module"]
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportExtern;
|
||||
@ -11,11 +11,12 @@ impl Command for ExportExtern {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Define an extern and export it from a module"
|
||||
"Define an extern and export it from a module."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("export extern")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("def_name", SyntaxShape::String, "definition name")
|
||||
.required("params", SyntaxShape::Signature, "parameters")
|
||||
.category(Category::Core)
|
||||
@ -23,7 +24,7 @@ impl Command for ExportExtern {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -34,10 +35,10 @@ impl Command for ExportExtern {
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
Ok(PipelineData::new(call.head))
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -47,4 +48,8 @@ impl Command for ExportExtern {
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["signature", "module", "declare"]
|
||||
}
|
||||
}
|
65
crates/nu-cmd-lang/src/core_commands/export_use.rs
Normal file
65
crates/nu-cmd-lang/src/core_commands/export_use.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportUse;
|
||||
|
||||
impl Command for ExportUse {
|
||||
fn name(&self) -> &str {
|
||||
"export use"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Use definitions from a module and export them from this module."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("export use")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("module", SyntaxShape::String, "Module or module file")
|
||||
.optional(
|
||||
"members",
|
||||
SyntaxShape::Any,
|
||||
"Which members of the module to import",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Re-export a command from another module",
|
||||
example: r#"module spam { export def foo [] { "foo" } }
|
||||
module eggs { export use spam foo }
|
||||
use eggs foo
|
||||
foo
|
||||
"#,
|
||||
result: Some(Value::test_string("foo")),
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["reexport", "import", "module"]
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Extern;
|
||||
@ -11,11 +11,12 @@ impl Command for Extern {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Define a signature for an external command"
|
||||
"Define a signature for an external command."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("extern")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("def_name", SyntaxShape::String, "definition name")
|
||||
.required("params", SyntaxShape::Signature, "parameters")
|
||||
.category(Category::Core)
|
||||
@ -23,7 +24,7 @@ impl Command for Extern {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -34,10 +35,10 @@ impl Command for Extern {
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
Ok(PipelineData::new(call.head))
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
222
crates/nu-cmd-lang/src/core_commands/for_.rs
Normal file
222
crates/nu-cmd-lang/src/core_commands/for_.rs
Normal file
@ -0,0 +1,222 @@
|
||||
use nu_engine::{eval_block, eval_expression, CallExt};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Block, Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct For;
|
||||
|
||||
impl Command for For {
|
||||
fn name(&self) -> &str {
|
||||
"for"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Loop over a range."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("for")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.allow_variants_without_examples(true)
|
||||
.required(
|
||||
"var_name",
|
||||
SyntaxShape::VarWithOptType,
|
||||
"name of the looping variable",
|
||||
)
|
||||
.required(
|
||||
"range",
|
||||
SyntaxShape::Keyword(b"in".to_vec(), Box::new(SyntaxShape::Any)),
|
||||
"range of the loop",
|
||||
)
|
||||
.required("block", SyntaxShape::Block, "the block to run")
|
||||
.switch(
|
||||
"numbered",
|
||||
"return a numbered item ($it.index and $it.item)",
|
||||
Some('n'),
|
||||
)
|
||||
.creates_scope()
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let var_id = call
|
||||
.positional_nth(0)
|
||||
.expect("checked through parser")
|
||||
.as_var()
|
||||
.expect("internal error: missing variable");
|
||||
|
||||
let keyword_expr = call
|
||||
.positional_nth(1)
|
||||
.expect("checked through parser")
|
||||
.as_keyword()
|
||||
.expect("internal error: missing keyword");
|
||||
let values = eval_expression(engine_state, stack, keyword_expr)?;
|
||||
|
||||
let block: Block = call.req(engine_state, stack, 2)?;
|
||||
|
||||
let numbered = call.has_flag("numbered");
|
||||
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let engine_state = engine_state.clone();
|
||||
let block = engine_state.get_block(block.block_id).clone();
|
||||
let redirect_stdout = call.redirect_stdout;
|
||||
let redirect_stderr = call.redirect_stderr;
|
||||
|
||||
match values {
|
||||
Value::List { vals, .. } => {
|
||||
for (idx, x) in ListStream::from_stream(vals.into_iter(), ctrlc).enumerate() {
|
||||
// with_env() is used here to ensure that each iteration uses
|
||||
// a different set of environment variables.
|
||||
// Hence, a 'cd' in the first loop won't affect the next loop.
|
||||
|
||||
stack.add_var(
|
||||
var_id,
|
||||
if numbered {
|
||||
Value::Record {
|
||||
cols: vec!["index".into(), "item".into()],
|
||||
vals: vec![Value::int(idx as i64, head), x],
|
||||
span: head,
|
||||
}
|
||||
} else {
|
||||
x
|
||||
},
|
||||
);
|
||||
|
||||
//let block = engine_state.get_block(block_id);
|
||||
match eval_block(
|
||||
&engine_state,
|
||||
stack,
|
||||
&block,
|
||||
PipelineData::empty(),
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
) {
|
||||
Err(ShellError::Break(_)) => {
|
||||
break;
|
||||
}
|
||||
Err(ShellError::Continue(_)) => {
|
||||
continue;
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err);
|
||||
}
|
||||
Ok(pipeline) => {
|
||||
let exit_code = pipeline.print(&engine_state, stack, false, false)?;
|
||||
if exit_code != 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Range { val, .. } => {
|
||||
for (idx, x) in val.into_range_iter(ctrlc)?.enumerate() {
|
||||
stack.add_var(
|
||||
var_id,
|
||||
if numbered {
|
||||
Value::Record {
|
||||
cols: vec!["index".into(), "item".into()],
|
||||
vals: vec![Value::int(idx as i64, head), x],
|
||||
span: head,
|
||||
}
|
||||
} else {
|
||||
x
|
||||
},
|
||||
);
|
||||
|
||||
//let block = engine_state.get_block(block_id);
|
||||
match eval_block(
|
||||
&engine_state,
|
||||
stack,
|
||||
&block,
|
||||
PipelineData::empty(),
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
) {
|
||||
Err(ShellError::Break(_)) => {
|
||||
break;
|
||||
}
|
||||
Err(ShellError::Continue(_)) => {
|
||||
continue;
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err);
|
||||
}
|
||||
Ok(pipeline) => {
|
||||
let exit_code = pipeline.print(&engine_state, stack, false, false)?;
|
||||
if exit_code != 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
x => {
|
||||
stack.add_var(var_id, x);
|
||||
|
||||
eval_block(
|
||||
&engine_state,
|
||||
stack,
|
||||
&block,
|
||||
PipelineData::empty(),
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
)?
|
||||
.into_value(head);
|
||||
}
|
||||
}
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Echo the square of each integer",
|
||||
example: "for x in [1 2 3] { print ($x * $x) }",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Work with elements of a range",
|
||||
example: "for $x in 1..3 { print $x }",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Number each item and echo a message",
|
||||
example:
|
||||
"for $it in ['bob' 'fred'] --numbered { print $\"($it.index) is ($it.item)\" }",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(For {})
|
||||
}
|
||||
}
|
260
crates/nu-cmd-lang/src/core_commands/help.rs
Normal file
260
crates/nu-cmd-lang/src/core_commands/help.rs
Normal file
@ -0,0 +1,260 @@
|
||||
use crate::help_aliases::help_aliases;
|
||||
use crate::help_commands::help_commands;
|
||||
use crate::help_modules::help_modules;
|
||||
use fancy_regex::Regex;
|
||||
use nu_ansi_term::{
|
||||
Color::{Red, White},
|
||||
Style,
|
||||
};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
span, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
||||
SyntaxShape, Type, Value,
|
||||
};
|
||||
#[derive(Clone)]
|
||||
pub struct Help;
|
||||
|
||||
impl Command for Help {
|
||||
fn name(&self) -> &str {
|
||||
"help"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("help")
|
||||
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::String,
|
||||
"the name of command, alias or module to get help on",
|
||||
)
|
||||
.named(
|
||||
"find",
|
||||
SyntaxShape::String,
|
||||
"string to find in command names, usage, and search terms",
|
||||
Some('f'),
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Display help information about different parts of Nushell."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"`help word` searches for "word" in commands, aliases and modules, in that order."#
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||
|
||||
if rest.is_empty() && find.is_none() {
|
||||
let msg = r#"Welcome to Nushell.
|
||||
|
||||
Here are some tips to help you get started.
|
||||
* help -h or help help - show available `help` subcommands and examples
|
||||
* help commands - list all available commands
|
||||
* help <name> - display help about a particular command, alias, or module
|
||||
* help --find <text to search> - search through all help commands table
|
||||
|
||||
Nushell works on the idea of a "pipeline". Pipelines are commands connected with the '|' character.
|
||||
Each stage in the pipeline works together to load, parse, and display information to you.
|
||||
|
||||
[Examples]
|
||||
|
||||
List the files in the current directory, sorted by size:
|
||||
ls | sort-by size
|
||||
|
||||
Get information about the current system:
|
||||
sys | get host
|
||||
|
||||
Get the processes on your system actively using CPU:
|
||||
ps | where cpu > 0
|
||||
|
||||
You can also learn more at https://www.nushell.sh/book/"#;
|
||||
|
||||
Ok(Value::string(msg, head).into_pipeline_data())
|
||||
} else if find.is_some() {
|
||||
help_commands(engine_state, stack, call)
|
||||
} else {
|
||||
let result = help_aliases(engine_state, stack, call);
|
||||
|
||||
let result = if let Err(ShellError::AliasNotFound(_)) = result {
|
||||
help_commands(engine_state, stack, call)
|
||||
} else {
|
||||
result
|
||||
};
|
||||
|
||||
let result = if let Err(ShellError::CommandNotFound(_)) = result {
|
||||
help_modules(engine_state, stack, call)
|
||||
} else {
|
||||
result
|
||||
};
|
||||
|
||||
if let Err(ShellError::ModuleNotFoundAtRuntime {
|
||||
mod_name: _,
|
||||
span: _,
|
||||
}) = result
|
||||
{
|
||||
let rest_spans: Vec<Span> = rest.iter().map(|arg| arg.span).collect();
|
||||
Err(ShellError::NotFound {
|
||||
span: span(&rest_spans),
|
||||
})
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "show help for single command, alias, or module",
|
||||
example: "help match",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "show help for single sub-command, alias, or module",
|
||||
example: "help str lpad",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "search for string in command names, usage and search terms",
|
||||
example: "help --find char",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn highlight_search_in_table(
|
||||
table: Vec<Value>, // list of records
|
||||
search_string: &str,
|
||||
searched_cols: &[&str],
|
||||
string_style: &Style,
|
||||
) -> Result<Vec<Value>, ShellError> {
|
||||
let orig_search_string = search_string;
|
||||
let search_string = search_string.to_lowercase();
|
||||
let mut matches = vec![];
|
||||
|
||||
for record in table {
|
||||
let (cols, mut vals, record_span) = if let Value::Record { cols, vals, span } = record {
|
||||
(cols, vals, span)
|
||||
} else {
|
||||
return Err(ShellError::NushellFailedSpanned {
|
||||
msg: "Expected record".to_string(),
|
||||
label: format!("got {}", record.get_type()),
|
||||
span: record.span()?,
|
||||
});
|
||||
};
|
||||
|
||||
let has_match = cols.iter().zip(vals.iter_mut()).fold(
|
||||
Ok(false),
|
||||
|acc: Result<bool, ShellError>, (col, val)| {
|
||||
if searched_cols.contains(&col.as_str()) {
|
||||
if let Value::String { val: s, span } = val {
|
||||
if s.to_lowercase().contains(&search_string) {
|
||||
*val = Value::String {
|
||||
val: highlight_search_string(s, orig_search_string, string_style)?,
|
||||
span: *span,
|
||||
};
|
||||
Ok(true)
|
||||
} else {
|
||||
// column does not contain the searched string
|
||||
acc
|
||||
}
|
||||
} else {
|
||||
// ignore non-string values
|
||||
acc
|
||||
}
|
||||
} else {
|
||||
// don't search this column
|
||||
acc
|
||||
}
|
||||
},
|
||||
)?;
|
||||
|
||||
if has_match {
|
||||
matches.push(Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: record_span,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(matches)
|
||||
}
|
||||
|
||||
// Highlight the search string using ANSI escape sequences and regular expressions.
|
||||
pub fn highlight_search_string(
|
||||
haystack: &str,
|
||||
needle: &str,
|
||||
string_style: &Style,
|
||||
) -> Result<String, ShellError> {
|
||||
let regex_string = format!("(?i){needle}");
|
||||
let regex = match Regex::new(®ex_string) {
|
||||
Ok(regex) => regex,
|
||||
Err(err) => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Could not compile regex".into(),
|
||||
err.to_string(),
|
||||
Some(Span::test_data()),
|
||||
None,
|
||||
Vec::new(),
|
||||
));
|
||||
}
|
||||
};
|
||||
// strip haystack to remove existing ansi style
|
||||
let stripped_haystack = nu_utils::strip_ansi_likely(haystack);
|
||||
let mut last_match_end = 0;
|
||||
let style = Style::new().fg(White).on(Red);
|
||||
let mut highlighted = String::new();
|
||||
|
||||
for cap in regex.captures_iter(stripped_haystack.as_ref()) {
|
||||
match cap {
|
||||
Ok(capture) => {
|
||||
let start = match capture.get(0) {
|
||||
Some(acap) => acap.start(),
|
||||
None => 0,
|
||||
};
|
||||
let end = match capture.get(0) {
|
||||
Some(acap) => acap.end(),
|
||||
None => 0,
|
||||
};
|
||||
highlighted.push_str(
|
||||
&string_style
|
||||
.paint(&stripped_haystack[last_match_end..start])
|
||||
.to_string(),
|
||||
);
|
||||
highlighted.push_str(&style.paint(&stripped_haystack[start..end]).to_string());
|
||||
last_match_end = end;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Error with regular expression capture".into(),
|
||||
e.to_string(),
|
||||
None,
|
||||
None,
|
||||
Vec::new(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
highlighted.push_str(
|
||||
&string_style
|
||||
.paint(&stripped_haystack[last_match_end..])
|
||||
.to_string(),
|
||||
);
|
||||
Ok(highlighted)
|
||||
}
|
181
crates/nu-cmd-lang/src/core_commands/help_aliases.rs
Normal file
181
crates/nu-cmd-lang/src/core_commands/help_aliases.rs
Normal file
@ -0,0 +1,181 @@
|
||||
use crate::help::highlight_search_in_table;
|
||||
use nu_color_config::StyleComputer;
|
||||
use nu_engine::{scope::ScopeData, CallExt};
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
span, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
|
||||
ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HelpAliases;
|
||||
|
||||
impl Command for HelpAliases {
|
||||
fn name(&self) -> &str {
|
||||
"help aliases"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show help on nushell aliases."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("help aliases")
|
||||
.category(Category::Core)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::String,
|
||||
"the name of alias to get help on",
|
||||
)
|
||||
.named(
|
||||
"find",
|
||||
SyntaxShape::String,
|
||||
"string to find in alias names and usage",
|
||||
Some('f'),
|
||||
)
|
||||
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||
.allow_variants_without_examples(true)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "show all aliases",
|
||||
example: "help aliases",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "show help for single alias",
|
||||
example: "help aliases my-alias",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "search for string in alias names and usages",
|
||||
example: "help aliases --find my-alias",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
help_aliases(engine_state, stack, call)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn help_aliases(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||
|
||||
// 🚩The following two-lines are copied from filters/find.rs:
|
||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||
// Currently, search results all use the same style.
|
||||
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
||||
// defined for "string").
|
||||
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
||||
|
||||
if let Some(f) = find {
|
||||
let all_cmds_vec = build_help_aliases(engine_state, stack, head);
|
||||
let found_cmds_vec =
|
||||
highlight_search_in_table(all_cmds_vec, &f.item, &["name", "usage"], &string_style)?;
|
||||
|
||||
return Ok(found_cmds_vec
|
||||
.into_iter()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()));
|
||||
}
|
||||
|
||||
if rest.is_empty() {
|
||||
let found_cmds_vec = build_help_aliases(engine_state, stack, head);
|
||||
|
||||
Ok(found_cmds_vec
|
||||
.into_iter()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||
} else {
|
||||
let mut name = String::new();
|
||||
|
||||
for r in &rest {
|
||||
if !name.is_empty() {
|
||||
name.push(' ');
|
||||
}
|
||||
name.push_str(&r.item);
|
||||
}
|
||||
|
||||
let alias_id = if let Some(id) = engine_state.find_alias(name.as_bytes(), &[]) {
|
||||
id
|
||||
} else {
|
||||
return Err(ShellError::AliasNotFound(span(
|
||||
&rest.iter().map(|r| r.span).collect::<Vec<Span>>(),
|
||||
)));
|
||||
};
|
||||
|
||||
let alias_expansion = engine_state
|
||||
.get_alias(alias_id)
|
||||
.iter()
|
||||
.map(|span| String::from_utf8_lossy(engine_state.get_span_contents(span)))
|
||||
.collect::<Vec<Cow<str>>>()
|
||||
.join(" ");
|
||||
|
||||
let alias_usage = engine_state.build_alias_usage(alias_id);
|
||||
|
||||
// TODO: merge this into documentation.rs at some point
|
||||
const G: &str = "\x1b[32m"; // green
|
||||
const C: &str = "\x1b[36m"; // cyan
|
||||
const RESET: &str = "\x1b[0m"; // reset
|
||||
|
||||
let mut long_desc = String::new();
|
||||
|
||||
if let Some((usage, extra_usage)) = alias_usage {
|
||||
long_desc.push_str(&usage);
|
||||
long_desc.push_str("\n\n");
|
||||
|
||||
if !extra_usage.is_empty() {
|
||||
long_desc.push_str(&extra_usage);
|
||||
long_desc.push_str("\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
long_desc.push_str(&format!("{G}Alias{RESET}: {C}{name}{RESET}"));
|
||||
long_desc.push_str("\n\n");
|
||||
long_desc.push_str(&format!("{G}Expansion{RESET}:\n {alias_expansion}"));
|
||||
|
||||
let config = engine_state.get_config();
|
||||
if !config.use_ansi_coloring {
|
||||
long_desc = nu_utils::strip_ansi_string_likely(long_desc);
|
||||
}
|
||||
|
||||
Ok(Value::String {
|
||||
val: long_desc,
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
fn build_help_aliases(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
|
||||
let mut scope_data = ScopeData::new(engine_state, stack);
|
||||
scope_data.populate_aliases();
|
||||
|
||||
scope_data.collect_aliases(span)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use super::HelpAliases;
|
||||
use crate::test_examples;
|
||||
test_examples(HelpAliases {})
|
||||
}
|
||||
}
|
185
crates/nu-cmd-lang/src/core_commands/help_commands.rs
Normal file
185
crates/nu-cmd-lang/src/core_commands/help_commands.rs
Normal file
@ -0,0 +1,185 @@
|
||||
use crate::help::highlight_search_in_table;
|
||||
use nu_color_config::StyleComputer;
|
||||
use nu_engine::{get_full_help, CallExt};
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
span, Category, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
|
||||
Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
use std::borrow::Borrow;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HelpCommands;
|
||||
|
||||
impl Command for HelpCommands {
|
||||
fn name(&self) -> &str {
|
||||
"help commands"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show help on nushell commands."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("help commands")
|
||||
.category(Category::Core)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::String,
|
||||
"the name of command to get help on",
|
||||
)
|
||||
.named(
|
||||
"find",
|
||||
SyntaxShape::String,
|
||||
"string to find in command names, usage, and search terms",
|
||||
Some('f'),
|
||||
)
|
||||
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||
.allow_variants_without_examples(true)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
help_commands(engine_state, stack, call)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn help_commands(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||
|
||||
// 🚩The following two-lines are copied from filters/find.rs:
|
||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||
// Currently, search results all use the same style.
|
||||
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
||||
// defined for "string").
|
||||
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
||||
|
||||
if let Some(f) = find {
|
||||
let all_cmds_vec = build_help_commands(engine_state, head);
|
||||
let found_cmds_vec = highlight_search_in_table(
|
||||
all_cmds_vec,
|
||||
&f.item,
|
||||
&["name", "usage", "search_terms"],
|
||||
&string_style,
|
||||
)?;
|
||||
|
||||
return Ok(found_cmds_vec
|
||||
.into_iter()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()));
|
||||
}
|
||||
|
||||
if rest.is_empty() {
|
||||
let found_cmds_vec = build_help_commands(engine_state, head);
|
||||
|
||||
Ok(found_cmds_vec
|
||||
.into_iter()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||
} else {
|
||||
let mut name = String::new();
|
||||
|
||||
for r in &rest {
|
||||
if !name.is_empty() {
|
||||
name.push(' ');
|
||||
}
|
||||
name.push_str(&r.item);
|
||||
}
|
||||
|
||||
let output = engine_state
|
||||
.get_signatures_with_examples(false)
|
||||
.iter()
|
||||
.filter(|(signature, _, _, _, _)| signature.name == name)
|
||||
.map(|(signature, examples, _, _, is_parser_keyword)| {
|
||||
get_full_help(signature, examples, engine_state, stack, *is_parser_keyword)
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
if !output.is_empty() {
|
||||
Ok(Value::String {
|
||||
val: output.join("======================\n\n"),
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
} else {
|
||||
Err(ShellError::CommandNotFound(span(&[
|
||||
rest[0].span,
|
||||
rest[rest.len() - 1].span,
|
||||
])))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec<Value> {
|
||||
let commands = engine_state.get_decls_sorted(false);
|
||||
let mut found_cmds_vec = Vec::new();
|
||||
|
||||
for (name_bytes, decl_id) in commands {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
let name = String::from_utf8_lossy(&name_bytes).to_string();
|
||||
let decl = engine_state.get_decl(decl_id);
|
||||
let sig = decl.signature().update_from_command(name, decl.borrow());
|
||||
|
||||
let signatures = sig.to_string().trim_start().replace("\n ", "\n");
|
||||
let key = sig.name;
|
||||
let usage = sig.usage;
|
||||
let search_terms = sig.search_terms;
|
||||
|
||||
cols.push("name".into());
|
||||
vals.push(Value::String { val: key, span });
|
||||
|
||||
cols.push("category".into());
|
||||
vals.push(Value::string(sig.category.to_string(), span));
|
||||
|
||||
cols.push("command_type".into());
|
||||
vals.push(Value::String {
|
||||
val: format!("{:?}", decl.command_type()).to_lowercase(),
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("usage".into());
|
||||
vals.push(Value::String { val: usage, span });
|
||||
|
||||
cols.push("signatures".into());
|
||||
vals.push(Value::String {
|
||||
val: if decl.is_parser_keyword() {
|
||||
"".to_string()
|
||||
} else {
|
||||
signatures
|
||||
},
|
||||
span,
|
||||
});
|
||||
|
||||
cols.push("search_terms".into());
|
||||
vals.push(Value::String {
|
||||
val: search_terms.join(", "),
|
||||
span,
|
||||
});
|
||||
|
||||
found_cmds_vec.push(Value::Record { cols, vals, span });
|
||||
}
|
||||
|
||||
found_cmds_vec
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use super::HelpCommands;
|
||||
use crate::test_examples;
|
||||
test_examples(HelpCommands {})
|
||||
}
|
||||
}
|
254
crates/nu-cmd-lang/src/core_commands/help_modules.rs
Normal file
254
crates/nu-cmd-lang/src/core_commands/help_modules.rs
Normal file
@ -0,0 +1,254 @@
|
||||
use crate::help::highlight_search_in_table;
|
||||
use nu_color_config::StyleComputer;
|
||||
use nu_engine::{scope::ScopeData, CallExt};
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
span, AliasId, Category, DeclId, Example, IntoInterruptiblePipelineData, IntoPipelineData,
|
||||
PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HelpModules;
|
||||
|
||||
impl Command for HelpModules {
|
||||
fn name(&self) -> &str {
|
||||
"help modules"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show help on nushell modules."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"When requesting help for a single module, its commands and aliases will be highlighted if they
|
||||
are also available in the current scope. Commands/aliases that were imported under a different name
|
||||
(such as with a prefix after `use some-module`) will be highlighted in parentheses."#
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("help modules")
|
||||
.category(Category::Core)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::String,
|
||||
"the name of module to get help on",
|
||||
)
|
||||
.named(
|
||||
"find",
|
||||
SyntaxShape::String,
|
||||
"string to find in module names and usage",
|
||||
Some('f'),
|
||||
)
|
||||
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||
.allow_variants_without_examples(true)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "show all modules",
|
||||
example: "help modules",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "show help for single module",
|
||||
example: "help modules my-module",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "search for string in module names and usages",
|
||||
example: "help modules --find my-module",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
help_modules(engine_state, stack, call)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn help_modules(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||
|
||||
// 🚩The following two-lines are copied from filters/find.rs:
|
||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||
// Currently, search results all use the same style.
|
||||
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
||||
// defined for "string").
|
||||
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
||||
|
||||
if let Some(f) = find {
|
||||
let all_cmds_vec = build_help_modules(engine_state, stack, head);
|
||||
let found_cmds_vec =
|
||||
highlight_search_in_table(all_cmds_vec, &f.item, &["name", "usage"], &string_style)?;
|
||||
|
||||
return Ok(found_cmds_vec
|
||||
.into_iter()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()));
|
||||
}
|
||||
|
||||
if rest.is_empty() {
|
||||
let found_cmds_vec = build_help_modules(engine_state, stack, head);
|
||||
|
||||
Ok(found_cmds_vec
|
||||
.into_iter()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||
} else {
|
||||
let mut name = String::new();
|
||||
|
||||
for r in &rest {
|
||||
if !name.is_empty() {
|
||||
name.push(' ');
|
||||
}
|
||||
name.push_str(&r.item);
|
||||
}
|
||||
|
||||
let module_id = if let Some(id) = engine_state.find_module(name.as_bytes(), &[]) {
|
||||
id
|
||||
} else {
|
||||
return Err(ShellError::ModuleNotFoundAtRuntime {
|
||||
mod_name: name,
|
||||
span: span(&rest.iter().map(|r| r.span).collect::<Vec<Span>>()),
|
||||
});
|
||||
};
|
||||
|
||||
let module = engine_state.get_module(module_id);
|
||||
|
||||
let module_usage = engine_state.build_module_usage(module_id);
|
||||
|
||||
// TODO: merge this into documentation.rs at some point
|
||||
const G: &str = "\x1b[32m"; // green
|
||||
const C: &str = "\x1b[36m"; // cyan
|
||||
const CB: &str = "\x1b[1;36m"; // cyan bold
|
||||
const RESET: &str = "\x1b[0m"; // reset
|
||||
|
||||
let mut long_desc = String::new();
|
||||
|
||||
if let Some((usage, extra_usage)) = module_usage {
|
||||
long_desc.push_str(&usage);
|
||||
long_desc.push_str("\n\n");
|
||||
|
||||
if !extra_usage.is_empty() {
|
||||
long_desc.push_str(&extra_usage);
|
||||
long_desc.push_str("\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
long_desc.push_str(&format!("{G}Module{RESET}: {C}{name}{RESET}"));
|
||||
long_desc.push_str("\n\n");
|
||||
|
||||
if !module.decls.is_empty() || module.main.is_some() {
|
||||
let commands: Vec<(Vec<u8>, DeclId)> = engine_state.get_decls_sorted(false).collect();
|
||||
|
||||
let mut module_commands = module.decls();
|
||||
module_commands.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
let commands_str = module_commands
|
||||
.iter()
|
||||
.map(|(name_bytes, id)| {
|
||||
let name = String::from_utf8_lossy(name_bytes);
|
||||
if let Some((used_name_bytes, _)) =
|
||||
commands.iter().find(|(_, decl_id)| id == decl_id)
|
||||
{
|
||||
if engine_state.find_decl(name.as_bytes(), &[]).is_some() {
|
||||
format!("{CB}{name}{RESET}")
|
||||
} else {
|
||||
let command_name = String::from_utf8_lossy(used_name_bytes);
|
||||
format!("{name} ({CB}{command_name}{RESET})")
|
||||
}
|
||||
} else {
|
||||
format!("{name}")
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
|
||||
long_desc.push_str(&format!("{G}Exported commands{RESET}:\n {commands_str}"));
|
||||
long_desc.push_str("\n\n");
|
||||
}
|
||||
|
||||
if !module.aliases.is_empty() {
|
||||
let aliases: Vec<(Vec<u8>, AliasId)> = engine_state.get_aliases_sorted(false).collect();
|
||||
|
||||
let mut module_aliases: Vec<(&[u8], AliasId)> = module
|
||||
.aliases
|
||||
.iter()
|
||||
.map(|(name, id)| (name.as_ref(), *id))
|
||||
.collect();
|
||||
module_aliases.sort_by(|a, b| a.0.cmp(b.0));
|
||||
|
||||
let aliases_str = module_aliases
|
||||
.iter()
|
||||
.map(|(name_bytes, id)| {
|
||||
let name = String::from_utf8_lossy(name_bytes);
|
||||
if let Some((used_name_bytes, _)) =
|
||||
aliases.iter().find(|(_, alias_id)| id == alias_id)
|
||||
{
|
||||
if engine_state.find_alias(name.as_bytes(), &[]).is_some() {
|
||||
format!("{CB}{name}{RESET}")
|
||||
} else {
|
||||
let alias_name = String::from_utf8_lossy(used_name_bytes);
|
||||
format!("{name} ({CB}{alias_name}{RESET})")
|
||||
}
|
||||
} else {
|
||||
format!("{name}")
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
|
||||
long_desc.push_str(&format!("{G}Exported aliases{RESET}:\n {aliases_str}"));
|
||||
long_desc.push_str("\n\n");
|
||||
}
|
||||
|
||||
if module.env_block.is_some() {
|
||||
long_desc.push_str(&format!("This module {C}exports{RESET} environment."));
|
||||
} else {
|
||||
long_desc.push_str(&format!(
|
||||
"This module {C}does not export{RESET} environment."
|
||||
));
|
||||
}
|
||||
|
||||
let config = engine_state.get_config();
|
||||
if !config.use_ansi_coloring {
|
||||
long_desc = nu_utils::strip_ansi_string_likely(long_desc);
|
||||
}
|
||||
|
||||
Ok(Value::String {
|
||||
val: long_desc,
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
fn build_help_modules(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
|
||||
let mut scope_data = ScopeData::new(engine_state, stack);
|
||||
scope_data.populate_modules();
|
||||
|
||||
scope_data.collect_modules(span)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use super::HelpModules;
|
||||
use crate::test_examples;
|
||||
test_examples(HelpModules {})
|
||||
}
|
||||
}
|
329
crates/nu-cmd-lang/src/core_commands/help_operators.rs
Normal file
329
crates/nu-cmd-lang/src/core_commands/help_operators.rs
Normal file
@ -0,0 +1,329 @@
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HelpOperators;
|
||||
|
||||
impl Command for HelpOperators {
|
||||
fn name(&self) -> &str {
|
||||
"help operators"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show help on nushell operators."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("help operators")
|
||||
.category(Category::Core)
|
||||
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||
.allow_variants_without_examples(true)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let op_info = generate_operator_info();
|
||||
let mut recs = vec![];
|
||||
|
||||
for op in op_info {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
cols.push("type".into());
|
||||
vals.push(Value::string(op.op_type, head));
|
||||
cols.push("operator".into());
|
||||
vals.push(Value::string(op.operator, head));
|
||||
cols.push("name".into());
|
||||
vals.push(Value::string(op.name, head));
|
||||
cols.push("description".into());
|
||||
vals.push(Value::string(op.description, head));
|
||||
cols.push("precedence".into());
|
||||
vals.push(Value::int(op.precedence, head));
|
||||
recs.push(Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: head,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(recs
|
||||
.into_iter()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
struct OperatorInfo {
|
||||
op_type: String,
|
||||
operator: String,
|
||||
name: String,
|
||||
description: String,
|
||||
precedence: i64,
|
||||
}
|
||||
|
||||
fn generate_operator_info() -> Vec<OperatorInfo> {
|
||||
vec![
|
||||
OperatorInfo {
|
||||
op_type: "Assignment".into(),
|
||||
operator: "=".into(),
|
||||
name: "Assign".into(),
|
||||
description: "Assigns a value to a variable.".into(),
|
||||
precedence: 10,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Assignment".into(),
|
||||
operator: "+=".into(),
|
||||
name: "PlusAssign".into(),
|
||||
description: "Adds a value to a variable.".into(),
|
||||
precedence: 10,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Assignment".into(),
|
||||
operator: "++=".into(),
|
||||
name: "AppendAssign".into(),
|
||||
description: "Appends a list or a value to a variable.".into(),
|
||||
precedence: 10,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Assignment".into(),
|
||||
operator: "-=".into(),
|
||||
name: "MinusAssign".into(),
|
||||
description: "Subtracts a value from a variable.".into(),
|
||||
precedence: 10,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Assignment".into(),
|
||||
operator: "*=".into(),
|
||||
name: "MultiplyAssign".into(),
|
||||
description: "Multiplies a variable by a value.".into(),
|
||||
precedence: 10,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Assignment".into(),
|
||||
operator: "/=".into(),
|
||||
name: "DivideAssign".into(),
|
||||
description: "Divides a variable by a value.".into(),
|
||||
precedence: 10,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "==".into(),
|
||||
name: "Equal".into(),
|
||||
description: "Checks if two values are equal.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "!=".into(),
|
||||
name: "NotEqual".into(),
|
||||
description: "Checks if two values are not equal.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "<".into(),
|
||||
name: "LessThan".into(),
|
||||
description: "Checks if a value is less than another.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "<=".into(),
|
||||
name: "LessThanOrEqual".into(),
|
||||
description: "Checks if a value is less than or equal to another.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: ">".into(),
|
||||
name: "GreaterThan".into(),
|
||||
description: "Checks if a value is greater than another.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: ">=".into(),
|
||||
name: "GreaterThanOrEqual".into(),
|
||||
description: "Checks if a value is greater than or equal to another.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "=~".into(),
|
||||
name: "RegexMatch".into(),
|
||||
description: "Checks if a value matches a regular expression.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "!~".into(),
|
||||
name: "NotRegexMatch".into(),
|
||||
description: "Checks if a value does not match a regular expression.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "in".into(),
|
||||
name: "In".into(),
|
||||
description: "Checks if a value is in a list or string.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "not-in".into(),
|
||||
name: "NotIn".into(),
|
||||
description: "Checks if a value is not in a list or string.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "starts-with".into(),
|
||||
name: "StartsWith".into(),
|
||||
description: "Checks if a string starts with another.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "ends-with".into(),
|
||||
name: "EndsWith".into(),
|
||||
description: "Checks if a string ends with another.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Comparison".into(),
|
||||
operator: "not".into(),
|
||||
name: "UnaryNot".into(),
|
||||
description: "Negates a value or expression.".into(),
|
||||
precedence: 0,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Math".into(),
|
||||
operator: "+".into(),
|
||||
name: "Plus".into(),
|
||||
description: "Adds two values.".into(),
|
||||
precedence: 90,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Math".into(),
|
||||
operator: "++".into(),
|
||||
name: "Append".into(),
|
||||
description: "Appends two lists or a list and a value.".into(),
|
||||
precedence: 80,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Math".into(),
|
||||
operator: "-".into(),
|
||||
name: "Minus".into(),
|
||||
description: "Subtracts two values.".into(),
|
||||
precedence: 90,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Math".into(),
|
||||
operator: "*".into(),
|
||||
name: "Multiply".into(),
|
||||
description: "Multiplies two values.".into(),
|
||||
precedence: 95,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Math".into(),
|
||||
operator: "/".into(),
|
||||
name: "Divide".into(),
|
||||
description: "Divides two values.".into(),
|
||||
precedence: 95,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Math".into(),
|
||||
operator: "//".into(),
|
||||
name: "FloorDivision".into(),
|
||||
description: "Divides two values and floors the result.".into(),
|
||||
precedence: 95,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Math".into(),
|
||||
operator: "mod".into(),
|
||||
name: "Modulo".into(),
|
||||
description: "Divides two values and returns the remainder.".into(),
|
||||
precedence: 95,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Math".into(),
|
||||
operator: "**".into(),
|
||||
name: "Pow ".into(),
|
||||
description: "Raises one value to the power of another.".into(),
|
||||
precedence: 100,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Bitwise".into(),
|
||||
operator: "bit-or".into(),
|
||||
name: "BitOr".into(),
|
||||
description: "Performs a bitwise OR on two values.".into(),
|
||||
precedence: 60,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Bitwise".into(),
|
||||
operator: "bit-xor".into(),
|
||||
name: "BitXor".into(),
|
||||
description: "Performs a bitwise XOR on two values.".into(),
|
||||
precedence: 70,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Bitwise".into(),
|
||||
operator: "bit-and".into(),
|
||||
name: "BitAnd".into(),
|
||||
description: "Performs a bitwise AND on two values.".into(),
|
||||
precedence: 75,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Bitwise".into(),
|
||||
operator: "bit-shl".into(),
|
||||
name: "ShiftLeft".into(),
|
||||
description: "Shifts a value left by another.".into(),
|
||||
precedence: 85,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Bitwise".into(),
|
||||
operator: "bit-shr".into(),
|
||||
name: "ShiftRight".into(),
|
||||
description: "Shifts a value right by another.".into(),
|
||||
precedence: 85,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Boolean".into(),
|
||||
operator: "and".into(),
|
||||
name: "And".into(),
|
||||
description: "Checks if two values are true.".into(),
|
||||
precedence: 50,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Boolean".into(),
|
||||
operator: "or".into(),
|
||||
name: "Or".into(),
|
||||
description: "Checks if either value is true.".into(),
|
||||
precedence: 40,
|
||||
},
|
||||
OperatorInfo {
|
||||
op_type: "Boolean".into(),
|
||||
operator: "xor".into(),
|
||||
name: "Xor".into(),
|
||||
description: "Checks if one value is true and the other is false.".into(),
|
||||
precedence: 45,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use super::HelpOperators;
|
||||
use crate::test_examples;
|
||||
test_examples(HelpOperators {})
|
||||
}
|
||||
}
|
64
crates/nu-cmd-lang/src/core_commands/hide.rs
Normal file
64
crates/nu-cmd-lang/src/core_commands/hide.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Hide;
|
||||
|
||||
impl Command for Hide {
|
||||
fn name(&self) -> &str {
|
||||
"hide"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("hide")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("module", SyntaxShape::String, "Module or module file")
|
||||
.optional(
|
||||
"members",
|
||||
SyntaxShape::Any,
|
||||
"Which members of the module to import",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Hide definitions in the current scope."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"Definitions are hidden by priority: First aliases, then custom commands.
|
||||
|
||||
This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Hide the alias just defined",
|
||||
example: r#"alias lll = ls -l; hide lll"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Hide a custom command",
|
||||
example: r#"def say-hi [] { echo 'Hi!' }; hide say-hi"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
79
crates/nu-cmd-lang/src/core_commands/hide_env.rs
Normal file
79
crates/nu-cmd-lang/src/core_commands/hide_env.rs
Normal file
@ -0,0 +1,79 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
did_you_mean, Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HideEnv;
|
||||
|
||||
impl Command for HideEnv {
|
||||
fn name(&self) -> &str {
|
||||
"hide-env"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("hide-env")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.rest(
|
||||
"name",
|
||||
SyntaxShape::String,
|
||||
"environment variable names to hide",
|
||||
)
|
||||
.switch(
|
||||
"ignore-errors",
|
||||
"do not throw an error if an environment variable was not found",
|
||||
Some('i'),
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Hide environment variables in the current scope."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let env_var_names: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||
let ignore_errors = call.has_flag("ignore-errors");
|
||||
|
||||
for name in env_var_names {
|
||||
if !stack.remove_env_var(engine_state, &name.item) && !ignore_errors {
|
||||
let all_names: Vec<String> = stack
|
||||
.get_env_var_names(engine_state)
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
if let Some(closest_match) = did_you_mean(&all_names, &name.item) {
|
||||
return Err(ShellError::DidYouMeanCustom(
|
||||
format!("Environment variable '{}' not found", name.item),
|
||||
closest_match,
|
||||
name.span,
|
||||
));
|
||||
} else {
|
||||
return Err(ShellError::EnvVarNotFoundAtRuntime {
|
||||
envvar_name: name.item,
|
||||
span: name.span,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Hide an environment variable",
|
||||
example: r#"let-env HZ_ENV_ABC = 1; hide-env HZ_ENV_ABC; 'HZ_ENV_ABC' in (env).name"#,
|
||||
result: Some(Value::test_bool(false)),
|
||||
}]
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
use nu_engine::{eval_block, eval_expression, eval_expression_with_input, CallExt};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
|
||||
use nu_protocol::engine::{Block, Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, FromValue, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -19,15 +19,22 @@ impl Command for If {
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("if")
|
||||
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||
.required("cond", SyntaxShape::Expression, "condition to check")
|
||||
.required(
|
||||
"then_block",
|
||||
SyntaxShape::Block(Some(vec![])),
|
||||
SyntaxShape::Block,
|
||||
"block to run if check succeeds",
|
||||
)
|
||||
.optional(
|
||||
"else_expression",
|
||||
SyntaxShape::Keyword(b"else".to_vec(), Box::new(SyntaxShape::Expression)),
|
||||
SyntaxShape::Keyword(
|
||||
b"else".to_vec(),
|
||||
Box::new(SyntaxShape::OneOf(vec![
|
||||
SyntaxShape::Block,
|
||||
SyntaxShape::Expression,
|
||||
])),
|
||||
),
|
||||
"expression or block to run if check fails",
|
||||
)
|
||||
.category(Category::Core)
|
||||
@ -35,7 +42,7 @@ impl Command for If {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -48,9 +55,9 @@ impl Command for If {
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let cond = call.positional_nth(0).expect("checked through parser");
|
||||
let then_block: CaptureBlock = call.req(engine_state, stack, 1)?;
|
||||
let then_block: Block = call.req(engine_state, stack, 1)?;
|
||||
let else_case = call.positional_nth(2);
|
||||
|
||||
let result = eval_expression(engine_state, stack, cond)?;
|
||||
@ -58,10 +65,9 @@ impl Command for If {
|
||||
Value::Bool { val, .. } => {
|
||||
if *val {
|
||||
let block = engine_state.get_block(then_block.block_id);
|
||||
let mut stack = stack.captures_to_stack(&then_block.captures);
|
||||
eval_block(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
stack,
|
||||
block,
|
||||
input,
|
||||
call.redirect_stdout,
|
||||
@ -70,14 +76,10 @@ impl Command for If {
|
||||
} else if let Some(else_case) = else_case {
|
||||
if let Some(else_expr) = else_case.as_keyword() {
|
||||
if let Some(block_id) = else_expr.as_block() {
|
||||
let result = eval_expression(engine_state, stack, else_expr)?;
|
||||
let else_block: CaptureBlock = FromValue::from_value(&result)?;
|
||||
|
||||
let mut stack = stack.captures_to_stack(&else_block.captures);
|
||||
let block = engine_state.get_block(block_id);
|
||||
eval_block(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
stack,
|
||||
block,
|
||||
input,
|
||||
call.redirect_stdout,
|
||||
@ -92,6 +94,7 @@ impl Command for If {
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
)
|
||||
.map(|res| res.0)
|
||||
}
|
||||
} else {
|
||||
eval_expression_with_input(
|
||||
@ -102,17 +105,18 @@ impl Command for If {
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
)
|
||||
.map(|res| res.0)
|
||||
}
|
||||
} else {
|
||||
Ok(PipelineData::new(call.head))
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
}
|
||||
x => Err(ShellError::CantConvert(
|
||||
"bool".into(),
|
||||
x.get_type().to_string(),
|
||||
result.span()?,
|
||||
None,
|
||||
)),
|
||||
x => Err(ShellError::CantConvert {
|
||||
to_type: "bool".into(),
|
||||
from_type: x.get_type().to_string(),
|
||||
span: result.span()?,
|
||||
help: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Ignore;
|
||||
@ -11,11 +11,17 @@ impl Command for Ignore {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Ignore the output of the previous command in the pipeline"
|
||||
"Ignore the output of the previous command in the pipeline."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("ignore").category(Category::Core)
|
||||
Signature::build("ignore")
|
||||
.input_output_types(vec![(Type::Any, Type::Nothing)])
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["silent", "quiet", "out-null"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -24,16 +30,16 @@ impl Command for Ignore {
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
input.into_value(call.head);
|
||||
Ok(PipelineData::new(call.head))
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Ignore the output of an echo command",
|
||||
example: "echo done | ignore",
|
||||
result: None,
|
||||
result: Some(Value::nothing(Span::test_data())),
|
||||
}]
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use nu_engine::eval_expression_with_input;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Let;
|
||||
@ -17,6 +17,8 @@ impl Command for Let {
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("let")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.allow_variants_without_examples(true)
|
||||
.required("var_name", SyntaxShape::VarWithOptType, "variable name")
|
||||
.required(
|
||||
"initial_value",
|
||||
@ -28,20 +30,24 @@ impl Command for Let {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["set", "const"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let var_id = call
|
||||
.positional_nth(0)
|
||||
.expect("checked through parser")
|
||||
@ -54,7 +60,7 @@ impl Command for Let {
|
||||
.as_keyword()
|
||||
.expect("internal error: missing keyword");
|
||||
|
||||
let rhs = eval_expression_with_input(
|
||||
let (rhs, external_failed) = eval_expression_with_input(
|
||||
engine_state,
|
||||
stack,
|
||||
keyword_expr,
|
||||
@ -62,11 +68,14 @@ impl Command for Let {
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
)?;
|
||||
|
||||
//println!("Adding: {:?} to {}", rhs, var_id);
|
||||
|
||||
stack.add_var(var_id, rhs.into_value(call.head));
|
||||
Ok(PipelineData::new(call.head))
|
||||
if external_failed {
|
||||
// rhs must be a PipelineData::ExternalStream and it's failed
|
||||
// return the failed stream (with a non-zero exit code) so the engine knows to stop running
|
||||
Ok(rhs)
|
||||
} else {
|
||||
stack.add_var(var_id, rhs.into_value(call.head));
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -92,6 +101,8 @@ impl Command for Let {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@ -100,4 +111,9 @@ mod test {
|
||||
|
||||
test_examples(Let {})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_command_type() {
|
||||
assert!(matches!(Let.command_type(), CommandType::Keyword));
|
||||
}
|
||||
}
|
99
crates/nu-cmd-lang/src/core_commands/loop_.rs
Normal file
99
crates/nu-cmd-lang/src/core_commands/loop_.rs
Normal file
@ -0,0 +1,99 @@
|
||||
use nu_engine::{eval_block, CallExt};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Block, Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Loop;
|
||||
|
||||
impl Command for Loop {
|
||||
fn name(&self) -> &str {
|
||||
"loop"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Run a block in a loop."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("loop")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.allow_variants_without_examples(true)
|
||||
.required("block", SyntaxShape::Block, "block to loop")
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let block: Block = call.req(engine_state, stack, 0)?;
|
||||
|
||||
loop {
|
||||
if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) {
|
||||
break;
|
||||
}
|
||||
|
||||
let block = engine_state.get_block(block.block_id);
|
||||
match eval_block(
|
||||
engine_state,
|
||||
stack,
|
||||
block,
|
||||
PipelineData::empty(),
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
) {
|
||||
Err(ShellError::Break(_)) => {
|
||||
break;
|
||||
}
|
||||
Err(ShellError::Continue(_)) => {
|
||||
continue;
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err);
|
||||
}
|
||||
Ok(pipeline) => {
|
||||
let exit_code = pipeline.print(engine_state, stack, false, false)?;
|
||||
if exit_code != 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Loop while a condition is true",
|
||||
example: "mut x = 0; loop { if $x > 10 { break }; $x = $x + 1 }; $x",
|
||||
result: Some(Value::test_int(11)),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(Loop {})
|
||||
}
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
mod alias;
|
||||
mod debug;
|
||||
mod break_;
|
||||
mod commandline;
|
||||
mod const_;
|
||||
mod continue_;
|
||||
mod def;
|
||||
mod def_env;
|
||||
mod describe;
|
||||
@ -10,24 +13,35 @@ mod export;
|
||||
mod export_alias;
|
||||
mod export_def;
|
||||
mod export_def_env;
|
||||
mod export_env;
|
||||
mod export_extern;
|
||||
mod export_use;
|
||||
mod extern_;
|
||||
mod for_;
|
||||
mod help;
|
||||
pub mod help;
|
||||
pub mod help_aliases;
|
||||
pub mod help_commands;
|
||||
pub mod help_modules;
|
||||
mod help_operators;
|
||||
mod hide;
|
||||
mod hide_env;
|
||||
mod if_;
|
||||
mod ignore;
|
||||
mod let_;
|
||||
mod metadata;
|
||||
mod loop_;
|
||||
mod module;
|
||||
mod mut_;
|
||||
pub(crate) mod overlay;
|
||||
mod source;
|
||||
mod return_;
|
||||
mod try_;
|
||||
mod use_;
|
||||
mod version;
|
||||
mod while_;
|
||||
|
||||
pub use alias::Alias;
|
||||
pub use debug::Debug;
|
||||
pub use break_::Break;
|
||||
pub use commandline::Commandline;
|
||||
pub use const_::Const;
|
||||
pub use continue_::Continue;
|
||||
pub use def::Def;
|
||||
pub use def_env::DefEnv;
|
||||
pub use describe::Describe;
|
||||
@ -38,23 +52,31 @@ pub use export::ExportCommand;
|
||||
pub use export_alias::ExportAlias;
|
||||
pub use export_def::ExportDef;
|
||||
pub use export_def_env::ExportDefEnv;
|
||||
pub use export_env::ExportEnv;
|
||||
pub use export_extern::ExportExtern;
|
||||
pub use export_use::ExportUse;
|
||||
pub use extern_::Extern;
|
||||
pub use for_::For;
|
||||
pub use help::Help;
|
||||
pub use help_aliases::HelpAliases;
|
||||
pub use help_commands::HelpCommands;
|
||||
pub use help_modules::HelpModules;
|
||||
pub use help_operators::HelpOperators;
|
||||
pub use hide::Hide;
|
||||
pub use hide_env::HideEnv;
|
||||
pub use if_::If;
|
||||
pub use ignore::Ignore;
|
||||
pub use let_::Let;
|
||||
pub use metadata::Metadata;
|
||||
pub use loop_::Loop;
|
||||
pub use module::Module;
|
||||
pub use mut_::Mut;
|
||||
pub use overlay::*;
|
||||
pub use source::Source;
|
||||
pub use return_::Return;
|
||||
pub use try_::Try;
|
||||
pub use use_::Use;
|
||||
pub use version::Version;
|
||||
#[cfg(feature = "plugin")]
|
||||
pub use while_::While;
|
||||
//#[cfg(feature = "plugin")]
|
||||
mod register;
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
//#[cfg(feature = "plugin")]
|
||||
pub use register::Register;
|
@ -1,6 +1,8 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Value};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Module;
|
||||
@ -11,23 +13,20 @@ impl Command for Module {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Define a custom module"
|
||||
"Define a custom module."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("module")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("module_name", SyntaxShape::String, "module name")
|
||||
.required(
|
||||
"block",
|
||||
SyntaxShape::Block(Some(vec![])),
|
||||
"body of the module",
|
||||
)
|
||||
.required("block", SyntaxShape::Block, "body of the module")
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -38,10 +37,10 @@ impl Command for Module {
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
Ok(PipelineData::new(call.head))
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -49,26 +48,17 @@ impl Command for Module {
|
||||
Example {
|
||||
description: "Define a custom command in a module and call it",
|
||||
example: r#"module spam { export def foo [] { "foo" } }; use spam foo; foo"#,
|
||||
result: Some(Value::String {
|
||||
val: "foo".to_string(),
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
result: Some(Value::test_string("foo")),
|
||||
},
|
||||
Example {
|
||||
description: "Define an environment variable in a module and evaluate it",
|
||||
example: r#"module foo { export env FOO_ENV { "BAZ" } }; use foo FOO_ENV; $env.FOO_ENV"#,
|
||||
result: Some(Value::String {
|
||||
val: "BAZ".to_string(),
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
description: "Define an environment variable in a module",
|
||||
example: r#"module foo { export-env { let-env FOO = "BAZ" } }; use foo; $env.FOO"#,
|
||||
result: Some(Value::test_string("BAZ")),
|
||||
},
|
||||
Example {
|
||||
description: "Define a custom command that participates in the environment in a module and call it",
|
||||
example: r#"module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR"#,
|
||||
result: Some(Value::String {
|
||||
val: "BAZ".to_string(),
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
result: Some(Value::test_string("BAZ")),
|
||||
},
|
||||
]
|
||||
}
|
122
crates/nu-cmd-lang/src/core_commands/mut_.rs
Normal file
122
crates/nu-cmd-lang/src/core_commands/mut_.rs
Normal file
@ -0,0 +1,122 @@
|
||||
use nu_engine::eval_expression_with_input;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Mut;
|
||||
|
||||
impl Command for Mut {
|
||||
fn name(&self) -> &str {
|
||||
"mut"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Create a mutable variable and give it a value."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("mut")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.allow_variants_without_examples(true)
|
||||
.required("var_name", SyntaxShape::VarWithOptType, "variable name")
|
||||
.required(
|
||||
"initial_value",
|
||||
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
|
||||
"equals sign followed by value",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["set", "mutable"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let var_id = call
|
||||
.positional_nth(0)
|
||||
.expect("checked through parser")
|
||||
.as_var()
|
||||
.expect("internal error: missing variable");
|
||||
|
||||
let keyword_expr = call
|
||||
.positional_nth(1)
|
||||
.expect("checked through parser")
|
||||
.as_keyword()
|
||||
.expect("internal error: missing keyword");
|
||||
|
||||
let rhs = eval_expression_with_input(
|
||||
engine_state,
|
||||
stack,
|
||||
keyword_expr,
|
||||
input,
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
)?
|
||||
.0;
|
||||
|
||||
//println!("Adding: {:?} to {}", rhs, var_id);
|
||||
|
||||
stack.add_var(var_id, rhs.into_value(call.head));
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Set a mutable variable to a value, then update it",
|
||||
example: "mut x = 10; $x = 12",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Upsert a value inside a mutable data structure",
|
||||
example: "mut a = {b:{c:1}}; $a.b.c = 2",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Set a mutable variable to the result of an expression",
|
||||
example: "mut x = 10 + 100",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Set a mutable variable based on the condition",
|
||||
example: "mut x = if false { -1 } else { 1 }",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use nu_protocol::engine::CommandType;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(Mut {})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_command_type() {
|
||||
assert!(matches!(Mut.command_type(), CommandType::Keyword));
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ use nu_engine::get_full_help;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, IntoPipelineData, PipelineData, Signature, Value,
|
||||
Category, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -14,7 +14,9 @@ impl Command for Overlay {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("overlay").category(Category::Core)
|
||||
Signature::build("overlay")
|
||||
.category(Category::Core)
|
||||
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -23,7 +25,9 @@ impl Command for Overlay {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nu.html
|
||||
|
||||
You must use one of the following subcommands. Using this command as-is will only produce this help message."#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
@ -36,23 +40,17 @@ impl Command for Overlay {
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(Value::String {
|
||||
val: get_full_help(&Overlay.signature(), &[], engine_state, stack),
|
||||
val: get_full_help(
|
||||
&Overlay.signature(),
|
||||
&[],
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
),
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(Overlay {})
|
||||
}
|
||||
}
|
137
crates/nu-cmd-lang/src/core_commands/overlay/hide.rs
Normal file
137
crates/nu-cmd-lang/src/core_commands/overlay/hide.rs
Normal file
@ -0,0 +1,137 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OverlayHide;
|
||||
|
||||
impl Command for OverlayHide {
|
||||
fn name(&self) -> &str {
|
||||
"overlay hide"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Hide an active overlay."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("overlay hide")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.optional("name", SyntaxShape::String, "Overlay to hide")
|
||||
.switch(
|
||||
"keep-custom",
|
||||
"Keep all newly added commands and aliases in the next activated overlay",
|
||||
Some('k'),
|
||||
)
|
||||
.named(
|
||||
"keep-env",
|
||||
SyntaxShape::List(Box::new(SyntaxShape::String)),
|
||||
"List of environment variables to keep in the next activated overlay",
|
||||
Some('e'),
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let overlay_name: Spanned<String> = if let Some(name) = call.opt(engine_state, stack, 0)? {
|
||||
name
|
||||
} else {
|
||||
Spanned {
|
||||
item: stack.last_overlay_name()?,
|
||||
span: call.head,
|
||||
}
|
||||
};
|
||||
|
||||
if !stack.is_overlay_active(&overlay_name.item) {
|
||||
return Err(ShellError::OverlayNotFoundAtRuntime {
|
||||
overlay_name: overlay_name.item,
|
||||
span: overlay_name.span,
|
||||
});
|
||||
}
|
||||
|
||||
let keep_env: Option<Vec<Spanned<String>>> =
|
||||
call.get_flag(engine_state, stack, "keep-env")?;
|
||||
|
||||
let env_vars_to_keep = if let Some(env_var_names_to_keep) = keep_env {
|
||||
let mut env_vars_to_keep = vec![];
|
||||
|
||||
for name in env_var_names_to_keep.into_iter() {
|
||||
match stack.get_env_var(engine_state, &name.item) {
|
||||
Some(val) => env_vars_to_keep.push((name.item, val.clone())),
|
||||
None => {
|
||||
return Err(ShellError::EnvVarNotFoundAtRuntime {
|
||||
envvar_name: name.item,
|
||||
span: name.span,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
env_vars_to_keep
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
stack.remove_overlay(&overlay_name.item);
|
||||
|
||||
for (name, val) in env_vars_to_keep {
|
||||
stack.add_env_var(name, val);
|
||||
}
|
||||
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Keep a custom command after hiding the overlay",
|
||||
example: r#"module spam { export def foo [] { "foo" } }
|
||||
overlay use spam
|
||||
def bar [] { "bar" }
|
||||
overlay hide spam --keep-custom
|
||||
bar
|
||||
"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Hide an overlay created from a file",
|
||||
example: r#"'export alias f = "foo"' | save spam.nu
|
||||
overlay use spam.nu
|
||||
overlay hide spam"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Hide the last activated overlay",
|
||||
example: r#"module spam { export-env { let-env FOO = "foo" } }
|
||||
overlay use spam
|
||||
overlay hide"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Keep the current working directory when removing an overlay",
|
||||
example: r#"overlay new spam
|
||||
cd some-dir
|
||||
overlay hide --keep-env [ PWD ] spam"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
58
crates/nu-cmd-lang/src/core_commands/overlay/list.rs
Normal file
58
crates/nu-cmd-lang/src/core_commands/overlay/list.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OverlayList;
|
||||
|
||||
impl Command for OverlayList {
|
||||
fn name(&self) -> &str {
|
||||
"overlay list"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"List all active overlays."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("overlay list")
|
||||
.category(Category::Core)
|
||||
.input_output_types(vec![(Type::Nothing, Type::List(Box::new(Type::String)))])
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
"The overlays are listed in the order they were activated."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let active_overlays_engine: Vec<Value> = stack
|
||||
.active_overlays
|
||||
.iter()
|
||||
.map(|s| Value::string(s, call.head))
|
||||
.collect();
|
||||
|
||||
Ok(Value::List {
|
||||
vals: active_overlays_engine,
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get the last activated overlay",
|
||||
example: r#"module spam { export def foo [] { "foo" } }
|
||||
overlay use spam
|
||||
overlay list | last"#,
|
||||
result: Some(Value::test_string("spam")),
|
||||
}]
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user