mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-30 22:57:15 +09:00
Compare commits
337 Commits
v2.0.14-rc
...
performanc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31b26da647 | ||
|
|
832cce7531 | ||
|
|
187ba51fd6 | ||
|
|
2e1249cc67 | ||
|
|
ceea2378f6 | ||
|
|
dc2d70bfe1 | ||
|
|
3e95779cf0 | ||
|
|
28e1b020e4 | ||
|
|
0a4e15b5a7 | ||
|
|
ab8c242044 | ||
|
|
4ead0e453b | ||
|
|
20842c0d30 | ||
|
|
5c98734f56 | ||
|
|
2e278712ef | ||
|
|
d1426b6fb2 | ||
|
|
a544015a35 | ||
|
|
adfc136506 | ||
|
|
ee09a0354a | ||
|
|
9a6c827880 | ||
|
|
6a62575bcf | ||
|
|
467eb88df0 | ||
|
|
d1ceacad88 | ||
|
|
331c43ebb5 | ||
|
|
118f5a3e35 | ||
|
|
70d9b64301 | ||
|
|
4debd29ccf | ||
|
|
c80e5cd97f | ||
|
|
70dfc7fcb4 | ||
|
|
a2620d1c02 | ||
|
|
560bfcf749 | ||
|
|
a577fc95ff | ||
|
|
bc5e59c670 | ||
|
|
4f1d2bb543 | ||
|
|
7a250b7df4 | ||
|
|
b39b5b5916 | ||
|
|
9183fbe640 | ||
|
|
fa68314123 | ||
|
|
ccf0a9f6d6 | ||
|
|
115e560ee2 | ||
|
|
284942dffd | ||
|
|
bab39079b3 | ||
|
|
02611f4ad2 | ||
|
|
1ce2202d9a | ||
|
|
78d2c617ed | ||
|
|
815ca0b6d8 | ||
|
|
ec8bb7c11d | ||
|
|
dcdddc191b | ||
|
|
85b4b2b788 | ||
|
|
ad24089e4e | ||
|
|
ed970eede3 | ||
|
|
4d95f5f121 | ||
|
|
45342bb0f1 | ||
|
|
1ef6459846 | ||
|
|
532c315f79 | ||
|
|
7b01fe4f56 | ||
|
|
0b9c7c0c4a | ||
|
|
e9f241af71 | ||
|
|
bbea2a3f28 | ||
|
|
b37fa2e34d | ||
|
|
4b2f8aa828 | ||
|
|
52f629cee7 | ||
|
|
d9245d9659 | ||
|
|
3fd2fe3cc7 | ||
|
|
0277516eef | ||
|
|
b8057f28c6 | ||
|
|
094b02da4c | ||
|
|
7a252f4986 | ||
|
|
6b15bf3b19 | ||
|
|
421da6752e | ||
|
|
c7d65d113a | ||
|
|
2b5eb3eca2 | ||
|
|
20ebca9be4 | ||
|
|
f735ff04b4 | ||
|
|
86a9fac7ef | ||
|
|
1dbb058773 | ||
|
|
bce573b6c9 | ||
|
|
774a6d8036 | ||
|
|
177f4d5b01 | ||
|
|
165a5a4896 | ||
|
|
0a9fa4f2ea | ||
|
|
a862c9709e | ||
|
|
04b878bc2d | ||
|
|
e127f08251 | ||
|
|
7aa495fd3f | ||
|
|
2c010afbe4 | ||
|
|
e84d44d451 | ||
|
|
f938f62e31 | ||
|
|
4ade5cdf24 | ||
|
|
21edb89693 | ||
|
|
1096c4f3ba | ||
|
|
c9f84cd2b7 | ||
|
|
f97cba34d2 | ||
|
|
87ce5738e1 | ||
|
|
d7e43d4974 | ||
|
|
dbdf753f27 | ||
|
|
7492195a5b | ||
|
|
3a7705a090 | ||
|
|
55a553041b | ||
|
|
532a229c57 | ||
|
|
cf92f77fdc | ||
|
|
0694cd2c1b | ||
|
|
c267c7c9aa | ||
|
|
ffdd4b43dd | ||
|
|
61d7f68f9b | ||
|
|
41b912b539 | ||
|
|
da02f836ff | ||
|
|
4db233acf4 | ||
|
|
e923640b37 | ||
|
|
73066fb69b | ||
|
|
baa632e6cc | ||
|
|
7861b00cd1 | ||
|
|
54ba3cdb4f | ||
|
|
97b5e3506e | ||
|
|
29dc892009 | ||
|
|
5eddf5b85d | ||
|
|
cd0dc9a701 | ||
|
|
bf255b6c35 | ||
|
|
98ff79dbca | ||
|
|
44d0368747 | ||
|
|
895d9d2c82 | ||
|
|
809db4ee24 | ||
|
|
58b6917526 | ||
|
|
63b6a1e6cf | ||
|
|
4769a94fb1 | ||
|
|
91832d0016 | ||
|
|
06fe85c8c9 | ||
|
|
ca32ffbb4a | ||
|
|
b61c8a4e1a | ||
|
|
333770bbd0 | ||
|
|
7e583fe6ff | ||
|
|
1eef4bb3e0 | ||
|
|
8e7089993d | ||
|
|
080d216ffd | ||
|
|
e5c3a3edc3 | ||
|
|
c457ae421a | ||
|
|
0d5b2b73e3 | ||
|
|
79fe4ae3e3 | ||
|
|
b88300ef7f | ||
|
|
d36e7f4057 | ||
|
|
2bb3c9aa73 | ||
|
|
f2454c9248 | ||
|
|
a699cac3be | ||
|
|
9fdf5f3a26 | ||
|
|
f4d62a498b | ||
|
|
948b05745f | ||
|
|
eadc402ae0 | ||
|
|
1bd86a8f79 | ||
|
|
219fb12482 | ||
|
|
02e69dddbe | ||
|
|
78f0a9cd30 | ||
|
|
0b75031ac5 | ||
|
|
fa317456e9 | ||
|
|
82b700390d | ||
|
|
9003243178 | ||
|
|
7d16dcdaa6 | ||
|
|
c9f12206e9 | ||
|
|
98356765c1 | ||
|
|
2ae9812f47 | ||
|
|
85e2b3bd86 | ||
|
|
3c68655f33 | ||
|
|
8b21724c6e | ||
|
|
fe134b92d5 | ||
|
|
6164050425 | ||
|
|
49aebe8aca | ||
|
|
79ce93fb7d | ||
|
|
771aab251c | ||
|
|
35d295dd04 | ||
|
|
8c883c6210 | ||
|
|
c4dcef3e66 | ||
|
|
e15bb88270 | ||
|
|
9592bb1549 | ||
|
|
f8d98558f0 | ||
|
|
c926649496 | ||
|
|
63d68ec441 | ||
|
|
c972360386 | ||
|
|
022ec0228a | ||
|
|
4ac8c786f5 | ||
|
|
21b7080935 | ||
|
|
1663a1a6e4 | ||
|
|
9b53257e50 | ||
|
|
6e8daa117a | ||
|
|
18a81f043c | ||
|
|
69064cf808 | ||
|
|
e828027cc0 | ||
|
|
c2bc4688dd | ||
|
|
5aac42dbe7 | ||
|
|
42ae05b082 | ||
|
|
0b871e174f | ||
|
|
7c659d1820 | ||
|
|
6066c1a10e | ||
|
|
6bcec2100c | ||
|
|
edc5ff75e3 | ||
|
|
3fcaf16074 | ||
|
|
5c21241fc4 | ||
|
|
272a308275 | ||
|
|
c93747926d | ||
|
|
0985d2cadd | ||
|
|
ddc6051b33 | ||
|
|
2e94235905 | ||
|
|
4a9058c3bd | ||
|
|
982a4fe065 | ||
|
|
930fbea74d | ||
|
|
00e568640c | ||
|
|
d992c606c5 | ||
|
|
4abd966a99 | ||
|
|
5a62a8ead4 | ||
|
|
bf4156c490 | ||
|
|
b9f1fc8da2 | ||
|
|
728526682e | ||
|
|
c105c940fe | ||
|
|
cdc9ab17f2 | ||
|
|
b432bb7cfa | ||
|
|
e4b0ad7107 | ||
|
|
57a6e81ddb | ||
|
|
5ee7fb6014 | ||
|
|
7aa72b6a96 | ||
|
|
dc18642985 | ||
|
|
c02036e52f | ||
|
|
698511c5b6 | ||
|
|
c61670e86f | ||
|
|
6309136322 | ||
|
|
9e46a38536 | ||
|
|
f5debdf8fe | ||
|
|
4377e56e7e | ||
|
|
9b3f7ff240 | ||
|
|
f49487dc3a | ||
|
|
c77ed02778 | ||
|
|
d9956bde38 | ||
|
|
ab6e170ec9 | ||
|
|
4d97076479 | ||
|
|
a883c14c18 | ||
|
|
ce356c7957 | ||
|
|
2ddf461ad8 | ||
|
|
6600430e88 | ||
|
|
82467ba9f6 | ||
|
|
99a27db4f7 | ||
|
|
58d38af8cd | ||
|
|
d1f54ea2a4 | ||
|
|
6b21fc5f92 | ||
|
|
de84da068d | ||
|
|
771b84141f | ||
|
|
415ceee46b | ||
|
|
aa24590070 | ||
|
|
505aad8ba0 | ||
|
|
2898f1590d | ||
|
|
aa0fefcaa1 | ||
|
|
8cdf68bbf6 | ||
|
|
fb20818042 | ||
|
|
71a26381c0 | ||
|
|
2c4754d484 | ||
|
|
831e31d483 | ||
|
|
50639015d7 | ||
|
|
aaf45a871f | ||
|
|
3a16197da7 | ||
|
|
56c1f75bad | ||
|
|
b881bf5606 | ||
|
|
c8eeb788cb | ||
|
|
aeabd5a7ba | ||
|
|
b2dbcb3eab | ||
|
|
eb880d8841 | ||
|
|
1ead9ce4fd | ||
|
|
b3227d6049 | ||
|
|
2c6dc32f5d | ||
|
|
3cb8069e4a | ||
|
|
8c0e0fa2ed | ||
|
|
f293f983bd | ||
|
|
07f8cfbef1 | ||
|
|
39b2b2639a | ||
|
|
47b84f75e1 | ||
|
|
ff4c5c83f2 | ||
|
|
acabf2b492 | ||
|
|
1023c8d1be | ||
|
|
2c62d4b70c | ||
|
|
26f0806915 | ||
|
|
e6ed161ca4 | ||
|
|
134cd999c6 | ||
|
|
6214abba9a | ||
|
|
85afb6eb87 | ||
|
|
d60413f03c | ||
|
|
af88b4d2a8 | ||
|
|
4baac3d3fb | ||
|
|
ac73f18191 | ||
|
|
3b3fe63f19 | ||
|
|
9cd1ce968d | ||
|
|
71da59fd1c | ||
|
|
3f1e5ea6df | ||
|
|
fcc7421bca | ||
|
|
6722cc81de | ||
|
|
90525a6a1d | ||
|
|
6e46ae3090 | ||
|
|
4d2ddc7940 | ||
|
|
4f4a13a9a1 | ||
|
|
9eaeb193d4 | ||
|
|
ca6012086b | ||
|
|
1539da7fdc | ||
|
|
a3211dce57 | ||
|
|
5f83661fee | ||
|
|
2e44db1ee9 | ||
|
|
e6d4e37922 | ||
|
|
d6d0b26041 | ||
|
|
f22252e5ae | ||
|
|
8c52d2426d | ||
|
|
596da97626 | ||
|
|
f391b59be6 | ||
|
|
debef6e51b | ||
|
|
a9b513a28a | ||
|
|
5554cd18e3 | ||
|
|
6e60dede36 | ||
|
|
5428b3fda2 | ||
|
|
2308bc5555 | ||
|
|
d8f7928b74 | ||
|
|
2b44fc3bbb | ||
|
|
47fb91e333 | ||
|
|
cc67b801ce | ||
|
|
f23c2b6115 | ||
|
|
e6b20b2ce9 | ||
|
|
968f5ba1ef | ||
|
|
04c577049c | ||
|
|
bf6584739f | ||
|
|
68d6f43c63 | ||
|
|
6f724bc424 | ||
|
|
25f71eec2d | ||
|
|
33a1bb120f | ||
|
|
04143c7a89 | ||
|
|
e6825f0e08 | ||
|
|
fdacb28962 | ||
|
|
9f7bdb109b | ||
|
|
c1bbd7b041 | ||
|
|
a317aefd6d | ||
|
|
830768b715 | ||
|
|
2860efbe3a | ||
|
|
52ed4315ff | ||
|
|
8bc67569f9 | ||
|
|
df8d5285bf | ||
|
|
19c69f9eaa | ||
|
|
7b50629094 | ||
|
|
27c3e1857a |
@@ -1,7 +1,6 @@
|
||||
# See http://editorconfig.org
|
||||
# See https://editorconfig.org
|
||||
|
||||
# In Go files we indent with tabs but still
|
||||
# set indent_size to control the GitHub web viewer.
|
||||
[*.go]
|
||||
indent_size=4
|
||||
|
||||
|
||||
9
.github/ISSUE_TEMPLATE
vendored
9
.github/ISSUE_TEMPLATE
vendored
@@ -1,9 +0,0 @@
|
||||
## Description of the problem or steps to reproduce
|
||||
|
||||
## Specifications
|
||||
|
||||
<!-- You can use `micro -version` to get the commit hash. -->
|
||||
|
||||
Commit hash:
|
||||
OS:
|
||||
Terminal:
|
||||
25
.github/ISSUE_TEMPLATE/01-bug.yml
vendored
Normal file
25
.github/ISSUE_TEMPLATE/01-bug.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Bug Report
|
||||
description: File a bug report.
|
||||
title: "<title>"
|
||||
labels: ["bug", "triage"]
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: Description of the problem and steps to reproduce.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Environment
|
||||
description: |
|
||||
examples:
|
||||
- **Version**: 2.0.15 and/or commit hash ($ micro -version)
|
||||
- **OS**: Debian
|
||||
- **Terminal**: ptyxis
|
||||
value: |
|
||||
- Version:
|
||||
- OS:
|
||||
- Terminal:
|
||||
validations:
|
||||
required: true
|
||||
11
.github/ISSUE_TEMPLATE/02-feature.yml
vendored
Normal file
11
.github/ISSUE_TEMPLATE/02-feature.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
name: Feature Request
|
||||
description: File a feature request.
|
||||
title: "<title>"
|
||||
labels: ["feature"]
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: Description of the feature.
|
||||
validations:
|
||||
required: true
|
||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
2
.github/workflows/nightly.yaml
vendored
2
.github/workflows/nightly.yaml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
nightly:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.19.x]
|
||||
go-version: [1.23.x]
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
|
||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
release:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.19.x]
|
||||
go-version: [1.23.x]
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
|
||||
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
@@ -4,7 +4,7 @@ jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.19.x]
|
||||
go-version: [1.19.x, 1.23.x]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
.DS_Store
|
||||
|
||||
micro
|
||||
micro.exe
|
||||
!cmd/micro
|
||||
binaries/
|
||||
tmp.sh
|
||||
|
||||
@@ -430,7 +430,7 @@ 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/micro-editor/tcell/LICENSE (fork)
|
||||
================
|
||||
|
||||
|
||||
@@ -1048,7 +1048,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
|
||||
github.com/flynn/json5/LICENSE
|
||||
================
|
||||
github.com/zyedidia/json5/LICENSE (fork)
|
||||
github.com/micro-editor/json5/LICENSE (fork)
|
||||
================
|
||||
|
||||
Decoder code based on package encoding/json from the Go language.
|
||||
@@ -1108,7 +1108,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
github.com/james4k/terminal/LICENSE
|
||||
================
|
||||
github.com/zyedidia/terminal/LICENSE (fork)
|
||||
github.com/micro-editor/terminal/LICENSE (fork)
|
||||
================
|
||||
|
||||
Copyright (C) 2013 James Gray
|
||||
|
||||
27
Makefile
27
Makefile
@@ -5,24 +5,31 @@ VERSION = $(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) \
|
||||
GOARCH=$(shell go env GOHOSTARCH) \
|
||||
go run tools/info-plist.go "$(shell go env GOOS)" "$(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
|
||||
GOVARS = -X github.com/micro-editor/micro/v2/internal/util.Version=$(VERSION) -X github.com/micro-editor/micro/v2/internal/util.CommitHash=$(HASH) -X 'github.com/micro-editor/micro/v2/internal/util.CompileDate=$(DATE)'
|
||||
DEBUGVAR = -X github.com/micro-editor/micro/v2/internal/util.Debug=ON
|
||||
VSCODE_TESTS_BASE_URL = 'https://raw.githubusercontent.com/microsoft/vscode/e6a45f4242ebddb7aa9a229f85555e8a3bd987e2/src/vs/editor/test/common/model/'
|
||||
CGO_ENABLED := $(if $(CGO_ENABLED),$(CGO_ENABLED),0)
|
||||
|
||||
ADDITIONAL_GO_LINKER_FLAGS := ""
|
||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||
ifeq ($(GOHOSTOS), darwin)
|
||||
# Native darwin resp. macOS builds need external and dynamic linking
|
||||
ADDITIONAL_GO_LINKER_FLAGS += $(shell GOOS=$(GOHOSTOS) \
|
||||
GOARCH=$(shell go env GOHOSTARCH) \
|
||||
go run tools/info-plist.go "$(shell go env GOOS)" "$(VERSION)")
|
||||
CGO_ENABLED = 1
|
||||
endif
|
||||
|
||||
build: generate build-quick
|
||||
|
||||
build-quick:
|
||||
go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
CGO_ENABLED=$(CGO_ENABLED) go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
|
||||
build-dbg:
|
||||
go build -trimpath -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS) $(DEBUGVAR)" ./cmd/micro
|
||||
CGO_ENABLED=$(CGO_ENABLED) go build -trimpath -ldflags "$(ADDITIONAL_GO_LINKER_FLAGS) $(DEBUGVAR)" ./cmd/micro
|
||||
|
||||
build-tags: fetch-tags generate
|
||||
go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
build-tags: fetch-tags build
|
||||
|
||||
build-all: build
|
||||
|
||||
@@ -32,7 +39,7 @@ install: generate
|
||||
install-all: install
|
||||
|
||||
fetch-tags:
|
||||
git fetch --tags
|
||||
git fetch --tags --force
|
||||
|
||||
generate:
|
||||
GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) go generate ./runtime
|
||||
|
||||
90
README.md
90
README.md
@@ -1,9 +1,9 @@
|
||||
<img alt="micro logo" src="./assets/micro-logo-drop.svg" width="500px"/>
|
||||
|
||||

|
||||
[](https://goreportcard.com/report/github.com/zyedidia/micro)
|
||||
[](https://github.com/zyedidia/micro/releases)
|
||||
[](https://github.com/zyedidia/micro/blob/master/LICENSE)
|
||||

|
||||
[](https://goreportcard.com/report/github.com/micro-editor/micro/v2)
|
||||
[](https://github.com/micro-editor/micro/releases)
|
||||
[](https://github.com/micro-editor/micro/blob/master/LICENSE)
|
||||
[](https://gitter.im/zyedidia/micro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://snapcraft.io/micro)
|
||||
|
||||
@@ -18,25 +18,9 @@ Here is a picture of micro editing its source code.
|
||||

|
||||
|
||||
To see more screenshots of micro, showcasing some of the default color schemes, see [here](https://micro-editor.github.io).
|
||||
|
||||
|
||||
You can also check out the website for Micro at https://micro-editor.github.io.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [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)
|
||||
|
||||
- - -
|
||||
|
||||
## Features
|
||||
@@ -63,7 +47,7 @@ You can also check out the website for Micro at https://micro-editor.github.io.
|
||||
- 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).
|
||||
- True color support.
|
||||
- Copy and paste with the system clipboard.
|
||||
- Small and simple.
|
||||
- Easily configurable.
|
||||
@@ -73,22 +57,22 @@ You can also check out the website for Micro at https://micro-editor.github.io.
|
||||
|
||||
## Installation
|
||||
|
||||
To install micro, you can download a [prebuilt binary](https://github.com/zyedidia/micro/releases), or you can build it from source.
|
||||
To install micro, you can download a [prebuilt binary](https://github.com/micro-editor/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).
|
||||
If you want more information about ways to install micro, see this [wiki page](https://github.com/micro-editor/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.
|
||||
|
||||
A desktop entry file and man page can be found in the [assets/packaging](https://github.com/zyedidia/micro/tree/master/assets/packaging) directory.
|
||||
A desktop entry file and man page can be found in the [assets/packaging](https://github.com/micro-editor/micro/tree/master/assets/packaging) directory.
|
||||
|
||||
### Pre-built binaries
|
||||
|
||||
Pre-built binaries are distributed in [releases](https://github.com/zyedidia/micro/releases).
|
||||
Pre-built binaries are distributed in [releases](https://github.com/micro-editor/micro/releases).
|
||||
|
||||
To uninstall micro, simply remove the binary, and the configuration directory at `~/.config/micro`.
|
||||
|
||||
#### Quick-install script
|
||||
#### Third-party quick-install script
|
||||
|
||||
```bash
|
||||
curl https://getmic.ro | bash
|
||||
@@ -101,14 +85,14 @@ The script will place the micro binary in the current directory. From there, you
|
||||
With [Eget](https://github.com/zyedidia/eget) installed, you can easily get a pre-built binary:
|
||||
|
||||
```
|
||||
eget zyedidia/micro
|
||||
eget micro-editor/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
|
||||
eget --tag nightly micro-editor/micro # download the nightly version (compiled every day at midnight UTC)
|
||||
eget --tag v2.0.8 micro-editor/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.
|
||||
@@ -125,7 +109,7 @@ 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.
|
||||
the section on [macOS terminals](https://github.com/micro-editor/micro#macos-terminal) further below.
|
||||
|
||||
On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/install)
|
||||
|
||||
@@ -148,7 +132,7 @@ for other operating systems. These packages are not guaranteed to be up-to-date.
|
||||
* `eopkg install micro` (Solus).
|
||||
* `pacstall -I micro` (Pacstall).
|
||||
* `apt-get install micro` (ALT Linux)
|
||||
* See [wiki](https://github.com/zyedidia/micro/wiki/Installing-Micro) for details about CRUX, Termux.
|
||||
* See [wiki](https://github.com/micro-editor/micro/wiki/Installing-Micro) for details about CRUX, Termux.
|
||||
* distro-agnostic package managers:
|
||||
* `nix profile install nixpkgs#micro` (with [Nix](https://nixos.org/) and flakes enabled)
|
||||
* `flox install micro` (with [Flox](https://flox.dev))
|
||||
@@ -158,7 +142,7 @@ for other operating systems. These packages are not guaranteed to be up-to-date.
|
||||
* `winget install zyedidia.micro`
|
||||
* OpenBSD: Available in the ports tree and also available as a binary package.
|
||||
* `pkg_add -v micro`.
|
||||
* NetBSD, macOS, Linux, Illumos, etc. with [pkgsrc](http://www.pkgsrc.org/)-current:
|
||||
* NetBSD, macOS, Linux, Illumos, etc. with [pkgsrc](https://www.pkgsrc.org/)-current:
|
||||
* `pkg_add micro`
|
||||
* macOS: Available in package managers.
|
||||
* `sudo port install micro` (with [MacPorts](https://www.macports.org))
|
||||
@@ -178,10 +162,10 @@ Without these tools installed, micro will use an internal clipboard for copy and
|
||||
|
||||
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.19 or greater and Go modules are enabled.
|
||||
|
||||
```
|
||||
git clone https://github.com/zyedidia/micro
|
||||
git clone https://github.com/micro-editor/micro
|
||||
cd micro
|
||||
make build
|
||||
sudo mv micro /usr/local/bin # optional
|
||||
@@ -192,23 +176,27 @@ anywhere you like (for example `/usr/local/bin`).
|
||||
|
||||
The command `make install` will install the binary to `$GOPATH/bin` or `$GOBIN`.
|
||||
|
||||
You can install directly with `go get` (`go get github.com/zyedidia/micro/cmd/micro`) but this isn't
|
||||
You can install directly with `go get` (`go get github.com/micro-editor/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.
|
||||
|
||||
### Fully static binary
|
||||
### Fully static or dynamically linked binary
|
||||
|
||||
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
|
||||
By default, the micro binary is linked statically to increase the portability of the prebuilt binaries.
|
||||
This behavior can simply be overriden by providing `CGO_ENABLED=1` to the build target.
|
||||
|
||||
```
|
||||
CGO_ENABLED=0 make build
|
||||
CGO_ENABLED=1 make build
|
||||
```
|
||||
|
||||
Afterwards the micro binary will dynamically link with the present core system libraries.
|
||||
|
||||
**Note for Mac:**
|
||||
Native macOS builds are done with `CGO_ENABLED=1` forced set to support adding the "Information Property List" in the linker step.
|
||||
|
||||
### 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 are using macOS, you should consider using [iTerm2](https://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>.
|
||||
@@ -273,14 +261,14 @@ click to enable line selection.
|
||||
micro has a built-in help system which you can access by pressing <kbd>Ctrl-e</kbd> and typing `help`. Additionally, you can
|
||||
view the help files here:
|
||||
|
||||
- [main help](https://github.com/zyedidia/micro/tree/master/runtime/help/help.md)
|
||||
- [keybindings](https://github.com/zyedidia/micro/tree/master/runtime/help/keybindings.md)
|
||||
- [commands](https://github.com/zyedidia/micro/tree/master/runtime/help/commands.md)
|
||||
- [colors](https://github.com/zyedidia/micro/tree/master/runtime/help/colors.md)
|
||||
- [options](https://github.com/zyedidia/micro/tree/master/runtime/help/options.md)
|
||||
- [plugins](https://github.com/zyedidia/micro/tree/master/runtime/help/plugins.md)
|
||||
- [main help](https://github.com/micro-editor/micro/tree/master/runtime/help/help.md)
|
||||
- [keybindings](https://github.com/micro-editor/micro/tree/master/runtime/help/keybindings.md)
|
||||
- [commands](https://github.com/micro-editor/micro/tree/master/runtime/help/commands.md)
|
||||
- [colors](https://github.com/micro-editor/micro/tree/master/runtime/help/colors.md)
|
||||
- [options](https://github.com/micro-editor/micro/tree/master/runtime/help/options.md)
|
||||
- [plugins](https://github.com/micro-editor/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
|
||||
I also recommend reading the [tutorial](https://github.com/micro-editor/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.
|
||||
@@ -289,9 +277,9 @@ There is also an unofficial Discord, which you can join at https://discord.gg/nh
|
||||
|
||||
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)
|
||||
You can use the [GitHub issue tracker](https://github.com/micro-editor/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.
|
||||
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/micro-editor/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.
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
.TH micro 1 "2020-02-10"
|
||||
.TH micro 1 "2025-09-03"
|
||||
.SH NAME
|
||||
micro \- A modern and intuitive terminal-based text editor
|
||||
.SH SYNOPSIS
|
||||
.B micro
|
||||
.RB [OPTIONS]
|
||||
[FILE]\&...
|
||||
|
||||
.RI [ OPTION ]...\&
|
||||
.RI [ FILE ]...\&
|
||||
.RI [+ LINE [: COL ]]\&
|
||||
.RI [+/ REGEX ]
|
||||
.br
|
||||
.B micro
|
||||
.RI [ OPTION ]...\&
|
||||
.RI [ FILE [: LINE [: COL ]]]...\&
|
||||
\& (only if the `parsecursor` option is enabled)
|
||||
.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.
|
||||
|
||||
@@ -15,73 +20,85 @@ As the name indicates, micro aims to be somewhat of a successor to the nano edit
|
||||
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
|
||||
|
||||
Use Ctrl-q to quit, Ctrl-s to save, and Ctrl-g to open the in-editor help menu.
|
||||
|
||||
.SH OPTIONS
|
||||
.PP
|
||||
\-clean
|
||||
.B \-clean
|
||||
.RS 4
|
||||
Cleans the configuration directory
|
||||
Clean the configuration directory and exit
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-config-dir dir
|
||||
.B \-config-dir
|
||||
.I dir
|
||||
.RS 4
|
||||
Specify a custom location for the configuration directory
|
||||
.RE
|
||||
|
||||
.PP
|
||||
[FILE]:LINE:COL
|
||||
.IR FILE : LINE [: COL ]
|
||||
(only if the `parsecursor` option is enabled)
|
||||
.br
|
||||
.IR FILE \ + LINE [: COL ]
|
||||
.RS 4
|
||||
Specify a line and column to start the cursor at when opening a buffer
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-options
|
||||
.RI +/ REGEX
|
||||
.RS 4
|
||||
Show all option help
|
||||
Specify a regex to search for when opening a buffer
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-debug
|
||||
.B \-options
|
||||
.RS 4
|
||||
Show all options help and exit
|
||||
.RE
|
||||
.PP
|
||||
.B \-debug
|
||||
.RS 4
|
||||
Enable debug mode (enables logging to ./log.txt)
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-version
|
||||
.B \-profile
|
||||
.RS 4
|
||||
Show the version number and information
|
||||
Enable CPU profiling (writes profile info to ./micro.prof so it can be analyzed later with "go tool pprof micro.prof")
|
||||
.RE
|
||||
.PP
|
||||
.B \-version
|
||||
.RS 4
|
||||
Show the version number and information and exit
|
||||
.RE
|
||||
|
||||
Micro's plugins can be managed at the command line with the following commands.
|
||||
.RS 4
|
||||
|
||||
.PP
|
||||
\-plugin remove [PLUGIN]...
|
||||
.B \-plugin install
|
||||
.RI [ PLUGIN ]...
|
||||
.RS 4
|
||||
Install plugin(s)
|
||||
.RE
|
||||
.PP
|
||||
.B \-plugin remove
|
||||
.RI [ PLUGIN ]...
|
||||
.RS 4
|
||||
Remove plugin(s)
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-plugin update [PLUGIN]...
|
||||
.B \-plugin update
|
||||
.RI [ PLUGIN ]...
|
||||
.RS 4
|
||||
Update plugin(s) (if no argument is given, updates all plugins)
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-plugin search [PLUGIN]...
|
||||
.B \-plugin search
|
||||
.RI [ PLUGIN ]...
|
||||
.RS 4
|
||||
Search for a plugin
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-plugin list
|
||||
.B \-plugin list
|
||||
.RS 4
|
||||
List installed plugins
|
||||
.RE
|
||||
|
||||
.PP
|
||||
\-plugin available
|
||||
.B \-plugin available
|
||||
.RS 4
|
||||
List available plugins
|
||||
.RE
|
||||
@@ -91,35 +108,33 @@ 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
|
||||
.BI \-< option >
|
||||
.I value
|
||||
.RS 4
|
||||
Set `option` to `value` for this session
|
||||
Set `option` to `value` for this session.
|
||||
.br
|
||||
For example: `micro -syntax off file.c`
|
||||
.RE
|
||||
|
||||
|
||||
.RE
|
||||
.PP
|
||||
Use `micro -options` to see the full list of configuration options.
|
||||
.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
|
||||
page at \fBhttps://github.com/micro-editor/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.
|
||||
See \fBhttps://github.com/micro-editor/micro\fP for details.
|
||||
|
||||
@@ -3,15 +3,16 @@ package main
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
func shouldContinue() bool {
|
||||
@@ -39,7 +40,16 @@ func CleanConfig() {
|
||||
}
|
||||
|
||||
fmt.Println("Cleaning default settings")
|
||||
config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
|
||||
|
||||
settingsFile := filepath.Join(config.ConfigDir, "settings.json")
|
||||
err := config.WriteSettings(settingsFile)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrOverwrite) {
|
||||
fmt.Println(err.Error())
|
||||
} else {
|
||||
fmt.Println("Error writing settings.json file: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// detect unused options
|
||||
var unusedOptions []string
|
||||
@@ -67,16 +77,20 @@ func CleanConfig() {
|
||||
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"))
|
||||
fmt.Printf("These options will be removed from %s\n", settingsFile)
|
||||
|
||||
if shouldContinue() {
|
||||
for _, s := range unusedOptions {
|
||||
delete(config.GlobalSettings, s)
|
||||
}
|
||||
|
||||
err := config.OverwriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
|
||||
err := config.OverwriteSettings(settingsFile)
|
||||
if err != nil {
|
||||
fmt.Println("Error writing settings.json file: " + err.Error())
|
||||
if errors.Is(err, util.ErrOverwrite) {
|
||||
fmt.Println(err.Error())
|
||||
} else {
|
||||
fmt.Println("Error overwriting settings.json file: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Removed unused options")
|
||||
@@ -85,12 +99,13 @@ func CleanConfig() {
|
||||
}
|
||||
|
||||
// detect incorrectly formatted buffer/ files
|
||||
files, err := ioutil.ReadDir(filepath.Join(config.ConfigDir, "buffers"))
|
||||
buffersPath := filepath.Join(config.ConfigDir, "buffers")
|
||||
files, err := os.ReadDir(buffersPath)
|
||||
if err == nil {
|
||||
var badFiles []string
|
||||
var buffer buffer.SerializedBuffer
|
||||
for _, f := range files {
|
||||
fname := filepath.Join(config.ConfigDir, "buffers", f.Name())
|
||||
fname := filepath.Join(buffersPath, f.Name())
|
||||
file, e := os.Open(fname)
|
||||
|
||||
if e == nil {
|
||||
@@ -105,9 +120,9 @@ func CleanConfig() {
|
||||
}
|
||||
|
||||
if len(badFiles) > 0 {
|
||||
fmt.Printf("Detected %d files with an invalid format in %s\n", len(badFiles), filepath.Join(config.ConfigDir, "buffers"))
|
||||
fmt.Printf("Detected %d files with an invalid format in %s\n", len(badFiles), buffersPath)
|
||||
fmt.Println("These files store cursor and undo history.")
|
||||
fmt.Printf("Removing badly formatted files in %s\n", filepath.Join(config.ConfigDir, "buffers"))
|
||||
fmt.Printf("Removing badly formatted files in %s\n", buffersPath)
|
||||
|
||||
if shouldContinue() {
|
||||
removed := 0
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// NullWriter simply sends writes into the void
|
||||
@@ -18,7 +18,7 @@ func (NullWriter) Write(data []byte) (n int, err error) {
|
||||
// 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)
|
||||
f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, util.FileMode)
|
||||
if err != nil {
|
||||
log.Fatalf("error opening file: %v", err)
|
||||
}
|
||||
|
||||
@@ -7,14 +7,14 @@ import (
|
||||
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"
|
||||
"github.com/micro-editor/micro/v2/internal/action"
|
||||
"github.com/micro-editor/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/display"
|
||||
ulua "github.com/micro-editor/micro/v2/internal/lua"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/shell"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -73,7 +73,7 @@ func luaImportMicroConfig() *lua.LTable {
|
||||
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, "TryBindKey", luar.New(ulua.L, action.TryBindKeyPlug))
|
||||
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))
|
||||
@@ -88,8 +88,8 @@ func luaImportMicroConfig() *lua.LTable {
|
||||
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, "SetGlobalOption", luar.New(ulua.L, action.SetGlobalOptionPlug))
|
||||
ulua.L.SetField(pkg, "SetGlobalOptionNative", luar.New(ulua.L, action.SetGlobalOptionNativePlug))
|
||||
ulua.L.SetField(pkg, "ConfigDir", luar.New(ulua.L, config.ConfigDir))
|
||||
|
||||
return pkg
|
||||
|
||||
@@ -4,10 +4,10 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
@@ -18,15 +18,15 @@ import (
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
isatty "github.com/mattn/go-isatty"
|
||||
"github.com/micro-editor/micro/v2/internal/action"
|
||||
"github.com/micro-editor/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/clipboard"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/shell"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
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"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -46,24 +46,28 @@ var (
|
||||
)
|
||||
|
||||
func InitFlags() {
|
||||
// Note: keep this in sync with the man page in assets/packaging/micro.1
|
||||
flag.Usage = func() {
|
||||
fmt.Println("Usage: micro [OPTIONS] [FILE]...")
|
||||
fmt.Println("Usage: micro [OPTION]... [FILE]... [+LINE[:COL]] [+/REGEX]")
|
||||
fmt.Println(" micro [OPTION]... [FILE[:LINE[:COL]]]... (only if the `parsecursor` option is enabled)")
|
||||
fmt.Println("-clean")
|
||||
fmt.Println(" \tCleans the configuration directory")
|
||||
fmt.Println(" \tClean the configuration directory and exit")
|
||||
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("FILE:LINE[:COL] (only if the `parsecursor` option is enabled)")
|
||||
fmt.Println("FILE +LINE[:COL]")
|
||||
fmt.Println(" \tSpecify a line and column to start the cursor at when opening a buffer")
|
||||
fmt.Println("+/REGEX")
|
||||
fmt.Println(" \tSpecify a regex to search for when opening a buffer")
|
||||
fmt.Println("-options")
|
||||
fmt.Println(" \tShow all option help")
|
||||
fmt.Println(" \tShow all options help and exit")
|
||||
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.Println(" \tShow the version number and information and exit")
|
||||
|
||||
fmt.Print("\nMicro's plugins can be managed at the command line with the following commands.\n")
|
||||
fmt.Println("-plugin install [PLUGIN]...")
|
||||
@@ -80,7 +84,7 @@ func InitFlags() {
|
||||
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("-<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")
|
||||
@@ -99,7 +103,7 @@ func InitFlags() {
|
||||
fmt.Println("Version:", util.Version)
|
||||
fmt.Println("Commit hash:", util.CommitHash)
|
||||
fmt.Println("Compiled on", util.CompileDate)
|
||||
os.Exit(0)
|
||||
exit(0)
|
||||
}
|
||||
|
||||
if *flagOptions {
|
||||
@@ -115,7 +119,7 @@ func InitFlags() {
|
||||
fmt.Printf("-%s value\n", k)
|
||||
fmt.Printf(" \tDefault value: '%v'\n", v)
|
||||
}
|
||||
os.Exit(0)
|
||||
exit(0)
|
||||
}
|
||||
|
||||
if util.Debug == "OFF" && *flagDebug {
|
||||
@@ -136,7 +140,7 @@ func DoPluginFlags() {
|
||||
CleanConfig()
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,50 +158,63 @@ func LoadInput(args []string) []*buffer.Buffer {
|
||||
// 3. If there is no input file and the input is a terminal, an empty buffer
|
||||
// should be opened
|
||||
|
||||
var filename string
|
||||
var input []byte
|
||||
var err error
|
||||
buffers := make([]*buffer.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])
|
||||
posFlagr := regexp.MustCompile(`^\+(\d+)(?::(\d+))?$`)
|
||||
posIndex := -1
|
||||
|
||||
searchText := ""
|
||||
searchFlagr := regexp.MustCompile(`^\+\/(.+)$`)
|
||||
searchIndex := -1
|
||||
|
||||
for i, a := range args {
|
||||
posMatch := posFlagr.FindStringSubmatch(a)
|
||||
if len(posMatch) == 3 && posMatch[2] != "" {
|
||||
line, err := strconv.Atoi(posMatch[1])
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
continue
|
||||
}
|
||||
col, err := strconv.Atoi(match[2])
|
||||
col, err := strconv.Atoi(posMatch[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])
|
||||
posIndex = i
|
||||
} else if len(posMatch) == 3 && posMatch[2] == "" {
|
||||
line, err := strconv.Atoi(posMatch[1])
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
continue
|
||||
}
|
||||
flagStartPos = buffer.Loc{0, line - 1}
|
||||
posIndex = i
|
||||
} else {
|
||||
files = append(files, a)
|
||||
searchMatch := searchFlagr.FindStringSubmatch(a)
|
||||
if len(searchMatch) == 2 {
|
||||
searchText = searchMatch[1]
|
||||
searchIndex = i
|
||||
} else {
|
||||
files = append(files, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
command := buffer.Command{
|
||||
StartCursor: flagStartPos,
|
||||
SearchRegex: searchText,
|
||||
SearchAfterStart: searchIndex > posIndex,
|
||||
}
|
||||
|
||||
if len(files) > 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)
|
||||
buf, err := buffer.NewBufferFromFileWithCommand(files[i], buffer.BTDefault, command)
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
continue
|
||||
@@ -205,30 +222,80 @@ func LoadInput(args []string) []*buffer.Buffer {
|
||||
// If the file didn't exist, input will be empty, and we'll open an empty buffer
|
||||
buffers = append(buffers, buf)
|
||||
}
|
||||
} else if !isatty.IsTerminal(os.Stdin.Fd()) {
|
||||
// Option 2
|
||||
// The input is not a terminal, so something is being piped in
|
||||
// and we should read from stdin
|
||||
input, err = ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error reading from stdin: ", err)
|
||||
input = []byte{}
|
||||
}
|
||||
buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
|
||||
} else {
|
||||
// Option 3, just open an empty buffer
|
||||
buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
|
||||
btype := buffer.BTDefault
|
||||
if !isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
btype = buffer.BTStdout
|
||||
}
|
||||
|
||||
if !isatty.IsTerminal(os.Stdin.Fd()) {
|
||||
// Option 2
|
||||
// The input is not a terminal, so something is being piped in
|
||||
// and we should read from stdin
|
||||
input, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error reading from stdin: ", err)
|
||||
input = []byte{}
|
||||
}
|
||||
buffers = append(buffers, buffer.NewBufferFromStringWithCommand(string(input), "", btype, command))
|
||||
} else {
|
||||
// Option 3, just open an empty buffer
|
||||
buffers = append(buffers, buffer.NewBufferFromStringWithCommand("", "", btype, command))
|
||||
}
|
||||
}
|
||||
|
||||
return buffers
|
||||
}
|
||||
|
||||
func checkBackup(name string) error {
|
||||
target := filepath.Join(config.ConfigDir, name)
|
||||
backup := target + util.BackupSuffix
|
||||
if info, err := os.Stat(backup); err == nil {
|
||||
input, err := os.ReadFile(backup)
|
||||
if err == nil {
|
||||
t := info.ModTime()
|
||||
msg := fmt.Sprintf(buffer.BackupMsg, target, t.Format("Mon Jan _2 at 15:04, 2006"), backup)
|
||||
choice := screen.TermPrompt(msg, []string{"r", "i", "a", "recover", "ignore", "abort"}, true)
|
||||
|
||||
if choice%3 == 0 {
|
||||
// recover
|
||||
err := os.WriteFile(target, input, util.FileMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Remove(backup)
|
||||
} else if choice%3 == 1 {
|
||||
// delete
|
||||
return os.Remove(backup)
|
||||
} else if choice%3 == 2 {
|
||||
// abort
|
||||
return errors.New("Aborted")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func exit(rc int) {
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
if !b.Modified() {
|
||||
b.Fini()
|
||||
}
|
||||
}
|
||||
|
||||
if screen.Screen != nil {
|
||||
screen.Screen.Fini()
|
||||
}
|
||||
|
||||
os.Exit(rc)
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
if util.Stdout.Len() > 0 {
|
||||
fmt.Fprint(os.Stdout, util.Stdout.String())
|
||||
}
|
||||
os.Exit(0)
|
||||
exit(0)
|
||||
}()
|
||||
|
||||
var err error
|
||||
@@ -256,6 +323,12 @@ func main() {
|
||||
config.InitRuntimeFiles(true)
|
||||
config.InitPlugins()
|
||||
|
||||
err = checkBackup("settings.json")
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
err = config.ReadSettings()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
@@ -268,7 +341,7 @@ func main() {
|
||||
// flag options
|
||||
for k, v := range optionFlags {
|
||||
if *v != "" {
|
||||
nativeValue, err := config.GetNativeValue(k, config.DefaultAllSettings()[k], *v)
|
||||
nativeValue, err := config.GetNativeValue(k, *v)
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
continue
|
||||
@@ -288,7 +361,7 @@ func main() {
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Fatal: Micro could not initialize a Screen.")
|
||||
os.Exit(1)
|
||||
exit(1)
|
||||
}
|
||||
m := clipboard.SetMethod(config.GetGlobalOption("clipboard").(string))
|
||||
clipErr := clipboard.Initialize(m)
|
||||
@@ -301,13 +374,15 @@ func main() {
|
||||
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")
|
||||
fmt.Println("Micro encountered an error:", errors.Wrap(err, 2).ErrorStack(), "\nIf you can reproduce this error, please report it at https://github.com/micro-editor/micro/issues")
|
||||
}
|
||||
// backup all open buffers
|
||||
// immediately backup all buffers with unsaved changes
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
b.Backup()
|
||||
if b.Modified() {
|
||||
b.Backup()
|
||||
}
|
||||
}
|
||||
os.Exit(1)
|
||||
exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -316,14 +391,15 @@ func main() {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
action.InitBindings()
|
||||
action.InitCommands()
|
||||
|
||||
err = config.InitColorscheme()
|
||||
err = checkBackup("bindings.json")
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
action.InitBindings()
|
||||
action.InitCommands()
|
||||
|
||||
err = config.RunPluginFn("preinit")
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
@@ -352,6 +428,11 @@ func main() {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
err = config.InitColorscheme()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
if clipErr != nil {
|
||||
log.Println(clipErr, " or change 'clipboard' option")
|
||||
}
|
||||
@@ -435,23 +516,9 @@ func DoEvent() {
|
||||
case f := <-timerChan:
|
||||
f()
|
||||
case <-sighup:
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
if !b.Modified() {
|
||||
b.Fini()
|
||||
}
|
||||
}
|
||||
os.Exit(0)
|
||||
exit(0)
|
||||
case <-util.Sigterm:
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
if !b.Modified() {
|
||||
b.Fini()
|
||||
}
|
||||
}
|
||||
|
||||
if screen.Screen != nil {
|
||||
screen.Screen.Fini()
|
||||
}
|
||||
os.Exit(0)
|
||||
exit(0)
|
||||
}
|
||||
|
||||
if e, ok := event.(*tcell.EventError); ok {
|
||||
@@ -459,16 +526,7 @@ func DoEvent() {
|
||||
|
||||
if e.Err() == io.EOF {
|
||||
// shutdown due to terminal closing/becoming inaccessible
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
if !b.Modified() {
|
||||
b.Fini()
|
||||
}
|
||||
}
|
||||
|
||||
if screen.Screen != nil {
|
||||
screen.Screen.Fini()
|
||||
}
|
||||
os.Exit(0)
|
||||
exit(0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,18 +2,17 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/micro-editor/micro/v2/internal/action"
|
||||
"github.com/micro-editor/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
"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
|
||||
@@ -26,7 +25,7 @@ func init() {
|
||||
func startup(args []string) (tcell.SimulationScreen, error) {
|
||||
var err error
|
||||
|
||||
tempDir, err = ioutil.TempDir("", "micro_test")
|
||||
tempDir, err = os.MkdirTemp("", "micro_test")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -56,9 +55,11 @@ func startup(args []string) (tcell.SimulationScreen, error) {
|
||||
if err := recover(); err != nil {
|
||||
screen.Screen.Fini()
|
||||
fmt.Println("Micro encountered an error:", err)
|
||||
// backup all open buffers
|
||||
// immediately backup all buffers with unsaved changes
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
b.Backup()
|
||||
if b.Modified() {
|
||||
b.Backup()
|
||||
}
|
||||
}
|
||||
// Print the stack trace too
|
||||
log.Fatalf(errors.Wrap(err, 2).ErrorStack())
|
||||
@@ -164,20 +165,22 @@ func findBuffer(file string) *buffer.Buffer {
|
||||
return buf
|
||||
}
|
||||
|
||||
func createTestFile(name string, content string) (string, error) {
|
||||
testf, err := ioutil.TempFile("", name)
|
||||
func createTestFile(t *testing.T, content string) string {
|
||||
f, err := os.CreateTemp(t.TempDir(), "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := f.WriteString(content); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := testf.Write([]byte(content)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := testf.Close(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return testf.Name(), nil
|
||||
return f.Name()
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -194,18 +197,12 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
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)
|
||||
file := createTestFile(t, "base content")
|
||||
|
||||
openFile(file)
|
||||
|
||||
if findBuffer(file) == nil {
|
||||
t.Errorf("Could not find buffer %s", file)
|
||||
return
|
||||
t.Fatalf("Could not find buffer %s", file)
|
||||
}
|
||||
|
||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
||||
@@ -223,28 +220,21 @@ func TestSimpleEdit(t *testing.T) {
|
||||
|
||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||
|
||||
data, err := ioutil.ReadFile(file)
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
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)
|
||||
file := createTestFile(t, "base content")
|
||||
|
||||
openFile(file)
|
||||
|
||||
if findBuffer(file) == nil {
|
||||
t.Errorf("Could not find buffer %s", file)
|
||||
return
|
||||
t.Fatalf("Could not find buffer %s", file)
|
||||
}
|
||||
|
||||
// buffer:
|
||||
@@ -275,10 +265,9 @@ func TestMouse(t *testing.T) {
|
||||
// base content
|
||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||
|
||||
data, err := ioutil.ReadFile(file)
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, "firstline\nsecondline\nbase content\n", string(data))
|
||||
@@ -301,18 +290,12 @@ 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)
|
||||
file := createTestFile(t, srTestStart)
|
||||
|
||||
openFile(file)
|
||||
|
||||
if findBuffer(file) == nil {
|
||||
t.Errorf("Could not find buffer %s", file)
|
||||
return
|
||||
t.Fatalf("Could not find buffer %s", file)
|
||||
}
|
||||
|
||||
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
|
||||
@@ -321,10 +304,9 @@ func TestSearchAndReplace(t *testing.T) {
|
||||
|
||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||
|
||||
data, err := ioutil.ReadFile(file)
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, srTest2, string(data))
|
||||
@@ -337,10 +319,9 @@ func TestSearchAndReplace(t *testing.T) {
|
||||
|
||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||
|
||||
data, err = ioutil.ReadFile(file)
|
||||
data, err = os.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, srTest3, string(data))
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
<category>TextEditor</category>
|
||||
</categories>
|
||||
<releases>
|
||||
<release version="2.0.15" date="2025-12-31"/>
|
||||
<release version="2.0.14" date="2024-08-27"/>
|
||||
<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"/>
|
||||
@@ -37,15 +39,15 @@
|
||||
<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>
|
||||
<image type="source">https://raw.githubusercontent.com/micro-editor/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="bugtracker">https://github.com/micro-editor/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>
|
||||
<url type="vcs-browser">https://github.com/micro-editor/micro</url>
|
||||
<url type="contribute">https://github.com/micro-editor/micro#contributing</url>
|
||||
</component>
|
||||
|
||||
126
data/micro.json
126
data/micro.json
@@ -1,43 +1,43 @@
|
||||
{
|
||||
"$comment": "https://github.com/zyedidia/micro",
|
||||
"$comment": "https://github.com/micro-editor/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",
|
||||
"description": "Whether to use the same indentation as a previous line\nhttps://github.com/micro-editor/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",
|
||||
"description": "A delay between automatic saves\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether attempt to use super user privileges\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to backup all open buffers\nhttps://github.com/micro-editor/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",
|
||||
"description": "A directory to store backups\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to show a basename instead of a full path\nhttps://github.com/micro-editor/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",
|
||||
"description": "A way to access the system clipboard\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"external",
|
||||
@@ -47,13 +47,13 @@
|
||||
"default": "external"
|
||||
},
|
||||
"colorcolumn": {
|
||||
"description": "A position to display a column\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"description": "A position to display a column\nhttps://github.com/micro-editor/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",
|
||||
"description": "A color scheme\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"atom-dark",
|
||||
@@ -85,42 +85,42 @@
|
||||
"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",
|
||||
"description": "Whether to highlight a line with a cursor with a different color\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to display diff inticators before lines\nhttps://github.com/micro-editor/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",
|
||||
"description": "Divider chars for vertical and horizontal splits\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to use inversed color scheme colors for splits\nhttps://github.com/micro-editor/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",
|
||||
"description": "An encoding used to open and save files\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to add a missing trailing new line\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to use a fast algorithm to determine whether a file is changed\nhttps://github.com/micro-editor/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",
|
||||
"description": "A line ending format\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"unix",
|
||||
@@ -129,53 +129,53 @@
|
||||
"default": "unix"
|
||||
},
|
||||
"filetype": {
|
||||
"description": "A filetype for the current buffer\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"description": "A filetype for the current buffer\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to highlight all instances of a searched text after a successful search\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to enable an incremental search in `Find` prompt\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to perform case-insensitive searches\nhttps://github.com/micro-editor/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",
|
||||
"description": "An indentation character\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to enable a line at the bottom where messages are printed\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether add a whitespace while using autoindent\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to display nano-style key menu at the bottom\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to show matching braces\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to underline or highlight matching braces\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"underline",
|
||||
@@ -184,132 +184,132 @@
|
||||
"default": "underline"
|
||||
},
|
||||
"mkparents": {
|
||||
"description": "Whether to create missing directories\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"description": "Whether to create missing directories\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to enable mouse support\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to treat characters sent from the terminal in a single chunk as a paste event\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to extract a line number and a column to open files with from file names\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to permanently save backups\nhttps://github.com/micro-editor/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",
|
||||
"description": "A file with list of plugin channels\nhttps://github.com/micro-editor/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",
|
||||
"description": "Plugin repositories\nhttps://github.com/micro-editor/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",
|
||||
"description": "A pluging repository\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to forbid buffer editing\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to remove trailing whitespaces\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to display line numbers\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to display relative line numbers\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to save cursor position in files\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to save command history between closing and re-opening editor\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to save undo after closing file\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to save undo after closing file\nhttps://github.com/micro-editor/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",
|
||||
"description": "A margin at which a view starts scrolling when a cursor approaches an edge of a view\nhttps://github.com/micro-editor/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",
|
||||
"description": "Line count to scroll for one scroll event\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to add a leading whitespace while pasting multiple lines\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to wrap long lines\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to create a new horizontal split below the current one\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to create a new vertical split right of the current one\nhttps://github.com/micro-editor/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",
|
||||
"description": "Format string of left-justified part of the statusline\nhttps://github.com/micro-editor/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",
|
||||
"description": "Format string of right-justified part of the statusline\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to display a status line\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"sucmd": {
|
||||
"description": "A super user command\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"description": "A super user command\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "string",
|
||||
"default": "sudo",
|
||||
"examples": [
|
||||
@@ -318,47 +318,47 @@
|
||||
]
|
||||
},
|
||||
"syntax": {
|
||||
"description": "Whether to enable a syntax highlighting\nhttps://github.com/zyedidia/micro/blob/master/runtime/help/options.md#options",
|
||||
"description": "Whether to enable a syntax highlighting\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to navigate spaces at the beginning of lines as if they are tabs\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to invert tab character colors\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to reverse tab bar colors\nhttps://github.com/micro-editor/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",
|
||||
"description": "A tab size\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to use spaces instead of tabs\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to use primary clipboard to copy selections in the background\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to wrap long lines by words\nhttps://github.com/micro-editor/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",
|
||||
"description": "Whether to assume that the current terminal is `xterm`\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
|
||||
36
go.mod
36
go.mod
@@ -1,30 +1,40 @@
|
||||
module github.com/zyedidia/micro/v2
|
||||
module github.com/micro-editor/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/mattn/go-isatty v0.0.20
|
||||
github.com/mattn/go-runewidth v0.0.16
|
||||
github.com/micro-editor/json5 v1.0.1-micro
|
||||
github.com/micro-editor/tcell/v2 v2.0.13
|
||||
github.com/micro-editor/terminal v0.0.0-20250324214352-e587e959c6b5
|
||||
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/yuin/gopher-lua v1.1.1
|
||||
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
|
||||
golang.org/x/text v0.4.0
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
layeh.com/gopher-luar v1.0.7
|
||||
layeh.com/gopher-luar v1.0.11
|
||||
)
|
||||
|
||||
replace github.com/kballard/go-shellquote => github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655
|
||||
require (
|
||||
github.com/creack/pty v1.1.18 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/zyedidia/poller v1.0.1 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/term v0.29.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/mattn/go-runewidth => github.com/zyedidia/go-runewidth v0.0.12
|
||||
replace github.com/kballard/go-shellquote => github.com/micro-editor/go-shellquote v0.0.0-20250101105543-feb6c39314f5
|
||||
|
||||
replace layeh.com/gopher-luar => github.com/layeh/gopher-luar v1.0.7
|
||||
replace layeh.com/gopher-luar v1.0.11 => github.com/layeh/gopher-luar v1.0.11
|
||||
|
||||
go 1.16
|
||||
go 1.19
|
||||
|
||||
83
go.sum
83
go.sum
@@ -19,84 +19,57 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||
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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/layeh/gopher-luar v1.0.11 h1:ss6t9OtykOiETBScJylSMPhuYAtOmpH5rSX10/wCcis=
|
||||
github.com/layeh/gopher-luar v1.0.11/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/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/micro-editor/go-shellquote v0.0.0-20250101105543-feb6c39314f5 h1:D7BPnsedXiKo/e8RTFX419/52ICNhU8UKPQGZ/0yiLc=
|
||||
github.com/micro-editor/go-shellquote v0.0.0-20250101105543-feb6c39314f5/go.mod h1:zaPgW/fDiW4MUfEwxpC+GB/bhvX44NJaNHmRAC9auHQ=
|
||||
github.com/micro-editor/json5 v1.0.1-micro h1:5Y4MuzhkmW0sQQNPvrIVevIOKi557qsznwjRr4iq1AI=
|
||||
github.com/micro-editor/json5 v1.0.1-micro/go.mod h1:cmlPHZ1JKOXNse0/3zwwKj/GUpzAVkzx4lZDkpHl4q0=
|
||||
github.com/micro-editor/tcell/v2 v2.0.13 h1:xyuSpBhSBsUH+bs7FER9IV2/TsQpBmCFiNWJVAEdT68=
|
||||
github.com/micro-editor/tcell/v2 v2.0.13/go.mod h1:ixpjICpoGp83FZVoLYFJPBwCAslHeTnvgPdhJVPLyy0=
|
||||
github.com/micro-editor/terminal v0.0.0-20250324214352-e587e959c6b5 h1:czSkYUNmHuWS2lv8VreufENEXZNOCGZcXd744YKf8yM=
|
||||
github.com/micro-editor/terminal v0.0.0-20250324214352-e587e959c6b5/go.mod h1:OszIG7ockt4osicVHq6gI2QmV4PBDK6H5/Bj8GDGv4Q=
|
||||
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/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGGF0=
|
||||
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/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
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/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
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=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
|
||||
gopkg.in/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=
|
||||
|
||||
@@ -11,14 +11,14 @@ import (
|
||||
"time"
|
||||
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"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/display"
|
||||
"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/micro-editor/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/clipboard"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/display"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/shell"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
)
|
||||
|
||||
// ScrollUp is not an action
|
||||
@@ -46,6 +46,14 @@ func (h *BufPane) ScrollAdjust() {
|
||||
h.SetView(v)
|
||||
}
|
||||
|
||||
// ScrollReachedEnd returns true if the view is at the end of the buffer,
|
||||
// i.e. the last line of the buffer is in the view.
|
||||
func (h *BufPane) ScrollReachedEnd() bool {
|
||||
v := h.GetView()
|
||||
end := h.SLocFromLoc(h.Buf.End())
|
||||
return h.Diff(v.StartLine, end) < h.BufView().Height
|
||||
}
|
||||
|
||||
// MousePress is the event that should happen when a normal click happens
|
||||
// This is almost always bound to left click
|
||||
func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
|
||||
@@ -65,12 +73,12 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
|
||||
h.Cursor.Loc = mouseLoc
|
||||
}
|
||||
if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
|
||||
if h.doubleClick {
|
||||
if h.DoubleClick {
|
||||
// Triple click
|
||||
h.lastClickTime = time.Now()
|
||||
|
||||
h.tripleClick = true
|
||||
h.doubleClick = false
|
||||
h.TripleClick = true
|
||||
h.DoubleClick = false
|
||||
|
||||
h.Cursor.SelectLine()
|
||||
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
||||
@@ -78,15 +86,15 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
|
||||
// Double click
|
||||
h.lastClickTime = time.Now()
|
||||
|
||||
h.doubleClick = true
|
||||
h.tripleClick = false
|
||||
h.DoubleClick = true
|
||||
h.TripleClick = false
|
||||
|
||||
h.Cursor.SelectWord()
|
||||
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
||||
}
|
||||
} else {
|
||||
h.doubleClick = false
|
||||
h.tripleClick = false
|
||||
h.DoubleClick = false
|
||||
h.TripleClick = false
|
||||
h.lastClickTime = time.Now()
|
||||
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
||||
@@ -108,9 +116,9 @@ func (h *BufPane) MouseDrag(e *tcell.EventMouse) bool {
|
||||
}
|
||||
h.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my})
|
||||
|
||||
if h.tripleClick {
|
||||
if h.TripleClick {
|
||||
h.Cursor.AddLineToSelection()
|
||||
} else if h.doubleClick {
|
||||
} else if h.DoubleClick {
|
||||
h.Cursor.AddWordToSelection()
|
||||
} else {
|
||||
h.Cursor.SelectTo(h.Cursor.Loc)
|
||||
@@ -127,7 +135,7 @@ func (h *BufPane) MouseRelease(e *tcell.EventMouse) bool {
|
||||
// that doesn't support mouse motion events. But when the mouse click is
|
||||
// within the scroll margin, that would cause a scroll and selection
|
||||
// even for a simple mouse click, which is not good.
|
||||
// if !h.doubleClick && !h.tripleClick {
|
||||
// if !h.DoubleClick && !h.TripleClick {
|
||||
// mx, my := e.Position()
|
||||
// h.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my})
|
||||
// h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
||||
@@ -145,7 +153,7 @@ func (h *BufPane) ScrollUpAction() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ScrollDownAction scrolls the view up
|
||||
// ScrollDownAction scrolls the view down
|
||||
func (h *BufPane) ScrollDownAction() bool {
|
||||
h.ScrollDown(util.IntOpt(h.Buf.Settings["scrollspeed"]))
|
||||
return true
|
||||
@@ -160,6 +168,52 @@ func (h *BufPane) Center() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// CursorToViewTop moves the cursor to the top of the view,
|
||||
// offset by scrollmargin unless at the beginning or end of the file
|
||||
func (h *BufPane) CursorToViewTop() bool {
|
||||
v := h.GetView()
|
||||
h.Buf.ClearCursors()
|
||||
scrollmargin := int(h.Buf.Settings["scrollmargin"].(float64))
|
||||
bStart := display.SLoc{0, 0}
|
||||
if v.StartLine == bStart {
|
||||
scrollmargin = 0
|
||||
}
|
||||
h.Cursor.GotoLoc(h.LocFromVLoc(display.VLoc{
|
||||
SLoc: h.Scroll(v.StartLine, scrollmargin),
|
||||
VisualX: 0,
|
||||
}))
|
||||
return true
|
||||
}
|
||||
|
||||
// CursorToViewCenter moves the cursor to the center of the view
|
||||
func (h *BufPane) CursorToViewCenter() bool {
|
||||
v := h.GetView()
|
||||
h.Buf.ClearCursors()
|
||||
h.Cursor.GotoLoc(h.LocFromVLoc(display.VLoc{
|
||||
SLoc: h.Scroll(v.StartLine, h.BufView().Height/2),
|
||||
VisualX: 0,
|
||||
}))
|
||||
return true
|
||||
}
|
||||
|
||||
// CursorToViewBottom moves the cursor to the bottom of the view,
|
||||
// offset by scrollmargin unless at the beginning or end of the file
|
||||
func (h *BufPane) CursorToViewBottom() bool {
|
||||
v := h.GetView()
|
||||
h.Buf.ClearCursors()
|
||||
scrollmargin := int(h.Buf.Settings["scrollmargin"].(float64))
|
||||
bEnd := h.SLocFromLoc(h.Buf.End())
|
||||
lastLine := h.Scroll(v.StartLine, h.BufView().Height-1)
|
||||
if lastLine == bEnd {
|
||||
scrollmargin = 0
|
||||
}
|
||||
h.Cursor.GotoLoc(h.LocFromVLoc(display.VLoc{
|
||||
SLoc: h.Scroll(lastLine, -scrollmargin),
|
||||
VisualX: 0,
|
||||
}))
|
||||
return true
|
||||
}
|
||||
|
||||
// MoveCursorUp is not an action
|
||||
func (h *BufPane) MoveCursorUp(n int) {
|
||||
if !h.Buf.Settings["softwrap"].(bool) {
|
||||
@@ -170,10 +224,10 @@ func (h *BufPane) MoveCursorUp(n int) {
|
||||
if sloc == vloc.SLoc {
|
||||
// we are at the beginning of buffer
|
||||
h.Cursor.Loc = h.Buf.Start()
|
||||
h.Cursor.LastVisualX = 0
|
||||
h.Cursor.StoreVisualX()
|
||||
} else {
|
||||
vloc.SLoc = sloc
|
||||
vloc.VisualX = h.Cursor.LastVisualX
|
||||
vloc.VisualX = h.Cursor.LastWrappedVisualX
|
||||
h.Cursor.Loc = h.LocFromVLoc(vloc)
|
||||
}
|
||||
}
|
||||
@@ -189,11 +243,10 @@ func (h *BufPane) MoveCursorDown(n int) {
|
||||
if sloc == vloc.SLoc {
|
||||
// we are at the end of buffer
|
||||
h.Cursor.Loc = h.Buf.End()
|
||||
vloc = h.VLocFromLoc(h.Cursor.Loc)
|
||||
h.Cursor.LastVisualX = vloc.VisualX
|
||||
h.Cursor.StoreVisualX()
|
||||
} else {
|
||||
vloc.SLoc = sloc
|
||||
vloc.VisualX = h.Cursor.LastVisualX
|
||||
vloc.VisualX = h.Cursor.LastWrappedVisualX
|
||||
h.Cursor.Loc = h.LocFromVLoc(vloc)
|
||||
}
|
||||
}
|
||||
@@ -209,8 +262,13 @@ func (h *BufPane) CursorUp() bool {
|
||||
|
||||
// CursorDown moves the cursor down
|
||||
func (h *BufPane) CursorDown() bool {
|
||||
selectionEndNewline := h.Cursor.HasSelection() && h.Cursor.CurSelection[1].X == 0
|
||||
h.Cursor.Deselect(false)
|
||||
h.MoveCursorDown(1)
|
||||
if selectionEndNewline {
|
||||
h.Cursor.Start()
|
||||
} else {
|
||||
h.MoveCursorDown(1)
|
||||
}
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
@@ -244,7 +302,6 @@ func (h *BufPane) CursorLeft() bool {
|
||||
func (h *BufPane) CursorRight() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
h.Cursor.Deselect(false)
|
||||
h.Cursor.Right()
|
||||
} else {
|
||||
tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
|
||||
tabmovement := h.Buf.Settings["tabmovement"].(bool)
|
||||
@@ -657,7 +714,7 @@ func (h *BufPane) InsertNewline() bool {
|
||||
h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: util.CharacterCount(line), Y: h.Cursor.Y - 1})
|
||||
}
|
||||
}
|
||||
h.Cursor.LastVisualX = h.Cursor.GetVisualX()
|
||||
h.Cursor.StoreVisualX()
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
@@ -687,7 +744,7 @@ func (h *BufPane) Backspace() bool {
|
||||
h.Buf.Remove(loc.Move(-1, h.Buf), loc)
|
||||
}
|
||||
}
|
||||
h.Cursor.LastVisualX = h.Cursor.GetVisualX()
|
||||
h.Cursor.StoreVisualX()
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
@@ -854,6 +911,11 @@ func (h *BufPane) Autocomplete() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if b.HasSuggestions {
|
||||
b.CycleAutocomplete(true)
|
||||
return true
|
||||
}
|
||||
|
||||
if h.Cursor.X == 0 {
|
||||
return false
|
||||
}
|
||||
@@ -864,10 +926,6 @@ func (h *BufPane) Autocomplete() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if b.HasSuggestions {
|
||||
b.CycleAutocomplete(true)
|
||||
return true
|
||||
}
|
||||
return b.Autocomplete(buffer.BufferComplete)
|
||||
}
|
||||
|
||||
@@ -889,7 +947,7 @@ func (h *BufPane) InsertTab() bool {
|
||||
b := h.Buf
|
||||
indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
|
||||
tabBytes := len(indent)
|
||||
bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
|
||||
bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX(false) % tabBytes)
|
||||
b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
|
||||
h.Relocate()
|
||||
return true
|
||||
@@ -946,6 +1004,9 @@ func (h *BufPane) SaveAsCB(action string, callback func()) bool {
|
||||
h.completeAction(action)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
InfoBar.YNPrompt(
|
||||
@@ -977,13 +1038,16 @@ func (h *BufPane) saveBufToFile(filename string, action string, callback func())
|
||||
err := h.Buf.SaveAs(filename)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrPermission) {
|
||||
if runtime.GOOS == "windows" {
|
||||
InfoBar.Error("Permission denied. Save with sudo not supported on Windows")
|
||||
return true
|
||||
}
|
||||
|
||||
saveWithSudo := func() {
|
||||
err = h.Buf.SaveAsWithSudo(filename)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
} else {
|
||||
h.Buf.Path = filename
|
||||
h.Buf.SetName(filename)
|
||||
InfoBar.Message("Saved " + filename)
|
||||
if callback != nil {
|
||||
callback()
|
||||
@@ -1008,8 +1072,6 @@ func (h *BufPane) saveBufToFile(filename string, action string, callback func())
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
} else {
|
||||
h.Buf.Path = filename
|
||||
h.Buf.SetName(filename)
|
||||
InfoBar.Message("Saved " + filename)
|
||||
if callback != nil {
|
||||
callback()
|
||||
@@ -1082,8 +1144,7 @@ func (h *BufPane) find(useRegex bool) bool {
|
||||
match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
if found {
|
||||
} else if found {
|
||||
h.Cursor.SetSelectionStart(match[0])
|
||||
h.Cursor.SetSelectionEnd(match[1])
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
|
||||
@@ -1154,6 +1215,14 @@ func (h *BufPane) FindNext() bool {
|
||||
match, found, err := h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.Buf.LastSearchRegex)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
} else if found && searchLoc == match[0] && match[0] == match[1] {
|
||||
// skip empty match at present cursor location
|
||||
if searchLoc == h.Buf.End() {
|
||||
searchLoc = h.Buf.Start()
|
||||
} else {
|
||||
searchLoc = searchLoc.Move(1, h.Buf)
|
||||
}
|
||||
match, found, _ = h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.Buf.LastSearchRegex)
|
||||
}
|
||||
if found {
|
||||
h.Cursor.SetSelectionStart(match[0])
|
||||
@@ -1183,6 +1252,14 @@ func (h *BufPane) FindPrevious() bool {
|
||||
match, found, err := h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.Buf.LastSearchRegex)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
} else if found && searchLoc == match[0] && match[0] == match[1] {
|
||||
// skip empty match at present cursor location
|
||||
if searchLoc == h.Buf.Start() {
|
||||
searchLoc = h.Buf.End()
|
||||
} else {
|
||||
searchLoc = searchLoc.Move(-1, h.Buf)
|
||||
}
|
||||
match, found, _ = h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.Buf.LastSearchRegex)
|
||||
}
|
||||
if found {
|
||||
h.Cursor.SetSelectionStart(match[0])
|
||||
@@ -1238,101 +1315,189 @@ func (h *BufPane) Redo() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *BufPane) selectLines() int {
|
||||
if h.Cursor.HasSelection() {
|
||||
start := h.Cursor.CurSelection[0]
|
||||
end := h.Cursor.CurSelection[1]
|
||||
if start.GreaterThan(end) {
|
||||
start, end = end, start
|
||||
}
|
||||
if end.X == 0 {
|
||||
end = end.Move(-1, h.Buf)
|
||||
}
|
||||
|
||||
h.Cursor.Deselect(true)
|
||||
h.Cursor.SetSelectionStart(buffer.Loc{0, start.Y})
|
||||
h.Cursor.SetSelectionEnd(buffer.Loc{0, end.Y + 1})
|
||||
} else {
|
||||
h.Cursor.SelectLine()
|
||||
}
|
||||
|
||||
nlines := h.Cursor.CurSelection[1].Y - h.Cursor.CurSelection[0].Y
|
||||
if nlines == 0 && h.Cursor.HasSelection() {
|
||||
// selected last line and it is not empty
|
||||
nlines++
|
||||
}
|
||||
return nlines
|
||||
}
|
||||
|
||||
// Copy the selection to the system clipboard
|
||||
func (h *BufPane) Copy() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||
h.freshClip = true
|
||||
InfoBar.Message("Copied selection")
|
||||
}
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// CopyLine copies the current line to the clipboard
|
||||
func (h *BufPane) CopyLine() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
return false
|
||||
}
|
||||
origLoc := h.Cursor.Loc
|
||||
h.Cursor.SelectLine()
|
||||
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||
h.freshClip = true
|
||||
InfoBar.Message("Copied line")
|
||||
|
||||
h.Cursor.Deselect(true)
|
||||
h.Cursor.Loc = origLoc
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// CutLine cuts the current line to the clipboard
|
||||
func (h *BufPane) CutLine() bool {
|
||||
h.Cursor.SelectLine()
|
||||
if !h.Cursor.HasSelection() {
|
||||
return false
|
||||
}
|
||||
if h.freshClip {
|
||||
if h.Cursor.HasSelection() {
|
||||
if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
|
||||
InfoBar.Error(err)
|
||||
} else {
|
||||
clipboard.WriteMulti(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
|
||||
}
|
||||
}
|
||||
} else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || !h.freshClip {
|
||||
h.Copy()
|
||||
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||
h.freshClip = false
|
||||
InfoBar.Message("Copied selection")
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// CopyLine copies the current line to the clipboard. If there is a selection,
|
||||
// CopyLine copies all the lines that are (fully or partially) in the selection.
|
||||
func (h *BufPane) CopyLine() bool {
|
||||
origLoc := h.Cursor.Loc
|
||||
origLastVisualX := h.Cursor.LastVisualX
|
||||
origLastWrappedVisualX := h.Cursor.LastWrappedVisualX
|
||||
origSelection := h.Cursor.CurSelection
|
||||
|
||||
nlines := h.selectLines()
|
||||
if nlines == 0 {
|
||||
return false
|
||||
}
|
||||
h.freshClip = true
|
||||
h.lastCutTime = time.Now()
|
||||
h.Cursor.DeleteSelection()
|
||||
h.Cursor.ResetSelection()
|
||||
InfoBar.Message("Cut line")
|
||||
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||
h.freshClip = false
|
||||
if nlines > 1 {
|
||||
InfoBar.Message(fmt.Sprintf("Copied %d lines", nlines))
|
||||
} else {
|
||||
InfoBar.Message("Copied line")
|
||||
}
|
||||
|
||||
h.Cursor.Loc = origLoc
|
||||
h.Cursor.LastVisualX = origLastVisualX
|
||||
h.Cursor.LastWrappedVisualX = origLastWrappedVisualX
|
||||
h.Cursor.CurSelection = origSelection
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// Cut the selection to the system clipboard
|
||||
func (h *BufPane) Cut() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||
h.Cursor.DeleteSelection()
|
||||
h.Cursor.ResetSelection()
|
||||
h.freshClip = true
|
||||
InfoBar.Message("Cut selection")
|
||||
|
||||
h.Relocate()
|
||||
return true
|
||||
if !h.Cursor.HasSelection() {
|
||||
return false
|
||||
}
|
||||
return h.CutLine()
|
||||
}
|
||||
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||
h.Cursor.DeleteSelection()
|
||||
h.Cursor.ResetSelection()
|
||||
h.freshClip = false
|
||||
InfoBar.Message("Cut selection")
|
||||
|
||||
// DuplicateLine duplicates the current line or selection
|
||||
func (h *BufPane) DuplicateLine() bool {
|
||||
var infoMessage = "Duplicated line"
|
||||
if h.Cursor.HasSelection() {
|
||||
infoMessage = "Duplicated selection"
|
||||
h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
|
||||
} else {
|
||||
h.Cursor.End()
|
||||
h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
|
||||
// h.Cursor.Right()
|
||||
}
|
||||
|
||||
InfoBar.Message(infoMessage)
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// DeleteLine deletes the current line
|
||||
func (h *BufPane) DeleteLine() bool {
|
||||
h.Cursor.SelectLine()
|
||||
// CutLine cuts the current line to the clipboard. If there is a selection,
|
||||
// CutLine cuts all the lines that are (fully or partially) in the selection.
|
||||
func (h *BufPane) CutLine() bool {
|
||||
nlines := h.selectLines()
|
||||
if nlines == 0 {
|
||||
return false
|
||||
}
|
||||
totalLines := nlines
|
||||
if h.freshClip {
|
||||
if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
|
||||
InfoBar.Error(err)
|
||||
return false
|
||||
} else {
|
||||
clipboard.WriteMulti(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
|
||||
totalLines = strings.Count(clip, "\n") + nlines
|
||||
}
|
||||
} else {
|
||||
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||
}
|
||||
h.freshClip = true
|
||||
h.Cursor.DeleteSelection()
|
||||
h.Cursor.ResetSelection()
|
||||
h.Cursor.StoreVisualX()
|
||||
if totalLines > 1 {
|
||||
InfoBar.Message(fmt.Sprintf("Cut %d lines", totalLines))
|
||||
} else {
|
||||
InfoBar.Message("Cut line")
|
||||
}
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// Duplicate the selection
|
||||
func (h *BufPane) Duplicate() bool {
|
||||
if !h.Cursor.HasSelection() {
|
||||
return false
|
||||
}
|
||||
h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
|
||||
InfoBar.Message("Duplicated selection")
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// DuplicateLine duplicates the current line. If there is a selection, DuplicateLine
|
||||
// duplicates all the lines that are (fully or partially) in the selection.
|
||||
func (h *BufPane) DuplicateLine() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
origLoc := h.Cursor.Loc
|
||||
origLastVisualX := h.Cursor.LastVisualX
|
||||
origLastWrappedVisualX := h.Cursor.LastWrappedVisualX
|
||||
origSelection := h.Cursor.CurSelection
|
||||
|
||||
start := h.Cursor.CurSelection[0]
|
||||
end := h.Cursor.CurSelection[1]
|
||||
if start.GreaterThan(end) {
|
||||
start, end = end, start
|
||||
}
|
||||
if end.X == 0 {
|
||||
end = end.Move(-1, h.Buf)
|
||||
}
|
||||
|
||||
h.Cursor.Deselect(true)
|
||||
h.Cursor.Loc = end
|
||||
h.Cursor.End()
|
||||
for y := start.Y; y <= end.Y; y++ {
|
||||
h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(y)))
|
||||
}
|
||||
|
||||
h.Cursor.Loc = origLoc
|
||||
h.Cursor.LastVisualX = origLastVisualX
|
||||
h.Cursor.LastWrappedVisualX = origLastWrappedVisualX
|
||||
h.Cursor.CurSelection = origSelection
|
||||
|
||||
if start.Y < end.Y {
|
||||
InfoBar.Message(fmt.Sprintf("Duplicated %d lines", end.Y-start.Y+1))
|
||||
} else {
|
||||
InfoBar.Message("Duplicated line")
|
||||
}
|
||||
} else {
|
||||
h.Cursor.End()
|
||||
h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
|
||||
InfoBar.Message("Duplicated line")
|
||||
}
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// DeleteLine deletes the current line. If there is a selection, DeleteLine
|
||||
// deletes all the lines that are (fully or partially) in the selection.
|
||||
func (h *BufPane) DeleteLine() bool {
|
||||
nlines := h.selectLines()
|
||||
if nlines == 0 {
|
||||
return false
|
||||
}
|
||||
h.Cursor.DeleteSelection()
|
||||
h.Cursor.ResetSelection()
|
||||
InfoBar.Message("Deleted line")
|
||||
h.Cursor.StoreVisualX()
|
||||
if nlines > 1 {
|
||||
InfoBar.Message(fmt.Sprintf("Deleted %d lines", nlines))
|
||||
} else {
|
||||
InfoBar.Message("Deleted line")
|
||||
}
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
@@ -1536,63 +1701,84 @@ func (h *BufPane) End() bool {
|
||||
|
||||
// PageUp scrolls the view up a page
|
||||
func (h *BufPane) PageUp() bool {
|
||||
h.ScrollUp(h.BufView().Height)
|
||||
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
||||
h.ScrollUp(h.BufView().Height - pageOverlap)
|
||||
return true
|
||||
}
|
||||
|
||||
// PageDown scrolls the view down a page
|
||||
func (h *BufPane) PageDown() bool {
|
||||
h.ScrollDown(h.BufView().Height)
|
||||
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
||||
h.ScrollDown(h.BufView().Height - pageOverlap)
|
||||
h.ScrollAdjust()
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectPageUp selects up one page
|
||||
func (h *BufPane) SelectPageUp() bool {
|
||||
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
||||
scrollAmount := h.BufView().Height - pageOverlap
|
||||
if !h.Cursor.HasSelection() {
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
||||
}
|
||||
h.MoveCursorUp(h.BufView().Height)
|
||||
h.MoveCursorUp(scrollAmount)
|
||||
h.Cursor.SelectTo(h.Cursor.Loc)
|
||||
if h.Cursor.Num == 0 {
|
||||
h.ScrollUp(scrollAmount)
|
||||
}
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectPageDown selects down one page
|
||||
func (h *BufPane) SelectPageDown() bool {
|
||||
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
||||
scrollAmount := h.BufView().Height - pageOverlap
|
||||
if !h.Cursor.HasSelection() {
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
||||
}
|
||||
h.MoveCursorDown(h.BufView().Height)
|
||||
h.MoveCursorDown(scrollAmount)
|
||||
h.Cursor.SelectTo(h.Cursor.Loc)
|
||||
if h.Cursor.Num == 0 && !h.ScrollReachedEnd() {
|
||||
h.ScrollDown(scrollAmount)
|
||||
h.ScrollAdjust()
|
||||
}
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// CursorPageUp places the cursor a page up
|
||||
// CursorPageUp places the cursor a page up,
|
||||
// moving the view to keep cursor at the same relative position in the view
|
||||
func (h *BufPane) CursorPageUp() bool {
|
||||
h.Cursor.Deselect(true)
|
||||
|
||||
if h.Cursor.HasSelection() {
|
||||
h.Cursor.Loc = h.Cursor.CurSelection[0]
|
||||
h.Cursor.ResetSelection()
|
||||
h.Cursor.StoreVisualX()
|
||||
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
||||
scrollAmount := h.BufView().Height - pageOverlap
|
||||
h.MoveCursorUp(scrollAmount)
|
||||
if h.Cursor.Num == 0 {
|
||||
h.ScrollUp(scrollAmount)
|
||||
}
|
||||
h.MoveCursorUp(h.BufView().Height)
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// CursorPageDown places the cursor a page up
|
||||
// CursorPageDown places the cursor a page down,
|
||||
// moving the view to keep cursor at the same relative position in the view
|
||||
func (h *BufPane) CursorPageDown() bool {
|
||||
selectionEndNewline := h.Cursor.HasSelection() && h.Cursor.CurSelection[1].X == 0
|
||||
h.Cursor.Deselect(false)
|
||||
|
||||
if h.Cursor.HasSelection() {
|
||||
h.Cursor.Loc = h.Cursor.CurSelection[1]
|
||||
h.Cursor.ResetSelection()
|
||||
h.Cursor.StoreVisualX()
|
||||
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
||||
scrollAmount := h.BufView().Height - pageOverlap
|
||||
if selectionEndNewline {
|
||||
scrollAmount--
|
||||
}
|
||||
h.MoveCursorDown(scrollAmount)
|
||||
if h.Cursor.Num == 0 && !h.ScrollReachedEnd() {
|
||||
h.ScrollDown(scrollAmount)
|
||||
h.ScrollAdjust()
|
||||
}
|
||||
if selectionEndNewline {
|
||||
h.Cursor.Start()
|
||||
}
|
||||
h.MoveCursorDown(h.BufView().Height)
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
@@ -1612,12 +1798,12 @@ func (h *BufPane) HalfPageDown() bool {
|
||||
|
||||
// ToggleDiffGutter turns the diff gutter off and on
|
||||
func (h *BufPane) ToggleDiffGutter() bool {
|
||||
if !h.Buf.Settings["diffgutter"].(bool) {
|
||||
h.Buf.Settings["diffgutter"] = true
|
||||
diffgutter := !h.Buf.Settings["diffgutter"].(bool)
|
||||
h.Buf.SetOptionNative("diffgutter", diffgutter)
|
||||
if diffgutter {
|
||||
h.Buf.UpdateDiff()
|
||||
InfoBar.Message("Enabled diff gutter")
|
||||
} else {
|
||||
h.Buf.Settings["diffgutter"] = false
|
||||
InfoBar.Message("Disabled diff gutter")
|
||||
}
|
||||
return true
|
||||
@@ -1625,11 +1811,11 @@ func (h *BufPane) ToggleDiffGutter() bool {
|
||||
|
||||
// ToggleRuler turns line numbers off and on
|
||||
func (h *BufPane) ToggleRuler() bool {
|
||||
if !h.Buf.Settings["ruler"].(bool) {
|
||||
h.Buf.Settings["ruler"] = true
|
||||
ruler := !h.Buf.Settings["ruler"].(bool)
|
||||
h.Buf.SetOptionNative("ruler", ruler)
|
||||
if ruler {
|
||||
InfoBar.Message("Enabled ruler")
|
||||
} else {
|
||||
h.Buf.Settings["ruler"] = false
|
||||
InfoBar.Message("Disabled ruler")
|
||||
}
|
||||
return true
|
||||
@@ -1645,7 +1831,8 @@ func (h *BufPane) ToggleHelp() bool {
|
||||
if h.Buf.Type == buffer.BTHelp {
|
||||
h.Quit()
|
||||
} else {
|
||||
h.openHelp("help")
|
||||
hsplit := config.GlobalSettings["helpsplit"] == "hsplit"
|
||||
h.openHelp("help", hsplit, false)
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1681,7 +1868,7 @@ func (h *BufPane) CommandMode() bool {
|
||||
|
||||
// ToggleOverwriteMode lets the user toggle the text overwrite mode
|
||||
func (h *BufPane) ToggleOverwriteMode() bool {
|
||||
h.isOverwriteMode = !h.isOverwriteMode
|
||||
h.Buf.OverwriteMode = !h.Buf.OverwriteMode
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1708,11 +1895,11 @@ func (h *BufPane) ClearInfo() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ForceQuit closes the current tab or view even if there are unsaved changes
|
||||
// ForceQuit closes the tab or view even if there are unsaved changes
|
||||
// (no prompt)
|
||||
func (h *BufPane) ForceQuit() bool {
|
||||
h.Buf.Close()
|
||||
if len(MainTab().Panes) > 1 {
|
||||
if len(h.tab.Panes) > 1 {
|
||||
h.Unsplit()
|
||||
} else if len(Tabs.List) > 1 {
|
||||
Tabs.RemoveTab(h.splitID)
|
||||
@@ -1724,23 +1911,29 @@ func (h *BufPane) ForceQuit() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// closePrompt displays a prompt to save the buffer before closing it to proceed
|
||||
// with a different action or command
|
||||
func (h *BufPane) closePrompt(action string, callback func()) {
|
||||
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
|
||||
if !canceled && !yes {
|
||||
callback()
|
||||
} else if !canceled && yes {
|
||||
h.SaveCB(action, callback)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Quit this will close the current tab or view that is open
|
||||
func (h *BufPane) Quit() bool {
|
||||
if h.Buf.Modified() {
|
||||
if config.GlobalSettings["autosave"].(float64) > 0 {
|
||||
if h.Buf.Modified() && !h.Buf.Shared() {
|
||||
if config.GlobalSettings["autosave"].(float64) > 0 && h.Buf.Path != "" {
|
||||
// autosave on means we automatically save when quitting
|
||||
h.SaveCB("Quit", func() {
|
||||
h.ForceQuit()
|
||||
})
|
||||
} else {
|
||||
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
|
||||
if !canceled && !yes {
|
||||
h.ForceQuit()
|
||||
} else if !canceled && yes {
|
||||
h.SaveCB("Quit", func() {
|
||||
h.ForceQuit()
|
||||
})
|
||||
}
|
||||
h.closePrompt("Quit", func() {
|
||||
h.ForceQuit()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@@ -1793,27 +1986,38 @@ func (h *BufPane) AddTab() bool {
|
||||
|
||||
// PreviousTab switches to the previous tab in the tab list
|
||||
func (h *BufPane) PreviousTab() bool {
|
||||
tabsLen := len(Tabs.List)
|
||||
if tabsLen == 1 {
|
||||
if Tabs.Active() == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
a := Tabs.Active() + tabsLen
|
||||
Tabs.SetActive((a - 1) % tabsLen)
|
||||
|
||||
Tabs.SetActive(Tabs.Active() - 1)
|
||||
return true
|
||||
}
|
||||
|
||||
// NextTab switches to the next tab in the tab list
|
||||
func (h *BufPane) NextTab() bool {
|
||||
tabsLen := len(Tabs.List)
|
||||
if tabsLen == 1 {
|
||||
if Tabs.Active() == len(Tabs.List)-1 {
|
||||
return false
|
||||
}
|
||||
Tabs.SetActive(Tabs.Active() + 1)
|
||||
return true
|
||||
}
|
||||
|
||||
a := Tabs.Active()
|
||||
Tabs.SetActive((a + 1) % tabsLen)
|
||||
// FirstTab switches to the first tab in the tab list
|
||||
func (h *BufPane) FirstTab() bool {
|
||||
if Tabs.Active() == 0 {
|
||||
return false
|
||||
}
|
||||
Tabs.SetActive(0)
|
||||
return true
|
||||
}
|
||||
|
||||
// LastTab switches to the last tab in the tab list
|
||||
func (h *BufPane) LastTab() bool {
|
||||
lastTabIndex := len(Tabs.List) - 1
|
||||
if Tabs.Active() == lastTabIndex {
|
||||
return false
|
||||
}
|
||||
Tabs.SetActive(lastTabIndex)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1848,47 +2052,49 @@ func (h *BufPane) Unsplit() bool {
|
||||
|
||||
// NextSplit changes the view to the next split
|
||||
func (h *BufPane) NextSplit() bool {
|
||||
if len(h.tab.Panes) == 1 {
|
||||
if h.tab.active == len(h.tab.Panes)-1 {
|
||||
return false
|
||||
}
|
||||
|
||||
a := h.tab.active
|
||||
if a < len(h.tab.Panes)-1 {
|
||||
a++
|
||||
} else {
|
||||
a = 0
|
||||
}
|
||||
|
||||
h.tab.SetActive(a)
|
||||
|
||||
h.tab.SetActive(h.tab.active + 1)
|
||||
return true
|
||||
}
|
||||
|
||||
// PreviousSplit changes the view to the previous split
|
||||
func (h *BufPane) PreviousSplit() bool {
|
||||
if len(h.tab.Panes) == 1 {
|
||||
if h.tab.active == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
a := h.tab.active
|
||||
if a > 0 {
|
||||
a--
|
||||
} else {
|
||||
a = len(h.tab.Panes) - 1
|
||||
}
|
||||
h.tab.SetActive(a)
|
||||
|
||||
h.tab.SetActive(h.tab.active - 1)
|
||||
return true
|
||||
}
|
||||
|
||||
var curmacro []interface{}
|
||||
// FirstSplit changes the view to the first split
|
||||
func (h *BufPane) FirstSplit() bool {
|
||||
if h.tab.active == 0 {
|
||||
return false
|
||||
}
|
||||
h.tab.SetActive(0)
|
||||
return true
|
||||
}
|
||||
|
||||
// LastSplit changes the view to the last split
|
||||
func (h *BufPane) LastSplit() bool {
|
||||
lastPaneIdx := len(h.tab.Panes) - 1
|
||||
if h.tab.active == lastPaneIdx {
|
||||
return false
|
||||
}
|
||||
h.tab.SetActive(lastPaneIdx)
|
||||
return true
|
||||
}
|
||||
|
||||
var curmacro []any
|
||||
var recordingMacro bool
|
||||
|
||||
// ToggleMacro toggles recording of a macro
|
||||
func (h *BufPane) ToggleMacro() bool {
|
||||
recordingMacro = !recordingMacro
|
||||
if recordingMacro {
|
||||
curmacro = []interface{}{}
|
||||
curmacro = []any{}
|
||||
InfoBar.Message("Recording")
|
||||
} else {
|
||||
InfoBar.Message("Stopped recording")
|
||||
@@ -1955,38 +2161,31 @@ func (h *BufPane) SpawnMultiCursor() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SpawnCursorAtLoc spawns a new cursor at a location and merges the cursors
|
||||
func (h *BufPane) SpawnCursorAtLoc(loc buffer.Loc) *buffer.Cursor {
|
||||
c := buffer.NewCursor(h.Buf, loc)
|
||||
h.Buf.AddCursor(c)
|
||||
h.Buf.MergeCursors()
|
||||
return c
|
||||
}
|
||||
|
||||
// SpawnMultiCursorUpN is not an action
|
||||
func (h *BufPane) SpawnMultiCursorUpN(n int) bool {
|
||||
lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
|
||||
var c *buffer.Cursor
|
||||
if !h.Buf.Settings["softwrap"].(bool) {
|
||||
if n > 0 && lastC.Y == 0 {
|
||||
return false
|
||||
}
|
||||
if n < 0 && lastC.Y+1 == h.Buf.LinesNum() {
|
||||
return false
|
||||
}
|
||||
|
||||
h.Buf.DeselectCursors()
|
||||
|
||||
c = buffer.NewCursor(h.Buf, buffer.Loc{lastC.X, lastC.Y - n})
|
||||
c.LastVisualX = lastC.LastVisualX
|
||||
c.X = c.GetCharPosInLine(h.Buf.LineBytes(c.Y), c.LastVisualX)
|
||||
c.Relocate()
|
||||
} else {
|
||||
vloc := h.VLocFromLoc(lastC.Loc)
|
||||
sloc := h.Scroll(vloc.SLoc, -n)
|
||||
if sloc == vloc.SLoc {
|
||||
return false
|
||||
}
|
||||
|
||||
h.Buf.DeselectCursors()
|
||||
|
||||
vloc.SLoc = sloc
|
||||
vloc.VisualX = lastC.LastVisualX
|
||||
c = buffer.NewCursor(h.Buf, h.LocFromVLoc(vloc))
|
||||
c.LastVisualX = lastC.LastVisualX
|
||||
if n > 0 && lastC.Y == 0 {
|
||||
return false
|
||||
}
|
||||
if n < 0 && lastC.Y+1 == h.Buf.LinesNum() {
|
||||
return false
|
||||
}
|
||||
|
||||
h.Buf.DeselectCursors()
|
||||
|
||||
c := buffer.NewCursor(h.Buf, buffer.Loc{lastC.X, lastC.Y - n})
|
||||
c.LastVisualX = lastC.LastVisualX
|
||||
c.LastWrappedVisualX = lastC.LastWrappedVisualX
|
||||
c.X = c.GetCharPosInLine(h.Buf.LineBytes(c.Y), c.LastVisualX)
|
||||
c.Relocate()
|
||||
|
||||
h.Buf.AddCursor(c)
|
||||
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
|
||||
@@ -2068,14 +2267,16 @@ func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SkipMultiCursor moves the current multiple cursor to the next available position
|
||||
func (h *BufPane) SkipMultiCursor() bool {
|
||||
func (h *BufPane) skipMultiCursor(forward bool) bool {
|
||||
lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
|
||||
if !lastC.HasSelection() {
|
||||
return false
|
||||
}
|
||||
sel := lastC.GetSelection()
|
||||
searchStart := lastC.CurSelection[1]
|
||||
if !forward {
|
||||
searchStart = lastC.CurSelection[0]
|
||||
}
|
||||
|
||||
search := string(sel)
|
||||
search = regexp.QuoteMeta(search)
|
||||
@@ -2083,7 +2284,7 @@ func (h *BufPane) SkipMultiCursor() bool {
|
||||
search = "\\b" + search + "\\b"
|
||||
}
|
||||
|
||||
match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
|
||||
match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, forward, true)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
@@ -2103,6 +2304,16 @@ func (h *BufPane) SkipMultiCursor() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SkipMultiCursor moves the current multiple cursor to the next available position
|
||||
func (h *BufPane) SkipMultiCursor() bool {
|
||||
return h.skipMultiCursor(true)
|
||||
}
|
||||
|
||||
// SkipMultiCursorBack moves the current multiple cursor to the previous available position
|
||||
func (h *BufPane) SkipMultiCursorBack() bool {
|
||||
return h.skipMultiCursor(false)
|
||||
}
|
||||
|
||||
// RemoveMultiCursor removes the latest multiple cursor
|
||||
func (h *BufPane) RemoveMultiCursor() bool {
|
||||
if h.Buf.NumCursors() > 1 {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build plan9 nacl windows
|
||||
//go:build plan9 || nacl || windows
|
||||
|
||||
package action
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// +build linux darwin dragonfly solaris openbsd netbsd freebsd
|
||||
//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd
|
||||
|
||||
package action
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
)
|
||||
|
||||
// Suspend sends micro to the background. This is the same as pressing CtrlZ in most unix programs.
|
||||
|
||||
@@ -4,17 +4,18 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io/fs"
|
||||
"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"
|
||||
"github.com/micro-editor/json5"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
)
|
||||
|
||||
var Binder = map[string]func(e Event, action string){
|
||||
@@ -23,21 +24,25 @@ var Binder = map[string]func(e Event, action string){
|
||||
"terminal": TermMapEvent,
|
||||
}
|
||||
|
||||
func writeFile(name string, txt []byte) error {
|
||||
return util.SafeWrite(name, txt, false)
|
||||
}
|
||||
|
||||
func createBindingsIfNotExist(fname string) {
|
||||
if _, e := os.Stat(fname); os.IsNotExist(e) {
|
||||
ioutil.WriteFile(fname, []byte("{}"), 0644)
|
||||
if _, e := os.Stat(fname); errors.Is(e, fs.ErrNotExist) {
|
||||
writeFile(fname, []byte("{}"))
|
||||
}
|
||||
}
|
||||
|
||||
// InitBindings intializes the bindings map by reading from bindings.json
|
||||
func InitBindings() {
|
||||
var parsed map[string]interface{}
|
||||
var parsed map[string]any
|
||||
|
||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||
createBindingsIfNotExist(filename)
|
||||
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
input, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error reading bindings.json file: " + err.Error())
|
||||
return
|
||||
@@ -61,7 +66,7 @@ func InitBindings() {
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
BindKey(k, val, Binder["buffer"])
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
bind, ok := Binder[k]
|
||||
if !ok || bind == nil {
|
||||
screen.TermMessage(fmt.Sprintf("%s is not a valid pane type", k))
|
||||
@@ -89,7 +94,7 @@ func BindKey(k, v string, bind func(e Event, a string)) {
|
||||
}
|
||||
|
||||
if strings.HasPrefix(k, "\x1b") {
|
||||
screen.Screen.RegisterRawSeq(k)
|
||||
screen.RegisterRawSeq(k)
|
||||
}
|
||||
|
||||
bind(event, v)
|
||||
@@ -256,16 +261,25 @@ func eventsEqual(e1 Event, e2 Event) bool {
|
||||
return e1 == e2
|
||||
}
|
||||
|
||||
// TryBindKeyPlug tries to bind a key for the plugin without writing to bindings.json.
|
||||
// This operation can be rejected by lockbindings to prevent unexpected actions by the user.
|
||||
func TryBindKeyPlug(k, v string, overwrite bool) (bool, error) {
|
||||
if l, ok := config.GlobalSettings["lockbindings"]; ok && l.(bool) {
|
||||
return false, errors.New("bindings is locked by the user")
|
||||
}
|
||||
return TryBindKey(k, v, overwrite, false)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Returns true if the keybinding already existed or is binded successfully and a possible error
|
||||
func TryBindKey(k, v string, overwrite bool, writeToFile bool) (bool, error) {
|
||||
var e error
|
||||
var parsed map[string]interface{}
|
||||
var parsed map[string]any
|
||||
|
||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||
createBindingsIfNotExist(filename)
|
||||
if _, e = os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
input, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return false, errors.New("Error reading bindings.json file: " + err.Error())
|
||||
}
|
||||
@@ -304,7 +318,13 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
|
||||
BindKey(k, v, Binder["buffer"])
|
||||
|
||||
txt, _ := json.MarshalIndent(parsed, "", " ")
|
||||
return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
||||
txt = append(txt, '\n')
|
||||
|
||||
if writeToFile {
|
||||
return true, writeFile(filename, txt)
|
||||
} else {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, e
|
||||
}
|
||||
@@ -312,12 +332,12 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
|
||||
// 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{}
|
||||
var parsed map[string]any
|
||||
|
||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||
createBindingsIfNotExist(filename)
|
||||
if _, e = os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
input, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return errors.New("Error reading bindings.json file: " + err.Error())
|
||||
}
|
||||
@@ -342,7 +362,7 @@ func UnbindKey(k string) error {
|
||||
}
|
||||
|
||||
if strings.HasPrefix(k, "\x1b") {
|
||||
screen.Screen.UnregisterRawSeq(k)
|
||||
screen.UnregisterRawSeq(k)
|
||||
}
|
||||
|
||||
defaults := DefaultBindings("buffer")
|
||||
@@ -354,7 +374,8 @@ func UnbindKey(k string) error {
|
||||
}
|
||||
|
||||
txt, _ := json.MarshalIndent(parsed, "", " ")
|
||||
return ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
||||
txt = append(txt, '\n')
|
||||
return writeFile(filename, txt)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -6,17 +6,17 @@ import (
|
||||
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
"github.com/micro-editor/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/display"
|
||||
ulua "github.com/micro-editor/micro/v2/internal/lua"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
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{}
|
||||
type BufAction any
|
||||
|
||||
// BufKeyAction represents an action bound to a key.
|
||||
type BufKeyAction func(*BufPane) bool
|
||||
@@ -100,9 +100,7 @@ func BufMapEvent(k Event, action string) {
|
||||
break
|
||||
}
|
||||
|
||||
// TODO: fix problem when complex bindings have these
|
||||
// characters (escape them?)
|
||||
idx := strings.IndexAny(action, "&|,")
|
||||
idx := util.IndexAnyUnquoted(action, "&|,")
|
||||
a := action
|
||||
if idx >= 0 {
|
||||
a = action[:idx]
|
||||
@@ -226,26 +224,21 @@ type BufPane struct {
|
||||
// (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 returns true if one or more lines have been cut to the clipboard
|
||||
// and have never been pasted yet.
|
||||
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
|
||||
DoubleClick bool
|
||||
// Same here, just to keep track for mouse move events
|
||||
tripleClick bool
|
||||
TripleClick bool
|
||||
|
||||
// Should the current multiple cursor selection search based on word or
|
||||
// based on selection (false for selection, true for word)
|
||||
@@ -328,18 +321,16 @@ func (h *BufPane) ResizePane(size int) {
|
||||
}
|
||||
|
||||
// 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)
|
||||
// error if there is one and returns the aggregate boolean response.
|
||||
// The bufpane is passed as the first argument to the callbacks,
|
||||
// optional args are passed as the next arguments.
|
||||
func (h *BufPane) PluginCB(cb string, args ...any) bool {
|
||||
largs := []lua.LValue{luar.New(ulua.L, h)}
|
||||
for _, a := range args {
|
||||
largs = append(largs, luar.New(ulua.L, a))
|
||||
}
|
||||
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)))
|
||||
b, err := config.RunPluginFnBool(h.Buf.Settings, cb, largs...)
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
@@ -363,9 +354,6 @@ func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
|
||||
// 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{}
|
||||
}
|
||||
|
||||
@@ -569,7 +557,7 @@ func (h *BufPane) execAction(action BufAction, name string, te *tcell.EventMouse
|
||||
h.Buf.HasSuggestions = false
|
||||
}
|
||||
|
||||
if !h.PluginCB("pre" + name) {
|
||||
if !h.PluginCB("pre"+name, te) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -580,7 +568,7 @@ func (h *BufPane) execAction(action BufAction, name string, te *tcell.EventMouse
|
||||
case BufMouseAction:
|
||||
success = a(h, te)
|
||||
}
|
||||
success = success && h.PluginCB("on"+name)
|
||||
success = success && h.PluginCB("on"+name, te)
|
||||
|
||||
if _, ok := MultiActions[name]; ok {
|
||||
if recordingMacro {
|
||||
@@ -636,7 +624,7 @@ func (h *BufPane) DoRuneInsert(r rune) {
|
||||
// Insert a character
|
||||
h.Buf.SetCurCursor(c.Num)
|
||||
h.Cursor = c
|
||||
if !h.PluginCBRune("preRune", r) {
|
||||
if !h.PluginCB("preRune", string(r)) {
|
||||
continue
|
||||
}
|
||||
if c.HasSelection() {
|
||||
@@ -644,7 +632,7 @@ func (h *BufPane) DoRuneInsert(r rune) {
|
||||
c.ResetSelection()
|
||||
}
|
||||
|
||||
if h.isOverwriteMode {
|
||||
if h.Buf.OverwriteMode {
|
||||
next := c.Loc
|
||||
next.X++
|
||||
h.Buf.Replace(c.Loc, next, string(r))
|
||||
@@ -655,27 +643,35 @@ func (h *BufPane) DoRuneInsert(r rune) {
|
||||
curmacro = append(curmacro, r)
|
||||
}
|
||||
h.Relocate()
|
||||
h.PluginCBRune("onRune", r)
|
||||
h.PluginCB("onRune", string(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)
|
||||
e.splitID = h.tab.GetNode(h.splitID).VSplit(right)
|
||||
currentPaneIdx := h.tab.GetPane(h.splitID)
|
||||
if right {
|
||||
currentPaneIdx++
|
||||
}
|
||||
h.tab.AddPane(e, currentPaneIdx)
|
||||
h.tab.Resize()
|
||||
h.tab.SetActive(currentPaneIdx)
|
||||
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)
|
||||
e.splitID = h.tab.GetNode(h.splitID).HSplit(bottom)
|
||||
currentPaneIdx := h.tab.GetPane(h.splitID)
|
||||
if bottom {
|
||||
currentPaneIdx++
|
||||
}
|
||||
h.tab.AddPane(e, currentPaneIdx)
|
||||
h.tab.Resize()
|
||||
h.tab.SetActive(currentPaneIdx)
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -733,6 +729,9 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||
"CursorRight": (*BufPane).CursorRight,
|
||||
"CursorStart": (*BufPane).CursorStart,
|
||||
"CursorEnd": (*BufPane).CursorEnd,
|
||||
"CursorToViewTop": (*BufPane).CursorToViewTop,
|
||||
"CursorToViewCenter": (*BufPane).CursorToViewCenter,
|
||||
"CursorToViewBottom": (*BufPane).CursorToViewBottom,
|
||||
"SelectToStart": (*BufPane).SelectToStart,
|
||||
"SelectToEnd": (*BufPane).SelectToEnd,
|
||||
"SelectUp": (*BufPane).SelectUp,
|
||||
@@ -780,6 +779,7 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||
"CopyLine": (*BufPane).CopyLine,
|
||||
"Cut": (*BufPane).Cut,
|
||||
"CutLine": (*BufPane).CutLine,
|
||||
"Duplicate": (*BufPane).Duplicate,
|
||||
"DuplicateLine": (*BufPane).DuplicateLine,
|
||||
"DeleteLine": (*BufPane).DeleteLine,
|
||||
"MoveLinesUp": (*BufPane).MoveLinesUp,
|
||||
@@ -824,8 +824,12 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||
"AddTab": (*BufPane).AddTab,
|
||||
"PreviousTab": (*BufPane).PreviousTab,
|
||||
"NextTab": (*BufPane).NextTab,
|
||||
"FirstTab": (*BufPane).FirstTab,
|
||||
"LastTab": (*BufPane).LastTab,
|
||||
"NextSplit": (*BufPane).NextSplit,
|
||||
"PreviousSplit": (*BufPane).PreviousSplit,
|
||||
"FirstSplit": (*BufPane).FirstSplit,
|
||||
"LastSplit": (*BufPane).LastSplit,
|
||||
"Unsplit": (*BufPane).Unsplit,
|
||||
"VSplit": (*BufPane).VSplitAction,
|
||||
"HSplit": (*BufPane).HSplitAction,
|
||||
@@ -841,6 +845,7 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||
"RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
|
||||
"RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
|
||||
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
|
||||
"SkipMultiCursorBack": (*BufPane).SkipMultiCursorBack,
|
||||
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
|
||||
"JumpLine": (*BufPane).JumpLine,
|
||||
"Deselect": (*BufPane).Deselect,
|
||||
@@ -907,6 +912,7 @@ var MultiActions = map[string]bool{
|
||||
"Copy": true,
|
||||
"Cut": true,
|
||||
"CutLine": true,
|
||||
"Duplicate": true,
|
||||
"DuplicateLine": true,
|
||||
"DeleteLine": true,
|
||||
"MoveLinesUp": true,
|
||||
|
||||
@@ -13,12 +13,12 @@ import (
|
||||
"strings"
|
||||
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"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/micro-editor/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/clipboard"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/shell"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// A Command contains information about how to execute a command
|
||||
@@ -32,39 +32,41 @@ var commands map[string]Command
|
||||
|
||||
func InitCommands() {
|
||||
commands = map[string]Command{
|
||||
"set": {(*BufPane).SetCmd, OptionValueComplete},
|
||||
"reset": {(*BufPane).ResetCmd, OptionValueComplete},
|
||||
"setlocal": {(*BufPane).SetLocalCmd, OptionValueComplete},
|
||||
"show": {(*BufPane).ShowCmd, OptionComplete},
|
||||
"showkey": {(*BufPane).ShowKeyCmd, nil},
|
||||
"run": {(*BufPane).RunCmd, nil},
|
||||
"bind": {(*BufPane).BindCmd, nil},
|
||||
"unbind": {(*BufPane).UnbindCmd, nil},
|
||||
"quit": {(*BufPane).QuitCmd, nil},
|
||||
"goto": {(*BufPane).GotoCmd, nil},
|
||||
"jump": {(*BufPane).JumpCmd, nil},
|
||||
"save": {(*BufPane).SaveCmd, nil},
|
||||
"replace": {(*BufPane).ReplaceCmd, nil},
|
||||
"replaceall": {(*BufPane).ReplaceAllCmd, nil},
|
||||
"vsplit": {(*BufPane).VSplitCmd, buffer.FileComplete},
|
||||
"hsplit": {(*BufPane).HSplitCmd, buffer.FileComplete},
|
||||
"tab": {(*BufPane).NewTabCmd, buffer.FileComplete},
|
||||
"help": {(*BufPane).HelpCmd, HelpComplete},
|
||||
"eval": {(*BufPane).EvalCmd, nil},
|
||||
"log": {(*BufPane).ToggleLogCmd, nil},
|
||||
"plugin": {(*BufPane).PluginCmd, PluginComplete},
|
||||
"reload": {(*BufPane).ReloadCmd, nil},
|
||||
"reopen": {(*BufPane).ReopenCmd, nil},
|
||||
"cd": {(*BufPane).CdCmd, buffer.FileComplete},
|
||||
"pwd": {(*BufPane).PwdCmd, nil},
|
||||
"open": {(*BufPane).OpenCmd, buffer.FileComplete},
|
||||
"tabmove": {(*BufPane).TabMoveCmd, nil},
|
||||
"tabswitch": {(*BufPane).TabSwitchCmd, nil},
|
||||
"term": {(*BufPane).TermCmd, nil},
|
||||
"memusage": {(*BufPane).MemUsageCmd, nil},
|
||||
"retab": {(*BufPane).RetabCmd, nil},
|
||||
"raw": {(*BufPane).RawCmd, nil},
|
||||
"textfilter": {(*BufPane).TextFilterCmd, nil},
|
||||
"set": {(*BufPane).SetCmd, OptionValueComplete},
|
||||
"setlocal": {(*BufPane).SetLocalCmd, OptionValueComplete},
|
||||
"toggle": {(*BufPane).ToggleCmd, OptionValueComplete},
|
||||
"togglelocal": {(*BufPane).ToggleLocalCmd, OptionValueComplete},
|
||||
"reset": {(*BufPane).ResetCmd, OptionValueComplete},
|
||||
"show": {(*BufPane).ShowCmd, OptionComplete},
|
||||
"showkey": {(*BufPane).ShowKeyCmd, nil},
|
||||
"run": {(*BufPane).RunCmd, nil},
|
||||
"bind": {(*BufPane).BindCmd, nil},
|
||||
"unbind": {(*BufPane).UnbindCmd, nil},
|
||||
"quit": {(*BufPane).QuitCmd, nil},
|
||||
"goto": {(*BufPane).GotoCmd, nil},
|
||||
"jump": {(*BufPane).JumpCmd, nil},
|
||||
"save": {(*BufPane).SaveCmd, nil},
|
||||
"replace": {(*BufPane).ReplaceCmd, nil},
|
||||
"replaceall": {(*BufPane).ReplaceAllCmd, nil},
|
||||
"vsplit": {(*BufPane).VSplitCmd, buffer.FileComplete},
|
||||
"hsplit": {(*BufPane).HSplitCmd, buffer.FileComplete},
|
||||
"tab": {(*BufPane).NewTabCmd, buffer.FileComplete},
|
||||
"help": {(*BufPane).HelpCmd, HelpComplete},
|
||||
"eval": {(*BufPane).EvalCmd, nil},
|
||||
"log": {(*BufPane).ToggleLogCmd, nil},
|
||||
"plugin": {(*BufPane).PluginCmd, PluginComplete},
|
||||
"reload": {(*BufPane).ReloadCmd, nil},
|
||||
"reopen": {(*BufPane).ReopenCmd, nil},
|
||||
"cd": {(*BufPane).CdCmd, buffer.FileComplete},
|
||||
"pwd": {(*BufPane).PwdCmd, nil},
|
||||
"open": {(*BufPane).OpenCmd, buffer.FileComplete},
|
||||
"tabmove": {(*BufPane).TabMoveCmd, nil},
|
||||
"tabswitch": {(*BufPane).TabSwitchCmd, nil},
|
||||
"term": {(*BufPane).TermCmd, nil},
|
||||
"memusage": {(*BufPane).MemUsageCmd, nil},
|
||||
"retab": {(*BufPane).RetabCmd, nil},
|
||||
"raw": {(*BufPane).RawCmd, nil},
|
||||
"textfilter": {(*BufPane).TextFilterCmd, nil},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,23 +141,25 @@ func (h *BufPane) TextFilterCmd(args []string) {
|
||||
InfoBar.Error("usage: textfilter arguments")
|
||||
return
|
||||
}
|
||||
sel := h.Cursor.GetSelection()
|
||||
if len(sel) == 0 {
|
||||
h.Cursor.SelectWord()
|
||||
sel = h.Cursor.GetSelection()
|
||||
for _, c := range h.Buf.GetCursors() {
|
||||
sel := c.GetSelection()
|
||||
if len(sel) == 0 {
|
||||
c.SelectWord()
|
||||
sel = c.GetSelection()
|
||||
}
|
||||
var bout, berr bytes.Buffer
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stdin = strings.NewReader(string(sel))
|
||||
cmd.Stderr = &berr
|
||||
cmd.Stdout = &bout
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
InfoBar.Error(err.Error() + " " + berr.String())
|
||||
return
|
||||
}
|
||||
c.DeleteSelection()
|
||||
h.Buf.Insert(c.Loc, bout.String())
|
||||
}
|
||||
var bout, berr bytes.Buffer
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stdin = strings.NewReader(string(sel))
|
||||
cmd.Stderr = &berr
|
||||
cmd.Stdout = &bout
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
InfoBar.Error(err.Error() + " " + berr.String())
|
||||
return
|
||||
}
|
||||
h.Cursor.DeleteSelection()
|
||||
h.Buf.Insert(h.Cursor.Loc, bout.String())
|
||||
}
|
||||
|
||||
// TabMoveCmd moves the current tab to a given index (starts at 1). The
|
||||
@@ -203,6 +207,7 @@ func (h *BufPane) TabMoveCmd(args []string) {
|
||||
Tabs.List = append(Tabs.List, nil)
|
||||
copy(Tabs.List[idxTo+1:], Tabs.List[idxTo:])
|
||||
Tabs.List[idxTo] = activeTab
|
||||
Tabs.Resize()
|
||||
Tabs.UpdateNames()
|
||||
Tabs.SetActive(idxTo)
|
||||
// InfoBar.Message(fmt.Sprintf("Moved tab from slot %d to %d", idxFrom+1, idxTo+1))
|
||||
@@ -285,35 +290,16 @@ func (h *BufPane) PwdCmd(args []string) {
|
||||
// OpenCmd opens a new buffer with a given filename
|
||||
func (h *BufPane) OpenCmd(args []string) {
|
||||
if len(args) > 0 {
|
||||
filename := args[0]
|
||||
// the filename might or might not be quoted, so unquote first then join the strings.
|
||||
args, err := shellquote.Split(filename)
|
||||
if err != nil {
|
||||
InfoBar.Error("Error parsing args ", err)
|
||||
return
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return
|
||||
}
|
||||
filename = strings.Join(args, " ")
|
||||
|
||||
open := func() {
|
||||
b, err := buffer.NewBufferFromFile(filename, buffer.BTDefault)
|
||||
b, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
h.OpenBuffer(b)
|
||||
}
|
||||
if h.Buf.Modified() {
|
||||
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
|
||||
if !canceled && !yes {
|
||||
open()
|
||||
} else if !canceled && yes {
|
||||
h.Save()
|
||||
open()
|
||||
}
|
||||
})
|
||||
if h.Buf.Modified() && !h.Buf.Shared() {
|
||||
h.closePrompt("Save", open)
|
||||
} else {
|
||||
open()
|
||||
}
|
||||
@@ -428,7 +414,7 @@ func (h *BufPane) ReopenCmd(args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BufPane) openHelp(page string) error {
|
||||
func (h *BufPane) openHelp(page string, hsplit bool, forceSplit bool) error {
|
||||
if data, err := config.FindRuntimeFile(config.RTHelp, page).Data(); err != nil {
|
||||
return errors.New(fmt.Sprintf("Unable to load help text for %s: %v", page, err))
|
||||
} else {
|
||||
@@ -437,33 +423,74 @@ func (h *BufPane) openHelp(page string) error {
|
||||
helpBuffer.SetOptionNative("hltaberrors", false)
|
||||
helpBuffer.SetOptionNative("hltrailingws", false)
|
||||
|
||||
if h.Buf.Type == buffer.BTHelp {
|
||||
if h.Buf.Type == buffer.BTHelp && !forceSplit {
|
||||
h.OpenBuffer(helpBuffer)
|
||||
} else {
|
||||
} else if hsplit {
|
||||
h.HSplitBuf(helpBuffer)
|
||||
} else {
|
||||
h.VSplitBuf(helpBuffer)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HelpCmd tries to open the given help page in a horizontal split
|
||||
// HelpCmd tries to open the given help page according to the split type
|
||||
// configured with the "helpsplit" option. It can be overridden by the optional
|
||||
// arguments "-vpslit" or "-hsplit". In case more than one help page is given
|
||||
// as argument then it opens all of them with the defined split type.
|
||||
func (h *BufPane) HelpCmd(args []string) {
|
||||
hsplit := config.GlobalSettings["helpsplit"] == "hsplit"
|
||||
if len(args) < 1 {
|
||||
// Open the default help if the user just typed "> help"
|
||||
h.openHelp("help")
|
||||
h.openHelp("help", hsplit, false)
|
||||
} else {
|
||||
if config.FindRuntimeFile(config.RTHelp, args[0]) != nil {
|
||||
err := h.openHelp(args[0])
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
var topics []string
|
||||
forceSplit := false
|
||||
const errSplit = "hsplit and vsplit are not allowed at the same time"
|
||||
for _, arg := range args {
|
||||
switch arg {
|
||||
case "-vsplit":
|
||||
if forceSplit {
|
||||
InfoBar.Error(errSplit)
|
||||
return
|
||||
}
|
||||
hsplit = false
|
||||
forceSplit = true
|
||||
case "-hsplit":
|
||||
if forceSplit {
|
||||
InfoBar.Error(errSplit)
|
||||
return
|
||||
}
|
||||
hsplit = true
|
||||
forceSplit = true
|
||||
default:
|
||||
topics = append(topics, arg)
|
||||
}
|
||||
}
|
||||
|
||||
if len(topics) < 1 {
|
||||
// Do the same as without arg
|
||||
h.openHelp("help", hsplit, forceSplit)
|
||||
return
|
||||
}
|
||||
if len(topics) > 1 {
|
||||
forceSplit = true
|
||||
}
|
||||
|
||||
for _, topic := range topics {
|
||||
if config.FindRuntimeFile(config.RTHelp, topic) != nil {
|
||||
err := h.openHelp(topic, hsplit, forceSplit)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
} else {
|
||||
InfoBar.Error("Sorry, no help for ", topic)
|
||||
}
|
||||
} else {
|
||||
InfoBar.Error("Sorry, no help for ", args[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// VSplitCmd opens a vertical split with file given in the first argument
|
||||
// VSplitCmd opens one or more vertical splits with the files given as arguments
|
||||
// If no file is given, it opens an empty buffer in a new split
|
||||
func (h *BufPane) VSplitCmd(args []string) {
|
||||
if len(args) == 0 {
|
||||
@@ -472,16 +499,18 @@ func (h *BufPane) VSplitCmd(args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
for _, a := range args {
|
||||
buf, err := buffer.NewBufferFromFile(a, buffer.BTDefault)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
h.VSplitBuf(buf)
|
||||
h.VSplitBuf(buf)
|
||||
}
|
||||
}
|
||||
|
||||
// HSplitCmd opens a horizontal split with file given in the first argument
|
||||
// HSplitCmd opens one or more horizontal splits with the files given as arguments
|
||||
// If no file is given, it opens an empty buffer in a new split
|
||||
func (h *BufPane) HSplitCmd(args []string) {
|
||||
if len(args) == 0 {
|
||||
@@ -490,13 +519,15 @@ func (h *BufPane) HSplitCmd(args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
for _, a := range args {
|
||||
buf, err := buffer.NewBufferFromFile(a, buffer.BTDefault)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
h.HSplitBuf(buf)
|
||||
h.HSplitBuf(buf)
|
||||
}
|
||||
}
|
||||
|
||||
// EvalCmd evaluates a lua expression
|
||||
@@ -504,7 +535,8 @@ func (h *BufPane) EvalCmd(args []string) {
|
||||
InfoBar.Error("Eval unsupported")
|
||||
}
|
||||
|
||||
// NewTabCmd opens the given file in a new tab
|
||||
// NewTabCmd opens one or more tabs with the files given as arguments
|
||||
// If no file is given, it opens an empty buffer in a new tab
|
||||
func (h *BufPane) NewTabCmd(args []string) {
|
||||
width, height := screen.Screen.Size()
|
||||
iOffset := config.GetInfoBarOffset()
|
||||
@@ -527,7 +559,7 @@ func (h *BufPane) NewTabCmd(args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func doSetGlobalOptionNative(option string, nativeValue interface{}) error {
|
||||
func doSetGlobalOptionNative(option string, nativeValue any) error {
|
||||
if reflect.DeepEqual(config.GlobalSettings[option], nativeValue) {
|
||||
return nil
|
||||
}
|
||||
@@ -586,7 +618,7 @@ func doSetGlobalOptionNative(option string, nativeValue interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetGlobalOptionNative(option string, nativeValue interface{}) error {
|
||||
func SetGlobalOptionNative(option string, nativeValue any, writeToFile bool) error {
|
||||
if err := config.OptionIsValid(option, nativeValue); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -609,20 +641,41 @@ func SetGlobalOptionNative(option string, nativeValue interface{}) error {
|
||||
delete(b.LocalSettings, option)
|
||||
}
|
||||
|
||||
return config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
|
||||
if !writeToFile {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrOverwrite) {
|
||||
screen.TermMessage(err)
|
||||
err = errors.Unwrap(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetGlobalOption(option, value string) error {
|
||||
func SetGlobalOption(option, value string, writeToFile bool) error {
|
||||
if _, ok := config.GlobalSettings[option]; !ok {
|
||||
return config.ErrInvalidOption
|
||||
}
|
||||
|
||||
nativeValue, err := config.GetNativeValue(option, config.GlobalSettings[option], value)
|
||||
nativeValue, err := config.GetNativeValue(option, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return SetGlobalOptionNative(option, nativeValue)
|
||||
return SetGlobalOptionNative(option, nativeValue, writeToFile)
|
||||
}
|
||||
|
||||
func SetGlobalOptionNativePlug(option string, nativeValue any) error {
|
||||
return SetGlobalOptionNative(option, nativeValue, false)
|
||||
}
|
||||
|
||||
func SetGlobalOptionPlug(option, value string) error {
|
||||
return SetGlobalOption(option, value, false)
|
||||
}
|
||||
|
||||
// ResetCmd resets a setting to its default value
|
||||
@@ -636,7 +689,7 @@ func (h *BufPane) ResetCmd(args []string) {
|
||||
defaults := config.DefaultAllSettings()
|
||||
|
||||
if _, ok := defaults[option]; ok {
|
||||
SetGlobalOptionNative(option, defaults[option])
|
||||
SetGlobalOptionNative(option, defaults[option], true)
|
||||
return
|
||||
}
|
||||
InfoBar.Error(config.ErrInvalidOption)
|
||||
@@ -652,7 +705,7 @@ func (h *BufPane) SetCmd(args []string) {
|
||||
option := args[0]
|
||||
value := args[1]
|
||||
|
||||
err := SetGlobalOption(option, value)
|
||||
err := SetGlobalOption(option, value, true)
|
||||
if err == config.ErrInvalidOption {
|
||||
err := h.Buf.SetOption(option, value)
|
||||
if err != nil {
|
||||
@@ -679,6 +732,65 @@ func (h *BufPane) SetLocalCmd(args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BufPane) toggleOption(option string, local bool) error {
|
||||
var curVal, newVal any
|
||||
|
||||
if local {
|
||||
curVal = h.Buf.Settings[option]
|
||||
} else {
|
||||
curVal = config.GetGlobalOption(option)
|
||||
}
|
||||
if curVal == nil {
|
||||
return config.ErrInvalidOption
|
||||
}
|
||||
|
||||
if choices, ok := config.OptionChoices[option]; ok && len(choices) == 2 {
|
||||
if curVal == choices[0] {
|
||||
newVal = choices[1]
|
||||
} else {
|
||||
newVal = choices[0]
|
||||
}
|
||||
} else if curValBool, ok := curVal.(bool); ok {
|
||||
newVal = !curValBool
|
||||
} else {
|
||||
return config.ErrOptNotToggleable
|
||||
}
|
||||
|
||||
if local {
|
||||
if err := h.Buf.SetOptionNative(option, newVal); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := SetGlobalOptionNative(option, newVal, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToggleCmd toggles a toggleable option
|
||||
func (h *BufPane) ToggleCmd(args []string) {
|
||||
if len(args) < 1 {
|
||||
InfoBar.Error("Not enough arguments: provide a toggleable option")
|
||||
return
|
||||
}
|
||||
if err := h.toggleOption(args[0], false); err != nil {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// ToggleCmd toggles a toggleable option local to the buffer
|
||||
func (h *BufPane) ToggleLocalCmd(args []string) {
|
||||
if len(args) < 1 {
|
||||
InfoBar.Error("Not enough arguments: provide a toggleable option")
|
||||
return
|
||||
}
|
||||
if err := h.toggleOption(args[0], true); err != nil {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// ShowCmd shows the value of the given option
|
||||
func (h *BufPane) ShowCmd(args []string) {
|
||||
if len(args) < 1 {
|
||||
@@ -686,7 +798,7 @@ func (h *BufPane) ShowCmd(args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
var option interface{}
|
||||
var option any
|
||||
if opt, ok := h.Buf.Settings[args[0]]; ok {
|
||||
option = opt
|
||||
} else if opt, ok := config.GlobalSettings[args[0]]; ok {
|
||||
@@ -732,9 +844,13 @@ func (h *BufPane) BindCmd(args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err := TryBindKey(parseKeyArg(args[0]), args[1], true)
|
||||
_, err := TryBindKey(parseKeyArg(args[0]), args[1], true, true)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
if errors.Is(err, util.ErrOverwrite) {
|
||||
screen.TermMessage(err)
|
||||
} else {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -747,7 +863,11 @@ func (h *BufPane) UnbindCmd(args []string) {
|
||||
|
||||
err := UnbindKey(parseKeyArg(args[0]))
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
if errors.Is(err, util.ErrOverwrite) {
|
||||
screen.TermMessage(err)
|
||||
} else {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -841,7 +961,7 @@ func (h *BufPane) SaveCmd(args []string) {
|
||||
if len(args) == 0 {
|
||||
h.Save()
|
||||
} else {
|
||||
h.Buf.SaveAs(args[0])
|
||||
h.saveBufToFile(args[0], "SaveAs", nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -902,10 +1022,12 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
||||
nreplaced := 0
|
||||
start := h.Buf.Start()
|
||||
end := h.Buf.End()
|
||||
searchLoc := h.Cursor.Loc
|
||||
selection := h.Cursor.HasSelection()
|
||||
if selection {
|
||||
start = h.Cursor.CurSelection[0]
|
||||
end = h.Cursor.CurSelection[1]
|
||||
searchLoc = start // otherwise me might start at the end
|
||||
}
|
||||
if all {
|
||||
nreplaced, _ = h.Buf.ReplaceRegex(start, end, regex, replace, !noRegex)
|
||||
@@ -914,7 +1036,7 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
||||
return l.GreaterEqual(start) && l.LessEqual(end)
|
||||
}
|
||||
|
||||
searchLoc := h.Cursor.Loc
|
||||
lastMatchEnd := buffer.Loc{-1, -1}
|
||||
var doReplacement func()
|
||||
doReplacement = func() {
|
||||
locs, found, err := h.Buf.FindNext(search, start, end, searchLoc, true, true)
|
||||
@@ -929,6 +1051,18 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
if lastMatchEnd == locs[1] {
|
||||
// skip empty match right after previous match
|
||||
if searchLoc == end {
|
||||
searchLoc = start
|
||||
lastMatchEnd = buffer.Loc{-1, -1}
|
||||
} else {
|
||||
searchLoc = searchLoc.Move(1, h.Buf)
|
||||
}
|
||||
doReplacement()
|
||||
return
|
||||
}
|
||||
|
||||
h.Cursor.SetSelectionStart(locs[0])
|
||||
h.Cursor.SetSelectionEnd(locs[1])
|
||||
h.GotoLoc(locs[0])
|
||||
@@ -954,6 +1088,7 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
||||
h.Buf.RelocateCursors()
|
||||
return
|
||||
}
|
||||
lastMatchEnd = searchLoc
|
||||
doReplacement()
|
||||
})
|
||||
}
|
||||
@@ -985,10 +1120,42 @@ func (h *BufPane) ReplaceAllCmd(args []string) {
|
||||
h.ReplaceCmd(append(args, "-a"))
|
||||
}
|
||||
|
||||
func (h *BufPane) openTerm(args []string, newtab bool) {
|
||||
t := new(shell.Terminal)
|
||||
err := t.Start(args, false, true, nil, nil)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
pane := 0
|
||||
id := h.ID()
|
||||
if newtab {
|
||||
h.AddTab()
|
||||
id = MainTab().Panes[pane].ID()
|
||||
} else {
|
||||
for i, p := range MainTab().Panes {
|
||||
if p.IsActive() {
|
||||
pane = i
|
||||
id = p.ID()
|
||||
p.Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v := h.GetView()
|
||||
tp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
MainTab().Panes[pane] = tp
|
||||
MainTab().SetActive(pane)
|
||||
}
|
||||
|
||||
// TermCmd opens a terminal in the current view
|
||||
func (h *BufPane) TermCmd(args []string) {
|
||||
ps := h.tab.Panes
|
||||
|
||||
if !TermEmuSupported {
|
||||
InfoBar.Error("Terminal emulator not supported on this system")
|
||||
return
|
||||
@@ -1003,56 +1170,19 @@ func (h *BufPane) TermCmd(args []string) {
|
||||
args = []string{sh}
|
||||
}
|
||||
|
||||
term := func(i int, newtab bool) {
|
||||
t := new(shell.Terminal)
|
||||
err := t.Start(args, false, true, nil, nil)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
id := h.ID()
|
||||
if newtab {
|
||||
h.AddTab()
|
||||
i = 0
|
||||
id = MainTab().Panes[0].ID()
|
||||
} else {
|
||||
MainTab().Panes[i].Close()
|
||||
}
|
||||
|
||||
v := h.GetView()
|
||||
tp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
MainTab().Panes[i] = tp
|
||||
MainTab().SetActive(i)
|
||||
}
|
||||
|
||||
// If there is only one open file we make a new tab instead of overwriting it
|
||||
newtab := len(MainTab().Panes) == 1 && len(Tabs.List) == 1
|
||||
|
||||
if newtab {
|
||||
term(0, true)
|
||||
h.openTerm(args, true)
|
||||
return
|
||||
}
|
||||
|
||||
for i, p := range ps {
|
||||
if p.ID() == h.ID() {
|
||||
if h.Buf.Modified() {
|
||||
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
|
||||
if !canceled && !yes {
|
||||
term(i, false)
|
||||
} else if !canceled && yes {
|
||||
h.Save()
|
||||
term(i, false)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
term(i, false)
|
||||
}
|
||||
}
|
||||
if h.Buf.Modified() && !h.Buf.Shared() {
|
||||
h.closePrompt("Save", func() {
|
||||
h.openTerm(args, false)
|
||||
})
|
||||
} else {
|
||||
h.openTerm(args, false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package action
|
||||
var termdefaults = map[string]string{
|
||||
"<Ctrl-q><Ctrl-q>": "Exit",
|
||||
"<Ctrl-e><Ctrl-e>": "CommandMode",
|
||||
"<Ctrl-w><Ctrl-w>": "NextSplit",
|
||||
"<Ctrl-w><Ctrl-w>": "NextSplit|FirstSplit",
|
||||
}
|
||||
|
||||
// DefaultBindings returns a map containing micro's default keybindings
|
||||
|
||||
@@ -45,23 +45,23 @@ var bufdefaults = map[string]string{
|
||||
"Alt-]": "DiffNext|CursorEnd",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-c": "Copy|CopyLine",
|
||||
"Ctrl-x": "Cut|CutLine",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-d": "DuplicateLine",
|
||||
"Ctrl-d": "Duplicate|DuplicateLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Ctrl-a": "SelectAll",
|
||||
"Ctrl-t": "AddTab",
|
||||
"Alt-,": "PreviousTab",
|
||||
"Alt-.": "NextTab",
|
||||
"Alt-,": "PreviousTab|LastTab",
|
||||
"Alt-.": "NextTab|FirstTab",
|
||||
"Home": "StartOfTextToggle",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"CtrlPageUp": "PreviousTab|LastTab",
|
||||
"CtrlPageDown": "NextTab|FirstTab",
|
||||
"ShiftPageUp": "SelectPageUp",
|
||||
"ShiftPageDown": "SelectPageDown",
|
||||
"Ctrl-g": "ToggleHelp",
|
||||
@@ -72,7 +72,7 @@ var bufdefaults = map[string]string{
|
||||
"Ctrl-b": "ShellMode",
|
||||
"Ctrl-q": "Quit",
|
||||
"Ctrl-e": "CommandMode",
|
||||
"Ctrl-w": "NextSplit",
|
||||
"Ctrl-w": "NextSplit|FirstSplit",
|
||||
"Ctrl-u": "ToggleMacro",
|
||||
"Ctrl-j": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
@@ -146,7 +146,7 @@ var infodefaults = map[string]string{
|
||||
"Backtab": "CycleAutocompleteBack",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-c": "Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-v": "Paste",
|
||||
|
||||
@@ -48,23 +48,23 @@ var bufdefaults = map[string]string{
|
||||
"Alt-]": "DiffNext|CursorEnd",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-c": "Copy|CopyLine",
|
||||
"Ctrl-x": "Cut|CutLine",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-d": "DuplicateLine",
|
||||
"Ctrl-d": "Duplicate|DuplicateLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Ctrl-a": "SelectAll",
|
||||
"Ctrl-t": "AddTab",
|
||||
"Alt-,": "PreviousTab",
|
||||
"Alt-.": "NextTab",
|
||||
"Alt-,": "PreviousTab|LastTab",
|
||||
"Alt-.": "NextTab|FirstTab",
|
||||
"Home": "StartOfTextToggle",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"CtrlPageUp": "PreviousTab|LastTab",
|
||||
"CtrlPageDown": "NextTab|FirstTab",
|
||||
"ShiftPageUp": "SelectPageUp",
|
||||
"ShiftPageDown": "SelectPageDown",
|
||||
"Ctrl-g": "ToggleHelp",
|
||||
@@ -75,7 +75,7 @@ var bufdefaults = map[string]string{
|
||||
"Ctrl-b": "ShellMode",
|
||||
"Ctrl-q": "Quit",
|
||||
"Ctrl-e": "CommandMode",
|
||||
"Ctrl-w": "NextSplit",
|
||||
"Ctrl-w": "NextSplit|FirstSplit",
|
||||
"Ctrl-u": "ToggleMacro",
|
||||
"Ctrl-j": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
@@ -149,7 +149,7 @@ var infodefaults = map[string]string{
|
||||
"Backtab": "CycleAutocompleteBack",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-c": "Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-v": "Paste",
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
)
|
||||
|
||||
type Event interface {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package action
|
||||
|
||||
import "github.com/zyedidia/micro/v2/internal/buffer"
|
||||
import "github.com/micro-editor/micro/v2/internal/buffer"
|
||||
|
||||
// InfoBar is the global info bar.
|
||||
var InfoBar *InfoPane
|
||||
@@ -11,7 +11,8 @@ var LogBufPane *BufPane
|
||||
// InitGlobals initializes the log buffer and the info bar
|
||||
func InitGlobals() {
|
||||
InfoBar = NewInfoBar()
|
||||
buffer.LogBuf = buffer.NewBufferFromString("", "Log", buffer.BTLog)
|
||||
buffer.LogBuf = buffer.NewBufferFromString("", "", buffer.BTLog)
|
||||
buffer.LogBuf.SetName("Log")
|
||||
}
|
||||
|
||||
// GetInfoBar returns the infobar pane
|
||||
|
||||
@@ -5,10 +5,10 @@ import (
|
||||
"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"
|
||||
"github.com/micro-editor/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
"github.com/micro-editor/micro/v2/pkg/highlight"
|
||||
)
|
||||
|
||||
// This file is meant (for now) for autocompletion in command mode, not
|
||||
@@ -135,15 +135,6 @@ headerLoop:
|
||||
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()
|
||||
@@ -202,7 +193,7 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
inputOpt = strings.TrimSpace(inputOpt)
|
||||
var suggestions []string
|
||||
// localSettings := config.DefaultLocalSettings()
|
||||
var optionVal interface{}
|
||||
var optionVal any
|
||||
for k, option := range config.GlobalSettings {
|
||||
if k == inputOpt {
|
||||
optionVal = option
|
||||
|
||||
@@ -3,12 +3,12 @@ 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"
|
||||
"github.com/micro-editor/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/display"
|
||||
"github.com/micro-editor/micro/v2/internal/info"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
)
|
||||
|
||||
type InfoKeyAction func(*InfoPane)
|
||||
|
||||
@@ -3,7 +3,7 @@ package action
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
)
|
||||
|
||||
type PaneKeyAction func(Pane) bool
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
"github.com/micro-editor/micro/v2/internal/display"
|
||||
)
|
||||
|
||||
// A Pane is a general interface for a window in the editor.
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/micro-editor/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/display"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
)
|
||||
|
||||
type RawPane struct {
|
||||
@@ -22,7 +22,10 @@ func NewRawPaneFromWin(b *buffer.Buffer, win display.BWindow, tab *Tab) *RawPane
|
||||
|
||||
func NewRawPane(tab *Tab) *RawPane {
|
||||
b := buffer.NewBufferFromString("", "", buffer.BTRaw)
|
||||
b.SetName("Raw event viewer")
|
||||
|
||||
w := display.NewBufWindow(0, 0, 0, 0, b)
|
||||
|
||||
return NewRawPaneFromWin(b, w, tab)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ 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"
|
||||
"github.com/micro-editor/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/display"
|
||||
ulua "github.com/micro-editor/micro/v2/internal/lua"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/views"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
)
|
||||
|
||||
// The TabList is a list of tabs and a window to display the tab bar
|
||||
@@ -211,7 +211,7 @@ func InitTabs(bufs []*buffer.Buffer) {
|
||||
for _, b := range bufs[1:] {
|
||||
if multiopen == "vsplit" {
|
||||
MainTab().CurPane().VSplitBuf(b)
|
||||
} else { // default hsplit
|
||||
} else { // default hsplit
|
||||
MainTab().CurPane().HSplitBuf(b)
|
||||
}
|
||||
}
|
||||
@@ -349,6 +349,16 @@ func (t *Tab) SetActive(i int) {
|
||||
}
|
||||
}
|
||||
|
||||
// AddPane adds a pane at a given index
|
||||
func (t *Tab) AddPane(pane Pane, i int) {
|
||||
if len(t.Panes) == i {
|
||||
t.Panes = append(t.Panes, pane)
|
||||
return
|
||||
}
|
||||
t.Panes = append(t.Panes[:i+1], t.Panes[i:]...)
|
||||
t.Panes[i] = pane
|
||||
}
|
||||
|
||||
// GetPane returns the pane with the given split index
|
||||
func (t *Tab) GetPane(splitid uint64) int {
|
||||
for i, p := range t.Panes {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// +build linux darwin dragonfly openbsd_amd64 freebsd
|
||||
//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd
|
||||
|
||||
package action
|
||||
|
||||
import (
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/micro-editor/micro/v2/internal/shell"
|
||||
)
|
||||
|
||||
// TermEmuSupported is a constant that marks if the terminal emulator is supported
|
||||
@@ -14,7 +14,7 @@ const TermEmuSupported = true
|
||||
// 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 {
|
||||
func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callback func(out string, userargs []any), userargs []any) error {
|
||||
args, err := shellquote.Split(input)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd_amd64
|
||||
//go:build plan9 || nacl || windows
|
||||
|
||||
package action
|
||||
|
||||
@@ -8,6 +8,6 @@ import "errors"
|
||||
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 {
|
||||
func RunTermEmulator(input string, wait bool, getOutput bool, callback func(out string, userargs []any), userargs []any) error {
|
||||
return errors.New("Unsupported operating system")
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ import (
|
||||
"errors"
|
||||
"runtime"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/clipboard"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/zyedidia/terminal"
|
||||
"github.com/micro-editor/micro/v2/internal/clipboard"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/display"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/shell"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
"github.com/micro-editor/terminal"
|
||||
)
|
||||
|
||||
type TermKeyAction func(*TermPane)
|
||||
|
||||
@@ -2,12 +2,12 @@ package buffer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"io/fs"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// A Completer is a function that takes a buffer and returns info
|
||||
@@ -109,15 +109,15 @@ func FileComplete(b *Buffer) ([]string, []string) {
|
||||
sep := string(os.PathSeparator)
|
||||
dirs := strings.Split(input, sep)
|
||||
|
||||
var files []os.FileInfo
|
||||
var files []fs.DirEntry
|
||||
var err error
|
||||
if len(dirs) > 1 {
|
||||
directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
|
||||
|
||||
directories, _ = util.ReplaceHome(directories)
|
||||
files, err = ioutil.ReadDir(directories)
|
||||
files, err = os.ReadDir(directories)
|
||||
} else {
|
||||
files, err = ioutil.ReadDir(".")
|
||||
files, err = os.ReadDir(".")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"golang.org/x/text/encoding"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
const backupMsg = `A backup was detected for this file. This likely means that micro
|
||||
crashed while editing this file, or another instance of micro is currently
|
||||
editing this file.
|
||||
const BackupMsg = `A backup was detected for:
|
||||
|
||||
The backup was created on %s, and the file is
|
||||
%s
|
||||
|
||||
This likely means that micro crashed while editing this file,
|
||||
or another instance of micro is currently editing this file,
|
||||
or an error occurred while saving this file so it may be corrupted.
|
||||
|
||||
The backup was created on %s and its path is:
|
||||
|
||||
%s
|
||||
|
||||
@@ -30,114 +32,148 @@ The backup was created on %s, and the file is
|
||||
|
||||
Options: [r]ecover, [i]gnore, [a]bort: `
|
||||
|
||||
var backupRequestChan chan *Buffer
|
||||
const backupSeconds = 8
|
||||
|
||||
func backupThread() {
|
||||
for {
|
||||
time.Sleep(time.Second * 8)
|
||||
type backupRequestType int
|
||||
|
||||
for len(backupRequestChan) > 0 {
|
||||
b := <-backupRequestChan
|
||||
bfini := atomic.LoadInt32(&(b.fini)) != 0
|
||||
if !bfini {
|
||||
b.Backup()
|
||||
}
|
||||
}
|
||||
}
|
||||
const (
|
||||
backupCreate = iota
|
||||
backupRemove
|
||||
)
|
||||
|
||||
type backupRequest struct {
|
||||
buf *SharedBuffer
|
||||
reqType backupRequestType
|
||||
}
|
||||
|
||||
var requestedBackups map[*SharedBuffer]bool
|
||||
|
||||
func init() {
|
||||
backupRequestChan = make(chan *Buffer, 10)
|
||||
|
||||
go backupThread()
|
||||
requestedBackups = make(map[*SharedBuffer]bool)
|
||||
}
|
||||
|
||||
func (b *Buffer) RequestBackup() {
|
||||
if !b.requestedBackup {
|
||||
select {
|
||||
case backupRequestChan <- b:
|
||||
default:
|
||||
// channel is full
|
||||
func (b *SharedBuffer) RequestBackup() {
|
||||
backupRequestChan <- backupRequest{buf: b, reqType: backupCreate}
|
||||
}
|
||||
|
||||
func (b *SharedBuffer) CancelBackup() {
|
||||
backupRequestChan <- backupRequest{buf: b, reqType: backupRemove}
|
||||
}
|
||||
|
||||
func handleBackupRequest(br backupRequest) {
|
||||
switch br.reqType {
|
||||
case backupCreate:
|
||||
// schedule periodic backup
|
||||
requestedBackups[br.buf] = true
|
||||
case backupRemove:
|
||||
br.buf.RemoveBackup()
|
||||
delete(requestedBackups, br.buf)
|
||||
}
|
||||
}
|
||||
|
||||
func periodicBackup() {
|
||||
for buf := range requestedBackups {
|
||||
err := buf.Backup()
|
||||
if err == nil {
|
||||
delete(requestedBackups, buf)
|
||||
}
|
||||
b.requestedBackup = true
|
||||
}
|
||||
}
|
||||
|
||||
// Backup saves the current buffer to ConfigDir/backups
|
||||
func (b *Buffer) Backup() error {
|
||||
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *SharedBuffer) backupDir() string {
|
||||
backupdir, err := util.ReplaceHome(b.Settings["backupdir"].(string))
|
||||
if backupdir == "" || err != nil {
|
||||
backupdir = filepath.Join(config.ConfigDir, "backups")
|
||||
}
|
||||
if _, err := os.Stat(backupdir); os.IsNotExist(err) {
|
||||
os.Mkdir(backupdir, os.ModePerm)
|
||||
return backupdir
|
||||
}
|
||||
|
||||
func (b *SharedBuffer) keepBackup() bool {
|
||||
return b.forceKeepBackup || b.Settings["permbackup"].(bool)
|
||||
}
|
||||
|
||||
func (b *SharedBuffer) writeBackup(path string) (string, string, error) {
|
||||
backupdir := b.backupDir()
|
||||
if _, err := os.Stat(backupdir); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return "", "", err
|
||||
}
|
||||
if err = os.Mkdir(backupdir, os.ModePerm); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
}
|
||||
|
||||
name := filepath.Join(backupdir, util.EscapePath(b.AbsPath))
|
||||
name, resolveName := util.DetermineEscapePath(backupdir, path)
|
||||
tmp := name + util.BackupSuffix
|
||||
|
||||
err = overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
|
||||
if len(b.lines) == 0 {
|
||||
return
|
||||
_, err := b.overwriteFile(tmp)
|
||||
if err != nil {
|
||||
os.Remove(tmp)
|
||||
return name, resolveName, err
|
||||
}
|
||||
err = os.Rename(tmp, name)
|
||||
if err != nil {
|
||||
os.Remove(tmp)
|
||||
return name, resolveName, err
|
||||
}
|
||||
|
||||
if resolveName != "" {
|
||||
err = util.SafeWrite(resolveName, []byte(path), true)
|
||||
if err != nil {
|
||||
return name, resolveName, err
|
||||
}
|
||||
}
|
||||
|
||||
// end of line
|
||||
eol := []byte{'\n'}
|
||||
return name, resolveName, nil
|
||||
}
|
||||
|
||||
// write lines
|
||||
if _, e = file.Write(b.lines[0].data); e != nil {
|
||||
return
|
||||
}
|
||||
func (b *SharedBuffer) removeBackup(path string, resolveName string) {
|
||||
os.Remove(path)
|
||||
if resolveName != "" {
|
||||
os.Remove(resolveName)
|
||||
}
|
||||
}
|
||||
|
||||
for _, l := range b.lines[1:] {
|
||||
if _, e = file.Write(eol); e != nil {
|
||||
return
|
||||
}
|
||||
if _, e = file.Write(l.data); e != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}, false)
|
||||
|
||||
b.requestedBackup = false
|
||||
// Backup saves the buffer to the backups directory
|
||||
func (b *SharedBuffer) Backup() error {
|
||||
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, _, err := b.writeBackup(b.AbsPath)
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveBackup removes any backup file associated with this buffer
|
||||
func (b *Buffer) RemoveBackup() {
|
||||
if !b.Settings["backup"].(bool) || b.Settings["permbackup"].(bool) || b.Path == "" || b.Type != BTDefault {
|
||||
func (b *SharedBuffer) RemoveBackup() {
|
||||
if b.keepBackup() || b.Path == "" || b.Type != BTDefault {
|
||||
return
|
||||
}
|
||||
f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
|
||||
os.Remove(f)
|
||||
f, resolveName := util.DetermineEscapePath(b.backupDir(), b.AbsPath)
|
||||
b.removeBackup(f, resolveName)
|
||||
}
|
||||
|
||||
// ApplyBackup applies the corresponding backup file to this buffer (if one exists)
|
||||
// Returns true if a backup was applied
|
||||
func (b *Buffer) ApplyBackup(fsize int64) (bool, bool) {
|
||||
func (b *SharedBuffer) ApplyBackup(fsize int64) (bool, bool) {
|
||||
if b.Settings["backup"].(bool) && !b.Settings["permbackup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
|
||||
backupfile := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
|
||||
backupfile, resolveName := util.DetermineEscapePath(b.backupDir(), b.AbsPath)
|
||||
if info, err := os.Stat(backupfile); err == nil {
|
||||
backup, err := os.Open(backupfile)
|
||||
if err == nil {
|
||||
defer backup.Close()
|
||||
t := info.ModTime()
|
||||
msg := fmt.Sprintf(backupMsg, t.Format("Mon Jan _2 at 15:04, 2006"), util.EscapePath(b.AbsPath))
|
||||
msg := fmt.Sprintf(BackupMsg, b.Path, t.Format("Mon Jan _2 at 15:04, 2006"), backupfile)
|
||||
choice := screen.TermPrompt(msg, []string{"r", "i", "a", "recover", "ignore", "abort"}, true)
|
||||
|
||||
if choice%3 == 0 {
|
||||
// recover
|
||||
b.LineArray = NewLineArray(uint64(fsize), FFAuto, backup)
|
||||
b.isModified = true
|
||||
b.setModified()
|
||||
return true, true
|
||||
} else if choice%3 == 1 {
|
||||
// delete
|
||||
os.Remove(backupfile)
|
||||
b.removeBackup(backupfile, resolveName)
|
||||
} else if choice%3 == 2 {
|
||||
return false, false
|
||||
}
|
||||
|
||||
@@ -7,31 +7,28 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
ulua "github.com/micro-editor/micro/v2/internal/lua"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
"github.com/micro-editor/micro/v2/pkg/highlight"
|
||||
dmp "github.com/sergi/go-diff/diffmatchpatch"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/micro/v2/pkg/highlight"
|
||||
"golang.org/x/text/encoding"
|
||||
"golang.org/x/text/encoding/htmlindex"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
const backupTime = 8000
|
||||
|
||||
var (
|
||||
// OpenBuffers is a list of the currently open buffers
|
||||
OpenBuffers []*Buffer
|
||||
@@ -85,10 +82,12 @@ type SharedBuffer struct {
|
||||
toStdout bool
|
||||
|
||||
// Settings customized by the user
|
||||
Settings map[string]interface{}
|
||||
Settings map[string]any
|
||||
// LocalSettings customized by the user for this buffer only
|
||||
LocalSettings map[string]bool
|
||||
|
||||
encoding encoding.Encoding
|
||||
|
||||
Suggestions []string
|
||||
Completions []string
|
||||
CurSuggestion int
|
||||
@@ -101,7 +100,7 @@ type SharedBuffer struct {
|
||||
diffLock sync.RWMutex
|
||||
diff map[int]DiffStatus
|
||||
|
||||
requestedBackup bool
|
||||
forceKeepBackup bool
|
||||
|
||||
// ReloadDisabled allows the user to disable reloads if they
|
||||
// are viewing a file that is constantly changing
|
||||
@@ -125,20 +124,62 @@ type SharedBuffer struct {
|
||||
}
|
||||
|
||||
func (b *SharedBuffer) insert(pos Loc, value []byte) {
|
||||
b.isModified = true
|
||||
b.HasSuggestions = false
|
||||
b.LineArray.insert(pos, value)
|
||||
b.setModified()
|
||||
|
||||
inslines := bytes.Count(value, []byte{'\n'})
|
||||
b.MarkModified(pos.Y, pos.Y+inslines)
|
||||
}
|
||||
|
||||
func (b *SharedBuffer) remove(start, end Loc) []byte {
|
||||
b.isModified = true
|
||||
b.HasSuggestions = false
|
||||
defer b.setModified()
|
||||
defer b.MarkModified(start.Y, end.Y)
|
||||
return b.LineArray.remove(start, end)
|
||||
}
|
||||
|
||||
func (b *SharedBuffer) setModified() {
|
||||
if b.Type.Scratch {
|
||||
return
|
||||
}
|
||||
|
||||
if b.Settings["fastdirty"].(bool) {
|
||||
b.isModified = true
|
||||
} else {
|
||||
var buff [md5.Size]byte
|
||||
|
||||
b.calcHash(&buff)
|
||||
b.isModified = buff != b.origHash
|
||||
}
|
||||
|
||||
if b.isModified {
|
||||
b.RequestBackup()
|
||||
} else {
|
||||
b.CancelBackup()
|
||||
}
|
||||
}
|
||||
|
||||
// calcHash calculates md5 hash of all lines in the buffer
|
||||
func (b *SharedBuffer) calcHash(out *[md5.Size]byte) {
|
||||
h := md5.New()
|
||||
|
||||
if len(b.lines) > 0 {
|
||||
h.Write(b.lines[0].data())
|
||||
|
||||
for _, l := range b.lines[1:] {
|
||||
if b.Endings == FFDos {
|
||||
h.Write([]byte{'\r', '\n'})
|
||||
} else {
|
||||
h.Write([]byte{'\n'})
|
||||
}
|
||||
h.Write(l.data())
|
||||
}
|
||||
}
|
||||
|
||||
h.Sum((*out)[:0])
|
||||
}
|
||||
|
||||
// MarkModified marks the buffer as modified for this frame
|
||||
// and performs rehighlighting if syntax highlighting is enabled
|
||||
func (b *SharedBuffer) MarkModified(start, end int) {
|
||||
@@ -174,6 +215,18 @@ const (
|
||||
|
||||
type DiffStatus byte
|
||||
|
||||
type Command struct {
|
||||
StartCursor Loc
|
||||
SearchRegex string
|
||||
SearchAfterStart bool
|
||||
}
|
||||
|
||||
var emptyCommand = Command{
|
||||
StartCursor: Loc{-1, -1},
|
||||
SearchRegex: "",
|
||||
SearchAfterStart: false,
|
||||
}
|
||||
|
||||
// Buffer stores the main information about a currently open file including
|
||||
// the actual text (in a LineArray), the undo/redo stack (in an EventHandler)
|
||||
// all the cursors, the syntax highlighting info, the settings for the buffer
|
||||
@@ -186,7 +239,6 @@ type Buffer struct {
|
||||
*EventHandler
|
||||
*SharedBuffer
|
||||
|
||||
fini int32
|
||||
cursors []*Cursor
|
||||
curCursor int
|
||||
StartCursor Loc
|
||||
@@ -196,7 +248,7 @@ type Buffer struct {
|
||||
// is properly updated when needed. This is a workaround for the fact that
|
||||
// the buffer module cannot directly call the display's API (it would mean
|
||||
// a circular dependency between packages).
|
||||
OptionCallback func(option string, nativeValue interface{})
|
||||
OptionCallback func(option string, nativeValue any)
|
||||
|
||||
// The display module registers its own GetVisualX function for getting
|
||||
// the correct visual x location of a cursor when softwrap is used.
|
||||
@@ -209,21 +261,26 @@ type Buffer struct {
|
||||
LastSearchRegex bool
|
||||
// HighlightSearch enables highlighting all instances of the last successful search
|
||||
HighlightSearch bool
|
||||
|
||||
// OverwriteMode indicates that we are in overwrite mode (toggled by
|
||||
// Insert key by default) i.e. that typing a character shall replace the
|
||||
// character under the cursor instead of inserting a character before it.
|
||||
OverwriteMode bool
|
||||
}
|
||||
|
||||
// NewBufferFromFileAtLoc opens a new buffer with a given cursor location
|
||||
// If cursorLoc is {-1, -1} the location does not overwrite what the cursor location
|
||||
// NewBufferFromFileWithCommand opens a new buffer with a given command
|
||||
// If cmd.StartCursor is {-1, -1} the location does not overwrite what the cursor location
|
||||
// would otherwise be (start of file, or saved cursor position if `savecursor` is
|
||||
// enabled)
|
||||
func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer, error) {
|
||||
func NewBufferFromFileWithCommand(path string, btype BufType, cmd Command) (*Buffer, error) {
|
||||
var err error
|
||||
filename := path
|
||||
if config.GetGlobalOption("parsecursor").(bool) && cursorLoc.X == -1 && cursorLoc.Y == -1 {
|
||||
if config.GetGlobalOption("parsecursor").(bool) && cmd.StartCursor.X == -1 && cmd.StartCursor.Y == -1 {
|
||||
var cursorPos []string
|
||||
filename, cursorPos = util.GetPathAndCursorPosition(filename)
|
||||
cursorLoc, err = ParseCursorLocation(cursorPos)
|
||||
cmd.StartCursor, err = ParseCursorLocation(cursorPos)
|
||||
if err != nil {
|
||||
cursorLoc = Loc{-1, -1}
|
||||
cmd.StartCursor = Loc{-1, -1}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,17 +289,20 @@ func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(filename, os.O_WRONLY, 0)
|
||||
readonly := os.IsPermission(err)
|
||||
f.Close()
|
||||
|
||||
fileInfo, serr := os.Stat(filename)
|
||||
if serr != nil && !os.IsNotExist(serr) {
|
||||
if serr != nil && !errors.Is(serr, fs.ErrNotExist) {
|
||||
return nil, serr
|
||||
}
|
||||
if serr == nil && fileInfo.IsDir() {
|
||||
return nil, errors.New("Error: " + filename + " is a directory and cannot be opened")
|
||||
}
|
||||
if serr == nil && !fileInfo.Mode().IsRegular() {
|
||||
return nil, errors.New("Error: " + filename + " is not a regular file and cannot be opened")
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(filename, os.O_WRONLY, 0)
|
||||
readonly := errors.Is(err, fs.ErrPermission)
|
||||
f.Close()
|
||||
|
||||
file, err := os.Open(filename)
|
||||
if err == nil {
|
||||
@@ -250,13 +310,13 @@ func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer,
|
||||
}
|
||||
|
||||
var buf *Buffer
|
||||
if os.IsNotExist(err) {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
// File does not exist -- create an empty buffer with that name
|
||||
buf = NewBufferFromString("", filename, btype)
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
buf = NewBuffer(file, util.FSize(file), filename, cursorLoc, btype)
|
||||
buf = NewBuffer(file, util.FSize(file), filename, btype, cmd)
|
||||
if buf == nil {
|
||||
return nil, errors.New("could not open file")
|
||||
}
|
||||
@@ -275,17 +335,18 @@ func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer,
|
||||
// It will return an empty buffer if the path does not exist
|
||||
// and an error if the file is a directory
|
||||
func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
|
||||
return NewBufferFromFileAtLoc(path, btype, Loc{-1, -1})
|
||||
return NewBufferFromFileWithCommand(path, btype, emptyCommand)
|
||||
}
|
||||
|
||||
// NewBufferFromStringAtLoc creates a new buffer containing the given string with a cursor loc
|
||||
func NewBufferFromStringAtLoc(text, path string, btype BufType, cursorLoc Loc) *Buffer {
|
||||
return NewBuffer(strings.NewReader(text), int64(len(text)), path, cursorLoc, btype)
|
||||
// NewBufferFromStringWithCommand creates a new buffer containing the given string
|
||||
// with a cursor loc and a search text
|
||||
func NewBufferFromStringWithCommand(text, path string, btype BufType, cmd Command) *Buffer {
|
||||
return NewBuffer(strings.NewReader(text), int64(len(text)), path, btype, cmd)
|
||||
}
|
||||
|
||||
// NewBufferFromString creates a new buffer containing the given string
|
||||
func NewBufferFromString(text, path string, btype BufType) *Buffer {
|
||||
return NewBuffer(strings.NewReader(text), int64(len(text)), path, Loc{-1, -1}, btype)
|
||||
return NewBuffer(strings.NewReader(text), int64(len(text)), path, btype, emptyCommand)
|
||||
}
|
||||
|
||||
// NewBuffer creates a new buffer from a given reader with a given path
|
||||
@@ -293,7 +354,7 @@ func NewBufferFromString(text, path string, btype BufType) *Buffer {
|
||||
// a new buffer
|
||||
// Places the cursor at startcursor. If startcursor is -1, -1 places the
|
||||
// cursor at an autodetected location (based on savecursor or :LINE:COL)
|
||||
func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufType) *Buffer {
|
||||
func NewBuffer(r io.Reader, size int64, path string, btype BufType, cmd Command) *Buffer {
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
absPath = path
|
||||
@@ -320,30 +381,19 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
b.AbsPath = absPath
|
||||
b.Path = path
|
||||
|
||||
// this is a little messy since we need to know some settings to read
|
||||
// the file properly, but some settings depend on the filetype, which
|
||||
// we don't know until reading the file. We first read the settings
|
||||
// into a local variable and then use that to determine the encoding,
|
||||
// readonly, and fileformat necessary for reading the file and
|
||||
// assigning the filetype.
|
||||
settings := config.DefaultCommonSettings()
|
||||
b.Settings = config.DefaultCommonSettings()
|
||||
b.LocalSettings = make(map[string]bool)
|
||||
for k, v := range config.GlobalSettings {
|
||||
if _, ok := config.DefaultGlobalOnlySettings[k]; !ok {
|
||||
// make sure setting is not global-only
|
||||
settings[k] = v
|
||||
b.Settings[k] = v
|
||||
}
|
||||
}
|
||||
config.InitLocalSettings(settings, absPath)
|
||||
b.Settings["readonly"] = settings["readonly"]
|
||||
b.Settings["filetype"] = settings["filetype"]
|
||||
b.Settings["syntax"] = settings["syntax"]
|
||||
config.UpdatePathGlobLocals(b.Settings, absPath)
|
||||
|
||||
enc, err := htmlindex.Get(settings["encoding"].(string))
|
||||
b.encoding, err = htmlindex.Get(b.Settings["encoding"].(string))
|
||||
if err != nil {
|
||||
enc = unicode.UTF8
|
||||
b.encoding = unicode.UTF8
|
||||
b.Settings["encoding"] = "utf-8"
|
||||
}
|
||||
|
||||
@@ -354,14 +404,14 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
return NewBufferFromString("", "", btype)
|
||||
}
|
||||
if !hasBackup {
|
||||
reader := bufio.NewReader(transform.NewReader(r, enc.NewDecoder()))
|
||||
reader := bufio.NewReader(transform.NewReader(r, b.encoding.NewDecoder()))
|
||||
|
||||
var ff FileFormat = FFAuto
|
||||
|
||||
if size == 0 {
|
||||
// for empty files, use the fileformat setting instead of
|
||||
// autodetection
|
||||
switch settings["fileformat"] {
|
||||
switch b.Settings["fileformat"] {
|
||||
case "unix":
|
||||
ff = FFUnix
|
||||
case "dos":
|
||||
@@ -392,15 +442,15 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
}
|
||||
|
||||
b.UpdateRules()
|
||||
// init local settings again now that we know the filetype
|
||||
config.InitLocalSettings(b.Settings, b.Path)
|
||||
// we know the filetype now, so update per-filetype settings
|
||||
config.UpdateFileTypeLocals(b.Settings, b.Settings["filetype"].(string))
|
||||
|
||||
if _, err := os.Stat(filepath.Join(config.ConfigDir, "buffers")); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(filepath.Join(config.ConfigDir, "buffers")); errors.Is(err, fs.ErrNotExist) {
|
||||
os.Mkdir(filepath.Join(config.ConfigDir, "buffers"), os.ModePerm)
|
||||
}
|
||||
|
||||
if startcursor.X != -1 && startcursor.Y != -1 {
|
||||
b.StartCursor = startcursor
|
||||
if cmd.StartCursor.X != -1 && cmd.StartCursor.Y != -1 {
|
||||
b.StartCursor = cmd.StartCursor
|
||||
} else if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
|
||||
err := b.Unserialize()
|
||||
if err != nil {
|
||||
@@ -411,6 +461,23 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
b.AddCursor(NewCursor(b, b.StartCursor))
|
||||
b.GetActiveCursor().Relocate()
|
||||
|
||||
if cmd.SearchRegex != "" {
|
||||
match, found, _ := b.FindNext(cmd.SearchRegex, b.Start(), b.End(), b.StartCursor, true, true)
|
||||
if found {
|
||||
if cmd.SearchAfterStart {
|
||||
// Search from current cursor and move it accordingly
|
||||
b.GetActiveCursor().SetSelectionStart(match[0])
|
||||
b.GetActiveCursor().SetSelectionEnd(match[1])
|
||||
b.GetActiveCursor().OrigSelection[0] = b.GetActiveCursor().CurSelection[0]
|
||||
b.GetActiveCursor().OrigSelection[1] = b.GetActiveCursor().CurSelection[1]
|
||||
b.GetActiveCursor().GotoLoc(match[1])
|
||||
}
|
||||
b.LastSearch = cmd.SearchRegex
|
||||
b.LastSearchRegex = true
|
||||
b.HighlightSearch = b.Settings["hlsearch"].(bool)
|
||||
}
|
||||
}
|
||||
|
||||
if !b.Settings["fastdirty"].(bool) && !found {
|
||||
if size > LargeFileThreshold {
|
||||
// If the file is larger than LargeFileThreshold fastdirty needs to be on
|
||||
@@ -418,7 +485,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
} else if !hasBackup {
|
||||
// since applying a backup does not save the applied backup to disk, we should
|
||||
// not calculate the original hash based on the backup data
|
||||
calcHash(b, &b.origHash)
|
||||
b.calcHash(&b.origHash)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,13 +527,11 @@ func (b *Buffer) Fini() {
|
||||
if !b.Modified() {
|
||||
b.Serialize()
|
||||
}
|
||||
b.RemoveBackup()
|
||||
b.CancelBackup()
|
||||
|
||||
if b.Type == BTStdout {
|
||||
fmt.Fprint(util.Stdout, string(b.Bytes()))
|
||||
}
|
||||
|
||||
atomic.StoreInt32(&(b.fini), int32(1))
|
||||
}
|
||||
|
||||
// GetName returns the name that should be displayed in the statusline
|
||||
@@ -480,7 +545,7 @@ func (b *Buffer) GetName() string {
|
||||
name = b.Path
|
||||
}
|
||||
if b.Settings["basename"].(bool) {
|
||||
return path.Base(name)
|
||||
return filepath.Base(name)
|
||||
}
|
||||
return name
|
||||
}
|
||||
@@ -496,8 +561,6 @@ func (b *Buffer) Insert(start Loc, text string) {
|
||||
b.EventHandler.cursors = b.cursors
|
||||
b.EventHandler.active = b.curCursor
|
||||
b.EventHandler.Insert(start, text)
|
||||
|
||||
b.RequestBackup()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,8 +570,6 @@ func (b *Buffer) Remove(start, end Loc) {
|
||||
b.EventHandler.cursors = b.cursors
|
||||
b.EventHandler.active = b.curCursor
|
||||
b.EventHandler.Remove(start, end)
|
||||
|
||||
b.RequestBackup()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,6 +600,7 @@ func (b *Buffer) ReOpen() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
|
||||
if err != nil {
|
||||
@@ -546,7 +608,7 @@ func (b *Buffer) ReOpen() error {
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(transform.NewReader(file, enc.NewDecoder()))
|
||||
data, err := ioutil.ReadAll(reader)
|
||||
data, err := io.ReadAll(reader)
|
||||
txt := string(data)
|
||||
|
||||
if err != nil {
|
||||
@@ -559,7 +621,7 @@ func (b *Buffer) ReOpen() error {
|
||||
if len(data) > LargeFileThreshold {
|
||||
b.Settings["fastdirty"] = true
|
||||
} else {
|
||||
calcHash(b, &b.origHash)
|
||||
b.calcHash(&b.origHash)
|
||||
}
|
||||
}
|
||||
b.isModified = false
|
||||
@@ -621,21 +683,20 @@ func (b *Buffer) WordAt(loc Loc) []byte {
|
||||
return b.Substr(start, end)
|
||||
}
|
||||
|
||||
// Shared returns if there are other buffers with the same file as this buffer
|
||||
func (b *Buffer) Shared() bool {
|
||||
for _, buf := range OpenBuffers {
|
||||
if buf != b && buf.SharedBuffer == b.SharedBuffer {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Modified returns if this buffer has been modified since
|
||||
// being opened
|
||||
func (b *Buffer) Modified() bool {
|
||||
if b.Type.Scratch {
|
||||
return false
|
||||
}
|
||||
|
||||
if b.Settings["fastdirty"].(bool) {
|
||||
return b.isModified
|
||||
}
|
||||
|
||||
var buff [md5.Size]byte
|
||||
|
||||
calcHash(b, &buff)
|
||||
return buff != b.origHash
|
||||
return b.isModified
|
||||
}
|
||||
|
||||
// Size returns the number of bytes in the current buffer
|
||||
@@ -654,26 +715,6 @@ func (b *Buffer) Size() int {
|
||||
return nb
|
||||
}
|
||||
|
||||
// calcHash calculates md5 hash of all lines in the buffer
|
||||
func calcHash(b *Buffer, out *[md5.Size]byte) {
|
||||
h := md5.New()
|
||||
|
||||
if len(b.lines) > 0 {
|
||||
h.Write(b.lines[0].data)
|
||||
|
||||
for _, l := range b.lines[1:] {
|
||||
if b.Endings == FFDos {
|
||||
h.Write([]byte{'\r', '\n'})
|
||||
} else {
|
||||
h.Write([]byte{'\n'})
|
||||
}
|
||||
h.Write(l.data)
|
||||
}
|
||||
}
|
||||
|
||||
h.Sum((*out)[:0])
|
||||
}
|
||||
|
||||
func parseDefFromFile(f config.RuntimeFile, header *highlight.Header) *highlight.Def {
|
||||
data, err := f.Data()
|
||||
if err != nil {
|
||||
@@ -825,7 +866,7 @@ func (b *Buffer) UpdateRules() {
|
||||
if header.MatchFileName(b.Path) {
|
||||
matchedFileName = true
|
||||
}
|
||||
if len(fnameMatches) == 0 && header.MatchFileHeader(b.lines[0].data) {
|
||||
if len(fnameMatches) == 0 && header.MatchFileHeader(b.lines[0].data()) {
|
||||
matchedFileHeader = true
|
||||
}
|
||||
} else if header.FileType == ft {
|
||||
@@ -879,7 +920,7 @@ func (b *Buffer) UpdateRules() {
|
||||
if header.MatchFileName(b.Path) {
|
||||
fnameMatches = append(fnameMatches, syntaxFileInfo{header, f.Name(), nil})
|
||||
}
|
||||
if len(fnameMatches) == 0 && header.MatchFileHeader(b.lines[0].data) {
|
||||
if len(fnameMatches) == 0 && header.MatchFileHeader(b.lines[0].data()) {
|
||||
headerMatches = append(headerMatches, syntaxFileInfo{header, f.Name(), nil})
|
||||
}
|
||||
} else if header.FileType == ft {
|
||||
@@ -912,7 +953,7 @@ func (b *Buffer) UpdateRules() {
|
||||
for _, m := range matches {
|
||||
if m.header.HasFileSignature() {
|
||||
for i := 0; i < limit; i++ {
|
||||
if m.header.MatchFileSignature(b.lines[i].data) {
|
||||
if m.header.MatchFileSignature(b.lines[i].data()) {
|
||||
syntaxFile = m.fileName
|
||||
if m.syntaxDef != nil {
|
||||
b.SyntaxDef = m.syntaxDef
|
||||
@@ -1089,11 +1130,11 @@ func (b *Buffer) MoveLinesUp(start int, end int) {
|
||||
if start < 1 || start >= end || end > len(b.lines) {
|
||||
return
|
||||
}
|
||||
l := string(b.LineBytes(start - 1))
|
||||
l := b.LineString(start - 1)
|
||||
if end == len(b.lines) {
|
||||
b.insert(
|
||||
Loc{
|
||||
util.CharacterCount(b.lines[end-1].data),
|
||||
len(b.lines[end-1].runes),
|
||||
end - 1,
|
||||
},
|
||||
[]byte{'\n'},
|
||||
@@ -1114,7 +1155,7 @@ func (b *Buffer) MoveLinesDown(start int, end int) {
|
||||
if start < 0 || start >= end || end >= len(b.lines) {
|
||||
return
|
||||
}
|
||||
l := string(b.LineBytes(end))
|
||||
l := b.LineString(end)
|
||||
b.Insert(
|
||||
Loc{0, start},
|
||||
l+"\n",
|
||||
@@ -1155,7 +1196,7 @@ func (b *Buffer) findMatchingBrace(braceType [2]rune, start Loc, char rune) (Loc
|
||||
}
|
||||
} else if char == braceType[1] {
|
||||
for y := start.Y; y >= 0; y-- {
|
||||
l := []rune(string(b.lines[y].data))
|
||||
l := []rune(string(b.LineBytes(y)))
|
||||
xInit := len(l) - 1
|
||||
if y == start.Y {
|
||||
xInit = start.X
|
||||
@@ -1224,7 +1265,6 @@ func (b *Buffer) FindMatchingBrace(start Loc) (Loc, bool, bool) {
|
||||
func (b *Buffer) Retab() {
|
||||
toSpaces := b.Settings["tabstospaces"].(bool)
|
||||
tabsize := util.IntOpt(b.Settings["tabsize"])
|
||||
dirty := false
|
||||
|
||||
for i := 0; i < b.LinesNum(); i++ {
|
||||
l := b.LineBytes(i)
|
||||
@@ -1241,14 +1281,20 @@ func (b *Buffer) Retab() {
|
||||
l = bytes.TrimLeft(l, " \t")
|
||||
|
||||
b.Lock()
|
||||
b.lines[i].data = append(ws, l...)
|
||||
ws = append(ws, l...)
|
||||
var runes []Character
|
||||
for len(ws) > 0 {
|
||||
combc, s := util.DecodeCombinedCharacter(ws)
|
||||
runes = append(runes, Character{combc})
|
||||
ws = ws[s:]
|
||||
}
|
||||
b.lines[i].runes = runes
|
||||
b.Unlock()
|
||||
|
||||
b.MarkModified(i, i)
|
||||
dirty = true
|
||||
}
|
||||
|
||||
b.isModified = dirty
|
||||
b.setModified()
|
||||
}
|
||||
|
||||
// ParseCursorLocation turns a cursor location like 10:5 (LINE:COL)
|
||||
@@ -1278,7 +1324,7 @@ func ParseCursorLocation(cursorPositions []string) (Loc, error) {
|
||||
|
||||
// Line returns the string representation of the given line number
|
||||
func (b *Buffer) Line(i int) string {
|
||||
return string(b.LineBytes(i))
|
||||
return b.LineString(i)
|
||||
}
|
||||
|
||||
func (b *Buffer) Write(bytes []byte) (n int, err error) {
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
ulua "github.com/micro-editor/micro/v2/internal/lua"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
type operation struct {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/v2/internal/clipboard"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/micro-editor/micro/v2/internal/clipboard"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// InBounds returns whether the given location is a valid character position in the given buffer
|
||||
@@ -20,8 +20,14 @@ type Cursor struct {
|
||||
buf *Buffer
|
||||
Loc
|
||||
|
||||
// Last cursor x position
|
||||
// Last visual x position of the cursor. Used in cursor up/down movements
|
||||
// for remembering the original x position when moving to a line that is
|
||||
// shorter than current x position.
|
||||
LastVisualX int
|
||||
// Similar to LastVisualX but takes softwrapping into account, i.e. last
|
||||
// visual x position in a visual (wrapped) line on the screen, which may be
|
||||
// different from the line in the buffer.
|
||||
LastWrappedVisualX int
|
||||
|
||||
// The current selection as a range of character numbers (inclusive)
|
||||
CurSelection [2]Loc
|
||||
@@ -61,8 +67,9 @@ func (c *Cursor) Buf() *Buffer {
|
||||
// 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.X, c.Y = b.X, b.Y
|
||||
c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection
|
||||
c.StoreVisualX()
|
||||
}
|
||||
|
||||
// GotoLoc puts the cursor at the given cursor's location and gives
|
||||
@@ -73,8 +80,8 @@ func (c *Cursor) GotoLoc(l Loc) {
|
||||
}
|
||||
|
||||
// GetVisualX returns the x value of the cursor in visual spaces
|
||||
func (c *Cursor) GetVisualX() int {
|
||||
if c.buf.GetVisualX != nil {
|
||||
func (c *Cursor) GetVisualX(wrap bool) int {
|
||||
if wrap && c.buf.GetVisualX != nil {
|
||||
return c.buf.GetVisualX(c.Loc)
|
||||
}
|
||||
|
||||
@@ -100,7 +107,7 @@ func (c *Cursor) GetCharPosInLine(b []byte, visualPos int) int {
|
||||
// Start moves the cursor to the start of the line it is on
|
||||
func (c *Cursor) Start() {
|
||||
c.X = 0
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
c.StoreVisualX()
|
||||
}
|
||||
|
||||
// StartOfText moves the cursor to the first non-whitespace rune of
|
||||
@@ -131,7 +138,7 @@ func (c *Cursor) IsStartOfText() bool {
|
||||
// End moves the cursor to the end of the line it is on
|
||||
func (c *Cursor) End() {
|
||||
c.X = util.CharacterCount(c.buf.LineBytes(c.Y))
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
c.StoreVisualX()
|
||||
}
|
||||
|
||||
// CopySelection copies the user's selection to either "primary"
|
||||
@@ -186,7 +193,7 @@ func (c *Cursor) Deselect(start bool) {
|
||||
if start {
|
||||
c.Loc = c.CurSelection[0]
|
||||
} else {
|
||||
c.Loc = c.CurSelection[1].Move(-1, c.buf)
|
||||
c.Loc = c.CurSelection[1]
|
||||
}
|
||||
c.ResetSelection()
|
||||
c.StoreVisualX()
|
||||
@@ -594,26 +601,16 @@ func (c *Cursor) SubWordLeft() {
|
||||
|
||||
// RuneUnder returns the rune under the given x position
|
||||
func (c *Cursor) RuneUnder(x int) rune {
|
||||
line := c.buf.LineBytes(c.Y)
|
||||
if len(line) == 0 || x >= util.CharacterCount(line) {
|
||||
line := c.buf.LineCharacters(c.Y)
|
||||
if len(line) == 0 || x >= len(line) {
|
||||
return '\n'
|
||||
} else if x < 0 {
|
||||
x = 0
|
||||
}
|
||||
i := 0
|
||||
for len(line) > 0 {
|
||||
r, _, size := util.DecodeCharacter(line)
|
||||
line = line[size:]
|
||||
|
||||
if i == x {
|
||||
return r
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
return '\n'
|
||||
return line[x].combc[0]
|
||||
}
|
||||
|
||||
func (c *Cursor) StoreVisualX() {
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
c.LastVisualX = c.GetVisualX(false)
|
||||
c.LastWrappedVisualX = c.GetVisualX(true)
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"bytes"
|
||||
"time"
|
||||
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
ulua "github.com/micro-editor/micro/v2/internal/lua"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
dmp "github.com/sergi/go-diff/diffmatchpatch"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
luar "layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
@@ -104,7 +104,7 @@ func (eh *EventHandler) DoTextEvent(t *TextEvent, useUndo bool) {
|
||||
c.OrigSelection[0] = move(c.OrigSelection[0])
|
||||
c.OrigSelection[1] = move(c.OrigSelection[1])
|
||||
c.Relocate()
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
c.StoreVisualX()
|
||||
}
|
||||
|
||||
if useUndo {
|
||||
|
||||
@@ -6,32 +6,10 @@ import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/micro/v2/pkg/highlight"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
"github.com/micro-editor/micro/v2/pkg/highlight"
|
||||
)
|
||||
|
||||
// Finds the byte index of the nth rune in a byte slice
|
||||
func runeToByteIndex(n int, txt []byte) int {
|
||||
if n == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
count := 0
|
||||
i := 0
|
||||
for len(txt) > 0 {
|
||||
_, _, size := util.DecodeCharacter(txt)
|
||||
|
||||
txt = txt[size:]
|
||||
count += size
|
||||
i++
|
||||
|
||||
if i == n {
|
||||
break
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// A searchState contains the search match info for a single line
|
||||
type searchState struct {
|
||||
search string
|
||||
@@ -41,10 +19,14 @@ type searchState struct {
|
||||
done bool
|
||||
}
|
||||
|
||||
// A Line contains the data in bytes as well as a highlight state, match
|
||||
type Character struct {
|
||||
combc []rune
|
||||
}
|
||||
|
||||
// A Line contains the slice of runes as well as a highlight state, match
|
||||
// and a flag for whether the highlighting needs to be updated
|
||||
type Line struct {
|
||||
data []byte
|
||||
runes []Character
|
||||
|
||||
state highlight.State
|
||||
match highlight.LineMatch
|
||||
@@ -59,6 +41,24 @@ type Line struct {
|
||||
search map[*Buffer]*searchState
|
||||
}
|
||||
|
||||
// data returns the line as byte slice
|
||||
func (l Line) data() []byte {
|
||||
var runes []rune
|
||||
for _, r := range l.runes {
|
||||
runes = append(runes, r.combc[0:]...)
|
||||
}
|
||||
return []byte(string(runes))
|
||||
}
|
||||
|
||||
// String returns the line as string
|
||||
func (l Line) String() string {
|
||||
var runes []rune
|
||||
for _, r := range l.runes {
|
||||
runes = append(runes, r.combc[0:]...)
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
const (
|
||||
// Line ending file formats
|
||||
FFAuto = 0 // Autodetect format
|
||||
@@ -94,7 +94,7 @@ func Append(slice []Line, data ...Line) []Line {
|
||||
return slice
|
||||
}
|
||||
|
||||
// NewLineArray returns a new line array from an array of bytes
|
||||
// NewLineArray returns a new line array from an array of runes
|
||||
func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray {
|
||||
la := new(LineArray)
|
||||
|
||||
@@ -144,10 +144,16 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
|
||||
loaded += dlen
|
||||
}
|
||||
|
||||
var runes []Character
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
for len(data) > 0 {
|
||||
combc, s := util.DecodeCombinedCharacter(data)
|
||||
runes = append(runes, Character{combc})
|
||||
data = data[s:]
|
||||
}
|
||||
la.lines = Append(la.lines, Line{
|
||||
data: data,
|
||||
runes: runes,
|
||||
state: nil,
|
||||
match: nil,
|
||||
})
|
||||
@@ -155,8 +161,14 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
|
||||
// Last line was read
|
||||
break
|
||||
} else {
|
||||
data = data[:dlen-1]
|
||||
for len(data) > 0 {
|
||||
combc, s := util.DecodeCombinedCharacter(data)
|
||||
runes = append(runes, Character{combc})
|
||||
data = data[s:]
|
||||
}
|
||||
la.lines = Append(la.lines, Line{
|
||||
data: data[:dlen-1],
|
||||
runes: runes,
|
||||
state: nil,
|
||||
match: nil,
|
||||
})
|
||||
@@ -174,7 +186,7 @@ func (la *LineArray) Bytes() []byte {
|
||||
// initsize should provide a good estimate
|
||||
b.Grow(int(la.initsize + 4096))
|
||||
for i, l := range la.lines {
|
||||
b.Write(l.data)
|
||||
b.Write(l.data())
|
||||
if i != len(la.lines)-1 {
|
||||
if la.Endings == FFDos {
|
||||
b.WriteByte('\r')
|
||||
@@ -188,13 +200,13 @@ func (la *LineArray) Bytes() []byte {
|
||||
// newlineBelow adds a newline below the given line number
|
||||
func (la *LineArray) newlineBelow(y int) {
|
||||
la.lines = append(la.lines, Line{
|
||||
data: []byte{' '},
|
||||
runes: []Character{},
|
||||
state: nil,
|
||||
match: nil,
|
||||
})
|
||||
copy(la.lines[y+2:], la.lines[y+1:])
|
||||
la.lines[y+1] = Line{
|
||||
data: []byte{},
|
||||
runes: []Character{},
|
||||
state: la.lines[y].state,
|
||||
match: nil,
|
||||
}
|
||||
@@ -205,41 +217,65 @@ func (la *LineArray) insert(pos Loc, value []byte) {
|
||||
la.lock.Lock()
|
||||
defer la.lock.Unlock()
|
||||
|
||||
x, y := runeToByteIndex(pos.X, la.lines[pos.Y].data), pos.Y
|
||||
for i := 0; i < len(value); i++ {
|
||||
if value[i] == '\n' || (value[i] == '\r' && i < len(value)-1 && value[i+1] == '\n') {
|
||||
la.split(Loc{x, y})
|
||||
x = 0
|
||||
y++
|
||||
var runes []Character
|
||||
for len(value) > 0 {
|
||||
combc, s := util.DecodeCombinedCharacter(value)
|
||||
runes = append(runes, Character{combc})
|
||||
value = value[s:]
|
||||
}
|
||||
x, y := util.Min(pos.X, len(la.lines[pos.Y].runes)), pos.Y
|
||||
start := -1
|
||||
|
||||
if value[i] == '\r' {
|
||||
i++
|
||||
outer:
|
||||
for i, r := range runes {
|
||||
for j := 0; j < len(r.combc); j++ {
|
||||
if r.combc[j] == '\n' || (r.combc[j] == '\r' && i < len(runes)-1 && r.combc[j+1] == '\n') {
|
||||
la.split(Loc{x, y})
|
||||
if i > 0 && start < len(runes) && start < i {
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
la.insertRunes(Loc{x, y}, runes[start:i])
|
||||
}
|
||||
|
||||
x = 0
|
||||
y++
|
||||
|
||||
if r.combc[j] == '\r' {
|
||||
i++
|
||||
}
|
||||
if i+1 <= len(runes) {
|
||||
start = i + 1
|
||||
}
|
||||
|
||||
continue outer
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
la.insertByte(Loc{x, y}, value[i])
|
||||
x++
|
||||
}
|
||||
if start < 0 {
|
||||
la.insertRunes(Loc{x, y}, runes)
|
||||
} else if start < len(runes) {
|
||||
la.insertRunes(Loc{x, y}, runes[start:])
|
||||
}
|
||||
}
|
||||
|
||||
// InsertByte 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
|
||||
// Inserts a rune array at a given location
|
||||
func (la *LineArray) insertRunes(pos Loc, runes []Character) {
|
||||
la.lines[pos.Y].runes = append(la.lines[pos.Y].runes, runes...)
|
||||
copy(la.lines[pos.Y].runes[pos.X+len(runes):], la.lines[pos.Y].runes[pos.X:])
|
||||
copy(la.lines[pos.Y].runes[pos.X:], runes)
|
||||
}
|
||||
|
||||
// joinLines joins the two lines a and b
|
||||
func (la *LineArray) joinLines(a, b int) {
|
||||
la.lines[a].data = append(la.lines[a].data, la.lines[b].data...)
|
||||
la.insertRunes(Loc{len(la.lines[a].runes), a}, la.lines[b].runes)
|
||||
la.deleteLine(b)
|
||||
}
|
||||
|
||||
// split splits a line at a given position
|
||||
func (la *LineArray) split(pos Loc) {
|
||||
la.newlineBelow(pos.Y)
|
||||
la.lines[pos.Y+1].data = append(la.lines[pos.Y+1].data, la.lines[pos.Y].data[pos.X:]...)
|
||||
la.insertRunes(Loc{0, pos.Y + 1}, la.lines[pos.Y].runes[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
|
||||
@@ -253,10 +289,10 @@ func (la *LineArray) remove(start, end Loc) []byte {
|
||||
defer la.lock.Unlock()
|
||||
|
||||
sub := la.Substr(start, end)
|
||||
startX := runeToByteIndex(start.X, la.lines[start.Y].data)
|
||||
endX := runeToByteIndex(end.X, la.lines[end.Y].data)
|
||||
startX := util.Min(start.X, len(la.lines[start.Y].runes))
|
||||
endX := util.Min(end.X, len(la.lines[end.Y].runes))
|
||||
if start.Y == end.Y {
|
||||
la.lines[start.Y].data = append(la.lines[start.Y].data[:startX], la.lines[start.Y].data[endX:]...)
|
||||
la.lines[start.Y].runes = append(la.lines[start.Y].runes[:startX], la.lines[start.Y].runes[endX:]...)
|
||||
} else {
|
||||
la.deleteLines(start.Y+1, end.Y-1)
|
||||
la.deleteToEnd(Loc{startX, start.Y})
|
||||
@@ -268,12 +304,12 @@ func (la *LineArray) remove(start, end Loc) []byte {
|
||||
|
||||
// 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]
|
||||
la.lines[pos.Y].runes = la.lines[pos.Y].runes[: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:]
|
||||
la.lines[pos.Y].runes = la.lines[pos.Y].runes[pos.X+1:]
|
||||
}
|
||||
|
||||
// deleteLine deletes the line number
|
||||
@@ -285,29 +321,37 @@ func (la *LineArray) deleteLines(y1, y2 int) {
|
||||
la.lines = la.lines[:y1+copy(la.lines[y1:], la.lines[y2+1:])]
|
||||
}
|
||||
|
||||
// DeleteByte deletes the byte at a position
|
||||
func (la *LineArray) deleteByte(pos Loc) {
|
||||
la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X+copy(la.lines[pos.Y].data[pos.X:], la.lines[pos.Y].data[pos.X+1:])]
|
||||
}
|
||||
|
||||
// Substr returns the string representation between two locations
|
||||
func (la *LineArray) Substr(start, end Loc) []byte {
|
||||
startX := runeToByteIndex(start.X, la.lines[start.Y].data)
|
||||
endX := runeToByteIndex(end.X, la.lines[end.Y].data)
|
||||
if start.Y == end.Y {
|
||||
src := la.lines[start.Y].data[startX:endX]
|
||||
dest := make([]byte, len(src))
|
||||
copy(dest, src)
|
||||
return dest
|
||||
startX := util.Min(start.X, len(la.lines[start.Y].runes))
|
||||
endX := util.Min(end.X, len(la.lines[end.Y].runes))
|
||||
var runes []rune
|
||||
if start.Y == end.Y && startX <= endX {
|
||||
for _, r := range la.lines[start.Y].runes[startX:endX] {
|
||||
runes = append(runes, r.combc[0:]...)
|
||||
}
|
||||
return []byte(string(runes))
|
||||
}
|
||||
str := make([]byte, 0, len(la.lines[start.Y+1].data)*(end.Y-start.Y))
|
||||
str = append(str, la.lines[start.Y].data[startX:]...)
|
||||
|
||||
var str []byte
|
||||
for _, r := range la.lines[start.Y].runes[startX:] {
|
||||
runes = append(runes, r.combc[0:]...)
|
||||
}
|
||||
str = append(str, []byte(string(runes))...)
|
||||
str = append(str, '\n')
|
||||
for i := start.Y + 1; i <= end.Y-1; i++ {
|
||||
str = append(str, la.lines[i].data...)
|
||||
runes = runes[:0]
|
||||
for _, r := range la.lines[i].runes {
|
||||
runes = append(runes, r.combc[0:]...)
|
||||
}
|
||||
str = append(str, []byte(string(runes))...)
|
||||
str = append(str, '\n')
|
||||
}
|
||||
str = append(str, la.lines[end.Y].data[:endX]...)
|
||||
runes = runes[:0]
|
||||
for _, r := range la.lines[end.Y].runes[:endX] {
|
||||
runes = append(runes, r.combc[0:]...)
|
||||
}
|
||||
str = append(str, []byte(string(runes))...)
|
||||
return str
|
||||
}
|
||||
|
||||
@@ -324,15 +368,38 @@ func (la *LineArray) Start() Loc {
|
||||
// End returns the location of the last character in the buffer
|
||||
func (la *LineArray) End() Loc {
|
||||
numlines := len(la.lines)
|
||||
return Loc{util.CharacterCount(la.lines[numlines-1].data), numlines - 1}
|
||||
return Loc{len(la.lines[numlines-1].runes), numlines - 1}
|
||||
}
|
||||
|
||||
// LineCharacters returns line n as an array of characters
|
||||
func (la *LineArray) LineCharacters(n int) []Character {
|
||||
if n >= len(la.lines) || n < 0 {
|
||||
return []Character{}
|
||||
}
|
||||
|
||||
return la.lines[n].runes
|
||||
}
|
||||
|
||||
// LineBytes returns line n as an array of bytes
|
||||
func (la *LineArray) LineBytes(lineN int) []byte {
|
||||
if lineN >= len(la.lines) || lineN < 0 {
|
||||
func (la *LineArray) LineBytes(n int) []byte {
|
||||
if n >= len(la.lines) || n < 0 {
|
||||
return []byte{}
|
||||
}
|
||||
return la.lines[lineN].data
|
||||
return la.lines[n].data()
|
||||
}
|
||||
|
||||
// LineString returns line n as an string
|
||||
func (la *LineArray) LineString(n int) string {
|
||||
if n >= len(la.lines) || n < 0 {
|
||||
return string("")
|
||||
}
|
||||
|
||||
var runes []rune
|
||||
for _, r := range la.lines[n].runes {
|
||||
runes = append(runes, r.combc[0:]...)
|
||||
}
|
||||
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
// State gets the highlight state for the given line number
|
||||
@@ -414,7 +481,7 @@ func (la *LineArray) SearchMatch(b *Buffer, pos Loc) bool {
|
||||
if !s.done {
|
||||
s.match = nil
|
||||
start := Loc{0, lineN}
|
||||
end := Loc{util.CharacterCount(la.lines[lineN].data), lineN}
|
||||
end := Loc{len(la.lines[lineN].runes), lineN}
|
||||
for start.X < end.X {
|
||||
m, found, _ := b.FindNext(b.LastSearch, start, end, start, true, b.LastSearchRegex)
|
||||
if !found {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// Loc stores a location
|
||||
@@ -47,6 +47,16 @@ func (l Loc) LessEqual(b Loc) bool {
|
||||
return l == b
|
||||
}
|
||||
|
||||
// Clamp clamps a loc between start and end
|
||||
func (l Loc) Clamp(start, end Loc) Loc {
|
||||
if l.GreaterEqual(end) {
|
||||
return end
|
||||
} else if l.LessThan(start) {
|
||||
return start
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// The following functions require a buffer to know where newlines are
|
||||
|
||||
// Diff returns the distance between two locations
|
||||
@@ -139,10 +149,5 @@ func ByteOffset(pos Loc, buf *Buffer) int {
|
||||
|
||||
// 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
|
||||
return pos.Clamp(la.Start(), la.End())
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
)
|
||||
|
||||
type MsgType int
|
||||
@@ -84,7 +84,7 @@ func (b *Buffer) ClearAllMessages() {
|
||||
}
|
||||
|
||||
type Messager interface {
|
||||
Message(msg ...interface{})
|
||||
Message(msg ...any)
|
||||
}
|
||||
|
||||
var prompt Messager
|
||||
|
||||
@@ -2,21 +2,21 @@ package buffer
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"golang.org/x/text/encoding"
|
||||
"golang.org/x/text/encoding/htmlindex"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
@@ -24,68 +24,192 @@ import (
|
||||
// because hashing is too slow
|
||||
const LargeFileThreshold = 50000
|
||||
|
||||
// overwriteFile opens the given file for writing, truncating if one exists, and then calls
|
||||
// the supplied function with the file as io.Writer object, also making sure the file is
|
||||
// closed afterwards.
|
||||
func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error, withSudo bool) (err error) {
|
||||
type wrappedFile struct {
|
||||
name string
|
||||
writeCloser io.WriteCloser
|
||||
withSudo bool
|
||||
screenb bool
|
||||
cmd *exec.Cmd
|
||||
sigChan chan os.Signal
|
||||
}
|
||||
|
||||
type saveResponse struct {
|
||||
size int
|
||||
err error
|
||||
}
|
||||
|
||||
type saveRequest struct {
|
||||
buf *Buffer
|
||||
path string
|
||||
withSudo bool
|
||||
newFile bool
|
||||
saveResponseChan chan saveResponse
|
||||
}
|
||||
|
||||
var saveRequestChan chan saveRequest
|
||||
var backupRequestChan chan backupRequest
|
||||
|
||||
func init() {
|
||||
// Both saveRequestChan and backupRequestChan need to be non-buffered
|
||||
// so the save/backup goroutine receives both save and backup requests
|
||||
// in the same order the main goroutine sends them.
|
||||
saveRequestChan = make(chan saveRequest)
|
||||
backupRequestChan = make(chan backupRequest)
|
||||
|
||||
go func() {
|
||||
duration := backupSeconds * float64(time.Second)
|
||||
backupTicker := time.NewTicker(time.Duration(duration))
|
||||
|
||||
for {
|
||||
select {
|
||||
case sr := <-saveRequestChan:
|
||||
size, err := sr.buf.safeWrite(sr.path, sr.withSudo, sr.newFile)
|
||||
sr.saveResponseChan <- saveResponse{size, err}
|
||||
case br := <-backupRequestChan:
|
||||
handleBackupRequest(br)
|
||||
case <-backupTicker.C:
|
||||
periodicBackup()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func openFile(name string, withSudo bool) (wrappedFile, error) {
|
||||
var err error
|
||||
var writeCloser io.WriteCloser
|
||||
var screenb bool
|
||||
var cmd *exec.Cmd
|
||||
var sigChan chan os.Signal
|
||||
|
||||
if withSudo {
|
||||
cmd = exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name)
|
||||
|
||||
if writeCloser, err = cmd.StdinPipe(); err != nil {
|
||||
return
|
||||
conv := "notrunc"
|
||||
// TODO: both platforms do not support dd with conv=fsync yet
|
||||
if !(runtime.GOOS == "illumos" || runtime.GOOS == "netbsd") {
|
||||
conv += ",fsync"
|
||||
}
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
<-c
|
||||
cmd.Process.Kill()
|
||||
}()
|
||||
cmd = exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "conv="+conv, "of="+name)
|
||||
writeCloser, err = cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return wrappedFile{}, err
|
||||
}
|
||||
|
||||
sigChan = make(chan os.Signal, 1)
|
||||
signal.Reset(os.Interrupt)
|
||||
signal.Notify(sigChan, os.Interrupt)
|
||||
|
||||
screenb = screen.TempFini()
|
||||
// need to start the process now, otherwise when we flush the file
|
||||
// contents to its stdin it might hang because the kernel's pipe size
|
||||
// is too small to handle the full file contents all at once
|
||||
if e := cmd.Start(); e != nil && err == nil {
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
screen.TempStart(screenb)
|
||||
return err
|
||||
|
||||
signal.Notify(util.Sigterm, os.Interrupt)
|
||||
signal.Stop(sigChan)
|
||||
|
||||
return wrappedFile{}, err
|
||||
}
|
||||
} else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
w := bufio.NewWriter(transform.NewWriter(writeCloser, enc.NewEncoder()))
|
||||
err = fn(w)
|
||||
|
||||
if err2 := w.Flush(); err2 != nil && err == nil {
|
||||
err = err2
|
||||
}
|
||||
// Call Sync() on the file to make sure the content is safely on disk.
|
||||
// Does not work with sudo as we don't have direct access to the file.
|
||||
if !withSudo {
|
||||
f := writeCloser.(*os.File)
|
||||
if err2 := f.Sync(); err2 != nil && err == nil {
|
||||
err = err2
|
||||
} else {
|
||||
writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, util.FileMode)
|
||||
if err != nil {
|
||||
return wrappedFile{}, err
|
||||
}
|
||||
}
|
||||
if err2 := writeCloser.Close(); err2 != nil && err == nil {
|
||||
err = err2
|
||||
|
||||
return wrappedFile{name, writeCloser, withSudo, screenb, cmd, sigChan}, nil
|
||||
}
|
||||
|
||||
func (wf wrappedFile) Truncate() error {
|
||||
if wf.withSudo {
|
||||
// we don't need to stop the screen here, since it is still stopped
|
||||
// by openFile()
|
||||
// truncate might not be available on every platfom, so use dd instead
|
||||
cmd := exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "count=0", "of="+wf.name)
|
||||
return cmd.Run()
|
||||
}
|
||||
return wf.writeCloser.(*os.File).Truncate(0)
|
||||
}
|
||||
|
||||
func (wf wrappedFile) Write(b *SharedBuffer) (int, error) {
|
||||
file := bufio.NewWriter(transform.NewWriter(wf.writeCloser, b.encoding.NewEncoder()))
|
||||
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
if len(b.lines) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if withSudo {
|
||||
// end of line
|
||||
var eol []byte
|
||||
if b.Endings == FFDos {
|
||||
eol = []byte{'\r', '\n'}
|
||||
} else {
|
||||
eol = []byte{'\n'}
|
||||
}
|
||||
|
||||
err := wf.Truncate()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// write lines
|
||||
size, err := file.Write(b.lines[0].data())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for _, l := range b.lines[1:] {
|
||||
if _, err = file.Write(eol); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err = file.Write(l.data()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
size += len(eol) + len(l.data())
|
||||
}
|
||||
|
||||
err = file.Flush()
|
||||
if err == nil && !wf.withSudo {
|
||||
// Call Sync() on the file to make sure the content is safely on disk.
|
||||
f := wf.writeCloser.(*os.File)
|
||||
err = f.Sync()
|
||||
}
|
||||
return size, err
|
||||
}
|
||||
|
||||
func (wf wrappedFile) Close() error {
|
||||
err := wf.writeCloser.Close()
|
||||
if wf.withSudo {
|
||||
// wait for dd to finish and restart the screen if we used sudo
|
||||
err := cmd.Wait()
|
||||
screen.TempStart(screenb)
|
||||
err := wf.cmd.Wait()
|
||||
screen.TempStart(wf.screenb)
|
||||
|
||||
signal.Notify(util.Sigterm, os.Interrupt)
|
||||
signal.Stop(wf.sigChan)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
func (b *SharedBuffer) overwriteFile(name string) (int, error) {
|
||||
file, err := openFile(name, false)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
size, err := file.Write(b)
|
||||
|
||||
err2 := file.Close()
|
||||
if err2 != nil && err == nil {
|
||||
err = err2
|
||||
}
|
||||
return size, err
|
||||
}
|
||||
|
||||
// Save saves the buffer to its default path
|
||||
@@ -95,9 +219,7 @@ func (b *Buffer) Save() error {
|
||||
|
||||
// AutoSave saves the buffer to its default path
|
||||
func (b *Buffer) AutoSave() error {
|
||||
// Doing full b.Modified() check every time would be costly, due to the hash
|
||||
// calculation. So use just isModified even if fastdirty is not set.
|
||||
if !b.isModified {
|
||||
if !b.Modified() {
|
||||
return nil
|
||||
}
|
||||
return b.saveToFile(b.Path, false, true)
|
||||
@@ -124,16 +246,12 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
|
||||
if b.Type.Scratch {
|
||||
return errors.New("Cannot save scratch buffer")
|
||||
}
|
||||
if withSudo && runtime.GOOS == "windows" {
|
||||
return errors.New("Save with sudo not supported on Windows")
|
||||
}
|
||||
|
||||
if !autoSave && b.Settings["rmtrailingws"].(bool) {
|
||||
for i, l := range b.lines {
|
||||
leftover := util.CharacterCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))
|
||||
|
||||
linelen := util.CharacterCount(l.data)
|
||||
b.Remove(Loc{leftover, i}, Loc{linelen, i})
|
||||
leftover := strings.TrimRightFunc(l.String(), unicode.IsSpace)
|
||||
linelen := len(l.runes)
|
||||
b.Remove(Loc{len(leftover), i}, Loc{linelen, i})
|
||||
}
|
||||
|
||||
b.RelocateCursors()
|
||||
@@ -146,19 +264,35 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
|
||||
}
|
||||
}
|
||||
|
||||
// Update the last time this file was updated after saving
|
||||
defer func() {
|
||||
b.ModTime, _ = util.GetModTime(filename)
|
||||
err = b.Serialize()
|
||||
}()
|
||||
filename, err = util.ReplaceHome(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Removes any tilde and replaces with the absolute path to home
|
||||
absFilename, _ := util.ReplaceHome(filename)
|
||||
newFile := false
|
||||
fileInfo, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
newFile = true
|
||||
}
|
||||
if err == nil && fileInfo.IsDir() {
|
||||
return errors.New("Error: " + filename + " is a directory and cannot be saved")
|
||||
}
|
||||
if err == nil && !fileInfo.Mode().IsRegular() {
|
||||
return errors.New("Error: " + filename + " is not a regular file and cannot be saved")
|
||||
}
|
||||
|
||||
absFilename, err := filepath.Abs(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the leading path to the file | "." is returned if there's no leading path provided
|
||||
if dirname := filepath.Dir(absFilename); dirname != "." {
|
||||
// Check if the parent dirs don't exist
|
||||
if _, statErr := os.Stat(dirname); os.IsNotExist(statErr) {
|
||||
if _, statErr := os.Stat(dirname); errors.Is(statErr, fs.ErrNotExist) {
|
||||
// Prompt to make sure they want to create the dirs that are missing
|
||||
if b.Settings["mkparents"].(bool) {
|
||||
// Create all leading dir(s) since they don't exist
|
||||
@@ -172,60 +306,94 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
|
||||
}
|
||||
}
|
||||
|
||||
var fileSize int
|
||||
|
||||
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
|
||||
saveResponseChan := make(chan saveResponse)
|
||||
saveRequestChan <- saveRequest{b, absFilename, withSudo, newFile, saveResponseChan}
|
||||
result := <-saveResponseChan
|
||||
err = result.err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if errors.Is(err, util.ErrOverwrite) {
|
||||
screen.TermMessage(err)
|
||||
err = errors.Unwrap(err)
|
||||
|
||||
fwriter := func(file io.Writer) (e error) {
|
||||
if len(b.lines) == 0 {
|
||||
return
|
||||
b.UpdateModTime()
|
||||
}
|
||||
|
||||
// end of line
|
||||
var eol []byte
|
||||
if b.Endings == FFDos {
|
||||
eol = []byte{'\r', '\n'}
|
||||
} else {
|
||||
eol = []byte{'\n'}
|
||||
}
|
||||
|
||||
// write lines
|
||||
if fileSize, e = file.Write(b.lines[0].data); e != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, l := range b.lines[1:] {
|
||||
if _, e = file.Write(eol); e != nil {
|
||||
return
|
||||
}
|
||||
if _, e = file.Write(l.data); e != nil {
|
||||
return
|
||||
}
|
||||
fileSize += len(eol) + len(l.data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err = overwriteFile(absFilename, enc, fwriter, withSudo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !b.Settings["fastdirty"].(bool) {
|
||||
if fileSize > LargeFileThreshold {
|
||||
if result.size > LargeFileThreshold {
|
||||
// For large files 'fastdirty' needs to be on
|
||||
b.Settings["fastdirty"] = true
|
||||
} else {
|
||||
calcHash(b, &b.origHash)
|
||||
b.calcHash(&b.origHash)
|
||||
}
|
||||
}
|
||||
|
||||
newPath := b.Path != filename
|
||||
if newPath {
|
||||
b.RemoveBackup()
|
||||
}
|
||||
|
||||
b.Path = filename
|
||||
absPath, _ := filepath.Abs(filename)
|
||||
b.AbsPath = absPath
|
||||
b.AbsPath = absFilename
|
||||
b.isModified = false
|
||||
b.UpdateRules()
|
||||
b.UpdateModTime()
|
||||
|
||||
if newPath {
|
||||
// need to update glob-based and filetype-based settings
|
||||
b.ReloadSettings(true)
|
||||
}
|
||||
|
||||
err = b.Serialize()
|
||||
return err
|
||||
}
|
||||
|
||||
// safeWrite writes the buffer to a file in a "safe" way, preventing loss of the
|
||||
// contents of the file if it fails to write the new contents.
|
||||
// This means that the file is not overwritten directly but by writing to the
|
||||
// backup file first.
|
||||
func (b *SharedBuffer) safeWrite(path string, withSudo bool, newFile bool) (int, error) {
|
||||
file, err := openFile(path, withSudo)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if newFile && err != nil {
|
||||
os.Remove(path)
|
||||
}
|
||||
}()
|
||||
|
||||
// Try to backup first before writing
|
||||
backupName, resolveName, err := b.writeBackup(path)
|
||||
if err != nil {
|
||||
file.Close()
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Backup saved, so cancel pending periodic backup, if any
|
||||
delete(requestedBackups, b)
|
||||
|
||||
b.forceKeepBackup = true
|
||||
size := 0
|
||||
{
|
||||
// If we failed to write or close, keep the backup we made
|
||||
size, err = file.Write(b)
|
||||
if err != nil {
|
||||
file.Close()
|
||||
return 0, util.OverwriteError{err, backupName}
|
||||
}
|
||||
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
return 0, util.OverwriteError{err, backupName}
|
||||
}
|
||||
}
|
||||
b.forceKeepBackup = false
|
||||
|
||||
if !b.keepBackup() {
|
||||
b.removeBackup(backupName, resolveName)
|
||||
}
|
||||
|
||||
return size, err
|
||||
}
|
||||
|
||||
@@ -2,10 +2,65 @@ package buffer
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// We want "^" and "$" to match only the beginning/end of a line, not the
|
||||
// beginning/end of the search region if it is in the middle of a line.
|
||||
// In that case we use padded regexps to require a rune before or after
|
||||
// the match. (This also affects other empty-string patters like "\\b".)
|
||||
// The following two flags indicate the padding used.
|
||||
const (
|
||||
padStart = 1 << iota
|
||||
padEnd
|
||||
)
|
||||
|
||||
func findLineParams(b *Buffer, start, end Loc, i int, r *regexp.Regexp) ([]byte, int, int, *regexp.Regexp) {
|
||||
l := b.LineBytes(i)
|
||||
charpos := 0
|
||||
padMode := 0
|
||||
|
||||
if i == end.Y {
|
||||
nchars := util.CharacterCount(l)
|
||||
end.X = util.Clamp(end.X, 0, nchars)
|
||||
if end.X < nchars {
|
||||
l = util.SliceStart(l, end.X+1)
|
||||
padMode |= padEnd
|
||||
}
|
||||
}
|
||||
|
||||
if i == start.Y {
|
||||
nchars := util.CharacterCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars)
|
||||
if start.X > 0 {
|
||||
charpos = start.X - 1
|
||||
l = util.SliceEnd(l, charpos)
|
||||
padMode |= padStart
|
||||
}
|
||||
}
|
||||
|
||||
if padMode != 0 {
|
||||
re, err := regexp.Compile(r.String() + `\E`)
|
||||
if err == nil {
|
||||
// r contains \Q without closing \E
|
||||
r = re
|
||||
}
|
||||
|
||||
if padMode == padStart {
|
||||
r = regexp.MustCompile(".(?:" + r.String() + ")")
|
||||
} else if padMode == padEnd {
|
||||
r = regexp.MustCompile("(?:" + r.String() + ").")
|
||||
} else {
|
||||
// padMode == padStart|padEnd
|
||||
r = regexp.MustCompile(".(?:" + r.String() + ").")
|
||||
}
|
||||
}
|
||||
|
||||
return l, charpos, padMode, r
|
||||
}
|
||||
|
||||
func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||
lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
|
||||
if start.Y > b.LinesNum()-1 {
|
||||
@@ -22,30 +77,19 @@ func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||
}
|
||||
|
||||
for i := start.Y; i <= end.Y; i++ {
|
||||
l := b.LineBytes(i)
|
||||
charpos := 0
|
||||
l, charpos, padMode, rPadded := findLineParams(b, start, end, i, r)
|
||||
|
||||
if i == start.Y && start.Y == end.Y {
|
||||
nchars := util.CharacterCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars)
|
||||
end.X = util.Clamp(end.X, 0, nchars)
|
||||
l = util.SliceStart(l, end.X)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == start.Y {
|
||||
nchars := util.CharacterCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == end.Y {
|
||||
nchars := util.CharacterCount(l)
|
||||
end.X = util.Clamp(end.X, 0, nchars)
|
||||
l = util.SliceStart(l, end.X)
|
||||
}
|
||||
|
||||
match := r.FindIndex(l)
|
||||
match := rPadded.FindIndex(l)
|
||||
|
||||
if match != nil {
|
||||
if padMode&padStart != 0 {
|
||||
_, size := utf8.DecodeRune(l[match[0]:])
|
||||
match[0] += size
|
||||
}
|
||||
if padMode&padEnd != 0 {
|
||||
_, size := utf8.DecodeLastRune(l[:match[1]])
|
||||
match[1] -= size
|
||||
}
|
||||
start := Loc{charpos + util.RunePos(l, match[0]), i}
|
||||
end := Loc{charpos + util.RunePos(l, match[1]), i}
|
||||
return [2]Loc{start, end}, true
|
||||
@@ -70,39 +114,39 @@ func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||
}
|
||||
|
||||
for i := end.Y; i >= start.Y; i-- {
|
||||
l := b.LineBytes(i)
|
||||
charpos := 0
|
||||
|
||||
if i == start.Y && start.Y == end.Y {
|
||||
nchars := util.CharacterCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars)
|
||||
end.X = util.Clamp(end.X, 0, nchars)
|
||||
l = util.SliceStart(l, end.X)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == start.Y {
|
||||
nchars := util.CharacterCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == end.Y {
|
||||
nchars := util.CharacterCount(l)
|
||||
end.X = util.Clamp(end.X, 0, nchars)
|
||||
l = util.SliceStart(l, end.X)
|
||||
}
|
||||
|
||||
allMatches := r.FindAllIndex(l, -1)
|
||||
charCount := util.CharacterCount(b.LineBytes(i))
|
||||
from := Loc{0, i}.Clamp(start, end)
|
||||
to := Loc{charCount, i}.Clamp(start, end)
|
||||
|
||||
allMatches := b.findAll(r, from, to)
|
||||
if allMatches != nil {
|
||||
match := allMatches[len(allMatches)-1]
|
||||
start := Loc{charpos + util.RunePos(l, match[0]), i}
|
||||
end := Loc{charpos + util.RunePos(l, match[1]), i}
|
||||
return [2]Loc{start, end}, true
|
||||
return [2]Loc{match[0], match[1]}, true
|
||||
}
|
||||
}
|
||||
return [2]Loc{}, false
|
||||
}
|
||||
|
||||
func (b *Buffer) findAll(r *regexp.Regexp, start, end Loc) [][2]Loc {
|
||||
var matches [][2]Loc
|
||||
loc := start
|
||||
for {
|
||||
match, found := b.findDown(r, loc, end)
|
||||
if !found {
|
||||
break
|
||||
}
|
||||
matches = append(matches, match)
|
||||
if match[0] != match[1] {
|
||||
loc = match[1]
|
||||
} else if match[1] != end {
|
||||
loc = match[1].Move(1, b)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
// FindNext finds the next occurrence of a given string in the buffer
|
||||
// It returns the start and end location of the match (if found) and
|
||||
// a boolean indicating if it was found
|
||||
@@ -146,53 +190,58 @@ func (b *Buffer) FindNext(s string, start, end, from Loc, down bool, useRegex bo
|
||||
}
|
||||
|
||||
// ReplaceRegex replaces all occurrences of 'search' with 'replace' in the given area
|
||||
// and returns the number of replacements made and the number of runes
|
||||
// and returns the number of replacements made and the number of characters
|
||||
// added or removed on the last line of the range
|
||||
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte, captureGroups bool) (int, int) {
|
||||
if start.GreaterThan(end) {
|
||||
start, end = end, start
|
||||
}
|
||||
|
||||
netrunes := 0
|
||||
|
||||
charsEnd := util.CharacterCount(b.LineBytes(end.Y))
|
||||
found := 0
|
||||
var deltas []Delta
|
||||
|
||||
for i := start.Y; i <= end.Y; i++ {
|
||||
l := b.lines[i].data
|
||||
charpos := 0
|
||||
l := b.LineBytes(i)
|
||||
charCount := util.CharacterCount(l)
|
||||
if (i == start.Y && start.X > 0) || (i == end.Y && end.X < charCount) {
|
||||
// This replacement code works in general, but it creates a separate
|
||||
// modification for each match. We only use it for the first and last
|
||||
// lines, which may use padded regexps
|
||||
|
||||
if start.Y == end.Y && i == start.Y {
|
||||
l = util.SliceStart(l, end.X)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == start.Y {
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == end.Y {
|
||||
l = util.SliceStart(l, end.X)
|
||||
}
|
||||
newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
|
||||
var result []byte
|
||||
if captureGroups {
|
||||
for _, submatches := range search.FindAllSubmatchIndex(in, -1) {
|
||||
result = search.Expand(result, replace, in, submatches)
|
||||
from := Loc{0, i}.Clamp(start, end)
|
||||
to := Loc{charCount, i}.Clamp(start, end)
|
||||
matches := b.findAll(search, from, to)
|
||||
found += len(matches)
|
||||
|
||||
for j := len(matches) - 1; j >= 0; j-- {
|
||||
// if we counted upwards, the different deltas would interfere
|
||||
match := matches[j]
|
||||
var newText []byte
|
||||
if captureGroups {
|
||||
newText = search.ReplaceAll(b.Substr(match[0], match[1]), replace)
|
||||
} else {
|
||||
newText = replace
|
||||
}
|
||||
} else {
|
||||
result = replace
|
||||
deltas = append(deltas, Delta{newText, match[0], match[1]})
|
||||
}
|
||||
found++
|
||||
if i == end.Y {
|
||||
netrunes += util.CharacterCount(result) - util.CharacterCount(in)
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
from := Loc{charpos, i}
|
||||
to := Loc{charpos + util.CharacterCount(l), i}
|
||||
|
||||
deltas = append(deltas, Delta{newText, from, to})
|
||||
} else {
|
||||
newLine := search.ReplaceAllFunc(l, func(in []byte) []byte {
|
||||
found++
|
||||
var result []byte
|
||||
if captureGroups {
|
||||
match := search.FindSubmatchIndex(in)
|
||||
result = search.Expand(result, replace, in, match)
|
||||
} else {
|
||||
result = replace
|
||||
}
|
||||
return result
|
||||
})
|
||||
deltas = append(deltas, Delta{newLine, Loc{0, i}, Loc{charCount, i}})
|
||||
}
|
||||
}
|
||||
|
||||
b.MultipleReplace(deltas)
|
||||
|
||||
return found, netrunes
|
||||
return found, util.CharacterCount(b.LineBytes(end.Y)) - charsEnd
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/encoding"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// The SerializedBuffer holds the types that get serialized when a buffer is saved
|
||||
@@ -31,16 +29,30 @@ func (b *Buffer) Serialize() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath))
|
||||
|
||||
return overwriteFile(name, encoding.Nop, func(file io.Writer) error {
|
||||
err := gob.NewEncoder(file).Encode(SerializedBuffer{
|
||||
b.EventHandler,
|
||||
b.GetActiveCursor().Loc,
|
||||
b.ModTime,
|
||||
})
|
||||
var buf bytes.Buffer
|
||||
err := gob.NewEncoder(&buf).Encode(SerializedBuffer{
|
||||
b.EventHandler,
|
||||
b.GetActiveCursor().Loc,
|
||||
b.ModTime,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}, false)
|
||||
}
|
||||
|
||||
name, resolveName := util.DetermineEscapePath(filepath.Join(config.ConfigDir, "buffers"), b.AbsPath)
|
||||
err = util.SafeWrite(name, buf.Bytes(), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resolveName != "" {
|
||||
err = util.SafeWrite(resolveName, []byte(b.AbsPath), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unserialize loads the buffer info from config.ConfigDir/buffers
|
||||
@@ -50,7 +62,8 @@ func (b *Buffer) Unserialize() error {
|
||||
if b.Path == "" {
|
||||
return nil
|
||||
}
|
||||
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath)))
|
||||
name, _ := util.DetermineEscapePath(filepath.Join(config.ConfigDir, "buffers"), b.AbsPath)
|
||||
file, err := os.Open(name)
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
var buffer SerializedBuffer
|
||||
|
||||
@@ -4,14 +4,23 @@ import (
|
||||
"crypto/md5"
|
||||
"reflect"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
ulua "github.com/micro-editor/micro/v2/internal/lua"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"golang.org/x/text/encoding/htmlindex"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
luar "layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
func (b *Buffer) ReloadSettings(reloadFiletype bool) {
|
||||
settings := config.ParsedSettings()
|
||||
config.UpdatePathGlobLocals(settings, b.AbsPath)
|
||||
|
||||
if _, ok := b.LocalSettings["filetype"]; !ok && reloadFiletype {
|
||||
oldFiletype := b.Settings["filetype"].(string)
|
||||
|
||||
_, local := b.LocalSettings["filetype"]
|
||||
_, volatile := config.VolatileSettings["filetype"]
|
||||
if reloadFiletype && !local && !volatile {
|
||||
// need to update filetype before updating other settings based on it
|
||||
b.Settings["filetype"] = "unknown"
|
||||
if v, ok := settings["filetype"]; ok {
|
||||
@@ -21,9 +30,14 @@ func (b *Buffer) ReloadSettings(reloadFiletype bool) {
|
||||
|
||||
// update syntax rules, which will also update filetype if needed
|
||||
b.UpdateRules()
|
||||
settings["filetype"] = b.Settings["filetype"]
|
||||
|
||||
config.InitLocalSettings(settings, b.Path)
|
||||
curFiletype := b.Settings["filetype"].(string)
|
||||
if oldFiletype != curFiletype {
|
||||
b.doCallbacks("filetype", oldFiletype, curFiletype)
|
||||
}
|
||||
|
||||
config.UpdateFileTypeLocals(settings, curFiletype)
|
||||
|
||||
for k, v := range config.DefaultCommonSettings() {
|
||||
if k == "filetype" {
|
||||
// prevent recursion
|
||||
@@ -45,8 +59,9 @@ func (b *Buffer) ReloadSettings(reloadFiletype bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
|
||||
if reflect.DeepEqual(b.Settings[option], nativeValue) {
|
||||
func (b *Buffer) DoSetOptionNative(option string, nativeValue any) {
|
||||
oldValue := b.Settings[option]
|
||||
if reflect.DeepEqual(oldValue, nativeValue) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -58,7 +73,7 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
|
||||
b.Settings["fastdirty"] = true
|
||||
} else {
|
||||
if !b.isModified {
|
||||
calcHash(b, &b.origHash)
|
||||
b.calcHash(&b.origHash)
|
||||
} else {
|
||||
// prevent using an old stale origHash value
|
||||
b.origHash = [md5.Size]byte{}
|
||||
@@ -76,7 +91,7 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
|
||||
case "dos":
|
||||
b.Endings = FFDos
|
||||
}
|
||||
b.isModified = true
|
||||
b.setModified()
|
||||
} else if option == "syntax" {
|
||||
if !nativeValue.(bool) {
|
||||
b.ClearMatches()
|
||||
@@ -84,7 +99,13 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
|
||||
b.UpdateRules()
|
||||
}
|
||||
} else if option == "encoding" {
|
||||
b.isModified = true
|
||||
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
|
||||
if err != nil {
|
||||
enc = unicode.UTF8
|
||||
b.Settings["encoding"] = "utf-8"
|
||||
}
|
||||
b.encoding = enc
|
||||
b.setModified()
|
||||
} else if option == "readonly" && b.Type.Kind == BTDefault.Kind {
|
||||
b.Type.Readonly = nativeValue.(bool)
|
||||
} else if option == "hlsearch" {
|
||||
@@ -114,12 +135,10 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
if b.OptionCallback != nil {
|
||||
b.OptionCallback(option, nativeValue)
|
||||
}
|
||||
b.doCallbacks(option, oldValue, nativeValue)
|
||||
}
|
||||
|
||||
func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
|
||||
func (b *Buffer) SetOptionNative(option string, nativeValue any) error {
|
||||
if err := config.OptionIsValid(option, nativeValue); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -136,10 +155,22 @@ func (b *Buffer) SetOption(option, value string) error {
|
||||
return config.ErrInvalidOption
|
||||
}
|
||||
|
||||
nativeValue, err := config.GetNativeValue(option, b.Settings[option], value)
|
||||
nativeValue, err := config.GetNativeValue(option, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.SetOptionNative(option, nativeValue)
|
||||
}
|
||||
|
||||
func (b *Buffer) doCallbacks(option string, oldValue any, newValue any) {
|
||||
if b.OptionCallback != nil {
|
||||
b.OptionCallback(option, newValue)
|
||||
}
|
||||
|
||||
if err := config.RunPluginFn("onBufferOptionChanged",
|
||||
luar.New(ulua.L, b), luar.New(ulua.L, option),
|
||||
luar.New(ulua.L, oldValue), luar.New(ulua.L, newValue)); err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
)
|
||||
|
||||
type terminalClipboard struct{}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
)
|
||||
|
||||
// DefStyle is Micro's default style
|
||||
@@ -55,6 +55,14 @@ func InitColorscheme() error {
|
||||
c, err := LoadDefaultColorscheme()
|
||||
if err == nil {
|
||||
Colorscheme = c
|
||||
} else {
|
||||
// The colorscheme setting seems broken (maybe because we have not validated
|
||||
// it earlier, see comment in verifySetting()). So reset it to the default
|
||||
// colorscheme and try again.
|
||||
GlobalSettings["colorscheme"] = DefaultGlobalOnlySettings["colorscheme"]
|
||||
if c, err2 := LoadDefaultColorscheme(); err2 == nil {
|
||||
Colorscheme = c
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
|
||||
@@ -3,8 +3,8 @@ package config
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
func TestSimpleStringToStyle(t *testing.T) {
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
ulua "github.com/micro-editor/micro/v2/internal/lua"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
)
|
||||
|
||||
// ErrNoSuchFunction is returned when Call is executed on a function that does not exist
|
||||
@@ -42,7 +42,7 @@ func RunPluginFn(fn string, args ...lua.LValue) error {
|
||||
// RunPluginFnBool runs a function in all plugins and returns
|
||||
// false if any one of them returned false
|
||||
// also returns an error if any of the plugins had an error
|
||||
func RunPluginFnBool(settings map[string]interface{}, fn string, args ...lua.LValue) (bool, error) {
|
||||
func RunPluginFnBool(settings map[string]any, fn string, args ...lua.LValue) (bool, error) {
|
||||
var reterr error
|
||||
retbool := true
|
||||
for _, p := range Plugins {
|
||||
@@ -71,7 +71,7 @@ type Plugin struct {
|
||||
Info *PluginInfo // json file containing info
|
||||
Srcs []RuntimeFile // lua files
|
||||
Loaded bool
|
||||
Default bool // pre-installed plugin
|
||||
Builtin bool
|
||||
}
|
||||
|
||||
// IsLoaded returns if a plugin is enabled
|
||||
@@ -143,15 +143,3 @@ func FindPlugin(name string) *Plugin {
|
||||
}
|
||||
return pl
|
||||
}
|
||||
|
||||
// FindAnyPlugin does not require the plugin to be enabled
|
||||
func FindAnyPlugin(name string) *Plugin {
|
||||
var pl *Plugin
|
||||
for _, p := range Plugins {
|
||||
if p.Name == name {
|
||||
pl = p
|
||||
break
|
||||
}
|
||||
}
|
||||
return pl
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -14,10 +13,10 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/micro-editor/json5"
|
||||
ulua "github.com/micro-editor/micro/v2/internal/lua"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/json5"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -43,6 +42,7 @@ type PluginPackage struct {
|
||||
Author string
|
||||
Tags []string
|
||||
Versions PluginVersions
|
||||
Builtin bool
|
||||
}
|
||||
|
||||
// PluginPackages is a list of PluginPackage instances.
|
||||
@@ -76,6 +76,9 @@ func (pp *PluginPackage) String() string {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString("Plugin: ")
|
||||
buf.WriteString(pp.Name)
|
||||
if pp.Builtin {
|
||||
buf.WriteString(" (built-in)")
|
||||
}
|
||||
buf.WriteRune('\n')
|
||||
if pp.Author != "" {
|
||||
buf.WriteString("Author: ")
|
||||
@@ -225,7 +228,7 @@ func GetAllPluginPackages(out io.Writer) PluginPackages {
|
||||
if strs, ok := data.([]string); ok {
|
||||
return strs
|
||||
}
|
||||
if ifs, ok := data.([]interface{}); ok {
|
||||
if ifs, ok := data.([]any); ok {
|
||||
result := make([]string, len(ifs))
|
||||
for i, urlIf := range ifs {
|
||||
if url, ok := urlIf.(string); ok {
|
||||
@@ -335,7 +338,7 @@ func isUnknownCoreVersion() bool {
|
||||
return err != nil
|
||||
}
|
||||
|
||||
func newStaticPluginVersion(name, version string) *PluginVersion {
|
||||
func newStaticPluginVersion(name, version string, builtin bool) *PluginVersion {
|
||||
vers, err := semver.ParseTolerant(version)
|
||||
|
||||
if err != nil {
|
||||
@@ -344,7 +347,8 @@ func newStaticPluginVersion(name, version string) *PluginVersion {
|
||||
}
|
||||
}
|
||||
pl := &PluginPackage{
|
||||
Name: name,
|
||||
Name: name,
|
||||
Builtin: builtin,
|
||||
}
|
||||
pv := &PluginVersion{
|
||||
pack: pl,
|
||||
@@ -359,7 +363,7 @@ 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, util.Version, true))
|
||||
}
|
||||
|
||||
for _, p := range Plugins {
|
||||
@@ -367,7 +371,7 @@ func GetInstalledVersions(withCore bool) PluginVersions {
|
||||
continue
|
||||
}
|
||||
version := GetInstalledPluginVersion(p.Name)
|
||||
if pv := newStaticPluginVersion(p.Name, version); pv != nil {
|
||||
if pv := newStaticPluginVersion(p.Name, version, p.Builtin); pv != nil {
|
||||
result = append(result, pv)
|
||||
}
|
||||
}
|
||||
@@ -396,7 +400,7 @@ func (pv *PluginVersion) DownloadAndInstall(out io.Writer) error {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -605,7 +609,7 @@ func UpdatePlugins(out io.Writer, plugins []string) {
|
||||
// if no plugins are specified, update all installed plugins.
|
||||
if len(plugins) == 0 {
|
||||
for _, p := range Plugins {
|
||||
if !p.IsLoaded() || p.Default {
|
||||
if !p.IsLoaded() || p.Builtin || p.Name == "initlua" {
|
||||
continue
|
||||
}
|
||||
plugins = append(plugins, p.Name)
|
||||
@@ -614,7 +618,7 @@ func UpdatePlugins(out io.Writer, plugins []string) {
|
||||
|
||||
fmt.Fprintln(out, "Checking for plugin updates")
|
||||
microVersion := PluginVersions{
|
||||
newStaticPluginVersion(CorePluginName, util.Version),
|
||||
newStaticPluginVersion(CorePluginName, util.Version, true),
|
||||
}
|
||||
|
||||
var updates = make(PluginDependencies, 0)
|
||||
@@ -646,14 +650,14 @@ func PluginCommand(out io.Writer, cmd string, args []string) {
|
||||
if pp == nil {
|
||||
fmt.Fprintln(out, "Unknown plugin \""+plugin+"\"")
|
||||
} else if err := pp.IsInstallable(out); err != nil {
|
||||
fmt.Fprintln(out, "Error installing ", plugin, ": ", err)
|
||||
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")
|
||||
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")
|
||||
fmt.Fprintln(out, pp.Name, "is already installed")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -664,10 +668,14 @@ func PluginCommand(out io.Writer, cmd string, args []string) {
|
||||
case "remove":
|
||||
removed := ""
|
||||
for _, plugin := range args {
|
||||
if plugin == "initlua" {
|
||||
fmt.Fprintln(out, "initlua cannot be removed, but can be disabled via settings.")
|
||||
continue
|
||||
}
|
||||
// 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.")
|
||||
if p.Name == plugin && p.Builtin {
|
||||
fmt.Fprintln(out, p.Name, "is a built-in plugin which cannot be removed, but can be disabled via settings.")
|
||||
continue
|
||||
}
|
||||
if p.Name == plugin {
|
||||
@@ -677,8 +685,9 @@ func PluginCommand(out io.Writer, cmd string, args []string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
removed = strings.TrimSpace(removed)
|
||||
if removed != "" {
|
||||
fmt.Fprintln(out, "Removed ", removed)
|
||||
fmt.Fprintln(out, "Removed", removed)
|
||||
} else {
|
||||
fmt.Fprintln(out, "No plugins removed")
|
||||
}
|
||||
@@ -688,11 +697,17 @@ func PluginCommand(out io.Writer, cmd string, args []string) {
|
||||
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)
|
||||
if p.Pack().Name == "initlua" {
|
||||
fmt.Fprintf(out, "%s\n", "initlua")
|
||||
} else if p.Pack().Builtin {
|
||||
fmt.Fprintf(out, "%s (built-in)\n", p.Pack().Name)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%s (%s)\n", p.Pack().Name, p.Version)
|
||||
}
|
||||
}
|
||||
case "search":
|
||||
plugins := SearchPlugin(out, args)
|
||||
fmt.Fprintln(out, len(plugins), " plugins found")
|
||||
fmt.Fprintln(out, len(plugins), "plugins found")
|
||||
for _, p := range plugins {
|
||||
fmt.Fprintln(out, "----------------")
|
||||
fmt.Fprintln(out, p.String())
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/blang/semver"
|
||||
|
||||
"github.com/zyedidia/json5"
|
||||
"github.com/micro-editor/json5"
|
||||
)
|
||||
|
||||
func TestDependencyResolving(t *testing.T) {
|
||||
|
||||
@@ -2,15 +2,13 @@ package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
rt "github.com/zyedidia/micro/v2/runtime"
|
||||
rt "github.com/micro-editor/micro/v2/runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -62,12 +60,6 @@ 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
|
||||
@@ -87,22 +79,18 @@ func (rf realFile) Name() string {
|
||||
}
|
||||
|
||||
func (rf realFile) Data() ([]byte, error) {
|
||||
return ioutil.ReadFile(string(rf))
|
||||
return os.ReadFile(string(rf))
|
||||
}
|
||||
|
||||
func (af assetFile) Name() string {
|
||||
fn := path.Base(string(af))
|
||||
return fn[:len(fn)-len(path.Ext(fn))]
|
||||
fn := filepath.Base(string(af))
|
||||
return fn[:len(fn)-len(filepath.Ext(fn))]
|
||||
}
|
||||
|
||||
func (af assetFile) Data() ([]byte, error) {
|
||||
return rt.Asset(string(af))
|
||||
}
|
||||
|
||||
func (nf namedFile) Name() string {
|
||||
return nf.name
|
||||
}
|
||||
|
||||
// AddRuntimeFile registers a file for the given filetype
|
||||
func AddRuntimeFile(fileType RTFiletype, file RuntimeFile) {
|
||||
allFiles[fileType] = append(allFiles[fileType], file)
|
||||
@@ -117,7 +105,7 @@ func AddRealRuntimeFile(fileType RTFiletype, file RuntimeFile) {
|
||||
// AddRuntimeFilesFromDirectory registers each file from the given directory for
|
||||
// the filetype which matches the file-pattern
|
||||
func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string) {
|
||||
files, _ := ioutil.ReadDir(directory)
|
||||
files, _ := os.ReadDir(directory)
|
||||
for _, f := range files {
|
||||
if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
|
||||
fullPath := filepath.Join(directory, f.Name())
|
||||
@@ -136,8 +124,8 @@ func AddRuntimeFilesFromAssets(fileType RTFiletype, directory, pattern string) {
|
||||
|
||||
assetLoop:
|
||||
for _, f := range files {
|
||||
if ok, _ := path.Match(pattern, f); ok {
|
||||
af := assetFile(path.Join(directory, f))
|
||||
if ok, _ := filepath.Match(pattern, f); ok {
|
||||
af := assetFile(filepath.Join(directory, f))
|
||||
for _, rf := range realFiles[fileType] {
|
||||
if af.Name() == rf.Name() {
|
||||
continue assetLoop
|
||||
@@ -178,7 +166,7 @@ func InitRuntimeFiles(user bool) {
|
||||
if user {
|
||||
AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
|
||||
}
|
||||
AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
|
||||
AddRuntimeFilesFromAssets(fileType, filepath.Join("runtime", dir), pattern)
|
||||
}
|
||||
|
||||
initRuntimeVars()
|
||||
@@ -199,19 +187,20 @@ func InitPlugins() {
|
||||
p.Name = "initlua"
|
||||
p.DirName = "initlua"
|
||||
p.Srcs = append(p.Srcs, realFile(initlua))
|
||||
p.Builtin = false
|
||||
Plugins = append(Plugins, p)
|
||||
}
|
||||
|
||||
// Search ConfigDir for plugin-scripts
|
||||
plugdir := filepath.Join(ConfigDir, "plug")
|
||||
files, _ := ioutil.ReadDir(plugdir)
|
||||
files, _ := os.ReadDir(plugdir)
|
||||
|
||||
isID := regexp.MustCompile(`^[_A-Za-z0-9]+$`).MatchString
|
||||
|
||||
for _, d := range files {
|
||||
plugpath := filepath.Join(plugdir, d.Name())
|
||||
if stat, err := os.Stat(plugpath); err == nil && stat.IsDir() {
|
||||
srcs, _ := ioutil.ReadDir(plugpath)
|
||||
srcs, _ := os.ReadDir(plugpath)
|
||||
p := new(Plugin)
|
||||
p.Name = d.Name()
|
||||
p.DirName = d.Name()
|
||||
@@ -219,7 +208,7 @@ func InitPlugins() {
|
||||
if strings.HasSuffix(f.Name(), ".lua") {
|
||||
p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
|
||||
} else if strings.HasSuffix(f.Name(), ".json") {
|
||||
data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
|
||||
data, err := os.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -254,7 +243,7 @@ func InitPlugins() {
|
||||
p := new(Plugin)
|
||||
p.Name = d
|
||||
p.DirName = d
|
||||
p.Default = true
|
||||
p.Builtin = true
|
||||
for _, f := range srcs {
|
||||
if strings.HasSuffix(f, ".lua") {
|
||||
p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))
|
||||
@@ -311,7 +300,7 @@ func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) e
|
||||
if _, err := os.Stat(fullpath); err == nil {
|
||||
AddRealRuntimeFile(filetype, realFile(fullpath))
|
||||
} else {
|
||||
fullpath = path.Join("runtime", "plugins", pldir, filePath)
|
||||
fullpath = filepath.Join("runtime", "plugins", pldir, filePath)
|
||||
AddRuntimeFile(filetype, assetFile(fullpath))
|
||||
}
|
||||
return nil
|
||||
@@ -328,7 +317,7 @@ func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, dire
|
||||
if _, err := os.Stat(fullpath); err == nil {
|
||||
AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
|
||||
} else {
|
||||
fullpath = path.Join("runtime", "plugins", pldir, directory)
|
||||
fullpath = filepath.Join("runtime", "plugins", pldir, directory)
|
||||
AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
@@ -12,13 +11,13 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/micro-editor/json5"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
"github.com/zyedidia/glob"
|
||||
"github.com/zyedidia/json5"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"golang.org/x/text/encoding/htmlindex"
|
||||
)
|
||||
|
||||
type optionValidator func(string, interface{}) error
|
||||
type optionValidator func(string, any) error
|
||||
|
||||
// a list of settings that need option validators
|
||||
var optionValidators = map[string]optionValidator{
|
||||
@@ -29,26 +28,31 @@ var optionValidators = map[string]optionValidator{
|
||||
"detectlimit": validateNonNegativeValue,
|
||||
"encoding": validateEncoding,
|
||||
"fileformat": validateChoice,
|
||||
"helpsplit": validateChoice,
|
||||
"matchbracestyle": validateChoice,
|
||||
"multiopen": validateChoice,
|
||||
"pageoverlap": validateNonNegativeValue,
|
||||
"reload": validateChoice,
|
||||
"scrollmargin": validateNonNegativeValue,
|
||||
"scrollspeed": validateNonNegativeValue,
|
||||
"tabsize": validatePositiveValue,
|
||||
"truecolor": validateChoice,
|
||||
}
|
||||
|
||||
// a list of settings with pre-defined choices
|
||||
var OptionChoices = map[string][]string{
|
||||
"clipboard": {"internal", "external", "terminal"},
|
||||
"fileformat": {"unix", "dos"},
|
||||
"helpsplit": {"hsplit", "vsplit"},
|
||||
"matchbracestyle": {"underline", "highlight"},
|
||||
"multiopen": {"tab", "hsplit", "vsplit"},
|
||||
"reload": {"prompt", "auto", "disabled"},
|
||||
"truecolor": {"auto", "on", "off"},
|
||||
}
|
||||
|
||||
// a list of settings that can be globally and locally modified and their
|
||||
// default values
|
||||
var defaultCommonSettings = map[string]interface{}{
|
||||
var defaultCommonSettings = map[string]any{
|
||||
"autoindent": true,
|
||||
"autosu": false,
|
||||
"backup": true,
|
||||
@@ -66,51 +70,56 @@ var defaultCommonSettings = map[string]interface{}{
|
||||
"hlsearch": false,
|
||||
"hltaberrors": false,
|
||||
"hltrailingws": false,
|
||||
"incsearch": true,
|
||||
"ignorecase": true,
|
||||
"indentchar": " ",
|
||||
"incsearch": true,
|
||||
"indentchar": " ", // Deprecated
|
||||
"keepautoindent": false,
|
||||
"matchbrace": true,
|
||||
"matchbraceleft": true,
|
||||
"matchbracestyle": "underline",
|
||||
"mkparents": false,
|
||||
"pageoverlap": float64(2),
|
||||
"permbackup": false,
|
||||
"readonly": false,
|
||||
"relativeruler": false,
|
||||
"reload": "prompt",
|
||||
"rmtrailingws": false,
|
||||
"ruler": true,
|
||||
"relativeruler": false,
|
||||
"savecursor": false,
|
||||
"saveundo": false,
|
||||
"scrollbar": false,
|
||||
"scrollmargin": float64(3),
|
||||
"scrollspeed": float64(2),
|
||||
"showchars": "",
|
||||
"smartpaste": true,
|
||||
"softwrap": false,
|
||||
"splitbottom": true,
|
||||
"splitright": true,
|
||||
"statusformatl": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
|
||||
"statusformatl": "$(filename) $(modified)$(overwrite)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
|
||||
"statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
|
||||
"statusline": true,
|
||||
"syntax": true,
|
||||
"tabmovement": false,
|
||||
"tabsize": float64(4),
|
||||
"tabstospaces": false,
|
||||
"truecolor": "auto",
|
||||
"useprimary": true,
|
||||
"wordwrap": false,
|
||||
}
|
||||
|
||||
// a list of settings that should only be globally modified and their
|
||||
// default values
|
||||
var DefaultGlobalOnlySettings = map[string]interface{}{
|
||||
var DefaultGlobalOnlySettings = map[string]any{
|
||||
"autosave": float64(0),
|
||||
"clipboard": "external",
|
||||
"colorscheme": "default",
|
||||
"divchars": "|-",
|
||||
"divreverse": true,
|
||||
"fakecursor": false,
|
||||
"fakecursor": defaultFakeCursor(),
|
||||
"helpsplit": "hsplit",
|
||||
"infobar": true,
|
||||
"keymenu": false,
|
||||
"lockbindings": false,
|
||||
"mouse": true,
|
||||
"multiopen": "tab",
|
||||
"parsecursor": false,
|
||||
@@ -132,14 +141,15 @@ var LocalSettings = []string{
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidOption = errors.New("Invalid option")
|
||||
ErrInvalidValue = errors.New("Invalid value")
|
||||
ErrInvalidOption = errors.New("Invalid option")
|
||||
ErrInvalidValue = errors.New("Invalid value")
|
||||
ErrOptNotToggleable = errors.New("Option not toggleable")
|
||||
|
||||
// The options that the user can set
|
||||
GlobalSettings map[string]interface{}
|
||||
GlobalSettings map[string]any
|
||||
|
||||
// This is the raw parsed json
|
||||
parsedSettings map[string]interface{}
|
||||
parsedSettings map[string]any
|
||||
settingsParseError bool
|
||||
|
||||
// ModifiedSettings is a map of settings which should be written to disk
|
||||
@@ -151,6 +161,10 @@ var (
|
||||
VolatileSettings map[string]bool
|
||||
)
|
||||
|
||||
func writeFile(name string, txt []byte) error {
|
||||
return util.SafeWrite(name, txt, false)
|
||||
}
|
||||
|
||||
func init() {
|
||||
ModifiedSettings = make(map[string]bool)
|
||||
VolatileSettings = make(map[string]bool)
|
||||
@@ -162,11 +176,11 @@ func validateParsedSettings() error {
|
||||
for k, v := range parsedSettings {
|
||||
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
||||
if strings.HasPrefix(k, "ft:") {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
for k1, v1 := range v.(map[string]any) {
|
||||
if _, ok := defaults[k1]; ok {
|
||||
if e := verifySetting(k1, v1, defaults[k1]); e != nil {
|
||||
err = e
|
||||
parsedSettings[k].(map[string]interface{})[k1] = defaults[k1]
|
||||
parsedSettings[k].(map[string]any)[k1] = defaults[k1]
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -177,11 +191,11 @@ func validateParsedSettings() error {
|
||||
delete(parsedSettings, k)
|
||||
continue
|
||||
}
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
for k1, v1 := range v.(map[string]any) {
|
||||
if _, ok := defaults[k1]; ok {
|
||||
if e := verifySetting(k1, v1, defaults[k1]); e != nil {
|
||||
err = e
|
||||
parsedSettings[k].(map[string]interface{})[k1] = defaults[k1]
|
||||
parsedSettings[k].(map[string]any)[k1] = defaults[k1]
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -202,6 +216,7 @@ func validateParsedSettings() error {
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := defaults[k]; ok {
|
||||
if e := verifySetting(k, v, defaults[k]); e != nil {
|
||||
err = e
|
||||
@@ -214,10 +229,10 @@ func validateParsedSettings() error {
|
||||
}
|
||||
|
||||
func ReadSettings() error {
|
||||
parsedSettings = make(map[string]interface{})
|
||||
parsedSettings = make(map[string]any)
|
||||
filename := filepath.Join(ConfigDir, "settings.json")
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
input, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
settingsParseError = true
|
||||
return errors.New("Error reading settings.json file: " + err.Error())
|
||||
@@ -238,16 +253,16 @@ func ReadSettings() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParsedSettings() map[string]interface{} {
|
||||
s := make(map[string]interface{})
|
||||
func ParsedSettings() map[string]any {
|
||||
s := make(map[string]any)
|
||||
for k, v := range parsedSettings {
|
||||
s[k] = v
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func verifySetting(option string, value interface{}, def interface{}) error {
|
||||
var interfaceArr []interface{}
|
||||
func verifySetting(option string, value any, def any) error {
|
||||
var interfaceArr []any
|
||||
valType := reflect.TypeOf(value)
|
||||
defType := reflect.TypeOf(def)
|
||||
assignable := false
|
||||
@@ -262,6 +277,12 @@ func verifySetting(option string, value interface{}, def interface{}) error {
|
||||
return fmt.Errorf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", option, valType, def, defType)
|
||||
}
|
||||
|
||||
if option == "colorscheme" {
|
||||
// Plugins are not initialized yet, so do not verify if the colorscheme
|
||||
// exists yet, since the colorscheme may be added by a plugin later.
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := OptionIsValid(option, value); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -283,22 +304,31 @@ func InitGlobalSettings() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// InitLocalSettings scans the json in settings.json and sets the options locally based
|
||||
// on whether the filetype or path matches ft or glob local settings
|
||||
// UpdatePathGlobLocals scans the already parsed settings and sets the options locally
|
||||
// based on whether the path matches a glob
|
||||
// Must be called after ReadSettings
|
||||
func InitLocalSettings(settings map[string]interface{}, path string) {
|
||||
func UpdatePathGlobLocals(settings map[string]any, path string) {
|
||||
for k, v := range parsedSettings {
|
||||
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
||||
if strings.HasPrefix(k, "ft:") {
|
||||
if settings["filetype"].(string) == k[3:] {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
settings[k1] = v1
|
||||
}
|
||||
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") && !strings.HasPrefix(k, "ft:") {
|
||||
g, _ := glob.Compile(k)
|
||||
if g.MatchString(path) {
|
||||
for k1, v1 := range v.(map[string]any) {
|
||||
settings[k1] = v1
|
||||
}
|
||||
} else {
|
||||
g, _ := glob.Compile(k)
|
||||
if g.MatchString(path) {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateFileTypeLocals scans the already parsed settings and sets the options locally
|
||||
// based on whether the filetype matches to "ft:"
|
||||
// Must be called after ReadSettings
|
||||
func UpdateFileTypeLocals(settings map[string]any, filetype string) {
|
||||
for k, v := range parsedSettings {
|
||||
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") && strings.HasPrefix(k, "ft:") {
|
||||
if filetype == k[3:] {
|
||||
for k1, v1 := range v.(map[string]any) {
|
||||
if k1 != "filetype" {
|
||||
settings[k1] = v1
|
||||
}
|
||||
}
|
||||
@@ -342,7 +372,8 @@ func WriteSettings(filename string) error {
|
||||
}
|
||||
|
||||
txt, _ := json.MarshalIndent(parsedSettings, "", " ")
|
||||
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
||||
txt = append(txt, '\n')
|
||||
err = writeFile(filename, txt)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -350,7 +381,7 @@ func WriteSettings(filename string) error {
|
||||
// OverwriteSettings writes the current settings to settings.json and
|
||||
// resets any user configuration of local settings present in settings.json
|
||||
func OverwriteSettings(filename string) error {
|
||||
settings := make(map[string]interface{})
|
||||
settings := make(map[string]any)
|
||||
|
||||
var err error
|
||||
if _, e := os.Stat(ConfigDir); e == nil {
|
||||
@@ -363,24 +394,25 @@ func OverwriteSettings(filename string) error {
|
||||
}
|
||||
}
|
||||
|
||||
txt, _ := json.MarshalIndent(settings, "", " ")
|
||||
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
||||
txt, _ := json.MarshalIndent(parsedSettings, "", " ")
|
||||
txt = append(txt, '\n')
|
||||
err = writeFile(filename, txt)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// RegisterCommonOptionPlug creates a new option (called pl.name). This is meant to be called by plugins to add options.
|
||||
func RegisterCommonOptionPlug(pl string, name string, defaultvalue interface{}) error {
|
||||
func RegisterCommonOptionPlug(pl string, name string, defaultvalue any) error {
|
||||
return RegisterCommonOption(pl+"."+name, defaultvalue)
|
||||
}
|
||||
|
||||
// RegisterGlobalOptionPlug creates a new global-only option (named pl.name)
|
||||
func RegisterGlobalOptionPlug(pl string, name string, defaultvalue interface{}) error {
|
||||
func RegisterGlobalOptionPlug(pl string, name string, defaultvalue any) error {
|
||||
return RegisterGlobalOption(pl+"."+name, defaultvalue)
|
||||
}
|
||||
|
||||
// RegisterCommonOption creates a new option
|
||||
func RegisterCommonOption(name string, defaultvalue interface{}) error {
|
||||
func RegisterCommonOption(name string, defaultvalue any) error {
|
||||
if _, ok := GlobalSettings[name]; !ok {
|
||||
GlobalSettings[name] = defaultvalue
|
||||
}
|
||||
@@ -389,7 +421,7 @@ func RegisterCommonOption(name string, defaultvalue interface{}) error {
|
||||
}
|
||||
|
||||
// RegisterGlobalOption creates a new global-only option
|
||||
func RegisterGlobalOption(name string, defaultvalue interface{}) error {
|
||||
func RegisterGlobalOption(name string, defaultvalue any) error {
|
||||
if _, ok := GlobalSettings[name]; !ok {
|
||||
GlobalSettings[name] = defaultvalue
|
||||
}
|
||||
@@ -398,7 +430,7 @@ func RegisterGlobalOption(name string, defaultvalue interface{}) error {
|
||||
}
|
||||
|
||||
// GetGlobalOption returns the global value of the given option
|
||||
func GetGlobalOption(name string) interface{} {
|
||||
func GetGlobalOption(name string) any {
|
||||
return GlobalSettings[name]
|
||||
}
|
||||
|
||||
@@ -409,6 +441,15 @@ func defaultFileFormat() string {
|
||||
return "unix"
|
||||
}
|
||||
|
||||
func defaultFakeCursor() bool {
|
||||
_, wt := os.LookupEnv("WT_SESSION")
|
||||
if runtime.GOOS == "windows" && !wt {
|
||||
// enabled for windows consoles where the cursor is slow
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetInfoBarOffset() int {
|
||||
offset := 0
|
||||
if GetGlobalOption("infobar").(bool) {
|
||||
@@ -422,8 +463,8 @@ func GetInfoBarOffset() int {
|
||||
|
||||
// DefaultCommonSettings returns a map of all common buffer settings
|
||||
// and their default values
|
||||
func DefaultCommonSettings() map[string]interface{} {
|
||||
commonsettings := make(map[string]interface{})
|
||||
func DefaultCommonSettings() map[string]any {
|
||||
commonsettings := make(map[string]any)
|
||||
for k, v := range defaultCommonSettings {
|
||||
commonsettings[k] = v
|
||||
}
|
||||
@@ -432,8 +473,8 @@ func DefaultCommonSettings() map[string]interface{} {
|
||||
|
||||
// DefaultAllSettings returns a map of all common buffer & global-only settings
|
||||
// and their default values
|
||||
func DefaultAllSettings() map[string]interface{} {
|
||||
allsettings := make(map[string]interface{})
|
||||
func DefaultAllSettings() map[string]any {
|
||||
allsettings := make(map[string]any)
|
||||
for k, v := range defaultCommonSettings {
|
||||
allsettings[k] = v
|
||||
}
|
||||
@@ -444,32 +485,34 @@ func DefaultAllSettings() map[string]interface{} {
|
||||
}
|
||||
|
||||
// GetNativeValue parses and validates a value for a given option
|
||||
func GetNativeValue(option string, realValue interface{}, value string) (interface{}, error) {
|
||||
var native interface{}
|
||||
kind := reflect.TypeOf(realValue).Kind()
|
||||
if kind == reflect.Bool {
|
||||
func GetNativeValue(option, value string) (any, error) {
|
||||
curVal := GetGlobalOption(option)
|
||||
if curVal == nil {
|
||||
return nil, ErrInvalidOption
|
||||
}
|
||||
|
||||
switch kind := reflect.TypeOf(curVal).Kind(); kind {
|
||||
case reflect.Bool:
|
||||
b, err := util.ParseBool(value)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidValue
|
||||
}
|
||||
native = b
|
||||
} else if kind == reflect.String {
|
||||
native = value
|
||||
} else if kind == reflect.Float64 {
|
||||
return b, nil
|
||||
case reflect.String:
|
||||
return value, nil
|
||||
case reflect.Float64:
|
||||
f, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidValue
|
||||
}
|
||||
native = f
|
||||
} else {
|
||||
return f, nil
|
||||
default:
|
||||
return nil, ErrInvalidValue
|
||||
}
|
||||
|
||||
return native, nil
|
||||
}
|
||||
|
||||
// OptionIsValid checks if a value is valid for a certain option
|
||||
func OptionIsValid(option string, value interface{}) error {
|
||||
func OptionIsValid(option string, value any) error {
|
||||
if validator, ok := optionValidators[option]; ok {
|
||||
return validator(option, value)
|
||||
}
|
||||
@@ -479,7 +522,7 @@ func OptionIsValid(option string, value interface{}) error {
|
||||
|
||||
// Option validators
|
||||
|
||||
func validatePositiveValue(option string, value interface{}) error {
|
||||
func validatePositiveValue(option string, value any) error {
|
||||
nativeValue, ok := value.(float64)
|
||||
|
||||
if !ok {
|
||||
@@ -493,7 +536,7 @@ func validatePositiveValue(option string, value interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateNonNegativeValue(option string, value interface{}) error {
|
||||
func validateNonNegativeValue(option string, value any) error {
|
||||
nativeValue, ok := value.(float64)
|
||||
|
||||
if !ok {
|
||||
@@ -507,7 +550,7 @@ func validateNonNegativeValue(option string, value interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChoice(option string, value interface{}) error {
|
||||
func validateChoice(option string, value any) error {
|
||||
if choices, ok := OptionChoices[option]; ok {
|
||||
val, ok := value.(string)
|
||||
if !ok {
|
||||
@@ -527,7 +570,7 @@ func validateChoice(option string, value interface{}) error {
|
||||
return errors.New("Option has no pre-defined choices")
|
||||
}
|
||||
|
||||
func validateColorscheme(option string, value interface{}) error {
|
||||
func validateColorscheme(option string, value any) error {
|
||||
colorscheme, ok := value.(string)
|
||||
|
||||
if !ok {
|
||||
@@ -541,7 +584,7 @@ func validateColorscheme(option string, value interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateEncoding(option string, value interface{}) error {
|
||||
func validateEncoding(option string, value any) error {
|
||||
_, err := htmlindex.Get(value.(string))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ package display
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/micro-editor/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
)
|
||||
|
||||
// The BufWindow provides a way of displaying a certain section of a buffer.
|
||||
@@ -46,7 +47,7 @@ func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow {
|
||||
// SetBuffer sets this window's buffer.
|
||||
func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
|
||||
w.Buf = b
|
||||
b.OptionCallback = func(option string, nativeValue interface{}) {
|
||||
b.OptionCallback = func(option string, nativeValue any) {
|
||||
if option == "softwrap" {
|
||||
if nativeValue.(bool) {
|
||||
w.StartCol = 0
|
||||
@@ -58,9 +59,15 @@ func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
|
||||
if option == "softwrap" || option == "wordwrap" {
|
||||
w.Relocate()
|
||||
for _, c := range w.Buf.GetCursors() {
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
c.LastWrappedVisualX = c.GetVisualX(true)
|
||||
}
|
||||
}
|
||||
|
||||
if option == "diffgutter" || option == "ruler" || option == "scrollbar" ||
|
||||
option == "statusline" {
|
||||
w.updateDisplayInfo()
|
||||
w.Relocate()
|
||||
}
|
||||
}
|
||||
b.GetVisualX = func(loc buffer.Loc) int {
|
||||
return w.VLocFromLoc(loc).VisualX
|
||||
@@ -160,7 +167,7 @@ func (w *BufWindow) updateDisplayInfo() {
|
||||
|
||||
if w.bufWidth != prevBufWidth && w.Buf.Settings["softwrap"].(bool) {
|
||||
for _, c := range w.Buf.GetCursors() {
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
c.LastWrappedVisualX = c.GetVisualX(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,7 +245,7 @@ func (w *BufWindow) Relocate() bool {
|
||||
|
||||
// horizontal relocation (scrolling)
|
||||
if !b.Settings["softwrap"].(bool) {
|
||||
cx := activeC.GetVisualX()
|
||||
cx := activeC.GetVisualX(false)
|
||||
rw := runewidth.RuneWidth(activeC.RuneUnder(activeC.X))
|
||||
if rw == 0 {
|
||||
rw = 1 // tab or newline
|
||||
@@ -248,8 +255,8 @@ func (w *BufWindow) Relocate() bool {
|
||||
w.StartCol = cx
|
||||
ret = true
|
||||
}
|
||||
if cx+w.gutterOffset+rw > w.StartCol+w.Width {
|
||||
w.StartCol = cx - w.Width + w.gutterOffset + rw
|
||||
if cx+rw > w.StartCol+w.bufWidth {
|
||||
w.StartCol = cx - w.bufWidth + rw
|
||||
ret = true
|
||||
}
|
||||
}
|
||||
@@ -444,12 +451,36 @@ func (w *BufWindow) displayBuffer() {
|
||||
cursors := b.GetCursors()
|
||||
|
||||
curStyle := config.DefStyle
|
||||
|
||||
// Parse showchars which is in the format of key1=val1,key2=val2,...
|
||||
spacechars := " "
|
||||
tabchars := b.Settings["indentchar"].(string)
|
||||
var indentspacechars string
|
||||
var indenttabchars string
|
||||
for _, entry := range strings.Split(b.Settings["showchars"].(string), ",") {
|
||||
split := strings.SplitN(entry, "=", 2)
|
||||
if len(split) < 2 {
|
||||
continue
|
||||
}
|
||||
key, val := split[0], split[1]
|
||||
switch key {
|
||||
case "space":
|
||||
spacechars = val
|
||||
case "tab":
|
||||
tabchars = val
|
||||
case "ispace":
|
||||
indentspacechars = val
|
||||
case "itab":
|
||||
indenttabchars = val
|
||||
}
|
||||
}
|
||||
|
||||
for ; vloc.Y < w.bufHeight; vloc.Y++ {
|
||||
vloc.X = 0
|
||||
|
||||
currentLine := false
|
||||
for _, c := range cursors {
|
||||
if bloc.Y == c.Y && w.active {
|
||||
if !c.HasSelection() && bloc.Y == c.Y && w.active {
|
||||
currentLine = true
|
||||
break
|
||||
}
|
||||
@@ -488,147 +519,190 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
bloc.X = bslice
|
||||
|
||||
draw := func(r rune, combc []rune, style tcell.Style, highlight bool, showcursor bool) {
|
||||
if nColsBeforeStart <= 0 && vloc.Y >= 0 {
|
||||
if highlight {
|
||||
if w.Buf.HighlightSearch && w.Buf.SearchMatch(bloc) {
|
||||
// returns the rune to be drawn, style of it and if the bg should be preserved
|
||||
getRuneStyle := func(r rune, style tcell.Style, showoffset int, linex int, isplaceholder bool) (rune, tcell.Style, bool) {
|
||||
if nColsBeforeStart > 0 || vloc.Y < 0 || isplaceholder {
|
||||
return r, style, false
|
||||
}
|
||||
|
||||
for _, mb := range matchingBraces {
|
||||
if mb.X == bloc.X && mb.Y == bloc.Y {
|
||||
if b.Settings["matchbracestyle"].(string) == "highlight" {
|
||||
if s, ok := config.Colorscheme["match-brace"]; ok {
|
||||
return r, s, false
|
||||
} else {
|
||||
return r, style.Reverse(true), false
|
||||
}
|
||||
} else {
|
||||
return r, style.Underline(true), false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if r != '\t' && r != ' ' {
|
||||
return r, style, false
|
||||
}
|
||||
|
||||
var indentrunes []rune
|
||||
switch r {
|
||||
case '\t':
|
||||
if bloc.X < leadingwsEnd && indenttabchars != "" {
|
||||
indentrunes = []rune(indenttabchars)
|
||||
} else {
|
||||
indentrunes = []rune(tabchars)
|
||||
}
|
||||
case ' ':
|
||||
if linex%tabsize == 0 && bloc.X < leadingwsEnd && indentspacechars != "" {
|
||||
indentrunes = []rune(indentspacechars)
|
||||
} else {
|
||||
indentrunes = []rune(spacechars)
|
||||
}
|
||||
}
|
||||
|
||||
var drawrune rune
|
||||
if showoffset < len(indentrunes) {
|
||||
drawrune = indentrunes[showoffset]
|
||||
} else {
|
||||
// use space if no showchars or after we showed showchars
|
||||
drawrune = ' '
|
||||
}
|
||||
|
||||
if s, ok := config.Colorscheme["indent-char"]; ok {
|
||||
fg, _, _ := s.Decompose()
|
||||
style = style.Foreground(fg)
|
||||
}
|
||||
|
||||
preservebg := false
|
||||
if b.Settings["hltaberrors"].(bool) && bloc.X < leadingwsEnd {
|
||||
if s, ok := config.Colorscheme["tab-error"]; ok {
|
||||
if b.Settings["tabstospaces"].(bool) && r == '\t' {
|
||||
fg, _, _ := s.Decompose()
|
||||
style = style.Background(fg)
|
||||
preservebg = true
|
||||
} else if !b.Settings["tabstospaces"].(bool) && r == ' ' {
|
||||
fg, _, _ := s.Decompose()
|
||||
style = style.Background(fg)
|
||||
preservebg = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if b.Settings["hltrailingws"].(bool) {
|
||||
if s, ok := config.Colorscheme["trailingws"]; ok {
|
||||
if bloc.X >= trailingwsStart && bloc.X < blineLen {
|
||||
hl := true
|
||||
for _, c := range cursors {
|
||||
if c.NewTrailingWsY == bloc.Y {
|
||||
hl = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if hl {
|
||||
fg, _, _ := s.Decompose()
|
||||
style = style.Background(fg)
|
||||
preservebg = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return drawrune, style, preservebg
|
||||
}
|
||||
|
||||
draw := func(r rune, combc []rune, style tcell.Style, highlight bool, showcursor bool, preservebg bool) {
|
||||
defer func() {
|
||||
if nColsBeforeStart <= 0 {
|
||||
vloc.X++
|
||||
}
|
||||
nColsBeforeStart--
|
||||
}()
|
||||
|
||||
if nColsBeforeStart > 0 || vloc.Y < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if highlight {
|
||||
if w.Buf.HighlightSearch && w.Buf.SearchMatch(bloc) {
|
||||
style = config.DefStyle.Reverse(true)
|
||||
if s, ok := config.Colorscheme["hlsearch"]; ok {
|
||||
style = s
|
||||
}
|
||||
}
|
||||
|
||||
_, origBg, _ := style.Decompose()
|
||||
_, defBg, _ := config.DefStyle.Decompose()
|
||||
|
||||
// syntax or hlsearch highlighting with non-default background takes precedence
|
||||
// over cursor-line and color-column
|
||||
if !preservebg && origBg != defBg {
|
||||
preservebg = true
|
||||
}
|
||||
|
||||
for _, c := range cursors {
|
||||
if c.HasSelection() &&
|
||||
(bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
|
||||
bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {
|
||||
// The current character is selected
|
||||
style = config.DefStyle.Reverse(true)
|
||||
if s, ok := config.Colorscheme["hlsearch"]; ok {
|
||||
|
||||
if s, ok := config.Colorscheme["selection"]; ok {
|
||||
style = s
|
||||
}
|
||||
}
|
||||
|
||||
_, origBg, _ := style.Decompose()
|
||||
_, defBg, _ := config.DefStyle.Decompose()
|
||||
|
||||
// syntax or hlsearch highlighting with non-default background takes precedence
|
||||
// over cursor-line and color-column
|
||||
dontOverrideBackground := origBg != defBg
|
||||
|
||||
if b.Settings["hltaberrors"].(bool) {
|
||||
if s, ok := config.Colorscheme["tab-error"]; ok {
|
||||
isTab := (r == '\t') || (r == ' ' && !showcursor)
|
||||
if (b.Settings["tabstospaces"].(bool) && isTab) ||
|
||||
(!b.Settings["tabstospaces"].(bool) && bloc.X < leadingwsEnd && r == ' ' && !isTab) {
|
||||
fg, _, _ := s.Decompose()
|
||||
style = style.Background(fg)
|
||||
dontOverrideBackground = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if b.Settings["hltrailingws"].(bool) {
|
||||
if s, ok := config.Colorscheme["trailingws"]; ok {
|
||||
if bloc.X >= trailingwsStart && bloc.X < blineLen {
|
||||
hl := true
|
||||
for _, c := range cursors {
|
||||
if c.NewTrailingWsY == bloc.Y {
|
||||
hl = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if hl {
|
||||
fg, _, _ := s.Decompose()
|
||||
style = style.Background(fg)
|
||||
dontOverrideBackground = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range cursors {
|
||||
if c.HasSelection() &&
|
||||
(bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
|
||||
bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {
|
||||
// The current character is selected
|
||||
style = config.DefStyle.Reverse(true)
|
||||
|
||||
if s, ok := config.Colorscheme["selection"]; ok {
|
||||
style = s
|
||||
}
|
||||
}
|
||||
|
||||
if b.Settings["cursorline"].(bool) && w.active && !dontOverrideBackground &&
|
||||
!c.HasSelection() && c.Y == bloc.Y {
|
||||
if s, ok := config.Colorscheme["cursor-line"]; ok {
|
||||
fg, _, _ := s.Decompose()
|
||||
style = style.Background(fg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, m := range b.Messages {
|
||||
if bloc.GreaterEqual(m.Start) && bloc.LessThan(m.End) ||
|
||||
bloc.LessThan(m.End) && bloc.GreaterEqual(m.Start) {
|
||||
style = style.Underline(true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if r == '\t' {
|
||||
indentrunes := []rune(b.Settings["indentchar"].(string))
|
||||
// if empty indentchar settings, use space
|
||||
if len(indentrunes) == 0 {
|
||||
indentrunes = []rune{' '}
|
||||
}
|
||||
|
||||
r = indentrunes[0]
|
||||
if s, ok := config.Colorscheme["indent-char"]; ok && r != ' ' {
|
||||
fg, _, _ := s.Decompose()
|
||||
style = style.Foreground(fg)
|
||||
}
|
||||
}
|
||||
|
||||
if s, ok := config.Colorscheme["color-column"]; ok {
|
||||
if colorcolumn != 0 && vloc.X-w.gutterOffset+w.StartCol == colorcolumn && !dontOverrideBackground {
|
||||
if b.Settings["cursorline"].(bool) && w.active && !preservebg &&
|
||||
!c.HasSelection() && c.Y == bloc.Y {
|
||||
if s, ok := config.Colorscheme["cursor-line"]; ok {
|
||||
fg, _, _ := s.Decompose()
|
||||
style = style.Background(fg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, mb := range matchingBraces {
|
||||
if mb.X == bloc.X && mb.Y == bloc.Y {
|
||||
if b.Settings["matchbracestyle"].(string) == "highlight" {
|
||||
if s, ok := config.Colorscheme["match-brace"]; ok {
|
||||
style = s
|
||||
} else {
|
||||
style = style.Reverse(true)
|
||||
}
|
||||
} else {
|
||||
style = style.Underline(true)
|
||||
}
|
||||
}
|
||||
for _, m := range b.Messages {
|
||||
if bloc.GreaterEqual(m.Start) && bloc.LessThan(m.End) ||
|
||||
bloc.LessThan(m.End) && bloc.GreaterEqual(m.Start) {
|
||||
style = style.Underline(true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, combc, style)
|
||||
|
||||
if showcursor {
|
||||
for _, c := range cursors {
|
||||
if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
|
||||
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
|
||||
}
|
||||
if s, ok := config.Colorscheme["color-column"]; ok {
|
||||
if colorcolumn != 0 && vloc.X-w.gutterOffset+w.StartCol == colorcolumn && !preservebg {
|
||||
fg, _, _ := s.Decompose()
|
||||
style = style.Background(fg)
|
||||
}
|
||||
}
|
||||
}
|
||||
if nColsBeforeStart <= 0 {
|
||||
vloc.X++
|
||||
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, combc, style)
|
||||
|
||||
if showcursor {
|
||||
for _, c := range cursors {
|
||||
if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
|
||||
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
nColsBeforeStart--
|
||||
}
|
||||
|
||||
wrap := func() {
|
||||
vloc.X = 0
|
||||
if w.hasMessage {
|
||||
w.drawGutter(&vloc, &bloc)
|
||||
}
|
||||
if b.Settings["diffgutter"].(bool) {
|
||||
w.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)
|
||||
}
|
||||
|
||||
// This will draw an empty line number because the current line is wrapped
|
||||
if b.Settings["ruler"].(bool) {
|
||||
w.drawLineNum(lineNumStyle, true, &vloc, &bloc)
|
||||
if vloc.Y >= 0 {
|
||||
if w.hasMessage {
|
||||
w.drawGutter(&vloc, &bloc)
|
||||
}
|
||||
if b.Settings["diffgutter"].(bool) {
|
||||
w.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)
|
||||
}
|
||||
|
||||
// This will draw an empty line number because the current line is wrapped
|
||||
if b.Settings["ruler"].(bool) {
|
||||
w.drawLineNum(lineNumStyle, true, &vloc, &bloc)
|
||||
}
|
||||
} else {
|
||||
vloc.X = w.gutterOffset
|
||||
}
|
||||
}
|
||||
|
||||
@@ -657,6 +731,7 @@ func (w *BufWindow) displayBuffer() {
|
||||
|
||||
width := 0
|
||||
|
||||
linex := totalwidth
|
||||
switch r {
|
||||
case '\t':
|
||||
ts := tabsize - (totalwidth % tabsize)
|
||||
@@ -681,7 +756,7 @@ func (w *BufWindow) displayBuffer() {
|
||||
// If a word (or just a wide rune) does not fit in the window
|
||||
if vloc.X+wordwidth > maxWidth && vloc.X > w.gutterOffset {
|
||||
for vloc.X < maxWidth {
|
||||
draw(' ', nil, config.DefStyle, false, false)
|
||||
draw(' ', nil, config.DefStyle, false, false, true)
|
||||
}
|
||||
|
||||
// We either stop or we wrap to draw the word in the next line
|
||||
@@ -697,18 +772,17 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
|
||||
for _, r := range word {
|
||||
draw(r.r, r.combc, r.style, true, true)
|
||||
drawrune, drawstyle, preservebg := getRuneStyle(r.r, r.style, 0, linex, false)
|
||||
draw(drawrune, r.combc, drawstyle, true, true, preservebg)
|
||||
|
||||
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
|
||||
if r.width > 1 {
|
||||
char := ' '
|
||||
if r.r != '\t' {
|
||||
char = '@'
|
||||
}
|
||||
|
||||
for i := 1; i < r.width; i++ {
|
||||
draw(char, nil, r.style, true, false)
|
||||
// Draw extra characters for tabs or wide runes
|
||||
for i := 1; i < r.width; i++ {
|
||||
if r.r == '\t' {
|
||||
drawrune, drawstyle, preservebg = getRuneStyle('\t', r.style, i, linex+i, false)
|
||||
} else {
|
||||
drawrune, drawstyle, preservebg = getRuneStyle(' ', r.style, i, linex+i, true)
|
||||
}
|
||||
draw(drawrune, nil, drawstyle, true, false, preservebg)
|
||||
}
|
||||
bloc.X++
|
||||
}
|
||||
@@ -753,7 +827,8 @@ func (w *BufWindow) displayBuffer() {
|
||||
|
||||
if vloc.X != maxWidth {
|
||||
// Display newline within a selection
|
||||
draw(' ', nil, config.DefStyle, true, true)
|
||||
drawrune, drawstyle, preservebg := getRuneStyle(' ', config.DefStyle, 0, totalwidth, true)
|
||||
draw(drawrune, nil, drawstyle, true, true, preservebg)
|
||||
}
|
||||
|
||||
bloc.X = w.StartCol
|
||||
|
||||
@@ -2,12 +2,12 @@ package display
|
||||
|
||||
import (
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/info"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/micro-editor/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/info"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
)
|
||||
|
||||
type InfoWindow struct {
|
||||
@@ -122,16 +122,8 @@ func (i *InfoWindow) displayBuffer() {
|
||||
|
||||
}
|
||||
|
||||
rw := runewidth.RuneWidth(r)
|
||||
for j := 0; j < rw; j++ {
|
||||
c := r
|
||||
if j > 0 {
|
||||
c = ' '
|
||||
combc = nil
|
||||
}
|
||||
screen.SetContent(vlocX, i.Y, c, combc, style)
|
||||
}
|
||||
vlocX++
|
||||
screen.SetContent(vlocX, i.Y, r, combc, style)
|
||||
vlocX += runewidth.RuneWidth(r)
|
||||
}
|
||||
nColsBeforeStart--
|
||||
}
|
||||
@@ -142,29 +134,22 @@ func (i *InfoWindow) displayBuffer() {
|
||||
curBX := blocX
|
||||
r, combc, size := util.DecodeCharacter(line)
|
||||
|
||||
draw(r, combc, i.defStyle())
|
||||
|
||||
width := 0
|
||||
|
||||
char := ' '
|
||||
switch r {
|
||||
case '\t':
|
||||
ts := tabsize - (totalwidth % tabsize)
|
||||
width = ts
|
||||
width = tabsize - (totalwidth % tabsize)
|
||||
for j := 0; j < width; j++ {
|
||||
draw(' ', nil, i.defStyle())
|
||||
}
|
||||
default:
|
||||
width = runewidth.RuneWidth(r)
|
||||
char = '@'
|
||||
draw(r, combc, i.defStyle())
|
||||
}
|
||||
|
||||
blocX++
|
||||
line = line[size:]
|
||||
|
||||
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
|
||||
if width > 1 {
|
||||
for j := 1; j < width; j++ {
|
||||
draw(char, nil, i.defStyle())
|
||||
}
|
||||
}
|
||||
if activeC.X == curBX {
|
||||
screen.ShowCursor(curVX, i.Y)
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ package display
|
||||
|
||||
import (
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/micro-editor/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// SLoc represents a vertical scrolling location, i.e. a location of a visual line
|
||||
@@ -291,13 +291,7 @@ func (w *BufWindow) diff(s1, s2 SLoc) int {
|
||||
// within the buffer boundaries.
|
||||
func (w *BufWindow) Scroll(s SLoc, n int) SLoc {
|
||||
if !w.Buf.Settings["softwrap"].(bool) {
|
||||
s.Line += n
|
||||
if s.Line < 0 {
|
||||
s.Line = 0
|
||||
}
|
||||
if s.Line > w.Buf.LinesNum()-1 {
|
||||
s.Line = w.Buf.LinesNum() - 1
|
||||
}
|
||||
s.Line = util.Clamp(s.Line+n, 0, w.Buf.LinesNum()-1)
|
||||
return s
|
||||
}
|
||||
return w.scroll(s, n)
|
||||
|
||||
@@ -10,12 +10,12 @@ import (
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/micro-editor/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
ulua "github.com/micro-editor/micro/v2/internal/lua"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// StatusLine represents the information line at the bottom
|
||||
@@ -47,6 +47,12 @@ var statusInfo = map[string]func(*buffer.Buffer) string{
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"overwrite": func(b *buffer.Buffer) string {
|
||||
if b.OverwriteMode && !b.Type.Readonly {
|
||||
return "[ovwr] "
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"lines": func(b *buffer.Buffer) string {
|
||||
return strconv.Itoa(b.LinesNum())
|
||||
},
|
||||
@@ -90,7 +96,7 @@ func NewStatusLine(win *BufWindow) *StatusLine {
|
||||
}
|
||||
|
||||
// FindOpt finds a given option in the current buffer's settings
|
||||
func (s *StatusLine) FindOpt(opt string) interface{} {
|
||||
func (s *StatusLine) FindOpt(opt string) any {
|
||||
if val, ok := s.win.Buf.Settings[opt]; ok {
|
||||
return val
|
||||
}
|
||||
@@ -146,7 +152,7 @@ func (s *StatusLine) Display() {
|
||||
name := match[2 : len(match)-1]
|
||||
if bytes.HasPrefix(name, []byte("opt")) {
|
||||
option := name[4:]
|
||||
return []byte(fmt.Sprint(s.FindOpt(string(option))))
|
||||
return fmt.Append(nil, s.FindOpt(string(option)))
|
||||
} else if bytes.HasPrefix(name, []byte("bind")) {
|
||||
binding := string(name[5:])
|
||||
for k, v := range config.Bindings["buffer"] {
|
||||
|
||||
@@ -2,11 +2,11 @@ package display
|
||||
|
||||
import (
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/micro-editor/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
)
|
||||
|
||||
type TabWindow struct {
|
||||
@@ -112,10 +112,10 @@ func (w *TabWindow) Display() {
|
||||
}
|
||||
return tabBarStyle, tabBarActiveStyle
|
||||
}
|
||||
|
||||
|
||||
draw := func(r rune, n int, active bool, reversed bool) {
|
||||
tabBarStyle, tabBarActiveStyle := reverseStyles(reversed)
|
||||
|
||||
|
||||
style := tabBarStyle
|
||||
if active {
|
||||
style = tabBarActiveStyle
|
||||
@@ -147,15 +147,15 @@ func (w *TabWindow) Display() {
|
||||
} else {
|
||||
draw(' ', 1, false, tabCharHighlight)
|
||||
}
|
||||
|
||||
|
||||
for _, c := range n {
|
||||
draw(c, 1, i == w.active, tabCharHighlight)
|
||||
}
|
||||
|
||||
|
||||
if i == len(w.Names)-1 {
|
||||
done = true
|
||||
}
|
||||
|
||||
|
||||
if i == w.active {
|
||||
draw(']', 1, true, tabCharHighlight)
|
||||
draw(' ', 2, true, globalTabReverse)
|
||||
@@ -163,7 +163,7 @@ func (w *TabWindow) Display() {
|
||||
draw(' ', 1, false, tabCharHighlight)
|
||||
draw(' ', 2, false, globalTabReverse)
|
||||
}
|
||||
|
||||
|
||||
if x >= w.Width {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/zyedidia/terminal"
|
||||
"github.com/micro-editor/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/shell"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
"github.com/micro-editor/terminal"
|
||||
)
|
||||
|
||||
type TermWindow struct {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/micro/v2/internal/views"
|
||||
"github.com/micro-editor/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
"github.com/micro-editor/micro/v2/internal/views"
|
||||
)
|
||||
|
||||
type UIWindow struct {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/buffer"
|
||||
)
|
||||
|
||||
type View struct {
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package info
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// LoadHistory attempts to load user history from configDir/buffers/history
|
||||
@@ -17,24 +21,23 @@ func (i *InfoBuf) LoadHistory() {
|
||||
if config.GetGlobalOption("savehistory").(bool) {
|
||||
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", "history"))
|
||||
var decodedMap map[string][]string
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
decoder := gob.NewDecoder(file)
|
||||
err = decoder.Decode(&decodedMap)
|
||||
|
||||
if err != nil {
|
||||
i.Error("Error loading history:", err)
|
||||
return
|
||||
if err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
i.Error("Error loading history: ", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
err = gob.NewDecoder(file).Decode(&decodedMap)
|
||||
if err != nil {
|
||||
i.Error("Error decoding history: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
if decodedMap != nil {
|
||||
i.History = decodedMap
|
||||
} else {
|
||||
i.History = make(map[string][]string)
|
||||
}
|
||||
} else {
|
||||
i.History = make(map[string][]string)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,16 +52,18 @@ func (i *InfoBuf) SaveHistory() {
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Create(filepath.Join(config.ConfigDir, "buffers", "history"))
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
encoder := gob.NewEncoder(file)
|
||||
var buf bytes.Buffer
|
||||
err := gob.NewEncoder(&buf).Encode(i.History)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error encoding history: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = encoder.Encode(i.History)
|
||||
if err != nil {
|
||||
i.Error("Error saving history:", err)
|
||||
return
|
||||
}
|
||||
filename := filepath.Join(config.ConfigDir, "buffers", "history")
|
||||
err = util.SafeWrite(filename, buf.Bytes(), true)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error saving history: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package info
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/buffer"
|
||||
)
|
||||
|
||||
// The InfoBuf displays messages and other info at the bottom of the screen.
|
||||
@@ -55,7 +55,7 @@ func (i *InfoBuf) Close() {
|
||||
}
|
||||
|
||||
// Message sends a message to the user
|
||||
func (i *InfoBuf) Message(msg ...interface{}) {
|
||||
func (i *InfoBuf) Message(msg ...any) {
|
||||
// only display a new message if there isn't an active prompt
|
||||
// this is to prevent overwriting an existing prompt to the user
|
||||
if !i.HasPrompt {
|
||||
@@ -67,7 +67,7 @@ func (i *InfoBuf) Message(msg ...interface{}) {
|
||||
}
|
||||
|
||||
// GutterMessage displays a message and marks it as a gutter message
|
||||
func (i *InfoBuf) GutterMessage(msg ...interface{}) {
|
||||
func (i *InfoBuf) GutterMessage(msg ...any) {
|
||||
i.Message(msg...)
|
||||
i.HasGutter = true
|
||||
}
|
||||
@@ -79,7 +79,7 @@ func (i *InfoBuf) ClearGutter() {
|
||||
}
|
||||
|
||||
// Error sends an error message to the user
|
||||
func (i *InfoBuf) Error(msg ...interface{}) {
|
||||
func (i *InfoBuf) Error(msg ...any) {
|
||||
// only display a new message if there isn't an active prompt
|
||||
// this is to prevent overwriting an existing prompt to the user
|
||||
if !i.HasPrompt {
|
||||
|
||||
@@ -125,6 +125,7 @@ func importIo() *lua.LTable {
|
||||
L.SetField(pkg, "MultiWriter", luar.New(L, io.MultiWriter))
|
||||
L.SetField(pkg, "NewSectionReader", luar.New(L, io.NewSectionReader))
|
||||
L.SetField(pkg, "Pipe", luar.New(L, io.Pipe))
|
||||
L.SetField(pkg, "ReadAll", luar.New(L, io.ReadAll))
|
||||
L.SetField(pkg, "ReadAtLeast", luar.New(L, io.ReadAtLeast))
|
||||
L.SetField(pkg, "ReadFull", luar.New(L, io.ReadFull))
|
||||
L.SetField(pkg, "TeeReader", luar.New(L, io.TeeReader))
|
||||
@@ -370,6 +371,8 @@ func importOs() *lua.LTable {
|
||||
L.SetField(pkg, "PathListSeparator", luar.New(L, os.PathListSeparator))
|
||||
L.SetField(pkg, "PathSeparator", luar.New(L, os.PathSeparator))
|
||||
L.SetField(pkg, "Pipe", luar.New(L, os.Pipe))
|
||||
L.SetField(pkg, "ReadDir", luar.New(L, os.ReadDir))
|
||||
L.SetField(pkg, "ReadFile", luar.New(L, os.ReadFile))
|
||||
L.SetField(pkg, "Readlink", luar.New(L, os.Readlink))
|
||||
L.SetField(pkg, "Remove", luar.New(L, os.Remove))
|
||||
L.SetField(pkg, "RemoveAll", luar.New(L, os.RemoveAll))
|
||||
@@ -388,6 +391,7 @@ func importOs() *lua.LTable {
|
||||
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))
|
||||
L.SetField(pkg, "WriteFile", luar.New(L, os.WriteFile))
|
||||
|
||||
return pkg
|
||||
}
|
||||
@@ -423,7 +427,6 @@ func importPath() *lua.LTable {
|
||||
func importFilePath() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Join", luar.New(L, filepath.Join))
|
||||
L.SetField(pkg, "Abs", luar.New(L, filepath.Abs))
|
||||
L.SetField(pkg, "Base", luar.New(L, filepath.Base))
|
||||
L.SetField(pkg, "Clean", luar.New(L, filepath.Clean))
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// 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{}) {
|
||||
func TermMessage(msg ...any) {
|
||||
screenb := TempFini()
|
||||
|
||||
fmt.Println(msg...)
|
||||
|
||||
@@ -6,9 +6,8 @@ import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/micro-editor/micro/v2/internal/config"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
)
|
||||
|
||||
// Screen is the tcell screen we use to draw to the terminal
|
||||
@@ -33,6 +32,12 @@ var lock sync.Mutex
|
||||
// written to even if no event user event has occurred
|
||||
var drawChan chan bool
|
||||
|
||||
// rawSeq is the list of raw escape sequences that are bound to some actions
|
||||
// via keybindings and thus should be parsed by tcell. We need to register
|
||||
// them in tcell every time we reinitialize the screen, so we need to remember
|
||||
// them in a list
|
||||
var rawSeq = make([]string, 0)
|
||||
|
||||
// Lock locks the screen lock
|
||||
func Lock() {
|
||||
lock.Lock()
|
||||
@@ -84,7 +89,7 @@ func ShowFakeCursor(x, y int) {
|
||||
}
|
||||
|
||||
func UseFake() bool {
|
||||
return util.FakeCursor || config.GetGlobalOption("fakecursor").(bool)
|
||||
return config.GetGlobalOption("fakecursor").(bool)
|
||||
}
|
||||
|
||||
// ShowFakeCursorMulti is the same as ShowFakeCursor except it does not
|
||||
@@ -121,6 +126,34 @@ func SetContent(x, y int, mainc rune, combc []rune, style tcell.Style) {
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRawSeq registers a raw escape sequence that should be parsed by tcell
|
||||
func RegisterRawSeq(r string) {
|
||||
for _, seq := range rawSeq {
|
||||
if seq == r {
|
||||
return
|
||||
}
|
||||
}
|
||||
rawSeq = append(rawSeq, r)
|
||||
|
||||
if Screen != nil {
|
||||
Screen.RegisterRawSeq(r)
|
||||
}
|
||||
}
|
||||
|
||||
// UnregisterRawSeq unregisters a raw escape sequence that should be parsed by tcell
|
||||
func UnregisterRawSeq(r string) {
|
||||
for i, seq := range rawSeq {
|
||||
if seq == r {
|
||||
rawSeq[i] = rawSeq[len(rawSeq)-1]
|
||||
rawSeq = rawSeq[:len(rawSeq)-1]
|
||||
}
|
||||
}
|
||||
|
||||
if Screen != nil {
|
||||
Screen.UnregisterRawSeq(r)
|
||||
}
|
||||
}
|
||||
|
||||
// TempFini shuts the screen down temporarily
|
||||
func TempFini() bool {
|
||||
screenWasNil := Screen == nil
|
||||
@@ -150,10 +183,13 @@ func Init() error {
|
||||
drawChan = make(chan bool, 8)
|
||||
|
||||
// Should we enable true color?
|
||||
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
|
||||
|
||||
if !truecolor {
|
||||
truecolor := config.GetGlobalOption("truecolor").(string)
|
||||
if truecolor == "on" || (truecolor == "auto" && os.Getenv("MICRO_TRUECOLOR") == "1") {
|
||||
os.Setenv("TCELL_TRUECOLOR", "enable")
|
||||
} else if truecolor == "off" {
|
||||
os.Setenv("TCELL_TRUECOLOR", "disable")
|
||||
} else {
|
||||
// For "auto", tcell already autodetects truecolor by default
|
||||
}
|
||||
|
||||
var oldTerm string
|
||||
@@ -195,6 +231,10 @@ func Init() error {
|
||||
Screen.EnableMouse()
|
||||
}
|
||||
|
||||
for _, r := range rawSeq {
|
||||
Screen.RegisterRawSeq(r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -24,17 +24,17 @@ func init() {
|
||||
// JobFunction is a representation of a job (this data structure is what is loaded
|
||||
// into the jobs channel)
|
||||
type JobFunction struct {
|
||||
Function func(string, []interface{})
|
||||
Function func(string, []any)
|
||||
Output string
|
||||
Args []interface{}
|
||||
Args []any
|
||||
}
|
||||
|
||||
// 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{}
|
||||
callback func(string, []any)
|
||||
args []any
|
||||
}
|
||||
|
||||
// Job stores the executing command for the job, and the stdin pipe
|
||||
@@ -53,13 +53,13 @@ func (f *CallbackFile) Write(data []byte) (int, error) {
|
||||
|
||||
// JobStart starts a shell command in the background with the given callbacks
|
||||
// It returns an *exec.Cmd as the job id
|
||||
func JobStart(cmd string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *Job {
|
||||
func JobStart(cmd string, onStdout, onStderr, onExit func(string, []any), userargs ...any) *Job {
|
||||
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 func(string, []any), userargs ...any) *Job {
|
||||
// Set up everything correctly if the functions have been provided
|
||||
proc := exec.Command(cmdName, cmdArgs...)
|
||||
var outbuf bytes.Buffer
|
||||
@@ -78,8 +78,10 @@ func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(
|
||||
go func() {
|
||||
// Run the process in the background and create the onExit callback
|
||||
proc.Run()
|
||||
jobFunc := JobFunction{onExit, outbuf.String(), userargs}
|
||||
Jobs <- jobFunc
|
||||
if onExit != nil {
|
||||
jobFunc := JobFunction{onExit, outbuf.String(), userargs}
|
||||
Jobs <- jobFunc
|
||||
}
|
||||
}()
|
||||
|
||||
return &Job{proc, stdin}
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"os/signal"
|
||||
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// ExecCommand executes a command using exec
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/terminal"
|
||||
"github.com/micro-editor/micro/v2/internal/buffer"
|
||||
"github.com/micro-editor/micro/v2/internal/screen"
|
||||
"github.com/micro-editor/terminal"
|
||||
)
|
||||
|
||||
type TermType int
|
||||
@@ -69,7 +69,7 @@ func (t *Terminal) GetSelection(width int) string {
|
||||
}
|
||||
|
||||
// Start begins a new command in this terminal with a given view
|
||||
func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
|
||||
func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback func(out string, userargs []any), userargs []any) error {
|
||||
if len(execCmd) <= 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -130,7 +130,7 @@ func (t *Terminal) Close() {
|
||||
if t.getOutput {
|
||||
if t.callback != nil {
|
||||
Jobs <- JobFunction{
|
||||
Function: func(out string, args []interface{}) {
|
||||
Function: func(out string, args []any) {
|
||||
t.callback(out)
|
||||
},
|
||||
Output: t.output.String(),
|
||||
|
||||
@@ -29,11 +29,27 @@ func isMark(r rune) bool {
|
||||
// DecodeCharacter returns the next character from an array of bytes
|
||||
// A character is a rune along with any accompanying combining runes
|
||||
func DecodeCharacter(b []byte) (rune, []rune, int) {
|
||||
combc, size := DecodeCombinedCharacter(b)
|
||||
return combc[0], combc[1:], size
|
||||
}
|
||||
|
||||
// DecodeCharacterInString returns the next character from a string
|
||||
// A character is a rune along with any accompanying combining runes
|
||||
func DecodeCharacterInString(str string) (rune, []rune, int) {
|
||||
combc, size := DecodeCombinedCharacterInString(str)
|
||||
return combc[0], combc[1:], size
|
||||
}
|
||||
|
||||
// DecodeCombinedCharacter returns the next combined character
|
||||
// from an array of bytes
|
||||
// A character is a rune along with any accompanying combining runes
|
||||
func DecodeCombinedCharacter(b []byte) ([]rune, int) {
|
||||
var combc []rune
|
||||
r, size := utf8.DecodeRune(b)
|
||||
combc = append(combc, r)
|
||||
b = b[size:]
|
||||
c, s := utf8.DecodeRune(b)
|
||||
|
||||
var combc []rune
|
||||
for isMark(c) {
|
||||
combc = append(combc, c)
|
||||
size += s
|
||||
@@ -42,17 +58,18 @@ func DecodeCharacter(b []byte) (rune, []rune, int) {
|
||||
c, s = utf8.DecodeRune(b)
|
||||
}
|
||||
|
||||
return r, combc, size
|
||||
return combc, size
|
||||
}
|
||||
|
||||
// DecodeCharacterInString returns the next character from a string
|
||||
// A character is a rune along with any accompanying combining runes
|
||||
func DecodeCharacterInString(str string) (rune, []rune, int) {
|
||||
// DecodeCombinedCharacterInString is the same as DecodeCombinedCharacter
|
||||
// but for strings
|
||||
func DecodeCombinedCharacterInString(str string) ([]rune, int) {
|
||||
var combc []rune
|
||||
r, size := utf8.DecodeRuneInString(str)
|
||||
combc = append(combc, r)
|
||||
str = str[size:]
|
||||
c, s := utf8.DecodeRuneInString(str)
|
||||
|
||||
var combc []rune
|
||||
for isMark(c) {
|
||||
combc = append(combc, c)
|
||||
size += s
|
||||
@@ -61,7 +78,7 @@ func DecodeCharacterInString(str string) (rune, []rune, int) {
|
||||
c, s = utf8.DecodeRuneInString(str)
|
||||
}
|
||||
|
||||
return r, combc, size
|
||||
return combc, size
|
||||
}
|
||||
|
||||
// CharacterCount returns the number of characters in a byte array
|
||||
|
||||
@@ -3,10 +3,13 @@ package util
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
@@ -35,16 +38,51 @@ var (
|
||||
CompileDate = "Unknown"
|
||||
// Debug logging
|
||||
Debug = "OFF"
|
||||
// FakeCursor is used to disable the terminal cursor and have micro
|
||||
// draw its own (enabled for windows consoles where the cursor is slow)
|
||||
FakeCursor = false
|
||||
|
||||
// Stdout is a buffer that is written to stdout when micro closes
|
||||
Stdout *bytes.Buffer
|
||||
// Sigterm is a channel where micro exits when written
|
||||
Sigterm chan os.Signal
|
||||
|
||||
// To be used for fails on (over-)write with safe writes
|
||||
ErrOverwrite = OverwriteError{}
|
||||
)
|
||||
|
||||
// To be used for file writes before umask is applied
|
||||
const FileMode os.FileMode = 0666
|
||||
|
||||
const BackupSuffix = ".micro-backup"
|
||||
|
||||
const OverwriteFailMsg = `An error occurred while writing to the file:
|
||||
|
||||
%s
|
||||
|
||||
The file may be corrupted now. The good news is that it has been
|
||||
successfully backed up. Next time you open this file with Micro,
|
||||
Micro will ask if you want to recover it from the backup.
|
||||
|
||||
The backup path is:
|
||||
|
||||
%s`
|
||||
|
||||
// OverwriteError is a custom error to add additional information
|
||||
type OverwriteError struct {
|
||||
What error
|
||||
BackupName string
|
||||
}
|
||||
|
||||
func (e OverwriteError) Error() string {
|
||||
return fmt.Sprintf(OverwriteFailMsg, e.What, e.BackupName)
|
||||
}
|
||||
|
||||
func (e OverwriteError) Is(target error) bool {
|
||||
return target == ErrOverwrite
|
||||
}
|
||||
|
||||
func (e OverwriteError) Unwrap() error {
|
||||
return e.What
|
||||
}
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
SemVersion, err = semver.Make(Version)
|
||||
@@ -52,10 +90,6 @@ func init() {
|
||||
fmt.Println("Invalid version: ", Version, err)
|
||||
}
|
||||
|
||||
_, wt := os.LookupEnv("WT_SESSION")
|
||||
if runtime.GOOS == "windows" && !wt {
|
||||
FakeCursor = true
|
||||
}
|
||||
Stdout = new(bytes.Buffer)
|
||||
}
|
||||
|
||||
@@ -233,18 +267,6 @@ func IsNonWordChar(r rune) bool {
|
||||
return !IsWordChar(r)
|
||||
}
|
||||
|
||||
// IsUpperWordChar returns whether or not a rune is an 'upper word character'
|
||||
// Upper word characters are defined as numbers, upper-case letters or sub-word delimiters
|
||||
func IsUpperWordChar(r rune) bool {
|
||||
return IsUpperAlphanumeric(r) || IsSubwordDelimiter(r)
|
||||
}
|
||||
|
||||
// IsLowerWordChar returns whether or not a rune is a 'lower word character'
|
||||
// Lower word characters are defined as numbers, lower-case letters or sub-word delimiters
|
||||
func IsLowerWordChar(r rune) bool {
|
||||
return IsLowerAlphanumeric(r) || IsSubwordDelimiter(r)
|
||||
}
|
||||
|
||||
// IsSubwordDelimiter returns whether or not a rune is a 'sub-word delimiter character'
|
||||
// i.e. is considered a part of the word and is used as a delimiter between sub-words of the word.
|
||||
// For now the only sub-word delimiter character is '_'.
|
||||
@@ -332,6 +354,28 @@ func RunePos(b []byte, i int) int {
|
||||
return CharacterCount(b[:i])
|
||||
}
|
||||
|
||||
// IndexAnyUnquoted returns the first position in s of a character from chars.
|
||||
// Escaped (with backslash) and quoted (with single or double quotes) characters
|
||||
// are ignored. Returns -1 if not successful
|
||||
func IndexAnyUnquoted(s, chars string) int {
|
||||
var e bool
|
||||
var q rune
|
||||
for i, r := range s {
|
||||
if e {
|
||||
e = false
|
||||
} else if (q == 0 || q == '"') && r == '\\' {
|
||||
e = true
|
||||
} else if r == q {
|
||||
q = 0
|
||||
} else if q == 0 && (r == '\'' || r == '"') {
|
||||
q = r
|
||||
} else if q == 0 && strings.IndexRune(chars, r) >= 0 {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// MakeRelative will attempt to make a relative path between path and base
|
||||
func MakeRelative(path, base string) (string, error) {
|
||||
if len(path) > 0 {
|
||||
@@ -398,8 +442,17 @@ func GetModTime(path string) (time.Time, error) {
|
||||
return info.ModTime(), nil
|
||||
}
|
||||
|
||||
// EscapePath replaces every path separator in a given path with a %
|
||||
func EscapePath(path string) string {
|
||||
func HashStringMd5(str string) string {
|
||||
return fmt.Sprintf("%x", md5.Sum([]byte(str)))
|
||||
}
|
||||
|
||||
// EscapePathUrl encodes the path in URL query form
|
||||
func EscapePathUrl(path string) string {
|
||||
return url.QueryEscape(filepath.ToSlash(path))
|
||||
}
|
||||
|
||||
// EscapePathLegacy replaces every path separator in a given path with a %
|
||||
func EscapePathLegacy(path string) string {
|
||||
path = filepath.ToSlash(path)
|
||||
if runtime.GOOS == "windows" {
|
||||
// ':' is not valid in a path name on Windows but is ok on Unix
|
||||
@@ -408,6 +461,34 @@ func EscapePath(path string) string {
|
||||
return strings.ReplaceAll(path, "/", "%")
|
||||
}
|
||||
|
||||
// DetermineEscapePath escapes a path, determining whether it should be escaped
|
||||
// using URL encoding (preferred, since it encodes unambiguously) or
|
||||
// legacy encoding with '%' (for backward compatibility, if the legacy-escaped
|
||||
// path exists in the given directory).
|
||||
// In case the length of the escaped path (plus the backup extension) exceeds
|
||||
// the filename length limit, a hash of the path is returned instead. In such
|
||||
// case the second return value is the name of a file the original path should
|
||||
// be saved to (since the original path cannot be derived from its hash).
|
||||
// Otherwise the second return value is an empty string.
|
||||
func DetermineEscapePath(dir string, path string) (string, string) {
|
||||
url := filepath.Join(dir, EscapePathUrl(path))
|
||||
if _, err := os.Stat(url); err == nil {
|
||||
return url, ""
|
||||
}
|
||||
|
||||
legacy := filepath.Join(dir, EscapePathLegacy(path))
|
||||
if _, err := os.Stat(legacy); err == nil {
|
||||
return legacy, ""
|
||||
}
|
||||
|
||||
if len(url)+len(BackupSuffix) > 255 {
|
||||
hash := HashStringMd5(path)
|
||||
return filepath.Join(dir, hash), filepath.Join(dir, hash+".path")
|
||||
}
|
||||
|
||||
return url, ""
|
||||
}
|
||||
|
||||
// GetLeadingWhitespace returns the leading whitespace of the given byte array
|
||||
func GetLeadingWhitespace(b []byte) []byte {
|
||||
ws := []byte{}
|
||||
@@ -447,7 +528,7 @@ func HasTrailingWhitespace(b []byte) bool {
|
||||
}
|
||||
|
||||
// IntOpt turns a float64 setting to an int
|
||||
func IntOpt(opt interface{}) int {
|
||||
func IntOpt(opt any) int {
|
||||
return int(opt.(float64))
|
||||
}
|
||||
|
||||
@@ -510,11 +591,6 @@ func IsAutocomplete(c rune) bool {
|
||||
return c == '.' || IsWordChar(c)
|
||||
}
|
||||
|
||||
// ParseSpecial replaces escaped ts with '\t'.
|
||||
func ParseSpecial(s string) string {
|
||||
return strings.ReplaceAll(s, "\\t", "\t")
|
||||
}
|
||||
|
||||
// String converts a byte array to a string (for lua plugins)
|
||||
func String(s []byte) string {
|
||||
return string(s)
|
||||
@@ -585,3 +661,77 @@ func HttpRequest(method string, url string, headers []string) (resp *http.Respon
|
||||
}
|
||||
return client.Do(req)
|
||||
}
|
||||
|
||||
// SafeWrite writes bytes to a file in a "safe" way, preventing loss of the
|
||||
// contents of the file if it fails to write the new contents.
|
||||
// This means that the file is not overwritten directly but by writing to a
|
||||
// temporary file first.
|
||||
//
|
||||
// If rename is true, write is performed atomically, by renaming the temporary
|
||||
// file to the target file after the data is successfully written to the
|
||||
// temporary file. This guarantees that the file will not remain in a corrupted
|
||||
// state, but it also has limitations, e.g. the file should not be a symlink
|
||||
// (otherwise SafeWrite silently replaces this symlink with a regular file),
|
||||
// the file creation date in Linux is not preserved (since the file inode
|
||||
// changes) etc. Use SafeWrite with rename=true for files that are only created
|
||||
// and used by micro for its own needs and are not supposed to be used directly
|
||||
// by the user.
|
||||
//
|
||||
// If rename is false, write is performed by overwriting the target file after
|
||||
// the data is successfully written to the temporary file.
|
||||
// This means that the target file may remain corrupted if overwrite fails,
|
||||
// but in such case the temporary file is preserved as a backup so the file
|
||||
// can be recovered later. So it is less convenient than atomic write but more
|
||||
// universal. Use SafeWrite with rename=false for files that may be managed
|
||||
// directly by the user, like settings.json and bindings.json.
|
||||
func SafeWrite(path string, bytes []byte, rename bool) error {
|
||||
var err error
|
||||
if _, err = os.Stat(path); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
// Force rename for new files!
|
||||
rename = true
|
||||
}
|
||||
|
||||
var file *os.File
|
||||
if !rename {
|
||||
file, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE, FileMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
}
|
||||
|
||||
tmp := path + BackupSuffix
|
||||
err = os.WriteFile(tmp, bytes, FileMode)
|
||||
if err != nil {
|
||||
os.Remove(tmp)
|
||||
return err
|
||||
}
|
||||
|
||||
if rename {
|
||||
err = os.Rename(tmp, path)
|
||||
} else {
|
||||
err = file.Truncate(0)
|
||||
if err == nil {
|
||||
_, err = file.Write(bytes)
|
||||
}
|
||||
if err == nil {
|
||||
err = file.Sync()
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if rename {
|
||||
os.Remove(tmp)
|
||||
} else {
|
||||
err = OverwriteError{err, tmp}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if !rename {
|
||||
os.Remove(tmp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -439,11 +439,12 @@ func (n *Node) VSplit(right bool) uint64 {
|
||||
}
|
||||
|
||||
// unsplits the child of a split
|
||||
func (n *Node) unsplit(i int, h bool) {
|
||||
func (n *Node) unsplit(i int) {
|
||||
copy(n.children[i:], n.children[i+1:])
|
||||
n.children[len(n.children)-1] = nil
|
||||
n.children = n.children[:len(n.children)-1]
|
||||
|
||||
h := n.Kind == STVert
|
||||
nonrs, numr := n.getResizeInfo(h)
|
||||
if numr == 0 {
|
||||
// This means that this was the last child
|
||||
@@ -470,18 +471,62 @@ func (n *Node) Unsplit() bool {
|
||||
ind = i
|
||||
}
|
||||
}
|
||||
if n.parent.Kind == STVert {
|
||||
n.parent.unsplit(ind, true)
|
||||
} else {
|
||||
n.parent.unsplit(ind, false)
|
||||
}
|
||||
|
||||
n.parent.unsplit(ind)
|
||||
if n.parent.IsLeaf() {
|
||||
return n.parent.Unsplit()
|
||||
}
|
||||
|
||||
n.parent.flatten()
|
||||
return true
|
||||
}
|
||||
|
||||
// flattens the tree by removing unnecessary intermediate parents that have only one child
|
||||
// and handles the side effect of it
|
||||
func (n *Node) flatten() {
|
||||
if n.parent == nil || len(n.children) != 1 {
|
||||
return
|
||||
}
|
||||
|
||||
ind := 0
|
||||
for i, c := range n.parent.children {
|
||||
if c.id == n.id {
|
||||
ind = i
|
||||
}
|
||||
}
|
||||
|
||||
// Replace current node with its child node to remove chained parent
|
||||
successor := n.children[0]
|
||||
n.parent.children[ind] = successor
|
||||
successor.parent = n.parent
|
||||
|
||||
// Maintain the tree in a consistent state: any child node's kind (horiz vs vert)
|
||||
// should be the opposite of its parent's kind.
|
||||
if successor.IsLeaf() {
|
||||
successor.Kind = n.Kind
|
||||
} else {
|
||||
// If the successor node has children, that means it is a chained parent as well.
|
||||
// Therefore it can be replaced by its own children.
|
||||
origsize := len(n.parent.children)
|
||||
|
||||
// Let's say we have 5 children and want to replace [2] with its children [a] [b] [c]
|
||||
// [0] [1] [2] [3] [4] --> [0] [1] [a] [b] [c] [3] [4]
|
||||
// insertcount will be `3 - 1 = 2` in this case
|
||||
insertcount := len(successor.children) - 1
|
||||
|
||||
n.parent.children = append(n.parent.children, make([]*Node, insertcount)...)
|
||||
copy(n.parent.children[ind+insertcount+1:], n.parent.children[ind+1:origsize])
|
||||
copy(n.parent.children[ind:], successor.children)
|
||||
|
||||
for i := 0; i < len(successor.children); i++ {
|
||||
n.parent.children[ind+i].parent = n.parent
|
||||
}
|
||||
}
|
||||
|
||||
// Update propW and propH since the parent of the children has been updated,
|
||||
// so the children have new siblings
|
||||
n.parent.markSizes()
|
||||
}
|
||||
|
||||
// String returns the string form of the node and all children (used for debugging)
|
||||
func (n *Node) String() string {
|
||||
var strf func(n *Node, ident int) string
|
||||
|
||||
@@ -51,19 +51,6 @@ func runePos(p int, str []byte) int {
|
||||
return CharacterCount(str[:p])
|
||||
}
|
||||
|
||||
func combineLineMatch(src, dst LineMatch) LineMatch {
|
||||
for k, v := range src {
|
||||
if g, ok := dst[k]; ok {
|
||||
if g == 0 {
|
||||
dst[k] = v
|
||||
}
|
||||
} else {
|
||||
dst[k] = v
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// A State represents the region at the end of a line
|
||||
type State *region
|
||||
|
||||
@@ -175,7 +162,7 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
|
||||
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])) {
|
||||
if (endLoc == nil) || (m[0] < endLoc[0]) {
|
||||
for i := m[0]; i < m[1]; i++ {
|
||||
fullHighlights[i] = p.group
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ type HeaderYaml struct {
|
||||
|
||||
type File struct {
|
||||
FileType string
|
||||
yamlSrc map[interface{}]interface{}
|
||||
yamlSrc map[any]any
|
||||
}
|
||||
|
||||
// A Pattern is one simple syntax rule
|
||||
@@ -197,7 +197,7 @@ func ParseFile(input []byte) (f *File, err error) {
|
||||
}
|
||||
}()
|
||||
|
||||
var rules map[interface{}]interface{}
|
||||
var rules map[any]any
|
||||
if err = yaml.Unmarshal(input, &rules); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -245,7 +245,7 @@ func ParseDef(f *File, header *Header) (s *Def, err error) {
|
||||
|
||||
for k, v := range src {
|
||||
if k == "rules" {
|
||||
inputRules := v.([]interface{})
|
||||
inputRules := v.([]any)
|
||||
|
||||
rules, err := parseRules(inputRules, nil)
|
||||
if err != nil {
|
||||
@@ -258,7 +258,7 @@ func ParseDef(f *File, header *Header) (s *Def, err error) {
|
||||
|
||||
if s.rules == nil {
|
||||
// allow empty rules
|
||||
s.rules = new(rules)
|
||||
s.rules = &rules{}
|
||||
}
|
||||
|
||||
return s, err
|
||||
@@ -336,7 +336,7 @@ func resolveIncludesInRegion(files []*File, region *region) {
|
||||
}
|
||||
}
|
||||
|
||||
func parseRules(input []interface{}, curRegion *region) (ru *rules, err error) {
|
||||
func parseRules(input []any, curRegion *region) (ru *rules, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
@@ -349,7 +349,7 @@ func parseRules(input []interface{}, curRegion *region) (ru *rules, err error) {
|
||||
ru = new(rules)
|
||||
|
||||
for _, v := range input {
|
||||
rule := v.(map[interface{}]interface{})
|
||||
rule := v.(map[any]any)
|
||||
for k, val := range rule {
|
||||
group := k
|
||||
|
||||
@@ -376,7 +376,7 @@ func parseRules(input []interface{}, curRegion *region) (ru *rules, err error) {
|
||||
groupNum := Groups[groupStr]
|
||||
ru.patterns = append(ru.patterns, &pattern{groupNum, r})
|
||||
}
|
||||
case map[interface{}]interface{}:
|
||||
case map[any]any:
|
||||
// region
|
||||
region, err := parseRegion(group.(string), object, curRegion)
|
||||
if err != nil {
|
||||
@@ -392,7 +392,7 @@ func parseRules(input []interface{}, curRegion *region) (ru *rules, err error) {
|
||||
return ru, nil
|
||||
}
|
||||
|
||||
func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegion *region) (r *region, err error) {
|
||||
func parseRegion(group string, regionInfo map[any]any, prevRegion *region) (r *region, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
@@ -476,10 +476,17 @@ func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegio
|
||||
r.limitGroup = r.group
|
||||
}
|
||||
|
||||
r.rules, err = parseRules(regionInfo["rules"].([]interface{}), r)
|
||||
// rules are optional
|
||||
if rules, ok := regionInfo["rules"]; ok {
|
||||
r.rules, err = parseRules(rules.([]any), r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if r.rules == nil {
|
||||
// allow empty rules
|
||||
r.rules = &rules{}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
|
||||
@@ -24,6 +24,6 @@ color-link gutter-warning "88"
|
||||
color-link cursor-line "229"
|
||||
#color-link color-column "196"
|
||||
color-link current-line-number "246"
|
||||
color-line match-brace "230,22"
|
||||
color-link match-brace "230,22"
|
||||
color-link tab-error "210"
|
||||
color-link trailingws "210"
|
||||
|
||||
@@ -47,10 +47,7 @@ color support comes in three flavors.
|
||||
displaying any colorscheme, but it should be noted that the user-configured
|
||||
16-color palette is ignored when using true-color mode (this means the
|
||||
colors while using the terminal emulator will be slightly off). Not all
|
||||
terminals support true color but at this point most do. True color
|
||||
support in micro is off by default but can be enabled by setting the
|
||||
environment variable `MICRO_TRUECOLOR` to 1. In addition your terminal
|
||||
must support it (usually indicated by setting `$COLORTERM` to `truecolor`).
|
||||
terminals support true color but at this point most do (see below).
|
||||
True-color colorschemes in micro typically end with `-tc`, such as
|
||||
`solarized-tc`, `atom-dark`, `material-tc`, etc... If true color is not
|
||||
enabled but a true color colorscheme is used, micro will do its best to
|
||||
@@ -84,11 +81,12 @@ These may vary widely based on the 16 colors selected for your terminal.
|
||||
|
||||
### True color
|
||||
|
||||
True color requires your terminal to support it. This means that the
|
||||
environment variable `COLORTERM` should have the value `truecolor`, `24bit`,
|
||||
or `24-bit`. In addition, to enable true color in micro, the environment
|
||||
variable `MICRO_TRUECOLOR` must be set to 1. Note that you have to create
|
||||
and set this variable yourself.
|
||||
Micro enables true color support by default as long as it detects that the
|
||||
terminal supports it (which is usually indicated by the environment variable
|
||||
`COLORTERM` being set to `truecolor`, `24bit` or `24-bit`). You can also force
|
||||
enabling it unconditionally by setting the option `truecolor` to `on` (or
|
||||
alternatively by setting the environment variable `MICRO_TRUECOLOR` to 1, which
|
||||
is supported for backward compatibility).
|
||||
|
||||
* `solarized-tc`: this is the solarized colorscheme for true color.
|
||||
* `atom-dark`: this colorscheme is based off of Atom's "dark" colorscheme.
|
||||
@@ -101,7 +99,7 @@ and set this variable yourself.
|
||||
|
||||
Micro's colorschemes are also extremely simple to create. The default ones can
|
||||
be found
|
||||
[here](https://github.com/zyedidia/micro/tree/master/runtime/colorschemes).
|
||||
[here](https://github.com/micro-editor/micro/tree/master/runtime/colorschemes).
|
||||
|
||||
Custom colorschemes should be placed in the `~/.config/micro/colorschemes`
|
||||
directory.
|
||||
@@ -177,10 +175,14 @@ Here is a list of the colorscheme groups that you can use:
|
||||
* todo
|
||||
* selection (Color of the text selection)
|
||||
* statusline (Color of the statusline)
|
||||
* statusline.inactive (Color of the statusline of inactive split panes)
|
||||
* statusline.suggestions (Color of the autocomplete suggestions menu)
|
||||
* tabbar (Color of the tabbar that lists open files)
|
||||
* tabbar.active (Color of the active tab in the tabbar)
|
||||
* indent-char (Color of the character which indicates tabs if the option is
|
||||
enabled)
|
||||
* line-number
|
||||
* gutter-info
|
||||
* gutter-error
|
||||
* gutter-warning
|
||||
* diff-added
|
||||
@@ -371,7 +373,6 @@ highlighted. For example:
|
||||
start: "\""
|
||||
end: "\""
|
||||
skip: "\\."
|
||||
rules: []
|
||||
```
|
||||
|
||||
#### Includes
|
||||
|
||||
@@ -21,10 +21,16 @@ quotes here but these are not necessary when entering the command in micro.
|
||||
This command will modify `bindings.json` and overwrite any bindings to
|
||||
`key` that already exist.
|
||||
|
||||
* `help ['topic']`: opens the corresponding help topic. If no topic is provided
|
||||
opens the default help screen. Help topics are stored as `.md` files in the
|
||||
`runtime/help` directory of the source tree, which is embedded in the final
|
||||
binary.
|
||||
* `help ['topic'] ['flags']`: opens the corresponding help topics.
|
||||
If no topic is provided opens the default help screen. If multiple topics are
|
||||
provided (separated via ` `) they are opened all as splits.
|
||||
Help topics are stored as `.md` files in the `runtime/help` directory of
|
||||
the source tree, which is embedded in the final binary.
|
||||
The `flags` are optional.
|
||||
* `-hsplit`: Opens the help topic in a horizontal split
|
||||
* `-vsplit`: Opens the help topic in a vertical split
|
||||
|
||||
The default split type is defined by the global `helpsplit` option.
|
||||
|
||||
* `save ['filename']`: saves the current buffer. If the file is provided it
|
||||
will 'save as' the filename.
|
||||
@@ -66,18 +72,33 @@ quotes here but these are not necessary when entering the command in micro.
|
||||
* `setlocal 'option' 'value'`: sets the option to value locally (only in the
|
||||
current buffer). This will *not* modify `settings.json`.
|
||||
|
||||
* `toggle 'option'`: toggles the option. Only works with options that accept
|
||||
exactly two values. This will modify your `settings.json` with the new value.
|
||||
|
||||
* `togglelocal 'option'`: toggles the option locally (only in the
|
||||
current buffer). Only works with options that accept exactly two values.
|
||||
This will *not* modify `settings.json`.
|
||||
|
||||
* `reset 'option'`: resets the given option to its default value.
|
||||
|
||||
* `show 'option'`: shows the current value of the given option.
|
||||
|
||||
* `showkey 'key'`: Show the action(s) bound to a given key. For example
|
||||
running `> showkey Ctrl-c` will display `Copy`.
|
||||
|
||||
* `run 'sh-command'`: runs the given shell command in the background. The
|
||||
command's output will be displayed in one line when it finishes running.
|
||||
|
||||
* `vsplit ['filename']`: opens a vertical split with `filename`. If no filename
|
||||
is provided, a vertical split is opened with an empty buffer.
|
||||
is provided, a vertical split is opened with an empty buffer. If multiple
|
||||
files are provided (separated via ` `) they are opened all as splits.
|
||||
|
||||
* `hsplit ['filename']`: same as `vsplit` but opens a horizontal split instead
|
||||
of a vertical split.
|
||||
|
||||
* `tab ['filename']`: opens the given file in a new tab.
|
||||
* `tab ['filename']`: opens the given file in a new tab. If no filename
|
||||
is provided, a tab is opened with an empty buffer. If multiple files are
|
||||
provided (separated via ` `) they are opened all as tabs.
|
||||
|
||||
* `tabmove '[-+]n'`: Moves the active tab to another slot. `n` is an integer.
|
||||
If `n` is prefixed with `-` or `+`, then it represents a relative position
|
||||
@@ -120,8 +141,6 @@ quotes here but these are not necessary when entering the command in micro.
|
||||
|
||||
* `reopen`: Reopens the current file from disk.
|
||||
|
||||
* `reset 'option'`: resets the given option to its default value
|
||||
|
||||
* `retab`: Replaces all leading tabs with spaces or leading spaces with tabs
|
||||
depending on the value of `tabstospaces`.
|
||||
|
||||
@@ -130,9 +149,6 @@ quotes here but these are not necessary when entering the command in micro.
|
||||
the terminal and helps you see which bindings aren't possible and why. This
|
||||
is most useful for debugging keybindings.
|
||||
|
||||
* `showkey 'key'`: Show the action(s) bound to a given key. For example
|
||||
running `> showkey Ctrl-c` will display `Copy`.
|
||||
|
||||
* `term ['exec']`: Open a terminal emulator running the given executable. If no
|
||||
executable is given, this will open the default shell in the terminal
|
||||
emulator.
|
||||
|
||||
@@ -23,7 +23,7 @@ Here is a list of terminal emulators and their status:
|
||||
* `st`: supported.
|
||||
|
||||
* `rxvt-unicode`: not natively supported, but there is a Perl extension
|
||||
[here](http://anti.teamidiot.de/static/nei/*/Code/urxvt/).
|
||||
[here](https://anti.teamidiot.de/static/nei/*/Code/urxvt/).
|
||||
|
||||
* `xterm`: supported, but disabled by default. It can be enabled by putting
|
||||
the following in `.Xresources` or `.Xdefaults`:
|
||||
|
||||
@@ -66,7 +66,16 @@ bindings, tab is bound as
|
||||
|
||||
This means that if the `Autocomplete` action is successful, the chain will
|
||||
abort. Otherwise, it will try `IndentSelection`, and if that fails too, it
|
||||
will execute `InsertTab`.
|
||||
will execute `InsertTab`. To use `,`, `|` or `&` in an action (as an argument
|
||||
to a command, for example), escape it with `\` or wrap it in single or double
|
||||
quotes.
|
||||
|
||||
If the action has an `onAction` lua callback, for example `onAutocomplete` (see
|
||||
`> help plugins`), then the action is only considered successful if the action
|
||||
itself succeeded *and* the callback returned true. If there are multiple
|
||||
`onAction` callbacks for this action, registered by multiple plugins, then the
|
||||
action is only considered successful if the action itself succeeded and all the
|
||||
callbacks returned true.
|
||||
|
||||
## Binding commands
|
||||
|
||||
@@ -103,6 +112,48 @@ Now when you press `Ctrl-g`, `help` will appear in the command bar and your
|
||||
cursor will be placed after it (note the space in the json that controls the
|
||||
cursor placement).
|
||||
|
||||
## Binding Lua functions
|
||||
|
||||
You can also bind a key to a Lua function provided by a plugin, or by your own
|
||||
`~/.config/micro/init.lua`. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"Alt-q": "lua:foo.bar"
|
||||
}
|
||||
```
|
||||
|
||||
where `foo` is the name of the plugin and `bar` is the name of the lua function
|
||||
in it, e.g.:
|
||||
|
||||
```lua
|
||||
local micro = import("micro")
|
||||
|
||||
function bar(bp)
|
||||
micro.InfoBar():Message("Bar action triggered")
|
||||
return true
|
||||
end
|
||||
```
|
||||
|
||||
See `> help plugins` for more informations on how to write lua functions.
|
||||
|
||||
For `~/.config/micro/init.lua` the plugin name is `initlua` (so the keybinding
|
||||
in this example would be `"Alt-q": "lua:initlua.bar"`).
|
||||
|
||||
The currently active bufpane is passed to the lua function as the argument. If
|
||||
the key is a mouse button, e.g. `MouseLeft` or `MouseWheelUp`, the mouse event
|
||||
info is passed to the lua function as the second argument, of type
|
||||
`*tcell.EventMouse`. See https://pkg.go.dev/github.com/micro-editor/tcell/v2#EventMouse
|
||||
for the description of this type and its methods.
|
||||
|
||||
The return value of the lua function defines whether the action has succeeded.
|
||||
This is used when chaining lua functions with other actions. They can be chained
|
||||
the same way as regular actions as described above, for example:
|
||||
|
||||
```
|
||||
"Alt-q": "lua:initlua.bar|Quit"
|
||||
```
|
||||
|
||||
## Binding raw escape sequences
|
||||
|
||||
Only read this section if you are interested in binding keys that aren't on the
|
||||
@@ -168,14 +219,15 @@ CursorLeft
|
||||
CursorRight
|
||||
CursorStart
|
||||
CursorEnd
|
||||
CursorToViewTop
|
||||
CursorToViewCenter
|
||||
CursorToViewBottom
|
||||
SelectToStart
|
||||
SelectToEnd
|
||||
SelectUp
|
||||
SelectDown
|
||||
SelectLeft
|
||||
SelectRight
|
||||
SelectToStartOfText
|
||||
SelectToStartOfTextToggle
|
||||
WordRight
|
||||
WordLeft
|
||||
SubWordRight
|
||||
@@ -184,20 +236,22 @@ SelectWordRight
|
||||
SelectWordLeft
|
||||
SelectSubWordRight
|
||||
SelectSubWordLeft
|
||||
MoveLinesUp
|
||||
MoveLinesDown
|
||||
DeleteWordRight
|
||||
DeleteWordLeft
|
||||
DeleteSubWordRight
|
||||
DeleteSubWordLeft
|
||||
SelectLine
|
||||
SelectToStartOfLine
|
||||
SelectToStartOfText
|
||||
SelectToStartOfTextToggle
|
||||
SelectToEndOfLine
|
||||
ParagraphPrevious
|
||||
ParagraphNext
|
||||
SelectToParagraphPrevious
|
||||
SelectToParagraphNext
|
||||
InsertNewline
|
||||
InsertSpace
|
||||
Backspace
|
||||
Delete
|
||||
Center
|
||||
InsertTab
|
||||
Save
|
||||
SaveAll
|
||||
@@ -206,21 +260,28 @@ Find
|
||||
FindLiteral
|
||||
FindNext
|
||||
FindPrevious
|
||||
DiffPrevious
|
||||
DiffNext
|
||||
DiffPrevious
|
||||
Center
|
||||
Undo
|
||||
Redo
|
||||
Copy
|
||||
CopyLine
|
||||
Cut
|
||||
CutLine
|
||||
Duplicate
|
||||
DuplicateLine
|
||||
DeleteLine
|
||||
MoveLinesUp
|
||||
MoveLinesDown
|
||||
IndentSelection
|
||||
OutdentSelection
|
||||
Autocomplete
|
||||
CycleAutocompleteBack
|
||||
OutdentLine
|
||||
IndentLine
|
||||
Paste
|
||||
PastePrimary
|
||||
SelectAll
|
||||
OpenFile
|
||||
Start
|
||||
@@ -231,33 +292,37 @@ SelectPageUp
|
||||
SelectPageDown
|
||||
HalfPageUp
|
||||
HalfPageDown
|
||||
StartOfLine
|
||||
EndOfLine
|
||||
StartOfText
|
||||
StartOfTextToggle
|
||||
ParagraphPrevious
|
||||
ParagraphNext
|
||||
SelectToParagraphPrevious
|
||||
SelectToParagraphNext
|
||||
StartOfLine
|
||||
EndOfLine
|
||||
ToggleHelp
|
||||
ToggleKeyMenu
|
||||
ToggleDiffGutter
|
||||
ToggleRuler
|
||||
JumpLine
|
||||
ToggleHighlightSearch
|
||||
UnhighlightSearch
|
||||
ResetSearch
|
||||
ClearInfo
|
||||
ClearStatus
|
||||
ShellMode
|
||||
CommandMode
|
||||
ToggleOverwriteMode
|
||||
Escape
|
||||
Quit
|
||||
QuitAll
|
||||
ForceQuit
|
||||
AddTab
|
||||
PreviousTab
|
||||
NextTab
|
||||
FirstTab
|
||||
LastTab
|
||||
NextSplit
|
||||
PreviousSplit
|
||||
FirstSplit
|
||||
LastSplit
|
||||
Unsplit
|
||||
VSplit
|
||||
HSplit
|
||||
PreviousSplit
|
||||
ToggleMacro
|
||||
PlayMacro
|
||||
Suspend (Unix only)
|
||||
@@ -270,18 +335,31 @@ SpawnMultiCursorSelect
|
||||
RemoveMultiCursor
|
||||
RemoveAllMultiCursors
|
||||
SkipMultiCursor
|
||||
None
|
||||
SkipMultiCursorBack
|
||||
JumpToMatchingBrace
|
||||
Autocomplete
|
||||
JumpLine
|
||||
Deselect
|
||||
ClearInfo
|
||||
None
|
||||
```
|
||||
|
||||
The `StartOfTextToggle` and `SelectToStartOfTextToggle` actions toggle between
|
||||
jumping to the start of the text (first) and start of the line.
|
||||
|
||||
The `CutLine` action cuts the current line and adds it to the previously cut
|
||||
lines in the clipboard since the last paste (rather than just replaces the
|
||||
clipboard contents with this line). So you can cut multiple, not necessarily
|
||||
consecutive lines to the clipboard just by pressing `Ctrl-k` multiple times,
|
||||
without selecting them. If you want the more traditional behavior i.e. just
|
||||
rewrite the clipboard every time, you can use `CopyLine,DeleteLine` action
|
||||
instead of `CutLine`.
|
||||
|
||||
You can also bind some mouse actions (these must be bound to mouse buttons)
|
||||
|
||||
```
|
||||
MousePress
|
||||
MouseDrag
|
||||
MouseRelease
|
||||
MouseMultiCursor
|
||||
```
|
||||
|
||||
@@ -495,23 +573,23 @@ conventions for text editing defaults.
|
||||
"Alt-]": "DiffNext|CursorEnd",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-c": "Copy|CopyLine",
|
||||
"Ctrl-x": "Cut|CutLine",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-d": "DuplicateLine",
|
||||
"Ctrl-d": "Duplicate|DuplicateLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Ctrl-a": "SelectAll",
|
||||
"Ctrl-t": "AddTab",
|
||||
"Alt-,": "PreviousTab",
|
||||
"Alt-.": "NextTab",
|
||||
"Alt-,": "PreviousTab|LastTab",
|
||||
"Alt-.": "NextTab|FirstTab",
|
||||
"Home": "StartOfText",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"CtrlPageUp": "PreviousTab|LastTab",
|
||||
"CtrlPageDown": "NextTab|FirstTab",
|
||||
"ShiftPageUp": "SelectPageUp",
|
||||
"ShiftPageDown": "SelectPageDown",
|
||||
"Ctrl-g": "ToggleHelp",
|
||||
@@ -522,7 +600,7 @@ conventions for text editing defaults.
|
||||
"Ctrl-b": "ShellMode",
|
||||
"Ctrl-q": "Quit",
|
||||
"Ctrl-e": "CommandMode",
|
||||
"Ctrl-w": "NextSplit",
|
||||
"Ctrl-w": "NextSplit|FirstSplit",
|
||||
"Ctrl-u": "ToggleMacro",
|
||||
"Ctrl-j": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
@@ -621,7 +699,7 @@ are given below:
|
||||
"Backtab": "CycleAutocompleteBack",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-c": "Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-v": "Paste",
|
||||
|
||||
@@ -79,22 +79,16 @@ Here are the available options:
|
||||
|
||||
default value: `0`
|
||||
|
||||
* `colorscheme`: loads the colorscheme stored in
|
||||
$(configDir)/colorschemes/`option`.micro, This setting is `global only`.
|
||||
* `colorscheme`: use the given colorscheme. This setting is `global only`.
|
||||
The colorscheme can be either one of the colorschemes that micro comes with
|
||||
by default (such as `default`, `solarized` or `solarized-tc`) which are
|
||||
embedded in the micro binary, or a custom colorscheme stored in
|
||||
`~/.config/micro/colorschemes/$(option).micro` where `$(option)` is the
|
||||
option value. You can read more about micro's colorschemes and see the list
|
||||
of default colorschemes in `> help colors`.
|
||||
|
||||
default value: `default`
|
||||
|
||||
Note that the default colorschemes (default, solarized, and solarized-tc)
|
||||
are not located in configDir, because they are embedded in the micro
|
||||
binary.
|
||||
|
||||
The colorscheme can be selected from all the files in the
|
||||
~/.config/micro/colorschemes/ directory. Micro comes by default with
|
||||
three colorschemes:
|
||||
|
||||
You can read more about micro's colorschemes in the `colors` help topic
|
||||
(`help colors`).
|
||||
|
||||
* `cursorline`: highlight the line that the cursor is on in a different color
|
||||
(the color is defined by the colorscheme you are using).
|
||||
|
||||
@@ -139,6 +133,8 @@ Here are the available options:
|
||||
* `fakecursor`: forces micro to render the cursor using terminal colors rather
|
||||
than the actual terminal cursor. This is useful when the terminal's cursor is
|
||||
slow or otherwise unavailable/undesirable to use.
|
||||
Note: This option defaults to `true` in case `micro` is used in the legacy
|
||||
Windows Console.
|
||||
|
||||
default value: `false`
|
||||
|
||||
@@ -148,8 +144,8 @@ Here are the available options:
|
||||
This is fast, but can be inaccurate. If `fastdirty` is off, then micro will
|
||||
hash the current buffer against a hash of the original file (created when
|
||||
the buffer was loaded). This is more accurate but obviously more resource
|
||||
intensive. This option will be automatically disabled if the file size
|
||||
exceeds 50KB.
|
||||
intensive. This option will be automatically enabled for the current buffer
|
||||
if the file size exceeds 50KB.
|
||||
|
||||
default value: `false`
|
||||
|
||||
@@ -172,6 +168,13 @@ Here are the available options:
|
||||
default value: `unknown`. This will be automatically overridden depending
|
||||
on the file you open.
|
||||
|
||||
* `helpsplit`: sets the split type to be used by the `help` command.
|
||||
Possible values:
|
||||
* `vsplit`: open help in a vertical split pane
|
||||
* `hsplit`: open help in a horizontal split pane
|
||||
|
||||
default value: `hsplit`
|
||||
|
||||
* `hlsearch`: highlight all instances of the searched text after a successful
|
||||
search. This highlighting can be temporarily turned off via the
|
||||
`UnhighlightSearch` action (triggered by the Esc key by default) or toggled
|
||||
@@ -194,20 +197,16 @@ Here are the available options:
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `incsearch`: enable incremental search in "Find" prompt (matching as you type).
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `ignorecase`: perform case-insensitive searches.
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `indentchar`: sets the indentation character. This will not be inserted into
|
||||
files; it is only a visual indicator that whitespace is present. If set to a
|
||||
printing character, it functions as a subset of the "show invisibles"
|
||||
setting available in many other text editors. The color of this character is
|
||||
determined by the `indent-char` field in the current theme rather than the
|
||||
default text color.
|
||||
* `incsearch`: enable incremental search in "Find" prompt (matching as you type).
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `indentchar`: sets the character to be shown to display tab characters.
|
||||
This option is **deprecated**, use the `tab` key in `showchars` option instead.
|
||||
|
||||
default value: ` ` (space)
|
||||
|
||||
@@ -230,6 +229,12 @@ Here are the available options:
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `lockbindings`: prevent plugins and lua scripts from binding any keys.
|
||||
Any custom actions must be binded manually either via commands like `bind`
|
||||
or by modifying the `bindings.json` file.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `matchbrace`: show matching braces for '()', '{}', '[]' when the cursor
|
||||
is on a brace character or (if `matchbraceleft` is enabled) next to it.
|
||||
|
||||
@@ -278,6 +283,23 @@ Here are the available options:
|
||||
|
||||
default value: `tab`
|
||||
|
||||
* `pageoverlap`: the number of lines from the current view to keep in view
|
||||
when paging up or down. If this is set to 2, for instance, and you page
|
||||
down, the last two lines of the previous page will be the first two lines
|
||||
of the next page.
|
||||
|
||||
default value: `2`
|
||||
|
||||
* `parsecursor`: if enabled, this will cause micro to parse filenames such as
|
||||
`file.txt:10:5` as requesting to open `file.txt` with the cursor at line 10
|
||||
and column 5. The column number can also be dropped to open the file at a
|
||||
given line and column 0. Note that with this option enabled it is not possible
|
||||
to open a file such as `file.txt:10:5`, where `:10:5` is part of the filename.
|
||||
It is also possible to open a file with a certain cursor location by using the
|
||||
`+LINE:COL` flag syntax. See `micro -help` for the command line options.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `paste`: treat characters sent from the terminal in a single chunk as a paste
|
||||
event rather than a series of manual key presses. If you are pasting using
|
||||
the terminal keybinding (not `Ctrl-v`, which is micro's default paste
|
||||
@@ -287,16 +309,6 @@ Here are the available options:
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `parsecursor`: if enabled, this will cause micro to parse filenames such as
|
||||
file.txt:10:5 as requesting to open `file.txt` with the cursor at line 10
|
||||
and column 5. The column number can also be dropped to open the file at a
|
||||
given line and column 0. Note that with this option enabled it is not possible
|
||||
to open a file such as `file.txt:10:5`, where `:10:5` is part of the filename.
|
||||
It is also possible to open a file with a certain cursor location by using the
|
||||
`+LINE:COL` flag syntax. See `micro -help` for the command line options.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `permbackup`: this option causes backups (see `backup` option) to be
|
||||
permanently saved. With permanent backups, micro will not remove backups when
|
||||
files are closed and will never apply them to existing files. Use this option
|
||||
@@ -321,6 +333,12 @@ Here are the available options:
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `relativeruler`: make line numbers display relatively. If set to true, all
|
||||
lines except for the line that the cursor is located will display the distance
|
||||
from the cursor's line.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `reload`: controls the reload behavior of the current buffer in case the file
|
||||
has changed. The available options are `prompt`, `auto` & `disabled`.
|
||||
|
||||
@@ -338,12 +356,6 @@ Here are the available options:
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `relativeruler`: make line numbers display relatively. If set to true, all
|
||||
lines except for the line that the cursor is located will display the distance
|
||||
from the cursor's line.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `savecursor`: remember where the cursor was last time the file was opened and
|
||||
put it there when you open the file again. Information is saved to
|
||||
`~/.config/micro/buffers/`
|
||||
@@ -378,6 +390,29 @@ Here are the available options:
|
||||
|
||||
default value: `2`
|
||||
|
||||
* `showchars`: sets what characters to be shown to display various invisible
|
||||
characters in the file. The characters shown will not be inserted into files.
|
||||
This option is specified in the form of `key1=value1,key2=value2,...`.
|
||||
|
||||
Here are the list of keys:
|
||||
- `space`: space characters
|
||||
- `tab`: tab characters. If set, overrides the `indentchar` option.
|
||||
- `ispace`: space characters at indent position before the first visible
|
||||
character in a line. If this is not set, `space` will be shown
|
||||
instead.
|
||||
- `itab`: tab characters before the first visible character in a line.
|
||||
If this is not set, `tab` will be shown instead.
|
||||
|
||||
Only `tab` and `itab` can display multiple characters (if possible),
|
||||
otherwise only the first character will be displayed.
|
||||
|
||||
An example of this option value could be `tab=>,space=.,itab=|>,ispace=|`
|
||||
|
||||
The color of the shown character is determined by the `indent-char`
|
||||
field in the current theme rather than the default text color.
|
||||
|
||||
default value: ``
|
||||
|
||||
* `smartpaste`: add leading whitespace when pasting multiple lines.
|
||||
This will attempt to preserve the current indentation level when pasting an
|
||||
unindented block.
|
||||
@@ -401,11 +436,11 @@ Here are the available options:
|
||||
* `statusformatl`: format string definition for the left-justified part of the
|
||||
statusline. Special directives should be placed inside `$()`. Special
|
||||
directives include: `filename`, `modified`, `line`, `col`, `lines`,
|
||||
`percentage`, `opt`, `bind`.
|
||||
`percentage`, `opt`, `overwrite`, `bind`.
|
||||
The `opt` and `bind` directives take either an option or an action afterward
|
||||
and fill in the value of the option or the key bound to the action.
|
||||
|
||||
default value: `$(filename) $(modified)($(line),$(col)) $(status.paste)|
|
||||
default value: `$(filename) $(modified)$(overwrite)($(line),$(col)) $(status.paste)|
|
||||
ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)`
|
||||
|
||||
* `statusformatr`: format string definition for the right-justified part of the
|
||||
@@ -427,33 +462,46 @@ Here are the available options:
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `tabhighlight`: inverts the tab characters' (filename, save indicator, etc)
|
||||
colors with respect to the tab bar.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `tabmovement`: navigate spaces at the beginning of lines as if they are tabs
|
||||
(e.g. move over 4 spaces at once). This option only does anything if
|
||||
`tabstospaces` is on.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `tabhighlight`: inverts the tab characters' (filename, save indicator, etc)
|
||||
colors with respect to the tab bar.
|
||||
|
||||
default value: false
|
||||
|
||||
* `tabreverse`: reverses the tab bar colors when active.
|
||||
|
||||
default value: true
|
||||
default value: `true`
|
||||
|
||||
* `tabsize`: the size in spaces that a tab character should be displayed with.
|
||||
|
||||
default value: `4`
|
||||
|
||||
* `tabstospaces`: use spaces instead of tabs. Note: This option will be
|
||||
overridden by [the `ftoptions` plugin](https://github.com/zyedidia/micro/blob/master/runtime/plugins/ftoptions/ftoptions.lua)
|
||||
overridden by [the `ftoptions` plugin](https://github.com/micro-editor/micro/blob/master/runtime/plugins/ftoptions/ftoptions.lua)
|
||||
for certain filetypes. To disable this behavior, add `"ftoptions": false` to
|
||||
your config. See [issue #2213](https://github.com/zyedidia/micro/issues/2213)
|
||||
your config. See [issue #2213](https://github.com/micro-editor/micro/issues/2213)
|
||||
for more details.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `truecolor`: controls whether micro will use true colors (24-bit colors) when
|
||||
using a colorscheme with true colors, such as `solarized-tc` or `atom-dark`.
|
||||
* `auto`: enable usage of true color if micro detects that it is supported by
|
||||
the terminal, otherwise disable it.
|
||||
* `on`: force usage of true color even if micro does not detect its support
|
||||
by the terminal (of course this is not guaranteed to work well unless the
|
||||
terminal actually supports true color).
|
||||
* `off`: disable true color usage.
|
||||
|
||||
Note: The change will take effect after the next start of `micro`.
|
||||
|
||||
default value: `auto`
|
||||
|
||||
* `useprimary` (only useful on unix): defines whether or not micro will use the
|
||||
primary clipboard to copy selections in the background. This does not affect
|
||||
the normal clipboard using `Ctrl-c` and `Ctrl-v`.
|
||||
@@ -493,13 +541,13 @@ or disable them:
|
||||
recent Git commit rather than the diff since opening the file.
|
||||
|
||||
Any option you set in the editor will be saved to the file
|
||||
~/.config/micro/settings.json so, in effect, your configuration file will be
|
||||
`~/.config/micro/settings.json` so, in effect, your configuration file will be
|
||||
created for you. If you'd like to take your configuration with you to another
|
||||
machine, simply copy the settings.json to the other machine.
|
||||
machine, simply copy the `settings.json` to the other machine.
|
||||
|
||||
## Settings.json file
|
||||
|
||||
The settings.json file should go in your configuration directory (by default
|
||||
The `settings.json` file should go in your configuration directory (by default
|
||||
at `~/.config/micro`), and should contain only options which have been modified
|
||||
from their default setting. Here is the full list of options in json format,
|
||||
so that you can see what the formatting should look like.
|
||||
@@ -518,18 +566,24 @@ so that you can see what the formatting should look like.
|
||||
"colorscheme": "default",
|
||||
"comment": true,
|
||||
"cursorline": true,
|
||||
"detectlimit": 100,
|
||||
"diff": true,
|
||||
"diffgutter": false,
|
||||
"divchars": "|-",
|
||||
"divreverse": true,
|
||||
"encoding": "utf-8",
|
||||
"eofnewline": true,
|
||||
"fakecursor": false,
|
||||
"fastdirty": false,
|
||||
"fileformat": "unix",
|
||||
"filetype": "unknown",
|
||||
"incsearch": true,
|
||||
"ftoptions": true,
|
||||
"helpsplit": "hsplit",
|
||||
"hlsearch": false,
|
||||
"hltaberrors": false,
|
||||
"hltrailingws": false,
|
||||
"ignorecase": true,
|
||||
"incsearch": true,
|
||||
"indentchar": " ",
|
||||
"infobar": true,
|
||||
"initlua": true,
|
||||
@@ -542,6 +596,8 @@ so that you can see what the formatting should look like.
|
||||
"matchbracestyle": "underline",
|
||||
"mkparents": false,
|
||||
"mouse": true,
|
||||
"multiopen": "tab",
|
||||
"pageoverlap": 2,
|
||||
"parsecursor": false,
|
||||
"paste": false,
|
||||
"permbackup": false,
|
||||
@@ -551,30 +607,34 @@ so that you can see what the formatting should look like.
|
||||
"pluginrepos": [],
|
||||
"readonly": false,
|
||||
"relativeruler": false,
|
||||
"reload": "prompt",
|
||||
"rmtrailingws": false,
|
||||
"ruler": true,
|
||||
"savecursor": false,
|
||||
"savehistory": true,
|
||||
"saveundo": false,
|
||||
"scrollbar": false,
|
||||
"scrollbarchar": "|",
|
||||
"scrollmargin": 3,
|
||||
"scrollspeed": 2,
|
||||
"showchars": "",
|
||||
"smartpaste": true,
|
||||
"softwrap": false,
|
||||
"splitbottom": true,
|
||||
"splitright": true,
|
||||
"status": true,
|
||||
"statusformatl": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
|
||||
"statusformatl": "$(filename) $(modified)$(overwrite)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
|
||||
"statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
|
||||
"statusline": true,
|
||||
"sucmd": "sudo",
|
||||
"syntax": true,
|
||||
"tabmovement": false,
|
||||
"tabhighlight": true,
|
||||
"tabmovement": false,
|
||||
"tabreverse": false,
|
||||
"tabsize": 4,
|
||||
"tabstospaces": false,
|
||||
"useprimary": true,
|
||||
"wordwrap": false,
|
||||
"xterm": false
|
||||
}
|
||||
```
|
||||
|
||||
@@ -57,11 +57,13 @@ that micro defines:
|
||||
|
||||
* `deinit()`: cleanup function called when your plugin is unloaded or reloaded.
|
||||
|
||||
* `onSetActive(bufpane)`: runs when changing the currently active panel.
|
||||
|
||||
* `onBufferOpen(buf)`: runs when a buffer is opened. The input contains
|
||||
the buffer object.
|
||||
|
||||
* `onBufferOptionChanged(buf, option, old, new)`: runs when an option of the
|
||||
buffer has changed. The input contains the buffer object, the option name,
|
||||
the old and the new value.
|
||||
|
||||
* `onBufPaneOpen(bufpane)`: runs when a bufpane is opened. The input
|
||||
contains the bufpane object.
|
||||
|
||||
@@ -69,13 +71,24 @@ that micro defines:
|
||||
|
||||
* `onAction(bufpane)`: runs when `Action` is triggered by the user, where
|
||||
`Action` is a bindable action (see `> help keybindings`). A bufpane
|
||||
is passed as input and the function should return a boolean defining
|
||||
whether the view should be relocated after this action is performed.
|
||||
is passed as input. The function should return a boolean defining
|
||||
whether the action was successful, which is used when the action is
|
||||
chained with other actions (see `> help keybindings`) to determine whether
|
||||
the next actions in the chain should be executed or not.
|
||||
|
||||
If the action is a mouse action, e.g. `MousePress`, the mouse event info
|
||||
is passed to the callback as an extra argument of type `*tcell.EventMouse`.
|
||||
See https://pkg.go.dev/github.com/micro-editor/tcell/v2#EventMouse for the
|
||||
description of this type and its methods.
|
||||
|
||||
* `preAction(bufpane)`: runs immediately before `Action` is triggered
|
||||
by the user. Returns a boolean which defines whether the action should
|
||||
be canceled.
|
||||
|
||||
Similarly to `onAction`, if the action is a mouse action, the mouse event
|
||||
info is passed to the callback as an extra argument of type
|
||||
`*tcell.EventMouse`.
|
||||
|
||||
* `onRune(bufpane, rune)`: runs when the composed rune has been inserted
|
||||
|
||||
* `preRune(bufpane, rune)`: runs before the composed rune will be inserted
|
||||
@@ -99,9 +112,6 @@ within. This is almost always the current bufpane.
|
||||
|
||||
All available actions are listed in the keybindings section of the help.
|
||||
|
||||
These functions should also return a boolean specifying whether the bufpane
|
||||
should be relocated to the cursor or not after the action is complete.
|
||||
|
||||
## Accessing micro functions
|
||||
|
||||
Some of micro's internal information is exposed in the form of packages, which
|
||||
@@ -116,7 +126,7 @@ micro.Log("Hello")
|
||||
The packages and their contents are listed below (in Go type signatures):
|
||||
|
||||
* `micro`
|
||||
- `TermMessage(msg interface{}...)`: temporarily close micro and print a
|
||||
- `TermMessage(msg any...)`: temporarily close micro and print a
|
||||
message
|
||||
|
||||
- `TermError(filename string, lineNum int, err string)`: temporarily close
|
||||
@@ -124,7 +134,7 @@ The packages and their contents are listed below (in Go type signatures):
|
||||
|
||||
- `InfoBar() *InfoPane`: return the infobar BufPane object.
|
||||
|
||||
- `Log(msg interface{}...)`: write a message to `log.txt` (requires
|
||||
- `Log(msg any...)`: write a message to `log.txt` (requires
|
||||
`-debug` flag, or binary built with `build-dbg`).
|
||||
|
||||
- `SetStatusInfoFn(fn string)`: register the given lua function as
|
||||
@@ -143,11 +153,10 @@ The packages and their contents are listed below (in Go type signatures):
|
||||
|
||||
Relevant links:
|
||||
[Time](https://pkg.go.dev/time#Duration)
|
||||
[BufPane](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/action#BufPane)
|
||||
[InfoPane](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/action#InfoPane)
|
||||
[Tab](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/action#Tab)
|
||||
[TabList](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/action#TabList)
|
||||
[interface{} / any](https://go.dev/tour/methods/14)
|
||||
[BufPane](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/action#BufPane)
|
||||
[InfoPane](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/action#InfoPane)
|
||||
[Tab](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/action#Tab)
|
||||
[TabList](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/action#TabList)
|
||||
|
||||
* `micro/config`
|
||||
- `MakeCommand(name string, action func(bp *BufPane, args[]string),
|
||||
@@ -165,11 +174,12 @@ The packages and their contents are listed below (in Go type signatures):
|
||||
values afterwards
|
||||
- `NoComplete`: no autocompletion suggestions
|
||||
|
||||
- `TryBindKey(k, v string, overwrite bool) (bool, error)`: bind the key
|
||||
`k` to the string `v` in the `bindings.json` file. If `overwrite` is
|
||||
true, this will overwrite any existing binding to key `k`. Returns true
|
||||
if the binding was made, and a possible error (for example writing to
|
||||
`bindings.json` can cause an error).
|
||||
- `TryBindKey(k, v string, overwrite bool) (bool, error)`:
|
||||
bind the key `k` to the string `v`. If `overwrite` is true, this will
|
||||
overwrite any existing binding to key `k`.
|
||||
Returns true if the binding was made, and a possible error.
|
||||
This operation can be rejected by `lockbindings` to prevent undesired
|
||||
actions by the user.
|
||||
|
||||
- `Reload()`: reload configuration files.
|
||||
|
||||
@@ -200,26 +210,26 @@ The packages and their contents are listed below (in Go type signatures):
|
||||
- `RTHelp`: runtime files for help documents.
|
||||
- `RTPlugin`: runtime files for plugin source code.
|
||||
|
||||
- `RegisterCommonOption(pl string, name string, defaultvalue interface{})`:
|
||||
- `RegisterCommonOption(pl string, name string, defaultvalue any)`:
|
||||
registers a new option for the given plugin. The name of the
|
||||
option will be `pl.name`, and will have the given default value. Since
|
||||
this registers a common option, the option will be modifiable on a
|
||||
per-buffer basis, while also having a global value (in the
|
||||
GlobalSettings map).
|
||||
|
||||
- `RegisterGlobalOption(pl string, name string, defaultvalue interface{})`:
|
||||
- `RegisterGlobalOption(pl string, name string, defaultvalue any)`:
|
||||
same as `RegisterCommonOption`, but the option cannot be modified
|
||||
locally to each buffer.
|
||||
|
||||
- `GetGlobalOption(name string) interface{}`: returns the value of a
|
||||
- `GetGlobalOption(name string) any`: returns the value of a
|
||||
given plugin in the `GlobalSettings` map.
|
||||
|
||||
- `SetGlobalOption(option, value string) error`: sets an option to a
|
||||
given value. Same as using the `> set` command. This will try to convert
|
||||
the value into the proper type for the option. Can return an error if the
|
||||
option name is not valid, or the value can not be converted.
|
||||
given value. This will try to convert the value into the proper
|
||||
type for the option. Can return an error if the option name is not
|
||||
valid, or the value can not be converted.
|
||||
|
||||
- `SetGlobalOptionNative(option string, value interface{}) error`: sets
|
||||
- `SetGlobalOptionNative(option string, value any) error`: sets
|
||||
an option to a given value, where the type of value is the actual
|
||||
type of the value internally. Can return an error if the provided value
|
||||
is not valid for the given option.
|
||||
@@ -227,10 +237,9 @@ The packages and their contents are listed below (in Go type signatures):
|
||||
- `ConfigDir`: the path to micro's currently active config directory.
|
||||
|
||||
Relevant links:
|
||||
[Buffer](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/buffer#Buffer)
|
||||
[buffer.Completer](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/buffer#Completer)
|
||||
[Buffer](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/buffer#Buffer)
|
||||
[buffer.Completer](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/buffer#Completer)
|
||||
[Error](https://pkg.go.dev/builtin#error)
|
||||
[interface{} / any](https://go.dev/tour/methods/14)
|
||||
[filepath.Match](https://pkg.go.dev/path/filepath#Match)
|
||||
|
||||
* `micro/shell`
|
||||
@@ -256,7 +265,7 @@ The packages and their contents are listed below (in Go type signatures):
|
||||
stdout from the command to the returned string.
|
||||
|
||||
- `JobStart(cmd string, onStdout, onStderr,
|
||||
onExit func(string, []interface{}), userargs ...interface{})
|
||||
onExit func(string, []any), userargs ...any)
|
||||
*exec.Cmd`:
|
||||
Starts a background job by running the shell on the given command
|
||||
(using `sh -c`). Three callbacks can be provided which will be called
|
||||
@@ -265,7 +274,7 @@ The packages and their contents are listed below (in Go type signatures):
|
||||
argument of the callback. Returns the started command.
|
||||
|
||||
- `JobSpawn(cmd string, cmdArgs []string, onStdout, onStderr,
|
||||
onExit func(string, []interface{}), userargs ...interface{})
|
||||
onExit func(string, []any), userargs ...any)
|
||||
*exec.Cmd`:
|
||||
same as `JobStart`, except doesn't run the command through the shell
|
||||
and instead takes as inputs the list of arguments. Returns the started
|
||||
@@ -275,8 +284,8 @@ The packages and their contents are listed below (in Go type signatures):
|
||||
- `JobSend(cmd *exec.Cmd, data string)`: sends some data to a job's stdin.
|
||||
|
||||
- `RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool,
|
||||
callback func(out string, userargs []interface{}),
|
||||
userargs []interface{}) error`:
|
||||
callback func(out string, userargs []any),
|
||||
userargs []any) error`:
|
||||
starts a terminal emulator from a given BufPane with the input command.
|
||||
If `wait` is true, it will wait for the user to exit by pressing enter
|
||||
once the executable has terminated, and if `getOutput` is true, it will
|
||||
@@ -295,7 +304,7 @@ The packages and their contents are listed below (in Go type signatures):
|
||||
|
||||
Relevant links:
|
||||
[Cmd](https://pkg.go.dev/os/exec#Cmd)
|
||||
[BufPane](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/action#BufPane)
|
||||
[BufPane](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/action#BufPane)
|
||||
[Error](https://pkg.go.dev/builtin#error)
|
||||
|
||||
* `micro/buffer`
|
||||
@@ -336,10 +345,10 @@ The packages and their contents are listed below (in Go type signatures):
|
||||
- `LogBuf() *Buffer`: returns the log buffer.
|
||||
|
||||
Relevant links:
|
||||
[Message](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/buffer#Message)
|
||||
[Loc](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/buffer#Loc)
|
||||
[display.SLoc](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/display#SLoc)
|
||||
[Buffer](https://pkg.go.dev/github.com/zyedidia/micro/v2/internal/buffer#Buffer)
|
||||
[Message](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/buffer#Message)
|
||||
[Loc](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/buffer#Loc)
|
||||
[display.SLoc](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/display#SLoc)
|
||||
[Buffer](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/buffer#Buffer)
|
||||
[Error](https://pkg.go.dev/builtin#error)
|
||||
|
||||
* `micro/util`
|
||||
@@ -350,7 +359,6 @@ The packages and their contents are listed below (in Go type signatures):
|
||||
- `IsWordChar(s string) bool`: returns true if the first rune in a
|
||||
string is a word character.
|
||||
- `String(b []byte) string`: converts a byte array to a string.
|
||||
- `RuneStr(r rune) string`: converts a rune to a string.
|
||||
- `Unzip(src, dest string) error`: unzips a file to given folder.
|
||||
- `Version`: micro's version number or commit hash
|
||||
- `SemVersion`: micro's semantic version
|
||||
@@ -368,7 +376,7 @@ returned by the functions have many methods. The Lua plugin may access any
|
||||
public methods of an object returned by any of the functions above.
|
||||
Unfortunately, it is not possible to list all the available functions on this
|
||||
page. Please go to the internal documentation at
|
||||
https://pkg.go.dev/github.com/zyedidia/micro/v2/internal to see the full list
|
||||
https://pkg.go.dev/github.com/micro-editor/micro/v2/internal to see the full list
|
||||
of available methods. Note that only methods of types that are available to
|
||||
plugins via the functions above can be called from a plugin. For an even more
|
||||
detailed reference, see the source code on Github.
|
||||
@@ -500,8 +508,8 @@ Micro also has a built in plugin manager, which you can invoke with the
|
||||
For the valid commands you can use, see the `commands` help topic.
|
||||
|
||||
The manager fetches plugins from the channels (which is simply a list of plugin
|
||||
metadata) which it knows about. By default, micro only knows about the official
|
||||
channel which is located at github.com/micro-editor/plugin-channel but you can
|
||||
metadata) which it knows about. By default, micro only knows about the [official
|
||||
channel](https://github.com/micro-editor/plugin-channel) but you can
|
||||
add your own third-party channels using the `pluginchannels` option and you can
|
||||
directly link third-party plugins to allow installation through the plugin
|
||||
manager with the `pluginrepos` option.
|
||||
@@ -528,9 +536,9 @@ This file will contain the metadata for your plugin. Here is an example:
|
||||
}]
|
||||
```
|
||||
|
||||
Then open a pull request at github.com/micro-editor/plugin-channel, adding a
|
||||
link to the raw `repo.json` that is in your plugin repository.
|
||||
Then open a pull request at the [official plugin channel](https://github.com/micro-editor/plugin-channel),
|
||||
adding a link to the raw `repo.json` that is in your plugin repository.
|
||||
|
||||
To make updating the plugin work, the first line of your plugin's lua code
|
||||
should contain the version of the plugin. (Like this: `VERSION = "1.0.0"`)
|
||||
Please make sure to use [semver](http://semver.org/) for versioning.
|
||||
Please make sure to use [semver](https://semver.org/) for versioning.
|
||||
|
||||
@@ -3,6 +3,7 @@ VERSION = "1.0.0"
|
||||
local util = import("micro/util")
|
||||
local config = import("micro/config")
|
||||
local buffer = import("micro/buffer")
|
||||
local micro = import("micro")
|
||||
|
||||
local ft = {}
|
||||
|
||||
@@ -54,6 +55,7 @@ ft["swift"] = "// %s"
|
||||
ft["tex"] = "% %s"
|
||||
ft["toml"] = "# %s"
|
||||
ft["twig"] = "{# %s #}"
|
||||
ft["typescript"] = "// %s"
|
||||
ft["v"] = "// %s"
|
||||
ft["xml"] = "<!-- %s -->"
|
||||
ft["yaml"] = "# %s"
|
||||
@@ -61,17 +63,21 @@ ft["zig"] = "// %s"
|
||||
ft["zscript"] = "// %s"
|
||||
ft["zsh"] = "# %s"
|
||||
|
||||
local last_ft
|
||||
|
||||
function updateCommentType(buf)
|
||||
if buf.Settings["commenttype"] == nil or (last_ft ~= buf.Settings["filetype"] and last_ft ~= nil) then
|
||||
if ft[buf.Settings["filetype"]] ~= nil then
|
||||
buf:SetOptionNative("commenttype", ft[buf.Settings["filetype"]])
|
||||
-- NOTE: Using DoSetOptionNative to avoid LocalSettings[option] = true
|
||||
-- so that "comment.type" can be reset by a "filetype" change to default.
|
||||
if (buf.Settings["comment.type"] == "") then
|
||||
-- NOTE: This won't get triggered if a filetype is change via `setlocal filetype`
|
||||
-- since it is not registered with `RegisterGlobalOption()``
|
||||
if buf.Settings["commenttype"] ~= nil then
|
||||
buf:DoSetOptionNative("comment.type", buf.Settings["commenttype"])
|
||||
else
|
||||
buf:SetOptionNative("commenttype", "# %s")
|
||||
if (ft[buf.Settings["filetype"]] ~= nil) then
|
||||
buf:DoSetOptionNative("comment.type", ft[buf.Settings["filetype"]])
|
||||
else
|
||||
buf:DoSetOptionNative("comment.type", "# %s")
|
||||
end
|
||||
end
|
||||
|
||||
last_ft = buf.Settings["filetype"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -88,7 +94,7 @@ function commentLine(bp, lineN, indentLen)
|
||||
updateCommentType(bp.Buf)
|
||||
|
||||
local line = bp.Buf:Line(lineN)
|
||||
local commentType = bp.Buf.Settings["commenttype"]
|
||||
local commentType = bp.Buf.Settings["comment.type"]
|
||||
local sel = -bp.Cursor.CurSelection
|
||||
local curpos = -bp.Cursor.Loc
|
||||
local index = string.find(commentType, "%%s") - 1
|
||||
@@ -107,14 +113,14 @@ function commentLine(bp, lineN, indentLen)
|
||||
bp.Cursor.Y = curpos.Y
|
||||
end
|
||||
bp.Cursor:Relocate()
|
||||
bp.Cursor.LastVisualX = bp.Cursor:GetVisualX()
|
||||
bp.Cursor:StoreVisualX()
|
||||
end
|
||||
|
||||
function uncommentLine(bp, lineN, commentRegex)
|
||||
updateCommentType(bp.Buf)
|
||||
|
||||
local line = bp.Buf:Line(lineN)
|
||||
local commentType = bp.Buf.Settings["commenttype"]
|
||||
local commentType = bp.Buf.Settings["comment.type"]
|
||||
local sel = -bp.Cursor.CurSelection
|
||||
local curpos = -bp.Cursor.Loc
|
||||
local index = string.find(commentType, "%%s") - 1
|
||||
@@ -135,7 +141,7 @@ function uncommentLine(bp, lineN, commentRegex)
|
||||
end
|
||||
end
|
||||
bp.Cursor:Relocate()
|
||||
bp.Cursor.LastVisualX = bp.Cursor:GetVisualX()
|
||||
bp.Cursor:StoreVisualX()
|
||||
end
|
||||
|
||||
function toggleCommentLine(bp, lineN, commentRegex)
|
||||
@@ -178,7 +184,7 @@ end
|
||||
function comment(bp, args)
|
||||
updateCommentType(bp.Buf)
|
||||
|
||||
local commentType = bp.Buf.Settings["commenttype"]
|
||||
local commentType = bp.Buf.Settings["comment.type"]
|
||||
local commentRegex = "^%s*" .. commentType:gsub("%%","%%%%"):gsub("%$","%$"):gsub("%)","%)"):gsub("%(","%("):gsub("%?","%?"):gsub("%*", "%*"):gsub("%-", "%-"):gsub("%.", "%."):gsub("%+", "%+"):gsub("%]", "%]"):gsub("%[", "%["):gsub("%%%%s", "(.*)")
|
||||
|
||||
if bp.Cursor:HasSelection() then
|
||||
@@ -204,6 +210,10 @@ function string.starts(String,Start)
|
||||
return string.sub(String,1,string.len(Start))==Start
|
||||
end
|
||||
|
||||
function preinit()
|
||||
config.RegisterCommonOption("comment", "type", "")
|
||||
end
|
||||
|
||||
function init()
|
||||
config.MakeCommand("comment", comment, config.NoComplete)
|
||||
config.TryBindKey("Alt-/", "lua:comment.comment", false)
|
||||
|
||||
@@ -80,10 +80,10 @@ but it is only available for certain filetypes:
|
||||
* zsh: `# %s`
|
||||
|
||||
If your filetype is not available here, you can simply modify
|
||||
the `commenttype` option:
|
||||
the `comment.type` option:
|
||||
|
||||
```
|
||||
set commenttype "/* %s */"
|
||||
set comment.type "/* %s */"
|
||||
```
|
||||
|
||||
Or in your `settings.json`:
|
||||
@@ -91,7 +91,12 @@ Or in your `settings.json`:
|
||||
```json
|
||||
{
|
||||
"*.c": {
|
||||
"commenttype": "/* %s */"
|
||||
"comment.type": "/* %s */"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`commenttype` (without the dot) is the legacy option that is
|
||||
superseded by `comment.type`. `commenttype` is still supported
|
||||
but deprecated.
|
||||
**Use `comment.type` instead.**
|
||||
|
||||
@@ -8,7 +8,10 @@ following filetypes and linters:
|
||||
* **c**: gcc
|
||||
* **c++**: g++
|
||||
* **d**: dmd
|
||||
* **d**: ldc2
|
||||
* **d**: gdc
|
||||
* **go**: go build
|
||||
* **go**: go vet
|
||||
* **haskell**: hlint
|
||||
* **java**: javac
|
||||
* **javascript**: jshint
|
||||
@@ -16,11 +19,16 @@ following filetypes and linters:
|
||||
* **literate**: lit
|
||||
* **lua**: luacheck
|
||||
* **nim**: nim
|
||||
* **nix**: nix-linter
|
||||
* **objective-c**: clang
|
||||
* **python**: pyflakes
|
||||
* **python**: flake8
|
||||
* **python**: mypy
|
||||
* **python**: pyflakes
|
||||
* **python**: pylint
|
||||
* **python**: ruff
|
||||
* **rust**: cargo clippy
|
||||
* **shell**: shfmt
|
||||
* **shell**: shellcheck
|
||||
* **swift**: swiftc (MacOS and Linux only)
|
||||
* **yaml**: yamllint
|
||||
|
||||
|
||||
@@ -66,8 +66,10 @@ function preinit()
|
||||
end
|
||||
|
||||
makeLinter("gcc", "c", "gcc", {"-fsyntax-only", "-Wall", "-Wextra", "%f"}, "%f:%l:%c:.+: %m")
|
||||
makeLinter("g++", "c++", "gcc", {"-fsyntax-only","-std=c++14", "-Wall", "-Wextra", "%f"}, "%f:%l:%c:.+: %m")
|
||||
makeLinter("g++", "c++", "g++", {"-fsyntax-only","-Wall", "-Wextra", "%f"}, "%f:%l:%c:.+: %m")
|
||||
makeLinter("dmd", "d", "dmd", {"-color=off", "-o-", "-w", "-wi", "-c", "%f"}, "%f%(%l%):.+: %m")
|
||||
makeLinter("ldc2", "d", "ldc2", {"--o-", "--vcolumns", "-w", "-c", "%f"}, "%f%(%l,%c%):[^:]+: %m")
|
||||
makeLinter("gdc", "d", "gdc", {"-fsyntax-only","-Wall", "-Wextra", "%f"}, "%f:%l:%c:.+: %m")
|
||||
makeLinter("eslint", "javascript", "eslint", {"-f","compact","%f"}, "%f: line %l, col %c, %m")
|
||||
makeLinter("gobuild", "go", "go", {"build", "-o", devnull, "%d"}, "%f:%l:%c:? %m")
|
||||
makeLinter("govet", "go", "go", {"vet"}, "%f:%l:%c: %m")
|
||||
@@ -82,6 +84,7 @@ function preinit()
|
||||
makeLinter("pyflakes", "python", "pyflakes", {"%f"}, "%f:%l:.-:? %m")
|
||||
makeLinter("mypy", "python", "mypy", {"%f"}, "%f:%l: %m")
|
||||
makeLinter("pylint", "python", "pylint", {"--output-format=parseable", "--reports=no", "%f"}, "%f:%l: %m")
|
||||
makeLinter("ruff", "python", "ruff", {"check", "--output-format=concise", "%f"}, "%f:%l:%c: %m")
|
||||
makeLinter("flake8", "python", "flake8", {"%f"}, "%f:%l:%c: %m")
|
||||
makeLinter("shfmt", "shell", "shfmt", {"%f"}, "%f:%l:%c: %m")
|
||||
makeLinter("shellcheck", "shell", "shellcheck", {"-f", "gcc", "%f"}, "%f:%l:%c:.+: %m")
|
||||
@@ -107,26 +110,29 @@ function contains(list, element)
|
||||
return false
|
||||
end
|
||||
|
||||
function checkFtMatch(ft, v)
|
||||
local ftmatch = ft == v.filetype
|
||||
if v.domatch then
|
||||
ftmatch = string.match(ft, v.filetype)
|
||||
end
|
||||
|
||||
local hasOS = contains(v.os, runtime.GOOS)
|
||||
if not hasOS and v.whitelist then
|
||||
ftmatch = false
|
||||
end
|
||||
if hasOS and not v.whitelist then
|
||||
ftmatch = false
|
||||
end
|
||||
return ftmatch
|
||||
end
|
||||
|
||||
function runLinter(buf)
|
||||
local ft = buf:FileType()
|
||||
local file = buf.Path
|
||||
local dir = "." .. util.RuneStr(os.PathSeparator) .. filepath.Dir(file)
|
||||
|
||||
for k, v in pairs(linters) do
|
||||
local ftmatch = ft == v.filetype
|
||||
if v.domatch then
|
||||
ftmatch = string.match(ft, v.filetype)
|
||||
end
|
||||
|
||||
local hasOS = contains(v.os, runtime.GOOS)
|
||||
if not hasOS and v.whitelist then
|
||||
ftmatch = false
|
||||
end
|
||||
if hasOS and not v.whitelist then
|
||||
ftmatch = false
|
||||
end
|
||||
|
||||
if ftmatch then
|
||||
if checkFtMatch(ft, v) then
|
||||
local args = {}
|
||||
for k, arg in pairs(v.args) do
|
||||
args[k] = arg:gsub("%%f", file):gsub("%%d", dir)
|
||||
@@ -141,6 +147,19 @@ function onSave(bp)
|
||||
return true
|
||||
end
|
||||
|
||||
function onBufferOptionChanged(buf, option, old, new)
|
||||
if option == "filetype" then
|
||||
if old ~= new then
|
||||
for k, v in pairs(linters) do
|
||||
if checkFtMatch(old, v) then
|
||||
buf:ClearMessages(k)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function lint(buf, linter, cmd, args, errorformat, loff, coff, callback)
|
||||
buf:ClearMessages(linter)
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ function onBufferOpen(buf)
|
||||
syntaxFile = syntaxFile .. " - special:\n"
|
||||
syntaxFile = syntaxFile .. " start: \"@\\\\{\"\n"
|
||||
syntaxFile = syntaxFile .. " end: \"\\\\}\"\n"
|
||||
syntaxFile = syntaxFile .. " rules: []\n"
|
||||
syntaxFile = syntaxFile .. " - include: " .. codetype .. "\n"
|
||||
|
||||
config.AddRuntimeFileFromMemory(config.RTSyntax, "literate.yaml", syntaxFile)
|
||||
|
||||
@@ -8,8 +8,10 @@ those options (`> help options`) for more information.
|
||||
|
||||
This plugin provides functions that can be used in the status line format:
|
||||
|
||||
* `status.branch`: returns the name of the current git branch.
|
||||
* `status.hash`: returns the hash of the current git commit.
|
||||
* `status.branch`: returns the name of the current git branch in the repository
|
||||
where the file is located.
|
||||
* `status.hash`: returns the hash of the current git commit in the repository
|
||||
where the file is located.
|
||||
* `status.paste`: returns "" if the paste option is disabled and "PASTE"
|
||||
if it is enabled.
|
||||
* `status.lines`: returns the number of lines in the buffer.
|
||||
|
||||
@@ -3,7 +3,10 @@ VERSION = "1.0.0"
|
||||
local micro = import("micro")
|
||||
local buffer = import("micro/buffer")
|
||||
local config = import("micro/config")
|
||||
local shell = import("micro/shell")
|
||||
local filepath = import("filepath")
|
||||
local humanize = import("humanize")
|
||||
local strings = import("strings")
|
||||
|
||||
function init()
|
||||
micro.SetStatusInfoFn("status.branch")
|
||||
@@ -21,7 +24,7 @@ function lines(b)
|
||||
end
|
||||
|
||||
function vcol(b)
|
||||
return tostring(b:GetActiveCursor():GetVisualX())
|
||||
return tostring(b:GetActiveCursor():GetVisualX(false))
|
||||
end
|
||||
|
||||
function bytes(b)
|
||||
@@ -32,30 +35,23 @@ function size(b)
|
||||
return humanize.Bytes(b:Size())
|
||||
end
|
||||
|
||||
function branch(b)
|
||||
local function parseRevision(b, opt)
|
||||
if b.Type.Kind ~= buffer.BTInfo then
|
||||
local shell = import("micro/shell")
|
||||
local strings = import("strings")
|
||||
|
||||
local branch, err = shell.ExecCommand("git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
local dir = filepath.Dir(b.Path)
|
||||
local str, err = shell.ExecCommand("git", "-C", dir, "rev-parse", opt, "HEAD")
|
||||
if err == nil then
|
||||
return strings.TrimSpace(branch)
|
||||
return strings.TrimSpace(str)
|
||||
end
|
||||
return ""
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
function branch(b)
|
||||
return parseRevision(b, "--abbrev-ref")
|
||||
end
|
||||
|
||||
function hash(b)
|
||||
if b.Type.Kind ~= 5 then
|
||||
local shell = import("micro/shell")
|
||||
local strings = import("strings")
|
||||
|
||||
local hash, err = shell.ExecCommand("git", "rev-parse", "--short", "HEAD")
|
||||
if err == nil then
|
||||
return strings.TrimSpace(hash)
|
||||
end
|
||||
return ""
|
||||
end
|
||||
return parseRevision(b, "--short")
|
||||
end
|
||||
|
||||
function paste(b)
|
||||
|
||||
@@ -6,11 +6,6 @@ Each yaml file specifies how to detect the filetype based on file extension or h
|
||||
In addition, a signature can be provided to help resolving ambiguities when multiple matching filetypes are detected.
|
||||
Then there are patterns and regions linked to highlight groups which tell micro how to highlight that filetype.
|
||||
|
||||
Making your own syntax files is very simple. I recommend you check the file after you are finished with the
|
||||
[`syntax_checker.go`](./syntax_checker.go) program (located in this directory). Just place your yaml syntax
|
||||
file in the current directory and run `go run syntax_checker.go` and it will check every file. If there are no
|
||||
errors it will print `No issues!`.
|
||||
|
||||
You can read more about how to write syntax files (and colorschemes) in the [colors](../help/colors.md) documentation.
|
||||
|
||||
# Legacy '.micro' filetype
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
filetype: ino
|
||||
|
||||
detect:
|
||||
filename: "\\.?ino$"
|
||||
filename: "\\.ino$"
|
||||
|
||||
rules:
|
||||
- identifier: "\\b[A-Z_][0-9A-Z_]+\\b"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user