mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-31 15:17:15 +09:00
Compare commits
289 Commits
highlight-
...
settings-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
660d345880 | ||
|
|
ae05ff1811 | ||
|
|
43924646f6 | ||
|
|
79ee757757 | ||
|
|
006165230d | ||
|
|
ead07e0b60 | ||
|
|
140662f1ec | ||
|
|
44c1929f9d | ||
|
|
397fe634d7 | ||
|
|
2e3d08580e | ||
|
|
466889f540 | ||
|
|
63900cb395 | ||
|
|
07860b8973 | ||
|
|
b473fe458d | ||
|
|
8cf56bfc56 | ||
|
|
51050811eb | ||
|
|
14cd3cdbf8 | ||
|
|
51ab8f9914 | ||
|
|
afeb07a024 | ||
|
|
3fc9a8ad9e | ||
|
|
b05d3a5193 | ||
|
|
ffc922a7c5 | ||
|
|
eeab114ed5 | ||
|
|
8bd7e5807c | ||
|
|
9b59e07b47 | ||
|
|
00edf0207f | ||
|
|
a95b17a4e7 | ||
|
|
8956448fca | ||
|
|
a915cf9283 | ||
|
|
dd10869eca | ||
|
|
4356c7e434 | ||
|
|
2e770ce9ce | ||
|
|
381e1b639d | ||
|
|
cc09712d14 | ||
|
|
c5b0c2d41f | ||
|
|
bd43a44194 | ||
|
|
bfe68b1626 | ||
|
|
0064b8268f | ||
|
|
9a22d93ea2 | ||
|
|
5c8a2332d9 | ||
|
|
ff0683d6d0 | ||
|
|
79c0ea17ad | ||
|
|
bdff221870 | ||
|
|
65be5efd83 | ||
|
|
a491dd1c52 | ||
|
|
d7ab44253f | ||
|
|
0a6720498f | ||
|
|
a150eef6f9 | ||
|
|
c46257222c | ||
|
|
299af4a3db | ||
|
|
d0f7ecf9ca | ||
|
|
fb35e0312a | ||
|
|
30395b1f67 | ||
|
|
ddf70953fe | ||
|
|
55e97596d3 | ||
|
|
66dc48ce9b | ||
|
|
c490a94700 | ||
|
|
eff89a98a7 | ||
|
|
221d8f462a | ||
|
|
7a23878250 | ||
|
|
5d3e4fc3d9 | ||
|
|
f52fbfa1f0 | ||
|
|
d60626c64b | ||
|
|
aaac0b1e6f | ||
|
|
0f984131fb | ||
|
|
eb7189dcdb | ||
|
|
74523d28c5 | ||
|
|
f894f0a26e | ||
|
|
a067ce1f41 | ||
|
|
f59468642d | ||
|
|
85e85b7ccc | ||
|
|
8f5888e7bf | ||
|
|
f0da73bae2 | ||
|
|
d92deacf99 | ||
|
|
ffd7b5c770 | ||
|
|
bd8c0d25c8 | ||
|
|
052a36b896 | ||
|
|
a76bf02f5f | ||
|
|
92f4cb7ef7 | ||
|
|
1cf9537340 | ||
|
|
60c8c81da3 | ||
|
|
c76a973877 | ||
|
|
6def99ce24 | ||
|
|
26930ca81f | ||
|
|
cd379cd838 | ||
|
|
ee157f6503 | ||
|
|
48ca19873f | ||
|
|
fee5528309 | ||
|
|
671a188802 | ||
|
|
18d540583b | ||
|
|
943ea15fa3 | ||
|
|
2ef57977d7 | ||
|
|
527750b68d | ||
|
|
629efe5eb7 | ||
|
|
a19cd2e6d0 | ||
|
|
d038d3040f | ||
|
|
9e8d76f2fa | ||
|
|
8a9a14562f | ||
|
|
a6f5dee45c | ||
|
|
b12886b066 | ||
|
|
56f5b475eb | ||
|
|
c51f84955e | ||
|
|
e4bf1e9984 | ||
|
|
917e2922a1 | ||
|
|
59e8f3aa3e | ||
|
|
53bda0cfa7 | ||
|
|
f059541e0d | ||
|
|
d78fe81e21 | ||
|
|
25b9342fbe | ||
|
|
70bcf9f618 | ||
|
|
8848388411 | ||
|
|
dff8b33e9c | ||
|
|
8a2048e7f6 | ||
|
|
0174d7dba4 | ||
|
|
e1827480c9 | ||
|
|
d8584d1ddb | ||
|
|
f0cdc3cabb | ||
|
|
2ef4f83358 | ||
|
|
a9120ce270 | ||
|
|
190d9d0609 | ||
|
|
cf3fdb344a | ||
|
|
b91242124c | ||
|
|
5ffc19f159 | ||
|
|
cc994b6241 | ||
|
|
087e7207f7 | ||
|
|
db32b84cd1 | ||
|
|
00006aa2b4 | ||
|
|
600d8558b2 | ||
|
|
4874823240 | ||
|
|
6a0e4b5564 | ||
|
|
b2d7c8c5d4 | ||
|
|
38f88ade60 | ||
|
|
8348cc8ec2 | ||
|
|
743d42e417 | ||
|
|
faa207907c | ||
|
|
9d1c489574 | ||
|
|
30ed25859a | ||
|
|
d2288c5f66 | ||
|
|
a07ee26b05 | ||
|
|
a7ce85d6f6 | ||
|
|
7c71995aaf | ||
|
|
357c4b0fcd | ||
|
|
0d4f85304b | ||
|
|
e18e41eb45 | ||
|
|
5519f053ac | ||
|
|
2f4b3b2a8c | ||
|
|
ea290e4fb5 | ||
|
|
8b414e6187 | ||
|
|
e7ef81ed97 | ||
|
|
12c286f9b1 | ||
|
|
7b5bc8fe37 | ||
|
|
bad78797bb | ||
|
|
bf1258578c | ||
|
|
6588f02f7b | ||
|
|
5e9c6375d0 | ||
|
|
7d47659481 | ||
|
|
dcd4bae96f | ||
|
|
4f628bf30b | ||
|
|
36e83a46e4 | ||
|
|
1a64ffb88b | ||
|
|
7c77927913 | ||
|
|
8224037080 | ||
|
|
d7e3fc99f1 | ||
|
|
feaf3951d2 | ||
|
|
399c629076 | ||
|
|
47ed7447f1 | ||
|
|
62a98ea3c5 | ||
|
|
0a9194b883 | ||
|
|
a938917b9b | ||
|
|
695d4c2b1b | ||
|
|
34724b941a | ||
|
|
8176e8c6f8 | ||
|
|
0d5b1cd64d | ||
|
|
9fd5b133ea | ||
|
|
432b57a070 | ||
|
|
ee55732be3 | ||
|
|
90304fb472 | ||
|
|
f6aec1af5f | ||
|
|
b77b61d677 | ||
|
|
05a0598f16 | ||
|
|
daa6f122a7 | ||
|
|
71f5f043fb | ||
|
|
f3eaf99665 | ||
|
|
c88c1b84da | ||
|
|
e1e310a96e | ||
|
|
d29b941be0 | ||
|
|
cde696915d | ||
|
|
185b8de17b | ||
|
|
d65285ee54 | ||
|
|
848bd1ba8c | ||
|
|
00205aa6a7 | ||
|
|
ecb9fd5a8a | ||
|
|
bdf9e6d3a4 | ||
|
|
3ed77dbb2e | ||
|
|
57a992c4a3 | ||
|
|
c63614213d | ||
|
|
b7a54fa74a | ||
|
|
63046ae909 | ||
|
|
af48e4b79b | ||
|
|
4e73d0779b | ||
|
|
6f424f3213 | ||
|
|
e110e93e0f | ||
|
|
8ddf335e68 | ||
|
|
ca9d102267 | ||
|
|
c3d120ccdf | ||
|
|
a4a6445e1d | ||
|
|
13e30a63eb | ||
|
|
e561952781 | ||
|
|
9fe58bf84f | ||
|
|
e84f85340b | ||
|
|
aaf90c6f52 | ||
|
|
0962e1bfba | ||
|
|
2f45644d14 | ||
|
|
c2a2316c28 | ||
|
|
6957e83cdb | ||
|
|
ce91e41e5a | ||
|
|
6d99d34eb0 | ||
|
|
b77980082c | ||
|
|
2fd59adffa | ||
|
|
57c34e2248 | ||
|
|
6514b77e0d | ||
|
|
8a907956d1 | ||
|
|
de33eac058 | ||
|
|
c4bfa825a1 | ||
|
|
b0c50d371f | ||
|
|
b4d4119572 | ||
|
|
674258787e | ||
|
|
2354412922 | ||
|
|
3ac30343b8 | ||
|
|
37343350ca | ||
|
|
d9e9d4403f | ||
|
|
741f494841 | ||
|
|
69b6c724fc | ||
|
|
31936358c1 | ||
|
|
fe58ff5753 | ||
|
|
9aaafe5dcf | ||
|
|
c2bd5e4eec | ||
|
|
98ddb62af4 | ||
|
|
24a684cff2 | ||
|
|
b4e7e981f3 | ||
|
|
e73549c61a | ||
|
|
e759d4087b | ||
|
|
106ba48079 | ||
|
|
ef768e36f3 | ||
|
|
f5e1f93ee5 | ||
|
|
a52c0c2907 | ||
|
|
be7d27bc49 | ||
|
|
f6a9c482a6 | ||
|
|
6e3f38b271 | ||
|
|
8483f1da1e | ||
|
|
28ed47e358 | ||
|
|
6a1b8f4a4f | ||
|
|
dba8ef2fdd | ||
|
|
b0624cb66e | ||
|
|
09ea82c97e | ||
|
|
d94b81b8e6 | ||
|
|
bcb1947a0a | ||
|
|
b0b5d7b392 | ||
|
|
2598d8ad70 | ||
|
|
f731e422ea | ||
|
|
abcdeb01e9 | ||
|
|
d326a9cddd | ||
|
|
e3131a0779 | ||
|
|
46c5a81b0d | ||
|
|
59146cabb1 | ||
|
|
35e3bddea0 | ||
|
|
016b8dcc4c | ||
|
|
03228762d4 | ||
|
|
953f5a0eff | ||
|
|
063389afdf | ||
|
|
1e998ab0e4 | ||
|
|
0827968f6b | ||
|
|
89ac5d7de2 | ||
|
|
aae0f4630e | ||
|
|
612ebb2e17 | ||
|
|
26172b5101 | ||
|
|
51691ed7bf | ||
|
|
5acbccf0b2 | ||
|
|
87661ef308 | ||
|
|
58e11c0b2d | ||
|
|
c46695bb57 | ||
|
|
457a4f8f98 | ||
|
|
e3fd914e0b | ||
|
|
6aa5aa540b | ||
|
|
81bad4d089 | ||
|
|
9f4da789db | ||
|
|
a732d03b4d | ||
|
|
28267b9eb2 | ||
|
|
cad43914b0 |
2
.github/ISSUE_TEMPLATE
vendored
2
.github/ISSUE_TEMPLATE
vendored
@@ -2,7 +2,7 @@
|
||||
|
||||
## Specifications
|
||||
|
||||
You can use `micro -version` to get the commit hash.
|
||||
<!-- You can use `micro -version` to get the commit hash. -->
|
||||
|
||||
Commit hash:
|
||||
OS:
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -11,8 +11,10 @@ todo.txt
|
||||
test.txt
|
||||
log.txt
|
||||
*.old
|
||||
benchmark_results*
|
||||
tools/build-version
|
||||
tools/build-date
|
||||
tools/info-plist
|
||||
tools/bindata
|
||||
tools/vscode-tests/
|
||||
*.hdr
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016-2017: Zachary Yedidia, et al.
|
||||
Copyright (c) 2016-2020: Zachary Yedidia, et al.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Third party libraries
|
||||
Third party libraries directly used by micro and their licenses
|
||||
================
|
||||
|
||||
github.com/golang/go/LICENSE
|
||||
@@ -637,37 +637,6 @@ github.com/zyedidia/tcell/LICENSE (fork)
|
||||
limitations under the License.
|
||||
|
||||
|
||||
golang.org/x/net/LICENSE
|
||||
================
|
||||
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
golang.org/x/text/LICENSE
|
||||
================
|
||||
|
||||
@@ -1079,6 +1048,8 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
|
||||
github.com/flynn/json5/LICENSE
|
||||
================
|
||||
github.com/zyedidia/json5/LICENSE (fork)
|
||||
================
|
||||
|
||||
Decoder code based on package encoding/json from the Go language.
|
||||
|
||||
@@ -1189,82 +1160,6 @@ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
github.com/smartystreets/goconvey/LICENSE.md
|
||||
================
|
||||
|
||||
Copyright (c) 2016 SmartyStreets, LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
NOTE: Various optional and subordinate components carry their own licensing
|
||||
requirements and restrictions. Use of those components is subject to the terms
|
||||
and conditions outlined the respective license of each component.
|
||||
|
||||
github.com/smartystreets/assertions/LICENSE.md
|
||||
================
|
||||
|
||||
Copyright (c) 2016 SmartyStreets, LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
NOTE: Various optional and subordinate components carry their own licensing
|
||||
requirements and restrictions. Use of those components is subject to the terms
|
||||
and conditions outlined the respective license of each component.
|
||||
|
||||
github.com/jtolds/gls/LICENSE
|
||||
================
|
||||
|
||||
Copyright (c) 2013, Space Monkey, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
github.com/npat-efault/poller/LICENSE.txt
|
||||
================
|
||||
github.com/zyedidia/poller/LICENSE.txt (fork)
|
||||
@@ -1362,10 +1257,35 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
github.com/lucasb-eyer/go-colorful/LICENSE
|
||||
================
|
||||
github.com/kballard/go-shellquote/LICENSE
|
||||
===============
|
||||
|
||||
Copyright (c) 2013 Lucas Beyer
|
||||
Copyright (C) 2014 Kevin Ballard
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
github.com/stretchr/testify
|
||||
=================
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2012-2018 Mat Ryer and Tyler Bunnell
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -1374,8 +1294,8 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
|
||||
38
Makefile
38
Makefile
@@ -8,14 +8,19 @@ DATE = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
||||
ADDITIONAL_GO_LINKER_FLAGS = $(shell GOOS=$(shell go env GOHOSTOS) \
|
||||
GOARCH=$(shell go env GOHOSTARCH))
|
||||
GOBIN ?= $(shell go env GOPATH)/bin
|
||||
GOVARS = -X github.com/zyedidia/micro/internal/util.Version=$(VERSION) -X github.com/zyedidia/micro/internal/util.CommitHash=$(HASH) -X 'github.com/zyedidia/micro/internal/util.CompileDate=$(DATE)' -X github.com/zyedidia/micro/internal/util.Debug=OFF
|
||||
GOVARS = -X github.com/zyedidia/micro/v2/internal/util.Version=$(VERSION) -X github.com/zyedidia/micro/v2/internal/util.CommitHash=$(HASH) -X 'github.com/zyedidia/micro/v2/internal/util.CompileDate=$(DATE)'
|
||||
DEBUGVAR = -X github.com/zyedidia/micro/v2/internal/util.Debug=ON
|
||||
VSCODE_TESTS_BASE_URL = 'https://raw.githubusercontent.com/microsoft/vscode/e6a45f4242ebddb7aa9a229f85555e8a3bd987e2/src/vs/editor/test/common/model/'
|
||||
|
||||
# Builds micro after checking dependencies but without updating the runtime
|
||||
build:
|
||||
go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
|
||||
build-dbg:
|
||||
go build -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
go build -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS) $(DEBUGVAR)" ./cmd/micro
|
||||
|
||||
build-tags: fetch-tags
|
||||
go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
|
||||
# Builds micro after building the runtime and checking dependencies
|
||||
build-all: runtime build
|
||||
@@ -35,6 +40,9 @@ install-all: runtime install
|
||||
install-quick:
|
||||
go install -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
|
||||
fetch-tags:
|
||||
git fetch --tags
|
||||
|
||||
# Builds the runtime
|
||||
runtime:
|
||||
git submodule update --init
|
||||
@@ -44,8 +52,34 @@ runtime:
|
||||
mv runtime.go internal/config
|
||||
gofmt -w internal/config/runtime.go
|
||||
|
||||
testgen:
|
||||
mkdir -p tools/vscode-tests
|
||||
cd tools/vscode-tests && \
|
||||
curl --remote-name-all $(VSCODE_TESTS_BASE_URL){editableTextModelAuto,editableTextModel,model.line}.test.ts
|
||||
tsc tools/vscode-tests/*.ts > /dev/null; true
|
||||
go run tools/testgen.go tools/vscode-tests/*.js > buffer_generated_test.go
|
||||
mv buffer_generated_test.go internal/buffer
|
||||
gofmt -w internal/buffer/buffer_generated_test.go
|
||||
|
||||
test:
|
||||
go test ./internal/...
|
||||
|
||||
bench:
|
||||
for i in 1 2 3; do \
|
||||
go test -bench=. ./internal/...; \
|
||||
done > benchmark_results
|
||||
benchstat benchmark_results
|
||||
|
||||
bench-baseline:
|
||||
for i in 1 2 3; do \
|
||||
go test -bench=. ./internal/...; \
|
||||
done > benchmark_results_baseline
|
||||
|
||||
bench-compare:
|
||||
for i in 1 2 3; do \
|
||||
go test -bench=. ./internal/...; \
|
||||
done > benchmark_results
|
||||
benchstat benchmark_results_baseline benchmark_results
|
||||
|
||||
clean:
|
||||
rm -f micro
|
||||
|
||||
204
README.md
204
README.md
@@ -1,32 +1,35 @@
|
||||
# 
|
||||
<img alt="micro logo" src="./assets/micro-logo.svg" width="500px"/>
|
||||
|
||||
[](https://travis-ci.org/zyedidia/micro)
|
||||
[](https://goreportcard.com/report/github.com/zyedidia/micro)
|
||||
[](https://gitter.im/zyedidia/micro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://github.com/zyedidia/micro/releases)
|
||||
[](https://github.com/zyedidia/micro/blob/master/LICENSE)
|
||||
[](https://gitter.im/zyedidia/micro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://build.snapcraft.io/user/zyedidia/micro)
|
||||
|
||||
Micro is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the full capabilities
|
||||
of modern terminals. It comes as one single, batteries-included, static binary with no dependencies, and you can download and use it right now.
|
||||
**micro** is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the capabilities
|
||||
of modern terminals. It comes as a single, batteries-included, static binary with no dependencies; you can download and use it right now!
|
||||
|
||||
As the name indicates, micro aims to be somewhat of a successor to the nano editor by being easy to install and use in a pinch, but micro also aims to be
|
||||
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
|
||||
As its name indicates, micro aims to be somewhat of a successor to the nano editor by being easy to install and use.
|
||||
It strives to be enjoyable as a full-time editor for people who prefer to work in a terminal, or those who regularly edit files over SSH.
|
||||
|
||||
Here is a picture of micro editing its source code.
|
||||
|
||||

|
||||
|
||||
To see more screenshots of micro, showcasing all of the default colorschemes, see [here](http://zbyedidia.webfactional.com/micro/screenshots.html).
|
||||
To see more screenshots of micro, showcasing some of the default color schemes, see [here](http://zbyedidia.webfactional.com/micro/screenshots.html).
|
||||
|
||||
You can also check out the website for Micro at https://micro-editor.github.io.
|
||||
|
||||
# Table of Contents
|
||||
## Table of Contents
|
||||
|
||||
- [Features](#features)
|
||||
- [Installation](#installation)
|
||||
- [Prebuilt binaries](#prebuilt-binaries)
|
||||
- [Package Managers](#package-managers)
|
||||
- [Building from source](#building-from-source)
|
||||
- [MacOS terminal](#macos-terminal)
|
||||
- [Fully static binary](#fully-static-binary)
|
||||
- [macOS terminal](#macos-terminal)
|
||||
- [Linux clipboard support](#linux-clipboard-support)
|
||||
- [Colors and syntax highlighting](#colors-and-syntax-highlighting)
|
||||
- [Plan9, Cygwin, Mingw](#plan9-cygwin-mingw)
|
||||
@@ -36,62 +39,63 @@ You can also check out the website for Micro at https://micro-editor.github.io.
|
||||
|
||||
- - -
|
||||
|
||||
# Features
|
||||
## Features
|
||||
|
||||
* Easy to use and to install
|
||||
* No dependencies or external files are needed -- just the binary you can download further down the page
|
||||
* Multiple cursors
|
||||
* Common keybindings (ctrl-s, ctrl-c, ctrl-v, ctrl-z...)
|
||||
* Keybindings can be rebound to your liking
|
||||
* Sane defaults
|
||||
* You shouldn't have to configure much out of the box (and it is extremely easy to configure)
|
||||
* Splits and tabs
|
||||
* Nano-like menu to help you remember the keybindings
|
||||
* Extremely good mouse support
|
||||
* This means mouse dragging to create a selection, double click to select by word, and triple click to select by line
|
||||
* Cross platform (It should work on all the platforms Go runs on)
|
||||
* Note that while Windows is supported Mingw/Cygwin is not (see below)
|
||||
* Plugin system (plugins are written in Lua)
|
||||
* Persistent undo
|
||||
* Automatic linting and error notifications
|
||||
* Syntax highlighting (for over [120 languages](runtime/syntax)!)
|
||||
* Colorscheme support
|
||||
* By default, micro comes with 16, 256, and true color themes.
|
||||
* True color support (set the `MICRO_TRUECOLOR` environment variable to 1 to enable it)
|
||||
* Copy and paste with the system clipboard
|
||||
* Small and simple
|
||||
* Easily configurable
|
||||
* Macros
|
||||
* Common editor things such as undo/redo, line numbers, Unicode support, softwrap...
|
||||
- Easy to use and install.
|
||||
- No dependencies or external files are needed — just the binary you can download further down the page.
|
||||
- Multiple cursors.
|
||||
- Common keybindings (<kbd>Ctrl+S</kbd>, <kbd>Ctrl+C</kbd>, <kbd>Ctrl+V</kbd>, <kbd>Ctrl+Z</kbd>, …).
|
||||
- Keybindings can be rebound to your liking.
|
||||
- Sane defaults.
|
||||
- You shouldn't have to configure much out of the box (and it is extremely easy to configure).
|
||||
- Splits and tabs.
|
||||
- nano-like menu to help you remember the keybindings.
|
||||
- Extremely good mouse support.
|
||||
- This means mouse dragging to create a selection, double click to select by word, and triple click to select by line.
|
||||
- Cross-platform (it should work on all the platforms Go runs on).
|
||||
- Note that while Windows is supported Mingw/Cygwin is not (see below)
|
||||
- Plugin system (plugins are written in Lua).
|
||||
- micro has a built-in plugin manager to automatically install, remove, and update plugins.
|
||||
- Built-in diff gutter.
|
||||
- Simple autocompletion.
|
||||
- Persistent undo.
|
||||
- Automatic linting and error notifications.
|
||||
- Syntax highlighting for over [130 languages](runtime/syntax).
|
||||
- Color scheme support.
|
||||
- By default, micro comes with 16, 256, and true color themes.
|
||||
- True color support (set the `MICRO_TRUECOLOR` environment variable to 1 to enable it).
|
||||
- Copy and paste with the system clipboard.
|
||||
- Small and simple.
|
||||
- Easily configurable.
|
||||
- Macros.
|
||||
- Common editor features such as undo/redo, line numbers, Unicode support, soft wrapping, …
|
||||
|
||||
# Installation
|
||||
## Installation
|
||||
|
||||
To install micro, you can download a [prebuilt binary](https://github.com/zyedidia/micro/releases), or you can build it from source.
|
||||
|
||||
If you want more information about ways to install micro, see this [wiki page](https://github.com/zyedidia/micro/wiki/Installing-Micro).
|
||||
|
||||
Use `micro -version` to get the version information after installing. It is only guaranteed that you are installing the most recent
|
||||
stable version if you install from the prebuilt binaries, Homebrew, or Snap.
|
||||
|
||||
### Prebuilt binaries
|
||||
|
||||
All you need to install micro is one file, the binary itself. It's as simple as that!
|
||||
|
||||
Download the binary from the [releases](https://github.com/zyedidia/micro/releases) page.
|
||||
|
||||
On that page you'll see the nightly release, which contains binaries for micro which are built every night,
|
||||
and you'll see all the stable releases with the corresponding binaries.
|
||||
|
||||
If you'd like to see more information after installing micro, run `micro -version`.
|
||||
|
||||
### Installation script
|
||||
|
||||
There is a great script which can install micro for you by downloading the latest prebuilt binary. You can find it at https://getmic.ro (the github repo for it is [here](https://github.com/benweissmann/getmic.ro)).
|
||||
There is a script which can install micro for you by downloading the latest prebuilt binary. You can find it at <https://getmic.ro>.
|
||||
|
||||
Then you can easily install micro:
|
||||
You can easily install micro by running
|
||||
|
||||
$ curl https://getmic.ro | bash
|
||||
```bash
|
||||
curl https://getmic.ro | bash
|
||||
```
|
||||
|
||||
The script will install the micro binary to the current directory.
|
||||
|
||||
See the [Github page](https://github.com/benweissmann/getmic.ro) for more information.
|
||||
The script will place the micro binary in the current directory. See its [GitHub repository](https://github.com/benweissmann/getmic.ro) for more information.
|
||||
|
||||
### Package managers
|
||||
|
||||
@@ -101,22 +105,35 @@ You can install micro using Homebrew on Mac:
|
||||
brew install micro
|
||||
```
|
||||
|
||||
On Debian Linux, you can install micro through [snap](https://snapcraft.io/docs/core/install)
|
||||
**Note for Mac:** All micro keybindings use the control or alt (option) key, not the command
|
||||
key. By default, macOS terminals do not forward alt key events. To fix this, please see
|
||||
the section on [macOS terminals](https://github.com/zyedidia/micro#macos-terminal) further below.
|
||||
|
||||
On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/install)
|
||||
|
||||
```
|
||||
snap install micro --classic
|
||||
```
|
||||
|
||||
Homebrew and snap are the two "officially" maintained package manager distributions of micro.
|
||||
**Note for Linux:** for interfacing with the local system clipboard, `xclip` or `xsel`
|
||||
must be installed. Please see the section on [Linux clipboard support](https://github.com/zyedidia/micro#linux-clipboard-support)
|
||||
further below.
|
||||
|
||||
Micro is also available through other package managers on Linux such as AUR, Nix, and package managers
|
||||
for other operating systems:
|
||||
Micro is also available through other package managers on Linux such as apt, dnf, AUR, Nix, and package managers
|
||||
for other operating systems. These packages are not guaranteed to be up-to-date.
|
||||
|
||||
* Windows: [Chocolatey](https://chocolatey.org) and [Scoop](https://github.com/lukesampson/scoop)
|
||||
* `choco install micro`
|
||||
* `scoop install micro`
|
||||
* OpenBSD: Available in the ports tree and also available as a binary package
|
||||
* `pkd_add -v micro`
|
||||
* Linux: Available in distro-specific package managers.
|
||||
* `apt install micro` (Ubuntu 20.04 `focal`, and Debian `unstable | testing | buster-backports`).
|
||||
* `dnf install micro` (Fedora).
|
||||
* `yay -S micro` (Arch Linux).
|
||||
* See [wiki](https://github.com/zyedidia/micro/wiki/Installing-Micro) for details about CRUX, Termux.
|
||||
* Windows: [Chocolatey](https://chocolatey.org) and [Scoop](https://github.com/lukesampson/scoop).
|
||||
* `choco install micro`.
|
||||
* `scoop install micro`.
|
||||
* OpenBSD: Available in the ports tree and also available as a binary package.
|
||||
* `pkd_add -v micro`.
|
||||
* NetBSD, macOS, Linux, Illumos, etc. with [pkgsrc](http://www.pkgsrc.org/)-current:
|
||||
* `pkg_add micro`
|
||||
|
||||
### Building from source
|
||||
|
||||
@@ -137,54 +154,67 @@ anywhere you like (for example `/usr/local/bin`).
|
||||
The command `make install` will install the binary to `$GOPATH/bin` or `$GOBIN`.
|
||||
|
||||
You can install directly with `go get` (`go get github.com/zyedidia/micro/cmd/micro`) but this isn't
|
||||
recommended because it doesn't build micro with version information, and doesn't disable debug mode.
|
||||
recommended because it doesn't build micro with version information (necessary for the plugin manager),
|
||||
and doesn't disable debug mode.
|
||||
|
||||
### MacOS terminal
|
||||
### Fully static binary
|
||||
|
||||
If you are using MacOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default Mac terminal. The iTerm2 terminal has much better mouse support as well as better handling of key events. For best keybinding behavior, choose `xterm defaults` under `Preferences->Profiles->Keys->Load Preset`. The newest versions also support true color.
|
||||
By default, the micro binary will dynamically link with core system libraries (this is generally
|
||||
recommended for security and portability). However, there is a fully static prebuilt binary that
|
||||
is provided for amd64 as `linux-static.tar.gz`, and to build a fully static binary from source, run
|
||||
|
||||
```
|
||||
CGO_ENABLED=0 make build
|
||||
```
|
||||
|
||||
### macOS terminal
|
||||
|
||||
If you are using macOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default terminal (Terminal.app). The iTerm2 terminal has much better mouse support as well as better handling of key events. For best keybinding behavior, choose `xterm defaults` under `Preferences->Profiles->Keys->Load Preset`. The newest versions also support true color.
|
||||
|
||||
If you still insist on using the default Mac terminal, be sure to set `Use Option key as Meta key` under
|
||||
`Preferences->Profiles->Keyboard` to use <kbd>option</kbd> as <kbd>alt</kbd>.
|
||||
|
||||
### Linux clipboard support
|
||||
|
||||
On Linux, clipboard support requires the 'xclip' or 'xsel' commands to be installed.
|
||||
On Linux, clipboard support requires:
|
||||
|
||||
For Ubuntu:
|
||||
- On X11, the `xclip` or `xsel` commands (for Ubuntu: `sudo apt install xclip`)
|
||||
- On Wayland, the `wl-clipboard` command
|
||||
|
||||
```sh
|
||||
sudo apt-get install xclip
|
||||
```
|
||||
|
||||
If you don't have xclip or xsel, micro will use an internal clipboard for copy and paste, but it won't work with external applications.
|
||||
If you don't have these commands, micro will use an internal clipboard for copy and paste, but it won't work with external applications.
|
||||
|
||||
### Colors and syntax highlighting
|
||||
|
||||
If you open micro and it doesn't seem like syntax highlighting is working, this is probably because
|
||||
you are using a terminal which does not support 256 colors. Try changing the colorscheme to `simple`
|
||||
by pressing CtrlE in micro and typing `set colorscheme simple`.
|
||||
you are using a terminal which does not support 256 color mode. Try changing the color scheme to `simple`
|
||||
by pressing <kbd>Ctrl+E</kbd> in micro and typing `set colorscheme simple`.
|
||||
|
||||
If you are using the default Ubuntu terminal, to enable 256 make sure your `TERM` variable is set
|
||||
to `xterm-256color`.
|
||||
|
||||
Many of the Windows terminals don't support more than 16 colors, which means
|
||||
that micro's default colorscheme won't look very good. You can either set
|
||||
the colorscheme to `simple`, or download a better terminal emulator, like
|
||||
mintty.
|
||||
that micro's default color scheme won't look very good. You can either set
|
||||
the color scheme to `simple`, or download and configure a better terminal emulator
|
||||
than the Windows default.
|
||||
|
||||
### Plan9, Cygwin, Mingw
|
||||
### Cygwin, Mingw, Plan9
|
||||
|
||||
These platforms are unfortunately not supported.
|
||||
Cygwin, Mingw, and Plan9 are unfortunately not officially supported. In Cygwin and Mingw, micro will often work when run using
|
||||
the `winpty` utility:
|
||||
|
||||
```
|
||||
winpty micro.exe ...
|
||||
```
|
||||
|
||||
Micro uses the amazing [tcell library](https://github.com/gdamore/tcell), but this
|
||||
means that micro is restricted to the platforms tcell supports. As a result, micro does not support
|
||||
Plan9, and Cygwin (although this may change in the future). Micro also doesn't support NaCl (but NaCl is deprecated anyways).
|
||||
Plan9, and Cygwin (although this may change in the future). Micro also doesn't support NaCl (which is deprecated anyway).
|
||||
|
||||
# Usage
|
||||
## Usage
|
||||
|
||||
Once you have built the editor, simply start it by running `micro path/to/file.txt` or simply `micro` to open an empty buffer.
|
||||
Once you have built the editor, start it by running `micro path/to/file.txt` or `micro` to open an empty buffer.
|
||||
|
||||
Micro also supports creating buffers from `stdin`:
|
||||
micro also supports creating buffers from `stdin`:
|
||||
|
||||
```sh
|
||||
ifconfig | micro
|
||||
@@ -196,22 +226,22 @@ You can also use the mouse to manipulate the text. Simply clicking and dragging
|
||||
will select text. You can also double click to enable word selection, and triple
|
||||
click to enable line selection.
|
||||
|
||||
# Documentation and Help
|
||||
## Documentation and Help
|
||||
|
||||
Micro has a built-in help system which you can access by pressing `Ctrl-E` and typing `help`. Additionally, you can
|
||||
micro has a built-in help system which you can access by pressing <kbd>Ctrl+E</kbd> and typing `help`. Additionally, you can
|
||||
view the help files here:
|
||||
|
||||
* [main help](https://github.com/zyedidia/micro/tree/master/runtime/help/help.md)
|
||||
* [keybindings](https://github.com/zyedidia/micro/tree/master/runtime/help/keybindings.md)
|
||||
* [commands](https://github.com/zyedidia/micro/tree/master/runtime/help/commands.md)
|
||||
* [colors](https://github.com/zyedidia/micro/tree/master/runtime/help/colors.md)
|
||||
* [options](https://github.com/zyedidia/micro/tree/master/runtime/help/options.md)
|
||||
* [plugins](https://github.com/zyedidia/micro/tree/master/runtime/help/plugins.md)
|
||||
- [main help](https://github.com/zyedidia/micro/tree/master/runtime/help/help.md)
|
||||
- [keybindings](https://github.com/zyedidia/micro/tree/master/runtime/help/keybindings.md)
|
||||
- [commands](https://github.com/zyedidia/micro/tree/master/runtime/help/commands.md)
|
||||
- [colors](https://github.com/zyedidia/micro/tree/master/runtime/help/colors.md)
|
||||
- [options](https://github.com/zyedidia/micro/tree/master/runtime/help/options.md)
|
||||
- [plugins](https://github.com/zyedidia/micro/tree/master/runtime/help/plugins.md)
|
||||
|
||||
I also recommend reading the [tutorial](https://github.com/zyedidia/micro/tree/master/runtime/help/tutorial.md) for
|
||||
a brief introduction to the more powerful configuration features micro offers.
|
||||
|
||||
# Contributing
|
||||
## Contributing
|
||||
|
||||
If you find any bugs, please report them! I am also happy to accept pull requests from anyone.
|
||||
|
||||
@@ -219,3 +249,5 @@ You can use the [GitHub issue tracker](https://github.com/zyedidia/micro/issues)
|
||||
to report bugs, ask questions, or suggest new features.
|
||||
|
||||
For a more informal setting to discuss the editor, you can join the [Gitter chat](https://gitter.im/zyedidia/micro).
|
||||
|
||||
Sometimes I am unresponsive, and I apologize! If that happens, please ping me.
|
||||
|
||||
BIN
assets/logo.png
BIN
assets/logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,63 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg3336"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
width="128"
|
||||
height="128"
|
||||
viewBox="0 0 128 128"
|
||||
sodipodi:docname="logo.svg">
|
||||
<metadata
|
||||
id="metadata3342">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs3340" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1355"
|
||||
inkscape:window-height="717"
|
||||
id="namedview3338"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.6243169"
|
||||
inkscape:cx="111.32302"
|
||||
inkscape:cy="30.538264"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg3336" />
|
||||
<path
|
||||
style="fill:#2e3192;fill-opacity:1"
|
||||
d="m 56.1,127.32358 c -13.68932,-1.70993 -27.156628,-8.3544 -37.112903,-18.31068 -25.0687936,-25.068788 -25.0687936,-65.95701 0,-91.025803 25.068793,-25.0687936 65.957015,-25.0687936 91.025803,0 25.0688,25.068793 25.0688,65.957015 0,91.025803 C 95.87457,123.15123 76.198116,129.83404 56.1,127.32358 Z"
|
||||
id="path3364"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#ffffff"
|
||||
d="m 40.756452,106.01908 c 1.442831,-1.83426 1.55476,-4.09687 0.414499,-8.37899 -0.678184,-2.546844 -0.684604,-4.05591 -0.03829,-9 1.276867,-9.767604 4.483143,-23.040636 5.565559,-23.039766 0.220979,1.74e-4 0.417725,2.092674 0.437213,4.65 0.04167,5.468298 1.558564,9.06891 4.638769,11.010942 2.551646,1.608774 9.15365,1.329324 12.80399,-0.541974 3.245124,-1.663572 7.649064,-6.112434 9.850956,-9.951438 L 76.188736,67.7 l 0.0054,3.922866 c 0.0042,2.867148 0.36894,4.642788 1.355628,6.59796 1.532058,3.035856 3.323226,4.15755 6.659322,4.17033 5.192928,0.01986 9.07014,-3.668676 10.866768,-10.338036 0.98277,-3.64821 1.064448,-11.21265 0.09235,-8.55312 -3.025218,8.276592 -4.468212,9.893562 -9.238056,10.351884 -2.629152,0.25263 -3.177804,0.08883 -4.921776,-1.469412 -1.609044,-1.437678 -2.016072,-2.308416 -2.258508,-4.8315 -0.262884,-2.73585 0.105942,-4.06497 3.32007,-11.964365 C 88.28388,40.315087 89.33625,35.536248 87,33.2 c -1.559352,-1.559353 -3.62787,-1.522741 -5.691792,0.10074 -2.295762,1.805846 -3.105984,4.070756 -5.14293,14.376662 -2.464164,12.46744 -6.525822,20.297092 -12.62193,24.331306 C 59.052142,74.98085 52.704914,73.6403 50.637191,69.282896 49.19967,66.253544 49.857706,62.552972 53.387813,53.814319 56.613526,45.829186 58.8,38.711369 58.8,36.195564 c 0,-4.161283 -4.366993,-5.665719 -7.364438,-2.537061 -2.183558,2.279144 -3.117251,5.256959 -4.280897,13.653016 -0.547956,3.953665 -1.259292,9.010489 -1.580746,11.237387 -0.321454,2.226896 -2.083918,8.706896 -3.916587,14.400002 -4.33165,13.456074 -6.85029,23.184822 -7.273674,28.096022 -0.325586,3.77675 -0.269352,4.00056 1.319044,5.25 2.187498,1.72068 3.541408,1.64679 5.05375,-0.27585 z"
|
||||
id="path3362"
|
||||
inkscape:connector-curvature="0" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.6 KiB |
70
assets/micro-logo.svg
Normal file
70
assets/micro-logo.svg
Normal file
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 299.89999 103.2"
|
||||
enable-background="new 0 0 960 560"
|
||||
xml:space="preserve"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="micro-logo.svg"
|
||||
width="299.89999"
|
||||
height="103.2"><metadata
|
||||
id="metadata21"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs19" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1237"
|
||||
inkscape:window-height="867"
|
||||
id="namedview17"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="1.1416667"
|
||||
inkscape:cx="75.655934"
|
||||
inkscape:cy="-4"
|
||||
inkscape:window-x="1097"
|
||||
inkscape:window-y="185"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="Layer_1" /><g
|
||||
id="g3"
|
||||
transform="translate(-178,-172.8)"><path
|
||||
d="m 306.8,213.8 0,-2.6 c 1.6,-0.1 2.9,-0.4 4.1,-0.8 1.2,-0.4 2.5,-1 4,-1.8 l 2.3,0 0,5.2 c 2.4,-1.9 4.2,-3.1 5.5,-3.8 2,-1 4,-1.5 5.8,-1.5 1.3,0 2.5,0.2 3.7,0.7 1.2,0.5 2.2,1 2.9,1.7 0.7,0.7 1.4,1.6 1.9,2.8 2.2,-1.9 4.2,-3.3 6,-4 1.9,-0.8 3.7,-1.2 5.6,-1.2 1.8,0 3.4,0.4 4.8,1.1 1.4,0.8 2.4,1.7 3,2.8 0.6,1.1 0.9,2.8 0.9,5 l 0,14.4 c 0,1.5 0,2.4 0.1,2.6 0.1,0.4 0.3,0.8 0.7,1.1 0.3,0.4 0.7,0.6 1.2,0.7 0.4,0.1 1.2,0.2 2.4,0.2 l 1,0 0,2.6 -15.5,0 0,-2.6 c 1.8,0 2.9,-0.1 3.5,-0.4 0.5,-0.2 0.9,-0.6 1.2,-1.2 0.3,-0.6 0.4,-1.6 0.4,-3.2 l 0,-13.7 c 0,-1.7 -0.2,-2.9 -0.5,-3.6 -0.3,-0.7 -0.9,-1.2 -1.7,-1.7 -0.8,-0.4 -1.8,-0.7 -3,-0.7 -1.5,0 -3,0.4 -4.6,1.2 -2.2,1.1 -3.9,2.3 -5.1,3.6 l 0,14.8 c 0,1.4 0.1,2.4 0.2,2.8 0.1,0.4 0.4,0.8 0.7,1.1 0.3,0.3 0.7,0.5 1.1,0.6 0.4,0.1 1.5,0.2 3.1,0.2 l 0,2.6 -15.3,0 0,-2.6 0.9,0 c 1.2,0 2.1,-0.1 2.6,-0.4 0.5,-0.3 0.9,-0.7 1.2,-1.3 0.2,-0.5 0.3,-1.5 0.3,-2.9 l 0,-13.2 c 0,-1.9 -0.2,-3.3 -0.5,-3.9 -0.3,-0.7 -0.9,-1.3 -1.7,-1.7 -0.8,-0.5 -1.8,-0.7 -3,-0.7 -1.3,0 -2.7,0.3 -4.1,1 -2,1 -3.9,2.2 -5.6,3.8 l 0,15.9 c 0,1 0.1,1.6 0.4,2.1 0.3,0.4 0.7,0.8 1.2,1.1 0.6,0.3 1.3,0.4 2.3,0.4 l 1.1,0 0,2.6 -15.6,0 0,-2.6 0.8,0 c 1.4,0 2.4,-0.1 2.8,-0.3 0.7,-0.3 1.1,-0.8 1.4,-1.5 0.2,-0.4 0.2,-1.3 0.2,-2.9 l 0,-18.1 -5.1,0 z"
|
||||
id="path5"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 366.4,213.7 0,-2.6 c 1.7,-0.2 3.2,-0.5 4.3,-0.9 1.2,-0.4 2.5,-1 4,-1.7 l 2.3,0 0,24.9 c 0,0.9 0.1,1.5 0.4,2 0.2,0.4 0.6,0.8 1,0.9 0.4,0.2 1.3,0.3 2.4,0.3 l 1.5,0 0,2.6 -15.9,0 0,-2.6 1.3,0 c 1.4,0 2.3,-0.1 2.8,-0.4 0.5,-0.2 0.8,-0.6 1,-1.1 0.2,-0.5 0.3,-1.5 0.3,-3.2 l 0,-18.3 -5.4,0 z m 7.9,-19.2 c 1,0 1.8,0.3 2.5,1 0.7,0.7 1.1,1.5 1.1,2.5 0,1 -0.4,1.8 -1.1,2.5 -0.7,0.7 -1.6,1.1 -2.5,1.1 -1,0 -1.8,-0.4 -2.5,-1.1 -0.7,-0.7 -1.1,-1.6 -1.1,-2.5 0,-1 0.4,-1.8 1.1,-2.5 0.6,-0.6 1.5,-1 2.5,-1 z"
|
||||
id="path7"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 413.1,230.6 2,1.6 c -3.9,5.2 -8.6,7.8 -14,7.8 -4.2,0 -7.8,-1.5 -10.7,-4.5 -2.9,-3 -4.4,-6.8 -4.4,-11.3 0,-3 0.7,-5.7 2,-8.1 1.3,-2.4 3.2,-4.2 5.6,-5.6 2.4,-1.3 5.2,-2 8.3,-2 3.6,0 6.5,0.9 8.9,2.6 2.4,1.7 3.6,3.5 3.6,5.3 0,1 -0.3,1.7 -0.8,2.2 -0.5,0.5 -1.2,0.8 -1.9,0.8 -0.4,0 -0.7,-0.1 -1.1,-0.3 -0.4,-0.2 -0.7,-0.5 -1.1,-0.9 -0.2,-0.2 -0.5,-0.8 -0.9,-1.7 -0.6,-1.2 -1,-2 -1.3,-2.4 -0.6,-0.8 -1.4,-1.5 -2.4,-2 -0.9,-0.5 -2,-0.7 -3.1,-0.7 -1.8,0 -3.4,0.5 -4.9,1.5 -1.5,1 -2.7,2.4 -3.6,4.3 -0.9,1.9 -1.3,4.2 -1.3,6.8 0,4.1 1.1,7.3 3.3,9.7 1.9,2.1 4.1,3.1 6.7,3.1 1.2,0 2.4,-0.2 3.6,-0.6 1.2,-0.4 2.4,-1 3.5,-1.8 0.9,-0.6 2.2,-1.8 4,-3.8 z"
|
||||
id="path9"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 418.7,213.7 0,-2.6 c 1.5,-0.1 2.8,-0.4 4,-0.8 1.2,-0.4 2.5,-1 4,-1.9 l 2.3,0 0,5.9 c 1.5,-1.8 3.2,-3.2 5.1,-4.3 1.9,-1.1 3.7,-1.6 5.2,-1.6 1.5,0 2.7,0.4 3.6,1.1 0.9,0.7 1.3,1.6 1.3,2.6 0,0.7 -0.3,1.4 -0.9,2 -0.6,0.6 -1.3,0.9 -2.1,0.9 -0.4,0 -0.7,-0.1 -1,-0.2 -0.3,-0.1 -0.7,-0.3 -1.2,-0.7 -1.1,-0.7 -2.1,-1.1 -2.9,-1.1 -1,0 -2.2,0.4 -3.4,1.3 -1.6,1.1 -2.8,2.2 -3.7,3.3 l 0,14.4 c 0,1.2 0.1,2.1 0.2,2.5 0.1,0.4 0.4,0.8 0.7,1.1 0.3,0.3 0.7,0.6 1.1,0.7 0.5,0.1 1.3,0.2 2.4,0.2 l 1,0 0,2.6 -16,0 0,-2.6 1.3,0 c 1.3,0 2.1,-0.1 2.5,-0.3 0.5,-0.3 0.9,-0.7 1.2,-1.3 0.3,-0.5 0.4,-1.5 0.4,-3 l 0,-18.3 -5.1,0 z"
|
||||
id="path11"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 462.8,208.5 c 3,0 5.7,0.6 7.9,1.9 2.2,1.3 4,3.1 5.3,5.5 1.3,2.4 1.9,5.2 1.9,8.3 0,3.1 -0.7,5.9 -2,8.3 -1.3,2.4 -3.1,4.3 -5.4,5.5 -2.3,1.3 -5,1.9 -8.1,1.9 -5,0 -8.8,-1.6 -11.3,-4.7 -2.5,-3.1 -3.8,-6.8 -3.8,-11 0,-3.1 0.7,-5.8 2,-8.2 1.3,-2.4 3.1,-4.2 5.5,-5.6 2.4,-1.2 5.1,-1.9 8,-1.9 z m -0.2,3 c -2.4,0 -4.4,0.9 -6,2.8 -2.1,2.3 -3.1,5.7 -3.1,10.1 0,4.3 0.9,7.5 2.6,9.7 1.6,2 3.8,3 6.5,3 1.8,0 3.3,-0.5 4.7,-1.4 1.4,-0.9 2.5,-2.4 3.3,-4.5 0.8,-2 1.3,-4.4 1.3,-7.2 0,-2.7 -0.5,-5.1 -1.4,-7.2 -0.7,-1.7 -1.8,-3 -3.2,-4 -1.3,-0.8 -2.9,-1.3 -4.7,-1.3 z"
|
||||
id="path13"
|
||||
inkscape:connector-curvature="0" /></g><path
|
||||
d="M 51.6,0 C 23.1,0 0,23.1 0,51.6 c 0,28.5 23.1,51.6 51.6,51.6 28.5,0 51.6,-23.1 51.6,-51.6 C 103.2,23.1 80.1,0 51.6,0 Z m 24.5,58.6 c -0.5,2 -1.3,3.6 -2.4,4.9 -1,1.3 -2,2.1 -3.1,2.5 -1.1,0.4 -2.2,0.6 -3.4,0.6 -1.2,0 -2.2,-0.2 -3,-0.7 C 63.4,65.5 62.8,64.8 62.3,64 61.8,63.2 61.5,62.2 61.3,61.1 61.1,60 61,58.8 61,57.5 c 0,-0.5 0,-1 0.1,-1.7 0.1,-0.7 0.2,-1.6 0.3,-1.6 l -0.2,0 c -1.6,4 -3.8,6.9 -6.6,9.2 -2.8,2.3 -5.9,3.4 -9.3,3.4 -2.3,0 -4.2,-0.9 -5.5,-2.6 -1.4,-1.7 -2.1,-4.3 -2.1,-7.7 0,-0.5 0,-1 0.1,-1.6 0.1,-0.5 0.1,-0.7 0.2,-1.7 l -0.7,0 c -0.9,2 -1.7,4.8 -2.3,7.3 -0.6,2.5 -1.1,4.8 -1.4,6.9 -0.4,2.1 -0.6,4 -0.8,5.6 -0.2,1.6 -0.3,2.7 -0.4,3.3 0.1,0.5 0.2,1 0.3,1.6 0.2,0.6 0.3,1.2 0.5,1.7 0.2,0.5 0.3,1.1 0.4,1.6 0.1,0.5 0.2,0.9 0.2,1.2 0,1.4 -0.3,2.5 -0.9,3.2 -0.6,0.7 -1.3,1.1 -2,1.1 -0.9,0 -1.7,-0.3 -2.3,-0.8 -0.7,-0.6 -1,-1.5 -1,-2.7 0,-1.7 0.3,-3.9 0.9,-6.5 0.6,-2.6 1.5,-5.9 2.6,-9.8 0.6,-1.8 1.1,-3.6 1.7,-5.4 0.6,-1.8 1.1,-3.5 1.6,-5 0.5,-1.5 0.9,-2.9 1.3,-4.1 0.4,-1.2 0.6,-2.1 0.7,-2.8 0.1,-0.3 0.2,-1 0.3,-2 0.1,-1 0.2,-2.1 0.4,-3.4 0.2,-1.3 0.3,-2.7 0.5,-4.1 0.2,-1.5 0.4,-2.8 0.5,-4 0.2,-0.9 0.3,-1.9 0.5,-3 0.2,-1.1 0.5,-2.2 0.9,-3.1 0.4,-1 1,-1.8 1.7,-2.5 0.7,-0.7 1.6,-1 2.7,-1 1.2,0 2,0.4 2.4,1.1 0.4,0.7 0.6,1.6 0.5,2.6 -0.1,1 -0.2,2.1 -0.5,3.2 -0.3,1.1 -0.6,2.1 -0.9,2.9 -0.8,2.5 -1.6,4.8 -2.5,6.7 -0.9,1.9 -1.7,4 -2.4,6.2 -0.6,1.5 -0.8,2.9 -0.8,4.1 0,2.2 0.7,3.8 2,5 1.4,1.2 3,1.7 4.9,1.7 1.5,0 3,-0.5 4.4,-1.6 1.4,-1.1 2.7,-2.4 3.9,-3.9 1.2,-1.5 2.2,-3.1 3,-4.9 0.8,-1.7 1.4,-3.3 1.8,-4.6 0.1,-0.2 0.2,-0.6 0.3,-1.4 0.2,-0.8 0.3,-1.7 0.5,-2.7 0.2,-1 0.4,-2 0.6,-3.1 0.2,-1.1 0.4,-2 0.5,-2.7 0.2,-0.8 0.3,-1.6 0.5,-2.6 0.2,-1 0.5,-1.9 0.9,-2.8 0.4,-0.9 1,-1.6 1.6,-2.2 0.7,-0.6 1.5,-0.9 2.6,-0.9 1.3,0 2.1,0.4 2.6,1.1 0.4,0.7 0.6,1.6 0.6,2.6 -0.1,1 -0.2,2 -0.5,3 -0.3,1 -0.5,1.8 -0.7,2.4 -0.8,2.5 -1.6,4.7 -2.4,6.7 -0.8,2 -1.5,3.8 -2.2,5.2 -0.6,1.5 -1.1,2.6 -1.5,3.5 -0.4,0.9 -0.6,1.5 -0.6,1.8 0,2.6 0.6,4.5 1.7,5.6 1.1,1.1 2.3,1.7 3.6,1.7 2.2,0 3.9,-0.7 5.2,-2 1.3,-1.4 2.3,-3.9 2.9,-6.9 l 0.8,0 c 0.2,2.9 -0.1,5.3 -0.6,7.3 z"
|
||||
id="path15"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#2e3192" /></svg>
|
||||
|
After Width: | Height: | Size: 7.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 253 KiB |
@@ -1,11 +1,4 @@
|
||||
.\" micro manual page - micro(1)
|
||||
.\"
|
||||
.\" Copyright © 2017 Zachary Yedidia <zyedidia@gmail.com>
|
||||
.\" Copyright © 2017 Collin Warren <anatoly@somethinghub.com>
|
||||
.\"
|
||||
.\" This document is provided under the same licensing as micro.
|
||||
.\" See \usr\share\doc\micro\LICENSE for more information.
|
||||
.TH micro 1 "2017-03-28"
|
||||
.TH micro 1 "2020-02-10"
|
||||
.SH NAME
|
||||
micro \- A modern and intuitive terminal-based text editor
|
||||
.SH SYNOPSIS
|
||||
@@ -22,39 +15,96 @@ As the name indicates, micro aims to be somewhat of a successor to the nano edit
|
||||
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
|
||||
|
||||
.SH OPTIONS
|
||||
.PP
|
||||
\-clean
|
||||
.RS 4
|
||||
Cleans the configuration directory
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-config-dir dir
|
||||
.RS 4
|
||||
Specify a custom location for the configuration directory
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-startpos LINE,COL
|
||||
[FILE]:LINE:COL
|
||||
.RS 4
|
||||
Specify a line and column to start the cursor at when opening a buffer
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-options
|
||||
.RS 4
|
||||
Show all option help
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-debug
|
||||
.RS 4
|
||||
Enable debug mode (enables logging to ./log.txt)
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-version
|
||||
.RS 4
|
||||
Show the version number and information
|
||||
.RE
|
||||
|
||||
Micro's plugin's can be managed at the command line with the following commands.
|
||||
.RS 4
|
||||
|
||||
.PP
|
||||
\-plugin remove [PLUGIN]...
|
||||
.RS 4
|
||||
Remove plugin(s)
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-plugin update [PLUGIN]...
|
||||
.RS 4
|
||||
Update plugin(s) (if no argument is given, updates all plugins)
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-plugin search [PLUGIN]...
|
||||
.RS 4
|
||||
Search for a plugin
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-plugin list
|
||||
.RS 4
|
||||
List installed plugins
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-plugin available
|
||||
.RS 4
|
||||
List available plugins
|
||||
.RE
|
||||
.RE
|
||||
|
||||
Micro's options can also be set via command line arguments for quick
|
||||
adjustments. For real configuration, please use the settings.json
|
||||
file (see 'help options').
|
||||
.RS 4
|
||||
|
||||
.PP
|
||||
\-option value
|
||||
.RS 4
|
||||
Set `option` to `value` for this session
|
||||
For example: `micro -syntax off file.c`
|
||||
.RE
|
||||
|
||||
|
||||
.SH CONFIGURATION
|
||||
|
||||
Micro uses
|
||||
\fI$XDG_CONFIG_HOME/micro\fR
|
||||
for configuration by default. If it is not set, micro uses ~/.config/micro.
|
||||
Two main configuration files are settings.json, containing the user's
|
||||
settings, and bindings.json, containing the user's custom keybindings.
|
||||
|
||||
.SH ENVIRONMENT
|
||||
Micro's behaviour can be changed by setting environment variables, of which there is currently only one:
|
||||
\fIMICRO_TRUECOLOR\fR.
|
||||
When MICRO_TRUECOLOR is set to 1, micro will attempt to treat your terminal as a true-color terminal and will be able to make full use of the true-color colorschemes that are included with micro. If MICRO_TRUECOLOR is not set or is set to 0, then micro will only make use of 256 color features and will internally map true-color colorschemes to the nearest colors available. For more information see micro's documentation.
|
||||
Micro uses $MICRO_CONFIG_HOME as the configuration directory.
|
||||
If this environment variable is not set, it uses $XDG_CONFIG_HOME/micro instead.
|
||||
If that environment variable is not set, it uses ~/.config/micro as the configuration directory.
|
||||
In the documentation, we use ~/.config/micro to refer to the configuration directory
|
||||
(even if it may in fact be somewhere else if you have set either of the above environment variables).
|
||||
|
||||
.SH NOTICE
|
||||
This manpage is intended only to serve as a quick guide to the invocation of
|
||||
@@ -69,5 +119,5 @@ and to report any newly encountered bugs you may find. We strive to correct
|
||||
bugs as swiftly as possible.
|
||||
|
||||
.SH COPYRIGHT
|
||||
Copyright \(co 2017 Zachary Yedidia, Collin Warren, et al.
|
||||
Copyright \(co 2020 Zachary Yedidia, et al.
|
||||
See /usr/share/doc/micro/LICENSE and /usr/share/doc/micro/AUTHORS for more information.
|
||||
|
||||
143
cmd/micro/clean.go
Normal file
143
cmd/micro/clean.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
)
|
||||
|
||||
func shouldContinue() bool {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Print("Continue [Y/n]: ")
|
||||
text, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return false
|
||||
}
|
||||
|
||||
if len(text) <= 1 {
|
||||
// default continue
|
||||
return true
|
||||
}
|
||||
|
||||
return strings.ToLower(text)[0] == 'y'
|
||||
}
|
||||
|
||||
// CleanConfig performs cleanup in the user's configuration directory
|
||||
func CleanConfig() {
|
||||
fmt.Println("Cleaning your configuration directory at", config.ConfigDir)
|
||||
fmt.Printf("Please consider backing up %s before continuing\n", config.ConfigDir)
|
||||
|
||||
if !shouldContinue() {
|
||||
fmt.Println("Stopping early")
|
||||
return
|
||||
}
|
||||
|
||||
// detect unused options
|
||||
var unusedOptions []string
|
||||
defaultSettings := config.DefaultAllSettings()
|
||||
for k := range config.GlobalSettings {
|
||||
if _, ok := defaultSettings[k]; !ok {
|
||||
valid := false
|
||||
for _, p := range config.Plugins {
|
||||
if strings.HasPrefix(k, p.Name+".") || k == p.Name {
|
||||
valid = true
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
unusedOptions = append(unusedOptions, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(unusedOptions) > 0 {
|
||||
fmt.Println("The following options are unused:")
|
||||
|
||||
sort.Strings(unusedOptions)
|
||||
|
||||
for _, s := range unusedOptions {
|
||||
fmt.Printf("%s (value: %v)\n", s, config.GlobalSettings[s])
|
||||
}
|
||||
|
||||
fmt.Printf("These options will be removed from %s\n", filepath.Join(config.ConfigDir, "settings.json"))
|
||||
|
||||
if shouldContinue() {
|
||||
for _, s := range unusedOptions {
|
||||
delete(config.GlobalSettings, s)
|
||||
}
|
||||
|
||||
err := config.OverwriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
|
||||
if err != nil {
|
||||
fmt.Println("Error writing settings.json file: " + err.Error())
|
||||
}
|
||||
|
||||
fmt.Println("Removed unused options")
|
||||
fmt.Print("\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
// detect incorrectly formatted buffer/ files
|
||||
files, err := ioutil.ReadDir(filepath.Join(config.ConfigDir, "buffers"))
|
||||
if err == nil {
|
||||
var badFiles []string
|
||||
var buffer buffer.SerializedBuffer
|
||||
for _, f := range files {
|
||||
fname := filepath.Join(config.ConfigDir, "buffers", f.Name())
|
||||
file, e := os.Open(fname)
|
||||
|
||||
if e == nil {
|
||||
defer file.Close()
|
||||
|
||||
decoder := gob.NewDecoder(file)
|
||||
err = decoder.Decode(&buffer)
|
||||
|
||||
if err != nil && f.Name() != "history" {
|
||||
badFiles = append(badFiles, fname)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(badFiles) > 0 {
|
||||
fmt.Printf("Detected %d files with an invalid format in %s\n", len(badFiles), filepath.Join(config.ConfigDir, "buffers"))
|
||||
fmt.Println("These files store cursor and undo history.")
|
||||
fmt.Printf("Removing badly formatted files in %s\n", filepath.Join(config.ConfigDir, "buffers"))
|
||||
|
||||
if shouldContinue() {
|
||||
for _, f := range badFiles {
|
||||
err := os.Remove(f)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Removed badly formatted files")
|
||||
fmt.Print("\n\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// detect plugins/ directory
|
||||
plugins := filepath.Join(config.ConfigDir, "plugins")
|
||||
if stat, err := os.Stat(plugins); err == nil && stat.IsDir() {
|
||||
fmt.Printf("Found directory %s\n", plugins)
|
||||
fmt.Printf("Plugins should now be stored in %s\n", filepath.Join(config.ConfigDir, "plug"))
|
||||
fmt.Printf("Removing %s\n", plugins)
|
||||
|
||||
if shouldContinue() {
|
||||
os.RemoveAll(plugins)
|
||||
}
|
||||
|
||||
fmt.Print("\n\n")
|
||||
}
|
||||
|
||||
fmt.Println("Done cleaning")
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// NullWriter simply sends writes into the void
|
||||
|
||||
@@ -6,14 +6,14 @@ import (
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
"github.com/zyedidia/micro/internal/action"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/display"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/shell"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/action"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -21,6 +21,7 @@ func init() {
|
||||
ulua.L.SetGlobal("import", luar.New(ulua.L, LuaImport))
|
||||
}
|
||||
|
||||
// LuaImport is meant to be called from lua by a plugin and will import the given micro package
|
||||
func LuaImport(pkg string) *lua.LTable {
|
||||
switch pkg {
|
||||
case "micro":
|
||||
@@ -46,6 +47,15 @@ func luaImportMicro() *lua.LTable {
|
||||
ulua.L.SetField(pkg, "InfoBar", luar.New(ulua.L, action.GetInfoBar))
|
||||
ulua.L.SetField(pkg, "Log", luar.New(ulua.L, log.Println))
|
||||
ulua.L.SetField(pkg, "SetStatusInfoFn", luar.New(ulua.L, display.SetStatusInfoFnLua))
|
||||
ulua.L.SetField(pkg, "CurPane", luar.New(ulua.L, func() action.Pane {
|
||||
return action.MainTab().CurPane()
|
||||
}))
|
||||
ulua.L.SetField(pkg, "CurTab", luar.New(ulua.L, func() *action.Tab {
|
||||
return action.MainTab()
|
||||
}))
|
||||
ulua.L.SetField(pkg, "Tabs", luar.New(ulua.L, func() *action.TabList {
|
||||
return action.Tabs
|
||||
}))
|
||||
|
||||
return pkg
|
||||
}
|
||||
@@ -53,7 +63,7 @@ func luaImportMicro() *lua.LTable {
|
||||
func luaImportMicroConfig() *lua.LTable {
|
||||
pkg := ulua.L.NewTable()
|
||||
|
||||
ulua.L.SetField(pkg, "MakeCommand", luar.New(ulua.L, action.LuaMakeCommand))
|
||||
ulua.L.SetField(pkg, "MakeCommand", luar.New(ulua.L, action.MakeCommand))
|
||||
ulua.L.SetField(pkg, "FileComplete", luar.New(ulua.L, buffer.FileComplete))
|
||||
ulua.L.SetField(pkg, "HelpComplete", luar.New(ulua.L, action.HelpComplete))
|
||||
ulua.L.SetField(pkg, "OptionComplete", luar.New(ulua.L, action.OptionComplete))
|
||||
@@ -62,19 +72,21 @@ func luaImportMicroConfig() *lua.LTable {
|
||||
ulua.L.SetField(pkg, "TryBindKey", luar.New(ulua.L, action.TryBindKey))
|
||||
ulua.L.SetField(pkg, "Reload", luar.New(ulua.L, action.ReloadConfig))
|
||||
ulua.L.SetField(pkg, "AddRuntimeFileFromMemory", luar.New(ulua.L, config.PluginAddRuntimeFileFromMemory))
|
||||
ulua.L.SetField(pkg, "AddRuntimeFilesFromDirectory", luar.New(ulua.L, config.PluginAddRuntimeFileFromMemory))
|
||||
ulua.L.SetField(pkg, "AddRuntimeFilesFromDirectory", luar.New(ulua.L, config.PluginAddRuntimeFilesFromDirectory))
|
||||
ulua.L.SetField(pkg, "AddRuntimeFile", luar.New(ulua.L, config.PluginAddRuntimeFile))
|
||||
ulua.L.SetField(pkg, "ListRuntimeFiles", luar.New(ulua.L, config.PluginListRuntimeFiles))
|
||||
ulua.L.SetField(pkg, "ReadRuntimeFile", luar.New(ulua.L, config.PluginReadRuntimeFile))
|
||||
ulua.L.SetField(pkg, "NewRTFiletype", luar.New(ulua.L, config.NewRTFiletype))
|
||||
ulua.L.SetField(pkg, "RTColorscheme", luar.New(ulua.L, config.RTColorscheme))
|
||||
ulua.L.SetField(pkg, "RTSyntax", luar.New(ulua.L, config.RTSyntax))
|
||||
ulua.L.SetField(pkg, "RTHelp", luar.New(ulua.L, config.RTHelp))
|
||||
ulua.L.SetField(pkg, "RTPlugin", luar.New(ulua.L, config.RTPlugin))
|
||||
ulua.L.SetField(pkg, "RegisterCommonOption", luar.New(ulua.L, config.RegisterCommonOption))
|
||||
ulua.L.SetField(pkg, "RegisterGlobalOption", luar.New(ulua.L, config.RegisterGlobalOption))
|
||||
ulua.L.SetField(pkg, "RegisterCommonOption", luar.New(ulua.L, config.RegisterCommonOptionPlug))
|
||||
ulua.L.SetField(pkg, "RegisterGlobalOption", luar.New(ulua.L, config.RegisterGlobalOptionPlug))
|
||||
ulua.L.SetField(pkg, "GetGlobalOption", luar.New(ulua.L, config.GetGlobalOption))
|
||||
ulua.L.SetField(pkg, "SetGlobalOption", luar.New(ulua.L, action.SetGlobalOption))
|
||||
ulua.L.SetField(pkg, "SetGlobalOptionNative", luar.New(ulua.L, action.SetGlobalOptionNative))
|
||||
ulua.L.SetField(pkg, "ConfigDir", luar.New(ulua.L, config.ConfigDir))
|
||||
|
||||
return pkg
|
||||
}
|
||||
@@ -113,6 +125,9 @@ func luaImportMicroBuffer() *lua.LTable {
|
||||
ulua.L.SetField(pkg, "BTScratch", luar.New(ulua.L, buffer.BTScratch.Kind))
|
||||
ulua.L.SetField(pkg, "BTRaw", luar.New(ulua.L, buffer.BTRaw.Kind))
|
||||
ulua.L.SetField(pkg, "BTInfo", luar.New(ulua.L, buffer.BTInfo.Kind))
|
||||
ulua.L.SetField(pkg, "NewBuffer", luar.New(ulua.L, func(text, path string) *buffer.Buffer {
|
||||
return buffer.NewBufferFromString(text, path, buffer.BTDefault)
|
||||
}))
|
||||
ulua.L.SetField(pkg, "NewBufferFromFile", luar.New(ulua.L, func(path string) (*buffer.Buffer, error) {
|
||||
return buffer.NewBufferFromFile(path, buffer.BTDefault)
|
||||
}))
|
||||
@@ -129,6 +144,11 @@ func luaImportMicroUtil() *lua.LTable {
|
||||
ulua.L.SetField(pkg, "RuneAt", luar.New(ulua.L, util.LuaRuneAt))
|
||||
ulua.L.SetField(pkg, "GetLeadingWhitespace", luar.New(ulua.L, util.LuaGetLeadingWhitespace))
|
||||
ulua.L.SetField(pkg, "IsWordChar", luar.New(ulua.L, util.LuaIsWordChar))
|
||||
ulua.L.SetField(pkg, "String", luar.New(ulua.L, util.String))
|
||||
ulua.L.SetField(pkg, "CharacterCountInString", luar.New(ulua.L, util.CharacterCountInString))
|
||||
ulua.L.SetField(pkg, "RuneStr", luar.New(ulua.L, func(r rune) string {
|
||||
return string(r)
|
||||
}))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
@@ -5,17 +5,21 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
isatty "github.com/mattn/go-isatty"
|
||||
"github.com/zyedidia/micro/internal/action"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/shell"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/v2/internal/action"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -29,17 +33,21 @@ var (
|
||||
flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
|
||||
flagOptions = flag.Bool("options", false, "Show all option help")
|
||||
flagDebug = flag.Bool("debug", false, "Enable debug mode (prints debug info to ./log.txt)")
|
||||
flagPlugin = flag.String("plugin", "", "Plugin command")
|
||||
flagClean = flag.Bool("clean", false, "Clean configuration directory")
|
||||
optionFlags map[string]*string
|
||||
)
|
||||
|
||||
func InitFlags() {
|
||||
flag.Usage = func() {
|
||||
fmt.Println("Usage: micro [OPTIONS] [FILE]...")
|
||||
fmt.Println("-clean")
|
||||
fmt.Println(" \tCleans the configuration directory")
|
||||
fmt.Println("-config-dir dir")
|
||||
fmt.Println(" \tSpecify a custom location for the configuration directory")
|
||||
fmt.Println("[FILE]:LINE:COL")
|
||||
fmt.Println("[FILE]:LINE:COL (if the `parsecursor` option is enabled)")
|
||||
fmt.Println("+LINE:COL")
|
||||
fmt.Println(" \tSpecify a line and column to start the cursor at when opening a buffer")
|
||||
fmt.Println(" \tThis can also be done by opening file:LINE:COL")
|
||||
fmt.Println("-options")
|
||||
fmt.Println(" \tShow all option help")
|
||||
fmt.Println("-debug")
|
||||
@@ -47,6 +55,20 @@ func InitFlags() {
|
||||
fmt.Println("-version")
|
||||
fmt.Println(" \tShow the version number and information")
|
||||
|
||||
fmt.Print("\nMicro's plugin's can be managed at the command line with the following commands.\n")
|
||||
fmt.Println("-plugin install [PLUGIN]...")
|
||||
fmt.Println(" \tInstall plugin(s)")
|
||||
fmt.Println("-plugin remove [PLUGIN]...")
|
||||
fmt.Println(" \tRemove plugin(s)")
|
||||
fmt.Println("-plugin update [PLUGIN]...")
|
||||
fmt.Println(" \tUpdate plugin(s) (if no argument is given, updates all plugins)")
|
||||
fmt.Println("-plugin search [PLUGIN]...")
|
||||
fmt.Println(" \tSearch for a plugin")
|
||||
fmt.Println("-plugin list")
|
||||
fmt.Println(" \tList installed plugins")
|
||||
fmt.Println("-plugin available")
|
||||
fmt.Println(" \tList available plugins")
|
||||
|
||||
fmt.Print("\nMicro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the settings.json\nfile (see 'help options').\n\n")
|
||||
fmt.Println("-option value")
|
||||
fmt.Println(" \tSet `option` to `value` for this session")
|
||||
@@ -91,6 +113,23 @@ func InitFlags() {
|
||||
}
|
||||
}
|
||||
|
||||
// DoPluginFlags parses and executes any flags that require LoadAllPlugins (-plugin and -clean)
|
||||
func DoPluginFlags() {
|
||||
if *flagClean || *flagPlugin != "" {
|
||||
config.LoadAllPlugins()
|
||||
|
||||
if *flagPlugin != "" {
|
||||
args := flag.Args()
|
||||
|
||||
config.PluginCommand(os.Stdout, *flagPlugin, args)
|
||||
} else if *flagClean {
|
||||
CleanConfig()
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadInput determines which files should be loaded into buffers
|
||||
// based on the input stored in flag.Args()
|
||||
func LoadInput() []*buffer.Buffer {
|
||||
@@ -111,11 +150,45 @@ func LoadInput() []*buffer.Buffer {
|
||||
args := flag.Args()
|
||||
buffers := make([]*buffer.Buffer, 0, len(args))
|
||||
|
||||
if len(args) > 0 {
|
||||
btype := buffer.BTDefault
|
||||
if !isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
btype = buffer.BTStdout
|
||||
}
|
||||
|
||||
files := make([]string, 0, len(args))
|
||||
flagStartPos := buffer.Loc{-1, -1}
|
||||
flagr := regexp.MustCompile(`^\+(\d+)(?::(\d+))?$`)
|
||||
for _, a := range args {
|
||||
match := flagr.FindStringSubmatch(a)
|
||||
if len(match) == 3 && match[2] != "" {
|
||||
line, err := strconv.Atoi(match[1])
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
continue
|
||||
}
|
||||
col, err := strconv.Atoi(match[2])
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
continue
|
||||
}
|
||||
flagStartPos = buffer.Loc{col - 1, line - 1}
|
||||
} else if len(match) == 3 && match[2] == "" {
|
||||
line, err := strconv.Atoi(match[1])
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
continue
|
||||
}
|
||||
flagStartPos = buffer.Loc{0, line - 1}
|
||||
} else {
|
||||
files = append(files, a)
|
||||
}
|
||||
}
|
||||
|
||||
if len(files) > 0 {
|
||||
// Option 1
|
||||
// We go through each file and load it
|
||||
for i := 0; i < len(args); i++ {
|
||||
buf, err := buffer.NewBufferFromFile(args[i], buffer.BTDefault)
|
||||
for i := 0; i < len(files); i++ {
|
||||
buf, err := buffer.NewBufferFromFileAtLoc(files[i], btype, flagStartPos)
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
continue
|
||||
@@ -132,17 +205,22 @@ func LoadInput() []*buffer.Buffer {
|
||||
screen.TermMessage("Error reading from stdin: ", err)
|
||||
input = []byte{}
|
||||
}
|
||||
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, buffer.BTDefault))
|
||||
buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
|
||||
} else {
|
||||
// Option 3, just open an empty buffer
|
||||
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, buffer.BTDefault))
|
||||
buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
|
||||
}
|
||||
|
||||
return buffers
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer os.Exit(0)
|
||||
defer func() {
|
||||
if util.Stdout.Len() > 0 {
|
||||
fmt.Fprint(os.Stdout, util.Stdout.String())
|
||||
}
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
// runtime.SetCPUProfileRate(400)
|
||||
// f, _ := os.Create("micro.prof")
|
||||
@@ -165,7 +243,10 @@ func main() {
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
config.InitGlobalSettings()
|
||||
err = config.InitGlobalSettings()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
// flag options
|
||||
for k, v := range optionFlags {
|
||||
@@ -179,11 +260,10 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
DoPluginFlags()
|
||||
|
||||
screen.Init()
|
||||
|
||||
// If we have an error, we can exit cleanly and not completely
|
||||
// mess up the terminal being worked in
|
||||
// In other words we need to shut down tcell before the program crashes
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
screen.Screen.Fini()
|
||||
@@ -198,19 +278,15 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
action.InitBindings()
|
||||
action.InitCommands()
|
||||
|
||||
err = config.InitColorscheme()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
err = config.LoadAllPlugins()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
err = config.RunPluginFn("init")
|
||||
|
||||
action.InitBindings()
|
||||
action.InitCommands()
|
||||
|
||||
err = config.InitColorscheme()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
@@ -226,6 +302,11 @@ func main() {
|
||||
action.InitTabs(b)
|
||||
action.InitGlobals()
|
||||
|
||||
err = config.RunPluginFn("init")
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
events = make(chan tcell.Event)
|
||||
|
||||
// Here is the event loop which runs in a separate thread
|
||||
@@ -240,38 +321,73 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
// clear the drawchan so we don't redraw excessively
|
||||
// if someone requested a redraw before we started displaying
|
||||
for len(screen.DrawChan()) > 0 {
|
||||
<-screen.DrawChan()
|
||||
}
|
||||
|
||||
// wait for initial resize event
|
||||
select {
|
||||
case event := <-events:
|
||||
action.Tabs.HandleEvent(event)
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
// time out after 10ms
|
||||
}
|
||||
|
||||
// Since this loop is very slow (waits for user input every time) it's
|
||||
// okay to be inefficient and run it via a function every time
|
||||
// We do this so we can recover from panics without crashing the editor
|
||||
for {
|
||||
// Display everything
|
||||
screen.Screen.Fill(' ', config.DefStyle)
|
||||
screen.Screen.HideCursor()
|
||||
action.Tabs.Display()
|
||||
for _, ep := range action.MainTab().Panes {
|
||||
ep.Display()
|
||||
}
|
||||
action.MainTab().Display()
|
||||
action.InfoBar.Display()
|
||||
screen.Screen.Show()
|
||||
|
||||
var event tcell.Event
|
||||
|
||||
// Check for new events
|
||||
select {
|
||||
case f := <-shell.Jobs:
|
||||
// If a new job has finished while running in the background we should execute the callback
|
||||
f.Function(f.Output, f.Args...)
|
||||
case <-config.Autosave:
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
b.Save()
|
||||
}
|
||||
case <-shell.CloseTerms:
|
||||
case event = <-events:
|
||||
case <-screen.DrawChan:
|
||||
}
|
||||
|
||||
if action.InfoBar.HasPrompt {
|
||||
action.InfoBar.HandleEvent(event)
|
||||
} else {
|
||||
action.Tabs.HandleEvent(event)
|
||||
}
|
||||
DoEvent()
|
||||
}
|
||||
}
|
||||
|
||||
// DoEvent runs the main action loop of the editor
|
||||
func DoEvent() {
|
||||
var event tcell.Event
|
||||
|
||||
// recover from errors without crashing the editor
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if e, ok := err.(*lua.ApiError); ok {
|
||||
screen.TermMessage("Lua API error:", e)
|
||||
} else {
|
||||
screen.TermMessage("Micro encountered an error:", errors.Wrap(err, 2).ErrorStack(), "\nIf you can reproduce this error, please report it at https://github.com/zyedidia/micro/issues")
|
||||
}
|
||||
}
|
||||
}()
|
||||
// Display everything
|
||||
screen.Screen.Fill(' ', config.DefStyle)
|
||||
screen.Screen.HideCursor()
|
||||
action.Tabs.Display()
|
||||
for _, ep := range action.MainTab().Panes {
|
||||
ep.Display()
|
||||
}
|
||||
action.MainTab().Display()
|
||||
action.InfoBar.Display()
|
||||
screen.Screen.Show()
|
||||
|
||||
// Check for new events
|
||||
select {
|
||||
case f := <-shell.Jobs:
|
||||
// If a new job has finished while running in the background we should execute the callback
|
||||
f.Function(f.Output, f.Args)
|
||||
case <-config.Autosave:
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
b.Save()
|
||||
}
|
||||
case <-shell.CloseTerms:
|
||||
case event = <-events:
|
||||
case <-screen.DrawChan():
|
||||
for len(screen.DrawChan()) > 0 {
|
||||
<-screen.DrawChan()
|
||||
}
|
||||
}
|
||||
|
||||
if action.InfoBar.HasPrompt {
|
||||
action.InfoBar.HandleEvent(event)
|
||||
} else {
|
||||
action.Tabs.HandleEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
8
go.mod
8
go.mod
@@ -1,4 +1,4 @@
|
||||
module github.com/zyedidia/micro
|
||||
module github.com/zyedidia/micro/v2
|
||||
|
||||
require (
|
||||
github.com/blang/semver v3.5.1+incompatible
|
||||
@@ -8,17 +8,19 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.11
|
||||
github.com/mattn/go-runewidth v0.0.7
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff
|
||||
github.com/sergi/go-diff v1.1.0
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
|
||||
github.com/zyedidia/clipboard v0.0.0-20190823154308-241f98e9b197
|
||||
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834
|
||||
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3
|
||||
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5
|
||||
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d
|
||||
github.com/zyedidia/pty v2.0.0+incompatible // indirect
|
||||
github.com/zyedidia/tcell v1.4.2
|
||||
github.com/zyedidia/tcell v1.4.6
|
||||
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415
|
||||
golang.org/x/text v0.3.2
|
||||
gopkg.in/sourcemap.v1 v1.0.5 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.7
|
||||
layeh.com/gopher-luar v1.0.7
|
||||
)
|
||||
|
||||
18
go.sum
18
go.sum
@@ -29,6 +29,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff h1:+6NUiITWwE5q1KO6SAfUX918c+Tab0+tGAM/mtdlUyA=
|
||||
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
@@ -40,6 +42,8 @@ github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox
|
||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||
github.com/zyedidia/clipboard v0.0.0-20190823154308-241f98e9b197 h1:gYTNnAW6azuB3BbA6QYWO/H4F2ABSOjjw3Z03tlXd2c=
|
||||
github.com/zyedidia/clipboard v0.0.0-20190823154308-241f98e9b197/go.mod h1:WDk3p8GiZV9+xFWlSo8qreeoLhW6Ik692rqXk+cNeRY=
|
||||
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834 h1:0nOfq3JwYRiY3+nwfWVQYEaXDmGCQgj3RKoqTifLzP4=
|
||||
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834/go.mod h1:zykFnZUXX0ErxqvYLUFEq7QDJKId8rmh2FgD0/Y8cjA=
|
||||
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3 h1:oMHjjTLfGXVuyOQBYj5/td9WC0mw4g1xDBPovIqmHew=
|
||||
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3/go.mod h1:YKbIYP//Eln8eDgAJGI3IDvR3s4Tv9Z9TGIOumiyQ5c=
|
||||
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5 h1:Zs6mpwXvlqpF9zHl5XaN0p5V4J9XvP+WBuiuXyIgqvc=
|
||||
@@ -50,12 +54,12 @@ github.com/zyedidia/poller v1.0.1 h1:Tt9S3AxAjXwWGNiC2TUdRJkQDZSzCBNVQ4xXiQ7440s
|
||||
github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
|
||||
github.com/zyedidia/pty v2.0.0+incompatible h1:Ou5vXL6tvjst+RV8sUFISbuKDnUJPhnpygApMFGweqw=
|
||||
github.com/zyedidia/pty v2.0.0+incompatible/go.mod h1:4y9l9yJZNxRa7GB/fB+mmDmGkG3CqmzLf4vUxGGotEA=
|
||||
github.com/zyedidia/tcell v1.4.0 h1:uhAz+bdB3HHlVP2hff3WURkI+pERNwgVfy27oi1Gb2A=
|
||||
github.com/zyedidia/tcell v1.4.0/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
|
||||
github.com/zyedidia/tcell v1.4.1 h1:zLci8cg1SLINjwSePZ1yUWnYOnZXMyr4h+zaOvhu5K8=
|
||||
github.com/zyedidia/tcell v1.4.1/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
|
||||
github.com/zyedidia/tcell v1.4.2 h1:JWMDs6O1saINPIR5M3kNqlWJwkfnBZeZDZszEJi3BW8=
|
||||
github.com/zyedidia/tcell v1.4.2/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
|
||||
github.com/zyedidia/tcell v1.4.4 h1:o34LXujNuSueuyTy+5eoQW+rQr8g0UbY8k1NczZyskQ=
|
||||
github.com/zyedidia/tcell v1.4.4/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
|
||||
github.com/zyedidia/tcell v1.4.5 h1:JFmOiWLxr3Fsk2vjRL3n8oRUoJeyrazGhkhZqW31kEY=
|
||||
github.com/zyedidia/tcell v1.4.5/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
|
||||
github.com/zyedidia/tcell v1.4.6 h1:8OYvZpUyqYQ3nigenBwOtnY3fXWEHekbm6QYchBeOxs=
|
||||
github.com/zyedidia/tcell v1.4.6/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
|
||||
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415 h1:752dTQ5OatJ9M5ULK2+9lor+nzyZz+LYDo3WGngg3Rc=
|
||||
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415/go.mod h1:8leT8G0Cm8NoJHdrrKHyR9MirWoF4YW7pZh06B6H+1E=
|
||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -69,6 +73,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
|
||||
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"log"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"github.com/zyedidia/clipboard"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/shell"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -176,7 +174,7 @@ func (h *BufPane) CursorRight() bool {
|
||||
if tabstospaces && tabmovement {
|
||||
tabsize := int(h.Buf.Settings["tabsize"].(float64))
|
||||
line := h.Buf.LineBytes(h.Cursor.Y)
|
||||
if h.Cursor.X+tabsize < utf8.RuneCount(line) && util.IsSpaces(line[h.Cursor.X:h.Cursor.X+tabsize]) && util.IsBytesWhitespace(line[0:h.Cursor.X]) {
|
||||
if h.Cursor.X+tabsize < util.CharacterCount(line) && util.IsSpaces(line[h.Cursor.X:h.Cursor.X+tabsize]) && util.IsBytesWhitespace(line[0:h.Cursor.X]) {
|
||||
for i := 0; i < tabsize; i++ {
|
||||
h.Cursor.Right()
|
||||
}
|
||||
@@ -284,7 +282,7 @@ func (h *BufPane) SelectWordLeft() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// StartOfLine moves the cursor to the start of the text of the line
|
||||
// StartOfText moves the cursor to the start of the text of the line
|
||||
func (h *BufPane) StartOfText() bool {
|
||||
h.Cursor.Deselect(true)
|
||||
h.Cursor.StartOfText()
|
||||
@@ -292,6 +290,19 @@ func (h *BufPane) StartOfText() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// StartOfTextToggle toggles the cursor between the start of the text of the line
|
||||
// and the start of the line
|
||||
func (h *BufPane) StartOfTextToggle() bool {
|
||||
h.Cursor.Deselect(true)
|
||||
if h.Cursor.IsStartOfText() {
|
||||
h.Cursor.Start()
|
||||
} else {
|
||||
h.Cursor.StartOfText()
|
||||
}
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// StartOfLine moves the cursor to the start of the line
|
||||
func (h *BufPane) StartOfLine() bool {
|
||||
h.Cursor.Deselect(true)
|
||||
@@ -326,6 +337,22 @@ func (h *BufPane) SelectToStartOfText() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectToStartOfTextToggle toggles the selection between the start of the text
|
||||
// on the current line and the start of the line
|
||||
func (h *BufPane) SelectToStartOfTextToggle() bool {
|
||||
if !h.Cursor.HasSelection() {
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
||||
}
|
||||
if h.Cursor.IsStartOfText() {
|
||||
h.Cursor.Start()
|
||||
} else {
|
||||
h.Cursor.StartOfText()
|
||||
}
|
||||
h.Cursor.SelectTo(h.Cursor.Loc)
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectToStartOfLine selects to the start of the current line
|
||||
func (h *BufPane) SelectToStartOfLine() bool {
|
||||
if !h.Cursor.HasSelection() {
|
||||
@@ -397,6 +424,7 @@ func (h *BufPane) CursorStart() bool {
|
||||
h.Cursor.Deselect(true)
|
||||
h.Cursor.X = 0
|
||||
h.Cursor.Y = 0
|
||||
h.Cursor.StoreVisualX()
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
@@ -457,7 +485,7 @@ func (h *BufPane) InsertNewline() bool {
|
||||
// Remove the whitespaces if keepautoindent setting is off
|
||||
if util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings["keepautoindent"].(bool) {
|
||||
line := h.Buf.LineBytes(h.Cursor.Y - 1)
|
||||
h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: utf8.RuneCount(line), Y: h.Cursor.Y - 1})
|
||||
h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: util.CharacterCount(line), Y: h.Cursor.Y - 1})
|
||||
}
|
||||
}
|
||||
h.Cursor.LastVisualX = h.Cursor.GetVisualX()
|
||||
@@ -482,7 +510,7 @@ func (h *BufPane) Backspace() bool {
|
||||
// tab (tabSize number of spaces)
|
||||
lineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)
|
||||
tabSize := int(h.Buf.Settings["tabsize"].(float64))
|
||||
if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 {
|
||||
if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && util.CharacterCount(lineStart)%tabSize == 0 {
|
||||
loc := h.Cursor.Loc
|
||||
h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
|
||||
} else {
|
||||
@@ -567,6 +595,20 @@ func (h *BufPane) IndentSelection() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IndentLine moves the current line forward one indentation
|
||||
func (h *BufPane) IndentLine() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
return false
|
||||
}
|
||||
|
||||
tabsize := int(h.Buf.Settings["tabsize"].(float64))
|
||||
indentstr := h.Buf.IndentString(tabsize)
|
||||
h.Buf.Insert(buffer.Loc{X: 0, Y: h.Cursor.Y}, indentstr)
|
||||
h.Buf.RelocateCursors()
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// OutdentLine moves the current line back one indentation
|
||||
func (h *BufPane) OutdentLine() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
@@ -621,6 +663,16 @@ func (h *BufPane) Autocomplete() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if h.Cursor.X == 0 {
|
||||
return false
|
||||
}
|
||||
r := h.Cursor.RuneUnder(h.Cursor.X)
|
||||
prev := h.Cursor.RuneUnder(h.Cursor.X - 1)
|
||||
if !util.IsAutocomplete(prev) || !util.IsNonAlphaNumeric(r) {
|
||||
// don't autocomplete if cursor is on alpha numeric character (middle of a word)
|
||||
return false
|
||||
}
|
||||
|
||||
if b.HasSuggestions {
|
||||
b.CycleAutocomplete(true)
|
||||
return true
|
||||
@@ -633,11 +685,9 @@ func (h *BufPane) CycleAutocompleteBack() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
return false
|
||||
}
|
||||
log.Println(h.Buf.HasSuggestions)
|
||||
|
||||
if h.Buf.HasSuggestions {
|
||||
h.Buf.CycleAutocomplete(false)
|
||||
log.Println("TRUE")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -662,23 +712,28 @@ func (h *BufPane) SaveAll() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Save the buffer to disk
|
||||
func (h *BufPane) Save() bool {
|
||||
// SaveCB performs a save and does a callback at the very end (after all prompts have been resolved)
|
||||
func (h *BufPane) SaveCB(action string, callback func()) bool {
|
||||
// If this is an empty buffer, ask for a filename
|
||||
if h.Buf.Path == "" {
|
||||
h.SaveAs()
|
||||
h.SaveAsCB(action, callback)
|
||||
} else {
|
||||
noPrompt := h.saveBufToFile(h.Buf.Path, "Save")
|
||||
noPrompt := h.saveBufToFile(h.Buf.Path, action, callback)
|
||||
if noPrompt {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// SaveAs saves the buffer to disk with the given name
|
||||
func (h *BufPane) SaveAs() bool {
|
||||
// Save the buffer to disk
|
||||
func (h *BufPane) Save() bool {
|
||||
return h.SaveCB("Save", nil)
|
||||
}
|
||||
|
||||
// SaveAsCB performs a save as and does a callback at the very end (after all prompts have been resolved)
|
||||
// The callback is only called if the save was successful
|
||||
func (h *BufPane) SaveAsCB(action string, callback func()) bool {
|
||||
InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) {
|
||||
if !canceled {
|
||||
// the filename might or might not be quoted, so unquote first then join the strings.
|
||||
@@ -692,35 +747,51 @@ func (h *BufPane) SaveAs() bool {
|
||||
return
|
||||
}
|
||||
filename := strings.Join(args, " ")
|
||||
noPrompt := h.saveBufToFile(filename, "SaveAs")
|
||||
noPrompt := h.saveBufToFile(filename, action, callback)
|
||||
if noPrompt {
|
||||
h.completeAction("SaveAs")
|
||||
h.completeAction(action)
|
||||
}
|
||||
}
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
// SaveAs saves the buffer to disk with the given name
|
||||
func (h *BufPane) SaveAs() bool {
|
||||
return h.SaveAsCB("SaveAs", nil)
|
||||
}
|
||||
|
||||
// This function saves the buffer to `filename` and changes the buffer's path and name
|
||||
// to `filename` if the save is successful
|
||||
func (h *BufPane) saveBufToFile(filename string, action string) bool {
|
||||
// The callback is only called if the save was successful
|
||||
func (h *BufPane) saveBufToFile(filename string, action string, callback func()) bool {
|
||||
err := h.Buf.SaveAs(filename)
|
||||
if err != nil {
|
||||
if strings.HasSuffix(err.Error(), "permission denied") {
|
||||
InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) {
|
||||
if yes && !canceled {
|
||||
err = h.Buf.SaveAsWithSudo(filename)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
} else {
|
||||
h.Buf.Path = filename
|
||||
h.Buf.SetName(filename)
|
||||
InfoBar.Message("Saved " + filename)
|
||||
saveWithSudo := func() {
|
||||
err = h.Buf.SaveAsWithSudo(filename)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
} else {
|
||||
h.Buf.Path = filename
|
||||
h.Buf.SetName(filename)
|
||||
InfoBar.Message("Saved " + filename)
|
||||
if callback != nil {
|
||||
callback()
|
||||
}
|
||||
h.completeAction(action)
|
||||
}
|
||||
})
|
||||
return false
|
||||
}
|
||||
if h.Buf.Settings["autosu"].(bool) {
|
||||
saveWithSudo()
|
||||
} else {
|
||||
InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) {
|
||||
if yes && !canceled {
|
||||
saveWithSudo()
|
||||
h.completeAction(action)
|
||||
}
|
||||
})
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
@@ -728,16 +799,32 @@ func (h *BufPane) saveBufToFile(filename string, action string) bool {
|
||||
h.Buf.Path = filename
|
||||
h.Buf.SetName(filename)
|
||||
InfoBar.Message("Saved " + filename)
|
||||
if callback != nil {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Find opens a prompt and searches forward for the input
|
||||
func (h *BufPane) Find() bool {
|
||||
return h.find(true)
|
||||
}
|
||||
|
||||
// FindLiteral is the same as Find() but does not support regular expressions
|
||||
func (h *BufPane) FindLiteral() bool {
|
||||
return h.find(false)
|
||||
}
|
||||
|
||||
func (h *BufPane) find(useRegex bool) bool {
|
||||
h.searchOrig = h.Cursor.Loc
|
||||
InfoBar.Prompt("Find: ", "", "Find", func(resp string) {
|
||||
prompt := "Find: "
|
||||
if useRegex {
|
||||
prompt = "Find (regex): "
|
||||
}
|
||||
InfoBar.Prompt(prompt, "", "Find", func(resp string) {
|
||||
// Event callback
|
||||
match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
|
||||
match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
|
||||
if found {
|
||||
h.Cursor.SetSelectionStart(match[0])
|
||||
h.Cursor.SetSelectionEnd(match[1])
|
||||
@@ -752,7 +839,7 @@ func (h *BufPane) Find() bool {
|
||||
}, func(resp string, canceled bool) {
|
||||
// Finished callback
|
||||
if !canceled {
|
||||
match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
|
||||
match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
@@ -763,6 +850,7 @@ func (h *BufPane) Find() bool {
|
||||
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
|
||||
h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
|
||||
h.lastSearch = resp
|
||||
h.lastSearchRegex = useRegex
|
||||
} else {
|
||||
h.Cursor.ResetSelection()
|
||||
InfoBar.Message("No matches found")
|
||||
@@ -786,7 +874,7 @@ func (h *BufPane) FindNext() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
searchLoc = h.Cursor.CurSelection[1]
|
||||
}
|
||||
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, true)
|
||||
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.lastSearchRegex)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
@@ -813,7 +901,7 @@ func (h *BufPane) FindPrevious() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
searchLoc = h.Cursor.CurSelection[0]
|
||||
}
|
||||
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, true)
|
||||
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.lastSearchRegex)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
@@ -861,6 +949,25 @@ func (h *BufPane) Copy() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Copy the current line to the clipboard
|
||||
func (h *BufPane) CopyLine() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
return false
|
||||
} else {
|
||||
h.Cursor.SelectLine()
|
||||
h.Cursor.CopySelection("clipboard")
|
||||
h.freshClip = true
|
||||
if clipboard.Unsupported {
|
||||
InfoBar.Message("Copied line (install xclip for external clipboard)")
|
||||
} else {
|
||||
InfoBar.Message("Copied line")
|
||||
}
|
||||
}
|
||||
h.Cursor.Deselect(true)
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// CutLine cuts the current line to the clipboard
|
||||
func (h *BufPane) CutLine() bool {
|
||||
h.Cursor.SelectLine()
|
||||
@@ -940,15 +1047,26 @@ func (h *BufPane) MoveLinesUp() bool {
|
||||
}
|
||||
start := h.Cursor.CurSelection[0].Y
|
||||
end := h.Cursor.CurSelection[1].Y
|
||||
sel := 1
|
||||
if start > end {
|
||||
end, start = start, end
|
||||
sel = 0
|
||||
}
|
||||
|
||||
compensate := false
|
||||
if h.Cursor.CurSelection[sel].X != 0 {
|
||||
end++
|
||||
} else {
|
||||
compensate = true
|
||||
}
|
||||
|
||||
h.Buf.MoveLinesUp(
|
||||
start,
|
||||
end,
|
||||
)
|
||||
h.Cursor.CurSelection[1].Y -= 1
|
||||
if compensate {
|
||||
h.Cursor.CurSelection[sel].Y -= 1
|
||||
}
|
||||
} else {
|
||||
if h.Cursor.Loc.Y == 0 {
|
||||
InfoBar.Message("Cannot move further up")
|
||||
@@ -973,8 +1091,14 @@ func (h *BufPane) MoveLinesDown() bool {
|
||||
}
|
||||
start := h.Cursor.CurSelection[0].Y
|
||||
end := h.Cursor.CurSelection[1].Y
|
||||
sel := 1
|
||||
if start > end {
|
||||
end, start = start, end
|
||||
sel = 0
|
||||
}
|
||||
|
||||
if h.Cursor.CurSelection[sel].X != 0 {
|
||||
end++
|
||||
}
|
||||
|
||||
h.Buf.MoveLinesDown(
|
||||
@@ -1043,11 +1167,15 @@ func (h *BufPane) JumpToMatchingBrace() bool {
|
||||
r := h.Cursor.RuneUnder(h.Cursor.X)
|
||||
rl := h.Cursor.RuneUnder(h.Cursor.X - 1)
|
||||
if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
|
||||
matchingBrace, left := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
|
||||
if left {
|
||||
h.Cursor.GotoLoc(matchingBrace)
|
||||
matchingBrace, left, found := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
|
||||
if found {
|
||||
if left {
|
||||
h.Cursor.GotoLoc(matchingBrace)
|
||||
} else {
|
||||
h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
|
||||
}
|
||||
} else {
|
||||
h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1077,6 +1205,16 @@ func (h *BufPane) OpenFile() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// OpenFile opens a new file in the buffer
|
||||
func (h *BufPane) JumpLine() bool {
|
||||
InfoBar.Prompt("> ", "goto ", "Command", nil, func(resp string, canceled bool) {
|
||||
if !canceled {
|
||||
h.HandleCommand(resp)
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
// Start moves the viewport to the start of the buffer
|
||||
func (h *BufPane) Start() bool {
|
||||
v := h.GetView()
|
||||
@@ -1198,6 +1336,21 @@ func (h *BufPane) HalfPageDown() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ToggleDiffGutter turns the diff gutter off and on
|
||||
func (h *BufPane) ToggleDiffGutter() bool {
|
||||
if !h.Buf.Settings["diffgutter"].(bool) {
|
||||
h.Buf.Settings["diffgutter"] = true
|
||||
h.Buf.UpdateDiff(func(synchronous bool) {
|
||||
screen.Redraw()
|
||||
})
|
||||
InfoBar.Message("Enabled diff gutter")
|
||||
} else {
|
||||
h.Buf.Settings["diffgutter"] = false
|
||||
InfoBar.Message("Disabled diff gutter")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ToggleRuler turns line numbers off and on
|
||||
func (h *BufPane) ToggleRuler() bool {
|
||||
if !h.Buf.Settings["ruler"].(bool) {
|
||||
@@ -1281,20 +1434,22 @@ func (h *BufPane) Quit() bool {
|
||||
}
|
||||
}
|
||||
if h.Buf.Modified() {
|
||||
// if config.GlobalSettings["autosave"].(float64) > 0 {
|
||||
// autosave on means we automatically save when quitting
|
||||
// h.Save()
|
||||
// quit()
|
||||
// } else {
|
||||
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
|
||||
if !canceled && !yes {
|
||||
if config.GlobalSettings["autosave"].(float64) > 0 {
|
||||
// autosave on means we automatically save when quitting
|
||||
h.SaveCB("Quit", func() {
|
||||
quit()
|
||||
} else if !canceled && yes {
|
||||
h.Save()
|
||||
quit()
|
||||
}
|
||||
})
|
||||
// }
|
||||
})
|
||||
} else {
|
||||
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
|
||||
if !canceled && !yes {
|
||||
quit()
|
||||
} else if !canceled && yes {
|
||||
h.SaveCB("Quit", func() {
|
||||
quit()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
quit()
|
||||
}
|
||||
@@ -1347,8 +1502,9 @@ func (h *BufPane) AddTab() bool {
|
||||
|
||||
// PreviousTab switches to the previous tab in the tab list
|
||||
func (h *BufPane) PreviousTab() bool {
|
||||
a := Tabs.Active()
|
||||
Tabs.SetActive(util.Clamp(a-1, 0, len(Tabs.List)-1))
|
||||
tabsLen := len(Tabs.List)
|
||||
a := Tabs.Active() + tabsLen
|
||||
Tabs.SetActive((a - 1) % tabsLen)
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1356,7 +1512,8 @@ func (h *BufPane) PreviousTab() bool {
|
||||
// NextTab switches to the next tab in the tab list
|
||||
func (h *BufPane) NextTab() bool {
|
||||
a := Tabs.Active()
|
||||
Tabs.SetActive(util.Clamp(a+1, 0, len(Tabs.List)-1))
|
||||
Tabs.SetActive((a + 1) % len(Tabs.List))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1376,38 +1533,42 @@ func (h *BufPane) HSplitAction() bool {
|
||||
|
||||
// Unsplit closes all splits in the current tab except the active one
|
||||
func (h *BufPane) Unsplit() bool {
|
||||
n := MainTab().GetNode(h.splitID)
|
||||
n.Unsplit()
|
||||
tab := h.tab
|
||||
n := tab.GetNode(h.splitID)
|
||||
ok := n.Unsplit()
|
||||
if ok {
|
||||
tab.RemovePane(tab.GetPane(h.splitID))
|
||||
tab.Resize()
|
||||
tab.SetActive(len(tab.Panes) - 1)
|
||||
|
||||
MainTab().RemovePane(MainTab().GetPane(h.splitID))
|
||||
MainTab().Resize()
|
||||
MainTab().SetActive(len(MainTab().Panes) - 1)
|
||||
return true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NextSplit changes the view to the next split
|
||||
func (h *BufPane) NextSplit() bool {
|
||||
a := MainTab().active
|
||||
if a < len(MainTab().Panes)-1 {
|
||||
a := h.tab.active
|
||||
if a < len(h.tab.Panes)-1 {
|
||||
a++
|
||||
} else {
|
||||
a = 0
|
||||
}
|
||||
|
||||
MainTab().SetActive(a)
|
||||
h.tab.SetActive(a)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// PreviousSplit changes the view to the previous split
|
||||
func (h *BufPane) PreviousSplit() bool {
|
||||
a := MainTab().active
|
||||
a := h.tab.active
|
||||
if a > 0 {
|
||||
a--
|
||||
} else {
|
||||
a = len(MainTab().Panes) - 1
|
||||
a = len(h.tab.Panes) - 1
|
||||
}
|
||||
MainTab().SetActive(a)
|
||||
h.tab.SetActive(a)
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1486,6 +1647,41 @@ func (h *BufPane) SpawnMultiCursor() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less.
|
||||
func (h *BufPane) SpawnMultiCursorUp() bool {
|
||||
if h.Cursor.Y == 0 {
|
||||
return false
|
||||
} else {
|
||||
h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
|
||||
h.Cursor.Relocate()
|
||||
}
|
||||
|
||||
c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
|
||||
h.Buf.AddCursor(c)
|
||||
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
|
||||
h.Buf.MergeCursors()
|
||||
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// SpawnMultiCursorDown creates additional cursor, at the same X (if possible), one Y more.
|
||||
func (h *BufPane) SpawnMultiCursorDown() bool {
|
||||
if h.Cursor.Y+1 == h.Buf.LinesNum() {
|
||||
return false
|
||||
} else {
|
||||
h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
|
||||
h.Cursor.Relocate()
|
||||
}
|
||||
|
||||
c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
|
||||
h.Buf.AddCursor(c)
|
||||
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
|
||||
h.Buf.MergeCursors()
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
|
||||
func (h *BufPane) SpawnMultiCursorSelect() bool {
|
||||
// Avoid cases where multiple cursors already exist, that would create problems
|
||||
|
||||
@@ -5,7 +5,7 @@ package action
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
)
|
||||
|
||||
// Suspend sends micro to the background. This is the same as pressing CtrlZ in most unix programs.
|
||||
|
||||
@@ -5,22 +5,32 @@ import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/zyedidia/json5"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
func createBindingsIfNotExist(fname string) {
|
||||
if _, e := os.Stat(fname); os.IsNotExist(e) {
|
||||
ioutil.WriteFile(fname, []byte("{}"), 0644)
|
||||
}
|
||||
}
|
||||
|
||||
// InitBindings intializes the bindings map by reading from bindings.json
|
||||
func InitBindings() {
|
||||
config.Bindings = DefaultBindings()
|
||||
|
||||
var parsed map[string]string
|
||||
defaults := DefaultBindings()
|
||||
|
||||
filename := config.ConfigDir + "/bindings.json"
|
||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||
createBindingsIfNotExist(filename)
|
||||
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
@@ -158,7 +168,8 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
|
||||
var e error
|
||||
var parsed map[string]string
|
||||
|
||||
filename := config.ConfigDir + "/bindings.json"
|
||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||
createBindingsIfNotExist(filename)
|
||||
if _, e = os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
@@ -207,7 +218,8 @@ func UnbindKey(k string) error {
|
||||
var e error
|
||||
var parsed map[string]string
|
||||
|
||||
filename := config.ConfigDir + "/bindings.json"
|
||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||
createBindingsIfNotExist(filename)
|
||||
if _, e = os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
@@ -237,6 +249,7 @@ func UnbindKey(k string) error {
|
||||
if a, ok := defaults[k]; ok {
|
||||
BindKey(k, a)
|
||||
} else if _, ok := config.Bindings[k]; ok {
|
||||
BufUnmap(key)
|
||||
delete(config.Bindings, k)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/display"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -91,12 +91,19 @@ func BufMapKey(k Event, action string) {
|
||||
screen.TermMessage("Lua Error:", a, "does not exist")
|
||||
continue
|
||||
}
|
||||
names = append(names, "")
|
||||
split := strings.SplitN(a, ".", 2)
|
||||
if len(split) > 1 {
|
||||
a = strings.Title(split[0]) + strings.Title(split[1])
|
||||
} else {
|
||||
a = strings.Title(a)
|
||||
}
|
||||
|
||||
names = append(names, a)
|
||||
} else if f, ok := BufKeyActions[a]; ok {
|
||||
afn = f
|
||||
names = append(names, a)
|
||||
} else {
|
||||
screen.TermMessage("Error:", a, "does not exist")
|
||||
screen.TermMessage("Error in bindings: action", a, "does not exist")
|
||||
continue
|
||||
}
|
||||
actionfns = append(actionfns, afn)
|
||||
@@ -106,6 +113,9 @@ func BufMapKey(k Event, action string) {
|
||||
success := true
|
||||
for i, a := range actionfns {
|
||||
for j, c := range cursors {
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
h.Buf.SetCurCursor(c.Num)
|
||||
h.Cursor = c
|
||||
if i == 0 || (success && types[i-1] == '&') || (!success && types[i-1] == '|') || (types[i-1] == ',') {
|
||||
@@ -114,6 +124,8 @@ func BufMapKey(k Event, action string) {
|
||||
break
|
||||
}
|
||||
}
|
||||
// if the action changed the current pane, update the reference
|
||||
h = MainTab().CurPane()
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -129,6 +141,17 @@ func BufMapMouse(k MouseEvent, action string) {
|
||||
}
|
||||
}
|
||||
|
||||
// BufUnmap unmaps a key or mouse event from any action
|
||||
func BufUnmap(k Event) {
|
||||
delete(BufKeyBindings, k)
|
||||
delete(BufKeyStrings, k)
|
||||
|
||||
switch e := k.(type) {
|
||||
case MouseEvent:
|
||||
delete(BufMouseBindings, e)
|
||||
}
|
||||
}
|
||||
|
||||
// The BufPane connects the buffer and the window
|
||||
// It provides a cursor (or multiple) and defines a set of actions
|
||||
// that can be taken on the buffer
|
||||
@@ -169,21 +192,24 @@ type BufPane struct {
|
||||
tripleClick bool
|
||||
|
||||
// Last search stores the last successful search for FindNext and FindPrev
|
||||
lastSearch string
|
||||
lastSearch string
|
||||
lastSearchRegex bool
|
||||
// Should the current multiple cursor selection search based on word or
|
||||
// based on selection (false for selection, true for word)
|
||||
multiWord bool
|
||||
|
||||
splitID uint64
|
||||
tab *Tab
|
||||
|
||||
// remember original location of a search in case the search is canceled
|
||||
searchOrig buffer.Loc
|
||||
}
|
||||
|
||||
func NewBufPane(buf *buffer.Buffer, win display.BWindow) *BufPane {
|
||||
func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
|
||||
h := new(BufPane)
|
||||
h.Buf = buf
|
||||
h.BWindow = win
|
||||
h.tab = tab
|
||||
|
||||
h.Cursor = h.Buf.GetActiveCursor()
|
||||
h.mouseReleased = true
|
||||
@@ -193,9 +219,23 @@ func NewBufPane(buf *buffer.Buffer, win display.BWindow) *BufPane {
|
||||
return h
|
||||
}
|
||||
|
||||
func NewBufPaneFromBuf(buf *buffer.Buffer) *BufPane {
|
||||
func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
|
||||
w := display.NewBufWindow(0, 0, 0, 0, buf)
|
||||
return NewBufPane(buf, w)
|
||||
return NewBufPane(buf, w, tab)
|
||||
}
|
||||
|
||||
func (h *BufPane) SetTab(t *Tab) {
|
||||
h.tab = t
|
||||
}
|
||||
|
||||
func (h *BufPane) Tab() *Tab {
|
||||
return h.tab
|
||||
}
|
||||
|
||||
func (h *BufPane) ResizePane(size int) {
|
||||
n := h.tab.GetNode(h.splitID)
|
||||
n.ResizeSplit(size)
|
||||
h.tab.Resize()
|
||||
}
|
||||
|
||||
// PluginCB calls all plugin callbacks with a certain name and
|
||||
@@ -244,13 +284,20 @@ func (h *BufPane) SetID(i uint64) {
|
||||
}
|
||||
|
||||
func (h *BufPane) Name() string {
|
||||
return h.Buf.GetName()
|
||||
n := h.Buf.GetName()
|
||||
if h.Buf.Modified() {
|
||||
n += " +"
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// HandleEvent executes the tcell event properly
|
||||
func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||
if h.Buf.ExternallyModified() {
|
||||
InfoBar.YNPrompt("The file on disk has changed. Reload file? (y,n)", func(yes, canceled bool) {
|
||||
if h.Buf.ExternallyModified() && !h.Buf.ReloadDisabled {
|
||||
InfoBar.YNPrompt("The file on disk has changed. Reload file? (y,n,esc)", func(yes, canceled bool) {
|
||||
if canceled {
|
||||
h.Buf.DisableReload()
|
||||
}
|
||||
if !yes || canceled {
|
||||
h.Buf.UpdateModTime()
|
||||
} else {
|
||||
@@ -285,7 +332,7 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||
switch e.Buttons() {
|
||||
case tcell.Button1:
|
||||
_, my := e.Position()
|
||||
if h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 {
|
||||
if h.Buf.Type.Kind != buffer.BTInfo.Kind && h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 {
|
||||
cancel = true
|
||||
}
|
||||
case tcell.ButtonNone:
|
||||
@@ -310,10 +357,11 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||
// release the mouse
|
||||
|
||||
// if !h.doubleClick && !h.tripleClick {
|
||||
// h.Cursor.Loc = mouseLoc
|
||||
// h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
||||
// h.Cursor.CopySelection("primary")
|
||||
// }
|
||||
if h.Cursor.HasSelection() {
|
||||
h.Cursor.CopySelection("primary")
|
||||
}
|
||||
h.mouseReleased = true
|
||||
}
|
||||
}
|
||||
@@ -429,26 +477,34 @@ func (h *BufPane) DoRuneInsert(r rune) {
|
||||
if recording_macro {
|
||||
curmacro = append(curmacro, r)
|
||||
}
|
||||
h.Relocate()
|
||||
h.PluginCBRune("onRune", r)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
|
||||
e := NewBufPaneFromBuf(buf)
|
||||
e.splitID = MainTab().GetNode(h.splitID).VSplit(h.Buf.Settings["splitright"].(bool))
|
||||
func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
|
||||
e := NewBufPaneFromBuf(buf, h.tab)
|
||||
e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
|
||||
MainTab().Panes = append(MainTab().Panes, e)
|
||||
MainTab().Resize()
|
||||
MainTab().SetActive(len(MainTab().Panes) - 1)
|
||||
return e
|
||||
}
|
||||
func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
|
||||
e := NewBufPaneFromBuf(buf)
|
||||
e.splitID = MainTab().GetNode(h.splitID).HSplit(h.Buf.Settings["splitbottom"].(bool))
|
||||
func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
|
||||
e := NewBufPaneFromBuf(buf, h.tab)
|
||||
e.splitID = MainTab().GetNode(h.splitID).HSplit(bottom)
|
||||
MainTab().Panes = append(MainTab().Panes, e)
|
||||
MainTab().Resize()
|
||||
MainTab().SetActive(len(MainTab().Panes) - 1)
|
||||
return e
|
||||
}
|
||||
|
||||
func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
|
||||
return h.VSplitIndex(buf, h.Buf.Settings["splitright"].(bool))
|
||||
}
|
||||
func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
|
||||
return h.HSplitIndex(buf, h.Buf.Settings["splitbottom"].(bool))
|
||||
}
|
||||
func (h *BufPane) Close() {
|
||||
h.Buf.Close()
|
||||
}
|
||||
@@ -475,102 +531,111 @@ func (h *BufPane) SetActive(b bool) {
|
||||
|
||||
// BufKeyActions contains the list of all possible key actions the bufhandler could execute
|
||||
var BufKeyActions = map[string]BufKeyAction{
|
||||
"CursorUp": (*BufPane).CursorUp,
|
||||
"CursorDown": (*BufPane).CursorDown,
|
||||
"CursorPageUp": (*BufPane).CursorPageUp,
|
||||
"CursorPageDown": (*BufPane).CursorPageDown,
|
||||
"CursorLeft": (*BufPane).CursorLeft,
|
||||
"CursorRight": (*BufPane).CursorRight,
|
||||
"CursorStart": (*BufPane).CursorStart,
|
||||
"CursorEnd": (*BufPane).CursorEnd,
|
||||
"SelectToStart": (*BufPane).SelectToStart,
|
||||
"SelectToEnd": (*BufPane).SelectToEnd,
|
||||
"SelectUp": (*BufPane).SelectUp,
|
||||
"SelectDown": (*BufPane).SelectDown,
|
||||
"SelectLeft": (*BufPane).SelectLeft,
|
||||
"SelectRight": (*BufPane).SelectRight,
|
||||
"WordRight": (*BufPane).WordRight,
|
||||
"WordLeft": (*BufPane).WordLeft,
|
||||
"SelectWordRight": (*BufPane).SelectWordRight,
|
||||
"SelectWordLeft": (*BufPane).SelectWordLeft,
|
||||
"DeleteWordRight": (*BufPane).DeleteWordRight,
|
||||
"DeleteWordLeft": (*BufPane).DeleteWordLeft,
|
||||
"SelectLine": (*BufPane).SelectLine,
|
||||
"SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
|
||||
"SelectToStartOfText": (*BufPane).SelectToStartOfText,
|
||||
"SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
|
||||
"ParagraphPrevious": (*BufPane).ParagraphPrevious,
|
||||
"ParagraphNext": (*BufPane).ParagraphNext,
|
||||
"InsertNewline": (*BufPane).InsertNewline,
|
||||
"Backspace": (*BufPane).Backspace,
|
||||
"Delete": (*BufPane).Delete,
|
||||
"InsertTab": (*BufPane).InsertTab,
|
||||
"Save": (*BufPane).Save,
|
||||
"SaveAll": (*BufPane).SaveAll,
|
||||
"SaveAs": (*BufPane).SaveAs,
|
||||
"Find": (*BufPane).Find,
|
||||
"FindNext": (*BufPane).FindNext,
|
||||
"FindPrevious": (*BufPane).FindPrevious,
|
||||
"Center": (*BufPane).Center,
|
||||
"Undo": (*BufPane).Undo,
|
||||
"Redo": (*BufPane).Redo,
|
||||
"Copy": (*BufPane).Copy,
|
||||
"Cut": (*BufPane).Cut,
|
||||
"CutLine": (*BufPane).CutLine,
|
||||
"DuplicateLine": (*BufPane).DuplicateLine,
|
||||
"DeleteLine": (*BufPane).DeleteLine,
|
||||
"MoveLinesUp": (*BufPane).MoveLinesUp,
|
||||
"MoveLinesDown": (*BufPane).MoveLinesDown,
|
||||
"IndentSelection": (*BufPane).IndentSelection,
|
||||
"OutdentSelection": (*BufPane).OutdentSelection,
|
||||
"Autocomplete": (*BufPane).Autocomplete,
|
||||
"CycleAutocompleteBack": (*BufPane).CycleAutocompleteBack,
|
||||
"OutdentLine": (*BufPane).OutdentLine,
|
||||
"Paste": (*BufPane).Paste,
|
||||
"PastePrimary": (*BufPane).PastePrimary,
|
||||
"SelectAll": (*BufPane).SelectAll,
|
||||
"OpenFile": (*BufPane).OpenFile,
|
||||
"Start": (*BufPane).Start,
|
||||
"End": (*BufPane).End,
|
||||
"PageUp": (*BufPane).PageUp,
|
||||
"PageDown": (*BufPane).PageDown,
|
||||
"SelectPageUp": (*BufPane).SelectPageUp,
|
||||
"SelectPageDown": (*BufPane).SelectPageDown,
|
||||
"HalfPageUp": (*BufPane).HalfPageUp,
|
||||
"HalfPageDown": (*BufPane).HalfPageDown,
|
||||
"StartOfText": (*BufPane).StartOfText,
|
||||
"StartOfLine": (*BufPane).StartOfLine,
|
||||
"EndOfLine": (*BufPane).EndOfLine,
|
||||
"ToggleHelp": (*BufPane).ToggleHelp,
|
||||
"ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
|
||||
"ToggleRuler": (*BufPane).ToggleRuler,
|
||||
"ClearStatus": (*BufPane).ClearStatus,
|
||||
"ShellMode": (*BufPane).ShellMode,
|
||||
"CommandMode": (*BufPane).CommandMode,
|
||||
"ToggleOverwriteMode": (*BufPane).ToggleOverwriteMode,
|
||||
"Escape": (*BufPane).Escape,
|
||||
"Quit": (*BufPane).Quit,
|
||||
"QuitAll": (*BufPane).QuitAll,
|
||||
"AddTab": (*BufPane).AddTab,
|
||||
"PreviousTab": (*BufPane).PreviousTab,
|
||||
"NextTab": (*BufPane).NextTab,
|
||||
"NextSplit": (*BufPane).NextSplit,
|
||||
"PreviousSplit": (*BufPane).PreviousSplit,
|
||||
"Unsplit": (*BufPane).Unsplit,
|
||||
"VSplit": (*BufPane).VSplitAction,
|
||||
"HSplit": (*BufPane).HSplitAction,
|
||||
"ToggleMacro": (*BufPane).ToggleMacro,
|
||||
"PlayMacro": (*BufPane).PlayMacro,
|
||||
"Suspend": (*BufPane).Suspend,
|
||||
"ScrollUp": (*BufPane).ScrollUpAction,
|
||||
"ScrollDown": (*BufPane).ScrollDownAction,
|
||||
"SpawnMultiCursor": (*BufPane).SpawnMultiCursor,
|
||||
"SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
|
||||
"RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
|
||||
"RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
|
||||
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
|
||||
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
|
||||
"None": (*BufPane).None,
|
||||
"CursorUp": (*BufPane).CursorUp,
|
||||
"CursorDown": (*BufPane).CursorDown,
|
||||
"CursorPageUp": (*BufPane).CursorPageUp,
|
||||
"CursorPageDown": (*BufPane).CursorPageDown,
|
||||
"CursorLeft": (*BufPane).CursorLeft,
|
||||
"CursorRight": (*BufPane).CursorRight,
|
||||
"CursorStart": (*BufPane).CursorStart,
|
||||
"CursorEnd": (*BufPane).CursorEnd,
|
||||
"SelectToStart": (*BufPane).SelectToStart,
|
||||
"SelectToEnd": (*BufPane).SelectToEnd,
|
||||
"SelectUp": (*BufPane).SelectUp,
|
||||
"SelectDown": (*BufPane).SelectDown,
|
||||
"SelectLeft": (*BufPane).SelectLeft,
|
||||
"SelectRight": (*BufPane).SelectRight,
|
||||
"WordRight": (*BufPane).WordRight,
|
||||
"WordLeft": (*BufPane).WordLeft,
|
||||
"SelectWordRight": (*BufPane).SelectWordRight,
|
||||
"SelectWordLeft": (*BufPane).SelectWordLeft,
|
||||
"DeleteWordRight": (*BufPane).DeleteWordRight,
|
||||
"DeleteWordLeft": (*BufPane).DeleteWordLeft,
|
||||
"SelectLine": (*BufPane).SelectLine,
|
||||
"SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
|
||||
"SelectToStartOfText": (*BufPane).SelectToStartOfText,
|
||||
"SelectToStartOfTextToggle": (*BufPane).SelectToStartOfTextToggle,
|
||||
"SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
|
||||
"ParagraphPrevious": (*BufPane).ParagraphPrevious,
|
||||
"ParagraphNext": (*BufPane).ParagraphNext,
|
||||
"InsertNewline": (*BufPane).InsertNewline,
|
||||
"Backspace": (*BufPane).Backspace,
|
||||
"Delete": (*BufPane).Delete,
|
||||
"InsertTab": (*BufPane).InsertTab,
|
||||
"Save": (*BufPane).Save,
|
||||
"SaveAll": (*BufPane).SaveAll,
|
||||
"SaveAs": (*BufPane).SaveAs,
|
||||
"Find": (*BufPane).Find,
|
||||
"FindLiteral": (*BufPane).FindLiteral,
|
||||
"FindNext": (*BufPane).FindNext,
|
||||
"FindPrevious": (*BufPane).FindPrevious,
|
||||
"Center": (*BufPane).Center,
|
||||
"Undo": (*BufPane).Undo,
|
||||
"Redo": (*BufPane).Redo,
|
||||
"Copy": (*BufPane).Copy,
|
||||
"CopyLine": (*BufPane).CopyLine,
|
||||
"Cut": (*BufPane).Cut,
|
||||
"CutLine": (*BufPane).CutLine,
|
||||
"DuplicateLine": (*BufPane).DuplicateLine,
|
||||
"DeleteLine": (*BufPane).DeleteLine,
|
||||
"MoveLinesUp": (*BufPane).MoveLinesUp,
|
||||
"MoveLinesDown": (*BufPane).MoveLinesDown,
|
||||
"IndentSelection": (*BufPane).IndentSelection,
|
||||
"OutdentSelection": (*BufPane).OutdentSelection,
|
||||
"Autocomplete": (*BufPane).Autocomplete,
|
||||
"CycleAutocompleteBack": (*BufPane).CycleAutocompleteBack,
|
||||
"OutdentLine": (*BufPane).OutdentLine,
|
||||
"IndentLine": (*BufPane).IndentLine,
|
||||
"Paste": (*BufPane).Paste,
|
||||
"PastePrimary": (*BufPane).PastePrimary,
|
||||
"SelectAll": (*BufPane).SelectAll,
|
||||
"OpenFile": (*BufPane).OpenFile,
|
||||
"Start": (*BufPane).Start,
|
||||
"End": (*BufPane).End,
|
||||
"PageUp": (*BufPane).PageUp,
|
||||
"PageDown": (*BufPane).PageDown,
|
||||
"SelectPageUp": (*BufPane).SelectPageUp,
|
||||
"SelectPageDown": (*BufPane).SelectPageDown,
|
||||
"HalfPageUp": (*BufPane).HalfPageUp,
|
||||
"HalfPageDown": (*BufPane).HalfPageDown,
|
||||
"StartOfText": (*BufPane).StartOfText,
|
||||
"StartOfTextToggle": (*BufPane).StartOfTextToggle,
|
||||
"StartOfLine": (*BufPane).StartOfLine,
|
||||
"EndOfLine": (*BufPane).EndOfLine,
|
||||
"ToggleHelp": (*BufPane).ToggleHelp,
|
||||
"ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
|
||||
"ToggleDiffGutter": (*BufPane).ToggleDiffGutter,
|
||||
"ToggleRuler": (*BufPane).ToggleRuler,
|
||||
"ClearStatus": (*BufPane).ClearStatus,
|
||||
"ShellMode": (*BufPane).ShellMode,
|
||||
"CommandMode": (*BufPane).CommandMode,
|
||||
"ToggleOverwriteMode": (*BufPane).ToggleOverwriteMode,
|
||||
"Escape": (*BufPane).Escape,
|
||||
"Quit": (*BufPane).Quit,
|
||||
"QuitAll": (*BufPane).QuitAll,
|
||||
"AddTab": (*BufPane).AddTab,
|
||||
"PreviousTab": (*BufPane).PreviousTab,
|
||||
"NextTab": (*BufPane).NextTab,
|
||||
"NextSplit": (*BufPane).NextSplit,
|
||||
"PreviousSplit": (*BufPane).PreviousSplit,
|
||||
"Unsplit": (*BufPane).Unsplit,
|
||||
"VSplit": (*BufPane).VSplitAction,
|
||||
"HSplit": (*BufPane).HSplitAction,
|
||||
"ToggleMacro": (*BufPane).ToggleMacro,
|
||||
"PlayMacro": (*BufPane).PlayMacro,
|
||||
"Suspend": (*BufPane).Suspend,
|
||||
"ScrollUp": (*BufPane).ScrollUpAction,
|
||||
"ScrollDown": (*BufPane).ScrollDownAction,
|
||||
"SpawnMultiCursor": (*BufPane).SpawnMultiCursor,
|
||||
"SpawnMultiCursorUp": (*BufPane).SpawnMultiCursorUp,
|
||||
"SpawnMultiCursorDown": (*BufPane).SpawnMultiCursorDown,
|
||||
"SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
|
||||
"RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
|
||||
"RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
|
||||
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
|
||||
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
|
||||
"JumpLine": (*BufPane).JumpLine,
|
||||
"None": (*BufPane).None,
|
||||
|
||||
// This was changed to InsertNewline but I don't want to break backwards compatibility
|
||||
"InsertEnter": (*BufPane).InsertNewline,
|
||||
@@ -587,51 +652,57 @@ var BufMouseActions = map[string]BufMouseAction{
|
||||
// Generally actions that modify global editor state like quitting or
|
||||
// saving should not be included in this list
|
||||
var MultiActions = map[string]bool{
|
||||
"CursorUp": true,
|
||||
"CursorDown": true,
|
||||
"CursorPageUp": true,
|
||||
"CursorPageDown": true,
|
||||
"CursorLeft": true,
|
||||
"CursorRight": true,
|
||||
"CursorStart": true,
|
||||
"CursorEnd": true,
|
||||
"SelectToStart": true,
|
||||
"SelectToEnd": true,
|
||||
"SelectUp": true,
|
||||
"SelectDown": true,
|
||||
"SelectLeft": true,
|
||||
"SelectRight": true,
|
||||
"WordRight": true,
|
||||
"WordLeft": true,
|
||||
"SelectWordRight": true,
|
||||
"SelectWordLeft": true,
|
||||
"DeleteWordRight": true,
|
||||
"DeleteWordLeft": true,
|
||||
"SelectLine": true,
|
||||
"SelectToStartOfLine": true,
|
||||
"SelectToEndOfLine": true,
|
||||
"ParagraphPrevious": true,
|
||||
"ParagraphNext": true,
|
||||
"InsertNewline": true,
|
||||
"Backspace": true,
|
||||
"Delete": true,
|
||||
"InsertTab": true,
|
||||
"FindNext": true,
|
||||
"FindPrevious": true,
|
||||
"Cut": true,
|
||||
"CutLine": true,
|
||||
"DuplicateLine": true,
|
||||
"DeleteLine": true,
|
||||
"MoveLinesUp": true,
|
||||
"MoveLinesDown": true,
|
||||
"IndentSelection": true,
|
||||
"OutdentSelection": true,
|
||||
"OutdentLine": true,
|
||||
"Paste": true,
|
||||
"PastePrimary": true,
|
||||
"SelectPageUp": true,
|
||||
"SelectPageDown": true,
|
||||
"StartOfLine": true,
|
||||
"EndOfLine": true,
|
||||
"JumpToMatchingBrace": true,
|
||||
"CursorUp": true,
|
||||
"CursorDown": true,
|
||||
"CursorPageUp": true,
|
||||
"CursorPageDown": true,
|
||||
"CursorLeft": true,
|
||||
"CursorRight": true,
|
||||
"CursorStart": true,
|
||||
"CursorEnd": true,
|
||||
"SelectToStart": true,
|
||||
"SelectToEnd": true,
|
||||
"SelectUp": true,
|
||||
"SelectDown": true,
|
||||
"SelectLeft": true,
|
||||
"SelectRight": true,
|
||||
"WordRight": true,
|
||||
"WordLeft": true,
|
||||
"SelectWordRight": true,
|
||||
"SelectWordLeft": true,
|
||||
"DeleteWordRight": true,
|
||||
"DeleteWordLeft": true,
|
||||
"SelectLine": true,
|
||||
"SelectToStartOfLine": true,
|
||||
"SelectToStartOfText": true,
|
||||
"SelectToStartOfTextToggle": true,
|
||||
"SelectToEndOfLine": true,
|
||||
"ParagraphPrevious": true,
|
||||
"ParagraphNext": true,
|
||||
"InsertNewline": true,
|
||||
"Backspace": true,
|
||||
"Delete": true,
|
||||
"InsertTab": true,
|
||||
"FindNext": true,
|
||||
"FindPrevious": true,
|
||||
"CopyLine": true,
|
||||
"Cut": true,
|
||||
"CutLine": true,
|
||||
"DuplicateLine": true,
|
||||
"DeleteLine": true,
|
||||
"MoveLinesUp": true,
|
||||
"MoveLinesDown": true,
|
||||
"IndentSelection": true,
|
||||
"OutdentSelection": true,
|
||||
"OutdentLine": true,
|
||||
"IndentLine": true,
|
||||
"Paste": true,
|
||||
"PastePrimary": true,
|
||||
"SelectPageUp": true,
|
||||
"SelectPageDown": true,
|
||||
"StartOfLine": true,
|
||||
"StartOfText": true,
|
||||
"StartOfTextToggle": true,
|
||||
"EndOfLine": true,
|
||||
"JumpToMatchingBrace": true,
|
||||
}
|
||||
|
||||
@@ -10,18 +10,13 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/shell"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// A Command contains information about how to execute a command
|
||||
@@ -60,6 +55,7 @@ func InitCommands() {
|
||||
"cd": {(*BufPane).CdCmd, buffer.FileComplete},
|
||||
"pwd": {(*BufPane).PwdCmd, nil},
|
||||
"open": {(*BufPane).OpenCmd, buffer.FileComplete},
|
||||
"tabmove": {(*BufPane).TabMoveCmd, nil},
|
||||
"tabswitch": {(*BufPane).TabSwitchCmd, nil},
|
||||
"term": {(*BufPane).TermCmd, nil},
|
||||
"memusage": {(*BufPane).MemUsageCmd, nil},
|
||||
@@ -71,29 +67,9 @@ func InitCommands() {
|
||||
|
||||
// MakeCommand is a function to easily create new commands
|
||||
// This can be called by plugins in Lua so that plugins can define their own commands
|
||||
func LuaMakeCommand(name, function string, completer buffer.Completer) {
|
||||
action := LuaFunctionCommand(function)
|
||||
commands[name] = Command{action, completer}
|
||||
}
|
||||
|
||||
// LuaFunctionCommand returns a normal function
|
||||
// so that a command can be bound to a lua function
|
||||
func LuaFunctionCommand(fn string) func(*BufPane, []string) {
|
||||
luaFn := strings.Split(fn, ".")
|
||||
if len(luaFn) <= 1 {
|
||||
return nil
|
||||
}
|
||||
plName, plFn := luaFn[0], luaFn[1]
|
||||
pl := config.FindPlugin(plName)
|
||||
if pl == nil {
|
||||
return nil
|
||||
}
|
||||
return func(bp *BufPane, args []string) {
|
||||
luaArgs := []lua.LValue{luar.New(ulua.L, bp), luar.New(ulua.L, args)}
|
||||
_, err := pl.Call(plFn, luaArgs...)
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
func MakeCommand(name string, action func(bp *BufPane, args []string), completer buffer.Completer) {
|
||||
if action != nil {
|
||||
commands[name] = Command{action, completer}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,106 +96,20 @@ func CommandAction(cmd string) BufKeyAction {
|
||||
}
|
||||
}
|
||||
|
||||
var PluginCmds = []string{"list", "info", "version"}
|
||||
var PluginCmds = []string{"install", "remove", "update", "available", "list", "search"}
|
||||
|
||||
// PluginCmd installs, removes, updates, lists, or searches for given plugins
|
||||
func (h *BufPane) PluginCmd(args []string) {
|
||||
if len(args) <= 0 {
|
||||
InfoBar.Error("Not enough arguments, see 'help commands'")
|
||||
if len(args) < 1 {
|
||||
InfoBar.Error("Not enough arguments")
|
||||
return
|
||||
}
|
||||
|
||||
valid := true
|
||||
switch args[0] {
|
||||
case "list":
|
||||
for _, pl := range config.Plugins {
|
||||
var en string
|
||||
if pl.IsEnabled() {
|
||||
en = "enabled"
|
||||
} else {
|
||||
en = "disabled"
|
||||
}
|
||||
WriteLog(fmt.Sprintf("%s: %s", pl.Name, en))
|
||||
if pl.Default {
|
||||
WriteLog(" (default)\n")
|
||||
} else {
|
||||
WriteLog("\n")
|
||||
}
|
||||
}
|
||||
WriteLog("Default plugins come pre-installed with micro.")
|
||||
case "version":
|
||||
if len(args) <= 1 {
|
||||
InfoBar.Error("No plugin provided to give info for")
|
||||
return
|
||||
}
|
||||
found := false
|
||||
for _, pl := range config.Plugins {
|
||||
if pl.Name == args[1] {
|
||||
found = true
|
||||
if pl.Info == nil {
|
||||
InfoBar.Message("Sorry no version for", pl.Name)
|
||||
return
|
||||
}
|
||||
|
||||
WriteLog("Version: " + pl.Info.Vstr + "\n")
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
InfoBar.Message(args[1], "is not installed")
|
||||
}
|
||||
case "info":
|
||||
if len(args) <= 1 {
|
||||
InfoBar.Error("No plugin provided to give info for")
|
||||
return
|
||||
}
|
||||
found := false
|
||||
for _, pl := range config.Plugins {
|
||||
if pl.Name == args[1] {
|
||||
found = true
|
||||
if pl.Info == nil {
|
||||
InfoBar.Message("Sorry no info for ", pl.Name)
|
||||
return
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString("Name: ")
|
||||
buffer.WriteString(pl.Info.Name)
|
||||
buffer.WriteString("\n")
|
||||
buffer.WriteString("Description: ")
|
||||
buffer.WriteString(pl.Info.Desc)
|
||||
buffer.WriteString("\n")
|
||||
buffer.WriteString("Website: ")
|
||||
buffer.WriteString(pl.Info.Site)
|
||||
buffer.WriteString("\n")
|
||||
buffer.WriteString("Installation link: ")
|
||||
buffer.WriteString(pl.Info.Install)
|
||||
buffer.WriteString("\n")
|
||||
buffer.WriteString("Version: ")
|
||||
buffer.WriteString(pl.Info.Vstr)
|
||||
buffer.WriteString("\n")
|
||||
buffer.WriteString("Requirements:")
|
||||
buffer.WriteString("\n")
|
||||
for _, r := range pl.Info.Require {
|
||||
buffer.WriteString(" - ")
|
||||
buffer.WriteString(r)
|
||||
buffer.WriteString("\n")
|
||||
}
|
||||
|
||||
WriteLog(buffer.String())
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
InfoBar.Message(args[1], "is not installed")
|
||||
return
|
||||
}
|
||||
default:
|
||||
InfoBar.Error("Not a valid plugin command")
|
||||
return
|
||||
if h.Buf.Type != buffer.BTLog {
|
||||
h.OpenLogBuf()
|
||||
}
|
||||
|
||||
if valid && h.Buf.Type != buffer.BTLog {
|
||||
OpenLogBuf(h)
|
||||
}
|
||||
config.PluginCommand(buffer.LogBuf, args[0], args[1:])
|
||||
}
|
||||
|
||||
// RetabCmd changes all spaces to tabs or all tabs to spaces
|
||||
@@ -233,7 +123,7 @@ func (h *BufPane) RetabCmd(args []string) {
|
||||
func (h *BufPane) RawCmd(args []string) {
|
||||
width, height := screen.Screen.Size()
|
||||
iOffset := config.GetInfoBarOffset()
|
||||
tp := NewTabFromPane(0, 0, width, height-iOffset, NewRawPane())
|
||||
tp := NewTabFromPane(0, 0, width, height-iOffset, NewRawPane(nil))
|
||||
Tabs.AddTab(tp)
|
||||
Tabs.SetActive(len(Tabs.List) - 1)
|
||||
}
|
||||
@@ -265,6 +155,56 @@ func (h *BufPane) TextFilterCmd(args []string) {
|
||||
h.Buf.Insert(h.Cursor.Loc, bout.String())
|
||||
}
|
||||
|
||||
// TabMoveCmd moves the current tab to a given index (starts at 1). The
|
||||
// displaced tabs are moved up.
|
||||
func (h *BufPane) TabMoveCmd(args []string) {
|
||||
if len(args) <= 0 {
|
||||
InfoBar.Error("Not enough arguments: provide an index, starting at 1")
|
||||
return
|
||||
}
|
||||
|
||||
if len(args[0]) <= 0 {
|
||||
InfoBar.Error("Invalid argument: empty string")
|
||||
return
|
||||
}
|
||||
|
||||
num, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
InfoBar.Error("Invalid argument: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Preserve sign for relative move, if one exists
|
||||
var shiftDirection byte
|
||||
if strings.Contains("-+", string([]byte{args[0][0]})) {
|
||||
shiftDirection = args[0][0]
|
||||
}
|
||||
|
||||
// Relative positions -> absolute positions
|
||||
idxFrom := Tabs.Active()
|
||||
idxTo := 0
|
||||
offset := util.Abs(num)
|
||||
if shiftDirection == '-' {
|
||||
idxTo = idxFrom - offset
|
||||
} else if shiftDirection == '+' {
|
||||
idxTo = idxFrom + offset
|
||||
} else {
|
||||
idxTo = offset - 1
|
||||
}
|
||||
|
||||
// Restrain position to within the valid range
|
||||
idxTo = util.Clamp(idxTo, 0, len(Tabs.List)-1)
|
||||
|
||||
activeTab := Tabs.List[idxFrom]
|
||||
Tabs.RemoveTab(activeTab.ID())
|
||||
Tabs.List = append(Tabs.List, nil)
|
||||
copy(Tabs.List[idxTo+1:], Tabs.List[idxTo:])
|
||||
Tabs.List[idxTo] = activeTab
|
||||
Tabs.UpdateNames()
|
||||
Tabs.SetActive(idxTo)
|
||||
// InfoBar.Message(fmt.Sprintf("Moved tab from slot %d to %d", idxFrom+1, idxTo+1))
|
||||
}
|
||||
|
||||
// TabSwitchCmd switches to a given tab either by name or by number
|
||||
func (h *BufPane) TabSwitchCmd(args []string) {
|
||||
if len(args) > 0 {
|
||||
@@ -382,7 +322,7 @@ func (h *BufPane) OpenCmd(args []string) {
|
||||
// ToggleLogCmd toggles the log view
|
||||
func (h *BufPane) ToggleLogCmd(args []string) {
|
||||
if h.Buf.Type != buffer.BTLog {
|
||||
OpenLogBuf(h)
|
||||
h.OpenLogBuf()
|
||||
} else {
|
||||
h.Quit()
|
||||
}
|
||||
@@ -399,7 +339,10 @@ func ReloadConfig() {
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
config.InitGlobalSettings()
|
||||
err = config.InitGlobalSettings()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
InitBindings()
|
||||
InitCommands()
|
||||
|
||||
@@ -537,6 +480,7 @@ func SetGlobalOptionNative(option string, nativeValue interface{}) error {
|
||||
|
||||
if !local {
|
||||
config.GlobalSettings[option] = nativeValue
|
||||
config.ModifiedSettings[option] = true
|
||||
|
||||
if option == "colorscheme" {
|
||||
// LoadSyntaxFiles()
|
||||
@@ -552,14 +496,13 @@ func SetGlobalOptionNative(option string, nativeValue interface{}) error {
|
||||
} else {
|
||||
screen.Screen.EnableMouse()
|
||||
}
|
||||
// autosave option has been removed
|
||||
// } else if option == "autosave" {
|
||||
// if nativeValue.(float64) > 0 {
|
||||
// config.SetAutoTime(int(nativeValue.(float64)))
|
||||
// config.StartAutoSave()
|
||||
// } else {
|
||||
// config.SetAutoTime(0)
|
||||
// }
|
||||
} else if option == "autosave" {
|
||||
if nativeValue.(float64) > 0 {
|
||||
config.SetAutoTime(int(nativeValue.(float64)))
|
||||
config.StartAutoSave()
|
||||
} else {
|
||||
config.SetAutoTime(0)
|
||||
}
|
||||
} else if option == "paste" {
|
||||
screen.Screen.SetPaste(nativeValue.(bool))
|
||||
} else {
|
||||
@@ -586,7 +529,7 @@ func SetGlobalOptionNative(option string, nativeValue interface{}) error {
|
||||
b.SetOptionNative(option, nativeValue)
|
||||
}
|
||||
|
||||
return config.WriteSettings(config.ConfigDir + "/settings.json")
|
||||
return config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
|
||||
}
|
||||
|
||||
func SetGlobalOption(option, value string) error {
|
||||
@@ -762,8 +705,11 @@ func (h *BufPane) GotoCmd(args []string) {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
if line < 0 {
|
||||
line = h.Buf.LinesNum() + 1 + line
|
||||
}
|
||||
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
|
||||
col = util.Clamp(col-1, 0, utf8.RuneCount(h.Buf.LineBytes(line)))
|
||||
col = util.Clamp(col-1, 0, util.CharacterCount(h.Buf.LineBytes(line)))
|
||||
h.Cursor.GotoLoc(buffer.Loc{col, line})
|
||||
} else {
|
||||
line, err := strconv.Atoi(args[0])
|
||||
@@ -771,6 +717,9 @@ func (h *BufPane) GotoCmd(args []string) {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
if line < 0 {
|
||||
line = h.Buf.LinesNum() + 1 + line
|
||||
}
|
||||
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
|
||||
h.Cursor.GotoLoc(buffer.Loc{0, line})
|
||||
}
|
||||
@@ -843,23 +792,23 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
||||
|
||||
nreplaced := 0
|
||||
start := h.Buf.Start()
|
||||
// end := h.Buf.End()
|
||||
// if h.Cursor.HasSelection() {
|
||||
// start = h.Cursor.CurSelection[0]
|
||||
// end = h.Cursor.CurSelection[1]
|
||||
// }
|
||||
end := h.Buf.End()
|
||||
selection := h.Cursor.HasSelection()
|
||||
if selection {
|
||||
start = h.Cursor.CurSelection[0]
|
||||
end = h.Cursor.CurSelection[1]
|
||||
}
|
||||
if all {
|
||||
nreplaced = h.Buf.ReplaceRegex(start, h.Buf.End(), regex, replace)
|
||||
nreplaced, _ = h.Buf.ReplaceRegex(start, end, regex, replace)
|
||||
} else {
|
||||
inRange := func(l buffer.Loc) bool {
|
||||
return l.GreaterEqual(start) && l.LessEqual(h.Buf.End())
|
||||
return l.GreaterEqual(start) && l.LessEqual(end)
|
||||
}
|
||||
|
||||
searchLoc := start
|
||||
searching := true
|
||||
var doReplacement func()
|
||||
doReplacement = func() {
|
||||
locs, found, err := h.Buf.FindNext(search, start, h.Buf.End(), searchLoc, true, !noRegex)
|
||||
locs, found, err := h.Buf.FindNext(search, start, end, searchLoc, true, !noRegex)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
@@ -872,40 +821,50 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
||||
|
||||
h.Cursor.SetSelectionStart(locs[0])
|
||||
h.Cursor.SetSelectionEnd(locs[1])
|
||||
h.Cursor.GotoLoc(locs[0])
|
||||
|
||||
h.Relocate()
|
||||
|
||||
InfoBar.YNPrompt("Perform replacement (y,n,esc)", func(yes, canceled bool) {
|
||||
if !canceled && yes {
|
||||
h.Buf.Replace(locs[0], locs[1], replaceStr)
|
||||
_, nrunes := h.Buf.ReplaceRegex(locs[0], locs[1], regex, replace)
|
||||
|
||||
searchLoc = locs[0]
|
||||
searchLoc.X += utf8.RuneCount(replace)
|
||||
searchLoc.X += nrunes + locs[0].Diff(locs[1], h.Buf)
|
||||
end.Move(nrunes, h.Buf)
|
||||
h.Cursor.Loc = searchLoc
|
||||
nreplaced++
|
||||
} else if !canceled && !yes {
|
||||
searchLoc = locs[0]
|
||||
searchLoc.X += utf8.RuneCount(replace)
|
||||
searchLoc.X += util.CharacterCount(replace)
|
||||
} else if canceled {
|
||||
h.Cursor.ResetSelection()
|
||||
h.Buf.RelocateCursors()
|
||||
return
|
||||
}
|
||||
if searching {
|
||||
doReplacement()
|
||||
}
|
||||
doReplacement()
|
||||
})
|
||||
}
|
||||
doReplacement()
|
||||
}
|
||||
|
||||
h.Buf.RelocateCursors()
|
||||
h.Relocate()
|
||||
|
||||
var s string
|
||||
if nreplaced > 1 {
|
||||
InfoBar.Message("Replaced ", nreplaced, " occurrences of ", search)
|
||||
s = fmt.Sprintf("Replaced %d occurrences of %s", nreplaced, search)
|
||||
} else if nreplaced == 1 {
|
||||
InfoBar.Message("Replaced ", nreplaced, " occurrence of ", search)
|
||||
s = fmt.Sprintf("Replaced 1 occurrence of %s", search)
|
||||
} else {
|
||||
InfoBar.Message("Nothing matched ", search)
|
||||
s = fmt.Sprintf("Nothing matched %s", search)
|
||||
}
|
||||
|
||||
if selection {
|
||||
s += " in selection"
|
||||
}
|
||||
|
||||
InfoBar.Message(s)
|
||||
}
|
||||
|
||||
// ReplaceAllCmd replaces search term all at once
|
||||
@@ -916,7 +875,12 @@ func (h *BufPane) ReplaceAllCmd(args []string) {
|
||||
|
||||
// TermCmd opens a terminal in the current view
|
||||
func (h *BufPane) TermCmd(args []string) {
|
||||
ps := MainTab().Panes
|
||||
ps := h.tab.Panes
|
||||
|
||||
if !TermEmuSupported {
|
||||
InfoBar.Error("Terminal emulator not supported on this system")
|
||||
return
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
sh := os.Getenv("SHELL")
|
||||
@@ -929,7 +893,11 @@ func (h *BufPane) TermCmd(args []string) {
|
||||
|
||||
term := func(i int, newtab bool) {
|
||||
t := new(shell.Terminal)
|
||||
t.Start(args, false, true, "", nil)
|
||||
err := t.Start(args, false, true, nil, nil)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
id := h.ID()
|
||||
if newtab {
|
||||
@@ -941,7 +909,12 @@ func (h *BufPane) TermCmd(args []string) {
|
||||
}
|
||||
|
||||
v := h.GetView()
|
||||
MainTab().Panes[i] = NewTermPane(v.X, v.Y, v.Width, v.Height, t, id)
|
||||
tp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
MainTab().Panes[i] = tp
|
||||
MainTab().SetActive(i)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@ func DefaultBindings() map[string]string {
|
||||
"AltDown": "MoveLinesDown",
|
||||
"AltShiftRight": "SelectWordRight",
|
||||
"AltShiftLeft": "SelectWordLeft",
|
||||
"CtrlLeft": "StartOfText",
|
||||
"CtrlLeft": "StartOfTextToggle",
|
||||
"CtrlRight": "EndOfLine",
|
||||
"CtrlShiftLeft": "SelectToStartOfText",
|
||||
"ShiftHome": "SelectToStartOfText",
|
||||
"CtrlShiftLeft": "SelectToStartOfTextToggle",
|
||||
"ShiftHome": "SelectToStartOfTextToggle",
|
||||
"CtrlShiftRight": "SelectToEndOfLine",
|
||||
"ShiftEnd": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
@@ -43,7 +43,7 @@ func DefaultBindings() map[string]string {
|
||||
"CtrlP": "FindPrevious",
|
||||
"CtrlZ": "Undo",
|
||||
"CtrlY": "Redo",
|
||||
"CtrlC": "Copy",
|
||||
"CtrlC": "CopyLine|Copy",
|
||||
"CtrlX": "Cut",
|
||||
"CtrlK": "CutLine",
|
||||
"CtrlD": "DuplicateLine",
|
||||
@@ -52,7 +52,7 @@ func DefaultBindings() map[string]string {
|
||||
"CtrlT": "AddTab",
|
||||
"Alt,": "PreviousTab",
|
||||
"Alt.": "NextTab",
|
||||
"Home": "StartOfText",
|
||||
"Home": "StartOfTextToggle",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
@@ -96,10 +96,12 @@ func DefaultBindings() map[string]string {
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"AltShiftUp": "SpawnMultiCursorUp",
|
||||
"AltShiftDown": "SpawnMultiCursorDown",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@ func DefaultBindings() map[string]string {
|
||||
"AltDown": "MoveLinesDown",
|
||||
"CtrlShiftRight": "SelectWordRight",
|
||||
"CtrlShiftLeft": "SelectWordLeft",
|
||||
"AltLeft": "StartOfText",
|
||||
"AltLeft": "StartOfTextToggle",
|
||||
"AltRight": "EndOfLine",
|
||||
"AltShiftLeft": "SelectToStartOfText",
|
||||
"ShiftHome": "SelectToStartOfText",
|
||||
"AltShiftLeft": "SelectToStartOfTextToggle",
|
||||
"ShiftHome": "SelectToStartOfTextToggle",
|
||||
"AltShiftRight": "SelectToEndOfLine",
|
||||
"ShiftEnd": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
@@ -45,7 +45,7 @@ func DefaultBindings() map[string]string {
|
||||
"CtrlP": "FindPrevious",
|
||||
"CtrlZ": "Undo",
|
||||
"CtrlY": "Redo",
|
||||
"CtrlC": "Copy",
|
||||
"CtrlC": "CopyLine|Copy",
|
||||
"CtrlX": "Cut",
|
||||
"CtrlK": "CutLine",
|
||||
"CtrlD": "DuplicateLine",
|
||||
@@ -54,7 +54,7 @@ func DefaultBindings() map[string]string {
|
||||
"CtrlT": "AddTab",
|
||||
"Alt,": "PreviousTab",
|
||||
"Alt.": "NextTab",
|
||||
"Home": "StartOfText",
|
||||
"Home": "StartOfTextToggle",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
@@ -98,10 +98,12 @@ func DefaultBindings() map[string]string {
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"AltShiftUp": "SpawnMultiCursorUp",
|
||||
"AltShiftDown": "SpawnMultiCursorDown",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
package action
|
||||
|
||||
import "github.com/zyedidia/micro/internal/buffer"
|
||||
import "github.com/zyedidia/micro/v2/internal/buffer"
|
||||
|
||||
var InfoBar *InfoPane
|
||||
var LogBufPane *BufPane
|
||||
|
||||
// InitGlobals initializes the log buffer and the info bar
|
||||
func InitGlobals() {
|
||||
InfoBar = NewInfoBar()
|
||||
buffer.LogBuf = buffer.NewBufferFromString("", "Log", buffer.BTLog)
|
||||
}
|
||||
|
||||
// GetInfoBar returns the infobar pane
|
||||
func GetInfoBar() *InfoPane {
|
||||
return InfoBar
|
||||
}
|
||||
|
||||
// WriteLog writes a string to the log buffer
|
||||
func WriteLog(s string) {
|
||||
buffer.WriteLog(s)
|
||||
if LogBufPane != nil {
|
||||
@@ -28,7 +31,10 @@ func WriteLog(s string) {
|
||||
}
|
||||
}
|
||||
|
||||
func OpenLogBuf(h *BufPane) {
|
||||
// OpenLogBuf opens the log buffer from the current bufpane
|
||||
// If the current bufpane is a log buffer nothing happens,
|
||||
// otherwise the log buffer is opened in a horizontal split
|
||||
func (h *BufPane) OpenLogBuf() {
|
||||
LogBufPane = h.HSplitBuf(buffer.LogBuf)
|
||||
LogBufPane.CursorEnd()
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// This file is meant (for now) for autocompletion in command mode, not
|
||||
@@ -197,7 +197,7 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
return completions, suggestions
|
||||
}
|
||||
|
||||
// OptionComplete autocompletes options
|
||||
// PluginCmdComplete autocompletes the plugin command
|
||||
func PluginCmdComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
c := b.GetActiveCursor()
|
||||
input, argstart := buffer.GetArg(b)
|
||||
@@ -253,51 +253,28 @@ func PluginComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
return completions, suggestions
|
||||
}
|
||||
|
||||
// // MakeCompletion registers a function from a plugin for autocomplete commands
|
||||
// func MakeCompletion(function string) Completion {
|
||||
// pluginCompletions = append(pluginCompletions, LuaFunctionComplete(function))
|
||||
// return Completion(-len(pluginCompletions))
|
||||
// }
|
||||
// PluginNameComplete completes with the names of loaded plugins
|
||||
// func PluginNameComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
// c := b.GetActiveCursor()
|
||||
// input, argstart := buffer.GetArg(b)
|
||||
//
|
||||
// // PluginComplete autocompletes from plugin function
|
||||
// func PluginComplete(complete Completion, input string) (chosen string, suggestions []string) {
|
||||
// idx := int(-complete) - 1
|
||||
//
|
||||
// if len(pluginCompletions) <= idx {
|
||||
// return "", nil
|
||||
// }
|
||||
// suggestions = pluginCompletions[idx](input)
|
||||
//
|
||||
// if len(suggestions) == 1 {
|
||||
// chosen = suggestions[0]
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // PluginCmdComplete completes with possible choices for the `> plugin` command
|
||||
// func PluginCmdComplete(input string) (chosen string, suggestions []string) {
|
||||
// for _, cmd := range []string{"install", "remove", "search", "update", "list"} {
|
||||
// if strings.HasPrefix(cmd, input) {
|
||||
// suggestions = append(suggestions, cmd)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if len(suggestions) == 1 {
|
||||
// chosen = suggestions[0]
|
||||
// }
|
||||
// return chosen, suggestions
|
||||
// }
|
||||
//
|
||||
// // PluginnameComplete completes with the names of loaded plugins
|
||||
// func PluginNameComplete(input string) (chosen string, suggestions []string) {
|
||||
// for _, pp := range GetAllPluginPackages() {
|
||||
// var suggestions []string
|
||||
// for _, pp := range config.GetAllPluginPackages(nil) {
|
||||
// if strings.HasPrefix(pp.Name, input) {
|
||||
// suggestions = append(suggestions, pp.Name)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if len(suggestions) == 1 {
|
||||
// chosen = suggestions[0]
|
||||
// sort.Strings(suggestions)
|
||||
// completions := make([]string, len(suggestions))
|
||||
// for i := range suggestions {
|
||||
// completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
|
||||
// }
|
||||
// return chosen, suggestions
|
||||
// return completions, suggestions
|
||||
// }
|
||||
|
||||
// // MakeCompletion registers a function from a plugin for autocomplete commands
|
||||
// func MakeCompletion(function string) Completion {
|
||||
// pluginCompletions = append(pluginCompletions, LuaFunctionComplete(function))
|
||||
// return Completion(-len(pluginCompletions))
|
||||
// }
|
||||
|
||||
@@ -4,9 +4,10 @@ import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/internal/display"
|
||||
"github.com/zyedidia/micro/internal/info"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
"github.com/zyedidia/micro/v2/internal/info"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -17,10 +18,10 @@ type InfoPane struct {
|
||||
*info.InfoBuf
|
||||
}
|
||||
|
||||
func NewInfoPane(ib *info.InfoBuf, w display.BWindow) *InfoPane {
|
||||
func NewInfoPane(ib *info.InfoBuf, w display.BWindow, tab *Tab) *InfoPane {
|
||||
ip := new(InfoPane)
|
||||
ip.InfoBuf = ib
|
||||
ip.BufPane = NewBufPane(ib.Buffer, w)
|
||||
ip.BufPane = NewBufPane(ib.Buffer, w, tab)
|
||||
|
||||
return ip
|
||||
}
|
||||
@@ -28,7 +29,7 @@ func NewInfoPane(ib *info.InfoBuf, w display.BWindow) *InfoPane {
|
||||
func NewInfoBar() *InfoPane {
|
||||
ib := info.NewBuffer()
|
||||
w := display.NewInfoWindow(ib)
|
||||
return NewInfoPane(ib, w)
|
||||
return NewInfoPane(ib, w, nil)
|
||||
}
|
||||
|
||||
func (h *InfoPane) Close() {
|
||||
@@ -68,11 +69,12 @@ func (h *InfoPane) HandleEvent(event tcell.Event) {
|
||||
h.EventCallback(resp)
|
||||
}
|
||||
}
|
||||
case *tcell.EventMouse:
|
||||
default:
|
||||
h.BufPane.HandleEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
// DoKeyEvent executes a key event for the command bar, doing any overridden actions
|
||||
func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
|
||||
done := false
|
||||
if action, ok := BufKeyBindings[e]; ok {
|
||||
@@ -85,7 +87,7 @@ func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
|
||||
for s, a := range InfoOverrides {
|
||||
// TODO this is a hack and really we should have support
|
||||
// for having binding overrides for different buffers
|
||||
if strings.Contains(estr, s) {
|
||||
if strings.HasPrefix(estr, s) {
|
||||
done = true
|
||||
a(h)
|
||||
break
|
||||
@@ -122,6 +124,7 @@ var InfoNones = []string{
|
||||
"HalfPageDown",
|
||||
"ToggleHelp",
|
||||
"ToggleKeyMenu",
|
||||
"ToggleDiffGutter",
|
||||
"ToggleRuler",
|
||||
"JumpLine",
|
||||
"ClearStatus",
|
||||
@@ -184,14 +187,17 @@ func (h *InfoPane) Autocomplete() {
|
||||
args := bytes.Split(l, []byte{' '})
|
||||
cmd := string(args[0])
|
||||
|
||||
if len(args) == 1 {
|
||||
b.Autocomplete(CommandComplete)
|
||||
} else {
|
||||
if action, ok := commands[cmd]; ok {
|
||||
if h.PromptType == "Command" {
|
||||
if len(args) == 1 {
|
||||
b.Autocomplete(CommandComplete)
|
||||
} else if action, ok := commands[cmd]; ok {
|
||||
if action.completer != nil {
|
||||
b.Autocomplete(action.completer)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// by default use filename autocompletion
|
||||
b.Autocomplete(buffer.FileComplete)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/internal/display"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
)
|
||||
|
||||
type Pane interface {
|
||||
@@ -11,4 +11,6 @@ type Pane interface {
|
||||
SetID(i uint64)
|
||||
Name() string
|
||||
Close()
|
||||
SetTab(t *Tab)
|
||||
Tab() *Tab
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/display"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -13,17 +13,17 @@ type RawPane struct {
|
||||
*BufPane
|
||||
}
|
||||
|
||||
func NewRawPaneFromWin(b *buffer.Buffer, win display.BWindow) *RawPane {
|
||||
func NewRawPaneFromWin(b *buffer.Buffer, win display.BWindow, tab *Tab) *RawPane {
|
||||
rh := new(RawPane)
|
||||
rh.BufPane = NewBufPane(b, win)
|
||||
rh.BufPane = NewBufPane(b, win, tab)
|
||||
|
||||
return rh
|
||||
}
|
||||
|
||||
func NewRawPane() *RawPane {
|
||||
func NewRawPane(tab *Tab) *RawPane {
|
||||
b := buffer.NewBufferFromString("", "", buffer.BTRaw)
|
||||
w := display.NewBufWindow(0, 0, 0, 0, b)
|
||||
return NewRawPaneFromWin(b, w)
|
||||
return NewRawPaneFromWin(b, w, tab)
|
||||
}
|
||||
|
||||
func (h *RawPane) HandleEvent(event tcell.Event) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/display"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/views"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/views"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -104,6 +104,13 @@ func (t *TabList) HandleEvent(event tcell.Event) {
|
||||
mx, my := e.Position()
|
||||
switch e.Buttons() {
|
||||
case tcell.Button1:
|
||||
if my == t.Y && mx == 0 {
|
||||
t.Scroll(-4)
|
||||
return
|
||||
} else if my == t.Y && mx == t.Width-1 {
|
||||
t.Scroll(4)
|
||||
return
|
||||
}
|
||||
if len(t.List) > 1 {
|
||||
ind := t.LocFromVisual(buffer.Loc{mx, my})
|
||||
if ind != -1 {
|
||||
@@ -159,6 +166,8 @@ type Tab struct {
|
||||
active int
|
||||
|
||||
resizing *views.Node // node currently being resized
|
||||
// captures whether the mouse is released
|
||||
release bool
|
||||
}
|
||||
|
||||
// NewTabFromBuffer creates a new tab from the given buffer
|
||||
@@ -167,7 +176,7 @@ func NewTabFromBuffer(x, y, width, height int, b *buffer.Buffer) *Tab {
|
||||
t.Node = views.NewRoot(x, y, width, height)
|
||||
t.UIWindow = display.NewUIWindow(t.Node)
|
||||
|
||||
e := NewBufPaneFromBuf(b)
|
||||
e := NewBufPaneFromBuf(b, t)
|
||||
e.SetID(t.ID())
|
||||
|
||||
t.Panes = append(t.Panes, e)
|
||||
@@ -178,7 +187,7 @@ func NewTabFromPane(x, y, width, height int, pane Pane) *Tab {
|
||||
t := new(Tab)
|
||||
t.Node = views.NewRoot(x, y, width, height)
|
||||
t.UIWindow = display.NewUIWindow(t.Node)
|
||||
|
||||
pane.SetTab(t)
|
||||
pane.SetID(t.ID())
|
||||
|
||||
t.Panes = append(t.Panes, pane)
|
||||
@@ -196,7 +205,8 @@ func (t *Tab) HandleEvent(event tcell.Event) {
|
||||
mx, my := e.Position()
|
||||
switch e.Buttons() {
|
||||
case tcell.Button1:
|
||||
resizeID := t.GetMouseSplitID(buffer.Loc{mx, my})
|
||||
wasReleased := t.release
|
||||
t.release = false
|
||||
if t.resizing != nil {
|
||||
var size int
|
||||
if t.resizing.Kind == views.STVert {
|
||||
@@ -209,21 +219,25 @@ func (t *Tab) HandleEvent(event tcell.Event) {
|
||||
return
|
||||
}
|
||||
|
||||
if resizeID != 0 {
|
||||
t.resizing = t.GetNode(uint64(resizeID))
|
||||
return
|
||||
}
|
||||
if wasReleased {
|
||||
resizeID := t.GetMouseSplitID(buffer.Loc{mx, my})
|
||||
if resizeID != 0 {
|
||||
t.resizing = t.GetNode(uint64(resizeID))
|
||||
return
|
||||
}
|
||||
|
||||
for i, p := range t.Panes {
|
||||
v := p.GetView()
|
||||
inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height
|
||||
if inpane {
|
||||
t.SetActive(i)
|
||||
break
|
||||
for i, p := range t.Panes {
|
||||
v := p.GetView()
|
||||
inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height
|
||||
if inpane {
|
||||
t.SetActive(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case tcell.ButtonNone:
|
||||
t.resizing = nil
|
||||
t.release = true
|
||||
default:
|
||||
for _, p := range t.Panes {
|
||||
v := p.GetView()
|
||||
@@ -284,6 +298,10 @@ func (t *Tab) Resize() {
|
||||
}
|
||||
|
||||
// CurPane returns the currently active pane
|
||||
func (t *Tab) CurPane() Pane {
|
||||
return t.Panes[t.active]
|
||||
func (t *Tab) CurPane() *BufPane {
|
||||
p, ok := t.Panes[t.active].(*BufPane)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
@@ -4,12 +4,17 @@ package action
|
||||
|
||||
import (
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"github.com/zyedidia/micro/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
)
|
||||
|
||||
// TermEmuSupported is a constant that marks if the terminal emulator is supported
|
||||
const TermEmuSupported = true
|
||||
|
||||
func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callback string, userargs []interface{}) error {
|
||||
// RunTermEmulator starts a terminal emulator from a bufpane with the given input (command)
|
||||
// if wait is true it will wait for the user to exit by pressing enter once the executable has terminated
|
||||
// if getOutput is true it will redirect the stdout of the process to a pipe which will be passed to the
|
||||
// callback which is a function that takes a string and a list of optional user arguments
|
||||
func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
|
||||
args, err := shellquote.Split(input)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -25,7 +30,12 @@ func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callba
|
||||
id := MainTab().Panes[0].ID()
|
||||
|
||||
v := h.GetView()
|
||||
MainTab().Panes[0] = NewTermPane(v.X, v.Y, v.Width, v.Height, t, id)
|
||||
|
||||
tp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
MainTab().Panes[0] = tp
|
||||
MainTab().SetActive(0)
|
||||
|
||||
return nil
|
||||
|
||||
@@ -4,8 +4,10 @@ package action
|
||||
|
||||
import "errors"
|
||||
|
||||
// TermEmuSupported is a constant that marks if the terminal emulator is supported
|
||||
const TermEmuSupported = false
|
||||
|
||||
func RunTermEmulator(input string, wait bool, getOutput bool, callback string, userargs []interface{}) error {
|
||||
// RunTermEmulator returns an error for unsupported systems (non-unix systems
|
||||
func RunTermEmulator(input string, wait bool, getOutput bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
|
||||
return errors.New("Unsupported operating system")
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
|
||||
"github.com/zyedidia/clipboard"
|
||||
"github.com/zyedidia/micro/internal/display"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/zyedidia/tcell"
|
||||
"github.com/zyedidia/terminal"
|
||||
)
|
||||
@@ -17,15 +18,21 @@ type TermPane struct {
|
||||
|
||||
mouseReleased bool
|
||||
id uint64
|
||||
tab *Tab
|
||||
}
|
||||
|
||||
func NewTermPane(x, y, w, h int, t *shell.Terminal, id uint64) *TermPane {
|
||||
func NewTermPane(x, y, w, h int, t *shell.Terminal, id uint64, tab *Tab) (*TermPane, error) {
|
||||
if !TermEmuSupported {
|
||||
return nil, errors.New("Terminal emulator is not supported on this system")
|
||||
}
|
||||
|
||||
th := new(TermPane)
|
||||
th.Terminal = t
|
||||
th.id = id
|
||||
th.mouseReleased = true
|
||||
th.Window = display.NewTermWindow(x, y, w, h, t)
|
||||
return th
|
||||
th.tab = tab
|
||||
return th, nil
|
||||
}
|
||||
|
||||
func (t *TermPane) ID() uint64 {
|
||||
@@ -36,6 +43,14 @@ func (t *TermPane) SetID(i uint64) {
|
||||
t.id = i
|
||||
}
|
||||
|
||||
func (t *TermPane) SetTab(tab *Tab) {
|
||||
t.tab = tab
|
||||
}
|
||||
|
||||
func (t *TermPane) Tab() *Tab {
|
||||
return t.tab
|
||||
}
|
||||
|
||||
func (t *TermPane) Close() {}
|
||||
|
||||
func (t *TermPane) Quit() {
|
||||
@@ -80,6 +95,10 @@ func (t *TermPane) HandleEvent(event tcell.Event) {
|
||||
} else if t.Status != shell.TTDone {
|
||||
t.WriteString(event.EscSeq())
|
||||
}
|
||||
} else if _, ok := event.(*tcell.EventPaste); ok {
|
||||
if t.Status != shell.TTDone {
|
||||
t.WriteString(event.EscSeq())
|
||||
}
|
||||
} else if e, ok := event.(*tcell.EventMouse); e != nil && (!ok || t.State.Mode(terminal.ModeMouseMask)) {
|
||||
// t.WriteString(event.EscSeq())
|
||||
} else if e != nil {
|
||||
|
||||
@@ -6,9 +6,8 @@ import (
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// A Completer is a function that takes a buffer and returns info
|
||||
@@ -54,7 +53,7 @@ func (b *Buffer) CycleAutocomplete(forward bool) {
|
||||
start := c.Loc
|
||||
end := c.Loc
|
||||
if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 {
|
||||
start = end.Move(-utf8.RuneCountInString(b.Completions[prevSuggestion]), b)
|
||||
start = end.Move(-util.CharacterCountInString(b.Completions[prevSuggestion]), b)
|
||||
} else {
|
||||
// end = start.Move(1, b)
|
||||
}
|
||||
@@ -82,7 +81,7 @@ func GetWord(b *Buffer) ([]byte, int) {
|
||||
|
||||
args := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
|
||||
input := args[len(args)-1]
|
||||
return input, c.X - utf8.RuneCount(input)
|
||||
return input, c.X - util.CharacterCount(input)
|
||||
}
|
||||
|
||||
// GetArg gets the most recent word (separated by ' ' only)
|
||||
@@ -98,7 +97,7 @@ func GetArg(b *Buffer) (string, int) {
|
||||
if i == len(args)-1 {
|
||||
break
|
||||
}
|
||||
argstart += utf8.RuneCount(a) + 1
|
||||
argstart += util.CharacterCount(a) + 1
|
||||
}
|
||||
|
||||
return input, argstart
|
||||
@@ -162,7 +161,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
|
||||
return []string{}, []string{}
|
||||
}
|
||||
|
||||
inputLen := utf8.RuneCount(input)
|
||||
inputLen := util.CharacterCount(input)
|
||||
|
||||
suggestionsSet := make(map[string]struct{})
|
||||
|
||||
@@ -171,7 +170,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
|
||||
l := b.LineBytes(i)
|
||||
words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
|
||||
for _, w := range words {
|
||||
if bytes.HasPrefix(w, input) && utf8.RuneCount(w) > inputLen {
|
||||
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
|
||||
strw := string(w)
|
||||
if _, ok := suggestionsSet[strw]; !ok {
|
||||
suggestionsSet[strw] = struct{}{}
|
||||
@@ -184,7 +183,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
|
||||
l := b.LineBytes(i)
|
||||
words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
|
||||
for _, w := range words {
|
||||
if bytes.HasPrefix(w, input) && utf8.RuneCount(w) > inputLen {
|
||||
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
|
||||
strw := string(w)
|
||||
if _, ok := suggestionsSet[strw]; !ok {
|
||||
suggestionsSet[strw] = struct{}{}
|
||||
|
||||
@@ -4,11 +4,12 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"golang.org/x/text/encoding"
|
||||
)
|
||||
|
||||
@@ -42,12 +43,12 @@ func (b *Buffer) Backup(checkTime bool) error {
|
||||
|
||||
b.lastbackup = time.Now()
|
||||
|
||||
backupdir := config.ConfigDir + "/backups/"
|
||||
backupdir := filepath.Join(config.ConfigDir, "backups")
|
||||
if _, err := os.Stat(backupdir); os.IsNotExist(err) {
|
||||
os.Mkdir(backupdir, os.ModePerm)
|
||||
}
|
||||
|
||||
name := backupdir + util.EscapePath(b.AbsPath)
|
||||
name := filepath.Join(backupdir, util.EscapePath(b.AbsPath))
|
||||
|
||||
err := overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
|
||||
if len(b.lines) == 0 {
|
||||
@@ -81,7 +82,7 @@ func (b *Buffer) RemoveBackup() {
|
||||
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
|
||||
return
|
||||
}
|
||||
f := config.ConfigDir + "/backups/" + util.EscapePath(b.AbsPath)
|
||||
f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
|
||||
os.Remove(f)
|
||||
}
|
||||
|
||||
@@ -89,7 +90,7 @@ func (b *Buffer) RemoveBackup() {
|
||||
// Returns true if a backup was applied
|
||||
func (b *Buffer) ApplyBackup(fsize int64) bool {
|
||||
if b.Settings["backup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
|
||||
backupfile := config.ConfigDir + "/backups/" + util.EscapePath(b.AbsPath)
|
||||
backupfile := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
|
||||
if info, err := os.Stat(backupfile); err == nil {
|
||||
backup, err := os.Open(backupfile)
|
||||
if err == nil {
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/pkg/highlight"
|
||||
dmp "github.com/sergi/go-diff/diffmatchpatch"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/micro/v2/pkg/highlight"
|
||||
"golang.org/x/text/encoding/htmlindex"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
@@ -56,6 +60,9 @@ var (
|
||||
BTRaw = BufType{4, false, true, false}
|
||||
// BTInfo is a buffer for inputting information
|
||||
BTInfo = BufType{5, false, true, false}
|
||||
// BTStdout is a buffer that only writes to stdout
|
||||
// when closed
|
||||
BTStdout = BufType{6, false, true, true}
|
||||
|
||||
// ErrFileTooLarge is returned when the file is too large to hash
|
||||
// (fastdirty is automatically enabled)
|
||||
@@ -71,23 +78,103 @@ type SharedBuffer struct {
|
||||
// Type of the buffer (e.g. help, raw, scratch etc..)
|
||||
Type BufType
|
||||
|
||||
// Path to the file on disk
|
||||
Path string
|
||||
// Absolute path to the file on disk
|
||||
AbsPath string
|
||||
// Name of the buffer on the status line
|
||||
name string
|
||||
|
||||
toStdout bool
|
||||
|
||||
// Settings customized by the user
|
||||
Settings map[string]interface{}
|
||||
|
||||
Suggestions []string
|
||||
Completions []string
|
||||
CurSuggestion int
|
||||
|
||||
Messages []*Message
|
||||
|
||||
updateDiffTimer *time.Timer
|
||||
diffBase []byte
|
||||
diffBaseLineCount int
|
||||
diffLock sync.RWMutex
|
||||
diff map[int]DiffStatus
|
||||
|
||||
// counts the number of edits
|
||||
// resets every backupTime edits
|
||||
lastbackup time.Time
|
||||
|
||||
// ReloadDisabled allows the user to disable reloads if they
|
||||
// are viewing a file that is constantly changing
|
||||
ReloadDisabled bool
|
||||
|
||||
isModified bool
|
||||
// Whether or not suggestions can be autocompleted must be shared because
|
||||
// it changes based on how the buffer has changed
|
||||
HasSuggestions bool
|
||||
|
||||
// The Highlighter struct actually performs the highlighting
|
||||
Highlighter *highlight.Highlighter
|
||||
// SyntaxDef represents the syntax highlighting definition being used
|
||||
// This stores the highlighting rules and filetype detection info
|
||||
SyntaxDef *highlight.Def
|
||||
|
||||
ModifiedThisFrame bool
|
||||
|
||||
// Hash of the original buffer -- empty if fastdirty is on
|
||||
origHash [md5.Size]byte
|
||||
}
|
||||
|
||||
func (b *SharedBuffer) insert(pos Loc, value []byte) {
|
||||
b.isModified = true
|
||||
b.HasSuggestions = false
|
||||
b.LineArray.insert(pos, value)
|
||||
|
||||
inslines := bytes.Count(value, []byte{'\n'})
|
||||
b.MarkModified(pos.Y, pos.Y+inslines)
|
||||
}
|
||||
func (b *SharedBuffer) remove(start, end Loc) []byte {
|
||||
b.isModified = true
|
||||
b.HasSuggestions = false
|
||||
defer b.MarkModified(start.Y, end.Y)
|
||||
return b.LineArray.remove(start, end)
|
||||
}
|
||||
|
||||
// MarkModified marks the buffer as modified for this frame
|
||||
// and performs rehighlighting if syntax highlighting is enabled
|
||||
func (b *SharedBuffer) MarkModified(start, end int) {
|
||||
b.ModifiedThisFrame = true
|
||||
|
||||
if !b.Settings["syntax"].(bool) || b.SyntaxDef == nil {
|
||||
return
|
||||
}
|
||||
|
||||
start = util.Clamp(start, 0, len(b.lines)-1)
|
||||
end = util.Clamp(end, 0, len(b.lines)-1)
|
||||
|
||||
l := -1
|
||||
for i := start; i <= end; i++ {
|
||||
l = util.Max(b.Highlighter.ReHighlightStates(b, i), l)
|
||||
}
|
||||
b.Highlighter.HighlightMatches(b, start, l)
|
||||
}
|
||||
|
||||
// DisableReload disables future reloads of this sharedbuffer
|
||||
func (b *SharedBuffer) DisableReload() {
|
||||
b.ReloadDisabled = true
|
||||
}
|
||||
|
||||
const (
|
||||
DSUnchanged = 0
|
||||
DSAdded = 1
|
||||
DSModified = 2
|
||||
DSDeletedAbove = 3
|
||||
)
|
||||
|
||||
type DiffStatus byte
|
||||
|
||||
// Buffer stores the main information about a currently open file including
|
||||
// the actual text (in a LineArray), the undo/redo stack (in an EventHandler)
|
||||
// all the cursors, the syntax highlighting info, the settings for the buffer
|
||||
@@ -102,46 +189,24 @@ type Buffer struct {
|
||||
cursors []*Cursor
|
||||
curCursor int
|
||||
StartCursor Loc
|
||||
|
||||
// Path to the file on disk
|
||||
Path string
|
||||
// Absolute path to the file on disk
|
||||
AbsPath string
|
||||
// Name of the buffer on the status line
|
||||
name string
|
||||
|
||||
// SyntaxDef represents the syntax highlighting definition being used
|
||||
// This stores the highlighting rules and filetype detection info
|
||||
SyntaxDef *highlight.Def
|
||||
// The Highlighter struct actually performs the highlighting
|
||||
Highlighter *highlight.Highlighter
|
||||
// Modifications is the list of modified regions for syntax highlighting
|
||||
Modifications []Loc
|
||||
|
||||
// Hash of the original buffer -- empty if fastdirty is on
|
||||
origHash [md5.Size]byte
|
||||
|
||||
// Settings customized by the user
|
||||
Settings map[string]interface{}
|
||||
|
||||
Suggestions []string
|
||||
Completions []string
|
||||
CurSuggestion int
|
||||
|
||||
Messages []*Message
|
||||
|
||||
// counts the number of edits
|
||||
// resets every backupTime edits
|
||||
lastbackup time.Time
|
||||
}
|
||||
|
||||
// NewBufferFromFile opens a new buffer using the given path
|
||||
// It will also automatically handle `~`, and line/column with filename:l:c
|
||||
// It will return an empty buffer if the path does not exist
|
||||
// and an error if the file is a directory
|
||||
func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
|
||||
// NewBufferFromFileAtLoc opens a new buffer with a given cursor location
|
||||
// If cursorLoc is {-1, -1} the location does not overwrite what the cursor location
|
||||
// would otherwise be (start of file, or saved cursor position if `savecursor` is
|
||||
// enabled)
|
||||
func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer, error) {
|
||||
var err error
|
||||
filename, cursorPos := util.GetPathAndCursorPosition(path)
|
||||
filename := path
|
||||
if config.GetGlobalOption("parsecursor").(bool) && cursorLoc.X == -1 && cursorLoc.Y == -1 {
|
||||
var cursorPos []string
|
||||
filename, cursorPos = util.GetPathAndCursorPosition(filename)
|
||||
cursorLoc, err = ParseCursorLocation(cursorPos)
|
||||
if err != nil {
|
||||
cursorLoc = Loc{-1, -1}
|
||||
}
|
||||
}
|
||||
|
||||
filename, err = util.ReplaceHome(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -156,11 +221,6 @@ func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
|
||||
|
||||
defer file.Close()
|
||||
|
||||
cursorLoc, cursorerr := ParseCursorLocation(cursorPos)
|
||||
if cursorerr != nil {
|
||||
cursorLoc = Loc{-1, -1}
|
||||
}
|
||||
|
||||
var buf *Buffer
|
||||
if err != nil {
|
||||
// File does not exist -- create an empty buffer with that name
|
||||
@@ -172,6 +232,19 @@ func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// NewBufferFromFile opens a new buffer using the given path
|
||||
// It will also automatically handle `~`, and line/column with filename:l:c
|
||||
// It will return an empty buffer if the path does not exist
|
||||
// and an error if the file is a directory
|
||||
func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
|
||||
return NewBufferFromFileAtLoc(path, btype, Loc{-1, -1})
|
||||
}
|
||||
|
||||
// NewBufferFromStringAtLoc creates a new buffer containing the given string with a cursor loc
|
||||
func NewBufferFromStringAtLoc(text, path string, btype BufType, cursorLoc Loc) *Buffer {
|
||||
return NewBuffer(strings.NewReader(text), int64(len(text)), path, cursorLoc, btype)
|
||||
}
|
||||
|
||||
// NewBufferFromString creates a new buffer containing the given string
|
||||
func NewBufferFromString(text, path string, btype BufType) *Buffer {
|
||||
return NewBuffer(strings.NewReader(text), int64(len(text)), path, Loc{-1, -1}, btype)
|
||||
@@ -187,22 +260,6 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
|
||||
b := new(Buffer)
|
||||
|
||||
b.Settings = config.DefaultCommonSettings()
|
||||
for k, v := range config.GlobalSettings {
|
||||
if _, ok := b.Settings[k]; ok {
|
||||
b.Settings[k] = v
|
||||
}
|
||||
}
|
||||
config.InitLocalSettings(b.Settings, path)
|
||||
|
||||
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
|
||||
if err != nil {
|
||||
enc = unicode.UTF8
|
||||
b.Settings["encoding"] = "utf-8"
|
||||
}
|
||||
|
||||
reader := transform.NewReader(r, enc.NewDecoder())
|
||||
|
||||
found := false
|
||||
if len(path) > 0 {
|
||||
for _, buf := range OpenBuffers {
|
||||
@@ -214,28 +271,58 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
}
|
||||
}
|
||||
|
||||
b.Path = path
|
||||
b.AbsPath = absPath
|
||||
|
||||
if !found {
|
||||
b.SharedBuffer = new(SharedBuffer)
|
||||
b.Type = btype
|
||||
|
||||
b.AbsPath = absPath
|
||||
b.Path = path
|
||||
|
||||
b.Settings = config.DefaultCommonSettings()
|
||||
for k, v := range config.GlobalSettings {
|
||||
if _, ok := config.DefaultGlobalOnlySettings[k]; !ok {
|
||||
// make sure setting is not global-only
|
||||
b.Settings[k] = v
|
||||
}
|
||||
}
|
||||
config.InitLocalSettings(b.Settings, path)
|
||||
|
||||
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
|
||||
if err != nil {
|
||||
enc = unicode.UTF8
|
||||
b.Settings["encoding"] = "utf-8"
|
||||
}
|
||||
|
||||
hasBackup := b.ApplyBackup(size)
|
||||
|
||||
if !hasBackup {
|
||||
b.LineArray = NewLineArray(uint64(size), FFAuto, reader)
|
||||
reader := bufio.NewReader(transform.NewReader(r, enc.NewDecoder()))
|
||||
|
||||
var ff FileFormat = FFAuto
|
||||
|
||||
if size == 0 {
|
||||
// for empty files, use the fileformat setting instead of
|
||||
// autodetection
|
||||
switch b.Settings["fileformat"] {
|
||||
case "unix":
|
||||
ff = FFUnix
|
||||
case "dos":
|
||||
ff = FFDos
|
||||
}
|
||||
}
|
||||
|
||||
b.LineArray = NewLineArray(uint64(size), ff, reader)
|
||||
}
|
||||
b.EventHandler = NewEventHandler(b.SharedBuffer, b.cursors)
|
||||
|
||||
// The last time this file was modified
|
||||
b.UpdateModTime()
|
||||
}
|
||||
|
||||
if b.Settings["readonly"].(bool) && b.Type == BTDefault {
|
||||
b.Type.Readonly = true
|
||||
}
|
||||
|
||||
// The last time this file was modified
|
||||
b.UpdateModTime()
|
||||
|
||||
switch b.Endings {
|
||||
case FFUnix:
|
||||
b.Settings["fileformat"] = "unix"
|
||||
@@ -244,10 +331,11 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
}
|
||||
|
||||
b.UpdateRules()
|
||||
// init local settings again now that we know the filetype
|
||||
config.InitLocalSettings(b.Settings, b.Path)
|
||||
|
||||
if _, err := os.Stat(config.ConfigDir + "/buffers/"); os.IsNotExist(err) {
|
||||
os.Mkdir(config.ConfigDir+"/buffers/", os.ModePerm)
|
||||
if _, err := os.Stat(filepath.Join(config.ConfigDir, "buffers")); os.IsNotExist(err) {
|
||||
os.Mkdir(filepath.Join(config.ConfigDir, "buffers"), os.ModePerm)
|
||||
}
|
||||
|
||||
if startcursor.X != -1 && startcursor.Y != -1 {
|
||||
@@ -264,7 +352,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
b.AddCursor(NewCursor(b, b.StartCursor))
|
||||
b.GetActiveCursor().Relocate()
|
||||
|
||||
if !b.Settings["fastdirty"].(bool) {
|
||||
if !b.Settings["fastdirty"].(bool) && !found {
|
||||
if size > LargeFileThreshold {
|
||||
// If the file is larger than LargeFileThreshold fastdirty needs to be on
|
||||
b.Settings["fastdirty"] = true
|
||||
@@ -273,13 +361,11 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
}
|
||||
}
|
||||
|
||||
err = config.RunPluginFn("onBufferOpen", luar.New(ulua.L, b))
|
||||
err := config.RunPluginFn("onBufferOpen", luar.New(ulua.L, b))
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
b.Modifications = make([]Loc, 0, 10)
|
||||
|
||||
OpenBuffers = append(OpenBuffers, b)
|
||||
|
||||
return b
|
||||
@@ -305,18 +391,26 @@ func (b *Buffer) Fini() {
|
||||
b.Serialize()
|
||||
}
|
||||
b.RemoveBackup()
|
||||
|
||||
if b.Type == BTStdout {
|
||||
fmt.Fprint(util.Stdout, string(b.Bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
// GetName returns the name that should be displayed in the statusline
|
||||
// for this buffer
|
||||
func (b *Buffer) GetName() string {
|
||||
if b.name == "" {
|
||||
name := b.name
|
||||
if name == "" {
|
||||
if b.Path == "" {
|
||||
return "No name"
|
||||
}
|
||||
return b.Path
|
||||
name = b.Path
|
||||
}
|
||||
return b.name
|
||||
if b.Settings["basename"].(bool) {
|
||||
return path.Base(name)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
//SetName changes the name for this buffer
|
||||
@@ -331,10 +425,6 @@ func (b *Buffer) Insert(start Loc, text string) {
|
||||
b.EventHandler.active = b.curCursor
|
||||
b.EventHandler.Insert(start, text)
|
||||
|
||||
// b.Modifications is cleared every screen redraw so it's
|
||||
// ok to append duplicates
|
||||
b.Modifications = append(b.Modifications, Loc{start.Y, start.Y + strings.Count(text, "\n")})
|
||||
|
||||
go b.Backup(true)
|
||||
}
|
||||
}
|
||||
@@ -346,22 +436,10 @@ func (b *Buffer) Remove(start, end Loc) {
|
||||
b.EventHandler.active = b.curCursor
|
||||
b.EventHandler.Remove(start, end)
|
||||
|
||||
b.Modifications = append(b.Modifications, Loc{start.Y, start.Y})
|
||||
|
||||
go b.Backup(true)
|
||||
}
|
||||
}
|
||||
|
||||
// ClearModifications clears the list of modified lines in this buffer
|
||||
// The list of modified lines is used for syntax highlighting so that
|
||||
// we can selectively highlight only the necessary lines
|
||||
// This function should be called every time this buffer is drawn to
|
||||
// the screen
|
||||
func (b *Buffer) ClearModifications() {
|
||||
// clear slice without resetting the cap
|
||||
b.Modifications = b.Modifications[:0]
|
||||
}
|
||||
|
||||
// FileType returns the buffer's filetype
|
||||
func (b *Buffer) FileType() string {
|
||||
return b.Settings["filetype"].(string)
|
||||
@@ -395,7 +473,7 @@ func (b *Buffer) ReOpen() error {
|
||||
return err
|
||||
}
|
||||
|
||||
reader := transform.NewReader(file, enc.NewDecoder())
|
||||
reader := bufio.NewReader(transform.NewReader(file, enc.NewDecoder()))
|
||||
data, err := ioutil.ReadAll(reader)
|
||||
txt := string(data)
|
||||
|
||||
@@ -405,6 +483,9 @@ func (b *Buffer) ReOpen() error {
|
||||
b.EventHandler.ApplyDiff(txt)
|
||||
|
||||
err = b.UpdateModTime()
|
||||
if !b.Settings["fastdirty"].(bool) {
|
||||
calcHash(b, &b.origHash)
|
||||
}
|
||||
b.isModified = false
|
||||
b.RelocateCursors()
|
||||
return err
|
||||
@@ -423,7 +504,7 @@ func (b *Buffer) RuneAt(loc Loc) rune {
|
||||
if len(line) > 0 {
|
||||
i := 0
|
||||
for len(line) > 0 {
|
||||
r, size := utf8.DecodeRune(line)
|
||||
r, _, size := util.DecodeCharacter(line)
|
||||
line = line[size:]
|
||||
i++
|
||||
|
||||
@@ -497,7 +578,37 @@ func (b *Buffer) UpdateRules() {
|
||||
return
|
||||
}
|
||||
syntaxFile := ""
|
||||
foundDef := false
|
||||
var header *highlight.Header
|
||||
// search for the syntax file in the user's custom syntax files
|
||||
for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
|
||||
data, err := f.Data()
|
||||
if err != nil {
|
||||
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
header, err = highlight.MakeHeaderYaml(data)
|
||||
file, err := highlight.ParseFile(data)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if ((ft == "unknown" || ft == "") && highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data)) || header.FileType == ft {
|
||||
syndef, err := highlight.ParseDef(file, header)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
b.SyntaxDef = syndef
|
||||
syntaxFile = f.Name()
|
||||
foundDef = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// search in the default syntax files
|
||||
for _, f := range config.ListRuntimeFiles(config.RTSyntaxHeader) {
|
||||
data, err := f.Data()
|
||||
if err != nil {
|
||||
@@ -522,33 +633,8 @@ func (b *Buffer) UpdateRules() {
|
||||
}
|
||||
}
|
||||
|
||||
if syntaxFile == "" {
|
||||
// search for the syntax file in the user's custom syntax files
|
||||
for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
|
||||
data, err := f.Data()
|
||||
if err != nil {
|
||||
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
header, err = highlight.MakeHeaderYaml(data)
|
||||
file, err := highlight.ParseFile(data)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if (ft == "unknown" || ft == "" && highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data)) || header.FileType == ft {
|
||||
syndef, err := highlight.ParseDef(file, header)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
b.SyntaxDef = syndef
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if syntaxFile != "" && !foundDef {
|
||||
// we found a syntax file using a syntax header file
|
||||
for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
|
||||
if f.Name() == syntaxFile {
|
||||
data, err := f.Data()
|
||||
@@ -610,7 +696,9 @@ func (b *Buffer) UpdateRules() {
|
||||
}
|
||||
|
||||
if b.Highlighter == nil || syntaxFile != "" {
|
||||
b.Settings["filetype"] = b.SyntaxDef.FileType
|
||||
if b.SyntaxDef != nil {
|
||||
b.Settings["filetype"] = b.SyntaxDef.FileType
|
||||
}
|
||||
} else {
|
||||
b.SyntaxDef = &highlight.EmptyDef
|
||||
}
|
||||
@@ -618,8 +706,11 @@ func (b *Buffer) UpdateRules() {
|
||||
if b.SyntaxDef != nil {
|
||||
b.Highlighter = highlight.NewHighlighter(b.SyntaxDef)
|
||||
if b.Settings["syntax"].(bool) {
|
||||
b.Highlighter.HighlightStates(b)
|
||||
b.Highlighter.HighlightMatches(b, 0, b.End().Y)
|
||||
go func() {
|
||||
b.Highlighter.HighlightStates(b)
|
||||
b.Highlighter.HighlightMatches(b, 0, b.End().Y)
|
||||
screen.Redraw()
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -748,7 +839,7 @@ func (b *Buffer) MoveLinesUp(start int, end int) {
|
||||
if end == len(b.lines) {
|
||||
b.Insert(
|
||||
Loc{
|
||||
utf8.RuneCount(b.lines[end-1].data),
|
||||
util.CharacterCount(b.lines[end-1].data),
|
||||
end - 1,
|
||||
},
|
||||
"\n"+l,
|
||||
@@ -795,7 +886,7 @@ var BracePairs = [][2]rune{
|
||||
// returns the location of the matching brace
|
||||
// if the boolean returned is true then the original matching brace is one character left
|
||||
// of the starting location
|
||||
func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool) {
|
||||
func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool, bool) {
|
||||
curLine := []rune(string(b.LineBytes(start.Y)))
|
||||
startChar := ' '
|
||||
if start.X >= 0 && start.X < len(curLine) {
|
||||
@@ -825,9 +916,9 @@ func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool) {
|
||||
i--
|
||||
if i == 0 {
|
||||
if startChar == braceType[0] {
|
||||
return Loc{x, y}, false
|
||||
return Loc{x, y}, false, true
|
||||
}
|
||||
return Loc{x, y}, true
|
||||
return Loc{x, y}, true, true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -849,9 +940,9 @@ func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool) {
|
||||
i--
|
||||
if i == 0 {
|
||||
if leftChar == braceType[1] {
|
||||
return Loc{x, y}, true
|
||||
return Loc{x, y}, true, true
|
||||
}
|
||||
return Loc{x, y}, false
|
||||
return Loc{x, y}, false, true
|
||||
}
|
||||
} else if r == braceType[1] {
|
||||
i++
|
||||
@@ -859,7 +950,7 @@ func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return start, true
|
||||
return start, true, false
|
||||
}
|
||||
|
||||
// Retab changes all tabs to spaces or vice versa
|
||||
@@ -882,6 +973,7 @@ func (b *Buffer) Retab() {
|
||||
|
||||
l = bytes.TrimLeft(l, " \t")
|
||||
b.lines[i].data = append(ws, l...)
|
||||
b.MarkModified(i, i)
|
||||
dirty = true
|
||||
}
|
||||
|
||||
@@ -900,12 +992,12 @@ func ParseCursorLocation(cursorPositions []string) (Loc, error) {
|
||||
}
|
||||
|
||||
startpos.Y, err = strconv.Atoi(cursorPositions[0])
|
||||
startpos.Y -= 1
|
||||
startpos.Y--
|
||||
if err == nil {
|
||||
if len(cursorPositions) > 1 {
|
||||
startpos.X, err = strconv.Atoi(cursorPositions[1])
|
||||
if startpos.X > 0 {
|
||||
startpos.X -= 1
|
||||
startpos.X--
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -918,6 +1010,106 @@ func (b *Buffer) Line(i int) string {
|
||||
return string(b.LineBytes(i))
|
||||
}
|
||||
|
||||
func (b *Buffer) Write(bytes []byte) (n int, err error) {
|
||||
b.EventHandler.InsertBytes(b.End(), bytes)
|
||||
return len(bytes), nil
|
||||
}
|
||||
|
||||
func (b *Buffer) updateDiffSync() {
|
||||
b.diffLock.Lock()
|
||||
defer b.diffLock.Unlock()
|
||||
|
||||
b.diff = make(map[int]DiffStatus)
|
||||
|
||||
if b.diffBase == nil {
|
||||
return
|
||||
}
|
||||
|
||||
differ := dmp.New()
|
||||
baseRunes, bufferRunes, _ := differ.DiffLinesToRunes(string(b.diffBase), string(b.Bytes()))
|
||||
diffs := differ.DiffMainRunes(baseRunes, bufferRunes, false)
|
||||
lineN := 0
|
||||
|
||||
for _, diff := range diffs {
|
||||
lineCount := len([]rune(diff.Text))
|
||||
|
||||
switch diff.Type {
|
||||
case dmp.DiffEqual:
|
||||
lineN += lineCount
|
||||
case dmp.DiffInsert:
|
||||
var status DiffStatus
|
||||
if b.diff[lineN] == DSDeletedAbove {
|
||||
status = DSModified
|
||||
} else {
|
||||
status = DSAdded
|
||||
}
|
||||
for i := 0; i < lineCount; i++ {
|
||||
b.diff[lineN] = status
|
||||
lineN++
|
||||
}
|
||||
case dmp.DiffDelete:
|
||||
b.diff[lineN] = DSDeletedAbove
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateDiff computes the diff between the diff base and the buffer content.
|
||||
// The update may be performed synchronously or asynchronously.
|
||||
// UpdateDiff calls the supplied callback when the update is complete.
|
||||
// The argument passed to the callback is set to true if and only if
|
||||
// the update was performed synchronously.
|
||||
// If an asynchronous update is already pending when UpdateDiff is called,
|
||||
// UpdateDiff does not schedule another update, in which case the callback
|
||||
// is not called.
|
||||
func (b *Buffer) UpdateDiff(callback func(bool)) {
|
||||
if b.updateDiffTimer != nil {
|
||||
return
|
||||
}
|
||||
|
||||
lineCount := b.LinesNum()
|
||||
if b.diffBaseLineCount > lineCount {
|
||||
lineCount = b.diffBaseLineCount
|
||||
}
|
||||
|
||||
if lineCount < 1000 {
|
||||
b.updateDiffSync()
|
||||
callback(true)
|
||||
} else if lineCount < 30000 {
|
||||
b.updateDiffTimer = time.AfterFunc(500*time.Millisecond, func() {
|
||||
b.updateDiffTimer = nil
|
||||
b.updateDiffSync()
|
||||
callback(false)
|
||||
})
|
||||
} else {
|
||||
// Don't compute diffs for very large files
|
||||
b.diffLock.Lock()
|
||||
b.diff = make(map[int]DiffStatus)
|
||||
b.diffLock.Unlock()
|
||||
callback(true)
|
||||
}
|
||||
}
|
||||
|
||||
// SetDiffBase sets the text that is used as the base for diffing the buffer content
|
||||
func (b *Buffer) SetDiffBase(diffBase []byte) {
|
||||
b.diffBase = diffBase
|
||||
if diffBase == nil {
|
||||
b.diffBaseLineCount = 0
|
||||
} else {
|
||||
b.diffBaseLineCount = strings.Count(string(diffBase), "\n")
|
||||
}
|
||||
b.UpdateDiff(func(synchronous bool) {
|
||||
screen.Redraw()
|
||||
})
|
||||
}
|
||||
|
||||
// DiffStatus returns the diff status for a line in the buffer
|
||||
func (b *Buffer) DiffStatus(lineN int) DiffStatus {
|
||||
b.diffLock.RLock()
|
||||
defer b.diffLock.RUnlock()
|
||||
// Note that the zero value for DiffStatus is equal to DSUnchanged
|
||||
return b.diff[lineN]
|
||||
}
|
||||
|
||||
// WriteLog writes a string to the log buffer
|
||||
func WriteLog(s string) {
|
||||
LogBuf.EventHandler.Insert(LogBuf.End(), s)
|
||||
|
||||
1827
internal/buffer/buffer_generated_test.go
Normal file
1827
internal/buffer/buffer_generated_test.go
Normal file
File diff suppressed because it is too large
Load Diff
322
internal/buffer/buffer_test.go
Normal file
322
internal/buffer/buffer_test.go
Normal file
@@ -0,0 +1,322 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
type operation struct {
|
||||
start Loc
|
||||
end Loc
|
||||
text []string
|
||||
}
|
||||
|
||||
func init() {
|
||||
ulua.L = lua.NewState()
|
||||
config.InitGlobalSettings()
|
||||
config.GlobalSettings["backup"] = false
|
||||
config.GlobalSettings["fastdirty"] = true
|
||||
}
|
||||
|
||||
func check(t *testing.T, before []string, operations []operation, after []string) {
|
||||
assert := assert.New(t)
|
||||
|
||||
b := NewBufferFromString(strings.Join(before, "\n"), "", BTDefault)
|
||||
|
||||
assert.NotEqual("", b.GetName())
|
||||
assert.Equal(false, b.ExternallyModified())
|
||||
assert.Equal(false, b.Modified())
|
||||
assert.Equal(1, b.NumCursors())
|
||||
|
||||
checkText := func(lines []string) {
|
||||
assert.Equal([]byte(strings.Join(lines, "\n")), b.Bytes())
|
||||
assert.Equal(len(lines), b.LinesNum())
|
||||
for i, s := range lines {
|
||||
assert.Equal(s, b.Line(i))
|
||||
assert.Equal([]byte(s), b.LineBytes(i))
|
||||
}
|
||||
}
|
||||
|
||||
checkText(before)
|
||||
|
||||
var cursors []*Cursor
|
||||
|
||||
for _, op := range operations {
|
||||
cursor := NewCursor(b, op.start)
|
||||
cursor.SetSelectionStart(op.start)
|
||||
cursor.SetSelectionEnd(op.end)
|
||||
b.AddCursor(cursor)
|
||||
cursors = append(cursors, cursor)
|
||||
}
|
||||
|
||||
assert.Equal(1+len(operations), b.NumCursors())
|
||||
|
||||
for i, op := range operations {
|
||||
cursor := cursors[i]
|
||||
b.SetCurCursor(cursor.Num)
|
||||
cursor.DeleteSelection()
|
||||
b.Insert(cursor.Loc, strings.Join(op.text, "\n"))
|
||||
}
|
||||
|
||||
checkText(after)
|
||||
|
||||
// must have exactly two events per operation (delete and insert)
|
||||
for range operations {
|
||||
b.UndoOneEvent()
|
||||
b.UndoOneEvent()
|
||||
}
|
||||
|
||||
checkText(before)
|
||||
|
||||
for i, op := range operations {
|
||||
cursor := cursors[i]
|
||||
if op.start == op.end {
|
||||
assert.Equal(op.start, cursor.Loc)
|
||||
} else {
|
||||
assert.Equal(op.start, cursor.CurSelection[0])
|
||||
assert.Equal(op.end, cursor.CurSelection[1])
|
||||
}
|
||||
}
|
||||
|
||||
for range operations {
|
||||
b.RedoOneEvent()
|
||||
b.RedoOneEvent()
|
||||
}
|
||||
|
||||
checkText(after)
|
||||
|
||||
b.Close()
|
||||
}
|
||||
|
||||
const maxLineLength = 200
|
||||
|
||||
var alphabet = []rune(" abcdeäم📚")
|
||||
|
||||
func randomString(length int) string {
|
||||
runes := make([]rune, length)
|
||||
for i := range runes {
|
||||
runes[i] = alphabet[rand.Intn(len(alphabet))]
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
func randomText(nLines int) string {
|
||||
lines := make([]string, nLines)
|
||||
for i := range lines {
|
||||
lines[i] = randomString(rand.Intn(maxLineLength + 1))
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func benchCreateAndClose(testingB *testing.B, nLines int) {
|
||||
rand.Seed(int64(nLines))
|
||||
|
||||
text := randomText(nLines)
|
||||
|
||||
testingB.ResetTimer()
|
||||
|
||||
for i := 0; i < testingB.N; i++ {
|
||||
b := NewBufferFromString(text, "", BTDefault)
|
||||
b.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func benchRead(testingB *testing.B, nLines int) {
|
||||
rand.Seed(int64(nLines))
|
||||
|
||||
b := NewBufferFromString(randomText(nLines), "", BTDefault)
|
||||
|
||||
testingB.ResetTimer()
|
||||
|
||||
for i := 0; i < testingB.N; i++ {
|
||||
b.Bytes()
|
||||
for j := 0; j < b.LinesNum(); j++ {
|
||||
b.Line(j)
|
||||
b.LineBytes(j)
|
||||
}
|
||||
}
|
||||
|
||||
testingB.StopTimer()
|
||||
|
||||
b.Close()
|
||||
}
|
||||
|
||||
func benchEdit(testingB *testing.B, nLines, nCursors int) {
|
||||
rand.Seed(int64(nLines + nCursors))
|
||||
|
||||
b := NewBufferFromString(randomText(nLines), "", BTDefault)
|
||||
|
||||
regionSize := nLines / nCursors
|
||||
|
||||
operations := make([]operation, nCursors)
|
||||
for i := range operations {
|
||||
startLine := (i * regionSize) + rand.Intn(regionSize-5)
|
||||
startColumn := rand.Intn(util.CharacterCountInString(b.Line(startLine)) + 1)
|
||||
endLine := startLine + 1 + rand.Intn(5)
|
||||
endColumn := rand.Intn(util.CharacterCountInString(b.Line(endLine)) + 1)
|
||||
|
||||
operations[i] = operation{
|
||||
start: Loc{startColumn, startLine},
|
||||
end: Loc{endColumn, endLine},
|
||||
text: []string{randomText(2 + rand.Intn(4))},
|
||||
}
|
||||
}
|
||||
|
||||
testingB.ResetTimer()
|
||||
|
||||
for i := 0; i < testingB.N; i++ {
|
||||
b.SetCursors([]*Cursor{})
|
||||
|
||||
var cursors []*Cursor
|
||||
|
||||
for _, op := range operations {
|
||||
cursor := NewCursor(b, op.start)
|
||||
cursor.SetSelectionStart(op.start)
|
||||
cursor.SetSelectionEnd(op.end)
|
||||
b.AddCursor(cursor)
|
||||
cursors = append(cursors, cursor)
|
||||
}
|
||||
|
||||
for j, op := range operations {
|
||||
cursor := cursors[j]
|
||||
b.SetCurCursor(cursor.Num)
|
||||
cursor.DeleteSelection()
|
||||
b.Insert(cursor.Loc, op.text[0])
|
||||
}
|
||||
|
||||
for b.UndoStack.Peek() != nil {
|
||||
b.UndoOneEvent()
|
||||
}
|
||||
}
|
||||
|
||||
testingB.StopTimer()
|
||||
|
||||
b.Close()
|
||||
}
|
||||
|
||||
func BenchmarkCreateAndClose10Lines(b *testing.B) {
|
||||
benchCreateAndClose(b, 10)
|
||||
}
|
||||
|
||||
func BenchmarkCreateAndClose100Lines(b *testing.B) {
|
||||
benchCreateAndClose(b, 100)
|
||||
}
|
||||
|
||||
func BenchmarkCreateAndClose1000Lines(b *testing.B) {
|
||||
benchCreateAndClose(b, 1000)
|
||||
}
|
||||
|
||||
func BenchmarkCreateAndClose10000Lines(b *testing.B) {
|
||||
benchCreateAndClose(b, 10000)
|
||||
}
|
||||
|
||||
func BenchmarkCreateAndClose100000Lines(b *testing.B) {
|
||||
benchCreateAndClose(b, 100000)
|
||||
}
|
||||
|
||||
func BenchmarkCreateAndClose1000000Lines(b *testing.B) {
|
||||
benchCreateAndClose(b, 1000000)
|
||||
}
|
||||
|
||||
func BenchmarkRead10Lines(b *testing.B) {
|
||||
benchRead(b, 10)
|
||||
}
|
||||
|
||||
func BenchmarkRead100Lines(b *testing.B) {
|
||||
benchRead(b, 100)
|
||||
}
|
||||
|
||||
func BenchmarkRead1000Lines(b *testing.B) {
|
||||
benchRead(b, 1000)
|
||||
}
|
||||
|
||||
func BenchmarkRead10000Lines(b *testing.B) {
|
||||
benchRead(b, 10000)
|
||||
}
|
||||
|
||||
func BenchmarkRead100000Lines(b *testing.B) {
|
||||
benchRead(b, 100000)
|
||||
}
|
||||
|
||||
func BenchmarkRead1000000Lines(b *testing.B) {
|
||||
benchRead(b, 1000000)
|
||||
}
|
||||
|
||||
func BenchmarkEdit10Lines1Cursor(b *testing.B) {
|
||||
benchEdit(b, 10, 1)
|
||||
}
|
||||
|
||||
func BenchmarkEdit100Lines1Cursor(b *testing.B) {
|
||||
benchEdit(b, 100, 1)
|
||||
}
|
||||
|
||||
func BenchmarkEdit100Lines10Cursors(b *testing.B) {
|
||||
benchEdit(b, 100, 10)
|
||||
}
|
||||
|
||||
func BenchmarkEdit1000Lines1Cursor(b *testing.B) {
|
||||
benchEdit(b, 1000, 1)
|
||||
}
|
||||
|
||||
func BenchmarkEdit1000Lines10Cursors(b *testing.B) {
|
||||
benchEdit(b, 1000, 10)
|
||||
}
|
||||
|
||||
func BenchmarkEdit1000Lines100Cursors(b *testing.B) {
|
||||
benchEdit(b, 1000, 100)
|
||||
}
|
||||
|
||||
func BenchmarkEdit10000Lines1Cursor(b *testing.B) {
|
||||
benchEdit(b, 10000, 1)
|
||||
}
|
||||
|
||||
func BenchmarkEdit10000Lines10Cursors(b *testing.B) {
|
||||
benchEdit(b, 10000, 10)
|
||||
}
|
||||
|
||||
func BenchmarkEdit10000Lines100Cursors(b *testing.B) {
|
||||
benchEdit(b, 10000, 100)
|
||||
}
|
||||
|
||||
func BenchmarkEdit10000Lines1000Cursors(b *testing.B) {
|
||||
benchEdit(b, 10000, 1000)
|
||||
}
|
||||
|
||||
func BenchmarkEdit100000Lines1Cursor(b *testing.B) {
|
||||
benchEdit(b, 100000, 1)
|
||||
}
|
||||
|
||||
func BenchmarkEdit100000Lines10Cursors(b *testing.B) {
|
||||
benchEdit(b, 100000, 10)
|
||||
}
|
||||
|
||||
func BenchmarkEdit100000Lines100Cursors(b *testing.B) {
|
||||
benchEdit(b, 100000, 100)
|
||||
}
|
||||
|
||||
func BenchmarkEdit100000Lines1000Cursors(b *testing.B) {
|
||||
benchEdit(b, 100000, 1000)
|
||||
}
|
||||
|
||||
func BenchmarkEdit1000000Lines1Cursor(b *testing.B) {
|
||||
benchEdit(b, 1000000, 1)
|
||||
}
|
||||
|
||||
func BenchmarkEdit1000000Lines10Cursors(b *testing.B) {
|
||||
benchEdit(b, 1000000, 10)
|
||||
}
|
||||
|
||||
func BenchmarkEdit1000000Lines100Cursors(b *testing.B) {
|
||||
benchEdit(b, 1000000, 100)
|
||||
}
|
||||
|
||||
func BenchmarkEdit1000000Lines1000Cursors(b *testing.B) {
|
||||
benchEdit(b, 1000000, 1000)
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/clipboard"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// InBounds returns whether the given location is a valid character position in the given buffer
|
||||
func InBounds(pos Loc, buf *Buffer) bool {
|
||||
if pos.Y < 0 || pos.Y >= len(buf.lines) || pos.X < 0 || pos.X > utf8.RuneCount(buf.LineBytes(pos.Y)) {
|
||||
if pos.Y < 0 || pos.Y >= len(buf.lines) || pos.X < 0 || pos.X > util.CharacterCount(buf.LineBytes(pos.Y)) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -76,9 +74,6 @@ func (c *Cursor) GetVisualX() int {
|
||||
|
||||
bytes := c.buf.LineBytes(c.Y)
|
||||
tabsize := int(c.buf.Settings["tabsize"].(float64))
|
||||
if c.X > utf8.RuneCount(bytes) {
|
||||
c.X = utf8.RuneCount(bytes) - 1
|
||||
}
|
||||
|
||||
return util.StringWidth(bytes, c.X, tabsize)
|
||||
}
|
||||
@@ -102,16 +97,29 @@ func (c *Cursor) Start() {
|
||||
func (c *Cursor) StartOfText() {
|
||||
c.Start()
|
||||
for util.IsWhitespace(c.RuneUnder(c.X)) {
|
||||
if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
|
||||
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
||||
break
|
||||
}
|
||||
c.Right()
|
||||
}
|
||||
}
|
||||
|
||||
// IsStartOfText returns whether the cursor is at the first
|
||||
// non-whitespace rune of the line it is on
|
||||
func (c *Cursor) IsStartOfText() bool {
|
||||
x := 0
|
||||
for util.IsWhitespace(c.RuneUnder(x)) {
|
||||
if x == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
||||
break
|
||||
}
|
||||
x++
|
||||
}
|
||||
return c.X == x
|
||||
}
|
||||
|
||||
// End moves the cursor to the end of the line it is on
|
||||
func (c *Cursor) End() {
|
||||
c.X = utf8.RuneCount(c.buf.LineBytes(c.Y))
|
||||
c.X = util.CharacterCount(c.buf.LineBytes(c.Y))
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
|
||||
@@ -229,8 +237,14 @@ func (c *Cursor) UpN(amount int) {
|
||||
bytes := c.buf.LineBytes(proposedY)
|
||||
c.X = c.GetCharPosInLine(bytes, c.LastVisualX)
|
||||
|
||||
if c.X > utf8.RuneCount(bytes) || (amount < 0 && proposedY == c.Y) {
|
||||
c.X = utf8.RuneCount(bytes)
|
||||
if c.X > util.CharacterCount(bytes) || (amount < 0 && proposedY == c.Y) {
|
||||
c.X = util.CharacterCount(bytes)
|
||||
c.StoreVisualX()
|
||||
}
|
||||
|
||||
if c.X < 0 || (amount > 0 && proposedY == c.Y) {
|
||||
c.X = 0
|
||||
c.StoreVisualX()
|
||||
}
|
||||
|
||||
c.Y = proposedY
|
||||
@@ -272,7 +286,7 @@ func (c *Cursor) Right() {
|
||||
if c.Loc == c.buf.End() {
|
||||
return
|
||||
}
|
||||
if c.X < utf8.RuneCount(c.buf.LineBytes(c.Y)) {
|
||||
if c.X < util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
||||
c.X++
|
||||
} else {
|
||||
c.Down()
|
||||
@@ -293,8 +307,8 @@ func (c *Cursor) Relocate() {
|
||||
|
||||
if c.X < 0 {
|
||||
c.X = 0
|
||||
} else if c.X > utf8.RuneCount(c.buf.LineBytes(c.Y)) {
|
||||
c.X = utf8.RuneCount(c.buf.LineBytes(c.Y))
|
||||
} else if c.X > util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
||||
c.X = util.CharacterCount(c.buf.LineBytes(c.Y))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +334,7 @@ func (c *Cursor) SelectWord() {
|
||||
c.SetSelectionStart(Loc{backward, c.Y})
|
||||
c.OrigSelection[0] = c.CurSelection[0]
|
||||
|
||||
lineLen := utf8.RuneCount(c.buf.LineBytes(c.Y)) - 1
|
||||
lineLen := util.CharacterCount(c.buf.LineBytes(c.Y)) - 1
|
||||
for forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) {
|
||||
forward++
|
||||
}
|
||||
@@ -352,7 +366,7 @@ func (c *Cursor) AddWordToSelection() {
|
||||
if c.Loc.GreaterThan(c.OrigSelection[1]) {
|
||||
forward := c.X
|
||||
|
||||
lineLen := utf8.RuneCount(c.buf.LineBytes(c.Y)) - 1
|
||||
lineLen := util.CharacterCount(c.buf.LineBytes(c.Y)) - 1
|
||||
for forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) {
|
||||
forward++
|
||||
}
|
||||
@@ -379,7 +393,7 @@ func (c *Cursor) SelectTo(loc Loc) {
|
||||
// WordRight moves the cursor one word to the right
|
||||
func (c *Cursor) WordRight() {
|
||||
for util.IsWhitespace(c.RuneUnder(c.X)) {
|
||||
if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
|
||||
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
||||
c.Right()
|
||||
return
|
||||
}
|
||||
@@ -387,7 +401,7 @@ func (c *Cursor) WordRight() {
|
||||
}
|
||||
c.Right()
|
||||
for util.IsWordChar(c.RuneUnder(c.X)) {
|
||||
if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
|
||||
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
|
||||
return
|
||||
}
|
||||
c.Right()
|
||||
@@ -416,14 +430,14 @@ func (c *Cursor) WordLeft() {
|
||||
// RuneUnder returns the rune under the given x position
|
||||
func (c *Cursor) RuneUnder(x int) rune {
|
||||
line := c.buf.LineBytes(c.Y)
|
||||
if len(line) == 0 || x >= utf8.RuneCount(line) {
|
||||
if len(line) == 0 || x >= util.CharacterCount(line) {
|
||||
return '\n'
|
||||
} else if x < 0 {
|
||||
x = 0
|
||||
}
|
||||
i := 0
|
||||
for len(line) > 0 {
|
||||
r, size := utf8.DecodeRune(line)
|
||||
r, _, size := util.DecodeCharacter(line)
|
||||
line = line[size:]
|
||||
|
||||
if i == x {
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
dmp "github.com/sergi/go-diff/diffmatchpatch"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
luar "layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -17,7 +22,7 @@ const (
|
||||
// TextEventReplace represents a replace event
|
||||
TextEventReplace = 0
|
||||
|
||||
undoThreshold = 500 // If two events are less than n milliseconds apart, undo both of them
|
||||
undoThreshold = 1000 // If two events are less than n milliseconds apart, undo both of them
|
||||
)
|
||||
|
||||
// TextEvent holds data for a manipulation on some text that can be undone
|
||||
@@ -36,6 +41,73 @@ type Delta struct {
|
||||
End Loc
|
||||
}
|
||||
|
||||
// DoTextEvent runs a text event
|
||||
func (eh *EventHandler) DoTextEvent(t *TextEvent, useUndo bool) {
|
||||
oldl := eh.buf.LinesNum()
|
||||
|
||||
if useUndo {
|
||||
eh.Execute(t)
|
||||
} else {
|
||||
ExecuteTextEvent(t, eh.buf)
|
||||
}
|
||||
|
||||
if len(t.Deltas) != 1 {
|
||||
return
|
||||
}
|
||||
|
||||
text := t.Deltas[0].Text
|
||||
start := t.Deltas[0].Start
|
||||
lastnl := -1
|
||||
var endX int
|
||||
var textX int
|
||||
if t.EventType == TextEventInsert {
|
||||
linecount := eh.buf.LinesNum() - oldl
|
||||
textcount := util.CharacterCount(text)
|
||||
lastnl = bytes.LastIndex(text, []byte{'\n'})
|
||||
if lastnl >= 0 {
|
||||
endX = util.CharacterCount(text[lastnl+1:])
|
||||
textX = endX
|
||||
} else {
|
||||
endX = start.X + textcount
|
||||
textX = textcount
|
||||
}
|
||||
t.Deltas[0].End = clamp(Loc{endX, start.Y + linecount}, eh.buf.LineArray)
|
||||
}
|
||||
end := t.Deltas[0].End
|
||||
|
||||
for _, c := range eh.cursors {
|
||||
move := func(loc Loc) Loc {
|
||||
if t.EventType == TextEventInsert {
|
||||
if start.Y != loc.Y && loc.GreaterThan(start) {
|
||||
loc.Y += end.Y - start.Y
|
||||
} else if loc.Y == start.Y && loc.GreaterEqual(start) {
|
||||
loc.Y += end.Y - start.Y
|
||||
if lastnl >= 0 {
|
||||
loc.X += textX - start.X
|
||||
} else {
|
||||
loc.X += textX
|
||||
}
|
||||
}
|
||||
return loc
|
||||
} else {
|
||||
if loc.Y != end.Y && loc.GreaterThan(end) {
|
||||
loc.Y -= end.Y - start.Y
|
||||
} else if loc.Y == end.Y && loc.GreaterEqual(end) {
|
||||
loc = loc.MoveLA(-DiffLA(start, end, eh.buf.LineArray), eh.buf.LineArray)
|
||||
}
|
||||
return loc
|
||||
}
|
||||
}
|
||||
c.Loc = move(c.Loc)
|
||||
c.CurSelection[0] = move(c.CurSelection[0])
|
||||
c.CurSelection[1] = move(c.CurSelection[1])
|
||||
c.OrigSelection[0] = move(c.OrigSelection[0])
|
||||
c.OrigSelection[1] = move(c.OrigSelection[1])
|
||||
c.Relocate()
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuteTextEvent runs a text event
|
||||
func ExecuteTextEvent(t *TextEvent, buf *SharedBuffer) {
|
||||
if t.EventType == TextEventInsert {
|
||||
@@ -51,7 +123,7 @@ func ExecuteTextEvent(t *TextEvent, buf *SharedBuffer) {
|
||||
t.Deltas[i].Text = buf.remove(d.Start, d.End)
|
||||
buf.insert(d.Start, d.Text)
|
||||
t.Deltas[i].Start = d.Start
|
||||
t.Deltas[i].End = Loc{d.Start.X + utf8.RuneCount(d.Text), d.Start.Y}
|
||||
t.Deltas[i].End = Loc{d.Start.X + util.CharacterCount(d.Text), d.Start.Y}
|
||||
}
|
||||
for i, j := 0, len(t.Deltas)-1; i < j; i, j = i+1, j-1 {
|
||||
t.Deltas[i], t.Deltas[j] = t.Deltas[j], t.Deltas[i]
|
||||
@@ -60,9 +132,9 @@ func ExecuteTextEvent(t *TextEvent, buf *SharedBuffer) {
|
||||
}
|
||||
|
||||
// UndoTextEvent undoes a text event
|
||||
func UndoTextEvent(t *TextEvent, buf *SharedBuffer) {
|
||||
func (eh *EventHandler) UndoTextEvent(t *TextEvent) {
|
||||
t.EventType = -t.EventType
|
||||
ExecuteTextEvent(t, buf)
|
||||
eh.DoTextEvent(t, false)
|
||||
}
|
||||
|
||||
// EventHandler executes text manipulations and allows undoing and redoing
|
||||
@@ -94,12 +166,12 @@ func (eh *EventHandler) ApplyDiff(new string) {
|
||||
loc := eh.buf.Start()
|
||||
for _, d := range diff {
|
||||
if d.Type == dmp.DiffDelete {
|
||||
eh.Remove(loc, loc.MoveLA(utf8.RuneCountInString(d.Text), eh.buf.LineArray))
|
||||
eh.Remove(loc, loc.MoveLA(util.CharacterCountInString(d.Text), eh.buf.LineArray))
|
||||
} else {
|
||||
if d.Type == dmp.DiffInsert {
|
||||
eh.Insert(loc, d.Text)
|
||||
}
|
||||
loc = loc.MoveLA(utf8.RuneCountInString(d.Text), eh.buf.LineArray)
|
||||
loc = loc.MoveLA(util.CharacterCountInString(d.Text), eh.buf.LineArray)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,60 +179,38 @@ func (eh *EventHandler) ApplyDiff(new string) {
|
||||
// Insert creates an insert text event and executes it
|
||||
func (eh *EventHandler) Insert(start Loc, textStr string) {
|
||||
text := []byte(textStr)
|
||||
eh.InsertBytes(start, text)
|
||||
}
|
||||
|
||||
// InsertBytes creates an insert text event and executes it
|
||||
func (eh *EventHandler) InsertBytes(start Loc, text []byte) {
|
||||
if len(text) == 0 {
|
||||
return
|
||||
}
|
||||
start = clamp(start, eh.buf.LineArray)
|
||||
e := &TextEvent{
|
||||
C: *eh.cursors[eh.active],
|
||||
EventType: TextEventInsert,
|
||||
Deltas: []Delta{{text, start, Loc{0, 0}}},
|
||||
Time: time.Now(),
|
||||
}
|
||||
eh.Execute(e)
|
||||
e.Deltas[0].End = start.MoveLA(utf8.RuneCount(text), eh.buf.LineArray)
|
||||
end := e.Deltas[0].End
|
||||
|
||||
for _, c := range eh.cursors {
|
||||
move := func(loc Loc) Loc {
|
||||
if start.Y != end.Y && loc.GreaterThan(start) {
|
||||
loc.Y += end.Y - start.Y
|
||||
} else if loc.Y == start.Y && loc.GreaterEqual(start) {
|
||||
loc = loc.MoveLA(utf8.RuneCount(text), eh.buf.LineArray)
|
||||
}
|
||||
return loc
|
||||
}
|
||||
c.Loc = move(c.Loc)
|
||||
c.CurSelection[0] = move(c.CurSelection[0])
|
||||
c.CurSelection[1] = move(c.CurSelection[1])
|
||||
c.OrigSelection[0] = move(c.OrigSelection[0])
|
||||
c.OrigSelection[1] = move(c.OrigSelection[1])
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
eh.DoTextEvent(e, true)
|
||||
}
|
||||
|
||||
// Remove creates a remove text event and executes it
|
||||
func (eh *EventHandler) Remove(start, end Loc) {
|
||||
if start == end {
|
||||
return
|
||||
}
|
||||
start = clamp(start, eh.buf.LineArray)
|
||||
end = clamp(end, eh.buf.LineArray)
|
||||
e := &TextEvent{
|
||||
C: *eh.cursors[eh.active],
|
||||
EventType: TextEventRemove,
|
||||
Deltas: []Delta{{[]byte{}, start, end}},
|
||||
Time: time.Now(),
|
||||
}
|
||||
eh.Execute(e)
|
||||
|
||||
for _, c := range eh.cursors {
|
||||
move := func(loc Loc) Loc {
|
||||
if start.Y != end.Y && loc.GreaterThan(end) {
|
||||
loc.Y -= end.Y - start.Y
|
||||
} else if loc.Y == end.Y && loc.GreaterEqual(end) {
|
||||
loc = loc.MoveLA(-DiffLA(start, end, eh.buf.LineArray), eh.buf.LineArray)
|
||||
}
|
||||
return loc
|
||||
}
|
||||
c.Loc = move(c.Loc)
|
||||
c.CurSelection[0] = move(c.CurSelection[0])
|
||||
c.CurSelection[1] = move(c.CurSelection[1])
|
||||
c.OrigSelection[0] = move(c.OrigSelection[0])
|
||||
c.OrigSelection[1] = move(c.OrigSelection[1])
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
eh.DoTextEvent(e, true)
|
||||
}
|
||||
|
||||
// MultipleReplace creates an multiple insertions executes them
|
||||
@@ -187,16 +237,14 @@ func (eh *EventHandler) Execute(t *TextEvent) {
|
||||
}
|
||||
eh.UndoStack.Push(t)
|
||||
|
||||
// TODO: Call plugins on text events
|
||||
// for pl := range loadedPlugins {
|
||||
// ret, err := Call(pl+".onBeforeTextEvent", t)
|
||||
// if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
|
||||
// screen.TermMessage(err)
|
||||
// }
|
||||
// if val, ok := ret.(lua.LBool); ok && val == lua.LFalse {
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
b, err := config.RunPluginFnBool("onBeforeTextEvent", luar.New(ulua.L, eh.buf), luar.New(ulua.L, t))
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
if !b {
|
||||
return
|
||||
}
|
||||
|
||||
ExecuteTextEvent(t, eh.buf)
|
||||
}
|
||||
@@ -209,8 +257,7 @@ func (eh *EventHandler) Undo() {
|
||||
}
|
||||
|
||||
startTime := t.Time.UnixNano() / int64(time.Millisecond)
|
||||
|
||||
eh.UndoOneEvent()
|
||||
endTime := startTime - (startTime % undoThreshold)
|
||||
|
||||
for {
|
||||
t = eh.UndoStack.Peek()
|
||||
@@ -218,10 +265,9 @@ func (eh *EventHandler) Undo() {
|
||||
return
|
||||
}
|
||||
|
||||
if startTime-(t.Time.UnixNano()/int64(time.Millisecond)) > undoThreshold {
|
||||
if t.Time.UnixNano()/int64(time.Millisecond) < endTime {
|
||||
return
|
||||
}
|
||||
startTime = t.Time.UnixNano() / int64(time.Millisecond)
|
||||
|
||||
eh.UndoOneEvent()
|
||||
}
|
||||
@@ -235,10 +281,9 @@ func (eh *EventHandler) UndoOneEvent() {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Undo it
|
||||
// Modifies the text event
|
||||
UndoTextEvent(t, eh.buf)
|
||||
eh.UndoTextEvent(t)
|
||||
|
||||
// Set the cursor in the right place
|
||||
teCursor := t.C
|
||||
@@ -261,8 +306,7 @@ func (eh *EventHandler) Redo() {
|
||||
}
|
||||
|
||||
startTime := t.Time.UnixNano() / int64(time.Millisecond)
|
||||
|
||||
eh.RedoOneEvent()
|
||||
endTime := startTime - (startTime % undoThreshold) + undoThreshold
|
||||
|
||||
for {
|
||||
t = eh.RedoStack.Peek()
|
||||
@@ -270,7 +314,7 @@ func (eh *EventHandler) Redo() {
|
||||
return
|
||||
}
|
||||
|
||||
if (t.Time.UnixNano()/int64(time.Millisecond))-startTime > undoThreshold {
|
||||
if t.Time.UnixNano()/int64(time.Millisecond) > endTime {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -285,9 +329,6 @@ func (eh *EventHandler) RedoOneEvent() {
|
||||
return
|
||||
}
|
||||
|
||||
// Modifies the text event
|
||||
UndoTextEvent(t, eh.buf)
|
||||
|
||||
teCursor := t.C
|
||||
if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
|
||||
t.C = *eh.cursors[teCursor.Num]
|
||||
@@ -296,5 +337,8 @@ func (eh *EventHandler) RedoOneEvent() {
|
||||
teCursor.Num = -1
|
||||
}
|
||||
|
||||
// Modifies the text event
|
||||
eh.UndoTextEvent(t)
|
||||
|
||||
eh.UndoStack.Push(t)
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ package buffer
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
"sync"
|
||||
|
||||
"github.com/zyedidia/micro/pkg/highlight"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/micro/v2/pkg/highlight"
|
||||
)
|
||||
|
||||
// Finds the byte index of the nth rune in a byte slice
|
||||
@@ -17,7 +19,7 @@ func runeToByteIndex(n int, txt []byte) int {
|
||||
count := 0
|
||||
i := 0
|
||||
for len(txt) > 0 {
|
||||
_, size := utf8.DecodeRune(txt)
|
||||
_, _, size := util.DecodeCharacter(txt)
|
||||
|
||||
txt = txt[size:]
|
||||
count += size
|
||||
@@ -38,6 +40,7 @@ type Line struct {
|
||||
state highlight.State
|
||||
match highlight.LineMatch
|
||||
rehighlight bool
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -84,6 +87,8 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
|
||||
br := bufio.NewReader(reader)
|
||||
var loaded int
|
||||
|
||||
la.Endings = endings
|
||||
|
||||
n := 0
|
||||
for {
|
||||
data, err := br.ReadBytes('\n')
|
||||
@@ -124,12 +129,22 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
|
||||
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
la.lines = Append(la.lines, Line{data[:], nil, nil, false})
|
||||
la.lines = Append(la.lines, Line{
|
||||
data: data[:],
|
||||
state: nil,
|
||||
match: nil,
|
||||
rehighlight: false,
|
||||
})
|
||||
}
|
||||
// Last line was read
|
||||
break
|
||||
} else {
|
||||
la.lines = Append(la.lines, Line{data[:dlen-1], nil, nil, false})
|
||||
la.lines = Append(la.lines, Line{
|
||||
data: data[:dlen-1],
|
||||
state: nil,
|
||||
match: nil,
|
||||
rehighlight: false,
|
||||
})
|
||||
}
|
||||
n++
|
||||
}
|
||||
@@ -140,24 +155,36 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
|
||||
// Bytes returns the string that should be written to disk when
|
||||
// the line array is saved
|
||||
func (la *LineArray) Bytes() []byte {
|
||||
str := make([]byte, 0, la.initsize+1000) // initsize should provide a good estimate
|
||||
b := new(bytes.Buffer)
|
||||
// initsize should provide a good estimate
|
||||
b.Grow(int(la.initsize + 4096))
|
||||
for i, l := range la.lines {
|
||||
str = append(str, l.data...)
|
||||
b.Write(l.data)
|
||||
if i != len(la.lines)-1 {
|
||||
if la.Endings == FFDos {
|
||||
str = append(str, '\r')
|
||||
b.WriteByte('\r')
|
||||
}
|
||||
str = append(str, '\n')
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
return str
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
// newlineBelow adds a newline below the given line number
|
||||
func (la *LineArray) newlineBelow(y int) {
|
||||
la.lines = append(la.lines, Line{[]byte{' '}, nil, nil, false})
|
||||
la.lines = append(la.lines, Line{
|
||||
data: []byte{' '},
|
||||
state: nil,
|
||||
match: nil,
|
||||
rehighlight: false,
|
||||
})
|
||||
copy(la.lines[y+2:], la.lines[y+1:])
|
||||
la.lines[y+1] = Line{[]byte{}, la.lines[y].state, nil, false}
|
||||
la.lines[y+1] = Line{
|
||||
data: []byte{},
|
||||
state: la.lines[y].state,
|
||||
match: nil,
|
||||
rehighlight: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Inserts a byte array at a given location
|
||||
@@ -208,9 +235,7 @@ func (la *LineArray) remove(start, end Loc) []byte {
|
||||
if start.Y == end.Y {
|
||||
la.lines[start.Y].data = append(la.lines[start.Y].data[:startX], la.lines[start.Y].data[endX:]...)
|
||||
} else {
|
||||
for i := start.Y + 1; i <= end.Y-1; i++ {
|
||||
la.deleteLine(start.Y + 1)
|
||||
}
|
||||
la.deleteLines(start.Y+1, end.Y-1)
|
||||
la.deleteToEnd(Loc{startX, start.Y})
|
||||
la.deleteFromStart(Loc{endX - 1, start.Y + 1})
|
||||
la.joinLines(start.Y, start.Y+1)
|
||||
@@ -233,6 +258,10 @@ func (la *LineArray) deleteLine(y int) {
|
||||
la.lines = la.lines[:y+copy(la.lines[y:], la.lines[y+1:])]
|
||||
}
|
||||
|
||||
func (la *LineArray) deleteLines(y1, y2 int) {
|
||||
la.lines = la.lines[:y1+copy(la.lines[y1:], la.lines[y2+1:])]
|
||||
}
|
||||
|
||||
// DeleteByte deletes the byte at a position
|
||||
func (la *LineArray) deleteByte(pos Loc) {
|
||||
la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X+copy(la.lines[pos.Y].data[pos.X:], la.lines[pos.Y].data[pos.X+1:])]
|
||||
@@ -272,7 +301,7 @@ func (la *LineArray) Start() Loc {
|
||||
// End returns the location of the last character in the buffer
|
||||
func (la *LineArray) End() Loc {
|
||||
numlines := len(la.lines)
|
||||
return Loc{utf8.RuneCount(la.lines[numlines-1].data), numlines - 1}
|
||||
return Loc{util.CharacterCount(la.lines[numlines-1].data), numlines - 1}
|
||||
}
|
||||
|
||||
// LineBytes returns line n as an array of bytes
|
||||
@@ -285,28 +314,40 @@ func (la *LineArray) LineBytes(n int) []byte {
|
||||
|
||||
// State gets the highlight state for the given line number
|
||||
func (la *LineArray) State(lineN int) highlight.State {
|
||||
la.lines[lineN].lock.Lock()
|
||||
defer la.lines[lineN].lock.Unlock()
|
||||
return la.lines[lineN].state
|
||||
}
|
||||
|
||||
// SetState sets the highlight state at the given line number
|
||||
func (la *LineArray) SetState(lineN int, s highlight.State) {
|
||||
la.lines[lineN].lock.Lock()
|
||||
defer la.lines[lineN].lock.Unlock()
|
||||
la.lines[lineN].state = s
|
||||
}
|
||||
|
||||
// SetMatch sets the match at the given line number
|
||||
func (la *LineArray) SetMatch(lineN int, m highlight.LineMatch) {
|
||||
la.lines[lineN].lock.Lock()
|
||||
defer la.lines[lineN].lock.Unlock()
|
||||
la.lines[lineN].match = m
|
||||
}
|
||||
|
||||
// Match retrieves the match for the given line number
|
||||
func (la *LineArray) Match(lineN int) highlight.LineMatch {
|
||||
la.lines[lineN].lock.Lock()
|
||||
defer la.lines[lineN].lock.Unlock()
|
||||
return la.lines[lineN].match
|
||||
}
|
||||
|
||||
func (la *LineArray) Rehighlight(lineN int) bool {
|
||||
la.lines[lineN].lock.Lock()
|
||||
defer la.lines[lineN].lock.Unlock()
|
||||
return la.lines[lineN].rehighlight
|
||||
}
|
||||
|
||||
func (la *LineArray) SetRehighlight(lineN int, on bool) {
|
||||
la.lines[lineN].lock.Lock()
|
||||
defer la.lines[lineN].lock.Unlock()
|
||||
la.lines[lineN].rehighlight = on
|
||||
}
|
||||
|
||||
@@ -46,14 +46,15 @@ func TestInsert(t *testing.T) {
|
||||
|
||||
assert.Equal(t, []byte("Uppen Sevarne staþe, foobar sel þar him þuhte,"), sub1)
|
||||
|
||||
la.insert(Loc{25, 2}, []byte("ಮಣ್ಣಾಗಿ"))
|
||||
la.insert(Loc{25, 2}, []byte("H̼̥̯͇͙̕͘͞e̸̦̞̠̣̰͙̼̥̦̼̖̬͕͕̰̯̫͇̕ĺ̜̠̩̯̯͙̼̭̠͕̮̞͜l̶͓̫̞̮͈͞ͅo̸͔͙̳̠͈̮̼̳͙̥̲͜͠"))
|
||||
|
||||
sub2 := la.Substr(Loc{0, 2}, Loc{60, 2})
|
||||
assert.Equal(t, []byte("He wonede at Ernleȝe at æಮಣ್ಣಾಗಿðelen are chirechen,"), sub2)
|
||||
assert.Equal(t, []byte("He wonede at Ernleȝe at æH̼̥̯͇͙̕͘͞e̸̦̞̠̣̰͙̼̥̦̼̖̬͕͕̰̯̫͇̕ĺ̜̠̩̯̯͙̼̭̠͕̮̞͜l̶͓̫̞̮͈͞ͅo̸͔͙̳̠͈̮̼̳͙̥̲͜͠ðelen are chirechen,"), sub2)
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
la.remove(Loc{20, 3}, Loc{27, 3})
|
||||
la.remove(Loc{25, 2}, Loc{32, 2})
|
||||
la.remove(Loc{25, 2}, Loc{30, 2})
|
||||
|
||||
bytes := la.Bytes()
|
||||
assert.Equal(t, unicode_txt, string(bytes))
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// Loc stores a location
|
||||
@@ -68,9 +66,9 @@ func DiffLA(a, b Loc, buf *LineArray) int {
|
||||
loc := 0
|
||||
for i := a.Y + 1; i < b.Y; i++ {
|
||||
// + 1 for the newline
|
||||
loc += utf8.RuneCount(buf.LineBytes(i)) + 1
|
||||
loc += util.CharacterCount(buf.LineBytes(i)) + 1
|
||||
}
|
||||
loc += utf8.RuneCount(buf.LineBytes(a.Y)) - a.X + b.X + 1
|
||||
loc += util.CharacterCount(buf.LineBytes(a.Y)) - a.X + b.X + 1
|
||||
return loc
|
||||
}
|
||||
|
||||
@@ -80,7 +78,7 @@ func (l Loc) right(buf *LineArray) Loc {
|
||||
return Loc{l.X + 1, l.Y}
|
||||
}
|
||||
var res Loc
|
||||
if l.X < utf8.RuneCount(buf.LineBytes(l.Y)) {
|
||||
if l.X < util.CharacterCount(buf.LineBytes(l.Y)) {
|
||||
res = Loc{l.X + 1, l.Y}
|
||||
} else {
|
||||
res = Loc{0, l.Y + 1}
|
||||
@@ -97,12 +95,12 @@ func (l Loc) left(buf *LineArray) Loc {
|
||||
if l.X > 0 {
|
||||
res = Loc{l.X - 1, l.Y}
|
||||
} else {
|
||||
res = Loc{utf8.RuneCount(buf.LineBytes(l.Y - 1)), l.Y - 1}
|
||||
res = Loc{util.CharacterCount(buf.LineBytes(l.Y - 1)), l.Y - 1}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Move moves the cursor n characters to the left or right
|
||||
// MoveLA moves the cursor n characters to the left or right
|
||||
// It moves the cursor left if n is negative
|
||||
func (l Loc) MoveLA(n int, buf *LineArray) Loc {
|
||||
if n > 0 {
|
||||
@@ -117,9 +115,12 @@ func (l Loc) MoveLA(n int, buf *LineArray) Loc {
|
||||
return l
|
||||
}
|
||||
|
||||
func (l Loc) Diff(a, b Loc, buf *Buffer) int {
|
||||
return DiffLA(a, b, buf.LineArray)
|
||||
// Diff returns the difference between two locs
|
||||
func (l Loc) Diff(b Loc, buf *Buffer) int {
|
||||
return DiffLA(l, b, buf.LineArray)
|
||||
}
|
||||
|
||||
// Move moves a loc n characters
|
||||
func (l Loc) Move(n int, buf *Buffer) Loc {
|
||||
return l.MoveLA(n, buf.LineArray)
|
||||
}
|
||||
@@ -135,3 +136,13 @@ func ByteOffset(pos Loc, buf *Buffer) int {
|
||||
loc += len(buf.Line(y)[:x])
|
||||
return loc
|
||||
}
|
||||
|
||||
// clamps a loc within a buffer
|
||||
func clamp(pos Loc, la *LineArray) Loc {
|
||||
if pos.GreaterEqual(la.End()) {
|
||||
return la.End()
|
||||
} else if pos.LessThan(la.Start()) {
|
||||
return la.Start()
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -13,13 +13,19 @@ const (
|
||||
MTError
|
||||
)
|
||||
|
||||
// Message represents the information for a gutter message
|
||||
type Message struct {
|
||||
Msg string
|
||||
// The Msg iteslf
|
||||
Msg string
|
||||
// Start and End locations for the message
|
||||
Start, End Loc
|
||||
Kind MsgType
|
||||
Owner string
|
||||
// The Kind stores the message type
|
||||
Kind MsgType
|
||||
// The Owner of the message
|
||||
Owner string
|
||||
}
|
||||
|
||||
// NewMessage creates a new gutter message
|
||||
func NewMessage(owner string, msg string, start, end Loc, kind MsgType) *Message {
|
||||
return &Message{
|
||||
Msg: msg,
|
||||
@@ -30,6 +36,7 @@ func NewMessage(owner string, msg string, start, end Loc, kind MsgType) *Message
|
||||
}
|
||||
}
|
||||
|
||||
// NewMessageAtLine creates a new gutter message at a given line
|
||||
func NewMessageAtLine(owner string, msg string, line int, kind MsgType) *Message {
|
||||
start := Loc{-1, line - 1}
|
||||
end := start
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
@@ -10,11 +11,10 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"golang.org/x/text/encoding"
|
||||
"golang.org/x/text/encoding/htmlindex"
|
||||
"golang.org/x/text/transform"
|
||||
@@ -28,41 +28,42 @@ const LargeFileThreshold = 50000
|
||||
// the supplied function with the file as io.Writer object, also making sure the file is
|
||||
// closed afterwards.
|
||||
func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error, withSudo bool) (err error) {
|
||||
var writeCloser io.WriteCloser
|
||||
var writeCloser io.WriteCloser
|
||||
|
||||
if withSudo {
|
||||
cmd := exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name)
|
||||
if withSudo {
|
||||
cmd := exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name)
|
||||
|
||||
if writeCloser, err = cmd.StdinPipe(); err != nil {
|
||||
return
|
||||
}
|
||||
if writeCloser, err = cmd.StdinPipe(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
<-c
|
||||
cmd.Process.Kill()
|
||||
}()
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
<-c
|
||||
cmd.Process.Kill()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
screenb := screen.TempFini()
|
||||
if e := cmd.Run(); e != nil && err == nil {
|
||||
err = e
|
||||
}
|
||||
screen.TempStart(screenb)
|
||||
}()
|
||||
} else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
screenb := screen.TempFini()
|
||||
if e := cmd.Run(); e != nil && err == nil {
|
||||
err = e
|
||||
}
|
||||
screen.TempStart(screenb)
|
||||
}()
|
||||
} else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
w := transform.NewWriter(writeCloser, enc.NewEncoder())
|
||||
err = fn(w)
|
||||
w := bufio.NewWriter(transform.NewWriter(writeCloser, enc.NewEncoder()))
|
||||
err = fn(w)
|
||||
w.Flush()
|
||||
|
||||
if e := writeCloser.Close(); e != nil && err == nil {
|
||||
err = e
|
||||
}
|
||||
if e := writeCloser.Close(); e != nil && err == nil {
|
||||
err = e
|
||||
}
|
||||
|
||||
return
|
||||
return
|
||||
}
|
||||
|
||||
// Save saves the buffer to its default path
|
||||
@@ -92,15 +93,14 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
|
||||
return errors.New("Cannot save scratch buffer")
|
||||
}
|
||||
if withSudo && runtime.GOOS == "windows" {
|
||||
return errors.New("Save with sudo not supported on Windows")
|
||||
return errors.New("Save with sudo not supported on Windows")
|
||||
}
|
||||
|
||||
b.UpdateRules()
|
||||
if b.Settings["rmtrailingws"].(bool) {
|
||||
for i, l := range b.lines {
|
||||
leftover := utf8.RuneCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))
|
||||
leftover := util.CharacterCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))
|
||||
|
||||
linelen := utf8.RuneCount(l.data)
|
||||
linelen := util.CharacterCount(l.data)
|
||||
b.Remove(Loc{leftover, i}, Loc{linelen, i})
|
||||
}
|
||||
|
||||
@@ -109,8 +109,8 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
|
||||
|
||||
if b.Settings["eofnewline"].(bool) {
|
||||
end := b.End()
|
||||
if b.RuneAt(Loc{end.X - 1, end.Y}) != '\n' {
|
||||
b.Insert(end, "\n")
|
||||
if b.RuneAt(Loc{end.X, end.Y}) != '\n' {
|
||||
b.insert(end, []byte{'\n'})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
|
||||
}
|
||||
|
||||
if err = overwriteFile(absFilename, enc, fwriter, withSudo); err != nil {
|
||||
return err
|
||||
return err
|
||||
}
|
||||
|
||||
if !b.Settings["fastdirty"].(bool) {
|
||||
@@ -194,5 +194,6 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
|
||||
absPath, _ := filepath.Abs(filename)
|
||||
b.AbsPath = absPath
|
||||
b.isModified = false
|
||||
b.UpdateRules()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,9 +2,8 @@ package buffer
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||
@@ -20,19 +19,19 @@ func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||
charpos := 0
|
||||
|
||||
if i == start.Y && start.Y == end.Y {
|
||||
nchars := utf8.RuneCount(l)
|
||||
nchars := util.CharacterCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars)
|
||||
end.X = util.Clamp(end.X, 0, nchars)
|
||||
l = util.SliceStart(l, end.X)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == start.Y {
|
||||
nchars := utf8.RuneCount(l)
|
||||
nchars := util.CharacterCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == end.Y {
|
||||
nchars := utf8.RuneCount(l)
|
||||
nchars := util.CharacterCount(l)
|
||||
end.X = util.Clamp(end.X, 0, nchars)
|
||||
l = util.SliceStart(l, end.X)
|
||||
}
|
||||
@@ -61,19 +60,19 @@ func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||
charpos := 0
|
||||
|
||||
if i == start.Y && start.Y == end.Y {
|
||||
nchars := utf8.RuneCount(l)
|
||||
nchars := util.CharacterCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars)
|
||||
end.X = util.Clamp(end.X, 0, nchars)
|
||||
l = util.SliceStart(l, end.X)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == start.Y {
|
||||
nchars := utf8.RuneCount(l)
|
||||
nchars := util.CharacterCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == end.Y {
|
||||
nchars := utf8.RuneCount(l)
|
||||
nchars := util.CharacterCount(l)
|
||||
end.X = util.Clamp(end.X, 0, nchars)
|
||||
l = util.SliceStart(l, end.X)
|
||||
}
|
||||
@@ -120,24 +119,27 @@ func (b *Buffer) FindNext(s string, start, end, from Loc, down bool, useRegex bo
|
||||
if down {
|
||||
l, found = b.findDown(r, from, end)
|
||||
if !found {
|
||||
l, found = b.findDown(r, start, from)
|
||||
l, found = b.findDown(r, start, end)
|
||||
}
|
||||
} else {
|
||||
l, found = b.findUp(r, from, start)
|
||||
if !found {
|
||||
l, found = b.findUp(r, end, from)
|
||||
l, found = b.findUp(r, end, start)
|
||||
}
|
||||
}
|
||||
return l, found, nil
|
||||
}
|
||||
|
||||
// ReplaceRegex replaces all occurrences of 'search' with 'replace' in the given area
|
||||
// and returns the number of replacements made
|
||||
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte) int {
|
||||
// and returns the number of replacements made and the number of runes
|
||||
// added or removed
|
||||
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte) (int, int) {
|
||||
if start.GreaterThan(end) {
|
||||
start, end = end, start
|
||||
}
|
||||
|
||||
netrunes := 0
|
||||
|
||||
found := 0
|
||||
var deltas []Delta
|
||||
for i := start.Y; i <= end.Y; i++ {
|
||||
@@ -155,16 +157,21 @@ func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []b
|
||||
l = util.SliceStart(l, end.X)
|
||||
}
|
||||
newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
|
||||
result := []byte{}
|
||||
for _, submatches := range search.FindAllSubmatchIndex(in, -1) {
|
||||
result = search.Expand(result, replace, in, submatches)
|
||||
}
|
||||
found++
|
||||
return replace
|
||||
netrunes += util.CharacterCount(in) - util.CharacterCount(result)
|
||||
return result
|
||||
})
|
||||
|
||||
from := Loc{charpos, i}
|
||||
to := Loc{charpos + utf8.RuneCount(l), i}
|
||||
to := Loc{charpos + util.CharacterCount(l), i}
|
||||
|
||||
deltas = append(deltas, Delta{newText, from, to})
|
||||
}
|
||||
b.MultipleReplace(deltas)
|
||||
|
||||
return found
|
||||
return found, netrunes
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/encoding"
|
||||
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// The SerializedBuffer holds the types that get serialized when a buffer is saved
|
||||
@@ -30,7 +31,7 @@ func (b *Buffer) Serialize() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := config.ConfigDir + "/buffers/" + util.EscapePath(b.AbsPath)
|
||||
name := filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath))
|
||||
|
||||
return overwriteFile(name, encoding.Nop, func(file io.Writer) error {
|
||||
err := gob.NewEncoder(file).Encode(SerializedBuffer{
|
||||
@@ -49,7 +50,7 @@ func (b *Buffer) Unserialize() error {
|
||||
if b.Path == "" {
|
||||
return nil
|
||||
}
|
||||
file, err := os.Open(config.ConfigDir + "/buffers/" + util.EscapePath(b.AbsPath))
|
||||
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath)))
|
||||
defer file.Close()
|
||||
if err == nil {
|
||||
var buffer SerializedBuffer
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
)
|
||||
|
||||
func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
|
||||
@@ -35,7 +35,7 @@ func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
|
||||
}
|
||||
} else if option == "encoding" {
|
||||
b.isModified = true
|
||||
} else if option == "readonly" && b.Type == BTDefault {
|
||||
} else if option == "readonly" && b.Type.Kind == BTDefault.Kind {
|
||||
b.Type.Readonly = nativeValue.(bool)
|
||||
}
|
||||
|
||||
|
||||
@@ -151,6 +151,9 @@ func StringToStyle(str string) tcell.Style {
|
||||
if strings.Contains(str, "bold") {
|
||||
style = style.Bold(true)
|
||||
}
|
||||
if strings.Contains(str, "italic") {
|
||||
style = style.Italic(true)
|
||||
}
|
||||
if strings.Contains(str, "reverse") {
|
||||
style = style.Reverse(true)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package config
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
)
|
||||
@@ -24,10 +25,10 @@ func InitConfigDir(flagConfigDir string) error {
|
||||
if err != nil {
|
||||
return errors.New("Error finding your home directory\nCan't load config files: " + err.Error())
|
||||
}
|
||||
xdgHome = home + "/.config"
|
||||
xdgHome = filepath.Join(home, ".config")
|
||||
}
|
||||
|
||||
microHome = xdgHome + "/micro"
|
||||
microHome = filepath.Join(xdgHome, "micro")
|
||||
}
|
||||
ConfigDir = microHome
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@ package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
)
|
||||
|
||||
// ErrNoSuchFunction is returned when Call is executed on a function that does not exist
|
||||
var ErrNoSuchFunction = errors.New("No such function exists")
|
||||
|
||||
// LoadAllPlugins loads all detected plugins (in runtime/plugins and ConfigDir/plugins)
|
||||
@@ -55,15 +57,14 @@ func RunPluginFnBool(fn string, args ...lua.LValue) (bool, error) {
|
||||
reterr = errors.New("Plugin " + p.Name + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
if v, ok := val.(lua.LBool); !ok {
|
||||
reterr = errors.New(p.Name + "." + fn + " should return a boolean")
|
||||
} else {
|
||||
if v, ok := val.(lua.LBool); ok {
|
||||
retbool = retbool && bool(v)
|
||||
}
|
||||
}
|
||||
return retbool, reterr
|
||||
}
|
||||
|
||||
// Plugin stores information about the source files/info for a plugin
|
||||
type Plugin struct {
|
||||
DirName string // name of plugin folder
|
||||
Name string // name of plugin
|
||||
@@ -73,6 +74,7 @@ type Plugin struct {
|
||||
Default bool // pre-installed plugin
|
||||
}
|
||||
|
||||
// IsEnabled returns if a plugin is enabled
|
||||
func (p *Plugin) IsEnabled() bool {
|
||||
if v, ok := GlobalSettings[p.Name]; ok {
|
||||
return v.(bool) && p.Loaded
|
||||
@@ -80,13 +82,15 @@ func (p *Plugin) IsEnabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Plugins is a list of all detected plugins (enabled or disabled)
|
||||
var Plugins []*Plugin
|
||||
|
||||
// Load creates an option for the plugin and runs all source files
|
||||
func (p *Plugin) Load() error {
|
||||
if v, ok := GlobalSettings[p.Name]; ok && !v.(bool) {
|
||||
return nil
|
||||
}
|
||||
for _, f := range p.Srcs {
|
||||
if v, ok := GlobalSettings[p.Name]; ok && !v.(bool) {
|
||||
return nil
|
||||
}
|
||||
dat, err := f.Data()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -95,14 +99,19 @@ func (p *Plugin) Load() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Loaded = true
|
||||
RegisterGlobalOption(p.Name, true)
|
||||
}
|
||||
p.Loaded = true
|
||||
RegisterGlobalOption(p.Name, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Call calls a given function in this plugin
|
||||
func (p *Plugin) Call(fn string, args ...lua.LValue) (lua.LValue, error) {
|
||||
plug := ulua.L.GetGlobal(p.Name)
|
||||
if plug == lua.LNil {
|
||||
log.Println("Plugin does not exist:", p.Name, "at", p.DirName, ":", p)
|
||||
return nil, nil
|
||||
}
|
||||
luafn := ulua.L.GetField(plug, fn)
|
||||
if luafn == lua.LNil {
|
||||
return nil, ErrNoSuchFunction
|
||||
@@ -120,7 +129,23 @@ func (p *Plugin) Call(fn string, args ...lua.LValue) (lua.LValue, error) {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// FindPlugin returns the plugin with the given name
|
||||
func FindPlugin(name string) *Plugin {
|
||||
var pl *Plugin
|
||||
for _, p := range Plugins {
|
||||
if !p.IsEnabled() {
|
||||
continue
|
||||
}
|
||||
if p.Name == name {
|
||||
pl = p
|
||||
break
|
||||
}
|
||||
}
|
||||
return pl
|
||||
}
|
||||
|
||||
// FindAnyPlugin does not require the plugin to be enabled
|
||||
func FindAnyPlugin(name string) *Plugin {
|
||||
var pl *Plugin
|
||||
for _, p := range Plugins {
|
||||
if p.Name == name {
|
||||
|
||||
712
internal/config/plugin_installer.go
Normal file
712
internal/config/plugin_installer.go
Normal file
@@ -0,0 +1,712 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/blang/semver"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/json5"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
var (
|
||||
allPluginPackages PluginPackages
|
||||
)
|
||||
|
||||
// CorePluginName is a plugin dependency name for the micro core.
|
||||
const CorePluginName = "micro"
|
||||
|
||||
// PluginChannel contains an url to a json list of PluginRepository
|
||||
type PluginChannel string
|
||||
|
||||
// PluginChannels is a slice of PluginChannel
|
||||
type PluginChannels []PluginChannel
|
||||
|
||||
// PluginRepository contains an url to json file containing PluginPackages
|
||||
type PluginRepository string
|
||||
|
||||
// PluginPackage contains the meta-data of a plugin and all available versions
|
||||
type PluginPackage struct {
|
||||
Name string
|
||||
Description string
|
||||
Author string
|
||||
Tags []string
|
||||
Versions PluginVersions
|
||||
}
|
||||
|
||||
// PluginPackages is a list of PluginPackage instances.
|
||||
type PluginPackages []*PluginPackage
|
||||
|
||||
// PluginVersion descripes a version of a PluginPackage. Containing a version, download url and also dependencies.
|
||||
type PluginVersion struct {
|
||||
pack *PluginPackage
|
||||
Version semver.Version
|
||||
Url string
|
||||
Require PluginDependencies
|
||||
}
|
||||
|
||||
func (pv *PluginVersion) Pack() *PluginPackage {
|
||||
return pv.pack
|
||||
}
|
||||
|
||||
// PluginVersions is a slice of PluginVersion
|
||||
type PluginVersions []*PluginVersion
|
||||
|
||||
// PluginDependency descripes a dependency to another plugin or micro itself.
|
||||
type PluginDependency struct {
|
||||
Name string
|
||||
Range semver.Range
|
||||
}
|
||||
|
||||
// PluginDependencies is a slice of PluginDependency
|
||||
type PluginDependencies []*PluginDependency
|
||||
|
||||
func (pp *PluginPackage) String() string {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString("Plugin: ")
|
||||
buf.WriteString(pp.Name)
|
||||
buf.WriteRune('\n')
|
||||
if pp.Author != "" {
|
||||
buf.WriteString("Author: ")
|
||||
buf.WriteString(pp.Author)
|
||||
buf.WriteRune('\n')
|
||||
}
|
||||
if pp.Description != "" {
|
||||
buf.WriteRune('\n')
|
||||
buf.WriteString(pp.Description)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func fetchAllSources(count int, fetcher func(i int) PluginPackages) PluginPackages {
|
||||
wgQuery := new(sync.WaitGroup)
|
||||
wgQuery.Add(count)
|
||||
|
||||
results := make(chan PluginPackages)
|
||||
|
||||
wgDone := new(sync.WaitGroup)
|
||||
wgDone.Add(1)
|
||||
var packages PluginPackages
|
||||
for i := 0; i < count; i++ {
|
||||
go func(i int) {
|
||||
results <- fetcher(i)
|
||||
wgQuery.Done()
|
||||
}(i)
|
||||
}
|
||||
go func() {
|
||||
packages = make(PluginPackages, 0)
|
||||
for res := range results {
|
||||
packages = append(packages, res...)
|
||||
}
|
||||
wgDone.Done()
|
||||
}()
|
||||
wgQuery.Wait()
|
||||
close(results)
|
||||
wgDone.Wait()
|
||||
return packages
|
||||
}
|
||||
|
||||
// Fetch retrieves all available PluginPackages from the given channels
|
||||
func (pc PluginChannels) Fetch(out io.Writer) PluginPackages {
|
||||
return fetchAllSources(len(pc), func(i int) PluginPackages {
|
||||
return pc[i].Fetch(out)
|
||||
})
|
||||
}
|
||||
|
||||
// Fetch retrieves all available PluginPackages from the given channel
|
||||
func (pc PluginChannel) Fetch(out io.Writer) PluginPackages {
|
||||
resp, err := http.Get(string(pc))
|
||||
if err != nil {
|
||||
fmt.Fprintln(out, "Failed to query plugin channel:\n", err)
|
||||
return PluginPackages{}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
decoder := json5.NewDecoder(resp.Body)
|
||||
|
||||
var repositories []PluginRepository
|
||||
if err := decoder.Decode(&repositories); err != nil {
|
||||
fmt.Fprintln(out, "Failed to decode channel data:\n", err)
|
||||
return PluginPackages{}
|
||||
}
|
||||
return fetchAllSources(len(repositories), func(i int) PluginPackages {
|
||||
return repositories[i].Fetch(out)
|
||||
})
|
||||
}
|
||||
|
||||
// Fetch retrieves all available PluginPackages from the given repository
|
||||
func (pr PluginRepository) Fetch(out io.Writer) PluginPackages {
|
||||
resp, err := http.Get(string(pr))
|
||||
if err != nil {
|
||||
fmt.Fprintln(out, "Failed to query plugin repository:\n", err)
|
||||
return PluginPackages{}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
decoder := json5.NewDecoder(resp.Body)
|
||||
|
||||
var plugins PluginPackages
|
||||
if err := decoder.Decode(&plugins); err != nil {
|
||||
fmt.Fprintln(out, "Failed to decode repository data:\n", err)
|
||||
return PluginPackages{}
|
||||
}
|
||||
if len(plugins) > 0 {
|
||||
return PluginPackages{plugins[0]}
|
||||
}
|
||||
return nil
|
||||
// return plugins
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals raw json to a PluginVersion
|
||||
func (pv *PluginVersion) UnmarshalJSON(data []byte) error {
|
||||
var values struct {
|
||||
Version semver.Version
|
||||
Url string
|
||||
Require map[string]string
|
||||
}
|
||||
|
||||
if err := json5.Unmarshal(data, &values); err != nil {
|
||||
return err
|
||||
}
|
||||
pv.Version = values.Version
|
||||
pv.Url = values.Url
|
||||
pv.Require = make(PluginDependencies, 0)
|
||||
|
||||
for k, v := range values.Require {
|
||||
// don't add the dependency if it's the core and
|
||||
// we have a unknown version number.
|
||||
// in that case just accept that dependency (which equals to not adding it.)
|
||||
if k != CorePluginName || !isUnknownCoreVersion() {
|
||||
if vRange, err := semver.ParseRange(v); err == nil {
|
||||
pv.Require = append(pv.Require, &PluginDependency{k, vRange})
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals raw json to a PluginPackage
|
||||
func (pp *PluginPackage) UnmarshalJSON(data []byte) error {
|
||||
var values struct {
|
||||
Name string
|
||||
Description string
|
||||
Author string
|
||||
Tags []string
|
||||
Versions PluginVersions
|
||||
}
|
||||
if err := json5.Unmarshal(data, &values); err != nil {
|
||||
return err
|
||||
}
|
||||
pp.Name = values.Name
|
||||
pp.Description = values.Description
|
||||
pp.Author = values.Author
|
||||
pp.Tags = values.Tags
|
||||
pp.Versions = values.Versions
|
||||
for _, v := range pp.Versions {
|
||||
v.pack = pp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAllPluginPackages gets all PluginPackages which may be available.
|
||||
func GetAllPluginPackages(out io.Writer) PluginPackages {
|
||||
if allPluginPackages == nil {
|
||||
getOption := func(name string) []string {
|
||||
data := GetGlobalOption(name)
|
||||
if strs, ok := data.([]string); ok {
|
||||
return strs
|
||||
}
|
||||
if ifs, ok := data.([]interface{}); ok {
|
||||
result := make([]string, len(ifs))
|
||||
for i, urlIf := range ifs {
|
||||
if url, ok := urlIf.(string); ok {
|
||||
result[i] = url
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
channels := PluginChannels{}
|
||||
for _, url := range getOption("pluginchannels") {
|
||||
channels = append(channels, PluginChannel(url))
|
||||
}
|
||||
repos := []PluginRepository{}
|
||||
for _, url := range getOption("pluginrepos") {
|
||||
repos = append(repos, PluginRepository(url))
|
||||
}
|
||||
allPluginPackages = fetchAllSources(len(repos)+1, func(i int) PluginPackages {
|
||||
if i == 0 {
|
||||
return channels.Fetch(out)
|
||||
}
|
||||
return repos[i-1].Fetch(out)
|
||||
})
|
||||
}
|
||||
return allPluginPackages
|
||||
}
|
||||
|
||||
func (pv PluginVersions) find(ppName string) *PluginVersion {
|
||||
for _, v := range pv {
|
||||
if v.pack.Name == ppName {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Len returns the number of pluginversions in this slice
|
||||
func (pv PluginVersions) Len() int {
|
||||
return len(pv)
|
||||
}
|
||||
|
||||
// Swap two entries of the slice
|
||||
func (pv PluginVersions) Swap(i, j int) {
|
||||
pv[i], pv[j] = pv[j], pv[i]
|
||||
}
|
||||
|
||||
// Less returns true if the version at position i is greater then the version at position j (used for sorting)
|
||||
func (pv PluginVersions) Less(i, j int) bool {
|
||||
return pv[i].Version.GT(pv[j].Version)
|
||||
}
|
||||
|
||||
// Match returns true if the package matches a given search text
|
||||
func (pp PluginPackage) Match(text string) bool {
|
||||
text = strings.ToLower(text)
|
||||
for _, t := range pp.Tags {
|
||||
if strings.ToLower(t) == text {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if strings.Contains(strings.ToLower(pp.Name), text) {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.Contains(strings.ToLower(pp.Description), text) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsInstallable returns true if the package can be installed.
|
||||
func (pp PluginPackage) IsInstallable(out io.Writer) error {
|
||||
_, err := GetAllPluginPackages(out).Resolve(GetInstalledVersions(true), PluginDependencies{
|
||||
&PluginDependency{
|
||||
Name: pp.Name,
|
||||
Range: semver.Range(func(v semver.Version) bool { return true }),
|
||||
}})
|
||||
return err
|
||||
}
|
||||
|
||||
// SearchPlugin retrieves a list of all PluginPackages which match the given search text and
|
||||
// could be or are already installed
|
||||
func SearchPlugin(out io.Writer, texts []string) (plugins PluginPackages) {
|
||||
plugins = make(PluginPackages, 0)
|
||||
|
||||
pluginLoop:
|
||||
for _, pp := range GetAllPluginPackages(out) {
|
||||
for _, text := range texts {
|
||||
if !pp.Match(text) {
|
||||
continue pluginLoop
|
||||
}
|
||||
}
|
||||
|
||||
if err := pp.IsInstallable(out); err == nil {
|
||||
plugins = append(plugins, pp)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func isUnknownCoreVersion() bool {
|
||||
_, err := semver.ParseTolerant(util.Version)
|
||||
return err != nil
|
||||
}
|
||||
|
||||
func newStaticPluginVersion(name, version string) *PluginVersion {
|
||||
vers, err := semver.ParseTolerant(version)
|
||||
|
||||
if err != nil {
|
||||
if vers, err = semver.ParseTolerant("0.0.0-" + version); err != nil {
|
||||
vers = semver.MustParse("0.0.0-unknown")
|
||||
}
|
||||
}
|
||||
pl := &PluginPackage{
|
||||
Name: name,
|
||||
}
|
||||
pv := &PluginVersion{
|
||||
pack: pl,
|
||||
Version: vers,
|
||||
}
|
||||
pl.Versions = PluginVersions{pv}
|
||||
return pv
|
||||
}
|
||||
|
||||
// GetInstalledVersions returns a list of all currently installed plugins including an entry for
|
||||
// micro itself. This can be used to resolve dependencies.
|
||||
func GetInstalledVersions(withCore bool) PluginVersions {
|
||||
result := PluginVersions{}
|
||||
if withCore {
|
||||
result = append(result, newStaticPluginVersion(CorePluginName, util.Version))
|
||||
}
|
||||
|
||||
for _, p := range Plugins {
|
||||
if !p.IsEnabled() {
|
||||
continue
|
||||
}
|
||||
version := GetInstalledPluginVersion(p.Name)
|
||||
if pv := newStaticPluginVersion(p.Name, version); pv != nil {
|
||||
result = append(result, pv)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetInstalledPluginVersion returns the string of the exported VERSION variable of a loaded plugin
|
||||
func GetInstalledPluginVersion(name string) string {
|
||||
plugin := ulua.L.GetGlobal(name)
|
||||
if plugin != lua.LNil {
|
||||
version := ulua.L.GetField(plugin, "VERSION")
|
||||
if str, ok := version.(lua.LString); ok {
|
||||
return string(str)
|
||||
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// DownloadAndInstall downloads and installs the given plugin and version
|
||||
func (pv *PluginVersion) DownloadAndInstall(out io.Writer) error {
|
||||
fmt.Fprintf(out, "Downloading %q (%s) from %q\n", pv.pack.Name, pv.Version, pv.Url)
|
||||
resp, err := http.Get(pv.Url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
zipbuf := bytes.NewReader(data)
|
||||
z, err := zip.NewReader(zipbuf, zipbuf.Size())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetDir := filepath.Join(ConfigDir, "plug", pv.pack.Name)
|
||||
dirPerm := os.FileMode(0755)
|
||||
if err = os.MkdirAll(targetDir, dirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if all files in zip are in the same directory.
|
||||
// this might be the case if the plugin zip contains the whole plugin dir
|
||||
// instead of its content.
|
||||
var prefix string
|
||||
allPrefixed := false
|
||||
for i, f := range z.File {
|
||||
parts := strings.Split(f.Name, "/")
|
||||
if i == 0 {
|
||||
prefix = parts[0]
|
||||
} else if parts[0] != prefix {
|
||||
allPrefixed = false
|
||||
break
|
||||
} else {
|
||||
// switch to true since we have at least a second file
|
||||
allPrefixed = true
|
||||
}
|
||||
}
|
||||
|
||||
// Install files and directory's
|
||||
for _, f := range z.File {
|
||||
parts := strings.Split(f.Name, "/")
|
||||
if allPrefixed {
|
||||
parts = parts[1:]
|
||||
}
|
||||
|
||||
targetName := filepath.Join(targetDir, filepath.Join(parts...))
|
||||
if f.FileInfo().IsDir() {
|
||||
if err := os.MkdirAll(targetName, dirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
basepath := filepath.Dir(targetName)
|
||||
|
||||
if err := os.MkdirAll(basepath, dirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer content.Close()
|
||||
target, err := os.Create(targetName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer target.Close()
|
||||
if _, err = io.Copy(target, content); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pl PluginPackages) Get(name string) *PluginPackage {
|
||||
for _, p := range pl {
|
||||
if p.Name == name {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pl PluginPackages) GetAllVersions(name string) PluginVersions {
|
||||
result := make(PluginVersions, 0)
|
||||
p := pl.Get(name)
|
||||
if p != nil {
|
||||
for _, v := range p.Versions {
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (req PluginDependencies) Join(other PluginDependencies) PluginDependencies {
|
||||
m := make(map[string]*PluginDependency)
|
||||
for _, r := range req {
|
||||
m[r.Name] = r
|
||||
}
|
||||
for _, o := range other {
|
||||
cur, ok := m[o.Name]
|
||||
if ok {
|
||||
m[o.Name] = &PluginDependency{
|
||||
o.Name,
|
||||
o.Range.AND(cur.Range),
|
||||
}
|
||||
} else {
|
||||
m[o.Name] = o
|
||||
}
|
||||
}
|
||||
result := make(PluginDependencies, 0, len(m))
|
||||
for _, v := range m {
|
||||
result = append(result, v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Resolve resolves dependencies between different plugins
|
||||
func (all PluginPackages) Resolve(selectedVersions PluginVersions, open PluginDependencies) (PluginVersions, error) {
|
||||
if len(open) == 0 {
|
||||
return selectedVersions, nil
|
||||
}
|
||||
currentRequirement, stillOpen := open[0], open[1:]
|
||||
if currentRequirement != nil {
|
||||
if selVersion := selectedVersions.find(currentRequirement.Name); selVersion != nil {
|
||||
if currentRequirement.Range(selVersion.Version) {
|
||||
return all.Resolve(selectedVersions, stillOpen)
|
||||
}
|
||||
return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
|
||||
}
|
||||
availableVersions := all.GetAllVersions(currentRequirement.Name)
|
||||
sort.Sort(availableVersions)
|
||||
|
||||
for _, version := range availableVersions {
|
||||
if currentRequirement.Range(version.Version) {
|
||||
resolved, err := all.Resolve(append(selectedVersions, version), stillOpen.Join(version.Require))
|
||||
|
||||
if err == nil {
|
||||
return resolved, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
|
||||
}
|
||||
return selectedVersions, nil
|
||||
}
|
||||
|
||||
func (pv PluginVersions) install(out io.Writer) {
|
||||
anyInstalled := false
|
||||
currentlyInstalled := GetInstalledVersions(true)
|
||||
|
||||
for _, sel := range pv {
|
||||
if sel.pack.Name != CorePluginName {
|
||||
shouldInstall := true
|
||||
if pv := currentlyInstalled.find(sel.pack.Name); pv != nil {
|
||||
if pv.Version.NE(sel.Version) {
|
||||
fmt.Fprintln(out, "Uninstalling", sel.pack.Name)
|
||||
UninstallPlugin(out, sel.pack.Name)
|
||||
} else {
|
||||
shouldInstall = false
|
||||
}
|
||||
}
|
||||
|
||||
if shouldInstall {
|
||||
if err := sel.DownloadAndInstall(out); err != nil {
|
||||
fmt.Fprintln(out, err)
|
||||
return
|
||||
}
|
||||
anyInstalled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if anyInstalled {
|
||||
fmt.Fprintln(out, "One or more plugins installed.")
|
||||
} else {
|
||||
fmt.Fprintln(out, "Nothing to install / update")
|
||||
}
|
||||
}
|
||||
|
||||
// UninstallPlugin deletes the plugin folder of the given plugin
|
||||
func UninstallPlugin(out io.Writer, name string) {
|
||||
for _, p := range Plugins {
|
||||
if !p.IsEnabled() {
|
||||
continue
|
||||
}
|
||||
if p.Name == name {
|
||||
p.Loaded = false
|
||||
if err := os.RemoveAll(filepath.Join(ConfigDir, "plug", p.DirName)); err != nil {
|
||||
fmt.Fprintln(out, err)
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Install installs the plugin
|
||||
func (pl PluginPackage) Install(out io.Writer) {
|
||||
selected, err := GetAllPluginPackages(out).Resolve(GetInstalledVersions(true), PluginDependencies{
|
||||
&PluginDependency{
|
||||
Name: pl.Name,
|
||||
Range: semver.Range(func(v semver.Version) bool { return true }),
|
||||
}})
|
||||
if err != nil {
|
||||
fmt.Fprintln(out, err)
|
||||
return
|
||||
}
|
||||
selected.install(out)
|
||||
}
|
||||
|
||||
// UpdatePlugins updates the given plugins
|
||||
func UpdatePlugins(out io.Writer, plugins []string) {
|
||||
// if no plugins are specified, update all installed plugins.
|
||||
if len(plugins) == 0 {
|
||||
for _, p := range Plugins {
|
||||
if !p.IsEnabled() {
|
||||
continue
|
||||
}
|
||||
plugins = append(plugins, p.Name)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(out, "Checking for plugin updates")
|
||||
microVersion := PluginVersions{
|
||||
newStaticPluginVersion(CorePluginName, util.Version),
|
||||
}
|
||||
|
||||
var updates = make(PluginDependencies, 0)
|
||||
for _, name := range plugins {
|
||||
pv := GetInstalledPluginVersion(name)
|
||||
r, err := semver.ParseRange(">=" + pv) // Try to get newer versions.
|
||||
if err == nil {
|
||||
updates = append(updates, &PluginDependency{
|
||||
Name: name,
|
||||
Range: r,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
selected, err := GetAllPluginPackages(out).Resolve(microVersion, updates)
|
||||
if err != nil {
|
||||
fmt.Fprintln(out, err)
|
||||
return
|
||||
}
|
||||
selected.install(out)
|
||||
}
|
||||
|
||||
func PluginCommand(out io.Writer, cmd string, args []string) {
|
||||
switch cmd {
|
||||
case "install":
|
||||
installedVersions := GetInstalledVersions(false)
|
||||
for _, plugin := range args {
|
||||
pp := GetAllPluginPackages(out).Get(plugin)
|
||||
if pp == nil {
|
||||
fmt.Fprintln(out, "Unknown plugin \""+plugin+"\"")
|
||||
} else if err := pp.IsInstallable(out); err != nil {
|
||||
fmt.Fprintln(out, "Error installing ", plugin, ": ", err)
|
||||
} else {
|
||||
for _, installed := range installedVersions {
|
||||
if pp.Name == installed.Pack().Name {
|
||||
if pp.Versions[0].Version.Compare(installed.Version) == 1 {
|
||||
fmt.Fprintln(out, pp.Name, " is already installed but out-of-date: use 'plugin update ", pp.Name, "' to update")
|
||||
} else {
|
||||
fmt.Fprintln(out, pp.Name, " is already installed")
|
||||
}
|
||||
}
|
||||
}
|
||||
pp.Install(out)
|
||||
}
|
||||
}
|
||||
|
||||
case "remove":
|
||||
removed := ""
|
||||
for _, plugin := range args {
|
||||
// check if the plugin exists.
|
||||
for _, p := range Plugins {
|
||||
if p.Name == plugin && p.Default {
|
||||
fmt.Fprintln(out, "Default plugins cannot be removed, but can be disabled via settings.")
|
||||
continue
|
||||
}
|
||||
if p.Name == plugin {
|
||||
UninstallPlugin(out, plugin)
|
||||
removed += plugin + " "
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if removed != "" {
|
||||
fmt.Fprintln(out, "Removed ", removed)
|
||||
} else {
|
||||
fmt.Fprintln(out, "No plugins removed")
|
||||
}
|
||||
case "update":
|
||||
UpdatePlugins(out, args)
|
||||
case "list":
|
||||
plugins := GetInstalledVersions(false)
|
||||
fmt.Fprintln(out, "The following plugins are currently installed:")
|
||||
for _, p := range plugins {
|
||||
fmt.Fprintf(out, "%s (%s)\n", p.Pack().Name, p.Version)
|
||||
}
|
||||
case "search":
|
||||
plugins := SearchPlugin(out, args)
|
||||
fmt.Fprintln(out, len(plugins), " plugins found")
|
||||
for _, p := range plugins {
|
||||
fmt.Fprintln(out, "----------------")
|
||||
fmt.Fprintln(out, p.String())
|
||||
}
|
||||
fmt.Fprintln(out, "----------------")
|
||||
case "available":
|
||||
packages := GetAllPluginPackages(out)
|
||||
fmt.Fprintln(out, "Available Plugins:")
|
||||
for _, pkg := range packages {
|
||||
fmt.Fprintln(out, pkg.Name)
|
||||
}
|
||||
default:
|
||||
fmt.Fprintln(out, "Invalid plugin command")
|
||||
}
|
||||
}
|
||||
56
internal/config/plugin_installer_test.go
Normal file
56
internal/config/plugin_installer_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/blang/semver"
|
||||
|
||||
"github.com/zyedidia/json5"
|
||||
)
|
||||
|
||||
func TestDependencyResolving(t *testing.T) {
|
||||
js := `
|
||||
[{
|
||||
"Name": "Foo",
|
||||
"Versions": [{ "Version": "1.0.0" }, { "Version": "1.5.0" },{ "Version": "2.0.0" }]
|
||||
}, {
|
||||
"Name": "Bar",
|
||||
"Versions": [{ "Version": "1.0.0", "Require": {"Foo": ">1.0.0 <2.0.0"} }]
|
||||
}, {
|
||||
"Name": "Unresolvable",
|
||||
"Versions": [{ "Version": "1.0.0", "Require": {"Foo": "<=1.0.0", "Bar": ">0.0.0"} }]
|
||||
}]
|
||||
`
|
||||
var all PluginPackages
|
||||
err := json5.Unmarshal([]byte(js), &all)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
selected, err := all.Resolve(PluginVersions{}, PluginDependencies{
|
||||
&PluginDependency{"Bar", semver.MustParseRange(">=1.0.0")},
|
||||
})
|
||||
|
||||
check := func(name, version string) {
|
||||
v := selected.find(name)
|
||||
expected := semver.MustParse(version)
|
||||
if v == nil {
|
||||
t.Errorf("Failed to resolve %s", name)
|
||||
} else if expected.NE(v.Version) {
|
||||
t.Errorf("%s resolved in wrong version %v", name, v)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
check("Foo", "1.5.0")
|
||||
check("Bar", "1.0.0")
|
||||
}
|
||||
|
||||
selected, err = all.Resolve(PluginVersions{}, PluginDependencies{
|
||||
&PluginDependency{"Unresolvable", semver.MustParseRange(">0.0.0")},
|
||||
})
|
||||
if err == nil {
|
||||
t.Error("Unresolvable package resolved:", selected)
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMissingName = errors.New("Missing or empty name field")
|
||||
ErrMissingDesc = errors.New("Missing or empty description field")
|
||||
ErrMissingSite = errors.New("Missing or empty website field")
|
||||
ErrMissingInstall = errors.New("Missing or empty install field")
|
||||
ErrMissingVstr = errors.New("Missing or empty versions field")
|
||||
ErrMissingRequire = errors.New("Missing or empty require field")
|
||||
ErrMissingName = errors.New("Missing or empty name field")
|
||||
ErrMissingDesc = errors.New("Missing or empty description field")
|
||||
ErrMissingSite = errors.New("Missing or empty website field")
|
||||
)
|
||||
|
||||
// PluginInfo contains all the needed info about a plugin
|
||||
@@ -27,19 +24,16 @@ var (
|
||||
// Vstr: version
|
||||
// Require: list of dependencies and requirements
|
||||
type PluginInfo struct {
|
||||
Name string `json:"name"`
|
||||
Desc string `json:"description"`
|
||||
Site string `json:"website"`
|
||||
Install string `json:"install"`
|
||||
Vstr string `json:"version"`
|
||||
Require []string `json:"require"`
|
||||
Name string `json:"Name"`
|
||||
Desc string `json:"Description"`
|
||||
Site string `json:"Website"`
|
||||
}
|
||||
|
||||
// NewPluginInfo parses a JSON input into a valid PluginInfo struct
|
||||
// Returns an error if there are any missing fields or any invalid fields
|
||||
// There are no optional fields in a plugin info json file
|
||||
func NewPluginInfo(data []byte) (*PluginInfo, error) {
|
||||
var info PluginInfo
|
||||
var info []PluginInfo
|
||||
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
// dec.DisallowUnknownFields() // Force errors
|
||||
@@ -48,19 +42,5 @@ func NewPluginInfo(data []byte) (*PluginInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if len(info.Name) == 0 {
|
||||
// return nil, ErrMissingName
|
||||
// } else if len(info.Desc) == 0 {
|
||||
// return nil, ErrMissingDesc
|
||||
// } else if len(info.Site) == 0 {
|
||||
// return nil, ErrMissingSite
|
||||
// } else if len(info.Install) == 0 {
|
||||
// return nil, ErrMissingInstall
|
||||
// } else if len(info.Vstr) == 0 {
|
||||
// return nil, ErrMissingVstr
|
||||
// } else if len(info.Require) == 0 {
|
||||
// return nil, ErrMissingRequire
|
||||
// }
|
||||
|
||||
return &info, nil
|
||||
return &info[0], nil
|
||||
}
|
||||
|
||||
@@ -17,10 +17,13 @@ const (
|
||||
RTHelp = 2
|
||||
RTPlugin = 3
|
||||
RTSyntaxHeader = 4
|
||||
NumTypes = 5 // How many filetypes are there
|
||||
)
|
||||
|
||||
type RTFiletype byte
|
||||
var (
|
||||
NumTypes = 5 // How many filetypes are there
|
||||
)
|
||||
|
||||
type RTFiletype int
|
||||
|
||||
// RuntimeFile allows the program to read runtime data like colorschemes or syntax files
|
||||
type RuntimeFile interface {
|
||||
@@ -31,8 +34,21 @@ type RuntimeFile interface {
|
||||
}
|
||||
|
||||
// allFiles contains all available files, mapped by filetype
|
||||
var allFiles [NumTypes][]RuntimeFile
|
||||
var realFiles [NumTypes][]RuntimeFile
|
||||
var allFiles [][]RuntimeFile
|
||||
var realFiles [][]RuntimeFile
|
||||
|
||||
func init() {
|
||||
allFiles = make([][]RuntimeFile, NumTypes)
|
||||
realFiles = make([][]RuntimeFile, NumTypes)
|
||||
}
|
||||
|
||||
// NewRTFiletype creates a new RTFiletype
|
||||
func NewRTFiletype() int {
|
||||
NumTypes++
|
||||
allFiles = append(allFiles, []RuntimeFile{})
|
||||
realFiles = append(realFiles, []RuntimeFile{})
|
||||
return NumTypes - 1
|
||||
}
|
||||
|
||||
// some file on filesystem
|
||||
type realFile string
|
||||
@@ -176,18 +192,21 @@ func InitRuntimeFiles() {
|
||||
for _, f := range srcs {
|
||||
if strings.HasSuffix(f.Name(), ".lua") {
|
||||
p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
|
||||
} else if f.Name() == "info.json" {
|
||||
data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), "info.json"))
|
||||
} else if strings.HasSuffix(f.Name(), ".json") {
|
||||
data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p.Info, err = NewPluginInfo(data)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p.Info, _ = NewPluginInfo(data)
|
||||
p.Name = p.Info.Name
|
||||
}
|
||||
}
|
||||
|
||||
if !isID(p.Name) {
|
||||
log.Println("Invalid plugin name", p.Name)
|
||||
if !isID(p.Name) || len(p.Srcs) <= 0 {
|
||||
log.Println(p.Name, "is not a plugin")
|
||||
continue
|
||||
}
|
||||
Plugins = append(Plugins, p)
|
||||
@@ -205,17 +224,20 @@ func InitRuntimeFiles() {
|
||||
for _, f := range srcs {
|
||||
if strings.HasSuffix(f, ".lua") {
|
||||
p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))
|
||||
} else if f == "info.json" {
|
||||
data, err := Asset(filepath.Join(plugdir, d, "info.json"))
|
||||
} else if strings.HasSuffix(f, ".json") {
|
||||
data, err := Asset(filepath.Join(plugdir, d, f))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p.Info, err = NewPluginInfo(data)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p.Info, _ = NewPluginInfo(data)
|
||||
p.Name = p.Info.Name
|
||||
}
|
||||
}
|
||||
if !isID(p.Name) {
|
||||
log.Println("Invalid plugin name", p.Name)
|
||||
if !isID(p.Name) || len(p.Srcs) <= 0 {
|
||||
log.Println(p.Name, "is not a plugin")
|
||||
continue
|
||||
}
|
||||
Plugins = append(Plugins, p)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -3,15 +3,17 @@ package config
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/glob"
|
||||
"github.com/zyedidia/json5"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"golang.org/x/text/encoding/htmlindex"
|
||||
)
|
||||
|
||||
@@ -26,15 +28,20 @@ var (
|
||||
|
||||
// This is the raw parsed json
|
||||
parsedSettings map[string]interface{}
|
||||
|
||||
// ModifiedSettings is a map of settings which should be written to disk
|
||||
// because they have been modified by the user in this session
|
||||
ModifiedSettings map[string]bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
ModifiedSettings = make(map[string]bool)
|
||||
parsedSettings = make(map[string]interface{})
|
||||
}
|
||||
|
||||
// Options with validators
|
||||
var optionValidators = map[string]optionValidator{
|
||||
// "autosave": validateNonNegativeValue,
|
||||
"autosave": validateNonNegativeValue,
|
||||
"tabsize": validatePositiveValue,
|
||||
"scrollmargin": validateNonNegativeValue,
|
||||
"scrollspeed": validateNonNegativeValue,
|
||||
@@ -45,7 +52,7 @@ var optionValidators = map[string]optionValidator{
|
||||
}
|
||||
|
||||
func ReadSettings() error {
|
||||
filename := ConfigDir + "/settings.json"
|
||||
filename := filepath.Join(ConfigDir, "settings.json")
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
@@ -57,21 +64,50 @@ func ReadSettings() error {
|
||||
if err != nil {
|
||||
return errors.New("Error reading settings.json: " + err.Error())
|
||||
}
|
||||
|
||||
// check if autosave is a boolean and convert it to float if so
|
||||
if v, ok := parsedSettings["autosave"]; ok {
|
||||
s, ok := v.(bool)
|
||||
if ok {
|
||||
if s {
|
||||
parsedSettings["autosave"] = 8.0
|
||||
} else {
|
||||
parsedSettings["autosave"] = 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifySetting(option string, value reflect.Type, def reflect.Type) bool {
|
||||
var interfaceArr []interface{}
|
||||
switch option {
|
||||
case "pluginrepos", "pluginchannels":
|
||||
return value.AssignableTo(reflect.TypeOf(interfaceArr))
|
||||
default:
|
||||
return def.AssignableTo(value)
|
||||
}
|
||||
}
|
||||
|
||||
// InitGlobalSettings initializes the options map and sets all options to their default values
|
||||
// Must be called after ReadSettings
|
||||
func InitGlobalSettings() {
|
||||
func InitGlobalSettings() error {
|
||||
var err error
|
||||
GlobalSettings = DefaultGlobalSettings()
|
||||
|
||||
for k, v := range parsedSettings {
|
||||
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
||||
if _, ok := GlobalSettings[k]; ok && !verifySetting(k, reflect.TypeOf(v), reflect.TypeOf(GlobalSettings[k])) {
|
||||
err = errors.New(fmt.Sprintf("Global Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v), GlobalSettings[k], reflect.TypeOf(GlobalSettings[k])))
|
||||
continue
|
||||
}
|
||||
|
||||
GlobalSettings[k] = v
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// InitLocalSettings scans the json in settings.json and sets the options locally based
|
||||
@@ -84,6 +120,10 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
|
||||
if strings.HasPrefix(k, "ft:") {
|
||||
if settings["filetype"].(string) == k[3:] {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
|
||||
parseError = errors.New(fmt.Sprintf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1])))
|
||||
continue
|
||||
}
|
||||
settings[k1] = v1
|
||||
}
|
||||
}
|
||||
@@ -96,6 +136,10 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
|
||||
|
||||
if g.MatchString(path) {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
|
||||
parseError = errors.New(fmt.Sprintf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1])))
|
||||
continue
|
||||
}
|
||||
settings[k1] = v1
|
||||
}
|
||||
}
|
||||
@@ -109,8 +153,25 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
|
||||
func WriteSettings(filename string) error {
|
||||
var err error
|
||||
if _, e := os.Stat(ConfigDir); e == nil {
|
||||
defaults := DefaultGlobalSettings()
|
||||
|
||||
// remove any options froms parsedSettings that have since been marked as default
|
||||
for k, v := range parsedSettings {
|
||||
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
||||
cur, okcur := GlobalSettings[k]
|
||||
if def, ok := defaults[k]; ok && okcur && reflect.DeepEqual(cur, def) {
|
||||
delete(parsedSettings, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add any options to parsedSettings that have since been marked as non-default
|
||||
for k, v := range GlobalSettings {
|
||||
parsedSettings[k] = v
|
||||
if def, ok := defaults[k]; !ok || !reflect.DeepEqual(v, def) {
|
||||
if _, wr := ModifiedSettings[k]; wr {
|
||||
parsedSettings[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
txt, _ := json.MarshalIndent(parsedSettings, "", " ")
|
||||
@@ -119,12 +180,35 @@ func WriteSettings(filename string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// RegisterCommonOption creates a new option. This is meant to be called by plugins to add options.
|
||||
func RegisterCommonOption(name string, defaultvalue interface{}) error {
|
||||
// OverwriteSettings writes the current settings to settings.json and
|
||||
// resets any user configuration of local settings present in settings.json
|
||||
func OverwriteSettings(filename string) error {
|
||||
settings := make(map[string]interface{})
|
||||
|
||||
var err error
|
||||
if _, e := os.Stat(ConfigDir); e == nil {
|
||||
defaults := DefaultGlobalSettings()
|
||||
for k, v := range GlobalSettings {
|
||||
if def, ok := defaults[k]; !ok || !reflect.DeepEqual(v, def) {
|
||||
if _, wr := ModifiedSettings[k]; wr {
|
||||
settings[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
txt, _ := json.MarshalIndent(settings, "", " ")
|
||||
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// RegisterCommonOptionPlug creates a new option (called pl.name). This is meant to be called by plugins to add options.
|
||||
func RegisterCommonOptionPlug(pl string, name string, defaultvalue interface{}) error {
|
||||
name = pl + "." + name
|
||||
if v, ok := GlobalSettings[name]; !ok {
|
||||
defaultCommonSettings[name] = defaultvalue
|
||||
GlobalSettings[name] = defaultvalue
|
||||
err := WriteSettings(ConfigDir + "/settings.json")
|
||||
err := WriteSettings(filepath.Join(ConfigDir, "settings.json"))
|
||||
if err != nil {
|
||||
return errors.New("Error writing settings.json file: " + err.Error())
|
||||
}
|
||||
@@ -134,16 +218,22 @@ func RegisterCommonOption(name string, defaultvalue interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterGlobalOptionPlug creates a new global-only option (named pl.name)
|
||||
func RegisterGlobalOptionPlug(pl string, name string, defaultvalue interface{}) error {
|
||||
return RegisterGlobalOption(pl+"."+name, defaultvalue)
|
||||
}
|
||||
|
||||
// RegisterGlobalOption creates a new global-only option
|
||||
func RegisterGlobalOption(name string, defaultvalue interface{}) error {
|
||||
if v, ok := GlobalSettings[name]; !ok {
|
||||
defaultGlobalSettings[name] = defaultvalue
|
||||
DefaultGlobalOnlySettings[name] = defaultvalue
|
||||
GlobalSettings[name] = defaultvalue
|
||||
err := WriteSettings(ConfigDir + "/settings.json")
|
||||
err := WriteSettings(filepath.Join(ConfigDir, "settings.json"))
|
||||
if err != nil {
|
||||
return errors.New("Error writing settings.json file: " + err.Error())
|
||||
}
|
||||
} else {
|
||||
defaultGlobalSettings[name] = v
|
||||
DefaultGlobalOnlySettings[name] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -155,13 +245,15 @@ func GetGlobalOption(name string) interface{} {
|
||||
|
||||
var defaultCommonSettings = map[string]interface{}{
|
||||
"autoindent": true,
|
||||
"autosu": false,
|
||||
"backup": true,
|
||||
"basename": false,
|
||||
"colorcolumn": float64(0),
|
||||
"cursorline": true,
|
||||
"diffgutter": false,
|
||||
"encoding": "utf-8",
|
||||
"eofnewline": false,
|
||||
"fastdirty": true,
|
||||
"eofnewline": true,
|
||||
"fastdirty": false,
|
||||
"fileformat": "unix",
|
||||
"filetype": "unknown",
|
||||
"ignorecase": false,
|
||||
@@ -172,6 +264,7 @@ var defaultCommonSettings = map[string]interface{}{
|
||||
"readonly": false,
|
||||
"rmtrailingws": false,
|
||||
"ruler": true,
|
||||
"relativeruler": false,
|
||||
"savecursor": false,
|
||||
"saveundo": false,
|
||||
"scrollbar": false,
|
||||
@@ -181,7 +274,7 @@ var defaultCommonSettings = map[string]interface{}{
|
||||
"softwrap": false,
|
||||
"splitbottom": true,
|
||||
"splitright": true,
|
||||
"statusformatl": "$(filename) $(modified)($(line),$(col)) | ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
|
||||
"statusformatl": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
|
||||
"statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
|
||||
"statusline": true,
|
||||
"syntax": true,
|
||||
@@ -214,15 +307,21 @@ func DefaultCommonSettings() map[string]interface{} {
|
||||
|
||||
// a list of settings that should only be globally modified and their
|
||||
// default values
|
||||
var defaultGlobalSettings = map[string]interface{}{
|
||||
// "autosave": float64(0),
|
||||
"colorscheme": "default",
|
||||
"infobar": true,
|
||||
"keymenu": false,
|
||||
"mouse": true,
|
||||
"paste": false,
|
||||
"savehistory": true,
|
||||
"sucmd": "sudo",
|
||||
var DefaultGlobalOnlySettings = map[string]interface{}{
|
||||
"autosave": float64(0),
|
||||
"colorscheme": "default",
|
||||
"divchars": "|-",
|
||||
"divreverse": true,
|
||||
"infobar": true,
|
||||
"keymenu": false,
|
||||
"mouse": true,
|
||||
"parsecursor": false,
|
||||
"paste": false,
|
||||
"savehistory": true,
|
||||
"sucmd": "sudo",
|
||||
"pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
|
||||
"pluginrepos": []string{},
|
||||
"xterm": false,
|
||||
}
|
||||
|
||||
// a list of settings that should never be globally modified
|
||||
@@ -238,7 +337,7 @@ func DefaultGlobalSettings() map[string]interface{} {
|
||||
for k, v := range defaultCommonSettings {
|
||||
globalsettings[k] = v
|
||||
}
|
||||
for k, v := range defaultGlobalSettings {
|
||||
for k, v := range DefaultGlobalOnlySettings {
|
||||
globalsettings[k] = v
|
||||
}
|
||||
return globalsettings
|
||||
@@ -251,7 +350,7 @@ func DefaultAllSettings() map[string]interface{} {
|
||||
for k, v := range defaultCommonSettings {
|
||||
allsettings[k] = v
|
||||
}
|
||||
for k, v := range defaultGlobalSettings {
|
||||
for k, v := range DefaultGlobalOnlySettings {
|
||||
allsettings[k] = v
|
||||
}
|
||||
return allsettings
|
||||
|
||||
@@ -2,13 +2,12 @@ package display
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"unicode/utf8"
|
||||
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -73,9 +72,9 @@ func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style)
|
||||
curStyle := config.DefStyle
|
||||
var s *tcell.Style
|
||||
for len(b) > 0 {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
r, _, size := util.DecodeCharacter(b)
|
||||
|
||||
curStyle, found := w.getStyle(curStyle, bloc, r)
|
||||
curStyle, found := w.getStyle(curStyle, bloc)
|
||||
if found {
|
||||
s = &curStyle
|
||||
}
|
||||
@@ -136,9 +135,6 @@ func (w *BufWindow) Relocate() bool {
|
||||
if w.drawStatus {
|
||||
h--
|
||||
}
|
||||
if b.LinesNum() <= h {
|
||||
height = w.Height
|
||||
}
|
||||
ret := false
|
||||
activeC := w.Buf.GetActiveCursor()
|
||||
cy := activeC.Y
|
||||
@@ -212,6 +208,9 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
|
||||
if hasMessage {
|
||||
vloc.X += 2
|
||||
}
|
||||
if b.Settings["diffgutter"].(bool) {
|
||||
vloc.X++
|
||||
}
|
||||
if b.Settings["ruler"].(bool) {
|
||||
vloc.X += maxLineNumLength + 1
|
||||
}
|
||||
@@ -237,7 +236,7 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
|
||||
return bloc
|
||||
}
|
||||
|
||||
r, size := utf8.DecodeRune(line)
|
||||
r, _, size := util.DecodeCharacter(line)
|
||||
draw()
|
||||
width := 0
|
||||
|
||||
@@ -272,11 +271,7 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
|
||||
if vloc.Y >= bufHeight {
|
||||
break
|
||||
}
|
||||
vloc.X = 0
|
||||
// This will draw an empty line number because the current line is wrapped
|
||||
if b.Settings["ruler"].(bool) {
|
||||
vloc.X += maxLineNumLength + 1
|
||||
}
|
||||
vloc.X = w.gutterOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -311,8 +306,43 @@ func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) {
|
||||
vloc.X++
|
||||
}
|
||||
|
||||
func (w *BufWindow) drawDiffGutter(backgroundStyle tcell.Style, softwrapped bool, vloc *buffer.Loc, bloc *buffer.Loc) {
|
||||
symbol := ' '
|
||||
styleName := ""
|
||||
|
||||
switch w.Buf.DiffStatus(bloc.Y) {
|
||||
case buffer.DSAdded:
|
||||
symbol = '\u258C' // Left half block
|
||||
styleName = "diff-added"
|
||||
case buffer.DSModified:
|
||||
symbol = '\u258C' // Left half block
|
||||
styleName = "diff-modified"
|
||||
case buffer.DSDeletedAbove:
|
||||
if !softwrapped {
|
||||
symbol = '\u2594' // Upper one eighth block
|
||||
styleName = "diff-deleted"
|
||||
}
|
||||
}
|
||||
|
||||
style := backgroundStyle
|
||||
if s, ok := config.Colorscheme[styleName]; ok {
|
||||
foreground, _, _ := s.Decompose()
|
||||
style = style.Foreground(foreground)
|
||||
}
|
||||
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, symbol, nil, style)
|
||||
vloc.X++
|
||||
}
|
||||
|
||||
func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *buffer.Loc, bloc *buffer.Loc) {
|
||||
lineNum := strconv.Itoa(bloc.Y + 1)
|
||||
cursorLine := w.Buf.GetActiveCursor().Loc.Y
|
||||
var lineInt int
|
||||
if w.Buf.Settings["relativeruler"] == false || cursorLine == bloc.Y {
|
||||
lineInt = bloc.Y + 1
|
||||
} else {
|
||||
lineInt = bloc.Y - cursorLine
|
||||
}
|
||||
lineNum := strconv.Itoa(util.Abs(lineInt))
|
||||
|
||||
// Write the spaces before the line number if necessary
|
||||
for i := 0; i < maxLineNumLength-len(lineNum); i++ {
|
||||
@@ -336,7 +366,7 @@ func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxL
|
||||
|
||||
// getStyle returns the highlight style for the given character position
|
||||
// If there is no change to the current highlight style it just returns that
|
||||
func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) (tcell.Style, bool) {
|
||||
func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc) (tcell.Style, bool) {
|
||||
if group, ok := w.Buf.Match(bloc.Y)[bloc.X]; ok {
|
||||
s := config.GetColor(group.String())
|
||||
return s, true
|
||||
@@ -373,15 +403,22 @@ func (w *BufWindow) displayBuffer() {
|
||||
bufWidth--
|
||||
}
|
||||
|
||||
if b.Settings["syntax"].(bool) && b.SyntaxDef != nil {
|
||||
for _, r := range b.Modifications {
|
||||
final := -1
|
||||
for i := r.X; i <= r.Y; i++ {
|
||||
final = util.Max(b.Highlighter.ReHighlightStates(b, i), final)
|
||||
}
|
||||
b.Highlighter.HighlightMatches(b, r.X, final+1)
|
||||
if b.ModifiedThisFrame {
|
||||
if b.Settings["diffgutter"].(bool) {
|
||||
b.UpdateDiff(func(synchronous bool) {
|
||||
// If the diff was updated asynchronously, the outer call to
|
||||
// displayBuffer might already be completed and we need to
|
||||
// schedule a redraw in order to display the new diff.
|
||||
// Note that this cannot lead to an infinite recursion
|
||||
// because the modifications were cleared above so there won't
|
||||
// be another call to UpdateDiff when displayBuffer is called
|
||||
// during the redraw.
|
||||
if !synchronous {
|
||||
screen.Redraw()
|
||||
}
|
||||
})
|
||||
}
|
||||
b.ClearModifications()
|
||||
b.ModifiedThisFrame = false
|
||||
}
|
||||
|
||||
var matchingBraces []buffer.Loc
|
||||
@@ -398,12 +435,14 @@ func (w *BufWindow) displayBuffer() {
|
||||
r := c.RuneUnder(curX)
|
||||
rl := c.RuneUnder(curX - 1)
|
||||
if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
|
||||
mb, left := b.FindMatchingBrace(bp, curLoc)
|
||||
matchingBraces = append(matchingBraces, mb)
|
||||
if !left {
|
||||
matchingBraces = append(matchingBraces, curLoc)
|
||||
} else {
|
||||
matchingBraces = append(matchingBraces, curLoc.Move(-1, b))
|
||||
mb, left, found := b.FindMatchingBrace(bp, curLoc)
|
||||
if found {
|
||||
matchingBraces = append(matchingBraces, mb)
|
||||
if !left {
|
||||
matchingBraces = append(matchingBraces, curLoc)
|
||||
} else {
|
||||
matchingBraces = append(matchingBraces, curLoc.Move(-1, b))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,7 +455,11 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
curNumStyle := config.DefStyle
|
||||
if style, ok := config.Colorscheme["current-line-number"]; ok {
|
||||
curNumStyle = style
|
||||
if !b.Settings["cursorline"].(bool) {
|
||||
curNumStyle = lineNumStyle
|
||||
} else {
|
||||
curNumStyle = style
|
||||
}
|
||||
}
|
||||
|
||||
// We need to know the string length of the largest line number
|
||||
@@ -440,18 +483,28 @@ func (w *BufWindow) displayBuffer() {
|
||||
for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
|
||||
vloc.X = 0
|
||||
|
||||
currentLine := false
|
||||
for _, c := range cursors {
|
||||
if bloc.Y == c.Y && w.active {
|
||||
currentLine = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
s := lineNumStyle
|
||||
if currentLine {
|
||||
s = curNumStyle
|
||||
}
|
||||
|
||||
if hasMessage {
|
||||
w.drawGutter(&vloc, &bloc)
|
||||
}
|
||||
|
||||
if b.Settings["diffgutter"].(bool) {
|
||||
w.drawDiffGutter(s, false, &vloc, &bloc)
|
||||
}
|
||||
|
||||
if b.Settings["ruler"].(bool) {
|
||||
s := lineNumStyle
|
||||
for _, c := range cursors {
|
||||
if bloc.Y == c.Y && w.active {
|
||||
s = curNumStyle
|
||||
break
|
||||
}
|
||||
}
|
||||
w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc)
|
||||
}
|
||||
|
||||
@@ -463,7 +516,7 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
bloc.X = bslice
|
||||
|
||||
draw := func(r rune, style tcell.Style, showcursor bool) {
|
||||
draw := func(r rune, combc []rune, style tcell.Style, showcursor bool) {
|
||||
if nColsBeforeStart <= 0 {
|
||||
for _, c := range cursors {
|
||||
if c.HasSelection() &&
|
||||
@@ -509,7 +562,7 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
|
||||
if s, ok := config.Colorscheme["color-column"]; ok {
|
||||
if colorcolumn != 0 && vloc.X-w.gutterOffset == colorcolumn {
|
||||
if colorcolumn != 0 && vloc.X-w.gutterOffset+w.StartCol == colorcolumn {
|
||||
fg, _, _ := s.Decompose()
|
||||
style = style.Background(fg)
|
||||
}
|
||||
@@ -521,7 +574,7 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
}
|
||||
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style)
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, combc, style)
|
||||
|
||||
if showcursor {
|
||||
for _, c := range cursors {
|
||||
@@ -537,10 +590,11 @@ func (w *BufWindow) displayBuffer() {
|
||||
|
||||
totalwidth := w.StartCol - nColsBeforeStart
|
||||
for len(line) > 0 {
|
||||
r, size := utf8.DecodeRune(line)
|
||||
curStyle, _ = w.getStyle(curStyle, bloc, r)
|
||||
r, combc, size := util.DecodeCharacter(line)
|
||||
|
||||
draw(r, curStyle, true)
|
||||
curStyle, _ = w.getStyle(curStyle, bloc)
|
||||
|
||||
draw(r, combc, curStyle, true)
|
||||
|
||||
width := 0
|
||||
|
||||
@@ -557,7 +611,7 @@ func (w *BufWindow) displayBuffer() {
|
||||
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
|
||||
if width > 1 {
|
||||
for i := 1; i < width; i++ {
|
||||
draw(char, curStyle, false)
|
||||
draw(char, nil, curStyle, false)
|
||||
}
|
||||
}
|
||||
bloc.X++
|
||||
@@ -575,6 +629,13 @@ func (w *BufWindow) displayBuffer() {
|
||||
break
|
||||
}
|
||||
vloc.X = 0
|
||||
if hasMessage {
|
||||
w.drawGutter(&vloc, &bloc)
|
||||
}
|
||||
if b.Settings["diffgutter"].(bool) {
|
||||
w.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)
|
||||
}
|
||||
|
||||
// This will draw an empty line number because the current line is wrapped
|
||||
if b.Settings["ruler"].(bool) {
|
||||
w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc)
|
||||
@@ -596,7 +657,7 @@ func (w *BufWindow) displayBuffer() {
|
||||
for i := vloc.X; i < bufWidth; i++ {
|
||||
curStyle := style
|
||||
if s, ok := config.Colorscheme["color-column"]; ok {
|
||||
if colorcolumn != 0 && i-w.gutterOffset == colorcolumn {
|
||||
if colorcolumn != 0 && i-w.gutterOffset+w.StartCol == colorcolumn {
|
||||
fg, _, _ := s.Decompose()
|
||||
curStyle = style.Background(fg)
|
||||
}
|
||||
@@ -605,7 +666,8 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
|
||||
if vloc.X != bufWidth {
|
||||
draw(' ', curStyle, true)
|
||||
// Display newline within a selection
|
||||
draw(' ', nil, config.DefStyle, true)
|
||||
}
|
||||
|
||||
bloc.X = w.StartCol
|
||||
@@ -628,8 +690,27 @@ func (w *BufWindow) displayStatusLine() {
|
||||
w.sline.Display()
|
||||
} else if w.Y+w.Height != infoY {
|
||||
w.drawStatus = true
|
||||
|
||||
divchars := config.GetGlobalOption("divchars").(string)
|
||||
if util.CharacterCountInString(divchars) != 2 {
|
||||
divchars = "|-"
|
||||
}
|
||||
|
||||
_, _, size := util.DecodeCharacterInString(divchars)
|
||||
divchar, combc, _ := util.DecodeCharacterInString(divchars[size:])
|
||||
|
||||
dividerStyle := config.DefStyle
|
||||
if style, ok := config.Colorscheme["divider"]; ok {
|
||||
dividerStyle = style
|
||||
}
|
||||
|
||||
divreverse := config.GetGlobalOption("divreverse").(bool)
|
||||
if divreverse {
|
||||
dividerStyle = dividerStyle.Reverse(true)
|
||||
}
|
||||
|
||||
for x := w.X; x < w.X+w.Width; x++ {
|
||||
screen.SetContent(x, w.Y+w.Height-1, '-', nil, config.DefStyle.Reverse(true))
|
||||
screen.SetContent(x, w.Y+w.Height-1, divchar, combc, dividerStyle)
|
||||
}
|
||||
} else {
|
||||
w.drawStatus = false
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/info"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/info"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
type InfoWindow struct {
|
||||
*info.InfoBuf
|
||||
*View
|
||||
|
||||
hscroll int
|
||||
}
|
||||
|
||||
func (i *InfoWindow) errStyle() tcell.Style {
|
||||
@@ -68,7 +68,7 @@ func (i *InfoWindow) IsActive() bool { return true }
|
||||
func (i *InfoWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {
|
||||
c := i.Buffer.GetActiveCursor()
|
||||
l := i.Buffer.LineBytes(0)
|
||||
n := utf8.RuneCountInString(i.Msg)
|
||||
n := util.CharacterCountInString(i.Msg)
|
||||
return buffer.Loc{c.GetCharPosInLine(l, vloc.X-n), 0}
|
||||
}
|
||||
|
||||
@@ -84,13 +84,13 @@ func (i *InfoWindow) displayBuffer() {
|
||||
activeC := b.GetActiveCursor()
|
||||
|
||||
blocX := 0
|
||||
vlocX := utf8.RuneCountInString(i.Msg)
|
||||
vlocX := util.CharacterCountInString(i.Msg)
|
||||
|
||||
tabsize := 4
|
||||
line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, blocX, tabsize)
|
||||
blocX = bslice
|
||||
|
||||
draw := func(r rune, style tcell.Style) {
|
||||
draw := func(r rune, combc []rune, style tcell.Style) {
|
||||
if nColsBeforeStart <= 0 {
|
||||
bloc := buffer.Loc{X: blocX, Y: 0}
|
||||
if activeC.HasSelection() &&
|
||||
@@ -110,8 +110,9 @@ func (i *InfoWindow) displayBuffer() {
|
||||
c := r
|
||||
if j > 0 {
|
||||
c = ' '
|
||||
combc = nil
|
||||
}
|
||||
screen.SetContent(vlocX, i.Y, c, nil, style)
|
||||
screen.SetContent(vlocX, i.Y, c, combc, style)
|
||||
}
|
||||
vlocX++
|
||||
}
|
||||
@@ -120,13 +121,11 @@ func (i *InfoWindow) displayBuffer() {
|
||||
|
||||
totalwidth := blocX - nColsBeforeStart
|
||||
for len(line) > 0 {
|
||||
if activeC.X == blocX {
|
||||
screen.ShowCursor(vlocX, i.Y)
|
||||
}
|
||||
curVX := vlocX
|
||||
curBX := blocX
|
||||
r, combc, size := util.DecodeCharacter(line)
|
||||
|
||||
r, size := utf8.DecodeRune(line)
|
||||
|
||||
draw(r, i.defStyle())
|
||||
draw(r, combc, i.defStyle())
|
||||
|
||||
width := 0
|
||||
|
||||
@@ -146,9 +145,12 @@ func (i *InfoWindow) displayBuffer() {
|
||||
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
|
||||
if width > 1 {
|
||||
for j := 1; j < width; j++ {
|
||||
draw(char, i.defStyle())
|
||||
draw(char, nil, i.defStyle())
|
||||
}
|
||||
}
|
||||
if activeC.X == curBX {
|
||||
screen.ShowCursor(curVX, i.Y)
|
||||
}
|
||||
totalwidth += width
|
||||
if vlocX >= i.Width {
|
||||
break
|
||||
@@ -175,13 +177,44 @@ func (i *InfoWindow) displayKeyMenu() {
|
||||
}
|
||||
}
|
||||
|
||||
func (i *InfoWindow) Display() {
|
||||
func (i *InfoWindow) totalSize() int {
|
||||
sum := 0
|
||||
for _, n := range i.Suggestions {
|
||||
sum += runewidth.StringWidth(n) + 1
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func (i *InfoWindow) scrollToSuggestion() {
|
||||
x := 0
|
||||
if config.GetGlobalOption("keymenu").(bool) {
|
||||
i.displayKeyMenu()
|
||||
s := i.totalSize()
|
||||
|
||||
for j, n := range i.Suggestions {
|
||||
c := util.CharacterCountInString(n)
|
||||
if j == i.CurSuggestion {
|
||||
if x+c >= i.hscroll+i.Width {
|
||||
i.hscroll = util.Clamp(x+c+1-i.Width, 0, s-i.Width)
|
||||
} else if x < i.hscroll {
|
||||
i.hscroll = util.Clamp(x-1, 0, s-i.Width)
|
||||
}
|
||||
break
|
||||
}
|
||||
x += c + 1
|
||||
}
|
||||
|
||||
if s-i.Width <= 0 {
|
||||
i.hscroll = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (i *InfoWindow) Display() {
|
||||
if i.HasPrompt || config.GlobalSettings["infobar"].(bool) {
|
||||
i.Clear()
|
||||
x := 0
|
||||
if config.GetGlobalOption("keymenu").(bool) {
|
||||
i.displayKeyMenu()
|
||||
}
|
||||
|
||||
if !i.HasPrompt && !i.HasMessage && !i.HasError {
|
||||
return
|
||||
}
|
||||
@@ -204,6 +237,11 @@ func (i *InfoWindow) Display() {
|
||||
}
|
||||
|
||||
if i.HasSuggestions && len(i.Suggestions) > 1 {
|
||||
i.scrollToSuggestion()
|
||||
|
||||
x := -i.hscroll
|
||||
done := false
|
||||
|
||||
statusLineStyle := config.DefStyle.Reverse(true)
|
||||
if style, ok := config.Colorscheme["statusline"]; ok {
|
||||
statusLineStyle = style
|
||||
@@ -212,29 +250,43 @@ func (i *InfoWindow) Display() {
|
||||
if config.GetGlobalOption("keymenu").(bool) {
|
||||
keymenuOffset = len(keydisplay)
|
||||
}
|
||||
x := 0
|
||||
|
||||
draw := func(r rune, s tcell.Style) {
|
||||
y := i.Y - keymenuOffset - 1
|
||||
rw := runewidth.RuneWidth(r)
|
||||
for j := 0; j < rw; j++ {
|
||||
c := r
|
||||
if j > 0 {
|
||||
c = ' '
|
||||
}
|
||||
|
||||
if x == i.Width-1 && !done {
|
||||
screen.SetContent(i.Width-1, y, '>', nil, s)
|
||||
x++
|
||||
break
|
||||
} else if x == 0 && i.hscroll > 0 {
|
||||
screen.SetContent(0, y, '<', nil, s)
|
||||
} else if x >= 0 && x < i.Width {
|
||||
screen.SetContent(x, y, c, nil, s)
|
||||
}
|
||||
x++
|
||||
}
|
||||
}
|
||||
|
||||
for j, s := range i.Suggestions {
|
||||
style := statusLineStyle
|
||||
if i.CurSuggestion == j {
|
||||
style = style.Reverse(true)
|
||||
}
|
||||
for _, r := range s {
|
||||
screen.SetContent(x, i.Y-keymenuOffset-1, r, nil, style)
|
||||
x++
|
||||
if x >= i.Width {
|
||||
return
|
||||
}
|
||||
}
|
||||
screen.SetContent(x, i.Y-keymenuOffset-1, ' ', nil, statusLineStyle)
|
||||
x++
|
||||
if x >= i.Width {
|
||||
return
|
||||
draw(r, style)
|
||||
// screen.SetContent(x, i.Y-keymenuOffset-1, r, nil, style)
|
||||
}
|
||||
draw(' ', statusLineStyle)
|
||||
}
|
||||
|
||||
for x < i.Width {
|
||||
screen.SetContent(x, i.Y-keymenuOffset-1, ' ', nil, statusLineStyle)
|
||||
x++
|
||||
draw(' ', statusLineStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,21 +3,19 @@ package display
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// StatusLine represents the information line at the bottom
|
||||
@@ -32,9 +30,6 @@ type StatusLine struct {
|
||||
|
||||
var statusInfo = map[string]func(*buffer.Buffer) string{
|
||||
"filename": func(b *buffer.Buffer) string {
|
||||
if b.Settings["basename"].(bool) {
|
||||
return path.Base(b.GetName())
|
||||
}
|
||||
return b.GetName()
|
||||
},
|
||||
"line": func(b *buffer.Buffer) string {
|
||||
@@ -47,6 +42,9 @@ var statusInfo = map[string]func(*buffer.Buffer) string{
|
||||
if b.Modified() {
|
||||
return "+ "
|
||||
}
|
||||
if b.Type.Readonly {
|
||||
return "[ro] "
|
||||
}
|
||||
return ""
|
||||
},
|
||||
}
|
||||
@@ -169,34 +167,36 @@ func (s *StatusLine) Display() {
|
||||
statusLineStyle = style
|
||||
}
|
||||
|
||||
leftLen := util.StringWidth(leftText, utf8.RuneCount(leftText), 1)
|
||||
rightLen := util.StringWidth(rightText, utf8.RuneCount(rightText), 1)
|
||||
leftLen := util.StringWidth(leftText, util.CharacterCount(leftText), 1)
|
||||
rightLen := util.StringWidth(rightText, util.CharacterCount(rightText), 1)
|
||||
|
||||
winX := s.win.X
|
||||
for x := 0; x < s.win.Width; x++ {
|
||||
if x < leftLen {
|
||||
r, size := utf8.DecodeRune(leftText)
|
||||
r, combc, size := util.DecodeCharacter(leftText)
|
||||
leftText = leftText[size:]
|
||||
rw := runewidth.RuneWidth(r)
|
||||
for j := 0; j < rw; j++ {
|
||||
c := r
|
||||
if j > 0 {
|
||||
c = ' '
|
||||
combc = nil
|
||||
x++
|
||||
}
|
||||
screen.SetContent(winX+x, y, c, nil, statusLineStyle)
|
||||
screen.SetContent(winX+x, y, c, combc, statusLineStyle)
|
||||
}
|
||||
} else if x >= s.win.Width-rightLen && x < rightLen+s.win.Width-rightLen {
|
||||
r, size := utf8.DecodeRune(rightText)
|
||||
r, combc, size := util.DecodeCharacter(rightText)
|
||||
rightText = rightText[size:]
|
||||
rw := runewidth.RuneWidth(r)
|
||||
for j := 0; j < rw; j++ {
|
||||
c := r
|
||||
if j > 0 {
|
||||
c = ' '
|
||||
combc = nil
|
||||
x++
|
||||
}
|
||||
screen.SetContent(winX+x, y, c, nil, statusLineStyle)
|
||||
screen.SetContent(winX+x, y, c, combc, statusLineStyle)
|
||||
}
|
||||
} else {
|
||||
screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
|
||||
|
||||
@@ -1,32 +1,30 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
type TabWindow struct {
|
||||
Names []string
|
||||
active int
|
||||
Y int
|
||||
width int
|
||||
Width int
|
||||
hscroll int
|
||||
}
|
||||
|
||||
func NewTabWindow(w int, y int) *TabWindow {
|
||||
tw := new(TabWindow)
|
||||
tw.width = w
|
||||
tw.Width = w
|
||||
tw.Y = y
|
||||
return tw
|
||||
}
|
||||
|
||||
func (w *TabWindow) Resize(width, height int) {
|
||||
w.width = width
|
||||
w.Width = width
|
||||
}
|
||||
|
||||
func (w *TabWindow) LocFromVisual(vloc buffer.Loc) int {
|
||||
@@ -34,13 +32,13 @@ func (w *TabWindow) LocFromVisual(vloc buffer.Loc) int {
|
||||
|
||||
for i, n := range w.Names {
|
||||
x++
|
||||
s := utf8.RuneCountInString(n)
|
||||
s := util.CharacterCountInString(n)
|
||||
if vloc.Y == w.Y && vloc.X < x+s {
|
||||
return i
|
||||
}
|
||||
x += s
|
||||
x += 3
|
||||
if x >= w.width {
|
||||
if x >= w.Width {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -50,9 +48,9 @@ func (w *TabWindow) LocFromVisual(vloc buffer.Loc) int {
|
||||
func (w *TabWindow) Scroll(amt int) {
|
||||
w.hscroll += amt
|
||||
s := w.TotalSize()
|
||||
w.hscroll = util.Clamp(w.hscroll, 0, s-w.width)
|
||||
w.hscroll = util.Clamp(w.hscroll, 0, s-w.Width)
|
||||
|
||||
if s-w.width <= 0 {
|
||||
if s-w.Width <= 0 {
|
||||
w.hscroll = 0
|
||||
}
|
||||
}
|
||||
@@ -75,19 +73,19 @@ func (w *TabWindow) SetActive(a int) {
|
||||
s := w.TotalSize()
|
||||
|
||||
for i, n := range w.Names {
|
||||
c := utf8.RuneCountInString(n)
|
||||
c := util.CharacterCountInString(n)
|
||||
if i == a {
|
||||
if x+c >= w.hscroll+w.width {
|
||||
w.hscroll = util.Clamp(x+c+1-w.width, 0, s-w.width)
|
||||
if x+c >= w.hscroll+w.Width {
|
||||
w.hscroll = util.Clamp(x+c+1-w.Width, 0, s-w.Width)
|
||||
} else if x < w.hscroll {
|
||||
w.hscroll = util.Clamp(x-4, 0, s-w.width)
|
||||
w.hscroll = util.Clamp(x-4, 0, s-w.Width)
|
||||
}
|
||||
break
|
||||
}
|
||||
x += c + 4
|
||||
}
|
||||
|
||||
if s-w.width <= 0 {
|
||||
if s-w.Width <= 0 {
|
||||
w.hscroll = 0
|
||||
}
|
||||
}
|
||||
@@ -96,6 +94,11 @@ func (w *TabWindow) Display() {
|
||||
x := -w.hscroll
|
||||
done := false
|
||||
|
||||
tabBarStyle := config.DefStyle.Reverse(true)
|
||||
if style, ok := config.Colorscheme["tabbar"]; ok {
|
||||
tabBarStyle = style
|
||||
}
|
||||
|
||||
draw := func(r rune, n int) {
|
||||
for i := 0; i < n; i++ {
|
||||
rw := runewidth.RuneWidth(r)
|
||||
@@ -104,14 +107,14 @@ func (w *TabWindow) Display() {
|
||||
if j > 0 {
|
||||
c = ' '
|
||||
}
|
||||
if x == w.width-1 && !done {
|
||||
screen.SetContent(w.width-1, w.Y, '>', nil, config.DefStyle.Reverse(true))
|
||||
if x == w.Width-1 && !done {
|
||||
screen.SetContent(w.Width-1, w.Y, '>', nil, tabBarStyle)
|
||||
x++
|
||||
break
|
||||
} else if x == 0 && w.hscroll > 0 {
|
||||
screen.SetContent(0, w.Y, '<', nil, config.DefStyle.Reverse(true))
|
||||
} else if x >= 0 && x < w.width {
|
||||
screen.SetContent(x, w.Y, c, nil, config.DefStyle.Reverse(true))
|
||||
screen.SetContent(0, w.Y, '<', nil, tabBarStyle)
|
||||
} else if x >= 0 && x < w.Width {
|
||||
screen.SetContent(x, w.Y, c, nil, tabBarStyle)
|
||||
}
|
||||
x++
|
||||
}
|
||||
@@ -136,12 +139,12 @@ func (w *TabWindow) Display() {
|
||||
} else {
|
||||
draw(' ', 3)
|
||||
}
|
||||
if x >= w.width {
|
||||
if x >= w.Width {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if x < w.width {
|
||||
draw(' ', w.width-x)
|
||||
if x < w.Width {
|
||||
draw(' ', w.Width-x)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell"
|
||||
"github.com/zyedidia/terminal"
|
||||
)
|
||||
@@ -98,12 +97,12 @@ func (w *TermWindow) Display() {
|
||||
}
|
||||
|
||||
text := []byte(w.Name())
|
||||
textLen := utf8.RuneCount(text)
|
||||
textLen := util.CharacterCount(text)
|
||||
for x := 0; x < w.Width; x++ {
|
||||
if x < textLen {
|
||||
r, size := utf8.DecodeRune(text)
|
||||
r, combc, size := util.DecodeCharacter(text)
|
||||
text = text[size:]
|
||||
screen.SetContent(w.X+x, w.Y+w.Height, r, nil, statusLineStyle)
|
||||
screen.SetContent(w.X+x, w.Y+w.Height, r, combc, statusLineStyle)
|
||||
} else {
|
||||
screen.SetContent(w.X+x, w.Y+w.Height, ' ', nil, statusLineStyle)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/internal/views"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/views"
|
||||
)
|
||||
|
||||
type UIWindow struct {
|
||||
@@ -24,11 +25,23 @@ func (w *UIWindow) drawNode(n *views.Node) {
|
||||
dividerStyle = style
|
||||
}
|
||||
|
||||
divchars := config.GetGlobalOption("divchars").(string)
|
||||
if util.CharacterCountInString(divchars) != 2 {
|
||||
divchars = "|-"
|
||||
}
|
||||
|
||||
divchar, combc, _ := util.DecodeCharacterInString(divchars)
|
||||
|
||||
divreverse := config.GetGlobalOption("divreverse").(bool)
|
||||
if divreverse {
|
||||
dividerStyle = dividerStyle.Reverse(true)
|
||||
}
|
||||
|
||||
for i, c := range cs {
|
||||
if c.IsLeaf() && c.Kind == views.STVert {
|
||||
if i != len(cs)-1 {
|
||||
for h := 0; h < c.H; h++ {
|
||||
screen.SetContent(c.X+c.W, c.Y+h, '|', nil, dividerStyle.Reverse(true))
|
||||
screen.SetContent(c.X+c.W, c.Y+h, divchar, combc, dividerStyle)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
)
|
||||
|
||||
type View struct {
|
||||
|
||||
@@ -3,8 +3,9 @@ package info
|
||||
import (
|
||||
"encoding/gob"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
)
|
||||
|
||||
// LoadHistory attempts to load user history from configDir/buffers/history
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
// The savehistory option must be on
|
||||
func (i *InfoBuf) LoadHistory() {
|
||||
if config.GetGlobalOption("savehistory").(bool) {
|
||||
file, err := os.Open(config.ConfigDir + "/buffers/history")
|
||||
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", "history"))
|
||||
defer file.Close()
|
||||
var decodedMap map[string][]string
|
||||
if err == nil {
|
||||
@@ -46,7 +47,7 @@ func (i *InfoBuf) SaveHistory() {
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Create(config.ConfigDir + "/buffers/history")
|
||||
file, err := os.Create(filepath.Join(config.ConfigDir, "buffers", "history"))
|
||||
defer file.Close()
|
||||
if err == nil {
|
||||
encoder := gob.NewEncoder(file)
|
||||
|
||||
@@ -2,14 +2,8 @@ package info
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
)
|
||||
|
||||
// The InfoBuf displays messages and other info at the bottom of the screen.
|
||||
@@ -134,63 +128,6 @@ func (i *InfoBuf) YNPrompt(prompt string, donecb func(bool, bool)) {
|
||||
i.YNCallback = donecb
|
||||
}
|
||||
|
||||
// PlugPrompt provides a plugin interface for calling "Prompt" with the appropriate Lua callbacks
|
||||
func (i *InfoBuf) PlugPrompt(prompt string, msg string, ptype string, eventcb string, donecb string) {
|
||||
eventLuaFn := strings.Split(eventcb, ".")
|
||||
doneLuaFn := strings.Split(donecb, ".")
|
||||
var luaEventcb func(string)
|
||||
var luaDonecb func(string, bool)
|
||||
|
||||
if len(eventLuaFn) == 2 {
|
||||
plName, plFn := doneLuaFn[0], doneLuaFn[1]
|
||||
pl := config.FindPlugin(plName)
|
||||
if pl != nil {
|
||||
luaEventcb = func(resp string) {
|
||||
_, err := pl.Call(plFn, luar.New(ulua.L, resp))
|
||||
if err != nil && err != config.ErrNoSuchFunction {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(doneLuaFn) == 2 {
|
||||
plName, plFn := doneLuaFn[0], doneLuaFn[1]
|
||||
pl := config.FindPlugin(plName)
|
||||
if pl != nil {
|
||||
luaDonecb = func(resp string, canceled bool) {
|
||||
_, err := pl.Call(plFn, luar.New(ulua.L, resp), luar.New(ulua.L, canceled))
|
||||
if err != nil && err != config.ErrNoSuchFunction {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i.Prompt(prompt, msg, ptype, luaEventcb, luaDonecb)
|
||||
}
|
||||
|
||||
// PlugYNPrompt provides a plugin interface for calling "YNPrompt" with the appropriate Lua callbacks
|
||||
func (i *InfoBuf) PlugYNPrompt(prompt string, donecb string) {
|
||||
doneLuaFn := strings.Split(donecb, ".")
|
||||
var luaDonecb func(bool, bool)
|
||||
|
||||
if len(doneLuaFn) == 2 {
|
||||
plName, plFn := doneLuaFn[0], doneLuaFn[1]
|
||||
pl := config.FindPlugin(plName)
|
||||
if pl != nil {
|
||||
luaDonecb = func(resp bool, canceled bool) {
|
||||
_, err := pl.Call(plFn, luar.New(ulua.L, resp), luar.New(ulua.L, canceled))
|
||||
if err != nil && err != config.ErrNoSuchFunction {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i.YNPrompt(prompt, luaDonecb)
|
||||
}
|
||||
|
||||
// DonePrompt finishes the current prompt and indicates whether or not it was canceled
|
||||
func (i *InfoBuf) DonePrompt(canceled bool) {
|
||||
hadYN := i.HasYN
|
||||
@@ -209,7 +146,7 @@ func (i *InfoBuf) DonePrompt(canceled bool) {
|
||||
h := i.History[i.PromptType]
|
||||
h[len(h)-1] = resp
|
||||
}
|
||||
i.PromptCallback = nil
|
||||
// i.PromptCallback = nil
|
||||
}
|
||||
i.Replace(i.Start(), i.End(), "")
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"unicode"
|
||||
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
"github.com/zyedidia/micro/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
@@ -21,9 +22,9 @@ var Screen tcell.Screen
|
||||
// The lock is necessary since the screen is polled on a separate thread
|
||||
var lock sync.Mutex
|
||||
|
||||
// DrawChan is a channel that will cause the screen to redraw when
|
||||
// drawChan is a channel that will cause the screen to redraw when
|
||||
// written to even if no event user event has occurred
|
||||
var DrawChan chan bool
|
||||
var drawChan chan bool
|
||||
|
||||
// Lock locks the screen lock
|
||||
func Lock() {
|
||||
@@ -37,7 +38,16 @@ func Unlock() {
|
||||
|
||||
// Redraw schedules a redraw with the draw channel
|
||||
func Redraw() {
|
||||
DrawChan <- true
|
||||
select {
|
||||
case drawChan <- true:
|
||||
default:
|
||||
// channel is full
|
||||
}
|
||||
}
|
||||
|
||||
// DrawChan returns the draw channel
|
||||
func DrawChan() chan bool {
|
||||
return drawChan
|
||||
}
|
||||
|
||||
type screenCell struct {
|
||||
@@ -88,6 +98,10 @@ func ShowCursor(x, y int) {
|
||||
// SetContent sets a cell at a point on the screen and makes sure that it is
|
||||
// synced with the last cursor location
|
||||
func SetContent(x, y int, mainc rune, combc []rune, style tcell.Style) {
|
||||
if !unicode.IsPrint(mainc) {
|
||||
mainc = '<27>'
|
||||
}
|
||||
|
||||
Screen.SetContent(x, y, mainc, combc, style)
|
||||
if util.FakeCursor && lastCursor.x == x && lastCursor.y == y {
|
||||
lastCursor.r = mainc
|
||||
@@ -118,7 +132,7 @@ func TempStart(screenWasNil bool) {
|
||||
|
||||
// Init creates and initializes the tcell screen
|
||||
func Init() {
|
||||
DrawChan = make(chan bool, 8)
|
||||
drawChan = make(chan bool, 8)
|
||||
|
||||
// Should we enable true color?
|
||||
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
|
||||
@@ -127,6 +141,12 @@ func Init() {
|
||||
os.Setenv("TCELL_TRUECOLOR", "disable")
|
||||
}
|
||||
|
||||
var oldTerm string
|
||||
if config.GetGlobalOption("xterm").(bool) {
|
||||
oldTerm = os.Getenv("TERM")
|
||||
os.Setenv("TERM", "xterm-256color")
|
||||
}
|
||||
|
||||
// Initilize tcell
|
||||
var err error
|
||||
Screen, err = tcell.NewScreen()
|
||||
@@ -140,6 +160,11 @@ func Init() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// restore TERM
|
||||
if config.GetGlobalOption("xterm").(bool) {
|
||||
os.Setenv("TERM", oldTerm)
|
||||
}
|
||||
|
||||
if config.GetGlobalOption("mouse").(bool) {
|
||||
Screen.EnableMouse()
|
||||
}
|
||||
|
||||
@@ -4,14 +4,6 @@ import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
)
|
||||
|
||||
var Jobs chan JobFunction
|
||||
@@ -32,7 +24,7 @@ func init() {
|
||||
// JobFunction is a representation of a job (this data structure is what is loaded
|
||||
// into the jobs channel)
|
||||
type JobFunction struct {
|
||||
Function func(string, ...interface{})
|
||||
Function func(string, []interface{})
|
||||
Output string
|
||||
Args []interface{}
|
||||
}
|
||||
@@ -41,7 +33,7 @@ type JobFunction struct {
|
||||
type CallbackFile struct {
|
||||
io.Writer
|
||||
|
||||
callback func(string, ...interface{})
|
||||
callback func(string, []interface{})
|
||||
args []interface{}
|
||||
}
|
||||
|
||||
@@ -55,23 +47,23 @@ func (f *CallbackFile) Write(data []byte) (int, error) {
|
||||
|
||||
// JobStart starts a shell command in the background with the given callbacks
|
||||
// It returns an *exec.Cmd as the job id
|
||||
func JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...interface{}) *exec.Cmd {
|
||||
func JobStart(cmd string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *exec.Cmd {
|
||||
return JobSpawn("sh", []string{"-c", cmd}, onStdout, onStderr, onExit, userargs...)
|
||||
}
|
||||
|
||||
// JobSpawn starts a process with args in the background with the given callbacks
|
||||
// It returns an *exec.Cmd as the job id
|
||||
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit string, userargs ...interface{}) *exec.Cmd {
|
||||
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *exec.Cmd {
|
||||
// Set up everything correctly if the functions have been provided
|
||||
proc := exec.Command(cmdName, cmdArgs...)
|
||||
var outbuf bytes.Buffer
|
||||
if onStdout != "" {
|
||||
proc.Stdout = &CallbackFile{&outbuf, luaFunctionJob(onStdout), userargs}
|
||||
if onStdout != nil {
|
||||
proc.Stdout = &CallbackFile{&outbuf, onStdout, userargs}
|
||||
} else {
|
||||
proc.Stdout = &outbuf
|
||||
}
|
||||
if onStderr != "" {
|
||||
proc.Stderr = &CallbackFile{&outbuf, luaFunctionJob(onStderr), userargs}
|
||||
if onStderr != nil {
|
||||
proc.Stderr = &CallbackFile{&outbuf, onStderr, userargs}
|
||||
} else {
|
||||
proc.Stderr = &outbuf
|
||||
}
|
||||
@@ -79,7 +71,7 @@ func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit strin
|
||||
go func() {
|
||||
// Run the process in the background and create the onExit callback
|
||||
proc.Run()
|
||||
jobFunc := JobFunction{luaFunctionJob(onExit), string(outbuf.Bytes()), userargs}
|
||||
jobFunc := JobFunction{onExit, string(outbuf.Bytes()), userargs}
|
||||
Jobs <- jobFunc
|
||||
}()
|
||||
|
||||
@@ -100,25 +92,3 @@ func JobSend(cmd *exec.Cmd, data string) {
|
||||
|
||||
stdin.Write([]byte(data))
|
||||
}
|
||||
|
||||
// luaFunctionJob returns a function that will call the given lua function
|
||||
// structured as a job call i.e. the job output and arguments are provided
|
||||
// to the lua function
|
||||
func luaFunctionJob(fn string) func(string, ...interface{}) {
|
||||
luaFn := strings.Split(fn, ".")
|
||||
if len(luaFn) <= 1 {
|
||||
return nil
|
||||
}
|
||||
plName, plFn := luaFn[0], luaFn[1]
|
||||
pl := config.FindPlugin(plName)
|
||||
if pl == nil {
|
||||
return nil
|
||||
}
|
||||
return func(output string, args ...interface{}) {
|
||||
luaArgs := []lua.LValue{luar.New(ulua.L, output), luar.New(ulua.L, args)}
|
||||
_, err := pl.Call(plFn, luaArgs...)
|
||||
if err != nil && err != config.ErrNoSuchFunction {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"strings"
|
||||
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
)
|
||||
|
||||
// ExecCommand executes a command using exec
|
||||
|
||||
@@ -2,19 +2,12 @@ package shell
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/internal/buffer"
|
||||
"github.com/zyedidia/micro/internal/config"
|
||||
ulua "github.com/zyedidia/micro/internal/lua"
|
||||
"github.com/zyedidia/micro/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/terminal"
|
||||
luar "layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
type TermType int
|
||||
@@ -76,7 +69,7 @@ func (t *Terminal) GetSelection(width int) string {
|
||||
}
|
||||
|
||||
// Start begins a new command in this terminal with a given view
|
||||
func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback string, userargs []interface{}) error {
|
||||
func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
|
||||
if len(execCmd) <= 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -95,27 +88,16 @@ func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback s
|
||||
t.Status = TTRunning
|
||||
t.title = execCmd[0] + ":" + strconv.Itoa(cmd.Process.Pid)
|
||||
t.wait = wait
|
||||
|
||||
luaFn := strings.Split(callback, ".")
|
||||
if len(luaFn) >= 2 {
|
||||
plName, plFn := luaFn[0], luaFn[1]
|
||||
pl := config.FindPlugin(plName)
|
||||
if pl != nil {
|
||||
t.callback = func(out string) {
|
||||
luaArgs := []lua.LValue{luar.New(ulua.L, out), luar.New(ulua.L, userargs)}
|
||||
_, err := pl.Call(plFn, luaArgs...)
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
t.callback = func(out string) {
|
||||
callback(out, userargs)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
err := Term.Parse()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "[Press enter to close]")
|
||||
Term.Write([]byte("Press enter to close"))
|
||||
screen.Redraw()
|
||||
break
|
||||
}
|
||||
screen.Redraw()
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// LuaRuneAt is a helper function for lua plugins to return the rune
|
||||
// at an index within a string
|
||||
func LuaRuneAt(str string, runeidx int) string {
|
||||
i := 0
|
||||
for len(str) > 0 {
|
||||
r, size := utf8.DecodeRuneInString(str)
|
||||
r, _, size := DecodeCharacterInString(str)
|
||||
|
||||
str = str[size:]
|
||||
|
||||
@@ -20,10 +18,11 @@ func LuaRuneAt(str string, runeidx int) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// LuaGetLeadingWhitespace returns the leading whitespace of a string (used by lua plugins)
|
||||
func LuaGetLeadingWhitespace(s string) string {
|
||||
ws := []byte{}
|
||||
for len(s) > 0 {
|
||||
r, size := utf8.DecodeRuneInString(s)
|
||||
r, _, size := DecodeCharacterInString(s)
|
||||
if r == ' ' || r == '\t' {
|
||||
ws = append(ws, byte(r))
|
||||
} else {
|
||||
@@ -35,7 +34,8 @@ func LuaGetLeadingWhitespace(s string) string {
|
||||
return string(ws)
|
||||
}
|
||||
|
||||
// LuaIsWordChar returns true if the first rune in a string is a word character
|
||||
func LuaIsWordChar(s string) bool {
|
||||
r, _ := utf8.DecodeRuneInString(s)
|
||||
r, _, _ := DecodeCharacterInString(s)
|
||||
return IsWordChar(r)
|
||||
}
|
||||
|
||||
86
internal/util/unicode.go
Normal file
86
internal/util/unicode.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Unicode is annoying. A "code point" (rune in Go-speak) may need up to
|
||||
// 4 bytes to represent it. In general, a code point will represent a
|
||||
// complete character, but this is not always the case. A character with
|
||||
// accents may be made up of multiple code points (the code point for the
|
||||
// original character, and additional code points for each accent/marking).
|
||||
// The functions below are meant to help deal with these additional "combining"
|
||||
// code points. In underlying operations (search, replace, etc...), micro will
|
||||
// treat a character with combining code points as just the original code point.
|
||||
// For rendering, micro will display the combining characters. It's not perfect
|
||||
// but it's pretty good.
|
||||
|
||||
// DecodeCharacter returns the next character from an array of bytes
|
||||
// A character is a rune along with any accompanying combining runes
|
||||
func DecodeCharacter(b []byte) (rune, []rune, int) {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
b = b[size:]
|
||||
c, s := utf8.DecodeRune(b)
|
||||
|
||||
var combc []rune
|
||||
for unicode.In(c, unicode.Mark) {
|
||||
combc = append(combc, c)
|
||||
size += s
|
||||
|
||||
b = b[s:]
|
||||
c, s = utf8.DecodeRune(b)
|
||||
}
|
||||
|
||||
return r, combc, size
|
||||
}
|
||||
|
||||
// DecodeCharacterInString returns the next character from a string
|
||||
// A character is a rune along with any accompanying combining runes
|
||||
func DecodeCharacterInString(str string) (rune, []rune, int) {
|
||||
r, size := utf8.DecodeRuneInString(str)
|
||||
str = str[size:]
|
||||
c, s := utf8.DecodeRuneInString(str)
|
||||
|
||||
var combc []rune
|
||||
for unicode.In(c, unicode.Mark) {
|
||||
combc = append(combc, c)
|
||||
size += s
|
||||
|
||||
str = str[s:]
|
||||
c, s = utf8.DecodeRuneInString(str)
|
||||
}
|
||||
|
||||
return r, combc, size
|
||||
}
|
||||
|
||||
// CharacterCount returns the number of characters in a byte array
|
||||
// Similar to utf8.RuneCount but for unicode characters
|
||||
func CharacterCount(b []byte) int {
|
||||
s := 0
|
||||
|
||||
for len(b) > 0 {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
if !unicode.In(r, unicode.Mark) {
|
||||
s++
|
||||
}
|
||||
|
||||
b = b[size:]
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// CharacterCount returns the number of characters in a string
|
||||
// Similar to utf8.RuneCountInString but for unicode characters
|
||||
func CharacterCountInString(str string) int {
|
||||
s := 0
|
||||
|
||||
for _, r := range str {
|
||||
if !unicode.In(r, unicode.Mark) {
|
||||
s++
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -12,7 +13,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/blang/semver"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
@@ -30,10 +30,13 @@ var (
|
||||
// CompileDate is the date this binary was compiled on
|
||||
CompileDate = "Unknown"
|
||||
// Debug logging
|
||||
Debug = "ON"
|
||||
Debug = "OFF"
|
||||
// FakeCursor is used to disable the terminal cursor and have micro
|
||||
// draw its own (enabled for windows consoles where the cursor is slow)
|
||||
FakeCursor = false
|
||||
|
||||
// Stdout is a buffer that is written to stdout when micro closes
|
||||
Stdout *bytes.Buffer
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -46,6 +49,7 @@ func init() {
|
||||
if runtime.GOOS == "windows" {
|
||||
FakeCursor = true
|
||||
}
|
||||
Stdout = new(bytes.Buffer)
|
||||
}
|
||||
|
||||
// SliceEnd returns a byte slice where the index is a rune index
|
||||
@@ -59,7 +63,7 @@ func SliceEnd(slc []byte, index int) []byte {
|
||||
return slc[totalSize:]
|
||||
}
|
||||
|
||||
_, size := utf8.DecodeRune(slc[totalSize:])
|
||||
_, _, size := DecodeCharacter(slc[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
@@ -77,7 +81,7 @@ func SliceEndStr(str string, index int) string {
|
||||
return str[totalSize:]
|
||||
}
|
||||
|
||||
_, size := utf8.DecodeRuneInString(str[totalSize:])
|
||||
_, _, size := DecodeCharacterInString(str[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
@@ -96,7 +100,7 @@ func SliceStart(slc []byte, index int) []byte {
|
||||
return slc[:totalSize]
|
||||
}
|
||||
|
||||
_, size := utf8.DecodeRune(slc[totalSize:])
|
||||
_, _, size := DecodeCharacter(slc[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
@@ -114,7 +118,7 @@ func SliceStartStr(str string, index int) string {
|
||||
return str[:totalSize]
|
||||
}
|
||||
|
||||
_, size := utf8.DecodeRuneInString(str[totalSize:])
|
||||
_, _, size := DecodeCharacterInString(str[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
@@ -130,7 +134,7 @@ func SliceVisualEnd(b []byte, n, tabsize int) ([]byte, int, int) {
|
||||
width := 0
|
||||
i := 0
|
||||
for len(b) > 0 {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
r, _, size := DecodeCharacter(b)
|
||||
|
||||
w := 0
|
||||
switch r {
|
||||
@@ -167,7 +171,7 @@ func StringWidth(b []byte, n, tabsize int) int {
|
||||
i := 0
|
||||
width := 0
|
||||
for len(b) > 0 {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
r, _, size := DecodeCharacter(b)
|
||||
b = b[size:]
|
||||
|
||||
switch r {
|
||||
@@ -260,7 +264,7 @@ func IsBytesWhitespace(b []byte) bool {
|
||||
// RunePos returns the rune index of a given byte index
|
||||
// Make sure the byte index is not between code points
|
||||
func RunePos(b []byte, i int) int {
|
||||
return utf8.RuneCount(b[:i])
|
||||
return CharacterCount(b[:i])
|
||||
}
|
||||
|
||||
// MakeRelative will attempt to make a relative path between path and base
|
||||
@@ -339,7 +343,7 @@ func EscapePath(path string) string {
|
||||
func GetLeadingWhitespace(b []byte) []byte {
|
||||
ws := []byte{}
|
||||
for len(b) > 0 {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
r, _, size := DecodeCharacter(b)
|
||||
if r == ' ' || r == '\t' {
|
||||
ws = append(ws, byte(r))
|
||||
} else {
|
||||
@@ -365,7 +369,7 @@ func GetCharPosInLine(b []byte, visualPos int, tabsize int) int {
|
||||
i := 0 // char pos
|
||||
width := 0 // string visual width
|
||||
for len(b) > 0 {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
r, _, size := DecodeCharacter(b)
|
||||
b = b[size:]
|
||||
|
||||
switch r {
|
||||
@@ -411,9 +415,18 @@ func Clamp(val, min, max int) int {
|
||||
}
|
||||
|
||||
func IsNonAlphaNumeric(c rune) bool {
|
||||
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
|
||||
return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '_'
|
||||
}
|
||||
|
||||
func IsAutocomplete(c rune) bool {
|
||||
return c == '.' || !IsNonAlphaNumeric(c)
|
||||
}
|
||||
|
||||
func ParseSpecial(s string) string {
|
||||
return strings.Replace(s, "\\t", "\t", -1)
|
||||
}
|
||||
|
||||
// String converts a byte array to a string (for lua plugins)
|
||||
func String(s []byte) string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
@@ -456,9 +456,9 @@ func (n *Node) unsplit(i int, h bool) {
|
||||
|
||||
// Unsplit deletes this split and resizes everything
|
||||
// else accordingly
|
||||
func (n *Node) Unsplit() {
|
||||
if !n.IsLeaf() {
|
||||
return
|
||||
func (n *Node) Unsplit() bool {
|
||||
if !n.IsLeaf() || n.parent == nil {
|
||||
return false
|
||||
}
|
||||
ind := 0
|
||||
for i, c := range n.parent.children {
|
||||
@@ -473,8 +473,9 @@ func (n *Node) Unsplit() {
|
||||
}
|
||||
|
||||
if n.parent.IsLeaf() {
|
||||
n.parent.Unsplit()
|
||||
return n.parent.Unsplit()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// String returns the string form of the node and all children (used for debugging)
|
||||
|
||||
@@ -3,7 +3,6 @@ package highlight
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func sliceStart(slc []byte, index int) []byte {
|
||||
@@ -15,7 +14,7 @@ func sliceStart(slc []byte, index int) []byte {
|
||||
return slc[totalSize:]
|
||||
}
|
||||
|
||||
_, size := utf8.DecodeRune(slc[totalSize:])
|
||||
_, _, size := DecodeCharacter(slc[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
@@ -32,7 +31,7 @@ func sliceEnd(slc []byte, index int) []byte {
|
||||
return slc[:totalSize]
|
||||
}
|
||||
|
||||
_, size := utf8.DecodeRune(slc[totalSize:])
|
||||
_, _, size := DecodeCharacter(slc[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
@@ -47,9 +46,9 @@ func runePos(p int, str []byte) int {
|
||||
return 0
|
||||
}
|
||||
if p >= len(str) {
|
||||
return utf8.RuneCount(str)
|
||||
return CharacterCount(str)
|
||||
}
|
||||
return utf8.RuneCount(str[:p])
|
||||
return CharacterCount(str[:p])
|
||||
}
|
||||
|
||||
func combineLineMatch(src, dst LineMatch) LineMatch {
|
||||
@@ -112,7 +111,7 @@ func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte, canMatchSt
|
||||
var strbytes []byte
|
||||
if skip != nil {
|
||||
strbytes = skip.ReplaceAllFunc(str, func(match []byte) []byte {
|
||||
res := make([]byte, utf8.RuneCount(match))
|
||||
res := make([]byte, CharacterCount(match))
|
||||
return res
|
||||
})
|
||||
} else {
|
||||
@@ -148,7 +147,7 @@ func findAllIndex(regex *regexp.Regexp, str []byte, canMatchStart, canMatchEnd b
|
||||
}
|
||||
|
||||
func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, curRegion *region, statesOnly bool) LineMatch {
|
||||
lineLen := utf8.RuneCount(line)
|
||||
lineLen := CharacterCount(line)
|
||||
if start == 0 {
|
||||
if !statesOnly {
|
||||
if _, ok := highlights[0]; !ok {
|
||||
@@ -236,7 +235,7 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
|
||||
}
|
||||
|
||||
func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, statesOnly bool) LineMatch {
|
||||
lineLen := utf8.RuneCount(line)
|
||||
lineLen := CharacterCount(line)
|
||||
if lineLen == 0 {
|
||||
if canMatchEnd {
|
||||
h.lastRegion = nil
|
||||
@@ -336,11 +335,11 @@ func (h *Highlighter) HighlightStates(input LineStates) {
|
||||
}
|
||||
}
|
||||
|
||||
// HighlightMatches sets the matches for each line in between startline and endline
|
||||
// HighlightMatches sets the matches for each line from startline to endline
|
||||
// It sets all other matches in the buffer to nil to conserve memory
|
||||
// This assumes that all the states are set correctly
|
||||
func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int) {
|
||||
for i := startline; i < endline; i++ {
|
||||
for i := startline; i <= endline; i++ {
|
||||
if i >= input.LinesNum() {
|
||||
break
|
||||
}
|
||||
|
||||
75
pkg/highlight/unicode.go
Normal file
75
pkg/highlight/unicode.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package highlight
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// DecodeCharacter returns the next character from an array of bytes
|
||||
// A character is a rune along with any accompanying combining runes
|
||||
func DecodeCharacter(b []byte) (rune, []rune, int) {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
b = b[size:]
|
||||
c, s := utf8.DecodeRune(b)
|
||||
|
||||
var combc []rune
|
||||
for unicode.In(c, unicode.Mark) {
|
||||
combc = append(combc, c)
|
||||
size += s
|
||||
|
||||
b = b[s:]
|
||||
c, s = utf8.DecodeRune(b)
|
||||
}
|
||||
|
||||
return r, combc, size
|
||||
}
|
||||
|
||||
// DecodeCharacterInString returns the next character from a string
|
||||
// A character is a rune along with any accompanying combining runes
|
||||
func DecodeCharacterInString(str string) (rune, []rune, int) {
|
||||
r, size := utf8.DecodeRuneInString(str)
|
||||
str = str[size:]
|
||||
c, s := utf8.DecodeRuneInString(str)
|
||||
|
||||
var combc []rune
|
||||
for unicode.In(c, unicode.Mark) {
|
||||
combc = append(combc, c)
|
||||
size += s
|
||||
|
||||
str = str[s:]
|
||||
c, s = utf8.DecodeRuneInString(str)
|
||||
}
|
||||
|
||||
return r, combc, size
|
||||
}
|
||||
|
||||
// CharacterCount returns the number of characters in a byte array
|
||||
// Similar to utf8.RuneCount but for unicode characters
|
||||
func CharacterCount(b []byte) int {
|
||||
s := 0
|
||||
|
||||
for len(b) > 0 {
|
||||
r, size := utf8.DecodeRune(b)
|
||||
if !unicode.In(r, unicode.Mark) {
|
||||
s++
|
||||
}
|
||||
|
||||
b = b[size:]
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// CharacterCount returns the number of characters in a string
|
||||
// Similar to utf8.RuneCountInString but for unicode characters
|
||||
func CharacterCountInString(str string) int {
|
||||
s := 0
|
||||
|
||||
for _, r := range str {
|
||||
if !unicode.In(r, unicode.Mark) {
|
||||
s++
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
@@ -16,6 +16,9 @@ color-link tabbar "#1D1F21,#C5C8C6"
|
||||
color-link indent-char "#505050,#1D1F21"
|
||||
color-link line-number "#656866,#232526"
|
||||
color-link current-line-number "#656866,#1D1F21"
|
||||
color-link diff-added "#00AF00"
|
||||
color-link diff-modified "#FFAF00"
|
||||
color-link diff-deleted "#D70000"
|
||||
color-link gutter-error "#FF4444,#1D1F21"
|
||||
color-link gutter-warning "#EEEE77,#1D1F21"
|
||||
color-link cursor-line "#2D2F31"
|
||||
@@ -14,11 +14,14 @@ color-link underlined "underline 241,231"
|
||||
color-link todo "246,231"
|
||||
color-link statusline "241,254"
|
||||
color-link tabbar "241,254"
|
||||
color-link diff-added "34"
|
||||
color-link diff-modified "214"
|
||||
color-link diff-deleted "160"
|
||||
color-link gutter-error "197,231"
|
||||
color-link gutter-warning "134,231"
|
||||
color-link line-number "246,254"
|
||||
color-link cursor-line "254"
|
||||
color-link color-column "254"
|
||||
#No extended types (bool in C, &c.) and plain brackets
|
||||
color-link type.extended "default"
|
||||
color-link symbol.brackets "default"
|
||||
color-link type.extended "241,231"
|
||||
color-link symbol.brackets "241,231"
|
||||
|
||||
@@ -33,6 +33,9 @@ color-link statusline "white,blue"
|
||||
color-link tabbar "white,blue"
|
||||
color-link current-line-number "red"
|
||||
color-link current-line-number.scroller "red"
|
||||
color-link diff-added "green"
|
||||
color-link diff-modified "yellow"
|
||||
color-link diff-deleted "red"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "red"
|
||||
color-link color-column "cyan"
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
#CaptainMcClellan's personal color scheme.
|
||||
#Paper version
|
||||
color-link default "black,white"
|
||||
color-link comment "bold black"
|
||||
color-link constant "cyan"
|
||||
color-link constant.bool "bold cyan"
|
||||
color-link constant.bool.true "bold green"
|
||||
color-link constant.bool.false "bold red"
|
||||
color-link constant.string "bold yellow"
|
||||
color-link constant.string.url "underline blue, white"
|
||||
color-link constant.number "constant"
|
||||
color-link constant.specialChar "bold magenta"
|
||||
color-link identifier "bold red"
|
||||
color-link identifier.macro "bold red"
|
||||
color-link identifier.var "bold blue"
|
||||
color-link identifier.class "bold green"
|
||||
color-link preproc "bold cyan"
|
||||
color-link statement "bold yellow"
|
||||
color-link symbol "red"
|
||||
color-link symbol.brackets "blue"
|
||||
color-link type "green"
|
||||
color-link type.keyword "bold green"
|
||||
color-link special "magenta"
|
||||
color-link ignore "default"
|
||||
color-link error ",brightred"
|
||||
color-link todo "black,brightyellow"
|
||||
color-link indent-char ",brightgreen"
|
||||
color-link line-number "green"
|
||||
color-link line-number.scrollbar "green"
|
||||
color-link statusline "white,blue"
|
||||
color-link tabbar "white,blue"
|
||||
color-link current-line-number "red"
|
||||
color-link current-line-number.scroller "red"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "red"
|
||||
color-link color-column "cyan"
|
||||
color-link underlined.url "underline blue, white"
|
||||
@@ -28,9 +28,12 @@ color-link statusline "#aaaaaa,#8a496b"
|
||||
color-link tabbar "#aaaaaa,#8a496b"
|
||||
color-link current-line-number "bold #e34234,#424549"
|
||||
color-link current-line-number.scroller "red"
|
||||
color-link diff-added "#00AF00"
|
||||
color-link diff-modified "#FFAF00"
|
||||
color-link diff-deleted "#D70000"
|
||||
color-link gutter-error ",#e34234"
|
||||
color-link gutter-warning "#e34234"
|
||||
color-link color-column "#f26522"
|
||||
color-link constant.bool "bold #55ffff"
|
||||
color-link constant.bool.true "bold #85ff85"
|
||||
color-link constant.bool.false "bold #ff8585"
|
||||
color-link constant.bool.false "bold #ff8585"
|
||||
|
||||
@@ -15,12 +15,15 @@ color-link todo "bold #D33682,#242424"
|
||||
color-link statusline "#242424,#CCCCCC"
|
||||
color-link tabbar "#242424,#CCCCCC"
|
||||
color-link indent-char "#4F4F4F,#242424"
|
||||
color-link line-number "#666666,#242424"
|
||||
color-link line-number "#666666,#2C2C2C"
|
||||
color-link current-line-number "#666666,#242424"
|
||||
color-link diff-added "#00AF00"
|
||||
color-link diff-modified "#FFAF00"
|
||||
color-link diff-deleted "#D70000"
|
||||
color-link gutter-error "#CB4B16,#242424"
|
||||
color-link gutter-warning "#E6DB74,#242424"
|
||||
color-link cursor-line "default,#2C2C2C"
|
||||
color-link color-column "default,#2C2C2C"
|
||||
color-link cursor-line "#2C2C2C"
|
||||
color-link color-column "#2C2C2C"
|
||||
#No extended types; Plain brackets.
|
||||
color-link type.extended "default"
|
||||
#color-link symbol.brackets "default"
|
||||
|
||||
@@ -17,6 +17,9 @@ color-link tabbar "#282828,#F8F8F2"
|
||||
color-link indent-char "#505050,#282828"
|
||||
color-link line-number "#AAAAAA,#323232"
|
||||
color-link current-line-number "#AAAAAA,#282828"
|
||||
color-link diff-added "#00AF00"
|
||||
color-link diff-modified "#FFAF00"
|
||||
color-link diff-deleted "#D70000"
|
||||
color-link gutter-error "#CB4B16,#282828"
|
||||
color-link gutter-warning "#E6DB74,#282828"
|
||||
color-link cursor-line "#323232"
|
||||
|
||||
34
runtime/colorschemes/dukedark-tc.micro
Normal file
34
runtime/colorschemes/dukedark-tc.micro
Normal file
@@ -0,0 +1,34 @@
|
||||
color-link color-column "#001e28"
|
||||
color-link comment "#608b4e,#001e28"
|
||||
color-link constant.bool "#fd971f,#001e28"
|
||||
color-link constant "#fd971f,#001e28"
|
||||
color-link constant.string "#a0f000,#001e28"
|
||||
color-link constant.string.char "#a0f000,#001e28"
|
||||
color-link constant.string.url "#a0f000,#001e28"
|
||||
color-link current-line-number "bold #fd971f,#001e28"
|
||||
color-link cursor-line "#001923"
|
||||
color-link default "#ffffff,#001e28"
|
||||
color-link diff-added "#00c8a0,#001e28"
|
||||
color-link diff-modified "#fd971f,#001e28"
|
||||
color-link diff-deleted "#cb4b16,#001e28"
|
||||
color-link divider "#001e28,#d0d0d0"
|
||||
color-link error "#cb4b16,#001e28"
|
||||
color-link gutter-error "#cb4b16,#001e28"
|
||||
color-link gutter-warning "#fce94f,#001e28"
|
||||
color-link identifier "#00c8a0,#001e28"
|
||||
color-link identifier.class "#00c8a0,#001e28"
|
||||
color-link indent-char "#a0a0a0,#001e28"
|
||||
color-link line-number "#a0a0a0,#001923"
|
||||
color-link preproc "bold #5aaae6,#001e28"
|
||||
color-link special "#a6e22e,#001e28"
|
||||
color-link statement "bold #5aaae6,#001e28"
|
||||
color-link statusline "#ffffff,#0078c8"
|
||||
color-link symbol "#00c8a0,#001e28"
|
||||
color-link symbol.brackets "#ffffff,#001e28"
|
||||
color-link symbol.tag "bold #5aaae6,#001e28"
|
||||
color-link tabbar "#001e28,#ffffff"
|
||||
color-link todo "#fce94f,#001e28"
|
||||
color-link type "bold #3cc83c,#001e28"
|
||||
color-link type.keyword "bold #5aaae6,#001e28"
|
||||
color-link type.extended "#ffffff,#001e28"
|
||||
color-link underlined "#608b4e,#001e28"
|
||||
34
runtime/colorschemes/dukelight-tc.micro
Normal file
34
runtime/colorschemes/dukelight-tc.micro
Normal file
@@ -0,0 +1,34 @@
|
||||
color-link color-column "#f0f0f0"
|
||||
color-link comment "#3f7f5f,#f0f0f0"
|
||||
color-link constant.bool "#641e00,#f0f0f0"
|
||||
color-link constant "#641e00,#f0f0f0"
|
||||
color-link constant.string "#0000ff,#f0f0f0"
|
||||
color-link constant.string.char "#0000ff,#f0f0f0"
|
||||
color-link constant.string.url "#0000ff,#f0f0f0"
|
||||
color-link current-line-number "bold #004080,#f0f0f0"
|
||||
color-link cursor-line "#e6e6e6"
|
||||
color-link default "#000000,#f0f0f0"
|
||||
color-link diff-added "#008040,#f0f0f0"
|
||||
color-link diff-modified "#641e00,#f0f0f0"
|
||||
color-link diff-deleted "#500000,#f0f0f0"
|
||||
color-link divider "#f0f0f0,#004080"
|
||||
color-link error "#500000,#f0f0f0"
|
||||
color-link gutter-error "#500000,#f0f0f0"
|
||||
color-link gutter-warning "#dcc800,#f0f0f0"
|
||||
color-link identifier "bold #0078a0,#f0f0f0"
|
||||
color-link identifier.class "bold #0078a0,#f0f0f0"
|
||||
color-link indent-char "#404040,#f0f0f0"
|
||||
color-link line-number "#404040,#e6e6e6"
|
||||
color-link preproc "bold #780050,#f0f0f0"
|
||||
color-link special "bold #0078a0,#f0f0f0"
|
||||
color-link statement "bold #780050,#f0f0f0"
|
||||
color-link statusline "#ffffff,#0078c8"
|
||||
color-link symbol "bold #0078a0,#f0f0f0"
|
||||
color-link symbol.brackets "#000000,#f0f0f0"
|
||||
color-link symbol.tag "bold #780050,#f0f0f0"
|
||||
color-link tabbar "#f0f0f0,#004080"
|
||||
color-link todo "#dcc800,#f0f0f0"
|
||||
color-link type "bold #004080,#f0f0f0"
|
||||
color-link type.keyword "bold #780050,#f0f0f0"
|
||||
color-link type.extended "#000000,#f0f0f0"
|
||||
color-link underlined "#3f7f5f,#f0f0f0"
|
||||
34
runtime/colorschemes/dukeubuntu-tc.micro
Normal file
34
runtime/colorschemes/dukeubuntu-tc.micro
Normal file
@@ -0,0 +1,34 @@
|
||||
color-link color-column "#2d0023"
|
||||
color-link comment "#886484,#2d0023"
|
||||
color-link constant.bool "#fd971f,#2d0023"
|
||||
color-link constant "#fd971f,#2d0023"
|
||||
color-link constant.string "#a0f000,#2d0023"
|
||||
color-link constant.string.char "#a0f000,#2d0023"
|
||||
color-link constant.string.url "#a0f000,#2d0023"
|
||||
color-link current-line-number "bold #fd971f,#2d0023"
|
||||
color-link cursor-line "#230019"
|
||||
color-link default "#ffffff,#2d0023"
|
||||
color-link diff-added "#00c8a0,#2d0023"
|
||||
color-link diff-modified "#fd971f,#2d0023"
|
||||
color-link diff-deleted "#cb4b16,#2d0023"
|
||||
color-link divider "#2d0023,#d0d0d0"
|
||||
color-link error "#cb4b16,#2d0023"
|
||||
color-link gutter-error "#cb4b16,#2d0023"
|
||||
color-link gutter-warning "#fce94f,#2d0023"
|
||||
color-link identifier "#00c8a0,#2d0023"
|
||||
color-link identifier.class "#00c8a0,#2d0023"
|
||||
color-link indent-char "#a0a0a0,#2d0023"
|
||||
color-link line-number "#a0a0a0,#230019"
|
||||
color-link preproc "bold #5aaae6,#2d0023"
|
||||
color-link special "#a6e22e,#2d0023"
|
||||
color-link statement "bold #5aaae6,#2d0023"
|
||||
color-link statusline "#ffffff,#0078c8"
|
||||
color-link symbol "#00c8a0,#2d0023"
|
||||
color-link symbol.brackets "#ffffff,#2d0023"
|
||||
color-link symbol.tag "bold #5aaae6,#2d0023"
|
||||
color-link tabbar "#2d0023,#ffffff"
|
||||
color-link todo "#fce94f,#2d0023"
|
||||
color-link type "bold #3cc83c,#2d0023"
|
||||
color-link type.keyword "bold #5aaae6,#2d0023"
|
||||
color-link type.extended "#ffffff,#2d0023"
|
||||
color-link underlined "#886484,#2d0023"
|
||||
@@ -1,7 +1,6 @@
|
||||
#Geany
|
||||
color-link comment "red"
|
||||
color-link constant "default"
|
||||
color-link constant.number
|
||||
color-link constant.string "bold yellow"
|
||||
color-link identifier "default"
|
||||
color-link preproc "cyan"
|
||||
@@ -19,5 +18,8 @@ color-link current-line-number ""
|
||||
color-link statusline "black,white"
|
||||
color-link tabbar "black,white"
|
||||
color-link color-column "bold geren"
|
||||
color-link diff-added "green"
|
||||
color-link diff-modified "yellow"
|
||||
color-link diff-deleted "red"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "red"
|
||||
color-link gutter-warning "red"
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
#True color theme based on Github's syntax highlighting.
|
||||
#Warning, this is based on how it rendered in my Firefox!
|
||||
#Yours may look different.
|
||||
color-link comment "bold #969896"
|
||||
color-link constant "#0086B9"
|
||||
color-link constant.number "#0086B9"
|
||||
color-link constant.specialChar "bold #1836BD"
|
||||
color-link constant.string "bold #1836BD"
|
||||
color-link constant.bool "#0086B9"
|
||||
color-link identifier "#A71D5D"
|
||||
color-link preproc "bold #A71D5D"
|
||||
color-link special "#A71D5D"
|
||||
color-link statement "#A71D5D"
|
||||
color-link symbol "default"
|
||||
color-link type "#A71D5D"
|
||||
color-link error "bold ,#E34234"
|
||||
color-link todo "white"
|
||||
color-link indent-char "default"
|
||||
color-link line-number "bold #969896"
|
||||
color-link current-line-number "bold #969896"
|
||||
color-link gutter-error "bold ,#E34234"
|
||||
color-link gutter-warning "bold #f26522"
|
||||
color-link statusline "bold #c8c9cb,#24292e"
|
||||
color-link tabbar "bold #c8c9cb,#24292e"
|
||||
25
runtime/colorschemes/gotham.micro
Normal file
25
runtime/colorschemes/gotham.micro
Normal file
@@ -0,0 +1,25 @@
|
||||
color-link default "#99D1CE,#0C1014"
|
||||
color-link comment "#245361,#0C1014"
|
||||
color-link identifier "#599CAB,#0C1014"
|
||||
color-link constant "#D26937,#0C1014"
|
||||
color-link constant.string "#2AA889,#0C1014"
|
||||
color-link constant.string.char "#D3EBE9,#0C1014"
|
||||
color-link statement "#599CAB,#0C1014"
|
||||
color-link preproc "#C23127,#0C1014"
|
||||
color-link type "#D26937,#0C1014"
|
||||
color-link special "#D26937,#0C1014"
|
||||
color-link underlined "#EDB443,#0C1014"
|
||||
color-link error "bold #C23127,#0C1014"
|
||||
color-link todo "bold #888CA6,#0C1014"
|
||||
color-link statusline "#091F2E,#599CAB"
|
||||
color-link indent-char "#505050,#0C1014"
|
||||
color-link line-number "#245361,#11151C"
|
||||
color-link current-line-number "#599CAB,#11151C"
|
||||
color-link diff-added "#00AF00"
|
||||
color-link diff-modified "#FFAF00"
|
||||
color-link diff-deleted "#D70000"
|
||||
color-link gutter-error "#C23127,#11151C"
|
||||
color-link gutter-warning "#EDB443,#11151C"
|
||||
color-link cursor-line "#091F2E"
|
||||
color-link color-column "#11151C"
|
||||
color-link symbol "#99D1CE,#0C1014"
|
||||
@@ -11,10 +11,13 @@ color-link type "#fb4934,#282828"
|
||||
color-link special "#d79921,#282828"
|
||||
color-link underlined "underline #282828"
|
||||
color-link error "#9d0006,#282828"
|
||||
color-link diff-added "#00AF00"
|
||||
color-link diff-modified "#FFAF00"
|
||||
color-link diff-deleted "#D70000"
|
||||
color-link gutter-error "#fb4934,#282828"
|
||||
color-link gutter-warning "#d79921,#282828"
|
||||
color-link line-number "#665c54,#282828"
|
||||
color-link current-line-number "#665c54,#3c3836"
|
||||
color-link line-number "#665c54,#3c3836"
|
||||
color-link current-line-number "#d79921,#282828"
|
||||
color-link cursor-line "#3c3836"
|
||||
color-link color-column "#79740e"
|
||||
color-link statusline "#ebdbb2,#665c54"
|
||||
|
||||
@@ -11,9 +11,12 @@ color-link special "172,235"
|
||||
color-link underlined "underline 109,235"
|
||||
color-link error "235,124"
|
||||
color-link todo "bold 223,235"
|
||||
color-link diff-added "34"
|
||||
color-link diff-modified "214"
|
||||
color-link diff-deleted "160"
|
||||
color-link line-number "243,237"
|
||||
color-link current-line-number "172,237"
|
||||
color-link current-line-number "172,235"
|
||||
color-link cursor-line "237"
|
||||
color-link color-column "237"
|
||||
color-link statusline "223,237"
|
||||
color-link tabbar "223,237"
|
||||
color-link tabbar "223,237"
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
#A colorscheme based on Code::Blocks IDE
|
||||
#but with a white background.
|
||||
color-link default "black,white"
|
||||
color-link comment "bold black"
|
||||
color-link constant "blue"
|
||||
color-link constant.number "bold magenta"
|
||||
color-link constant.string "bold blue"
|
||||
color-link identifier "black"
|
||||
color-link preproc "green"
|
||||
color-link statement "blue"
|
||||
color-link symbol "red"
|
||||
color-link symbol.brackets "blue"
|
||||
color-link type "blue"
|
||||
color-link special "magenta"
|
||||
color-link ignore "default"
|
||||
color-link error "bold white,brightred"
|
||||
color-link todo "bold black,brightyellow"
|
||||
color-link indent-char "bold black"
|
||||
color-link line-number "black,white"
|
||||
color-link statusline "white,red"
|
||||
color-link tabbar "white,red"
|
||||
color-link current-line-number "red,black"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "red"
|
||||
color-link color-column "black"
|
||||
@@ -1,23 +0,0 @@
|
||||
#Theme based on Code::Blocks IDE's default syntax highlighting.
|
||||
color-link comment "bold black"
|
||||
color-link constant "blue"
|
||||
color-link constant.string "bold blue"
|
||||
color-link constant.number "bold magenta"
|
||||
color-link identifier "default"
|
||||
color-link preproc "green"
|
||||
color-link statement "blue"
|
||||
color-link symbol "red"
|
||||
color-link symbol.brackets "blue"
|
||||
color-link type "blue"
|
||||
color-link special "magenta"
|
||||
color-link ignore "default"
|
||||
color-link error ",brightred"
|
||||
color-link todo "bold black,brightyellow"
|
||||
color-link indent-char "bold black"
|
||||
color-link line-number "black,white"
|
||||
color-link statusline "white,red"
|
||||
color-link tabbar "white,red"
|
||||
color-link current-line-number "red"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "red"
|
||||
color-link color-column "white"
|
||||
@@ -1,31 +0,0 @@
|
||||
#Funky Cactus theme
|
||||
color-link comment "bold black"
|
||||
color-link constant "cyan"
|
||||
color-link constant.bool "bold cyan"
|
||||
color-link constant.bool.true "bold green"
|
||||
color-link constant.bool.false "bold red"
|
||||
color-link constant.string "yellow"
|
||||
color-link constant.number "constant"
|
||||
color-link constant.specialChar "bold magenta"
|
||||
color-link identifier "bold red"
|
||||
color-link identifier.macro "bold red"
|
||||
color-link identifier.var "bold blue"
|
||||
color-link identifier.class "bold green"
|
||||
color-link preproc "bold cyan"
|
||||
color-link statement "bold yellow"
|
||||
color-link symbol "red"
|
||||
color-link symbol.brackets "blue"
|
||||
color-link type "green"
|
||||
color-link type.keyword "bold green"
|
||||
color-link special "magenta"
|
||||
color-link ignore "default"
|
||||
color-link error "bold ,brightred"
|
||||
color-link todo "underline ,brightyellow"
|
||||
color-link indent-char "bold ,brightgreen"
|
||||
color-link line-number "green"
|
||||
color-link statusline "black,green"
|
||||
color-link tabbar "black,magenta"
|
||||
color-link current-line-number "bold magenta"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "red"
|
||||
color-link color-column "bold green"
|
||||
@@ -1,23 +0,0 @@
|
||||
#Gameboy theme
|
||||
color-link default "#3f3f3f,#bfc180"
|
||||
color-link comment "#7d7343"
|
||||
color-link constant "#7d7343"
|
||||
color-link identifier "#ddde7d"
|
||||
color-link preproc "#ddde7d,#7d7343"
|
||||
color-link special "#7d7343"
|
||||
color-link statement "#7d7343"
|
||||
color-link symbol "#7d7343"
|
||||
color-link type "#7d7343"
|
||||
color-link error "#ddde7d,#7d7343"
|
||||
color-link todo "#7d7343,#ddde7d"
|
||||
color-link statusline "#ddde7d,#7d7343"
|
||||
color-link tabbar "#ddde7d,#7d7343"
|
||||
color-link color-column "#7d7343"
|
||||
color-link line-number "#ddde7d,#7d7343"
|
||||
color-link current-line-number "#3f3f3f,#bfc180"
|
||||
color-link gutter-error "#ddde7d,#7d7343"
|
||||
color-link gutter-warning "default"
|
||||
#3f3f3f
|
||||
#7d7343
|
||||
#bfc180
|
||||
#ddde76
|
||||
@@ -1,21 +0,0 @@
|
||||
#Geany Alternate theme
|
||||
color-link default "#000000,#fefefe"
|
||||
color-link comment "#808080"
|
||||
color-link constant "default"
|
||||
color-link constant.bool "#003030"
|
||||
color-link constant.number "#300008"
|
||||
color-link constant.string "#008000"
|
||||
color-link identifier "default"
|
||||
color-link preproc "#bbbb77"
|
||||
color-link special "#003030"
|
||||
color-link statement "#003030"
|
||||
color-link symbol "#300008"
|
||||
color-link symbol.tag "bold #4e9d71"
|
||||
color-link type "#003030"
|
||||
color-link error "#a52a2a"
|
||||
color-link todo "#ffa500"
|
||||
color-link line-number "#000000,#d0d0d0"
|
||||
color-link current-line-number "#000000,#d0d0d0"
|
||||
color-link color-column "#c2ebc2"
|
||||
color-link cursor-line "#f0f0f0"
|
||||
color-link type.extended "default"
|
||||
@@ -1,24 +0,0 @@
|
||||
#Theme based on Github's syntax highlighting.
|
||||
color-link comment "bold black"
|
||||
color-link constant "cyan"
|
||||
color-link constant.number "cyan"
|
||||
color-link constant.specialChar "bold blue"
|
||||
color-link constant.string "bold blue"
|
||||
color-link constant.bool "cyan"
|
||||
color-link identifier "magenta"
|
||||
color-link preproc "bold magenta"
|
||||
color-link special "magenta"
|
||||
color-link statement "magenta"
|
||||
color-link symbol "default"
|
||||
color-link type "magenta"
|
||||
color-link error "bold ,brightred"
|
||||
color-link todo "white"
|
||||
color-link indent-char "default"
|
||||
color-link line-number "bold black"
|
||||
color-link current-line-number "bold black"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "bold yellow"
|
||||
color-link statusline "bold white,black"
|
||||
color-link tabbar "bold white,black"
|
||||
#Plain brackets.
|
||||
#color-link symbol.brackets "default"
|
||||
@@ -1,25 +0,0 @@
|
||||
#Midnight Commander inspired theme.
|
||||
color-link default "white,blue"
|
||||
color-link comment "bold black"
|
||||
color-link constant "bold white"
|
||||
color-link constant.string "bold yellow"
|
||||
color-link identifier "bold red"
|
||||
color-link statement "bold cyan"
|
||||
color-link symbol "white"
|
||||
color-link symbol.brackets "white"
|
||||
color-link symbol.tag "bold green"
|
||||
color-link preproc "black,cyan"
|
||||
color-link type "green"
|
||||
color-link special "magenta"
|
||||
color-link ignore "default"
|
||||
color-link error ",brightred"
|
||||
color-link todo ",brightyellow"
|
||||
color-link indent-char ",cyan"
|
||||
color-link line-number "green"
|
||||
color-link statusline "black,cyan"
|
||||
color-link tabbar "black,cyan"
|
||||
color-link current-line-number "black,cyan"
|
||||
color-link cursor-line "black,cyan"
|
||||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "red"
|
||||
color-link color-column "cyan"
|
||||
@@ -1,5 +0,0 @@
|
||||
#Monochrome Paper theme.
|
||||
#Edit your files on a white background without colors.
|
||||
color-link default "black,white"
|
||||
color-link statusline "white,black"
|
||||
color-link tabbar "white,black"
|
||||
@@ -1,3 +0,0 @@
|
||||
#Monochrome
|
||||
#This makes micro use only the terminal's default
|
||||
# foreground and background colours.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user