mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-29 22:27:13 +09:00
Compare commits
330 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56aa289f81 | ||
|
|
3c01947cb3 | ||
|
|
53e142fb88 | ||
|
|
2e64499f96 | ||
|
|
11cb702d7f | ||
|
|
7a2820cbc0 | ||
|
|
b181342ff1 | ||
|
|
f0e2f3cc96 | ||
|
|
6ef273accd | ||
|
|
0eadf283a5 | ||
|
|
f8a171379a | ||
|
|
1a62ede320 | ||
|
|
1bb1da4765 | ||
|
|
987d48038a | ||
|
|
abc04ec521 | ||
|
|
b7706d775c | ||
|
|
ac0b89366b | ||
|
|
3293160dcb | ||
|
|
804943a1e8 | ||
|
|
89f50638d7 | ||
|
|
c606c51c8b | ||
|
|
4bde88d126 | ||
|
|
41bae11c1e | ||
|
|
f43a1b5ced | ||
|
|
219f934656 | ||
|
|
26da85dcb1 | ||
|
|
2885b42c62 | ||
|
|
b12eca0a98 | ||
|
|
3e612d2597 | ||
|
|
ade5efef5d | ||
|
|
cb45481526 | ||
|
|
88d8b0b181 | ||
|
|
ea6a87d41a | ||
|
|
1c2fd30cab | ||
|
|
69ed07cc62 | ||
|
|
6d2cbb6cce | ||
|
|
397c29443a | ||
|
|
5b26702d5e | ||
|
|
b9e77eee6a | ||
|
|
8e5fd674cc | ||
|
|
5038167650 | ||
|
|
6787db9eb3 | ||
|
|
75b9c8c1ec | ||
|
|
a37c30b889 | ||
|
|
f17b42bcd2 | ||
|
|
7bfc90d080 | ||
|
|
1d24609ed1 | ||
|
|
aa81cf5cf6 | ||
|
|
4790c39dfc | ||
|
|
35a9245c5d | ||
|
|
3e3cdfc5b5 | ||
|
|
f0e453b4f9 | ||
|
|
3325b98063 | ||
|
|
4632c3594f | ||
|
|
96c7b1d07b | ||
|
|
f48116801b | ||
|
|
aaf098bb47 | ||
|
|
6d4134a178 | ||
|
|
015fcf5fec | ||
|
|
fddf1690e3 | ||
|
|
0913a1aeb3 | ||
|
|
a19a6d28a7 | ||
|
|
af520cf047 | ||
|
|
db75e11e32 | ||
|
|
797e5cc27f | ||
|
|
36dc6647dd | ||
|
|
44b64f7129 | ||
|
|
0a49ea0a0d | ||
|
|
4f41881c10 | ||
|
|
63299df4b9 | ||
|
|
10b8fb7b26 | ||
|
|
0a7e4c8f06 | ||
|
|
83190a578e | ||
|
|
79349562b2 | ||
|
|
0cb1ad09cd | ||
|
|
6ef00c4c3b | ||
|
|
bb598ae566 | ||
|
|
13c63a9951 | ||
|
|
cf06d06fb3 | ||
|
|
808e3a7c9f | ||
|
|
16e9068cb9 | ||
|
|
3924e363d1 | ||
|
|
a274daeaaf | ||
|
|
e26417fd14 | ||
|
|
d7ba2f600e | ||
|
|
1cf4baa743 | ||
|
|
7e3aa337f6 | ||
|
|
3f01101da4 | ||
|
|
9a6054fc43 | ||
|
|
b2a0745747 | ||
|
|
7911ce1f16 | ||
|
|
8bff7f00d0 | ||
|
|
957273fc92 | ||
|
|
805d6ccaf7 | ||
|
|
fc2566a0de | ||
|
|
86c08bd747 | ||
|
|
0b47502e62 | ||
|
|
2afbcef825 | ||
|
|
0a500be3ba | ||
|
|
3b36316b00 | ||
|
|
d668050ebe | ||
|
|
dd47f167f1 | ||
|
|
2ebeb9d5a5 | ||
|
|
8629357c70 | ||
|
|
c8ff764467 | ||
|
|
8e741599dc | ||
|
|
770cb87f7a | ||
|
|
d82867ee53 | ||
|
|
275bce7d69 | ||
|
|
9094c174cc | ||
|
|
a814677b51 | ||
|
|
8b60e4f3b1 | ||
|
|
c32f5a4859 | ||
|
|
df44f538fd | ||
|
|
a4ae7a1e11 | ||
|
|
70616b335e | ||
|
|
f6e9a16724 | ||
|
|
ac41e186a0 | ||
|
|
a90cb64265 | ||
|
|
5124dd04b3 | ||
|
|
7867d50d67 | ||
|
|
0ba60728e8 | ||
|
|
981263eb81 | ||
|
|
79deabbbd6 | ||
|
|
ba4b028076 | ||
|
|
649e5799c2 | ||
|
|
7339a88d68 | ||
|
|
b0cfb2e691 | ||
|
|
4e0d402cea | ||
|
|
f882248f41 | ||
|
|
f58c5412a8 | ||
|
|
b0e4043513 | ||
|
|
47dd65d4e5 | ||
|
|
fa84f6ddc3 | ||
|
|
2bf40f096e | ||
|
|
4802403308 | ||
|
|
e443adef31 | ||
|
|
cdb057dfc3 | ||
|
|
9da1ef178e | ||
|
|
bf33ab532c | ||
|
|
46c7437270 | ||
|
|
09cab07352 | ||
|
|
b7214da4ea | ||
|
|
5138ae2436 | ||
|
|
98778a80c2 | ||
|
|
e0a8e90ad9 | ||
|
|
2ae9f88eaa | ||
|
|
ee8e022ccf | ||
|
|
3ca55f77a6 | ||
|
|
5f304db4a1 | ||
|
|
93b8f10b02 | ||
|
|
bdb699211a | ||
|
|
acd42df13c | ||
|
|
5fc8f847a6 | ||
|
|
af6ef4f87f | ||
|
|
7f287b62fb | ||
|
|
36d72c4cab | ||
|
|
71ee185b80 | ||
|
|
0360a2fcb5 | ||
|
|
2ee7adb196 | ||
|
|
d247db3e9d | ||
|
|
e4c2f5d259 | ||
|
|
cc15df9307 | ||
|
|
812b547679 | ||
|
|
1c43bb572a | ||
|
|
f96e9e9c1d | ||
|
|
7dfeda1ae5 | ||
|
|
d6ccaf0e41 | ||
|
|
6b6fcc8ba0 | ||
|
|
07bfcc9747 | ||
|
|
423f4675d2 | ||
|
|
c01ba97215 | ||
|
|
288717451f | ||
|
|
a1f3499825 | ||
|
|
63fa8fec41 | ||
|
|
b9e916999f | ||
|
|
afedad9977 | ||
|
|
d82ea2279d | ||
|
|
5b5998cf14 | ||
|
|
7b6430af1c | ||
|
|
a0d475bebf | ||
|
|
31cd4b5795 | ||
|
|
19ee4b281e | ||
|
|
a171795654 | ||
|
|
98d8bfa879 | ||
|
|
7bc2d870cd | ||
|
|
678819683a | ||
|
|
08e46f9112 | ||
|
|
e071209add | ||
|
|
74e79dc8f2 | ||
|
|
955e8ffb08 | ||
|
|
b87a74711e | ||
|
|
ade0e9dd39 | ||
|
|
f05f0b06ac | ||
|
|
f2006f592a | ||
|
|
5e66489836 | ||
|
|
9daa05d696 | ||
|
|
d76704839a | ||
|
|
329669ce79 | ||
|
|
4365b66398 | ||
|
|
5af5140362 | ||
|
|
bf6ce3a17e | ||
|
|
e99fd1337e | ||
|
|
17dac164ea | ||
|
|
b7c99c52d2 | ||
|
|
278aa6b050 | ||
|
|
773c54a40d | ||
|
|
74589af1fc | ||
|
|
f01ad3f726 | ||
|
|
a0f3ec805d | ||
|
|
ea6012922f | ||
|
|
da33b59858 | ||
|
|
9703d4f52f | ||
|
|
f3a30412f4 | ||
|
|
3116b082d8 | ||
|
|
3e0a1b4517 | ||
|
|
ac3de065d9 | ||
|
|
3e63ec74b9 | ||
|
|
c7334eb3b7 | ||
|
|
dfbddd4b86 | ||
|
|
299416062f | ||
|
|
8b8fffb98d | ||
|
|
ec221c0bc4 | ||
|
|
d27f8f9802 | ||
|
|
c40c79427a | ||
|
|
8a4f2193d8 | ||
|
|
aa667f6ba9 | ||
|
|
d067de8150 | ||
|
|
b3559df543 | ||
|
|
f4e94d6d34 | ||
|
|
13daa4e715 | ||
|
|
75be4f5f61 | ||
|
|
46ced988eb | ||
|
|
28acfc6d3f | ||
|
|
660c7d3be5 | ||
|
|
52617bd5a8 | ||
|
|
9db181037f | ||
|
|
861ea5aabc | ||
|
|
e4125c0c6a | ||
|
|
ff9a8a1247 | ||
|
|
ac29e30f54 | ||
|
|
a02ae3ceed | ||
|
|
54c02f4781 | ||
|
|
a5e721b107 | ||
|
|
12a4dd58f3 | ||
|
|
5a7ddb8330 | ||
|
|
cb75531818 | ||
|
|
6229a0579f | ||
|
|
fb980bb695 | ||
|
|
19dc9d7bbc | ||
|
|
1e55b6f6b3 | ||
|
|
0a35bfe2f5 | ||
|
|
2f587c6d48 | ||
|
|
5b426aee86 | ||
|
|
f700769b27 | ||
|
|
04b672eebe | ||
|
|
f7238e8e53 | ||
|
|
33cb39d318 | ||
|
|
612658d9c4 | ||
|
|
c31613b2c7 | ||
|
|
d7419d213a | ||
|
|
67a3f86cc9 | ||
|
|
b0c0747a09 | ||
|
|
64d574c35c | ||
|
|
4e531c2d1e | ||
|
|
ee8fa60bc5 | ||
|
|
e40ff56e07 | ||
|
|
0c1db1e813 | ||
|
|
a9b14d4c1b | ||
|
|
69f77ee2f1 | ||
|
|
3b5b9bbb21 | ||
|
|
65b5d6c5a9 | ||
|
|
1a575bc9ae | ||
|
|
ab242c5b17 | ||
|
|
90977fb4e1 | ||
|
|
404e5d206d | ||
|
|
d41a255361 | ||
|
|
efff850e54 | ||
|
|
4fcdde4149 | ||
|
|
c4d8b9e7fb | ||
|
|
68526dc119 | ||
|
|
4f2fc096e5 | ||
|
|
0f62ef687c | ||
|
|
32eb1135ed | ||
|
|
f88b4a6d57 | ||
|
|
9628b73525 | ||
|
|
d70a48bd13 | ||
|
|
660f1e181a | ||
|
|
773284369b | ||
|
|
f199c15269 | ||
|
|
e7facd74ba | ||
|
|
47e612afef | ||
|
|
921f88b95d | ||
|
|
7fe8d73473 | ||
|
|
fcb09556b1 | ||
|
|
69c6d8a099 | ||
|
|
dd5afc0560 | ||
|
|
471486b531 | ||
|
|
202cfb574c | ||
|
|
ebb0976866 | ||
|
|
5c785ab1ac | ||
|
|
2024b8b2c2 | ||
|
|
86c695ca52 | ||
|
|
9dd1df36d5 | ||
|
|
27f99b6309 | ||
|
|
ed9bc66060 | ||
|
|
d1598bb754 | ||
|
|
cc5855d07b | ||
|
|
487b36f48f | ||
|
|
c0b00c9a4c | ||
|
|
86c2ac95bb | ||
|
|
edb79f2972 | ||
|
|
305cefe461 | ||
|
|
315391b0aa | ||
|
|
fd45acc910 | ||
|
|
a574ae6b6a | ||
|
|
fa56a477c2 | ||
|
|
0db4556efb | ||
|
|
e6797e0303 | ||
|
|
6041e063e2 | ||
|
|
c3861955e0 | ||
|
|
4a45e69eb1 | ||
|
|
e52d05113e | ||
|
|
45992a0e0a | ||
|
|
1fb405afd3 | ||
|
|
e23d4d8fa1 | ||
|
|
d8aab386f1 | ||
|
|
7d422bfae2 | ||
|
|
7bc870e72f | ||
|
|
edee53f6f2 |
7
.editorconfig
Normal file
7
.editorconfig
Normal file
@@ -0,0 +1,7 @@
|
||||
# See http://editorconfig.org
|
||||
|
||||
# In Go files we indent with tabs but still
|
||||
# set indent_size to control the GitHub web viewer.
|
||||
[*.go]
|
||||
indent_size=4
|
||||
|
||||
21
.gitmodules
vendored
21
.gitmodules
vendored
@@ -31,9 +31,6 @@
|
||||
[submodule "cmd/micro/vendor/github.com/zyedidia/glob"]
|
||||
path = cmd/micro/vendor/github.com/zyedidia/glob
|
||||
url = https://github.com/zyedidia/glob
|
||||
[submodule "cmd/micro/vendor/github.com/zyedidia/json5"]
|
||||
path = cmd/micro/vendor/github.com/zyedidia/json5
|
||||
url = https://github.com/zyedidia/json5
|
||||
[submodule "cmd/micro/vendor/github.com/zyedidia/tcell"]
|
||||
path = cmd/micro/vendor/github.com/zyedidia/tcell
|
||||
url = https://github.com/zyedidia/tcell
|
||||
@@ -55,3 +52,21 @@
|
||||
[submodule "cmd/micro/vendor/github.com/zyedidia/poller"]
|
||||
path = cmd/micro/vendor/github.com/zyedidia/poller
|
||||
url = https://github.com/zyedidia/poller
|
||||
[submodule "cmd/micro/vendor/github.com/flynn/json5"]
|
||||
path = cmd/micro/vendor/github.com/flynn/json5
|
||||
url = https://github.com/flynn/json5
|
||||
[submodule "cmd/micro/vendor/github.com/zyedidia/terminal"]
|
||||
path = cmd/micro/vendor/github.com/zyedidia/terminal
|
||||
url = https://github.com/zyedidia/terminal
|
||||
[submodule "cmd/micro/vendor/github.com/zyedidia/pty"]
|
||||
path = cmd/micro/vendor/github.com/zyedidia/pty
|
||||
url = https://github.com/zyedidia/pty
|
||||
[submodule "cmd/micro/vendor/github.com/smartystreets/goconvey"]
|
||||
path = cmd/micro/vendor/github.com/smartystreets/goconvey
|
||||
url = https://github.com/smartystreets/goconvey
|
||||
[submodule "cmd/micro/vendor/github.com/jtolds/gls"]
|
||||
path = cmd/micro/vendor/github.com/jtolds/gls
|
||||
url = https://github.com/jtolds/gls
|
||||
[submodule "cmd/micro/vendor/github.com/smartystreets/assertions"]
|
||||
path = cmd/micro/vendor/github.com/smartystreets/assertions
|
||||
url = https://github.com/smartystreets/assertions
|
||||
|
||||
4
LICENSE
4
LICENSE
@@ -1,6 +1,6 @@
|
||||
Micro is licensed under the MIT "Expat" License:
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016: Zachary Yedidia, et al.
|
||||
Copyright (c) 2016-2017: 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
|
||||
|
||||
@@ -58,7 +58,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
|
||||
github.com/gdamore/encoding/LICENSE
|
||||
================
|
||||
|
||||
@@ -395,7 +394,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
github.com/zyedidia/clipboard/LICENSE
|
||||
github.com/atotto/clipboard/LICENSE
|
||||
================
|
||||
github.com/zyedidia/clipboard/LICENSE (fork)
|
||||
================
|
||||
|
||||
Copyright (c) 2013 Ato Araki. All rights reserved.
|
||||
@@ -427,7 +428,9 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
github.com/zyedidia/tcell/LICENSE
|
||||
github.com/gdamore/tcell/LICENSE
|
||||
================
|
||||
github.com/zyedidia/tcell/LICENSE (fork)
|
||||
================
|
||||
|
||||
|
||||
@@ -665,39 +668,6 @@ 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/sys/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
|
||||
================
|
||||
|
||||
@@ -1106,3 +1076,311 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
|
||||
github.com/flynn/json5/LICENSE
|
||||
================
|
||||
|
||||
Decoder code based on package encoding/json from the Go language.
|
||||
|
||||
Copyright (c) 2012 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.
|
||||
|
||||
|
||||
|
||||
Test data based on the parse cases from https://github.com/json5/json5
|
||||
|
||||
Copyright (c) 2012-2016 Aseem Kishore, and others.
|
||||
|
||||
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/james4k/terminal/LICENSE
|
||||
================
|
||||
github.com/zyedidia/terminal/LICENSE (fork)
|
||||
================
|
||||
|
||||
Copyright (C) 2013 James Gray
|
||||
|
||||
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 liitation 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 thismssion 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/kr/pty/License
|
||||
================
|
||||
github.com/zyedidia/pty/License (fork)
|
||||
================
|
||||
|
||||
Copyright (c) 2011 Keith Rarick
|
||||
|
||||
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/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)
|
||||
================
|
||||
|
||||
Copyright (c) 2014, Nick Patavalis (npat@efault.net)
|
||||
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.
|
||||
|
||||
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 HOLDER 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.
|
||||
|
||||
github.com/zyedidia/glob
|
||||
================
|
||||
|
||||
Glob is licensed under the MIT "Expat" License:
|
||||
|
||||
Copyright (c) 2016: Zachary Yedidia.
|
||||
|
||||
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/dustin/go-humanize/LICENSE
|
||||
================
|
||||
|
||||
Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net>
|
||||
|
||||
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.
|
||||
|
||||
<http://www.opensource.org/licenses/mit-license.php>
|
||||
|
||||
gopkg.in/yaml.v2/LICENSE
|
||||
================
|
||||
|
||||
Copyright 2011-2016 Canonical Ltd.
|
||||
|
||||
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.
|
||||
|
||||
github.com/lucasb-eyer/go-colorful/LICENSE
|
||||
================
|
||||
|
||||
Copyright (c) 2013 Lucas Beyer
|
||||
|
||||
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.
|
||||
|
||||
58
README.md
58
README.md
@@ -4,6 +4,7 @@
|
||||
[](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/blob/master/LICENSE)
|
||||
[](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.
|
||||
@@ -19,6 +20,22 @@ To see more screenshots of micro, showcasing all of the default colorschemes, se
|
||||
|
||||
You can also check out the website for Micro at https://micro-editor.github.io.
|
||||
|
||||
# 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)
|
||||
- [Linux clipboard support](#linux-clipboard-support)
|
||||
- [Colors and syntax highlighting](#colors-and-syntax-highlighting)
|
||||
- [Plan9, Cygwin](#plan9-cygwin)
|
||||
- [Usage](#usage)
|
||||
- [Documentation and Help](#documentation-and-help)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
- - -
|
||||
|
||||
# Features
|
||||
|
||||
* Easy to use and to install
|
||||
@@ -29,6 +46,7 @@ You can also check out the website for Micro at https://micro-editor.github.io.
|
||||
* 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)
|
||||
@@ -49,7 +67,7 @@ You can also check out the website for Micro at https://micro-editor.github.io.
|
||||
* Macros
|
||||
* Common editor things such as undo/redo, line numbers, Unicode support, softwrap...
|
||||
|
||||
Although not yet implemented, I hope to add more features such as autocompletion ([#174](https://github.com/zyedidia/micro/issues/174)), and multiple cursors ([#5](https://github.com/zyedidia/micro/issues/5)) in the future.
|
||||
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
|
||||
|
||||
@@ -68,7 +86,19 @@ 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`.
|
||||
|
||||
### Package Managers
|
||||
### 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)).
|
||||
|
||||
Then you can easily install micro:
|
||||
|
||||
$ 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.
|
||||
|
||||
### Package managers
|
||||
|
||||
You can install micro using Homebrew on Mac:
|
||||
|
||||
@@ -76,23 +106,35 @@ You can install micro using Homebrew on Mac:
|
||||
brew install micro
|
||||
```
|
||||
|
||||
On Windows, you can install micro through Chocolatey:
|
||||
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
|
||||
```
|
||||
|
||||
On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/install)
|
||||
|
||||
```
|
||||
snap install micro --beta
|
||||
snap install micro --classic
|
||||
```
|
||||
|
||||
On OpenBSD, micro is available in the ports tree. It is also available as a binary package.
|
||||
|
||||
```
|
||||
pkg_add -v 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 recommand setting it to `~/go` if you don't have one).
|
||||
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).
|
||||
|
||||
```
|
||||
go get -d github.com/zyedidia/micro/cmd/micro
|
||||
@@ -108,7 +150,7 @@ You can install directly with `go get` (`go get -u github.com/zyedidia/micro/cmd
|
||||
|
||||
### MacOS terminal
|
||||
|
||||
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. The newest versions also support true color.
|
||||
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.
|
||||
|
||||
### Linux clipboard support
|
||||
|
||||
@@ -136,11 +178,11 @@ 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.
|
||||
|
||||
### Plan9, NaCl, Cygwin
|
||||
### Plan9, Cygwin
|
||||
|
||||
Please note that 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, NaCl, and Cygwin (although this may change in the future).
|
||||
Plan9, and Cygwin (although this may change in the future). Micro also doesn't support NaCl (but NaCl is deprecated anyways).
|
||||
|
||||
# Usage
|
||||
|
||||
|
||||
BIN
assets/logo.png
BIN
assets/logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 4.2 KiB |
63
assets/logo.svg
Normal file
63
assets/logo.svg
Normal file
@@ -0,0 +1,63 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 289 KiB After Width: | Height: | Size: 68 KiB |
73
assets/packaging/micro.1
Normal file
73
assets/packaging/micro.1
Normal file
@@ -0,0 +1,73 @@
|
||||
.\" 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"
|
||||
.SH NAME
|
||||
micro \- A modern and intuitive terminal-based text editor
|
||||
.SH SYNOPSIS
|
||||
.B micro
|
||||
.RB [OPTIONS]
|
||||
[FILE]\&...
|
||||
|
||||
.SH DESCRIPTION
|
||||
|
||||
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.
|
||||
|
||||
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).
|
||||
|
||||
.SH OPTIONS
|
||||
.PP
|
||||
\-config-dir dir
|
||||
.RS 4
|
||||
Specify a custom location for the configuration directory
|
||||
.RE
|
||||
.PP
|
||||
\-startpos 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
|
||||
\-version
|
||||
.RS 4
|
||||
Show the version number and information
|
||||
.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.
|
||||
|
||||
.SH NOTICE
|
||||
This manpage is intended only to serve as a quick guide to the invocation of
|
||||
micro and is not intended to replace the full documentation included with micro
|
||||
which can be accessed from within micro. Micro tells you what key combination to
|
||||
press to get help in the lower right.
|
||||
|
||||
.SH BUGS
|
||||
A comprehensive list of bugs will not be listed in this manpage. See the Github
|
||||
page at \fBhttps://github.com/zyedidia/micro/issues\fP for a list of known bugs
|
||||
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.
|
||||
See /usr/share/doc/micro/LICENSE and /usr/share/doc/micro/AUTHORS for more information.
|
||||
15
assets/packaging/micro.desktop
Normal file
15
assets/packaging/micro.desktop
Normal file
@@ -0,0 +1,15 @@
|
||||
[Desktop Entry]
|
||||
|
||||
Name=Micro
|
||||
GenericName=Text Editor
|
||||
Comment=Edit text files in a terminal
|
||||
|
||||
Icon=micro
|
||||
Type=Application
|
||||
Categories=terminal;TextEditor;
|
||||
Keywords=text;editor;syntax;terminal;
|
||||
|
||||
Exec=micro %U
|
||||
StartupNotify=false
|
||||
Terminal=true
|
||||
MimeType=text/plain;text/x-chdr;text/x-csrc;text/x-c++hdr;text/x-c++src;text/x-java;text/x-dsrc;text/x-pascal;text/x-perl;text/x-python;application/x-php;application/x-httpd-php3;application/x-httpd-php4;application/x-httpd-php5;application/xml;text/html;text/css;text/x-sql;text/x-diff;
|
||||
@@ -1,13 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/clipboard"
|
||||
"github.com/zyedidia/micro/cmd/micro/shellwords"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -76,7 +80,7 @@ func (v *View) MousePress(usePlugin bool, e *tcell.EventMouse) bool {
|
||||
v.Cursor.ResetSelection()
|
||||
v.Relocate()
|
||||
}
|
||||
if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold {
|
||||
if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold && (x == v.lastLoc.X && y == v.lastLoc.Y) {
|
||||
if v.doubleClick {
|
||||
// Triple click
|
||||
v.lastClickTime = time.Now()
|
||||
@@ -117,6 +121,8 @@ func (v *View) MousePress(usePlugin bool, e *tcell.EventMouse) bool {
|
||||
}
|
||||
}
|
||||
|
||||
v.lastLoc = Loc{x, y}
|
||||
|
||||
if usePlugin {
|
||||
PostActionCall("MousePress", v, e)
|
||||
}
|
||||
@@ -248,7 +254,7 @@ func (v *View) CursorRight(usePlugin bool) bool {
|
||||
}
|
||||
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.Loc = v.Cursor.CurSelection[1].Move(-1, v.Buf)
|
||||
v.Cursor.Loc = v.Cursor.CurSelection[1]
|
||||
v.Cursor.ResetSelection()
|
||||
v.Cursor.StoreVisualX()
|
||||
} else {
|
||||
@@ -346,7 +352,7 @@ func (v *View) SelectLeft(usePlugin bool) bool {
|
||||
}
|
||||
|
||||
loc := v.Cursor.Loc
|
||||
count := v.Buf.End().Move(-1, v.Buf)
|
||||
count := v.Buf.End()
|
||||
if loc.GreaterThan(count) {
|
||||
loc = count
|
||||
}
|
||||
@@ -369,7 +375,7 @@ func (v *View) SelectRight(usePlugin bool) bool {
|
||||
}
|
||||
|
||||
loc := v.Cursor.Loc
|
||||
count := v.Buf.End().Move(-1, v.Buf)
|
||||
count := v.Buf.End()
|
||||
if loc.GreaterThan(count) {
|
||||
loc = count
|
||||
}
|
||||
@@ -429,7 +435,11 @@ func (v *View) StartOfLine(usePlugin bool) bool {
|
||||
|
||||
v.deselect(0)
|
||||
|
||||
v.Cursor.Start()
|
||||
if v.Cursor.X != 0 {
|
||||
v.Cursor.Start()
|
||||
} else {
|
||||
v.Cursor.StartOfText()
|
||||
}
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("StartOfLine", v)
|
||||
@@ -453,6 +463,20 @@ func (v *View) EndOfLine(usePlugin bool) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectLine selects the entire current line
|
||||
func (v *View) SelectLine(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("SelectLine", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
v.Cursor.SelectLine()
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("SelectLine", v)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectToStartOfLine selects to the start of the current line
|
||||
func (v *View) SelectToStartOfLine(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("SelectToStartOfLine", v) {
|
||||
@@ -489,6 +513,91 @@ func (v *View) SelectToEndOfLine(usePlugin bool) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
|
||||
func (v *View) ParagraphPrevious(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("ParagraphPrevious", v) {
|
||||
return false
|
||||
}
|
||||
var line int
|
||||
for line = v.Cursor.Y; line > 0; line-- {
|
||||
if len(v.Buf.lines[line].data) == 0 && line != v.Cursor.Y {
|
||||
v.Cursor.X = 0
|
||||
v.Cursor.Y = line
|
||||
break
|
||||
}
|
||||
}
|
||||
// If no empty line found. move cursor to end of buffer
|
||||
if line == 0 {
|
||||
v.Cursor.Loc = v.Buf.Start()
|
||||
}
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("ParagraphPrevious", v)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
|
||||
func (v *View) ParagraphNext(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("ParagraphNext", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
var line int
|
||||
for line = v.Cursor.Y; line < len(v.Buf.lines); line++ {
|
||||
if len(v.Buf.lines[line].data) == 0 && line != v.Cursor.Y {
|
||||
v.Cursor.X = 0
|
||||
v.Cursor.Y = line
|
||||
break
|
||||
}
|
||||
}
|
||||
// If no empty line found. move cursor to end of buffer
|
||||
if line == len(v.Buf.lines) {
|
||||
v.Cursor.Loc = v.Buf.End()
|
||||
}
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("ParagraphNext", v)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Retab changes all tabs to spaces or all spaces to tabs depending
|
||||
// on the user's settings
|
||||
func (v *View) Retab(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("Retab", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
toSpaces := v.Buf.Settings["tabstospaces"].(bool)
|
||||
tabsize := int(v.Buf.Settings["tabsize"].(float64))
|
||||
dirty := false
|
||||
|
||||
for i := 0; i < v.Buf.NumLines; i++ {
|
||||
l := v.Buf.Line(i)
|
||||
|
||||
ws := GetLeadingWhitespace(l)
|
||||
if ws != "" {
|
||||
if toSpaces {
|
||||
ws = strings.Replace(ws, "\t", Spaces(tabsize), -1)
|
||||
} else {
|
||||
ws = strings.Replace(ws, Spaces(tabsize), "\t", -1)
|
||||
}
|
||||
}
|
||||
|
||||
l = strings.TrimLeft(l, " \t")
|
||||
v.Buf.lines[i].data = []byte(ws + l)
|
||||
dirty = true
|
||||
}
|
||||
|
||||
v.Buf.IsModified = dirty
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("Retab", v)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// CursorStart moves the cursor to the start of the buffer
|
||||
func (v *View) CursorStart(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("CursorStart", v) {
|
||||
@@ -591,10 +700,14 @@ func (v *View) InsertNewline(usePlugin bool) bool {
|
||||
}
|
||||
|
||||
ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
|
||||
cx := v.Cursor.X
|
||||
v.Buf.Insert(v.Cursor.Loc, "\n")
|
||||
// v.Cursor.Right()
|
||||
|
||||
if v.Buf.Settings["autoindent"].(bool) {
|
||||
if cx < len(ws) {
|
||||
ws = ws[0:cx]
|
||||
}
|
||||
v.Buf.Insert(v.Cursor.Loc, ws)
|
||||
// for i := 0; i < len(ws); i++ {
|
||||
// v.Cursor.Right()
|
||||
@@ -634,9 +747,9 @@ func (v *View) Backspace(usePlugin bool) bool {
|
||||
// If the user is using spaces instead of tabs and they are deleting
|
||||
// whitespace at the start of the line, we should delete as if it's a
|
||||
// tab (tabSize number of spaces)
|
||||
lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
|
||||
lineStart := sliceEnd(v.Buf.LineBytes(v.Cursor.Y), v.Cursor.X)
|
||||
tabSize := int(v.Buf.Settings["tabsize"].(float64))
|
||||
if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
|
||||
if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && utf8.RuneCount(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 {
|
||||
loc := v.Cursor.Loc
|
||||
v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
|
||||
} else {
|
||||
@@ -721,13 +834,15 @@ func (v *View) IndentSelection(usePlugin bool) bool {
|
||||
end := v.Cursor.CurSelection[1]
|
||||
if end.Y < start.Y {
|
||||
start, end = end, start
|
||||
v.Cursor.SetSelectionStart(start)
|
||||
v.Cursor.SetSelectionEnd(end)
|
||||
}
|
||||
|
||||
startY := start.Y
|
||||
endY := end.Move(-1, v.Buf).Y
|
||||
endX := end.Move(-1, v.Buf).X
|
||||
tabsize := len(v.Buf.IndentString())
|
||||
for y := startY; y <= endY; y++ {
|
||||
tabsize := len(v.Buf.IndentString())
|
||||
v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
|
||||
if y == startY && start.X > 0 {
|
||||
v.Cursor.SetSelectionStart(start.Move(tabsize, v.Buf))
|
||||
@@ -781,23 +896,18 @@ func (v *View) OutdentSelection(usePlugin bool) bool {
|
||||
end := v.Cursor.CurSelection[1]
|
||||
if end.Y < start.Y {
|
||||
start, end = end, start
|
||||
v.Cursor.SetSelectionStart(start)
|
||||
v.Cursor.SetSelectionEnd(end)
|
||||
}
|
||||
|
||||
startY := start.Y
|
||||
endY := end.Move(-1, v.Buf).Y
|
||||
endX := end.Move(-1, v.Buf).X
|
||||
for y := startY; y <= endY; y++ {
|
||||
for x := 0; x < len(v.Buf.IndentString()); x++ {
|
||||
if len(GetLeadingWhitespace(v.Buf.Line(y))) == 0 {
|
||||
break
|
||||
}
|
||||
v.Buf.Remove(Loc{0, y}, Loc{1, y})
|
||||
if y == startY && start.X > 0 {
|
||||
v.Cursor.SetSelectionStart(start.Move(-1, v.Buf))
|
||||
}
|
||||
if y == endY {
|
||||
v.Cursor.SetSelectionEnd(Loc{endX - x, endY})
|
||||
}
|
||||
}
|
||||
}
|
||||
v.Cursor.Relocate()
|
||||
@@ -841,7 +951,7 @@ func (v *View) SaveAll(usePlugin bool) bool {
|
||||
}
|
||||
|
||||
for _, t := range tabs {
|
||||
for _, v := range t.views {
|
||||
for _, v := range t.Views {
|
||||
v.Save(false)
|
||||
}
|
||||
}
|
||||
@@ -860,7 +970,7 @@ func (v *View) Save(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.Type.scratch == true {
|
||||
if v.Type.Scratch == true {
|
||||
// We can't save any view type with scratch set. eg help and log text
|
||||
return false
|
||||
}
|
||||
@@ -910,19 +1020,24 @@ func (v *View) saveToFile(filename string) {
|
||||
// SaveAs saves the buffer to disk with the given name
|
||||
func (v *View) SaveAs(usePlugin bool) bool {
|
||||
if v.mainCursor() {
|
||||
if usePlugin && !PreActionCall("Find", v) {
|
||||
if usePlugin && !PreActionCall("SaveAs", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
filename, canceled := messenger.Prompt("Filename: ", "", "Save", NoCompletion)
|
||||
if !canceled {
|
||||
// the filename might or might not be quoted, so unquote first then join the strings.
|
||||
filename = strings.Join(SplitCommandArgs(filename), " ")
|
||||
args, err := shellwords.Split(filename)
|
||||
filename = strings.Join(args, " ")
|
||||
if err != nil {
|
||||
messenger.Error("Error parsing arguments: ", err)
|
||||
return false
|
||||
}
|
||||
v.saveToFile(filename)
|
||||
}
|
||||
|
||||
if usePlugin {
|
||||
PostActionCall("Find", v)
|
||||
PostActionCall("SaveAs", v)
|
||||
}
|
||||
}
|
||||
return false
|
||||
@@ -1002,6 +1117,10 @@ func (v *View) Undo(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.Buf.curCursor == 0 {
|
||||
v.Buf.clearCursors()
|
||||
}
|
||||
|
||||
v.Buf.Undo()
|
||||
messenger.Message("Undid action")
|
||||
|
||||
@@ -1017,6 +1136,10 @@ func (v *View) Redo(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.Buf.curCursor == 0 {
|
||||
v.Buf.clearCursors()
|
||||
}
|
||||
|
||||
v.Buf.Redo()
|
||||
messenger.Message("Redid action")
|
||||
|
||||
@@ -1096,9 +1219,9 @@ func (v *View) Cut(usePlugin bool) bool {
|
||||
return PostActionCall("Cut", v)
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return v.CutLine(usePlugin)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// DuplicateLine duplicates the current line or selection
|
||||
@@ -1154,12 +1277,16 @@ func (v *View) MoveLinesUp(usePlugin bool) bool {
|
||||
messenger.Message("Can not move further up")
|
||||
return true
|
||||
}
|
||||
start := v.Cursor.CurSelection[0].Y
|
||||
end := v.Cursor.CurSelection[1].Y
|
||||
if start > end {
|
||||
end, start = start, end
|
||||
}
|
||||
|
||||
v.Buf.MoveLinesUp(
|
||||
v.Cursor.CurSelection[0].Y,
|
||||
v.Cursor.CurSelection[1].Y,
|
||||
start,
|
||||
end,
|
||||
)
|
||||
v.Cursor.UpN(1)
|
||||
v.Cursor.CurSelection[0].Y -= 1
|
||||
v.Cursor.CurSelection[1].Y -= 1
|
||||
messenger.Message("Moved up selected line(s)")
|
||||
} else {
|
||||
@@ -1171,7 +1298,6 @@ func (v *View) MoveLinesUp(usePlugin bool) bool {
|
||||
v.Cursor.Loc.Y,
|
||||
v.Cursor.Loc.Y+1,
|
||||
)
|
||||
v.Cursor.UpN(1)
|
||||
messenger.Message("Moved up current line")
|
||||
}
|
||||
v.Buf.IsModified = true
|
||||
@@ -1193,13 +1319,16 @@ func (v *View) MoveLinesDown(usePlugin bool) bool {
|
||||
messenger.Message("Can not move further down")
|
||||
return true
|
||||
}
|
||||
start := v.Cursor.CurSelection[0].Y
|
||||
end := v.Cursor.CurSelection[1].Y
|
||||
if start > end {
|
||||
end, start = start, end
|
||||
}
|
||||
|
||||
v.Buf.MoveLinesDown(
|
||||
v.Cursor.CurSelection[0].Y,
|
||||
v.Cursor.CurSelection[1].Y,
|
||||
start,
|
||||
end,
|
||||
)
|
||||
v.Cursor.DownN(1)
|
||||
v.Cursor.CurSelection[0].Y += 1
|
||||
v.Cursor.CurSelection[1].Y += 1
|
||||
messenger.Message("Moved down selected line(s)")
|
||||
} else {
|
||||
if v.Cursor.Loc.Y >= len(v.Buf.lines)-1 {
|
||||
@@ -1210,7 +1339,6 @@ func (v *View) MoveLinesDown(usePlugin bool) bool {
|
||||
v.Cursor.Loc.Y,
|
||||
v.Cursor.Loc.Y+1,
|
||||
)
|
||||
v.Cursor.DownN(1)
|
||||
messenger.Message("Moved down current line")
|
||||
}
|
||||
v.Buf.IsModified = true
|
||||
@@ -1252,6 +1380,27 @@ func (v *View) PastePrimary(usePlugin bool) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// JumpToMatchingBrace moves the cursor to the matching brace if it is
|
||||
// currently on a brace
|
||||
func (v *View) JumpToMatchingBrace(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("JumpToMatchingBrace", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, bp := range bracePairs {
|
||||
r := v.Cursor.RuneUnder(v.Cursor.X)
|
||||
if r == bp[0] || r == bp[1] {
|
||||
matchingBrace := v.Buf.FindMatchingBrace(bp, v.Cursor.Loc)
|
||||
v.Cursor.GotoLoc(matchingBrace)
|
||||
}
|
||||
}
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("JumpToMatchingBrace", v)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectAll selects the entire buffer
|
||||
func (v *View) SelectAll(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("SelectAll", v) {
|
||||
@@ -1479,20 +1628,38 @@ func (v *View) JumpLine(usePlugin bool) bool {
|
||||
}
|
||||
|
||||
// Prompt for line number
|
||||
linestring, canceled := messenger.Prompt("Jump to line # ", "", "LineNumber", NoCompletion)
|
||||
message := fmt.Sprintf("Jump to line:col (1 - %v) # ", v.Buf.NumLines)
|
||||
input, canceled := messenger.Prompt(message, "", "LineNumber", NoCompletion)
|
||||
if canceled {
|
||||
return false
|
||||
}
|
||||
lineint, err := strconv.Atoi(linestring)
|
||||
lineint = lineint - 1 // fix offset
|
||||
if err != nil {
|
||||
messenger.Error(err) // return errors
|
||||
return false
|
||||
var lineInt int
|
||||
var colInt int
|
||||
var err error
|
||||
if strings.Contains(input, ":") {
|
||||
split := strings.Split(input, ":")
|
||||
lineInt, err = strconv.Atoi(split[0])
|
||||
if err != nil {
|
||||
messenger.Message("Invalid line number")
|
||||
return false
|
||||
}
|
||||
colInt, err = strconv.Atoi(split[1])
|
||||
if err != nil {
|
||||
messenger.Message("Invalid column number")
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
lineInt, err = strconv.Atoi(input)
|
||||
if err != nil {
|
||||
messenger.Message("Invalid line number")
|
||||
return false
|
||||
}
|
||||
}
|
||||
lineInt--
|
||||
// Move cursor and view if possible.
|
||||
if lineint < v.Buf.NumLines && lineint >= 0 {
|
||||
v.Cursor.X = 0
|
||||
v.Cursor.Y = lineint
|
||||
if lineInt < v.Buf.NumLines && lineInt >= 0 {
|
||||
v.Cursor.X = colInt
|
||||
v.Cursor.Y = lineInt
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("JumpLine", v)
|
||||
@@ -1540,6 +1707,25 @@ func (v *View) ToggleHelp(usePlugin bool) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ToggleKeyMenu toggles the keymenu option and resizes all tabs
|
||||
func (v *View) ToggleKeyMenu(usePlugin bool) bool {
|
||||
if v.mainCursor() {
|
||||
if usePlugin && !PreActionCall("ToggleBindings", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
globalSettings["keymenu"] = !globalSettings["keymenu"].(bool)
|
||||
for _, tab := range tabs {
|
||||
tab.Resize()
|
||||
}
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("ToggleBindings", v)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ShellMode opens a terminal to run a shell command
|
||||
func (v *View) ShellMode(usePlugin bool) bool {
|
||||
if v.mainCursor() {
|
||||
@@ -1578,6 +1764,22 @@ func (v *View) CommandMode(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ToggleOverwriteMode lets the user toggle the text overwrite mode
|
||||
func (v *View) ToggleOverwriteMode(usePlugin bool) bool {
|
||||
if v.mainCursor() {
|
||||
if usePlugin && !PreActionCall("ToggleOverwriteMode", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
v.isOverwriteMode = !v.isOverwriteMode
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("ToggleOverwriteMode", v)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Escape leaves current mode
|
||||
func (v *View) Escape(usePlugin bool) bool {
|
||||
if v.mainCursor() {
|
||||
@@ -1606,12 +1808,12 @@ func (v *View) Quit(usePlugin bool) bool {
|
||||
// Make sure not to quit if there are unsaved changes
|
||||
if v.CanClose() {
|
||||
v.CloseBuffer()
|
||||
if len(tabs[curTab].views) > 1 {
|
||||
if len(tabs[curTab].Views) > 1 {
|
||||
v.splitNode.Delete()
|
||||
tabs[v.TabNum].Cleanup()
|
||||
tabs[v.TabNum].Resize()
|
||||
} else if len(tabs) > 1 {
|
||||
if len(tabs[v.TabNum].views) == 1 {
|
||||
if len(tabs[v.TabNum].Views) == 1 {
|
||||
tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
|
||||
for i, t := range tabs {
|
||||
t.SetNum(i)
|
||||
@@ -1629,6 +1831,7 @@ func (v *View) Quit(usePlugin bool) bool {
|
||||
}
|
||||
|
||||
screen.Fini()
|
||||
messenger.SaveHistory()
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
@@ -1649,7 +1852,7 @@ func (v *View) QuitAll(usePlugin bool) bool {
|
||||
|
||||
closeAll := true
|
||||
for _, tab := range tabs {
|
||||
for _, v := range tab.views {
|
||||
for _, v := range tab.Views {
|
||||
if !v.CanClose() {
|
||||
closeAll = false
|
||||
}
|
||||
@@ -1662,7 +1865,7 @@ func (v *View) QuitAll(usePlugin bool) bool {
|
||||
|
||||
if shouldQuit {
|
||||
for _, tab := range tabs {
|
||||
for _, v := range tab.views {
|
||||
for _, v := range tab.Views {
|
||||
v.CloseBuffer()
|
||||
}
|
||||
}
|
||||
@@ -1672,6 +1875,7 @@ func (v *View) QuitAll(usePlugin bool) bool {
|
||||
}
|
||||
|
||||
screen.Fini()
|
||||
messenger.SaveHistory()
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
@@ -1693,7 +1897,7 @@ func (v *View) AddTab(usePlugin bool) bool {
|
||||
curTab = len(tabs) - 1
|
||||
if len(tabs) == 2 {
|
||||
for _, t := range tabs {
|
||||
for _, v := range t.views {
|
||||
for _, v := range t.Views {
|
||||
v.ToggleTabbar()
|
||||
}
|
||||
}
|
||||
@@ -1786,8 +1990,8 @@ func (v *View) Unsplit(usePlugin bool) bool {
|
||||
}
|
||||
|
||||
curView := tabs[curTab].CurView
|
||||
for i := len(tabs[curTab].views) - 1; i >= 0; i-- {
|
||||
view := tabs[curTab].views[i]
|
||||
for i := len(tabs[curTab].Views) - 1; i >= 0; i-- {
|
||||
view := tabs[curTab].Views[i]
|
||||
if view != nil && view.Num != curView {
|
||||
view.Quit(true)
|
||||
// messenger.Message("Quit ", view.Buf.Path)
|
||||
@@ -1809,7 +2013,7 @@ func (v *View) NextSplit(usePlugin bool) bool {
|
||||
}
|
||||
|
||||
tab := tabs[curTab]
|
||||
if tab.CurView < len(tab.views)-1 {
|
||||
if tab.CurView < len(tab.Views)-1 {
|
||||
tab.CurView++
|
||||
} else {
|
||||
tab.CurView = 0
|
||||
@@ -1833,7 +2037,7 @@ func (v *View) PreviousSplit(usePlugin bool) bool {
|
||||
if tab.CurView > 0 {
|
||||
tab.CurView--
|
||||
} else {
|
||||
tab.CurView = len(tab.views) - 1
|
||||
tab.CurView = len(tab.Views) - 1
|
||||
}
|
||||
|
||||
if usePlugin {
|
||||
@@ -1903,7 +2107,7 @@ func (v *View) PlayMacro(usePlugin bool) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SpawnMultiCursor creates a new multiple cursor at the next occurence of the current selection or current word
|
||||
// SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
|
||||
func (v *View) SpawnMultiCursor(usePlugin bool) bool {
|
||||
spawner := v.Buf.cursors[len(v.Buf.cursors)-1]
|
||||
// You can only spawn a cursor from the main cursor
|
||||
@@ -1923,7 +2127,7 @@ func (v *View) SpawnMultiCursor(usePlugin bool) bool {
|
||||
|
||||
searchStart = spawner.CurSelection[1]
|
||||
v.Cursor = c
|
||||
Search(sel, v, true)
|
||||
Search(regexp.QuoteMeta(sel), v, true)
|
||||
|
||||
for _, cur := range v.Buf.cursors {
|
||||
if c.Loc == cur.Loc {
|
||||
@@ -1944,6 +2148,54 @@ func (v *View) SpawnMultiCursor(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
|
||||
func (v *View) SpawnMultiCursorSelect(usePlugin bool) bool {
|
||||
if v.Cursor == &v.Buf.Cursor {
|
||||
if usePlugin && !PreActionCall("SpawnMultiCursorSelect", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Avoid cases where multiple cursors already exist, that would create problems
|
||||
if len(v.Buf.cursors) > 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
var startLine int
|
||||
var endLine int
|
||||
|
||||
a, b := v.Cursor.CurSelection[0].Y, v.Cursor.CurSelection[1].Y
|
||||
if a > b {
|
||||
startLine, endLine = b, a
|
||||
} else {
|
||||
startLine, endLine = a, b
|
||||
}
|
||||
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.ResetSelection()
|
||||
v.Cursor.GotoLoc(Loc{0, startLine})
|
||||
|
||||
for i := startLine; i <= endLine; i++ {
|
||||
c := &Cursor{
|
||||
buf: v.Buf,
|
||||
}
|
||||
c.GotoLoc(Loc{0, i})
|
||||
v.Buf.cursors = append(v.Buf.cursors, c)
|
||||
}
|
||||
v.Buf.MergeCursors()
|
||||
v.Buf.UpdateCursors()
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
if usePlugin {
|
||||
PostActionCall("SpawnMultiCursorSelect", v)
|
||||
}
|
||||
|
||||
messenger.Message("Added cursors from selection")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
|
||||
func (v *View) MouseMultiCursor(usePlugin bool, e *tcell.EventMouse) bool {
|
||||
if v.Cursor == &v.Buf.Cursor {
|
||||
@@ -1963,6 +2215,7 @@ func (v *View) MouseMultiCursor(usePlugin bool, e *tcell.EventMouse) bool {
|
||||
v.Cursor = &v.Buf.Cursor
|
||||
|
||||
v.Buf.cursors = append(v.Buf.cursors, c)
|
||||
v.Buf.MergeCursors()
|
||||
v.Buf.UpdateCursors()
|
||||
|
||||
if usePlugin {
|
||||
@@ -1984,7 +2237,7 @@ func (v *View) SkipMultiCursor(usePlugin bool) bool {
|
||||
|
||||
searchStart = cursor.CurSelection[1]
|
||||
v.Cursor = cursor
|
||||
Search(sel, v, true)
|
||||
Search(regexp.QuoteMeta(sel), v, true)
|
||||
v.Relocate()
|
||||
v.Cursor = cursor
|
||||
|
||||
@@ -2027,12 +2280,7 @@ func (v *View) RemoveAllMultiCursors(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 1; i < len(v.Buf.cursors); i++ {
|
||||
v.Buf.cursors[i] = nil
|
||||
}
|
||||
v.Buf.cursors = v.Buf.cursors[:1]
|
||||
v.Buf.UpdateCursors()
|
||||
v.Cursor.ResetSelection()
|
||||
v.Buf.clearCursors()
|
||||
v.Relocate()
|
||||
|
||||
if usePlugin {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// +build !linux
|
||||
// +build plan9 nacl windows
|
||||
|
||||
package main
|
||||
|
||||
func (v *View) Suspend(usePlugin bool) bool {
|
||||
messenger.Error("Suspend is only supported on Linux")
|
||||
messenger.Error("Suspend is only supported on Posix")
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build linux darwin dragonfly solaris openbsd netbsd freebsd
|
||||
|
||||
package main
|
||||
|
||||
import "syscall"
|
||||
@@ -19,8 +21,7 @@ func (v *View) Suspend(usePlugin bool) bool {
|
||||
|
||||
// suspend the process
|
||||
pid := syscall.Getpid()
|
||||
tid := syscall.Gettid()
|
||||
err := syscall.Tgkill(pid, tid, syscall.SIGSTOP)
|
||||
err := syscall.Kill(pid, syscall.SIGSTOP)
|
||||
if err != nil {
|
||||
TermMessage(err)
|
||||
}
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
var pluginCompletions []func(string) []string
|
||||
@@ -22,13 +20,9 @@ func FileComplete(input string) (string, []string) {
|
||||
var files []os.FileInfo
|
||||
var err error
|
||||
if len(dirs) > 1 {
|
||||
home, _ := homedir.Dir()
|
||||
|
||||
directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
|
||||
|
||||
if strings.HasPrefix(directories, "~") {
|
||||
directories = strings.Replace(directories, "~", home, 1)
|
||||
}
|
||||
directories = ReplaceHome(directories)
|
||||
files, err = ioutil.ReadDir(directories)
|
||||
} else {
|
||||
files, err = ioutil.ReadDir(".")
|
||||
@@ -148,6 +142,63 @@ func OptionComplete(input string) (string, []string) {
|
||||
return chosen, suggestions
|
||||
}
|
||||
|
||||
// OptionValueComplete completes values for various options
|
||||
func OptionValueComplete(inputOpt, input string) (string, []string) {
|
||||
inputOpt = strings.TrimSpace(inputOpt)
|
||||
var suggestions []string
|
||||
localSettings := DefaultLocalSettings()
|
||||
var optionVal interface{}
|
||||
for k, option := range globalSettings {
|
||||
if k == inputOpt {
|
||||
optionVal = option
|
||||
}
|
||||
}
|
||||
for k, option := range localSettings {
|
||||
if k == inputOpt {
|
||||
optionVal = option
|
||||
}
|
||||
}
|
||||
|
||||
switch optionVal.(type) {
|
||||
case bool:
|
||||
if strings.HasPrefix("on", input) {
|
||||
suggestions = append(suggestions, "on")
|
||||
} else if strings.HasPrefix("true", input) {
|
||||
suggestions = append(suggestions, "true")
|
||||
}
|
||||
if strings.HasPrefix("off", input) {
|
||||
suggestions = append(suggestions, "off")
|
||||
} else if strings.HasPrefix("false", input) {
|
||||
suggestions = append(suggestions, "false")
|
||||
}
|
||||
case string:
|
||||
switch inputOpt {
|
||||
case "colorscheme":
|
||||
_, suggestions = ColorschemeComplete(input)
|
||||
case "fileformat":
|
||||
if strings.HasPrefix("unix", input) {
|
||||
suggestions = append(suggestions, "unix")
|
||||
}
|
||||
if strings.HasPrefix("dos", input) {
|
||||
suggestions = append(suggestions, "dos")
|
||||
}
|
||||
case "sucmd":
|
||||
if strings.HasPrefix("sudo", input) {
|
||||
suggestions = append(suggestions, "sudo")
|
||||
}
|
||||
if strings.HasPrefix("doas", input) {
|
||||
suggestions = append(suggestions, "doas")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var chosen string
|
||||
if len(suggestions) == 1 {
|
||||
chosen = suggestions[0]
|
||||
}
|
||||
return chosen, suggestions
|
||||
}
|
||||
|
||||
// MakeCompletion registers a function from a plugin for autocomplete commands
|
||||
func MakeCompletion(function string) Completion {
|
||||
pluginCompletions = append(pluginCompletions, LuaFunctionComplete(function))
|
||||
@@ -169,6 +220,7 @@ func PluginComplete(complete Completion, input string) (chosen string, suggestio
|
||||
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) {
|
||||
@@ -182,6 +234,7 @@ func PluginCmdComplete(input string) (chosen string, suggestions []string) {
|
||||
return chosen, suggestions
|
||||
}
|
||||
|
||||
// PluginnameComplete completes with the names of loaded plugins
|
||||
func PluginNameComplete(input string) (chosen string, suggestions []string) {
|
||||
for _, pp := range GetAllPluginPackages() {
|
||||
if strings.HasPrefix(pp.Name, input) {
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/zyedidia/json5/encoding/json5"
|
||||
"github.com/flynn/json5"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
var bindingsStr map[string]string
|
||||
var bindings map[Key][]func(*View, bool) bool
|
||||
var mouseBindings map[Key][]func(*View, bool, *tcell.EventMouse) bool
|
||||
var helpBinding string
|
||||
var kmenuBinding string
|
||||
|
||||
var mouseBindingActions = map[string]func(*View, bool, *tcell.EventMouse) bool{
|
||||
"MousePress": (*View).MousePress,
|
||||
@@ -19,90 +23,97 @@ var mouseBindingActions = map[string]func(*View, bool, *tcell.EventMouse) bool{
|
||||
}
|
||||
|
||||
var bindingActions = map[string]func(*View, bool) bool{
|
||||
"CursorUp": (*View).CursorUp,
|
||||
"CursorDown": (*View).CursorDown,
|
||||
"CursorPageUp": (*View).CursorPageUp,
|
||||
"CursorPageDown": (*View).CursorPageDown,
|
||||
"CursorLeft": (*View).CursorLeft,
|
||||
"CursorRight": (*View).CursorRight,
|
||||
"CursorStart": (*View).CursorStart,
|
||||
"CursorEnd": (*View).CursorEnd,
|
||||
"SelectToStart": (*View).SelectToStart,
|
||||
"SelectToEnd": (*View).SelectToEnd,
|
||||
"SelectUp": (*View).SelectUp,
|
||||
"SelectDown": (*View).SelectDown,
|
||||
"SelectLeft": (*View).SelectLeft,
|
||||
"SelectRight": (*View).SelectRight,
|
||||
"WordRight": (*View).WordRight,
|
||||
"WordLeft": (*View).WordLeft,
|
||||
"SelectWordRight": (*View).SelectWordRight,
|
||||
"SelectWordLeft": (*View).SelectWordLeft,
|
||||
"DeleteWordRight": (*View).DeleteWordRight,
|
||||
"DeleteWordLeft": (*View).DeleteWordLeft,
|
||||
"SelectToStartOfLine": (*View).SelectToStartOfLine,
|
||||
"SelectToEndOfLine": (*View).SelectToEndOfLine,
|
||||
"InsertNewline": (*View).InsertNewline,
|
||||
"InsertSpace": (*View).InsertSpace,
|
||||
"Backspace": (*View).Backspace,
|
||||
"Delete": (*View).Delete,
|
||||
"InsertTab": (*View).InsertTab,
|
||||
"Save": (*View).Save,
|
||||
"SaveAll": (*View).SaveAll,
|
||||
"SaveAs": (*View).SaveAs,
|
||||
"Find": (*View).Find,
|
||||
"FindNext": (*View).FindNext,
|
||||
"FindPrevious": (*View).FindPrevious,
|
||||
"Center": (*View).Center,
|
||||
"Undo": (*View).Undo,
|
||||
"Redo": (*View).Redo,
|
||||
"Copy": (*View).Copy,
|
||||
"Cut": (*View).Cut,
|
||||
"CutLine": (*View).CutLine,
|
||||
"DuplicateLine": (*View).DuplicateLine,
|
||||
"DeleteLine": (*View).DeleteLine,
|
||||
"MoveLinesUp": (*View).MoveLinesUp,
|
||||
"MoveLinesDown": (*View).MoveLinesDown,
|
||||
"IndentSelection": (*View).IndentSelection,
|
||||
"OutdentSelection": (*View).OutdentSelection,
|
||||
"OutdentLine": (*View).OutdentLine,
|
||||
"Paste": (*View).Paste,
|
||||
"PastePrimary": (*View).PastePrimary,
|
||||
"SelectAll": (*View).SelectAll,
|
||||
"OpenFile": (*View).OpenFile,
|
||||
"Start": (*View).Start,
|
||||
"End": (*View).End,
|
||||
"PageUp": (*View).PageUp,
|
||||
"PageDown": (*View).PageDown,
|
||||
"HalfPageUp": (*View).HalfPageUp,
|
||||
"HalfPageDown": (*View).HalfPageDown,
|
||||
"StartOfLine": (*View).StartOfLine,
|
||||
"EndOfLine": (*View).EndOfLine,
|
||||
"ToggleHelp": (*View).ToggleHelp,
|
||||
"ToggleRuler": (*View).ToggleRuler,
|
||||
"JumpLine": (*View).JumpLine,
|
||||
"ClearStatus": (*View).ClearStatus,
|
||||
"ShellMode": (*View).ShellMode,
|
||||
"CommandMode": (*View).CommandMode,
|
||||
"Escape": (*View).Escape,
|
||||
"Quit": (*View).Quit,
|
||||
"QuitAll": (*View).QuitAll,
|
||||
"AddTab": (*View).AddTab,
|
||||
"PreviousTab": (*View).PreviousTab,
|
||||
"NextTab": (*View).NextTab,
|
||||
"NextSplit": (*View).NextSplit,
|
||||
"PreviousSplit": (*View).PreviousSplit,
|
||||
"Unsplit": (*View).Unsplit,
|
||||
"VSplit": (*View).VSplitBinding,
|
||||
"HSplit": (*View).HSplitBinding,
|
||||
"ToggleMacro": (*View).ToggleMacro,
|
||||
"PlayMacro": (*View).PlayMacro,
|
||||
"Suspend": (*View).Suspend,
|
||||
"ScrollUp": (*View).ScrollUpAction,
|
||||
"ScrollDown": (*View).ScrollDownAction,
|
||||
"SpawnMultiCursor": (*View).SpawnMultiCursor,
|
||||
"RemoveMultiCursor": (*View).RemoveMultiCursor,
|
||||
"RemoveAllMultiCursors": (*View).RemoveAllMultiCursors,
|
||||
"SkipMultiCursor": (*View).SkipMultiCursor,
|
||||
"CursorUp": (*View).CursorUp,
|
||||
"CursorDown": (*View).CursorDown,
|
||||
"CursorPageUp": (*View).CursorPageUp,
|
||||
"CursorPageDown": (*View).CursorPageDown,
|
||||
"CursorLeft": (*View).CursorLeft,
|
||||
"CursorRight": (*View).CursorRight,
|
||||
"CursorStart": (*View).CursorStart,
|
||||
"CursorEnd": (*View).CursorEnd,
|
||||
"SelectToStart": (*View).SelectToStart,
|
||||
"SelectToEnd": (*View).SelectToEnd,
|
||||
"SelectUp": (*View).SelectUp,
|
||||
"SelectDown": (*View).SelectDown,
|
||||
"SelectLeft": (*View).SelectLeft,
|
||||
"SelectRight": (*View).SelectRight,
|
||||
"WordRight": (*View).WordRight,
|
||||
"WordLeft": (*View).WordLeft,
|
||||
"SelectWordRight": (*View).SelectWordRight,
|
||||
"SelectWordLeft": (*View).SelectWordLeft,
|
||||
"DeleteWordRight": (*View).DeleteWordRight,
|
||||
"DeleteWordLeft": (*View).DeleteWordLeft,
|
||||
"SelectLine": (*View).SelectLine,
|
||||
"SelectToStartOfLine": (*View).SelectToStartOfLine,
|
||||
"SelectToEndOfLine": (*View).SelectToEndOfLine,
|
||||
"ParagraphPrevious": (*View).ParagraphPrevious,
|
||||
"ParagraphNext": (*View).ParagraphNext,
|
||||
"InsertNewline": (*View).InsertNewline,
|
||||
"InsertSpace": (*View).InsertSpace,
|
||||
"Backspace": (*View).Backspace,
|
||||
"Delete": (*View).Delete,
|
||||
"InsertTab": (*View).InsertTab,
|
||||
"Save": (*View).Save,
|
||||
"SaveAll": (*View).SaveAll,
|
||||
"SaveAs": (*View).SaveAs,
|
||||
"Find": (*View).Find,
|
||||
"FindNext": (*View).FindNext,
|
||||
"FindPrevious": (*View).FindPrevious,
|
||||
"Center": (*View).Center,
|
||||
"Undo": (*View).Undo,
|
||||
"Redo": (*View).Redo,
|
||||
"Copy": (*View).Copy,
|
||||
"Cut": (*View).Cut,
|
||||
"CutLine": (*View).CutLine,
|
||||
"DuplicateLine": (*View).DuplicateLine,
|
||||
"DeleteLine": (*View).DeleteLine,
|
||||
"MoveLinesUp": (*View).MoveLinesUp,
|
||||
"MoveLinesDown": (*View).MoveLinesDown,
|
||||
"IndentSelection": (*View).IndentSelection,
|
||||
"OutdentSelection": (*View).OutdentSelection,
|
||||
"OutdentLine": (*View).OutdentLine,
|
||||
"Paste": (*View).Paste,
|
||||
"PastePrimary": (*View).PastePrimary,
|
||||
"SelectAll": (*View).SelectAll,
|
||||
"OpenFile": (*View).OpenFile,
|
||||
"Start": (*View).Start,
|
||||
"End": (*View).End,
|
||||
"PageUp": (*View).PageUp,
|
||||
"PageDown": (*View).PageDown,
|
||||
"HalfPageUp": (*View).HalfPageUp,
|
||||
"HalfPageDown": (*View).HalfPageDown,
|
||||
"StartOfLine": (*View).StartOfLine,
|
||||
"EndOfLine": (*View).EndOfLine,
|
||||
"ToggleHelp": (*View).ToggleHelp,
|
||||
"ToggleKeyMenu": (*View).ToggleKeyMenu,
|
||||
"ToggleRuler": (*View).ToggleRuler,
|
||||
"JumpLine": (*View).JumpLine,
|
||||
"ClearStatus": (*View).ClearStatus,
|
||||
"ShellMode": (*View).ShellMode,
|
||||
"CommandMode": (*View).CommandMode,
|
||||
"ToggleOverwriteMode": (*View).ToggleOverwriteMode,
|
||||
"Escape": (*View).Escape,
|
||||
"Quit": (*View).Quit,
|
||||
"QuitAll": (*View).QuitAll,
|
||||
"AddTab": (*View).AddTab,
|
||||
"PreviousTab": (*View).PreviousTab,
|
||||
"NextTab": (*View).NextTab,
|
||||
"NextSplit": (*View).NextSplit,
|
||||
"PreviousSplit": (*View).PreviousSplit,
|
||||
"Unsplit": (*View).Unsplit,
|
||||
"VSplit": (*View).VSplitBinding,
|
||||
"HSplit": (*View).HSplitBinding,
|
||||
"ToggleMacro": (*View).ToggleMacro,
|
||||
"PlayMacro": (*View).PlayMacro,
|
||||
"Suspend": (*View).Suspend,
|
||||
"ScrollUp": (*View).ScrollUpAction,
|
||||
"ScrollDown": (*View).ScrollDownAction,
|
||||
"SpawnMultiCursor": (*View).SpawnMultiCursor,
|
||||
"SpawnMultiCursorSelect": (*View).SpawnMultiCursorSelect,
|
||||
"RemoveMultiCursor": (*View).RemoveMultiCursor,
|
||||
"RemoveAllMultiCursors": (*View).RemoveAllMultiCursors,
|
||||
"SkipMultiCursor": (*View).SkipMultiCursor,
|
||||
"JumpToMatchingBrace": (*View).JumpToMatchingBrace,
|
||||
|
||||
// This was changed to InsertNewline but I don't want to break backwards compatibility
|
||||
"InsertEnter": (*View).InsertNewline,
|
||||
@@ -256,11 +267,13 @@ type Key struct {
|
||||
modifiers tcell.ModMask
|
||||
buttons tcell.ButtonMask
|
||||
r rune
|
||||
escape string
|
||||
}
|
||||
|
||||
// InitBindings initializes the keybindings for micro
|
||||
func InitBindings() {
|
||||
bindings = make(map[Key][]func(*View, bool) bool)
|
||||
bindingsStr = make(map[string]string)
|
||||
mouseBindings = make(map[Key][]func(*View, bool, *tcell.EventMouse) bool)
|
||||
|
||||
var parsed map[string]string
|
||||
@@ -312,16 +325,29 @@ modSearch:
|
||||
case strings.HasPrefix(k, "Shift"):
|
||||
k = k[5:]
|
||||
modifiers |= tcell.ModShift
|
||||
case strings.HasPrefix(k, "\x1b"):
|
||||
return Key{
|
||||
keyCode: -1,
|
||||
modifiers: modifiers,
|
||||
buttons: -1,
|
||||
r: 0,
|
||||
escape: k,
|
||||
}, true
|
||||
default:
|
||||
break modSearch
|
||||
}
|
||||
}
|
||||
|
||||
if len(k) == 0 {
|
||||
return Key{buttons: -1}, false
|
||||
}
|
||||
|
||||
// Control is handled specially, since some character codes in bindingKeys
|
||||
// are different when Control is depressed. We should check for Control keys
|
||||
// first.
|
||||
if modifiers&tcell.ModCtrl != 0 {
|
||||
// see if the key is in bindingKeys with the Ctrl prefix.
|
||||
k = string(unicode.ToUpper(rune(k[0]))) + k[1:]
|
||||
if code, ok := bindingKeys["Ctrl"+k]; ok {
|
||||
// It is, we're done.
|
||||
return Key{
|
||||
@@ -387,6 +413,43 @@ func findMouseAction(v string) func(*View, bool, *tcell.EventMouse) bool {
|
||||
return action
|
||||
}
|
||||
|
||||
// TryBindKey tries to bind a key by writing to configDir/bindings.json
|
||||
// This function is unused for now
|
||||
func TryBindKey(k, v string) {
|
||||
filename := configDir + "/bindings.json"
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
TermMessage("Error reading bindings.json file: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
conflict := -1
|
||||
lines := strings.Split(string(input), "\n")
|
||||
for i, l := range lines {
|
||||
parts := strings.Split(l, ":")
|
||||
if len(parts) >= 2 {
|
||||
if strings.Contains(parts[0], k) {
|
||||
conflict = i
|
||||
TermMessage("Warning: Keybinding conflict:", k, " has been overwritten")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding := fmt.Sprintf(" \"%s\": \"%s\",", k, v)
|
||||
if conflict == -1 {
|
||||
lines = append([]string{lines[0], binding}, lines[conflict:]...)
|
||||
} else {
|
||||
lines = append(append(lines[:conflict], binding), lines[conflict+1:]...)
|
||||
}
|
||||
txt := strings.Join(lines, "\n")
|
||||
err = ioutil.WriteFile(filename, []byte(txt), 0644)
|
||||
if err != nil {
|
||||
TermMessage("Error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BindKey takes a key and an action and binds the two together
|
||||
func BindKey(k, v string) {
|
||||
key, ok := findKey(k)
|
||||
@@ -397,14 +460,21 @@ func BindKey(k, v string) {
|
||||
if v == "ToggleHelp" {
|
||||
helpBinding = k
|
||||
}
|
||||
if v == "ToggleKeyMenu" {
|
||||
kmenuBinding = k
|
||||
}
|
||||
if helpBinding == k && v != "ToggleHelp" {
|
||||
helpBinding = ""
|
||||
}
|
||||
if kmenuBinding == k && v != "ToggleKeyMenu" {
|
||||
kmenuBinding = ""
|
||||
}
|
||||
|
||||
actionNames := strings.Split(v, ",")
|
||||
if actionNames[0] == "UnbindKey" {
|
||||
delete(bindings, key)
|
||||
delete(mouseBindings, key)
|
||||
delete(bindingsStr, k)
|
||||
if len(actionNames) == 1 {
|
||||
return
|
||||
}
|
||||
@@ -415,6 +485,12 @@ func BindKey(k, v string) {
|
||||
for _, actionName := range actionNames {
|
||||
if strings.HasPrefix(actionName, "Mouse") {
|
||||
mouseActions = append(mouseActions, findMouseAction(actionName))
|
||||
} else if strings.HasPrefix(actionName, "command:") {
|
||||
cmd := strings.SplitN(actionName, ":", 2)[1]
|
||||
actions = append(actions, CommandAction(cmd))
|
||||
} else if strings.HasPrefix(actionName, "command-edit:") {
|
||||
cmd := strings.SplitN(actionName, ":", 2)[1]
|
||||
actions = append(actions, CommandEditAction(cmd))
|
||||
} else {
|
||||
actions = append(actions, findAction(actionName))
|
||||
}
|
||||
@@ -424,6 +500,7 @@ func BindKey(k, v string) {
|
||||
// Can't have a binding be both mouse and normal
|
||||
delete(mouseBindings, key)
|
||||
bindings[key] = actions
|
||||
bindingsStr[k] = v
|
||||
} else if len(mouseActions) > 0 {
|
||||
// Can't have a binding be both mouse and normal
|
||||
delete(bindings, key)
|
||||
@@ -458,6 +535,8 @@ func DefaultBindings() map[string]string {
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Alt-{": "ParagraphPrevious",
|
||||
"Alt-}": "ParagraphNext",
|
||||
"Enter": "InsertNewline",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
@@ -490,6 +569,7 @@ func DefaultBindings() map[string]string {
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"CtrlG": "ToggleHelp",
|
||||
"Alt-g": "ToggleKeyMenu",
|
||||
"CtrlR": "ToggleRuler",
|
||||
"CtrlL": "JumpLine",
|
||||
"Delete": "Delete",
|
||||
@@ -499,6 +579,7 @@ func DefaultBindings() map[string]string {
|
||||
"CtrlW": "NextSplit",
|
||||
"CtrlU": "ToggleMacro",
|
||||
"CtrlJ": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
@@ -509,7 +590,6 @@ func DefaultBindings() map[string]string {
|
||||
// "Alt-n": "CursorDown",
|
||||
|
||||
// Integration with file managers
|
||||
"F1": "ToggleHelp",
|
||||
"F2": "Save",
|
||||
"F3": "Find",
|
||||
"F4": "Quit",
|
||||
@@ -525,6 +605,7 @@ func DefaultBindings() map[string]string {
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
|
||||
@@ -2,7 +2,9 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -15,10 +17,16 @@ import (
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/zyedidia/micro/cmd/micro/highlight"
|
||||
)
|
||||
|
||||
var (
|
||||
// 0 - no line type detected
|
||||
// 1 - lf detected
|
||||
// 2 - crlf detected
|
||||
fileformat = 0
|
||||
)
|
||||
|
||||
// Buffer stores the text for files that are loaded into the text editor
|
||||
// It uses a rope to efficiently store the string and contains some
|
||||
// simple functions for saving and wrapper functions for modifying the rope
|
||||
@@ -45,11 +53,15 @@ type Buffer struct {
|
||||
// Stores the last modification time of the file the buffer is pointing to
|
||||
ModTime time.Time
|
||||
|
||||
// NumLines is the number of lines in the buffer
|
||||
NumLines int
|
||||
|
||||
syntaxDef *highlight.Def
|
||||
highlighter *highlight.Highlighter
|
||||
|
||||
// Hash of the original buffer -- empty if fastdirty is on
|
||||
origHash [16]byte
|
||||
|
||||
// Buffer local settings
|
||||
Settings map[string]interface{}
|
||||
}
|
||||
@@ -62,15 +74,64 @@ type SerializedBuffer struct {
|
||||
ModTime time.Time
|
||||
}
|
||||
|
||||
// NewBufferFromFile opens a new buffer using the given filepath
|
||||
// It will also automatically handle `~`, and line/column with filename:l:c
|
||||
// It will return an empty buffer if the filepath does not exist
|
||||
// and an error if the file is a directory
|
||||
func NewBufferFromFile(path string) (*Buffer, error) {
|
||||
filename := GetPath(path)
|
||||
filename = ReplaceHome(filename)
|
||||
file, err := os.Open(filename)
|
||||
fileInfo, _ := os.Stat(filename)
|
||||
|
||||
if err == nil && fileInfo.IsDir() {
|
||||
return nil, errors.New(filename + " is a directory")
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
var buf *Buffer
|
||||
if err != nil {
|
||||
// File does not exist -- create an empty buffer with that name
|
||||
buf = NewBufferFromString("", path)
|
||||
} else {
|
||||
buf = NewBuffer(file, FSize(file), path)
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// NewBufferFromString creates a new buffer containing the given
|
||||
// string
|
||||
func NewBufferFromString(text, path string) *Buffer {
|
||||
return NewBuffer(strings.NewReader(text), int64(len(text)), path)
|
||||
}
|
||||
|
||||
// NewBuffer creates a new buffer from a given reader with a given path
|
||||
func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
|
||||
startpos := Loc{0, 0}
|
||||
startposErr := true
|
||||
if strings.Contains(path, ":") {
|
||||
var err error
|
||||
split := strings.Split(path, ":")
|
||||
path = split[0]
|
||||
startpos.Y, err = strconv.Atoi(split[1])
|
||||
if err != nil {
|
||||
messenger.Error("Error opening file: ", err)
|
||||
} else {
|
||||
startposErr = false
|
||||
if len(split) > 2 {
|
||||
startpos.X, err = strconv.Atoi(split[2])
|
||||
if err != nil {
|
||||
messenger.Error("Error opening file: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if path != "" {
|
||||
for _, tab := range tabs {
|
||||
for _, view := range tab.views {
|
||||
for _, view := range tab.Views {
|
||||
if view.Buf.Path == path {
|
||||
return view.Buf
|
||||
}
|
||||
@@ -88,6 +149,12 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
|
||||
}
|
||||
}
|
||||
|
||||
if fileformat == 1 {
|
||||
b.Settings["fileformat"] = "unix"
|
||||
} else if fileformat == 2 {
|
||||
b.Settings["fileformat"] = "dos"
|
||||
}
|
||||
|
||||
absPath, _ := filepath.Abs(path)
|
||||
|
||||
b.Path = path
|
||||
@@ -109,11 +176,18 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
|
||||
cursorStartX := 0
|
||||
cursorStartY := 0
|
||||
// If -startpos LINE,COL was passed, use start position LINE,COL
|
||||
if len(*flagStartPos) > 0 {
|
||||
if len(*flagStartPos) > 0 || !startposErr {
|
||||
positions := strings.Split(*flagStartPos, ",")
|
||||
if len(positions) == 2 {
|
||||
lineNum, errPos1 := strconv.Atoi(positions[0])
|
||||
colNum, errPos2 := strconv.Atoi(positions[1])
|
||||
if len(positions) == 2 || !startposErr {
|
||||
var lineNum, colNum int
|
||||
var errPos1, errPos2 error
|
||||
if !startposErr {
|
||||
lineNum = startpos.Y
|
||||
colNum = startpos.X
|
||||
} else {
|
||||
lineNum, errPos1 = strconv.Atoi(positions[0])
|
||||
colNum, errPos2 = strconv.Atoi(positions[1])
|
||||
}
|
||||
if errPos1 == nil && errPos2 == nil {
|
||||
cursorStartX = colNum
|
||||
cursorStartY = lineNum - 1
|
||||
@@ -142,10 +216,11 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
|
||||
|
||||
InitLocalSettings(b)
|
||||
|
||||
if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
|
||||
if startposErr && len(*flagStartPos) == 0 && (b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool)) {
|
||||
// If either savecursor or saveundo is turned on, we need to load the serialized information
|
||||
// from ~/.config/micro/buffers
|
||||
file, err := os.Open(configDir + "/buffers/" + EscapePath(b.AbsPath))
|
||||
defer file.Close()
|
||||
if err == nil {
|
||||
var buffer SerializedBuffer
|
||||
decoder := gob.NewDecoder(file)
|
||||
@@ -161,14 +236,22 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
|
||||
}
|
||||
|
||||
if b.Settings["saveundo"].(bool) {
|
||||
// We should only use last time's eventhandler if the file wasn't by someone else in the meantime
|
||||
// We should only use last time's eventhandler if the file wasn't modified by someone else in the meantime
|
||||
if b.ModTime == buffer.ModTime {
|
||||
b.EventHandler = buffer.EventHandler
|
||||
b.EventHandler.buf = b
|
||||
}
|
||||
}
|
||||
}
|
||||
file.Close()
|
||||
}
|
||||
|
||||
if !b.Settings["fastdirty"].(bool) {
|
||||
if size > 50000 {
|
||||
// If the file is larger than a megabyte fastdirty needs to be on
|
||||
b.Settings["fastdirty"] = true
|
||||
} else {
|
||||
b.origHash = md5.Sum([]byte(b.String()))
|
||||
}
|
||||
}
|
||||
|
||||
b.cursors = []*Cursor{&b.Cursor}
|
||||
@@ -176,6 +259,8 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
|
||||
return b
|
||||
}
|
||||
|
||||
// GetName returns the name that should be displayed in the statusline
|
||||
// for this buffer
|
||||
func (b *Buffer) GetName() string {
|
||||
if b.name == "" {
|
||||
if b.Path == "" {
|
||||
@@ -240,7 +325,6 @@ func (b *Buffer) UpdateRules() {
|
||||
if b.syntaxDef != nil {
|
||||
highlight.ResolveIncludes(b.syntaxDef, files)
|
||||
}
|
||||
files = nil
|
||||
|
||||
if b.highlighter == nil || rehighlight {
|
||||
if b.syntaxDef != nil {
|
||||
@@ -309,6 +393,8 @@ func (b *Buffer) Update() {
|
||||
b.NumLines = len(b.lines)
|
||||
}
|
||||
|
||||
// MergeCursors merges any cursors that are at the same position
|
||||
// into one cursor
|
||||
func (b *Buffer) MergeCursors() {
|
||||
var cursors []*Cursor
|
||||
for i := 0; i < len(b.cursors); i++ {
|
||||
@@ -325,8 +411,17 @@ func (b *Buffer) MergeCursors() {
|
||||
}
|
||||
|
||||
b.cursors = cursors
|
||||
|
||||
for i := range b.cursors {
|
||||
b.cursors[i].Num = i
|
||||
}
|
||||
|
||||
if b.curCursor >= len(b.cursors) {
|
||||
b.curCursor = len(b.cursors) - 1
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateCursors updates all the cursors indicies
|
||||
func (b *Buffer) UpdateCursors() {
|
||||
for i, c := range b.cursors {
|
||||
c.Num = i
|
||||
@@ -356,7 +451,7 @@ func (b *Buffer) Serialize() error {
|
||||
b.ModTime,
|
||||
})
|
||||
}
|
||||
file.Close()
|
||||
err = file.Close()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -365,7 +460,6 @@ func (b *Buffer) Serialize() error {
|
||||
// SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
|
||||
func (b *Buffer) SaveAs(filename string) error {
|
||||
b.UpdateRules()
|
||||
dir, _ := homedir.Dir()
|
||||
if b.Settings["rmtrailingws"].(bool) {
|
||||
r, _ := regexp.Compile(`[ \t]+$`)
|
||||
for lineNum, line := range b.Lines(0, b.NumLines) {
|
||||
@@ -384,17 +478,74 @@ func (b *Buffer) SaveAs(filename string) error {
|
||||
b.Insert(end, "\n")
|
||||
}
|
||||
}
|
||||
str := b.String()
|
||||
data := []byte(str)
|
||||
err := ioutil.WriteFile(filename, data, 0644)
|
||||
if err == nil {
|
||||
b.Path = strings.Replace(filename, "~", dir, 1)
|
||||
b.IsModified = false
|
||||
|
||||
defer func() {
|
||||
b.ModTime, _ = GetModTime(filename)
|
||||
return b.Serialize()
|
||||
}()
|
||||
|
||||
// Removes any tilde and replaces with the absolute path to home
|
||||
var absFilename string = ReplaceHome(filename)
|
||||
|
||||
// Get the leading path to the file | "." is returned if there's no leading path provided
|
||||
if dirname := filepath.Dir(absFilename); dirname != "." {
|
||||
// Check if the parent dirs don't exist
|
||||
if _, statErr := os.Stat(dirname); os.IsNotExist(statErr) {
|
||||
// Prompt to make sure they want to create the dirs that are missing
|
||||
if yes, canceled := messenger.YesNoPrompt("Parent folders \"" + dirname + "\" do not exist. Create them? (y,n)"); yes && !canceled {
|
||||
// Create all leading dir(s) since they don't exist
|
||||
if mkdirallErr := os.MkdirAll(dirname, os.ModePerm); mkdirallErr != nil {
|
||||
// If there was an error creating the dirs
|
||||
return mkdirallErr
|
||||
}
|
||||
} else {
|
||||
// If they canceled the creation of leading dirs
|
||||
return errors.New("Save aborted")
|
||||
}
|
||||
}
|
||||
}
|
||||
b.ModTime, _ = GetModTime(filename)
|
||||
return err
|
||||
|
||||
f, err := os.OpenFile(absFilename, os.O_WRONLY|os.O_CREATE, 0644)
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.Truncate(0); err != nil {
|
||||
return err
|
||||
}
|
||||
useCrlf := b.Settings["fileformat"] == "dos"
|
||||
size := 0
|
||||
for i, l := range b.lines {
|
||||
size += len(l.data)
|
||||
if _, err := f.Write(l.data); err != nil {
|
||||
return err
|
||||
}
|
||||
if i != len(b.lines)-1 {
|
||||
if useCrlf {
|
||||
size += 2
|
||||
if _, err := f.Write([]byte{'\r', '\n'}); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
size++
|
||||
if _, err := f.Write([]byte{'\n'}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !b.Settings["fastdirty"].(bool) {
|
||||
if size > 50000 {
|
||||
// If the file is larger than a megabyte fastdirty needs to be on
|
||||
b.Settings["fastdirty"] = true
|
||||
} else {
|
||||
b.origHash = md5.Sum([]byte(b.String()))
|
||||
}
|
||||
}
|
||||
|
||||
b.Path = filename
|
||||
b.IsModified = false
|
||||
return b.Serialize()
|
||||
}
|
||||
|
||||
// SaveAsWithSudo is the same as SaveAs except it uses a neat trick
|
||||
@@ -408,8 +559,8 @@ func (b *Buffer) SaveAsWithSudo(filename string) error {
|
||||
screen = nil
|
||||
|
||||
// Set up everything for the command
|
||||
cmd := exec.Command("sudo", "tee", filename)
|
||||
cmd.Stdin = bytes.NewBufferString(b.String())
|
||||
cmd := exec.Command(globalSettings["sucmd"].(string), "tee", filename)
|
||||
cmd.Stdin = bytes.NewBufferString(b.SaveString(b.Settings["fileformat"] == "dos"))
|
||||
|
||||
// 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
|
||||
@@ -435,6 +586,15 @@ func (b *Buffer) SaveAsWithSudo(filename string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Modified returns if this buffer has been modified since
|
||||
// being opened
|
||||
func (b *Buffer) Modified() bool {
|
||||
if b.Settings["fastdirty"].(bool) {
|
||||
return b.IsModified
|
||||
}
|
||||
return b.origHash != md5.Sum([]byte(b.String()))
|
||||
}
|
||||
|
||||
func (b *Buffer) insert(pos Loc, value []byte) {
|
||||
b.IsModified = true
|
||||
b.LineArray.insert(pos, value)
|
||||
@@ -464,13 +624,29 @@ func (b *Buffer) End() Loc {
|
||||
|
||||
// RuneAt returns the rune at a given location in the buffer
|
||||
func (b *Buffer) RuneAt(loc Loc) rune {
|
||||
line := []rune(b.Line(loc.Y))
|
||||
line := b.LineRunes(loc.Y)
|
||||
if len(line) > 0 {
|
||||
return line[loc.X]
|
||||
}
|
||||
return '\n'
|
||||
}
|
||||
|
||||
// Line returns a single line as an array of runes
|
||||
func (b *Buffer) LineBytes(n int) []byte {
|
||||
if n >= len(b.lines) {
|
||||
return []byte{}
|
||||
}
|
||||
return b.lines[n].data
|
||||
}
|
||||
|
||||
// Line returns a single line as an array of runes
|
||||
func (b *Buffer) LineRunes(n int) []rune {
|
||||
if n >= len(b.lines) {
|
||||
return []rune{}
|
||||
}
|
||||
return toRunes(b.lines[n].data)
|
||||
}
|
||||
|
||||
// Line returns a single line
|
||||
func (b *Buffer) Line(n int) string {
|
||||
if n >= len(b.lines) {
|
||||
@@ -479,6 +655,7 @@ func (b *Buffer) Line(n int) string {
|
||||
return string(b.lines[n].data)
|
||||
}
|
||||
|
||||
// LinesNum returns the number of lines in the buffer
|
||||
func (b *Buffer) LinesNum() int {
|
||||
return len(b.lines)
|
||||
}
|
||||
@@ -550,3 +727,67 @@ func (b *Buffer) ClearMatches() {
|
||||
b.SetState(i, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) clearCursors() {
|
||||
for i := 1; i < len(b.cursors); i++ {
|
||||
b.cursors[i] = nil
|
||||
}
|
||||
b.cursors = b.cursors[:1]
|
||||
b.UpdateCursors()
|
||||
b.Cursor.ResetSelection()
|
||||
}
|
||||
|
||||
var bracePairs = [][2]rune{
|
||||
{'(', ')'},
|
||||
{'{', '}'},
|
||||
{'[', ']'},
|
||||
}
|
||||
|
||||
// FindMatchingBrace returns the location in the buffer of the matching bracket
|
||||
// It is given a brace type containing the open and closing character, (for example
|
||||
// '{' and '}') as well as the location to match from
|
||||
func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) Loc {
|
||||
curLine := b.LineRunes(start.Y)
|
||||
startChar := curLine[start.X]
|
||||
var i int
|
||||
if startChar == braceType[0] {
|
||||
for y := start.Y; y < b.NumLines; y++ {
|
||||
l := b.LineRunes(y)
|
||||
xInit := 0
|
||||
if y == start.Y {
|
||||
xInit = start.X
|
||||
}
|
||||
for x := xInit; x < len(l); x++ {
|
||||
r := l[x]
|
||||
if r == braceType[0] {
|
||||
i++
|
||||
} else if r == braceType[1] {
|
||||
i--
|
||||
if i == 0 {
|
||||
return Loc{x, y}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if startChar == braceType[1] {
|
||||
for y := start.Y; y >= 0; y-- {
|
||||
l := []rune(string(b.lines[y].data))
|
||||
xInit := len(l) - 1
|
||||
if y == start.Y {
|
||||
xInit = start.X
|
||||
}
|
||||
for x := xInit; x >= 0; x-- {
|
||||
r := l[x]
|
||||
if r == braceType[0] {
|
||||
i--
|
||||
if i == 0 {
|
||||
return Loc{x, y}
|
||||
}
|
||||
} else if r == braceType[1] {
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return start
|
||||
}
|
||||
|
||||
@@ -65,12 +65,36 @@ type CellView struct {
|
||||
}
|
||||
|
||||
func (c *CellView) Draw(buf *Buffer, top, height, left, width int) {
|
||||
if width <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
matchingBrace := Loc{-1, -1}
|
||||
// bracePairs is defined in buffer.go
|
||||
if buf.Settings["matchbrace"].(bool) {
|
||||
for _, bp := range bracePairs {
|
||||
curX := buf.Cursor.X
|
||||
curLoc := buf.Cursor.Loc
|
||||
if buf.Settings["matchbraceleft"].(bool) {
|
||||
curX--
|
||||
if curX > 0 {
|
||||
curLoc = curLoc.Move(-1, buf)
|
||||
}
|
||||
}
|
||||
|
||||
r := buf.Cursor.RuneUnder(curX)
|
||||
if r == bp[0] || r == bp[1] {
|
||||
matchingBrace = buf.FindMatchingBrace(bp, curLoc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tabsize := int(buf.Settings["tabsize"].(float64))
|
||||
softwrap := buf.Settings["softwrap"].(bool)
|
||||
indentrunes := []rune(buf.Settings["indentchar"].(string))
|
||||
// if empty indentchar settings, use space
|
||||
if indentrunes == nil || len(indentrunes) == 0 {
|
||||
indentrunes = []rune(" ")
|
||||
indentrunes = []rune{' '}
|
||||
}
|
||||
indentchar := indentrunes[0]
|
||||
|
||||
@@ -133,7 +157,13 @@ func (c *CellView) Draw(buf *Buffer, top, height, left, width int) {
|
||||
char := line[colN]
|
||||
|
||||
if viewCol >= 0 {
|
||||
c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, char, curStyle, 1}
|
||||
st := curStyle
|
||||
if colN == matchingBrace.X && lineN == matchingBrace.Y && !buf.Cursor.HasSelection() {
|
||||
st = curStyle.Reverse(true)
|
||||
}
|
||||
if viewCol < len(c.lines[viewLine]) {
|
||||
c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, char, st, 1}
|
||||
}
|
||||
}
|
||||
if char == '\t' {
|
||||
charWidth := tabsize - (viewCol+left)%tabsize
|
||||
@@ -142,7 +172,8 @@ func (c *CellView) Draw(buf *Buffer, top, height, left, width int) {
|
||||
c.lines[viewLine][viewCol].width = charWidth
|
||||
|
||||
indentStyle := curStyle
|
||||
if group, ok := colorscheme["indent-char"]; ok {
|
||||
ch := buf.Settings["indentchar"].(string)
|
||||
if group, ok := colorscheme["indent-char"]; ok && !IsStrWhitespace(ch) && ch != "" {
|
||||
indentStyle = group
|
||||
}
|
||||
|
||||
@@ -151,7 +182,7 @@ func (c *CellView) Draw(buf *Buffer, top, height, left, width int) {
|
||||
|
||||
for i := 1; i < charWidth; i++ {
|
||||
viewCol++
|
||||
if viewCol >= 0 && viewCol < lineLength {
|
||||
if viewCol >= 0 && viewCol < lineLength && viewCol < len(c.lines[viewLine]) {
|
||||
c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, ' ', curStyle, 1}
|
||||
}
|
||||
}
|
||||
@@ -163,7 +194,7 @@ func (c *CellView) Draw(buf *Buffer, top, height, left, width int) {
|
||||
}
|
||||
for i := 1; i < charWidth; i++ {
|
||||
viewCol++
|
||||
if viewCol >= 0 && viewCol < lineLength {
|
||||
if viewCol >= 0 && viewCol < lineLength && viewCol < len(c.lines[viewLine]) {
|
||||
c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, ' ', curStyle, 1}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ type Colorscheme map[string]tcell.Style
|
||||
// The current colorscheme
|
||||
var colorscheme Colorscheme
|
||||
|
||||
// This takes in a syntax group and returns the colorscheme's style for that group
|
||||
// GetColor takes in a syntax group and returns the colorscheme's style for that group
|
||||
func GetColor(color string) tcell.Style {
|
||||
st := defStyle
|
||||
if color == "" {
|
||||
@@ -54,7 +54,7 @@ func InitColorscheme() {
|
||||
Foreground(tcell.ColorDefault).
|
||||
Background(tcell.ColorDefault)
|
||||
if screen != nil {
|
||||
screen.SetStyle(defStyle)
|
||||
// screen.SetStyle(defStyle)
|
||||
}
|
||||
|
||||
LoadDefaultColorscheme()
|
||||
@@ -109,7 +109,7 @@ func ParseColorscheme(text string) Colorscheme {
|
||||
defStyle = style
|
||||
}
|
||||
if screen != nil {
|
||||
screen.SetStyle(defStyle)
|
||||
// screen.SetStyle(defStyle)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Color-link statement is not valid:", line)
|
||||
@@ -252,5 +252,9 @@ func GetColor256(color int) tcell.Color {
|
||||
tcell.Color253, tcell.Color254, tcell.Color255,
|
||||
}
|
||||
|
||||
return colors[color]
|
||||
if color >= 0 && color < len(colors) {
|
||||
return colors[color]
|
||||
}
|
||||
|
||||
return tcell.ColorDefault
|
||||
}
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/zyedidia/micro/cmd/micro/shellwords"
|
||||
)
|
||||
|
||||
// A Command contains a action (a function to call) as well as information about how to autocomplete the command
|
||||
@@ -38,6 +35,7 @@ func init() {
|
||||
"Set": Set,
|
||||
"SetLocal": SetLocal,
|
||||
"Show": Show,
|
||||
"ShowKey": ShowKey,
|
||||
"Run": Run,
|
||||
"Bind": Bind,
|
||||
"Quit": Quit,
|
||||
@@ -56,7 +54,10 @@ func init() {
|
||||
"Pwd": Pwd,
|
||||
"Open": Open,
|
||||
"TabSwitch": TabSwitch,
|
||||
"Term": Term,
|
||||
"MemUsage": MemUsage,
|
||||
"Retab": Retab,
|
||||
"Raw": Raw,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,9 +91,10 @@ func MakeCommand(name, function string, completions ...Completion) {
|
||||
// DefaultCommands returns a map containing micro's default commands
|
||||
func DefaultCommands() map[string]StrCommand {
|
||||
return map[string]StrCommand{
|
||||
"set": {"Set", []Completion{OptionCompletion, NoCompletion}},
|
||||
"setlocal": {"SetLocal", []Completion{OptionCompletion, NoCompletion}},
|
||||
"set": {"Set", []Completion{OptionCompletion, OptionValueCompletion}},
|
||||
"setlocal": {"SetLocal", []Completion{OptionCompletion, OptionValueCompletion}},
|
||||
"show": {"Show", []Completion{OptionCompletion, NoCompletion}},
|
||||
"showkey": {"ShowKey", []Completion{NoCompletion}},
|
||||
"bind": {"Bind", []Completion{NoCompletion}},
|
||||
"run": {"Run", []Completion{NoCompletion}},
|
||||
"quit": {"Quit", []Completion{NoCompletion}},
|
||||
@@ -111,7 +113,32 @@ func DefaultCommands() map[string]StrCommand {
|
||||
"pwd": {"Pwd", []Completion{NoCompletion}},
|
||||
"open": {"Open", []Completion{FileCompletion}},
|
||||
"tabswitch": {"TabSwitch", []Completion{NoCompletion}},
|
||||
"term": {"Term", []Completion{NoCompletion}},
|
||||
"memusage": {"MemUsage", []Completion{NoCompletion}},
|
||||
"retab": {"Retab", []Completion{NoCompletion}},
|
||||
"raw": {"Raw", []Completion{NoCompletion}},
|
||||
}
|
||||
}
|
||||
|
||||
// CommandEditAction returns a bindable function that opens a prompt with
|
||||
// the given string and executes the command when the user presses
|
||||
// enter
|
||||
func CommandEditAction(prompt string) func(*View, bool) bool {
|
||||
return func(v *View, usePlugin bool) bool {
|
||||
input, canceled := messenger.Prompt("> ", prompt, "Command", CommandCompletion)
|
||||
if !canceled {
|
||||
HandleCommand(input)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// CommandAction returns a bindable function which executes the
|
||||
// given command
|
||||
func CommandAction(cmd string) func(*View, bool) bool {
|
||||
return func(v *View, usePlugin bool) bool {
|
||||
HandleCommand(cmd)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +177,7 @@ func PluginCmd(args []string) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !IsSpaces(removed) {
|
||||
if !IsSpaces([]byte(removed)) {
|
||||
messenger.Message("Removed ", removed)
|
||||
} else {
|
||||
messenger.Error("The requested plugins do not exist")
|
||||
@@ -198,6 +225,34 @@ func PluginCmd(args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Retab changes all spaces to tabs or all tabs to spaces
|
||||
// depending on the user's settings
|
||||
func Retab(args []string) {
|
||||
CurView().Retab(true)
|
||||
}
|
||||
|
||||
// Raw opens a new raw view which displays the escape sequences micro
|
||||
// is receiving in real-time
|
||||
func Raw(args []string) {
|
||||
buf := NewBufferFromString("", "Raw events")
|
||||
|
||||
view := NewView(buf)
|
||||
view.Buf.Insert(view.Cursor.Loc, "Warning: Showing raw event escape codes\n")
|
||||
view.Buf.Insert(view.Cursor.Loc, "Use CtrlQ to exit\n")
|
||||
view.Type = vtRaw
|
||||
tab := NewTabFromView(view)
|
||||
tab.SetNum(len(tabs))
|
||||
tabs = append(tabs, tab)
|
||||
curTab = len(tabs) - 1
|
||||
if len(tabs) == 2 {
|
||||
for _, t := range tabs {
|
||||
for _, v := range t.Views {
|
||||
v.ToggleTabbar()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TabSwitch switches to a given tab either by name or by number
|
||||
func TabSwitch(args []string) {
|
||||
if len(args) > 0 {
|
||||
@@ -207,7 +262,7 @@ func TabSwitch(args []string) {
|
||||
|
||||
found := false
|
||||
for _, t := range tabs {
|
||||
v := t.views[t.CurView]
|
||||
v := t.Views[t.CurView]
|
||||
if v.Buf.GetName() == args[0] {
|
||||
curTab = v.TabNum
|
||||
found = true
|
||||
@@ -230,12 +285,19 @@ func TabSwitch(args []string) {
|
||||
// Cd changes the current working directory
|
||||
func Cd(args []string) {
|
||||
if len(args) > 0 {
|
||||
home, _ := homedir.Dir()
|
||||
path := strings.Replace(args[0], "~", home, 1)
|
||||
os.Chdir(path)
|
||||
path := ReplaceHome(args[0])
|
||||
err := os.Chdir(path)
|
||||
if err != nil {
|
||||
messenger.Error("Error with cd: ", err)
|
||||
return
|
||||
}
|
||||
wd, _ := os.Getwd()
|
||||
for _, tab := range tabs {
|
||||
for _, view := range tab.views {
|
||||
wd, _ := os.Getwd()
|
||||
for _, view := range tab.Views {
|
||||
if len(view.Buf.name) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
view.Buf.Path, _ = MakeRelative(view.Buf.AbsPath, wd)
|
||||
if p, _ := filepath.Abs(view.Buf.Path); !strings.Contains(p, wd) {
|
||||
view.Buf.Path = view.Buf.AbsPath
|
||||
@@ -274,7 +336,12 @@ func Open(args []string) {
|
||||
if len(args) > 0 {
|
||||
filename := args[0]
|
||||
// the filename might or might not be quoted, so unquote first then join the strings.
|
||||
filename = strings.Join(SplitCommandArgs(filename), " ")
|
||||
args, err := shellwords.Split(filename)
|
||||
if err != nil {
|
||||
messenger.Error("Error parsing args ", err)
|
||||
return
|
||||
}
|
||||
filename = strings.Join(args, " ")
|
||||
|
||||
CurView().Open(filename)
|
||||
} else {
|
||||
@@ -324,25 +391,10 @@ func VSplit(args []string) {
|
||||
if len(args) == 0 {
|
||||
CurView().VSplit(NewBufferFromString("", ""))
|
||||
} else {
|
||||
filename := args[0]
|
||||
home, _ := homedir.Dir()
|
||||
filename = strings.Replace(filename, "~", home, 1)
|
||||
file, err := os.Open(filename)
|
||||
fileInfo, _ := os.Stat(filename)
|
||||
|
||||
if err == nil && fileInfo.IsDir() {
|
||||
messenger.Error(filename, " is a directory")
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
var buf *Buffer
|
||||
buf, err := NewBufferFromFile(args[0])
|
||||
if err != nil {
|
||||
// File does not exist -- create an empty buffer with that name
|
||||
buf = NewBufferFromString("", filename)
|
||||
} else {
|
||||
buf = NewBuffer(file, FSize(file), filename)
|
||||
messenger.Error(err)
|
||||
return
|
||||
}
|
||||
CurView().VSplit(buf)
|
||||
}
|
||||
@@ -354,25 +406,10 @@ func HSplit(args []string) {
|
||||
if len(args) == 0 {
|
||||
CurView().HSplit(NewBufferFromString("", ""))
|
||||
} else {
|
||||
filename := args[0]
|
||||
home, _ := homedir.Dir()
|
||||
filename = strings.Replace(filename, "~", home, 1)
|
||||
file, err := os.Open(filename)
|
||||
fileInfo, _ := os.Stat(filename)
|
||||
|
||||
if err == nil && fileInfo.IsDir() {
|
||||
messenger.Error(filename, " is a directory")
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
var buf *Buffer
|
||||
buf, err := NewBufferFromFile(args[0])
|
||||
if err != nil {
|
||||
// File does not exist -- create an empty buffer with that name
|
||||
buf = NewBufferFromString("", filename)
|
||||
} else {
|
||||
buf = NewBuffer(file, FSize(file), filename)
|
||||
messenger.Error(err)
|
||||
return
|
||||
}
|
||||
CurView().HSplit(buf)
|
||||
}
|
||||
@@ -395,24 +432,10 @@ func NewTab(args []string) {
|
||||
if len(args) == 0 {
|
||||
CurView().AddTab(true)
|
||||
} else {
|
||||
filename := args[0]
|
||||
home, _ := homedir.Dir()
|
||||
filename = strings.Replace(filename, "~", home, 1)
|
||||
file, err := os.Open(filename)
|
||||
fileInfo, _ := os.Stat(filename)
|
||||
|
||||
if err == nil && fileInfo.IsDir() {
|
||||
messenger.Error(filename, " is a directory")
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
var buf *Buffer
|
||||
buf, err := NewBufferFromFile(args[0])
|
||||
if err != nil {
|
||||
buf = NewBufferFromString("", filename)
|
||||
} else {
|
||||
buf = NewBuffer(file, FSize(file), filename)
|
||||
messenger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
tab := NewTabFromView(NewView(buf))
|
||||
@@ -421,7 +444,7 @@ func NewTab(args []string) {
|
||||
curTab = len(tabs) - 1
|
||||
if len(tabs) == 2 {
|
||||
for _, t := range tabs {
|
||||
for _, v := range t.views {
|
||||
for _, v := range t.Views {
|
||||
v.ToggleTabbar()
|
||||
}
|
||||
}
|
||||
@@ -475,6 +498,20 @@ func Show(args []string) {
|
||||
messenger.Message(option)
|
||||
}
|
||||
|
||||
// ShowKey displays the action that a key is bound to
|
||||
func ShowKey(args []string) {
|
||||
if len(args) < 1 {
|
||||
messenger.Error("Please provide a key to show")
|
||||
return
|
||||
}
|
||||
|
||||
if action, ok := bindingsStr[args[0]]; ok {
|
||||
messenger.Message(action)
|
||||
} else {
|
||||
messenger.Message(args[0], " has no binding")
|
||||
}
|
||||
}
|
||||
|
||||
// Bind creates a new keybinding
|
||||
func Bind(args []string) {
|
||||
if len(args) < 2 {
|
||||
@@ -487,7 +524,7 @@ func Bind(args []string) {
|
||||
// Run runs a shell command in the background
|
||||
func Run(args []string) {
|
||||
// Run a shell command in the background (openTerm is false)
|
||||
HandleShellCommand(JoinCommandArgs(args...), false, true)
|
||||
HandleShellCommand(shellwords.Join(args...), false, true)
|
||||
}
|
||||
|
||||
// Quit closes the main view
|
||||
@@ -508,25 +545,37 @@ func Save(args []string) {
|
||||
|
||||
// Replace runs search and replace
|
||||
func Replace(args []string) {
|
||||
if len(args) < 2 || len(args) > 3 {
|
||||
if len(args) < 2 || len(args) > 4 {
|
||||
// We need to find both a search and replace expression
|
||||
messenger.Error("Invalid replace statement: " + strings.Join(args, " "))
|
||||
return
|
||||
}
|
||||
|
||||
allAtOnce := false
|
||||
if len(args) == 3 {
|
||||
// user added -a flag
|
||||
if args[2] == "-a" {
|
||||
allAtOnce = true
|
||||
} else {
|
||||
messenger.Error("Invalid replace flag: " + args[2])
|
||||
return
|
||||
all := false
|
||||
noRegex := false
|
||||
|
||||
if len(args) > 2 {
|
||||
for _, arg := range args[2:] {
|
||||
switch arg {
|
||||
case "-a":
|
||||
all = true
|
||||
case "-l":
|
||||
noRegex = true
|
||||
default:
|
||||
messenger.Error("Invalid flag: " + arg)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
search := string(args[0])
|
||||
|
||||
if noRegex {
|
||||
search = regexp.QuoteMeta(search)
|
||||
}
|
||||
|
||||
replace := string(args[1])
|
||||
replaceBytes := []byte(replace)
|
||||
|
||||
regex, err := regexp.Compile("(?m)" + search)
|
||||
if err != nil {
|
||||
@@ -540,28 +589,21 @@ func Replace(args []string) {
|
||||
found := 0
|
||||
replaceAll := func() {
|
||||
var deltas []Delta
|
||||
deltaXOffset := Count(replace) - Count(search)
|
||||
for i := 0; i < view.Buf.LinesNum(); i++ {
|
||||
matches := regex.FindAllIndex(view.Buf.lines[i].data, -1)
|
||||
str := string(view.Buf.lines[i].data)
|
||||
newText := regex.ReplaceAllFunc(view.Buf.lines[i].data, func(in []byte) []byte {
|
||||
found++
|
||||
return replaceBytes
|
||||
})
|
||||
|
||||
if matches != nil {
|
||||
xOffset := 0
|
||||
for _, m := range matches {
|
||||
from := Loc{runePos(m[0], str) + xOffset, i}
|
||||
to := Loc{runePos(m[1], str) + xOffset, i}
|
||||
from := Loc{0, i}
|
||||
to := Loc{utf8.RuneCount(view.Buf.lines[i].data), i}
|
||||
|
||||
xOffset += deltaXOffset
|
||||
|
||||
deltas = append(deltas, Delta{replace, from, to})
|
||||
found++
|
||||
}
|
||||
}
|
||||
deltas = append(deltas, Delta{string(newText), from, to})
|
||||
}
|
||||
view.Buf.MultipleReplace(deltas)
|
||||
}
|
||||
|
||||
if allAtOnce {
|
||||
if all {
|
||||
replaceAll()
|
||||
} else {
|
||||
for {
|
||||
@@ -619,93 +661,27 @@ func ReplaceAll(args []string) {
|
||||
Replace(append(args, "-a"))
|
||||
}
|
||||
|
||||
// RunShellCommand executes a shell command and returns the output/error
|
||||
func RunShellCommand(input string) (string, error) {
|
||||
inputCmd := SplitCommandArgs(input)[0]
|
||||
args := SplitCommandArgs(input)[1:]
|
||||
|
||||
cmd := exec.Command(inputCmd, args...)
|
||||
outputBytes := &bytes.Buffer{}
|
||||
cmd.Stdout = outputBytes
|
||||
cmd.Stderr = outputBytes
|
||||
cmd.Start()
|
||||
err := cmd.Wait() // wait for command to finish
|
||||
outstring := outputBytes.String()
|
||||
return outstring, err
|
||||
}
|
||||
|
||||
// HandleShellCommand runs the shell command
|
||||
// The openTerm argument specifies whether a terminal should be opened (for viewing output
|
||||
// or interacting with stdin)
|
||||
func HandleShellCommand(input string, openTerm bool, waitToFinish bool) string {
|
||||
inputCmd := SplitCommandArgs(input)[0]
|
||||
if !openTerm {
|
||||
// Simply run the command in the background and notify the user when it's done
|
||||
messenger.Message("Running...")
|
||||
go func() {
|
||||
output, err := RunShellCommand(input)
|
||||
totalLines := strings.Split(output, "\n")
|
||||
|
||||
if len(totalLines) < 3 {
|
||||
if err == nil {
|
||||
messenger.Message(inputCmd, " exited without error")
|
||||
} else {
|
||||
messenger.Message(inputCmd, " exited with error: ", err, ": ", output)
|
||||
}
|
||||
} else {
|
||||
messenger.Message(output)
|
||||
}
|
||||
// We have to make sure to redraw
|
||||
RedrawAll()
|
||||
}()
|
||||
// Term opens a terminal in the current view
|
||||
func Term(args []string) {
|
||||
var err error
|
||||
if len(args) == 0 {
|
||||
err = CurView().StartTerminal([]string{os.Getenv("SHELL"), "-i"}, true, false, "")
|
||||
} else {
|
||||
// Shut down the screen because we're going to interact directly with the shell
|
||||
screen.Fini()
|
||||
screen = nil
|
||||
|
||||
args := SplitCommandArgs(input)[1:]
|
||||
|
||||
// Set up everything for the command
|
||||
var outputBuf bytes.Buffer
|
||||
cmd := exec.Command(inputCmd, args...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = io.MultiWriter(os.Stdout, &outputBuf)
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
// 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 {
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
}()
|
||||
|
||||
cmd.Start()
|
||||
err := cmd.Wait()
|
||||
|
||||
output := outputBuf.String()
|
||||
if err != nil {
|
||||
output = err.Error()
|
||||
}
|
||||
|
||||
if waitToFinish {
|
||||
// This is just so we don't return right away and let the user press enter to return
|
||||
TermMessage("")
|
||||
}
|
||||
|
||||
// Start the screen back up
|
||||
InitScreen()
|
||||
|
||||
return output
|
||||
err = CurView().StartTerminal(args, true, false, "")
|
||||
}
|
||||
if err != nil {
|
||||
messenger.Error(err)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// HandleCommand handles input from the user
|
||||
func HandleCommand(input string) {
|
||||
args := SplitCommandArgs(input)
|
||||
args, err := shellwords.Split(input)
|
||||
if err != nil {
|
||||
messenger.Error("Error parsing args ", err)
|
||||
return
|
||||
}
|
||||
|
||||
inputCmd := args[0]
|
||||
|
||||
if _, ok := commands[inputCmd]; !ok {
|
||||
|
||||
@@ -28,13 +28,22 @@ type Cursor struct {
|
||||
Num int
|
||||
}
|
||||
|
||||
// Goto puts the cursor at the given cursor's location and gives the current cursor its selection too
|
||||
// Goto puts the cursor at the given cursor's location and gives
|
||||
// the current cursor its selection too
|
||||
func (c *Cursor) Goto(b Cursor) {
|
||||
c.X, c.Y, c.LastVisualX = b.X, b.Y, b.LastVisualX
|
||||
c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection
|
||||
}
|
||||
|
||||
// CopySelection copies the user's selection to either "primary" or "clipboard"
|
||||
// GotoLoc puts the cursor at the given cursor's location and gives
|
||||
// the current cursor its selection too
|
||||
func (c *Cursor) GotoLoc(l Loc) {
|
||||
c.X, c.Y = l.X, l.Y
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
|
||||
// CopySelection copies the user's selection to either "primary"
|
||||
// or "clipboard"
|
||||
func (c *Cursor) CopySelection(target string) {
|
||||
if c.HasSelection() {
|
||||
if target != "primary" || c.buf.Settings["useprimary"].(bool) {
|
||||
@@ -151,7 +160,8 @@ func (c *Cursor) SelectWord() {
|
||||
c.Loc = c.CurSelection[1]
|
||||
}
|
||||
|
||||
// AddWordToSelection adds the word the cursor is currently on to the selection
|
||||
// AddWordToSelection adds the word the cursor is currently on
|
||||
// to the selection
|
||||
func (c *Cursor) AddWordToSelection() {
|
||||
if c.Loc.GreaterThan(c.OrigSelection[0]) && c.Loc.LessThan(c.OrigSelection[1]) {
|
||||
c.CurSelection = c.OrigSelection
|
||||
@@ -183,7 +193,8 @@ func (c *Cursor) AddWordToSelection() {
|
||||
c.Loc = c.CurSelection[1]
|
||||
}
|
||||
|
||||
// SelectTo selects from the current cursor location to the given location
|
||||
// SelectTo selects from the current cursor location to the given
|
||||
// location
|
||||
func (c *Cursor) SelectTo(loc Loc) {
|
||||
if loc.GreaterThan(c.OrigSelection[0]) {
|
||||
c.SetSelectionStart(c.OrigSelection[0])
|
||||
@@ -250,19 +261,19 @@ func (c *Cursor) UpN(amount int) {
|
||||
proposedY := c.Y - amount
|
||||
if proposedY < 0 {
|
||||
proposedY = 0
|
||||
c.LastVisualX = 0
|
||||
} else if proposedY >= c.buf.NumLines {
|
||||
proposedY = c.buf.NumLines - 1
|
||||
}
|
||||
if proposedY == c.Y {
|
||||
return
|
||||
|
||||
runes := []rune(c.buf.Line(c.Y))
|
||||
c.X = c.GetCharPosInLine(proposedY, c.LastVisualX)
|
||||
|
||||
if c.X > len(runes) || (amount < 0 && proposedY == c.Y) {
|
||||
c.X = len(runes)
|
||||
}
|
||||
|
||||
c.Y = proposedY
|
||||
runes := []rune(c.buf.Line(c.Y))
|
||||
c.X = c.GetCharPosInLine(c.Y, c.LastVisualX)
|
||||
if c.X > len(runes) {
|
||||
c.X = len(runes)
|
||||
}
|
||||
}
|
||||
|
||||
// DownN moves the cursor down N lines (if possible)
|
||||
@@ -280,7 +291,8 @@ func (c *Cursor) Down() {
|
||||
c.DownN(1)
|
||||
}
|
||||
|
||||
// Left moves the cursor left one cell (if possible) or to the last line if it is at the beginning
|
||||
// Left moves the cursor left one cell (if possible) or to
|
||||
// the previous line if it is at the beginning
|
||||
func (c *Cursor) Left() {
|
||||
if c.Loc == c.buf.Start() {
|
||||
return
|
||||
@@ -294,7 +306,8 @@ func (c *Cursor) Left() {
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
|
||||
// Right moves the cursor right one cell (if possible) or to the next line if it is at the end
|
||||
// Right moves the cursor right one cell (if possible) or
|
||||
// to the next line if it is at the end
|
||||
func (c *Cursor) Right() {
|
||||
if c.Loc == c.buf.End() {
|
||||
return
|
||||
@@ -320,7 +333,21 @@ func (c *Cursor) Start() {
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
|
||||
// GetCharPosInLine gets the char position of a visual x y coordinate (this is necessary because tabs are 1 char but 4 visual spaces)
|
||||
// StartOfText moves the cursor to the first non-whitespace rune of
|
||||
// the line it is on
|
||||
func (c *Cursor) StartOfText() {
|
||||
c.Start()
|
||||
for IsWhitespace(c.RuneUnder(c.X)) {
|
||||
if c.X == Count(c.buf.Line(c.Y)) {
|
||||
break
|
||||
}
|
||||
c.Right()
|
||||
}
|
||||
}
|
||||
|
||||
// GetCharPosInLine gets the char position of a visual x y
|
||||
// coordinate (this is necessary because tabs are 1 char but
|
||||
// 4 visual spaces)
|
||||
func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
|
||||
// Get the tab size
|
||||
tabSize := int(c.buf.Settings["tabsize"].(float64))
|
||||
@@ -355,8 +382,9 @@ func (c *Cursor) StoreVisualX() {
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
|
||||
// Relocate makes sure that the cursor is inside the bounds of the buffer
|
||||
// If it isn't, it moves it to be within the buffer's lines
|
||||
// Relocate makes sure that the cursor is inside the bounds
|
||||
// of the buffer If it isn't, it moves it to be within the
|
||||
// buffer's lines
|
||||
func (c *Cursor) Relocate() {
|
||||
if c.Y < 0 {
|
||||
c.Y = 0
|
||||
|
||||
@@ -28,6 +28,7 @@ type TextEvent struct {
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
// A Delta is a change to the buffer
|
||||
type Delta struct {
|
||||
Text string
|
||||
Start Loc
|
||||
@@ -48,6 +49,11 @@ func ExecuteTextEvent(t *TextEvent, buf *Buffer) {
|
||||
for i, d := range t.Deltas {
|
||||
t.Deltas[i].Text = buf.remove(d.Start, d.End)
|
||||
buf.insert(d.Start, []byte(d.Text))
|
||||
t.Deltas[i].Start = d.Start
|
||||
t.Deltas[i].End = Loc{d.Start.X + Count(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]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,7 +105,7 @@ func (eh *EventHandler) Insert(start Loc, text string) {
|
||||
e := &TextEvent{
|
||||
C: *eh.buf.cursors[eh.buf.curCursor],
|
||||
EventType: TextEventInsert,
|
||||
Deltas: []Delta{Delta{text, start, Loc{0, 0}}},
|
||||
Deltas: []Delta{{text, start, Loc{0, 0}}},
|
||||
Time: time.Now(),
|
||||
}
|
||||
eh.Execute(e)
|
||||
@@ -129,7 +135,7 @@ func (eh *EventHandler) Remove(start, end Loc) {
|
||||
e := &TextEvent{
|
||||
C: *eh.buf.cursors[eh.buf.curCursor],
|
||||
EventType: TextEventRemove,
|
||||
Deltas: []Delta{Delta{"", start, end}},
|
||||
Deltas: []Delta{{"", start, end}},
|
||||
Time: time.Now(),
|
||||
}
|
||||
eh.Execute(e)
|
||||
|
||||
@@ -2,7 +2,7 @@ package highlight
|
||||
|
||||
import "regexp"
|
||||
|
||||
// DetectFiletype will use the list of syntax definitions provided and the filename and first line of the file
|
||||
// MatchFiletype will use the list of syntax definitions provided and the filename and first line of the file
|
||||
// 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 {
|
||||
|
||||
@@ -6,16 +6,50 @@ import (
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func sliceStart(slc []byte, index int) []byte {
|
||||
len := len(slc)
|
||||
i := 0
|
||||
totalSize := 0
|
||||
for totalSize < len {
|
||||
if i >= index {
|
||||
return slc[totalSize:]
|
||||
}
|
||||
|
||||
_, size := utf8.DecodeRune(slc[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
|
||||
return slc[totalSize:]
|
||||
}
|
||||
|
||||
func sliceEnd(slc []byte, index int) []byte {
|
||||
len := len(slc)
|
||||
i := 0
|
||||
totalSize := 0
|
||||
for totalSize < len {
|
||||
if i >= index {
|
||||
return slc[:totalSize]
|
||||
}
|
||||
|
||||
_, size := utf8.DecodeRune(slc[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
|
||||
return slc[:totalSize]
|
||||
}
|
||||
|
||||
// RunePos returns the rune index of a given byte index
|
||||
// This could cause problems if the byte index is between code points
|
||||
func runePos(p int, str string) int {
|
||||
func runePos(p int, str []byte) int {
|
||||
if p < 0 {
|
||||
return 0
|
||||
}
|
||||
if p >= len(str) {
|
||||
return utf8.RuneCountInString(str)
|
||||
return utf8.RuneCount(str)
|
||||
}
|
||||
return utf8.RuneCountInString(str[:p])
|
||||
return utf8.RuneCount(str[:p])
|
||||
}
|
||||
|
||||
func combineLineMatch(src, dst LineMatch) LineMatch {
|
||||
@@ -36,7 +70,7 @@ type State *region
|
||||
|
||||
// LineStates is an interface for a buffer-like object which can also store the states and matches for every line
|
||||
type LineStates interface {
|
||||
Line(n int) string
|
||||
LineBytes(n int) []byte
|
||||
LinesNum() int
|
||||
State(lineN int) State
|
||||
SetState(lineN int, s State)
|
||||
@@ -60,7 +94,7 @@ func NewHighlighter(def *Def) *Highlighter {
|
||||
// color's group (represented as one byte)
|
||||
type LineMatch map[int]Group
|
||||
|
||||
func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []rune, canMatchStart, canMatchEnd bool) []int {
|
||||
func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte, canMatchStart, canMatchEnd bool) []int {
|
||||
regexStr := regex.String()
|
||||
if strings.Contains(regexStr, "^") {
|
||||
if !canMatchStart {
|
||||
@@ -75,12 +109,12 @@ func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []rune, canMatchSt
|
||||
|
||||
var strbytes []byte
|
||||
if skip != nil {
|
||||
strbytes = skip.ReplaceAllFunc([]byte(string(str)), func(match []byte) []byte {
|
||||
strbytes = skip.ReplaceAllFunc(str, func(match []byte) []byte {
|
||||
res := make([]byte, utf8.RuneCount(match))
|
||||
return res
|
||||
})
|
||||
} else {
|
||||
strbytes = []byte(string(str))
|
||||
strbytes = str
|
||||
}
|
||||
|
||||
match := regex.FindIndex(strbytes)
|
||||
@@ -88,10 +122,10 @@ func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []rune, canMatchSt
|
||||
return nil
|
||||
}
|
||||
// return []int{match.Index, match.Index + match.Length}
|
||||
return []int{runePos(match[0], string(str)), runePos(match[1], string(str))}
|
||||
return []int{runePos(match[0], str), runePos(match[1], str)}
|
||||
}
|
||||
|
||||
func findAllIndex(regex *regexp.Regexp, str []rune, canMatchStart, canMatchEnd bool) [][]int {
|
||||
func findAllIndex(regex *regexp.Regexp, str []byte, canMatchStart, canMatchEnd bool) [][]int {
|
||||
regexStr := regex.String()
|
||||
if strings.Contains(regexStr, "^") {
|
||||
if !canMatchStart {
|
||||
@@ -103,17 +137,16 @@ func findAllIndex(regex *regexp.Regexp, str []rune, canMatchStart, canMatchEnd b
|
||||
return nil
|
||||
}
|
||||
}
|
||||
matches := regex.FindAllIndex([]byte(string(str)), -1)
|
||||
matches := regex.FindAllIndex(str, -1)
|
||||
for i, m := range matches {
|
||||
matches[i][0] = runePos(m[0], string(str))
|
||||
matches[i][1] = runePos(m[1], string(str))
|
||||
matches[i][0] = runePos(m[0], str)
|
||||
matches[i][1] = runePos(m[1], str)
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []rune, curRegion *region, statesOnly bool) LineMatch {
|
||||
// highlights := make(LineMatch)
|
||||
|
||||
func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, curRegion *region, statesOnly bool) LineMatch {
|
||||
lineLen := utf8.RuneCount(line)
|
||||
if start == 0 {
|
||||
if !statesOnly {
|
||||
if _, ok := highlights[0]; !ok {
|
||||
@@ -130,20 +163,20 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
|
||||
if curRegion.parent == nil {
|
||||
if !statesOnly {
|
||||
highlights[start+loc[1]] = 0
|
||||
h.highlightRegion(highlights, start, false, lineNum, line[:loc[0]], curRegion, statesOnly)
|
||||
h.highlightRegion(highlights, start, false, lineNum, sliceEnd(line, loc[0]), curRegion, statesOnly)
|
||||
}
|
||||
h.highlightEmptyRegion(highlights, start+loc[1], canMatchEnd, lineNum, line[loc[1]:], statesOnly)
|
||||
h.highlightEmptyRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), statesOnly)
|
||||
return highlights
|
||||
}
|
||||
if !statesOnly {
|
||||
highlights[start+loc[1]] = curRegion.parent.group
|
||||
h.highlightRegion(highlights, start, false, lineNum, line[:loc[0]], curRegion, statesOnly)
|
||||
h.highlightRegion(highlights, start, false, lineNum, sliceEnd(line, loc[0]), curRegion, statesOnly)
|
||||
}
|
||||
h.highlightRegion(highlights, start+loc[1], canMatchEnd, lineNum, line[loc[1]:], curRegion.parent, statesOnly)
|
||||
h.highlightRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), curRegion.parent, statesOnly)
|
||||
return highlights
|
||||
}
|
||||
|
||||
if len(line) == 0 || statesOnly {
|
||||
if lineLen == 0 || statesOnly {
|
||||
if canMatchEnd {
|
||||
h.lastRegion = curRegion
|
||||
}
|
||||
@@ -151,7 +184,7 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
|
||||
return highlights
|
||||
}
|
||||
|
||||
firstLoc := []int{len(line), 0}
|
||||
firstLoc := []int{lineLen, 0}
|
||||
|
||||
var firstRegion *region
|
||||
for _, r := range curRegion.rules.regions {
|
||||
@@ -163,14 +196,14 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
|
||||
}
|
||||
}
|
||||
}
|
||||
if firstLoc[0] != len(line) {
|
||||
if firstLoc[0] != lineLen {
|
||||
highlights[start+firstLoc[0]] = firstRegion.limitGroup
|
||||
h.highlightRegion(highlights, start, false, lineNum, line[:firstLoc[0]], curRegion, statesOnly)
|
||||
h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, line[firstLoc[1]:], firstRegion, statesOnly)
|
||||
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, len([]rune(string(line))))
|
||||
fullHighlights := make([]Group, lineLen)
|
||||
for i := 0; i < len(fullHighlights); i++ {
|
||||
fullHighlights[i] = curRegion.group
|
||||
}
|
||||
@@ -185,9 +218,7 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
|
||||
}
|
||||
for i, h := range fullHighlights {
|
||||
if i == 0 || h != fullHighlights[i-1] {
|
||||
// if _, ok := highlights[start+i]; !ok {
|
||||
highlights[start+i] = h
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,15 +229,16 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
|
||||
return highlights
|
||||
}
|
||||
|
||||
func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []rune, statesOnly bool) LineMatch {
|
||||
if len(line) == 0 {
|
||||
func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, statesOnly bool) LineMatch {
|
||||
lineLen := utf8.RuneCount(line)
|
||||
if lineLen == 0 {
|
||||
if canMatchEnd {
|
||||
h.lastRegion = nil
|
||||
}
|
||||
return highlights
|
||||
}
|
||||
|
||||
firstLoc := []int{len(line), 0}
|
||||
firstLoc := []int{lineLen, 0}
|
||||
var firstRegion *region
|
||||
for _, r := range h.Def.rules.regions {
|
||||
loc := findIndex(r.start, nil, line, start == 0, canMatchEnd)
|
||||
@@ -217,12 +249,12 @@ func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canM
|
||||
}
|
||||
}
|
||||
}
|
||||
if firstLoc[0] != len(line) {
|
||||
if firstLoc[0] != lineLen {
|
||||
if !statesOnly {
|
||||
highlights[start+firstLoc[0]] = firstRegion.limitGroup
|
||||
}
|
||||
h.highlightEmptyRegion(highlights, start, false, lineNum, line[:firstLoc[0]], statesOnly)
|
||||
h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, line[firstLoc[1]:], firstRegion, statesOnly)
|
||||
h.highlightEmptyRegion(highlights, start, false, lineNum, sliceEnd(line, firstLoc[0]), statesOnly)
|
||||
h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), firstRegion, statesOnly)
|
||||
return highlights
|
||||
}
|
||||
|
||||
@@ -267,7 +299,7 @@ func (h *Highlighter) HighlightString(input string) []LineMatch {
|
||||
var lineMatches []LineMatch
|
||||
|
||||
for i := 0; i < len(lines); i++ {
|
||||
line := []rune(lines[i])
|
||||
line := []byte(lines[i])
|
||||
highlights := make(LineMatch)
|
||||
|
||||
if i == 0 || h.lastRegion == nil {
|
||||
@@ -283,7 +315,7 @@ func (h *Highlighter) HighlightString(input string) []LineMatch {
|
||||
// HighlightStates correctly sets all states for the buffer
|
||||
func (h *Highlighter) HighlightStates(input LineStates) {
|
||||
for i := 0; i < input.LinesNum(); i++ {
|
||||
line := []rune(input.Line(i))
|
||||
line := input.LineBytes(i)
|
||||
// highlights := make(LineMatch)
|
||||
|
||||
if i == 0 || h.lastRegion == nil {
|
||||
@@ -307,7 +339,7 @@ func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int)
|
||||
break
|
||||
}
|
||||
|
||||
line := []rune(input.Line(i))
|
||||
line := input.LineBytes(i)
|
||||
highlights := make(LineMatch)
|
||||
|
||||
var match LineMatch
|
||||
@@ -331,7 +363,7 @@ func (h *Highlighter) ReHighlightStates(input LineStates, startline int) {
|
||||
h.lastRegion = input.State(startline - 1)
|
||||
}
|
||||
for i := startline; i < input.LinesNum(); i++ {
|
||||
line := []rune(input.Line(i))
|
||||
line := input.LineBytes(i)
|
||||
// highlights := make(LineMatch)
|
||||
|
||||
// var match LineMatch
|
||||
@@ -353,7 +385,7 @@ func (h *Highlighter) ReHighlightStates(input LineStates, startline int) {
|
||||
|
||||
// ReHighlightLine will rehighlight the state and match for a single line
|
||||
func (h *Highlighter) ReHighlightLine(input LineStates, lineN int) {
|
||||
line := []rune(input.Line(lineN))
|
||||
line := input.LineBytes(lineN)
|
||||
highlights := make(LineMatch)
|
||||
|
||||
h.lastRegion = nil
|
||||
|
||||
20
cmd/micro/keymenu.go
Normal file
20
cmd/micro/keymenu.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
// DisplayKeyMenu displays the nano-style key menu at the bottom of the screen
|
||||
func DisplayKeyMenu() {
|
||||
w, h := screen.Size()
|
||||
|
||||
bot := h - 3
|
||||
|
||||
display := []string{"^Q Quit, ^S Save, ^O Open, ^G Help, ^E Command Bar, ^K Cut Line", "^F Find, ^Z Undo, ^Y Redo, ^A Select All, ^D Duplicate Line, ^T New Tab"}
|
||||
|
||||
for y := 0; y < len(display); y++ {
|
||||
for x := 0; x < w; x++ {
|
||||
if x < len(display[y]) {
|
||||
screen.SetContent(x, bot+y, rune(display[y][x]), nil, defStyle)
|
||||
} else {
|
||||
screen.SetContent(x, bot+y, ' ', nil, defStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,8 @@ func runeToByteIndex(n int, txt []byte) int {
|
||||
return count
|
||||
}
|
||||
|
||||
// A Line contains the data in bytes as well as a highlight state, match
|
||||
// and a flag for whether the highlighting needs to be updated
|
||||
type Line struct {
|
||||
data []byte
|
||||
|
||||
@@ -43,10 +45,12 @@ type LineArray struct {
|
||||
lines []Line
|
||||
}
|
||||
|
||||
// Append efficiently appends lines together
|
||||
// It allocates an additional 10000 lines if the original estimate
|
||||
// is incorrect
|
||||
func Append(slice []Line, data ...Line) []Line {
|
||||
l := len(slice)
|
||||
if l+len(data) > cap(slice) { // reallocate
|
||||
// Allocate double what's needed, for future growth.
|
||||
newSlice := make([]Line, (l+len(data))+10000)
|
||||
// The copy function is predeclared and works for any slice type.
|
||||
copy(newSlice, slice)
|
||||
@@ -71,11 +75,20 @@ func NewLineArray(size int64, reader io.Reader) *LineArray {
|
||||
n := 0
|
||||
for {
|
||||
data, err := br.ReadBytes('\n')
|
||||
if len(data) > 1 && data[len(data)-2] == '\r' {
|
||||
data = append(data[:len(data)-2], '\n')
|
||||
if fileformat == 0 {
|
||||
fileformat = 2
|
||||
}
|
||||
} else if len(data) > 0 {
|
||||
if fileformat == 0 {
|
||||
fileformat = 1
|
||||
}
|
||||
}
|
||||
|
||||
if n >= 1000 && loaded >= 0 {
|
||||
totalLinesNum := int(float64(size) * (float64(n) / float64(loaded)))
|
||||
newSlice := make([]Line, len(la.lines), totalLinesNum+10000)
|
||||
// The copy function is predeclared and works for any slice type.
|
||||
copy(newSlice, la.lines)
|
||||
la.lines = newSlice
|
||||
loaded = -1
|
||||
@@ -87,7 +100,7 @@ func NewLineArray(size int64, reader io.Reader) *LineArray {
|
||||
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
la.lines = Append(la.lines, Line{data[:len(data)], nil, nil, false})
|
||||
la.lines = Append(la.lines, Line{data[:], nil, nil, false})
|
||||
// la.lines = Append(la.lines, Line{data[:len(data)]})
|
||||
}
|
||||
// Last line was read
|
||||
@@ -114,11 +127,28 @@ func (la *LineArray) String() string {
|
||||
return str
|
||||
}
|
||||
|
||||
// SaveString returns the string that should be written to disk when
|
||||
// the line array is saved
|
||||
// It is the same as string but uses crlf or lf line endings depending
|
||||
func (la *LineArray) SaveString(useCrlf bool) string {
|
||||
str := ""
|
||||
for i, l := range la.lines {
|
||||
str += string(l.data)
|
||||
if i != len(la.lines)-1 {
|
||||
if useCrlf {
|
||||
str += "\r"
|
||||
}
|
||||
str += "\n"
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// 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{[]byte{' '}, nil, nil, 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{[]byte{}, la.lines[y].state, nil, false}
|
||||
}
|
||||
|
||||
// inserts a byte array at a given location
|
||||
@@ -216,18 +246,22 @@ func (la *LineArray) Substr(start, end Loc) string {
|
||||
return str
|
||||
}
|
||||
|
||||
// State gets the highlight state for the given line number
|
||||
func (la *LineArray) State(lineN int) highlight.State {
|
||||
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].state = s
|
||||
}
|
||||
|
||||
// SetMatch sets the match at the given line number
|
||||
func (la *LineArray) SetMatch(lineN int, m highlight.LineMatch) {
|
||||
la.lines[lineN].match = m
|
||||
}
|
||||
|
||||
// Match retrieves the match for the given line number
|
||||
func (la *LineArray) Match(lineN int) highlight.LineMatch {
|
||||
return la.lines[lineN].match
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ type Loc struct {
|
||||
X, Y int
|
||||
}
|
||||
|
||||
// Diff returns the distance between two locations
|
||||
func Diff(a, b Loc, buf *Buffer) int {
|
||||
if a.Y == b.Y {
|
||||
if a.X > b.X {
|
||||
|
||||
539
cmd/micro/lua.go
Normal file
539
cmd/micro/lua.go
Normal file
@@ -0,0 +1,539 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
var L *lua.LState
|
||||
|
||||
func init() {
|
||||
L = lua.NewState()
|
||||
L.SetGlobal("import", luar.New(L, Import))
|
||||
}
|
||||
|
||||
// LoadFile loads a lua file
|
||||
func LoadFile(module string, file string, data string) error {
|
||||
pluginDef := "local P = {};" + module + " = P;setmetatable(" + module + ", {__index = _G});setfenv(1, P);"
|
||||
|
||||
if fn, err := L.Load(strings.NewReader(pluginDef+data), file); err != nil {
|
||||
return err
|
||||
} else {
|
||||
L.Push(fn)
|
||||
return L.PCall(0, lua.MultRet, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Import allows a lua plugin to import a package
|
||||
func Import(pkg string) *lua.LTable {
|
||||
switch pkg {
|
||||
case "fmt":
|
||||
return importFmt()
|
||||
case "io":
|
||||
return importIo()
|
||||
case "io/ioutil", "ioutil":
|
||||
return importIoUtil()
|
||||
case "net":
|
||||
return importNet()
|
||||
case "math":
|
||||
return importMath()
|
||||
case "math/rand":
|
||||
return importMathRand()
|
||||
case "os":
|
||||
return importOs()
|
||||
case "runtime":
|
||||
return importRuntime()
|
||||
case "path":
|
||||
return importPath()
|
||||
case "filepath":
|
||||
return importFilePath()
|
||||
case "strings":
|
||||
return importStrings()
|
||||
case "regexp":
|
||||
return importRegexp()
|
||||
case "errors":
|
||||
return importErrors()
|
||||
case "time":
|
||||
return importTime()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func importFmt() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "tErrorf", luar.New(L, fmt.Errorf))
|
||||
L.SetField(pkg, "Fprint", luar.New(L, fmt.Fprint))
|
||||
L.SetField(pkg, "Fprintf", luar.New(L, fmt.Fprintf))
|
||||
L.SetField(pkg, "Fprintln", luar.New(L, fmt.Fprintln))
|
||||
L.SetField(pkg, "Fscan", luar.New(L, fmt.Fscan))
|
||||
L.SetField(pkg, "Fscanf", luar.New(L, fmt.Fscanf))
|
||||
L.SetField(pkg, "Fscanln", luar.New(L, fmt.Fscanln))
|
||||
L.SetField(pkg, "Print", luar.New(L, fmt.Print))
|
||||
L.SetField(pkg, "Printf", luar.New(L, fmt.Printf))
|
||||
L.SetField(pkg, "Println", luar.New(L, fmt.Println))
|
||||
L.SetField(pkg, "Scan", luar.New(L, fmt.Scan))
|
||||
L.SetField(pkg, "Scanf", luar.New(L, fmt.Scanf))
|
||||
L.SetField(pkg, "Scanln", luar.New(L, fmt.Scanln))
|
||||
L.SetField(pkg, "Sprint", luar.New(L, fmt.Sprint))
|
||||
L.SetField(pkg, "Sprintf", luar.New(L, fmt.Sprintf))
|
||||
L.SetField(pkg, "Sprintln", luar.New(L, fmt.Sprintln))
|
||||
L.SetField(pkg, "Sscan", luar.New(L, fmt.Sscan))
|
||||
L.SetField(pkg, "Sscanf", luar.New(L, fmt.Sscanf))
|
||||
L.SetField(pkg, "Sscanln", luar.New(L, fmt.Sscanln))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importIo() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Copy", luar.New(L, io.Copy))
|
||||
L.SetField(pkg, "CopyN", luar.New(L, io.CopyN))
|
||||
L.SetField(pkg, "EOF", luar.New(L, io.EOF))
|
||||
L.SetField(pkg, "ErrClosedPipe", luar.New(L, io.ErrClosedPipe))
|
||||
L.SetField(pkg, "ErrNoProgress", luar.New(L, io.ErrNoProgress))
|
||||
L.SetField(pkg, "ErrShortBuffer", luar.New(L, io.ErrShortBuffer))
|
||||
L.SetField(pkg, "ErrShortWrite", luar.New(L, io.ErrShortWrite))
|
||||
L.SetField(pkg, "ErrUnexpectedEOF", luar.New(L, io.ErrUnexpectedEOF))
|
||||
L.SetField(pkg, "LimitReader", luar.New(L, io.LimitReader))
|
||||
L.SetField(pkg, "MultiReader", luar.New(L, io.MultiReader))
|
||||
L.SetField(pkg, "MultiWriter", luar.New(L, io.MultiWriter))
|
||||
L.SetField(pkg, "NewSectionReader", luar.New(L, io.NewSectionReader))
|
||||
L.SetField(pkg, "Pipe", luar.New(L, io.Pipe))
|
||||
L.SetField(pkg, "ReadAtLeast", luar.New(L, io.ReadAtLeast))
|
||||
L.SetField(pkg, "ReadFull", luar.New(L, io.ReadFull))
|
||||
L.SetField(pkg, "TeeReader", luar.New(L, io.TeeReader))
|
||||
L.SetField(pkg, "WriteString", luar.New(L, io.WriteString))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importIoUtil() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "ReadAll", luar.New(L, ioutil.ReadAll))
|
||||
L.SetField(pkg, "ReadDir", luar.New(L, ioutil.ReadDir))
|
||||
L.SetField(pkg, "ReadFile", luar.New(L, ioutil.ReadFile))
|
||||
L.SetField(pkg, "WriteFile", luar.New(L, ioutil.WriteFile))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importNet() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "CIDRMask", luar.New(L, net.CIDRMask))
|
||||
L.SetField(pkg, "Dial", luar.New(L, net.Dial))
|
||||
L.SetField(pkg, "DialIP", luar.New(L, net.DialIP))
|
||||
L.SetField(pkg, "DialTCP", luar.New(L, net.DialTCP))
|
||||
L.SetField(pkg, "DialTimeout", luar.New(L, net.DialTimeout))
|
||||
L.SetField(pkg, "DialUDP", luar.New(L, net.DialUDP))
|
||||
L.SetField(pkg, "DialUnix", luar.New(L, net.DialUnix))
|
||||
L.SetField(pkg, "ErrWriteToConnected", luar.New(L, net.ErrWriteToConnected))
|
||||
L.SetField(pkg, "FileConn", luar.New(L, net.FileConn))
|
||||
L.SetField(pkg, "FileListener", luar.New(L, net.FileListener))
|
||||
L.SetField(pkg, "FilePacketConn", luar.New(L, net.FilePacketConn))
|
||||
L.SetField(pkg, "FlagBroadcast", luar.New(L, net.FlagBroadcast))
|
||||
L.SetField(pkg, "FlagLoopback", luar.New(L, net.FlagLoopback))
|
||||
L.SetField(pkg, "FlagMulticast", luar.New(L, net.FlagMulticast))
|
||||
L.SetField(pkg, "FlagPointToPoint", luar.New(L, net.FlagPointToPoint))
|
||||
L.SetField(pkg, "FlagUp", luar.New(L, net.FlagUp))
|
||||
L.SetField(pkg, "IPv4", luar.New(L, net.IPv4))
|
||||
L.SetField(pkg, "IPv4Mask", luar.New(L, net.IPv4Mask))
|
||||
L.SetField(pkg, "IPv4allrouter", luar.New(L, net.IPv4allrouter))
|
||||
L.SetField(pkg, "IPv4allsys", luar.New(L, net.IPv4allsys))
|
||||
L.SetField(pkg, "IPv4bcast", luar.New(L, net.IPv4bcast))
|
||||
L.SetField(pkg, "IPv4len", luar.New(L, net.IPv4len))
|
||||
L.SetField(pkg, "IPv4zero", luar.New(L, net.IPv4zero))
|
||||
L.SetField(pkg, "IPv6interfacelocalallnodes", luar.New(L, net.IPv6interfacelocalallnodes))
|
||||
L.SetField(pkg, "IPv6len", luar.New(L, net.IPv6len))
|
||||
L.SetField(pkg, "IPv6linklocalallnodes", luar.New(L, net.IPv6linklocalallnodes))
|
||||
L.SetField(pkg, "IPv6linklocalallrouters", luar.New(L, net.IPv6linklocalallrouters))
|
||||
L.SetField(pkg, "IPv6loopback", luar.New(L, net.IPv6loopback))
|
||||
L.SetField(pkg, "IPv6unspecified", luar.New(L, net.IPv6unspecified))
|
||||
L.SetField(pkg, "IPv6zero", luar.New(L, net.IPv6zero))
|
||||
L.SetField(pkg, "InterfaceAddrs", luar.New(L, net.InterfaceAddrs))
|
||||
L.SetField(pkg, "InterfaceByIndex", luar.New(L, net.InterfaceByIndex))
|
||||
L.SetField(pkg, "InterfaceByName", luar.New(L, net.InterfaceByName))
|
||||
L.SetField(pkg, "Interfaces", luar.New(L, net.Interfaces))
|
||||
L.SetField(pkg, "JoinHostPort", luar.New(L, net.JoinHostPort))
|
||||
L.SetField(pkg, "Listen", luar.New(L, net.Listen))
|
||||
L.SetField(pkg, "ListenIP", luar.New(L, net.ListenIP))
|
||||
L.SetField(pkg, "ListenMulticastUDP", luar.New(L, net.ListenMulticastUDP))
|
||||
L.SetField(pkg, "ListenPacket", luar.New(L, net.ListenPacket))
|
||||
L.SetField(pkg, "ListenTCP", luar.New(L, net.ListenTCP))
|
||||
L.SetField(pkg, "ListenUDP", luar.New(L, net.ListenUDP))
|
||||
L.SetField(pkg, "ListenUnix", luar.New(L, net.ListenUnix))
|
||||
L.SetField(pkg, "ListenUnixgram", luar.New(L, net.ListenUnixgram))
|
||||
L.SetField(pkg, "LookupAddr", luar.New(L, net.LookupAddr))
|
||||
L.SetField(pkg, "LookupCNAME", luar.New(L, net.LookupCNAME))
|
||||
L.SetField(pkg, "LookupHost", luar.New(L, net.LookupHost))
|
||||
L.SetField(pkg, "LookupIP", luar.New(L, net.LookupIP))
|
||||
L.SetField(pkg, "LookupMX", luar.New(L, net.LookupMX))
|
||||
L.SetField(pkg, "LookupNS", luar.New(L, net.LookupNS))
|
||||
L.SetField(pkg, "LookupPort", luar.New(L, net.LookupPort))
|
||||
L.SetField(pkg, "LookupSRV", luar.New(L, net.LookupSRV))
|
||||
L.SetField(pkg, "LookupTXT", luar.New(L, net.LookupTXT))
|
||||
L.SetField(pkg, "ParseCIDR", luar.New(L, net.ParseCIDR))
|
||||
L.SetField(pkg, "ParseIP", luar.New(L, net.ParseIP))
|
||||
L.SetField(pkg, "ParseMAC", luar.New(L, net.ParseMAC))
|
||||
L.SetField(pkg, "Pipe", luar.New(L, net.Pipe))
|
||||
L.SetField(pkg, "ResolveIPAddr", luar.New(L, net.ResolveIPAddr))
|
||||
L.SetField(pkg, "ResolveTCPAddr", luar.New(L, net.ResolveTCPAddr))
|
||||
L.SetField(pkg, "ResolveUDPAddr", luar.New(L, net.ResolveUDPAddr))
|
||||
L.SetField(pkg, "ResolveUnixAddr", luar.New(L, net.ResolveUnixAddr))
|
||||
L.SetField(pkg, "SplitHostPort", luar.New(L, net.SplitHostPort))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importMath() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Abs", luar.New(L, math.Abs))
|
||||
L.SetField(pkg, "Acos", luar.New(L, math.Acos))
|
||||
L.SetField(pkg, "Acosh", luar.New(L, math.Acosh))
|
||||
L.SetField(pkg, "Asin", luar.New(L, math.Asin))
|
||||
L.SetField(pkg, "Asinh", luar.New(L, math.Asinh))
|
||||
L.SetField(pkg, "Atan", luar.New(L, math.Atan))
|
||||
L.SetField(pkg, "Atan2", luar.New(L, math.Atan2))
|
||||
L.SetField(pkg, "Atanh", luar.New(L, math.Atanh))
|
||||
L.SetField(pkg, "Cbrt", luar.New(L, math.Cbrt))
|
||||
L.SetField(pkg, "Ceil", luar.New(L, math.Ceil))
|
||||
L.SetField(pkg, "Copysign", luar.New(L, math.Copysign))
|
||||
L.SetField(pkg, "Cos", luar.New(L, math.Cos))
|
||||
L.SetField(pkg, "Cosh", luar.New(L, math.Cosh))
|
||||
L.SetField(pkg, "Dim", luar.New(L, math.Dim))
|
||||
L.SetField(pkg, "Erf", luar.New(L, math.Erf))
|
||||
L.SetField(pkg, "Erfc", luar.New(L, math.Erfc))
|
||||
L.SetField(pkg, "Exp", luar.New(L, math.Exp))
|
||||
L.SetField(pkg, "Exp2", luar.New(L, math.Exp2))
|
||||
L.SetField(pkg, "Expm1", luar.New(L, math.Expm1))
|
||||
L.SetField(pkg, "Float32bits", luar.New(L, math.Float32bits))
|
||||
L.SetField(pkg, "Float32frombits", luar.New(L, math.Float32frombits))
|
||||
L.SetField(pkg, "Float64bits", luar.New(L, math.Float64bits))
|
||||
L.SetField(pkg, "Float64frombits", luar.New(L, math.Float64frombits))
|
||||
L.SetField(pkg, "Floor", luar.New(L, math.Floor))
|
||||
L.SetField(pkg, "Frexp", luar.New(L, math.Frexp))
|
||||
L.SetField(pkg, "Gamma", luar.New(L, math.Gamma))
|
||||
L.SetField(pkg, "Hypot", luar.New(L, math.Hypot))
|
||||
L.SetField(pkg, "Ilogb", luar.New(L, math.Ilogb))
|
||||
L.SetField(pkg, "Inf", luar.New(L, math.Inf))
|
||||
L.SetField(pkg, "IsInf", luar.New(L, math.IsInf))
|
||||
L.SetField(pkg, "IsNaN", luar.New(L, math.IsNaN))
|
||||
L.SetField(pkg, "J0", luar.New(L, math.J0))
|
||||
L.SetField(pkg, "J1", luar.New(L, math.J1))
|
||||
L.SetField(pkg, "Jn", luar.New(L, math.Jn))
|
||||
L.SetField(pkg, "Ldexp", luar.New(L, math.Ldexp))
|
||||
L.SetField(pkg, "Lgamma", luar.New(L, math.Lgamma))
|
||||
L.SetField(pkg, "Log", luar.New(L, math.Log))
|
||||
L.SetField(pkg, "Log10", luar.New(L, math.Log10))
|
||||
L.SetField(pkg, "Log1p", luar.New(L, math.Log1p))
|
||||
L.SetField(pkg, "Log2", luar.New(L, math.Log2))
|
||||
L.SetField(pkg, "Logb", luar.New(L, math.Logb))
|
||||
L.SetField(pkg, "Max", luar.New(L, math.Max))
|
||||
L.SetField(pkg, "Min", luar.New(L, math.Min))
|
||||
L.SetField(pkg, "Mod", luar.New(L, math.Mod))
|
||||
L.SetField(pkg, "Modf", luar.New(L, math.Modf))
|
||||
L.SetField(pkg, "NaN", luar.New(L, math.NaN))
|
||||
L.SetField(pkg, "Nextafter", luar.New(L, math.Nextafter))
|
||||
L.SetField(pkg, "Pow", luar.New(L, math.Pow))
|
||||
L.SetField(pkg, "Pow10", luar.New(L, math.Pow10))
|
||||
L.SetField(pkg, "Remainder", luar.New(L, math.Remainder))
|
||||
L.SetField(pkg, "Signbit", luar.New(L, math.Signbit))
|
||||
L.SetField(pkg, "Sin", luar.New(L, math.Sin))
|
||||
L.SetField(pkg, "Sincos", luar.New(L, math.Sincos))
|
||||
L.SetField(pkg, "Sinh", luar.New(L, math.Sinh))
|
||||
L.SetField(pkg, "Sqrt", luar.New(L, math.Sqrt))
|
||||
L.SetField(pkg, "Tan", luar.New(L, math.Tan))
|
||||
L.SetField(pkg, "Tanh", luar.New(L, math.Tanh))
|
||||
L.SetField(pkg, "Trunc", luar.New(L, math.Trunc))
|
||||
L.SetField(pkg, "Y0", luar.New(L, math.Y0))
|
||||
L.SetField(pkg, "Y1", luar.New(L, math.Y1))
|
||||
L.SetField(pkg, "Yn", luar.New(L, math.Yn))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importMathRand() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "ExpFloat64", luar.New(L, rand.ExpFloat64))
|
||||
L.SetField(pkg, "Float32", luar.New(L, rand.Float32))
|
||||
L.SetField(pkg, "Float64", luar.New(L, rand.Float64))
|
||||
L.SetField(pkg, "Int", luar.New(L, rand.Int))
|
||||
L.SetField(pkg, "Int31", luar.New(L, rand.Int31))
|
||||
L.SetField(pkg, "Int31n", luar.New(L, rand.Int31n))
|
||||
L.SetField(pkg, "Int63", luar.New(L, rand.Int63))
|
||||
L.SetField(pkg, "Int63n", luar.New(L, rand.Int63n))
|
||||
L.SetField(pkg, "Intn", luar.New(L, rand.Intn))
|
||||
L.SetField(pkg, "NormFloat64", luar.New(L, rand.NormFloat64))
|
||||
L.SetField(pkg, "Perm", luar.New(L, rand.Perm))
|
||||
L.SetField(pkg, "Seed", luar.New(L, rand.Seed))
|
||||
L.SetField(pkg, "Uint32", luar.New(L, rand.Uint32))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importOs() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Args", luar.New(L, os.Args))
|
||||
L.SetField(pkg, "Chdir", luar.New(L, os.Chdir))
|
||||
L.SetField(pkg, "Chmod", luar.New(L, os.Chmod))
|
||||
L.SetField(pkg, "Chown", luar.New(L, os.Chown))
|
||||
L.SetField(pkg, "Chtimes", luar.New(L, os.Chtimes))
|
||||
L.SetField(pkg, "Clearenv", luar.New(L, os.Clearenv))
|
||||
L.SetField(pkg, "Create", luar.New(L, os.Create))
|
||||
L.SetField(pkg, "DevNull", luar.New(L, os.DevNull))
|
||||
L.SetField(pkg, "Environ", luar.New(L, os.Environ))
|
||||
L.SetField(pkg, "ErrExist", luar.New(L, os.ErrExist))
|
||||
L.SetField(pkg, "ErrInvalid", luar.New(L, os.ErrInvalid))
|
||||
L.SetField(pkg, "ErrNotExist", luar.New(L, os.ErrNotExist))
|
||||
L.SetField(pkg, "ErrPermission", luar.New(L, os.ErrPermission))
|
||||
L.SetField(pkg, "Exit", luar.New(L, os.Exit))
|
||||
L.SetField(pkg, "Expand", luar.New(L, os.Expand))
|
||||
L.SetField(pkg, "ExpandEnv", luar.New(L, os.ExpandEnv))
|
||||
L.SetField(pkg, "FindProcess", luar.New(L, os.FindProcess))
|
||||
L.SetField(pkg, "Getegid", luar.New(L, os.Getegid))
|
||||
L.SetField(pkg, "Getenv", luar.New(L, os.Getenv))
|
||||
L.SetField(pkg, "Geteuid", luar.New(L, os.Geteuid))
|
||||
L.SetField(pkg, "Getgid", luar.New(L, os.Getgid))
|
||||
L.SetField(pkg, "Getgroups", luar.New(L, os.Getgroups))
|
||||
L.SetField(pkg, "Getpagesize", luar.New(L, os.Getpagesize))
|
||||
L.SetField(pkg, "Getpid", luar.New(L, os.Getpid))
|
||||
L.SetField(pkg, "Getuid", luar.New(L, os.Getuid))
|
||||
L.SetField(pkg, "Getwd", luar.New(L, os.Getwd))
|
||||
L.SetField(pkg, "Hostname", luar.New(L, os.Hostname))
|
||||
L.SetField(pkg, "Interrupt", luar.New(L, os.Interrupt))
|
||||
L.SetField(pkg, "IsExist", luar.New(L, os.IsExist))
|
||||
L.SetField(pkg, "IsNotExist", luar.New(L, os.IsNotExist))
|
||||
L.SetField(pkg, "IsPathSeparator", luar.New(L, os.IsPathSeparator))
|
||||
L.SetField(pkg, "IsPermission", luar.New(L, os.IsPermission))
|
||||
L.SetField(pkg, "Kill", luar.New(L, os.Kill))
|
||||
L.SetField(pkg, "Lchown", luar.New(L, os.Lchown))
|
||||
L.SetField(pkg, "Link", luar.New(L, os.Link))
|
||||
L.SetField(pkg, "Lstat", luar.New(L, os.Lstat))
|
||||
L.SetField(pkg, "Mkdir", luar.New(L, os.Mkdir))
|
||||
L.SetField(pkg, "MkdirAll", luar.New(L, os.MkdirAll))
|
||||
L.SetField(pkg, "ModeAppend", luar.New(L, os.ModeAppend))
|
||||
L.SetField(pkg, "ModeCharDevice", luar.New(L, os.ModeCharDevice))
|
||||
L.SetField(pkg, "ModeDevice", luar.New(L, os.ModeDevice))
|
||||
L.SetField(pkg, "ModeDir", luar.New(L, os.ModeDir))
|
||||
L.SetField(pkg, "ModeExclusive", luar.New(L, os.ModeExclusive))
|
||||
L.SetField(pkg, "ModeNamedPipe", luar.New(L, os.ModeNamedPipe))
|
||||
L.SetField(pkg, "ModePerm", luar.New(L, os.ModePerm))
|
||||
L.SetField(pkg, "ModeSetgid", luar.New(L, os.ModeSetgid))
|
||||
L.SetField(pkg, "ModeSetuid", luar.New(L, os.ModeSetuid))
|
||||
L.SetField(pkg, "ModeSocket", luar.New(L, os.ModeSocket))
|
||||
L.SetField(pkg, "ModeSticky", luar.New(L, os.ModeSticky))
|
||||
L.SetField(pkg, "ModeSymlink", luar.New(L, os.ModeSymlink))
|
||||
L.SetField(pkg, "ModeTemporary", luar.New(L, os.ModeTemporary))
|
||||
L.SetField(pkg, "ModeType", luar.New(L, os.ModeType))
|
||||
L.SetField(pkg, "NewFile", luar.New(L, os.NewFile))
|
||||
L.SetField(pkg, "NewSyscallError", luar.New(L, os.NewSyscallError))
|
||||
L.SetField(pkg, "O_APPEND", luar.New(L, os.O_APPEND))
|
||||
L.SetField(pkg, "O_CREATE", luar.New(L, os.O_CREATE))
|
||||
L.SetField(pkg, "O_EXCL", luar.New(L, os.O_EXCL))
|
||||
L.SetField(pkg, "O_RDONLY", luar.New(L, os.O_RDONLY))
|
||||
L.SetField(pkg, "O_RDWR", luar.New(L, os.O_RDWR))
|
||||
L.SetField(pkg, "O_SYNC", luar.New(L, os.O_SYNC))
|
||||
L.SetField(pkg, "O_TRUNC", luar.New(L, os.O_TRUNC))
|
||||
L.SetField(pkg, "O_WRONLY", luar.New(L, os.O_WRONLY))
|
||||
L.SetField(pkg, "Open", luar.New(L, os.Open))
|
||||
L.SetField(pkg, "OpenFile", luar.New(L, os.OpenFile))
|
||||
L.SetField(pkg, "PathListSeparator", luar.New(L, os.PathListSeparator))
|
||||
L.SetField(pkg, "PathSeparator", luar.New(L, os.PathSeparator))
|
||||
L.SetField(pkg, "Pipe", luar.New(L, os.Pipe))
|
||||
L.SetField(pkg, "Readlink", luar.New(L, os.Readlink))
|
||||
L.SetField(pkg, "Remove", luar.New(L, os.Remove))
|
||||
L.SetField(pkg, "RemoveAll", luar.New(L, os.RemoveAll))
|
||||
L.SetField(pkg, "Rename", luar.New(L, os.Rename))
|
||||
L.SetField(pkg, "SEEK_CUR", luar.New(L, os.SEEK_CUR))
|
||||
L.SetField(pkg, "SEEK_END", luar.New(L, os.SEEK_END))
|
||||
L.SetField(pkg, "SEEK_SET", luar.New(L, os.SEEK_SET))
|
||||
L.SetField(pkg, "SameFile", luar.New(L, os.SameFile))
|
||||
L.SetField(pkg, "Setenv", luar.New(L, os.Setenv))
|
||||
L.SetField(pkg, "StartProcess", luar.New(L, os.StartProcess))
|
||||
L.SetField(pkg, "Stat", luar.New(L, os.Stat))
|
||||
L.SetField(pkg, "Stderr", luar.New(L, os.Stderr))
|
||||
L.SetField(pkg, "Stdin", luar.New(L, os.Stdin))
|
||||
L.SetField(pkg, "Stdout", luar.New(L, os.Stdout))
|
||||
L.SetField(pkg, "Symlink", luar.New(L, os.Symlink))
|
||||
L.SetField(pkg, "TempDir", luar.New(L, os.TempDir))
|
||||
L.SetField(pkg, "Truncate", luar.New(L, os.Truncate))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importRuntime() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "GC", luar.New(L, runtime.GC))
|
||||
L.SetField(pkg, "GOARCH", luar.New(L, runtime.GOARCH))
|
||||
L.SetField(pkg, "GOMAXPROCS", luar.New(L, runtime.GOMAXPROCS))
|
||||
L.SetField(pkg, "GOOS", luar.New(L, runtime.GOOS))
|
||||
L.SetField(pkg, "GOROOT", luar.New(L, runtime.GOROOT))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importPath() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Base", luar.New(L, path.Base))
|
||||
L.SetField(pkg, "Clean", luar.New(L, path.Clean))
|
||||
L.SetField(pkg, "Dir", luar.New(L, path.Dir))
|
||||
L.SetField(pkg, "ErrBadPattern", luar.New(L, path.ErrBadPattern))
|
||||
L.SetField(pkg, "Ext", luar.New(L, path.Ext))
|
||||
L.SetField(pkg, "IsAbs", luar.New(L, path.IsAbs))
|
||||
L.SetField(pkg, "Join", luar.New(L, path.Join))
|
||||
L.SetField(pkg, "Match", luar.New(L, path.Match))
|
||||
L.SetField(pkg, "Split", luar.New(L, path.Split))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importFilePath() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Join", luar.New(L, filepath.Join))
|
||||
L.SetField(pkg, "Abs", luar.New(L, filepath.Abs))
|
||||
L.SetField(pkg, "Base", luar.New(L, filepath.Base))
|
||||
L.SetField(pkg, "Clean", luar.New(L, filepath.Clean))
|
||||
L.SetField(pkg, "Dir", luar.New(L, filepath.Dir))
|
||||
L.SetField(pkg, "EvalSymlinks", luar.New(L, filepath.EvalSymlinks))
|
||||
L.SetField(pkg, "Ext", luar.New(L, filepath.Ext))
|
||||
L.SetField(pkg, "FromSlash", luar.New(L, filepath.FromSlash))
|
||||
L.SetField(pkg, "Glob", luar.New(L, filepath.Glob))
|
||||
L.SetField(pkg, "HasPrefix", luar.New(L, filepath.HasPrefix))
|
||||
L.SetField(pkg, "IsAbs", luar.New(L, filepath.IsAbs))
|
||||
L.SetField(pkg, "Join", luar.New(L, filepath.Join))
|
||||
L.SetField(pkg, "Match", luar.New(L, filepath.Match))
|
||||
L.SetField(pkg, "Rel", luar.New(L, filepath.Rel))
|
||||
L.SetField(pkg, "Split", luar.New(L, filepath.Split))
|
||||
L.SetField(pkg, "SplitList", luar.New(L, filepath.SplitList))
|
||||
L.SetField(pkg, "ToSlash", luar.New(L, filepath.ToSlash))
|
||||
L.SetField(pkg, "VolumeName", luar.New(L, filepath.VolumeName))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importStrings() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Contains", luar.New(L, strings.Contains))
|
||||
L.SetField(pkg, "ContainsAny", luar.New(L, strings.ContainsAny))
|
||||
L.SetField(pkg, "ContainsRune", luar.New(L, strings.ContainsRune))
|
||||
L.SetField(pkg, "Count", luar.New(L, strings.Count))
|
||||
L.SetField(pkg, "EqualFold", luar.New(L, strings.EqualFold))
|
||||
L.SetField(pkg, "Fields", luar.New(L, strings.Fields))
|
||||
L.SetField(pkg, "FieldsFunc", luar.New(L, strings.FieldsFunc))
|
||||
L.SetField(pkg, "HasPrefix", luar.New(L, strings.HasPrefix))
|
||||
L.SetField(pkg, "HasSuffix", luar.New(L, strings.HasSuffix))
|
||||
L.SetField(pkg, "Index", luar.New(L, strings.Index))
|
||||
L.SetField(pkg, "IndexAny", luar.New(L, strings.IndexAny))
|
||||
L.SetField(pkg, "IndexByte", luar.New(L, strings.IndexByte))
|
||||
L.SetField(pkg, "IndexFunc", luar.New(L, strings.IndexFunc))
|
||||
L.SetField(pkg, "IndexRune", luar.New(L, strings.IndexRune))
|
||||
L.SetField(pkg, "Join", luar.New(L, strings.Join))
|
||||
L.SetField(pkg, "LastIndex", luar.New(L, strings.LastIndex))
|
||||
L.SetField(pkg, "LastIndexAny", luar.New(L, strings.LastIndexAny))
|
||||
L.SetField(pkg, "LastIndexFunc", luar.New(L, strings.LastIndexFunc))
|
||||
L.SetField(pkg, "Map", luar.New(L, strings.Map))
|
||||
L.SetField(pkg, "NewReader", luar.New(L, strings.NewReader))
|
||||
L.SetField(pkg, "NewReplacer", luar.New(L, strings.NewReplacer))
|
||||
L.SetField(pkg, "Repeat", luar.New(L, strings.Repeat))
|
||||
L.SetField(pkg, "Replace", luar.New(L, strings.Replace))
|
||||
L.SetField(pkg, "Split", luar.New(L, strings.Split))
|
||||
L.SetField(pkg, "SplitAfter", luar.New(L, strings.SplitAfter))
|
||||
L.SetField(pkg, "SplitAfterN", luar.New(L, strings.SplitAfterN))
|
||||
L.SetField(pkg, "SplitN", luar.New(L, strings.SplitN))
|
||||
L.SetField(pkg, "Title", luar.New(L, strings.Title))
|
||||
L.SetField(pkg, "ToLower", luar.New(L, strings.ToLower))
|
||||
L.SetField(pkg, "ToLowerSpecial", luar.New(L, strings.ToLowerSpecial))
|
||||
L.SetField(pkg, "ToTitle", luar.New(L, strings.ToTitle))
|
||||
L.SetField(pkg, "ToTitleSpecial", luar.New(L, strings.ToTitleSpecial))
|
||||
L.SetField(pkg, "ToUpper", luar.New(L, strings.ToUpper))
|
||||
L.SetField(pkg, "ToUpperSpecial", luar.New(L, strings.ToUpperSpecial))
|
||||
L.SetField(pkg, "Trim", luar.New(L, strings.Trim))
|
||||
L.SetField(pkg, "TrimFunc", luar.New(L, strings.TrimFunc))
|
||||
L.SetField(pkg, "TrimLeft", luar.New(L, strings.TrimLeft))
|
||||
L.SetField(pkg, "TrimLeftFunc", luar.New(L, strings.TrimLeftFunc))
|
||||
L.SetField(pkg, "TrimPrefix", luar.New(L, strings.TrimPrefix))
|
||||
L.SetField(pkg, "TrimRight", luar.New(L, strings.TrimRight))
|
||||
L.SetField(pkg, "TrimRightFunc", luar.New(L, strings.TrimRightFunc))
|
||||
L.SetField(pkg, "TrimSpace", luar.New(L, strings.TrimSpace))
|
||||
L.SetField(pkg, "TrimSuffix", luar.New(L, strings.TrimSuffix))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importRegexp() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Match", luar.New(L, regexp.Match))
|
||||
L.SetField(pkg, "MatchReader", luar.New(L, regexp.MatchReader))
|
||||
L.SetField(pkg, "MatchString", luar.New(L, regexp.MatchString))
|
||||
L.SetField(pkg, "QuoteMeta", luar.New(L, regexp.QuoteMeta))
|
||||
L.SetField(pkg, "Compile", luar.New(L, regexp.Compile))
|
||||
L.SetField(pkg, "CompilePOSIX", luar.New(L, regexp.CompilePOSIX))
|
||||
L.SetField(pkg, "MustCompile", luar.New(L, regexp.MustCompile))
|
||||
L.SetField(pkg, "MustCompilePOSIX", luar.New(L, regexp.MustCompilePOSIX))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importErrors() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "New", luar.New(L, errors.New))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importTime() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "After", luar.New(L, time.After))
|
||||
L.SetField(pkg, "Sleep", luar.New(L, time.Sleep))
|
||||
L.SetField(pkg, "Tick", luar.New(L, time.Tick))
|
||||
L.SetField(pkg, "Since", luar.New(L, time.Since))
|
||||
L.SetField(pkg, "FixedZone", luar.New(L, time.FixedZone))
|
||||
L.SetField(pkg, "LoadLocation", luar.New(L, time.LoadLocation))
|
||||
L.SetField(pkg, "NewTicker", luar.New(L, time.NewTicker))
|
||||
L.SetField(pkg, "Date", luar.New(L, time.Date))
|
||||
L.SetField(pkg, "Now", luar.New(L, time.Now))
|
||||
L.SetField(pkg, "Parse", luar.New(L, time.Parse))
|
||||
L.SetField(pkg, "ParseDuration", luar.New(L, time.ParseDuration))
|
||||
L.SetField(pkg, "ParseInLocation", luar.New(L, time.ParseInLocation))
|
||||
L.SetField(pkg, "Unix", luar.New(L, time.Unix))
|
||||
L.SetField(pkg, "AfterFunc", luar.New(L, time.AfterFunc))
|
||||
L.SetField(pkg, "NewTimer", luar.New(L, time.NewTimer))
|
||||
L.SetField(pkg, "Nanosecond", luar.New(L, time.Nanosecond))
|
||||
L.SetField(pkg, "Microsecond", luar.New(L, time.Microsecond))
|
||||
L.SetField(pkg, "Millisecond", luar.New(L, time.Millisecond))
|
||||
L.SetField(pkg, "Second", luar.New(L, time.Second))
|
||||
L.SetField(pkg, "Minute", luar.New(L, time.Minute))
|
||||
L.SetField(pkg, "Hour", luar.New(L, time.Hour))
|
||||
|
||||
return pkg
|
||||
}
|
||||
@@ -3,13 +3,15 @@ package main
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/zyedidia/clipboard"
|
||||
"github.com/zyedidia/tcell"
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/zyedidia/clipboard"
|
||||
"github.com/zyedidia/micro/cmd/micro/shellwords"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
// TermMessage sends a message to the user in the terminal. This usually occurs before
|
||||
@@ -70,9 +72,11 @@ type Messenger struct {
|
||||
gutterMessage bool
|
||||
}
|
||||
|
||||
func (m *Messenger) AddLog(msg string) {
|
||||
// AddLog sends a message to the log view
|
||||
func (m *Messenger) AddLog(msg ...interface{}) {
|
||||
logMessage := fmt.Sprint(msg...)
|
||||
buffer := m.getBuffer()
|
||||
buffer.insert(buffer.End(), []byte(msg+"\n"))
|
||||
buffer.insert(buffer.End(), []byte(logMessage+"\n"))
|
||||
buffer.Cursor.Loc = buffer.End()
|
||||
buffer.Cursor.Relocate()
|
||||
}
|
||||
@@ -219,6 +223,7 @@ func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool)
|
||||
}
|
||||
}
|
||||
|
||||
// Completion represents a type of completion
|
||||
type Completion int
|
||||
|
||||
const (
|
||||
@@ -229,6 +234,7 @@ const (
|
||||
OptionCompletion
|
||||
PluginCmdCompletion
|
||||
PluginNameCompletion
|
||||
OptionValueCompletion
|
||||
)
|
||||
|
||||
// Prompt sends the user a message and waits for a response to be typed in
|
||||
@@ -255,6 +261,11 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
|
||||
event := <-events
|
||||
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventResize:
|
||||
for _, t := range tabs {
|
||||
t.Resize()
|
||||
}
|
||||
RedrawAll()
|
||||
case *tcell.EventKey:
|
||||
switch e.Key() {
|
||||
case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape:
|
||||
@@ -268,9 +279,16 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
|
||||
response, canceled = m.response, false
|
||||
m.history[historyType][len(m.history[historyType])-1] = response
|
||||
case tcell.KeyTab:
|
||||
args := SplitCommandArgs(m.response)
|
||||
currentArgNum := len(args) - 1
|
||||
currentArg := args[currentArgNum]
|
||||
args, err := shellwords.Split(m.response)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
currentArg := ""
|
||||
currentArgNum := 0
|
||||
if len(args) > 0 {
|
||||
currentArgNum = len(args) - 1
|
||||
currentArg = args[currentArgNum]
|
||||
}
|
||||
var completionType Completion
|
||||
|
||||
if completionTypes[0] == CommandCompletion && currentArgNum > 0 {
|
||||
@@ -294,6 +312,10 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
|
||||
chosen, suggestions = HelpComplete(currentArg)
|
||||
} else if completionType == OptionCompletion {
|
||||
chosen, suggestions = OptionComplete(currentArg)
|
||||
} else if completionType == OptionValueCompletion {
|
||||
if currentArgNum-1 > 0 {
|
||||
chosen, suggestions = OptionValueComplete(args[currentArgNum-1], currentArg)
|
||||
}
|
||||
} else if completionType == PluginCmdCompletion {
|
||||
chosen, suggestions = PluginCmdComplete(currentArg)
|
||||
} else if completionType == PluginNameCompletion {
|
||||
@@ -306,8 +328,8 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
|
||||
chosen = chosen + CommonSubstring(suggestions...)
|
||||
}
|
||||
|
||||
if chosen != "" {
|
||||
m.response = JoinCommandArgs(append(args[:len(args)-1], chosen)...)
|
||||
if len(suggestions) != 0 && chosen != "" {
|
||||
m.response = shellwords.Join(append(args[:len(args)-1], chosen)...)
|
||||
m.cursorx = Count(m.response)
|
||||
}
|
||||
}
|
||||
@@ -316,7 +338,7 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
|
||||
m.HandleEvent(event, m.history[historyType])
|
||||
|
||||
m.Clear()
|
||||
for _, v := range tabs[curTab].views {
|
||||
for _, v := range tabs[curTab].Views {
|
||||
v.Display()
|
||||
}
|
||||
DisplayTabs()
|
||||
@@ -332,62 +354,160 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
|
||||
return response, canceled
|
||||
}
|
||||
|
||||
// UpHistory fetches the previous item in the history
|
||||
func (m *Messenger) UpHistory(history []string) {
|
||||
if m.historyNum > 0 {
|
||||
m.historyNum--
|
||||
m.response = history[m.historyNum]
|
||||
m.cursorx = Count(m.response)
|
||||
}
|
||||
}
|
||||
|
||||
// DownHistory fetches the next item in the history
|
||||
func (m *Messenger) DownHistory(history []string) {
|
||||
if m.historyNum < len(history)-1 {
|
||||
m.historyNum++
|
||||
m.response = history[m.historyNum]
|
||||
m.cursorx = Count(m.response)
|
||||
}
|
||||
}
|
||||
|
||||
// CursorLeft moves the cursor one character left
|
||||
func (m *Messenger) CursorLeft() {
|
||||
if m.cursorx > 0 {
|
||||
m.cursorx--
|
||||
}
|
||||
}
|
||||
|
||||
// CursorRight moves the cursor one character right
|
||||
func (m *Messenger) CursorRight() {
|
||||
if m.cursorx < Count(m.response) {
|
||||
m.cursorx++
|
||||
}
|
||||
}
|
||||
|
||||
// Start moves the cursor to the start of the line
|
||||
func (m *Messenger) Start() {
|
||||
m.cursorx = 0
|
||||
}
|
||||
|
||||
// End moves the cursor to the end of the line
|
||||
func (m *Messenger) End() {
|
||||
m.cursorx = Count(m.response)
|
||||
}
|
||||
|
||||
// Backspace deletes one character
|
||||
func (m *Messenger) Backspace() {
|
||||
if m.cursorx > 0 {
|
||||
m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
|
||||
m.cursorx--
|
||||
}
|
||||
}
|
||||
|
||||
// Paste pastes the clipboard
|
||||
func (m *Messenger) Paste() {
|
||||
clip, _ := clipboard.ReadAll("clipboard")
|
||||
m.response = Insert(m.response, m.cursorx, clip)
|
||||
m.cursorx += Count(clip)
|
||||
}
|
||||
|
||||
// WordLeft moves the cursor one word to the left
|
||||
func (m *Messenger) WordLeft() {
|
||||
response := []rune(m.response)
|
||||
m.CursorLeft()
|
||||
if m.cursorx <= 0 {
|
||||
return
|
||||
}
|
||||
for IsWhitespace(response[m.cursorx]) {
|
||||
if m.cursorx <= 0 {
|
||||
return
|
||||
}
|
||||
m.CursorLeft()
|
||||
}
|
||||
m.CursorLeft()
|
||||
for IsWordChar(string(response[m.cursorx])) {
|
||||
if m.cursorx <= 0 {
|
||||
return
|
||||
}
|
||||
m.CursorLeft()
|
||||
}
|
||||
m.CursorRight()
|
||||
}
|
||||
|
||||
// WordRight moves the cursor one word to the right
|
||||
func (m *Messenger) WordRight() {
|
||||
response := []rune(m.response)
|
||||
if m.cursorx >= len(response) {
|
||||
return
|
||||
}
|
||||
for IsWhitespace(response[m.cursorx]) {
|
||||
m.CursorRight()
|
||||
if m.cursorx >= len(response) {
|
||||
m.CursorRight()
|
||||
return
|
||||
}
|
||||
}
|
||||
m.CursorRight()
|
||||
if m.cursorx >= len(response) {
|
||||
return
|
||||
}
|
||||
for IsWordChar(string(response[m.cursorx])) {
|
||||
m.CursorRight()
|
||||
if m.cursorx >= len(response) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteWordLeft deletes one word to the left
|
||||
func (m *Messenger) DeleteWordLeft() {
|
||||
m.WordLeft()
|
||||
m.response = string([]rune(m.response)[:m.cursorx])
|
||||
}
|
||||
|
||||
// HandleEvent handles an event for the prompter
|
||||
func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
if e.Key() != tcell.KeyRune || e.Modifiers() != 0 {
|
||||
for key, actions := range bindings {
|
||||
if e.Key() == key.keyCode {
|
||||
if e.Key() == tcell.KeyRune {
|
||||
if e.Rune() != key.r {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if e.Modifiers() == key.modifiers {
|
||||
for _, action := range actions {
|
||||
funcName := FuncName(action)
|
||||
switch funcName {
|
||||
case "main.(*View).CursorUp":
|
||||
if m.historyNum > 0 {
|
||||
m.historyNum--
|
||||
m.response = history[m.historyNum]
|
||||
m.cursorx = Count(m.response)
|
||||
}
|
||||
case "main.(*View).CursorDown":
|
||||
if m.historyNum < len(history)-1 {
|
||||
m.historyNum++
|
||||
m.response = history[m.historyNum]
|
||||
m.cursorx = Count(m.response)
|
||||
}
|
||||
case "main.(*View).CursorLeft":
|
||||
if m.cursorx > 0 {
|
||||
m.cursorx--
|
||||
}
|
||||
case "main.(*View).CursorRight":
|
||||
if m.cursorx < Count(m.response) {
|
||||
m.cursorx++
|
||||
}
|
||||
case "main.(*View).CursorStart", "main.(*View).StartOfLine":
|
||||
m.cursorx = 0
|
||||
case "main.(*View).CursorEnd", "main.(*View).EndOfLine":
|
||||
m.cursorx = Count(m.response)
|
||||
case "main.(*View).Backspace":
|
||||
if m.cursorx > 0 {
|
||||
m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
|
||||
m.cursorx--
|
||||
}
|
||||
case "main.(*View).Paste":
|
||||
clip, _ := clipboard.ReadAll("clipboard")
|
||||
m.response = Insert(m.response, m.cursorx, clip)
|
||||
m.cursorx += Count(clip)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
switch e.Key() {
|
||||
case tcell.KeyCtrlA:
|
||||
m.Start()
|
||||
case tcell.KeyCtrlE:
|
||||
m.End()
|
||||
case tcell.KeyUp:
|
||||
m.UpHistory(history)
|
||||
case tcell.KeyDown:
|
||||
m.DownHistory(history)
|
||||
case tcell.KeyLeft:
|
||||
if e.Modifiers() == tcell.ModCtrl {
|
||||
m.Start()
|
||||
} else if e.Modifiers() == tcell.ModAlt || e.Modifiers() == tcell.ModMeta {
|
||||
m.WordLeft()
|
||||
} else {
|
||||
m.CursorLeft()
|
||||
}
|
||||
case tcell.KeyRight:
|
||||
if e.Modifiers() == tcell.ModCtrl {
|
||||
m.End()
|
||||
} else if e.Modifiers() == tcell.ModAlt || e.Modifiers() == tcell.ModMeta {
|
||||
m.WordRight()
|
||||
} else {
|
||||
m.CursorRight()
|
||||
}
|
||||
case tcell.KeyBackspace2, tcell.KeyBackspace:
|
||||
if e.Modifiers() == tcell.ModCtrl || e.Modifiers() == tcell.ModAlt || e.Modifiers() == tcell.ModMeta {
|
||||
m.DeleteWordLeft()
|
||||
} else {
|
||||
m.Backspace()
|
||||
}
|
||||
case tcell.KeyCtrlW:
|
||||
m.DeleteWordLeft()
|
||||
case tcell.KeyCtrlV:
|
||||
m.Paste()
|
||||
case tcell.KeyCtrlF:
|
||||
m.WordRight()
|
||||
case tcell.KeyCtrlB:
|
||||
m.WordLeft()
|
||||
case tcell.KeyRune:
|
||||
m.response = Insert(m.response, m.cursorx, string(e.Rune()))
|
||||
m.cursorx++
|
||||
@@ -478,6 +598,59 @@ func (m *Messenger) Display() {
|
||||
}
|
||||
}
|
||||
|
||||
// LoadHistory attempts to load user history from configDir/buffers/history
|
||||
// into the history map
|
||||
// The savehistory option must be on
|
||||
func (m *Messenger) LoadHistory() {
|
||||
if GetGlobalOption("savehistory").(bool) {
|
||||
file, err := os.Open(configDir + "/buffers/history")
|
||||
defer file.Close()
|
||||
var decodedMap map[string][]string
|
||||
if err == nil {
|
||||
decoder := gob.NewDecoder(file)
|
||||
err = decoder.Decode(&decodedMap)
|
||||
|
||||
if err != nil {
|
||||
m.Error("Error loading history:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if decodedMap != nil {
|
||||
m.history = decodedMap
|
||||
} else {
|
||||
m.history = make(map[string][]string)
|
||||
}
|
||||
} else {
|
||||
m.history = make(map[string][]string)
|
||||
}
|
||||
}
|
||||
|
||||
// SaveHistory saves the user's command history to configDir/buffers/history
|
||||
// only if the savehistory option is on
|
||||
func (m *Messenger) SaveHistory() {
|
||||
if GetGlobalOption("savehistory").(bool) {
|
||||
// Don't save history past 100
|
||||
for k, v := range m.history {
|
||||
if len(v) > 100 {
|
||||
m.history[k] = v[len(m.history[k])-100:]
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Create(configDir + "/buffers/history")
|
||||
defer file.Close()
|
||||
if err == nil {
|
||||
encoder := gob.NewEncoder(file)
|
||||
|
||||
err = encoder.Encode(m.history)
|
||||
if err != nil {
|
||||
m.Error("Error saving history:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A GutterMessage is a message displayed on the side of the editor
|
||||
type GutterMessage struct {
|
||||
lineNum int
|
||||
|
||||
@@ -15,14 +15,13 @@ import (
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/clipboard"
|
||||
"github.com/zyedidia/micro/cmd/micro/terminfo"
|
||||
"github.com/zyedidia/tcell"
|
||||
"github.com/zyedidia/tcell/encoding"
|
||||
"layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
const (
|
||||
synLinesUp = 75 // How many lines up to look to do syntax highlighting
|
||||
synLinesDown = 75 // How many lines down to look to do syntax highlighting
|
||||
doubleClickThreshold = 400 // How many milliseconds to wait before a second click is not a double click
|
||||
undoThreshold = 500 // If two events are less than n milliseconds apart, undo both of them
|
||||
autosaveTime = 8 // Number of seconds to wait before autosaving
|
||||
@@ -46,14 +45,12 @@ var (
|
||||
|
||||
// Version is the version number or commit hash
|
||||
// These variables should be set by the linker when compiling
|
||||
Version = "0.0.0-unknown"
|
||||
CommitHash = "Unknown"
|
||||
Version = "0.0.0-unknown"
|
||||
// CommitHash is the commit this version was built on
|
||||
CommitHash = "Unknown"
|
||||
// CompileDate is the date this binary was compiled on
|
||||
CompileDate = "Unknown"
|
||||
|
||||
// L is the lua state
|
||||
// This is the VM that runs the plugins
|
||||
L *lua.LState
|
||||
|
||||
// The list of views
|
||||
tabs []*Tab
|
||||
// This is the currently open tab
|
||||
@@ -62,9 +59,17 @@ var (
|
||||
|
||||
// Channel of jobs running in the background
|
||||
jobs chan JobFunction
|
||||
|
||||
// Event channel
|
||||
events chan tcell.Event
|
||||
autosave chan bool
|
||||
|
||||
// Channels for the terminal emulator
|
||||
updateterm chan bool
|
||||
closeterm chan int
|
||||
|
||||
// How many redraws have happened
|
||||
numRedraw uint
|
||||
)
|
||||
|
||||
// LoadInput determines which files should be loaded into buffers
|
||||
@@ -91,30 +96,23 @@ func LoadInput() []*Buffer {
|
||||
// Option 1
|
||||
// We go through each file and load it
|
||||
for i := 0; i < len(args); i++ {
|
||||
filename = args[i]
|
||||
if strings.HasPrefix(args[i], "+") {
|
||||
if strings.Contains(args[i], ":") {
|
||||
split := strings.Split(args[i], ":")
|
||||
*flagStartPos = split[0][1:] + "," + split[1]
|
||||
} else {
|
||||
*flagStartPos = args[i][1:] + ",0"
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Check that the file exists
|
||||
var input *os.File
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
// If it exists we load it into a buffer
|
||||
input, err = os.Open(filename)
|
||||
stat, _ := input.Stat()
|
||||
defer input.Close()
|
||||
if err != nil {
|
||||
TermMessage(err)
|
||||
continue
|
||||
}
|
||||
if stat.IsDir() {
|
||||
TermMessage("Cannot read", filename, "because it is a directory")
|
||||
continue
|
||||
}
|
||||
buf, err := NewBufferFromFile(args[i])
|
||||
if err != nil {
|
||||
TermMessage(err)
|
||||
continue
|
||||
}
|
||||
// If the file didn't exist, input will be empty, and we'll open an empty buffer
|
||||
if input != nil {
|
||||
buffers = append(buffers, NewBuffer(input, FSize(input), filename))
|
||||
} else {
|
||||
buffers = append(buffers, NewBufferFromString("", filename))
|
||||
}
|
||||
buffers = append(buffers, buf)
|
||||
}
|
||||
} else if !isatty.IsTerminal(os.Stdin.Fd()) {
|
||||
// Option 2
|
||||
@@ -149,6 +147,15 @@ func InitConfigDir() {
|
||||
}
|
||||
configDir = xdgHome + "/micro"
|
||||
|
||||
if len(*flagConfigDir) > 0 {
|
||||
if _, err := os.Stat(*flagConfigDir); os.IsNotExist(err) {
|
||||
TermMessage("Error: " + *flagConfigDir + " does not exist. Defaulting to " + configDir + ".")
|
||||
} else {
|
||||
configDir = *flagConfigDir
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(xdgHome); os.IsNotExist(err) {
|
||||
// If the xdgHome doesn't exist we should create it
|
||||
err = os.Mkdir(xdgHome, os.ModePerm)
|
||||
@@ -169,7 +176,20 @@ func InitConfigDir() {
|
||||
// InitScreen creates and initializes the tcell screen
|
||||
func InitScreen() {
|
||||
// Should we enable true color?
|
||||
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
|
||||
truecolor := false
|
||||
colorterm := os.Getenv("COLORTERM")
|
||||
if colorterm == "24bit" || colorterm == "truecolor" {
|
||||
truecolor = true
|
||||
}
|
||||
microtc := os.Getenv("MICRO_TRUECOLOR")
|
||||
if microtc == "1" {
|
||||
truecolor = true
|
||||
} else if microtc == "0" {
|
||||
truecolor = false
|
||||
}
|
||||
|
||||
tcelldb := os.Getenv("TCELLDB")
|
||||
os.Setenv("TCELLDB", configDir+"/.tcelldb")
|
||||
|
||||
// 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
|
||||
@@ -182,12 +202,19 @@ func InitScreen() {
|
||||
var err error
|
||||
screen, err = tcell.NewScreen()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
if err == tcell.ErrTermNotFound {
|
||||
fmt.Println("Micro does not recognize your terminal:", oldTerm)
|
||||
fmt.Println("Please go to https://github.com/zyedidia/mkinfo to read about how to fix this problem (it should be easy to fix).")
|
||||
terminfo.WriteDB(configDir + "/.tcelldb")
|
||||
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)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
if err = screen.Init(); err != nil {
|
||||
fmt.Println(err)
|
||||
@@ -199,8 +226,13 @@ func InitScreen() {
|
||||
os.Setenv("TERM", oldTerm)
|
||||
}
|
||||
|
||||
screen.SetStyle(defStyle)
|
||||
screen.EnableMouse()
|
||||
if GetGlobalOption("mouse").(bool) {
|
||||
screen.EnableMouse()
|
||||
}
|
||||
|
||||
os.Setenv("TCELLDB", tcelldb)
|
||||
|
||||
// screen.SetStyle(defStyle)
|
||||
}
|
||||
|
||||
// RedrawAll redraws everything -- all the views and the messenger
|
||||
@@ -214,12 +246,20 @@ func RedrawAll() {
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range tabs[curTab].views {
|
||||
for _, v := range tabs[curTab].Views {
|
||||
v.Display()
|
||||
}
|
||||
DisplayTabs()
|
||||
messenger.Display()
|
||||
if globalSettings["keymenu"].(bool) {
|
||||
DisplayKeyMenu()
|
||||
}
|
||||
screen.Show()
|
||||
|
||||
if numRedraw%50 == 0 {
|
||||
runtime.GC()
|
||||
}
|
||||
numRedraw++
|
||||
}
|
||||
|
||||
func LoadAll() {
|
||||
@@ -238,22 +278,37 @@ func LoadAll() {
|
||||
InitColorscheme()
|
||||
|
||||
for _, tab := range tabs {
|
||||
for _, v := range tab.views {
|
||||
for _, v := range tab.Views {
|
||||
v.Buf.UpdateRules()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Passing -version as a flag will have micro print out the version number
|
||||
// Command line flags
|
||||
var flagVersion = flag.Bool("version", false, "Show the version number and information")
|
||||
var flagStartPos = flag.String("startpos", "", "LINE,COL to start the cursor at when opening a buffer.")
|
||||
var flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
|
||||
var flagOptions = flag.Bool("options", false, "Show all option help")
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Println("Usage: micro [OPTIONS] [FILE]...")
|
||||
fmt.Print("Micro's options can be set via command line arguments for quick adjustments. For real configuration, please use the bindings.json file (see 'help options').\n\n")
|
||||
flag.CommandLine.SetOutput(os.Stdout)
|
||||
flag.PrintDefaults()
|
||||
fmt.Println("-config-dir dir")
|
||||
fmt.Println(" \tSpecify a custom location for the configuration directory")
|
||||
fmt.Println("-startpos LINE,COL")
|
||||
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("-version")
|
||||
fmt.Println(" \tShow the version number and information")
|
||||
|
||||
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")
|
||||
fmt.Println(" \tFor example: `micro -syntax off file.c`")
|
||||
fmt.Println("\nUse `micro -options` to see the full list of configuration options")
|
||||
}
|
||||
|
||||
optionFlags := make(map[string]*string)
|
||||
@@ -272,6 +327,15 @@ func main() {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *flagOptions {
|
||||
// If -options was passed
|
||||
for k, v := range DefaultGlobalSettings() {
|
||||
fmt.Printf("-%s value\n", k)
|
||||
fmt.Printf(" \tThe %s option. Default value: '%v'\n", k, v)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Start the Lua VM for running plugins
|
||||
L = lua.NewState()
|
||||
defer L.Close()
|
||||
@@ -311,7 +375,7 @@ func main() {
|
||||
// Create a new messenger
|
||||
// This is used for sending the user messages in the bottom of the editor
|
||||
messenger = new(Messenger)
|
||||
messenger.history = make(map[string][]string)
|
||||
messenger.LoadHistory()
|
||||
|
||||
// Now we load the input
|
||||
buffers := LoadInput()
|
||||
@@ -326,7 +390,7 @@ func main() {
|
||||
tab.SetNum(len(tabs))
|
||||
tabs = append(tabs, tab)
|
||||
for _, t := range tabs {
|
||||
for _, v := range t.views {
|
||||
for _, v := range t.Views {
|
||||
v.Center(false)
|
||||
}
|
||||
|
||||
@@ -356,15 +420,23 @@ func main() {
|
||||
L.SetGlobal("IsWordChar", luar.New(L, IsWordChar))
|
||||
L.SetGlobal("HandleCommand", luar.New(L, HandleCommand))
|
||||
L.SetGlobal("HandleShellCommand", luar.New(L, HandleShellCommand))
|
||||
L.SetGlobal("ExecCommand", luar.New(L, ExecCommand))
|
||||
L.SetGlobal("RunShellCommand", luar.New(L, RunShellCommand))
|
||||
L.SetGlobal("RunBackgroundShell", luar.New(L, RunBackgroundShell))
|
||||
L.SetGlobal("RunInteractiveShell", luar.New(L, RunInteractiveShell))
|
||||
L.SetGlobal("TermEmuSupported", luar.New(L, TermEmuSupported))
|
||||
L.SetGlobal("RunTermEmulator", luar.New(L, RunTermEmulator))
|
||||
L.SetGlobal("GetLeadingWhitespace", luar.New(L, GetLeadingWhitespace))
|
||||
L.SetGlobal("MakeCompletion", luar.New(L, MakeCompletion))
|
||||
L.SetGlobal("NewBuffer", luar.New(L, NewBufferFromString))
|
||||
L.SetGlobal("NewBufferFromFile", luar.New(L, NewBufferFromFile))
|
||||
L.SetGlobal("RuneStr", luar.New(L, func(r rune) string {
|
||||
return string(r)
|
||||
}))
|
||||
L.SetGlobal("Loc", luar.New(L, func(x, y int) Loc {
|
||||
return Loc{x, y}
|
||||
}))
|
||||
L.SetGlobal("WorkingDirectory", luar.New(L, os.Getwd))
|
||||
L.SetGlobal("JoinPaths", luar.New(L, filepath.Join))
|
||||
L.SetGlobal("DirectoryName", luar.New(L, filepath.Dir))
|
||||
L.SetGlobal("configDir", luar.New(L, configDir))
|
||||
@@ -385,25 +457,26 @@ func main() {
|
||||
L.SetGlobal("AddRuntimeFilesFromDirectory", luar.New(L, PluginAddRuntimeFilesFromDirectory))
|
||||
L.SetGlobal("AddRuntimeFileFromMemory", luar.New(L, PluginAddRuntimeFileFromMemory))
|
||||
|
||||
// Access to Go stdlib
|
||||
L.SetGlobal("import", luar.New(L, Import))
|
||||
|
||||
jobs = make(chan JobFunction, 100)
|
||||
events = make(chan tcell.Event, 100)
|
||||
autosave = make(chan bool)
|
||||
updateterm = make(chan bool)
|
||||
closeterm = make(chan int)
|
||||
|
||||
LoadPlugins()
|
||||
|
||||
for _, t := range tabs {
|
||||
for _, v := range t.views {
|
||||
for pl := range loadedPlugins {
|
||||
_, err := Call(pl+".onViewOpen", v)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
|
||||
TermMessage(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
for _, v := range t.Views {
|
||||
GlobalPluginCall("onViewOpen", v)
|
||||
GlobalPluginCall("onBufferOpen", v.Buf)
|
||||
}
|
||||
}
|
||||
|
||||
InitColorscheme()
|
||||
messenger.style = defStyle
|
||||
|
||||
// Here is the event loop which runs in a separate thread
|
||||
go func() {
|
||||
@@ -436,11 +509,19 @@ func main() {
|
||||
f.function(f.output, f.args...)
|
||||
continue
|
||||
case <-autosave:
|
||||
CurView().Save(true)
|
||||
if CurView().Buf.Path != "" {
|
||||
CurView().Save(true)
|
||||
}
|
||||
case <-updateterm:
|
||||
continue
|
||||
case vnum := <-closeterm:
|
||||
tabs[curTab].Views[vnum].CloseTerminal()
|
||||
case event = <-events:
|
||||
}
|
||||
|
||||
for event != nil {
|
||||
didAction := false
|
||||
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventResize:
|
||||
for _, t := range tabs {
|
||||
@@ -464,30 +545,44 @@ func main() {
|
||||
if CurView().mouseReleased {
|
||||
// We loop through each view in the current tab and make sure the current view
|
||||
// is the one being clicked in
|
||||
for _, v := range tabs[curTab].views {
|
||||
for _, v := range tabs[curTab].Views {
|
||||
if x >= v.x && x < v.x+v.Width && y >= v.y && y < v.y+v.Height {
|
||||
tabs[curTab].CurView = v.Num
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if e.Buttons() == tcell.WheelUp || e.Buttons() == tcell.WheelDown {
|
||||
var view *View
|
||||
x, y := e.Position()
|
||||
for _, v := range tabs[curTab].Views {
|
||||
if x >= v.x && x < v.x+v.Width && y >= v.y && y < v.y+v.Height {
|
||||
view = tabs[curTab].Views[v.Num]
|
||||
}
|
||||
}
|
||||
if view != nil {
|
||||
view.HandleEvent(e)
|
||||
didAction = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function checks the mouse event for the possibility of changing the current tab
|
||||
// If the tab was changed it returns true
|
||||
if TabbarHandleMouseEvent(event) {
|
||||
break
|
||||
}
|
||||
if !didAction {
|
||||
// This function checks the mouse event for the possibility of changing the current tab
|
||||
// If the tab was changed it returns true
|
||||
if TabbarHandleMouseEvent(event) {
|
||||
break
|
||||
}
|
||||
|
||||
if searching {
|
||||
// Since searching is done in real time, we need to redraw every time
|
||||
// there is a new event in the search bar so we need a special function
|
||||
// to run instead of the standard HandleEvent.
|
||||
HandleSearchEvent(event, CurView())
|
||||
} else {
|
||||
// Send it to the view
|
||||
CurView().HandleEvent(event)
|
||||
if searching {
|
||||
// Since searching is done in real time, we need to redraw every time
|
||||
// there is a new event in the search bar so we need a special function
|
||||
// to run instead of the standard HandleEvent.
|
||||
HandleSearchEvent(event, CurView())
|
||||
} else {
|
||||
// Send it to the view
|
||||
CurView().HandleEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
|
||||
@@ -61,6 +61,10 @@ func LuaFunctionBinding(function string) func(*View, bool) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// LuaFunctionMouseBinding is a function generator which takes the name of a lua function
|
||||
// and creates a function that will call that lua function
|
||||
// Specifically it creates a function that can be called as a mouse binding because this is used
|
||||
// to bind mouse actions to lua functions
|
||||
func LuaFunctionMouseBinding(function string) func(*View, bool, *tcell.EventMouse) bool {
|
||||
return func(v *View, _ bool, e *tcell.EventMouse) bool {
|
||||
_, err := Call(function, e)
|
||||
@@ -114,10 +118,13 @@ func LuaFunctionComplete(function string) func(string) []string {
|
||||
}
|
||||
}
|
||||
|
||||
// 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(function string) func(string, ...string) {
|
||||
return func(output string, args ...string) {
|
||||
_, err := Call(function, unpack(append([]string{output}, args...))...)
|
||||
if err != nil {
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
|
||||
TermMessage(err)
|
||||
}
|
||||
}
|
||||
@@ -130,11 +137,9 @@ func luaPluginName(name string) string {
|
||||
|
||||
// LoadPlugins loads the pre-installed plugins and the plugins located in ~/.config/micro/plugins
|
||||
func LoadPlugins() {
|
||||
|
||||
loadedPlugins = make(map[string]string)
|
||||
|
||||
for _, plugin := range ListRuntimeFiles(RTPlugin) {
|
||||
|
||||
pluginName := plugin.Name()
|
||||
if _, ok := loadedPlugins[pluginName]; ok {
|
||||
continue
|
||||
@@ -147,9 +152,8 @@ func LoadPlugins() {
|
||||
}
|
||||
|
||||
pluginLuaName := luaPluginName(pluginName)
|
||||
pluginDef := "\nlocal P = {}\n" + pluginLuaName + " = P\nsetmetatable(" + pluginLuaName + ", {__index = _G})\nsetfenv(1, P)\n"
|
||||
|
||||
if err := L.DoString(pluginDef + string(data)); err != nil {
|
||||
if err := LoadFile(pluginLuaName, pluginLuaName, string(data)); err != nil {
|
||||
TermMessage(err)
|
||||
continue
|
||||
}
|
||||
@@ -159,11 +163,22 @@ func LoadPlugins() {
|
||||
}
|
||||
|
||||
if _, err := os.Stat(configDir + "/init.lua"); err == nil {
|
||||
pluginDef := "\nlocal P = {}\n" + "init" + " = P\nsetmetatable(" + "init" + ", {__index = _G})\nsetfenv(1, P)\n"
|
||||
data, _ := ioutil.ReadFile(configDir + "/init.lua")
|
||||
if err := L.DoString(pluginDef + string(data)); err != nil {
|
||||
if err := LoadFile("init", configDir+"init.lua", string(data)); err != nil {
|
||||
TermMessage(err)
|
||||
}
|
||||
loadedPlugins["init"] = "init"
|
||||
}
|
||||
}
|
||||
|
||||
// GlobalCall makes a call to a function in every plugin that is currently
|
||||
// loaded
|
||||
func GlobalPluginCall(function string, args ...interface{}) {
|
||||
for pl := range loadedPlugins {
|
||||
_, err := Call(pl+"."+function, args...)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
|
||||
TermMessage(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/flynn/json5"
|
||||
"github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/json5/encoding/json5"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -422,6 +422,7 @@ func (pv *PluginVersion) DownloadAndInstall() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Install files and directory's
|
||||
for _, f := range z.File {
|
||||
parts := strings.Split(f.Name, "/")
|
||||
if allPrefixed {
|
||||
@@ -434,6 +435,12 @@ func (pv *PluginVersion) DownloadAndInstall() error {
|
||||
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
|
||||
@@ -534,7 +541,7 @@ func (pv PluginVersions) install() {
|
||||
shouldInstall := true
|
||||
if pv := currentlyInstalled.find(sel.pack.Name); pv != nil {
|
||||
if pv.Version.NE(sel.Version) {
|
||||
messenger.AddLog(fmt.Sprint("Uninstalling %q", sel.pack.Name))
|
||||
messenger.AddLog("Uninstalling", sel.pack.Name)
|
||||
UninstallPlugin(sel.pack.Name)
|
||||
} else {
|
||||
shouldInstall = false
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/blang/semver"
|
||||
"testing"
|
||||
|
||||
"github.com/zyedidia/json5/encoding/json5"
|
||||
"github.com/blang/semver"
|
||||
|
||||
"github.com/flynn/json5"
|
||||
)
|
||||
|
||||
func TestDependencyResolving(t *testing.T) {
|
||||
@@ -35,7 +36,7 @@ func TestDependencyResolving(t *testing.T) {
|
||||
if v == nil {
|
||||
t.Errorf("Failed to resolve %s", name)
|
||||
} else if expected.NE(v.Version) {
|
||||
t.Errorf("%s resolved in wrong version got %s", name, v)
|
||||
t.Errorf("%s resolved in wrong version %v", name, v)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
20
cmd/micro/scrollbar.go
Normal file
20
cmd/micro/scrollbar.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
// ScrollBar represents an optional scrollbar that can be used
|
||||
type ScrollBar struct {
|
||||
view *View
|
||||
}
|
||||
|
||||
// Display shows the scrollbar
|
||||
func (sb *ScrollBar) Display() {
|
||||
style := defStyle.Reverse(true)
|
||||
screen.SetContent(sb.view.x+sb.view.Width-1, sb.view.y+sb.pos(), ' ', nil, style)
|
||||
}
|
||||
|
||||
func (sb *ScrollBar) pos() int {
|
||||
numlines := sb.view.Buf.NumLines
|
||||
h := sb.view.Height
|
||||
filepercent := float32(sb.view.Topline) / float32(numlines)
|
||||
|
||||
return int(filepercent * float32(h))
|
||||
}
|
||||
@@ -64,7 +64,12 @@ func HandleSearchEvent(event tcell.Event, v *View) {
|
||||
// Exit the search mode
|
||||
ExitSearch(v)
|
||||
return
|
||||
case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEnter:
|
||||
case tcell.KeyEnter:
|
||||
// If the user has pressed Enter, they want this to be the lastSearch
|
||||
lastSearch = messenger.response
|
||||
EndSearch()
|
||||
return
|
||||
case tcell.KeyCtrlQ, tcell.KeyCtrlC:
|
||||
// Done
|
||||
EndSearch()
|
||||
return
|
||||
@@ -113,6 +118,8 @@ func searchDown(r *regexp.Regexp, v *View, start, end Loc) bool {
|
||||
if match != nil {
|
||||
v.Cursor.SetSelectionStart(Loc{charPos + runePos(match[0], string(l)), i})
|
||||
v.Cursor.SetSelectionEnd(Loc{charPos + runePos(match[1], string(l)), i})
|
||||
v.Cursor.OrigSelection[0] = v.Cursor.CurSelection[0]
|
||||
v.Cursor.OrigSelection[1] = v.Cursor.CurSelection[1]
|
||||
v.Cursor.Loc = v.Cursor.CurSelection[1]
|
||||
|
||||
return true
|
||||
@@ -140,6 +147,8 @@ func searchUp(r *regexp.Regexp, v *View, start, end Loc) bool {
|
||||
if match != nil {
|
||||
v.Cursor.SetSelectionStart(Loc{runePos(match[0], string(l)), i})
|
||||
v.Cursor.SetSelectionEnd(Loc{runePos(match[1], string(l)), i})
|
||||
v.Cursor.OrigSelection[0] = v.Cursor.CurSelection[0]
|
||||
v.Cursor.OrigSelection[1] = v.Cursor.CurSelection[1]
|
||||
v.Cursor.Loc = v.Cursor.CurSelection[1]
|
||||
|
||||
return true
|
||||
@@ -175,9 +184,7 @@ func Search(searchStr string, v *View, down bool) {
|
||||
found = searchUp(r, v, v.Buf.End(), searchStart)
|
||||
}
|
||||
}
|
||||
if found {
|
||||
lastSearch = searchStr
|
||||
} else {
|
||||
if !found {
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -8,8 +10,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/flynn/json5"
|
||||
"github.com/zyedidia/glob"
|
||||
"github.com/zyedidia/json5/encoding/json5"
|
||||
)
|
||||
|
||||
type optionValidator func(string, interface{}) error
|
||||
@@ -17,6 +19,8 @@ type optionValidator func(string, interface{}) error
|
||||
// The options that the user can set
|
||||
var globalSettings map[string]interface{}
|
||||
|
||||
var invalidSettings bool
|
||||
|
||||
// Options with validators
|
||||
var optionValidators = map[string]optionValidator{
|
||||
"tabsize": validatePositiveValue,
|
||||
@@ -24,10 +28,12 @@ var optionValidators = map[string]optionValidator{
|
||||
"scrollspeed": validateNonNegativeValue,
|
||||
"colorscheme": validateColorscheme,
|
||||
"colorcolumn": validateNonNegativeValue,
|
||||
"fileformat": validateLineEnding,
|
||||
}
|
||||
|
||||
// InitGlobalSettings initializes the options map and sets all options to their default values
|
||||
func InitGlobalSettings() {
|
||||
invalidSettings = false
|
||||
defaults := DefaultGlobalSettings()
|
||||
var parsed map[string]interface{}
|
||||
|
||||
@@ -38,12 +44,14 @@ func InitGlobalSettings() {
|
||||
if !strings.HasPrefix(string(input), "null") {
|
||||
if err != nil {
|
||||
TermMessage("Error reading settings.json file: " + err.Error())
|
||||
invalidSettings = true
|
||||
return
|
||||
}
|
||||
|
||||
err = json5.Unmarshal(input, &parsed)
|
||||
if err != nil {
|
||||
TermMessage("Error reading settings.json:", err.Error())
|
||||
invalidSettings = true
|
||||
}
|
||||
} else {
|
||||
writeSettings = true
|
||||
@@ -71,6 +79,7 @@ func InitGlobalSettings() {
|
||||
// InitLocalSettings scans the json in settings.json and sets the options locally based
|
||||
// on whether the buffer matches the glob
|
||||
func InitLocalSettings(buf *Buffer) {
|
||||
invalidSettings = false
|
||||
var parsed map[string]interface{}
|
||||
|
||||
filename := configDir + "/settings.json"
|
||||
@@ -78,26 +87,36 @@ func InitLocalSettings(buf *Buffer) {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
TermMessage("Error reading settings.json file: " + err.Error())
|
||||
invalidSettings = true
|
||||
return
|
||||
}
|
||||
|
||||
err = json5.Unmarshal(input, &parsed)
|
||||
if err != nil {
|
||||
TermMessage("Error reading settings.json:", err.Error())
|
||||
invalidSettings = true
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range parsed {
|
||||
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
||||
g, err := glob.Compile(k)
|
||||
if err != nil {
|
||||
TermMessage("Error with glob setting ", k, ": ", err)
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(k, "ft:") {
|
||||
if buf.Settings["filetype"].(string) == k[3:] {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
buf.Settings[k1] = v1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
g, err := glob.Compile(k)
|
||||
if err != nil {
|
||||
TermMessage("Error with glob setting ", k, ": ", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if g.MatchString(buf.Path) {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
buf.Settings[k1] = v1
|
||||
if g.MatchString(buf.Path) {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
buf.Settings[k1] = v1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,6 +125,11 @@ func InitLocalSettings(buf *Buffer) {
|
||||
|
||||
// WriteSettings writes the settings to the specified filename as JSON
|
||||
func WriteSettings(filename string) error {
|
||||
if invalidSettings {
|
||||
// Do not write the settings if there was an error when reading them
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
if _, e := os.Stat(configDir); e == nil {
|
||||
parsed := make(map[string]interface{})
|
||||
@@ -124,6 +148,7 @@ func WriteSettings(filename string) error {
|
||||
err = json5.Unmarshal(input, &parsed)
|
||||
if err != nil {
|
||||
TermMessage("Error reading settings.json:", err.Error())
|
||||
invalidSettings = true
|
||||
}
|
||||
|
||||
for k, v := range parsed {
|
||||
@@ -136,7 +161,7 @@ func WriteSettings(filename string) error {
|
||||
}
|
||||
}
|
||||
|
||||
txt, _ := json5.MarshalIndent(parsed, "", " ")
|
||||
txt, _ := json.MarshalIndent(parsed, "", " ")
|
||||
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
||||
}
|
||||
return err
|
||||
@@ -176,35 +201,44 @@ func GetOption(name string) interface{} {
|
||||
func DefaultGlobalSettings() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"autoindent": true,
|
||||
"keepautoindent": false,
|
||||
"autosave": false,
|
||||
"basename": false,
|
||||
"colorcolumn": float64(0),
|
||||
"colorscheme": "default",
|
||||
"cursorline": true,
|
||||
"eofnewline": false,
|
||||
"rmtrailingws": false,
|
||||
"fastdirty": true,
|
||||
"fileformat": "unix",
|
||||
"hidehelp": false,
|
||||
"ignorecase": false,
|
||||
"indentchar": " ",
|
||||
"infobar": true,
|
||||
"keepautoindent": false,
|
||||
"keymenu": false,
|
||||
"matchbrace": false,
|
||||
"matchbraceleft": false,
|
||||
"mouse": true,
|
||||
"pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
|
||||
"pluginrepos": []string{},
|
||||
"rmtrailingws": false,
|
||||
"ruler": true,
|
||||
"savecursor": false,
|
||||
"savehistory": true,
|
||||
"saveundo": false,
|
||||
"scrollspeed": float64(2),
|
||||
"scrollbar": false,
|
||||
"scrollmargin": float64(3),
|
||||
"scrollspeed": float64(2),
|
||||
"softwrap": false,
|
||||
"splitRight": true,
|
||||
"splitBottom": true,
|
||||
"splitbottom": true,
|
||||
"splitright": true,
|
||||
"statusline": true,
|
||||
"sucmd": "sudo",
|
||||
"syntax": true,
|
||||
"tabmovement": false,
|
||||
"tabsize": float64(4),
|
||||
"tabstospaces": false,
|
||||
"termtitle": false,
|
||||
"pluginchannels": []string{
|
||||
"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json",
|
||||
},
|
||||
"pluginrepos": []string{},
|
||||
"useprimary": true,
|
||||
"useprimary": true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,23 +247,30 @@ func DefaultGlobalSettings() map[string]interface{} {
|
||||
func DefaultLocalSettings() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"autoindent": true,
|
||||
"keepautoindent": false,
|
||||
"autosave": false,
|
||||
"basename": false,
|
||||
"colorcolumn": float64(0),
|
||||
"cursorline": true,
|
||||
"eofnewline": false,
|
||||
"rmtrailingws": false,
|
||||
"fastdirty": true,
|
||||
"fileformat": "unix",
|
||||
"filetype": "Unknown",
|
||||
"hidehelp": false,
|
||||
"ignorecase": false,
|
||||
"indentchar": " ",
|
||||
"keepautoindent": false,
|
||||
"matchbrace": false,
|
||||
"matchbraceleft": false,
|
||||
"rmtrailingws": false,
|
||||
"ruler": true,
|
||||
"savecursor": false,
|
||||
"saveundo": false,
|
||||
"scrollspeed": float64(2),
|
||||
"scrollbar": false,
|
||||
"scrollmargin": float64(3),
|
||||
"scrollspeed": float64(2),
|
||||
"softwrap": false,
|
||||
"splitRight": true,
|
||||
"splitBottom": true,
|
||||
"splitbottom": true,
|
||||
"splitright": true,
|
||||
"statusline": true,
|
||||
"syntax": true,
|
||||
"tabmovement": false,
|
||||
@@ -283,22 +324,32 @@ func SetOption(option, value string) error {
|
||||
// LoadSyntaxFiles()
|
||||
InitColorscheme()
|
||||
for _, tab := range tabs {
|
||||
for _, view := range tab.views {
|
||||
for _, view := range tab.Views {
|
||||
view.Buf.UpdateRules()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if option == "infobar" {
|
||||
if option == "infobar" || option == "keymenu" {
|
||||
for _, tab := range tabs {
|
||||
tab.Resize()
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := CurView().Buf.Settings[option]; ok {
|
||||
for _, tab := range tabs {
|
||||
for _, view := range tab.views {
|
||||
SetLocalOption(option, value, view)
|
||||
if option == "mouse" {
|
||||
if !nativeValue.(bool) {
|
||||
screen.DisableMouse()
|
||||
} else {
|
||||
screen.EnableMouse()
|
||||
}
|
||||
}
|
||||
|
||||
if len(tabs) != 0 {
|
||||
if _, ok := CurView().Buf.Settings[option]; ok {
|
||||
for _, tab := range tabs {
|
||||
for _, view := range tab.Views {
|
||||
SetLocalOption(option, value, view)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,6 +389,26 @@ func SetLocalOption(option, value string, view *View) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if option == "fastdirty" {
|
||||
// If it is being turned off, we have to hash every open buffer
|
||||
var empty [16]byte
|
||||
for _, tab := range tabs {
|
||||
for _, v := range tab.Views {
|
||||
if !nativeValue.(bool) {
|
||||
if v.Buf.origHash == empty {
|
||||
data, err := ioutil.ReadFile(v.Buf.AbsPath)
|
||||
if err != nil {
|
||||
data = []byte{}
|
||||
}
|
||||
v.Buf.origHash = md5.Sum(data)
|
||||
}
|
||||
} else {
|
||||
v.Buf.IsModified = v.Buf.Modified()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf.Settings[option] = nativeValue
|
||||
|
||||
if option == "statusline" {
|
||||
@@ -350,11 +421,17 @@ func SetLocalOption(option, value string, view *View) error {
|
||||
buf.UpdateRules()
|
||||
}
|
||||
|
||||
if option == "fileformat" {
|
||||
buf.IsModified = true
|
||||
}
|
||||
|
||||
if option == "syntax" {
|
||||
if !nativeValue.(bool) {
|
||||
buf.ClearMatches()
|
||||
} else {
|
||||
buf.highlighter.HighlightStates(buf)
|
||||
if buf.highlighter != nil {
|
||||
buf.highlighter.HighlightStates(buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,3 +507,17 @@ func validateColorscheme(option string, value interface{}) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateLineEnding(option string, value interface{}) error {
|
||||
endingType, ok := value.(string)
|
||||
|
||||
if !ok {
|
||||
return errors.New("Expected string type for file format")
|
||||
}
|
||||
|
||||
if endingType != "unix" && endingType != "dos" {
|
||||
return errors.New("File format must be either 'unix' or 'dos'")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
129
cmd/micro/shell.go
Normal file
129
cmd/micro/shell.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/cmd/micro/shellwords"
|
||||
)
|
||||
|
||||
// ExecCommand executes a command using exec
|
||||
// It returns any output/errors
|
||||
func ExecCommand(name string, arg ...string) (string, error) {
|
||||
var err error
|
||||
cmd := exec.Command(name, arg...)
|
||||
outputBytes := &bytes.Buffer{}
|
||||
cmd.Stdout = outputBytes
|
||||
cmd.Stderr = outputBytes
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = cmd.Wait() // wait for command to finish
|
||||
outstring := outputBytes.String()
|
||||
return outstring, err
|
||||
}
|
||||
|
||||
// RunShellCommand executes a shell command and returns the output/error
|
||||
func RunShellCommand(input string) (string, error) {
|
||||
args, err := shellwords.Split(input)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
inputCmd := args[0]
|
||||
|
||||
return ExecCommand(inputCmd, args[1:]...)
|
||||
}
|
||||
|
||||
func RunBackgroundShell(input string) {
|
||||
args, err := shellwords.Split(input)
|
||||
if err != nil {
|
||||
messenger.Error(err)
|
||||
return
|
||||
}
|
||||
inputCmd := args[0]
|
||||
messenger.Message("Running...")
|
||||
go func() {
|
||||
output, err := RunShellCommand(input)
|
||||
totalLines := strings.Split(output, "\n")
|
||||
|
||||
if len(totalLines) < 3 {
|
||||
if err == nil {
|
||||
messenger.Message(inputCmd, " exited without error")
|
||||
} else {
|
||||
messenger.Message(inputCmd, " exited with error: ", err, ": ", output)
|
||||
}
|
||||
} else {
|
||||
messenger.Message(output)
|
||||
}
|
||||
// We have to make sure to redraw
|
||||
RedrawAll()
|
||||
}()
|
||||
}
|
||||
|
||||
func RunInteractiveShell(input string, wait bool, getOutput bool) (string, error) {
|
||||
args, err := shellwords.Split(input)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
inputCmd := args[0]
|
||||
|
||||
// Shut down the screen because we're going to interact directly with the shell
|
||||
screen.Fini()
|
||||
screen = nil
|
||||
|
||||
args = args[1:]
|
||||
|
||||
// Set up everything for the command
|
||||
outputBytes := &bytes.Buffer{}
|
||||
cmd := exec.Command(inputCmd, args...)
|
||||
cmd.Stdin = os.Stdin
|
||||
if getOutput {
|
||||
cmd.Stdout = io.MultiWriter(os.Stdout, outputBytes)
|
||||
} else {
|
||||
cmd.Stdout = os.Stdout
|
||||
}
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
// 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 {
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
}()
|
||||
|
||||
cmd.Start()
|
||||
err = cmd.Wait()
|
||||
|
||||
output := outputBytes.String()
|
||||
|
||||
if wait {
|
||||
// This is just so we don't return right away and let the user press enter to return
|
||||
TermMessage("")
|
||||
}
|
||||
|
||||
// Start the screen back up
|
||||
InitScreen()
|
||||
|
||||
return output, err
|
||||
}
|
||||
|
||||
// HandleShellCommand runs the shell command
|
||||
// The openTerm argument specifies whether a terminal should be opened (for viewing output
|
||||
// or interacting with stdin)
|
||||
func HandleShellCommand(input string, openTerm bool, waitToFinish bool) string {
|
||||
if !openTerm {
|
||||
RunBackgroundShell(input)
|
||||
return ""
|
||||
} else {
|
||||
output, _ := RunInteractiveShell(input, waitToFinish, false)
|
||||
return output
|
||||
}
|
||||
}
|
||||
18
cmd/micro/shell_supported.go
Normal file
18
cmd/micro/shell_supported.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// +build linux darwin dragonfly openbsd_amd64 freebsd
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/cmd/micro/shellwords"
|
||||
)
|
||||
|
||||
const TermEmuSupported = true
|
||||
|
||||
func RunTermEmulator(input string, wait bool, getOutput bool, callback string) error {
|
||||
args, err := shellwords.Split(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = CurView().StartTerminal(args, wait, getOutput, callback)
|
||||
return err
|
||||
}
|
||||
11
cmd/micro/shell_unsupported.go
Normal file
11
cmd/micro/shell_unsupported.go
Normal file
@@ -0,0 +1,11 @@
|
||||
// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd_amd64
|
||||
|
||||
package main
|
||||
|
||||
import "errors"
|
||||
|
||||
const TermEmuSupported = false
|
||||
|
||||
func RunTermEmulator(input string, wait bool, getOutput bool) error {
|
||||
return errors.New("Unsupported operating system")
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Stepets
|
||||
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
|
||||
49
cmd/micro/shellwords/README.md
Normal file
49
cmd/micro/shellwords/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
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)
|
||||
180
cmd/micro/shellwords/shellwords.go
Normal file
180
cmd/micro/shellwords/shellwords.go
Normal file
@@ -0,0 +1,180 @@
|
||||
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()
|
||||
}
|
||||
229
cmd/micro/shellwords/shellwords_test.go
Normal file
229
cmd/micro/shellwords/shellwords_test.go
Normal file
@@ -0,0 +1,229 @@
|
||||
package shellwords
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testcases = []struct {
|
||||
line string
|
||||
expected []string
|
||||
}{
|
||||
{`var --bar=baz`, []string{`var`, `--bar=baz`}},
|
||||
{`var --bar="baz"`, []string{`var`, `--bar=baz`}},
|
||||
{`var "--bar=baz"`, []string{`var`, `--bar=baz`}},
|
||||
{`var "--bar='baz'"`, []string{`var`, `--bar='baz'`}},
|
||||
{"var --bar=`baz`", []string{`var`, "--bar=`baz`"}},
|
||||
{`var "--bar=\"baz'"`, []string{`var`, `--bar="baz'`}},
|
||||
{`var "--bar=\'baz\'"`, []string{`var`, `--bar='baz'`}},
|
||||
{`var --bar='\'`, []string{`var`, `--bar=\`}},
|
||||
{`var "--bar baz"`, []string{`var`, `--bar baz`}},
|
||||
{`var --"bar baz"`, []string{`var`, `--bar baz`}},
|
||||
{`var --"bar baz"`, []string{`var`, `--bar baz`}},
|
||||
}
|
||||
|
||||
func TestSimple(t *testing.T) {
|
||||
for _, testcase := range testcases {
|
||||
args, err := Parse(testcase.line)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(args, testcase.expected) {
|
||||
t.Fatalf("Expected %#v, but %#v:", testcase.expected, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
_, err := Parse("foo '")
|
||||
if err == nil {
|
||||
t.Fatal("Should be an error")
|
||||
}
|
||||
_, err = Parse(`foo "`)
|
||||
if err == nil {
|
||||
t.Fatal("Should be an error")
|
||||
}
|
||||
|
||||
_, err = Parse("foo `")
|
||||
if err == nil {
|
||||
t.Fatal("Should be an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLastSpace(t *testing.T) {
|
||||
args, err := Parse("foo bar\\ ")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(args) != 2 {
|
||||
t.Fatal("Should have two elements")
|
||||
}
|
||||
if args[0] != "foo" {
|
||||
t.Fatal("1st element should be `foo`")
|
||||
}
|
||||
if args[1] != "bar " {
|
||||
t.Fatal("1st element should be `bar `")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBacktick(t *testing.T) {
|
||||
goversion, err := shellRun("go version")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
parser := NewParser()
|
||||
parser.ParseBacktick = true
|
||||
args, err := parser.Parse("echo `go version`")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := []string{"echo", goversion}
|
||||
if !reflect.DeepEqual(args, expected) {
|
||||
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||
}
|
||||
|
||||
args, err = parser.Parse(`echo $(echo foo)`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected = []string{"echo", "foo"}
|
||||
if !reflect.DeepEqual(args, expected) {
|
||||
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||
}
|
||||
|
||||
parser.ParseBacktick = false
|
||||
args, err = parser.Parse(`echo $(echo "foo")`)
|
||||
expected = []string{"echo", `$(echo "foo")`}
|
||||
if !reflect.DeepEqual(args, expected) {
|
||||
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||
}
|
||||
args, err = parser.Parse("echo $(`echo1)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected = []string{"echo", "$(`echo1)"}
|
||||
if !reflect.DeepEqual(args, expected) {
|
||||
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBacktickError(t *testing.T) {
|
||||
parser := NewParser()
|
||||
parser.ParseBacktick = true
|
||||
_, err := parser.Parse("echo `go Version`")
|
||||
if err == nil {
|
||||
t.Fatal("Should be an error")
|
||||
}
|
||||
expected := "exit status 2:go: unknown subcommand \"Version\"\nRun 'go help' for usage.\n"
|
||||
if expected != err.Error() {
|
||||
t.Fatalf("Expected %q, but %q", expected, err.Error())
|
||||
}
|
||||
_, err = parser.Parse(`echo $(echo1)`)
|
||||
if err == nil {
|
||||
t.Fatal("Should be an error")
|
||||
}
|
||||
_, err = parser.Parse(`echo $(echo1`)
|
||||
if err == nil {
|
||||
t.Fatal("Should be an error")
|
||||
}
|
||||
_, err = parser.Parse(`echo $ (echo1`)
|
||||
if err == nil {
|
||||
t.Fatal("Should be an error")
|
||||
}
|
||||
_, err = parser.Parse(`echo (echo1`)
|
||||
if err == nil {
|
||||
t.Fatal("Should be an error")
|
||||
}
|
||||
_, err = parser.Parse(`echo )echo1`)
|
||||
if err == nil {
|
||||
t.Fatal("Should be an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnv(t *testing.T) {
|
||||
os.Setenv("FOO", "bar")
|
||||
|
||||
parser := NewParser()
|
||||
parser.ParseEnv = true
|
||||
args, err := parser.Parse("echo $FOO")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := []string{"echo", "bar"}
|
||||
if !reflect.DeepEqual(args, expected) {
|
||||
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoEnv(t *testing.T) {
|
||||
parser := NewParser()
|
||||
parser.ParseEnv = true
|
||||
args, err := parser.Parse("echo $BAR")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := []string{"echo", ""}
|
||||
if !reflect.DeepEqual(args, expected) {
|
||||
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDupEnv(t *testing.T) {
|
||||
os.Setenv("FOO", "bar")
|
||||
os.Setenv("FOO_BAR", "baz")
|
||||
|
||||
parser := NewParser()
|
||||
parser.ParseEnv = true
|
||||
args, err := parser.Parse("echo $$FOO$")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := []string{"echo", "$bar$"}
|
||||
if !reflect.DeepEqual(args, expected) {
|
||||
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||
}
|
||||
|
||||
args, err = parser.Parse("echo $${FOO_BAR}$")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected = []string{"echo", "$baz$"}
|
||||
if !reflect.DeepEqual(args, expected) {
|
||||
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHaveMore(t *testing.T) {
|
||||
parser := NewParser()
|
||||
parser.ParseEnv = true
|
||||
|
||||
line := "echo foo; seq 1 10"
|
||||
args, err := parser.Parse(line)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
expected := []string{"echo", "foo"}
|
||||
if !reflect.DeepEqual(args, expected) {
|
||||
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||
}
|
||||
|
||||
if parser.Position == 0 {
|
||||
t.Fatalf("Commands should be remaining")
|
||||
}
|
||||
|
||||
line = string([]rune(line)[parser.Position+1:])
|
||||
args, err = parser.Parse(line)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
expected = []string{"seq", "1", "10"}
|
||||
if !reflect.DeepEqual(args, expected) {
|
||||
t.Fatalf("Expected %#v, but %#v:", expected, args)
|
||||
}
|
||||
|
||||
if parser.Position > 0 {
|
||||
t.Fatalf("Commands should not be remaining")
|
||||
}
|
||||
}
|
||||
22
cmd/micro/shellwords/util_posix.go
Normal file
22
cmd/micro/shellwords/util_posix.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// +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
|
||||
}
|
||||
20
cmd/micro/shellwords/util_windows.go
Normal file
20
cmd/micro/shellwords/util_windows.go
Normal file
@@ -0,0 +1,20 @@
|
||||
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
|
||||
}
|
||||
@@ -70,9 +70,9 @@ func (l *LeafNode) VSplit(buf *Buffer, splitIndex int) {
|
||||
copy(l.parent.children[splitIndex+1:], l.parent.children[splitIndex:])
|
||||
l.parent.children[splitIndex] = NewLeafNode(newView, l.parent)
|
||||
|
||||
tab.views = append(tab.views, nil)
|
||||
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
|
||||
tab.views[splitIndex] = newView
|
||||
tab.Views = append(tab.Views, nil)
|
||||
copy(tab.Views[splitIndex+1:], tab.Views[splitIndex:])
|
||||
tab.Views[splitIndex] = newView
|
||||
|
||||
tab.CurView = splitIndex
|
||||
} else {
|
||||
@@ -94,9 +94,9 @@ func (l *LeafNode) VSplit(buf *Buffer, splitIndex int) {
|
||||
l.parent.children[search(l.parent.children, l)] = s
|
||||
l.parent = s
|
||||
|
||||
tab.views = append(tab.views, nil)
|
||||
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
|
||||
tab.views[splitIndex] = newView
|
||||
tab.Views = append(tab.Views, nil)
|
||||
copy(tab.Views[splitIndex+1:], tab.Views[splitIndex:])
|
||||
tab.Views[splitIndex] = newView
|
||||
|
||||
tab.CurView = splitIndex
|
||||
}
|
||||
@@ -123,9 +123,9 @@ func (l *LeafNode) HSplit(buf *Buffer, splitIndex int) {
|
||||
copy(l.parent.children[splitIndex+1:], l.parent.children[splitIndex:])
|
||||
l.parent.children[splitIndex] = NewLeafNode(newView, l.parent)
|
||||
|
||||
tab.views = append(tab.views, nil)
|
||||
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
|
||||
tab.views[splitIndex] = newView
|
||||
tab.Views = append(tab.Views, nil)
|
||||
copy(tab.Views[splitIndex+1:], tab.Views[splitIndex:])
|
||||
tab.Views[splitIndex] = newView
|
||||
|
||||
tab.CurView = splitIndex
|
||||
} else {
|
||||
@@ -139,7 +139,7 @@ func (l *LeafNode) HSplit(buf *Buffer, splitIndex int) {
|
||||
s.parent = l.parent
|
||||
newView := NewView(buf)
|
||||
newView.TabNum = l.parent.tabNum
|
||||
newView.Num = len(tab.views)
|
||||
newView.Num = len(tab.Views)
|
||||
if splitIndex == 1 {
|
||||
s.children = []Node{l, NewLeafNode(newView, s)}
|
||||
} else {
|
||||
@@ -148,9 +148,9 @@ func (l *LeafNode) HSplit(buf *Buffer, splitIndex int) {
|
||||
l.parent.children[search(l.parent.children, l)] = s
|
||||
l.parent = s
|
||||
|
||||
tab.views = append(tab.views, nil)
|
||||
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
|
||||
tab.views[splitIndex] = newView
|
||||
tab.Views = append(tab.Views, nil)
|
||||
copy(tab.Views[splitIndex+1:], tab.Views[splitIndex:])
|
||||
tab.Views[splitIndex] = newView
|
||||
|
||||
tab.CurView = splitIndex
|
||||
}
|
||||
@@ -167,12 +167,12 @@ func (l *LeafNode) Delete() {
|
||||
l.parent.children = l.parent.children[:len(l.parent.children)-1]
|
||||
|
||||
tab := tabs[l.parent.tabNum]
|
||||
j := findView(tab.views, l.view)
|
||||
copy(tab.views[j:], tab.views[j+1:])
|
||||
tab.views[len(tab.views)-1] = nil // or the zero value of T
|
||||
tab.views = tab.views[:len(tab.views)-1]
|
||||
j := findView(tab.Views, l.view)
|
||||
copy(tab.Views[j:], tab.Views[j+1:])
|
||||
tab.Views[len(tab.Views)-1] = nil // or the zero value of T
|
||||
tab.Views = tab.Views[:len(tab.Views)-1]
|
||||
|
||||
for i, v := range tab.views {
|
||||
for i, v := range tab.Views {
|
||||
v.Num = i
|
||||
}
|
||||
if tab.CurView > 0 {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
@@ -14,13 +15,20 @@ type Statusline struct {
|
||||
|
||||
// Display draws the statusline to the screen
|
||||
func (sline *Statusline) Display() {
|
||||
if messenger.hasPrompt && !GetGlobalOption("infobar").(bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// We'll draw the line at the lowest line in the view
|
||||
y := sline.view.Height + sline.view.y
|
||||
|
||||
file := sline.view.Buf.GetName()
|
||||
if sline.view.Buf.Settings["basename"].(bool) {
|
||||
file = path.Base(file)
|
||||
}
|
||||
|
||||
// If the buffer is dirty (has been modified) write a little '+'
|
||||
if sline.view.Buf.IsModified {
|
||||
if sline.view.Buf.Modified() {
|
||||
file += " +"
|
||||
}
|
||||
|
||||
@@ -36,12 +44,28 @@ func (sline *Statusline) Display() {
|
||||
// Add the filetype
|
||||
file += " " + sline.view.Buf.FileType()
|
||||
|
||||
file += " " + sline.view.Buf.Settings["fileformat"].(string)
|
||||
|
||||
rightText := ""
|
||||
if len(helpBinding) > 0 {
|
||||
rightText = helpBinding + " for help "
|
||||
if sline.view.Type == vtHelp {
|
||||
rightText = helpBinding + " to close help "
|
||||
if !sline.view.Buf.Settings["hidehelp"].(bool) {
|
||||
if len(kmenuBinding) > 0 {
|
||||
if globalSettings["keymenu"].(bool) {
|
||||
rightText += kmenuBinding + ": hide bindings"
|
||||
} else {
|
||||
rightText += kmenuBinding + ": show bindings"
|
||||
}
|
||||
}
|
||||
if len(helpBinding) > 0 {
|
||||
if len(kmenuBinding) > 0 {
|
||||
rightText += ", "
|
||||
}
|
||||
if sline.view.Type == vtHelp {
|
||||
rightText += helpBinding + ": close help"
|
||||
} else {
|
||||
rightText += helpBinding + ": open help"
|
||||
}
|
||||
}
|
||||
rightText += " "
|
||||
}
|
||||
|
||||
statusLineStyle := defStyle.Reverse(true)
|
||||
@@ -51,6 +75,12 @@ func (sline *Statusline) Display() {
|
||||
|
||||
// Maybe there is a unicode filename?
|
||||
fileRunes := []rune(file)
|
||||
|
||||
if sline.view.Type == vtTerm {
|
||||
fileRunes = []rune(sline.view.term.title)
|
||||
rightText = ""
|
||||
}
|
||||
|
||||
viewX := sline.view.x
|
||||
if viewX != 0 {
|
||||
screen.SetContent(viewX, y, ' ', nil, statusLineStyle)
|
||||
|
||||
@@ -8,11 +8,13 @@ import (
|
||||
|
||||
var tabBarOffset int
|
||||
|
||||
// A Tab holds an array of views and a splitTree to determine how the
|
||||
// views should be arranged
|
||||
type Tab struct {
|
||||
// This contains all the views in this tab
|
||||
// There is generally only one view per tab, but you can have
|
||||
// multiple views with splits
|
||||
views []*View
|
||||
Views []*View
|
||||
// This is the current view for this tab
|
||||
CurView int
|
||||
|
||||
@@ -22,12 +24,12 @@ type Tab struct {
|
||||
// NewTabFromView creates a new tab and puts the given view in the tab
|
||||
func NewTabFromView(v *View) *Tab {
|
||||
t := new(Tab)
|
||||
t.views = append(t.views, v)
|
||||
t.views[0].Num = 0
|
||||
t.Views = append(t.Views, v)
|
||||
t.Views[0].Num = 0
|
||||
|
||||
t.tree = new(SplitTree)
|
||||
t.tree.kind = VerticalSplit
|
||||
t.tree.children = []Node{NewLeafNode(t.views[0], t.tree)}
|
||||
t.tree.children = []Node{NewLeafNode(t.Views[0], t.tree)}
|
||||
|
||||
w, h := screen.Size()
|
||||
t.tree.width = w
|
||||
@@ -36,6 +38,9 @@ func NewTabFromView(v *View) *Tab {
|
||||
if globalSettings["infobar"].(bool) {
|
||||
t.tree.height--
|
||||
}
|
||||
if globalSettings["keymenu"].(bool) {
|
||||
t.tree.height -= 2
|
||||
}
|
||||
|
||||
t.Resize()
|
||||
|
||||
@@ -45,15 +50,18 @@ func NewTabFromView(v *View) *Tab {
|
||||
// SetNum sets all this tab's views to have the correct tab number
|
||||
func (t *Tab) SetNum(num int) {
|
||||
t.tree.tabNum = num
|
||||
for _, v := range t.views {
|
||||
for _, v := range t.Views {
|
||||
v.TabNum = num
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup cleans up the tree (for example if views have closed)
|
||||
func (t *Tab) Cleanup() {
|
||||
t.tree.Cleanup()
|
||||
}
|
||||
|
||||
// Resize handles a resize event from the terminal and resizes
|
||||
// all child views correctly
|
||||
func (t *Tab) Resize() {
|
||||
w, h := screen.Size()
|
||||
t.tree.width = w
|
||||
@@ -62,18 +70,24 @@ func (t *Tab) Resize() {
|
||||
if globalSettings["infobar"].(bool) {
|
||||
t.tree.height--
|
||||
}
|
||||
if globalSettings["keymenu"].(bool) {
|
||||
t.tree.height -= 2
|
||||
}
|
||||
|
||||
t.tree.ResizeSplits()
|
||||
|
||||
for i, v := range t.views {
|
||||
for i, v := range t.Views {
|
||||
v.Num = i
|
||||
if v.Type == vtTerm {
|
||||
v.term.Resize(v.Width, v.Height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CurView returns the current view
|
||||
func CurView() *View {
|
||||
curTab := tabs[curTab]
|
||||
return curTab.views[curTab.CurView]
|
||||
return curTab.Views[curTab.CurView]
|
||||
}
|
||||
|
||||
// TabbarString returns the string that should be displayed in the tabbar
|
||||
@@ -89,9 +103,9 @@ func TabbarString() (string, map[int]int) {
|
||||
} else {
|
||||
str += " "
|
||||
}
|
||||
buf := t.views[t.CurView].Buf
|
||||
buf := t.Views[t.CurView].Buf
|
||||
str += buf.GetName()
|
||||
if buf.IsModified {
|
||||
if buf.Modified() {
|
||||
str += " +"
|
||||
}
|
||||
if i == curTab {
|
||||
|
||||
228
cmd/micro/terminal.go
Normal file
228
cmd/micro/terminal.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/clipboard"
|
||||
"github.com/zyedidia/tcell"
|
||||
"github.com/zyedidia/terminal"
|
||||
)
|
||||
|
||||
const (
|
||||
VTIdle = iota // Waiting for a new command
|
||||
VTRunning // Currently running a command
|
||||
VTDone // Finished running a command
|
||||
)
|
||||
|
||||
// A Terminal holds information for the terminal emulator
|
||||
type Terminal struct {
|
||||
state terminal.State
|
||||
view *View
|
||||
vtOld ViewType
|
||||
term *terminal.VT
|
||||
title string
|
||||
status int
|
||||
selection [2]Loc
|
||||
wait bool
|
||||
getOutput bool
|
||||
output *bytes.Buffer
|
||||
callback string
|
||||
}
|
||||
|
||||
// HasSelection returns whether this terminal has a valid selection
|
||||
func (t *Terminal) HasSelection() bool {
|
||||
return t.selection[0] != t.selection[1]
|
||||
}
|
||||
|
||||
// GetSelection returns the selected text
|
||||
func (t *Terminal) GetSelection(width int) string {
|
||||
start := t.selection[0]
|
||||
end := t.selection[1]
|
||||
if start.GreaterThan(end) {
|
||||
start, end = end, start
|
||||
}
|
||||
var ret string
|
||||
var l Loc
|
||||
for y := start.Y; y <= end.Y; y++ {
|
||||
for x := 0; x < width; x++ {
|
||||
l.X, l.Y = x, y
|
||||
if l.GreaterEqual(start) && l.LessThan(end) {
|
||||
c, _, _ := t.state.Cell(x, y)
|
||||
ret += string(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Start begins a new command in this terminal with a given view
|
||||
func (t *Terminal) Start(execCmd []string, view *View, getOutput bool) error {
|
||||
if len(execCmd) <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := exec.Command(execCmd[0], execCmd[1:]...)
|
||||
t.output = nil
|
||||
if getOutput {
|
||||
t.output = bytes.NewBuffer([]byte{})
|
||||
}
|
||||
term, _, err := terminal.Start(&t.state, cmd, t.output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.term = term
|
||||
t.view = view
|
||||
t.getOutput = getOutput
|
||||
t.vtOld = view.Type
|
||||
t.status = VTRunning
|
||||
t.title = execCmd[0] + ":" + strconv.Itoa(cmd.Process.Pid)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
err := term.Parse()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "[Press enter to close]")
|
||||
break
|
||||
}
|
||||
updateterm <- true
|
||||
}
|
||||
closeterm <- view.Num
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resize informs the terminal of a resize event
|
||||
func (t *Terminal) Resize(width, height int) {
|
||||
t.term.Resize(width, height)
|
||||
}
|
||||
|
||||
// HandleEvent handles a tcell event by forwarding it to the terminal emulator
|
||||
// If the event is a mouse event and the program running in the emulator
|
||||
// does not have mouse support, the emulator will support selections and
|
||||
// copy-paste
|
||||
func (t *Terminal) HandleEvent(event tcell.Event) {
|
||||
if e, ok := event.(*tcell.EventKey); ok {
|
||||
if t.status == VTDone {
|
||||
switch e.Key() {
|
||||
case tcell.KeyEscape, tcell.KeyCtrlQ, tcell.KeyEnter:
|
||||
t.Close()
|
||||
t.view.Type = vtDefault
|
||||
default:
|
||||
}
|
||||
}
|
||||
if e.Key() == tcell.KeyCtrlC && t.HasSelection() {
|
||||
clipboard.WriteAll(t.GetSelection(t.view.Width), "clipboard")
|
||||
messenger.Message("Copied selection to clipboard")
|
||||
} else if t.status != VTDone {
|
||||
t.WriteString(event.EscSeq())
|
||||
}
|
||||
} else if e, ok := event.(*tcell.EventMouse); !ok || t.state.Mode(terminal.ModeMouseMask) {
|
||||
t.WriteString(event.EscSeq())
|
||||
} else {
|
||||
x, y := e.Position()
|
||||
x -= t.view.x
|
||||
y += t.view.y
|
||||
|
||||
if e.Buttons() == tcell.Button1 {
|
||||
if !t.view.mouseReleased {
|
||||
// drag
|
||||
t.selection[1].X = x
|
||||
t.selection[1].Y = y
|
||||
} else {
|
||||
t.selection[0].X = x
|
||||
t.selection[0].Y = y
|
||||
t.selection[1].X = x
|
||||
t.selection[1].Y = y
|
||||
}
|
||||
|
||||
t.view.mouseReleased = false
|
||||
} else if e.Buttons() == tcell.ButtonNone {
|
||||
if !t.view.mouseReleased {
|
||||
t.selection[1].X = x
|
||||
t.selection[1].Y = y
|
||||
}
|
||||
t.view.mouseReleased = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stops execution of the terminal and sets the status
|
||||
// to VTDone
|
||||
func (t *Terminal) Stop() {
|
||||
t.term.File().Close()
|
||||
t.term.Close()
|
||||
if t.wait {
|
||||
t.status = VTDone
|
||||
} else {
|
||||
t.Close()
|
||||
t.view.Type = t.vtOld
|
||||
}
|
||||
}
|
||||
|
||||
// Close sets the status to VTIdle indicating that the terminal
|
||||
// is ready for a new command to execute
|
||||
func (t *Terminal) Close() {
|
||||
t.status = VTIdle
|
||||
// call the lua function that the user has given as a callback
|
||||
if t.getOutput {
|
||||
_, err := Call(t.callback, t.output.String())
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
|
||||
TermMessage(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WriteString writes a given string to this terminal's pty
|
||||
func (t *Terminal) WriteString(str string) {
|
||||
t.term.File().WriteString(str)
|
||||
}
|
||||
|
||||
// Display displays this terminal in a view
|
||||
func (t *Terminal) Display() {
|
||||
divider := 0
|
||||
if t.view.x != 0 {
|
||||
divider = 1
|
||||
dividerStyle := defStyle
|
||||
if style, ok := colorscheme["divider"]; ok {
|
||||
dividerStyle = style
|
||||
}
|
||||
for i := 0; i < t.view.Height; i++ {
|
||||
screen.SetContent(t.view.x, t.view.y+i, '|', nil, dividerStyle.Reverse(true))
|
||||
}
|
||||
}
|
||||
t.state.Lock()
|
||||
defer t.state.Unlock()
|
||||
|
||||
var l Loc
|
||||
for y := 0; y < t.view.Height; y++ {
|
||||
for x := 0; x < t.view.Width; x++ {
|
||||
l.X, l.Y = x, y
|
||||
c, f, b := t.state.Cell(x, y)
|
||||
|
||||
fg, bg := int(f), int(b)
|
||||
if f == terminal.DefaultFG {
|
||||
fg = int(tcell.ColorDefault)
|
||||
}
|
||||
if b == terminal.DefaultBG {
|
||||
bg = int(tcell.ColorDefault)
|
||||
}
|
||||
st := tcell.StyleDefault.Foreground(GetColor256(int(fg))).Background(GetColor256(int(bg)))
|
||||
|
||||
if l.LessThan(t.selection[1]) && l.GreaterEqual(t.selection[0]) || l.LessThan(t.selection[0]) && l.GreaterEqual(t.selection[1]) {
|
||||
st = st.Reverse(true)
|
||||
}
|
||||
|
||||
screen.SetContent(t.view.x+x+divider, t.view.y+y, c, nil, st)
|
||||
}
|
||||
}
|
||||
if t.state.CursorVisible() && tabs[curTab].CurView == t.view.Num {
|
||||
curx, cury := t.state.Cursor()
|
||||
screen.ShowCursor(curx+t.view.x+divider, cury+t.view.y)
|
||||
}
|
||||
}
|
||||
203
cmd/micro/terminfo/LICENSE.md
Normal file
203
cmd/micro/terminfo/LICENSE.md
Normal file
@@ -0,0 +1,203 @@
|
||||
|
||||
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.
|
||||
|
||||
6
cmd/micro/terminfo/README.md
Normal file
6
cmd/micro/terminfo/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# 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.
|
||||
512
cmd/micro/terminfo/mkinfo.go
Normal file
512
cmd/micro/terminfo/mkinfo.go
Normal file
@@ -0,0 +1,512 @@
|
||||
// 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 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
|
||||
}
|
||||
837
cmd/micro/terminfo/terminfo.go
Normal file
837
cmd/micro/terminfo/terminfo.go
Normal file
@@ -0,0 +1,837 @@
|
||||
// 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.Reader
|
||||
if f, e = os.Open(fname); e != nil {
|
||||
return nil, e
|
||||
}
|
||||
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
|
||||
}
|
||||
194
cmd/micro/terminfo/terminfo_test.go
Normal file
194
cmd/micro/terminfo/terminfo_test.go
Normal file
@@ -0,0 +1,194 @@
|
||||
// Copyright 2016 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"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
// This terminfo entry is a stripped down version from
|
||||
// xterm-256color, but I've added some of my own entries.
|
||||
var testTerminfo = &Terminfo{
|
||||
Name: "simulation_test",
|
||||
Columns: 80,
|
||||
Lines: 24,
|
||||
Colors: 256,
|
||||
Bell: "\a",
|
||||
Blink: "\x1b2ms$<2>",
|
||||
Reverse: "\x1b[7m",
|
||||
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
|
||||
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
|
||||
AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
|
||||
Mouse: "\x1b[M",
|
||||
MouseMode: "%?%p1%{1}%=%t%'h'%Pa%e%'l'%Pa%;\x1b[?1000%ga%c\x1b[?1003%ga%c\x1b[?1006%ga%c",
|
||||
SetCursor: "\x1b[%i%p1%d;%p2%dH",
|
||||
PadChar: "\x00",
|
||||
}
|
||||
|
||||
func TestTerminfo(t *testing.T) {
|
||||
|
||||
ti := testTerminfo
|
||||
|
||||
Convey("Terminfo parameter processing", t, func() {
|
||||
// This tests %i, and basic parameter strings too
|
||||
Convey("TGoto works", func() {
|
||||
s := ti.TGoto(7, 9)
|
||||
So(s, ShouldEqual, "\x1b[10;8H")
|
||||
})
|
||||
|
||||
// This tests some conditionals
|
||||
Convey("TParm extended formats work", func() {
|
||||
s := ti.TParm("A[%p1%2.2X]B", 47)
|
||||
So(s, ShouldEqual, "A[2F]B")
|
||||
})
|
||||
|
||||
// This tests some conditionals
|
||||
Convey("TParm colors work", func() {
|
||||
s := ti.TParm(ti.SetFg, 7)
|
||||
So(s, ShouldEqual, "\x1b[37m")
|
||||
|
||||
s = ti.TParm(ti.SetFg, 15)
|
||||
So(s, ShouldEqual, "\x1b[97m")
|
||||
|
||||
s = ti.TParm(ti.SetFg, 200)
|
||||
So(s, ShouldEqual, "\x1b[38;5;200m")
|
||||
})
|
||||
|
||||
// This tests variables
|
||||
Convey("TParm mouse mode works", func() {
|
||||
s := ti.TParm(ti.MouseMode, 1)
|
||||
So(s, ShouldEqual, "\x1b[?1000h\x1b[?1003h\x1b[?1006h")
|
||||
s = ti.TParm(ti.MouseMode, 0)
|
||||
So(s, ShouldEqual, "\x1b[?1000l\x1b[?1003l\x1b[?1006l")
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Convey("Terminfo delay handling", t, func() {
|
||||
|
||||
Convey("19200 baud", func() {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
ti.TPuts(buf, ti.Blink, 19200)
|
||||
s := string(buf.Bytes())
|
||||
So(s, ShouldEqual, "\x1b2ms\x00\x00\x00\x00")
|
||||
})
|
||||
|
||||
Convey("50 baud", func() {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
ti.TPuts(buf, ti.Blink, 50)
|
||||
s := string(buf.Bytes())
|
||||
So(s, ShouldEqual, "\x1b2ms")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestTerminfoDatabase(t *testing.T) {
|
||||
|
||||
Convey("Database Lookups work", t, func() {
|
||||
Convey("Basic lookup works", func() {
|
||||
os.Setenv("TCELLDB", "testdata/test1")
|
||||
ti, err := LookupTerminfo("test1")
|
||||
So(err, ShouldBeNil)
|
||||
So(ti, ShouldNotBeNil)
|
||||
So(ti.Columns, ShouldEqual, 80)
|
||||
|
||||
ti, err = LookupTerminfo("alias1")
|
||||
So(err, ShouldBeNil)
|
||||
So(ti, ShouldNotBeNil)
|
||||
So(ti.Columns, ShouldEqual, 80)
|
||||
|
||||
os.Setenv("TCELLDB", "testdata")
|
||||
ti, err = LookupTerminfo("test2")
|
||||
So(err, ShouldBeNil)
|
||||
So(ti, ShouldNotBeNil)
|
||||
So(ti.Columns, ShouldEqual, 80)
|
||||
So(len(ti.Aliases), ShouldEqual, 1)
|
||||
So(ti.Aliases[0], ShouldEqual, "alias2")
|
||||
})
|
||||
|
||||
Convey("Incorrect primary name works", func() {
|
||||
os.Setenv("TCELLDB", "testdata")
|
||||
ti, err := LookupTerminfo("test3")
|
||||
So(err, ShouldNotBeNil)
|
||||
So(ti, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Loops fail", func() {
|
||||
os.Setenv("TCELLDB", "testdata")
|
||||
ti, err := LookupTerminfo("loop1")
|
||||
So(ti, ShouldBeNil)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Gzip database works", func() {
|
||||
os.Setenv("TCELLDB", "testdata")
|
||||
ti, err := LookupTerminfo("test-gzip")
|
||||
So(err, ShouldBeNil)
|
||||
So(ti, ShouldNotBeNil)
|
||||
So(ti.Columns, ShouldEqual, 80)
|
||||
})
|
||||
|
||||
Convey("Gzip alias lookup works", func() {
|
||||
os.Setenv("TCELLDB", "testdata")
|
||||
ti, err := LookupTerminfo("alias-gzip")
|
||||
So(err, ShouldBeNil)
|
||||
So(ti, ShouldNotBeNil)
|
||||
So(ti.Columns, ShouldEqual, 80)
|
||||
})
|
||||
|
||||
Convey("Broken alias works", func() {
|
||||
os.Setenv("TCELLDB", "testdata")
|
||||
ti, err := LookupTerminfo("alias-none")
|
||||
So(err, ShouldNotBeNil)
|
||||
So(ti, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Combined database works", func() {
|
||||
os.Setenv("TCELLDB", "testdata/combined")
|
||||
ti, err := LookupTerminfo("combined2")
|
||||
So(err, ShouldBeNil)
|
||||
So(ti, ShouldNotBeNil)
|
||||
So(ti.Lines, ShouldEqual, 102)
|
||||
|
||||
ti, err = LookupTerminfo("alias-comb1")
|
||||
So(err, ShouldBeNil)
|
||||
So(ti, ShouldNotBeNil)
|
||||
So(ti.Lines, ShouldEqual, 101)
|
||||
|
||||
ti, err = LookupTerminfo("combined3")
|
||||
So(err, ShouldBeNil)
|
||||
So(ti, ShouldNotBeNil)
|
||||
So(ti.Lines, ShouldEqual, 103)
|
||||
|
||||
ti, err = LookupTerminfo("combined1")
|
||||
So(err, ShouldBeNil)
|
||||
So(ti, ShouldNotBeNil)
|
||||
So(ti.Lines, ShouldEqual, 101)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkSetFgBg(b *testing.B) {
|
||||
ti := testTerminfo
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
ti.TParm(ti.SetFg, 100, 200)
|
||||
ti.TParm(ti.SetBg, 100, 200)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
@@ -23,6 +23,54 @@ func Count(s string) int {
|
||||
return utf8.RuneCountInString(s)
|
||||
}
|
||||
|
||||
// Convert byte array to rune array
|
||||
func toRunes(b []byte) []rune {
|
||||
runes := make([]rune, 0, utf8.RuneCount(b))
|
||||
|
||||
for len(b) > 0 {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
runes = append(runes, r)
|
||||
|
||||
b = b[size:]
|
||||
}
|
||||
|
||||
return runes
|
||||
}
|
||||
|
||||
func sliceStart(slc []byte, index int) []byte {
|
||||
len := len(slc)
|
||||
i := 0
|
||||
totalSize := 0
|
||||
for totalSize < len {
|
||||
if i >= index {
|
||||
return slc[totalSize:]
|
||||
}
|
||||
|
||||
_, size := utf8.DecodeRune(slc[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
|
||||
return slc[totalSize:]
|
||||
}
|
||||
|
||||
func sliceEnd(slc []byte, index int) []byte {
|
||||
len := len(slc)
|
||||
i := 0
|
||||
totalSize := 0
|
||||
for totalSize < len {
|
||||
if i >= index {
|
||||
return slc[:totalSize]
|
||||
}
|
||||
|
||||
_, size := utf8.DecodeRune(slc[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
|
||||
return slc[:totalSize]
|
||||
}
|
||||
|
||||
// NumOccurrences counts the number of occurrences of a byte in a string
|
||||
func NumOccurrences(s string, c byte) int {
|
||||
var n int
|
||||
@@ -55,6 +103,7 @@ func Max(a, b int) int {
|
||||
return b
|
||||
}
|
||||
|
||||
// FSize gets the size of a file
|
||||
func FSize(f *os.File) int64 {
|
||||
fi, _ := f.Stat()
|
||||
// get the size
|
||||
@@ -129,7 +178,7 @@ func GetLeadingWhitespace(str string) string {
|
||||
}
|
||||
|
||||
// IsSpaces checks if a given string is only spaces
|
||||
func IsSpaces(str string) bool {
|
||||
func IsSpaces(str []byte) bool {
|
||||
for _, c := range str {
|
||||
if c != ' ' {
|
||||
return false
|
||||
@@ -245,6 +294,7 @@ func lcs(a, b string) string {
|
||||
return lcs
|
||||
}
|
||||
|
||||
// CommonSubstring gets a common substring among the inputs
|
||||
func CommonSubstring(arr ...string) string {
|
||||
commonStr := arr[0]
|
||||
|
||||
@@ -273,93 +323,44 @@ func ShortFuncName(i interface{}) string {
|
||||
return strings.TrimPrefix(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name(), "main.(*View).")
|
||||
}
|
||||
|
||||
// SplitCommandArgs separates multiple command arguments which may be quoted.
|
||||
// The returned slice contains at least one string
|
||||
func SplitCommandArgs(input string) []string {
|
||||
var result []string
|
||||
var curQuote *bytes.Buffer
|
||||
|
||||
curArg := new(bytes.Buffer)
|
||||
escape := false
|
||||
|
||||
finishQuote := func() {
|
||||
if curQuote == nil {
|
||||
return
|
||||
}
|
||||
str := curQuote.String()
|
||||
if unquoted, err := strconv.Unquote(str); err == nil {
|
||||
str = unquoted
|
||||
}
|
||||
curArg.WriteString(str)
|
||||
curQuote = nil
|
||||
// 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 {
|
||||
if !strings.HasPrefix(path, "~") {
|
||||
return path
|
||||
}
|
||||
|
||||
appendResult := func() {
|
||||
finishQuote()
|
||||
escape = false
|
||||
var userData *user.User
|
||||
var err error
|
||||
|
||||
str := curArg.String()
|
||||
result = append(result, str)
|
||||
curArg.Reset()
|
||||
}
|
||||
|
||||
for _, r := range input {
|
||||
if r == ' ' && curQuote == nil {
|
||||
appendResult()
|
||||
} else {
|
||||
runeHandled := false
|
||||
appendRuneToBuff := func() {
|
||||
if curQuote != nil {
|
||||
curQuote.WriteRune(r)
|
||||
} else {
|
||||
curArg.WriteRune(r)
|
||||
}
|
||||
runeHandled = true
|
||||
}
|
||||
|
||||
if r == '"' && curQuote == nil {
|
||||
curQuote = new(bytes.Buffer)
|
||||
appendRuneToBuff()
|
||||
homeString := strings.Split(path, "/")[0]
|
||||
if homeString == "~" {
|
||||
userData, err = user.Current()
|
||||
if err != nil {
|
||||
messenger.Error("Could not find user: ", err)
|
||||
}
|
||||
} else {
|
||||
userData, err = user.Lookup(homeString[1:])
|
||||
if err != nil {
|
||||
if messenger != nil {
|
||||
messenger.Error("Could not find user: ", err)
|
||||
} else {
|
||||
if curQuote != nil && !escape {
|
||||
if r == '"' {
|
||||
appendRuneToBuff()
|
||||
finishQuote()
|
||||
} else if r == '\\' {
|
||||
appendRuneToBuff()
|
||||
escape = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
TermMessage("Could not find user: ", err)
|
||||
}
|
||||
if !runeHandled {
|
||||
appendRuneToBuff()
|
||||
}
|
||||
}
|
||||
|
||||
escape = false
|
||||
}
|
||||
appendResult()
|
||||
return result
|
||||
}
|
||||
|
||||
// JoinCommandArgs joins multiple command arguments and quote the strings if needed.
|
||||
func JoinCommandArgs(args ...string) string {
|
||||
buf := new(bytes.Buffer)
|
||||
first := true
|
||||
for _, arg := range args {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
buf.WriteRune(' ')
|
||||
}
|
||||
quoted := strconv.Quote(arg)
|
||||
if quoted[1:len(quoted)-1] != arg || strings.ContainsRune(arg, ' ') {
|
||||
buf.WriteString(quoted)
|
||||
} else {
|
||||
buf.WriteString(arg)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
home := userData.HomeDir
|
||||
|
||||
return strings.Replace(path, homeString, home, 1)
|
||||
}
|
||||
|
||||
// GetPath returns a filename without everything following a `:`
|
||||
// This is used for opening files like util.go:10:5 to specify a line and column
|
||||
func GetPath(path string) string {
|
||||
if strings.Contains(path, ":") {
|
||||
path = strings.Split(path, ":")[0]
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -67,56 +66,6 @@ func TestIsWordChar(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestJoinAndSplitCommandArgs(t *testing.T) {
|
||||
tests := []struct {
|
||||
Query []string
|
||||
Wanted string
|
||||
}{
|
||||
{[]string{`test case`}, `"test case"`},
|
||||
{[]string{`quote "test"`}, `"quote \"test\""`},
|
||||
{[]string{`slash\\\ test`}, `"slash\\\\\\ test"`},
|
||||
{[]string{`path 1`, `path\" 2`}, `"path 1" "path\\\" 2"`},
|
||||
{[]string{`foo`}, `foo`},
|
||||
{[]string{`foo\"bar`}, `"foo\\\"bar"`},
|
||||
{[]string{``}, ``},
|
||||
{[]string{`"`}, `"\""`},
|
||||
{[]string{`a`, ``}, `a `},
|
||||
{[]string{``, ``, ``, ``}, ` `},
|
||||
{[]string{"\n"}, `"\n"`},
|
||||
{[]string{"foo\tbar"}, `"foo\tbar"`},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
if result := JoinCommandArgs(test.Query...); test.Wanted != result {
|
||||
t.Errorf("JoinCommandArgs failed at Test %d\nGot: %q", i, result)
|
||||
}
|
||||
|
||||
if result := SplitCommandArgs(test.Wanted); !reflect.DeepEqual(test.Query, result) {
|
||||
t.Errorf("SplitCommandArgs failed at Test %d\nGot: `%q`", i, result)
|
||||
}
|
||||
}
|
||||
|
||||
splitTests := []struct {
|
||||
Query string
|
||||
Wanted []string
|
||||
}{
|
||||
{`"hallo""Welt"`, []string{`halloWelt`}},
|
||||
{`"hallo" "Welt"`, []string{`hallo`, `Welt`}},
|
||||
{`\"`, []string{`\"`}},
|
||||
{`"foo`, []string{`"foo`}},
|
||||
{`"foo"`, []string{`foo`}},
|
||||
{`"\"`, []string{`"\"`}},
|
||||
{`"C:\\"foo.txt`, []string{`C:\foo.txt`}},
|
||||
{`"\n"new"\n"line`, []string{"\nnew\nline"}},
|
||||
}
|
||||
|
||||
for i, test := range splitTests {
|
||||
if result := SplitCommandArgs(test.Query); !reflect.DeepEqual(test.Wanted, result) {
|
||||
t.Errorf("SplitCommandArgs failed at Split-Test %d\nGot: `%q`", i, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringWidth(t *testing.T) {
|
||||
tabsize := 4
|
||||
if w := StringWidth("1\t2", tabsize); w != 5 {
|
||||
|
||||
1
cmd/micro/vendor/github.com/flynn/json5
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/flynn/json5
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/flynn/json5 added at 7620272ed6
1
cmd/micro/vendor/github.com/jtolds/gls
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/jtolds/gls
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/jtolds/gls added at 77f18212c9
1
cmd/micro/vendor/github.com/smartystreets/assertions
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/smartystreets/assertions
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/smartystreets/assertions added at 0b37b35ec7
1
cmd/micro/vendor/github.com/smartystreets/goconvey
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/smartystreets/goconvey
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/smartystreets/goconvey added at e5b2b7c911
2
cmd/micro/vendor/github.com/zyedidia/clipboard
generated
vendored
2
cmd/micro/vendor/github.com/zyedidia/clipboard
generated
vendored
Submodule cmd/micro/vendor/github.com/zyedidia/clipboard updated: adacf416ce...4611e809d8
1
cmd/micro/vendor/github.com/zyedidia/json5
generated
vendored
1
cmd/micro/vendor/github.com/zyedidia/json5
generated
vendored
Submodule cmd/micro/vendor/github.com/zyedidia/json5 deleted from 2518f8beeb
1
cmd/micro/vendor/github.com/zyedidia/pty
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/zyedidia/pty
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/zyedidia/pty added at 30364665a2
2
cmd/micro/vendor/github.com/zyedidia/tcell
generated
vendored
2
cmd/micro/vendor/github.com/zyedidia/tcell
generated
vendored
Submodule cmd/micro/vendor/github.com/zyedidia/tcell updated: 8ae342e877...208b6e8f2f
1
cmd/micro/vendor/github.com/zyedidia/terminal
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/zyedidia/terminal
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/zyedidia/terminal added at 1760577dbc
@@ -1,19 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
// The ViewType defines what kind of view this is
|
||||
type ViewType struct {
|
||||
kind int
|
||||
readonly bool // The file cannot be edited
|
||||
scratch bool // The file cannot be saved
|
||||
Kind int
|
||||
Readonly bool // The file cannot be edited
|
||||
Scratch bool // The file cannot be saved
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -21,6 +22,8 @@ var (
|
||||
vtHelp = ViewType{1, true, true}
|
||||
vtLog = ViewType{2, true, true}
|
||||
vtScratch = ViewType{3, false, true}
|
||||
vtRaw = ViewType{4, true, true}
|
||||
vtTerm = ViewType{5, true, true}
|
||||
)
|
||||
|
||||
// The View struct stores information about a view into a buffer.
|
||||
@@ -62,7 +65,7 @@ type View struct {
|
||||
// The buffer
|
||||
Buf *Buffer
|
||||
// The statusline
|
||||
sline Statusline
|
||||
sline *Statusline
|
||||
|
||||
// Since tcell doesn't differentiate between a mouse release event
|
||||
// and a mouse move event with no keys pressed, we need to keep
|
||||
@@ -70,9 +73,12 @@ type View struct {
|
||||
// mouse release events
|
||||
mouseReleased bool
|
||||
|
||||
// We need to keep track of insert key press toggle
|
||||
isOverwriteMode bool
|
||||
// This stores when the last click was
|
||||
// This is useful for detecting double and triple clicks
|
||||
lastClickTime time.Time
|
||||
lastLoc Loc
|
||||
|
||||
// lastCutTime stores when the last ctrl+k was issued.
|
||||
// It is used for clearing the clipboard to replace it with fresh cut lines.
|
||||
@@ -88,9 +94,16 @@ type View struct {
|
||||
// Same here, just to keep track for mouse move events
|
||||
tripleClick bool
|
||||
|
||||
// The cellview used for displaying and syntax highlighting
|
||||
cellview *CellView
|
||||
|
||||
splitNode *LeafNode
|
||||
|
||||
// The scrollbar
|
||||
scrollbar *ScrollBar
|
||||
|
||||
// Virtual terminal
|
||||
term *Terminal
|
||||
}
|
||||
|
||||
// NewView returns a new fullscreen view
|
||||
@@ -116,7 +129,11 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View {
|
||||
|
||||
v.messages = make(map[string][]GutterMessage)
|
||||
|
||||
v.sline = Statusline{
|
||||
v.sline = &Statusline{
|
||||
view: v,
|
||||
}
|
||||
|
||||
v.scrollbar = &ScrollBar{
|
||||
view: v,
|
||||
}
|
||||
|
||||
@@ -124,6 +141,8 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View {
|
||||
v.Height--
|
||||
}
|
||||
|
||||
v.term = new(Terminal)
|
||||
|
||||
for pl := range loadedPlugins {
|
||||
_, err := Call(pl+".onViewOpen", v)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
|
||||
@@ -144,6 +163,24 @@ func (v *View) ToggleStatusLine() {
|
||||
}
|
||||
}
|
||||
|
||||
// StartTerminal execs a command in this view
|
||||
func (v *View) StartTerminal(execCmd []string, wait bool, getOutput bool, luaCallback string) error {
|
||||
err := v.term.Start(execCmd, v, getOutput)
|
||||
v.term.wait = wait
|
||||
v.term.callback = luaCallback
|
||||
if err == nil {
|
||||
v.term.Resize(v.Width, v.Height)
|
||||
v.Type = vtTerm
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// CloseTerminal shuts down the tty running in this view
|
||||
// and returns it to the default view type
|
||||
func (v *View) CloseTerminal() {
|
||||
v.term.Stop()
|
||||
}
|
||||
|
||||
// ToggleTabbar creates an extra row for the tabbar if necessary
|
||||
func (v *View) ToggleTabbar() {
|
||||
if len(tabs) > 1 {
|
||||
@@ -198,7 +235,7 @@ func (v *View) ScrollDown(n int) {
|
||||
// If there are unsaved changes, the user will be asked if the view can be closed
|
||||
// causing them to lose the unsaved changes
|
||||
func (v *View) CanClose() bool {
|
||||
if v.Type == vtDefault && v.Buf.IsModified {
|
||||
if v.Type == vtDefault && v.Buf.Modified() {
|
||||
var choice bool
|
||||
var canceled bool
|
||||
if v.Buf.Settings["autosave"].(bool) {
|
||||
@@ -210,15 +247,12 @@ func (v *View) CanClose() bool {
|
||||
//if char == 'y' {
|
||||
if choice {
|
||||
v.Save(true)
|
||||
return true
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
// OpenBuffer opens a new buffer in this view.
|
||||
@@ -238,30 +272,21 @@ func (v *View) OpenBuffer(buf *Buffer) {
|
||||
// Set mouseReleased to true because we assume the mouse is not being pressed when
|
||||
// the editor is opened
|
||||
v.mouseReleased = true
|
||||
// Set isOverwriteMode to false, because we assume we are in the default mode when editor
|
||||
// is opened
|
||||
v.isOverwriteMode = false
|
||||
v.lastClickTime = time.Time{}
|
||||
|
||||
GlobalPluginCall("onBufferOpen", v.Buf)
|
||||
GlobalPluginCall("onViewOpen", v)
|
||||
}
|
||||
|
||||
// Open opens the given file in the view
|
||||
func (v *View) Open(filename string) {
|
||||
home, _ := homedir.Dir()
|
||||
filename = strings.Replace(filename, "~", home, 1)
|
||||
file, err := os.Open(filename)
|
||||
fileInfo, _ := os.Stat(filename)
|
||||
|
||||
if err == nil && fileInfo.IsDir() {
|
||||
messenger.Error(filename, " is a directory")
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
var buf *Buffer
|
||||
func (v *View) Open(path string) {
|
||||
buf, err := NewBufferFromFile(path)
|
||||
if err != nil {
|
||||
messenger.Message(err.Error())
|
||||
// File does not exist -- create an empty buffer with that name
|
||||
buf = NewBufferFromString("", filename)
|
||||
} else {
|
||||
buf = NewBuffer(file, FSize(file), filename)
|
||||
messenger.Error(err)
|
||||
return
|
||||
}
|
||||
v.OpenBuffer(buf)
|
||||
}
|
||||
@@ -285,7 +310,7 @@ func (v *View) ReOpen() {
|
||||
// HSplit opens a horizontal split with the given buffer
|
||||
func (v *View) HSplit(buf *Buffer) {
|
||||
i := 0
|
||||
if v.Buf.Settings["splitBottom"].(bool) {
|
||||
if v.Buf.Settings["splitbottom"].(bool) {
|
||||
i = 1
|
||||
}
|
||||
v.splitNode.HSplit(buf, v.Num+i)
|
||||
@@ -294,7 +319,7 @@ func (v *View) HSplit(buf *Buffer) {
|
||||
// VSplit opens a vertical split with the given buffer
|
||||
func (v *View) VSplit(buf *Buffer) {
|
||||
i := 0
|
||||
if v.Buf.Settings["splitRight"].(bool) {
|
||||
if v.Buf.Settings["splitright"].(bool) {
|
||||
i = 1
|
||||
}
|
||||
v.splitNode.VSplit(buf, v.Num+i)
|
||||
@@ -355,6 +380,10 @@ func (v *View) GetSoftWrapLocation(vx, vy int) (int, int) {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// Bottomline returns the line number of the lowest line in the view
|
||||
// You might think that this is obviously just v.Topline + v.Height
|
||||
// but if softwrap is enabled things get complicated since one buffer
|
||||
// line can take up multiple lines in the view
|
||||
func (v *View) Bottomline() int {
|
||||
if !v.Buf.Settings["softwrap"].(bool) {
|
||||
return v.Topline + v.Height
|
||||
@@ -407,7 +436,7 @@ func (v *View) Relocate() bool {
|
||||
if cy > v.Topline+height-1-scrollmargin && cy < v.Buf.NumLines-scrollmargin {
|
||||
v.Topline = cy - height + 1 + scrollmargin
|
||||
ret = true
|
||||
} else if cy >= v.Buf.NumLines-scrollmargin && cy > height {
|
||||
} else if cy >= v.Buf.NumLines-scrollmargin && cy >= height {
|
||||
v.Topline = v.Buf.NumLines - height
|
||||
ret = true
|
||||
}
|
||||
@@ -426,6 +455,31 @@ func (v *View) Relocate() bool {
|
||||
return ret
|
||||
}
|
||||
|
||||
// GetMouseClickLocation gets the location in the buffer from a mouse click
|
||||
// on the screen
|
||||
func (v *View) GetMouseClickLocation(x, y int) (int, int) {
|
||||
x -= v.lineNumOffset - v.leftCol + v.x
|
||||
y += v.Topline - v.y
|
||||
|
||||
if y-v.Topline > v.Height-1 {
|
||||
v.ScrollDown(1)
|
||||
y = v.Height + v.Topline - 1
|
||||
}
|
||||
if y < 0 {
|
||||
y = 0
|
||||
}
|
||||
if x < 0 {
|
||||
x = 0
|
||||
}
|
||||
|
||||
newX, newY := v.GetSoftWrapLocation(x, y)
|
||||
if newX > Count(v.Buf.Line(newY)) {
|
||||
newX = Count(v.Buf.Line(newY))
|
||||
}
|
||||
|
||||
return newX, newY
|
||||
}
|
||||
|
||||
// MoveToMouseClick moves the cursor to location x, y assuming x, y were given
|
||||
// by a mouse click
|
||||
func (v *View) MoveToMouseClick(x, y int) {
|
||||
@@ -441,7 +495,6 @@ func (v *View) MoveToMouseClick(x, y int) {
|
||||
}
|
||||
|
||||
x, y = v.GetSoftWrapLocation(x, y)
|
||||
// x = v.Cursor.GetCharPosInLine(y, x)
|
||||
if x > Count(v.Buf.Line(y)) {
|
||||
x = Count(v.Buf.Line(y))
|
||||
}
|
||||
@@ -450,13 +503,15 @@ func (v *View) MoveToMouseClick(x, y int) {
|
||||
v.Cursor.LastVisualX = v.Cursor.GetVisualX()
|
||||
}
|
||||
|
||||
// Execute actions executes the supplied actions
|
||||
func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool {
|
||||
relocate := false
|
||||
readonlyBindingsList := []string{"Delete", "Insert", "Backspace", "Cut", "Play", "Paste", "Move", "Add", "DuplicateLine", "Macro"}
|
||||
for _, action := range actions {
|
||||
readonlyBindingsResult := false
|
||||
funcName := ShortFuncName(action)
|
||||
if v.Type.readonly == true {
|
||||
curv := CurView()
|
||||
if curv.Type.Readonly == true {
|
||||
// check for readonly and if true only let key bindings get called if they do not change the contents.
|
||||
for _, readonlyBindings := range readonlyBindingsList {
|
||||
if strings.Contains(funcName, readonlyBindings) {
|
||||
@@ -466,7 +521,7 @@ func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool {
|
||||
}
|
||||
if !readonlyBindingsResult {
|
||||
// call the key binding
|
||||
relocate = action(v, true) || relocate
|
||||
relocate = action(curv, true) || relocate
|
||||
// Macro
|
||||
if funcName != "ToggleMacro" && funcName != "PlayMacro" {
|
||||
if recordingMacro {
|
||||
@@ -479,13 +534,38 @@ func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool {
|
||||
return relocate
|
||||
}
|
||||
|
||||
func (v *View) SetCursor(c *Cursor) {
|
||||
// SetCursor sets the view's and buffer's cursor
|
||||
func (v *View) SetCursor(c *Cursor) bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
v.Cursor = c
|
||||
v.Buf.curCursor = c.Num
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// HandleEvent handles an event passed by the main loop
|
||||
func (v *View) HandleEvent(event tcell.Event) {
|
||||
if v.Type == vtTerm {
|
||||
v.term.HandleEvent(event)
|
||||
return
|
||||
}
|
||||
|
||||
if v.Type == vtRaw {
|
||||
v.Buf.Insert(v.Cursor.Loc, reflect.TypeOf(event).String()[7:])
|
||||
v.Buf.Insert(v.Cursor.Loc, fmt.Sprintf(": %q\n", event.EscSeq()))
|
||||
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
if e.Key() == tcell.KeyCtrlQ {
|
||||
v.Quit(true)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// This bool determines whether the view is relocated at the end of the function
|
||||
// By default it's true because most events should cause a relocate
|
||||
relocate := true
|
||||
@@ -493,6 +573,24 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
v.Buf.CheckModTime()
|
||||
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventRaw:
|
||||
for key, actions := range bindings {
|
||||
if key.keyCode == -1 {
|
||||
if e.EscSeq() == key.escape {
|
||||
for _, c := range v.Buf.cursors {
|
||||
ok := v.SetCursor(c)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
relocate = false
|
||||
relocate = v.ExecuteActions(actions) || relocate
|
||||
}
|
||||
v.SetCursor(&v.Buf.Cursor)
|
||||
v.Buf.MergeCursors()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case *tcell.EventKey:
|
||||
// Check first if input is a key binding, if it is we 'eat' the input and don't insert a rune
|
||||
isBinding := false
|
||||
@@ -505,7 +603,10 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
}
|
||||
if e.Modifiers() == key.modifiers {
|
||||
for _, c := range v.Buf.cursors {
|
||||
v.SetCursor(c)
|
||||
ok := v.SetCursor(c)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
relocate = false
|
||||
isBinding = true
|
||||
relocate = v.ExecuteActions(actions) || relocate
|
||||
@@ -516,9 +617,10 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !isBinding && e.Key() == tcell.KeyRune {
|
||||
// Check viewtype if readonly don't insert a rune (readonly help and log view etc.)
|
||||
if v.Type.readonly == false {
|
||||
if v.Type.Readonly == false {
|
||||
for _, c := range v.Buf.cursors {
|
||||
v.SetCursor(c)
|
||||
|
||||
@@ -527,7 +629,14 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
v.Cursor.DeleteSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
|
||||
|
||||
if v.isOverwriteMode {
|
||||
next := v.Cursor.Loc
|
||||
next.X++
|
||||
v.Buf.Replace(v.Cursor.Loc, next, string(e.Rune()))
|
||||
} else {
|
||||
v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
|
||||
}
|
||||
|
||||
for pl := range loadedPlugins {
|
||||
_, err := Call(pl+".onRune", string(e.Rune()), v)
|
||||
@@ -545,7 +654,7 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
}
|
||||
case *tcell.EventPaste:
|
||||
// Check viewtype if readonly don't paste (readonly help and log view etc.)
|
||||
if v.Type.readonly == false {
|
||||
if v.Type.Readonly == false {
|
||||
if !PreActionCall("Paste", v) {
|
||||
break
|
||||
}
|
||||
@@ -553,7 +662,6 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
for _, c := range v.Buf.cursors {
|
||||
v.SetCursor(c)
|
||||
v.paste(e.Text())
|
||||
|
||||
}
|
||||
v.SetCursor(&v.Buf.Cursor)
|
||||
|
||||
@@ -568,7 +676,10 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
for key, actions := range bindings {
|
||||
if button == key.buttons && e.Modifiers() == key.modifiers {
|
||||
for _, c := range v.Buf.cursors {
|
||||
v.SetCursor(c)
|
||||
ok := v.SetCursor(c)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
relocate = v.ExecuteActions(actions) || relocate
|
||||
}
|
||||
v.SetCursor(&v.Buf.Cursor)
|
||||
@@ -673,13 +784,19 @@ func (v *View) openHelp(helpPage string) {
|
||||
}
|
||||
}
|
||||
|
||||
// DisplayView draws the view to the screen
|
||||
func (v *View) DisplayView() {
|
||||
if v.Type == vtTerm {
|
||||
v.term.Display()
|
||||
return
|
||||
}
|
||||
|
||||
if v.Buf.Settings["softwrap"].(bool) && v.leftCol != 0 {
|
||||
v.leftCol = 0
|
||||
}
|
||||
|
||||
if v.Type == vtLog {
|
||||
// Log views should always follow the cursor...
|
||||
if v.Type == vtLog || v.Type == vtRaw {
|
||||
// Log or raw views should always follow the cursor...
|
||||
v.Relocate()
|
||||
}
|
||||
|
||||
@@ -743,11 +860,11 @@ func (v *View) DisplayView() {
|
||||
}
|
||||
|
||||
colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64))
|
||||
if colorcolumn != 0 {
|
||||
if colorcolumn != 0 && xOffset+colorcolumn-v.leftCol < v.Width {
|
||||
style := GetColor("color-column")
|
||||
fg, _, _ := style.Decompose()
|
||||
st := defStyle.Background(fg)
|
||||
screen.SetContent(xOffset+colorcolumn, yOffset+visualLineN, ' ', nil, st)
|
||||
screen.SetContent(xOffset+colorcolumn-v.leftCol, yOffset+visualLineN, ' ', nil, st)
|
||||
}
|
||||
|
||||
screenX = v.x
|
||||
@@ -935,7 +1052,7 @@ func (v *View) DisplayView() {
|
||||
|
||||
if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
|
||||
!v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
|
||||
for i := lastX; i < xOffset+v.Width; i++ {
|
||||
for i := lastX; i < xOffset+v.Width-v.lineNumOffset; i++ {
|
||||
style := GetColor("cursor-line")
|
||||
fg, _, _ := style.Decompose()
|
||||
style = style.Background(fg)
|
||||
@@ -980,6 +1097,11 @@ func (v *View) Display() {
|
||||
screen.HideCursor()
|
||||
}
|
||||
_, screenH := screen.Size()
|
||||
|
||||
if v.Buf.Settings["scrollbar"].(bool) {
|
||||
v.scrollbar.Display()
|
||||
}
|
||||
|
||||
if v.Buf.Settings["statusline"].(bool) {
|
||||
v.sline.Display()
|
||||
} else if (v.y + v.Height) != screenH-1 {
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component>
|
||||
<id>com.github.zyedidia.micro</id>
|
||||
<name>Micro Text Editor</name>
|
||||
<summary>A modern and intuitive terminal-based text editor</summary>
|
||||
<url type="homepage">https://micro-editor.github.io</url>
|
||||
<url type="bugtracker">https://github.com/zyedidia/micro</url>
|
||||
<metadata_license>MIT</metadata_license>
|
||||
<categories>
|
||||
<category>Development</category>
|
||||
<category>TextEditor</category>
|
||||
</categories>
|
||||
|
||||
<provides>
|
||||
<binary>micro</binary>
|
||||
</provides>
|
||||
<releases>
|
||||
<release version="1.2.0" date="2017-05-28" />
|
||||
</releases>
|
||||
<developer_name>Zachary Yedidia</developer_name>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>Micro Text Editor editing its source code.</caption>
|
||||
<image type="source">https://raw.githubusercontent.com/zyedidia/micro/master/assets/micro-solarized.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<id>com.github.zyedidia.micro</id>
|
||||
<name>Micro Text Editor</name>
|
||||
<summary>A modern and intuitive terminal-based text editor</summary>
|
||||
<metadata_license>MIT</metadata_license>
|
||||
<categories>
|
||||
<category>Development</category>
|
||||
<category>TextEditor</category>
|
||||
</categories>
|
||||
<provides>
|
||||
<binary>micro</binary>
|
||||
</provides>
|
||||
<developer_name>Zachary Yedidia</developer_name>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>Micro Text Editor editing its source code.</caption>
|
||||
<image type="source">https://raw.githubusercontent.com/zyedidia/micro/master/assets/micro-solarized.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<url type="homepage">https://micro-editor.github.io</url>
|
||||
<url type="bugtracker">https://github.com/zyedidia/micro/issues</url>
|
||||
</component>
|
||||
|
||||
@@ -19,8 +19,8 @@ color-link line-number "#666666,#242424"
|
||||
color-link current-line-number "#666666,#242424"
|
||||
color-link gutter-error "#CB4B16,#242424"
|
||||
color-link gutter-warning "#E6DB74,#242424"
|
||||
color-link cursor-line "#2C2C2C"
|
||||
color-link color-column "#2C2C2C"
|
||||
color-link cursor-line "default,#2C2C2C"
|
||||
color-link color-column "default,#2C2C2C"
|
||||
#No extended types; Plain brackets.
|
||||
color-link type.extended "default"
|
||||
#color-link symbol.brackets "default"
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
#Flamepoint theme
|
||||
#By CaptainMcClellan
|
||||
color-link default ""
|
||||
color-link comment ""
|
||||
color-link constant ""
|
||||
color-link constant.bool ""
|
||||
color-link constant.bool.true ""
|
||||
color-link constant.bool.false ""
|
||||
color-link constant.number ""
|
||||
color-link constant.specialChar ""
|
||||
color-link constant.string ""
|
||||
color-link constant.string.url "underline"
|
||||
color-link identifier ""
|
||||
color-link identifier.var ""
|
||||
color-link preproc ""
|
||||
color-link special ""
|
||||
color-link statement ""
|
||||
color-link symbol ""
|
||||
color-link symbol.brackets ""
|
||||
color-link symbol.tag ""
|
||||
color-link type ""
|
||||
color-link type.keyword ""
|
||||
color-link error ""
|
||||
color-link todo ""
|
||||
color-link cursor-line ""
|
||||
color-link statusline ""
|
||||
color-link tabbar ""
|
||||
color-link color-column ""
|
||||
color-link gutter-error ""
|
||||
color-link gutter-warning ""
|
||||
@@ -1 +0,0 @@
|
||||
#Funky Cactus theme in true colour.
|
||||
@@ -1,30 +0,0 @@
|
||||
#NES
|
||||
#A color theme only using NES pallette colours
|
||||
color-link default ""
|
||||
color-link comment ""
|
||||
color-link constant ""
|
||||
color-link constant.bool ""
|
||||
color-link constant.bool.true ""
|
||||
color-link constant.bool.false ""
|
||||
color-link constant.number ""
|
||||
color-link constant.specialChar ""
|
||||
color-link constant.string ""
|
||||
color-link constant.string.url "underline"
|
||||
color-link identifier ""
|
||||
color-link identifier.var ""
|
||||
color-link preproc ""
|
||||
color-link special ""
|
||||
color-link statement ""
|
||||
color-link symbol ""
|
||||
color-link symbol.brackets ""
|
||||
color-link symbol.tag ""
|
||||
color-link type ""
|
||||
color-link type.keyword ""
|
||||
color-link error ""
|
||||
color-link todo ""
|
||||
color-link cursor-line ""
|
||||
color-link statusline ""
|
||||
color-link tabbar ""
|
||||
color-link color-column ""
|
||||
color-link gutter-error ""
|
||||
color-link gutter-warning ""
|
||||
26
runtime/colorschemes/railscast.micro
Normal file
26
runtime/colorschemes/railscast.micro
Normal file
@@ -0,0 +1,26 @@
|
||||
color-link default "#e6e1dc,#2b2b2b"
|
||||
color-link comment "#bc9458,#2b2b2b"
|
||||
color-link statement "#cc7833,#2b2b2b"
|
||||
color-link constant "#a5c261,#2b2b2b"
|
||||
color-link constant.bool "#6d9cbe,#2b2b2b"
|
||||
color-link type "#6d9cbe,#2b2b2b"
|
||||
color-link preproc "#cc7833,#2b2b2b"
|
||||
color-link special "#cc7833,#2b2b2b"
|
||||
color-link underlined "#cc7833,#2b2b2b"
|
||||
color-link todo "bold #cc7833,#2b2b2b"
|
||||
color-link error "bold #cc7833,#2b2b2b"
|
||||
color-link gutter-error "#cc7833,#11151C"
|
||||
color-link indent-char "#414141,#2b2b2b"
|
||||
color-link line-number "#a1a1a1,#353535"
|
||||
color-link current-line-number "#e6e1dc,#2b2b2b"
|
||||
color-link gutter-warning "#a5c261,#11151C"
|
||||
color-link symbol "#edb753,#2b2b2b"
|
||||
color-link identifier "#edb753,#2b2b2b"
|
||||
color-link statusline "#a1a1a1,#414141"
|
||||
color-link tabbar "bold #a1a1a1,#414141"
|
||||
color-link cursor-line "#353535"
|
||||
color-link color-column "#353535"
|
||||
color-link space "underline #e6e1dc,#2b2b2b"
|
||||
|
||||
#the Python syntax definition are wrong. This is not how you should do decorators!
|
||||
color-link brightgreen "#edb753,#2b2b2b"
|
||||
32
runtime/colorschemes/twilight.micro
Normal file
32
runtime/colorschemes/twilight.micro
Normal file
@@ -0,0 +1,32 @@
|
||||
# Twilight color scheme
|
||||
color-link default "#F8F8F8,#141414"
|
||||
color-link color-column "#1B1B1B"
|
||||
color-link comment "#5F5A60"
|
||||
color-link constant "#CF6A4C"
|
||||
#color-link constant.number "#CF6A4C"
|
||||
color-link constant.specialChar "#DDF2A4"
|
||||
color-link constant.string "#8F9D6A"
|
||||
color-link current-line-number "#868686,#1B1B1B"
|
||||
color-link cursor-line "#1B1B1B"
|
||||
color-link divider "#1E1E1E"
|
||||
color-link error "#D2A8A1"
|
||||
color-link gutter-error "#9B859D"
|
||||
color-link gutter-warning "#9B859D"
|
||||
color-link identifier "#9B703F"
|
||||
color-link identifier.class "#DAD085"
|
||||
color-link identifier.var "#7587A6"
|
||||
color-link indent-char "#515151"
|
||||
color-link line-number "#868686"
|
||||
color-link preproc "#E0C589"
|
||||
color-link special "#E0C589"
|
||||
color-link statement "#CDA869"
|
||||
color-link statusline "#515151,#1E1E1E"
|
||||
color-link symbol "#AC885B"
|
||||
color-link symbol.brackets "#F8F8F8"
|
||||
color-link symbol.operator "#CDA869"
|
||||
color-link symbol.tag "#AC885B"
|
||||
color-link tabbar "#F2F0EC,#2D2D2D"
|
||||
color-link todo "#8B98AB"
|
||||
color-link type "#F9EE98"
|
||||
color-link type.keyword "#CDA869"
|
||||
color-link underlined "#8996A8"
|
||||
@@ -5,79 +5,78 @@ This help page aims to cover two aspects of micro's syntax highlighting engine:
|
||||
- How to create colorschemes and use them
|
||||
- How to create syntax files to add to the list of languages micro can highlight
|
||||
|
||||
|
||||
## Colorschemes
|
||||
|
||||
To change your colorscheme, press Ctrl-E in micro to bring up the command
|
||||
prompt, and type:
|
||||
```
|
||||
set colorscheme solarized
|
||||
```
|
||||
(or whichever colorscheme you choose).
|
||||
|
||||
Micro comes with a number of colorschemes by default. Here is the list:
|
||||
|
||||
* simple: this is the simplest colorscheme. It uses 16 colors which are
|
||||
set by your terminal
|
||||
### 256 color
|
||||
|
||||
* mc: A 16-color theme based on the look and feel of GNU Midnight Commander.
|
||||
This will look great used in conjunction with Midnight Commander.
|
||||
|
||||
* nano: A 16-color theme loosely based on GNU nano's syntax highlighting.
|
||||
|
||||
* monokai: this is the monokai colorscheme; you may recognize it as
|
||||
Sublime Text's default colorscheme. It requires true color to
|
||||
look perfect, but the 256 color approximation looks very good as well.
|
||||
It's also the default colorscheme.
|
||||
These should work and look nice in most terminals. I recommend these
|
||||
themes the most.
|
||||
|
||||
* zenburn: The 'zenburn' colorscheme and works well with 256 color terminals
|
||||
* `monokai`: this is the monokai colorscheme; you may recognize it as Sublime
|
||||
Text's default colorscheme. It requires true color to look perfect, but the
|
||||
256 color approximation looks very good as well. It's also the default
|
||||
colorscheme.
|
||||
* `zenburn`
|
||||
* `gruvbox`
|
||||
* `darcula`
|
||||
* `twilight`
|
||||
* `railscast`
|
||||
* `bubblegum`: a light colorscheme
|
||||
|
||||
* solarized: this is the solarized colorscheme.
|
||||
You should have the solarized color palette in your terminal to use it.
|
||||
### 16 color
|
||||
|
||||
* solarized-tc: this is the solarized colorscheme for true color; just
|
||||
make sure your terminal supports true color before using it and that the
|
||||
MICRO_TRUECOLOR environment variable is set to 1 before starting micro.
|
||||
These may vary widely based on the 16 colors selected for your terminal.
|
||||
|
||||
* atom-dark-tc: this colorscheme is based off of Atom's "dark" colorscheme.
|
||||
It requires true color to look good.
|
||||
* `simple`: this is the simplest colorscheme. It uses 16 colors which are set by
|
||||
your terminal
|
||||
* `solarized`: You should have the solarized color palette in your terminal to use this colorscheme properly.
|
||||
* `cmc-16`
|
||||
* `cmc-paper`: cmc-16, but on a white background. (Actually light grey
|
||||
on most ANSI (16-color) terminals)
|
||||
* `geany`: Colorscheme based on geany's default highlighting.
|
||||
|
||||
* cmc-16: A very nice 16-color theme. Written by contributor CaptainMcClellan
|
||||
(Collin Warren.) Licensed under the same license as the rest of the themes.
|
||||
### True color
|
||||
|
||||
* cmc-paper: Basically cmc-16, but on a white background. ( Actually light grey on most
|
||||
ANSI (16-color) terminals.)
|
||||
These require terminals that support true color and require `MICRO_TRUECOLOR=1` (this is an environment variable).
|
||||
|
||||
* cmc-tc: A true colour variant of the cmc theme.
|
||||
It requires true color to look its best. Use cmc-16 if your terminal doesn't support true color.
|
||||
* `solarized-tc`: this is the solarized colorscheme for true color.
|
||||
* `atom-dark-tc`: this colorscheme is based off of Atom's "dark" colorscheme.
|
||||
* `cmc-tc`: A true colour variant of the cmc theme. It requires true color to
|
||||
look its best. Use cmc-16 if your terminal doesn't support true color.
|
||||
* `gruvbox-tc`: The true color version of the gruvbox colorscheme
|
||||
* `github-tc`: The true color version of the Github colorscheme
|
||||
|
||||
* codeblocks: A colorscheme based on the Code::Blocks IDE's default syntax highlighting.
|
||||
### Monochrome
|
||||
|
||||
* codeblocks-paper: Same as codeblocks, but on a white background. ( Actually light grey. )
|
||||
You can also use `monochrome` if you'd prefer to have just the terminal's default
|
||||
foreground and background colors. Note: This provides no syntax highlighting!
|
||||
|
||||
* github-tc: A colorscheme based on Github's syntax highlighting. Requires true color to look its best.
|
||||
|
||||
* paper-tc: A nice minimalist theme with a light background, good for editing documents on.
|
||||
Requires true color to look its best. Not to be confused with `-paper` suffixed themes.
|
||||
|
||||
* geany: Colorscheme based on geany's default highlighting.
|
||||
|
||||
* geany-alt-tc: Based on an alternate theme bundled with geany.
|
||||
|
||||
* flamepoint-tc: A fire inspired, high intensity true color theme written by CaptainMcClellan.
|
||||
As with all the other `-tc` suffixed themes, it looks its best on a
|
||||
|
||||
To enable one of these colorschemes just press CtrlE in micro and type `set colorscheme solarized`.
|
||||
(or whichever one you choose). You can also use `set colorscheme monochrome` if you'd prefer
|
||||
to have just the terminal's default foreground and background colors.
|
||||
Note: This provides no syntax highlighting!
|
||||
### Other
|
||||
|
||||
See `help gimmickcolors` for a list of some true colour themes that are more
|
||||
just for fun than for serious use. ( Though feel free if you want! )
|
||||
just for fun than for serious use. (Though feel free if you want!)
|
||||
|
||||
---
|
||||
|
||||
### Creating a Colorscheme
|
||||
## Creating a Colorscheme
|
||||
|
||||
Micro's colorschemes are also extremely simple to create. The default ones can be found
|
||||
Micro's colorschemes are also extremely simple to create. The default ones can
|
||||
be found
|
||||
[here](https://github.com/zyedidia/micro/tree/master/runtime/colorschemes).
|
||||
|
||||
They are only about 18-30 lines in total.
|
||||
|
||||
Basically to create the colorscheme you need to link highlight groups with actual colors.
|
||||
This is done using the `color-link` command.
|
||||
Basically to create the colorscheme you need to link highlight groups with
|
||||
actual colors. This is done using the `color-link` command.
|
||||
|
||||
For example, to highlight all comments in green, you would use the command:
|
||||
|
||||
@@ -109,20 +108,22 @@ color-link comment "bold red"
|
||||
|
||||
There are three different ways to specify the color.
|
||||
|
||||
Color terminals usually have 16 colors that are preset by the user. This means that
|
||||
you cannot depend on those colors always being the same. You can use those colors with
|
||||
the names `black, red, green, yellow, blue, magenta, cyan, white` and the bright variants
|
||||
of each one (brightblack, brightred...).
|
||||
Color terminals usually have 16 colors that are preset by the user. This means
|
||||
that you cannot depend on those colors always being the same. You can use those
|
||||
colors with the names `black, red, green, yellow, blue, magenta, cyan, white`
|
||||
and the bright variants of each one (brightblack, brightred...).
|
||||
|
||||
Then you can use the terminals 256 colors by using their numbers 1-256 (numbers 1-16 will
|
||||
refer to the named colors).
|
||||
Then you can use the terminals 256 colors by using their numbers 1-256 (numbers
|
||||
1-16 will refer to the named colors).
|
||||
|
||||
If the user's terminal supports true color, then you can also specify colors exactly using
|
||||
their hex codes. If the terminal is not true color but micro is told to use a true color colorscheme
|
||||
it will attempt to map the colors to the available 256 colors.
|
||||
If the user's terminal supports true color, then you can also specify colors
|
||||
exactly using their hex codes. If the terminal is not true color but micro is
|
||||
told to use a true color colorscheme it will attempt to map the colors to the
|
||||
available 256 colors.
|
||||
|
||||
Generally colorschemes which require true color terminals to look good are marked with a `-tc` suffix
|
||||
and colorschemes which supply a white background are marked with a `-paper` suffix.
|
||||
Generally colorschemes which require true color terminals to look good are
|
||||
marked with a `-tc` suffix and colorschemes which supply a white background are
|
||||
marked with a `-paper` suffix.
|
||||
|
||||
---
|
||||
|
||||
@@ -140,9 +141,10 @@ Here is a list of the colorscheme groups that you can use:
|
||||
* underlined
|
||||
* error
|
||||
* todo
|
||||
* statusline ( Color of the statusline)
|
||||
* tabbar ( Color of the tabbar that lists open files.)
|
||||
* indent-char ( Color of the character which indicates tabs if the option is enabled)
|
||||
* statusline (Color of the statusline)
|
||||
* tabbar (Color of the tabbar that lists open files)
|
||||
* indent-char (Color of the character which indicates tabs if the option is
|
||||
enabled)
|
||||
* line-number
|
||||
* gutter-error
|
||||
* gutter-warning
|
||||
@@ -150,29 +152,30 @@ Here is a list of the colorscheme groups that you can use:
|
||||
* current-line-number
|
||||
* color-column
|
||||
* ignore
|
||||
* divider ( Color of the divider between vertical splits. )
|
||||
* divider (Color of the divider between vertical splits)
|
||||
|
||||
Colorschemes must be placed in the `~/.config/micro/colorschemes` directory to be used.
|
||||
Colorschemes must be placed in the `~/.config/micro/colorschemes` directory to
|
||||
be used.
|
||||
|
||||
---
|
||||
|
||||
In addition to the main colorscheme groups, there are subgroups that you can
|
||||
specify by adding `.subgroup` to the group. If you're creating your own
|
||||
custom syntax files, you can make use of your own subgroups.
|
||||
specify by adding `.subgroup` to the group. If you're creating your own custom
|
||||
syntax files, you can make use of your own subgroups.
|
||||
|
||||
If micro can't match the subgroup, it'll default to the root group, so
|
||||
it's safe and recommended to use subgroups in your custom syntax files.
|
||||
If micro can't match the subgroup, it'll default to the root group, so it's
|
||||
safe and recommended to use subgroups in your custom syntax files.
|
||||
|
||||
For example if `constant.string` is found in your colorscheme, micro will
|
||||
use that for highlighting strings. If it's not found, it will use constant
|
||||
instead. Micro tries to match the largest set of groups it can find in the
|
||||
colorscheme definitions, so if, for examle `constant.bool.true` is found then
|
||||
micro will use that. If `constant.bool.true` is not found but `constant.bool`
|
||||
is found micro will use `constant.bool`. If not, it uses `constant`.
|
||||
For example if `constant.string` is found in your colorscheme, micro will us
|
||||
that for highlighting strings. If it's not found, it will use constant instead.
|
||||
Micro tries to match the largest set of groups it can find in the colorscheme
|
||||
definitions, so if, for examle `constant.bool.true` is found then micro will use
|
||||
that. If `constant.bool.true` is not found but `constant.bool` is found micro
|
||||
will use `constant.bool`. If not, it uses `constant`.
|
||||
|
||||
Here's a list of subgroups used in micro's built-in syntax files.
|
||||
|
||||
* comment.bright ( Some filetypes have distinctions between types of comments.)
|
||||
* comment.bright (Some filetypes have distinctions between types of comments)
|
||||
* constant.bool
|
||||
* constant.bool.true
|
||||
* constant.bool.false
|
||||
@@ -180,29 +183,32 @@ Here's a list of subgroups used in micro's built-in syntax files.
|
||||
* constant.specialChar
|
||||
* constant.string
|
||||
* constant.string.url
|
||||
* identifier.class ( Also used for functions. )
|
||||
* identifier.class (Also used for functions)
|
||||
* identifier.macro
|
||||
* identifier.var
|
||||
* preproc.shebang ( The #! at the beginning of a file that tells the os what script interpreter to use. )
|
||||
* symbol.brackets ( {}()[] and sometimes <> )
|
||||
* symbol.operator ( Color operator symbols differently. )
|
||||
* symbol.tag ( For html tags, among other things.)
|
||||
* type.keyword ( If you want a special highlight for keywords like `private` )
|
||||
* preproc.shebang (The #! at the beginning of a file that tells the os what
|
||||
script interpreter to use)
|
||||
* symbol.brackets (`{}()[]` and sometimes `<>`)
|
||||
* symbol.operator (Color operator symbols differently)
|
||||
* symbol.tag (For html tags, among other things)
|
||||
* type.keyword (If you want a special highlight for keywords like `private`)
|
||||
|
||||
In the future, plugins may also be able to use color groups for styling.
|
||||
|
||||
|
||||
## Syntax files
|
||||
|
||||
The syntax files is written in yaml-format and specify how to highlight languages.
|
||||
The syntax files is written in yaml-format and specify how to highlight
|
||||
languages.
|
||||
|
||||
Micro's builtin syntax highlighting tries very hard to be sane, sensible
|
||||
and provide ample coverage of the meaningful elements of a language. Micro has
|
||||
syntax files built int for over 100 languages now. However, there may be
|
||||
situations where you find Micro's highlighting to be insufficient or not to
|
||||
your liking. Good news is you can create syntax files (.micro extension), place them in
|
||||
`~/.config/micro/syntax` and Micro will use those instead.
|
||||
Micro's builtin syntax highlighting tries very hard to be sane, sensible and
|
||||
provide ample coverage of the meaningful elements of a language. Micro has
|
||||
syntax files built in for over 100 languages now! However, there may be
|
||||
situations where you find Micro's highlighting to be insufficient or not to your
|
||||
liking. The good news is that you can create your own syntax files, and place them
|
||||
in `~/.config/micro/syntax` and Micro will use those instead.
|
||||
|
||||
### Filetype defintion
|
||||
### Filetype definition
|
||||
|
||||
You must start the syntax file by declaring the filetype:
|
||||
|
||||
@@ -219,8 +225,9 @@ detect:
|
||||
filename: "\\.go$"
|
||||
```
|
||||
|
||||
Micro will match this regex against a given filename to detect the filetype. You may also
|
||||
provide an optional `header` regex that will check the first line of the file. For example:
|
||||
Micro will match this regex against a given filename to detect the filetype. You
|
||||
may also provide an optional `header` regex that will check the first line of
|
||||
the file. For example:
|
||||
|
||||
```
|
||||
detect:
|
||||
@@ -230,9 +237,10 @@ detect:
|
||||
|
||||
#### Syntax rules
|
||||
|
||||
Next you must provide the syntax highlighting rules. There are two types of rules: patterns and regions.
|
||||
A pattern is matched on a single line and usually a single word as well. A region highlights between two
|
||||
patterns over multiple lines and may have rules of its own inside the region.
|
||||
Next you must provide the syntax highlighting rules. There are two types of
|
||||
rules: patterns and regions. A pattern is matched on a single line and usually a
|
||||
single word as well. A region highlights between two patterns over multiple
|
||||
lines and may have rules of its own inside the region.
|
||||
|
||||
Here are some example patterns in Go:
|
||||
|
||||
@@ -243,7 +251,8 @@ rules:
|
||||
- preproc: "\\b(package|import|const|var|type|struct|func|go|defer|iota)\\b"
|
||||
```
|
||||
|
||||
The order of patterns does matter as patterns lower in the file will overwrite the ones defined above them.
|
||||
The order of patterns does matter as patterns lower in the file will overwrite
|
||||
the ones defined above them.
|
||||
|
||||
And here are some example regions for Go:
|
||||
|
||||
@@ -269,12 +278,15 @@ And here are some example regions for Go:
|
||||
- todo: "(TODO|XXX|FIXME):?"
|
||||
```
|
||||
|
||||
Notice how the regions may contain rules inside of them. Any inner rules that are matched are then skipped when searching
|
||||
for the end of the region. For example, when highlighting `"foo \" bar"`, since `\"` is matched by an inner rule in the
|
||||
region, it is skipped. Likewise for `"foo \\" bar`, since `\\` is matched by an inner rule, it is skipped, and then the `"`
|
||||
is found and the string ends at the correct place.
|
||||
Notice how the regions may contain rules inside of them. Any inner rules that
|
||||
are matched are then skipped when searching for the end of the region. For
|
||||
example, when highlighting `"foo \" bar"`, since `\"` is matched by an inner
|
||||
rule in the region, it is skipped. Likewise for `"foo \\" bar`, since `\\` is
|
||||
matched by an inner rule, it is skipped, and then the `"` is found and the
|
||||
string ends at the correct place.
|
||||
|
||||
You may also explicitly mark skip regexes if you don't want them to be highlighted. For example:
|
||||
You may also explicitly mark skip regexes if you don't want them to be
|
||||
highlighted. For example:
|
||||
|
||||
```
|
||||
- constant.string:
|
||||
@@ -286,8 +298,8 @@ You may also explicitly mark skip regexes if you don't want them to be highlight
|
||||
|
||||
#### Includes
|
||||
|
||||
You may also include rules from other syntax files as embedded languages. For example, the following is possible
|
||||
for html:
|
||||
You may also include rules from other syntax files as embedded languages. For
|
||||
example, the following is possible for html:
|
||||
|
||||
```
|
||||
- default:
|
||||
|
||||
@@ -5,24 +5,24 @@ Here are the possible commands that you can use.
|
||||
|
||||
* `quit`: Quits micro.
|
||||
|
||||
* `save filename?`: Saves the current buffer. If the filename is provided it will
|
||||
'save as' the filename.
|
||||
* `save filename?`: Saves the current buffer. If the filename is provided it
|
||||
will 'save as' the filename.
|
||||
|
||||
* `replace "search" "value" flags`: This will replace `search` with `value`.
|
||||
The `flags` are optional.
|
||||
At this point, there is only one flag: `-a`, which replaces all occurrences
|
||||
at once.
|
||||
The `flags` are optional. Possible flags are:
|
||||
* `-a`: Replace all occurrences at once
|
||||
* `-l`: Do a literal search instead of a regex search
|
||||
|
||||
Note that `search` must be a valid regex. If one of the arguments
|
||||
does not have any spaces in it, you may omit the quotes.
|
||||
Note that `search` must be a valid regex (unless `-l` is passed). If one
|
||||
of the arguments does not have any spaces in it, you may omit the quotes.
|
||||
|
||||
* `replaceall "search" "value"`: This will replace `search` with `value` without
|
||||
user confirmation.
|
||||
user confirmation.
|
||||
|
||||
See `replace` command for more information.
|
||||
See `replace` command for more information.
|
||||
|
||||
* `set option value`: sets the option to value. See the `options` help topic
|
||||
for a list of options you can set.
|
||||
* `set option value`: sets the option to value. See the `options` help topic for
|
||||
a list of options you can set.
|
||||
|
||||
* `setlocal option value`: sets the option to value locally (only in the current
|
||||
buffer).
|
||||
@@ -30,27 +30,25 @@ Here are the possible commands that you can use.
|
||||
* `show option`: shows the current value of the given option.
|
||||
|
||||
* `eval "expression"`: Evaluates a Lua expression. Note that micro will not
|
||||
print anything so you should use `messenger:Message(...)` to display a
|
||||
value.
|
||||
print anything so you should use `messenger:Message(...)` to display a value.
|
||||
|
||||
* `run sh-command`: runs the given shell command in the background. The
|
||||
command's output will be displayed in one line when it finishes running.
|
||||
|
||||
* `bind key action`: creates a keybinding from key to action. See the sections on
|
||||
keybindings above for more info about what keys and actions are available.
|
||||
* `bind key action`: creates a keybinding from key to action. See the sections
|
||||
on keybindings above for more info about what keys and actions are available.
|
||||
|
||||
* `vsplit filename`: opens a vertical split with `filename`. If no filename is
|
||||
provided, a vertical split is opened with an empty buffer.
|
||||
|
||||
* `hsplit filename`: same as `vsplit` but opens a horizontal split instead of
|
||||
a vertical split.
|
||||
* `hsplit filename`: same as `vsplit` but opens a horizontal split instead of a
|
||||
vertical split.
|
||||
|
||||
* `tab filename`: opens the given file in a new tab.
|
||||
|
||||
* `tabswitch tab`: This command will switch to the specified tab.
|
||||
The `tab` can either be a tab number, or a name of a tab.
|
||||
|
||||
|
||||
* `tabswitch tab`: This command will switch to the specified tab. The `tab` can
|
||||
either be a tab number, or a name of a tab.
|
||||
|
||||
* `log`: opens a log of all messages and debug statements.
|
||||
|
||||
* `plugin install plugin_name`: installs the given plugin.
|
||||
@@ -61,15 +59,15 @@ Here are the possible commands that you can use.
|
||||
|
||||
* `plugin update`: updates all installed plugins.
|
||||
|
||||
* `plugin search plugin_name`: searches for the given plugin.
|
||||
Note that you can find a list of all available plugins at
|
||||
* `plugin search plugin_name`: searches for the given plugin. Note that you can
|
||||
find a list of all available plugins at
|
||||
github.com/micro-editor/plugin-channel.
|
||||
|
||||
You can also see more information about the plugin manager
|
||||
in the `Plugin Manager` section of the `plugins` help topic.
|
||||
You can also see more information about the plugin manager in the
|
||||
`Plugin Manager` section of the `plugins` help topic.
|
||||
|
||||
* `plugin available`: list plugins available for download (this includes
|
||||
any plugins that may be already installed).
|
||||
* `plugin available`: list plugins available for download (this includes any
|
||||
plugins that may be already installed).
|
||||
|
||||
* `reload`: reloads all runtime files.
|
||||
|
||||
@@ -79,8 +77,29 @@ Here are the possible commands that you can use.
|
||||
|
||||
* `open filename`: Open a file in the current buffer.
|
||||
|
||||
* `retab`: Replaces all leading tabs with spaces or leading spaces with tabs
|
||||
depending on the value of `tabstospaces`.
|
||||
|
||||
* `raw`: Micro will open a new tab and show the escape sequence for every event
|
||||
it receives from the terminal. This shows you what micro actually sees from
|
||||
the terminal and helps you see which bindings aren't possible and why. This
|
||||
is most useful for debugging keybindings.
|
||||
|
||||
* `showkey`: Show the action(s) bound to a given key. For example
|
||||
running `> showkey CtrlC` will display `main.(*View).Copy`. Unfortuately
|
||||
showkey does not work well for keys bound to plugin actions. For those
|
||||
it just shows "LuaFunctionBinding."
|
||||
|
||||
---
|
||||
|
||||
The following commands are provided by the default plugins:
|
||||
|
||||
* `lint`: Lint the current file for errors.
|
||||
|
||||
# Command Parsing
|
||||
|
||||
When running a command, you can use extra syntax that micro will expand before
|
||||
running the command. To use an argument with a space in it, simply put it in
|
||||
quotes. You can also use environment variables in the command bar and they
|
||||
will be expanded to their value. Finally, you can put an expression in backticks
|
||||
and it will be evaluated by the shell beforehand.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# Default Keys
|
||||
|
||||
Below are simple charts of the default hotkeys and their functions.
|
||||
For more information about binding custom hotkeys or changing
|
||||
default bindings, please run `> help keybindings`
|
||||
Below are simple charts of the default hotkeys and their functions. For more
|
||||
information about binding custom hotkeys or changing default bindings, please
|
||||
run `> help keybindings`
|
||||
|
||||
Please remember that *all* keys here are rebindable!
|
||||
If you don't like it, you can change it!
|
||||
Please remember that *all* keys here are rebindable! If you don't like it, you
|
||||
can change it!
|
||||
|
||||
# Power user
|
||||
### Power user
|
||||
|
||||
| Key | Description of function |
|
||||
|-------- |-------------------------------------------------------------------------------------------------- |
|
||||
@@ -15,7 +15,7 @@ If you don't like it, you can change it!
|
||||
| Tab | In command prompt, it will autocomplete if possible. |
|
||||
| Ctrl+B | Run a shell command (this will close micro while your command executes). |
|
||||
|
||||
# Navigation
|
||||
### Navigation
|
||||
|
||||
| Key | Description of function |
|
||||
|-------------------------- |------------------------------------------------------------------------------------------ |
|
||||
@@ -25,6 +25,8 @@ If you don't like it, you can change it!
|
||||
| End or CtrlRightArrow | Move to the end of the current line |
|
||||
| AltLeftArrow | Move cursor one word left |
|
||||
| AltRightArrow | Move cursor one word right |
|
||||
| Alt+{ | Move cursor to previous empty line, or beginning of document |
|
||||
| Alt+} | Move cursor to next empty line, or end of document |
|
||||
| PageUp | Move cursor up one page |
|
||||
| PageDown | Move cursor down one page |
|
||||
| CtrlHome or CtrlUpArrow | Move cursor to start of document |
|
||||
@@ -32,7 +34,7 @@ If you don't like it, you can change it!
|
||||
| Ctrl+L | Jump to a line in the file (prompts with #) |
|
||||
| Ctrl+W | Cycle between splits in the current tab (use `> vsplit` or `> hsplit` to create a split) |
|
||||
|
||||
# Tabs
|
||||
### Tabs
|
||||
|
||||
| Key | Description of function |
|
||||
|-------- |------------------------- |
|
||||
@@ -40,7 +42,7 @@ If you don't like it, you can change it!
|
||||
| Alt+, | Previous tab |
|
||||
| Alt+. | Next tab |
|
||||
|
||||
# Find Operations
|
||||
### Find Operations
|
||||
|
||||
| Key | Description of function |
|
||||
|-------- |------------------------------------------ |
|
||||
@@ -48,7 +50,7 @@ If you don't like it, you can change it!
|
||||
| Ctrl+N | Find next instance of current search |
|
||||
| Ctrl+P | Find previous instance of current search |
|
||||
|
||||
# File Operations
|
||||
### File Operations
|
||||
|
||||
| Key | Description of function |
|
||||
|-------- |---------------------------------------------------------------- |
|
||||
@@ -56,7 +58,7 @@ If you don't like it, you can change it!
|
||||
| Ctrl+O | Open a file (prompts for filename) |
|
||||
| Ctrl+S | Save current file |
|
||||
|
||||
# Text operations
|
||||
### Text operations
|
||||
|
||||
| Key | Description of function |
|
||||
|--------------------------------- |------------------------------------------ |
|
||||
@@ -78,14 +80,14 @@ If you don't like it, you can change it!
|
||||
| AltBackspace or AltCtrl+H | Delete word left |
|
||||
| Ctrl+A | Select all |
|
||||
|
||||
# Macros
|
||||
### Macros
|
||||
|
||||
| Key | Description of function |
|
||||
|-------- |---------------------------------------------------------------------------------- |
|
||||
| Ctrl+U | Toggle macro recording (press Ctrl+U to start recording and press again to stop) |
|
||||
| Ctrl+J | Run latest recorded macro |
|
||||
|
||||
# Multiple cursors
|
||||
### Multiple cursors
|
||||
|
||||
| Key | Description of function |
|
||||
|---------------- |---------------------------------------------------------------------------------------------- |
|
||||
@@ -93,9 +95,10 @@ If you don't like it, you can change it!
|
||||
| Alt+P | Remove latest multiple cursor |
|
||||
| Alt+C | Remove all multiple cursors (cancel) |
|
||||
| Alt+X | Skip multiple cursor selection |
|
||||
| Alt+M | Spawn a new cursor at the beginning of every line in the current selection |
|
||||
| Ctrl-MouseLeft | Place a multiple cursor at any location |
|
||||
|
||||
# Other
|
||||
### Other
|
||||
|
||||
| Key | Description of function |
|
||||
|-------- |----------------------------------------------------------------------------------- |
|
||||
@@ -103,7 +106,7 @@ If you don't like it, you can change it!
|
||||
| Ctrl+H | Backspace (old terminals do not support the backspace key and use Ctrl+H instead) |
|
||||
| Ctrl+R | Toggle the line number ruler |
|
||||
|
||||
# Emacs style actions
|
||||
### Emacs style actions
|
||||
|
||||
| Key | Description of function |
|
||||
|------- |------------------------- |
|
||||
@@ -112,7 +115,7 @@ If you don't like it, you can change it!
|
||||
| Alt+A | Move to start of line |
|
||||
| Alt+E | Move to end of line |
|
||||
|
||||
# Function keys.
|
||||
### Function keys.
|
||||
|
||||
Warning! The function keys may not work in all terminals!
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ We have included a few colorschemes that are for fun:
|
||||
Nintendo Entertainment System color palette.
|
||||
* symbian-tc: Colorscheme based on SymbOS's GUI.
|
||||
* matrix: Pretend it's 1981 with a colorscheme based on a monochrome
|
||||
IBM 5151. ( Does not include the ghosting and trailing. )
|
||||
IBM 5151. (Does not include the ghosting and trailing)
|
||||
|
||||
Check the plugin repo periodically for gimmick-color extension packs
|
||||
and genuine additional themes.
|
||||
Check the plugin repo periodically for gimmick-color extension packs and genuine
|
||||
additional themes.
|
||||
@@ -1,51 +1,62 @@
|
||||
# Micro help text
|
||||
|
||||
Thank you for downloading and using 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.
|
||||
|
||||
If you want to see all the keybindings press CtrlE and type `help keybindings`.
|
||||
|
||||
|
||||
For a list of the default keybindings press CtrlE and type `help defaultkeys`.
|
||||
For more information on keybindings see `> help keybindings`.
|
||||
|
||||
See the next section for more information about documentation and help.
|
||||
|
||||
### Quick-start
|
||||
## Quick-start
|
||||
|
||||
Press CtrlQ to quit, and CtrlS to save. Press CtrlE to start typing commands
|
||||
and you can see which commands are available by pressing tab, or by
|
||||
viewing the help topic `> help commands`. When I write `> ...` I mean press
|
||||
CtrlE and then type whatever is there.
|
||||
Press CtrlQ to quit, and CtrlS to save. Press CtrlE to start typing commands and
|
||||
you can see which commands are available by pressing tab, or by viewing the help
|
||||
topic `> help commands`. When I write `> ...` I mean press CtrlE and then type
|
||||
whatever is there.
|
||||
|
||||
Move the cursor around with the mouse or the arrow keys. Type `> help defaultkeys` to
|
||||
get a quick, easy overview of the default hotkeys and what they do. For more info
|
||||
on rebinding keys, see type `> help keybindings`
|
||||
Move the cursor around with the mouse or the arrow keys. Type
|
||||
`> help defaultkeys` to get a quick, easy overview of the default hotkeys and
|
||||
what they do. For more info on rebinding keys, see type `> help keybindings`.
|
||||
|
||||
If the colorscheme doesn't look good, you can change it with `> set colorscheme ...`.
|
||||
You can press tab to see the available colorschemes, or see more information with
|
||||
`> help colors`.
|
||||
If the colorscheme doesn't look good, you can change it with
|
||||
`> set colorscheme ...`. You can press tab to see the available colorschemes, or
|
||||
see more information with `> help colors`.
|
||||
|
||||
Press CtrlW to move between splits, and type `> vsplit filename` or `> hsplit filename`
|
||||
to open a new split.
|
||||
Press CtrlW to move between splits, and type `> vsplit filename` or
|
||||
`> hsplit filename` to open a new split.
|
||||
|
||||
### Accessing more help
|
||||
|
||||
## Accessing more help
|
||||
|
||||
Micro has a built-in help system much like Vim's (although less extensive).
|
||||
|
||||
To use it, press CtrlE to access command mode and type in `help` followed by a topic.
|
||||
Typing `help` followed by nothing will open this page.
|
||||
To use it, press CtrlE to access command mode and type in `help` followed by a
|
||||
topic. Typing `help` followed by nothing will open this page.
|
||||
|
||||
Here are the possible help topics that you can read:
|
||||
|
||||
* tutorial: A brief tutorial which gives an overview of all the other help topics
|
||||
* keybindings: Gives a full list of the default keybindings as well as how to rebind them
|
||||
* defaultkeys: Gives a more straight-forward list of the hotkey commands and what they do.
|
||||
* tutorial: A brief tutorial which gives an overview of all the other help
|
||||
topics
|
||||
* keybindings: Gives a full list of the default keybindings as well as how to
|
||||
rebind them
|
||||
* defaultkeys: Gives a more straight-forward list of the hotkey commands and what
|
||||
they do.
|
||||
* commands: Gives a list of all the commands and what they do
|
||||
* options: Gives a list of all the options you can customize
|
||||
* plugins: Explains how micro's plugin system works and how to create your own plugins
|
||||
* colors: Explains micro's colorscheme and syntax highlighting engine and how to create your
|
||||
own colorschemes or add new languages to the engine
|
||||
* plugins: Explains how micro's plugin system works and how to create your own
|
||||
plugins
|
||||
* colors: Explains micro's colorscheme and syntax highlighting engine and how to
|
||||
create your own colorschemes or add new languages to the engine
|
||||
|
||||
For example, to open the help page on plugins you would press CtrlE and type `help plugins`.
|
||||
For example, to open the help page on plugins you would press CtrlE and type
|
||||
`help plugins`.
|
||||
|
||||
I recommend looking at the `tutorial` help file because it is short for each section and
|
||||
gives concrete examples of how to use the various configuration options in micro. However,
|
||||
it does not give the in-depth documentation that the other topics provide.
|
||||
I recommend looking at the `tutorial` help file because it is short for each
|
||||
section and gives concrete examples of how to use the various configuration
|
||||
options in micro. However, it does not give the in-depth documentation that the
|
||||
other topics provide.
|
||||
|
||||
@@ -2,25 +2,28 @@
|
||||
|
||||
Micro has a plethora of hotkeys that make it easy and powerful to use and all
|
||||
hotkeys are fully customizable to your liking.
|
||||
Custom keybindings are stored internally in micro if changed with the `>bind` command or
|
||||
you can also be added in the file `~/.config/micro/bindings.json` as discussed below.
|
||||
For a list of the default keybindings in the json format used by micro, please see
|
||||
the end of this file. For a more user-friendly list with explanations of what the default
|
||||
hotkeys are and what they do, please see `>help defaultkeys`
|
||||
|
||||
Custom keybindings are stored internally in micro if changed with the `> bind`
|
||||
command or you can also be added in the file `~/.config/micro/bindings.json` as
|
||||
discussed below. For a list of the default keybindings in the json format used
|
||||
by micro, please see the end of this file. For a more user-friendly list with
|
||||
explanations of what the default hotkeys are and what they do, please see
|
||||
`>help defaultkeys`
|
||||
|
||||
If `~/.config/micro/bindings.json` does not exist, you can simply create it.
|
||||
Micro will know what to do with it.
|
||||
|
||||
You can use the alt keys + arrows to move word by word.
|
||||
Ctrl left and right move the cursor to the start and end of the line, and
|
||||
ctrl up and down move the cursor the start and end of the buffer.
|
||||
You can use the alt keys + arrows to move word by word. Ctrl left and right move
|
||||
the cursor to the start and end of the line, and ctrl up and down move the
|
||||
cursor the start and end of the buffer.
|
||||
|
||||
You can hold shift with all of these movement actions to select while moving.
|
||||
|
||||
# Rebinding keys
|
||||
|
||||
The bindings may be rebound using the `~/.config/micro/bindings.json`
|
||||
file. Each key is bound to an action.
|
||||
## Rebinding keys
|
||||
|
||||
The bindings may be rebound using the `~/.config/micro/bindings.json` file. Each
|
||||
key is bound to an action.
|
||||
|
||||
For example, to bind `Ctrl-y` to undo and `Ctrl-z` to redo, you could put the
|
||||
following in the `bindings.json` file.
|
||||
@@ -35,8 +38,8 @@ following in the `bindings.json` file.
|
||||
In addition to editing your `~/.config/micro/bindings.json`, you can run
|
||||
`>bind <keycombo> <action>` For a list of bindable actions, see below.
|
||||
|
||||
You can also chain commands when rebinding. For example, if you want Alt-s to save
|
||||
and quit you can bind it like so:
|
||||
You can also chain commands when rebinding. For example, if you want Alt-s to
|
||||
save and quit you can bind it like so:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -44,12 +47,86 @@ and quit you can bind it like so:
|
||||
}
|
||||
```
|
||||
|
||||
# Unbinding keys
|
||||
## Binding commands
|
||||
|
||||
You can also bind a key to execute a command in command mode (see
|
||||
`help commands`). Simply prepend the binding with `command:`. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"Alt-p": "command:pwd"
|
||||
}
|
||||
```
|
||||
|
||||
Now when you press `Alt-p` the `pwd` command will be executed which will show
|
||||
your working directory in the infobar.
|
||||
|
||||
You can also bind an "editable" command with `command-edit:`. This means that
|
||||
micro won't immediately execute the command when you press the binding, but
|
||||
instead just place the string in the infobar in command mode. For example,
|
||||
you could rebind `CtrlG` to `> help`:
|
||||
|
||||
```json
|
||||
{
|
||||
"CtrlG": "command-edit:help "
|
||||
}
|
||||
```
|
||||
|
||||
Now when you press `CtrlG`, `help` will appear in the command bar and your cursor will
|
||||
be placed after it (note the space in the json that controls the cursor placement).
|
||||
|
||||
## Binding raw escape sequences
|
||||
|
||||
Only read this section if you are interested in binding keys that aren't on the
|
||||
list of supported keys for binding.
|
||||
|
||||
One of the drawbacks of using a terminal-based editor is that the editor must
|
||||
get all of its information about key events through the terminal. The terminal
|
||||
sends these events in the form of escape sequences often (but not always)
|
||||
starting with `0x1b`.
|
||||
|
||||
For example, if micro reads `\x1b[1;5D`, on most terminals this will mean the
|
||||
user pressed CtrlLeft.
|
||||
|
||||
For many key chords though, the terminal won't send any escape code or will send
|
||||
an escape code already in use. For example for `CtrlBackspace`, my terminal
|
||||
sends `\u007f` (note this doesn't start with `0x1b`), which it also sends for
|
||||
`Backspace` meaning micro can't bind `CtrlBackspace`.
|
||||
|
||||
However, some terminals do allow you to bind keys to send specific escape
|
||||
sequences you define. Then from micro you can directly bind those escape
|
||||
sequences to actions. For example, to bind `CtrlBackspace` you can instruct your
|
||||
terminal to send `\x1bctrlback` and then bind it in `bindings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"\u001bctrlback": "DeleteWordLeft"
|
||||
}
|
||||
```
|
||||
|
||||
Here are some instructions for sending raw escapes in different terminals
|
||||
|
||||
### iTerm2
|
||||
|
||||
In iTerm2, you can do this in `Preferences->Profiles->Keys` then click the `+`,
|
||||
input your keybinding, and for the `Action` select `Send Escape Sequence`. For
|
||||
the above example your would type `ctrlback` into the box (the `\x1b`) is
|
||||
automatically sent by iTerm2.
|
||||
|
||||
### Linux using loadkeys
|
||||
|
||||
You can do this in linux using the loadkeys program.
|
||||
|
||||
Coming soon!
|
||||
|
||||
|
||||
## Unbinding keys
|
||||
|
||||
It is also possible to disable any of the default key bindings by use of the
|
||||
`UnbindKey` action in the user's `bindings.json` file.
|
||||
|
||||
# Bindable actions and bindable keys
|
||||
|
||||
## Bindable actions and bindable keys
|
||||
|
||||
The list of default keybindings contains most of the possible actions and keys
|
||||
which you can use, but not all of them. Here is a full list of both.
|
||||
@@ -79,6 +156,7 @@ MoveLinesUp
|
||||
MoveLinesDown
|
||||
DeleteWordRight
|
||||
DeleteWordLeft
|
||||
SelectLine
|
||||
SelectToStartOfLine
|
||||
SelectToEndOfLine
|
||||
InsertNewline
|
||||
@@ -113,6 +191,8 @@ HalfPageUp
|
||||
HalfPageDown
|
||||
StartOfLine
|
||||
EndOfLine
|
||||
ParagraphPrevious
|
||||
ParagraphNext
|
||||
ToggleHelp
|
||||
ToggleRuler
|
||||
JumpLine
|
||||
@@ -131,17 +211,20 @@ HSplit
|
||||
PreviousSplit
|
||||
ToggleMacro
|
||||
PlayMacro
|
||||
Suspend (Linux only)
|
||||
Suspend (Unix only)
|
||||
ScrollUp
|
||||
ScrollDown
|
||||
SpawnMultiCursor
|
||||
SpawnMultiCursorSelect
|
||||
RemoveMultiCursor
|
||||
RemoveAllMultiCursors
|
||||
SkipMultiCursor
|
||||
UnbindKey
|
||||
JumpToMatchingBrace
|
||||
```
|
||||
|
||||
You can also bind some mouse actions (these must be bound to mouse buttons)
|
||||
|
||||
```
|
||||
MousePress
|
||||
MouseMultiCursor
|
||||
@@ -275,7 +358,8 @@ Escape
|
||||
Enter
|
||||
```
|
||||
|
||||
You can also bind some mouse buttons (they may be bound to normal actions or mouse actions)
|
||||
You can also bind some mouse buttons (they may be bound to normal actions or
|
||||
mouse actions)
|
||||
|
||||
```
|
||||
MouseLeft
|
||||
@@ -315,6 +399,8 @@ MouseWheelRight
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Alt-{": "ParagraphPrevious",
|
||||
"Alt-}": "ParagraphNext",
|
||||
"Enter": "InsertNewline",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
@@ -379,18 +465,20 @@ MouseWheelRight
|
||||
|
||||
// Multiple cursors bindings
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
}
|
||||
```
|
||||
|
||||
# Final notes
|
||||
Note: On some old terminal emulators and on Windows machines, `CtrlH` should be used
|
||||
for backspace.
|
||||
## Final notes
|
||||
|
||||
Additionally, alt keys can be bound by using `Alt-key`. For example `Alt-a`
|
||||
or `Alt-Up`. Micro supports an optional `-` between modifiers like `Alt` and `Ctrl`
|
||||
so `Alt-a` could be rewritten as `Alta` (case matters for alt bindings). This is
|
||||
why in the default keybindings you can see `AltShiftLeft` instead of `Alt-ShiftLeft`
|
||||
(they are equivalent).
|
||||
Note: On some old terminal emulators and on Windows machines, `CtrlH` should be
|
||||
used for backspace.
|
||||
|
||||
Additionally, alt keys can be bound by using `Alt-key`. For example `Alt-a` or
|
||||
`Alt-Up`. Micro supports an optional `-` between modifiers like `Alt` and
|
||||
`Ctrl` so `Alt-a` could be rewritten as `Alta` (case matters for alt bindings).
|
||||
This is why in the default keybindings you can see `AltShiftLeft` instead of
|
||||
`Alt-ShiftLeft` (they are equivalent).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
### Options
|
||||
# Options
|
||||
|
||||
Micro stores all of the user configuration in its configuration directory.
|
||||
|
||||
@@ -8,11 +8,34 @@ the config directory.
|
||||
|
||||
Here are the options that you can set:
|
||||
|
||||
* `autoindent`: when creating a new line use the same indentation as the
|
||||
previous line.
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `autosave`: micro will save the buffer every 8 seconds automatically. Micro
|
||||
also will automatically save and quit when you exit without asking. Be
|
||||
careful when using this feature, because you might accidentally save a file,
|
||||
overwriting what was there before.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `basename`: in the infobar, show only the basename of the file being edited
|
||||
rather than the full path.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `colorcolumn`: if this is not set to 0, it will display a column at the
|
||||
specified column. This is useful if you want column 80 to be highlighted
|
||||
special for example.
|
||||
|
||||
default value: `0`
|
||||
|
||||
* `colorscheme`: loads the colorscheme stored in
|
||||
$(configDir)/colorschemes/`option`.micro
|
||||
This setting is `global only`.
|
||||
$(configDir)/colorschemes/`option`.micro, This setting is `global only`.
|
||||
|
||||
default value: `default`
|
||||
|
||||
Note that the default colorschemes (default, solarized, and solarized-tc)
|
||||
are not located in configDir, because they are embedded in the micro binary.
|
||||
|
||||
@@ -20,189 +43,255 @@ Here are the options that you can set:
|
||||
~/.config/micro/colorschemes/ directory. Micro comes by default with three
|
||||
colorschemes:
|
||||
|
||||
You can read more about micro's colorschemes in the `colors` help topic
|
||||
(`help colors`).
|
||||
You can read more about micro's colorschemes in the `colors` help topic
|
||||
(`help colors`).
|
||||
|
||||
* `colorcolumn`: if this is not set to 0, it will display a column at the specified
|
||||
column. This is useful if you want column 80 to be highlighted special for example.
|
||||
* `cursorline`: highlight the line that the cursor is on in a different color
|
||||
(the color is defined by the colorscheme you are using).
|
||||
|
||||
default value: `0`
|
||||
default value: `true`
|
||||
|
||||
* `eofnewline`: micro will automatically add a newline to the file.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `fastdirty`: this determines what kind of algorithm micro uses to determine if
|
||||
a buffer is modified or not. When `fastdirty` is on, micro just uses a
|
||||
boolean `modified` that is set to `true` as soon as the user makes an edit.
|
||||
This is fast, but can be inaccurate. If `fastdirty` is off, then micro will
|
||||
hash the current buffer against a hash of the original file (created when the
|
||||
buffer was loaded). This is more accurate but obviously more resource
|
||||
intensive. This option is only for people who really care about having
|
||||
accurate modified status.
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `fileformat`: this determines what kind of line endings micro will use for the
|
||||
file. UNIX line endings are just `\n` (lf) whereas dos line endings are
|
||||
`\r\n` (crlf). The two possible values for this option are `unix` and `dos`.
|
||||
The fileformat will be automatically detected and displayed on the statusline
|
||||
but this option is useful if you would like to change the line endings or if
|
||||
you are starting a new file.
|
||||
|
||||
default value: `unix`
|
||||
|
||||
* `filetype`: sets the filetype for the current buffer. This setting is
|
||||
`local only`.
|
||||
|
||||
default value: this will be automatically set depending on the file you have
|
||||
open
|
||||
|
||||
* `ignorecase`: perform case-insensitive searches.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `indentchar`: sets the indentation character.
|
||||
|
||||
default value: ` `
|
||||
|
||||
* `infobar`: enables the line at the bottom of the editor where messages are
|
||||
printed. This option is `global only`.
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `keepautoindent`: when using autoindent, whitespace is added for you. This
|
||||
option determines if when you move to the next line without any insertions
|
||||
the whitespace that was added should be deleted. By default the autoindent
|
||||
whitespace is deleted if the line was left empty.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `keymenu`: display the nano-style key menu at the bottom of the screen. Note
|
||||
that ToggleKeyMenu is bound to `Alt-g` by default and this is displayed in
|
||||
the statusline. To disable this, simply by `Alt-g` to `UnbindKey`.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `mouse`: whether to enable mouse support. When mouse support is disabled,
|
||||
usually the terminal will be able to access mouse events which can be useful
|
||||
if you want to copy from the terminal instead of from micro (if over ssh for
|
||||
example, because the terminal has access to the local clipboard and micro
|
||||
does not).
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `pluginchannels`: contains all the channels micro's plugin manager will search
|
||||
for plugins in. A channel is simply a list of 'repository' json files which
|
||||
contain metadata about the given plugin. See the `Plugin Manager` section of
|
||||
the `plugins` help topic for more information.
|
||||
|
||||
default value: `https://github.com/micro-editor/plugin-channel`
|
||||
|
||||
* `pluginrepos`: contains all the 'repositories' micro's plugin manager will
|
||||
search for plugins in. A repository consists of a `repo.json` file which
|
||||
contains metadata for a single plugin.
|
||||
|
||||
default value: ` `
|
||||
|
||||
* `rmtrailingws`: micro will automatically trim trailing whitespaces at eol.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `ruler`: display line numbers.
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `savecursor`: remember where the cursor was last time the file was opened and
|
||||
put it there when you open the file again.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `savehistory`: remember command history between closing and re-opening
|
||||
micro.
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `saveundo`: when this option is on, undo is saved even after you close a file
|
||||
so if you close and reopen a file, you can keep undoing.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `scrollbar`: display a scroll bar
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `scrollmargin`: amount of lines you would like to see above and below the
|
||||
cursor.
|
||||
|
||||
default value: `3`
|
||||
|
||||
* `scrollspeed`: amount of lines to scroll for one scroll event.
|
||||
|
||||
default value: `2`
|
||||
|
||||
* `softwrap`: should micro wrap lines that are too long to fit on the screen.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `splitbottom`: when a horizontal split is created, should it be created below
|
||||
the current split?
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `splitright`: when a vertical split is created, should it be created to the
|
||||
right of the current split?
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `statusline`: display the status line at the bottom of the screen.
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `matchbrace`: highlight matching braces for '()', '{}', '[]'
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `matchbraceleft`: when matching a closing brace, should matching match the
|
||||
brace directly under the cursor, or the character to the left? only matters
|
||||
if `matchbrace` is true
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `syntax`: turns syntax on or off.
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `sucmd`: specifies the super user command. On most systems this is "sudo" but
|
||||
on BSD it can be "doas." This option can be customized and is only used when
|
||||
saving with su.
|
||||
|
||||
default value: `sudo`
|
||||
|
||||
* `tabmovement`: navigate spaces at the beginning of lines as if they are tabs
|
||||
(e.g. move over 4 spaces at once). This option only does anything if
|
||||
`tabstospaces` is on.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `tabsize`: sets the tab size to `option`
|
||||
|
||||
default value: `4`
|
||||
|
||||
* `indentchar`: sets the indentation character
|
||||
|
||||
default value: ` `
|
||||
|
||||
* `infobar`: enables the line at the bottom of the editor where messages are printed.
|
||||
This option is `global only`.
|
||||
|
||||
default value: `on`
|
||||
|
||||
* `filetype`: sets the filetype for the current buffer. This setting is `local only`
|
||||
|
||||
default value: this will be automatically set depending on the file you have open
|
||||
|
||||
* `ignorecase`: perform case-insensitive searches
|
||||
|
||||
default value: `off`
|
||||
|
||||
* `syntax`: turns syntax on or off
|
||||
|
||||
default value: `on`
|
||||
|
||||
* `tabstospaces`: use spaces instead of tabs
|
||||
|
||||
default value: `off`
|
||||
default value: `false`
|
||||
|
||||
* `tabmovement`: navigate spaces at the beginning of lines as if they are tabs (e.g. move over 4 spaces at once).
|
||||
This option only does anything if `tabstospaces` is on.
|
||||
* `termtitle`: defines whether or not your terminal's title will be set by micro
|
||||
when opened.
|
||||
|
||||
default value: `off`
|
||||
default value: `false`
|
||||
|
||||
* `autoindent`: when creating a new line use the same indentation as the
|
||||
previous line
|
||||
* `useprimary` (only useful on *nix): defines whether or not micro will use the
|
||||
primary clipboard to copy selections in the background. This does not affect
|
||||
the normal clipboard using Ctrl-C and Ctrl-V.
|
||||
|
||||
default value: `on`
|
||||
|
||||
* `cursorline`: highlight the line that the cursor is on in a different color
|
||||
(the color is defined by the colorscheme you are using)
|
||||
|
||||
default value: `on`
|
||||
|
||||
* `ruler`: display line numbers
|
||||
|
||||
default value: `on`
|
||||
|
||||
* `statusline`: display the status line at the bottom of the screen
|
||||
|
||||
default value: `on`
|
||||
|
||||
* `savecursor`: remember where the cursor was last time the file was opened and
|
||||
put it there when you open the file again
|
||||
|
||||
default value: `off`
|
||||
|
||||
* `saveundo`: when this option is on, undo is saved even after you close a file
|
||||
so if you close and reopen a file, you can keep undoing
|
||||
|
||||
default value: `off`
|
||||
|
||||
* `scrollmargin`: amount of lines you would like to see above and below the cursor
|
||||
|
||||
default value: `3`
|
||||
|
||||
* `scrollspeed`: amount of lines to scroll for one scroll event
|
||||
|
||||
default value: `2`
|
||||
|
||||
* `softwrap`: should micro wrap lines that are too long to fit on the screen
|
||||
|
||||
default value: `off`
|
||||
|
||||
* `splitRight`: when a vertical split is created, should it be created to the right of
|
||||
the current split?
|
||||
|
||||
default value: `on`
|
||||
|
||||
* `splitBottom`: when a horizontal split is created, should it be created below the
|
||||
current split?
|
||||
|
||||
default value: `on`
|
||||
|
||||
* `autosave`: micro will save the buffer every 8 seconds automatically.
|
||||
Micro also will automatically save and quit when you exit without asking.
|
||||
Be careful when using this feature, because you might accidentally save a file,
|
||||
overwriting what was there before.
|
||||
|
||||
default value: `off`
|
||||
|
||||
* `pluginchannels`: contains all the channels micro's plugin manager will search
|
||||
for plugins in. A channel is simply a list of 'repository' json files which contain
|
||||
metadata about the given plugin. See the `Plugin Manager` section of the `plugins` help topic
|
||||
for more information.
|
||||
|
||||
default value: `https://github.com/micro-editor/plugin-channel`
|
||||
|
||||
* `pluginrepos`: contains all the 'repositories' micro's plugin manager will search for
|
||||
plugins in. A repository consists of a `repo.json` file which contains metadata for a
|
||||
single plugin.
|
||||
|
||||
default value: ` `
|
||||
|
||||
* `useprimary` (only useful on Linux): defines whether or not micro will use the primary clipboard to copy selections
|
||||
in the background. This does not affect the normal clipboard using Ctrl-C and Ctrl-V.
|
||||
|
||||
default value: `on`
|
||||
|
||||
* `keepautoindent`: when using autoindent, whitespace is added for you. This option determines if
|
||||
when you move to the next line without any insertions the whitespace that was added should be deleted.
|
||||
By default the autoindent whitespace is deleted if the line was left empty.
|
||||
|
||||
default value: `off`
|
||||
|
||||
* `termtitle`: defines whether or not your terminal's title will be set by micro when opened.
|
||||
|
||||
default value: `off`
|
||||
default value: `true`
|
||||
|
||||
---
|
||||
|
||||
Default plugin options:
|
||||
|
||||
* `autoclose`: Automatically close `{}` `()` `[]` `""` `''`. Provided by the `autoclose` plugin
|
||||
* `autoclose`: automatically close `{}` `()` `[]` `""` `''`. Provided by the
|
||||
`autoclose` plugin
|
||||
|
||||
default value: `on`
|
||||
default value: `true`
|
||||
|
||||
* `linter`: Automatically lint when the file is saved. Provided by the `linter` plugin
|
||||
* `ftoptions`: by default, micro will set some options based on the filetype. At
|
||||
the moment, micro will use tabs for makefiles and spaces for python and yaml
|
||||
files regardless of your settings. If you would like to disable this behavior
|
||||
turn this option off.
|
||||
|
||||
default value: `on`
|
||||
default value: `true`
|
||||
|
||||
* `ftoptions`: by default, micro will set some options based on the filetype. At the moment, micro will
|
||||
use tabs for makefiles and spaces for python files regardless of your settings. If you would like to
|
||||
disable this behavior turn this option off.
|
||||
* `linter`: Automatically lint when the file is saved. Provided by the `linter`
|
||||
plugin.
|
||||
|
||||
default value: `on`
|
||||
default value: `true`
|
||||
|
||||
Any option you set in the editor will be saved to the file
|
||||
~/.config/micro/settings.json so, in effect, your configuration file will be
|
||||
created for you. If you'd like to take your configuration with you to another
|
||||
machine, simply copy the settings.json to the other machine.
|
||||
|
||||
# Global and local settings
|
||||
|
||||
You can set these settings either globally or locally. Locally means that the setting
|
||||
won't be saved to `~/.config/micro/settings.json` and that it will only be set in
|
||||
the current buffer. Setting an option globally is the default, and will set the option
|
||||
in all buffers.
|
||||
## Global and local settings
|
||||
|
||||
The `colorscheme` option is global only, and the `filetype` option is local only. To
|
||||
set an option locally, use `setlocal` instead of `set`.
|
||||
You can set these settings either globally or locally. Locally means that the
|
||||
setting won't be saved to `~/.config/micro/settings.json` and that it will only
|
||||
be set in the current buffer. Setting an option globally is the default, and
|
||||
will set the option in all buffers.
|
||||
|
||||
In the `settings.json` file you can also put set options locally by specifying a glob.
|
||||
Here is an example which has `tabstospaces` on for all files except Go files, and
|
||||
`tabsize` 4 for all files except Ruby files:
|
||||
The `colorscheme` option is global only, and the `filetype` option is local
|
||||
only. To set an option locally, use `setlocal` instead of `set`.
|
||||
|
||||
In the `settings.json` file you can also put set options locally by specifying either
|
||||
a glob or a filetype. Here is an example which has `tabstospaces` on for all files except Go
|
||||
files, and `tabsize` 4 for all files except Ruby files:
|
||||
|
||||
```json
|
||||
{
|
||||
"*.go": {
|
||||
"tabstospaces": false
|
||||
},
|
||||
"*.rb": {
|
||||
"tabsize": 2
|
||||
},
|
||||
"tabstospaces": true,
|
||||
"tabsize": 4
|
||||
"ft:go": {
|
||||
"tabstospaces": false
|
||||
},
|
||||
"ft:ruby": {
|
||||
"tabsize": 2
|
||||
},
|
||||
"tabstospaces": true,
|
||||
"tabsize": 4
|
||||
}
|
||||
```
|
||||
|
||||
As you can see it is quite easy to set options locally using the `settings.json` file.
|
||||
Or similarly you can match with globs:
|
||||
|
||||
```json
|
||||
{
|
||||
"*.go": {
|
||||
"tabstospaces": false
|
||||
},
|
||||
"*.rb": {
|
||||
"tabsize": 2
|
||||
},
|
||||
"tabstospaces": true,
|
||||
"tabsize": 4
|
||||
}
|
||||
```
|
||||
|
||||
@@ -4,10 +4,9 @@ Micro supports creating plugins with a simple Lua system. Every plugin has a
|
||||
main script which is run at startup which should be placed in
|
||||
`~/.config/micro/plugins/pluginName/pluginName.lua`.
|
||||
|
||||
There are a number of callback functions which you can create in your
|
||||
plugin to run code at times other than startup. The naming scheme is
|
||||
`onAction(view)`. For example a function which is run every time the user saves
|
||||
the buffer would be:
|
||||
There are a number of callback functions which you can create in your plugin to
|
||||
run code at times other than startup. The naming scheme is `onAction(view)`. For
|
||||
example a function which is run every time the user saves the buffer would be:
|
||||
|
||||
```lua
|
||||
function onSave(view)
|
||||
@@ -17,7 +16,8 @@ end
|
||||
```
|
||||
|
||||
The `view` variable is a reference to the view the action is being executed on.
|
||||
This is almost always the current view, which you can get with `CurView()` as well.
|
||||
This is almost always the current view, which you can get with `CurView()` as
|
||||
well.
|
||||
|
||||
All available actions are listed in the keybindings section of the help.
|
||||
|
||||
@@ -31,27 +31,28 @@ function onMousePress(view, event)
|
||||
end
|
||||
```
|
||||
|
||||
These functions should also return a boolean specifying whether the view
|
||||
should be relocated to the cursor or not after the action is complete.
|
||||
These functions should also return a boolean specifying whether the view should
|
||||
be relocated to the cursor or not after the action is complete.
|
||||
|
||||
Note that these callbacks occur after the action has been completed. If you
|
||||
want a callback before the action is executed, use `preAction()`. In this case
|
||||
the boolean returned specifies whether or not the action should be executed
|
||||
after the lua code completes.
|
||||
Note that these callbacks occur after the action has been completed. If you want
|
||||
a callback before the action is executed, use `preAction()`. In this case the
|
||||
boolean returned specifies whether or not the action should be executed after
|
||||
the lua code completes.
|
||||
|
||||
Another useful callback to know about which is not a action is
|
||||
Another useful callback to know about which is not an action is
|
||||
`onViewOpen(view)` which is called whenever a new view is opened and the new
|
||||
view is passed in. This is useful for setting local options based on the filetype,
|
||||
for example turning off `tabstospaces` only for Go files when they are opened.
|
||||
view is passed in. This is useful for setting local options based on the
|
||||
filetype, for example turning off `tabstospaces` only for Go files when they are
|
||||
opened.
|
||||
|
||||
---
|
||||
|
||||
There are a number of functions and variables that are available to you in
|
||||
order to access the inner workings of micro. Here is a list (the type signatures
|
||||
for functions are given using Go's type system):
|
||||
There are a number of functions and variables that are available to you in order
|
||||
to access the inner workings of micro. Here is a list (the type signatures for
|
||||
functions are given using Go's type system):
|
||||
|
||||
* `OS`: variable which gives the OS micro is currently running on (this is the same
|
||||
as Go's GOOS variable, so `darwin`, `windows`, `linux`, `freebsd`...)
|
||||
* `OS`: variable which gives the OS micro is currently running on (this is the
|
||||
same as Go's GOOS variable, so `darwin`, `windows`, `linux`, `freebsd`...)
|
||||
|
||||
* `configDir`: contains the path to the micro configuration files
|
||||
|
||||
@@ -61,27 +62,38 @@ as Go's GOOS variable, so `darwin`, `windows`, `linux`, `freebsd`...)
|
||||
|
||||
* `messenger`: lets you send messages to the user or create prompts
|
||||
|
||||
* `NewBuffer(text, path string) *Buffer`: creates a new buffer from a given reader with a given path
|
||||
* `NewBuffer(text, path string) *Buffer`: creates a new buffer from a given
|
||||
reader with a given path
|
||||
|
||||
* `GetLeadingWhitespace() bool`: returns the leading whitespace of the given string
|
||||
* `NewBufferFromFile(path string) *Buffer`: creates a new buffer from a given
|
||||
path
|
||||
|
||||
* `IsWordChar(str string) bool`: returns whether or not the string is a 'word character'
|
||||
* `GetLeadingWhitespace() bool`: returns the leading whitespace of the given
|
||||
string
|
||||
|
||||
* `IsWordChar(str string) bool`: returns whether or not the string is a 'word
|
||||
character'
|
||||
|
||||
* `RuneStr(r rune) string`: returns a string containing the given rune
|
||||
|
||||
* `Loc(x, y int) Loc`: returns a new `Loc` struct
|
||||
|
||||
* `JoinPaths(dir... string) string` combines multiple directories to a full path
|
||||
* `WorkingDirectory() string`: returns a rooted path name to the current working
|
||||
directory
|
||||
|
||||
* `DirectoryName(path string)` returns all but the last element of path ,typically the path's directory
|
||||
* `JoinPaths(dir... string) string`: combines multiple directories to a full
|
||||
path
|
||||
|
||||
* `DirectoryName(path string)`: returns all but the last element of path,
|
||||
typically the path's directory
|
||||
|
||||
* `GetOption(name string)`: returns the value of the requested option
|
||||
|
||||
* `AddOption(name string, value interface{})`: sets the given option with the given
|
||||
value (`interface{}` means any type in Go)
|
||||
* `AddOption(name string, value interface{})`: sets the given option with the
|
||||
given value (`interface{}` means any type in Go)
|
||||
|
||||
* `SetOption(option, value string)`: sets the given option to the value. This will
|
||||
set the option globally, unless it is a local only option.
|
||||
* `SetOption(option, value string)`: sets the given option to the value. This
|
||||
will set the option globally, unless it is a local only option.
|
||||
|
||||
* `SetLocalOption(option, value string, view *View)`: sets the given option to
|
||||
the value locally in the given buffer
|
||||
@@ -89,8 +101,8 @@ as Go's GOOS variable, so `darwin`, `windows`, `linux`, `freebsd`...)
|
||||
* `BindKey(key, action string)`: binds `key` to `action`
|
||||
|
||||
* `MakeCommand(name, function string, completions ...Completion)`:
|
||||
creates a command with `name` which will call `function` when executed.
|
||||
Use 0 for completions to get NoCompletion.
|
||||
creates a command with `name` which will call `function` when executed. Use 0
|
||||
for completions to get NoCompletion.
|
||||
|
||||
* `MakeCompletion(function string)`:
|
||||
creates a `Completion` to use with `MakeCommand`
|
||||
@@ -99,68 +111,179 @@ as Go's GOOS variable, so `darwin`, `windows`, `linux`, `freebsd`...)
|
||||
|
||||
* `HandleCommand(cmd string)`: runs the given command
|
||||
|
||||
* `HandleShellCommand(shellCmd string, interactive bool, waitToClose bool)`: runs the given shell
|
||||
command. The `interactive` bool specifies whether the command should run in the background. The
|
||||
`waitToClose` bool only applies if `interactive` is true and means that it should wait before
|
||||
returning to the editor.
|
||||
* `ExecCommand(name string, args []string) (string, error)`: exec a (shell) command with the
|
||||
given arguments. Returns the command's output and a possible error.
|
||||
|
||||
* `ToCharPos(loc Loc, buf *Buffer) int`: returns the character position of a given x, y location
|
||||
* `RunShellCommand(cmd string) (string, error)`: Run a shell command. This uses `ExecCommand`
|
||||
under the hood but also does some parsing for the arguments (i.e. quoted arguments). The
|
||||
function returns the command's output and a possible error.
|
||||
|
||||
* `RunBackgroundShell(cmd string)`: Run a shell command in the background.
|
||||
|
||||
* `RunInteractiveShell(cmd string, wait bool, getOutput bool) (string, error)`: Run a shell command
|
||||
by closing micro and running the command interactively. If `wait` is true, a prompt will be
|
||||
used after the process exits to prevent the terminal from immediately returning to micro, allowing
|
||||
the user to view the output of the process. If `getOutput` is true, the command's standard output
|
||||
will be returned. Note that if `getOutput` is true, some interactive commands may not behave
|
||||
normally because `isatty` will return false.
|
||||
|
||||
* `RunTermEmulator(cmd string, wait bool, getOutput bool, callback string) error`: Same as
|
||||
`RunInteractiveShell` except the command is run within the current split in a terminal emulator.
|
||||
The `callback` input is a string callback to a lua function which will be called when the process
|
||||
exits. The output of the process will be provided as the first and only argument to the callback
|
||||
(it will be empty if `getOutput` is false).
|
||||
Note that this functionality is only supported on some operating systems (linux, darwin, dragonfly,
|
||||
openbsd, freebsd). Use the `TermEmuSupported` (see below) boolean to determine if the current
|
||||
system is supported.
|
||||
|
||||
* `TermEmuSupported`: Boolean specifying if the terminal emulator is supported on the version of
|
||||
micro that is running.
|
||||
|
||||
* `ToCharPos(loc Loc, buf *Buffer) int`: returns the character position of a
|
||||
given x, y location
|
||||
|
||||
* `Reload`: (Re)load everything
|
||||
|
||||
* `ByteOffset(loc Loc, buf *Buffer) int`: exactly like `ToCharPos` except it it counts bytes instead of runes
|
||||
* `ByteOffset(loc Loc, buf *Buffer) int`: exactly like `ToCharPos` except it it
|
||||
counts bytes instead of runes
|
||||
|
||||
* `JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit string, userargs ...string)`:
|
||||
Starts running the given process in the background. `onStdout` `onStderr` and `onExit`
|
||||
are callbacks to lua functions which will be called when the given actions happen
|
||||
to the background process.
|
||||
`userargs` are the arguments which will get passed to the callback functions
|
||||
Starts running the given process in the background. `onStdout` `onStderr` and
|
||||
`onExit` are callbacks to lua functions which will be called when the given
|
||||
actions happen to the background process. `userargs` are the arguments which
|
||||
will get passed to the callback functions
|
||||
|
||||
* `JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...string)`:
|
||||
Starts running the given shell command in the background. Note that the command execute
|
||||
is first parsed by a shell when using this command. It is executed with `sh -c`.
|
||||
Starts running the given shell command in the background. Note that the
|
||||
command execute is first parsed by a shell when using this command. It is
|
||||
executed with `sh -c`.
|
||||
|
||||
* `JobSend(cmd *exec.Cmd, data string)`: send a string into the stdin of the job process
|
||||
* `JobSend(cmd *exec.Cmd, data string)`: send a string into the stdin of the job
|
||||
process
|
||||
|
||||
* `JobStop(cmd *exec.Cmd)`: kill a job
|
||||
|
||||
This may seem like a small list of available functions but some of the objects
|
||||
returned by the functions have many methods. `CurView()` returns a view object
|
||||
which has all the actions which you can call. For example `CurView():Save(false)`.
|
||||
You can see the full list of possible actions in the keybindings help topic.
|
||||
The boolean on all the actions indicates whether or not the lua callbacks should
|
||||
be run. I would recommend generally sticking to false when making a plugin to
|
||||
avoid recursive problems, for example if you call `CurView():Save(true)` in `onSave()`.
|
||||
Just use `CurView():Save(false)` so that it won't call `onSave()` again.
|
||||
which has all the actions which you can call. For example
|
||||
`CurView():Save(false)`. You can see the full list of possible actions in the
|
||||
keybindings help topic. The boolean on all the actions indicates whether or not
|
||||
the lua callbacks should be run. I would recommend generally sticking to false
|
||||
when making a plugin to avoid recursive problems, for example if you call
|
||||
`CurView():Save(true)` in `onSave()`. Just use `CurView():Save(false)` so that
|
||||
it won't call `onSave()` again.
|
||||
|
||||
Using the view object, you can also access the buffer associated with that view
|
||||
by using `CurView().Buf`, which lets you access the `FileType`, `Path`, `Name`...
|
||||
by using `CurView().Buf`, which lets you access the `FileType`, `Path`,
|
||||
`Name`...
|
||||
|
||||
The possible methods which you can call using the `messenger` variable are:
|
||||
|
||||
* `messenger.Message(msg ...interface{})`
|
||||
* `messenger.Error(msg ...interface{})`
|
||||
* `messenger.YesNoPrompt(prompt string) (bool, bool)`
|
||||
* `messenger.YesNoPrompt(prompt string) (bool,bool)`
|
||||
* `messenger.Prompt(prompt, historyType string, completionType Completion) (string, bool)`
|
||||
* `messenger.AddLog(msg ...interface{})`
|
||||
|
||||
If you want a standard prompt, just use `messenger.Prompt(prompt, "", 0)`
|
||||
#### Note
|
||||
|
||||
# Adding help files, syntax files, or colorschemes in your plugin
|
||||
Go function signatures use `.` and lua uses `:` so
|
||||
|
||||
You can use the `AddRuntimeFile(name, type, path string)` function to add various kinds of
|
||||
files to your plugin. For example, if you'd like to add a help topic to your plugin
|
||||
called `test`, you would create a `test.md` file, and call the function:
|
||||
```go
|
||||
messenger.Message()
|
||||
```
|
||||
|
||||
turns to
|
||||
|
||||
```lua
|
||||
messenger:Message()
|
||||
```
|
||||
|
||||
If you want a standard prompt, just use
|
||||
|
||||
```lua
|
||||
messenger:Prompt(prompt, "", 0)
|
||||
```
|
||||
|
||||
Debug or logging your plugin can be done with below lua example code.
|
||||
|
||||
```lua
|
||||
messenger:AddLog("Message goes here ",pluginVariableToPrintHere)
|
||||
```
|
||||
|
||||
In Micro to see your plugin logging output press `CtrlE` then type `log`, a
|
||||
logging window will open and any logging sent from your plugin will be displayed
|
||||
here.
|
||||
|
||||
|
||||
## Accessing the Go standard library
|
||||
|
||||
It is possible for your lua code to access many of the functions in the Go
|
||||
standard library.
|
||||
|
||||
Simply import the package you'd like and then you can use it. For example:
|
||||
|
||||
```lua
|
||||
local ioutil = import("io/ioutil")
|
||||
local fmt = import("fmt")
|
||||
|
||||
local data, err = ioutil.ReadFile("SomeFile.txt")
|
||||
|
||||
if err ~= nil then
|
||||
messenger:Error("Error reading file: SomeFile.txt")
|
||||
else
|
||||
-- Data is returned as an array of bytes
|
||||
-- Using Sprintf will convert it to a string
|
||||
local str = fmt.Sprintf("%s", data)
|
||||
|
||||
-- Do something with the file you just read!
|
||||
-- ...
|
||||
end
|
||||
```
|
||||
|
||||
Here are the packages from the Go standard library that you can access.
|
||||
Nearly all functions from these packages are supported. For an exact
|
||||
list of which functions are supported you can look through `lua.go`
|
||||
(which should be easy to understand).
|
||||
|
||||
```
|
||||
fmt
|
||||
io
|
||||
io/ioutil
|
||||
net
|
||||
math
|
||||
math/rand
|
||||
os
|
||||
runtime
|
||||
path
|
||||
filepath
|
||||
strings
|
||||
regexp
|
||||
errors
|
||||
time
|
||||
```
|
||||
|
||||
For documentation for each of these functions, you can simply look
|
||||
through the Go standard library documentation.
|
||||
|
||||
## Adding help files, syntax files, or colorschemes in your plugin
|
||||
|
||||
You can use the `AddRuntimeFile(name, type, path string)` function to add
|
||||
various kinds of files to your plugin. For example, if you'd like to add a help
|
||||
topic to your plugin called `test`, you would create a `test.md` file, and call
|
||||
the function:
|
||||
|
||||
```lua
|
||||
AddRuntimeFile("test", "help", "test.md")
|
||||
```
|
||||
|
||||
Use `AddRuntimeFilesFromDirectory(name, type, dir, pattern)` to add a number of files
|
||||
to the runtime.
|
||||
To read the content of a runtime file use `ReadRuntimeFile(fileType, name string)`
|
||||
or `ListRuntimeFiles(fileType string)` for all runtime files.
|
||||
Use `AddRuntimeFilesFromDirectory(name, type, dir, pattern)` to add a number of
|
||||
files to the runtime. To read the content of a runtime file use
|
||||
`ReadRuntimeFile(fileType, name string)` or `ListRuntimeFiles(fileType string)`
|
||||
for all runtime files.
|
||||
|
||||
# Autocomplete command arguments
|
||||
|
||||
## Autocomplete command arguments
|
||||
|
||||
See this example to learn how to use `MakeCompletion` and `MakeCommand`
|
||||
|
||||
@@ -190,27 +313,32 @@ end
|
||||
MakeCommand("foo", "example.foo", MakeCompletion("example.complete"))
|
||||
```
|
||||
|
||||
# Default plugins
|
||||
|
||||
## Default plugins
|
||||
|
||||
For examples of plugins, see the default `autoclose` and `linter` plugins
|
||||
(stored in the normal micro core repo under `runtime/plugins`) as well as
|
||||
any plugins that are stored in the official channel [here](https://github.com/micro-editor/plugin-channel).
|
||||
(stored in the normal micro core repo under `runtime/plugins`) as well as any
|
||||
plugins that are stored in the official channel
|
||||
[here](https://github.com/micro-editor/plugin-channel).
|
||||
|
||||
# Plugin Manager
|
||||
|
||||
Micro also has a built in plugin manager which you can invoke with the `> plugin ...` command.
|
||||
## Plugin Manager
|
||||
|
||||
Micro also has a built in plugin manager which you can invoke with the
|
||||
`> plugin ...` command.
|
||||
|
||||
For the valid commands you can use, see the `command` help topic.
|
||||
|
||||
The manager fetches plugins from the channels (which is simply a list of plugin metadata)
|
||||
which it knows about. By default, micro only knows about the official channel which is located
|
||||
at github.com/micro-editor/plugin-channel but you can add your own third-party channels using
|
||||
the `pluginchannels` option and you can directly link third-party plugins to allow installation
|
||||
through the plugin manager with the `pluginrepos` option.
|
||||
The manager fetches plugins from the channels (which is simply a list of plugin
|
||||
metadata) which it knows about. By default, micro only knows about the official
|
||||
channel which is located at github.com/micro-editor/plugin-channel but you can
|
||||
add your own third-party channels using the `pluginchannels` option and you can
|
||||
directly link third-party plugins to allow installation through the plugin
|
||||
manager with the `pluginrepos` option.
|
||||
|
||||
If you'd like to publish a plugin you've made as an official plugin, you should upload your
|
||||
plugin online (to Github preferably) and add a `repo.json` file. This file will contain the
|
||||
metadata for your plugin. Here is an example:
|
||||
If you'd like to publish a plugin you've made as an official plugin, you should
|
||||
upload your plugin online (to Github preferably) and add a `repo.json` file.
|
||||
This file will contain the metadata for your plugin. Here is an example:
|
||||
|
||||
```json
|
||||
[{
|
||||
@@ -229,7 +357,8 @@ metadata for your plugin. Here is an example:
|
||||
}]
|
||||
```
|
||||
|
||||
Then open a pull request at github.com/micro-editor/plugin-channel adding a link to the
|
||||
raw `repo.json` that is in your plugin repository.
|
||||
To make updating the plugin work, the first line of your plugins lua code should contain the version of the plugin. (Like this: `VERSION = "1.0.0"`)
|
||||
Please make sure to use [semver](http://semver.org/) for versioning.
|
||||
Then open a pull request at github.com/micro-editor/plugin-channel adding a link
|
||||
to the raw `repo.json` that is in your plugin repository. To make updating the
|
||||
plugin work, the first line of your plugins lua code should contain the version
|
||||
of the plugin. (Like this: `VERSION = "1.0.0"`) Please make sure to use
|
||||
[semver](http://semver.org/) for versioning.
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
# Tutorial
|
||||
|
||||
This is a brief intro to micro's configuration system that will give some
|
||||
simple examples showing how to configure settings, rebind keys,
|
||||
and use `init.lua` to configure micro to your liking.
|
||||
This is a brief intro to micro's configuration system that will give some simple
|
||||
examples showing how to configure settings, rebind keys, and use `init.lua` to
|
||||
configure micro to your liking.
|
||||
|
||||
Hopefully you'll find this useful.
|
||||
|
||||
See `> help defaultkeys` for a list an explanation of the default keybindings.
|
||||
|
||||
### Plugins
|
||||
|
||||
Micro has a plugin manager which can automatically download plugins for you.
|
||||
To see the plugins 'official' plugins, go to github.com/micro-editor/plugin-channel.
|
||||
Micro has a plugin manager which can automatically download plugins for you. To
|
||||
see the 'official' plugins, go to github.com/micro-editor/plugin-channel.
|
||||
|
||||
For example, if you'd like to install the snippets plugin (listed in that repo),
|
||||
just press `CtrlE` to execute a command, and type `plugin install snippets`.
|
||||
@@ -20,23 +22,23 @@ topic.
|
||||
### Settings
|
||||
|
||||
In micro, your settings are stored in `~/.config/micro/settings.json`, a file
|
||||
that is created the first time you run micro. It is a json file which holds
|
||||
all the settings and their values. To change an option, you can either
|
||||
change the value in the `settings.json` file, or you can type it in directly
|
||||
while using micro.
|
||||
that is created the first time you run micro. It is a json file which holds all
|
||||
the settings and their values. To change an option, you can either change the
|
||||
value in the `settings.json` file, or you can type it in directly while using
|
||||
micro.
|
||||
|
||||
Simply press CtrlE to go to command mode, and type `set option value` (in the
|
||||
future, I will use `> set option value` to indicate pressing CtrlE). The
|
||||
change will take effect immediately and will also be saved to the `settings.json`
|
||||
file so that the setting will stick even after you close micro.
|
||||
future, I will use `> set option value` to indicate pressing CtrlE). The change
|
||||
will take effect immediately and will also be saved to the `settings.json` file
|
||||
so that the setting will stick even after you close micro.
|
||||
|
||||
You can also set options locally which means that the setting will only have
|
||||
the value you give it in the buffer you set it in. For example, if you have
|
||||
two splits open, and you type `> setlocal tabsize 2`, the tabsize will only
|
||||
be 2 in the current buffer. Also micro will not save this local change to the
|
||||
You can also set options locally which means that the setting will only have the
|
||||
value you give it in the buffer you set it in. For example, if you have two
|
||||
splits open, and you type `> setlocal tabsize 2`, the tabsize will only be 2 in
|
||||
the current buffer. Also micro will not save this local change to the
|
||||
`settings.json` file. However, you can still set options locally in the
|
||||
`settings.json` file. For example, if you want the `tabsize` to be 2 only
|
||||
in Ruby files, and 4 otherwise, you could put the following in `settings.json`:
|
||||
`settings.json` file. For example, if you want the `tabsize` to be 2 only in
|
||||
Ruby files, and 4 otherwise, you could put the following in `settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -54,8 +56,8 @@ If you would like to know more about all the available options, see the
|
||||
|
||||
### Keybindings
|
||||
|
||||
Keybindings work in much the same way as options. You configure them using
|
||||
the `~/.config/micro/bindings.json` file.
|
||||
Keybindings work in much the same way as options. You configure them using the
|
||||
`~/.config/micro/bindings.json` file.
|
||||
|
||||
For example if you would like to bind `CtrlR` to redo you could put the
|
||||
following in `bindings.json`:
|
||||
@@ -78,8 +80,8 @@ what actions are available, see the `keybindings` help topic (`> help keybinding
|
||||
### Configuration with Lua
|
||||
|
||||
If you need more power than the json files provide, you can use the `init.lua`
|
||||
file. Create it in `~/.config/micro`. This file is a lua file that is run
|
||||
when micro starts and is essentially a one-file plugin.
|
||||
file. Create it in `~/.config/micro`. This file is a lua file that is run when
|
||||
micro starts and is essentially a one-file plugin.
|
||||
|
||||
I'll show you how to use the `init.lua` file by giving an example of how to
|
||||
create a binding to `CtrlR` which will execute `go run` on the current file,
|
||||
@@ -98,8 +100,8 @@ end
|
||||
BindKey("CtrlR", "init.gorun")
|
||||
```
|
||||
|
||||
Alternatively, you could get rid of the `BindKey` line, and put this line in
|
||||
the `bindings.json` file:
|
||||
Alternatively, you could get rid of the `BindKey` line, and put this line in the
|
||||
`bindings.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -107,5 +109,5 @@ the `bindings.json` file:
|
||||
}
|
||||
```
|
||||
|
||||
For more information about plugins and the lua system that micro uses, see
|
||||
the `plugins` help topic (`> help plugins`).
|
||||
For more information about plugins and the lua system that micro uses, see the
|
||||
`plugins` help topic (`> help plugins`).
|
||||
|
||||
@@ -1108,6 +1108,7 @@ function preInsertNewline(v)
|
||||
v:InsertNewline(false)
|
||||
v:InsertTab(false)
|
||||
v.Buf:Insert(-v.Cursor.Loc, "\n" .. ws)
|
||||
v:StartOfLine(false)
|
||||
v:CursorLeft(false)
|
||||
return false
|
||||
end
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
# utf8.lua
|
||||
one-file pure-lua 5.1 regex library
|
||||
|
||||
This library _is_ the simple way to add utf8 support into your application.
|
||||
|
||||
Some examples from http://www.lua.org/manual/5.1/manual.html#5.4 :
|
||||
```Lua
|
||||
local utf8 = require "utf8"
|
||||
|
||||
local s = "hello world from Lua"
|
||||
for w in string.gmatch(s, "%a+") do
|
||||
print(w)
|
||||
end
|
||||
--[[
|
||||
hello
|
||||
world
|
||||
from
|
||||
Lua
|
||||
]]--
|
||||
|
||||
s = "Привет, мир, от Lua"
|
||||
for w in utf8.gmatch(s, "[^%p%d%s%c]+") do
|
||||
print(w)
|
||||
end
|
||||
--[[
|
||||
Привет
|
||||
мир
|
||||
от
|
||||
Lua
|
||||
]]--
|
||||
|
||||
local t = {}
|
||||
s = "from=world, to=Lua"
|
||||
for k, v in string.gmatch(s, "(%w+)=(%w+)") do
|
||||
t[k] = v
|
||||
end
|
||||
for k,v in pairs(t) do
|
||||
print(k,v)
|
||||
end
|
||||
--[[
|
||||
to Lua
|
||||
from world
|
||||
]]--
|
||||
|
||||
t = {}
|
||||
s = "从=世界, 到=Lua"
|
||||
for k, v in utf8.gmatch(s, "([^%p%s%c]+)=([^%p%s%c]+)") do
|
||||
t[k] = v
|
||||
end
|
||||
for k,v in pairs(t) do
|
||||
print(k,v)
|
||||
end
|
||||
--[[
|
||||
到 Lua
|
||||
从 世界
|
||||
]]--
|
||||
|
||||
local x = string.gsub("hello world", "(%w+)", "%1 %1")
|
||||
print(x)
|
||||
--hello hello world world
|
||||
|
||||
x = utf8.gsub("Ahoj světe", "([^%p%s%c]+)", "%1 %1")
|
||||
print(x)
|
||||
--Ahoj Ahoj světe světe
|
||||
|
||||
x = string.gsub("hello world", "%w+", "%0 %0", 1)
|
||||
print(x)
|
||||
--hello hello world
|
||||
|
||||
x = utf8.gsub("Ahoj světe", "[^%p%s%c]+", "%0 %0", 1)
|
||||
print(x)
|
||||
--Ahoj Ahoj světe
|
||||
|
||||
x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1")
|
||||
print(x)
|
||||
--world hello Lua from
|
||||
|
||||
x = utf8.gsub("γεια κόσμο από Lua", "([^%p%s%c]+)%s*([^%p%s%c]+)", "%2 %1")
|
||||
print(x)
|
||||
--κόσμο γεια Lua από
|
||||
```
|
||||
Notice, there are some classes that can work only with latin(ASCII) symbols,
|
||||
for details see: https://github.com/Stepets/utf8.lua/blob/master/utf8.lua#L470
|
||||
|
||||
Of course you can do this trick:
|
||||
```Lua
|
||||
for k,v in pairs(utf8) do
|
||||
string[k] = v
|
||||
end
|
||||
```
|
||||
But this can lead to very strange errors. You were warned.
|
||||
|
||||
A little bit more interesting examples:
|
||||
```Lua
|
||||
local utf8 = require 'utf8'
|
||||
for k,v in pairs(utf8) do
|
||||
string[k] = v
|
||||
end
|
||||
|
||||
local str = "пыщпыщ ололоо я водитель нло"
|
||||
print(str:find("(.л.+)н"))
|
||||
-- 8 26 ололоо я водитель
|
||||
|
||||
print(str:gsub("ло+", "보라"))
|
||||
-- пыщпыщ о보라보라 я водитель н보라 3
|
||||
|
||||
print(str:match("^п[лопыщ ]*я"))
|
||||
-- пыщпыщ ололоо я
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,9 +9,15 @@ function onViewOpen(view)
|
||||
|
||||
local ft = view.Buf.Settings["filetype"]
|
||||
|
||||
if ft == "makefile" or ft == "go" then
|
||||
if ft == "go" or
|
||||
ft == "makefile" then
|
||||
SetOption("tabstospaces", "off")
|
||||
elseif ft == "python" or ft == "python2" or ft == "python3" then
|
||||
elseif ft == "fish" or
|
||||
ft == "python" or
|
||||
ft == "python2" or
|
||||
ft == "python3" or
|
||||
ft == "yaml" or
|
||||
ft == "nim" then
|
||||
SetOption("tabstospaces", "on")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,37 +16,40 @@ function runLinter()
|
||||
if OS == "windows" then
|
||||
devnull = "NUL"
|
||||
else
|
||||
devnull = "/dev/null"
|
||||
devnull = "/dev/null"
|
||||
end
|
||||
if ft == "go" then
|
||||
lint("gobuild", "go", {"build", "-o", devnull}, "%f:%l: %m")
|
||||
lint("golint", "golint", {file}, "%f:%l:%d+: %m")
|
||||
elseif ft == "lua" then
|
||||
lint("luacheck", "luacheck", {"--no-color", file}, "%f:%l:%d+: %m")
|
||||
elseif ft == "python" then
|
||||
lint("pyflakes", "pyflakes", {file}, "%f:%l:.-:? %m")
|
||||
lint("mypy", "mypy", {file}, "%f:%l: %m")
|
||||
lint("pylint", "pylint", {"--output-format=parseable", "--reports=no", file}, "%f:%l: %m")
|
||||
elseif ft == "c" then
|
||||
|
||||
if ft == "c" then
|
||||
lint("gcc", "gcc", {"-fsyntax-only", "-Wall", "-Wextra", file}, "%f:%l:%d+:.+: %m")
|
||||
elseif ft == "c++" then
|
||||
lint("gcc", "gcc", {"-fsyntax-only","-std=c++14", "-Wall", "-Wextra", file}, "%f:%l:%d+:.+: %m")
|
||||
elseif ft == "swift" and OS == "darwin" then
|
||||
lint("switfc", "xcrun", {"swiftc", file}, "%f:%l:%d+:.+: %m")
|
||||
elseif ft == "swift" and OS == "linux" then
|
||||
lint("switfc", "swiftc", {file}, "%f:%l:%d+:.+: %m")
|
||||
elseif ft == "Objective-C" then
|
||||
lint("clang", "xcrun", {"clang", "-fsyntax-only", "-Wall", "-Wextra", file}, "%f:%l:%d+:.+: %m")
|
||||
elseif ft == "c++" then
|
||||
lint("gcc", "gcc", {"-fsyntax-only","-std=c++14", "-Wall", "-Wextra", file}, "%f:%l:%d+:.+: %m")
|
||||
elseif ft == "d" then
|
||||
lint("dmd", "dmd", {"-color=off", "-o-", "-w", "-wi", "-c", file}, "%f%(%l%):.+: %m")
|
||||
elseif ft == "go" then
|
||||
lint("gobuild", "go", {"build", "-o", devnull}, "%f:%l: %m")
|
||||
lint("golint", "golint", {file}, "%f:%l:%d+: %m")
|
||||
elseif ft == "java" then
|
||||
lint("javac", "javac", {"-d", dir, file}, "%f:%l: error: %m")
|
||||
elseif ft == "javascript" then
|
||||
lint("jshint", "jshint", {file}, "%f: line %l,.+, %m")
|
||||
elseif ft == "nim" then
|
||||
lint("nim", "nim", {"check", "--listFullPaths", "--stdout", "--hints:off", file}, "%f.%l, %d+. %m")
|
||||
elseif string.match(ft, "literate") then
|
||||
lint("literate", "lit", {"-c", file}, "%f:%l:%m")
|
||||
elseif ft == "lua" then
|
||||
lint("luacheck", "luacheck", {"--no-color", file}, "%f:%l:%d+: %m")
|
||||
elseif ft == "nim" then
|
||||
lint("nim", "nim", {"check", "--listFullPaths", "--stdout", "--hints:off", file}, "%f.%l, %d+. %m")
|
||||
elseif ft == "Objective-C" then
|
||||
lint("clang", "xcrun", {"clang", "-fsyntax-only", "-Wall", "-Wextra", file}, "%f:%l:%d+:.+: %m")
|
||||
elseif ft == "python" then
|
||||
lint("pyflakes", "pyflakes", {file}, "%f:%l:.-:? %m")
|
||||
lint("mypy", "mypy", {file}, "%f:%l: %m")
|
||||
lint("pylint", "pylint", {"--output-format=parseable", "--reports=no", file}, "%f:%l: %m")
|
||||
elseif ft == "shell" then
|
||||
lint("shfmt", "shfmt", {file}, "%f:%l:%d+: %m")
|
||||
elseif ft == "swift" and OS == "darwin" then
|
||||
lint("switfc", "xcrun", {"swiftc", file}, "%f:%l:%d+:.+: %m")
|
||||
elseif ft == "swift" and OS == "linux" then
|
||||
lint("switfc", "swiftc", {file}, "%f:%l:%d+:.+: %m")
|
||||
elseif ft == "yaml" then
|
||||
lint("yaml", "yamllint", {"--format", "parsable", file}, "%f:%l:%d+:.+ %m")
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user