mirror of
https://github.com/zyedidia/micro.git
synced 2026-04-02 07:59:48 +09:00
Compare commits
169 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1856891622 | ||
|
|
8a250f7d95 | ||
|
|
7a013f666e | ||
|
|
41a24e61d6 | ||
|
|
d953339a56 | ||
|
|
76e1d7a3a7 | ||
|
|
91b65001c9 | ||
|
|
aa74b1233c | ||
|
|
61baa73d70 | ||
|
|
efe343b37c | ||
|
|
cc8e9a7e06 | ||
|
|
d7f7d845b9 | ||
|
|
8f0418c9a8 | ||
|
|
71af765b4e | ||
|
|
c0f279ffe8 | ||
|
|
ae9bb763fb | ||
|
|
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 | ||
|
|
4365b66398 |
15
.gitmodules
vendored
15
.gitmodules
vendored
@@ -55,3 +55,18 @@
|
||||
[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
|
||||
|
||||
@@ -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
|
||||
================
|
||||
|
||||
@@ -1164,3 +1134,253 @@ 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.
|
||||
|
||||
1
Makefile
1
Makefile
@@ -41,6 +41,7 @@ runtime:
|
||||
go get -u github.com/jteeuwen/go-bindata/...
|
||||
$(GOBIN)/go-bindata -nometadata -o runtime.go runtime/...
|
||||
mv runtime.go cmd/micro
|
||||
gofmt -w cmd/micro/runtime.go
|
||||
|
||||
test:
|
||||
go test ./cmd/micro
|
||||
|
||||
17
README.md
17
README.md
@@ -20,6 +20,22 @@ To see more screenshots of micro, showcasing all of the default colorschemes, se
|
||||
|
||||
You can also check out the website for Micro at https://micro-editor.github.io.
|
||||
|
||||
# Table of Contents
|
||||
- [Features](#features)
|
||||
- [Installation](#installation)
|
||||
- [Prebuilt binaries](#prebuilt-binaries)
|
||||
- [Package Managers](#package-managers)
|
||||
- [Building from source](#building-from-source)
|
||||
- [MacOS terminal](#macos-terminal)
|
||||
- [Linux clipboard support](#linux-clipboard-support)
|
||||
- [Colors and syntax highlighting](#colors-and-syntax-highlighting)
|
||||
- [Plan9, Cygwin](#plan9-cygwin)
|
||||
- [Usage](#usage)
|
||||
- [Documentation and Help](#documentation-and-help)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
- - -
|
||||
|
||||
# Features
|
||||
|
||||
* Easy to use and to install
|
||||
@@ -30,6 +46,7 @@ You can also check out the website for Micro at https://micro-editor.github.io.
|
||||
* Sane defaults
|
||||
* You shouldn't have to configure much out of the box (and it is extremely easy to configure)
|
||||
* Splits and tabs
|
||||
* Nano-like menu to help you remember the keybindings
|
||||
* Extremely good mouse support
|
||||
* This means mouse dragging to create a selection, double click to select by word, and triple click to select by line
|
||||
* Cross platform (It should work on all the platforms Go runs on)
|
||||
|
||||
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 |
Binary file not shown.
|
Before Width: | Height: | Size: 289 KiB After Width: | Height: | Size: 68 KiB |
@@ -7,16 +7,13 @@
|
||||
.\" See \usr\share\doc\micro\LICENSE for more information.
|
||||
.TH micro 1 "2017-03-28"
|
||||
.SH NAME
|
||||
micro \- An intuitive and modern terminal text editor
|
||||
.
|
||||
micro \- A modern and intuitive terminal-based text editor
|
||||
.SH SYNOPSIS
|
||||
.B micro
|
||||
.RB []
|
||||
[
|
||||
.I "filename \&..."
|
||||
]
|
||||
.RB [OPTIONS]
|
||||
[FILE]\&...
|
||||
|
||||
.SH DESCRIPTION
|
||||
( Copied from the README file. )
|
||||
|
||||
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.
|
||||
@@ -25,19 +22,39 @@ As the name indicates, micro aims to be somewhat of a successor to the nano edit
|
||||
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
|
||||
|
||||
.SH OPTIONS
|
||||
.B \-v --version
|
||||
Displays the current version of micro and the git commit hash.
|
||||
.TP
|
||||
.SH ENVIRONMENT
|
||||
Micro's behaviour can be changed by setting environment variables, of which
|
||||
there is currently only one:
|
||||
.I MICRO_TRUE_COLOR
|
||||
.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
|
||||
|
||||
When MICRO_TRUE_COLOR 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_TRUE_COLOR 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 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
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/clipboard"
|
||||
@@ -434,7 +435,11 @@ func (v *View) StartOfLine(usePlugin bool) bool {
|
||||
|
||||
v.deselect(0)
|
||||
|
||||
v.Cursor.Start()
|
||||
if v.Cursor.X != 0 {
|
||||
v.Cursor.Start()
|
||||
} else {
|
||||
v.Cursor.StartOfText()
|
||||
}
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("StartOfLine", v)
|
||||
@@ -458,6 +463,20 @@ func (v *View) EndOfLine(usePlugin bool) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectLine selects the entire current line
|
||||
func (v *View) SelectLine(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("SelectLine", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
v.Cursor.SelectLine()
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("SelectLine", v)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectToStartOfLine selects to the start of the current line
|
||||
func (v *View) SelectToStartOfLine(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("SelectToStartOfLine", v) {
|
||||
@@ -543,6 +562,8 @@ func (v *View) ParagraphNext(usePlugin bool) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Retab changes all tabs to spaces or all spaces to tabs depending
|
||||
// on the user's settings
|
||||
func (v *View) Retab(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("Retab", v) {
|
||||
return false
|
||||
@@ -679,10 +700,14 @@ func (v *View) InsertNewline(usePlugin bool) bool {
|
||||
}
|
||||
|
||||
ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
|
||||
cx := v.Cursor.X
|
||||
v.Buf.Insert(v.Cursor.Loc, "\n")
|
||||
// v.Cursor.Right()
|
||||
|
||||
if v.Buf.Settings["autoindent"].(bool) {
|
||||
if cx < len(ws) {
|
||||
ws = ws[0:cx]
|
||||
}
|
||||
v.Buf.Insert(v.Cursor.Loc, ws)
|
||||
// for i := 0; i < len(ws); i++ {
|
||||
// v.Cursor.Right()
|
||||
@@ -722,9 +747,9 @@ func (v *View) Backspace(usePlugin bool) bool {
|
||||
// If the user is using spaces instead of tabs and they are deleting
|
||||
// whitespace at the start of the line, we should delete as if it's a
|
||||
// tab (tabSize number of spaces)
|
||||
lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
|
||||
lineStart := sliceEnd(v.Buf.LineBytes(v.Cursor.Y), v.Cursor.X)
|
||||
tabSize := int(v.Buf.Settings["tabsize"].(float64))
|
||||
if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
|
||||
if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && utf8.RuneCount(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 {
|
||||
loc := v.Cursor.Loc
|
||||
v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
|
||||
} else {
|
||||
@@ -809,13 +834,15 @@ func (v *View) IndentSelection(usePlugin bool) bool {
|
||||
end := v.Cursor.CurSelection[1]
|
||||
if end.Y < start.Y {
|
||||
start, end = end, start
|
||||
v.Cursor.SetSelectionStart(start)
|
||||
v.Cursor.SetSelectionEnd(end)
|
||||
}
|
||||
|
||||
startY := start.Y
|
||||
endY := end.Move(-1, v.Buf).Y
|
||||
endX := end.Move(-1, v.Buf).X
|
||||
tabsize := len(v.Buf.IndentString())
|
||||
for y := startY; y <= endY; y++ {
|
||||
tabsize := len(v.Buf.IndentString())
|
||||
v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
|
||||
if y == startY && start.X > 0 {
|
||||
v.Cursor.SetSelectionStart(start.Move(tabsize, v.Buf))
|
||||
@@ -869,6 +896,8 @@ func (v *View) OutdentSelection(usePlugin bool) bool {
|
||||
end := v.Cursor.CurSelection[1]
|
||||
if end.Y < start.Y {
|
||||
start, end = end, start
|
||||
v.Cursor.SetSelectionStart(start)
|
||||
v.Cursor.SetSelectionEnd(end)
|
||||
}
|
||||
|
||||
startY := start.Y
|
||||
@@ -922,7 +951,7 @@ func (v *View) SaveAll(usePlugin bool) bool {
|
||||
}
|
||||
|
||||
for _, t := range tabs {
|
||||
for _, v := range t.views {
|
||||
for _, v := range t.Views {
|
||||
v.Save(false)
|
||||
}
|
||||
}
|
||||
@@ -941,7 +970,7 @@ func (v *View) Save(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.Type.scratch == true {
|
||||
if v.Type.Scratch == true {
|
||||
// We can't save any view type with scratch set. eg help and log text
|
||||
return false
|
||||
}
|
||||
@@ -991,7 +1020,7 @@ func (v *View) saveToFile(filename string) {
|
||||
// SaveAs saves the buffer to disk with the given name
|
||||
func (v *View) SaveAs(usePlugin bool) bool {
|
||||
if v.mainCursor() {
|
||||
if usePlugin && !PreActionCall("Find", v) {
|
||||
if usePlugin && !PreActionCall("SaveAs", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1008,7 +1037,7 @@ func (v *View) SaveAs(usePlugin bool) bool {
|
||||
}
|
||||
|
||||
if usePlugin {
|
||||
PostActionCall("Find", v)
|
||||
PostActionCall("SaveAs", v)
|
||||
}
|
||||
}
|
||||
return false
|
||||
@@ -1190,9 +1219,9 @@ func (v *View) Cut(usePlugin bool) bool {
|
||||
return PostActionCall("Cut", v)
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return v.CutLine(usePlugin)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// DuplicateLine duplicates the current line or selection
|
||||
@@ -1351,6 +1380,27 @@ func (v *View) PastePrimary(usePlugin bool) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// JumpToMatchingBrace moves the cursor to the matching brace if it is
|
||||
// currently on a brace
|
||||
func (v *View) JumpToMatchingBrace(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("JumpToMatchingBrace", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, bp := range bracePairs {
|
||||
r := v.Cursor.RuneUnder(v.Cursor.X)
|
||||
if r == bp[0] || r == bp[1] {
|
||||
matchingBrace := v.Buf.FindMatchingBrace(bp, v.Cursor.Loc)
|
||||
v.Cursor.GotoLoc(matchingBrace)
|
||||
}
|
||||
}
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("JumpToMatchingBrace", v)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectAll selects the entire buffer
|
||||
func (v *View) SelectAll(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("SelectAll", v) {
|
||||
@@ -1465,6 +1515,42 @@ func (v *View) PageDown(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SelectPageUp selects up one page
|
||||
func (v *View) SelectPageUp(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("SelectPageUp", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !v.Cursor.HasSelection() {
|
||||
v.Cursor.OrigSelection[0] = v.Cursor.Loc
|
||||
}
|
||||
v.Cursor.UpN(v.Height)
|
||||
v.Cursor.SelectTo(v.Cursor.Loc)
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("SelectPageUp", v)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectPageDown selects down one page
|
||||
func (v *View) SelectPageDown(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("SelectPageDown", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !v.Cursor.HasSelection() {
|
||||
v.Cursor.OrigSelection[0] = v.Cursor.Loc
|
||||
}
|
||||
v.Cursor.DownN(v.Height)
|
||||
v.Cursor.SelectTo(v.Cursor.Loc)
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("SelectPageDown", v)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// CursorPageUp places the cursor a page up
|
||||
func (v *View) CursorPageUp(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("CursorPageUp", v) {
|
||||
@@ -1578,21 +1664,38 @@ func (v *View) JumpLine(usePlugin bool) bool {
|
||||
}
|
||||
|
||||
// Prompt for line number
|
||||
message := fmt.Sprintf("Jump to line (1 - %v) # ", v.Buf.NumLines)
|
||||
linestring, canceled := messenger.Prompt(message, "", "LineNumber", NoCompletion)
|
||||
message := fmt.Sprintf("Jump to line:col (1 - %v) # ", v.Buf.NumLines)
|
||||
input, canceled := messenger.Prompt(message, "", "LineNumber", NoCompletion)
|
||||
if canceled {
|
||||
return false
|
||||
}
|
||||
lineint, err := strconv.Atoi(linestring)
|
||||
lineint = lineint - 1 // fix offset
|
||||
if err != nil {
|
||||
messenger.Error(err) // return errors
|
||||
return false
|
||||
var lineInt int
|
||||
var colInt int
|
||||
var err error
|
||||
if strings.Contains(input, ":") {
|
||||
split := strings.Split(input, ":")
|
||||
lineInt, err = strconv.Atoi(split[0])
|
||||
if err != nil {
|
||||
messenger.Message("Invalid line number")
|
||||
return false
|
||||
}
|
||||
colInt, err = strconv.Atoi(split[1])
|
||||
if err != nil {
|
||||
messenger.Message("Invalid column number")
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
lineInt, err = strconv.Atoi(input)
|
||||
if err != nil {
|
||||
messenger.Message("Invalid line number")
|
||||
return false
|
||||
}
|
||||
}
|
||||
lineInt--
|
||||
// Move cursor and view if possible.
|
||||
if lineint < v.Buf.NumLines && lineint >= 0 {
|
||||
v.Cursor.X = 0
|
||||
v.Cursor.Y = lineint
|
||||
if lineInt < v.Buf.NumLines && lineInt >= 0 {
|
||||
v.Cursor.X = colInt
|
||||
v.Cursor.Y = lineInt
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("JumpLine", v)
|
||||
@@ -1697,6 +1800,22 @@ func (v *View) CommandMode(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ToggleOverwriteMode lets the user toggle the text overwrite mode
|
||||
func (v *View) ToggleOverwriteMode(usePlugin bool) bool {
|
||||
if v.mainCursor() {
|
||||
if usePlugin && !PreActionCall("ToggleOverwriteMode", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
v.isOverwriteMode = !v.isOverwriteMode
|
||||
|
||||
if usePlugin {
|
||||
return PostActionCall("ToggleOverwriteMode", v)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Escape leaves current mode
|
||||
func (v *View) Escape(usePlugin bool) bool {
|
||||
if v.mainCursor() {
|
||||
@@ -1725,12 +1844,12 @@ func (v *View) Quit(usePlugin bool) bool {
|
||||
// Make sure not to quit if there are unsaved changes
|
||||
if v.CanClose() {
|
||||
v.CloseBuffer()
|
||||
if len(tabs[curTab].views) > 1 {
|
||||
if len(tabs[curTab].Views) > 1 {
|
||||
v.splitNode.Delete()
|
||||
tabs[v.TabNum].Cleanup()
|
||||
tabs[v.TabNum].Resize()
|
||||
} else if len(tabs) > 1 {
|
||||
if len(tabs[v.TabNum].views) == 1 {
|
||||
if len(tabs[v.TabNum].Views) == 1 {
|
||||
tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
|
||||
for i, t := range tabs {
|
||||
t.SetNum(i)
|
||||
@@ -1769,7 +1888,7 @@ func (v *View) QuitAll(usePlugin bool) bool {
|
||||
|
||||
closeAll := true
|
||||
for _, tab := range tabs {
|
||||
for _, v := range tab.views {
|
||||
for _, v := range tab.Views {
|
||||
if !v.CanClose() {
|
||||
closeAll = false
|
||||
}
|
||||
@@ -1782,7 +1901,7 @@ func (v *View) QuitAll(usePlugin bool) bool {
|
||||
|
||||
if shouldQuit {
|
||||
for _, tab := range tabs {
|
||||
for _, v := range tab.views {
|
||||
for _, v := range tab.Views {
|
||||
v.CloseBuffer()
|
||||
}
|
||||
}
|
||||
@@ -1814,7 +1933,7 @@ func (v *View) AddTab(usePlugin bool) bool {
|
||||
curTab = len(tabs) - 1
|
||||
if len(tabs) == 2 {
|
||||
for _, t := range tabs {
|
||||
for _, v := range t.views {
|
||||
for _, v := range t.Views {
|
||||
v.ToggleTabbar()
|
||||
}
|
||||
}
|
||||
@@ -1907,8 +2026,8 @@ func (v *View) Unsplit(usePlugin bool) bool {
|
||||
}
|
||||
|
||||
curView := tabs[curTab].CurView
|
||||
for i := len(tabs[curTab].views) - 1; i >= 0; i-- {
|
||||
view := tabs[curTab].views[i]
|
||||
for i := len(tabs[curTab].Views) - 1; i >= 0; i-- {
|
||||
view := tabs[curTab].Views[i]
|
||||
if view != nil && view.Num != curView {
|
||||
view.Quit(true)
|
||||
// messenger.Message("Quit ", view.Buf.Path)
|
||||
@@ -1930,7 +2049,7 @@ func (v *View) NextSplit(usePlugin bool) bool {
|
||||
}
|
||||
|
||||
tab := tabs[curTab]
|
||||
if tab.CurView < len(tab.views)-1 {
|
||||
if tab.CurView < len(tab.Views)-1 {
|
||||
tab.CurView++
|
||||
} else {
|
||||
tab.CurView = 0
|
||||
@@ -1954,7 +2073,7 @@ func (v *View) PreviousSplit(usePlugin bool) bool {
|
||||
if tab.CurView > 0 {
|
||||
tab.CurView--
|
||||
} else {
|
||||
tab.CurView = len(tab.views) - 1
|
||||
tab.CurView = len(tab.Views) - 1
|
||||
}
|
||||
|
||||
if usePlugin {
|
||||
@@ -2065,6 +2184,54 @@ func (v *View) SpawnMultiCursor(usePlugin bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
|
||||
func (v *View) SpawnMultiCursorSelect(usePlugin bool) bool {
|
||||
if v.Cursor == &v.Buf.Cursor {
|
||||
if usePlugin && !PreActionCall("SpawnMultiCursorSelect", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Avoid cases where multiple cursors already exist, that would create problems
|
||||
if len(v.Buf.cursors) > 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
var startLine int
|
||||
var endLine int
|
||||
|
||||
a, b := v.Cursor.CurSelection[0].Y, v.Cursor.CurSelection[1].Y
|
||||
if a > b {
|
||||
startLine, endLine = b, a
|
||||
} else {
|
||||
startLine, endLine = a, b
|
||||
}
|
||||
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.ResetSelection()
|
||||
v.Cursor.GotoLoc(Loc{0, startLine})
|
||||
|
||||
for i := startLine; i <= endLine; i++ {
|
||||
c := &Cursor{
|
||||
buf: v.Buf,
|
||||
}
|
||||
c.GotoLoc(Loc{0, i})
|
||||
v.Buf.cursors = append(v.Buf.cursors, c)
|
||||
}
|
||||
v.Buf.MergeCursors()
|
||||
v.Buf.UpdateCursors()
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
if usePlugin {
|
||||
PostActionCall("SpawnMultiCursorSelect", v)
|
||||
}
|
||||
|
||||
messenger.Message("Added cursors from selection")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
|
||||
func (v *View) MouseMultiCursor(usePlugin bool, e *tcell.EventMouse) bool {
|
||||
if v.Cursor == &v.Buf.Cursor {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// +build android plan9 nacl windows
|
||||
// +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
|
||||
}
|
||||
|
||||
@@ -142,6 +142,7 @@ 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
|
||||
@@ -219,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) {
|
||||
@@ -232,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,14 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"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
|
||||
@@ -20,93 +23,99 @@ var mouseBindingActions = map[string]func(*View, bool, *tcell.EventMouse) bool{
|
||||
}
|
||||
|
||||
var bindingActions = map[string]func(*View, bool) bool{
|
||||
"CursorUp": (*View).CursorUp,
|
||||
"CursorDown": (*View).CursorDown,
|
||||
"CursorPageUp": (*View).CursorPageUp,
|
||||
"CursorPageDown": (*View).CursorPageDown,
|
||||
"CursorLeft": (*View).CursorLeft,
|
||||
"CursorRight": (*View).CursorRight,
|
||||
"CursorStart": (*View).CursorStart,
|
||||
"CursorEnd": (*View).CursorEnd,
|
||||
"SelectToStart": (*View).SelectToStart,
|
||||
"SelectToEnd": (*View).SelectToEnd,
|
||||
"SelectUp": (*View).SelectUp,
|
||||
"SelectDown": (*View).SelectDown,
|
||||
"SelectLeft": (*View).SelectLeft,
|
||||
"SelectRight": (*View).SelectRight,
|
||||
"WordRight": (*View).WordRight,
|
||||
"WordLeft": (*View).WordLeft,
|
||||
"SelectWordRight": (*View).SelectWordRight,
|
||||
"SelectWordLeft": (*View).SelectWordLeft,
|
||||
"DeleteWordRight": (*View).DeleteWordRight,
|
||||
"DeleteWordLeft": (*View).DeleteWordLeft,
|
||||
"SelectToStartOfLine": (*View).SelectToStartOfLine,
|
||||
"SelectToEndOfLine": (*View).SelectToEndOfLine,
|
||||
"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,
|
||||
"Escape": (*View).Escape,
|
||||
"Quit": (*View).Quit,
|
||||
"QuitAll": (*View).QuitAll,
|
||||
"AddTab": (*View).AddTab,
|
||||
"PreviousTab": (*View).PreviousTab,
|
||||
"NextTab": (*View).NextTab,
|
||||
"NextSplit": (*View).NextSplit,
|
||||
"PreviousSplit": (*View).PreviousSplit,
|
||||
"Unsplit": (*View).Unsplit,
|
||||
"VSplit": (*View).VSplitBinding,
|
||||
"HSplit": (*View).HSplitBinding,
|
||||
"ToggleMacro": (*View).ToggleMacro,
|
||||
"PlayMacro": (*View).PlayMacro,
|
||||
"Suspend": (*View).Suspend,
|
||||
"ScrollUp": (*View).ScrollUpAction,
|
||||
"ScrollDown": (*View).ScrollDownAction,
|
||||
"SpawnMultiCursor": (*View).SpawnMultiCursor,
|
||||
"RemoveMultiCursor": (*View).RemoveMultiCursor,
|
||||
"RemoveAllMultiCursors": (*View).RemoveAllMultiCursors,
|
||||
"SkipMultiCursor": (*View).SkipMultiCursor,
|
||||
"CursorUp": (*View).CursorUp,
|
||||
"CursorDown": (*View).CursorDown,
|
||||
"CursorPageUp": (*View).CursorPageUp,
|
||||
"CursorPageDown": (*View).CursorPageDown,
|
||||
"CursorLeft": (*View).CursorLeft,
|
||||
"CursorRight": (*View).CursorRight,
|
||||
"CursorStart": (*View).CursorStart,
|
||||
"CursorEnd": (*View).CursorEnd,
|
||||
"SelectToStart": (*View).SelectToStart,
|
||||
"SelectToEnd": (*View).SelectToEnd,
|
||||
"SelectUp": (*View).SelectUp,
|
||||
"SelectDown": (*View).SelectDown,
|
||||
"SelectLeft": (*View).SelectLeft,
|
||||
"SelectRight": (*View).SelectRight,
|
||||
"WordRight": (*View).WordRight,
|
||||
"WordLeft": (*View).WordLeft,
|
||||
"SelectWordRight": (*View).SelectWordRight,
|
||||
"SelectWordLeft": (*View).SelectWordLeft,
|
||||
"DeleteWordRight": (*View).DeleteWordRight,
|
||||
"DeleteWordLeft": (*View).DeleteWordLeft,
|
||||
"SelectLine": (*View).SelectLine,
|
||||
"SelectToStartOfLine": (*View).SelectToStartOfLine,
|
||||
"SelectToEndOfLine": (*View).SelectToEndOfLine,
|
||||
"ParagraphPrevious": (*View).ParagraphPrevious,
|
||||
"ParagraphNext": (*View).ParagraphNext,
|
||||
"InsertNewline": (*View).InsertNewline,
|
||||
"InsertSpace": (*View).InsertSpace,
|
||||
"Backspace": (*View).Backspace,
|
||||
"Delete": (*View).Delete,
|
||||
"InsertTab": (*View).InsertTab,
|
||||
"Save": (*View).Save,
|
||||
"SaveAll": (*View).SaveAll,
|
||||
"SaveAs": (*View).SaveAs,
|
||||
"Find": (*View).Find,
|
||||
"FindNext": (*View).FindNext,
|
||||
"FindPrevious": (*View).FindPrevious,
|
||||
"Center": (*View).Center,
|
||||
"Undo": (*View).Undo,
|
||||
"Redo": (*View).Redo,
|
||||
"Copy": (*View).Copy,
|
||||
"Cut": (*View).Cut,
|
||||
"CutLine": (*View).CutLine,
|
||||
"DuplicateLine": (*View).DuplicateLine,
|
||||
"DeleteLine": (*View).DeleteLine,
|
||||
"MoveLinesUp": (*View).MoveLinesUp,
|
||||
"MoveLinesDown": (*View).MoveLinesDown,
|
||||
"IndentSelection": (*View).IndentSelection,
|
||||
"OutdentSelection": (*View).OutdentSelection,
|
||||
"OutdentLine": (*View).OutdentLine,
|
||||
"Paste": (*View).Paste,
|
||||
"PastePrimary": (*View).PastePrimary,
|
||||
"SelectAll": (*View).SelectAll,
|
||||
"OpenFile": (*View).OpenFile,
|
||||
"Start": (*View).Start,
|
||||
"End": (*View).End,
|
||||
"PageUp": (*View).PageUp,
|
||||
"PageDown": (*View).PageDown,
|
||||
"SelectPageUp": (*View).SelectPageUp,
|
||||
"SelectPageDown": (*View).SelectPageDown,
|
||||
"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,
|
||||
@@ -248,6 +257,7 @@ var bindingKeys = map[string]tcell.Key{
|
||||
"Escape": tcell.KeyEscape,
|
||||
"Enter": tcell.KeyEnter,
|
||||
"Backspace": tcell.KeyBackspace2,
|
||||
"OldBackspace": tcell.KeyBackspace,
|
||||
|
||||
// I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
|
||||
"PgUp": tcell.KeyPgUp,
|
||||
@@ -266,6 +276,7 @@ type Key struct {
|
||||
// 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
|
||||
@@ -330,11 +341,16 @@ 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{
|
||||
@@ -400,6 +416,43 @@ func findMouseAction(v string) func(*View, bool, *tcell.EventMouse) bool {
|
||||
return action
|
||||
}
|
||||
|
||||
// TryBindKey tries to bind a key by writing to configDir/bindings.json
|
||||
// This function is unused for now
|
||||
func TryBindKey(k, v string) {
|
||||
filename := configDir + "/bindings.json"
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
TermMessage("Error reading bindings.json file: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
conflict := -1
|
||||
lines := strings.Split(string(input), "\n")
|
||||
for i, l := range lines {
|
||||
parts := strings.Split(l, ":")
|
||||
if len(parts) >= 2 {
|
||||
if strings.Contains(parts[0], k) {
|
||||
conflict = i
|
||||
TermMessage("Warning: Keybinding conflict:", k, " has been overwritten")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding := fmt.Sprintf(" \"%s\": \"%s\",", k, v)
|
||||
if conflict == -1 {
|
||||
lines = append([]string{lines[0], binding}, lines[conflict:]...)
|
||||
} else {
|
||||
lines = append(append(lines[:conflict], binding), lines[conflict+1:]...)
|
||||
}
|
||||
txt := strings.Join(lines, "\n")
|
||||
err = ioutil.WriteFile(filename, []byte(txt), 0644)
|
||||
if err != nil {
|
||||
TermMessage("Error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BindKey takes a key and an action and binds the two together
|
||||
func BindKey(k, v string) {
|
||||
key, ok := findKey(k)
|
||||
@@ -424,6 +477,7 @@ func BindKey(k, v string) {
|
||||
if actionNames[0] == "UnbindKey" {
|
||||
delete(bindings, key)
|
||||
delete(mouseBindings, key)
|
||||
delete(bindingsStr, k)
|
||||
if len(actionNames) == 1 {
|
||||
return
|
||||
}
|
||||
@@ -434,6 +488,12 @@ func BindKey(k, v string) {
|
||||
for _, actionName := range actionNames {
|
||||
if strings.HasPrefix(actionName, "Mouse") {
|
||||
mouseActions = append(mouseActions, findMouseAction(actionName))
|
||||
} else if strings.HasPrefix(actionName, "command:") {
|
||||
cmd := strings.SplitN(actionName, ":", 2)[1]
|
||||
actions = append(actions, CommandAction(cmd))
|
||||
} else if strings.HasPrefix(actionName, "command-edit:") {
|
||||
cmd := strings.SplitN(actionName, ":", 2)[1]
|
||||
actions = append(actions, CommandEditAction(cmd))
|
||||
} else {
|
||||
actions = append(actions, findAction(actionName))
|
||||
}
|
||||
@@ -443,6 +503,7 @@ func BindKey(k, v string) {
|
||||
// Can't have a binding be both mouse and normal
|
||||
delete(mouseBindings, key)
|
||||
bindings[key] = actions
|
||||
bindingsStr[k] = v
|
||||
} else if len(mouseActions) > 0 {
|
||||
// Can't have a binding be both mouse and normal
|
||||
delete(bindings, key)
|
||||
@@ -521,6 +582,7 @@ func DefaultBindings() map[string]string {
|
||||
"CtrlW": "NextSplit",
|
||||
"CtrlU": "ToggleMacro",
|
||||
"CtrlJ": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
@@ -546,6 +608,7 @@ func DefaultBindings() map[string]string {
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/micro/cmd/micro/highlight"
|
||||
)
|
||||
|
||||
const LargeFileThreshold = 50000
|
||||
|
||||
var (
|
||||
// 0 - no line type detected
|
||||
// 1 - lf detected
|
||||
@@ -52,13 +56,14 @@ 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
|
||||
origHash [md5.Size]byte
|
||||
|
||||
// Buffer local settings
|
||||
Settings map[string]interface{}
|
||||
@@ -72,15 +77,44 @@ type SerializedBuffer struct {
|
||||
ModTime time.Time
|
||||
}
|
||||
|
||||
// NewBufferFromFile opens a new buffer using the given path
|
||||
// It will also automatically handle `~`, and line/column with filename:l:c
|
||||
// It will return an empty buffer if the path does not exist
|
||||
// and an error if the file is a directory
|
||||
func NewBufferFromFile(path string) (*Buffer, error) {
|
||||
filename, cursorPosition := GetPathAndCursorPosition(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("", filename)
|
||||
} else {
|
||||
buf = NewBuffer(file, FSize(file), filename, cursorPosition)
|
||||
}
|
||||
|
||||
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)
|
||||
return NewBuffer(strings.NewReader(text), int64(len(text)), path, nil)
|
||||
}
|
||||
|
||||
// NewBuffer creates a new buffer from a given reader with a given path
|
||||
func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
|
||||
func NewBuffer(reader io.Reader, size int64, path string, cursorPosition []string) *Buffer {
|
||||
// check if the file is already open in a tab. If it's open return the buffer to that tab
|
||||
if path != "" {
|
||||
for _, tab := range tabs {
|
||||
for _, view := range tab.views {
|
||||
for _, view := range tab.Views {
|
||||
if view.Buf.Path == path {
|
||||
return view.Buf
|
||||
}
|
||||
@@ -121,47 +155,19 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
|
||||
os.Mkdir(configDir+"/buffers/", os.ModePerm)
|
||||
}
|
||||
|
||||
// Put the cursor at the first spot
|
||||
cursorStartX := 0
|
||||
cursorStartY := 0
|
||||
// If -startpos LINE,COL was passed, use start position LINE,COL
|
||||
if len(*flagStartPos) > 0 {
|
||||
positions := strings.Split(*flagStartPos, ",")
|
||||
if len(positions) == 2 {
|
||||
lineNum, errPos1 := strconv.Atoi(positions[0])
|
||||
colNum, errPos2 := strconv.Atoi(positions[1])
|
||||
if errPos1 == nil && errPos2 == nil {
|
||||
cursorStartX = colNum
|
||||
cursorStartY = lineNum - 1
|
||||
// Check to avoid line overflow
|
||||
if cursorStartY > b.NumLines {
|
||||
cursorStartY = b.NumLines - 1
|
||||
} else if cursorStartY < 0 {
|
||||
cursorStartY = 0
|
||||
}
|
||||
// Check to avoid column overflow
|
||||
if cursorStartX > len(b.Line(cursorStartY)) {
|
||||
cursorStartX = len(b.Line(cursorStartY))
|
||||
} else if cursorStartX < 0 {
|
||||
cursorStartX = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cursorLocation, cursorLocationError := GetBufferCursorLocation(cursorPosition, b)
|
||||
b.Cursor = Cursor{
|
||||
Loc: Loc{
|
||||
X: cursorStartX,
|
||||
Y: cursorStartY,
|
||||
},
|
||||
Loc: cursorLocation,
|
||||
buf: b,
|
||||
}
|
||||
|
||||
InitLocalSettings(b)
|
||||
|
||||
if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
|
||||
if cursorLocationError != nil && 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)
|
||||
@@ -184,15 +190,14 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
|
||||
}
|
||||
}
|
||||
}
|
||||
file.Close()
|
||||
}
|
||||
|
||||
if !b.Settings["fastdirty"].(bool) {
|
||||
if size > 50000 {
|
||||
if size > LargeFileThreshold {
|
||||
// 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()))
|
||||
calcHash(b, &b.origHash)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,6 +206,54 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
|
||||
return b
|
||||
}
|
||||
|
||||
func GetBufferCursorLocation(cursorPosition []string, b *Buffer) (Loc, error) {
|
||||
// parse the cursor position. The cursor location is ALWAYS initialised to 0, 0 even when
|
||||
// an error occurs due to lack of arguments or because the arguments are not numbers
|
||||
cursorLocation, cursorLocationError := ParseCursorLocation(cursorPosition)
|
||||
|
||||
// Put the cursor at the first spot. In the logic for cursor position the -startpos
|
||||
// flag is processed first and will overwrite any line/col parameters with colons after the filename
|
||||
if len(*flagStartPos) > 0 || cursorLocationError == nil {
|
||||
var lineNum, colNum int
|
||||
var errPos1, errPos2 error
|
||||
|
||||
positions := strings.Split(*flagStartPos, ",")
|
||||
|
||||
// if the -startpos flag contains enough args use them for the cursor location.
|
||||
// In this case args passed at the end of the filename will be ignored
|
||||
if len(positions) == 2 {
|
||||
lineNum, errPos1 = strconv.Atoi(positions[0])
|
||||
colNum, errPos2 = strconv.Atoi(positions[1])
|
||||
}
|
||||
// if -startpos has invalid arguments, use the arguments from filename.
|
||||
// This will have a default value (0, 0) even when the filename arguments are invalid
|
||||
if errPos1 != nil || errPos2 != nil || len(*flagStartPos) == 0 {
|
||||
// otherwise check if there are any arguments after the filename and use them
|
||||
lineNum = cursorLocation.Y
|
||||
colNum = cursorLocation.X
|
||||
}
|
||||
|
||||
// if some arguments were found make sure they don't go outside the file and cause overflows
|
||||
cursorLocation.Y = lineNum - 1
|
||||
cursorLocation.X = colNum
|
||||
// Check to avoid line overflow
|
||||
if cursorLocation.Y > b.NumLines-1 {
|
||||
cursorLocation.Y = b.NumLines - 1
|
||||
} else if cursorLocation.Y < 0 {
|
||||
cursorLocation.Y = 0
|
||||
}
|
||||
// Check to avoid column overflow
|
||||
if cursorLocation.X > len(b.Line(cursorLocation.Y)) {
|
||||
cursorLocation.X = len(b.Line(cursorLocation.Y))
|
||||
} else if cursorLocation.X < 0 {
|
||||
cursorLocation.X = 0
|
||||
}
|
||||
}
|
||||
return cursorLocation, cursorLocationError
|
||||
}
|
||||
|
||||
// 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 == "" {
|
||||
@@ -333,6 +386,8 @@ func (b *Buffer) Update() {
|
||||
b.NumLines = len(b.lines)
|
||||
}
|
||||
|
||||
// MergeCursors merges any cursors that are at the same position
|
||||
// into one cursor
|
||||
func (b *Buffer) MergeCursors() {
|
||||
var cursors []*Cursor
|
||||
for i := 0; i < len(b.cursors); i++ {
|
||||
@@ -359,6 +414,7 @@ func (b *Buffer) MergeCursors() {
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateCursors updates all the cursors indicies
|
||||
func (b *Buffer) UpdateCursors() {
|
||||
for i, c := range b.cursors {
|
||||
c.Num = i
|
||||
@@ -377,55 +433,167 @@ func (b *Buffer) SaveWithSudo() error {
|
||||
|
||||
// Serialize serializes the buffer to configDir/buffers
|
||||
func (b *Buffer) Serialize() error {
|
||||
if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
|
||||
file, err := os.Create(configDir + "/buffers/" + EscapePath(b.AbsPath))
|
||||
if err == nil {
|
||||
enc := gob.NewEncoder(file)
|
||||
gob.Register(TextEvent{})
|
||||
err = enc.Encode(SerializedBuffer{
|
||||
b.EventHandler,
|
||||
b.Cursor,
|
||||
b.ModTime,
|
||||
})
|
||||
}
|
||||
err = file.Close()
|
||||
return err
|
||||
if !b.Settings["savecursor"].(bool) && !b.Settings["saveundo"].(bool) {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
|
||||
name := configDir + "/buffers/" + EscapePath(b.AbsPath)
|
||||
|
||||
return overwriteFile(name, func(file io.Writer) error {
|
||||
return gob.NewEncoder(file).Encode(SerializedBuffer{
|
||||
b.EventHandler,
|
||||
b.Cursor,
|
||||
b.ModTime,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register(TextEvent{})
|
||||
gob.Register(SerializedBuffer{})
|
||||
}
|
||||
|
||||
// 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()
|
||||
if b.Settings["rmtrailingws"].(bool) {
|
||||
r, _ := regexp.Compile(`[ \t]+$`)
|
||||
for lineNum, line := range b.Lines(0, b.NumLines) {
|
||||
indices := r.FindStringIndex(line)
|
||||
if indices == nil {
|
||||
continue
|
||||
for i, l := range b.lines {
|
||||
pos := len(bytes.TrimRightFunc(l.data, unicode.IsSpace))
|
||||
|
||||
if pos < len(l.data) {
|
||||
b.deleteToEnd(Loc{pos, i})
|
||||
}
|
||||
startLoc := Loc{indices[0], lineNum}
|
||||
b.deleteToEnd(startLoc)
|
||||
}
|
||||
|
||||
b.Cursor.Relocate()
|
||||
}
|
||||
|
||||
if b.Settings["eofnewline"].(bool) {
|
||||
end := b.End()
|
||||
if b.RuneAt(Loc{end.X - 1, end.Y}) != '\n' {
|
||||
b.Insert(end, "\n")
|
||||
}
|
||||
}
|
||||
str := b.SaveString(b.Settings["fileformat"] == "dos")
|
||||
data := []byte(str)
|
||||
err := ioutil.WriteFile(ReplaceHome(filename), data, 0644)
|
||||
if err == nil {
|
||||
b.Path = filename
|
||||
b.IsModified = false
|
||||
|
||||
defer func() {
|
||||
b.ModTime, _ = GetModTime(filename)
|
||||
return b.Serialize()
|
||||
}()
|
||||
|
||||
// Removes any tilde and replaces with the absolute path to home
|
||||
absFilename := 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
|
||||
|
||||
var fileSize int
|
||||
|
||||
err := overwriteFile(absFilename, func(file io.Writer) (e error) {
|
||||
if len(b.lines) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// end of line
|
||||
var eol []byte
|
||||
|
||||
if b.Settings["fileformat"] == "dos" {
|
||||
eol = []byte{'\r', '\n'}
|
||||
} else {
|
||||
eol = []byte{'\n'}
|
||||
}
|
||||
|
||||
// write lines
|
||||
if fileSize, e = file.Write(b.lines[0].data); e != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, l := range b.lines[1:] {
|
||||
if _, e = file.Write(eol); e != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, e = file.Write(l.data); e != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fileSize += len(eol) + len(l.data)
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !b.Settings["fastdirty"].(bool) {
|
||||
if fileSize > LargeFileThreshold {
|
||||
// For large files 'fastdirty' needs to be on
|
||||
b.Settings["fastdirty"] = true
|
||||
} else {
|
||||
calcHash(b, &b.origHash)
|
||||
}
|
||||
}
|
||||
|
||||
b.Path = filename
|
||||
b.IsModified = false
|
||||
return b.Serialize()
|
||||
}
|
||||
|
||||
// overwriteFile opens the given file for writing, truncating if one exists, and then calls
|
||||
// the supplied function with the file as io.Writer object, also making sure the file is
|
||||
// closed afterwards.
|
||||
func overwriteFile(name string, fn func(io.Writer) error) (err error) {
|
||||
var file *os.File
|
||||
|
||||
if file, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if e := file.Close(); e != nil && err == nil {
|
||||
err = e
|
||||
}
|
||||
}()
|
||||
|
||||
w := bufio.NewWriter(file)
|
||||
|
||||
if err = fn(w); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = w.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
// calcHash calculates md5 hash of all lines in the buffer
|
||||
func calcHash(b *Buffer, out *[md5.Size]byte) {
|
||||
h := md5.New()
|
||||
|
||||
if len(b.lines) > 0 {
|
||||
h.Write(b.lines[0].data)
|
||||
|
||||
for _, l := range b.lines[1:] {
|
||||
h.Write([]byte{'\n'})
|
||||
h.Write(l.data)
|
||||
}
|
||||
}
|
||||
|
||||
h.Sum((*out)[:0])
|
||||
}
|
||||
|
||||
// SaveAsWithSudo is the same as SaveAs except it uses a neat trick
|
||||
@@ -466,11 +634,17 @@ 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()))
|
||||
|
||||
var buff [md5.Size]byte
|
||||
|
||||
calcHash(b, &buff)
|
||||
return buff != b.origHash
|
||||
}
|
||||
|
||||
func (b *Buffer) insert(pos Loc, value []byte) {
|
||||
@@ -502,13 +676,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'
|
||||
}
|
||||
|
||||
// LineBytes 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
|
||||
}
|
||||
|
||||
// LineRunes 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) {
|
||||
@@ -517,6 +707,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)
|
||||
}
|
||||
@@ -532,8 +723,16 @@ func (b *Buffer) Lines(start, end int) []string {
|
||||
}
|
||||
|
||||
// Len gives the length of the buffer
|
||||
func (b *Buffer) Len() int {
|
||||
return Count(b.String())
|
||||
func (b *Buffer) Len() (n int) {
|
||||
for _, l := range b.lines {
|
||||
n += utf8.RuneCount(l.data)
|
||||
}
|
||||
|
||||
if len(b.lines) > 1 {
|
||||
n += len(b.lines) - 1 // account for newlines
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MoveLinesUp moves the range of lines up one row
|
||||
@@ -597,3 +796,58 @@ func (b *Buffer) clearCursors() {
|
||||
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
|
||||
}
|
||||
|
||||
117
cmd/micro/buffer_test.go
Normal file
117
cmd/micro/buffer_test.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetBufferCursorLocationEmptyArgs(t *testing.T) {
|
||||
buf := NewBufferFromString("this is my\nbuffer\nfile\nhello", "")
|
||||
|
||||
location, err := GetBufferCursorLocation(nil, buf)
|
||||
|
||||
assertEqual(t, 0, location.Y)
|
||||
assertEqual(t, 0, location.X)
|
||||
|
||||
// an error is present due to the cursorLocation being nil
|
||||
assertTrue(t, err != nil)
|
||||
|
||||
}
|
||||
|
||||
func TestGetBufferCursorLocationStartposFlag(t *testing.T) {
|
||||
buf := NewBufferFromString("this is my\nbuffer\nfile\nhello", "")
|
||||
|
||||
*flagStartPos = "1,2"
|
||||
|
||||
location, err := GetBufferCursorLocation(nil, buf)
|
||||
|
||||
// note: 1 is subtracted from the line to get the correct index in the buffer
|
||||
assertTrue(t, 0 == location.Y)
|
||||
assertTrue(t, 2 == location.X)
|
||||
|
||||
// an error is present due to the cursorLocation being nil
|
||||
assertTrue(t, err != nil)
|
||||
}
|
||||
|
||||
func TestGetBufferCursorLocationInvalidStartposFlag(t *testing.T) {
|
||||
buf := NewBufferFromString("this is my\nbuffer\nfile\nhello", "")
|
||||
|
||||
*flagStartPos = "apples,2"
|
||||
|
||||
location, err := GetBufferCursorLocation(nil, buf)
|
||||
// expect to default to the start of the file, which is 0,0
|
||||
assertEqual(t, 0, location.Y)
|
||||
assertEqual(t, 0, location.X)
|
||||
|
||||
// an error is present due to the cursorLocation being nil
|
||||
assertTrue(t, err != nil)
|
||||
}
|
||||
func TestGetBufferCursorLocationStartposFlagAndCursorPosition(t *testing.T) {
|
||||
text := "this is my\nbuffer\nfile\nhello"
|
||||
cursorPosition := []string{"3", "1"}
|
||||
|
||||
buf := NewBufferFromString(text, "")
|
||||
|
||||
*flagStartPos = "1,2"
|
||||
|
||||
location, err := GetBufferCursorLocation(cursorPosition, buf)
|
||||
// expect to have the flag positions, not the cursor position
|
||||
// note: 1 is subtracted from the line to get the correct index in the buffer
|
||||
assertEqual(t, 0, location.Y)
|
||||
assertEqual(t, 2, location.X)
|
||||
|
||||
assertTrue(t, err == nil)
|
||||
}
|
||||
func TestGetBufferCursorLocationCursorPositionAndInvalidStartposFlag(t *testing.T) {
|
||||
text := "this is my\nbuffer\nfile\nhello"
|
||||
cursorPosition := []string{"3", "1"}
|
||||
|
||||
buf := NewBufferFromString(text, "")
|
||||
|
||||
*flagStartPos = "apples,2"
|
||||
|
||||
location, err := GetBufferCursorLocation(cursorPosition, buf)
|
||||
// expect to have the flag positions, not the cursor position
|
||||
// note: 1 is subtracted from the line to get the correct index in the buffer
|
||||
assertEqual(t, 2, location.Y)
|
||||
assertEqual(t, 1, location.X)
|
||||
|
||||
// no errors this time as cursorPosition is not nil
|
||||
assertTrue(t, err == nil)
|
||||
}
|
||||
|
||||
func TestGetBufferCursorLocationNoErrorWhenOverflowWithStartpos(t *testing.T) {
|
||||
text := "this is my\nbuffer\nfile\nhello"
|
||||
|
||||
buf := NewBufferFromString(text, "")
|
||||
|
||||
*flagStartPos = "50,50"
|
||||
|
||||
location, err := GetBufferCursorLocation(nil, buf)
|
||||
// expect to have the flag positions, not the cursor position
|
||||
assertEqual(t, buf.NumLines-1, location.Y)
|
||||
assertEqual(t, 5, location.X)
|
||||
|
||||
// error is expected as cursorPosition is nil
|
||||
assertTrue(t, err != nil)
|
||||
}
|
||||
func TestGetBufferCursorLocationNoErrorWhenOverflowWithCursorPosition(t *testing.T) {
|
||||
text := "this is my\nbuffer\nfile\nhello"
|
||||
cursorPosition := []string{"50", "2"}
|
||||
|
||||
*flagStartPos = ""
|
||||
|
||||
buf := NewBufferFromString(text, "")
|
||||
|
||||
location, err := GetBufferCursorLocation(cursorPosition, buf)
|
||||
// expect to have the flag positions, not the cursor position
|
||||
assertEqual(t, buf.NumLines-1, location.Y)
|
||||
assertEqual(t, 2, location.X)
|
||||
|
||||
// error is expected as cursorPosition is nil
|
||||
assertTrue(t, err == nil)
|
||||
}
|
||||
|
||||
//func TestGetBufferCursorLocationColonArgs(t *testing.T) {
|
||||
// buf := new(Buffer)
|
||||
|
||||
//}
|
||||
@@ -69,12 +69,32 @@ func (c *CellView) Draw(buf *Buffer, top, height, left, width int) {
|
||||
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) {
|
||||
if curX > 0 {
|
||||
curX--
|
||||
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]
|
||||
|
||||
@@ -137,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
|
||||
@@ -146,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
|
||||
}
|
||||
|
||||
@@ -155,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}
|
||||
}
|
||||
}
|
||||
@@ -167,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}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,16 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/zyedidia/micro/cmd/micro/shellwords"
|
||||
@@ -37,6 +35,7 @@ func init() {
|
||||
"Set": Set,
|
||||
"SetLocal": SetLocal,
|
||||
"Show": Show,
|
||||
"ShowKey": ShowKey,
|
||||
"Run": Run,
|
||||
"Bind": Bind,
|
||||
"Quit": Quit,
|
||||
@@ -55,8 +54,10 @@ func init() {
|
||||
"Pwd": Pwd,
|
||||
"Open": Open,
|
||||
"TabSwitch": TabSwitch,
|
||||
"Term": Term,
|
||||
"MemUsage": MemUsage,
|
||||
"Retab": Retab,
|
||||
"Raw": Raw,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +94,7 @@ func DefaultCommands() map[string]StrCommand {
|
||||
"set": {"Set", []Completion{OptionCompletion, OptionValueCompletion}},
|
||||
"setlocal": {"SetLocal", []Completion{OptionCompletion, OptionValueCompletion}},
|
||||
"show": {"Show", []Completion{OptionCompletion, NoCompletion}},
|
||||
"showkey": {"ShowKey", []Completion{NoCompletion}},
|
||||
"bind": {"Bind", []Completion{NoCompletion}},
|
||||
"run": {"Run", []Completion{NoCompletion}},
|
||||
"quit": {"Quit", []Completion{NoCompletion}},
|
||||
@@ -111,8 +113,32 @@ func DefaultCommands() map[string]StrCommand {
|
||||
"pwd": {"Pwd", []Completion{NoCompletion}},
|
||||
"open": {"Open", []Completion{FileCompletion}},
|
||||
"tabswitch": {"TabSwitch", []Completion{NoCompletion}},
|
||||
"term": {"Term", []Completion{NoCompletion}},
|
||||
"memusage": {"MemUsage", []Completion{NoCompletion}},
|
||||
"retab": {"Retab", []Completion{NoCompletion}},
|
||||
"raw": {"Raw", []Completion{NoCompletion}},
|
||||
}
|
||||
}
|
||||
|
||||
// CommandEditAction returns a bindable function that opens a prompt with
|
||||
// the given string and executes the command when the user presses
|
||||
// enter
|
||||
func CommandEditAction(prompt string) func(*View, bool) bool {
|
||||
return func(v *View, usePlugin bool) bool {
|
||||
input, canceled := messenger.Prompt("> ", prompt, "Command", CommandCompletion)
|
||||
if !canceled {
|
||||
HandleCommand(input)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// CommandAction returns a bindable function which executes the
|
||||
// given command
|
||||
func CommandAction(cmd string) func(*View, bool) bool {
|
||||
return func(v *View, usePlugin bool) bool {
|
||||
HandleCommand(cmd)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,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")
|
||||
@@ -199,10 +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 {
|
||||
@@ -212,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
|
||||
@@ -243,7 +293,7 @@ func Cd(args []string) {
|
||||
}
|
||||
wd, _ := os.Getwd()
|
||||
for _, tab := range tabs {
|
||||
for _, view := range tab.views {
|
||||
for _, view := range tab.Views {
|
||||
if len(view.Buf.name) == 0 {
|
||||
continue
|
||||
}
|
||||
@@ -341,24 +391,10 @@ func VSplit(args []string) {
|
||||
if len(args) == 0 {
|
||||
CurView().VSplit(NewBufferFromString("", ""))
|
||||
} else {
|
||||
filename := args[0]
|
||||
filename = ReplaceHome(filename)
|
||||
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)
|
||||
}
|
||||
@@ -370,24 +406,10 @@ func HSplit(args []string) {
|
||||
if len(args) == 0 {
|
||||
CurView().HSplit(NewBufferFromString("", ""))
|
||||
} else {
|
||||
filename := args[0]
|
||||
filename = ReplaceHome(filename)
|
||||
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)
|
||||
}
|
||||
@@ -410,23 +432,10 @@ func NewTab(args []string) {
|
||||
if len(args) == 0 {
|
||||
CurView().AddTab(true)
|
||||
} else {
|
||||
filename := args[0]
|
||||
filename = ReplaceHome(filename)
|
||||
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))
|
||||
@@ -435,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()
|
||||
}
|
||||
}
|
||||
@@ -489,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 {
|
||||
@@ -552,6 +575,7 @@ func Replace(args []string) {
|
||||
}
|
||||
|
||||
replace := string(args[1])
|
||||
replaceBytes := []byte(replace)
|
||||
|
||||
regex, err := regexp.Compile("(?m)" + search)
|
||||
if err != nil {
|
||||
@@ -565,23 +589,16 @@ func Replace(args []string) {
|
||||
found := 0
|
||||
replaceAll := func() {
|
||||
var deltas []Delta
|
||||
deltaXOffset := Count(replace) - Count(search)
|
||||
for i := 0; i < view.Buf.LinesNum(); i++ {
|
||||
matches := regex.FindAllIndex(view.Buf.lines[i].data, -1)
|
||||
str := string(view.Buf.lines[i].data)
|
||||
newText := regex.ReplaceAllFunc(view.Buf.lines[i].data, func(in []byte) []byte {
|
||||
found++
|
||||
return replaceBytes
|
||||
})
|
||||
|
||||
if matches != nil {
|
||||
xOffset := 0
|
||||
for _, m := range matches {
|
||||
from := Loc{runePos(m[0], str) + xOffset, i}
|
||||
to := Loc{runePos(m[1], str) + xOffset, i}
|
||||
from := Loc{0, i}
|
||||
to := Loc{utf8.RuneCount(view.Buf.lines[i].data), i}
|
||||
|
||||
xOffset += deltaXOffset
|
||||
|
||||
deltas = append(deltas, Delta{replace, from, to})
|
||||
found++
|
||||
}
|
||||
}
|
||||
deltas = append(deltas, Delta{string(newText), from, to})
|
||||
}
|
||||
view.Buf.MultipleReplace(deltas)
|
||||
}
|
||||
@@ -644,94 +661,17 @@ func ReplaceAll(args []string) {
|
||||
Replace(append(args, "-a"))
|
||||
}
|
||||
|
||||
// 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]
|
||||
|
||||
cmd := exec.Command(inputCmd, args[1:]...)
|
||||
outputBytes := &bytes.Buffer{}
|
||||
cmd.Stdout = outputBytes
|
||||
cmd.Stderr = outputBytes
|
||||
cmd.Start()
|
||||
err = cmd.Wait() // wait for command to finish
|
||||
outstring := outputBytes.String()
|
||||
return outstring, err
|
||||
}
|
||||
|
||||
// HandleShellCommand runs the shell command
|
||||
// The openTerm argument specifies whether a terminal should be opened (for viewing output
|
||||
// or interacting with stdin)
|
||||
func HandleShellCommand(input string, openTerm bool, waitToFinish bool) string {
|
||||
args, err := shellwords.Split(input)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
inputCmd := args[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 := args[1:]
|
||||
|
||||
// Set up everything for the command
|
||||
var output string
|
||||
cmd := exec.Command(inputCmd, args...)
|
||||
cmd.Stdin = os.Stdin
|
||||
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()
|
||||
|
||||
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
|
||||
|
||||
@@ -35,6 +35,13 @@ func (c *Cursor) Goto(b Cursor) {
|
||||
c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@@ -326,6 +333,18 @@ func (c *Cursor) Start() {
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -85,7 +89,6 @@ func NewLineArray(size int64, reader io.Reader) *LineArray {
|
||||
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
|
||||
@@ -143,9 +146,9 @@ func (la *LineArray) SaveString(useCrlf bool) string {
|
||||
|
||||
// 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
|
||||
@@ -243,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
|
||||
}
|
||||
|
||||
@@ -416,7 +416,6 @@ func importFilePath() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Join", luar.New(L, filepath.Join))
|
||||
L.SetField(pkg, "Clean", 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))
|
||||
|
||||
@@ -223,6 +223,7 @@ func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool)
|
||||
}
|
||||
}
|
||||
|
||||
// Completion represents a type of completion
|
||||
type Completion int
|
||||
|
||||
const (
|
||||
@@ -260,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:
|
||||
@@ -322,7 +328,7 @@ func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTy
|
||||
chosen = chosen + CommonSubstring(suggestions...)
|
||||
}
|
||||
|
||||
if chosen != "" {
|
||||
if len(suggestions) != 0 && chosen != "" {
|
||||
m.response = shellwords.Join(append(args[:len(args)-1], chosen)...)
|
||||
m.cursorx = Count(m.response)
|
||||
}
|
||||
@@ -332,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()
|
||||
@@ -348,6 +354,7 @@ 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--
|
||||
@@ -355,6 +362,8 @@ func (m *Messenger) UpHistory(history []string) {
|
||||
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++
|
||||
@@ -362,33 +371,47 @@ func (m *Messenger) DownHistory(history []string) {
|
||||
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()
|
||||
@@ -410,6 +433,8 @@ func (m *Messenger) WordLeft() {
|
||||
}
|
||||
m.CursorRight()
|
||||
}
|
||||
|
||||
// WordRight moves the cursor one word to the right
|
||||
func (m *Messenger) WordRight() {
|
||||
response := []rune(m.response)
|
||||
if m.cursorx >= len(response) {
|
||||
@@ -433,6 +458,8 @@ func (m *Messenger) WordRight() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteWordLeft deletes one word to the left
|
||||
func (m *Messenger) DeleteWordLeft() {
|
||||
m.WordLeft()
|
||||
m.response = string([]rune(m.response)[:m.cursorx])
|
||||
@@ -577,11 +604,11 @@ func (m *Messenger) Display() {
|
||||
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)
|
||||
file.Close()
|
||||
|
||||
if err != nil {
|
||||
m.Error("Error loading history:", err)
|
||||
@@ -606,11 +633,12 @@ func (m *Messenger) SaveHistory() {
|
||||
// Don't save history past 100
|
||||
for k, v := range m.history {
|
||||
if len(v) > 100 {
|
||||
m.history[k] = v[0: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)
|
||||
|
||||
@@ -619,7 +647,6 @@ func (m *Messenger) SaveHistory() {
|
||||
m.Error("Error saving history:", err)
|
||||
return
|
||||
}
|
||||
file.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ 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"
|
||||
@@ -44,8 +45,10 @@ 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"
|
||||
|
||||
// The list of views
|
||||
@@ -56,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
|
||||
@@ -85,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
|
||||
@@ -174,6 +178,9 @@ func InitScreen() {
|
||||
// Should we enable true color?
|
||||
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
|
||||
|
||||
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
|
||||
oldTerm := os.Getenv("TERM")
|
||||
@@ -185,12 +192,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)
|
||||
@@ -206,7 +220,9 @@ func InitScreen() {
|
||||
screen.EnableMouse()
|
||||
}
|
||||
|
||||
screen.SetStyle(defStyle)
|
||||
os.Setenv("TCELLDB", tcelldb)
|
||||
|
||||
// screen.SetStyle(defStyle)
|
||||
}
|
||||
|
||||
// RedrawAll redraws everything -- all the views and the messenger
|
||||
@@ -220,7 +236,7 @@ func RedrawAll() {
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range tabs[curTab].views {
|
||||
for _, v := range tabs[curTab].Views {
|
||||
v.Display()
|
||||
}
|
||||
DisplayTabs()
|
||||
@@ -229,6 +245,11 @@ func RedrawAll() {
|
||||
DisplayKeyMenu()
|
||||
}
|
||||
screen.Show()
|
||||
|
||||
if numRedraw%50 == 0 {
|
||||
runtime.GC()
|
||||
}
|
||||
numRedraw++
|
||||
}
|
||||
|
||||
func LoadAll() {
|
||||
@@ -247,13 +268,13 @@ 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")
|
||||
@@ -265,13 +286,15 @@ func main() {
|
||||
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 bindings.json\nfile (see 'help options').\n\n")
|
||||
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`")
|
||||
@@ -357,7 +380,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)
|
||||
}
|
||||
|
||||
@@ -387,9 +410,16 @@ 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)
|
||||
}))
|
||||
@@ -423,22 +453,20 @@ func main() {
|
||||
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() {
|
||||
@@ -471,7 +499,13 @@ 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:
|
||||
}
|
||||
|
||||
@@ -501,7 +535,7 @@ 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
|
||||
}
|
||||
@@ -510,13 +544,15 @@ func main() {
|
||||
} else if e.Buttons() == tcell.WheelUp || e.Buttons() == tcell.WheelDown {
|
||||
var view *View
|
||||
x, y := e.Position()
|
||||
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 {
|
||||
view = tabs[curTab].views[v.Num]
|
||||
view = tabs[curTab].Views[v.Num]
|
||||
}
|
||||
}
|
||||
view.HandleEvent(e)
|
||||
didAction = true
|
||||
if view != nil {
|
||||
view.HandleEvent(e)
|
||||
didAction = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,10 @@ func LuaFunctionBinding(function string) func(*View, bool) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// LuaFunctionMouseBinding is a function generator which takes the name of a lua function
|
||||
// and creates a function that will call that lua function
|
||||
// Specifically it creates a function that can be called as a mouse binding because this is used
|
||||
// to bind mouse actions to lua functions
|
||||
func LuaFunctionMouseBinding(function string) func(*View, bool, *tcell.EventMouse) bool {
|
||||
return func(v *View, _ bool, e *tcell.EventMouse) bool {
|
||||
_, err := Call(function, e)
|
||||
@@ -114,10 +118,13 @@ func LuaFunctionComplete(function string) func(string) []string {
|
||||
}
|
||||
}
|
||||
|
||||
// LuaFunctionJob returns a function that will call the given lua function
|
||||
// structured as a job call i.e. the job output and arguments are provided
|
||||
// to the lua function
|
||||
func LuaFunctionJob(function string) func(string, ...string) {
|
||||
return func(output string, args ...string) {
|
||||
_, err := Call(function, unpack(append([]string{output}, args...))...)
|
||||
if err != nil {
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
|
||||
TermMessage(err)
|
||||
}
|
||||
}
|
||||
@@ -163,3 +170,15 @@ func LoadPlugins() {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -423,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 {
|
||||
@@ -435,7 +435,7 @@ func (pv *PluginVersion) DownloadAndInstall() error {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
basepath := path.Dir(targetName)
|
||||
basepath := filepath.Dir(targetName)
|
||||
|
||||
if err := os.MkdirAll(basepath, dirPerm); err != nil {
|
||||
return err
|
||||
@@ -541,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
|
||||
|
||||
@@ -36,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
@@ -1,15 +1,17 @@
|
||||
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)
|
||||
screen.SetContent(sb.view.x+sb.view.Width-1, sb.view.y+sb.pos(), ' ', nil, style)
|
||||
}
|
||||
|
||||
func (sb *ScrollBar) Pos() int {
|
||||
func (sb *ScrollBar) pos() int {
|
||||
numlines := sb.view.Buf.NumLines
|
||||
h := sb.view.Height
|
||||
filepercent := float32(sb.view.Topline) / float32(numlines)
|
||||
|
||||
@@ -4,11 +4,13 @@ import (
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/flynn/json5"
|
||||
"github.com/zyedidia/glob"
|
||||
@@ -100,15 +102,23 @@ func InitLocalSettings(buf *Buffer) {
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,17 +204,21 @@ func DefaultGlobalSettings() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"autoindent": true,
|
||||
"autosave": false,
|
||||
"basename": false,
|
||||
"colorcolumn": float64(0),
|
||||
"colorscheme": "default",
|
||||
"cursorline": true,
|
||||
"eofnewline": 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{},
|
||||
@@ -236,15 +250,19 @@ func DefaultLocalSettings() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"autoindent": true,
|
||||
"autosave": false,
|
||||
"basename": false,
|
||||
"colorcolumn": float64(0),
|
||||
"cursorline": true,
|
||||
"eofnewline": false,
|
||||
"fastdirty": true,
|
||||
"fileformat": "unix",
|
||||
"filetype": "Unknown",
|
||||
"hidehelp": false,
|
||||
"ignorecase": false,
|
||||
"indentchar": " ",
|
||||
"keepautoindent": false,
|
||||
"matchbrace": false,
|
||||
"matchbraceleft": false,
|
||||
"rmtrailingws": false,
|
||||
"ruler": true,
|
||||
"savecursor": false,
|
||||
@@ -308,7 +326,7 @@ 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()
|
||||
}
|
||||
}
|
||||
@@ -331,7 +349,7 @@ func SetOption(option, value string) error {
|
||||
if len(tabs) != 0 {
|
||||
if _, ok := CurView().Buf.Settings[option]; ok {
|
||||
for _, tab := range tabs {
|
||||
for _, view := range tab.views {
|
||||
for _, view := range tab.Views {
|
||||
SetLocalOption(option, value, view)
|
||||
}
|
||||
}
|
||||
@@ -375,22 +393,36 @@ func SetLocalOption(option, value string, view *View) error {
|
||||
|
||||
if option == "fastdirty" {
|
||||
// If it is being turned off, we have to hash every open buffer
|
||||
var empty [16]byte
|
||||
var empty [md5.Size]byte
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for _, tab := range tabs {
|
||||
for _, v := range tab.views {
|
||||
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)
|
||||
wg.Add(1)
|
||||
|
||||
go func(b *Buffer) { // calculate md5 hash of the file
|
||||
defer wg.Done()
|
||||
|
||||
if file, e := os.Open(b.AbsPath); e == nil {
|
||||
defer file.Close()
|
||||
|
||||
h := md5.New()
|
||||
|
||||
if _, e = io.Copy(h, file); e == nil {
|
||||
h.Sum(b.origHash[:0])
|
||||
}
|
||||
}
|
||||
}(v.Buf)
|
||||
}
|
||||
} else {
|
||||
v.Buf.IsModified = v.Buf.Modified()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
buf.Settings[option] = nativeValue
|
||||
@@ -413,7 +445,9 @@ func SetLocalOption(option, value string, view *View) error {
|
||||
if !nativeValue.(bool) {
|
||||
buf.ClearMatches()
|
||||
} else {
|
||||
buf.highlighter.HighlightStates(buf)
|
||||
if buf.highlighter != nil {
|
||||
buf.highlighter.HighlightStates(buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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")
|
||||
}
|
||||
@@ -168,19 +168,10 @@ func Join(args ...string) string {
|
||||
|
||||
for _, b := range w {
|
||||
switch b {
|
||||
case 'a', 'b', 'c', 'd', 'e', 'f', 'g',
|
||||
'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
||||
'o', 'p', 'q', 'r', 's', 't', 'u',
|
||||
'v', 'w', 'x', 'y', 'z',
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G',
|
||||
'H', 'I', 'J', 'K', 'L', 'M', 'N',
|
||||
'O', 'P', 'Q', 'R', 'S', 'T', 'U',
|
||||
'V', 'W', 'X', 'Y', 'Z',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'_', '-', '.', ',', ':', '/', '@':
|
||||
case ' ', '\t', '\r', '\n':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteString(string(b))
|
||||
default:
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteString(string(b))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -22,6 +23,9 @@ func (sline *Statusline) Display() {
|
||||
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.Modified() {
|
||||
@@ -43,24 +47,26 @@ func (sline *Statusline) Display() {
|
||||
file += " " + sline.view.Buf.Settings["fileformat"].(string)
|
||||
|
||||
rightText := ""
|
||||
if len(kmenuBinding) > 0 {
|
||||
if globalSettings["keymenu"].(bool) {
|
||||
rightText += kmenuBinding + ": hide bindings"
|
||||
} else {
|
||||
rightText += kmenuBinding + ": show bindings"
|
||||
}
|
||||
}
|
||||
if len(helpBinding) > 0 {
|
||||
if !sline.view.Buf.Settings["hidehelp"].(bool) {
|
||||
if len(kmenuBinding) > 0 {
|
||||
rightText += ", "
|
||||
if globalSettings["keymenu"].(bool) {
|
||||
rightText += kmenuBinding + ": hide bindings"
|
||||
} else {
|
||||
rightText += kmenuBinding + ": show bindings"
|
||||
}
|
||||
}
|
||||
if sline.view.Type == vtHelp {
|
||||
rightText += helpBinding + ": close help"
|
||||
} else {
|
||||
rightText += helpBinding + ": open help"
|
||||
if len(helpBinding) > 0 {
|
||||
if len(kmenuBinding) > 0 {
|
||||
rightText += ", "
|
||||
}
|
||||
if sline.view.Type == vtHelp {
|
||||
rightText += helpBinding + ": close help"
|
||||
} else {
|
||||
rightText += helpBinding + ": open help"
|
||||
}
|
||||
}
|
||||
rightText += " "
|
||||
}
|
||||
rightText += " "
|
||||
|
||||
statusLineStyle := defStyle.Reverse(true)
|
||||
if style, ok := colorscheme["statusline"]; ok {
|
||||
@@ -69,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
|
||||
@@ -48,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
|
||||
@@ -71,15 +76,18 @@ func (t *Tab) Resize() {
|
||||
|
||||
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
|
||||
@@ -95,7 +103,7 @@ 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.Modified() {
|
||||
str += " +"
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
@@ -10,8 +11,9 @@ import (
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/mattn/go-runewidth"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Util.go is a collection of utility functions that are used throughout
|
||||
@@ -23,6 +25,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
|
||||
@@ -130,7 +180,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
|
||||
@@ -282,10 +332,69 @@ func ReplaceHome(path string) string {
|
||||
return path
|
||||
}
|
||||
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
messenger.Error("Could not find home directory: ", err)
|
||||
return path
|
||||
var userData *user.User
|
||||
var err error
|
||||
|
||||
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 {
|
||||
TermMessage("Could not find user: ", err)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return strings.Replace(path, "~", home, 1)
|
||||
|
||||
home := userData.HomeDir
|
||||
|
||||
return strings.Replace(path, homeString, home, 1)
|
||||
}
|
||||
|
||||
// GetPathAndCursorPosition returns a filename without everything following a `:`
|
||||
// This is used for opening files like util.go:10:5 to specify a line and column
|
||||
// Special cases like Windows Absolute path (C:\myfile.txt:10:5) are handled correctly.
|
||||
func GetPathAndCursorPosition(path string) (string, []string) {
|
||||
re := regexp.MustCompile(`([\s\S]+?)(?::(\d+))(?::(\d+))?`)
|
||||
match := re.FindStringSubmatch(path)
|
||||
// no lines/columns were specified in the path, return just the path with no cursor location
|
||||
if len(match) == 0 {
|
||||
return path, nil
|
||||
} else if match[len(match)-1] != "" {
|
||||
// if the last capture group match isn't empty then both line and column were provided
|
||||
return match[1], match[2:]
|
||||
}
|
||||
// if it was empty, then only a line was provided, so default to column 0
|
||||
return match[1], []string{match[2], "0"}
|
||||
}
|
||||
|
||||
func ParseCursorLocation(cursorPositions []string) (Loc, error) {
|
||||
startpos := Loc{0, 0}
|
||||
var err error
|
||||
|
||||
// if no positions are available exit early
|
||||
if cursorPositions == nil {
|
||||
return startpos, errors.New("No cursor positions were provided.")
|
||||
}
|
||||
|
||||
startpos.Y, err = strconv.Atoi(cursorPositions[0])
|
||||
if err != nil {
|
||||
messenger.Error("Error parsing cursor position: ", err)
|
||||
} else {
|
||||
if len(cursorPositions) > 1 {
|
||||
startpos.X, err = strconv.Atoi(cursorPositions[1])
|
||||
if err != nil {
|
||||
messenger.Error("Error parsing cursor position: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return startpos, err
|
||||
}
|
||||
|
||||
@@ -103,3 +103,229 @@ func TestWidthOfLargeRunes(t *testing.T) {
|
||||
t.Error("WidthOfLargeRunes 5 Failed. Got", w)
|
||||
}
|
||||
}
|
||||
|
||||
func assertEqual(t *testing.T, expected interface{}, result interface{}) {
|
||||
if expected != result {
|
||||
t.Fatalf("Expected: %d != Got: %d", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func assertTrue(t *testing.T, condition bool) {
|
||||
if !condition {
|
||||
t.Fatalf("Condition was not true. Got false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPathRelativeWithDot(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition("./myfile:10:5")
|
||||
|
||||
assertEqual(t, path, "./myfile")
|
||||
assertEqual(t, "10", cursorPosition[0])
|
||||
assertEqual(t, "5", cursorPosition[1])
|
||||
}
|
||||
func TestGetPathRelativeWithDotWindows(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition(".\\myfile:10:5")
|
||||
|
||||
assertEqual(t, path, ".\\myfile")
|
||||
assertEqual(t, "10", cursorPosition[0])
|
||||
assertEqual(t, cursorPosition[1], "5")
|
||||
}
|
||||
func TestGetPathRelativeNoDot(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition("myfile:10:5")
|
||||
|
||||
assertEqual(t, path, "myfile")
|
||||
assertEqual(t, "10", cursorPosition[0])
|
||||
|
||||
assertEqual(t, cursorPosition[1], "5")
|
||||
}
|
||||
func TestGetPathAbsoluteWindows(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition("C:\\myfile:10:5")
|
||||
|
||||
assertEqual(t, path, "C:\\myfile")
|
||||
assertEqual(t, "10", cursorPosition[0])
|
||||
|
||||
assertEqual(t, cursorPosition[1], "5")
|
||||
|
||||
path, cursorPosition = GetPathAndCursorPosition("C:/myfile:10:5")
|
||||
|
||||
assertEqual(t, path, "C:/myfile")
|
||||
assertEqual(t, "10", cursorPosition[0])
|
||||
assertEqual(t, "5", cursorPosition[1])
|
||||
}
|
||||
|
||||
func TestGetPathAbsoluteUnix(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition("/home/user/myfile:10:5")
|
||||
|
||||
assertEqual(t, path, "/home/user/myfile")
|
||||
assertEqual(t, "10", cursorPosition[0])
|
||||
assertEqual(t, "5", cursorPosition[1])
|
||||
}
|
||||
|
||||
func TestGetPathRelativeWithDotWithoutLineAndColumn(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition("./myfile")
|
||||
|
||||
assertEqual(t, path, "./myfile")
|
||||
// no cursor position in filename, nil should be returned
|
||||
assertTrue(t, cursorPosition == nil)
|
||||
}
|
||||
func TestGetPathRelativeWithDotWindowsWithoutLineAndColumn(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition(".\\myfile")
|
||||
|
||||
assertEqual(t, path, ".\\myfile")
|
||||
assertTrue(t, cursorPosition == nil)
|
||||
|
||||
}
|
||||
func TestGetPathRelativeNoDotWithoutLineAndColumn(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition("myfile")
|
||||
|
||||
assertEqual(t, path, "myfile")
|
||||
assertTrue(t, cursorPosition == nil)
|
||||
|
||||
}
|
||||
func TestGetPathAbsoluteWindowsWithoutLineAndColumn(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition("C:\\myfile")
|
||||
|
||||
assertEqual(t, path, "C:\\myfile")
|
||||
assertTrue(t, cursorPosition == nil)
|
||||
|
||||
path, cursorPosition = GetPathAndCursorPosition("C:/myfile")
|
||||
|
||||
assertEqual(t, path, "C:/myfile")
|
||||
assertTrue(t, cursorPosition == nil)
|
||||
|
||||
}
|
||||
func TestGetPathAbsoluteUnixWithoutLineAndColumn(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition("/home/user/myfile")
|
||||
|
||||
assertEqual(t, path, "/home/user/myfile")
|
||||
assertTrue(t, cursorPosition == nil)
|
||||
|
||||
}
|
||||
func TestGetPathSingleLetterFileRelativePath(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition("a:5:6")
|
||||
|
||||
assertEqual(t, path, "a")
|
||||
assertEqual(t, "5", cursorPosition[0])
|
||||
assertEqual(t, "6", cursorPosition[1])
|
||||
}
|
||||
func TestGetPathSingleLetterFileAbsolutePathWindows(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition("C:\\a:5:6")
|
||||
|
||||
assertEqual(t, path, "C:\\a")
|
||||
assertEqual(t, "5", cursorPosition[0])
|
||||
assertEqual(t, "6", cursorPosition[1])
|
||||
|
||||
path, cursorPosition = GetPathAndCursorPosition("C:/a:5:6")
|
||||
|
||||
assertEqual(t, path, "C:/a")
|
||||
assertEqual(t, "5", cursorPosition[0])
|
||||
assertEqual(t, "6", cursorPosition[1])
|
||||
}
|
||||
func TestGetPathSingleLetterFileAbsolutePathUnix(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition("/home/user/a:5:6")
|
||||
|
||||
assertEqual(t, path, "/home/user/a")
|
||||
assertEqual(t, "5", cursorPosition[0])
|
||||
assertEqual(t, "6", cursorPosition[1])
|
||||
}
|
||||
func TestGetPathSingleLetterFileAbsolutePathWindowsWithoutLineAndColumn(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition("C:\\a")
|
||||
|
||||
assertEqual(t, path, "C:\\a")
|
||||
assertTrue(t, cursorPosition == nil)
|
||||
|
||||
path, cursorPosition = GetPathAndCursorPosition("C:/a")
|
||||
|
||||
assertEqual(t, path, "C:/a")
|
||||
assertTrue(t, cursorPosition == nil)
|
||||
|
||||
}
|
||||
func TestGetPathSingleLetterFileAbsolutePathUnixWithoutLineAndColumn(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition("/home/user/a")
|
||||
|
||||
assertEqual(t, path, "/home/user/a")
|
||||
assertTrue(t, cursorPosition == nil)
|
||||
|
||||
}
|
||||
|
||||
func TestGetPathRelativeWithDotOnlyLine(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition("./myfile:10")
|
||||
|
||||
assertEqual(t, path, "./myfile")
|
||||
assertEqual(t, "10", cursorPosition[0])
|
||||
assertEqual(t, "0", cursorPosition[1])
|
||||
}
|
||||
func TestGetPathRelativeWithDotWindowsOnlyLine(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition(".\\myfile:10")
|
||||
|
||||
assertEqual(t, path, ".\\myfile")
|
||||
assertEqual(t, "10", cursorPosition[0])
|
||||
assertEqual(t, "0", cursorPosition[1])
|
||||
}
|
||||
func TestGetPathRelativeNoDotOnlyLine(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition("myfile:10")
|
||||
|
||||
assertEqual(t, path, "myfile")
|
||||
assertEqual(t, "10", cursorPosition[0])
|
||||
assertEqual(t, "0", cursorPosition[1])
|
||||
}
|
||||
func TestGetPathAbsoluteWindowsOnlyLine(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition("C:\\myfile:10")
|
||||
|
||||
assertEqual(t, path, "C:\\myfile")
|
||||
assertEqual(t, "10", cursorPosition[0])
|
||||
assertEqual(t, "0", cursorPosition[1])
|
||||
|
||||
path, cursorPosition = GetPathAndCursorPosition("C:/myfile:10")
|
||||
|
||||
assertEqual(t, path, "C:/myfile")
|
||||
assertEqual(t, "10", cursorPosition[0])
|
||||
assertEqual(t, "0", cursorPosition[1])
|
||||
}
|
||||
func TestGetPathAbsoluteUnixOnlyLine(t *testing.T) {
|
||||
path, cursorPosition := GetPathAndCursorPosition("/home/user/myfile:10")
|
||||
|
||||
assertEqual(t, path, "/home/user/myfile")
|
||||
assertEqual(t, "10", cursorPosition[0])
|
||||
assertEqual(t, "0", cursorPosition[1])
|
||||
}
|
||||
func TestParseCursorLocationOneArg(t *testing.T) {
|
||||
location, err := ParseCursorLocation([]string{"3"})
|
||||
|
||||
assertEqual(t, 3, location.Y)
|
||||
assertEqual(t, 0, location.X)
|
||||
assertEqual(t, nil, err)
|
||||
}
|
||||
func TestParseCursorLocationTwoArgs(t *testing.T) {
|
||||
location, err := ParseCursorLocation([]string{"3", "15"})
|
||||
|
||||
assertEqual(t, 3, location.Y)
|
||||
assertEqual(t, 15, location.X)
|
||||
assertEqual(t, nil, err)
|
||||
}
|
||||
func TestParseCursorLocationNoArgs(t *testing.T) {
|
||||
location, err := ParseCursorLocation(nil)
|
||||
// the expected result is the start position - 0, 0
|
||||
assertEqual(t, 0, location.Y)
|
||||
assertEqual(t, 0, location.X)
|
||||
// an error will be present here as the positions we're parsing are a nil
|
||||
assertTrue(t, err != nil)
|
||||
}
|
||||
func TestParseCursorLocationFirstArgNotValidNumber(t *testing.T) {
|
||||
// the messenger is necessary as ParseCursorLocation
|
||||
// puts a message in it on error
|
||||
messenger = new(Messenger)
|
||||
_, err := ParseCursorLocation([]string{"apples", "1"})
|
||||
// the expected result is the start position - 0, 0
|
||||
assertTrue(t, messenger.hasMessage)
|
||||
assertTrue(t, err != nil)
|
||||
}
|
||||
func TestParseCursorLocationSecondArgNotValidNumber(t *testing.T) {
|
||||
// the messenger is necessary as ParseCursorLocation
|
||||
// puts a message in it on error
|
||||
messenger = new(Messenger)
|
||||
_, err := ParseCursorLocation([]string{"1", "apples"})
|
||||
// the expected result is the start position - 0, 0
|
||||
assertTrue(t, messenger.hasMessage)
|
||||
assertTrue(t, err != nil)
|
||||
}
|
||||
|
||||
1
cmd/micro/vendor/github.com/jtolds/gls
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/jtolds/gls
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/jtolds/gls added at 77f18212c9
1
cmd/micro/vendor/github.com/smartystreets/assertions
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/smartystreets/assertions
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/smartystreets/assertions added at 0b37b35ec7
1
cmd/micro/vendor/github.com/smartystreets/goconvey
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/smartystreets/goconvey
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/smartystreets/goconvey added at e5b2b7c911
2
cmd/micro/vendor/github.com/zyedidia/clipboard
generated
vendored
2
cmd/micro/vendor/github.com/zyedidia/clipboard
generated
vendored
Submodule cmd/micro/vendor/github.com/zyedidia/clipboard updated: adacf416ce...4611e809d8
1
cmd/micro/vendor/github.com/zyedidia/pty
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/zyedidia/pty
generated
vendored
Submodule
Submodule cmd/micro/vendor/github.com/zyedidia/pty added at 30364665a2
2
cmd/micro/vendor/github.com/zyedidia/tcell
generated
vendored
2
cmd/micro/vendor/github.com/zyedidia/tcell
generated
vendored
Submodule cmd/micro/vendor/github.com/zyedidia/tcell updated: c92e80b717...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,7 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -11,9 +12,9 @@ import (
|
||||
|
||||
// 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.
|
||||
@@ -70,6 +73,8 @@ 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
|
||||
@@ -89,11 +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
|
||||
@@ -131,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") {
|
||||
@@ -151,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 {
|
||||
@@ -168,7 +198,10 @@ func (v *View) ToggleTabbar() {
|
||||
}
|
||||
|
||||
func (v *View) paste(clip string) {
|
||||
leadingWS := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
|
||||
leadingWS := ""
|
||||
if v.Cursor.X > 0 {
|
||||
leadingWS = GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
|
||||
}
|
||||
|
||||
if v.Cursor.HasSelection() {
|
||||
v.Cursor.DeleteSelection()
|
||||
@@ -242,29 +275,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) {
|
||||
filename = ReplaceHome(filename)
|
||||
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)
|
||||
}
|
||||
@@ -358,6 +383,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
|
||||
@@ -410,7 +439,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
|
||||
}
|
||||
@@ -429,6 +458,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) {
|
||||
@@ -444,7 +498,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))
|
||||
}
|
||||
@@ -460,7 +513,8 @@ func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool {
|
||||
for _, action := range actions {
|
||||
readonlyBindingsResult := false
|
||||
funcName := ShortFuncName(action)
|
||||
if v.Type.readonly == true {
|
||||
curv := CurView()
|
||||
if curv.Type.Readonly == true {
|
||||
// check for readonly and if true only let key bindings get called if they do not change the contents.
|
||||
for _, readonlyBindings := range readonlyBindingsList {
|
||||
if strings.Contains(funcName, readonlyBindings) {
|
||||
@@ -470,7 +524,7 @@ func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool {
|
||||
}
|
||||
if !readonlyBindingsResult {
|
||||
// call the key binding
|
||||
relocate = action(v, true) || relocate
|
||||
relocate = action(curv, true) || relocate
|
||||
// Macro
|
||||
if funcName != "ToggleMacro" && funcName != "PlayMacro" {
|
||||
if recordingMacro {
|
||||
@@ -496,6 +550,25 @@ func (v *View) SetCursor(c *Cursor) bool {
|
||||
|
||||
// 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
|
||||
@@ -506,7 +579,7 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
case *tcell.EventRaw:
|
||||
for key, actions := range bindings {
|
||||
if key.keyCode == -1 {
|
||||
if e.EscapeCode() == key.escape {
|
||||
if e.EscSeq() == key.escape {
|
||||
for _, c := range v.Buf.cursors {
|
||||
ok := v.SetCursor(c)
|
||||
if !ok {
|
||||
@@ -547,9 +620,10 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !isBinding && e.Key() == tcell.KeyRune {
|
||||
// Check viewtype if readonly don't insert a rune (readonly help and log view etc.)
|
||||
if v.Type.readonly == false {
|
||||
if v.Type.Readonly == false {
|
||||
for _, c := range v.Buf.cursors {
|
||||
v.SetCursor(c)
|
||||
|
||||
@@ -558,7 +632,14 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
v.Cursor.DeleteSelection()
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
|
||||
|
||||
if v.isOverwriteMode {
|
||||
next := v.Cursor.Loc
|
||||
next.X++
|
||||
v.Buf.Replace(v.Cursor.Loc, next, string(e.Rune()))
|
||||
} else {
|
||||
v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
|
||||
}
|
||||
|
||||
for pl := range loadedPlugins {
|
||||
_, err := Call(pl+".onRune", string(e.Rune()), v)
|
||||
@@ -576,7 +657,7 @@ func (v *View) HandleEvent(event tcell.Event) {
|
||||
}
|
||||
case *tcell.EventPaste:
|
||||
// Check viewtype if readonly don't paste (readonly help and log view etc.)
|
||||
if v.Type.readonly == false {
|
||||
if v.Type.Readonly == false {
|
||||
if !PreActionCall("Paste", v) {
|
||||
break
|
||||
}
|
||||
@@ -708,12 +789,17 @@ 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()
|
||||
}
|
||||
|
||||
@@ -777,11 +863,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
|
||||
|
||||
@@ -19,8 +19,8 @@ color-link line-number "#666666,#242424"
|
||||
color-link current-line-number "#666666,#242424"
|
||||
color-link gutter-error "#CB4B16,#242424"
|
||||
color-link gutter-warning "#E6DB74,#242424"
|
||||
color-link cursor-line "#2C2C2C"
|
||||
color-link color-column "#2C2C2C"
|
||||
color-link cursor-line "default,#2C2C2C"
|
||||
color-link color-column "default,#2C2C2C"
|
||||
#No extended types; Plain brackets.
|
||||
color-link type.extended "default"
|
||||
#color-link symbol.brackets "default"
|
||||
|
||||
26
runtime/colorschemes/railscast.micro
Normal file
26
runtime/colorschemes/railscast.micro
Normal file
@@ -0,0 +1,26 @@
|
||||
color-link default "#e6e1dc,#2b2b2b"
|
||||
color-link comment "#bc9458,#2b2b2b"
|
||||
color-link statement "#cc7833,#2b2b2b"
|
||||
color-link constant "#a5c261,#2b2b2b"
|
||||
color-link constant.bool "#6d9cbe,#2b2b2b"
|
||||
color-link type "#6d9cbe,#2b2b2b"
|
||||
color-link preproc "#cc7833,#2b2b2b"
|
||||
color-link special "#cc7833,#2b2b2b"
|
||||
color-link underlined "#cc7833,#2b2b2b"
|
||||
color-link todo "bold #cc7833,#2b2b2b"
|
||||
color-link error "bold #cc7833,#2b2b2b"
|
||||
color-link gutter-error "#cc7833,#11151C"
|
||||
color-link indent-char "#414141,#2b2b2b"
|
||||
color-link line-number "#a1a1a1,#353535"
|
||||
color-link current-line-number "#e6e1dc,#2b2b2b"
|
||||
color-link gutter-warning "#a5c261,#11151C"
|
||||
color-link symbol "#edb753,#2b2b2b"
|
||||
color-link identifier "#edb753,#2b2b2b"
|
||||
color-link statusline "#a1a1a1,#414141"
|
||||
color-link tabbar "bold #a1a1a1,#414141"
|
||||
color-link cursor-line "#353535"
|
||||
color-link color-column "#353535"
|
||||
color-link space "underline #e6e1dc,#2b2b2b"
|
||||
|
||||
#the Python syntax definition are wrong. This is not how you should do decorators!
|
||||
color-link brightgreen "#edb753,#2b2b2b"
|
||||
@@ -8,75 +8,68 @@ This help page aims to cover two aspects of micro's syntax highlighting engine:
|
||||
|
||||
## Colorschemes
|
||||
|
||||
To change your colorscheme, press Ctrl-E in micro to bring up the command
|
||||
prompt, and type:
|
||||
```
|
||||
set colorscheme solarized
|
||||
```
|
||||
(or whichever colorscheme you choose).
|
||||
|
||||
Micro comes with a number of colorschemes by default. Here is the list:
|
||||
|
||||
* simple: this is the simplest colorscheme. It uses 16 colors which are set by
|
||||
your terminal
|
||||
### 256 color
|
||||
|
||||
* mc: A 16-color theme based on the look and feel of GNU Midnight Commander.
|
||||
This will look great used in conjunction with Midnight Commander.
|
||||
|
||||
* nano: A 16-color theme loosely based on GNU nano's syntax highlighting.
|
||||
|
||||
* monokai: this is the monokai colorscheme; you may recognize it as Sublime
|
||||
These should work and look nice in most terminals. I recommend these
|
||||
themes the most.
|
||||
|
||||
* `monokai`: this is the monokai colorscheme; you may recognize it as Sublime
|
||||
Text's default colorscheme. It requires true color to look perfect, but the
|
||||
256 color approximation looks very good as well. It's also the default
|
||||
colorscheme.
|
||||
* `zenburn`
|
||||
* `gruvbox`
|
||||
* `darcula`
|
||||
* `twilight`
|
||||
* `railscast`
|
||||
* `bubblegum`: a light colorscheme
|
||||
|
||||
* zenburn: The 'zenburn' colorscheme and works well with 256 color terminals
|
||||
### 16 color
|
||||
|
||||
* solarized: this is the solarized colorscheme. You should have the solarized
|
||||
color palette in your terminal to use it.
|
||||
These may vary widely based on the 16 colors selected for your terminal.
|
||||
|
||||
* solarized-tc: this is the solarized colorscheme for true color; just make sure
|
||||
your terminal supports true color before using it and that the MICRO_TRUECOLOR
|
||||
environment variable is set to 1 before starting micro.
|
||||
|
||||
* atom-dark-tc: this colorscheme is based off of Atom's "dark" colorscheme. It
|
||||
requires true color to look good.
|
||||
|
||||
* cmc-16: A very nice 16-color theme. Written by contributor CaptainMcClellan
|
||||
(Collin Warren.) Licensed under the same license as the rest of the themes.
|
||||
|
||||
* cmc-paper: Basically cmc-16, but on a white background. (Actually light grey
|
||||
* `simple`: this is the simplest colorscheme. It uses 16 colors which are set by
|
||||
your terminal
|
||||
* `solarized`: You should have the solarized color palette in your terminal to use this colorscheme properly.
|
||||
* `cmc-16`
|
||||
* `cmc-paper`: cmc-16, but on a white background. (Actually light grey
|
||||
on most ANSI (16-color) terminals)
|
||||
* `geany`: Colorscheme based on geany's default highlighting.
|
||||
|
||||
* cmc-tc: A true colour variant of the cmc theme. It requires true color to
|
||||
### True color
|
||||
|
||||
These require terminals that support true color and require `MICRO_TRUECOLOR=1` (this is an environment variable).
|
||||
|
||||
* `solarized-tc`: this is the solarized colorscheme for true color.
|
||||
* `atom-dark-tc`: this colorscheme is based off of Atom's "dark" colorscheme.
|
||||
* `cmc-tc`: A true colour variant of the cmc theme. It requires true color to
|
||||
look its best. Use cmc-16 if your terminal doesn't support true color.
|
||||
* `gruvbox-tc`: The true color version of the gruvbox colorscheme
|
||||
* `github-tc`: The true color version of the Github colorscheme
|
||||
|
||||
* codeblocks: A colorscheme based on the Code::Blocks IDE's default syntax
|
||||
highlighting.
|
||||
### Monochrome
|
||||
|
||||
* codeblocks-paper: Same as codeblocks, but on a white background. (Actually
|
||||
light grey)
|
||||
|
||||
* github-tc: A colorscheme based on Github's syntax highlighting. Requires true
|
||||
color to look its best.
|
||||
|
||||
* paper-tc: A nice minimalist theme with a light background, good for editing
|
||||
documents on. Requires true color to look its best. Not to be confused with
|
||||
`-paper` suffixed themes.
|
||||
|
||||
* geany: Colorscheme based on geany's default highlighting.
|
||||
|
||||
* geany-alt-tc: Based on an alternate theme bundled with geany.
|
||||
|
||||
* flamepoint-tc: A fire inspired, high intensity true color theme written by
|
||||
CaptainMcClellan. As with all the other `-tc` suffixed themes, it looks its
|
||||
best on a
|
||||
|
||||
To enable one of these colorschemes just press CtrlE in micro and type
|
||||
`set colorscheme solarized`. (or whichever one you choose). You can also use
|
||||
`set colorscheme monochrome` if you'd prefer to have just the terminal's default
|
||||
You can also use `monochrome` if you'd prefer to have just the terminal's default
|
||||
foreground and background colors. Note: This provides no syntax highlighting!
|
||||
|
||||
### Other
|
||||
|
||||
See `help gimmickcolors` for a list of some true colour themes that are more
|
||||
just for fun than for serious use. (Though feel free if you want!)
|
||||
|
||||
|
||||
## Creating a Colorscheme
|
||||
|
||||
Micro's colorschemes are also extremely simple to create. The default ones ca
|
||||
Micro's colorschemes are also extremely simple to create. The default ones can
|
||||
be found
|
||||
[here](https://github.com/zyedidia/micro/tree/master/runtime/colorschemes).
|
||||
|
||||
|
||||
@@ -80,6 +80,16 @@ Here are the possible commands that you can use.
|
||||
* `retab`: Replaces all leading tabs with spaces or leading spaces with tabs
|
||||
depending on the value of `tabstospaces`.
|
||||
|
||||
* `raw`: Micro will open a new tab and show the escape sequence for every event
|
||||
it receives from the terminal. This shows you what micro actually sees from
|
||||
the terminal and helps you see which bindings aren't possible and why. This
|
||||
is most useful for debugging keybindings.
|
||||
|
||||
* `showkey`: Show the action(s) bound to a given key. For example
|
||||
running `> showkey CtrlC` will display `main.(*View).Copy`. Unfortuately
|
||||
showkey does not work well for keys bound to plugin actions. For those
|
||||
it just shows "LuaFunctionBinding."
|
||||
|
||||
---
|
||||
|
||||
The following commands are provided by the default plugins:
|
||||
|
||||
@@ -95,6 +95,7 @@ can change it!
|
||||
| Alt+P | Remove latest multiple cursor |
|
||||
| Alt+C | Remove all multiple cursors (cancel) |
|
||||
| Alt+X | Skip multiple cursor selection |
|
||||
| Alt+M | Spawn a new cursor at the beginning of every line in the current selection |
|
||||
| Ctrl-MouseLeft | Place a multiple cursor at any location |
|
||||
|
||||
### Other
|
||||
|
||||
@@ -5,16 +5,18 @@ Thank you for downloading and using micro.
|
||||
Micro is a terminal-based text editor that aims to be easy to use and intuitive,
|
||||
while also taking advantage of the full capabilities of modern terminals.
|
||||
|
||||
If you want to see all the keybindings press CtrlE and type `help keybindings`.
|
||||
|
||||
|
||||
For a list of the default keybindings press CtrlE and type `help defaultkeys`.
|
||||
For more information on keybindings see `> help keybindings`.
|
||||
|
||||
See the next section for more information about documentation and help.
|
||||
|
||||
|
||||
## Quick-start
|
||||
|
||||
Press CtrlQ to quit, and CtrlS to save. Press CtrlE to start typing commands and
|
||||
you can see which commands are available by pressing tab, or by viewing the help
|
||||
topic `> help commands`. When I write `> ...` I mean press Ctrl0E and then type
|
||||
topic `> help commands`. When I write `> ...` I mean press CtrlE and then type
|
||||
whatever is there.
|
||||
|
||||
Move the cursor around with the mouse or the arrow keys. Type
|
||||
|
||||
@@ -47,6 +47,33 @@ save and quit you can bind it like so:
|
||||
}
|
||||
```
|
||||
|
||||
## Binding commands
|
||||
|
||||
You can also bind a key to execute a command in command mode (see
|
||||
`help commands`). Simply prepend the binding with `command:`. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"Alt-p": "command:pwd"
|
||||
}
|
||||
```
|
||||
|
||||
Now when you press `Alt-p` the `pwd` command will be executed which will show
|
||||
your working directory in the infobar.
|
||||
|
||||
You can also bind an "editable" command with `command-edit:`. This means that
|
||||
micro won't immediately execute the command when you press the binding, but
|
||||
instead just place the string in the infobar in command mode. For example,
|
||||
you could rebind `CtrlG` to `> help`:
|
||||
|
||||
```json
|
||||
{
|
||||
"CtrlG": "command-edit:help "
|
||||
}
|
||||
```
|
||||
|
||||
Now when you press `CtrlG`, `help` will appear in the command bar and your cursor will
|
||||
be placed after it (note the space in the json that controls the cursor placement).
|
||||
|
||||
## Binding raw escape sequences
|
||||
|
||||
@@ -129,6 +156,7 @@ MoveLinesUp
|
||||
MoveLinesDown
|
||||
DeleteWordRight
|
||||
DeleteWordLeft
|
||||
SelectLine
|
||||
SelectToStartOfLine
|
||||
SelectToEndOfLine
|
||||
InsertNewline
|
||||
@@ -159,6 +187,8 @@ Start
|
||||
End
|
||||
PageUp
|
||||
PageDown
|
||||
SelectPageUp
|
||||
SelectPageDown
|
||||
HalfPageUp
|
||||
HalfPageDown
|
||||
StartOfLine
|
||||
@@ -183,14 +213,16 @@ HSplit
|
||||
PreviousSplit
|
||||
ToggleMacro
|
||||
PlayMacro
|
||||
Suspend (Linux only)
|
||||
Suspend (Unix only)
|
||||
ScrollUp
|
||||
ScrollDown
|
||||
SpawnMultiCursor
|
||||
SpawnMultiCursorSelect
|
||||
RemoveMultiCursor
|
||||
RemoveAllMultiCursors
|
||||
SkipMultiCursor
|
||||
UnbindKey
|
||||
JumpToMatchingBrace
|
||||
```
|
||||
|
||||
You can also bind some mouse actions (these must be bound to mouse buttons)
|
||||
@@ -322,6 +354,7 @@ CtrlRightSq
|
||||
CtrlCarat
|
||||
CtrlUnderscore
|
||||
Backspace
|
||||
OldBackspace
|
||||
Tab
|
||||
Esc
|
||||
Escape
|
||||
@@ -435,6 +468,7 @@ MouseWheelRight
|
||||
|
||||
// Multiple cursors bindings
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
|
||||
@@ -11,14 +11,19 @@ Here are the options that you can set:
|
||||
* `autoindent`: when creating a new line use the same indentation as the
|
||||
previous line.
|
||||
|
||||
default value: `on`
|
||||
default value: `true`
|
||||
|
||||
* `autosave`: micro will save the buffer every 8 seconds automatically. Micro
|
||||
also will automatically save and quit when you exit without asking. Be
|
||||
careful when using this feature, because you might accidentally save a file,
|
||||
overwriting what was there before.
|
||||
|
||||
default value: `off`
|
||||
default value: `false`
|
||||
|
||||
* `basename`: in the infobar, show only the basename of the file being edited
|
||||
rather than the full path.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `colorcolumn`: if this is not set to 0, it will display a column at the
|
||||
specified column. This is useful if you want column 80 to be highlighted
|
||||
@@ -44,7 +49,7 @@ Here are the options that you can set:
|
||||
* `cursorline`: highlight the line that the cursor is on in a different color
|
||||
(the color is defined by the colorscheme you are using).
|
||||
|
||||
default value: `on`
|
||||
default value: `true`
|
||||
|
||||
* `eofnewline`: micro will automatically add a newline to the file.
|
||||
|
||||
@@ -59,7 +64,7 @@ Here are the options that you can set:
|
||||
intensive. This option is only for people who really care about having
|
||||
accurate modified status.
|
||||
|
||||
default value: `on`
|
||||
default value: `true`
|
||||
|
||||
* `fileformat`: this determines what kind of line endings micro will use for the
|
||||
file. UNIX line endings are just `\n` (lf) whereas dos line endings are
|
||||
@@ -78,7 +83,7 @@ Here are the options that you can set:
|
||||
|
||||
* `ignorecase`: perform case-insensitive searches.
|
||||
|
||||
default value: `off`
|
||||
default value: `false`
|
||||
|
||||
* `indentchar`: sets the indentation character.
|
||||
|
||||
@@ -87,20 +92,20 @@ Here are the options that you can set:
|
||||
* `infobar`: enables the line at the bottom of the editor where messages are
|
||||
printed. This option is `global only`.
|
||||
|
||||
default value: `on`
|
||||
default value: `true`
|
||||
|
||||
* `keepautoindent`: when using autoindent, whitespace is added for you. This
|
||||
option determines if when you move to the next line without any insertions
|
||||
the whitespace that was added should be deleted. By default the autoindent
|
||||
whitespace is deleted if the line was left empty.
|
||||
|
||||
default value: `off`
|
||||
default value: `false`
|
||||
|
||||
* `keymenu`: display the nano-style key menu at the bottom of the screen. Note
|
||||
that ToggleKeyMenu is bound to `Alt-g` by default and this is displayed in
|
||||
the statusline. To disable this, simply by `Alt-g` to `UnbindKey`.
|
||||
|
||||
default value: `off`
|
||||
default value: `false`
|
||||
|
||||
* `mouse`: whether to enable mouse support. When mouse support is disabled,
|
||||
usually the terminal will be able to access mouse events which can be useful
|
||||
@@ -108,7 +113,7 @@ Here are the options that you can set:
|
||||
example, because the terminal has access to the local clipboard and micro
|
||||
does not).
|
||||
|
||||
default value: `on`
|
||||
default value: `true`
|
||||
|
||||
* `pluginchannels`: contains all the channels micro's plugin manager will search
|
||||
for plugins in. A channel is simply a list of 'repository' json files which
|
||||
@@ -129,26 +134,26 @@ Here are the options that you can set:
|
||||
|
||||
* `ruler`: display line numbers.
|
||||
|
||||
default value: `on`
|
||||
default value: `true`
|
||||
|
||||
* `savecursor`: remember where the cursor was last time the file was opened and
|
||||
put it there when you open the file again.
|
||||
|
||||
default value: `off`
|
||||
default value: `false`
|
||||
|
||||
* `savehistory`: remember command history between closing and re-opening
|
||||
micro.
|
||||
|
||||
default value: `on`
|
||||
default value: `true`
|
||||
|
||||
* `saveundo`: when this option is on, undo is saved even after you close a file
|
||||
so if you close and reopen a file, you can keep undoing.
|
||||
|
||||
default value: `off`
|
||||
default value: `false`
|
||||
|
||||
* `scrollbar`: display a scroll bar
|
||||
|
||||
default value: `off`
|
||||
default value: `false`
|
||||
|
||||
* `scrollmargin`: amount of lines you would like to see above and below the
|
||||
cursor.
|
||||
@@ -161,25 +166,35 @@ Here are the options that you can set:
|
||||
|
||||
* `softwrap`: should micro wrap lines that are too long to fit on the screen.
|
||||
|
||||
default value: `off`
|
||||
default value: `false`
|
||||
|
||||
* `splitbottom`: when a horizontal split is created, should it be created below
|
||||
the current split?
|
||||
|
||||
default value: `on`
|
||||
default value: `true`
|
||||
|
||||
* `splitright`: when a vertical split is created, should it be created to the
|
||||
right of the current split?
|
||||
|
||||
default value: `on`
|
||||
default value: `true`
|
||||
|
||||
* `statusline`: display the status line at the bottom of the screen.
|
||||
|
||||
default value: `on`
|
||||
default value: `true`
|
||||
|
||||
* `matchbrace`: highlight matching braces for '()', '{}', '[]'
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `matchbraceleft`: when matching a closing brace, should matching match the
|
||||
brace directly under the cursor, or the character to the left? only matters
|
||||
if `matchbrace` is true
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `syntax`: turns syntax on or off.
|
||||
|
||||
default value: `on`
|
||||
default value: `true`
|
||||
|
||||
* `sucmd`: specifies the super user command. On most systems this is "sudo" but
|
||||
on BSD it can be "doas." This option can be customized and is only used when
|
||||
@@ -191,7 +206,7 @@ Here are the options that you can set:
|
||||
(e.g. move over 4 spaces at once). This option only does anything if
|
||||
`tabstospaces` is on.
|
||||
|
||||
default value: `off`
|
||||
default value: `false`
|
||||
|
||||
* `tabsize`: sets the tab size to `option`
|
||||
|
||||
@@ -199,18 +214,18 @@ Here are the options that you can set:
|
||||
|
||||
* `tabstospaces`: use spaces instead of tabs
|
||||
|
||||
default value: `off`
|
||||
default value: `false`
|
||||
|
||||
* `termtitle`: defines whether or not your terminal's title will be set by micro
|
||||
when opened.
|
||||
|
||||
default value: `off`
|
||||
default value: `false`
|
||||
|
||||
* `useprimary` (only useful on *nix): defines whether or not micro will use the
|
||||
primary clipboard to copy selections in the background. This does not affect
|
||||
the normal clipboard using Ctrl-C and Ctrl-V.
|
||||
|
||||
default value: `on`
|
||||
default value: `true`
|
||||
|
||||
---
|
||||
|
||||
@@ -219,19 +234,19 @@ Default plugin options:
|
||||
* `autoclose`: automatically close `{}` `()` `[]` `""` `''`. Provided by the
|
||||
`autoclose` plugin
|
||||
|
||||
default value: `on`
|
||||
default value: `true`
|
||||
|
||||
* `ftoptions`: by default, micro will set some options based on the filetype. At
|
||||
the moment, micro will use tabs for makefiles and spaces for python and yaml
|
||||
files regardless of your settings. If you would like to disable this behavior
|
||||
turn this option off.
|
||||
|
||||
default value: `on`
|
||||
default value: `true`
|
||||
|
||||
* `linter`: Automatically lint when the file is saved. Provided by the `linter`
|
||||
plugin.
|
||||
|
||||
default value: `on`
|
||||
default value: `true`
|
||||
|
||||
Any option you set in the editor will be saved to the file
|
||||
~/.config/micro/settings.json so, in effect, your configuration file will be
|
||||
@@ -249,10 +264,25 @@ will set the option in all buffers.
|
||||
The `colorscheme` option is global only, and the `filetype` option is local
|
||||
only. To set an option locally, use `setlocal` instead of `set`.
|
||||
|
||||
In the `settings.json` file you can also put set options locally by specifying a
|
||||
glob. Here is an example which has `tabstospaces` on for all files except Go
|
||||
In the `settings.json` file you can also put set options locally by specifying either
|
||||
a glob or a filetype. Here is an example which has `tabstospaces` on for all files except Go
|
||||
files, and `tabsize` 4 for all files except Ruby files:
|
||||
|
||||
```json
|
||||
{
|
||||
"ft:go": {
|
||||
"tabstospaces": false
|
||||
},
|
||||
"ft:ruby": {
|
||||
"tabsize": 2
|
||||
},
|
||||
"tabstospaces": true,
|
||||
"tabsize": 4
|
||||
}
|
||||
```
|
||||
|
||||
Or similarly you can match with globs:
|
||||
|
||||
```json
|
||||
{
|
||||
"*.go": {
|
||||
@@ -265,6 +295,3 @@ files, and `tabsize` 4 for all files except Ruby files:
|
||||
"tabsize": 4
|
||||
}
|
||||
```
|
||||
|
||||
As you can see it is quite easy to set options locally using the `settings.json`
|
||||
file.
|
||||
|
||||
@@ -65,6 +65,9 @@ functions are given using Go's type system):
|
||||
* `NewBuffer(text, path string) *Buffer`: creates a new buffer from a given
|
||||
reader with a given path
|
||||
|
||||
* `NewBufferFromFile(path string) *Buffer`: creates a new buffer from a given
|
||||
path
|
||||
|
||||
* `GetLeadingWhitespace() bool`: returns the leading whitespace of the given
|
||||
string
|
||||
|
||||
@@ -108,11 +111,33 @@ functions are given using Go's type system):
|
||||
|
||||
* `HandleCommand(cmd string)`: runs the given command
|
||||
|
||||
* `HandleShellCommand(shellCmd string, interactive bool, waitToClose bool)`:
|
||||
runs the given shell command. The `interactive` bool specifies whether the
|
||||
command should run in the background. The `waitToClose` bool only applies if
|
||||
`interactive` is true and means that it should wait before returning to the
|
||||
editor.
|
||||
* `ExecCommand(name string, args []string) (string, error)`: exec a (shell) command with the
|
||||
given arguments. Returns the command's output and a possible error.
|
||||
|
||||
* `RunShellCommand(cmd string) (string, error)`: Run a shell command. This uses `ExecCommand`
|
||||
under the hood but also does some parsing for the arguments (i.e. quoted arguments). The
|
||||
function returns the command's output and a possible error.
|
||||
|
||||
* `RunBackgroundShell(cmd string)`: Run a shell command in the background.
|
||||
|
||||
* `RunInteractiveShell(cmd string, wait bool, getOutput bool) (string, error)`: Run a shell command
|
||||
by closing micro and running the command interactively. If `wait` is true, a prompt will be
|
||||
used after the process exits to prevent the terminal from immediately returning to micro, allowing
|
||||
the user to view the output of the process. If `getOutput` is true, the command's standard output
|
||||
will be returned. Note that if `getOutput` is true, some interactive commands may not behave
|
||||
normally because `isatty` will return false.
|
||||
|
||||
* `RunTermEmulator(cmd string, wait bool, getOutput bool, callback string) error`: Same as
|
||||
`RunInteractiveShell` except the command is run within the current split in a terminal emulator.
|
||||
The `callback` input is a string callback to a lua function which will be called when the process
|
||||
exits. The output of the process will be provided as the first and only argument to the callback
|
||||
(it will be empty if `getOutput` is false).
|
||||
Note that this functionality is only supported on some operating systems (linux, darwin, dragonfly,
|
||||
openbsd, freebsd). Use the `TermEmuSupported` (see below) boolean to determine if the current
|
||||
system is supported.
|
||||
|
||||
* `TermEmuSupported`: Boolean specifying if the terminal emulator is supported on the version of
|
||||
micro that is running.
|
||||
|
||||
* `ToCharPos(loc Loc, buf *Buffer) int`: returns the character position of a
|
||||
given x, y location
|
||||
|
||||
@@ -6,10 +6,12 @@ configure micro to your liking.
|
||||
|
||||
Hopefully you'll find this useful.
|
||||
|
||||
See `> help defaultkeys` for a list an explanation of the default keybindings.
|
||||
|
||||
### Plugins
|
||||
|
||||
Micro has a plugin manager which can automatically download plugins for you. To
|
||||
see the plugins 'official' plugins, go to github.com/micro-editor/plugin-channel.
|
||||
see the 'official' plugins, go to github.com/micro-editor/plugin-channel.
|
||||
|
||||
For example, if you'd like to install the snippets plugin (listed in that repo),
|
||||
just press `CtrlE` to execute a command, and type `plugin install snippets`.
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Stepets
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,109 +0,0 @@
|
||||
# utf8.lua
|
||||
one-file pure-lua 5.1 regex library
|
||||
|
||||
This library _is_ the simple way to add utf8 support into your application.
|
||||
|
||||
Some examples from http://www.lua.org/manual/5.1/manual.html#5.4 :
|
||||
```Lua
|
||||
local utf8 = require "utf8"
|
||||
|
||||
local s = "hello world from Lua"
|
||||
for w in string.gmatch(s, "%a+") do
|
||||
print(w)
|
||||
end
|
||||
--[[
|
||||
hello
|
||||
world
|
||||
from
|
||||
Lua
|
||||
]]--
|
||||
|
||||
s = "Привет, мир, от Lua"
|
||||
for w in utf8.gmatch(s, "[^%p%d%s%c]+") do
|
||||
print(w)
|
||||
end
|
||||
--[[
|
||||
Привет
|
||||
мир
|
||||
от
|
||||
Lua
|
||||
]]--
|
||||
|
||||
local t = {}
|
||||
s = "from=world, to=Lua"
|
||||
for k, v in string.gmatch(s, "(%w+)=(%w+)") do
|
||||
t[k] = v
|
||||
end
|
||||
for k,v in pairs(t) do
|
||||
print(k,v)
|
||||
end
|
||||
--[[
|
||||
to Lua
|
||||
from world
|
||||
]]--
|
||||
|
||||
t = {}
|
||||
s = "从=世界, 到=Lua"
|
||||
for k, v in utf8.gmatch(s, "([^%p%s%c]+)=([^%p%s%c]+)") do
|
||||
t[k] = v
|
||||
end
|
||||
for k,v in pairs(t) do
|
||||
print(k,v)
|
||||
end
|
||||
--[[
|
||||
到 Lua
|
||||
从 世界
|
||||
]]--
|
||||
|
||||
local x = string.gsub("hello world", "(%w+)", "%1 %1")
|
||||
print(x)
|
||||
--hello hello world world
|
||||
|
||||
x = utf8.gsub("Ahoj světe", "([^%p%s%c]+)", "%1 %1")
|
||||
print(x)
|
||||
--Ahoj Ahoj světe světe
|
||||
|
||||
x = string.gsub("hello world", "%w+", "%0 %0", 1)
|
||||
print(x)
|
||||
--hello hello world
|
||||
|
||||
x = utf8.gsub("Ahoj světe", "[^%p%s%c]+", "%0 %0", 1)
|
||||
print(x)
|
||||
--Ahoj Ahoj světe
|
||||
|
||||
x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1")
|
||||
print(x)
|
||||
--world hello Lua from
|
||||
|
||||
x = utf8.gsub("γεια κόσμο από Lua", "([^%p%s%c]+)%s*([^%p%s%c]+)", "%2 %1")
|
||||
print(x)
|
||||
--κόσμο γεια Lua από
|
||||
```
|
||||
Notice, there are some classes that can work only with latin(ASCII) symbols,
|
||||
for details see: https://github.com/Stepets/utf8.lua/blob/master/utf8.lua#L470
|
||||
|
||||
Of course you can do this trick:
|
||||
```Lua
|
||||
for k,v in pairs(utf8) do
|
||||
string[k] = v
|
||||
end
|
||||
```
|
||||
But this can lead to very strange errors. You were warned.
|
||||
|
||||
A little bit more interesting examples:
|
||||
```Lua
|
||||
local utf8 = require 'utf8'
|
||||
for k,v in pairs(utf8) do
|
||||
string[k] = v
|
||||
end
|
||||
|
||||
local str = "пыщпыщ ололоо я водитель нло"
|
||||
print(str:find("(.л.+)н"))
|
||||
-- 8 26 ололоо я водитель
|
||||
|
||||
print(str:gsub("ло+", "보라"))
|
||||
-- пыщпыщ о보라보라 я водитель н보라 3
|
||||
|
||||
print(str:match("^п[лопыщ ]*я"))
|
||||
-- пыщпыщ ололоо я
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,7 @@
|
||||
VERSION = "1.0.0"
|
||||
-- VERSION = "1.0.0"
|
||||
if GetOption("literate") == nil then
|
||||
AddOption("literate", true)
|
||||
end
|
||||
|
||||
function startswith(str, start)
|
||||
return string.sub(str,1,string.len(start))==start
|
||||
@@ -16,6 +19,7 @@ function split(string, sep)
|
||||
end
|
||||
|
||||
function onViewOpen(view)
|
||||
if not GetOption("literate") then return end
|
||||
if not endswith(view.Buf.Path, ".lit") then
|
||||
return
|
||||
end
|
||||
|
||||
129
runtime/syntax/PowerShell.yaml
Normal file
129
runtime/syntax/PowerShell.yaml
Normal file
@@ -0,0 +1,129 @@
|
||||
# PowerShell syntax highlighting file for micro - https://micro-editor.github.io/
|
||||
# PowerShell syntax taken from: https://github.com/PowerShell/EditorSyntax
|
||||
|
||||
filetype: powershell
|
||||
|
||||
detect:
|
||||
filename: "\\.ps(1|m1|d1)$"
|
||||
#header: ""
|
||||
|
||||
rules:
|
||||
# - comment.block: # Block Comment
|
||||
# - comment.doc: # Doc Comment
|
||||
# - comment.line: # Line Comment
|
||||
# - comment.shebang: # Shebang Line
|
||||
|
||||
# - constant: # Constant
|
||||
# - constant.bool: # Constant (true, false)
|
||||
# - constant.interpolation:
|
||||
# - constant.number: # Constant (null)
|
||||
# - constant.specialChar:
|
||||
# - constant.string: # String
|
||||
# - constant.string.char:
|
||||
# - constant.string.url: # Uri
|
||||
# - constant.unicode:
|
||||
|
||||
# - identifier: # Also used for functions
|
||||
# - identifier.class: # Also used for functions
|
||||
# - identifier.macro:
|
||||
# - identifier.var:
|
||||
|
||||
# - preproc: # Preprocessor
|
||||
# - preproc.DebugIdentifier: # Preprocessor
|
||||
# - preproc.shebang: # The #! at the beginning of a file that tells the os what script interpreter to use
|
||||
|
||||
# - special: # Special (global|local|private|script|using|workflow)
|
||||
|
||||
# - statement: # Statements Keywords
|
||||
# - statement.built_in:
|
||||
# - statement.declaration: # Declaration Keywords
|
||||
# - statement.meta: # Meta
|
||||
# - statement.reserved: # Reserved Keywords
|
||||
|
||||
# - symbol
|
||||
# - symbol.brackets: # {}()[] and sometimes <>
|
||||
# - symbol.operator: # Operators
|
||||
# - symbol.tag: # For html tags, among other things
|
||||
|
||||
# - type
|
||||
# - type.collections: # Collections (array, hashtable)
|
||||
# - type.ctypes: # CTypes (CBool, CChar, etc.)
|
||||
# - type.keyword: # If you want a special highlight for keywords like 'private'
|
||||
# - type.storage: # Storage Types (int, uint, string, etc.)
|
||||
|
||||
# Class
|
||||
- identifier.class: "class +[A-Za-z0-9]+ *((:) +[A-Za-z0-9.]+)?"
|
||||
- identifier.class: "(function)(?:([[:space:]][A-Za-z0-9]+[[:space:]]*))"
|
||||
|
||||
# Verbs taken from PwSh 6.0.2
|
||||
- identifier: "(Add|Approve|Assert|Backup|Block|Build|Checkpoint|Clear|Close|Compare|Complete|Compress|Confirm|Connect|Convert|ConvertFrom|ConvertTo|Copy)[-][A-Za-z0-9]+"
|
||||
- identifier: "(Debug|Deny|Deploy|Disable|Disconnect|Dismount|Edit|Enable|Enter|Exit|Expand|Export|Find|Format|Get|Grant|Group|Hide)[-][A-Za-z0-9]+"
|
||||
- identifier: "(Import|Initialize|Install|Invoke|Join|Limit|Lock|Measure|Merge|Mount|Move|New|Open|Optimize|Out|Ping|Pop|Protect|Publish|Push)[-][A-Za-z0-9]+"
|
||||
- identifier: "(Read|Receive|Redo|Register|Remove|Rename|Repair|Request|Reset|Resize|Resolve|Restart|Restore|Resume|Revoke)[-][A-Za-z0-9]+"
|
||||
- identifier: "(Save|Search|Select|Send|Set|Show|Skip|Split|Start|Step|Stop|Submit|Suspend|Switch|Sync|Test|Trace)[-][A-Za-z0-9]+"
|
||||
- identifier: "(Unblock|Undo|Uninstall|Unlock|Unprotect|Unpublish|Unregister|Update|Use|Wait|Watch|Write)[-][A-Za-z0-9]+"
|
||||
- identifier.var: "\\$(?i)((Global|Local|Private|Script|Using|Workflow)[:])?[A-Za-z0-9]*"
|
||||
|
||||
|
||||
# Expression and types
|
||||
- type: "\\[\\b([A-Za-z]+|[A-Za-z]+[0-9]+)\\b\\]"
|
||||
|
||||
# Keywords
|
||||
- statement: "\\b(alias|as|begin|break|catch|continue|data|default|define|do|dynamicparam)\\b"
|
||||
- statement: "\\b(else|elseif|end|exit|finally|for|foreach|foreach-object|from|if|in|inlinescript)\\b"
|
||||
- statement: "\\b(parallel|param|process|return|switch|throw|trap|try|until|using|var|where|where-object|while)\\b"
|
||||
|
||||
# Special Keywords
|
||||
- special: "\\b(break|continue|exit)\\b"
|
||||
|
||||
- symbol.brackets: "(\\{|\\})"
|
||||
- symbol.brackets: "(\\(|\\))"
|
||||
- symbol.brackets: "(\\[|\\])"
|
||||
- symbol.operator: "[\\-+/*=<>?:!~%&|]"
|
||||
- symbol.operator: "[[:space:]][-](ne|eq|gt|ge|lt|le|like|notlike|match|notmatch|contains|notcontains|in|notin|replace|is|isnot)[[:space:]]"
|
||||
|
||||
# Constants
|
||||
- constant.bool: "\\b\\$(true|false|null)\\b"
|
||||
- constant.number: "\\b([0-9._]+|0x[A-Fa-f0-9_]+|0b[0-1_]+)[FL]?\\b"
|
||||
|
||||
# Expression Mode String
|
||||
- constant.string:
|
||||
start: "\""
|
||||
end: "\""
|
||||
#skip: "\\\\."
|
||||
rules:
|
||||
- constant.specialChar: "\\\\([btnfr]|'|\\\"|\\\\)"
|
||||
- constant.specialChar: "\\\\u[A-Fa-f0-9]{4}"
|
||||
|
||||
# Argument Mode String
|
||||
- constant.string:
|
||||
start: "'"
|
||||
end: "'"
|
||||
#skip: "\\\\."
|
||||
rules:
|
||||
- constant.specialChar: "\\\\([btnfr]|'|\\\"|\\\\)"
|
||||
- constant.specialChar: "\\\\u[A-Fa-f0-9]{4}"
|
||||
|
||||
# Line Comment
|
||||
- comment:
|
||||
start: "#"
|
||||
end: "$"
|
||||
rules:
|
||||
- todo: "(TODO|XXX|FIXME|BUG):?"
|
||||
|
||||
# Block Comment
|
||||
- comment:
|
||||
start: "<#"
|
||||
end: "#>"
|
||||
rules:
|
||||
- todo: "(TODO|XXX|FIXME|BUG):?"
|
||||
|
||||
# Embedded C#
|
||||
- default:
|
||||
start: "@\""
|
||||
end: "\"@"
|
||||
rules:
|
||||
- include: "csharp"
|
||||
|
||||
# Todo
|
||||
- todo: "(TODO|XXX|FIXME|BUG):?"
|
||||
44
runtime/syntax/ada.yaml
Normal file
44
runtime/syntax/ada.yaml
Normal file
@@ -0,0 +1,44 @@
|
||||
filetype: ada
|
||||
|
||||
detect:
|
||||
filename: "(\\.ads$|\\.adb$|\\.ada$)"
|
||||
|
||||
rules:
|
||||
# Operators
|
||||
- symbol.operator: ([.:;,+*|=!?\\%]|<|>|/|-|&)
|
||||
- symbol.brackets: "[(){}]|\\[|\\]"
|
||||
# keyword.reserved
|
||||
- statement.reserved: \b(abort|abs|abstract|accept|access|aliased|all|and|array|at|begin|body|case)\b
|
||||
- statement.reserved: \b(constant|declare|delay|delta|digits|do|else|elsif|end|entry|exception|exit|for|function)\b
|
||||
- statement.reserved: \b(generic|goto|if|in|interface|is|limited|loop|mod|new|not|null|of|or|others|out|overriding)\b
|
||||
- statement.reserved: \b(package|pragma|private|procedure|protected|raise|range|record|rem|renames|return|requeue)\b
|
||||
- statement.reserved: \b(reverse|select|separate|some|subtype|synchronized|tagged|task|terminate|then|type|until)\b
|
||||
- statement.reserved: \b(use|when|while|with|xor)\b
|
||||
|
||||
# Constant
|
||||
- constant.bool: \b(TRUE|FALSE)
|
||||
- constant.number: ([0-9]+)
|
||||
|
||||
# Storage Types
|
||||
- type.storage: \b(INTEGER|NATURAL|POSITIVE|FLOAT|CHARACTER|STRING)\b
|
||||
- type.storage: \b(LONG_INTEGER|SHORT_INTEGER|LONG_FLOAT|SHORT_FLOAT)\b
|
||||
|
||||
#Character
|
||||
- constant.string.char: \'.\'
|
||||
|
||||
# String
|
||||
- constant.string:
|
||||
start: \"
|
||||
end: \"
|
||||
skip: \\.
|
||||
rules:
|
||||
- constant.specialChar: (\\0|\\\\|\\t|\\n|\\r|\\"|\\')
|
||||
- constant.interpolation: \\\([[:graph:]]*\)
|
||||
- constant.unicode: \\u\{[[:xdigit:]]+}
|
||||
|
||||
|
||||
# Line Comment
|
||||
- comment.line: "--.*"
|
||||
|
||||
# Todo
|
||||
- todo: "(TODO|XXX|FIXME):?"
|
||||
99
runtime/syntax/ats.yaml
Normal file
99
runtime/syntax/ats.yaml
Normal file
@@ -0,0 +1,99 @@
|
||||
filetype: ATS
|
||||
|
||||
detect:
|
||||
filename: "\\.(d|h|s)ats$"
|
||||
|
||||
rules:
|
||||
- default: \b[[:alnum:]]+[^0-9A-Za-z]
|
||||
|
||||
# Operators
|
||||
- symbol.operator: "[.:;+`|~<>?='\\&]|/|%|-|,|!|\\*|@|\\#"
|
||||
- symbol.operator: \^
|
||||
|
||||
# Types, abstract types and some predefined sorts
|
||||
- type: \b(addr|age?z|bool|char|cls|schar|uchar|double|ldouble|eff|exn|float|int(ptr)?|lincloptr|uint)\b
|
||||
- type: \b(lint|ulint|llint|ullint|nat|ptr|real|ref|size_t|ssize_t|sint|usint|string|tkind|viewt|v?t0p|vt|void)\b
|
||||
- type: \b(prop|t[@0]ype|type|viewt[@0]ype|viewtype|vt[@0]ype|vtype|view)\b
|
||||
- type: \b(prop[+-]|t[@0]ype[+-]|type[+-]|viewt[@0]ype[+-]|viewtype[+-]|vt[@0]ype[+-]|vtype[+-]|view[+-])
|
||||
|
||||
# Statements
|
||||
- statement: \b(abstype|abst|absprop|absviewt|absvt(ype)?|absview|and|andalso|as|(re)?assume|begin|(pr)?case|s?case)\b
|
||||
- statement: \b(classdec|dataprop|data(v|view)?type|dataview|datasort|do|dynload|else|end|exception|extype|extva(r|l)|s?if)\b
|
||||
- statement: \b(ifcase|import|for|in|infix(l|r)?|let|local|macrodef|macdef|not|of|op|or|orelse|overload|(pre|post|non)fix)\b
|
||||
- statement: \b(propdef|rec|sortdef|stacst|stadef|staload|stavar|sta(tic)?|symelim|symintr|tkindef|then|try|viewdef|v?typedef)\b
|
||||
- statement: \b(viewtypedef|(pr)?va(l|r)|when|where|while|with|withtype|withprop|withv(iew)?type|withview)\b
|
||||
- statement: \b(abst[@0]ype|absviewt[@0]?ype|absvt[@0]ype|abstbox|abstflat|absvtbox|absvtflat|absimpl|absreimpl|abs)\b
|
||||
- statement: \b(case[+-]|(pr)?va(l|r)[+-]|for\*|while\*)
|
||||
|
||||
# Numbers
|
||||
- constant.number: \b[0-9.]+([eE][+-]?[0-9]+)?[fFlL]?\b
|
||||
- constant.number: \b0[xX][0-9A-Fa-f]*(\.[0-9A-Fa-f]*)?[pP][+-]?[0-9]+[fFlL]?\b
|
||||
- constant.number: \b([0-9]+|0[xX][0-9A-Fa-f]+)[lLuU]*\b
|
||||
|
||||
# Function-related keywords, special functions and namespaces. Not really identifiers
|
||||
- identifier: \b(fix|(pr)?fu?n|fnx|castfn|praxi|extern|lam|llam|(pr)?implement|(pr)?implmnt)\b
|
||||
- identifier: \b(fix@|fold@|free@|lam@|llam@|addr@|view@|ref@|fn\*)
|
||||
- identifier: \$\w*\b
|
||||
|
||||
# Other keywords, function effects...
|
||||
- special: (\$(arrpsz|arrptrsize|break|continue|d2ctype|delay|effmask_(ntm|exn|ref|wrt|all)))\b
|
||||
- special: (\$(effmask|extern|extype_struct|extype|extkind|extval|extfcall|extmcall|ldelay|literal))\b
|
||||
- special: (\$(li?st_vt|li?st_t|li?st|myfilename|myfunction|mylocation|raise|rec(ord)?_vt))\b
|
||||
- special: (\$(rec(ord)?_t|rec(ord)?|showtype|solver_assert|solver_verify|tempenver))\b
|
||||
- special: (\$(tup(le)?_vt|tup(le)?_t|tup(le)?|tyrep|vararg|vcopyenv_vt|vcopyenv_v))\b
|
||||
- special: \!(wrt|exnref|exnwrt|exn|refwrt|ref|all|ntm|laz)\b
|
||||
- special: \b(fun(0|1)|(lin)?cloptr(0|1)?|cloref(0|1)?|clo(0|1)?|lin|prf)\b
|
||||
- special: \b(f?print(ln)?!|prerr(ln)?!|tupz!)
|
||||
|
||||
# Some C macros and other ATS macros
|
||||
- preproc: ^[[:space:]]*#[[:space:]]*(define|pragma|include|(un|ifn?)def|endif|el(if|se)|if|warning|error|assert)\b
|
||||
- preproc: ^[[:space:]]*#[[:space:]]*(codegen2|codegen3|elifdef|elifndef|prerr|print|require|then|staload|dynload)\b
|
||||
|
||||
# Boolean values
|
||||
- constant.bool: \b(true|false|null)\b
|
||||
|
||||
|
||||
# The "%{ ... %}" block inserts foreign code into ATS at compile-time
|
||||
# Code inside of it is generally C or JavaScript
|
||||
- default:
|
||||
start: "%{[#^$]?"
|
||||
end: "%}"
|
||||
skip: "\\."
|
||||
limit-group: symbol.operator
|
||||
rules:
|
||||
- include: "c"
|
||||
- include: "javascript"
|
||||
|
||||
|
||||
# Strings and chars
|
||||
- constant.string: \"[^"]*\"
|
||||
- constant.string: \'[^']*\'
|
||||
|
||||
|
||||
# Comments
|
||||
# "////" comments everything until it reaches EOF
|
||||
- comment.block:
|
||||
start: ////
|
||||
end: $a
|
||||
rules:
|
||||
- todo: (TODO|XXX|FIXME)
|
||||
|
||||
- comment.line:
|
||||
start: //
|
||||
end: $
|
||||
rules:
|
||||
- todo: (TODO|XXX|FIXME)
|
||||
|
||||
# ML-like block comment
|
||||
- comment.block:
|
||||
start: \(\*
|
||||
end: \*\)
|
||||
rules:
|
||||
- todo: (TODO|XXX|FIXME)
|
||||
|
||||
# C-like block comment
|
||||
- comment.block:
|
||||
start: /\*
|
||||
end: \*\/
|
||||
rules:
|
||||
- todo: (TODO|XXX|FIXME)
|
||||
30
runtime/syntax/elixir.yaml
Normal file
30
runtime/syntax/elixir.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
filetype: elixir
|
||||
|
||||
detect:
|
||||
filename: "\\.ex$|\\.exs$"
|
||||
|
||||
rules:
|
||||
- statement: "\\b(abs|trunc|rem|div|round|max|min|and|or|not|throw|raise|reraise|hd|tl|in|length|elem|put_elem|destructure|to_(string|charlist)|is_(atom|binary|bitstring|boolean|float|function|integer|list|map|nil|number|pid|port|reference|tuple)|(bit|byte|map|tuple)_size|binary_part|def(delegate|exception|guard|guardp|impl|macro|macrop|module|overridable|p|protocol|struct)?|sigil_[crswCRSWDNT]|if|else|unless|cond|binding|node|self|spawn|spawn_link|spawn_monitor|send|exit|struct|get_and_update_in|get_in|put_in|pop_in|update_in|apply|inspect|make_ref|use|do|end)\\b"
|
||||
- statement: "\\b(alias|import|require|case|fn|receive|after|try|catch|rescue|super|quote|unquote|unquote_splicing|for|with)\\b"
|
||||
|
||||
- constant: "\\b\\[A-Z]+\\b"
|
||||
- constant.number: "\\b[0-9]+\\b"
|
||||
|
||||
- constant.string: "`[^`]*`|%x\\{[^}]*\\}"
|
||||
- constant.string: "\"([^\"]|(\\\\\"))*\"|%[QW]?\\{[^}]*\\}|%[QW]?\\([^)]*\\)|%[QW]?<[^>]*>|%[QW]?\\[[^]]*\\]|%[QW]?\\$[^$]*\\$|%[QW]?\\^[^^]*\\^|%[QW]?![^!]*!"
|
||||
- constant.string: "'([^']|(\\\\'))*'|%[qw]\\{[^}]*\\}|%[qw]\\([^)]*\\)|%[qw]<[^>]*>|%[qw]\\[[^]]*\\]|%[qw]\\$[^$]*\\$|%[qw]\\^[^^]*\\^|%[qw]![^!]*!"
|
||||
|
||||
- symbol.brackets: "\\{|\\}|\\[|\\]|\\(|\\)"
|
||||
|
||||
- comment: "#[^{].*$|#$"
|
||||
- comment.bright: "##[^{].*$|##$"
|
||||
|
||||
- type.keyword: "\\:[a-zA-Z][a-zA-Z0-9_]*"
|
||||
- type.keyword: "\\b(describe|test)"
|
||||
- statement: "\\b(expected|assert|assert_raise|assert_in_delta|assert_received|catch_error|catch_throw|flunk|refute|refute_in_delta|refute_received)\\b"
|
||||
- symbol.tag: "^\\s*\\@[a-zA-Z][a-zA-Z0-9_]*\\b"
|
||||
|
||||
- identifier.macro: "\\b(__CALLER__|__DIR__|__ENV__|__MODULE__|__aliases__|__block__|defmacro)\\b"
|
||||
|
||||
- todo: "(XXX|TODO|FIXME|\\?\\?\\?)"
|
||||
- preproc.shebang: "\\W*#!.+?( |$)"
|
||||
48
runtime/syntax/fsharp.yaml
Normal file
48
runtime/syntax/fsharp.yaml
Normal file
@@ -0,0 +1,48 @@
|
||||
filetype: fsharp
|
||||
|
||||
detect:
|
||||
filename: "\\.fs?$"
|
||||
|
||||
rules:
|
||||
- identifier: "\\b[A-Z][0-9a-z_]{2,}\\b"
|
||||
#declarations
|
||||
- statement: "\\b(let|val|method|in|and|rec|private|virtual|constraint)\\b"
|
||||
#structure items
|
||||
- type: "\\b(type|open|class|module|exception|external)\\b"
|
||||
#patterns
|
||||
- statement: "\\b(fun|function|functor|match|try|with)\\b"
|
||||
#patterns-modifiers
|
||||
- statement: "\\b(as|when|of)\\b"
|
||||
#conditions
|
||||
- statement: "\\b(if|then|else)\\b"
|
||||
#blocs
|
||||
- type: "\\b(begin|end|object|struct|sig|for|while|do|done|to|downto)\\b"
|
||||
#constantes
|
||||
- constant.bool: "\\b(true|false)\\b"
|
||||
#modules/classes
|
||||
- special: "\\b(include|inherit|initializer)\\b"
|
||||
#expr modifiers
|
||||
- special: "\\b(new|ref|mutable|lazy|assert|raise)\\b"
|
||||
#keywords which don't exist in ocaml
|
||||
- type: "\\b(base|delegate|downcast|extern|finally|fixed|global|inline|interface|internal|let!|member|namespace|null|override|private|public)\\b"
|
||||
- type: "\\b(return|return!|select|static|upcast|use|use!|void|yield|yield!)\\b"
|
||||
- constant.string:
|
||||
start: "'"
|
||||
end: "'"
|
||||
skip: "\\\\."
|
||||
rules:
|
||||
- constant.specialChar: "%."
|
||||
- constant.specialChar: "\\\\[abfnrtv'\\\"\\\\]"
|
||||
- constant.specialChar: "\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})"
|
||||
- constant.string:
|
||||
start: "\""
|
||||
end: "\""
|
||||
skip: "\\\\."
|
||||
rules:
|
||||
- constant.specialChar: "%."
|
||||
- constant.specialChar: "\\\\[abfnrtv'\\\"\\\\]"
|
||||
- constant.specialChar: "\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})"
|
||||
- comment:
|
||||
start: "\\(\\*"
|
||||
end: "\\*\\)"
|
||||
rules: []
|
||||
@@ -1,29 +1,25 @@
|
||||
filetype: git-commit
|
||||
|
||||
detect:
|
||||
filename: "COMMIT_EDITMSG|TAG_EDITMSG"
|
||||
filename: "^(.*[\\/])?(COMMIT_EDITMSG|TAG_EDITMSG)$"
|
||||
|
||||
rules:
|
||||
# Commit message
|
||||
- ignore: ".*"
|
||||
# Comments
|
||||
- comment:
|
||||
start: "#"
|
||||
end: "$"
|
||||
rules: []
|
||||
# File changes
|
||||
- type.keyword: "#[[:space:]](deleted|modified|new file|renamed):[[:space:]].*"
|
||||
- type.keyword: "#[[:space:]]deleted:"
|
||||
- type.keyword: "#[[:space:]]modified:"
|
||||
- type.keyword: "#[[:space:]]new file:"
|
||||
- type.keyword: "#[[:space:]]renamed:"
|
||||
# Untracked filenames
|
||||
- error: "^# [^/?*:;{}\\\\]+\\.[^/?*:;{}\\\\]+$"
|
||||
- type.keyword: "^#[[:space:]]Changes.*[:]"
|
||||
- type.keyword: "^#[[:space:]]Your branch and '[^']+"
|
||||
- type.keyword: "^#[[:space:]]Your branch and '"
|
||||
- type.keyword: "^#[[:space:]]On branch [^ ]+"
|
||||
- type.keyword: "^#[[:space:]]On branch"
|
||||
# Recolor hash symbols
|
||||
- special: "#"
|
||||
# Color keywords for closing issues (such as on Github)
|
||||
- type.keyword: "\\b(?i)((fix(es|ed)?|close(s|d)?) #[0-9]+)\\b"
|
||||
|
||||
# Comments
|
||||
- comment.line:
|
||||
start: "^#"
|
||||
end: "$"
|
||||
rules: []
|
||||
|
||||
@@ -1,28 +1,19 @@
|
||||
filetype: git-rebase-todo
|
||||
|
||||
detect:
|
||||
filename: "git-rebase-todo"
|
||||
filename: "^(.*[\\/])?git\\-rebase\\-todo$"
|
||||
|
||||
rules:
|
||||
# Rebase commands
|
||||
- statement: "^(p(ick)?|r(eword)?|e(dit)?|s(quash)?|f(ixup)?|x|exec|d(rop)?)\\b"
|
||||
# Commit IDs
|
||||
- identifier: "\\b([0-9a-f]{7,40})\\b"
|
||||
|
||||
# Color keywords for Github (and others)
|
||||
- type.keyword: "\\b(?i)((fix(es|ed)?|close(s|d)?) #[0-9]+)\\b"
|
||||
|
||||
# Comments
|
||||
- comment:
|
||||
start: "#"
|
||||
- comment.line:
|
||||
start: "^#"
|
||||
end: "$"
|
||||
rules: []
|
||||
# Rebase commands
|
||||
- statement: "^(e|edit) [0-9a-f]{7,40}"
|
||||
- statement: "^# (e, edit)"
|
||||
- statement: "^(f|fixup) [0-9a-f]{7,40}"
|
||||
- statement: "^# (f, fixup)"
|
||||
- statement: "^(p|pick) [0-9a-f]{7,40}"
|
||||
- statement: "^# (p, pick)"
|
||||
- statement: "^(r|reword) [0-9a-f]{7,40}"
|
||||
- statement: "^# (r, reword)"
|
||||
- statement: "^(s|squash) [0-9a-f]{7,40}"
|
||||
- statement: "^# (s, squash)"
|
||||
- statement: "^(x|exec) [^ ]+ [0-9a-f]{7,40}"
|
||||
- statement: "^# (x, exec)"
|
||||
# Recolor hash symbols
|
||||
- special: "#"
|
||||
# Commit IDs
|
||||
- identifier: "[0-9a-f]{7,40}"
|
||||
|
||||
@@ -10,5 +10,15 @@ rules:
|
||||
- identifier: "^[[:space:]]*[^=]*="
|
||||
- special: "^[[:space:]]*\\[.*\\]$"
|
||||
- statement: "[=;]"
|
||||
- comment: "(^|[[:space:]])#([^{].*)?$"
|
||||
- constant.string: "\"(\\\\.|[^\"])*\"|'(\\\\.|[^'])*'"
|
||||
|
||||
- comment:
|
||||
start: "#"
|
||||
end: "$"
|
||||
rules:
|
||||
- todo: "(TODO|XXX|FIXME):?"
|
||||
- comment:
|
||||
start: ";"
|
||||
end: "$"
|
||||
rules:
|
||||
- todo: "(TODO|XXX|FIXME):?"
|
||||
|
||||
@@ -15,16 +15,20 @@ rules:
|
||||
- symbol.brackets: "(\\{|\\})"
|
||||
- symbol.brackets: "(\\(|\\))"
|
||||
- symbol.brackets: "(\\[|\\])"
|
||||
- statement: "\\b(break|case|catch|continue|default|delete|do|else|finally)\\b"
|
||||
- statement: "\\b(for|function|class|extends|get|if|in|instanceof|new|return|set|switch|async|await)\\b"
|
||||
- statement: "\\b(switch|this|throw|try|typeof|var|const|let|void|while|with)\\b"
|
||||
- symbol.operator: "[-+/*=<>!~%?:&|]"
|
||||
- statement: "\\b(async|await|break|case|catch|const|continue|debugger|default|delete|do|else|export|finally)\\b"
|
||||
- statement: "\\b(for|function|class|extends|get|if|import|in|instanceof|let|new|return|set)\\b"
|
||||
- statement: "\\b(super|switch|this|throw|try|typeof|var|void|while|with|yield)\\b"
|
||||
# reserved but unassigned
|
||||
- error: "\\b(enum|implements|interface|package|private|protected|public)"
|
||||
- constant: "\\b(null|undefined|NaN)\\b"
|
||||
- constant: "\\b(true|false)\\b"
|
||||
- type: "\\b(Array|Boolean|Date|Enumerator|Error|Function|Math)\\b"
|
||||
- type: "\\b(Number|Object|RegExp|String)\\b"
|
||||
- statement: "[-+/*=<>!~%?:&|]"
|
||||
- constant: "/[^*]([^/]|(\\\\/))*[^\\\\]/[gim]*"
|
||||
# - constant: "/[^*]([^/]|(\\\\/))*[^\\\\]/[gim]*"
|
||||
- constant: "\\\\[0-7][0-7]?[0-7]?|\\\\x[0-9a-fA-F]+|\\\\[bfnrt'\"\\?\\\\]"
|
||||
- comment: "^#!.*/(env +)?node( |$)"
|
||||
|
||||
|
||||
- constant.string:
|
||||
start: "\""
|
||||
@@ -40,6 +44,12 @@ rules:
|
||||
rules:
|
||||
- constant.specialChar: "\\\\."
|
||||
|
||||
- constant.string:
|
||||
start: "`"
|
||||
end: "`"
|
||||
rules:
|
||||
- constant.specialChar: "\\\\."
|
||||
|
||||
- comment:
|
||||
start: "//"
|
||||
end: "$"
|
||||
|
||||
@@ -13,7 +13,7 @@ rules:
|
||||
# definitions
|
||||
- identifier: "[A-Za-z_][A-Za-z0-9_]*[[:space:]]*[(]"
|
||||
# keywords
|
||||
- statement: "\\b(begin|break|catch|continue|function|elseif|else|end|finally|for|global|local|if|include|using|require|macro|println|return|try|type|while|module)\\b"
|
||||
- statement: "\\b(begin|break|catch|continue|function|elseif|struct|else|end|finally|for|global|local|const|if|include|import|using|require|macro|println|return|try|type|while|module)\\b"
|
||||
# decorators
|
||||
- identifier.macro: "@[A-Za-z0-9_]+"
|
||||
# operators
|
||||
|
||||
@@ -18,11 +18,12 @@ rules:
|
||||
- identifier: "coroutine\\.\\b(create|isyieldable|resume|running|status|wrap|yield)\\b"
|
||||
- identifier: "debug\\.\\b(debug|getfenv|gethook|getinfo|getlocal|getmetatable|getregistry|getupvalue|getuservalue|setfenv|sethook|setlocal|setmetatable|setupvalue|setuservalue|traceback|upvalueid|upvaluejoin)\\b"
|
||||
- identifier: "bit32\\.\\b(arshift|band|bnot|bor|btest|bxor|extract|replace|lrotate|lshift|rrotate|rshift)\\b"
|
||||
- identifier: "\\:\\b(close|flush|lines|read|seek|setvbuf|write)\\b"
|
||||
- identifier: "\\:\\b(close|flush|lines|read|seek|setvbuf|write|byte|char|dump|find|format|gmatch|gsub|len|lower|match|pack|packsize|rep|reverse|sub|unpack|upper)\\b"
|
||||
- identifier: "\\b(self|arg)\\b"
|
||||
- constant: "\\b(false|nil|true)\\b"
|
||||
- statement: "(\\b(dofile|require|include)|%q|%!|%Q|%r|%x)\\b"
|
||||
- constant.number: "\\b([0-9]+)\\b"
|
||||
- symbol: "(\\(|\\)|\\[|\\]|\\{|\\}|\\*\\*|\\*|/|%|\\+|-|\\^|>|>=|<|<=|~=|=|\\.\\.|#)"
|
||||
- symbol: "(\\(|\\)|\\[|\\]|\\{|\\}|\\*\\*|\\*|/|%|\\+|-|\\^|>|>=|<|<=|~=|=|[\\.]{2,3}|#)"
|
||||
|
||||
- constant.string:
|
||||
start: "\""
|
||||
@@ -46,13 +47,16 @@ rules:
|
||||
|
||||
- special: "\\\\[0-7][0-7][0-7]|\\\\x[0-9a-fA-F][0-9a-fA-F]|\\\\[abefnrs]|(\\\\c|\\\\C-|\\\\M-|\\\\M-\\\\C-)."
|
||||
|
||||
- comment.block:
|
||||
start: "\\-\\-\\[(\\=*|\\#*)\\["
|
||||
end: "\\-\\-\\](\\=*|\\#*)\\]"
|
||||
rules:
|
||||
- todo: "(TODO|NOTE|FIXME):?"
|
||||
|
||||
# this has to go after block comment or block comment does not work
|
||||
|
||||
- comment:
|
||||
start: "\\-\\-"
|
||||
end: "$"
|
||||
rules: []
|
||||
|
||||
- comment:
|
||||
start: "\\-\\-\\[\\["
|
||||
end: "\\]\\]"
|
||||
rules: []
|
||||
|
||||
rules:
|
||||
- todo: "(TODO|NOTE|FIXME):?"
|
||||
|
||||
@@ -7,8 +7,8 @@ detect:
|
||||
rules:
|
||||
- preproc: "\\<(ifeq|ifdef|ifneq|ifndef|else|endif)\\>"
|
||||
- statement: "^(export|include|override)\\>"
|
||||
- operator: "^[^:= ]+:"
|
||||
- operator: "([=,%]|\\+=|\\?=|:=|&&|\\|\\|)"
|
||||
- symbol.operator: "^[^:= ]+:"
|
||||
- symbol.operator: "([=,%]|\\+=|\\?=|:=|&&|\\|\\|)"
|
||||
- statement: "\\$\\((abspath|addprefix|addsuffix|and|basename|call|dir)[[:space:]]"
|
||||
- statement: "\\$\\((error|eval|filter|filter-out|findstring|firstword)[[:space:]]"
|
||||
- statement: "\\$\\((flavor|foreach|if|info|join|lastword|notdir|or)[[:space:]]"
|
||||
|
||||
@@ -6,13 +6,25 @@ detect:
|
||||
rules:
|
||||
- statement: "\\b(syntax|color(-link)?)\\b"
|
||||
- statement: "\\b(start=|end=)\\b"
|
||||
- identifier: "\\b(default|comment|symbol|identifier|constant(.string(.char)?|.number)?|statement|preproc|type|special|underlined|error|todo|statusline|indent-char|(current-)?line-number|gutter-error|gutter-warning|cursor-line|color-column)\\b"
|
||||
# Simple one-liners
|
||||
- identifier: "\\b(default|number|statement|underlined|error|todo|statusline|indent-char|cursor\\-line|color\\-column|ignore|divider|tabbar)\\b"
|
||||
# Separate identifiers to keep "complex" regex clean
|
||||
- identifier: "\\b(special(Char)?)\\b"
|
||||
- identifier: "\\b((current\\-)?line\\-number)\\b"
|
||||
- identifier: "\\b(gutter\\-(info|error|warning){1})\\b"
|
||||
- identifier: "\\b(comment(\\.bright)?)\\b"
|
||||
- identifier: "\\b(symbol(\\.(brackets|operator|tag))?)\\b"
|
||||
- identifier: "\\b(identifier(\\.(class|macro|var))?)\\b"
|
||||
- identifier: "\\b(constant(\\.(bool(\\.(true|false){1})?|number|specialChar|string(\\.url)?){1})?)\\b"
|
||||
- identifier: "\\b(preproc(\\.shebang)?)\\b"
|
||||
- identifier: "\\b(type(\\.keyword)?)\\b"
|
||||
- constant.number: "\\b(|h|A|0x)+[0-9]+(|h|A)+\\b"
|
||||
- constant.number: "\\b0x[0-9 a-f A-F]+\\b"
|
||||
- comment:
|
||||
start: "#"
|
||||
end: "$"
|
||||
rules: []
|
||||
rules:
|
||||
- todo: "(FIXME|TODO|NOTE):?"
|
||||
- constant.string:
|
||||
start: "\""
|
||||
end: "\""
|
||||
@@ -20,4 +32,3 @@ rules:
|
||||
rules:
|
||||
- constant.specialChar: "\\\\."
|
||||
- constant.number: "#[0-9 A-F a-f]+"
|
||||
|
||||
|
||||
@@ -4,23 +4,42 @@ detect:
|
||||
filename: "\\.mli?$"
|
||||
|
||||
rules:
|
||||
# Numbers
|
||||
## Integers
|
||||
### Binary
|
||||
- constant.number: "-?0[bB][01][01_]*"
|
||||
### Octal
|
||||
- constant.number: "-?0[oO][0-7][0-7_]*"
|
||||
### Decimal
|
||||
- constant.number: "-?\\d[\\d_]*"
|
||||
### Hexadecimal
|
||||
- constant.number: "-?0[xX][0-9a-fA-F][0-9a-fA-F_]*"
|
||||
## Real
|
||||
### Decimal
|
||||
- constant.number: "-?\\d[\\d_]*.\\d[\\d_]*([eE][+-]\\d[\\d_]*.\\d[\\d_]*)?"
|
||||
### Hexadecimal
|
||||
- constant.number: "-?0[xX][0-9a-fA-F][0-9a-fA-F_]*.[0-9a-fA-F][0-9a-fA-F_]*([pP][+-][0-9a-fA-F][0-9a-fA-F_]*.[0-9a-fA-F][0-9a-fA-F_]*)?"
|
||||
# Comments
|
||||
- identifier: "\\b[A-Z][0-9a-z_]{2,}\\b"
|
||||
#declarations
|
||||
- statement: "\\b(let|val|method|in|and|rec|private|virtual|constraint)\\b"
|
||||
#structure items
|
||||
- type: "\\b(type|open|class|module|exception|external)\\b"
|
||||
#patterns
|
||||
- statement: "\\b(fun|function|functor|match|try|with)\\b"
|
||||
#patterns-modifiers
|
||||
- statement: "\\b(as|when|of)\\b"
|
||||
#conditions
|
||||
- statement: "\\b(if|then|else)\\b"
|
||||
#blocs
|
||||
- type: "\\b(begin|end|object|struct|sig|for|while|do|done|to|downto)\\b"
|
||||
#constantes
|
||||
- constant.bool: "\\b(true|false)\\b"
|
||||
#modules/classes
|
||||
- special: "\\b(include|inherit|initializer)\\b"
|
||||
#expr modifiers
|
||||
- special: "\\b(new|ref|mutable|lazy|assert|raise)\\b"
|
||||
- constant.string:
|
||||
start: "'"
|
||||
end: "'"
|
||||
skip: "\\\\."
|
||||
rules:
|
||||
- constant.specialChar: "%."
|
||||
- constant.specialChar: "\\\\[abfnrtv'\\\"\\\\]"
|
||||
- constant.specialChar: "\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})"
|
||||
- constant.string:
|
||||
start: "\""
|
||||
end: "\""
|
||||
skip: "\\\\."
|
||||
rules:
|
||||
- constant.specialChar: "%."
|
||||
- constant.specialChar: "\\\\[abfnrtv'\\\"\\\\]"
|
||||
- constant.specialChar: "\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})"
|
||||
- comment:
|
||||
start: "\\(\\*"
|
||||
end: "\\*\\)"
|
||||
rules: []
|
||||
rules: []
|
||||
|
||||
@@ -18,10 +18,10 @@ rules:
|
||||
- comment: "<!--.+?-->"
|
||||
- default: "<\\?(php|=)\" end=\"\\?>"
|
||||
- identifier.class: "([a-zA-Z0-9_-]+)\\("
|
||||
- preproc: "(require|include|require_once|include_once)"
|
||||
- preproc: "\\b(require|include)(_once)?\\b"
|
||||
- type: "\\b(var|class|extends|function|echo|case|default|exit|switch|extends|as|define|do|declare|in|trait|interface|[E|e]xception|array|int|string|bool|iterable|void)\\b"
|
||||
- identifier.class: "[a-zA-Z\\\\]+::"
|
||||
- identifier: "([A-Z][a-zA-Z0-9_]+)\\s"
|
||||
- identifier: "\\b([A-Z][a-zA-Z0-9_]+)\\b"
|
||||
- identifier: "([A-Z0-9_]+)[;|\\s|\\)|,]"
|
||||
- type.keyword: "(global|public|private|protected|static|const)"
|
||||
- statement: "(implements|abstract|instanceof|if|else(if)?|endif|namespace|use|as|new|throw|catch|try|while|print|(end)?(foreach)?)\\b"
|
||||
|
||||
@@ -24,7 +24,6 @@ type MultiRule struct {
|
||||
func JoinRule(rule string) string {
|
||||
split := strings.Split(rule, `" "`)
|
||||
joined := strings.Join(split, "|")
|
||||
joined = joined
|
||||
return joined
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
filetype: tex
|
||||
|
||||
detect:
|
||||
filename: "\\.tex$|bib|\\.bib$|cls|\\.cls$"
|
||||
filename: "\\.tex$|\\.bib$|\\.cls$"
|
||||
|
||||
rules:
|
||||
# colorize the identifiers of {<identifier>} and [<identifier>]
|
||||
|
||||
@@ -12,11 +12,14 @@ apps:
|
||||
command: bin/micro
|
||||
|
||||
parts:
|
||||
go:
|
||||
source-tag: go1.10
|
||||
micro:
|
||||
after: [go]
|
||||
source: .
|
||||
source-type: git
|
||||
plugin: nil
|
||||
build-packages: [golang-go, make]
|
||||
build-packages: [make]
|
||||
prepare: |
|
||||
mkdir -p ../go/src/github.com/zyedidia/micro
|
||||
cp -R . ../go/src/github.com/zyedidia/micro
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
# This script creates the nightly release on Github for micro
|
||||
# You must have the correct Github access token to run this script
|
||||
|
||||
commitID=$(git rev-parse HEAD)
|
||||
info=$(github-release info -u zyedidia -r micro -t nightly)
|
||||
|
||||
if [[ $info = *$commitID* ]]; then
|
||||
echo "No new commits since last nightly"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Deleting old release"
|
||||
github-release delete \
|
||||
--user zyedidia \
|
||||
--repo micro \
|
||||
--tag nightly
|
||||
|
||||
commitID=$(git rev-parse HEAD)
|
||||
|
||||
echo "Moving tag"
|
||||
git tag --force nightly $commitID
|
||||
git push --force --tags
|
||||
|
||||
Reference in New Issue
Block a user