mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-31 15:17:15 +09:00
Compare commits
403 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 | ||
|
|
fad95c028a | ||
|
|
5c462f5600 | ||
|
|
5dcc486214 | ||
|
|
9d2915c328 | ||
|
|
4f8f6f1ca3 | ||
|
|
fbf58486fb | ||
|
|
97aae225da | ||
|
|
1ca2debd7c | ||
|
|
c1584dd72f | ||
|
|
2bbd29998e | ||
|
|
3b2d7abe3d | ||
|
|
994d1acbfc | ||
|
|
1f4ae1e2d5 | ||
|
|
18ad74a982 | ||
|
|
4cad06c7b3 | ||
|
|
ec77dccb1d | ||
|
|
63b4848bb0 | ||
|
|
e27802c41e | ||
|
|
75329830f9 | ||
|
|
030a05c103 | ||
|
|
e4751fd84c | ||
|
|
252def5b95 | ||
|
|
42f2af7956 | ||
|
|
91fb8225d1 | ||
|
|
bee60023ae | ||
|
|
0ffae1896b | ||
|
|
8f4820ba28 | ||
|
|
3a02ad8664 | ||
|
|
244e0ded60 | ||
|
|
19926d95fe | ||
|
|
f27ee60149 | ||
|
|
3908813afe | ||
|
|
e6f24b0924 | ||
|
|
b2c1c8f8db | ||
|
|
5a2f9a374b | ||
|
|
59ab5107bf | ||
|
|
06c65d8404 | ||
|
|
d38055f825 | ||
|
|
4aeb4c78ac | ||
|
|
3741a71cc5 | ||
|
|
fc9ddaf941 | ||
|
|
1f6a9cfa46 | ||
|
|
1a18fad1a4 | ||
|
|
39b5c4746e | ||
|
|
5ec08d0a29 | ||
|
|
7ec222895c | ||
|
|
da1ec3132f | ||
|
|
167f1e5770 | ||
|
|
8719b9d75c | ||
|
|
af1f161b06 | ||
|
|
7e4ff05c57 | ||
|
|
f1ecd37578 | ||
|
|
7ccec0e3f7 | ||
|
|
397361f23d | ||
|
|
681da2e90c | ||
|
|
118e6b1804 | ||
|
|
f933b90c66 | ||
|
|
21840d3ffe | ||
|
|
5e80ab9362 | ||
|
|
43eb238b08 | ||
|
|
00718f99cf | ||
|
|
c3a73d63b8 | ||
|
|
bc3c8eaf74 | ||
|
|
8d268ef021 | ||
|
|
c45ff4dd4f | ||
|
|
37ad137012 | ||
|
|
0165c4a40a | ||
|
|
3270acdd00 | ||
|
|
ee84296dfe | ||
|
|
42849e7104 | ||
|
|
a1f6dd6f4f | ||
|
|
47cdfb3de0 | ||
|
|
480a220fda | ||
|
|
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
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
.DS_Store
|
||||
|
||||
micro
|
||||
!cmd/micro
|
||||
binaries/
|
||||
@@ -5,4 +7,3 @@ tmp.sh
|
||||
test/
|
||||
.idea/
|
||||
packages/
|
||||
cmd/micro/vendor
|
||||
|
||||
72
.gitmodules
vendored
Normal file
72
.gitmodules
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
[submodule "cmd/micro/vendor/github.com/blang/semver"]
|
||||
path = cmd/micro/vendor/github.com/blang/semver
|
||||
url = https://github.com/blang/semver
|
||||
[submodule "cmd/micro/vendor/github.com/dustin/go-humanize"]
|
||||
path = cmd/micro/vendor/github.com/dustin/go-humanize
|
||||
url = https://github.com/dustin/go-humanize
|
||||
[submodule "cmd/micro/vendor/github.com/go-errors/errors"]
|
||||
path = cmd/micro/vendor/github.com/go-errors/errors
|
||||
url = https://github.com/go-errors/errors
|
||||
[submodule "cmd/micro/vendor/github.com/mattn/go-isatty"]
|
||||
path = cmd/micro/vendor/github.com/mattn/go-isatty
|
||||
url = https://github.com/mattn/go-isatty
|
||||
[submodule "cmd/micro/vendor/github.com/mattn/go-runewidth"]
|
||||
path = cmd/micro/vendor/github.com/mattn/go-runewidth
|
||||
url = https://github.com/mattn/go-runewidth
|
||||
[submodule "cmd/micro/vendor/github.com/mitchellh/go-homedir"]
|
||||
path = cmd/micro/vendor/github.com/mitchellh/go-homedir
|
||||
url = https://github.com/mitchellh/go-homedir
|
||||
[submodule "cmd/micro/vendor/github.com/sergi/go-diff"]
|
||||
path = cmd/micro/vendor/github.com/sergi/go-diff
|
||||
url = https://github.com/sergi/go-diff
|
||||
[submodule "cmd/micro/vendor/github.com/yuin/gopher-lua"]
|
||||
path = cmd/micro/vendor/github.com/yuin/gopher-lua
|
||||
url = https://github.com/yuin/gopher-lua
|
||||
[submodule "cmd/micro/vendor/golang.org/x/net"]
|
||||
path = cmd/micro/vendor/golang.org/x/net
|
||||
url = https://go.googlesource.com/net
|
||||
[submodule "cmd/micro/vendor/github.com/zyedidia/clipboard"]
|
||||
path = cmd/micro/vendor/github.com/zyedidia/clipboard
|
||||
url = https://github.com/zyedidia/clipboard
|
||||
[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/tcell"]
|
||||
path = cmd/micro/vendor/github.com/zyedidia/tcell
|
||||
url = https://github.com/zyedidia/tcell
|
||||
[submodule "cmd/micro/vendor/github.com/gdamore/encoding"]
|
||||
path = cmd/micro/vendor/github.com/gdamore/encoding
|
||||
url = https://github.com/gdamore/encoding
|
||||
[submodule "cmd/micro/vendor/golang.org/x/text"]
|
||||
path = cmd/micro/vendor/golang.org/x/text
|
||||
url = https://go.googlesource.com/text
|
||||
[submodule "cmd/micro/vendor/github.com/lucasb-eyer/go-colorful"]
|
||||
path = cmd/micro/vendor/github.com/lucasb-eyer/go-colorful
|
||||
url = https://github.com/lucasb-eyer/go-colorful
|
||||
[submodule "cmd/micro/vendor/layeh.com/gopher-luar"]
|
||||
path = cmd/micro/vendor/layeh.com/gopher-luar
|
||||
url = https://github.com/layeh/gopher-luar
|
||||
[submodule "cmd/micro/vendor/gopkg.in/yaml.v2"]
|
||||
path = cmd/micro/vendor/gopkg.in/yaml.v2
|
||||
url = https://gopkg.in/yaml.v2
|
||||
[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.
|
||||
|
||||
11
Makefile
11
Makefile
@@ -11,7 +11,7 @@ ADDITIONAL_GO_LINKER_FLAGS := $(shell GOOS=$(shell go env GOHOSTOS) \
|
||||
GOBIN ?= $(shell go env GOPATH)/bin
|
||||
|
||||
# Builds micro after checking dependencies but without updating the runtime
|
||||
build: deps
|
||||
build: update
|
||||
go build -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
|
||||
# Builds micro after building the runtime and checking dependencies
|
||||
@@ -22,7 +22,7 @@ build-quick:
|
||||
go build -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
|
||||
# Same as 'build' but installs to $GOBIN afterward
|
||||
install: deps
|
||||
install: update
|
||||
go install -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
|
||||
# Same as 'build-all' but installs to $GOBIN afterward
|
||||
@@ -32,13 +32,9 @@ install-all: runtime install
|
||||
install-quick:
|
||||
go install -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
|
||||
# Checks for dependencies
|
||||
deps:
|
||||
go get -d ./cmd/micro
|
||||
|
||||
update:
|
||||
git pull
|
||||
go get -u -d ./cmd/micro
|
||||
git submodule update --init
|
||||
|
||||
# Builds the runtime
|
||||
runtime:
|
||||
@@ -47,7 +43,6 @@ runtime:
|
||||
mv runtime.go cmd/micro
|
||||
|
||||
test:
|
||||
go get -d ./cmd/micro
|
||||
go test ./cmd/micro
|
||||
|
||||
clean:
|
||||
|
||||
65
README.md
65
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,15 +20,33 @@ 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
|
||||
* No dependencies or external files are needed -- just the binary you can download further down the page
|
||||
* Multiple cursors
|
||||
* Common keybindings (ctrl-s, ctrl-c, ctrl-v, ctrl-z...)
|
||||
* Keybindings can be rebound to your liking
|
||||
* Sane defaults
|
||||
* You shouldn't have to configure much out of the box (and it is extremely easy to configure)
|
||||
* Splits and tabs
|
||||
* Nano-like menu to help you remember the keybindings
|
||||
* Extremely good mouse support
|
||||
* This means mouse dragging to create a selection, double click to select by word, and triple click to select by line
|
||||
* Cross platform (It should work on all the platforms Go runs on)
|
||||
@@ -48,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
|
||||
|
||||
@@ -67,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:
|
||||
|
||||
@@ -75,39 +106,51 @@ 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/...
|
||||
go get -d github.com/zyedidia/micro/cmd/micro
|
||||
cd $GOPATH/src/github.com/zyedidia/micro
|
||||
make install
|
||||
```
|
||||
|
||||
Please make sure that when you are working with micro's code, you are working on your `GOPATH`.
|
||||
|
||||
The binary will then be installed to `$GOPATH/bin` (or your `$GOBIN`).
|
||||
|
||||
Please make sure that when you are working with micro's code, you are working on your `GOPATH`.
|
||||
|
||||
You can install directly with `go get` (`go get -u github.com/zyedidia/micro/cmd/micro`) but this isn't recommended because it doesn't build micro with version information which is useful for the plugin manager.
|
||||
|
||||
### 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
|
||||
|
||||
@@ -135,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;
|
||||
123
cmd/micro/Gopkg.lock
generated
123
cmd/micro/Gopkg.lock
generated
@@ -1,123 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/blang/semver"
|
||||
packages = ["."]
|
||||
revision = "4a1e882c79dcf4ec00d2e29fac74b9c8938d5052"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/dustin/go-humanize"
|
||||
packages = ["."]
|
||||
revision = "259d2a102b871d17f30e3cd9881a642961a1e486"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/gdamore/encoding"
|
||||
packages = ["."]
|
||||
revision = "b23993cbb6353f0e6aa98d0ee318a34728f628b9"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/go-errors/errors"
|
||||
packages = ["."]
|
||||
revision = "8fa88b06e5974e97fbf9899a7f86a344bfd1f105"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/lucasb-eyer/go-colorful"
|
||||
packages = ["."]
|
||||
revision = "9c2852a141bf4711e4276f8f119c90d0f20a556c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mattn/go-isatty"
|
||||
packages = ["."]
|
||||
revision = "fc9e8d8ef48496124e79ae0df75490096eccf6fe"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mattn/go-runewidth"
|
||||
packages = ["."]
|
||||
revision = "97311d9f7767e3d6f422ea06661bc2c7a19e8a5d"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
packages = ["."]
|
||||
revision = "b8bc1bf767474819792c23f32d8286a45736f1c6"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/sergi/go-diff"
|
||||
packages = ["diffmatchpatch"]
|
||||
revision = "feef008d51ad2b3778f85d387ccf91735543008d"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/yuin/gopher-lua"
|
||||
packages = [".","ast","parse","pm"]
|
||||
revision = "b402f3114ec730d8bddb074a6c137309f561aa78"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/zyedidia/clipboard"
|
||||
packages = ["."]
|
||||
revision = "adacf416cec40266b051e7bc096c52951f2725e9"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/zyedidia/glob"
|
||||
packages = ["."]
|
||||
revision = "72567a468b2481490f359cdfb015231389e0bf9d"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/zyedidia/json5"
|
||||
packages = ["encoding/json5"]
|
||||
revision = "2518f8beebde6814f2d30d566260480d2ded2f76"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/zyedidia/tcell"
|
||||
packages = [".","encoding"]
|
||||
revision = "7095cc1c7f4173ae48314d80878e9985a0658889"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = ["context"]
|
||||
revision = "007e530097ad7f954752df63046b4036f98ba6a6"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
revision = "d4feaf1a7e61e1d9e79e6c4e76c6349e9cab0a03"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/text"
|
||||
packages = ["encoding","encoding/charmap","encoding/internal","encoding/internal/identifier","encoding/japanese","encoding/korean","encoding/simplifiedchinese","encoding/traditionalchinese","internal/gen","transform","unicode/cldr"]
|
||||
revision = "506f9d5c962f284575e88337e7d9296d27e729d3"
|
||||
|
||||
[[projects]]
|
||||
branch = "v2"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
revision = "a83829b6f1293c91addabc89d0571c246397bbf4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "layeh.com/gopher-luar"
|
||||
packages = ["."]
|
||||
revision = "80196fe2abc5682963fc7a5261f5a5d77509938b"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "aebc6aa50d78830f86e8964c1ebc1804c1f9603477ddba606717a972ca70d261"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
@@ -1,123 +0,0 @@
|
||||
|
||||
## Gopkg.toml example (these lines may be deleted)
|
||||
|
||||
## "metadata" defines metadata about the project that could be used by other independent
|
||||
## systems. The metadata defined here will be ignored by dep.
|
||||
# [metadata]
|
||||
# key1 = "value that convey data to other systems"
|
||||
# system1-data = "value that is used by a system"
|
||||
# system2-data = "value that is used by another system"
|
||||
|
||||
## "required" lists a set of packages (not projects) that must be included in
|
||||
## Gopkg.lock. This list is merged with the set of packages imported by the current
|
||||
## project. Use it when your project needs a package it doesn't explicitly import -
|
||||
## including "main" packages.
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
|
||||
## "ignored" lists a set of packages (not projects) that are ignored when
|
||||
## dep statically analyzes source code. Ignored packages can be in this project,
|
||||
## or in a dependency.
|
||||
# ignored = ["github.com/user/project/badpkg"]
|
||||
|
||||
## Constraints are rules for how directly imported projects
|
||||
## may be incorporated into the depgraph. They are respected by
|
||||
## dep whether coming from the Gopkg.toml of the current project or a dependency.
|
||||
# [[constraint]]
|
||||
## Required: the root import path of the project being constrained.
|
||||
# name = "github.com/user/project"
|
||||
#
|
||||
## Recommended: the version constraint to enforce for the project.
|
||||
## Only one of "branch", "version" or "revision" can be specified.
|
||||
# version = "1.0.0"
|
||||
# branch = "master"
|
||||
# revision = "abc123"
|
||||
#
|
||||
## Optional: an alternate location (URL or import path) for the project's source.
|
||||
# source = "https://github.com/myfork/package.git"
|
||||
#
|
||||
## "metadata" defines metadata about the dependency or override that could be used
|
||||
## by other independent systems. The metadata defined here will be ignored by dep.
|
||||
# [metadata]
|
||||
# key1 = "value that convey data to other systems"
|
||||
# system1-data = "value that is used by a system"
|
||||
# system2-data = "value that is used by another system"
|
||||
|
||||
## Overrides have the same structure as [[constraint]], but supersede all
|
||||
## [[constraint]] declarations from all projects. Only [[override]] from
|
||||
## the current project's are applied.
|
||||
##
|
||||
## Overrides are a sledgehammer. Use them only as a last resort.
|
||||
# [[override]]
|
||||
## Required: the root import path of the project being constrained.
|
||||
# name = "github.com/user/project"
|
||||
#
|
||||
## Optional: specifying a version constraint override will cause all other
|
||||
## constraints on this project to be ignored; only the overridden constraint
|
||||
## need be satisfied.
|
||||
## Again, only one of "branch", "version" or "revision" can be specified.
|
||||
# version = "1.0.0"
|
||||
# branch = "master"
|
||||
# revision = "abc123"
|
||||
#
|
||||
## Optional: specifying an alternate source location as an override will
|
||||
## enforce that the alternate location is used for that project, regardless of
|
||||
## what source location any dependent projects specify.
|
||||
# source = "https://github.com/myfork/package.git"
|
||||
|
||||
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/blang/semver"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/dustin/go-humanize"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/go-errors/errors"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/mattn/go-isatty"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/mattn/go-runewidth"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/sergi/go-diff"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/yuin/gopher-lua"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/zyedidia/clipboard"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/zyedidia/glob"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/zyedidia/json5"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/zyedidia/tcell"
|
||||
|
||||
[[constraint]]
|
||||
branch = "v2"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "layeh.com/gopher-luar"
|
||||
1338
cmd/micro/actions.go
1338
cmd/micro/actions.go
File diff suppressed because it is too large
Load Diff
@@ -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,101 +1,134 @@
|
||||
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,
|
||||
"MouseMultiCursor": (*View).MouseMultiCursor,
|
||||
}
|
||||
|
||||
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,
|
||||
"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,
|
||||
}
|
||||
|
||||
var bindingMouse = map[string]tcell.ButtonMask{
|
||||
"MouseLeft": tcell.Button1,
|
||||
"MouseMiddle": tcell.Button2,
|
||||
"MouseRight": tcell.Button3,
|
||||
"MouseWheelUp": tcell.WheelUp,
|
||||
"MouseWheelDown": tcell.WheelDown,
|
||||
"MouseWheelLeft": tcell.WheelLeft,
|
||||
"MouseWheelRight": tcell.WheelRight,
|
||||
}
|
||||
|
||||
var bindingKeys = map[string]tcell.Key{
|
||||
"Up": tcell.KeyUp,
|
||||
"Down": tcell.KeyDown,
|
||||
@@ -215,6 +248,8 @@ var bindingKeys = map[string]tcell.Key{
|
||||
"CtrlRightSq": tcell.KeyCtrlRightSq,
|
||||
"CtrlCarat": tcell.KeyCtrlCarat,
|
||||
"CtrlUnderscore": tcell.KeyCtrlUnderscore,
|
||||
"CtrlPageUp": tcell.KeyCtrlPgUp,
|
||||
"CtrlPageDown": tcell.KeyCtrlPgDn,
|
||||
"Tab": tcell.KeyTab,
|
||||
"Esc": tcell.KeyEsc,
|
||||
"Escape": tcell.KeyEscape,
|
||||
@@ -230,12 +265,16 @@ var bindingKeys = map[string]tcell.Key{
|
||||
type Key struct {
|
||||
keyCode tcell.Key
|
||||
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
|
||||
defaults := DefaultBindings()
|
||||
@@ -286,21 +325,35 @@ 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{
|
||||
keyCode: code,
|
||||
modifiers: modifiers,
|
||||
buttons: -1,
|
||||
r: 0,
|
||||
}, true
|
||||
}
|
||||
@@ -311,6 +364,16 @@ modSearch:
|
||||
return Key{
|
||||
keyCode: code,
|
||||
modifiers: modifiers,
|
||||
buttons: -1,
|
||||
r: 0,
|
||||
}, true
|
||||
}
|
||||
|
||||
// See if we can find the key in bindingMouse
|
||||
if code, ok := bindingMouse[k]; ok {
|
||||
return Key{
|
||||
modifiers: modifiers,
|
||||
buttons: code,
|
||||
r: 0,
|
||||
}, true
|
||||
}
|
||||
@@ -320,12 +383,13 @@ modSearch:
|
||||
return Key{
|
||||
keyCode: tcell.KeyRune,
|
||||
modifiers: modifiers,
|
||||
buttons: -1,
|
||||
r: rune(k[0]),
|
||||
}, true
|
||||
}
|
||||
|
||||
// We don't know what happened.
|
||||
return Key{}, false
|
||||
return Key{buttons: -1}, false
|
||||
}
|
||||
|
||||
// findAction will find 'action' using string 'v'
|
||||
@@ -339,6 +403,53 @@ func findAction(v string) (action func(*View, bool) bool) {
|
||||
return action
|
||||
}
|
||||
|
||||
func findMouseAction(v string) func(*View, bool, *tcell.EventMouse) bool {
|
||||
action, ok := mouseBindingActions[v]
|
||||
if !ok {
|
||||
// If the user seems to be binding a function that doesn't exist
|
||||
// We hope that it's a lua function that exists and bind it to that
|
||||
action = LuaFunctionMouseBinding(v)
|
||||
}
|
||||
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)
|
||||
@@ -349,25 +460,52 @@ 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 {
|
||||
actionNames = make([]string, 0, 0)
|
||||
} else {
|
||||
actionNames = append(actionNames[:0], actionNames[1:]...)
|
||||
return
|
||||
}
|
||||
actionNames = append(actionNames[:0], actionNames[1:]...)
|
||||
}
|
||||
actions := make([]func(*View, bool) bool, 0, len(actionNames))
|
||||
mouseActions := make([]func(*View, bool, *tcell.EventMouse) bool, 0, len(actionNames))
|
||||
for _, actionName := range actionNames {
|
||||
actions = append(actions, findAction(actionName))
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
bindings[key] = actions
|
||||
if len(actions) > 0 {
|
||||
// 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)
|
||||
mouseBindings[key] = mouseActions
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultBindings returns a map containing micro's default keybindings
|
||||
@@ -397,6 +535,8 @@ func DefaultBindings() map[string]string {
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Alt-{": "ParagraphPrevious",
|
||||
"Alt-}": "ParagraphNext",
|
||||
"Enter": "InsertNewline",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
@@ -426,7 +566,10 @@ func DefaultBindings() map[string]string {
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"CtrlG": "ToggleHelp",
|
||||
"Alt-g": "ToggleKeyMenu",
|
||||
"CtrlR": "ToggleRuler",
|
||||
"CtrlL": "JumpLine",
|
||||
"Delete": "Delete",
|
||||
@@ -436,22 +579,35 @@ func DefaultBindings() map[string]string {
|
||||
"CtrlW": "NextSplit",
|
||||
"CtrlU": "ToggleMacro",
|
||||
"CtrlJ": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
"Alt-b": "WordLeft",
|
||||
"Alt-a": "StartOfLine",
|
||||
"Alt-e": "EndOfLine",
|
||||
"Alt-p": "CursorUp",
|
||||
"Alt-n": "CursorDown",
|
||||
// "Alt-p": "CursorUp",
|
||||
// "Alt-n": "CursorDown",
|
||||
|
||||
// Integration with file managers
|
||||
"F1": "ToggleHelp",
|
||||
"F2": "Save",
|
||||
"F3": "Find",
|
||||
"F4": "Quit",
|
||||
"F7": "Find",
|
||||
"F10": "Quit",
|
||||
"Esc": "Escape",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "ScrollUp",
|
||||
"MouseWheelDown": "ScrollDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -28,7 +36,9 @@ type Buffer struct {
|
||||
// This stores all the text in the buffer as an array of lines
|
||||
*LineArray
|
||||
|
||||
Cursor Cursor
|
||||
Cursor Cursor
|
||||
cursors []*Cursor // for multiple cursors
|
||||
curCursor int // the current cursor
|
||||
|
||||
// Path to the file on disk
|
||||
Path string
|
||||
@@ -43,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{}
|
||||
}
|
||||
@@ -60,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
|
||||
}
|
||||
@@ -86,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
|
||||
@@ -107,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
|
||||
@@ -140,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)
|
||||
@@ -159,19 +236,31 @@ 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}
|
||||
|
||||
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 == "" {
|
||||
@@ -204,7 +293,7 @@ func (b *Buffer) UpdateRules() {
|
||||
}
|
||||
|
||||
ft := b.Settings["filetype"].(string)
|
||||
if ft == "Unknown" || ft == "" {
|
||||
if (ft == "Unknown" || ft == "") && !rehighlight {
|
||||
if highlight.MatchFiletype(ftdetect, b.Path, b.lines[0].data) {
|
||||
header := new(highlight.Header)
|
||||
header.FileType = file.FileType
|
||||
@@ -217,7 +306,7 @@ func (b *Buffer) UpdateRules() {
|
||||
rehighlight = true
|
||||
}
|
||||
} else {
|
||||
if file.FileType == ft {
|
||||
if file.FileType == ft && !rehighlight {
|
||||
header := new(highlight.Header)
|
||||
header.FileType = file.FileType
|
||||
header.FtDetect = ftdetect
|
||||
@@ -236,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 {
|
||||
@@ -305,6 +393,41 @@ 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++ {
|
||||
c1 := b.cursors[i]
|
||||
if c1 != nil {
|
||||
for j := 0; j < len(b.cursors); j++ {
|
||||
c2 := b.cursors[j]
|
||||
if c2 != nil && i != j && c1.Loc == c2.Loc {
|
||||
b.cursors[j] = nil
|
||||
}
|
||||
}
|
||||
cursors = append(cursors, c1)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Save saves the buffer to its default path
|
||||
func (b *Buffer) Save() error {
|
||||
return b.SaveAs(b.Path)
|
||||
@@ -328,7 +451,7 @@ func (b *Buffer) Serialize() error {
|
||||
b.ModTime,
|
||||
})
|
||||
}
|
||||
file.Close()
|
||||
err = file.Close()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -337,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) {
|
||||
@@ -356,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
|
||||
@@ -375,21 +554,13 @@ func (b *Buffer) SaveAsWithSudo(filename string) error {
|
||||
b.UpdateRules()
|
||||
b.Path = filename
|
||||
|
||||
// The user may have already used sudo in which case we won't need the password
|
||||
// It's a bit nicer for them if they don't have to enter the password every time
|
||||
_, err := RunShellCommand("sudo -v")
|
||||
needPassword := err != nil
|
||||
|
||||
// If we need the password, we have to close the screen and ask using the shell
|
||||
if needPassword {
|
||||
// Shut down the screen because we're going to interact directly with the shell
|
||||
screen.Fini()
|
||||
screen = nil
|
||||
}
|
||||
// Shut down the screen because we're going to interact directly with the shell
|
||||
screen.Fini()
|
||||
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
|
||||
@@ -403,13 +574,10 @@ func (b *Buffer) SaveAsWithSudo(filename string) error {
|
||||
|
||||
// Start the command
|
||||
cmd.Start()
|
||||
err = cmd.Wait()
|
||||
err := cmd.Wait()
|
||||
|
||||
// If we needed the password, we closed the screen, so we have to initialize it again
|
||||
if needPassword {
|
||||
// Start the screen back up
|
||||
InitScreen()
|
||||
}
|
||||
// Start the screen back up
|
||||
InitScreen()
|
||||
if err == nil {
|
||||
b.IsModified = false
|
||||
b.ModTime, _ = GetModTime(filename)
|
||||
@@ -418,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)
|
||||
@@ -447,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) {
|
||||
@@ -462,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)
|
||||
}
|
||||
@@ -533,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
|
||||
@@ -35,27 +32,32 @@ var commandActions map[string]func([]string)
|
||||
|
||||
func init() {
|
||||
commandActions = map[string]func([]string){
|
||||
"Set": Set,
|
||||
"SetLocal": SetLocal,
|
||||
"Show": Show,
|
||||
"Run": Run,
|
||||
"Bind": Bind,
|
||||
"Quit": Quit,
|
||||
"Save": Save,
|
||||
"Replace": Replace,
|
||||
"VSplit": VSplit,
|
||||
"HSplit": HSplit,
|
||||
"Tab": NewTab,
|
||||
"Help": Help,
|
||||
"Eval": Eval,
|
||||
"ToggleLog": ToggleLog,
|
||||
"Plugin": PluginCmd,
|
||||
"Reload": Reload,
|
||||
"Cd": Cd,
|
||||
"Pwd": Pwd,
|
||||
"Open": Open,
|
||||
"TabSwitch": TabSwitch,
|
||||
"MemUsage": MemUsage,
|
||||
"Set": Set,
|
||||
"SetLocal": SetLocal,
|
||||
"Show": Show,
|
||||
"ShowKey": ShowKey,
|
||||
"Run": Run,
|
||||
"Bind": Bind,
|
||||
"Quit": Quit,
|
||||
"Save": Save,
|
||||
"Replace": Replace,
|
||||
"ReplaceAll": ReplaceAll,
|
||||
"VSplit": VSplit,
|
||||
"HSplit": HSplit,
|
||||
"Tab": NewTab,
|
||||
"Help": Help,
|
||||
"Eval": Eval,
|
||||
"ToggleLog": ToggleLog,
|
||||
"Plugin": PluginCmd,
|
||||
"Reload": Reload,
|
||||
"Cd": Cd,
|
||||
"Pwd": Pwd,
|
||||
"Open": Open,
|
||||
"TabSwitch": TabSwitch,
|
||||
"Term": Term,
|
||||
"MemUsage": MemUsage,
|
||||
"Retab": Retab,
|
||||
"Raw": Raw,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,27 +91,54 @@ 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}},
|
||||
"show": {"Show", []Completion{OptionCompletion, NoCompletion}},
|
||||
"bind": {"Bind", []Completion{NoCompletion}},
|
||||
"run": {"Run", []Completion{NoCompletion}},
|
||||
"quit": {"Quit", []Completion{NoCompletion}},
|
||||
"save": {"Save", []Completion{NoCompletion}},
|
||||
"replace": {"Replace", []Completion{NoCompletion}},
|
||||
"vsplit": {"VSplit", []Completion{FileCompletion, NoCompletion}},
|
||||
"hsplit": {"HSplit", []Completion{FileCompletion, NoCompletion}},
|
||||
"tab": {"Tab", []Completion{FileCompletion, NoCompletion}},
|
||||
"help": {"Help", []Completion{HelpCompletion, NoCompletion}},
|
||||
"eval": {"Eval", []Completion{NoCompletion}},
|
||||
"log": {"ToggleLog", []Completion{NoCompletion}},
|
||||
"plugin": {"Plugin", []Completion{PluginCmdCompletion, PluginNameCompletion}},
|
||||
"reload": {"Reload", []Completion{NoCompletion}},
|
||||
"cd": {"Cd", []Completion{FileCompletion}},
|
||||
"pwd": {"Pwd", []Completion{NoCompletion}},
|
||||
"open": {"Open", []Completion{FileCompletion}},
|
||||
"tabswitch": {"TabSwitch", []Completion{NoCompletion}},
|
||||
"memusage": {"MemUsage", []Completion{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}},
|
||||
"save": {"Save", []Completion{NoCompletion}},
|
||||
"replace": {"Replace", []Completion{NoCompletion}},
|
||||
"replaceall": {"ReplaceAll", []Completion{NoCompletion}},
|
||||
"vsplit": {"VSplit", []Completion{FileCompletion, NoCompletion}},
|
||||
"hsplit": {"HSplit", []Completion{FileCompletion, NoCompletion}},
|
||||
"tab": {"Tab", []Completion{FileCompletion, NoCompletion}},
|
||||
"help": {"Help", []Completion{HelpCompletion, NoCompletion}},
|
||||
"eval": {"Eval", []Completion{NoCompletion}},
|
||||
"log": {"ToggleLog", []Completion{NoCompletion}},
|
||||
"plugin": {"Plugin", []Completion{PluginCmdCompletion, PluginNameCompletion}},
|
||||
"reload": {"Reload", []Completion{NoCompletion}},
|
||||
"cd": {"Cd", []Completion{FileCompletion}},
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,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")
|
||||
@@ -196,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 {
|
||||
@@ -205,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
|
||||
@@ -228,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
|
||||
@@ -272,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 {
|
||||
@@ -322,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)
|
||||
}
|
||||
@@ -352,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)
|
||||
}
|
||||
@@ -393,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))
|
||||
@@ -419,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()
|
||||
}
|
||||
}
|
||||
@@ -473,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 {
|
||||
@@ -485,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
|
||||
@@ -506,20 +545,37 @@ func Save(args []string) {
|
||||
|
||||
// Replace runs search and replace
|
||||
func Replace(args []string) {
|
||||
if len(args) < 2 {
|
||||
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
|
||||
}
|
||||
|
||||
var flags string
|
||||
if len(args) == 3 {
|
||||
// The user included some flags
|
||||
flags = args[2]
|
||||
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 {
|
||||
@@ -531,7 +587,25 @@ func Replace(args []string) {
|
||||
view := CurView()
|
||||
|
||||
found := 0
|
||||
if strings.Contains(flags, "c") {
|
||||
replaceAll := func() {
|
||||
var deltas []Delta
|
||||
for i := 0; i < view.Buf.LinesNum(); i++ {
|
||||
newText := regex.ReplaceAllFunc(view.Buf.lines[i].data, func(in []byte) []byte {
|
||||
found++
|
||||
return replaceBytes
|
||||
})
|
||||
|
||||
from := Loc{0, i}
|
||||
to := Loc{utf8.RuneCount(view.Buf.lines[i].data), i}
|
||||
|
||||
deltas = append(deltas, Delta{string(newText), from, to})
|
||||
}
|
||||
view.Buf.MultipleReplace(deltas)
|
||||
}
|
||||
|
||||
if all {
|
||||
replaceAll()
|
||||
} else {
|
||||
for {
|
||||
// The 'check' flag was used
|
||||
Search(search, view, true)
|
||||
@@ -540,7 +614,7 @@ func Replace(args []string) {
|
||||
}
|
||||
view.Relocate()
|
||||
RedrawAll()
|
||||
choice, canceled := messenger.YesNoPrompt("Perform replacement? (y,n)")
|
||||
choice, canceled := messenger.LetterPrompt("Perform replacement? (y,n,a)", 'y', 'n', 'a')
|
||||
if canceled {
|
||||
if view.Cursor.HasSelection() {
|
||||
view.Cursor.Loc = view.Cursor.CurSelection[0]
|
||||
@@ -548,42 +622,27 @@ func Replace(args []string) {
|
||||
}
|
||||
messenger.Reset()
|
||||
break
|
||||
}
|
||||
if choice {
|
||||
} else if choice == 'a' {
|
||||
if view.Cursor.HasSelection() {
|
||||
view.Cursor.Loc = view.Cursor.CurSelection[0]
|
||||
view.Cursor.ResetSelection()
|
||||
}
|
||||
messenger.Reset()
|
||||
replaceAll()
|
||||
break
|
||||
} else if choice == 'y' {
|
||||
view.Cursor.DeleteSelection()
|
||||
view.Buf.Insert(view.Cursor.Loc, replace)
|
||||
view.Cursor.ResetSelection()
|
||||
messenger.Reset()
|
||||
found++
|
||||
}
|
||||
if view.Cursor.HasSelection() {
|
||||
searchStart = view.Cursor.CurSelection[1]
|
||||
} else {
|
||||
if view.Cursor.HasSelection() {
|
||||
searchStart = ToCharPos(view.Cursor.CurSelection[1], view.Buf)
|
||||
} else {
|
||||
searchStart = ToCharPos(view.Cursor.Loc, view.Buf)
|
||||
}
|
||||
continue
|
||||
searchStart = view.Cursor.Loc
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// var deltas []Delta
|
||||
for i := 0; i < view.Buf.LinesNum(); i++ {
|
||||
// view.Buf.lines[i].data = regex.ReplaceAll(view.Buf.lines[i].data, []byte(replace))
|
||||
for {
|
||||
m := regex.FindIndex(view.Buf.lines[i].data)
|
||||
|
||||
if m != nil {
|
||||
from := Loc{m[0], i}
|
||||
to := Loc{m[1], i}
|
||||
|
||||
// deltas = append(deltas, Delta{replace, from, to})
|
||||
view.Buf.Replace(from, to, replace)
|
||||
found++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// view.Buf.MultipleReplace(deltas)
|
||||
}
|
||||
view.Cursor.Relocate()
|
||||
|
||||
@@ -596,93 +655,33 @@ func Replace(args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// ReplaceAll replaces search term all at once
|
||||
func ReplaceAll(args []string) {
|
||||
// aliased to Replace command
|
||||
Replace(append(args, "-a"))
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package main
|
||||
|
||||
import "github.com/zyedidia/clipboard"
|
||||
import (
|
||||
"github.com/zyedidia/clipboard"
|
||||
)
|
||||
|
||||
// The Cursor struct stores the location of the cursor in the view
|
||||
// The complicated part about the cursor is storing its location.
|
||||
@@ -21,15 +23,27 @@ type Cursor struct {
|
||||
// This is used for line and word selection where it is necessary
|
||||
// to know what the original selection was
|
||||
OrigSelection [2]Loc
|
||||
|
||||
// Which cursor index is this (for multiple cursors)
|
||||
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) {
|
||||
@@ -146,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
|
||||
@@ -178,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])
|
||||
@@ -245,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)
|
||||
@@ -275,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
|
||||
@@ -289,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
|
||||
@@ -315,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))
|
||||
@@ -334,11 +366,25 @@ func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
|
||||
func (c *Cursor) GetVisualX() int {
|
||||
runes := []rune(c.buf.Line(c.Y))
|
||||
tabSize := int(c.buf.Settings["tabsize"].(float64))
|
||||
if c.X > len(runes) {
|
||||
c.X = len(runes) - 1
|
||||
}
|
||||
|
||||
if c.X < 0 {
|
||||
c.X = 0
|
||||
}
|
||||
|
||||
return StringWidth(string(runes[:c.X]), tabSize)
|
||||
}
|
||||
|
||||
// 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
|
||||
// StoreVisualX stores the current visual x value in the cursor
|
||||
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
|
||||
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]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,30 +103,65 @@ func (eh *EventHandler) ApplyDiff(new string) {
|
||||
// Insert creates an insert text event and executes it
|
||||
func (eh *EventHandler) Insert(start Loc, text string) {
|
||||
e := &TextEvent{
|
||||
C: eh.buf.Cursor,
|
||||
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)
|
||||
e.Deltas[0].End = start.Move(Count(text), eh.buf)
|
||||
end := e.Deltas[0].End
|
||||
|
||||
for _, c := range eh.buf.cursors {
|
||||
move := func(loc Loc) Loc {
|
||||
if start.Y != end.Y && loc.GreaterThan(start) {
|
||||
loc.Y += end.Y - start.Y
|
||||
} else if loc.Y == start.Y && loc.GreaterEqual(start) {
|
||||
loc = loc.Move(Count(text), eh.buf)
|
||||
}
|
||||
return loc
|
||||
}
|
||||
c.Loc = move(c.Loc)
|
||||
c.CurSelection[0] = move(c.CurSelection[0])
|
||||
c.CurSelection[1] = move(c.CurSelection[1])
|
||||
c.OrigSelection[0] = move(c.OrigSelection[0])
|
||||
c.OrigSelection[1] = move(c.OrigSelection[1])
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
}
|
||||
|
||||
// Remove creates a remove text event and executes it
|
||||
func (eh *EventHandler) Remove(start, end Loc) {
|
||||
e := &TextEvent{
|
||||
C: eh.buf.Cursor,
|
||||
C: *eh.buf.cursors[eh.buf.curCursor],
|
||||
EventType: TextEventRemove,
|
||||
Deltas: []Delta{Delta{"", start, end}},
|
||||
Deltas: []Delta{{"", start, end}},
|
||||
Time: time.Now(),
|
||||
}
|
||||
eh.Execute(e)
|
||||
|
||||
for _, c := range eh.buf.cursors {
|
||||
move := func(loc Loc) Loc {
|
||||
if start.Y != end.Y && loc.GreaterThan(end) {
|
||||
loc.Y -= end.Y - start.Y
|
||||
} else if loc.Y == end.Y && loc.GreaterEqual(end) {
|
||||
loc = loc.Move(-Diff(start, end, eh.buf), eh.buf)
|
||||
}
|
||||
return loc
|
||||
}
|
||||
c.Loc = move(c.Loc)
|
||||
c.CurSelection[0] = move(c.CurSelection[0])
|
||||
c.CurSelection[1] = move(c.CurSelection[1])
|
||||
c.OrigSelection[0] = move(c.OrigSelection[0])
|
||||
c.OrigSelection[1] = move(c.OrigSelection[1])
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
}
|
||||
|
||||
// MultipleReplace creates an multiple insertions executes them
|
||||
func (eh *EventHandler) MultipleReplace(deltas []Delta) {
|
||||
e := &TextEvent{
|
||||
C: eh.buf.Cursor,
|
||||
C: *eh.buf.cursors[eh.buf.curCursor],
|
||||
EventType: TextEventReplace,
|
||||
Deltas: deltas,
|
||||
Time: time.Now(),
|
||||
@@ -195,8 +236,12 @@ func (eh *EventHandler) UndoOneEvent() {
|
||||
|
||||
// Set the cursor in the right place
|
||||
teCursor := t.C
|
||||
t.C = eh.buf.Cursor
|
||||
eh.buf.Cursor.Goto(teCursor)
|
||||
if teCursor.Num >= 0 && teCursor.Num < len(eh.buf.cursors) {
|
||||
t.C = *eh.buf.cursors[teCursor.Num]
|
||||
eh.buf.cursors[teCursor.Num].Goto(teCursor)
|
||||
} else {
|
||||
teCursor.Num = -1
|
||||
}
|
||||
|
||||
// Push it to the redo stack
|
||||
eh.RedoStack.Push(t)
|
||||
@@ -238,8 +283,12 @@ func (eh *EventHandler) RedoOneEvent() {
|
||||
UndoTextEvent(t, eh.buf)
|
||||
|
||||
teCursor := t.C
|
||||
t.C = eh.buf.Cursor
|
||||
eh.buf.Cursor.Goto(teCursor)
|
||||
if teCursor.Num >= 0 && teCursor.Num < len(eh.buf.cursors) {
|
||||
t.C = *eh.buf.cursors[teCursor.Num]
|
||||
eh.buf.cursors[teCursor.Num].Goto(teCursor)
|
||||
} else {
|
||||
teCursor.Num = -1
|
||||
}
|
||||
|
||||
eh.UndoStack.Push(t)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package highlight
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
@@ -82,6 +83,16 @@ func init() {
|
||||
}
|
||||
|
||||
func ParseFtDetect(file *File) (r [2]*regexp.Regexp, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
err, ok = r.(error)
|
||||
if !ok {
|
||||
err = fmt.Errorf("pkg: %v", r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
rules := file.yamlSrc
|
||||
|
||||
loaded := 0
|
||||
@@ -112,14 +123,22 @@ func ParseFtDetect(file *File) (r [2]*regexp.Regexp, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if loaded == 0 {
|
||||
return r, errors.New("No detect regexes found")
|
||||
}
|
||||
|
||||
return r, err
|
||||
}
|
||||
|
||||
func ParseFile(input []byte) (f *File, err error) {
|
||||
// This is just so if we have an error, we can exit cleanly and return the parse error to the user
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = e.(error)
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
err, ok = r.(error)
|
||||
if !ok {
|
||||
err = fmt.Errorf("pkg: %v", r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -147,8 +166,12 @@ func ParseFile(input []byte) (f *File, err error) {
|
||||
func ParseDef(f *File, header *Header) (s *Def, err error) {
|
||||
// This is just so if we have an error, we can exit cleanly and return the parse error to the user
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = e.(error)
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
err, ok = r.(error)
|
||||
if !ok {
|
||||
err = fmt.Errorf("pkg: %v", r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -211,8 +234,17 @@ func resolveIncludesInRegion(files []*File, region *region) {
|
||||
}
|
||||
}
|
||||
|
||||
func parseRules(input []interface{}, curRegion *region) (*rules, error) {
|
||||
rules := new(rules)
|
||||
func parseRules(input []interface{}, curRegion *region) (ru *rules, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
err, ok = r.(error)
|
||||
if !ok {
|
||||
err = fmt.Errorf("pkg: %v", r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
ru = new(rules)
|
||||
|
||||
for _, v := range input {
|
||||
rule := v.(map[interface{}]interface{})
|
||||
@@ -222,7 +254,7 @@ func parseRules(input []interface{}, curRegion *region) (*rules, error) {
|
||||
switch object := val.(type) {
|
||||
case string:
|
||||
if k == "include" {
|
||||
rules.includes = append(rules.includes, object)
|
||||
ru.includes = append(ru.includes, object)
|
||||
} else {
|
||||
// Pattern
|
||||
r, err := regexp.Compile(object)
|
||||
@@ -236,7 +268,7 @@ func parseRules(input []interface{}, curRegion *region) (*rules, error) {
|
||||
Groups[groupStr] = numGroups
|
||||
}
|
||||
groupNum := Groups[groupStr]
|
||||
rules.patterns = append(rules.patterns, &pattern{groupNum, r})
|
||||
ru.patterns = append(ru.patterns, &pattern{groupNum, r})
|
||||
}
|
||||
case map[interface{}]interface{}:
|
||||
// region
|
||||
@@ -244,35 +276,43 @@ func parseRules(input []interface{}, curRegion *region) (*rules, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rules.regions = append(rules.regions, region)
|
||||
ru.regions = append(ru.regions, region)
|
||||
default:
|
||||
return nil, fmt.Errorf("Bad type %T", object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rules, nil
|
||||
return ru, nil
|
||||
}
|
||||
|
||||
func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegion *region) (*region, error) {
|
||||
var err error
|
||||
func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegion *region) (r *region, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
err, ok = r.(error)
|
||||
if !ok {
|
||||
err = fmt.Errorf("pkg: %v", r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
region := new(region)
|
||||
r = new(region)
|
||||
if _, ok := Groups[group]; !ok {
|
||||
numGroups++
|
||||
Groups[group] = numGroups
|
||||
}
|
||||
groupNum := Groups[group]
|
||||
region.group = groupNum
|
||||
region.parent = prevRegion
|
||||
r.group = groupNum
|
||||
r.parent = prevRegion
|
||||
|
||||
region.start, err = regexp.Compile(regionInfo["start"].(string))
|
||||
r.start, err = regexp.Compile(regionInfo["start"].(string))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
region.end, err = regexp.Compile(regionInfo["end"].(string))
|
||||
r.end, err = regexp.Compile(regionInfo["end"].(string))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -280,7 +320,7 @@ func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegio
|
||||
|
||||
// skip is optional
|
||||
if _, ok := regionInfo["skip"]; ok {
|
||||
region.skip, err = regexp.Compile(regionInfo["skip"].(string))
|
||||
r.skip, err = regexp.Compile(regionInfo["skip"].(string))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -295,20 +335,20 @@ func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegio
|
||||
Groups[groupStr] = numGroups
|
||||
}
|
||||
groupNum := Groups[groupStr]
|
||||
region.limitGroup = groupNum
|
||||
r.limitGroup = groupNum
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
region.limitGroup = region.group
|
||||
r.limitGroup = r.group
|
||||
}
|
||||
|
||||
region.rules, err = parseRules(regionInfo["rules"].([]interface{}), region)
|
||||
r.rules, err = parseRules(regionInfo["rules"].([]interface{}), r)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return region, nil
|
||||
return r, 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,29 @@ 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 {
|
||||
return a.X - b.X
|
||||
}
|
||||
return b.X - a.X
|
||||
}
|
||||
|
||||
// Make sure a is guaranteed to be less than b
|
||||
if b.LessThan(a) {
|
||||
a, b = b, a
|
||||
}
|
||||
|
||||
loc := 0
|
||||
for i := a.Y + 1; i < b.Y; i++ {
|
||||
// + 1 for the newline
|
||||
loc += Count(buf.Line(i)) + 1
|
||||
}
|
||||
loc += Count(buf.Line(a.Y)) - a.X + b.X + 1
|
||||
return loc
|
||||
}
|
||||
|
||||
// LessThan returns true if b is smaller
|
||||
func (l Loc) LessThan(b Loc) bool {
|
||||
if l.Y < b.Y {
|
||||
|
||||
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,11 +3,14 @@ package main
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/zyedidia/clipboard"
|
||||
"github.com/zyedidia/micro/cmd/micro/shellwords"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -69,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()
|
||||
}
|
||||
@@ -218,6 +223,7 @@ func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool)
|
||||
}
|
||||
}
|
||||
|
||||
// Completion represents a type of completion
|
||||
type Completion int
|
||||
|
||||
const (
|
||||
@@ -228,6 +234,7 @@ const (
|
||||
OptionCompletion
|
||||
PluginCmdCompletion
|
||||
PluginNameCompletion
|
||||
OptionValueCompletion
|
||||
)
|
||||
|
||||
// Prompt sends the user a message and waits for a response to be typed in
|
||||
@@ -254,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:
|
||||
@@ -267,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 {
|
||||
@@ -293,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 {
|
||||
@@ -305,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)
|
||||
}
|
||||
}
|
||||
@@ -315,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()
|
||||
@@ -331,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++
|
||||
@@ -463,8 +584,10 @@ func (m *Messenger) Display() {
|
||||
if m.hasMessage {
|
||||
if m.hasPrompt || globalSettings["infobar"].(bool) {
|
||||
runes := []rune(m.message + m.response)
|
||||
posx := 0
|
||||
for x := 0; x < len(runes); x++ {
|
||||
screen.SetContent(x, h-1, runes[x], nil, m.style)
|
||||
screen.SetContent(posx, h-1, runes[x], nil, m.style)
|
||||
posx += runewidth.RuneWidth(runes[x])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -475,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,21 +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.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)
|
||||
@@ -271,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()
|
||||
@@ -310,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()
|
||||
@@ -325,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)
|
||||
}
|
||||
|
||||
@@ -355,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))
|
||||
@@ -384,30 +457,33 @@ 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() {
|
||||
for {
|
||||
events <- screen.PollEvent()
|
||||
if screen != nil {
|
||||
events <- screen.PollEvent()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -433,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 {
|
||||
@@ -461,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 {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/tcell"
|
||||
"layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
@@ -60,6 +61,20 @@ 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)
|
||||
if err != nil {
|
||||
TermMessage(err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func unpack(old []string) []interface{} {
|
||||
new := make([]interface{}, len(old))
|
||||
for i, v := range old {
|
||||
@@ -103,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)
|
||||
}
|
||||
}
|
||||
@@ -119,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
|
||||
@@ -136,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
|
||||
}
|
||||
@@ -148,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))
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
@@ -11,7 +12,7 @@ var (
|
||||
lastSearch string
|
||||
|
||||
// Where should we start the search down from (or up from)
|
||||
searchStart int
|
||||
searchStart Loc
|
||||
|
||||
// Is there currently a search in progress
|
||||
searching bool
|
||||
@@ -43,7 +44,7 @@ func EndSearch() {
|
||||
}
|
||||
}
|
||||
|
||||
// exit the search mode, reset active search phrase, and clear status bar
|
||||
// ExitSearch exits the search mode, reset active search phrase, and clear status bar
|
||||
func ExitSearch(v *View) {
|
||||
lastSearch = ""
|
||||
searching = false
|
||||
@@ -63,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
|
||||
@@ -91,6 +97,66 @@ func HandleSearchEvent(event tcell.Event, v *View) {
|
||||
return
|
||||
}
|
||||
|
||||
func searchDown(r *regexp.Regexp, v *View, start, end Loc) bool {
|
||||
for i := start.Y; i <= end.Y; i++ {
|
||||
var l []byte
|
||||
var charPos int
|
||||
if i == start.Y {
|
||||
runes := []rune(string(v.Buf.lines[i].data))
|
||||
l = []byte(string(runes[start.X:]))
|
||||
charPos = start.X
|
||||
|
||||
if strings.Contains(r.String(), "^") && start.X != 0 {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
l = v.Buf.lines[i].data
|
||||
}
|
||||
|
||||
match := r.FindIndex(l)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func searchUp(r *regexp.Regexp, v *View, start, end Loc) bool {
|
||||
for i := start.Y; i >= end.Y; i-- {
|
||||
var l []byte
|
||||
if i == start.Y {
|
||||
runes := []rune(string(v.Buf.lines[i].data))
|
||||
l = []byte(string(runes[:start.X]))
|
||||
|
||||
if strings.Contains(r.String(), "$") && start.X != Count(string(l)) {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
l = v.Buf.lines[i].data
|
||||
}
|
||||
|
||||
match := r.FindIndex(l)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Search searches in the view for the given regex. The down bool
|
||||
// specifies whether it should search down from the searchStart position
|
||||
// or up from there
|
||||
@@ -98,15 +164,6 @@ func Search(searchStr string, v *View, down bool) {
|
||||
if searchStr == "" {
|
||||
return
|
||||
}
|
||||
var str string
|
||||
var charPos int
|
||||
text := v.Buf.String()
|
||||
if down {
|
||||
str = string([]rune(text)[searchStart:])
|
||||
charPos = searchStart
|
||||
} else {
|
||||
str = string([]rune(text)[:searchStart])
|
||||
}
|
||||
r, err := regexp.Compile(searchStr)
|
||||
if v.Buf.Settings["ignorecase"].(bool) {
|
||||
r, err = regexp.Compile("(?i)" + searchStr)
|
||||
@@ -114,37 +171,20 @@ func Search(searchStr string, v *View, down bool) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
matches := r.FindAllStringIndex(str, -1)
|
||||
var match []int
|
||||
if matches == nil {
|
||||
// Search the entire buffer now
|
||||
matches = r.FindAllStringIndex(text, -1)
|
||||
charPos = 0
|
||||
if matches == nil {
|
||||
v.Cursor.ResetSelection()
|
||||
return
|
||||
}
|
||||
|
||||
if !down {
|
||||
match = matches[len(matches)-1]
|
||||
} else {
|
||||
match = matches[0]
|
||||
var found bool
|
||||
if down {
|
||||
found = searchDown(r, v, searchStart, v.Buf.End())
|
||||
if !found {
|
||||
found = searchDown(r, v, v.Buf.Start(), searchStart)
|
||||
}
|
||||
str = text
|
||||
}
|
||||
|
||||
if !down {
|
||||
match = matches[len(matches)-1]
|
||||
} else {
|
||||
match = matches[0]
|
||||
found = searchUp(r, v, searchStart, v.Buf.Start())
|
||||
if !found {
|
||||
found = searchUp(r, v, v.Buf.End(), searchStart)
|
||||
}
|
||||
}
|
||||
|
||||
if match[0] == match[1] {
|
||||
return
|
||||
if !found {
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
|
||||
v.Cursor.SetSelectionStart(FromCharPos(charPos+runePos(match[0], str), v.Buf))
|
||||
v.Cursor.SetSelectionEnd(FromCharPos(charPos+runePos(match[1], str), v.Buf))
|
||||
v.Cursor.Loc = v.Cursor.CurSelection[1]
|
||||
lastSearch = searchStr
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -36,11 +84,7 @@ func NumOccurrences(s string, c byte) int {
|
||||
|
||||
// Spaces returns a string with n spaces
|
||||
func Spaces(n int) string {
|
||||
var str string
|
||||
for i := 0; i < n; i++ {
|
||||
str += " "
|
||||
}
|
||||
return str
|
||||
return strings.Repeat(" ", n)
|
||||
}
|
||||
|
||||
// Min takes the min of two ints
|
||||
@@ -59,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
|
||||
@@ -133,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
|
||||
@@ -249,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]
|
||||
|
||||
@@ -277,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/blang/semver
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/blang/semver
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/blang/semver added at 4a1e882c79
1
cmd/micro/vendor/github.com/dustin/go-humanize
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/dustin/go-humanize
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/dustin/go-humanize added at 259d2a102b
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/gdamore/encoding
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/gdamore/encoding
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/gdamore/encoding added at b23993cbb6
1
cmd/micro/vendor/github.com/go-errors/errors
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/go-errors/errors
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/go-errors/errors added at 8fa88b06e5
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/lucasb-eyer/go-colorful
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/lucasb-eyer/go-colorful
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/lucasb-eyer/go-colorful added at c900de9dbb
1
cmd/micro/vendor/github.com/mattn/go-isatty
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/mattn/go-isatty
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/mattn/go-isatty added at fc9e8d8ef4
1
cmd/micro/vendor/github.com/mattn/go-runewidth
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/mattn/go-runewidth
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/mattn/go-runewidth added at 97311d9f77
1
cmd/micro/vendor/github.com/mitchellh/go-homedir
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/mitchellh/go-homedir
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/mitchellh/go-homedir added at b8bc1bf767
1
cmd/micro/vendor/github.com/sergi/go-diff
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/sergi/go-diff
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/sergi/go-diff added at feef008d51
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
1
cmd/micro/vendor/github.com/yuin/gopher-lua
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/yuin/gopher-lua
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/yuin/gopher-lua added at b402f3114e
1
cmd/micro/vendor/github.com/zyedidia/clipboard
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/zyedidia/clipboard
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/zyedidia/clipboard added at 4611e809d8
1
cmd/micro/vendor/github.com/zyedidia/glob
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/zyedidia/glob
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/zyedidia/glob added at dd4023a66d
1
cmd/micro/vendor/github.com/zyedidia/poller
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/zyedidia/poller
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/zyedidia/poller added at ab09682913
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
1
cmd/micro/vendor/github.com/zyedidia/tcell
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/zyedidia/tcell
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/zyedidia/tcell added at 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
cmd/micro/vendor/golang.org/x/net
generated
vendored
Submodule
1
cmd/micro/vendor/golang.org/x/net
generated
vendored
Submodule
Submodule cmd/micro/vendor/golang.org/x/net added at 1a68b1313c
1
cmd/micro/vendor/golang.org/x/text
generated
vendored
Submodule
1
cmd/micro/vendor/golang.org/x/text
generated
vendored
Submodule
Submodule cmd/micro/vendor/golang.org/x/text added at 210eee5cf7
1
cmd/micro/vendor/gopkg.in/yaml.v2
generated
vendored
Submodule
1
cmd/micro/vendor/gopkg.in/yaml.v2
generated
vendored
Submodule
Submodule cmd/micro/vendor/gopkg.in/yaml.v2 added at cd8b52f826
1
cmd/micro/vendor/layeh.com/gopher-luar
generated
vendored
Submodule
1
cmd/micro/vendor/layeh.com/gopher-luar
generated
vendored
Submodule
Submodule cmd/micro/vendor/layeh.com/gopher-luar added at 16281577df
@@ -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 {
|
||||
@@ -169,7 +206,7 @@ func (v *View) paste(clip string) {
|
||||
}
|
||||
clip = strings.Replace(clip, "\n", "\n"+leadingWS, -1)
|
||||
v.Buf.Insert(v.Cursor.Loc, clip)
|
||||
v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
|
||||
// v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
|
||||
v.freshClip = false
|
||||
messenger.Message("Pasted clipboard")
|
||||
}
|
||||
@@ -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,8 +503,69 @@ 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)
|
||||
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) {
|
||||
readonlyBindingsResult = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !readonlyBindingsResult {
|
||||
// call the key binding
|
||||
relocate = action(curv, true) || relocate
|
||||
// Macro
|
||||
if funcName != "ToggleMacro" && funcName != "PlayMacro" {
|
||||
if recordingMacro {
|
||||
curMacro = append(curMacro, action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return relocate
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -459,149 +573,138 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
v.Buf.CheckModTime()
|
||||
|
||||
switch e := event.(type) {
|
||||
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
|
||||
readonlyBindingsList := []string{"Delete", "Insert", "Backspace", "Cut", "Play", "Paste", "Move", "Add", "DuplicateLine", "Macro"}
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
if e.Modifiers() == key.modifiers {
|
||||
relocate = false
|
||||
isBinding = true
|
||||
for _, action := range actions {
|
||||
readonlyBindingsResult := false
|
||||
funcName := ShortFuncName(action)
|
||||
if v.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) {
|
||||
readonlyBindingsResult = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !readonlyBindingsResult {
|
||||
// call the key binding
|
||||
relocate = action(v, true) || relocate
|
||||
// Macro
|
||||
if funcName != "ToggleMacro" && funcName != "PlayMacro" {
|
||||
if recordingMacro {
|
||||
curMacro = append(curMacro, action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
relocate = v.ExecuteActions(actions) || relocate
|
||||
}
|
||||
v.SetCursor(&v.Buf.Cursor)
|
||||
v.Buf.MergeCursors()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
// Insert a character
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.DeleteSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
|
||||
v.Cursor.Right()
|
||||
|
||||
for pl := range loadedPlugins {
|
||||
_, err := Call(pl+".onRune", string(e.Rune()), v)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
|
||||
TermMessage(err)
|
||||
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
|
||||
for key, actions := range bindings {
|
||||
if e.Key() == key.keyCode {
|
||||
if e.Key() == tcell.KeyRune {
|
||||
if e.Rune() != key.r {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if recordingMacro {
|
||||
curMacro = append(curMacro, e.Rune())
|
||||
if e.Modifiers() == key.modifiers {
|
||||
for _, c := range v.Buf.cursors {
|
||||
ok := v.SetCursor(c)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
relocate = false
|
||||
isBinding = true
|
||||
relocate = v.ExecuteActions(actions) || relocate
|
||||
}
|
||||
v.SetCursor(&v.Buf.Cursor)
|
||||
v.Buf.MergeCursors()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
for _, c := range v.Buf.cursors {
|
||||
v.SetCursor(c)
|
||||
|
||||
// Insert a character
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.DeleteSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
|
||||
TermMessage(err)
|
||||
}
|
||||
}
|
||||
|
||||
if recordingMacro {
|
||||
curMacro = append(curMacro, e.Rune())
|
||||
}
|
||||
}
|
||||
v.SetCursor(&v.Buf.Cursor)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
v.paste(e.Text())
|
||||
for _, c := range v.Buf.cursors {
|
||||
v.SetCursor(c)
|
||||
v.paste(e.Text())
|
||||
}
|
||||
v.SetCursor(&v.Buf.Cursor)
|
||||
|
||||
PostActionCall("Paste", v)
|
||||
}
|
||||
case *tcell.EventMouse:
|
||||
x, y := e.Position()
|
||||
x -= v.lineNumOffset - v.leftCol + v.x
|
||||
y += v.Topline - v.y
|
||||
// Don't relocate for mouse events
|
||||
relocate = false
|
||||
|
||||
button := e.Buttons()
|
||||
|
||||
switch button {
|
||||
case tcell.Button1:
|
||||
// Left click
|
||||
if v.mouseReleased {
|
||||
v.MoveToMouseClick(x, y)
|
||||
if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold {
|
||||
if v.doubleClick {
|
||||
// Triple click
|
||||
v.lastClickTime = time.Now()
|
||||
|
||||
v.tripleClick = true
|
||||
v.doubleClick = false
|
||||
|
||||
v.Cursor.SelectLine()
|
||||
v.Cursor.CopySelection("primary")
|
||||
} else {
|
||||
// Double click
|
||||
v.lastClickTime = time.Now()
|
||||
|
||||
v.doubleClick = true
|
||||
v.tripleClick = false
|
||||
|
||||
v.Cursor.SelectWord()
|
||||
v.Cursor.CopySelection("primary")
|
||||
for key, actions := range bindings {
|
||||
if button == key.buttons && e.Modifiers() == key.modifiers {
|
||||
for _, c := range v.Buf.cursors {
|
||||
ok := v.SetCursor(c)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
v.doubleClick = false
|
||||
v.tripleClick = false
|
||||
v.lastClickTime = time.Now()
|
||||
relocate = v.ExecuteActions(actions) || relocate
|
||||
}
|
||||
v.SetCursor(&v.Buf.Cursor)
|
||||
v.Buf.MergeCursors()
|
||||
}
|
||||
}
|
||||
|
||||
v.Cursor.OrigSelection[0] = v.Cursor.Loc
|
||||
v.Cursor.CurSelection[0] = v.Cursor.Loc
|
||||
v.Cursor.CurSelection[1] = v.Cursor.Loc
|
||||
}
|
||||
v.mouseReleased = false
|
||||
} else if !v.mouseReleased {
|
||||
v.MoveToMouseClick(x, y)
|
||||
if v.tripleClick {
|
||||
v.Cursor.AddLineToSelection()
|
||||
} else if v.doubleClick {
|
||||
v.Cursor.AddWordToSelection()
|
||||
} else {
|
||||
v.Cursor.SetSelectionEnd(v.Cursor.Loc)
|
||||
v.Cursor.CopySelection("primary")
|
||||
for key, actions := range mouseBindings {
|
||||
if button == key.buttons && e.Modifiers() == key.modifiers {
|
||||
for _, action := range actions {
|
||||
action(v, true, e)
|
||||
}
|
||||
}
|
||||
case tcell.Button2:
|
||||
// Check viewtype if readonly don't paste (readonly help and log view etc.)
|
||||
if v.Type.readonly == false {
|
||||
// Middle mouse button was clicked,
|
||||
// We should paste primary
|
||||
v.PastePrimary(true)
|
||||
}
|
||||
}
|
||||
|
||||
switch button {
|
||||
case tcell.ButtonNone:
|
||||
// Mouse event with no click
|
||||
if !v.mouseReleased {
|
||||
// Mouse was just released
|
||||
|
||||
x, y := e.Position()
|
||||
x -= v.lineNumOffset - v.leftCol + v.x
|
||||
y += v.Topline - v.y
|
||||
|
||||
// Relocating here isn't really necessary because the cursor will
|
||||
// be in the right place from the last mouse event
|
||||
// However, if we are running in a terminal that doesn't support mouse motion
|
||||
@@ -615,14 +718,6 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
}
|
||||
v.mouseReleased = true
|
||||
}
|
||||
case tcell.WheelUp:
|
||||
// Scroll up
|
||||
scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
|
||||
v.ScrollUp(scrollspeed)
|
||||
case tcell.WheelDown:
|
||||
// Scroll down
|
||||
scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
|
||||
v.ScrollDown(scrollspeed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -637,6 +732,10 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
}
|
||||
}
|
||||
|
||||
func (v *View) mainCursor() bool {
|
||||
return v.Buf.curCursor == len(v.Buf.cursors)-1
|
||||
}
|
||||
|
||||
// GutterMessage creates a message in this view's gutter
|
||||
func (v *View) GutterMessage(section string, lineN int, msg string, kind int) {
|
||||
lineN--
|
||||
@@ -685,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()
|
||||
}
|
||||
|
||||
@@ -755,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
|
||||
@@ -863,22 +968,20 @@ func (v *View) DisplayView() {
|
||||
}
|
||||
|
||||
charLoc := char.realLoc
|
||||
if v.Cursor.HasSelection() &&
|
||||
(charLoc.GreaterEqual(v.Cursor.CurSelection[0]) && charLoc.LessThan(v.Cursor.CurSelection[1]) ||
|
||||
charLoc.LessThan(v.Cursor.CurSelection[0]) && charLoc.GreaterEqual(v.Cursor.CurSelection[1])) {
|
||||
// The current character is selected
|
||||
lineStyle = defStyle.Reverse(true)
|
||||
for _, c := range v.Buf.cursors {
|
||||
v.SetCursor(c)
|
||||
if v.Cursor.HasSelection() &&
|
||||
(charLoc.GreaterEqual(v.Cursor.CurSelection[0]) && charLoc.LessThan(v.Cursor.CurSelection[1]) ||
|
||||
charLoc.LessThan(v.Cursor.CurSelection[0]) && charLoc.GreaterEqual(v.Cursor.CurSelection[1])) {
|
||||
// The current character is selected
|
||||
lineStyle = defStyle.Reverse(true)
|
||||
|
||||
if style, ok := colorscheme["selection"]; ok {
|
||||
lineStyle = style
|
||||
if style, ok := colorscheme["selection"]; ok {
|
||||
lineStyle = style
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
|
||||
v.Cursor.Y == char.realLoc.Y && v.Cursor.X == char.realLoc.X && !cursorSet {
|
||||
screen.ShowCursor(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y)
|
||||
cursorSet = true
|
||||
}
|
||||
v.SetCursor(&v.Buf.Cursor)
|
||||
|
||||
if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num &&
|
||||
!v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
|
||||
@@ -889,6 +992,16 @@ func (v *View) DisplayView() {
|
||||
|
||||
screen.SetContent(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, char.drawChar, nil, lineStyle)
|
||||
|
||||
for i, c := range v.Buf.cursors {
|
||||
v.SetCursor(c)
|
||||
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
|
||||
v.Cursor.Y == char.realLoc.Y && v.Cursor.X == char.realLoc.X && (!cursorSet || i != 0) {
|
||||
ShowMultiCursor(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, i)
|
||||
cursorSet = true
|
||||
}
|
||||
}
|
||||
v.SetCursor(&v.Buf.Cursor)
|
||||
|
||||
lastChar = char
|
||||
}
|
||||
}
|
||||
@@ -899,19 +1012,27 @@ func (v *View) DisplayView() {
|
||||
var cx, cy int
|
||||
if lastChar != nil {
|
||||
lastX = xOffset + lastChar.visualLoc.X + lastChar.width
|
||||
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
|
||||
v.Cursor.Y == lastChar.realLoc.Y && v.Cursor.X == lastChar.realLoc.X+1 {
|
||||
screen.ShowCursor(lastX, yOffset+lastChar.visualLoc.Y)
|
||||
cx, cy = lastX, yOffset+lastChar.visualLoc.Y
|
||||
for i, c := range v.Buf.cursors {
|
||||
v.SetCursor(c)
|
||||
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
|
||||
v.Cursor.Y == lastChar.realLoc.Y && v.Cursor.X == lastChar.realLoc.X+1 {
|
||||
ShowMultiCursor(lastX, yOffset+lastChar.visualLoc.Y, i)
|
||||
cx, cy = lastX, yOffset+lastChar.visualLoc.Y
|
||||
}
|
||||
}
|
||||
realLoc = Loc{lastChar.realLoc.X, realLineN}
|
||||
v.SetCursor(&v.Buf.Cursor)
|
||||
realLoc = Loc{lastChar.realLoc.X + 1, realLineN}
|
||||
visualLoc = Loc{lastX - xOffset, lastChar.visualLoc.Y}
|
||||
} else if len(line) == 0 {
|
||||
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
|
||||
v.Cursor.Y == realLineN {
|
||||
screen.ShowCursor(xOffset, yOffset+visualLineN)
|
||||
cx, cy = xOffset, yOffset+visualLineN
|
||||
for i, c := range v.Buf.cursors {
|
||||
v.SetCursor(c)
|
||||
if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
|
||||
v.Cursor.Y == realLineN {
|
||||
ShowMultiCursor(xOffset, yOffset+visualLineN, i)
|
||||
cx, cy = xOffset, yOffset+visualLineN
|
||||
}
|
||||
}
|
||||
v.SetCursor(&v.Buf.Cursor)
|
||||
lastX = xOffset
|
||||
realLoc = Loc{0, realLineN}
|
||||
visualLoc = Loc{0, visualLineN}
|
||||
@@ -931,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)
|
||||
@@ -953,6 +1074,18 @@ func (v *View) DisplayView() {
|
||||
}
|
||||
}
|
||||
|
||||
// ShowMultiCursor will display a cursor at a location
|
||||
// If i == 0 then the terminal cursor will be used
|
||||
// Otherwise a fake cursor will be drawn at the position
|
||||
func ShowMultiCursor(x, y, i int) {
|
||||
if i == 0 {
|
||||
screen.ShowCursor(x, y)
|
||||
} else {
|
||||
r, _, _, _ := screen.GetContent(x, y)
|
||||
screen.SetContent(x, y, r, nil, defStyle.Reverse(true))
|
||||
}
|
||||
}
|
||||
|
||||
// Display renders the view, the cursor, and statusline
|
||||
func (v *View) Display() {
|
||||
if globalSettings["termtitle"].(bool) {
|
||||
@@ -964,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>
|
||||
|
||||
27
runtime/colorschemes/darcula.micro
Normal file
27
runtime/colorschemes/darcula.micro
Normal file
@@ -0,0 +1,27 @@
|
||||
color-link default "#CCCCCC,#242424"
|
||||
color-link comment "#707070,#242424"
|
||||
color-link identifier "#FFC66D,#242424"
|
||||
color-link constant "#7A9EC2,#242424"
|
||||
color-link constant.string "#6A8759,#242424"
|
||||
color-link constant.string.char "#6A8759,#242424"
|
||||
color-link statement "#CC8242,#242424"
|
||||
color-link symbol "#CCCCCC,#242424"
|
||||
color-link preproc "#CC8242,#242424"
|
||||
color-link type "#CC8242,#242424"
|
||||
color-link special "#CC8242,#242424"
|
||||
color-link underlined "#D33682,#242424"
|
||||
color-link error "bold #CB4B16,#242424"
|
||||
color-link todo "bold #D33682,#242424"
|
||||
color-link statusline "#242424,#CCCCCC"
|
||||
color-link tabbar "#242424,#CCCCCC"
|
||||
color-link indent-char "#4F4F4F,#242424"
|
||||
color-link line-number "#666666,#242424"
|
||||
color-link current-line-number "#666666,#242424"
|
||||
color-link gutter-error "#CB4B16,#242424"
|
||||
color-link gutter-warning "#E6DB74,#242424"
|
||||
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"
|
||||
color-link symbol.tag "#AE81FF,#242424"
|
||||
@@ -1,4 +1,3 @@
|
||||
# This is the monokai colorscheme
|
||||
color-link default "#F8F8F2,#282828"
|
||||
color-link comment "#75715E,#282828"
|
||||
color-link identifier "#66D9EF,#282828"
|
||||
@@ -14,7 +13,7 @@ color-link underlined "#D33682,#282828"
|
||||
color-link error "bold #CB4B16,#282828"
|
||||
color-link todo "bold #D33682,#282828"
|
||||
color-link statusline "#282828,#F8F8F2"
|
||||
color-link tabbar "#282828,#f8f8f2"
|
||||
color-link tabbar "#282828,#F8F8F2"
|
||||
color-link indent-char "#505050,#282828"
|
||||
color-link line-number "#AAAAAA,#323232"
|
||||
color-link current-line-number "#AAAAAA,#282828"
|
||||
@@ -22,6 +21,7 @@ color-link gutter-error "#CB4B16,#282828"
|
||||
color-link gutter-warning "#E6DB74,#282828"
|
||||
color-link cursor-line "#323232"
|
||||
color-link color-column "#323232"
|
||||
#No extended types; plain brackets
|
||||
#No extended types; Plain brackets.
|
||||
color-link type.extended "default"
|
||||
color-link symbol.brackets "default"
|
||||
#color-link symbol.brackets "default"
|
||||
color-link symbol.tag "#AE81FF,#282828"
|
||||
|
||||
@@ -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.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user