mirror of
https://github.com/nushell/nushell.git
synced 2025-06-30 22:50:14 +02:00
Compare commits
2111 Commits
Author | SHA1 | Date | |
---|---|---|---|
390d06d4e7 | |||
89acbda877 | |||
0d40d0438f | |||
1e8212a938 | |||
2da8310b11 | |||
c16d8f0d5f | |||
2ac5b0480a | |||
4e90b478b7 | |||
3a38fb94f0 | |||
c6f6dcb57c | |||
b80299eba7 | |||
a48616697a | |||
b82dccf0bd | |||
84caf8859f | |||
be7f35246e | |||
3917fda7ed | |||
3b357e5402 | |||
79da470239 | |||
37949e70e0 | |||
5d00ecef56 | |||
6dde231dde | |||
58fa2e51a2 | |||
cf0877bf72 | |||
a0db4ce747 | |||
6ee13126f7 | |||
1c15a4ed3a | |||
7aabc381a3 | |||
8c9dced71b | |||
06d5a31301 | |||
ffbc0b0180 | |||
c0901ef707 | |||
d3e84daa49 | |||
228ede18cf | |||
c5a69271a2 | |||
dc9d939c83 | |||
32f0f94b46 | |||
a142d1a192 | |||
173d60d59d | |||
f2989bf704 | |||
575ddbd4ef | |||
ef9b72d360 | |||
25349a1eac | |||
99e4c44862 | |||
1345f97202 | |||
f02076daa8 | |||
533e04a60a | |||
13c152b00f | |||
f231a6df4a | |||
3c0bccb900 | |||
f43a65d7a7 | |||
0827ed143d | |||
4b84825dbf | |||
82ae06865c | |||
128ce6f9b7 | |||
44cbd88b55 | |||
7164929c61 | |||
848ff8453b | |||
f94ca6cfde | |||
fab3f8fd40 | |||
dbcfcdae89 | |||
08aa248c42 | |||
9f07bcc66f | |||
2caa44cea8 | |||
28c21121cf | |||
a17d46f200 | |||
6cc8402127 | |||
5f0ad1d6ad | |||
8d7bb9147e | |||
bc48b4553c | |||
28c07a5072 | |||
30c8dabeb4 | |||
8b368b6a4e | |||
8c0d60d0fb | |||
8b0a4ccf4c | |||
cfe4eff566 | |||
38f3957edf | |||
cb66d2bcad | |||
ff73623873 | |||
d1c719a8cc | |||
4d854f36af | |||
8d5848c955 | |||
fe88d58b1e | |||
42dbfd1fa0 | |||
534e1fc3ce | |||
ff946a2f21 | |||
3c0cbec993 | |||
48e29e9ed6 | |||
ff53352afe | |||
4fd4136d50 | |||
dc1248a454 | |||
de554f8e5f | |||
44979f3051 | |||
7ae7394c85 | |||
9dbf7556b8 | |||
caafd26deb | |||
43a218240c | |||
11d7d8ea1e | |||
2dea9e6f1f | |||
c5cb369d8d | |||
b6959197bf | |||
d5b99ae316 | |||
9d10007085 | |||
2e0b964d5b | |||
5bae7e56ef | |||
b42ef45c7c | |||
3423cd54a1 | |||
837f0463eb | |||
56f6f683fc | |||
c57f41e5f2 | |||
8c74b1e437 | |||
8318d59ef1 | |||
64efa30f3e | |||
820a6bfb08 | |||
b8d253cbd7 | |||
3c421c5726 | |||
75b2d26187 | |||
17a5aa3052 | |||
e4a22799d5 | |||
fda456e469 | |||
e5d38dcff6 | |||
a82fa75c31 | |||
0c16464320 | |||
888758b813 | |||
cb909f810e | |||
a75318d7e8 | |||
7a9bf06005 | |||
a06299c77a | |||
e4bcd1934d | |||
4673adecc5 | |||
1b8051ece5 | |||
d44059c36b | |||
b79abdb2a5 | |||
ee8a0c9477 | |||
41853b9f18 | |||
997d56a288 | |||
0769e9b750 | |||
f5519e2a09 | |||
8259d463aa | |||
e2c015f725 | |||
eb12fffbc6 | |||
c42096c34e | |||
46eb34b35d | |||
23a73cd31f | |||
6c07bc10e2 | |||
6365ba0286 | |||
545b1dcd94 | |||
fb89f2f48c | |||
f6ee21f76b | |||
d69a4db2e7 | |||
d4bfbb5eaf | |||
507f24d029 | |||
230c36f2fb | |||
219c719e98 | |||
50146bdef3 | |||
2042f7f769 | |||
0594f9e7aa | |||
3b8deb9ec7 | |||
727ff5f2d4 | |||
3d62528d8c | |||
a42d419b66 | |||
9602e82029 | |||
8e98df8b28 | |||
2daf8ec72d | |||
afcacda35f | |||
06cf3fa5ad | |||
9a482ce284 | |||
8018ae3286 | |||
ef322a24c5 | |||
a8db4f0b0e | |||
98a4280c41 | |||
0e1bfae13d | |||
6ff717c0ba | |||
d534a89867 | |||
5bc9246f0f | |||
1e89cc3578 | |||
06f5199570 | |||
9e5e9819d6 | |||
1f8ccd8e5e | |||
e9d8b19d4d | |||
7c63ce15d8 | |||
a3a9571dac | |||
2cc5952c37 | |||
aa88449f29 | |||
06199d731b | |||
0ba86d7eb8 | |||
6efd1bcb3f | |||
0d06b6259f | |||
8fdc272bcc | |||
0ea7a38c21 | |||
1999e0dcf3 | |||
ac30b3d108 | |||
2b1e05aad0 | |||
6c56829976 | |||
2c58beec13 | |||
9c779b071b | |||
1e94793df5 | |||
7d9a77f179 | |||
bb079608dd | |||
5fa42eeb8c | |||
3e09158afc | |||
7a78171b34 | |||
633ebc7e43 | |||
f0cb2f38df | |||
f26d3bf8d7 | |||
498672f5e5 | |||
038391519b | |||
8004e8e2a0 | |||
e192684612 | |||
5d40fc2726 | |||
a22d70718f | |||
24a49f1b0a | |||
04473a5593 | |||
d1e7884d19 | |||
2b96c93b8d | |||
fc41a0f96b | |||
8bd68416e3 | |||
2062e33c37 | |||
c6383874e9 | |||
d90b25c633 | |||
44bcfb3403 | |||
c047fd4778 | |||
16bd7b6d0d | |||
3cef94ba39 | |||
f818193b53 | |||
1aec4a343a | |||
852de79212 | |||
06f40405fe | |||
65bac77e8a | |||
32d1939a95 | |||
53e35670ea | |||
a92567489f | |||
2145feff5d | |||
0b95465ea1 | |||
ec804f4568 | |||
4717ac70fd | |||
9969fbfbb1 | |||
5f39267a80 | |||
94a9380e8b | |||
1d64863585 | |||
8218f72eea | |||
c0b99b7131 | |||
75c033e4d1 | |||
d88d057bf6 | |||
b00098ccc6 | |||
7e5e9c28dd | |||
8ffffe9bcc | |||
8030f7e9f0 | |||
e4959d2f9f | |||
f311da9623 | |||
14d80d54fe | |||
23b467061b | |||
8d8f25b210 | |||
7ee22603ac | |||
4052a99ff5 | |||
ccfa35289b | |||
54fc164e1c | |||
3a35bf7d4e | |||
a61d09222f | |||
07ac3c3aab | |||
061e9294b3 | |||
374757f286 | |||
ca75cd7c0a | |||
d08c072f19 | |||
9b99b2f6ac | |||
1cb449b2d1 | |||
6cc66c8afd | |||
08e495ea67 | |||
b0647f780d | |||
2dfd975940 | |||
fbdb125141 | |||
c2ea993f7e | |||
e14e60dd2c | |||
768ff47d28 | |||
78a1879e36 | |||
0b9c0fea9d | |||
02a3430ef0 | |||
6623ed9061 | |||
48cf103439 | |||
1bcb87c48d | |||
da104050e6 | |||
d306b834ca | |||
d4371438d1 | |||
6a972312d4 | |||
ac48f5a318 | |||
e36649f74b | |||
1a52460695 | |||
ab98ecd55b | |||
9a8e939cbe | |||
bb27b9f371 | |||
1ca3063ac3 | |||
7c9a78d922 | |||
49cbc30974 | |||
07a7bb14bf | |||
74f1c5b67b | |||
3b0151aba6 | |||
4a69819f9a | |||
1f7d3498cd | |||
f0b9dc9da1 | |||
96f8691c8d | |||
07255e576d | |||
260be40774 | |||
14c9bd44ef | |||
92785ab92c | |||
98ab31e15e | |||
80d57d70cd | |||
8dc199d817 | |||
435693a8bb | |||
5077242892 | |||
7a7aa310aa | |||
07893e01c1 | |||
f16401152b | |||
3df03e2e6d | |||
7c6f976d65 | |||
ae9c0fc138 | |||
9da2e142b2 | |||
5999506f87 | |||
1fc7abcc38 | |||
2659ea3dbd | |||
fa27110651 | |||
b4f8798a3a | |||
7714956276 | |||
8e5cc655e9 | |||
c78e28511d | |||
f189369fd7 | |||
2516305fa8 | |||
f2d7454330 | |||
3cf3329e49 | |||
d2bc2dcbb2 | |||
4ec4649903 | |||
55e5106695 | |||
5f35e4ad1e | |||
e7831d38ae | |||
5c9fe85ec4 | |||
cd5199de31 | |||
5319544481 | |||
be3f0edc97 | |||
fb8f7b114e | |||
187f2454c8 | |||
3492d4015d | |||
190f379ff3 | |||
5c2bc73d7b | |||
aeed8670f1 | |||
b38f90d4c7 | |||
9771270b38 | |||
f6b99b2d8f | |||
ec611526ac | |||
cd2df83ddc | |||
3eb447030b | |||
f2a45b3eac | |||
e94d13da1b | |||
c20ba95885 | |||
8eab311565 | |||
e2b510b65e | |||
e6a70f9846 | |||
667eb27d1b | |||
b9eb213f36 | |||
cc78446ffd | |||
5ff2ae628b | |||
661283c4d2 | |||
ee29a15119 | |||
2a18206771 | |||
a26272b44b | |||
7e730e28bb | |||
96253c69fb | |||
ded9d1cedb | |||
d1cc70fc4a | |||
18c9b62b00 | |||
1295495758 | |||
e97ba9b74c | |||
09b972f1dc | |||
0fb6f8f93c | |||
995d8db1fe | |||
7e97be1dd4 | |||
b501db673a | |||
c0ce1e9057 | |||
4d7b86f278 | |||
f2d47f97da | |||
0de289f6b7 | |||
ae674bfaec | |||
76079d5183 | |||
409f1480f5 | |||
e206555d9d | |||
88ec4186ec | |||
dd1d9b7623 | |||
1314a87cb0 | |||
cf65f77b02 | |||
c9f05f074a | |||
7710317224 | |||
0a990ed105 | |||
a35b975d84 | |||
6e85b04923 | |||
4d31139a44 | |||
1bad40726d | |||
cb3276fb3b | |||
c17129a92a | |||
5bf1c98a39 | |||
13b371ab58 | |||
2a3991cfdb | |||
583b7b1821 | |||
581afc9023 | |||
8e2847431e | |||
6a1378c1bb | |||
2fe14a7a5a | |||
7490392eb9 | |||
9844e6125b | |||
56af7e8d5f | |||
dc612e7ffb | |||
1d1dbfd04c | |||
c150e11cb4 | |||
87c684c7da | |||
10792a29f7 | |||
257290acc2 | |||
cfefb65d55 | |||
3783c19d02 | |||
1f6e0255c0 | |||
b61af9a26a | |||
6e2f6c71fe | |||
f51d5789a3 | |||
933cee27ae | |||
4566c904d0 | |||
9b020c056b | |||
60b5863058 | |||
836f914163 | |||
594006cfa0 | |||
57761149f4 | |||
521e28dcdc | |||
a30930324d | |||
625e807a35 | |||
d18f34daa4 | |||
4fd73ef54a | |||
58f395989a | |||
791e8a0e59 | |||
14066ccc30 | |||
683b912263 | |||
3bac480ca0 | |||
97eb8492a3 | |||
0892a16a3d | |||
0b85938415 | |||
aaec840b91 | |||
74d0f19291 | |||
7ce570e52c | |||
3a0eded0b8 | |||
5afd45414e | |||
6ed033737d | |||
d38a3a8b4e | |||
6b4cb8b0e0 | |||
48fa25fd42 | |||
bdfad6b1de | |||
4f974efeba | |||
e86c1b118e | |||
5e177fe8e7 | |||
4129f15eb9 | |||
690ec9abfa | |||
b2c52b51b7 | |||
888369022f | |||
4409185e1b | |||
ef1934a7ee | |||
591fb4bd36 | |||
12d3e4e424 | |||
c3bed1352a | |||
3ceb39c82c | |||
13869e7d52 | |||
121a4f06fb | |||
d0e636ae7a | |||
6e7e2dbb97 | |||
d64cf1687e | |||
657b631fdc | |||
fa6ed7a40b | |||
80f21d37e0 | |||
e2cf4cc7d6 | |||
abe028f930 | |||
ef1cf7e634 | |||
1e4b33b9c6 | |||
4654f03d31 | |||
608b6f3634 | |||
a86e6ce89b | |||
c4cfbaec2d | |||
d40109f210 | |||
20be8a4987 | |||
d6dd4078b1 | |||
6649da3f5d | |||
62901573d0 | |||
80c9888f82 | |||
19c3570cf9 | |||
2cb815b7b4 | |||
a088081695 | |||
4bb95a880f | |||
9beecff736 | |||
fa0400c3f2 | |||
1d2d31580b | |||
834f993547 | |||
2193910579 | |||
f692da487e | |||
0986c61a5d | |||
6a6471b04b | |||
05f7d7d38b | |||
d89ad4fafd | |||
385bc40627 | |||
e2d24c5956 | |||
82633e2df7 | |||
31a4fc41eb | |||
79182db587 | |||
a2872b4ccc | |||
0afa18ac4a | |||
1aef3a730a | |||
e934062542 | |||
2e3b74f1b2 | |||
a87f53072a | |||
047081fa72 | |||
5586d4a0a0 | |||
911fba8a8a | |||
2873e943b3 | |||
0c9dd6a29a | |||
a4410fef40 | |||
0011f4df56 | |||
ee5064abed | |||
82e3bb0f38 | |||
319930a1b9 | |||
a64e0956cd | |||
91e17d2f9f | |||
56a546e73d | |||
cf88c8eef3 | |||
3484e0defd | |||
79e4d35f01 | |||
71dd857926 | |||
7a789d68a2 | |||
8a9cc33aac | |||
66087b01e6 | |||
19fa41b114 | |||
91cd1717e9 | |||
12b85beecc | |||
2252833917 | |||
4e9c1067fb | |||
e505e57a7a | |||
d122827a30 | |||
b007290a4e | |||
7c92791eed | |||
80769b7197 | |||
9b5dff828d | |||
90013295aa | |||
ea7c8c237e | |||
5d5b02d8dc | |||
00b67d338d | |||
d32e878868 | |||
41af2e4b30 | |||
e9f9aab79f | |||
e826540037 | |||
a435a9924c | |||
02ed15b932 | |||
81e269c483 | |||
eceae26b0a | |||
ec5fd62f9f | |||
74af31a42f | |||
1c964cdfe7 | |||
352cf31db2 | |||
66e736dab4 | |||
18067138aa | |||
bd7a506897 | |||
1d38ff071e | |||
e6a5011fdb | |||
d5f23ab592 | |||
bd5778fa24 | |||
f3bb1d11d3 | |||
285f91e67a | |||
01c1e5e8b0 | |||
d6669d3f33 | |||
3db608eb5c | |||
b293282e9b | |||
983d115bc0 | |||
5a1af4d661 | |||
4f05e9f4a6 | |||
7773c4cd4d | |||
d0cbb2d12c | |||
0986eefb64 | |||
6e69d40bb9 | |||
9db356e174 | |||
6700fbeed7 | |||
ca12f39db3 | |||
460d635ed0 | |||
1a16b9a2c4 | |||
0bd8664f33 | |||
762da0989c | |||
65baeaecd4 | |||
cb5d997adf | |||
10d805c1fa | |||
54d9fff4f2 | |||
6e65aef9bf | |||
72daf8c64e | |||
c023d4111a | |||
ff3dffd813 | |||
30bb090cd4 | |||
dfffd45bcd | |||
c73d8d5f95 | |||
0ff9cc679e | |||
b342270112 | |||
ccc85a2979 | |||
005301647a | |||
5fcc670860 | |||
90b2ec537f | |||
f3626f7c3a | |||
7debb27d78 | |||
675d30d980 | |||
2f70442165 | |||
ce690ed18f | |||
14dc662e50 | |||
ffa3aaaa56 | |||
2b3843c7c0 | |||
9abb14b5fd | |||
12bf23faa6 | |||
643cce8a6f | |||
3bdd924349 | |||
be43b3c5fc | |||
355b1d9929 | |||
0d82d7df60 | |||
8fcf51908a | |||
0835073d85 | |||
925e9f4dcb | |||
e0fac7bc72 | |||
fac086c826 | |||
088d19ad47 | |||
99f7636b03 | |||
2ac990655e | |||
4ddf24269a | |||
b73af3b8df | |||
dc0c5a9772 | |||
477f3be8df | |||
ae7c0b1097 | |||
cede9b3156 | |||
299fea8538 | |||
35ff1076f3 | |||
073f8655eb | |||
1837bf775c | |||
a3df2e5631 | |||
0a95bc7e60 | |||
a2723c2ba4 | |||
0b6b321ad6 | |||
4f43d75130 | |||
fbbbde1489 | |||
7701c6b1d4 | |||
5ae5ef5146 | |||
69fd777120 | |||
fa7d66347f | |||
4aa9a18c63 | |||
1527b34d9c | |||
a4a8f5df54 | |||
32601bb352 | |||
6a1e504a50 | |||
488f81d012 | |||
bc119a5e98 | |||
9c17c73d5f | |||
cd721fc363 | |||
5b3cc73ac6 | |||
02dfb57ed1 | |||
fbb2e7136c | |||
b714e034aa | |||
e64ca97fe2 | |||
eef3de2d05 | |||
89b7f4d57c | |||
7c205d7a3a | |||
1157fcf372 | |||
eeef9f27eb | |||
47d5501f9f | |||
97b3e4a233 | |||
52f4c4ba7e | |||
7d0531d270 | |||
13f2048ffb | |||
210d25f2a0 | |||
2fd42d25b1 | |||
d90b7953dd | |||
50399c349f | |||
96a1bf5f8d | |||
fd88920a9d | |||
88d7b50e37 | |||
0da9213de6 | |||
4965f4cbf4 | |||
fef7f38da8 | |||
42f1874a3a | |||
2a89936bee | |||
ece5e7dbb7 | |||
a6a96b29cb | |||
e3100e6afd | |||
cb5c61d217 | |||
b09acdb7f9 | |||
0924975b4c | |||
d6a6c4b034 | |||
eec1730449 | |||
10364c4f22 | |||
ef70c8dbe4 | |||
0f437589fc | |||
796d4920ab | |||
7819210037 | |||
4ebbe07d27 | |||
10ceac998e | |||
446c2aab17 | |||
995757c055 | |||
799fa98411 | |||
d2bd71d2aa | |||
11bc056576 | |||
3eca43c0bb | |||
ed46f0ea17 | |||
0c3ea636fb | |||
2b377391c2 | |||
c6a3066103 | |||
977ef66356 | |||
e6570b41ca | |||
2126bef052 | |||
e8a6458f0d | |||
2b1e4dd242 | |||
70009c015d | |||
cbad648d0e | |||
3c62d27c28 | |||
2c9d8c4818 | |||
c984ce9dc9 | |||
308ab91aff | |||
c3979ef1cf | |||
784382edde | |||
feb4f5c347 | |||
21c0f7d738 | |||
4b18fdcc6e | |||
63487941fb | |||
676457acd3 | |||
f507613b38 | |||
25712760ba | |||
d054a724d1 | |||
c2bad71123 | |||
b448d1dbe1 | |||
3e8a41fbc9 | |||
31925c3d40 | |||
9888f8f298 | |||
739e403cd5 | |||
359bb6eebe | |||
6d4784a7c1 | |||
e4dcdcb254 | |||
88fa40d698 | |||
24fc9c657e | |||
6670b77b27 | |||
c0a1d18e3d | |||
2e167ea0c6 | |||
41fa1ab656 | |||
53b5012f1e | |||
07cd8f483e | |||
d1ec05b12b | |||
a2c4c92fce | |||
917886f8ad | |||
4f367a59de | |||
968427c4e9 | |||
d454fad4dc | |||
a96f8b891e | |||
5befa6f80a | |||
5bf2ffeaf5 | |||
9b2a022f5b | |||
fd22211737 | |||
2ba12afb01 | |||
6024a17a5b | |||
56aacc4852 | |||
643c5097d6 | |||
52ee1917ba | |||
9ea5a2ecd3 | |||
a32ce93c79 | |||
b92aaf0432 | |||
2ecae0ef43 | |||
aea4355d04 | |||
a6c565ed4e | |||
7163721571 | |||
965cea3af5 | |||
efd62f917f | |||
ac99ac003a | |||
3ecf17e7af | |||
da42100374 | |||
dfc478e074 | |||
28b5399fb7 | |||
3f14b75153 | |||
0f4f660759 | |||
d53eaac7a1 | |||
f085bd97f6 | |||
c893cc1485 | |||
e5bf56a7dd | |||
06f9047be4 | |||
786e4ab971 | |||
f65955ccc5 | |||
1235d516a5 | |||
dd11be03be | |||
a967854332 | |||
a5f9ad2a43 | |||
1377693f0f | |||
bccce0ab46 | |||
c7c427723b | |||
d4cd3f9578 | |||
9415352447 | |||
8f5b857fcf | |||
fa75c93765 | |||
393cb7ca6f | |||
f5f9d56c37 | |||
d50ccdf083 | |||
6e733f49bc | |||
f169a9be3b | |||
b8b2737890 | |||
d620f76a21 | |||
b64ac9eb7b | |||
5b6156687e | |||
c4e1559f89 | |||
644435bfe3 | |||
bd96ce4e9c | |||
7e6430def0 | |||
e763a8dcef | |||
df07e8e410 | |||
f824388f63 | |||
f11fa99d30 | |||
56b3fc61a3 | |||
66669d7839 | |||
5c1a1be02b | |||
9114a2d31d | |||
84f85ff9ae | |||
a743db8e8f | |||
fbaafaa459 | |||
f3d3e819fb | |||
63a2c2bc2d | |||
8c0a2d3c15 | |||
06f5affc0b | |||
7a3aeaf080 | |||
3576350b4b | |||
0fc03dbb00 | |||
4fdfc76d04 | |||
a520599fa0 | |||
77eb4c4188 | |||
e82ffc4dee | |||
6fc082f6e9 | |||
560be6e73e | |||
c5e7bccee5 | |||
73f94105a5 | |||
94a0e3060a | |||
eceb2d5106 | |||
9829e449e3 | |||
baf6348e66 | |||
cc171b6ad4 | |||
0256e42e3b | |||
48f4766a5f | |||
8ccc8e445f | |||
1fd7b9ac38 | |||
b4b7524206 | |||
328f7e92a0 | |||
fcc13224c1 | |||
926177235c | |||
85d1a681c7 | |||
968ef1e953 | |||
a16e485cce | |||
a767fa369c | |||
886ed5ab2d | |||
e16d6ae00c | |||
ba4d8ae8c3 | |||
e6db37bc82 | |||
0e5f4d88c5 | |||
2e3b2a48ee | |||
5cf91cb30d | |||
28947ff9a9 | |||
c2118e7505 | |||
e1f98c1bfd | |||
12d4c2986c | |||
f275644e13 | |||
fc88a8538b | |||
5d18e07b7d | |||
5a1d81221f | |||
43850bf20e | |||
94ab981235 | |||
f9e1c4ef50 | |||
659da3c4a4 | |||
9c7feb2b19 | |||
cf20eed7bc | |||
6d303f2ca3 | |||
b16e72f0a5 | |||
56ba57c74a | |||
baceb54660 | |||
19caef260d | |||
565be6aaef | |||
7242e52faa | |||
101f4f62a8 | |||
5fabfda57b | |||
a660720b68 | |||
d70d91e559 | |||
10c4c50f1f | |||
dbcadbc12c | |||
fdce6c49ab | |||
9259a56a28 | |||
265ee1281d | |||
a78c82d811 | |||
3ab55f7de9 | |||
84d3620d9b | |||
8a373dd554 | |||
c3e0e8eb5c | |||
de4449c3ee | |||
796b7a1962 | |||
a911b21256 | |||
80306f9ba6 | |||
2dd32c2b88 | |||
3eba90232a | |||
c4858fb202 | |||
8a93548de2 | |||
e45e8109aa | |||
709927cee4 | |||
abaeffab91 | |||
73dcec8ea1 | |||
b26acf97bd | |||
f29dbeddd7 | |||
8204cc4f28 | |||
c2f6dfa75c | |||
90f6b6aedf | |||
ece1e43238 | |||
fefd5fef12 | |||
dd2d601471 | |||
c6dad0d5eb | |||
522a53af68 | |||
1a246d141e | |||
b86c6db400 | |||
1e86af2fb9 | |||
a008f1aa80 | |||
ac0b331f00 | |||
3d3298290a | |||
e1c28cf06b | |||
2f0bbf5adb | |||
0043b9da74 | |||
b9c2bf226f | |||
cc1b784e3d | |||
cbdc0e2010 | |||
004d7b5ff0 | |||
ebaa584c5e | |||
c80a15cdfe | |||
4c9df9c7c1 | |||
96fedb47ee | |||
b1aa8f4edf | |||
d62716c83e | |||
def5869c1c | |||
76a4455255 | |||
2fbd182993 | |||
67cb720f24 | |||
a51d45b99d | |||
1fd0ddb52c | |||
060a4b3f48 | |||
95a5e9229a | |||
3c8716873e | |||
44821d9941 | |||
bffb4950c2 | |||
dc6f1c496b | |||
65ae3160ca | |||
1a25970645 | |||
9450bcb90c | |||
e91d8655c6 | |||
4c029d2545 | |||
c37f844644 | |||
86eeb4a5e7 | |||
020ad24b25 | |||
3f9fa28ae3 | |||
e11ac9f6f8 | |||
fd9e380a1e | |||
bfb9822475 | |||
9926561dd7 | |||
267ff4b0cf | |||
04395ee05c | |||
6f4b7efd3e | |||
a4421434d9 | |||
e8b8836977 | |||
78b5da8255 | |||
83ec374995 | |||
8ee619954d | |||
cdc8e67d61 | |||
285f65ba34 | |||
3023af66fd | |||
1ca3e03578 | |||
f4c0538653 | |||
69954a362d | |||
0cecaf82b1 | |||
5c749fcc63 | |||
6e44012a2f | |||
988a873466 | |||
ec94ca46bb | |||
53f41c1985 | |||
12189d417b | |||
0875d0451b | |||
62e9698b11 | |||
3d0b1ef1ce | |||
525ed7653f | |||
8a1b2d0812 | |||
d4fb95a98c | |||
f82e2fbac6 | |||
e11a030780 | |||
4e171203cc | |||
be0d221d56 | |||
fd3eec81b5 | |||
3d40e169ce | |||
bf9340ec48 | |||
310ecb79b6 | |||
89d852f76c | |||
af52def93c | |||
6a446f708d | |||
afe83104c6 | |||
b58aad5eb0 | |||
47d004ae24 | |||
446f160320 | |||
564c2dd7d1 | |||
e1272f3b73 | |||
6fa022b0a8 | |||
2df37d6ec2 | |||
0651e2b31f | |||
0ef0277882 | |||
939745ad67 | |||
f44954da68 | |||
846a048bba | |||
057bfff0cb | |||
ac07d93b02 | |||
91883bd572 | |||
69b2ed5566 | |||
b4e61a056c | |||
724cfaa890 | |||
65ef7b630b | |||
45b3592739 | |||
33ffb2c39a | |||
d4b6b4b09a | |||
54ed82a19a | |||
be8c905ca7 | |||
d2d22815fb | |||
6514a30b5d | |||
73ad862042 | |||
71feacf46c | |||
db704ebaed | |||
ff9d88887b | |||
6d554398a7 | |||
60cbb7e75d | |||
efd9c5c7c3 | |||
20eb348896 | |||
2c75aabbfc | |||
01e691c5ba | |||
ac36f32647 | |||
085a7c18cb | |||
0f85646d8e | |||
c55b6c5ed5 | |||
283a615ecc | |||
9b128b7a03 | |||
bfe3c50dce | |||
5fae96a6b1 | |||
3b4baa31b6 | |||
746641edae | |||
fa5aab8170 | |||
b78924c777 | |||
75db4a75bc | |||
8f4ee14d85 | |||
89d99db94f | |||
f9c0d223c1 | |||
21a7278259 | |||
bee5ba3deb | |||
a7241f9899 | |||
40484966c3 | |||
7c23ae5cb0 | |||
ca215c1152 | |||
2b6ce4dfe5 | |||
bc1e1aa944 | |||
1ecbebb24a | |||
82d90f4930 | |||
d0f9943709 | |||
58c5ea4937 | |||
186da4d725 | |||
47495715a6 | |||
ffb086d56f | |||
74fd78e02c | |||
160339bd1f | |||
d3bfc61524 | |||
733b2836f1 | |||
3a17b60862 | |||
7970e71bd4 | |||
b49885bb85 | |||
4860014cec | |||
d63eac69e5 | |||
38e0527083 | |||
3b467bedd9 | |||
f964ce9bc0 | |||
f016a5cb72 | |||
3478f35330 | |||
eab6b322bb | |||
8a0d2b4e32 | |||
e44789556b | |||
d39e8c15fe | |||
47544ad219 | |||
d0c280f6cc | |||
14cd798f00 | |||
cc1ae969fe | |||
3c2a336ef9 | |||
f71e16685c | |||
058738c48c | |||
affb9696c7 | |||
c158d29577 | |||
b4c72e85e1 | |||
41dbc641cc | |||
4584d69715 | |||
74dcd91cc3 | |||
8f6843c600 | |||
4d1ce6c27b | |||
857ecda050 | |||
36079f1a3d | |||
b6fcd46075 | |||
cb8b7e08a5 | |||
681e37cec6 | |||
91d807b1d2 | |||
fe5f65a247 | |||
9535e2c309 | |||
850f66aa9d | |||
354d51a3a6 | |||
c9dcd212ba | |||
ffaaa53526 | |||
f7e3d4de24 | |||
a56994ccc5 | |||
ac487dfcbc | |||
4383b372f5 | |||
7fa1ad010b | |||
5d58f68c59 | |||
f734995170 | |||
44791b5835 | |||
15b979b06e | |||
18ddcdcb97 | |||
2320987862 | |||
822309be8e | |||
15b0424d73 | |||
56ae07adb9 | |||
80649f2341 | |||
7faa4fbff4 | |||
7d1d6f075c | |||
832a801c11 | |||
c8330523c8 | |||
e94b8007c1 | |||
0c1a7459b2 | |||
384ea111eb | |||
5c94528fe2 | |||
53330c5676 | |||
1837acfc70 | |||
1dbf351425 | |||
f50f37c853 | |||
3706bef0a1 | |||
de30236f38 | |||
e1c92e90ca | |||
39f03bf5e4 | |||
e62e0fb679 | |||
89a000a572 | |||
ca6baf7a46 | |||
d603086d2f | |||
a811eee6b8 | |||
1efae6876d | |||
3522bead97 | |||
7f0921a14b | |||
b719f8d4eb | |||
29c8b826d4 | |||
ba1ff4cf6c | |||
5c83f4d405 | |||
f3c175562d | |||
c33104c4ae | |||
ef59b4aa51 | |||
3389baa392 | |||
5d3b63fa90 | |||
061c822c5d | |||
0c920f7d05 | |||
43dd0960a0 | |||
9fb12fefb0 | |||
8ba3e3570c | |||
ea6912c3f7 | |||
deeb1da359 | |||
52dba91e1a | |||
266fac910a | |||
3ad5d4af66 | |||
a93a9b9029 | |||
6a35e6b7b6 | |||
c3a16902fe | |||
fc7ed1bfe4 | |||
1609101e62 | |||
0571a6ee34 | |||
152467a858 | |||
caf73c36f2 | |||
e949658381 | |||
ff5b7e5ad2 | |||
cf5048205f | |||
c37bdcd119 | |||
038ad951da | |||
2883d6cd1e | |||
b54e9b6bfd | |||
ebf57c70e0 | |||
00bb203756 | |||
8933dde324 | |||
b3b328d19d | |||
46b86f3541 | |||
d8847f1082 | |||
ada9c742c6 | |||
6f6340186a | |||
6ba1e6172c | |||
438c2df8b6 | |||
6a0f404558 | |||
342584e5f8 | |||
efb4a9f95c | |||
bf6780967b | |||
a148ad8697 | |||
9a864b5017 | |||
17a7a85c78 | |||
89e2169521 | |||
e289630920 | |||
1d74d9c5ae | |||
aea2adc44a | |||
0450cc25e0 | |||
e9525627e6 | |||
1cbb785969 | |||
a41ae72bc1 | |||
a5c1dd0da5 | |||
04a9c8f3fd | |||
673fe2b56a | |||
930cb26e99 | |||
3701fd1d76 | |||
ee6ab17fde | |||
486f91e3a7 | |||
906c0e6bca | |||
1336acd34a | |||
2013e9300a | |||
90ddb23492 | |||
bee7ef21eb | |||
d1d1402512 | |||
c33d082ecc | |||
6f53912655 | |||
34a8a897c5 | |||
4d7dd23779 | |||
f8e6620e48 | |||
7cbeebaac1 | |||
9d7685e565 | |||
c2aa6c708d | |||
626b1b99cd | |||
4103abc685 | |||
d0119ea05d | |||
3df5e63c05 | |||
e77c6bb284 | |||
95841e3489 | |||
c2c4a1968b | |||
2e2d5ef0eb | |||
7a892ec5d7 | |||
865906e450 | |||
7319b6b168 | |||
c3b6e07de6 | |||
5c27ffa42e | |||
3dc19d4179 | |||
a8e5cb871e | |||
a7a213b3f2 | |||
512dcf0988 | |||
8d027a0617 | |||
11a781fc36 | |||
a42bbea98d | |||
c8b9913718 | |||
1fd26727c5 | |||
fdde95f675 | |||
9548e5ef5b | |||
29efbee285 | |||
22469a9cb1 | |||
03e22b071a | |||
71a8eb6f8e | |||
ddd8c3d9dc | |||
c6aff972da | |||
82aa84706e | |||
3e0c5e55b6 | |||
8a06ea133b | |||
eed22605ef | |||
8cf4402e6c | |||
df5ac9b71c | |||
bef138232c | |||
ee45755ea9 | |||
405a4e58c7 | |||
f3c8d35eb7 | |||
a28d38b05f | |||
574d7f6936 | |||
3d8394a909 | |||
349e83abd0 | |||
bf82417d52 | |||
c5297d2b64 | |||
d9bedaae2f | |||
19766556f3 | |||
687fefd791 | |||
ccd5f59314 | |||
ff673ba0ba | |||
f57d629b55 | |||
43972db131 | |||
f2aa952e86 | |||
071066b6d9 | |||
ac2afab40b | |||
99de2b1d77 | |||
45eba8b922 | |||
56307553ae | |||
2bbba3f5da | |||
34e0fd622b | |||
124561ff12 | |||
d8c721282b | |||
d2a1564b94 | |||
7cf96c6597 | |||
b8f1fea7fe | |||
3916ac4165 | |||
c17e1473db | |||
21ddfc61f4 | |||
ce4d9dc7c6 | |||
414ed4877a | |||
5de12da765 | |||
bab8f6bd28 | |||
ee239a0d37 | |||
e07ce57423 | |||
6d58e2b51e | |||
c8b16c14d5 | |||
e1e7e94261 | |||
8c0fa0d26e | |||
f7f8b0dbff | |||
63c3d19c67 | |||
0ba0daa2c4 | |||
5d88ed6c75 | |||
f052b3313d | |||
8043516d75 | |||
6a1942b18f | |||
76019f434e | |||
a2aaeb38ed | |||
143855b662 | |||
d30dfc63c4 | |||
250743f60f | |||
00aac850fd | |||
e01e73cb67 | |||
ff43ca4d24 | |||
88988dc9f4 | |||
aa7226d5f6 | |||
adb7eeb740 | |||
96bdcc4ff7 | |||
f8f437b060 | |||
b35914bd17 | |||
6fbe02eb21 | |||
5459d30a24 | |||
ab22619f4a | |||
42367ddf6d | |||
e324c1a078 | |||
4fd020ab7f | |||
50cbd16ec7 | |||
be827e5628 | |||
f1b2ab0b27 | |||
0f107b2830 | |||
e76451866d | |||
08d316f6a7 | |||
14a2918bba | |||
db2bca56c9 | |||
e756a9ea04 | |||
568e566adf | |||
586c6d9fa8 | |||
f5b20f0e3b | |||
75cfee28b2 | |||
d094f654c3 | |||
bb1740d733 | |||
0f516a0830 | |||
ef20b5f1ef | |||
e1468c0440 | |||
6f4993618d | |||
2103294d11 | |||
9c3c7b82c8 | |||
0a20052799 | |||
34617fabd9 | |||
47628946b6 | |||
ce714f098f | |||
066afb059e | |||
e9a7def183 | |||
e0a26cd048 | |||
b5bade6187 | |||
fcee3c65bd | |||
19645575d6 | |||
dd6452dfaa | |||
cfd40ffaf5 | |||
00a8752c76 | |||
7e070e2e5b | |||
573cb38bab | |||
a1f141d18a | |||
6c31377c21 | |||
d401ed64ed | |||
02b8027749 | |||
c7d159a0f3 | |||
345b51b272 | |||
5837cdb3f1 | |||
183d200b9f | |||
8c43f60e2e | |||
f8e48aa0af | |||
44fad9e698 | |||
14f30287f1 | |||
ae1109139d | |||
1d356276c2 | |||
4a1df604c9 | |||
d23929fc80 | |||
cfd24bc2ad | |||
f6d7df5a45 | |||
2b03748681 | |||
1949ba080e | |||
260838e5ea | |||
112ebe1842 | |||
47ebde4087 | |||
bfae75ca2e | |||
806cd4851f | |||
ea27300ca0 | |||
d3e5c5a342 | |||
5ae823612f | |||
20f3b8b274 | |||
6906de7c48 | |||
bf6c3e53a0 | |||
af5799c702 | |||
756773a6ed | |||
8192ba8d88 | |||
86e1092785 | |||
e193bf43fb | |||
12eed1f98a | |||
d134774f4b | |||
dfb846dec6 | |||
5e42b14026 | |||
78cc3452df | |||
070067b75e | |||
52cb50b937 | |||
b53570ceaa | |||
ce54764bea | |||
6e49d0f84b | |||
e1ea0d42a9 | |||
8368b52ac7 | |||
19301751ee | |||
7b2116dc29 | |||
732ff317f0 | |||
25846d3c1e | |||
bc8d90672e | |||
d856cebebd | |||
3c1b3473ae | |||
89b8ee6ad8 | |||
4a68c989e4 | |||
e16b0e7b01 | |||
e14945fdf5 | |||
1c2741c598 | |||
89225cf55c | |||
1f4c34fa04 | |||
f4ed4fa7e3 | |||
c56a233808 | |||
468b9affde | |||
ef94c71866 | |||
43c3cfecf7 | |||
3176f60b5b | |||
ef56d482b2 | |||
304c7a0c92 | |||
8707fbee33 | |||
032356bfb7 | |||
3437dacf0b | |||
80a4a5eb28 | |||
b340672331 | |||
73ae3daf85 | |||
f182524298 | |||
b7c0ba104f | |||
7112664b3f | |||
5add6035a4 | |||
a390f66dbf | |||
fa8a0958e4 | |||
20c770370b | |||
e549c1112b | |||
da515b1c9d | |||
37f7a36123 | |||
9838154ad1 | |||
f301f686b5 | |||
2dcfecbbd7 | |||
751595e72e | |||
a160d480b1 | |||
cf3f3fde92 | |||
6e6df46469 | |||
624edce4f7 | |||
51e48bee53 | |||
d853127c2e | |||
520d9e1fb6 | |||
37150af970 | |||
bac8b8a450 | |||
40ad9acbc3 | |||
1308eb45d5 | |||
f92e9d25a5 | |||
4fc533340b | |||
c998bbbe6d | |||
c114f41545 | |||
9baf720156 | |||
4b31fe1924 | |||
656e86a7ca | |||
5d62f1a9c1 | |||
6d6b850911 | |||
b5329fe4ec | |||
78256b4923 | |||
bd6c550470 | |||
95628bef16 | |||
ca7ff37697 | |||
af02c8f6ea | |||
0f27249319 | |||
1eb93e5c07 | |||
3625324bad | |||
7e66aca18e | |||
595fc7a7f6 | |||
402a4acd7a | |||
a240aead8c | |||
75b3b3e090 | |||
5163dbb7a1 | |||
cbda1b1650 | |||
e66fd91045 | |||
a29c333cb1 | |||
0c7ec03ba0 | |||
6b14f9d6b0 | |||
29dde84394 | |||
543c566ccc | |||
9995cbc03b | |||
abb6d9f10f | |||
e039e5f6a4 | |||
9b67899f8d | |||
5455270446 | |||
11d8e6c71f | |||
2ce034d0f0 | |||
017b1d8996 | |||
a2c2a07638 | |||
3a5b943d11 | |||
766726d0fa | |||
798552b1b3 | |||
df07ed5bf6 | |||
301d1f6f87 | |||
962adf5a12 | |||
c18f0dcc84 | |||
4be61ce604 | |||
85a69c0a45 | |||
d29208dd9e | |||
f84582ca2b | |||
5d19017603 | |||
3f313da4c3 | |||
baac60a5a7 | |||
b5965ee8ef | |||
397a31e69c | |||
b6d269e90a | |||
aa5ab8a666 | |||
ab9d6b206d | |||
ffb361833b | |||
36a834c1e3 | |||
90c750285c | |||
4bb2406772 | |||
4887b5e4fd | |||
1296100d31 | |||
5a1d99cefb | |||
232790f488 | |||
297f3ba575 | |||
7bd5f887d1 | |||
66dad40092 | |||
51bea2e884 | |||
b1d7e3aa49 | |||
1c52919103 | |||
b322a12f58 | |||
11070ffbbe | |||
7ef5a7945f | |||
e330fdabb7 | |||
c9439c962b | |||
b7ad4dc78a | |||
1b745015c3 | |||
2be26127c9 | |||
68601629c0 | |||
82b0415d92 | |||
bd5009a865 | |||
5bd20e4d36 | |||
28b26ca44d | |||
b3192ddc97 | |||
8c2ae1eed1 | |||
9807b4a484 | |||
fdf6bbb6fc | |||
68b7aa9470 | |||
0d7b10fd0b | |||
9ea7cdfc33 | |||
87d57108e6 | |||
dcda7a4e50 | |||
fdd2c35fd9 | |||
ff6cc2cbdd | |||
5c46138563 | |||
ef58348ea2 | |||
e473bdb26d | |||
9b60f76d04 | |||
a760e46c1c | |||
f5ce63ad55 | |||
151bdc8910 | |||
2b99e49792 | |||
94d00b28b7 | |||
8fee0b32e7 | |||
97d7157773 | |||
ffd922f393 | |||
1ea124a65b | |||
6024a001b4 | |||
67b8438bda | |||
270e4fdd4c | |||
aea8627c30 | |||
5f14faf4b4 | |||
60f0394106 | |||
c8277a3da9 | |||
9f02307bd7 | |||
96419f168b | |||
1f45304cf9 | |||
db62bce6aa | |||
63e3552eef | |||
ce81cd6e2f | |||
0d031636a9 | |||
1a15f30eb8 | |||
6e92812cdf | |||
0676f32509 | |||
576471cc3c | |||
e0433076ff | |||
a67be074e6 | |||
dada7a9867 | |||
ea9aad9b5d | |||
38bc394a12 | |||
020143d050 | |||
d33a9549b5 | |||
c4fe190cee | |||
ba73e0eb06 | |||
0504a7a776 | |||
1b9b709dec | |||
0e36b4b1bd | |||
acb0360180 | |||
4d0a253924 | |||
c3a032950d | |||
b4344b3964 | |||
491efab09b | |||
7cafdc9675 | |||
89267df9eb | |||
ecee5a9845 | |||
77c520e10b | |||
40741254f6 | |||
0b35905ce9 | |||
beb15dcc77 | |||
97ca242634 | |||
53f3d2572c | |||
a0a63c966f | |||
d5fdfdb614 | |||
75de7f7e61 | |||
4e443b2088 | |||
9e7e8ed48f | |||
5f9ad0947d | |||
4235cf1191 | |||
357b9ccaa9 | |||
d1f0740765 | |||
29cbcb8459 | |||
7f06d6144f | |||
7db6b876ab | |||
d3bc096d47 | |||
8783cf0138 | |||
563a0b92b5 | |||
8df9ea6c68 | |||
b28f876095 | |||
5d36d37d20 | |||
789fc30bf9 | |||
2c01901fcf | |||
e4ce41ba15 | |||
a1bfa2788c | |||
8756e88e3c | |||
41366f6cc4 | |||
e3e4ae0591 | |||
9a3b0312d2 | |||
2cd1f634d0 | |||
ddea4b416d | |||
5c29a83a7a | |||
60f9fe1aa4 | |||
44fbf0fce3 | |||
4ddc953e38 | |||
6c5a99b1a5 | |||
64d83142c3 | |||
b654415494 | |||
dea9c1482b | |||
c79dca999c | |||
1b977c658c | |||
ed9fa2b4c3 | |||
42113a767a | |||
7f3f41ff14 | |||
c636c30a19 | |||
5ddf0d209d | |||
1a3a837f3e | |||
c4dabe8327 | |||
a2eba38e81 | |||
bdfe8c0888 | |||
c4977ae143 | |||
54a41c535b | |||
8550f50522 | |||
e8e1ead99d | |||
adabc839bf | |||
698f768a06 | |||
ae8b315e76 | |||
58d73d4c23 | |||
5021d61800 | |||
06d819ecc8 | |||
bb126e8e09 | |||
248decc546 | |||
2500f23fcb | |||
7eb022b58c | |||
d481d5ca96 | |||
996ee363b7 | |||
011ad2e4e6 | |||
d6d0bad7aa | |||
b35d47c500 | |||
b3b51a2ed6 | |||
c7de1ee13a | |||
cc8a470668 | |||
74d4c501a8 | |||
5cc7fbcde7 | |||
48f534cd3b | |||
8536c12bd9 | |||
8dc3ebd6e2 | |||
5da1310696 | |||
9d49618e87 | |||
7697f7bdce | |||
51a43f5617 | |||
11b40a6c31 | |||
3c843f7f61 | |||
e402adbba0 | |||
c4ea398160 | |||
27dcbe5c8a | |||
4eb43adef2 | |||
0ef0588e29 | |||
80e7a8d594 | |||
1b96da5e5b | |||
e3ce58475b | |||
31ce8c1e33 | |||
14426433aa | |||
535ece4e76 | |||
75ec0d123a | |||
c884d5ca31 | |||
f80e9d4b60 | |||
26166192e5 | |||
7c2bf68d45 | |||
6f5f1fa43a | |||
58b0e571d3 | |||
a88058006a | |||
1e1e12b027 | |||
9737d4a614 | |||
0fe525de87 | |||
4dacfaa44a | |||
b2148e32b8 | |||
e325fd114d | |||
dfd321a679 | |||
909b7d2160 | |||
2b5cc63118 | |||
75e323ee35 | |||
758fce8ae3 | |||
91090e1db1 | |||
5bf51b5a7a | |||
1d7ab28a0f | |||
eba3484611 | |||
b5ec9e0360 | |||
0cc121876b | |||
e4e1b7a11e | |||
be68b84473 | |||
ae34a34e00 | |||
81cd03626d | |||
6f4df31927 | |||
03339beae1 | |||
9a64f1bff3 | |||
63a0aa6088 | |||
a8f9e6dcc2 | |||
6b76dd7cd7 | |||
7899745c4b | |||
5843acec02 | |||
9e7285ad46 | |||
8ef16c6da6 | |||
e1a0ad2987 | |||
2d4e471052 | |||
16c60f44d5 | |||
adb92b970e | |||
6595c06598 | |||
3567bbbf32 | |||
c5e9ff5f14 | |||
2c1b074bdc | |||
fb0f83e574 | |||
891d79d2aa | |||
25b05dec9e | |||
2af8116f50 | |||
aa06a71e1f | |||
8ed6afe1e5 | |||
244289c901 | |||
7488254cca | |||
3cbf99053f | |||
93521da9d8 | |||
561feff365 | |||
1b89ccf25b | |||
5b3b74ebec | |||
a16144baf1 | |||
5a5205d5d9 | |||
503939dcbe | |||
000db46618 | |||
d6e24cceb4 | |||
99666829e0 | |||
db3e9efc4b | |||
3e232a5db8 | |||
e00755a2e9 | |||
d34e083976 | |||
8250b44ce5 | |||
f0d5e2dcf1 | |||
125c8c82c3 | |||
2b5ef1b2d7 | |||
719920fa37 | |||
3b134a1ae2 | |||
84d0e0a059 | |||
0a48bc973d | |||
0108a935ed | |||
5ccbf4df67 | |||
9ee4dc49ee | |||
756269ee8d | |||
abb0d7bd22 | |||
47421e9ca7 | |||
3f8f3ecf9a | |||
f57f7b2def | |||
9e176674a5 | |||
57a07385ac | |||
12cf1a8f83 | |||
e9f1575924 | |||
1015ea814c | |||
abac7e3795 | |||
22c6ed4718 | |||
3421a8b58b | |||
75510b172a | |||
04a8280d51 | |||
139775dcce | |||
d9c42eb194 | |||
6387401041 | |||
dadc354847 | |||
25a776c36b | |||
637e4f6e6d | |||
b12a265f1e | |||
cf60f72452 | |||
a176f12c9e | |||
a2996abd47 | |||
b8d218e65b | |||
d1da75d315 | |||
767d822cbf | |||
b4977f1515 | |||
6c589affe7 | |||
cb9db792a6 | |||
04990eeba4 | |||
772f8598dd | |||
95439f5e9c | |||
36c32e9832 | |||
660e8b5b73 | |||
5d442a287f | |||
984538555c | |||
0ccbebee7a | |||
9f9bec38e1 | |||
d1474c0691 | |||
673137be8b | |||
180dafb84c | |||
2553da3dc4 | |||
a7ecf7af90 | |||
ff1adbd346 | |||
32f39c2fb8 | |||
923330aadd | |||
c87414e462 | |||
dbfd2808ba | |||
3c18cac134 | |||
4841d62d76 | |||
e5aa8b9d3f | |||
a1d6cefdf8 | |||
d532f3f304 | |||
29771c7d23 | |||
cb0914ecb0 | |||
672dd5a868 | |||
cbe85cbeaf | |||
6731e3542d | |||
5a6aebfcb2 | |||
96af23f370 | |||
4e6b6a8902 | |||
bafc50fd5c | |||
c03324ec9c | |||
1a9247b77f | |||
22e30d5ea7 | |||
b4f918b889 | |||
7b54e5c4ab | |||
c4d1c458a2 | |||
7aa1d8ac2a | |||
b6fdf611f6 | |||
c0bad7ab23 | |||
d7a3c7522b | |||
4dfde7393b | |||
32c1f0c8d4 | |||
eb67eab122 | |||
d88e46d2d1 | |||
caa6236f1f | |||
f459f77335 | |||
66c58217af | |||
8f07f40f22 | |||
e6a2e27e33 | |||
8577d3ff41 | |||
78054a5352 | |||
ce0b5bf4ab | |||
9936946eb5 | |||
013b12a864 | |||
2f04c172fe | |||
648fe052db | |||
55aa70c88a | |||
aa7ebdc9ce | |||
9c98783917 | |||
4b8ba29cdb | |||
4749776984 | |||
47ee50072e | |||
198c884158 | |||
1d945d8ce3 | |||
2d3a56f0d3 | |||
bfd05772ef | |||
9a16a8fd06 | |||
2ea19aeac0 | |||
0794ebf5fa | |||
a8ba00b250 | |||
26d50ebcd5 | |||
0694245ccd | |||
c1194b3d1e | |||
16baf5e16a | |||
5edcf3910d | |||
abda6f148c | |||
f7333ebe58 | |||
6b2f639095 | |||
bb6781a3b1 | |||
b821b14987 | |||
56b3f119c0 | |||
4ee1776ceb | |||
90204bd0c8 | |||
2d7192e390 | |||
1e09a8e5ff | |||
85a45ccf6a | |||
d35a58e05c | |||
5605678bab | |||
ecbe7bf8d7 | |||
1e4146aec5 | |||
3990120813 | |||
fa84205e31 | |||
6dd9f05ea1 | |||
ab3820890b | |||
8e8ef83875 | |||
ed6abced5b | |||
2904002008 | |||
eccf0b9903 | |||
a8646f94ab | |||
71bbd70a57 | |||
6af3affee2 | |||
b0ab78a767 | |||
2055b83c34 | |||
e00da070fd | |||
b8e8061787 | |||
bdce34676a | |||
8f54ba10aa | |||
8db844a8d0 | |||
3b7d7861e3 | |||
f71b7e89e0 | |||
f7a19d37c6 | |||
c027a14b9b | |||
f91d0d6d65 | |||
4ce9a5c894 | |||
7191b08903 | |||
3534bd8a64 | |||
cdbd333c9b | |||
a1f7a3c17b | |||
76c92fc706 | |||
9b56221b5c | |||
3b99ce71a0 | |||
5f4cc50ce7 | |||
96b0edf9b0 | |||
9e7d96ea50 | |||
faa53de893 | |||
b930fc5d9d | |||
979faf853a | |||
aaee3a8b61 | |||
036c6a9a52 | |||
b3d287815d | |||
fda7e096cd | |||
57677a50b5 | |||
6f17695891 | |||
6ebc97dec2 | |||
56c8987e0f | |||
7ae4ca88b6 | |||
f0d469f1d4 | |||
6b4fee88c9 | |||
672fa852b3 | |||
0b412cd6b3 | |||
2794556eaa | |||
a26c42a9b6 | |||
331ccd544f | |||
d6b1ff932a | |||
26b1f022b7 | |||
ab307c8d38 | |||
a3d4794341 | |||
25c7d8ead6 | |||
2834d71a12 | |||
bf9b6d8088 | |||
d9cff4238d | |||
198a36b744 | |||
f259992b4b | |||
ca8d311c78 | |||
acc035dbef | |||
5e33b8536b | |||
74bb2af3e1 | |||
b20c4047d4 | |||
82cf6caba4 | |||
bc3f820227 | |||
12d80c2732 | |||
6c0ce95d0f | |||
750502c870 | |||
df63490266 | |||
7c8504ea24 | |||
94687a7603 | |||
e1be8f61fc | |||
3d252a9797 | |||
45683a53c9 | |||
c4c4d82bf4 | |||
4ed79614ac | |||
73f6a57b12 | |||
fcc1cd3d57 | |||
5da2ab1b7d | |||
d0be193307 | |||
b3fb106cce | |||
46d2efca13 | |||
24cd1b591c | |||
bb9e6731ea | |||
5dd5a89775 | |||
2f91aca897 | |||
35c3622405 | |||
8ab7b27d4f | |||
9e76fb2231 | |||
9c7d2ab8f2 | |||
739425431a | |||
dda6554990 | |||
2f43cc353b | |||
ceea7e5aeb | |||
579814895d | |||
7655b070df | |||
1355a5dd33 | |||
f62e3119c4 | |||
828585a312 | |||
ef4af443a5 | |||
1a3e1e0959 | |||
40004e64a6 | |||
50dc0ad207 | |||
3da4f02ffa | |||
8a2bba4efb | |||
1ba80224ad | |||
bf19918e3c | |||
38fef28c84 | |||
273f964293 | |||
d2577acccd | |||
d92e661253 | |||
cb11f042ab | |||
b82a4869d5 | |||
c2be740ad4 | |||
61258d03ad | |||
79a05d63c8 | |||
18752672d0 | |||
cdc37bb142 | |||
083dcd4541 | |||
b6f00d07e8 | |||
b0ffaf1c91 | |||
2af61bd07e | |||
1caae90c02 | |||
184125a70a | |||
7cac5bb633 | |||
53314cb8b2 | |||
b5e287e065 | |||
2eeceae613 | |||
a8366ebf15 | |||
ad48387aa0 | |||
a4bcc1ff3d | |||
fca3a6b75e | |||
6fcdc76059 | |||
3eefa6dec8 | |||
8c6feb7e80 | |||
37f8ff0efc | |||
07c22c7e81 | |||
1ac0c0bfc5 | |||
c25209eb34 | |||
4deed7c836 | |||
92f72b4103 | |||
30f54626d3 | |||
3a8206d1fb | |||
6b0b8744c1 | |||
0b8352049c | |||
c03f700662 | |||
d08f2e73d0 | |||
aa7f23e1e1 | |||
4249c5b3e0 | |||
6f1a5c8e02 | |||
03a93bd089 | |||
6aef00ecff | |||
949c6a5932 | |||
7922bb4020 | |||
697bf16f26 | |||
9916f35b22 | |||
0a6f62bc0e | |||
bc974a3e7d | |||
1aa70c50aa | |||
134b45dc03 | |||
96c0b933d9 | |||
7b51c5c49f | |||
eac02b55f6 | |||
5d4ae4a2a4 | |||
04cbef3aa8 | |||
e540f0ad26 | |||
bf1a23afcf | |||
04a6a4f860 | |||
666bee61f7 | |||
a6e0f0bb74 | |||
03ce896f6f | |||
80e0cd4e00 | |||
049477a9bd | |||
d644a8d41f | |||
e0c2074ed5 | |||
d8bf48e692 | |||
a91efc3cbd | |||
fb42c94b79 | |||
ba2e3d94eb | |||
4ef65f0983 | |||
2675ad9304 | |||
c1240f214c | |||
7f3eab418f | |||
4f89ed5d66 | |||
43fd0b6ae9 | |||
e3abadd686 | |||
3d2e227f11 | |||
29d2449fb3 |
@ -1,66 +0,0 @@
|
||||
trigger:
|
||||
- main
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
linux-stable:
|
||||
image: ubuntu-18.04
|
||||
style: 'unflagged'
|
||||
linux-minimal:
|
||||
image: ubuntu-18.04
|
||||
style: 'minimal'
|
||||
linux-extra:
|
||||
image: ubuntu-18.04
|
||||
style: 'extra'
|
||||
linux-wasm:
|
||||
image: ubuntu-18.04
|
||||
style: 'wasm'
|
||||
macos-stable:
|
||||
image: macOS-10.15
|
||||
style: 'unflagged'
|
||||
windows-stable:
|
||||
image: windows-2019
|
||||
style: 'unflagged'
|
||||
fmt:
|
||||
image: ubuntu-18.04
|
||||
style: 'fmt'
|
||||
|
||||
pool:
|
||||
vmImage: $(image)
|
||||
|
||||
steps:
|
||||
- bash: |
|
||||
set -e
|
||||
if [ -e /etc/debian_version ]
|
||||
then
|
||||
sudo apt-get -y install libxcb-composite0-dev libx11-dev
|
||||
fi
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path --default-toolchain "stable"
|
||||
echo "Installing clippy"
|
||||
rustup component add clippy --toolchain stable-x86_64-apple-darwin
|
||||
export PATH=$HOME/.cargo/bin:$PATH
|
||||
fi
|
||||
# rustup update
|
||||
# rustc -Vv
|
||||
# echo "##vso[task.prependpath]$HOME/.cargo/bin"
|
||||
# rustup component add rustfmt
|
||||
displayName: Install Rust
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all
|
||||
condition: eq(variables['style'], 'unflagged')
|
||||
displayName: Run tests
|
||||
- bash: RUSTFLAGS="-D warnings" cargo clippy --all -- -D clippy::unwrap_used -A clippy::needless_collect
|
||||
condition: eq(variables['style'], 'unflagged')
|
||||
displayName: Check clippy lints
|
||||
- bash: cd samples/wasm && npm install wasm-pack && node ./node_modules/wasm-pack/run.js build
|
||||
condition: eq(variables['style'], 'wasm')
|
||||
displayName: Wasm build
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --no-default-features --features=rustyline-support
|
||||
condition: eq(variables['style'], 'minimal')
|
||||
displayName: Run tests
|
||||
- bash: RUSTFLAGS="-D warnings" cargo test --all --features=extra
|
||||
condition: eq(variables['style'], 'extra')
|
||||
displayName: Run tests
|
||||
- bash: cargo fmt --all -- --check
|
||||
condition: eq(variables['style'], 'fmt')
|
||||
displayName: Lint
|
@ -1,6 +1,14 @@
|
||||
# use LLD as linker on Windows because it could be faster
|
||||
# for full and incremental builds compared to default
|
||||
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
#linker = "lld-link.exe"
|
||||
rustflags = ["-C", "link-args=-stack:10000000"]
|
||||
# increase the default windows stack size
|
||||
# statically link the CRT so users don't have to install it
|
||||
rustflags = ["-C", "link-args=-stack:10000000", "-C", "target-feature=+crt-static"]
|
||||
|
||||
# keeping this but commentting out in case we need them in the future
|
||||
|
||||
# set a 2 gb stack size (0x80000000 = 2147483648 bytes = 2 GB)
|
||||
# [target.x86_64-unknown-linux-gnu]
|
||||
# rustflags = ["-C", "link-args=-Wl,-z stack-size=0x80000000"]
|
||||
|
||||
# set a 2 gb stack size (0x80000000 = 2147483648 bytes = 2 GB)
|
||||
# [target.x86_64-apple-darwin]
|
||||
# rustflags = ["-C", "link-args=-Wl,-stack_size,0x80000000"]
|
||||
|
@ -1,165 +0,0 @@
|
||||
# CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/configuration-reference/ for more details
|
||||
# See https://circleci.com/docs/2.0/config-intro/#section=configuration for spec
|
||||
#
|
||||
version: 2.1
|
||||
|
||||
# Commands
|
||||
|
||||
commands:
|
||||
|
||||
pull_cache:
|
||||
description: Pulls Quay.io docker images (latest) for our cache
|
||||
parameters:
|
||||
tag:
|
||||
type: string
|
||||
default: "devel"
|
||||
steps:
|
||||
- run: echo "Tag is << parameters.tag >>"
|
||||
- run: docker pull quay.io/nushell/nu:<< parameters.tag >>
|
||||
- run: docker pull quay.io/nushell/nu-base:<< parameters.tag >>
|
||||
|
||||
orbs:
|
||||
# https://circleci.com/orbs/registry/orb/circleci/docker
|
||||
docker: circleci/docker@0.5.13
|
||||
|
||||
workflows:
|
||||
version: 2.0
|
||||
|
||||
# This builds on all pull requests to test, and ignores main
|
||||
build_without_deploy:
|
||||
jobs:
|
||||
- docker/publish:
|
||||
deploy: false
|
||||
image: nushell/nu-base
|
||||
tag: latest
|
||||
dockerfile: docker/Dockerfile.nu-base
|
||||
extra_build_args: --cache-from=quay.io/nushell/nu-base:devel
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- main
|
||||
before_build:
|
||||
- pull_cache
|
||||
after_build:
|
||||
- run:
|
||||
name: Build Multistage (smaller) container
|
||||
command: |
|
||||
docker build -f docker/Dockerfile -t quay.io/nushell/nu .
|
||||
- run:
|
||||
name: Preview Docker Tag for Nushell Build
|
||||
command: |
|
||||
DOCKER_TAG=$(docker run quay.io/nushell/nu --version | cut -d' ' -f2)
|
||||
echo "Version that would be used for Docker tag is v${DOCKER_TAG}"
|
||||
- run:
|
||||
name: Test Executable
|
||||
command: |
|
||||
docker run --rm quay.io/nushell/nu-base --help
|
||||
docker run --rm quay.io/nushell/nu --help
|
||||
|
||||
# workflow publishes to Docker Hub, with each job having different triggers
|
||||
build_with_deploy:
|
||||
jobs:
|
||||
|
||||
# Deploy versioned and latest images on tags (releases) only - builds --release.
|
||||
- docker/publish:
|
||||
image: nushell/nu-base
|
||||
registry: quay.io
|
||||
tag: latest
|
||||
dockerfile: docker/Dockerfile.nu-base
|
||||
extra_build_args: --cache-from=quay.io/nushell/nu-base:latest,quay.io/nushell/nu:latest --build-arg RELEASE=true
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /^\d+\.\d+\.\d+$/
|
||||
before_build:
|
||||
- run: docker pull quay.io/nushell/nu:latest
|
||||
- run: docker pull quay.io/nushell/nu-base:latest
|
||||
after_build:
|
||||
- run:
|
||||
name: Build Multistage (smaller) container
|
||||
command: |
|
||||
docker build -f docker/Dockerfile -t quay.io/nushell/nu .
|
||||
- run:
|
||||
name: Test Executable
|
||||
command: |
|
||||
docker run --rm quay.io/nushell/nu --help
|
||||
docker run --rm quay.io/nushell/nu-base --help
|
||||
- run:
|
||||
name: Publish Docker Tag with Nushell Version
|
||||
command: |
|
||||
DOCKER_TAG=$(docker run quay.io/nushell/nu --version | cut -d' ' -f2)
|
||||
echo "Version for Docker tag is ${DOCKER_TAG}"
|
||||
docker tag quay.io/nushell/nu-base:latest quay.io/nushell/nu-base:${DOCKER_TAG}
|
||||
docker tag quay.io/nushell/nu:latest quay.io/nushell/nu:${DOCKER_TAG}
|
||||
docker push quay.io/nushell/nu-base
|
||||
docker push quay.io/nushell/nu
|
||||
|
||||
|
||||
# publish devel to Docker Hub on merge to main (doesn't build --release)
|
||||
build_with_deploy_devel:
|
||||
jobs:
|
||||
|
||||
# Deploy devel tag on merge to main
|
||||
- docker/publish:
|
||||
image: nushell/nu-base
|
||||
registry: quay.io
|
||||
tag: devel
|
||||
dockerfile: docker/Dockerfile.nu-base
|
||||
extra_build_args: --cache-from=quay.io/nushell/nu-base:devel
|
||||
before_build:
|
||||
- pull_cache
|
||||
filters:
|
||||
branches:
|
||||
only: main
|
||||
after_build:
|
||||
- run:
|
||||
name: Build Multistage (smaller) container
|
||||
command: |
|
||||
docker build --build-arg FROMTAG=devel -f docker/Dockerfile -t quay.io/nushell/nu:devel .
|
||||
- run:
|
||||
name: Test Executable
|
||||
command: |
|
||||
docker run --rm quay.io/nushell/nu:devel --help
|
||||
docker run --rm quay.io/nushell/nu-base:devel --help
|
||||
- run:
|
||||
name: Publish Development Docker Tags
|
||||
command: |
|
||||
docker push quay.io/nushell/nu-base:devel
|
||||
docker push quay.io/nushell/nu:devel
|
||||
|
||||
nightly:
|
||||
triggers:
|
||||
- schedule:
|
||||
cron: "0 0 * * *"
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
jobs:
|
||||
- docker/publish:
|
||||
image: nushell/nu-base
|
||||
registry: quay.io
|
||||
tag: nightly
|
||||
dockerfile: docker/Dockerfile.nu-base
|
||||
extra_build_args: --cache-from=quay.io/nushell/nu-base:nightly --build-arg RELEASE=true
|
||||
before_build:
|
||||
- run: docker pull quay.io/nushell/nu:nightly
|
||||
- run: docker pull quay.io/nushell/nu-base:nightly
|
||||
after_build:
|
||||
- run:
|
||||
name: Build Multistage (smaller) container
|
||||
command: |
|
||||
docker build -f docker/Dockerfile -t quay.io/nushell/nu:nightly .
|
||||
- run:
|
||||
name: Test Executable
|
||||
command: |
|
||||
docker run --rm quay.io/nushell/nu:nightly --help
|
||||
docker run --rm quay.io/nushell/nu-base:nightly --help
|
||||
- run:
|
||||
name: Publish Nightly Nushell Containers
|
||||
command: |
|
||||
docker push quay.io/nushell/nu-base:nightly
|
||||
docker push quay.io/nushell/nu:nightly
|
@ -1 +0,0 @@
|
||||
target
|
@ -1,14 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = false
|
||||
end_of_line = lf
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -5,7 +5,7 @@ body:
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: Thank you for your bug report. We are working diligently with our community to integrate our latest code base that we call [engine-q](https://github.com/nushell/engine-q). We would like your help with this by checking to see if this bug report is still needed in engine-q. Thank you for your patience while we ready the next version of nushell.
|
||||
description: Thank you for your bug report.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
@ -38,9 +38,9 @@ body:
|
||||
id: config
|
||||
attributes:
|
||||
label: Configuration
|
||||
description: "Please run `version | pivot key value | to md --pretty` and paste the output to show OS, features, etc."
|
||||
description: "Please run `version | transpose key value | to md --pretty` and paste the output to show OS, features, etc."
|
||||
placeholder: |
|
||||
> version | pivot key value | to md --pretty
|
||||
> version | transpose key value | to md --pretty
|
||||
| key | value |
|
||||
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| version | 0.40.0 |
|
||||
|
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -5,7 +5,7 @@ body:
|
||||
id: problem
|
||||
attributes:
|
||||
label: Related problem
|
||||
description: Thank you for your feature request. We are working diligently with our community to integrate our latest code base that we call [engine-q](https://github.com/nushell/engine-q). We would like your help with this by checking to see if this feature request is still needed in engine-q. Thank you for your patience while we ready the next version of nushell.
|
||||
description: Thank you for your feature request.
|
||||
placeholder: |
|
||||
A clear and concise description of what the problem is.
|
||||
Example: I am trying to do [...] but [...]
|
||||
|
17
.github/pull_request_template.md
vendored
Normal file
17
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# Description
|
||||
|
||||
(description of your pull request here)
|
||||
|
||||
# Tests
|
||||
|
||||
Make sure you've done the following:
|
||||
|
||||
- [ ] 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.
|
||||
|
||||
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
|
183
.github/workflows/ci.yml
vendored
Normal file
183
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,183 @@
|
||||
on:
|
||||
pull_request:
|
||||
push: # Run CI on the main branch after every merge. This is important to fill the GitHub Actions cache in a way that pull requests can see it
|
||||
branches:
|
||||
- main
|
||||
|
||||
name: continuous-integration
|
||||
|
||||
jobs:
|
||||
nu-fmt-clippy:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
platform: [windows-latest, macos-latest, ubuntu-latest]
|
||||
rust:
|
||||
- stable
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
NUSHELL_CARGO_TARGET: ci
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- 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: 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
|
||||
|
||||
nu-tests:
|
||||
env:
|
||||
NUSHELL_CARGO_TARGET: ci
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
platform: [windows-latest, macos-latest, ubuntu-latest]
|
||||
style: [extra, default]
|
||||
rust:
|
||||
- stable
|
||||
include:
|
||||
- style: extra
|
||||
flags: "--features=extra"
|
||||
- style: default
|
||||
flags: ""
|
||||
exclude:
|
||||
- platform: windows-latest
|
||||
style: default
|
||||
- platform: macos-latest
|
||||
style: default
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- 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: Tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
|
||||
|
||||
python-virtualenv:
|
||||
env:
|
||||
NUSHELL_CARGO_TARGET: ci
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
rust:
|
||||
- stable
|
||||
py:
|
||||
- py
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- 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: Install Nushell
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: --path=. --profile ci --no-default-features
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- run: python -m pip install tox
|
||||
|
||||
- name: Install virtualenv
|
||||
run: |
|
||||
git clone https://github.com/kubouch/virtualenv.git && \
|
||||
cd virtualenv && \
|
||||
git checkout engine-q-update
|
||||
shell: bash
|
||||
|
||||
- name: Test Nushell in virtualenv
|
||||
run: cd virtualenv && 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
|
||||
# also helps test that the plugins build without any feature unification shenanigans
|
||||
plugins:
|
||||
env:
|
||||
NUSHELL_CARGO_TARGET: ci
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
platform: [windows-latest, macos-latest, ubuntu-latest]
|
||||
rust:
|
||||
- stable
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
- 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
|
||||
|
||||
- name: Tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --profile ci --package nu_plugin_*
|
155
.github/workflows/release-pkg.nu
vendored
Executable file
155
.github/workflows/release-pkg.nu
vendored
Executable file
@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env nu
|
||||
|
||||
# Created: 2022/05/26 19:05:20
|
||||
# Description:
|
||||
# A script to do the github release task, need nushell to be installed.
|
||||
# REF:
|
||||
# 1. https://github.com/volks73/cargo-wix
|
||||
|
||||
# The main binary file to be released
|
||||
let bin = 'nu'
|
||||
let os = $env.OS
|
||||
let target = $env.TARGET
|
||||
# Repo source dir like `/home/runner/work/nushell/nushell`
|
||||
let src = $env.GITHUB_WORKSPACE
|
||||
let flags = $env.TARGET_RUSTFLAGS
|
||||
let dist = $'($env.GITHUB_WORKSPACE)/output'
|
||||
let version = (open Cargo.toml | get package.version)
|
||||
|
||||
# $env
|
||||
|
||||
$'(char nl)Packaging ($bin) v($version) for ($target) in ($src)...'; hr-line -b
|
||||
if not ('Cargo.lock' | path exists) { cargo generate-lockfile }
|
||||
|
||||
$'Start building ($bin)...'; hr-line
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Build for Ubuntu and macOS
|
||||
# ----------------------------------------------------------------------------
|
||||
if $os in ['ubuntu-latest', 'macos-latest'] {
|
||||
if $os == 'ubuntu-latest' {
|
||||
sudo apt-get install libxcb-composite0-dev -y
|
||||
}
|
||||
if $target == 'aarch64-unknown-linux-gnu' {
|
||||
sudo apt-get install gcc-aarch64-linux-gnu -y
|
||||
let-env CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER = 'aarch64-linux-gnu-gcc'
|
||||
cargo-build-nu $flags
|
||||
} else if $target == 'armv7-unknown-linux-gnueabihf' {
|
||||
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 {
|
||||
# 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
|
||||
cargo-build-nu $flags
|
||||
}
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 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
|
||||
} else {
|
||||
cargo build --release --all --target $target --features=extra $flags
|
||||
}
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Prepare for the release archive
|
||||
# ----------------------------------------------------------------------------
|
||||
let suffix = if $os == 'windows-latest' { '.exe' }
|
||||
# nu, nu_plugin_* were all included
|
||||
let executable = $'target/($target)/release/($bin)*($suffix)'
|
||||
$'Current executable file: ($executable)'
|
||||
|
||||
cd $src; mkdir $dist;
|
||||
rm -rf $'target/($target)/release/*.d' $'target/($target)/release/nu_pretty_hex*'
|
||||
$'(char nl)All executable files:'; hr-line
|
||||
ls -f $executable
|
||||
|
||||
$'(char nl)Copying release files...'; hr-line
|
||||
cp -v README.release.txt $'($dist)/README.txt'
|
||||
[LICENSE $executable] | each {|it| cp -rv $it $dist } | flatten
|
||||
|
||||
$'(char nl)Check binary release version detail:'; hr-line
|
||||
let ver = if $os == 'windows-latest' {
|
||||
(do -i { ./output/nu.exe -c 'version' }) | str collect
|
||||
} else {
|
||||
(do -i { ./output/nu -c 'version' }) | str collect
|
||||
}
|
||||
if ($ver | str trim | empty?) {
|
||||
$'(ansi r)Incompatible nu binary...(ansi reset)'
|
||||
} else { $ver }
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 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'] {
|
||||
|
||||
$'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls
|
||||
|
||||
let archive = $'($dist)/($bin)-($version)-($target).tar.gz'
|
||||
tar czf $archive *
|
||||
print $'archive: ---> ($archive)'; ls $archive
|
||||
echo $'::set-output name=archive::($archive)'
|
||||
|
||||
} 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://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt
|
||||
|
||||
# Create Windows msi release package
|
||||
if (get-env _EXTRA_) == 'msi' {
|
||||
|
||||
let wixRelease = $'($src)/target/wix/($releaseStem).msi'
|
||||
$'(char nl)Start creating Windows msi package...'
|
||||
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 wix --no-build --nocapture --package nu --output $wixRelease
|
||||
echo $'::set-output name=archive::($wixRelease)'
|
||||
|
||||
} else {
|
||||
|
||||
$'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls
|
||||
let archive = $'($dist)/($releaseStem).zip'
|
||||
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)'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def 'cargo-build-nu' [ options: string ] {
|
||||
if ($options | str trim | empty?) {
|
||||
cargo build --release --all --target $target --features=extra,static-link-openssl
|
||||
} else {
|
||||
cargo build --release --all --target $target --features=extra,static-link-openssl $options
|
||||
}
|
||||
}
|
||||
|
||||
# Print a horizontal line marker
|
||||
def 'hr-line' [
|
||||
--blank-line(-b): bool
|
||||
] {
|
||||
print $'(ansi g)---------------------------------------------------------------------------->(ansi reset)'
|
||||
if $blank-line { char nl }
|
||||
}
|
||||
|
||||
# Get the specified env key's value or ''
|
||||
def 'get-env' [
|
||||
key: string # The key to get it's env value
|
||||
default: string = '' # The default value for an empty env
|
||||
] {
|
||||
$env | get -i $key | default $default
|
||||
}
|
528
.github/workflows/release.yml
vendored
528
.github/workflows/release.yml
vendored
@ -1,3 +1,7 @@
|
||||
#
|
||||
# REF:
|
||||
# 1. https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrixinclude
|
||||
#
|
||||
name: Create Release Draft
|
||||
|
||||
on:
|
||||
@ -5,445 +9,89 @@ on:
|
||||
push:
|
||||
tags: ["[0-9]+.[0-9]+.[0-9]+*"]
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Build Linux
|
||||
runs-on: ubuntu-latest
|
||||
all:
|
||||
name: All
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- aarch64-apple-darwin
|
||||
- x86_64-apple-darwin
|
||||
- x86_64-pc-windows-msvc
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
- aarch64-unknown-linux-gnu
|
||||
- armv7-unknown-linux-gnueabihf
|
||||
extra: ['bin']
|
||||
include:
|
||||
- target: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
target_rustflags: ''
|
||||
- target: x86_64-apple-darwin
|
||||
os: macos-latest
|
||||
target_rustflags: ''
|
||||
- target: x86_64-pc-windows-msvc
|
||||
extra: 'bin'
|
||||
os: windows-latest
|
||||
target_rustflags: ''
|
||||
- target: x86_64-pc-windows-msvc
|
||||
extra: msi
|
||||
os: windows-latest
|
||||
target_rustflags: ''
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
target_rustflags: ''
|
||||
- target: x86_64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
target_rustflags: ''
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
target_rustflags: ''
|
||||
- target: armv7-unknown-linux-gnueabihf
|
||||
os: ubuntu-latest
|
||||
target_rustflags: ''
|
||||
|
||||
runs-on: ${{matrix.os}}
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install libxcb
|
||||
run: sudo apt-get install libxcb-composite0-dev
|
||||
|
||||
- name: Set up cargo
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --all --features=extra
|
||||
|
||||
# - name: Strip binaries (nu)
|
||||
# run: strip target/release/nu
|
||||
|
||||
# - name: Strip binaries (nu_plugin_inc)
|
||||
# run: strip target/release/nu_plugin_inc
|
||||
|
||||
# - name: Strip binaries (nu_plugin_match)
|
||||
# run: strip target/release/nu_plugin_match
|
||||
|
||||
# - name: Strip binaries (nu_plugin_textview)
|
||||
# run: strip target/release/nu_plugin_textview
|
||||
|
||||
# - name: Strip binaries (nu_plugin_binaryview)
|
||||
# run: strip target/release/nu_plugin_binaryview
|
||||
|
||||
# - name: Strip binaries (nu_plugin_chart_bar)
|
||||
# run: strip target/release/nu_plugin_chart_bar
|
||||
|
||||
# - name: Strip binaries (nu_plugin_chart_line)
|
||||
# run: strip target/release/nu_plugin_chart_line
|
||||
|
||||
# - name: Strip binaries (nu_plugin_from_bson)
|
||||
# run: strip target/release/nu_plugin_from_bson
|
||||
|
||||
# - name: Strip binaries (nu_plugin_from_sqlite)
|
||||
# run: strip target/release/nu_plugin_from_sqlite
|
||||
|
||||
# - name: Strip binaries (nu_plugin_from_mp4)
|
||||
# run: strip target/release/nu_plugin_from_mp4
|
||||
|
||||
# - name: Strip binaries (nu_plugin_query_json)
|
||||
# run: strip target/release/nu_plugin_query_json
|
||||
|
||||
# - name: Strip binaries (nu_plugin_s3)
|
||||
# run: strip target/release/nu_plugin_s3
|
||||
|
||||
# - name: Strip binaries (nu_plugin_selector)
|
||||
# run: strip target/release/nu_plugin_selector
|
||||
|
||||
# - name: Strip binaries (nu_plugin_start)
|
||||
# run: strip target/release/nu_plugin_start
|
||||
|
||||
# - name: Strip binaries (nu_plugin_to_bson)
|
||||
# run: strip target/release/nu_plugin_to_bson
|
||||
|
||||
# - name: Strip binaries (nu_plugin_to_sqlite)
|
||||
# run: strip target/release/nu_plugin_to_sqlite
|
||||
|
||||
# - name: Strip binaries (nu_plugin_tree)
|
||||
# run: strip target/release/nu_plugin_tree
|
||||
|
||||
# - name: Strip binaries (nu_plugin_xpath)
|
||||
# run: strip target/release/nu_plugin_xpath
|
||||
|
||||
- name: Create output directory
|
||||
run: mkdir output
|
||||
|
||||
- name: Copy files to output
|
||||
run: |
|
||||
cp target/release/nu target/release/nu_plugin_* output/
|
||||
cp README.build.txt output/README.txt
|
||||
cp LICENSE output/LICENSE
|
||||
rm output/*.d
|
||||
rm output/nu_plugin_core_*
|
||||
rm output/nu_plugin_extra_*
|
||||
|
||||
# Note: If OpenSSL changes, this path will need to be updated
|
||||
- name: Copy OpenSSL to output
|
||||
run: cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 output/
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: linux
|
||||
path: output/*
|
||||
|
||||
macos:
|
||||
name: Build macOS
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up cargo
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --all --features=extra
|
||||
|
||||
# - name: Strip binaries (nu)
|
||||
# run: strip target/release/nu
|
||||
|
||||
# - name: Strip binaries (nu_plugin_inc)
|
||||
# run: strip target/release/nu_plugin_inc
|
||||
|
||||
# - name: Strip binaries (nu_plugin_match)
|
||||
# run: strip target/release/nu_plugin_match
|
||||
|
||||
# - name: Strip binaries (nu_plugin_textview)
|
||||
# run: strip target/release/nu_plugin_textview
|
||||
|
||||
# - name: Strip binaries (nu_plugin_binaryview)
|
||||
# run: strip target/release/nu_plugin_binaryview
|
||||
|
||||
# - name: Strip binaries (nu_plugin_chart_bar)
|
||||
# run: strip target/release/nu_plugin_chart_bar
|
||||
|
||||
# - name: Strip binaries (nu_plugin_chart_line)
|
||||
# run: strip target/release/nu_plugin_chart_line
|
||||
|
||||
# - name: Strip binaries (nu_plugin_from_bson)
|
||||
# run: strip target/release/nu_plugin_from_bson
|
||||
|
||||
# - name: Strip binaries (nu_plugin_from_sqlite)
|
||||
# run: strip target/release/nu_plugin_from_sqlite
|
||||
|
||||
# - name: Strip binaries (nu_plugin_from_mp4)
|
||||
# run: strip target/release/nu_plugin_from_mp4
|
||||
|
||||
# - name: Strip binaries (nu_plugin_query_json)
|
||||
# run: strip target/release/nu_plugin_query_json
|
||||
|
||||
# - name: Strip binaries (nu_plugin_s3)
|
||||
# run: strip target/release/nu_plugin_s3
|
||||
|
||||
# - name: Strip binaries (nu_plugin_selector)
|
||||
# run: strip target/release/nu_plugin_selector
|
||||
|
||||
# - name: Strip binaries (nu_plugin_start)
|
||||
# run: strip target/release/nu_plugin_start
|
||||
|
||||
# - name: Strip binaries (nu_plugin_to_bson)
|
||||
# run: strip target/release/nu_plugin_to_bson
|
||||
|
||||
# - name: Strip binaries (nu_plugin_to_sqlite)
|
||||
# run: strip target/release/nu_plugin_to_sqlite
|
||||
|
||||
# - name: Strip binaries (nu_plugin_tree)
|
||||
# run: strip target/release/nu_plugin_tree
|
||||
|
||||
# - name: Strip binaries (nu_plugin_xpath)
|
||||
# run: strip target/release/nu_plugin_xpath
|
||||
|
||||
- name: Create output directory
|
||||
run: mkdir output
|
||||
|
||||
- name: Copy files to output
|
||||
run: |
|
||||
cp target/release/nu target/release/nu_plugin_* output/
|
||||
cp README.build.txt output/README.txt
|
||||
cp LICENSE output/LICENSE
|
||||
rm output/*.d
|
||||
rm output/nu_plugin_core_*
|
||||
rm output/nu_plugin_extra_*
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: macos
|
||||
path: output/*
|
||||
|
||||
windows:
|
||||
name: Build Windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up cargo
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- name: Add cargo-wix subcommand
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: cargo-wix --version 0.3.1
|
||||
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --all --features=extra
|
||||
|
||||
# - name: Strip binaries (nu.exe)
|
||||
# run: strip target/release/nu.exe
|
||||
|
||||
# - name: Strip binaries (nu_plugin_inc.exe)
|
||||
# run: strip target/release/nu_plugin_inc.exe
|
||||
|
||||
# - name: Strip binaries (nu_plugin_match.exe)
|
||||
# run: strip target/release/nu_plugin_match.exe
|
||||
|
||||
# - name: Strip binaries (nu_plugin_textview.exe)
|
||||
# run: strip target/release/nu_plugin_textview.exe
|
||||
|
||||
# - name: Strip binaries (nu_plugin_binaryview.exe)
|
||||
# run: strip target/release/nu_plugin_binaryview.exe
|
||||
|
||||
# - name: Strip binaries (nu_plugin_chart_bar.exe)
|
||||
# run: strip target/release/nu_plugin_chart_bar.exe
|
||||
|
||||
# - name: Strip binaries (nu_plugin_chart_line.exe)
|
||||
# run: strip target/release/nu_plugin_chart_line.exe
|
||||
|
||||
# - name: Strip binaries (nu_plugin_from_bson.exe)
|
||||
# run: strip target/release/nu_plugin_from_bson.exe
|
||||
|
||||
# - name: Strip binaries (nu_plugin_from_sqlite.exe)
|
||||
# run: strip target/release/nu_plugin_from_sqlite.exe
|
||||
|
||||
# - name: Strip binaries (nu_plugin_from_mp4.exe)
|
||||
# run: strip target/release/nu_plugin_from_mp4.exe
|
||||
|
||||
# - name: Strip binaries (nu_plugin_query_json.exe)
|
||||
# run: strip target/release/nu_plugin_query_json.exe
|
||||
|
||||
# - name: Strip binaries (nu_plugin_s3.exe)
|
||||
# run: strip target/release/nu_plugin_s3.exe
|
||||
|
||||
# - name: Strip binaries (nu_plugin_selector.exe)
|
||||
# run: strip target/release/nu_plugin_selector.exe
|
||||
|
||||
# - name: Strip binaries (nu_plugin_start.exe)
|
||||
# run: strip target/release/nu_plugin_start.exe
|
||||
|
||||
# - name: Strip binaries (nu_plugin_to_bson.exe)
|
||||
# run: strip target/release/nu_plugin_to_bson.exe
|
||||
|
||||
# - name: Strip binaries (nu_plugin_to_sqlite.exe)
|
||||
# run: strip target/release/nu_plugin_to_sqlite.exe
|
||||
|
||||
# - name: Strip binaries (nu_plugin_tree.exe)
|
||||
# run: strip target/release/nu_plugin_tree.exe
|
||||
|
||||
# - name: Strip binaries (nu_plugin_xpath.exe)
|
||||
# run: strip target/release/nu_plugin_xpath.exe
|
||||
|
||||
- name: Create output directory
|
||||
run: mkdir output
|
||||
|
||||
- name: Download Less Binary
|
||||
run: Invoke-WebRequest -Uri "https://github.com/jftuga/less-Windows/releases/download/less-v562.0/less.exe" -OutFile "target\release\less.exe"
|
||||
|
||||
- name: Download Less License
|
||||
run: Invoke-WebRequest -Uri "https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE" -OutFile "target\release\LICENSE-for-less.txt"
|
||||
|
||||
- name: Copy files to output
|
||||
run: |
|
||||
cp target\release\nu.exe output\
|
||||
cp LICENSE output\
|
||||
cp target\release\LICENSE-for-less.txt output\
|
||||
rm target\release\nu_plugin_core_*.exe
|
||||
rm target\release\nu_plugin_extra_*.exe
|
||||
cp target\release\nu_plugin_*.exe output\
|
||||
cp README.build.txt output\README.txt
|
||||
cp target\release\less.exe output\
|
||||
# Note: If the version of `less.exe` needs to be changed, update this URL
|
||||
# Similarly, if `less.exe` is checked into the repo, copy from the local path here
|
||||
# moved this stuff down to create wix after we download less
|
||||
|
||||
- name: Create msi with wix
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: wix
|
||||
args: --no-build --nocapture --output target\wix\nushell-windows.msi
|
||||
|
||||
- name: Upload installer
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: windows-installer
|
||||
path: target\wix\nushell-windows.msi
|
||||
|
||||
- name: Upload zip
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: windows-zip
|
||||
path: output\*
|
||||
|
||||
release:
|
||||
name: Publish Release
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- linux
|
||||
- macos
|
||||
- windows
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Determine Release Info
|
||||
id: info
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
run: |
|
||||
VERSION=${GITHUB_REF##*/}
|
||||
MAJOR=${VERSION%%.*}
|
||||
MINOR=${VERSION%.*}
|
||||
MINOR=${MINOR#*.}
|
||||
PATCH=${VERSION##*.}
|
||||
echo "::set-output name=version::${VERSION}"
|
||||
echo "::set-output name=linuxdir::nu_${MAJOR}_${MINOR}_${PATCH}_linux"
|
||||
echo "::set-output name=macosdir::nu_${MAJOR}_${MINOR}_${PATCH}_macOS"
|
||||
echo "::set-output name=windowsdir::nu_${MAJOR}_${MINOR}_${PATCH}_windows"
|
||||
echo "::set-output name=innerdir::nushell-${VERSION}"
|
||||
|
||||
- name: Create Release Draft
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ steps.info.outputs.version }} Release
|
||||
draft: true
|
||||
|
||||
- name: Create Linux Directory
|
||||
run: mkdir -p ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
|
||||
|
||||
- name: Download Linux Artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: linux
|
||||
path: ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
|
||||
|
||||
- name: Restore Linux File Modes
|
||||
run: |
|
||||
chmod 755 ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}/nu*
|
||||
chmod 755 ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}/libssl*
|
||||
|
||||
- name: Create Linux tarball
|
||||
run: tar -zcvf ${{ steps.info.outputs.linuxdir }}.tar.gz ${{ steps.info.outputs.linuxdir }}
|
||||
|
||||
- name: Upload Linux Artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./${{ steps.info.outputs.linuxdir }}.tar.gz
|
||||
asset_name: ${{ steps.info.outputs.linuxdir }}.tar.gz
|
||||
asset_content_type: application/gzip
|
||||
|
||||
- name: Create macOS Directory
|
||||
run: mkdir -p ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
|
||||
|
||||
- name: Download macOS Artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: macos
|
||||
path: ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
|
||||
|
||||
- name: Restore macOS File Modes
|
||||
run: chmod 755 ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}/nu*
|
||||
|
||||
- name: Create macOS Archive
|
||||
run: zip -r ${{ steps.info.outputs.macosdir }}.zip ${{ steps.info.outputs.macosdir }}
|
||||
|
||||
- name: Upload macOS Artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./${{ steps.info.outputs.macosdir }}.zip
|
||||
asset_name: ${{ steps.info.outputs.macosdir }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Create Windows Directory
|
||||
run: mkdir -p ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
||||
|
||||
- name: Download Windows zip
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: windows-zip
|
||||
path: ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
||||
|
||||
- name: Show Windows Artifacts
|
||||
run: ls -la ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
|
||||
|
||||
- name: Create macOS Archive
|
||||
run: zip -r ${{ steps.info.outputs.windowsdir }}.zip ${{ steps.info.outputs.windowsdir }}
|
||||
|
||||
- name: Upload Windows zip
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./${{ steps.info.outputs.windowsdir }}.zip
|
||||
asset_name: ${{ steps.info.outputs.windowsdir }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Download Windows installer
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: windows-installer
|
||||
path: ./
|
||||
|
||||
- name: Upload Windows installer
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./nushell-windows.msi
|
||||
asset_name: ${{ steps.info.outputs.windowsdir }}.msi
|
||||
asset_content_type: application/x-msi
|
||||
- uses: actions/checkout@v3.0.2
|
||||
|
||||
- name: Install Rust Toolchain Components
|
||||
uses: actions-rs/toolchain@v1.0.6
|
||||
with:
|
||||
override: true
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v1
|
||||
with:
|
||||
version: 0.63.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Release Nu Binary
|
||||
id: nu
|
||||
run: nu .github/workflows/release-pkg.nu
|
||||
env:
|
||||
OS: ${{ matrix.os }}
|
||||
REF: ${{ github.ref }}
|
||||
TARGET: ${{ matrix.target }}
|
||||
_EXTRA_: ${{ matrix.extra }}
|
||||
TARGET_RUSTFLAGS: ${{ matrix.target_rustflags }}
|
||||
|
||||
# REF: https://github.com/marketplace/actions/gh-release
|
||||
- name: Publish Archive
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
with:
|
||||
draft: true
|
||||
files: ${{ steps.nu.outputs.archive }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
5
.github/workflows/winget-submission.yml
vendored
5
.github/workflows/winget-submission.yml
vendored
@ -1,6 +1,7 @@
|
||||
name: Submit Nushell package to Windows Package Manager Community Repository
|
||||
name: Submit Nushell package to Windows Package Manager Community Repository
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
@ -14,5 +15,5 @@ jobs:
|
||||
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.msi' | Select -ExpandProperty browser_download_url -First 1
|
||||
$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 }}
|
||||
|
9
.gitignore
vendored
9
.gitignore
vendored
@ -4,6 +4,7 @@
|
||||
history.txt
|
||||
tests/fixtures/nuplayground
|
||||
crates/*/target
|
||||
.mailmap
|
||||
|
||||
# Debian/Ubuntu
|
||||
debian/.debhelper/
|
||||
@ -20,3 +21,11 @@ debian/nu/
|
||||
|
||||
# VSCode's IDE items
|
||||
.vscode/*
|
||||
|
||||
# Helix configuration folder
|
||||
.helix/*
|
||||
.helix
|
||||
|
||||
# Coverage tools
|
||||
lcov.info
|
||||
tarpaulin-report.html
|
||||
|
18
.gitpod.Dockerfile
vendored
18
.gitpod.Dockerfile
vendored
@ -1,18 +0,0 @@
|
||||
FROM gitpod/workspace-full
|
||||
|
||||
# Gitpod will not rebuild Nushell's dev image unless *some* change is made to this Dockerfile.
|
||||
# To force a rebuild, simply increase this counter:
|
||||
ENV TRIGGER_REBUILD 2
|
||||
|
||||
USER gitpod
|
||||
|
||||
RUN sudo apt-get update && \
|
||||
sudo apt-get install -y \
|
||||
libssl-dev \
|
||||
libxcb-composite0-dev \
|
||||
pkg-config \
|
||||
libpython3.6 \
|
||||
rust-lldb \
|
||||
&& sudo rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV RUST_LLDB=/usr/bin/lldb-11
|
25
.gitpod.yml
25
.gitpod.yml
@ -1,25 +0,0 @@
|
||||
image:
|
||||
file: .gitpod.Dockerfile
|
||||
tasks:
|
||||
- name: Clippy
|
||||
init: cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used
|
||||
- name: Testing
|
||||
init: cargo test --all --features=stable
|
||||
- name: Build
|
||||
init: cargo build --features=stable
|
||||
- name: Nu
|
||||
init: cargo install --path . --features=stable
|
||||
command: nu
|
||||
github:
|
||||
prebuilds:
|
||||
branches: true
|
||||
pullRequestsFromForks: true
|
||||
addLabel: prebuilt-in-gitpod
|
||||
vscode:
|
||||
extensions:
|
||||
- hbenl.vscode-test-explorer@2.15.0:koqDUMWDPJzELp/hdS/lWw==
|
||||
- Swellaby.vscode-rust-test-adapter@0.11.0:Xg+YeZZQiVpVUsIkH+uiiw==
|
||||
- serayuzgur.crates@0.4.7:HMkoguLcXp9M3ud7ac3eIw==
|
||||
- belfz.search-crates-io@1.2.1:kSLnyrOhXtYPjQpKnMr4eQ==
|
||||
- bungcip.better-toml@0.3.2:3QfgGxxYtGHfJKQU7H0nEw==
|
||||
- webfreak.debug@0.24.0:1zVcRsAhewYEX3/A9xjMNw==
|
@ -1,14 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "gdb",
|
||||
"request": "launch",
|
||||
"name": "Debug Rust Code",
|
||||
"preLaunchTask": "cargo",
|
||||
"target": "${workspaceFolder}/target/debug/nu",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"valuesFormatting": "parseText"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
{
|
||||
"tasks": [
|
||||
{
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"build"
|
||||
],
|
||||
"type": "process",
|
||||
"label": "cargo",
|
||||
}
|
||||
],
|
||||
}
|
@ -1,21 +1,14 @@
|
||||
# Contributing
|
||||
|
||||
Welcome to nushell!
|
||||
|
||||
*Note: for a more complete guide see [The nu contributor book](https://www.nushell.sh/contributor-book/)*
|
||||
|
||||
For speedy contributions open it in Gitpod, nu will be pre-installed with the latest build in a VSCode like editor all from your browser.
|
||||
|
||||
[](https://gitpod.io/#https://github.com/nushell/nushell)
|
||||
Welcome to Nushell!
|
||||
|
||||
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)!
|
||||
<!--WIP-->
|
||||
|
||||
## Developing
|
||||
|
||||
### Set up
|
||||
### Setup
|
||||
|
||||
This is no different than other Rust projects.
|
||||
Nushell requires a recent Rust toolchain and some dependencies; [refer to the Nu Book for up-to-date requirements](https://www.nushell.sh/book/installation.html#build-from-source). After installing dependencies, you should be able to clone+build Nu like any other Rust project:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/nushell/nushell
|
||||
@ -28,24 +21,24 @@ cargo build
|
||||
- Build and run Nushell:
|
||||
|
||||
```shell
|
||||
cargo build --release && cargo run --release
|
||||
cargo run
|
||||
```
|
||||
|
||||
- Build and run with extra features:
|
||||
- Build and run with extra features. Currently extra features include dataframes and sqlite database support.
|
||||
```shell
|
||||
cargo build --release --features=extra && cargo run --release --features=extra
|
||||
cargo run --features=extra
|
||||
```
|
||||
|
||||
- Run Clippy on Nushell:
|
||||
|
||||
```shell
|
||||
cargo clippy --all --features=stable
|
||||
cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
|
||||
```
|
||||
|
||||
- Run all tests:
|
||||
|
||||
```shell
|
||||
cargo test --all --features=stable
|
||||
cargo test --workspace --features=extra
|
||||
```
|
||||
|
||||
- Run all tests for a specific command
|
||||
@ -71,5 +64,5 @@ cargo build
|
||||
- To view verbose logs when developing, enable the `trace` log level.
|
||||
|
||||
```shell
|
||||
cargo build --release --features=extra && cargo run --release --features=extra -- --loglevel trace
|
||||
cargo run --release --features=extra -- --log-level trace
|
||||
```
|
||||
|
3849
Cargo.lock
generated
3849
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
274
Cargo.toml
274
Cargo.toml
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
authors = ["The Nu Project Contributors"]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
default-run = "nu"
|
||||
description = "A new type of shell"
|
||||
documentation = "https://www.nushell.sh/book/"
|
||||
@ -10,218 +10,110 @@ license = "MIT"
|
||||
name = "nu"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
version = "0.43.0"
|
||||
|
||||
[workspace]
|
||||
members = ["crates/*/"]
|
||||
rust-version = "1.60"
|
||||
version = "0.65.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/nu-cli",
|
||||
"crates/nu-engine",
|
||||
"crates/nu-parser",
|
||||
"crates/nu-system",
|
||||
"crates/nu-command",
|
||||
"crates/nu-protocol",
|
||||
"crates/nu-plugin",
|
||||
"crates/nu_plugin_inc",
|
||||
"crates/nu_plugin_gstat",
|
||||
"crates/nu_plugin_example",
|
||||
"crates/nu_plugin_query",
|
||||
"crates/nu-utils",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { version = "0.43.0", path="./crates/nu-cli", default-features=false }
|
||||
nu-command = { version = "0.43.0", path="./crates/nu-command" }
|
||||
nu-completion = { version = "0.43.0", path="./crates/nu-completion" }
|
||||
nu-data = { version = "0.43.0", path="./crates/nu-data" }
|
||||
nu-engine = { version = "0.43.0", path="./crates/nu-engine" }
|
||||
nu-errors = { version = "0.43.0", path="./crates/nu-errors" }
|
||||
nu-parser = { version = "0.43.0", path="./crates/nu-parser" }
|
||||
nu-path = { version = "0.43.0", path="./crates/nu-path" }
|
||||
nu-plugin = { version = "0.43.0", path="./crates/nu-plugin" }
|
||||
nu-protocol = { version = "0.43.0", path="./crates/nu-protocol" }
|
||||
nu-source = { version = "0.43.0", path="./crates/nu-source" }
|
||||
nu-value-ext = { version = "0.43.0", path="./crates/nu-value-ext" }
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
crossterm = "0.23.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"
|
||||
is_executable = "1.0.1"
|
||||
|
||||
nu_plugin_binaryview = { version = "0.43.0", path="./crates/nu_plugin_binaryview", optional=true }
|
||||
nu_plugin_chart = { version = "0.43.0", path="./crates/nu_plugin_chart", optional=true }
|
||||
nu_plugin_from_bson = { version = "0.43.0", path="./crates/nu_plugin_from_bson", optional=true }
|
||||
nu_plugin_from_sqlite = { version = "0.43.0", path="./crates/nu_plugin_from_sqlite", optional=true }
|
||||
nu_plugin_inc = { version = "0.43.0", path="./crates/nu_plugin_inc", optional=true }
|
||||
nu_plugin_match = { version = "0.43.0", path="./crates/nu_plugin_match", optional=true }
|
||||
nu_plugin_query_json = { version = "0.43.0", path="./crates/nu_plugin_query_json", optional=true }
|
||||
nu_plugin_s3 = { version = "0.43.0", path="./crates/nu_plugin_s3", optional=true }
|
||||
nu_plugin_selector = { version = "0.43.0", path="./crates/nu_plugin_selector", optional=true }
|
||||
nu_plugin_start = { version = "0.43.0", path="./crates/nu_plugin_start", optional=true }
|
||||
nu_plugin_textview = { version = "0.43.0", path="./crates/nu_plugin_textview", optional=true }
|
||||
nu_plugin_to_bson = { version = "0.43.0", path="./crates/nu_plugin_to_bson", optional=true }
|
||||
nu_plugin_to_sqlite = { version = "0.43.0", path="./crates/nu_plugin_to_sqlite", optional=true }
|
||||
nu_plugin_tree = { version = "0.43.0", path="./crates/nu_plugin_tree", optional=true }
|
||||
nu_plugin_xpath = { version = "0.43.0", path="./crates/nu_plugin_xpath", optional=true }
|
||||
|
||||
# Required to bootstrap the main binary
|
||||
ctrlc = { version="3.1.7", optional=true }
|
||||
futures = { version="0.3.12", features=["compat", "io-compat"] }
|
||||
itertools = "0.10.0"
|
||||
[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 = { version = "0.43.0", path="./crates/nu-test-support" }
|
||||
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.10.0"
|
||||
rstest = "0.12.0"
|
||||
itertools = "0.10.3"
|
||||
|
||||
[build-dependencies]
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
winres = "0.1"
|
||||
|
||||
[features]
|
||||
fetch-support = ["nu-command/fetch", "nu-command/post"]
|
||||
sys-support = ["nu-command/sys", "nu-command/ps"]
|
||||
ctrlc-support = ["nu-cli/ctrlc", "nu-command/ctrlc"]
|
||||
rustyline-support = ["nu-cli/rustyline-support", "nu-command/rustyline-support"]
|
||||
term-support = ["nu-command/term"]
|
||||
uuid-support = ["nu-command/uuid_crate"]
|
||||
which-support = ["nu-command/which", "nu-engine/which"]
|
||||
|
||||
default = [
|
||||
"nu-cli/shadow-rs",
|
||||
"sys-support",
|
||||
"ctrlc-support",
|
||||
"which-support",
|
||||
"term-support",
|
||||
"rustyline-support",
|
||||
"match",
|
||||
"fetch-support",
|
||||
"zip-support",
|
||||
"dataframe",
|
||||
]
|
||||
|
||||
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
|
||||
default = ["plugin", "which-support", "trash-support"]
|
||||
stable = ["default"]
|
||||
extra = [
|
||||
"default",
|
||||
"binaryview",
|
||||
"inc",
|
||||
"tree",
|
||||
"textview",
|
||||
"trash-support",
|
||||
"uuid-support",
|
||||
"start",
|
||||
"bson",
|
||||
"sqlite",
|
||||
"s3",
|
||||
"chart",
|
||||
"xpath",
|
||||
"selector",
|
||||
"query-json",
|
||||
]
|
||||
|
||||
wasi = ["inc", "match", "match", "tree", "rustyline-support"]
|
||||
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"]
|
||||
|
||||
# Stable (Default)
|
||||
inc = ["nu_plugin_inc"]
|
||||
match = ["nu_plugin_match"]
|
||||
textview = ["nu_plugin_textview"]
|
||||
which-support = ["nu-command/which-support"]
|
||||
trash-support = ["nu-command/trash-support"]
|
||||
|
||||
# Extra
|
||||
binaryview = ["nu_plugin_binaryview"]
|
||||
bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
|
||||
chart = ["nu_plugin_chart"]
|
||||
query-json = ["nu_plugin_query_json"]
|
||||
s3 = ["nu_plugin_s3"]
|
||||
selector = ["nu_plugin_selector"]
|
||||
sqlite = ["nu_plugin_from_sqlite", "nu_plugin_to_sqlite"]
|
||||
start = ["nu_plugin_start"]
|
||||
trash-support = [
|
||||
"nu-command/trash-support",
|
||||
"nu-engine/trash-support",
|
||||
]
|
||||
tree = ["nu_plugin_tree"]
|
||||
xpath = ["nu_plugin_xpath"]
|
||||
zip-support = ["nu-command/zip"]
|
||||
|
||||
#dataframe feature for nushell
|
||||
dataframe = [
|
||||
"nu-engine/dataframe",
|
||||
"nu-protocol/dataframe",
|
||||
"nu-command/dataframe",
|
||||
"nu-value-ext/dataframe",
|
||||
"nu-data/dataframe",
|
||||
"nu_plugin_to_bson/dataframe",
|
||||
]
|
||||
# Dataframe feature for nushell
|
||||
dataframe = ["nu-command/dataframe"]
|
||||
|
||||
# Database commands for nushell
|
||||
database = ["nu-command/database"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = "s" # Optimize for size.
|
||||
opt-level = "s" # Optimize for size
|
||||
strip = "debuginfo"
|
||||
lto = "thin"
|
||||
|
||||
# Core plugins that ship with `cargo install nu` by default
|
||||
# Currently, Cargo limits us to installing only one binary
|
||||
# unless we use [[bin]], so we use this as a workaround
|
||||
[[bin]]
|
||||
name = "nu_plugin_core_textview"
|
||||
path = "src/plugins/nu_plugin_core_textview.rs"
|
||||
required-features = ["textview"]
|
||||
# build with `cargo build --profile profiling`
|
||||
# to analyze performance with tooling like linux perf
|
||||
[profile.profiling]
|
||||
inherits = "release"
|
||||
strip = false
|
||||
debug = true
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_core_inc"
|
||||
path = "src/plugins/nu_plugin_core_inc.rs"
|
||||
required-features = ["inc"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_core_match"
|
||||
path = "src/plugins/nu_plugin_core_match.rs"
|
||||
required-features = ["match"]
|
||||
|
||||
# Extra plugins
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_binaryview"
|
||||
path = "src/plugins/nu_plugin_extra_binaryview.rs"
|
||||
required-features = ["binaryview"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_tree"
|
||||
path = "src/plugins/nu_plugin_extra_tree.rs"
|
||||
required-features = ["tree"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_query_json"
|
||||
path = "src/plugins/nu_plugin_extra_query_json.rs"
|
||||
required-features = ["query-json"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_start"
|
||||
path = "src/plugins/nu_plugin_extra_start.rs"
|
||||
required-features = ["start"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_s3"
|
||||
path = "src/plugins/nu_plugin_extra_s3.rs"
|
||||
required-features = ["s3"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_chart_bar"
|
||||
path = "src/plugins/nu_plugin_extra_chart_bar.rs"
|
||||
required-features = ["chart"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_chart_line"
|
||||
path = "src/plugins/nu_plugin_extra_chart_line.rs"
|
||||
required-features = ["chart"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_xpath"
|
||||
path = "src/plugins/nu_plugin_extra_xpath.rs"
|
||||
required-features = ["xpath"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_selector"
|
||||
path = "src/plugins/nu_plugin_extra_selector.rs"
|
||||
required-features = ["selector"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_from_bson"
|
||||
path = "src/plugins/nu_plugin_extra_from_bson.rs"
|
||||
required-features = ["bson"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_to_bson"
|
||||
path = "src/plugins/nu_plugin_extra_to_bson.rs"
|
||||
required-features = ["bson"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_from_sqlite"
|
||||
path = "src/plugins/nu_plugin_extra_from_sqlite.rs"
|
||||
required-features = ["sqlite"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_to_sqlite"
|
||||
path = "src/plugins/nu_plugin_extra_to_sqlite.rs"
|
||||
required-features = ["sqlite"]
|
||||
# build with `cargo build --profile ci`
|
||||
# to analyze performance with tooling like linux perf
|
||||
[profile.ci]
|
||||
inherits = "dev"
|
||||
strip = false
|
||||
debug = false
|
||||
|
||||
# Main nu binary
|
||||
[[bin]]
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 - 2021 Nushell Project
|
||||
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
|
||||
|
@ -1,24 +0,0 @@
|
||||
[tasks.lalrpop]
|
||||
install_crate = { crate_name = "lalrpop", binary = "lalrpop", test_arg = "--help" }
|
||||
command = "lalrpop"
|
||||
args = ["src/parser/parser.lalrpop"]
|
||||
|
||||
[tasks.baseline]
|
||||
command = "cargo"
|
||||
args = ["build", "--bins"]
|
||||
|
||||
[tasks.run]
|
||||
command = "cargo"
|
||||
args = ["run"]
|
||||
dependencies = ["baseline"]
|
||||
|
||||
[tasks.test]
|
||||
command = "cargo"
|
||||
args = ["test"]
|
||||
dependencies = ["baseline"]
|
||||
|
||||
[tasks.check]
|
||||
command = "cargo"
|
||||
args = ["check"]
|
||||
dependencies = ["baseline"]
|
||||
|
@ -1 +0,0 @@
|
||||
Nu will look for the plugins in your PATH on startup. While nu will have some functionality without them, for full functionality you'll need to copy them into your path so they can be loaded.
|
277
README.md
277
README.md
@ -1,132 +1,106 @@
|
||||
# README
|
||||
|
||||
# Nushell <!-- omit in toc -->
|
||||
[](https://crates.io/crates/nu)
|
||||
[](https://dev.azure.com/nushell/nushell/_build/latest?definitionId=2&branchName=main)
|
||||

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

|
||||

|
||||
|
||||
## Nushell
|
||||
|
||||
A new type of shell.
|
||||
|
||||

|
||||

|
||||
|
||||
## Table of Contents <!-- omit in toc -->
|
||||
|
||||
- [Status](#status)
|
||||
- [Learning About Nu](#learning-about-nu)
|
||||
- [Installation](#installation)
|
||||
- [Philosophy](#philosophy)
|
||||
- [Pipelines](#pipelines)
|
||||
- [Opening files](#opening-files)
|
||||
- [Plugins](#plugins)
|
||||
- [Goals](#goals)
|
||||
- [Progress](#progress)
|
||||
- [Officially Supported By](#officially-supported-by)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
|
||||
## Status
|
||||
|
||||
This project has reached a minimum-viable product level of quality.
|
||||
While contributors dogfood it as their daily driver, it may be unstable for some commands.
|
||||
Future releases will work to fill out missing features and improve stability.
|
||||
Its design is also subject to change as it matures.
|
||||
This project has reached a minimum-viable-product level of quality. Many people use it as their daily driver, but it may be unstable for some commands. Nu's design is subject to change as it matures.
|
||||
|
||||
Nu comes with a set of built-in commands (listed below).
|
||||
If a command is unknown, the command will shell-out and execute it (using cmd on Windows or bash on Linux and macOS), correctly passing through stdin, stdout, and stderr, so things like your daily git workflows and even `vim` will work just fine.
|
||||
## Learning About Nu
|
||||
|
||||
## Learning more
|
||||
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/).
|
||||
|
||||
There are a few good resources to learn about Nu.
|
||||
There is a [book](https://www.nushell.sh/book/) about Nu that is currently in progress.
|
||||
The book focuses on using Nu and its core concepts.
|
||||
|
||||
If you're a developer who would like to contribute to Nu, we're also working on a [book for developers](https://www.nushell.sh/contributor-book/) to help you get started.
|
||||
There are also [good first issues](https://github.com/nushell/nushell/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) to help you dive in.
|
||||
|
||||
We also have an active [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell) if you'd like to come and chat with us.
|
||||
|
||||
You can also find information on more specific topics in our [cookbook](https://www.nushell.sh/cookbook/).
|
||||
|
||||
Try it in Gitpod.
|
||||
|
||||
[](https://gitpod.io/#https://github.com/nushell/nushell)
|
||||
We're also active on [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell); come and chat with us!
|
||||
|
||||
## Installation
|
||||
|
||||
### Local
|
||||
To quickly install Nu:
|
||||
|
||||
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
|
||||
|
||||
To build Nu, you will need to use the **latest stable (1.51 or later)** version of the compiler.
|
||||
|
||||
Required dependencies:
|
||||
|
||||
- pkg-config and libssl (only needed on Linux)
|
||||
- On Debian/Ubuntu: `apt install pkg-config libssl-dev`
|
||||
|
||||
Optional dependencies:
|
||||
|
||||
- To use Nu with all possible optional features enabled, you'll also need the following:
|
||||
- On Linux (on Debian/Ubuntu): `apt install libxcb-composite0-dev libx11-dev`
|
||||
|
||||
To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs/) and the latest stable compiler via `rustup install stable`):
|
||||
|
||||
```shell
|
||||
cargo install nu
|
||||
```
|
||||
|
||||
To install Nu via the [Windows Package Manager](https://aka.ms/winget-cli):
|
||||
|
||||
```shell
|
||||
```bash
|
||||
# Linux and macOS
|
||||
brew install nushell
|
||||
# Windows
|
||||
winget install nushell
|
||||
```
|
||||
|
||||
You can also build Nu yourself with all the bells and whistles (be sure to have installed the [dependencies](https://www.nushell.sh/book/installation.html#dependencies) for your platform), once you have checked out this repo with git:
|
||||
To use `Nu` in Github Action, check [setup-nu](https://github.com/marketplace/actions/setup-nu) for more detail.
|
||||
|
||||
```shell
|
||||
cargo build --workspace --features=extra
|
||||
```
|
||||
### Packaging status
|
||||
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:
|
||||
|
||||
[](https://repology.org/project/nushell/versions)
|
||||
|
||||
#### Fedora
|
||||
|
||||
[COPR repo](https://copr.fedorainfracloud.org/coprs/atim/nushell/): `sudo dnf copr enable atim/nushell -y && sudo dnf install nushell -y`
|
||||
|
||||
## Philosophy
|
||||
|
||||
Nu draws inspiration from projects like PowerShell, functional programming languages, and modern CLI tools.
|
||||
Rather than thinking of files and services as raw streams of text, Nu looks at each input as something with structure.
|
||||
For example, when you list the contents of a directory, what you get back is a table of rows, where each row represents an item in that directory.
|
||||
Rather than thinking of files and data as raw streams of text, Nu looks at each input as something with structure.
|
||||
For example, when you list the contents of a directory what you get back is a table of rows, where each row represents an item in that directory.
|
||||
These values can be piped through a series of steps, in a series of commands called a 'pipeline'.
|
||||
|
||||
### Pipelines
|
||||
|
||||
In Unix, it's common to pipe between commands to split up a sophisticated command over multiple steps.
|
||||
Nu takes this a step further and builds heavily on the idea of _pipelines_.
|
||||
Just as the Unix philosophy, Nu allows commands to output to stdout and read from stdin.
|
||||
As in the Unix philosophy, Nu allows commands to output to stdout and read from stdin.
|
||||
Additionally, commands can output structured data (you can think of this as a third kind of stream).
|
||||
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 consume the output of the pipeline (e.g., `autoview`)
|
||||
- Commands that filter a stream (eg, `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.
|
||||
|
||||
```shell
|
||||
> ls | where type == "Dir" | autoview
|
||||
───┬────────┬──────┬───────┬──────────────
|
||||
# │ name │ type │ size │ modified
|
||||
───┼────────┼──────┼───────┼──────────────
|
||||
0 │ assets │ Dir │ 128 B │ 5 months ago
|
||||
1 │ crates │ Dir │ 704 B │ 50 mins ago
|
||||
2 │ debian │ Dir │ 352 B │ 5 months ago
|
||||
3 │ docker │ Dir │ 288 B │ 3 months ago
|
||||
4 │ docs │ Dir │ 192 B │ 50 mins ago
|
||||
5 │ images │ Dir │ 160 B │ 5 months ago
|
||||
6 │ src │ Dir │ 128 B │ 1 day ago
|
||||
7 │ target │ Dir │ 160 B │ 5 days ago
|
||||
8 │ tests │ Dir │ 192 B │ 3 months ago
|
||||
───┴────────┴──────┴───────┴──────────────
|
||||
> ls | where type == "dir" | table
|
||||
╭────┬──────────┬──────┬─────────┬───────────────╮
|
||||
│ # │ name │ type │ size │ modified │
|
||||
├────┼──────────┼──────┼─────────┼───────────────┤
|
||||
│ 0 │ .cargo │ dir │ 0 B │ 9 minutes ago │
|
||||
│ 1 │ assets │ dir │ 0 B │ 2 weeks ago │
|
||||
│ 2 │ crates │ dir │ 4.0 KiB │ 2 weeks ago │
|
||||
│ 3 │ docker │ dir │ 0 B │ 2 weeks ago │
|
||||
│ 4 │ docs │ dir │ 0 B │ 2 weeks ago │
|
||||
│ 5 │ images │ dir │ 0 B │ 2 weeks ago │
|
||||
│ 6 │ pkg_mgrs │ dir │ 0 B │ 2 weeks ago │
|
||||
│ 7 │ samples │ dir │ 0 B │ 2 weeks ago │
|
||||
│ 8 │ src │ dir │ 4.0 KiB │ 2 weeks ago │
|
||||
│ 9 │ target │ dir │ 0 B │ a day ago │
|
||||
│ 10 │ tests │ dir │ 4.0 KiB │ 2 weeks ago │
|
||||
│ 11 │ wix │ dir │ 0 B │ 2 weeks ago │
|
||||
╰────┴──────────┴──────┴─────────┴───────────────╯
|
||||
```
|
||||
|
||||
Because most of the time you'll want to see the output of a pipeline, `autoview` is assumed.
|
||||
Because most of the time you'll want to see the output of a pipeline, `table` is assumed.
|
||||
We could have also written the above:
|
||||
|
||||
```shell
|
||||
> ls | where type == Dir
|
||||
> ls | where type == "dir"
|
||||
```
|
||||
|
||||
Being able to use the same commands and compose them differently is an important philosophy in Nu.
|
||||
@ -134,15 +108,13 @@ For example, we could use the built-in `ps` command to get a list of the running
|
||||
|
||||
```shell
|
||||
> ps | where cpu > 0
|
||||
───┬────────┬───────────────────┬──────────┬─────────┬──────────┬──────────
|
||||
# │ pid │ name │ status │ cpu │ mem │ virtual
|
||||
───┼────────┼───────────────────┼──────────┼─────────┼──────────┼──────────
|
||||
0 │ 435 │ irq/142-SYNA327 │ Sleeping │ 7.5699 │ 0 B │ 0 B
|
||||
1 │ 1609 │ pulseaudio │ Sleeping │ 6.5605 │ 10.6 MB │ 2.3 GB
|
||||
2 │ 1625 │ gnome-shell │ Sleeping │ 6.5684 │ 639.6 MB │ 7.3 GB
|
||||
3 │ 2202 │ Web Content │ Sleeping │ 6.8157 │ 320.8 MB │ 3.0 GB
|
||||
4 │ 328788 │ nu_plugin_core_ps │ Sleeping │ 92.5750 │ 5.9 MB │ 633.2 MB
|
||||
───┴────────┴───────────────────┴──────────┴─────────┴──────────┴──────────
|
||||
╭───┬───────┬───────────┬───────┬───────────┬───────────╮
|
||||
│ # │ pid │ name │ cpu │ mem │ virtual │
|
||||
├───┼───────┼───────────┼───────┼───────────┼───────────┤
|
||||
│ 0 │ 2240 │ Slack.exe │ 16.40 │ 178.3 MiB │ 232.6 MiB │
|
||||
│ 1 │ 16948 │ Slack.exe │ 16.32 │ 205.0 MiB │ 197.9 MiB │
|
||||
│ 2 │ 17700 │ nu.exe │ 3.77 │ 26.1 MiB │ 8.8 MiB │
|
||||
╰───┴───────┴───────────┴───────┴───────────┴───────────╯
|
||||
```
|
||||
|
||||
### Opening files
|
||||
@ -152,72 +124,49 @@ For example, you can load a .toml file as structured data and explore it:
|
||||
|
||||
```shell
|
||||
> open Cargo.toml
|
||||
────────────────────┬───────────────────────────
|
||||
bin │ [table 18 rows]
|
||||
build-dependencies │ [row serde toml]
|
||||
dependencies │ [row 29 columns]
|
||||
dev-dependencies │ [row nu-test-support]
|
||||
features │ [row 19 columns]
|
||||
package │ [row 12 columns]
|
||||
workspace │ [row members]
|
||||
────────────────────┴───────────────────────────
|
||||
╭──────────────────┬────────────────────╮
|
||||
│ bin │ [table 1 row] │
|
||||
│ dependencies │ {record 24 fields} │
|
||||
│ dev-dependencies │ {record 8 fields} │
|
||||
│ features │ {record 10 fields} │
|
||||
│ package │ {record 13 fields} │
|
||||
│ profile │ {record 3 fields} │
|
||||
│ target │ {record 2 fields} │
|
||||
│ workspace │ {record 1 field} │
|
||||
╰──────────────────┴────────────────────╯
|
||||
```
|
||||
|
||||
We can pipeline this into a command that gets the contents of one of the columns:
|
||||
We can pipe this into a command that gets the contents of one of the columns:
|
||||
|
||||
```shell
|
||||
> open Cargo.toml | get package
|
||||
───────────────┬────────────────────────────────────
|
||||
authors │ [table 1 rows]
|
||||
default-run │ nu
|
||||
description │ A new type of shell
|
||||
documentation │ https://www.nushell.sh/book/
|
||||
edition │ 2018
|
||||
exclude │ [table 1 rows]
|
||||
homepage │ https://www.nushell.sh
|
||||
license │ MIT
|
||||
name │ nu
|
||||
readme │ README.md
|
||||
repository │ https://github.com/nushell/nushell
|
||||
version │ 0.32.0
|
||||
───────────────┴────────────────────────────────────
|
||||
╭───────────────┬────────────────────────────────────╮
|
||||
│ authors │ [list 1 item] │
|
||||
│ default-run │ nu │
|
||||
│ description │ A new type of shell │
|
||||
│ documentation │ https://www.nushell.sh/book/ │
|
||||
│ edition │ 2018 │
|
||||
│ exclude │ [list 1 item] │
|
||||
│ homepage │ https://www.nushell.sh │
|
||||
│ license │ MIT │
|
||||
│ name │ nu │
|
||||
│ readme │ README.md │
|
||||
│ repository │ https://github.com/nushell/nushell │
|
||||
│ rust-version │ 1.60 │
|
||||
│ version │ 0.63.1 │
|
||||
╰───────────────┴────────────────────────────────────╯
|
||||
```
|
||||
|
||||
Finally, we can use commands outside of Nu once we have the data we want:
|
||||
And if needed we can drill down further:
|
||||
|
||||
```shell
|
||||
> open Cargo.toml | get package.version
|
||||
0.32.0
|
||||
0.63.1
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
Nu has early support for configuring the shell. You can refer to the book for a list of [all supported variables](https://www.nushell.sh/book/configuration.html).
|
||||
|
||||
To set one of these variables, you can use `config set`. For example:
|
||||
|
||||
```shell
|
||||
> config set line_editor.edit_mode "vi"
|
||||
> config set path $nu.path
|
||||
```
|
||||
|
||||
### Shells
|
||||
|
||||
Nu will work inside of a single directory and allow you to navigate around your filesystem by default.
|
||||
Nu also offers a way of adding additional working directories that you can jump between, allowing you to work in multiple directories simultaneously.
|
||||
|
||||
To do so, use the `enter` command, which will allow you to create a new "shell" and enter it at the specified path.
|
||||
You can toggle between this new shell and the original shell with the `p` (for previous) and `n` (for next), allowing you to navigate around a ring buffer of shells.
|
||||
Once you're done with a shell, you can `exit` it and remove it from the ring buffer.
|
||||
|
||||
Finally, to get a list of all the current shells, you can use the `shells` command.
|
||||
|
||||
### Plugins
|
||||
|
||||
Nu supports plugins that offer additional functionality to the shell and follow the same structured data model that built-in commands use.
|
||||
This allows you to extend nu for your needs.
|
||||
|
||||
There are a few examples in the `plugins` directory.
|
||||
Nu supports plugins that offer additional functionality to the shell and follow the same structured data model that built-in commands use. There are a few examples in the `crates/nu_plugins_*` directories.
|
||||
|
||||
Plugins are binaries that are available in your path and follow a `nu_plugin_*` naming convention.
|
||||
These binaries interact with nu via a simple JSON-RPC protocol where the command identifies itself and passes along its configuration, making it available for use.
|
||||
@ -228,59 +177,53 @@ If the plugin is a sink, it is given the full vector of final data and is given
|
||||
|
||||
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
|
||||
|
||||
- First and foremost, Nu is cross-platform. Commands and techniques should carry between platforms and offer consistent first-class support for Windows, macOS, and Linux.
|
||||
- First and foremost, Nu is cross-platform. Commands and techniques should work across platforms and Nu has first-class support for Windows, macOS, and Linux.
|
||||
|
||||
- Nu ensures direct compatibility with existing platform-specific executables that make up people's workflows.
|
||||
- Nu ensures compatibility with existing platform-specific executables.
|
||||
|
||||
- Nu's workflow and tools should have the usability in day-to-day experience of using a shell in 2019 (and beyond).
|
||||
- Nu's workflow and tools should have the usability expected of modern software in 2022 (and beyond).
|
||||
|
||||
- Nu views data as both structured and unstructured. It is a structured shell like PowerShell.
|
||||
- Nu views data as either structured or unstructured. It is a structured shell like PowerShell.
|
||||
|
||||
- Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state.
|
||||
|
||||
## Commands
|
||||
|
||||
You can find a list of Nu commands, complete with documentation, in [quick command references](https://www.nushell.sh/book/command_reference.html).
|
||||
|
||||
## Progress
|
||||
|
||||
Nu is in heavy development and will naturally change as it matures and people use it. The chart below isn't meant to be exhaustive, but rather helps give an idea for some of the areas of development and their relative completion:
|
||||
Nu is under heavy development and will naturally change as it matures. The chart below isn't meant to be exhaustive, but it helps give an idea for some of the areas of development and their relative maturity:
|
||||
|
||||
| Features | Not started | Prototype | MVP | Preview | Mature | Notes |
|
||||
| ------------- | :---------: | :-------: | :-: | :-----: | :----: | -------------------------------------------------------------------- |
|
||||
| Aliases | | | X | | | Aliases allow for shortening large commands, while passing flags |
|
||||
| Aliases | | | | X | | Aliases allow for shortening large commands, while passing flags |
|
||||
| Notebook | | X | | | | Initial jupyter support, but it loses state and lacks features |
|
||||
| File ops | | | X | | | cp, mv, rm, mkdir have some support, but lacking others |
|
||||
| Environment | | | X | | | Temporary environment and scoped environment variables |
|
||||
| Shells | | X | | | | Basic value and file shells, but no opt-in/opt-out for commands |
|
||||
| Protocol | | | X | | | Streaming protocol is serviceable |
|
||||
| Plugins | | X | | | | Plugins work on one row at a time, lack batching and expression eval |
|
||||
| Errors | | | X | | | Error reporting works, but could use usability polish |
|
||||
| File ops | | | | X | | cp, mv, rm, mkdir have some support, but lacking others |
|
||||
| Environment | | | | X | | Temporary environment and scoped environment variables |
|
||||
| Shells | | | | X | | Basic value and file shells, but no opt-in/opt-out for commands |
|
||||
| Protocol | | | | X | | Streaming protocol is serviceable |
|
||||
| Plugins | | | X | | | Plugins work on one row at a time, lack batching and expression eval |
|
||||
| Errors | | | | X | | Error reporting works, but could use usability polish |
|
||||
| Documentation | | | X | | | Book updated to latest release, including usage examples |
|
||||
| Paging | | X | | | | Textview has paging, but we'd like paging for tables |
|
||||
| Functions | | | X | | | Functions and aliases are supported |
|
||||
| Variables | | | X | | | Nu supports variables and environment variables |
|
||||
| Completions | | | X | | | Completions for filepaths |
|
||||
| Paging | | | | X | | Textview has paging, but we'd like paging for tables |
|
||||
| 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 |
|
||||
|
||||
## Officially Supported By
|
||||
|
||||
Please submit an issue or PR to be added to this list.
|
||||
|
||||
### Integrations
|
||||
- [zoxide](https://github.com/ajeetdsouza/zoxide)
|
||||
- [starship](https://github.com/starship/starship)
|
||||
### Mentions
|
||||
- [The Python Launcher for Unix](https://github.com/brettcannon/python-launcher#how-do-i-get-a-table-of-python-executables-in-nushell)
|
||||
- [oh-my-posh](https://ohmyposh.dev)
|
||||
- [Couchbase Shell](https://couchbase.sh)
|
||||
- [virtualenv](https://github.com/pypa/virtualenv)
|
||||
|
||||
## Contributing
|
||||
|
||||
See [Contributing](CONTRIBUTING.md) for details.
|
||||
|
||||
Thanks to all the people who already contributed!
|
||||
See [Contributing](CONTRIBUTING.md) for details. Thanks to all the people who already contributed!
|
||||
|
||||
<a href="https://github.com/nushell/nushell/graphs/contributors">
|
||||
<img src="https://contributors-img.web.app/image?repo=nushell/nushell" />
|
||||
<img src="https://contributors-img.web.app/image?repo=nushell/nushell&max=500" />
|
||||
</a>
|
||||
|
||||
## License
|
||||
|
3
README.release.txt
Normal file
3
README.release.txt
Normal file
@ -0,0 +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
|
BIN
assets/icons/nushell-original.png
Normal file
BIN
assets/icons/nushell-original.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
BIN
assets/nu_logo.ico
Normal file
BIN
assets/nu_logo.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.3 KiB |
23
build-all-maclin.sh
Executable file
23
build-all-maclin.sh
Executable file
@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "---------------------------------------------------------------"
|
||||
echo "Building nushell (nu) with --features=extra and all the plugins"
|
||||
echo "---------------------------------------------------------------"
|
||||
echo ""
|
||||
|
||||
NU_PLUGINS=(
|
||||
'nu_plugin_example'
|
||||
'nu_plugin_gstat'
|
||||
'nu_plugin_inc'
|
||||
'nu_plugin_query'
|
||||
)
|
||||
|
||||
echo "Building nushell"
|
||||
cargo build --features=extra
|
||||
for plugin in "${NU_PLUGINS[@]}"
|
||||
do
|
||||
echo '' && cd crates/$plugin
|
||||
echo "Building $plugin..."
|
||||
echo "-----------------------------"
|
||||
cargo build && cd ../..
|
||||
done
|
32
build-all-windows.cmd
Normal file
32
build-all-windows.cmd
Normal file
@ -0,0 +1,32 @@
|
||||
@echo off
|
||||
@echo -------------------------------------------------------------------
|
||||
@echo Building nushell (nu.exe) with --features=extra and all the plugins
|
||||
@echo -------------------------------------------------------------------
|
||||
@echo.
|
||||
|
||||
echo Building nushell.exe
|
||||
cargo build --features=extra
|
||||
@echo.
|
||||
|
||||
@cd crates\nu_plugin_example
|
||||
echo Building nu_plugin_example.exe
|
||||
cargo build
|
||||
@echo.
|
||||
|
||||
@cd ..\..\crates\nu_plugin_gstat
|
||||
echo Building nu_plugin_gstat.exe
|
||||
cargo build
|
||||
@echo.
|
||||
|
||||
@cd ..\..\crates\nu_plugin_inc
|
||||
echo Building nu_plugin_inc.exe
|
||||
cargo build
|
||||
@echo.
|
||||
|
||||
@cd ..\..\crates\nu_plugin_query
|
||||
|
||||
echo Building nu_plugin_query.exe
|
||||
cargo build
|
||||
@echo.
|
||||
|
||||
@cd ..\..
|
22
build-all.nu
Normal file
22
build-all.nu
Normal file
@ -0,0 +1,22 @@
|
||||
echo '-------------------------------------------------------------------'
|
||||
echo 'Building nushell (nu) with --features=extra and all the plugins'
|
||||
echo '-------------------------------------------------------------------'
|
||||
|
||||
echo $'(char nl)Building nushell'
|
||||
echo '----------------------------'
|
||||
cargo build --features=extra
|
||||
|
||||
let plugins = [
|
||||
nu_plugin_inc,
|
||||
nu_plugin_gstat,
|
||||
nu_plugin_query,
|
||||
nu_plugin_example,
|
||||
]
|
||||
|
||||
for plugin in $plugins {
|
||||
$'(char nl)Building ($plugin)'
|
||||
'----------------------------'
|
||||
cd $'crates/($plugin)'
|
||||
cargo build
|
||||
ignore
|
||||
}
|
13
build.rs
Normal file
13
build.rs
Normal file
@ -0,0 +1,13 @@
|
||||
#[cfg(windows)]
|
||||
fn main() {
|
||||
let mut res = winres::WindowsResource::new();
|
||||
res.set("ProductName", "Nushell");
|
||||
res.set("FileDescription", "Nushell");
|
||||
res.set("LegalCopyright", "Copyright (C) 2022");
|
||||
res.set_icon("assets/nu_logo.ico");
|
||||
res.compile()
|
||||
.expect("Failed to run the Windows resource compiler (rc.exe)");
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn main() {}
|
2
crates/nu-ansi-term/.gitignore
vendored
2
crates/nu-ansi-term/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
target
|
||||
Cargo.lock
|
@ -1,39 +0,0 @@
|
||||
[package]
|
||||
authors = [
|
||||
"ogham@bsago.me",
|
||||
"Ryan Scheel (Havvy) <ryan.havvy@gmail.com>",
|
||||
"Josh Triplett <josh@joshtriplett.org>",
|
||||
"The Nu Project Contributors",
|
||||
]
|
||||
description = "Library for ANSI terminal colors and styles (bold, underline)"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-ansi-term"
|
||||
version = "0.43.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
# name = "nu-ansi-term"
|
||||
|
||||
[features]
|
||||
derive_serde_style = ["serde"]
|
||||
|
||||
[dependencies]
|
||||
overload = "0.1.1"
|
||||
serde = { version="1.0.90", features=["derive"], optional=true }
|
||||
|
||||
# [dependencies.serde]
|
||||
# version = "1.0.90"
|
||||
# features = ["derive"]
|
||||
# optional = true
|
||||
|
||||
[target.'cfg(target_os="windows")'.dependencies.winapi]
|
||||
version = "0.3.4"
|
||||
features = ["consoleapi", "errhandlingapi", "fileapi", "handleapi", "processenv"]
|
||||
|
||||
[dev-dependencies]
|
||||
doc-comment = "0.3"
|
||||
regex = "1.1.9"
|
||||
|
||||
[dev-dependencies.serde_json]
|
||||
version = "1.0.39"
|
@ -1,182 +0,0 @@
|
||||
# nu-ansi-term
|
||||
|
||||
> This is a copy of rust-ansi-term but with Color change to Color and light foreground colors added (90-97) as well as light background colors added (100-107).
|
||||
|
||||
This is a library for controlling colors and formatting, such as red bold text or blue underlined text, on ANSI terminals.
|
||||
|
||||
### [View the Rustdoc](https://docs.rs/nu_ansi_term/)
|
||||
|
||||
# Installation
|
||||
|
||||
This crate works with [Cargo](http://crates.io). Add the following to your `Cargo.toml` dependencies section:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
nu_ansi_term = "0.13"
|
||||
```
|
||||
|
||||
## Basic usage
|
||||
|
||||
There are three main types in this crate that you need to be concerned with: `ANSIString`, `Style`, and `Color`.
|
||||
|
||||
A `Style` holds stylistic information: foreground and background colors, whether the text should be bold, or blinking, or other properties.
|
||||
The `Color` enum represents the available colors.
|
||||
And an `ANSIString` is a string paired with a `Style`.
|
||||
|
||||
`Color` is also available as an alias to `Color`.
|
||||
|
||||
To format a string, call the `paint` method on a `Style` or a `Color`, passing in the string you want to format as the argument.
|
||||
For example, here’s how to get some red text:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Red;
|
||||
|
||||
println!("This is in red: {}", Red.paint("a red string"));
|
||||
```
|
||||
|
||||
It’s important to note that the `paint` method does _not_ actually return a string with the ANSI control characters surrounding it.
|
||||
Instead, it returns an `ANSIString` value that has a `Display` implementation that, when formatted, returns the characters.
|
||||
This allows strings to be printed with a minimum of `String` allocations being performed behind the scenes.
|
||||
|
||||
If you _do_ want to get at the escape codes, then you can convert the `ANSIString` to a string as you would any other `Display` value:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Red;
|
||||
|
||||
let red_string = Red.paint("a red string").to_string();
|
||||
```
|
||||
|
||||
**Note for Windows 10 users:** On Windows 10, the application must enable ANSI support first:
|
||||
|
||||
```rust,ignore
|
||||
let enabled = nu_ansi_term::enable_ansi_support();
|
||||
```
|
||||
|
||||
## Bold, underline, background, and other styles
|
||||
|
||||
For anything more complex than plain foreground color changes, you need to construct `Style` values themselves, rather than beginning with a `Color`.
|
||||
You can do this by chaining methods based on a new `Style`, created with `Style::new()`.
|
||||
Each method creates a new style that has that specific property set.
|
||||
For example:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Style;
|
||||
|
||||
println!("How about some {} and {}?",
|
||||
Style::new().bold().paint("bold"),
|
||||
Style::new().underline().paint("underline"));
|
||||
```
|
||||
|
||||
For brevity, these methods have also been implemented for `Color` values, so you can give your styles a foreground color without having to begin with an empty `Style` value:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::{Blue, Yellow};
|
||||
|
||||
println!("Demonstrating {} and {}!",
|
||||
Blue.bold().paint("blue bold"),
|
||||
Yellow.underline().paint("yellow underline"));
|
||||
|
||||
println!("Yellow on blue: {}", Yellow.on(Blue).paint("wow!"));
|
||||
```
|
||||
|
||||
The complete list of styles you can use are:
|
||||
`bold`, `dimmed`, `italic`, `underline`, `blink`, `reverse`, `hidden`, and `on` for background colors.
|
||||
|
||||
In some cases, you may find it easier to change the foreground on an existing `Style` rather than starting from the appropriate `Color`.
|
||||
You can do this using the `fg` method:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Style;
|
||||
use nu_ansi_term::Color::{Blue, Cyan, Yellow};
|
||||
|
||||
println!("Yellow on blue: {}", Style::new().on(Blue).fg(Yellow).paint("yow!"));
|
||||
println!("Also yellow on blue: {}", Cyan.on(Blue).fg(Yellow).paint("zow!"));
|
||||
```
|
||||
|
||||
You can turn a `Color` into a `Style` with the `normal` method.
|
||||
This will produce the exact same `ANSIString` as if you just used the `paint` method on the `Color` directly, but it’s useful in certain cases: for example, you may have a method that returns `Styles`, and need to represent both the “red bold” and “red, but not bold” styles with values of the same type. The `Style` struct also has a `Default` implementation if you want to have a style with _nothing_ set.
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Style;
|
||||
use nu_ansi_term::Color::Red;
|
||||
|
||||
Red.normal().paint("yet another red string");
|
||||
Style::default().paint("a completely regular string");
|
||||
```
|
||||
|
||||
## Extended colors
|
||||
|
||||
You can access the extended range of 256 colors by using the `Color::Fixed` variant, which takes an argument of the color number to use.
|
||||
This can be included wherever you would use a `Color`:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Fixed;
|
||||
|
||||
Fixed(134).paint("A sort of light purple");
|
||||
Fixed(221).on(Fixed(124)).paint("Mustard in the ketchup");
|
||||
```
|
||||
|
||||
The first sixteen of these values are the same as the normal and bold standard color variants.
|
||||
There’s nothing stopping you from using these as `Fixed` colors instead, but there’s nothing to be gained by doing so either.
|
||||
|
||||
You can also access full 24-bit color by using the `Color::RGB` variant, which takes separate `u8` arguments for red, green, and blue:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::RGB;
|
||||
|
||||
RGB(70, 130, 180).paint("Steel blue");
|
||||
```
|
||||
|
||||
## Combining successive coloured strings
|
||||
|
||||
The benefit of writing ANSI escape codes to the terminal is that they _stack_: you do not need to end every coloured string with a reset code if the text that follows it is of a similar style.
|
||||
For example, if you want to have some blue text followed by some blue bold text, it’s possible to send the ANSI code for blue, followed by the ANSI code for bold, and finishing with a reset code without having to have an extra one between the two strings.
|
||||
|
||||
This crate can optimise the ANSI codes that get printed in situations like this, making life easier for your terminal renderer.
|
||||
The `ANSIStrings` struct takes a slice of several `ANSIString` values, and will iterate over each of them, printing only the codes for the styles that need to be updated as part of its formatting routine.
|
||||
|
||||
The following code snippet uses this to enclose a binary number displayed in red bold text inside some red, but not bold, brackets:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Red;
|
||||
use nu_ansi_term::{ANSIString, ANSIStrings};
|
||||
|
||||
let some_value = format!("{:b}", 42);
|
||||
let strings: &[ANSIString<'static>] = &[
|
||||
Red.paint("["),
|
||||
Red.bold().paint(some_value),
|
||||
Red.paint("]"),
|
||||
];
|
||||
|
||||
println!("Value: {}", ANSIStrings(strings));
|
||||
```
|
||||
|
||||
There are several things to note here.
|
||||
Firstly, the `paint` method can take _either_ an owned `String` or a borrowed `&str`.
|
||||
Internally, an `ANSIString` holds a copy-on-write (`Cow`) string value to deal with both owned and borrowed strings at the same time.
|
||||
This is used here to display a `String`, the result of the `format!` call, using the same mechanism as some statically-available `&str` slices.
|
||||
Secondly, that the `ANSIStrings` value works in the same way as its singular counterpart, with a `Display` implementation that only performs the formatting when required.
|
||||
|
||||
## Byte strings
|
||||
|
||||
This library also supports formatting `[u8]` byte strings; this supports applications working with text in an unknown encoding.
|
||||
`Style` and `Color` support painting `[u8]` values, resulting in an `ANSIByteString`.
|
||||
This type does not implement `Display`, as it may not contain UTF-8, but it does provide a method `write_to` to write the result to any value that implements `Write`:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Green;
|
||||
|
||||
Green.paint("user data".as_bytes()).write_to(&mut std::io::stdout()).unwrap();
|
||||
```
|
||||
|
||||
Similarly, the type `ANSIByteStrings` supports writing a list of `ANSIByteString` values with minimal escape sequences:
|
||||
|
||||
```rust
|
||||
use nu_ansi_term::Color::Green;
|
||||
use nu_ansi_term::ANSIByteStrings;
|
||||
|
||||
ANSIByteStrings(&[
|
||||
Green.paint("user data 1\n".as_bytes()),
|
||||
Green.bold().paint("user data 2\n".as_bytes()),
|
||||
]).write_to(&mut std::io::stdout()).unwrap();
|
||||
```
|
@ -1,72 +0,0 @@
|
||||
extern crate nu_ansi_term;
|
||||
use nu_ansi_term::Color;
|
||||
|
||||
// This example prints out the 256 colors.
|
||||
// They're arranged like this:
|
||||
//
|
||||
// - 0 to 8 are the eight standard colors.
|
||||
// - 9 to 15 are the eight bold colors.
|
||||
// - 16 to 231 are six blocks of six-by-six color squares.
|
||||
// - 232 to 255 are shades of grey.
|
||||
|
||||
fn main() {
|
||||
// First two lines
|
||||
for c in 0..8 {
|
||||
glow(c, c != 0);
|
||||
print!(" ");
|
||||
}
|
||||
println!();
|
||||
for c in 8..16 {
|
||||
glow(c, c != 8);
|
||||
print!(" ");
|
||||
}
|
||||
println!("\n");
|
||||
|
||||
// Six lines of the first three squares
|
||||
for row in 0..6 {
|
||||
for square in 0..3 {
|
||||
for column in 0..6 {
|
||||
glow(16 + square * 36 + row * 6 + column, row >= 3);
|
||||
print!(" ");
|
||||
}
|
||||
|
||||
print!(" ");
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
println!();
|
||||
|
||||
// Six more lines of the other three squares
|
||||
for row in 0..6 {
|
||||
for square in 0..3 {
|
||||
for column in 0..6 {
|
||||
glow(124 + square * 36 + row * 6 + column, row >= 3);
|
||||
print!(" ");
|
||||
}
|
||||
|
||||
print!(" ");
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
println!();
|
||||
|
||||
// The last greyscale lines
|
||||
for c in 232..=243 {
|
||||
glow(c, false);
|
||||
print!(" ");
|
||||
}
|
||||
println!();
|
||||
for c in 244..=255 {
|
||||
glow(c, true);
|
||||
print!(" ");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
fn glow(c: u8, light_bg: bool) {
|
||||
let base = if light_bg { Color::Black } else { Color::White };
|
||||
let style = base.on(Color::Fixed(c));
|
||||
print!("{}", style.paint(&format!(" {:3} ", c)));
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
extern crate nu_ansi_term;
|
||||
use nu_ansi_term::{Color::*, Style};
|
||||
|
||||
// This example prints out the 16 basic colors.
|
||||
|
||||
fn main() {
|
||||
let normal = Style::default();
|
||||
|
||||
println!("{} {}", normal.paint("Normal"), normal.bold().paint("bold"));
|
||||
println!("{} {}", Black.paint("Black"), Black.bold().paint("bold"));
|
||||
println!("{} {}", Red.paint("Red"), Red.bold().paint("bold"));
|
||||
println!("{} {}", Green.paint("Green"), Green.bold().paint("bold"));
|
||||
println!("{} {}", Yellow.paint("Yellow"), Yellow.bold().paint("bold"));
|
||||
println!("{} {}", Blue.paint("Blue"), Blue.bold().paint("bold"));
|
||||
println!("{} {}", Purple.paint("Purple"), Purple.bold().paint("bold"));
|
||||
println!("{} {}", Cyan.paint("Cyan"), Cyan.bold().paint("bold"));
|
||||
println!("{} {}", White.paint("White"), White.bold().paint("bold"));
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
use nu_ansi_term::{build_all_gradient_text, Color, Gradient, Rgb, TargetGround};
|
||||
|
||||
fn main() {
|
||||
let text = "lorem ipsum quia dolor sit amet, consectetur, adipisci velit";
|
||||
|
||||
// a gradient from hex colors
|
||||
let start = Rgb::from_hex(0x40c9ff);
|
||||
let end = Rgb::from_hex(0xe81cff);
|
||||
let grad0 = Gradient::new(start, end);
|
||||
|
||||
// a gradient from color::rgb()
|
||||
let start = Color::Rgb(64, 201, 255);
|
||||
let end = Color::Rgb(232, 28, 255);
|
||||
let gradient = Gradient::from_color_rgb(start, end);
|
||||
|
||||
// a slightly different gradient
|
||||
let start2 = Color::Rgb(128, 64, 255);
|
||||
let end2 = Color::Rgb(0, 28, 255);
|
||||
let gradient2 = Gradient::from_color_rgb(start2, end2);
|
||||
|
||||
// reverse the gradient
|
||||
let gradient3 = gradient.reverse();
|
||||
|
||||
let build_fg = gradient.build(text, TargetGround::Foreground);
|
||||
println!("{}", build_fg);
|
||||
let build_bg = gradient.build(text, TargetGround::Background);
|
||||
println!("{}", build_bg);
|
||||
let bgt = build_all_gradient_text(text, gradient, gradient2);
|
||||
println!("{}", bgt);
|
||||
let bgt2 = build_all_gradient_text(text, gradient, gradient3);
|
||||
println!("{}", bgt2);
|
||||
|
||||
println!(
|
||||
"{}",
|
||||
grad0.build("nushell is awesome", TargetGround::Foreground)
|
||||
);
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
extern crate nu_ansi_term;
|
||||
use nu_ansi_term::{Color, Style};
|
||||
|
||||
// This example prints out a color gradient in a grid by calculating each
|
||||
// character’s red, green, and blue components, and using 24-bit color codes
|
||||
// to display them.
|
||||
|
||||
const WIDTH: i32 = 80;
|
||||
const HEIGHT: i32 = 24;
|
||||
|
||||
fn main() {
|
||||
for row in 0..HEIGHT {
|
||||
for col in 0..WIDTH {
|
||||
let r = (row * 255 / HEIGHT) as u8;
|
||||
let g = (col * 255 / WIDTH) as u8;
|
||||
let b = 128;
|
||||
|
||||
print!("{}", Style::default().on(Color::Rgb(r, g, b)).paint(" "));
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
}
|
@ -1,405 +0,0 @@
|
||||
#![allow(missing_docs)]
|
||||
use crate::style::{Color, Style};
|
||||
use crate::write::AnyWrite;
|
||||
use std::fmt;
|
||||
|
||||
impl Style {
|
||||
/// Write any bytes that go *before* a piece of text to the given writer.
|
||||
fn write_prefix<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
// If there are actually no styles here, then don’t write *any* codes
|
||||
// as the prefix. An empty ANSI code may not affect the terminal
|
||||
// output at all, but a user may just want a code-free string.
|
||||
if self.is_plain() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Write the codes’ prefix, then write numbers, separated by
|
||||
// semicolons, for each text style we want to apply.
|
||||
write!(f, "\x1B[")?;
|
||||
let mut written_anything = false;
|
||||
|
||||
{
|
||||
let mut write_char = |c| {
|
||||
if written_anything {
|
||||
write!(f, ";")?;
|
||||
}
|
||||
written_anything = true;
|
||||
write!(f, "{}", c)?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
if self.is_bold {
|
||||
write_char('1')?
|
||||
}
|
||||
if self.is_dimmed {
|
||||
write_char('2')?
|
||||
}
|
||||
if self.is_italic {
|
||||
write_char('3')?
|
||||
}
|
||||
if self.is_underline {
|
||||
write_char('4')?
|
||||
}
|
||||
if self.is_blink {
|
||||
write_char('5')?
|
||||
}
|
||||
if self.is_reverse {
|
||||
write_char('7')?
|
||||
}
|
||||
if self.is_hidden {
|
||||
write_char('8')?
|
||||
}
|
||||
if self.is_strikethrough {
|
||||
write_char('9')?
|
||||
}
|
||||
}
|
||||
|
||||
// The foreground and background colors, if specified, need to be
|
||||
// handled specially because the number codes are more complicated.
|
||||
// (see `write_background_code` and `write_foreground_code`)
|
||||
if let Some(bg) = self.background {
|
||||
if written_anything {
|
||||
write!(f, ";")?;
|
||||
}
|
||||
written_anything = true;
|
||||
bg.write_background_code(f)?;
|
||||
}
|
||||
|
||||
if let Some(fg) = self.foreground {
|
||||
if written_anything {
|
||||
write!(f, ";")?;
|
||||
}
|
||||
fg.write_foreground_code(f)?;
|
||||
}
|
||||
|
||||
// All the codes end with an `m`, because reasons.
|
||||
write!(f, "m")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write any bytes that go *after* a piece of text to the given writer.
|
||||
fn write_suffix<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
if self.is_plain() {
|
||||
Ok(())
|
||||
} else {
|
||||
write!(f, "{}", RESET)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The code to send to reset all styles and return to `Style::default()`.
|
||||
pub static RESET: &str = "\x1B[0m";
|
||||
|
||||
impl Color {
|
||||
fn write_foreground_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
match self {
|
||||
Color::Black => write!(f, "30"),
|
||||
Color::Red => write!(f, "31"),
|
||||
Color::Green => write!(f, "32"),
|
||||
Color::Yellow => write!(f, "33"),
|
||||
Color::Blue => write!(f, "34"),
|
||||
Color::Purple => write!(f, "35"),
|
||||
Color::Magenta => write!(f, "35"),
|
||||
Color::Cyan => write!(f, "36"),
|
||||
Color::White => write!(f, "37"),
|
||||
Color::Fixed(num) => write!(f, "38;5;{}", num),
|
||||
Color::Rgb(r, g, b) => write!(f, "38;2;{};{};{}", r, g, b),
|
||||
Color::DarkGray => write!(f, "90"),
|
||||
Color::LightRed => write!(f, "91"),
|
||||
Color::LightGreen => write!(f, "92"),
|
||||
Color::LightYellow => write!(f, "93"),
|
||||
Color::LightBlue => write!(f, "94"),
|
||||
Color::LightPurple => write!(f, "95"),
|
||||
Color::LightMagenta => write!(f, "95"),
|
||||
Color::LightCyan => write!(f, "96"),
|
||||
Color::LightGray => write!(f, "97"),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_background_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
match self {
|
||||
Color::Black => write!(f, "40"),
|
||||
Color::Red => write!(f, "41"),
|
||||
Color::Green => write!(f, "42"),
|
||||
Color::Yellow => write!(f, "43"),
|
||||
Color::Blue => write!(f, "44"),
|
||||
Color::Purple => write!(f, "45"),
|
||||
Color::Magenta => write!(f, "45"),
|
||||
Color::Cyan => write!(f, "46"),
|
||||
Color::White => write!(f, "47"),
|
||||
Color::Fixed(num) => write!(f, "48;5;{}", num),
|
||||
Color::Rgb(r, g, b) => write!(f, "48;2;{};{};{}", r, g, b),
|
||||
Color::DarkGray => write!(f, "100"),
|
||||
Color::LightRed => write!(f, "101"),
|
||||
Color::LightGreen => write!(f, "102"),
|
||||
Color::LightYellow => write!(f, "103"),
|
||||
Color::LightBlue => write!(f, "104"),
|
||||
Color::LightPurple => write!(f, "105"),
|
||||
Color::LightMagenta => write!(f, "105"),
|
||||
Color::LightCyan => write!(f, "106"),
|
||||
Color::LightGray => write!(f, "107"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `ANSIString`, but only displays the style prefix.
|
||||
///
|
||||
/// This type implements the `Display` trait, meaning it can be written to a
|
||||
/// `std::fmt` formatting without doing any extra allocation, and written to a
|
||||
/// string with the `.to_string()` method. For examples, see
|
||||
/// [`Style::prefix`](struct.Style.html#method.prefix).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Prefix(Style);
|
||||
|
||||
/// Like `ANSIString`, but only displays the difference between two
|
||||
/// styles.
|
||||
///
|
||||
/// This type implements the `Display` trait, meaning it can be written to a
|
||||
/// `std::fmt` formatting without doing any extra allocation, and written to a
|
||||
/// string with the `.to_string()` method. For examples, see
|
||||
/// [`Style::infix`](struct.Style.html#method.infix).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Infix(Style, Style);
|
||||
|
||||
/// Like `ANSIString`, but only displays the style suffix.
|
||||
///
|
||||
/// This type implements the `Display` trait, meaning it can be written to a
|
||||
/// `std::fmt` formatting without doing any extra allocation, and written to a
|
||||
/// string with the `.to_string()` method. For examples, see
|
||||
/// [`Style::suffix`](struct.Style.html#method.suffix).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Suffix(Style);
|
||||
|
||||
impl Style {
|
||||
/// The prefix bytes for this style. These are the bytes that tell the
|
||||
/// terminal to use a different color or font style.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color::Blue};
|
||||
///
|
||||
/// let style = Style::default().bold();
|
||||
/// assert_eq!("\x1b[1m",
|
||||
/// style.prefix().to_string());
|
||||
///
|
||||
/// let style = Blue.bold();
|
||||
/// assert_eq!("\x1b[1;34m",
|
||||
/// style.prefix().to_string());
|
||||
///
|
||||
/// let style = Style::default();
|
||||
/// assert_eq!("",
|
||||
/// style.prefix().to_string());
|
||||
/// ```
|
||||
pub fn prefix(self) -> Prefix {
|
||||
Prefix(self)
|
||||
}
|
||||
|
||||
/// The infix bytes between this style and `next` style. These are the bytes
|
||||
/// that tell the terminal to change the style to `next`. These may include
|
||||
/// a reset followed by the next color and style, depending on the two styles.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color::Green};
|
||||
///
|
||||
/// let style = Style::default().bold();
|
||||
/// assert_eq!("\x1b[32m",
|
||||
/// style.infix(Green.bold()).to_string());
|
||||
///
|
||||
/// let style = Green.normal();
|
||||
/// assert_eq!("\x1b[1m",
|
||||
/// style.infix(Green.bold()).to_string());
|
||||
///
|
||||
/// let style = Style::default();
|
||||
/// assert_eq!("",
|
||||
/// style.infix(style).to_string());
|
||||
/// ```
|
||||
pub fn infix(self, next: Style) -> Infix {
|
||||
Infix(self, next)
|
||||
}
|
||||
|
||||
/// The suffix for this style. These are the bytes that tell the terminal
|
||||
/// to reset back to its normal color and font style.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color::Green};
|
||||
///
|
||||
/// let style = Style::default().bold();
|
||||
/// assert_eq!("\x1b[0m",
|
||||
/// style.suffix().to_string());
|
||||
///
|
||||
/// let style = Green.normal().bold();
|
||||
/// assert_eq!("\x1b[0m",
|
||||
/// style.suffix().to_string());
|
||||
///
|
||||
/// let style = Style::default();
|
||||
/// assert_eq!("",
|
||||
/// style.suffix().to_string());
|
||||
/// ```
|
||||
pub fn suffix(self) -> Suffix {
|
||||
Suffix(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// The prefix bytes for this color as a `Style`. These are the bytes
|
||||
/// that tell the terminal to use a different color or font style.
|
||||
///
|
||||
/// See also [`Style::prefix`](struct.Style.html#method.prefix).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color::Green;
|
||||
///
|
||||
/// assert_eq!("\x1b[0m",
|
||||
/// Green.suffix().to_string());
|
||||
/// ```
|
||||
pub fn prefix(self) -> Prefix {
|
||||
Prefix(self.normal())
|
||||
}
|
||||
|
||||
/// The infix bytes between this color and `next` color. These are the bytes
|
||||
/// that tell the terminal to use the `next` color, or to do nothing if
|
||||
/// the two colors are equal.
|
||||
///
|
||||
/// See also [`Style::infix`](struct.Style.html#method.infix).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color::{Red, Yellow};
|
||||
///
|
||||
/// assert_eq!("\x1b[33m",
|
||||
/// Red.infix(Yellow).to_string());
|
||||
/// ```
|
||||
pub fn infix(self, next: Color) -> Infix {
|
||||
Infix(self.normal(), next.normal())
|
||||
}
|
||||
|
||||
/// The suffix for this color as a `Style`. These are the bytes that
|
||||
/// tell the terminal to reset back to its normal color and font style.
|
||||
///
|
||||
/// See also [`Style::suffix`](struct.Style.html#method.suffix).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color::Purple;
|
||||
///
|
||||
/// assert_eq!("\x1b[0m",
|
||||
/// Purple.suffix().to_string());
|
||||
/// ```
|
||||
pub fn suffix(self) -> Suffix {
|
||||
Suffix(self.normal())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Prefix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
self.0.write_prefix(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Infix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use crate::difference::Difference;
|
||||
|
||||
match Difference::between(&self.0, &self.1) {
|
||||
Difference::ExtraStyles(style) => {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
style.write_prefix(f)
|
||||
}
|
||||
Difference::Reset => {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
write!(f, "{}{}", RESET, self.1.prefix())
|
||||
}
|
||||
Difference::Empty => {
|
||||
Ok(()) // nothing to write
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Suffix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
self.0.write_suffix(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::style::Color::*;
|
||||
use crate::style::Style;
|
||||
|
||||
macro_rules! test {
|
||||
($name: ident: $style: expr; $input: expr => $result: expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
assert_eq!($style.paint($input).to_string(), $result.to_string());
|
||||
|
||||
let mut v = Vec::new();
|
||||
$style.paint($input.as_bytes()).write_to(&mut v).unwrap();
|
||||
assert_eq!(v.as_slice(), $result.as_bytes());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test!(plain: Style::default(); "text/plain" => "text/plain");
|
||||
test!(red: Red; "hi" => "\x1B[31mhi\x1B[0m");
|
||||
test!(black: Black.normal(); "hi" => "\x1B[30mhi\x1B[0m");
|
||||
test!(yellow_bold: Yellow.bold(); "hi" => "\x1B[1;33mhi\x1B[0m");
|
||||
test!(yellow_bold_2: Yellow.normal().bold(); "hi" => "\x1B[1;33mhi\x1B[0m");
|
||||
test!(blue_underline: Blue.underline(); "hi" => "\x1B[4;34mhi\x1B[0m");
|
||||
test!(green_bold_ul: Green.bold().underline(); "hi" => "\x1B[1;4;32mhi\x1B[0m");
|
||||
test!(green_bold_ul_2: Green.underline().bold(); "hi" => "\x1B[1;4;32mhi\x1B[0m");
|
||||
test!(purple_on_white: Purple.on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
|
||||
test!(purple_on_white_2: Purple.normal().on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
|
||||
test!(yellow_on_blue: Style::new().on(Blue).fg(Yellow); "hi" => "\x1B[44;33mhi\x1B[0m");
|
||||
test!(magenta_on_white: Magenta.on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
|
||||
test!(magenta_on_white_2: Magenta.normal().on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
|
||||
test!(yellow_on_blue_2: Cyan.on(Blue).fg(Yellow); "hi" => "\x1B[44;33mhi\x1B[0m");
|
||||
test!(cyan_bold_on_white: Cyan.bold().on(White); "hi" => "\x1B[1;47;36mhi\x1B[0m");
|
||||
test!(cyan_ul_on_white: Cyan.underline().on(White); "hi" => "\x1B[4;47;36mhi\x1B[0m");
|
||||
test!(cyan_bold_ul_on_white: Cyan.bold().underline().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m");
|
||||
test!(cyan_ul_bold_on_white: Cyan.underline().bold().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m");
|
||||
test!(fixed: Fixed(100); "hi" => "\x1B[38;5;100mhi\x1B[0m");
|
||||
test!(fixed_on_purple: Fixed(100).on(Purple); "hi" => "\x1B[45;38;5;100mhi\x1B[0m");
|
||||
test!(fixed_on_fixed: Fixed(100).on(Fixed(200)); "hi" => "\x1B[48;5;200;38;5;100mhi\x1B[0m");
|
||||
test!(rgb: Rgb(70,130,180); "hi" => "\x1B[38;2;70;130;180mhi\x1B[0m");
|
||||
test!(rgb_on_blue: Rgb(70,130,180).on(Blue); "hi" => "\x1B[44;38;2;70;130;180mhi\x1B[0m");
|
||||
test!(blue_on_rgb: Blue.on(Rgb(70,130,180)); "hi" => "\x1B[48;2;70;130;180;34mhi\x1B[0m");
|
||||
test!(rgb_on_rgb: Rgb(70,130,180).on(Rgb(5,10,15)); "hi" => "\x1B[48;2;5;10;15;38;2;70;130;180mhi\x1B[0m");
|
||||
test!(bold: Style::new().bold(); "hi" => "\x1B[1mhi\x1B[0m");
|
||||
test!(underline: Style::new().underline(); "hi" => "\x1B[4mhi\x1B[0m");
|
||||
test!(bunderline: Style::new().bold().underline(); "hi" => "\x1B[1;4mhi\x1B[0m");
|
||||
test!(dimmed: Style::new().dimmed(); "hi" => "\x1B[2mhi\x1B[0m");
|
||||
test!(italic: Style::new().italic(); "hi" => "\x1B[3mhi\x1B[0m");
|
||||
test!(blink: Style::new().blink(); "hi" => "\x1B[5mhi\x1B[0m");
|
||||
test!(reverse: Style::new().reverse(); "hi" => "\x1B[7mhi\x1B[0m");
|
||||
test!(hidden: Style::new().hidden(); "hi" => "\x1B[8mhi\x1B[0m");
|
||||
test!(stricken: Style::new().strikethrough(); "hi" => "\x1B[9mhi\x1B[0m");
|
||||
test!(lr_on_lr: LightRed.on(LightRed); "hi" => "\x1B[101;91mhi\x1B[0m");
|
||||
|
||||
#[test]
|
||||
fn test_infix() {
|
||||
assert_eq!(
|
||||
Style::new().dimmed().infix(Style::new()).to_string(),
|
||||
"\x1B[0m"
|
||||
);
|
||||
assert_eq!(
|
||||
White.dimmed().infix(White.normal()).to_string(),
|
||||
"\x1B[0m\x1B[37m"
|
||||
);
|
||||
assert_eq!(White.normal().infix(White.bold()).to_string(), "\x1B[1m");
|
||||
assert_eq!(White.normal().infix(Blue.normal()).to_string(), "\x1B[34m");
|
||||
assert_eq!(Blue.bold().infix(Blue.bold()).to_string(), "");
|
||||
}
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
use crate::style::Style;
|
||||
use std::fmt;
|
||||
|
||||
/// Styles have a special `Debug` implementation that only shows the fields that
|
||||
/// are set. Fields that haven’t been touched aren’t included in the output.
|
||||
///
|
||||
/// This behaviour gets bypassed when using the alternate formatting mode
|
||||
/// `format!("{:#?}")`.
|
||||
///
|
||||
/// use nu_ansi_term::Color::{Red, Blue};
|
||||
/// assert_eq!("Style { fg(Red), on(Blue), bold, italic }",
|
||||
/// format!("{:?}", Red.on(Blue).bold().italic()));
|
||||
impl fmt::Debug for Style {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
if fmt.alternate() {
|
||||
fmt.debug_struct("Style")
|
||||
.field("foreground", &self.foreground)
|
||||
.field("background", &self.background)
|
||||
.field("blink", &self.is_blink)
|
||||
.field("bold", &self.is_bold)
|
||||
.field("dimmed", &self.is_dimmed)
|
||||
.field("hidden", &self.is_hidden)
|
||||
.field("italic", &self.is_italic)
|
||||
.field("reverse", &self.is_reverse)
|
||||
.field("strikethrough", &self.is_strikethrough)
|
||||
.field("underline", &self.is_underline)
|
||||
.finish()
|
||||
} else if self.is_plain() {
|
||||
fmt.write_str("Style {}")
|
||||
} else {
|
||||
fmt.write_str("Style { ")?;
|
||||
|
||||
let mut written_anything = false;
|
||||
|
||||
if let Some(fg) = self.foreground {
|
||||
if written_anything {
|
||||
fmt.write_str(", ")?
|
||||
}
|
||||
written_anything = true;
|
||||
write!(fmt, "fg({:?})", fg)?
|
||||
}
|
||||
|
||||
if let Some(bg) = self.background {
|
||||
if written_anything {
|
||||
fmt.write_str(", ")?
|
||||
}
|
||||
written_anything = true;
|
||||
write!(fmt, "on({:?})", bg)?
|
||||
}
|
||||
|
||||
{
|
||||
let mut write_flag = |name| {
|
||||
if written_anything {
|
||||
fmt.write_str(", ")?
|
||||
}
|
||||
written_anything = true;
|
||||
fmt.write_str(name)
|
||||
};
|
||||
|
||||
if self.is_blink {
|
||||
write_flag("blink")?
|
||||
}
|
||||
if self.is_bold {
|
||||
write_flag("bold")?
|
||||
}
|
||||
if self.is_dimmed {
|
||||
write_flag("dimmed")?
|
||||
}
|
||||
if self.is_hidden {
|
||||
write_flag("hidden")?
|
||||
}
|
||||
if self.is_italic {
|
||||
write_flag("italic")?
|
||||
}
|
||||
if self.is_reverse {
|
||||
write_flag("reverse")?
|
||||
}
|
||||
if self.is_strikethrough {
|
||||
write_flag("strikethrough")?
|
||||
}
|
||||
if self.is_underline {
|
||||
write_flag("underline")?
|
||||
}
|
||||
}
|
||||
|
||||
write!(fmt, " }}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::style::Color::*;
|
||||
use crate::style::Style;
|
||||
|
||||
fn style() -> Style {
|
||||
Style::new()
|
||||
}
|
||||
|
||||
macro_rules! test {
|
||||
($name: ident: $obj: expr => $result: expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
assert_eq!($result, format!("{:?}", $obj));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test!(empty: style() => "Style {}");
|
||||
test!(bold: style().bold() => "Style { bold }");
|
||||
test!(italic: style().italic() => "Style { italic }");
|
||||
test!(both: style().bold().italic() => "Style { bold, italic }");
|
||||
|
||||
test!(red: Red.normal() => "Style { fg(Red) }");
|
||||
test!(redblue: Red.normal().on(Rgb(3, 2, 4)) => "Style { fg(Red), on(Rgb(3, 2, 4)) }");
|
||||
|
||||
test!(everything:
|
||||
Red.on(Blue).blink().bold().dimmed().hidden().italic().reverse().strikethrough().underline() =>
|
||||
"Style { fg(Red), on(Blue), blink, bold, dimmed, hidden, italic, reverse, strikethrough, underline }");
|
||||
|
||||
#[test]
|
||||
fn long_and_detailed() {
|
||||
extern crate regex;
|
||||
let expected_debug = "Style { fg(Blue), bold }";
|
||||
let expected_pretty_repat = r##"(?x)
|
||||
Style\s+\{\s+
|
||||
foreground:\s+Some\(\s+
|
||||
Blue,?\s+
|
||||
\),\s+
|
||||
background:\s+None,\s+
|
||||
blink:\s+false,\s+
|
||||
bold:\s+true,\s+
|
||||
dimmed:\s+false,\s+
|
||||
hidden:\s+false,\s+
|
||||
italic:\s+false,\s+
|
||||
reverse:\s+false,\s+
|
||||
strikethrough:\s+
|
||||
false,\s+
|
||||
underline:\s+false,?\s+
|
||||
\}"##;
|
||||
let re = regex::Regex::new(expected_pretty_repat).unwrap();
|
||||
|
||||
let style = Blue.bold();
|
||||
let style_fmt_debug = format!("{:?}", style);
|
||||
let style_fmt_pretty = format!("{:#?}", style);
|
||||
println!("style_fmt_debug:\n{}", style_fmt_debug);
|
||||
println!("style_fmt_pretty:\n{}", style_fmt_pretty);
|
||||
|
||||
assert_eq!(expected_debug, style_fmt_debug);
|
||||
assert!(re.is_match(&style_fmt_pretty));
|
||||
}
|
||||
}
|
@ -1,174 +0,0 @@
|
||||
use super::Style;
|
||||
|
||||
/// When printing out one colored string followed by another, use one of
|
||||
/// these rules to figure out which *extra* control codes need to be sent.
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
pub enum Difference {
|
||||
/// Print out the control codes specified by this style to end up looking
|
||||
/// like the second string's styles.
|
||||
ExtraStyles(Style),
|
||||
|
||||
/// Converting between these two is impossible, so just send a reset
|
||||
/// command and then the second string's styles.
|
||||
Reset,
|
||||
|
||||
/// The before style is exactly the same as the after style, so no further
|
||||
/// control codes need to be printed.
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl Difference {
|
||||
/// Compute the 'style difference' required to turn an existing style into
|
||||
/// the given, second style.
|
||||
///
|
||||
/// For example, to turn green text into green bold text, it's redundant
|
||||
/// to write a reset command then a second green+bold command, instead of
|
||||
/// just writing one bold command. This method should see that both styles
|
||||
/// use the foreground color green, and reduce it to a single command.
|
||||
///
|
||||
/// This method returns an enum value because it's not actually always
|
||||
/// possible to turn one style into another: for example, text could be
|
||||
/// made bold and underlined, but you can't remove the bold property
|
||||
/// without also removing the underline property. So when this has to
|
||||
/// happen, this function returns None, meaning that the entire set of
|
||||
/// styles should be reset and begun again.
|
||||
pub fn between(first: &Style, next: &Style) -> Difference {
|
||||
use self::Difference::*;
|
||||
|
||||
// XXX(Havvy): This algorithm is kind of hard to replicate without
|
||||
// having the Plain/Foreground enum variants, so I'm just leaving
|
||||
// it commented out for now, and defaulting to Reset.
|
||||
|
||||
if first == next {
|
||||
return Empty;
|
||||
}
|
||||
|
||||
// Cannot un-bold, so must Reset.
|
||||
if first.is_bold && !next.is_bold {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_dimmed && !next.is_dimmed {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_italic && !next.is_italic {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
// Cannot un-underline, so must Reset.
|
||||
if first.is_underline && !next.is_underline {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_blink && !next.is_blink {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_reverse && !next.is_reverse {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_hidden && !next.is_hidden {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
if first.is_strikethrough && !next.is_strikethrough {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
// Cannot go from foreground to no foreground, so must Reset.
|
||||
if first.foreground.is_some() && next.foreground.is_none() {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
// Cannot go from background to no background, so must Reset.
|
||||
if first.background.is_some() && next.background.is_none() {
|
||||
return Reset;
|
||||
}
|
||||
|
||||
let mut extra_styles = Style::default();
|
||||
|
||||
if first.is_bold != next.is_bold {
|
||||
extra_styles.is_bold = true;
|
||||
}
|
||||
|
||||
if first.is_dimmed != next.is_dimmed {
|
||||
extra_styles.is_dimmed = true;
|
||||
}
|
||||
|
||||
if first.is_italic != next.is_italic {
|
||||
extra_styles.is_italic = true;
|
||||
}
|
||||
|
||||
if first.is_underline != next.is_underline {
|
||||
extra_styles.is_underline = true;
|
||||
}
|
||||
|
||||
if first.is_blink != next.is_blink {
|
||||
extra_styles.is_blink = true;
|
||||
}
|
||||
|
||||
if first.is_reverse != next.is_reverse {
|
||||
extra_styles.is_reverse = true;
|
||||
}
|
||||
|
||||
if first.is_hidden != next.is_hidden {
|
||||
extra_styles.is_hidden = true;
|
||||
}
|
||||
|
||||
if first.is_strikethrough != next.is_strikethrough {
|
||||
extra_styles.is_strikethrough = true;
|
||||
}
|
||||
|
||||
if first.foreground != next.foreground {
|
||||
extra_styles.foreground = next.foreground;
|
||||
}
|
||||
|
||||
if first.background != next.background {
|
||||
extra_styles.background = next.background;
|
||||
}
|
||||
|
||||
ExtraStyles(extra_styles)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Difference::*;
|
||||
use super::*;
|
||||
use crate::style::Color::*;
|
||||
use crate::style::Style;
|
||||
|
||||
fn style() -> Style {
|
||||
Style::new()
|
||||
}
|
||||
|
||||
macro_rules! test {
|
||||
($name: ident: $first: expr; $next: expr => $result: expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
assert_eq!($result, Difference::between(&$first, &$next));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test!(nothing: Green.normal(); Green.normal() => Empty);
|
||||
test!(uppercase: Green.normal(); Green.bold() => ExtraStyles(style().bold()));
|
||||
test!(lowercase: Green.bold(); Green.normal() => Reset);
|
||||
test!(nothing2: Green.bold(); Green.bold() => Empty);
|
||||
|
||||
test!(color_change: Red.normal(); Blue.normal() => ExtraStyles(Blue.normal()));
|
||||
|
||||
test!(addition_of_blink: style(); style().blink() => ExtraStyles(style().blink()));
|
||||
test!(addition_of_dimmed: style(); style().dimmed() => ExtraStyles(style().dimmed()));
|
||||
test!(addition_of_hidden: style(); style().hidden() => ExtraStyles(style().hidden()));
|
||||
test!(addition_of_reverse: style(); style().reverse() => ExtraStyles(style().reverse()));
|
||||
test!(addition_of_strikethrough: style(); style().strikethrough() => ExtraStyles(style().strikethrough()));
|
||||
|
||||
test!(removal_of_strikethrough: style().strikethrough(); style() => Reset);
|
||||
test!(removal_of_reverse: style().reverse(); style() => Reset);
|
||||
test!(removal_of_hidden: style().hidden(); style() => Reset);
|
||||
test!(removal_of_dimmed: style().dimmed(); style() => Reset);
|
||||
test!(removal_of_blink: style().blink(); style() => Reset);
|
||||
}
|
@ -1,303 +0,0 @@
|
||||
use crate::ansi::RESET;
|
||||
use crate::difference::Difference;
|
||||
use crate::style::{Color, Style};
|
||||
use crate::write::AnyWrite;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// An `ANSIGenericString` includes a generic string type and a `Style` to
|
||||
/// display that string. `ANSIString` and `ANSIByteString` are aliases for
|
||||
/// this type on `str` and `\[u8]`, respectively.
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct AnsiGenericString<'a, S: 'a + ToOwned + ?Sized>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
style: Style,
|
||||
string: Cow<'a, S>,
|
||||
}
|
||||
|
||||
/// Cloning an `ANSIGenericString` will clone its underlying string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::ANSIString;
|
||||
///
|
||||
/// let plain_string = ANSIString::from("a plain string");
|
||||
/// let clone_string = plain_string.clone();
|
||||
/// assert_eq!(clone_string, plain_string);
|
||||
/// ```
|
||||
impl<'a, S: 'a + ToOwned + ?Sized> Clone for AnsiGenericString<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
fn clone(&self) -> AnsiGenericString<'a, S> {
|
||||
AnsiGenericString {
|
||||
style: self.style,
|
||||
string: self.string.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// You might think that the hand-written Clone impl above is the same as the
|
||||
// one that gets generated with #[derive]. But it’s not *quite* the same!
|
||||
//
|
||||
// `str` is not Clone, and the derived Clone implementation puts a Clone
|
||||
// constraint on the S type parameter (generated using --pretty=expanded):
|
||||
//
|
||||
// ↓_________________↓
|
||||
// impl <'a, S: ::std::clone::Clone + 'a + ToOwned + ?Sized> ::std::clone::Clone
|
||||
// for ANSIGenericString<'a, S> where
|
||||
// <S as ToOwned>::Owned: fmt::Debug { ... }
|
||||
//
|
||||
// This resulted in compile errors when you tried to derive Clone on a type
|
||||
// that used it:
|
||||
//
|
||||
// #[derive(PartialEq, Debug, Clone, Default)]
|
||||
// pub struct TextCellContents(Vec<ANSIString<'static>>);
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
// error[E0277]: the trait `std::clone::Clone` is not implemented for `str`
|
||||
//
|
||||
// The hand-written impl above can ignore that constraint and still compile.
|
||||
|
||||
/// An ANSI String is a string coupled with the `Style` to display it
|
||||
/// in a terminal.
|
||||
///
|
||||
/// Although not technically a string itself, it can be turned into
|
||||
/// one with the `to_string` method.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::ANSIString;
|
||||
/// use nu_ansi_term::Color::Red;
|
||||
///
|
||||
/// let red_string = Red.paint("a red string");
|
||||
/// println!("{}", red_string);
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::ANSIString;
|
||||
///
|
||||
/// let plain_string = ANSIString::from("a plain string");
|
||||
/// assert_eq!(&*plain_string, "a plain string");
|
||||
/// ```
|
||||
pub type AnsiString<'a> = AnsiGenericString<'a, str>;
|
||||
|
||||
/// An `AnsiByteString` represents a formatted series of bytes. Use
|
||||
/// `AnsiByteString` when styling text with an unknown encoding.
|
||||
pub type AnsiByteString<'a> = AnsiGenericString<'a, [u8]>;
|
||||
|
||||
impl<'a, I, S: 'a + ToOwned + ?Sized> From<I> for AnsiGenericString<'a, S>
|
||||
where
|
||||
I: Into<Cow<'a, S>>,
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
fn from(input: I) -> AnsiGenericString<'a, S> {
|
||||
AnsiGenericString {
|
||||
string: input.into(),
|
||||
style: Style::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericString<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
/// Directly access the style
|
||||
pub fn style_ref(&self) -> &Style {
|
||||
&self.style
|
||||
}
|
||||
|
||||
/// Directly access the style mutably
|
||||
pub fn style_ref_mut(&mut self) -> &mut Style {
|
||||
&mut self.style
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + ToOwned + ?Sized> Deref for AnsiGenericString<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
type Target = S;
|
||||
|
||||
fn deref(&self) -> &S {
|
||||
self.string.deref()
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of `AnsiGenericStrings`s collected together, in order to be
|
||||
/// written with a minimum of control characters.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct AnsiGenericStrings<'a, S: 'a + ToOwned + ?Sized>(pub &'a [AnsiGenericString<'a, S>])
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
S: PartialEq;
|
||||
|
||||
/// A set of `AnsiString`s collected together, in order to be written with a
|
||||
/// minimum of control characters.
|
||||
pub type AnsiStrings<'a> = AnsiGenericStrings<'a, str>;
|
||||
|
||||
/// A function to construct an `AnsiStrings` instance.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn AnsiStrings<'a>(arg: &'a [AnsiString<'a>]) -> AnsiStrings<'a> {
|
||||
AnsiGenericStrings(arg)
|
||||
}
|
||||
|
||||
/// A set of `AnsiByteString`s collected together, in order to be
|
||||
/// written with a minimum of control characters.
|
||||
pub type AnsiByteStrings<'a> = AnsiGenericStrings<'a, [u8]>;
|
||||
|
||||
/// A function to construct an `ANSIByteStrings` instance.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ANSIByteStrings<'a>(arg: &'a [AnsiByteString<'a>]) -> AnsiByteStrings<'a> {
|
||||
AnsiGenericStrings(arg)
|
||||
}
|
||||
|
||||
// ---- paint functions ----
|
||||
|
||||
impl Style {
|
||||
/// Paints the given text with this color, returning an ANSI string.
|
||||
#[must_use]
|
||||
pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> AnsiGenericString<'a, S>
|
||||
where
|
||||
I: Into<Cow<'a, S>>,
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
AnsiGenericString {
|
||||
string: input.into(),
|
||||
style: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// Paints the given text with this color, returning an ANSI string.
|
||||
/// This is a short-cut so you don’t have to use `Blue.normal()` just
|
||||
/// to get blue text.
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color::Blue;
|
||||
/// println!("{}", Blue.paint("da ba dee"));
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> AnsiGenericString<'a, S>
|
||||
where
|
||||
I: Into<Cow<'a, S>>,
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
{
|
||||
AnsiGenericString {
|
||||
string: input.into(),
|
||||
style: self.normal(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- writers for individual ANSI strings ----
|
||||
|
||||
impl<'a> fmt::Display for AnsiString<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let w: &mut dyn fmt::Write = f;
|
||||
self.write_to_any(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AnsiByteString<'a> {
|
||||
/// Write an `ANSIByteString` to an `io::Write`. This writes the escape
|
||||
/// sequences for the associated `Style` around the bytes.
|
||||
pub fn write_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
let w: &mut dyn io::Write = w;
|
||||
self.write_to_any(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericString<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
&'a S: AsRef<[u8]>,
|
||||
{
|
||||
fn write_to_any<W: AnyWrite<Wstr = S> + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
|
||||
write!(w, "{}", self.style.prefix())?;
|
||||
w.write_str(self.string.as_ref())?;
|
||||
write!(w, "{}", self.style.suffix())
|
||||
}
|
||||
}
|
||||
|
||||
// ---- writers for combined ANSI strings ----
|
||||
|
||||
impl<'a> fmt::Display for AnsiStrings<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let f: &mut dyn fmt::Write = f;
|
||||
self.write_to_any(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AnsiByteStrings<'a> {
|
||||
/// Write `ANSIByteStrings` to an `io::Write`. This writes the minimal
|
||||
/// escape sequences for the associated `Style`s around each set of
|
||||
/// bytes.
|
||||
pub fn write_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
let w: &mut dyn io::Write = w;
|
||||
self.write_to_any(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + ToOwned + ?Sized + PartialEq> AnsiGenericStrings<'a, S>
|
||||
where
|
||||
<S as ToOwned>::Owned: fmt::Debug,
|
||||
&'a S: AsRef<[u8]>,
|
||||
{
|
||||
fn write_to_any<W: AnyWrite<Wstr = S> + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
|
||||
use self::Difference::*;
|
||||
|
||||
let first = match self.0.first() {
|
||||
None => return Ok(()),
|
||||
Some(f) => f,
|
||||
};
|
||||
|
||||
write!(w, "{}", first.style.prefix())?;
|
||||
w.write_str(first.string.as_ref())?;
|
||||
|
||||
for window in self.0.windows(2) {
|
||||
match Difference::between(&window[0].style, &window[1].style) {
|
||||
ExtraStyles(style) => write!(w, "{}", style.prefix())?,
|
||||
Reset => write!(w, "{}{}", RESET, window[1].style.prefix())?,
|
||||
Empty => { /* Do nothing! */ }
|
||||
}
|
||||
|
||||
w.write_str(&window[1].string)?;
|
||||
}
|
||||
|
||||
// Write the final reset string after all of the ANSIStrings have been
|
||||
// written, *except* if the last one has no styles, because it would
|
||||
// have already been written by this point.
|
||||
if let Some(last) = self.0.last() {
|
||||
if !last.style.is_plain() {
|
||||
write!(w, "{}", RESET)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ---- tests ----
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
pub use super::super::AnsiStrings;
|
||||
pub use crate::style::Color::*;
|
||||
pub use crate::style::Style;
|
||||
|
||||
#[test]
|
||||
fn no_control_codes_for_plain() {
|
||||
let one = Style::default().paint("one");
|
||||
let two = Style::default().paint("two");
|
||||
let output = AnsiStrings(&[one, two]).to_string();
|
||||
assert_eq!(output, "onetwo");
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
use crate::{rgb::Rgb, Color};
|
||||
|
||||
/// Linear color gradient between two color stops
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Gradient {
|
||||
/// Start Color of Gradient
|
||||
pub start: Rgb,
|
||||
|
||||
/// End Color of Gradient
|
||||
pub end: Rgb,
|
||||
}
|
||||
|
||||
impl Gradient {
|
||||
/// Creates a new [Gradient] with two [Rgb] colors, `start` and `end`
|
||||
#[inline]
|
||||
pub const fn new(start: Rgb, end: Rgb) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
pub const fn from_color_rgb(start: Color, end: Color) -> Self {
|
||||
let start_grad = match start {
|
||||
Color::Rgb(r, g, b) => Rgb { r, g, b },
|
||||
_ => Rgb { r: 0, g: 0, b: 0 },
|
||||
};
|
||||
let end_grad = match end {
|
||||
Color::Rgb(r, g, b) => Rgb { r, g, b },
|
||||
_ => Rgb { r: 0, g: 0, b: 0 },
|
||||
};
|
||||
|
||||
Self {
|
||||
start: start_grad,
|
||||
end: end_grad,
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the [Rgb] color between `start` and `end` for `t`
|
||||
pub fn at(&self, t: f32) -> Rgb {
|
||||
self.start.lerp(self.end, t)
|
||||
}
|
||||
|
||||
/// Returns the reverse of `self`
|
||||
#[inline]
|
||||
pub const fn reverse(&self) -> Self {
|
||||
Self::new(self.end, self.start)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn build(&self, text: &str, target: TargetGround) -> String {
|
||||
let delta = 1.0 / text.len() as f32;
|
||||
let mut result = text.char_indices().fold(String::new(), |mut acc, (i, c)| {
|
||||
let temp = format!(
|
||||
"\x1B[{}m{}",
|
||||
self.at(i as f32 * delta).ansi_color_code(target),
|
||||
c
|
||||
);
|
||||
acc.push_str(&temp);
|
||||
acc
|
||||
});
|
||||
|
||||
result.push_str("\x1B[0m");
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn build_all_gradient_text(text: &str, foreground: Gradient, background: Gradient) -> String {
|
||||
let delta = 1.0 / text.len() as f32;
|
||||
let mut result = text.char_indices().fold(String::new(), |mut acc, (i, c)| {
|
||||
let step = i as f32 * delta;
|
||||
let temp = format!(
|
||||
"\x1B[{};{}m{}",
|
||||
foreground
|
||||
.at(step)
|
||||
.ansi_color_code(TargetGround::Foreground),
|
||||
background
|
||||
.at(step)
|
||||
.ansi_color_code(TargetGround::Background),
|
||||
c
|
||||
);
|
||||
acc.push_str(&temp);
|
||||
acc
|
||||
});
|
||||
|
||||
result.push_str("\x1B[0m");
|
||||
result
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TargetGround {
|
||||
Foreground,
|
||||
Background,
|
||||
}
|
||||
|
||||
impl TargetGround {
|
||||
#[inline]
|
||||
pub const fn code(&self) -> u8 {
|
||||
match self {
|
||||
Self::Foreground => 30,
|
||||
Self::Background => 40,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ANSIColorCode {
|
||||
fn ansi_color_code(&self, target: TargetGround) -> String;
|
||||
}
|
@ -1,272 +0,0 @@
|
||||
//! This is a library for controlling colors and formatting, such as
|
||||
//! red bold text or blue underlined text, on ANSI terminals.
|
||||
//!
|
||||
//!
|
||||
//! ## Basic usage
|
||||
//!
|
||||
//! There are three main types in this crate that you need to be
|
||||
//! concerned with: [`ANSIString`], [`Style`], and [`Color`].
|
||||
//!
|
||||
//! A `Style` holds stylistic information: foreground and background colors,
|
||||
//! whether the text should be bold, or blinking, or other properties. The
|
||||
//! [`Color`] enum represents the available colors. And an [`ANSIString`] is a
|
||||
//! string paired with a [`Style`].
|
||||
//!
|
||||
//! [`Color`] is also available as an alias to `Color`.
|
||||
//!
|
||||
//! To format a string, call the `paint` method on a `Style` or a `Color`,
|
||||
//! passing in the string you want to format as the argument. For example,
|
||||
//! here’s how to get some red text:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Red;
|
||||
//!
|
||||
//! println!("This is in red: {}", Red.paint("a red string"));
|
||||
//! ```
|
||||
//!
|
||||
//! It’s important to note that the `paint` method does *not* actually return a
|
||||
//! string with the ANSI control characters surrounding it. Instead, it returns
|
||||
//! an [`ANSIString`] value that has a [`Display`] implementation that, when
|
||||
//! formatted, returns the characters. This allows strings to be printed with a
|
||||
//! minimum of [`String`] allocations being performed behind the scenes.
|
||||
//!
|
||||
//! If you *do* want to get at the escape codes, then you can convert the
|
||||
//! [`ANSIString`] to a string as you would any other `Display` value:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Red;
|
||||
//!
|
||||
//! let red_string = Red.paint("a red string").to_string();
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//! ## Bold, underline, background, and other styles
|
||||
//!
|
||||
//! For anything more complex than plain foreground color changes, you need to
|
||||
//! construct `Style` values themselves, rather than beginning with a `Color`.
|
||||
//! You can do this by chaining methods based on a new `Style`, created with
|
||||
//! [`Style::new()`]. Each method creates a new style that has that specific
|
||||
//! property set. For example:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Style;
|
||||
//!
|
||||
//! println!("How about some {} and {}?",
|
||||
//! Style::new().bold().paint("bold"),
|
||||
//! Style::new().underline().paint("underline"));
|
||||
//! ```
|
||||
//!
|
||||
//! For brevity, these methods have also been implemented for `Color` values,
|
||||
//! so you can give your styles a foreground color without having to begin with
|
||||
//! an empty `Style` value:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::{Blue, Yellow};
|
||||
//!
|
||||
//! println!("Demonstrating {} and {}!",
|
||||
//! Blue.bold().paint("blue bold"),
|
||||
//! Yellow.underline().paint("yellow underline"));
|
||||
//!
|
||||
//! println!("Yellow on blue: {}", Yellow.on(Blue).paint("wow!"));
|
||||
//! ```
|
||||
//!
|
||||
//! The complete list of styles you can use are: [`bold`], [`dimmed`], [`italic`],
|
||||
//! [`underline`], [`blink`], [`reverse`], [`hidden`], [`strikethrough`], and [`on`] for
|
||||
//! background colors.
|
||||
//!
|
||||
//! In some cases, you may find it easier to change the foreground on an
|
||||
//! existing `Style` rather than starting from the appropriate `Color`.
|
||||
//! You can do this using the [`fg`] method:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Style;
|
||||
//! use nu_ansi_term::Color::{Blue, Cyan, Yellow};
|
||||
//!
|
||||
//! println!("Yellow on blue: {}", Style::new().on(Blue).fg(Yellow).paint("yow!"));
|
||||
//! println!("Also yellow on blue: {}", Cyan.on(Blue).fg(Yellow).paint("zow!"));
|
||||
//! ```
|
||||
//!
|
||||
//! You can turn a `Color` into a `Style` with the [`normal`] method.
|
||||
//! This will produce the exact same `ANSIString` as if you just used the
|
||||
//! `paint` method on the `Color` directly, but it’s useful in certain cases:
|
||||
//! for example, you may have a method that returns `Styles`, and need to
|
||||
//! represent both the “red bold” and “red, but not bold” styles with values of
|
||||
//! the same type. The `Style` struct also has a [`Default`] implementation if you
|
||||
//! want to have a style with *nothing* set.
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Style;
|
||||
//! use nu_ansi_term::Color::Red;
|
||||
//!
|
||||
//! Red.normal().paint("yet another red string");
|
||||
//! Style::default().paint("a completely regular string");
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//! ## Extended colors
|
||||
//!
|
||||
//! You can access the extended range of 256 colors by using the `Color::Fixed`
|
||||
//! variant, which takes an argument of the color number to use. This can be
|
||||
//! included wherever you would use a `Color`:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Fixed;
|
||||
//!
|
||||
//! Fixed(134).paint("A sort of light purple");
|
||||
//! Fixed(221).on(Fixed(124)).paint("Mustard in the ketchup");
|
||||
//! ```
|
||||
//!
|
||||
//! The first sixteen of these values are the same as the normal and bold
|
||||
//! standard color variants. There’s nothing stopping you from using these as
|
||||
//! `Fixed` colors instead, but there’s nothing to be gained by doing so
|
||||
//! either.
|
||||
//!
|
||||
//! You can also access full 24-bit color by using the `Color::Rgb` variant,
|
||||
//! which takes separate `u8` arguments for red, green, and blue:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Rgb;
|
||||
//!
|
||||
//! Rgb(70, 130, 180).paint("Steel blue");
|
||||
//! ```
|
||||
//!
|
||||
//! ## Combining successive colored strings
|
||||
//!
|
||||
//! The benefit of writing ANSI escape codes to the terminal is that they
|
||||
//! *stack*: you do not need to end every colored string with a reset code if
|
||||
//! the text that follows it is of a similar style. For example, if you want to
|
||||
//! have some blue text followed by some blue bold text, it’s possible to send
|
||||
//! the ANSI code for blue, followed by the ANSI code for bold, and finishing
|
||||
//! with a reset code without having to have an extra one between the two
|
||||
//! strings.
|
||||
//!
|
||||
//! This crate can optimise the ANSI codes that get printed in situations like
|
||||
//! this, making life easier for your terminal renderer. The [`ANSIStrings`]
|
||||
//! type takes a slice of several [`ANSIString`] values, and will iterate over
|
||||
//! each of them, printing only the codes for the styles that need to be updated
|
||||
//! as part of its formatting routine.
|
||||
//!
|
||||
//! The following code snippet uses this to enclose a binary number displayed in
|
||||
//! red bold text inside some red, but not bold, brackets:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Red;
|
||||
//! use nu_ansi_term::{ANSIString, ANSIStrings};
|
||||
//!
|
||||
//! let some_value = format!("{:b}", 42);
|
||||
//! let strings: &[ANSIString<'static>] = &[
|
||||
//! Red.paint("["),
|
||||
//! Red.bold().paint(some_value),
|
||||
//! Red.paint("]"),
|
||||
//! ];
|
||||
//!
|
||||
//! println!("Value: {}", ANSIStrings(strings));
|
||||
//! ```
|
||||
//!
|
||||
//! There are several things to note here. Firstly, the [`paint`] method can take
|
||||
//! *either* an owned [`String`] or a borrowed [`&str`]. Internally, an [`ANSIString`]
|
||||
//! holds a copy-on-write ([`Cow`]) string value to deal with both owned and
|
||||
//! borrowed strings at the same time. This is used here to display a `String`,
|
||||
//! the result of the `format!` call, using the same mechanism as some
|
||||
//! statically-available `&str` slices. Secondly, that the [`ANSIStrings`] value
|
||||
//! works in the same way as its singular counterpart, with a [`Display`]
|
||||
//! implementation that only performs the formatting when required.
|
||||
//!
|
||||
//! ## Byte strings
|
||||
//!
|
||||
//! This library also supports formatting `\[u8]` byte strings; this supports
|
||||
//! applications working with text in an unknown encoding. [`Style`] and
|
||||
//! [`Color`] support painting `\[u8]` values, resulting in an [`ANSIByteString`].
|
||||
//! This type does not implement [`Display`], as it may not contain UTF-8, but
|
||||
//! it does provide a method [`write_to`] to write the result to any value that
|
||||
//! implements [`Write`]:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Green;
|
||||
//!
|
||||
//! Green.paint("user data".as_bytes()).write_to(&mut std::io::stdout()).unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! Similarly, the type [`ANSIByteStrings`] supports writing a list of
|
||||
//! [`ANSIByteString`] values with minimal escape sequences:
|
||||
//!
|
||||
//! ```
|
||||
//! use nu_ansi_term::Color::Green;
|
||||
//! use nu_ansi_term::ANSIByteStrings;
|
||||
//!
|
||||
//! ANSIByteStrings(&[
|
||||
//! Green.paint("user data 1\n".as_bytes()),
|
||||
//! Green.bold().paint("user data 2\n".as_bytes()),
|
||||
//! ]).write_to(&mut std::io::stdout()).unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! [`Cow`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html
|
||||
//! [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html
|
||||
//! [`Default`]: https://doc.rust-lang.org/std/default/trait.Default.html
|
||||
//! [`String`]: https://doc.rust-lang.org/std/string/struct.String.html
|
||||
//! [`&str`]: https://doc.rust-lang.org/std/primitive.str.html
|
||||
//! [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
|
||||
//! [`Style`]: struct.Style.html
|
||||
//! [`Style::new()`]: struct.Style.html#method.new
|
||||
//! [`Color`]: enum.Color.html
|
||||
//! [`Color`]: enum.Color.html
|
||||
//! [`ANSIString`]: type.ANSIString.html
|
||||
//! [`ANSIStrings`]: type.ANSIStrings.html
|
||||
//! [`ANSIByteString`]: type.ANSIByteString.html
|
||||
//! [`ANSIByteStrings`]: type.ANSIByteStrings.html
|
||||
//! [`write_to`]: type.ANSIByteString.html#method.write_to
|
||||
//! [`paint`]: type.ANSIByteString.html#method.write_to
|
||||
//! [`normal`]: enum.Color.html#method.normal
|
||||
//!
|
||||
//! [`bold`]: struct.Style.html#method.bold
|
||||
//! [`dimmed`]: struct.Style.html#method.dimmed
|
||||
//! [`italic`]: struct.Style.html#method.italic
|
||||
//! [`underline`]: struct.Style.html#method.underline
|
||||
//! [`blink`]: struct.Style.html#method.blink
|
||||
//! [`reverse`]: struct.Style.html#method.reverse
|
||||
//! [`hidden`]: struct.Style.html#method.hidden
|
||||
//! [`strikethrough`]: struct.Style.html#method.strikethrough
|
||||
//! [`fg`]: struct.Style.html#method.fg
|
||||
//! [`on`]: struct.Style.html#method.on
|
||||
|
||||
#![crate_name = "nu_ansi_term"]
|
||||
#![crate_type = "rlib"]
|
||||
#![warn(missing_copy_implementations)]
|
||||
// #![warn(missing_docs)]
|
||||
#![warn(trivial_casts, trivial_numeric_casts)]
|
||||
// #![warn(unused_extern_crates, unused_qualifications)]
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
extern crate winapi;
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate doc_comment;
|
||||
|
||||
#[cfg(test)]
|
||||
doctest!("../README.md");
|
||||
|
||||
pub mod ansi;
|
||||
pub use ansi::{Infix, Prefix, Suffix};
|
||||
|
||||
mod style;
|
||||
pub use style::{Color, Style};
|
||||
|
||||
mod difference;
|
||||
mod display;
|
||||
pub use display::*;
|
||||
|
||||
mod write;
|
||||
|
||||
mod windows;
|
||||
pub use windows::*;
|
||||
|
||||
mod util;
|
||||
pub use util::*;
|
||||
|
||||
mod debug;
|
||||
|
||||
pub mod gradient;
|
||||
pub use gradient::*;
|
||||
|
||||
mod rgb;
|
||||
pub use rgb::*;
|
@ -1,173 +0,0 @@
|
||||
// Code liberally borrowed from here
|
||||
// https://github.com/navierr/coloriz
|
||||
use std::ops;
|
||||
use std::u32;
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Rgb {
|
||||
/// Red
|
||||
pub r: u8,
|
||||
/// Green
|
||||
pub g: u8,
|
||||
/// Blue
|
||||
pub b: u8,
|
||||
}
|
||||
|
||||
impl Rgb {
|
||||
/// Creates a new [Rgb] color
|
||||
#[inline]
|
||||
pub const fn new(r: u8, g: u8, b: u8) -> Self {
|
||||
Self { r, g, b }
|
||||
}
|
||||
|
||||
/// Creates a new [Rgb] color with a hex code
|
||||
#[inline]
|
||||
pub const fn from_hex(hex: u32) -> Self {
|
||||
Self::new((hex >> 16) as u8, (hex >> 8) as u8, hex as u8)
|
||||
}
|
||||
|
||||
pub fn from_hex_string(hex: String) -> Self {
|
||||
if hex.chars().count() == 8 && hex.starts_with("0x") {
|
||||
// eprintln!("hex:{:?}", hex);
|
||||
let (_, value_string) = hex.split_at(2);
|
||||
// eprintln!("value_string:{:?}", value_string);
|
||||
let int_val = u64::from_str_radix(value_string, 16);
|
||||
match int_val {
|
||||
Ok(num) => Self::new(
|
||||
((num & 0xff0000) >> 16) as u8,
|
||||
((num & 0xff00) >> 8) as u8,
|
||||
(num & 0xff) as u8,
|
||||
),
|
||||
// Don't fail, just make the color black
|
||||
// Should we fail?
|
||||
_ => Self::new(0, 0, 0),
|
||||
}
|
||||
} else {
|
||||
// Don't fail, just make the color black.
|
||||
// Should we fail?
|
||||
Self::new(0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [Rgb] color with three [f32] values
|
||||
pub fn from_f32(r: f32, g: f32, b: f32) -> Self {
|
||||
Self::new(
|
||||
(r.clamp(0.0, 1.0) * 255.0) as u8,
|
||||
(g.clamp(0.0, 1.0) * 255.0) as u8,
|
||||
(b.clamp(0.0, 1.0) * 255.0) as u8,
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a grayscale [Rgb] color
|
||||
#[inline]
|
||||
pub const fn gray(x: u8) -> Self {
|
||||
Self::new(x, x, x)
|
||||
}
|
||||
|
||||
/// Creates a grayscale [Rgb] color with a [f32] value
|
||||
pub fn gray_f32(x: f32) -> Self {
|
||||
Self::from_f32(x, x, x)
|
||||
}
|
||||
|
||||
/// Creates a new [Rgb] color from a [HSL] color
|
||||
// pub fn from_hsl(hsl: HSL) -> Self {
|
||||
// if hsl.s == 0.0 {
|
||||
// return Self::gray_f32(hsl.l);
|
||||
// }
|
||||
|
||||
// let q = if hsl.l < 0.5 {
|
||||
// hsl.l * (1.0 + hsl.s)
|
||||
// } else {
|
||||
// hsl.l + hsl.s - hsl.l * hsl.s
|
||||
// };
|
||||
// let p = 2.0 * hsl.l - q;
|
||||
// let h2c = |t: f32| {
|
||||
// let t = t.clamp(0.0, 1.0);
|
||||
// if 6.0 * t < 1.0 {
|
||||
// p + 6.0 * (q - p) * t
|
||||
// } else if t < 0.5 {
|
||||
// q
|
||||
// } else if 1.0 < 1.5 * t {
|
||||
// p + 6.0 * (q - p) * (1.0 / 1.5 - t)
|
||||
// } else {
|
||||
// p
|
||||
// }
|
||||
// };
|
||||
|
||||
// Self::from_f32(h2c(hsl.h + 1.0 / 3.0), h2c(hsl.h), h2c(hsl.h - 1.0 / 3.0))
|
||||
// }
|
||||
|
||||
/// Computes the linear interpolation between `self` and `other` for `t`
|
||||
pub fn lerp(&self, other: Self, t: f32) -> Self {
|
||||
let t = t.clamp(0.0, 1.0);
|
||||
self * (1.0 - t) + other * t
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u8, u8, u8)> for Rgb {
|
||||
fn from((r, g, b): (u8, u8, u8)) -> Self {
|
||||
Self::new(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(f32, f32, f32)> for Rgb {
|
||||
fn from((r, g, b): (f32, f32, f32)) -> Self {
|
||||
Self::from_f32(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
use crate::ANSIColorCode;
|
||||
use crate::TargetGround;
|
||||
impl ANSIColorCode for Rgb {
|
||||
fn ansi_color_code(&self, target: TargetGround) -> String {
|
||||
format!("{};2;{};{};{}", target.code() + 8, self.r, self.g, self.b)
|
||||
}
|
||||
}
|
||||
|
||||
overload::overload!(
|
||||
(lhs: ?Rgb) + (rhs: ?Rgb) -> Rgb {
|
||||
Rgb::new(
|
||||
lhs.r.saturating_add(rhs.r),
|
||||
lhs.g.saturating_add(rhs.g),
|
||||
lhs.b.saturating_add(rhs.b)
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
overload::overload!(
|
||||
(lhs: ?Rgb) - (rhs: ?Rgb) -> Rgb {
|
||||
Rgb::new(
|
||||
lhs.r.saturating_sub(rhs.r),
|
||||
lhs.g.saturating_sub(rhs.g),
|
||||
lhs.b.saturating_sub(rhs.b)
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
overload::overload!(
|
||||
(lhs: ?Rgb) * (rhs: ?f32) -> Rgb {
|
||||
Rgb::new(
|
||||
(lhs.r as f32 * rhs.clamp(0.0, 1.0)) as u8,
|
||||
(lhs.g as f32 * rhs.clamp(0.0, 1.0)) as u8,
|
||||
(lhs.b as f32 * rhs.clamp(0.0, 1.0)) as u8
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
overload::overload!(
|
||||
(lhs: ?f32) * (rhs: ?Rgb) -> Rgb {
|
||||
Rgb::new(
|
||||
(rhs.r as f32 * lhs.clamp(0.0, 1.0)) as u8,
|
||||
(rhs.g as f32 * lhs.clamp(0.0, 1.0)) as u8,
|
||||
(rhs.b as f32 * lhs.clamp(0.0, 1.0)) as u8
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
overload::overload!(
|
||||
-(rgb: ?Rgb) -> Rgb {
|
||||
Rgb::new(
|
||||
255 - rgb.r,
|
||||
255 - rgb.g,
|
||||
255 - rgb.b)
|
||||
}
|
||||
);
|
@ -1,626 +0,0 @@
|
||||
/// A style is a collection of properties that can format a string
|
||||
/// using ANSI escape codes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color};
|
||||
///
|
||||
/// let style = Style::new().bold().on(Color::Black);
|
||||
/// println!("{}", style.paint("Bold on black"));
|
||||
/// ```
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
#[cfg_attr(
|
||||
feature = "derive_serde_style",
|
||||
derive(serde::Deserialize, serde::Serialize)
|
||||
)]
|
||||
pub struct Style {
|
||||
/// The style's foreground color, if it has one.
|
||||
pub foreground: Option<Color>,
|
||||
|
||||
/// The style's background color, if it has one.
|
||||
pub background: Option<Color>,
|
||||
|
||||
/// Whether this style is bold.
|
||||
pub is_bold: bool,
|
||||
|
||||
/// Whether this style is dimmed.
|
||||
pub is_dimmed: bool,
|
||||
|
||||
/// Whether this style is italic.
|
||||
pub is_italic: bool,
|
||||
|
||||
/// Whether this style is underlined.
|
||||
pub is_underline: bool,
|
||||
|
||||
/// Whether this style is blinking.
|
||||
pub is_blink: bool,
|
||||
|
||||
/// Whether this style has reverse colors.
|
||||
pub is_reverse: bool,
|
||||
|
||||
/// Whether this style is hidden.
|
||||
pub is_hidden: bool,
|
||||
|
||||
/// Whether this style is struckthrough.
|
||||
pub is_strikethrough: bool,
|
||||
}
|
||||
|
||||
impl Style {
|
||||
/// Creates a new Style with no properties set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new();
|
||||
/// println!("{}", style.paint("hi"));
|
||||
/// ```
|
||||
pub fn new() -> Style {
|
||||
Style::default()
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the bold property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().bold();
|
||||
/// println!("{}", style.paint("hey"));
|
||||
/// ```
|
||||
pub fn bold(&self) -> Style {
|
||||
Style {
|
||||
is_bold: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the dimmed property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().dimmed();
|
||||
/// println!("{}", style.paint("sup"));
|
||||
/// ```
|
||||
pub fn dimmed(&self) -> Style {
|
||||
Style {
|
||||
is_dimmed: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the italic property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().italic();
|
||||
/// println!("{}", style.paint("greetings"));
|
||||
/// ```
|
||||
pub fn italic(&self) -> Style {
|
||||
Style {
|
||||
is_italic: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the underline property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().underline();
|
||||
/// println!("{}", style.paint("salutations"));
|
||||
/// ```
|
||||
pub fn underline(&self) -> Style {
|
||||
Style {
|
||||
is_underline: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the blink property set.
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().blink();
|
||||
/// println!("{}", style.paint("wazzup"));
|
||||
/// ```
|
||||
pub fn blink(&self) -> Style {
|
||||
Style {
|
||||
is_blink: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the reverse property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().reverse();
|
||||
/// println!("{}", style.paint("aloha"));
|
||||
/// ```
|
||||
pub fn reverse(&self) -> Style {
|
||||
Style {
|
||||
is_reverse: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the hidden property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().hidden();
|
||||
/// println!("{}", style.paint("ahoy"));
|
||||
/// ```
|
||||
pub fn hidden(&self) -> Style {
|
||||
Style {
|
||||
is_hidden: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the strikethrough property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// let style = Style::new().strikethrough();
|
||||
/// println!("{}", style.paint("yo"));
|
||||
/// ```
|
||||
pub fn strikethrough(&self) -> Style {
|
||||
Style {
|
||||
is_strikethrough: true,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color};
|
||||
///
|
||||
/// let style = Style::new().fg(Color::Yellow);
|
||||
/// println!("{}", style.paint("hi"));
|
||||
/// ```
|
||||
pub fn fg(&self, foreground: Color) -> Style {
|
||||
Style {
|
||||
foreground: Some(foreground),
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the background color property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color};
|
||||
///
|
||||
/// let style = Style::new().on(Color::Blue);
|
||||
/// println!("{}", style.paint("eyyyy"));
|
||||
/// ```
|
||||
pub fn on(&self, background: Color) -> Style {
|
||||
Style {
|
||||
background: Some(background),
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if this `Style` has no actual styles, and can be written
|
||||
/// without any control characters.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
///
|
||||
/// assert_eq!(true, Style::default().is_plain());
|
||||
/// assert_eq!(false, Style::default().bold().is_plain());
|
||||
/// ```
|
||||
pub fn is_plain(self) -> bool {
|
||||
self == Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
/// Returns a style with *no* properties set. Formatting text using this
|
||||
/// style returns the exact same text.
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Style;
|
||||
/// assert_eq!(None, Style::default().foreground);
|
||||
/// assert_eq!(None, Style::default().background);
|
||||
/// assert_eq!(false, Style::default().is_bold);
|
||||
/// assert_eq!("txt", Style::default().paint("txt").to_string());
|
||||
/// ```
|
||||
fn default() -> Style {
|
||||
Style {
|
||||
foreground: None,
|
||||
background: None,
|
||||
is_bold: false,
|
||||
is_dimmed: false,
|
||||
is_italic: false,
|
||||
is_underline: false,
|
||||
is_blink: false,
|
||||
is_reverse: false,
|
||||
is_hidden: false,
|
||||
is_strikethrough: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- colors ----
|
||||
|
||||
/// A color is one specific type of ANSI escape code, and can refer
|
||||
/// to either the foreground or background color.
|
||||
///
|
||||
/// These use the standard numeric sequences.
|
||||
/// See <http://invisible-island.net/xterm/ctlseqs/ctlseqs.html>
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
#[cfg_attr(
|
||||
feature = "derive_serde_style",
|
||||
derive(serde::Deserialize, serde::Serialize)
|
||||
)]
|
||||
pub enum Color {
|
||||
/// Color #0 (foreground code `30`, background code `40`).
|
||||
///
|
||||
/// This is not necessarily the background color, and using it as one may
|
||||
/// render the text hard to read on terminals with dark backgrounds.
|
||||
Black,
|
||||
|
||||
/// Color #0 (foreground code `90`, background code `100`).
|
||||
DarkGray,
|
||||
|
||||
/// Color #1 (foreground code `31`, background code `41`).
|
||||
Red,
|
||||
|
||||
/// Color #1 (foreground code `91`, background code `101`).
|
||||
LightRed,
|
||||
|
||||
/// Color #2 (foreground code `32`, background code `42`).
|
||||
Green,
|
||||
|
||||
/// Color #2 (foreground code `92`, background code `102`).
|
||||
LightGreen,
|
||||
|
||||
/// Color #3 (foreground code `33`, background code `43`).
|
||||
Yellow,
|
||||
|
||||
/// Color #3 (foreground code `93`, background code `103`).
|
||||
LightYellow,
|
||||
|
||||
/// Color #4 (foreground code `34`, background code `44`).
|
||||
Blue,
|
||||
|
||||
/// Color #4 (foreground code `94`, background code `104`).
|
||||
LightBlue,
|
||||
|
||||
/// Color #5 (foreground code `35`, background code `45`).
|
||||
Purple,
|
||||
|
||||
/// Color #5 (foreground code `95`, background code `105`).
|
||||
LightPurple,
|
||||
|
||||
/// Color #5 (foreground code `35`, background code `45`).
|
||||
Magenta,
|
||||
|
||||
/// Color #5 (foreground code `95`, background code `105`).
|
||||
LightMagenta,
|
||||
|
||||
/// Color #6 (foreground code `36`, background code `46`).
|
||||
Cyan,
|
||||
|
||||
/// Color #6 (foreground code `96`, background code `106`).
|
||||
LightCyan,
|
||||
|
||||
/// Color #7 (foreground code `37`, background code `47`).
|
||||
///
|
||||
/// As above, this is not necessarily the foreground color, and may be
|
||||
/// hard to read on terminals with light backgrounds.
|
||||
White,
|
||||
|
||||
/// Color #7 (foreground code `97`, background code `107`).
|
||||
LightGray,
|
||||
|
||||
/// A color number from 0 to 255, for use in 256-color terminal
|
||||
/// environments.
|
||||
///
|
||||
/// - colors 0 to 7 are the `Black` to `White` variants respectively.
|
||||
/// These colors can usually be changed in the terminal emulator.
|
||||
/// - colors 8 to 15 are brighter versions of the eight colors above.
|
||||
/// These can also usually be changed in the terminal emulator, or it
|
||||
/// could be configured to use the original colors and show the text in
|
||||
/// bold instead. It varies depending on the program.
|
||||
/// - colors 16 to 231 contain several palettes of bright colors,
|
||||
/// arranged in six squares measuring six by six each.
|
||||
/// - colors 232 to 255 are shades of grey from black to white.
|
||||
///
|
||||
/// It might make more sense to look at a [color chart][cc].
|
||||
///
|
||||
/// [cc]: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg
|
||||
Fixed(u8),
|
||||
|
||||
/// A 24-bit Rgb color, as specified by ISO-8613-3.
|
||||
Rgb(u8, u8, u8),
|
||||
}
|
||||
|
||||
impl Default for Color {
|
||||
fn default() -> Self {
|
||||
Color::White
|
||||
}
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// Returns a `Style` with the foreground color set to this color.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Red.normal();
|
||||
/// println!("{}", style.paint("hi"));
|
||||
/// ```
|
||||
pub fn normal(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// bold property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Green.bold();
|
||||
/// println!("{}", style.paint("hey"));
|
||||
/// ```
|
||||
pub fn bold(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_bold: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// dimmed property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Yellow.dimmed();
|
||||
/// println!("{}", style.paint("sup"));
|
||||
/// ```
|
||||
pub fn dimmed(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_dimmed: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// italic property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Blue.italic();
|
||||
/// println!("{}", style.paint("greetings"));
|
||||
/// ```
|
||||
pub fn italic(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_italic: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// underline property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Purple.underline();
|
||||
/// println!("{}", style.paint("salutations"));
|
||||
/// ```
|
||||
pub fn underline(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_underline: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// blink property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Cyan.blink();
|
||||
/// println!("{}", style.paint("wazzup"));
|
||||
/// ```
|
||||
pub fn blink(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_blink: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// reverse property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Black.reverse();
|
||||
/// println!("{}", style.paint("aloha"));
|
||||
/// ```
|
||||
pub fn reverse(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_reverse: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// hidden property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::White.hidden();
|
||||
/// println!("{}", style.paint("ahoy"));
|
||||
/// ```
|
||||
pub fn hidden(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_hidden: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// strikethrough property set.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Fixed(244).strikethrough();
|
||||
/// println!("{}", style.paint("yo"));
|
||||
/// ```
|
||||
pub fn strikethrough(self) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
is_strikethrough: true,
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground color set to this color and the
|
||||
/// background color property set to the given color.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::Color;
|
||||
///
|
||||
/// let style = Color::Rgb(31, 31, 31).on(Color::White);
|
||||
/// println!("{}", style.paint("eyyyy"));
|
||||
/// ```
|
||||
pub fn on(self, background: Color) -> Style {
|
||||
Style {
|
||||
foreground: Some(self),
|
||||
background: Some(background),
|
||||
..Style::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for Style {
|
||||
/// You can turn a `Color` into a `Style` with the foreground color set
|
||||
/// with the `From` trait.
|
||||
///
|
||||
/// ```
|
||||
/// use nu_ansi_term::{Style, Color};
|
||||
/// let green_foreground = Style::default().fg(Color::Green);
|
||||
/// assert_eq!(green_foreground, Color::Green.normal());
|
||||
/// assert_eq!(green_foreground, Color::Green.into());
|
||||
/// assert_eq!(green_foreground, Style::from(Color::Green));
|
||||
/// ```
|
||||
fn from(color: Color) -> Style {
|
||||
color.normal()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "derive_serde_style")]
|
||||
mod serde_json_tests {
|
||||
use super::{Color, Style};
|
||||
|
||||
#[test]
|
||||
fn color_serialization() {
|
||||
let colors = &[
|
||||
Color::Red,
|
||||
Color::Blue,
|
||||
Color::Rgb(123, 123, 123),
|
||||
Color::Fixed(255),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_string(&colors).unwrap(),
|
||||
String::from("[\"Red\",\"Blue\",{\"Rgb\":[123,123,123]},{\"Fixed\":255}]")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color_deserialization() {
|
||||
let colors = [
|
||||
Color::Red,
|
||||
Color::Blue,
|
||||
Color::Rgb(123, 123, 123),
|
||||
Color::Fixed(255),
|
||||
];
|
||||
|
||||
for color in colors {
|
||||
let serialized = serde_json::to_string(&color).unwrap();
|
||||
let deserialized: Color = serde_json::from_str(&serialized).unwrap();
|
||||
|
||||
assert_eq!(color, deserialized);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_serialization() {
|
||||
let style = Style::default();
|
||||
|
||||
assert_eq!(serde_json::to_string(&style).unwrap(), "{\"foreground\":null,\"background\":null,\"is_bold\":false,\"is_dimmed\":false,\"is_italic\":false,\"is_underline\":false,\"is_blink\":false,\"is_reverse\":false,\"is_hidden\":false,\"is_strikethrough\":false}".to_string());
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
use crate::display::{AnsiString, AnsiStrings};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Return a substring of the given ANSIStrings sequence, while keeping the formatting.
|
||||
pub fn sub_string<'a>(
|
||||
start: usize,
|
||||
len: usize,
|
||||
strs: &AnsiStrings<'a>,
|
||||
) -> Vec<AnsiString<'static>> {
|
||||
let mut vec = Vec::new();
|
||||
let mut pos = start;
|
||||
let mut len_rem = len;
|
||||
|
||||
for i in strs.0.iter() {
|
||||
let fragment = i.deref();
|
||||
let frag_len = fragment.len();
|
||||
if pos >= frag_len {
|
||||
pos -= frag_len;
|
||||
continue;
|
||||
}
|
||||
if len_rem == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let end = pos + len_rem;
|
||||
let pos_end = if end >= frag_len { frag_len } else { end };
|
||||
|
||||
vec.push(i.style_ref().paint(String::from(&fragment[pos..pos_end])));
|
||||
|
||||
if end <= frag_len {
|
||||
break;
|
||||
}
|
||||
|
||||
len_rem -= pos_end - pos;
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
vec
|
||||
}
|
||||
|
||||
/// Return a concatenated copy of `strs` without the formatting, as an allocated `String`.
|
||||
pub fn unstyle(strs: &AnsiStrings) -> String {
|
||||
let mut s = String::new();
|
||||
|
||||
for i in strs.0.iter() {
|
||||
s += i.deref();
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
/// Return the unstyled length of ANSIStrings. This is equaivalent to `unstyle(strs).len()`.
|
||||
pub fn unstyled_len(strs: &AnsiStrings) -> usize {
|
||||
let mut l = 0;
|
||||
for i in strs.0.iter() {
|
||||
l += i.deref().len();
|
||||
}
|
||||
l
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::Color::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let l = [
|
||||
Black.paint("first"),
|
||||
Red.paint("-second"),
|
||||
White.paint("-third"),
|
||||
];
|
||||
let a = AnsiStrings(&l);
|
||||
assert_eq!(unstyle(&a), "first-second-third");
|
||||
assert_eq!(unstyled_len(&a), 18);
|
||||
|
||||
let l2 = [Black.paint("st"), Red.paint("-second"), White.paint("-t")];
|
||||
assert_eq!(sub_string(3, 11, &a), l2);
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
/// Enables ANSI code support on Windows 10.
|
||||
///
|
||||
/// This uses Windows API calls to alter the properties of the console that
|
||||
/// the program is running in.
|
||||
///
|
||||
/// https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx
|
||||
///
|
||||
/// Returns a `Result` with the Windows error code if unsuccessful.
|
||||
#[cfg(windows)]
|
||||
pub fn enable_ansi_support() -> Result<(), u32> {
|
||||
// ref: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#EXAMPLE_OF_ENABLING_VIRTUAL_TERMINAL_PROCESSING @@ https://archive.is/L7wRJ#76%
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::iter::once;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::ptr::null_mut;
|
||||
use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode};
|
||||
use winapi::um::errhandlingapi::GetLastError;
|
||||
use winapi::um::fileapi::{CreateFileW, OPEN_EXISTING};
|
||||
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
|
||||
use winapi::um::winnt::{FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE};
|
||||
|
||||
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
|
||||
|
||||
unsafe {
|
||||
// ref: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
|
||||
// Using `CreateFileW("CONOUT$", ...)` to retrieve the console handle works correctly even if STDOUT and/or STDERR are redirected
|
||||
let console_out_name: Vec<u16> =
|
||||
OsStr::new("CONOUT$").encode_wide().chain(once(0)).collect();
|
||||
let console_handle = CreateFileW(
|
||||
console_out_name.as_ptr(),
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_WRITE,
|
||||
null_mut(),
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
null_mut(),
|
||||
);
|
||||
if console_handle == INVALID_HANDLE_VALUE {
|
||||
return Err(GetLastError());
|
||||
}
|
||||
|
||||
// ref: https://docs.microsoft.com/en-us/windows/console/getconsolemode
|
||||
let mut console_mode: u32 = 0;
|
||||
if 0 == GetConsoleMode(console_handle, &mut console_mode) {
|
||||
return Err(GetLastError());
|
||||
}
|
||||
|
||||
// VT processing not already enabled?
|
||||
if console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
|
||||
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
|
||||
if 0 == SetConsoleMode(
|
||||
console_handle,
|
||||
console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING,
|
||||
) {
|
||||
return Err(GetLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
|
||||
pub trait AnyWrite {
|
||||
type Wstr: ?Sized;
|
||||
type Error;
|
||||
|
||||
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error>;
|
||||
|
||||
fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
impl<'a> AnyWrite for dyn fmt::Write + 'a {
|
||||
type Wstr = str;
|
||||
type Error = fmt::Error;
|
||||
|
||||
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error> {
|
||||
fmt::Write::write_fmt(self, fmt)
|
||||
}
|
||||
|
||||
fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error> {
|
||||
fmt::Write::write_str(self, s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AnyWrite for dyn io::Write + 'a {
|
||||
type Wstr = [u8];
|
||||
type Error = io::Error;
|
||||
|
||||
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error> {
|
||||
io::Write::write_fmt(self, fmt)
|
||||
}
|
||||
|
||||
fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error> {
|
||||
io::Write::write_all(self, s)
|
||||
}
|
||||
}
|
@ -1,43 +1,33 @@
|
||||
[package]
|
||||
authors = ["The Nu Project Contributors"]
|
||||
description = "CLI for nushell"
|
||||
edition = "2018"
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "CLI-related functionality for Nushell"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.43.0"
|
||||
build = "build.rs"
|
||||
version = "0.65.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="../nu-test-support", version = "0.65.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.65.0" }
|
||||
|
||||
[dependencies]
|
||||
nu-completion = { version = "0.43.0", path="../nu-completion" }
|
||||
nu-command = { version = "0.43.0", path="../nu-command" }
|
||||
nu-data = { version = "0.43.0", path="../nu-data" }
|
||||
nu-engine = { version = "0.43.0", path="../nu-engine" }
|
||||
nu-errors = { version = "0.43.0", path="../nu-errors" }
|
||||
nu-parser = { version = "0.43.0", path="../nu-parser" }
|
||||
nu-protocol = { version = "0.43.0", path="../nu-protocol" }
|
||||
nu-source = { version = "0.43.0", path="../nu-source" }
|
||||
nu-stream = { version = "0.43.0", path="../nu-stream" }
|
||||
nu-ansi-term = { version = "0.43.0", path="../nu-ansi-term" }
|
||||
nu-path = { version = "0.43.0", path="../nu-path" }
|
||||
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"
|
||||
|
||||
indexmap ="1.6.1"
|
||||
log = "0.4.14"
|
||||
pretty_env_logger = "0.4.0"
|
||||
strip-ansi-escapes = "0.1.0"
|
||||
rustyline = { version="9.0.0", optional=true }
|
||||
ctrlc = { version="3.1.7", optional=true }
|
||||
shadow-rs = { version = "0.8.1", default-features = false, optional = true }
|
||||
serde = { version="1.0.123", features=["derive"] }
|
||||
serde_yaml = "0.8.16"
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = "0.8.1"
|
||||
log = "0.4"
|
||||
is_executable = "1.0.1"
|
||||
chrono = "0.4.19"
|
||||
sysinfo = "0.24.1"
|
||||
|
||||
[features]
|
||||
default = ["shadow-rs"]
|
||||
rustyline-support = ["rustyline", "nu-engine/rustyline-support"]
|
||||
stable = []
|
||||
plugin = []
|
||||
|
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2014 Benjamin Sago
|
||||
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
|
@ -1,4 +0,0 @@
|
||||
# nu-cli
|
||||
|
||||
This crate provides the fundamental needs when creating the Nushell interactive REPL. In it, you'll find features for interacting with the line editor (the piece which writes the prompt and takes input from the user), keybindings, handlers for the commandline arguments passed to the REPL as it starts up, and more.
|
||||
|
@ -1,3 +0,0 @@
|
||||
fn main() -> shadow_rs::SdResult<()> {
|
||||
shadow_rs::new()
|
||||
}
|
@ -1,638 +0,0 @@
|
||||
mod logger;
|
||||
mod options;
|
||||
mod options_parser;
|
||||
pub mod stopwatch;
|
||||
|
||||
use self::stopwatch::Stopwatch;
|
||||
use lazy_static::lazy_static;
|
||||
use nu_command::{commands::NuSignature as Nu, utils::test_bins as binaries};
|
||||
use nu_engine::{get_full_help, EvaluationContext};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{Call, Expression, SpannedExpression, Synthetic};
|
||||
use nu_protocol::{Primitive, UntaggedValue};
|
||||
use nu_source::{Span, Tag};
|
||||
use nu_stream::InputStream;
|
||||
pub use options::{CliOptions, NuScript, Options};
|
||||
use options_parser::{NuParser, OptionsParser};
|
||||
use std::sync::Mutex;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref STOPWATCH: Mutex<Stopwatch> = {
|
||||
let mut sw = Stopwatch::default();
|
||||
sw.start();
|
||||
sw.stop();
|
||||
Mutex::new(sw)
|
||||
};
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
parser: Box<dyn OptionsParser>,
|
||||
pub options: Options,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(parser: Box<dyn OptionsParser>, options: Options) -> Self {
|
||||
Self { parser, options }
|
||||
}
|
||||
|
||||
pub fn run(args: &[String]) -> Result<(), ShellError> {
|
||||
let nu = Box::new(NuParser::new());
|
||||
let options = Options::default();
|
||||
let ui = App::new(nu, options);
|
||||
|
||||
ui.main(args)
|
||||
}
|
||||
|
||||
pub fn main(&self, argv: &[String]) -> Result<(), ShellError> {
|
||||
if self.perf() {
|
||||
// start the stopwatch running
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.start();
|
||||
}
|
||||
|
||||
let argv = quote_positionals(argv).join(" ");
|
||||
|
||||
if let Err(cause) = self.parse(&argv) {
|
||||
self.parser
|
||||
.context()
|
||||
.host()
|
||||
.lock()
|
||||
.print_err(cause, &nu_source::Text::from(argv));
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if self.help() {
|
||||
let context = self.parser.context();
|
||||
let stream = nu_stream::OutputStream::one(
|
||||
UntaggedValue::string(get_full_help(&Nu, &context.scope))
|
||||
.into_value(nu_source::Tag::unknown()),
|
||||
);
|
||||
|
||||
consume(context, stream)?;
|
||||
|
||||
if self.perf() {
|
||||
// stop the stopwatch since we're exiting
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.stop();
|
||||
eprintln!(
|
||||
"help {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed()
|
||||
);
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
if self.version() {
|
||||
let context = self.parser.context();
|
||||
|
||||
let stream = nu_command::commands::version(nu_engine::CommandArgs {
|
||||
context: context.clone(),
|
||||
call_info: nu_engine::UnevaluatedCallInfo {
|
||||
args: Call::new(
|
||||
Box::new(SpannedExpression::new(
|
||||
Expression::Synthetic(Synthetic::String("version".to_string())),
|
||||
Span::unknown(),
|
||||
)),
|
||||
Span::unknown(),
|
||||
),
|
||||
name_tag: Tag::unknown(),
|
||||
},
|
||||
input: InputStream::empty(),
|
||||
})?;
|
||||
|
||||
let stream = {
|
||||
let command = context
|
||||
.get_command("pivot")
|
||||
.expect("could not find version command");
|
||||
|
||||
context.run_command(
|
||||
command,
|
||||
Tag::unknown(),
|
||||
Call::new(
|
||||
Box::new(SpannedExpression::new(
|
||||
Expression::Synthetic(Synthetic::String("pivot".to_string())),
|
||||
Span::unknown(),
|
||||
)),
|
||||
Span::unknown(),
|
||||
),
|
||||
stream,
|
||||
)?
|
||||
};
|
||||
|
||||
consume(context, stream)?;
|
||||
|
||||
if self.perf() {
|
||||
// stop the stopwatch since we're exiting
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.stop();
|
||||
eprintln!(
|
||||
"version {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed()
|
||||
);
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
if let Some(bin) = self.testbin() {
|
||||
match bin.as_deref() {
|
||||
Ok("echo_env") => binaries::echo_env(),
|
||||
Ok("cococo") => binaries::cococo(),
|
||||
Ok("meow") => binaries::meow(),
|
||||
Ok("iecho") => binaries::iecho(),
|
||||
Ok("fail") => binaries::fail(),
|
||||
Ok("nonu") => binaries::nonu(),
|
||||
Ok("chop") => binaries::chop(),
|
||||
Ok("repeater") => binaries::repeater(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut opts = CliOptions::new();
|
||||
opts.config = self.config().map(std::ffi::OsString::from);
|
||||
opts.stdin = self.takes_stdin();
|
||||
opts.save_history = self.save_history();
|
||||
opts.perf = self.perf();
|
||||
|
||||
use logger::{configure, debug_filters, logger, trace_filters};
|
||||
|
||||
logger(|builder| {
|
||||
configure(self, builder)?;
|
||||
trace_filters(self, builder)?;
|
||||
debug_filters(self, builder)?;
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
if self.perf() {
|
||||
// start a new split
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.start()
|
||||
}
|
||||
|
||||
if let Some(commands) = self.commands() {
|
||||
let commands = commands?;
|
||||
let script = NuScript::code(&commands)?;
|
||||
opts.scripts = vec![script];
|
||||
let context = crate::create_default_context(false)?;
|
||||
return crate::run_script_file(context, opts);
|
||||
}
|
||||
|
||||
if self.perf() {
|
||||
// start a new spit
|
||||
eprintln!(
|
||||
"commands using -c at launch: {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed_split()
|
||||
);
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.start();
|
||||
}
|
||||
|
||||
if let Some(scripts) = self.scripts() {
|
||||
let mut source_files = vec![];
|
||||
for script in scripts {
|
||||
let script_name = script?;
|
||||
let path = std::ffi::OsString::from(&script_name);
|
||||
|
||||
match NuScript::source_file(path.as_os_str()) {
|
||||
Ok(file) => source_files.push(file),
|
||||
Err(_) => {
|
||||
eprintln!("File not found: {}", script_name);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for file in source_files {
|
||||
let mut opts = opts.clone();
|
||||
opts.scripts = vec![file];
|
||||
|
||||
let context = crate::create_default_context(false)?;
|
||||
crate::run_script_file(context, opts)?;
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.perf() {
|
||||
// start a new split
|
||||
eprintln!(
|
||||
"script file(s) passed in: {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed_split()
|
||||
);
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.start();
|
||||
}
|
||||
|
||||
let context = crate::create_default_context(true)?;
|
||||
|
||||
if !self.skip_plugins() {
|
||||
let _ = crate::register_plugins(&context);
|
||||
}
|
||||
|
||||
if self.perf() {
|
||||
// start a new split
|
||||
eprintln!(
|
||||
"plugins registered: {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed_split()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
{
|
||||
crate::cli(context, opts)?;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "rustyline-support"))]
|
||||
{
|
||||
println!("Nushell needs the 'rustyline-support' feature for CLI support");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn commands(&self) -> Option<Result<String, ShellError>> {
|
||||
self.options.get("commands").map(|v| match v.value {
|
||||
UntaggedValue::Error(err) => Err(err),
|
||||
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name),
|
||||
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn perf(&self) -> bool {
|
||||
self.options
|
||||
.get("perf")
|
||||
.map(|v| matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn help(&self) -> bool {
|
||||
self.options
|
||||
.get("help")
|
||||
.map(|v| matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn version(&self) -> bool {
|
||||
self.options
|
||||
.get("version")
|
||||
.map(|v| matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn scripts(&self) -> Option<Vec<Result<String, ShellError>>> {
|
||||
self.options.get("args").map(|v| {
|
||||
v.table_entries()
|
||||
.map(|v| match &v.value {
|
||||
UntaggedValue::Error(err) => Err(err.clone()),
|
||||
UntaggedValue::Primitive(Primitive::FilePath(path)) => {
|
||||
Ok(path.display().to_string())
|
||||
}
|
||||
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name.clone()),
|
||||
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn takes_stdin(&self) -> bool {
|
||||
self.options
|
||||
.get("stdin")
|
||||
.map(|v| matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn config(&self) -> Option<String> {
|
||||
self.options
|
||||
.get("config-file")
|
||||
.map(|v| v.as_string().expect("not a string"))
|
||||
}
|
||||
|
||||
pub fn develop(&self) -> Option<Vec<Result<String, ShellError>>> {
|
||||
self.options.get("develop").map(|v| {
|
||||
let mut values = vec![];
|
||||
|
||||
match v.value {
|
||||
UntaggedValue::Error(err) => values.push(Err(err)),
|
||||
UntaggedValue::Primitive(Primitive::String(filters)) => {
|
||||
values.extend(filters.split(',').map(|filter| Ok(filter.to_string())));
|
||||
}
|
||||
_ => values.push(Err(ShellError::untagged_runtime_error(
|
||||
"Unsupported option",
|
||||
))),
|
||||
};
|
||||
|
||||
values
|
||||
})
|
||||
}
|
||||
|
||||
pub fn debug(&self) -> Option<Vec<Result<String, ShellError>>> {
|
||||
self.options.get("debug").map(|v| {
|
||||
let mut values = vec![];
|
||||
|
||||
match v.value {
|
||||
UntaggedValue::Error(err) => values.push(Err(err)),
|
||||
UntaggedValue::Primitive(Primitive::String(filters)) => {
|
||||
values.extend(filters.split(',').map(|filter| Ok(filter.to_string())));
|
||||
}
|
||||
_ => values.push(Err(ShellError::untagged_runtime_error(
|
||||
"Unsupported option",
|
||||
))),
|
||||
};
|
||||
|
||||
values
|
||||
})
|
||||
}
|
||||
|
||||
pub fn loglevel(&self) -> Option<Result<String, ShellError>> {
|
||||
self.options.get("loglevel").map(|v| match v.value {
|
||||
UntaggedValue::Error(err) => Err(err),
|
||||
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name),
|
||||
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn testbin(&self) -> Option<Result<String, ShellError>> {
|
||||
self.options.get("testbin").map(|v| match v.value {
|
||||
UntaggedValue::Error(err) => Err(err),
|
||||
UntaggedValue::Primitive(Primitive::String(name)) => Ok(name),
|
||||
_ => Err(ShellError::untagged_runtime_error("Unsupported option")),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn skip_plugins(&self) -> bool {
|
||||
self.options
|
||||
.get("skip-plugins")
|
||||
.map(|v| matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn save_history(&self) -> bool {
|
||||
self.options
|
||||
.get("no-history")
|
||||
.map(|v| !matches!(v.as_bool(), Ok(true)))
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
pub fn parse(&self, args: &str) -> Result<(), ShellError> {
|
||||
self.parser.parse(args).map(|options| {
|
||||
self.options.swap(&options);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn quote_positionals(parameters: &[String]) -> Vec<String> {
|
||||
parameters
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|arg| {
|
||||
if arg.contains(' ') {
|
||||
format!("\"{}\"", arg)
|
||||
} else {
|
||||
arg
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn consume(context: &EvaluationContext, stream: InputStream) -> Result<(), ShellError> {
|
||||
let autoview_cmd = context
|
||||
.get_command("autoview")
|
||||
.expect("could not find autoview command");
|
||||
|
||||
let stream = context.run_command(
|
||||
autoview_cmd,
|
||||
Tag::unknown(),
|
||||
Call::new(
|
||||
Box::new(SpannedExpression::new(
|
||||
Expression::Synthetic(Synthetic::String("autoview".to_string())),
|
||||
Span::unknown(),
|
||||
)),
|
||||
Span::unknown(),
|
||||
),
|
||||
stream,
|
||||
)?;
|
||||
|
||||
for _ in stream {}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn cli_app() -> App {
|
||||
let parser = Box::new(NuParser::new());
|
||||
let options = Options::default();
|
||||
|
||||
App::new(parser, options)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_options() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu")?;
|
||||
assert!(!ui.version());
|
||||
assert!(!ui.help());
|
||||
assert!(!ui.takes_stdin());
|
||||
assert!(ui.save_history());
|
||||
assert!(!ui.skip_plugins());
|
||||
assert_eq!(ui.config(), None);
|
||||
assert_eq!(ui.loglevel(), None);
|
||||
assert_eq!(ui.debug(), None);
|
||||
assert_eq!(ui.develop(), None);
|
||||
assert_eq!(ui.testbin(), None);
|
||||
assert_eq!(ui.commands(), None);
|
||||
assert_eq!(ui.scripts(), None);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reports_errors_on_unsupported_flags() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
assert!(ui.parse("nu --coonfig-file /path/to/config.toml").is_err());
|
||||
assert!(ui.config().is_none());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configures_debug_trace_level_with_filters() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
ui.parse("nu --develop=cli,parser")?;
|
||||
assert_eq!(ui.develop().unwrap()[0], Ok("cli".to_string()));
|
||||
assert_eq!(ui.develop().unwrap()[1], Ok("parser".to_string()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configures_debug_level_with_filters() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
ui.parse("nu --debug=cli,run")?;
|
||||
assert_eq!(ui.debug().unwrap()[0], Ok("cli".to_string()));
|
||||
assert_eq!(ui.debug().unwrap()[1], Ok("run".to_string()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_use_loglevels() -> Result<(), ShellError> {
|
||||
for level in ["error", "warn", "info", "debug", "trace"] {
|
||||
let ui = cli_app();
|
||||
let args = format!("nu --loglevel={}", level);
|
||||
ui.parse(&args)?;
|
||||
assert_eq!(ui.loglevel().unwrap(), Ok(level.to_string()));
|
||||
}
|
||||
|
||||
let ui = cli_app();
|
||||
ui.parse("nu --loglevel=nada")?;
|
||||
assert_eq!(
|
||||
ui.loglevel().unwrap(),
|
||||
Err(ShellError::untagged_runtime_error("nada is not supported."))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_be_login() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
ui.parse("nu -l")?;
|
||||
|
||||
let ui = cli_app();
|
||||
ui.parse("nu --login")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_be_passed_nu_scripts() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
ui.parse("nu code.nu bootstrap.nu")?;
|
||||
assert_eq!(ui.scripts().unwrap()[0], Ok("code.nu".into()));
|
||||
assert_eq!(ui.scripts().unwrap()[1], Ok("bootstrap.nu".into()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_use_test_binaries() -> Result<(), ShellError> {
|
||||
for binarie_name in [
|
||||
"echo_env", "cococo", "iecho", "fail", "nonu", "chop", "repeater", "meow",
|
||||
] {
|
||||
let ui = cli_app();
|
||||
let args = format!("nu --testbin={}", binarie_name);
|
||||
ui.parse(&args)?;
|
||||
assert_eq!(ui.testbin().unwrap(), Ok(binarie_name.to_string()));
|
||||
}
|
||||
|
||||
let ui = cli_app();
|
||||
ui.parse("nu --testbin=andres")?;
|
||||
assert_eq!(
|
||||
ui.testbin().unwrap(),
|
||||
Err(ShellError::untagged_runtime_error(
|
||||
"andres is not supported."
|
||||
))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_version() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --version")?;
|
||||
assert!(ui.version());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_help() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --help")?;
|
||||
assert!(ui.help());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_take_stdin() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --stdin")?;
|
||||
assert!(ui.takes_stdin());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_opt_to_avoid_saving_history() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --no-history")?;
|
||||
assert!(!ui.save_history());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_opt_to_skip_plugins() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --skip-plugins")?;
|
||||
assert!(ui.skip_plugins());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn understands_commands_need_to_be_run() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu -c \"ls | get name\"")?;
|
||||
assert_eq!(ui.commands().unwrap(), Ok(String::from("ls | get name")));
|
||||
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu -c \"echo 'hola'\"")?;
|
||||
assert_eq!(ui.commands().unwrap(), Ok(String::from("echo 'hola'")));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn knows_custom_configurations() -> Result<(), ShellError> {
|
||||
let ui = cli_app();
|
||||
|
||||
ui.parse("nu --config-file /path/to/config.toml")?;
|
||||
assert_eq!(ui.config().unwrap(), String::from("/path/to/config.toml"));
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
use super::App;
|
||||
use log::LevelFilter;
|
||||
use nu_errors::ShellError;
|
||||
use pretty_env_logger::env_logger::Builder;
|
||||
|
||||
pub fn logger(f: impl FnOnce(&mut Builder) -> Result<(), ShellError>) -> Result<(), ShellError> {
|
||||
let mut builder = pretty_env_logger::formatted_builder();
|
||||
f(&mut builder)?;
|
||||
let _ = builder.try_init();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn configure(app: &App, logger: &mut Builder) -> Result<(), ShellError> {
|
||||
if let Some(level) = app.loglevel() {
|
||||
let level = match level.as_deref() {
|
||||
Ok("error") => LevelFilter::Error,
|
||||
Ok("warn") => LevelFilter::Warn,
|
||||
Ok("info") => LevelFilter::Info,
|
||||
Ok("debug") => LevelFilter::Debug,
|
||||
Ok("trace") => LevelFilter::Trace,
|
||||
Ok(_) | Err(_) => LevelFilter::Warn,
|
||||
};
|
||||
|
||||
logger.filter_module("nu", level);
|
||||
};
|
||||
|
||||
if let Ok(s) = std::env::var("RUST_LOG") {
|
||||
logger.parse_filters(&s);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn trace_filters(app: &App, logger: &mut Builder) -> Result<(), ShellError> {
|
||||
if let Some(filters) = app.develop() {
|
||||
filters.into_iter().filter_map(Result::ok).for_each(|name| {
|
||||
logger.filter_module(&name, LevelFilter::Trace);
|
||||
})
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn debug_filters(app: &App, logger: &mut Builder) -> Result<(), ShellError> {
|
||||
if let Some(filters) = app.debug() {
|
||||
filters.into_iter().filter_map(Result::ok).for_each(|name| {
|
||||
logger.filter_module(&name, LevelFilter::Debug);
|
||||
})
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
use std::cell::RefCell;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CliOptions {
|
||||
pub config: Option<OsString>,
|
||||
pub stdin: bool,
|
||||
pub scripts: Vec<NuScript>,
|
||||
pub save_history: bool,
|
||||
pub perf: bool,
|
||||
}
|
||||
|
||||
impl Default for CliOptions {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl CliOptions {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
config: None,
|
||||
stdin: false,
|
||||
scripts: vec![],
|
||||
save_history: true,
|
||||
perf: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Options {
|
||||
inner: RefCell<IndexMap<String, Value>>,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
inner: RefCell::new(IndexMap::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &str) -> Option<Value> {
|
||||
self.inner.borrow().get(key).cloned()
|
||||
}
|
||||
|
||||
pub fn put(&self, key: &str, value: Value) {
|
||||
self.inner.borrow_mut().insert(key.into(), value);
|
||||
}
|
||||
|
||||
pub fn shift(&self) {
|
||||
if let Some(Value {
|
||||
value: UntaggedValue::Table(ref mut args),
|
||||
..
|
||||
}) = self.inner.borrow_mut().get_mut("args")
|
||||
{
|
||||
args.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn swap(&self, other: &Options) {
|
||||
self.inner.swap(&other.inner);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NuScript {
|
||||
pub filepath: Option<OsString>,
|
||||
pub contents: String,
|
||||
}
|
||||
|
||||
impl NuScript {
|
||||
pub fn code(content: &str) -> Result<Self, ShellError> {
|
||||
Ok(Self {
|
||||
filepath: None,
|
||||
contents: content.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_code(&self) -> &str {
|
||||
&self.contents
|
||||
}
|
||||
|
||||
pub fn source_file(path: &OsStr) -> Result<Self, ShellError> {
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
let path = path.to_os_string();
|
||||
let mut file = File::open(&path)?;
|
||||
let mut buffer = String::new();
|
||||
|
||||
file.read_to_string(&mut buffer)?;
|
||||
|
||||
Ok(Self {
|
||||
filepath: Some(path),
|
||||
contents: buffer,
|
||||
})
|
||||
}
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
use super::Options;
|
||||
|
||||
use nu_command::commands::{loglevels, testbins, NuSignature as Nu};
|
||||
use nu_command::commands::{Autoview, Pivot, Table, Version as NuVersion};
|
||||
use nu_engine::{whole_stream_command, EvaluationContext};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{ClassifiedCommand, InternalCommand, NamedValue};
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_source::Tag;
|
||||
|
||||
pub struct NuParser {
|
||||
context: EvaluationContext,
|
||||
}
|
||||
|
||||
pub trait OptionsParser {
|
||||
fn parse(&self, input: &str) -> Result<Options, ShellError>;
|
||||
fn context(&self) -> &EvaluationContext;
|
||||
}
|
||||
|
||||
impl NuParser {
|
||||
pub fn new() -> Self {
|
||||
let context = EvaluationContext::basic();
|
||||
context.add_commands(vec![
|
||||
whole_stream_command(Nu {}),
|
||||
whole_stream_command(NuVersion {}),
|
||||
whole_stream_command(Autoview {}),
|
||||
whole_stream_command(Pivot {}),
|
||||
whole_stream_command(Table {}),
|
||||
]);
|
||||
|
||||
Self { context }
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionsParser for NuParser {
|
||||
fn context(&self) -> &EvaluationContext {
|
||||
&self.context
|
||||
}
|
||||
|
||||
fn parse(&self, input: &str) -> Result<Options, ShellError> {
|
||||
let options = Options::default();
|
||||
let (lite_result, _err) = nu_parser::lex(input, 0, nu_parser::NewlineMode::Normal);
|
||||
let (lite_result, _err) = nu_parser::parse_block(lite_result);
|
||||
|
||||
let (parsed, err) = nu_parser::classify_block(&lite_result, &self.context.scope);
|
||||
|
||||
if let Some(reason) = err {
|
||||
return Err(reason.into());
|
||||
}
|
||||
|
||||
match parsed.block[0].pipelines[0].list[0] {
|
||||
ClassifiedCommand::Internal(InternalCommand { ref args, .. }) => {
|
||||
if let Some(ref params) = args.named {
|
||||
params.iter().for_each(|(k, v)| {
|
||||
let value = match v {
|
||||
NamedValue::AbsentSwitch => {
|
||||
Some(UntaggedValue::from(false).into_untagged_value())
|
||||
}
|
||||
NamedValue::PresentSwitch(span) => {
|
||||
Some(UntaggedValue::from(true).into_value(Tag::from(span)))
|
||||
}
|
||||
NamedValue::AbsentValue => None,
|
||||
NamedValue::Value(span, exprs) => {
|
||||
let value = nu_engine::evaluate_baseline_expr(exprs, &self.context)
|
||||
.expect("value");
|
||||
Some(value.value.into_value(Tag::from(span)))
|
||||
}
|
||||
};
|
||||
|
||||
let value = value.map(|v| match k.as_ref() {
|
||||
"testbin" => {
|
||||
if let Ok(name) = v.as_string() {
|
||||
if testbins().iter().any(|n| name == *n) {
|
||||
v
|
||||
} else {
|
||||
UntaggedValue::Error(ShellError::untagged_runtime_error(
|
||||
format!("{} is not supported.", name),
|
||||
))
|
||||
.into_value(v.tag)
|
||||
}
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
"loglevel" => {
|
||||
if let Ok(name) = v.as_string() {
|
||||
if loglevels().iter().any(|n| name == *n) {
|
||||
v
|
||||
} else {
|
||||
UntaggedValue::Error(ShellError::untagged_runtime_error(
|
||||
format!("{} is not supported.", name),
|
||||
))
|
||||
.into_value(v.tag)
|
||||
}
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
_ => v,
|
||||
});
|
||||
|
||||
if let Some(value) = value {
|
||||
options.put(k, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let mut positional_args = vec![];
|
||||
|
||||
if let Some(positional) = &args.positional {
|
||||
for pos in positional {
|
||||
let result = nu_engine::evaluate_baseline_expr(pos, &self.context)?;
|
||||
positional_args.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
if !positional_args.is_empty() {
|
||||
options.put(
|
||||
"args",
|
||||
UntaggedValue::Table(positional_args).into_untagged_value(),
|
||||
);
|
||||
}
|
||||
}
|
||||
ClassifiedCommand::Error(ref reason) => {
|
||||
return Err(reason.clone().into());
|
||||
}
|
||||
_ => return Err(ShellError::untagged_runtime_error("unrecognized command")),
|
||||
}
|
||||
|
||||
Ok(options)
|
||||
}
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
use std::default::Default;
|
||||
use std::fmt;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Stopwatch {
|
||||
/// The time the stopwatch was started last, if ever.
|
||||
start_time: Option<Instant>,
|
||||
/// The time the stopwatch was split last, if ever.
|
||||
split_time: Option<Instant>,
|
||||
/// The time elapsed while the stopwatch was running (between start() and stop()).
|
||||
elapsed: Duration,
|
||||
}
|
||||
|
||||
impl Default for Stopwatch {
|
||||
fn default() -> Stopwatch {
|
||||
Stopwatch {
|
||||
start_time: None,
|
||||
split_time: None,
|
||||
elapsed: Duration::from_secs(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Stopwatch {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
return write!(f, "{}ms", self.elapsed_ms());
|
||||
}
|
||||
}
|
||||
|
||||
impl Stopwatch {
|
||||
/// Returns a new stopwatch.
|
||||
pub fn new() -> Stopwatch {
|
||||
let sw: Stopwatch = Default::default();
|
||||
sw
|
||||
}
|
||||
|
||||
/// Returns a new stopwatch which will immediately be started.
|
||||
pub fn start_new() -> Stopwatch {
|
||||
let mut sw = Stopwatch::new();
|
||||
sw.start();
|
||||
sw
|
||||
}
|
||||
|
||||
/// Starts the stopwatch.
|
||||
pub fn start(&mut self) {
|
||||
self.start_time = Some(Instant::now());
|
||||
}
|
||||
|
||||
/// Stops the stopwatch.
|
||||
pub fn stop(&mut self) {
|
||||
self.elapsed = self.elapsed();
|
||||
self.start_time = None;
|
||||
self.split_time = None;
|
||||
}
|
||||
|
||||
/// Resets all counters and stops the stopwatch.
|
||||
pub fn reset(&mut self) {
|
||||
self.elapsed = Duration::from_secs(0);
|
||||
self.start_time = None;
|
||||
self.split_time = None;
|
||||
}
|
||||
|
||||
/// Resets and starts the stopwatch again.
|
||||
pub fn restart(&mut self) {
|
||||
self.reset();
|
||||
self.start();
|
||||
}
|
||||
|
||||
/// Returns whether the stopwatch is running.
|
||||
pub fn is_running(&self) -> bool {
|
||||
self.start_time.is_some()
|
||||
}
|
||||
|
||||
/// Returns the elapsed time since the start of the stopwatch.
|
||||
pub fn elapsed(&self) -> Duration {
|
||||
match self.start_time {
|
||||
// stopwatch is running
|
||||
Some(t1) => t1.elapsed() + self.elapsed,
|
||||
// stopwatch is not running
|
||||
None => self.elapsed,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the elapsed time since the start of the stopwatch in milliseconds.
|
||||
pub fn elapsed_ms(&self) -> i64 {
|
||||
let dur = self.elapsed();
|
||||
(dur.as_secs() * 1000 + dur.subsec_millis() as u64) as i64
|
||||
}
|
||||
|
||||
/// Returns the elapsed time since last split or start/restart.
|
||||
///
|
||||
/// If the stopwatch is in stopped state this will always return a zero Duration.
|
||||
pub fn elapsed_split(&mut self) -> Duration {
|
||||
match self.start_time {
|
||||
// stopwatch is running
|
||||
Some(start) => {
|
||||
let res = match self.split_time {
|
||||
Some(split) => split.elapsed(),
|
||||
None => start.elapsed(),
|
||||
};
|
||||
self.split_time = Some(Instant::now());
|
||||
res
|
||||
}
|
||||
// stopwatch is not running
|
||||
None => Duration::from_secs(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the elapsed time since last split or start/restart in milliseconds.
|
||||
///
|
||||
/// If the stopwatch is in stopped state this will always return zero.
|
||||
pub fn elapsed_split_ms(&mut self) -> i64 {
|
||||
let dur = self.elapsed_split();
|
||||
(dur.as_secs() * 1000 + dur.subsec_millis() as u64) as i64
|
||||
}
|
||||
}
|
@ -1,538 +0,0 @@
|
||||
use crate::app::STOPWATCH;
|
||||
use crate::line_editor::configure_ctrl_c;
|
||||
use nu_ansi_term::Color;
|
||||
use nu_engine::{maybe_print_errors, run_block, script::run_script_standalone, EvaluationContext};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use nu_engine::script::{process_script, LineResult};
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use crate::line_editor::{
|
||||
configure_rustyline_editor, convert_rustyline_result_to_string,
|
||||
default_rustyline_editor_configuration, nu_line_editor_helper,
|
||||
};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use nu_data::config;
|
||||
use nu_source::{Tag, Text};
|
||||
use nu_stream::InputStream;
|
||||
#[allow(unused_imports)]
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use rustyline::{self, error::ReadlineError};
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::ParserScope;
|
||||
use nu_path::expand_tilde;
|
||||
use nu_protocol::{hir::ExternalRedirection, ConfigPath, UntaggedValue, Value};
|
||||
|
||||
use log::trace;
|
||||
use std::error::Error;
|
||||
use std::iter::Iterator;
|
||||
use std::path::PathBuf;
|
||||
|
||||
// Name of environment variable where the prompt could be stored
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
const PROMPT_COMMAND: &str = "PROMPT_COMMAND";
|
||||
|
||||
pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
use std::env;
|
||||
let mut search_paths = Vec::new();
|
||||
|
||||
// Automatically add path `nu` is in as a search path
|
||||
if let Ok(exe_path) = env::current_exe() {
|
||||
if let Some(exe_dir) = exe_path.parent() {
|
||||
search_paths.push(exe_dir.to_path_buf());
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
|
||||
if let Some(Value {
|
||||
value: UntaggedValue::Table(pipelines),
|
||||
..
|
||||
}) = config.get("plugin_dirs")
|
||||
{
|
||||
for pipeline in pipelines {
|
||||
if let Ok(plugin_dir) = pipeline.as_string() {
|
||||
search_paths.push(expand_tilde(plugin_dir));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
search_paths
|
||||
}
|
||||
|
||||
pub fn run_script_file(
|
||||
context: EvaluationContext,
|
||||
options: super::app::CliOptions,
|
||||
) -> Result<(), ShellError> {
|
||||
if let Some(cfg) = options.config {
|
||||
load_cfg_as_global_cfg(&context, PathBuf::from(cfg));
|
||||
} else {
|
||||
load_global_cfg(&context);
|
||||
}
|
||||
|
||||
let _ = register_plugins(&context);
|
||||
let _ = configure_ctrl_c(&context);
|
||||
|
||||
let script = options
|
||||
.scripts
|
||||
.get(0)
|
||||
.ok_or_else(|| ShellError::unexpected("Nu source code not available"))?;
|
||||
|
||||
run_script_standalone(script.get_code().to_string(), options.stdin, &context, true)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn default_prompt_string(cwd: &str) -> String {
|
||||
format!(
|
||||
"{}{}{}{}{}{}> ",
|
||||
Color::Green.bold().prefix(),
|
||||
cwd,
|
||||
nu_ansi_term::ansi::RESET,
|
||||
Color::Cyan.bold().prefix(),
|
||||
current_branch(),
|
||||
nu_ansi_term::ansi::RESET
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
fn evaluate_prompt_string(prompt_line: &str, context: &EvaluationContext, cwd: &str) -> String {
|
||||
context.scope.enter_scope();
|
||||
let (prompt_block, err) = nu_parser::parse(prompt_line, 0, &context.scope);
|
||||
|
||||
if err.is_some() {
|
||||
context.scope.exit_scope();
|
||||
default_prompt_string(cwd)
|
||||
} else {
|
||||
let run_result = run_block(
|
||||
&prompt_block,
|
||||
context,
|
||||
InputStream::empty(),
|
||||
ExternalRedirection::Stdout,
|
||||
);
|
||||
context.scope.exit_scope();
|
||||
|
||||
match run_result {
|
||||
Ok(result) => match result.collect_string(Tag::unknown()) {
|
||||
Ok(string_result) => {
|
||||
let errors = context.get_errors();
|
||||
maybe_print_errors(context, Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
if !errors.is_empty() {
|
||||
"> ".into()
|
||||
} else {
|
||||
string_result.item
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
context.host().lock().print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".into()
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
context.host().lock().print_err(e, &Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"> ".into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn cli(
|
||||
context: EvaluationContext,
|
||||
options: super::app::CliOptions,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let _ = configure_ctrl_c(&context);
|
||||
|
||||
// start time for running startup scripts (this metric includes loading of the cfg, but w/e)
|
||||
let startup_commands_start_time = std::time::Instant::now();
|
||||
|
||||
if let Some(cfg) = options.config {
|
||||
load_cfg_as_global_cfg(&context, PathBuf::from(cfg));
|
||||
} else {
|
||||
load_global_cfg(&context);
|
||||
}
|
||||
|
||||
// Store cmd duration in an env var
|
||||
context.scope.add_env_var(
|
||||
"CMD_DURATION_MS",
|
||||
startup_commands_start_time
|
||||
.elapsed()
|
||||
.as_millis()
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
if options.perf {
|
||||
eprintln!(
|
||||
"config loaded: {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed_split()
|
||||
);
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.start();
|
||||
}
|
||||
|
||||
//Configure rustyline
|
||||
let mut rl = default_rustyline_editor_configuration();
|
||||
let history_path = if let Some(cfg) = &context.configs().lock().global_config {
|
||||
let _ = configure_rustyline_editor(&mut rl, cfg);
|
||||
let helper = Some(nu_line_editor_helper(&context, cfg));
|
||||
rl.set_helper(helper);
|
||||
nu_data::config::path::history_path_or_default(cfg)
|
||||
} else {
|
||||
nu_data::config::path::default_history_path()
|
||||
};
|
||||
|
||||
if options.perf {
|
||||
eprintln!(
|
||||
"rustyline configuration: {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed_split()
|
||||
);
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.start();
|
||||
}
|
||||
|
||||
// Don't load history if it's not necessary
|
||||
if options.save_history {
|
||||
let _ = rl.load_history(&history_path);
|
||||
}
|
||||
|
||||
if options.perf {
|
||||
eprintln!(
|
||||
"history load: {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed_split()
|
||||
);
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.start();
|
||||
}
|
||||
|
||||
//set vars from cfg if present
|
||||
let (skip_welcome_message, prompt) = if let Some(cfg) = &context.configs().lock().global_config
|
||||
{
|
||||
(
|
||||
cfg.var("skip_welcome_message")
|
||||
.map(|x| x.is_true())
|
||||
.unwrap_or(false),
|
||||
cfg.var("prompt"),
|
||||
)
|
||||
} else {
|
||||
(false, None)
|
||||
};
|
||||
|
||||
if options.perf {
|
||||
eprintln!(
|
||||
"load custom prompt: {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed_split()
|
||||
);
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.start();
|
||||
}
|
||||
|
||||
//Check whether dir we start in contains local cfg file and if so load it.
|
||||
load_local_cfg_if_present(&context);
|
||||
|
||||
// Give ourselves a scope to work in
|
||||
context.scope.enter_scope();
|
||||
|
||||
let mut session_text = String::new();
|
||||
let mut line_start: usize = 0;
|
||||
|
||||
if !skip_welcome_message {
|
||||
println!(
|
||||
"Welcome to Nushell {} (type 'help' for more info)",
|
||||
nu_command::commands::core_version()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = nu_ansi_term::enable_ansi_support();
|
||||
}
|
||||
|
||||
let mut ctrlcbreak = false;
|
||||
|
||||
if options.perf {
|
||||
eprintln!(
|
||||
"timing stopped. starting run loop: {:?}",
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.elapsed_split()
|
||||
);
|
||||
STOPWATCH
|
||||
.lock()
|
||||
.expect("unable to lock the stopwatch")
|
||||
.stop();
|
||||
}
|
||||
|
||||
loop {
|
||||
if context.ctrl_c().load(Ordering::SeqCst) {
|
||||
context.ctrl_c().store(false, Ordering::SeqCst);
|
||||
continue;
|
||||
}
|
||||
|
||||
let cwd = context.shell_manager().path();
|
||||
|
||||
// Check if the PROMPT_COMMAND env variable is set. This env variable
|
||||
// contains nu code that is used to overwrite the prompt
|
||||
let colored_prompt = match context.scope.get_env(PROMPT_COMMAND) {
|
||||
Some(env_prompt) => evaluate_prompt_string(&env_prompt, &context, &cwd),
|
||||
None => {
|
||||
if let Some(prompt) = &prompt {
|
||||
let prompt_line = prompt.as_string()?;
|
||||
evaluate_prompt_string(&prompt_line, &context, &cwd)
|
||||
} else {
|
||||
default_prompt_string(&cwd)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let prompt = {
|
||||
if let Ok(bytes) = strip_ansi_escapes::strip(&colored_prompt) {
|
||||
String::from_utf8_lossy(&bytes).to_string()
|
||||
} else {
|
||||
"> ".to_string()
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(helper) = rl.helper_mut() {
|
||||
helper.colored_prompt = colored_prompt;
|
||||
}
|
||||
let mut initial_command = Some(String::new());
|
||||
let mut readline = Err(ReadlineError::Eof);
|
||||
while let Some(ref cmd) = initial_command {
|
||||
readline = rl.readline_with_initial(&prompt, (cmd, ""));
|
||||
initial_command = None;
|
||||
}
|
||||
|
||||
if let Ok(line) = &readline {
|
||||
line_start = session_text.len();
|
||||
session_text.push_str(line);
|
||||
session_text.push('\n');
|
||||
}
|
||||
|
||||
// start time for command duration
|
||||
let cmd_start_time = std::time::Instant::now();
|
||||
|
||||
let line = match convert_rustyline_result_to_string(readline) {
|
||||
LineResult::Success(_) => process_script(
|
||||
&session_text[line_start..],
|
||||
&context,
|
||||
false,
|
||||
line_start,
|
||||
true,
|
||||
),
|
||||
x => x,
|
||||
};
|
||||
|
||||
// Store cmd duration in an env var
|
||||
context.scope.add_env_var(
|
||||
"CMD_DURATION_MS",
|
||||
cmd_start_time.elapsed().as_millis().to_string(),
|
||||
);
|
||||
|
||||
match line {
|
||||
LineResult::Success(line) => {
|
||||
if options.save_history && !line.trim().is_empty() {
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.append_history(&history_path);
|
||||
}
|
||||
maybe_print_errors(&context, Text::from(session_text.clone()));
|
||||
}
|
||||
|
||||
LineResult::ClearHistory => {
|
||||
if options.save_history {
|
||||
rl.clear_history();
|
||||
std::fs::remove_file(&history_path)?;
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::Error(line, err) => {
|
||||
if options.save_history && !line.trim().is_empty() {
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.append_history(&history_path);
|
||||
}
|
||||
|
||||
context
|
||||
.host()
|
||||
.lock()
|
||||
.print_err(err, &Text::from(session_text.clone()));
|
||||
|
||||
// I am not so sure, we don't need maybe_print_errors here (as we printed an err
|
||||
// above), because maybe_print_errors also clears the errors.
|
||||
// TODO Analyze where above err comes from, and whether we need to clear
|
||||
// context.errors here
|
||||
// Or just be consistent and return errors always in context.errors...
|
||||
maybe_print_errors(&context, Text::from(session_text.clone()));
|
||||
}
|
||||
|
||||
LineResult::CtrlC => {
|
||||
let config_ctrlc_exit = context
|
||||
.configs()
|
||||
.lock()
|
||||
.global_config
|
||||
.as_ref()
|
||||
.and_then(|cfg| cfg.var("ctrlc_exit"))
|
||||
.map(|ctrl_c| ctrl_c.is_true())
|
||||
.unwrap_or(false); // default behavior is to allow CTRL-C spamming similar to other shells
|
||||
|
||||
if !config_ctrlc_exit {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ctrlcbreak {
|
||||
if options.save_history {
|
||||
let _ = rl.append_history(&history_path);
|
||||
}
|
||||
std::process::exit(0);
|
||||
} else {
|
||||
context.with_host(|host| host.stdout("CTRL-C pressed (again to quit)"));
|
||||
ctrlcbreak = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::CtrlD => {
|
||||
context.shell_manager().remove_at_current();
|
||||
if context.shell_manager().is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::Break => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ctrlcbreak = false;
|
||||
}
|
||||
|
||||
// we are ok if we can not save history
|
||||
if options.save_history {
|
||||
let _ = rl.append_history(&history_path);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn load_local_cfg_if_present(context: &EvaluationContext) {
|
||||
trace!("Loading local cfg if present");
|
||||
match config::loadable_cfg_exists_in_dir(PathBuf::from(context.shell_manager().path())) {
|
||||
Ok(Some(cfg_path)) => {
|
||||
if let Err(err) = context.load_config(&ConfigPath::Local(cfg_path)) {
|
||||
context.host().lock().print_err(err, &Text::from(""))
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
//Report error while checking for local cfg file
|
||||
context.host().lock().print_err(e, &Text::from(""))
|
||||
}
|
||||
Ok(None) => {
|
||||
//No local cfg file present in start dir
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_cfg_as_global_cfg(context: &EvaluationContext, path: PathBuf) {
|
||||
if let Err(err) = context.load_config(&ConfigPath::Global(path)) {
|
||||
context.host().lock().print_err(err, &Text::from(""));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_global_cfg(context: &EvaluationContext) {
|
||||
match config::default_path() {
|
||||
Ok(path) => {
|
||||
load_cfg_as_global_cfg(context, path);
|
||||
}
|
||||
Err(e) => {
|
||||
context.host().lock().print_err(e, &Text::from(""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_plugins(context: &EvaluationContext) -> Result<(), ShellError> {
|
||||
if let Ok(plugins) = nu_engine::plugin::build_plugin::scan(search_paths()) {
|
||||
context.add_commands(
|
||||
plugins
|
||||
.into_iter()
|
||||
.filter(|p| !context.is_command_registered(p.name()))
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn parse_and_eval(line: &str, ctx: &EvaluationContext) -> Result<String, ShellError> {
|
||||
// FIXME: do we still need this?
|
||||
let line = if let Some(s) = line.strip_suffix('\n') {
|
||||
s
|
||||
} else {
|
||||
line
|
||||
};
|
||||
|
||||
// TODO ensure the command whose examples we're testing is actually in the pipeline
|
||||
ctx.scope.enter_scope();
|
||||
let (classified_block, err) = nu_parser::parse(line, 0, &ctx.scope);
|
||||
if let Some(err) = err {
|
||||
ctx.scope.exit_scope();
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
let input_stream = InputStream::empty();
|
||||
|
||||
let result = run_block(
|
||||
&classified_block,
|
||||
ctx,
|
||||
input_stream,
|
||||
ExternalRedirection::Stdout,
|
||||
);
|
||||
ctx.scope.exit_scope();
|
||||
|
||||
result?.collect_string(Tag::unknown()).map(|x| x.item)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn current_branch() -> String {
|
||||
#[cfg(feature = "shadow-rs")]
|
||||
{
|
||||
Some(shadow_rs::branch())
|
||||
.map(|x| x.trim().to_string())
|
||||
.filter(|x| !x.is_empty())
|
||||
.map(|x| format!("({})", x))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
#[cfg(not(feature = "shadow-rs"))]
|
||||
{
|
||||
"".to_string()
|
||||
}
|
||||
}
|
94
crates/nu-cli/src/commands.rs
Normal file
94
crates/nu-cli/src/commands.rs
Normal file
@ -0,0 +1,94 @@
|
||||
use crate::util::report_error;
|
||||
use log::info;
|
||||
use miette::Result;
|
||||
use nu_engine::{convert_env_values, eval_block};
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::engine::Stack;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateDelta, StateWorkingSet},
|
||||
PipelineData, Spanned, Value,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
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
|
||||
let (block, delta) = {
|
||||
if let Some(ref t_mode) = table_mode {
|
||||
let mut config = engine_state.get_config().clone();
|
||||
config.table_mode = t_mode.as_string()?;
|
||||
engine_state.set_config(&config);
|
||||
}
|
||||
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
let (output, err) = parse(&mut working_set, None, commands.item.as_bytes(), false, &[]);
|
||||
if let Some(err) = err {
|
||||
report_error(&working_set, &err);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
if let Err(err) = engine_state.merge_delta(delta, None, init_cwd) {
|
||||
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);
|
||||
}
|
||||
|
||||
let exit_code = match eval_block(engine_state, stack, &block, input, false, false) {
|
||||
Ok(pipeline_data) => {
|
||||
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
|
||||
}
|
||||
Err(err) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
if is_perf_true {
|
||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
Ok(exit_code)
|
||||
}
|
43
crates/nu-cli/src/completions/base.rs
Normal file
43
crates/nu-cli/src/completions/base.rs
Normal file
@ -0,0 +1,43 @@
|
||||
use crate::completions::{CompletionOptions, SortBy};
|
||||
use nu_protocol::{engine::StateWorkingSet, levenshtein_distance, Span};
|
||||
use reedline::Suggestion;
|
||||
|
||||
// Completer trait represents the three stages of the completion
|
||||
// fetch, filter and sort
|
||||
pub trait Completer {
|
||||
fn fetch(
|
||||
&mut self,
|
||||
working_set: &StateWorkingSet,
|
||||
prefix: Vec<u8>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
pos: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion>;
|
||||
|
||||
fn get_sort_by(&self) -> SortBy {
|
||||
SortBy::Ascending
|
||||
}
|
||||
|
||||
fn sort(&self, items: Vec<Suggestion>, prefix: Vec<u8>) -> Vec<Suggestion> {
|
||||
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
||||
let mut filtered_items = items;
|
||||
|
||||
// Sort items
|
||||
match self.get_sort_by() {
|
||||
SortBy::LevenshteinDistance => {
|
||||
filtered_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)
|
||||
});
|
||||
}
|
||||
SortBy::Ascending => {
|
||||
filtered_items.sort_by(|a, b| a.value.cmp(&b.value));
|
||||
}
|
||||
SortBy::None => {}
|
||||
};
|
||||
|
||||
filtered_items
|
||||
}
|
||||
}
|
228
crates/nu-cli/src/completions/command_completions.rs
Normal file
228
crates/nu-cli/src/completions/command_completions.rs
Normal file
@ -0,0 +1,228 @@
|
||||
use crate::completions::{Completer, CompletionOptions, MatchAlgorithm, SortBy};
|
||||
use nu_parser::FlatShape;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
Span,
|
||||
};
|
||||
use reedline::Suggestion;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct CommandCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
flattened: Vec<(Span, FlatShape)>,
|
||||
flat_shape: FlatShape,
|
||||
}
|
||||
|
||||
impl CommandCompletion {
|
||||
pub fn new(
|
||||
engine_state: Arc<EngineState>,
|
||||
_: &StateWorkingSet,
|
||||
flattened: Vec<(Span, FlatShape)>,
|
||||
flat_shape: FlatShape,
|
||||
) -> Self {
|
||||
Self {
|
||||
engine_state,
|
||||
flattened,
|
||||
flat_shape,
|
||||
}
|
||||
}
|
||||
|
||||
fn external_command_completion(
|
||||
&self,
|
||||
prefix: &str,
|
||||
match_algorithm: MatchAlgorithm,
|
||||
) -> Vec<String> {
|
||||
let mut executables = vec![];
|
||||
|
||||
let paths = self.engine_state.get_env_var("PATH");
|
||||
|
||||
if let Some(paths) = paths {
|
||||
if let Ok(paths) = paths.as_list() {
|
||||
for path in paths {
|
||||
let path = path.as_string().unwrap_or_default();
|
||||
|
||||
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
|
||||
.matches_str(&x.to_string_lossy(), prefix)),
|
||||
Some(true)
|
||||
) && is_executable::is_executable(&item.path())
|
||||
{
|
||||
if let Ok(name) = item.file_name().into_string() {
|
||||
executables.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
executables
|
||||
}
|
||||
|
||||
fn complete_commands(
|
||||
&self,
|
||||
working_set: &StateWorkingSet,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
find_externals: bool,
|
||||
match_algorithm: MatchAlgorithm,
|
||||
) -> Vec<Suggestion> {
|
||||
let partial = working_set.get_span_contents(span);
|
||||
|
||||
let filter_predicate = |command: &[u8]| match_algorithm.matches_u8(command, partial);
|
||||
|
||||
let results = working_set
|
||||
.find_commands_by_predicate(filter_predicate)
|
||||
.into_iter()
|
||||
.map(move |x| Suggestion {
|
||||
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,
|
||||
},
|
||||
append_whitespace: true,
|
||||
});
|
||||
|
||||
let results_aliases = working_set
|
||||
.find_aliases_by_predicate(filter_predicate)
|
||||
.into_iter()
|
||||
.map(move |x| Suggestion {
|
||||
value: String::from_utf8_lossy(&x).to_string(),
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: true,
|
||||
});
|
||||
|
||||
let mut results = results.chain(results_aliases).collect::<Vec<_>>();
|
||||
|
||||
let partial = working_set.get_span_contents(span);
|
||||
let partial = String::from_utf8_lossy(partial).to_string();
|
||||
let results = if find_externals {
|
||||
let results_external = self
|
||||
.external_command_completion(&partial, match_algorithm)
|
||||
.into_iter()
|
||||
.map(move |x| Suggestion {
|
||||
value: x,
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: true,
|
||||
});
|
||||
|
||||
for external in results_external {
|
||||
if results.contains(&external) {
|
||||
results.push(Suggestion {
|
||||
value: format!("^{}", external.value),
|
||||
description: None,
|
||||
extra: None,
|
||||
span: external.span,
|
||||
append_whitespace: true,
|
||||
})
|
||||
} else {
|
||||
results.push(external)
|
||||
}
|
||||
}
|
||||
|
||||
results
|
||||
} else {
|
||||
results
|
||||
};
|
||||
|
||||
results
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for CommandCompletion {
|
||||
fn fetch(
|
||||
&mut self,
|
||||
working_set: &StateWorkingSet,
|
||||
_prefix: Vec<u8>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
pos: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
let last = self
|
||||
.flattened
|
||||
.iter()
|
||||
.rev()
|
||||
.skip_while(|x| x.0.end > pos)
|
||||
.take_while(|x| {
|
||||
matches!(
|
||||
x.1,
|
||||
FlatShape::InternalCall
|
||||
| FlatShape::External
|
||||
| FlatShape::ExternalArg
|
||||
| FlatShape::Literal
|
||||
| FlatShape::String
|
||||
)
|
||||
})
|
||||
.last();
|
||||
|
||||
// The last item here would be the earliest shape that could possible by part of this subcommand
|
||||
let subcommands = if let Some(last) = last {
|
||||
self.complete_commands(
|
||||
working_set,
|
||||
Span {
|
||||
start: last.0.start,
|
||||
end: pos,
|
||||
},
|
||||
offset,
|
||||
false,
|
||||
options.match_algorithm,
|
||||
)
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
if !subcommands.is_empty() {
|
||||
return subcommands;
|
||||
}
|
||||
|
||||
let config = working_set.get_config();
|
||||
let commands = if matches!(self.flat_shape, nu_parser::FlatShape::External)
|
||||
|| matches!(self.flat_shape, nu_parser::FlatShape::InternalCall)
|
||||
|| ((span.end - span.start) == 0)
|
||||
{
|
||||
// we're in a gap or at a command
|
||||
self.complete_commands(
|
||||
working_set,
|
||||
span,
|
||||
offset,
|
||||
config.enable_external_completion,
|
||||
options.match_algorithm,
|
||||
)
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
subcommands
|
||||
.into_iter()
|
||||
.chain(commands.into_iter())
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn get_sort_by(&self) -> SortBy {
|
||||
SortBy::LevenshteinDistance
|
||||
}
|
||||
}
|
385
crates/nu-cli/src/completions/completer.rs
Normal file
385
crates/nu-cli/src/completions/completer.rs
Normal file
@ -0,0 +1,385 @@
|
||||
use crate::completions::{
|
||||
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
||||
DotNuCompletion, FileCompletion, FlagCompletion, MatchAlgorithm, VariableCompletion,
|
||||
};
|
||||
use nu_parser::{flatten_expression, parse, FlatShape};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Span,
|
||||
};
|
||||
use reedline::{Completer as ReedlineCompleter, Suggestion};
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NuCompleter {
|
||||
engine_state: Arc<EngineState>,
|
||||
stack: Stack,
|
||||
}
|
||||
|
||||
impl NuCompleter {
|
||||
pub fn new(engine_state: Arc<EngineState>, stack: Stack) -> Self {
|
||||
Self {
|
||||
engine_state,
|
||||
stack,
|
||||
}
|
||||
}
|
||||
|
||||
// Process the completion for a given completer
|
||||
fn process_completion<T: Completer>(
|
||||
&self,
|
||||
completer: &mut T,
|
||||
working_set: &StateWorkingSet,
|
||||
prefix: Vec<u8>,
|
||||
new_span: Span,
|
||||
offset: usize,
|
||||
pos: usize,
|
||||
) -> Vec<Suggestion> {
|
||||
let config = self.engine_state.get_config();
|
||||
|
||||
let mut options = CompletionOptions {
|
||||
case_sensitive: config.case_sensitive_completions,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if config.completion_algorithm == "fuzzy" {
|
||||
options.match_algorithm = MatchAlgorithm::Fuzzy;
|
||||
}
|
||||
|
||||
// Fetch
|
||||
let mut suggestions =
|
||||
completer.fetch(working_set, prefix.clone(), new_span, offset, pos, &options);
|
||||
|
||||
// Sort
|
||||
suggestions = completer.sort(suggestions, prefix);
|
||||
|
||||
suggestions
|
||||
}
|
||||
|
||||
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 pos = offset + pos;
|
||||
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 (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());
|
||||
|
||||
// Create a new span
|
||||
let new_span = if flat_idx == 0 {
|
||||
Span {
|
||||
start: flat.0.start,
|
||||
end: flat.0.end - 1 - span_offset,
|
||||
}
|
||||
} 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));
|
||||
|
||||
// Variables completion
|
||||
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
||||
let mut completer = VariableCompletion::new(
|
||||
self.engine_state.clone(),
|
||||
self.stack.clone(),
|
||||
most_left_var.unwrap_or((vec![], vec![])),
|
||||
);
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
|
||||
// Flags completion
|
||||
if prefix.starts_with(b"-") {
|
||||
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,
|
||||
);
|
||||
} 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,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vec![];
|
||||
}
|
||||
}
|
||||
|
||||
impl ReedlineCompleter for NuCompleter {
|
||||
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
self.completion_helper(line, pos)
|
||||
}
|
||||
}
|
||||
|
||||
type MatchedAlias = Vec<(Vec<u8>, Vec<u8>)>;
|
||||
|
||||
// Handler the completion when giving lines contains at least one alias. (e.g: `g checkout`)
|
||||
// that `g` is an alias of `git`
|
||||
fn try_find_alias(line: &[u8], working_set: &StateWorkingSet) -> (Vec<u8>, Vec<usize>) {
|
||||
// An vector represents the offsets of alias
|
||||
// e.g: the offset is 2 for the alias `g` of `git`
|
||||
let mut alias_offset = vec![];
|
||||
let mut output = vec![];
|
||||
if let Some(matched_alias) = search_alias(line, working_set) {
|
||||
let mut lens = matched_alias.len();
|
||||
for (input_vec, line_vec) in matched_alias {
|
||||
alias_offset.push(line_vec.len() - input_vec.len());
|
||||
output.extend(line_vec);
|
||||
if lens > 1 {
|
||||
output.push(b' ');
|
||||
lens -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if !line.is_empty() {
|
||||
let last = line.last().expect("input is empty");
|
||||
if last == &b' ' {
|
||||
output.push(b' ');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
output = line.to_vec();
|
||||
}
|
||||
|
||||
(output, alias_offset)
|
||||
}
|
||||
|
||||
fn search_alias(input: &[u8], working_set: &StateWorkingSet) -> Option<MatchedAlias> {
|
||||
let mut vec_names = vec![];
|
||||
let mut vec_alias = vec![];
|
||||
let mut pos = 0;
|
||||
let mut is_alias = false;
|
||||
for (index, character) in input.iter().enumerate() {
|
||||
if *character == b' ' {
|
||||
let range = &input[pos..index];
|
||||
vec_names.push(range.to_owned());
|
||||
pos = index + 1;
|
||||
}
|
||||
}
|
||||
// Push the rest to names vector.
|
||||
if pos < input.len() {
|
||||
vec_names.push((&input[pos..]).to_owned());
|
||||
}
|
||||
|
||||
for name in &vec_names {
|
||||
if let Some(alias_id) = working_set.find_alias(&name[..]) {
|
||||
let alias_span = working_set.get_alias(alias_id);
|
||||
let mut span_vec = vec![];
|
||||
is_alias = true;
|
||||
for alias in alias_span {
|
||||
let name = working_set.get_span_contents(*alias);
|
||||
if !name.is_empty() {
|
||||
span_vec.push(name);
|
||||
}
|
||||
}
|
||||
// Join span of vector together for complex alias, e.g: `f` is an alias for `git remote -v`
|
||||
let full_aliases = span_vec.join(&[b' '][..]);
|
||||
vec_alias.push(full_aliases);
|
||||
} else {
|
||||
vec_alias.push(name.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
if is_alias {
|
||||
// Zip names and alias vectors, the original inputs and its aliases mapping.
|
||||
// e.g:(['g'], ['g','i','t'])
|
||||
let output = vec_names.into_iter().zip(vec_alias).collect();
|
||||
Some(output)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// reads the most left variable returning it's name (e.g: $myvar)
|
||||
// and the depth (a.b.c)
|
||||
fn most_left_variable(
|
||||
idx: usize,
|
||||
working_set: &StateWorkingSet<'_>,
|
||||
flattened: Vec<(Span, FlatShape)>,
|
||||
) -> Option<(Vec<u8>, Vec<Vec<u8>>)> {
|
||||
// Reverse items to read the list backwards and truncate
|
||||
// because the only items that matters are the ones before the current index
|
||||
let mut rev = flattened;
|
||||
rev.truncate(idx);
|
||||
rev = rev.into_iter().rev().collect();
|
||||
|
||||
// Store the variables and sub levels found and reverse to correct order
|
||||
let mut variables_found: Vec<Vec<u8>> = vec![];
|
||||
let mut found_var = false;
|
||||
for item in rev.clone() {
|
||||
let result = working_set.get_span_contents(item.0).to_vec();
|
||||
|
||||
match item.1 {
|
||||
FlatShape::Variable => {
|
||||
variables_found.push(result);
|
||||
found_var = true;
|
||||
|
||||
break;
|
||||
}
|
||||
FlatShape::String => {
|
||||
variables_found.push(result);
|
||||
}
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If most left var was not found
|
||||
if !found_var {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Reverse the order back
|
||||
variables_found = variables_found.into_iter().rev().collect();
|
||||
|
||||
// Extract the variable and the sublevels
|
||||
let var = variables_found.first().unwrap_or(&vec![]).to_vec();
|
||||
let sublevels: Vec<Vec<u8>> = variables_found.into_iter().skip(1).collect();
|
||||
|
||||
Some((var, sublevels))
|
||||
}
|
137
crates/nu-cli/src/completions/completion_options.rs
Normal file
137
crates/nu-cli/src/completions/completion_options.rs
Normal file
@ -0,0 +1,137 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
||||
use nu_parser::trim_quotes_str;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum SortBy {
|
||||
LevenshteinDistance,
|
||||
Ascending,
|
||||
None,
|
||||
}
|
||||
|
||||
/// Describes how suggestions should be matched.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum MatchAlgorithm {
|
||||
/// Only show suggestions which begin with the given input
|
||||
///
|
||||
/// Example:
|
||||
/// "git switch" is matched by "git sw"
|
||||
Prefix,
|
||||
|
||||
/// Only show suggestions which contain the input chars at any place
|
||||
///
|
||||
/// Example:
|
||||
/// "git checkout" is matched by "gco"
|
||||
Fuzzy,
|
||||
}
|
||||
|
||||
impl MatchAlgorithm {
|
||||
/// Returns whether the `needle` search text matches the given `haystack`.
|
||||
pub fn matches_str(&self, haystack: &str, needle: &str) -> bool {
|
||||
let haystack = trim_quotes_str(haystack);
|
||||
let needle = trim_quotes_str(needle);
|
||||
match *self {
|
||||
MatchAlgorithm::Prefix => haystack.starts_with(needle),
|
||||
MatchAlgorithm::Fuzzy => {
|
||||
let matcher = SkimMatcherV2::default();
|
||||
matcher.fuzzy_match(haystack, needle).is_some()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the `needle` search text matches the given `haystack`.
|
||||
pub fn matches_u8(&self, haystack: &[u8], needle: &[u8]) -> bool {
|
||||
match *self {
|
||||
MatchAlgorithm::Prefix => haystack.starts_with(needle),
|
||||
MatchAlgorithm::Fuzzy => {
|
||||
let haystack_str = String::from_utf8_lossy(haystack);
|
||||
let needle_str = String::from_utf8_lossy(needle);
|
||||
|
||||
let matcher = SkimMatcherV2::default();
|
||||
matcher.fuzzy_match(&haystack_str, &needle_str).is_some()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for MatchAlgorithm {
|
||||
type Error = InvalidMatchAlgorithm;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
match value.as_str() {
|
||||
"prefix" => Ok(Self::Prefix),
|
||||
"fuzzy" => Ok(Self::Fuzzy),
|
||||
_ => Err(InvalidMatchAlgorithm::Unknown),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InvalidMatchAlgorithm {
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Display for InvalidMatchAlgorithm {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match *self {
|
||||
InvalidMatchAlgorithm::Unknown => write!(f, "unknown match algorithm"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for InvalidMatchAlgorithm {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CompletionOptions {
|
||||
pub case_sensitive: bool,
|
||||
pub positional: bool,
|
||||
pub sort_by: SortBy,
|
||||
pub match_algorithm: MatchAlgorithm,
|
||||
}
|
||||
|
||||
impl Default for CompletionOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
case_sensitive: true,
|
||||
positional: true,
|
||||
sort_by: SortBy::Ascending,
|
||||
match_algorithm: MatchAlgorithm::Prefix,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::MatchAlgorithm;
|
||||
|
||||
#[test]
|
||||
fn match_algorithm_prefix() {
|
||||
let algorithm = MatchAlgorithm::Prefix;
|
||||
|
||||
assert!(algorithm.matches_str("example text", ""));
|
||||
assert!(algorithm.matches_str("example text", "examp"));
|
||||
assert!(!algorithm.matches_str("example text", "text"));
|
||||
|
||||
assert!(algorithm.matches_u8(&[1, 2, 3], &[]));
|
||||
assert!(algorithm.matches_u8(&[1, 2, 3], &[1, 2]));
|
||||
assert!(!algorithm.matches_u8(&[1, 2, 3], &[2, 3]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_algorithm_fuzzy() {
|
||||
let algorithm = MatchAlgorithm::Fuzzy;
|
||||
|
||||
assert!(algorithm.matches_str("example text", ""));
|
||||
assert!(algorithm.matches_str("example text", "examp"));
|
||||
assert!(algorithm.matches_str("example text", "ext"));
|
||||
assert!(algorithm.matches_str("example text", "mplxt"));
|
||||
assert!(!algorithm.matches_str("example text", "mpp"));
|
||||
|
||||
assert!(algorithm.matches_u8(&[1, 2, 3], &[]));
|
||||
assert!(algorithm.matches_u8(&[1, 2, 3], &[1, 2]));
|
||||
assert!(algorithm.matches_u8(&[1, 2, 3], &[2, 3]));
|
||||
assert!(algorithm.matches_u8(&[1, 2, 3], &[1, 3]));
|
||||
assert!(!algorithm.matches_u8(&[1, 2, 3], &[2, 2]));
|
||||
}
|
||||
}
|
233
crates/nu-cli/src/completions/custom_completions.rs
Normal file
233
crates/nu-cli/src/completions/custom_completions.rs
Normal file
@ -0,0 +1,233 @@
|
||||
use crate::completions::{Completer, CompletionOptions, MatchAlgorithm, SortBy};
|
||||
use nu_engine::eval_call;
|
||||
use nu_protocol::{
|
||||
ast::{Argument, Call, Expr, Expression},
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
PipelineData, Span, Type, Value,
|
||||
};
|
||||
use reedline::Suggestion;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct CustomCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
stack: Stack,
|
||||
decl_id: usize,
|
||||
line: String,
|
||||
sort_by: SortBy,
|
||||
}
|
||||
|
||||
impl CustomCompletion {
|
||||
pub fn new(engine_state: Arc<EngineState>, stack: Stack, decl_id: usize, line: String) -> Self {
|
||||
Self {
|
||||
engine_state,
|
||||
stack,
|
||||
decl_id,
|
||||
line,
|
||||
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 {
|
||||
fn fetch(
|
||||
&mut self,
|
||||
_: &StateWorkingSet,
|
||||
prefix: Vec<u8>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
pos: usize,
|
||||
completion_options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
// Line position
|
||||
let line_pos = pos - offset;
|
||||
|
||||
// Call custom declaration
|
||||
let result = eval_call(
|
||||
&self.engine_state,
|
||||
&mut self.stack,
|
||||
&Call {
|
||||
decl_id: self.decl_id,
|
||||
head: span,
|
||||
arguments: vec![
|
||||
Argument::Positional(Expression {
|
||||
span: Span { start: 0, end: 0 },
|
||||
ty: Type::String,
|
||||
expr: Expr::String(self.line.clone()),
|
||||
custom_completion: None,
|
||||
}),
|
||||
Argument::Positional(Expression {
|
||||
span: Span { start: 0, end: 0 },
|
||||
ty: Type::Int,
|
||||
expr: Expr::Int(line_pos as i64),
|
||||
custom_completion: None,
|
||||
}),
|
||||
],
|
||||
redirect_stdout: true,
|
||||
redirect_stderr: true,
|
||||
},
|
||||
PipelineData::new(span),
|
||||
);
|
||||
|
||||
let mut custom_completion_options = None;
|
||||
|
||||
// Parse result
|
||||
let suggestions = match result {
|
||||
Ok(pd) => {
|
||||
let value = pd.into_value(span);
|
||||
match &value {
|
||||
Value::Record { .. } => {
|
||||
let completions = value
|
||||
.get_data_by_key("completions")
|
||||
.and_then(|val| {
|
||||
val.as_list()
|
||||
.ok()
|
||||
.map(|it| self.map_completions(it.iter(), span, offset))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let options = value.get_data_by_key("options");
|
||||
|
||||
if let Some(Value::Record { .. }) = &options {
|
||||
let options = options.unwrap_or_default();
|
||||
let should_sort = options
|
||||
.get_data_by_key("sort")
|
||||
.and_then(|val| val.as_bool().ok())
|
||||
.unwrap_or(false);
|
||||
|
||||
if should_sort {
|
||||
self.sort_by = SortBy::Ascending;
|
||||
}
|
||||
|
||||
custom_completion_options = Some(CompletionOptions {
|
||||
case_sensitive: options
|
||||
.get_data_by_key("case_sensitive")
|
||||
.and_then(|val| val.as_bool().ok())
|
||||
.unwrap_or(true),
|
||||
positional: options
|
||||
.get_data_by_key("positional")
|
||||
.and_then(|val| val.as_bool().ok())
|
||||
.unwrap_or(true),
|
||||
sort_by: if should_sort {
|
||||
SortBy::Ascending
|
||||
} else {
|
||||
SortBy::None
|
||||
},
|
||||
match_algorithm: match options
|
||||
.get_data_by_key("completion_algorithm")
|
||||
{
|
||||
Some(option) => option
|
||||
.as_string()
|
||||
.ok()
|
||||
.and_then(|option| option.try_into().ok())
|
||||
.unwrap_or(MatchAlgorithm::Prefix),
|
||||
None => completion_options.match_algorithm,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
completions
|
||||
}
|
||||
Value::List { vals, .. } => self.map_completions(vals.iter(), span, offset),
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
_ => vec![],
|
||||
};
|
||||
|
||||
if let Some(custom_completion_options) = custom_completion_options {
|
||||
filter(&prefix, suggestions, &custom_completion_options)
|
||||
} else {
|
||||
filter(&prefix, suggestions, completion_options)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sort_by(&self) -> SortBy {
|
||||
self.sort_by
|
||||
}
|
||||
}
|
||||
|
||||
fn filter(prefix: &[u8], items: Vec<Suggestion>, options: &CompletionOptions) -> Vec<Suggestion> {
|
||||
items
|
||||
.into_iter()
|
||||
.filter(|it| match options.match_algorithm {
|
||||
MatchAlgorithm::Prefix => match (options.case_sensitive, options.positional) {
|
||||
(true, true) => it.value.as_bytes().starts_with(prefix),
|
||||
(true, false) => it.value.contains(std::str::from_utf8(prefix).unwrap_or("")),
|
||||
(false, positional) => {
|
||||
let value = it.value.to_lowercase();
|
||||
let prefix = std::str::from_utf8(prefix).unwrap_or("").to_lowercase();
|
||||
if positional {
|
||||
value.starts_with(&prefix)
|
||||
} else {
|
||||
value.contains(&prefix)
|
||||
}
|
||||
}
|
||||
},
|
||||
MatchAlgorithm::Fuzzy => options
|
||||
.match_algorithm
|
||||
.matches_u8(it.value.as_bytes(), prefix),
|
||||
})
|
||||
.collect()
|
||||
}
|
160
crates/nu-cli/src/completions/directory_completions.rs
Normal file
160
crates/nu-cli/src/completions/directory_completions.rs
Normal file
@ -0,0 +1,160 @@
|
||||
use crate::completions::{matches, Completer, CompletionOptions};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
levenshtein_distance, Span,
|
||||
};
|
||||
use reedline::Suggestion;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{partial_from, prepend_base_dir};
|
||||
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DirectoryCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
}
|
||||
|
||||
impl DirectoryCompletion {
|
||||
pub fn new(engine_state: Arc<EngineState>) -> Self {
|
||||
Self { engine_state }
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for DirectoryCompletion {
|
||||
fn fetch(
|
||||
&mut self,
|
||||
_: &StateWorkingSet,
|
||||
prefix: Vec<u8>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
_: 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 partial = String::from_utf8_lossy(&prefix).to_string();
|
||||
|
||||
// Filter only the folders
|
||||
let output: Vec<_> = directory_completion(span, &partial, &cwd, options)
|
||||
.into_iter()
|
||||
.map(move |x| Suggestion {
|
||||
value: x.1,
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: x.0.start - offset,
|
||||
end: x.0.end - offset,
|
||||
},
|
||||
append_whitespace: false,
|
||||
})
|
||||
.collect();
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
// Sort results prioritizing the non hidden folders
|
||||
fn sort(&self, items: Vec<Suggestion>, prefix: Vec<u8>) -> Vec<Suggestion> {
|
||||
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
||||
|
||||
// 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)
|
||||
});
|
||||
|
||||
// Separate the results between hidden and non hidden
|
||||
let mut hidden: Vec<Suggestion> = vec![];
|
||||
let mut non_hidden: Vec<Suggestion> = vec![];
|
||||
|
||||
for item in sorted_items.into_iter() {
|
||||
let item_path = Path::new(&item.value);
|
||||
|
||||
if let Some(value) = item_path.file_name() {
|
||||
if let Some(value) = value.to_str() {
|
||||
if value.starts_with('.') {
|
||||
hidden.push(item);
|
||||
} else {
|
||||
non_hidden.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append the hidden folders to the non hidden vec to avoid creating a new vec
|
||||
non_hidden.append(&mut hidden);
|
||||
|
||||
non_hidden
|
||||
}
|
||||
}
|
||||
|
||||
pub fn directory_completion(
|
||||
span: nu_protocol::Span,
|
||||
partial: &str,
|
||||
cwd: &str,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<(nu_protocol::Span, String)> {
|
||||
let original_input = partial;
|
||||
|
||||
let (base_dir_name, partial) = partial_from(partial);
|
||||
|
||||
let base_dir = nu_path::expand_path_with(&base_dir_name, cwd);
|
||||
|
||||
// This check is here as base_dir.read_dir() with base_dir == "" will open the current dir
|
||||
// which we don't want in this case (if we did, base_dir would already be ".")
|
||||
if base_dir == Path::new("") {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
if let Ok(result) = base_dir.read_dir() {
|
||||
return result
|
||||
.filter_map(|entry| {
|
||||
entry.ok().and_then(|entry| {
|
||||
if let Ok(metadata) = fs::metadata(entry.path()) {
|
||||
if metadata.is_dir() {
|
||||
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)
|
||||
} else {
|
||||
file_name.to_string()
|
||||
};
|
||||
|
||||
if entry.path().is_dir() {
|
||||
path.push(SEP);
|
||||
file_name.push(SEP);
|
||||
}
|
||||
|
||||
// Fix files or folders with quotes
|
||||
if path.contains('\'') || path.contains('"') || path.contains(' ') {
|
||||
path = format!("`{}`", path);
|
||||
}
|
||||
|
||||
Some((span, path))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
Vec::new()
|
||||
}
|
124
crates/nu-cli/src/completions/dotnu_completions.rs
Normal file
124
crates/nu-cli/src/completions/dotnu_completions.rs
Normal file
@ -0,0 +1,124 @@
|
||||
use crate::completions::{
|
||||
file_path_completion, partial_from, Completer, CompletionOptions, SortBy,
|
||||
};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
Span,
|
||||
};
|
||||
use reedline::Suggestion;
|
||||
use std::sync::Arc;
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DotNuCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
}
|
||||
|
||||
impl DotNuCompletion {
|
||||
pub fn new(engine_state: Arc<EngineState>) -> Self {
|
||||
Self { engine_state }
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for DotNuCompletion {
|
||||
fn fetch(
|
||||
&mut self,
|
||||
_: &StateWorkingSet,
|
||||
prefix: Vec<u8>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
_: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
||||
let mut search_dirs: Vec<String> = vec![];
|
||||
let (base_dir, mut partial) = partial_from(&prefix_str);
|
||||
let mut is_current_folder = false;
|
||||
|
||||
// Fetch the lib dirs
|
||||
let lib_dirs: Vec<String> =
|
||||
if let Some(lib_dirs) = self.engine_state.get_env_var("NU_LIB_DIRS") {
|
||||
lib_dirs
|
||||
.as_list()
|
||||
.into_iter()
|
||||
.flat_map(|it| {
|
||||
it.iter().map(|x| {
|
||||
x.as_path()
|
||||
.expect("internal error: failed to convert lib path")
|
||||
})
|
||||
})
|
||||
.map(|it| {
|
||||
it.into_os_string()
|
||||
.into_string()
|
||||
.expect("internal error: failed to convert OS path")
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
// Check if the base_dir is a folder
|
||||
if base_dir != format!(".{}", SEP) {
|
||||
// Add the base dir into the directories to be searched
|
||||
search_dirs.push(base_dir.clone());
|
||||
|
||||
// Reset the partial adding the basic dir back
|
||||
// in order to make the span replace work properly
|
||||
let mut base_dir_partial = base_dir;
|
||||
base_dir_partial.push_str(&partial);
|
||||
|
||||
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()
|
||||
};
|
||||
is_current_folder = true;
|
||||
|
||||
// Add the current folder and the lib dirs into the
|
||||
// directories to be searched
|
||||
search_dirs.push(current_folder);
|
||||
search_dirs.extend(lib_dirs);
|
||||
}
|
||||
|
||||
// Fetch the files filtering the ones that ends with .nu
|
||||
// and transform them into suggestions
|
||||
let output: Vec<Suggestion> = search_dirs
|
||||
.into_iter()
|
||||
.flat_map(|it| {
|
||||
file_path_completion(span, &partial, &it, options)
|
||||
.into_iter()
|
||||
.filter(|it| {
|
||||
// Different base dir, so we list the .nu files or folders
|
||||
if !is_current_folder {
|
||||
it.1.ends_with(".nu") || it.1.ends_with(SEP)
|
||||
} else {
|
||||
// Lib dirs, so we filter only the .nu files
|
||||
it.1.ends_with(".nu")
|
||||
}
|
||||
})
|
||||
.map(move |x| Suggestion {
|
||||
value: x.1,
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: x.0.start - offset,
|
||||
end: x.0.end - offset,
|
||||
},
|
||||
append_whitespace: true,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn get_sort_by(&self) -> SortBy {
|
||||
SortBy::LevenshteinDistance
|
||||
}
|
||||
}
|
190
crates/nu-cli/src/completions/file_completions.rs
Normal file
190
crates/nu-cli/src/completions/file_completions.rs
Normal file
@ -0,0 +1,190 @@
|
||||
use crate::completions::{Completer, CompletionOptions};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
levenshtein_distance, Span,
|
||||
};
|
||||
use reedline::Suggestion;
|
||||
use std::path::{is_separator, Path};
|
||||
use std::sync::Arc;
|
||||
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FileCompletion {
|
||||
engine_state: Arc<EngineState>,
|
||||
}
|
||||
|
||||
impl FileCompletion {
|
||||
pub fn new(engine_state: Arc<EngineState>) -> Self {
|
||||
Self { engine_state }
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for FileCompletion {
|
||||
fn fetch(
|
||||
&mut self,
|
||||
_: &StateWorkingSet,
|
||||
prefix: Vec<u8>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
_: 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 prefix = String::from_utf8_lossy(&prefix).to_string();
|
||||
let output: Vec<_> = file_path_completion(span, &prefix, &cwd, options)
|
||||
.into_iter()
|
||||
.map(move |x| Suggestion {
|
||||
value: x.1,
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: x.0.start - offset,
|
||||
end: x.0.end - offset,
|
||||
},
|
||||
append_whitespace: false,
|
||||
})
|
||||
.collect();
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
// Sort results prioritizing the non hidden folders
|
||||
fn sort(&self, items: Vec<Suggestion>, prefix: Vec<u8>) -> Vec<Suggestion> {
|
||||
let prefix_str = String::from_utf8_lossy(&prefix).to_string();
|
||||
|
||||
// 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)
|
||||
});
|
||||
|
||||
// Separate the results between hidden and non hidden
|
||||
let mut hidden: Vec<Suggestion> = vec![];
|
||||
let mut non_hidden: Vec<Suggestion> = vec![];
|
||||
|
||||
for item in sorted_items.into_iter() {
|
||||
let item_path = Path::new(&item.value);
|
||||
|
||||
if let Some(value) = item_path.file_name() {
|
||||
if let Some(value) = value.to_str() {
|
||||
if value.starts_with('.') {
|
||||
hidden.push(item);
|
||||
} else {
|
||||
non_hidden.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append the hidden folders to the non hidden vec to avoid creating a new vec
|
||||
non_hidden.append(&mut hidden);
|
||||
|
||||
non_hidden
|
||||
}
|
||||
}
|
||||
|
||||
pub fn partial_from(input: &str) -> (String, String) {
|
||||
let partial = input.replace('`', "");
|
||||
|
||||
// If partial is only a word we want to search in the current dir
|
||||
let (base, rest) = partial.rsplit_once(is_separator).unwrap_or((".", &partial));
|
||||
// On windows, this standardizes paths to use \
|
||||
let mut base = base.replace(is_separator, &SEP.to_string());
|
||||
|
||||
// rsplit_once removes the separator
|
||||
base.push(SEP);
|
||||
|
||||
(base.to_string(), rest.to_string())
|
||||
}
|
||||
|
||||
pub fn file_path_completion(
|
||||
span: nu_protocol::Span,
|
||||
partial: &str,
|
||||
cwd: &str,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<(nu_protocol::Span, String)> {
|
||||
let original_input = partial;
|
||||
let (base_dir_name, partial) = partial_from(partial);
|
||||
|
||||
let base_dir = nu_path::expand_path_with(&base_dir_name, cwd);
|
||||
// This check is here as base_dir.read_dir() with base_dir == "" will open the current dir
|
||||
// which we don't want in this case (if we did, base_dir would already be ".")
|
||||
if base_dir == Path::new("") {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
if let Ok(result) = base_dir.read_dir() {
|
||||
return result
|
||||
.filter_map(|entry| {
|
||||
entry.ok().and_then(|entry| {
|
||||
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)
|
||||
} else {
|
||||
file_name.to_string()
|
||||
};
|
||||
|
||||
if entry.path().is_dir() {
|
||||
path.push(SEP);
|
||||
file_name.push(SEP);
|
||||
}
|
||||
|
||||
// Fix files or folders with quotes
|
||||
if path.contains('\'') || path.contains('"') || path.contains(' ') {
|
||||
path = format!("`{}`", path);
|
||||
}
|
||||
|
||||
Some((span, path))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
pub fn matches(partial: &str, from: &str, options: &CompletionOptions) -> bool {
|
||||
// Check for case sensitive
|
||||
if !options.case_sensitive {
|
||||
return options
|
||||
.match_algorithm
|
||||
.matches_str(&from.to_ascii_lowercase(), &partial.to_ascii_lowercase());
|
||||
}
|
||||
|
||||
options.match_algorithm.matches_str(from, partial)
|
||||
}
|
||||
|
||||
/// 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 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 = {
|
||||
let mut chars = input.chars();
|
||||
let first_char = chars.next();
|
||||
let second_char = chars.next();
|
||||
|
||||
first_char == Some('.') && second_char.map(is_separator).unwrap_or(false)
|
||||
};
|
||||
|
||||
manually_entered
|
||||
} else {
|
||||
// always prepend the base dir if it is a subfolder
|
||||
true
|
||||
}
|
||||
}
|
86
crates/nu-cli/src/completions/flag_completions.rs
Normal file
86
crates/nu-cli/src/completions/flag_completions.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use crate::completions::{Completer, CompletionOptions};
|
||||
use nu_protocol::{
|
||||
ast::{Expr, Expression},
|
||||
engine::StateWorkingSet,
|
||||
Span,
|
||||
};
|
||||
|
||||
use reedline::Suggestion;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FlagCompletion {
|
||||
expression: Expression,
|
||||
}
|
||||
|
||||
impl FlagCompletion {
|
||||
pub fn new(expression: Expression) -> Self {
|
||||
Self { expression }
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for FlagCompletion {
|
||||
fn fetch(
|
||||
&mut self,
|
||||
working_set: &StateWorkingSet,
|
||||
prefix: Vec<u8>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
_: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
// Check if it's a flag
|
||||
if let Expr::Call(call) = &self.expression.expr {
|
||||
let decl = working_set.get_decl(call.decl_id);
|
||||
let sig = decl.signature();
|
||||
|
||||
let mut output = vec![];
|
||||
|
||||
for named in &sig.named {
|
||||
let flag_desc = &named.desc;
|
||||
if let Some(short) = named.short {
|
||||
let mut named = vec![0; short.len_utf8()];
|
||||
short.encode_utf8(&mut named);
|
||||
named.insert(0, b'-');
|
||||
|
||||
if options.match_algorithm.matches_u8(&named, &prefix) {
|
||||
output.push(Suggestion {
|
||||
value: String::from_utf8_lossy(&named).to_string(),
|
||||
description: Some(flag_desc.to_string()),
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if named.long.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut named = named.long.as_bytes().to_vec();
|
||||
named.insert(0, b'-');
|
||||
named.insert(0, b'-');
|
||||
|
||||
if options.match_algorithm.matches_u8(&named, &prefix) {
|
||||
output.push(Suggestion {
|
||||
value: String::from_utf8_lossy(&named).to_string(),
|
||||
description: Some(flag_desc.to_string()),
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
},
|
||||
append_whitespace: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
vec![]
|
||||
}
|
||||
}
|
23
crates/nu-cli/src/completions/mod.rs
Normal file
23
crates/nu-cli/src/completions/mod.rs
Normal file
@ -0,0 +1,23 @@
|
||||
mod base;
|
||||
mod command_completions;
|
||||
mod completer;
|
||||
mod completion_options;
|
||||
mod custom_completions;
|
||||
mod directory_completions;
|
||||
mod dotnu_completions;
|
||||
mod file_completions;
|
||||
mod flag_completions;
|
||||
mod variable_completions;
|
||||
|
||||
pub use base::Completer;
|
||||
pub use command_completions::CommandCompletion;
|
||||
pub use completer::NuCompleter;
|
||||
pub use completion_options::{CompletionOptions, MatchAlgorithm, SortBy};
|
||||
pub use custom_completions::CustomCompletion;
|
||||
pub use directory_completions::DirectoryCompletion;
|
||||
pub use dotnu_completions::DotNuCompletion;
|
||||
pub use file_completions::{
|
||||
file_path_completion, matches, partial_from, prepend_base_dir, FileCompletion,
|
||||
};
|
||||
pub use flag_completions::FlagCompletion;
|
||||
pub use variable_completions::VariableCompletion;
|
292
crates/nu-cli/src/completions/variable_completions.rs
Normal file
292
crates/nu-cli/src/completions/variable_completions.rs
Normal file
@ -0,0 +1,292 @@
|
||||
use crate::completions::{Completer, CompletionOptions};
|
||||
use nu_engine::eval_variable;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Span, Value,
|
||||
};
|
||||
|
||||
use reedline::Suggestion;
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VariableCompletion {
|
||||
engine_state: Arc<EngineState>, // TODO: Is engine state necessary? It's already a part of working set in fetch()
|
||||
stack: Stack,
|
||||
var_context: (Vec<u8>, Vec<Vec<u8>>), // tuple with $var and the sublevels (.b.c.d)
|
||||
}
|
||||
|
||||
impl VariableCompletion {
|
||||
pub fn new(
|
||||
engine_state: Arc<EngineState>,
|
||||
stack: Stack,
|
||||
var_context: (Vec<u8>, Vec<Vec<u8>>),
|
||||
) -> Self {
|
||||
Self {
|
||||
engine_state,
|
||||
stack,
|
||||
var_context,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for VariableCompletion {
|
||||
fn fetch(
|
||||
&mut self,
|
||||
working_set: &StateWorkingSet,
|
||||
prefix: Vec<u8>,
|
||||
span: Span,
|
||||
offset: usize,
|
||||
_: usize,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
let mut output = vec![];
|
||||
let builtins = ["$nu", "$in", "$env", "$nothing"];
|
||||
let var_str = std::str::from_utf8(&self.var_context.0)
|
||||
.unwrap_or("")
|
||||
.to_lowercase();
|
||||
let var_id = working_set.find_variable(&self.var_context.0);
|
||||
let current_span = reedline::Span {
|
||||
start: span.start - offset,
|
||||
end: span.end - offset,
|
||||
};
|
||||
let sublevels_count = self.var_context.1.len();
|
||||
|
||||
// Completions for the given variable
|
||||
if !var_str.is_empty() {
|
||||
// Completion for $env.<tab>
|
||||
if var_str.as_str() == "$env" {
|
||||
let env_vars = self.stack.get_env_vars(&self.engine_state);
|
||||
|
||||
// Return nested values
|
||||
if sublevels_count > 0 {
|
||||
// Extract the target var ($env.<target-var>)
|
||||
let target_var = self.var_context.1[0].clone();
|
||||
let target_var_str =
|
||||
str::from_utf8(&target_var).unwrap_or_default().to_string();
|
||||
|
||||
// Everything after the target var is the nested level ($env.<target-var>.<nested_levels>...)
|
||||
let nested_levels: Vec<Vec<u8>> =
|
||||
self.var_context.1.clone().into_iter().skip(1).collect();
|
||||
|
||||
if let Some(val) = env_vars.get(&target_var_str) {
|
||||
for suggestion in
|
||||
nested_suggestions(val.clone(), nested_levels, current_span)
|
||||
{
|
||||
if options
|
||||
.match_algorithm
|
||||
.matches_u8(suggestion.value.as_bytes(), &prefix)
|
||||
{
|
||||
output.push(suggestion);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
} 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)
|
||||
{
|
||||
output.push(Suggestion {
|
||||
value: env_var.0,
|
||||
description: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
// Completions for $nu.<tab>
|
||||
if var_str.as_str() == "$nu" {
|
||||
// Eval nu var
|
||||
if let Ok(nuval) = eval_variable(
|
||||
&self.engine_state,
|
||||
&self.stack,
|
||||
nu_protocol::NU_VARIABLE_ID,
|
||||
nu_protocol::Span {
|
||||
start: current_span.start,
|
||||
end: 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)
|
||||
{
|
||||
output.push(suggestion);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
},
|
||||
);
|
||||
|
||||
// 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)
|
||||
{
|
||||
output.push(suggestion);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Variable completion (e.g: $en<tab> to complete $env)
|
||||
for builtin in builtins {
|
||||
if options
|
||||
.match_algorithm
|
||||
.matches_u8(builtin.as_bytes(), &prefix)
|
||||
{
|
||||
output.push(Suggestion {
|
||||
value: builtin.to_string(),
|
||||
description: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: The following can be refactored (see find_commands_by_predicate() used in
|
||||
// command_completions).
|
||||
let mut removed_overlays = vec![];
|
||||
// Working set scope vars
|
||||
for scope_frame in working_set.delta.scope.iter().rev() {
|
||||
for overlay_frame in scope_frame
|
||||
.active_overlays(&mut removed_overlays)
|
||||
.iter()
|
||||
.rev()
|
||||
{
|
||||
for v in &overlay_frame.vars {
|
||||
if options.match_algorithm.matches_u8(v.0, &prefix) {
|
||||
output.push(Suggestion {
|
||||
value: String::from_utf8_lossy(v.0).to_string(),
|
||||
description: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Permanent state vars
|
||||
// for scope in &self.engine_state.scope {
|
||||
for overlay_frame in self
|
||||
.engine_state
|
||||
.active_overlays(&removed_overlays)
|
||||
.iter()
|
||||
.rev()
|
||||
{
|
||||
for v in &overlay_frame.vars {
|
||||
if options.match_algorithm.matches_u8(v.0, &prefix) {
|
||||
output.push(Suggestion {
|
||||
value: String::from_utf8_lossy(v.0).to_string(),
|
||||
description: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output.dedup(); // TODO: Removes only consecutive duplicates, is it intended?
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
// Find recursively the values for sublevels
|
||||
// if no sublevels are set it returns the current value
|
||||
fn nested_suggestions(
|
||||
val: Value,
|
||||
sublevels: Vec<Vec<u8>>,
|
||||
current_span: reedline::Span,
|
||||
) -> Vec<Suggestion> {
|
||||
let mut output: Vec<Suggestion> = vec![];
|
||||
let value = recursive_value(val, sublevels);
|
||||
|
||||
match value {
|
||||
Value::Record {
|
||||
cols,
|
||||
vals: _,
|
||||
span: _,
|
||||
} => {
|
||||
// Add all the columns as completion
|
||||
for item in cols {
|
||||
output.push(Suggestion {
|
||||
value: item,
|
||||
description: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
_ => output,
|
||||
}
|
||||
}
|
||||
|
||||
// Extracts the recursive value (e.g: $var.a.b.c)
|
||||
fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
|
||||
// Go to next sublevel
|
||||
if let Some(next_sublevel) = sublevels.clone().into_iter().next() {
|
||||
match val {
|
||||
Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: _,
|
||||
} => {
|
||||
for item in cols.into_iter().zip(vals.into_iter()) {
|
||||
// Check if index matches with sublevel
|
||||
if item.0.as_bytes().to_vec() == next_sublevel {
|
||||
// If matches try to fetch recursively the next
|
||||
return recursive_value(item.1, sublevels.into_iter().skip(1).collect());
|
||||
}
|
||||
}
|
||||
|
||||
// Current sublevel value not found
|
||||
return Value::Nothing {
|
||||
span: Span { start: 0, end: 0 },
|
||||
};
|
||||
}
|
||||
_ => return val,
|
||||
}
|
||||
}
|
||||
|
||||
val
|
||||
}
|
100
crates/nu-cli/src/config_files.rs
Normal file
100
crates/nu-cli/src/config_files.rs
Normal file
@ -0,0 +1,100 @@
|
||||
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 std::path::PathBuf;
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
const PLUGIN_FILE: &str = "plugin.nu";
|
||||
|
||||
const HISTORY_FILE_TXT: &str = "history.txt";
|
||||
const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
pub fn read_plugin_file(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
storage_path: &str,
|
||||
is_perf_true: bool,
|
||||
) {
|
||||
// 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);
|
||||
|
||||
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();
|
||||
|
||||
if let Ok(contents) = std::fs::read(&plugin_path) {
|
||||
eval_source(
|
||||
engine_state,
|
||||
stack,
|
||||
&contents,
|
||||
&plugin_filename,
|
||||
PipelineData::new(Span::new(0, 0)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if is_perf_true {
|
||||
info!("read_plugin_file {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
}
|
||||
|
||||
#[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() {
|
||||
// Path to store plugins signatures
|
||||
plugin_path.push(storage_path);
|
||||
plugin_path.push(PLUGIN_FILE);
|
||||
engine_state.plugin_signatures = Some(plugin_path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_config_contents(
|
||||
config_path: PathBuf,
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
) {
|
||||
if config_path.exists() & config_path.is_file() {
|
||||
let config_filename = config_path.to_string_lossy().to_owned();
|
||||
|
||||
if let Ok(contents) = std::fs::read(&config_path) {
|
||||
eval_source(
|
||||
engine_state,
|
||||
stack,
|
||||
&contents,
|
||||
&config_filename,
|
||||
PipelineData::new(Span::new(0, 0)),
|
||||
);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_history_path(storage_path: &str, mode: HistoryFileFormat) -> Option<PathBuf> {
|
||||
nu_path::config_dir().map(|mut history_path| {
|
||||
history_path.push(storage_path);
|
||||
history_path.push(match mode {
|
||||
HistoryFileFormat::PlainText => HISTORY_FILE_TXT,
|
||||
HistoryFileFormat::Sqlite => HISTORY_FILE_SQLITE,
|
||||
});
|
||||
history_path
|
||||
})
|
||||
}
|
143
crates/nu-cli/src/eval_file.rs
Normal file
143
crates/nu-cli/src/eval_file.rs
Normal file
@ -0,0 +1,143 @@
|
||||
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_parser::parse;
|
||||
use nu_protocol::Type;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Config, PipelineData, Span, Value,
|
||||
};
|
||||
use nu_utils::stdout_write_all_and_flush;
|
||||
|
||||
/// Main function used when a file path is found as argument for nu
|
||||
pub fn evaluate_file(
|
||||
path: String,
|
||||
args: &[String],
|
||||
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) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let file = std::fs::read(&path).into_diagnostic()?;
|
||||
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
trace!("parsing file: {}", path);
|
||||
|
||||
let _ = parse(&mut working_set, Some(&path), &file, false, &[]);
|
||||
|
||||
if working_set.find_decl(b"main", &Type::Any).is_some() {
|
||||
let args = format!("main {}", args.join(" "));
|
||||
|
||||
if !eval_source(
|
||||
engine_state,
|
||||
stack,
|
||||
&file,
|
||||
&path,
|
||||
PipelineData::new(Span::new(0, 0)),
|
||||
) {
|
||||
std::process::exit(1);
|
||||
}
|
||||
if !eval_source(engine_state, stack, args.as_bytes(), "<commandline>", input) {
|
||||
std::process::exit(1);
|
||||
}
|
||||
} else if !eval_source(engine_state, stack, &file, &path, input) {
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if is_perf_true {
|
||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn print_table_or_error(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
mut pipeline_data: PipelineData,
|
||||
config: &mut Config,
|
||||
) -> Option<i64> {
|
||||
let exit_code = match &mut pipeline_data {
|
||||
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// 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,
|
||||
);
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure everything has finished
|
||||
if let Some(exit_code) = exit_code {
|
||||
let mut exit_code: Vec<_> = exit_code.into_iter().collect();
|
||||
exit_code
|
||||
.pop()
|
||||
.and_then(|last_exit_code| match last_exit_code {
|
||||
Value::Int { val: code, .. } => Some(code),
|
||||
_ => None,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
@ -1,473 +0,0 @@
|
||||
use rustyline::{KeyCode as RustyKeyCode, Modifiers};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn convert_keyevent(key_event: KeyCode, modifiers: Option<Modifiers>) -> rustyline::KeyEvent {
|
||||
match key_event {
|
||||
KeyCode::UnknownEscSeq => convert_to_rl_keyevent(RustyKeyCode::UnknownEscSeq, modifiers),
|
||||
KeyCode::Backspace => convert_to_rl_keyevent(RustyKeyCode::Backspace, modifiers),
|
||||
KeyCode::BackTab => convert_to_rl_keyevent(RustyKeyCode::BackTab, modifiers),
|
||||
KeyCode::BracketedPasteStart => {
|
||||
convert_to_rl_keyevent(RustyKeyCode::BracketedPasteStart, modifiers)
|
||||
}
|
||||
KeyCode::BracketedPasteEnd => {
|
||||
convert_to_rl_keyevent(RustyKeyCode::BracketedPasteEnd, modifiers)
|
||||
}
|
||||
KeyCode::Char(c) => convert_to_rl_keyevent(RustyKeyCode::Char(c), modifiers),
|
||||
KeyCode::Delete => convert_to_rl_keyevent(RustyKeyCode::Delete, modifiers),
|
||||
KeyCode::Down => convert_to_rl_keyevent(RustyKeyCode::Down, modifiers),
|
||||
KeyCode::End => convert_to_rl_keyevent(RustyKeyCode::End, modifiers),
|
||||
KeyCode::Enter => convert_to_rl_keyevent(RustyKeyCode::Enter, modifiers),
|
||||
KeyCode::Esc => convert_to_rl_keyevent(RustyKeyCode::Esc, modifiers),
|
||||
KeyCode::F(u) => convert_to_rl_keyevent(RustyKeyCode::F(u), modifiers),
|
||||
KeyCode::Home => convert_to_rl_keyevent(RustyKeyCode::Home, modifiers),
|
||||
KeyCode::Insert => convert_to_rl_keyevent(RustyKeyCode::Insert, modifiers),
|
||||
KeyCode::Left => convert_to_rl_keyevent(RustyKeyCode::Left, modifiers),
|
||||
KeyCode::Null => convert_to_rl_keyevent(RustyKeyCode::Null, modifiers),
|
||||
KeyCode::PageDown => convert_to_rl_keyevent(RustyKeyCode::PageDown, modifiers),
|
||||
KeyCode::PageUp => convert_to_rl_keyevent(RustyKeyCode::PageUp, modifiers),
|
||||
KeyCode::Right => convert_to_rl_keyevent(RustyKeyCode::Right, modifiers),
|
||||
KeyCode::Tab => convert_to_rl_keyevent(RustyKeyCode::Tab, modifiers),
|
||||
KeyCode::Up => convert_to_rl_keyevent(RustyKeyCode::Up, modifiers),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_to_rl_keyevent(
|
||||
key_code: RustyKeyCode,
|
||||
modifier: Option<Modifiers>,
|
||||
) -> rustyline::KeyEvent {
|
||||
rustyline::KeyEvent {
|
||||
0: key_code,
|
||||
1: modifier.unwrap_or(Modifiers::NONE),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_word(word: Word) -> rustyline::Word {
|
||||
match word {
|
||||
Word::Big => rustyline::Word::Big,
|
||||
Word::Emacs => rustyline::Word::Emacs,
|
||||
Word::Vi => rustyline::Word::Vi,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_at(at: At) -> rustyline::At {
|
||||
match at {
|
||||
At::AfterEnd => rustyline::At::AfterEnd,
|
||||
At::BeforeEnd => rustyline::At::BeforeEnd,
|
||||
At::Start => rustyline::At::Start,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_char_search(search: CharSearch) -> rustyline::CharSearch {
|
||||
match search {
|
||||
CharSearch::Backward(c) => rustyline::CharSearch::Backward(c),
|
||||
CharSearch::BackwardAfter(c) => rustyline::CharSearch::BackwardAfter(c),
|
||||
CharSearch::Forward(c) => rustyline::CharSearch::Forward(c),
|
||||
CharSearch::ForwardBefore(c) => rustyline::CharSearch::ForwardBefore(c),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_movement(movement: Movement) -> rustyline::Movement {
|
||||
match movement {
|
||||
Movement::BackwardChar(u) => rustyline::Movement::BackwardChar(u),
|
||||
Movement::BackwardWord { repeat, word } => {
|
||||
rustyline::Movement::BackwardWord(repeat, convert_word(word))
|
||||
}
|
||||
Movement::BeginningOfBuffer => rustyline::Movement::BeginningOfBuffer,
|
||||
Movement::BeginningOfLine => rustyline::Movement::BeginningOfLine,
|
||||
Movement::EndOfBuffer => rustyline::Movement::EndOfBuffer,
|
||||
Movement::EndOfLine => rustyline::Movement::EndOfLine,
|
||||
Movement::ForwardChar(u) => rustyline::Movement::ForwardChar(u),
|
||||
Movement::ForwardWord { repeat, at, word } => {
|
||||
rustyline::Movement::ForwardWord(repeat, convert_at(at), convert_word(word))
|
||||
}
|
||||
Movement::LineDown(u) => rustyline::Movement::LineDown(u),
|
||||
Movement::LineUp(u) => rustyline::Movement::LineUp(u),
|
||||
Movement::ViCharSearch { repeat, search } => {
|
||||
rustyline::Movement::ViCharSearch(repeat, convert_char_search(search))
|
||||
}
|
||||
Movement::ViFirstPrint => rustyline::Movement::ViFirstPrint,
|
||||
Movement::WholeBuffer => rustyline::Movement::WholeBuffer,
|
||||
Movement::WholeLine => rustyline::Movement::WholeLine,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_anchor(anchor: Anchor) -> rustyline::Anchor {
|
||||
match anchor {
|
||||
Anchor::After => rustyline::Anchor::After,
|
||||
Anchor::Before => rustyline::Anchor::Before,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_cmd(cmd: Cmd) -> rustyline::Cmd {
|
||||
match cmd {
|
||||
Cmd::Abort => rustyline::Cmd::Abort,
|
||||
Cmd::AcceptLine => rustyline::Cmd::AcceptLine,
|
||||
Cmd::AcceptOrInsertLine => rustyline::Cmd::AcceptOrInsertLine {
|
||||
accept_in_the_middle: false,
|
||||
},
|
||||
Cmd::BeginningOfHistory => rustyline::Cmd::BeginningOfHistory,
|
||||
Cmd::CapitalizeWord => rustyline::Cmd::CapitalizeWord,
|
||||
Cmd::ClearScreen => rustyline::Cmd::ClearScreen,
|
||||
Cmd::Complete => rustyline::Cmd::Complete,
|
||||
Cmd::CompleteBackward => rustyline::Cmd::CompleteBackward,
|
||||
Cmd::CompleteHint => rustyline::Cmd::CompleteHint,
|
||||
Cmd::Dedent(movement) => rustyline::Cmd::Dedent(convert_movement(movement)),
|
||||
Cmd::DowncaseWord => rustyline::Cmd::DowncaseWord,
|
||||
Cmd::EndOfFile => rustyline::Cmd::EndOfFile,
|
||||
Cmd::EndOfHistory => rustyline::Cmd::EndOfHistory,
|
||||
Cmd::ForwardSearchHistory => rustyline::Cmd::ForwardSearchHistory,
|
||||
Cmd::HistorySearchBackward => rustyline::Cmd::HistorySearchBackward,
|
||||
Cmd::HistorySearchForward => rustyline::Cmd::HistorySearchForward,
|
||||
Cmd::Indent(movement) => rustyline::Cmd::Indent(convert_movement(movement)),
|
||||
Cmd::Insert { repeat, string } => rustyline::Cmd::Insert(repeat, string),
|
||||
Cmd::Interrupt => rustyline::Cmd::Interrupt,
|
||||
Cmd::Kill(movement) => rustyline::Cmd::Kill(convert_movement(movement)),
|
||||
Cmd::LineDownOrNextHistory(u) => rustyline::Cmd::LineDownOrNextHistory(u),
|
||||
Cmd::LineUpOrPreviousHistory(u) => rustyline::Cmd::LineUpOrPreviousHistory(u),
|
||||
Cmd::Move(movement) => rustyline::Cmd::Move(convert_movement(movement)),
|
||||
Cmd::NextHistory => rustyline::Cmd::NextHistory,
|
||||
Cmd::Newline => rustyline::Cmd::Newline,
|
||||
Cmd::Noop => rustyline::Cmd::Noop,
|
||||
Cmd::Overwrite(c) => rustyline::Cmd::Overwrite(c),
|
||||
#[cfg(windows)]
|
||||
Cmd::PasteFromClipboard => rustyline::Cmd::PasteFromClipboard,
|
||||
Cmd::PreviousHistory => rustyline::Cmd::PreviousHistory,
|
||||
Cmd::QuotedInsert => rustyline::Cmd::QuotedInsert,
|
||||
Cmd::Replace {
|
||||
movement,
|
||||
replacement,
|
||||
} => rustyline::Cmd::Replace(convert_movement(movement), replacement),
|
||||
Cmd::ReplaceChar { repeat, ch } => rustyline::Cmd::ReplaceChar(repeat, ch),
|
||||
Cmd::ReverseSearchHistory => rustyline::Cmd::ReverseSearchHistory,
|
||||
Cmd::SelfInsert { repeat, ch } => rustyline::Cmd::SelfInsert(repeat, ch),
|
||||
Cmd::Suspend => rustyline::Cmd::Suspend,
|
||||
Cmd::TransposeChars => rustyline::Cmd::TransposeChars,
|
||||
Cmd::TransposeWords(u) => rustyline::Cmd::TransposeWords(u),
|
||||
Cmd::Undo(u) => rustyline::Cmd::Undo(u),
|
||||
Cmd::Unknown => rustyline::Cmd::Unknown,
|
||||
Cmd::UpcaseWord => rustyline::Cmd::UpcaseWord,
|
||||
Cmd::ViYankTo(movement) => rustyline::Cmd::ViYankTo(convert_movement(movement)),
|
||||
Cmd::Yank { repeat, anchor } => rustyline::Cmd::Yank(repeat, convert_anchor(anchor)),
|
||||
Cmd::YankPop => rustyline::Cmd::YankPop,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_keybinding(keybinding: Keybinding) -> (rustyline::KeyEvent, rustyline::Cmd) {
|
||||
let rusty_modifiers = match keybinding.modifiers {
|
||||
Some(mods) => match mods {
|
||||
NuModifiers::Ctrl => Some(Modifiers::CTRL),
|
||||
NuModifiers::Alt => Some(Modifiers::ALT),
|
||||
NuModifiers::Shift => Some(Modifiers::SHIFT),
|
||||
NuModifiers::None => Some(Modifiers::NONE),
|
||||
NuModifiers::CtrlShift => Some(Modifiers::CTRL_SHIFT),
|
||||
NuModifiers::AltShift => Some(Modifiers::ALT_SHIFT),
|
||||
NuModifiers::CtrlAlt => Some(Modifiers::CTRL_ALT),
|
||||
NuModifiers::CtrlAltShift => Some(Modifiers::CTRL_ALT_SHIFT),
|
||||
// _ => None,
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
(
|
||||
convert_keyevent(keybinding.key, rusty_modifiers),
|
||||
convert_cmd(keybinding.binding),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum KeyCode {
|
||||
/// Unsupported escape sequence (on unix platform)
|
||||
UnknownEscSeq,
|
||||
/// ⌫ or `KeyEvent::Ctrl('H')`
|
||||
Backspace,
|
||||
/// ⇤ (usually Shift-Tab)
|
||||
BackTab,
|
||||
/// Paste (on unix platform)
|
||||
BracketedPasteStart,
|
||||
/// Paste (on unix platform)
|
||||
BracketedPasteEnd,
|
||||
/// Single char
|
||||
Char(char),
|
||||
/// ⌦
|
||||
Delete,
|
||||
/// ↓ arrow key
|
||||
Down,
|
||||
/// ⇲
|
||||
End,
|
||||
/// ↵ or `KeyEvent::Ctrl('M')`
|
||||
Enter,
|
||||
/// Escape or `KeyEvent::Ctrl('[')`
|
||||
Esc,
|
||||
/// Function key
|
||||
F(u8),
|
||||
/// ⇱
|
||||
Home,
|
||||
/// Insert key
|
||||
Insert,
|
||||
/// ← arrow key
|
||||
Left,
|
||||
// /// `KeyEvent::Char('\0')`
|
||||
Null,
|
||||
/// ⇟
|
||||
PageDown,
|
||||
/// ⇞
|
||||
PageUp,
|
||||
/// → arrow key
|
||||
Right,
|
||||
/// ⇥ or `KeyEvent::Ctrl('I')`
|
||||
Tab,
|
||||
/// ↑ arrow key
|
||||
Up,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum Cmd {
|
||||
/// abort
|
||||
Abort, // Miscellaneous Command
|
||||
/// accept-line
|
||||
AcceptLine,
|
||||
/// beginning-of-history
|
||||
BeginningOfHistory,
|
||||
/// capitalize-word
|
||||
CapitalizeWord,
|
||||
/// clear-screen
|
||||
ClearScreen,
|
||||
/// complete
|
||||
Complete,
|
||||
/// complete-backward
|
||||
CompleteBackward,
|
||||
/// complete-hint
|
||||
CompleteHint,
|
||||
/// Dedent current line
|
||||
Dedent(Movement),
|
||||
/// downcase-word
|
||||
DowncaseWord,
|
||||
/// vi-eof-maybe
|
||||
EndOfFile,
|
||||
/// end-of-history
|
||||
EndOfHistory,
|
||||
/// forward-search-history
|
||||
ForwardSearchHistory,
|
||||
/// history-search-backward
|
||||
HistorySearchBackward,
|
||||
/// history-search-forward
|
||||
HistorySearchForward,
|
||||
/// Indent current line
|
||||
Indent(Movement),
|
||||
/// Insert text
|
||||
Insert { repeat: RepeatCount, string: String },
|
||||
/// Interrupt signal (Ctrl-C)
|
||||
Interrupt,
|
||||
/// backward-delete-char, backward-kill-line, backward-kill-word
|
||||
/// delete-char, kill-line, kill-word, unix-line-discard, unix-word-rubout,
|
||||
/// vi-delete, vi-delete-to, vi-rubout
|
||||
Kill(Movement),
|
||||
/// backward-char, backward-word, beginning-of-line, end-of-line,
|
||||
/// forward-char, forward-word, vi-char-search, vi-end-word, vi-next-word,
|
||||
/// vi-prev-word
|
||||
Move(Movement),
|
||||
/// Inserts a newline
|
||||
Newline,
|
||||
/// next-history
|
||||
NextHistory,
|
||||
/// No action
|
||||
Noop,
|
||||
/// vi-replace
|
||||
Overwrite(char),
|
||||
/// Paste from the clipboard
|
||||
#[cfg(windows)]
|
||||
PasteFromClipboard,
|
||||
/// previous-history
|
||||
PreviousHistory,
|
||||
/// quoted-insert
|
||||
QuotedInsert,
|
||||
/// vi-change-char
|
||||
ReplaceChar { repeat: RepeatCount, ch: char },
|
||||
/// vi-change-to, vi-substitute
|
||||
Replace {
|
||||
movement: Movement,
|
||||
replacement: Option<String>,
|
||||
},
|
||||
/// reverse-search-history
|
||||
ReverseSearchHistory,
|
||||
/// self-insert
|
||||
SelfInsert { repeat: RepeatCount, ch: char },
|
||||
/// Suspend signal (Ctrl-Z on unix platform)
|
||||
Suspend,
|
||||
/// transpose-chars
|
||||
TransposeChars,
|
||||
/// transpose-words
|
||||
TransposeWords(RepeatCount),
|
||||
/// undo
|
||||
Undo(RepeatCount),
|
||||
/// Unsupported / unexpected
|
||||
Unknown,
|
||||
/// upcase-word
|
||||
UpcaseWord,
|
||||
/// vi-yank-to
|
||||
ViYankTo(Movement),
|
||||
/// yank, vi-put
|
||||
Yank { repeat: RepeatCount, anchor: Anchor },
|
||||
/// yank-pop
|
||||
YankPop,
|
||||
/// moves cursor to the line above or switches to prev history entry if
|
||||
/// the cursor is already on the first line
|
||||
LineUpOrPreviousHistory(RepeatCount),
|
||||
/// moves cursor to the line below or switches to next history entry if
|
||||
/// the cursor is already on the last line
|
||||
LineDownOrNextHistory(RepeatCount),
|
||||
/// accepts the line when cursor is at the end of the text (non including
|
||||
/// trailing whitespace), inserts newline character otherwise
|
||||
AcceptOrInsertLine,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum Movement {
|
||||
/// Whole current line (not really a movement but a range)
|
||||
WholeLine,
|
||||
/// beginning-of-line
|
||||
BeginningOfLine,
|
||||
/// end-of-line
|
||||
EndOfLine,
|
||||
/// backward-word, vi-prev-word
|
||||
BackwardWord { repeat: RepeatCount, word: Word }, // Backward until start of word
|
||||
/// forward-word, vi-end-word, vi-next-word
|
||||
ForwardWord {
|
||||
repeat: RepeatCount,
|
||||
at: At,
|
||||
word: Word,
|
||||
}, // Forward until start/end of word
|
||||
/// vi-char-search
|
||||
ViCharSearch {
|
||||
repeat: RepeatCount,
|
||||
search: CharSearch,
|
||||
},
|
||||
/// vi-first-print
|
||||
ViFirstPrint,
|
||||
/// backward-char
|
||||
BackwardChar(RepeatCount),
|
||||
/// forward-char
|
||||
ForwardChar(RepeatCount),
|
||||
/// move to the same column on the previous line
|
||||
LineUp(RepeatCount),
|
||||
/// move to the same column on the next line
|
||||
LineDown(RepeatCount),
|
||||
/// Whole user input (not really a movement but a range)
|
||||
WholeBuffer,
|
||||
/// beginning-of-buffer
|
||||
BeginningOfBuffer,
|
||||
/// end-of-buffer
|
||||
EndOfBuffer,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
enum InputMode {
|
||||
/// Vi Command/Alternate
|
||||
Command,
|
||||
/// Insert/Input mode
|
||||
Insert,
|
||||
/// Overwrite mode
|
||||
Replace,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum Word {
|
||||
/// non-blanks characters
|
||||
Big,
|
||||
/// alphanumeric characters
|
||||
Emacs,
|
||||
/// alphanumeric (and '_') characters
|
||||
Vi,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum At {
|
||||
/// Start of word.
|
||||
Start,
|
||||
/// Before end of word.
|
||||
BeforeEnd,
|
||||
/// After end of word.
|
||||
AfterEnd,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum Anchor {
|
||||
/// After cursor
|
||||
After,
|
||||
/// Before cursor
|
||||
Before,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum CharSearch {
|
||||
/// Forward search
|
||||
Forward(char),
|
||||
/// Forward search until
|
||||
ForwardBefore(char),
|
||||
/// Backward search
|
||||
Backward(char),
|
||||
/// Backward search until
|
||||
BackwardAfter(char),
|
||||
}
|
||||
|
||||
/// The set of modifier keys that were triggered along with a key press.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum NuModifiers {
|
||||
/// Control modifier
|
||||
#[serde(alias = "CTRL")]
|
||||
Ctrl = 8,
|
||||
/// Escape or Alt modifier
|
||||
#[serde(alias = "ALT")]
|
||||
Alt = 4,
|
||||
/// Shift modifier
|
||||
#[serde(alias = "SHIFT")]
|
||||
Shift = 2,
|
||||
/// No modifier
|
||||
#[serde(alias = "NONE")]
|
||||
None = 0,
|
||||
/// Ctrl + Shift
|
||||
#[serde(alias = "CTRL_SHIFT")]
|
||||
CtrlShift = 10,
|
||||
/// Alt + Shift
|
||||
#[serde(alias = "ALT_SHIFT")]
|
||||
AltShift = 6,
|
||||
/// Ctrl + Alt
|
||||
#[serde(alias = "CTRL_ALT")]
|
||||
CtrlAlt = 12,
|
||||
/// Ctrl + Alt + Shift
|
||||
#[serde(alias = "CTRL_ALT_SHIFT")]
|
||||
CtrlAltShift = 14,
|
||||
}
|
||||
|
||||
/// The number of times one command should be repeated.
|
||||
pub type RepeatCount = usize;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Keybinding {
|
||||
key: KeyCode,
|
||||
modifiers: Option<NuModifiers>,
|
||||
binding: Cmd,
|
||||
}
|
||||
|
||||
type Keybindings = Vec<Keybinding>;
|
||||
|
||||
pub(crate) fn load_keybindings(
|
||||
rl: &mut rustyline::Editor<crate::shell::Helper>,
|
||||
) -> Result<(), nu_errors::ShellError> {
|
||||
let filename = nu_data::keybinding::keybinding_path()?;
|
||||
let contents = std::fs::read_to_string(filename);
|
||||
|
||||
// Silently fail if there is no file there
|
||||
if let Ok(contents) = contents {
|
||||
let keybindings: Keybindings = serde_yaml::from_str(&contents)?;
|
||||
// eprintln!("{:#?}", keybindings);
|
||||
for keybinding in keybindings {
|
||||
let (k, b) = convert_keybinding(keybinding);
|
||||
// eprintln!("{:?} {:?}", k, b);
|
||||
|
||||
rl.bind_sequence(k, b);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,15 +1,32 @@
|
||||
pub mod app;
|
||||
mod cli;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
mod keybinding;
|
||||
mod line_editor;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
mod shell;
|
||||
mod commands;
|
||||
mod completions;
|
||||
mod config_files;
|
||||
mod eval_file;
|
||||
mod menus;
|
||||
mod nu_highlight;
|
||||
mod print;
|
||||
mod prompt;
|
||||
mod prompt_update;
|
||||
mod reedline_config;
|
||||
mod repl;
|
||||
mod syntax_highlight;
|
||||
mod util;
|
||||
mod validation;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub use crate::cli::cli;
|
||||
pub use commands::evaluate_commands;
|
||||
pub use completions::{FileCompletion, NuCompleter};
|
||||
pub use config_files::eval_config_contents;
|
||||
pub use eval_file::evaluate_file;
|
||||
pub use menus::{DescriptionMenu, NuHelpCompleter};
|
||||
pub use nu_highlight::NuHighlight;
|
||||
pub use print::Print;
|
||||
pub use prompt::NushellPrompt;
|
||||
pub use repl::evaluate_repl;
|
||||
pub use syntax_highlight::NuHighlighter;
|
||||
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error};
|
||||
pub use validation::NuValidator;
|
||||
|
||||
pub use crate::app::App;
|
||||
pub use crate::cli::{parse_and_eval, register_plugins, run_script_file};
|
||||
|
||||
pub use nu_command::create_default_context;
|
||||
#[cfg(feature = "plugin")]
|
||||
pub use config_files::add_plugin_file;
|
||||
#[cfg(feature = "plugin")]
|
||||
pub use config_files::read_plugin_file;
|
||||
|
@ -1,276 +0,0 @@
|
||||
use nu_engine::EvaluationContext;
|
||||
use nu_errors::ShellError;
|
||||
use std::error::Error;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use nu_engine::script::LineResult;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use crate::keybinding::{convert_keyevent, KeyCode};
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use crate::shell::Helper;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
use rustyline::{
|
||||
self,
|
||||
config::Configurer,
|
||||
config::{ColorMode, CompletionType, Config},
|
||||
error::ReadlineError,
|
||||
line_buffer::LineBuffer,
|
||||
At, Cmd, ConditionalEventHandler, Editor, EventHandler, Modifiers, Movement, Word,
|
||||
};
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn convert_rustyline_result_to_string(input: Result<String, ReadlineError>) -> LineResult {
|
||||
match input {
|
||||
Ok(s) if s == "history -c" || s == "history --clear" => LineResult::ClearHistory,
|
||||
Ok(s) => LineResult::Success(s),
|
||||
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
|
||||
Err(ReadlineError::Eof) => LineResult::CtrlD,
|
||||
Err(err) => {
|
||||
eprintln!("Error: {:?}", err);
|
||||
LineResult::Break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
struct PartialCompleteHintHandler;
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
impl ConditionalEventHandler for PartialCompleteHintHandler {
|
||||
fn handle(
|
||||
&self,
|
||||
_evt: &rustyline::Event,
|
||||
_n: rustyline::RepeatCount,
|
||||
_positive: bool,
|
||||
ctx: &rustyline::EventContext,
|
||||
) -> Option<Cmd> {
|
||||
Some(match ctx.hint_text() {
|
||||
Some(hint_text) if ctx.pos() == ctx.line().len() => {
|
||||
let mut line_buffer = LineBuffer::with_capacity(hint_text.len());
|
||||
line_buffer.update(hint_text, 0);
|
||||
line_buffer.move_to_next_word(At::AfterEnd, Word::Vi, 1);
|
||||
|
||||
let text = hint_text[0..line_buffer.pos()].to_string();
|
||||
|
||||
Cmd::Insert(1, text)
|
||||
}
|
||||
_ => Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn default_rustyline_editor_configuration() -> Editor<Helper> {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||
|
||||
let config = Config::builder()
|
||||
.check_cursor_position(true)
|
||||
.color_mode(ColorMode::Forced)
|
||||
.history_ignore_dups(false)
|
||||
.max_history_size(10_000)
|
||||
.build();
|
||||
let mut rl: Editor<_> = Editor::with_config(config);
|
||||
|
||||
// add key bindings to move over a whole word with Ctrl+ArrowLeft and Ctrl+ArrowRight
|
||||
//M modifier, E KeyEvent, K KeyCode
|
||||
rl.bind_sequence(
|
||||
convert_keyevent(KeyCode::Left, Some(Modifiers::CTRL)),
|
||||
Cmd::Move(Movement::BackwardWord(1, Word::Vi)),
|
||||
);
|
||||
|
||||
rl.bind_sequence(
|
||||
convert_keyevent(KeyCode::Right, Some(Modifiers::CTRL)),
|
||||
EventHandler::Conditional(Box::new(PartialCompleteHintHandler)),
|
||||
);
|
||||
|
||||
// workaround for multiline-paste hang in rustyline (see https://github.com/kkawakam/rustyline/issues/202)
|
||||
rl.bind_sequence(
|
||||
convert_keyevent(KeyCode::BracketedPasteStart, None),
|
||||
rustyline::Cmd::Noop,
|
||||
);
|
||||
// Let's set the defaults up front and then override them later if the user indicates
|
||||
// defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167
|
||||
rl.set_max_history_size(100);
|
||||
rl.set_history_ignore_dups(true);
|
||||
rl.set_history_ignore_space(false);
|
||||
rl.set_completion_type(DEFAULT_COMPLETION_MODE);
|
||||
rl.set_completion_prompt_limit(100);
|
||||
rl.set_keyseq_timeout(-1);
|
||||
rl.set_edit_mode(rustyline::config::EditMode::Emacs);
|
||||
rl.set_auto_add_history(false);
|
||||
rl.set_bell_style(rustyline::config::BellStyle::default());
|
||||
rl.set_color_mode(rustyline::ColorMode::Enabled);
|
||||
rl.set_tab_stop(8);
|
||||
|
||||
if let Err(e) = crate::keybinding::load_keybindings(&mut rl) {
|
||||
println!("Error loading keybindings: {:?}", e);
|
||||
}
|
||||
|
||||
rl
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn configure_rustyline_editor(
|
||||
rl: &mut Editor<Helper>,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> Result<(), ShellError> {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||
|
||||
if let Some(line_editor_vars) = config.var("line_editor") {
|
||||
for (idx, value) in line_editor_vars.row_entries() {
|
||||
match idx.as_ref() {
|
||||
"max_history_size" => {
|
||||
if let Ok(max_history_size) = value.as_u64() {
|
||||
rl.set_max_history_size(max_history_size as usize);
|
||||
}
|
||||
}
|
||||
"history_duplicates" => {
|
||||
// history_duplicates = match value.as_string() {
|
||||
// Ok(s) if s.to_lowercase() == "alwaysadd" => {
|
||||
// rustyline::config::HistoryDuplicates::AlwaysAdd
|
||||
// }
|
||||
// Ok(s) if s.to_lowercase() == "ignoreconsecutive" => {
|
||||
// rustyline::config::HistoryDuplicates::IgnoreConsecutive
|
||||
// }
|
||||
// _ => rustyline::config::HistoryDuplicates::AlwaysAdd,
|
||||
// };
|
||||
if let Ok(history_duplicates) = value.as_bool() {
|
||||
rl.set_history_ignore_dups(history_duplicates);
|
||||
}
|
||||
}
|
||||
"history_ignore_space" => {
|
||||
if let Ok(history_ignore_space) = value.as_bool() {
|
||||
rl.set_history_ignore_space(history_ignore_space);
|
||||
}
|
||||
}
|
||||
"completion_type" => {
|
||||
let completion_type = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "circular" => {
|
||||
rustyline::config::CompletionType::Circular
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "list" => {
|
||||
rustyline::config::CompletionType::List
|
||||
}
|
||||
#[cfg(all(unix, feature = "with-fuzzy"))]
|
||||
Ok(s) if s.to_lowercase() == "fuzzy" => {
|
||||
rustyline::config::CompletionType::Fuzzy
|
||||
}
|
||||
_ => DEFAULT_COMPLETION_MODE,
|
||||
};
|
||||
rl.set_completion_type(completion_type);
|
||||
}
|
||||
"completion_prompt_limit" => {
|
||||
if let Ok(completion_prompt_limit) = value.as_u64() {
|
||||
rl.set_completion_prompt_limit(completion_prompt_limit as usize);
|
||||
}
|
||||
}
|
||||
"keyseq_timeout_ms" => {
|
||||
if let Ok(keyseq_timeout_ms) = value.as_u64() {
|
||||
rl.set_keyseq_timeout(keyseq_timeout_ms as i32);
|
||||
}
|
||||
}
|
||||
"edit_mode" => {
|
||||
let edit_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "vi" => rustyline::config::EditMode::Vi,
|
||||
Ok(s) if s.to_lowercase() == "emacs" => rustyline::config::EditMode::Emacs,
|
||||
_ => rustyline::config::EditMode::Emacs,
|
||||
};
|
||||
rl.set_edit_mode(edit_mode);
|
||||
// Note: When edit_mode is Emacs, the keyseq_timeout_ms is set to -1
|
||||
// no matter what you may have configured. This is so that key chords
|
||||
// can be applied without having to do them in a given timeout. So,
|
||||
// it essentially turns off the keyseq timeout.
|
||||
}
|
||||
"auto_add_history" => {
|
||||
if let Ok(auto_add_history) = value.as_bool() {
|
||||
rl.set_auto_add_history(auto_add_history);
|
||||
}
|
||||
}
|
||||
"bell_style" => {
|
||||
let bell_style = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "audible" => {
|
||||
rustyline::config::BellStyle::Audible
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "none" => rustyline::config::BellStyle::None,
|
||||
Ok(s) if s.to_lowercase() == "visible" => {
|
||||
rustyline::config::BellStyle::Visible
|
||||
}
|
||||
_ => rustyline::config::BellStyle::default(),
|
||||
};
|
||||
rl.set_bell_style(bell_style);
|
||||
}
|
||||
"color_mode" => {
|
||||
let color_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "enabled" => rustyline::ColorMode::Enabled,
|
||||
Ok(s) if s.to_lowercase() == "forced" => rustyline::ColorMode::Forced,
|
||||
Ok(s) if s.to_lowercase() == "disabled" => rustyline::ColorMode::Disabled,
|
||||
_ => rustyline::ColorMode::Enabled,
|
||||
};
|
||||
rl.set_color_mode(color_mode);
|
||||
}
|
||||
"tab_stop" => {
|
||||
if let Ok(tab_stop) = value.as_u64() {
|
||||
rl.set_tab_stop(tab_stop as usize);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn nu_line_editor_helper(
|
||||
context: &EvaluationContext,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> crate::shell::Helper {
|
||||
let hinter = rustyline_hinter(config);
|
||||
crate::shell::Helper::new(context.clone(), hinter)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub fn rustyline_hinter(
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> Option<rustyline::hint::HistoryHinter> {
|
||||
if let Some(line_editor_vars) = config.var("line_editor") {
|
||||
for (idx, value) in line_editor_vars.row_entries() {
|
||||
if idx == "show_hints" && value.as_bool() == Ok(false) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(rustyline::hint::HistoryHinter {})
|
||||
}
|
||||
|
||||
pub fn configure_ctrl_c(_context: &EvaluationContext) -> Result<(), Box<dyn Error>> {
|
||||
#[cfg(feature = "ctrlc")]
|
||||
{
|
||||
let cc = _context.ctrl_c().clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
cc.store(true, Ordering::SeqCst);
|
||||
})?;
|
||||
|
||||
if _context.ctrl_c().load(Ordering::SeqCst) {
|
||||
_context.ctrl_c().store(false, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
724
crates/nu-cli/src/menus/description_menu.rs
Normal file
724
crates/nu-cli/src/menus/description_menu.rs
Normal file
@ -0,0 +1,724 @@
|
||||
use {
|
||||
nu_ansi_term::{ansi::RESET, Style},
|
||||
reedline::{
|
||||
menu_functions::string_difference, Completer, LineBuffer, Menu, MenuEvent, MenuTextStyle,
|
||||
Painter, Suggestion,
|
||||
},
|
||||
};
|
||||
|
||||
/// Default values used as reference for the menu. These values are set during
|
||||
/// the initial declaration of the menu and are always kept as reference for the
|
||||
/// changeable [`WorkingDetails`]
|
||||
struct DefaultMenuDetails {
|
||||
/// Number of columns that the menu will have
|
||||
pub columns: u16,
|
||||
/// Column width
|
||||
pub col_width: Option<usize>,
|
||||
/// Column padding
|
||||
pub col_padding: usize,
|
||||
/// Number of rows for commands
|
||||
pub selection_rows: u16,
|
||||
/// Number of rows allowed to display the description
|
||||
pub description_rows: usize,
|
||||
}
|
||||
|
||||
impl Default for DefaultMenuDetails {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
columns: 4,
|
||||
col_width: None,
|
||||
col_padding: 2,
|
||||
selection_rows: 4,
|
||||
description_rows: 10,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the actual column conditions of the menu. These conditions change
|
||||
/// since they need to accommodate possible different line sizes for the column values
|
||||
#[derive(Default)]
|
||||
struct WorkingDetails {
|
||||
/// Number of columns that the menu will have
|
||||
pub columns: u16,
|
||||
/// Column width
|
||||
pub col_width: usize,
|
||||
/// Number of rows for description
|
||||
pub description_rows: usize,
|
||||
}
|
||||
|
||||
/// Completion menu definition
|
||||
pub struct DescriptionMenu {
|
||||
/// Menu name
|
||||
name: String,
|
||||
/// Menu status
|
||||
active: bool,
|
||||
/// Menu coloring
|
||||
color: MenuTextStyle,
|
||||
/// Default column details that are set when creating the menu
|
||||
/// These values are the reference for the working details
|
||||
default_details: DefaultMenuDetails,
|
||||
/// Number of minimum rows that are displayed when
|
||||
/// the required lines is larger than the available lines
|
||||
min_rows: u16,
|
||||
/// Working column details keep changing based on the collected values
|
||||
working_details: WorkingDetails,
|
||||
/// Menu cached values
|
||||
values: Vec<Suggestion>,
|
||||
/// column position of the cursor. Starts from 0
|
||||
col_pos: u16,
|
||||
/// row position in the menu. Starts from 0
|
||||
row_pos: u16,
|
||||
/// Menu marker when active
|
||||
marker: String,
|
||||
/// Event sent to the menu
|
||||
event: Option<MenuEvent>,
|
||||
/// String collected after the menu is activated
|
||||
input: Option<String>,
|
||||
/// Examples to select
|
||||
examples: Vec<String>,
|
||||
/// Example index
|
||||
example_index: Option<usize>,
|
||||
/// Examples may not be shown if there is not enough space in the screen
|
||||
show_examples: bool,
|
||||
/// Skipped description rows
|
||||
skipped_rows: usize,
|
||||
/// Calls the completer using only the line buffer difference difference
|
||||
/// after the menu was activated
|
||||
only_buffer_difference: bool,
|
||||
}
|
||||
|
||||
impl Default for DescriptionMenu {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: "description_menu".to_string(),
|
||||
active: false,
|
||||
color: MenuTextStyle::default(),
|
||||
default_details: DefaultMenuDetails::default(),
|
||||
min_rows: 3,
|
||||
working_details: WorkingDetails::default(),
|
||||
values: Vec::new(),
|
||||
col_pos: 0,
|
||||
row_pos: 0,
|
||||
marker: "? ".to_string(),
|
||||
event: None,
|
||||
input: None,
|
||||
examples: Vec::new(),
|
||||
example_index: None,
|
||||
show_examples: true,
|
||||
skipped_rows: 0,
|
||||
only_buffer_difference: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Menu configuration
|
||||
impl DescriptionMenu {
|
||||
/// Menu builder with new name
|
||||
pub fn with_name(mut self, name: &str) -> Self {
|
||||
self.name = name.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with new value for text style
|
||||
pub fn with_text_style(mut self, text_style: Style) -> Self {
|
||||
self.color.text_style = text_style;
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with new value for text style
|
||||
pub fn with_selected_text_style(mut self, selected_text_style: Style) -> Self {
|
||||
self.color.selected_text_style = selected_text_style;
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with new value for text style
|
||||
pub fn with_description_text_style(mut self, description_text_style: Style) -> Self {
|
||||
self.color.description_style = description_text_style;
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with new columns value
|
||||
pub fn with_columns(mut self, columns: u16) -> Self {
|
||||
self.default_details.columns = columns;
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with new column width value
|
||||
pub fn with_column_width(mut self, col_width: Option<usize>) -> Self {
|
||||
self.default_details.col_width = col_width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with new column width value
|
||||
pub fn with_column_padding(mut self, col_padding: usize) -> Self {
|
||||
self.default_details.col_padding = col_padding;
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with new selection rows value
|
||||
pub fn with_selection_rows(mut self, selection_rows: u16) -> Self {
|
||||
self.default_details.selection_rows = selection_rows;
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with new description rows value
|
||||
pub fn with_description_rows(mut self, description_rows: usize) -> Self {
|
||||
self.default_details.description_rows = description_rows;
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with marker
|
||||
pub fn with_marker(mut self, marker: String) -> Self {
|
||||
self.marker = marker;
|
||||
self
|
||||
}
|
||||
|
||||
/// Menu builder with new only buffer difference
|
||||
pub fn with_only_buffer_difference(mut self, only_buffer_difference: bool) -> Self {
|
||||
self.only_buffer_difference = only_buffer_difference;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// Menu functionality
|
||||
impl DescriptionMenu {
|
||||
/// Move menu cursor to the next element
|
||||
fn move_next(&mut self) {
|
||||
let mut new_col = self.col_pos + 1;
|
||||
let mut new_row = self.row_pos;
|
||||
|
||||
if new_col >= self.get_cols() {
|
||||
new_row += 1;
|
||||
new_col = 0;
|
||||
}
|
||||
|
||||
if new_row >= self.get_rows() {
|
||||
new_row = 0;
|
||||
new_col = 0;
|
||||
}
|
||||
|
||||
let position = new_row * self.get_cols() + new_col;
|
||||
if position >= self.get_values().len() as u16 {
|
||||
self.reset_position();
|
||||
} else {
|
||||
self.col_pos = new_col;
|
||||
self.row_pos = new_row;
|
||||
}
|
||||
}
|
||||
|
||||
/// Move menu cursor to the previous element
|
||||
fn move_previous(&mut self) {
|
||||
let new_col = self.col_pos.checked_sub(1);
|
||||
|
||||
let (new_col, new_row) = match new_col {
|
||||
Some(col) => (col, self.row_pos),
|
||||
None => match self.row_pos.checked_sub(1) {
|
||||
Some(row) => (self.get_cols().saturating_sub(1), row),
|
||||
None => (
|
||||
self.get_cols().saturating_sub(1),
|
||||
self.get_rows().saturating_sub(1),
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
let position = new_row * self.get_cols() + new_col;
|
||||
if position >= self.get_values().len() as u16 {
|
||||
self.col_pos = (self.get_values().len() as u16 % self.get_cols()).saturating_sub(1);
|
||||
self.row_pos = self.get_rows().saturating_sub(1);
|
||||
} else {
|
||||
self.col_pos = new_col;
|
||||
self.row_pos = new_row;
|
||||
}
|
||||
}
|
||||
|
||||
/// Menu index based on column and row position
|
||||
fn index(&self) -> usize {
|
||||
let index = self.row_pos * self.get_cols() + self.col_pos;
|
||||
index as usize
|
||||
}
|
||||
|
||||
/// Get selected value from the menu
|
||||
fn get_value(&self) -> Option<Suggestion> {
|
||||
self.get_values().get(self.index()).cloned()
|
||||
}
|
||||
|
||||
/// Calculates how many rows the Menu will use
|
||||
fn get_rows(&self) -> u16 {
|
||||
let values = self.get_values().len() as u16;
|
||||
|
||||
if values == 0 {
|
||||
// When the values are empty the no_records_msg is shown, taking 1 line
|
||||
return 1;
|
||||
}
|
||||
|
||||
let rows = values / self.get_cols();
|
||||
if values % self.get_cols() != 0 {
|
||||
rows + 1
|
||||
} else {
|
||||
rows
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns working details col width
|
||||
fn get_width(&self) -> usize {
|
||||
self.working_details.col_width
|
||||
}
|
||||
|
||||
/// Reset menu position
|
||||
fn reset_position(&mut self) {
|
||||
self.col_pos = 0;
|
||||
self.row_pos = 0;
|
||||
self.skipped_rows = 0;
|
||||
}
|
||||
|
||||
fn no_records_msg(&self, use_ansi_coloring: bool) -> String {
|
||||
let msg = "TYPE TO START SEARCH";
|
||||
if use_ansi_coloring {
|
||||
format!(
|
||||
"{}{}{}",
|
||||
self.color.selected_text_style.prefix(),
|
||||
msg,
|
||||
RESET
|
||||
)
|
||||
} else {
|
||||
msg.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns working details columns
|
||||
fn get_cols(&self) -> u16 {
|
||||
self.working_details.columns.max(1)
|
||||
}
|
||||
|
||||
/// End of line for menu
|
||||
fn end_of_line(&self, column: u16, index: usize) -> &str {
|
||||
let is_last = index == self.values.len().saturating_sub(1);
|
||||
if column == self.get_cols().saturating_sub(1) || is_last {
|
||||
"\r\n"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/// Update list of examples from the actual value
|
||||
fn update_examples(&mut self) {
|
||||
self.examples = self
|
||||
.get_value()
|
||||
.and_then(|suggestion| suggestion.extra)
|
||||
.unwrap_or_default();
|
||||
|
||||
self.example_index = None;
|
||||
}
|
||||
|
||||
/// Creates default string that represents one suggestion from the menu
|
||||
fn create_entry_string(
|
||||
&self,
|
||||
suggestion: &Suggestion,
|
||||
index: usize,
|
||||
column: u16,
|
||||
empty_space: usize,
|
||||
use_ansi_coloring: bool,
|
||||
) -> String {
|
||||
if use_ansi_coloring {
|
||||
if index == self.index() {
|
||||
format!(
|
||||
"{}{}{}{:>empty$}{}",
|
||||
self.color.selected_text_style.prefix(),
|
||||
&suggestion.value,
|
||||
RESET,
|
||||
"",
|
||||
self.end_of_line(column, index),
|
||||
empty = empty_space,
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{}{}{}{:>empty$}{}",
|
||||
self.color.text_style.prefix(),
|
||||
&suggestion.value,
|
||||
RESET,
|
||||
"",
|
||||
self.end_of_line(column, index),
|
||||
empty = empty_space,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// If no ansi coloring is found, then the selection word is
|
||||
// the line in uppercase
|
||||
let (marker, empty_space) = if index == self.index() {
|
||||
(">", empty_space.saturating_sub(1))
|
||||
} else {
|
||||
("", empty_space)
|
||||
};
|
||||
|
||||
let line = format!(
|
||||
"{}{}{:>empty$}{}",
|
||||
marker,
|
||||
&suggestion.value,
|
||||
"",
|
||||
self.end_of_line(column, index),
|
||||
empty = empty_space,
|
||||
);
|
||||
|
||||
if index == self.index() {
|
||||
line.to_uppercase()
|
||||
} else {
|
||||
line
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Description string with color
|
||||
fn create_description_string(&self, use_ansi_coloring: bool) -> String {
|
||||
let description = self
|
||||
.get_value()
|
||||
.and_then(|suggestion| suggestion.description)
|
||||
.unwrap_or_else(|| "".to_string())
|
||||
.lines()
|
||||
.skip(self.skipped_rows)
|
||||
.take(self.working_details.description_rows)
|
||||
.collect::<Vec<&str>>()
|
||||
.join("\r\n");
|
||||
|
||||
if use_ansi_coloring && !description.is_empty() {
|
||||
format!(
|
||||
"{}{}{}",
|
||||
self.color.description_style.prefix(),
|
||||
description,
|
||||
RESET,
|
||||
)
|
||||
} else {
|
||||
description
|
||||
}
|
||||
}
|
||||
|
||||
/// Selectable list of examples from the actual value
|
||||
fn create_example_string(&self, use_ansi_coloring: bool) -> String {
|
||||
if !self.show_examples {
|
||||
return "".into();
|
||||
}
|
||||
|
||||
let examples: String = self
|
||||
.examples
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, example)| {
|
||||
if let Some(example_index) = self.example_index {
|
||||
if index == example_index {
|
||||
format!(
|
||||
" {}{}{}\r\n",
|
||||
self.color.selected_text_style.prefix(),
|
||||
example,
|
||||
RESET
|
||||
)
|
||||
} else {
|
||||
format!(" {}\r\n", example)
|
||||
}
|
||||
} else {
|
||||
format!(" {}\r\n", example)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if examples.is_empty() {
|
||||
"".into()
|
||||
} else if use_ansi_coloring {
|
||||
format!(
|
||||
"{}\r\n\r\nExamples:\r\n{}{}",
|
||||
self.color.description_style.prefix(),
|
||||
RESET,
|
||||
examples,
|
||||
)
|
||||
} else {
|
||||
format!("\r\n\r\nExamples:\r\n{}", examples,)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Menu for DescriptionMenu {
|
||||
/// Menu name
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
/// Menu indicator
|
||||
fn indicator(&self) -> &str {
|
||||
self.marker.as_str()
|
||||
}
|
||||
|
||||
/// Deactivates context menu
|
||||
fn is_active(&self) -> bool {
|
||||
self.active
|
||||
}
|
||||
|
||||
/// The menu stays active even with one record
|
||||
fn can_quick_complete(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// The menu does not need to partially complete
|
||||
fn can_partially_complete(
|
||||
&mut self,
|
||||
_values_updated: bool,
|
||||
_line_buffer: &mut LineBuffer,
|
||||
_completer: &mut dyn Completer,
|
||||
) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Selects what type of event happened with the menu
|
||||
fn menu_event(&mut self, event: MenuEvent) {
|
||||
match &event {
|
||||
MenuEvent::Activate(_) => self.active = true,
|
||||
MenuEvent::Deactivate => {
|
||||
self.active = false;
|
||||
self.input = None;
|
||||
self.values = Vec::new();
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
self.event = Some(event);
|
||||
}
|
||||
|
||||
/// Updates menu values
|
||||
fn update_values(&mut self, line_buffer: &mut LineBuffer, 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);
|
||||
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());
|
||||
self.reset_position();
|
||||
}
|
||||
}
|
||||
|
||||
/// The working details for the menu changes based on the size of the lines
|
||||
/// collected from the completer
|
||||
fn update_working_details(
|
||||
&mut self,
|
||||
line_buffer: &mut LineBuffer,
|
||||
completer: &mut dyn Completer,
|
||||
painter: &Painter,
|
||||
) {
|
||||
if let Some(event) = self.event.take() {
|
||||
// Updating all working parameters from the menu before executing any of the
|
||||
// possible event
|
||||
let max_width = self.get_values().iter().fold(0, |acc, suggestion| {
|
||||
let str_len = suggestion.value.len() + self.default_details.col_padding;
|
||||
if str_len > acc {
|
||||
str_len
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
});
|
||||
|
||||
// If no default width is found, then the total screen width is used to estimate
|
||||
// the column width based on the default number of columns
|
||||
let default_width = if let Some(col_width) = self.default_details.col_width {
|
||||
col_width
|
||||
} else {
|
||||
let col_width = painter.screen_width() / self.default_details.columns;
|
||||
col_width as usize
|
||||
};
|
||||
|
||||
// Adjusting the working width of the column based the max line width found
|
||||
// in the menu values
|
||||
if max_width > default_width {
|
||||
self.working_details.col_width = max_width;
|
||||
} else {
|
||||
self.working_details.col_width = default_width;
|
||||
};
|
||||
|
||||
// The working columns is adjusted based on possible number of columns
|
||||
// that could be fitted in the screen with the calculated column width
|
||||
let possible_cols = painter.screen_width() / self.working_details.col_width as u16;
|
||||
if possible_cols > self.default_details.columns {
|
||||
self.working_details.columns = self.default_details.columns.max(1);
|
||||
} else {
|
||||
self.working_details.columns = possible_cols;
|
||||
}
|
||||
|
||||
// Updating the working rows to display the description
|
||||
if self.menu_required_lines(painter.screen_width()) <= painter.remaining_lines() {
|
||||
self.working_details.description_rows = self.default_details.description_rows;
|
||||
self.show_examples = true;
|
||||
} else {
|
||||
self.working_details.description_rows = painter
|
||||
.remaining_lines()
|
||||
.saturating_sub(self.default_details.selection_rows + 1)
|
||||
as usize;
|
||||
|
||||
self.show_examples = false;
|
||||
}
|
||||
|
||||
match event {
|
||||
MenuEvent::Activate(_) => {
|
||||
self.reset_position();
|
||||
self.input = Some(line_buffer.get_buffer().to_string());
|
||||
self.update_values(line_buffer, completer);
|
||||
}
|
||||
MenuEvent::Deactivate => self.active = false,
|
||||
MenuEvent::Edit(_) => {
|
||||
self.reset_position();
|
||||
self.update_values(line_buffer, completer);
|
||||
self.update_examples()
|
||||
}
|
||||
MenuEvent::NextElement => {
|
||||
self.skipped_rows = 0;
|
||||
self.move_next();
|
||||
self.update_examples();
|
||||
}
|
||||
MenuEvent::PreviousElement => {
|
||||
self.skipped_rows = 0;
|
||||
self.move_previous();
|
||||
self.update_examples();
|
||||
}
|
||||
MenuEvent::MoveUp => {
|
||||
if let Some(example_index) = self.example_index {
|
||||
if let Some(index) = example_index.checked_sub(1) {
|
||||
self.example_index = Some(index);
|
||||
} else {
|
||||
self.example_index = Some(self.examples.len().saturating_sub(1));
|
||||
}
|
||||
} else if !self.examples.is_empty() {
|
||||
self.example_index = Some(0);
|
||||
}
|
||||
}
|
||||
MenuEvent::MoveDown => {
|
||||
if let Some(example_index) = self.example_index {
|
||||
let index = example_index + 1;
|
||||
if index < self.examples.len() {
|
||||
self.example_index = Some(index);
|
||||
} else {
|
||||
self.example_index = Some(0);
|
||||
}
|
||||
} else if !self.examples.is_empty() {
|
||||
self.example_index = Some(0);
|
||||
}
|
||||
}
|
||||
MenuEvent::MoveLeft => self.skipped_rows = self.skipped_rows.saturating_sub(1),
|
||||
MenuEvent::MoveRight => {
|
||||
let skipped = self.skipped_rows + 1;
|
||||
let description_rows = self
|
||||
.get_value()
|
||||
.and_then(|suggestion| suggestion.description)
|
||||
.unwrap_or_else(|| "".to_string())
|
||||
.lines()
|
||||
.count();
|
||||
|
||||
let allowed_skips =
|
||||
description_rows.saturating_sub(self.working_details.description_rows);
|
||||
|
||||
if skipped < allowed_skips {
|
||||
self.skipped_rows = skipped;
|
||||
} else {
|
||||
self.skipped_rows = allowed_skips;
|
||||
}
|
||||
}
|
||||
MenuEvent::PreviousPage | MenuEvent::NextPage => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The buffer gets replaced in the Span location
|
||||
fn replace_in_buffer(&self, line_buffer: &mut LineBuffer) {
|
||||
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 string_len = if let Some(example_index) = self.example_index {
|
||||
let example = self
|
||||
.examples
|
||||
.get(example_index)
|
||||
.expect("the example index is always checked");
|
||||
|
||||
line_buffer.replace(start..end, example);
|
||||
example.len()
|
||||
} else {
|
||||
line_buffer.replace(start..end, &value);
|
||||
value.len()
|
||||
};
|
||||
|
||||
let mut offset = line_buffer.insertion_point();
|
||||
offset += string_len.saturating_sub(end.saturating_sub(start));
|
||||
line_buffer.set_insertion_point(offset);
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimum rows that should be displayed by the menu
|
||||
fn min_rows(&self) -> u16 {
|
||||
self.get_rows().min(self.min_rows)
|
||||
}
|
||||
|
||||
/// Gets values from filler that will be displayed in the menu
|
||||
fn get_values(&self) -> &[Suggestion] {
|
||||
&self.values
|
||||
}
|
||||
|
||||
fn menu_required_lines(&self, _terminal_columns: u16) -> u16 {
|
||||
let example_lines = self
|
||||
.examples
|
||||
.iter()
|
||||
.fold(0, |acc, example| example.lines().count() + acc);
|
||||
|
||||
self.default_details.selection_rows
|
||||
+ self.default_details.description_rows as u16
|
||||
+ example_lines as u16
|
||||
+ 3
|
||||
}
|
||||
|
||||
fn menu_string(&self, _available_lines: u16, use_ansi_coloring: bool) -> String {
|
||||
if self.get_values().is_empty() {
|
||||
self.no_records_msg(use_ansi_coloring)
|
||||
} else {
|
||||
// The skip values represent the number of lines that should be skipped
|
||||
// while printing the menu
|
||||
let available_lines = self.default_details.selection_rows;
|
||||
let skip_values = if self.row_pos >= available_lines {
|
||||
let skip_lines = self.row_pos.saturating_sub(available_lines) + 1;
|
||||
(skip_lines * self.get_cols()) as usize
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
// It seems that crossterm prefers to have a complete string ready to be printed
|
||||
// rather than looping through the values and printing multiple things
|
||||
// This reduces the flickering when printing the menu
|
||||
let available_values = (available_lines * self.get_cols()) as usize;
|
||||
let selection_values: String = self
|
||||
.get_values()
|
||||
.iter()
|
||||
.skip(skip_values)
|
||||
.take(available_values)
|
||||
.enumerate()
|
||||
.map(|(index, suggestion)| {
|
||||
// Correcting the enumerate index based on the number of skipped values
|
||||
let index = index + skip_values;
|
||||
let column = index as u16 % self.get_cols();
|
||||
let empty_space = self.get_width().saturating_sub(suggestion.value.len());
|
||||
|
||||
self.create_entry_string(
|
||||
suggestion,
|
||||
index,
|
||||
column,
|
||||
empty_space,
|
||||
use_ansi_coloring,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
format!(
|
||||
"{}{}{}",
|
||||
selection_values,
|
||||
self.create_description_string(use_ansi_coloring),
|
||||
self.create_example_string(use_ansi_coloring)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
112
crates/nu-cli/src/menus/help_completions.rs
Normal file
112
crates/nu-cli/src/menus/help_completions.rs
Normal file
@ -0,0 +1,112 @@
|
||||
use nu_engine::documentation::get_flags_section;
|
||||
use nu_protocol::{engine::EngineState, levenshtein_distance};
|
||||
use reedline::{Completer, Suggestion};
|
||||
use std::fmt::Write;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct NuHelpCompleter(Arc<EngineState>);
|
||||
|
||||
impl NuHelpCompleter {
|
||||
pub fn new(engine_state: Arc<EngineState>) -> Self {
|
||||
Self(engine_state)
|
||||
}
|
||||
|
||||
fn completion_helper(&self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
let full_commands = self.0.get_signatures_with_examples(false);
|
||||
|
||||
//Vec<(Signature, Vec<Example>, bool, bool)> {
|
||||
let mut commands = full_commands
|
||||
.iter()
|
||||
.filter(|(sig, _, _, _)| {
|
||||
sig.name.to_lowercase().contains(&line.to_lowercase())
|
||||
|| sig.usage.to_lowercase().contains(&line.to_lowercase())
|
||||
|| sig
|
||||
.search_terms
|
||||
.iter()
|
||||
.any(|term| term.to_lowercase().contains(&line.to_lowercase()))
|
||||
|| sig
|
||||
.extra_usage
|
||||
.to_lowercase()
|
||||
.contains(&line.to_lowercase())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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)
|
||||
});
|
||||
|
||||
commands
|
||||
.into_iter()
|
||||
.map(|(sig, examples, _, _)| {
|
||||
let mut long_desc = String::new();
|
||||
|
||||
let usage = &sig.usage;
|
||||
if !usage.is_empty() {
|
||||
long_desc.push_str(usage);
|
||||
long_desc.push_str("\r\n\r\n");
|
||||
}
|
||||
|
||||
let extra_usage = &sig.extra_usage;
|
||||
if !extra_usage.is_empty() {
|
||||
long_desc.push_str(extra_usage);
|
||||
long_desc.push_str("\r\n\r\n");
|
||||
}
|
||||
|
||||
let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature());
|
||||
|
||||
if !sig.named.is_empty() {
|
||||
long_desc.push_str(&get_flags_section(sig))
|
||||
}
|
||||
|
||||
if !sig.required_positional.is_empty()
|
||||
|| !sig.optional_positional.is_empty()
|
||||
|| sig.rest_positional.is_some()
|
||||
{
|
||||
long_desc.push_str("\r\nParameters:\r\n");
|
||||
for positional in &sig.required_positional {
|
||||
let _ = write!(long_desc, " {}: {}\r\n", positional.name, positional.desc);
|
||||
}
|
||||
for positional in &sig.optional_positional {
|
||||
let _ = write!(
|
||||
long_desc,
|
||||
" (optional) {}: {}\r\n",
|
||||
positional.name, positional.desc
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(rest_positional) = &sig.rest_positional {
|
||||
let _ = write!(
|
||||
long_desc,
|
||||
" ...{}: {}\r\n",
|
||||
rest_positional.name, rest_positional.desc
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let extra: Vec<String> = examples
|
||||
.iter()
|
||||
.map(|example| example.example.replace('\n', "\r\n"))
|
||||
.collect();
|
||||
|
||||
Suggestion {
|
||||
value: sig.name.clone(),
|
||||
description: Some(long_desc),
|
||||
extra: Some(extra),
|
||||
span: reedline::Span {
|
||||
start: pos,
|
||||
end: pos + line.len(),
|
||||
},
|
||||
append_whitespace: false,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for NuHelpCompleter {
|
||||
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
self.completion_helper(line, pos)
|
||||
}
|
||||
}
|
176
crates/nu-cli/src/menus/menu_completions.rs
Normal file
176
crates/nu-cli/src/menus/menu_completions.rs
Normal file
@ -0,0 +1,176 @@
|
||||
use nu_engine::eval_block;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
IntoPipelineData, Span, Value,
|
||||
};
|
||||
use reedline::{menu_functions::parse_selection_char, Completer, Suggestion};
|
||||
use std::sync::Arc;
|
||||
|
||||
const SELECTION_CHAR: char = '!';
|
||||
|
||||
pub struct NuMenuCompleter {
|
||||
block_id: usize,
|
||||
span: Span,
|
||||
stack: Stack,
|
||||
engine_state: Arc<EngineState>,
|
||||
only_buffer_difference: bool,
|
||||
}
|
||||
|
||||
impl NuMenuCompleter {
|
||||
pub fn new(
|
||||
block_id: usize,
|
||||
span: Span,
|
||||
stack: Stack,
|
||||
engine_state: Arc<EngineState>,
|
||||
only_buffer_difference: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
block_id,
|
||||
span,
|
||||
stack,
|
||||
engine_state,
|
||||
only_buffer_difference,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for NuMenuCompleter {
|
||||
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
let parsed = parse_selection_char(line, SELECTION_CHAR);
|
||||
|
||||
let block = self.engine_state.get_block(self.block_id);
|
||||
|
||||
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,
|
||||
};
|
||||
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,
|
||||
};
|
||||
self.stack.add_var(*position_id, line_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
let input = Value::nothing(self.span).into_pipeline_data();
|
||||
let res = eval_block(
|
||||
&self.engine_state,
|
||||
&mut self.stack,
|
||||
block,
|
||||
input,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
if let Ok(values) = res {
|
||||
let values = values.into_value(self.span);
|
||||
convert_to_suggestions(values, line, pos, self.only_buffer_difference)
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_to_suggestions(
|
||||
value: Value,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
only_buffer_difference: bool,
|
||||
) -> Vec<Suggestion> {
|
||||
match value {
|
||||
Value::Record { .. } => {
|
||||
let text = match value
|
||||
.get_data_by_key("value")
|
||||
.and_then(|val| val.as_string().ok())
|
||||
{
|
||||
Some(val) => val,
|
||||
None => "No value key".to_string(),
|
||||
};
|
||||
|
||||
let description = value
|
||||
.get_data_by_key("description")
|
||||
.and_then(|val| val.as_string().ok());
|
||||
|
||||
let span = match value.get_data_by_key("span") {
|
||||
Some(span @ Value::Record { .. }) => {
|
||||
let start = span
|
||||
.get_data_by_key("start")
|
||||
.and_then(|val| val.as_integer().ok());
|
||||
let end = span
|
||||
.get_data_by_key("end")
|
||||
.and_then(|val| val.as_integer().ok());
|
||||
match (start, end) {
|
||||
(Some(start), Some(end)) => {
|
||||
let start = start.min(end);
|
||||
reedline::Span {
|
||||
start: start as usize,
|
||||
end: end as usize,
|
||||
}
|
||||
}
|
||||
_ => reedline::Span {
|
||||
start: if only_buffer_difference { pos } else { 0 },
|
||||
end: if only_buffer_difference {
|
||||
pos + line.len()
|
||||
} else {
|
||||
line.len()
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => reedline::Span {
|
||||
start: if only_buffer_difference { pos } else { 0 },
|
||||
end: if only_buffer_difference {
|
||||
pos + line.len()
|
||||
} else {
|
||||
line.len()
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let extra = match value.get_data_by_key("extra") {
|
||||
Some(Value::List { vals, .. }) => {
|
||||
let extra: Vec<String> = vals
|
||||
.into_iter()
|
||||
.filter_map(|extra| match extra {
|
||||
Value::String { val, .. } => Some(val),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Some(extra)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
vec![Suggestion {
|
||||
value: text,
|
||||
description,
|
||||
extra,
|
||||
span,
|
||||
append_whitespace: false,
|
||||
}]
|
||||
}
|
||||
Value::List { vals, .. } => vals
|
||||
.into_iter()
|
||||
.flat_map(|val| convert_to_suggestions(val, line, pos, only_buffer_difference))
|
||||
.collect(),
|
||||
_ => vec![Suggestion {
|
||||
value: format!("Not a record: {:?}", value),
|
||||
description: None,
|
||||
extra: None,
|
||||
span: reedline::Span {
|
||||
start: 0,
|
||||
end: line.len(),
|
||||
},
|
||||
append_whitespace: false,
|
||||
}],
|
||||
}
|
||||
}
|
7
crates/nu-cli/src/menus/mod.rs
Normal file
7
crates/nu-cli/src/menus/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
mod description_menu;
|
||||
mod help_completions;
|
||||
mod menu_completions;
|
||||
|
||||
pub use description_menu::DescriptionMenu;
|
||||
pub use help_completions::NuHelpCompleter;
|
||||
pub use menu_completions::NuMenuCompleter;
|
67
crates/nu-cli/src/nu_highlight.rs
Normal file
67
crates/nu-cli/src/nu_highlight.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Value};
|
||||
use reedline::Highlighter;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NuHighlight;
|
||||
|
||||
impl Command for NuHighlight {
|
||||
fn name(&self) -> &str {
|
||||
"nu-highlight"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("nu-highlight").category(Category::Strings)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Syntax highlight the input string."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["syntax", "color", "convert"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let engine_state = engine_state.clone();
|
||||
let config = engine_state.get_config().clone();
|
||||
|
||||
let highlighter = crate::NuHighlighter {
|
||||
engine_state,
|
||||
config,
|
||||
};
|
||||
|
||||
input.map(
|
||||
move |x| match x.as_string() {
|
||||
Ok(line) => {
|
||||
let highlights = highlighter.highlight(&line, line.len());
|
||||
|
||||
Value::String {
|
||||
val: highlights.render_simple(),
|
||||
span: head,
|
||||
}
|
||||
}
|
||||
Err(err) => Value::Error { error: err },
|
||||
},
|
||||
ctrlc,
|
||||
)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Describe the type of a string",
|
||||
example: "'let x = 3' | nu-highlight",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
77
crates/nu-cli/src/print.rs
Normal file
77
crates/nu-cli/src/print.rs
Normal file
@ -0,0 +1,77 @@
|
||||
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,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Print;
|
||||
|
||||
impl Command for Print {
|
||||
fn name(&self) -> &str {
|
||||
"print"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("print")
|
||||
.rest("rest", SyntaxShape::Any, "the values to print")
|
||||
.switch(
|
||||
"no-newline",
|
||||
"print without inserting a newline for the line ending",
|
||||
Some('n'),
|
||||
)
|
||||
.switch("stderr", "print to stderr instead of stdout", Some('e'))
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Print the given values to stdout"
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"Unlike `echo`, this command does not return any value (`print | describe` will return "nothing").
|
||||
Since this command has no output, there is no point in piping it with other commands.
|
||||
|
||||
`print` may be used inside blocks of code (e.g.: hooks) to display text during execution without interfering with the pipeline."#
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["display"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_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)?;
|
||||
}
|
||||
|
||||
Ok(PipelineData::new(head))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Print 'hello world'",
|
||||
example: r#"print "hello world""#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Print the sum of 2 and 3",
|
||||
example: r#"print (2 + 3)"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
159
crates/nu-cli/src/prompt.rs
Normal file
159
crates/nu-cli/src/prompt.rs
Normal file
@ -0,0 +1,159 @@
|
||||
use reedline::DefaultPrompt;
|
||||
|
||||
use {
|
||||
reedline::{
|
||||
Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode,
|
||||
},
|
||||
std::borrow::Cow,
|
||||
};
|
||||
|
||||
/// Nushell prompt definition
|
||||
#[derive(Clone)]
|
||||
pub struct NushellPrompt {
|
||||
left_prompt_string: Option<String>,
|
||||
right_prompt_string: Option<String>,
|
||||
default_prompt_indicator: Option<String>,
|
||||
default_vi_insert_prompt_indicator: Option<String>,
|
||||
default_vi_normal_prompt_indicator: Option<String>,
|
||||
default_multiline_indicator: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for NushellPrompt {
|
||||
fn default() -> Self {
|
||||
NushellPrompt::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl NushellPrompt {
|
||||
pub fn new() -> NushellPrompt {
|
||||
NushellPrompt {
|
||||
left_prompt_string: None,
|
||||
right_prompt_string: None,
|
||||
default_prompt_indicator: None,
|
||||
default_vi_insert_prompt_indicator: None,
|
||||
default_vi_normal_prompt_indicator: None,
|
||||
default_multiline_indicator: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_prompt_left(&mut self, prompt_string: Option<String>) {
|
||||
self.left_prompt_string = prompt_string;
|
||||
}
|
||||
|
||||
pub fn update_prompt_right(&mut self, prompt_string: Option<String>) {
|
||||
self.right_prompt_string = prompt_string;
|
||||
}
|
||||
|
||||
pub fn update_prompt_indicator(&mut self, prompt_indicator_string: Option<String>) {
|
||||
self.default_prompt_indicator = prompt_indicator_string;
|
||||
}
|
||||
|
||||
pub fn update_prompt_vi_insert(&mut self, prompt_vi_insert_string: Option<String>) {
|
||||
self.default_vi_insert_prompt_indicator = prompt_vi_insert_string;
|
||||
}
|
||||
|
||||
pub fn update_prompt_vi_normal(&mut self, prompt_vi_normal_string: Option<String>) {
|
||||
self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
|
||||
}
|
||||
|
||||
pub fn update_prompt_multiline(&mut self, prompt_multiline_indicator_string: Option<String>) {
|
||||
self.default_multiline_indicator = prompt_multiline_indicator_string;
|
||||
}
|
||||
|
||||
pub fn update_all_prompt_strings(
|
||||
&mut self,
|
||||
left_prompt_string: Option<String>,
|
||||
right_prompt_string: Option<String>,
|
||||
prompt_indicator_string: Option<String>,
|
||||
prompt_multiline_indicator_string: Option<String>,
|
||||
prompt_vi: (Option<String>, Option<String>),
|
||||
) {
|
||||
let (prompt_vi_insert_string, prompt_vi_normal_string) = prompt_vi;
|
||||
|
||||
self.left_prompt_string = left_prompt_string;
|
||||
self.right_prompt_string = right_prompt_string;
|
||||
self.default_prompt_indicator = prompt_indicator_string;
|
||||
self.default_multiline_indicator = prompt_multiline_indicator_string;
|
||||
|
||||
self.default_vi_insert_prompt_indicator = prompt_vi_insert_string;
|
||||
self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
|
||||
}
|
||||
|
||||
fn default_wrapped_custom_string(&self, str: String) -> String {
|
||||
format!("({})", str)
|
||||
}
|
||||
}
|
||||
|
||||
impl Prompt for NushellPrompt {
|
||||
fn render_prompt_left(&self) -> Cow<str> {
|
||||
if let Some(prompt_string) = &self.left_prompt_string {
|
||||
prompt_string.replace('\n', "\r\n").into()
|
||||
} else {
|
||||
let default = DefaultPrompt::new();
|
||||
default
|
||||
.render_prompt_left()
|
||||
.to_string()
|
||||
.replace('\n', "\r\n")
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn render_prompt_right(&self) -> Cow<str> {
|
||||
if let Some(prompt_string) = &self.right_prompt_string {
|
||||
prompt_string.replace('\n', "\r\n").into()
|
||||
} else {
|
||||
let default = DefaultPrompt::new();
|
||||
default
|
||||
.render_prompt_right()
|
||||
.to_string()
|
||||
.replace('\n', "\r\n")
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
},
|
||||
PromptEditMode::Emacs => match &self.default_prompt_indicator {
|
||||
Some(indicator) => indicator.as_str().into(),
|
||||
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(),
|
||||
},
|
||||
PromptViMode::Insert => match &self.default_vi_insert_prompt_indicator {
|
||||
Some(indicator) => indicator.as_str().into(),
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_prompt_history_search_indicator(
|
||||
&self,
|
||||
history_search: PromptHistorySearch,
|
||||
) -> Cow<str> {
|
||||
let prefix = match history_search.status {
|
||||
PromptHistorySearchStatus::Passing => "",
|
||||
PromptHistorySearchStatus::Failing => "failing ",
|
||||
};
|
||||
|
||||
Cow::Owned(format!(
|
||||
"({}reverse-search: {})",
|
||||
prefix, history_search.term
|
||||
))
|
||||
}
|
||||
}
|
156
crates/nu-cli/src/prompt_update.rs
Normal file
156
crates/nu-cli/src/prompt_update.rs
Normal file
@ -0,0 +1,156 @@
|
||||
use crate::util::report_error;
|
||||
use crate::NushellPrompt;
|
||||
use log::info;
|
||||
use nu_engine::eval_subexpression;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Config, PipelineData, Span, Value,
|
||||
};
|
||||
use reedline::Prompt;
|
||||
|
||||
// Name of environment variable where the prompt could be stored
|
||||
pub(crate) const PROMPT_COMMAND: &str = "PROMPT_COMMAND";
|
||||
pub(crate) const PROMPT_COMMAND_RIGHT: &str = "PROMPT_COMMAND_RIGHT";
|
||||
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";
|
||||
|
||||
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 {
|
||||
val: block_id,
|
||||
captures,
|
||||
..
|
||||
} => {
|
||||
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
|
||||
);
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"get_prompt_string (block) {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
}
|
||||
|
||||
match ret_val {
|
||||
Ok(ret_val) => Some(ret_val),
|
||||
Err(err) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::String { .. } => Some(PipelineData::Value(v.clone(), None)),
|
||||
_ => None,
|
||||
})
|
||||
.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)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn update_prompt<'prompt>(
|
||||
config: &Config,
|
||||
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 right_prompt_string = get_prompt_string(
|
||||
PROMPT_COMMAND_RIGHT,
|
||||
config,
|
||||
engine_state,
|
||||
&mut stack,
|
||||
is_perf_true,
|
||||
);
|
||||
|
||||
let prompt_indicator_string = get_prompt_string(
|
||||
PROMPT_INDICATOR,
|
||||
config,
|
||||
engine_state,
|
||||
&mut stack,
|
||||
is_perf_true,
|
||||
);
|
||||
|
||||
let prompt_multiline_string = get_prompt_string(
|
||||
PROMPT_MULTILINE_INDICATOR,
|
||||
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,
|
||||
is_perf_true,
|
||||
);
|
||||
|
||||
let prompt_vi_normal_string = get_prompt_string(
|
||||
PROMPT_INDICATOR_VI_NORMAL,
|
||||
config,
|
||||
engine_state,
|
||||
&mut stack,
|
||||
is_perf_true,
|
||||
);
|
||||
|
||||
// apply the other indicators
|
||||
nu_prompt.update_all_prompt_strings(
|
||||
left_prompt_string,
|
||||
right_prompt_string,
|
||||
prompt_indicator_string,
|
||||
prompt_multiline_string,
|
||||
(prompt_vi_insert_string, prompt_vi_normal_string),
|
||||
);
|
||||
|
||||
let ret_val = nu_prompt as &dyn Prompt;
|
||||
if is_perf_true {
|
||||
info!("update_prompt {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
ret_val
|
||||
}
|
1196
crates/nu-cli/src/reedline_config.rs
Normal file
1196
crates/nu-cli/src/reedline_config.rs
Normal file
File diff suppressed because it is too large
Load Diff
576
crates/nu-cli/src/repl.rs
Normal file
576
crates/nu-cli/src/repl.rs
Normal file
@ -0,0 +1,576 @@
|
||||
use crate::{
|
||||
completions::NuCompleter,
|
||||
prompt_update,
|
||||
reedline_config::{add_menus, create_keybindings, KeybindingsMode},
|
||||
util::{eval_source, report_error},
|
||||
NuHighlighter, NuValidator, NushellPrompt,
|
||||
};
|
||||
use log::{info, trace};
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use nu_color_config::get_color_config;
|
||||
use nu_engine::{convert_env_values, eval_block};
|
||||
use nu_parser::lex;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span, Value,
|
||||
};
|
||||
use reedline::{DefaultHinter, Emacs, SqliteBackedHistory, Vi};
|
||||
use std::io::{self, Write};
|
||||
use std::{sync::atomic::Ordering, time::Instant};
|
||||
use sysinfo::SystemExt;
|
||||
|
||||
const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
|
||||
const PRE_EXECUTE_MARKER: &str = "\x1b]133;C\x1b\\";
|
||||
const CMD_FINISHED_MARKER: &str = "\x1b]133;D\x1b\\";
|
||||
const RESET_APPLICATION_MODE: &str = "\x1b[?1l";
|
||||
|
||||
pub fn evaluate_repl(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
nushell_path: &str,
|
||||
is_perf_true: bool,
|
||||
) -> Result<()> {
|
||||
use reedline::{FileBackedHistory, Reedline, Signal};
|
||||
|
||||
let mut entry_num = 0;
|
||||
|
||||
let mut nu_prompt = NushellPrompt::new();
|
||||
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"translate environment vars {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// seed env vars
|
||||
stack.add_env_var(
|
||||
"CMD_DURATION_MS".into(),
|
||||
Value::String {
|
||||
val: "0823".to_string(),
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
);
|
||||
|
||||
stack.add_env_var(
|
||||
"LAST_EXIT_CODE".into(),
|
||||
Value::Int {
|
||||
val: 0,
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
);
|
||||
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"load config initially {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
}
|
||||
|
||||
// Get the config once for the history `max_history_size`
|
||||
// Updating that will not be possible in one session
|
||||
let mut config = engine_state.get_config();
|
||||
|
||||
if is_perf_true {
|
||||
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
let mut line_editor = Reedline::create();
|
||||
let history_path = crate::config_files::get_history_path(
|
||||
nushell_path,
|
||||
engine_state.config.history_file_format,
|
||||
);
|
||||
if let Some(history_path) = history_path.as_deref() {
|
||||
if is_perf_true {
|
||||
info!("setup history {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
let history: Box<dyn reedline::History> = match engine_state.config.history_file_format {
|
||||
HistoryFileFormat::PlainText => Box::new(
|
||||
FileBackedHistory::with_file(
|
||||
config.max_history_size as usize,
|
||||
history_path.to_path_buf(),
|
||||
)
|
||||
.into_diagnostic()?,
|
||||
),
|
||||
HistoryFileFormat::Sqlite => Box::new(
|
||||
SqliteBackedHistory::with_file(history_path.to_path_buf()).into_diagnostic()?,
|
||||
),
|
||||
};
|
||||
line_editor = line_editor.with_history(history);
|
||||
};
|
||||
|
||||
let sys = sysinfo::System::new();
|
||||
|
||||
loop {
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"load config each loop {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
}
|
||||
|
||||
//Reset the ctrl-c handler
|
||||
if let Some(ctrlc) = &mut engine_state.ctrlc {
|
||||
ctrlc.store(false, Ordering::SeqCst);
|
||||
}
|
||||
// Reset the SIGQUIT handler
|
||||
if let Some(sig_quit) = engine_state.get_sig_quit() {
|
||||
sig_quit.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
config = engine_state.get_config();
|
||||
|
||||
if is_perf_true {
|
||||
info!("setup colors {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
let color_hm = get_color_config(config);
|
||||
|
||||
if is_perf_true {
|
||||
info!("update reedline {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
let engine_reference = std::sync::Arc::new(engine_state.clone());
|
||||
line_editor = line_editor
|
||||
.with_highlighter(Box::new(NuHighlighter {
|
||||
engine_state: engine_state.clone(),
|
||||
config: config.clone(),
|
||||
}))
|
||||
.with_animation(config.animate_prompt)
|
||||
.with_validator(Box::new(NuValidator {
|
||||
engine_state: engine_state.clone(),
|
||||
}))
|
||||
.with_completer(Box::new(NuCompleter::new(
|
||||
engine_reference.clone(),
|
||||
stack.clone(),
|
||||
)))
|
||||
.with_quick_completions(config.quick_completions)
|
||||
.with_partial_completions(config.partial_completions)
|
||||
.with_ansi_colors(config.use_ansi_coloring);
|
||||
|
||||
line_editor = if config.use_ansi_coloring {
|
||||
line_editor.with_hinter(Box::new(
|
||||
DefaultHinter::default().with_style(color_hm["hints"]),
|
||||
))
|
||||
} else {
|
||||
line_editor.disable_hints()
|
||||
};
|
||||
|
||||
line_editor = match add_menus(line_editor, engine_reference, stack, config) {
|
||||
Ok(line_editor) => line_editor,
|
||||
Err(e) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
Reedline::create()
|
||||
}
|
||||
};
|
||||
|
||||
let buffer_editor = if !config.buffer_editor.is_empty() {
|
||||
Some(config.buffer_editor.clone())
|
||||
} else {
|
||||
stack
|
||||
.get_env_var(engine_state, "EDITOR")
|
||||
.map(|v| v.as_string().unwrap_or_default())
|
||||
.filter(|v| !v.is_empty())
|
||||
.or_else(|| {
|
||||
stack
|
||||
.get_env_var(engine_state, "VISUAL")
|
||||
.map(|v| v.as_string().unwrap_or_default())
|
||||
.filter(|v| !v.is_empty())
|
||||
})
|
||||
};
|
||||
|
||||
line_editor = if let Some(buffer_editor) = buffer_editor {
|
||||
line_editor.with_buffer_editor(buffer_editor, "nu".into())
|
||||
} else {
|
||||
line_editor
|
||||
};
|
||||
|
||||
if config.sync_history_on_enter {
|
||||
if is_perf_true {
|
||||
info!("sync history {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
line_editor.sync_history().into_diagnostic()?;
|
||||
}
|
||||
|
||||
if is_perf_true {
|
||||
info!("setup keybindings {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
// Changing the line editor based on the found keybindings
|
||||
line_editor = match create_keybindings(config) {
|
||||
Ok(keybindings) => match keybindings {
|
||||
KeybindingsMode::Emacs(keybindings) => {
|
||||
let edit_mode = Box::new(Emacs::new(keybindings));
|
||||
line_editor.with_edit_mode(edit_mode)
|
||||
}
|
||||
KeybindingsMode::Vi {
|
||||
insert_keybindings,
|
||||
normal_keybindings,
|
||||
} => {
|
||||
let edit_mode = Box::new(Vi::new(insert_keybindings, normal_keybindings));
|
||||
line_editor.with_edit_mode(edit_mode)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
line_editor
|
||||
}
|
||||
};
|
||||
|
||||
if is_perf_true {
|
||||
info!("prompt_update {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
// Right before we start our prompt and take input from the user,
|
||||
// fire the "pre_prompt" hook
|
||||
if let Some(hook) = &config.hooks.pre_prompt {
|
||||
if let Err(err) = run_hook(engine_state, stack, vec![], hook) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
}
|
||||
}
|
||||
|
||||
// Next, check all the environment variables they ask for
|
||||
// fire the "env_change" hook
|
||||
if let Some(hook) = config.hooks.env_change.clone() {
|
||||
match hook {
|
||||
Value::Record {
|
||||
cols, vals: blocks, ..
|
||||
} => {
|
||||
for (idx, env_var) in cols.iter().enumerate() {
|
||||
let before = engine_state
|
||||
.previous_env_vars
|
||||
.get(env_var)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let after = stack.get_env_var(engine_state, env_var).unwrap_or_default();
|
||||
if before != after {
|
||||
if let Err(err) = run_hook(
|
||||
engine_state,
|
||||
stack,
|
||||
vec![before, after.clone()],
|
||||
&blocks[idx],
|
||||
) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
}
|
||||
|
||||
engine_state
|
||||
.previous_env_vars
|
||||
.insert(env_var.to_string(), after);
|
||||
}
|
||||
}
|
||||
}
|
||||
x => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::TypeMismatch(
|
||||
"record for 'env_change' hook".to_string(),
|
||||
x.span().unwrap_or_else(|_| Span::new(0, 0)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config = engine_state.get_config();
|
||||
|
||||
let shell_integration = config.shell_integration;
|
||||
if shell_integration {
|
||||
run_ansi_sequence(PRE_PROMPT_MARKER)?;
|
||||
}
|
||||
|
||||
let prompt =
|
||||
prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt, is_perf_true);
|
||||
|
||||
entry_num += 1;
|
||||
|
||||
if is_perf_true {
|
||||
info!(
|
||||
"finished setup, starting repl {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
}
|
||||
|
||||
let input = line_editor.read_line(prompt);
|
||||
|
||||
match input {
|
||||
Ok(Signal::Success(s)) => {
|
||||
let history_supports_meta =
|
||||
matches!(config.history_file_format, HistoryFileFormat::Sqlite);
|
||||
if history_supports_meta && !s.is_empty() {
|
||||
line_editor
|
||||
.update_last_command_context(&|mut c| {
|
||||
c.start_timestamp = Some(chrono::Utc::now());
|
||||
c.hostname = sys.host_name();
|
||||
|
||||
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
|
||||
c
|
||||
})
|
||||
.into_diagnostic()?; // todo: don't stop repl if error here?
|
||||
}
|
||||
|
||||
// Right before we start running the code the user gave us,
|
||||
// fire the "pre_execution" hook
|
||||
if let Some(hook) = &config.hooks.pre_execution {
|
||||
if let Err(err) = run_hook(engine_state, stack, vec![], hook) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
}
|
||||
}
|
||||
|
||||
if shell_integration {
|
||||
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
||||
run_ansi_sequence(PRE_EXECUTE_MARKER)?;
|
||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
let path = cwd.as_string()?;
|
||||
// Try to abbreviate string for windows title
|
||||
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
||||
path.replace(&p.as_path().display().to_string(), "~")
|
||||
} else {
|
||||
path
|
||||
};
|
||||
|
||||
// Set window title too
|
||||
// https://tldp.org/HOWTO/Xterm-Title-3.html
|
||||
// ESC]0;stringBEL -- Set icon name and window title to string
|
||||
// ESC]1;stringBEL -- Set icon name to string
|
||||
// ESC]2;stringBEL -- Set window title to string
|
||||
run_ansi_sequence(&format!("\x1b]2;{}\x07", maybe_abbrev_path))?;
|
||||
}
|
||||
}
|
||||
|
||||
let start_time = Instant::now();
|
||||
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
|
||||
// Check if this is a single call to a directory, if so auto-cd
|
||||
let cwd = nu_engine::env::current_dir_str(engine_state, stack)?;
|
||||
let path = nu_path::expand_path_with(&s, &cwd);
|
||||
|
||||
let orig = s.clone();
|
||||
|
||||
if (orig.starts_with('.')
|
||||
|| orig.starts_with('~')
|
||||
|| orig.starts_with('/')
|
||||
|| orig.starts_with('\\'))
|
||||
&& path.is_dir()
|
||||
&& tokens.0.len() == 1
|
||||
{
|
||||
// We have an auto-cd
|
||||
let (path, span) = {
|
||||
if !path.exists() {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::DirectoryNotFound(tokens.0[0].span, None),
|
||||
);
|
||||
}
|
||||
let path = nu_path::canonicalize_with(path, &cwd)
|
||||
.expect("internal error: cannot canonicalize known path");
|
||||
(path.to_string_lossy().to_string(), tokens.0[0].span)
|
||||
};
|
||||
|
||||
//FIXME: this only changes the current scope, but instead this environment variable
|
||||
//should probably be a block that loads the information from the state in the overlay
|
||||
stack.add_env_var(
|
||||
"PWD".into(),
|
||||
Value::String {
|
||||
val: path.clone(),
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
);
|
||||
let cwd = Value::String { val: cwd, span };
|
||||
|
||||
let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS");
|
||||
let mut shells = if let Some(v) = shells {
|
||||
v.as_list()
|
||||
.map(|x| x.to_vec())
|
||||
.unwrap_or_else(|_| vec![cwd])
|
||||
} else {
|
||||
vec![cwd]
|
||||
};
|
||||
|
||||
let current_shell = stack.get_env_var(engine_state, "NUSHELL_CURRENT_SHELL");
|
||||
let current_shell = if let Some(v) = current_shell {
|
||||
v.as_integer().unwrap_or_default() as usize
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
shells[current_shell] = Value::String { val: path, span };
|
||||
|
||||
stack.add_env_var("NUSHELL_SHELLS".into(), Value::List { vals: shells, span });
|
||||
} else {
|
||||
trace!("eval source: {}", s);
|
||||
|
||||
eval_source(
|
||||
engine_state,
|
||||
stack,
|
||||
s.as_bytes(),
|
||||
&format!("entry #{}", entry_num),
|
||||
PipelineData::new(Span::new(0, 0)),
|
||||
);
|
||||
}
|
||||
let cmd_duration = start_time.elapsed();
|
||||
|
||||
stack.add_env_var(
|
||||
"CMD_DURATION_MS".into(),
|
||||
Value::String {
|
||||
val: format!("{}", cmd_duration.as_millis()),
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
);
|
||||
|
||||
// FIXME: permanent state changes like this hopefully in time can be removed
|
||||
// and be replaced by just passing the cwd in where needed
|
||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
let path = cwd.as_string()?;
|
||||
let _ = std::env::set_current_dir(path);
|
||||
engine_state.add_env_var("PWD".into(), cwd);
|
||||
}
|
||||
|
||||
if history_supports_meta && !s.is_empty() {
|
||||
line_editor
|
||||
.update_last_command_context(&|mut c| {
|
||||
c.duration = Some(cmd_duration);
|
||||
c.exit_status = stack
|
||||
.get_env_var(engine_state, "LAST_EXIT_CODE")
|
||||
.and_then(|e| e.as_i64().ok());
|
||||
c
|
||||
})
|
||||
.into_diagnostic()?; // todo: don't stop repl if error here?
|
||||
}
|
||||
|
||||
if shell_integration {
|
||||
// FIXME: use variant with exit code, if apropriate
|
||||
run_ansi_sequence(CMD_FINISHED_MARKER)?;
|
||||
}
|
||||
}
|
||||
Ok(Signal::CtrlC) => {
|
||||
// `Reedline` clears the line content. New prompt is shown
|
||||
if shell_integration {
|
||||
run_ansi_sequence(CMD_FINISHED_MARKER)?;
|
||||
}
|
||||
}
|
||||
Ok(Signal::CtrlD) => {
|
||||
// When exiting clear to a new line
|
||||
if shell_integration {
|
||||
run_ansi_sequence(CMD_FINISHED_MARKER)?;
|
||||
}
|
||||
println!();
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
let message = err.to_string();
|
||||
if !message.contains("duration") {
|
||||
println!("Error: {:?}", err);
|
||||
}
|
||||
if shell_integration {
|
||||
run_ansi_sequence(CMD_FINISHED_MARKER)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
|
||||
match io::stdout().write_all(seq.as_bytes()) {
|
||||
Ok(it) => it,
|
||||
Err(err) => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Error writing ansi sequence".into(),
|
||||
err.to_string(),
|
||||
Some(Span { start: 0, end: 0 }),
|
||||
None,
|
||||
Vec::new(),
|
||||
));
|
||||
}
|
||||
};
|
||||
io::stdout().flush().map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Error flushing stdio".into(),
|
||||
e.to_string(),
|
||||
Some(Span { start: 0, end: 0 }),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run_hook(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
arguments: Vec<Value>,
|
||||
value: &Value,
|
||||
) -> Result<(), ShellError> {
|
||||
match value {
|
||||
Value::List { vals, .. } => {
|
||||
for val in vals {
|
||||
run_hook(engine_state, stack, arguments.clone(), val)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Value::Block {
|
||||
val: block_id,
|
||||
span,
|
||||
..
|
||||
} => run_hook_block(engine_state, stack, *block_id, arguments, *span),
|
||||
x => match x.span() {
|
||||
Ok(span) => Err(ShellError::MissingConfigValue(
|
||||
"block for hook in config".into(),
|
||||
span,
|
||||
)),
|
||||
_ => Err(ShellError::MissingConfigValue(
|
||||
"block for hook in config".into(),
|
||||
Span { start: 0, end: 0 },
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_hook_block(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
block_id: BlockId,
|
||||
arguments: Vec<Value>,
|
||||
span: Span,
|
||||
) -> Result<(), ShellError> {
|
||||
let block = engine_state.get_block(block_id);
|
||||
let input = PipelineData::new(span);
|
||||
|
||||
let mut callee_stack = stack.gather_captures(&block.captures);
|
||||
|
||||
for (idx, PositionalArg { var_id, .. }) in
|
||||
block.signature.required_positional.iter().enumerate()
|
||||
{
|
||||
if let Some(var_id) = var_id {
|
||||
callee_stack.add_var(*var_id, arguments[idx].clone())
|
||||
}
|
||||
}
|
||||
|
||||
match eval_block(engine_state, &mut callee_stack, block, input, false, false) {
|
||||
Ok(pipeline_data) => match pipeline_data.into_value(span) {
|
||||
Value::Error { error } => Err(error),
|
||||
_ => Ok(()),
|
||||
},
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
@ -1,234 +0,0 @@
|
||||
use nu_ansi_term::Color;
|
||||
use nu_completion::NuCompleter;
|
||||
use nu_engine::{DefaultPalette, EvaluationContext, Painter};
|
||||
use nu_source::{Tag, Tagged};
|
||||
use std::borrow::Cow::{self, Owned};
|
||||
|
||||
pub struct Helper {
|
||||
completer: NuCompleter,
|
||||
hinter: Option<rustyline::hint::HistoryHinter>,
|
||||
context: EvaluationContext,
|
||||
pub colored_prompt: String,
|
||||
validator: NuValidator,
|
||||
}
|
||||
|
||||
impl Helper {
|
||||
pub(crate) fn new(
|
||||
context: EvaluationContext,
|
||||
hinter: Option<rustyline::hint::HistoryHinter>,
|
||||
) -> Helper {
|
||||
Helper {
|
||||
completer: NuCompleter {},
|
||||
hinter,
|
||||
context,
|
||||
colored_prompt: String::new(),
|
||||
validator: NuValidator {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use nu_protocol::{SignatureRegistry, VariableRegistry};
|
||||
struct CompletionContext<'a>(&'a EvaluationContext);
|
||||
|
||||
impl<'a> nu_completion::CompletionContext for CompletionContext<'a> {
|
||||
fn signature_registry(&self) -> &dyn SignatureRegistry {
|
||||
&self.0.scope
|
||||
}
|
||||
|
||||
fn scope(&self) -> &dyn nu_parser::ParserScope {
|
||||
&self.0.scope
|
||||
}
|
||||
|
||||
fn source(&self) -> &EvaluationContext {
|
||||
self.as_ref()
|
||||
}
|
||||
|
||||
fn variable_registry(&self) -> &dyn VariableRegistry {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsRef<EvaluationContext> for CompletionContext<'a> {
|
||||
fn as_ref(&self) -> &EvaluationContext {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CompletionSuggestion(nu_completion::Suggestion);
|
||||
|
||||
impl rustyline::completion::Candidate for CompletionSuggestion {
|
||||
fn display(&self) -> &str {
|
||||
&self.0.display
|
||||
}
|
||||
|
||||
fn replacement(&self) -> &str {
|
||||
&self.0.replacement
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::completion::Completer for Helper {
|
||||
type Candidate = CompletionSuggestion;
|
||||
|
||||
fn complete(
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
_ctx: &rustyline::Context<'_>,
|
||||
) -> Result<(usize, Vec<Self::Candidate>), rustyline::error::ReadlineError> {
|
||||
let ctx = CompletionContext(&self.context);
|
||||
let (position, suggestions) = self.completer.complete(line, pos, &ctx);
|
||||
let suggestions = suggestions.into_iter().map(CompletionSuggestion).collect();
|
||||
Ok((position, suggestions))
|
||||
}
|
||||
|
||||
fn update(&self, line: &mut rustyline::line_buffer::LineBuffer, start: usize, elected: &str) {
|
||||
let end = line.pos();
|
||||
line.replace(start..end, elected)
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::hint::Hinter for Helper {
|
||||
type Hint = String;
|
||||
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
|
||||
match &self.hinter {
|
||||
Some(the_hinter) => the_hinter.hint(line, pos, ctx),
|
||||
None => Some("".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::highlight::Highlighter for Helper {
|
||||
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
|
||||
&'s self,
|
||||
prompt: &'p str,
|
||||
default: bool,
|
||||
) -> Cow<'b, str> {
|
||||
use std::borrow::Cow::Borrowed;
|
||||
|
||||
if default {
|
||||
Borrowed(&self.colored_prompt)
|
||||
} else {
|
||||
Borrowed(prompt)
|
||||
}
|
||||
}
|
||||
|
||||
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
|
||||
Owned(Color::DarkGray.prefix().to_string() + hint + nu_ansi_term::ansi::RESET)
|
||||
}
|
||||
|
||||
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
|
||||
let cfg = &self.context.configs().lock();
|
||||
if let Some(palette) = &cfg.syntax_config {
|
||||
Painter::paint_string(line, &self.context.scope, palette)
|
||||
} else {
|
||||
Painter::paint_string(line, &self.context.scope, &DefaultPalette {})
|
||||
}
|
||||
}
|
||||
|
||||
fn highlight_char(&self, _line: &str, _pos: usize) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::validate::Validator for Helper {
|
||||
fn validate(
|
||||
&self,
|
||||
ctx: &mut rustyline::validate::ValidationContext,
|
||||
) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
||||
self.validator.validate(ctx)
|
||||
}
|
||||
|
||||
fn validate_while_typing(&self) -> bool {
|
||||
self.validator.validate_while_typing()
|
||||
}
|
||||
}
|
||||
|
||||
struct NuValidator {}
|
||||
|
||||
impl rustyline::validate::Validator for NuValidator {
|
||||
fn validate(
|
||||
&self,
|
||||
ctx: &mut rustyline::validate::ValidationContext,
|
||||
) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
||||
let src = ctx.input();
|
||||
|
||||
let (tokens, err) = nu_parser::lex(src, 0, nu_parser::NewlineMode::Normal);
|
||||
if let Some(err) = err {
|
||||
if let nu_errors::ParseErrorReason::Eof { .. } = err.reason() {
|
||||
return Ok(rustyline::validate::ValidationResult::Incomplete);
|
||||
}
|
||||
}
|
||||
|
||||
let (_, err) = nu_parser::parse_block(tokens);
|
||||
|
||||
if let Some(err) = err {
|
||||
if let nu_errors::ParseErrorReason::Eof { .. } = err.reason() {
|
||||
return Ok(rustyline::validate::ValidationResult::Incomplete);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(rustyline::validate::ValidationResult::Valid(None))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn vec_tag<T>(input: Vec<Tagged<T>>) -> Option<Tag> {
|
||||
let mut iter = input.iter();
|
||||
let first = iter.next()?.tag.clone();
|
||||
let last = iter.last();
|
||||
|
||||
Some(match last {
|
||||
None => first,
|
||||
Some(last) => first.until(&last.tag),
|
||||
})
|
||||
}
|
||||
|
||||
impl rustyline::Helper for Helper {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nu_engine::EvaluationContext;
|
||||
use rustyline::completion::Completer;
|
||||
use rustyline::line_buffer::LineBuffer;
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn closing_quote_should_replaced() {
|
||||
let text = "cd \"folder with spaces\\subdirectory\\\"";
|
||||
let replacement = "\"folder with spaces\\subdirectory\\subsubdirectory\\\"";
|
||||
|
||||
let mut buffer = LineBuffer::with_capacity(256);
|
||||
buffer.insert_str(0, text);
|
||||
buffer.set_pos(text.len() - 1);
|
||||
|
||||
let helper = Helper::new(EvaluationContext::basic(), None);
|
||||
|
||||
helper.update(&mut buffer, "cd ".len(), replacement);
|
||||
|
||||
assert_eq!(
|
||||
buffer.as_str(),
|
||||
"cd \"folder with spaces\\subdirectory\\subsubdirectory\\\""
|
||||
);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn replacement_with_cursor_in_text() {
|
||||
let text = "cd \"folder with spaces\\subdirectory\\\"";
|
||||
let replacement = "\"folder with spaces\\subdirectory\\subsubdirectory\\\"";
|
||||
|
||||
let mut buffer = LineBuffer::with_capacity(256);
|
||||
buffer.insert_str(0, text);
|
||||
buffer.set_pos(text.len() - 30);
|
||||
|
||||
let helper = Helper::new(EvaluationContext::basic(), None);
|
||||
|
||||
helper.update(&mut buffer, "cd ".len(), replacement);
|
||||
|
||||
assert_eq!(
|
||||
buffer.as_str(),
|
||||
"cd \"folder with spaces\\subdirectory\\subsubdirectory\\\""
|
||||
);
|
||||
}
|
||||
}
|
218
crates/nu-cli/src/syntax_highlight.rs
Normal file
218
crates/nu-cli/src/syntax_highlight.rs
Normal file
@ -0,0 +1,218 @@
|
||||
use log::trace;
|
||||
use nu_ansi_term::Style;
|
||||
use nu_color_config::get_shape_color;
|
||||
use nu_parser::{flatten_block, parse, FlatShape};
|
||||
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
||||
use nu_protocol::Config;
|
||||
use reedline::{Highlighter, StyledText};
|
||||
|
||||
pub struct NuHighlighter {
|
||||
pub engine_state: EngineState,
|
||||
pub config: Config,
|
||||
}
|
||||
|
||||
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 (block, _) = parse(&mut working_set, None, line.as_bytes(), false, &[]);
|
||||
|
||||
let shapes = flatten_block(&working_set, &block);
|
||||
(shapes, self.engine_state.next_span_start())
|
||||
};
|
||||
|
||||
let mut output = StyledText::default();
|
||||
let mut last_seen_span = global_span_offset;
|
||||
|
||||
for shape in &shapes {
|
||||
if shape.0.end <= last_seen_span
|
||||
|| last_seen_span < global_span_offset
|
||||
|| shape.0.start < global_span_offset
|
||||
{
|
||||
// We've already output something for this span
|
||||
// so just skip this one
|
||||
continue;
|
||||
}
|
||||
if shape.0.start > last_seen_span {
|
||||
let gap = line
|
||||
[(last_seen_span - global_span_offset)..(shape.0.start - global_span_offset)]
|
||||
.to_string();
|
||||
output.push((Style::new(), gap));
|
||||
}
|
||||
let next_token = line
|
||||
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
|
||||
.to_string();
|
||||
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::List => {
|
||||
// nushell ???
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
}
|
||||
FlatShape::Table => {
|
||||
// nushell ???
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
}
|
||||
FlatShape::Record => {
|
||||
// nushell ???
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
next_token,
|
||||
))
|
||||
}
|
||||
FlatShape::Block => {
|
||||
// nushell ???
|
||||
output.push((
|
||||
get_shape_color(shape.1.to_string(), &self.config),
|
||||
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,
|
||||
)),
|
||||
}
|
||||
last_seen_span = shape.0.end;
|
||||
}
|
||||
|
||||
let remainder = line[(last_seen_span - global_span_offset)..].to_string();
|
||||
if !remainder.is_empty() {
|
||||
output.push((Style::new(), remainder));
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
347
crates/nu-cli/src/util.rs
Normal file
347
crates/nu-cli/src/util.rs
Normal file
@ -0,0 +1,347 @@
|
||||
use log::trace;
|
||||
use nu_engine::eval_block;
|
||||
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,
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use nu_utils::enable_vt_processing;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
// This will collect environment variables from std::env and adds them to a stack.
|
||||
//
|
||||
// In order to ensure the values have spans, it first creates a dummy file, writes the collected
|
||||
// env vars into it (in a "NAME"="value" format, quite similar to the output of the Unix 'env'
|
||||
// tool), then uses the file to get the spans. The file stays in memory, no filesystem IO is done.
|
||||
//
|
||||
// The "PWD" env value will be forced to `init_cwd`.
|
||||
// The reason to use `init_cwd`:
|
||||
//
|
||||
// While gathering parent env vars, the parent `PWD` may not be the same as `current working directory`.
|
||||
// Consider to the following command as the case (assume we execute command inside `/tmp`):
|
||||
//
|
||||
// tmux split-window -v -c "#{pane_current_path}"
|
||||
//
|
||||
// Here nu execute external command `tmux`, and tmux starts a new `nushell`, with `init_cwd` value "#{pane_current_path}".
|
||||
// But at the same time `PWD` still remains to be `/tmp`.
|
||||
//
|
||||
// In this scenario, the new `nushell`'s PWD should be "#{pane_current_path}" rather init_cwd.
|
||||
pub fn gather_parent_env_vars(engine_state: &mut EngineState, init_cwd: &Path) {
|
||||
gather_env_vars(std::env::vars(), engine_state, init_cwd);
|
||||
}
|
||||
|
||||
fn gather_env_vars(
|
||||
vars: impl Iterator<Item = (String, String)>,
|
||||
engine_state: &mut EngineState,
|
||||
init_cwd: &Path,
|
||||
) {
|
||||
fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::GenericError(
|
||||
format!("Environment variable was not captured: {}", env_str),
|
||||
"".to_string(),
|
||||
None,
|
||||
Some(msg.into()),
|
||||
Vec::new(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn put_env_to_fake_file(name: &str, val: &str, fake_env_file: &mut String) {
|
||||
fake_env_file.push_str(&escape_quote_string(name));
|
||||
fake_env_file.push('=');
|
||||
fake_env_file.push_str(&escape_quote_string(val));
|
||||
fake_env_file.push('\n');
|
||||
}
|
||||
|
||||
let mut fake_env_file = String::new();
|
||||
// Write all the env vars into a fake file
|
||||
for (name, val) in vars {
|
||||
put_env_to_fake_file(&name, &val, &mut fake_env_file);
|
||||
}
|
||||
|
||||
match init_cwd.to_str() {
|
||||
Some(cwd) => {
|
||||
put_env_to_fake_file("PWD", cwd, &mut fake_env_file);
|
||||
}
|
||||
None => {
|
||||
// Could not capture current working directory
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::GenericError(
|
||||
"Current directory is not a valid utf-8 path".to_string(),
|
||||
"".to_string(),
|
||||
None,
|
||||
Some(format!(
|
||||
"Retrieving current directory failed: {:?} not a valid utf-8 path",
|
||||
init_cwd
|
||||
)),
|
||||
Vec::new(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Lex the fake file, assign spans to all environment variables and add them
|
||||
// to stack
|
||||
let span_offset = engine_state.next_span_start();
|
||||
|
||||
engine_state.add_file(
|
||||
"Host Environment Variables".to_string(),
|
||||
fake_env_file.as_bytes().to_vec(),
|
||||
);
|
||||
|
||||
let (tokens, _) = lex(fake_env_file.as_bytes(), span_offset, &[], &[], true);
|
||||
|
||||
for token in tokens {
|
||||
if let Token {
|
||||
contents: TokenContents::Item,
|
||||
span: full_span,
|
||||
} = token
|
||||
{
|
||||
let contents = engine_state.get_span_contents(&full_span);
|
||||
let (parts, _) = lex(contents, full_span.start, &[], &[b'='], true);
|
||||
|
||||
let name = if let Some(Token {
|
||||
contents: TokenContents::Item,
|
||||
span,
|
||||
}) = parts.get(0)
|
||||
{
|
||||
let bytes = engine_state.get_span_contents(span);
|
||||
|
||||
if bytes.len() < 2 {
|
||||
report_capture_error(
|
||||
engine_state,
|
||||
&String::from_utf8_lossy(contents),
|
||||
"Got empty name.",
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let (bytes, parse_error) = unescape_unquote_string(bytes, *span);
|
||||
|
||||
if parse_error.is_some() {
|
||||
report_capture_error(
|
||||
engine_state,
|
||||
&String::from_utf8_lossy(contents),
|
||||
"Got unparsable name.",
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
bytes
|
||||
} else {
|
||||
report_capture_error(
|
||||
engine_state,
|
||||
&String::from_utf8_lossy(contents),
|
||||
"Got empty name.",
|
||||
);
|
||||
|
||||
continue;
|
||||
};
|
||||
|
||||
let value = if let Some(Token {
|
||||
contents: TokenContents::Item,
|
||||
span,
|
||||
}) = parts.get(2)
|
||||
{
|
||||
let bytes = engine_state.get_span_contents(span);
|
||||
|
||||
if bytes.len() < 2 {
|
||||
report_capture_error(
|
||||
engine_state,
|
||||
&String::from_utf8_lossy(contents),
|
||||
"Got empty value.",
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let (bytes, parse_error) = unescape_unquote_string(bytes, *span);
|
||||
|
||||
if parse_error.is_some() {
|
||||
report_capture_error(
|
||||
engine_state,
|
||||
&String::from_utf8_lossy(contents),
|
||||
"Got unparsable value.",
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
Value::String {
|
||||
val: bytes,
|
||||
span: *span,
|
||||
}
|
||||
} else {
|
||||
report_capture_error(
|
||||
engine_state,
|
||||
&String::from_utf8_lossy(contents),
|
||||
"Got empty value.",
|
||||
);
|
||||
|
||||
continue;
|
||||
};
|
||||
|
||||
// stack.add_env_var(name, value);
|
||||
engine_state.add_env_var(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_source(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
source: &[u8],
|
||||
fname: &str,
|
||||
input: PipelineData,
|
||||
) -> bool {
|
||||
trace!("eval_source");
|
||||
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
let (output, err) = parse(
|
||||
&mut working_set,
|
||||
Some(fname), // format!("entry #{}", entry_num)
|
||||
source,
|
||||
false,
|
||||
&[],
|
||||
);
|
||||
if let Some(err) = err {
|
||||
set_last_exit_code(stack, 1);
|
||||
report_error(&working_set, &err);
|
||||
return false;
|
||||
}
|
||||
|
||||
(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()
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
set_last_exit_code(stack, 0);
|
||||
}
|
||||
|
||||
if let Err(err) = pipeline_data.print(engine_state, stack, false, false) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &err);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = enable_vt_processing();
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
set_last_exit_code(stack, 1);
|
||||
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &err);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
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 },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn report_error(
|
||||
working_set: &StateWorkingSet,
|
||||
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
|
||||
) {
|
||||
eprintln!("Error: {:?}", CliError(error, working_set));
|
||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = nu_utils::enable_vt_processing();
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_gather_env_vars() {
|
||||
let mut engine_state = EngineState::new();
|
||||
let symbols = r##" !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"##;
|
||||
|
||||
gather_env_vars(
|
||||
[
|
||||
("FOO".into(), "foo".into()),
|
||||
("SYMBOLS".into(), symbols.into()),
|
||||
(symbols.into(), "symbols".into()),
|
||||
]
|
||||
.into_iter(),
|
||||
&mut engine_state,
|
||||
Path::new("t"),
|
||||
);
|
||||
|
||||
let env = engine_state.render_env_vars();
|
||||
|
||||
assert!(
|
||||
matches!(env.get(&"FOO".to_string()), Some(&Value::String { val, .. }) if val == "foo")
|
||||
);
|
||||
assert!(
|
||||
matches!(env.get(&"SYMBOLS".to_string()), Some(&Value::String { val, .. }) if val == symbols)
|
||||
);
|
||||
assert!(
|
||||
matches!(env.get(&symbols.to_string()), Some(&Value::String { val, .. }) if val == "symbols")
|
||||
);
|
||||
assert!(env.get(&"PWD".to_string()).is_some());
|
||||
assert_eq!(env.len(), 4);
|
||||
}
|
||||
}
|
20
crates/nu-cli/src/validation.rs
Normal file
20
crates/nu-cli/src/validation.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use nu_parser::{parse, ParseError};
|
||||
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
||||
use reedline::{ValidationResult, Validator};
|
||||
|
||||
pub struct NuValidator {
|
||||
pub engine_state: EngineState,
|
||||
}
|
||||
|
||||
impl Validator for NuValidator {
|
||||
fn validate(&self, line: &str) -> ValidationResult {
|
||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||
let (_, err) = parse(&mut working_set, None, line.as_bytes(), false, &[]);
|
||||
|
||||
if matches!(err, Some(ParseError::UnexpectedEof(..))) {
|
||||
ValidationResult::Incomplete
|
||||
} else {
|
||||
ValidationResult::Complete
|
||||
}
|
||||
}
|
||||
}
|
65
crates/nu-cli/tests/alias.rs
Normal file
65
crates/nu-cli/tests/alias.rs
Normal file
@ -0,0 +1,65 @@
|
||||
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)
|
||||
}
|
29
crates/nu-cli/tests/custom_completions.rs
Normal file
29
crates/nu-cli/tests/custom_completions.rs
Normal file
@ -0,0 +1,29 @@
|
||||
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);
|
||||
}
|
28
crates/nu-cli/tests/dotnu_completions.rs
Normal file
28
crates/nu-cli/tests/dotnu_completions.rs
Normal file
@ -0,0 +1,28 @@
|
||||
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);
|
||||
}
|
272
crates/nu-cli/tests/file_completions.rs
Normal file
272
crates/nu-cli/tests/file_completions.rs
Normal file
@ -0,0 +1,272 @@
|
||||
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)
|
||||
}
|
36
crates/nu-cli/tests/flag_completions.rs
Normal file
36
crates/nu-cli/tests/flag_completions.rs
Normal file
@ -0,0 +1,36 @@
|
||||
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);
|
||||
}
|
29
crates/nu-cli/tests/folder_completions.rs
Normal file
29
crates/nu-cli/tests/folder_completions.rs
Normal file
@ -0,0 +1,29 @@
|
||||
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);
|
||||
}
|
117
crates/nu-cli/tests/support/completions_helpers.rs
Normal file
117
crates/nu-cli/tests/support/completions_helpers.rs
Normal file
@ -0,0 +1,117 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use nu_command::create_default_context;
|
||||
use nu_engine::eval_block;
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateDelta, StateWorkingSet},
|
||||
PipelineData, ShellError, Span, Value,
|
||||
};
|
||||
use nu_test_support::fs;
|
||||
use reedline::Suggestion;
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
// creates a new engine with the current path into the completions fixtures folder
|
||||
pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||
// Target folder inside assets
|
||||
let dir = fs::fixtures().join("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(&dir);
|
||||
|
||||
// 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(),
|
||||
},
|
||||
},
|
||||
);
|
||||
stack.add_env_var(
|
||||
"TEST".to_string(),
|
||||
Value::String {
|
||||
val: "NUSHELL".to_string(),
|
||||
span: nu_protocol::Span {
|
||||
start: 0,
|
||||
end: dir_str.len(),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Merge delta
|
||||
let merge_result = engine_state.merge_delta(delta, Some(&mut stack), &dir);
|
||||
assert!(merge_result.is_ok());
|
||||
|
||||
(dir, dir_str, engine_state, stack)
|
||||
}
|
||||
|
||||
// match a list of suggestions with the expected values
|
||||
pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
|
||||
expected.iter().zip(suggestions).for_each(|it| {
|
||||
assert_eq!(it.0, &it.1.value);
|
||||
});
|
||||
}
|
||||
|
||||
// append the separator to the converted path
|
||||
pub fn folder(path: PathBuf) -> String {
|
||||
let mut converted_path = file(path);
|
||||
converted_path.push(SEP);
|
||||
|
||||
converted_path
|
||||
}
|
||||
|
||||
// convert a given path to string
|
||||
pub fn file(path: PathBuf) -> String {
|
||||
path.into_os_string().into_string().unwrap_or_default()
|
||||
}
|
||||
|
||||
// merge_input executes the given input into the engine
|
||||
// and merges the state
|
||||
pub fn merge_input(
|
||||
input: &[u8],
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
dir: PathBuf,
|
||||
) -> Result<(), ShellError> {
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
let (block, err) = parse(&mut working_set, None, input, false, &[]);
|
||||
|
||||
assert!(err.is_none());
|
||||
|
||||
(block, working_set.render())
|
||||
};
|
||||
assert!(eval_block(
|
||||
engine_state,
|
||||
stack,
|
||||
&block,
|
||||
PipelineData::Value(
|
||||
Value::Nothing {
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
None
|
||||
),
|
||||
false,
|
||||
false
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
// Merge delta
|
||||
engine_state.merge_delta(delta, Some(stack), &dir)
|
||||
}
|
3
crates/nu-cli/tests/support/mod.rs
Normal file
3
crates/nu-cli/tests/support/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod completions_helpers;
|
||||
|
||||
pub use completions_helpers::{file, folder, match_suggestions, merge_input, new_engine};
|
88
crates/nu-cli/tests/variables_completions.rs
Normal file
88
crates/nu-cli/tests/variables_completions.rs
Normal file
@ -0,0 +1,88 @@
|
||||
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);
|
||||
}
|
14
crates/nu-color-config/Cargo.toml
Normal file
14
crates/nu-color-config/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "Color configuration code used by Nushell"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-color-config"
|
||||
version = "0.65.0"
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.65.0" }
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-json = { path = "../nu-json", version = "0.65.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.65.0" }
|
||||
serde = { version="1.0.123", features=["derive"] }
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user