mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-30 22:57:15 +09:00
Compare commits
470 Commits
refactor2.
...
v2.0.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb258dd57a | ||
|
|
891b117a33 | ||
|
|
f5dc0a51ba | ||
|
|
8cbe7fa92b | ||
|
|
a584ff36de | ||
|
|
f5405cee18 | ||
|
|
3516c8a9a6 | ||
|
|
c19dce87e4 | ||
|
|
2adba18159 | ||
|
|
f9f2ef02ac | ||
|
|
0976eb3e51 | ||
|
|
ac2d1491ff | ||
|
|
5bfc892a74 | ||
|
|
1793b6268b | ||
|
|
9b62aa4170 | ||
|
|
6fef5d6232 | ||
|
|
fe19b13b3b | ||
|
|
6559b116c0 | ||
|
|
ca976a8a3c | ||
|
|
cfc595e80e | ||
|
|
fde4b92b9f | ||
|
|
b8ec7b320a | ||
|
|
1786165d8b | ||
|
|
0322e91933 | ||
|
|
b2261fc225 | ||
|
|
5ce26cca71 | ||
|
|
efb38b8636 | ||
|
|
0654db334a | ||
|
|
660d345880 | ||
|
|
1f58eecf3c | ||
|
|
ae05ff1811 | ||
|
|
43924646f6 | ||
|
|
79ee757757 | ||
|
|
006165230d | ||
|
|
ead07e0b60 | ||
|
|
140662f1ec | ||
|
|
44c1929f9d | ||
|
|
397fe634d7 | ||
|
|
2e3d08580e | ||
|
|
466889f540 | ||
|
|
63900cb395 | ||
|
|
07860b8973 | ||
|
|
b473fe458d | ||
|
|
8cf56bfc56 | ||
|
|
51050811eb | ||
|
|
14cd3cdbf8 | ||
|
|
51ab8f9914 | ||
|
|
afeb07a024 | ||
|
|
3fc9a8ad9e | ||
|
|
b05d3a5193 | ||
|
|
ffc922a7c5 | ||
|
|
eeab114ed5 | ||
|
|
8bd7e5807c | ||
|
|
9b59e07b47 | ||
|
|
00edf0207f | ||
|
|
a95b17a4e7 | ||
|
|
8956448fca | ||
|
|
a915cf9283 | ||
|
|
dd10869eca | ||
|
|
4356c7e434 | ||
|
|
2e770ce9ce | ||
|
|
381e1b639d | ||
|
|
cc09712d14 | ||
|
|
c5b0c2d41f | ||
|
|
bd43a44194 | ||
|
|
bfe68b1626 | ||
|
|
0064b8268f | ||
|
|
9a22d93ea2 | ||
|
|
5c8a2332d9 | ||
|
|
ff0683d6d0 | ||
|
|
79c0ea17ad | ||
|
|
bdff221870 | ||
|
|
65be5efd83 | ||
|
|
a491dd1c52 | ||
|
|
d7ab44253f | ||
|
|
0a6720498f | ||
|
|
a150eef6f9 | ||
|
|
c46257222c | ||
|
|
299af4a3db | ||
|
|
d0f7ecf9ca | ||
|
|
fb35e0312a | ||
|
|
30395b1f67 | ||
|
|
ddf70953fe | ||
|
|
55e97596d3 | ||
|
|
66dc48ce9b | ||
|
|
c490a94700 | ||
|
|
eff89a98a7 | ||
|
|
221d8f462a | ||
|
|
7a23878250 | ||
|
|
5d3e4fc3d9 | ||
|
|
f52fbfa1f0 | ||
|
|
d60626c64b | ||
|
|
aaac0b1e6f | ||
|
|
0f984131fb | ||
|
|
eb7189dcdb | ||
|
|
74523d28c5 | ||
|
|
f894f0a26e | ||
|
|
a067ce1f41 | ||
|
|
f59468642d | ||
|
|
85e85b7ccc | ||
|
|
8f5888e7bf | ||
|
|
f0da73bae2 | ||
|
|
d92deacf99 | ||
|
|
ffd7b5c770 | ||
|
|
bd8c0d25c8 | ||
|
|
052a36b896 | ||
|
|
a76bf02f5f | ||
|
|
92f4cb7ef7 | ||
|
|
1cf9537340 | ||
|
|
60c8c81da3 | ||
|
|
c76a973877 | ||
|
|
6def99ce24 | ||
|
|
26930ca81f | ||
|
|
cd379cd838 | ||
|
|
ee157f6503 | ||
|
|
48ca19873f | ||
|
|
fee5528309 | ||
|
|
671a188802 | ||
|
|
18d540583b | ||
|
|
943ea15fa3 | ||
|
|
2ef57977d7 | ||
|
|
527750b68d | ||
|
|
629efe5eb7 | ||
|
|
a19cd2e6d0 | ||
|
|
d038d3040f | ||
|
|
9e8d76f2fa | ||
|
|
8a9a14562f | ||
|
|
a6f5dee45c | ||
|
|
b12886b066 | ||
|
|
56f5b475eb | ||
|
|
c51f84955e | ||
|
|
e4bf1e9984 | ||
|
|
917e2922a1 | ||
|
|
59e8f3aa3e | ||
|
|
53bda0cfa7 | ||
|
|
f059541e0d | ||
|
|
d78fe81e21 | ||
|
|
25b9342fbe | ||
|
|
70bcf9f618 | ||
|
|
8848388411 | ||
|
|
dff8b33e9c | ||
|
|
8a2048e7f6 | ||
|
|
0174d7dba4 | ||
|
|
e1827480c9 | ||
|
|
d8584d1ddb | ||
|
|
f0cdc3cabb | ||
|
|
2ef4f83358 | ||
|
|
a9120ce270 | ||
|
|
190d9d0609 | ||
|
|
cf3fdb344a | ||
|
|
b91242124c | ||
|
|
5ffc19f159 | ||
|
|
cc994b6241 | ||
|
|
087e7207f7 | ||
|
|
db32b84cd1 | ||
|
|
00006aa2b4 | ||
|
|
600d8558b2 | ||
|
|
4874823240 | ||
|
|
6a0e4b5564 | ||
|
|
b2d7c8c5d4 | ||
|
|
38f88ade60 | ||
|
|
8348cc8ec2 | ||
|
|
743d42e417 | ||
|
|
faa207907c | ||
|
|
9d1c489574 | ||
|
|
30ed25859a | ||
|
|
d2288c5f66 | ||
|
|
a07ee26b05 | ||
|
|
a7ce85d6f6 | ||
|
|
7c71995aaf | ||
|
|
357c4b0fcd | ||
|
|
0d4f85304b | ||
|
|
e18e41eb45 | ||
|
|
5519f053ac | ||
|
|
2f4b3b2a8c | ||
|
|
ea290e4fb5 | ||
|
|
8b414e6187 | ||
|
|
e7ef81ed97 | ||
|
|
12c286f9b1 | ||
|
|
7b5bc8fe37 | ||
|
|
bad78797bb | ||
|
|
bf1258578c | ||
|
|
6588f02f7b | ||
|
|
5e9c6375d0 | ||
|
|
7d47659481 | ||
|
|
dcd4bae96f | ||
|
|
4f628bf30b | ||
|
|
36e83a46e4 | ||
|
|
1a64ffb88b | ||
|
|
7c77927913 | ||
|
|
8224037080 | ||
|
|
d7e3fc99f1 | ||
|
|
feaf3951d2 | ||
|
|
399c629076 | ||
|
|
47ed7447f1 | ||
|
|
62a98ea3c5 | ||
|
|
0a9194b883 | ||
|
|
a938917b9b | ||
|
|
695d4c2b1b | ||
|
|
34724b941a | ||
|
|
8176e8c6f8 | ||
|
|
0d5b1cd64d | ||
|
|
9fd5b133ea | ||
|
|
432b57a070 | ||
|
|
ee55732be3 | ||
|
|
90304fb472 | ||
|
|
f6aec1af5f | ||
|
|
b77b61d677 | ||
|
|
05a0598f16 | ||
|
|
daa6f122a7 | ||
|
|
71f5f043fb | ||
|
|
f3eaf99665 | ||
|
|
c88c1b84da | ||
|
|
e1e310a96e | ||
|
|
d29b941be0 | ||
|
|
cde696915d | ||
|
|
185b8de17b | ||
|
|
d65285ee54 | ||
|
|
848bd1ba8c | ||
|
|
00205aa6a7 | ||
|
|
ecb9fd5a8a | ||
|
|
bdf9e6d3a4 | ||
|
|
3ed77dbb2e | ||
|
|
57a992c4a3 | ||
|
|
c63614213d | ||
|
|
b7a54fa74a | ||
|
|
63046ae909 | ||
|
|
af48e4b79b | ||
|
|
4e73d0779b | ||
|
|
6f424f3213 | ||
|
|
e110e93e0f | ||
|
|
8ddf335e68 | ||
|
|
ca9d102267 | ||
|
|
c3d120ccdf | ||
|
|
a4a6445e1d | ||
|
|
13e30a63eb | ||
|
|
e561952781 | ||
|
|
9fe58bf84f | ||
|
|
e84f85340b | ||
|
|
aaf90c6f52 | ||
|
|
0962e1bfba | ||
|
|
2f45644d14 | ||
|
|
c2a2316c28 | ||
|
|
6957e83cdb | ||
|
|
ce91e41e5a | ||
|
|
6d99d34eb0 | ||
|
|
b77980082c | ||
|
|
2fd59adffa | ||
|
|
57c34e2248 | ||
|
|
6514b77e0d | ||
|
|
8a907956d1 | ||
|
|
de33eac058 | ||
|
|
c4bfa825a1 | ||
|
|
b0c50d371f | ||
|
|
b4d4119572 | ||
|
|
674258787e | ||
|
|
2354412922 | ||
|
|
3ac30343b8 | ||
|
|
37343350ca | ||
|
|
d9e9d4403f | ||
|
|
741f494841 | ||
|
|
69b6c724fc | ||
|
|
31936358c1 | ||
|
|
fe58ff5753 | ||
|
|
9aaafe5dcf | ||
|
|
c2bd5e4eec | ||
|
|
98ddb62af4 | ||
|
|
24a684cff2 | ||
|
|
b4e7e981f3 | ||
|
|
e73549c61a | ||
|
|
e759d4087b | ||
|
|
106ba48079 | ||
|
|
ef768e36f3 | ||
|
|
f5e1f93ee5 | ||
|
|
a52c0c2907 | ||
|
|
be7d27bc49 | ||
|
|
f6a9c482a6 | ||
|
|
6e3f38b271 | ||
|
|
8483f1da1e | ||
|
|
28ed47e358 | ||
|
|
6a1b8f4a4f | ||
|
|
dba8ef2fdd | ||
|
|
b0624cb66e | ||
|
|
09ea82c97e | ||
|
|
d94b81b8e6 | ||
|
|
bcb1947a0a | ||
|
|
b0b5d7b392 | ||
|
|
2598d8ad70 | ||
|
|
f731e422ea | ||
|
|
abcdeb01e9 | ||
|
|
d326a9cddd | ||
|
|
e3131a0779 | ||
|
|
46c5a81b0d | ||
|
|
59146cabb1 | ||
|
|
35e3bddea0 | ||
|
|
016b8dcc4c | ||
|
|
03228762d4 | ||
|
|
953f5a0eff | ||
|
|
477bdb3dc8 | ||
|
|
d74f40882d | ||
|
|
d965e8de4f | ||
|
|
866b3c9238 | ||
|
|
3252324d24 | ||
|
|
8e7a016917 | ||
|
|
cf41a587a3 | ||
|
|
1dc1c65565 | ||
|
|
97ee344268 | ||
|
|
b658f94e5a | ||
|
|
0abe427026 | ||
|
|
063389afdf | ||
|
|
1e998ab0e4 | ||
|
|
b3e40a2644 | ||
|
|
fa4103f7aa | ||
|
|
17f0eb80cd | ||
|
|
35dfb1830b | ||
|
|
76f09adb48 | ||
|
|
289c7147e4 | ||
|
|
be34e1241c | ||
|
|
83ea8d8be9 | ||
|
|
7c90f4d6f1 | ||
|
|
61a90f7666 | ||
|
|
8d373cde6e | ||
|
|
6a465500bc | ||
|
|
f3e8413e77 | ||
|
|
f2a1e2337f | ||
|
|
c7f36f9480 | ||
|
|
955bde4abc | ||
|
|
afb03aa37f | ||
|
|
6c3814dfac | ||
|
|
d234e9ec41 | ||
|
|
c2c0325384 | ||
|
|
dfb6bc0312 | ||
|
|
0c6a7e2837 | ||
|
|
ddc8bf455e | ||
|
|
2855ae204c | ||
|
|
0bf54ff0e7 | ||
|
|
50ff45c213 | ||
|
|
eb2b546600 | ||
|
|
dc4da37908 | ||
|
|
9333354fc8 | ||
|
|
b557ed2221 | ||
|
|
021f8da6f1 | ||
|
|
6d0128059b | ||
|
|
d6dd838abd | ||
|
|
938fb7983a | ||
|
|
d9e262c394 | ||
|
|
e98be1a1e5 | ||
|
|
41fe7d090e | ||
|
|
aadf5b40ec | ||
|
|
ebf6d69f26 | ||
|
|
ebf616399e | ||
|
|
08708f79bf | ||
|
|
d7b39fe7a5 | ||
|
|
48ace1c530 | ||
|
|
fde1cc563f | ||
|
|
abf07a8357 | ||
|
|
a2f7080602 | ||
|
|
a2916c0e32 | ||
|
|
0301e3539e | ||
|
|
6632ab0a77 | ||
|
|
466c48da31 | ||
|
|
2c72a3755c | ||
|
|
92054aa649 | ||
|
|
4b5be43e60 | ||
|
|
d18864e607 | ||
|
|
4010a16784 | ||
|
|
01c6ea26b8 | ||
|
|
d83b912b3b | ||
|
|
604d78de0f | ||
|
|
9e8420aab5 | ||
|
|
dc3d6f5bc3 | ||
|
|
93431a9ddf | ||
|
|
ec6be38391 | ||
|
|
3777bcf295 | ||
|
|
3d09dfe574 | ||
|
|
532f932712 | ||
|
|
eeeb927a1d | ||
|
|
f9ce549663 | ||
|
|
5a715e7a0b | ||
|
|
e420872a27 | ||
|
|
cef32d4ac7 | ||
|
|
35375a6ea2 | ||
|
|
60eec0eccd | ||
|
|
97665573c7 | ||
|
|
f874377573 | ||
|
|
ce868faece | ||
|
|
e1c1e402a9 | ||
|
|
3b66a3364c | ||
|
|
9b03a3dc6d | ||
|
|
ff24ad5fa8 | ||
|
|
5180634947 | ||
|
|
da643a0c1f | ||
|
|
cd6765673f | ||
|
|
1b73abcfd0 | ||
|
|
f3778baaf4 | ||
|
|
cf1f9fa007 | ||
|
|
34619e111f | ||
|
|
29a5cef559 | ||
|
|
bd83c6a8a9 | ||
|
|
4b0348f64a | ||
|
|
5b52b8a60f | ||
|
|
c0e6dad88b | ||
|
|
a61616d79e | ||
|
|
8663014bbe | ||
|
|
351c7b099a | ||
|
|
c2e7fd34a7 | ||
|
|
a3e61a6e71 | ||
|
|
bd0c172667 | ||
|
|
7746039724 | ||
|
|
629f20720a | ||
|
|
2a3d7720f3 | ||
|
|
b47cf33c3b | ||
|
|
5fba432d78 | ||
|
|
b1efabaaed | ||
|
|
185d54d664 | ||
|
|
96322a6df9 | ||
|
|
42af0816d5 | ||
|
|
8368989341 | ||
|
|
9e60d468ca | ||
|
|
da32457037 | ||
|
|
2d2dbfebff | ||
|
|
6681387b47 | ||
|
|
6a4a915188 | ||
|
|
6ba66320f0 | ||
|
|
1367084a18 | ||
|
|
ec2976b069 | ||
|
|
bad8ed7473 | ||
|
|
e912b7de12 | ||
|
|
8570ff9a8c | ||
|
|
7f7ad29671 | ||
|
|
a95dab078e | ||
|
|
f8218e0648 | ||
|
|
e66d01e989 | ||
|
|
05331e1fa2 | ||
|
|
c5d3008d85 | ||
|
|
922a57b3e7 | ||
|
|
5d81fc7815 | ||
|
|
0827968f6b | ||
|
|
2d475fbca8 | ||
|
|
36862137db | ||
|
|
34c12e1282 | ||
|
|
8b0c858f28 | ||
|
|
daa0d67c6f | ||
|
|
aaac5466d1 | ||
|
|
89ac5d7de2 | ||
|
|
be0dcd5d10 | ||
|
|
9e0d3c7cbe | ||
|
|
f12061ea88 | ||
|
|
e87917f1e1 | ||
|
|
aae0f4630e | ||
|
|
523592be26 | ||
|
|
612ebb2e17 | ||
|
|
a189d08c30 | ||
|
|
26172b5101 | ||
|
|
51691ed7bf | ||
|
|
5acbccf0b2 | ||
|
|
e33489c04f | ||
|
|
bcb8765049 | ||
|
|
87661ef308 | ||
|
|
58e11c0b2d | ||
|
|
1739e0c09c | ||
|
|
c46695bb57 | ||
|
|
457a4f8f98 | ||
|
|
e3fd914e0b | ||
|
|
6aa5aa540b | ||
|
|
81bad4d089 | ||
|
|
9f4da789db | ||
|
|
a732d03b4d | ||
|
|
28267b9eb2 | ||
|
|
cad43914b0 |
2
.github/ISSUE_TEMPLATE
vendored
2
.github/ISSUE_TEMPLATE
vendored
@@ -2,7 +2,7 @@
|
||||
|
||||
## Specifications
|
||||
|
||||
You can use `micro -version` to get the commit hash.
|
||||
<!-- You can use `micro -version` to get the commit hash. -->
|
||||
|
||||
Commit hash:
|
||||
OS:
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -11,7 +11,10 @@ todo.txt
|
||||
test.txt
|
||||
log.txt
|
||||
*.old
|
||||
benchmark_results*
|
||||
tools/build-version
|
||||
tools/build-date
|
||||
tools/info-plist
|
||||
tools/bindata
|
||||
tools/vscode-tests/
|
||||
*.hdr
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016-2017: Zachary Yedidia, et al.
|
||||
Copyright (c) 2016-2020: Zachary Yedidia, et al.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Third party libraries
|
||||
Third party libraries directly used by micro and their licenses
|
||||
================
|
||||
|
||||
github.com/golang/go/LICENSE
|
||||
@@ -637,37 +637,6 @@ github.com/zyedidia/tcell/LICENSE (fork)
|
||||
limitations under the License.
|
||||
|
||||
|
||||
golang.org/x/net/LICENSE
|
||||
================
|
||||
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
golang.org/x/text/LICENSE
|
||||
================
|
||||
|
||||
@@ -1079,6 +1048,8 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
|
||||
github.com/flynn/json5/LICENSE
|
||||
================
|
||||
github.com/zyedidia/json5/LICENSE (fork)
|
||||
================
|
||||
|
||||
Decoder code based on package encoding/json from the Go language.
|
||||
|
||||
@@ -1189,82 +1160,6 @@ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
github.com/smartystreets/goconvey/LICENSE.md
|
||||
================
|
||||
|
||||
Copyright (c) 2016 SmartyStreets, LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
NOTE: Various optional and subordinate components carry their own licensing
|
||||
requirements and restrictions. Use of those components is subject to the terms
|
||||
and conditions outlined the respective license of each component.
|
||||
|
||||
github.com/smartystreets/assertions/LICENSE.md
|
||||
================
|
||||
|
||||
Copyright (c) 2016 SmartyStreets, LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
NOTE: Various optional and subordinate components carry their own licensing
|
||||
requirements and restrictions. Use of those components is subject to the terms
|
||||
and conditions outlined the respective license of each component.
|
||||
|
||||
github.com/jtolds/gls/LICENSE
|
||||
================
|
||||
|
||||
Copyright (c) 2013, Space Monkey, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
github.com/npat-efault/poller/LICENSE.txt
|
||||
================
|
||||
github.com/zyedidia/poller/LICENSE.txt (fork)
|
||||
@@ -1362,10 +1257,35 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
github.com/lucasb-eyer/go-colorful/LICENSE
|
||||
================
|
||||
github.com/kballard/go-shellquote/LICENSE
|
||||
===============
|
||||
|
||||
Copyright (c) 2013 Lucas Beyer
|
||||
Copyright (C) 2014 Kevin Ballard
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
github.com/stretchr/testify
|
||||
=================
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2012-2018 Mat Ryer and Tyler Bunnell
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -1374,8 +1294,8 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
|
||||
51
Makefile
51
Makefile
@@ -1,22 +1,26 @@
|
||||
.PHONY: runtime
|
||||
|
||||
VERSION := $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
||||
VERSION = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
||||
go run tools/build-version.go)
|
||||
HASH := $(shell git rev-parse --short HEAD)
|
||||
DATE := $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
||||
HASH = $(shell git rev-parse --short HEAD)
|
||||
DATE = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
||||
go run tools/build-date.go)
|
||||
ADDITIONAL_GO_LINKER_FLAGS := $(shell GOOS=$(shell go env GOHOSTOS) \
|
||||
GOARCH=$(shell go env GOHOSTARCH) \
|
||||
go run tools/info-plist.go "$(VERSION)")
|
||||
ADDITIONAL_GO_LINKER_FLAGS = $(shell GOOS=$(shell go env GOHOSTOS) \
|
||||
GOARCH=$(shell go env GOHOSTARCH))
|
||||
GOBIN ?= $(shell go env GOPATH)/bin
|
||||
GOVARS := -X github.com/zyedidia/micro/internal/util.Version=$(VERSION) -X github.com/zyedidia/micro/internal/util.CommitHash=$(HASH) -X 'github.com/zyedidia/micro/internal/util.CompileDate=$(DATE)' -X github.com/zyedidia/micro/internal/util.Debug=OFF
|
||||
GOVARS = -X github.com/zyedidia/micro/v2/internal/util.Version=$(VERSION) -X github.com/zyedidia/micro/v2/internal/util.CommitHash=$(HASH) -X 'github.com/zyedidia/micro/v2/internal/util.CompileDate=$(DATE)'
|
||||
DEBUGVAR = -X github.com/zyedidia/micro/v2/internal/util.Debug=ON
|
||||
VSCODE_TESTS_BASE_URL = 'https://raw.githubusercontent.com/microsoft/vscode/e6a45f4242ebddb7aa9a229f85555e8a3bd987e2/src/vs/editor/test/common/model/'
|
||||
|
||||
# Builds micro after checking dependencies but without updating the runtime
|
||||
build:
|
||||
go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
|
||||
build-dbg:
|
||||
go build -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
go build -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS) $(DEBUGVAR)" ./cmd/micro
|
||||
|
||||
build-tags: fetch-tags
|
||||
go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
|
||||
# Builds micro after building the runtime and checking dependencies
|
||||
build-all: runtime build
|
||||
@@ -36,16 +40,47 @@ install-all: runtime install
|
||||
install-quick:
|
||||
go install -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
|
||||
fetch-tags:
|
||||
git fetch --tags
|
||||
|
||||
# Builds the runtime
|
||||
runtime:
|
||||
git submodule update --init
|
||||
rm -f runtime/syntax/*.hdr
|
||||
go run runtime/syntax/make_headers.go runtime/syntax
|
||||
go build -o tools/bindata ./tools/go-bindata
|
||||
tools/bindata -pkg config -nomemcopy -nometadata -o runtime.go runtime/...
|
||||
mv runtime.go internal/config
|
||||
gofmt -w internal/config/runtime.go
|
||||
|
||||
testgen:
|
||||
mkdir -p tools/vscode-tests
|
||||
cd tools/vscode-tests && \
|
||||
curl --remote-name-all $(VSCODE_TESTS_BASE_URL){editableTextModelAuto,editableTextModel,model.line}.test.ts
|
||||
tsc tools/vscode-tests/*.ts > /dev/null; true
|
||||
go run tools/testgen.go tools/vscode-tests/*.js > buffer_generated_test.go
|
||||
mv buffer_generated_test.go internal/buffer
|
||||
gofmt -w internal/buffer/buffer_generated_test.go
|
||||
|
||||
test:
|
||||
go test ./internal/...
|
||||
|
||||
bench:
|
||||
for i in 1 2 3; do \
|
||||
go test -bench=. ./internal/...; \
|
||||
done > benchmark_results
|
||||
benchstat benchmark_results
|
||||
|
||||
bench-baseline:
|
||||
for i in 1 2 3; do \
|
||||
go test -bench=. ./internal/...; \
|
||||
done > benchmark_results_baseline
|
||||
|
||||
bench-compare:
|
||||
for i in 1 2 3; do \
|
||||
go test -bench=. ./internal/...; \
|
||||
done > benchmark_results
|
||||
benchstat -alpha 0.15 benchmark_results_baseline benchmark_results
|
||||
|
||||
clean:
|
||||
rm -f micro
|
||||
|
||||
236
README.md
236
README.md
@@ -1,102 +1,101 @@
|
||||
# 
|
||||
<img alt="micro logo" src="./assets/micro-logo.svg" width="500px"/>
|
||||
|
||||
[](https://travis-ci.org/zyedidia/micro)
|
||||
[](https://goreportcard.com/report/github.com/zyedidia/micro)
|
||||
[](https://gitter.im/zyedidia/micro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://github.com/zyedidia/micro/releases)
|
||||
[](https://github.com/zyedidia/micro/blob/master/LICENSE)
|
||||
[](https://gitter.im/zyedidia/micro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://build.snapcraft.io/user/zyedidia/micro)
|
||||
|
||||
Micro is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the full capabilities
|
||||
of modern terminals. It comes as one single, batteries-included, static binary with no dependencies, and you can download and use it right now.
|
||||
**micro** is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the capabilities
|
||||
of modern terminals. It comes as a single, batteries-included, static binary with no dependencies; you can download and use it right now!
|
||||
|
||||
As the name indicates, micro aims to be somewhat of a successor to the nano editor by being easy to install and use in a pinch, but micro also aims to be
|
||||
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
|
||||
As its name indicates, micro aims to be somewhat of a successor to the nano editor by being easy to install and use.
|
||||
It strives to be enjoyable as a full-time editor for people who prefer to work in a terminal, or those who regularly edit files over SSH.
|
||||
|
||||
Here is a picture of micro editing its source code.
|
||||
|
||||

|
||||
|
||||
To see more screenshots of micro, showcasing all of the default colorschemes, see [here](http://zbyedidia.webfactional.com/micro/screenshots.html).
|
||||
To see more screenshots of micro, showcasing some of the default color schemes, see [here](http://zbyedidia.webfactional.com/micro/screenshots.html).
|
||||
|
||||
You can also check out the website for Micro at https://micro-editor.github.io.
|
||||
|
||||
# Table of Contents
|
||||
## Table of Contents
|
||||
|
||||
- [Features](#features)
|
||||
- [Installation](#installation)
|
||||
- [Prebuilt binaries](#prebuilt-binaries)
|
||||
- [Package Managers](#package-managers)
|
||||
- [Building from source](#building-from-source)
|
||||
- [MacOS terminal](#macos-terminal)
|
||||
- [Fully static binary](#fully-static-binary)
|
||||
- [macOS terminal](#macos-terminal)
|
||||
- [Linux clipboard support](#linux-clipboard-support)
|
||||
- [Colors and syntax highlighting](#colors-and-syntax-highlighting)
|
||||
- [Plan9, Cygwin](#plan9-cygwin)
|
||||
- [Plan9, Cygwin, Mingw](#plan9-cygwin-mingw)
|
||||
- [Usage](#usage)
|
||||
- [Documentation and Help](#documentation-and-help)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
- - -
|
||||
|
||||
# Features
|
||||
## Features
|
||||
|
||||
* Easy to use and to install
|
||||
* No dependencies or external files are needed -- just the binary you can download further down the page
|
||||
* Multiple cursors
|
||||
* Common keybindings (ctrl-s, ctrl-c, ctrl-v, ctrl-z...)
|
||||
* Keybindings can be rebound to your liking
|
||||
* Sane defaults
|
||||
* You shouldn't have to configure much out of the box (and it is extremely easy to configure)
|
||||
* Splits and tabs
|
||||
* Nano-like menu to help you remember the keybindings
|
||||
* Extremely good mouse support
|
||||
* This means mouse dragging to create a selection, double click to select by word, and triple click to select by line
|
||||
* Cross platform (It should work on all the platforms Go runs on)
|
||||
* Note that while Windows is supported, there are still some bugs that need to be worked out
|
||||
* Plugin system (plugins are written in Lua)
|
||||
* Micro has a built-in plugin manager to automatically install, remove, and update all your plugins
|
||||
* Persistent undo
|
||||
* Automatic linting and error notifications
|
||||
* Syntax highlighting (for over [90 languages](runtime/syntax)!)
|
||||
* Colorscheme support
|
||||
* By default, micro comes with 16, 256, and true color themes.
|
||||
* True color support (set the `MICRO_TRUECOLOR` env variable to 1 to enable it)
|
||||
* Snippets
|
||||
* The snippet plugin can be installed with `> plugin install snippets`
|
||||
* Copy and paste with the system clipboard
|
||||
* Small and simple
|
||||
* Easily configurable
|
||||
* Macros
|
||||
* Common editor things such as undo/redo, line numbers, Unicode support, softwrap...
|
||||
- Easy to use and install.
|
||||
- No dependencies or external files are needed — just the binary you can download further down the page.
|
||||
- Multiple cursors.
|
||||
- Common keybindings (<kbd>Ctrl-s</kbd>, <kbd>Ctrl-c</kbd>, <kbd>Ctrl-v</kbd>, <kbd>Ctrl-z</kbd>, …).
|
||||
- Keybindings can be rebound to your liking.
|
||||
- Sane defaults.
|
||||
- You shouldn't have to configure much out of the box (and it is extremely easy to configure).
|
||||
- Splits and tabs.
|
||||
- nano-like menu to help you remember the keybindings.
|
||||
- Extremely good mouse support.
|
||||
- This means mouse dragging to create a selection, double click to select by word, and triple click to select by line.
|
||||
- Cross-platform (it should work on all the platforms Go runs on).
|
||||
- Note that while Windows is supported Mingw/Cygwin is not (see below).
|
||||
- Plugin system (plugins are written in Lua).
|
||||
- micro has a built-in plugin manager to automatically install, remove, and update plugins.
|
||||
- Built-in diff gutter.
|
||||
- Simple autocompletion.
|
||||
- Persistent undo.
|
||||
- Automatic linting and error notifications.
|
||||
- Syntax highlighting for over [130 languages](runtime/syntax).
|
||||
- Color scheme support.
|
||||
- By default, micro comes with 16, 256, and true color themes.
|
||||
- True color support (set the `MICRO_TRUECOLOR` environment variable to 1 to enable it).
|
||||
- Copy and paste with the system clipboard.
|
||||
- Small and simple.
|
||||
- Easily configurable.
|
||||
- Macros.
|
||||
- Common editor features such as undo/redo, line numbers, Unicode support, soft wrapping, …
|
||||
|
||||
Although not yet implemented, I hope to add more features such as autocompletion ([#174](https://github.com/zyedidia/micro/issues/174)) or a tree view ([#249](https://github.com/zyedidia/micro/issues/249)) in the future.
|
||||
|
||||
# Installation
|
||||
## Installation
|
||||
|
||||
To install micro, you can download a [prebuilt binary](https://github.com/zyedidia/micro/releases), or you can build it from source.
|
||||
|
||||
If you want more information about ways to install micro, see this [wiki page](https://github.com/zyedidia/micro/wiki/Installing-Micro).
|
||||
|
||||
Use `micro -version` to get the version information after installing. It is only guaranteed that you are installing the most recent
|
||||
stable version if you install from the prebuilt binaries, Homebrew, or Snap.
|
||||
|
||||
### Prebuilt binaries
|
||||
|
||||
All you need to install micro is one file, the binary itself. It's as simple as that!
|
||||
|
||||
Download the binary from the [releases](https://github.com/zyedidia/micro/releases) page.
|
||||
|
||||
On that page you'll see the nightly release, which contains binaries for micro which are built every night,
|
||||
and you'll see all the stable releases with the corresponding binaries.
|
||||
|
||||
If you'd like to see more information after installing micro, run `micro -version`.
|
||||
|
||||
### Installation script
|
||||
|
||||
There is a great script which can install micro for you by downloading the latest prebuilt binary. You can find it at https://getmic.ro (the github repo for it is [here](https://github.com/benweissmann/getmic.ro)).
|
||||
There is a script which can install micro for you by downloading the latest prebuilt binary. You can find it at <https://getmic.ro>.
|
||||
|
||||
Then you can easily install micro:
|
||||
You can easily install micro by running
|
||||
|
||||
$ curl https://getmic.ro | bash
|
||||
```bash
|
||||
curl https://getmic.ro | bash
|
||||
```
|
||||
|
||||
The script will install the micro binary to the current directory.
|
||||
|
||||
See the [Github page](https://github.com/benweissmann/getmic.ro) for more information.
|
||||
The script will place the micro binary in the current directory. See its [GitHub repository](https://github.com/benweissmann/getmic.ro) for more information.
|
||||
|
||||
### Package managers
|
||||
|
||||
@@ -106,17 +105,9 @@ You can install micro using Homebrew on Mac:
|
||||
brew install micro
|
||||
```
|
||||
|
||||
On Windows, you can install micro through [Chocolatey](https://chocolatey.org/) or [Scoop](https://github.com/lukesampson/scoop):
|
||||
|
||||
```
|
||||
choco install micro
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
scoop install micro
|
||||
```
|
||||
**Note for Mac:** All micro keybindings use the control or alt (option) key, not the command
|
||||
key. By default, macOS terminals do not forward alt key events. To fix this, please see
|
||||
the section on [macOS terminals](https://github.com/zyedidia/micro#macos-terminal) further below.
|
||||
|
||||
On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/install)
|
||||
|
||||
@@ -124,71 +115,106 @@ On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/in
|
||||
snap install micro --classic
|
||||
```
|
||||
|
||||
On OpenBSD, micro is available in the ports tree. It is also available as a binary package.
|
||||
**Note for Linux:** for interfacing with the local system clipboard, `xclip` or `xsel`
|
||||
must be installed. Please see the section on [Linux clipboard support](https://github.com/zyedidia/micro#linux-clipboard-support)
|
||||
further below.
|
||||
|
||||
```
|
||||
pkg_add -v micro
|
||||
```
|
||||
Micro is also available through other package managers on Linux such as apt, dnf, AUR, Nix, and package managers
|
||||
for other operating systems. These packages are not guaranteed to be up-to-date.
|
||||
|
||||
* Linux: Available in distro-specific package managers.
|
||||
* `apt install micro` (Ubuntu 20.04 `focal`, and Debian `unstable | testing | buster-backports`).
|
||||
* `dnf install micro` (Fedora).
|
||||
* `yay -S micro` (Arch Linux).
|
||||
* See [wiki](https://github.com/zyedidia/micro/wiki/Installing-Micro) for details about CRUX, Termux.
|
||||
* Windows: [Chocolatey](https://chocolatey.org) and [Scoop](https://github.com/lukesampson/scoop).
|
||||
* `choco install micro`.
|
||||
* `scoop install micro`.
|
||||
* OpenBSD: Available in the ports tree and also available as a binary package.
|
||||
* `pkd_add -v micro`.
|
||||
* NetBSD, macOS, Linux, Illumos, etc. with [pkgsrc](http://www.pkgsrc.org/)-current:
|
||||
* `pkg_add micro`
|
||||
|
||||
### Building from source
|
||||
|
||||
If your operating system does not have a binary release, but does run Go, you can build from source.
|
||||
|
||||
Make sure that you have Go version 1.5 or greater (Go 1.4 will work if your version supports CGO) and that your `GOPATH` env variable is set (I recommend setting it to `~/go` if you don't have one).
|
||||
Make sure that you have Go version 1.11 or greater and Go modules are enabled.
|
||||
|
||||
```
|
||||
go get -d github.com/zyedidia/micro/cmd/micro
|
||||
cd $GOPATH/src/github.com/zyedidia/micro
|
||||
make install
|
||||
git clone https://github.com/zyedidia/micro
|
||||
cd micro
|
||||
make build
|
||||
sudo mv micro /usr/local/bin # optional
|
||||
```
|
||||
|
||||
The binary will then be installed to `$GOPATH/bin` (or your `$GOBIN`).
|
||||
The binary will be placed in the current directory and can be moved to
|
||||
anywhere you like (for example `/usr/local/bin`).
|
||||
|
||||
Please make sure that when you are working with micro's code, you are working on your `GOPATH`.
|
||||
The command `make install` will install the binary to `$GOPATH/bin` or `$GOBIN`.
|
||||
|
||||
You can install directly with `go get` (`go get -u github.com/zyedidia/micro/cmd/micro`) but this isn't recommended because it doesn't build micro with version information which is useful for the plugin manager.
|
||||
You can install directly with `go get` (`go get github.com/zyedidia/micro/cmd/micro`) but this isn't
|
||||
recommended because it doesn't build micro with version information (necessary for the plugin manager),
|
||||
and doesn't disable debug mode.
|
||||
|
||||
### MacOS terminal
|
||||
### Fully static binary
|
||||
|
||||
If you are using MacOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default Mac terminal. The iTerm2 terminal has much better mouse support as well as better handling of key events. For best keybinding behavior, choose `xterm defaults` under `Preferences->Profiles->Keys->Load Preset`. The newest versions also support true color.
|
||||
By default, the micro binary will dynamically link with core system libraries (this is generally
|
||||
recommended for security and portability). However, there is a fully static prebuilt binary that
|
||||
is provided for amd64 as `linux-static.tar.gz`, and to build a fully static binary from source, run
|
||||
|
||||
```
|
||||
CGO_ENABLED=0 make build
|
||||
```
|
||||
|
||||
### macOS terminal
|
||||
|
||||
If you are using macOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default terminal (Terminal.app). The iTerm2 terminal has much better mouse support as well as better handling of key events. For best keybinding behavior, choose `xterm defaults` under `Preferences->Profiles->Keys->Presets...`, and select `Esc+` for `Left Option Key` in the same menu. The newest versions also support true color.
|
||||
|
||||
If you still insist on using the default Mac terminal, be sure to set `Use Option key as Meta key` under
|
||||
`Preferences->Profiles->Keyboard` to use <kbd>option</kbd> as <kbd>alt</kbd>.
|
||||
|
||||
### Linux clipboard support
|
||||
|
||||
On Linux, clipboard support requires the 'xclip' or 'xsel' commands to be installed.
|
||||
On Linux, clipboard support requires:
|
||||
|
||||
For Ubuntu:
|
||||
- On X11, the `xclip` or `xsel` commands (for Ubuntu: `sudo apt install xclip`)
|
||||
- On Wayland, the `wl-clipboard` command
|
||||
|
||||
```sh
|
||||
sudo apt-get install xclip
|
||||
```
|
||||
|
||||
If you don't have xclip or xsel, micro will use an internal clipboard for copy and paste, but it won't work with external applications.
|
||||
If you don't have these commands, micro will use an internal clipboard for copy and paste, but it won't work with external applications.
|
||||
|
||||
### Colors and syntax highlighting
|
||||
|
||||
If you open micro and it doesn't seem like syntax highlighting is working, this is probably because
|
||||
you are using a terminal which does not support 256 color. Try changing the colorscheme to `simple`
|
||||
by pressing CtrlE in micro and typing `set colorscheme simple`.
|
||||
you are using a terminal which does not support 256 color mode. Try changing the color scheme to `simple`
|
||||
by pressing <kbd>Ctrl-e</kbd> in micro and typing `set colorscheme simple`.
|
||||
|
||||
If you are using the default Ubuntu terminal, to enable 256 make sure your `TERM` variable is set
|
||||
to `xterm-256color`.
|
||||
|
||||
Many of the Windows terminals don't support more than 16 colors, which means
|
||||
that micro's default colorscheme won't look very good. You can either set
|
||||
the colorscheme to `simple`, or download a better terminal emulator, like
|
||||
mintty.
|
||||
that micro's default color scheme won't look very good. You can either set
|
||||
the color scheme to `simple`, or download and configure a better terminal emulator
|
||||
than the Windows default.
|
||||
|
||||
### Plan9, Cygwin
|
||||
### Cygwin, Mingw, Plan9
|
||||
|
||||
Please note that micro uses the amazing [tcell library](https://github.com/gdamore/tcell), but this
|
||||
Cygwin, Mingw, and Plan9 are unfortunately not officially supported. In Cygwin and Mingw, micro will often work when run using
|
||||
the `winpty` utility:
|
||||
|
||||
```
|
||||
winpty micro.exe ...
|
||||
```
|
||||
|
||||
Micro uses the amazing [tcell library](https://github.com/gdamore/tcell), but this
|
||||
means that micro is restricted to the platforms tcell supports. As a result, micro does not support
|
||||
Plan9, and Cygwin (although this may change in the future). Micro also doesn't support NaCl (but NaCl is deprecated anyways).
|
||||
Plan9, and Cygwin (although this may change in the future). Micro also doesn't support NaCl (which is deprecated anyway).
|
||||
|
||||
# Usage
|
||||
## Usage
|
||||
|
||||
Once you have built the editor, simply start it by running `micro path/to/file.txt` or simply `micro` to open an empty buffer.
|
||||
Once you have built the editor, start it by running `micro path/to/file.txt` or `micro` to open an empty buffer.
|
||||
|
||||
Micro also supports creating buffers from `stdin`:
|
||||
micro also supports creating buffers from `stdin`:
|
||||
|
||||
```sh
|
||||
ifconfig | micro
|
||||
@@ -200,22 +226,22 @@ You can also use the mouse to manipulate the text. Simply clicking and dragging
|
||||
will select text. You can also double click to enable word selection, and triple
|
||||
click to enable line selection.
|
||||
|
||||
# Documentation and Help
|
||||
## Documentation and Help
|
||||
|
||||
Micro has a built-in help system which you can access by pressing `Ctrl-E` and typing `help`. Additionally, you can
|
||||
micro has a built-in help system which you can access by pressing <kbd>Ctrl-e</kbd> and typing `help`. Additionally, you can
|
||||
view the help files here:
|
||||
|
||||
* [main help](https://github.com/zyedidia/micro/tree/master/runtime/help/help.md)
|
||||
* [keybindings](https://github.com/zyedidia/micro/tree/master/runtime/help/keybindings.md)
|
||||
* [commands](https://github.com/zyedidia/micro/tree/master/runtime/help/commands.md)
|
||||
* [colors](https://github.com/zyedidia/micro/tree/master/runtime/help/colors.md)
|
||||
* [options](https://github.com/zyedidia/micro/tree/master/runtime/help/options.md)
|
||||
* [plugins](https://github.com/zyedidia/micro/tree/master/runtime/help/plugins.md)
|
||||
- [main help](https://github.com/zyedidia/micro/tree/master/runtime/help/help.md)
|
||||
- [keybindings](https://github.com/zyedidia/micro/tree/master/runtime/help/keybindings.md)
|
||||
- [commands](https://github.com/zyedidia/micro/tree/master/runtime/help/commands.md)
|
||||
- [colors](https://github.com/zyedidia/micro/tree/master/runtime/help/colors.md)
|
||||
- [options](https://github.com/zyedidia/micro/tree/master/runtime/help/options.md)
|
||||
- [plugins](https://github.com/zyedidia/micro/tree/master/runtime/help/plugins.md)
|
||||
|
||||
I also recommend reading the [tutorial](https://github.com/zyedidia/micro/tree/master/runtime/help/tutorial.md) for
|
||||
a brief introduction to the more powerful configuration features micro offers.
|
||||
|
||||
# Contributing
|
||||
## Contributing
|
||||
|
||||
If you find any bugs, please report them! I am also happy to accept pull requests from anyone.
|
||||
|
||||
@@ -223,3 +249,5 @@ You can use the [GitHub issue tracker](https://github.com/zyedidia/micro/issues)
|
||||
to report bugs, ask questions, or suggest new features.
|
||||
|
||||
For a more informal setting to discuss the editor, you can join the [Gitter chat](https://gitter.im/zyedidia/micro).
|
||||
|
||||
Sometimes I am unresponsive, and I apologize! If that happens, please ping me.
|
||||
|
||||
BIN
assets/logo.png
BIN
assets/logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,63 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg3336"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
width="128"
|
||||
height="128"
|
||||
viewBox="0 0 128 128"
|
||||
sodipodi:docname="logo.svg">
|
||||
<metadata
|
||||
id="metadata3342">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs3340" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1355"
|
||||
inkscape:window-height="717"
|
||||
id="namedview3338"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.6243169"
|
||||
inkscape:cx="111.32302"
|
||||
inkscape:cy="30.538264"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg3336" />
|
||||
<path
|
||||
style="fill:#2e3192;fill-opacity:1"
|
||||
d="m 56.1,127.32358 c -13.68932,-1.70993 -27.156628,-8.3544 -37.112903,-18.31068 -25.0687936,-25.068788 -25.0687936,-65.95701 0,-91.025803 25.068793,-25.0687936 65.957015,-25.0687936 91.025803,0 25.0688,25.068793 25.0688,65.957015 0,91.025803 C 95.87457,123.15123 76.198116,129.83404 56.1,127.32358 Z"
|
||||
id="path3364"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#ffffff"
|
||||
d="m 40.756452,106.01908 c 1.442831,-1.83426 1.55476,-4.09687 0.414499,-8.37899 -0.678184,-2.546844 -0.684604,-4.05591 -0.03829,-9 1.276867,-9.767604 4.483143,-23.040636 5.565559,-23.039766 0.220979,1.74e-4 0.417725,2.092674 0.437213,4.65 0.04167,5.468298 1.558564,9.06891 4.638769,11.010942 2.551646,1.608774 9.15365,1.329324 12.80399,-0.541974 3.245124,-1.663572 7.649064,-6.112434 9.850956,-9.951438 L 76.188736,67.7 l 0.0054,3.922866 c 0.0042,2.867148 0.36894,4.642788 1.355628,6.59796 1.532058,3.035856 3.323226,4.15755 6.659322,4.17033 5.192928,0.01986 9.07014,-3.668676 10.866768,-10.338036 0.98277,-3.64821 1.064448,-11.21265 0.09235,-8.55312 -3.025218,8.276592 -4.468212,9.893562 -9.238056,10.351884 -2.629152,0.25263 -3.177804,0.08883 -4.921776,-1.469412 -1.609044,-1.437678 -2.016072,-2.308416 -2.258508,-4.8315 -0.262884,-2.73585 0.105942,-4.06497 3.32007,-11.964365 C 88.28388,40.315087 89.33625,35.536248 87,33.2 c -1.559352,-1.559353 -3.62787,-1.522741 -5.691792,0.10074 -2.295762,1.805846 -3.105984,4.070756 -5.14293,14.376662 -2.464164,12.46744 -6.525822,20.297092 -12.62193,24.331306 C 59.052142,74.98085 52.704914,73.6403 50.637191,69.282896 49.19967,66.253544 49.857706,62.552972 53.387813,53.814319 56.613526,45.829186 58.8,38.711369 58.8,36.195564 c 0,-4.161283 -4.366993,-5.665719 -7.364438,-2.537061 -2.183558,2.279144 -3.117251,5.256959 -4.280897,13.653016 -0.547956,3.953665 -1.259292,9.010489 -1.580746,11.237387 -0.321454,2.226896 -2.083918,8.706896 -3.916587,14.400002 -4.33165,13.456074 -6.85029,23.184822 -7.273674,28.096022 -0.325586,3.77675 -0.269352,4.00056 1.319044,5.25 2.187498,1.72068 3.541408,1.64679 5.05375,-0.27585 z"
|
||||
id="path3362"
|
||||
inkscape:connector-curvature="0" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.6 KiB |
70
assets/micro-logo.svg
Normal file
70
assets/micro-logo.svg
Normal file
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 299.89999 103.2"
|
||||
enable-background="new 0 0 960 560"
|
||||
xml:space="preserve"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="micro-logo.svg"
|
||||
width="299.89999"
|
||||
height="103.2"><metadata
|
||||
id="metadata21"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs19" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1237"
|
||||
inkscape:window-height="867"
|
||||
id="namedview17"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="1.1416667"
|
||||
inkscape:cx="75.655934"
|
||||
inkscape:cy="-4"
|
||||
inkscape:window-x="1097"
|
||||
inkscape:window-y="185"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="Layer_1" /><g
|
||||
id="g3"
|
||||
transform="translate(-178,-172.8)"><path
|
||||
d="m 306.8,213.8 0,-2.6 c 1.6,-0.1 2.9,-0.4 4.1,-0.8 1.2,-0.4 2.5,-1 4,-1.8 l 2.3,0 0,5.2 c 2.4,-1.9 4.2,-3.1 5.5,-3.8 2,-1 4,-1.5 5.8,-1.5 1.3,0 2.5,0.2 3.7,0.7 1.2,0.5 2.2,1 2.9,1.7 0.7,0.7 1.4,1.6 1.9,2.8 2.2,-1.9 4.2,-3.3 6,-4 1.9,-0.8 3.7,-1.2 5.6,-1.2 1.8,0 3.4,0.4 4.8,1.1 1.4,0.8 2.4,1.7 3,2.8 0.6,1.1 0.9,2.8 0.9,5 l 0,14.4 c 0,1.5 0,2.4 0.1,2.6 0.1,0.4 0.3,0.8 0.7,1.1 0.3,0.4 0.7,0.6 1.2,0.7 0.4,0.1 1.2,0.2 2.4,0.2 l 1,0 0,2.6 -15.5,0 0,-2.6 c 1.8,0 2.9,-0.1 3.5,-0.4 0.5,-0.2 0.9,-0.6 1.2,-1.2 0.3,-0.6 0.4,-1.6 0.4,-3.2 l 0,-13.7 c 0,-1.7 -0.2,-2.9 -0.5,-3.6 -0.3,-0.7 -0.9,-1.2 -1.7,-1.7 -0.8,-0.4 -1.8,-0.7 -3,-0.7 -1.5,0 -3,0.4 -4.6,1.2 -2.2,1.1 -3.9,2.3 -5.1,3.6 l 0,14.8 c 0,1.4 0.1,2.4 0.2,2.8 0.1,0.4 0.4,0.8 0.7,1.1 0.3,0.3 0.7,0.5 1.1,0.6 0.4,0.1 1.5,0.2 3.1,0.2 l 0,2.6 -15.3,0 0,-2.6 0.9,0 c 1.2,0 2.1,-0.1 2.6,-0.4 0.5,-0.3 0.9,-0.7 1.2,-1.3 0.2,-0.5 0.3,-1.5 0.3,-2.9 l 0,-13.2 c 0,-1.9 -0.2,-3.3 -0.5,-3.9 -0.3,-0.7 -0.9,-1.3 -1.7,-1.7 -0.8,-0.5 -1.8,-0.7 -3,-0.7 -1.3,0 -2.7,0.3 -4.1,1 -2,1 -3.9,2.2 -5.6,3.8 l 0,15.9 c 0,1 0.1,1.6 0.4,2.1 0.3,0.4 0.7,0.8 1.2,1.1 0.6,0.3 1.3,0.4 2.3,0.4 l 1.1,0 0,2.6 -15.6,0 0,-2.6 0.8,0 c 1.4,0 2.4,-0.1 2.8,-0.3 0.7,-0.3 1.1,-0.8 1.4,-1.5 0.2,-0.4 0.2,-1.3 0.2,-2.9 l 0,-18.1 -5.1,0 z"
|
||||
id="path5"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 366.4,213.7 0,-2.6 c 1.7,-0.2 3.2,-0.5 4.3,-0.9 1.2,-0.4 2.5,-1 4,-1.7 l 2.3,0 0,24.9 c 0,0.9 0.1,1.5 0.4,2 0.2,0.4 0.6,0.8 1,0.9 0.4,0.2 1.3,0.3 2.4,0.3 l 1.5,0 0,2.6 -15.9,0 0,-2.6 1.3,0 c 1.4,0 2.3,-0.1 2.8,-0.4 0.5,-0.2 0.8,-0.6 1,-1.1 0.2,-0.5 0.3,-1.5 0.3,-3.2 l 0,-18.3 -5.4,0 z m 7.9,-19.2 c 1,0 1.8,0.3 2.5,1 0.7,0.7 1.1,1.5 1.1,2.5 0,1 -0.4,1.8 -1.1,2.5 -0.7,0.7 -1.6,1.1 -2.5,1.1 -1,0 -1.8,-0.4 -2.5,-1.1 -0.7,-0.7 -1.1,-1.6 -1.1,-2.5 0,-1 0.4,-1.8 1.1,-2.5 0.6,-0.6 1.5,-1 2.5,-1 z"
|
||||
id="path7"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 413.1,230.6 2,1.6 c -3.9,5.2 -8.6,7.8 -14,7.8 -4.2,0 -7.8,-1.5 -10.7,-4.5 -2.9,-3 -4.4,-6.8 -4.4,-11.3 0,-3 0.7,-5.7 2,-8.1 1.3,-2.4 3.2,-4.2 5.6,-5.6 2.4,-1.3 5.2,-2 8.3,-2 3.6,0 6.5,0.9 8.9,2.6 2.4,1.7 3.6,3.5 3.6,5.3 0,1 -0.3,1.7 -0.8,2.2 -0.5,0.5 -1.2,0.8 -1.9,0.8 -0.4,0 -0.7,-0.1 -1.1,-0.3 -0.4,-0.2 -0.7,-0.5 -1.1,-0.9 -0.2,-0.2 -0.5,-0.8 -0.9,-1.7 -0.6,-1.2 -1,-2 -1.3,-2.4 -0.6,-0.8 -1.4,-1.5 -2.4,-2 -0.9,-0.5 -2,-0.7 -3.1,-0.7 -1.8,0 -3.4,0.5 -4.9,1.5 -1.5,1 -2.7,2.4 -3.6,4.3 -0.9,1.9 -1.3,4.2 -1.3,6.8 0,4.1 1.1,7.3 3.3,9.7 1.9,2.1 4.1,3.1 6.7,3.1 1.2,0 2.4,-0.2 3.6,-0.6 1.2,-0.4 2.4,-1 3.5,-1.8 0.9,-0.6 2.2,-1.8 4,-3.8 z"
|
||||
id="path9"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 418.7,213.7 0,-2.6 c 1.5,-0.1 2.8,-0.4 4,-0.8 1.2,-0.4 2.5,-1 4,-1.9 l 2.3,0 0,5.9 c 1.5,-1.8 3.2,-3.2 5.1,-4.3 1.9,-1.1 3.7,-1.6 5.2,-1.6 1.5,0 2.7,0.4 3.6,1.1 0.9,0.7 1.3,1.6 1.3,2.6 0,0.7 -0.3,1.4 -0.9,2 -0.6,0.6 -1.3,0.9 -2.1,0.9 -0.4,0 -0.7,-0.1 -1,-0.2 -0.3,-0.1 -0.7,-0.3 -1.2,-0.7 -1.1,-0.7 -2.1,-1.1 -2.9,-1.1 -1,0 -2.2,0.4 -3.4,1.3 -1.6,1.1 -2.8,2.2 -3.7,3.3 l 0,14.4 c 0,1.2 0.1,2.1 0.2,2.5 0.1,0.4 0.4,0.8 0.7,1.1 0.3,0.3 0.7,0.6 1.1,0.7 0.5,0.1 1.3,0.2 2.4,0.2 l 1,0 0,2.6 -16,0 0,-2.6 1.3,0 c 1.3,0 2.1,-0.1 2.5,-0.3 0.5,-0.3 0.9,-0.7 1.2,-1.3 0.3,-0.5 0.4,-1.5 0.4,-3 l 0,-18.3 -5.1,0 z"
|
||||
id="path11"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 462.8,208.5 c 3,0 5.7,0.6 7.9,1.9 2.2,1.3 4,3.1 5.3,5.5 1.3,2.4 1.9,5.2 1.9,8.3 0,3.1 -0.7,5.9 -2,8.3 -1.3,2.4 -3.1,4.3 -5.4,5.5 -2.3,1.3 -5,1.9 -8.1,1.9 -5,0 -8.8,-1.6 -11.3,-4.7 -2.5,-3.1 -3.8,-6.8 -3.8,-11 0,-3.1 0.7,-5.8 2,-8.2 1.3,-2.4 3.1,-4.2 5.5,-5.6 2.4,-1.2 5.1,-1.9 8,-1.9 z m -0.2,3 c -2.4,0 -4.4,0.9 -6,2.8 -2.1,2.3 -3.1,5.7 -3.1,10.1 0,4.3 0.9,7.5 2.6,9.7 1.6,2 3.8,3 6.5,3 1.8,0 3.3,-0.5 4.7,-1.4 1.4,-0.9 2.5,-2.4 3.3,-4.5 0.8,-2 1.3,-4.4 1.3,-7.2 0,-2.7 -0.5,-5.1 -1.4,-7.2 -0.7,-1.7 -1.8,-3 -3.2,-4 -1.3,-0.8 -2.9,-1.3 -4.7,-1.3 z"
|
||||
id="path13"
|
||||
inkscape:connector-curvature="0" /></g><path
|
||||
d="M 51.6,0 C 23.1,0 0,23.1 0,51.6 c 0,28.5 23.1,51.6 51.6,51.6 28.5,0 51.6,-23.1 51.6,-51.6 C 103.2,23.1 80.1,0 51.6,0 Z m 24.5,58.6 c -0.5,2 -1.3,3.6 -2.4,4.9 -1,1.3 -2,2.1 -3.1,2.5 -1.1,0.4 -2.2,0.6 -3.4,0.6 -1.2,0 -2.2,-0.2 -3,-0.7 C 63.4,65.5 62.8,64.8 62.3,64 61.8,63.2 61.5,62.2 61.3,61.1 61.1,60 61,58.8 61,57.5 c 0,-0.5 0,-1 0.1,-1.7 0.1,-0.7 0.2,-1.6 0.3,-1.6 l -0.2,0 c -1.6,4 -3.8,6.9 -6.6,9.2 -2.8,2.3 -5.9,3.4 -9.3,3.4 -2.3,0 -4.2,-0.9 -5.5,-2.6 -1.4,-1.7 -2.1,-4.3 -2.1,-7.7 0,-0.5 0,-1 0.1,-1.6 0.1,-0.5 0.1,-0.7 0.2,-1.7 l -0.7,0 c -0.9,2 -1.7,4.8 -2.3,7.3 -0.6,2.5 -1.1,4.8 -1.4,6.9 -0.4,2.1 -0.6,4 -0.8,5.6 -0.2,1.6 -0.3,2.7 -0.4,3.3 0.1,0.5 0.2,1 0.3,1.6 0.2,0.6 0.3,1.2 0.5,1.7 0.2,0.5 0.3,1.1 0.4,1.6 0.1,0.5 0.2,0.9 0.2,1.2 0,1.4 -0.3,2.5 -0.9,3.2 -0.6,0.7 -1.3,1.1 -2,1.1 -0.9,0 -1.7,-0.3 -2.3,-0.8 -0.7,-0.6 -1,-1.5 -1,-2.7 0,-1.7 0.3,-3.9 0.9,-6.5 0.6,-2.6 1.5,-5.9 2.6,-9.8 0.6,-1.8 1.1,-3.6 1.7,-5.4 0.6,-1.8 1.1,-3.5 1.6,-5 0.5,-1.5 0.9,-2.9 1.3,-4.1 0.4,-1.2 0.6,-2.1 0.7,-2.8 0.1,-0.3 0.2,-1 0.3,-2 0.1,-1 0.2,-2.1 0.4,-3.4 0.2,-1.3 0.3,-2.7 0.5,-4.1 0.2,-1.5 0.4,-2.8 0.5,-4 0.2,-0.9 0.3,-1.9 0.5,-3 0.2,-1.1 0.5,-2.2 0.9,-3.1 0.4,-1 1,-1.8 1.7,-2.5 0.7,-0.7 1.6,-1 2.7,-1 1.2,0 2,0.4 2.4,1.1 0.4,0.7 0.6,1.6 0.5,2.6 -0.1,1 -0.2,2.1 -0.5,3.2 -0.3,1.1 -0.6,2.1 -0.9,2.9 -0.8,2.5 -1.6,4.8 -2.5,6.7 -0.9,1.9 -1.7,4 -2.4,6.2 -0.6,1.5 -0.8,2.9 -0.8,4.1 0,2.2 0.7,3.8 2,5 1.4,1.2 3,1.7 4.9,1.7 1.5,0 3,-0.5 4.4,-1.6 1.4,-1.1 2.7,-2.4 3.9,-3.9 1.2,-1.5 2.2,-3.1 3,-4.9 0.8,-1.7 1.4,-3.3 1.8,-4.6 0.1,-0.2 0.2,-0.6 0.3,-1.4 0.2,-0.8 0.3,-1.7 0.5,-2.7 0.2,-1 0.4,-2 0.6,-3.1 0.2,-1.1 0.4,-2 0.5,-2.7 0.2,-0.8 0.3,-1.6 0.5,-2.6 0.2,-1 0.5,-1.9 0.9,-2.8 0.4,-0.9 1,-1.6 1.6,-2.2 0.7,-0.6 1.5,-0.9 2.6,-0.9 1.3,0 2.1,0.4 2.6,1.1 0.4,0.7 0.6,1.6 0.6,2.6 -0.1,1 -0.2,2 -0.5,3 -0.3,1 -0.5,1.8 -0.7,2.4 -0.8,2.5 -1.6,4.7 -2.4,6.7 -0.8,2 -1.5,3.8 -2.2,5.2 -0.6,1.5 -1.1,2.6 -1.5,3.5 -0.4,0.9 -0.6,1.5 -0.6,1.8 0,2.6 0.6,4.5 1.7,5.6 1.1,1.1 2.3,1.7 3.6,1.7 2.2,0 3.9,-0.7 5.2,-2 1.3,-1.4 2.3,-3.9 2.9,-6.9 l 0.8,0 c 0.2,2.9 -0.1,5.3 -0.6,7.3 z"
|
||||
id="path15"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#2e3192" /></svg>
|
||||
|
After Width: | Height: | Size: 7.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 253 KiB |
@@ -1,11 +1,4 @@
|
||||
.\" micro manual page - micro(1)
|
||||
.\"
|
||||
.\" Copyright © 2017 Zachary Yedidia <zyedidia@gmail.com>
|
||||
.\" Copyright © 2017 Collin Warren <anatoly@somethinghub.com>
|
||||
.\"
|
||||
.\" This document is provided under the same licensing as micro.
|
||||
.\" See \usr\share\doc\micro\LICENSE for more information.
|
||||
.TH micro 1 "2017-03-28"
|
||||
.TH micro 1 "2020-02-10"
|
||||
.SH NAME
|
||||
micro \- A modern and intuitive terminal-based text editor
|
||||
.SH SYNOPSIS
|
||||
@@ -22,39 +15,96 @@ As the name indicates, micro aims to be somewhat of a successor to the nano edit
|
||||
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
|
||||
|
||||
.SH OPTIONS
|
||||
.PP
|
||||
\-clean
|
||||
.RS 4
|
||||
Cleans the configuration directory
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-config-dir dir
|
||||
.RS 4
|
||||
Specify a custom location for the configuration directory
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-startpos LINE,COL
|
||||
[FILE]:LINE:COL
|
||||
.RS 4
|
||||
Specify a line and column to start the cursor at when opening a buffer
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-options
|
||||
.RS 4
|
||||
Show all option help
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-debug
|
||||
.RS 4
|
||||
Enable debug mode (enables logging to ./log.txt)
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-version
|
||||
.RS 4
|
||||
Show the version number and information
|
||||
.RE
|
||||
|
||||
Micro's plugin's can be managed at the command line with the following commands.
|
||||
.RS 4
|
||||
|
||||
.PP
|
||||
\-plugin remove [PLUGIN]...
|
||||
.RS 4
|
||||
Remove plugin(s)
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-plugin update [PLUGIN]...
|
||||
.RS 4
|
||||
Update plugin(s) (if no argument is given, updates all plugins)
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-plugin search [PLUGIN]...
|
||||
.RS 4
|
||||
Search for a plugin
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-plugin list
|
||||
.RS 4
|
||||
List installed plugins
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-plugin available
|
||||
.RS 4
|
||||
List available plugins
|
||||
.RE
|
||||
.RE
|
||||
|
||||
Micro's options can also be set via command line arguments for quick
|
||||
adjustments. For real configuration, please use the settings.json
|
||||
file (see 'help options').
|
||||
.RS 4
|
||||
|
||||
.PP
|
||||
\-option value
|
||||
.RS 4
|
||||
Set `option` to `value` for this session
|
||||
For example: `micro -syntax off file.c`
|
||||
.RE
|
||||
|
||||
|
||||
.SH CONFIGURATION
|
||||
|
||||
Micro uses
|
||||
\fI$XDG_CONFIG_HOME/micro\fR
|
||||
for configuration by default. If it is not set, micro uses ~/.config/micro.
|
||||
Two main configuration files are settings.json, containing the user's
|
||||
settings, and bindings.json, containing the user's custom keybindings.
|
||||
|
||||
.SH ENVIRONMENT
|
||||
Micro's behaviour can be changed by setting environment variables, of which there is currently only one:
|
||||
\fIMICRO_TRUECOLOR\fR.
|
||||
When MICRO_TRUECOLOR is set to 1, micro will attempt to treat your terminal as a true-color terminal and will be able to make full use of the true-color colorschemes that are included with micro. If MICRO_TRUECOLOR is not set or is set to 0, then micro will only make use of 256 color features and will internally map true-color colorschemes to the nearest colors available. For more information see micro's documentation.
|
||||
Micro uses $MICRO_CONFIG_HOME as the configuration directory.
|
||||
If this environment variable is not set, it uses $XDG_CONFIG_HOME/micro instead.
|
||||
If that environment variable is not set, it uses ~/.config/micro as the configuration directory.
|
||||
In the documentation, we use ~/.config/micro to refer to the configuration directory
|
||||
(even if it may in fact be somewhere else if you have set either of the above environment variables).
|
||||
|
||||
.SH NOTICE
|
||||
This manpage is intended only to serve as a quick guide to the invocation of
|
||||
@@ -69,5 +119,5 @@ and to report any newly encountered bugs you may find. We strive to correct
|
||||
bugs as swiftly as possible.
|
||||
|
||||
.SH COPYRIGHT
|
||||
Copyright \(co 2017 Zachary Yedidia, Collin Warren, et al.
|
||||
Copyright \(co 2020 Zachary Yedidia, et al.
|
||||
See /usr/share/doc/micro/LICENSE and /usr/share/doc/micro/AUTHORS for more information.
|
||||
|
||||
146
cmd/micro/clean.go
Normal file
146
cmd/micro/clean.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
)
|
||||
|
||||
func shouldContinue() bool {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Print("Continue [Y/n]: ")
|
||||
text, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return false
|
||||
}
|
||||
|
||||
if len(text) <= 1 {
|
||||
// default continue
|
||||
return true
|
||||
}
|
||||
|
||||
return strings.ToLower(text)[0] == 'y'
|
||||
}
|
||||
|
||||
// CleanConfig performs cleanup in the user's configuration directory
|
||||
func CleanConfig() {
|
||||
fmt.Println("Cleaning your configuration directory at", config.ConfigDir)
|
||||
fmt.Printf("Please consider backing up %s before continuing\n", config.ConfigDir)
|
||||
|
||||
if !shouldContinue() {
|
||||
fmt.Println("Stopping early")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Cleaning default settings")
|
||||
config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
|
||||
|
||||
// detect unused options
|
||||
var unusedOptions []string
|
||||
defaultSettings := config.DefaultAllSettings()
|
||||
for k := range config.GlobalSettings {
|
||||
if _, ok := defaultSettings[k]; !ok {
|
||||
valid := false
|
||||
for _, p := range config.Plugins {
|
||||
if strings.HasPrefix(k, p.Name+".") || k == p.Name {
|
||||
valid = true
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
unusedOptions = append(unusedOptions, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(unusedOptions) > 0 {
|
||||
fmt.Println("The following options are unused:")
|
||||
|
||||
sort.Strings(unusedOptions)
|
||||
|
||||
for _, s := range unusedOptions {
|
||||
fmt.Printf("%s (value: %v)\n", s, config.GlobalSettings[s])
|
||||
}
|
||||
|
||||
fmt.Printf("These options will be removed from %s\n", filepath.Join(config.ConfigDir, "settings.json"))
|
||||
|
||||
if shouldContinue() {
|
||||
for _, s := range unusedOptions {
|
||||
delete(config.GlobalSettings, s)
|
||||
}
|
||||
|
||||
err := config.OverwriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
|
||||
if err != nil {
|
||||
fmt.Println("Error writing settings.json file: " + err.Error())
|
||||
}
|
||||
|
||||
fmt.Println("Removed unused options")
|
||||
fmt.Print("\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
// detect incorrectly formatted buffer/ files
|
||||
files, err := ioutil.ReadDir(filepath.Join(config.ConfigDir, "buffers"))
|
||||
if err == nil {
|
||||
var badFiles []string
|
||||
var buffer buffer.SerializedBuffer
|
||||
for _, f := range files {
|
||||
fname := filepath.Join(config.ConfigDir, "buffers", f.Name())
|
||||
file, e := os.Open(fname)
|
||||
|
||||
if e == nil {
|
||||
defer file.Close()
|
||||
|
||||
decoder := gob.NewDecoder(file)
|
||||
err = decoder.Decode(&buffer)
|
||||
|
||||
if err != nil && f.Name() != "history" {
|
||||
badFiles = append(badFiles, fname)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(badFiles) > 0 {
|
||||
fmt.Printf("Detected %d files with an invalid format in %s\n", len(badFiles), filepath.Join(config.ConfigDir, "buffers"))
|
||||
fmt.Println("These files store cursor and undo history.")
|
||||
fmt.Printf("Removing badly formatted files in %s\n", filepath.Join(config.ConfigDir, "buffers"))
|
||||
|
||||
if shouldContinue() {
|
||||
for _, f := range badFiles {
|
||||
err := os.Remove(f)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Removed badly formatted files")
|
||||
fmt.Print("\n\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// detect plugins/ directory
|
||||
plugins := filepath.Join(config.ConfigDir, "plugins")
|
||||
if stat, err := os.Stat(plugins); err == nil && stat.IsDir() {
|
||||
fmt.Printf("Found directory %s\n", plugins)
|
||||
fmt.Printf("Plugins should now be stored in %s\n", filepath.Join(config.ConfigDir, "plug"))
|
||||
fmt.Printf("Removing %s\n", plugins)
|
||||
|
||||
if shouldContinue() {
|
||||
os.RemoveAll(plugins)
|
||||
}
|
||||
|
||||
fmt.Print("\n\n")
|
||||
}
|
||||
|
||||
fmt.Println("Done cleaning")
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// NullWriter simply sends writes into the void
|
||||
|
||||
@@ -6,14 +6,14 @@ import (
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
"github.com/zyedidia/micro/internal/action"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/display"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/shell"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/action"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -21,6 +21,7 @@ func init() {
|
||||
ulua.L.SetGlobal("import", luar.New(ulua.L, LuaImport))
|
||||
}
|
||||
|
||||
// LuaImport is meant to be called from lua by a plugin and will import the given micro package
|
||||
func LuaImport(pkg string) *lua.LTable {
|
||||
switch pkg {
|
||||
case "micro":
|
||||
@@ -46,6 +47,15 @@ func luaImportMicro() *lua.LTable {
|
||||
ulua.L.SetField(pkg, "InfoBar", luar.New(ulua.L, action.GetInfoBar))
|
||||
ulua.L.SetField(pkg, "Log", luar.New(ulua.L, log.Println))
|
||||
ulua.L.SetField(pkg, "SetStatusInfoFn", luar.New(ulua.L, display.SetStatusInfoFnLua))
|
||||
ulua.L.SetField(pkg, "CurPane", luar.New(ulua.L, func() action.Pane {
|
||||
return action.MainTab().CurPane()
|
||||
}))
|
||||
ulua.L.SetField(pkg, "CurTab", luar.New(ulua.L, func() *action.Tab {
|
||||
return action.MainTab()
|
||||
}))
|
||||
ulua.L.SetField(pkg, "Tabs", luar.New(ulua.L, func() *action.TabList {
|
||||
return action.Tabs
|
||||
}))
|
||||
|
||||
return pkg
|
||||
}
|
||||
@@ -53,7 +63,7 @@ func luaImportMicro() *lua.LTable {
|
||||
func luaImportMicroConfig() *lua.LTable {
|
||||
pkg := ulua.L.NewTable()
|
||||
|
||||
ulua.L.SetField(pkg, "MakeCommand", luar.New(ulua.L, action.LuaMakeCommand))
|
||||
ulua.L.SetField(pkg, "MakeCommand", luar.New(ulua.L, action.MakeCommand))
|
||||
ulua.L.SetField(pkg, "FileComplete", luar.New(ulua.L, buffer.FileComplete))
|
||||
ulua.L.SetField(pkg, "HelpComplete", luar.New(ulua.L, action.HelpComplete))
|
||||
ulua.L.SetField(pkg, "OptionComplete", luar.New(ulua.L, action.OptionComplete))
|
||||
@@ -62,16 +72,21 @@ func luaImportMicroConfig() *lua.LTable {
|
||||
ulua.L.SetField(pkg, "TryBindKey", luar.New(ulua.L, action.TryBindKey))
|
||||
ulua.L.SetField(pkg, "Reload", luar.New(ulua.L, action.ReloadConfig))
|
||||
ulua.L.SetField(pkg, "AddRuntimeFileFromMemory", luar.New(ulua.L, config.PluginAddRuntimeFileFromMemory))
|
||||
ulua.L.SetField(pkg, "AddRuntimeFilesFromDirectory", luar.New(ulua.L, config.PluginAddRuntimeFileFromMemory))
|
||||
ulua.L.SetField(pkg, "AddRuntimeFilesFromDirectory", luar.New(ulua.L, config.PluginAddRuntimeFilesFromDirectory))
|
||||
ulua.L.SetField(pkg, "AddRuntimeFile", luar.New(ulua.L, config.PluginAddRuntimeFile))
|
||||
ulua.L.SetField(pkg, "ListRuntimeFiles", luar.New(ulua.L, config.PluginListRuntimeFiles))
|
||||
ulua.L.SetField(pkg, "ReadRuntimeFile", luar.New(ulua.L, config.PluginReadRuntimeFile))
|
||||
ulua.L.SetField(pkg, "NewRTFiletype", luar.New(ulua.L, config.NewRTFiletype))
|
||||
ulua.L.SetField(pkg, "RTColorscheme", luar.New(ulua.L, config.RTColorscheme))
|
||||
ulua.L.SetField(pkg, "RTSyntax", luar.New(ulua.L, config.RTSyntax))
|
||||
ulua.L.SetField(pkg, "RTHelp", luar.New(ulua.L, config.RTHelp))
|
||||
ulua.L.SetField(pkg, "RTPlugin", luar.New(ulua.L, config.RTPlugin))
|
||||
ulua.L.SetField(pkg, "RegisterCommonOption", luar.New(ulua.L, config.RegisterCommonOption))
|
||||
ulua.L.SetField(pkg, "RegisterGlobalOption", luar.New(ulua.L, config.RegisterGlobalOption))
|
||||
ulua.L.SetField(pkg, "RegisterCommonOption", luar.New(ulua.L, config.RegisterCommonOptionPlug))
|
||||
ulua.L.SetField(pkg, "RegisterGlobalOption", luar.New(ulua.L, config.RegisterGlobalOptionPlug))
|
||||
ulua.L.SetField(pkg, "GetGlobalOption", luar.New(ulua.L, config.GetGlobalOption))
|
||||
ulua.L.SetField(pkg, "SetGlobalOption", luar.New(ulua.L, action.SetGlobalOption))
|
||||
ulua.L.SetField(pkg, "SetGlobalOptionNative", luar.New(ulua.L, action.SetGlobalOptionNative))
|
||||
ulua.L.SetField(pkg, "ConfigDir", luar.New(ulua.L, config.ConfigDir))
|
||||
|
||||
return pkg
|
||||
}
|
||||
@@ -110,10 +125,15 @@ func luaImportMicroBuffer() *lua.LTable {
|
||||
ulua.L.SetField(pkg, "BTScratch", luar.New(ulua.L, buffer.BTScratch.Kind))
|
||||
ulua.L.SetField(pkg, "BTRaw", luar.New(ulua.L, buffer.BTRaw.Kind))
|
||||
ulua.L.SetField(pkg, "BTInfo", luar.New(ulua.L, buffer.BTInfo.Kind))
|
||||
ulua.L.SetField(pkg, "NewBuffer", luar.New(ulua.L, func(text, path string) *buffer.Buffer {
|
||||
return buffer.NewBufferFromString(text, path, buffer.BTDefault)
|
||||
}))
|
||||
ulua.L.SetField(pkg, "NewBufferFromFile", luar.New(ulua.L, func(path string) (*buffer.Buffer, error) {
|
||||
return buffer.NewBufferFromFile(path, buffer.BTDefault)
|
||||
}))
|
||||
ulua.L.SetField(pkg, "ByteOffset", luar.New(ulua.L, buffer.ByteOffset))
|
||||
ulua.L.SetField(pkg, "Log", luar.New(ulua.L, buffer.WriteLog))
|
||||
ulua.L.SetField(pkg, "LogBuf", luar.New(ulua.L, buffer.GetLogBuf))
|
||||
|
||||
return pkg
|
||||
}
|
||||
@@ -124,6 +144,11 @@ func luaImportMicroUtil() *lua.LTable {
|
||||
ulua.L.SetField(pkg, "RuneAt", luar.New(ulua.L, util.LuaRuneAt))
|
||||
ulua.L.SetField(pkg, "GetLeadingWhitespace", luar.New(ulua.L, util.LuaGetLeadingWhitespace))
|
||||
ulua.L.SetField(pkg, "IsWordChar", luar.New(ulua.L, util.LuaIsWordChar))
|
||||
ulua.L.SetField(pkg, "String", luar.New(ulua.L, util.String))
|
||||
ulua.L.SetField(pkg, "CharacterCountInString", luar.New(ulua.L, util.CharacterCountInString))
|
||||
ulua.L.SetField(pkg, "RuneStr", luar.New(ulua.L, func(r rune) string {
|
||||
return string(r)
|
||||
}))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
@@ -5,16 +5,21 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
isatty "github.com/mattn/go-isatty"
|
||||
"github.com/zyedidia/micro/internal/action"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/shell"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/v2/internal/action"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -27,22 +32,43 @@ var (
|
||||
flagVersion = flag.Bool("version", false, "Show the version number and information")
|
||||
flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
|
||||
flagOptions = flag.Bool("options", false, "Show all option help")
|
||||
flagDebug = flag.Bool("debug", false, "Enable debug mode (prints debug info to ./log.txt)")
|
||||
flagPlugin = flag.String("plugin", "", "Plugin command")
|
||||
flagClean = flag.Bool("clean", false, "Clean configuration directory")
|
||||
optionFlags map[string]*string
|
||||
)
|
||||
|
||||
func InitFlags() {
|
||||
flag.Usage = func() {
|
||||
fmt.Println("Usage: micro [OPTIONS] [FILE]...")
|
||||
fmt.Println("-clean")
|
||||
fmt.Println(" \tCleans the configuration directory")
|
||||
fmt.Println("-config-dir dir")
|
||||
fmt.Println(" \tSpecify a custom location for the configuration directory")
|
||||
fmt.Println("[FILE]:LINE:COL")
|
||||
fmt.Println("[FILE]:LINE:COL (if the `parsecursor` option is enabled)")
|
||||
fmt.Println("+LINE:COL")
|
||||
fmt.Println(" \tSpecify a line and column to start the cursor at when opening a buffer")
|
||||
fmt.Println(" \tThis can also be done by opening file:LINE:COL")
|
||||
fmt.Println("-options")
|
||||
fmt.Println(" \tShow all option help")
|
||||
fmt.Println("-debug")
|
||||
fmt.Println(" \tEnable debug mode (enables logging to ./log.txt)")
|
||||
fmt.Println("-version")
|
||||
fmt.Println(" \tShow the version number and information")
|
||||
|
||||
fmt.Print("\nMicro's plugin's can be managed at the command line with the following commands.\n")
|
||||
fmt.Println("-plugin install [PLUGIN]...")
|
||||
fmt.Println(" \tInstall plugin(s)")
|
||||
fmt.Println("-plugin remove [PLUGIN]...")
|
||||
fmt.Println(" \tRemove plugin(s)")
|
||||
fmt.Println("-plugin update [PLUGIN]...")
|
||||
fmt.Println(" \tUpdate plugin(s) (if no argument is given, updates all plugins)")
|
||||
fmt.Println("-plugin search [PLUGIN]...")
|
||||
fmt.Println(" \tSearch for a plugin")
|
||||
fmt.Println("-plugin list")
|
||||
fmt.Println(" \tList installed plugins")
|
||||
fmt.Println("-plugin available")
|
||||
fmt.Println(" \tList available plugins")
|
||||
|
||||
fmt.Print("\nMicro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the settings.json\nfile (see 'help options').\n\n")
|
||||
fmt.Println("-option value")
|
||||
fmt.Println(" \tSet `option` to `value` for this session")
|
||||
@@ -81,6 +107,27 @@ func InitFlags() {
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if util.Debug == "OFF" && *flagDebug {
|
||||
util.Debug = "ON"
|
||||
}
|
||||
}
|
||||
|
||||
// DoPluginFlags parses and executes any flags that require LoadAllPlugins (-plugin and -clean)
|
||||
func DoPluginFlags() {
|
||||
if *flagClean || *flagPlugin != "" {
|
||||
config.LoadAllPlugins()
|
||||
|
||||
if *flagPlugin != "" {
|
||||
args := flag.Args()
|
||||
|
||||
config.PluginCommand(os.Stdout, *flagPlugin, args)
|
||||
} else if *flagClean {
|
||||
CleanConfig()
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadInput determines which files should be loaded into buffers
|
||||
@@ -103,11 +150,45 @@ func LoadInput() []*buffer.Buffer {
|
||||
args := flag.Args()
|
||||
buffers := make([]*buffer.Buffer, 0, len(args))
|
||||
|
||||
if len(args) > 0 {
|
||||
btype := buffer.BTDefault
|
||||
if !isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
btype = buffer.BTStdout
|
||||
}
|
||||
|
||||
files := make([]string, 0, len(args))
|
||||
flagStartPos := buffer.Loc{-1, -1}
|
||||
flagr := regexp.MustCompile(`^\+(\d+)(?::(\d+))?$`)
|
||||
for _, a := range args {
|
||||
match := flagr.FindStringSubmatch(a)
|
||||
if len(match) == 3 && match[2] != "" {
|
||||
line, err := strconv.Atoi(match[1])
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
continue
|
||||
}
|
||||
col, err := strconv.Atoi(match[2])
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
continue
|
||||
}
|
||||
flagStartPos = buffer.Loc{col - 1, line - 1}
|
||||
} else if len(match) == 3 && match[2] == "" {
|
||||
line, err := strconv.Atoi(match[1])
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
continue
|
||||
}
|
||||
flagStartPos = buffer.Loc{0, line - 1}
|
||||
} else {
|
||||
files = append(files, a)
|
||||
}
|
||||
}
|
||||
|
||||
if len(files) > 0 {
|
||||
// Option 1
|
||||
// We go through each file and load it
|
||||
for i := 0; i < len(args); i++ {
|
||||
buf, err := buffer.NewBufferFromFile(args[i], buffer.BTDefault)
|
||||
for i := 0; i < len(files); i++ {
|
||||
buf, err := buffer.NewBufferFromFileAtLoc(files[i], btype, flagStartPos)
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
continue
|
||||
@@ -124,17 +205,22 @@ func LoadInput() []*buffer.Buffer {
|
||||
screen.TermMessage("Error reading from stdin: ", err)
|
||||
input = []byte{}
|
||||
}
|
||||
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, buffer.BTDefault))
|
||||
buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
|
||||
} else {
|
||||
// Option 3, just open an empty buffer
|
||||
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, buffer.BTDefault))
|
||||
buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
|
||||
}
|
||||
|
||||
return buffers
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer os.Exit(0)
|
||||
defer func() {
|
||||
if util.Stdout.Len() > 0 {
|
||||
fmt.Fprint(os.Stdout, util.Stdout.String())
|
||||
}
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
// runtime.SetCPUProfileRate(400)
|
||||
// f, _ := os.Create("micro.prof")
|
||||
@@ -143,10 +229,10 @@ func main() {
|
||||
|
||||
var err error
|
||||
|
||||
InitLog()
|
||||
|
||||
InitFlags()
|
||||
|
||||
InitLog()
|
||||
|
||||
err = config.InitConfigDir(*flagConfigDir)
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
@@ -157,7 +243,10 @@ func main() {
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
config.InitGlobalSettings()
|
||||
err = config.InitGlobalSettings()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
// flag options
|
||||
for k, v := range optionFlags {
|
||||
@@ -171,28 +260,10 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
action.InitBindings()
|
||||
action.InitCommands()
|
||||
|
||||
err = config.InitColorscheme()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
err = config.LoadAllPlugins()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
err = config.RunPluginFn("init")
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
DoPluginFlags()
|
||||
|
||||
screen.Init()
|
||||
|
||||
// If we have an error, we can exit cleanly and not completely
|
||||
// mess up the terminal being worked in
|
||||
// In other words we need to shut down tcell before the program crashes
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
screen.Screen.Fini()
|
||||
@@ -207,13 +278,39 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
err = config.LoadAllPlugins()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
action.InitBindings()
|
||||
action.InitCommands()
|
||||
|
||||
err = config.InitColorscheme()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
b := LoadInput()
|
||||
|
||||
if len(b) == 0 {
|
||||
// No buffers to open
|
||||
screen.Screen.Fini()
|
||||
runtime.Goexit()
|
||||
}
|
||||
|
||||
action.InitTabs(b)
|
||||
action.InitGlobals()
|
||||
|
||||
err = config.RunPluginFn("init")
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
events = make(chan tcell.Event)
|
||||
|
||||
// Here is the event loop which runs in a separate thread
|
||||
go func() {
|
||||
events = make(chan tcell.Event)
|
||||
for {
|
||||
screen.Lock()
|
||||
e := screen.Screen.PollEvent()
|
||||
@@ -224,38 +321,73 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
// clear the drawchan so we don't redraw excessively
|
||||
// if someone requested a redraw before we started displaying
|
||||
for len(screen.DrawChan()) > 0 {
|
||||
<-screen.DrawChan()
|
||||
}
|
||||
|
||||
// wait for initial resize event
|
||||
select {
|
||||
case event := <-events:
|
||||
action.Tabs.HandleEvent(event)
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
// time out after 10ms
|
||||
}
|
||||
|
||||
// Since this loop is very slow (waits for user input every time) it's
|
||||
// okay to be inefficient and run it via a function every time
|
||||
// We do this so we can recover from panics without crashing the editor
|
||||
for {
|
||||
// Display everything
|
||||
screen.Screen.Fill(' ', config.DefStyle)
|
||||
screen.Screen.HideCursor()
|
||||
action.Tabs.Display()
|
||||
for _, ep := range action.MainTab().Panes {
|
||||
ep.Display()
|
||||
}
|
||||
action.MainTab().Display()
|
||||
action.InfoBar.Display()
|
||||
screen.Screen.Show()
|
||||
|
||||
var event tcell.Event
|
||||
|
||||
// Check for new events
|
||||
select {
|
||||
case f := <-shell.Jobs:
|
||||
// If a new job has finished while running in the background we should execute the callback
|
||||
f.Function(f.Output, f.Args...)
|
||||
case <-config.Autosave:
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
b.Save()
|
||||
}
|
||||
case <-shell.CloseTerms:
|
||||
case event = <-events:
|
||||
case <-screen.DrawChan:
|
||||
}
|
||||
|
||||
if action.InfoBar.HasPrompt {
|
||||
action.InfoBar.HandleEvent(event)
|
||||
} else {
|
||||
action.Tabs.HandleEvent(event)
|
||||
}
|
||||
DoEvent()
|
||||
}
|
||||
}
|
||||
|
||||
// DoEvent runs the main action loop of the editor
|
||||
func DoEvent() {
|
||||
var event tcell.Event
|
||||
|
||||
// recover from errors without crashing the editor
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if e, ok := err.(*lua.ApiError); ok {
|
||||
screen.TermMessage("Lua API error:", e)
|
||||
} else {
|
||||
screen.TermMessage("Micro encountered an error:", errors.Wrap(err, 2).ErrorStack(), "\nIf you can reproduce this error, please report it at https://github.com/zyedidia/micro/issues")
|
||||
}
|
||||
}
|
||||
}()
|
||||
// Display everything
|
||||
screen.Screen.Fill(' ', config.DefStyle)
|
||||
screen.Screen.HideCursor()
|
||||
action.Tabs.Display()
|
||||
for _, ep := range action.MainTab().Panes {
|
||||
ep.Display()
|
||||
}
|
||||
action.MainTab().Display()
|
||||
action.InfoBar.Display()
|
||||
screen.Screen.Show()
|
||||
|
||||
// Check for new events
|
||||
select {
|
||||
case f := <-shell.Jobs:
|
||||
// If a new job has finished while running in the background we should execute the callback
|
||||
f.Function(f.Output, f.Args)
|
||||
case <-config.Autosave:
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
b.Save()
|
||||
}
|
||||
case <-shell.CloseTerms:
|
||||
case event = <-events:
|
||||
case <-screen.DrawChan():
|
||||
for len(screen.DrawChan()) > 0 {
|
||||
<-screen.DrawChan()
|
||||
}
|
||||
}
|
||||
|
||||
if action.InfoBar.HasPrompt {
|
||||
action.InfoBar.HandleEvent(event)
|
||||
} else {
|
||||
action.Tabs.HandleEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
50
go.mod
50
go.mod
@@ -1,38 +1,32 @@
|
||||
module github.com/zyedidia/micro
|
||||
module github.com/zyedidia/micro/v2
|
||||
|
||||
require (
|
||||
github.com/blang/semver v3.5.1+incompatible
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/flynn/json5 v0.0.0-20160717195620-7620272ed633
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
github.com/go-errors/errors v1.0.1
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08 // indirect
|
||||
github.com/mattn/go-isatty v0.0.4
|
||||
github.com/mattn/go-runewidth v0.0.4
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/mattn/go-isatty v0.0.11
|
||||
github.com/mattn/go-runewidth v0.0.7
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/npat-efault/poller v2.0.0+incompatible // indirect
|
||||
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d // indirect
|
||||
github.com/sergi/go-diff v1.0.0
|
||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 // indirect
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
|
||||
github.com/stretchr/testify v1.3.0
|
||||
github.com/yuin/gopher-lua v0.0.0-20190125051437-7b9317363aa9
|
||||
github.com/zyedidia/clipboard v0.0.0-20180718195219-bd31d747117d
|
||||
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff
|
||||
github.com/sergi/go-diff v1.1.0
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
|
||||
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834
|
||||
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3
|
||||
github.com/zyedidia/poller v2.0.0+incompatible // indirect
|
||||
github.com/zyedidia/pty v1.1.2-0.20180126010845-30364665a244 // indirect
|
||||
github.com/zyedidia/tcell v0.0.0-20191219170756-59b50b23fa9b
|
||||
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5
|
||||
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d
|
||||
github.com/zyedidia/pty v2.0.0+incompatible // indirect
|
||||
github.com/zyedidia/tcell v1.4.7
|
||||
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415
|
||||
golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9 // indirect
|
||||
golang.org/x/text v0.3.0
|
||||
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
golang.org/x/text v0.3.2
|
||||
gopkg.in/sourcemap.v1 v1.0.5 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
layeh.com/gopher-luar v1.0.4
|
||||
gopkg.in/yaml.v2 v2.2.7
|
||||
layeh.com/gopher-luar v1.0.7
|
||||
)
|
||||
|
||||
replace github.com/kballard/go-shellquote => github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655
|
||||
|
||||
replace github.com/mattn/go-runewidth => github.com/p-e-w/go-runewidth v0.0.10-0.20200613030200-3e1705c5c059
|
||||
|
||||
go 1.11
|
||||
|
||||
94
go.sum
94
go.sum
@@ -1,79 +1,77 @@
|
||||
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/flynn/json5 v0.0.0-20160717195620-7620272ed633 h1:xJMmr4GMYIbALX5edyoDIOQpc2bOQTeJiWMeCl9lX/8=
|
||||
github.com/flynn/json5 v0.0.0-20160717195620-7620272ed633/go.mod h1:NJDK3/o7abx6PP54EOe0G0n0RLmhCo9xv61gUYpI0EY=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08 h1:5MnxBC15uMxFv5FY/J/8vzyaBiArCOkMdFT9Jsw78iY=
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
|
||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/npat-efault/poller v2.0.0+incompatible h1:jtTdXWKgN5kDK41ts8hoY1rvTEi0K08MTB8/bRO9MqE=
|
||||
github.com/npat-efault/poller v2.0.0+incompatible/go.mod h1:lni01B89P8PtVpwlAhdhK1niN5rPkDGGpGGgBJzpSgo=
|
||||
github.com/p-e-w/go-runewidth v0.0.10-0.20200613030200-3e1705c5c059 h1:/+h2b6i15wh4EWsFkfdNdBE1jjGA872tpXEyhPM5aYg=
|
||||
github.com/p-e-w/go-runewidth v0.0.10-0.20200613030200-3e1705c5c059/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d h1:1VUlQbCfkoSGv7qP7Y+ro3ap1P1pPZxgdGVqiTVy5C4=
|
||||
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
|
||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff h1:+6NUiITWwE5q1KO6SAfUX918c+Tab0+tGAM/mtdlUyA=
|
||||
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/yuin/gopher-lua v0.0.0-20190125051437-7b9317363aa9 h1:Wy3fAQLBPP0JSWdq3kBnmbFgXDHcyhtPpd+8kENV7mU=
|
||||
github.com/yuin/gopher-lua v0.0.0-20190125051437-7b9317363aa9/go.mod h1:fFiAh+CowNFr0NK5VASokuwKwkbacRmHsVA7Yb1Tqac=
|
||||
github.com/zyedidia/clipboard v0.0.0-20180718195219-bd31d747117d h1:Lhqt2eo+rgM8aswvM7nTtAMVm8ARPWzkE9n6eZDOccY=
|
||||
github.com/zyedidia/clipboard v0.0.0-20180718195219-bd31d747117d/go.mod h1:WDk3p8GiZV9+xFWlSo8qreeoLhW6Ik692rqXk+cNeRY=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
|
||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834 h1:0nOfq3JwYRiY3+nwfWVQYEaXDmGCQgj3RKoqTifLzP4=
|
||||
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834/go.mod h1:zykFnZUXX0ErxqvYLUFEq7QDJKId8rmh2FgD0/Y8cjA=
|
||||
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3 h1:oMHjjTLfGXVuyOQBYj5/td9WC0mw4g1xDBPovIqmHew=
|
||||
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3/go.mod h1:YKbIYP//Eln8eDgAJGI3IDvR3s4Tv9Z9TGIOumiyQ5c=
|
||||
github.com/zyedidia/poller v2.0.0+incompatible h1:DMOvB0EXz2JTokqOKfxPWN/8xXFJbO+m4vNhMkOY7Lo=
|
||||
github.com/zyedidia/poller v2.0.0+incompatible/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
|
||||
github.com/zyedidia/pty v1.1.2-0.20180126010845-30364665a244 h1:DZ7mZvUV5+oXeXV1E1t6ZIXRihHYyqYVIOSA+RGo88A=
|
||||
github.com/zyedidia/pty v1.1.2-0.20180126010845-30364665a244/go.mod h1:4y9l9yJZNxRa7GB/fB+mmDmGkG3CqmzLf4vUxGGotEA=
|
||||
github.com/zyedidia/tcell v0.0.0-20190204041104-518c15c24302 h1:ruNSURcO81y+J+XnqrLLt+zxcdFtq8QNoZfWXSsybYQ=
|
||||
github.com/zyedidia/tcell v0.0.0-20190204041104-518c15c24302/go.mod h1:yXgdp23+aW8OMENYVBvpKoeiBtjaVWJ9HhpPDu6LBfM=
|
||||
github.com/zyedidia/tcell v0.0.0-20191219170756-59b50b23fa9b h1:cryFENlMxJJrkimVx/CUMFDCxC4vpmey2x3A3tAgTNM=
|
||||
github.com/zyedidia/tcell v0.0.0-20191219170756-59b50b23fa9b/go.mod h1:yXgdp23+aW8OMENYVBvpKoeiBtjaVWJ9HhpPDu6LBfM=
|
||||
github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655 h1:Z3RhH6hvcSx7eX6Q/pP6YVsgea/1eMDG99vtWwi3nK4=
|
||||
github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655/go.mod h1:1sTqqO+kcYzZp43M5VsJe1tns9IzlSeC9jB6c2+o/5Y=
|
||||
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5 h1:Zs6mpwXvlqpF9zHl5XaN0p5V4J9XvP+WBuiuXyIgqvc=
|
||||
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5/go.mod h1:c1r+Ob9tUTPB0FKWO1+x+Hsc/zNa45WdGq7Y38Ybip0=
|
||||
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d h1:zmDMkh22zXOB7gz8jFaI4GpI7llsPgzm38/jG0UgxjE=
|
||||
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d/go.mod h1:NDJSTTYWivnza6zkRapeX2/LwhKPEMQ7bJxqgDVT78I=
|
||||
github.com/zyedidia/poller v1.0.1 h1:Tt9S3AxAjXwWGNiC2TUdRJkQDZSzCBNVQ4xXiQ7440s=
|
||||
github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
|
||||
github.com/zyedidia/pty v2.0.0+incompatible h1:Ou5vXL6tvjst+RV8sUFISbuKDnUJPhnpygApMFGweqw=
|
||||
github.com/zyedidia/pty v2.0.0+incompatible/go.mod h1:4y9l9yJZNxRa7GB/fB+mmDmGkG3CqmzLf4vUxGGotEA=
|
||||
github.com/zyedidia/tcell v1.4.7 h1:bKXRjv8RglPyOFqofzUUJkrdsLs9p9mT89W2ShFFlco=
|
||||
github.com/zyedidia/tcell v1.4.7/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
|
||||
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415 h1:752dTQ5OatJ9M5ULK2+9lor+nzyZz+LYDo3WGngg3Rc=
|
||||
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415/go.mod h1:8leT8G0Cm8NoJHdrrKHyR9MirWoF4YW7pZh06B6H+1E=
|
||||
golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9 h1:lkiLiLBHGoH3XnqSLUIaBsilGMUjI+Uy2Xu2JLUtTas=
|
||||
golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 h1:FVCohIoYO7IJoDDVpV2pdq7SgrMH6wHnuTyrdrxJNoY=
|
||||
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
|
||||
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
layeh.com/gopher-luar v1.0.4 h1:BFgt94J/CXh4HkDcE2b7A7pBaVeQKEVfHEBRKL/K/Tc=
|
||||
layeh.com/gopher-luar v1.0.4/go.mod h1:N3rev/ttQd8yVluXaYsa0M/eknzRYWe+pxZ35ZFmaaI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
layeh.com/gopher-luar v1.0.7 h1:53iv6CCkRs5wyofZ+qVXcyAYQOIG52s6pt4xkqZdq7k=
|
||||
layeh.com/gopher-luar v1.0.7/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=
|
||||
|
||||
@@ -5,15 +5,14 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"github.com/zyedidia/clipboard"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/shell"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/pkg/shellwords"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -23,6 +22,8 @@ func (h *BufPane) ScrollUp(n int) {
|
||||
if v.StartLine >= n {
|
||||
v.StartLine -= n
|
||||
h.SetView(v)
|
||||
} else {
|
||||
v.StartLine = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +87,6 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
|
||||
h.Cursor.AddWordToSelection()
|
||||
} else {
|
||||
h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
||||
h.Cursor.CopySelection("primary")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ func (h *BufPane) CursorRight() bool {
|
||||
if tabstospaces && tabmovement {
|
||||
tabsize := int(h.Buf.Settings["tabsize"].(float64))
|
||||
line := h.Buf.LineBytes(h.Cursor.Y)
|
||||
if h.Cursor.X+tabsize < utf8.RuneCount(line) && util.IsSpaces(line[h.Cursor.X:h.Cursor.X+tabsize]) && util.IsBytesWhitespace(line[0:h.Cursor.X]) {
|
||||
if h.Cursor.X+tabsize < util.CharacterCount(line) && util.IsSpaces(line[h.Cursor.X:h.Cursor.X+tabsize]) && util.IsBytesWhitespace(line[0:h.Cursor.X]) {
|
||||
for i := 0; i < tabsize; i++ {
|
||||
h.Cursor.Right()
|
||||
}
|
||||
@@ -282,15 +282,31 @@ func (h *BufPane) SelectWordLeft() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// StartOfText moves the cursor to the start of the text of the line
|
||||
func (h *BufPane) StartOfText() bool {
|
||||
h.Cursor.Deselect(true)
|
||||
h.Cursor.StartOfText()
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// StartOfTextToggle toggles the cursor between the start of the text of the line
|
||||
// and the start of the line
|
||||
func (h *BufPane) StartOfTextToggle() bool {
|
||||
h.Cursor.Deselect(true)
|
||||
if h.Cursor.IsStartOfText() {
|
||||
h.Cursor.Start()
|
||||
} else {
|
||||
h.Cursor.StartOfText()
|
||||
}
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// StartOfLine moves the cursor to the start of the line
|
||||
func (h *BufPane) StartOfLine() bool {
|
||||
h.Cursor.Deselect(true)
|
||||
h.Cursor.StartOfText()
|
||||
// if h.Cursor.X != 0 {
|
||||
// h.Cursor.Start()
|
||||
// } else {
|
||||
// h.Cursor.StartOfText()
|
||||
// }
|
||||
h.Cursor.Start()
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
@@ -310,6 +326,33 @@ func (h *BufPane) SelectLine() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectToStartOfText selects to the start of the text on the current line
|
||||
func (h *BufPane) SelectToStartOfText() bool {
|
||||
if !h.Cursor.HasSelection() {
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
||||
}
|
||||
h.Cursor.StartOfText()
|
||||
h.Cursor.SelectTo(h.Cursor.Loc)
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectToStartOfTextToggle toggles the selection between the start of the text
|
||||
// on the current line and the start of the line
|
||||
func (h *BufPane) SelectToStartOfTextToggle() bool {
|
||||
if !h.Cursor.HasSelection() {
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
||||
}
|
||||
if h.Cursor.IsStartOfText() {
|
||||
h.Cursor.Start()
|
||||
} else {
|
||||
h.Cursor.StartOfText()
|
||||
}
|
||||
h.Cursor.SelectTo(h.Cursor.Loc)
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectToStartOfLine selects to the start of the current line
|
||||
func (h *BufPane) SelectToStartOfLine() bool {
|
||||
if !h.Cursor.HasSelection() {
|
||||
@@ -381,6 +424,7 @@ func (h *BufPane) CursorStart() bool {
|
||||
h.Cursor.Deselect(true)
|
||||
h.Cursor.X = 0
|
||||
h.Cursor.Y = 0
|
||||
h.Cursor.StoreVisualX()
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
@@ -441,7 +485,7 @@ func (h *BufPane) InsertNewline() bool {
|
||||
// Remove the whitespaces if keepautoindent setting is off
|
||||
if util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings["keepautoindent"].(bool) {
|
||||
line := h.Buf.LineBytes(h.Cursor.Y - 1)
|
||||
h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: utf8.RuneCount(line), Y: h.Cursor.Y - 1})
|
||||
h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: util.CharacterCount(line), Y: h.Cursor.Y - 1})
|
||||
}
|
||||
}
|
||||
h.Cursor.LastVisualX = h.Cursor.GetVisualX()
|
||||
@@ -466,7 +510,7 @@ func (h *BufPane) Backspace() bool {
|
||||
// tab (tabSize number of spaces)
|
||||
lineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)
|
||||
tabSize := int(h.Buf.Settings["tabsize"].(float64))
|
||||
if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 {
|
||||
if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && util.CharacterCount(lineStart)%tabSize == 0 {
|
||||
loc := h.Cursor.Loc
|
||||
h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
|
||||
} else {
|
||||
@@ -533,12 +577,14 @@ func (h *BufPane) IndentSelection() bool {
|
||||
tabsize := int(h.Buf.Settings["tabsize"].(float64))
|
||||
indentsize := len(h.Buf.IndentString(tabsize))
|
||||
for y := startY; y <= endY; y++ {
|
||||
h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
|
||||
if y == startY && start.X > 0 {
|
||||
h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
|
||||
}
|
||||
if y == endY {
|
||||
h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
|
||||
if len(h.Buf.LineBytes(y)) > 0 {
|
||||
h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
|
||||
if y == startY && start.X > 0 {
|
||||
h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
|
||||
}
|
||||
if y == endY {
|
||||
h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
|
||||
}
|
||||
}
|
||||
}
|
||||
h.Buf.RelocateCursors()
|
||||
@@ -549,6 +595,20 @@ func (h *BufPane) IndentSelection() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IndentLine moves the current line forward one indentation
|
||||
func (h *BufPane) IndentLine() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
return false
|
||||
}
|
||||
|
||||
tabsize := int(h.Buf.Settings["tabsize"].(float64))
|
||||
indentstr := h.Buf.IndentString(tabsize)
|
||||
h.Buf.Insert(buffer.Loc{X: 0, Y: h.Cursor.Y}, indentstr)
|
||||
h.Buf.RelocateCursors()
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// OutdentLine moves the current line back one indentation
|
||||
func (h *BufPane) OutdentLine() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
@@ -603,6 +663,16 @@ func (h *BufPane) Autocomplete() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if h.Cursor.X == 0 {
|
||||
return false
|
||||
}
|
||||
r := h.Cursor.RuneUnder(h.Cursor.X)
|
||||
prev := h.Cursor.RuneUnder(h.Cursor.X - 1)
|
||||
if !util.IsAutocomplete(prev) || !util.IsNonAlphaNumeric(r) {
|
||||
// don't autocomplete if cursor is on alpha numeric character (middle of a word)
|
||||
return false
|
||||
}
|
||||
|
||||
if b.HasSuggestions {
|
||||
b.CycleAutocomplete(true)
|
||||
return true
|
||||
@@ -610,11 +680,22 @@ func (h *BufPane) Autocomplete() bool {
|
||||
return b.Autocomplete(buffer.BufferComplete)
|
||||
}
|
||||
|
||||
// CycleAutocompleteBack cycles back in the autocomplete suggestion list
|
||||
func (h *BufPane) CycleAutocompleteBack() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
return false
|
||||
}
|
||||
|
||||
if h.Buf.HasSuggestions {
|
||||
h.Buf.CycleAutocomplete(false)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// InsertTab inserts a tab or spaces
|
||||
func (h *BufPane) InsertTab() bool {
|
||||
b := h.Buf
|
||||
l := b.LineBytes(h.Cursor.Y)
|
||||
l = util.SliceStart(l, h.Cursor.X)
|
||||
indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
|
||||
tabBytes := len(indent)
|
||||
bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
|
||||
@@ -631,54 +712,86 @@ func (h *BufPane) SaveAll() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Save the buffer to disk
|
||||
func (h *BufPane) Save() bool {
|
||||
// SaveCB performs a save and does a callback at the very end (after all prompts have been resolved)
|
||||
func (h *BufPane) SaveCB(action string, callback func()) bool {
|
||||
// If this is an empty buffer, ask for a filename
|
||||
if h.Buf.Path == "" {
|
||||
h.SaveAs()
|
||||
h.SaveAsCB(action, callback)
|
||||
} else {
|
||||
h.saveBufToFile(h.Buf.Path)
|
||||
noPrompt := h.saveBufToFile(h.Buf.Path, action, callback)
|
||||
if noPrompt {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
// SaveAs saves the buffer to disk with the given name
|
||||
func (h *BufPane) SaveAs() bool {
|
||||
// Save the buffer to disk
|
||||
func (h *BufPane) Save() bool {
|
||||
return h.SaveCB("Save", nil)
|
||||
}
|
||||
|
||||
// SaveAsCB performs a save as and does a callback at the very end (after all prompts have been resolved)
|
||||
// The callback is only called if the save was successful
|
||||
func (h *BufPane) SaveAsCB(action string, callback func()) bool {
|
||||
InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) {
|
||||
if !canceled {
|
||||
// the filename might or might not be quoted, so unquote first then join the strings.
|
||||
args, err := shellwords.Split(resp)
|
||||
filename := strings.Join(args, " ")
|
||||
args, err := shellquote.Split(resp)
|
||||
if err != nil {
|
||||
InfoBar.Error("Error parsing arguments: ", err)
|
||||
return
|
||||
}
|
||||
h.saveBufToFile(filename)
|
||||
|
||||
if len(args) == 0 {
|
||||
InfoBar.Error("No filename given")
|
||||
return
|
||||
}
|
||||
filename := strings.Join(args, " ")
|
||||
noPrompt := h.saveBufToFile(filename, action, callback)
|
||||
if noPrompt {
|
||||
h.completeAction(action)
|
||||
}
|
||||
}
|
||||
})
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
// SaveAs saves the buffer to disk with the given name
|
||||
func (h *BufPane) SaveAs() bool {
|
||||
return h.SaveAsCB("SaveAs", nil)
|
||||
}
|
||||
|
||||
// This function saves the buffer to `filename` and changes the buffer's path and name
|
||||
// to `filename` if the save is successful
|
||||
func (h *BufPane) saveBufToFile(filename string) {
|
||||
// The callback is only called if the save was successful
|
||||
func (h *BufPane) saveBufToFile(filename string, action string, callback func()) bool {
|
||||
err := h.Buf.SaveAs(filename)
|
||||
if err != nil {
|
||||
if strings.HasSuffix(err.Error(), "permission denied") {
|
||||
InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) {
|
||||
if yes && !canceled {
|
||||
err = h.Buf.SaveAsWithSudo(filename)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
} else {
|
||||
h.Buf.Path = filename
|
||||
h.Buf.SetName(filename)
|
||||
InfoBar.Message("Saved " + filename)
|
||||
saveWithSudo := func() {
|
||||
err = h.Buf.SaveAsWithSudo(filename)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
} else {
|
||||
h.Buf.Path = filename
|
||||
h.Buf.SetName(filename)
|
||||
InfoBar.Message("Saved " + filename)
|
||||
if callback != nil {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if h.Buf.Settings["autosu"].(bool) {
|
||||
saveWithSudo()
|
||||
} else {
|
||||
InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) {
|
||||
if yes && !canceled {
|
||||
saveWithSudo()
|
||||
h.completeAction(action)
|
||||
}
|
||||
})
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
@@ -686,15 +799,32 @@ func (h *BufPane) saveBufToFile(filename string) {
|
||||
h.Buf.Path = filename
|
||||
h.Buf.SetName(filename)
|
||||
InfoBar.Message("Saved " + filename)
|
||||
if callback != nil {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Find opens a prompt and searches forward for the input
|
||||
func (h *BufPane) Find() bool {
|
||||
return h.find(true)
|
||||
}
|
||||
|
||||
// FindLiteral is the same as Find() but does not support regular expressions
|
||||
func (h *BufPane) FindLiteral() bool {
|
||||
return h.find(false)
|
||||
}
|
||||
|
||||
func (h *BufPane) find(useRegex bool) bool {
|
||||
h.searchOrig = h.Cursor.Loc
|
||||
InfoBar.Prompt("Find: ", "", "Find", func(resp string) {
|
||||
prompt := "Find: "
|
||||
if useRegex {
|
||||
prompt = "Find (regex): "
|
||||
}
|
||||
InfoBar.Prompt(prompt, "", "Find", func(resp string) {
|
||||
// Event callback
|
||||
match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
|
||||
match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
|
||||
if found {
|
||||
h.Cursor.SetSelectionStart(match[0])
|
||||
h.Cursor.SetSelectionEnd(match[1])
|
||||
@@ -709,7 +839,7 @@ func (h *BufPane) Find() bool {
|
||||
}, func(resp string, canceled bool) {
|
||||
// Finished callback
|
||||
if !canceled {
|
||||
match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
|
||||
match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
@@ -720,6 +850,7 @@ func (h *BufPane) Find() bool {
|
||||
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
|
||||
h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
|
||||
h.lastSearch = resp
|
||||
h.lastSearchRegex = useRegex
|
||||
} else {
|
||||
h.Cursor.ResetSelection()
|
||||
InfoBar.Message("No matches found")
|
||||
@@ -743,7 +874,7 @@ func (h *BufPane) FindNext() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
searchLoc = h.Cursor.CurSelection[1]
|
||||
}
|
||||
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, true)
|
||||
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.lastSearchRegex)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
@@ -770,7 +901,7 @@ func (h *BufPane) FindPrevious() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
searchLoc = h.Cursor.CurSelection[0]
|
||||
}
|
||||
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, true)
|
||||
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.lastSearchRegex)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
@@ -808,12 +939,35 @@ func (h *BufPane) Copy() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
h.Cursor.CopySelection("clipboard")
|
||||
h.freshClip = true
|
||||
InfoBar.Message("Copied selection")
|
||||
if clipboard.Unsupported {
|
||||
InfoBar.Message("Copied selection (install xclip for external clipboard)")
|
||||
} else {
|
||||
InfoBar.Message("Copied selection")
|
||||
}
|
||||
}
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// Copy the current line to the clipboard
|
||||
func (h *BufPane) CopyLine() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
return false
|
||||
} else {
|
||||
h.Cursor.SelectLine()
|
||||
h.Cursor.CopySelection("clipboard")
|
||||
h.freshClip = true
|
||||
if clipboard.Unsupported {
|
||||
InfoBar.Message("Copied line (install xclip for external clipboard)")
|
||||
} else {
|
||||
InfoBar.Message("Copied line")
|
||||
}
|
||||
}
|
||||
h.Cursor.Deselect(true)
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// CutLine cuts the current line to the clipboard
|
||||
func (h *BufPane) CutLine() bool {
|
||||
h.Cursor.SelectLine()
|
||||
@@ -893,15 +1047,26 @@ func (h *BufPane) MoveLinesUp() bool {
|
||||
}
|
||||
start := h.Cursor.CurSelection[0].Y
|
||||
end := h.Cursor.CurSelection[1].Y
|
||||
sel := 1
|
||||
if start > end {
|
||||
end, start = start, end
|
||||
sel = 0
|
||||
}
|
||||
|
||||
compensate := false
|
||||
if h.Cursor.CurSelection[sel].X != 0 {
|
||||
end++
|
||||
} else {
|
||||
compensate = true
|
||||
}
|
||||
|
||||
h.Buf.MoveLinesUp(
|
||||
start,
|
||||
end,
|
||||
)
|
||||
h.Cursor.CurSelection[1].Y -= 1
|
||||
if compensate {
|
||||
h.Cursor.CurSelection[sel].Y -= 1
|
||||
}
|
||||
} else {
|
||||
if h.Cursor.Loc.Y == 0 {
|
||||
InfoBar.Message("Cannot move further up")
|
||||
@@ -926,8 +1091,14 @@ func (h *BufPane) MoveLinesDown() bool {
|
||||
}
|
||||
start := h.Cursor.CurSelection[0].Y
|
||||
end := h.Cursor.CurSelection[1].Y
|
||||
sel := 1
|
||||
if start > end {
|
||||
end, start = start, end
|
||||
sel = 0
|
||||
}
|
||||
|
||||
if h.Cursor.CurSelection[sel].X != 0 {
|
||||
end++
|
||||
}
|
||||
|
||||
h.Buf.MoveLinesDown(
|
||||
@@ -982,7 +1153,11 @@ func (h *BufPane) paste(clip string) {
|
||||
h.Buf.Insert(h.Cursor.Loc, clip)
|
||||
// h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
|
||||
h.freshClip = false
|
||||
InfoBar.Message("Pasted clipboard")
|
||||
if clipboard.Unsupported {
|
||||
InfoBar.Message("Pasted clipboard (install xclip for external clipboard)")
|
||||
} else {
|
||||
InfoBar.Message("Pasted clipboard")
|
||||
}
|
||||
}
|
||||
|
||||
// JumpToMatchingBrace moves the cursor to the matching brace if it is
|
||||
@@ -992,11 +1167,15 @@ func (h *BufPane) JumpToMatchingBrace() bool {
|
||||
r := h.Cursor.RuneUnder(h.Cursor.X)
|
||||
rl := h.Cursor.RuneUnder(h.Cursor.X - 1)
|
||||
if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
|
||||
matchingBrace, left := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
|
||||
if left {
|
||||
h.Cursor.GotoLoc(matchingBrace)
|
||||
matchingBrace, left, found := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
|
||||
if found {
|
||||
if left {
|
||||
h.Cursor.GotoLoc(matchingBrace)
|
||||
} else {
|
||||
h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
|
||||
}
|
||||
} else {
|
||||
h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1026,6 +1205,16 @@ func (h *BufPane) OpenFile() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// OpenFile opens a new file in the buffer
|
||||
func (h *BufPane) JumpLine() bool {
|
||||
InfoBar.Prompt("> ", "goto ", "Command", nil, func(resp string, canceled bool) {
|
||||
if !canceled {
|
||||
h.HandleCommand(resp)
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
// Start moves the viewport to the start of the buffer
|
||||
func (h *BufPane) Start() bool {
|
||||
v := h.GetView()
|
||||
@@ -1147,6 +1336,21 @@ func (h *BufPane) HalfPageDown() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ToggleDiffGutter turns the diff gutter off and on
|
||||
func (h *BufPane) ToggleDiffGutter() bool {
|
||||
if !h.Buf.Settings["diffgutter"].(bool) {
|
||||
h.Buf.Settings["diffgutter"] = true
|
||||
h.Buf.UpdateDiff(func(synchronous bool) {
|
||||
screen.Redraw()
|
||||
})
|
||||
InfoBar.Message("Enabled diff gutter")
|
||||
} else {
|
||||
h.Buf.Settings["diffgutter"] = false
|
||||
InfoBar.Message("Disabled diff gutter")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ToggleRuler turns line numbers off and on
|
||||
func (h *BufPane) ToggleRuler() bool {
|
||||
if !h.Buf.Settings["ruler"].(bool) {
|
||||
@@ -1232,15 +1436,17 @@ func (h *BufPane) Quit() bool {
|
||||
if h.Buf.Modified() {
|
||||
if config.GlobalSettings["autosave"].(float64) > 0 {
|
||||
// autosave on means we automatically save when quitting
|
||||
h.Save()
|
||||
quit()
|
||||
h.SaveCB("Quit", func() {
|
||||
quit()
|
||||
})
|
||||
} else {
|
||||
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
|
||||
if !canceled && !yes {
|
||||
quit()
|
||||
} else if !canceled && yes {
|
||||
h.Save()
|
||||
quit()
|
||||
h.SaveCB("Quit", func() {
|
||||
quit()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1296,8 +1502,9 @@ func (h *BufPane) AddTab() bool {
|
||||
|
||||
// PreviousTab switches to the previous tab in the tab list
|
||||
func (h *BufPane) PreviousTab() bool {
|
||||
a := Tabs.Active()
|
||||
Tabs.SetActive(util.Clamp(a-1, 0, len(Tabs.List)-1))
|
||||
tabsLen := len(Tabs.List)
|
||||
a := Tabs.Active() + tabsLen
|
||||
Tabs.SetActive((a - 1) % tabsLen)
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1305,7 +1512,8 @@ func (h *BufPane) PreviousTab() bool {
|
||||
// NextTab switches to the next tab in the tab list
|
||||
func (h *BufPane) NextTab() bool {
|
||||
a := Tabs.Active()
|
||||
Tabs.SetActive(util.Clamp(a+1, 0, len(Tabs.List)-1))
|
||||
Tabs.SetActive((a + 1) % len(Tabs.List))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1325,38 +1533,42 @@ func (h *BufPane) HSplitAction() bool {
|
||||
|
||||
// Unsplit closes all splits in the current tab except the active one
|
||||
func (h *BufPane) Unsplit() bool {
|
||||
n := MainTab().GetNode(h.splitID)
|
||||
n.Unsplit()
|
||||
tab := h.tab
|
||||
n := tab.GetNode(h.splitID)
|
||||
ok := n.Unsplit()
|
||||
if ok {
|
||||
tab.RemovePane(tab.GetPane(h.splitID))
|
||||
tab.Resize()
|
||||
tab.SetActive(len(tab.Panes) - 1)
|
||||
|
||||
MainTab().RemovePane(MainTab().GetPane(h.splitID))
|
||||
MainTab().Resize()
|
||||
MainTab().SetActive(len(MainTab().Panes) - 1)
|
||||
return true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NextSplit changes the view to the next split
|
||||
func (h *BufPane) NextSplit() bool {
|
||||
a := MainTab().active
|
||||
if a < len(MainTab().Panes)-1 {
|
||||
a := h.tab.active
|
||||
if a < len(h.tab.Panes)-1 {
|
||||
a++
|
||||
} else {
|
||||
a = 0
|
||||
}
|
||||
|
||||
MainTab().SetActive(a)
|
||||
h.tab.SetActive(a)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// PreviousSplit changes the view to the previous split
|
||||
func (h *BufPane) PreviousSplit() bool {
|
||||
a := MainTab().active
|
||||
a := h.tab.active
|
||||
if a > 0 {
|
||||
a--
|
||||
} else {
|
||||
a = len(MainTab().Panes) - 1
|
||||
a = len(h.tab.Panes) - 1
|
||||
}
|
||||
MainTab().SetActive(a)
|
||||
h.tab.SetActive(a)
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1435,6 +1647,41 @@ func (h *BufPane) SpawnMultiCursor() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less.
|
||||
func (h *BufPane) SpawnMultiCursorUp() bool {
|
||||
if h.Cursor.Y == 0 {
|
||||
return false
|
||||
} else {
|
||||
h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
|
||||
h.Cursor.Relocate()
|
||||
}
|
||||
|
||||
c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
|
||||
h.Buf.AddCursor(c)
|
||||
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
|
||||
h.Buf.MergeCursors()
|
||||
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// SpawnMultiCursorDown creates additional cursor, at the same X (if possible), one Y more.
|
||||
func (h *BufPane) SpawnMultiCursorDown() bool {
|
||||
if h.Cursor.Y+1 == h.Buf.LinesNum() {
|
||||
return false
|
||||
} else {
|
||||
h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
|
||||
h.Cursor.Relocate()
|
||||
}
|
||||
|
||||
c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
|
||||
h.Buf.AddCursor(c)
|
||||
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
|
||||
h.Buf.MergeCursors()
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
|
||||
func (h *BufPane) SpawnMultiCursorSelect() bool {
|
||||
// Avoid cases where multiple cursors already exist, that would create problems
|
||||
|
||||
@@ -5,7 +5,7 @@ package action
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
)
|
||||
|
||||
// Suspend sends micro to the background. This is the same as pressing CtrlZ in most unix programs.
|
||||
|
||||
@@ -5,22 +5,32 @@ import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/flynn/json5"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/json5"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
func createBindingsIfNotExist(fname string) {
|
||||
if _, e := os.Stat(fname); os.IsNotExist(e) {
|
||||
ioutil.WriteFile(fname, []byte("{}"), 0644)
|
||||
}
|
||||
}
|
||||
|
||||
// InitBindings intializes the bindings map by reading from bindings.json
|
||||
func InitBindings() {
|
||||
config.Bindings = DefaultBindings()
|
||||
|
||||
var parsed map[string]string
|
||||
defaults := DefaultBindings()
|
||||
|
||||
filename := config.ConfigDir + "/bindings.json"
|
||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||
createBindingsIfNotExist(filename)
|
||||
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
@@ -83,6 +93,7 @@ modSearch:
|
||||
k = k[5:]
|
||||
modifiers |= tcell.ModShift
|
||||
case strings.HasPrefix(k, "\x1b"):
|
||||
screen.Screen.RegisterRawSeq(k)
|
||||
return RawEvent{
|
||||
esc: k,
|
||||
}, true
|
||||
@@ -157,7 +168,8 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
|
||||
var e error
|
||||
var parsed map[string]string
|
||||
|
||||
filename := config.ConfigDir + "/bindings.json"
|
||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||
createBindingsIfNotExist(filename)
|
||||
if _, e = os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
@@ -206,7 +218,8 @@ func UnbindKey(k string) error {
|
||||
var e error
|
||||
var parsed map[string]string
|
||||
|
||||
filename := config.ConfigDir + "/bindings.json"
|
||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||
createBindingsIfNotExist(filename)
|
||||
if _, e = os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
@@ -236,6 +249,7 @@ func UnbindKey(k string) error {
|
||||
if a, ok := defaults[k]; ok {
|
||||
BindKey(k, a)
|
||||
} else if _, ok := config.Bindings[k]; ok {
|
||||
BufUnmap(key)
|
||||
delete(config.Bindings, k)
|
||||
}
|
||||
|
||||
@@ -374,8 +388,6 @@ var keyEvents = map[string]tcell.Key{
|
||||
"CtrlRightSq": tcell.KeyCtrlRightSq,
|
||||
"CtrlCarat": tcell.KeyCtrlCarat,
|
||||
"CtrlUnderscore": tcell.KeyCtrlUnderscore,
|
||||
"CtrlPageUp": tcell.KeyCtrlPgUp,
|
||||
"CtrlPageDown": tcell.KeyCtrlPgDn,
|
||||
"Tab": tcell.KeyTab,
|
||||
"Esc": tcell.KeyEsc,
|
||||
"Escape": tcell.KeyEscape,
|
||||
@@ -387,107 +399,3 @@ var keyEvents = map[string]tcell.Key{
|
||||
"PgUp": tcell.KeyPgUp,
|
||||
"PgDown": tcell.KeyPgDn,
|
||||
}
|
||||
|
||||
// DefaultBindings returns a map containing micro's default keybindings
|
||||
func DefaultBindings() map[string]string {
|
||||
return map[string]string{
|
||||
"Up": "CursorUp",
|
||||
"Down": "CursorDown",
|
||||
"Right": "CursorRight",
|
||||
"Left": "CursorLeft",
|
||||
"ShiftUp": "SelectUp",
|
||||
"ShiftDown": "SelectDown",
|
||||
"ShiftLeft": "SelectLeft",
|
||||
"ShiftRight": "SelectRight",
|
||||
"AltLeft": "WordLeft",
|
||||
"AltRight": "WordRight",
|
||||
"AltUp": "MoveLinesUp",
|
||||
"AltDown": "MoveLinesDown",
|
||||
"AltShiftRight": "SelectWordRight",
|
||||
"AltShiftLeft": "SelectWordLeft",
|
||||
"CtrlLeft": "StartOfLine",
|
||||
"CtrlRight": "EndOfLine",
|
||||
"CtrlShiftLeft": "SelectToStartOfLine",
|
||||
"ShiftHome": "SelectToStartOfLine",
|
||||
"CtrlShiftRight": "SelectToEndOfLine",
|
||||
"ShiftEnd": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Alt-{": "ParagraphPrevious",
|
||||
"Alt-}": "ParagraphNext",
|
||||
"Enter": "InsertNewline",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Tab": "Autocomplete|IndentSelection|InsertTab",
|
||||
"Backtab": "OutdentSelection|OutdentLine",
|
||||
"CtrlO": "OpenFile",
|
||||
"CtrlS": "Save",
|
||||
"CtrlF": "Find",
|
||||
"CtrlN": "FindNext",
|
||||
"CtrlP": "FindPrevious",
|
||||
"CtrlZ": "Undo",
|
||||
"CtrlY": "Redo",
|
||||
"CtrlC": "Copy",
|
||||
"CtrlX": "Cut",
|
||||
"CtrlK": "CutLine",
|
||||
"CtrlD": "DuplicateLine",
|
||||
"CtrlV": "Paste",
|
||||
"CtrlA": "SelectAll",
|
||||
"CtrlT": "AddTab",
|
||||
"Alt,": "PreviousTab",
|
||||
"Alt.": "NextTab",
|
||||
"Home": "StartOfLine",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"CtrlG": "ToggleHelp",
|
||||
"Alt-g": "ToggleKeyMenu",
|
||||
"CtrlR": "ToggleRuler",
|
||||
"CtrlL": "command-edit:goto ",
|
||||
"Delete": "Delete",
|
||||
"CtrlB": "ShellMode",
|
||||
"CtrlQ": "Quit",
|
||||
"CtrlE": "CommandMode",
|
||||
"CtrlW": "NextSplit",
|
||||
"CtrlU": "ToggleMacro",
|
||||
"CtrlJ": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
"Alt-b": "WordLeft",
|
||||
"Alt-a": "StartOfLine",
|
||||
"Alt-e": "EndOfLine",
|
||||
// "Alt-p": "CursorUp",
|
||||
// "Alt-n": "CursorDown",
|
||||
|
||||
// Integration with file managers
|
||||
"F2": "Save",
|
||||
"F3": "Find",
|
||||
"F4": "Quit",
|
||||
"F7": "Find",
|
||||
"F10": "Quit",
|
||||
"Esc": "Escape",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "ScrollUp",
|
||||
"MouseWheelDown": "ScrollDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/display"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -62,6 +62,8 @@ func BufMapKey(k Event, action string) {
|
||||
break
|
||||
}
|
||||
|
||||
// TODO: fix problem when complex bindings have these
|
||||
// characters (escape them?)
|
||||
idx := strings.IndexAny(action, "&|,")
|
||||
a := action
|
||||
if idx >= 0 {
|
||||
@@ -74,7 +76,7 @@ func BufMapKey(k Event, action string) {
|
||||
}
|
||||
|
||||
var afn func(*BufPane) bool
|
||||
if strings.HasPrefix(action, "command:") {
|
||||
if strings.HasPrefix(a, "command:") {
|
||||
a = strings.SplitN(a, ":", 2)[1]
|
||||
afn = CommandAction(a)
|
||||
names = append(names, "")
|
||||
@@ -86,15 +88,22 @@ func BufMapKey(k Event, action string) {
|
||||
a = strings.SplitN(a, ":", 2)[1]
|
||||
afn = LuaAction(a)
|
||||
if afn == nil {
|
||||
screen.TermMessage("Lua Error:", action, "does not exist")
|
||||
screen.TermMessage("Lua Error:", a, "does not exist")
|
||||
continue
|
||||
}
|
||||
names = append(names, "")
|
||||
split := strings.SplitN(a, ".", 2)
|
||||
if len(split) > 1 {
|
||||
a = strings.Title(split[0]) + strings.Title(split[1])
|
||||
} else {
|
||||
a = strings.Title(a)
|
||||
}
|
||||
|
||||
names = append(names, a)
|
||||
} else if f, ok := BufKeyActions[a]; ok {
|
||||
afn = f
|
||||
names = append(names, a)
|
||||
} else {
|
||||
screen.TermMessage("Error:", action, "does not exist")
|
||||
screen.TermMessage("Error in bindings: action", a, "does not exist")
|
||||
continue
|
||||
}
|
||||
actionfns = append(actionfns, afn)
|
||||
@@ -104,6 +113,9 @@ func BufMapKey(k Event, action string) {
|
||||
success := true
|
||||
for i, a := range actionfns {
|
||||
for j, c := range cursors {
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
h.Buf.SetCurCursor(c.Num)
|
||||
h.Cursor = c
|
||||
if i == 0 || (success && types[i-1] == '&') || (!success && types[i-1] == '|') || (types[i-1] == ',') {
|
||||
@@ -112,6 +124,8 @@ func BufMapKey(k Event, action string) {
|
||||
break
|
||||
}
|
||||
}
|
||||
// if the action changed the current pane, update the reference
|
||||
h = MainTab().CurPane()
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -127,6 +141,17 @@ func BufMapMouse(k MouseEvent, action string) {
|
||||
}
|
||||
}
|
||||
|
||||
// BufUnmap unmaps a key or mouse event from any action
|
||||
func BufUnmap(k Event) {
|
||||
delete(BufKeyBindings, k)
|
||||
delete(BufKeyStrings, k)
|
||||
|
||||
switch e := k.(type) {
|
||||
case MouseEvent:
|
||||
delete(BufMouseBindings, e)
|
||||
}
|
||||
}
|
||||
|
||||
// The BufPane connects the buffer and the window
|
||||
// It provides a cursor (or multiple) and defines a set of actions
|
||||
// that can be taken on the buffer
|
||||
@@ -167,21 +192,24 @@ type BufPane struct {
|
||||
tripleClick bool
|
||||
|
||||
// Last search stores the last successful search for FindNext and FindPrev
|
||||
lastSearch string
|
||||
lastSearch string
|
||||
lastSearchRegex bool
|
||||
// Should the current multiple cursor selection search based on word or
|
||||
// based on selection (false for selection, true for word)
|
||||
multiWord bool
|
||||
|
||||
splitID uint64
|
||||
tab *Tab
|
||||
|
||||
// remember original location of a search in case the search is canceled
|
||||
searchOrig buffer.Loc
|
||||
}
|
||||
|
||||
func NewBufPane(buf *buffer.Buffer, win display.BWindow) *BufPane {
|
||||
func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
|
||||
h := new(BufPane)
|
||||
h.Buf = buf
|
||||
h.BWindow = win
|
||||
h.tab = tab
|
||||
|
||||
h.Cursor = h.Buf.GetActiveCursor()
|
||||
h.mouseReleased = true
|
||||
@@ -191,9 +219,23 @@ func NewBufPane(buf *buffer.Buffer, win display.BWindow) *BufPane {
|
||||
return h
|
||||
}
|
||||
|
||||
func NewBufPaneFromBuf(buf *buffer.Buffer) *BufPane {
|
||||
func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
|
||||
w := display.NewBufWindow(0, 0, 0, 0, buf)
|
||||
return NewBufPane(buf, w)
|
||||
return NewBufPane(buf, w, tab)
|
||||
}
|
||||
|
||||
func (h *BufPane) SetTab(t *Tab) {
|
||||
h.tab = t
|
||||
}
|
||||
|
||||
func (h *BufPane) Tab() *Tab {
|
||||
return h.tab
|
||||
}
|
||||
|
||||
func (h *BufPane) ResizePane(size int) {
|
||||
n := h.tab.GetNode(h.splitID)
|
||||
n.ResizeSplit(size)
|
||||
h.tab.Resize()
|
||||
}
|
||||
|
||||
// PluginCB calls all plugin callbacks with a certain name and
|
||||
@@ -223,8 +265,6 @@ func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
|
||||
h.BWindow.SetBuffer(b)
|
||||
h.Cursor = b.GetActiveCursor()
|
||||
h.Resize(h.GetView().Width, h.GetView().Height)
|
||||
v := new(display.View)
|
||||
h.SetView(v)
|
||||
h.Relocate()
|
||||
// Set mouseReleased to true because we assume the mouse is not being pressed when
|
||||
// the editor is opened
|
||||
@@ -244,17 +284,38 @@ func (h *BufPane) SetID(i uint64) {
|
||||
}
|
||||
|
||||
func (h *BufPane) Name() string {
|
||||
return h.Buf.GetName()
|
||||
n := h.Buf.GetName()
|
||||
if h.Buf.Modified() {
|
||||
n += " +"
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// HandleEvent executes the tcell event properly
|
||||
func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||
if h.Buf.ExternallyModified() && !h.Buf.ReloadDisabled {
|
||||
InfoBar.YNPrompt("The file on disk has changed. Reload file? (y,n,esc)", func(yes, canceled bool) {
|
||||
if canceled {
|
||||
h.Buf.DisableReload()
|
||||
}
|
||||
if !yes || canceled {
|
||||
h.Buf.UpdateModTime()
|
||||
} else {
|
||||
h.Buf.ReOpen()
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventRaw:
|
||||
re := RawEvent{
|
||||
esc: e.EscSeq(),
|
||||
}
|
||||
h.DoKeyEvent(re)
|
||||
case *tcell.EventPaste:
|
||||
h.paste(e.Text())
|
||||
h.Relocate()
|
||||
case *tcell.EventKey:
|
||||
ke := KeyEvent{
|
||||
code: e.Key(),
|
||||
@@ -267,14 +328,27 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||
h.DoRuneInsert(e.Rune())
|
||||
}
|
||||
case *tcell.EventMouse:
|
||||
cancel := false
|
||||
switch e.Buttons() {
|
||||
case tcell.Button1:
|
||||
_, my := e.Position()
|
||||
if h.Buf.Type.Kind != buffer.BTInfo.Kind && h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 {
|
||||
cancel = true
|
||||
}
|
||||
case tcell.ButtonNone:
|
||||
// Mouse event with no click
|
||||
if !h.mouseReleased {
|
||||
// Mouse was just released
|
||||
|
||||
mx, my := e.Position()
|
||||
mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
|
||||
// mx, my := e.Position()
|
||||
// mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
|
||||
|
||||
// we could finish the selection based on the release location as described
|
||||
// below but when the mouse click is within the scroll margin this will
|
||||
// cause a scroll and selection even for a simple mouse click which is
|
||||
// not good
|
||||
// for terminals that don't support mouse motion events, selection via
|
||||
// the mouse won't work but this is ok
|
||||
|
||||
// Relocating here isn't really necessary because the cursor will
|
||||
// be in the right place from the last mouse event
|
||||
@@ -282,20 +356,23 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||
// events, this still allows the user to make selections, except only after they
|
||||
// release the mouse
|
||||
|
||||
if !h.doubleClick && !h.tripleClick {
|
||||
h.Cursor.Loc = mouseLoc
|
||||
h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
||||
// if !h.doubleClick && !h.tripleClick {
|
||||
// h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
||||
// }
|
||||
if h.Cursor.HasSelection() {
|
||||
h.Cursor.CopySelection("primary")
|
||||
}
|
||||
h.mouseReleased = true
|
||||
}
|
||||
}
|
||||
|
||||
me := MouseEvent{
|
||||
btn: e.Buttons(),
|
||||
mod: e.Modifiers(),
|
||||
if !cancel {
|
||||
me := MouseEvent{
|
||||
btn: e.Buttons(),
|
||||
mod: e.Modifiers(),
|
||||
}
|
||||
h.DoMouseEvent(me, e)
|
||||
}
|
||||
h.DoMouseEvent(me, e)
|
||||
}
|
||||
h.Buf.MergeCursors()
|
||||
|
||||
@@ -326,15 +403,15 @@ func (h *BufPane) DoKeyEvent(e Event) bool {
|
||||
}
|
||||
|
||||
func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int) bool {
|
||||
if name != "Autocomplete" {
|
||||
if name != "Autocomplete" && name != "CycleAutocompleteBack" {
|
||||
h.Buf.HasSuggestions = false
|
||||
}
|
||||
|
||||
_, isMulti := MultiActions[name]
|
||||
if (!isMulti && cursor == 0) || isMulti {
|
||||
if h.PluginCB("pre" + name) {
|
||||
asuccess := action(h)
|
||||
psuccess := h.PluginCB("on" + name)
|
||||
success := action(h)
|
||||
success = success && h.PluginCB("on"+name)
|
||||
|
||||
if isMulti {
|
||||
if recording_macro {
|
||||
@@ -344,13 +421,17 @@ func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int
|
||||
}
|
||||
}
|
||||
|
||||
return asuccess && psuccess
|
||||
return success
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *BufPane) completeAction(action string) {
|
||||
h.PluginCB("on" + action)
|
||||
}
|
||||
|
||||
func (h *BufPane) HasKeyEvent(e Event) bool {
|
||||
_, ok := BufKeyBindings[e]
|
||||
return ok
|
||||
@@ -396,26 +477,34 @@ func (h *BufPane) DoRuneInsert(r rune) {
|
||||
if recording_macro {
|
||||
curmacro = append(curmacro, r)
|
||||
}
|
||||
h.Relocate()
|
||||
h.PluginCBRune("onRune", r)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
|
||||
e := NewBufPaneFromBuf(buf)
|
||||
e.splitID = MainTab().GetNode(h.splitID).VSplit(h.Buf.Settings["splitright"].(bool))
|
||||
func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
|
||||
e := NewBufPaneFromBuf(buf, h.tab)
|
||||
e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
|
||||
MainTab().Panes = append(MainTab().Panes, e)
|
||||
MainTab().Resize()
|
||||
MainTab().SetActive(len(MainTab().Panes) - 1)
|
||||
return e
|
||||
}
|
||||
func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
|
||||
e := NewBufPaneFromBuf(buf)
|
||||
e.splitID = MainTab().GetNode(h.splitID).HSplit(h.Buf.Settings["splitbottom"].(bool))
|
||||
func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
|
||||
e := NewBufPaneFromBuf(buf, h.tab)
|
||||
e.splitID = MainTab().GetNode(h.splitID).HSplit(bottom)
|
||||
MainTab().Panes = append(MainTab().Panes, e)
|
||||
MainTab().Resize()
|
||||
MainTab().SetActive(len(MainTab().Panes) - 1)
|
||||
return e
|
||||
}
|
||||
|
||||
func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
|
||||
return h.VSplitIndex(buf, h.Buf.Settings["splitright"].(bool))
|
||||
}
|
||||
func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
|
||||
return h.HSplitIndex(buf, h.Buf.Settings["splitbottom"].(bool))
|
||||
}
|
||||
func (h *BufPane) Close() {
|
||||
h.Buf.Close()
|
||||
}
|
||||
@@ -442,99 +531,111 @@ func (h *BufPane) SetActive(b bool) {
|
||||
|
||||
// BufKeyActions contains the list of all possible key actions the bufhandler could execute
|
||||
var BufKeyActions = map[string]BufKeyAction{
|
||||
"CursorUp": (*BufPane).CursorUp,
|
||||
"CursorDown": (*BufPane).CursorDown,
|
||||
"CursorPageUp": (*BufPane).CursorPageUp,
|
||||
"CursorPageDown": (*BufPane).CursorPageDown,
|
||||
"CursorLeft": (*BufPane).CursorLeft,
|
||||
"CursorRight": (*BufPane).CursorRight,
|
||||
"CursorStart": (*BufPane).CursorStart,
|
||||
"CursorEnd": (*BufPane).CursorEnd,
|
||||
"SelectToStart": (*BufPane).SelectToStart,
|
||||
"SelectToEnd": (*BufPane).SelectToEnd,
|
||||
"SelectUp": (*BufPane).SelectUp,
|
||||
"SelectDown": (*BufPane).SelectDown,
|
||||
"SelectLeft": (*BufPane).SelectLeft,
|
||||
"SelectRight": (*BufPane).SelectRight,
|
||||
"WordRight": (*BufPane).WordRight,
|
||||
"WordLeft": (*BufPane).WordLeft,
|
||||
"SelectWordRight": (*BufPane).SelectWordRight,
|
||||
"SelectWordLeft": (*BufPane).SelectWordLeft,
|
||||
"DeleteWordRight": (*BufPane).DeleteWordRight,
|
||||
"DeleteWordLeft": (*BufPane).DeleteWordLeft,
|
||||
"SelectLine": (*BufPane).SelectLine,
|
||||
"SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
|
||||
"SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
|
||||
"ParagraphPrevious": (*BufPane).ParagraphPrevious,
|
||||
"ParagraphNext": (*BufPane).ParagraphNext,
|
||||
"InsertNewline": (*BufPane).InsertNewline,
|
||||
"Backspace": (*BufPane).Backspace,
|
||||
"Delete": (*BufPane).Delete,
|
||||
"InsertTab": (*BufPane).InsertTab,
|
||||
"Save": (*BufPane).Save,
|
||||
"SaveAll": (*BufPane).SaveAll,
|
||||
"SaveAs": (*BufPane).SaveAs,
|
||||
"Find": (*BufPane).Find,
|
||||
"FindNext": (*BufPane).FindNext,
|
||||
"FindPrevious": (*BufPane).FindPrevious,
|
||||
"Center": (*BufPane).Center,
|
||||
"Undo": (*BufPane).Undo,
|
||||
"Redo": (*BufPane).Redo,
|
||||
"Copy": (*BufPane).Copy,
|
||||
"Cut": (*BufPane).Cut,
|
||||
"CutLine": (*BufPane).CutLine,
|
||||
"DuplicateLine": (*BufPane).DuplicateLine,
|
||||
"DeleteLine": (*BufPane).DeleteLine,
|
||||
"MoveLinesUp": (*BufPane).MoveLinesUp,
|
||||
"MoveLinesDown": (*BufPane).MoveLinesDown,
|
||||
"IndentSelection": (*BufPane).IndentSelection,
|
||||
"OutdentSelection": (*BufPane).OutdentSelection,
|
||||
"Autocomplete": (*BufPane).Autocomplete,
|
||||
"OutdentLine": (*BufPane).OutdentLine,
|
||||
"Paste": (*BufPane).Paste,
|
||||
"PastePrimary": (*BufPane).PastePrimary,
|
||||
"SelectAll": (*BufPane).SelectAll,
|
||||
"OpenFile": (*BufPane).OpenFile,
|
||||
"Start": (*BufPane).Start,
|
||||
"End": (*BufPane).End,
|
||||
"PageUp": (*BufPane).PageUp,
|
||||
"PageDown": (*BufPane).PageDown,
|
||||
"SelectPageUp": (*BufPane).SelectPageUp,
|
||||
"SelectPageDown": (*BufPane).SelectPageDown,
|
||||
"HalfPageUp": (*BufPane).HalfPageUp,
|
||||
"HalfPageDown": (*BufPane).HalfPageDown,
|
||||
"StartOfLine": (*BufPane).StartOfLine,
|
||||
"EndOfLine": (*BufPane).EndOfLine,
|
||||
"ToggleHelp": (*BufPane).ToggleHelp,
|
||||
"ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
|
||||
"ToggleRuler": (*BufPane).ToggleRuler,
|
||||
"ClearStatus": (*BufPane).ClearStatus,
|
||||
"ShellMode": (*BufPane).ShellMode,
|
||||
"CommandMode": (*BufPane).CommandMode,
|
||||
"ToggleOverwriteMode": (*BufPane).ToggleOverwriteMode,
|
||||
"Escape": (*BufPane).Escape,
|
||||
"Quit": (*BufPane).Quit,
|
||||
"QuitAll": (*BufPane).QuitAll,
|
||||
"AddTab": (*BufPane).AddTab,
|
||||
"PreviousTab": (*BufPane).PreviousTab,
|
||||
"NextTab": (*BufPane).NextTab,
|
||||
"NextSplit": (*BufPane).NextSplit,
|
||||
"PreviousSplit": (*BufPane).PreviousSplit,
|
||||
"Unsplit": (*BufPane).Unsplit,
|
||||
"VSplit": (*BufPane).VSplitAction,
|
||||
"HSplit": (*BufPane).HSplitAction,
|
||||
"ToggleMacro": (*BufPane).ToggleMacro,
|
||||
"PlayMacro": (*BufPane).PlayMacro,
|
||||
"Suspend": (*BufPane).Suspend,
|
||||
"ScrollUp": (*BufPane).ScrollUpAction,
|
||||
"ScrollDown": (*BufPane).ScrollDownAction,
|
||||
"SpawnMultiCursor": (*BufPane).SpawnMultiCursor,
|
||||
"SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
|
||||
"RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
|
||||
"RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
|
||||
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
|
||||
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
|
||||
"None": (*BufPane).None,
|
||||
"CursorUp": (*BufPane).CursorUp,
|
||||
"CursorDown": (*BufPane).CursorDown,
|
||||
"CursorPageUp": (*BufPane).CursorPageUp,
|
||||
"CursorPageDown": (*BufPane).CursorPageDown,
|
||||
"CursorLeft": (*BufPane).CursorLeft,
|
||||
"CursorRight": (*BufPane).CursorRight,
|
||||
"CursorStart": (*BufPane).CursorStart,
|
||||
"CursorEnd": (*BufPane).CursorEnd,
|
||||
"SelectToStart": (*BufPane).SelectToStart,
|
||||
"SelectToEnd": (*BufPane).SelectToEnd,
|
||||
"SelectUp": (*BufPane).SelectUp,
|
||||
"SelectDown": (*BufPane).SelectDown,
|
||||
"SelectLeft": (*BufPane).SelectLeft,
|
||||
"SelectRight": (*BufPane).SelectRight,
|
||||
"WordRight": (*BufPane).WordRight,
|
||||
"WordLeft": (*BufPane).WordLeft,
|
||||
"SelectWordRight": (*BufPane).SelectWordRight,
|
||||
"SelectWordLeft": (*BufPane).SelectWordLeft,
|
||||
"DeleteWordRight": (*BufPane).DeleteWordRight,
|
||||
"DeleteWordLeft": (*BufPane).DeleteWordLeft,
|
||||
"SelectLine": (*BufPane).SelectLine,
|
||||
"SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
|
||||
"SelectToStartOfText": (*BufPane).SelectToStartOfText,
|
||||
"SelectToStartOfTextToggle": (*BufPane).SelectToStartOfTextToggle,
|
||||
"SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
|
||||
"ParagraphPrevious": (*BufPane).ParagraphPrevious,
|
||||
"ParagraphNext": (*BufPane).ParagraphNext,
|
||||
"InsertNewline": (*BufPane).InsertNewline,
|
||||
"Backspace": (*BufPane).Backspace,
|
||||
"Delete": (*BufPane).Delete,
|
||||
"InsertTab": (*BufPane).InsertTab,
|
||||
"Save": (*BufPane).Save,
|
||||
"SaveAll": (*BufPane).SaveAll,
|
||||
"SaveAs": (*BufPane).SaveAs,
|
||||
"Find": (*BufPane).Find,
|
||||
"FindLiteral": (*BufPane).FindLiteral,
|
||||
"FindNext": (*BufPane).FindNext,
|
||||
"FindPrevious": (*BufPane).FindPrevious,
|
||||
"Center": (*BufPane).Center,
|
||||
"Undo": (*BufPane).Undo,
|
||||
"Redo": (*BufPane).Redo,
|
||||
"Copy": (*BufPane).Copy,
|
||||
"CopyLine": (*BufPane).CopyLine,
|
||||
"Cut": (*BufPane).Cut,
|
||||
"CutLine": (*BufPane).CutLine,
|
||||
"DuplicateLine": (*BufPane).DuplicateLine,
|
||||
"DeleteLine": (*BufPane).DeleteLine,
|
||||
"MoveLinesUp": (*BufPane).MoveLinesUp,
|
||||
"MoveLinesDown": (*BufPane).MoveLinesDown,
|
||||
"IndentSelection": (*BufPane).IndentSelection,
|
||||
"OutdentSelection": (*BufPane).OutdentSelection,
|
||||
"Autocomplete": (*BufPane).Autocomplete,
|
||||
"CycleAutocompleteBack": (*BufPane).CycleAutocompleteBack,
|
||||
"OutdentLine": (*BufPane).OutdentLine,
|
||||
"IndentLine": (*BufPane).IndentLine,
|
||||
"Paste": (*BufPane).Paste,
|
||||
"PastePrimary": (*BufPane).PastePrimary,
|
||||
"SelectAll": (*BufPane).SelectAll,
|
||||
"OpenFile": (*BufPane).OpenFile,
|
||||
"Start": (*BufPane).Start,
|
||||
"End": (*BufPane).End,
|
||||
"PageUp": (*BufPane).PageUp,
|
||||
"PageDown": (*BufPane).PageDown,
|
||||
"SelectPageUp": (*BufPane).SelectPageUp,
|
||||
"SelectPageDown": (*BufPane).SelectPageDown,
|
||||
"HalfPageUp": (*BufPane).HalfPageUp,
|
||||
"HalfPageDown": (*BufPane).HalfPageDown,
|
||||
"StartOfText": (*BufPane).StartOfText,
|
||||
"StartOfTextToggle": (*BufPane).StartOfTextToggle,
|
||||
"StartOfLine": (*BufPane).StartOfLine,
|
||||
"EndOfLine": (*BufPane).EndOfLine,
|
||||
"ToggleHelp": (*BufPane).ToggleHelp,
|
||||
"ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
|
||||
"ToggleDiffGutter": (*BufPane).ToggleDiffGutter,
|
||||
"ToggleRuler": (*BufPane).ToggleRuler,
|
||||
"ClearStatus": (*BufPane).ClearStatus,
|
||||
"ShellMode": (*BufPane).ShellMode,
|
||||
"CommandMode": (*BufPane).CommandMode,
|
||||
"ToggleOverwriteMode": (*BufPane).ToggleOverwriteMode,
|
||||
"Escape": (*BufPane).Escape,
|
||||
"Quit": (*BufPane).Quit,
|
||||
"QuitAll": (*BufPane).QuitAll,
|
||||
"AddTab": (*BufPane).AddTab,
|
||||
"PreviousTab": (*BufPane).PreviousTab,
|
||||
"NextTab": (*BufPane).NextTab,
|
||||
"NextSplit": (*BufPane).NextSplit,
|
||||
"PreviousSplit": (*BufPane).PreviousSplit,
|
||||
"Unsplit": (*BufPane).Unsplit,
|
||||
"VSplit": (*BufPane).VSplitAction,
|
||||
"HSplit": (*BufPane).HSplitAction,
|
||||
"ToggleMacro": (*BufPane).ToggleMacro,
|
||||
"PlayMacro": (*BufPane).PlayMacro,
|
||||
"Suspend": (*BufPane).Suspend,
|
||||
"ScrollUp": (*BufPane).ScrollUpAction,
|
||||
"ScrollDown": (*BufPane).ScrollDownAction,
|
||||
"SpawnMultiCursor": (*BufPane).SpawnMultiCursor,
|
||||
"SpawnMultiCursorUp": (*BufPane).SpawnMultiCursorUp,
|
||||
"SpawnMultiCursorDown": (*BufPane).SpawnMultiCursorDown,
|
||||
"SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
|
||||
"RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
|
||||
"RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
|
||||
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
|
||||
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
|
||||
"JumpLine": (*BufPane).JumpLine,
|
||||
"None": (*BufPane).None,
|
||||
|
||||
// This was changed to InsertNewline but I don't want to break backwards compatibility
|
||||
"InsertEnter": (*BufPane).InsertNewline,
|
||||
@@ -551,51 +652,57 @@ var BufMouseActions = map[string]BufMouseAction{
|
||||
// Generally actions that modify global editor state like quitting or
|
||||
// saving should not be included in this list
|
||||
var MultiActions = map[string]bool{
|
||||
"CursorUp": true,
|
||||
"CursorDown": true,
|
||||
"CursorPageUp": true,
|
||||
"CursorPageDown": true,
|
||||
"CursorLeft": true,
|
||||
"CursorRight": true,
|
||||
"CursorStart": true,
|
||||
"CursorEnd": true,
|
||||
"SelectToStart": true,
|
||||
"SelectToEnd": true,
|
||||
"SelectUp": true,
|
||||
"SelectDown": true,
|
||||
"SelectLeft": true,
|
||||
"SelectRight": true,
|
||||
"WordRight": true,
|
||||
"WordLeft": true,
|
||||
"SelectWordRight": true,
|
||||
"SelectWordLeft": true,
|
||||
"DeleteWordRight": true,
|
||||
"DeleteWordLeft": true,
|
||||
"SelectLine": true,
|
||||
"SelectToStartOfLine": true,
|
||||
"SelectToEndOfLine": true,
|
||||
"ParagraphPrevious": true,
|
||||
"ParagraphNext": true,
|
||||
"InsertNewline": true,
|
||||
"Backspace": true,
|
||||
"Delete": true,
|
||||
"InsertTab": true,
|
||||
"FindNext": true,
|
||||
"FindPrevious": true,
|
||||
"Cut": true,
|
||||
"CutLine": true,
|
||||
"DuplicateLine": true,
|
||||
"DeleteLine": true,
|
||||
"MoveLinesUp": true,
|
||||
"MoveLinesDown": true,
|
||||
"IndentSelection": true,
|
||||
"OutdentSelection": true,
|
||||
"OutdentLine": true,
|
||||
"Paste": true,
|
||||
"PastePrimary": true,
|
||||
"SelectPageUp": true,
|
||||
"SelectPageDown": true,
|
||||
"StartOfLine": true,
|
||||
"EndOfLine": true,
|
||||
"JumpToMatchingBrace": true,
|
||||
"CursorUp": true,
|
||||
"CursorDown": true,
|
||||
"CursorPageUp": true,
|
||||
"CursorPageDown": true,
|
||||
"CursorLeft": true,
|
||||
"CursorRight": true,
|
||||
"CursorStart": true,
|
||||
"CursorEnd": true,
|
||||
"SelectToStart": true,
|
||||
"SelectToEnd": true,
|
||||
"SelectUp": true,
|
||||
"SelectDown": true,
|
||||
"SelectLeft": true,
|
||||
"SelectRight": true,
|
||||
"WordRight": true,
|
||||
"WordLeft": true,
|
||||
"SelectWordRight": true,
|
||||
"SelectWordLeft": true,
|
||||
"DeleteWordRight": true,
|
||||
"DeleteWordLeft": true,
|
||||
"SelectLine": true,
|
||||
"SelectToStartOfLine": true,
|
||||
"SelectToStartOfText": true,
|
||||
"SelectToStartOfTextToggle": true,
|
||||
"SelectToEndOfLine": true,
|
||||
"ParagraphPrevious": true,
|
||||
"ParagraphNext": true,
|
||||
"InsertNewline": true,
|
||||
"Backspace": true,
|
||||
"Delete": true,
|
||||
"InsertTab": true,
|
||||
"FindNext": true,
|
||||
"FindPrevious": true,
|
||||
"CopyLine": true,
|
||||
"Cut": true,
|
||||
"CutLine": true,
|
||||
"DuplicateLine": true,
|
||||
"DeleteLine": true,
|
||||
"MoveLinesUp": true,
|
||||
"MoveLinesDown": true,
|
||||
"IndentSelection": true,
|
||||
"OutdentSelection": true,
|
||||
"OutdentLine": true,
|
||||
"IndentLine": true,
|
||||
"Paste": true,
|
||||
"PastePrimary": true,
|
||||
"SelectPageUp": true,
|
||||
"SelectPageDown": true,
|
||||
"StartOfLine": true,
|
||||
"StartOfText": true,
|
||||
"StartOfTextToggle": true,
|
||||
"EndOfLine": true,
|
||||
"JumpToMatchingBrace": true,
|
||||
}
|
||||
|
||||
@@ -10,18 +10,13 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/shell"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/pkg/shellwords"
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// A Command contains information about how to execute a command
|
||||
@@ -35,65 +30,46 @@ var commands map[string]Command
|
||||
|
||||
func InitCommands() {
|
||||
commands = map[string]Command{
|
||||
"set": Command{(*BufPane).SetCmd, OptionValueComplete},
|
||||
"reset": Command{(*BufPane).ResetCmd, OptionValueComplete},
|
||||
"setlocal": Command{(*BufPane).SetLocalCmd, OptionValueComplete},
|
||||
"show": Command{(*BufPane).ShowCmd, OptionComplete},
|
||||
"showkey": Command{(*BufPane).ShowKeyCmd, nil},
|
||||
"run": Command{(*BufPane).RunCmd, nil},
|
||||
"bind": Command{(*BufPane).BindCmd, nil},
|
||||
"unbind": Command{(*BufPane).UnbindCmd, nil},
|
||||
"quit": Command{(*BufPane).QuitCmd, nil},
|
||||
"goto": Command{(*BufPane).GotoCmd, nil},
|
||||
"save": Command{(*BufPane).SaveCmd, nil},
|
||||
"replace": Command{(*BufPane).ReplaceCmd, nil},
|
||||
"replaceall": Command{(*BufPane).ReplaceAllCmd, nil},
|
||||
"vsplit": Command{(*BufPane).VSplitCmd, buffer.FileComplete},
|
||||
"hsplit": Command{(*BufPane).HSplitCmd, buffer.FileComplete},
|
||||
"tab": Command{(*BufPane).NewTabCmd, buffer.FileComplete},
|
||||
"help": Command{(*BufPane).HelpCmd, HelpComplete},
|
||||
"eval": Command{(*BufPane).EvalCmd, nil},
|
||||
"log": Command{(*BufPane).ToggleLogCmd, nil},
|
||||
"plugin": Command{(*BufPane).PluginCmd, PluginComplete},
|
||||
"reload": Command{(*BufPane).ReloadCmd, nil},
|
||||
"reopen": Command{(*BufPane).ReopenCmd, nil},
|
||||
"cd": Command{(*BufPane).CdCmd, buffer.FileComplete},
|
||||
"pwd": Command{(*BufPane).PwdCmd, nil},
|
||||
"open": Command{(*BufPane).OpenCmd, buffer.FileComplete},
|
||||
"tabswitch": Command{(*BufPane).TabSwitchCmd, nil},
|
||||
"term": Command{(*BufPane).TermCmd, nil},
|
||||
"memusage": Command{(*BufPane).MemUsageCmd, nil},
|
||||
"retab": Command{(*BufPane).RetabCmd, nil},
|
||||
"raw": Command{(*BufPane).RawCmd, nil},
|
||||
"textfilter": Command{(*BufPane).TextFilterCmd, nil},
|
||||
"set": {(*BufPane).SetCmd, OptionValueComplete},
|
||||
"reset": {(*BufPane).ResetCmd, OptionValueComplete},
|
||||
"setlocal": {(*BufPane).SetLocalCmd, OptionValueComplete},
|
||||
"show": {(*BufPane).ShowCmd, OptionComplete},
|
||||
"showkey": {(*BufPane).ShowKeyCmd, nil},
|
||||
"run": {(*BufPane).RunCmd, nil},
|
||||
"bind": {(*BufPane).BindCmd, nil},
|
||||
"unbind": {(*BufPane).UnbindCmd, nil},
|
||||
"quit": {(*BufPane).QuitCmd, nil},
|
||||
"goto": {(*BufPane).GotoCmd, nil},
|
||||
"save": {(*BufPane).SaveCmd, nil},
|
||||
"replace": {(*BufPane).ReplaceCmd, nil},
|
||||
"replaceall": {(*BufPane).ReplaceAllCmd, nil},
|
||||
"vsplit": {(*BufPane).VSplitCmd, buffer.FileComplete},
|
||||
"hsplit": {(*BufPane).HSplitCmd, buffer.FileComplete},
|
||||
"tab": {(*BufPane).NewTabCmd, buffer.FileComplete},
|
||||
"help": {(*BufPane).HelpCmd, HelpComplete},
|
||||
"eval": {(*BufPane).EvalCmd, nil},
|
||||
"log": {(*BufPane).ToggleLogCmd, nil},
|
||||
"plugin": {(*BufPane).PluginCmd, PluginComplete},
|
||||
"reload": {(*BufPane).ReloadCmd, nil},
|
||||
"reopen": {(*BufPane).ReopenCmd, nil},
|
||||
"cd": {(*BufPane).CdCmd, buffer.FileComplete},
|
||||
"pwd": {(*BufPane).PwdCmd, nil},
|
||||
"open": {(*BufPane).OpenCmd, buffer.FileComplete},
|
||||
"tabmove": {(*BufPane).TabMoveCmd, nil},
|
||||
"tabswitch": {(*BufPane).TabSwitchCmd, nil},
|
||||
"term": {(*BufPane).TermCmd, nil},
|
||||
"memusage": {(*BufPane).MemUsageCmd, nil},
|
||||
"retab": {(*BufPane).RetabCmd, nil},
|
||||
"raw": {(*BufPane).RawCmd, nil},
|
||||
"textfilter": {(*BufPane).TextFilterCmd, nil},
|
||||
}
|
||||
}
|
||||
|
||||
// MakeCommand is a function to easily create new commands
|
||||
// This can be called by plugins in Lua so that plugins can define their own commands
|
||||
func LuaMakeCommand(name, function string, completer buffer.Completer) {
|
||||
action := LuaFunctionCommand(function)
|
||||
commands[name] = Command{action, completer}
|
||||
}
|
||||
|
||||
// LuaFunctionCommand returns a normal function
|
||||
// so that a command can be bound to a lua function
|
||||
func LuaFunctionCommand(fn string) func(*BufPane, []string) {
|
||||
luaFn := strings.Split(fn, ".")
|
||||
if len(luaFn) <= 1 {
|
||||
return nil
|
||||
}
|
||||
plName, plFn := luaFn[0], luaFn[1]
|
||||
pl := config.FindPlugin(plName)
|
||||
if pl == nil {
|
||||
return nil
|
||||
}
|
||||
return func(bp *BufPane, args []string) {
|
||||
luaArgs := []lua.LValue{luar.New(ulua.L, bp), luar.New(ulua.L, args)}
|
||||
_, err := pl.Call(plFn, luaArgs...)
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
func MakeCommand(name string, action func(bp *BufPane, args []string), completer buffer.Completer) {
|
||||
if action != nil {
|
||||
commands[name] = Command{action, completer}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,106 +96,20 @@ func CommandAction(cmd string) BufKeyAction {
|
||||
}
|
||||
}
|
||||
|
||||
var PluginCmds = []string{"list", "info", "version"}
|
||||
var PluginCmds = []string{"install", "remove", "update", "available", "list", "search"}
|
||||
|
||||
// PluginCmd installs, removes, updates, lists, or searches for given plugins
|
||||
func (h *BufPane) PluginCmd(args []string) {
|
||||
if len(args) <= 0 {
|
||||
InfoBar.Error("Not enough arguments, see 'help commands'")
|
||||
if len(args) < 1 {
|
||||
InfoBar.Error("Not enough arguments")
|
||||
return
|
||||
}
|
||||
|
||||
valid := true
|
||||
switch args[0] {
|
||||
case "list":
|
||||
for _, pl := range config.Plugins {
|
||||
var en string
|
||||
if pl.IsEnabled() {
|
||||
en = "enabled"
|
||||
} else {
|
||||
en = "disabled"
|
||||
}
|
||||
WriteLog(fmt.Sprintf("%s: %s", pl.Name, en))
|
||||
if pl.Default {
|
||||
WriteLog(" (default)\n")
|
||||
} else {
|
||||
WriteLog("\n")
|
||||
}
|
||||
}
|
||||
WriteLog("Default plugins come pre-installed with micro.")
|
||||
case "version":
|
||||
if len(args) <= 1 {
|
||||
InfoBar.Error("No plugin provided to give info for")
|
||||
return
|
||||
}
|
||||
found := false
|
||||
for _, pl := range config.Plugins {
|
||||
if pl.Name == args[1] {
|
||||
found = true
|
||||
if pl.Info == nil {
|
||||
InfoBar.Message("Sorry no version for", pl.Name)
|
||||
return
|
||||
}
|
||||
|
||||
WriteLog("Version: " + pl.Info.Vstr + "\n")
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
InfoBar.Message(args[1], "is not installed")
|
||||
}
|
||||
case "info":
|
||||
if len(args) <= 1 {
|
||||
InfoBar.Error("No plugin provided to give info for")
|
||||
return
|
||||
}
|
||||
found := false
|
||||
for _, pl := range config.Plugins {
|
||||
if pl.Name == args[1] {
|
||||
found = true
|
||||
if pl.Info == nil {
|
||||
InfoBar.Message("Sorry no info for ", pl.Name)
|
||||
return
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString("Name: ")
|
||||
buffer.WriteString(pl.Info.Name)
|
||||
buffer.WriteString("\n")
|
||||
buffer.WriteString("Description: ")
|
||||
buffer.WriteString(pl.Info.Desc)
|
||||
buffer.WriteString("\n")
|
||||
buffer.WriteString("Website: ")
|
||||
buffer.WriteString(pl.Info.Site)
|
||||
buffer.WriteString("\n")
|
||||
buffer.WriteString("Installation link: ")
|
||||
buffer.WriteString(pl.Info.Install)
|
||||
buffer.WriteString("\n")
|
||||
buffer.WriteString("Version: ")
|
||||
buffer.WriteString(pl.Info.Vstr)
|
||||
buffer.WriteString("\n")
|
||||
buffer.WriteString("Requirements:")
|
||||
buffer.WriteString("\n")
|
||||
for _, r := range pl.Info.Require {
|
||||
buffer.WriteString(" - ")
|
||||
buffer.WriteString(r)
|
||||
buffer.WriteString("\n")
|
||||
}
|
||||
|
||||
WriteLog(buffer.String())
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
InfoBar.Message(args[1], "is not installed")
|
||||
return
|
||||
}
|
||||
default:
|
||||
InfoBar.Error("Not a valid plugin command")
|
||||
return
|
||||
if h.Buf.Type != buffer.BTLog {
|
||||
h.OpenLogBuf()
|
||||
}
|
||||
|
||||
if valid && h.Buf.Type != buffer.BTLog {
|
||||
OpenLogBuf(h)
|
||||
}
|
||||
config.PluginCommand(buffer.LogBuf, args[0], args[1:])
|
||||
}
|
||||
|
||||
// RetabCmd changes all spaces to tabs or all tabs to spaces
|
||||
@@ -233,14 +123,14 @@ func (h *BufPane) RetabCmd(args []string) {
|
||||
func (h *BufPane) RawCmd(args []string) {
|
||||
width, height := screen.Screen.Size()
|
||||
iOffset := config.GetInfoBarOffset()
|
||||
tp := NewTabFromPane(0, 0, width, height-iOffset, NewRawPane())
|
||||
tp := NewTabFromPane(0, 0, width, height-iOffset, NewRawPane(nil))
|
||||
Tabs.AddTab(tp)
|
||||
Tabs.SetActive(len(Tabs.List) - 1)
|
||||
}
|
||||
|
||||
// TextFilterCmd filters the selection through the command.
|
||||
// Selection goes to the command input.
|
||||
// On successfull run command output replaces the current selection.
|
||||
// On successful run command output replaces the current selection.
|
||||
func (h *BufPane) TextFilterCmd(args []string) {
|
||||
if len(args) == 0 {
|
||||
InfoBar.Error("usage: textfilter arguments")
|
||||
@@ -265,6 +155,56 @@ func (h *BufPane) TextFilterCmd(args []string) {
|
||||
h.Buf.Insert(h.Cursor.Loc, bout.String())
|
||||
}
|
||||
|
||||
// TabMoveCmd moves the current tab to a given index (starts at 1). The
|
||||
// displaced tabs are moved up.
|
||||
func (h *BufPane) TabMoveCmd(args []string) {
|
||||
if len(args) <= 0 {
|
||||
InfoBar.Error("Not enough arguments: provide an index, starting at 1")
|
||||
return
|
||||
}
|
||||
|
||||
if len(args[0]) <= 0 {
|
||||
InfoBar.Error("Invalid argument: empty string")
|
||||
return
|
||||
}
|
||||
|
||||
num, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
InfoBar.Error("Invalid argument: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Preserve sign for relative move, if one exists
|
||||
var shiftDirection byte
|
||||
if strings.Contains("-+", string([]byte{args[0][0]})) {
|
||||
shiftDirection = args[0][0]
|
||||
}
|
||||
|
||||
// Relative positions -> absolute positions
|
||||
idxFrom := Tabs.Active()
|
||||
idxTo := 0
|
||||
offset := util.Abs(num)
|
||||
if shiftDirection == '-' {
|
||||
idxTo = idxFrom - offset
|
||||
} else if shiftDirection == '+' {
|
||||
idxTo = idxFrom + offset
|
||||
} else {
|
||||
idxTo = offset - 1
|
||||
}
|
||||
|
||||
// Restrain position to within the valid range
|
||||
idxTo = util.Clamp(idxTo, 0, len(Tabs.List)-1)
|
||||
|
||||
activeTab := Tabs.List[idxFrom]
|
||||
Tabs.RemoveTab(activeTab.ID())
|
||||
Tabs.List = append(Tabs.List, nil)
|
||||
copy(Tabs.List[idxTo+1:], Tabs.List[idxTo:])
|
||||
Tabs.List[idxTo] = activeTab
|
||||
Tabs.UpdateNames()
|
||||
Tabs.SetActive(idxTo)
|
||||
// InfoBar.Message(fmt.Sprintf("Moved tab from slot %d to %d", idxFrom+1, idxTo+1))
|
||||
}
|
||||
|
||||
// TabSwitchCmd switches to a given tab either by name or by number
|
||||
func (h *BufPane) TabSwitchCmd(args []string) {
|
||||
if len(args) > 0 {
|
||||
@@ -344,11 +284,14 @@ func (h *BufPane) OpenCmd(args []string) {
|
||||
if len(args) > 0 {
|
||||
filename := args[0]
|
||||
// the filename might or might not be quoted, so unquote first then join the strings.
|
||||
args, err := shellwords.Split(filename)
|
||||
args, err := shellquote.Split(filename)
|
||||
if err != nil {
|
||||
InfoBar.Error("Error parsing args ", err)
|
||||
return
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return
|
||||
}
|
||||
filename = strings.Join(args, " ")
|
||||
|
||||
open := func() {
|
||||
@@ -379,7 +322,7 @@ func (h *BufPane) OpenCmd(args []string) {
|
||||
// ToggleLogCmd toggles the log view
|
||||
func (h *BufPane) ToggleLogCmd(args []string) {
|
||||
if h.Buf.Type != buffer.BTLog {
|
||||
OpenLogBuf(h)
|
||||
h.OpenLogBuf()
|
||||
} else {
|
||||
h.Quit()
|
||||
}
|
||||
@@ -396,7 +339,10 @@ func ReloadConfig() {
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
config.InitGlobalSettings()
|
||||
err = config.InitGlobalSettings()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
InitBindings()
|
||||
InitCommands()
|
||||
|
||||
@@ -497,6 +443,7 @@ func (h *BufPane) HSplitCmd(args []string) {
|
||||
|
||||
// EvalCmd evaluates a lua expression
|
||||
func (h *BufPane) EvalCmd(args []string) {
|
||||
InfoBar.Error("Eval unsupported")
|
||||
}
|
||||
|
||||
// NewTabCmd opens the given file in a new tab
|
||||
@@ -523,37 +470,56 @@ func (h *BufPane) NewTabCmd(args []string) {
|
||||
}
|
||||
|
||||
func SetGlobalOptionNative(option string, nativeValue interface{}) error {
|
||||
config.GlobalSettings[option] = nativeValue
|
||||
local := false
|
||||
for _, s := range config.LocalSettings {
|
||||
if s == option {
|
||||
local = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if option == "colorscheme" {
|
||||
// LoadSyntaxFiles()
|
||||
config.InitColorscheme()
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
b.UpdateRules()
|
||||
}
|
||||
} else if option == "infobar" || option == "keymenu" {
|
||||
Tabs.Resize()
|
||||
} else if option == "mouse" {
|
||||
if !nativeValue.(bool) {
|
||||
screen.Screen.DisableMouse()
|
||||
if !local {
|
||||
config.GlobalSettings[option] = nativeValue
|
||||
config.ModifiedSettings[option] = true
|
||||
|
||||
if option == "colorscheme" {
|
||||
// LoadSyntaxFiles()
|
||||
config.InitColorscheme()
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
b.UpdateRules()
|
||||
}
|
||||
} else if option == "infobar" || option == "keymenu" {
|
||||
Tabs.Resize()
|
||||
} else if option == "mouse" {
|
||||
if !nativeValue.(bool) {
|
||||
screen.Screen.DisableMouse()
|
||||
} else {
|
||||
screen.Screen.EnableMouse()
|
||||
}
|
||||
} else if option == "autosave" {
|
||||
if nativeValue.(float64) > 0 {
|
||||
config.SetAutoTime(int(nativeValue.(float64)))
|
||||
config.StartAutoSave()
|
||||
} else {
|
||||
config.SetAutoTime(0)
|
||||
}
|
||||
} else if option == "paste" {
|
||||
screen.Screen.SetPaste(nativeValue.(bool))
|
||||
} else {
|
||||
screen.Screen.EnableMouse()
|
||||
}
|
||||
} else if option == "autosave" {
|
||||
if nativeValue.(float64) > 0 {
|
||||
config.SetAutoTime(int(nativeValue.(float64)))
|
||||
config.StartAutoSave()
|
||||
} else {
|
||||
config.SetAutoTime(0)
|
||||
}
|
||||
} else {
|
||||
for _, pl := range config.Plugins {
|
||||
if option == pl.Name {
|
||||
if nativeValue.(bool) && !pl.Loaded {
|
||||
pl.Load()
|
||||
pl.Call("init")
|
||||
} else if !nativeValue.(bool) && pl.Loaded {
|
||||
pl.Call("deinit")
|
||||
for _, pl := range config.Plugins {
|
||||
if option == pl.Name {
|
||||
if nativeValue.(bool) && !pl.Loaded {
|
||||
pl.Load()
|
||||
_, err := pl.Call("init")
|
||||
if err != nil && err != config.ErrNoSuchFunction {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
} else if !nativeValue.(bool) && pl.Loaded {
|
||||
_, err := pl.Call("deinit")
|
||||
if err != nil && err != config.ErrNoSuchFunction {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -563,7 +529,7 @@ func SetGlobalOptionNative(option string, nativeValue interface{}) error {
|
||||
b.SetOptionNative(option, nativeValue)
|
||||
}
|
||||
|
||||
return config.WriteSettings(config.ConfigDir + "/settings.json")
|
||||
return config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
|
||||
}
|
||||
|
||||
func SetGlobalOption(option, value string) error {
|
||||
@@ -691,7 +657,7 @@ func (h *BufPane) BindCmd(args []string) {
|
||||
// UnbindCmd binds a key to its default action
|
||||
func (h *BufPane) UnbindCmd(args []string) {
|
||||
if len(args) < 1 {
|
||||
InfoBar.Error("Not enough arguements")
|
||||
InfoBar.Error("Not enough arguments")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -703,7 +669,7 @@ func (h *BufPane) UnbindCmd(args []string) {
|
||||
|
||||
// RunCmd runs a shell command in the background
|
||||
func (h *BufPane) RunCmd(args []string) {
|
||||
runf, err := shell.RunBackgroundShell(shellwords.Join(args...))
|
||||
runf, err := shell.RunBackgroundShell(shellquote.Join(args...))
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
} else {
|
||||
@@ -739,8 +705,11 @@ func (h *BufPane) GotoCmd(args []string) {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
if line < 0 {
|
||||
line = h.Buf.LinesNum() + 1 + line
|
||||
}
|
||||
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
|
||||
col = util.Clamp(col-1, 0, utf8.RuneCount(h.Buf.LineBytes(line)))
|
||||
col = util.Clamp(col-1, 0, util.CharacterCount(h.Buf.LineBytes(line)))
|
||||
h.Cursor.GotoLoc(buffer.Loc{col, line})
|
||||
} else {
|
||||
line, err := strconv.Atoi(args[0])
|
||||
@@ -748,6 +717,9 @@ func (h *BufPane) GotoCmd(args []string) {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
if line < 0 {
|
||||
line = h.Buf.LinesNum() + 1 + line
|
||||
}
|
||||
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
|
||||
h.Cursor.GotoLoc(buffer.Loc{0, line})
|
||||
}
|
||||
@@ -821,19 +793,19 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
||||
nreplaced := 0
|
||||
start := h.Buf.Start()
|
||||
end := h.Buf.End()
|
||||
if h.Cursor.HasSelection() {
|
||||
selection := h.Cursor.HasSelection()
|
||||
if selection {
|
||||
start = h.Cursor.CurSelection[0]
|
||||
end = h.Cursor.CurSelection[1]
|
||||
}
|
||||
if all {
|
||||
nreplaced = h.Buf.ReplaceRegex(start, end, regex, replace)
|
||||
nreplaced, _ = h.Buf.ReplaceRegex(start, end, regex, replace)
|
||||
} else {
|
||||
inRange := func(l buffer.Loc) bool {
|
||||
return l.GreaterEqual(start) && l.LessThan(end)
|
||||
return l.GreaterEqual(start) && l.LessEqual(end)
|
||||
}
|
||||
|
||||
searchLoc := start
|
||||
searching := true
|
||||
searchLoc := h.Cursor.Loc
|
||||
var doReplacement func()
|
||||
doReplacement = func() {
|
||||
locs, found, err := h.Buf.FindNext(search, start, end, searchLoc, true, !noRegex)
|
||||
@@ -844,44 +816,58 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
||||
if !found || !inRange(locs[0]) || !inRange(locs[1]) {
|
||||
h.Cursor.ResetSelection()
|
||||
h.Buf.RelocateCursors()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
h.Cursor.SetSelectionStart(locs[0])
|
||||
h.Cursor.SetSelectionEnd(locs[1])
|
||||
h.Cursor.GotoLoc(locs[0])
|
||||
|
||||
h.Relocate()
|
||||
|
||||
InfoBar.YNPrompt("Perform replacement (y,n,esc)", func(yes, canceled bool) {
|
||||
if !canceled && yes {
|
||||
h.Buf.Replace(locs[0], locs[1], replaceStr)
|
||||
_, nrunes := h.Buf.ReplaceRegex(locs[0], locs[1], regex, replace)
|
||||
|
||||
searchLoc = locs[0]
|
||||
searchLoc.X += utf8.RuneCount(replace)
|
||||
searchLoc.X += nrunes + locs[0].Diff(locs[1], h.Buf)
|
||||
if end.Y == locs[1].Y {
|
||||
end = end.Move(nrunes, h.Buf)
|
||||
}
|
||||
h.Cursor.Loc = searchLoc
|
||||
nreplaced++
|
||||
} else if !canceled && !yes {
|
||||
searchLoc = locs[0]
|
||||
searchLoc.X += utf8.RuneCount(replace)
|
||||
searchLoc.X += util.CharacterCount(replace)
|
||||
} else if canceled {
|
||||
h.Cursor.ResetSelection()
|
||||
h.Buf.RelocateCursors()
|
||||
return
|
||||
}
|
||||
if searching {
|
||||
doReplacement()
|
||||
}
|
||||
doReplacement()
|
||||
})
|
||||
}
|
||||
doReplacement()
|
||||
}
|
||||
|
||||
h.Buf.RelocateCursors()
|
||||
h.Relocate()
|
||||
|
||||
var s string
|
||||
if nreplaced > 1 {
|
||||
InfoBar.Message("Replaced ", nreplaced, " occurrences of ", search)
|
||||
s = fmt.Sprintf("Replaced %d occurrences of %s", nreplaced, search)
|
||||
} else if nreplaced == 1 {
|
||||
InfoBar.Message("Replaced ", nreplaced, " occurrence of ", search)
|
||||
s = fmt.Sprintf("Replaced 1 occurrence of %s", search)
|
||||
} else {
|
||||
InfoBar.Message("Nothing matched ", search)
|
||||
s = fmt.Sprintf("Nothing matched %s", search)
|
||||
}
|
||||
|
||||
if selection {
|
||||
s += " in selection"
|
||||
}
|
||||
|
||||
InfoBar.Message(s)
|
||||
}
|
||||
|
||||
// ReplaceAllCmd replaces search term all at once
|
||||
@@ -892,7 +878,12 @@ func (h *BufPane) ReplaceAllCmd(args []string) {
|
||||
|
||||
// TermCmd opens a terminal in the current view
|
||||
func (h *BufPane) TermCmd(args []string) {
|
||||
ps := MainTab().Panes
|
||||
ps := h.tab.Panes
|
||||
|
||||
if !TermEmuSupported {
|
||||
InfoBar.Error("Terminal emulator not supported on this system")
|
||||
return
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
sh := os.Getenv("SHELL")
|
||||
@@ -905,7 +896,11 @@ func (h *BufPane) TermCmd(args []string) {
|
||||
|
||||
term := func(i int, newtab bool) {
|
||||
t := new(shell.Terminal)
|
||||
t.Start(args, false, true, "", nil)
|
||||
err := t.Start(args, false, true, nil, nil)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
id := h.ID()
|
||||
if newtab {
|
||||
@@ -917,7 +912,12 @@ func (h *BufPane) TermCmd(args []string) {
|
||||
}
|
||||
|
||||
v := h.GetView()
|
||||
MainTab().Panes[i] = NewTermPane(v.X, v.Y, v.Width, v.Height, t, id)
|
||||
tp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
MainTab().Panes[i] = tp
|
||||
MainTab().SetActive(i)
|
||||
}
|
||||
|
||||
@@ -949,12 +949,16 @@ func (h *BufPane) TermCmd(args []string) {
|
||||
|
||||
// HandleCommand handles input from the user
|
||||
func (h *BufPane) HandleCommand(input string) {
|
||||
args, err := shellwords.Split(input)
|
||||
args, err := shellquote.Split(input)
|
||||
if err != nil {
|
||||
InfoBar.Error("Error parsing args ", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
inputCmd := args[0]
|
||||
|
||||
if _, ok := commands[inputCmd]; !ok {
|
||||
|
||||
107
internal/action/defaults_darwin.go
Normal file
107
internal/action/defaults_darwin.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package action
|
||||
|
||||
// DefaultBindings returns a map containing micro's default keybindings
|
||||
func DefaultBindings() map[string]string {
|
||||
return map[string]string{
|
||||
"Up": "CursorUp",
|
||||
"Down": "CursorDown",
|
||||
"Right": "CursorRight",
|
||||
"Left": "CursorLeft",
|
||||
"ShiftUp": "SelectUp",
|
||||
"ShiftDown": "SelectDown",
|
||||
"ShiftLeft": "SelectLeft",
|
||||
"ShiftRight": "SelectRight",
|
||||
"AltLeft": "WordLeft",
|
||||
"AltRight": "WordRight",
|
||||
"AltUp": "MoveLinesUp",
|
||||
"AltDown": "MoveLinesDown",
|
||||
"AltShiftRight": "SelectWordRight",
|
||||
"AltShiftLeft": "SelectWordLeft",
|
||||
"CtrlLeft": "StartOfTextToggle",
|
||||
"CtrlRight": "EndOfLine",
|
||||
"CtrlShiftLeft": "SelectToStartOfTextToggle",
|
||||
"ShiftHome": "SelectToStartOfTextToggle",
|
||||
"CtrlShiftRight": "SelectToEndOfLine",
|
||||
"ShiftEnd": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Alt-{": "ParagraphPrevious",
|
||||
"Alt-}": "ParagraphNext",
|
||||
"Enter": "InsertNewline",
|
||||
"Ctrl-h": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Tab": "Autocomplete|IndentSelection|InsertTab",
|
||||
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
|
||||
"Ctrl-o": "OpenFile",
|
||||
"Ctrl-s": "Save",
|
||||
"Ctrl-f": "Find",
|
||||
"Ctrl-n": "FindNext",
|
||||
"Ctrl-p": "FindPrevious",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-d": "DuplicateLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Ctrl-a": "SelectAll",
|
||||
"Ctrl-t": "AddTab",
|
||||
"Alt-,": "PreviousTab",
|
||||
"Alt-.": "NextTab",
|
||||
"Home": "StartOfTextToggle",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"Ctrl-g": "ToggleHelp",
|
||||
"Alt-g": "ToggleKeyMenu",
|
||||
"Ctrl-r": "ToggleRuler",
|
||||
"Ctrl-l": "command-edit:goto ",
|
||||
"Delete": "Delete",
|
||||
"Ctrl-b": "ShellMode",
|
||||
"Ctrl-q": "Quit",
|
||||
"Ctrl-e": "CommandMode",
|
||||
"Ctrl-w": "NextSplit",
|
||||
"Ctrl-u": "ToggleMacro",
|
||||
"Ctrl-j": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
"Alt-b": "WordLeft",
|
||||
"Alt-a": "StartOfText",
|
||||
"Alt-e": "EndOfLine",
|
||||
// "Alt-p": "CursorUp",
|
||||
// "Alt-n": "CursorDown",
|
||||
|
||||
// Integration with file managers
|
||||
"F2": "Save",
|
||||
"F3": "Find",
|
||||
"F4": "Quit",
|
||||
"F7": "Find",
|
||||
"F10": "Quit",
|
||||
"Esc": "Escape",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "ScrollUp",
|
||||
"MouseWheelDown": "ScrollDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"AltShiftUp": "SpawnMultiCursorUp",
|
||||
"AltShiftDown": "SpawnMultiCursorDown",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
}
|
||||
}
|
||||
109
internal/action/defaults_other.go
Normal file
109
internal/action/defaults_other.go
Normal file
@@ -0,0 +1,109 @@
|
||||
// +build !darwin
|
||||
|
||||
package action
|
||||
|
||||
// DefaultBindings returns a map containing micro's default keybindings
|
||||
func DefaultBindings() map[string]string {
|
||||
return map[string]string{
|
||||
"Up": "CursorUp",
|
||||
"Down": "CursorDown",
|
||||
"Right": "CursorRight",
|
||||
"Left": "CursorLeft",
|
||||
"ShiftUp": "SelectUp",
|
||||
"ShiftDown": "SelectDown",
|
||||
"ShiftLeft": "SelectLeft",
|
||||
"ShiftRight": "SelectRight",
|
||||
"CtrlLeft": "WordLeft",
|
||||
"CtrlRight": "WordRight",
|
||||
"AltUp": "MoveLinesUp",
|
||||
"AltDown": "MoveLinesDown",
|
||||
"CtrlShiftRight": "SelectWordRight",
|
||||
"CtrlShiftLeft": "SelectWordLeft",
|
||||
"AltLeft": "StartOfTextToggle",
|
||||
"AltRight": "EndOfLine",
|
||||
"AltShiftLeft": "SelectToStartOfTextToggle",
|
||||
"ShiftHome": "SelectToStartOfTextToggle",
|
||||
"AltShiftRight": "SelectToEndOfLine",
|
||||
"ShiftEnd": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Alt-{": "ParagraphPrevious",
|
||||
"Alt-}": "ParagraphNext",
|
||||
"Enter": "InsertNewline",
|
||||
"Ctrl-h": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Tab": "Autocomplete|IndentSelection|InsertTab",
|
||||
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
|
||||
"Ctrl-o": "OpenFile",
|
||||
"Ctrl-s": "Save",
|
||||
"Ctrl-f": "Find",
|
||||
"Ctrl-n": "FindNext",
|
||||
"Ctrl-p": "FindPrevious",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-d": "DuplicateLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Ctrl-a": "SelectAll",
|
||||
"Ctrl-t": "AddTab",
|
||||
"Alt-,": "PreviousTab",
|
||||
"Alt-.": "NextTab",
|
||||
"Home": "StartOfTextToggle",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"Ctrl-g": "ToggleHelp",
|
||||
"Alt-g": "ToggleKeyMenu",
|
||||
"Ctrl-r": "ToggleRuler",
|
||||
"Ctrl-l": "command-edit:goto ",
|
||||
"Delete": "Delete",
|
||||
"Ctrl-b": "ShellMode",
|
||||
"Ctrl-q": "Quit",
|
||||
"Ctrl-e": "CommandMode",
|
||||
"Ctrl-w": "NextSplit",
|
||||
"Ctrl-u": "ToggleMacro",
|
||||
"Ctrl-j": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
"Alt-b": "WordLeft",
|
||||
"Alt-a": "StartOfText",
|
||||
"Alt-e": "EndOfLine",
|
||||
// "Alt-p": "CursorUp",
|
||||
// "Alt-n": "CursorDown",
|
||||
|
||||
// Integration with file managers
|
||||
"F2": "Save",
|
||||
"F3": "Find",
|
||||
"F4": "Quit",
|
||||
"F7": "Find",
|
||||
"F10": "Quit",
|
||||
"Esc": "Escape",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "ScrollUp",
|
||||
"MouseWheelDown": "ScrollDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"AltShiftUp": "SpawnMultiCursorUp",
|
||||
"AltShiftDown": "SpawnMultiCursorDown",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,22 @@
|
||||
package action
|
||||
|
||||
import "github.com/zyedidia/micro/internal/buffer"
|
||||
import "github.com/zyedidia/micro/v2/internal/buffer"
|
||||
|
||||
var InfoBar *InfoPane
|
||||
var LogBufPane *BufPane
|
||||
|
||||
// InitGlobals initializes the log buffer and the info bar
|
||||
func InitGlobals() {
|
||||
InfoBar = NewInfoBar()
|
||||
buffer.LogBuf = buffer.NewBufferFromString("", "Log", buffer.BTLog)
|
||||
}
|
||||
|
||||
// GetInfoBar returns the infobar pane
|
||||
func GetInfoBar() *InfoPane {
|
||||
return InfoBar
|
||||
}
|
||||
|
||||
// WriteLog writes a string to the log buffer
|
||||
func WriteLog(s string) {
|
||||
buffer.WriteLog(s)
|
||||
if LogBufPane != nil {
|
||||
@@ -28,7 +31,10 @@ func WriteLog(s string) {
|
||||
}
|
||||
}
|
||||
|
||||
func OpenLogBuf(h *BufPane) {
|
||||
// OpenLogBuf opens the log buffer from the current bufpane
|
||||
// If the current bufpane is a log buffer nothing happens,
|
||||
// otherwise the log buffer is opened in a horizontal split
|
||||
func (h *BufPane) OpenLogBuf() {
|
||||
LogBufPane = h.HSplitBuf(buffer.LogBuf)
|
||||
LogBufPane.CursorEnd()
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// This file is meant (for now) for autocompletion in command mode, not
|
||||
@@ -197,7 +197,7 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
return completions, suggestions
|
||||
}
|
||||
|
||||
// OptionComplete autocompletes options
|
||||
// PluginCmdComplete autocompletes the plugin command
|
||||
func PluginCmdComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
c := b.GetActiveCursor()
|
||||
input, argstart := buffer.GetArg(b)
|
||||
@@ -253,51 +253,28 @@ func PluginComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
return completions, suggestions
|
||||
}
|
||||
|
||||
// // MakeCompletion registers a function from a plugin for autocomplete commands
|
||||
// func MakeCompletion(function string) Completion {
|
||||
// pluginCompletions = append(pluginCompletions, LuaFunctionComplete(function))
|
||||
// return Completion(-len(pluginCompletions))
|
||||
// }
|
||||
// PluginNameComplete completes with the names of loaded plugins
|
||||
// func PluginNameComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
// c := b.GetActiveCursor()
|
||||
// input, argstart := buffer.GetArg(b)
|
||||
//
|
||||
// // PluginComplete autocompletes from plugin function
|
||||
// func PluginComplete(complete Completion, input string) (chosen string, suggestions []string) {
|
||||
// idx := int(-complete) - 1
|
||||
//
|
||||
// if len(pluginCompletions) <= idx {
|
||||
// return "", nil
|
||||
// }
|
||||
// suggestions = pluginCompletions[idx](input)
|
||||
//
|
||||
// if len(suggestions) == 1 {
|
||||
// chosen = suggestions[0]
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // PluginCmdComplete completes with possible choices for the `> plugin` command
|
||||
// func PluginCmdComplete(input string) (chosen string, suggestions []string) {
|
||||
// for _, cmd := range []string{"install", "remove", "search", "update", "list"} {
|
||||
// if strings.HasPrefix(cmd, input) {
|
||||
// suggestions = append(suggestions, cmd)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if len(suggestions) == 1 {
|
||||
// chosen = suggestions[0]
|
||||
// }
|
||||
// return chosen, suggestions
|
||||
// }
|
||||
//
|
||||
// // PluginnameComplete completes with the names of loaded plugins
|
||||
// func PluginNameComplete(input string) (chosen string, suggestions []string) {
|
||||
// for _, pp := range GetAllPluginPackages() {
|
||||
// var suggestions []string
|
||||
// for _, pp := range config.GetAllPluginPackages(nil) {
|
||||
// if strings.HasPrefix(pp.Name, input) {
|
||||
// suggestions = append(suggestions, pp.Name)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if len(suggestions) == 1 {
|
||||
// chosen = suggestions[0]
|
||||
// sort.Strings(suggestions)
|
||||
// completions := make([]string, len(suggestions))
|
||||
// for i := range suggestions {
|
||||
// completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
|
||||
// }
|
||||
// return chosen, suggestions
|
||||
// return completions, suggestions
|
||||
// }
|
||||
|
||||
// // MakeCompletion registers a function from a plugin for autocomplete commands
|
||||
// func MakeCompletion(function string) Completion {
|
||||
// pluginCompletions = append(pluginCompletions, LuaFunctionComplete(function))
|
||||
// return Completion(-len(pluginCompletions))
|
||||
// }
|
||||
|
||||
@@ -4,9 +4,10 @@ import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/internal/display"
|
||||
"github.com/zyedidia/micro/internal/info"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
"github.com/zyedidia/micro/v2/internal/info"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -17,10 +18,10 @@ type InfoPane struct {
|
||||
*info.InfoBuf
|
||||
}
|
||||
|
||||
func NewInfoPane(ib *info.InfoBuf, w display.BWindow) *InfoPane {
|
||||
func NewInfoPane(ib *info.InfoBuf, w display.BWindow, tab *Tab) *InfoPane {
|
||||
ip := new(InfoPane)
|
||||
ip.InfoBuf = ib
|
||||
ip.BufPane = NewBufPane(ib.Buffer, w)
|
||||
ip.BufPane = NewBufPane(ib.Buffer, w, tab)
|
||||
|
||||
return ip
|
||||
}
|
||||
@@ -28,7 +29,7 @@ func NewInfoPane(ib *info.InfoBuf, w display.BWindow) *InfoPane {
|
||||
func NewInfoBar() *InfoPane {
|
||||
ib := info.NewBuffer()
|
||||
w := display.NewInfoWindow(ib)
|
||||
return NewInfoPane(ib, w)
|
||||
return NewInfoPane(ib, w, nil)
|
||||
}
|
||||
|
||||
func (h *InfoPane) Close() {
|
||||
@@ -68,11 +69,12 @@ func (h *InfoPane) HandleEvent(event tcell.Event) {
|
||||
h.EventCallback(resp)
|
||||
}
|
||||
}
|
||||
case *tcell.EventMouse:
|
||||
default:
|
||||
h.BufPane.HandleEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
// DoKeyEvent executes a key event for the command bar, doing any overridden actions
|
||||
func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
|
||||
done := false
|
||||
if action, ok := BufKeyBindings[e]; ok {
|
||||
@@ -85,7 +87,7 @@ func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
|
||||
for s, a := range InfoOverrides {
|
||||
// TODO this is a hack and really we should have support
|
||||
// for having binding overrides for different buffers
|
||||
if strings.Contains(estr, s) {
|
||||
if strings.HasPrefix(estr, s) {
|
||||
done = true
|
||||
a(h)
|
||||
break
|
||||
@@ -122,6 +124,7 @@ var InfoNones = []string{
|
||||
"HalfPageDown",
|
||||
"ToggleHelp",
|
||||
"ToggleKeyMenu",
|
||||
"ToggleDiffGutter",
|
||||
"ToggleRuler",
|
||||
"JumpLine",
|
||||
"ClearStatus",
|
||||
@@ -147,14 +150,13 @@ var InfoNones = []string{
|
||||
"SkipMultiCursor",
|
||||
}
|
||||
|
||||
// InfoOverrides is the list of actions which have been overriden
|
||||
// InfoOverrides is the list of actions which have been overridden
|
||||
// by the infohandler
|
||||
var InfoOverrides = map[string]InfoKeyAction{
|
||||
"CursorUp": (*InfoPane).CursorUp,
|
||||
"CursorDown": (*InfoPane).CursorDown,
|
||||
"InsertNewline": (*InfoPane).InsertNewline,
|
||||
"Autocomplete": (*InfoPane).Autocomplete,
|
||||
"OutdentLine": (*InfoPane).CycleBack,
|
||||
"Escape": (*InfoPane).Escape,
|
||||
"Quit": (*InfoPane).Quit,
|
||||
"QuitAll": (*InfoPane).QuitAll,
|
||||
@@ -185,21 +187,17 @@ func (h *InfoPane) Autocomplete() {
|
||||
args := bytes.Split(l, []byte{' '})
|
||||
cmd := string(args[0])
|
||||
|
||||
if len(args) == 1 {
|
||||
b.Autocomplete(CommandComplete)
|
||||
} else {
|
||||
if action, ok := commands[cmd]; ok {
|
||||
if h.PromptType == "Command" {
|
||||
if len(args) == 1 {
|
||||
b.Autocomplete(CommandComplete)
|
||||
} else if action, ok := commands[cmd]; ok {
|
||||
if action.completer != nil {
|
||||
b.Autocomplete(action.completer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CycleBack cycles back in the autocomplete suggestion list
|
||||
func (h *InfoPane) CycleBack() {
|
||||
if h.Buf.HasSuggestions {
|
||||
h.Buf.CycleAutocomplete(false)
|
||||
} else {
|
||||
// by default use filename autocompletion
|
||||
b.Autocomplete(buffer.FileComplete)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/internal/display"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
)
|
||||
|
||||
type Pane interface {
|
||||
@@ -11,4 +11,6 @@ type Pane interface {
|
||||
SetID(i uint64)
|
||||
Name() string
|
||||
Close()
|
||||
SetTab(t *Tab)
|
||||
Tab() *Tab
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/display"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -13,17 +13,17 @@ type RawPane struct {
|
||||
*BufPane
|
||||
}
|
||||
|
||||
func NewRawPaneFromWin(b *buffer.Buffer, win display.BWindow) *RawPane {
|
||||
func NewRawPaneFromWin(b *buffer.Buffer, win display.BWindow, tab *Tab) *RawPane {
|
||||
rh := new(RawPane)
|
||||
rh.BufPane = NewBufPane(b, win)
|
||||
rh.BufPane = NewBufPane(b, win, tab)
|
||||
|
||||
return rh
|
||||
}
|
||||
|
||||
func NewRawPane() *RawPane {
|
||||
func NewRawPane(tab *Tab) *RawPane {
|
||||
b := buffer.NewBufferFromString("", "", buffer.BTRaw)
|
||||
w := display.NewBufWindow(0, 0, 0, 0, b)
|
||||
return NewRawPaneFromWin(b, w)
|
||||
return NewRawPaneFromWin(b, w, tab)
|
||||
}
|
||||
|
||||
func (h *RawPane) HandleEvent(event tcell.Event) {
|
||||
@@ -35,6 +35,13 @@ func (h *RawPane) HandleEvent(event tcell.Event) {
|
||||
}
|
||||
|
||||
h.Buf.Insert(h.Cursor.Loc, reflect.TypeOf(event).String()[7:])
|
||||
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
h.Buf.Insert(h.Cursor.Loc, fmt.Sprintf(": %s", e.Name()))
|
||||
}
|
||||
|
||||
h.Buf.Insert(h.Cursor.Loc, fmt.Sprintf(": %q\n", event.EscSeq()))
|
||||
|
||||
h.Relocate()
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/display"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/views"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/views"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -91,6 +91,7 @@ func (t *TabList) Resize() {
|
||||
t.List[0].Node.Resize(w, h-iOffset)
|
||||
t.List[0].Resize()
|
||||
}
|
||||
t.TabWindow.Resize(w, h)
|
||||
}
|
||||
|
||||
// HandleEvent checks for a resize event or a mouse event on the tab bar
|
||||
@@ -103,9 +104,22 @@ func (t *TabList) HandleEvent(event tcell.Event) {
|
||||
mx, my := e.Position()
|
||||
switch e.Buttons() {
|
||||
case tcell.Button1:
|
||||
ind := t.LocFromVisual(buffer.Loc{mx, my})
|
||||
if ind != -1 {
|
||||
t.SetActive(ind)
|
||||
if my == t.Y && mx == 0 {
|
||||
t.Scroll(-4)
|
||||
return
|
||||
} else if my == t.Y && mx == t.Width-1 {
|
||||
t.Scroll(4)
|
||||
return
|
||||
}
|
||||
if len(t.List) > 1 {
|
||||
ind := t.LocFromVisual(buffer.Loc{mx, my})
|
||||
if ind != -1 {
|
||||
t.SetActive(ind)
|
||||
return
|
||||
}
|
||||
if my == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
case tcell.WheelUp:
|
||||
if my == t.Y {
|
||||
@@ -152,6 +166,8 @@ type Tab struct {
|
||||
active int
|
||||
|
||||
resizing *views.Node // node currently being resized
|
||||
// captures whether the mouse is released
|
||||
release bool
|
||||
}
|
||||
|
||||
// NewTabFromBuffer creates a new tab from the given buffer
|
||||
@@ -160,7 +176,7 @@ func NewTabFromBuffer(x, y, width, height int, b *buffer.Buffer) *Tab {
|
||||
t.Node = views.NewRoot(x, y, width, height)
|
||||
t.UIWindow = display.NewUIWindow(t.Node)
|
||||
|
||||
e := NewBufPaneFromBuf(b)
|
||||
e := NewBufPaneFromBuf(b, t)
|
||||
e.SetID(t.ID())
|
||||
|
||||
t.Panes = append(t.Panes, e)
|
||||
@@ -171,7 +187,7 @@ func NewTabFromPane(x, y, width, height int, pane Pane) *Tab {
|
||||
t := new(Tab)
|
||||
t.Node = views.NewRoot(x, y, width, height)
|
||||
t.UIWindow = display.NewUIWindow(t.Node)
|
||||
|
||||
pane.SetTab(t)
|
||||
pane.SetID(t.ID())
|
||||
|
||||
t.Panes = append(t.Panes, pane)
|
||||
@@ -189,7 +205,8 @@ func (t *Tab) HandleEvent(event tcell.Event) {
|
||||
mx, my := e.Position()
|
||||
switch e.Buttons() {
|
||||
case tcell.Button1:
|
||||
resizeID := t.GetMouseSplitID(buffer.Loc{mx, my})
|
||||
wasReleased := t.release
|
||||
t.release = false
|
||||
if t.resizing != nil {
|
||||
var size int
|
||||
if t.resizing.Kind == views.STVert {
|
||||
@@ -202,21 +219,25 @@ func (t *Tab) HandleEvent(event tcell.Event) {
|
||||
return
|
||||
}
|
||||
|
||||
if resizeID != 0 {
|
||||
t.resizing = t.GetNode(uint64(resizeID))
|
||||
return
|
||||
}
|
||||
if wasReleased {
|
||||
resizeID := t.GetMouseSplitID(buffer.Loc{mx, my})
|
||||
if resizeID != 0 {
|
||||
t.resizing = t.GetNode(uint64(resizeID))
|
||||
return
|
||||
}
|
||||
|
||||
for i, p := range t.Panes {
|
||||
v := p.GetView()
|
||||
inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height
|
||||
if inpane {
|
||||
t.SetActive(i)
|
||||
break
|
||||
for i, p := range t.Panes {
|
||||
v := p.GetView()
|
||||
inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height
|
||||
if inpane {
|
||||
t.SetActive(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case tcell.ButtonNone:
|
||||
t.resizing = nil
|
||||
t.release = true
|
||||
default:
|
||||
for _, p := range t.Panes {
|
||||
v := p.GetView()
|
||||
@@ -277,6 +298,10 @@ func (t *Tab) Resize() {
|
||||
}
|
||||
|
||||
// CurPane returns the currently active pane
|
||||
func (t *Tab) CurPane() Pane {
|
||||
return t.Panes[t.active]
|
||||
func (t *Tab) CurPane() *BufPane {
|
||||
p, ok := t.Panes[t.active].(*BufPane)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
@@ -3,27 +3,39 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/internal/shell"
|
||||
"github.com/zyedidia/micro/pkg/shellwords"
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
)
|
||||
|
||||
// TermEmuSupported is a constant that marks if the terminal emulator is supported
|
||||
const TermEmuSupported = true
|
||||
|
||||
func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callback string, userargs []interface{}) error {
|
||||
args, err := shellwords.Split(input)
|
||||
// RunTermEmulator starts a terminal emulator from a bufpane with the given input (command)
|
||||
// if wait is true it will wait for the user to exit by pressing enter once the executable has terminated
|
||||
// if getOutput is true it will redirect the stdout of the process to a pipe which will be passed to the
|
||||
// callback which is a function that takes a string and a list of optional user arguments
|
||||
func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
|
||||
args, err := shellquote.Split(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
t := new(shell.Terminal)
|
||||
t.Start(args, getOutput, wait, callback, userargs)
|
||||
|
||||
id := h.ID()
|
||||
h.AddTab()
|
||||
id = MainTab().Panes[0].ID()
|
||||
id := MainTab().Panes[0].ID()
|
||||
|
||||
v := h.GetView()
|
||||
MainTab().Panes[0] = NewTermPane(v.X, v.Y, v.Width, v.Height, t, id)
|
||||
|
||||
tp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
MainTab().Panes[0] = tp
|
||||
MainTab().SetActive(0)
|
||||
|
||||
return nil
|
||||
|
||||
@@ -4,8 +4,10 @@ package action
|
||||
|
||||
import "errors"
|
||||
|
||||
// TermEmuSupported is a constant that marks if the terminal emulator is supported
|
||||
const TermEmuSupported = false
|
||||
|
||||
func RunTermEmulator(input string, wait bool, getOutput bool, callback string, userargs []interface{}) error {
|
||||
// RunTermEmulator returns an error for unsupported systems (non-unix systems
|
||||
func RunTermEmulator(input string, wait bool, getOutput bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
|
||||
return errors.New("Unsupported operating system")
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
|
||||
"github.com/zyedidia/clipboard"
|
||||
"github.com/zyedidia/micro/internal/display"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/zyedidia/tcell"
|
||||
"github.com/zyedidia/terminal"
|
||||
)
|
||||
@@ -17,15 +18,21 @@ type TermPane struct {
|
||||
|
||||
mouseReleased bool
|
||||
id uint64
|
||||
tab *Tab
|
||||
}
|
||||
|
||||
func NewTermPane(x, y, w, h int, t *shell.Terminal, id uint64) *TermPane {
|
||||
func NewTermPane(x, y, w, h int, t *shell.Terminal, id uint64, tab *Tab) (*TermPane, error) {
|
||||
if !TermEmuSupported {
|
||||
return nil, errors.New("Terminal emulator is not supported on this system")
|
||||
}
|
||||
|
||||
th := new(TermPane)
|
||||
th.Terminal = t
|
||||
th.id = id
|
||||
th.mouseReleased = true
|
||||
th.Window = display.NewTermWindow(x, y, w, h, t)
|
||||
return th
|
||||
th.tab = tab
|
||||
return th, nil
|
||||
}
|
||||
|
||||
func (t *TermPane) ID() uint64 {
|
||||
@@ -36,6 +43,14 @@ func (t *TermPane) SetID(i uint64) {
|
||||
t.id = i
|
||||
}
|
||||
|
||||
func (t *TermPane) SetTab(tab *Tab) {
|
||||
t.tab = tab
|
||||
}
|
||||
|
||||
func (t *TermPane) Tab() *Tab {
|
||||
return t.tab
|
||||
}
|
||||
|
||||
func (t *TermPane) Close() {}
|
||||
|
||||
func (t *TermPane) Quit() {
|
||||
@@ -80,8 +95,12 @@ func (t *TermPane) HandleEvent(event tcell.Event) {
|
||||
} else if t.Status != shell.TTDone {
|
||||
t.WriteString(event.EscSeq())
|
||||
}
|
||||
} else if _, ok := event.(*tcell.EventPaste); ok {
|
||||
if t.Status != shell.TTDone {
|
||||
t.WriteString(event.EscSeq())
|
||||
}
|
||||
} else if e, ok := event.(*tcell.EventMouse); e != nil && (!ok || t.State.Mode(terminal.ModeMouseMask)) {
|
||||
t.WriteString(event.EscSeq())
|
||||
// t.WriteString(event.EscSeq())
|
||||
} else if e != nil {
|
||||
x, y := e.Position()
|
||||
v := t.GetView()
|
||||
|
||||
@@ -6,9 +6,8 @@ import (
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// A Completer is a function that takes a buffer and returns info
|
||||
@@ -54,7 +53,7 @@ func (b *Buffer) CycleAutocomplete(forward bool) {
|
||||
start := c.Loc
|
||||
end := c.Loc
|
||||
if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 {
|
||||
start = end.Move(-utf8.RuneCountInString(b.Completions[prevSuggestion]), b)
|
||||
start = end.Move(-util.CharacterCountInString(b.Completions[prevSuggestion]), b)
|
||||
} else {
|
||||
// end = start.Move(1, b)
|
||||
}
|
||||
@@ -82,7 +81,7 @@ func GetWord(b *Buffer) ([]byte, int) {
|
||||
|
||||
args := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
|
||||
input := args[len(args)-1]
|
||||
return input, c.X - utf8.RuneCount(input)
|
||||
return input, c.X - util.CharacterCount(input)
|
||||
}
|
||||
|
||||
// GetArg gets the most recent word (separated by ' ' only)
|
||||
@@ -98,7 +97,7 @@ func GetArg(b *Buffer) (string, int) {
|
||||
if i == len(args)-1 {
|
||||
break
|
||||
}
|
||||
argstart += utf8.RuneCount(a) + 1
|
||||
argstart += util.CharacterCount(a) + 1
|
||||
}
|
||||
|
||||
return input, argstart
|
||||
@@ -162,7 +161,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
|
||||
return []string{}, []string{}
|
||||
}
|
||||
|
||||
inputLen := utf8.RuneCount(input)
|
||||
inputLen := util.CharacterCount(input)
|
||||
|
||||
suggestionsSet := make(map[string]struct{})
|
||||
|
||||
@@ -171,7 +170,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
|
||||
l := b.LineBytes(i)
|
||||
words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
|
||||
for _, w := range words {
|
||||
if bytes.HasPrefix(w, input) && utf8.RuneCount(w) > inputLen {
|
||||
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
|
||||
strw := string(w)
|
||||
if _, ok := suggestionsSet[strw]; !ok {
|
||||
suggestionsSet[strw] = struct{}{}
|
||||
@@ -184,7 +183,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
|
||||
l := b.LineBytes(i)
|
||||
words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
|
||||
for _, w := range words {
|
||||
if bytes.HasPrefix(w, input) && utf8.RuneCount(w) > inputLen {
|
||||
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
|
||||
strw := string(w)
|
||||
if _, ok := suggestionsSet[strw]; !ok {
|
||||
suggestionsSet[strw] = struct{}{}
|
||||
|
||||
@@ -3,13 +3,13 @@ package buffer
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"golang.org/x/text/encoding"
|
||||
)
|
||||
|
||||
@@ -17,7 +17,9 @@ const backupMsg = `A backup was detected for this file. This likely means that m
|
||||
crashed while editing this file, or another instance of micro is currently
|
||||
editing this file.
|
||||
|
||||
The backup was created on %s.
|
||||
The backup was created on %s, and the file is
|
||||
|
||||
%s
|
||||
|
||||
* 'recover' will apply the backup as unsaved changes to the current buffer.
|
||||
When the buffer is closed, the backup will be removed.
|
||||
@@ -28,29 +30,25 @@ Options: [r]ecover, [i]gnore: `
|
||||
|
||||
// Backup saves the current buffer to ConfigDir/backups
|
||||
func (b *Buffer) Backup(checkTime bool) error {
|
||||
if !b.Settings["backup"].(bool) || b.Path == "" {
|
||||
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
|
||||
return nil
|
||||
}
|
||||
|
||||
if checkTime {
|
||||
sub := time.Now().Sub(b.lastbackup)
|
||||
if sub < time.Duration(backup_time)*time.Millisecond {
|
||||
log.Println("Backup event but not enough time has passed", sub)
|
||||
if sub < time.Duration(backupTime)*time.Millisecond {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
b.lastbackup = time.Now()
|
||||
|
||||
backupdir := config.ConfigDir + "/backups/"
|
||||
backupdir := filepath.Join(config.ConfigDir, "backups")
|
||||
if _, err := os.Stat(backupdir); os.IsNotExist(err) {
|
||||
os.Mkdir(backupdir, os.ModePerm)
|
||||
log.Println("Creating backup dir")
|
||||
}
|
||||
|
||||
name := backupdir + util.EscapePath(b.AbsPath)
|
||||
|
||||
log.Println("Backing up to", name)
|
||||
name := filepath.Join(backupdir, util.EscapePath(b.AbsPath))
|
||||
|
||||
err := overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
|
||||
if len(b.lines) == 0 {
|
||||
@@ -74,31 +72,31 @@ func (b *Buffer) Backup(checkTime bool) error {
|
||||
}
|
||||
}
|
||||
return
|
||||
})
|
||||
}, false)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveBackup removes any backup file associated with this buffer
|
||||
func (b *Buffer) RemoveBackup() {
|
||||
if !b.Settings["backup"].(bool) || b.Path == "" {
|
||||
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
|
||||
return
|
||||
}
|
||||
f := config.ConfigDir + "/backups/" + util.EscapePath(b.AbsPath)
|
||||
f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
|
||||
os.Remove(f)
|
||||
}
|
||||
|
||||
// ApplyBackup applies the corresponding backup file to this buffer (if one exists)
|
||||
// Returns true if a backup was applied
|
||||
func (b *Buffer) ApplyBackup(fsize int64) bool {
|
||||
if b.Settings["backup"].(bool) && len(b.Path) > 0 {
|
||||
backupfile := config.ConfigDir + "/backups/" + util.EscapePath(b.AbsPath)
|
||||
if b.Settings["backup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
|
||||
backupfile := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
|
||||
if info, err := os.Stat(backupfile); err == nil {
|
||||
backup, err := os.Open(backupfile)
|
||||
if err == nil {
|
||||
defer backup.Close()
|
||||
t := info.ModTime()
|
||||
msg := fmt.Sprintf(backupMsg, t.Format("Mon Jan _2 at 15:04, 2006"))
|
||||
msg := fmt.Sprintf(backupMsg, t.Format("Mon Jan _2 at 15:04, 2006"), util.EscapePath(b.AbsPath))
|
||||
choice := screen.TermPrompt(msg, []string{"r", "i", "recover", "ignore"}, true)
|
||||
|
||||
if choice%2 == 0 {
|
||||
|
||||
@@ -1,35 +1,42 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
. "github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/pkg/highlight"
|
||||
dmp "github.com/sergi/go-diff/diffmatchpatch"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/micro/v2/pkg/highlight"
|
||||
"golang.org/x/text/encoding/htmlindex"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
const backup_time = 8000
|
||||
const backupTime = 8000
|
||||
|
||||
var (
|
||||
// OpenBuffers is a list of the currently open buffers
|
||||
OpenBuffers []*Buffer
|
||||
LogBuf *Buffer
|
||||
// LogBuf is a reference to the log buffer which can be opened with the
|
||||
// `> log` command
|
||||
LogBuf *Buffer
|
||||
)
|
||||
|
||||
// The BufType defines what kind of buffer this is
|
||||
@@ -41,16 +48,29 @@ type BufType struct {
|
||||
}
|
||||
|
||||
var (
|
||||
// BTDefault is a default buffer
|
||||
BTDefault = BufType{0, false, false, true}
|
||||
BTHelp = BufType{1, true, true, true}
|
||||
BTLog = BufType{2, true, true, false}
|
||||
// BTHelp is a help buffer
|
||||
BTHelp = BufType{1, true, true, true}
|
||||
// BTLog is a log buffer
|
||||
BTLog = BufType{2, true, true, false}
|
||||
// BTScratch is a buffer that cannot be saved (for scratch work)
|
||||
BTScratch = BufType{3, false, true, false}
|
||||
BTRaw = BufType{4, false, true, false}
|
||||
BTInfo = BufType{5, false, true, false}
|
||||
// BTRaw is is a buffer that shows raw terminal events
|
||||
BTRaw = BufType{4, false, true, false}
|
||||
// BTInfo is a buffer for inputting information
|
||||
BTInfo = BufType{5, false, true, false}
|
||||
// BTStdout is a buffer that only writes to stdout
|
||||
// when closed
|
||||
BTStdout = BufType{6, false, true, true}
|
||||
|
||||
// ErrFileTooLarge is returned when the file is too large to hash
|
||||
// (fastdirty is automatically enabled)
|
||||
ErrFileTooLarge = errors.New("File is too large to hash")
|
||||
)
|
||||
|
||||
// SharedBuffer is a struct containing info that is shared among buffers
|
||||
// that have the same file open
|
||||
type SharedBuffer struct {
|
||||
*LineArray
|
||||
// Stores the last modification time of the file the buffer is pointing to
|
||||
@@ -58,23 +78,103 @@ type SharedBuffer struct {
|
||||
// Type of the buffer (e.g. help, raw, scratch etc..)
|
||||
Type BufType
|
||||
|
||||
// Path to the file on disk
|
||||
Path string
|
||||
// Absolute path to the file on disk
|
||||
AbsPath string
|
||||
// Name of the buffer on the status line
|
||||
name string
|
||||
|
||||
toStdout bool
|
||||
|
||||
// Settings customized by the user
|
||||
Settings map[string]interface{}
|
||||
|
||||
Suggestions []string
|
||||
Completions []string
|
||||
CurSuggestion int
|
||||
|
||||
Messages []*Message
|
||||
|
||||
updateDiffTimer *time.Timer
|
||||
diffBase []byte
|
||||
diffBaseLineCount int
|
||||
diffLock sync.RWMutex
|
||||
diff map[int]DiffStatus
|
||||
|
||||
// counts the number of edits
|
||||
// resets every backupTime edits
|
||||
lastbackup time.Time
|
||||
|
||||
// ReloadDisabled allows the user to disable reloads if they
|
||||
// are viewing a file that is constantly changing
|
||||
ReloadDisabled bool
|
||||
|
||||
isModified bool
|
||||
// Whether or not suggestions can be autocompleted must be shared because
|
||||
// it changes based on how the buffer has changed
|
||||
HasSuggestions bool
|
||||
|
||||
// The Highlighter struct actually performs the highlighting
|
||||
Highlighter *highlight.Highlighter
|
||||
// SyntaxDef represents the syntax highlighting definition being used
|
||||
// This stores the highlighting rules and filetype detection info
|
||||
SyntaxDef *highlight.Def
|
||||
|
||||
ModifiedThisFrame bool
|
||||
|
||||
// Hash of the original buffer -- empty if fastdirty is on
|
||||
origHash [md5.Size]byte
|
||||
}
|
||||
|
||||
func (b *SharedBuffer) insert(pos Loc, value []byte) {
|
||||
b.isModified = true
|
||||
b.HasSuggestions = false
|
||||
b.LineArray.insert(pos, value)
|
||||
|
||||
inslines := bytes.Count(value, []byte{'\n'})
|
||||
b.MarkModified(pos.Y, pos.Y+inslines)
|
||||
}
|
||||
func (b *SharedBuffer) remove(start, end Loc) []byte {
|
||||
b.isModified = true
|
||||
b.HasSuggestions = false
|
||||
defer b.MarkModified(start.Y, end.Y)
|
||||
return b.LineArray.remove(start, end)
|
||||
}
|
||||
|
||||
// MarkModified marks the buffer as modified for this frame
|
||||
// and performs rehighlighting if syntax highlighting is enabled
|
||||
func (b *SharedBuffer) MarkModified(start, end int) {
|
||||
b.ModifiedThisFrame = true
|
||||
|
||||
if !b.Settings["syntax"].(bool) || b.SyntaxDef == nil {
|
||||
return
|
||||
}
|
||||
|
||||
start = util.Clamp(start, 0, len(b.lines)-1)
|
||||
end = util.Clamp(end, 0, len(b.lines)-1)
|
||||
|
||||
l := -1
|
||||
for i := start; i <= end; i++ {
|
||||
l = util.Max(b.Highlighter.ReHighlightStates(b, i), l)
|
||||
}
|
||||
b.Highlighter.HighlightMatches(b, start, l)
|
||||
}
|
||||
|
||||
// DisableReload disables future reloads of this sharedbuffer
|
||||
func (b *SharedBuffer) DisableReload() {
|
||||
b.ReloadDisabled = true
|
||||
}
|
||||
|
||||
const (
|
||||
DSUnchanged = 0
|
||||
DSAdded = 1
|
||||
DSModified = 2
|
||||
DSDeletedAbove = 3
|
||||
)
|
||||
|
||||
type DiffStatus byte
|
||||
|
||||
// Buffer stores the main information about a currently open file including
|
||||
// the actual text (in a LineArray), the undo/redo stack (in an EventHandler)
|
||||
// all the cursors, the syntax highlighting info, the settings for the buffer
|
||||
@@ -89,42 +189,25 @@ type Buffer struct {
|
||||
cursors []*Cursor
|
||||
curCursor int
|
||||
StartCursor Loc
|
||||
|
||||
// Path to the file on disk
|
||||
Path string
|
||||
// Absolute path to the file on disk
|
||||
AbsPath string
|
||||
// Name of the buffer on the status line
|
||||
name string
|
||||
|
||||
SyntaxDef *highlight.Def
|
||||
Highlighter *highlight.Highlighter
|
||||
|
||||
// Hash of the original buffer -- empty if fastdirty is on
|
||||
origHash [md5.Size]byte
|
||||
|
||||
// Settings customized by the user
|
||||
Settings map[string]interface{}
|
||||
|
||||
Suggestions []string
|
||||
Completions []string
|
||||
CurSuggestion int
|
||||
|
||||
Messages []*Message
|
||||
|
||||
// counts the number of edits
|
||||
// resets every backup_time edits
|
||||
lastbackup time.Time
|
||||
}
|
||||
|
||||
// NewBufferFromFile opens a new buffer using the given path
|
||||
// It will also automatically handle `~`, and line/column with filename:l:c
|
||||
// It will return an empty buffer if the path does not exist
|
||||
// and an error if the file is a directory
|
||||
func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
|
||||
// NewBufferFromFileAtLoc opens a new buffer with a given cursor location
|
||||
// If cursorLoc is {-1, -1} the location does not overwrite what the cursor location
|
||||
// would otherwise be (start of file, or saved cursor position if `savecursor` is
|
||||
// enabled)
|
||||
func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer, error) {
|
||||
var err error
|
||||
filename, cursorPos := GetPathAndCursorPosition(path)
|
||||
filename, err = ReplaceHome(filename)
|
||||
filename := path
|
||||
if config.GetGlobalOption("parsecursor").(bool) && cursorLoc.X == -1 && cursorLoc.Y == -1 {
|
||||
var cursorPos []string
|
||||
filename, cursorPos = util.GetPathAndCursorPosition(filename)
|
||||
cursorLoc, err = ParseCursorLocation(cursorPos)
|
||||
if err != nil {
|
||||
cursorLoc = Loc{-1, -1}
|
||||
}
|
||||
}
|
||||
|
||||
filename, err = util.ReplaceHome(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -133,27 +216,35 @@ func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
|
||||
fileInfo, _ := os.Stat(filename)
|
||||
|
||||
if err == nil && fileInfo.IsDir() {
|
||||
return nil, errors.New(filename + " is a directory")
|
||||
return nil, errors.New("Error: " + filename + " is a directory and cannot be opened")
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
cursorLoc, cursorerr := ParseCursorLocation(cursorPos)
|
||||
if cursorerr != nil {
|
||||
cursorLoc = Loc{-1, -1}
|
||||
}
|
||||
|
||||
var buf *Buffer
|
||||
if err != nil {
|
||||
// File does not exist -- create an empty buffer with that name
|
||||
buf = NewBufferFromString("", filename, btype)
|
||||
} else {
|
||||
buf = NewBuffer(file, FSize(file), filename, cursorLoc, btype)
|
||||
buf = NewBuffer(file, util.FSize(file), filename, cursorLoc, btype)
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// NewBufferFromFile opens a new buffer using the given path
|
||||
// It will also automatically handle `~`, and line/column with filename:l:c
|
||||
// It will return an empty buffer if the path does not exist
|
||||
// and an error if the file is a directory
|
||||
func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
|
||||
return NewBufferFromFileAtLoc(path, btype, Loc{-1, -1})
|
||||
}
|
||||
|
||||
// NewBufferFromStringAtLoc creates a new buffer containing the given string with a cursor loc
|
||||
func NewBufferFromStringAtLoc(text, path string, btype BufType, cursorLoc Loc) *Buffer {
|
||||
return NewBuffer(strings.NewReader(text), int64(len(text)), path, cursorLoc, btype)
|
||||
}
|
||||
|
||||
// NewBufferFromString creates a new buffer containing the given string
|
||||
func NewBufferFromString(text, path string, btype BufType) *Buffer {
|
||||
return NewBuffer(strings.NewReader(text), int64(len(text)), path, Loc{-1, -1}, btype)
|
||||
@@ -169,22 +260,6 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
|
||||
b := new(Buffer)
|
||||
|
||||
b.Settings = config.DefaultCommonSettings()
|
||||
for k, v := range config.GlobalSettings {
|
||||
if _, ok := b.Settings[k]; ok {
|
||||
b.Settings[k] = v
|
||||
}
|
||||
}
|
||||
config.InitLocalSettings(b.Settings, path)
|
||||
|
||||
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
|
||||
if err != nil {
|
||||
enc = unicode.UTF8
|
||||
b.Settings["encoding"] = "utf-8"
|
||||
}
|
||||
|
||||
reader := transform.NewReader(r, enc.NewDecoder())
|
||||
|
||||
found := false
|
||||
if len(path) > 0 {
|
||||
for _, buf := range OpenBuffers {
|
||||
@@ -196,28 +271,58 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
}
|
||||
}
|
||||
|
||||
b.Path = path
|
||||
b.AbsPath = absPath
|
||||
|
||||
if !found {
|
||||
b.SharedBuffer = new(SharedBuffer)
|
||||
b.Type = btype
|
||||
|
||||
b.AbsPath = absPath
|
||||
b.Path = path
|
||||
|
||||
b.Settings = config.DefaultCommonSettings()
|
||||
for k, v := range config.GlobalSettings {
|
||||
if _, ok := config.DefaultGlobalOnlySettings[k]; !ok {
|
||||
// make sure setting is not global-only
|
||||
b.Settings[k] = v
|
||||
}
|
||||
}
|
||||
config.InitLocalSettings(b.Settings, path)
|
||||
|
||||
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
|
||||
if err != nil {
|
||||
enc = unicode.UTF8
|
||||
b.Settings["encoding"] = "utf-8"
|
||||
}
|
||||
|
||||
hasBackup := b.ApplyBackup(size)
|
||||
|
||||
if !hasBackup {
|
||||
b.LineArray = NewLineArray(uint64(size), FFAuto, reader)
|
||||
reader := bufio.NewReader(transform.NewReader(r, enc.NewDecoder()))
|
||||
|
||||
var ff FileFormat = FFAuto
|
||||
|
||||
if size == 0 {
|
||||
// for empty files, use the fileformat setting instead of
|
||||
// autodetection
|
||||
switch b.Settings["fileformat"] {
|
||||
case "unix":
|
||||
ff = FFUnix
|
||||
case "dos":
|
||||
ff = FFDos
|
||||
}
|
||||
}
|
||||
|
||||
b.LineArray = NewLineArray(uint64(size), ff, reader)
|
||||
}
|
||||
b.EventHandler = NewEventHandler(b.SharedBuffer, b.cursors)
|
||||
|
||||
// The last time this file was modified
|
||||
b.UpdateModTime()
|
||||
}
|
||||
|
||||
if b.Settings["readonly"].(bool) {
|
||||
if b.Settings["readonly"].(bool) && b.Type == BTDefault {
|
||||
b.Type.Readonly = true
|
||||
}
|
||||
|
||||
// The last time this file was modified
|
||||
b.ModTime, _ = GetModTime(b.Path)
|
||||
|
||||
switch b.Endings {
|
||||
case FFUnix:
|
||||
b.Settings["fileformat"] = "unix"
|
||||
@@ -226,10 +331,11 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
}
|
||||
|
||||
b.UpdateRules()
|
||||
// init local settings again now that we know the filetype
|
||||
config.InitLocalSettings(b.Settings, b.Path)
|
||||
|
||||
if _, err := os.Stat(config.ConfigDir + "/buffers/"); os.IsNotExist(err) {
|
||||
os.Mkdir(config.ConfigDir+"/buffers/", os.ModePerm)
|
||||
if _, err := os.Stat(filepath.Join(config.ConfigDir, "buffers")); os.IsNotExist(err) {
|
||||
os.Mkdir(filepath.Join(config.ConfigDir, "buffers"), os.ModePerm)
|
||||
}
|
||||
|
||||
if startcursor.X != -1 && startcursor.Y != -1 {
|
||||
@@ -246,7 +352,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
b.AddCursor(NewCursor(b, b.StartCursor))
|
||||
b.GetActiveCursor().Relocate()
|
||||
|
||||
if !b.Settings["fastdirty"].(bool) {
|
||||
if !b.Settings["fastdirty"].(bool) && !found {
|
||||
if size > LargeFileThreshold {
|
||||
// If the file is larger than LargeFileThreshold fastdirty needs to be on
|
||||
b.Settings["fastdirty"] = true
|
||||
@@ -255,7 +361,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
}
|
||||
}
|
||||
|
||||
err = config.RunPluginFn("onBufferOpen", luar.New(ulua.L, b))
|
||||
err := config.RunPluginFn("onBufferOpen", luar.New(ulua.L, b))
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
@@ -285,18 +391,26 @@ func (b *Buffer) Fini() {
|
||||
b.Serialize()
|
||||
}
|
||||
b.RemoveBackup()
|
||||
|
||||
if b.Type == BTStdout {
|
||||
fmt.Fprint(util.Stdout, string(b.Bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
// GetName returns the name that should be displayed in the statusline
|
||||
// for this buffer
|
||||
func (b *Buffer) GetName() string {
|
||||
if b.name == "" {
|
||||
name := b.name
|
||||
if name == "" {
|
||||
if b.Path == "" {
|
||||
return "No name"
|
||||
}
|
||||
return b.Path
|
||||
name = b.Path
|
||||
}
|
||||
return b.name
|
||||
if b.Settings["basename"].(bool) {
|
||||
return path.Base(name)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
//SetName changes the name for this buffer
|
||||
@@ -304,6 +418,7 @@ func (b *Buffer) SetName(s string) {
|
||||
b.name = s
|
||||
}
|
||||
|
||||
// Insert inserts the given string of text at the start location
|
||||
func (b *Buffer) Insert(start Loc, text string) {
|
||||
if !b.Type.Readonly {
|
||||
b.EventHandler.cursors = b.cursors
|
||||
@@ -314,6 +429,7 @@ func (b *Buffer) Insert(start Loc, text string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes the characters between the start and end locations
|
||||
func (b *Buffer) Remove(start, end Loc) {
|
||||
if !b.Type.Readonly {
|
||||
b.EventHandler.cursors = b.cursors
|
||||
@@ -329,6 +445,22 @@ func (b *Buffer) FileType() string {
|
||||
return b.Settings["filetype"].(string)
|
||||
}
|
||||
|
||||
// ExternallyModified returns whether the file being edited has
|
||||
// been modified by some external process
|
||||
func (b *Buffer) ExternallyModified() bool {
|
||||
modTime, err := util.GetModTime(b.Path)
|
||||
if err == nil {
|
||||
return modTime != b.ModTime
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// UpdateModTime updates the modtime of this file
|
||||
func (b *Buffer) UpdateModTime() (err error) {
|
||||
b.ModTime, err = util.GetModTime(b.Path)
|
||||
return
|
||||
}
|
||||
|
||||
// ReOpen reloads the current buffer from disk
|
||||
func (b *Buffer) ReOpen() error {
|
||||
file, err := os.Open(b.Path)
|
||||
@@ -341,7 +473,7 @@ func (b *Buffer) ReOpen() error {
|
||||
return err
|
||||
}
|
||||
|
||||
reader := transform.NewReader(file, enc.NewDecoder())
|
||||
reader := bufio.NewReader(transform.NewReader(file, enc.NewDecoder()))
|
||||
data, err := ioutil.ReadAll(reader)
|
||||
txt := string(data)
|
||||
|
||||
@@ -350,12 +482,16 @@ func (b *Buffer) ReOpen() error {
|
||||
}
|
||||
b.EventHandler.ApplyDiff(txt)
|
||||
|
||||
b.ModTime, err = GetModTime(b.Path)
|
||||
err = b.UpdateModTime()
|
||||
if !b.Settings["fastdirty"].(bool) {
|
||||
calcHash(b, &b.origHash)
|
||||
}
|
||||
b.isModified = false
|
||||
b.RelocateCursors()
|
||||
return err
|
||||
}
|
||||
|
||||
// RelocateCursors relocates all cursors (makes sure they are in the buffer)
|
||||
func (b *Buffer) RelocateCursors() {
|
||||
for _, c := range b.cursors {
|
||||
c.Relocate()
|
||||
@@ -368,7 +504,7 @@ func (b *Buffer) RuneAt(loc Loc) rune {
|
||||
if len(line) > 0 {
|
||||
i := 0
|
||||
for len(line) > 0 {
|
||||
r, size := utf8.DecodeRune(line)
|
||||
r, _, size := util.DecodeCharacter(line)
|
||||
line = line[size:]
|
||||
i++
|
||||
|
||||
@@ -437,65 +573,144 @@ func (b *Buffer) UpdateRules() {
|
||||
if !b.Type.Syntax {
|
||||
return
|
||||
}
|
||||
rehighlight := false
|
||||
var files []*highlight.File
|
||||
for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
|
||||
ft := b.Settings["filetype"].(string)
|
||||
if ft == "off" {
|
||||
return
|
||||
}
|
||||
syntaxFile := ""
|
||||
foundDef := false
|
||||
var header *highlight.Header
|
||||
// search for the syntax file in the user's custom syntax files
|
||||
for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
|
||||
data, err := f.Data()
|
||||
if err != nil {
|
||||
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
} else {
|
||||
file, err := highlight.ParseFile(data)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
ftdetect, err := highlight.ParseFtDetect(file)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
ft := b.Settings["filetype"].(string)
|
||||
if (ft == "unknown" || ft == "") && !rehighlight {
|
||||
if highlight.MatchFiletype(ftdetect, b.Path, b.lines[0].data) {
|
||||
header := new(highlight.Header)
|
||||
header.FileType = file.FileType
|
||||
header.FtDetect = ftdetect
|
||||
b.SyntaxDef, err = highlight.ParseDef(file, header)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
rehighlight = true
|
||||
}
|
||||
} else {
|
||||
if file.FileType == ft && !rehighlight {
|
||||
header := new(highlight.Header)
|
||||
header.FileType = file.FileType
|
||||
header.FtDetect = ftdetect
|
||||
b.SyntaxDef, err = highlight.ParseDef(file, header)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
rehighlight = true
|
||||
}
|
||||
header, err = highlight.MakeHeaderYaml(data)
|
||||
file, err := highlight.ParseFile(data)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if ((ft == "unknown" || ft == "") && highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data)) || header.FileType == ft {
|
||||
syndef, err := highlight.ParseDef(file, header)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
files = append(files, file)
|
||||
b.SyntaxDef = syndef
|
||||
syntaxFile = f.Name()
|
||||
foundDef = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if b.SyntaxDef != nil {
|
||||
// search in the default syntax files
|
||||
for _, f := range config.ListRuntimeFiles(config.RTSyntaxHeader) {
|
||||
data, err := f.Data()
|
||||
if err != nil {
|
||||
screen.TermMessage("Error loading syntax header file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
header, err = highlight.MakeHeader(data)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error reading syntax header file", f.Name(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
if ft == "unknown" || ft == "" {
|
||||
if highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data) {
|
||||
syntaxFile = f.Name()
|
||||
break
|
||||
}
|
||||
} else if header.FileType == ft {
|
||||
syntaxFile = f.Name()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if syntaxFile != "" && !foundDef {
|
||||
// we found a syntax file using a syntax header file
|
||||
for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
|
||||
if f.Name() == syntaxFile {
|
||||
data, err := f.Data()
|
||||
if err != nil {
|
||||
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
file, err := highlight.ParseFile(data)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
syndef, err := highlight.ParseDef(file, header)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
b.SyntaxDef = syndef
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if b.SyntaxDef != nil && highlight.HasIncludes(b.SyntaxDef) {
|
||||
includes := highlight.GetIncludes(b.SyntaxDef)
|
||||
|
||||
var files []*highlight.File
|
||||
for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
|
||||
data, err := f.Data()
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
header, err := highlight.MakeHeaderYaml(data)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
for _, i := range includes {
|
||||
if header.FileType == i {
|
||||
file, err := highlight.ParseFile(data)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
files = append(files, file)
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(files) >= len(includes) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
highlight.ResolveIncludes(b.SyntaxDef, files)
|
||||
}
|
||||
|
||||
if b.Highlighter == nil || rehighlight {
|
||||
if b.Highlighter == nil || syntaxFile != "" {
|
||||
if b.SyntaxDef != nil {
|
||||
b.Settings["filetype"] = b.SyntaxDef.FileType
|
||||
b.Highlighter = highlight.NewHighlighter(b.SyntaxDef)
|
||||
if b.Settings["syntax"].(bool) {
|
||||
}
|
||||
} else {
|
||||
b.SyntaxDef = &highlight.EmptyDef
|
||||
}
|
||||
|
||||
if b.SyntaxDef != nil {
|
||||
b.Highlighter = highlight.NewHighlighter(b.SyntaxDef)
|
||||
if b.Settings["syntax"].(bool) {
|
||||
go func() {
|
||||
b.Highlighter.HighlightStates(b)
|
||||
}
|
||||
b.Highlighter.HighlightMatches(b, 0, b.End().Y)
|
||||
screen.Redraw()
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -512,7 +727,7 @@ func (b *Buffer) ClearMatches() {
|
||||
// depending on the settings)
|
||||
func (b *Buffer) IndentString(tabsize int) string {
|
||||
if b.Settings["tabstospaces"].(bool) {
|
||||
return Spaces(tabsize)
|
||||
return util.Spaces(tabsize)
|
||||
}
|
||||
return "\t"
|
||||
}
|
||||
@@ -600,7 +815,7 @@ func (b *Buffer) RemoveCursor(i int) {
|
||||
copy(b.cursors[i:], b.cursors[i+1:])
|
||||
b.cursors[len(b.cursors)-1] = nil
|
||||
b.cursors = b.cursors[:len(b.cursors)-1]
|
||||
b.curCursor = Clamp(b.curCursor, 0, len(b.cursors)-1)
|
||||
b.curCursor = util.Clamp(b.curCursor, 0, len(b.cursors)-1)
|
||||
b.UpdateCursors()
|
||||
}
|
||||
|
||||
@@ -622,19 +837,18 @@ func (b *Buffer) MoveLinesUp(start int, end int) {
|
||||
}
|
||||
l := string(b.LineBytes(start - 1))
|
||||
if end == len(b.lines) {
|
||||
b.Insert(
|
||||
b.insert(
|
||||
Loc{
|
||||
utf8.RuneCount(b.lines[end-1].data),
|
||||
util.CharacterCount(b.lines[end-1].data),
|
||||
end - 1,
|
||||
},
|
||||
"\n"+l,
|
||||
)
|
||||
} else {
|
||||
b.Insert(
|
||||
Loc{0, end},
|
||||
l+"\n",
|
||||
[]byte{'\n'},
|
||||
)
|
||||
}
|
||||
b.Insert(
|
||||
Loc{0, end},
|
||||
l+"\n",
|
||||
)
|
||||
b.Remove(
|
||||
Loc{0, start - 1},
|
||||
Loc{0, start},
|
||||
@@ -643,7 +857,7 @@ func (b *Buffer) MoveLinesUp(start int, end int) {
|
||||
|
||||
// MoveLinesDown moves the range of lines down one row
|
||||
func (b *Buffer) MoveLinesDown(start int, end int) {
|
||||
if start < 0 || start >= end || end >= len(b.lines)-1 {
|
||||
if start < 0 || start >= end || end >= len(b.lines) {
|
||||
return
|
||||
}
|
||||
l := string(b.LineBytes(end))
|
||||
@@ -671,7 +885,7 @@ var BracePairs = [][2]rune{
|
||||
// returns the location of the matching brace
|
||||
// if the boolean returned is true then the original matching brace is one character left
|
||||
// of the starting location
|
||||
func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool) {
|
||||
func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool, bool) {
|
||||
curLine := []rune(string(b.LineBytes(start.Y)))
|
||||
startChar := ' '
|
||||
if start.X >= 0 && start.X < len(curLine) {
|
||||
@@ -701,9 +915,9 @@ func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool) {
|
||||
i--
|
||||
if i == 0 {
|
||||
if startChar == braceType[0] {
|
||||
return Loc{x, y}, false
|
||||
return Loc{x, y}, false, true
|
||||
}
|
||||
return Loc{x, y}, true
|
||||
return Loc{x, y}, true, true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -725,9 +939,9 @@ func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool) {
|
||||
i--
|
||||
if i == 0 {
|
||||
if leftChar == braceType[1] {
|
||||
return Loc{x, y}, true
|
||||
return Loc{x, y}, true, true
|
||||
}
|
||||
return Loc{x, y}, false
|
||||
return Loc{x, y}, false, true
|
||||
}
|
||||
} else if r == braceType[1] {
|
||||
i++
|
||||
@@ -735,19 +949,19 @@ func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return start, true
|
||||
return start, true, false
|
||||
}
|
||||
|
||||
// Retab changes all tabs to spaces or vice versa
|
||||
func (b *Buffer) Retab() {
|
||||
toSpaces := b.Settings["tabstospaces"].(bool)
|
||||
tabsize := IntOpt(b.Settings["tabsize"])
|
||||
tabsize := util.IntOpt(b.Settings["tabsize"])
|
||||
dirty := false
|
||||
|
||||
for i := 0; i < b.LinesNum(); i++ {
|
||||
l := b.LineBytes(i)
|
||||
|
||||
ws := GetLeadingWhitespace(l)
|
||||
ws := util.GetLeadingWhitespace(l)
|
||||
if len(ws) != 0 {
|
||||
if toSpaces {
|
||||
ws = bytes.Replace(ws, []byte{'\t'}, bytes.Repeat([]byte{' '}, tabsize), -1)
|
||||
@@ -758,6 +972,7 @@ func (b *Buffer) Retab() {
|
||||
|
||||
l = bytes.TrimLeft(l, " \t")
|
||||
b.lines[i].data = append(ws, l...)
|
||||
b.MarkModified(i, i)
|
||||
dirty = true
|
||||
}
|
||||
|
||||
@@ -776,12 +991,12 @@ func ParseCursorLocation(cursorPositions []string) (Loc, error) {
|
||||
}
|
||||
|
||||
startpos.Y, err = strconv.Atoi(cursorPositions[0])
|
||||
startpos.Y -= 1
|
||||
startpos.Y--
|
||||
if err == nil {
|
||||
if len(cursorPositions) > 1 {
|
||||
startpos.X, err = strconv.Atoi(cursorPositions[1])
|
||||
if startpos.X > 0 {
|
||||
startpos.X -= 1
|
||||
startpos.X--
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -789,10 +1004,117 @@ func ParseCursorLocation(cursorPositions []string) (Loc, error) {
|
||||
return startpos, err
|
||||
}
|
||||
|
||||
// Line returns the string representation of the given line number
|
||||
func (b *Buffer) Line(i int) string {
|
||||
return string(b.LineBytes(i))
|
||||
}
|
||||
|
||||
func (b *Buffer) Write(bytes []byte) (n int, err error) {
|
||||
b.EventHandler.InsertBytes(b.End(), bytes)
|
||||
return len(bytes), nil
|
||||
}
|
||||
|
||||
func (b *Buffer) updateDiffSync() {
|
||||
b.diffLock.Lock()
|
||||
defer b.diffLock.Unlock()
|
||||
|
||||
b.diff = make(map[int]DiffStatus)
|
||||
|
||||
if b.diffBase == nil {
|
||||
return
|
||||
}
|
||||
|
||||
differ := dmp.New()
|
||||
baseRunes, bufferRunes, _ := differ.DiffLinesToRunes(string(b.diffBase), string(b.Bytes()))
|
||||
diffs := differ.DiffMainRunes(baseRunes, bufferRunes, false)
|
||||
lineN := 0
|
||||
|
||||
for _, diff := range diffs {
|
||||
lineCount := len([]rune(diff.Text))
|
||||
|
||||
switch diff.Type {
|
||||
case dmp.DiffEqual:
|
||||
lineN += lineCount
|
||||
case dmp.DiffInsert:
|
||||
var status DiffStatus
|
||||
if b.diff[lineN] == DSDeletedAbove {
|
||||
status = DSModified
|
||||
} else {
|
||||
status = DSAdded
|
||||
}
|
||||
for i := 0; i < lineCount; i++ {
|
||||
b.diff[lineN] = status
|
||||
lineN++
|
||||
}
|
||||
case dmp.DiffDelete:
|
||||
b.diff[lineN] = DSDeletedAbove
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateDiff computes the diff between the diff base and the buffer content.
|
||||
// The update may be performed synchronously or asynchronously.
|
||||
// UpdateDiff calls the supplied callback when the update is complete.
|
||||
// The argument passed to the callback is set to true if and only if
|
||||
// the update was performed synchronously.
|
||||
// If an asynchronous update is already pending when UpdateDiff is called,
|
||||
// UpdateDiff does not schedule another update, in which case the callback
|
||||
// is not called.
|
||||
func (b *Buffer) UpdateDiff(callback func(bool)) {
|
||||
if b.updateDiffTimer != nil {
|
||||
return
|
||||
}
|
||||
|
||||
lineCount := b.LinesNum()
|
||||
if b.diffBaseLineCount > lineCount {
|
||||
lineCount = b.diffBaseLineCount
|
||||
}
|
||||
|
||||
if lineCount < 1000 {
|
||||
b.updateDiffSync()
|
||||
callback(true)
|
||||
} else if lineCount < 30000 {
|
||||
b.updateDiffTimer = time.AfterFunc(500*time.Millisecond, func() {
|
||||
b.updateDiffTimer = nil
|
||||
b.updateDiffSync()
|
||||
callback(false)
|
||||
})
|
||||
} else {
|
||||
// Don't compute diffs for very large files
|
||||
b.diffLock.Lock()
|
||||
b.diff = make(map[int]DiffStatus)
|
||||
b.diffLock.Unlock()
|
||||
callback(true)
|
||||
}
|
||||
}
|
||||
|
||||
// SetDiffBase sets the text that is used as the base for diffing the buffer content
|
||||
func (b *Buffer) SetDiffBase(diffBase []byte) {
|
||||
b.diffBase = diffBase
|
||||
if diffBase == nil {
|
||||
b.diffBaseLineCount = 0
|
||||
} else {
|
||||
b.diffBaseLineCount = strings.Count(string(diffBase), "\n")
|
||||
}
|
||||
b.UpdateDiff(func(synchronous bool) {
|
||||
screen.Redraw()
|
||||
})
|
||||
}
|
||||
|
||||
// DiffStatus returns the diff status for a line in the buffer
|
||||
func (b *Buffer) DiffStatus(lineN int) DiffStatus {
|
||||
b.diffLock.RLock()
|
||||
defer b.diffLock.RUnlock()
|
||||
// Note that the zero value for DiffStatus is equal to DSUnchanged
|
||||
return b.diff[lineN]
|
||||
}
|
||||
|
||||
// WriteLog writes a string to the log buffer
|
||||
func WriteLog(s string) {
|
||||
LogBuf.EventHandler.Insert(LogBuf.End(), s)
|
||||
}
|
||||
|
||||
// GetLogBuf returns the log buffer
|
||||
func GetLogBuf() *Buffer {
|
||||
return LogBuf
|
||||
}
|
||||
|
||||
1827
internal/buffer/buffer_generated_test.go
Normal file
1827
internal/buffer/buffer_generated_test.go
Normal file
File diff suppressed because it is too large
Load Diff
322
internal/buffer/buffer_test.go
Normal file
322
internal/buffer/buffer_test.go
Normal file
@@ -0,0 +1,322 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
type operation struct {
|
||||
start Loc
|
||||
end Loc
|
||||
text []string
|
||||
}
|
||||
|
||||
func init() {
|
||||
ulua.L = lua.NewState()
|
||||
config.InitGlobalSettings()
|
||||
config.GlobalSettings["backup"] = false
|
||||
config.GlobalSettings["fastdirty"] = true
|
||||
}
|
||||
|
||||
func check(t *testing.T, before []string, operations []operation, after []string) {
|
||||
assert := assert.New(t)
|
||||
|
||||
b := NewBufferFromString(strings.Join(before, "\n"), "", BTDefault)
|
||||
|
||||
assert.NotEqual("", b.GetName())
|
||||
assert.Equal(false, b.ExternallyModified())
|
||||
assert.Equal(false, b.Modified())
|
||||
assert.Equal(1, b.NumCursors())
|
||||
|
||||
checkText := func(lines []string) {
|
||||
assert.Equal([]byte(strings.Join(lines, "\n")), b.Bytes())
|
||||
assert.Equal(len(lines), b.LinesNum())
|
||||
for i, s := range lines {
|
||||
assert.Equal(s, b.Line(i))
|
||||
assert.Equal([]byte(s), b.LineBytes(i))
|
||||
}
|
||||
}
|
||||
|
||||
checkText(before)
|
||||
|
||||
var cursors []*Cursor
|
||||
|
||||
for _, op := range operations {
|
||||
cursor := NewCursor(b, op.start)
|
||||
cursor.SetSelectionStart(op.start)
|
||||
cursor.SetSelectionEnd(op.end)
|
||||
b.AddCursor(cursor)
|
||||
cursors = append(cursors, cursor)
|
||||
}
|
||||
|
||||
assert.Equal(1+len(operations), b.NumCursors())
|
||||
|
||||
for i, op := range operations {
|
||||
cursor := cursors[i]
|
||||
b.SetCurCursor(cursor.Num)
|
||||
cursor.DeleteSelection()
|
||||
b.Insert(cursor.Loc, strings.Join(op.text, "\n"))
|
||||
}
|
||||
|
||||
checkText(after)
|
||||
|
||||
// must have exactly two events per operation (delete and insert)
|
||||
for range operations {
|
||||
b.UndoOneEvent()
|
||||
b.UndoOneEvent()
|
||||
}
|
||||
|
||||
checkText(before)
|
||||
|
||||
for i, op := range operations {
|
||||
cursor := cursors[i]
|
||||
if op.start == op.end {
|
||||
assert.Equal(op.start, cursor.Loc)
|
||||
} else {
|
||||
assert.Equal(op.start, cursor.CurSelection[0])
|
||||
assert.Equal(op.end, cursor.CurSelection[1])
|
||||
}
|
||||
}
|
||||
|
||||
for range operations {
|
||||
b.RedoOneEvent()
|
||||
b.RedoOneEvent()
|
||||
}
|
||||
|
||||
checkText(after)
|
||||
|
||||
b.Close()
|
||||
}
|
||||
|
||||
const maxLineLength = 200
|
||||
|
||||
var alphabet = []rune(" abcdeäم📚")
|
||||
|
||||
func randomString(length int) string {
|
||||
runes := make([]rune, length)
|
||||
for i := range runes {
|
||||
runes[i] = alphabet[rand.Intn(len(alphabet))]
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
func randomText(nLines int) string {
|
||||
lines := make([]string, nLines)
|
||||
for i := range lines {
|
||||
lines[i] = randomString(rand.Intn(maxLineLength + 1))
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func benchCreateAndClose(testingB *testing.B, nLines int) {
|
||||
rand.Seed(int64(nLines))
|
||||
|
||||
text := randomText(nLines)
|
||||
|
||||
testingB.ResetTimer()
|
||||
|
||||
for i := 0; i < testingB.N; i++ {
|
||||
b := NewBufferFromString(text, "", BTDefault)
|
||||
b.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func benchRead(testingB *testing.B, nLines int) {
|
||||
rand.Seed(int64(nLines))
|
||||
|
||||
b := NewBufferFromString(randomText(nLines), "", BTDefault)
|
||||
|
||||
testingB.ResetTimer()
|
||||
|
||||
for i := 0; i < testingB.N; i++ {
|
||||
b.Bytes()
|
||||
for j := 0; j < b.LinesNum(); j++ {
|
||||
b.Line(j)
|
||||
b.LineBytes(j)
|
||||
}
|
||||
}
|
||||
|
||||
testingB.StopTimer()
|
||||
|
||||
b.Close()
|
||||
}
|
||||
|
||||
func benchEdit(testingB *testing.B, nLines, nCursors int) {
|
||||
rand.Seed(int64(nLines + nCursors))
|
||||
|
||||
b := NewBufferFromString(randomText(nLines), "", BTDefault)
|
||||
|
||||
regionSize := nLines / nCursors
|
||||
|
||||
operations := make([]operation, nCursors)
|
||||
for i := range operations {
|
||||
startLine := (i * regionSize) + rand.Intn(regionSize-5)
|
||||
startColumn := rand.Intn(util.CharacterCountInString(b.Line(startLine)) + 1)
|
||||
endLine := startLine + 1 + rand.Intn(5)
|
||||
endColumn := rand.Intn(util.CharacterCountInString(b.Line(endLine)) + 1)
|
||||
|
||||
operations[i] = operation{
|
||||
start: Loc{startColumn, startLine},
|
||||
end: Loc{endColumn, endLine},
|
||||
text: []string{randomText(2 + rand.Intn(4))},
|
||||
}
|
||||
}
|
||||
|
||||
testingB.ResetTimer()
|
||||
|
||||
for i := 0; i < testingB.N; i++ {
|
||||
b.SetCursors([]*Cursor{})
|
||||
|
||||
var cursors []*Cursor
|
||||
|
||||
for _, op := range operations {
|
||||
cursor := NewCursor(b, op.start)
|
||||
cursor.SetSelectionStart(op.start)
|
||||
cursor.SetSelectionEnd(op.end)
|
||||
b.AddCursor(cursor)
|
||||
cursors = append(cursors, cursor)
|
||||
}
|
||||
|
||||
for j, op := range operations {
|
||||
cursor := cursors[j]
|
||||
b.SetCurCursor(cursor.Num)
|
||||
cursor.DeleteSelection()
|
||||
b.Insert(cursor.Loc, op.text[0])
|
||||
}
|
||||
|
||||
for b.UndoStack.Peek() != nil {
|
||||
b.UndoOneEvent()
|
||||
}
|
||||
}
|
||||
|
||||
testingB.StopTimer()
|
||||
|
||||
b.Close()
|
||||
}
|
||||
|
||||
func BenchmarkCreateAndClose10Lines(b *testing.B) {
|
||||
benchCreateAndClose(b, 10)
|
||||
}
|
||||
|
||||
func BenchmarkCreateAndClose100Lines(b *testing.B) {
|
||||
benchCreateAndClose(b, 100)
|
||||
}
|
||||
|
||||
func BenchmarkCreateAndClose1000Lines(b *testing.B) {
|
||||
benchCreateAndClose(b, 1000)
|
||||
}
|
||||
|
||||
func BenchmarkCreateAndClose10000Lines(b *testing.B) {
|
||||
benchCreateAndClose(b, 10000)
|
||||
}
|
||||
|
||||
func BenchmarkCreateAndClose100000Lines(b *testing.B) {
|
||||
benchCreateAndClose(b, 100000)
|
||||
}
|
||||
|
||||
func BenchmarkCreateAndClose1000000Lines(b *testing.B) {
|
||||
benchCreateAndClose(b, 1000000)
|
||||
}
|
||||
|
||||
func BenchmarkRead10Lines(b *testing.B) {
|
||||
benchRead(b, 10)
|
||||
}
|
||||
|
||||
func BenchmarkRead100Lines(b *testing.B) {
|
||||
benchRead(b, 100)
|
||||
}
|
||||
|
||||
func BenchmarkRead1000Lines(b *testing.B) {
|
||||
benchRead(b, 1000)
|
||||
}
|
||||
|
||||
func BenchmarkRead10000Lines(b *testing.B) {
|
||||
benchRead(b, 10000)
|
||||
}
|
||||
|
||||
func BenchmarkRead100000Lines(b *testing.B) {
|
||||
benchRead(b, 100000)
|
||||
}
|
||||
|
||||
func BenchmarkRead1000000Lines(b *testing.B) {
|
||||
benchRead(b, 1000000)
|
||||
}
|
||||
|
||||
func BenchmarkEdit10Lines1Cursor(b *testing.B) {
|
||||
benchEdit(b, 10, 1)
|
||||
}
|
||||
|
||||
func BenchmarkEdit100Lines1Cursor(b *testing.B) {
|
||||
benchEdit(b, 100, 1)
|
||||
}
|
||||
|
||||
func BenchmarkEdit100Lines10Cursors(b *testing.B) {
|
||||
benchEdit(b, 100, 10)
|
||||
}
|
||||
|
||||
func BenchmarkEdit1000Lines1Cursor(b *testing.B) {
|
||||
benchEdit(b, 1000, 1)
|
||||
}
|
||||
|
||||
func BenchmarkEdit1000Lines10Cursors(b *testing.B) {
|
||||
benchEdit(b, 1000, 10)
|
||||
}
|
||||
|
||||
func BenchmarkEdit1000Lines100Cursors(b *testing.B) {
|
||||
benchEdit(b, 1000, 100)
|
||||
}
|
||||
|
||||
func BenchmarkEdit10000Lines1Cursor(b *testing.B) {
|
||||
benchEdit(b, 10000, 1)
|
||||
}
|
||||
|
||||
func BenchmarkEdit10000Lines10Cursors(b *testing.B) {
|
||||
benchEdit(b, 10000, 10)
|
||||
}
|
||||
|
||||
func BenchmarkEdit10000Lines100Cursors(b *testing.B) {
|
||||
benchEdit(b, 10000, 100)
|
||||
}
|
||||
|
||||
func BenchmarkEdit10000Lines1000Cursors(b *testing.B) {
|
||||
benchEdit(b, 10000, 1000)
|
||||
}
|
||||
|
||||
func BenchmarkEdit100000Lines1Cursor(b *testing.B) {
|
||||
benchEdit(b, 100000, 1)
|
||||
}
|
||||
|
||||
func BenchmarkEdit100000Lines10Cursors(b *testing.B) {
|
||||
benchEdit(b, 100000, 10)
|
||||
}
|
||||
|
||||
func BenchmarkEdit100000Lines100Cursors(b *testing.B) {
|
||||
benchEdit(b, 100000, 100)
|
||||
}
|
||||
|
||||
func BenchmarkEdit100000Lines1000Cursors(b *testing.B) {
|
||||
benchEdit(b, 100000, 1000)
|
||||
}
|
||||
|
||||
func BenchmarkEdit1000000Lines1Cursor(b *testing.B) {
|
||||
benchEdit(b, 1000000, 1)
|
||||
}
|
||||
|
||||
func BenchmarkEdit1000000Lines10Cursors(b *testing.B) {
|
||||
benchEdit(b, 1000000, 10)
|
||||
}
|
||||
|
||||
func BenchmarkEdit1000000Lines100Cursors(b *testing.B) {
|
||||
benchEdit(b, 1000000, 100)
|
||||
}
|
||||
|
||||
func BenchmarkEdit1000000Lines1000Cursors(b *testing.B) {
|
||||
benchEdit(b, 1000000, 1000)
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/clipboard"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// InBounds returns whether the given location is a valid character position in the given buffer
|
||||
func InBounds(pos Loc, buf *Buffer) bool {
|
||||
if pos.Y < 0 || pos.Y >= len(buf.lines) || pos.X < 0 || pos.X > utf8.RuneCount(buf.LineBytes(pos.Y)) {
|
||||
if pos.Y < 0 || pos.Y >= len(buf.lines) || pos.X < 0 || pos.X > util.CharacterCount(buf.LineBytes(pos.Y)) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -76,9 +74,6 @@ func (c *Cursor) GetVisualX() int {
|
||||
|
||||
bytes := c.buf.LineBytes(c.Y)
|
||||
tabsize := int(c.buf.Settings["tabsize"].(float64))
|
||||
if c.X > utf8.RuneCount(bytes) {
|
||||
c.X = utf8.RuneCount(bytes) - 1
|
||||
}
|
||||
|
||||
return util.StringWidth(bytes, c.X, tabsize)
|
||||
}
|
||||
@@ -102,16 +97,29 @@ func (c *Cursor) Start() {
|
||||
func (c *Cursor) StartOfText() {
|
||||
c.Start()
|
||||
for util.IsWhitespace(c.RuneUnder(c.X)) {
|
||||
if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
|
||||
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
||||
break
|
||||
}
|
||||
c.Right()
|
||||
}
|
||||
}
|
||||
|
||||
// IsStartOfText returns whether the cursor is at the first
|
||||
// non-whitespace rune of the line it is on
|
||||
func (c *Cursor) IsStartOfText() bool {
|
||||
x := 0
|
||||
for util.IsWhitespace(c.RuneUnder(x)) {
|
||||
if x == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
||||
break
|
||||
}
|
||||
x++
|
||||
}
|
||||
return c.X == x
|
||||
}
|
||||
|
||||
// End moves the cursor to the end of the line it is on
|
||||
func (c *Cursor) End() {
|
||||
c.X = utf8.RuneCount(c.buf.LineBytes(c.Y))
|
||||
c.X = util.CharacterCount(c.buf.LineBytes(c.Y))
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
|
||||
@@ -229,8 +237,14 @@ func (c *Cursor) UpN(amount int) {
|
||||
bytes := c.buf.LineBytes(proposedY)
|
||||
c.X = c.GetCharPosInLine(bytes, c.LastVisualX)
|
||||
|
||||
if c.X > utf8.RuneCount(bytes) || (amount < 0 && proposedY == c.Y) {
|
||||
c.X = utf8.RuneCount(bytes)
|
||||
if c.X > util.CharacterCount(bytes) || (amount < 0 && proposedY == c.Y) {
|
||||
c.X = util.CharacterCount(bytes)
|
||||
c.StoreVisualX()
|
||||
}
|
||||
|
||||
if c.X < 0 || (amount > 0 && proposedY == c.Y) {
|
||||
c.X = 0
|
||||
c.StoreVisualX()
|
||||
}
|
||||
|
||||
c.Y = proposedY
|
||||
@@ -272,7 +286,7 @@ func (c *Cursor) Right() {
|
||||
if c.Loc == c.buf.End() {
|
||||
return
|
||||
}
|
||||
if c.X < utf8.RuneCount(c.buf.LineBytes(c.Y)) {
|
||||
if c.X < util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
||||
c.X++
|
||||
} else {
|
||||
c.Down()
|
||||
@@ -293,8 +307,8 @@ func (c *Cursor) Relocate() {
|
||||
|
||||
if c.X < 0 {
|
||||
c.X = 0
|
||||
} else if c.X > utf8.RuneCount(c.buf.LineBytes(c.Y)) {
|
||||
c.X = utf8.RuneCount(c.buf.LineBytes(c.Y))
|
||||
} else if c.X > util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
||||
c.X = util.CharacterCount(c.buf.LineBytes(c.Y))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +334,7 @@ func (c *Cursor) SelectWord() {
|
||||
c.SetSelectionStart(Loc{backward, c.Y})
|
||||
c.OrigSelection[0] = c.CurSelection[0]
|
||||
|
||||
lineLen := utf8.RuneCount(c.buf.LineBytes(c.Y)) - 1
|
||||
lineLen := util.CharacterCount(c.buf.LineBytes(c.Y)) - 1
|
||||
for forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) {
|
||||
forward++
|
||||
}
|
||||
@@ -352,7 +366,7 @@ func (c *Cursor) AddWordToSelection() {
|
||||
if c.Loc.GreaterThan(c.OrigSelection[1]) {
|
||||
forward := c.X
|
||||
|
||||
lineLen := utf8.RuneCount(c.buf.LineBytes(c.Y)) - 1
|
||||
lineLen := util.CharacterCount(c.buf.LineBytes(c.Y)) - 1
|
||||
for forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) {
|
||||
forward++
|
||||
}
|
||||
@@ -379,7 +393,7 @@ func (c *Cursor) SelectTo(loc Loc) {
|
||||
// WordRight moves the cursor one word to the right
|
||||
func (c *Cursor) WordRight() {
|
||||
for util.IsWhitespace(c.RuneUnder(c.X)) {
|
||||
if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
|
||||
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
||||
c.Right()
|
||||
return
|
||||
}
|
||||
@@ -387,7 +401,7 @@ func (c *Cursor) WordRight() {
|
||||
}
|
||||
c.Right()
|
||||
for util.IsWordChar(c.RuneUnder(c.X)) {
|
||||
if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
|
||||
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
||||
return
|
||||
}
|
||||
c.Right()
|
||||
@@ -416,14 +430,14 @@ func (c *Cursor) WordLeft() {
|
||||
// RuneUnder returns the rune under the given x position
|
||||
func (c *Cursor) RuneUnder(x int) rune {
|
||||
line := c.buf.LineBytes(c.Y)
|
||||
if len(line) == 0 || x >= utf8.RuneCount(line) {
|
||||
if len(line) == 0 || x >= util.CharacterCount(line) {
|
||||
return '\n'
|
||||
} else if x < 0 {
|
||||
x = 0
|
||||
}
|
||||
i := 0
|
||||
for len(line) > 0 {
|
||||
r, size := utf8.DecodeRune(line)
|
||||
r, _, size := util.DecodeCharacter(line)
|
||||
line = line[size:]
|
||||
|
||||
if i == x {
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
dmp "github.com/sergi/go-diff/diffmatchpatch"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
luar "layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -17,7 +22,7 @@ const (
|
||||
// TextEventReplace represents a replace event
|
||||
TextEventReplace = 0
|
||||
|
||||
undoThreshold = 500 // If two events are less than n milliseconds apart, undo both of them
|
||||
undoThreshold = 1000 // If two events are less than n milliseconds apart, undo both of them
|
||||
)
|
||||
|
||||
// TextEvent holds data for a manipulation on some text that can be undone
|
||||
@@ -36,6 +41,73 @@ type Delta struct {
|
||||
End Loc
|
||||
}
|
||||
|
||||
// DoTextEvent runs a text event
|
||||
func (eh *EventHandler) DoTextEvent(t *TextEvent, useUndo bool) {
|
||||
oldl := eh.buf.LinesNum()
|
||||
|
||||
if useUndo {
|
||||
eh.Execute(t)
|
||||
} else {
|
||||
ExecuteTextEvent(t, eh.buf)
|
||||
}
|
||||
|
||||
if len(t.Deltas) != 1 {
|
||||
return
|
||||
}
|
||||
|
||||
text := t.Deltas[0].Text
|
||||
start := t.Deltas[0].Start
|
||||
lastnl := -1
|
||||
var endX int
|
||||
var textX int
|
||||
if t.EventType == TextEventInsert {
|
||||
linecount := eh.buf.LinesNum() - oldl
|
||||
textcount := util.CharacterCount(text)
|
||||
lastnl = bytes.LastIndex(text, []byte{'\n'})
|
||||
if lastnl >= 0 {
|
||||
endX = util.CharacterCount(text[lastnl+1:])
|
||||
textX = endX
|
||||
} else {
|
||||
endX = start.X + textcount
|
||||
textX = textcount
|
||||
}
|
||||
t.Deltas[0].End = clamp(Loc{endX, start.Y + linecount}, eh.buf.LineArray)
|
||||
}
|
||||
end := t.Deltas[0].End
|
||||
|
||||
for _, c := range eh.cursors {
|
||||
move := func(loc Loc) Loc {
|
||||
if t.EventType == TextEventInsert {
|
||||
if start.Y != loc.Y && loc.GreaterThan(start) {
|
||||
loc.Y += end.Y - start.Y
|
||||
} else if loc.Y == start.Y && loc.GreaterEqual(start) {
|
||||
loc.Y += end.Y - start.Y
|
||||
if lastnl >= 0 {
|
||||
loc.X += textX - start.X
|
||||
} else {
|
||||
loc.X += textX
|
||||
}
|
||||
}
|
||||
return loc
|
||||
} else {
|
||||
if loc.Y != end.Y && loc.GreaterThan(end) {
|
||||
loc.Y -= end.Y - start.Y
|
||||
} else if loc.Y == end.Y && loc.GreaterEqual(end) {
|
||||
loc = loc.MoveLA(-DiffLA(start, end, eh.buf.LineArray), eh.buf.LineArray)
|
||||
}
|
||||
return loc
|
||||
}
|
||||
}
|
||||
c.Loc = move(c.Loc)
|
||||
c.CurSelection[0] = move(c.CurSelection[0])
|
||||
c.CurSelection[1] = move(c.CurSelection[1])
|
||||
c.OrigSelection[0] = move(c.OrigSelection[0])
|
||||
c.OrigSelection[1] = move(c.OrigSelection[1])
|
||||
c.Relocate()
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuteTextEvent runs a text event
|
||||
func ExecuteTextEvent(t *TextEvent, buf *SharedBuffer) {
|
||||
if t.EventType == TextEventInsert {
|
||||
@@ -51,7 +123,7 @@ func ExecuteTextEvent(t *TextEvent, buf *SharedBuffer) {
|
||||
t.Deltas[i].Text = buf.remove(d.Start, d.End)
|
||||
buf.insert(d.Start, d.Text)
|
||||
t.Deltas[i].Start = d.Start
|
||||
t.Deltas[i].End = Loc{d.Start.X + utf8.RuneCount(d.Text), d.Start.Y}
|
||||
t.Deltas[i].End = Loc{d.Start.X + util.CharacterCount(d.Text), d.Start.Y}
|
||||
}
|
||||
for i, j := 0, len(t.Deltas)-1; i < j; i, j = i+1, j-1 {
|
||||
t.Deltas[i], t.Deltas[j] = t.Deltas[j], t.Deltas[i]
|
||||
@@ -60,9 +132,9 @@ func ExecuteTextEvent(t *TextEvent, buf *SharedBuffer) {
|
||||
}
|
||||
|
||||
// UndoTextEvent undoes a text event
|
||||
func UndoTextEvent(t *TextEvent, buf *SharedBuffer) {
|
||||
func (eh *EventHandler) UndoTextEvent(t *TextEvent) {
|
||||
t.EventType = -t.EventType
|
||||
ExecuteTextEvent(t, buf)
|
||||
eh.DoTextEvent(t, false)
|
||||
}
|
||||
|
||||
// EventHandler executes text manipulations and allows undoing and redoing
|
||||
@@ -94,12 +166,12 @@ func (eh *EventHandler) ApplyDiff(new string) {
|
||||
loc := eh.buf.Start()
|
||||
for _, d := range diff {
|
||||
if d.Type == dmp.DiffDelete {
|
||||
eh.Remove(loc, loc.MoveLA(utf8.RuneCountInString(d.Text), eh.buf.LineArray))
|
||||
eh.Remove(loc, loc.MoveLA(util.CharacterCountInString(d.Text), eh.buf.LineArray))
|
||||
} else {
|
||||
if d.Type == dmp.DiffInsert {
|
||||
eh.Insert(loc, d.Text)
|
||||
}
|
||||
loc = loc.MoveLA(utf8.RuneCountInString(d.Text), eh.buf.LineArray)
|
||||
loc = loc.MoveLA(util.CharacterCountInString(d.Text), eh.buf.LineArray)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,60 +179,38 @@ func (eh *EventHandler) ApplyDiff(new string) {
|
||||
// Insert creates an insert text event and executes it
|
||||
func (eh *EventHandler) Insert(start Loc, textStr string) {
|
||||
text := []byte(textStr)
|
||||
eh.InsertBytes(start, text)
|
||||
}
|
||||
|
||||
// InsertBytes creates an insert text event and executes it
|
||||
func (eh *EventHandler) InsertBytes(start Loc, text []byte) {
|
||||
if len(text) == 0 {
|
||||
return
|
||||
}
|
||||
start = clamp(start, eh.buf.LineArray)
|
||||
e := &TextEvent{
|
||||
C: *eh.cursors[eh.active],
|
||||
EventType: TextEventInsert,
|
||||
Deltas: []Delta{{text, start, Loc{0, 0}}},
|
||||
Time: time.Now(),
|
||||
}
|
||||
eh.Execute(e)
|
||||
e.Deltas[0].End = start.MoveLA(utf8.RuneCount(text), eh.buf.LineArray)
|
||||
end := e.Deltas[0].End
|
||||
|
||||
for _, c := range eh.cursors {
|
||||
move := func(loc Loc) Loc {
|
||||
if start.Y != end.Y && loc.GreaterThan(start) {
|
||||
loc.Y += end.Y - start.Y
|
||||
} else if loc.Y == start.Y && loc.GreaterEqual(start) {
|
||||
loc = loc.MoveLA(utf8.RuneCount(text), eh.buf.LineArray)
|
||||
}
|
||||
return loc
|
||||
}
|
||||
c.Loc = move(c.Loc)
|
||||
c.CurSelection[0] = move(c.CurSelection[0])
|
||||
c.CurSelection[1] = move(c.CurSelection[1])
|
||||
c.OrigSelection[0] = move(c.OrigSelection[0])
|
||||
c.OrigSelection[1] = move(c.OrigSelection[1])
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
eh.DoTextEvent(e, true)
|
||||
}
|
||||
|
||||
// Remove creates a remove text event and executes it
|
||||
func (eh *EventHandler) Remove(start, end Loc) {
|
||||
if start == end {
|
||||
return
|
||||
}
|
||||
start = clamp(start, eh.buf.LineArray)
|
||||
end = clamp(end, eh.buf.LineArray)
|
||||
e := &TextEvent{
|
||||
C: *eh.cursors[eh.active],
|
||||
EventType: TextEventRemove,
|
||||
Deltas: []Delta{{[]byte{}, start, end}},
|
||||
Time: time.Now(),
|
||||
}
|
||||
eh.Execute(e)
|
||||
|
||||
for _, c := range eh.cursors {
|
||||
move := func(loc Loc) Loc {
|
||||
if start.Y != end.Y && loc.GreaterThan(end) {
|
||||
loc.Y -= end.Y - start.Y
|
||||
} else if loc.Y == end.Y && loc.GreaterEqual(end) {
|
||||
loc = loc.MoveLA(-DiffLA(start, end, eh.buf.LineArray), eh.buf.LineArray)
|
||||
}
|
||||
return loc
|
||||
}
|
||||
c.Loc = move(c.Loc)
|
||||
c.CurSelection[0] = move(c.CurSelection[0])
|
||||
c.CurSelection[1] = move(c.CurSelection[1])
|
||||
c.OrigSelection[0] = move(c.OrigSelection[0])
|
||||
c.OrigSelection[1] = move(c.OrigSelection[1])
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
eh.DoTextEvent(e, true)
|
||||
}
|
||||
|
||||
// MultipleReplace creates an multiple insertions executes them
|
||||
@@ -187,16 +237,14 @@ func (eh *EventHandler) Execute(t *TextEvent) {
|
||||
}
|
||||
eh.UndoStack.Push(t)
|
||||
|
||||
// TODO: Call plugins on text events
|
||||
// for pl := range loadedPlugins {
|
||||
// ret, err := Call(pl+".onBeforeTextEvent", t)
|
||||
// if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
|
||||
// screen.TermMessage(err)
|
||||
// }
|
||||
// if val, ok := ret.(lua.LBool); ok && val == lua.LFalse {
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
b, err := config.RunPluginFnBool("onBeforeTextEvent", luar.New(ulua.L, eh.buf), luar.New(ulua.L, t))
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
if !b {
|
||||
return
|
||||
}
|
||||
|
||||
ExecuteTextEvent(t, eh.buf)
|
||||
}
|
||||
@@ -209,8 +257,7 @@ func (eh *EventHandler) Undo() {
|
||||
}
|
||||
|
||||
startTime := t.Time.UnixNano() / int64(time.Millisecond)
|
||||
|
||||
eh.UndoOneEvent()
|
||||
endTime := startTime - (startTime % undoThreshold)
|
||||
|
||||
for {
|
||||
t = eh.UndoStack.Peek()
|
||||
@@ -218,10 +265,9 @@ func (eh *EventHandler) Undo() {
|
||||
return
|
||||
}
|
||||
|
||||
if startTime-(t.Time.UnixNano()/int64(time.Millisecond)) > undoThreshold {
|
||||
if t.Time.UnixNano()/int64(time.Millisecond) < endTime {
|
||||
return
|
||||
}
|
||||
startTime = t.Time.UnixNano() / int64(time.Millisecond)
|
||||
|
||||
eh.UndoOneEvent()
|
||||
}
|
||||
@@ -235,10 +281,9 @@ func (eh *EventHandler) UndoOneEvent() {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Undo it
|
||||
// Modifies the text event
|
||||
UndoTextEvent(t, eh.buf)
|
||||
eh.UndoTextEvent(t)
|
||||
|
||||
// Set the cursor in the right place
|
||||
teCursor := t.C
|
||||
@@ -261,8 +306,7 @@ func (eh *EventHandler) Redo() {
|
||||
}
|
||||
|
||||
startTime := t.Time.UnixNano() / int64(time.Millisecond)
|
||||
|
||||
eh.RedoOneEvent()
|
||||
endTime := startTime - (startTime % undoThreshold) + undoThreshold
|
||||
|
||||
for {
|
||||
t = eh.RedoStack.Peek()
|
||||
@@ -270,7 +314,7 @@ func (eh *EventHandler) Redo() {
|
||||
return
|
||||
}
|
||||
|
||||
if (t.Time.UnixNano()/int64(time.Millisecond))-startTime > undoThreshold {
|
||||
if t.Time.UnixNano()/int64(time.Millisecond) > endTime {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -285,9 +329,6 @@ func (eh *EventHandler) RedoOneEvent() {
|
||||
return
|
||||
}
|
||||
|
||||
// Modifies the text event
|
||||
UndoTextEvent(t, eh.buf)
|
||||
|
||||
teCursor := t.C
|
||||
if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
|
||||
t.C = *eh.cursors[teCursor.Num]
|
||||
@@ -296,5 +337,8 @@ func (eh *EventHandler) RedoOneEvent() {
|
||||
teCursor.Num = -1
|
||||
}
|
||||
|
||||
// Modifies the text event
|
||||
eh.UndoTextEvent(t)
|
||||
|
||||
eh.UndoStack.Push(t)
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ package buffer
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
"sync"
|
||||
|
||||
"github.com/zyedidia/micro/pkg/highlight"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/micro/v2/pkg/highlight"
|
||||
)
|
||||
|
||||
// Finds the byte index of the nth rune in a byte slice
|
||||
@@ -17,7 +19,7 @@ func runeToByteIndex(n int, txt []byte) int {
|
||||
count := 0
|
||||
i := 0
|
||||
for len(txt) > 0 {
|
||||
_, size := utf8.DecodeRune(txt)
|
||||
_, _, size := util.DecodeCharacter(txt)
|
||||
|
||||
txt = txt[size:]
|
||||
count += size
|
||||
@@ -38,6 +40,7 @@ type Line struct {
|
||||
state highlight.State
|
||||
match highlight.LineMatch
|
||||
rehighlight bool
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -84,6 +87,8 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
|
||||
br := bufio.NewReader(reader)
|
||||
var loaded int
|
||||
|
||||
la.Endings = endings
|
||||
|
||||
n := 0
|
||||
for {
|
||||
data, err := br.ReadBytes('\n')
|
||||
@@ -124,12 +129,22 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
|
||||
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
la.lines = Append(la.lines, Line{data[:], nil, nil, false})
|
||||
la.lines = Append(la.lines, Line{
|
||||
data: data[:],
|
||||
state: nil,
|
||||
match: nil,
|
||||
rehighlight: false,
|
||||
})
|
||||
}
|
||||
// Last line was read
|
||||
break
|
||||
} else {
|
||||
la.lines = Append(la.lines, Line{data[:dlen-1], nil, nil, false})
|
||||
la.lines = Append(la.lines, Line{
|
||||
data: data[:dlen-1],
|
||||
state: nil,
|
||||
match: nil,
|
||||
rehighlight: false,
|
||||
})
|
||||
}
|
||||
n++
|
||||
}
|
||||
@@ -140,24 +155,36 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
|
||||
// Bytes returns the string that should be written to disk when
|
||||
// the line array is saved
|
||||
func (la *LineArray) Bytes() []byte {
|
||||
str := make([]byte, 0, la.initsize+1000) // initsize should provide a good estimate
|
||||
b := new(bytes.Buffer)
|
||||
// initsize should provide a good estimate
|
||||
b.Grow(int(la.initsize + 4096))
|
||||
for i, l := range la.lines {
|
||||
str = append(str, l.data...)
|
||||
b.Write(l.data)
|
||||
if i != len(la.lines)-1 {
|
||||
if la.Endings == FFDos {
|
||||
str = append(str, '\r')
|
||||
b.WriteByte('\r')
|
||||
}
|
||||
str = append(str, '\n')
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
return str
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
// newlineBelow adds a newline below the given line number
|
||||
func (la *LineArray) newlineBelow(y int) {
|
||||
la.lines = append(la.lines, Line{[]byte{' '}, nil, nil, false})
|
||||
la.lines = append(la.lines, Line{
|
||||
data: []byte{' '},
|
||||
state: nil,
|
||||
match: nil,
|
||||
rehighlight: false,
|
||||
})
|
||||
copy(la.lines[y+2:], la.lines[y+1:])
|
||||
la.lines[y+1] = Line{[]byte{}, la.lines[y].state, nil, false}
|
||||
la.lines[y+1] = Line{
|
||||
data: []byte{},
|
||||
state: la.lines[y].state,
|
||||
match: nil,
|
||||
rehighlight: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Inserts a byte array at a given location
|
||||
@@ -208,9 +235,7 @@ func (la *LineArray) remove(start, end Loc) []byte {
|
||||
if start.Y == end.Y {
|
||||
la.lines[start.Y].data = append(la.lines[start.Y].data[:startX], la.lines[start.Y].data[endX:]...)
|
||||
} else {
|
||||
for i := start.Y + 1; i <= end.Y-1; i++ {
|
||||
la.deleteLine(start.Y + 1)
|
||||
}
|
||||
la.deleteLines(start.Y+1, end.Y-1)
|
||||
la.deleteToEnd(Loc{startX, start.Y})
|
||||
la.deleteFromStart(Loc{endX - 1, start.Y + 1})
|
||||
la.joinLines(start.Y, start.Y+1)
|
||||
@@ -233,6 +258,10 @@ func (la *LineArray) deleteLine(y int) {
|
||||
la.lines = la.lines[:y+copy(la.lines[y:], la.lines[y+1:])]
|
||||
}
|
||||
|
||||
func (la *LineArray) deleteLines(y1, y2 int) {
|
||||
la.lines = la.lines[:y1+copy(la.lines[y1:], la.lines[y2+1:])]
|
||||
}
|
||||
|
||||
// DeleteByte deletes the byte at a position
|
||||
func (la *LineArray) deleteByte(pos Loc) {
|
||||
la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X+copy(la.lines[pos.Y].data[pos.X:], la.lines[pos.Y].data[pos.X+1:])]
|
||||
@@ -272,7 +301,7 @@ func (la *LineArray) Start() Loc {
|
||||
// End returns the location of the last character in the buffer
|
||||
func (la *LineArray) End() Loc {
|
||||
numlines := len(la.lines)
|
||||
return Loc{utf8.RuneCount(la.lines[numlines-1].data), numlines - 1}
|
||||
return Loc{util.CharacterCount(la.lines[numlines-1].data), numlines - 1}
|
||||
}
|
||||
|
||||
// LineBytes returns line n as an array of bytes
|
||||
@@ -285,28 +314,40 @@ func (la *LineArray) LineBytes(n int) []byte {
|
||||
|
||||
// State gets the highlight state for the given line number
|
||||
func (la *LineArray) State(lineN int) highlight.State {
|
||||
la.lines[lineN].lock.Lock()
|
||||
defer la.lines[lineN].lock.Unlock()
|
||||
return la.lines[lineN].state
|
||||
}
|
||||
|
||||
// SetState sets the highlight state at the given line number
|
||||
func (la *LineArray) SetState(lineN int, s highlight.State) {
|
||||
la.lines[lineN].lock.Lock()
|
||||
defer la.lines[lineN].lock.Unlock()
|
||||
la.lines[lineN].state = s
|
||||
}
|
||||
|
||||
// SetMatch sets the match at the given line number
|
||||
func (la *LineArray) SetMatch(lineN int, m highlight.LineMatch) {
|
||||
la.lines[lineN].lock.Lock()
|
||||
defer la.lines[lineN].lock.Unlock()
|
||||
la.lines[lineN].match = m
|
||||
}
|
||||
|
||||
// Match retrieves the match for the given line number
|
||||
func (la *LineArray) Match(lineN int) highlight.LineMatch {
|
||||
la.lines[lineN].lock.Lock()
|
||||
defer la.lines[lineN].lock.Unlock()
|
||||
return la.lines[lineN].match
|
||||
}
|
||||
|
||||
func (la *LineArray) Rehighlight(lineN int) bool {
|
||||
la.lines[lineN].lock.Lock()
|
||||
defer la.lines[lineN].lock.Unlock()
|
||||
return la.lines[lineN].rehighlight
|
||||
}
|
||||
|
||||
func (la *LineArray) SetRehighlight(lineN int, on bool) {
|
||||
la.lines[lineN].lock.Lock()
|
||||
defer la.lines[lineN].lock.Unlock()
|
||||
la.lines[lineN].rehighlight = on
|
||||
}
|
||||
|
||||
@@ -46,14 +46,15 @@ func TestInsert(t *testing.T) {
|
||||
|
||||
assert.Equal(t, []byte("Uppen Sevarne staþe, foobar sel þar him þuhte,"), sub1)
|
||||
|
||||
la.insert(Loc{25, 2}, []byte("ಮಣ್ಣಾಗಿ"))
|
||||
la.insert(Loc{25, 2}, []byte("H̼̥̯͇͙̕͘͞e̸̦̞̠̣̰͙̼̥̦̼̖̬͕͕̰̯̫͇̕ĺ̜̠̩̯̯͙̼̭̠͕̮̞͜l̶͓̫̞̮͈͞ͅo̸͔͙̳̠͈̮̼̳͙̥̲͜͠"))
|
||||
|
||||
sub2 := la.Substr(Loc{0, 2}, Loc{60, 2})
|
||||
assert.Equal(t, []byte("He wonede at Ernleȝe at æಮಣ್ಣಾಗಿðelen are chirechen,"), sub2)
|
||||
assert.Equal(t, []byte("He wonede at Ernleȝe at æH̼̥̯͇͙̕͘͞e̸̦̞̠̣̰͙̼̥̦̼̖̬͕͕̰̯̫͇̕ĺ̜̠̩̯̯͙̼̭̠͕̮̞͜l̶͓̫̞̮͈͞ͅo̸͔͙̳̠͈̮̼̳͙̥̲͜͠ðelen are chirechen,"), sub2)
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
la.remove(Loc{20, 3}, Loc{27, 3})
|
||||
la.remove(Loc{25, 2}, Loc{32, 2})
|
||||
la.remove(Loc{25, 2}, Loc{30, 2})
|
||||
|
||||
bytes := la.Bytes()
|
||||
assert.Equal(t, unicode_txt, string(bytes))
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// Loc stores a location
|
||||
@@ -68,9 +66,9 @@ func DiffLA(a, b Loc, buf *LineArray) int {
|
||||
loc := 0
|
||||
for i := a.Y + 1; i < b.Y; i++ {
|
||||
// + 1 for the newline
|
||||
loc += utf8.RuneCount(buf.LineBytes(i)) + 1
|
||||
loc += util.CharacterCount(buf.LineBytes(i)) + 1
|
||||
}
|
||||
loc += utf8.RuneCount(buf.LineBytes(a.Y)) - a.X + b.X + 1
|
||||
loc += util.CharacterCount(buf.LineBytes(a.Y)) - a.X + b.X + 1
|
||||
return loc
|
||||
}
|
||||
|
||||
@@ -80,7 +78,7 @@ func (l Loc) right(buf *LineArray) Loc {
|
||||
return Loc{l.X + 1, l.Y}
|
||||
}
|
||||
var res Loc
|
||||
if l.X < utf8.RuneCount(buf.LineBytes(l.Y)) {
|
||||
if l.X < util.CharacterCount(buf.LineBytes(l.Y)) {
|
||||
res = Loc{l.X + 1, l.Y}
|
||||
} else {
|
||||
res = Loc{0, l.Y + 1}
|
||||
@@ -97,12 +95,12 @@ func (l Loc) left(buf *LineArray) Loc {
|
||||
if l.X > 0 {
|
||||
res = Loc{l.X - 1, l.Y}
|
||||
} else {
|
||||
res = Loc{utf8.RuneCount(buf.LineBytes(l.Y - 1)), l.Y - 1}
|
||||
res = Loc{util.CharacterCount(buf.LineBytes(l.Y - 1)), l.Y - 1}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Move moves the cursor n characters to the left or right
|
||||
// MoveLA moves the cursor n characters to the left or right
|
||||
// It moves the cursor left if n is negative
|
||||
func (l Loc) MoveLA(n int, buf *LineArray) Loc {
|
||||
if n > 0 {
|
||||
@@ -117,9 +115,12 @@ func (l Loc) MoveLA(n int, buf *LineArray) Loc {
|
||||
return l
|
||||
}
|
||||
|
||||
func (l Loc) Diff(a, b Loc, buf *Buffer) int {
|
||||
return DiffLA(a, b, buf.LineArray)
|
||||
// Diff returns the difference between two locs
|
||||
func (l Loc) Diff(b Loc, buf *Buffer) int {
|
||||
return DiffLA(l, b, buf.LineArray)
|
||||
}
|
||||
|
||||
// Move moves a loc n characters
|
||||
func (l Loc) Move(n int, buf *Buffer) Loc {
|
||||
return l.MoveLA(n, buf.LineArray)
|
||||
}
|
||||
@@ -135,3 +136,13 @@ func ByteOffset(pos Loc, buf *Buffer) int {
|
||||
loc += len(buf.Line(y)[:x])
|
||||
return loc
|
||||
}
|
||||
|
||||
// clamps a loc within a buffer
|
||||
func clamp(pos Loc, la *LineArray) Loc {
|
||||
if pos.GreaterEqual(la.End()) {
|
||||
return la.End()
|
||||
} else if pos.LessThan(la.Start()) {
|
||||
return la.Start()
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -13,13 +13,19 @@ const (
|
||||
MTError
|
||||
)
|
||||
|
||||
// Message represents the information for a gutter message
|
||||
type Message struct {
|
||||
Msg string
|
||||
// The Msg iteslf
|
||||
Msg string
|
||||
// Start and End locations for the message
|
||||
Start, End Loc
|
||||
Kind MsgType
|
||||
Owner string
|
||||
// The Kind stores the message type
|
||||
Kind MsgType
|
||||
// The Owner of the message
|
||||
Owner string
|
||||
}
|
||||
|
||||
// NewMessage creates a new gutter message
|
||||
func NewMessage(owner string, msg string, start, end Loc, kind MsgType) *Message {
|
||||
return &Message{
|
||||
Msg: msg,
|
||||
@@ -30,6 +36,7 @@ func NewMessage(owner string, msg string, start, end Loc, kind MsgType) *Message
|
||||
}
|
||||
}
|
||||
|
||||
// NewMessageAtLine creates a new gutter message at a given line
|
||||
func NewMessageAtLine(owner string, msg string, line int, kind MsgType) *Message {
|
||||
start := Loc{-1, line - 1}
|
||||
end := start
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
@@ -8,12 +9,12 @@ import (
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
. "github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"golang.org/x/text/encoding"
|
||||
"golang.org/x/text/encoding/htmlindex"
|
||||
"golang.org/x/text/transform"
|
||||
@@ -26,70 +27,43 @@ const LargeFileThreshold = 50000
|
||||
// overwriteFile opens the given file for writing, truncating if one exists, and then calls
|
||||
// the supplied function with the file as io.Writer object, also making sure the file is
|
||||
// closed afterwards.
|
||||
func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error) (err error) {
|
||||
var file *os.File
|
||||
func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error, withSudo bool) (err error) {
|
||||
var writeCloser io.WriteCloser
|
||||
|
||||
if file, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
|
||||
return
|
||||
}
|
||||
if withSudo {
|
||||
cmd := exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name)
|
||||
|
||||
defer func() {
|
||||
if e := file.Close(); e != nil && err == nil {
|
||||
err = e
|
||||
if writeCloser, err = cmd.StdinPipe(); err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
w := transform.NewWriter(file, enc.NewEncoder())
|
||||
// w := bufio.NewWriter(file)
|
||||
|
||||
if err = fn(w); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// err = w.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
// overwriteFileAsRoot executes dd as root and then calls the supplied function
|
||||
// with dd's standard input as an io.Writer object. Dd opens the given file for writing,
|
||||
// truncating it if it exists, and writes what it receives on its standard input to the file.
|
||||
func overwriteFileAsRoot(name string, enc encoding.Encoding, fn func(io.Writer) error) (err error) {
|
||||
cmd := exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "status=none", "bs=4K", "of="+name)
|
||||
var stdin io.WriteCloser
|
||||
|
||||
screenb := screen.TempFini()
|
||||
|
||||
// This is a trap for Ctrl-C so that it doesn't kill micro
|
||||
// Instead we trap Ctrl-C to kill the program we're running
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
for range c {
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
<-c
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
}()
|
||||
}()
|
||||
|
||||
if stdin, err = cmd.StdinPipe(); err != nil {
|
||||
defer func() {
|
||||
screenb := screen.TempFini()
|
||||
if e := cmd.Run(); e != nil && err == nil {
|
||||
err = e
|
||||
}
|
||||
screen.TempStart(screenb)
|
||||
}()
|
||||
} else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = cmd.Start(); err != nil {
|
||||
return
|
||||
w := bufio.NewWriter(transform.NewWriter(writeCloser, enc.NewEncoder()))
|
||||
err = fn(w)
|
||||
w.Flush()
|
||||
|
||||
if e := writeCloser.Close(); e != nil && err == nil {
|
||||
err = e
|
||||
}
|
||||
|
||||
e := fn(stdin)
|
||||
|
||||
if err = stdin.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = cmd.Wait(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
screen.TempStart(screenb)
|
||||
|
||||
return e
|
||||
return
|
||||
}
|
||||
|
||||
// Save saves the buffer to its default path
|
||||
@@ -118,13 +92,15 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
|
||||
if b.Type.Scratch {
|
||||
return errors.New("Cannot save scratch buffer")
|
||||
}
|
||||
if withSudo && runtime.GOOS == "windows" {
|
||||
return errors.New("Save with sudo not supported on Windows")
|
||||
}
|
||||
|
||||
b.UpdateRules()
|
||||
if b.Settings["rmtrailingws"].(bool) {
|
||||
for i, l := range b.lines {
|
||||
leftover := utf8.RuneCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))
|
||||
leftover := util.CharacterCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))
|
||||
|
||||
linelen := utf8.RuneCount(l.data)
|
||||
linelen := util.CharacterCount(l.data)
|
||||
b.Remove(Loc{leftover, i}, Loc{linelen, i})
|
||||
}
|
||||
|
||||
@@ -133,19 +109,19 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
|
||||
|
||||
if b.Settings["eofnewline"].(bool) {
|
||||
end := b.End()
|
||||
if b.RuneAt(Loc{end.X - 1, end.Y}) != '\n' {
|
||||
b.Insert(end, "\n")
|
||||
if b.RuneAt(Loc{end.X, end.Y}) != '\n' {
|
||||
b.insert(end, []byte{'\n'})
|
||||
}
|
||||
}
|
||||
|
||||
// Update the last time this file was updated after saving
|
||||
defer func() {
|
||||
b.ModTime, _ = GetModTime(filename)
|
||||
b.ModTime, _ = util.GetModTime(filename)
|
||||
err = b.Serialize()
|
||||
}()
|
||||
|
||||
// Removes any tilde and replaces with the absolute path to home
|
||||
absFilename, _ := ReplaceHome(filename)
|
||||
absFilename, _ := util.ReplaceHome(filename)
|
||||
|
||||
// Get the leading path to the file | "." is returned if there's no leading path provided
|
||||
if dirname := filepath.Dir(absFilename); dirname != "." {
|
||||
@@ -201,13 +177,7 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
|
||||
return
|
||||
}
|
||||
|
||||
if withSudo {
|
||||
err = overwriteFileAsRoot(absFilename, enc, fwriter)
|
||||
} else {
|
||||
err = overwriteFile(absFilename, enc, fwriter)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err = overwriteFile(absFilename, enc, fwriter, withSudo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -224,5 +194,6 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
|
||||
absPath, _ := filepath.Abs(filename)
|
||||
b.AbsPath = absPath
|
||||
b.isModified = false
|
||||
b.UpdateRules()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,12 +2,18 @@ package buffer
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||
lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
|
||||
if start.Y > b.LinesNum()-1 {
|
||||
start.X = lastcn - 1
|
||||
}
|
||||
if end.Y > b.LinesNum()-1 {
|
||||
end.X = lastcn
|
||||
}
|
||||
start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
|
||||
end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)
|
||||
|
||||
@@ -20,19 +26,19 @@ func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||
charpos := 0
|
||||
|
||||
if i == start.Y && start.Y == end.Y {
|
||||
nchars := utf8.RuneCount(l)
|
||||
nchars := util.CharacterCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars)
|
||||
end.X = util.Clamp(end.X, 0, nchars)
|
||||
l = util.SliceStart(l, end.X)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == start.Y {
|
||||
nchars := utf8.RuneCount(l)
|
||||
nchars := util.CharacterCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == end.Y {
|
||||
nchars := utf8.RuneCount(l)
|
||||
nchars := util.CharacterCount(l)
|
||||
end.X = util.Clamp(end.X, 0, nchars)
|
||||
l = util.SliceStart(l, end.X)
|
||||
}
|
||||
@@ -49,6 +55,13 @@ func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||
}
|
||||
|
||||
func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||
lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
|
||||
if start.Y > b.LinesNum()-1 {
|
||||
start.X = lastcn - 1
|
||||
}
|
||||
if end.Y > b.LinesNum()-1 {
|
||||
end.X = lastcn
|
||||
}
|
||||
start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
|
||||
end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)
|
||||
|
||||
@@ -61,19 +74,19 @@ func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||
charpos := 0
|
||||
|
||||
if i == start.Y && start.Y == end.Y {
|
||||
nchars := utf8.RuneCount(l)
|
||||
nchars := util.CharacterCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars)
|
||||
end.X = util.Clamp(end.X, 0, nchars)
|
||||
l = util.SliceStart(l, end.X)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == start.Y {
|
||||
nchars := utf8.RuneCount(l)
|
||||
nchars := util.CharacterCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == end.Y {
|
||||
nchars := utf8.RuneCount(l)
|
||||
nchars := util.CharacterCount(l)
|
||||
end.X = util.Clamp(end.X, 0, nchars)
|
||||
l = util.SliceStart(l, end.X)
|
||||
}
|
||||
@@ -115,29 +128,32 @@ func (b *Buffer) FindNext(s string, start, end, from Loc, down bool, useRegex bo
|
||||
return [2]Loc{}, false, err
|
||||
}
|
||||
|
||||
found := false
|
||||
var found bool
|
||||
var l [2]Loc
|
||||
if down {
|
||||
l, found = b.findDown(r, from, end)
|
||||
if !found {
|
||||
l, found = b.findDown(r, start, from)
|
||||
l, found = b.findDown(r, start, end)
|
||||
}
|
||||
} else {
|
||||
l, found = b.findUp(r, from, start)
|
||||
if !found {
|
||||
l, found = b.findUp(r, end, from)
|
||||
l, found = b.findUp(r, end, start)
|
||||
}
|
||||
}
|
||||
return l, found, nil
|
||||
}
|
||||
|
||||
// ReplaceRegex replaces all occurrences of 'search' with 'replace' in the given area
|
||||
// and returns the number of replacements made
|
||||
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte) int {
|
||||
// and returns the number of replacements made and the number of runes
|
||||
// added or removed on the last line of the range
|
||||
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte) (int, int) {
|
||||
if start.GreaterThan(end) {
|
||||
start, end = end, start
|
||||
}
|
||||
|
||||
netrunes := 0
|
||||
|
||||
found := 0
|
||||
var deltas []Delta
|
||||
for i := start.Y; i <= end.Y; i++ {
|
||||
@@ -155,16 +171,23 @@ func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []b
|
||||
l = util.SliceStart(l, end.X)
|
||||
}
|
||||
newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
|
||||
result := []byte{}
|
||||
for _, submatches := range search.FindAllSubmatchIndex(in, -1) {
|
||||
result = search.Expand(result, replace, in, submatches)
|
||||
}
|
||||
found++
|
||||
return replace
|
||||
if i == end.Y {
|
||||
netrunes += util.CharacterCount(result) - util.CharacterCount(in)
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
from := Loc{charpos, i}
|
||||
to := Loc{charpos + utf8.RuneCount(l), i}
|
||||
to := Loc{charpos + util.CharacterCount(l), i}
|
||||
|
||||
deltas = append(deltas, Delta{newText, from, to})
|
||||
}
|
||||
b.MultipleReplace(deltas)
|
||||
|
||||
return found
|
||||
return found, netrunes
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/encoding"
|
||||
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
. "github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// The SerializedBuffer holds the types that get serialized when a buffer is saved
|
||||
@@ -30,7 +31,7 @@ func (b *Buffer) Serialize() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := config.ConfigDir + "/buffers/" + EscapePath(b.AbsPath)
|
||||
name := filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath))
|
||||
|
||||
return overwriteFile(name, encoding.Nop, func(file io.Writer) error {
|
||||
err := gob.NewEncoder(file).Encode(SerializedBuffer{
|
||||
@@ -39,23 +40,24 @@ func (b *Buffer) Serialize() error {
|
||||
b.ModTime,
|
||||
})
|
||||
return err
|
||||
})
|
||||
}, false)
|
||||
}
|
||||
|
||||
// Unserialize loads the buffer info from config.ConfigDir/buffers
|
||||
func (b *Buffer) Unserialize() error {
|
||||
// If either savecursor or saveundo is turned on, we need to load the serialized information
|
||||
// from ~/.config/micro/buffers
|
||||
if b.Path == "" {
|
||||
return nil
|
||||
}
|
||||
file, err := os.Open(config.ConfigDir + "/buffers/" + EscapePath(b.AbsPath))
|
||||
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath)))
|
||||
defer file.Close()
|
||||
if err == nil {
|
||||
var buffer SerializedBuffer
|
||||
decoder := gob.NewDecoder(file)
|
||||
err = decoder.Decode(&buffer)
|
||||
if err != nil {
|
||||
return errors.New(err.Error() + "\nYou may want to remove the files in ~/.config/micro/buffers (these files store the information for the 'saveundo' and 'savecursor' options) if this problem persists.")
|
||||
return errors.New(err.Error() + "\nYou may want to remove the files in ~/.config/micro/buffers (these files\nstore the information for the 'saveundo' and 'savecursor' options) if\nthis problem persists.\nThis may be caused by upgrading to version 2.0, and removing the 'buffers'\ndirectory will reset the cursor and undo history and solve the problem.")
|
||||
}
|
||||
if b.Settings["savecursor"].(bool) {
|
||||
b.StartCursor = buffer.Cursor
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
)
|
||||
|
||||
func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
|
||||
@@ -35,7 +35,7 @@ func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
|
||||
}
|
||||
} else if option == "encoding" {
|
||||
b.isModified = true
|
||||
} else if option == "readonly" {
|
||||
} else if option == "readonly" && b.Type.Kind == BTDefault.Kind {
|
||||
b.Type.Readonly = nativeValue.(bool)
|
||||
}
|
||||
|
||||
|
||||
@@ -151,6 +151,9 @@ func StringToStyle(str string) tcell.Style {
|
||||
if strings.Contains(str, "bold") {
|
||||
style = style.Bold(true)
|
||||
}
|
||||
if strings.Contains(str, "italic") {
|
||||
style = style.Italic(true)
|
||||
}
|
||||
if strings.Contains(str, "reverse") {
|
||||
style = style.Reverse(true)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package config
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
)
|
||||
@@ -14,16 +15,22 @@ var ConfigDir string
|
||||
func InitConfigDir(flagConfigDir string) error {
|
||||
var e error
|
||||
|
||||
xdgHome := os.Getenv("XDG_CONFIG_HOME")
|
||||
if xdgHome == "" {
|
||||
// The user has not set $XDG_CONFIG_HOME so we should act like it was set to ~/.config
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return errors.New("Error finding your home directory\nCan't load config files")
|
||||
microHome := os.Getenv("MICRO_CONFIG_HOME")
|
||||
if microHome == "" {
|
||||
// The user has not set $MICRO_CONFIG_HOME so we'll try $XDG_CONFIG_HOME
|
||||
xdgHome := os.Getenv("XDG_CONFIG_HOME")
|
||||
if xdgHome == "" {
|
||||
// The user has not set $XDG_CONFIG_HOME so we should act like it was set to ~/.config
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return errors.New("Error finding your home directory\nCan't load config files: " + err.Error())
|
||||
}
|
||||
xdgHome = filepath.Join(home, ".config")
|
||||
}
|
||||
xdgHome = home + "/.config"
|
||||
|
||||
microHome = filepath.Join(xdgHome, "micro")
|
||||
}
|
||||
ConfigDir = xdgHome + "/micro"
|
||||
ConfigDir = microHome
|
||||
|
||||
if len(flagConfigDir) > 0 {
|
||||
if _, err := os.Stat(flagConfigDir); os.IsNotExist(err) {
|
||||
@@ -34,20 +41,11 @@ func InitConfigDir(flagConfigDir string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(xdgHome); os.IsNotExist(err) {
|
||||
// If the xdgHome doesn't exist we should create it
|
||||
err = os.Mkdir(xdgHome, os.ModePerm)
|
||||
if err != nil {
|
||||
return errors.New("Error creating XDG_CONFIG_HOME directory: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(ConfigDir); os.IsNotExist(err) {
|
||||
// If the micro specific config directory doesn't exist we should create that too
|
||||
err = os.Mkdir(ConfigDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return errors.New("Error creating configuration directory: " + err.Error())
|
||||
}
|
||||
// Create micro config home directory if it does not exist
|
||||
// This creates parent directories and does nothing if it already exists
|
||||
err := os.MkdirAll(ConfigDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return errors.New("Error creating configuration directory: " + err.Error())
|
||||
}
|
||||
|
||||
return e
|
||||
|
||||
@@ -2,11 +2,13 @@ package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
)
|
||||
|
||||
// ErrNoSuchFunction is returned when Call is executed on a function that does not exist
|
||||
var ErrNoSuchFunction = errors.New("No such function exists")
|
||||
|
||||
// LoadAllPlugins loads all detected plugins (in runtime/plugins and ConfigDir/plugins)
|
||||
@@ -55,16 +57,16 @@ func RunPluginFnBool(fn string, args ...lua.LValue) (bool, error) {
|
||||
reterr = errors.New("Plugin " + p.Name + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
if v, ok := val.(lua.LBool); !ok {
|
||||
reterr = errors.New(p.Name + "." + fn + " should return a boolean")
|
||||
} else {
|
||||
if v, ok := val.(lua.LBool); ok {
|
||||
retbool = retbool && bool(v)
|
||||
}
|
||||
}
|
||||
return retbool, reterr
|
||||
}
|
||||
|
||||
// Plugin stores information about the source files/info for a plugin
|
||||
type Plugin struct {
|
||||
DirName string // name of plugin folder
|
||||
Name string // name of plugin
|
||||
Info *PluginInfo // json file containing info
|
||||
Srcs []RuntimeFile // lua files
|
||||
@@ -72,6 +74,7 @@ type Plugin struct {
|
||||
Default bool // pre-installed plugin
|
||||
}
|
||||
|
||||
// IsEnabled returns if a plugin is enabled
|
||||
func (p *Plugin) IsEnabled() bool {
|
||||
if v, ok := GlobalSettings[p.Name]; ok {
|
||||
return v.(bool) && p.Loaded
|
||||
@@ -79,13 +82,15 @@ func (p *Plugin) IsEnabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Plugins is a list of all detected plugins (enabled or disabled)
|
||||
var Plugins []*Plugin
|
||||
|
||||
// Load creates an option for the plugin and runs all source files
|
||||
func (p *Plugin) Load() error {
|
||||
if v, ok := GlobalSettings[p.Name]; ok && !v.(bool) {
|
||||
return nil
|
||||
}
|
||||
for _, f := range p.Srcs {
|
||||
if v, ok := GlobalSettings[p.Name]; ok && !v.(bool) {
|
||||
return nil
|
||||
}
|
||||
dat, err := f.Data()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -94,14 +99,19 @@ func (p *Plugin) Load() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Loaded = true
|
||||
RegisterGlobalOption(p.Name, true)
|
||||
}
|
||||
p.Loaded = true
|
||||
RegisterGlobalOption(p.Name, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Call calls a given function in this plugin
|
||||
func (p *Plugin) Call(fn string, args ...lua.LValue) (lua.LValue, error) {
|
||||
plug := ulua.L.GetGlobal(p.Name)
|
||||
if plug == lua.LNil {
|
||||
log.Println("Plugin does not exist:", p.Name, "at", p.DirName, ":", p)
|
||||
return nil, nil
|
||||
}
|
||||
luafn := ulua.L.GetField(plug, fn)
|
||||
if luafn == lua.LNil {
|
||||
return nil, ErrNoSuchFunction
|
||||
@@ -119,7 +129,23 @@ func (p *Plugin) Call(fn string, args ...lua.LValue) (lua.LValue, error) {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// FindPlugin returns the plugin with the given name
|
||||
func FindPlugin(name string) *Plugin {
|
||||
var pl *Plugin
|
||||
for _, p := range Plugins {
|
||||
if !p.IsEnabled() {
|
||||
continue
|
||||
}
|
||||
if p.Name == name {
|
||||
pl = p
|
||||
break
|
||||
}
|
||||
}
|
||||
return pl
|
||||
}
|
||||
|
||||
// FindAnyPlugin does not require the plugin to be enabled
|
||||
func FindAnyPlugin(name string) *Plugin {
|
||||
var pl *Plugin
|
||||
for _, p := range Plugins {
|
||||
if p.Name == name {
|
||||
|
||||
712
internal/config/plugin_installer.go
Normal file
712
internal/config/plugin_installer.go
Normal file
@@ -0,0 +1,712 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/blang/semver"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/json5"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
var (
|
||||
allPluginPackages PluginPackages
|
||||
)
|
||||
|
||||
// CorePluginName is a plugin dependency name for the micro core.
|
||||
const CorePluginName = "micro"
|
||||
|
||||
// PluginChannel contains an url to a json list of PluginRepository
|
||||
type PluginChannel string
|
||||
|
||||
// PluginChannels is a slice of PluginChannel
|
||||
type PluginChannels []PluginChannel
|
||||
|
||||
// PluginRepository contains an url to json file containing PluginPackages
|
||||
type PluginRepository string
|
||||
|
||||
// PluginPackage contains the meta-data of a plugin and all available versions
|
||||
type PluginPackage struct {
|
||||
Name string
|
||||
Description string
|
||||
Author string
|
||||
Tags []string
|
||||
Versions PluginVersions
|
||||
}
|
||||
|
||||
// PluginPackages is a list of PluginPackage instances.
|
||||
type PluginPackages []*PluginPackage
|
||||
|
||||
// PluginVersion descripes a version of a PluginPackage. Containing a version, download url and also dependencies.
|
||||
type PluginVersion struct {
|
||||
pack *PluginPackage
|
||||
Version semver.Version
|
||||
Url string
|
||||
Require PluginDependencies
|
||||
}
|
||||
|
||||
func (pv *PluginVersion) Pack() *PluginPackage {
|
||||
return pv.pack
|
||||
}
|
||||
|
||||
// PluginVersions is a slice of PluginVersion
|
||||
type PluginVersions []*PluginVersion
|
||||
|
||||
// PluginDependency descripes a dependency to another plugin or micro itself.
|
||||
type PluginDependency struct {
|
||||
Name string
|
||||
Range semver.Range
|
||||
}
|
||||
|
||||
// PluginDependencies is a slice of PluginDependency
|
||||
type PluginDependencies []*PluginDependency
|
||||
|
||||
func (pp *PluginPackage) String() string {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString("Plugin: ")
|
||||
buf.WriteString(pp.Name)
|
||||
buf.WriteRune('\n')
|
||||
if pp.Author != "" {
|
||||
buf.WriteString("Author: ")
|
||||
buf.WriteString(pp.Author)
|
||||
buf.WriteRune('\n')
|
||||
}
|
||||
if pp.Description != "" {
|
||||
buf.WriteRune('\n')
|
||||
buf.WriteString(pp.Description)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func fetchAllSources(count int, fetcher func(i int) PluginPackages) PluginPackages {
|
||||
wgQuery := new(sync.WaitGroup)
|
||||
wgQuery.Add(count)
|
||||
|
||||
results := make(chan PluginPackages)
|
||||
|
||||
wgDone := new(sync.WaitGroup)
|
||||
wgDone.Add(1)
|
||||
var packages PluginPackages
|
||||
for i := 0; i < count; i++ {
|
||||
go func(i int) {
|
||||
results <- fetcher(i)
|
||||
wgQuery.Done()
|
||||
}(i)
|
||||
}
|
||||
go func() {
|
||||
packages = make(PluginPackages, 0)
|
||||
for res := range results {
|
||||
packages = append(packages, res...)
|
||||
}
|
||||
wgDone.Done()
|
||||
}()
|
||||
wgQuery.Wait()
|
||||
close(results)
|
||||
wgDone.Wait()
|
||||
return packages
|
||||
}
|
||||
|
||||
// Fetch retrieves all available PluginPackages from the given channels
|
||||
func (pc PluginChannels) Fetch(out io.Writer) PluginPackages {
|
||||
return fetchAllSources(len(pc), func(i int) PluginPackages {
|
||||
return pc[i].Fetch(out)
|
||||
})
|
||||
}
|
||||
|
||||
// Fetch retrieves all available PluginPackages from the given channel
|
||||
func (pc PluginChannel) Fetch(out io.Writer) PluginPackages {
|
||||
resp, err := http.Get(string(pc))
|
||||
if err != nil {
|
||||
fmt.Fprintln(out, "Failed to query plugin channel:\n", err)
|
||||
return PluginPackages{}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
decoder := json5.NewDecoder(resp.Body)
|
||||
|
||||
var repositories []PluginRepository
|
||||
if err := decoder.Decode(&repositories); err != nil {
|
||||
fmt.Fprintln(out, "Failed to decode channel data:\n", err)
|
||||
return PluginPackages{}
|
||||
}
|
||||
return fetchAllSources(len(repositories), func(i int) PluginPackages {
|
||||
return repositories[i].Fetch(out)
|
||||
})
|
||||
}
|
||||
|
||||
// Fetch retrieves all available PluginPackages from the given repository
|
||||
func (pr PluginRepository) Fetch(out io.Writer) PluginPackages {
|
||||
resp, err := http.Get(string(pr))
|
||||
if err != nil {
|
||||
fmt.Fprintln(out, "Failed to query plugin repository:\n", err)
|
||||
return PluginPackages{}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
decoder := json5.NewDecoder(resp.Body)
|
||||
|
||||
var plugins PluginPackages
|
||||
if err := decoder.Decode(&plugins); err != nil {
|
||||
fmt.Fprintln(out, "Failed to decode repository data:\n", err)
|
||||
return PluginPackages{}
|
||||
}
|
||||
if len(plugins) > 0 {
|
||||
return PluginPackages{plugins[0]}
|
||||
}
|
||||
return nil
|
||||
// return plugins
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals raw json to a PluginVersion
|
||||
func (pv *PluginVersion) UnmarshalJSON(data []byte) error {
|
||||
var values struct {
|
||||
Version semver.Version
|
||||
Url string
|
||||
Require map[string]string
|
||||
}
|
||||
|
||||
if err := json5.Unmarshal(data, &values); err != nil {
|
||||
return err
|
||||
}
|
||||
pv.Version = values.Version
|
||||
pv.Url = values.Url
|
||||
pv.Require = make(PluginDependencies, 0)
|
||||
|
||||
for k, v := range values.Require {
|
||||
// don't add the dependency if it's the core and
|
||||
// we have a unknown version number.
|
||||
// in that case just accept that dependency (which equals to not adding it.)
|
||||
if k != CorePluginName || !isUnknownCoreVersion() {
|
||||
if vRange, err := semver.ParseRange(v); err == nil {
|
||||
pv.Require = append(pv.Require, &PluginDependency{k, vRange})
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals raw json to a PluginPackage
|
||||
func (pp *PluginPackage) UnmarshalJSON(data []byte) error {
|
||||
var values struct {
|
||||
Name string
|
||||
Description string
|
||||
Author string
|
||||
Tags []string
|
||||
Versions PluginVersions
|
||||
}
|
||||
if err := json5.Unmarshal(data, &values); err != nil {
|
||||
return err
|
||||
}
|
||||
pp.Name = values.Name
|
||||
pp.Description = values.Description
|
||||
pp.Author = values.Author
|
||||
pp.Tags = values.Tags
|
||||
pp.Versions = values.Versions
|
||||
for _, v := range pp.Versions {
|
||||
v.pack = pp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAllPluginPackages gets all PluginPackages which may be available.
|
||||
func GetAllPluginPackages(out io.Writer) PluginPackages {
|
||||
if allPluginPackages == nil {
|
||||
getOption := func(name string) []string {
|
||||
data := GetGlobalOption(name)
|
||||
if strs, ok := data.([]string); ok {
|
||||
return strs
|
||||
}
|
||||
if ifs, ok := data.([]interface{}); ok {
|
||||
result := make([]string, len(ifs))
|
||||
for i, urlIf := range ifs {
|
||||
if url, ok := urlIf.(string); ok {
|
||||
result[i] = url
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
channels := PluginChannels{}
|
||||
for _, url := range getOption("pluginchannels") {
|
||||
channels = append(channels, PluginChannel(url))
|
||||
}
|
||||
repos := []PluginRepository{}
|
||||
for _, url := range getOption("pluginrepos") {
|
||||
repos = append(repos, PluginRepository(url))
|
||||
}
|
||||
allPluginPackages = fetchAllSources(len(repos)+1, func(i int) PluginPackages {
|
||||
if i == 0 {
|
||||
return channels.Fetch(out)
|
||||
}
|
||||
return repos[i-1].Fetch(out)
|
||||
})
|
||||
}
|
||||
return allPluginPackages
|
||||
}
|
||||
|
||||
func (pv PluginVersions) find(ppName string) *PluginVersion {
|
||||
for _, v := range pv {
|
||||
if v.pack.Name == ppName {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Len returns the number of pluginversions in this slice
|
||||
func (pv PluginVersions) Len() int {
|
||||
return len(pv)
|
||||
}
|
||||
|
||||
// Swap two entries of the slice
|
||||
func (pv PluginVersions) Swap(i, j int) {
|
||||
pv[i], pv[j] = pv[j], pv[i]
|
||||
}
|
||||
|
||||
// Less returns true if the version at position i is greater then the version at position j (used for sorting)
|
||||
func (pv PluginVersions) Less(i, j int) bool {
|
||||
return pv[i].Version.GT(pv[j].Version)
|
||||
}
|
||||
|
||||
// Match returns true if the package matches a given search text
|
||||
func (pp PluginPackage) Match(text string) bool {
|
||||
text = strings.ToLower(text)
|
||||
for _, t := range pp.Tags {
|
||||
if strings.ToLower(t) == text {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if strings.Contains(strings.ToLower(pp.Name), text) {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.Contains(strings.ToLower(pp.Description), text) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsInstallable returns true if the package can be installed.
|
||||
func (pp PluginPackage) IsInstallable(out io.Writer) error {
|
||||
_, err := GetAllPluginPackages(out).Resolve(GetInstalledVersions(true), PluginDependencies{
|
||||
&PluginDependency{
|
||||
Name: pp.Name,
|
||||
Range: semver.Range(func(v semver.Version) bool { return true }),
|
||||
}})
|
||||
return err
|
||||
}
|
||||
|
||||
// SearchPlugin retrieves a list of all PluginPackages which match the given search text and
|
||||
// could be or are already installed
|
||||
func SearchPlugin(out io.Writer, texts []string) (plugins PluginPackages) {
|
||||
plugins = make(PluginPackages, 0)
|
||||
|
||||
pluginLoop:
|
||||
for _, pp := range GetAllPluginPackages(out) {
|
||||
for _, text := range texts {
|
||||
if !pp.Match(text) {
|
||||
continue pluginLoop
|
||||
}
|
||||
}
|
||||
|
||||
if err := pp.IsInstallable(out); err == nil {
|
||||
plugins = append(plugins, pp)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func isUnknownCoreVersion() bool {
|
||||
_, err := semver.ParseTolerant(util.Version)
|
||||
return err != nil
|
||||
}
|
||||
|
||||
func newStaticPluginVersion(name, version string) *PluginVersion {
|
||||
vers, err := semver.ParseTolerant(version)
|
||||
|
||||
if err != nil {
|
||||
if vers, err = semver.ParseTolerant("0.0.0-" + version); err != nil {
|
||||
vers = semver.MustParse("0.0.0-unknown")
|
||||
}
|
||||
}
|
||||
pl := &PluginPackage{
|
||||
Name: name,
|
||||
}
|
||||
pv := &PluginVersion{
|
||||
pack: pl,
|
||||
Version: vers,
|
||||
}
|
||||
pl.Versions = PluginVersions{pv}
|
||||
return pv
|
||||
}
|
||||
|
||||
// GetInstalledVersions returns a list of all currently installed plugins including an entry for
|
||||
// micro itself. This can be used to resolve dependencies.
|
||||
func GetInstalledVersions(withCore bool) PluginVersions {
|
||||
result := PluginVersions{}
|
||||
if withCore {
|
||||
result = append(result, newStaticPluginVersion(CorePluginName, util.Version))
|
||||
}
|
||||
|
||||
for _, p := range Plugins {
|
||||
if !p.IsEnabled() {
|
||||
continue
|
||||
}
|
||||
version := GetInstalledPluginVersion(p.Name)
|
||||
if pv := newStaticPluginVersion(p.Name, version); pv != nil {
|
||||
result = append(result, pv)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetInstalledPluginVersion returns the string of the exported VERSION variable of a loaded plugin
|
||||
func GetInstalledPluginVersion(name string) string {
|
||||
plugin := ulua.L.GetGlobal(name)
|
||||
if plugin != lua.LNil {
|
||||
version := ulua.L.GetField(plugin, "VERSION")
|
||||
if str, ok := version.(lua.LString); ok {
|
||||
return string(str)
|
||||
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// DownloadAndInstall downloads and installs the given plugin and version
|
||||
func (pv *PluginVersion) DownloadAndInstall(out io.Writer) error {
|
||||
fmt.Fprintf(out, "Downloading %q (%s) from %q\n", pv.pack.Name, pv.Version, pv.Url)
|
||||
resp, err := http.Get(pv.Url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
zipbuf := bytes.NewReader(data)
|
||||
z, err := zip.NewReader(zipbuf, zipbuf.Size())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetDir := filepath.Join(ConfigDir, "plug", pv.pack.Name)
|
||||
dirPerm := os.FileMode(0755)
|
||||
if err = os.MkdirAll(targetDir, dirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if all files in zip are in the same directory.
|
||||
// this might be the case if the plugin zip contains the whole plugin dir
|
||||
// instead of its content.
|
||||
var prefix string
|
||||
allPrefixed := false
|
||||
for i, f := range z.File {
|
||||
parts := strings.Split(f.Name, "/")
|
||||
if i == 0 {
|
||||
prefix = parts[0]
|
||||
} else if parts[0] != prefix {
|
||||
allPrefixed = false
|
||||
break
|
||||
} else {
|
||||
// switch to true since we have at least a second file
|
||||
allPrefixed = true
|
||||
}
|
||||
}
|
||||
|
||||
// Install files and directory's
|
||||
for _, f := range z.File {
|
||||
parts := strings.Split(f.Name, "/")
|
||||
if allPrefixed {
|
||||
parts = parts[1:]
|
||||
}
|
||||
|
||||
targetName := filepath.Join(targetDir, filepath.Join(parts...))
|
||||
if f.FileInfo().IsDir() {
|
||||
if err := os.MkdirAll(targetName, dirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
basepath := filepath.Dir(targetName)
|
||||
|
||||
if err := os.MkdirAll(basepath, dirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer content.Close()
|
||||
target, err := os.Create(targetName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer target.Close()
|
||||
if _, err = io.Copy(target, content); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pl PluginPackages) Get(name string) *PluginPackage {
|
||||
for _, p := range pl {
|
||||
if p.Name == name {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pl PluginPackages) GetAllVersions(name string) PluginVersions {
|
||||
result := make(PluginVersions, 0)
|
||||
p := pl.Get(name)
|
||||
if p != nil {
|
||||
for _, v := range p.Versions {
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (req PluginDependencies) Join(other PluginDependencies) PluginDependencies {
|
||||
m := make(map[string]*PluginDependency)
|
||||
for _, r := range req {
|
||||
m[r.Name] = r
|
||||
}
|
||||
for _, o := range other {
|
||||
cur, ok := m[o.Name]
|
||||
if ok {
|
||||
m[o.Name] = &PluginDependency{
|
||||
o.Name,
|
||||
o.Range.AND(cur.Range),
|
||||
}
|
||||
} else {
|
||||
m[o.Name] = o
|
||||
}
|
||||
}
|
||||
result := make(PluginDependencies, 0, len(m))
|
||||
for _, v := range m {
|
||||
result = append(result, v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Resolve resolves dependencies between different plugins
|
||||
func (all PluginPackages) Resolve(selectedVersions PluginVersions, open PluginDependencies) (PluginVersions, error) {
|
||||
if len(open) == 0 {
|
||||
return selectedVersions, nil
|
||||
}
|
||||
currentRequirement, stillOpen := open[0], open[1:]
|
||||
if currentRequirement != nil {
|
||||
if selVersion := selectedVersions.find(currentRequirement.Name); selVersion != nil {
|
||||
if currentRequirement.Range(selVersion.Version) {
|
||||
return all.Resolve(selectedVersions, stillOpen)
|
||||
}
|
||||
return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
|
||||
}
|
||||
availableVersions := all.GetAllVersions(currentRequirement.Name)
|
||||
sort.Sort(availableVersions)
|
||||
|
||||
for _, version := range availableVersions {
|
||||
if currentRequirement.Range(version.Version) {
|
||||
resolved, err := all.Resolve(append(selectedVersions, version), stillOpen.Join(version.Require))
|
||||
|
||||
if err == nil {
|
||||
return resolved, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
|
||||
}
|
||||
return selectedVersions, nil
|
||||
}
|
||||
|
||||
func (pv PluginVersions) install(out io.Writer) {
|
||||
anyInstalled := false
|
||||
currentlyInstalled := GetInstalledVersions(true)
|
||||
|
||||
for _, sel := range pv {
|
||||
if sel.pack.Name != CorePluginName {
|
||||
shouldInstall := true
|
||||
if pv := currentlyInstalled.find(sel.pack.Name); pv != nil {
|
||||
if pv.Version.NE(sel.Version) {
|
||||
fmt.Fprintln(out, "Uninstalling", sel.pack.Name)
|
||||
UninstallPlugin(out, sel.pack.Name)
|
||||
} else {
|
||||
shouldInstall = false
|
||||
}
|
||||
}
|
||||
|
||||
if shouldInstall {
|
||||
if err := sel.DownloadAndInstall(out); err != nil {
|
||||
fmt.Fprintln(out, err)
|
||||
return
|
||||
}
|
||||
anyInstalled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if anyInstalled {
|
||||
fmt.Fprintln(out, "One or more plugins installed.")
|
||||
} else {
|
||||
fmt.Fprintln(out, "Nothing to install / update")
|
||||
}
|
||||
}
|
||||
|
||||
// UninstallPlugin deletes the plugin folder of the given plugin
|
||||
func UninstallPlugin(out io.Writer, name string) {
|
||||
for _, p := range Plugins {
|
||||
if !p.IsEnabled() {
|
||||
continue
|
||||
}
|
||||
if p.Name == name {
|
||||
p.Loaded = false
|
||||
if err := os.RemoveAll(filepath.Join(ConfigDir, "plug", p.DirName)); err != nil {
|
||||
fmt.Fprintln(out, err)
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Install installs the plugin
|
||||
func (pl PluginPackage) Install(out io.Writer) {
|
||||
selected, err := GetAllPluginPackages(out).Resolve(GetInstalledVersions(true), PluginDependencies{
|
||||
&PluginDependency{
|
||||
Name: pl.Name,
|
||||
Range: semver.Range(func(v semver.Version) bool { return true }),
|
||||
}})
|
||||
if err != nil {
|
||||
fmt.Fprintln(out, err)
|
||||
return
|
||||
}
|
||||
selected.install(out)
|
||||
}
|
||||
|
||||
// UpdatePlugins updates the given plugins
|
||||
func UpdatePlugins(out io.Writer, plugins []string) {
|
||||
// if no plugins are specified, update all installed plugins.
|
||||
if len(plugins) == 0 {
|
||||
for _, p := range Plugins {
|
||||
if !p.IsEnabled() {
|
||||
continue
|
||||
}
|
||||
plugins = append(plugins, p.Name)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(out, "Checking for plugin updates")
|
||||
microVersion := PluginVersions{
|
||||
newStaticPluginVersion(CorePluginName, util.Version),
|
||||
}
|
||||
|
||||
var updates = make(PluginDependencies, 0)
|
||||
for _, name := range plugins {
|
||||
pv := GetInstalledPluginVersion(name)
|
||||
r, err := semver.ParseRange(">=" + pv) // Try to get newer versions.
|
||||
if err == nil {
|
||||
updates = append(updates, &PluginDependency{
|
||||
Name: name,
|
||||
Range: r,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
selected, err := GetAllPluginPackages(out).Resolve(microVersion, updates)
|
||||
if err != nil {
|
||||
fmt.Fprintln(out, err)
|
||||
return
|
||||
}
|
||||
selected.install(out)
|
||||
}
|
||||
|
||||
func PluginCommand(out io.Writer, cmd string, args []string) {
|
||||
switch cmd {
|
||||
case "install":
|
||||
installedVersions := GetInstalledVersions(false)
|
||||
for _, plugin := range args {
|
||||
pp := GetAllPluginPackages(out).Get(plugin)
|
||||
if pp == nil {
|
||||
fmt.Fprintln(out, "Unknown plugin \""+plugin+"\"")
|
||||
} else if err := pp.IsInstallable(out); err != nil {
|
||||
fmt.Fprintln(out, "Error installing ", plugin, ": ", err)
|
||||
} else {
|
||||
for _, installed := range installedVersions {
|
||||
if pp.Name == installed.Pack().Name {
|
||||
if pp.Versions[0].Version.Compare(installed.Version) == 1 {
|
||||
fmt.Fprintln(out, pp.Name, " is already installed but out-of-date: use 'plugin update ", pp.Name, "' to update")
|
||||
} else {
|
||||
fmt.Fprintln(out, pp.Name, " is already installed")
|
||||
}
|
||||
}
|
||||
}
|
||||
pp.Install(out)
|
||||
}
|
||||
}
|
||||
|
||||
case "remove":
|
||||
removed := ""
|
||||
for _, plugin := range args {
|
||||
// check if the plugin exists.
|
||||
for _, p := range Plugins {
|
||||
if p.Name == plugin && p.Default {
|
||||
fmt.Fprintln(out, "Default plugins cannot be removed, but can be disabled via settings.")
|
||||
continue
|
||||
}
|
||||
if p.Name == plugin {
|
||||
UninstallPlugin(out, plugin)
|
||||
removed += plugin + " "
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if removed != "" {
|
||||
fmt.Fprintln(out, "Removed ", removed)
|
||||
} else {
|
||||
fmt.Fprintln(out, "No plugins removed")
|
||||
}
|
||||
case "update":
|
||||
UpdatePlugins(out, args)
|
||||
case "list":
|
||||
plugins := GetInstalledVersions(false)
|
||||
fmt.Fprintln(out, "The following plugins are currently installed:")
|
||||
for _, p := range plugins {
|
||||
fmt.Fprintf(out, "%s (%s)\n", p.Pack().Name, p.Version)
|
||||
}
|
||||
case "search":
|
||||
plugins := SearchPlugin(out, args)
|
||||
fmt.Fprintln(out, len(plugins), " plugins found")
|
||||
for _, p := range plugins {
|
||||
fmt.Fprintln(out, "----------------")
|
||||
fmt.Fprintln(out, p.String())
|
||||
}
|
||||
fmt.Fprintln(out, "----------------")
|
||||
case "available":
|
||||
packages := GetAllPluginPackages(out)
|
||||
fmt.Fprintln(out, "Available Plugins:")
|
||||
for _, pkg := range packages {
|
||||
fmt.Fprintln(out, pkg.Name)
|
||||
}
|
||||
default:
|
||||
fmt.Fprintln(out, "Invalid plugin command")
|
||||
}
|
||||
}
|
||||
56
internal/config/plugin_installer_test.go
Normal file
56
internal/config/plugin_installer_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/blang/semver"
|
||||
|
||||
"github.com/zyedidia/json5"
|
||||
)
|
||||
|
||||
func TestDependencyResolving(t *testing.T) {
|
||||
js := `
|
||||
[{
|
||||
"Name": "Foo",
|
||||
"Versions": [{ "Version": "1.0.0" }, { "Version": "1.5.0" },{ "Version": "2.0.0" }]
|
||||
}, {
|
||||
"Name": "Bar",
|
||||
"Versions": [{ "Version": "1.0.0", "Require": {"Foo": ">1.0.0 <2.0.0"} }]
|
||||
}, {
|
||||
"Name": "Unresolvable",
|
||||
"Versions": [{ "Version": "1.0.0", "Require": {"Foo": "<=1.0.0", "Bar": ">0.0.0"} }]
|
||||
}]
|
||||
`
|
||||
var all PluginPackages
|
||||
err := json5.Unmarshal([]byte(js), &all)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
selected, err := all.Resolve(PluginVersions{}, PluginDependencies{
|
||||
&PluginDependency{"Bar", semver.MustParseRange(">=1.0.0")},
|
||||
})
|
||||
|
||||
check := func(name, version string) {
|
||||
v := selected.find(name)
|
||||
expected := semver.MustParse(version)
|
||||
if v == nil {
|
||||
t.Errorf("Failed to resolve %s", name)
|
||||
} else if expected.NE(v.Version) {
|
||||
t.Errorf("%s resolved in wrong version %v", name, v)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
check("Foo", "1.5.0")
|
||||
check("Bar", "1.0.0")
|
||||
}
|
||||
|
||||
selected, err = all.Resolve(PluginVersions{}, PluginDependencies{
|
||||
&PluginDependency{"Unresolvable", semver.MustParseRange(">0.0.0")},
|
||||
})
|
||||
if err == nil {
|
||||
t.Error("Unresolvable package resolved:", selected)
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMissingName = errors.New("Missing or empty name field")
|
||||
ErrMissingDesc = errors.New("Missing or empty description field")
|
||||
ErrMissingSite = errors.New("Missing or empty website field")
|
||||
ErrMissingInstall = errors.New("Missing or empty install field")
|
||||
ErrMissingVstr = errors.New("Missing or empty versions field")
|
||||
ErrMissingRequire = errors.New("Missing or empty require field")
|
||||
ErrMissingName = errors.New("Missing or empty name field")
|
||||
ErrMissingDesc = errors.New("Missing or empty description field")
|
||||
ErrMissingSite = errors.New("Missing or empty website field")
|
||||
)
|
||||
|
||||
// PluginInfo contains all the needed info about a plugin
|
||||
@@ -27,19 +24,16 @@ var (
|
||||
// Vstr: version
|
||||
// Require: list of dependencies and requirements
|
||||
type PluginInfo struct {
|
||||
Name string `json:"name"`
|
||||
Desc string `json:"description"`
|
||||
Site string `json:"website"`
|
||||
Install string `json:"install"`
|
||||
Vstr string `json:"version"`
|
||||
Require []string `json:"require"`
|
||||
Name string `json:"Name"`
|
||||
Desc string `json:"Description"`
|
||||
Site string `json:"Website"`
|
||||
}
|
||||
|
||||
// NewPluginInfo parses a JSON input into a valid PluginInfo struct
|
||||
// Returns an error if there are any missing fields or any invalid fields
|
||||
// There are no optional fields in a plugin info json file
|
||||
func NewPluginInfo(data []byte) (*PluginInfo, error) {
|
||||
var info PluginInfo
|
||||
var info []PluginInfo
|
||||
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
// dec.DisallowUnknownFields() // Force errors
|
||||
@@ -48,19 +42,5 @@ func NewPluginInfo(data []byte) (*PluginInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if len(info.Name) == 0 {
|
||||
// return nil, ErrMissingName
|
||||
// } else if len(info.Desc) == 0 {
|
||||
// return nil, ErrMissingDesc
|
||||
// } else if len(info.Site) == 0 {
|
||||
// return nil, ErrMissingSite
|
||||
// } else if len(info.Install) == 0 {
|
||||
// return nil, ErrMissingInstall
|
||||
// } else if len(info.Vstr) == 0 {
|
||||
// return nil, ErrMissingVstr
|
||||
// } else if len(info.Require) == 0 {
|
||||
// return nil, ErrMissingRequire
|
||||
// }
|
||||
|
||||
return &info, nil
|
||||
return &info[0], nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
@@ -11,14 +12,18 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
RTColorscheme = 0
|
||||
RTSyntax = 1
|
||||
RTHelp = 2
|
||||
RTPlugin = 3
|
||||
NumTypes = 4 // How many filetypes are there
|
||||
RTColorscheme = 0
|
||||
RTSyntax = 1
|
||||
RTHelp = 2
|
||||
RTPlugin = 3
|
||||
RTSyntaxHeader = 4
|
||||
)
|
||||
|
||||
type RTFiletype byte
|
||||
var (
|
||||
NumTypes = 5 // How many filetypes are there
|
||||
)
|
||||
|
||||
type RTFiletype int
|
||||
|
||||
// RuntimeFile allows the program to read runtime data like colorschemes or syntax files
|
||||
type RuntimeFile interface {
|
||||
@@ -29,7 +34,21 @@ type RuntimeFile interface {
|
||||
}
|
||||
|
||||
// allFiles contains all available files, mapped by filetype
|
||||
var allFiles [NumTypes][]RuntimeFile
|
||||
var allFiles [][]RuntimeFile
|
||||
var realFiles [][]RuntimeFile
|
||||
|
||||
func init() {
|
||||
allFiles = make([][]RuntimeFile, NumTypes)
|
||||
realFiles = make([][]RuntimeFile, NumTypes)
|
||||
}
|
||||
|
||||
// NewRTFiletype creates a new RTFiletype
|
||||
func NewRTFiletype() int {
|
||||
NumTypes++
|
||||
allFiles = append(allFiles, []RuntimeFile{})
|
||||
realFiles = append(realFiles, []RuntimeFile{})
|
||||
return NumTypes - 1
|
||||
}
|
||||
|
||||
// some file on filesystem
|
||||
type realFile string
|
||||
@@ -83,6 +102,12 @@ func AddRuntimeFile(fileType RTFiletype, file RuntimeFile) {
|
||||
allFiles[fileType] = append(allFiles[fileType], file)
|
||||
}
|
||||
|
||||
// AddRealRuntimeFile registers a file for the given filetype
|
||||
func AddRealRuntimeFile(fileType RTFiletype, file RuntimeFile) {
|
||||
allFiles[fileType] = append(allFiles[fileType], file)
|
||||
realFiles[fileType] = append(realFiles[fileType], file)
|
||||
}
|
||||
|
||||
// AddRuntimeFilesFromDirectory registers each file from the given directory for
|
||||
// the filetype which matches the file-pattern
|
||||
func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string) {
|
||||
@@ -90,7 +115,7 @@ func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string
|
||||
for _, f := range files {
|
||||
if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
|
||||
fullPath := filepath.Join(directory, f.Name())
|
||||
AddRuntimeFile(fileType, realFile(fullPath))
|
||||
AddRealRuntimeFile(fileType, realFile(fullPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,6 +150,12 @@ func ListRuntimeFiles(fileType RTFiletype) []RuntimeFile {
|
||||
return allFiles[fileType]
|
||||
}
|
||||
|
||||
// ListRealRuntimeFiles lists all real runtime files (on disk) for a filetype
|
||||
// these runtime files will be ones defined by the user and loaded from the config directory
|
||||
func ListRealRuntimeFiles(fileType RTFiletype) []RuntimeFile {
|
||||
return realFiles[fileType]
|
||||
}
|
||||
|
||||
// InitRuntimeFiles initializes all assets file and the config directory
|
||||
func InitRuntimeFiles() {
|
||||
add := func(fileType RTFiletype, dir, pattern string) {
|
||||
@@ -134,12 +165,14 @@ func InitRuntimeFiles() {
|
||||
|
||||
add(RTColorscheme, "colorschemes", "*.micro")
|
||||
add(RTSyntax, "syntax", "*.yaml")
|
||||
add(RTSyntaxHeader, "syntax", "*.hdr")
|
||||
add(RTHelp, "help", "*.md")
|
||||
|
||||
initlua := filepath.Join(ConfigDir, "init.lua")
|
||||
if _, err := os.Stat(initlua); !os.IsNotExist(err) {
|
||||
p := new(Plugin)
|
||||
p.Name = "initlua"
|
||||
p.DirName = "initlua"
|
||||
p.Srcs = append(p.Srcs, realFile(initlua))
|
||||
Plugins = append(Plugins, p)
|
||||
}
|
||||
@@ -155,21 +188,25 @@ func InitRuntimeFiles() {
|
||||
srcs, _ := ioutil.ReadDir(filepath.Join(plugdir, d.Name()))
|
||||
p := new(Plugin)
|
||||
p.Name = d.Name()
|
||||
p.DirName = d.Name()
|
||||
for _, f := range srcs {
|
||||
if strings.HasSuffix(f.Name(), ".lua") {
|
||||
p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
|
||||
} else if f.Name() == "info.json" {
|
||||
data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), "info.json"))
|
||||
} else if strings.HasSuffix(f.Name(), ".json") {
|
||||
data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p.Info, err = NewPluginInfo(data)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p.Info, _ = NewPluginInfo(data)
|
||||
p.Name = p.Info.Name
|
||||
}
|
||||
}
|
||||
|
||||
if !isID(p.Name) {
|
||||
log.Println("Invalid plugin name", p.Name)
|
||||
if !isID(p.Name) || len(p.Srcs) <= 0 {
|
||||
log.Println(p.Name, "is not a plugin")
|
||||
continue
|
||||
}
|
||||
Plugins = append(Plugins, p)
|
||||
@@ -182,21 +219,25 @@ func InitRuntimeFiles() {
|
||||
if srcs, err := AssetDir(filepath.Join(plugdir, d)); err == nil {
|
||||
p := new(Plugin)
|
||||
p.Name = d
|
||||
p.DirName = d
|
||||
p.Default = true
|
||||
for _, f := range srcs {
|
||||
if strings.HasSuffix(f, ".lua") {
|
||||
p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))
|
||||
} else if f == "info.json" {
|
||||
data, err := Asset(filepath.Join(plugdir, d, "info.json"))
|
||||
} else if strings.HasSuffix(f, ".json") {
|
||||
data, err := Asset(filepath.Join(plugdir, d, f))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p.Info, err = NewPluginInfo(data)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p.Info, _ = NewPluginInfo(data)
|
||||
p.Name = p.Info.Name
|
||||
}
|
||||
}
|
||||
if !isID(p.Name) {
|
||||
log.Println("Invalid plugin name", p.Name)
|
||||
if !isID(p.Name) || len(p.Srcs) <= 0 {
|
||||
log.Println(p.Name, "is not a plugin")
|
||||
continue
|
||||
}
|
||||
Plugins = append(Plugins, p)
|
||||
@@ -226,28 +267,40 @@ func PluginListRuntimeFiles(fileType RTFiletype) []string {
|
||||
}
|
||||
|
||||
// PluginAddRuntimeFile adds a file to the runtime files for a plugin
|
||||
func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) {
|
||||
fullpath := filepath.Join(ConfigDir, "plug", plugin, filePath)
|
||||
func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) error {
|
||||
pl := FindPlugin(plugin)
|
||||
if pl == nil {
|
||||
return errors.New("Plugin " + plugin + " does not exist")
|
||||
}
|
||||
pldir := pl.DirName
|
||||
fullpath := filepath.Join(ConfigDir, "plug", pldir, filePath)
|
||||
if _, err := os.Stat(fullpath); err == nil {
|
||||
AddRuntimeFile(filetype, realFile(fullpath))
|
||||
AddRealRuntimeFile(filetype, realFile(fullpath))
|
||||
} else {
|
||||
fullpath = path.Join("runtime", "plugins", plugin, filePath)
|
||||
fullpath = path.Join("runtime", "plugins", pldir, filePath)
|
||||
AddRuntimeFile(filetype, assetFile(fullpath))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PluginAddRuntimeFilesFromDirectory adds files from a directory to the runtime files for a plugin
|
||||
func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, directory, pattern string) {
|
||||
fullpath := filepath.Join(ConfigDir, "plug", plugin, directory)
|
||||
func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, directory, pattern string) error {
|
||||
pl := FindPlugin(plugin)
|
||||
if pl == nil {
|
||||
return errors.New("Plugin " + plugin + " does not exist")
|
||||
}
|
||||
pldir := pl.DirName
|
||||
fullpath := filepath.Join(ConfigDir, "plug", pldir, directory)
|
||||
if _, err := os.Stat(fullpath); err == nil {
|
||||
AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
|
||||
} else {
|
||||
fullpath = path.Join("runtime", "plugins", plugin, directory)
|
||||
fullpath = path.Join("runtime", "plugins", pldir, directory)
|
||||
AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PluginAddRuntimeFileFromMemory adds a file to the runtime files for a plugin from a given string
|
||||
func PluginAddRuntimeFileFromMemory(plugin string, filetype RTFiletype, filename, data string) {
|
||||
AddRuntimeFile(filetype, memoryFile{filename, []byte(data)})
|
||||
func PluginAddRuntimeFileFromMemory(filetype RTFiletype, filename, data string) {
|
||||
AddRealRuntimeFile(filetype, memoryFile{filename, []byte(data)})
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -3,15 +3,17 @@ package config
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/flynn/json5"
|
||||
"github.com/zyedidia/glob"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/json5"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"golang.org/x/text/encoding/htmlindex"
|
||||
)
|
||||
|
||||
@@ -26,9 +28,14 @@ var (
|
||||
|
||||
// This is the raw parsed json
|
||||
parsedSettings map[string]interface{}
|
||||
|
||||
// ModifiedSettings is a map of settings which should be written to disk
|
||||
// because they have been modified by the user in this session
|
||||
ModifiedSettings map[string]bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
ModifiedSettings = make(map[string]bool)
|
||||
parsedSettings = make(map[string]interface{})
|
||||
}
|
||||
|
||||
@@ -45,7 +52,7 @@ var optionValidators = map[string]optionValidator{
|
||||
}
|
||||
|
||||
func ReadSettings() error {
|
||||
filename := ConfigDir + "/settings.json"
|
||||
filename := filepath.Join(ConfigDir, "settings.json")
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
@@ -57,21 +64,50 @@ func ReadSettings() error {
|
||||
if err != nil {
|
||||
return errors.New("Error reading settings.json: " + err.Error())
|
||||
}
|
||||
|
||||
// check if autosave is a boolean and convert it to float if so
|
||||
if v, ok := parsedSettings["autosave"]; ok {
|
||||
s, ok := v.(bool)
|
||||
if ok {
|
||||
if s {
|
||||
parsedSettings["autosave"] = 8.0
|
||||
} else {
|
||||
parsedSettings["autosave"] = 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifySetting(option string, value reflect.Type, def reflect.Type) bool {
|
||||
var interfaceArr []interface{}
|
||||
switch option {
|
||||
case "pluginrepos", "pluginchannels":
|
||||
return value.AssignableTo(reflect.TypeOf(interfaceArr))
|
||||
default:
|
||||
return def.AssignableTo(value)
|
||||
}
|
||||
}
|
||||
|
||||
// InitGlobalSettings initializes the options map and sets all options to their default values
|
||||
// Must be called after ReadSettings
|
||||
func InitGlobalSettings() {
|
||||
func InitGlobalSettings() error {
|
||||
var err error
|
||||
GlobalSettings = DefaultGlobalSettings()
|
||||
|
||||
for k, v := range parsedSettings {
|
||||
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
||||
if _, ok := GlobalSettings[k]; ok && !verifySetting(k, reflect.TypeOf(v), reflect.TypeOf(GlobalSettings[k])) {
|
||||
err = errors.New(fmt.Sprintf("Global Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v), GlobalSettings[k], reflect.TypeOf(GlobalSettings[k])))
|
||||
continue
|
||||
}
|
||||
|
||||
GlobalSettings[k] = v
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// InitLocalSettings scans the json in settings.json and sets the options locally based
|
||||
@@ -84,6 +120,10 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
|
||||
if strings.HasPrefix(k, "ft:") {
|
||||
if settings["filetype"].(string) == k[3:] {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
|
||||
parseError = errors.New(fmt.Sprintf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1])))
|
||||
continue
|
||||
}
|
||||
settings[k1] = v1
|
||||
}
|
||||
}
|
||||
@@ -96,6 +136,10 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
|
||||
|
||||
if g.MatchString(path) {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
|
||||
parseError = errors.New(fmt.Sprintf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1])))
|
||||
continue
|
||||
}
|
||||
settings[k1] = v1
|
||||
}
|
||||
}
|
||||
@@ -109,8 +153,25 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
|
||||
func WriteSettings(filename string) error {
|
||||
var err error
|
||||
if _, e := os.Stat(ConfigDir); e == nil {
|
||||
defaults := DefaultGlobalSettings()
|
||||
|
||||
// remove any options froms parsedSettings that have since been marked as default
|
||||
for k, v := range parsedSettings {
|
||||
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
||||
cur, okcur := GlobalSettings[k]
|
||||
if def, ok := defaults[k]; ok && okcur && reflect.DeepEqual(cur, def) {
|
||||
delete(parsedSettings, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add any options to parsedSettings that have since been marked as non-default
|
||||
for k, v := range GlobalSettings {
|
||||
parsedSettings[k] = v
|
||||
if def, ok := defaults[k]; !ok || !reflect.DeepEqual(v, def) {
|
||||
if _, wr := ModifiedSettings[k]; wr {
|
||||
parsedSettings[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
txt, _ := json.MarshalIndent(parsedSettings, "", " ")
|
||||
@@ -119,12 +180,35 @@ func WriteSettings(filename string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// RegisterCommonOption creates a new option. This is meant to be called by plugins to add options.
|
||||
func RegisterCommonOption(name string, defaultvalue interface{}) error {
|
||||
// OverwriteSettings writes the current settings to settings.json and
|
||||
// resets any user configuration of local settings present in settings.json
|
||||
func OverwriteSettings(filename string) error {
|
||||
settings := make(map[string]interface{})
|
||||
|
||||
var err error
|
||||
if _, e := os.Stat(ConfigDir); e == nil {
|
||||
defaults := DefaultGlobalSettings()
|
||||
for k, v := range GlobalSettings {
|
||||
if def, ok := defaults[k]; !ok || !reflect.DeepEqual(v, def) {
|
||||
if _, wr := ModifiedSettings[k]; wr {
|
||||
settings[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
txt, _ := json.MarshalIndent(settings, "", " ")
|
||||
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// RegisterCommonOptionPlug creates a new option (called pl.name). This is meant to be called by plugins to add options.
|
||||
func RegisterCommonOptionPlug(pl string, name string, defaultvalue interface{}) error {
|
||||
name = pl + "." + name
|
||||
if v, ok := GlobalSettings[name]; !ok {
|
||||
defaultCommonSettings[name] = defaultvalue
|
||||
GlobalSettings[name] = defaultvalue
|
||||
err := WriteSettings(ConfigDir + "/settings.json")
|
||||
err := WriteSettings(filepath.Join(ConfigDir, "settings.json"))
|
||||
if err != nil {
|
||||
return errors.New("Error writing settings.json file: " + err.Error())
|
||||
}
|
||||
@@ -134,16 +218,22 @@ func RegisterCommonOption(name string, defaultvalue interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterGlobalOptionPlug creates a new global-only option (named pl.name)
|
||||
func RegisterGlobalOptionPlug(pl string, name string, defaultvalue interface{}) error {
|
||||
return RegisterGlobalOption(pl+"."+name, defaultvalue)
|
||||
}
|
||||
|
||||
// RegisterGlobalOption creates a new global-only option
|
||||
func RegisterGlobalOption(name string, defaultvalue interface{}) error {
|
||||
if v, ok := GlobalSettings[name]; !ok {
|
||||
defaultGlobalSettings[name] = defaultvalue
|
||||
DefaultGlobalOnlySettings[name] = defaultvalue
|
||||
GlobalSettings[name] = defaultvalue
|
||||
err := WriteSettings(ConfigDir + "/settings.json")
|
||||
err := WriteSettings(filepath.Join(ConfigDir, "settings.json"))
|
||||
if err != nil {
|
||||
return errors.New("Error writing settings.json file: " + err.Error())
|
||||
}
|
||||
} else {
|
||||
defaultGlobalSettings[name] = v
|
||||
DefaultGlobalOnlySettings[name] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -155,13 +245,15 @@ func GetGlobalOption(name string) interface{} {
|
||||
|
||||
var defaultCommonSettings = map[string]interface{}{
|
||||
"autoindent": true,
|
||||
"autosu": false,
|
||||
"backup": true,
|
||||
"basename": false,
|
||||
"colorcolumn": float64(0),
|
||||
"cursorline": true,
|
||||
"diffgutter": false,
|
||||
"encoding": "utf-8",
|
||||
"eofnewline": false,
|
||||
"fastdirty": true,
|
||||
"eofnewline": true,
|
||||
"fastdirty": false,
|
||||
"fileformat": "unix",
|
||||
"filetype": "unknown",
|
||||
"ignorecase": false,
|
||||
@@ -172,6 +264,7 @@ var defaultCommonSettings = map[string]interface{}{
|
||||
"readonly": false,
|
||||
"rmtrailingws": false,
|
||||
"ruler": true,
|
||||
"relativeruler": false,
|
||||
"savecursor": false,
|
||||
"saveundo": false,
|
||||
"scrollbar": false,
|
||||
@@ -181,8 +274,8 @@ var defaultCommonSettings = map[string]interface{}{
|
||||
"softwrap": false,
|
||||
"splitbottom": true,
|
||||
"splitright": true,
|
||||
"statusformatl": "$(filename) $(modified)($(line),$(col)) | ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
|
||||
"statusformatr": "$(bind:ToggleKeyMenu): show bindings, $(bind:ToggleHelp): toggle help",
|
||||
"statusformatl": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
|
||||
"statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
|
||||
"statusline": true,
|
||||
"syntax": true,
|
||||
"tabmovement": false,
|
||||
@@ -212,15 +305,29 @@ func DefaultCommonSettings() map[string]interface{} {
|
||||
return commonsettings
|
||||
}
|
||||
|
||||
var defaultGlobalSettings = map[string]interface{}{
|
||||
"autosave": float64(0),
|
||||
"colorscheme": "default",
|
||||
"infobar": true,
|
||||
"keymenu": false,
|
||||
"mouse": true,
|
||||
"savehistory": true,
|
||||
"sucmd": "sudo",
|
||||
"termtitle": false,
|
||||
// a list of settings that should only be globally modified and their
|
||||
// default values
|
||||
var DefaultGlobalOnlySettings = map[string]interface{}{
|
||||
"autosave": float64(0),
|
||||
"colorscheme": "default",
|
||||
"divchars": "|-",
|
||||
"divreverse": true,
|
||||
"infobar": true,
|
||||
"keymenu": false,
|
||||
"mouse": true,
|
||||
"parsecursor": false,
|
||||
"paste": false,
|
||||
"savehistory": true,
|
||||
"sucmd": "sudo",
|
||||
"pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
|
||||
"pluginrepos": []string{},
|
||||
"xterm": false,
|
||||
}
|
||||
|
||||
// a list of settings that should never be globally modified
|
||||
var LocalSettings = []string{
|
||||
"filetype",
|
||||
"readonly",
|
||||
}
|
||||
|
||||
// DefaultGlobalSettings returns the default global settings for micro
|
||||
@@ -230,7 +337,7 @@ func DefaultGlobalSettings() map[string]interface{} {
|
||||
for k, v := range defaultCommonSettings {
|
||||
globalsettings[k] = v
|
||||
}
|
||||
for k, v := range defaultGlobalSettings {
|
||||
for k, v := range DefaultGlobalOnlySettings {
|
||||
globalsettings[k] = v
|
||||
}
|
||||
return globalsettings
|
||||
@@ -243,12 +350,13 @@ func DefaultAllSettings() map[string]interface{} {
|
||||
for k, v := range defaultCommonSettings {
|
||||
allsettings[k] = v
|
||||
}
|
||||
for k, v := range defaultGlobalSettings {
|
||||
for k, v := range DefaultGlobalOnlySettings {
|
||||
allsettings[k] = v
|
||||
}
|
||||
return allsettings
|
||||
}
|
||||
|
||||
// GetNativeValue parses and validates a value for a given option
|
||||
func GetNativeValue(option string, realValue interface{}, value string) (interface{}, error) {
|
||||
var native interface{}
|
||||
kind := reflect.TypeOf(realValue).Kind()
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strconv"
|
||||
"unicode/utf8"
|
||||
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -45,12 +43,12 @@ func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
|
||||
w.Buf = b
|
||||
}
|
||||
|
||||
func (v *View) GetView() *View {
|
||||
return v
|
||||
func (w *BufWindow) GetView() *View {
|
||||
return w.View
|
||||
}
|
||||
|
||||
func (v *View) SetView(view *View) {
|
||||
v = view
|
||||
func (w *BufWindow) SetView(view *View) {
|
||||
w.View = view
|
||||
}
|
||||
|
||||
func (w *BufWindow) Resize(width, height int) {
|
||||
@@ -74,9 +72,9 @@ func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style)
|
||||
curStyle := config.DefStyle
|
||||
var s *tcell.Style
|
||||
for len(b) > 0 {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
r, _, size := util.DecodeCharacter(b)
|
||||
|
||||
curStyle, found := w.getStyle(curStyle, bloc, r)
|
||||
curStyle, found := w.getStyle(curStyle, bloc)
|
||||
if found {
|
||||
s = &curStyle
|
||||
}
|
||||
@@ -103,7 +101,7 @@ func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style)
|
||||
func (w *BufWindow) Clear() {
|
||||
for y := 0; y < w.Height; y++ {
|
||||
for x := 0; x < w.Width; x++ {
|
||||
screen.Screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
|
||||
screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,9 +119,8 @@ func (w *BufWindow) Bottomline() int {
|
||||
return h
|
||||
}
|
||||
|
||||
l := w.LocFromVisual(buffer.Loc{0, w.Height})
|
||||
l := w.LocFromVisual(buffer.Loc{0, w.Y + w.Height})
|
||||
|
||||
log.Println("Bottom line:", l.Y)
|
||||
return l.Y
|
||||
}
|
||||
|
||||
@@ -134,14 +131,10 @@ func (w *BufWindow) Relocate() bool {
|
||||
b := w.Buf
|
||||
// how many buffer lines are in the view
|
||||
height := w.Bottomline() + 1 - w.StartLine
|
||||
log.Printf("Height: %d, w.Height: %d\n", height, w.Height)
|
||||
h := w.Height
|
||||
if w.drawStatus {
|
||||
h--
|
||||
}
|
||||
if b.LinesNum() <= h {
|
||||
height = w.Height
|
||||
}
|
||||
ret := false
|
||||
activeC := w.Buf.GetActiveCursor()
|
||||
cy := activeC.Y
|
||||
@@ -191,6 +184,11 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
|
||||
bufHeight--
|
||||
}
|
||||
|
||||
bufWidth := w.Width
|
||||
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
|
||||
bufWidth--
|
||||
}
|
||||
|
||||
// We need to know the string length of the largest line number
|
||||
// so we can pad appropriately when displaying line numbers
|
||||
maxLineNumLength := len(strconv.Itoa(b.LinesNum()))
|
||||
@@ -210,6 +208,9 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
|
||||
if hasMessage {
|
||||
vloc.X += 2
|
||||
}
|
||||
if b.Settings["diffgutter"].(bool) {
|
||||
vloc.X++
|
||||
}
|
||||
if b.Settings["ruler"].(bool) {
|
||||
vloc.X += maxLineNumLength + 1
|
||||
}
|
||||
@@ -235,7 +236,7 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
|
||||
return bloc
|
||||
}
|
||||
|
||||
r, size := utf8.DecodeRune(line)
|
||||
r, _, size := util.DecodeCharacter(line)
|
||||
draw()
|
||||
width := 0
|
||||
|
||||
@@ -262,7 +263,7 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
|
||||
totalwidth += width
|
||||
|
||||
// If we reach the end of the window then we either stop or we wrap for softwrap
|
||||
if vloc.X >= w.Width {
|
||||
if vloc.X >= bufWidth {
|
||||
if !softwrap {
|
||||
break
|
||||
} else {
|
||||
@@ -270,9 +271,7 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
|
||||
if vloc.Y >= bufHeight {
|
||||
break
|
||||
}
|
||||
vloc.X = 0
|
||||
// This will draw an empty line number because the current line is wrapped
|
||||
vloc.X += maxLineNumLength + 1
|
||||
vloc.X = w.gutterOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,38 +300,73 @@ func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) {
|
||||
break
|
||||
}
|
||||
}
|
||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
|
||||
vloc.X++
|
||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
|
||||
vloc.X++
|
||||
}
|
||||
|
||||
func (w *BufWindow) drawDiffGutter(backgroundStyle tcell.Style, softwrapped bool, vloc *buffer.Loc, bloc *buffer.Loc) {
|
||||
symbol := ' '
|
||||
styleName := ""
|
||||
|
||||
switch w.Buf.DiffStatus(bloc.Y) {
|
||||
case buffer.DSAdded:
|
||||
symbol = '\u258C' // Left half block
|
||||
styleName = "diff-added"
|
||||
case buffer.DSModified:
|
||||
symbol = '\u258C' // Left half block
|
||||
styleName = "diff-modified"
|
||||
case buffer.DSDeletedAbove:
|
||||
if !softwrapped {
|
||||
symbol = '\u2594' // Upper one eighth block
|
||||
styleName = "diff-deleted"
|
||||
}
|
||||
}
|
||||
|
||||
style := backgroundStyle
|
||||
if s, ok := config.Colorscheme[styleName]; ok {
|
||||
foreground, _, _ := s.Decompose()
|
||||
style = style.Foreground(foreground)
|
||||
}
|
||||
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, symbol, nil, style)
|
||||
vloc.X++
|
||||
}
|
||||
|
||||
func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *buffer.Loc, bloc *buffer.Loc) {
|
||||
lineNum := strconv.Itoa(bloc.Y + 1)
|
||||
cursorLine := w.Buf.GetActiveCursor().Loc.Y
|
||||
var lineInt int
|
||||
if w.Buf.Settings["relativeruler"] == false || cursorLine == bloc.Y {
|
||||
lineInt = bloc.Y + 1
|
||||
} else {
|
||||
lineInt = bloc.Y - cursorLine
|
||||
}
|
||||
lineNum := strconv.Itoa(util.Abs(lineInt))
|
||||
|
||||
// Write the spaces before the line number if necessary
|
||||
for i := 0; i < maxLineNumLength-len(lineNum); i++ {
|
||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
||||
vloc.X++
|
||||
}
|
||||
// Write the actual line number
|
||||
for _, ch := range lineNum {
|
||||
if softwrapped {
|
||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
||||
} else {
|
||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle)
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle)
|
||||
}
|
||||
vloc.X++
|
||||
}
|
||||
|
||||
// Write the extra space
|
||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
||||
vloc.X++
|
||||
}
|
||||
|
||||
// getStyle returns the highlight style for the given character position
|
||||
// If there is no change to the current highlight style it just returns that
|
||||
func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) (tcell.Style, bool) {
|
||||
func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc) (tcell.Style, bool) {
|
||||
if group, ok := w.Buf.Match(bloc.Y)[bloc.X]; ok {
|
||||
s := config.GetColor(group.String())
|
||||
return s, true
|
||||
@@ -343,10 +377,9 @@ func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) (tcell.
|
||||
func (w *BufWindow) showCursor(x, y int, main bool) {
|
||||
if w.active {
|
||||
if main {
|
||||
screen.Screen.ShowCursor(x, y)
|
||||
screen.ShowCursor(x, y)
|
||||
} else {
|
||||
r, _, _, _ := screen.Screen.GetContent(x, y)
|
||||
screen.Screen.SetContent(x, y, r, nil, config.DefStyle.Reverse(true))
|
||||
screen.ShowFakeCursorMulti(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -355,6 +388,10 @@ func (w *BufWindow) showCursor(x, y int, main bool) {
|
||||
func (w *BufWindow) displayBuffer() {
|
||||
b := w.Buf
|
||||
|
||||
if w.Height <= 0 || w.Width <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
hasMessage := len(b.Messages) > 0
|
||||
bufHeight := w.Height
|
||||
if w.drawStatus {
|
||||
@@ -366,18 +403,22 @@ func (w *BufWindow) displayBuffer() {
|
||||
bufWidth--
|
||||
}
|
||||
|
||||
if b.Settings["syntax"].(bool) && b.SyntaxDef != nil {
|
||||
for _, c := range b.GetCursors() {
|
||||
// rehighlight starting from where the cursor is
|
||||
start := c.Y
|
||||
if start > 0 && b.Rehighlight(start-1) {
|
||||
b.Highlighter.ReHighlightLine(b, start-1)
|
||||
b.SetRehighlight(start-1, false)
|
||||
}
|
||||
|
||||
b.Highlighter.ReHighlightStates(b, start)
|
||||
b.Highlighter.HighlightMatches(b, w.StartLine, w.StartLine+bufHeight)
|
||||
if b.ModifiedThisFrame {
|
||||
if b.Settings["diffgutter"].(bool) {
|
||||
b.UpdateDiff(func(synchronous bool) {
|
||||
// If the diff was updated asynchronously, the outer call to
|
||||
// displayBuffer might already be completed and we need to
|
||||
// schedule a redraw in order to display the new diff.
|
||||
// Note that this cannot lead to an infinite recursion
|
||||
// because the modifications were cleared above so there won't
|
||||
// be another call to UpdateDiff when displayBuffer is called
|
||||
// during the redraw.
|
||||
if !synchronous {
|
||||
screen.Redraw()
|
||||
}
|
||||
})
|
||||
}
|
||||
b.ModifiedThisFrame = false
|
||||
}
|
||||
|
||||
var matchingBraces []buffer.Loc
|
||||
@@ -394,12 +435,14 @@ func (w *BufWindow) displayBuffer() {
|
||||
r := c.RuneUnder(curX)
|
||||
rl := c.RuneUnder(curX - 1)
|
||||
if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
|
||||
mb, left := b.FindMatchingBrace(bp, curLoc)
|
||||
matchingBraces = append(matchingBraces, mb)
|
||||
if !left {
|
||||
matchingBraces = append(matchingBraces, curLoc)
|
||||
} else {
|
||||
matchingBraces = append(matchingBraces, curLoc.Move(-1, b))
|
||||
mb, left, found := b.FindMatchingBrace(bp, curLoc)
|
||||
if found {
|
||||
matchingBraces = append(matchingBraces, mb)
|
||||
if !left {
|
||||
matchingBraces = append(matchingBraces, curLoc)
|
||||
} else {
|
||||
matchingBraces = append(matchingBraces, curLoc.Move(-1, b))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -412,7 +455,11 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
curNumStyle := config.DefStyle
|
||||
if style, ok := config.Colorscheme["current-line-number"]; ok {
|
||||
curNumStyle = style
|
||||
if !b.Settings["cursorline"].(bool) {
|
||||
curNumStyle = lineNumStyle
|
||||
} else {
|
||||
curNumStyle = style
|
||||
}
|
||||
}
|
||||
|
||||
// We need to know the string length of the largest line number
|
||||
@@ -436,18 +483,28 @@ func (w *BufWindow) displayBuffer() {
|
||||
for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
|
||||
vloc.X = 0
|
||||
|
||||
currentLine := false
|
||||
for _, c := range cursors {
|
||||
if bloc.Y == c.Y && w.active {
|
||||
currentLine = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
s := lineNumStyle
|
||||
if currentLine {
|
||||
s = curNumStyle
|
||||
}
|
||||
|
||||
if hasMessage {
|
||||
w.drawGutter(&vloc, &bloc)
|
||||
}
|
||||
|
||||
if b.Settings["diffgutter"].(bool) {
|
||||
w.drawDiffGutter(s, false, &vloc, &bloc)
|
||||
}
|
||||
|
||||
if b.Settings["ruler"].(bool) {
|
||||
s := lineNumStyle
|
||||
for _, c := range cursors {
|
||||
if bloc.Y == c.Y && w.active {
|
||||
s = curNumStyle
|
||||
break
|
||||
}
|
||||
}
|
||||
w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc)
|
||||
}
|
||||
|
||||
@@ -459,8 +516,15 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
bloc.X = bslice
|
||||
|
||||
draw := func(r rune, style tcell.Style, showcursor bool) {
|
||||
draw := func(r rune, combc []rune, style tcell.Style, showcursor bool) {
|
||||
if nColsBeforeStart <= 0 {
|
||||
_, origBg, _ := style.Decompose()
|
||||
_, defBg, _ := config.DefStyle.Decompose()
|
||||
|
||||
// syntax highlighting with non-default background takes precedence
|
||||
// over cursor-line and color-column
|
||||
dontOverrideBackground := origBg != defBg
|
||||
|
||||
for _, c := range cursors {
|
||||
if c.HasSelection() &&
|
||||
(bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
|
||||
@@ -473,7 +537,7 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
}
|
||||
|
||||
if b.Settings["cursorline"].(bool) && w.active &&
|
||||
if b.Settings["cursorline"].(bool) && w.active && !dontOverrideBackground &&
|
||||
!c.HasSelection() && c.Y == bloc.Y {
|
||||
if s, ok := config.Colorscheme["cursor-line"]; ok {
|
||||
fg, _, _ := s.Decompose()
|
||||
@@ -505,7 +569,7 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
|
||||
if s, ok := config.Colorscheme["color-column"]; ok {
|
||||
if colorcolumn != 0 && vloc.X-w.gutterOffset == colorcolumn {
|
||||
if colorcolumn != 0 && vloc.X-w.gutterOffset+w.StartCol == colorcolumn && !dontOverrideBackground {
|
||||
fg, _, _ := s.Decompose()
|
||||
style = style.Background(fg)
|
||||
}
|
||||
@@ -517,7 +581,7 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
}
|
||||
|
||||
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style)
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, combc, style)
|
||||
|
||||
if showcursor {
|
||||
for _, c := range cursors {
|
||||
@@ -533,10 +597,11 @@ func (w *BufWindow) displayBuffer() {
|
||||
|
||||
totalwidth := w.StartCol - nColsBeforeStart
|
||||
for len(line) > 0 {
|
||||
r, size := utf8.DecodeRune(line)
|
||||
curStyle, _ = w.getStyle(curStyle, bloc, r)
|
||||
r, combc, size := util.DecodeCharacter(line)
|
||||
|
||||
draw(r, curStyle, true)
|
||||
curStyle, _ = w.getStyle(curStyle, bloc)
|
||||
|
||||
draw(r, combc, curStyle, true)
|
||||
|
||||
width := 0
|
||||
|
||||
@@ -553,7 +618,7 @@ func (w *BufWindow) displayBuffer() {
|
||||
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
|
||||
if width > 1 {
|
||||
for i := 1; i < width; i++ {
|
||||
draw(char, curStyle, false)
|
||||
draw(char, nil, curStyle, false)
|
||||
}
|
||||
}
|
||||
bloc.X++
|
||||
@@ -571,8 +636,17 @@ func (w *BufWindow) displayBuffer() {
|
||||
break
|
||||
}
|
||||
vloc.X = 0
|
||||
if hasMessage {
|
||||
w.drawGutter(&vloc, &bloc)
|
||||
}
|
||||
if b.Settings["diffgutter"].(bool) {
|
||||
w.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)
|
||||
}
|
||||
|
||||
// This will draw an empty line number because the current line is wrapped
|
||||
w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc)
|
||||
if b.Settings["ruler"].(bool) {
|
||||
w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -590,22 +664,19 @@ func (w *BufWindow) displayBuffer() {
|
||||
for i := vloc.X; i < bufWidth; i++ {
|
||||
curStyle := style
|
||||
if s, ok := config.Colorscheme["color-column"]; ok {
|
||||
if colorcolumn != 0 && i-w.gutterOffset == colorcolumn {
|
||||
if colorcolumn != 0 && i-w.gutterOffset+w.StartCol == colorcolumn {
|
||||
fg, _, _ := s.Decompose()
|
||||
curStyle = style.Background(fg)
|
||||
}
|
||||
}
|
||||
screen.Screen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, curStyle)
|
||||
screen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, curStyle)
|
||||
}
|
||||
|
||||
for _, c := range cursors {
|
||||
if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
|
||||
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
|
||||
}
|
||||
if vloc.X != bufWidth {
|
||||
// Display newline within a selection
|
||||
draw(' ', nil, config.DefStyle, true)
|
||||
}
|
||||
|
||||
draw(' ', curStyle, false)
|
||||
|
||||
bloc.X = w.StartCol
|
||||
bloc.Y++
|
||||
if bloc.Y >= b.LinesNum() {
|
||||
@@ -626,8 +697,27 @@ func (w *BufWindow) displayStatusLine() {
|
||||
w.sline.Display()
|
||||
} else if w.Y+w.Height != infoY {
|
||||
w.drawStatus = true
|
||||
|
||||
divchars := config.GetGlobalOption("divchars").(string)
|
||||
if util.CharacterCountInString(divchars) != 2 {
|
||||
divchars = "|-"
|
||||
}
|
||||
|
||||
_, _, size := util.DecodeCharacterInString(divchars)
|
||||
divchar, combc, _ := util.DecodeCharacterInString(divchars[size:])
|
||||
|
||||
dividerStyle := config.DefStyle
|
||||
if style, ok := config.Colorscheme["divider"]; ok {
|
||||
dividerStyle = style
|
||||
}
|
||||
|
||||
divreverse := config.GetGlobalOption("divreverse").(bool)
|
||||
if divreverse {
|
||||
dividerStyle = dividerStyle.Reverse(true)
|
||||
}
|
||||
|
||||
for x := w.X; x < w.X+w.Width; x++ {
|
||||
screen.Screen.SetContent(x, w.Y+w.Height-1, '-', nil, config.DefStyle.Reverse(true))
|
||||
screen.SetContent(x, w.Y+w.Height-1, divchar, combc, dividerStyle)
|
||||
}
|
||||
} else {
|
||||
w.drawStatus = false
|
||||
@@ -647,7 +737,7 @@ func (w *BufWindow) displayScrollBar() {
|
||||
}
|
||||
barstart := w.Y + int(float64(w.StartLine)/float64(w.Buf.LinesNum())*float64(w.Height))
|
||||
for y := barstart; y < util.Min(barstart+barsize, w.Y+bufHeight); y++ {
|
||||
screen.Screen.SetContent(scrollX, y, '|', nil, config.DefStyle.Reverse(true))
|
||||
screen.SetContent(scrollX, y, '|', nil, config.DefStyle.Reverse(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/info"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/info"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
type InfoWindow struct {
|
||||
*info.InfoBuf
|
||||
*View
|
||||
|
||||
hscroll int
|
||||
}
|
||||
|
||||
func (i *InfoWindow) errStyle() tcell.Style {
|
||||
@@ -68,13 +68,13 @@ func (i *InfoWindow) IsActive() bool { return true }
|
||||
func (i *InfoWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {
|
||||
c := i.Buffer.GetActiveCursor()
|
||||
l := i.Buffer.LineBytes(0)
|
||||
n := utf8.RuneCountInString(i.Msg)
|
||||
n := util.CharacterCountInString(i.Msg)
|
||||
return buffer.Loc{c.GetCharPosInLine(l, vloc.X-n), 0}
|
||||
}
|
||||
|
||||
func (i *InfoWindow) Clear() {
|
||||
for x := 0; x < i.Width; x++ {
|
||||
screen.Screen.SetContent(x, i.Y, ' ', nil, i.defStyle())
|
||||
screen.SetContent(x, i.Y, ' ', nil, i.defStyle())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,13 +84,13 @@ func (i *InfoWindow) displayBuffer() {
|
||||
activeC := b.GetActiveCursor()
|
||||
|
||||
blocX := 0
|
||||
vlocX := utf8.RuneCountInString(i.Msg)
|
||||
vlocX := util.CharacterCountInString(i.Msg)
|
||||
|
||||
tabsize := 4
|
||||
line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, blocX, tabsize)
|
||||
blocX = bslice
|
||||
|
||||
draw := func(r rune, style tcell.Style) {
|
||||
draw := func(r rune, combc []rune, style tcell.Style) {
|
||||
if nColsBeforeStart <= 0 {
|
||||
bloc := buffer.Loc{X: blocX, Y: 0}
|
||||
if activeC.HasSelection() &&
|
||||
@@ -110,8 +110,9 @@ func (i *InfoWindow) displayBuffer() {
|
||||
c := r
|
||||
if j > 0 {
|
||||
c = ' '
|
||||
combc = nil
|
||||
}
|
||||
screen.Screen.SetContent(vlocX, i.Y, c, nil, style)
|
||||
screen.SetContent(vlocX, i.Y, c, combc, style)
|
||||
}
|
||||
vlocX++
|
||||
}
|
||||
@@ -120,13 +121,11 @@ func (i *InfoWindow) displayBuffer() {
|
||||
|
||||
totalwidth := blocX - nColsBeforeStart
|
||||
for len(line) > 0 {
|
||||
if activeC.X == blocX {
|
||||
screen.Screen.ShowCursor(vlocX, i.Y)
|
||||
}
|
||||
curVX := vlocX
|
||||
curBX := blocX
|
||||
r, combc, size := util.DecodeCharacter(line)
|
||||
|
||||
r, size := utf8.DecodeRune(line)
|
||||
|
||||
draw(r, i.defStyle())
|
||||
draw(r, combc, i.defStyle())
|
||||
|
||||
width := 0
|
||||
|
||||
@@ -146,16 +145,19 @@ func (i *InfoWindow) displayBuffer() {
|
||||
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
|
||||
if width > 1 {
|
||||
for j := 1; j < width; j++ {
|
||||
draw(char, i.defStyle())
|
||||
draw(char, nil, i.defStyle())
|
||||
}
|
||||
}
|
||||
if activeC.X == curBX {
|
||||
screen.ShowCursor(curVX, i.Y)
|
||||
}
|
||||
totalwidth += width
|
||||
if vlocX >= i.Width {
|
||||
break
|
||||
}
|
||||
}
|
||||
if activeC.X == blocX {
|
||||
screen.Screen.ShowCursor(vlocX, i.Y)
|
||||
screen.ShowCursor(vlocX, i.Y)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,21 +169,52 @@ func (i *InfoWindow) displayKeyMenu() {
|
||||
for y := 0; y < len(keydisplay); y++ {
|
||||
for x := 0; x < i.Width; x++ {
|
||||
if x < len(keydisplay[y]) {
|
||||
screen.Screen.SetContent(x, i.Y-len(keydisplay)+y, rune(keydisplay[y][x]), nil, i.defStyle())
|
||||
screen.SetContent(x, i.Y-len(keydisplay)+y, rune(keydisplay[y][x]), nil, i.defStyle())
|
||||
} else {
|
||||
screen.Screen.SetContent(x, i.Y-len(keydisplay)+y, ' ', nil, i.defStyle())
|
||||
screen.SetContent(x, i.Y-len(keydisplay)+y, ' ', nil, i.defStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *InfoWindow) Display() {
|
||||
func (i *InfoWindow) totalSize() int {
|
||||
sum := 0
|
||||
for _, n := range i.Suggestions {
|
||||
sum += runewidth.StringWidth(n) + 1
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func (i *InfoWindow) scrollToSuggestion() {
|
||||
x := 0
|
||||
if config.GetGlobalOption("keymenu").(bool) {
|
||||
i.displayKeyMenu()
|
||||
s := i.totalSize()
|
||||
|
||||
for j, n := range i.Suggestions {
|
||||
c := util.CharacterCountInString(n)
|
||||
if j == i.CurSuggestion {
|
||||
if x+c >= i.hscroll+i.Width {
|
||||
i.hscroll = util.Clamp(x+c+1-i.Width, 0, s-i.Width)
|
||||
} else if x < i.hscroll {
|
||||
i.hscroll = util.Clamp(x-1, 0, s-i.Width)
|
||||
}
|
||||
break
|
||||
}
|
||||
x += c + 1
|
||||
}
|
||||
|
||||
if s-i.Width <= 0 {
|
||||
i.hscroll = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (i *InfoWindow) Display() {
|
||||
if i.HasPrompt || config.GlobalSettings["infobar"].(bool) {
|
||||
i.Clear()
|
||||
x := 0
|
||||
if config.GetGlobalOption("keymenu").(bool) {
|
||||
i.displayKeyMenu()
|
||||
}
|
||||
|
||||
if !i.HasPrompt && !i.HasMessage && !i.HasError {
|
||||
return
|
||||
}
|
||||
@@ -194,7 +227,7 @@ func (i *InfoWindow) Display() {
|
||||
|
||||
display := i.Msg
|
||||
for _, c := range display {
|
||||
screen.Screen.SetContent(x, i.Y, c, nil, style)
|
||||
screen.SetContent(x, i.Y, c, nil, style)
|
||||
x += runewidth.RuneWidth(c)
|
||||
}
|
||||
|
||||
@@ -204,6 +237,11 @@ func (i *InfoWindow) Display() {
|
||||
}
|
||||
|
||||
if i.HasSuggestions && len(i.Suggestions) > 1 {
|
||||
i.scrollToSuggestion()
|
||||
|
||||
x := -i.hscroll
|
||||
done := false
|
||||
|
||||
statusLineStyle := config.DefStyle.Reverse(true)
|
||||
if style, ok := config.Colorscheme["statusline"]; ok {
|
||||
statusLineStyle = style
|
||||
@@ -212,29 +250,43 @@ func (i *InfoWindow) Display() {
|
||||
if config.GetGlobalOption("keymenu").(bool) {
|
||||
keymenuOffset = len(keydisplay)
|
||||
}
|
||||
x := 0
|
||||
|
||||
draw := func(r rune, s tcell.Style) {
|
||||
y := i.Y - keymenuOffset - 1
|
||||
rw := runewidth.RuneWidth(r)
|
||||
for j := 0; j < rw; j++ {
|
||||
c := r
|
||||
if j > 0 {
|
||||
c = ' '
|
||||
}
|
||||
|
||||
if x == i.Width-1 && !done {
|
||||
screen.SetContent(i.Width-1, y, '>', nil, s)
|
||||
x++
|
||||
break
|
||||
} else if x == 0 && i.hscroll > 0 {
|
||||
screen.SetContent(0, y, '<', nil, s)
|
||||
} else if x >= 0 && x < i.Width {
|
||||
screen.SetContent(x, y, c, nil, s)
|
||||
}
|
||||
x++
|
||||
}
|
||||
}
|
||||
|
||||
for j, s := range i.Suggestions {
|
||||
style := statusLineStyle
|
||||
if i.CurSuggestion == j {
|
||||
style = style.Reverse(true)
|
||||
}
|
||||
for _, r := range s {
|
||||
screen.Screen.SetContent(x, i.Y-keymenuOffset-1, r, nil, style)
|
||||
x++
|
||||
if x >= i.Width {
|
||||
return
|
||||
}
|
||||
}
|
||||
screen.Screen.SetContent(x, i.Y-keymenuOffset-1, ' ', nil, statusLineStyle)
|
||||
x++
|
||||
if x >= i.Width {
|
||||
return
|
||||
draw(r, style)
|
||||
// screen.SetContent(x, i.Y-keymenuOffset-1, r, nil, style)
|
||||
}
|
||||
draw(' ', statusLineStyle)
|
||||
}
|
||||
|
||||
for x < i.Width {
|
||||
screen.Screen.SetContent(x, i.Y-keymenuOffset-1, ' ', nil, statusLineStyle)
|
||||
x++
|
||||
draw(' ', statusLineStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,21 +3,19 @@ package display
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// StatusLine represents the information line at the bottom
|
||||
@@ -32,9 +30,6 @@ type StatusLine struct {
|
||||
|
||||
var statusInfo = map[string]func(*buffer.Buffer) string{
|
||||
"filename": func(b *buffer.Buffer) string {
|
||||
if b.Settings["basename"].(bool) {
|
||||
return path.Base(b.GetName())
|
||||
}
|
||||
return b.GetName()
|
||||
},
|
||||
"line": func(b *buffer.Buffer) string {
|
||||
@@ -47,11 +42,14 @@ var statusInfo = map[string]func(*buffer.Buffer) string{
|
||||
if b.Modified() {
|
||||
return "+ "
|
||||
}
|
||||
if b.Type.Readonly {
|
||||
return "[ro] "
|
||||
}
|
||||
return ""
|
||||
},
|
||||
}
|
||||
|
||||
func SetStatusInfoFnLua(s string, fn string) {
|
||||
func SetStatusInfoFnLua(fn string) {
|
||||
luaFn := strings.Split(fn, ".")
|
||||
if len(luaFn) <= 1 {
|
||||
return
|
||||
@@ -61,7 +59,7 @@ func SetStatusInfoFnLua(s string, fn string) {
|
||||
if pl == nil {
|
||||
return
|
||||
}
|
||||
statusInfo[s] = func(b *buffer.Buffer) string {
|
||||
statusInfo[fn] = func(b *buffer.Buffer) string {
|
||||
if pl == nil || !pl.IsEnabled() {
|
||||
return ""
|
||||
}
|
||||
@@ -118,13 +116,13 @@ func (s *StatusLine) Display() {
|
||||
style = style.Reverse(true)
|
||||
}
|
||||
for _, r := range sug {
|
||||
screen.Screen.SetContent(x, y-keymenuOffset, r, nil, style)
|
||||
screen.SetContent(x, y-keymenuOffset, r, nil, style)
|
||||
x++
|
||||
if x >= s.win.Width {
|
||||
return
|
||||
}
|
||||
}
|
||||
screen.Screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
|
||||
screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
|
||||
x++
|
||||
if x >= s.win.Width {
|
||||
return
|
||||
@@ -132,7 +130,7 @@ func (s *StatusLine) Display() {
|
||||
}
|
||||
|
||||
for x < s.win.Width {
|
||||
screen.Screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
|
||||
screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
|
||||
x++
|
||||
}
|
||||
return
|
||||
@@ -169,37 +167,39 @@ func (s *StatusLine) Display() {
|
||||
statusLineStyle = style
|
||||
}
|
||||
|
||||
leftLen := util.StringWidth(leftText, utf8.RuneCount(leftText), 1)
|
||||
rightLen := util.StringWidth(rightText, utf8.RuneCount(rightText), 1)
|
||||
leftLen := util.StringWidth(leftText, util.CharacterCount(leftText), 1)
|
||||
rightLen := util.StringWidth(rightText, util.CharacterCount(rightText), 1)
|
||||
|
||||
winX := s.win.X
|
||||
for x := 0; x < s.win.Width; x++ {
|
||||
if x < leftLen {
|
||||
r, size := utf8.DecodeRune(leftText)
|
||||
r, combc, size := util.DecodeCharacter(leftText)
|
||||
leftText = leftText[size:]
|
||||
rw := runewidth.RuneWidth(r)
|
||||
for j := 0; j < rw; j++ {
|
||||
c := r
|
||||
if j > 0 {
|
||||
c = ' '
|
||||
combc = nil
|
||||
x++
|
||||
}
|
||||
screen.Screen.SetContent(winX+x, y, c, nil, statusLineStyle)
|
||||
screen.SetContent(winX+x, y, c, combc, statusLineStyle)
|
||||
}
|
||||
} else if x >= s.win.Width-rightLen && x < rightLen+s.win.Width-rightLen {
|
||||
r, size := utf8.DecodeRune(rightText)
|
||||
r, combc, size := util.DecodeCharacter(rightText)
|
||||
rightText = rightText[size:]
|
||||
rw := runewidth.RuneWidth(r)
|
||||
for j := 0; j < rw; j++ {
|
||||
c := r
|
||||
if j > 0 {
|
||||
c = ' '
|
||||
combc = nil
|
||||
x++
|
||||
}
|
||||
screen.Screen.SetContent(winX+x, y, c, nil, statusLineStyle)
|
||||
screen.SetContent(winX+x, y, c, combc, statusLineStyle)
|
||||
}
|
||||
} else {
|
||||
screen.Screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
|
||||
screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,44 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
type TabWindow struct {
|
||||
Names []string
|
||||
active int
|
||||
Y int
|
||||
width int
|
||||
Width int
|
||||
hscroll int
|
||||
}
|
||||
|
||||
func NewTabWindow(w int, y int) *TabWindow {
|
||||
tw := new(TabWindow)
|
||||
tw.width = w
|
||||
tw.Width = w
|
||||
tw.Y = y
|
||||
return tw
|
||||
}
|
||||
|
||||
func (w *TabWindow) Resize(width, height int) {
|
||||
w.Width = width
|
||||
}
|
||||
|
||||
func (w *TabWindow) LocFromVisual(vloc buffer.Loc) int {
|
||||
x := -w.hscroll
|
||||
|
||||
for i, n := range w.Names {
|
||||
x++
|
||||
s := utf8.RuneCountInString(n)
|
||||
s := util.CharacterCountInString(n)
|
||||
if vloc.Y == w.Y && vloc.X < x+s {
|
||||
return i
|
||||
}
|
||||
x += s
|
||||
x += 3
|
||||
if x >= w.width {
|
||||
if x >= w.Width {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -45,13 +47,18 @@ func (w *TabWindow) LocFromVisual(vloc buffer.Loc) int {
|
||||
|
||||
func (w *TabWindow) Scroll(amt int) {
|
||||
w.hscroll += amt
|
||||
w.hscroll = util.Clamp(w.hscroll, 0, w.TotalSize()-w.width)
|
||||
s := w.TotalSize()
|
||||
w.hscroll = util.Clamp(w.hscroll, 0, s-w.Width)
|
||||
|
||||
if s-w.Width <= 0 {
|
||||
w.hscroll = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (w *TabWindow) TotalSize() int {
|
||||
sum := 2
|
||||
for _, n := range w.Names {
|
||||
sum += utf8.RuneCountInString(n) + 4
|
||||
sum += runewidth.StringWidth(n) + 4
|
||||
}
|
||||
return sum - 4
|
||||
}
|
||||
@@ -64,24 +71,34 @@ func (w *TabWindow) SetActive(a int) {
|
||||
w.active = a
|
||||
x := 2
|
||||
s := w.TotalSize()
|
||||
|
||||
for i, n := range w.Names {
|
||||
c := utf8.RuneCountInString(n)
|
||||
c := util.CharacterCountInString(n)
|
||||
if i == a {
|
||||
if x+c >= w.hscroll+w.width {
|
||||
w.hscroll = util.Clamp(x+c+1-w.width, 0, s-w.width)
|
||||
if x+c >= w.hscroll+w.Width {
|
||||
w.hscroll = util.Clamp(x+c+1-w.Width, 0, s-w.Width)
|
||||
} else if x < w.hscroll {
|
||||
w.hscroll = util.Clamp(x-4, 0, s-w.width)
|
||||
w.hscroll = util.Clamp(x-4, 0, s-w.Width)
|
||||
}
|
||||
break
|
||||
}
|
||||
x += c + 4
|
||||
}
|
||||
|
||||
if s-w.Width <= 0 {
|
||||
w.hscroll = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (w *TabWindow) Display() {
|
||||
x := -w.hscroll
|
||||
done := false
|
||||
|
||||
tabBarStyle := config.DefStyle.Reverse(true)
|
||||
if style, ok := config.Colorscheme["tabbar"]; ok {
|
||||
tabBarStyle = style
|
||||
}
|
||||
|
||||
draw := func(r rune, n int) {
|
||||
for i := 0; i < n; i++ {
|
||||
rw := runewidth.RuneWidth(r)
|
||||
@@ -90,14 +107,14 @@ func (w *TabWindow) Display() {
|
||||
if j > 0 {
|
||||
c = ' '
|
||||
}
|
||||
if x == w.width-1 && !done {
|
||||
screen.Screen.SetContent(w.width-1, w.Y, '>', nil, config.DefStyle.Reverse(true))
|
||||
if x == w.Width-1 && !done {
|
||||
screen.SetContent(w.Width-1, w.Y, '>', nil, tabBarStyle)
|
||||
x++
|
||||
break
|
||||
} else if x == 0 && w.hscroll > 0 {
|
||||
screen.Screen.SetContent(0, w.Y, '<', nil, config.DefStyle.Reverse(true))
|
||||
} else if x >= 0 && x < w.width {
|
||||
screen.Screen.SetContent(x, w.Y, c, nil, config.DefStyle.Reverse(true))
|
||||
screen.SetContent(0, w.Y, '<', nil, tabBarStyle)
|
||||
} else if x >= 0 && x < w.Width {
|
||||
screen.SetContent(x, w.Y, c, nil, tabBarStyle)
|
||||
}
|
||||
x++
|
||||
}
|
||||
@@ -122,12 +139,12 @@ func (w *TabWindow) Display() {
|
||||
} else {
|
||||
draw(' ', 3)
|
||||
}
|
||||
if x >= w.width {
|
||||
if x >= w.Width {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if x < w.width {
|
||||
draw(' ', w.width-x)
|
||||
if x < w.Width {
|
||||
draw(' ', w.Width-x)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell"
|
||||
"github.com/zyedidia/terminal"
|
||||
)
|
||||
@@ -51,7 +50,7 @@ func (w *TermWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {
|
||||
func (w *TermWindow) Clear() {
|
||||
for y := 0; y < w.Height; y++ {
|
||||
for x := 0; x < w.Width; x++ {
|
||||
screen.Screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
|
||||
screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,7 +87,7 @@ func (w *TermWindow) Display() {
|
||||
st = st.Reverse(true)
|
||||
}
|
||||
|
||||
screen.Screen.SetContent(w.X+x, w.Y+y, c, nil, st)
|
||||
screen.SetContent(w.X+x, w.Y+y, c, nil, st)
|
||||
}
|
||||
}
|
||||
if config.GetGlobalOption("statusline").(bool) {
|
||||
@@ -98,19 +97,19 @@ func (w *TermWindow) Display() {
|
||||
}
|
||||
|
||||
text := []byte(w.Name())
|
||||
textLen := utf8.RuneCount(text)
|
||||
textLen := util.CharacterCount(text)
|
||||
for x := 0; x < w.Width; x++ {
|
||||
if x < textLen {
|
||||
r, size := utf8.DecodeRune(text)
|
||||
r, combc, size := util.DecodeCharacter(text)
|
||||
text = text[size:]
|
||||
screen.Screen.SetContent(w.X+x, w.Y+w.Height, r, nil, statusLineStyle)
|
||||
screen.SetContent(w.X+x, w.Y+w.Height, r, combc, statusLineStyle)
|
||||
} else {
|
||||
screen.Screen.SetContent(w.X+x, w.Y+w.Height, ' ', nil, statusLineStyle)
|
||||
screen.SetContent(w.X+x, w.Y+w.Height, ' ', nil, statusLineStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
if w.State.CursorVisible() && w.active {
|
||||
curx, cury := w.State.Cursor()
|
||||
screen.Screen.ShowCursor(curx+w.X, cury+w.Y)
|
||||
screen.ShowCursor(curx+w.X, cury+w.Y)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/views"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/views"
|
||||
)
|
||||
|
||||
type UIWindow struct {
|
||||
@@ -24,11 +25,23 @@ func (w *UIWindow) drawNode(n *views.Node) {
|
||||
dividerStyle = style
|
||||
}
|
||||
|
||||
divchars := config.GetGlobalOption("divchars").(string)
|
||||
if util.CharacterCountInString(divchars) != 2 {
|
||||
divchars = "|-"
|
||||
}
|
||||
|
||||
divchar, combc, _ := util.DecodeCharacterInString(divchars)
|
||||
|
||||
divreverse := config.GetGlobalOption("divreverse").(bool)
|
||||
if divreverse {
|
||||
dividerStyle = dividerStyle.Reverse(true)
|
||||
}
|
||||
|
||||
for i, c := range cs {
|
||||
if c.IsLeaf() && c.Kind == views.STVert {
|
||||
if i != len(cs)-1 {
|
||||
for h := 0; h < c.H; h++ {
|
||||
screen.Screen.SetContent(c.X+c.W, c.Y+h, '|', nil, dividerStyle.Reverse(true))
|
||||
screen.SetContent(c.X+c.W, c.Y+h, divchar, combc, dividerStyle)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
)
|
||||
|
||||
type View struct {
|
||||
|
||||
@@ -3,8 +3,9 @@ package info
|
||||
import (
|
||||
"encoding/gob"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
)
|
||||
|
||||
// LoadHistory attempts to load user history from configDir/buffers/history
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
// The savehistory option must be on
|
||||
func (i *InfoBuf) LoadHistory() {
|
||||
if config.GetGlobalOption("savehistory").(bool) {
|
||||
file, err := os.Open(config.ConfigDir + "/buffers/history")
|
||||
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", "history"))
|
||||
defer file.Close()
|
||||
var decodedMap map[string][]string
|
||||
if err == nil {
|
||||
@@ -46,7 +47,7 @@ func (i *InfoBuf) SaveHistory() {
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Create(config.ConfigDir + "/buffers/history")
|
||||
file, err := os.Create(filepath.Join(config.ConfigDir, "buffers", "history"))
|
||||
defer file.Close()
|
||||
if err == nil {
|
||||
encoder := gob.NewEncoder(file)
|
||||
|
||||
@@ -2,14 +2,8 @@ package info
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
)
|
||||
|
||||
// The InfoBuf displays messages and other info at the bottom of the screen.
|
||||
@@ -134,63 +128,6 @@ func (i *InfoBuf) YNPrompt(prompt string, donecb func(bool, bool)) {
|
||||
i.YNCallback = donecb
|
||||
}
|
||||
|
||||
// PlugPrompt provides a plugin interface for calling "Prompt" with the appropriate Lua callbacks
|
||||
func (i *InfoBuf) PlugPrompt(prompt string, msg string, ptype string, eventcb string, donecb string) {
|
||||
eventLuaFn := strings.Split(eventcb, ".")
|
||||
doneLuaFn := strings.Split(donecb, ".")
|
||||
var luaEventcb func(string)
|
||||
var luaDonecb func(string, bool)
|
||||
|
||||
if len(eventLuaFn) == 2 {
|
||||
plName, plFn := doneLuaFn[0], doneLuaFn[1]
|
||||
pl := config.FindPlugin(plName)
|
||||
if pl != nil {
|
||||
luaEventcb = func(resp string) {
|
||||
_, err := pl.Call(plFn, luar.New(ulua.L, resp))
|
||||
if err != nil && err != config.ErrNoSuchFunction {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(doneLuaFn) == 2 {
|
||||
plName, plFn := doneLuaFn[0], doneLuaFn[1]
|
||||
pl := config.FindPlugin(plName)
|
||||
if pl != nil {
|
||||
luaDonecb = func(resp string, canceled bool) {
|
||||
_, err := pl.Call(plFn, luar.New(ulua.L, resp), luar.New(ulua.L, canceled))
|
||||
if err != nil && err != config.ErrNoSuchFunction {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i.Prompt(prompt, msg, ptype, luaEventcb, luaDonecb)
|
||||
}
|
||||
|
||||
// PlugYNPrompt provides a plugin interface for calling "YNPrompt" with the appropriate Lua callbacks
|
||||
func (i *InfoBuf) PlugYNPrompt(prompt string, donecb string) {
|
||||
doneLuaFn := strings.Split(donecb, ".")
|
||||
var luaDonecb func(bool, bool)
|
||||
|
||||
if len(doneLuaFn) == 2 {
|
||||
plName, plFn := doneLuaFn[0], doneLuaFn[1]
|
||||
pl := config.FindPlugin(plName)
|
||||
if pl != nil {
|
||||
luaDonecb = func(resp bool, canceled bool) {
|
||||
_, err := pl.Call(plFn, luar.New(ulua.L, resp), luar.New(ulua.L, canceled))
|
||||
if err != nil && err != config.ErrNoSuchFunction {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i.YNPrompt(prompt, luaDonecb)
|
||||
}
|
||||
|
||||
// DonePrompt finishes the current prompt and indicates whether or not it was canceled
|
||||
func (i *InfoBuf) DonePrompt(canceled bool) {
|
||||
hadYN := i.HasYN
|
||||
@@ -209,7 +146,7 @@ func (i *InfoBuf) DonePrompt(canceled bool) {
|
||||
h := i.History[i.PromptType]
|
||||
h[len(h)-1] = resp
|
||||
}
|
||||
i.PromptCallback = nil
|
||||
// i.PromptCallback = nil
|
||||
}
|
||||
i.Replace(i.Start(), i.End(), "")
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"unicode"
|
||||
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/pkg/terminfo"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -17,19 +18,96 @@ import (
|
||||
// screen. TODO: maybe we should worry about polling and drawing at the
|
||||
// same time too.
|
||||
var Screen tcell.Screen
|
||||
var lock sync.Mutex
|
||||
var DrawChan chan bool
|
||||
|
||||
// The lock is necessary since the screen is polled on a separate thread
|
||||
var lock sync.Mutex
|
||||
|
||||
// drawChan is a channel that will cause the screen to redraw when
|
||||
// written to even if no event user event has occurred
|
||||
var drawChan chan bool
|
||||
|
||||
// Lock locks the screen lock
|
||||
func Lock() {
|
||||
lock.Lock()
|
||||
}
|
||||
|
||||
// Unlock unlocks the screen lock
|
||||
func Unlock() {
|
||||
lock.Unlock()
|
||||
}
|
||||
|
||||
// Redraw schedules a redraw with the draw channel
|
||||
func Redraw() {
|
||||
DrawChan <- true
|
||||
select {
|
||||
case drawChan <- true:
|
||||
default:
|
||||
// channel is full
|
||||
}
|
||||
}
|
||||
|
||||
// DrawChan returns the draw channel
|
||||
func DrawChan() chan bool {
|
||||
return drawChan
|
||||
}
|
||||
|
||||
type screenCell struct {
|
||||
x, y int
|
||||
r rune
|
||||
combc []rune
|
||||
style tcell.Style
|
||||
}
|
||||
|
||||
var lastCursor screenCell
|
||||
|
||||
// ShowFakeCursor displays a cursor at the given position by modifying the
|
||||
// style of the given column instead of actually using the terminal cursor
|
||||
// This can be useful in certain terminals such as the windows console where
|
||||
// modifying the cursor location is slow and frequent modifications cause flashing
|
||||
// This keeps track of the most recent fake cursor location and resets it when
|
||||
// a new fake cursor location is specified
|
||||
func ShowFakeCursor(x, y int) {
|
||||
r, combc, style, _ := Screen.GetContent(x, y)
|
||||
Screen.SetContent(lastCursor.x, lastCursor.y, lastCursor.r, lastCursor.combc, lastCursor.style)
|
||||
Screen.SetContent(x, y, r, combc, config.DefStyle.Reverse(true))
|
||||
|
||||
lastCursor.x, lastCursor.y = x, y
|
||||
lastCursor.r = r
|
||||
lastCursor.combc = combc
|
||||
lastCursor.style = style
|
||||
}
|
||||
|
||||
// ShowFakeCursorMulti is the same as ShowFakeCursor except it does not
|
||||
// reset previous locations of the cursor
|
||||
// Fake cursors are also necessary to display multiple cursors
|
||||
func ShowFakeCursorMulti(x, y int) {
|
||||
r, _, _, _ := Screen.GetContent(x, y)
|
||||
Screen.SetContent(x, y, r, nil, config.DefStyle.Reverse(true))
|
||||
}
|
||||
|
||||
// ShowCursor puts the cursor at the given location using a fake cursor
|
||||
// if enabled or using the terminal cursor otherwise
|
||||
// By default only the windows console will use a fake cursor
|
||||
func ShowCursor(x, y int) {
|
||||
if util.FakeCursor {
|
||||
ShowFakeCursor(x, y)
|
||||
} else {
|
||||
Screen.ShowCursor(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
// SetContent sets a cell at a point on the screen and makes sure that it is
|
||||
// synced with the last cursor location
|
||||
func SetContent(x, y int, mainc rune, combc []rune, style tcell.Style) {
|
||||
if !unicode.IsPrint(mainc) {
|
||||
mainc = '<27>'
|
||||
}
|
||||
|
||||
Screen.SetContent(x, y, mainc, combc, style)
|
||||
if util.FakeCursor && lastCursor.x == x && lastCursor.y == y {
|
||||
lastCursor.r = mainc
|
||||
lastCursor.style = style
|
||||
lastCursor.combc = combc
|
||||
}
|
||||
}
|
||||
|
||||
// TempFini shuts the screen down temporarily
|
||||
@@ -54,57 +132,40 @@ func TempStart(screenWasNil bool) {
|
||||
|
||||
// Init creates and initializes the tcell screen
|
||||
func Init() {
|
||||
DrawChan = make(chan bool, 8)
|
||||
drawChan = make(chan bool, 8)
|
||||
|
||||
// Should we enable true color?
|
||||
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
|
||||
|
||||
tcelldb := os.Getenv("TCELLDB")
|
||||
os.Setenv("TCELLDB", config.ConfigDir+"/.tcelldb")
|
||||
if !truecolor {
|
||||
os.Setenv("TCELL_TRUECOLOR", "disable")
|
||||
}
|
||||
|
||||
// In order to enable true color, we have to set the TERM to `xterm-truecolor` when
|
||||
// initializing tcell, but after that, we can set the TERM back to whatever it was
|
||||
oldTerm := os.Getenv("TERM")
|
||||
if truecolor {
|
||||
os.Setenv("TERM", "xterm-truecolor")
|
||||
var oldTerm string
|
||||
if config.GetGlobalOption("xterm").(bool) {
|
||||
oldTerm = os.Getenv("TERM")
|
||||
os.Setenv("TERM", "xterm-256color")
|
||||
}
|
||||
|
||||
// Initilize tcell
|
||||
var err error
|
||||
Screen, err = tcell.NewScreen()
|
||||
if err != nil {
|
||||
if err == tcell.ErrTermNotFound {
|
||||
err = terminfo.WriteDB(config.ConfigDir + "/.tcelldb")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Fatal: Micro could not create terminal database file", config.ConfigDir+"/.tcelldb")
|
||||
os.Exit(1)
|
||||
}
|
||||
Screen, err = tcell.NewScreen()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Fatal: Micro could not initialize a Screen.")
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Fatal: Micro could not initialize a Screen.")
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(err)
|
||||
fmt.Println("Fatal: Micro could not initialize a Screen.")
|
||||
os.Exit(1)
|
||||
}
|
||||
if err = Screen.Init(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Now we can put the TERM back to what it was before
|
||||
if truecolor {
|
||||
// restore TERM
|
||||
if config.GetGlobalOption("xterm").(bool) {
|
||||
os.Setenv("TERM", oldTerm)
|
||||
}
|
||||
|
||||
if config.GetGlobalOption("mouse").(bool) {
|
||||
Screen.EnableMouse()
|
||||
}
|
||||
|
||||
os.Setenv("TCELLDB", tcelldb)
|
||||
}
|
||||
|
||||
@@ -4,14 +4,6 @@ import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
)
|
||||
|
||||
var Jobs chan JobFunction
|
||||
@@ -32,7 +24,7 @@ func init() {
|
||||
// JobFunction is a representation of a job (this data structure is what is loaded
|
||||
// into the jobs channel)
|
||||
type JobFunction struct {
|
||||
Function func(string, ...interface{})
|
||||
Function func(string, []interface{})
|
||||
Output string
|
||||
Args []interface{}
|
||||
}
|
||||
@@ -41,7 +33,7 @@ type JobFunction struct {
|
||||
type CallbackFile struct {
|
||||
io.Writer
|
||||
|
||||
callback func(string, ...interface{})
|
||||
callback func(string, []interface{})
|
||||
args []interface{}
|
||||
}
|
||||
|
||||
@@ -55,23 +47,23 @@ func (f *CallbackFile) Write(data []byte) (int, error) {
|
||||
|
||||
// JobStart starts a shell command in the background with the given callbacks
|
||||
// It returns an *exec.Cmd as the job id
|
||||
func JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...interface{}) *exec.Cmd {
|
||||
func JobStart(cmd string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *exec.Cmd {
|
||||
return JobSpawn("sh", []string{"-c", cmd}, onStdout, onStderr, onExit, userargs...)
|
||||
}
|
||||
|
||||
// JobSpawn starts a process with args in the background with the given callbacks
|
||||
// It returns an *exec.Cmd as the job id
|
||||
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit string, userargs ...interface{}) *exec.Cmd {
|
||||
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *exec.Cmd {
|
||||
// Set up everything correctly if the functions have been provided
|
||||
proc := exec.Command(cmdName, cmdArgs...)
|
||||
var outbuf bytes.Buffer
|
||||
if onStdout != "" {
|
||||
proc.Stdout = &CallbackFile{&outbuf, luaFunctionJob(onStdout), userargs}
|
||||
if onStdout != nil {
|
||||
proc.Stdout = &CallbackFile{&outbuf, onStdout, userargs}
|
||||
} else {
|
||||
proc.Stdout = &outbuf
|
||||
}
|
||||
if onStderr != "" {
|
||||
proc.Stderr = &CallbackFile{&outbuf, luaFunctionJob(onStderr), userargs}
|
||||
if onStderr != nil {
|
||||
proc.Stderr = &CallbackFile{&outbuf, onStderr, userargs}
|
||||
} else {
|
||||
proc.Stderr = &outbuf
|
||||
}
|
||||
@@ -79,7 +71,7 @@ func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit strin
|
||||
go func() {
|
||||
// Run the process in the background and create the onExit callback
|
||||
proc.Run()
|
||||
jobFunc := JobFunction{luaFunctionJob(onExit), string(outbuf.Bytes()), userargs}
|
||||
jobFunc := JobFunction{onExit, string(outbuf.Bytes()), userargs}
|
||||
Jobs <- jobFunc
|
||||
}()
|
||||
|
||||
@@ -100,25 +92,3 @@ func JobSend(cmd *exec.Cmd, data string) {
|
||||
|
||||
stdin.Write([]byte(data))
|
||||
}
|
||||
|
||||
// luaFunctionJob returns a function that will call the given lua function
|
||||
// structured as a job call i.e. the job output and arguments are provided
|
||||
// to the lua function
|
||||
func luaFunctionJob(fn string) func(string, ...interface{}) {
|
||||
luaFn := strings.Split(fn, ".")
|
||||
if len(luaFn) <= 1 {
|
||||
return nil
|
||||
}
|
||||
plName, plFn := luaFn[0], luaFn[1]
|
||||
pl := config.FindPlugin(plName)
|
||||
if pl == nil {
|
||||
return nil
|
||||
}
|
||||
return func(output string, args ...interface{}) {
|
||||
luaArgs := []lua.LValue{luar.New(ulua.L, output), luar.New(ulua.L, args)}
|
||||
_, err := pl.Call(plFn, luaArgs...)
|
||||
if err != nil && err != config.ErrNoSuchFunction {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package shell
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -9,8 +10,8 @@ import (
|
||||
"os/signal"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/pkg/shellwords"
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
)
|
||||
|
||||
// ExecCommand executes a command using exec
|
||||
@@ -32,10 +33,13 @@ func ExecCommand(name string, arg ...string) (string, error) {
|
||||
|
||||
// RunCommand executes a shell command and returns the output/error
|
||||
func RunCommand(input string) (string, error) {
|
||||
args, err := shellwords.Split(input)
|
||||
args, err := shellquote.Split(input)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return "", errors.New("No arguments")
|
||||
}
|
||||
inputCmd := args[0]
|
||||
|
||||
return ExecCommand(inputCmd, args[1:]...)
|
||||
@@ -45,10 +49,13 @@ func RunCommand(input string) (string, error) {
|
||||
// It returns a function which will run the command and returns a string
|
||||
// message result
|
||||
func RunBackgroundShell(input string) (func() string, error) {
|
||||
args, err := shellwords.Split(input)
|
||||
args, err := shellquote.Split(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return nil, errors.New("No arguments")
|
||||
}
|
||||
inputCmd := args[0]
|
||||
return func() string {
|
||||
output, err := RunCommand(input)
|
||||
@@ -68,10 +75,13 @@ func RunBackgroundShell(input string) (func() string, error) {
|
||||
|
||||
// RunInteractiveShell runs a shellcommand interactively
|
||||
func RunInteractiveShell(input string, wait bool, getOutput bool) (string, error) {
|
||||
args, err := shellwords.Split(input)
|
||||
args, err := shellquote.Split(input)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return "", errors.New("No arguments")
|
||||
}
|
||||
inputCmd := args[0]
|
||||
|
||||
// Shut down the screen because we're going to interact directly with the shell
|
||||
|
||||
@@ -2,19 +2,12 @@ package shell
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/terminal"
|
||||
luar "layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
type TermType int
|
||||
@@ -76,7 +69,7 @@ func (t *Terminal) GetSelection(width int) string {
|
||||
}
|
||||
|
||||
// Start begins a new command in this terminal with a given view
|
||||
func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback string, userargs []interface{}) error {
|
||||
func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
|
||||
if len(execCmd) <= 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -95,27 +88,16 @@ func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback s
|
||||
t.Status = TTRunning
|
||||
t.title = execCmd[0] + ":" + strconv.Itoa(cmd.Process.Pid)
|
||||
t.wait = wait
|
||||
|
||||
luaFn := strings.Split(callback, ".")
|
||||
if len(luaFn) >= 2 {
|
||||
plName, plFn := luaFn[0], luaFn[1]
|
||||
pl := config.FindPlugin(plName)
|
||||
if pl != nil {
|
||||
t.callback = func(out string) {
|
||||
luaArgs := []lua.LValue{luar.New(ulua.L, out), luar.New(ulua.L, userargs)}
|
||||
_, err := pl.Call(plFn, luaArgs...)
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
t.callback = func(out string) {
|
||||
callback(out, userargs)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
err := Term.Parse()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "[Press enter to close]")
|
||||
Term.Write([]byte("Press enter to close"))
|
||||
screen.Redraw()
|
||||
break
|
||||
}
|
||||
screen.Redraw()
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// LuaRuneAt is a helper function for lua plugins to return the rune
|
||||
// at an index within a string
|
||||
func LuaRuneAt(str string, runeidx int) string {
|
||||
i := 0
|
||||
for len(str) > 0 {
|
||||
r, size := utf8.DecodeRuneInString(str)
|
||||
r, _, size := DecodeCharacterInString(str)
|
||||
|
||||
str = str[size:]
|
||||
|
||||
@@ -20,10 +18,11 @@ func LuaRuneAt(str string, runeidx int) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// LuaGetLeadingWhitespace returns the leading whitespace of a string (used by lua plugins)
|
||||
func LuaGetLeadingWhitespace(s string) string {
|
||||
ws := []byte{}
|
||||
for len(s) > 0 {
|
||||
r, size := utf8.DecodeRuneInString(s)
|
||||
r, _, size := DecodeCharacterInString(s)
|
||||
if r == ' ' || r == '\t' {
|
||||
ws = append(ws, byte(r))
|
||||
} else {
|
||||
@@ -35,7 +34,8 @@ func LuaGetLeadingWhitespace(s string) string {
|
||||
return string(ws)
|
||||
}
|
||||
|
||||
// LuaIsWordChar returns true if the first rune in a string is a word character
|
||||
func LuaIsWordChar(s string) bool {
|
||||
r, _ := utf8.DecodeRuneInString(s)
|
||||
r, _, _ := DecodeCharacterInString(s)
|
||||
return IsWordChar(r)
|
||||
}
|
||||
|
||||
@@ -16,14 +16,12 @@ func GetMemStats() string {
|
||||
return fmt.Sprintf("Alloc: %s, Sys: %s, GC: %d, PauseTotalNs: %dns", humanize.Bytes(memstats.Alloc), humanize.Bytes(memstats.Sys), memstats.NumGC, memstats.PauseTotalNs)
|
||||
}
|
||||
|
||||
var start time.Time
|
||||
|
||||
func Tic(s string) {
|
||||
func Tic(s string) time.Time {
|
||||
log.Println("START:", s)
|
||||
start = time.Now()
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func Toc() {
|
||||
func Toc(start time.Time) {
|
||||
end := time.Now()
|
||||
log.Println("END: ElapsedTime in seconds:", end.Sub(start))
|
||||
}
|
||||
|
||||
96
internal/util/unicode.go
Normal file
96
internal/util/unicode.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Unicode is annoying. A "code point" (rune in Go-speak) may need up to
|
||||
// 4 bytes to represent it. In general, a code point will represent a
|
||||
// complete character, but this is not always the case. A character with
|
||||
// accents may be made up of multiple code points (the code point for the
|
||||
// original character, and additional code points for each accent/marking).
|
||||
// The functions below are meant to help deal with these additional "combining"
|
||||
// code points. In underlying operations (search, replace, etc...), micro will
|
||||
// treat a character with combining code points as just the original code point.
|
||||
// For rendering, micro will display the combining characters. It's not perfect
|
||||
// but it's pretty good.
|
||||
|
||||
var minMark = rune(unicode.Mark.R16[0].Lo)
|
||||
|
||||
func isMark(r rune) bool {
|
||||
// Fast path
|
||||
if r < minMark {
|
||||
return false
|
||||
}
|
||||
return unicode.In(r, unicode.Mark)
|
||||
}
|
||||
|
||||
// DecodeCharacter returns the next character from an array of bytes
|
||||
// A character is a rune along with any accompanying combining runes
|
||||
func DecodeCharacter(b []byte) (rune, []rune, int) {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
b = b[size:]
|
||||
c, s := utf8.DecodeRune(b)
|
||||
|
||||
var combc []rune
|
||||
for isMark(c) {
|
||||
combc = append(combc, c)
|
||||
size += s
|
||||
|
||||
b = b[s:]
|
||||
c, s = utf8.DecodeRune(b)
|
||||
}
|
||||
|
||||
return r, combc, size
|
||||
}
|
||||
|
||||
// DecodeCharacterInString returns the next character from a string
|
||||
// A character is a rune along with any accompanying combining runes
|
||||
func DecodeCharacterInString(str string) (rune, []rune, int) {
|
||||
r, size := utf8.DecodeRuneInString(str)
|
||||
str = str[size:]
|
||||
c, s := utf8.DecodeRuneInString(str)
|
||||
|
||||
var combc []rune
|
||||
for isMark(c) {
|
||||
combc = append(combc, c)
|
||||
size += s
|
||||
|
||||
str = str[s:]
|
||||
c, s = utf8.DecodeRuneInString(str)
|
||||
}
|
||||
|
||||
return r, combc, size
|
||||
}
|
||||
|
||||
// CharacterCount returns the number of characters in a byte array
|
||||
// Similar to utf8.RuneCount but for unicode characters
|
||||
func CharacterCount(b []byte) int {
|
||||
s := 0
|
||||
|
||||
for len(b) > 0 {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
if !isMark(r) {
|
||||
s++
|
||||
}
|
||||
|
||||
b = b[size:]
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// CharacterCount returns the number of characters in a string
|
||||
// Similar to utf8.RuneCountInString but for unicode characters
|
||||
func CharacterCountInString(str string) int {
|
||||
s := 0
|
||||
|
||||
for _, r := range str {
|
||||
if !isMark(r) {
|
||||
s++
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/blang/semver"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
@@ -29,7 +30,13 @@ var (
|
||||
// CompileDate is the date this binary was compiled on
|
||||
CompileDate = "Unknown"
|
||||
// Debug logging
|
||||
Debug = "ON"
|
||||
Debug = "OFF"
|
||||
// FakeCursor is used to disable the terminal cursor and have micro
|
||||
// draw its own (enabled for windows consoles where the cursor is slow)
|
||||
FakeCursor = false
|
||||
|
||||
// Stdout is a buffer that is written to stdout when micro closes
|
||||
Stdout *bytes.Buffer
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -38,6 +45,11 @@ func init() {
|
||||
if err != nil {
|
||||
fmt.Println("Invalid version: ", Version, err)
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
FakeCursor = true
|
||||
}
|
||||
Stdout = new(bytes.Buffer)
|
||||
}
|
||||
|
||||
// SliceEnd returns a byte slice where the index is a rune index
|
||||
@@ -51,7 +63,7 @@ func SliceEnd(slc []byte, index int) []byte {
|
||||
return slc[totalSize:]
|
||||
}
|
||||
|
||||
_, size := utf8.DecodeRune(slc[totalSize:])
|
||||
_, _, size := DecodeCharacter(slc[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
@@ -69,7 +81,7 @@ func SliceEndStr(str string, index int) string {
|
||||
return str[totalSize:]
|
||||
}
|
||||
|
||||
_, size := utf8.DecodeRuneInString(str[totalSize:])
|
||||
_, _, size := DecodeCharacterInString(str[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
@@ -88,7 +100,7 @@ func SliceStart(slc []byte, index int) []byte {
|
||||
return slc[:totalSize]
|
||||
}
|
||||
|
||||
_, size := utf8.DecodeRune(slc[totalSize:])
|
||||
_, _, size := DecodeCharacter(slc[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
@@ -106,7 +118,7 @@ func SliceStartStr(str string, index int) string {
|
||||
return str[:totalSize]
|
||||
}
|
||||
|
||||
_, size := utf8.DecodeRuneInString(str[totalSize:])
|
||||
_, _, size := DecodeCharacterInString(str[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
@@ -122,7 +134,7 @@ func SliceVisualEnd(b []byte, n, tabsize int) ([]byte, int, int) {
|
||||
width := 0
|
||||
i := 0
|
||||
for len(b) > 0 {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
r, _, size := DecodeCharacter(b)
|
||||
|
||||
w := 0
|
||||
switch r {
|
||||
@@ -159,7 +171,7 @@ func StringWidth(b []byte, n, tabsize int) int {
|
||||
i := 0
|
||||
width := 0
|
||||
for len(b) > 0 {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
r, _, size := DecodeCharacter(b)
|
||||
b = b[size:]
|
||||
|
||||
switch r {
|
||||
@@ -252,7 +264,7 @@ func IsBytesWhitespace(b []byte) bool {
|
||||
// RunePos returns the rune index of a given byte index
|
||||
// Make sure the byte index is not between code points
|
||||
func RunePos(b []byte, i int) int {
|
||||
return utf8.RuneCount(b[:i])
|
||||
return CharacterCount(b[:i])
|
||||
}
|
||||
|
||||
// MakeRelative will attempt to make a relative path between path and base
|
||||
@@ -267,7 +279,6 @@ func MakeRelative(path, base string) (string, error) {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// TODO: consider changing because of snap segfault
|
||||
// ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
|
||||
// home directory. Does nothing if the path does not start with '~'.
|
||||
func ReplaceHome(path string) (string, error) {
|
||||
@@ -332,7 +343,7 @@ func EscapePath(path string) string {
|
||||
func GetLeadingWhitespace(b []byte) []byte {
|
||||
ws := []byte{}
|
||||
for len(b) > 0 {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
r, _, size := DecodeCharacter(b)
|
||||
if r == ' ' || r == '\t' {
|
||||
ws = append(ws, byte(r))
|
||||
} else {
|
||||
@@ -358,7 +369,7 @@ func GetCharPosInLine(b []byte, visualPos int, tabsize int) int {
|
||||
i := 0 // char pos
|
||||
width := 0 // string visual width
|
||||
for len(b) > 0 {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
r, _, size := DecodeCharacter(b)
|
||||
b = b[size:]
|
||||
|
||||
switch r {
|
||||
@@ -404,9 +415,18 @@ func Clamp(val, min, max int) int {
|
||||
}
|
||||
|
||||
func IsNonAlphaNumeric(c rune) bool {
|
||||
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
|
||||
return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '_'
|
||||
}
|
||||
|
||||
func IsAutocomplete(c rune) bool {
|
||||
return c == '.' || !IsNonAlphaNumeric(c)
|
||||
}
|
||||
|
||||
func ParseSpecial(s string) string {
|
||||
return strings.Replace(s, "\\t", "\t", -1)
|
||||
}
|
||||
|
||||
// String converts a byte array to a string (for lua plugins)
|
||||
func String(s []byte) string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
@@ -456,9 +456,9 @@ func (n *Node) unsplit(i int, h bool) {
|
||||
|
||||
// Unsplit deletes this split and resizes everything
|
||||
// else accordingly
|
||||
func (n *Node) Unsplit() {
|
||||
if !n.IsLeaf() {
|
||||
return
|
||||
func (n *Node) Unsplit() bool {
|
||||
if !n.IsLeaf() || n.parent == nil {
|
||||
return false
|
||||
}
|
||||
ind := 0
|
||||
for i, c := range n.parent.children {
|
||||
@@ -473,8 +473,9 @@ func (n *Node) Unsplit() {
|
||||
}
|
||||
|
||||
if n.parent.IsLeaf() {
|
||||
n.parent.Unsplit()
|
||||
return n.parent.Unsplit()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// String returns the string form of the node and all children (used for debugging)
|
||||
|
||||
@@ -6,7 +6,7 @@ import "regexp"
|
||||
// to determine the filetype of the file
|
||||
// It will return the corresponding syntax definition for the filetype
|
||||
func MatchFiletype(ftdetect [2]*regexp.Regexp, filename string, firstLine []byte) bool {
|
||||
if ftdetect[0].MatchString(filename) {
|
||||
if ftdetect[0] != nil && ftdetect[0].MatchString(filename) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package highlight
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func sliceStart(slc []byte, index int) []byte {
|
||||
@@ -15,7 +14,7 @@ func sliceStart(slc []byte, index int) []byte {
|
||||
return slc[totalSize:]
|
||||
}
|
||||
|
||||
_, size := utf8.DecodeRune(slc[totalSize:])
|
||||
_, _, size := DecodeCharacter(slc[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
@@ -32,7 +31,7 @@ func sliceEnd(slc []byte, index int) []byte {
|
||||
return slc[:totalSize]
|
||||
}
|
||||
|
||||
_, size := utf8.DecodeRune(slc[totalSize:])
|
||||
_, _, size := DecodeCharacter(slc[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
@@ -47,9 +46,9 @@ func runePos(p int, str []byte) int {
|
||||
return 0
|
||||
}
|
||||
if p >= len(str) {
|
||||
return utf8.RuneCount(str)
|
||||
return CharacterCount(str)
|
||||
}
|
||||
return utf8.RuneCount(str[:p])
|
||||
return CharacterCount(str[:p])
|
||||
}
|
||||
|
||||
func combineLineMatch(src, dst LineMatch) LineMatch {
|
||||
@@ -68,6 +67,8 @@ func combineLineMatch(src, dst LineMatch) LineMatch {
|
||||
// A State represents the region at the end of a line
|
||||
type State *region
|
||||
|
||||
var EmptyDef = Def{nil, &rules{}}
|
||||
|
||||
// LineStates is an interface for a buffer-like object which can also store the states and matches for every line
|
||||
type LineStates interface {
|
||||
LineBytes(n int) []byte
|
||||
@@ -110,7 +111,7 @@ func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte, canMatchSt
|
||||
var strbytes []byte
|
||||
if skip != nil {
|
||||
strbytes = skip.ReplaceAllFunc(str, func(match []byte) []byte {
|
||||
res := make([]byte, utf8.RuneCount(match))
|
||||
res := make([]byte, CharacterCount(match))
|
||||
return res
|
||||
})
|
||||
} else {
|
||||
@@ -146,7 +147,7 @@ func findAllIndex(regex *regexp.Regexp, str []byte, canMatchStart, canMatchEnd b
|
||||
}
|
||||
|
||||
func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, curRegion *region, statesOnly bool) LineMatch {
|
||||
lineLen := utf8.RuneCount(line)
|
||||
lineLen := CharacterCount(line)
|
||||
if start == 0 {
|
||||
if !statesOnly {
|
||||
if _, ok := highlights[0]; !ok {
|
||||
@@ -176,7 +177,7 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
|
||||
return highlights
|
||||
}
|
||||
|
||||
if lineLen == 0 || statesOnly {
|
||||
if lineLen == 0 {
|
||||
if canMatchEnd {
|
||||
h.lastRegion = curRegion
|
||||
}
|
||||
@@ -197,28 +198,32 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
|
||||
}
|
||||
}
|
||||
if firstLoc[0] != lineLen {
|
||||
highlights[start+firstLoc[0]] = firstRegion.limitGroup
|
||||
if !statesOnly {
|
||||
highlights[start+firstLoc[0]] = firstRegion.limitGroup
|
||||
}
|
||||
h.highlightRegion(highlights, start, false, lineNum, sliceEnd(line, firstLoc[0]), curRegion, statesOnly)
|
||||
h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), firstRegion, statesOnly)
|
||||
return highlights
|
||||
}
|
||||
|
||||
fullHighlights := make([]Group, lineLen)
|
||||
for i := 0; i < len(fullHighlights); i++ {
|
||||
fullHighlights[i] = curRegion.group
|
||||
}
|
||||
if !statesOnly {
|
||||
fullHighlights := make([]Group, lineLen)
|
||||
for i := 0; i < len(fullHighlights); i++ {
|
||||
fullHighlights[i] = curRegion.group
|
||||
}
|
||||
|
||||
for _, p := range curRegion.rules.patterns {
|
||||
matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
|
||||
for _, m := range matches {
|
||||
for i := m[0]; i < m[1]; i++ {
|
||||
fullHighlights[i] = p.group
|
||||
for _, p := range curRegion.rules.patterns {
|
||||
matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
|
||||
for _, m := range matches {
|
||||
for i := m[0]; i < m[1]; i++ {
|
||||
fullHighlights[i] = p.group
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, h := range fullHighlights {
|
||||
if i == 0 || h != fullHighlights[i-1] {
|
||||
highlights[start+i] = h
|
||||
for i, h := range fullHighlights {
|
||||
if i == 0 || h != fullHighlights[i-1] {
|
||||
highlights[start+i] = h
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +235,7 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
|
||||
}
|
||||
|
||||
func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, statesOnly bool) LineMatch {
|
||||
lineLen := utf8.RuneCount(line)
|
||||
lineLen := CharacterCount(line)
|
||||
if lineLen == 0 {
|
||||
if canMatchEnd {
|
||||
h.lastRegion = nil
|
||||
@@ -330,11 +335,11 @@ func (h *Highlighter) HighlightStates(input LineStates) {
|
||||
}
|
||||
}
|
||||
|
||||
// HighlightMatches sets the matches for each line in between startline and endline
|
||||
// HighlightMatches sets the matches for each line from startline to endline
|
||||
// It sets all other matches in the buffer to nil to conserve memory
|
||||
// This assumes that all the states are set correctly
|
||||
func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int) {
|
||||
for i := startline; i < endline; i++ {
|
||||
for i := startline; i <= endline; i++ {
|
||||
if i >= input.LinesNum() {
|
||||
break
|
||||
}
|
||||
@@ -354,8 +359,9 @@ func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int)
|
||||
}
|
||||
|
||||
// ReHighlightStates will scan down from `startline` and set the appropriate end of line state
|
||||
// for each line until it comes across the same state in two consecutive lines
|
||||
func (h *Highlighter) ReHighlightStates(input LineStates, startline int) {
|
||||
// for each line until it comes across a line whose state does not change
|
||||
// returns the number of the final line
|
||||
func (h *Highlighter) ReHighlightStates(input LineStates, startline int) int {
|
||||
// lines := input.LineData()
|
||||
|
||||
h.lastRegion = nil
|
||||
@@ -378,9 +384,11 @@ func (h *Highlighter) ReHighlightStates(input LineStates, startline int) {
|
||||
input.SetState(i, curState)
|
||||
|
||||
if curState == lastState {
|
||||
break
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return input.LinesNum() - 1
|
||||
}
|
||||
|
||||
// ReHighlightLine will rehighlight the state and match for a single line
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package highlight
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
@@ -41,6 +42,14 @@ type Header struct {
|
||||
FtDetect [2]*regexp.Regexp
|
||||
}
|
||||
|
||||
type HeaderYaml struct {
|
||||
FileType string `yaml:"filetype"`
|
||||
Detect struct {
|
||||
FNameRgx string `yaml:"filename"`
|
||||
HeaderRgx string `yaml:"header"`
|
||||
} `yaml:"detect"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
FileType string
|
||||
|
||||
@@ -82,52 +91,59 @@ func init() {
|
||||
Groups = make(map[string]Group)
|
||||
}
|
||||
|
||||
func ParseFtDetect(file *File) (r [2]*regexp.Regexp, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
err, ok = r.(error)
|
||||
if !ok {
|
||||
err = fmt.Errorf("pkg: %v", r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
// MakeHeader takes a header (.hdr file) file and parses the header
|
||||
// Header files make parsing more efficient when you only want to compute
|
||||
// on the headers of syntax files
|
||||
// A yaml file might take ~400us to parse while a header file only takes ~20us
|
||||
func MakeHeader(data []byte) (*Header, error) {
|
||||
lines := bytes.Split(data, []byte{'\n'})
|
||||
if len(lines) < 3 {
|
||||
return nil, errors.New("Header file has incorrect format")
|
||||
}
|
||||
header := new(Header)
|
||||
var err error
|
||||
header.FileType = string(lines[0])
|
||||
fnameRgx := string(lines[1])
|
||||
headerRgx := string(lines[2])
|
||||
|
||||
rules := file.yamlSrc
|
||||
|
||||
loaded := 0
|
||||
for k, v := range rules {
|
||||
if k == "detect" {
|
||||
ftdetect := v.(map[interface{}]interface{})
|
||||
if len(ftdetect) >= 1 {
|
||||
syntax, err := regexp.Compile(ftdetect["filename"].(string))
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
|
||||
r[0] = syntax
|
||||
}
|
||||
if len(ftdetect) >= 2 {
|
||||
header, err := regexp.Compile(ftdetect["header"].(string))
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
|
||||
r[1] = header
|
||||
}
|
||||
loaded++
|
||||
}
|
||||
|
||||
if loaded >= 2 {
|
||||
break
|
||||
}
|
||||
if fnameRgx != "" {
|
||||
header.FtDetect[0], err = regexp.Compile(fnameRgx)
|
||||
}
|
||||
if headerRgx != "" {
|
||||
header.FtDetect[1], err = regexp.Compile(headerRgx)
|
||||
}
|
||||
|
||||
if loaded == 0 {
|
||||
return r, errors.New("No detect regexes found")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, err
|
||||
return header, nil
|
||||
}
|
||||
|
||||
// MakeHeaderYaml takes a yaml spec for a syntax file and parses the
|
||||
// header
|
||||
func MakeHeaderYaml(data []byte) (*Header, error) {
|
||||
var hdrYaml HeaderYaml
|
||||
err := yaml.Unmarshal(data, &hdrYaml)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
header := new(Header)
|
||||
header.FileType = hdrYaml.FileType
|
||||
|
||||
if hdrYaml.Detect.FNameRgx != "" {
|
||||
header.FtDetect[0], err = regexp.Compile(hdrYaml.Detect.FNameRgx)
|
||||
}
|
||||
if hdrYaml.Detect.HeaderRgx != "" {
|
||||
header.FtDetect[1], err = regexp.Compile(hdrYaml.Detect.HeaderRgx)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return header, nil
|
||||
}
|
||||
|
||||
func ParseFile(input []byte) (f *File, err error) {
|
||||
@@ -196,6 +212,40 @@ func ParseDef(f *File, header *Header) (s *Def, err error) {
|
||||
return s, err
|
||||
}
|
||||
|
||||
// HasIncludes returns whether this syntax def has any include statements
|
||||
func HasIncludes(d *Def) bool {
|
||||
hasIncludes := len(d.rules.includes) > 0
|
||||
for _, r := range d.rules.regions {
|
||||
hasIncludes = hasIncludes || hasIncludesInRegion(r)
|
||||
}
|
||||
return hasIncludes
|
||||
}
|
||||
|
||||
func hasIncludesInRegion(region *region) bool {
|
||||
hasIncludes := len(region.rules.includes) > 0
|
||||
for _, r := range region.rules.regions {
|
||||
hasIncludes = hasIncludes || hasIncludesInRegion(r)
|
||||
}
|
||||
return hasIncludes
|
||||
}
|
||||
|
||||
// GetIncludes returns a list of filetypes that are included by this syntax def
|
||||
func GetIncludes(d *Def) []string {
|
||||
includes := d.rules.includes
|
||||
for _, r := range d.rules.regions {
|
||||
includes = append(includes, getIncludesInRegion(r)...)
|
||||
}
|
||||
return includes
|
||||
}
|
||||
|
||||
func getIncludesInRegion(region *region) []string {
|
||||
includes := region.rules.includes
|
||||
for _, r := range region.rules.regions {
|
||||
includes = append(includes, getIncludesInRegion(r)...)
|
||||
}
|
||||
return includes
|
||||
}
|
||||
|
||||
// ResolveIncludes will sort out the rules for including other filetypes
|
||||
// You should call this after parsing all the Defs
|
||||
func ResolveIncludes(def *Def, files []*File) {
|
||||
|
||||
85
pkg/highlight/unicode.go
Normal file
85
pkg/highlight/unicode.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package highlight
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var minMark = rune(unicode.Mark.R16[0].Lo)
|
||||
|
||||
func isMark(r rune) bool {
|
||||
// Fast path
|
||||
if r < minMark {
|
||||
return false
|
||||
}
|
||||
return unicode.In(r, unicode.Mark)
|
||||
}
|
||||
|
||||
// DecodeCharacter returns the next character from an array of bytes
|
||||
// A character is a rune along with any accompanying combining runes
|
||||
func DecodeCharacter(b []byte) (rune, []rune, int) {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
b = b[size:]
|
||||
c, s := utf8.DecodeRune(b)
|
||||
|
||||
var combc []rune
|
||||
for isMark(c) {
|
||||
combc = append(combc, c)
|
||||
size += s
|
||||
|
||||
b = b[s:]
|
||||
c, s = utf8.DecodeRune(b)
|
||||
}
|
||||
|
||||
return r, combc, size
|
||||
}
|
||||
|
||||
// DecodeCharacterInString returns the next character from a string
|
||||
// A character is a rune along with any accompanying combining runes
|
||||
func DecodeCharacterInString(str string) (rune, []rune, int) {
|
||||
r, size := utf8.DecodeRuneInString(str)
|
||||
str = str[size:]
|
||||
c, s := utf8.DecodeRuneInString(str)
|
||||
|
||||
var combc []rune
|
||||
for isMark(c) {
|
||||
combc = append(combc, c)
|
||||
size += s
|
||||
|
||||
str = str[s:]
|
||||
c, s = utf8.DecodeRuneInString(str)
|
||||
}
|
||||
|
||||
return r, combc, size
|
||||
}
|
||||
|
||||
// CharacterCount returns the number of characters in a byte array
|
||||
// Similar to utf8.RuneCount but for unicode characters
|
||||
func CharacterCount(b []byte) int {
|
||||
s := 0
|
||||
|
||||
for len(b) > 0 {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
if !isMark(r) {
|
||||
s++
|
||||
}
|
||||
|
||||
b = b[size:]
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// CharacterCount returns the number of characters in a string
|
||||
// Similar to utf8.RuneCountInString but for unicode characters
|
||||
func CharacterCountInString(str string) int {
|
||||
s := 0
|
||||
|
||||
for _, r := range str {
|
||||
if !isMark(r) {
|
||||
s++
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Yasuhiro Matsumoto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,49 +0,0 @@
|
||||
This is a modified version of `go-shellwords` for the micro editor.
|
||||
|
||||
# go-shellwords
|
||||
|
||||
[](https://coveralls.io/r/mattn/go-shellwords?branch=master)
|
||||
[](https://travis-ci.org/mattn/go-shellwords)
|
||||
|
||||
Parse line as shell words.
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
args, err := shellwords.Parse("./foo --bar=baz")
|
||||
// args should be ["./foo", "--bar=baz"]
|
||||
```
|
||||
|
||||
```go
|
||||
os.Setenv("FOO", "bar")
|
||||
p := shellwords.NewParser()
|
||||
p.ParseEnv = true
|
||||
args, err := p.Parse("./foo $FOO")
|
||||
// args should be ["./foo", "bar"]
|
||||
```
|
||||
|
||||
```go
|
||||
p := shellwords.NewParser()
|
||||
p.ParseBacktick = true
|
||||
args, err := p.Parse("./foo `echo $SHELL`")
|
||||
// args should be ["./foo", "/bin/bash"]
|
||||
```
|
||||
|
||||
```go
|
||||
shellwords.ParseBacktick = true
|
||||
p := shellwords.NewParser()
|
||||
args, err := p.Parse("./foo `echo $SHELL`")
|
||||
// args should be ["./foo", "/bin/bash"]
|
||||
```
|
||||
|
||||
# Thanks
|
||||
|
||||
This is based on cpan module [Parse::CommandLine](https://metacpan.org/pod/Parse::CommandLine).
|
||||
|
||||
# License
|
||||
|
||||
under the MIT License: http://mattn.mit-license.org/2017
|
||||
|
||||
# Author
|
||||
|
||||
Yasuhiro Matsumoto (a.k.a mattn)
|
||||
@@ -1,180 +0,0 @@
|
||||
package shellwords
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var envRe = regexp.MustCompile(`\$({[a-zA-Z0-9_]+}|[a-zA-Z0-9_]+)`)
|
||||
|
||||
func isSpace(r rune) bool {
|
||||
switch r {
|
||||
case ' ', '\t', '\r', '\n':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func replaceEnv(s string) string {
|
||||
return envRe.ReplaceAllStringFunc(s, func(s string) string {
|
||||
s = s[1:]
|
||||
if s[0] == '{' {
|
||||
s = s[1 : len(s)-1]
|
||||
}
|
||||
return os.Getenv(s)
|
||||
})
|
||||
}
|
||||
|
||||
type Parser struct {
|
||||
Position int
|
||||
}
|
||||
|
||||
func NewParser() *Parser {
|
||||
return &Parser{0}
|
||||
}
|
||||
|
||||
func (p *Parser) Parse(line string) ([]string, error) {
|
||||
args := []string{}
|
||||
buf := ""
|
||||
var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool
|
||||
backtick := ""
|
||||
|
||||
pos := -1
|
||||
got := false
|
||||
|
||||
loop:
|
||||
for i, r := range line {
|
||||
if escaped {
|
||||
buf += string(r)
|
||||
escaped = false
|
||||
continue
|
||||
}
|
||||
|
||||
if r == '\\' {
|
||||
if singleQuoted {
|
||||
buf += string(r)
|
||||
} else {
|
||||
escaped = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if isSpace(r) {
|
||||
if singleQuoted || doubleQuoted || backQuote || dollarQuote {
|
||||
buf += string(r)
|
||||
backtick += string(r)
|
||||
} else if got {
|
||||
buf = replaceEnv(buf)
|
||||
args = append(args, buf)
|
||||
buf = ""
|
||||
got = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch r {
|
||||
case '`':
|
||||
if !singleQuoted && !doubleQuoted && !dollarQuote {
|
||||
if backQuote {
|
||||
out, err := shellRun(backtick)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = out
|
||||
}
|
||||
backtick = ""
|
||||
backQuote = !backQuote
|
||||
continue
|
||||
backtick = ""
|
||||
backQuote = !backQuote
|
||||
}
|
||||
case ')':
|
||||
if !singleQuoted && !doubleQuoted && !backQuote {
|
||||
if dollarQuote {
|
||||
out, err := shellRun(backtick)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = out
|
||||
}
|
||||
backtick = ""
|
||||
dollarQuote = !dollarQuote
|
||||
continue
|
||||
backtick = ""
|
||||
dollarQuote = !dollarQuote
|
||||
}
|
||||
case '(':
|
||||
if !singleQuoted && !doubleQuoted && !backQuote {
|
||||
if !dollarQuote && len(buf) > 0 && buf == "$" {
|
||||
dollarQuote = true
|
||||
buf += "("
|
||||
continue
|
||||
} else {
|
||||
return nil, errors.New("invalid command line string")
|
||||
}
|
||||
}
|
||||
case '"':
|
||||
if !singleQuoted && !dollarQuote {
|
||||
doubleQuoted = !doubleQuoted
|
||||
continue
|
||||
}
|
||||
case '\'':
|
||||
if !doubleQuoted && !dollarQuote {
|
||||
singleQuoted = !singleQuoted
|
||||
continue
|
||||
}
|
||||
case ';', '&', '|', '<', '>':
|
||||
if !(escaped || singleQuoted || doubleQuoted || backQuote) {
|
||||
pos = i
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
got = true
|
||||
buf += string(r)
|
||||
if backQuote || dollarQuote {
|
||||
backtick += string(r)
|
||||
}
|
||||
}
|
||||
|
||||
buf = replaceEnv(buf)
|
||||
args = append(args, buf)
|
||||
|
||||
if escaped || singleQuoted || doubleQuoted || backQuote || dollarQuote {
|
||||
return nil, errors.New("invalid command line string")
|
||||
}
|
||||
|
||||
p.Position = pos
|
||||
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func Split(line string) ([]string, error) {
|
||||
return NewParser().Parse(line)
|
||||
}
|
||||
|
||||
func Join(args ...string) string {
|
||||
var buf bytes.Buffer
|
||||
for i, w := range args {
|
||||
if i != 0 {
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
if w == "" {
|
||||
buf.WriteString("''")
|
||||
continue
|
||||
}
|
||||
|
||||
for _, b := range w {
|
||||
switch b {
|
||||
case ' ', '\t', '\r', '\n':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteString(string(b))
|
||||
default:
|
||||
buf.WriteString(string(b))
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package shellwords
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func shellRun(line string) (string, error) {
|
||||
shell := os.Getenv("SHELL")
|
||||
b, err := exec.Command(shell, "-c", line).Output()
|
||||
if err != nil {
|
||||
if eerr, ok := err.(*exec.ExitError); ok {
|
||||
b = eerr.Stderr
|
||||
}
|
||||
return "", errors.New(err.Error() + ":" + string(b))
|
||||
}
|
||||
return strings.TrimSpace(string(b)), nil
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package shellwords
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func shellRun(line string) (string, error) {
|
||||
shell := os.Getenv("COMSPEC")
|
||||
b, err := exec.Command(shell, "/c", line).Output()
|
||||
if err != nil {
|
||||
if eerr, ok := err.(*exec.ExitError); ok {
|
||||
b = eerr.Stderr
|
||||
}
|
||||
return "", errors.New(err.Error() + ":" + string(b))
|
||||
}
|
||||
return strings.TrimSpace(string(b)), nil
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# Terminfo parser
|
||||
|
||||
This terminfo parser was written by the authors of [tcell](https://github.com/gdamore/tcell). We are using it here
|
||||
to compile the terminal database if the terminal entry is not found in set of precompiled terminals.
|
||||
|
||||
The source for `mkinfo.go` is adapted from tcell's `mkinfo` tool to be more of a library.
|
||||
@@ -1,518 +0,0 @@
|
||||
// Copyright 2017 The TCell Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use file except in compliance with the License.
|
||||
// You may obtain a copy of the license at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This command is used to generate suitable configuration files in either
|
||||
// go syntax or in JSON. It defaults to JSON output on stdout. If no
|
||||
// term values are specified on the command line, then $TERM is used.
|
||||
//
|
||||
// Usage is like this:
|
||||
//
|
||||
// mkinfo [-init] [-go file.go] [-json file.json] [-quiet] [-nofatal] [<term>...]
|
||||
//
|
||||
// -gzip specifies output should be compressed (json only)
|
||||
// -go specifies Go output into the named file. Use - for stdout.
|
||||
// -json specifies JSON output in the named file. Use - for stdout
|
||||
// -nofatal indicates that errors loading definitions should not be fatal
|
||||
//
|
||||
|
||||
package terminfo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type termcap struct {
|
||||
name string
|
||||
desc string
|
||||
aliases []string
|
||||
bools map[string]bool
|
||||
nums map[string]int
|
||||
strs map[string]string
|
||||
}
|
||||
|
||||
func (tc *termcap) getnum(s string) int {
|
||||
return (tc.nums[s])
|
||||
}
|
||||
|
||||
func (tc *termcap) getflag(s string) bool {
|
||||
return (tc.bools[s])
|
||||
}
|
||||
|
||||
func (tc *termcap) getstr(s string) string {
|
||||
return (tc.strs[s])
|
||||
}
|
||||
|
||||
const (
|
||||
NONE = iota
|
||||
CTRL
|
||||
ESC
|
||||
)
|
||||
|
||||
func unescape(s string) string {
|
||||
// Various escapes are in \x format. Control codes are
|
||||
// encoded as ^M (carat followed by ASCII equivalent).
|
||||
// Escapes are: \e, \E - escape
|
||||
// \0 NULL, \n \l \r \t \b \f \s for equivalent C escape.
|
||||
buf := &bytes.Buffer{}
|
||||
esc := NONE
|
||||
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
switch esc {
|
||||
case NONE:
|
||||
switch c {
|
||||
case '\\':
|
||||
esc = ESC
|
||||
case '^':
|
||||
esc = CTRL
|
||||
default:
|
||||
buf.WriteByte(c)
|
||||
}
|
||||
case CTRL:
|
||||
buf.WriteByte(c - 0x40)
|
||||
esc = NONE
|
||||
case ESC:
|
||||
switch c {
|
||||
case 'E', 'e':
|
||||
buf.WriteByte(0x1b)
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||
if i+2 < len(s) && s[i+1] >= '0' && s[i+1] <= '7' && s[i+2] >= '0' && s[i+2] <= '7' {
|
||||
buf.WriteByte(((c - '0') * 64) + ((s[i+1] - '0') * 8) + (s[i+2] - '0'))
|
||||
i = i + 2
|
||||
} else if c == '0' {
|
||||
buf.WriteByte(0)
|
||||
}
|
||||
case 'n':
|
||||
buf.WriteByte('\n')
|
||||
case 'r':
|
||||
buf.WriteByte('\r')
|
||||
case 't':
|
||||
buf.WriteByte('\t')
|
||||
case 'b':
|
||||
buf.WriteByte('\b')
|
||||
case 'f':
|
||||
buf.WriteByte('\f')
|
||||
case 's':
|
||||
buf.WriteByte(' ')
|
||||
case 'l':
|
||||
panic("WTF: weird format: " + s)
|
||||
default:
|
||||
buf.WriteByte(c)
|
||||
}
|
||||
esc = NONE
|
||||
}
|
||||
}
|
||||
return (buf.String())
|
||||
}
|
||||
|
||||
func (tc *termcap) setupterm(name string) error {
|
||||
cmd := exec.Command("infocmp", "-1", name)
|
||||
output := &bytes.Buffer{}
|
||||
cmd.Stdout = output
|
||||
|
||||
tc.strs = make(map[string]string)
|
||||
tc.bools = make(map[string]bool)
|
||||
tc.nums = make(map[string]int)
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now parse the output.
|
||||
// We get comment lines (starting with "#"), followed by
|
||||
// a header line that looks like "<name>|<alias>|...|<desc>"
|
||||
// then capabilities, one per line, starting with a tab and ending
|
||||
// with a comma and newline.
|
||||
lines := strings.Split(output.String(), "\n")
|
||||
for len(lines) > 0 && strings.HasPrefix(lines[0], "#") {
|
||||
lines = lines[1:]
|
||||
}
|
||||
|
||||
// Ditch trailing empty last line
|
||||
if lines[len(lines)-1] == "" {
|
||||
lines = lines[:len(lines)-1]
|
||||
}
|
||||
header := lines[0]
|
||||
if strings.HasSuffix(header, ",") {
|
||||
header = header[:len(header)-1]
|
||||
}
|
||||
names := strings.Split(header, "|")
|
||||
tc.name = names[0]
|
||||
names = names[1:]
|
||||
if len(names) > 0 {
|
||||
tc.desc = names[len(names)-1]
|
||||
names = names[:len(names)-1]
|
||||
}
|
||||
tc.aliases = names
|
||||
for _, val := range lines[1:] {
|
||||
if (!strings.HasPrefix(val, "\t")) ||
|
||||
(!strings.HasSuffix(val, ",")) {
|
||||
return (errors.New("malformed infocmp: " + val))
|
||||
}
|
||||
|
||||
val = val[1:]
|
||||
val = val[:len(val)-1]
|
||||
|
||||
if k := strings.SplitN(val, "=", 2); len(k) == 2 {
|
||||
tc.strs[k[0]] = unescape(k[1])
|
||||
} else if k := strings.SplitN(val, "#", 2); len(k) == 2 {
|
||||
if strings.HasPrefix(k[1], "0x") {
|
||||
if u, err := strconv.ParseUint(k[1][2:], 16, 0); err != nil {
|
||||
return (err)
|
||||
} else {
|
||||
tc.nums[k[0]] = int(u)
|
||||
}
|
||||
} else if u, err := strconv.ParseUint(k[1], 10, 0); err != nil {
|
||||
return (err)
|
||||
} else {
|
||||
tc.nums[k[0]] = int(u)
|
||||
}
|
||||
} else {
|
||||
tc.bools[val] = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// This program is used to collect data from the system's terminfo library,
|
||||
// and write it into Go source code. That is, we maintain our terminfo
|
||||
// capabilities encoded in the program. It should never need to be run by
|
||||
// an end user, but developers can use this to add codes for additional
|
||||
// terminal types.
|
||||
//
|
||||
// If a terminal name ending with -truecolor is given, and we cannot find
|
||||
// one, we will try to fabricate one from either the -256color (if present)
|
||||
// or the unadorned base name, adding the XTerm specific 24-bit color
|
||||
// escapes. We believe that all 24-bit capable terminals use the same
|
||||
// escape sequences, and terminfo has yet to evolve to support this.
|
||||
func getinfo(name string) (*Terminfo, string, error) {
|
||||
var tc termcap
|
||||
addTrueColor := false
|
||||
if err := tc.setupterm(name); err != nil {
|
||||
if strings.HasSuffix(name, "-truecolor") {
|
||||
base := name[:len(name)-len("-truecolor")]
|
||||
// Probably -256color is closest to what we want
|
||||
if err = tc.setupterm(base + "-256color"); err != nil {
|
||||
err = tc.setupterm(base)
|
||||
}
|
||||
if err == nil {
|
||||
addTrueColor = true
|
||||
}
|
||||
tc.name = name
|
||||
}
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
t := &Terminfo{}
|
||||
// If this is an alias record, then just emit the alias
|
||||
t.Name = tc.name
|
||||
if t.Name != name {
|
||||
return t, "", nil
|
||||
}
|
||||
t.Aliases = tc.aliases
|
||||
t.Colors = tc.getnum("colors")
|
||||
t.Columns = tc.getnum("cols")
|
||||
t.Lines = tc.getnum("lines")
|
||||
t.Bell = tc.getstr("bel")
|
||||
t.Clear = tc.getstr("clear")
|
||||
t.EnterCA = tc.getstr("smcup")
|
||||
t.ExitCA = tc.getstr("rmcup")
|
||||
t.ShowCursor = tc.getstr("cnorm")
|
||||
t.HideCursor = tc.getstr("civis")
|
||||
t.AttrOff = tc.getstr("sgr0")
|
||||
t.Underline = tc.getstr("smul")
|
||||
t.Bold = tc.getstr("bold")
|
||||
t.Blink = tc.getstr("blink")
|
||||
t.Dim = tc.getstr("dim")
|
||||
t.Reverse = tc.getstr("rev")
|
||||
t.EnterKeypad = tc.getstr("smkx")
|
||||
t.ExitKeypad = tc.getstr("rmkx")
|
||||
t.SetFg = tc.getstr("setaf")
|
||||
t.SetBg = tc.getstr("setab")
|
||||
t.SetCursor = tc.getstr("cup")
|
||||
t.CursorBack1 = tc.getstr("cub1")
|
||||
t.CursorUp1 = tc.getstr("cuu1")
|
||||
t.KeyF1 = tc.getstr("kf1")
|
||||
t.KeyF2 = tc.getstr("kf2")
|
||||
t.KeyF3 = tc.getstr("kf3")
|
||||
t.KeyF4 = tc.getstr("kf4")
|
||||
t.KeyF5 = tc.getstr("kf5")
|
||||
t.KeyF6 = tc.getstr("kf6")
|
||||
t.KeyF7 = tc.getstr("kf7")
|
||||
t.KeyF8 = tc.getstr("kf8")
|
||||
t.KeyF9 = tc.getstr("kf9")
|
||||
t.KeyF10 = tc.getstr("kf10")
|
||||
t.KeyF11 = tc.getstr("kf11")
|
||||
t.KeyF12 = tc.getstr("kf12")
|
||||
t.KeyF13 = tc.getstr("kf13")
|
||||
t.KeyF14 = tc.getstr("kf14")
|
||||
t.KeyF15 = tc.getstr("kf15")
|
||||
t.KeyF16 = tc.getstr("kf16")
|
||||
t.KeyF17 = tc.getstr("kf17")
|
||||
t.KeyF18 = tc.getstr("kf18")
|
||||
t.KeyF19 = tc.getstr("kf19")
|
||||
t.KeyF20 = tc.getstr("kf20")
|
||||
t.KeyF21 = tc.getstr("kf21")
|
||||
t.KeyF22 = tc.getstr("kf22")
|
||||
t.KeyF23 = tc.getstr("kf23")
|
||||
t.KeyF24 = tc.getstr("kf24")
|
||||
t.KeyF25 = tc.getstr("kf25")
|
||||
t.KeyF26 = tc.getstr("kf26")
|
||||
t.KeyF27 = tc.getstr("kf27")
|
||||
t.KeyF28 = tc.getstr("kf28")
|
||||
t.KeyF29 = tc.getstr("kf29")
|
||||
t.KeyF30 = tc.getstr("kf30")
|
||||
t.KeyF31 = tc.getstr("kf31")
|
||||
t.KeyF32 = tc.getstr("kf32")
|
||||
t.KeyF33 = tc.getstr("kf33")
|
||||
t.KeyF34 = tc.getstr("kf34")
|
||||
t.KeyF35 = tc.getstr("kf35")
|
||||
t.KeyF36 = tc.getstr("kf36")
|
||||
t.KeyF37 = tc.getstr("kf37")
|
||||
t.KeyF38 = tc.getstr("kf38")
|
||||
t.KeyF39 = tc.getstr("kf39")
|
||||
t.KeyF40 = tc.getstr("kf40")
|
||||
t.KeyF41 = tc.getstr("kf41")
|
||||
t.KeyF42 = tc.getstr("kf42")
|
||||
t.KeyF43 = tc.getstr("kf43")
|
||||
t.KeyF44 = tc.getstr("kf44")
|
||||
t.KeyF45 = tc.getstr("kf45")
|
||||
t.KeyF46 = tc.getstr("kf46")
|
||||
t.KeyF47 = tc.getstr("kf47")
|
||||
t.KeyF48 = tc.getstr("kf48")
|
||||
t.KeyF49 = tc.getstr("kf49")
|
||||
t.KeyF50 = tc.getstr("kf50")
|
||||
t.KeyF51 = tc.getstr("kf51")
|
||||
t.KeyF52 = tc.getstr("kf52")
|
||||
t.KeyF53 = tc.getstr("kf53")
|
||||
t.KeyF54 = tc.getstr("kf54")
|
||||
t.KeyF55 = tc.getstr("kf55")
|
||||
t.KeyF56 = tc.getstr("kf56")
|
||||
t.KeyF57 = tc.getstr("kf57")
|
||||
t.KeyF58 = tc.getstr("kf58")
|
||||
t.KeyF59 = tc.getstr("kf59")
|
||||
t.KeyF60 = tc.getstr("kf60")
|
||||
t.KeyF61 = tc.getstr("kf61")
|
||||
t.KeyF62 = tc.getstr("kf62")
|
||||
t.KeyF63 = tc.getstr("kf63")
|
||||
t.KeyF64 = tc.getstr("kf64")
|
||||
t.KeyInsert = tc.getstr("kich1")
|
||||
t.KeyDelete = tc.getstr("kdch1")
|
||||
t.KeyBackspace = tc.getstr("kbs")
|
||||
t.KeyHome = tc.getstr("khome")
|
||||
t.KeyEnd = tc.getstr("kend")
|
||||
t.KeyUp = tc.getstr("kcuu1")
|
||||
t.KeyDown = tc.getstr("kcud1")
|
||||
t.KeyRight = tc.getstr("kcuf1")
|
||||
t.KeyLeft = tc.getstr("kcub1")
|
||||
t.KeyPgDn = tc.getstr("knp")
|
||||
t.KeyPgUp = tc.getstr("kpp")
|
||||
t.KeyBacktab = tc.getstr("kcbt")
|
||||
t.KeyExit = tc.getstr("kext")
|
||||
t.KeyCancel = tc.getstr("kcan")
|
||||
t.KeyPrint = tc.getstr("kprt")
|
||||
t.KeyHelp = tc.getstr("khlp")
|
||||
t.KeyClear = tc.getstr("kclr")
|
||||
t.AltChars = tc.getstr("acsc")
|
||||
t.EnterAcs = tc.getstr("smacs")
|
||||
t.ExitAcs = tc.getstr("rmacs")
|
||||
t.EnableAcs = tc.getstr("enacs")
|
||||
t.Mouse = tc.getstr("kmous")
|
||||
t.KeyShfRight = tc.getstr("kRIT")
|
||||
t.KeyShfLeft = tc.getstr("kLFT")
|
||||
t.KeyShfHome = tc.getstr("kHOM")
|
||||
t.KeyShfEnd = tc.getstr("kEND")
|
||||
|
||||
// Terminfo lacks descriptions for a bunch of modified keys,
|
||||
// but modern XTerm and emulators often have them. Let's add them,
|
||||
// if the shifted right and left arrows are defined.
|
||||
if t.KeyShfRight == "\x1b[1;2C" && t.KeyShfLeft == "\x1b[1;2D" {
|
||||
t.KeyShfUp = "\x1b[1;2A"
|
||||
t.KeyShfDown = "\x1b[1;2B"
|
||||
t.KeyMetaUp = "\x1b[1;9A"
|
||||
t.KeyMetaDown = "\x1b[1;9B"
|
||||
t.KeyMetaRight = "\x1b[1;9C"
|
||||
t.KeyMetaLeft = "\x1b[1;9D"
|
||||
t.KeyAltUp = "\x1b[1;3A"
|
||||
t.KeyAltDown = "\x1b[1;3B"
|
||||
t.KeyAltRight = "\x1b[1;3C"
|
||||
t.KeyAltLeft = "\x1b[1;3D"
|
||||
t.KeyCtrlUp = "\x1b[1;5A"
|
||||
t.KeyCtrlDown = "\x1b[1;5B"
|
||||
t.KeyCtrlRight = "\x1b[1;5C"
|
||||
t.KeyCtrlLeft = "\x1b[1;5D"
|
||||
t.KeyAltShfUp = "\x1b[1;4A"
|
||||
t.KeyAltShfDown = "\x1b[1;4B"
|
||||
t.KeyAltShfRight = "\x1b[1;4C"
|
||||
t.KeyAltShfLeft = "\x1b[1;4D"
|
||||
|
||||
t.KeyMetaShfUp = "\x1b[1;10A"
|
||||
t.KeyMetaShfDown = "\x1b[1;10B"
|
||||
t.KeyMetaShfRight = "\x1b[1;10C"
|
||||
t.KeyMetaShfLeft = "\x1b[1;10D"
|
||||
|
||||
t.KeyCtrlShfUp = "\x1b[1;6A"
|
||||
t.KeyCtrlShfDown = "\x1b[1;6B"
|
||||
t.KeyCtrlShfRight = "\x1b[1;6C"
|
||||
t.KeyCtrlShfLeft = "\x1b[1;6D"
|
||||
}
|
||||
// And also for Home and End
|
||||
if t.KeyShfHome == "\x1b[1;2H" && t.KeyShfEnd == "\x1b[1;2F" {
|
||||
t.KeyCtrlHome = "\x1b[1;5H"
|
||||
t.KeyCtrlEnd = "\x1b[1;5F"
|
||||
t.KeyAltHome = "\x1b[1;9H"
|
||||
t.KeyAltEnd = "\x1b[1;9F"
|
||||
t.KeyCtrlShfHome = "\x1b[1;6H"
|
||||
t.KeyCtrlShfEnd = "\x1b[1;6F"
|
||||
t.KeyAltShfHome = "\x1b[1;4H"
|
||||
t.KeyAltShfEnd = "\x1b[1;4F"
|
||||
t.KeyMetaShfHome = "\x1b[1;10H"
|
||||
t.KeyMetaShfEnd = "\x1b[1;10F"
|
||||
}
|
||||
|
||||
// And the same thing for rxvt and workalikes (Eterm, aterm, etc.)
|
||||
// It seems that urxvt at least send ESC as ALT prefix for these,
|
||||
// although some places seem to indicate a separate ALT key sesquence.
|
||||
if t.KeyShfRight == "\x1b[c" && t.KeyShfLeft == "\x1b[d" {
|
||||
t.KeyShfUp = "\x1b[a"
|
||||
t.KeyShfDown = "\x1b[b"
|
||||
t.KeyCtrlUp = "\x1b[Oa"
|
||||
t.KeyCtrlDown = "\x1b[Ob"
|
||||
t.KeyCtrlRight = "\x1b[Oc"
|
||||
t.KeyCtrlLeft = "\x1b[Od"
|
||||
}
|
||||
if t.KeyShfHome == "\x1b[7$" && t.KeyShfEnd == "\x1b[8$" {
|
||||
t.KeyCtrlHome = "\x1b[7^"
|
||||
t.KeyCtrlEnd = "\x1b[8^"
|
||||
}
|
||||
|
||||
// If the kmous entry is present, then we need to record the
|
||||
// the codes to enter and exit mouse mode. Sadly, this is not
|
||||
// part of the terminfo databases anywhere that I've found, but
|
||||
// is an extension. The escape codes are documented in the XTerm
|
||||
// manual, and all terminals that have kmous are expected to
|
||||
// use these same codes, unless explicitly configured otherwise
|
||||
// vi XM. Note that in any event, we only known how to parse either
|
||||
// x11 or SGR mouse events -- if your terminal doesn't support one
|
||||
// of these two forms, you maybe out of luck.
|
||||
t.MouseMode = tc.getstr("XM")
|
||||
if t.Mouse != "" && t.MouseMode == "" {
|
||||
// we anticipate that all xterm mouse tracking compatible
|
||||
// terminals understand mouse tracking (1000), but we hope
|
||||
// that those that don't understand any-event tracking (1003)
|
||||
// will at least ignore it. Likewise we hope that terminals
|
||||
// that don't understand SGR reporting (1006) just ignore it.
|
||||
t.MouseMode = "%?%p1%{1}%=%t%'h'%Pa%e%'l'%Pa%;" +
|
||||
"\x1b[?1000%ga%c\x1b[?1002%ga%c\x1b[?1003%ga%c\x1b[?1006%ga%c"
|
||||
}
|
||||
|
||||
// We only support colors in ANSI 8 or 256 color mode.
|
||||
if t.Colors < 8 || t.SetFg == "" {
|
||||
t.Colors = 0
|
||||
}
|
||||
if t.SetCursor == "" {
|
||||
return nil, "", errors.New("terminal not cursor addressable")
|
||||
}
|
||||
|
||||
// For padding, we lookup the pad char. If that isn't present,
|
||||
// and npc is *not* set, then we assume a null byte.
|
||||
t.PadChar = tc.getstr("pad")
|
||||
if t.PadChar == "" {
|
||||
if !tc.getflag("npc") {
|
||||
t.PadChar = "\u0000"
|
||||
}
|
||||
}
|
||||
|
||||
// For some terminals we fabricate a -truecolor entry, that may
|
||||
// not exist in terminfo.
|
||||
if addTrueColor {
|
||||
t.SetFgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%dm"
|
||||
t.SetBgRGB = "\x1b[48;2;%p1%d;%p2%d;%p3%dm"
|
||||
t.SetFgBgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%d;" +
|
||||
"48;2;%p4%d;%p5%d;%p6%dm"
|
||||
}
|
||||
|
||||
// For terminals that use "standard" SGR sequences, lets combine the
|
||||
// foreground and background together.
|
||||
if strings.HasPrefix(t.SetFg, "\x1b[") &&
|
||||
strings.HasPrefix(t.SetBg, "\x1b[") &&
|
||||
strings.HasSuffix(t.SetFg, "m") &&
|
||||
strings.HasSuffix(t.SetBg, "m") {
|
||||
fg := t.SetFg[:len(t.SetFg)-1]
|
||||
r := regexp.MustCompile("%p1")
|
||||
bg := r.ReplaceAllString(t.SetBg[2:], "%p2")
|
||||
t.SetFgBg = fg + ";" + bg
|
||||
}
|
||||
|
||||
return t, tc.desc, nil
|
||||
}
|
||||
|
||||
func WriteDB(filename string) error {
|
||||
var e error
|
||||
js := []byte{}
|
||||
args := []string{os.Getenv("TERM")}
|
||||
|
||||
tdata := make(map[string]*Terminfo)
|
||||
descs := make(map[string]string)
|
||||
|
||||
for _, term := range args {
|
||||
if t, desc, e := getinfo(term); e != nil {
|
||||
return e
|
||||
} else {
|
||||
tdata[term] = t
|
||||
descs[term] = desc
|
||||
}
|
||||
}
|
||||
|
||||
if len(tdata) == 0 {
|
||||
// No data.
|
||||
return errors.New("No data")
|
||||
}
|
||||
o := os.Stdout
|
||||
if o, e = os.Create(filename); e != nil {
|
||||
return e
|
||||
}
|
||||
var w io.WriteCloser
|
||||
w = o
|
||||
for _, term := range args {
|
||||
if t := tdata[term]; t != nil {
|
||||
js, e = json.Marshal(t)
|
||||
fmt.Fprintln(w, string(js))
|
||||
}
|
||||
// arguably if there is more than one term, this
|
||||
// should be a javascript array, but that's not how
|
||||
// we load it. We marshal objects one at a time from
|
||||
// the file.
|
||||
}
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
w.Close()
|
||||
if w != o {
|
||||
o.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,838 +0,0 @@
|
||||
// Copyright 2017 The TCell Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use file except in compliance with the License.
|
||||
// You may obtain a copy of the license at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package terminfo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrTermNotFound indicates that a suitable terminal entry could
|
||||
// not be found. This can result from either not having TERM set,
|
||||
// or from the TERM failing to support certain minimal functionality,
|
||||
// in particular absolute cursor addressability (the cup capability)
|
||||
// is required. For example, legacy "adm3" lacks this capability,
|
||||
// whereas the slightly newer "adm3a" supports it. This failure
|
||||
// occurs most often with "dumb".
|
||||
ErrTermNotFound = errors.New("terminal entry not found")
|
||||
)
|
||||
|
||||
// Terminfo represents a terminfo entry. Note that we use friendly names
|
||||
// in Go, but when we write out JSON, we use the same names as terminfo.
|
||||
// The name, aliases and smous, rmous fields do not come from terminfo directly.
|
||||
type Terminfo struct {
|
||||
Name string `json:"name"`
|
||||
Aliases []string `json:"aliases,omitempty"`
|
||||
Columns int `json:"cols,omitempty"` // cols
|
||||
Lines int `json:"lines,omitempty"` // lines
|
||||
Colors int `json:"colors,omitempty"` // colors
|
||||
Bell string `json:"bell,omitempty"` // bell
|
||||
Clear string `json:"clear,omitempty"` // clear
|
||||
EnterCA string `json:"smcup,omitempty"` // smcup
|
||||
ExitCA string `json:"rmcup,omitempty"` // rmcup
|
||||
ShowCursor string `json:"cnorm,omitempty"` // cnorm
|
||||
HideCursor string `json:"civis,omitempty"` // civis
|
||||
AttrOff string `json:"sgr0,omitempty"` // sgr0
|
||||
Underline string `json:"smul,omitempty"` // smul
|
||||
Bold string `json:"bold,omitempty"` // bold
|
||||
Blink string `json:"blink,omitempty"` // blink
|
||||
Reverse string `json:"rev,omitempty"` // rev
|
||||
Dim string `json:"dim,omitempty"` // dim
|
||||
EnterKeypad string `json:"smkx,omitempty"` // smkx
|
||||
ExitKeypad string `json:"rmkx,omitempty"` // rmkx
|
||||
SetFg string `json:"setaf,omitempty"` // setaf
|
||||
SetBg string `json:"setbg,omitempty"` // setab
|
||||
SetCursor string `json:"cup,omitempty"` // cup
|
||||
CursorBack1 string `json:"cub1,omitempty"` // cub1
|
||||
CursorUp1 string `json:"cuu1,omitempty"` // cuu1
|
||||
PadChar string `json:"pad,omitempty"` // pad
|
||||
KeyBackspace string `json:"kbs,omitempty"` // kbs
|
||||
KeyF1 string `json:"kf1,omitempty"` // kf1
|
||||
KeyF2 string `json:"kf2,omitempty"` // kf2
|
||||
KeyF3 string `json:"kf3,omitempty"` // kf3
|
||||
KeyF4 string `json:"kf4,omitempty"` // kf4
|
||||
KeyF5 string `json:"kf5,omitempty"` // kf5
|
||||
KeyF6 string `json:"kf6,omitempty"` // kf6
|
||||
KeyF7 string `json:"kf7,omitempty"` // kf7
|
||||
KeyF8 string `json:"kf8,omitempty"` // kf8
|
||||
KeyF9 string `json:"kf9,omitempty"` // kf9
|
||||
KeyF10 string `json:"kf10,omitempty"` // kf10
|
||||
KeyF11 string `json:"kf11,omitempty"` // kf11
|
||||
KeyF12 string `json:"kf12,omitempty"` // kf12
|
||||
KeyF13 string `json:"kf13,omitempty"` // kf13
|
||||
KeyF14 string `json:"kf14,omitempty"` // kf14
|
||||
KeyF15 string `json:"kf15,omitempty"` // kf15
|
||||
KeyF16 string `json:"kf16,omitempty"` // kf16
|
||||
KeyF17 string `json:"kf17,omitempty"` // kf17
|
||||
KeyF18 string `json:"kf18,omitempty"` // kf18
|
||||
KeyF19 string `json:"kf19,omitempty"` // kf19
|
||||
KeyF20 string `json:"kf20,omitempty"` // kf20
|
||||
KeyF21 string `json:"kf21,omitempty"` // kf21
|
||||
KeyF22 string `json:"kf22,omitempty"` // kf22
|
||||
KeyF23 string `json:"kf23,omitempty"` // kf23
|
||||
KeyF24 string `json:"kf24,omitempty"` // kf24
|
||||
KeyF25 string `json:"kf25,omitempty"` // kf25
|
||||
KeyF26 string `json:"kf26,omitempty"` // kf26
|
||||
KeyF27 string `json:"kf27,omitempty"` // kf27
|
||||
KeyF28 string `json:"kf28,omitempty"` // kf28
|
||||
KeyF29 string `json:"kf29,omitempty"` // kf29
|
||||
KeyF30 string `json:"kf30,omitempty"` // kf30
|
||||
KeyF31 string `json:"kf31,omitempty"` // kf31
|
||||
KeyF32 string `json:"kf32,omitempty"` // kf32
|
||||
KeyF33 string `json:"kf33,omitempty"` // kf33
|
||||
KeyF34 string `json:"kf34,omitempty"` // kf34
|
||||
KeyF35 string `json:"kf35,omitempty"` // kf35
|
||||
KeyF36 string `json:"kf36,omitempty"` // kf36
|
||||
KeyF37 string `json:"kf37,omitempty"` // kf37
|
||||
KeyF38 string `json:"kf38,omitempty"` // kf38
|
||||
KeyF39 string `json:"kf39,omitempty"` // kf39
|
||||
KeyF40 string `json:"kf40,omitempty"` // kf40
|
||||
KeyF41 string `json:"kf41,omitempty"` // kf41
|
||||
KeyF42 string `json:"kf42,omitempty"` // kf42
|
||||
KeyF43 string `json:"kf43,omitempty"` // kf43
|
||||
KeyF44 string `json:"kf44,omitempty"` // kf44
|
||||
KeyF45 string `json:"kf45,omitempty"` // kf45
|
||||
KeyF46 string `json:"kf46,omitempty"` // kf46
|
||||
KeyF47 string `json:"kf47,omitempty"` // kf47
|
||||
KeyF48 string `json:"kf48,omitempty"` // kf48
|
||||
KeyF49 string `json:"kf49,omitempty"` // kf49
|
||||
KeyF50 string `json:"kf50,omitempty"` // kf50
|
||||
KeyF51 string `json:"kf51,omitempty"` // kf51
|
||||
KeyF52 string `json:"kf52,omitempty"` // kf52
|
||||
KeyF53 string `json:"kf53,omitempty"` // kf53
|
||||
KeyF54 string `json:"kf54,omitempty"` // kf54
|
||||
KeyF55 string `json:"kf55,omitempty"` // kf55
|
||||
KeyF56 string `json:"kf56,omitempty"` // kf56
|
||||
KeyF57 string `json:"kf57,omitempty"` // kf57
|
||||
KeyF58 string `json:"kf58,omitempty"` // kf58
|
||||
KeyF59 string `json:"kf59,omitempty"` // kf59
|
||||
KeyF60 string `json:"kf60,omitempty"` // kf60
|
||||
KeyF61 string `json:"kf61,omitempty"` // kf61
|
||||
KeyF62 string `json:"kf62,omitempty"` // kf62
|
||||
KeyF63 string `json:"kf63,omitempty"` // kf63
|
||||
KeyF64 string `json:"kf64,omitempty"` // kf64
|
||||
KeyInsert string `json:"kich,omitempty"` // kich1
|
||||
KeyDelete string `json:"kdch,omitempty"` // kdch1
|
||||
KeyHome string `json:"khome,omitempty"` // khome
|
||||
KeyEnd string `json:"kend,omitempty"` // kend
|
||||
KeyHelp string `json:"khlp,omitempty"` // khlp
|
||||
KeyPgUp string `json:"kpp,omitempty"` // kpp
|
||||
KeyPgDn string `json:"knp,omitempty"` // knp
|
||||
KeyUp string `json:"kcuu1,omitempty"` // kcuu1
|
||||
KeyDown string `json:"kcud1,omitempty"` // kcud1
|
||||
KeyLeft string `json:"kcub1,omitempty"` // kcub1
|
||||
KeyRight string `json:"kcuf1,omitempty"` // kcuf1
|
||||
KeyBacktab string `json:"kcbt,omitempty"` // kcbt
|
||||
KeyExit string `json:"kext,omitempty"` // kext
|
||||
KeyClear string `json:"kclr,omitempty"` // kclr
|
||||
KeyPrint string `json:"kprt,omitempty"` // kprt
|
||||
KeyCancel string `json:"kcan,omitempty"` // kcan
|
||||
Mouse string `json:"kmous,omitempty"` // kmous
|
||||
MouseMode string `json:"XM,omitempty"` // XM
|
||||
AltChars string `json:"acsc,omitempty"` // acsc
|
||||
EnterAcs string `json:"smacs,omitempty"` // smacs
|
||||
ExitAcs string `json:"rmacs,omitempty"` // rmacs
|
||||
EnableAcs string `json:"enacs,omitempty"` // enacs
|
||||
KeyShfRight string `json:"kRIT,omitempty"` // kRIT
|
||||
KeyShfLeft string `json:"kLFT,omitempty"` // kLFT
|
||||
KeyShfHome string `json:"kHOM,omitempty"` // kHOM
|
||||
KeyShfEnd string `json:"kEND,omitempty"` // kEND
|
||||
|
||||
// These are non-standard extensions to terminfo. This includes
|
||||
// true color support, and some additional keys. Its kind of bizarre
|
||||
// that shifted variants of left and right exist, but not up and down.
|
||||
// Terminal support for these are going to vary amongst XTerm
|
||||
// emulations, so don't depend too much on them in your application.
|
||||
|
||||
SetFgBg string `json:"_setfgbg,omitempty"` // setfgbg
|
||||
SetFgBgRGB string `json:"_setfgbgrgb,omitempty"` // setfgbgrgb
|
||||
SetFgRGB string `json:"_setfrgb,omitempty"` // setfrgb
|
||||
SetBgRGB string `json:"_setbrgb,omitempty"` // setbrgb
|
||||
KeyShfUp string `json:"_kscu1,omitempty"` // shift-up
|
||||
KeyShfDown string `json:"_kscud1,omitempty"` // shift-down
|
||||
KeyCtrlUp string `json:"_kccu1,omitempty"` // ctrl-up
|
||||
KeyCtrlDown string `json:"_kccud1,omitempty"` // ctrl-left
|
||||
KeyCtrlRight string `json:"_kccuf1,omitempty"` // ctrl-right
|
||||
KeyCtrlLeft string `json:"_kccub1,omitempty"` // ctrl-left
|
||||
KeyMetaUp string `json:"_kmcu1,omitempty"` // meta-up
|
||||
KeyMetaDown string `json:"_kmcud1,omitempty"` // meta-left
|
||||
KeyMetaRight string `json:"_kmcuf1,omitempty"` // meta-right
|
||||
KeyMetaLeft string `json:"_kmcub1,omitempty"` // meta-left
|
||||
KeyAltUp string `json:"_kacu1,omitempty"` // alt-up
|
||||
KeyAltDown string `json:"_kacud1,omitempty"` // alt-left
|
||||
KeyAltRight string `json:"_kacuf1,omitempty"` // alt-right
|
||||
KeyAltLeft string `json:"_kacub1,omitempty"` // alt-left
|
||||
KeyCtrlHome string `json:"_kchome,omitempty"`
|
||||
KeyCtrlEnd string `json:"_kcend,omitempty"`
|
||||
KeyMetaHome string `json:"_kmhome,omitempty"`
|
||||
KeyMetaEnd string `json:"_kmend,omitempty"`
|
||||
KeyAltHome string `json:"_kahome,omitempty"`
|
||||
KeyAltEnd string `json:"_kaend,omitempty"`
|
||||
KeyAltShfUp string `json:"_kascu1,omitempty"`
|
||||
KeyAltShfDown string `json:"_kascud1,omitempty"`
|
||||
KeyAltShfLeft string `json:"_kascub1,omitempty"`
|
||||
KeyAltShfRight string `json:"_kascuf1,omitempty"`
|
||||
KeyMetaShfUp string `json:"_kmscu1,omitempty"`
|
||||
KeyMetaShfDown string `json:"_kmscud1,omitempty"`
|
||||
KeyMetaShfLeft string `json:"_kmscub1,omitempty"`
|
||||
KeyMetaShfRight string `json:"_kmscuf1,omitempty"`
|
||||
KeyCtrlShfUp string `json:"_kcscu1,omitempty"`
|
||||
KeyCtrlShfDown string `json:"_kcscud1,omitempty"`
|
||||
KeyCtrlShfLeft string `json:"_kcscub1,omitempty"`
|
||||
KeyCtrlShfRight string `json:"_kcscuf1,omitempty"`
|
||||
KeyCtrlShfHome string `json:"_kcHOME,omitempty"`
|
||||
KeyCtrlShfEnd string `json:"_kcEND,omitempty"`
|
||||
KeyAltShfHome string `json:"_kaHOME,omitempty"`
|
||||
KeyAltShfEnd string `json:"_kaEND,omitempty"`
|
||||
KeyMetaShfHome string `json:"_kmHOME,omitempty"`
|
||||
KeyMetaShfEnd string `json:"_kmEND,omitempty"`
|
||||
}
|
||||
|
||||
type stackElem struct {
|
||||
s string
|
||||
i int
|
||||
isStr bool
|
||||
isInt bool
|
||||
}
|
||||
|
||||
type stack []stackElem
|
||||
|
||||
func (st stack) Push(v string) stack {
|
||||
e := stackElem{
|
||||
s: v,
|
||||
isStr: true,
|
||||
}
|
||||
return append(st, e)
|
||||
}
|
||||
|
||||
func (st stack) Pop() (string, stack) {
|
||||
v := ""
|
||||
if len(st) > 0 {
|
||||
e := st[len(st)-1]
|
||||
st = st[:len(st)-1]
|
||||
if e.isStr {
|
||||
v = e.s
|
||||
} else {
|
||||
v = strconv.Itoa(e.i)
|
||||
}
|
||||
}
|
||||
return v, st
|
||||
}
|
||||
|
||||
func (st stack) PopInt() (int, stack) {
|
||||
if len(st) > 0 {
|
||||
e := st[len(st)-1]
|
||||
st = st[:len(st)-1]
|
||||
if e.isInt {
|
||||
return e.i, st
|
||||
} else if e.isStr {
|
||||
i, _ := strconv.Atoi(e.s)
|
||||
return i, st
|
||||
}
|
||||
}
|
||||
return 0, st
|
||||
}
|
||||
|
||||
func (st stack) PopBool() (bool, stack) {
|
||||
if len(st) > 0 {
|
||||
e := st[len(st)-1]
|
||||
st = st[:len(st)-1]
|
||||
if e.isStr {
|
||||
if e.s == "1" {
|
||||
return true, st
|
||||
}
|
||||
return false, st
|
||||
} else if e.i == 1 {
|
||||
return true, st
|
||||
} else {
|
||||
return false, st
|
||||
}
|
||||
}
|
||||
return false, st
|
||||
}
|
||||
|
||||
func (st stack) PushInt(i int) stack {
|
||||
e := stackElem{
|
||||
i: i,
|
||||
isInt: true,
|
||||
}
|
||||
return append(st, e)
|
||||
}
|
||||
|
||||
func (st stack) PushBool(i bool) stack {
|
||||
if i {
|
||||
return st.PushInt(1)
|
||||
}
|
||||
return st.PushInt(0)
|
||||
}
|
||||
|
||||
func nextch(s string, index int) (byte, int) {
|
||||
if index < len(s) {
|
||||
return s[index], index + 1
|
||||
}
|
||||
return 0, index
|
||||
}
|
||||
|
||||
// static vars
|
||||
var svars [26]string
|
||||
|
||||
// paramsBuffer handles some persistent state for TParam. Technically we
|
||||
// could probably dispense with this, but caching buffer arrays gives us
|
||||
// a nice little performance boost. Furthermore, we know that TParam is
|
||||
// rarely (never?) called re-entrantly, so we can just reuse the same
|
||||
// buffers, making it thread-safe by stashing a lock.
|
||||
type paramsBuffer struct {
|
||||
out bytes.Buffer
|
||||
buf bytes.Buffer
|
||||
lk sync.Mutex
|
||||
}
|
||||
|
||||
// Start initializes the params buffer with the initial string data.
|
||||
// It also locks the paramsBuffer. The caller must call End() when
|
||||
// finished.
|
||||
func (pb *paramsBuffer) Start(s string) {
|
||||
pb.lk.Lock()
|
||||
pb.out.Reset()
|
||||
pb.buf.Reset()
|
||||
pb.buf.WriteString(s)
|
||||
}
|
||||
|
||||
// End returns the final output from TParam, but it also releases the lock.
|
||||
func (pb *paramsBuffer) End() string {
|
||||
s := pb.out.String()
|
||||
pb.lk.Unlock()
|
||||
return s
|
||||
}
|
||||
|
||||
// NextCh returns the next input character to the expander.
|
||||
func (pb *paramsBuffer) NextCh() (byte, error) {
|
||||
return pb.buf.ReadByte()
|
||||
}
|
||||
|
||||
// PutCh "emits" (rather schedules for output) a single byte character.
|
||||
func (pb *paramsBuffer) PutCh(ch byte) {
|
||||
pb.out.WriteByte(ch)
|
||||
}
|
||||
|
||||
// PutString schedules a string for output.
|
||||
func (pb *paramsBuffer) PutString(s string) {
|
||||
pb.out.WriteString(s)
|
||||
}
|
||||
|
||||
var pb = ¶msBuffer{}
|
||||
|
||||
// TParm takes a terminfo parameterized string, such as setaf or cup, and
|
||||
// evaluates the string, and returns the result with the parameter
|
||||
// applied.
|
||||
func (t *Terminfo) TParm(s string, p ...int) string {
|
||||
var stk stack
|
||||
var a, b string
|
||||
var ai, bi int
|
||||
var ab bool
|
||||
var dvars [26]string
|
||||
var params [9]int
|
||||
|
||||
pb.Start(s)
|
||||
|
||||
// make sure we always have 9 parameters -- makes it easier
|
||||
// later to skip checks
|
||||
for i := 0; i < len(params) && i < len(p); i++ {
|
||||
params[i] = p[i]
|
||||
}
|
||||
|
||||
nest := 0
|
||||
|
||||
for {
|
||||
|
||||
ch, err := pb.NextCh()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if ch != '%' {
|
||||
pb.PutCh(ch)
|
||||
continue
|
||||
}
|
||||
|
||||
ch, err = pb.NextCh()
|
||||
if err != nil {
|
||||
// XXX Error
|
||||
break
|
||||
}
|
||||
|
||||
switch ch {
|
||||
case '%': // quoted %
|
||||
pb.PutCh(ch)
|
||||
|
||||
case 'i': // increment both parameters (ANSI cup support)
|
||||
params[0]++
|
||||
params[1]++
|
||||
|
||||
case 'c', 's':
|
||||
// NB: these, and 'd' below are special cased for
|
||||
// efficiency. They could be handled by the richer
|
||||
// format support below, less efficiently.
|
||||
a, stk = stk.Pop()
|
||||
pb.PutString(a)
|
||||
|
||||
case 'd':
|
||||
ai, stk = stk.PopInt()
|
||||
pb.PutString(strconv.Itoa(ai))
|
||||
|
||||
case '0', '1', '2', '3', '4', 'x', 'X', 'o', ':':
|
||||
// This is pretty suboptimal, but this is rarely used.
|
||||
// None of the mainstream terminals use any of this,
|
||||
// and it would surprise me if this code is ever
|
||||
// executed outside of test cases.
|
||||
f := "%"
|
||||
if ch == ':' {
|
||||
ch, _ = pb.NextCh()
|
||||
}
|
||||
f += string(ch)
|
||||
for ch == '+' || ch == '-' || ch == '#' || ch == ' ' {
|
||||
ch, _ = pb.NextCh()
|
||||
f += string(ch)
|
||||
}
|
||||
for (ch >= '0' && ch <= '9') || ch == '.' {
|
||||
ch, _ = pb.NextCh()
|
||||
f += string(ch)
|
||||
}
|
||||
switch ch {
|
||||
case 'd', 'x', 'X', 'o':
|
||||
ai, stk = stk.PopInt()
|
||||
pb.PutString(fmt.Sprintf(f, ai))
|
||||
case 'c', 's':
|
||||
a, stk = stk.Pop()
|
||||
pb.PutString(fmt.Sprintf(f, a))
|
||||
}
|
||||
|
||||
case 'p': // push parameter
|
||||
ch, _ = pb.NextCh()
|
||||
ai = int(ch - '1')
|
||||
if ai >= 0 && ai < len(params) {
|
||||
stk = stk.PushInt(params[ai])
|
||||
} else {
|
||||
stk = stk.PushInt(0)
|
||||
}
|
||||
|
||||
case 'P': // pop & store variable
|
||||
ch, _ = pb.NextCh()
|
||||
if ch >= 'A' && ch <= 'Z' {
|
||||
svars[int(ch-'A')], stk = stk.Pop()
|
||||
} else if ch >= 'a' && ch <= 'z' {
|
||||
dvars[int(ch-'a')], stk = stk.Pop()
|
||||
}
|
||||
|
||||
case 'g': // recall & push variable
|
||||
ch, _ = pb.NextCh()
|
||||
if ch >= 'A' && ch <= 'Z' {
|
||||
stk = stk.Push(svars[int(ch-'A')])
|
||||
} else if ch >= 'a' && ch <= 'z' {
|
||||
stk = stk.Push(dvars[int(ch-'a')])
|
||||
}
|
||||
|
||||
case '\'': // push(char)
|
||||
ch, _ = pb.NextCh()
|
||||
pb.NextCh() // must be ' but we don't check
|
||||
stk = stk.Push(string(ch))
|
||||
|
||||
case '{': // push(int)
|
||||
ai = 0
|
||||
ch, _ = pb.NextCh()
|
||||
for ch >= '0' && ch <= '9' {
|
||||
ai *= 10
|
||||
ai += int(ch - '0')
|
||||
ch, _ = pb.NextCh()
|
||||
}
|
||||
// ch must be '}' but no verification
|
||||
stk = stk.PushInt(ai)
|
||||
|
||||
case 'l': // push(strlen(pop))
|
||||
a, stk = stk.Pop()
|
||||
stk = stk.PushInt(len(a))
|
||||
|
||||
case '+':
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushInt(ai + bi)
|
||||
|
||||
case '-':
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushInt(ai - bi)
|
||||
|
||||
case '*':
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushInt(ai * bi)
|
||||
|
||||
case '/':
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
if bi != 0 {
|
||||
stk = stk.PushInt(ai / bi)
|
||||
} else {
|
||||
stk = stk.PushInt(0)
|
||||
}
|
||||
|
||||
case 'm': // push(pop mod pop)
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
if bi != 0 {
|
||||
stk = stk.PushInt(ai % bi)
|
||||
} else {
|
||||
stk = stk.PushInt(0)
|
||||
}
|
||||
|
||||
case '&': // AND
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushInt(ai & bi)
|
||||
|
||||
case '|': // OR
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushInt(ai | bi)
|
||||
|
||||
case '^': // XOR
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushInt(ai ^ bi)
|
||||
|
||||
case '~': // bit complement
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushInt(ai ^ -1)
|
||||
|
||||
case '!': // logical NOT
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushBool(ai != 0)
|
||||
|
||||
case '=': // numeric compare or string compare
|
||||
b, stk = stk.Pop()
|
||||
a, stk = stk.Pop()
|
||||
stk = stk.PushBool(a == b)
|
||||
|
||||
case '>': // greater than, numeric
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushBool(ai > bi)
|
||||
|
||||
case '<': // less than, numeric
|
||||
bi, stk = stk.PopInt()
|
||||
ai, stk = stk.PopInt()
|
||||
stk = stk.PushBool(ai < bi)
|
||||
|
||||
case '?': // start conditional
|
||||
|
||||
case 't':
|
||||
ab, stk = stk.PopBool()
|
||||
if ab {
|
||||
// just keep going
|
||||
break
|
||||
}
|
||||
nest = 0
|
||||
ifloop:
|
||||
// this loop consumes everything until we hit our else,
|
||||
// or the end of the conditional
|
||||
for {
|
||||
ch, err = pb.NextCh()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if ch != '%' {
|
||||
continue
|
||||
}
|
||||
ch, _ = pb.NextCh()
|
||||
switch ch {
|
||||
case ';':
|
||||
if nest == 0 {
|
||||
break ifloop
|
||||
}
|
||||
nest--
|
||||
case '?':
|
||||
nest++
|
||||
case 'e':
|
||||
if nest == 0 {
|
||||
break ifloop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case 'e':
|
||||
// if we got here, it means we didn't use the else
|
||||
// in the 't' case above, and we should skip until
|
||||
// the end of the conditional
|
||||
nest = 0
|
||||
elloop:
|
||||
for {
|
||||
ch, err = pb.NextCh()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if ch != '%' {
|
||||
continue
|
||||
}
|
||||
ch, _ = pb.NextCh()
|
||||
switch ch {
|
||||
case ';':
|
||||
if nest == 0 {
|
||||
break elloop
|
||||
}
|
||||
nest--
|
||||
case '?':
|
||||
nest++
|
||||
}
|
||||
}
|
||||
|
||||
case ';': // endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return pb.End()
|
||||
}
|
||||
|
||||
// TPuts emits the string to the writer, but expands inline padding
|
||||
// indications (of the form $<[delay]> where [delay] is msec) to
|
||||
// a suitable number of padding characters (usually null bytes) based
|
||||
// upon the supplied baud. At high baud rates, more padding characters
|
||||
// will be inserted. All Terminfo based strings should be emitted using
|
||||
// this function.
|
||||
func (t *Terminfo) TPuts(w io.Writer, s string, baud int) {
|
||||
for {
|
||||
beg := strings.Index(s, "$<")
|
||||
if beg < 0 {
|
||||
// Most strings don't need padding, which is good news!
|
||||
io.WriteString(w, s)
|
||||
return
|
||||
}
|
||||
io.WriteString(w, s[:beg])
|
||||
s = s[beg+2:]
|
||||
end := strings.Index(s, ">")
|
||||
if end < 0 {
|
||||
// unterminated.. just emit bytes unadulterated
|
||||
io.WriteString(w, "$<"+s)
|
||||
return
|
||||
}
|
||||
val := s[:end]
|
||||
s = s[end+1:]
|
||||
padus := 0
|
||||
unit := 1000
|
||||
dot := false
|
||||
loop:
|
||||
for i := range val {
|
||||
switch val[i] {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
padus *= 10
|
||||
padus += int(val[i] - '0')
|
||||
if dot {
|
||||
unit *= 10
|
||||
}
|
||||
case '.':
|
||||
if !dot {
|
||||
dot = true
|
||||
} else {
|
||||
break loop
|
||||
}
|
||||
default:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
cnt := int(((baud / 8) * padus) / unit)
|
||||
for cnt > 0 {
|
||||
io.WriteString(w, t.PadChar)
|
||||
cnt--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TGoto returns a string suitable for addressing the cursor at the given
|
||||
// row and column. The origin 0, 0 is in the upper left corner of the screen.
|
||||
func (t *Terminfo) TGoto(col, row int) string {
|
||||
return t.TParm(t.SetCursor, row, col)
|
||||
}
|
||||
|
||||
// TColor returns a string corresponding to the given foreground and background
|
||||
// colors. Either fg or bg can be set to -1 to elide.
|
||||
func (t *Terminfo) TColor(fi, bi int) string {
|
||||
rv := ""
|
||||
// As a special case, we map bright colors to lower versions if the
|
||||
// color table only holds 8. For the remaining 240 colors, the user
|
||||
// is out of luck. Someday we could create a mapping table, but its
|
||||
// not worth it.
|
||||
if t.Colors == 8 {
|
||||
if fi > 7 && fi < 16 {
|
||||
fi -= 8
|
||||
}
|
||||
if bi > 7 && bi < 16 {
|
||||
bi -= 8
|
||||
}
|
||||
}
|
||||
if t.Colors > fi && fi >= 0 {
|
||||
rv += t.TParm(t.SetFg, fi)
|
||||
}
|
||||
if t.Colors > bi && bi >= 0 {
|
||||
rv += t.TParm(t.SetBg, bi)
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
var (
|
||||
dblock sync.Mutex
|
||||
terminfos = make(map[string]*Terminfo)
|
||||
aliases = make(map[string]string)
|
||||
)
|
||||
|
||||
// AddTerminfo can be called to register a new Terminfo entry.
|
||||
func AddTerminfo(t *Terminfo) {
|
||||
dblock.Lock()
|
||||
terminfos[t.Name] = t
|
||||
for _, x := range t.Aliases {
|
||||
terminfos[x] = t
|
||||
}
|
||||
dblock.Unlock()
|
||||
}
|
||||
|
||||
func loadFromFile(fname string, term string) (*Terminfo, error) {
|
||||
var e error
|
||||
var f io.ReadCloser
|
||||
if f, e = os.Open(fname); e != nil {
|
||||
return nil, e
|
||||
}
|
||||
defer f.Close()
|
||||
if strings.HasSuffix(fname, ".gz") {
|
||||
if f, e = gzip.NewReader(f); e != nil {
|
||||
return nil, e
|
||||
}
|
||||
}
|
||||
d := json.NewDecoder(f)
|
||||
for {
|
||||
t := &Terminfo{}
|
||||
if e := d.Decode(t); e != nil {
|
||||
if e == io.EOF {
|
||||
return nil, ErrTermNotFound
|
||||
}
|
||||
return nil, e
|
||||
}
|
||||
if t.SetCursor == "" {
|
||||
// This must be an alias record, return it.
|
||||
return t, nil
|
||||
}
|
||||
if t.Name == term {
|
||||
return t, nil
|
||||
}
|
||||
for _, a := range t.Aliases {
|
||||
if a == term {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LookupTerminfo attempts to find a definition for the named $TERM.
|
||||
// It first looks in the builtin database, which should cover just about
|
||||
// everyone. If it can't find one there, then it will attempt to read
|
||||
// one from the JSON file located in either $TCELLDB, $HOME/.tcelldb
|
||||
// or in this package's source directory as database.json).
|
||||
func LookupTerminfo(name string) (*Terminfo, error) {
|
||||
if name == "" {
|
||||
// else on windows: index out of bounds
|
||||
// on the name[0] reference below
|
||||
return nil, ErrTermNotFound
|
||||
}
|
||||
|
||||
dblock.Lock()
|
||||
t := terminfos[name]
|
||||
dblock.Unlock()
|
||||
|
||||
if t == nil {
|
||||
|
||||
var files []string
|
||||
letter := fmt.Sprintf("%02x", name[0])
|
||||
gzfile := path.Join(letter, name+".gz")
|
||||
jsfile := path.Join(letter, name)
|
||||
|
||||
// Build up the search path. Old versions of tcell used a
|
||||
// single database file, whereas the new ones locate them
|
||||
// in JSON (optionally compressed) files.
|
||||
//
|
||||
// The search path looks like:
|
||||
//
|
||||
// $TCELLDB/x/xterm.gz
|
||||
// $TCELLDB/x/xterm
|
||||
// $TCELLDB
|
||||
// $HOME/.tcelldb/x/xterm.gz
|
||||
// $HOME/.tcelldb/x/xterm
|
||||
// $HOME/.tcelldb
|
||||
// $GOPATH/terminfo/database/x/xterm.gz
|
||||
// $GOPATH/terminfo/database/x/xterm
|
||||
//
|
||||
if pth := os.Getenv("TCELLDB"); pth != "" {
|
||||
files = append(files, path.Join(pth, gzfile))
|
||||
files = append(files, path.Join(pth, jsfile))
|
||||
files = append(files, pth)
|
||||
}
|
||||
if pth := os.Getenv("HOME"); pth != "" {
|
||||
pth = path.Join(pth, ".tcelldb")
|
||||
files = append(files, path.Join(pth, gzfile))
|
||||
files = append(files, path.Join(pth, jsfile))
|
||||
files = append(files, pth)
|
||||
}
|
||||
|
||||
for _, pth := range strings.Split(os.Getenv("GOPATH"), string(os.PathListSeparator)) {
|
||||
pth = path.Join(pth, "src", "github.com", "gdamore", "tcell", "terminfo", "database")
|
||||
files = append(files, path.Join(pth, gzfile))
|
||||
files = append(files, path.Join(pth, jsfile))
|
||||
}
|
||||
|
||||
for _, fname := range files {
|
||||
t, _ = loadFromFile(fname, name)
|
||||
if t != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if t != nil {
|
||||
if t.Name != name {
|
||||
// Check for a database loop (no infinite
|
||||
// recursion).
|
||||
dblock.Lock()
|
||||
if aliases[name] != "" {
|
||||
dblock.Unlock()
|
||||
return nil, ErrTermNotFound
|
||||
}
|
||||
aliases[name] = t.Name
|
||||
dblock.Unlock()
|
||||
return LookupTerminfo(t.Name)
|
||||
}
|
||||
dblock.Lock()
|
||||
terminfos[name] = t
|
||||
dblock.Unlock()
|
||||
}
|
||||
}
|
||||
if t == nil {
|
||||
return nil, ErrTermNotFound
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
@@ -16,6 +16,9 @@ color-link tabbar "#1D1F21,#C5C8C6"
|
||||
color-link indent-char "#505050,#1D1F21"
|
||||
color-link line-number "#656866,#232526"
|
||||
color-link current-line-number "#656866,#1D1F21"
|
||||
color-link diff-added "#00AF00"
|
||||
color-link diff-modified "#FFAF00"
|
||||
color-link diff-deleted "#D70000"
|
||||
color-link gutter-error "#FF4444,#1D1F21"
|
||||
color-link gutter-warning "#EEEE77,#1D1F21"
|
||||
color-link cursor-line "#2D2F31"
|
||||
@@ -14,11 +14,14 @@ color-link underlined "underline 241,231"
|
||||
color-link todo "246,231"
|
||||
color-link statusline "241,254"
|
||||
color-link tabbar "241,254"
|
||||
color-link diff-added "34"
|
||||
color-link diff-modified "214"
|
||||
color-link diff-deleted "160"
|
||||
color-link gutter-error "197,231"
|
||||
color-link gutter-warning "134,231"
|
||||
color-link line-number "246,254"
|
||||
color-link cursor-line "254"
|
||||
color-link color-column "254"
|
||||
#No extended types (bool in C, &c.) and plain brackets
|
||||
color-link type.extended "default"
|
||||
color-link symbol.brackets "default"
|
||||
color-link type.extended "241,231"
|
||||
color-link symbol.brackets "241,231"
|
||||
|
||||
@@ -33,6 +33,9 @@ color-link statusline "white,blue"
|
||||
color-link tabbar "white,blue"
|
||||
color-link current-line-number "red"
|
||||
color-link current-line-number.scroller "red"
|
||||
color-link diff-added "green"
|
||||
color-link diff-modified "yellow"
|
||||
color-link diff-deleted "red"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "red"
|
||||
color-link color-column "cyan"
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
#CaptainMcClellan's personal color scheme.
|
||||
#Paper version
|
||||
color-link default "black,white"
|
||||
color-link comment "bold black"
|
||||
color-link constant "cyan"
|
||||
color-link constant.bool "bold cyan"
|
||||
color-link constant.bool.true "bold green"
|
||||
color-link constant.bool.false "bold red"
|
||||
color-link constant.string "bold yellow"
|
||||
color-link constant.string.url "underline blue, white"
|
||||
color-link constant.number "constant"
|
||||
color-link constant.specialChar "bold magenta"
|
||||
color-link identifier "bold red"
|
||||
color-link identifier.macro "bold red"
|
||||
color-link identifier.var "bold blue"
|
||||
color-link identifier.class "bold green"
|
||||
color-link preproc "bold cyan"
|
||||
color-link statement "bold yellow"
|
||||
color-link symbol "red"
|
||||
color-link symbol.brackets "blue"
|
||||
color-link type "green"
|
||||
color-link type.keyword "bold green"
|
||||
color-link special "magenta"
|
||||
color-link ignore "default"
|
||||
color-link error ",brightred"
|
||||
color-link todo "black,brightyellow"
|
||||
color-link indent-char ",brightgreen"
|
||||
color-link line-number "green"
|
||||
color-link line-number.scrollbar "green"
|
||||
color-link statusline "white,blue"
|
||||
color-link tabbar "white,blue"
|
||||
color-link current-line-number "red"
|
||||
color-link current-line-number.scroller "red"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "red"
|
||||
color-link color-column "cyan"
|
||||
color-link underlined.url "underline blue, white"
|
||||
@@ -28,9 +28,12 @@ color-link statusline "#aaaaaa,#8a496b"
|
||||
color-link tabbar "#aaaaaa,#8a496b"
|
||||
color-link current-line-number "bold #e34234,#424549"
|
||||
color-link current-line-number.scroller "red"
|
||||
color-link diff-added "#00AF00"
|
||||
color-link diff-modified "#FFAF00"
|
||||
color-link diff-deleted "#D70000"
|
||||
color-link gutter-error ",#e34234"
|
||||
color-link gutter-warning "#e34234"
|
||||
color-link color-column "#f26522"
|
||||
color-link constant.bool "bold #55ffff"
|
||||
color-link constant.bool.true "bold #85ff85"
|
||||
color-link constant.bool.false "bold #ff8585"
|
||||
color-link constant.bool.false "bold #ff8585"
|
||||
|
||||
@@ -15,12 +15,15 @@ color-link todo "bold #D33682,#242424"
|
||||
color-link statusline "#242424,#CCCCCC"
|
||||
color-link tabbar "#242424,#CCCCCC"
|
||||
color-link indent-char "#4F4F4F,#242424"
|
||||
color-link line-number "#666666,#242424"
|
||||
color-link line-number "#666666,#2C2C2C"
|
||||
color-link current-line-number "#666666,#242424"
|
||||
color-link diff-added "#00AF00"
|
||||
color-link diff-modified "#FFAF00"
|
||||
color-link diff-deleted "#D70000"
|
||||
color-link gutter-error "#CB4B16,#242424"
|
||||
color-link gutter-warning "#E6DB74,#242424"
|
||||
color-link cursor-line "default,#2C2C2C"
|
||||
color-link color-column "default,#2C2C2C"
|
||||
color-link cursor-line "#2C2C2C"
|
||||
color-link color-column "#2C2C2C"
|
||||
#No extended types; Plain brackets.
|
||||
color-link type.extended "default"
|
||||
#color-link symbol.brackets "default"
|
||||
|
||||
@@ -17,6 +17,9 @@ color-link tabbar "#282828,#F8F8F2"
|
||||
color-link indent-char "#505050,#282828"
|
||||
color-link line-number "#AAAAAA,#323232"
|
||||
color-link current-line-number "#AAAAAA,#282828"
|
||||
color-link diff-added "#00AF00"
|
||||
color-link diff-modified "#FFAF00"
|
||||
color-link diff-deleted "#D70000"
|
||||
color-link gutter-error "#CB4B16,#282828"
|
||||
color-link gutter-warning "#E6DB74,#282828"
|
||||
color-link cursor-line "#323232"
|
||||
|
||||
34
runtime/colorschemes/dukedark-tc.micro
Normal file
34
runtime/colorschemes/dukedark-tc.micro
Normal file
@@ -0,0 +1,34 @@
|
||||
color-link color-column "#001e28"
|
||||
color-link comment "#608b4e,#001e28"
|
||||
color-link constant.bool "#fd971f,#001e28"
|
||||
color-link constant "#fd971f,#001e28"
|
||||
color-link constant.string "#a0f000,#001e28"
|
||||
color-link constant.string.char "#a0f000,#001e28"
|
||||
color-link constant.string.url "#a0f000,#001e28"
|
||||
color-link current-line-number "bold #fd971f,#001e28"
|
||||
color-link cursor-line "#001923"
|
||||
color-link default "#ffffff,#001e28"
|
||||
color-link diff-added "#00c8a0,#001e28"
|
||||
color-link diff-modified "#fd971f,#001e28"
|
||||
color-link diff-deleted "#cb4b16,#001e28"
|
||||
color-link divider "#001e28,#d0d0d0"
|
||||
color-link error "#cb4b16,#001e28"
|
||||
color-link gutter-error "#cb4b16,#001e28"
|
||||
color-link gutter-warning "#fce94f,#001e28"
|
||||
color-link identifier "#00c8a0,#001e28"
|
||||
color-link identifier.class "#00c8a0,#001e28"
|
||||
color-link indent-char "#a0a0a0,#001e28"
|
||||
color-link line-number "#a0a0a0,#001923"
|
||||
color-link preproc "bold #5aaae6,#001e28"
|
||||
color-link special "#a6e22e,#001e28"
|
||||
color-link statement "bold #5aaae6,#001e28"
|
||||
color-link statusline "#ffffff,#0078c8"
|
||||
color-link symbol "#00c8a0,#001e28"
|
||||
color-link symbol.brackets "#ffffff,#001e28"
|
||||
color-link symbol.tag "bold #5aaae6,#001e28"
|
||||
color-link tabbar "#001e28,#ffffff"
|
||||
color-link todo "#fce94f,#001e28"
|
||||
color-link type "bold #3cc83c,#001e28"
|
||||
color-link type.keyword "bold #5aaae6,#001e28"
|
||||
color-link type.extended "#ffffff,#001e28"
|
||||
color-link underlined "#608b4e,#001e28"
|
||||
34
runtime/colorschemes/dukelight-tc.micro
Normal file
34
runtime/colorschemes/dukelight-tc.micro
Normal file
@@ -0,0 +1,34 @@
|
||||
color-link color-column "#f0f0f0"
|
||||
color-link comment "#3f7f5f,#f0f0f0"
|
||||
color-link constant.bool "#641e00,#f0f0f0"
|
||||
color-link constant "#641e00,#f0f0f0"
|
||||
color-link constant.string "#0000ff,#f0f0f0"
|
||||
color-link constant.string.char "#0000ff,#f0f0f0"
|
||||
color-link constant.string.url "#0000ff,#f0f0f0"
|
||||
color-link current-line-number "bold #004080,#f0f0f0"
|
||||
color-link cursor-line "#e6e6e6"
|
||||
color-link default "#000000,#f0f0f0"
|
||||
color-link diff-added "#008040,#f0f0f0"
|
||||
color-link diff-modified "#641e00,#f0f0f0"
|
||||
color-link diff-deleted "#500000,#f0f0f0"
|
||||
color-link divider "#f0f0f0,#004080"
|
||||
color-link error "#500000,#f0f0f0"
|
||||
color-link gutter-error "#500000,#f0f0f0"
|
||||
color-link gutter-warning "#dcc800,#f0f0f0"
|
||||
color-link identifier "bold #0078a0,#f0f0f0"
|
||||
color-link identifier.class "bold #0078a0,#f0f0f0"
|
||||
color-link indent-char "#404040,#f0f0f0"
|
||||
color-link line-number "#404040,#e6e6e6"
|
||||
color-link preproc "bold #780050,#f0f0f0"
|
||||
color-link special "bold #0078a0,#f0f0f0"
|
||||
color-link statement "bold #780050,#f0f0f0"
|
||||
color-link statusline "#ffffff,#0078c8"
|
||||
color-link symbol "bold #0078a0,#f0f0f0"
|
||||
color-link symbol.brackets "#000000,#f0f0f0"
|
||||
color-link symbol.tag "bold #780050,#f0f0f0"
|
||||
color-link tabbar "#f0f0f0,#004080"
|
||||
color-link todo "#dcc800,#f0f0f0"
|
||||
color-link type "bold #004080,#f0f0f0"
|
||||
color-link type.keyword "bold #780050,#f0f0f0"
|
||||
color-link type.extended "#000000,#f0f0f0"
|
||||
color-link underlined "#3f7f5f,#f0f0f0"
|
||||
34
runtime/colorschemes/dukeubuntu-tc.micro
Normal file
34
runtime/colorschemes/dukeubuntu-tc.micro
Normal file
@@ -0,0 +1,34 @@
|
||||
color-link color-column "#2d0023"
|
||||
color-link comment "#886484,#2d0023"
|
||||
color-link constant.bool "#fd971f,#2d0023"
|
||||
color-link constant "#fd971f,#2d0023"
|
||||
color-link constant.string "#a0f000,#2d0023"
|
||||
color-link constant.string.char "#a0f000,#2d0023"
|
||||
color-link constant.string.url "#a0f000,#2d0023"
|
||||
color-link current-line-number "bold #fd971f,#2d0023"
|
||||
color-link cursor-line "#230019"
|
||||
color-link default "#ffffff,#2d0023"
|
||||
color-link diff-added "#00c8a0,#2d0023"
|
||||
color-link diff-modified "#fd971f,#2d0023"
|
||||
color-link diff-deleted "#cb4b16,#2d0023"
|
||||
color-link divider "#2d0023,#d0d0d0"
|
||||
color-link error "#cb4b16,#2d0023"
|
||||
color-link gutter-error "#cb4b16,#2d0023"
|
||||
color-link gutter-warning "#fce94f,#2d0023"
|
||||
color-link identifier "#00c8a0,#2d0023"
|
||||
color-link identifier.class "#00c8a0,#2d0023"
|
||||
color-link indent-char "#a0a0a0,#2d0023"
|
||||
color-link line-number "#a0a0a0,#230019"
|
||||
color-link preproc "bold #5aaae6,#2d0023"
|
||||
color-link special "#a6e22e,#2d0023"
|
||||
color-link statement "bold #5aaae6,#2d0023"
|
||||
color-link statusline "#ffffff,#0078c8"
|
||||
color-link symbol "#00c8a0,#2d0023"
|
||||
color-link symbol.brackets "#ffffff,#2d0023"
|
||||
color-link symbol.tag "bold #5aaae6,#2d0023"
|
||||
color-link tabbar "#2d0023,#ffffff"
|
||||
color-link todo "#fce94f,#2d0023"
|
||||
color-link type "bold #3cc83c,#2d0023"
|
||||
color-link type.keyword "bold #5aaae6,#2d0023"
|
||||
color-link type.extended "#ffffff,#2d0023"
|
||||
color-link underlined "#886484,#2d0023"
|
||||
@@ -1,7 +1,6 @@
|
||||
#Geany
|
||||
color-link comment "red"
|
||||
color-link constant "default"
|
||||
color-link constant.number
|
||||
color-link constant.string "bold yellow"
|
||||
color-link identifier "default"
|
||||
color-link preproc "cyan"
|
||||
@@ -19,5 +18,8 @@ color-link current-line-number ""
|
||||
color-link statusline "black,white"
|
||||
color-link tabbar "black,white"
|
||||
color-link color-column "bold geren"
|
||||
color-link diff-added "green"
|
||||
color-link diff-modified "yellow"
|
||||
color-link diff-deleted "red"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "red"
|
||||
color-link gutter-warning "red"
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
#True color theme based on Github's syntax highlighting.
|
||||
#Warning, this is based on how it rendered in my Firefox!
|
||||
#Yours may look different.
|
||||
color-link comment "bold #969896"
|
||||
color-link constant "#0086B9"
|
||||
color-link constant.number "#0086B9"
|
||||
color-link constant.specialChar "bold #1836BD"
|
||||
color-link constant.string "bold #1836BD"
|
||||
color-link constant.bool "#0086B9"
|
||||
color-link identifier "#A71D5D"
|
||||
color-link preproc "bold #A71D5D"
|
||||
color-link special "#A71D5D"
|
||||
color-link statement "#A71D5D"
|
||||
color-link symbol "default"
|
||||
color-link type "#A71D5D"
|
||||
color-link error "bold ,#E34234"
|
||||
color-link todo "white"
|
||||
color-link indent-char "default"
|
||||
color-link line-number "bold #969896"
|
||||
color-link current-line-number "bold #969896"
|
||||
color-link gutter-error "bold ,#E34234"
|
||||
color-link gutter-warning "bold #f26522"
|
||||
color-link statusline "bold #c8c9cb,#24292e"
|
||||
color-link tabbar "bold #c8c9cb,#24292e"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user