Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
|
8c403655a7 |
381 changed files with 19924 additions and 29482 deletions
|
@ -1,7 +0,0 @@
|
|||
# See http://editorconfig.org
|
||||
|
||||
# In Go files we indent with tabs but still
|
||||
# set indent_size to control the GitHub web viewer.
|
||||
[*.go]
|
||||
indent_size=4
|
||||
|
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:
|
||||
|
|
22
.github/workflows/test.yaml
vendored
22
.github/workflows/test.yaml
vendored
|
@ -1,22 +0,0 @@
|
|||
on: [push, pull_request]
|
||||
name: Build and Test
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.19.x]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
make build
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
make test
|
10
.gitignore
vendored
10
.gitignore
vendored
|
@ -7,13 +7,3 @@ tmp.sh
|
|||
test/
|
||||
.idea/
|
||||
packages/
|
||||
todo.txt
|
||||
test.txt
|
||||
log.txt
|
||||
*.old
|
||||
benchmark_results*
|
||||
tools/build-version
|
||||
tools/build-date
|
||||
tools/info-plist
|
||||
tools/vscode-tests/
|
||||
*.hdr
|
||||
|
|
57
.gitmodules
vendored
Normal file
57
.gitmodules
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
[submodule "cmd/micro/vendor/github.com/blang/semver"]
|
||||
path = cmd/micro/vendor/github.com/blang/semver
|
||||
url = https://github.com/blang/semver
|
||||
[submodule "cmd/micro/vendor/github.com/dustin/go-humanize"]
|
||||
path = cmd/micro/vendor/github.com/dustin/go-humanize
|
||||
url = https://github.com/dustin/go-humanize
|
||||
[submodule "cmd/micro/vendor/github.com/go-errors/errors"]
|
||||
path = cmd/micro/vendor/github.com/go-errors/errors
|
||||
url = https://github.com/go-errors/errors
|
||||
[submodule "cmd/micro/vendor/github.com/mattn/go-isatty"]
|
||||
path = cmd/micro/vendor/github.com/mattn/go-isatty
|
||||
url = https://github.com/mattn/go-isatty
|
||||
[submodule "cmd/micro/vendor/github.com/mattn/go-runewidth"]
|
||||
path = cmd/micro/vendor/github.com/mattn/go-runewidth
|
||||
url = https://github.com/mattn/go-runewidth
|
||||
[submodule "cmd/micro/vendor/github.com/mitchellh/go-homedir"]
|
||||
path = cmd/micro/vendor/github.com/mitchellh/go-homedir
|
||||
url = https://github.com/mitchellh/go-homedir
|
||||
[submodule "cmd/micro/vendor/github.com/sergi/go-diff"]
|
||||
path = cmd/micro/vendor/github.com/sergi/go-diff
|
||||
url = https://github.com/sergi/go-diff
|
||||
[submodule "cmd/micro/vendor/github.com/yuin/gopher-lua"]
|
||||
path = cmd/micro/vendor/github.com/yuin/gopher-lua
|
||||
url = https://github.com/yuin/gopher-lua
|
||||
[submodule "cmd/micro/vendor/golang.org/x/net"]
|
||||
path = cmd/micro/vendor/golang.org/x/net
|
||||
url = https://go.googlesource.com/net
|
||||
[submodule "cmd/micro/vendor/github.com/zyedidia/clipboard"]
|
||||
path = cmd/micro/vendor/github.com/zyedidia/clipboard
|
||||
url = https://github.com/zyedidia/clipboard
|
||||
[submodule "cmd/micro/vendor/github.com/zyedidia/glob"]
|
||||
path = cmd/micro/vendor/github.com/zyedidia/glob
|
||||
url = https://github.com/zyedidia/glob
|
||||
[submodule "cmd/micro/vendor/github.com/zyedidia/tcell"]
|
||||
path = cmd/micro/vendor/github.com/zyedidia/tcell
|
||||
url = https://github.com/zyedidia/tcell
|
||||
[submodule "cmd/micro/vendor/github.com/gdamore/encoding"]
|
||||
path = cmd/micro/vendor/github.com/gdamore/encoding
|
||||
url = https://github.com/gdamore/encoding
|
||||
[submodule "cmd/micro/vendor/golang.org/x/text"]
|
||||
path = cmd/micro/vendor/golang.org/x/text
|
||||
url = https://go.googlesource.com/text
|
||||
[submodule "cmd/micro/vendor/github.com/lucasb-eyer/go-colorful"]
|
||||
path = cmd/micro/vendor/github.com/lucasb-eyer/go-colorful
|
||||
url = https://github.com/lucasb-eyer/go-colorful
|
||||
[submodule "cmd/micro/vendor/layeh.com/gopher-luar"]
|
||||
path = cmd/micro/vendor/layeh.com/gopher-luar
|
||||
url = https://github.com/layeh/gopher-luar
|
||||
[submodule "cmd/micro/vendor/gopkg.in/yaml.v2"]
|
||||
path = cmd/micro/vendor/gopkg.in/yaml.v2
|
||||
url = https://gopkg.in/yaml.v2
|
||||
[submodule "cmd/micro/vendor/github.com/zyedidia/poller"]
|
||||
path = cmd/micro/vendor/github.com/zyedidia/poller
|
||||
url = https://github.com/zyedidia/poller
|
||||
[submodule "cmd/micro/vendor/github.com/flynn/json5"]
|
||||
path = cmd/micro/vendor/github.com/flynn/json5
|
||||
url = https://github.com/flynn/json5
|
2
.travis.yml
Normal file
2
.travis.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
language: go
|
||||
script: make test
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2016-2020: Zachary Yedidia, et al.
|
||||
Copyright (c) 2016-2017: Zachary Yedidia, et al.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Third party libraries directly used by micro and their licenses
|
||||
Third party libraries
|
||||
================
|
||||
|
||||
github.com/golang/go/LICENSE
|
||||
|
@ -58,6 +58,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|||
THE SOFTWARE.
|
||||
|
||||
|
||||
|
||||
github.com/gdamore/encoding/LICENSE
|
||||
================
|
||||
|
||||
|
@ -394,9 +395,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||
SOFTWARE.
|
||||
|
||||
|
||||
github.com/atotto/clipboard/LICENSE
|
||||
================
|
||||
github.com/zyedidia/clipboard/LICENSE (fork)
|
||||
github.com/zyedidia/clipboard/LICENSE
|
||||
================
|
||||
|
||||
Copyright (c) 2013 Ato Araki. All rights reserved.
|
||||
|
@ -428,9 +427,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
github.com/gdamore/tcell/LICENSE
|
||||
================
|
||||
github.com/zyedidia/tcell/LICENSE (fork)
|
||||
github.com/zyedidia/tcell/LICENSE
|
||||
================
|
||||
|
||||
|
||||
|
@ -637,6 +634,70 @@ 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/sys/LICENSE
|
||||
================
|
||||
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
golang.org/x/text/LICENSE
|
||||
================
|
||||
|
||||
|
@ -1048,8 +1109,6 @@ 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.
|
||||
|
||||
|
@ -1105,202 +1164,3 @@ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
github.com/james4k/terminal/LICENSE
|
||||
================
|
||||
github.com/zyedidia/terminal/LICENSE (fork)
|
||||
================
|
||||
|
||||
Copyright (C) 2013 James Gray
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without liitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and thismssion notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
github.com/kr/pty/License
|
||||
================
|
||||
github.com/zyedidia/pty/License (fork)
|
||||
================
|
||||
|
||||
Copyright (c) 2011 Keith Rarick
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall
|
||||
be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
||||
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
github.com/npat-efault/poller/LICENSE.txt
|
||||
================
|
||||
github.com/zyedidia/poller/LICENSE.txt (fork)
|
||||
================
|
||||
|
||||
Copyright (c) 2014, Nick Patavalis (npat@efault.net)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
github.com/zyedidia/glob
|
||||
================
|
||||
|
||||
Glob is licensed under the MIT "Expat" License:
|
||||
|
||||
Copyright (c) 2016: Zachary Yedidia.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
github.com/dustin/go-humanize/LICENSE
|
||||
================
|
||||
|
||||
Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
<http://www.opensource.org/licenses/mit-license.php>
|
||||
|
||||
gopkg.in/yaml.v2/LICENSE
|
||||
================
|
||||
|
||||
Copyright 2011-2016 Canonical Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
github.com/kballard/go-shellquote/LICENSE
|
||||
===============
|
||||
|
||||
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
|
||||
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.
|
||||
|
|
84
Makefile
84
Makefile
|
@ -1,71 +1,49 @@
|
|||
.PHONY: runtime build generate build-quick
|
||||
.PHONY: runtime
|
||||
|
||||
VERSION = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
||||
VERSION := $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
||||
go run tools/build-version.go)
|
||||
HASH = $(shell git rev-parse --short HEAD)
|
||||
DATE = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
||||
HASH := $(shell git rev-parse --short HEAD)
|
||||
DATE := $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
||||
go run tools/build-date.go)
|
||||
ADDITIONAL_GO_LINKER_FLAGS = $(shell GOOS=$(shell go env GOHOSTOS) \
|
||||
ADDITIONAL_GO_LINKER_FLAGS := $(shell GOOS=$(shell go env GOHOSTOS) \
|
||||
GOARCH=$(shell go env GOHOSTARCH) \
|
||||
go run tools/info-plist.go "$(shell go env GOOS)" "$(VERSION)")
|
||||
go run tools/info-plist.go "$(VERSION)")
|
||||
GOBIN ?= $(shell go env GOPATH)/bin
|
||||
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/'
|
||||
|
||||
build: generate build-quick
|
||||
# Builds micro after checking dependencies but without updating the runtime
|
||||
build: update
|
||||
go build -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
|
||||
# Builds micro after building the runtime and checking dependencies
|
||||
build-all: runtime build
|
||||
|
||||
# Builds micro without checking for dependencies
|
||||
build-quick:
|
||||
go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
go build -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
|
||||
build-dbg:
|
||||
go build -trimpath -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS) $(DEBUGVAR)" ./cmd/micro
|
||||
# Same as 'build' but installs to $GOBIN afterward
|
||||
install: update
|
||||
go install -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
|
||||
build-tags: fetch-tags generate
|
||||
go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
# Same as 'build-all' but installs to $GOBIN afterward
|
||||
install-all: runtime install
|
||||
|
||||
build-all: build
|
||||
# Same as 'build-quick' but installs to $GOBIN afterward
|
||||
install-quick:
|
||||
go install -ldflags "-s -w -X main.Version=$(VERSION) -X main.CommitHash=$(HASH) -X 'main.CompileDate=$(DATE)' $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
|
||||
install: generate
|
||||
go install -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
update:
|
||||
git pull
|
||||
git submodule update --init
|
||||
|
||||
install-all: install
|
||||
|
||||
fetch-tags:
|
||||
git fetch --tags
|
||||
|
||||
generate:
|
||||
GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) go generate ./runtime
|
||||
|
||||
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
|
||||
# Builds the runtime
|
||||
runtime:
|
||||
go get -u github.com/jteeuwen/go-bindata/...
|
||||
$(GOBIN)/go-bindata -nometadata -o runtime.go runtime/...
|
||||
mv runtime.go cmd/micro
|
||||
|
||||
test:
|
||||
go test ./internal/...
|
||||
go test ./cmd/...
|
||||
|
||||
bench:
|
||||
for i in 1 2 3; do \
|
||||
go test -bench=. ./internal/...; \
|
||||
done > benchmark_results
|
||||
benchstat benchmark_results
|
||||
|
||||
bench-baseline:
|
||||
for i in 1 2 3; do \
|
||||
go test -bench=. ./internal/...; \
|
||||
done > benchmark_results_baseline
|
||||
|
||||
bench-compare:
|
||||
for i in 1 2 3; do \
|
||||
go test -bench=. ./internal/...; \
|
||||
done > benchmark_results
|
||||
benchstat -alpha 0.15 benchmark_results_baseline benchmark_results
|
||||
go test ./cmd/micro
|
||||
|
||||
clean:
|
||||
rm -f micro
|
||||
|
|
298
README.md
298
README.md
|
@ -1,121 +1,75 @@
|
|||
<img alt="micro logo" src="./assets/micro-logo-drop.svg" width="500px"/>
|
||||
# ![Micro](./assets/logo.png)
|
||||
|
||||
![Test Workflow](https://github.com/zyedidia/micro/actions/workflows/test.yaml/badge.svg)
|
||||
[![Build Status](https://travis-ci.org/zyedidia/micro.svg?branch=master)](https://travis-ci.org/zyedidia/micro)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/zyedidia/micro)](https://goreportcard.com/report/github.com/zyedidia/micro)
|
||||
[![Release](https://img.shields.io/github/release/zyedidia/micro.svg?label=Release)](https://github.com/zyedidia/micro/releases)
|
||||
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/zyedidia/micro/blob/master/LICENSE)
|
||||
[![Join the chat at https://gitter.im/zyedidia/micro](https://badges.gitter.im/zyedidia/micro.svg)](https://gitter.im/zyedidia/micro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[![Snap Status](https://snapcraft.io/micro/badge.svg)](https://snapcraft.io/micro)
|
||||
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/zyedidia/micro/blob/master/LICENSE)
|
||||
[![Snap Status](https://build.snapcraft.io/badge/zyedidia/micro.svg)](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 capabilities
|
||||
of modern terminals. It comes as a single, batteries-included, static binary with no dependencies; 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 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.
|
||||
|
||||
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.
|
||||
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).
|
||||
|
||||
Here is a picture of micro editing its source code.
|
||||
|
||||
![Screenshot](./assets/micro-solarized.png)
|
||||
|
||||
To see more screenshots of micro, showcasing some of the default color schemes, see [here](https://micro-editor.github.io).
|
||||
|
||||
To see more screenshots of micro, showcasing all of the default colorschemes, 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
|
||||
# Features
|
||||
|
||||
- [Features](#features)
|
||||
- [Installation](#installation)
|
||||
- [Prebuilt binaries](#pre-built-binaries)
|
||||
- [Package Managers](#package-managers)
|
||||
- [Building from source](#building-from-source)
|
||||
- [Fully static binary](#fully-static-binary)
|
||||
- [macOS terminal](#macos-terminal)
|
||||
- [Linux clipboard support](#linux-clipboard-support)
|
||||
- [Colors and syntax highlighting](#colors-and-syntax-highlighting)
|
||||
- [Cygwin, Mingw, Plan9](#cygwin-mingw-plan9)
|
||||
- [Usage](#usage)
|
||||
- [Documentation and Help](#documentation-and-help)
|
||||
- [Contributing](#contributing)
|
||||
* 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
|
||||
* Extremely good mouse support
|
||||
* This means mouse dragging to create a selection, double click to select by word, and triple click to select by line
|
||||
* Cross platform (It should work on all the platforms Go runs on)
|
||||
* Note that while Windows is supported, there are still some bugs that need to be worked out
|
||||
* Plugin system (plugins are written in Lua)
|
||||
* Micro has a built-in plugin manager to automatically install, remove, and update all your plugins
|
||||
* Persistent undo
|
||||
* Automatic linting and error notifications
|
||||
* Syntax highlighting (for over [90 languages](runtime/syntax)!)
|
||||
* Colorscheme support
|
||||
* By default, micro comes with 16, 256, and true color themes.
|
||||
* True color support (set the `MICRO_TRUECOLOR` env variable to 1 to enable it)
|
||||
* Snippets
|
||||
* The snippet plugin can be installed with `> plugin install snippets`
|
||||
* Copy and paste with the system clipboard
|
||||
* Small and simple
|
||||
* Easily configurable
|
||||
* Macros
|
||||
* Common editor things such as undo/redo, line numbers, Unicode support, softwrap...
|
||||
|
||||
- - -
|
||||
Although not yet implemented, I hope to add more features such as autocompletion ([#174](https://github.com/zyedidia/micro/issues/174)), and multiple cursors ([#5](https://github.com/zyedidia/micro/issues/5)) in the future.
|
||||
|
||||
## Features
|
||||
|
||||
- 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.
|
||||
- Smart highlighting of trailing whitespace and tab vs space errors.
|
||||
- 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
|
||||
|
||||
A desktop entry file and man page can be found in the [assets/packaging](https://github.com/zyedidia/micro/tree/master/assets/packaging) directory.
|
||||
All you need to install micro is one file, the binary itself. It's as simple as that!
|
||||
|
||||
### Pre-built binaries
|
||||
Download the binary from the [releases](https://github.com/zyedidia/micro/releases) page.
|
||||
|
||||
Pre-built binaries are distributed in [releases](https://github.com/zyedidia/micro/releases).
|
||||
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.
|
||||
|
||||
To uninstall micro, simply remove the binary, and the configuration directory at `~/.config/micro`.
|
||||
If you'd like to see more information after installing micro, run `micro -version`.
|
||||
|
||||
#### Quick-install script
|
||||
|
||||
```bash
|
||||
curl https://getmic.ro | bash
|
||||
```
|
||||
|
||||
The script will place the micro binary in the current directory. From there, you can move it to a directory on your path of your choosing (e.g. `sudo mv micro /usr/bin`). See its [GitHub repository](https://github.com/benweissmann/getmic.ro) for more information.
|
||||
|
||||
#### Eget
|
||||
|
||||
With [Eget](https://github.com/zyedidia/eget) installed, you can easily get a pre-built binary:
|
||||
|
||||
```
|
||||
eget zyedidia/micro
|
||||
```
|
||||
|
||||
Use `--tag VERSION` to download a specific tagged version.
|
||||
|
||||
```
|
||||
eget --tag nightly zyedidia/micro # download the nightly version (compiled every day at midnight UTC)
|
||||
eget --tag v2.0.8 zyedidia/micro # download version 2.0.8 rather than the latest release
|
||||
```
|
||||
|
||||
You can install `micro` by adding `--to /usr/local/bin` to the `eget` command, or move the binary manually to a directory on your `$PATH` after the download completes.
|
||||
|
||||
See [Eget](https://github.com/zyedidia/eget) for more information.
|
||||
|
||||
### Package managers
|
||||
### Package Managers
|
||||
|
||||
You can install micro using Homebrew on Mac:
|
||||
|
||||
|
@ -123,134 +77,86 @@ You can install micro using Homebrew on Mac:
|
|||
brew install micro
|
||||
```
|
||||
|
||||
**Note for Mac:** All micro keybindings use the control or alt (option) key, not the command
|
||||
key. By default, macOS terminals do not forward alt key events. To fix this, please see
|
||||
the section on [macOS terminals](https://github.com/zyedidia/micro#macos-terminal) further below.
|
||||
On Windows, you can install micro through [Chocolatey](https://chocolatey.org/) or [Scoop](https://github.com/lukesampson/scoop):
|
||||
|
||||
```
|
||||
choco install micro
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
scoop install micro
|
||||
```
|
||||
|
||||
On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/install)
|
||||
|
||||
```
|
||||
snap install micro --classic
|
||||
snap install micro --edge --classic
|
||||
```
|
||||
|
||||
Micro is also available through other package managers on Linux such as dnf, AUR, Nix, and package managers
|
||||
for other operating systems. These packages are not guaranteed to be up-to-date.
|
||||
|
||||
<!-- * `apt install micro` (Ubuntu 20.04 `focal`, and Debian `unstable | testing | buster-backports`). At the moment, this package (2.0.1-1) is outdated and has a known bug where debug mode is enabled. -->
|
||||
|
||||
* Linux: Available in distro-specific package managers.
|
||||
* `dnf install micro` (Fedora).
|
||||
* `apt install micro` (Ubuntu and Debian).
|
||||
* `pacman -S micro` (Arch Linux).
|
||||
* `emerge app-editors/micro` (Gentoo).
|
||||
* `zypper install micro-editor` (SUSE)
|
||||
* `eopkg install micro` (Solus).
|
||||
* `pacstall -I micro` (Pacstall).
|
||||
* 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`
|
||||
* macOS with [MacPorts](https://www.macports.org):
|
||||
* `sudo port install micro`
|
||||
|
||||
**Note for Linux desktop environments:**
|
||||
|
||||
For interfacing with the local system clipboard, the following tools need to be installed:
|
||||
* For X11, `xclip` or `xsel`
|
||||
* For [Wayland](https://wayland.freedesktop.org/), `wl-clipboard`
|
||||
|
||||
Without these tools installed, micro will use an internal clipboard for copy and paste, but it won't be accessible to external applications.
|
||||
|
||||
### Building from source
|
||||
|
||||
If your operating system does not have a binary release, but does run Go, you can build from source.
|
||||
|
||||
Make sure that you have Go version 1.16 or greater and Go modules are enabled.
|
||||
Make sure that you have Go version 1.5 or greater (Go 1.4 will work if your version supports CGO) and that your `GOPATH` env variable is set (I recommand setting it to `~/go` if you don't have one).
|
||||
|
||||
```
|
||||
git clone https://github.com/zyedidia/micro
|
||||
cd micro
|
||||
make build
|
||||
sudo mv micro /usr/local/bin # optional
|
||||
go get -d github.com/zyedidia/micro/cmd/micro
|
||||
cd $GOPATH/src/github.com/zyedidia/micro
|
||||
make install
|
||||
```
|
||||
|
||||
The binary will be placed in the current directory and can be moved to
|
||||
anywhere you like (for example `/usr/local/bin`).
|
||||
The binary will then be installed to `$GOPATH/bin` (or your `$GOBIN`).
|
||||
|
||||
The command `make install` will install the binary to `$GOPATH/bin` or `$GOBIN`.
|
||||
Please make sure that when you are working with micro's code, you are working on your `GOPATH`.
|
||||
|
||||
You can install directly with `go get` (`go get github.com/zyedidia/micro/cmd/micro`) but this isn't
|
||||
recommended because it doesn't build micro with version information (necessary for the plugin manager),
|
||||
and doesn't disable debug mode.
|
||||
You can install directly with `go get` (`go get -u github.com/zyedidia/micro/cmd/micro`) but this isn't recommended because it doesn't build micro with version information which is useful for the plugin manager.
|
||||
|
||||
### Fully static binary
|
||||
### MacOS terminal
|
||||
|
||||
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
|
||||
If you are using MacOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default Mac terminal. The iTerm2 terminal has much better mouse support as well as better handling of key events. The newest versions also support true color.
|
||||
|
||||
```
|
||||
CGO_ENABLED=0 make build
|
||||
### Linux clipboard support
|
||||
|
||||
On Linux, clipboard support requires the 'xclip' or 'xsel' commands to be installed.
|
||||
|
||||
For Ubuntu:
|
||||
|
||||
```sh
|
||||
sudo apt-get install xclip
|
||||
```
|
||||
|
||||
### macOS terminal
|
||||
|
||||
If you are using macOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default terminal (Terminal.app). The iTerm2 terminal has much better mouse support as well as better handling of key events. For best keybinding behavior, choose `xterm defaults` under `Preferences->Profiles->Keys->Presets...`, and select `Esc+` for `Left Option Key` in the same menu. The newest versions also support true color.
|
||||
|
||||
If you still insist on using the default Mac terminal, be sure to set `Use Option key as Meta key` under
|
||||
`Preferences->Profiles->Keyboard` to use <kbd>option</kbd> as <kbd>alt</kbd>.
|
||||
|
||||
### WSL and Windows Console
|
||||
|
||||
If you use micro within WSL, it is highly recommended that you use the [Windows
|
||||
Terminal](https://apps.microsoft.com/store/detail/windows-terminal/9N0DX20HK701?hl=en-us&gl=us)
|
||||
instead of the default Windows Console.
|
||||
|
||||
If you must use Windows Console for some reason, note that there is a bug in
|
||||
Windows Console WSL that causes a font change whenever micro tries to access
|
||||
the external clipboard via powershell. To fix this, use an internal clipboard
|
||||
with `set clipboard internal` (though your system clipboard will no longer be
|
||||
available in micro).
|
||||
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.
|
||||
|
||||
### Colors and syntax highlighting
|
||||
|
||||
If you open micro and it doesn't seem like syntax highlighting is working, this is probably because
|
||||
you are using a terminal which does not support 256 color mode. Try changing the color scheme to `simple`
|
||||
by pressing <kbd>Ctrl-e</kbd> in micro and typing `set colorscheme simple`.
|
||||
you are using a terminal which does not support 256 color. Try changing the colorscheme to `simple`
|
||||
by pressing CtrlE in micro and typing `set colorscheme simple`.
|
||||
|
||||
If you are using the default Ubuntu terminal, to enable 256 color mode make sure your `TERM` variable is set
|
||||
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 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.
|
||||
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.
|
||||
|
||||
### Cygwin, Mingw, Plan9
|
||||
### Plan9, Cygwin
|
||||
|
||||
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
|
||||
Please note that micro uses the amazing [tcell library](https://github.com/gdamore/tcell), but this
|
||||
means that micro is restricted to the platforms tcell supports. As a result, micro does not support
|
||||
Plan9 or Cygwin (although this may change in the future). Micro also doesn't support NaCl (which is deprecated anyway).
|
||||
Plan9, and Cygwin (although this may change in the future). Micro also doesn't support NaCl (but NaCl is deprecated anyways).
|
||||
|
||||
## Usage
|
||||
# Usage
|
||||
|
||||
Once you have built the editor, start it by running `micro path/to/file.txt` or `micro` to open an empty buffer.
|
||||
Once you have built the editor, simply start it by running `micro path/to/file.txt` or simply `micro` to open an empty buffer.
|
||||
|
||||
micro also supports creating buffers from `stdin`:
|
||||
Micro also supports creating buffers from `stdin`:
|
||||
|
||||
```sh
|
||||
ip a | micro
|
||||
ifconfig | micro
|
||||
```
|
||||
|
||||
You can move the cursor around with the arrow keys and mouse.
|
||||
|
@ -259,30 +165,26 @@ 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 <kbd>Ctrl-e</kbd> and typing `help`. Additionally, you can
|
||||
Micro has a built-in help system which you can access by pressing `Ctrl-E` 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.
|
||||
|
||||
There is also an unofficial Discord, which you can join at https://discord.gg/nhWR6armnR.
|
||||
|
||||
## Contributing
|
||||
# Contributing
|
||||
|
||||
If you find any bugs, please report them! I am also happy to accept pull requests from anyone.
|
||||
|
||||
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) or the [Discord](https://discord.gg/nhWR6armnR). You can also use the [Discussions](https://github.com/zyedidia/micro/discussions) section on Github for a forum-like setting or for Q&A.
|
||||
|
||||
Sometimes I am unresponsive, and I apologize! If that happens, please ping me.
|
||||
For a more informal setting to discuss the editor, you can join the [Gitter chat](https://gitter.im/zyedidia/micro).
|
||||
|
|
BIN
assets/logo.png
Normal file
BIN
assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.5 KiB |
|
@ -1,109 +0,0 @@
|
|||
<?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
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 304.70001 103.2"
|
||||
enable-background="new 0 0 960 560"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="micro-logo-drop.svg"
|
||||
width="304.70001"
|
||||
height="103.2"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><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" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs19"><filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
inkscape:label="Blur"
|
||||
id="filter1040"
|
||||
x="-0.028037383"
|
||||
y="-0.10549451"
|
||||
width="1.0560748"
|
||||
height="1.210989"><feGaussianBlur
|
||||
stdDeviation="2 2"
|
||||
result="blur"
|
||||
id="feGaussianBlur1038" /></filter></defs><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1080"
|
||||
id="namedview17"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="13.204388"
|
||||
inkscape:cx="71.832181"
|
||||
inkscape:cy="63.956011"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:deskcolor="#d1d1d1" /><g
|
||||
id="g838"
|
||||
transform="translate(-178,-172.8)"
|
||||
style="fill:#ffffff;fill-opacity:1;filter:url(#filter1040)"><path
|
||||
d="m 306.8,213.8 v -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 h 2.3 v 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 v 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 h 1 v 2.6 h -15.5 v -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 v -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 v 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 v 2.6 h -15.3 v -2.6 h 0.9 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 v -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 v 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 h 1.1 v 2.6 h -15.6 v -2.6 h 0.8 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 v -18.1 h -5.1 z"
|
||||
id="path828"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff;fill-opacity:1" /><path
|
||||
d="m 366.4,213.7 v -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 h 2.3 v 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 h 1.5 v 2.6 h -15.9 v -2.6 h 1.3 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 v -18.3 h -5.4 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="path830"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff;fill-opacity:1" /><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="path832"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff;fill-opacity:1" /><path
|
||||
d="m 418.7,213.7 v -2.6 c 1.5,-0.1 2.8,-0.4 4,-0.8 1.2,-0.4 2.5,-1 4,-1.9 h 2.3 v 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 V 232 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 h 1 v 2.6 h -16 v -2.6 h 1.3 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 v -18.3 h -5.1 z"
|
||||
id="path834"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff;fill-opacity:1" /><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="path836"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff;fill-opacity:1" /></g><g
|
||||
id="g3"
|
||||
transform="translate(-178,-172.8)"><path
|
||||
d="m 306.8,213.8 v -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 h 2.3 v 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 v 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 h 1 v 2.6 h -15.5 v -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 v -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 v 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 v 2.6 h -15.3 v -2.6 h 0.9 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 v -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 v 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 h 1.1 v 2.6 h -15.6 v -2.6 h 0.8 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 v -18.1 h -5.1 z"
|
||||
id="path5"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 366.4,213.7 v -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 h 2.3 v 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 h 1.5 v 2.6 h -15.9 v -2.6 h 1.3 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 v -18.3 h -5.4 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 v -2.6 c 1.5,-0.1 2.8,-0.4 4,-0.8 1.2,-0.4 2.5,-1 4,-1.9 h 2.3 v 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 V 232 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 h 1 v 2.6 h -16 v -2.6 h 1.3 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 v -18.3 h -5.1 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 h -0.2 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 h -0.7 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 h 0.8 c 0.2,2.9 -0.1,5.3 -0.6,7.3 z"
|
||||
id="path15"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#2e3192" /><path
|
||||
style="fill:#ffffff;stroke-width:0.0757324"
|
||||
d="m 30.026506,86.559353 c -1.017302,-0.241662 -1.787869,-0.887419 -2.143612,-1.796406 -0.545654,-1.394246 -0.158934,-4.812615 1.126179,-9.954732 1.255925,-5.025324 2.459082,-9.096362 5.109736,-17.289458 0.344312,-1.064257 1.654133,-5.2136 1.888607,-5.982859 0.296596,-0.97307 0.598551,-2.708021 0.79743,-4.581811 0.108312,-1.020494 0.246431,-2.186451 0.306932,-2.591018 0.0605,-0.404565 0.178758,-1.341754 0.262796,-2.082641 0.224837,-1.982189 0.649291,-5.218012 0.916787,-6.98913 0.444542,-2.943359 0.753682,-4.198397 1.354756,-5.499991 0.686842,-1.487323 1.771061,-2.655188 2.805126,-3.021538 0.542395,-0.19216 1.381388,-0.270583 1.982594,-0.185316 1.252526,0.17764 1.883508,0.754167 2.211742,2.020866 0.313761,1.21084 -0.05565,3.930951 -0.877141,6.458782 -1.290698,3.971623 -2.036395,5.990995 -2.986916,8.088674 -1.185138,2.61545 -2.712212,6.873258 -2.939609,8.196258 -0.49042,2.853282 0.04972,5.146283 1.578225,6.6999 0.913915,0.928929 2.023939,1.521458 3.413442,1.82209 0.903748,0.195534 2.608483,0.179674 3.407958,-0.03171 1.383427,-0.365777 2.763884,-1.250325 4.377299,-2.804821 3.163126,-3.047616 5.113532,-6.222841 6.797438,-11.066108 0.353971,-1.018094 0.493359,-1.574562 0.749316,-2.991429 0.271014,-1.500218 1.040858,-5.574621 1.51657,-8.026458 0.08082,-0.416528 0.218253,-1.149239 0.305416,-1.628246 0.472088,-2.594388 1.148516,-4.178722 2.330295,-5.458032 0.763841,-0.826879 1.674493,-1.206419 2.894632,-1.206419 1.24359,0 2.138991,0.401576 2.574266,1.154526 0.974305,1.685378 0.683954,4.053139 -1.163626,9.489195 -0.954432,2.808181 -2.572717,6.998752 -3.493593,9.046702 -0.971745,2.161077 -2.201912,5.041664 -2.441809,5.717796 l -0.268706,0.757324 0.09021,1.120423 c 0.212423,2.638199 0.889316,4.086035 2.469149,5.281365 0.932959,0.705895 1.786459,0.982601 3.026274,0.981126 2.426542,-0.0029 4.480731,-1.028876 5.685658,-2.839769 0.811784,-1.220036 1.58443,-3.158397 2.044887,-5.130071 l 0.207813,-0.889855 h 0.356374 0.356373 l 0.04799,0.892492 c 0.0554,1.030319 -0.04881,3.015268 -0.219241,4.175846 -0.345822,2.354993 -1.040859,4.427262 -1.983165,5.91286 -0.701565,1.106055 -1.958204,2.491062 -2.717404,2.994989 -1.555814,1.032691 -4.187858,1.499135 -6.161832,1.091984 -0.603718,-0.124523 -1.72865,-0.689523 -2.178956,-1.094387 -1.477985,-1.328835 -2.187139,-3.341642 -2.360358,-6.699454 -0.08196,-1.588814 0.0522,-3.504923 0.298559,-4.263967 0.05681,-0.175039 0.04587,-0.208265 -0.06857,-0.208265 -0.09667,0 -0.197671,0.148268 -0.348229,0.511194 -0.711765,1.715746 -1.965261,3.867832 -3.142896,5.395934 -0.680786,0.883388 -2.612844,2.822501 -3.483678,3.496397 -2.517073,1.947843 -5.073167,2.951502 -8.060525,3.164993 -1.592379,0.1138 -2.868371,-0.07567 -4.016971,-0.596469 -1.69649,-0.769225 -3.109446,-2.469115 -3.819014,-4.594555 -0.614034,-1.839276 -0.863382,-4.754214 -0.580679,-6.788275 0.05951,-0.428202 0.126068,-0.957467 0.147897,-1.176145 l 0.03969,-0.397595 H 37.651633 37.254872 L 36.96284,53.90253 c -0.705326,1.783387 -1.458627,4.293583 -2.085205,6.948448 -1.027173,4.352223 -1.56307,7.486558 -2.197428,12.852248 -0.310323,2.624858 -0.310577,2.629265 -0.189513,3.294359 0.13956,0.766706 0.417018,1.85334 0.68249,2.672894 0.306093,0.944956 0.565598,2.296449 0.565598,2.945615 0,1.819491 -0.751236,3.258298 -2.006909,3.84374 -0.402074,0.187462 -1.15114,0.231172 -1.705369,0.09951 z"
|
||||
id="path218" /></svg>
|
Before Width: | Height: | Size: 16 KiB |
|
@ -1,59 +0,0 @@
|
|||
<?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
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 103.2 103.2"
|
||||
enable-background="new 0 0 960 560"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="micro-logo-mark.svg"
|
||||
width="103.2"
|
||||
height="103.2"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
||||
id="metadata9"><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 /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs7" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1080"
|
||||
id="namedview5"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="5.405335"
|
||||
inkscape:cx="75.573484"
|
||||
inkscape:cy="51.153166"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="Layer_1"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1" /><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="path3"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#2e3192" /><path
|
||||
style="fill:#ffffff;stroke-width:0.185002"
|
||||
d="m 29.320064,86.164872 c -1.277771,-0.647664 -1.573829,-1.327981 -1.549788,-3.561297 0.04016,-3.730697 1.622887,-10.030031 5.903272,-23.495306 2.770635,-8.715885 2.799071,-8.822813 3.148729,-11.840154 0.585284,-5.050637 1.565844,-12.45598 1.8369,-13.872547 0.43516,-2.274196 0.976755,-3.690519 1.880879,-4.918684 0.974445,-1.323691 1.896478,-1.826405 3.360953,-1.832474 3.009215,-0.01247 3.55713,2.574946 1.786201,8.434969 -0.742771,2.45784 -2.2493,6.487571 -3.407575,9.114735 -0.420971,0.954834 -1.151241,2.827983 -1.622823,4.162554 -0.839682,2.376289 -0.857669,2.47434 -0.869358,4.739023 -0.01095,2.122185 0.02796,2.3976 0.472736,3.346042 0.91751,1.956495 2.602228,3.131322 5.078862,3.541714 2.587757,0.428804 4.551892,-0.347899 7.187533,-2.842264 2.232774,-2.113092 3.746907,-4.117682 4.998184,-6.617188 1.816108,-3.627792 2.213624,-4.978174 3.527565,-11.983266 0.66466,-3.543546 1.376157,-6.951356 1.581104,-7.57291 0.970636,-2.943689 2.922262,-4.567831 5.096985,-4.241711 1.740397,0.260989 2.500104,1.361773 2.494406,3.614287 -0.0068,2.696563 -2.48184,9.966491 -6.424307,18.870246 l -1.269708,2.867537 0.02005,1.757523 c 0.01504,1.318294 0.119434,2.015481 0.417735,2.789716 1.028756,2.67011 3.517063,4.054736 6.342356,3.529224 3.19144,-0.593617 4.98902,-2.612828 6.217715,-6.984325 0.403553,-1.435775 0.552101,-1.739647 0.850428,-1.739647 0.34646,0 0.356492,0.101757 0.241656,2.451282 -0.238951,4.888854 -1.330826,7.853563 -3.80789,10.339358 -1.255532,1.259957 -1.547319,1.456015 -2.694109,1.81022 -1.395674,0.431082 -3.784736,0.537505 -4.865716,0.216749 -1.759682,-0.522141 -3.031085,-2.027386 -3.686869,-4.364972 -0.336042,-1.197843 -0.516218,-5.455318 -0.283812,-6.706338 0.266094,-1.432359 -0.105859,-1.235144 -0.879069,0.466093 -1.724383,3.794037 -4.750586,7.236231 -8.063683,9.172148 -2.368072,1.383716 -5.903865,2.143782 -8.230062,1.769159 -2.672688,-0.430424 -4.588062,-2.213422 -5.66376,-5.272324 -0.491128,-1.396592 -0.514658,-1.618704 -0.512739,-4.840059 0.0018,-3.093063 -0.02515,-3.376294 -0.321772,-3.376294 -0.414677,0 -0.706335,0.582138 -1.434591,2.863386 -1.443227,4.52088 -2.73082,10.895957 -3.516703,17.411762 l -0.381426,3.162426 0.469219,1.740138 c 0.927877,3.441104 1.066474,4.326417 0.841521,5.375336 -0.537458,2.506081 -2.272098,3.528416 -4.269226,2.516133 z"
|
||||
id="path210" /></svg>
|
Before Width: | Height: | Size: 6.2 KiB |
|
@ -1,76 +0,0 @@
|
|||
<?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
|
||||
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="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="micro-logo.svg"
|
||||
width="299.89999"
|
||||
height="103.2"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><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 /></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="1920"
|
||||
inkscape:window-height="1080"
|
||||
id="namedview17"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="16.645603"
|
||||
inkscape:cx="65.092264"
|
||||
inkscape:cy="49.051992"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="Layer_1"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1" /><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" /><path
|
||||
style="fill:#ffffff;stroke-width:0.0600759"
|
||||
d="m 30.192709,86.597991 c -0.530828,-0.09608 -1.19875,-0.411872 -1.578921,-0.746511 -0.792953,-0.697985 -1.054327,-1.680313 -0.947823,-3.562219 0.16271,-2.875042 0.852662,-6.034057 2.963728,-13.569713 0.66017,-2.356543 0.955814,-3.307037 3.762987,-12.097989 1.219825,-3.820007 1.435496,-4.505244 1.616654,-5.136492 0.306236,-1.067081 0.590331,-2.663175 0.753866,-4.235353 0.08592,-0.826044 0.236455,-2.096649 0.334514,-2.823568 0.09806,-0.726919 0.246246,-1.916422 0.329306,-2.643341 0.08306,-0.726918 0.231698,-1.902905 0.330307,-2.613302 0.09861,-0.710398 0.231242,-1.724179 0.294741,-2.252848 0.19473,-1.621264 0.604712,-4.037809 0.845956,-4.986301 0.495326,-1.947452 1.158621,-3.216325 2.26111,-4.325467 0.731983,-0.736399 1.547763,-1.051329 2.723316,-1.051329 1.344787,0 2.103359,0.409522 2.539237,1.370828 0.373167,0.823003 0.432731,1.702332 0.227502,3.358553 -0.206897,1.669687 -0.429401,2.498899 -1.62432,6.053417 -0.891865,2.653022 -1.418886,4.025585 -2.237847,5.828196 -0.890733,1.960586 -1.401439,3.281416 -2.291175,5.925621 -0.696894,2.071095 -0.858755,3.003396 -0.79649,4.587665 0.05016,1.276299 0.270881,2.168068 0.761945,3.078469 1.114561,2.066325 3.341124,3.259541 6.082361,3.259541 0.831865,0 1.52957,-0.113832 2.245267,-0.366322 1.037155,-0.365895 1.69838,-0.767468 2.829986,-1.718697 2.058613,-1.730473 4.031033,-4.098263 5.356083,-6.429706 1.132231,-1.992175 2.742129,-5.986041 2.978686,-7.389579 0.126006,-0.747618 0.37151,-2.073261 0.753923,-4.070941 0.459374,-2.399719 0.965049,-5.073707 1.26106,-6.668427 0.439666,-2.368642 0.948255,-3.731056 1.831386,-4.905927 1.000947,-1.33161 1.919678,-1.818989 3.424905,-1.816884 1.371199,0.0019 2.259901,0.453797 2.692584,1.369104 0.199937,0.42295 0.37898,1.160518 0.431897,1.779189 0.0423,0.494585 -0.08313,1.707742 -0.270194,2.613303 -0.520247,2.51845 -2.995194,9.527499 -4.836622,13.697311 -0.189691,0.429543 -0.709117,1.619046 -1.154281,2.64334 -0.445164,1.024295 -0.903857,2.078627 -1.019317,2.342962 -0.593057,1.357747 -0.644155,1.607255 -0.563046,2.7493 0.142046,2.000035 0.604952,3.420811 1.436759,4.409774 0.719848,0.85585 1.902762,1.62255 2.859809,1.853569 0.533147,0.128695 1.669602,0.128252 2.472607,-9.67e-4 1.437635,-0.231339 2.769133,-0.900566 3.72751,-1.873493 1.098243,-1.114915 2.227996,-3.662559 2.785802,-6.282105 l 0.13752,-0.645816 h 0.37414 0.37414 l 0.04419,0.94284 c 0.124949,2.666054 -0.382363,6.016009 -1.237138,8.16926 -0.848692,2.137927 -2.617365,4.354096 -4.156972,5.208738 -1.58257,0.878493 -4.420415,1.19721 -6.111929,0.68643 -0.649563,-0.196146 -1.47209,-0.685817 -1.961392,-1.167665 -1.354216,-1.333585 -1.999054,-3.254244 -2.18916,-6.52045 -0.03525,-0.60571 -0.04689,-1.38515 -0.02584,-1.732089 0.04435,-0.731258 0.257009,-2.357205 0.335205,-2.562875 0.04613,-0.121335 0.03516,-0.140427 -0.08025,-0.139702 -0.11259,7.09e-4 -0.171074,0.09313 -0.370649,0.58574 -0.571777,1.411317 -1.625409,3.288777 -2.58713,4.609988 -2.555402,3.510606 -5.935984,6.014779 -9.311242,6.897323 -1.386313,0.362485 -1.927076,0.42829 -3.514441,0.427668 -1.398071,-5.41e-4 -1.500695,-0.0084 -2.047014,-0.157216 -1.248806,-0.340101 -2.244463,-0.904197 -3.05944,-1.733346 -1.343156,-1.366511 -2.129105,-3.116872 -2.494126,-5.554581 -0.150028,-1.001927 -0.191427,-3.616227 -0.06949,-4.388291 0.05195,-0.328906 0.113311,-0.84947 0.136367,-1.156809 l 0.04192,-0.558799 -0.380315,0.01812 -0.380315,0.01812 -0.231805,0.570721 c -1.478913,3.641182 -3.072314,10.383891 -3.918324,16.580955 -0.190557,1.395837 -0.701916,5.676121 -0.706953,5.917479 -0.0093,0.446744 0.454257,2.427922 0.818884,3.499628 0.121802,0.358001 0.382754,1.549663 0.538684,2.459961 0.04595,0.268246 -0.06655,1.468043 -0.178759,1.906478 -0.165253,0.645686 -0.477741,1.20884 -0.915337,1.649588 -0.463951,0.467293 -0.819805,0.689321 -1.309045,0.816755 -0.410787,0.106995 -0.564727,0.106887 -1.159735,-7.81e-4 z"
|
||||
id="path240" /></svg>
|
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 289 KiB |
|
@ -1,9 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ]; then
|
||||
update-alternatives --install /usr/bin/editor editor /usr/bin/micro 40 \
|
||||
--slave /usr/share/man/man1/editor.1 editor.1 \
|
||||
/usr/share/man/man1/micro.1
|
||||
fi
|
|
@ -1,7 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$1" != "upgrade" ]; then
|
||||
update-alternatives --remove editor /usr/bin/micro
|
||||
fi
|
|
@ -1,125 +0,0 @@
|
|||
.TH micro 1 "2020-02-10"
|
||||
.SH NAME
|
||||
micro \- A modern and intuitive terminal-based text editor
|
||||
.SH SYNOPSIS
|
||||
.B micro
|
||||
.RB [OPTIONS]
|
||||
[FILE]\&...
|
||||
|
||||
.SH DESCRIPTION
|
||||
|
||||
Micro is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the full capabilities
|
||||
of modern terminals. It comes as one single, batteries-included, static binary with no dependencies.
|
||||
|
||||
As the name indicates, micro aims to be somewhat of a successor to the nano editor by being easy to install and use in a pinch, but micro also aims to be
|
||||
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
|
||||
|
||||
Use Ctrl-q to quit, Ctrl-s to save, and Ctrl-g to open the in-editor help menu.
|
||||
|
||||
.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
|
||||
[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 plugins 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 $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
|
||||
micro and is not intended to replace the full documentation included with micro
|
||||
which can be accessed from within micro. Micro tells you what key combination to
|
||||
press to get help in the lower right.
|
||||
|
||||
.SH BUGS
|
||||
A comprehensive list of bugs will not be listed in this manpage. See the Github
|
||||
page at \fBhttps://github.com/zyedidia/micro/issues\fP for a list of known bugs
|
||||
and to report any newly encountered bugs you may find. We strive to correct
|
||||
bugs as swiftly as possible.
|
||||
|
||||
.SH COPYRIGHT
|
||||
Copyright \(co 2020 Zachary Yedidia, et al. MIT license.
|
||||
See \fBhttps://github.com/zyedidia/micro\fP for details.
|
|
@ -1,15 +0,0 @@
|
|||
[Desktop Entry]
|
||||
|
||||
Name=Micro
|
||||
GenericName=Text Editor
|
||||
Comment=Edit text files in a terminal
|
||||
|
||||
Icon=micro
|
||||
Type=Application
|
||||
Categories=Utility;TextEditor;Development;
|
||||
Keywords=text;editor;syntax;terminal;
|
||||
|
||||
Exec=micro %F
|
||||
StartupNotify=false
|
||||
Terminal=true
|
||||
MimeType=text/plain;text/x-chdr;text/x-csrc;text/x-c++hdr;text/x-c++src;text/x-java;text/x-dsrc;text/x-pascal;text/x-perl;text/x-python;application/x-php;application/x-httpd-php3;application/x-httpd-php4;application/x-httpd-php5;application/xml;text/html;text/css;text/x-sql;text/x-diff;
|
2066
cmd/micro/actions.go
Normal file
2066
cmd/micro/actions.go
Normal file
File diff suppressed because it is too large
Load diff
9
cmd/micro/actions_other.go
Normal file
9
cmd/micro/actions_other.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
// +build android plan9 nacl windows
|
||||
|
||||
package main
|
||||
|
||||
func (v *View) Suspend(usePlugin bool) bool {
|
||||
messenger.Error("Suspend is only supported on Linux")
|
||||
|
||||
return false
|
||||
}
|
|
@ -1,27 +1,37 @@
|
|||
// +build linux darwin dragonfly solaris openbsd netbsd freebsd
|
||||
|
||||
package action
|
||||
package main
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
)
|
||||
import "syscall"
|
||||
|
||||
// Suspend sends micro to the background. This is the same as pressing CtrlZ in most unix programs.
|
||||
// This only works on linux and has no default binding.
|
||||
// This code was adapted from the suspend code in nsf/godit
|
||||
func (*BufPane) Suspend() bool {
|
||||
screenb := screen.TempFini()
|
||||
func (v *View) Suspend(usePlugin bool) bool {
|
||||
if usePlugin && !PreActionCall("Suspend", v) {
|
||||
return false
|
||||
}
|
||||
|
||||
screenWasNil := screen == nil
|
||||
|
||||
if !screenWasNil {
|
||||
screen.Fini()
|
||||
screen = nil
|
||||
}
|
||||
|
||||
// suspend the process
|
||||
pid := syscall.Getpid()
|
||||
err := syscall.Kill(pid, syscall.SIGSTOP)
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
TermMessage(err)
|
||||
}
|
||||
|
||||
screen.TempStart(screenb)
|
||||
if !screenWasNil {
|
||||
InitScreen()
|
||||
}
|
||||
|
||||
return false
|
||||
if usePlugin {
|
||||
return PostActionCall("Suspend", v)
|
||||
}
|
||||
return true
|
||||
}
|
196
cmd/micro/autocomplete.go
Normal file
196
cmd/micro/autocomplete.go
Normal file
|
@ -0,0 +1,196 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
var pluginCompletions []func(string) []string
|
||||
|
||||
// This file is meant (for now) for autocompletion in command mode, not
|
||||
// while coding. This helps micro autocomplete commands and then filenames
|
||||
// for example with `vsplit filename`.
|
||||
|
||||
// FileComplete autocompletes filenames
|
||||
func FileComplete(input string) (string, []string) {
|
||||
var sep string = string(os.PathSeparator)
|
||||
dirs := strings.Split(input, sep)
|
||||
|
||||
var files []os.FileInfo
|
||||
var err error
|
||||
if len(dirs) > 1 {
|
||||
home, _ := homedir.Dir()
|
||||
|
||||
directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
|
||||
|
||||
if strings.HasPrefix(directories, "~") {
|
||||
directories = strings.Replace(directories, "~", home, 1)
|
||||
}
|
||||
files, err = ioutil.ReadDir(directories)
|
||||
} else {
|
||||
files, err = ioutil.ReadDir(".")
|
||||
}
|
||||
|
||||
var suggestions []string
|
||||
if err != nil {
|
||||
return "", suggestions
|
||||
}
|
||||
for _, f := range files {
|
||||
name := f.Name()
|
||||
if f.IsDir() {
|
||||
name += sep
|
||||
}
|
||||
if strings.HasPrefix(name, dirs[len(dirs)-1]) {
|
||||
suggestions = append(suggestions, name)
|
||||
}
|
||||
}
|
||||
|
||||
var chosen string
|
||||
if len(suggestions) == 1 {
|
||||
if len(dirs) > 1 {
|
||||
chosen = strings.Join(dirs[:len(dirs)-1], sep) + sep + suggestions[0]
|
||||
} else {
|
||||
chosen = suggestions[0]
|
||||
}
|
||||
} else {
|
||||
if len(dirs) > 1 {
|
||||
chosen = strings.Join(dirs[:len(dirs)-1], sep) + sep
|
||||
}
|
||||
}
|
||||
|
||||
return chosen, suggestions
|
||||
}
|
||||
|
||||
// CommandComplete autocompletes commands
|
||||
func CommandComplete(input string) (string, []string) {
|
||||
var suggestions []string
|
||||
for cmd := range commands {
|
||||
if strings.HasPrefix(cmd, input) {
|
||||
suggestions = append(suggestions, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
var chosen string
|
||||
if len(suggestions) == 1 {
|
||||
chosen = suggestions[0]
|
||||
}
|
||||
return chosen, suggestions
|
||||
}
|
||||
|
||||
// HelpComplete autocompletes help topics
|
||||
func HelpComplete(input string) (string, []string) {
|
||||
var suggestions []string
|
||||
|
||||
for _, file := range ListRuntimeFiles(RTHelp) {
|
||||
topic := file.Name()
|
||||
if strings.HasPrefix(topic, input) {
|
||||
suggestions = append(suggestions, topic)
|
||||
}
|
||||
}
|
||||
|
||||
var chosen string
|
||||
if len(suggestions) == 1 {
|
||||
chosen = suggestions[0]
|
||||
}
|
||||
return chosen, suggestions
|
||||
}
|
||||
|
||||
// ColorschemeComplete tab-completes names of colorschemes.
|
||||
func ColorschemeComplete(input string) (string, []string) {
|
||||
var suggestions []string
|
||||
files := ListRuntimeFiles(RTColorscheme)
|
||||
|
||||
for _, f := range files {
|
||||
if strings.HasPrefix(f.Name(), input) {
|
||||
suggestions = append(suggestions, f.Name())
|
||||
}
|
||||
}
|
||||
|
||||
var chosen string
|
||||
if len(suggestions) == 1 {
|
||||
chosen = suggestions[0]
|
||||
}
|
||||
|
||||
return chosen, suggestions
|
||||
}
|
||||
|
||||
func contains(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// OptionComplete autocompletes options
|
||||
func OptionComplete(input string) (string, []string) {
|
||||
var suggestions []string
|
||||
localSettings := DefaultLocalSettings()
|
||||
for option := range globalSettings {
|
||||
if strings.HasPrefix(option, input) {
|
||||
suggestions = append(suggestions, option)
|
||||
}
|
||||
}
|
||||
for option := range localSettings {
|
||||
if strings.HasPrefix(option, input) && !contains(suggestions, option) {
|
||||
suggestions = append(suggestions, option)
|
||||
}
|
||||
}
|
||||
|
||||
var chosen string
|
||||
if len(suggestions) == 1 {
|
||||
chosen = suggestions[0]
|
||||
}
|
||||
return chosen, suggestions
|
||||
}
|
||||
|
||||
// MakeCompletion registers a function from a plugin for autocomplete commands
|
||||
func MakeCompletion(function string) Completion {
|
||||
pluginCompletions = append(pluginCompletions, LuaFunctionComplete(function))
|
||||
return Completion(-len(pluginCompletions))
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func PluginNameComplete(input string) (chosen string, suggestions []string) {
|
||||
for _, pp := range GetAllPluginPackages() {
|
||||
if strings.HasPrefix(pp.Name, input) {
|
||||
suggestions = append(suggestions, pp.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(suggestions) == 1 {
|
||||
chosen = suggestions[0]
|
||||
}
|
||||
return chosen, suggestions
|
||||
}
|
532
cmd/micro/bindings.go
Normal file
532
cmd/micro/bindings.go
Normal file
|
@ -0,0 +1,532 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/flynn/json5"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
var bindings map[Key][]func(*View, bool) bool
|
||||
var mouseBindings map[Key][]func(*View, bool, *tcell.EventMouse) bool
|
||||
var helpBinding string
|
||||
|
||||
var mouseBindingActions = map[string]func(*View, bool, *tcell.EventMouse) bool{
|
||||
"MousePress": (*View).MousePress,
|
||||
"MouseMultiCursor": (*View).MouseMultiCursor,
|
||||
}
|
||||
|
||||
var bindingActions = map[string]func(*View, bool) bool{
|
||||
"CursorUp": (*View).CursorUp,
|
||||
"CursorDown": (*View).CursorDown,
|
||||
"CursorPageUp": (*View).CursorPageUp,
|
||||
"CursorPageDown": (*View).CursorPageDown,
|
||||
"CursorLeft": (*View).CursorLeft,
|
||||
"CursorRight": (*View).CursorRight,
|
||||
"CursorStart": (*View).CursorStart,
|
||||
"CursorEnd": (*View).CursorEnd,
|
||||
"SelectToStart": (*View).SelectToStart,
|
||||
"SelectToEnd": (*View).SelectToEnd,
|
||||
"SelectUp": (*View).SelectUp,
|
||||
"SelectDown": (*View).SelectDown,
|
||||
"SelectLeft": (*View).SelectLeft,
|
||||
"SelectRight": (*View).SelectRight,
|
||||
"WordRight": (*View).WordRight,
|
||||
"WordLeft": (*View).WordLeft,
|
||||
"SelectWordRight": (*View).SelectWordRight,
|
||||
"SelectWordLeft": (*View).SelectWordLeft,
|
||||
"DeleteWordRight": (*View).DeleteWordRight,
|
||||
"DeleteWordLeft": (*View).DeleteWordLeft,
|
||||
"SelectToStartOfLine": (*View).SelectToStartOfLine,
|
||||
"SelectToEndOfLine": (*View).SelectToEndOfLine,
|
||||
"InsertNewline": (*View).InsertNewline,
|
||||
"InsertSpace": (*View).InsertSpace,
|
||||
"Backspace": (*View).Backspace,
|
||||
"Delete": (*View).Delete,
|
||||
"InsertTab": (*View).InsertTab,
|
||||
"Save": (*View).Save,
|
||||
"SaveAll": (*View).SaveAll,
|
||||
"SaveAs": (*View).SaveAs,
|
||||
"Find": (*View).Find,
|
||||
"FindNext": (*View).FindNext,
|
||||
"FindPrevious": (*View).FindPrevious,
|
||||
"Center": (*View).Center,
|
||||
"Undo": (*View).Undo,
|
||||
"Redo": (*View).Redo,
|
||||
"Copy": (*View).Copy,
|
||||
"Cut": (*View).Cut,
|
||||
"CutLine": (*View).CutLine,
|
||||
"DuplicateLine": (*View).DuplicateLine,
|
||||
"DeleteLine": (*View).DeleteLine,
|
||||
"MoveLinesUp": (*View).MoveLinesUp,
|
||||
"MoveLinesDown": (*View).MoveLinesDown,
|
||||
"IndentSelection": (*View).IndentSelection,
|
||||
"OutdentSelection": (*View).OutdentSelection,
|
||||
"OutdentLine": (*View).OutdentLine,
|
||||
"Paste": (*View).Paste,
|
||||
"PastePrimary": (*View).PastePrimary,
|
||||
"SelectAll": (*View).SelectAll,
|
||||
"OpenFile": (*View).OpenFile,
|
||||
"Start": (*View).Start,
|
||||
"End": (*View).End,
|
||||
"PageUp": (*View).PageUp,
|
||||
"PageDown": (*View).PageDown,
|
||||
"HalfPageUp": (*View).HalfPageUp,
|
||||
"HalfPageDown": (*View).HalfPageDown,
|
||||
"StartOfLine": (*View).StartOfLine,
|
||||
"EndOfLine": (*View).EndOfLine,
|
||||
"ToggleHelp": (*View).ToggleHelp,
|
||||
"ToggleRuler": (*View).ToggleRuler,
|
||||
"JumpLine": (*View).JumpLine,
|
||||
"ClearStatus": (*View).ClearStatus,
|
||||
"ShellMode": (*View).ShellMode,
|
||||
"CommandMode": (*View).CommandMode,
|
||||
"Escape": (*View).Escape,
|
||||
"Quit": (*View).Quit,
|
||||
"QuitAll": (*View).QuitAll,
|
||||
"AddTab": (*View).AddTab,
|
||||
"PreviousTab": (*View).PreviousTab,
|
||||
"NextTab": (*View).NextTab,
|
||||
"NextSplit": (*View).NextSplit,
|
||||
"PreviousSplit": (*View).PreviousSplit,
|
||||
"Unsplit": (*View).Unsplit,
|
||||
"VSplit": (*View).VSplitBinding,
|
||||
"HSplit": (*View).HSplitBinding,
|
||||
"ToggleMacro": (*View).ToggleMacro,
|
||||
"PlayMacro": (*View).PlayMacro,
|
||||
"Suspend": (*View).Suspend,
|
||||
"ScrollUp": (*View).ScrollUpAction,
|
||||
"ScrollDown": (*View).ScrollDownAction,
|
||||
"SpawnMultiCursor": (*View).SpawnMultiCursor,
|
||||
"RemoveMultiCursor": (*View).RemoveMultiCursor,
|
||||
"RemoveAllMultiCursors": (*View).RemoveAllMultiCursors,
|
||||
"SkipMultiCursor": (*View).SkipMultiCursor,
|
||||
|
||||
// This was changed to InsertNewline but I don't want to break backwards compatibility
|
||||
"InsertEnter": (*View).InsertNewline,
|
||||
}
|
||||
|
||||
var bindingMouse = map[string]tcell.ButtonMask{
|
||||
"MouseLeft": tcell.Button1,
|
||||
"MouseMiddle": tcell.Button2,
|
||||
"MouseRight": tcell.Button3,
|
||||
"MouseWheelUp": tcell.WheelUp,
|
||||
"MouseWheelDown": tcell.WheelDown,
|
||||
"MouseWheelLeft": tcell.WheelLeft,
|
||||
"MouseWheelRight": tcell.WheelRight,
|
||||
}
|
||||
|
||||
var bindingKeys = map[string]tcell.Key{
|
||||
"Up": tcell.KeyUp,
|
||||
"Down": tcell.KeyDown,
|
||||
"Right": tcell.KeyRight,
|
||||
"Left": tcell.KeyLeft,
|
||||
"UpLeft": tcell.KeyUpLeft,
|
||||
"UpRight": tcell.KeyUpRight,
|
||||
"DownLeft": tcell.KeyDownLeft,
|
||||
"DownRight": tcell.KeyDownRight,
|
||||
"Center": tcell.KeyCenter,
|
||||
"PageUp": tcell.KeyPgUp,
|
||||
"PageDown": tcell.KeyPgDn,
|
||||
"Home": tcell.KeyHome,
|
||||
"End": tcell.KeyEnd,
|
||||
"Insert": tcell.KeyInsert,
|
||||
"Delete": tcell.KeyDelete,
|
||||
"Help": tcell.KeyHelp,
|
||||
"Exit": tcell.KeyExit,
|
||||
"Clear": tcell.KeyClear,
|
||||
"Cancel": tcell.KeyCancel,
|
||||
"Print": tcell.KeyPrint,
|
||||
"Pause": tcell.KeyPause,
|
||||
"Backtab": tcell.KeyBacktab,
|
||||
"F1": tcell.KeyF1,
|
||||
"F2": tcell.KeyF2,
|
||||
"F3": tcell.KeyF3,
|
||||
"F4": tcell.KeyF4,
|
||||
"F5": tcell.KeyF5,
|
||||
"F6": tcell.KeyF6,
|
||||
"F7": tcell.KeyF7,
|
||||
"F8": tcell.KeyF8,
|
||||
"F9": tcell.KeyF9,
|
||||
"F10": tcell.KeyF10,
|
||||
"F11": tcell.KeyF11,
|
||||
"F12": tcell.KeyF12,
|
||||
"F13": tcell.KeyF13,
|
||||
"F14": tcell.KeyF14,
|
||||
"F15": tcell.KeyF15,
|
||||
"F16": tcell.KeyF16,
|
||||
"F17": tcell.KeyF17,
|
||||
"F18": tcell.KeyF18,
|
||||
"F19": tcell.KeyF19,
|
||||
"F20": tcell.KeyF20,
|
||||
"F21": tcell.KeyF21,
|
||||
"F22": tcell.KeyF22,
|
||||
"F23": tcell.KeyF23,
|
||||
"F24": tcell.KeyF24,
|
||||
"F25": tcell.KeyF25,
|
||||
"F26": tcell.KeyF26,
|
||||
"F27": tcell.KeyF27,
|
||||
"F28": tcell.KeyF28,
|
||||
"F29": tcell.KeyF29,
|
||||
"F30": tcell.KeyF30,
|
||||
"F31": tcell.KeyF31,
|
||||
"F32": tcell.KeyF32,
|
||||
"F33": tcell.KeyF33,
|
||||
"F34": tcell.KeyF34,
|
||||
"F35": tcell.KeyF35,
|
||||
"F36": tcell.KeyF36,
|
||||
"F37": tcell.KeyF37,
|
||||
"F38": tcell.KeyF38,
|
||||
"F39": tcell.KeyF39,
|
||||
"F40": tcell.KeyF40,
|
||||
"F41": tcell.KeyF41,
|
||||
"F42": tcell.KeyF42,
|
||||
"F43": tcell.KeyF43,
|
||||
"F44": tcell.KeyF44,
|
||||
"F45": tcell.KeyF45,
|
||||
"F46": tcell.KeyF46,
|
||||
"F47": tcell.KeyF47,
|
||||
"F48": tcell.KeyF48,
|
||||
"F49": tcell.KeyF49,
|
||||
"F50": tcell.KeyF50,
|
||||
"F51": tcell.KeyF51,
|
||||
"F52": tcell.KeyF52,
|
||||
"F53": tcell.KeyF53,
|
||||
"F54": tcell.KeyF54,
|
||||
"F55": tcell.KeyF55,
|
||||
"F56": tcell.KeyF56,
|
||||
"F57": tcell.KeyF57,
|
||||
"F58": tcell.KeyF58,
|
||||
"F59": tcell.KeyF59,
|
||||
"F60": tcell.KeyF60,
|
||||
"F61": tcell.KeyF61,
|
||||
"F62": tcell.KeyF62,
|
||||
"F63": tcell.KeyF63,
|
||||
"F64": tcell.KeyF64,
|
||||
"CtrlSpace": tcell.KeyCtrlSpace,
|
||||
"CtrlA": tcell.KeyCtrlA,
|
||||
"CtrlB": tcell.KeyCtrlB,
|
||||
"CtrlC": tcell.KeyCtrlC,
|
||||
"CtrlD": tcell.KeyCtrlD,
|
||||
"CtrlE": tcell.KeyCtrlE,
|
||||
"CtrlF": tcell.KeyCtrlF,
|
||||
"CtrlG": tcell.KeyCtrlG,
|
||||
"CtrlH": tcell.KeyCtrlH,
|
||||
"CtrlI": tcell.KeyCtrlI,
|
||||
"CtrlJ": tcell.KeyCtrlJ,
|
||||
"CtrlK": tcell.KeyCtrlK,
|
||||
"CtrlL": tcell.KeyCtrlL,
|
||||
"CtrlM": tcell.KeyCtrlM,
|
||||
"CtrlN": tcell.KeyCtrlN,
|
||||
"CtrlO": tcell.KeyCtrlO,
|
||||
"CtrlP": tcell.KeyCtrlP,
|
||||
"CtrlQ": tcell.KeyCtrlQ,
|
||||
"CtrlR": tcell.KeyCtrlR,
|
||||
"CtrlS": tcell.KeyCtrlS,
|
||||
"CtrlT": tcell.KeyCtrlT,
|
||||
"CtrlU": tcell.KeyCtrlU,
|
||||
"CtrlV": tcell.KeyCtrlV,
|
||||
"CtrlW": tcell.KeyCtrlW,
|
||||
"CtrlX": tcell.KeyCtrlX,
|
||||
"CtrlY": tcell.KeyCtrlY,
|
||||
"CtrlZ": tcell.KeyCtrlZ,
|
||||
"CtrlLeftSq": tcell.KeyCtrlLeftSq,
|
||||
"CtrlBackslash": tcell.KeyCtrlBackslash,
|
||||
"CtrlRightSq": tcell.KeyCtrlRightSq,
|
||||
"CtrlCarat": tcell.KeyCtrlCarat,
|
||||
"CtrlUnderscore": tcell.KeyCtrlUnderscore,
|
||||
"CtrlPageUp": tcell.KeyCtrlPgUp,
|
||||
"CtrlPageDown": tcell.KeyCtrlPgDn,
|
||||
"Tab": tcell.KeyTab,
|
||||
"Esc": tcell.KeyEsc,
|
||||
"Escape": tcell.KeyEscape,
|
||||
"Enter": tcell.KeyEnter,
|
||||
"Backspace": tcell.KeyBackspace2,
|
||||
|
||||
// I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
|
||||
"PgUp": tcell.KeyPgUp,
|
||||
"PgDown": tcell.KeyPgDn,
|
||||
}
|
||||
|
||||
// The Key struct holds the data for a keypress (keycode + modifiers)
|
||||
type Key struct {
|
||||
keyCode tcell.Key
|
||||
modifiers tcell.ModMask
|
||||
buttons tcell.ButtonMask
|
||||
r rune
|
||||
}
|
||||
|
||||
// InitBindings initializes the keybindings for micro
|
||||
func InitBindings() {
|
||||
bindings = make(map[Key][]func(*View, bool) bool)
|
||||
mouseBindings = make(map[Key][]func(*View, bool, *tcell.EventMouse) bool)
|
||||
|
||||
var parsed map[string]string
|
||||
defaults := DefaultBindings()
|
||||
|
||||
filename := configDir + "/bindings.json"
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
TermMessage("Error reading bindings.json file: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = json5.Unmarshal(input, &parsed)
|
||||
if err != nil {
|
||||
TermMessage("Error reading bindings.json:", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
parseBindings(defaults)
|
||||
parseBindings(parsed)
|
||||
}
|
||||
|
||||
func parseBindings(userBindings map[string]string) {
|
||||
for k, v := range userBindings {
|
||||
BindKey(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// findKey will find binding Key 'b' using string 'k'
|
||||
func findKey(k string) (b Key, ok bool) {
|
||||
modifiers := tcell.ModNone
|
||||
|
||||
// First, we'll strip off all the modifiers in the name and add them to the
|
||||
// ModMask
|
||||
modSearch:
|
||||
for {
|
||||
switch {
|
||||
case strings.HasPrefix(k, "-"):
|
||||
// We optionally support dashes between modifiers
|
||||
k = k[1:]
|
||||
case strings.HasPrefix(k, "Ctrl") && k != "CtrlH":
|
||||
// CtrlH technically does not have a 'Ctrl' modifier because it is really backspace
|
||||
k = k[4:]
|
||||
modifiers |= tcell.ModCtrl
|
||||
case strings.HasPrefix(k, "Alt"):
|
||||
k = k[3:]
|
||||
modifiers |= tcell.ModAlt
|
||||
case strings.HasPrefix(k, "Shift"):
|
||||
k = k[5:]
|
||||
modifiers |= tcell.ModShift
|
||||
default:
|
||||
break modSearch
|
||||
}
|
||||
}
|
||||
|
||||
// Control is handled specially, since some character codes in bindingKeys
|
||||
// are different when Control is depressed. We should check for Control keys
|
||||
// first.
|
||||
if modifiers&tcell.ModCtrl != 0 {
|
||||
// see if the key is in bindingKeys with the Ctrl prefix.
|
||||
if code, ok := bindingKeys["Ctrl"+k]; ok {
|
||||
// It is, we're done.
|
||||
return Key{
|
||||
keyCode: code,
|
||||
modifiers: modifiers,
|
||||
buttons: -1,
|
||||
r: 0,
|
||||
}, true
|
||||
}
|
||||
}
|
||||
|
||||
// See if we can find the key in bindingKeys
|
||||
if code, ok := bindingKeys[k]; ok {
|
||||
return Key{
|
||||
keyCode: code,
|
||||
modifiers: modifiers,
|
||||
buttons: -1,
|
||||
r: 0,
|
||||
}, true
|
||||
}
|
||||
|
||||
// See if we can find the key in bindingMouse
|
||||
if code, ok := bindingMouse[k]; ok {
|
||||
return Key{
|
||||
modifiers: modifiers,
|
||||
buttons: code,
|
||||
r: 0,
|
||||
}, true
|
||||
}
|
||||
|
||||
// If we were given one character, then we've got a rune.
|
||||
if len(k) == 1 {
|
||||
return Key{
|
||||
keyCode: tcell.KeyRune,
|
||||
modifiers: modifiers,
|
||||
buttons: -1,
|
||||
r: rune(k[0]),
|
||||
}, true
|
||||
}
|
||||
|
||||
// We don't know what happened.
|
||||
return Key{buttons: -1}, false
|
||||
}
|
||||
|
||||
// findAction will find 'action' using string 'v'
|
||||
func findAction(v string) (action func(*View, bool) bool) {
|
||||
action, ok := bindingActions[v]
|
||||
if !ok {
|
||||
// If the user seems to be binding a function that doesn't exist
|
||||
// We hope that it's a lua function that exists and bind it to that
|
||||
action = LuaFunctionBinding(v)
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
func findMouseAction(v string) func(*View, bool, *tcell.EventMouse) bool {
|
||||
action, ok := mouseBindingActions[v]
|
||||
if !ok {
|
||||
// If the user seems to be binding a function that doesn't exist
|
||||
// We hope that it's a lua function that exists and bind it to that
|
||||
action = LuaFunctionMouseBinding(v)
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
// BindKey takes a key and an action and binds the two together
|
||||
func BindKey(k, v string) {
|
||||
key, ok := findKey(k)
|
||||
if !ok {
|
||||
TermMessage("Unknown keybinding: " + k)
|
||||
return
|
||||
}
|
||||
if v == "ToggleHelp" {
|
||||
helpBinding = k
|
||||
}
|
||||
if helpBinding == k && v != "ToggleHelp" {
|
||||
helpBinding = ""
|
||||
}
|
||||
|
||||
actionNames := strings.Split(v, ",")
|
||||
if actionNames[0] == "UnbindKey" {
|
||||
delete(bindings, key)
|
||||
delete(mouseBindings, key)
|
||||
if len(actionNames) == 1 {
|
||||
return
|
||||
}
|
||||
actionNames = append(actionNames[:0], actionNames[1:]...)
|
||||
}
|
||||
actions := make([]func(*View, bool) bool, 0, len(actionNames))
|
||||
mouseActions := make([]func(*View, bool, *tcell.EventMouse) bool, 0, len(actionNames))
|
||||
for _, actionName := range actionNames {
|
||||
if strings.HasPrefix(actionName, "Mouse") {
|
||||
mouseActions = append(mouseActions, findMouseAction(actionName))
|
||||
} else {
|
||||
actions = append(actions, findAction(actionName))
|
||||
}
|
||||
}
|
||||
|
||||
if len(actions) > 0 {
|
||||
// Can't have a binding be both mouse and normal
|
||||
delete(mouseBindings, key)
|
||||
bindings[key] = actions
|
||||
} else if len(mouseActions) > 0 {
|
||||
// Can't have a binding be both mouse and normal
|
||||
delete(bindings, key)
|
||||
mouseBindings[key] = mouseActions
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultBindings returns a map containing micro's default keybindings
|
||||
func DefaultBindings() map[string]string {
|
||||
return map[string]string{
|
||||
"Up": "CursorUp",
|
||||
"Down": "CursorDown",
|
||||
"Right": "CursorRight",
|
||||
"Left": "CursorLeft",
|
||||
"ShiftUp": "SelectUp",
|
||||
"ShiftDown": "SelectDown",
|
||||
"ShiftLeft": "SelectLeft",
|
||||
"ShiftRight": "SelectRight",
|
||||
"AltLeft": "WordLeft",
|
||||
"AltRight": "WordRight",
|
||||
"AltUp": "MoveLinesUp",
|
||||
"AltDown": "MoveLinesDown",
|
||||
"AltShiftRight": "SelectWordRight",
|
||||
"AltShiftLeft": "SelectWordLeft",
|
||||
"CtrlLeft": "StartOfLine",
|
||||
"CtrlRight": "EndOfLine",
|
||||
"CtrlShiftLeft": "SelectToStartOfLine",
|
||||
"ShiftHome": "SelectToStartOfLine",
|
||||
"CtrlShiftRight": "SelectToEndOfLine",
|
||||
"ShiftEnd": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Enter": "InsertNewline",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Tab": "IndentSelection,InsertTab",
|
||||
"Backtab": "OutdentSelection,OutdentLine",
|
||||
"CtrlO": "OpenFile",
|
||||
"CtrlS": "Save",
|
||||
"CtrlF": "Find",
|
||||
"CtrlN": "FindNext",
|
||||
"CtrlP": "FindPrevious",
|
||||
"CtrlZ": "Undo",
|
||||
"CtrlY": "Redo",
|
||||
"CtrlC": "Copy",
|
||||
"CtrlX": "Cut",
|
||||
"CtrlK": "CutLine",
|
||||
"CtrlD": "DuplicateLine",
|
||||
"CtrlV": "Paste",
|
||||
"CtrlA": "SelectAll",
|
||||
"CtrlT": "AddTab",
|
||||
"Alt,": "PreviousTab",
|
||||
"Alt.": "NextTab",
|
||||
"Home": "StartOfLine",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"CtrlG": "ToggleHelp",
|
||||
"CtrlR": "ToggleRuler",
|
||||
"CtrlL": "JumpLine",
|
||||
"Delete": "Delete",
|
||||
"CtrlB": "ShellMode",
|
||||
"CtrlQ": "Quit",
|
||||
"CtrlE": "CommandMode",
|
||||
"CtrlW": "NextSplit",
|
||||
"CtrlU": "ToggleMacro",
|
||||
"CtrlJ": "PlayMacro",
|
||||
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
"Alt-b": "WordLeft",
|
||||
"Alt-a": "StartOfLine",
|
||||
"Alt-e": "EndOfLine",
|
||||
// "Alt-p": "CursorUp",
|
||||
// "Alt-n": "CursorDown",
|
||||
|
||||
// Integration with file managers
|
||||
"F1": "ToggleHelp",
|
||||
"F2": "Save",
|
||||
"F3": "Find",
|
||||
"F4": "Quit",
|
||||
"F7": "Find",
|
||||
"F10": "Quit",
|
||||
"Esc": "Escape",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "ScrollUp",
|
||||
"MouseWheelDown": "ScrollDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
}
|
||||
}
|
577
cmd/micro/buffer.go
Normal file
577
cmd/micro/buffer.go
Normal file
|
@ -0,0 +1,577 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/zyedidia/micro/cmd/micro/highlight"
|
||||
)
|
||||
|
||||
var (
|
||||
// 0 - no line type detected
|
||||
// 1 - lf detected
|
||||
// 2 - crlf detected
|
||||
fileformat = 0
|
||||
)
|
||||
|
||||
// Buffer stores the text for files that are loaded into the text editor
|
||||
// It uses a rope to efficiently store the string and contains some
|
||||
// simple functions for saving and wrapper functions for modifying the rope
|
||||
type Buffer struct {
|
||||
// The eventhandler for undo/redo
|
||||
*EventHandler
|
||||
// This stores all the text in the buffer as an array of lines
|
||||
*LineArray
|
||||
|
||||
Cursor Cursor
|
||||
cursors []*Cursor // for multiple cursors
|
||||
curCursor int // the current cursor
|
||||
|
||||
// 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
|
||||
|
||||
// Whether or not the buffer has been modified since it was opened
|
||||
IsModified bool
|
||||
|
||||
// Stores the last modification time of the file the buffer is pointing to
|
||||
ModTime time.Time
|
||||
|
||||
NumLines int
|
||||
|
||||
syntaxDef *highlight.Def
|
||||
highlighter *highlight.Highlighter
|
||||
|
||||
// Buffer local settings
|
||||
Settings map[string]interface{}
|
||||
}
|
||||
|
||||
// The SerializedBuffer holds the types that get serialized when a buffer is saved
|
||||
// These are used for the savecursor and saveundo options
|
||||
type SerializedBuffer struct {
|
||||
EventHandler *EventHandler
|
||||
Cursor Cursor
|
||||
ModTime time.Time
|
||||
}
|
||||
|
||||
func NewBufferFromString(text, path string) *Buffer {
|
||||
return NewBuffer(strings.NewReader(text), int64(len(text)), path)
|
||||
}
|
||||
|
||||
// NewBuffer creates a new buffer from a given reader with a given path
|
||||
func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
|
||||
if path != "" {
|
||||
for _, tab := range tabs {
|
||||
for _, view := range tab.views {
|
||||
if view.Buf.Path == path {
|
||||
return view.Buf
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b := new(Buffer)
|
||||
b.LineArray = NewLineArray(size, reader)
|
||||
|
||||
b.Settings = DefaultLocalSettings()
|
||||
for k, v := range globalSettings {
|
||||
if _, ok := b.Settings[k]; ok {
|
||||
b.Settings[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if fileformat == 1 {
|
||||
b.Settings["fileformat"] = "unix"
|
||||
} else if fileformat == 2 {
|
||||
b.Settings["fileformat"] = "dos"
|
||||
}
|
||||
|
||||
absPath, _ := filepath.Abs(path)
|
||||
|
||||
b.Path = path
|
||||
b.AbsPath = absPath
|
||||
|
||||
// The last time this file was modified
|
||||
b.ModTime, _ = GetModTime(b.Path)
|
||||
|
||||
b.EventHandler = NewEventHandler(b)
|
||||
|
||||
b.Update()
|
||||
b.UpdateRules()
|
||||
|
||||
if _, err := os.Stat(configDir + "/buffers/"); os.IsNotExist(err) {
|
||||
os.Mkdir(configDir+"/buffers/", os.ModePerm)
|
||||
}
|
||||
|
||||
// Put the cursor at the first spot
|
||||
cursorStartX := 0
|
||||
cursorStartY := 0
|
||||
// If -startpos LINE,COL was passed, use start position LINE,COL
|
||||
if len(*flagStartPos) > 0 {
|
||||
positions := strings.Split(*flagStartPos, ",")
|
||||
if len(positions) == 2 {
|
||||
lineNum, errPos1 := strconv.Atoi(positions[0])
|
||||
colNum, errPos2 := strconv.Atoi(positions[1])
|
||||
if errPos1 == nil && errPos2 == nil {
|
||||
cursorStartX = colNum
|
||||
cursorStartY = lineNum - 1
|
||||
// Check to avoid line overflow
|
||||
if cursorStartY > b.NumLines {
|
||||
cursorStartY = b.NumLines - 1
|
||||
} else if cursorStartY < 0 {
|
||||
cursorStartY = 0
|
||||
}
|
||||
// Check to avoid column overflow
|
||||
if cursorStartX > len(b.Line(cursorStartY)) {
|
||||
cursorStartX = len(b.Line(cursorStartY))
|
||||
} else if cursorStartX < 0 {
|
||||
cursorStartX = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
b.Cursor = Cursor{
|
||||
Loc: Loc{
|
||||
X: cursorStartX,
|
||||
Y: cursorStartY,
|
||||
},
|
||||
buf: b,
|
||||
}
|
||||
|
||||
InitLocalSettings(b)
|
||||
|
||||
if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
|
||||
// If either savecursor or saveundo is turned on, we need to load the serialized information
|
||||
// from ~/.config/micro/buffers
|
||||
file, err := os.Open(configDir + "/buffers/" + EscapePath(b.AbsPath))
|
||||
if err == nil {
|
||||
var buffer SerializedBuffer
|
||||
decoder := gob.NewDecoder(file)
|
||||
gob.Register(TextEvent{})
|
||||
err = decoder.Decode(&buffer)
|
||||
if err != nil {
|
||||
TermMessage(err.Error(), "\n", "You may want to remove the files in ~/.config/micro/buffers (these files store the information for the 'saveundo' and 'savecursor' options) if this problem persists.")
|
||||
}
|
||||
if b.Settings["savecursor"].(bool) {
|
||||
b.Cursor = buffer.Cursor
|
||||
b.Cursor.buf = b
|
||||
b.Cursor.Relocate()
|
||||
}
|
||||
|
||||
if b.Settings["saveundo"].(bool) {
|
||||
// We should only use last time's eventhandler if the file wasn't by someone else in the meantime
|
||||
if b.ModTime == buffer.ModTime {
|
||||
b.EventHandler = buffer.EventHandler
|
||||
b.EventHandler.buf = b
|
||||
}
|
||||
}
|
||||
}
|
||||
file.Close()
|
||||
}
|
||||
|
||||
if b.Settings["mouse"].(bool) {
|
||||
screen.EnableMouse()
|
||||
}
|
||||
|
||||
b.cursors = []*Cursor{&b.Cursor}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Buffer) GetName() string {
|
||||
if b.name == "" {
|
||||
if b.Path == "" {
|
||||
return "No name"
|
||||
}
|
||||
return b.Path
|
||||
}
|
||||
return b.name
|
||||
}
|
||||
|
||||
// UpdateRules updates the syntax rules and filetype for this buffer
|
||||
// This is called when the colorscheme changes
|
||||
func (b *Buffer) UpdateRules() {
|
||||
rehighlight := false
|
||||
var files []*highlight.File
|
||||
for _, f := range ListRuntimeFiles(RTSyntax) {
|
||||
data, err := f.Data()
|
||||
if err != nil {
|
||||
TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
} else {
|
||||
file, err := highlight.ParseFile(data)
|
||||
if err != nil {
|
||||
TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
ftdetect, err := highlight.ParseFtDetect(file)
|
||||
if err != nil {
|
||||
TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
ft := b.Settings["filetype"].(string)
|
||||
if (ft == "Unknown" || ft == "") && !rehighlight {
|
||||
if highlight.MatchFiletype(ftdetect, b.Path, b.lines[0].data) {
|
||||
header := new(highlight.Header)
|
||||
header.FileType = file.FileType
|
||||
header.FtDetect = ftdetect
|
||||
b.syntaxDef, err = highlight.ParseDef(file, header)
|
||||
if err != nil {
|
||||
TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
rehighlight = true
|
||||
}
|
||||
} else {
|
||||
if file.FileType == ft && !rehighlight {
|
||||
header := new(highlight.Header)
|
||||
header.FileType = file.FileType
|
||||
header.FtDetect = ftdetect
|
||||
b.syntaxDef, err = highlight.ParseDef(file, header)
|
||||
if err != nil {
|
||||
TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
rehighlight = true
|
||||
}
|
||||
}
|
||||
files = append(files, file)
|
||||
}
|
||||
}
|
||||
|
||||
if b.syntaxDef != nil {
|
||||
highlight.ResolveIncludes(b.syntaxDef, files)
|
||||
}
|
||||
|
||||
if b.highlighter == nil || rehighlight {
|
||||
if b.syntaxDef != nil {
|
||||
b.Settings["filetype"] = b.syntaxDef.FileType
|
||||
b.highlighter = highlight.NewHighlighter(b.syntaxDef)
|
||||
if b.Settings["syntax"].(bool) {
|
||||
b.highlighter.HighlightStates(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FileType returns the buffer's filetype
|
||||
func (b *Buffer) FileType() string {
|
||||
return b.Settings["filetype"].(string)
|
||||
}
|
||||
|
||||
// IndentString returns a string representing one level of indentation
|
||||
func (b *Buffer) IndentString() string {
|
||||
if b.Settings["tabstospaces"].(bool) {
|
||||
return Spaces(int(b.Settings["tabsize"].(float64)))
|
||||
}
|
||||
return "\t"
|
||||
}
|
||||
|
||||
// CheckModTime makes sure that the file this buffer points to hasn't been updated
|
||||
// by an external program since it was last read
|
||||
// If it has, we ask the user if they would like to reload the file
|
||||
func (b *Buffer) CheckModTime() {
|
||||
modTime, ok := GetModTime(b.Path)
|
||||
if ok {
|
||||
if modTime != b.ModTime {
|
||||
choice, canceled := messenger.YesNoPrompt("The file has changed since it was last read. Reload file? (y,n)")
|
||||
messenger.Reset()
|
||||
messenger.Clear()
|
||||
if !choice || canceled {
|
||||
// Don't load new changes -- do nothing
|
||||
b.ModTime, _ = GetModTime(b.Path)
|
||||
} else {
|
||||
// Load new changes
|
||||
b.ReOpen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ReOpen reloads the current buffer from disk
|
||||
func (b *Buffer) ReOpen() {
|
||||
data, err := ioutil.ReadFile(b.Path)
|
||||
txt := string(data)
|
||||
|
||||
if err != nil {
|
||||
messenger.Error(err.Error())
|
||||
return
|
||||
}
|
||||
b.EventHandler.ApplyDiff(txt)
|
||||
|
||||
b.ModTime, _ = GetModTime(b.Path)
|
||||
b.IsModified = false
|
||||
b.Update()
|
||||
b.Cursor.Relocate()
|
||||
}
|
||||
|
||||
// Update fetches the string from the rope and updates the `text` and `lines` in the buffer
|
||||
func (b *Buffer) Update() {
|
||||
b.NumLines = len(b.lines)
|
||||
}
|
||||
|
||||
func (b *Buffer) MergeCursors() {
|
||||
var cursors []*Cursor
|
||||
for i := 0; i < len(b.cursors); i++ {
|
||||
c1 := b.cursors[i]
|
||||
if c1 != nil {
|
||||
for j := 0; j < len(b.cursors); j++ {
|
||||
c2 := b.cursors[j]
|
||||
if c2 != nil && i != j && c1.Loc == c2.Loc {
|
||||
b.cursors[j] = nil
|
||||
}
|
||||
}
|
||||
cursors = append(cursors, c1)
|
||||
}
|
||||
}
|
||||
|
||||
b.cursors = cursors
|
||||
}
|
||||
|
||||
func (b *Buffer) UpdateCursors() {
|
||||
for i, c := range b.cursors {
|
||||
c.Num = i
|
||||
}
|
||||
}
|
||||
|
||||
// Save saves the buffer to its default path
|
||||
func (b *Buffer) Save() error {
|
||||
return b.SaveAs(b.Path)
|
||||
}
|
||||
|
||||
// SaveWithSudo saves the buffer to the default path with sudo
|
||||
func (b *Buffer) SaveWithSudo() error {
|
||||
return b.SaveAsWithSudo(b.Path)
|
||||
}
|
||||
|
||||
// Serialize serializes the buffer to configDir/buffers
|
||||
func (b *Buffer) Serialize() error {
|
||||
if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
|
||||
file, err := os.Create(configDir + "/buffers/" + EscapePath(b.AbsPath))
|
||||
if err == nil {
|
||||
enc := gob.NewEncoder(file)
|
||||
gob.Register(TextEvent{})
|
||||
err = enc.Encode(SerializedBuffer{
|
||||
b.EventHandler,
|
||||
b.Cursor,
|
||||
b.ModTime,
|
||||
})
|
||||
}
|
||||
err = file.Close()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
|
||||
func (b *Buffer) SaveAs(filename string) error {
|
||||
b.UpdateRules()
|
||||
dir, _ := homedir.Dir()
|
||||
if b.Settings["rmtrailingws"].(bool) {
|
||||
r, _ := regexp.Compile(`[ \t]+$`)
|
||||
for lineNum, line := range b.Lines(0, b.NumLines) {
|
||||
indices := r.FindStringIndex(line)
|
||||
if indices == nil {
|
||||
continue
|
||||
}
|
||||
startLoc := Loc{indices[0], lineNum}
|
||||
b.deleteToEnd(startLoc)
|
||||
}
|
||||
b.Cursor.Relocate()
|
||||
}
|
||||
if b.Settings["eofnewline"].(bool) {
|
||||
end := b.End()
|
||||
if b.RuneAt(Loc{end.X - 1, end.Y}) != '\n' {
|
||||
b.Insert(end, "\n")
|
||||
}
|
||||
}
|
||||
str := b.SaveString(b.Settings["fileformat"] == "dos")
|
||||
data := []byte(str)
|
||||
err := ioutil.WriteFile(filename, data, 0644)
|
||||
if err == nil {
|
||||
b.Path = strings.Replace(filename, "~", dir, 1)
|
||||
b.IsModified = false
|
||||
b.ModTime, _ = GetModTime(filename)
|
||||
return b.Serialize()
|
||||
}
|
||||
b.ModTime, _ = GetModTime(filename)
|
||||
return err
|
||||
}
|
||||
|
||||
// SaveAsWithSudo is the same as SaveAs except it uses a neat trick
|
||||
// with tee to use sudo so the user doesn't have to reopen micro with sudo
|
||||
func (b *Buffer) SaveAsWithSudo(filename string) error {
|
||||
b.UpdateRules()
|
||||
b.Path = filename
|
||||
|
||||
// Shut down the screen because we're going to interact directly with the shell
|
||||
screen.Fini()
|
||||
screen = nil
|
||||
|
||||
// Set up everything for the command
|
||||
cmd := exec.Command("sudo", "tee", filename)
|
||||
cmd.Stdin = bytes.NewBufferString(b.SaveString(b.Settings["fileformat"] == "dos"))
|
||||
|
||||
// This is a trap for Ctrl-C so that it doesn't kill micro
|
||||
// Instead we trap Ctrl-C to kill the program we're running
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
for range c {
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
}()
|
||||
|
||||
// Start the command
|
||||
cmd.Start()
|
||||
err := cmd.Wait()
|
||||
|
||||
// Start the screen back up
|
||||
InitScreen()
|
||||
if err == nil {
|
||||
b.IsModified = false
|
||||
b.ModTime, _ = GetModTime(filename)
|
||||
b.Serialize()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *Buffer) insert(pos Loc, value []byte) {
|
||||
b.IsModified = true
|
||||
b.LineArray.insert(pos, value)
|
||||
b.Update()
|
||||
}
|
||||
func (b *Buffer) remove(start, end Loc) string {
|
||||
b.IsModified = true
|
||||
sub := b.LineArray.remove(start, end)
|
||||
b.Update()
|
||||
return sub
|
||||
}
|
||||
func (b *Buffer) deleteToEnd(start Loc) {
|
||||
b.IsModified = true
|
||||
b.LineArray.DeleteToEnd(start)
|
||||
b.Update()
|
||||
}
|
||||
|
||||
// Start returns the location of the first character in the buffer
|
||||
func (b *Buffer) Start() Loc {
|
||||
return Loc{0, 0}
|
||||
}
|
||||
|
||||
// End returns the location of the last character in the buffer
|
||||
func (b *Buffer) End() Loc {
|
||||
return Loc{utf8.RuneCount(b.lines[b.NumLines-1].data), b.NumLines - 1}
|
||||
}
|
||||
|
||||
// RuneAt returns the rune at a given location in the buffer
|
||||
func (b *Buffer) RuneAt(loc Loc) rune {
|
||||
line := []rune(b.Line(loc.Y))
|
||||
if len(line) > 0 {
|
||||
return line[loc.X]
|
||||
}
|
||||
return '\n'
|
||||
}
|
||||
|
||||
// Line returns a single line
|
||||
func (b *Buffer) Line(n int) string {
|
||||
if n >= len(b.lines) {
|
||||
return ""
|
||||
}
|
||||
return string(b.lines[n].data)
|
||||
}
|
||||
|
||||
func (b *Buffer) LinesNum() int {
|
||||
return len(b.lines)
|
||||
}
|
||||
|
||||
// Lines returns an array of strings containing the lines from start to end
|
||||
func (b *Buffer) Lines(start, end int) []string {
|
||||
lines := b.lines[start:end]
|
||||
var slice []string
|
||||
for _, line := range lines {
|
||||
slice = append(slice, string(line.data))
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// Len gives the length of the buffer
|
||||
func (b *Buffer) Len() int {
|
||||
return Count(b.String())
|
||||
}
|
||||
|
||||
// MoveLinesUp moves the range of lines up one row
|
||||
func (b *Buffer) MoveLinesUp(start int, end int) {
|
||||
// 0 < start < end <= len(b.lines)
|
||||
if start < 1 || start >= end || end > len(b.lines) {
|
||||
return // what to do? FIXME
|
||||
}
|
||||
if end == len(b.lines) {
|
||||
b.Insert(
|
||||
Loc{
|
||||
utf8.RuneCount(b.lines[end-1].data),
|
||||
end - 1,
|
||||
},
|
||||
"\n"+b.Line(start-1),
|
||||
)
|
||||
} else {
|
||||
b.Insert(
|
||||
Loc{0, end},
|
||||
b.Line(start-1)+"\n",
|
||||
)
|
||||
}
|
||||
b.Remove(
|
||||
Loc{0, start - 1},
|
||||
Loc{0, start},
|
||||
)
|
||||
}
|
||||
|
||||
// MoveLinesDown moves the range of lines down one row
|
||||
func (b *Buffer) MoveLinesDown(start int, end int) {
|
||||
// 0 <= start < end < len(b.lines)
|
||||
// if end == len(b.lines), we can't do anything here because the
|
||||
// last line is unaccessible, FIXME
|
||||
if start < 0 || start >= end || end >= len(b.lines)-1 {
|
||||
return // what to do? FIXME
|
||||
}
|
||||
b.Insert(
|
||||
Loc{0, start},
|
||||
b.Line(end)+"\n",
|
||||
)
|
||||
end++
|
||||
b.Remove(
|
||||
Loc{0, end},
|
||||
Loc{0, end + 1},
|
||||
)
|
||||
}
|
||||
|
||||
// ClearMatches clears all of the syntax highlighting for this buffer
|
||||
func (b *Buffer) ClearMatches() {
|
||||
for i := range b.lines {
|
||||
b.SetMatch(i, nil)
|
||||
b.SetState(i, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) clearCursors() {
|
||||
for i := 1; i < len(b.cursors); i++ {
|
||||
b.cursors[i] = nil
|
||||
}
|
||||
b.cursors = b.cursors[:1]
|
||||
b.UpdateCursors()
|
||||
b.Cursor.ResetSelection()
|
||||
}
|
222
cmd/micro/cellview.go
Normal file
222
cmd/micro/cellview.go
Normal file
|
@ -0,0 +1,222 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
func min(a, b int) int {
|
||||
if a <= b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func visualToCharPos(visualIndex int, lineN int, str string, buf *Buffer, tabsize int) (int, int, *tcell.Style) {
|
||||
charPos := 0
|
||||
var lineIdx int
|
||||
var lastWidth int
|
||||
var style *tcell.Style
|
||||
var width int
|
||||
var rw int
|
||||
for i, c := range str {
|
||||
// width := StringWidth(str[:i], tabsize)
|
||||
|
||||
if group, ok := buf.Match(lineN)[charPos]; ok {
|
||||
s := GetColor(group.String())
|
||||
style = &s
|
||||
}
|
||||
|
||||
if width >= visualIndex {
|
||||
return charPos, visualIndex - lastWidth, style
|
||||
}
|
||||
|
||||
if i != 0 {
|
||||
charPos++
|
||||
lineIdx += rw
|
||||
}
|
||||
lastWidth = width
|
||||
rw = 0
|
||||
if c == '\t' {
|
||||
rw = tabsize - (lineIdx % tabsize)
|
||||
width += rw
|
||||
} else {
|
||||
rw = runewidth.RuneWidth(c)
|
||||
width += rw
|
||||
}
|
||||
}
|
||||
|
||||
return -1, -1, style
|
||||
}
|
||||
|
||||
type Char struct {
|
||||
visualLoc Loc
|
||||
realLoc Loc
|
||||
char rune
|
||||
// The actual character that is drawn
|
||||
// This is only different from char if it's for example hidden character
|
||||
drawChar rune
|
||||
style tcell.Style
|
||||
width int
|
||||
}
|
||||
|
||||
type CellView struct {
|
||||
lines [][]*Char
|
||||
}
|
||||
|
||||
func (c *CellView) Draw(buf *Buffer, top, height, left, width int) {
|
||||
tabsize := int(buf.Settings["tabsize"].(float64))
|
||||
softwrap := buf.Settings["softwrap"].(bool)
|
||||
indentrunes := []rune(buf.Settings["indentchar"].(string))
|
||||
// if empty indentchar settings, use space
|
||||
if indentrunes == nil || len(indentrunes) == 0 {
|
||||
indentrunes = []rune(" ")
|
||||
}
|
||||
indentchar := indentrunes[0]
|
||||
|
||||
start := buf.Cursor.Y
|
||||
if buf.Settings["syntax"].(bool) && buf.syntaxDef != nil {
|
||||
if start > 0 && buf.lines[start-1].rehighlight {
|
||||
buf.highlighter.ReHighlightLine(buf, start-1)
|
||||
buf.lines[start-1].rehighlight = false
|
||||
}
|
||||
|
||||
buf.highlighter.ReHighlightStates(buf, start)
|
||||
|
||||
buf.highlighter.HighlightMatches(buf, top, top+height)
|
||||
}
|
||||
|
||||
c.lines = make([][]*Char, 0)
|
||||
|
||||
viewLine := 0
|
||||
lineN := top
|
||||
|
||||
curStyle := defStyle
|
||||
for viewLine < height {
|
||||
if lineN >= len(buf.lines) {
|
||||
break
|
||||
}
|
||||
|
||||
lineStr := buf.Line(lineN)
|
||||
line := []rune(lineStr)
|
||||
|
||||
colN, startOffset, startStyle := visualToCharPos(left, lineN, lineStr, buf, tabsize)
|
||||
if colN < 0 {
|
||||
colN = len(line)
|
||||
}
|
||||
viewCol := -startOffset
|
||||
if startStyle != nil {
|
||||
curStyle = *startStyle
|
||||
}
|
||||
|
||||
// We'll either draw the length of the line, or the width of the screen
|
||||
// whichever is smaller
|
||||
lineLength := min(StringWidth(lineStr, tabsize), width)
|
||||
if lineLength < 0 {
|
||||
return
|
||||
}
|
||||
c.lines = append(c.lines, make([]*Char, lineLength))
|
||||
|
||||
wrap := false
|
||||
// We only need to wrap if the length of the line is greater than the width of the terminal screen
|
||||
if softwrap && StringWidth(lineStr, tabsize) > width {
|
||||
wrap = true
|
||||
// We're going to draw the entire line now
|
||||
lineLength = StringWidth(lineStr, tabsize)
|
||||
}
|
||||
|
||||
for viewCol < lineLength {
|
||||
if colN >= len(line) {
|
||||
break
|
||||
}
|
||||
if group, ok := buf.Match(lineN)[colN]; ok {
|
||||
curStyle = GetColor(group.String())
|
||||
}
|
||||
|
||||
char := line[colN]
|
||||
|
||||
if viewCol >= 0 {
|
||||
if viewCol < len(c.lines[viewLine]) {
|
||||
c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, char, curStyle, 1}
|
||||
}
|
||||
}
|
||||
if char == '\t' {
|
||||
charWidth := tabsize - (viewCol+left)%tabsize
|
||||
if viewCol >= 0 {
|
||||
if viewCol < len(c.lines[viewLine]) {
|
||||
c.lines[viewLine][viewCol].drawChar = indentchar
|
||||
c.lines[viewLine][viewCol].width = charWidth
|
||||
}
|
||||
|
||||
indentStyle := curStyle
|
||||
if group, ok := colorscheme["indent-char"]; ok {
|
||||
indentStyle = group
|
||||
}
|
||||
|
||||
if viewCol < len(c.lines[viewLine]) {
|
||||
c.lines[viewLine][viewCol].style = indentStyle
|
||||
}
|
||||
}
|
||||
|
||||
for i := 1; i < charWidth; i++ {
|
||||
viewCol++
|
||||
if viewCol >= 0 && viewCol < lineLength {
|
||||
if viewCol < len(c.lines[viewLine]) {
|
||||
c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, ' ', curStyle, 1}
|
||||
}
|
||||
}
|
||||
}
|
||||
viewCol++
|
||||
} else if runewidth.RuneWidth(char) > 1 {
|
||||
charWidth := runewidth.RuneWidth(char)
|
||||
if viewCol >= 0 {
|
||||
if viewCol < len(c.lines[viewLine]) {
|
||||
c.lines[viewLine][viewCol].width = charWidth
|
||||
}
|
||||
}
|
||||
for i := 1; i < charWidth; i++ {
|
||||
viewCol++
|
||||
if viewCol >= 0 && viewCol < lineLength {
|
||||
if viewCol < len(c.lines[viewLine]) {
|
||||
c.lines[viewLine][viewCol] = &Char{Loc{viewCol, viewLine}, Loc{colN, lineN}, char, ' ', curStyle, 1}
|
||||
}
|
||||
}
|
||||
}
|
||||
viewCol++
|
||||
} else {
|
||||
viewCol++
|
||||
}
|
||||
colN++
|
||||
|
||||
if wrap && viewCol >= width {
|
||||
viewLine++
|
||||
|
||||
// If we go too far soft wrapping we have to cut off
|
||||
if viewLine >= height {
|
||||
break
|
||||
}
|
||||
|
||||
nextLine := line[colN:]
|
||||
lineLength := min(StringWidth(string(nextLine), tabsize), width)
|
||||
c.lines = append(c.lines, make([]*Char, lineLength))
|
||||
|
||||
viewCol = 0
|
||||
}
|
||||
|
||||
}
|
||||
if group, ok := buf.Match(lineN)[len(line)]; ok {
|
||||
curStyle = GetColor(group.String())
|
||||
}
|
||||
|
||||
// newline
|
||||
viewLine++
|
||||
lineN++
|
||||
}
|
||||
|
||||
for i := top; i < top+height; i++ {
|
||||
if i >= buf.NumLines {
|
||||
break
|
||||
}
|
||||
buf.SetMatch(i, nil)
|
||||
}
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
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
|
||||
}
|
||||
|
||||
text = strings.TrimRight(text, "\r\n")
|
||||
|
||||
return len(text) == 0 || strings.ToLower(text)[0] == 'y'
|
||||
}
|
||||
|
||||
// CleanConfig performs cleanup in the user's configuration directory
|
||||
func CleanConfig() {
|
||||
fmt.Println("Cleaning your configuration directory at", config.ConfigDir)
|
||||
fmt.Printf("Please consider backing up %s before continuing\n", config.ConfigDir)
|
||||
|
||||
if !shouldContinue() {
|
||||
fmt.Println("Stopping early")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Cleaning default settings")
|
||||
config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
|
||||
|
||||
// detect unused options
|
||||
var unusedOptions []string
|
||||
defaultSettings := config.DefaultAllSettings()
|
||||
for k := range config.GlobalSettings {
|
||||
if _, ok := defaultSettings[k]; !ok {
|
||||
valid := false
|
||||
for _, p := range config.Plugins {
|
||||
if strings.HasPrefix(k, p.Name+".") || k == p.Name {
|
||||
valid = true
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
unusedOptions = append(unusedOptions, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(unusedOptions) > 0 {
|
||||
fmt.Println("The following options are unused:")
|
||||
|
||||
sort.Strings(unusedOptions)
|
||||
|
||||
for _, s := range unusedOptions {
|
||||
fmt.Printf("%s (value: %v)\n", s, config.GlobalSettings[s])
|
||||
}
|
||||
|
||||
fmt.Printf("These options will be removed from %s\n", filepath.Join(config.ConfigDir, "settings.json"))
|
||||
|
||||
if shouldContinue() {
|
||||
for _, s := range unusedOptions {
|
||||
delete(config.GlobalSettings, s)
|
||||
}
|
||||
|
||||
err := config.OverwriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
|
||||
if err != nil {
|
||||
fmt.Println("Error writing settings.json file: " + err.Error())
|
||||
}
|
||||
|
||||
fmt.Println("Removed unused options")
|
||||
fmt.Print("\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
// detect incorrectly formatted buffer/ files
|
||||
files, err := ioutil.ReadDir(filepath.Join(config.ConfigDir, "buffers"))
|
||||
if err == nil {
|
||||
var badFiles []string
|
||||
var buffer buffer.SerializedBuffer
|
||||
for _, f := range files {
|
||||
fname := filepath.Join(config.ConfigDir, "buffers", f.Name())
|
||||
file, e := os.Open(fname)
|
||||
|
||||
if e == nil {
|
||||
decoder := gob.NewDecoder(file)
|
||||
err = decoder.Decode(&buffer)
|
||||
|
||||
if err != nil && f.Name() != "history" {
|
||||
badFiles = append(badFiles, fname)
|
||||
}
|
||||
file.Close()
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
removed := 0
|
||||
for _, f := range badFiles {
|
||||
err := os.Remove(f)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
removed++
|
||||
}
|
||||
|
||||
if removed == 0 {
|
||||
fmt.Println("Failed to remove files")
|
||||
} else {
|
||||
fmt.Printf("Removed %d badly formatted files\n", removed)
|
||||
}
|
||||
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")
|
||||
}
|
256
cmd/micro/colorscheme.go
Normal file
256
cmd/micro/colorscheme.go
Normal file
|
@ -0,0 +1,256 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
// Colorscheme is a map from string to style -- it represents a colorscheme
|
||||
type Colorscheme map[string]tcell.Style
|
||||
|
||||
// The current colorscheme
|
||||
var colorscheme Colorscheme
|
||||
|
||||
// This takes in a syntax group and returns the colorscheme's style for that group
|
||||
func GetColor(color string) tcell.Style {
|
||||
st := defStyle
|
||||
if color == "" {
|
||||
return st
|
||||
}
|
||||
groups := strings.Split(color, ".")
|
||||
if len(groups) > 1 {
|
||||
curGroup := ""
|
||||
for i, g := range groups {
|
||||
if i != 0 {
|
||||
curGroup += "."
|
||||
}
|
||||
curGroup += g
|
||||
if style, ok := colorscheme[curGroup]; ok {
|
||||
st = style
|
||||
}
|
||||
}
|
||||
} else if style, ok := colorscheme[color]; ok {
|
||||
st = style
|
||||
} else {
|
||||
st = StringToStyle(color)
|
||||
}
|
||||
|
||||
return st
|
||||
}
|
||||
|
||||
// ColorschemeExists checks if a given colorscheme exists
|
||||
func ColorschemeExists(colorschemeName string) bool {
|
||||
return FindRuntimeFile(RTColorscheme, colorschemeName) != nil
|
||||
}
|
||||
|
||||
// InitColorscheme picks and initializes the colorscheme when micro starts
|
||||
func InitColorscheme() {
|
||||
colorscheme = make(Colorscheme)
|
||||
defStyle = tcell.StyleDefault.
|
||||
Foreground(tcell.ColorDefault).
|
||||
Background(tcell.ColorDefault)
|
||||
if screen != nil {
|
||||
screen.SetStyle(defStyle)
|
||||
}
|
||||
|
||||
LoadDefaultColorscheme()
|
||||
}
|
||||
|
||||
// LoadDefaultColorscheme loads the default colorscheme from $(configDir)/colorschemes
|
||||
func LoadDefaultColorscheme() {
|
||||
LoadColorscheme(globalSettings["colorscheme"].(string))
|
||||
}
|
||||
|
||||
// LoadColorscheme loads the given colorscheme from a directory
|
||||
func LoadColorscheme(colorschemeName string) {
|
||||
file := FindRuntimeFile(RTColorscheme, colorschemeName)
|
||||
if file == nil {
|
||||
TermMessage(colorschemeName, "is not a valid colorscheme")
|
||||
} else {
|
||||
if data, err := file.Data(); err != nil {
|
||||
TermMessage("Error loading colorscheme:", err)
|
||||
} else {
|
||||
colorscheme = ParseColorscheme(string(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ParseColorscheme parses the text definition for a colorscheme and returns the corresponding object
|
||||
// Colorschemes are made up of color-link statements linking a color group to a list of colors
|
||||
// For example, color-link keyword (blue,red) makes all keywords have a blue foreground and
|
||||
// red background
|
||||
func ParseColorscheme(text string) Colorscheme {
|
||||
parser := regexp.MustCompile(`color-link\s+(\S*)\s+"(.*)"`)
|
||||
|
||||
lines := strings.Split(text, "\n")
|
||||
|
||||
c := make(Colorscheme)
|
||||
|
||||
for _, line := range lines {
|
||||
if strings.TrimSpace(line) == "" ||
|
||||
strings.TrimSpace(line)[0] == '#' {
|
||||
// Ignore this line
|
||||
continue
|
||||
}
|
||||
|
||||
matches := parser.FindSubmatch([]byte(line))
|
||||
if len(matches) == 3 {
|
||||
link := string(matches[1])
|
||||
colors := string(matches[2])
|
||||
|
||||
style := StringToStyle(colors)
|
||||
c[link] = style
|
||||
|
||||
if link == "default" {
|
||||
defStyle = style
|
||||
}
|
||||
if screen != nil {
|
||||
screen.SetStyle(defStyle)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Color-link statement is not valid:", line)
|
||||
}
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// StringToStyle returns a style from a string
|
||||
// The strings must be in the format "extra foregroundcolor,backgroundcolor"
|
||||
// The 'extra' can be bold, reverse, or underline
|
||||
func StringToStyle(str string) tcell.Style {
|
||||
var fg, bg string
|
||||
spaceSplit := strings.Split(str, " ")
|
||||
var split []string
|
||||
if len(spaceSplit) > 1 {
|
||||
split = strings.Split(spaceSplit[1], ",")
|
||||
} else {
|
||||
split = strings.Split(str, ",")
|
||||
}
|
||||
if len(split) > 1 {
|
||||
fg, bg = split[0], split[1]
|
||||
} else {
|
||||
fg = split[0]
|
||||
}
|
||||
fg = strings.TrimSpace(fg)
|
||||
bg = strings.TrimSpace(bg)
|
||||
|
||||
var fgColor, bgColor tcell.Color
|
||||
if fg == "" {
|
||||
fgColor, _, _ = defStyle.Decompose()
|
||||
} else {
|
||||
fgColor = StringToColor(fg)
|
||||
}
|
||||
if bg == "" {
|
||||
_, bgColor, _ = defStyle.Decompose()
|
||||
} else {
|
||||
bgColor = StringToColor(bg)
|
||||
}
|
||||
|
||||
style := defStyle.Foreground(fgColor).Background(bgColor)
|
||||
if strings.Contains(str, "bold") {
|
||||
style = style.Bold(true)
|
||||
}
|
||||
if strings.Contains(str, "reverse") {
|
||||
style = style.Reverse(true)
|
||||
}
|
||||
if strings.Contains(str, "underline") {
|
||||
style = style.Underline(true)
|
||||
}
|
||||
return style
|
||||
}
|
||||
|
||||
// StringToColor returns a tcell color from a string representation of a color
|
||||
// We accept either bright... or light... to mean the brighter version of a color
|
||||
func StringToColor(str string) tcell.Color {
|
||||
switch str {
|
||||
case "black":
|
||||
return tcell.ColorBlack
|
||||
case "red":
|
||||
return tcell.ColorMaroon
|
||||
case "green":
|
||||
return tcell.ColorGreen
|
||||
case "yellow":
|
||||
return tcell.ColorOlive
|
||||
case "blue":
|
||||
return tcell.ColorNavy
|
||||
case "magenta":
|
||||
return tcell.ColorPurple
|
||||
case "cyan":
|
||||
return tcell.ColorTeal
|
||||
case "white":
|
||||
return tcell.ColorSilver
|
||||
case "brightblack", "lightblack":
|
||||
return tcell.ColorGray
|
||||
case "brightred", "lightred":
|
||||
return tcell.ColorRed
|
||||
case "brightgreen", "lightgreen":
|
||||
return tcell.ColorLime
|
||||
case "brightyellow", "lightyellow":
|
||||
return tcell.ColorYellow
|
||||
case "brightblue", "lightblue":
|
||||
return tcell.ColorBlue
|
||||
case "brightmagenta", "lightmagenta":
|
||||
return tcell.ColorFuchsia
|
||||
case "brightcyan", "lightcyan":
|
||||
return tcell.ColorAqua
|
||||
case "brightwhite", "lightwhite":
|
||||
return tcell.ColorWhite
|
||||
case "default":
|
||||
return tcell.ColorDefault
|
||||
default:
|
||||
// Check if this is a 256 color
|
||||
if num, err := strconv.Atoi(str); err == nil {
|
||||
return GetColor256(num)
|
||||
}
|
||||
// Probably a truecolor hex value
|
||||
return tcell.GetColor(str)
|
||||
}
|
||||
}
|
||||
|
||||
// GetColor256 returns the tcell color for a number between 0 and 255
|
||||
func GetColor256(color int) tcell.Color {
|
||||
colors := []tcell.Color{tcell.ColorBlack, tcell.ColorMaroon, tcell.ColorGreen,
|
||||
tcell.ColorOlive, tcell.ColorNavy, tcell.ColorPurple,
|
||||
tcell.ColorTeal, tcell.ColorSilver, tcell.ColorGray,
|
||||
tcell.ColorRed, tcell.ColorLime, tcell.ColorYellow,
|
||||
tcell.ColorBlue, tcell.ColorFuchsia, tcell.ColorAqua,
|
||||
tcell.ColorWhite, tcell.Color16, tcell.Color17, tcell.Color18, tcell.Color19, tcell.Color20,
|
||||
tcell.Color21, tcell.Color22, tcell.Color23, tcell.Color24, tcell.Color25, tcell.Color26, tcell.Color27, tcell.Color28,
|
||||
tcell.Color29, tcell.Color30, tcell.Color31, tcell.Color32, tcell.Color33, tcell.Color34, tcell.Color35, tcell.Color36,
|
||||
tcell.Color37, tcell.Color38, tcell.Color39, tcell.Color40, tcell.Color41, tcell.Color42, tcell.Color43, tcell.Color44,
|
||||
tcell.Color45, tcell.Color46, tcell.Color47, tcell.Color48, tcell.Color49, tcell.Color50, tcell.Color51, tcell.Color52,
|
||||
tcell.Color53, tcell.Color54, tcell.Color55, tcell.Color56, tcell.Color57, tcell.Color58, tcell.Color59, tcell.Color60,
|
||||
tcell.Color61, tcell.Color62, tcell.Color63, tcell.Color64, tcell.Color65, tcell.Color66, tcell.Color67, tcell.Color68,
|
||||
tcell.Color69, tcell.Color70, tcell.Color71, tcell.Color72, tcell.Color73, tcell.Color74, tcell.Color75, tcell.Color76,
|
||||
tcell.Color77, tcell.Color78, tcell.Color79, tcell.Color80, tcell.Color81, tcell.Color82, tcell.Color83, tcell.Color84,
|
||||
tcell.Color85, tcell.Color86, tcell.Color87, tcell.Color88, tcell.Color89, tcell.Color90, tcell.Color91, tcell.Color92,
|
||||
tcell.Color93, tcell.Color94, tcell.Color95, tcell.Color96, tcell.Color97, tcell.Color98, tcell.Color99, tcell.Color100,
|
||||
tcell.Color101, tcell.Color102, tcell.Color103, tcell.Color104, tcell.Color105, tcell.Color106, tcell.Color107, tcell.Color108,
|
||||
tcell.Color109, tcell.Color110, tcell.Color111, tcell.Color112, tcell.Color113, tcell.Color114, tcell.Color115, tcell.Color116,
|
||||
tcell.Color117, tcell.Color118, tcell.Color119, tcell.Color120, tcell.Color121, tcell.Color122, tcell.Color123, tcell.Color124,
|
||||
tcell.Color125, tcell.Color126, tcell.Color127, tcell.Color128, tcell.Color129, tcell.Color130, tcell.Color131, tcell.Color132,
|
||||
tcell.Color133, tcell.Color134, tcell.Color135, tcell.Color136, tcell.Color137, tcell.Color138, tcell.Color139, tcell.Color140,
|
||||
tcell.Color141, tcell.Color142, tcell.Color143, tcell.Color144, tcell.Color145, tcell.Color146, tcell.Color147, tcell.Color148,
|
||||
tcell.Color149, tcell.Color150, tcell.Color151, tcell.Color152, tcell.Color153, tcell.Color154, tcell.Color155, tcell.Color156,
|
||||
tcell.Color157, tcell.Color158, tcell.Color159, tcell.Color160, tcell.Color161, tcell.Color162, tcell.Color163, tcell.Color164,
|
||||
tcell.Color165, tcell.Color166, tcell.Color167, tcell.Color168, tcell.Color169, tcell.Color170, tcell.Color171, tcell.Color172,
|
||||
tcell.Color173, tcell.Color174, tcell.Color175, tcell.Color176, tcell.Color177, tcell.Color178, tcell.Color179, tcell.Color180,
|
||||
tcell.Color181, tcell.Color182, tcell.Color183, tcell.Color184, tcell.Color185, tcell.Color186, tcell.Color187, tcell.Color188,
|
||||
tcell.Color189, tcell.Color190, tcell.Color191, tcell.Color192, tcell.Color193, tcell.Color194, tcell.Color195, tcell.Color196,
|
||||
tcell.Color197, tcell.Color198, tcell.Color199, tcell.Color200, tcell.Color201, tcell.Color202, tcell.Color203, tcell.Color204,
|
||||
tcell.Color205, tcell.Color206, tcell.Color207, tcell.Color208, tcell.Color209, tcell.Color210, tcell.Color211, tcell.Color212,
|
||||
tcell.Color213, tcell.Color214, tcell.Color215, tcell.Color216, tcell.Color217, tcell.Color218, tcell.Color219, tcell.Color220,
|
||||
tcell.Color221, tcell.Color222, tcell.Color223, tcell.Color224, tcell.Color225, tcell.Color226, tcell.Color227, tcell.Color228,
|
||||
tcell.Color229, tcell.Color230, tcell.Color231, tcell.Color232, tcell.Color233, tcell.Color234, tcell.Color235, tcell.Color236,
|
||||
tcell.Color237, tcell.Color238, tcell.Color239, tcell.Color240, tcell.Color241, tcell.Color242, tcell.Color243, tcell.Color244,
|
||||
tcell.Color245, tcell.Color246, tcell.Color247, tcell.Color248, tcell.Color249, tcell.Color250, tcell.Color251, tcell.Color252,
|
||||
tcell.Color253, tcell.Color254, tcell.Color255,
|
||||
}
|
||||
|
||||
return colors[color]
|
||||
}
|
716
cmd/micro/command.go
Normal file
716
cmd/micro/command.go
Normal file
|
@ -0,0 +1,716 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
// A Command contains a action (a function to call) as well as information about how to autocomplete the command
|
||||
type Command struct {
|
||||
action func([]string)
|
||||
completions []Completion
|
||||
}
|
||||
|
||||
// A StrCommand is similar to a command but keeps the name of the action
|
||||
type StrCommand struct {
|
||||
action string
|
||||
completions []Completion
|
||||
}
|
||||
|
||||
var commands map[string]Command
|
||||
|
||||
var commandActions map[string]func([]string)
|
||||
|
||||
func init() {
|
||||
commandActions = map[string]func([]string){
|
||||
"Set": Set,
|
||||
"SetLocal": SetLocal,
|
||||
"Show": Show,
|
||||
"Run": Run,
|
||||
"Bind": Bind,
|
||||
"Quit": Quit,
|
||||
"Save": Save,
|
||||
"Replace": Replace,
|
||||
"ReplaceAll": ReplaceAll,
|
||||
"VSplit": VSplit,
|
||||
"HSplit": HSplit,
|
||||
"Tab": NewTab,
|
||||
"Help": Help,
|
||||
"Eval": Eval,
|
||||
"ToggleLog": ToggleLog,
|
||||
"Plugin": PluginCmd,
|
||||
"Reload": Reload,
|
||||
"Cd": Cd,
|
||||
"Pwd": Pwd,
|
||||
"Open": Open,
|
||||
"TabSwitch": TabSwitch,
|
||||
"MemUsage": MemUsage,
|
||||
}
|
||||
}
|
||||
|
||||
// InitCommands initializes the default commands
|
||||
func InitCommands() {
|
||||
commands = make(map[string]Command)
|
||||
|
||||
defaults := DefaultCommands()
|
||||
parseCommands(defaults)
|
||||
}
|
||||
|
||||
func parseCommands(userCommands map[string]StrCommand) {
|
||||
for k, v := range userCommands {
|
||||
MakeCommand(k, v.action, v.completions...)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 MakeCommand(name, function string, completions ...Completion) {
|
||||
action := commandActions[function]
|
||||
if _, ok := commandActions[function]; !ok {
|
||||
// If the user seems to be binding a function that doesn't exist
|
||||
// We hope that it's a lua function that exists and bind it to that
|
||||
action = LuaFunctionCommand(function)
|
||||
}
|
||||
|
||||
commands[name] = Command{action, completions}
|
||||
}
|
||||
|
||||
// DefaultCommands returns a map containing micro's default commands
|
||||
func DefaultCommands() map[string]StrCommand {
|
||||
return map[string]StrCommand{
|
||||
"set": {"Set", []Completion{OptionCompletion, NoCompletion}},
|
||||
"setlocal": {"SetLocal", []Completion{OptionCompletion, NoCompletion}},
|
||||
"show": {"Show", []Completion{OptionCompletion, NoCompletion}},
|
||||
"bind": {"Bind", []Completion{NoCompletion}},
|
||||
"run": {"Run", []Completion{NoCompletion}},
|
||||
"quit": {"Quit", []Completion{NoCompletion}},
|
||||
"save": {"Save", []Completion{NoCompletion}},
|
||||
"replace": {"Replace", []Completion{NoCompletion}},
|
||||
"replaceall": {"ReplaceAll", []Completion{NoCompletion}},
|
||||
"vsplit": {"VSplit", []Completion{FileCompletion, NoCompletion}},
|
||||
"hsplit": {"HSplit", []Completion{FileCompletion, NoCompletion}},
|
||||
"tab": {"Tab", []Completion{FileCompletion, NoCompletion}},
|
||||
"help": {"Help", []Completion{HelpCompletion, NoCompletion}},
|
||||
"eval": {"Eval", []Completion{NoCompletion}},
|
||||
"log": {"ToggleLog", []Completion{NoCompletion}},
|
||||
"plugin": {"Plugin", []Completion{PluginCmdCompletion, PluginNameCompletion}},
|
||||
"reload": {"Reload", []Completion{NoCompletion}},
|
||||
"cd": {"Cd", []Completion{FileCompletion}},
|
||||
"pwd": {"Pwd", []Completion{NoCompletion}},
|
||||
"open": {"Open", []Completion{FileCompletion}},
|
||||
"tabswitch": {"TabSwitch", []Completion{NoCompletion}},
|
||||
"memusage": {"MemUsage", []Completion{NoCompletion}},
|
||||
}
|
||||
}
|
||||
|
||||
// PluginCmd installs, removes, updates, lists, or searches for given plugins
|
||||
func PluginCmd(args []string) {
|
||||
if len(args) >= 1 {
|
||||
switch args[0] {
|
||||
case "install":
|
||||
installedVersions := GetInstalledVersions(false)
|
||||
for _, plugin := range args[1:] {
|
||||
pp := GetAllPluginPackages().Get(plugin)
|
||||
if pp == nil {
|
||||
messenger.Error("Unknown plugin \"" + plugin + "\"")
|
||||
} else if err := pp.IsInstallable(); err != nil {
|
||||
messenger.Error("Error installing ", plugin, ": ", err)
|
||||
} else {
|
||||
for _, installed := range installedVersions {
|
||||
if pp.Name == installed.pack.Name {
|
||||
if pp.Versions[0].Version.Compare(installed.Version) == 1 {
|
||||
messenger.Error(pp.Name, " is already installed but out-of-date: use 'plugin update ", pp.Name, "' to update")
|
||||
} else {
|
||||
messenger.Error(pp.Name, " is already installed")
|
||||
}
|
||||
}
|
||||
}
|
||||
pp.Install()
|
||||
}
|
||||
}
|
||||
case "remove":
|
||||
removed := ""
|
||||
for _, plugin := range args[1:] {
|
||||
// check if the plugin exists.
|
||||
if _, ok := loadedPlugins[plugin]; ok {
|
||||
UninstallPlugin(plugin)
|
||||
removed += plugin + " "
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !IsSpaces(removed) {
|
||||
messenger.Message("Removed ", removed)
|
||||
} else {
|
||||
messenger.Error("The requested plugins do not exist")
|
||||
}
|
||||
case "update":
|
||||
UpdatePlugins(args[1:])
|
||||
case "list":
|
||||
plugins := GetInstalledVersions(false)
|
||||
messenger.AddLog("----------------")
|
||||
messenger.AddLog("The following plugins are currently installed:\n")
|
||||
for _, p := range plugins {
|
||||
messenger.AddLog(fmt.Sprintf("%s (%s)", p.pack.Name, p.Version))
|
||||
}
|
||||
messenger.AddLog("----------------")
|
||||
if len(plugins) > 0 {
|
||||
if CurView().Type != vtLog {
|
||||
ToggleLog([]string{})
|
||||
}
|
||||
}
|
||||
case "search":
|
||||
plugins := SearchPlugin(args[1:])
|
||||
messenger.Message(len(plugins), " plugins found")
|
||||
for _, p := range plugins {
|
||||
messenger.AddLog("----------------")
|
||||
messenger.AddLog(p.String())
|
||||
}
|
||||
messenger.AddLog("----------------")
|
||||
if len(plugins) > 0 {
|
||||
if CurView().Type != vtLog {
|
||||
ToggleLog([]string{})
|
||||
}
|
||||
}
|
||||
case "available":
|
||||
packages := GetAllPluginPackages()
|
||||
messenger.AddLog("Available Plugins:")
|
||||
for _, pkg := range packages {
|
||||
messenger.AddLog(pkg.Name)
|
||||
}
|
||||
if CurView().Type != vtLog {
|
||||
ToggleLog([]string{})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
messenger.Error("Not enough arguments")
|
||||
}
|
||||
}
|
||||
|
||||
// TabSwitch switches to a given tab either by name or by number
|
||||
func TabSwitch(args []string) {
|
||||
if len(args) > 0 {
|
||||
num, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
// Check for tab with this name
|
||||
|
||||
found := false
|
||||
for _, t := range tabs {
|
||||
v := t.views[t.CurView]
|
||||
if v.Buf.GetName() == args[0] {
|
||||
curTab = v.TabNum
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
messenger.Error("Could not find tab: ", err)
|
||||
}
|
||||
} else {
|
||||
num--
|
||||
if num >= 0 && num < len(tabs) {
|
||||
curTab = num
|
||||
} else {
|
||||
messenger.Error("Invalid tab index")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cd changes the current working directory
|
||||
func Cd(args []string) {
|
||||
if len(args) > 0 {
|
||||
home, _ := homedir.Dir()
|
||||
path := strings.Replace(args[0], "~", home, 1)
|
||||
os.Chdir(path)
|
||||
for _, tab := range tabs {
|
||||
for _, view := range tab.views {
|
||||
wd, _ := os.Getwd()
|
||||
view.Buf.Path, _ = MakeRelative(view.Buf.AbsPath, wd)
|
||||
if p, _ := filepath.Abs(view.Buf.Path); !strings.Contains(p, wd) {
|
||||
view.Buf.Path = view.Buf.AbsPath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MemUsage prints micro's memory usage
|
||||
// Alloc shows how many bytes are currently in use
|
||||
// Sys shows how many bytes have been requested from the operating system
|
||||
// NumGC shows how many times the GC has been run
|
||||
// Note that Go commonly reserves more memory from the OS than is currently in-use/required
|
||||
// Additionally, even if Go returns memory to the OS, the OS does not always claim it because
|
||||
// there may be plenty of memory to spare
|
||||
func MemUsage(args []string) {
|
||||
var mem runtime.MemStats
|
||||
runtime.ReadMemStats(&mem)
|
||||
|
||||
messenger.Message(fmt.Sprintf("Alloc: %v, Sys: %v, NumGC: %v", humanize.Bytes(mem.Alloc), humanize.Bytes(mem.Sys), mem.NumGC))
|
||||
}
|
||||
|
||||
// Pwd prints the current working directory
|
||||
func Pwd(args []string) {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
messenger.Message(err.Error())
|
||||
} else {
|
||||
messenger.Message(wd)
|
||||
}
|
||||
}
|
||||
|
||||
// Open opens a new buffer with a given filename
|
||||
func Open(args []string) {
|
||||
if len(args) > 0 {
|
||||
filename := args[0]
|
||||
// the filename might or might not be quoted, so unquote first then join the strings.
|
||||
filename = strings.Join(SplitCommandArgs(filename), " ")
|
||||
|
||||
CurView().Open(filename)
|
||||
} else {
|
||||
messenger.Error("No filename")
|
||||
}
|
||||
}
|
||||
|
||||
// ToggleLog toggles the log view
|
||||
func ToggleLog(args []string) {
|
||||
buffer := messenger.getBuffer()
|
||||
if CurView().Type != vtLog {
|
||||
CurView().HSplit(buffer)
|
||||
CurView().Type = vtLog
|
||||
RedrawAll()
|
||||
buffer.Cursor.Loc = buffer.Start()
|
||||
CurView().Relocate()
|
||||
buffer.Cursor.Loc = buffer.End()
|
||||
CurView().Relocate()
|
||||
} else {
|
||||
CurView().Quit(true)
|
||||
}
|
||||
}
|
||||
|
||||
// Reload reloads all files (syntax files, colorschemes...)
|
||||
func Reload(args []string) {
|
||||
LoadAll()
|
||||
}
|
||||
|
||||
// Help tries to open the given help page in a horizontal split
|
||||
func Help(args []string) {
|
||||
if len(args) < 1 {
|
||||
// Open the default help if the user just typed "> help"
|
||||
CurView().openHelp("help")
|
||||
} else {
|
||||
helpPage := args[0]
|
||||
if FindRuntimeFile(RTHelp, helpPage) != nil {
|
||||
CurView().openHelp(helpPage)
|
||||
} else {
|
||||
messenger.Error("Sorry, no help for ", helpPage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// VSplit opens a vertical split with file given in the first argument
|
||||
// If no file is given, it opens an empty buffer in a new split
|
||||
func VSplit(args []string) {
|
||||
if len(args) == 0 {
|
||||
CurView().VSplit(NewBufferFromString("", ""))
|
||||
} else {
|
||||
filename := args[0]
|
||||
home, _ := homedir.Dir()
|
||||
filename = strings.Replace(filename, "~", home, 1)
|
||||
file, err := os.Open(filename)
|
||||
fileInfo, _ := os.Stat(filename)
|
||||
|
||||
if err == nil && fileInfo.IsDir() {
|
||||
messenger.Error(filename, " is a directory")
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
var buf *Buffer
|
||||
if err != nil {
|
||||
// File does not exist -- create an empty buffer with that name
|
||||
buf = NewBufferFromString("", filename)
|
||||
} else {
|
||||
buf = NewBuffer(file, FSize(file), filename)
|
||||
}
|
||||
CurView().VSplit(buf)
|
||||
}
|
||||
}
|
||||
|
||||
// HSplit opens a horizontal split with file given in the first argument
|
||||
// If no file is given, it opens an empty buffer in a new split
|
||||
func HSplit(args []string) {
|
||||
if len(args) == 0 {
|
||||
CurView().HSplit(NewBufferFromString("", ""))
|
||||
} else {
|
||||
filename := args[0]
|
||||
home, _ := homedir.Dir()
|
||||
filename = strings.Replace(filename, "~", home, 1)
|
||||
file, err := os.Open(filename)
|
||||
fileInfo, _ := os.Stat(filename)
|
||||
|
||||
if err == nil && fileInfo.IsDir() {
|
||||
messenger.Error(filename, " is a directory")
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
var buf *Buffer
|
||||
if err != nil {
|
||||
// File does not exist -- create an empty buffer with that name
|
||||
buf = NewBufferFromString("", filename)
|
||||
} else {
|
||||
buf = NewBuffer(file, FSize(file), filename)
|
||||
}
|
||||
CurView().HSplit(buf)
|
||||
}
|
||||
}
|
||||
|
||||
// Eval evaluates a lua expression
|
||||
func Eval(args []string) {
|
||||
if len(args) >= 1 {
|
||||
err := L.DoString(args[0])
|
||||
if err != nil {
|
||||
messenger.Error(err)
|
||||
}
|
||||
} else {
|
||||
messenger.Error("Not enough arguments")
|
||||
}
|
||||
}
|
||||
|
||||
// NewTab opens the given file in a new tab
|
||||
func NewTab(args []string) {
|
||||
if len(args) == 0 {
|
||||
CurView().AddTab(true)
|
||||
} else {
|
||||
filename := args[0]
|
||||
home, _ := homedir.Dir()
|
||||
filename = strings.Replace(filename, "~", home, 1)
|
||||
file, err := os.Open(filename)
|
||||
fileInfo, _ := os.Stat(filename)
|
||||
|
||||
if err == nil && fileInfo.IsDir() {
|
||||
messenger.Error(filename, " is a directory")
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
var buf *Buffer
|
||||
if err != nil {
|
||||
buf = NewBufferFromString("", filename)
|
||||
} else {
|
||||
buf = NewBuffer(file, FSize(file), filename)
|
||||
}
|
||||
|
||||
tab := NewTabFromView(NewView(buf))
|
||||
tab.SetNum(len(tabs))
|
||||
tabs = append(tabs, tab)
|
||||
curTab = len(tabs) - 1
|
||||
if len(tabs) == 2 {
|
||||
for _, t := range tabs {
|
||||
for _, v := range t.views {
|
||||
v.ToggleTabbar()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets an option
|
||||
func Set(args []string) {
|
||||
if len(args) < 2 {
|
||||
messenger.Error("Not enough arguments")
|
||||
return
|
||||
}
|
||||
|
||||
option := args[0]
|
||||
value := args[1]
|
||||
|
||||
SetOptionAndSettings(option, value)
|
||||
}
|
||||
|
||||
// SetLocal sets an option local to the buffer
|
||||
func SetLocal(args []string) {
|
||||
if len(args) < 2 {
|
||||
messenger.Error("Not enough arguments")
|
||||
return
|
||||
}
|
||||
|
||||
option := args[0]
|
||||
value := args[1]
|
||||
|
||||
err := SetLocalOption(option, value, CurView())
|
||||
if err != nil {
|
||||
messenger.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Show shows the value of the given option
|
||||
func Show(args []string) {
|
||||
if len(args) < 1 {
|
||||
messenger.Error("Please provide an option to show")
|
||||
return
|
||||
}
|
||||
|
||||
option := GetOption(args[0])
|
||||
|
||||
if option == nil {
|
||||
messenger.Error(args[0], " is not a valid option")
|
||||
return
|
||||
}
|
||||
|
||||
messenger.Message(option)
|
||||
}
|
||||
|
||||
// Bind creates a new keybinding
|
||||
func Bind(args []string) {
|
||||
if len(args) < 2 {
|
||||
messenger.Error("Not enough arguments")
|
||||
return
|
||||
}
|
||||
BindKey(args[0], args[1])
|
||||
}
|
||||
|
||||
// Run runs a shell command in the background
|
||||
func Run(args []string) {
|
||||
// Run a shell command in the background (openTerm is false)
|
||||
HandleShellCommand(JoinCommandArgs(args...), false, true)
|
||||
}
|
||||
|
||||
// Quit closes the main view
|
||||
func Quit(args []string) {
|
||||
// Close the main view
|
||||
CurView().Quit(true)
|
||||
}
|
||||
|
||||
// Save saves the buffer in the main view
|
||||
func Save(args []string) {
|
||||
if len(args) == 0 {
|
||||
// Save the main view
|
||||
CurView().Save(true)
|
||||
} else {
|
||||
CurView().Buf.SaveAs(args[0])
|
||||
}
|
||||
}
|
||||
|
||||
// Replace runs search and replace
|
||||
func Replace(args []string) {
|
||||
if len(args) < 2 || len(args) > 3 {
|
||||
// We need to find both a search and replace expression
|
||||
messenger.Error("Invalid replace statement: " + strings.Join(args, " "))
|
||||
return
|
||||
}
|
||||
|
||||
allAtOnce := false
|
||||
if len(args) == 3 {
|
||||
// user added -a flag
|
||||
if args[2] == "-a" {
|
||||
allAtOnce = true
|
||||
} else {
|
||||
messenger.Error("Invalid replace flag: " + args[2])
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
search := string(args[0])
|
||||
replace := string(args[1])
|
||||
|
||||
regex, err := regexp.Compile("(?m)" + search)
|
||||
if err != nil {
|
||||
// There was an error with the user's regex
|
||||
messenger.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
view := CurView()
|
||||
|
||||
found := 0
|
||||
replaceAll := func() {
|
||||
var deltas []Delta
|
||||
deltaXOffset := Count(replace) - Count(search)
|
||||
for i := 0; i < view.Buf.LinesNum(); i++ {
|
||||
matches := regex.FindAllIndex(view.Buf.lines[i].data, -1)
|
||||
str := string(view.Buf.lines[i].data)
|
||||
|
||||
if matches != nil {
|
||||
xOffset := 0
|
||||
for _, m := range matches {
|
||||
from := Loc{runePos(m[0], str) + xOffset, i}
|
||||
to := Loc{runePos(m[1], str) + xOffset, i}
|
||||
|
||||
xOffset += deltaXOffset
|
||||
|
||||
deltas = append(deltas, Delta{replace, from, to})
|
||||
found++
|
||||
}
|
||||
}
|
||||
}
|
||||
view.Buf.MultipleReplace(deltas)
|
||||
}
|
||||
|
||||
if allAtOnce {
|
||||
replaceAll()
|
||||
} else {
|
||||
for {
|
||||
// The 'check' flag was used
|
||||
Search(search, view, true)
|
||||
if !view.Cursor.HasSelection() {
|
||||
break
|
||||
}
|
||||
view.Relocate()
|
||||
RedrawAll()
|
||||
choice, canceled := messenger.LetterPrompt("Perform replacement? (y,n,a)", 'y', 'n', 'a')
|
||||
if canceled {
|
||||
if view.Cursor.HasSelection() {
|
||||
view.Cursor.Loc = view.Cursor.CurSelection[0]
|
||||
view.Cursor.ResetSelection()
|
||||
}
|
||||
messenger.Reset()
|
||||
break
|
||||
} else if choice == 'a' {
|
||||
if view.Cursor.HasSelection() {
|
||||
view.Cursor.Loc = view.Cursor.CurSelection[0]
|
||||
view.Cursor.ResetSelection()
|
||||
}
|
||||
messenger.Reset()
|
||||
replaceAll()
|
||||
break
|
||||
} else if choice == 'y' {
|
||||
view.Cursor.DeleteSelection()
|
||||
view.Buf.Insert(view.Cursor.Loc, replace)
|
||||
view.Cursor.ResetSelection()
|
||||
messenger.Reset()
|
||||
found++
|
||||
}
|
||||
if view.Cursor.HasSelection() {
|
||||
searchStart = view.Cursor.CurSelection[1]
|
||||
} else {
|
||||
searchStart = view.Cursor.Loc
|
||||
}
|
||||
}
|
||||
}
|
||||
view.Cursor.Relocate()
|
||||
|
||||
if found > 1 {
|
||||
messenger.Message("Replaced ", found, " occurrences of ", search)
|
||||
} else if found == 1 {
|
||||
messenger.Message("Replaced ", found, " occurrence of ", search)
|
||||
} else {
|
||||
messenger.Message("Nothing matched ", search)
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceAll replaces search term all at once
|
||||
func ReplaceAll(args []string) {
|
||||
// aliased to Replace command
|
||||
Replace(append(args, "-a"))
|
||||
}
|
||||
|
||||
// RunShellCommand executes a shell command and returns the output/error
|
||||
func RunShellCommand(input string) (string, error) {
|
||||
inputCmd := SplitCommandArgs(input)[0]
|
||||
args := SplitCommandArgs(input)[1:]
|
||||
|
||||
cmd := exec.Command(inputCmd, args...)
|
||||
outputBytes := &bytes.Buffer{}
|
||||
cmd.Stdout = outputBytes
|
||||
cmd.Stderr = outputBytes
|
||||
cmd.Start()
|
||||
err := cmd.Wait() // wait for command to finish
|
||||
outstring := outputBytes.String()
|
||||
return outstring, err
|
||||
}
|
||||
|
||||
// HandleShellCommand runs the shell command
|
||||
// The openTerm argument specifies whether a terminal should be opened (for viewing output
|
||||
// or interacting with stdin)
|
||||
func HandleShellCommand(input string, openTerm bool, waitToFinish bool) string {
|
||||
inputCmd := SplitCommandArgs(input)[0]
|
||||
if !openTerm {
|
||||
// Simply run the command in the background and notify the user when it's done
|
||||
messenger.Message("Running...")
|
||||
go func() {
|
||||
output, err := RunShellCommand(input)
|
||||
totalLines := strings.Split(output, "\n")
|
||||
|
||||
if len(totalLines) < 3 {
|
||||
if err == nil {
|
||||
messenger.Message(inputCmd, " exited without error")
|
||||
} else {
|
||||
messenger.Message(inputCmd, " exited with error: ", err, ": ", output)
|
||||
}
|
||||
} else {
|
||||
messenger.Message(output)
|
||||
}
|
||||
// We have to make sure to redraw
|
||||
RedrawAll()
|
||||
}()
|
||||
} else {
|
||||
// Shut down the screen because we're going to interact directly with the shell
|
||||
screen.Fini()
|
||||
screen = nil
|
||||
|
||||
args := SplitCommandArgs(input)[1:]
|
||||
|
||||
// Set up everything for the command
|
||||
var outputBuf bytes.Buffer
|
||||
cmd := exec.Command(inputCmd, args...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = io.MultiWriter(os.Stdout, &outputBuf)
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
// This is a trap for Ctrl-C so that it doesn't kill micro
|
||||
// Instead we trap Ctrl-C to kill the program we're running
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
for range c {
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
}()
|
||||
|
||||
cmd.Start()
|
||||
err := cmd.Wait()
|
||||
|
||||
output := outputBuf.String()
|
||||
if err != nil {
|
||||
output = err.Error()
|
||||
}
|
||||
|
||||
if waitToFinish {
|
||||
// This is just so we don't return right away and let the user press enter to return
|
||||
TermMessage("")
|
||||
}
|
||||
|
||||
// Start the screen back up
|
||||
InitScreen()
|
||||
|
||||
return output
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// HandleCommand handles input from the user
|
||||
func HandleCommand(input string) {
|
||||
args := SplitCommandArgs(input)
|
||||
inputCmd := args[0]
|
||||
|
||||
if _, ok := commands[inputCmd]; !ok {
|
||||
messenger.Error("Unknown command ", inputCmd)
|
||||
} else {
|
||||
commands[inputCmd].action(args[1:])
|
||||
}
|
||||
}
|
372
cmd/micro/cursor.go
Normal file
372
cmd/micro/cursor.go
Normal file
|
@ -0,0 +1,372 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/clipboard"
|
||||
)
|
||||
|
||||
// The Cursor struct stores the location of the cursor in the view
|
||||
// The complicated part about the cursor is storing its location.
|
||||
// The cursor must be displayed at an x, y location, but since the buffer
|
||||
// uses a rope to store text, to insert text we must have an index. It
|
||||
// is also simpler to use character indicies for other tasks such as
|
||||
// selection.
|
||||
type Cursor struct {
|
||||
buf *Buffer
|
||||
Loc
|
||||
|
||||
// Last cursor x position
|
||||
LastVisualX int
|
||||
|
||||
// The current selection as a range of character numbers (inclusive)
|
||||
CurSelection [2]Loc
|
||||
// The original selection as a range of character numbers
|
||||
// This is used for line and word selection where it is necessary
|
||||
// to know what the original selection was
|
||||
OrigSelection [2]Loc
|
||||
|
||||
// Which cursor index is this (for multiple cursors)
|
||||
Num int
|
||||
}
|
||||
|
||||
// Goto puts the cursor at the given cursor's location and gives the current cursor its selection too
|
||||
func (c *Cursor) Goto(b Cursor) {
|
||||
c.X, c.Y, c.LastVisualX = b.X, b.Y, b.LastVisualX
|
||||
c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection
|
||||
}
|
||||
|
||||
// CopySelection copies the user's selection to either "primary" or "clipboard"
|
||||
func (c *Cursor) CopySelection(target string) {
|
||||
if c.HasSelection() {
|
||||
if target != "primary" || c.buf.Settings["useprimary"].(bool) {
|
||||
clipboard.WriteAll(c.GetSelection(), target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ResetSelection resets the user's selection
|
||||
func (c *Cursor) ResetSelection() {
|
||||
c.CurSelection[0] = c.buf.Start()
|
||||
c.CurSelection[1] = c.buf.Start()
|
||||
}
|
||||
|
||||
// SetSelectionStart sets the start of the selection
|
||||
func (c *Cursor) SetSelectionStart(pos Loc) {
|
||||
c.CurSelection[0] = pos
|
||||
}
|
||||
|
||||
// SetSelectionEnd sets the end of the selection
|
||||
func (c *Cursor) SetSelectionEnd(pos Loc) {
|
||||
c.CurSelection[1] = pos
|
||||
}
|
||||
|
||||
// HasSelection returns whether or not the user has selected anything
|
||||
func (c *Cursor) HasSelection() bool {
|
||||
return c.CurSelection[0] != c.CurSelection[1]
|
||||
}
|
||||
|
||||
// DeleteSelection deletes the currently selected text
|
||||
func (c *Cursor) DeleteSelection() {
|
||||
if c.CurSelection[0].GreaterThan(c.CurSelection[1]) {
|
||||
c.buf.Remove(c.CurSelection[1], c.CurSelection[0])
|
||||
c.Loc = c.CurSelection[1]
|
||||
} else if !c.HasSelection() {
|
||||
return
|
||||
} else {
|
||||
c.buf.Remove(c.CurSelection[0], c.CurSelection[1])
|
||||
c.Loc = c.CurSelection[0]
|
||||
}
|
||||
}
|
||||
|
||||
// GetSelection returns the cursor's selection
|
||||
func (c *Cursor) GetSelection() string {
|
||||
if InBounds(c.CurSelection[0], c.buf) && InBounds(c.CurSelection[1], c.buf) {
|
||||
if c.CurSelection[0].GreaterThan(c.CurSelection[1]) {
|
||||
return c.buf.Substr(c.CurSelection[1], c.CurSelection[0])
|
||||
}
|
||||
return c.buf.Substr(c.CurSelection[0], c.CurSelection[1])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SelectLine selects the current line
|
||||
func (c *Cursor) SelectLine() {
|
||||
c.Start()
|
||||
c.SetSelectionStart(c.Loc)
|
||||
c.End()
|
||||
if c.buf.NumLines-1 > c.Y {
|
||||
c.SetSelectionEnd(c.Loc.Move(1, c.buf))
|
||||
} else {
|
||||
c.SetSelectionEnd(c.Loc)
|
||||
}
|
||||
|
||||
c.OrigSelection = c.CurSelection
|
||||
}
|
||||
|
||||
// AddLineToSelection adds the current line to the selection
|
||||
func (c *Cursor) AddLineToSelection() {
|
||||
if c.Loc.LessThan(c.OrigSelection[0]) {
|
||||
c.Start()
|
||||
c.SetSelectionStart(c.Loc)
|
||||
c.SetSelectionEnd(c.OrigSelection[1])
|
||||
}
|
||||
if c.Loc.GreaterThan(c.OrigSelection[1]) {
|
||||
c.End()
|
||||
c.SetSelectionEnd(c.Loc.Move(1, c.buf))
|
||||
c.SetSelectionStart(c.OrigSelection[0])
|
||||
}
|
||||
|
||||
if c.Loc.LessThan(c.OrigSelection[1]) && c.Loc.GreaterThan(c.OrigSelection[0]) {
|
||||
c.CurSelection = c.OrigSelection
|
||||
}
|
||||
}
|
||||
|
||||
// SelectWord selects the word the cursor is currently on
|
||||
func (c *Cursor) SelectWord() {
|
||||
if len(c.buf.Line(c.Y)) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if !IsWordChar(string(c.RuneUnder(c.X))) {
|
||||
c.SetSelectionStart(c.Loc)
|
||||
c.SetSelectionEnd(c.Loc.Move(1, c.buf))
|
||||
c.OrigSelection = c.CurSelection
|
||||
return
|
||||
}
|
||||
|
||||
forward, backward := c.X, c.X
|
||||
|
||||
for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
|
||||
backward--
|
||||
}
|
||||
|
||||
c.SetSelectionStart(Loc{backward, c.Y})
|
||||
c.OrigSelection[0] = c.CurSelection[0]
|
||||
|
||||
for forward < Count(c.buf.Line(c.Y))-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
|
||||
forward++
|
||||
}
|
||||
|
||||
c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))
|
||||
c.OrigSelection[1] = c.CurSelection[1]
|
||||
c.Loc = c.CurSelection[1]
|
||||
}
|
||||
|
||||
// AddWordToSelection adds the word the cursor is currently on to the selection
|
||||
func (c *Cursor) AddWordToSelection() {
|
||||
if c.Loc.GreaterThan(c.OrigSelection[0]) && c.Loc.LessThan(c.OrigSelection[1]) {
|
||||
c.CurSelection = c.OrigSelection
|
||||
return
|
||||
}
|
||||
|
||||
if c.Loc.LessThan(c.OrigSelection[0]) {
|
||||
backward := c.X
|
||||
|
||||
for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
|
||||
backward--
|
||||
}
|
||||
|
||||
c.SetSelectionStart(Loc{backward, c.Y})
|
||||
c.SetSelectionEnd(c.OrigSelection[1])
|
||||
}
|
||||
|
||||
if c.Loc.GreaterThan(c.OrigSelection[1]) {
|
||||
forward := c.X
|
||||
|
||||
for forward < Count(c.buf.Line(c.Y))-1 && IsWordChar(string(c.RuneUnder(forward+1))) {
|
||||
forward++
|
||||
}
|
||||
|
||||
c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))
|
||||
c.SetSelectionStart(c.OrigSelection[0])
|
||||
}
|
||||
|
||||
c.Loc = c.CurSelection[1]
|
||||
}
|
||||
|
||||
// SelectTo selects from the current cursor location to the given location
|
||||
func (c *Cursor) SelectTo(loc Loc) {
|
||||
if loc.GreaterThan(c.OrigSelection[0]) {
|
||||
c.SetSelectionStart(c.OrigSelection[0])
|
||||
c.SetSelectionEnd(loc)
|
||||
} else {
|
||||
c.SetSelectionStart(loc)
|
||||
c.SetSelectionEnd(c.OrigSelection[0])
|
||||
}
|
||||
}
|
||||
|
||||
// WordRight moves the cursor one word to the right
|
||||
func (c *Cursor) WordRight() {
|
||||
for IsWhitespace(c.RuneUnder(c.X)) {
|
||||
if c.X == Count(c.buf.Line(c.Y)) {
|
||||
c.Right()
|
||||
return
|
||||
}
|
||||
c.Right()
|
||||
}
|
||||
c.Right()
|
||||
for IsWordChar(string(c.RuneUnder(c.X))) {
|
||||
if c.X == Count(c.buf.Line(c.Y)) {
|
||||
return
|
||||
}
|
||||
c.Right()
|
||||
}
|
||||
}
|
||||
|
||||
// WordLeft moves the cursor one word to the left
|
||||
func (c *Cursor) WordLeft() {
|
||||
c.Left()
|
||||
for IsWhitespace(c.RuneUnder(c.X)) {
|
||||
if c.X == 0 {
|
||||
return
|
||||
}
|
||||
c.Left()
|
||||
}
|
||||
c.Left()
|
||||
for IsWordChar(string(c.RuneUnder(c.X))) {
|
||||
if c.X == 0 {
|
||||
return
|
||||
}
|
||||
c.Left()
|
||||
}
|
||||
c.Right()
|
||||
}
|
||||
|
||||
// RuneUnder returns the rune under the given x position
|
||||
func (c *Cursor) RuneUnder(x int) rune {
|
||||
line := []rune(c.buf.Line(c.Y))
|
||||
if len(line) == 0 {
|
||||
return '\n'
|
||||
}
|
||||
if x >= len(line) {
|
||||
return '\n'
|
||||
} else if x < 0 {
|
||||
x = 0
|
||||
}
|
||||
return line[x]
|
||||
}
|
||||
|
||||
// UpN moves the cursor up N lines (if possible)
|
||||
func (c *Cursor) UpN(amount int) {
|
||||
proposedY := c.Y - amount
|
||||
if proposedY < 0 {
|
||||
proposedY = 0
|
||||
c.LastVisualX = 0
|
||||
} else if proposedY >= c.buf.NumLines {
|
||||
proposedY = c.buf.NumLines - 1
|
||||
}
|
||||
|
||||
runes := []rune(c.buf.Line(c.Y))
|
||||
c.X = c.GetCharPosInLine(proposedY, c.LastVisualX)
|
||||
|
||||
if c.X > len(runes) {
|
||||
c.X = len(runes)
|
||||
}
|
||||
|
||||
c.Y = proposedY
|
||||
}
|
||||
|
||||
// DownN moves the cursor down N lines (if possible)
|
||||
func (c *Cursor) DownN(amount int) {
|
||||
c.UpN(-amount)
|
||||
}
|
||||
|
||||
// Up moves the cursor up one line (if possible)
|
||||
func (c *Cursor) Up() {
|
||||
c.UpN(1)
|
||||
}
|
||||
|
||||
// Down moves the cursor down one line (if possible)
|
||||
func (c *Cursor) Down() {
|
||||
c.DownN(1)
|
||||
}
|
||||
|
||||
// Left moves the cursor left one cell (if possible) or to the last line if it is at the beginning
|
||||
func (c *Cursor) Left() {
|
||||
if c.Loc == c.buf.Start() {
|
||||
return
|
||||
}
|
||||
if c.X > 0 {
|
||||
c.X--
|
||||
} else {
|
||||
c.Up()
|
||||
c.End()
|
||||
}
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
|
||||
// Right moves the cursor right one cell (if possible) or to the next line if it is at the end
|
||||
func (c *Cursor) Right() {
|
||||
if c.Loc == c.buf.End() {
|
||||
return
|
||||
}
|
||||
if c.X < Count(c.buf.Line(c.Y)) {
|
||||
c.X++
|
||||
} else {
|
||||
c.Down()
|
||||
c.Start()
|
||||
}
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
|
||||
// End moves the cursor to the end of the line it is on
|
||||
func (c *Cursor) End() {
|
||||
c.X = Count(c.buf.Line(c.Y))
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
|
||||
// Start moves the cursor to the start of the line it is on
|
||||
func (c *Cursor) Start() {
|
||||
c.X = 0
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
|
||||
// GetCharPosInLine gets the char position of a visual x y coordinate (this is necessary because tabs are 1 char but 4 visual spaces)
|
||||
func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
|
||||
// Get the tab size
|
||||
tabSize := int(c.buf.Settings["tabsize"].(float64))
|
||||
visualLineLen := StringWidth(c.buf.Line(lineNum), tabSize)
|
||||
if visualPos > visualLineLen {
|
||||
visualPos = visualLineLen
|
||||
}
|
||||
width := WidthOfLargeRunes(c.buf.Line(lineNum), tabSize)
|
||||
if visualPos >= width {
|
||||
return visualPos - width
|
||||
}
|
||||
return visualPos / tabSize
|
||||
}
|
||||
|
||||
// GetVisualX returns the x value of the cursor in visual spaces
|
||||
func (c *Cursor) GetVisualX() int {
|
||||
runes := []rune(c.buf.Line(c.Y))
|
||||
tabSize := int(c.buf.Settings["tabsize"].(float64))
|
||||
if c.X > len(runes) {
|
||||
c.X = len(runes) - 1
|
||||
}
|
||||
|
||||
if c.X < 0 {
|
||||
c.X = 0
|
||||
}
|
||||
|
||||
return StringWidth(string(runes[:c.X]), tabSize)
|
||||
}
|
||||
|
||||
// StoreVisualX stores the current visual x value in the cursor
|
||||
func (c *Cursor) StoreVisualX() {
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
|
||||
// Relocate makes sure that the cursor is inside the bounds of the buffer
|
||||
// If it isn't, it moves it to be within the buffer's lines
|
||||
func (c *Cursor) Relocate() {
|
||||
if c.Y < 0 {
|
||||
c.Y = 0
|
||||
} else if c.Y >= c.buf.NumLines {
|
||||
c.Y = c.buf.NumLines - 1
|
||||
}
|
||||
|
||||
if c.X < 0 {
|
||||
c.X = 0
|
||||
} else if c.X > Count(c.buf.Line(c.Y)) {
|
||||
c.X = Count(c.buf.Line(c.Y))
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// NullWriter simply sends writes into the void
|
||||
type NullWriter struct{}
|
||||
|
||||
// Write is empty
|
||||
func (NullWriter) Write(data []byte) (n int, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// InitLog sets up the debug log system for micro if it has been enabled by compile-time variables
|
||||
func InitLog() {
|
||||
if util.Debug == "ON" {
|
||||
f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
log.Fatalf("error opening file: %v", err)
|
||||
}
|
||||
|
||||
log.SetOutput(f)
|
||||
log.Println("Micro started")
|
||||
} else {
|
||||
log.SetOutput(NullWriter{})
|
||||
}
|
||||
}
|
293
cmd/micro/eventhandler.go
Normal file
293
cmd/micro/eventhandler.go
Normal file
|
@ -0,0 +1,293 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dmp "github.com/sergi/go-diff/diffmatchpatch"
|
||||
"github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
const (
|
||||
// Opposite and undoing events must have opposite values
|
||||
|
||||
// TextEventInsert represents an insertion event
|
||||
TextEventInsert = 1
|
||||
// TextEventRemove represents a deletion event
|
||||
TextEventRemove = -1
|
||||
// TextEventReplace represents a replace event
|
||||
TextEventReplace = 0
|
||||
)
|
||||
|
||||
// TextEvent holds data for a manipulation on some text that can be undone
|
||||
type TextEvent struct {
|
||||
C Cursor
|
||||
|
||||
EventType int
|
||||
Deltas []Delta
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
type Delta struct {
|
||||
Text string
|
||||
Start Loc
|
||||
End Loc
|
||||
}
|
||||
|
||||
// ExecuteTextEvent runs a text event
|
||||
func ExecuteTextEvent(t *TextEvent, buf *Buffer) {
|
||||
if t.EventType == TextEventInsert {
|
||||
for _, d := range t.Deltas {
|
||||
buf.insert(d.Start, []byte(d.Text))
|
||||
}
|
||||
} else if t.EventType == TextEventRemove {
|
||||
for i, d := range t.Deltas {
|
||||
t.Deltas[i].Text = buf.remove(d.Start, d.End)
|
||||
}
|
||||
} else if t.EventType == TextEventReplace {
|
||||
for i, d := range t.Deltas {
|
||||
t.Deltas[i].Text = buf.remove(d.Start, d.End)
|
||||
buf.insert(d.Start, []byte(d.Text))
|
||||
t.Deltas[i].Start = d.Start
|
||||
t.Deltas[i].End = Loc{d.Start.X + Count(d.Text), d.Start.Y}
|
||||
}
|
||||
for i, j := 0, len(t.Deltas)-1; i < j; i, j = i+1, j-1 {
|
||||
t.Deltas[i], t.Deltas[j] = t.Deltas[j], t.Deltas[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UndoTextEvent undoes a text event
|
||||
func UndoTextEvent(t *TextEvent, buf *Buffer) {
|
||||
t.EventType = -t.EventType
|
||||
ExecuteTextEvent(t, buf)
|
||||
}
|
||||
|
||||
// EventHandler executes text manipulations and allows undoing and redoing
|
||||
type EventHandler struct {
|
||||
buf *Buffer
|
||||
UndoStack *Stack
|
||||
RedoStack *Stack
|
||||
}
|
||||
|
||||
// NewEventHandler returns a new EventHandler
|
||||
func NewEventHandler(buf *Buffer) *EventHandler {
|
||||
eh := new(EventHandler)
|
||||
eh.UndoStack = new(Stack)
|
||||
eh.RedoStack = new(Stack)
|
||||
eh.buf = buf
|
||||
return eh
|
||||
}
|
||||
|
||||
// ApplyDiff takes a string and runs the necessary insertion and deletion events to make
|
||||
// the buffer equal to that string
|
||||
// This means that we can transform the buffer into any string and still preserve undo/redo
|
||||
// through insert and delete events
|
||||
func (eh *EventHandler) ApplyDiff(new string) {
|
||||
differ := dmp.New()
|
||||
diff := differ.DiffMain(eh.buf.String(), new, false)
|
||||
loc := eh.buf.Start()
|
||||
for _, d := range diff {
|
||||
if d.Type == dmp.DiffDelete {
|
||||
eh.Remove(loc, loc.Move(Count(d.Text), eh.buf))
|
||||
} else {
|
||||
if d.Type == dmp.DiffInsert {
|
||||
eh.Insert(loc, d.Text)
|
||||
}
|
||||
loc = loc.Move(Count(d.Text), eh.buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert creates an insert text event and executes it
|
||||
func (eh *EventHandler) Insert(start Loc, text string) {
|
||||
e := &TextEvent{
|
||||
C: *eh.buf.cursors[eh.buf.curCursor],
|
||||
EventType: TextEventInsert,
|
||||
Deltas: []Delta{{text, start, Loc{0, 0}}},
|
||||
Time: time.Now(),
|
||||
}
|
||||
eh.Execute(e)
|
||||
e.Deltas[0].End = start.Move(Count(text), eh.buf)
|
||||
end := e.Deltas[0].End
|
||||
|
||||
for _, c := range eh.buf.cursors {
|
||||
move := func(loc Loc) Loc {
|
||||
if start.Y != end.Y && loc.GreaterThan(start) {
|
||||
loc.Y += end.Y - start.Y
|
||||
} else if loc.Y == start.Y && loc.GreaterEqual(start) {
|
||||
loc = loc.Move(Count(text), eh.buf)
|
||||
}
|
||||
return loc
|
||||
}
|
||||
c.Loc = move(c.Loc)
|
||||
c.CurSelection[0] = move(c.CurSelection[0])
|
||||
c.CurSelection[1] = move(c.CurSelection[1])
|
||||
c.OrigSelection[0] = move(c.OrigSelection[0])
|
||||
c.OrigSelection[1] = move(c.OrigSelection[1])
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
}
|
||||
|
||||
// Remove creates a remove text event and executes it
|
||||
func (eh *EventHandler) Remove(start, end Loc) {
|
||||
e := &TextEvent{
|
||||
C: *eh.buf.cursors[eh.buf.curCursor],
|
||||
EventType: TextEventRemove,
|
||||
Deltas: []Delta{{"", start, end}},
|
||||
Time: time.Now(),
|
||||
}
|
||||
eh.Execute(e)
|
||||
|
||||
for _, c := range eh.buf.cursors {
|
||||
move := func(loc Loc) Loc {
|
||||
if start.Y != end.Y && loc.GreaterThan(end) {
|
||||
loc.Y -= end.Y - start.Y
|
||||
} else if loc.Y == end.Y && loc.GreaterEqual(end) {
|
||||
loc = loc.Move(-Diff(start, end, eh.buf), eh.buf)
|
||||
}
|
||||
return loc
|
||||
}
|
||||
c.Loc = move(c.Loc)
|
||||
c.CurSelection[0] = move(c.CurSelection[0])
|
||||
c.CurSelection[1] = move(c.CurSelection[1])
|
||||
c.OrigSelection[0] = move(c.OrigSelection[0])
|
||||
c.OrigSelection[1] = move(c.OrigSelection[1])
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
}
|
||||
|
||||
// MultipleReplace creates an multiple insertions executes them
|
||||
func (eh *EventHandler) MultipleReplace(deltas []Delta) {
|
||||
e := &TextEvent{
|
||||
C: *eh.buf.cursors[eh.buf.curCursor],
|
||||
EventType: TextEventReplace,
|
||||
Deltas: deltas,
|
||||
Time: time.Now(),
|
||||
}
|
||||
eh.Execute(e)
|
||||
}
|
||||
|
||||
// Replace deletes from start to end and replaces it with the given string
|
||||
func (eh *EventHandler) Replace(start, end Loc, replace string) {
|
||||
eh.Remove(start, end)
|
||||
eh.Insert(start, replace)
|
||||
}
|
||||
|
||||
// Execute a textevent and add it to the undo stack
|
||||
func (eh *EventHandler) Execute(t *TextEvent) {
|
||||
if eh.RedoStack.Len() > 0 {
|
||||
eh.RedoStack = new(Stack)
|
||||
}
|
||||
eh.UndoStack.Push(t)
|
||||
|
||||
for pl := range loadedPlugins {
|
||||
ret, err := Call(pl+".onBeforeTextEvent", t)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
|
||||
TermMessage(err)
|
||||
}
|
||||
if val, ok := ret.(lua.LBool); ok && val == lua.LFalse {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ExecuteTextEvent(t, eh.buf)
|
||||
}
|
||||
|
||||
// Undo the first event in the undo stack
|
||||
func (eh *EventHandler) Undo() {
|
||||
t := eh.UndoStack.Peek()
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
startTime := t.Time.UnixNano() / int64(time.Millisecond)
|
||||
|
||||
eh.UndoOneEvent()
|
||||
|
||||
for {
|
||||
t = eh.UndoStack.Peek()
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if startTime-(t.Time.UnixNano()/int64(time.Millisecond)) > undoThreshold {
|
||||
return
|
||||
}
|
||||
startTime = t.Time.UnixNano() / int64(time.Millisecond)
|
||||
|
||||
eh.UndoOneEvent()
|
||||
}
|
||||
}
|
||||
|
||||
// UndoOneEvent undoes one event
|
||||
func (eh *EventHandler) UndoOneEvent() {
|
||||
// This event should be undone
|
||||
// Pop it off the stack
|
||||
t := eh.UndoStack.Pop()
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Undo it
|
||||
// Modifies the text event
|
||||
UndoTextEvent(t, eh.buf)
|
||||
|
||||
// Set the cursor in the right place
|
||||
teCursor := t.C
|
||||
if teCursor.Num >= 0 && teCursor.Num < len(eh.buf.cursors) {
|
||||
t.C = *eh.buf.cursors[teCursor.Num]
|
||||
eh.buf.cursors[teCursor.Num].Goto(teCursor)
|
||||
} else {
|
||||
teCursor.Num = -1
|
||||
}
|
||||
|
||||
// Push it to the redo stack
|
||||
eh.RedoStack.Push(t)
|
||||
}
|
||||
|
||||
// Redo the first event in the redo stack
|
||||
func (eh *EventHandler) Redo() {
|
||||
t := eh.RedoStack.Peek()
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
startTime := t.Time.UnixNano() / int64(time.Millisecond)
|
||||
|
||||
eh.RedoOneEvent()
|
||||
|
||||
for {
|
||||
t = eh.RedoStack.Peek()
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if (t.Time.UnixNano()/int64(time.Millisecond))-startTime > undoThreshold {
|
||||
return
|
||||
}
|
||||
|
||||
eh.RedoOneEvent()
|
||||
}
|
||||
}
|
||||
|
||||
// RedoOneEvent redoes one event
|
||||
func (eh *EventHandler) RedoOneEvent() {
|
||||
t := eh.RedoStack.Pop()
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Modifies the text event
|
||||
UndoTextEvent(t, eh.buf)
|
||||
|
||||
teCursor := t.C
|
||||
if teCursor.Num >= 0 && teCursor.Num < len(eh.buf.cursors) {
|
||||
t.C = *eh.buf.cursors[teCursor.Num]
|
||||
eh.buf.cursors[teCursor.Num].Goto(teCursor)
|
||||
} else {
|
||||
teCursor.Num = -1
|
||||
}
|
||||
|
||||
eh.UndoStack.Push(t)
|
||||
}
|
18
cmd/micro/highlight/ftdetect.go
Normal file
18
cmd/micro/highlight/ftdetect.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package highlight
|
||||
|
||||
import "regexp"
|
||||
|
||||
// DetectFiletype will use the list of syntax definitions provided and the filename and first line of the file
|
||||
// to determine the filetype of the file
|
||||
// It will return the corresponding syntax definition for the filetype
|
||||
func MatchFiletype(ftdetect [2]*regexp.Regexp, filename string, firstLine []byte) bool {
|
||||
if ftdetect[0].MatchString(filename) {
|
||||
return true
|
||||
}
|
||||
|
||||
if ftdetect[1] != nil {
|
||||
return ftdetect[1].Match(firstLine)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
|
@ -3,52 +3,19 @@ package highlight
|
|||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func sliceStart(slc []byte, index int) []byte {
|
||||
len := len(slc)
|
||||
i := 0
|
||||
totalSize := 0
|
||||
for totalSize < len {
|
||||
if i >= index {
|
||||
return slc[totalSize:]
|
||||
}
|
||||
|
||||
_, _, size := DecodeCharacter(slc[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
|
||||
return slc[totalSize:]
|
||||
}
|
||||
|
||||
func sliceEnd(slc []byte, index int) []byte {
|
||||
len := len(slc)
|
||||
i := 0
|
||||
totalSize := 0
|
||||
for totalSize < len {
|
||||
if i >= index {
|
||||
return slc[:totalSize]
|
||||
}
|
||||
|
||||
_, _, size := DecodeCharacter(slc[totalSize:])
|
||||
totalSize += size
|
||||
i++
|
||||
}
|
||||
|
||||
return slc[:totalSize]
|
||||
}
|
||||
|
||||
// RunePos returns the rune index of a given byte index
|
||||
// This could cause problems if the byte index is between code points
|
||||
func runePos(p int, str []byte) int {
|
||||
func runePos(p int, str string) int {
|
||||
if p < 0 {
|
||||
return 0
|
||||
}
|
||||
if p >= len(str) {
|
||||
return CharacterCount(str)
|
||||
return utf8.RuneCountInString(str)
|
||||
}
|
||||
return CharacterCount(str[:p])
|
||||
return utf8.RuneCountInString(str[:p])
|
||||
}
|
||||
|
||||
func combineLineMatch(src, dst LineMatch) LineMatch {
|
||||
|
@ -69,13 +36,11 @@ type State *region
|
|||
|
||||
// LineStates is an interface for a buffer-like object which can also store the states and matches for every line
|
||||
type LineStates interface {
|
||||
LineBytes(n int) []byte
|
||||
Line(n int) string
|
||||
LinesNum() int
|
||||
State(lineN int) State
|
||||
SetState(lineN int, s State)
|
||||
SetMatch(lineN int, m LineMatch)
|
||||
Lock()
|
||||
Unlock()
|
||||
}
|
||||
|
||||
// A Highlighter contains the information needed to highlight a string
|
||||
|
@ -95,15 +60,27 @@ func NewHighlighter(def *Def) *Highlighter {
|
|||
// color's group (represented as one byte)
|
||||
type LineMatch map[int]Group
|
||||
|
||||
func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte) []int {
|
||||
func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []rune, canMatchStart, canMatchEnd bool) []int {
|
||||
regexStr := regex.String()
|
||||
if strings.Contains(regexStr, "^") {
|
||||
if !canMatchStart {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if strings.Contains(regexStr, "$") {
|
||||
if !canMatchEnd {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var strbytes []byte
|
||||
if skip != nil {
|
||||
strbytes = skip.ReplaceAllFunc(str, func(match []byte) []byte {
|
||||
res := make([]byte, CharacterCount(match))
|
||||
strbytes = skip.ReplaceAllFunc([]byte(string(str)), func(match []byte) []byte {
|
||||
res := make([]byte, utf8.RuneCount(match))
|
||||
return res
|
||||
})
|
||||
} else {
|
||||
strbytes = str
|
||||
strbytes = []byte(string(str))
|
||||
}
|
||||
|
||||
match := regex.FindIndex(strbytes)
|
||||
|
@ -111,20 +88,32 @@ func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte) []int {
|
|||
return nil
|
||||
}
|
||||
// return []int{match.Index, match.Index + match.Length}
|
||||
return []int{runePos(match[0], str), runePos(match[1], str)}
|
||||
return []int{runePos(match[0], string(str)), runePos(match[1], string(str))}
|
||||
}
|
||||
|
||||
func findAllIndex(regex *regexp.Regexp, str []byte) [][]int {
|
||||
matches := regex.FindAllIndex(str, -1)
|
||||
func findAllIndex(regex *regexp.Regexp, str []rune, canMatchStart, canMatchEnd bool) [][]int {
|
||||
regexStr := regex.String()
|
||||
if strings.Contains(regexStr, "^") {
|
||||
if !canMatchStart {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if strings.Contains(regexStr, "$") {
|
||||
if !canMatchEnd {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
matches := regex.FindAllIndex([]byte(string(str)), -1)
|
||||
for i, m := range matches {
|
||||
matches[i][0] = runePos(m[0], str)
|
||||
matches[i][1] = runePos(m[1], str)
|
||||
matches[i][0] = runePos(m[0], string(str))
|
||||
matches[i][1] = runePos(m[1], string(str))
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, curRegion *region, statesOnly bool) LineMatch {
|
||||
lineLen := CharacterCount(line)
|
||||
func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []rune, curRegion *region, statesOnly bool) LineMatch {
|
||||
// highlights := make(LineMatch)
|
||||
|
||||
if start == 0 {
|
||||
if !statesOnly {
|
||||
if _, ok := highlights[0]; !ok {
|
||||
|
@ -133,65 +122,7 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
|
|||
}
|
||||
}
|
||||
|
||||
var firstRegion *region
|
||||
firstLoc := []int{lineLen, 0}
|
||||
searchNesting := true
|
||||
endLoc := findIndex(curRegion.end, curRegion.skip, line)
|
||||
if endLoc != nil {
|
||||
if start == endLoc[0] {
|
||||
searchNesting = false
|
||||
} else {
|
||||
firstLoc = endLoc
|
||||
}
|
||||
}
|
||||
if searchNesting {
|
||||
for _, r := range curRegion.rules.regions {
|
||||
loc := findIndex(r.start, r.skip, line)
|
||||
if loc != nil {
|
||||
if loc[0] < firstLoc[0] {
|
||||
firstLoc = loc
|
||||
firstRegion = r
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if firstRegion != nil && firstLoc[0] != lineLen {
|
||||
if !statesOnly {
|
||||
highlights[start+firstLoc[0]] = firstRegion.limitGroup
|
||||
}
|
||||
h.highlightEmptyRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), statesOnly)
|
||||
h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), firstRegion, statesOnly)
|
||||
return highlights
|
||||
}
|
||||
|
||||
if !statesOnly {
|
||||
fullHighlights := make([]Group, lineLen)
|
||||
for i := 0; i < len(fullHighlights); i++ {
|
||||
fullHighlights[i] = curRegion.group
|
||||
}
|
||||
|
||||
if searchNesting {
|
||||
for _, p := range curRegion.rules.patterns {
|
||||
if curRegion.group == curRegion.limitGroup || p.group == curRegion.limitGroup {
|
||||
matches := findAllIndex(p.regex, line)
|
||||
for _, m := range matches {
|
||||
if ((endLoc == nil) || (m[0] < endLoc[0])) {
|
||||
for i := m[0]; i < m[1]; i++ {
|
||||
fullHighlights[i] = p.group
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, h := range fullHighlights {
|
||||
if i == 0 || h != fullHighlights[i-1] {
|
||||
highlights[start+i] = h
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loc := endLoc
|
||||
loc := findIndex(curRegion.end, curRegion.skip, line, start == 0, canMatchEnd)
|
||||
if loc != nil {
|
||||
if !statesOnly {
|
||||
highlights[start+loc[0]] = curRegion.limitGroup
|
||||
|
@ -199,17 +130,67 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
|
|||
if curRegion.parent == nil {
|
||||
if !statesOnly {
|
||||
highlights[start+loc[1]] = 0
|
||||
h.highlightRegion(highlights, start, false, lineNum, line[:loc[0]], curRegion, statesOnly)
|
||||
}
|
||||
h.highlightEmptyRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), statesOnly)
|
||||
h.highlightEmptyRegion(highlights, start+loc[1], canMatchEnd, lineNum, line[loc[1]:], statesOnly)
|
||||
return highlights
|
||||
}
|
||||
if !statesOnly {
|
||||
highlights[start+loc[1]] = curRegion.parent.group
|
||||
h.highlightRegion(highlights, start, false, lineNum, line[:loc[0]], curRegion, statesOnly)
|
||||
}
|
||||
h.highlightRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), curRegion.parent, statesOnly)
|
||||
h.highlightRegion(highlights, start+loc[1], canMatchEnd, lineNum, line[loc[1]:], curRegion.parent, statesOnly)
|
||||
return highlights
|
||||
}
|
||||
|
||||
if len(line) == 0 || statesOnly {
|
||||
if canMatchEnd {
|
||||
h.lastRegion = curRegion
|
||||
}
|
||||
|
||||
return highlights
|
||||
}
|
||||
|
||||
firstLoc := []int{len(line), 0}
|
||||
|
||||
var firstRegion *region
|
||||
for _, r := range curRegion.rules.regions {
|
||||
loc := findIndex(r.start, nil, line, start == 0, canMatchEnd)
|
||||
if loc != nil {
|
||||
if loc[0] < firstLoc[0] {
|
||||
firstLoc = loc
|
||||
firstRegion = r
|
||||
}
|
||||
}
|
||||
}
|
||||
if firstLoc[0] != len(line) {
|
||||
highlights[start+firstLoc[0]] = firstRegion.limitGroup
|
||||
h.highlightRegion(highlights, start, false, lineNum, line[:firstLoc[0]], curRegion, statesOnly)
|
||||
h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, line[firstLoc[1]:], firstRegion, statesOnly)
|
||||
return highlights
|
||||
}
|
||||
|
||||
fullHighlights := make([]Group, len([]rune(string(line))))
|
||||
for i := 0; i < len(fullHighlights); i++ {
|
||||
fullHighlights[i] = curRegion.group
|
||||
}
|
||||
|
||||
for _, p := range curRegion.rules.patterns {
|
||||
matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
|
||||
for _, m := range matches {
|
||||
for i := m[0]; i < m[1]; i++ {
|
||||
fullHighlights[i] = p.group
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, h := range fullHighlights {
|
||||
if i == 0 || h != fullHighlights[i-1] {
|
||||
// if _, ok := highlights[start+i]; !ok {
|
||||
highlights[start+i] = h
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
if canMatchEnd {
|
||||
h.lastRegion = curRegion
|
||||
}
|
||||
|
@ -217,19 +198,18 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
|
|||
return highlights
|
||||
}
|
||||
|
||||
func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, statesOnly bool) LineMatch {
|
||||
lineLen := CharacterCount(line)
|
||||
if lineLen == 0 {
|
||||
func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []rune, statesOnly bool) LineMatch {
|
||||
if len(line) == 0 {
|
||||
if canMatchEnd {
|
||||
h.lastRegion = nil
|
||||
}
|
||||
return highlights
|
||||
}
|
||||
|
||||
firstLoc := []int{len(line), 0}
|
||||
var firstRegion *region
|
||||
firstLoc := []int{lineLen, 0}
|
||||
for _, r := range h.Def.rules.regions {
|
||||
loc := findIndex(r.start, r.skip, line)
|
||||
loc := findIndex(r.start, nil, line, start == 0, canMatchEnd)
|
||||
if loc != nil {
|
||||
if loc[0] < firstLoc[0] {
|
||||
firstLoc = loc
|
||||
|
@ -237,12 +217,12 @@ func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canM
|
|||
}
|
||||
}
|
||||
}
|
||||
if firstRegion != nil && firstLoc[0] != lineLen {
|
||||
if firstLoc[0] != len(line) {
|
||||
if !statesOnly {
|
||||
highlights[start+firstLoc[0]] = firstRegion.limitGroup
|
||||
}
|
||||
h.highlightEmptyRegion(highlights, start, false, lineNum, sliceEnd(line, firstLoc[0]), statesOnly)
|
||||
h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), firstRegion, statesOnly)
|
||||
h.highlightEmptyRegion(highlights, start, false, lineNum, line[:firstLoc[0]], statesOnly)
|
||||
h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, line[firstLoc[1]:], firstRegion, statesOnly)
|
||||
return highlights
|
||||
}
|
||||
|
||||
|
@ -256,7 +236,7 @@ func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canM
|
|||
|
||||
fullHighlights := make([]Group, len(line))
|
||||
for _, p := range h.Def.rules.patterns {
|
||||
matches := findAllIndex(p.regex, line)
|
||||
matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
|
||||
for _, m := range matches {
|
||||
for i := m[0]; i < m[1]; i++ {
|
||||
fullHighlights[i] = p.group
|
||||
|
@ -287,7 +267,7 @@ func (h *Highlighter) HighlightString(input string) []LineMatch {
|
|||
var lineMatches []LineMatch
|
||||
|
||||
for i := 0; i < len(lines); i++ {
|
||||
line := []byte(lines[i])
|
||||
line := []rune(lines[i])
|
||||
highlights := make(LineMatch)
|
||||
|
||||
if i == 0 || h.lastRegion == nil {
|
||||
|
@ -302,14 +282,8 @@ func (h *Highlighter) HighlightString(input string) []LineMatch {
|
|||
|
||||
// HighlightStates correctly sets all states for the buffer
|
||||
func (h *Highlighter) HighlightStates(input LineStates) {
|
||||
for i := 0; ; i++ {
|
||||
input.Lock()
|
||||
if i >= input.LinesNum() {
|
||||
input.Unlock()
|
||||
break
|
||||
}
|
||||
|
||||
line := input.LineBytes(i)
|
||||
for i := 0; i < input.LinesNum(); i++ {
|
||||
line := []rune(input.Line(i))
|
||||
// highlights := make(LineMatch)
|
||||
|
||||
if i == 0 || h.lastRegion == nil {
|
||||
|
@ -321,22 +295,19 @@ func (h *Highlighter) HighlightStates(input LineStates) {
|
|||
curState := h.lastRegion
|
||||
|
||||
input.SetState(i, curState)
|
||||
input.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// HighlightMatches sets the matches for each line from startline to endline
|
||||
// HighlightMatches sets the matches for each line in between startline and 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++ {
|
||||
input.Lock()
|
||||
for i := startline; i < endline; i++ {
|
||||
if i >= input.LinesNum() {
|
||||
input.Unlock()
|
||||
break
|
||||
}
|
||||
|
||||
line := input.LineBytes(i)
|
||||
line := []rune(input.Line(i))
|
||||
highlights := make(LineMatch)
|
||||
|
||||
var match LineMatch
|
||||
|
@ -347,32 +318,20 @@ func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int)
|
|||
}
|
||||
|
||||
input.SetMatch(i, match)
|
||||
input.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// ReHighlightStates will scan down from `startline` and set the appropriate end of line state
|
||||
// for each line until it comes across a line whose state does not change
|
||||
// returns the number of the final line
|
||||
func (h *Highlighter) ReHighlightStates(input LineStates, startline int) int {
|
||||
// for each line until it comes across the same state in two consecutive lines
|
||||
func (h *Highlighter) ReHighlightStates(input LineStates, startline int) {
|
||||
// lines := input.LineData()
|
||||
|
||||
h.lastRegion = nil
|
||||
if startline > 0 {
|
||||
input.Lock()
|
||||
if startline-1 < input.LinesNum() {
|
||||
h.lastRegion = input.State(startline - 1)
|
||||
}
|
||||
input.Unlock()
|
||||
h.lastRegion = input.State(startline - 1)
|
||||
}
|
||||
for i := startline; ; i++ {
|
||||
input.Lock()
|
||||
if i >= input.LinesNum() {
|
||||
input.Unlock()
|
||||
break
|
||||
}
|
||||
|
||||
line := input.LineBytes(i)
|
||||
for i := startline; i < input.LinesNum(); i++ {
|
||||
line := []rune(input.Line(i))
|
||||
// highlights := make(LineMatch)
|
||||
|
||||
// var match LineMatch
|
||||
|
@ -385,22 +344,16 @@ func (h *Highlighter) ReHighlightStates(input LineStates, startline int) int {
|
|||
lastState := input.State(i)
|
||||
|
||||
input.SetState(i, curState)
|
||||
input.Unlock()
|
||||
|
||||
if curState == lastState {
|
||||
return i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return input.LinesNum() - 1
|
||||
}
|
||||
|
||||
// ReHighlightLine will rehighlight the state and match for a single line
|
||||
func (h *Highlighter) ReHighlightLine(input LineStates, lineN int) {
|
||||
input.Lock()
|
||||
defer input.Unlock()
|
||||
|
||||
line := input.LineBytes(lineN)
|
||||
line := []rune(input.Line(lineN))
|
||||
highlights := make(LineMatch)
|
||||
|
||||
h.lastRegion = nil
|
|
@ -1,7 +1,6 @@
|
|||
package highlight
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
@ -33,28 +32,19 @@ func (g Group) String() string {
|
|||
// Then it has the rules which define how to highlight the file
|
||||
type Def struct {
|
||||
*Header
|
||||
|
||||
rules *rules
|
||||
}
|
||||
|
||||
type Header struct {
|
||||
FileType string
|
||||
FileNameRegex *regexp.Regexp
|
||||
HeaderRegex *regexp.Regexp
|
||||
SignatureRegex *regexp.Regexp
|
||||
}
|
||||
|
||||
type HeaderYaml struct {
|
||||
FileType string `yaml:"filetype"`
|
||||
Detect struct {
|
||||
FNameRegexStr string `yaml:"filename"`
|
||||
HeaderRegexStr string `yaml:"header"`
|
||||
SignatureRegexStr string `yaml:"signature"`
|
||||
} `yaml:"detect"`
|
||||
FileType string
|
||||
FtDetect [2]*regexp.Regexp
|
||||
}
|
||||
|
||||
type File struct {
|
||||
FileType string
|
||||
yamlSrc map[interface{}]interface{}
|
||||
|
||||
yamlSrc map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// A Pattern is one simple syntax rule
|
||||
|
@ -92,97 +82,52 @@ func init() {
|
|||
Groups = make(map[string]Group)
|
||||
}
|
||||
|
||||
// MakeHeader takes a header (.hdr file) file and parses the header
|
||||
// Header files make parsing more efficient when you only want to compute
|
||||
// on the headers of syntax files
|
||||
// A yaml file might take ~400us to parse while a header file only takes ~20us
|
||||
func MakeHeader(data []byte) (*Header, error) {
|
||||
lines := bytes.Split(data, []byte{'\n'})
|
||||
if len(lines) < 4 {
|
||||
return nil, errors.New("Header file has incorrect format")
|
||||
}
|
||||
header := new(Header)
|
||||
var err error
|
||||
header.FileType = string(lines[0])
|
||||
fnameRegexStr := string(lines[1])
|
||||
headerRegexStr := string(lines[2])
|
||||
signatureRegexStr := string(lines[3])
|
||||
func ParseFtDetect(file *File) (r [2]*regexp.Regexp, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
err, ok = r.(error)
|
||||
if !ok {
|
||||
err = fmt.Errorf("pkg: %v", r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if fnameRegexStr != "" {
|
||||
header.FileNameRegex, err = regexp.Compile(fnameRegexStr)
|
||||
}
|
||||
if err == nil && headerRegexStr != "" {
|
||||
header.HeaderRegex, err = regexp.Compile(headerRegexStr)
|
||||
}
|
||||
if err == nil && signatureRegexStr != "" {
|
||||
header.SignatureRegex, err = regexp.Compile(signatureRegexStr)
|
||||
rules := file.yamlSrc
|
||||
|
||||
loaded := 0
|
||||
for k, v := range rules {
|
||||
if k == "detect" {
|
||||
ftdetect := v.(map[interface{}]interface{})
|
||||
if len(ftdetect) >= 1 {
|
||||
syntax, err := regexp.Compile(ftdetect["filename"].(string))
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
|
||||
r[0] = syntax
|
||||
}
|
||||
if len(ftdetect) >= 2 {
|
||||
header, err := regexp.Compile(ftdetect["header"].(string))
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
|
||||
r[1] = header
|
||||
}
|
||||
loaded++
|
||||
}
|
||||
|
||||
if loaded >= 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if loaded == 0 {
|
||||
return r, errors.New("No detect regexes found")
|
||||
}
|
||||
|
||||
return header, nil
|
||||
}
|
||||
|
||||
// MakeHeaderYaml takes a yaml spec for a syntax file and parses the
|
||||
// header
|
||||
func MakeHeaderYaml(data []byte) (*Header, error) {
|
||||
var hdrYaml HeaderYaml
|
||||
err := yaml.Unmarshal(data, &hdrYaml)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
header := new(Header)
|
||||
header.FileType = hdrYaml.FileType
|
||||
|
||||
if hdrYaml.Detect.FNameRegexStr != "" {
|
||||
header.FileNameRegex, err = regexp.Compile(hdrYaml.Detect.FNameRegexStr)
|
||||
}
|
||||
if err == nil && hdrYaml.Detect.HeaderRegexStr != "" {
|
||||
header.HeaderRegex, err = regexp.Compile(hdrYaml.Detect.HeaderRegexStr)
|
||||
}
|
||||
if err == nil && hdrYaml.Detect.SignatureRegexStr != "" {
|
||||
header.SignatureRegex, err = regexp.Compile(hdrYaml.Detect.SignatureRegexStr)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return header, nil
|
||||
}
|
||||
|
||||
// MatchFileName will check the given file name with the stored regex
|
||||
func (header *Header) MatchFileName(filename string) bool {
|
||||
if header.FileNameRegex != nil {
|
||||
return header.FileNameRegex.MatchString(filename)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (header *Header) MatchFileHeader(firstLine []byte) bool {
|
||||
if header.HeaderRegex != nil {
|
||||
return header.HeaderRegex.Match(firstLine)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// HasFileSignature checks the presence of a stored signature
|
||||
func (header *Header) HasFileSignature() bool {
|
||||
return header.SignatureRegex != nil
|
||||
}
|
||||
|
||||
// MatchFileSignature will check the given line with the stored regex
|
||||
func (header *Header) MatchFileSignature(line []byte) bool {
|
||||
if header.SignatureRegex != nil {
|
||||
return header.SignatureRegex.Match(line)
|
||||
}
|
||||
|
||||
return false
|
||||
return r, err
|
||||
}
|
||||
|
||||
func ParseFile(input []byte) (f *File, err error) {
|
||||
|
@ -209,19 +154,11 @@ func ParseFile(input []byte) (f *File, err error) {
|
|||
if k == "filetype" {
|
||||
filetype := v.(string)
|
||||
|
||||
if filetype == "" {
|
||||
return nil, errors.New("empty filetype")
|
||||
}
|
||||
|
||||
f.FileType = filetype
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if f.FileType == "" {
|
||||
return nil, errors.New("missing filetype")
|
||||
}
|
||||
|
||||
return f, err
|
||||
}
|
||||
|
||||
|
@ -238,12 +175,12 @@ func ParseDef(f *File, header *Header) (s *Def, err error) {
|
|||
}
|
||||
}()
|
||||
|
||||
src := f.yamlSrc
|
||||
rules := f.yamlSrc
|
||||
|
||||
s = new(Def)
|
||||
s.Header = header
|
||||
|
||||
for k, v := range src {
|
||||
for k, v := range rules {
|
||||
if k == "rules" {
|
||||
inputRules := v.([]interface{})
|
||||
|
||||
|
@ -256,48 +193,9 @@ func ParseDef(f *File, header *Header) (s *Def, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
if s.rules == nil {
|
||||
// allow empty rules
|
||||
s.rules = new(rules)
|
||||
}
|
||||
|
||||
return s, err
|
||||
}
|
||||
|
||||
// HasIncludes returns whether this syntax def has any include statements
|
||||
func HasIncludes(d *Def) bool {
|
||||
hasIncludes := len(d.rules.includes) > 0
|
||||
for _, r := range d.rules.regions {
|
||||
hasIncludes = hasIncludes || hasIncludesInRegion(r)
|
||||
}
|
||||
return hasIncludes
|
||||
}
|
||||
|
||||
func hasIncludesInRegion(region *region) bool {
|
||||
hasIncludes := len(region.rules.includes) > 0
|
||||
for _, r := range region.rules.regions {
|
||||
hasIncludes = hasIncludes || hasIncludesInRegion(r)
|
||||
}
|
||||
return hasIncludes
|
||||
}
|
||||
|
||||
// GetIncludes returns a list of filetypes that are included by this syntax def
|
||||
func GetIncludes(d *Def) []string {
|
||||
includes := d.rules.includes
|
||||
for _, r := range d.rules.regions {
|
||||
includes = append(includes, getIncludesInRegion(r)...)
|
||||
}
|
||||
return includes
|
||||
}
|
||||
|
||||
func getIncludesInRegion(region *region) []string {
|
||||
includes := region.rules.includes
|
||||
for _, r := range region.rules.regions {
|
||||
includes = append(includes, getIncludesInRegion(r)...)
|
||||
}
|
||||
return includes
|
||||
}
|
||||
|
||||
// ResolveIncludes will sort out the rules for including other filetypes
|
||||
// You should call this after parsing all the Defs
|
||||
func ResolveIncludes(def *Def, files []*File) {
|
||||
|
@ -355,10 +253,6 @@ func parseRules(input []interface{}, curRegion *region) (ru *rules, err error) {
|
|||
|
||||
switch object := val.(type) {
|
||||
case string:
|
||||
if object == "" {
|
||||
return nil, fmt.Errorf("Empty rule %s", k)
|
||||
}
|
||||
|
||||
if k == "include" {
|
||||
ru.includes = append(ru.includes, object)
|
||||
} else {
|
||||
|
@ -412,56 +306,30 @@ func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegio
|
|||
r.group = groupNum
|
||||
r.parent = prevRegion
|
||||
|
||||
// start is mandatory
|
||||
if start, ok := regionInfo["start"]; ok {
|
||||
start := start.(string)
|
||||
if start == "" {
|
||||
return nil, fmt.Errorf("Empty start in %s", group)
|
||||
}
|
||||
r.start, err = regexp.Compile(regionInfo["start"].(string))
|
||||
|
||||
r.start, err = regexp.Compile(start)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("Missing start in %s", group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// end is mandatory
|
||||
if end, ok := regionInfo["end"]; ok {
|
||||
end := end.(string)
|
||||
if end == "" {
|
||||
return nil, fmt.Errorf("Empty end in %s", group)
|
||||
}
|
||||
r.end, err = regexp.Compile(regionInfo["end"].(string))
|
||||
|
||||
r.end, err = regexp.Compile(end)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("Missing end in %s", group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// skip is optional
|
||||
if skip, ok := regionInfo["skip"]; ok {
|
||||
skip := skip.(string)
|
||||
if skip == "" {
|
||||
return nil, fmt.Errorf("Empty skip in %s", group)
|
||||
}
|
||||
if _, ok := regionInfo["skip"]; ok {
|
||||
r.skip, err = regexp.Compile(regionInfo["skip"].(string))
|
||||
|
||||
r.skip, err = regexp.Compile(skip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// limit-color is optional
|
||||
if groupStr, ok := regionInfo["limit-group"]; ok {
|
||||
groupStr := groupStr.(string)
|
||||
if groupStr == "" {
|
||||
return nil, fmt.Errorf("Empty limit-group in %s", group)
|
||||
}
|
||||
|
||||
if _, ok := regionInfo["limit-group"]; ok {
|
||||
groupStr := regionInfo["limit-group"].(string)
|
||||
if _, ok := Groups[groupStr]; !ok {
|
||||
numGroups++
|
||||
Groups[groupStr] = numGroups
|
28
cmd/micro/highlighter.go
Normal file
28
cmd/micro/highlighter.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package main
|
||||
|
||||
import "github.com/zyedidia/micro/cmd/micro/highlight"
|
||||
|
||||
var syntaxFiles []*highlight.File
|
||||
|
||||
func LoadSyntaxFiles() {
|
||||
InitColorscheme()
|
||||
for _, f := range ListRuntimeFiles(RTSyntax) {
|
||||
data, err := f.Data()
|
||||
if err != nil {
|
||||
TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
|
||||
} else {
|
||||
LoadSyntaxFile(data, f.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func LoadSyntaxFile(text []byte, filename string) {
|
||||
f, err := highlight.ParseFile(text)
|
||||
|
||||
if err != nil {
|
||||
TermMessage("Syntax file error: " + filename + ": " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
syntaxFiles = append(syntaxFiles, f)
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
"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() {
|
||||
ulua.L = lua.NewState()
|
||||
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":
|
||||
return luaImportMicro()
|
||||
case "micro/shell":
|
||||
return luaImportMicroShell()
|
||||
case "micro/buffer":
|
||||
return luaImportMicroBuffer()
|
||||
case "micro/config":
|
||||
return luaImportMicroConfig()
|
||||
case "micro/util":
|
||||
return luaImportMicroUtil()
|
||||
default:
|
||||
return ulua.Import(pkg)
|
||||
}
|
||||
}
|
||||
|
||||
func luaImportMicro() *lua.LTable {
|
||||
pkg := ulua.L.NewTable()
|
||||
|
||||
ulua.L.SetField(pkg, "TermMessage", luar.New(ulua.L, screen.TermMessage))
|
||||
ulua.L.SetField(pkg, "TermError", luar.New(ulua.L, screen.TermError))
|
||||
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.BufPane {
|
||||
return action.MainTab().CurPane()
|
||||
}))
|
||||
ulua.L.SetField(pkg, "CurTab", luar.New(ulua.L, action.MainTab))
|
||||
ulua.L.SetField(pkg, "Tabs", luar.New(ulua.L, func() *action.TabList {
|
||||
return action.Tabs
|
||||
}))
|
||||
ulua.L.SetField(pkg, "After", luar.New(ulua.L, func(t time.Duration, f func()) {
|
||||
time.AfterFunc(t, func() {
|
||||
timerChan <- f
|
||||
})
|
||||
}))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func luaImportMicroConfig() *lua.LTable {
|
||||
pkg := ulua.L.NewTable()
|
||||
|
||||
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))
|
||||
ulua.L.SetField(pkg, "OptionValueComplete", luar.New(ulua.L, action.OptionValueComplete))
|
||||
ulua.L.SetField(pkg, "NoComplete", luar.New(ulua.L, nil))
|
||||
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.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.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
|
||||
}
|
||||
|
||||
func luaImportMicroShell() *lua.LTable {
|
||||
pkg := ulua.L.NewTable()
|
||||
|
||||
ulua.L.SetField(pkg, "ExecCommand", luar.New(ulua.L, shell.ExecCommand))
|
||||
ulua.L.SetField(pkg, "RunCommand", luar.New(ulua.L, shell.RunCommand))
|
||||
ulua.L.SetField(pkg, "RunBackgroundShell", luar.New(ulua.L, shell.RunBackgroundShell))
|
||||
ulua.L.SetField(pkg, "RunInteractiveShell", luar.New(ulua.L, shell.RunInteractiveShell))
|
||||
ulua.L.SetField(pkg, "JobStart", luar.New(ulua.L, shell.JobStart))
|
||||
ulua.L.SetField(pkg, "JobSpawn", luar.New(ulua.L, shell.JobSpawn))
|
||||
ulua.L.SetField(pkg, "JobStop", luar.New(ulua.L, shell.JobStop))
|
||||
ulua.L.SetField(pkg, "JobSend", luar.New(ulua.L, shell.JobSend))
|
||||
ulua.L.SetField(pkg, "RunTermEmulator", luar.New(ulua.L, action.RunTermEmulator))
|
||||
ulua.L.SetField(pkg, "TermEmuSupported", luar.New(ulua.L, action.TermEmuSupported))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func luaImportMicroBuffer() *lua.LTable {
|
||||
pkg := ulua.L.NewTable()
|
||||
|
||||
ulua.L.SetField(pkg, "NewMessage", luar.New(ulua.L, buffer.NewMessage))
|
||||
ulua.L.SetField(pkg, "NewMessageAtLine", luar.New(ulua.L, buffer.NewMessageAtLine))
|
||||
ulua.L.SetField(pkg, "MTInfo", luar.New(ulua.L, buffer.MTInfo))
|
||||
ulua.L.SetField(pkg, "MTWarning", luar.New(ulua.L, buffer.MTWarning))
|
||||
ulua.L.SetField(pkg, "MTError", luar.New(ulua.L, buffer.MTError))
|
||||
ulua.L.SetField(pkg, "Loc", luar.New(ulua.L, func(x, y int) buffer.Loc {
|
||||
return buffer.Loc{x, y}
|
||||
}))
|
||||
ulua.L.SetField(pkg, "SLoc", luar.New(ulua.L, func(line, row int) display.SLoc {
|
||||
return display.SLoc{line, row}
|
||||
}))
|
||||
ulua.L.SetField(pkg, "BTDefault", luar.New(ulua.L, buffer.BTDefault.Kind))
|
||||
ulua.L.SetField(pkg, "BTHelp", luar.New(ulua.L, buffer.BTHelp.Kind))
|
||||
ulua.L.SetField(pkg, "BTLog", luar.New(ulua.L, buffer.BTLog.Kind))
|
||||
ulua.L.SetField(pkg, "BTScratch", luar.New(ulua.L, buffer.BTScratch.Kind))
|
||||
ulua.L.SetField(pkg, "BTRaw", luar.New(ulua.L, buffer.BTRaw.Kind))
|
||||
ulua.L.SetField(pkg, "BTInfo", luar.New(ulua.L, buffer.BTInfo.Kind))
|
||||
ulua.L.SetField(pkg, "NewBuffer", luar.New(ulua.L, func(text, path string) *buffer.Buffer {
|
||||
return buffer.NewBufferFromString(text, path, buffer.BTDefault)
|
||||
}))
|
||||
ulua.L.SetField(pkg, "NewBufferFromFile", luar.New(ulua.L, func(path string) (*buffer.Buffer, error) {
|
||||
return buffer.NewBufferFromFile(path, buffer.BTDefault)
|
||||
}))
|
||||
ulua.L.SetField(pkg, "ByteOffset", luar.New(ulua.L, buffer.ByteOffset))
|
||||
ulua.L.SetField(pkg, "Log", luar.New(ulua.L, buffer.WriteLog))
|
||||
ulua.L.SetField(pkg, "LogBuf", luar.New(ulua.L, buffer.GetLogBuf))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func luaImportMicroUtil() *lua.LTable {
|
||||
pkg := ulua.L.NewTable()
|
||||
|
||||
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, "Unzip", luar.New(ulua.L, util.Unzip))
|
||||
ulua.L.SetField(pkg, "Version", luar.New(ulua.L, util.Version))
|
||||
ulua.L.SetField(pkg, "SemVersion", luar.New(ulua.L, util.SemVersion))
|
||||
ulua.L.SetField(pkg, "HttpRequest", luar.New(ulua.L, util.HttpRequest))
|
||||
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
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package shell
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -6,12 +6,6 @@ import (
|
|||
"os/exec"
|
||||
)
|
||||
|
||||
var Jobs chan JobFunction
|
||||
|
||||
func init() {
|
||||
Jobs = make(chan JobFunction, 100)
|
||||
}
|
||||
|
||||
// Jobs are the way plugins can run processes in the background
|
||||
// A job is simply a process that gets executed asynchronously
|
||||
// There are callbacks for when the job exits, when the job creates stdout
|
||||
|
@ -24,73 +18,71 @@ 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{})
|
||||
Output string
|
||||
Args []interface{}
|
||||
function func(string, ...string)
|
||||
output string
|
||||
args []string
|
||||
}
|
||||
|
||||
// A CallbackFile is the data structure that makes it possible to catch stderr and stdout write events
|
||||
type CallbackFile struct {
|
||||
io.Writer
|
||||
|
||||
callback func(string, []interface{})
|
||||
args []interface{}
|
||||
}
|
||||
|
||||
// Job stores the executing command for the job, and the stdin pipe
|
||||
type Job struct {
|
||||
*exec.Cmd
|
||||
Stdin io.WriteCloser
|
||||
callback func(string, ...string)
|
||||
args []string
|
||||
}
|
||||
|
||||
func (f *CallbackFile) Write(data []byte) (int, error) {
|
||||
// This is either stderr or stdout
|
||||
// In either case we create a new job function callback and put it in the jobs channel
|
||||
jobFunc := JobFunction{f.callback, string(data), f.args}
|
||||
Jobs <- jobFunc
|
||||
jobs <- jobFunc
|
||||
return f.Writer.Write(data)
|
||||
}
|
||||
|
||||
// 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 func(string, []interface{}), userargs ...interface{}) *Job {
|
||||
func JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...string) *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 func(string, []interface{}), userargs ...interface{}) *Job {
|
||||
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit string, userargs ...string) *exec.Cmd {
|
||||
// Set up everything correctly if the functions have been provided
|
||||
proc := exec.Command(cmdName, cmdArgs...)
|
||||
var outbuf bytes.Buffer
|
||||
if onStdout != nil {
|
||||
proc.Stdout = &CallbackFile{&outbuf, onStdout, userargs}
|
||||
if onStdout != "" {
|
||||
proc.Stdout = &CallbackFile{&outbuf, LuaFunctionJob(onStdout), userargs}
|
||||
} else {
|
||||
proc.Stdout = &outbuf
|
||||
}
|
||||
if onStderr != nil {
|
||||
proc.Stderr = &CallbackFile{&outbuf, onStderr, userargs}
|
||||
if onStderr != "" {
|
||||
proc.Stderr = &CallbackFile{&outbuf, LuaFunctionJob(onStderr), userargs}
|
||||
} else {
|
||||
proc.Stderr = &outbuf
|
||||
}
|
||||
stdin, _ := proc.StdinPipe()
|
||||
|
||||
go func() {
|
||||
// Run the process in the background and create the onExit callback
|
||||
proc.Run()
|
||||
jobFunc := JobFunction{onExit, outbuf.String(), userargs}
|
||||
Jobs <- jobFunc
|
||||
jobFunc := JobFunction{LuaFunctionJob(onExit), string(outbuf.Bytes()), userargs}
|
||||
jobs <- jobFunc
|
||||
}()
|
||||
|
||||
return &Job{proc, stdin}
|
||||
return proc
|
||||
}
|
||||
|
||||
// JobStop kills a job
|
||||
func JobStop(j *Job) {
|
||||
j.Process.Kill()
|
||||
func JobStop(cmd *exec.Cmd) {
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
|
||||
// JobSend sends the given data into the job's stdin stream
|
||||
func JobSend(j *Job, data string) {
|
||||
j.Stdin.Write([]byte(data))
|
||||
func JobSend(cmd *exec.Cmd, data string) {
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
stdin.Write([]byte(data))
|
||||
}
|
260
cmd/micro/lineArray.go
Normal file
260
cmd/micro/lineArray.go
Normal file
|
@ -0,0 +1,260 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/micro/cmd/micro/highlight"
|
||||
)
|
||||
|
||||
func runeToByteIndex(n int, txt []byte) int {
|
||||
if n == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
count := 0
|
||||
i := 0
|
||||
for len(txt) > 0 {
|
||||
_, size := utf8.DecodeRune(txt)
|
||||
|
||||
txt = txt[size:]
|
||||
count += size
|
||||
i++
|
||||
|
||||
if i == n {
|
||||
break
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
type Line struct {
|
||||
data []byte
|
||||
|
||||
state highlight.State
|
||||
match highlight.LineMatch
|
||||
rehighlight bool
|
||||
}
|
||||
|
||||
// A LineArray simply stores and array of lines and makes it easy to insert
|
||||
// and delete in it
|
||||
type LineArray struct {
|
||||
lines []Line
|
||||
}
|
||||
|
||||
func Append(slice []Line, data ...Line) []Line {
|
||||
l := len(slice)
|
||||
if l+len(data) > cap(slice) { // reallocate
|
||||
// Allocate double what's needed, for future growth.
|
||||
newSlice := make([]Line, (l+len(data))+10000)
|
||||
// The copy function is predeclared and works for any slice type.
|
||||
copy(newSlice, slice)
|
||||
slice = newSlice
|
||||
}
|
||||
slice = slice[0 : l+len(data)]
|
||||
for i, c := range data {
|
||||
slice[l+i] = c
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// NewLineArray returns a new line array from an array of bytes
|
||||
func NewLineArray(size int64, reader io.Reader) *LineArray {
|
||||
la := new(LineArray)
|
||||
|
||||
la.lines = make([]Line, 0, 1000)
|
||||
|
||||
br := bufio.NewReader(reader)
|
||||
var loaded int
|
||||
|
||||
n := 0
|
||||
for {
|
||||
data, err := br.ReadBytes('\n')
|
||||
if len(data) > 1 && data[len(data)-2] == '\r' {
|
||||
data = append(data[:len(data)-2], '\n')
|
||||
if fileformat == 0 {
|
||||
fileformat = 2
|
||||
}
|
||||
} else if len(data) > 0 {
|
||||
if fileformat == 0 {
|
||||
fileformat = 1
|
||||
}
|
||||
}
|
||||
|
||||
if n >= 1000 && loaded >= 0 {
|
||||
totalLinesNum := int(float64(size) * (float64(n) / float64(loaded)))
|
||||
newSlice := make([]Line, len(la.lines), totalLinesNum+10000)
|
||||
// The copy function is predeclared and works for any slice type.
|
||||
copy(newSlice, la.lines)
|
||||
la.lines = newSlice
|
||||
loaded = -1
|
||||
}
|
||||
|
||||
if loaded >= 0 {
|
||||
loaded += len(data)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
la.lines = Append(la.lines, Line{data[:], nil, nil, false})
|
||||
// la.lines = Append(la.lines, Line{data[:len(data)]})
|
||||
}
|
||||
// Last line was read
|
||||
break
|
||||
} else {
|
||||
// la.lines = Append(la.lines, Line{data[:len(data)-1]})
|
||||
la.lines = Append(la.lines, Line{data[:len(data)-1], nil, nil, false})
|
||||
}
|
||||
n++
|
||||
}
|
||||
|
||||
return la
|
||||
}
|
||||
|
||||
// Returns the String representation of the LineArray
|
||||
func (la *LineArray) String() string {
|
||||
str := ""
|
||||
for i, l := range la.lines {
|
||||
str += string(l.data)
|
||||
if i != len(la.lines)-1 {
|
||||
str += "\n"
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// SaveString returns the string that should be written to disk when
|
||||
// the line array is saved
|
||||
// It is the same as string but uses crlf or lf line endings depending
|
||||
func (la *LineArray) SaveString(useCrlf bool) string {
|
||||
str := ""
|
||||
for i, l := range la.lines {
|
||||
str += string(l.data)
|
||||
if i != len(la.lines)-1 {
|
||||
if useCrlf {
|
||||
str += "\r"
|
||||
}
|
||||
str += "\n"
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// NewlineBelow adds a newline below the given line number
|
||||
func (la *LineArray) NewlineBelow(y int) {
|
||||
la.lines = append(la.lines, Line{[]byte(" "), nil, nil, false})
|
||||
copy(la.lines[y+2:], la.lines[y+1:])
|
||||
la.lines[y+1] = Line{[]byte(""), la.lines[y].state, nil, false}
|
||||
}
|
||||
|
||||
// inserts a byte array at a given location
|
||||
func (la *LineArray) insert(pos Loc, value []byte) {
|
||||
x, y := runeToByteIndex(pos.X, la.lines[pos.Y].data), pos.Y
|
||||
// x, y := pos.x, pos.y
|
||||
for i := 0; i < len(value); i++ {
|
||||
if value[i] == '\n' {
|
||||
la.Split(Loc{x, y})
|
||||
x = 0
|
||||
y++
|
||||
continue
|
||||
}
|
||||
la.insertByte(Loc{x, y}, value[i])
|
||||
x++
|
||||
}
|
||||
}
|
||||
|
||||
// inserts a byte at a given location
|
||||
func (la *LineArray) insertByte(pos Loc, value byte) {
|
||||
la.lines[pos.Y].data = append(la.lines[pos.Y].data, 0)
|
||||
copy(la.lines[pos.Y].data[pos.X+1:], la.lines[pos.Y].data[pos.X:])
|
||||
la.lines[pos.Y].data[pos.X] = value
|
||||
}
|
||||
|
||||
// JoinLines joins the two lines a and b
|
||||
func (la *LineArray) JoinLines(a, b int) {
|
||||
la.insert(Loc{len(la.lines[a].data), a}, la.lines[b].data)
|
||||
la.DeleteLine(b)
|
||||
}
|
||||
|
||||
// Split splits a line at a given position
|
||||
func (la *LineArray) Split(pos Loc) {
|
||||
la.NewlineBelow(pos.Y)
|
||||
la.insert(Loc{0, pos.Y + 1}, la.lines[pos.Y].data[pos.X:])
|
||||
la.lines[pos.Y+1].state = la.lines[pos.Y].state
|
||||
la.lines[pos.Y].state = nil
|
||||
la.lines[pos.Y].match = nil
|
||||
la.lines[pos.Y+1].match = nil
|
||||
la.lines[pos.Y].rehighlight = true
|
||||
la.DeleteToEnd(Loc{pos.X, pos.Y})
|
||||
}
|
||||
|
||||
// removes from start to end
|
||||
func (la *LineArray) remove(start, end Loc) string {
|
||||
sub := la.Substr(start, end)
|
||||
startX := runeToByteIndex(start.X, la.lines[start.Y].data)
|
||||
endX := runeToByteIndex(end.X, la.lines[end.Y].data)
|
||||
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.DeleteToEnd(Loc{startX, start.Y})
|
||||
la.DeleteFromStart(Loc{endX - 1, start.Y + 1})
|
||||
la.JoinLines(start.Y, start.Y+1)
|
||||
}
|
||||
return sub
|
||||
}
|
||||
|
||||
// DeleteToEnd deletes from the end of a line to the position
|
||||
func (la *LineArray) DeleteToEnd(pos Loc) {
|
||||
la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X]
|
||||
}
|
||||
|
||||
// DeleteFromStart deletes from the start of a line to the position
|
||||
func (la *LineArray) DeleteFromStart(pos Loc) {
|
||||
la.lines[pos.Y].data = la.lines[pos.Y].data[pos.X+1:]
|
||||
}
|
||||
|
||||
// DeleteLine deletes the line number
|
||||
func (la *LineArray) DeleteLine(y int) {
|
||||
la.lines = la.lines[:y+copy(la.lines[y:], la.lines[y+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:])]
|
||||
}
|
||||
|
||||
// Substr returns the string representation between two locations
|
||||
func (la *LineArray) Substr(start, end Loc) string {
|
||||
startX := runeToByteIndex(start.X, la.lines[start.Y].data)
|
||||
endX := runeToByteIndex(end.X, la.lines[end.Y].data)
|
||||
if start.Y == end.Y {
|
||||
return string(la.lines[start.Y].data[startX:endX])
|
||||
}
|
||||
var str string
|
||||
str += string(la.lines[start.Y].data[startX:]) + "\n"
|
||||
for i := start.Y + 1; i <= end.Y-1; i++ {
|
||||
str += string(la.lines[i].data) + "\n"
|
||||
}
|
||||
str += string(la.lines[end.Y].data[:endX])
|
||||
return str
|
||||
}
|
||||
|
||||
func (la *LineArray) State(lineN int) highlight.State {
|
||||
return la.lines[lineN].state
|
||||
}
|
||||
|
||||
func (la *LineArray) SetState(lineN int, s highlight.State) {
|
||||
la.lines[lineN].state = s
|
||||
}
|
||||
|
||||
func (la *LineArray) SetMatch(lineN int, m highlight.LineMatch) {
|
||||
la.lines[lineN].match = m
|
||||
}
|
||||
|
||||
func (la *LineArray) Match(lineN int) highlight.LineMatch {
|
||||
return la.lines[lineN].match
|
||||
}
|
|
@ -1,56 +1,60 @@
|
|||
package buffer
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
// FromCharPos converts from a character position to an x, y position
|
||||
func FromCharPos(loc int, buf *Buffer) Loc {
|
||||
charNum := 0
|
||||
x, y := 0, 0
|
||||
|
||||
lineLen := Count(buf.Line(y)) + 1
|
||||
for charNum+lineLen <= loc {
|
||||
charNum += lineLen
|
||||
y++
|
||||
lineLen = Count(buf.Line(y)) + 1
|
||||
}
|
||||
x = loc - charNum
|
||||
|
||||
return Loc{x, y}
|
||||
}
|
||||
|
||||
// ToCharPos converts from an x, y position to a character position
|
||||
func ToCharPos(start Loc, buf *Buffer) int {
|
||||
x, y := start.X, start.Y
|
||||
loc := 0
|
||||
for i := 0; i < y; i++ {
|
||||
// + 1 for the newline
|
||||
loc += Count(buf.Line(i)) + 1
|
||||
}
|
||||
loc += x
|
||||
return loc
|
||||
}
|
||||
|
||||
// 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 >= buf.NumLines || pos.X < 0 || pos.X > Count(buf.Line(pos.Y)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ByteOffset is just like ToCharPos except it counts bytes instead of runes
|
||||
func ByteOffset(pos Loc, buf *Buffer) int {
|
||||
x, y := pos.X, pos.Y
|
||||
loc := 0
|
||||
for i := 0; i < y; i++ {
|
||||
// + 1 for the newline
|
||||
loc += len(buf.Line(i)) + 1
|
||||
}
|
||||
loc += len(buf.Line(y)[:x])
|
||||
return loc
|
||||
}
|
||||
|
||||
// Loc stores a location
|
||||
type Loc struct {
|
||||
X, Y int
|
||||
}
|
||||
|
||||
// LessThan returns true if b is smaller
|
||||
func (l Loc) LessThan(b Loc) bool {
|
||||
if l.Y < b.Y {
|
||||
return true
|
||||
}
|
||||
return l.Y == b.Y && l.X < b.X
|
||||
}
|
||||
|
||||
// GreaterThan returns true if b is bigger
|
||||
func (l Loc) GreaterThan(b Loc) bool {
|
||||
if l.Y > b.Y {
|
||||
return true
|
||||
}
|
||||
return l.Y == b.Y && l.X > b.X
|
||||
}
|
||||
|
||||
// GreaterEqual returns true if b is greater than or equal to b
|
||||
func (l Loc) GreaterEqual(b Loc) bool {
|
||||
if l.Y > b.Y {
|
||||
return true
|
||||
}
|
||||
if l.Y == b.Y && l.X > b.X {
|
||||
return true
|
||||
}
|
||||
return l == b
|
||||
}
|
||||
|
||||
// LessEqual returns true if b is less than or equal to b
|
||||
func (l Loc) LessEqual(b Loc) bool {
|
||||
if l.Y < b.Y {
|
||||
return true
|
||||
}
|
||||
if l.Y == b.Y && l.X < b.X {
|
||||
return true
|
||||
}
|
||||
return l == b
|
||||
}
|
||||
|
||||
// The following functions require a buffer to know where newlines are
|
||||
|
||||
// Diff returns the distance between two locations
|
||||
func DiffLA(a, b Loc, buf *LineArray) int {
|
||||
func Diff(a, b Loc, buf *Buffer) int {
|
||||
if a.Y == b.Y {
|
||||
if a.X > b.X {
|
||||
return a.X - b.X
|
||||
|
@ -66,19 +70,69 @@ func DiffLA(a, b Loc, buf *LineArray) int {
|
|||
loc := 0
|
||||
for i := a.Y + 1; i < b.Y; i++ {
|
||||
// + 1 for the newline
|
||||
loc += util.CharacterCount(buf.LineBytes(i)) + 1
|
||||
loc += Count(buf.Line(i)) + 1
|
||||
}
|
||||
loc += util.CharacterCount(buf.LineBytes(a.Y)) - a.X + b.X + 1
|
||||
loc += Count(buf.Line(a.Y)) - a.X + b.X + 1
|
||||
return loc
|
||||
}
|
||||
|
||||
// LessThan returns true if b is smaller
|
||||
func (l Loc) LessThan(b Loc) bool {
|
||||
if l.Y < b.Y {
|
||||
return true
|
||||
}
|
||||
if l.Y == b.Y && l.X < b.X {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GreaterThan returns true if b is bigger
|
||||
func (l Loc) GreaterThan(b Loc) bool {
|
||||
if l.Y > b.Y {
|
||||
return true
|
||||
}
|
||||
if l.Y == b.Y && l.X > b.X {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GreaterEqual returns true if b is greater than or equal to b
|
||||
func (l Loc) GreaterEqual(b Loc) bool {
|
||||
if l.Y > b.Y {
|
||||
return true
|
||||
}
|
||||
if l.Y == b.Y && l.X > b.X {
|
||||
return true
|
||||
}
|
||||
if l == b {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LessEqual returns true if b is less than or equal to b
|
||||
func (l Loc) LessEqual(b Loc) bool {
|
||||
if l.Y < b.Y {
|
||||
return true
|
||||
}
|
||||
if l.Y == b.Y && l.X < b.X {
|
||||
return true
|
||||
}
|
||||
if l == b {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// This moves the location one character to the right
|
||||
func (l Loc) right(buf *LineArray) Loc {
|
||||
func (l Loc) right(buf *Buffer) Loc {
|
||||
if l == buf.End() {
|
||||
return Loc{l.X + 1, l.Y}
|
||||
}
|
||||
var res Loc
|
||||
if l.X < util.CharacterCount(buf.LineBytes(l.Y)) {
|
||||
if l.X < Count(buf.Line(l.Y)) {
|
||||
res = Loc{l.X + 1, l.Y}
|
||||
} else {
|
||||
res = Loc{0, l.Y + 1}
|
||||
|
@ -87,7 +141,7 @@ func (l Loc) right(buf *LineArray) Loc {
|
|||
}
|
||||
|
||||
// This moves the given location one character to the left
|
||||
func (l Loc) left(buf *LineArray) Loc {
|
||||
func (l Loc) left(buf *Buffer) Loc {
|
||||
if l == buf.Start() {
|
||||
return Loc{l.X - 1, l.Y}
|
||||
}
|
||||
|
@ -95,54 +149,22 @@ func (l Loc) left(buf *LineArray) Loc {
|
|||
if l.X > 0 {
|
||||
res = Loc{l.X - 1, l.Y}
|
||||
} else {
|
||||
res = Loc{util.CharacterCount(buf.LineBytes(l.Y - 1)), l.Y - 1}
|
||||
res = Loc{Count(buf.Line(l.Y - 1)), l.Y - 1}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// MoveLA moves the cursor n characters to the left or right
|
||||
// Move 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 {
|
||||
func (l Loc) Move(n int, buf *Buffer) Loc {
|
||||
if n > 0 {
|
||||
for i := 0; i < n; i++ {
|
||||
l = l.right(buf)
|
||||
}
|
||||
return l
|
||||
}
|
||||
for i := 0; i < util.Abs(n); i++ {
|
||||
for i := 0; i < Abs(n); i++ {
|
||||
l = l.left(buf)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// ByteOffset is just like ToCharPos except it counts bytes instead of runes
|
||||
func ByteOffset(pos Loc, buf *Buffer) int {
|
||||
x, y := pos.X, pos.Y
|
||||
loc := 0
|
||||
for i := 0; i < y; i++ {
|
||||
// + 1 for the newline
|
||||
loc += len(buf.Line(i)) + 1
|
||||
}
|
||||
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,8 +1,6 @@
|
|||
package lua
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -10,7 +8,6 @@ import (
|
|||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
@ -18,20 +15,23 @@ import (
|
|||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
var L *lua.LState
|
||||
|
||||
// LoadFile loads a lua file
|
||||
func LoadFile(module string, file string, data []byte) error {
|
||||
pluginDef := []byte("module(\"" + module + "\", package.seeall)")
|
||||
func init() {
|
||||
L = lua.NewState()
|
||||
L.SetGlobal("import", luar.New(L, Import))
|
||||
}
|
||||
|
||||
if fn, err := L.Load(bytes.NewReader(append(pluginDef, data...)), file); err != nil {
|
||||
func LoadFile(module string, file string, data string) error {
|
||||
pluginDef := "local P = {};" + module + " = P;setmetatable(" + module + ", {__index = _G});setfenv(1, P);"
|
||||
|
||||
if fn, err := L.Load(strings.NewReader(pluginDef+data), file); err != nil {
|
||||
return err
|
||||
} else {
|
||||
L.Push(fn)
|
||||
|
@ -39,54 +39,43 @@ func LoadFile(module string, file string, data []byte) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Import allows a lua plugin to import a package
|
||||
func Import(pkg string) *lua.LTable {
|
||||
switch pkg {
|
||||
case "fmt":
|
||||
return importFmt()
|
||||
return ImportFmt()
|
||||
case "io":
|
||||
return importIo()
|
||||
case "io/ioutil", "ioutil":
|
||||
return importIoUtil()
|
||||
return ImportIo()
|
||||
case "ioutil":
|
||||
return ImportIoUtil()
|
||||
case "net":
|
||||
return importNet()
|
||||
return ImportNet()
|
||||
case "math":
|
||||
return importMath()
|
||||
case "math/rand":
|
||||
return importMathRand()
|
||||
return ImportMath()
|
||||
case "os":
|
||||
return importOs()
|
||||
return ImportOs()
|
||||
case "runtime":
|
||||
return importRuntime()
|
||||
return ImportRuntime()
|
||||
case "path":
|
||||
return importPath()
|
||||
case "path/filepath", "filepath":
|
||||
return importFilePath()
|
||||
return ImportPath()
|
||||
case "filepath":
|
||||
return ImportFilePath()
|
||||
case "strings":
|
||||
return importStrings()
|
||||
return ImportStrings()
|
||||
case "regexp":
|
||||
return importRegexp()
|
||||
return ImportRegexp()
|
||||
case "errors":
|
||||
return importErrors()
|
||||
return ImportErrors()
|
||||
case "time":
|
||||
return importTime()
|
||||
case "unicode/utf8", "utf8":
|
||||
return importUtf8()
|
||||
case "humanize":
|
||||
return importHumanize()
|
||||
case "net/http", "http":
|
||||
return importHTTP()
|
||||
case "archive/zip":
|
||||
return importArchiveZip()
|
||||
return ImportTime()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func importFmt() *lua.LTable {
|
||||
func ImportFmt() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Errorf", luar.New(L, fmt.Errorf))
|
||||
L.SetField(pkg, "tErrorf", luar.New(L, fmt.Errorf))
|
||||
L.SetField(pkg, "Fprint", luar.New(L, fmt.Fprint))
|
||||
L.SetField(pkg, "Fprintf", luar.New(L, fmt.Fprintf))
|
||||
L.SetField(pkg, "Fprintln", luar.New(L, fmt.Fprintln))
|
||||
|
@ -109,7 +98,7 @@ func importFmt() *lua.LTable {
|
|||
return pkg
|
||||
}
|
||||
|
||||
func importIo() *lua.LTable {
|
||||
func ImportIo() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Copy", luar.New(L, io.Copy))
|
||||
|
@ -133,7 +122,7 @@ func importIo() *lua.LTable {
|
|||
return pkg
|
||||
}
|
||||
|
||||
func importIoUtil() *lua.LTable {
|
||||
func ImportIoUtil() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "ReadAll", luar.New(L, ioutil.ReadAll))
|
||||
|
@ -144,7 +133,7 @@ func importIoUtil() *lua.LTable {
|
|||
return pkg
|
||||
}
|
||||
|
||||
func importNet() *lua.LTable {
|
||||
func ImportNet() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "CIDRMask", luar.New(L, net.CIDRMask))
|
||||
|
@ -212,7 +201,7 @@ func importNet() *lua.LTable {
|
|||
return pkg
|
||||
}
|
||||
|
||||
func importMath() *lua.LTable {
|
||||
func ImportMath() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Abs", luar.New(L, math.Abs))
|
||||
|
@ -280,7 +269,7 @@ func importMath() *lua.LTable {
|
|||
return pkg
|
||||
}
|
||||
|
||||
func importMathRand() *lua.LTable {
|
||||
func ImportMathRand() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "ExpFloat64", luar.New(L, rand.ExpFloat64))
|
||||
|
@ -300,7 +289,7 @@ func importMathRand() *lua.LTable {
|
|||
return pkg
|
||||
}
|
||||
|
||||
func importOs() *lua.LTable {
|
||||
func ImportOs() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Args", luar.New(L, os.Args))
|
||||
|
@ -374,9 +363,9 @@ func importOs() *lua.LTable {
|
|||
L.SetField(pkg, "Remove", luar.New(L, os.Remove))
|
||||
L.SetField(pkg, "RemoveAll", luar.New(L, os.RemoveAll))
|
||||
L.SetField(pkg, "Rename", luar.New(L, os.Rename))
|
||||
L.SetField(pkg, "SEEK_CUR", luar.New(L, io.SeekCurrent))
|
||||
L.SetField(pkg, "SEEK_END", luar.New(L, io.SeekEnd))
|
||||
L.SetField(pkg, "SEEK_SET", luar.New(L, io.SeekStart))
|
||||
L.SetField(pkg, "SEEK_CUR", luar.New(L, os.SEEK_CUR))
|
||||
L.SetField(pkg, "SEEK_END", luar.New(L, os.SEEK_END))
|
||||
L.SetField(pkg, "SEEK_SET", luar.New(L, os.SEEK_SET))
|
||||
L.SetField(pkg, "SameFile", luar.New(L, os.SameFile))
|
||||
L.SetField(pkg, "Setenv", luar.New(L, os.Setenv))
|
||||
L.SetField(pkg, "StartProcess", luar.New(L, os.StartProcess))
|
||||
|
@ -387,12 +376,11 @@ func importOs() *lua.LTable {
|
|||
L.SetField(pkg, "Symlink", luar.New(L, os.Symlink))
|
||||
L.SetField(pkg, "TempDir", luar.New(L, os.TempDir))
|
||||
L.SetField(pkg, "Truncate", luar.New(L, os.Truncate))
|
||||
L.SetField(pkg, "UserHomeDir", luar.New(L, os.UserHomeDir))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importRuntime() *lua.LTable {
|
||||
func ImportRuntime() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "GC", luar.New(L, runtime.GC))
|
||||
|
@ -404,7 +392,7 @@ func importRuntime() *lua.LTable {
|
|||
return pkg
|
||||
}
|
||||
|
||||
func importPath() *lua.LTable {
|
||||
func ImportPath() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Base", luar.New(L, path.Base))
|
||||
|
@ -420,10 +408,11 @@ func importPath() *lua.LTable {
|
|||
return pkg
|
||||
}
|
||||
|
||||
func importFilePath() *lua.LTable {
|
||||
func ImportFilePath() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Join", luar.New(L, filepath.Join))
|
||||
L.SetField(pkg, "Clean", luar.New(L, filepath.Join))
|
||||
L.SetField(pkg, "Abs", luar.New(L, filepath.Abs))
|
||||
L.SetField(pkg, "Base", luar.New(L, filepath.Base))
|
||||
L.SetField(pkg, "Clean", luar.New(L, filepath.Clean))
|
||||
|
@ -445,7 +434,7 @@ func importFilePath() *lua.LTable {
|
|||
return pkg
|
||||
}
|
||||
|
||||
func importStrings() *lua.LTable {
|
||||
func ImportStrings() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Contains", luar.New(L, strings.Contains))
|
||||
|
@ -495,7 +484,7 @@ func importStrings() *lua.LTable {
|
|||
return pkg
|
||||
}
|
||||
|
||||
func importRegexp() *lua.LTable {
|
||||
func ImportRegexp() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Match", luar.New(L, regexp.Match))
|
||||
|
@ -510,7 +499,7 @@ func importRegexp() *lua.LTable {
|
|||
return pkg
|
||||
}
|
||||
|
||||
func importErrors() *lua.LTable {
|
||||
func ImportErrors() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "New", luar.New(L, errors.New))
|
||||
|
@ -518,19 +507,24 @@ func importErrors() *lua.LTable {
|
|||
return pkg
|
||||
}
|
||||
|
||||
func importTime() *lua.LTable {
|
||||
func ImportTime() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "After", luar.New(L, time.After))
|
||||
L.SetField(pkg, "Sleep", luar.New(L, time.Sleep))
|
||||
L.SetField(pkg, "Tick", luar.New(L, time.Tick))
|
||||
L.SetField(pkg, "Since", luar.New(L, time.Since))
|
||||
L.SetField(pkg, "FixedZone", luar.New(L, time.FixedZone))
|
||||
L.SetField(pkg, "LoadLocation", luar.New(L, time.LoadLocation))
|
||||
L.SetField(pkg, "NewTicker", luar.New(L, time.NewTicker))
|
||||
L.SetField(pkg, "Date", luar.New(L, time.Date))
|
||||
L.SetField(pkg, "Now", luar.New(L, time.Now))
|
||||
L.SetField(pkg, "Parse", luar.New(L, time.Parse))
|
||||
L.SetField(pkg, "ParseDuration", luar.New(L, time.ParseDuration))
|
||||
L.SetField(pkg, "ParseInLocation", luar.New(L, time.ParseInLocation))
|
||||
L.SetField(pkg, "Unix", luar.New(L, time.Unix))
|
||||
L.SetField(pkg, "AfterFunc", luar.New(L, time.AfterFunc))
|
||||
L.SetField(pkg, "NewTimer", luar.New(L, time.NewTimer))
|
||||
L.SetField(pkg, "Nanosecond", luar.New(L, time.Nanosecond))
|
||||
L.SetField(pkg, "Microsecond", luar.New(L, time.Microsecond))
|
||||
L.SetField(pkg, "Millisecond", luar.New(L, time.Millisecond))
|
||||
|
@ -538,63 +532,5 @@ func importTime() *lua.LTable {
|
|||
L.SetField(pkg, "Minute", luar.New(L, time.Minute))
|
||||
L.SetField(pkg, "Hour", luar.New(L, time.Hour))
|
||||
|
||||
// TODO: these raw Go timer APIs don't provide any synchronization
|
||||
// with micro. Stop exposing them to lua once plugins switch to using
|
||||
// the safer micro.After() interface instead. See issue #3209
|
||||
L.SetField(pkg, "After", luar.New(L, time.After))
|
||||
L.SetField(pkg, "AfterFunc", luar.New(L, time.AfterFunc))
|
||||
L.SetField(pkg, "NewTicker", luar.New(L, time.NewTicker))
|
||||
L.SetField(pkg, "NewTimer", luar.New(L, time.NewTimer))
|
||||
L.SetField(pkg, "Tick", luar.New(L, time.Tick))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importUtf8() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "DecodeLastRune", luar.New(L, utf8.DecodeLastRune))
|
||||
L.SetField(pkg, "DecodeLastRuneInString", luar.New(L, utf8.DecodeLastRuneInString))
|
||||
L.SetField(pkg, "DecodeRune", luar.New(L, utf8.DecodeRune))
|
||||
L.SetField(pkg, "DecodeRuneInString", luar.New(L, utf8.DecodeRuneInString))
|
||||
L.SetField(pkg, "EncodeRune", luar.New(L, utf8.EncodeRune))
|
||||
L.SetField(pkg, "FullRune", luar.New(L, utf8.FullRune))
|
||||
L.SetField(pkg, "FullRuneInString", luar.New(L, utf8.FullRuneInString))
|
||||
L.SetField(pkg, "RuneCount", luar.New(L, utf8.RuneCount))
|
||||
L.SetField(pkg, "RuneCountInString", luar.New(L, utf8.RuneCountInString))
|
||||
L.SetField(pkg, "RuneLen", luar.New(L, utf8.RuneLen))
|
||||
L.SetField(pkg, "RuneStart", luar.New(L, utf8.RuneStart))
|
||||
L.SetField(pkg, "Valid", luar.New(L, utf8.Valid))
|
||||
L.SetField(pkg, "ValidRune", luar.New(L, utf8.ValidRune))
|
||||
L.SetField(pkg, "ValidString", luar.New(L, utf8.ValidString))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importHumanize() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Bytes", luar.New(L, humanize.Bytes))
|
||||
L.SetField(pkg, "Ordinal", luar.New(L, humanize.Ordinal))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importHTTP() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Get", luar.New(L, http.Get))
|
||||
L.SetField(pkg, "Post", luar.New(L, http.Post))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importArchiveZip() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "OpenReader", luar.New(L, zip.OpenReader))
|
||||
L.SetField(pkg, "NewReader", luar.New(L, zip.NewReader))
|
||||
L.SetField(pkg, "NewWriter", luar.New(L, zip.NewWriter))
|
||||
|
||||
return pkg
|
||||
}
|
498
cmd/micro/messenger.go
Normal file
498
cmd/micro/messenger.go
Normal file
|
@ -0,0 +1,498 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/zyedidia/clipboard"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
// TermMessage sends a message to the user in the terminal. This usually occurs before
|
||||
// micro has been fully initialized -- ie if there is an error in the syntax highlighting
|
||||
// regular expressions
|
||||
// The function must be called when the screen is not initialized
|
||||
// This will write the message, and wait for the user
|
||||
// to press and key to continue
|
||||
func TermMessage(msg ...interface{}) {
|
||||
screenWasNil := screen == nil
|
||||
if !screenWasNil {
|
||||
screen.Fini()
|
||||
screen = nil
|
||||
}
|
||||
|
||||
fmt.Println(msg...)
|
||||
fmt.Print("\nPress enter to continue")
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
reader.ReadString('\n')
|
||||
|
||||
if !screenWasNil {
|
||||
InitScreen()
|
||||
}
|
||||
}
|
||||
|
||||
// TermError sends an error to the user in the terminal. Like TermMessage except formatted
|
||||
// as an error
|
||||
func TermError(filename string, lineNum int, err string) {
|
||||
TermMessage(filename + ", " + strconv.Itoa(lineNum) + ": " + err)
|
||||
}
|
||||
|
||||
// Messenger is an object that makes it easy to send messages to the user
|
||||
// and get input from the user
|
||||
type Messenger struct {
|
||||
log *Buffer
|
||||
// Are we currently prompting the user?
|
||||
hasPrompt bool
|
||||
// Is there a message to print
|
||||
hasMessage bool
|
||||
|
||||
// Message to print
|
||||
message string
|
||||
// The user's response to a prompt
|
||||
response string
|
||||
// style to use when drawing the message
|
||||
style tcell.Style
|
||||
|
||||
// We have to keep track of the cursor for prompting
|
||||
cursorx int
|
||||
|
||||
// This map stores the history for all the different kinds of uses Prompt has
|
||||
// It's a map of history type -> history array
|
||||
history map[string][]string
|
||||
historyNum int
|
||||
|
||||
// Is the current message a message from the gutter
|
||||
gutterMessage bool
|
||||
}
|
||||
|
||||
// AddLog sends a message to the log view
|
||||
func (m *Messenger) AddLog(msg ...interface{}) {
|
||||
logMessage := fmt.Sprint(msg...)
|
||||
buffer := m.getBuffer()
|
||||
buffer.insert(buffer.End(), []byte(logMessage+"\n"))
|
||||
buffer.Cursor.Loc = buffer.End()
|
||||
buffer.Cursor.Relocate()
|
||||
}
|
||||
|
||||
func (m *Messenger) getBuffer() *Buffer {
|
||||
if m.log == nil {
|
||||
m.log = NewBufferFromString("", "")
|
||||
m.log.name = "Log"
|
||||
}
|
||||
return m.log
|
||||
}
|
||||
|
||||
// Message sends a message to the user
|
||||
func (m *Messenger) Message(msg ...interface{}) {
|
||||
displayMessage := fmt.Sprint(msg...)
|
||||
// only display a new message if there isn't an active prompt
|
||||
// this is to prevent overwriting an existing prompt to the user
|
||||
if m.hasPrompt == false {
|
||||
// if there is no active prompt then style and display the message as normal
|
||||
m.message = displayMessage
|
||||
|
||||
m.style = defStyle
|
||||
|
||||
if _, ok := colorscheme["message"]; ok {
|
||||
m.style = colorscheme["message"]
|
||||
}
|
||||
|
||||
m.hasMessage = true
|
||||
}
|
||||
// add the message to the log regardless of active prompts
|
||||
m.AddLog(displayMessage)
|
||||
}
|
||||
|
||||
// Error sends an error message to the user
|
||||
func (m *Messenger) Error(msg ...interface{}) {
|
||||
buf := new(bytes.Buffer)
|
||||
fmt.Fprint(buf, msg...)
|
||||
|
||||
// only display a new message if there isn't an active prompt
|
||||
// this is to prevent overwriting an existing prompt to the user
|
||||
if m.hasPrompt == false {
|
||||
// if there is no active prompt then style and display the message as normal
|
||||
m.message = buf.String()
|
||||
m.style = defStyle.
|
||||
Foreground(tcell.ColorBlack).
|
||||
Background(tcell.ColorMaroon)
|
||||
|
||||
if _, ok := colorscheme["error-message"]; ok {
|
||||
m.style = colorscheme["error-message"]
|
||||
}
|
||||
m.hasMessage = true
|
||||
}
|
||||
// add the message to the log regardless of active prompts
|
||||
m.AddLog(buf.String())
|
||||
}
|
||||
|
||||
func (m *Messenger) PromptText(msg ...interface{}) {
|
||||
displayMessage := fmt.Sprint(msg...)
|
||||
// if there is no active prompt then style and display the message as normal
|
||||
m.message = displayMessage
|
||||
|
||||
m.style = defStyle
|
||||
|
||||
if _, ok := colorscheme["message"]; ok {
|
||||
m.style = colorscheme["message"]
|
||||
}
|
||||
|
||||
m.hasMessage = true
|
||||
// add the message to the log regardless of active prompts
|
||||
m.AddLog(displayMessage)
|
||||
}
|
||||
|
||||
// YesNoPrompt asks the user a yes or no question (waits for y or n) and returns the result
|
||||
func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) {
|
||||
m.hasPrompt = true
|
||||
m.PromptText(prompt)
|
||||
|
||||
_, h := screen.Size()
|
||||
for {
|
||||
m.Clear()
|
||||
m.Display()
|
||||
screen.ShowCursor(Count(m.message), h-1)
|
||||
screen.Show()
|
||||
event := <-events
|
||||
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
switch e.Key() {
|
||||
case tcell.KeyRune:
|
||||
if e.Rune() == 'y' || e.Rune() == 'Y' {
|
||||
m.AddLog("\t--> y")
|
||||
m.hasPrompt = false
|
||||
return true, false
|
||||
} else if e.Rune() == 'n' || e.Rune() == 'N' {
|
||||
m.AddLog("\t--> n")
|
||||
m.hasPrompt = false
|
||||
return false, false
|
||||
}
|
||||
case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
|
||||
m.AddLog("\t--> (cancel)")
|
||||
m.Clear()
|
||||
m.Reset()
|
||||
m.hasPrompt = false
|
||||
return false, true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LetterPrompt gives the user a prompt and waits for a one letter response
|
||||
func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool) {
|
||||
m.hasPrompt = true
|
||||
m.PromptText(prompt)
|
||||
|
||||
_, h := screen.Size()
|
||||
for {
|
||||
m.Clear()
|
||||
m.Display()
|
||||
screen.ShowCursor(Count(m.message), h-1)
|
||||
screen.Show()
|
||||
event := <-events
|
||||
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
switch e.Key() {
|
||||
case tcell.KeyRune:
|
||||
for _, r := range responses {
|
||||
if e.Rune() == r {
|
||||
m.AddLog("\t--> " + string(r))
|
||||
m.Clear()
|
||||
m.Reset()
|
||||
m.hasPrompt = false
|
||||
return r, false
|
||||
}
|
||||
}
|
||||
case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
|
||||
m.AddLog("\t--> (cancel)")
|
||||
m.Clear()
|
||||
m.Reset()
|
||||
m.hasPrompt = false
|
||||
return ' ', true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Completion int
|
||||
|
||||
const (
|
||||
NoCompletion Completion = iota
|
||||
FileCompletion
|
||||
CommandCompletion
|
||||
HelpCompletion
|
||||
OptionCompletion
|
||||
PluginCmdCompletion
|
||||
PluginNameCompletion
|
||||
)
|
||||
|
||||
// Prompt sends the user a message and waits for a response to be typed in
|
||||
// This function blocks the main loop while waiting for input
|
||||
func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTypes ...Completion) (string, bool) {
|
||||
m.hasPrompt = true
|
||||
m.PromptText(prompt)
|
||||
if _, ok := m.history[historyType]; !ok {
|
||||
m.history[historyType] = []string{""}
|
||||
} else {
|
||||
m.history[historyType] = append(m.history[historyType], "")
|
||||
}
|
||||
m.historyNum = len(m.history[historyType]) - 1
|
||||
|
||||
response, canceled := placeholder, true
|
||||
m.response = response
|
||||
m.cursorx = Count(placeholder)
|
||||
|
||||
RedrawAll()
|
||||
for m.hasPrompt {
|
||||
var suggestions []string
|
||||
m.Clear()
|
||||
|
||||
event := <-events
|
||||
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
switch e.Key() {
|
||||
case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape:
|
||||
// Cancel
|
||||
m.AddLog("\t--> (cancel)")
|
||||
m.hasPrompt = false
|
||||
case tcell.KeyEnter:
|
||||
// User is done entering their response
|
||||
m.AddLog("\t--> " + m.response)
|
||||
m.hasPrompt = false
|
||||
response, canceled = m.response, false
|
||||
m.history[historyType][len(m.history[historyType])-1] = response
|
||||
case tcell.KeyTab:
|
||||
args := SplitCommandArgs(m.response)
|
||||
currentArgNum := len(args) - 1
|
||||
currentArg := args[currentArgNum]
|
||||
var completionType Completion
|
||||
|
||||
if completionTypes[0] == CommandCompletion && currentArgNum > 0 {
|
||||
if command, ok := commands[args[0]]; ok {
|
||||
completionTypes = append([]Completion{CommandCompletion}, command.completions...)
|
||||
}
|
||||
}
|
||||
|
||||
if currentArgNum >= len(completionTypes) {
|
||||
completionType = completionTypes[len(completionTypes)-1]
|
||||
} else {
|
||||
completionType = completionTypes[currentArgNum]
|
||||
}
|
||||
|
||||
var chosen string
|
||||
if completionType == FileCompletion {
|
||||
chosen, suggestions = FileComplete(currentArg)
|
||||
} else if completionType == CommandCompletion {
|
||||
chosen, suggestions = CommandComplete(currentArg)
|
||||
} else if completionType == HelpCompletion {
|
||||
chosen, suggestions = HelpComplete(currentArg)
|
||||
} else if completionType == OptionCompletion {
|
||||
chosen, suggestions = OptionComplete(currentArg)
|
||||
} else if completionType == PluginCmdCompletion {
|
||||
chosen, suggestions = PluginCmdComplete(currentArg)
|
||||
} else if completionType == PluginNameCompletion {
|
||||
chosen, suggestions = PluginNameComplete(currentArg)
|
||||
} else if completionType < NoCompletion {
|
||||
chosen, suggestions = PluginComplete(completionType, currentArg)
|
||||
}
|
||||
|
||||
if len(suggestions) > 1 {
|
||||
chosen = chosen + CommonSubstring(suggestions...)
|
||||
}
|
||||
|
||||
if chosen != "" {
|
||||
m.response = JoinCommandArgs(append(args[:len(args)-1], chosen)...)
|
||||
m.cursorx = Count(m.response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m.HandleEvent(event, m.history[historyType])
|
||||
|
||||
m.Clear()
|
||||
for _, v := range tabs[curTab].views {
|
||||
v.Display()
|
||||
}
|
||||
DisplayTabs()
|
||||
m.Display()
|
||||
if len(suggestions) > 1 {
|
||||
m.DisplaySuggestions(suggestions)
|
||||
}
|
||||
screen.Show()
|
||||
}
|
||||
|
||||
m.Clear()
|
||||
m.Reset()
|
||||
return response, canceled
|
||||
}
|
||||
|
||||
// HandleEvent handles an event for the prompter
|
||||
func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
if e.Key() != tcell.KeyRune || e.Modifiers() != 0 {
|
||||
for key, actions := range bindings {
|
||||
if e.Key() == key.keyCode {
|
||||
if e.Key() == tcell.KeyRune {
|
||||
if e.Rune() != key.r {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if e.Modifiers() == key.modifiers {
|
||||
for _, action := range actions {
|
||||
funcName := FuncName(action)
|
||||
switch funcName {
|
||||
case "main.(*View).CursorUp":
|
||||
if m.historyNum > 0 {
|
||||
m.historyNum--
|
||||
m.response = history[m.historyNum]
|
||||
m.cursorx = Count(m.response)
|
||||
}
|
||||
case "main.(*View).CursorDown":
|
||||
if m.historyNum < len(history)-1 {
|
||||
m.historyNum++
|
||||
m.response = history[m.historyNum]
|
||||
m.cursorx = Count(m.response)
|
||||
}
|
||||
case "main.(*View).CursorLeft":
|
||||
if m.cursorx > 0 {
|
||||
m.cursorx--
|
||||
}
|
||||
case "main.(*View).CursorRight":
|
||||
if m.cursorx < Count(m.response) {
|
||||
m.cursorx++
|
||||
}
|
||||
case "main.(*View).CursorStart", "main.(*View).StartOfLine":
|
||||
m.cursorx = 0
|
||||
case "main.(*View).CursorEnd", "main.(*View).EndOfLine":
|
||||
m.cursorx = Count(m.response)
|
||||
case "main.(*View).Backspace":
|
||||
if m.cursorx > 0 {
|
||||
m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
|
||||
m.cursorx--
|
||||
}
|
||||
case "main.(*View).Paste":
|
||||
clip, _ := clipboard.ReadAll("clipboard")
|
||||
m.response = Insert(m.response, m.cursorx, clip)
|
||||
m.cursorx += Count(clip)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
switch e.Key() {
|
||||
case tcell.KeyRune:
|
||||
m.response = Insert(m.response, m.cursorx, string(e.Rune()))
|
||||
m.cursorx++
|
||||
}
|
||||
history[m.historyNum] = m.response
|
||||
|
||||
case *tcell.EventPaste:
|
||||
clip := e.Text()
|
||||
m.response = Insert(m.response, m.cursorx, clip)
|
||||
m.cursorx += Count(clip)
|
||||
case *tcell.EventMouse:
|
||||
x, y := e.Position()
|
||||
x -= Count(m.message)
|
||||
button := e.Buttons()
|
||||
_, screenH := screen.Size()
|
||||
|
||||
if y == screenH-1 {
|
||||
switch button {
|
||||
case tcell.Button1:
|
||||
m.cursorx = x
|
||||
if m.cursorx < 0 {
|
||||
m.cursorx = 0
|
||||
} else if m.cursorx > Count(m.response) {
|
||||
m.cursorx = Count(m.response)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset resets the messenger's cursor, message and response
|
||||
func (m *Messenger) Reset() {
|
||||
m.cursorx = 0
|
||||
m.message = ""
|
||||
m.response = ""
|
||||
}
|
||||
|
||||
// Clear clears the line at the bottom of the editor
|
||||
func (m *Messenger) Clear() {
|
||||
w, h := screen.Size()
|
||||
for x := 0; x < w; x++ {
|
||||
screen.SetContent(x, h-1, ' ', nil, defStyle)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Messenger) DisplaySuggestions(suggestions []string) {
|
||||
w, screenH := screen.Size()
|
||||
|
||||
y := screenH - 2
|
||||
|
||||
statusLineStyle := defStyle.Reverse(true)
|
||||
if style, ok := colorscheme["statusline"]; ok {
|
||||
statusLineStyle = style
|
||||
}
|
||||
|
||||
for x := 0; x < w; x++ {
|
||||
screen.SetContent(x, y, ' ', nil, statusLineStyle)
|
||||
}
|
||||
|
||||
x := 0
|
||||
for _, suggestion := range suggestions {
|
||||
for _, c := range suggestion {
|
||||
screen.SetContent(x, y, c, nil, statusLineStyle)
|
||||
x++
|
||||
}
|
||||
screen.SetContent(x, y, ' ', nil, statusLineStyle)
|
||||
x++
|
||||
}
|
||||
}
|
||||
|
||||
// Display displays messages or prompts
|
||||
func (m *Messenger) Display() {
|
||||
_, h := screen.Size()
|
||||
if m.hasMessage {
|
||||
if m.hasPrompt || globalSettings["infobar"].(bool) {
|
||||
runes := []rune(m.message + m.response)
|
||||
posx := 0
|
||||
for x := 0; x < len(runes); x++ {
|
||||
screen.SetContent(posx, h-1, runes[x], nil, m.style)
|
||||
posx += runewidth.RuneWidth(runes[x])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if m.hasPrompt {
|
||||
screen.ShowCursor(Count(m.message)+m.cursorx, h-1)
|
||||
screen.Show()
|
||||
}
|
||||
}
|
||||
|
||||
// A GutterMessage is a message displayed on the side of the editor
|
||||
type GutterMessage struct {
|
||||
lineNum int
|
||||
msg string
|
||||
kind int
|
||||
}
|
||||
|
||||
// These are the different types of messages
|
||||
const (
|
||||
// GutterInfo represents a simple info message
|
||||
GutterInfo = iota
|
||||
// GutterWarning represents a compiler warning
|
||||
GutterWarning
|
||||
// GutterError represents a compiler error
|
||||
GutterError
|
||||
)
|
|
@ -3,147 +3,67 @@ package main
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"sort"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
isatty "github.com/mattn/go-isatty"
|
||||
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/clipboard"
|
||||
"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/v2"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/clipboard"
|
||||
"github.com/zyedidia/tcell"
|
||||
"github.com/zyedidia/tcell/encoding"
|
||||
"layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
const (
|
||||
doubleClickThreshold = 400 // How many milliseconds to wait before a second click is not a double click
|
||||
undoThreshold = 500 // If two events are less than n milliseconds apart, undo both of them
|
||||
autosaveTime = 8 // Number of seconds to wait before autosaving
|
||||
)
|
||||
|
||||
var (
|
||||
// Command line flags
|
||||
flagVersion = flag.Bool("version", false, "Show the version number and information")
|
||||
flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
|
||||
flagOptions = flag.Bool("options", false, "Show all option help")
|
||||
flagDebug = flag.Bool("debug", false, "Enable debug mode (prints debug info to ./log.txt)")
|
||||
flagProfile = flag.Bool("profile", false, "Enable CPU profiling (writes profile info to ./micro.prof)")
|
||||
flagPlugin = flag.String("plugin", "", "Plugin command")
|
||||
flagClean = flag.Bool("clean", false, "Clean configuration directory")
|
||||
optionFlags map[string]*string
|
||||
// The main screen
|
||||
screen tcell.Screen
|
||||
|
||||
sigterm chan os.Signal
|
||||
sighup chan os.Signal
|
||||
// Object to send messages and prompts to the user
|
||||
messenger *Messenger
|
||||
|
||||
timerChan chan func()
|
||||
// The default highlighting style
|
||||
// This simply defines the default foreground and background colors
|
||||
defStyle tcell.Style
|
||||
|
||||
// Where the user's configuration is
|
||||
// This should be $XDG_CONFIG_HOME/micro
|
||||
// If $XDG_CONFIG_HOME is not set, it is ~/.config/micro
|
||||
configDir string
|
||||
|
||||
// Version is the version number or commit hash
|
||||
// These variables should be set by the linker when compiling
|
||||
Version = "0.0.0-unknown"
|
||||
CommitHash = "Unknown"
|
||||
CompileDate = "Unknown"
|
||||
|
||||
// The list of views
|
||||
tabs []*Tab
|
||||
// This is the currently open tab
|
||||
// It's just an index to the tab in the tabs array
|
||||
curTab int
|
||||
|
||||
// Channel of jobs running in the background
|
||||
jobs chan JobFunction
|
||||
// Event channel
|
||||
events chan tcell.Event
|
||||
autosave chan bool
|
||||
)
|
||||
|
||||
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 (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("-options")
|
||||
fmt.Println(" \tShow all option help")
|
||||
fmt.Println("-debug")
|
||||
fmt.Println(" \tEnable debug mode (enables logging to ./log.txt)")
|
||||
fmt.Println("-profile")
|
||||
fmt.Println(" \tEnable CPU profiling (writes profile info to ./micro.prof")
|
||||
fmt.Println(" \tso it can be analyzed later with \"go tool pprof micro.prof\")")
|
||||
fmt.Println("-version")
|
||||
fmt.Println(" \tShow the version number and information")
|
||||
|
||||
fmt.Print("\nMicro's plugins 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")
|
||||
fmt.Println(" \tFor example: `micro -syntax off file.c`")
|
||||
fmt.Println("\nUse `micro -options` to see the full list of configuration options")
|
||||
}
|
||||
|
||||
optionFlags = make(map[string]*string)
|
||||
|
||||
for k, v := range config.DefaultAllSettings() {
|
||||
optionFlags[k] = flag.String(k, "", fmt.Sprintf("The %s option. Default value: '%v'.", k, v))
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *flagVersion {
|
||||
// If -version was passed
|
||||
fmt.Println("Version:", util.Version)
|
||||
fmt.Println("Commit hash:", util.CommitHash)
|
||||
fmt.Println("Compiled on", util.CompileDate)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *flagOptions {
|
||||
// If -options was passed
|
||||
var keys []string
|
||||
m := config.DefaultAllSettings()
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
v := m[k]
|
||||
fmt.Printf("-%s value\n", k)
|
||||
fmt.Printf(" \tDefault value: '%v'\n", v)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if util.Debug == "OFF" && *flagDebug {
|
||||
util.Debug = "ON"
|
||||
}
|
||||
}
|
||||
|
||||
// DoPluginFlags parses and executes any flags that require LoadAllPlugins (-plugin and -clean)
|
||||
func DoPluginFlags() {
|
||||
if *flagClean || *flagPlugin != "" {
|
||||
config.LoadAllPlugins()
|
||||
|
||||
if *flagPlugin != "" {
|
||||
args := flag.Args()
|
||||
|
||||
config.PluginCommand(os.Stdout, *flagPlugin, args)
|
||||
} else if *flagClean {
|
||||
CleanConfig()
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadInput determines which files should be loaded into buffers
|
||||
// based on the input stored in flag.Args()
|
||||
func LoadInput(args []string) []*buffer.Buffer {
|
||||
func LoadInput() []*Buffer {
|
||||
// There are a number of ways micro should start given its input
|
||||
|
||||
// 1. If it is given a files in flag.Args(), it should open those
|
||||
|
@ -158,53 +78,37 @@ func LoadInput(args []string) []*buffer.Buffer {
|
|||
var filename string
|
||||
var input []byte
|
||||
var err error
|
||||
buffers := make([]*buffer.Buffer, 0, len(args))
|
||||
args := flag.Args()
|
||||
buffers := make([]*Buffer, 0, len(args))
|
||||
|
||||
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 {
|
||||
if len(args) > 0 {
|
||||
// Option 1
|
||||
// We go through each file and load it
|
||||
for i := 0; i < len(files); i++ {
|
||||
buf, err := buffer.NewBufferFromFileAtLoc(files[i], btype, flagStartPos)
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
continue
|
||||
for i := 0; i < len(args); i++ {
|
||||
filename = args[i]
|
||||
|
||||
// Check that the file exists
|
||||
var input *os.File
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
// If it exists we load it into a buffer
|
||||
input, err = os.Open(filename)
|
||||
stat, _ := input.Stat()
|
||||
defer input.Close()
|
||||
if err != nil {
|
||||
TermMessage(err)
|
||||
continue
|
||||
}
|
||||
if stat.IsDir() {
|
||||
TermMessage("Cannot read", filename, "because it is a directory")
|
||||
continue
|
||||
}
|
||||
}
|
||||
// If the file didn't exist, input will be empty, and we'll open an empty buffer
|
||||
buffers = append(buffers, buf)
|
||||
if input != nil {
|
||||
buffers = append(buffers, NewBuffer(input, FSize(input), filename))
|
||||
} else {
|
||||
buffers = append(buffers, NewBufferFromString("", filename))
|
||||
}
|
||||
}
|
||||
} else if !isatty.IsTerminal(os.Stdin.Fd()) {
|
||||
// Option 2
|
||||
|
@ -212,277 +116,396 @@ func LoadInput(args []string) []*buffer.Buffer {
|
|||
// and we should read from stdin
|
||||
input, err = ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error reading from stdin: ", err)
|
||||
TermMessage("Error reading from stdin: ", err)
|
||||
input = []byte{}
|
||||
}
|
||||
buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
|
||||
buffers = append(buffers, NewBufferFromString(string(input), filename))
|
||||
} else {
|
||||
// Option 3, just open an empty buffer
|
||||
buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
|
||||
buffers = append(buffers, NewBufferFromString(string(input), filename))
|
||||
}
|
||||
|
||||
return buffers
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
if util.Stdout.Len() > 0 {
|
||||
fmt.Fprint(os.Stdout, util.Stdout.String())
|
||||
}
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
var err error
|
||||
|
||||
InitFlags()
|
||||
|
||||
if *flagProfile {
|
||||
f, err := os.Create("micro.prof")
|
||||
// InitConfigDir finds the configuration directory for micro according to the XDG spec.
|
||||
// If no directory is found, it creates one.
|
||||
func InitConfigDir() {
|
||||
xdgHome := os.Getenv("XDG_CONFIG_HOME")
|
||||
if xdgHome == "" {
|
||||
// The user has not set $XDG_CONFIG_HOME so we should act like it was set to ~/.config
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
log.Fatal("error creating CPU profile: ", err)
|
||||
TermMessage("Error finding your home directory\nCan't load config files")
|
||||
return
|
||||
}
|
||||
if err := pprof.StartCPUProfile(f); err != nil {
|
||||
log.Fatal("error starting CPU profile: ", err)
|
||||
}
|
||||
defer pprof.StopCPUProfile()
|
||||
xdgHome = home + "/.config"
|
||||
}
|
||||
configDir = xdgHome + "/micro"
|
||||
|
||||
InitLog()
|
||||
|
||||
err = config.InitConfigDir(*flagConfigDir)
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
config.InitRuntimeFiles(true)
|
||||
config.InitPlugins()
|
||||
|
||||
err = config.ReadSettings()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
err = config.InitGlobalSettings()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
// flag options
|
||||
for k, v := range optionFlags {
|
||||
if *v != "" {
|
||||
nativeValue, err := config.GetNativeValue(k, config.DefaultAllSettings()[k], *v)
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
continue
|
||||
}
|
||||
config.GlobalSettings[k] = nativeValue
|
||||
config.VolatileSettings[k] = true
|
||||
if len(*flagConfigDir) > 0 {
|
||||
if _, err := os.Stat(*flagConfigDir); os.IsNotExist(err) {
|
||||
TermMessage("Error: " + *flagConfigDir + " does not exist. Defaulting to " + configDir + ".")
|
||||
} else {
|
||||
configDir = *flagConfigDir
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
DoPluginFlags()
|
||||
if _, err := os.Stat(xdgHome); os.IsNotExist(err) {
|
||||
// If the xdgHome doesn't exist we should create it
|
||||
err = os.Mkdir(xdgHome, os.ModePerm)
|
||||
if err != nil {
|
||||
TermMessage("Error creating XDG_CONFIG_HOME directory: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
err = screen.Init()
|
||||
if _, err := os.Stat(configDir); os.IsNotExist(err) {
|
||||
// If the micro specific config directory doesn't exist we should create that too
|
||||
err = os.Mkdir(configDir, os.ModePerm)
|
||||
if err != nil {
|
||||
TermMessage("Error creating configuration directory: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InitScreen creates and initializes the tcell screen
|
||||
func InitScreen() {
|
||||
// Should we enable true color?
|
||||
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
|
||||
|
||||
// In order to enable true color, we have to set the TERM to `xterm-truecolor` when
|
||||
// initializing tcell, but after that, we can set the TERM back to whatever it was
|
||||
oldTerm := os.Getenv("TERM")
|
||||
if truecolor {
|
||||
os.Setenv("TERM", "xterm-truecolor")
|
||||
}
|
||||
|
||||
// Initilize tcell
|
||||
var err error
|
||||
screen, err = tcell.NewScreen()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Fatal: Micro could not initialize a Screen.")
|
||||
if err == tcell.ErrTermNotFound {
|
||||
fmt.Println("Micro does not recognize your terminal:", oldTerm)
|
||||
fmt.Println("Please go to https://github.com/zyedidia/mkinfo to read about how to fix this problem (it should be easy to fix).")
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
if err = screen.Init(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
m := clipboard.SetMethod(config.GetGlobalOption("clipboard").(string))
|
||||
clipErr := clipboard.Initialize(m)
|
||||
|
||||
// Now we can put the TERM back to what it was before
|
||||
if truecolor {
|
||||
os.Setenv("TERM", oldTerm)
|
||||
}
|
||||
|
||||
screen.SetStyle(defStyle)
|
||||
}
|
||||
|
||||
// RedrawAll redraws everything -- all the views and the messenger
|
||||
func RedrawAll() {
|
||||
messenger.Clear()
|
||||
|
||||
w, h := screen.Size()
|
||||
for x := 0; x < w; x++ {
|
||||
for y := 0; y < h; y++ {
|
||||
screen.SetContent(x, y, ' ', nil, defStyle)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range tabs[curTab].views {
|
||||
v.Display()
|
||||
}
|
||||
DisplayTabs()
|
||||
messenger.Display()
|
||||
screen.Show()
|
||||
}
|
||||
|
||||
func LoadAll() {
|
||||
// Find the user's configuration directory (probably $XDG_CONFIG_HOME/micro)
|
||||
InitConfigDir()
|
||||
|
||||
// Build a list of available Extensions (Syntax, Colorscheme etc.)
|
||||
InitRuntimeFiles()
|
||||
|
||||
// Load the user's settings
|
||||
InitGlobalSettings()
|
||||
|
||||
InitCommands()
|
||||
InitBindings()
|
||||
|
||||
InitColorscheme()
|
||||
|
||||
for _, tab := range tabs {
|
||||
for _, v := range tab.views {
|
||||
v.Buf.UpdateRules()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Passing -version as a flag will have micro print out the version number
|
||||
var flagVersion = flag.Bool("version", false, "Show the version number and information")
|
||||
var flagStartPos = flag.String("startpos", "", "LINE,COL to start the cursor at when opening a buffer.")
|
||||
var flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
|
||||
var optionFlagSet = flag.NewFlagSet("option", flag.ExitOnError)
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Println("Usage: micro [OPTIONS] [FILE]...")
|
||||
flag.CommandLine.SetOutput(os.Stdout)
|
||||
flag.PrintDefaults()
|
||||
optionFlagSet.SetOutput(os.Stdout)
|
||||
fmt.Print("\n------------------------------------------------------------------\n")
|
||||
fmt.Print("Micro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the bindings.json\nfile (see 'help options').\n\n")
|
||||
optionFlagSet.PrintDefaults()
|
||||
}
|
||||
|
||||
optionFlags := make(map[string]*string)
|
||||
|
||||
for k, v := range DefaultGlobalSettings() {
|
||||
optionFlags[k] = optionFlagSet.String(k, "", fmt.Sprintf("The %s option. Default value: '%v'", k, v))
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *flagVersion {
|
||||
// If -version was passed
|
||||
fmt.Println("Version:", Version)
|
||||
fmt.Println("Commit hash:", CommitHash)
|
||||
fmt.Println("Compiled on", CompileDate)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Start the Lua VM for running plugins
|
||||
L = lua.NewState()
|
||||
defer L.Close()
|
||||
|
||||
// Some encoding stuff in case the user isn't using UTF-8
|
||||
encoding.Register()
|
||||
tcell.SetEncodingFallback(tcell.EncodingFallbackASCII)
|
||||
|
||||
// Find the user's configuration directory (probably $XDG_CONFIG_HOME/micro)
|
||||
InitConfigDir()
|
||||
|
||||
// Build a list of available Extensions (Syntax, Colorscheme etc.)
|
||||
InitRuntimeFiles()
|
||||
|
||||
// Load the user's settings
|
||||
InitGlobalSettings()
|
||||
|
||||
InitCommands()
|
||||
InitBindings()
|
||||
|
||||
// Start the screen
|
||||
InitScreen()
|
||||
|
||||
// This is just so 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 {
|
||||
if screen.Screen != nil {
|
||||
screen.Screen.Fini()
|
||||
}
|
||||
if e, ok := err.(*lua.ApiError); ok {
|
||||
fmt.Println("Lua API error:", e)
|
||||
} else {
|
||||
fmt.Println("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")
|
||||
}
|
||||
// backup all open buffers
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
b.Backup()
|
||||
}
|
||||
screen.Fini()
|
||||
fmt.Println("Micro encountered an error:", err)
|
||||
// Print the stack trace too
|
||||
fmt.Print(errors.Wrap(err, 2).ErrorStack())
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
err = config.LoadAllPlugins()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
// Create a new messenger
|
||||
// This is used for sending the user messages in the bottom of the editor
|
||||
messenger = new(Messenger)
|
||||
messenger.history = make(map[string][]string)
|
||||
|
||||
// Now we load the input
|
||||
buffers := LoadInput()
|
||||
if len(buffers) == 0 {
|
||||
screen.Fini()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
action.InitBindings()
|
||||
action.InitCommands()
|
||||
for _, buf := range buffers {
|
||||
// For each buffer we create a new tab and place the view in that tab
|
||||
tab := NewTabFromView(NewView(buf))
|
||||
tab.SetNum(len(tabs))
|
||||
tabs = append(tabs, tab)
|
||||
for _, t := range tabs {
|
||||
for _, v := range t.views {
|
||||
v.Center(false)
|
||||
}
|
||||
|
||||
err = config.InitColorscheme()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
t.Resize()
|
||||
}
|
||||
}
|
||||
|
||||
err = config.RunPluginFn("preinit")
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
for k, v := range optionFlags {
|
||||
if *v != "" {
|
||||
SetOption(k, *v)
|
||||
}
|
||||
}
|
||||
|
||||
action.InitGlobals()
|
||||
buffer.SetMessager(action.InfoBar)
|
||||
args := flag.Args()
|
||||
b := LoadInput(args)
|
||||
// Load all the plugin stuff
|
||||
// We give plugins access to a bunch of variables here which could be useful to them
|
||||
L.SetGlobal("OS", luar.New(L, runtime.GOOS))
|
||||
L.SetGlobal("tabs", luar.New(L, tabs))
|
||||
L.SetGlobal("curTab", luar.New(L, curTab))
|
||||
L.SetGlobal("messenger", luar.New(L, messenger))
|
||||
L.SetGlobal("GetOption", luar.New(L, GetOption))
|
||||
L.SetGlobal("AddOption", luar.New(L, AddOption))
|
||||
L.SetGlobal("SetOption", luar.New(L, SetOption))
|
||||
L.SetGlobal("SetLocalOption", luar.New(L, SetLocalOption))
|
||||
L.SetGlobal("BindKey", luar.New(L, BindKey))
|
||||
L.SetGlobal("MakeCommand", luar.New(L, MakeCommand))
|
||||
L.SetGlobal("CurView", luar.New(L, CurView))
|
||||
L.SetGlobal("IsWordChar", luar.New(L, IsWordChar))
|
||||
L.SetGlobal("HandleCommand", luar.New(L, HandleCommand))
|
||||
L.SetGlobal("HandleShellCommand", luar.New(L, HandleShellCommand))
|
||||
L.SetGlobal("GetLeadingWhitespace", luar.New(L, GetLeadingWhitespace))
|
||||
L.SetGlobal("MakeCompletion", luar.New(L, MakeCompletion))
|
||||
L.SetGlobal("NewBuffer", luar.New(L, NewBufferFromString))
|
||||
L.SetGlobal("RuneStr", luar.New(L, func(r rune) string {
|
||||
return string(r)
|
||||
}))
|
||||
L.SetGlobal("Loc", luar.New(L, func(x, y int) Loc {
|
||||
return Loc{x, y}
|
||||
}))
|
||||
L.SetGlobal("WorkingDirectory", luar.New(L, os.Getwd))
|
||||
L.SetGlobal("JoinPaths", luar.New(L, filepath.Join))
|
||||
L.SetGlobal("DirectoryName", luar.New(L, filepath.Dir))
|
||||
L.SetGlobal("configDir", luar.New(L, configDir))
|
||||
L.SetGlobal("Reload", luar.New(L, LoadAll))
|
||||
L.SetGlobal("ByteOffset", luar.New(L, ByteOffset))
|
||||
L.SetGlobal("ToCharPos", luar.New(L, ToCharPos))
|
||||
|
||||
if len(b) == 0 {
|
||||
// No buffers to open
|
||||
screen.Screen.Fini()
|
||||
runtime.Goexit()
|
||||
// Used for asynchronous jobs
|
||||
L.SetGlobal("JobStart", luar.New(L, JobStart))
|
||||
L.SetGlobal("JobSpawn", luar.New(L, JobSpawn))
|
||||
L.SetGlobal("JobSend", luar.New(L, JobSend))
|
||||
L.SetGlobal("JobStop", luar.New(L, JobStop))
|
||||
|
||||
// Extension Files
|
||||
L.SetGlobal("ReadRuntimeFile", luar.New(L, PluginReadRuntimeFile))
|
||||
L.SetGlobal("ListRuntimeFiles", luar.New(L, PluginListRuntimeFiles))
|
||||
L.SetGlobal("AddRuntimeFile", luar.New(L, PluginAddRuntimeFile))
|
||||
L.SetGlobal("AddRuntimeFilesFromDirectory", luar.New(L, PluginAddRuntimeFilesFromDirectory))
|
||||
L.SetGlobal("AddRuntimeFileFromMemory", luar.New(L, PluginAddRuntimeFileFromMemory))
|
||||
|
||||
// Access to Go stdlib
|
||||
L.SetGlobal("import", luar.New(L, Import))
|
||||
|
||||
jobs = make(chan JobFunction, 100)
|
||||
events = make(chan tcell.Event, 100)
|
||||
autosave = make(chan bool)
|
||||
|
||||
LoadPlugins()
|
||||
|
||||
for _, t := range tabs {
|
||||
for _, v := range t.views {
|
||||
for pl := range loadedPlugins {
|
||||
_, err := Call(pl+".onViewOpen", v)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
|
||||
TermMessage(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
action.InitTabs(b)
|
||||
|
||||
err = config.RunPluginFn("init")
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
err = config.RunPluginFn("postinit")
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
if clipErr != nil {
|
||||
log.Println(clipErr, " or change 'clipboard' option")
|
||||
}
|
||||
|
||||
if a := config.GetGlobalOption("autosave").(float64); a > 0 {
|
||||
config.SetAutoTime(int(a))
|
||||
config.StartAutoSave()
|
||||
}
|
||||
|
||||
screen.Events = make(chan tcell.Event)
|
||||
|
||||
sigterm = make(chan os.Signal, 1)
|
||||
sighup = make(chan os.Signal, 1)
|
||||
signal.Notify(sigterm, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGABRT)
|
||||
signal.Notify(sighup, syscall.SIGHUP)
|
||||
|
||||
timerChan = make(chan func())
|
||||
InitColorscheme()
|
||||
|
||||
// Here is the event loop which runs in a separate thread
|
||||
go func() {
|
||||
for {
|
||||
screen.Lock()
|
||||
e := screen.Screen.PollEvent()
|
||||
screen.Unlock()
|
||||
if e != nil {
|
||||
screen.Events <- e
|
||||
if screen != nil {
|
||||
events <- screen.PollEvent()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// 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 := <-screen.Events:
|
||||
action.Tabs.HandleEvent(event)
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
// time out after 10ms
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(autosaveTime * time.Second)
|
||||
if globalSettings["autosave"].(bool) {
|
||||
autosave <- true
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
DoEvent()
|
||||
}
|
||||
}
|
||||
// Display everything
|
||||
RedrawAll()
|
||||
|
||||
// DoEvent runs the main action loop of the editor
|
||||
func DoEvent() {
|
||||
var event tcell.Event
|
||||
var event tcell.Event
|
||||
|
||||
// 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.AutoSave()
|
||||
}
|
||||
case <-shell.CloseTerms:
|
||||
case event = <-screen.Events:
|
||||
case <-screen.DrawChan():
|
||||
for len(screen.DrawChan()) > 0 {
|
||||
<-screen.DrawChan()
|
||||
}
|
||||
case f := <-timerChan:
|
||||
f()
|
||||
case <-sighup:
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
if !b.Modified() {
|
||||
b.Fini()
|
||||
}
|
||||
}
|
||||
os.Exit(0)
|
||||
case <-sigterm:
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
if !b.Modified() {
|
||||
b.Fini()
|
||||
}
|
||||
// Check for new events
|
||||
select {
|
||||
case f := <-jobs:
|
||||
// If a new job has finished while running in the background we should execute the callback
|
||||
f.function(f.output, f.args...)
|
||||
continue
|
||||
case <-autosave:
|
||||
CurView().Save(true)
|
||||
case event = <-events:
|
||||
}
|
||||
|
||||
if screen.Screen != nil {
|
||||
screen.Screen.Fini()
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
for event != nil {
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventResize:
|
||||
for _, t := range tabs {
|
||||
t.Resize()
|
||||
}
|
||||
case *tcell.EventMouse:
|
||||
if !searching {
|
||||
if e.Buttons() == tcell.Button1 {
|
||||
// If the user left clicked we check a couple things
|
||||
_, h := screen.Size()
|
||||
x, y := e.Position()
|
||||
if y == h-1 && messenger.message != "" && globalSettings["infobar"].(bool) {
|
||||
// If the user clicked in the bottom bar, and there is a message down there
|
||||
// we copy it to the clipboard.
|
||||
// Often error messages are displayed down there so it can be useful to easily
|
||||
// copy the message
|
||||
clipboard.WriteAll(messenger.message, "primary")
|
||||
break
|
||||
}
|
||||
|
||||
if e, ok := event.(*tcell.EventError); ok {
|
||||
log.Println("tcell event error: ", e.Error())
|
||||
|
||||
if e.Err() == io.EOF {
|
||||
// shutdown due to terminal closing/becoming inaccessible
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
if !b.Modified() {
|
||||
b.Fini()
|
||||
if CurView().mouseReleased {
|
||||
// We loop through each view in the current tab and make sure the current view
|
||||
// is the one being clicked in
|
||||
for _, v := range tabs[curTab].views {
|
||||
if x > v.x && x <= v.x+v.Width && y > v.y && y <= v.y+v.Height {
|
||||
tabs[curTab].CurView = v.Num
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if screen.Screen != nil {
|
||||
screen.Screen.Fini()
|
||||
// This function checks the mouse event for the possibility of changing the current tab
|
||||
// If the tab was changed it returns true
|
||||
if TabbarHandleMouseEvent(event) {
|
||||
break
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if event != nil {
|
||||
_, resize := event.(*tcell.EventResize)
|
||||
if resize {
|
||||
action.InfoBar.HandleEvent(event)
|
||||
action.Tabs.HandleEvent(event)
|
||||
} else if action.InfoBar.HasPrompt {
|
||||
action.InfoBar.HandleEvent(event)
|
||||
} else {
|
||||
action.Tabs.HandleEvent(event)
|
||||
}
|
||||
}
|
||||
if searching {
|
||||
// Since searching is done in real time, we need to redraw every time
|
||||
// there is a new event in the search bar so we need a special function
|
||||
// to run instead of the standard HandleEvent.
|
||||
HandleSearchEvent(event, CurView())
|
||||
} else {
|
||||
// Send it to the view
|
||||
CurView().HandleEvent(event)
|
||||
}
|
||||
|
||||
err := config.RunPluginFn("onAnyEvent")
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
select {
|
||||
case event = <-events:
|
||||
default:
|
||||
event = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,357 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"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/tcell/v2"
|
||||
)
|
||||
|
||||
var tempDir string
|
||||
var sim tcell.SimulationScreen
|
||||
|
||||
func init() {
|
||||
screen.Events = make(chan tcell.Event, 8)
|
||||
}
|
||||
|
||||
func startup(args []string) (tcell.SimulationScreen, error) {
|
||||
var err error
|
||||
|
||||
tempDir, err = ioutil.TempDir("", "micro_test")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = config.InitConfigDir(tempDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.InitRuntimeFiles(true)
|
||||
config.InitPlugins()
|
||||
|
||||
err = config.ReadSettings()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = config.InitGlobalSettings()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s, err := screen.InitSimScreen()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
screen.Screen.Fini()
|
||||
fmt.Println("Micro encountered an error:", err)
|
||||
// backup all open buffers
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
b.Backup()
|
||||
}
|
||||
// Print the stack trace too
|
||||
log.Fatalf(errors.Wrap(err, 2).ErrorStack())
|
||||
}
|
||||
}()
|
||||
|
||||
err = config.LoadAllPlugins()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
action.InitBindings()
|
||||
action.InitCommands()
|
||||
|
||||
err = config.InitColorscheme()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b := LoadInput(args)
|
||||
|
||||
if len(b) == 0 {
|
||||
return nil, errors.New("No buffers opened")
|
||||
}
|
||||
|
||||
action.InitTabs(b)
|
||||
action.InitGlobals()
|
||||
|
||||
err = config.RunPluginFn("init")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.InjectResize()
|
||||
handleEvent()
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func cleanup() {
|
||||
os.RemoveAll(tempDir)
|
||||
}
|
||||
|
||||
func handleEvent() {
|
||||
screen.Lock()
|
||||
e := screen.Screen.PollEvent()
|
||||
screen.Unlock()
|
||||
if e != nil {
|
||||
screen.Events <- e
|
||||
}
|
||||
|
||||
for len(screen.DrawChan()) > 0 || len(screen.Events) > 0 {
|
||||
DoEvent()
|
||||
}
|
||||
}
|
||||
|
||||
func injectKey(key tcell.Key, r rune, mod tcell.ModMask) {
|
||||
sim.InjectKey(key, r, mod)
|
||||
handleEvent()
|
||||
}
|
||||
|
||||
func injectMouse(x, y int, buttons tcell.ButtonMask, mod tcell.ModMask) {
|
||||
sim.InjectMouse(x, y, buttons, mod)
|
||||
handleEvent()
|
||||
}
|
||||
|
||||
func injectString(str string) {
|
||||
// the tcell simulation screen event channel can only handle
|
||||
// 10 events at once, so we need to divide up the key events
|
||||
// into chunks of 10 and handle the 10 events before sending
|
||||
// another chunk of events
|
||||
iters := len(str) / 10
|
||||
extra := len(str) % 10
|
||||
|
||||
for i := 0; i < iters; i++ {
|
||||
s := i * 10
|
||||
e := i*10 + 10
|
||||
sim.InjectKeyBytes([]byte(str[s:e]))
|
||||
for i := 0; i < 10; i++ {
|
||||
handleEvent()
|
||||
}
|
||||
}
|
||||
|
||||
sim.InjectKeyBytes([]byte(str[len(str)-extra:]))
|
||||
for i := 0; i < extra; i++ {
|
||||
handleEvent()
|
||||
}
|
||||
}
|
||||
|
||||
func openFile(file string) {
|
||||
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
|
||||
injectString(fmt.Sprintf("open %s", file))
|
||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
||||
}
|
||||
|
||||
func findBuffer(file string) *buffer.Buffer {
|
||||
var buf *buffer.Buffer
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
if b.Path == file {
|
||||
buf = b
|
||||
}
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
func createTestFile(name string, content string) (string, error) {
|
||||
testf, err := ioutil.TempFile("", name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := testf.Write([]byte(content)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := testf.Close(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return testf.Name(), nil
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
var err error
|
||||
sim, err = startup([]string{})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
retval := m.Run()
|
||||
cleanup()
|
||||
|
||||
os.Exit(retval)
|
||||
}
|
||||
|
||||
func TestSimpleEdit(t *testing.T) {
|
||||
file, err := createTestFile("micro_simple_edit_test", "base content")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(file)
|
||||
|
||||
openFile(file)
|
||||
|
||||
if findBuffer(file) == nil {
|
||||
t.Errorf("Could not find buffer %s", file)
|
||||
return
|
||||
}
|
||||
|
||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
||||
injectKey(tcell.KeyUp, 0, tcell.ModNone)
|
||||
injectString("first line")
|
||||
|
||||
// test both kinds of backspace
|
||||
for i := 0; i < len("ne"); i++ {
|
||||
injectKey(tcell.KeyBackspace, rune(tcell.KeyBackspace), tcell.ModNone)
|
||||
}
|
||||
for i := 0; i < len(" li"); i++ {
|
||||
injectKey(tcell.KeyBackspace2, rune(tcell.KeyBackspace2), tcell.ModNone)
|
||||
}
|
||||
injectString("foobar")
|
||||
|
||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, "firstfoobar\nbase content\n", string(data))
|
||||
}
|
||||
|
||||
func TestMouse(t *testing.T) {
|
||||
file, err := createTestFile("micro_mouse_test", "base content")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(file)
|
||||
|
||||
openFile(file)
|
||||
|
||||
if findBuffer(file) == nil {
|
||||
t.Errorf("Could not find buffer %s", file)
|
||||
return
|
||||
}
|
||||
|
||||
// buffer:
|
||||
// base content
|
||||
// the selections need to happen at different locations to avoid a double click
|
||||
injectMouse(3, 0, tcell.Button1, tcell.ModNone)
|
||||
injectKey(tcell.KeyLeft, 0, tcell.ModNone)
|
||||
injectMouse(0, 0, tcell.ButtonNone, tcell.ModNone)
|
||||
injectString("secondline")
|
||||
// buffer:
|
||||
// secondlinebase content
|
||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
||||
// buffer:
|
||||
// secondline
|
||||
// base content
|
||||
injectMouse(2, 0, tcell.Button1, tcell.ModNone)
|
||||
injectMouse(0, 0, tcell.ButtonNone, tcell.ModNone)
|
||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
||||
// buffer:
|
||||
//
|
||||
// secondline
|
||||
// base content
|
||||
injectKey(tcell.KeyUp, 0, tcell.ModNone)
|
||||
injectString("firstline")
|
||||
// buffer:
|
||||
// firstline
|
||||
// secondline
|
||||
// base content
|
||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, "firstline\nsecondline\nbase content\n", string(data))
|
||||
}
|
||||
|
||||
var srTestStart = `foo
|
||||
foo
|
||||
foofoofoo
|
||||
Ernleȝe foo æðelen
|
||||
`
|
||||
var srTest2 = `test_string
|
||||
test_string
|
||||
test_stringtest_stringtest_string
|
||||
Ernleȝe test_string æðelen
|
||||
`
|
||||
var srTest3 = `test_foo
|
||||
test_string
|
||||
test_footest_stringtest_foo
|
||||
Ernleȝe test_string æðelen
|
||||
`
|
||||
|
||||
func TestSearchAndReplace(t *testing.T) {
|
||||
file, err := createTestFile("micro_search_replace_test", srTestStart)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(file)
|
||||
|
||||
openFile(file)
|
||||
|
||||
if findBuffer(file) == nil {
|
||||
t.Errorf("Could not find buffer %s", file)
|
||||
return
|
||||
}
|
||||
|
||||
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
|
||||
injectString(fmt.Sprintf("replaceall %s %s", "foo", "test_string"))
|
||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
||||
|
||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, srTest2, string(data))
|
||||
|
||||
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
|
||||
injectString(fmt.Sprintf("replace %s %s", "string", "foo"))
|
||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
||||
injectString("ynyny")
|
||||
injectKey(tcell.KeyEscape, 0, tcell.ModNone)
|
||||
|
||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||
|
||||
data, err = ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, srTest3, string(data))
|
||||
}
|
||||
|
||||
func TestMultiCursor(t *testing.T) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func TestSettingsPersistence(t *testing.T) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
// more tests (rendering, tabs, plugins)?
|
165
cmd/micro/plugin.go
Normal file
165
cmd/micro/plugin.go
Normal file
|
@ -0,0 +1,165 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/tcell"
|
||||
"layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
var loadedPlugins map[string]string
|
||||
|
||||
// Call calls the lua function 'function'
|
||||
// If it does not exist nothing happens, if there is an error,
|
||||
// the error is returned
|
||||
func Call(function string, args ...interface{}) (lua.LValue, error) {
|
||||
var luaFunc lua.LValue
|
||||
if strings.Contains(function, ".") {
|
||||
plugin := L.GetGlobal(strings.Split(function, ".")[0])
|
||||
if plugin.String() == "nil" {
|
||||
return nil, errors.New("function does not exist: " + function)
|
||||
}
|
||||
luaFunc = L.GetField(plugin, strings.Split(function, ".")[1])
|
||||
} else {
|
||||
luaFunc = L.GetGlobal(function)
|
||||
}
|
||||
|
||||
if luaFunc.String() == "nil" {
|
||||
return nil, errors.New("function does not exist: " + function)
|
||||
}
|
||||
var luaArgs []lua.LValue
|
||||
for _, v := range args {
|
||||
luaArgs = append(luaArgs, luar.New(L, v))
|
||||
}
|
||||
err := L.CallByParam(lua.P{
|
||||
Fn: luaFunc,
|
||||
NRet: 1,
|
||||
Protect: true,
|
||||
}, luaArgs...)
|
||||
ret := L.Get(-1) // returned value
|
||||
if ret.String() != "nil" {
|
||||
L.Pop(1) // remove received value
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// LuaFunctionBinding is a function generator which takes the name of a lua function
|
||||
// and creates a function that will call that lua function
|
||||
// Specifically it creates a function that can be called as a binding because this is used
|
||||
// to bind keys to lua functions
|
||||
func LuaFunctionBinding(function string) func(*View, bool) bool {
|
||||
return func(v *View, _ bool) bool {
|
||||
_, err := Call(function, nil)
|
||||
if err != nil {
|
||||
TermMessage(err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func LuaFunctionMouseBinding(function string) func(*View, bool, *tcell.EventMouse) bool {
|
||||
return func(v *View, _ bool, e *tcell.EventMouse) bool {
|
||||
_, err := Call(function, e)
|
||||
if err != nil {
|
||||
TermMessage(err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func unpack(old []string) []interface{} {
|
||||
new := make([]interface{}, len(old))
|
||||
for i, v := range old {
|
||||
new[i] = v
|
||||
}
|
||||
return new
|
||||
}
|
||||
|
||||
// LuaFunctionCommand is the same as LuaFunctionBinding except it returns a normal function
|
||||
// so that a command can be bound to a lua function
|
||||
func LuaFunctionCommand(function string) func([]string) {
|
||||
return func(args []string) {
|
||||
_, err := Call(function, unpack(args)...)
|
||||
if err != nil {
|
||||
TermMessage(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LuaFunctionComplete returns a function which can be used for autocomplete in plugins
|
||||
func LuaFunctionComplete(function string) func(string) []string {
|
||||
return func(input string) (result []string) {
|
||||
|
||||
res, err := Call(function, input)
|
||||
if err != nil {
|
||||
TermMessage(err)
|
||||
}
|
||||
if tbl, ok := res.(*lua.LTable); !ok {
|
||||
TermMessage(function, "should return a table of strings")
|
||||
} else {
|
||||
for i := 1; i <= tbl.Len(); i++ {
|
||||
val := tbl.RawGetInt(i)
|
||||
if v, ok := val.(lua.LString); !ok {
|
||||
TermMessage(function, "should return a table of strings")
|
||||
} else {
|
||||
result = append(result, string(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
func LuaFunctionJob(function string) func(string, ...string) {
|
||||
return func(output string, args ...string) {
|
||||
_, err := Call(function, unpack(append([]string{output}, args...))...)
|
||||
if err != nil {
|
||||
TermMessage(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// luaPluginName convert a human-friendly plugin name into a valid lua variable name.
|
||||
func luaPluginName(name string) string {
|
||||
return strings.Replace(name, "-", "_", -1)
|
||||
}
|
||||
|
||||
// LoadPlugins loads the pre-installed plugins and the plugins located in ~/.config/micro/plugins
|
||||
func LoadPlugins() {
|
||||
loadedPlugins = make(map[string]string)
|
||||
|
||||
for _, plugin := range ListRuntimeFiles(RTPlugin) {
|
||||
pluginName := plugin.Name()
|
||||
if _, ok := loadedPlugins[pluginName]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := plugin.Data()
|
||||
if err != nil {
|
||||
TermMessage("Error loading plugin: " + pluginName)
|
||||
continue
|
||||
}
|
||||
|
||||
pluginLuaName := luaPluginName(pluginName)
|
||||
|
||||
if err := LoadFile(pluginName, pluginName, string(data)); err != nil {
|
||||
TermMessage(err)
|
||||
continue
|
||||
}
|
||||
|
||||
loadedPlugins[pluginName] = pluginLuaName
|
||||
|
||||
}
|
||||
|
||||
if _, err := os.Stat(configDir + "/init.lua"); err == nil {
|
||||
data, _ := ioutil.ReadFile(configDir + "/init.lua")
|
||||
if err := LoadFile("init", configDir+"init.lua", string(data)); err != nil {
|
||||
TermMessage(err)
|
||||
}
|
||||
loadedPlugins["init"] = "init"
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package config
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
|
@ -8,16 +8,15 @@ import (
|
|||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"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"
|
||||
"github.com/flynn/json5"
|
||||
"github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -56,10 +55,6 @@ type PluginVersion struct {
|
|||
Require PluginDependencies
|
||||
}
|
||||
|
||||
func (pv *PluginVersion) Pack() *PluginPackage {
|
||||
return pv.pack
|
||||
}
|
||||
|
||||
// PluginVersions is a slice of PluginVersion
|
||||
type PluginVersions []*PluginVersion
|
||||
|
||||
|
@ -118,17 +113,18 @@ func fetchAllSources(count int, fetcher func(i int) PluginPackages) PluginPackag
|
|||
}
|
||||
|
||||
// Fetch retrieves all available PluginPackages from the given channels
|
||||
func (pc PluginChannels) Fetch(out io.Writer) PluginPackages {
|
||||
func (pc PluginChannels) Fetch() PluginPackages {
|
||||
return fetchAllSources(len(pc), func(i int) PluginPackages {
|
||||
return pc[i].Fetch(out)
|
||||
return pc[i].Fetch()
|
||||
})
|
||||
}
|
||||
|
||||
// Fetch retrieves all available PluginPackages from the given channel
|
||||
func (pc PluginChannel) Fetch(out io.Writer) PluginPackages {
|
||||
func (pc PluginChannel) Fetch() PluginPackages {
|
||||
// messenger.AddLog(fmt.Sprintf("Fetching channel: %q", string(pc)))
|
||||
resp, err := http.Get(string(pc))
|
||||
if err != nil {
|
||||
fmt.Fprintln(out, "Failed to query plugin channel:\n", err)
|
||||
TermMessage("Failed to query plugin channel:\n", err)
|
||||
return PluginPackages{}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
@ -136,19 +132,20 @@ func (pc PluginChannel) Fetch(out io.Writer) PluginPackages {
|
|||
|
||||
var repositories []PluginRepository
|
||||
if err := decoder.Decode(&repositories); err != nil {
|
||||
fmt.Fprintln(out, "Failed to decode channel data:\n", err)
|
||||
TermMessage("Failed to decode channel data:\n", err)
|
||||
return PluginPackages{}
|
||||
}
|
||||
return fetchAllSources(len(repositories), func(i int) PluginPackages {
|
||||
return repositories[i].Fetch(out)
|
||||
return repositories[i].Fetch()
|
||||
})
|
||||
}
|
||||
|
||||
// Fetch retrieves all available PluginPackages from the given repository
|
||||
func (pr PluginRepository) Fetch(out io.Writer) PluginPackages {
|
||||
func (pr PluginRepository) Fetch() PluginPackages {
|
||||
// messenger.AddLog(fmt.Sprintf("Fetching repository: %q", string(pr)))
|
||||
resp, err := http.Get(string(pr))
|
||||
if err != nil {
|
||||
fmt.Fprintln(out, "Failed to query plugin repository:\n", err)
|
||||
TermMessage("Failed to query plugin repository:\n", err)
|
||||
return PluginPackages{}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
@ -156,7 +153,7 @@ func (pr PluginRepository) Fetch(out io.Writer) PluginPackages {
|
|||
|
||||
var plugins PluginPackages
|
||||
if err := decoder.Decode(&plugins); err != nil {
|
||||
fmt.Fprintln(out, "Failed to decode repository data:\n", err)
|
||||
TermMessage("Failed to decode repository data:\n", err)
|
||||
return PluginPackages{}
|
||||
}
|
||||
if len(plugins) > 0 {
|
||||
|
@ -218,10 +215,10 @@ func (pp *PluginPackage) UnmarshalJSON(data []byte) error {
|
|||
}
|
||||
|
||||
// GetAllPluginPackages gets all PluginPackages which may be available.
|
||||
func GetAllPluginPackages(out io.Writer) PluginPackages {
|
||||
func GetAllPluginPackages() PluginPackages {
|
||||
if allPluginPackages == nil {
|
||||
getOption := func(name string) []string {
|
||||
data := GetGlobalOption(name)
|
||||
data := GetOption(name)
|
||||
if strs, ok := data.([]string); ok {
|
||||
return strs
|
||||
}
|
||||
|
@ -249,9 +246,9 @@ func GetAllPluginPackages(out io.Writer) PluginPackages {
|
|||
}
|
||||
allPluginPackages = fetchAllSources(len(repos)+1, func(i int) PluginPackages {
|
||||
if i == 0 {
|
||||
return channels.Fetch(out)
|
||||
return channels.Fetch()
|
||||
}
|
||||
return repos[i-1].Fetch(out)
|
||||
return repos[i-1].Fetch()
|
||||
})
|
||||
}
|
||||
return allPluginPackages
|
||||
|
@ -301,8 +298,8 @@ func (pp PluginPackage) Match(text string) bool {
|
|||
}
|
||||
|
||||
// IsInstallable returns true if the package can be installed.
|
||||
func (pp PluginPackage) IsInstallable(out io.Writer) error {
|
||||
_, err := GetAllPluginPackages(out).Resolve(GetInstalledVersions(true), PluginDependencies{
|
||||
func (pp PluginPackage) IsInstallable() error {
|
||||
_, err := GetAllPluginPackages().Resolve(GetInstalledVersions(true), PluginDependencies{
|
||||
&PluginDependency{
|
||||
Name: pp.Name,
|
||||
Range: semver.Range(func(v semver.Version) bool { return true }),
|
||||
|
@ -312,18 +309,18 @@ func (pp PluginPackage) IsInstallable(out io.Writer) error {
|
|||
|
||||
// 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) {
|
||||
func SearchPlugin(texts []string) (plugins PluginPackages) {
|
||||
plugins = make(PluginPackages, 0)
|
||||
|
||||
pluginLoop:
|
||||
for _, pp := range GetAllPluginPackages(out) {
|
||||
for _, pp := range GetAllPluginPackages() {
|
||||
for _, text := range texts {
|
||||
if !pp.Match(text) {
|
||||
continue pluginLoop
|
||||
}
|
||||
}
|
||||
|
||||
if err := pp.IsInstallable(out); err == nil {
|
||||
if err := pp.IsInstallable(); err == nil {
|
||||
plugins = append(plugins, pp)
|
||||
}
|
||||
}
|
||||
|
@ -331,7 +328,7 @@ pluginLoop:
|
|||
}
|
||||
|
||||
func isUnknownCoreVersion() bool {
|
||||
_, err := semver.ParseTolerant(util.Version)
|
||||
_, err := semver.ParseTolerant(Version)
|
||||
return err != nil
|
||||
}
|
||||
|
||||
|
@ -359,15 +356,12 @@ func newStaticPluginVersion(name, version string) *PluginVersion {
|
|||
func GetInstalledVersions(withCore bool) PluginVersions {
|
||||
result := PluginVersions{}
|
||||
if withCore {
|
||||
result = append(result, newStaticPluginVersion(CorePluginName, util.Version))
|
||||
result = append(result, newStaticPluginVersion(CorePluginName, Version))
|
||||
}
|
||||
|
||||
for _, p := range Plugins {
|
||||
if !p.IsLoaded() {
|
||||
continue
|
||||
}
|
||||
version := GetInstalledPluginVersion(p.Name)
|
||||
if pv := newStaticPluginVersion(p.Name, version); pv != nil {
|
||||
for name, lpname := range loadedPlugins {
|
||||
version := GetInstalledPluginVersion(lpname)
|
||||
if pv := newStaticPluginVersion(name, version); pv != nil {
|
||||
result = append(result, pv)
|
||||
}
|
||||
}
|
||||
|
@ -377,9 +371,9 @@ func GetInstalledVersions(withCore bool) PluginVersions {
|
|||
|
||||
// GetInstalledPluginVersion returns the string of the exported VERSION variable of a loaded plugin
|
||||
func GetInstalledPluginVersion(name string) string {
|
||||
plugin := ulua.L.GetGlobal(name)
|
||||
plugin := L.GetGlobal(name)
|
||||
if plugin != lua.LNil {
|
||||
version := ulua.L.GetField(plugin, "VERSION")
|
||||
version := L.GetField(plugin, "VERSION")
|
||||
if str, ok := version.(lua.LString); ok {
|
||||
return string(str)
|
||||
|
||||
|
@ -389,8 +383,8 @@ func GetInstalledPluginVersion(name string) string {
|
|||
}
|
||||
|
||||
// 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)
|
||||
func (pv *PluginVersion) DownloadAndInstall() error {
|
||||
messenger.AddLog(fmt.Sprintf("Downloading %q (%s) from %q", pv.pack.Name, pv.Version, pv.Url))
|
||||
resp, err := http.Get(pv.Url)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -405,7 +399,7 @@ func (pv *PluginVersion) DownloadAndInstall(out io.Writer) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetDir := filepath.Join(ConfigDir, "plug", pv.pack.Name)
|
||||
targetDir := filepath.Join(configDir, "plugins", pv.pack.Name)
|
||||
dirPerm := os.FileMode(0755)
|
||||
if err = os.MkdirAll(targetDir, dirPerm); err != nil {
|
||||
return err
|
||||
|
@ -429,7 +423,6 @@ func (pv *PluginVersion) DownloadAndInstall(out io.Writer) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Install files and directory's
|
||||
for _, f := range z.File {
|
||||
parts := strings.Split(f.Name, "/")
|
||||
if allPrefixed {
|
||||
|
@ -442,7 +435,7 @@ func (pv *PluginVersion) DownloadAndInstall(out io.Writer) error {
|
|||
return err
|
||||
}
|
||||
} else {
|
||||
basepath := filepath.Dir(targetName)
|
||||
basepath := path.Dir(targetName)
|
||||
|
||||
if err := os.MkdirAll(basepath, dirPerm); err != nil {
|
||||
return err
|
||||
|
@ -479,7 +472,9 @@ func (pl PluginPackages) GetAllVersions(name string) PluginVersions {
|
|||
result := make(PluginVersions, 0)
|
||||
p := pl.Get(name)
|
||||
if p != nil {
|
||||
result = append(result, p.Versions...)
|
||||
for _, v := range p.Versions {
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
@ -537,7 +532,7 @@ func (all PluginPackages) Resolve(selectedVersions PluginVersions, open PluginDe
|
|||
return selectedVersions, nil
|
||||
}
|
||||
|
||||
func (pv PluginVersions) install(out io.Writer) {
|
||||
func (pv PluginVersions) install() {
|
||||
anyInstalled := false
|
||||
currentlyInstalled := GetInstalledVersions(true)
|
||||
|
||||
|
@ -546,16 +541,16 @@ func (pv PluginVersions) install(out io.Writer) {
|
|||
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)
|
||||
messenger.AddLog(fmt.Sprint("Uninstalling %q", sel.pack.Name))
|
||||
UninstallPlugin(sel.pack.Name)
|
||||
} else {
|
||||
shouldInstall = false
|
||||
}
|
||||
}
|
||||
|
||||
if shouldInstall {
|
||||
if err := sel.DownloadAndInstall(out); err != nil {
|
||||
fmt.Fprintln(out, err)
|
||||
if err := sel.DownloadAndInstall(); err != nil {
|
||||
messenger.Error(err)
|
||||
return
|
||||
}
|
||||
anyInstalled = true
|
||||
|
@ -563,58 +558,47 @@ func (pv PluginVersions) install(out io.Writer) {
|
|||
}
|
||||
}
|
||||
if anyInstalled {
|
||||
fmt.Fprintln(out, "One or more plugins installed.")
|
||||
messenger.Message("One or more plugins installed. Please restart micro.")
|
||||
} else {
|
||||
fmt.Fprintln(out, "Nothing to install / update")
|
||||
messenger.AddLog("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.IsLoaded() {
|
||||
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
|
||||
}
|
||||
func UninstallPlugin(name string) {
|
||||
if err := os.RemoveAll(filepath.Join(configDir, "plugins", name)); err != nil {
|
||||
messenger.Error(err)
|
||||
return
|
||||
}
|
||||
delete(loadedPlugins, name)
|
||||
}
|
||||
|
||||
// Install installs the plugin
|
||||
func (pl PluginPackage) Install(out io.Writer) {
|
||||
selected, err := GetAllPluginPackages(out).Resolve(GetInstalledVersions(true), PluginDependencies{
|
||||
func (pl PluginPackage) Install() {
|
||||
selected, err := GetAllPluginPackages().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)
|
||||
TermMessage(err)
|
||||
return
|
||||
}
|
||||
selected.install(out)
|
||||
selected.install()
|
||||
}
|
||||
|
||||
// UpdatePlugins updates the given plugins
|
||||
func UpdatePlugins(out io.Writer, plugins []string) {
|
||||
func UpdatePlugins(plugins []string) {
|
||||
// if no plugins are specified, update all installed plugins.
|
||||
if len(plugins) == 0 {
|
||||
for _, p := range Plugins {
|
||||
if !p.IsLoaded() || p.Default {
|
||||
continue
|
||||
}
|
||||
plugins = append(plugins, p.Name)
|
||||
for name := range loadedPlugins {
|
||||
plugins = append(plugins, name)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(out, "Checking for plugin updates")
|
||||
messenger.AddLog("Checking for plugin updates")
|
||||
microVersion := PluginVersions{
|
||||
newStaticPluginVersion(CorePluginName, util.Version),
|
||||
newStaticPluginVersion(CorePluginName, Version),
|
||||
}
|
||||
|
||||
var updates = make(PluginDependencies, 0)
|
||||
|
@ -629,82 +613,10 @@ func UpdatePlugins(out io.Writer, plugins []string) {
|
|||
}
|
||||
}
|
||||
|
||||
selected, err := GetAllPluginPackages(out).Resolve(microVersion, updates)
|
||||
selected, err := GetAllPluginPackages().Resolve(microVersion, updates)
|
||||
if err != nil {
|
||||
fmt.Fprintln(out, err)
|
||||
TermMessage(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")
|
||||
}
|
||||
selected.install()
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
package config
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/blang/semver"
|
||||
|
||||
"github.com/zyedidia/json5"
|
||||
"github.com/flynn/json5"
|
||||
)
|
||||
|
||||
func TestDependencyResolving(t *testing.T) {
|
||||
|
@ -36,7 +36,7 @@ func TestDependencyResolving(t *testing.T) {
|
|||
if v == nil {
|
||||
t.Errorf("Failed to resolve %s", name)
|
||||
} else if expected.NE(v.Version) {
|
||||
t.Errorf("%s resolved in wrong version %v", name, v)
|
||||
t.Errorf("%s resolved in wrong version got %s", name, v)
|
||||
}
|
||||
}
|
||||
|
207
cmd/micro/rtfiles.go
Normal file
207
cmd/micro/rtfiles.go
Normal file
|
@ -0,0 +1,207 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
RTColorscheme = "colorscheme"
|
||||
RTSyntax = "syntax"
|
||||
RTHelp = "help"
|
||||
RTPlugin = "plugin"
|
||||
)
|
||||
|
||||
// RuntimeFile allows the program to read runtime data like colorschemes or syntax files
|
||||
type RuntimeFile interface {
|
||||
// Name returns a name of the file without paths or extensions
|
||||
Name() string
|
||||
// Data returns the content of the file.
|
||||
Data() ([]byte, error)
|
||||
}
|
||||
|
||||
// allFiles contains all available files, mapped by filetype
|
||||
var allFiles map[string][]RuntimeFile
|
||||
|
||||
// some file on filesystem
|
||||
type realFile string
|
||||
|
||||
// some asset file
|
||||
type assetFile string
|
||||
|
||||
// some file on filesystem but with a different name
|
||||
type namedFile struct {
|
||||
realFile
|
||||
name string
|
||||
}
|
||||
|
||||
// a file with the data stored in memory
|
||||
type memoryFile struct {
|
||||
name string
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (mf memoryFile) Name() string {
|
||||
return mf.name
|
||||
}
|
||||
func (mf memoryFile) Data() ([]byte, error) {
|
||||
return mf.data, nil
|
||||
}
|
||||
|
||||
func (rf realFile) Name() string {
|
||||
fn := filepath.Base(string(rf))
|
||||
return fn[:len(fn)-len(filepath.Ext(fn))]
|
||||
}
|
||||
|
||||
func (rf realFile) Data() ([]byte, error) {
|
||||
return ioutil.ReadFile(string(rf))
|
||||
}
|
||||
|
||||
func (af assetFile) Name() string {
|
||||
fn := path.Base(string(af))
|
||||
return fn[:len(fn)-len(path.Ext(fn))]
|
||||
}
|
||||
|
||||
func (af assetFile) Data() ([]byte, error) {
|
||||
return Asset(string(af))
|
||||
}
|
||||
|
||||
func (nf namedFile) Name() string {
|
||||
return nf.name
|
||||
}
|
||||
|
||||
// AddRuntimeFile registers a file for the given filetype
|
||||
func AddRuntimeFile(fileType string, file RuntimeFile) {
|
||||
if allFiles == nil {
|
||||
allFiles = make(map[string][]RuntimeFile)
|
||||
}
|
||||
allFiles[fileType] = append(allFiles[fileType], file)
|
||||
}
|
||||
|
||||
// AddRuntimeFilesFromDirectory registers each file from the given directory for
|
||||
// the filetype which matches the file-pattern
|
||||
func AddRuntimeFilesFromDirectory(fileType, directory, pattern string) {
|
||||
files, _ := ioutil.ReadDir(directory)
|
||||
for _, f := range files {
|
||||
if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
|
||||
fullPath := filepath.Join(directory, f.Name())
|
||||
AddRuntimeFile(fileType, realFile(fullPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddRuntimeFilesFromAssets registers each file from the given asset-directory for
|
||||
// the filetype which matches the file-pattern
|
||||
func AddRuntimeFilesFromAssets(fileType, directory, pattern string) {
|
||||
files, err := AssetDir(directory)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, f := range files {
|
||||
if ok, _ := path.Match(pattern, f); ok {
|
||||
AddRuntimeFile(fileType, assetFile(path.Join(directory, f)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FindRuntimeFile finds a runtime file of the given filetype and name
|
||||
// will return nil if no file was found
|
||||
func FindRuntimeFile(fileType, name string) RuntimeFile {
|
||||
for _, f := range ListRuntimeFiles(fileType) {
|
||||
if f.Name() == name {
|
||||
return f
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListRuntimeFiles lists all known runtime files for the given filetype
|
||||
func ListRuntimeFiles(fileType string) []RuntimeFile {
|
||||
if files, ok := allFiles[fileType]; ok {
|
||||
return files
|
||||
}
|
||||
return []RuntimeFile{}
|
||||
}
|
||||
|
||||
// InitRuntimeFiles initializes all assets file and the config directory
|
||||
func InitRuntimeFiles() {
|
||||
add := func(fileType, dir, pattern string) {
|
||||
AddRuntimeFilesFromDirectory(fileType, filepath.Join(configDir, dir), pattern)
|
||||
AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
|
||||
}
|
||||
|
||||
add(RTColorscheme, "colorschemes", "*.micro")
|
||||
add(RTSyntax, "syntax", "*.yaml")
|
||||
add(RTHelp, "help", "*.md")
|
||||
|
||||
// Search configDir for plugin-scripts
|
||||
files, _ := ioutil.ReadDir(filepath.Join(configDir, "plugins"))
|
||||
for _, f := range files {
|
||||
realpath, _ := filepath.EvalSymlinks(filepath.Join(configDir, "plugins", f.Name()))
|
||||
realpathStat, _ := os.Stat(realpath)
|
||||
if realpathStat.IsDir() {
|
||||
scriptPath := filepath.Join(configDir, "plugins", f.Name(), f.Name()+".lua")
|
||||
if _, err := os.Stat(scriptPath); err == nil {
|
||||
AddRuntimeFile(RTPlugin, realFile(scriptPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if files, err := AssetDir("runtime/plugins"); err == nil {
|
||||
for _, f := range files {
|
||||
scriptPath := path.Join("runtime/plugins", f, f+".lua")
|
||||
if _, err := AssetInfo(scriptPath); err == nil {
|
||||
AddRuntimeFile(RTPlugin, assetFile(scriptPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PluginReadRuntimeFile allows plugin scripts to read the content of a runtime file
|
||||
func PluginReadRuntimeFile(fileType, name string) string {
|
||||
if file := FindRuntimeFile(fileType, name); file != nil {
|
||||
if data, err := file.Data(); err == nil {
|
||||
return string(data)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// PluginListRuntimeFiles allows plugins to lists all runtime files of the given type
|
||||
func PluginListRuntimeFiles(fileType string) []string {
|
||||
files := ListRuntimeFiles(fileType)
|
||||
result := make([]string, len(files))
|
||||
for i, f := range files {
|
||||
result[i] = f.Name()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// PluginAddRuntimeFile adds a file to the runtime files for a plugin
|
||||
func PluginAddRuntimeFile(plugin, filetype, filePath string) {
|
||||
fullpath := filepath.Join(configDir, "plugins", plugin, filePath)
|
||||
if _, err := os.Stat(fullpath); err == nil {
|
||||
AddRuntimeFile(filetype, realFile(fullpath))
|
||||
} else {
|
||||
fullpath = path.Join("runtime", "plugins", plugin, filePath)
|
||||
AddRuntimeFile(filetype, assetFile(fullpath))
|
||||
}
|
||||
}
|
||||
|
||||
// PluginAddRuntimeFilesFromDirectory adds files from a directory to the runtime files for a plugin
|
||||
func PluginAddRuntimeFilesFromDirectory(plugin, filetype, directory, pattern string) {
|
||||
fullpath := filepath.Join(configDir, "plugins", plugin, directory)
|
||||
if _, err := os.Stat(fullpath); err == nil {
|
||||
AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
|
||||
} else {
|
||||
fullpath = path.Join("runtime", "plugins", plugin, directory)
|
||||
AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
|
||||
}
|
||||
}
|
||||
|
||||
// PluginAddRuntimeFileFromMemory adds a file to the runtime files for a plugin from a given string
|
||||
func PluginAddRuntimeFileFromMemory(plugin, filetype, filename, data string) {
|
||||
AddRuntimeFile(filetype, memoryFile{filename, []byte(data)})
|
||||
}
|
3981
cmd/micro/runtime.go
Normal file
3981
cmd/micro/runtime.go
Normal file
File diff suppressed because one or more lines are too long
187
cmd/micro/search.go
Normal file
187
cmd/micro/search.go
Normal file
|
@ -0,0 +1,187 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
var (
|
||||
// What was the last search
|
||||
lastSearch string
|
||||
|
||||
// Where should we start the search down from (or up from)
|
||||
searchStart Loc
|
||||
|
||||
// Is there currently a search in progress
|
||||
searching bool
|
||||
|
||||
// Stores the history for searching
|
||||
searchHistory []string
|
||||
)
|
||||
|
||||
// BeginSearch starts a search
|
||||
func BeginSearch(searchStr string) {
|
||||
searchHistory = append(searchHistory, "")
|
||||
messenger.historyNum = len(searchHistory) - 1
|
||||
searching = true
|
||||
messenger.response = searchStr
|
||||
messenger.cursorx = Count(searchStr)
|
||||
messenger.Message("Find: ")
|
||||
messenger.hasPrompt = true
|
||||
}
|
||||
|
||||
// EndSearch stops the current search
|
||||
func EndSearch() {
|
||||
searchHistory[len(searchHistory)-1] = messenger.response
|
||||
searching = false
|
||||
messenger.hasPrompt = false
|
||||
messenger.Clear()
|
||||
messenger.Reset()
|
||||
if lastSearch != "" {
|
||||
messenger.Message("^P Previous ^N Next")
|
||||
}
|
||||
}
|
||||
|
||||
// ExitSearch exits the search mode, reset active search phrase, and clear status bar
|
||||
func ExitSearch(v *View) {
|
||||
lastSearch = ""
|
||||
searching = false
|
||||
messenger.hasPrompt = false
|
||||
messenger.Clear()
|
||||
messenger.Reset()
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
|
||||
// HandleSearchEvent takes an event and a view and will do a real time match from the messenger's output
|
||||
// to the current buffer. It searches down the buffer.
|
||||
func HandleSearchEvent(event tcell.Event, v *View) {
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
switch e.Key() {
|
||||
case tcell.KeyEscape:
|
||||
// Exit the search mode
|
||||
ExitSearch(v)
|
||||
return
|
||||
case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEnter:
|
||||
// Done
|
||||
EndSearch()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
messenger.HandleEvent(event, searchHistory)
|
||||
|
||||
if messenger.cursorx < 0 {
|
||||
// Done
|
||||
EndSearch()
|
||||
return
|
||||
}
|
||||
|
||||
if messenger.response == "" {
|
||||
v.Cursor.ResetSelection()
|
||||
// We don't end the search though
|
||||
return
|
||||
}
|
||||
|
||||
Search(messenger.response, v, true)
|
||||
|
||||
v.Relocate()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func searchDown(r *regexp.Regexp, v *View, start, end Loc) bool {
|
||||
for i := start.Y; i <= end.Y; i++ {
|
||||
var l []byte
|
||||
var charPos int
|
||||
if i == start.Y {
|
||||
runes := []rune(string(v.Buf.lines[i].data))
|
||||
l = []byte(string(runes[start.X:]))
|
||||
charPos = start.X
|
||||
|
||||
if strings.Contains(r.String(), "^") && start.X != 0 {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
l = v.Buf.lines[i].data
|
||||
}
|
||||
|
||||
match := r.FindIndex(l)
|
||||
|
||||
if match != nil {
|
||||
v.Cursor.SetSelectionStart(Loc{charPos + runePos(match[0], string(l)), i})
|
||||
v.Cursor.SetSelectionEnd(Loc{charPos + runePos(match[1], string(l)), i})
|
||||
v.Cursor.OrigSelection[0] = v.Cursor.CurSelection[0]
|
||||
v.Cursor.OrigSelection[1] = v.Cursor.CurSelection[1]
|
||||
v.Cursor.Loc = v.Cursor.CurSelection[1]
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func searchUp(r *regexp.Regexp, v *View, start, end Loc) bool {
|
||||
for i := start.Y; i >= end.Y; i-- {
|
||||
var l []byte
|
||||
if i == start.Y {
|
||||
runes := []rune(string(v.Buf.lines[i].data))
|
||||
l = []byte(string(runes[:start.X]))
|
||||
|
||||
if strings.Contains(r.String(), "$") && start.X != Count(string(l)) {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
l = v.Buf.lines[i].data
|
||||
}
|
||||
|
||||
match := r.FindIndex(l)
|
||||
|
||||
if match != nil {
|
||||
v.Cursor.SetSelectionStart(Loc{runePos(match[0], string(l)), i})
|
||||
v.Cursor.SetSelectionEnd(Loc{runePos(match[1], string(l)), i})
|
||||
v.Cursor.OrigSelection[0] = v.Cursor.CurSelection[0]
|
||||
v.Cursor.OrigSelection[1] = v.Cursor.CurSelection[1]
|
||||
v.Cursor.Loc = v.Cursor.CurSelection[1]
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Search searches in the view for the given regex. The down bool
|
||||
// specifies whether it should search down from the searchStart position
|
||||
// or up from there
|
||||
func Search(searchStr string, v *View, down bool) {
|
||||
if searchStr == "" {
|
||||
return
|
||||
}
|
||||
r, err := regexp.Compile(searchStr)
|
||||
if v.Buf.Settings["ignorecase"].(bool) {
|
||||
r, err = regexp.Compile("(?i)" + searchStr)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var found bool
|
||||
if down {
|
||||
found = searchDown(r, v, searchStart, v.Buf.End())
|
||||
if !found {
|
||||
found = searchDown(r, v, v.Buf.Start(), searchStart)
|
||||
}
|
||||
} else {
|
||||
found = searchUp(r, v, searchStart, v.Buf.Start())
|
||||
if !found {
|
||||
found = searchUp(r, v, v.Buf.End(), searchStart)
|
||||
}
|
||||
}
|
||||
if found {
|
||||
lastSearch = searchStr
|
||||
} else {
|
||||
v.Cursor.ResetSelection()
|
||||
}
|
||||
}
|
478
cmd/micro/settings.go
Normal file
478
cmd/micro/settings.go
Normal file
|
@ -0,0 +1,478 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/flynn/json5"
|
||||
"github.com/zyedidia/glob"
|
||||
)
|
||||
|
||||
type optionValidator func(string, interface{}) error
|
||||
|
||||
// The options that the user can set
|
||||
var globalSettings map[string]interface{}
|
||||
|
||||
var invalidSettings bool
|
||||
|
||||
// Options with validators
|
||||
var optionValidators = map[string]optionValidator{
|
||||
"tabsize": validatePositiveValue,
|
||||
"scrollmargin": validateNonNegativeValue,
|
||||
"scrollspeed": validateNonNegativeValue,
|
||||
"colorscheme": validateColorscheme,
|
||||
"colorcolumn": validateNonNegativeValue,
|
||||
"fileformat": validateLineEnding,
|
||||
}
|
||||
|
||||
// InitGlobalSettings initializes the options map and sets all options to their default values
|
||||
func InitGlobalSettings() {
|
||||
invalidSettings = false
|
||||
defaults := DefaultGlobalSettings()
|
||||
var parsed map[string]interface{}
|
||||
|
||||
filename := configDir + "/settings.json"
|
||||
writeSettings := false
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if !strings.HasPrefix(string(input), "null") {
|
||||
if err != nil {
|
||||
TermMessage("Error reading settings.json file: " + err.Error())
|
||||
invalidSettings = true
|
||||
return
|
||||
}
|
||||
|
||||
err = json5.Unmarshal(input, &parsed)
|
||||
if err != nil {
|
||||
TermMessage("Error reading settings.json:", err.Error())
|
||||
invalidSettings = true
|
||||
}
|
||||
} else {
|
||||
writeSettings = true
|
||||
}
|
||||
}
|
||||
|
||||
globalSettings = make(map[string]interface{})
|
||||
for k, v := range defaults {
|
||||
globalSettings[k] = v
|
||||
}
|
||||
for k, v := range parsed {
|
||||
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
||||
globalSettings[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filename); os.IsNotExist(err) || writeSettings {
|
||||
err := WriteSettings(filename)
|
||||
if err != nil {
|
||||
TermMessage("Error writing settings.json file: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InitLocalSettings scans the json in settings.json and sets the options locally based
|
||||
// on whether the buffer matches the glob
|
||||
func InitLocalSettings(buf *Buffer) {
|
||||
invalidSettings = false
|
||||
var parsed map[string]interface{}
|
||||
|
||||
filename := configDir + "/settings.json"
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
TermMessage("Error reading settings.json file: " + err.Error())
|
||||
invalidSettings = true
|
||||
return
|
||||
}
|
||||
|
||||
err = json5.Unmarshal(input, &parsed)
|
||||
if err != nil {
|
||||
TermMessage("Error reading settings.json:", err.Error())
|
||||
invalidSettings = true
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range parsed {
|
||||
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
||||
g, err := glob.Compile(k)
|
||||
if err != nil {
|
||||
TermMessage("Error with glob setting ", k, ": ", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if g.MatchString(buf.Path) {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
buf.Settings[k1] = v1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WriteSettings writes the settings to the specified filename as JSON
|
||||
func WriteSettings(filename string) error {
|
||||
if invalidSettings {
|
||||
// Do not write the settings if there was an error when reading them
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
if _, e := os.Stat(configDir); e == nil {
|
||||
parsed := make(map[string]interface{})
|
||||
|
||||
filename := configDir + "/settings.json"
|
||||
for k, v := range globalSettings {
|
||||
parsed[k] = v
|
||||
}
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if string(input) != "null" {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json5.Unmarshal(input, &parsed)
|
||||
if err != nil {
|
||||
TermMessage("Error reading settings.json:", err.Error())
|
||||
invalidSettings = true
|
||||
}
|
||||
|
||||
for k, v := range parsed {
|
||||
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
||||
if _, ok := globalSettings[k]; ok {
|
||||
parsed[k] = globalSettings[k]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
txt, _ := json.MarshalIndent(parsed, "", " ")
|
||||
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// AddOption creates a new option. This is meant to be called by plugins to add options.
|
||||
func AddOption(name string, value interface{}) {
|
||||
globalSettings[name] = value
|
||||
err := WriteSettings(configDir + "/settings.json")
|
||||
if err != nil {
|
||||
TermMessage("Error writing settings.json file: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// GetGlobalOption returns the global value of the given option
|
||||
func GetGlobalOption(name string) interface{} {
|
||||
return globalSettings[name]
|
||||
}
|
||||
|
||||
// GetLocalOption returns the local value of the given option
|
||||
func GetLocalOption(name string, buf *Buffer) interface{} {
|
||||
return buf.Settings[name]
|
||||
}
|
||||
|
||||
// GetOption returns the value of the given option
|
||||
// If there is a local version of the option, it returns that
|
||||
// otherwise it will return the global version
|
||||
func GetOption(name string) interface{} {
|
||||
if GetLocalOption(name, CurView().Buf) != nil {
|
||||
return GetLocalOption(name, CurView().Buf)
|
||||
}
|
||||
return GetGlobalOption(name)
|
||||
}
|
||||
|
||||
// DefaultGlobalSettings returns the default global settings for micro
|
||||
// Note that colorscheme is a global only option
|
||||
func DefaultGlobalSettings() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"autoindent": true,
|
||||
"keepautoindent": false,
|
||||
"autosave": false,
|
||||
"colorcolumn": float64(0),
|
||||
"colorscheme": "default",
|
||||
"cursorline": true,
|
||||
"eofnewline": false,
|
||||
"rmtrailingws": false,
|
||||
"ignorecase": false,
|
||||
"indentchar": " ",
|
||||
"infobar": true,
|
||||
"ruler": true,
|
||||
"savecursor": false,
|
||||
"saveundo": false,
|
||||
"scrollspeed": float64(2),
|
||||
"scrollmargin": float64(3),
|
||||
"softwrap": false,
|
||||
"splitRight": true,
|
||||
"splitBottom": true,
|
||||
"statusline": true,
|
||||
"syntax": true,
|
||||
"tabmovement": false,
|
||||
"tabsize": float64(4),
|
||||
"tabstospaces": false,
|
||||
"termtitle": false,
|
||||
"pluginchannels": []string{
|
||||
"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json",
|
||||
},
|
||||
"pluginrepos": []string{},
|
||||
"useprimary": true,
|
||||
"fileformat": "unix",
|
||||
"mouse": true,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultLocalSettings returns the default local settings
|
||||
// Note that filetype is a local only option
|
||||
func DefaultLocalSettings() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"autoindent": true,
|
||||
"keepautoindent": false,
|
||||
"autosave": false,
|
||||
"colorcolumn": float64(0),
|
||||
"cursorline": true,
|
||||
"eofnewline": false,
|
||||
"rmtrailingws": false,
|
||||
"filetype": "Unknown",
|
||||
"ignorecase": false,
|
||||
"indentchar": " ",
|
||||
"ruler": true,
|
||||
"savecursor": false,
|
||||
"saveundo": false,
|
||||
"scrollspeed": float64(2),
|
||||
"scrollmargin": float64(3),
|
||||
"softwrap": false,
|
||||
"splitRight": true,
|
||||
"splitBottom": true,
|
||||
"statusline": true,
|
||||
"syntax": true,
|
||||
"tabmovement": false,
|
||||
"tabsize": float64(4),
|
||||
"tabstospaces": false,
|
||||
"useprimary": true,
|
||||
"fileformat": "unix",
|
||||
"mouse": true,
|
||||
}
|
||||
}
|
||||
|
||||
// SetOption attempts to set the given option to the value
|
||||
// By default it will set the option as global, but if the option
|
||||
// is local only it will set the local version
|
||||
// Use setlocal to force an option to be set locally
|
||||
func SetOption(option, value string) error {
|
||||
if _, ok := globalSettings[option]; !ok {
|
||||
if _, ok := CurView().Buf.Settings[option]; !ok {
|
||||
return errors.New("Invalid option")
|
||||
}
|
||||
SetLocalOption(option, value, CurView())
|
||||
return nil
|
||||
}
|
||||
|
||||
var nativeValue interface{}
|
||||
|
||||
kind := reflect.TypeOf(globalSettings[option]).Kind()
|
||||
if kind == reflect.Bool {
|
||||
b, err := ParseBool(value)
|
||||
if err != nil {
|
||||
return errors.New("Invalid value")
|
||||
}
|
||||
nativeValue = b
|
||||
} else if kind == reflect.String {
|
||||
nativeValue = value
|
||||
} else if kind == reflect.Float64 {
|
||||
i, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return errors.New("Invalid value")
|
||||
}
|
||||
nativeValue = float64(i)
|
||||
} else {
|
||||
return errors.New("Option has unsupported value type")
|
||||
}
|
||||
|
||||
if err := optionIsValid(option, nativeValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
globalSettings[option] = nativeValue
|
||||
|
||||
if option == "colorscheme" {
|
||||
// LoadSyntaxFiles()
|
||||
InitColorscheme()
|
||||
for _, tab := range tabs {
|
||||
for _, view := range tab.views {
|
||||
view.Buf.UpdateRules()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if option == "infobar" {
|
||||
for _, tab := range tabs {
|
||||
tab.Resize()
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := CurView().Buf.Settings[option]; ok {
|
||||
for _, tab := range tabs {
|
||||
for _, view := range tab.views {
|
||||
SetLocalOption(option, value, view)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLocalOption sets the local version of this option
|
||||
func SetLocalOption(option, value string, view *View) error {
|
||||
buf := view.Buf
|
||||
if _, ok := buf.Settings[option]; !ok {
|
||||
return errors.New("Invalid option")
|
||||
}
|
||||
|
||||
var nativeValue interface{}
|
||||
|
||||
kind := reflect.TypeOf(buf.Settings[option]).Kind()
|
||||
if kind == reflect.Bool {
|
||||
b, err := ParseBool(value)
|
||||
if err != nil {
|
||||
return errors.New("Invalid value")
|
||||
}
|
||||
nativeValue = b
|
||||
} else if kind == reflect.String {
|
||||
nativeValue = value
|
||||
} else if kind == reflect.Float64 {
|
||||
i, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return errors.New("Invalid value")
|
||||
}
|
||||
nativeValue = float64(i)
|
||||
} else {
|
||||
return errors.New("Option has unsupported value type")
|
||||
}
|
||||
|
||||
if err := optionIsValid(option, nativeValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf.Settings[option] = nativeValue
|
||||
|
||||
if option == "statusline" {
|
||||
view.ToggleStatusLine()
|
||||
}
|
||||
|
||||
if option == "filetype" {
|
||||
// LoadSyntaxFiles()
|
||||
InitColorscheme()
|
||||
buf.UpdateRules()
|
||||
}
|
||||
|
||||
if option == "fileformat" {
|
||||
buf.IsModified = true
|
||||
}
|
||||
|
||||
if option == "syntax" {
|
||||
if !nativeValue.(bool) {
|
||||
buf.ClearMatches()
|
||||
} else {
|
||||
buf.highlighter.HighlightStates(buf)
|
||||
}
|
||||
}
|
||||
|
||||
if option == "mouse" {
|
||||
if !nativeValue.(bool) {
|
||||
screen.DisableMouse()
|
||||
} else {
|
||||
screen.EnableMouse()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetOptionAndSettings sets the given option and saves the option setting to the settings config file
|
||||
func SetOptionAndSettings(option, value string) {
|
||||
filename := configDir + "/settings.json"
|
||||
|
||||
err := SetOption(option, value)
|
||||
|
||||
if err != nil {
|
||||
messenger.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = WriteSettings(filename)
|
||||
if err != nil {
|
||||
messenger.Error("Error writing to settings.json: " + err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func optionIsValid(option string, value interface{}) error {
|
||||
if validator, ok := optionValidators[option]; ok {
|
||||
return validator(option, value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Option validators
|
||||
|
||||
func validatePositiveValue(option string, value interface{}) error {
|
||||
tabsize, ok := value.(float64)
|
||||
|
||||
if !ok {
|
||||
return errors.New("Expected numeric type for " + option)
|
||||
}
|
||||
|
||||
if tabsize < 1 {
|
||||
return errors.New(option + " must be greater than 0")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateNonNegativeValue(option string, value interface{}) error {
|
||||
nativeValue, ok := value.(float64)
|
||||
|
||||
if !ok {
|
||||
return errors.New("Expected numeric type for " + option)
|
||||
}
|
||||
|
||||
if nativeValue < 0 {
|
||||
return errors.New(option + " must be non-negative")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateColorscheme(option string, value interface{}) error {
|
||||
colorscheme, ok := value.(string)
|
||||
|
||||
if !ok {
|
||||
return errors.New("Expected string type for colorscheme")
|
||||
}
|
||||
|
||||
if !ColorschemeExists(colorscheme) {
|
||||
return errors.New(colorscheme + " is not a valid colorscheme")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateLineEnding(option string, value interface{}) error {
|
||||
endingType, ok := value.(string)
|
||||
|
||||
if !ok {
|
||||
return errors.New("Expected string type for file format")
|
||||
}
|
||||
|
||||
if endingType != "unix" && endingType != "dos" {
|
||||
return errors.New("File format must be either 'unix' or 'dos'")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
317
cmd/micro/split_tree.go
Normal file
317
cmd/micro/split_tree.go
Normal file
|
@ -0,0 +1,317 @@
|
|||
package main
|
||||
|
||||
// SplitType specifies whether a split is horizontal or vertical
|
||||
type SplitType bool
|
||||
|
||||
const (
|
||||
// VerticalSplit type
|
||||
VerticalSplit = false
|
||||
// HorizontalSplit type
|
||||
HorizontalSplit = true
|
||||
)
|
||||
|
||||
// A Node on the split tree
|
||||
type Node interface {
|
||||
VSplit(buf *Buffer, splitIndex int)
|
||||
HSplit(buf *Buffer, splitIndex int)
|
||||
String() string
|
||||
}
|
||||
|
||||
// A LeafNode is an actual split so it contains a view
|
||||
type LeafNode struct {
|
||||
view *View
|
||||
|
||||
parent *SplitTree
|
||||
}
|
||||
|
||||
// NewLeafNode returns a new leaf node containing the given view
|
||||
func NewLeafNode(v *View, parent *SplitTree) *LeafNode {
|
||||
n := new(LeafNode)
|
||||
n.view = v
|
||||
n.view.splitNode = n
|
||||
n.parent = parent
|
||||
return n
|
||||
}
|
||||
|
||||
// A SplitTree is a Node itself and it contains other nodes
|
||||
type SplitTree struct {
|
||||
kind SplitType
|
||||
|
||||
parent *SplitTree
|
||||
children []Node
|
||||
|
||||
x int
|
||||
y int
|
||||
|
||||
width int
|
||||
height int
|
||||
lockWidth bool
|
||||
lockHeight bool
|
||||
|
||||
tabNum int
|
||||
}
|
||||
|
||||
// VSplit creates a vertical split
|
||||
func (l *LeafNode) VSplit(buf *Buffer, splitIndex int) {
|
||||
if splitIndex < 0 {
|
||||
splitIndex = 0
|
||||
}
|
||||
|
||||
tab := tabs[l.parent.tabNum]
|
||||
if l.parent.kind == VerticalSplit {
|
||||
if splitIndex > len(l.parent.children) {
|
||||
splitIndex = len(l.parent.children)
|
||||
}
|
||||
|
||||
newView := NewView(buf)
|
||||
newView.TabNum = l.parent.tabNum
|
||||
|
||||
l.parent.children = append(l.parent.children, nil)
|
||||
copy(l.parent.children[splitIndex+1:], l.parent.children[splitIndex:])
|
||||
l.parent.children[splitIndex] = NewLeafNode(newView, l.parent)
|
||||
|
||||
tab.views = append(tab.views, nil)
|
||||
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
|
||||
tab.views[splitIndex] = newView
|
||||
|
||||
tab.CurView = splitIndex
|
||||
} else {
|
||||
if splitIndex > 1 {
|
||||
splitIndex = 1
|
||||
}
|
||||
|
||||
s := new(SplitTree)
|
||||
s.kind = VerticalSplit
|
||||
s.parent = l.parent
|
||||
s.tabNum = l.parent.tabNum
|
||||
newView := NewView(buf)
|
||||
newView.TabNum = l.parent.tabNum
|
||||
if splitIndex == 1 {
|
||||
s.children = []Node{l, NewLeafNode(newView, s)}
|
||||
} else {
|
||||
s.children = []Node{NewLeafNode(newView, s), l}
|
||||
}
|
||||
l.parent.children[search(l.parent.children, l)] = s
|
||||
l.parent = s
|
||||
|
||||
tab.views = append(tab.views, nil)
|
||||
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
|
||||
tab.views[splitIndex] = newView
|
||||
|
||||
tab.CurView = splitIndex
|
||||
}
|
||||
|
||||
tab.Resize()
|
||||
}
|
||||
|
||||
// HSplit creates a horizontal split
|
||||
func (l *LeafNode) HSplit(buf *Buffer, splitIndex int) {
|
||||
if splitIndex < 0 {
|
||||
splitIndex = 0
|
||||
}
|
||||
|
||||
tab := tabs[l.parent.tabNum]
|
||||
if l.parent.kind == HorizontalSplit {
|
||||
if splitIndex > len(l.parent.children) {
|
||||
splitIndex = len(l.parent.children)
|
||||
}
|
||||
|
||||
newView := NewView(buf)
|
||||
newView.TabNum = l.parent.tabNum
|
||||
|
||||
l.parent.children = append(l.parent.children, nil)
|
||||
copy(l.parent.children[splitIndex+1:], l.parent.children[splitIndex:])
|
||||
l.parent.children[splitIndex] = NewLeafNode(newView, l.parent)
|
||||
|
||||
tab.views = append(tab.views, nil)
|
||||
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
|
||||
tab.views[splitIndex] = newView
|
||||
|
||||
tab.CurView = splitIndex
|
||||
} else {
|
||||
if splitIndex > 1 {
|
||||
splitIndex = 1
|
||||
}
|
||||
|
||||
s := new(SplitTree)
|
||||
s.kind = HorizontalSplit
|
||||
s.tabNum = l.parent.tabNum
|
||||
s.parent = l.parent
|
||||
newView := NewView(buf)
|
||||
newView.TabNum = l.parent.tabNum
|
||||
newView.Num = len(tab.views)
|
||||
if splitIndex == 1 {
|
||||
s.children = []Node{l, NewLeafNode(newView, s)}
|
||||
} else {
|
||||
s.children = []Node{NewLeafNode(newView, s), l}
|
||||
}
|
||||
l.parent.children[search(l.parent.children, l)] = s
|
||||
l.parent = s
|
||||
|
||||
tab.views = append(tab.views, nil)
|
||||
copy(tab.views[splitIndex+1:], tab.views[splitIndex:])
|
||||
tab.views[splitIndex] = newView
|
||||
|
||||
tab.CurView = splitIndex
|
||||
}
|
||||
|
||||
tab.Resize()
|
||||
}
|
||||
|
||||
// Delete deletes a split
|
||||
func (l *LeafNode) Delete() {
|
||||
i := search(l.parent.children, l)
|
||||
|
||||
copy(l.parent.children[i:], l.parent.children[i+1:])
|
||||
l.parent.children[len(l.parent.children)-1] = nil
|
||||
l.parent.children = l.parent.children[:len(l.parent.children)-1]
|
||||
|
||||
tab := tabs[l.parent.tabNum]
|
||||
j := findView(tab.views, l.view)
|
||||
copy(tab.views[j:], tab.views[j+1:])
|
||||
tab.views[len(tab.views)-1] = nil // or the zero value of T
|
||||
tab.views = tab.views[:len(tab.views)-1]
|
||||
|
||||
for i, v := range tab.views {
|
||||
v.Num = i
|
||||
}
|
||||
if tab.CurView > 0 {
|
||||
tab.CurView--
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup rearranges all the parents after a split has been deleted
|
||||
func (s *SplitTree) Cleanup() {
|
||||
for i, node := range s.children {
|
||||
if n, ok := node.(*SplitTree); ok {
|
||||
if len(n.children) == 1 {
|
||||
if child, ok := n.children[0].(*LeafNode); ok {
|
||||
s.children[i] = child
|
||||
child.parent = s
|
||||
continue
|
||||
}
|
||||
}
|
||||
n.Cleanup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ResizeSplits resizes all the splits correctly
|
||||
func (s *SplitTree) ResizeSplits() {
|
||||
lockedWidth := 0
|
||||
lockedHeight := 0
|
||||
lockedChildren := 0
|
||||
for _, node := range s.children {
|
||||
if n, ok := node.(*LeafNode); ok {
|
||||
if s.kind == VerticalSplit {
|
||||
if n.view.LockWidth {
|
||||
lockedWidth += n.view.Width
|
||||
lockedChildren++
|
||||
}
|
||||
} else {
|
||||
if n.view.LockHeight {
|
||||
lockedHeight += n.view.Height
|
||||
lockedChildren++
|
||||
}
|
||||
}
|
||||
} else if n, ok := node.(*SplitTree); ok {
|
||||
if s.kind == VerticalSplit {
|
||||
if n.lockWidth {
|
||||
lockedWidth += n.width
|
||||
lockedChildren++
|
||||
}
|
||||
} else {
|
||||
if n.lockHeight {
|
||||
lockedHeight += n.height
|
||||
lockedChildren++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
x, y := 0, 0
|
||||
for _, node := range s.children {
|
||||
if n, ok := node.(*LeafNode); ok {
|
||||
if s.kind == VerticalSplit {
|
||||
if !n.view.LockWidth {
|
||||
n.view.Width = (s.width - lockedWidth) / (len(s.children) - lockedChildren)
|
||||
}
|
||||
n.view.Height = s.height
|
||||
|
||||
n.view.x = s.x + x
|
||||
n.view.y = s.y
|
||||
x += n.view.Width
|
||||
} else {
|
||||
if !n.view.LockHeight {
|
||||
n.view.Height = (s.height - lockedHeight) / (len(s.children) - lockedChildren)
|
||||
}
|
||||
n.view.Width = s.width
|
||||
|
||||
n.view.y = s.y + y
|
||||
n.view.x = s.x
|
||||
y += n.view.Height
|
||||
}
|
||||
if n.view.Buf.Settings["statusline"].(bool) {
|
||||
n.view.Height--
|
||||
}
|
||||
|
||||
n.view.ToggleTabbar()
|
||||
} else if n, ok := node.(*SplitTree); ok {
|
||||
if s.kind == VerticalSplit {
|
||||
if !n.lockWidth {
|
||||
n.width = (s.width - lockedWidth) / (len(s.children) - lockedChildren)
|
||||
}
|
||||
n.height = s.height
|
||||
|
||||
n.x = s.x + x
|
||||
n.y = s.y
|
||||
x += n.width
|
||||
} else {
|
||||
if !n.lockHeight {
|
||||
n.height = (s.height - lockedHeight) / (len(s.children) - lockedChildren)
|
||||
}
|
||||
n.width = s.width
|
||||
|
||||
n.y = s.y + y
|
||||
n.x = s.x
|
||||
y += n.height
|
||||
}
|
||||
n.ResizeSplits()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LeafNode) String() string {
|
||||
return l.view.Buf.GetName()
|
||||
}
|
||||
|
||||
func search(haystack []Node, needle Node) int {
|
||||
for i, x := range haystack {
|
||||
if x == needle {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func findView(haystack []*View, needle *View) int {
|
||||
for i, x := range haystack {
|
||||
if x == needle {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// VSplit is here just to make SplitTree fit the Node interface
|
||||
func (s *SplitTree) VSplit(buf *Buffer, splitIndex int) {}
|
||||
|
||||
// HSplit is here just to make SplitTree fit the Node interface
|
||||
func (s *SplitTree) HSplit(buf *Buffer, splitIndex int) {}
|
||||
|
||||
func (s *SplitTree) String() string {
|
||||
str := "["
|
||||
for _, child := range s.children {
|
||||
str += child.String() + ", "
|
||||
}
|
||||
return str + "]"
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package buffer
|
||||
package main
|
||||
|
||||
// TEStack is a simple implementation of a LIFO stack for text events
|
||||
type TEStack struct {
|
||||
// Stack is a simple implementation of a LIFO stack for text events
|
||||
type Stack struct {
|
||||
Top *Element
|
||||
Size int
|
||||
}
|
||||
|
@ -13,19 +13,19 @@ type Element struct {
|
|||
}
|
||||
|
||||
// Len returns the stack's length
|
||||
func (s *TEStack) Len() int {
|
||||
func (s *Stack) Len() int {
|
||||
return s.Size
|
||||
}
|
||||
|
||||
// Push a new element onto the stack
|
||||
func (s *TEStack) Push(value *TextEvent) {
|
||||
func (s *Stack) Push(value *TextEvent) {
|
||||
s.Top = &Element{value, s.Top}
|
||||
s.Size++
|
||||
}
|
||||
|
||||
// Pop removes the top element from the stack and returns its value
|
||||
// If the stack is empty, return nil
|
||||
func (s *TEStack) Pop() (value *TextEvent) {
|
||||
func (s *Stack) Pop() (value *TextEvent) {
|
||||
if s.Size > 0 {
|
||||
value, s.Top = s.Top.Value, s.Top.Next
|
||||
s.Size--
|
||||
|
@ -35,7 +35,7 @@ func (s *TEStack) Pop() (value *TextEvent) {
|
|||
}
|
||||
|
||||
// Peek returns the top element of the stack without removing it
|
||||
func (s *TEStack) Peek() *TextEvent {
|
||||
func (s *Stack) Peek() *TextEvent {
|
||||
if s.Size > 0 {
|
||||
return s.Top.Value
|
||||
}
|
70
cmd/micro/statusline.go
Normal file
70
cmd/micro/statusline.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Statusline represents the information line at the bottom
|
||||
// of each view
|
||||
// It gives information such as filename, whether the file has been
|
||||
// modified, filetype, cursor location
|
||||
type Statusline struct {
|
||||
view *View
|
||||
}
|
||||
|
||||
// Display draws the statusline to the screen
|
||||
func (sline *Statusline) Display() {
|
||||
// We'll draw the line at the lowest line in the view
|
||||
y := sline.view.Height + sline.view.y
|
||||
|
||||
file := sline.view.Buf.GetName()
|
||||
|
||||
// If the buffer is dirty (has been modified) write a little '+'
|
||||
if sline.view.Buf.IsModified {
|
||||
file += " +"
|
||||
}
|
||||
|
||||
// Add one to cursor.x and cursor.y because (0,0) is the top left,
|
||||
// but users will be used to (1,1) (first line,first column)
|
||||
// We use GetVisualX() here because otherwise we get the column number in runes
|
||||
// so a '\t' is only 1, when it should be tabSize
|
||||
columnNum := strconv.Itoa(sline.view.Cursor.GetVisualX() + 1)
|
||||
lineNum := strconv.Itoa(sline.view.Cursor.Y + 1)
|
||||
|
||||
file += " (" + lineNum + "," + columnNum + ")"
|
||||
|
||||
// Add the filetype
|
||||
file += " " + sline.view.Buf.FileType()
|
||||
|
||||
file += " " + sline.view.Buf.Settings["fileformat"].(string)
|
||||
|
||||
rightText := ""
|
||||
if len(helpBinding) > 0 {
|
||||
rightText = helpBinding + " for help "
|
||||
if sline.view.Type == vtHelp {
|
||||
rightText = helpBinding + " to close help "
|
||||
}
|
||||
}
|
||||
|
||||
statusLineStyle := defStyle.Reverse(true)
|
||||
if style, ok := colorscheme["statusline"]; ok {
|
||||
statusLineStyle = style
|
||||
}
|
||||
|
||||
// Maybe there is a unicode filename?
|
||||
fileRunes := []rune(file)
|
||||
viewX := sline.view.x
|
||||
if viewX != 0 {
|
||||
screen.SetContent(viewX, y, ' ', nil, statusLineStyle)
|
||||
viewX++
|
||||
}
|
||||
for x := 0; x < sline.view.Width; x++ {
|
||||
if x < len(fileRunes) {
|
||||
screen.SetContent(viewX+x, y, fileRunes[x], nil, statusLineStyle)
|
||||
} else if x >= sline.view.Width-len(rightText) && x < len(rightText)+sline.view.Width-len(rightText) {
|
||||
screen.SetContent(viewX+x, y, []rune(rightText)[x-sline.view.Width+len(rightText)], nil, statusLineStyle)
|
||||
} else {
|
||||
screen.SetContent(viewX+x, y, ' ', nil, statusLineStyle)
|
||||
}
|
||||
}
|
||||
}
|
265
cmd/micro/tab.go
Normal file
265
cmd/micro/tab.go
Normal file
|
@ -0,0 +1,265 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
var tabBarOffset int
|
||||
|
||||
type Tab struct {
|
||||
// This contains all the views in this tab
|
||||
// There is generally only one view per tab, but you can have
|
||||
// multiple views with splits
|
||||
views []*View
|
||||
// This is the current view for this tab
|
||||
CurView int
|
||||
|
||||
tree *SplitTree
|
||||
}
|
||||
|
||||
// NewTabFromView creates a new tab and puts the given view in the tab
|
||||
func NewTabFromView(v *View) *Tab {
|
||||
t := new(Tab)
|
||||
t.views = append(t.views, v)
|
||||
t.views[0].Num = 0
|
||||
|
||||
t.tree = new(SplitTree)
|
||||
t.tree.kind = VerticalSplit
|
||||
t.tree.children = []Node{NewLeafNode(t.views[0], t.tree)}
|
||||
|
||||
w, h := screen.Size()
|
||||
t.tree.width = w
|
||||
t.tree.height = h
|
||||
|
||||
if globalSettings["infobar"].(bool) {
|
||||
t.tree.height--
|
||||
}
|
||||
|
||||
t.Resize()
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// SetNum sets all this tab's views to have the correct tab number
|
||||
func (t *Tab) SetNum(num int) {
|
||||
t.tree.tabNum = num
|
||||
for _, v := range t.views {
|
||||
v.TabNum = num
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tab) Cleanup() {
|
||||
t.tree.Cleanup()
|
||||
}
|
||||
|
||||
func (t *Tab) Resize() {
|
||||
w, h := screen.Size()
|
||||
t.tree.width = w
|
||||
t.tree.height = h
|
||||
|
||||
if globalSettings["infobar"].(bool) {
|
||||
t.tree.height--
|
||||
}
|
||||
|
||||
t.tree.ResizeSplits()
|
||||
|
||||
for i, v := range t.views {
|
||||
v.Num = i
|
||||
}
|
||||
}
|
||||
|
||||
// CurView returns the current view
|
||||
func CurView() *View {
|
||||
curTab := tabs[curTab]
|
||||
return curTab.views[curTab.CurView]
|
||||
}
|
||||
|
||||
// TabbarString returns the string that should be displayed in the tabbar
|
||||
// It also returns a map containing which indicies correspond to which tab number
|
||||
// This is useful when we know that the mouse click has occurred at an x location
|
||||
// but need to know which tab that corresponds to to accurately change the tab
|
||||
func TabbarString() (string, map[int]int) {
|
||||
str := ""
|
||||
indicies := make(map[int]int)
|
||||
for i, t := range tabs {
|
||||
if i == curTab {
|
||||
str += "["
|
||||
} else {
|
||||
str += " "
|
||||
}
|
||||
buf := t.views[t.CurView].Buf
|
||||
str += buf.GetName()
|
||||
if buf.IsModified {
|
||||
str += " +"
|
||||
}
|
||||
if i == curTab {
|
||||
str += "]"
|
||||
} else {
|
||||
str += " "
|
||||
}
|
||||
indicies[Count(str)-1] = i + 1
|
||||
str += " "
|
||||
}
|
||||
return str, indicies
|
||||
}
|
||||
|
||||
// TabbarHandleMouseEvent checks the given mouse event if it is clicking on the tabbar
|
||||
// If it is it changes the current tab accordingly
|
||||
// This function returns true if the tab is changed
|
||||
func TabbarHandleMouseEvent(event tcell.Event) bool {
|
||||
// There is no tabbar displayed if there are less than 2 tabs
|
||||
if len(tabs) <= 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventMouse:
|
||||
button := e.Buttons()
|
||||
// Must be a left click
|
||||
if button == tcell.Button1 {
|
||||
x, y := e.Position()
|
||||
if y != 0 {
|
||||
return false
|
||||
}
|
||||
str, indicies := TabbarString()
|
||||
if x+tabBarOffset >= len(str) {
|
||||
return false
|
||||
}
|
||||
var tabnum int
|
||||
var keys []int
|
||||
for k := range indicies {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Ints(keys)
|
||||
for _, k := range keys {
|
||||
if x+tabBarOffset <= k {
|
||||
tabnum = indicies[k] - 1
|
||||
break
|
||||
}
|
||||
}
|
||||
curTab = tabnum
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// DisplayTabs displays the tabbar at the top of the editor if there are multiple tabs
|
||||
func DisplayTabs() {
|
||||
if len(tabs) <= 1 {
|
||||
return
|
||||
}
|
||||
|
||||
str, indicies := TabbarString()
|
||||
|
||||
tabBarStyle := defStyle.Reverse(true)
|
||||
if style, ok := colorscheme["tabbar"]; ok {
|
||||
tabBarStyle = style
|
||||
}
|
||||
|
||||
// Maybe there is a unicode filename?
|
||||
fileRunes := []rune(str)
|
||||
w, _ := screen.Size()
|
||||
tooWide := (w < len(fileRunes))
|
||||
|
||||
// if the entire tab-bar is longer than the screen is wide,
|
||||
// then it should be truncated appropriately to keep the
|
||||
// active tab visible on the UI.
|
||||
if tooWide == true {
|
||||
// first we have to work out where the selected tab is
|
||||
// out of the total length of the tab bar. this is done
|
||||
// by extracting the hit-areas from the indicies map
|
||||
// that was constructed by `TabbarString()`
|
||||
var keys []int
|
||||
for offset := range indicies {
|
||||
keys = append(keys, offset)
|
||||
}
|
||||
// sort them to be in ascending order so that values will
|
||||
// correctly reflect the displayed ordering of the tabs
|
||||
sort.Ints(keys)
|
||||
// record the offset of each tab and the previous tab so
|
||||
// we can find the position of the tab's hit-box.
|
||||
previousTabOffset := 0
|
||||
currentTabOffset := 0
|
||||
for _, k := range keys {
|
||||
tabIndex := indicies[k] - 1
|
||||
if tabIndex == curTab {
|
||||
currentTabOffset = k
|
||||
break
|
||||
}
|
||||
// this is +2 because there are two padding spaces that aren't accounted
|
||||
// for in the display. please note that this is for cosmetic purposes only.
|
||||
previousTabOffset = k + 2
|
||||
}
|
||||
// get the width of the hitbox of the active tab, from there calculate the offsets
|
||||
// to the left and right of it to approximately center it on the tab bar display.
|
||||
centeringOffset := (w - (currentTabOffset - previousTabOffset))
|
||||
leftBuffer := previousTabOffset - (centeringOffset / 2)
|
||||
rightBuffer := currentTabOffset + (centeringOffset / 2)
|
||||
|
||||
// check to make sure we haven't overshot the bounds of the string,
|
||||
// if we have, then take that remainder and put it on the left side
|
||||
overshotRight := rightBuffer - len(fileRunes)
|
||||
if overshotRight > 0 {
|
||||
leftBuffer = leftBuffer + overshotRight
|
||||
}
|
||||
|
||||
overshotLeft := leftBuffer - 0
|
||||
if overshotLeft < 0 {
|
||||
leftBuffer = 0
|
||||
rightBuffer = leftBuffer + (w - 1)
|
||||
} else {
|
||||
rightBuffer = leftBuffer + (w - 2)
|
||||
}
|
||||
|
||||
if rightBuffer > len(fileRunes)-1 {
|
||||
rightBuffer = len(fileRunes) - 1
|
||||
}
|
||||
|
||||
// construct a new buffer of text to put the
|
||||
// newly formatted tab bar text into.
|
||||
var displayText []rune
|
||||
|
||||
// if the left-side of the tab bar isn't at the start
|
||||
// of the constructed tab bar text, then show that are
|
||||
// more tabs to the left by displaying a "+"
|
||||
if leftBuffer != 0 {
|
||||
displayText = append(displayText, '+')
|
||||
}
|
||||
// copy the runes in from the original tab bar text string
|
||||
// into the new display buffer
|
||||
for x := leftBuffer; x < rightBuffer; x++ {
|
||||
displayText = append(displayText, fileRunes[x])
|
||||
}
|
||||
// if there is more text to the right of the right-most
|
||||
// column in the tab bar text, then indicate there are more
|
||||
// tabs to the right by displaying a "+"
|
||||
if rightBuffer < len(fileRunes)-1 {
|
||||
displayText = append(displayText, '+')
|
||||
}
|
||||
|
||||
// now store the offset from zero of the left-most text
|
||||
// that is being displayed. This is to ensure that when
|
||||
// clicking on the tab bar, the correct tab gets selected.
|
||||
tabBarOffset = leftBuffer
|
||||
|
||||
// use the constructed buffer as the display buffer to print
|
||||
// onscreen.
|
||||
fileRunes = displayText
|
||||
} else {
|
||||
tabBarOffset = 0
|
||||
}
|
||||
|
||||
// iterate over the width of the terminal display and for each column,
|
||||
// write a character into the tab display area with the appropriate style.
|
||||
for x := 0; x < w; x++ {
|
||||
if x < len(fileRunes) {
|
||||
screen.SetContent(x, 0, fileRunes[x], nil, tabBarStyle)
|
||||
} else {
|
||||
screen.SetContent(x, 0, ' ', nil, tabBarStyle)
|
||||
}
|
||||
}
|
||||
}
|
365
cmd/micro/util.go
Normal file
365
cmd/micro/util.go
Normal file
|
@ -0,0 +1,365 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// Util.go is a collection of utility functions that are used throughout
|
||||
// the program
|
||||
|
||||
// Count returns the length of a string in runes
|
||||
// This is exactly equivalent to utf8.RuneCountInString(), just less characters
|
||||
func Count(s string) int {
|
||||
return utf8.RuneCountInString(s)
|
||||
}
|
||||
|
||||
// NumOccurrences counts the number of occurrences of a byte in a string
|
||||
func NumOccurrences(s string, c byte) int {
|
||||
var n int
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == c {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Spaces returns a string with n spaces
|
||||
func Spaces(n int) string {
|
||||
return strings.Repeat(" ", n)
|
||||
}
|
||||
|
||||
// Min takes the min of two ints
|
||||
func Min(a, b int) int {
|
||||
if a > b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Max takes the max of two ints
|
||||
func Max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func FSize(f *os.File) int64 {
|
||||
fi, _ := f.Stat()
|
||||
// get the size
|
||||
return fi.Size()
|
||||
}
|
||||
|
||||
// IsWordChar returns whether or not the string is a 'word character'
|
||||
// If it is a unicode character, then it does not match
|
||||
// Word characters are defined as [A-Za-z0-9_]
|
||||
func IsWordChar(str string) bool {
|
||||
if len(str) > 1 {
|
||||
// Unicode
|
||||
return true
|
||||
}
|
||||
c := str[0]
|
||||
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
|
||||
}
|
||||
|
||||
// IsWhitespace returns true if the given rune is a space, tab, or newline
|
||||
func IsWhitespace(c rune) bool {
|
||||
return c == ' ' || c == '\t' || c == '\n'
|
||||
}
|
||||
|
||||
// IsStrWhitespace returns true if the given string is all whitespace
|
||||
func IsStrWhitespace(str string) bool {
|
||||
for _, c := range str {
|
||||
if !IsWhitespace(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Contains returns whether or not a string array contains a given string
|
||||
func Contains(list []string, a string) bool {
|
||||
for _, b := range list {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Insert makes a simple insert into a string at the given position
|
||||
func Insert(str string, pos int, value string) string {
|
||||
return string([]rune(str)[:pos]) + value + string([]rune(str)[pos:])
|
||||
}
|
||||
|
||||
// MakeRelative will attempt to make a relative path between path and base
|
||||
func MakeRelative(path, base string) (string, error) {
|
||||
if len(path) > 0 {
|
||||
rel, err := filepath.Rel(base, path)
|
||||
if err != nil {
|
||||
return path, err
|
||||
}
|
||||
return rel, nil
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// GetLeadingWhitespace returns the leading whitespace of the given string
|
||||
func GetLeadingWhitespace(str string) string {
|
||||
ws := ""
|
||||
for _, c := range str {
|
||||
if c == ' ' || c == '\t' {
|
||||
ws += string(c)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return ws
|
||||
}
|
||||
|
||||
// IsSpaces checks if a given string is only spaces
|
||||
func IsSpaces(str string) bool {
|
||||
for _, c := range str {
|
||||
if c != ' ' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsSpacesOrTabs checks if a given string contains only spaces and tabs
|
||||
func IsSpacesOrTabs(str string) bool {
|
||||
for _, c := range str {
|
||||
if c != ' ' && c != '\t' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ParseBool is almost exactly like strconv.ParseBool, except it also accepts 'on' and 'off'
|
||||
// as 'true' and 'false' respectively
|
||||
func ParseBool(str string) (bool, error) {
|
||||
if str == "on" {
|
||||
return true, nil
|
||||
}
|
||||
if str == "off" {
|
||||
return false, nil
|
||||
}
|
||||
return strconv.ParseBool(str)
|
||||
}
|
||||
|
||||
// EscapePath replaces every path separator in a given path with a %
|
||||
func EscapePath(path string) string {
|
||||
path = filepath.ToSlash(path)
|
||||
return strings.Replace(path, "/", "%", -1)
|
||||
}
|
||||
|
||||
// GetModTime returns the last modification time for a given file
|
||||
// It also returns a boolean if there was a problem accessing the file
|
||||
func GetModTime(path string) (time.Time, bool) {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return time.Now(), false
|
||||
}
|
||||
return info.ModTime(), true
|
||||
}
|
||||
|
||||
// StringWidth returns the width of a string where tabs count as `tabsize` width
|
||||
func StringWidth(str string, tabsize int) int {
|
||||
sw := runewidth.StringWidth(str)
|
||||
lineIdx := 0
|
||||
for _, ch := range str {
|
||||
switch ch {
|
||||
case '\t':
|
||||
ts := tabsize - (lineIdx % tabsize)
|
||||
sw += ts
|
||||
lineIdx += ts
|
||||
case '\n':
|
||||
lineIdx = 0
|
||||
default:
|
||||
lineIdx++
|
||||
}
|
||||
}
|
||||
return sw
|
||||
}
|
||||
|
||||
// WidthOfLargeRunes searches all the runes in a string and counts up all the widths of runes
|
||||
// that have a width larger than 1 (this also counts tabs as `tabsize` width)
|
||||
func WidthOfLargeRunes(str string, tabsize int) int {
|
||||
count := 0
|
||||
lineIdx := 0
|
||||
for _, ch := range str {
|
||||
var w int
|
||||
if ch == '\t' {
|
||||
w = tabsize - (lineIdx % tabsize)
|
||||
} else {
|
||||
w = runewidth.RuneWidth(ch)
|
||||
}
|
||||
if w > 1 {
|
||||
count += (w - 1)
|
||||
}
|
||||
if ch == '\n' {
|
||||
lineIdx = 0
|
||||
} else {
|
||||
lineIdx += w
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// RunePos returns the rune index of a given byte index
|
||||
// This could cause problems if the byte index is between code points
|
||||
func runePos(p int, str string) int {
|
||||
return utf8.RuneCountInString(str[:p])
|
||||
}
|
||||
|
||||
func lcs(a, b string) string {
|
||||
arunes := []rune(a)
|
||||
brunes := []rune(b)
|
||||
|
||||
lcs := ""
|
||||
for i, r := range arunes {
|
||||
if i >= len(brunes) {
|
||||
break
|
||||
}
|
||||
if r == brunes[i] {
|
||||
lcs += string(r)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return lcs
|
||||
}
|
||||
|
||||
func CommonSubstring(arr ...string) string {
|
||||
commonStr := arr[0]
|
||||
|
||||
for _, str := range arr[1:] {
|
||||
commonStr = lcs(commonStr, str)
|
||||
}
|
||||
|
||||
return commonStr
|
||||
}
|
||||
|
||||
// Abs is a simple absolute value function for ints
|
||||
func Abs(n int) int {
|
||||
if n < 0 {
|
||||
return -n
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// FuncName returns the full name of a given function object
|
||||
func FuncName(i interface{}) string {
|
||||
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
|
||||
}
|
||||
|
||||
// ShortFuncName returns the name only of a given function object
|
||||
func ShortFuncName(i interface{}) string {
|
||||
return strings.TrimPrefix(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name(), "main.(*View).")
|
||||
}
|
||||
|
||||
// SplitCommandArgs separates multiple command arguments which may be quoted.
|
||||
// The returned slice contains at least one string
|
||||
func SplitCommandArgs(input string) []string {
|
||||
var result []string
|
||||
var curQuote *bytes.Buffer
|
||||
|
||||
curArg := new(bytes.Buffer)
|
||||
escape := false
|
||||
|
||||
finishQuote := func() {
|
||||
if curQuote == nil {
|
||||
return
|
||||
}
|
||||
str := curQuote.String()
|
||||
if unquoted, err := strconv.Unquote(str); err == nil {
|
||||
str = unquoted
|
||||
}
|
||||
curArg.WriteString(str)
|
||||
curQuote = nil
|
||||
}
|
||||
|
||||
appendResult := func() {
|
||||
finishQuote()
|
||||
escape = false
|
||||
|
||||
str := curArg.String()
|
||||
result = append(result, str)
|
||||
curArg.Reset()
|
||||
}
|
||||
|
||||
for _, r := range input {
|
||||
if r == ' ' && curQuote == nil {
|
||||
appendResult()
|
||||
} else {
|
||||
runeHandled := false
|
||||
appendRuneToBuff := func() {
|
||||
if curQuote != nil {
|
||||
curQuote.WriteRune(r)
|
||||
} else {
|
||||
curArg.WriteRune(r)
|
||||
}
|
||||
runeHandled = true
|
||||
}
|
||||
|
||||
if r == '"' && curQuote == nil {
|
||||
curQuote = new(bytes.Buffer)
|
||||
appendRuneToBuff()
|
||||
} else {
|
||||
if curQuote != nil && !escape {
|
||||
if r == '"' {
|
||||
appendRuneToBuff()
|
||||
finishQuote()
|
||||
} else if r == '\\' {
|
||||
appendRuneToBuff()
|
||||
escape = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if !runeHandled {
|
||||
appendRuneToBuff()
|
||||
}
|
||||
}
|
||||
|
||||
escape = false
|
||||
}
|
||||
appendResult()
|
||||
return result
|
||||
}
|
||||
|
||||
// JoinCommandArgs joins multiple command arguments and quote the strings if needed.
|
||||
func JoinCommandArgs(args ...string) string {
|
||||
buf := new(bytes.Buffer)
|
||||
first := true
|
||||
for _, arg := range args {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
buf.WriteRune(' ')
|
||||
}
|
||||
quoted := strconv.Quote(arg)
|
||||
if quoted[1:len(quoted)-1] != arg || strings.ContainsRune(arg, ' ') {
|
||||
buf.WriteString(quoted)
|
||||
} else {
|
||||
buf.WriteString(arg)
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
156
cmd/micro/util_test.go
Normal file
156
cmd/micro/util_test.go
Normal file
|
@ -0,0 +1,156 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNumOccurences(t *testing.T) {
|
||||
var tests = []struct {
|
||||
inputStr string
|
||||
inputChar byte
|
||||
want int
|
||||
}{
|
||||
{"aaaa", 'a', 4},
|
||||
{"\trfd\ta", '\t', 2},
|
||||
{"∆ƒ\tø ® \t\t", '\t', 3},
|
||||
}
|
||||
for _, test := range tests {
|
||||
if got := NumOccurrences(test.inputStr, test.inputChar); got != test.want {
|
||||
t.Errorf("NumOccurences(%s, %c) = %d", test.inputStr, test.inputChar, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpaces(t *testing.T) {
|
||||
var tests = []struct {
|
||||
input int
|
||||
want string
|
||||
}{
|
||||
{4, " "},
|
||||
{0, ""},
|
||||
}
|
||||
for _, test := range tests {
|
||||
if got := Spaces(test.input); got != test.want {
|
||||
t.Errorf("Spaces(%d) = \"%s\"", test.input, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsWordChar(t *testing.T) {
|
||||
if IsWordChar("t") == false {
|
||||
t.Errorf("IsWordChar(t) = false")
|
||||
}
|
||||
if IsWordChar("T") == false {
|
||||
t.Errorf("IsWordChar(T) = false")
|
||||
}
|
||||
if IsWordChar("5") == false {
|
||||
t.Errorf("IsWordChar(5) = false")
|
||||
}
|
||||
if IsWordChar("_") == false {
|
||||
t.Errorf("IsWordChar(_) = false")
|
||||
}
|
||||
if IsWordChar("ß") == false {
|
||||
t.Errorf("IsWordChar(ß) = false")
|
||||
}
|
||||
if IsWordChar("~") == true {
|
||||
t.Errorf("IsWordChar(~) = true")
|
||||
}
|
||||
if IsWordChar(" ") == true {
|
||||
t.Errorf("IsWordChar( ) = true")
|
||||
}
|
||||
if IsWordChar(")") == true {
|
||||
t.Errorf("IsWordChar()) = true")
|
||||
}
|
||||
if IsWordChar("\n") == true {
|
||||
t.Errorf("IsWordChar(\n)) = true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJoinAndSplitCommandArgs(t *testing.T) {
|
||||
tests := []struct {
|
||||
Query []string
|
||||
Wanted string
|
||||
}{
|
||||
{[]string{`test case`}, `"test case"`},
|
||||
{[]string{`quote "test"`}, `"quote \"test\""`},
|
||||
{[]string{`slash\\\ test`}, `"slash\\\\\\ test"`},
|
||||
{[]string{`path 1`, `path\" 2`}, `"path 1" "path\\\" 2"`},
|
||||
{[]string{`foo`}, `foo`},
|
||||
{[]string{`foo\"bar`}, `"foo\\\"bar"`},
|
||||
{[]string{``}, ``},
|
||||
{[]string{`"`}, `"\""`},
|
||||
{[]string{`a`, ``}, `a `},
|
||||
{[]string{``, ``, ``, ``}, ` `},
|
||||
{[]string{"\n"}, `"\n"`},
|
||||
{[]string{"foo\tbar"}, `"foo\tbar"`},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
if result := JoinCommandArgs(test.Query...); test.Wanted != result {
|
||||
t.Errorf("JoinCommandArgs failed at Test %d\nGot: %q", i, result)
|
||||
}
|
||||
|
||||
if result := SplitCommandArgs(test.Wanted); !reflect.DeepEqual(test.Query, result) {
|
||||
t.Errorf("SplitCommandArgs failed at Test %d\nGot: `%q`", i, result)
|
||||
}
|
||||
}
|
||||
|
||||
splitTests := []struct {
|
||||
Query string
|
||||
Wanted []string
|
||||
}{
|
||||
{`"hallo""Welt"`, []string{`halloWelt`}},
|
||||
{`"hallo" "Welt"`, []string{`hallo`, `Welt`}},
|
||||
{`\"`, []string{`\"`}},
|
||||
{`"foo`, []string{`"foo`}},
|
||||
{`"foo"`, []string{`foo`}},
|
||||
{`"\"`, []string{`"\"`}},
|
||||
{`"C:\\"foo.txt`, []string{`C:\foo.txt`}},
|
||||
{`"\n"new"\n"line`, []string{"\nnew\nline"}},
|
||||
}
|
||||
|
||||
for i, test := range splitTests {
|
||||
if result := SplitCommandArgs(test.Query); !reflect.DeepEqual(test.Wanted, result) {
|
||||
t.Errorf("SplitCommandArgs failed at Split-Test %d\nGot: `%q`", i, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringWidth(t *testing.T) {
|
||||
tabsize := 4
|
||||
if w := StringWidth("1\t2", tabsize); w != 5 {
|
||||
t.Error("StringWidth 1 Failed. Got", w)
|
||||
}
|
||||
if w := StringWidth("\t", tabsize); w != 4 {
|
||||
t.Error("StringWidth 2 Failed. Got", w)
|
||||
}
|
||||
if w := StringWidth("1\t", tabsize); w != 4 {
|
||||
t.Error("StringWidth 3 Failed. Got", w)
|
||||
}
|
||||
if w := StringWidth("\t\t", tabsize); w != 8 {
|
||||
t.Error("StringWidth 4 Failed. Got", w)
|
||||
}
|
||||
if w := StringWidth("12\t2\t", tabsize); w != 8 {
|
||||
t.Error("StringWidth 5 Failed. Got", w)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWidthOfLargeRunes(t *testing.T) {
|
||||
tabsize := 4
|
||||
if w := WidthOfLargeRunes("1\t2", tabsize); w != 2 {
|
||||
t.Error("WidthOfLargeRunes 1 Failed. Got", w)
|
||||
}
|
||||
if w := WidthOfLargeRunes("\t", tabsize); w != 3 {
|
||||
t.Error("WidthOfLargeRunes 2 Failed. Got", w)
|
||||
}
|
||||
if w := WidthOfLargeRunes("1\t", tabsize); w != 2 {
|
||||
t.Error("WidthOfLargeRunes 3 Failed. Got", w)
|
||||
}
|
||||
if w := WidthOfLargeRunes("\t\t", tabsize); w != 6 {
|
||||
t.Error("WidthOfLargeRunes 4 Failed. Got", w)
|
||||
}
|
||||
if w := WidthOfLargeRunes("12\t2\t", tabsize); w != 3 {
|
||||
t.Error("WidthOfLargeRunes 5 Failed. Got", w)
|
||||
}
|
||||
}
|
1
cmd/micro/vendor/github.com/blang/semver
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/blang/semver
generated
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 4a1e882c79dcf4ec00d2e29fac74b9c8938d5052
|
1
cmd/micro/vendor/github.com/dustin/go-humanize
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/dustin/go-humanize
generated
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 259d2a102b871d17f30e3cd9881a642961a1e486
|
1
cmd/micro/vendor/github.com/flynn/json5
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/flynn/json5
generated
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 7620272ed63390e979cf5882d2fa0506fe2a8db5
|
1
cmd/micro/vendor/github.com/gdamore/encoding
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/gdamore/encoding
generated
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit b23993cbb6353f0e6aa98d0ee318a34728f628b9
|
1
cmd/micro/vendor/github.com/go-errors/errors
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/go-errors/errors
generated
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 8fa88b06e5974e97fbf9899a7f86a344bfd1f105
|
1
cmd/micro/vendor/github.com/lucasb-eyer/go-colorful
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/lucasb-eyer/go-colorful
generated
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit c900de9dbbc73129068f5af6a823068fc5f2308c
|
1
cmd/micro/vendor/github.com/mattn/go-isatty
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/mattn/go-isatty
generated
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit fc9e8d8ef48496124e79ae0df75490096eccf6fe
|
1
cmd/micro/vendor/github.com/mattn/go-runewidth
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/mattn/go-runewidth
generated
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 97311d9f7767e3d6f422ea06661bc2c7a19e8a5d
|
1
cmd/micro/vendor/github.com/mitchellh/go-homedir
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/mitchellh/go-homedir
generated
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit b8bc1bf767474819792c23f32d8286a45736f1c6
|
1
cmd/micro/vendor/github.com/sergi/go-diff
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/sergi/go-diff
generated
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit feef008d51ad2b3778f85d387ccf91735543008d
|
1
cmd/micro/vendor/github.com/yuin/gopher-lua
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/yuin/gopher-lua
generated
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit b402f3114ec730d8bddb074a6c137309f561aa78
|
1
cmd/micro/vendor/github.com/zyedidia/clipboard
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/zyedidia/clipboard
generated
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit adacf416cec40266b051e7bc096c52951f2725e9
|
1
cmd/micro/vendor/github.com/zyedidia/glob
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/zyedidia/glob
generated
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit dd4023a66dc351ae26e592d21cd133b5b143f3d8
|
1
cmd/micro/vendor/github.com/zyedidia/poller
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/zyedidia/poller
generated
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit ab09682913b79f402713d1df1977dedc19eb25ac
|
1
cmd/micro/vendor/github.com/zyedidia/tcell
generated
vendored
Submodule
1
cmd/micro/vendor/github.com/zyedidia/tcell
generated
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 37b78458fe9cbcbdd74fc579537aa13f50eb9f2f
|
1
cmd/micro/vendor/golang.org/x/net
generated
vendored
Submodule
1
cmd/micro/vendor/golang.org/x/net
generated
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 1a68b1313cf4ad7778376e82641197b60c02f65c
|
1
cmd/micro/vendor/golang.org/x/text
generated
vendored
Submodule
1
cmd/micro/vendor/golang.org/x/text
generated
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 210eee5cf7323015d097341bcf7166130d001cd8
|
1
cmd/micro/vendor/gopkg.in/yaml.v2
generated
vendored
Submodule
1
cmd/micro/vendor/gopkg.in/yaml.v2
generated
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b
|
1
cmd/micro/vendor/layeh.com/gopher-luar
generated
vendored
Submodule
1
cmd/micro/vendor/layeh.com/gopher-luar
generated
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 16281577dff29557258daa8ca9bc9b2e05f451f2
|
1008
cmd/micro/view.go
Normal file
1008
cmd/micro/view.go
Normal file
File diff suppressed because it is too large
Load diff
27
data/com.github.zyedidia.micro.metainfo.xml
Normal file
27
data/com.github.zyedidia.micro.metainfo.xml
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component>
|
||||
<id>com.github.zyedidia.micro</id>
|
||||
<name>Micro Text Editor</name>
|
||||
<summary>A modern and intuitive terminal-based text editor</summary>
|
||||
<url type="homepage">https://micro-editor.github.io</url>
|
||||
<url type="bugtracker">https://github.com/zyedidia/micro</url>
|
||||
<metadata_license>MIT</metadata_license>
|
||||
<categories>
|
||||
<category>Development</category>
|
||||
<category>TextEditor</category>
|
||||
</categories>
|
||||
|
||||
<provides>
|
||||
<binary>micro</binary>
|
||||
</provides>
|
||||
<releases>
|
||||
<release version="1.2.0" date="2017-05-28" />
|
||||
</releases>
|
||||
<developer_name>Zachary Yedidia</developer_name>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>Micro Text Editor editing its source code.</caption>
|
||||
<image type="source">https://raw.githubusercontent.com/zyedidia/micro/master/assets/micro-solarized.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
</component>
|
|
@ -1,51 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>io.github.zyedidia.micro</id>
|
||||
<launchable type="desktop-id">micro.desktop</launchable>
|
||||
<name>Micro Text Editor</name>
|
||||
<summary>A modern and intuitive terminal-based text editor</summary>
|
||||
<description>
|
||||
<p>
|
||||
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!
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</description>
|
||||
<metadata_license>MIT</metadata_license>
|
||||
<project_license>MIT</project_license>
|
||||
<categories>
|
||||
<category>Development</category>
|
||||
<category>TextEditor</category>
|
||||
</categories>
|
||||
<releases>
|
||||
<release version="2.0.13" date="2023-10-22"/>
|
||||
<release version="2.0.12" date="2023-09-06"/>
|
||||
<release version="2.0.11" date="2022-08-01"/>
|
||||
</releases>
|
||||
<provides>
|
||||
<binary>micro</binary>
|
||||
<id>com.github.zyedidia.micro</id>
|
||||
</provides>
|
||||
<developer_name>Zachary Yedidia</developer_name>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>Micro Text Editor editing its source code</caption>
|
||||
<image type="source">https://raw.githubusercontent.com/zyedidia/micro/master/assets/micro-solarized.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.1" />
|
||||
<url type="homepage">https://micro-editor.github.io</url>
|
||||
<url type="bugtracker">https://github.com/zyedidia/micro/issues</url>
|
||||
<url type="faq">https://micro-editor.github.io/about.html</url>
|
||||
<url type="help">https://micro-editor.github.io/about.html</url>
|
||||
<url type="contact">https://github.com/zyedidia</url>
|
||||
<url type="vcs-browser">https://github.com/zyedidia/micro</url>
|
||||
<url type="contribute">https://github.com/zyedidia/micro#contributing</url>
|
||||
</component>
|
367
data/micro.json
367
data/micro.json
|
@ -1,367 +0,0 @@
|
|||
{
|
||||
"$comment": "https://github.com/zyedidia/micro",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "options",
|
||||
"description": "A micro editor config schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"autoindent": {
|
||||
"description": "Whether to use the same indentation as a previous line\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"autosave": {
|
||||
"description": "A delay between automatic saves\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"default": 0
|
||||
},
|
||||
"autosu": {
|
||||
"description": "Whether attempt to use super user privileges\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"backup": {
|
||||
"description": "Whether to backup all open buffers\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"backupdir": {
|
||||
"description": "A directory to store backups\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"basename": {
|
||||
"description": "Whether to show a basename instead of a full path\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"clipboard": {
|
||||
"description": "A way to access the system clipboard\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"external",
|
||||
"terminal",
|
||||
"internal"
|
||||
],
|
||||
"default": "external"
|
||||
},
|
||||
"colorcolumn": {
|
||||
"description": "A position to display a column\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"default": 0
|
||||
},
|
||||
"colorscheme": {
|
||||
"description": "A color scheme\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"atom-dark",
|
||||
"bubblegum",
|
||||
"cmc-16",
|
||||
"cmc-tc",
|
||||
"darcula",
|
||||
"default",
|
||||
"dracula-tc",
|
||||
"dukedark-tc",
|
||||
"dukelight-tc",
|
||||
"dukeubuntu-tc",
|
||||
"geany",
|
||||
"gotham",
|
||||
"gruvbox",
|
||||
"gruvbox-tc",
|
||||
"material-tc",
|
||||
"monokai-dark",
|
||||
"monokai",
|
||||
"one-dark",
|
||||
"railscast",
|
||||
"simple",
|
||||
"solarized",
|
||||
"solarized-tc",
|
||||
"sunny-day",
|
||||
"twilight",
|
||||
"zenburn"
|
||||
],
|
||||
"default": "default"
|
||||
},
|
||||
"cursorline": {
|
||||
"description": "Whether to highlight a line with a cursor with a different color\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"diffgutter": {
|
||||
"description": "Whether to display diff inticators before lines\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"divchars": {
|
||||
"description": "Divider chars for vertical and horizontal splits\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string",
|
||||
"default": "|-"
|
||||
},
|
||||
"divreverse": {
|
||||
"description": "Whether to use inversed color scheme colors for splits\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"encoding": {
|
||||
"description": "An encoding used to open and save files\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string",
|
||||
"default": "utf-8"
|
||||
},
|
||||
"eofnewline": {
|
||||
"description": "Whether to add a missing trailing new line\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"fastdirty": {
|
||||
"description": "Whether to use a fast algorithm to determine whether a file is changed\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"fileformat": {
|
||||
"description": "A line ending format\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"unix",
|
||||
"dos"
|
||||
],
|
||||
"default": "unix"
|
||||
},
|
||||
"filetype": {
|
||||
"description": "A filetype for the current buffer\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string",
|
||||
"default": "unknown"
|
||||
},
|
||||
"hlsearch": {
|
||||
"description": "Whether to highlight all instances of a searched text after a successful search\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"incsearch": {
|
||||
"description": "Whether to enable an incremental search in `Find` prompt\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"ignorecase": {
|
||||
"description": "Whether to perform case-insensitive searches\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"indentchar": {
|
||||
"description": "An indentation character\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string",
|
||||
"maxLength": 1,
|
||||
"default": " "
|
||||
},
|
||||
"infobar": {
|
||||
"description": "Whether to enable a line at the bottom where messages are printed\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"keepautoindent": {
|
||||
"description": "Whether add a whitespace while using autoindent\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"keymenu": {
|
||||
"description": "Whether to display nano-style key menu at the bottom\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"matchbrace": {
|
||||
"description": "Whether to show matching braces\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"matchbracestyle": {
|
||||
"description": "Whether to underline or highlight matching braces\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"underline",
|
||||
"highlight"
|
||||
],
|
||||
"default": "underline"
|
||||
},
|
||||
"mkparents": {
|
||||
"description": "Whether to create missing directories\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"mouse": {
|
||||
"description": "Whether to enable mouse support\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"paste": {
|
||||
"description": "Whether to treat characters sent from the terminal in a single chunk as a paste event\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"parsecursor": {
|
||||
"description": "Whether to extract a line number and a column to open files with from file names\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"permbackup": {
|
||||
"description": "Whether to permanently save backups\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"pluginchannels": {
|
||||
"description": "A file with list of plugin channels\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string",
|
||||
"default": "https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"
|
||||
},
|
||||
"pluginrepos": {
|
||||
"description": "Plugin repositories\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"description": "A pluging repository\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"readonly": {
|
||||
"description": "Whether to forbid buffer editing\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"rmtrailingws": {
|
||||
"description": "Whether to remove trailing whitespaces\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"ruler": {
|
||||
"description": "Whether to display line numbers\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"relativeruler": {
|
||||
"description": "Whether to display relative line numbers\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"savecursor": {
|
||||
"description": "Whether to save cursor position in files\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"savehistory": {
|
||||
"description": "Whether to save command history between closing and re-opening editor\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"saveundo": {
|
||||
"description": "Whether to save undo after closing file\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"scrollbar": {
|
||||
"description": "Whether to save undo after closing file\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"scrollmargin": {
|
||||
"description": "A margin at which a view starts scrolling when a cursor approaches an edge of a view\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "integer",
|
||||
"default": 3
|
||||
},
|
||||
"scrollspeed": {
|
||||
"description": "Line count to scroll for one scroll event\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "integer",
|
||||
"default": 2
|
||||
},
|
||||
"smartpaste": {
|
||||
"description": "Whether to add a leading whitespace while pasting multiple lines\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"softwrap": {
|
||||
"description": "Whether to wrap long lines\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"splitbottom": {
|
||||
"description": "Whether to create a new horizontal split below the current one\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"splitright": {
|
||||
"description": "Whether to create a new vertical split right of the current one\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"statusformatl": {
|
||||
"description": "Format string of left-justified part of the statusline\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string",
|
||||
"default": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)"
|
||||
},
|
||||
"statusformatr": {
|
||||
"description": "Format string of right-justified part of the statusline\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string",
|
||||
"default": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help"
|
||||
},
|
||||
"statusline": {
|
||||
"description": "Whether to display a status line\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string",
|
||||
"default": "sudo"
|
||||
},
|
||||
"sucmd": {
|
||||
"description": "A super user command\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string",
|
||||
"default": "sudo",
|
||||
"examples": [
|
||||
"sudo",
|
||||
"doas"
|
||||
]
|
||||
},
|
||||
"syntax": {
|
||||
"description": "Whether to enable a syntax highlighting\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"tabmovement": {
|
||||
"description": "Whether to navigate spaces at the beginning of lines as if they are tabs\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"tabhighlight": {
|
||||
"description": "Whether to invert tab character colors\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"tabreverse": {
|
||||
"description": "Whether to reverse tab bar colors\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"tabsize": {
|
||||
"description": "A tab size\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "integer",
|
||||
"default": 4
|
||||
},
|
||||
"tabstospaces": {
|
||||
"description": "Whether to use spaces instead of tabs\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"useprimary": {
|
||||
"description": "Whether to use primary clipboard to copy selections in the background\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"wordwrap": {
|
||||
"description": "Whether to wrap long lines by words\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"xterm": {
|
||||
"description": "Whether to assume that the current terminal is `xterm`\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
30
go.mod
30
go.mod
|
@ -1,30 +0,0 @@
|
|||
module github.com/zyedidia/micro/v2
|
||||
|
||||
require (
|
||||
github.com/blang/semver v3.5.1+incompatible
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/go-errors/errors v1.0.1
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/mattn/go-isatty v0.0.11
|
||||
github.com/mattn/go-runewidth v0.0.7
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/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/clipper v0.1.1
|
||||
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3
|
||||
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d
|
||||
github.com/zyedidia/tcell/v2 v2.0.10 // indirect
|
||||
github.com/zyedidia/terminal v0.0.0-20230315200948-4b3bcf6dddef
|
||||
golang.org/x/text v0.3.8
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
layeh.com/gopher-luar v1.0.7
|
||||
)
|
||||
|
||||
replace github.com/kballard/go-shellquote => github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655
|
||||
|
||||
replace github.com/mattn/go-runewidth => github.com/zyedidia/go-runewidth v0.0.12
|
||||
|
||||
replace layeh.com/gopher-luar => github.com/layeh/gopher-luar v1.0.7
|
||||
|
||||
go 1.16
|
103
go.sum
103
go.sum
|
@ -1,103 +0,0 @@
|
|||
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/layeh/gopher-luar v1.0.7 h1:wnfZhYiJM748y1A4qYBfcFeMY9HWbdERny+ZL0f/jWc=
|
||||
github.com/layeh/gopher-luar v1.0.7/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
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/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/xo/terminfo v0.0.0-20200218205459-454e5b68f9e8 h1:woqigIZtZUZxws1zZA99nAvuz2mQrxtWsuZSR9c8I/A=
|
||||
github.com/xo/terminfo v0.0.0-20200218205459-454e5b68f9e8/go.mod h1:6Yhx5ZJl5942QrNRWLwITArVT9okUXc5c3brgWJMoDc=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
|
||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||
github.com/zyedidia/clipper v0.1.1 h1:HBgguFNDq/QmSQKBnhy4sMKzILINr139VEgAhftOUTw=
|
||||
github.com/zyedidia/clipper v0.1.1/go.mod h1:7YApPNiiTZTXdKKZG92G50qj6mnWEX975Sdu65J7YpQ=
|
||||
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/go-runewidth v0.0.12 h1:aHWj8qL3aH7caRzoPBJXe1pEaZBXHpKtfTuiBo5p74Q=
|
||||
github.com/zyedidia/go-runewidth v0.0.12/go.mod h1:vF8djYdLmG8BJaUZ4CznFYCJ3pFR8m4B4VinTvTTarU=
|
||||
github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655 h1:Z3RhH6hvcSx7eX6Q/pP6YVsgea/1eMDG99vtWwi3nK4=
|
||||
github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655/go.mod h1:1sTqqO+kcYzZp43M5VsJe1tns9IzlSeC9jB6c2+o/5Y=
|
||||
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d h1:zmDMkh22zXOB7gz8jFaI4GpI7llsPgzm38/jG0UgxjE=
|
||||
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d/go.mod h1:NDJSTTYWivnza6zkRapeX2/LwhKPEMQ7bJxqgDVT78I=
|
||||
github.com/zyedidia/poller v1.0.1 h1:Tt9S3AxAjXwWGNiC2TUdRJkQDZSzCBNVQ4xXiQ7440s=
|
||||
github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
|
||||
github.com/zyedidia/tcell/v2 v2.0.9 h1:FxXRkE62N0GPHES7EMLtp2rteYqC9r1kVid8vJN1kOE=
|
||||
github.com/zyedidia/tcell/v2 v2.0.9/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
|
||||
github.com/zyedidia/tcell/v2 v2.0.10-0.20221007181625-f562052bccb8 h1:53ULv4mmLyQDnqbjVxanckP57WSreWHwTmlLJrJEutY=
|
||||
github.com/zyedidia/tcell/v2 v2.0.10-0.20221007181625-f562052bccb8/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
|
||||
github.com/zyedidia/tcell/v2 v2.0.10-0.20230320201625-54f6acdada4a h1:W4TWa++Wk6uRGxZoxr2nPX1TpIEl+Wxv0mTtocG4TYc=
|
||||
github.com/zyedidia/tcell/v2 v2.0.10-0.20230320201625-54f6acdada4a/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
|
||||
github.com/zyedidia/tcell/v2 v2.0.10-0.20230831153116-061c5b2c7260 h1:SCAmAacT5BxZsmOFdFy5zwwi6nj1MjA60gydjKdTgXo=
|
||||
github.com/zyedidia/tcell/v2 v2.0.10-0.20230831153116-061c5b2c7260/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
|
||||
github.com/zyedidia/tcell/v2 v2.0.10 h1:6fbbYAx/DYc9A//4jU1OeBrxtc9qJxYCZXCtGQbtTWU=
|
||||
github.com/zyedidia/tcell/v2 v2.0.10/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
|
||||
github.com/zyedidia/terminal v0.0.0-20230315200948-4b3bcf6dddef h1:LeB4Qs0Tss4r/Qh8pfsTTqagDYHysfKJLYzAH3MVfu0=
|
||||
github.com/zyedidia/terminal v0.0.0-20230315200948-4b3bcf6dddef/go.mod h1:zeb8MJdcCObFKVvur3n2B4BANIPuo2Q8r4iiNs9Enx0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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/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.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
File diff suppressed because it is too large
Load diff
|
@ -1,8 +0,0 @@
|
|||
// +build plan9 nacl windows
|
||||
|
||||
package action
|
||||
|
||||
func (*BufPane) Suspend() bool {
|
||||
InfoBar.Error("Suspend is only supported on BSD/Linux")
|
||||
return false
|
||||
}
|
|
@ -1,514 +0,0 @@
|
|||
package action
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/zyedidia/json5"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
var Binder = map[string]func(e Event, action string){
|
||||
"command": InfoMapEvent,
|
||||
"buffer": BufMapEvent,
|
||||
"terminal": TermMapEvent,
|
||||
}
|
||||
|
||||
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() {
|
||||
var parsed map[string]interface{}
|
||||
|
||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||
createBindingsIfNotExist(filename)
|
||||
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error reading bindings.json file: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = json5.Unmarshal(input, &parsed)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error reading bindings.json:", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
for p, bind := range Binder {
|
||||
defaults := DefaultBindings(p)
|
||||
|
||||
for k, v := range defaults {
|
||||
BindKey(k, v, bind)
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range parsed {
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
BindKey(k, val, Binder["buffer"])
|
||||
case map[string]interface{}:
|
||||
bind, ok := Binder[k]
|
||||
if !ok || bind == nil {
|
||||
screen.TermMessage(fmt.Sprintf("%s is not a valid pane type", k))
|
||||
continue
|
||||
}
|
||||
for e, a := range val {
|
||||
s, ok := a.(string)
|
||||
if !ok {
|
||||
screen.TermMessage("Error reading bindings.json: non-string and non-map entry", k)
|
||||
} else {
|
||||
BindKey(e, s, bind)
|
||||
}
|
||||
}
|
||||
default:
|
||||
screen.TermMessage("Error reading bindings.json: non-string and non-map entry", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BindKey(k, v string, bind func(e Event, a string)) {
|
||||
event, err := findEvent(k)
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(k, "\x1b") {
|
||||
screen.Screen.RegisterRawSeq(k)
|
||||
}
|
||||
|
||||
bind(event, v)
|
||||
|
||||
// switch e := event.(type) {
|
||||
// case KeyEvent:
|
||||
// InfoMapKey(e, v)
|
||||
// case KeySequenceEvent:
|
||||
// InfoMapKey(e, v)
|
||||
// case MouseEvent:
|
||||
// InfoMapMouse(e, v)
|
||||
// case RawEvent:
|
||||
// InfoMapKey(e, v)
|
||||
// }
|
||||
}
|
||||
|
||||
var r = regexp.MustCompile("<(.+?)>")
|
||||
|
||||
func findEvents(k string) (b KeySequenceEvent, ok bool, err error) {
|
||||
var events []Event = nil
|
||||
for len(k) > 0 {
|
||||
groups := r.FindStringSubmatchIndex(k)
|
||||
|
||||
if len(groups) > 3 {
|
||||
if events == nil {
|
||||
events = make([]Event, 0, 3)
|
||||
}
|
||||
|
||||
e, ok := findSingleEvent(k[groups[2]:groups[3]])
|
||||
if !ok {
|
||||
return KeySequenceEvent{}, false, errors.New("Invalid event " + k[groups[2]:groups[3]])
|
||||
}
|
||||
|
||||
events = append(events, e)
|
||||
|
||||
k = k[groups[3]+1:]
|
||||
} else {
|
||||
return KeySequenceEvent{}, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return KeySequenceEvent{events}, true, nil
|
||||
}
|
||||
|
||||
// findSingleEvent will find binding Key 'b' using string 'k'
|
||||
func findSingleEvent(k string) (b Event, ok bool) {
|
||||
modifiers := tcell.ModNone
|
||||
|
||||
// First, we'll strip off all the modifiers in the name and add them to the
|
||||
// ModMask
|
||||
modSearch:
|
||||
for {
|
||||
switch {
|
||||
case strings.HasPrefix(k, "-") && k != "-":
|
||||
// We optionally support dashes between modifiers
|
||||
k = k[1:]
|
||||
case strings.HasPrefix(k, "Ctrl") && k != "CtrlH":
|
||||
// CtrlH technically does not have a 'Ctrl' modifier because it is really backspace
|
||||
k = k[4:]
|
||||
modifiers |= tcell.ModCtrl
|
||||
case strings.HasPrefix(k, "Alt"):
|
||||
k = k[3:]
|
||||
modifiers |= tcell.ModAlt
|
||||
case strings.HasPrefix(k, "Shift"):
|
||||
k = k[5:]
|
||||
modifiers |= tcell.ModShift
|
||||
case strings.HasPrefix(k, "\x1b"):
|
||||
return RawEvent{
|
||||
esc: k,
|
||||
}, true
|
||||
default:
|
||||
break modSearch
|
||||
}
|
||||
}
|
||||
|
||||
if k == "" {
|
||||
return KeyEvent{}, false
|
||||
}
|
||||
|
||||
// Control is handled in a special way, since the terminal sends explicitly
|
||||
// marked escape sequences for control keys
|
||||
// We should check for Control keys first
|
||||
if modifiers&tcell.ModCtrl != 0 {
|
||||
// see if the key is in bindingKeys with the Ctrl prefix.
|
||||
k = string(unicode.ToUpper(rune(k[0]))) + k[1:]
|
||||
if code, ok := keyEvents["Ctrl"+k]; ok {
|
||||
var r tcell.Key
|
||||
// Special case for escape, for some reason tcell doesn't send it with the esc character
|
||||
if code < 256 && code != 27 {
|
||||
r = code
|
||||
}
|
||||
// It is, we're done.
|
||||
return KeyEvent{
|
||||
code: code,
|
||||
mod: modifiers,
|
||||
r: rune(r),
|
||||
}, true
|
||||
}
|
||||
}
|
||||
|
||||
// See if we can find the key in bindingKeys
|
||||
if code, ok := keyEvents[k]; ok {
|
||||
var r tcell.Key
|
||||
// Special case for escape, for some reason tcell doesn't send it with the esc character
|
||||
if code < 256 && code != 27 {
|
||||
r = code
|
||||
}
|
||||
return KeyEvent{
|
||||
code: code,
|
||||
mod: modifiers,
|
||||
r: rune(r),
|
||||
}, true
|
||||
}
|
||||
|
||||
var mstate MouseState = MousePress
|
||||
if strings.HasSuffix(k, "Drag") {
|
||||
k = k[:len(k)-4]
|
||||
mstate = MouseDrag
|
||||
} else if strings.HasSuffix(k, "Release") {
|
||||
k = k[:len(k)-7]
|
||||
mstate = MouseRelease
|
||||
}
|
||||
// See if we can find the key in bindingMouse
|
||||
if code, ok := mouseEvents[k]; ok {
|
||||
return MouseEvent{
|
||||
btn: code,
|
||||
mod: modifiers,
|
||||
state: mstate,
|
||||
}, true
|
||||
}
|
||||
|
||||
// If we were given one character, then we've got a rune.
|
||||
if len(k) == 1 {
|
||||
return KeyEvent{
|
||||
code: tcell.KeyRune,
|
||||
mod: modifiers,
|
||||
r: rune(k[0]),
|
||||
}, true
|
||||
}
|
||||
|
||||
// We don't know what happened.
|
||||
return KeyEvent{}, false
|
||||
}
|
||||
|
||||
func findEvent(k string) (Event, error) {
|
||||
var event Event
|
||||
event, ok, err := findEvents(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
event, ok = findSingleEvent(k)
|
||||
if !ok {
|
||||
return nil, errors.New(k + " is not a bindable event")
|
||||
}
|
||||
}
|
||||
|
||||
return event, nil
|
||||
}
|
||||
|
||||
func eventsEqual(e1 Event, e2 Event) bool {
|
||||
seq1, ok1 := e1.(KeySequenceEvent)
|
||||
seq2, ok2 := e2.(KeySequenceEvent)
|
||||
if ok1 && ok2 {
|
||||
if len(seq1.keys) != len(seq2.keys) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(seq1.keys); i++ {
|
||||
if seq1.keys[i] != seq2.keys[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return e1 == e2
|
||||
}
|
||||
|
||||
// TryBindKey tries to bind a key by writing to config.ConfigDir/bindings.json
|
||||
// Returns true if the keybinding already existed and a possible error
|
||||
func TryBindKey(k, v string, overwrite bool) (bool, error) {
|
||||
var e error
|
||||
var parsed map[string]interface{}
|
||||
|
||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||
createBindingsIfNotExist(filename)
|
||||
if _, e = os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return false, errors.New("Error reading bindings.json file: " + err.Error())
|
||||
}
|
||||
|
||||
err = json5.Unmarshal(input, &parsed)
|
||||
if err != nil {
|
||||
return false, errors.New("Error reading bindings.json: " + err.Error())
|
||||
}
|
||||
|
||||
key, err := findEvent(k)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
found := false
|
||||
var ev string
|
||||
for ev = range parsed {
|
||||
if e, err := findEvent(ev); err == nil {
|
||||
if eventsEqual(e, key) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
if overwrite {
|
||||
parsed[ev] = v
|
||||
} else {
|
||||
return true, nil
|
||||
}
|
||||
} else {
|
||||
parsed[k] = v
|
||||
}
|
||||
|
||||
BindKey(k, v, Binder["buffer"])
|
||||
|
||||
txt, _ := json.MarshalIndent(parsed, "", " ")
|
||||
return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
||||
}
|
||||
return false, e
|
||||
}
|
||||
|
||||
// UnbindKey removes the binding for a key from the bindings.json file
|
||||
func UnbindKey(k string) error {
|
||||
var e error
|
||||
var parsed map[string]interface{}
|
||||
|
||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||
createBindingsIfNotExist(filename)
|
||||
if _, e = os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return errors.New("Error reading bindings.json file: " + err.Error())
|
||||
}
|
||||
|
||||
err = json5.Unmarshal(input, &parsed)
|
||||
if err != nil {
|
||||
return errors.New("Error reading bindings.json: " + err.Error())
|
||||
}
|
||||
|
||||
key, err := findEvent(k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for ev := range parsed {
|
||||
if e, err := findEvent(ev); err == nil {
|
||||
if eventsEqual(e, key) {
|
||||
delete(parsed, ev)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(k, "\x1b") {
|
||||
screen.Screen.UnregisterRawSeq(k)
|
||||
}
|
||||
|
||||
defaults := DefaultBindings("buffer")
|
||||
if a, ok := defaults[k]; ok {
|
||||
BindKey(k, a, Binder["buffer"])
|
||||
} else if _, ok := config.Bindings["buffer"][k]; ok {
|
||||
BufUnmap(key)
|
||||
delete(config.Bindings["buffer"], k)
|
||||
}
|
||||
|
||||
txt, _ := json.MarshalIndent(parsed, "", " ")
|
||||
return ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
var mouseEvents = map[string]tcell.ButtonMask{
|
||||
"MouseLeft": tcell.ButtonPrimary,
|
||||
"MouseMiddle": tcell.ButtonMiddle,
|
||||
"MouseRight": tcell.ButtonSecondary,
|
||||
"MouseWheelUp": tcell.WheelUp,
|
||||
"MouseWheelDown": tcell.WheelDown,
|
||||
"MouseWheelLeft": tcell.WheelLeft,
|
||||
"MouseWheelRight": tcell.WheelRight,
|
||||
}
|
||||
|
||||
var keyEvents = map[string]tcell.Key{
|
||||
"Up": tcell.KeyUp,
|
||||
"Down": tcell.KeyDown,
|
||||
"Right": tcell.KeyRight,
|
||||
"Left": tcell.KeyLeft,
|
||||
"UpLeft": tcell.KeyUpLeft,
|
||||
"UpRight": tcell.KeyUpRight,
|
||||
"DownLeft": tcell.KeyDownLeft,
|
||||
"DownRight": tcell.KeyDownRight,
|
||||
"Center": tcell.KeyCenter,
|
||||
"PageUp": tcell.KeyPgUp,
|
||||
"PageDown": tcell.KeyPgDn,
|
||||
"Home": tcell.KeyHome,
|
||||
"End": tcell.KeyEnd,
|
||||
"Insert": tcell.KeyInsert,
|
||||
"Delete": tcell.KeyDelete,
|
||||
"Help": tcell.KeyHelp,
|
||||
"Exit": tcell.KeyExit,
|
||||
"Clear": tcell.KeyClear,
|
||||
"Cancel": tcell.KeyCancel,
|
||||
"Print": tcell.KeyPrint,
|
||||
"Pause": tcell.KeyPause,
|
||||
"Backtab": tcell.KeyBacktab,
|
||||
"F1": tcell.KeyF1,
|
||||
"F2": tcell.KeyF2,
|
||||
"F3": tcell.KeyF3,
|
||||
"F4": tcell.KeyF4,
|
||||
"F5": tcell.KeyF5,
|
||||
"F6": tcell.KeyF6,
|
||||
"F7": tcell.KeyF7,
|
||||
"F8": tcell.KeyF8,
|
||||
"F9": tcell.KeyF9,
|
||||
"F10": tcell.KeyF10,
|
||||
"F11": tcell.KeyF11,
|
||||
"F12": tcell.KeyF12,
|
||||
"F13": tcell.KeyF13,
|
||||
"F14": tcell.KeyF14,
|
||||
"F15": tcell.KeyF15,
|
||||
"F16": tcell.KeyF16,
|
||||
"F17": tcell.KeyF17,
|
||||
"F18": tcell.KeyF18,
|
||||
"F19": tcell.KeyF19,
|
||||
"F20": tcell.KeyF20,
|
||||
"F21": tcell.KeyF21,
|
||||
"F22": tcell.KeyF22,
|
||||
"F23": tcell.KeyF23,
|
||||
"F24": tcell.KeyF24,
|
||||
"F25": tcell.KeyF25,
|
||||
"F26": tcell.KeyF26,
|
||||
"F27": tcell.KeyF27,
|
||||
"F28": tcell.KeyF28,
|
||||
"F29": tcell.KeyF29,
|
||||
"F30": tcell.KeyF30,
|
||||
"F31": tcell.KeyF31,
|
||||
"F32": tcell.KeyF32,
|
||||
"F33": tcell.KeyF33,
|
||||
"F34": tcell.KeyF34,
|
||||
"F35": tcell.KeyF35,
|
||||
"F36": tcell.KeyF36,
|
||||
"F37": tcell.KeyF37,
|
||||
"F38": tcell.KeyF38,
|
||||
"F39": tcell.KeyF39,
|
||||
"F40": tcell.KeyF40,
|
||||
"F41": tcell.KeyF41,
|
||||
"F42": tcell.KeyF42,
|
||||
"F43": tcell.KeyF43,
|
||||
"F44": tcell.KeyF44,
|
||||
"F45": tcell.KeyF45,
|
||||
"F46": tcell.KeyF46,
|
||||
"F47": tcell.KeyF47,
|
||||
"F48": tcell.KeyF48,
|
||||
"F49": tcell.KeyF49,
|
||||
"F50": tcell.KeyF50,
|
||||
"F51": tcell.KeyF51,
|
||||
"F52": tcell.KeyF52,
|
||||
"F53": tcell.KeyF53,
|
||||
"F54": tcell.KeyF54,
|
||||
"F55": tcell.KeyF55,
|
||||
"F56": tcell.KeyF56,
|
||||
"F57": tcell.KeyF57,
|
||||
"F58": tcell.KeyF58,
|
||||
"F59": tcell.KeyF59,
|
||||
"F60": tcell.KeyF60,
|
||||
"F61": tcell.KeyF61,
|
||||
"F62": tcell.KeyF62,
|
||||
"F63": tcell.KeyF63,
|
||||
"F64": tcell.KeyF64,
|
||||
"CtrlSpace": tcell.KeyCtrlSpace,
|
||||
"CtrlA": tcell.KeyCtrlA,
|
||||
"CtrlB": tcell.KeyCtrlB,
|
||||
"CtrlC": tcell.KeyCtrlC,
|
||||
"CtrlD": tcell.KeyCtrlD,
|
||||
"CtrlE": tcell.KeyCtrlE,
|
||||
"CtrlF": tcell.KeyCtrlF,
|
||||
"CtrlG": tcell.KeyCtrlG,
|
||||
"CtrlH": tcell.KeyCtrlH,
|
||||
"CtrlI": tcell.KeyCtrlI,
|
||||
"CtrlJ": tcell.KeyCtrlJ,
|
||||
"CtrlK": tcell.KeyCtrlK,
|
||||
"CtrlL": tcell.KeyCtrlL,
|
||||
"CtrlM": tcell.KeyCtrlM,
|
||||
"CtrlN": tcell.KeyCtrlN,
|
||||
"CtrlO": tcell.KeyCtrlO,
|
||||
"CtrlP": tcell.KeyCtrlP,
|
||||
"CtrlQ": tcell.KeyCtrlQ,
|
||||
"CtrlR": tcell.KeyCtrlR,
|
||||
"CtrlS": tcell.KeyCtrlS,
|
||||
"CtrlT": tcell.KeyCtrlT,
|
||||
"CtrlU": tcell.KeyCtrlU,
|
||||
"CtrlV": tcell.KeyCtrlV,
|
||||
"CtrlW": tcell.KeyCtrlW,
|
||||
"CtrlX": tcell.KeyCtrlX,
|
||||
"CtrlY": tcell.KeyCtrlY,
|
||||
"CtrlZ": tcell.KeyCtrlZ,
|
||||
"CtrlLeftSq": tcell.KeyCtrlLeftSq,
|
||||
"CtrlBackslash": tcell.KeyCtrlBackslash,
|
||||
"CtrlRightSq": tcell.KeyCtrlRightSq,
|
||||
"CtrlCarat": tcell.KeyCtrlCarat,
|
||||
"CtrlUnderscore": tcell.KeyCtrlUnderscore,
|
||||
"Tab": tcell.KeyTab,
|
||||
"Esc": tcell.KeyEsc,
|
||||
"Escape": tcell.KeyEscape,
|
||||
"Enter": tcell.KeyEnter,
|
||||
"Backspace": tcell.KeyBackspace2,
|
||||
"OldBackspace": tcell.KeyBackspace,
|
||||
|
||||
// I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
|
||||
"PgUp": tcell.KeyPgUp,
|
||||
"PgDown": tcell.KeyPgDn,
|
||||
}
|
|
@ -1,929 +0,0 @@
|
|||
package action
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"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/util"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
type BufAction interface{}
|
||||
|
||||
// BufKeyAction represents an action bound to a key.
|
||||
type BufKeyAction func(*BufPane) bool
|
||||
|
||||
// BufMouseAction is an action that must be bound to a mouse event.
|
||||
type BufMouseAction func(*BufPane, *tcell.EventMouse) bool
|
||||
|
||||
// BufBindings stores the bindings for the buffer pane type.
|
||||
var BufBindings *KeyTree
|
||||
|
||||
// BufKeyActionGeneral makes a general pane action from a BufKeyAction.
|
||||
func BufKeyActionGeneral(a BufKeyAction) PaneKeyAction {
|
||||
return func(p Pane) bool {
|
||||
return a(p.(*BufPane))
|
||||
}
|
||||
}
|
||||
|
||||
// BufMouseActionGeneral makes a general pane mouse action from a BufKeyAction.
|
||||
func BufMouseActionGeneral(a BufMouseAction) PaneMouseAction {
|
||||
return func(p Pane, me *tcell.EventMouse) bool {
|
||||
return a(p.(*BufPane), me)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
BufBindings = NewKeyTree()
|
||||
}
|
||||
|
||||
// LuaAction makes an action from a lua function. It returns either a BufKeyAction
|
||||
// or a BufMouseAction depending on the event type.
|
||||
func LuaAction(fn string, k Event) BufAction {
|
||||
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
|
||||
}
|
||||
|
||||
var action BufAction
|
||||
switch k.(type) {
|
||||
case KeyEvent, KeySequenceEvent, RawEvent:
|
||||
action = BufKeyAction(func(h *BufPane) bool {
|
||||
val, err := pl.Call(plFn, luar.New(ulua.L, h))
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
if v, ok := val.(lua.LBool); !ok {
|
||||
return false
|
||||
} else {
|
||||
return bool(v)
|
||||
}
|
||||
})
|
||||
case MouseEvent:
|
||||
action = BufMouseAction(func(h *BufPane, te *tcell.EventMouse) bool {
|
||||
val, err := pl.Call(plFn, luar.New(ulua.L, h), luar.New(ulua.L, te))
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
if v, ok := val.(lua.LBool); !ok {
|
||||
return false
|
||||
} else {
|
||||
return bool(v)
|
||||
}
|
||||
})
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
// BufMapEvent maps an event to an action
|
||||
func BufMapEvent(k Event, action string) {
|
||||
config.Bindings["buffer"][k.Name()] = action
|
||||
|
||||
var actionfns []BufAction
|
||||
var names []string
|
||||
var types []byte
|
||||
for i := 0; ; i++ {
|
||||
if action == "" {
|
||||
break
|
||||
}
|
||||
|
||||
// TODO: fix problem when complex bindings have these
|
||||
// characters (escape them?)
|
||||
idx := strings.IndexAny(action, "&|,")
|
||||
a := action
|
||||
if idx >= 0 {
|
||||
a = action[:idx]
|
||||
types = append(types, action[idx])
|
||||
action = action[idx+1:]
|
||||
} else {
|
||||
types = append(types, ' ')
|
||||
action = ""
|
||||
}
|
||||
|
||||
var afn BufAction
|
||||
if strings.HasPrefix(a, "command:") {
|
||||
a = strings.SplitN(a, ":", 2)[1]
|
||||
afn = CommandAction(a)
|
||||
names = append(names, "")
|
||||
} else if strings.HasPrefix(a, "command-edit:") {
|
||||
a = strings.SplitN(a, ":", 2)[1]
|
||||
afn = CommandEditAction(a)
|
||||
names = append(names, "")
|
||||
} else if strings.HasPrefix(a, "lua:") {
|
||||
a = strings.SplitN(a, ":", 2)[1]
|
||||
afn = LuaAction(a, k)
|
||||
if afn == nil {
|
||||
screen.TermMessage("Lua Error:", a, "does not exist")
|
||||
continue
|
||||
}
|
||||
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 if f, ok := BufMouseActions[a]; ok {
|
||||
afn = f
|
||||
names = append(names, a)
|
||||
} else {
|
||||
screen.TermMessage("Error in bindings: action", a, "does not exist")
|
||||
continue
|
||||
}
|
||||
actionfns = append(actionfns, afn)
|
||||
}
|
||||
bufAction := func(h *BufPane, te *tcell.EventMouse) bool {
|
||||
cursors := h.Buf.GetCursors()
|
||||
success := true
|
||||
for i, a := range actionfns {
|
||||
innerSuccess := true
|
||||
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] == ',') {
|
||||
innerSuccess = innerSuccess && h.execAction(a, names[i], j, te)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// if the action changed the current pane, update the reference
|
||||
h = MainTab().CurPane()
|
||||
success = innerSuccess
|
||||
if h == nil {
|
||||
// stop, in case the current pane is not a BufPane
|
||||
break
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
switch e := k.(type) {
|
||||
case KeyEvent, KeySequenceEvent, RawEvent:
|
||||
BufBindings.RegisterKeyBinding(e, BufKeyActionGeneral(func(h *BufPane) bool {
|
||||
return bufAction(h, nil)
|
||||
}))
|
||||
case MouseEvent:
|
||||
BufBindings.RegisterMouseBinding(e, BufMouseActionGeneral(bufAction))
|
||||
}
|
||||
}
|
||||
|
||||
// BufUnmap unmaps a key or mouse event from any action
|
||||
func BufUnmap(k Event) {
|
||||
// TODO
|
||||
// delete(BufKeyBindings, 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
|
||||
// The ActionHandler can access the window for necessary info about
|
||||
// visual positions for mouse clicks and scrolling
|
||||
type BufPane struct {
|
||||
display.BWindow
|
||||
|
||||
// Buf is the buffer this BufPane views
|
||||
Buf *buffer.Buffer
|
||||
// Bindings stores the association of key events and actions
|
||||
bindings *KeyTree
|
||||
|
||||
// Cursor is the currently active buffer cursor
|
||||
Cursor *buffer.Cursor
|
||||
|
||||
// Since tcell doesn't differentiate between a mouse press event
|
||||
// and a mouse move event with button pressed (nor between a mouse
|
||||
// release event and a mouse move event with no buttons pressed),
|
||||
// we need to keep track of whether or not the mouse was previously
|
||||
// pressed, to determine mouse release and mouse drag events.
|
||||
// Moreover, since in case of a release event tcell doesn't tell us
|
||||
// which button was released, we need to keep track of which
|
||||
// (possibly multiple) buttons were pressed previously.
|
||||
mousePressed map[MouseEvent]bool
|
||||
|
||||
// We need to keep track of insert key press toggle
|
||||
isOverwriteMode bool
|
||||
// This stores when the last click was
|
||||
// This is useful for detecting double and triple clicks
|
||||
lastClickTime time.Time
|
||||
lastLoc buffer.Loc
|
||||
|
||||
// lastCutTime stores when the last ctrl+k was issued.
|
||||
// It is used for clearing the clipboard to replace it with fresh cut lines.
|
||||
lastCutTime time.Time
|
||||
|
||||
// freshClip returns true if the clipboard has never been pasted.
|
||||
freshClip bool
|
||||
|
||||
// Was the last mouse event actually a double click?
|
||||
// Useful for detecting triple clicks -- if a double click is detected
|
||||
// but the last mouse event was actually a double click, it's a triple click
|
||||
doubleClick bool
|
||||
// Same here, just to keep track for mouse move events
|
||||
tripleClick 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
|
||||
|
||||
// The pane may not yet be fully initialized after its creation
|
||||
// since we may not know the window geometry yet. In such case we finish
|
||||
// its initialization a bit later, after the initial resize.
|
||||
initialized bool
|
||||
}
|
||||
|
||||
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.mousePressed = make(map[MouseEvent]bool)
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// NewBufPane creates a new buffer pane with the given window.
|
||||
func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
|
||||
h := newBufPane(buf, win, tab)
|
||||
h.finishInitialize()
|
||||
return h
|
||||
}
|
||||
|
||||
// NewBufPaneFromBuf constructs a new pane from the given buffer and automatically
|
||||
// creates a buf window.
|
||||
func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
|
||||
w := display.NewBufWindow(0, 0, 0, 0, buf)
|
||||
h := newBufPane(buf, w, tab)
|
||||
// Postpone finishing initializing the pane until we know the actual geometry
|
||||
// of the buf window.
|
||||
return h
|
||||
}
|
||||
|
||||
// TODO: make sure splitID and tab are set before finishInitialize is called
|
||||
func (h *BufPane) finishInitialize() {
|
||||
h.initialRelocate()
|
||||
h.initialized = true
|
||||
|
||||
err := config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Resize resizes the pane
|
||||
func (h *BufPane) Resize(width, height int) {
|
||||
h.BWindow.Resize(width, height)
|
||||
if !h.initialized {
|
||||
h.finishInitialize()
|
||||
}
|
||||
}
|
||||
|
||||
// SetTab sets this pane's tab.
|
||||
func (h *BufPane) SetTab(t *Tab) {
|
||||
h.tab = t
|
||||
}
|
||||
|
||||
// Tab returns this pane's tab.
|
||||
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 displays an
|
||||
// error if there is one and returns the aggregate boolean response
|
||||
func (h *BufPane) PluginCB(cb string) bool {
|
||||
b, err := config.RunPluginFnBool(h.Buf.Settings, cb, luar.New(ulua.L, h))
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// PluginCBRune is the same as PluginCB but also passes a rune to the plugins
|
||||
func (h *BufPane) PluginCBRune(cb string, r rune) bool {
|
||||
b, err := config.RunPluginFnBool(h.Buf.Settings, cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r)))
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (h *BufPane) resetMouse() {
|
||||
for me := range h.mousePressed {
|
||||
delete(h.mousePressed, me)
|
||||
}
|
||||
}
|
||||
|
||||
// OpenBuffer opens the given buffer in this pane.
|
||||
func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
|
||||
h.Buf.Close()
|
||||
h.Buf = b
|
||||
h.BWindow.SetBuffer(b)
|
||||
h.Cursor = b.GetActiveCursor()
|
||||
h.Resize(h.GetView().Width, h.GetView().Height)
|
||||
h.initialRelocate()
|
||||
// Set mouseReleased to true because we assume the mouse is not being
|
||||
// pressed when the editor is opened
|
||||
h.resetMouse()
|
||||
// Set isOverwriteMode to false, because we assume we are in the default
|
||||
// mode when editor is opened
|
||||
h.isOverwriteMode = false
|
||||
h.lastClickTime = time.Time{}
|
||||
}
|
||||
|
||||
// GotoLoc moves the cursor to a new location and adjusts the view accordingly.
|
||||
// Use GotoLoc when the new location may be far away from the current location.
|
||||
func (h *BufPane) GotoLoc(loc buffer.Loc) {
|
||||
sloc := h.SLocFromLoc(loc)
|
||||
d := h.Diff(h.SLocFromLoc(h.Cursor.Loc), sloc)
|
||||
|
||||
h.Cursor.GotoLoc(loc)
|
||||
|
||||
// If the new location is far away from the previous one,
|
||||
// ensure the cursor is at 25% of the window height
|
||||
height := h.BufView().Height
|
||||
if util.Abs(d) >= height {
|
||||
v := h.GetView()
|
||||
v.StartLine = h.Scroll(sloc, -height/4)
|
||||
h.ScrollAdjust()
|
||||
v.StartCol = 0
|
||||
}
|
||||
h.Relocate()
|
||||
}
|
||||
|
||||
func (h *BufPane) initialRelocate() {
|
||||
sloc := h.SLocFromLoc(h.Cursor.Loc)
|
||||
height := h.BufView().Height
|
||||
|
||||
// If the initial cursor location is far away from the beginning
|
||||
// of the buffer, ensure the cursor is at 25% of the window height
|
||||
v := h.GetView()
|
||||
if h.Diff(display.SLoc{0, 0}, sloc) < height {
|
||||
v.StartLine = display.SLoc{0, 0}
|
||||
} else {
|
||||
v.StartLine = h.Scroll(sloc, -height/4)
|
||||
h.ScrollAdjust()
|
||||
}
|
||||
v.StartCol = 0
|
||||
h.Relocate()
|
||||
}
|
||||
|
||||
// ID returns this pane's split id.
|
||||
func (h *BufPane) ID() uint64 {
|
||||
return h.splitID
|
||||
}
|
||||
|
||||
// SetID sets the split ID of this pane.
|
||||
func (h *BufPane) SetID(i uint64) {
|
||||
h.splitID = i
|
||||
}
|
||||
|
||||
// Name returns the BufPane's name.
|
||||
func (h *BufPane) Name() string {
|
||||
n := h.Buf.GetName()
|
||||
if h.Buf.Modified() {
|
||||
n += " +"
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// ReOpen reloads the file opened in the bufpane from disk
|
||||
func (h *BufPane) ReOpen() {
|
||||
h.Buf.ReOpen()
|
||||
h.Relocate()
|
||||
}
|
||||
|
||||
func (h *BufPane) getReloadSetting() string {
|
||||
reloadSetting := h.Buf.Settings["reload"]
|
||||
return reloadSetting.(string)
|
||||
}
|
||||
|
||||
// HandleEvent executes the tcell event properly
|
||||
func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||
if h.Buf.ExternallyModified() && !h.Buf.ReloadDisabled {
|
||||
reload := h.getReloadSetting()
|
||||
|
||||
if reload == "prompt" {
|
||||
InfoBar.YNPrompt("The file on disk has changed. Reload file? (y,n,esc)", func(yes, canceled bool) {
|
||||
if canceled {
|
||||
h.Buf.DisableReload()
|
||||
}
|
||||
if !yes || canceled {
|
||||
h.Buf.UpdateModTime()
|
||||
} else {
|
||||
h.ReOpen()
|
||||
}
|
||||
})
|
||||
} else if reload == "auto" {
|
||||
h.ReOpen()
|
||||
} else if reload == "disabled" {
|
||||
h.Buf.DisableReload()
|
||||
} else {
|
||||
InfoBar.Message("Invalid reload setting")
|
||||
}
|
||||
}
|
||||
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventRaw:
|
||||
re := RawEvent{
|
||||
esc: e.EscSeq(),
|
||||
}
|
||||
h.DoKeyEvent(re)
|
||||
case *tcell.EventPaste:
|
||||
h.paste(e.Text())
|
||||
h.Relocate()
|
||||
case *tcell.EventKey:
|
||||
ke := KeyEvent{
|
||||
code: e.Key(),
|
||||
mod: metaToAlt(e.Modifiers()),
|
||||
r: e.Rune(),
|
||||
}
|
||||
|
||||
done := h.DoKeyEvent(ke)
|
||||
if !done && e.Key() == tcell.KeyRune {
|
||||
h.DoRuneInsert(e.Rune())
|
||||
}
|
||||
case *tcell.EventMouse:
|
||||
if e.Buttons() != tcell.ButtonNone {
|
||||
me := MouseEvent{
|
||||
btn: e.Buttons(),
|
||||
mod: metaToAlt(e.Modifiers()),
|
||||
state: MousePress,
|
||||
}
|
||||
isDrag := len(h.mousePressed) > 0
|
||||
|
||||
if e.Buttons() & ^(tcell.WheelUp|tcell.WheelDown|tcell.WheelLeft|tcell.WheelRight) != tcell.ButtonNone {
|
||||
h.mousePressed[me] = true
|
||||
}
|
||||
|
||||
if isDrag {
|
||||
me.state = MouseDrag
|
||||
}
|
||||
h.DoMouseEvent(me, e)
|
||||
} else {
|
||||
// Mouse event with no click - mouse was just released.
|
||||
// If there were multiple mouse buttons pressed, we don't know which one
|
||||
// was actually released, so we assume they all were released.
|
||||
pressed := len(h.mousePressed) > 0
|
||||
for me := range h.mousePressed {
|
||||
delete(h.mousePressed, me)
|
||||
|
||||
me.state = MouseRelease
|
||||
h.DoMouseEvent(me, e)
|
||||
}
|
||||
if !pressed {
|
||||
// Propagate the mouse release in case the press wasn't for this BufPane
|
||||
Tabs.ResetMouse()
|
||||
}
|
||||
}
|
||||
}
|
||||
h.Buf.MergeCursors()
|
||||
|
||||
if h.IsActive() {
|
||||
// Display any gutter messages for this line
|
||||
c := h.Buf.GetActiveCursor()
|
||||
none := true
|
||||
for _, m := range h.Buf.Messages {
|
||||
if c.Y == m.Start.Y || c.Y == m.End.Y {
|
||||
InfoBar.GutterMessage(m.Msg)
|
||||
none = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if none && InfoBar.HasGutter {
|
||||
InfoBar.ClearGutter()
|
||||
}
|
||||
}
|
||||
|
||||
cursors := h.Buf.GetCursors()
|
||||
for _, c := range cursors {
|
||||
if c.NewTrailingWsY != c.Y && (!c.HasSelection() ||
|
||||
(c.NewTrailingWsY != c.CurSelection[0].Y && c.NewTrailingWsY != c.CurSelection[1].Y)) {
|
||||
c.NewTrailingWsY = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bindings returns the current bindings tree for this buffer.
|
||||
func (h *BufPane) Bindings() *KeyTree {
|
||||
if h.bindings != nil {
|
||||
return h.bindings
|
||||
}
|
||||
return BufBindings
|
||||
}
|
||||
|
||||
// DoKeyEvent executes a key event by finding the action it is bound
|
||||
// to and executing it (possibly multiple times for multiple cursors).
|
||||
// Returns true if the action was executed OR if there are more keys
|
||||
// remaining to process before executing an action (if this is a key
|
||||
// sequence event). Returns false if no action found.
|
||||
func (h *BufPane) DoKeyEvent(e Event) bool {
|
||||
binds := h.Bindings()
|
||||
action, more := binds.NextEvent(e, nil)
|
||||
if action != nil && !more {
|
||||
action(h)
|
||||
binds.ResetEvents()
|
||||
return true
|
||||
} else if action == nil && !more {
|
||||
binds.ResetEvents()
|
||||
}
|
||||
return more
|
||||
}
|
||||
|
||||
func (h *BufPane) execAction(action BufAction, name string, cursor int, te *tcell.EventMouse) bool {
|
||||
if name != "Autocomplete" && name != "CycleAutocompleteBack" {
|
||||
h.Buf.HasSuggestions = false
|
||||
}
|
||||
|
||||
_, isMulti := MultiActions[name]
|
||||
if (!isMulti && cursor == 0) || isMulti {
|
||||
if h.PluginCB("pre" + name) {
|
||||
var success bool
|
||||
switch a := action.(type) {
|
||||
case BufKeyAction:
|
||||
success = a(h)
|
||||
case BufMouseAction:
|
||||
success = a(h, te)
|
||||
}
|
||||
success = success && h.PluginCB("on"+name)
|
||||
|
||||
if isMulti {
|
||||
if recordingMacro {
|
||||
if name != "ToggleMacro" && name != "PlayMacro" {
|
||||
curmacro = append(curmacro, action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return success
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *BufPane) completeAction(action string) {
|
||||
h.PluginCB("on" + action)
|
||||
}
|
||||
|
||||
func (h *BufPane) HasKeyEvent(e Event) bool {
|
||||
// TODO
|
||||
return true
|
||||
// _, ok := BufKeyBindings[e]
|
||||
// return ok
|
||||
}
|
||||
|
||||
// DoMouseEvent executes a mouse event by finding the action it is bound
|
||||
// to and executing it
|
||||
func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
|
||||
binds := h.Bindings()
|
||||
action, _ := binds.NextEvent(e, te)
|
||||
if action != nil {
|
||||
action(h)
|
||||
binds.ResetEvents()
|
||||
return true
|
||||
}
|
||||
// TODO
|
||||
return false
|
||||
|
||||
// if action, ok := BufMouseBindings[e]; ok {
|
||||
// if action(h, te) {
|
||||
// h.Relocate()
|
||||
// }
|
||||
// return true
|
||||
// } else if h.HasKeyEvent(e) {
|
||||
// return h.DoKeyEvent(e)
|
||||
// }
|
||||
// return false
|
||||
}
|
||||
|
||||
// DoRuneInsert inserts a given rune into the current buffer
|
||||
// (possibly multiple times for multiple cursors)
|
||||
func (h *BufPane) DoRuneInsert(r rune) {
|
||||
cursors := h.Buf.GetCursors()
|
||||
for _, c := range cursors {
|
||||
// Insert a character
|
||||
h.Buf.SetCurCursor(c.Num)
|
||||
h.Cursor = c
|
||||
if !h.PluginCBRune("preRune", r) {
|
||||
continue
|
||||
}
|
||||
if c.HasSelection() {
|
||||
c.DeleteSelection()
|
||||
c.ResetSelection()
|
||||
}
|
||||
|
||||
if h.isOverwriteMode {
|
||||
next := c.Loc
|
||||
next.X++
|
||||
h.Buf.Replace(c.Loc, next, string(r))
|
||||
} else {
|
||||
h.Buf.Insert(c.Loc, string(r))
|
||||
}
|
||||
if recordingMacro {
|
||||
curmacro = append(curmacro, r)
|
||||
}
|
||||
h.Relocate()
|
||||
h.PluginCBRune("onRune", r)
|
||||
}
|
||||
}
|
||||
|
||||
// VSplitIndex opens the given buffer in a vertical split on the given side.
|
||||
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
|
||||
}
|
||||
|
||||
// HSplitIndex opens the given buffer in a horizontal split on the given side.
|
||||
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
|
||||
}
|
||||
|
||||
// VSplitBuf opens the given buffer in a new vertical split.
|
||||
func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
|
||||
return h.VSplitIndex(buf, h.Buf.Settings["splitright"].(bool))
|
||||
}
|
||||
|
||||
// HSplitBuf opens the given buffer in a new horizontal split.
|
||||
func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
|
||||
return h.HSplitIndex(buf, h.Buf.Settings["splitbottom"].(bool))
|
||||
}
|
||||
|
||||
// Close this pane.
|
||||
func (h *BufPane) Close() {
|
||||
h.Buf.Close()
|
||||
}
|
||||
|
||||
// SetActive marks this pane as active.
|
||||
func (h *BufPane) SetActive(b bool) {
|
||||
if h.IsActive() == b {
|
||||
return
|
||||
}
|
||||
|
||||
h.BWindow.SetActive(b)
|
||||
if b {
|
||||
// Display any gutter messages for this line
|
||||
c := h.Buf.GetActiveCursor()
|
||||
none := true
|
||||
for _, m := range h.Buf.Messages {
|
||||
if c.Y == m.Start.Y || c.Y == m.End.Y {
|
||||
InfoBar.GutterMessage(m.Msg)
|
||||
none = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if none && InfoBar.HasGutter {
|
||||
InfoBar.ClearGutter()
|
||||
}
|
||||
|
||||
err := config.RunPluginFn("onSetActive", luar.New(ulua.L, h))
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
"SubWordRight": (*BufPane).SubWordRight,
|
||||
"SubWordLeft": (*BufPane).SubWordLeft,
|
||||
"SelectWordRight": (*BufPane).SelectWordRight,
|
||||
"SelectWordLeft": (*BufPane).SelectWordLeft,
|
||||
"SelectSubWordRight": (*BufPane).SelectSubWordRight,
|
||||
"SelectSubWordLeft": (*BufPane).SelectSubWordLeft,
|
||||
"DeleteWordRight": (*BufPane).DeleteWordRight,
|
||||
"DeleteWordLeft": (*BufPane).DeleteWordLeft,
|
||||
"DeleteSubWordRight": (*BufPane).DeleteSubWordRight,
|
||||
"DeleteSubWordLeft": (*BufPane).DeleteSubWordLeft,
|
||||
"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,
|
||||
"DiffNext": (*BufPane).DiffNext,
|
||||
"DiffPrevious": (*BufPane).DiffPrevious,
|
||||
"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,
|
||||
"ToggleHighlightSearch": (*BufPane).ToggleHighlightSearch,
|
||||
"UnhighlightSearch": (*BufPane).UnhighlightSearch,
|
||||
"ClearStatus": (*BufPane).ClearStatus,
|
||||
"ShellMode": (*BufPane).ShellMode,
|
||||
"CommandMode": (*BufPane).CommandMode,
|
||||
"ToggleOverwriteMode": (*BufPane).ToggleOverwriteMode,
|
||||
"Escape": (*BufPane).Escape,
|
||||
"Quit": (*BufPane).Quit,
|
||||
"QuitAll": (*BufPane).QuitAll,
|
||||
"ForceQuit": (*BufPane).ForceQuit,
|
||||
"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,
|
||||
"Deselect": (*BufPane).Deselect,
|
||||
"ClearInfo": (*BufPane).ClearInfo,
|
||||
"None": (*BufPane).None,
|
||||
|
||||
// This was changed to InsertNewline but I don't want to break backwards compatibility
|
||||
"InsertEnter": (*BufPane).InsertNewline,
|
||||
}
|
||||
|
||||
// BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
|
||||
var BufMouseActions = map[string]BufMouseAction{
|
||||
"MousePress": (*BufPane).MousePress,
|
||||
"MouseDrag": (*BufPane).MouseDrag,
|
||||
"MouseRelease": (*BufPane).MouseRelease,
|
||||
"MouseMultiCursor": (*BufPane).MouseMultiCursor,
|
||||
}
|
||||
|
||||
// MultiActions is a list of actions that should be executed multiple
|
||||
// times if there are multiple cursors (one per cursor)
|
||||
// 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,
|
||||
"SubWordRight": true,
|
||||
"SubWordLeft": true,
|
||||
"SelectWordRight": true,
|
||||
"SelectWordLeft": true,
|
||||
"SelectSubWordRight": true,
|
||||
"SelectSubWordLeft": true,
|
||||
"DeleteWordRight": true,
|
||||
"DeleteWordLeft": true,
|
||||
"DeleteSubWordRight": true,
|
||||
"DeleteSubWordLeft": 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,
|
||||
"Copy": 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,
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,21 +0,0 @@
|
|||
package action
|
||||
|
||||
var termdefaults = map[string]string{
|
||||
"<Ctrl-q><Ctrl-q>": "Exit",
|
||||
"<Ctrl-e><Ctrl-e>": "CommandMode",
|
||||
"<Ctrl-w><Ctrl-w>": "NextSplit",
|
||||
}
|
||||
|
||||
// DefaultBindings returns a map containing micro's default keybindings
|
||||
func DefaultBindings(pane string) map[string]string {
|
||||
switch pane {
|
||||
case "command":
|
||||
return infodefaults
|
||||
case "buffer":
|
||||
return bufdefaults
|
||||
case "terminal":
|
||||
return termdefaults
|
||||
default:
|
||||
return map[string]string{}
|
||||
}
|
||||
}
|
|
@ -1,186 +0,0 @@
|
|||
package action
|
||||
|
||||
var bufdefaults = map[string]string{
|
||||
"Up": "CursorUp",
|
||||
"Down": "CursorDown",
|
||||
"Right": "CursorRight",
|
||||
"Left": "CursorLeft",
|
||||
"ShiftUp": "SelectUp",
|
||||
"ShiftDown": "SelectDown",
|
||||
"ShiftLeft": "SelectLeft",
|
||||
"ShiftRight": "SelectRight",
|
||||
"AltLeft": "WordLeft",
|
||||
"AltRight": "WordRight",
|
||||
"AltUp": "MoveLinesUp",
|
||||
"AltDown": "MoveLinesDown",
|
||||
"AltShiftRight": "SelectWordRight",
|
||||
"AltShiftLeft": "SelectWordLeft",
|
||||
"CtrlLeft": "StartOfTextToggle",
|
||||
"CtrlRight": "EndOfLine",
|
||||
"CtrlShiftLeft": "SelectToStartOfTextToggle",
|
||||
"ShiftHome": "SelectToStartOfTextToggle",
|
||||
"CtrlShiftRight": "SelectToEndOfLine",
|
||||
"ShiftEnd": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Alt-{": "ParagraphPrevious",
|
||||
"Alt-}": "ParagraphNext",
|
||||
"Enter": "InsertNewline",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
"OldBackspace": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Tab": "Autocomplete|IndentSelection|InsertTab",
|
||||
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
|
||||
"Ctrl-o": "OpenFile",
|
||||
"Ctrl-s": "Save",
|
||||
"Ctrl-f": "Find",
|
||||
"Alt-F": "FindLiteral",
|
||||
"Ctrl-n": "FindNext",
|
||||
"Ctrl-p": "FindPrevious",
|
||||
"Alt-[": "DiffPrevious|CursorStart",
|
||||
"Alt-]": "DiffNext|CursorEnd",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-d": "DuplicateLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Ctrl-a": "SelectAll",
|
||||
"Ctrl-t": "AddTab",
|
||||
"Alt-,": "PreviousTab",
|
||||
"Alt-.": "NextTab",
|
||||
"Home": "StartOfTextToggle",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"Ctrl-g": "ToggleHelp",
|
||||
"Alt-g": "ToggleKeyMenu",
|
||||
"Ctrl-r": "ToggleRuler",
|
||||
"Ctrl-l": "command-edit:goto ",
|
||||
"Delete": "Delete",
|
||||
"Ctrl-b": "ShellMode",
|
||||
"Ctrl-q": "Quit",
|
||||
"Ctrl-e": "CommandMode",
|
||||
"Ctrl-w": "NextSplit",
|
||||
"Ctrl-u": "ToggleMacro",
|
||||
"Ctrl-j": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
"Alt-b": "WordLeft",
|
||||
"Alt-a": "StartOfText",
|
||||
"Alt-e": "EndOfLine",
|
||||
// "Alt-p": "CursorUp",
|
||||
// "Alt-n": "CursorDown",
|
||||
|
||||
// Integration with file managers
|
||||
"F2": "Save",
|
||||
"F3": "Find",
|
||||
"F4": "Quit",
|
||||
"F7": "Find",
|
||||
"F10": "Quit",
|
||||
"Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "ScrollUp",
|
||||
"MouseWheelDown": "ScrollDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseLeftDrag": "MouseDrag",
|
||||
"MouseLeftRelease": "MouseRelease",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"AltShiftUp": "SpawnMultiCursorUp",
|
||||
"AltShiftDown": "SpawnMultiCursorDown",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
}
|
||||
|
||||
var infodefaults = map[string]string{
|
||||
"Up": "HistoryUp",
|
||||
"Down": "HistoryDown",
|
||||
"Right": "CursorRight",
|
||||
"Left": "CursorLeft",
|
||||
"ShiftUp": "SelectUp",
|
||||
"ShiftDown": "SelectDown",
|
||||
"ShiftLeft": "SelectLeft",
|
||||
"ShiftRight": "SelectRight",
|
||||
"AltLeft": "WordLeft",
|
||||
"AltRight": "WordRight",
|
||||
"AltUp": "CursorStart",
|
||||
"AltDown": "CursorEnd",
|
||||
"AltShiftRight": "SelectWordRight",
|
||||
"AltShiftLeft": "SelectWordLeft",
|
||||
"CtrlLeft": "StartOfTextToggle",
|
||||
"CtrlRight": "EndOfLine",
|
||||
"CtrlShiftLeft": "SelectToStartOfTextToggle",
|
||||
"ShiftHome": "SelectToStartOfTextToggle",
|
||||
"CtrlShiftRight": "SelectToEndOfLine",
|
||||
"ShiftEnd": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Enter": "ExecuteCommand",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
"OldBackspace": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Tab": "CommandComplete",
|
||||
"Backtab": "CycleAutocompleteBack",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Home": "StartOfTextToggle",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"Delete": "Delete",
|
||||
"Ctrl-q": "AbortCommand",
|
||||
"Ctrl-e": "EndOfLine",
|
||||
"Ctrl-a": "StartOfLine",
|
||||
"Ctrl-w": "DeleteWordLeft",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
"Ctrl-b": "WordLeft",
|
||||
"Ctrl-f": "WordRight",
|
||||
"Ctrl-d": "DeleteWordLeft",
|
||||
"Ctrl-m": "ExecuteCommand",
|
||||
"Ctrl-n": "HistoryDown",
|
||||
"Ctrl-p": "HistoryUp",
|
||||
"Ctrl-u": "SelectToStart",
|
||||
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
"Alt-b": "WordLeft",
|
||||
"Alt-a": "StartOfText",
|
||||
"Alt-e": "EndOfLine",
|
||||
|
||||
// Integration with file managers
|
||||
"F10": "AbortCommand",
|
||||
"Esc": "AbortCommand",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "HistoryUp",
|
||||
"MouseWheelDown": "HistoryDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseLeftDrag": "MouseDrag",
|
||||
"MouseLeftRelease": "MouseRelease",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
}
|
|
@ -1,189 +0,0 @@
|
|||
//go:build !darwin
|
||||
// +build !darwin
|
||||
|
||||
package action
|
||||
|
||||
var bufdefaults = map[string]string{
|
||||
"Up": "CursorUp",
|
||||
"Down": "CursorDown",
|
||||
"Right": "CursorRight",
|
||||
"Left": "CursorLeft",
|
||||
"ShiftUp": "SelectUp",
|
||||
"ShiftDown": "SelectDown",
|
||||
"ShiftLeft": "SelectLeft",
|
||||
"ShiftRight": "SelectRight",
|
||||
"CtrlLeft": "WordLeft",
|
||||
"CtrlRight": "WordRight",
|
||||
"AltUp": "MoveLinesUp",
|
||||
"AltDown": "MoveLinesDown",
|
||||
"CtrlShiftRight": "SelectWordRight",
|
||||
"CtrlShiftLeft": "SelectWordLeft",
|
||||
"AltLeft": "StartOfTextToggle",
|
||||
"AltRight": "EndOfLine",
|
||||
"AltShiftLeft": "SelectToStartOfTextToggle",
|
||||
"ShiftHome": "SelectToStartOfTextToggle",
|
||||
"AltShiftRight": "SelectToEndOfLine",
|
||||
"ShiftEnd": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Alt-{": "ParagraphPrevious",
|
||||
"Alt-}": "ParagraphNext",
|
||||
"Enter": "InsertNewline",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
"OldBackspace": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Tab": "Autocomplete|IndentSelection|InsertTab",
|
||||
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
|
||||
"Ctrl-o": "OpenFile",
|
||||
"Ctrl-s": "Save",
|
||||
"Ctrl-f": "Find",
|
||||
"Alt-F": "FindLiteral",
|
||||
"Ctrl-n": "FindNext",
|
||||
"Ctrl-p": "FindPrevious",
|
||||
"Alt-[": "DiffPrevious|CursorStart",
|
||||
"Alt-]": "DiffNext|CursorEnd",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-d": "DuplicateLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Ctrl-a": "SelectAll",
|
||||
"Ctrl-t": "AddTab",
|
||||
"Alt-,": "PreviousTab",
|
||||
"Alt-.": "NextTab",
|
||||
"Home": "StartOfTextToggle",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"Ctrl-g": "ToggleHelp",
|
||||
"Alt-g": "ToggleKeyMenu",
|
||||
"Ctrl-r": "ToggleRuler",
|
||||
"Ctrl-l": "command-edit:goto ",
|
||||
"Delete": "Delete",
|
||||
"Ctrl-b": "ShellMode",
|
||||
"Ctrl-q": "Quit",
|
||||
"Ctrl-e": "CommandMode",
|
||||
"Ctrl-w": "NextSplit",
|
||||
"Ctrl-u": "ToggleMacro",
|
||||
"Ctrl-j": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
"Alt-b": "WordLeft",
|
||||
"Alt-a": "StartOfText",
|
||||
"Alt-e": "EndOfLine",
|
||||
// "Alt-p": "CursorUp",
|
||||
// "Alt-n": "CursorDown",
|
||||
|
||||
// Integration with file managers
|
||||
"F2": "Save",
|
||||
"F3": "Find",
|
||||
"F4": "Quit",
|
||||
"F7": "Find",
|
||||
"F10": "Quit",
|
||||
"Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "ScrollUp",
|
||||
"MouseWheelDown": "ScrollDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseLeftDrag": "MouseDrag",
|
||||
"MouseLeftRelease": "MouseRelease",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"AltShiftUp": "SpawnMultiCursorUp",
|
||||
"AltShiftDown": "SpawnMultiCursorDown",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
}
|
||||
|
||||
var infodefaults = map[string]string{
|
||||
"Up": "HistoryUp",
|
||||
"Down": "HistoryDown",
|
||||
"Right": "CursorRight",
|
||||
"Left": "CursorLeft",
|
||||
"ShiftUp": "SelectUp",
|
||||
"ShiftDown": "SelectDown",
|
||||
"ShiftLeft": "SelectLeft",
|
||||
"ShiftRight": "SelectRight",
|
||||
"AltLeft": "StartOfTextToggle",
|
||||
"AltRight": "EndOfLine",
|
||||
"AltUp": "CursorStart",
|
||||
"AltDown": "CursorEnd",
|
||||
"AltShiftRight": "SelectWordRight",
|
||||
"AltShiftLeft": "SelectWordLeft",
|
||||
"CtrlLeft": "WordLeft",
|
||||
"CtrlRight": "WordRight",
|
||||
"CtrlShiftLeft": "SelectToStartOfTextToggle",
|
||||
"ShiftHome": "SelectToStartOfTextToggle",
|
||||
"CtrlShiftRight": "SelectToEndOfLine",
|
||||
"ShiftEnd": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Enter": "ExecuteCommand",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
"OldBackspace": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Tab": "CommandComplete",
|
||||
"Backtab": "CycleAutocompleteBack",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Home": "StartOfTextToggle",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"Delete": "Delete",
|
||||
"Ctrl-q": "AbortCommand",
|
||||
"Ctrl-e": "EndOfLine",
|
||||
"Ctrl-a": "StartOfLine",
|
||||
"Ctrl-w": "DeleteWordLeft",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
"Ctrl-b": "WordLeft",
|
||||
"Ctrl-f": "WordRight",
|
||||
"Ctrl-d": "DeleteWordLeft",
|
||||
"Ctrl-m": "ExecuteCommand",
|
||||
"Ctrl-n": "HistoryDown",
|
||||
"Ctrl-p": "HistoryUp",
|
||||
"Ctrl-u": "SelectToStart",
|
||||
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
"Alt-b": "WordLeft",
|
||||
"Alt-a": "StartOfText",
|
||||
"Alt-e": "EndOfLine",
|
||||
|
||||
// Integration with file managers
|
||||
"F10": "AbortCommand",
|
||||
"Esc": "AbortCommand",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "HistoryUp",
|
||||
"MouseWheelDown": "HistoryDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseLeftDrag": "MouseDrag",
|
||||
"MouseLeftRelease": "MouseRelease",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
}
|
|
@ -1,181 +0,0 @@
|
|||
package action
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
type Event interface {
|
||||
Name() string
|
||||
}
|
||||
|
||||
// RawEvent is simply an escape code
|
||||
// We allow users to directly bind escape codes
|
||||
// to get around some of a limitations of terminals
|
||||
type RawEvent struct {
|
||||
esc string
|
||||
}
|
||||
|
||||
func (r RawEvent) Name() string {
|
||||
return r.esc
|
||||
}
|
||||
|
||||
// KeyEvent is a key event containing a key code,
|
||||
// some possible modifiers (alt, ctrl, etc...) and
|
||||
// a rune if it was simply a character press
|
||||
// Note: to be compatible with tcell events,
|
||||
// for ctrl keys r=code
|
||||
type KeyEvent struct {
|
||||
code tcell.Key
|
||||
mod tcell.ModMask
|
||||
r rune
|
||||
any bool
|
||||
}
|
||||
|
||||
func metaToAlt(mod tcell.ModMask) tcell.ModMask {
|
||||
if mod&tcell.ModMeta != 0 {
|
||||
mod &= ^tcell.ModMeta
|
||||
mod |= tcell.ModAlt
|
||||
}
|
||||
return mod
|
||||
}
|
||||
|
||||
func (k KeyEvent) Name() string {
|
||||
if k.any {
|
||||
return "<any>"
|
||||
}
|
||||
s := ""
|
||||
m := []string{}
|
||||
if k.mod&tcell.ModShift != 0 {
|
||||
m = append(m, "Shift")
|
||||
}
|
||||
if k.mod&tcell.ModAlt != 0 {
|
||||
m = append(m, "Alt")
|
||||
}
|
||||
if k.mod&tcell.ModMeta != 0 {
|
||||
m = append(m, "Meta")
|
||||
}
|
||||
if k.mod&tcell.ModCtrl != 0 {
|
||||
m = append(m, "Ctrl")
|
||||
}
|
||||
|
||||
ok := false
|
||||
if s, ok = tcell.KeyNames[k.code]; !ok {
|
||||
if k.code == tcell.KeyRune {
|
||||
s = string(k.r)
|
||||
} else {
|
||||
s = fmt.Sprintf("Key[%d,%d]", k.code, int(k.r))
|
||||
}
|
||||
}
|
||||
if len(m) != 0 {
|
||||
if k.mod&tcell.ModCtrl != 0 && strings.HasPrefix(s, "Ctrl-") {
|
||||
s = s[5:]
|
||||
if len(s) == 1 {
|
||||
s = strings.ToLower(s)
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s-%s", strings.Join(m, "-"), s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// A KeySequence defines a list of consecutive
|
||||
// events. All events in the sequence must be KeyEvents
|
||||
// or MouseEvents.
|
||||
type KeySequenceEvent struct {
|
||||
keys []Event
|
||||
}
|
||||
|
||||
func (k KeySequenceEvent) Name() string {
|
||||
buf := bytes.Buffer{}
|
||||
for _, e := range k.keys {
|
||||
buf.WriteByte('<')
|
||||
buf.WriteString(e.Name())
|
||||
buf.WriteByte('>')
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
type MouseState int
|
||||
|
||||
const (
|
||||
MousePress = iota
|
||||
MouseDrag
|
||||
MouseRelease
|
||||
)
|
||||
|
||||
// MouseEvent is a mouse event with a mouse button and
|
||||
// any possible key modifiers
|
||||
type MouseEvent struct {
|
||||
btn tcell.ButtonMask
|
||||
mod tcell.ModMask
|
||||
state MouseState
|
||||
}
|
||||
|
||||
func (m MouseEvent) Name() string {
|
||||
mod := ""
|
||||
if m.mod&tcell.ModShift != 0 {
|
||||
mod = "Shift-"
|
||||
}
|
||||
if m.mod&tcell.ModAlt != 0 {
|
||||
mod = "Alt-"
|
||||
}
|
||||
if m.mod&tcell.ModMeta != 0 {
|
||||
mod = "Meta-"
|
||||
}
|
||||
if m.mod&tcell.ModCtrl != 0 {
|
||||
mod = "Ctrl-"
|
||||
}
|
||||
|
||||
state := ""
|
||||
switch m.state {
|
||||
case MouseDrag:
|
||||
state = "Drag"
|
||||
case MouseRelease:
|
||||
state = "Release"
|
||||
}
|
||||
|
||||
for k, v := range mouseEvents {
|
||||
if v == m.btn {
|
||||
return fmt.Sprintf("%s%s%s", mod, k, state)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ConstructEvent takes a tcell event and returns a micro
|
||||
// event. Note that tcell events can't express certain
|
||||
// micro events such as key sequences. This function is
|
||||
// mostly used for debugging/raw panes or constructing
|
||||
// intermediate micro events while parsing a sequence.
|
||||
func ConstructEvent(event tcell.Event) (Event, error) {
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
return KeyEvent{
|
||||
code: e.Key(),
|
||||
mod: metaToAlt(e.Modifiers()),
|
||||
r: e.Rune(),
|
||||
}, nil
|
||||
case *tcell.EventRaw:
|
||||
return RawEvent{
|
||||
esc: e.EscSeq(),
|
||||
}, nil
|
||||
case *tcell.EventMouse:
|
||||
return MouseEvent{
|
||||
btn: e.Buttons(),
|
||||
mod: metaToAlt(e.Modifiers()),
|
||||
}, nil
|
||||
}
|
||||
return nil, errors.New("No micro event equivalent")
|
||||
}
|
||||
|
||||
// A Handler will take a tcell event and execute it
|
||||
// appropriately
|
||||
type Handler interface {
|
||||
HandleEvent(tcell.Event)
|
||||
HandleCommand(string)
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package action
|
||||
|
||||
import "github.com/zyedidia/micro/v2/internal/buffer"
|
||||
|
||||
// InfoBar is the global info bar.
|
||||
var InfoBar *InfoPane
|
||||
|
||||
// LogBufPane is a global log buffer.
|
||||
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 {
|
||||
LogBufPane.CursorEnd()
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
|
@ -1,341 +0,0 @@
|
|||
package action
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/micro/v2/pkg/highlight"
|
||||
)
|
||||
|
||||
// This file is meant (for now) for autocompletion in command mode, not
|
||||
// while coding. This helps micro autocomplete commands and then filenames
|
||||
// for example with `vsplit filename`.
|
||||
|
||||
// CommandComplete autocompletes commands
|
||||
func CommandComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
c := b.GetActiveCursor()
|
||||
input, argstart := b.GetArg()
|
||||
|
||||
var suggestions []string
|
||||
for cmd := range commands {
|
||||
if strings.HasPrefix(cmd, input) {
|
||||
suggestions = append(suggestions, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(suggestions)
|
||||
completions := make([]string, len(suggestions))
|
||||
for i := range suggestions {
|
||||
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
|
||||
}
|
||||
|
||||
return completions, suggestions
|
||||
}
|
||||
|
||||
// HelpComplete autocompletes help topics
|
||||
func HelpComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
c := b.GetActiveCursor()
|
||||
input, argstart := b.GetArg()
|
||||
|
||||
var suggestions []string
|
||||
|
||||
for _, file := range config.ListRuntimeFiles(config.RTHelp) {
|
||||
topic := file.Name()
|
||||
if strings.HasPrefix(topic, input) {
|
||||
suggestions = append(suggestions, topic)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(suggestions)
|
||||
completions := make([]string, len(suggestions))
|
||||
for i := range suggestions {
|
||||
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
|
||||
}
|
||||
return completions, suggestions
|
||||
}
|
||||
|
||||
// colorschemeComplete tab-completes names of colorschemes.
|
||||
// This is just a heper value for OptionValueComplete
|
||||
func colorschemeComplete(input string) (string, []string) {
|
||||
var suggestions []string
|
||||
files := config.ListRuntimeFiles(config.RTColorscheme)
|
||||
|
||||
for _, f := range files {
|
||||
if strings.HasPrefix(f.Name(), input) {
|
||||
suggestions = append(suggestions, f.Name())
|
||||
}
|
||||
}
|
||||
|
||||
var chosen string
|
||||
if len(suggestions) == 1 {
|
||||
chosen = suggestions[0]
|
||||
}
|
||||
|
||||
return chosen, suggestions
|
||||
}
|
||||
|
||||
// filetypeComplete autocompletes filetype
|
||||
func filetypeComplete(input string) (string, []string) {
|
||||
var suggestions []string
|
||||
|
||||
// We cannot match filetypes just by names of syntax files,
|
||||
// since those names may be different from the actual filetype values
|
||||
// specified inside syntax files (e.g. "c++" filetype in cpp.yaml).
|
||||
// So we need to parse filetype values out of those files.
|
||||
for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
|
||||
data, err := f.Data()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
header, err := highlight.MakeHeaderYaml(data)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// Prevent duplicated defaults
|
||||
if header.FileType == "off" || header.FileType == "unknown" {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(header.FileType, input) {
|
||||
suggestions = append(suggestions, header.FileType)
|
||||
}
|
||||
}
|
||||
headerLoop:
|
||||
for _, f := range config.ListRuntimeFiles(config.RTSyntaxHeader) {
|
||||
data, err := f.Data()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
header, err := highlight.MakeHeader(data)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, v := range suggestions {
|
||||
if v == header.FileType {
|
||||
continue headerLoop
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(header.FileType, input) {
|
||||
suggestions = append(suggestions, header.FileType)
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix("off", input) {
|
||||
suggestions = append(suggestions, "off")
|
||||
}
|
||||
|
||||
var chosen string
|
||||
if len(suggestions) == 1 {
|
||||
chosen = suggestions[0]
|
||||
}
|
||||
|
||||
return chosen, suggestions
|
||||
}
|
||||
|
||||
func contains(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// OptionComplete autocompletes options
|
||||
func OptionComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
c := b.GetActiveCursor()
|
||||
input, argstart := b.GetArg()
|
||||
|
||||
var suggestions []string
|
||||
for option := range config.GlobalSettings {
|
||||
if strings.HasPrefix(option, input) {
|
||||
suggestions = append(suggestions, option)
|
||||
}
|
||||
}
|
||||
// for option := range localSettings {
|
||||
// if strings.HasPrefix(option, input) && !contains(suggestions, option) {
|
||||
// suggestions = append(suggestions, option)
|
||||
// }
|
||||
// }
|
||||
|
||||
sort.Strings(suggestions)
|
||||
completions := make([]string, len(suggestions))
|
||||
for i := range suggestions {
|
||||
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
|
||||
}
|
||||
return completions, suggestions
|
||||
}
|
||||
|
||||
// OptionValueComplete completes values for various options
|
||||
func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
c := b.GetActiveCursor()
|
||||
l := b.LineBytes(c.Y)
|
||||
l = util.SliceStart(l, c.X)
|
||||
input, argstart := b.GetArg()
|
||||
|
||||
completeValue := false
|
||||
args := bytes.Split(l, []byte{' '})
|
||||
if len(args) >= 2 {
|
||||
// localSettings := config.DefaultLocalSettings()
|
||||
for option := range config.GlobalSettings {
|
||||
if option == string(args[len(args)-2]) {
|
||||
completeValue = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// for option := range localSettings {
|
||||
// if option == string(args[len(args)-2]) {
|
||||
// completeValue = true
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
}
|
||||
if !completeValue {
|
||||
return OptionComplete(b)
|
||||
}
|
||||
|
||||
inputOpt := string(args[len(args)-2])
|
||||
|
||||
inputOpt = strings.TrimSpace(inputOpt)
|
||||
var suggestions []string
|
||||
// localSettings := config.DefaultLocalSettings()
|
||||
var optionVal interface{}
|
||||
for k, option := range config.GlobalSettings {
|
||||
if k == inputOpt {
|
||||
optionVal = option
|
||||
}
|
||||
}
|
||||
// for k, option := range localSettings {
|
||||
// if k == inputOpt {
|
||||
// optionVal = option
|
||||
// }
|
||||
// }
|
||||
|
||||
switch optionVal.(type) {
|
||||
case bool:
|
||||
if strings.HasPrefix("on", input) {
|
||||
suggestions = append(suggestions, "on")
|
||||
} else if strings.HasPrefix("true", input) {
|
||||
suggestions = append(suggestions, "true")
|
||||
}
|
||||
if strings.HasPrefix("off", input) {
|
||||
suggestions = append(suggestions, "off")
|
||||
} else if strings.HasPrefix("false", input) {
|
||||
suggestions = append(suggestions, "false")
|
||||
}
|
||||
case string:
|
||||
switch inputOpt {
|
||||
case "colorscheme":
|
||||
_, suggestions = colorschemeComplete(input)
|
||||
case "filetype":
|
||||
_, suggestions = filetypeComplete(input)
|
||||
case "sucmd":
|
||||
if strings.HasPrefix("sudo", input) {
|
||||
suggestions = append(suggestions, "sudo")
|
||||
}
|
||||
if strings.HasPrefix("doas", input) {
|
||||
suggestions = append(suggestions, "doas")
|
||||
}
|
||||
default:
|
||||
if choices, ok := config.OptionChoices[inputOpt]; ok {
|
||||
for _, choice := range choices {
|
||||
if strings.HasPrefix(choice, input) {
|
||||
suggestions = append(suggestions, choice)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Strings(suggestions)
|
||||
|
||||
completions := make([]string, len(suggestions))
|
||||
for i := range suggestions {
|
||||
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
|
||||
}
|
||||
return completions, suggestions
|
||||
}
|
||||
|
||||
// PluginCmdComplete autocompletes the plugin command
|
||||
func PluginCmdComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
c := b.GetActiveCursor()
|
||||
input, argstart := b.GetArg()
|
||||
|
||||
var suggestions []string
|
||||
for _, cmd := range PluginCmds {
|
||||
if strings.HasPrefix(cmd, input) {
|
||||
suggestions = append(suggestions, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(suggestions)
|
||||
completions := make([]string, len(suggestions))
|
||||
for i := range suggestions {
|
||||
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
|
||||
}
|
||||
return completions, suggestions
|
||||
}
|
||||
|
||||
// PluginComplete completes values for the plugin command
|
||||
func PluginComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
c := b.GetActiveCursor()
|
||||
l := b.LineBytes(c.Y)
|
||||
l = util.SliceStart(l, c.X)
|
||||
input, argstart := b.GetArg()
|
||||
|
||||
completeValue := false
|
||||
args := bytes.Split(l, []byte{' '})
|
||||
if len(args) >= 2 {
|
||||
for _, cmd := range PluginCmds {
|
||||
if cmd == string(args[len(args)-2]) {
|
||||
completeValue = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !completeValue {
|
||||
return PluginCmdComplete(b)
|
||||
}
|
||||
|
||||
var suggestions []string
|
||||
for _, pl := range config.Plugins {
|
||||
if strings.HasPrefix(pl.Name, input) {
|
||||
suggestions = append(suggestions, pl.Name)
|
||||
}
|
||||
}
|
||||
sort.Strings(suggestions)
|
||||
|
||||
completions := make([]string, len(suggestions))
|
||||
for i := range suggestions {
|
||||
completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
|
||||
}
|
||||
return completions, suggestions
|
||||
}
|
||||
|
||||
// PluginNameComplete completes with the names of loaded plugins
|
||||
// func PluginNameComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
// c := b.GetActiveCursor()
|
||||
// input, argstart := buffer.GetArg(b)
|
||||
//
|
||||
// var suggestions []string
|
||||
// for _, pp := range config.GetAllPluginPackages(nil) {
|
||||
// if strings.HasPrefix(pp.Name, input) {
|
||||
// suggestions = append(suggestions, pp.Name)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// sort.Strings(suggestions)
|
||||
// completions := make([]string, len(suggestions))
|
||||
// for i := range suggestions {
|
||||
// completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
|
||||
// }
|
||||
// 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))
|
||||
// }
|
|
@ -1,245 +0,0 @@
|
|||
package action
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"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/info"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
type InfoKeyAction func(*InfoPane)
|
||||
|
||||
var InfoBindings *KeyTree
|
||||
var InfoBufBindings *KeyTree
|
||||
|
||||
func init() {
|
||||
InfoBindings = NewKeyTree()
|
||||
InfoBufBindings = NewKeyTree()
|
||||
}
|
||||
|
||||
func InfoMapEvent(k Event, action string) {
|
||||
config.Bindings["command"][k.Name()] = action
|
||||
|
||||
switch e := k.(type) {
|
||||
case KeyEvent, KeySequenceEvent, RawEvent:
|
||||
infoMapKey(e, action)
|
||||
case MouseEvent:
|
||||
infoMapMouse(e, action)
|
||||
}
|
||||
}
|
||||
|
||||
func infoMapKey(k Event, action string) {
|
||||
if f, ok := InfoKeyActions[action]; ok {
|
||||
InfoBindings.RegisterKeyBinding(k, InfoKeyActionGeneral(f))
|
||||
} else if f, ok := BufKeyActions[action]; ok {
|
||||
InfoBufBindings.RegisterKeyBinding(k, BufKeyActionGeneral(f))
|
||||
}
|
||||
}
|
||||
|
||||
func infoMapMouse(k MouseEvent, action string) {
|
||||
// TODO: map mouse
|
||||
if f, ok := BufMouseActions[action]; ok {
|
||||
InfoBufBindings.RegisterMouseBinding(k, BufMouseActionGeneral(f))
|
||||
} else {
|
||||
infoMapKey(k, action)
|
||||
}
|
||||
}
|
||||
|
||||
func InfoKeyActionGeneral(a InfoKeyAction) PaneKeyAction {
|
||||
return func(p Pane) bool {
|
||||
a(p.(*InfoPane))
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
type InfoPane struct {
|
||||
*BufPane
|
||||
*info.InfoBuf
|
||||
}
|
||||
|
||||
func NewInfoPane(ib *info.InfoBuf, w display.BWindow, tab *Tab) *InfoPane {
|
||||
ip := new(InfoPane)
|
||||
ip.InfoBuf = ib
|
||||
ip.BufPane = NewBufPane(ib.Buffer, w, tab)
|
||||
ip.BufPane.bindings = InfoBufBindings
|
||||
|
||||
return ip
|
||||
}
|
||||
|
||||
func NewInfoBar() *InfoPane {
|
||||
ib := info.NewBuffer()
|
||||
w := display.NewInfoWindow(ib)
|
||||
return NewInfoPane(ib, w, nil)
|
||||
}
|
||||
|
||||
func (h *InfoPane) Close() {
|
||||
h.InfoBuf.Close()
|
||||
h.BufPane.Close()
|
||||
}
|
||||
|
||||
func (h *InfoPane) HandleEvent(event tcell.Event) {
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventResize:
|
||||
// TODO
|
||||
case *tcell.EventKey:
|
||||
ke := KeyEvent{
|
||||
code: e.Key(),
|
||||
mod: metaToAlt(e.Modifiers()),
|
||||
r: e.Rune(),
|
||||
}
|
||||
|
||||
done := h.DoKeyEvent(ke)
|
||||
hasYN := h.HasYN
|
||||
if e.Key() == tcell.KeyRune && hasYN {
|
||||
y := e.Rune() == 'y' || e.Rune() == 'Y'
|
||||
n := e.Rune() == 'n' || e.Rune() == 'N'
|
||||
if y || n {
|
||||
h.YNResp = y
|
||||
h.DonePrompt(false)
|
||||
|
||||
InfoBindings.ResetEvents()
|
||||
InfoBufBindings.ResetEvents()
|
||||
}
|
||||
}
|
||||
if e.Key() == tcell.KeyRune && !done && !hasYN {
|
||||
h.DoRuneInsert(e.Rune())
|
||||
done = true
|
||||
}
|
||||
if done && h.HasPrompt && !hasYN {
|
||||
resp := string(h.LineBytes(0))
|
||||
hist := h.History[h.PromptType]
|
||||
if resp != hist[h.HistoryNum] {
|
||||
h.HistoryNum = len(hist) - 1
|
||||
hist[h.HistoryNum] = resp
|
||||
h.HistorySearch = false
|
||||
}
|
||||
if h.EventCallback != nil {
|
||||
h.EventCallback(resp)
|
||||
}
|
||||
}
|
||||
default:
|
||||
h.BufPane.HandleEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
// DoKeyEvent executes a key event for the command bar, doing any overridden actions.
|
||||
// Returns true if the action was executed OR if there are more keys remaining
|
||||
// to process before executing an action (if this is a key sequence event).
|
||||
// Returns false if no action found.
|
||||
func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
|
||||
action, more := InfoBindings.NextEvent(e, nil)
|
||||
if action != nil && !more {
|
||||
action(h)
|
||||
InfoBindings.ResetEvents()
|
||||
|
||||
return true
|
||||
} else if action == nil && !more {
|
||||
InfoBindings.ResetEvents()
|
||||
// return false //TODO:?
|
||||
}
|
||||
|
||||
if !more {
|
||||
// If no infopane action found, try to find a bufpane action.
|
||||
//
|
||||
// TODO: this is buggy. For example, if the command bar has the following
|
||||
// two bindings:
|
||||
//
|
||||
// "<Ctrl-x><Ctrl-p>": "HistoryUp",
|
||||
// "<Ctrl-x><Ctrl-v>": "Paste",
|
||||
//
|
||||
// the 2nd binding (with a bufpane action) doesn't work, since <Ctrl-x>
|
||||
// has been already consumed by the 1st binding (with an infopane action).
|
||||
//
|
||||
// We should either iterate both InfoBindings and InfoBufBindings keytrees
|
||||
// together, or just use the same keytree for both infopane and bufpane
|
||||
// bindings.
|
||||
action, more = InfoBufBindings.NextEvent(e, nil)
|
||||
if action != nil && !more {
|
||||
action(h.BufPane)
|
||||
InfoBufBindings.ResetEvents()
|
||||
return true
|
||||
} else if action == nil && !more {
|
||||
InfoBufBindings.ResetEvents()
|
||||
}
|
||||
}
|
||||
|
||||
return more
|
||||
}
|
||||
|
||||
// HistoryUp cycles history up
|
||||
func (h *InfoPane) HistoryUp() {
|
||||
h.UpHistory(h.History[h.PromptType])
|
||||
}
|
||||
|
||||
// HistoryDown cycles history down
|
||||
func (h *InfoPane) HistoryDown() {
|
||||
h.DownHistory(h.History[h.PromptType])
|
||||
}
|
||||
|
||||
// HistorySearchUp fetches the previous history item beginning with the text
|
||||
// in the infobuffer before cursor
|
||||
func (h *InfoPane) HistorySearchUp() {
|
||||
h.SearchUpHistory(h.History[h.PromptType])
|
||||
}
|
||||
|
||||
// HistorySearchDown fetches the next history item beginning with the text
|
||||
// in the infobuffer before cursor
|
||||
func (h *InfoPane) HistorySearchDown() {
|
||||
h.SearchDownHistory(h.History[h.PromptType])
|
||||
}
|
||||
|
||||
// Autocomplete begins autocompletion
|
||||
func (h *InfoPane) CommandComplete() {
|
||||
b := h.Buf
|
||||
if b.HasSuggestions {
|
||||
b.CycleAutocomplete(true)
|
||||
return
|
||||
}
|
||||
|
||||
c := b.GetActiveCursor()
|
||||
l := b.LineBytes(0)
|
||||
l = util.SliceStart(l, c.X)
|
||||
|
||||
args := bytes.Split(l, []byte{' '})
|
||||
cmd := string(args[0])
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuteCommand completes the prompt
|
||||
func (h *InfoPane) ExecuteCommand() {
|
||||
if !h.HasYN {
|
||||
h.DonePrompt(false)
|
||||
}
|
||||
}
|
||||
|
||||
// AbortCommand cancels the prompt
|
||||
func (h *InfoPane) AbortCommand() {
|
||||
h.DonePrompt(true)
|
||||
}
|
||||
|
||||
// InfoKeyActions contains the list of all possible key actions the infopane could execute
|
||||
var InfoKeyActions = map[string]InfoKeyAction{
|
||||
"HistoryUp": (*InfoPane).HistoryUp,
|
||||
"HistoryDown": (*InfoPane).HistoryDown,
|
||||
"HistorySearchUp": (*InfoPane).HistorySearchUp,
|
||||
"HistorySearchDown": (*InfoPane).HistorySearchDown,
|
||||
"CommandComplete": (*InfoPane).CommandComplete,
|
||||
"ExecuteCommand": (*InfoPane).ExecuteCommand,
|
||||
"AbortCommand": (*InfoPane).AbortCommand,
|
||||
}
|
|
@ -1,261 +0,0 @@
|
|||
package action
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
type PaneKeyAction func(Pane) bool
|
||||
type PaneMouseAction func(Pane, *tcell.EventMouse) bool
|
||||
type PaneKeyAnyAction func(Pane, []KeyEvent) bool
|
||||
|
||||
// A KeyTreeNode stores a single node in the KeyTree (trie). The
|
||||
// children are stored as a map, and any node may store a list of
|
||||
// actions (the list will be nil if no actions correspond to a certain
|
||||
// node)
|
||||
type KeyTreeNode struct {
|
||||
children map[Event]*KeyTreeNode
|
||||
|
||||
// Only one of these actions may be active in the current
|
||||
// mode, and only one will be returned. If multiple actions
|
||||
// are active, it is undefined which one will be the one
|
||||
// returned.
|
||||
actions []TreeAction
|
||||
}
|
||||
|
||||
func NewKeyTreeNode() *KeyTreeNode {
|
||||
n := new(KeyTreeNode)
|
||||
n.children = make(map[Event]*KeyTreeNode)
|
||||
n.actions = []TreeAction{}
|
||||
return n
|
||||
}
|
||||
|
||||
// A TreeAction stores an action, and a set of mode constraints for
|
||||
// the action to be active.
|
||||
type TreeAction struct {
|
||||
// only one of these can be non-nil
|
||||
action PaneKeyAction
|
||||
any PaneKeyAnyAction
|
||||
mouse PaneMouseAction
|
||||
|
||||
modes []ModeConstraint
|
||||
}
|
||||
|
||||
// A KeyTree is a data structure for storing keybindings. It maps
|
||||
// key events to actions, and maintains a set of currently enabled
|
||||
// modes, which affects the action that is returned for a key event.
|
||||
// The tree acts like a Trie for Events to handle sequence events.
|
||||
type KeyTree struct {
|
||||
root *KeyTreeNode
|
||||
modes map[string]bool
|
||||
|
||||
cursor KeyTreeCursor
|
||||
}
|
||||
|
||||
// A KeyTreeCursor keeps track of the current location within the
|
||||
// tree, and stores any information from previous events that may
|
||||
// be needed to execute the action (values of wildcard events or
|
||||
// mouse events)
|
||||
type KeyTreeCursor struct {
|
||||
node *KeyTreeNode
|
||||
|
||||
recordedEvents []Event
|
||||
wildcards []KeyEvent
|
||||
mouseInfo *tcell.EventMouse
|
||||
}
|
||||
|
||||
// MakeClosure uses the information stored in a key tree cursor to construct
|
||||
// a PaneKeyAction from a TreeAction (which may have a PaneKeyAction, PaneMouseAction,
|
||||
// or AnyAction)
|
||||
func (k *KeyTreeCursor) MakeClosure(a TreeAction) PaneKeyAction {
|
||||
if a.action != nil {
|
||||
return a.action
|
||||
} else if a.any != nil {
|
||||
return func(p Pane) bool {
|
||||
return a.any(p, k.wildcards)
|
||||
}
|
||||
} else if a.mouse != nil {
|
||||
return func(p Pane) bool {
|
||||
return a.mouse(p, k.mouseInfo)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewKeyTree allocates and returns an empty key tree
|
||||
func NewKeyTree() *KeyTree {
|
||||
root := NewKeyTreeNode()
|
||||
tree := new(KeyTree)
|
||||
|
||||
tree.root = root
|
||||
tree.modes = make(map[string]bool)
|
||||
tree.cursor = KeyTreeCursor{
|
||||
node: root,
|
||||
wildcards: []KeyEvent{},
|
||||
mouseInfo: nil,
|
||||
}
|
||||
|
||||
return tree
|
||||
}
|
||||
|
||||
// A ModeConstraint specifies that an action can only be executed
|
||||
// while a certain mode is enabled or disabled.
|
||||
type ModeConstraint struct {
|
||||
mode string
|
||||
disabled bool
|
||||
}
|
||||
|
||||
// RegisterKeyBinding registers a PaneKeyAction with an Event.
|
||||
func (k *KeyTree) RegisterKeyBinding(e Event, a PaneKeyAction) {
|
||||
k.registerBinding(e, TreeAction{
|
||||
action: a,
|
||||
any: nil,
|
||||
mouse: nil,
|
||||
modes: nil,
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterKeyAnyBinding registers a PaneKeyAnyAction with an Event.
|
||||
// The event should contain an "any" event.
|
||||
func (k *KeyTree) RegisterKeyAnyBinding(e Event, a PaneKeyAnyAction) {
|
||||
k.registerBinding(e, TreeAction{
|
||||
action: nil,
|
||||
any: a,
|
||||
mouse: nil,
|
||||
modes: nil,
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterMouseBinding registers a PaneMouseAction with an Event.
|
||||
// The event should contain a mouse event.
|
||||
func (k *KeyTree) RegisterMouseBinding(e Event, a PaneMouseAction) {
|
||||
k.registerBinding(e, TreeAction{
|
||||
action: nil,
|
||||
any: nil,
|
||||
mouse: a,
|
||||
modes: nil,
|
||||
})
|
||||
}
|
||||
|
||||
func (k *KeyTree) registerBinding(e Event, a TreeAction) {
|
||||
switch ev := e.(type) {
|
||||
case KeyEvent, MouseEvent, RawEvent:
|
||||
newNode, ok := k.root.children[e]
|
||||
if !ok {
|
||||
newNode = NewKeyTreeNode()
|
||||
k.root.children[e] = newNode
|
||||
}
|
||||
// newNode.actions = append(newNode.actions, a)
|
||||
newNode.actions = []TreeAction{a}
|
||||
case KeySequenceEvent:
|
||||
n := k.root
|
||||
for _, key := range ev.keys {
|
||||
newNode, ok := n.children[key]
|
||||
if !ok {
|
||||
newNode = NewKeyTreeNode()
|
||||
n.children[key] = newNode
|
||||
}
|
||||
|
||||
n = newNode
|
||||
}
|
||||
// n.actions = append(n.actions, a)
|
||||
n.actions = []TreeAction{a}
|
||||
}
|
||||
}
|
||||
|
||||
// NextEvent returns the action for the current sequence where e is the next
|
||||
// event. Even if the action was registered as a PaneKeyAnyAction or PaneMouseAction,
|
||||
// it will be returned as a PaneKeyAction closure where the appropriate arguments
|
||||
// have been provided.
|
||||
// If no action is associated with the given Event, or mode constraints are not
|
||||
// met for that action, nil is returned.
|
||||
// A boolean is returned to indicate if there is a conflict with this action. A
|
||||
// conflict occurs when there is an active action for this event but there are
|
||||
// bindings associated with further sequences starting with this event. The
|
||||
// calling function can decide what to do about the conflict (e.g. use a
|
||||
// timeout).
|
||||
func (k *KeyTree) NextEvent(e Event, mouse *tcell.EventMouse) (PaneKeyAction, bool) {
|
||||
n := k.cursor.node
|
||||
c, ok := n.children[e]
|
||||
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
more := len(c.children) > 0
|
||||
|
||||
k.cursor.node = c
|
||||
|
||||
k.cursor.recordedEvents = append(k.cursor.recordedEvents, e)
|
||||
|
||||
switch ev := e.(type) {
|
||||
case KeyEvent:
|
||||
if ev.any {
|
||||
k.cursor.wildcards = append(k.cursor.wildcards, ev)
|
||||
}
|
||||
case MouseEvent:
|
||||
k.cursor.mouseInfo = mouse
|
||||
}
|
||||
|
||||
if len(c.actions) > 0 {
|
||||
// check if actions are active
|
||||
for _, a := range c.actions {
|
||||
active := true
|
||||
for _, mc := range a.modes {
|
||||
// if any mode constraint is not met, the action is not active
|
||||
hasMode := k.modes[mc.mode]
|
||||
if hasMode != mc.disabled {
|
||||
active = false
|
||||
}
|
||||
}
|
||||
|
||||
if active {
|
||||
// the first active action to be found is returned
|
||||
return k.cursor.MakeClosure(a), more
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, more
|
||||
}
|
||||
|
||||
// ResetEvents sets the current sequence back to the initial value.
|
||||
func (k *KeyTree) ResetEvents() {
|
||||
k.cursor.node = k.root
|
||||
k.cursor.wildcards = []KeyEvent{}
|
||||
k.cursor.recordedEvents = []Event{}
|
||||
k.cursor.mouseInfo = nil
|
||||
}
|
||||
|
||||
// RecordedEventsStr returns the list of recorded events as a string
|
||||
func (k *KeyTree) RecordedEventsStr() string {
|
||||
buf := &bytes.Buffer{}
|
||||
for _, e := range k.cursor.recordedEvents {
|
||||
buf.WriteString(e.Name())
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// DeleteBinding removes any currently active actions associated with the
|
||||
// given event.
|
||||
func (k *KeyTree) DeleteBinding(e Event) {
|
||||
|
||||
}
|
||||
|
||||
// DeleteAllBindings removes all actions associated with the given event,
|
||||
// regardless of whether they are active or not.
|
||||
func (k *KeyTree) DeleteAllBindings(e Event) {
|
||||
|
||||
}
|
||||
|
||||
// SetMode enables or disabled a given mode
|
||||
func (k *KeyTree) SetMode(mode string, en bool) {
|
||||
k.modes[mode] = en
|
||||
}
|
||||
|
||||
// HasMode returns if the given mode is currently active
|
||||
func (k *KeyTree) HasMode(mode string) bool {
|
||||
return k.modes[mode]
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package action
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
)
|
||||
|
||||
// A Pane is a general interface for a window in the editor.
|
||||
type Pane interface {
|
||||
Handler
|
||||
display.Window
|
||||
ID() uint64
|
||||
SetID(i uint64)
|
||||
Name() string
|
||||
Close()
|
||||
SetTab(t *Tab)
|
||||
Tab() *Tab
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package action
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
type RawPane struct {
|
||||
*BufPane
|
||||
}
|
||||
|
||||
func NewRawPaneFromWin(b *buffer.Buffer, win display.BWindow, tab *Tab) *RawPane {
|
||||
rh := new(RawPane)
|
||||
rh.BufPane = NewBufPane(b, win, tab)
|
||||
|
||||
return rh
|
||||
}
|
||||
|
||||
func NewRawPane(tab *Tab) *RawPane {
|
||||
b := buffer.NewBufferFromString("", "", buffer.BTRaw)
|
||||
w := display.NewBufWindow(0, 0, 0, 0, b)
|
||||
return NewRawPaneFromWin(b, w, tab)
|
||||
}
|
||||
|
||||
func (h *RawPane) HandleEvent(event tcell.Event) {
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
if e.Key() == tcell.KeyCtrlQ {
|
||||
h.Quit()
|
||||
}
|
||||
}
|
||||
|
||||
h.Buf.Insert(h.Cursor.Loc, reflect.TypeOf(event).String()[7:])
|
||||
|
||||
e, err := ConstructEvent(event)
|
||||
if err == nil {
|
||||
h.Buf.Insert(h.Cursor.Loc, fmt.Sprintf(": %s", e.Name()))
|
||||
}
|
||||
|
||||
h.Buf.Insert(h.Cursor.Loc, fmt.Sprintf(": %q\n", event.EscSeq()))
|
||||
|
||||
h.Relocate()
|
||||
}
|
|
@ -1,384 +0,0 @@
|
|||
package action
|
||||
|
||||
import (
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
"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/views"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
// The TabList is a list of tabs and a window to display the tab bar
|
||||
// at the top of the screen
|
||||
type TabList struct {
|
||||
*display.TabWindow
|
||||
List []*Tab
|
||||
}
|
||||
|
||||
// NewTabList creates a TabList from a list of buffers by creating a Tab
|
||||
// for each buffer
|
||||
func NewTabList(bufs []*buffer.Buffer) *TabList {
|
||||
w, h := screen.Screen.Size()
|
||||
iOffset := config.GetInfoBarOffset()
|
||||
tl := new(TabList)
|
||||
tl.List = make([]*Tab, len(bufs))
|
||||
if len(bufs) > 1 {
|
||||
for i, b := range bufs {
|
||||
tl.List[i] = NewTabFromBuffer(0, 1, w, h-1-iOffset, b)
|
||||
}
|
||||
} else {
|
||||
tl.List[0] = NewTabFromBuffer(0, 0, w, h-iOffset, bufs[0])
|
||||
}
|
||||
tl.TabWindow = display.NewTabWindow(w, 0)
|
||||
tl.Names = make([]string, len(bufs))
|
||||
|
||||
return tl
|
||||
}
|
||||
|
||||
// UpdateNames makes sure that the list of names the tab window has access to is
|
||||
// correct
|
||||
func (t *TabList) UpdateNames() {
|
||||
t.Names = t.Names[:0]
|
||||
for _, p := range t.List {
|
||||
t.Names = append(t.Names, p.Panes[p.active].Name())
|
||||
}
|
||||
}
|
||||
|
||||
// AddTab adds a new tab to this TabList
|
||||
func (t *TabList) AddTab(p *Tab) {
|
||||
t.List = append(t.List, p)
|
||||
t.Resize()
|
||||
t.UpdateNames()
|
||||
}
|
||||
|
||||
// RemoveTab removes a tab with the given id from the TabList
|
||||
func (t *TabList) RemoveTab(id uint64) {
|
||||
for i, p := range t.List {
|
||||
if len(p.Panes) == 0 {
|
||||
continue
|
||||
}
|
||||
if p.Panes[0].ID() == id {
|
||||
copy(t.List[i:], t.List[i+1:])
|
||||
t.List[len(t.List)-1] = nil
|
||||
t.List = t.List[:len(t.List)-1]
|
||||
if t.Active() >= len(t.List) {
|
||||
t.SetActive(len(t.List) - 1)
|
||||
}
|
||||
t.Resize()
|
||||
t.UpdateNames()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resize resizes all elements within the tab list
|
||||
// One thing to note is that when there is only 1 tab
|
||||
// the tab bar should not be drawn so resizing must take
|
||||
// that into account
|
||||
func (t *TabList) Resize() {
|
||||
w, h := screen.Screen.Size()
|
||||
iOffset := config.GetInfoBarOffset()
|
||||
InfoBar.Resize(w, h-1)
|
||||
if len(t.List) > 1 {
|
||||
for _, p := range t.List {
|
||||
p.Y = 1
|
||||
p.Node.Resize(w, h-1-iOffset)
|
||||
p.Resize()
|
||||
}
|
||||
} else if len(t.List) == 1 {
|
||||
t.List[0].Y = 0
|
||||
t.List[0].Node.Resize(w, h-iOffset)
|
||||
t.List[0].Resize()
|
||||
}
|
||||
t.TabWindow.Resize(w, h)
|
||||
}
|
||||
|
||||
// HandleEvent checks for a resize event or a mouse event on the tab bar
|
||||
// otherwise it will forward the event to the currently active tab
|
||||
func (t *TabList) HandleEvent(event tcell.Event) {
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventResize:
|
||||
t.Resize()
|
||||
case *tcell.EventMouse:
|
||||
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 {
|
||||
t.SetActive(ind)
|
||||
return
|
||||
}
|
||||
if my == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
case tcell.ButtonNone:
|
||||
if t.List[t.Active()].release {
|
||||
// Mouse release received, while already released
|
||||
t.ResetMouse()
|
||||
return
|
||||
}
|
||||
case tcell.WheelUp:
|
||||
if my == t.Y {
|
||||
t.Scroll(4)
|
||||
return
|
||||
}
|
||||
case tcell.WheelDown:
|
||||
if my == t.Y {
|
||||
t.Scroll(-4)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
t.List[t.Active()].HandleEvent(event)
|
||||
}
|
||||
|
||||
// Display updates the names and then displays the tab bar
|
||||
func (t *TabList) Display() {
|
||||
t.UpdateNames()
|
||||
if len(t.List) > 1 {
|
||||
t.TabWindow.Display()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TabList) SetActive(a int) {
|
||||
t.TabWindow.SetActive(a)
|
||||
|
||||
for i, p := range t.List {
|
||||
if i == a {
|
||||
if !p.isActive {
|
||||
p.isActive = true
|
||||
|
||||
err := config.RunPluginFn("onSetActive", luar.New(ulua.L, p.CurPane()))
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
p.isActive = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ResetMouse resets the mouse release state after the screen was stopped
|
||||
// or the pane changed.
|
||||
// This prevents situations in which mouse releases are received at the wrong place
|
||||
// and the mouse state is still pressed.
|
||||
func (t *TabList) ResetMouse() {
|
||||
for _, tab := range t.List {
|
||||
if !tab.release && tab.resizing != nil {
|
||||
tab.resizing = nil
|
||||
}
|
||||
|
||||
tab.release = true
|
||||
|
||||
for _, p := range tab.Panes {
|
||||
if bp, ok := p.(*BufPane); ok {
|
||||
bp.resetMouse()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tabs is the global tab list
|
||||
var Tabs *TabList
|
||||
|
||||
func InitTabs(bufs []*buffer.Buffer) {
|
||||
multiopen := config.GetGlobalOption("multiopen").(string)
|
||||
if multiopen == "tab" {
|
||||
Tabs = NewTabList(bufs)
|
||||
} else {
|
||||
Tabs = NewTabList(bufs[:1])
|
||||
for _, b := range bufs[1:] {
|
||||
if multiopen == "vsplit" {
|
||||
MainTab().CurPane().VSplitBuf(b)
|
||||
} else { // default hsplit
|
||||
MainTab().CurPane().HSplitBuf(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
screen.RestartCallback = Tabs.ResetMouse
|
||||
}
|
||||
|
||||
func MainTab() *Tab {
|
||||
return Tabs.List[Tabs.Active()]
|
||||
}
|
||||
|
||||
// A Tab represents a single tab
|
||||
// It consists of a list of edit panes (the open buffers),
|
||||
// a split tree (stored as just the root node), and a uiwindow
|
||||
// to display the UI elements like the borders between splits
|
||||
type Tab struct {
|
||||
*views.Node
|
||||
*display.UIWindow
|
||||
|
||||
isActive bool
|
||||
|
||||
Panes []Pane
|
||||
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
|
||||
func NewTabFromBuffer(x, y, width, height int, b *buffer.Buffer) *Tab {
|
||||
t := new(Tab)
|
||||
t.Node = views.NewRoot(x, y, width, height)
|
||||
t.UIWindow = display.NewUIWindow(t.Node)
|
||||
t.release = true
|
||||
|
||||
e := NewBufPaneFromBuf(b, t)
|
||||
e.SetID(t.ID())
|
||||
|
||||
t.Panes = append(t.Panes, e)
|
||||
return t
|
||||
}
|
||||
|
||||
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)
|
||||
t.release = true
|
||||
pane.SetTab(t)
|
||||
pane.SetID(t.ID())
|
||||
|
||||
t.Panes = append(t.Panes, pane)
|
||||
return t
|
||||
}
|
||||
|
||||
// HandleEvent takes a tcell event and usually dispatches it to the current
|
||||
// active pane. However if the event is a resize or a mouse event where the user
|
||||
// is interacting with the UI (resizing splits) then the event is consumed here
|
||||
// If the event is a mouse press event in a pane, that pane will become active
|
||||
// and get the event
|
||||
func (t *Tab) HandleEvent(event tcell.Event) {
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventMouse:
|
||||
mx, my := e.Position()
|
||||
btn := e.Buttons()
|
||||
switch {
|
||||
case btn & ^(tcell.WheelUp|tcell.WheelDown|tcell.WheelLeft|tcell.WheelRight) != tcell.ButtonNone:
|
||||
// button press or drag
|
||||
wasReleased := t.release
|
||||
t.release = false
|
||||
|
||||
if btn == tcell.Button1 {
|
||||
if t.resizing != nil {
|
||||
var size int
|
||||
if t.resizing.Kind == views.STVert {
|
||||
size = mx - t.resizing.X
|
||||
} else {
|
||||
size = my - t.resizing.Y + 1
|
||||
}
|
||||
t.resizing.ResizeSplit(size)
|
||||
t.Resize()
|
||||
return
|
||||
}
|
||||
if wasReleased {
|
||||
t.resizing = t.GetMouseSplitNode(buffer.Loc{mx, my})
|
||||
if t.resizing != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if wasReleased {
|
||||
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 btn == tcell.ButtonNone:
|
||||
// button release
|
||||
t.release = true
|
||||
if t.resizing != nil {
|
||||
t.resizing = nil
|
||||
return
|
||||
}
|
||||
default:
|
||||
// wheel move
|
||||
for _, 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 {
|
||||
p.HandleEvent(event)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
t.Panes[t.active].HandleEvent(event)
|
||||
}
|
||||
|
||||
// SetActive changes the currently active pane to the specified index
|
||||
func (t *Tab) SetActive(i int) {
|
||||
t.active = i
|
||||
for j, p := range t.Panes {
|
||||
if j == i {
|
||||
p.SetActive(true)
|
||||
} else {
|
||||
p.SetActive(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetPane returns the pane with the given split index
|
||||
func (t *Tab) GetPane(splitid uint64) int {
|
||||
for i, p := range t.Panes {
|
||||
if p.ID() == splitid {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Remove pane removes the pane with the given index
|
||||
func (t *Tab) RemovePane(i int) {
|
||||
copy(t.Panes[i:], t.Panes[i+1:])
|
||||
t.Panes[len(t.Panes)-1] = nil
|
||||
t.Panes = t.Panes[:len(t.Panes)-1]
|
||||
}
|
||||
|
||||
// Resize resizes all panes according to their corresponding split nodes
|
||||
func (t *Tab) Resize() {
|
||||
for _, p := range t.Panes {
|
||||
n := t.GetNode(p.ID())
|
||||
pv := p.GetView()
|
||||
offset := 0
|
||||
if n.X != 0 {
|
||||
offset = 1
|
||||
}
|
||||
pv.X, pv.Y = n.X+offset, n.Y
|
||||
p.SetView(pv)
|
||||
p.Resize(n.W-offset, n.H)
|
||||
}
|
||||
}
|
||||
|
||||
// CurPane returns the currently active pane
|
||||
func (t *Tab) CurPane() *BufPane {
|
||||
p, ok := t.Panes[t.active].(*BufPane)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return p
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
// +build linux darwin dragonfly openbsd_amd64 freebsd
|
||||
|
||||
package action
|
||||
|
||||
import (
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
)
|
||||
|
||||
// TermEmuSupported is a constant that marks if the terminal emulator is supported
|
||||
const TermEmuSupported = true
|
||||
|
||||
// RunTermEmulator starts a terminal emulator from a bufpane with the given input (command)
|
||||
// if wait is true it will wait for the user to exit by pressing enter once the executable has terminated
|
||||
// if getOutput is true it will redirect the stdout of the process to a pipe which will be passed to the
|
||||
// callback which is a function that takes a string and a list of optional user arguments
|
||||
func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
|
||||
args, err := shellquote.Split(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
t := new(shell.Terminal)
|
||||
err = t.Start(args, getOutput, wait, callback, userargs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.AddTab()
|
||||
id := MainTab().Panes[0].ID()
|
||||
|
||||
v := h.GetView()
|
||||
|
||||
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
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd_amd64
|
||||
|
||||
package action
|
||||
|
||||
import "errors"
|
||||
|
||||
// TermEmuSupported is a constant that marks if the terminal emulator is supported
|
||||
const TermEmuSupported = false
|
||||
|
||||
// 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")
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue