mirror of
https://github.com/zyedidia/micro.git
synced 2025-06-18 23:05:40 -04:00
Compare commits
221 Commits
v2.0.14-rc
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 |
2
.github/workflows/nightly.yaml
vendored
2
.github/workflows/nightly.yaml
vendored
@ -7,7 +7,7 @@ jobs:
|
|||||||
nightly:
|
nightly:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.19.x]
|
go-version: [1.23.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
|
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@ -8,7 +8,7 @@ jobs:
|
|||||||
release:
|
release:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.19.x]
|
go-version: [1.23.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
|
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
@ -4,7 +4,7 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.19.x]
|
go-version: [1.19.x, 1.23.x]
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
|
@ -430,7 +430,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||||||
|
|
||||||
github.com/gdamore/tcell/LICENSE
|
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/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.
|
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/james4k/terminal/LICENSE
|
||||||
================
|
================
|
||||||
github.com/zyedidia/terminal/LICENSE (fork)
|
github.com/micro-editor/terminal/LICENSE (fork)
|
||||||
================
|
================
|
||||||
|
|
||||||
Copyright (C) 2013 James Gray
|
Copyright (C) 2013 James Gray
|
||||||
|
23
Makefile
23
Makefile
@ -5,24 +5,31 @@ VERSION = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH
|
|||||||
HASH = $(shell git rev-parse --short HEAD)
|
HASH = $(shell git rev-parse --short HEAD)
|
||||||
DATE = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
DATE = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
||||||
go run tools/build-date.go)
|
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
|
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)'
|
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
|
DEBUGVAR = -X github.com/zyedidia/micro/v2/internal/util.Debug=ON
|
||||||
VSCODE_TESTS_BASE_URL = 'https://raw.githubusercontent.com/microsoft/vscode/e6a45f4242ebddb7aa9a229f85555e8a3bd987e2/src/vs/editor/test/common/model/'
|
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: generate build-quick
|
||||||
|
|
||||||
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:
|
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
|
build-tags: fetch-tags build
|
||||||
go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
|
||||||
|
|
||||||
build-all: build
|
build-all: build
|
||||||
|
|
||||||
@ -32,7 +39,7 @@ install: generate
|
|||||||
install-all: install
|
install-all: install
|
||||||
|
|
||||||
fetch-tags:
|
fetch-tags:
|
||||||
git fetch --tags
|
git fetch --tags --force
|
||||||
|
|
||||||
generate:
|
generate:
|
||||||
GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) go generate ./runtime
|
GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) go generate ./runtime
|
||||||
|
36
README.md
36
README.md
@ -21,22 +21,6 @@ To see more screenshots of micro, showcasing some of the default color schemes,
|
|||||||
|
|
||||||
You can also check out the website for Micro at 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
|
## 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).
|
- Syntax highlighting for over [130 languages](runtime/syntax).
|
||||||
- Color scheme support.
|
- Color scheme support.
|
||||||
- By default, micro comes with 16, 256, and true color themes.
|
- 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.
|
- Copy and paste with the system clipboard.
|
||||||
- Small and simple.
|
- Small and simple.
|
||||||
- Easily configurable.
|
- Easily configurable.
|
||||||
@ -88,7 +72,7 @@ Pre-built binaries are distributed in [releases](https://github.com/zyedidia/mic
|
|||||||
|
|
||||||
To uninstall micro, simply remove the binary, and the configuration directory at `~/.config/micro`.
|
To uninstall micro, simply remove the binary, and the configuration directory at `~/.config/micro`.
|
||||||
|
|
||||||
#### Quick-install script
|
#### Third-party quick-install script
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl https://getmic.ro | bash
|
curl https://getmic.ro | bash
|
||||||
@ -178,7 +162,7 @@ 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.
|
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/zyedidia/micro
|
||||||
@ -196,16 +180,20 @@ You can install directly with `go get` (`go get github.com/zyedidia/micro/cmd/mi
|
|||||||
recommended because it doesn't build micro with version information (necessary for the plugin manager),
|
recommended because it doesn't build micro with version information (necessary for the plugin manager),
|
||||||
and doesn't disable debug mode.
|
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
|
By default, the micro binary is linked statically to increase the portability of the prebuilt binaries.
|
||||||
recommended for security and portability). However, there is a fully static prebuilt binary that
|
This behavior can simply be overriden by providing `CGO_ENABLED=1` to the build target.
|
||||||
is provided for amd64 as `linux-static.tar.gz`, and to build a fully static binary from source, run
|
|
||||||
|
|
||||||
```
|
```
|
||||||
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
|
### 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](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.
|
||||||
|
@ -3,8 +3,8 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func shouldContinue() bool {
|
func shouldContinue() bool {
|
||||||
@ -39,7 +40,16 @@ func CleanConfig() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Cleaning default settings")
|
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
|
// detect unused options
|
||||||
var unusedOptions []string
|
var unusedOptions []string
|
||||||
@ -67,16 +77,20 @@ func CleanConfig() {
|
|||||||
fmt.Printf("%s (value: %v)\n", s, config.GlobalSettings[s])
|
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() {
|
if shouldContinue() {
|
||||||
for _, s := range unusedOptions {
|
for _, s := range unusedOptions {
|
||||||
delete(config.GlobalSettings, s)
|
delete(config.GlobalSettings, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := config.OverwriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
|
err := config.OverwriteSettings(settingsFile)
|
||||||
if err != nil {
|
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")
|
fmt.Println("Removed unused options")
|
||||||
@ -85,12 +99,13 @@ func CleanConfig() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// detect incorrectly formatted buffer/ files
|
// 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 {
|
if err == nil {
|
||||||
var badFiles []string
|
var badFiles []string
|
||||||
var buffer buffer.SerializedBuffer
|
var buffer buffer.SerializedBuffer
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
fname := filepath.Join(config.ConfigDir, "buffers", f.Name())
|
fname := filepath.Join(buffersPath, f.Name())
|
||||||
file, e := os.Open(fname)
|
file, e := os.Open(fname)
|
||||||
|
|
||||||
if e == nil {
|
if e == nil {
|
||||||
@ -105,9 +120,9 @@ func CleanConfig() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(badFiles) > 0 {
|
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.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() {
|
if shouldContinue() {
|
||||||
removed := 0
|
removed := 0
|
||||||
|
@ -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
|
// InitLog sets up the debug log system for micro if it has been enabled by compile-time variables
|
||||||
func InitLog() {
|
func InitLog() {
|
||||||
if util.Debug == "ON" {
|
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 {
|
if err != nil {
|
||||||
log.Fatalf("error opening file: %v", err)
|
log.Fatalf("error opening file: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,10 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
@ -18,6 +18,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
isatty "github.com/mattn/go-isatty"
|
isatty "github.com/mattn/go-isatty"
|
||||||
|
"github.com/micro-editor/tcell/v2"
|
||||||
lua "github.com/yuin/gopher-lua"
|
lua "github.com/yuin/gopher-lua"
|
||||||
"github.com/zyedidia/micro/v2/internal/action"
|
"github.com/zyedidia/micro/v2/internal/action"
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
@ -26,7 +27,6 @@ import (
|
|||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/micro/v2/internal/shell"
|
"github.com/zyedidia/micro/v2/internal/shell"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -99,7 +99,7 @@ func InitFlags() {
|
|||||||
fmt.Println("Version:", util.Version)
|
fmt.Println("Version:", util.Version)
|
||||||
fmt.Println("Commit hash:", util.CommitHash)
|
fmt.Println("Commit hash:", util.CommitHash)
|
||||||
fmt.Println("Compiled on", util.CompileDate)
|
fmt.Println("Compiled on", util.CompileDate)
|
||||||
os.Exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *flagOptions {
|
if *flagOptions {
|
||||||
@ -115,7 +115,7 @@ func InitFlags() {
|
|||||||
fmt.Printf("-%s value\n", k)
|
fmt.Printf("-%s value\n", k)
|
||||||
fmt.Printf(" \tDefault value: '%v'\n", v)
|
fmt.Printf(" \tDefault value: '%v'\n", v)
|
||||||
}
|
}
|
||||||
os.Exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if util.Debug == "OFF" && *flagDebug {
|
if util.Debug == "OFF" && *flagDebug {
|
||||||
@ -136,7 +136,7 @@ func DoPluginFlags() {
|
|||||||
CleanConfig()
|
CleanConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +209,7 @@ func LoadInput(args []string) []*buffer.Buffer {
|
|||||||
// Option 2
|
// Option 2
|
||||||
// The input is not a terminal, so something is being piped in
|
// The input is not a terminal, so something is being piped in
|
||||||
// and we should read from stdin
|
// and we should read from stdin
|
||||||
input, err = ioutil.ReadAll(os.Stdin)
|
input, err = io.ReadAll(os.Stdin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
screen.TermMessage("Error reading from stdin: ", err)
|
screen.TermMessage("Error reading from stdin: ", err)
|
||||||
input = []byte{}
|
input = []byte{}
|
||||||
@ -223,12 +223,55 @@ func LoadInput(args []string) []*buffer.Buffer {
|
|||||||
return buffers
|
return buffers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkBackup(name string) error {
|
||||||
|
target := filepath.Join(config.ConfigDir, name)
|
||||||
|
backup := util.AppendBackupSuffix(target)
|
||||||
|
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() {
|
func main() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if util.Stdout.Len() > 0 {
|
if util.Stdout.Len() > 0 {
|
||||||
fmt.Fprint(os.Stdout, util.Stdout.String())
|
fmt.Fprint(os.Stdout, util.Stdout.String())
|
||||||
}
|
}
|
||||||
os.Exit(0)
|
exit(0)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@ -256,6 +299,12 @@ func main() {
|
|||||||
config.InitRuntimeFiles(true)
|
config.InitRuntimeFiles(true)
|
||||||
config.InitPlugins()
|
config.InitPlugins()
|
||||||
|
|
||||||
|
err = checkBackup("settings.json")
|
||||||
|
if err != nil {
|
||||||
|
screen.TermMessage(err)
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
err = config.ReadSettings()
|
err = config.ReadSettings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
screen.TermMessage(err)
|
screen.TermMessage(err)
|
||||||
@ -288,7 +337,7 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
fmt.Println("Fatal: Micro could not initialize a Screen.")
|
fmt.Println("Fatal: Micro could not initialize a Screen.")
|
||||||
os.Exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
m := clipboard.SetMethod(config.GetGlobalOption("clipboard").(string))
|
m := clipboard.SetMethod(config.GetGlobalOption("clipboard").(string))
|
||||||
clipErr := clipboard.Initialize(m)
|
clipErr := clipboard.Initialize(m)
|
||||||
@ -307,7 +356,7 @@ func main() {
|
|||||||
for _, b := range buffer.OpenBuffers {
|
for _, b := range buffer.OpenBuffers {
|
||||||
b.Backup()
|
b.Backup()
|
||||||
}
|
}
|
||||||
os.Exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -316,6 +365,12 @@ func main() {
|
|||||||
screen.TermMessage(err)
|
screen.TermMessage(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = checkBackup("bindings.json")
|
||||||
|
if err != nil {
|
||||||
|
screen.TermMessage(err)
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
action.InitBindings()
|
action.InitBindings()
|
||||||
action.InitCommands()
|
action.InitCommands()
|
||||||
|
|
||||||
@ -434,24 +489,12 @@ func DoEvent() {
|
|||||||
}
|
}
|
||||||
case f := <-timerChan:
|
case f := <-timerChan:
|
||||||
f()
|
f()
|
||||||
|
case b := <-buffer.BackupCompleteChan:
|
||||||
|
b.RequestedBackup = false
|
||||||
case <-sighup:
|
case <-sighup:
|
||||||
for _, b := range buffer.OpenBuffers {
|
exit(0)
|
||||||
if !b.Modified() {
|
|
||||||
b.Fini()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
case <-util.Sigterm:
|
case <-util.Sigterm:
|
||||||
for _, b := range buffer.OpenBuffers {
|
exit(0)
|
||||||
if !b.Modified() {
|
|
||||||
b.Fini()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if screen.Screen != nil {
|
|
||||||
screen.Screen.Fini()
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if e, ok := event.(*tcell.EventError); ok {
|
if e, ok := event.(*tcell.EventError); ok {
|
||||||
@ -459,16 +502,7 @@ func DoEvent() {
|
|||||||
|
|
||||||
if e.Err() == io.EOF {
|
if e.Err() == io.EOF {
|
||||||
// shutdown due to terminal closing/becoming inaccessible
|
// shutdown due to terminal closing/becoming inaccessible
|
||||||
for _, b := range buffer.OpenBuffers {
|
exit(0)
|
||||||
if !b.Modified() {
|
|
||||||
b.Fini()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if screen.Screen != nil {
|
|
||||||
screen.Screen.Fini()
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,17 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
|
"github.com/micro-editor/tcell/v2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zyedidia/micro/v2/internal/action"
|
"github.com/zyedidia/micro/v2/internal/action"
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var tempDir string
|
var tempDir string
|
||||||
@ -26,7 +25,7 @@ func init() {
|
|||||||
func startup(args []string) (tcell.SimulationScreen, error) {
|
func startup(args []string) (tcell.SimulationScreen, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
tempDir, err = ioutil.TempDir("", "micro_test")
|
tempDir, err = os.MkdirTemp("", "micro_test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -164,20 +163,22 @@ func findBuffer(file string) *buffer.Buffer {
|
|||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTestFile(name string, content string) (string, error) {
|
func createTestFile(t *testing.T, content string) string {
|
||||||
testf, err := ioutil.TempFile("", name)
|
f, err := os.CreateTemp(t.TempDir(), "")
|
||||||
if err != nil {
|
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 f.Name()
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if err := testf.Close(); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return testf.Name(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
@ -194,18 +195,12 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSimpleEdit(t *testing.T) {
|
func TestSimpleEdit(t *testing.T) {
|
||||||
file, err := createTestFile("micro_simple_edit_test", "base content")
|
file := createTestFile(t, "base content")
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.Remove(file)
|
|
||||||
|
|
||||||
openFile(file)
|
openFile(file)
|
||||||
|
|
||||||
if findBuffer(file) == nil {
|
if findBuffer(file) == nil {
|
||||||
t.Errorf("Could not find buffer %s", file)
|
t.Fatalf("Could not find buffer %s", file)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
||||||
@ -223,28 +218,21 @@ func TestSimpleEdit(t *testing.T) {
|
|||||||
|
|
||||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(file)
|
data, err := os.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Fatal(err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, "firstfoobar\nbase content\n", string(data))
|
assert.Equal(t, "firstfoobar\nbase content\n", string(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMouse(t *testing.T) {
|
func TestMouse(t *testing.T) {
|
||||||
file, err := createTestFile("micro_mouse_test", "base content")
|
file := createTestFile(t, "base content")
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.Remove(file)
|
|
||||||
|
|
||||||
openFile(file)
|
openFile(file)
|
||||||
|
|
||||||
if findBuffer(file) == nil {
|
if findBuffer(file) == nil {
|
||||||
t.Errorf("Could not find buffer %s", file)
|
t.Fatalf("Could not find buffer %s", file)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// buffer:
|
// buffer:
|
||||||
@ -275,10 +263,9 @@ func TestMouse(t *testing.T) {
|
|||||||
// base content
|
// base content
|
||||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(file)
|
data, err := os.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Fatal(err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, "firstline\nsecondline\nbase content\n", string(data))
|
assert.Equal(t, "firstline\nsecondline\nbase content\n", string(data))
|
||||||
@ -301,18 +288,12 @@ Ernleȝe test_string æðelen
|
|||||||
`
|
`
|
||||||
|
|
||||||
func TestSearchAndReplace(t *testing.T) {
|
func TestSearchAndReplace(t *testing.T) {
|
||||||
file, err := createTestFile("micro_search_replace_test", srTestStart)
|
file := createTestFile(t, srTestStart)
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.Remove(file)
|
|
||||||
|
|
||||||
openFile(file)
|
openFile(file)
|
||||||
|
|
||||||
if findBuffer(file) == nil {
|
if findBuffer(file) == nil {
|
||||||
t.Errorf("Could not find buffer %s", file)
|
t.Fatalf("Could not find buffer %s", file)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
|
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
|
||||||
@ -321,10 +302,9 @@ func TestSearchAndReplace(t *testing.T) {
|
|||||||
|
|
||||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(file)
|
data, err := os.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Fatal(err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, srTest2, string(data))
|
assert.Equal(t, srTest2, string(data))
|
||||||
@ -337,10 +317,9 @@ func TestSearchAndReplace(t *testing.T) {
|
|||||||
|
|
||||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||||
|
|
||||||
data, err = ioutil.ReadFile(file)
|
data, err = os.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Fatal(err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, srTest3, string(data))
|
assert.Equal(t, srTest3, string(data))
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
<category>TextEditor</category>
|
<category>TextEditor</category>
|
||||||
</categories>
|
</categories>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="2.0.14" date="2024-08-27"/>
|
||||||
<release version="2.0.13" date="2023-10-22"/>
|
<release version="2.0.13" date="2023-10-22"/>
|
||||||
<release version="2.0.12" date="2023-09-06"/>
|
<release version="2.0.12" date="2023-09-06"/>
|
||||||
<release version="2.0.11" date="2022-08-01"/>
|
<release version="2.0.11" date="2022-08-01"/>
|
||||||
|
33
go.mod
33
go.mod
@ -5,26 +5,35 @@ require (
|
|||||||
github.com/dustin/go-humanize v1.0.0
|
github.com/dustin/go-humanize v1.0.0
|
||||||
github.com/go-errors/errors v1.0.1
|
github.com/go-errors/errors v1.0.1
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||||
github.com/mattn/go-isatty v0.0.11
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/mattn/go-runewidth v0.0.7
|
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.11
|
||||||
|
github.com/micro-editor/terminal v0.0.0-20250324214352-e587e959c6b5
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/sergi/go-diff v1.1.0
|
github.com/sergi/go-diff v1.1.0
|
||||||
github.com/stretchr/testify v1.4.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/clipper v0.1.1
|
||||||
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3
|
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3
|
||||||
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d
|
golang.org/x/text v0.4.0
|
||||||
github.com/zyedidia/tcell/v2 v2.0.10 // indirect
|
|
||||||
github.com/zyedidia/terminal v0.0.0-20230315200948-4b3bcf6dddef
|
|
||||||
golang.org/x/text v0.3.8
|
|
||||||
gopkg.in/yaml.v2 v2.2.8
|
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.28.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
|
||||||
|
80
go.sum
80
go.sum
@ -19,84 +19,58 @@ 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/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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/layeh/gopher-luar v1.0.7/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=
|
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 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
||||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
|
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.11 h1:USjdpBSmbocx2yPARbY19KcUSj+ZerScrdmBqGjzoX4=
|
||||||
|
github.com/micro-editor/tcell/v2 v2.0.11/go.mod h1:kVYk6NOwYJrboL/7IA7cCupk4o2NzyF/0UMLjeEJN/s=
|
||||||
|
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 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
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 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
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/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 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
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/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-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
|
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
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 h1:HBgguFNDq/QmSQKBnhy4sMKzILINr139VEgAhftOUTw=
|
||||||
github.com/zyedidia/clipper v0.1.1/go.mod h1:7YApPNiiTZTXdKKZG92G50qj6mnWEX975Sdu65J7YpQ=
|
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 h1:oMHjjTLfGXVuyOQBYj5/td9WC0mw4g1xDBPovIqmHew=
|
||||||
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3/go.mod h1:YKbIYP//Eln8eDgAJGI3IDvR3s4Tv9Z9TGIOumiyQ5c=
|
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 h1:Tt9S3AxAjXwWGNiC2TUdRJkQDZSzCBNVQ4xXiQ7440s=
|
||||||
github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
|
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-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-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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.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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
shellquote "github.com/kballard/go-shellquote"
|
shellquote "github.com/kballard/go-shellquote"
|
||||||
|
"github.com/micro-editor/tcell/v2"
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
"github.com/zyedidia/micro/v2/internal/clipboard"
|
"github.com/zyedidia/micro/v2/internal/clipboard"
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
@ -18,7 +19,6 @@ import (
|
|||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/micro/v2/internal/shell"
|
"github.com/zyedidia/micro/v2/internal/shell"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ScrollUp is not an action
|
// ScrollUp is not an action
|
||||||
@ -46,6 +46,14 @@ func (h *BufPane) ScrollAdjust() {
|
|||||||
h.SetView(v)
|
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
|
// MousePress is the event that should happen when a normal click happens
|
||||||
// This is almost always bound to left click
|
// This is almost always bound to left click
|
||||||
func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
|
func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
|
||||||
@ -65,12 +73,12 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
|
|||||||
h.Cursor.Loc = mouseLoc
|
h.Cursor.Loc = mouseLoc
|
||||||
}
|
}
|
||||||
if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
|
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
|
// Triple click
|
||||||
h.lastClickTime = time.Now()
|
h.lastClickTime = time.Now()
|
||||||
|
|
||||||
h.tripleClick = true
|
h.TripleClick = true
|
||||||
h.doubleClick = false
|
h.DoubleClick = false
|
||||||
|
|
||||||
h.Cursor.SelectLine()
|
h.Cursor.SelectLine()
|
||||||
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
||||||
@ -78,15 +86,15 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
|
|||||||
// Double click
|
// Double click
|
||||||
h.lastClickTime = time.Now()
|
h.lastClickTime = time.Now()
|
||||||
|
|
||||||
h.doubleClick = true
|
h.DoubleClick = true
|
||||||
h.tripleClick = false
|
h.TripleClick = false
|
||||||
|
|
||||||
h.Cursor.SelectWord()
|
h.Cursor.SelectWord()
|
||||||
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
h.doubleClick = false
|
h.DoubleClick = false
|
||||||
h.tripleClick = false
|
h.TripleClick = false
|
||||||
h.lastClickTime = time.Now()
|
h.lastClickTime = time.Now()
|
||||||
|
|
||||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
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})
|
h.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my})
|
||||||
|
|
||||||
if h.tripleClick {
|
if h.TripleClick {
|
||||||
h.Cursor.AddLineToSelection()
|
h.Cursor.AddLineToSelection()
|
||||||
} else if h.doubleClick {
|
} else if h.DoubleClick {
|
||||||
h.Cursor.AddWordToSelection()
|
h.Cursor.AddWordToSelection()
|
||||||
} else {
|
} else {
|
||||||
h.Cursor.SelectTo(h.Cursor.Loc)
|
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
|
// that doesn't support mouse motion events. But when the mouse click is
|
||||||
// within the scroll margin, that would cause a scroll and selection
|
// within the scroll margin, that would cause a scroll and selection
|
||||||
// even for a simple mouse click, which is not good.
|
// even for a simple mouse click, which is not good.
|
||||||
// if !h.doubleClick && !h.tripleClick {
|
// if !h.DoubleClick && !h.TripleClick {
|
||||||
// mx, my := e.Position()
|
// mx, my := e.Position()
|
||||||
// h.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my})
|
// h.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my})
|
||||||
// h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
// h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
||||||
@ -145,7 +153,7 @@ func (h *BufPane) ScrollUpAction() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScrollDownAction scrolls the view up
|
// ScrollDownAction scrolls the view down
|
||||||
func (h *BufPane) ScrollDownAction() bool {
|
func (h *BufPane) ScrollDownAction() bool {
|
||||||
h.ScrollDown(util.IntOpt(h.Buf.Settings["scrollspeed"]))
|
h.ScrollDown(util.IntOpt(h.Buf.Settings["scrollspeed"]))
|
||||||
return true
|
return true
|
||||||
@ -160,6 +168,52 @@ func (h *BufPane) Center() bool {
|
|||||||
return true
|
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
|
// MoveCursorUp is not an action
|
||||||
func (h *BufPane) MoveCursorUp(n int) {
|
func (h *BufPane) MoveCursorUp(n int) {
|
||||||
if !h.Buf.Settings["softwrap"].(bool) {
|
if !h.Buf.Settings["softwrap"].(bool) {
|
||||||
@ -170,10 +224,10 @@ func (h *BufPane) MoveCursorUp(n int) {
|
|||||||
if sloc == vloc.SLoc {
|
if sloc == vloc.SLoc {
|
||||||
// we are at the beginning of buffer
|
// we are at the beginning of buffer
|
||||||
h.Cursor.Loc = h.Buf.Start()
|
h.Cursor.Loc = h.Buf.Start()
|
||||||
h.Cursor.LastVisualX = 0
|
h.Cursor.StoreVisualX()
|
||||||
} else {
|
} else {
|
||||||
vloc.SLoc = sloc
|
vloc.SLoc = sloc
|
||||||
vloc.VisualX = h.Cursor.LastVisualX
|
vloc.VisualX = h.Cursor.LastWrappedVisualX
|
||||||
h.Cursor.Loc = h.LocFromVLoc(vloc)
|
h.Cursor.Loc = h.LocFromVLoc(vloc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -189,11 +243,10 @@ func (h *BufPane) MoveCursorDown(n int) {
|
|||||||
if sloc == vloc.SLoc {
|
if sloc == vloc.SLoc {
|
||||||
// we are at the end of buffer
|
// we are at the end of buffer
|
||||||
h.Cursor.Loc = h.Buf.End()
|
h.Cursor.Loc = h.Buf.End()
|
||||||
vloc = h.VLocFromLoc(h.Cursor.Loc)
|
h.Cursor.StoreVisualX()
|
||||||
h.Cursor.LastVisualX = vloc.VisualX
|
|
||||||
} else {
|
} else {
|
||||||
vloc.SLoc = sloc
|
vloc.SLoc = sloc
|
||||||
vloc.VisualX = h.Cursor.LastVisualX
|
vloc.VisualX = h.Cursor.LastWrappedVisualX
|
||||||
h.Cursor.Loc = h.LocFromVLoc(vloc)
|
h.Cursor.Loc = h.LocFromVLoc(vloc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -209,8 +262,13 @@ func (h *BufPane) CursorUp() bool {
|
|||||||
|
|
||||||
// CursorDown moves the cursor down
|
// CursorDown moves the cursor down
|
||||||
func (h *BufPane) CursorDown() bool {
|
func (h *BufPane) CursorDown() bool {
|
||||||
|
selectionEndNewline := h.Cursor.HasSelection() && h.Cursor.CurSelection[1].X == 0
|
||||||
h.Cursor.Deselect(false)
|
h.Cursor.Deselect(false)
|
||||||
|
if selectionEndNewline {
|
||||||
|
h.Cursor.Start()
|
||||||
|
} else {
|
||||||
h.MoveCursorDown(1)
|
h.MoveCursorDown(1)
|
||||||
|
}
|
||||||
h.Relocate()
|
h.Relocate()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -244,7 +302,6 @@ func (h *BufPane) CursorLeft() bool {
|
|||||||
func (h *BufPane) CursorRight() bool {
|
func (h *BufPane) CursorRight() bool {
|
||||||
if h.Cursor.HasSelection() {
|
if h.Cursor.HasSelection() {
|
||||||
h.Cursor.Deselect(false)
|
h.Cursor.Deselect(false)
|
||||||
h.Cursor.Right()
|
|
||||||
} else {
|
} else {
|
||||||
tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
|
tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
|
||||||
tabmovement := h.Buf.Settings["tabmovement"].(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.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()
|
h.Relocate()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -687,7 +744,7 @@ func (h *BufPane) Backspace() bool {
|
|||||||
h.Buf.Remove(loc.Move(-1, h.Buf), loc)
|
h.Buf.Remove(loc.Move(-1, h.Buf), loc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
h.Cursor.LastVisualX = h.Cursor.GetVisualX()
|
h.Cursor.StoreVisualX()
|
||||||
h.Relocate()
|
h.Relocate()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -854,6 +911,11 @@ func (h *BufPane) Autocomplete() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if b.HasSuggestions {
|
||||||
|
b.CycleAutocomplete(true)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
if h.Cursor.X == 0 {
|
if h.Cursor.X == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -864,10 +926,6 @@ func (h *BufPane) Autocomplete() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.HasSuggestions {
|
|
||||||
b.CycleAutocomplete(true)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return b.Autocomplete(buffer.BufferComplete)
|
return b.Autocomplete(buffer.BufferComplete)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -889,7 +947,7 @@ func (h *BufPane) InsertTab() bool {
|
|||||||
b := h.Buf
|
b := h.Buf
|
||||||
indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
|
indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
|
||||||
tabBytes := len(indent)
|
tabBytes := len(indent)
|
||||||
bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
|
bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX(false) % tabBytes)
|
||||||
b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
|
b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
|
||||||
h.Relocate()
|
h.Relocate()
|
||||||
return true
|
return true
|
||||||
@ -946,6 +1004,9 @@ func (h *BufPane) SaveAsCB(action string, callback func()) bool {
|
|||||||
h.completeAction(action)
|
h.completeAction(action)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
InfoBar.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
InfoBar.YNPrompt(
|
InfoBar.YNPrompt(
|
||||||
@ -982,8 +1043,6 @@ func (h *BufPane) saveBufToFile(filename string, action string, callback func())
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
InfoBar.Error(err)
|
InfoBar.Error(err)
|
||||||
} else {
|
} else {
|
||||||
h.Buf.Path = filename
|
|
||||||
h.Buf.SetName(filename)
|
|
||||||
InfoBar.Message("Saved " + filename)
|
InfoBar.Message("Saved " + filename)
|
||||||
if callback != nil {
|
if callback != nil {
|
||||||
callback()
|
callback()
|
||||||
@ -1008,8 +1067,6 @@ func (h *BufPane) saveBufToFile(filename string, action string, callback func())
|
|||||||
InfoBar.Error(err)
|
InfoBar.Error(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
h.Buf.Path = filename
|
|
||||||
h.Buf.SetName(filename)
|
|
||||||
InfoBar.Message("Saved " + filename)
|
InfoBar.Message("Saved " + filename)
|
||||||
if callback != nil {
|
if callback != nil {
|
||||||
callback()
|
callback()
|
||||||
@ -1154,6 +1211,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)
|
match, found, err := h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.Buf.LastSearchRegex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
InfoBar.Error(err)
|
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 {
|
if found {
|
||||||
h.Cursor.SetSelectionStart(match[0])
|
h.Cursor.SetSelectionStart(match[0])
|
||||||
@ -1183,6 +1248,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)
|
match, found, err := h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.Buf.LastSearchRegex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
InfoBar.Error(err)
|
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 {
|
if found {
|
||||||
h.Cursor.SetSelectionStart(match[0])
|
h.Cursor.SetSelectionStart(match[0])
|
||||||
@ -1238,101 +1311,189 @@ func (h *BufPane) Redo() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the selection to the system clipboard
|
func (h *BufPane) selectLines() int {
|
||||||
func (h *BufPane) Copy() bool {
|
|
||||||
if h.Cursor.HasSelection() {
|
if h.Cursor.HasSelection() {
|
||||||
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
start := h.Cursor.CurSelection[0]
|
||||||
h.freshClip = true
|
end := h.Cursor.CurSelection[1]
|
||||||
InfoBar.Message("Copied selection")
|
if start.GreaterThan(end) {
|
||||||
|
start, end = end, start
|
||||||
}
|
}
|
||||||
h.Relocate()
|
if end.X == 0 {
|
||||||
return true
|
end = end.Move(-1, h.Buf)
|
||||||
}
|
|
||||||
|
|
||||||
// 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.Deselect(true)
|
||||||
h.Cursor.Loc = origLoc
|
h.Cursor.SetSelectionStart(buffer.Loc{0, start.Y})
|
||||||
h.Relocate()
|
h.Cursor.SetSelectionEnd(buffer.Loc{0, end.Y + 1})
|
||||||
return true
|
} 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// CutLine cuts the current line to the clipboard
|
// Copy the selection to the system clipboard
|
||||||
func (h *BufPane) CutLine() bool {
|
func (h *BufPane) Copy() bool {
|
||||||
h.Cursor.SelectLine()
|
|
||||||
if !h.Cursor.HasSelection() {
|
if !h.Cursor.HasSelection() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if h.freshClip {
|
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||||
if h.Cursor.HasSelection() {
|
h.freshClip = false
|
||||||
if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
|
InfoBar.Message("Copied selection")
|
||||||
InfoBar.Error(err)
|
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.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||||
|
h.freshClip = false
|
||||||
|
if nlines > 1 {
|
||||||
|
InfoBar.Message(fmt.Sprintf("Copied %d lines", nlines))
|
||||||
} else {
|
} else {
|
||||||
clipboard.WriteMulti(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
|
InfoBar.Message("Copied line")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || !h.freshClip {
|
h.Cursor.Loc = origLoc
|
||||||
h.Copy()
|
h.Cursor.LastVisualX = origLastVisualX
|
||||||
}
|
h.Cursor.LastWrappedVisualX = origLastWrappedVisualX
|
||||||
h.freshClip = true
|
h.Cursor.CurSelection = origSelection
|
||||||
h.lastCutTime = time.Now()
|
|
||||||
h.Cursor.DeleteSelection()
|
|
||||||
h.Cursor.ResetSelection()
|
|
||||||
InfoBar.Message("Cut line")
|
|
||||||
h.Relocate()
|
h.Relocate()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cut the selection to the system clipboard
|
// Cut the selection to the system clipboard
|
||||||
func (h *BufPane) Cut() bool {
|
func (h *BufPane) Cut() bool {
|
||||||
if h.Cursor.HasSelection() {
|
if !h.Cursor.HasSelection() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||||
h.Cursor.DeleteSelection()
|
h.Cursor.DeleteSelection()
|
||||||
h.Cursor.ResetSelection()
|
h.Cursor.ResetSelection()
|
||||||
h.freshClip = true
|
h.freshClip = false
|
||||||
InfoBar.Message("Cut selection")
|
InfoBar.Message("Cut selection")
|
||||||
|
|
||||||
h.Relocate()
|
h.Relocate()
|
||||||
return true
|
return true
|
||||||
}
|
|
||||||
return h.CutLine()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DuplicateLine duplicates the current line or selection
|
// CutLine cuts the current line to the clipboard. If there is a selection,
|
||||||
func (h *BufPane) DuplicateLine() bool {
|
// CutLine cuts all the lines that are (fully or partially) in the selection.
|
||||||
var infoMessage = "Duplicated line"
|
func (h *BufPane) CutLine() bool {
|
||||||
if h.Cursor.HasSelection() {
|
nlines := h.selectLines()
|
||||||
infoMessage = "Duplicated selection"
|
if nlines == 0 {
|
||||||
h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
|
return false
|
||||||
} else {
|
}
|
||||||
h.Cursor.End()
|
totalLines := nlines
|
||||||
h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
|
if h.freshClip {
|
||||||
// h.Cursor.Right()
|
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoBar.Message(infoMessage)
|
|
||||||
h.Relocate()
|
h.Relocate()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteLine deletes the current line
|
// Duplicate the selection
|
||||||
func (h *BufPane) DeleteLine() bool {
|
func (h *BufPane) Duplicate() bool {
|
||||||
h.Cursor.SelectLine()
|
|
||||||
if !h.Cursor.HasSelection() {
|
if !h.Cursor.HasSelection() {
|
||||||
return false
|
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.DeleteSelection()
|
||||||
h.Cursor.ResetSelection()
|
h.Cursor.ResetSelection()
|
||||||
|
h.Cursor.StoreVisualX()
|
||||||
|
if nlines > 1 {
|
||||||
|
InfoBar.Message(fmt.Sprintf("Deleted %d lines", nlines))
|
||||||
|
} else {
|
||||||
InfoBar.Message("Deleted line")
|
InfoBar.Message("Deleted line")
|
||||||
|
}
|
||||||
h.Relocate()
|
h.Relocate()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -1536,63 +1697,84 @@ func (h *BufPane) End() bool {
|
|||||||
|
|
||||||
// PageUp scrolls the view up a page
|
// PageUp scrolls the view up a page
|
||||||
func (h *BufPane) PageUp() bool {
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// PageDown scrolls the view down a page
|
// PageDown scrolls the view down a page
|
||||||
func (h *BufPane) PageDown() bool {
|
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()
|
h.ScrollAdjust()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// SelectPageUp selects up one page
|
// SelectPageUp selects up one page
|
||||||
func (h *BufPane) SelectPageUp() bool {
|
func (h *BufPane) SelectPageUp() bool {
|
||||||
|
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
||||||
|
scrollAmount := h.BufView().Height - pageOverlap
|
||||||
if !h.Cursor.HasSelection() {
|
if !h.Cursor.HasSelection() {
|
||||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
||||||
}
|
}
|
||||||
h.MoveCursorUp(h.BufView().Height)
|
h.MoveCursorUp(scrollAmount)
|
||||||
h.Cursor.SelectTo(h.Cursor.Loc)
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
||||||
|
if h.Cursor.Num == 0 {
|
||||||
|
h.ScrollUp(scrollAmount)
|
||||||
|
}
|
||||||
h.Relocate()
|
h.Relocate()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// SelectPageDown selects down one page
|
// SelectPageDown selects down one page
|
||||||
func (h *BufPane) SelectPageDown() bool {
|
func (h *BufPane) SelectPageDown() bool {
|
||||||
|
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
||||||
|
scrollAmount := h.BufView().Height - pageOverlap
|
||||||
if !h.Cursor.HasSelection() {
|
if !h.Cursor.HasSelection() {
|
||||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
||||||
}
|
}
|
||||||
h.MoveCursorDown(h.BufView().Height)
|
h.MoveCursorDown(scrollAmount)
|
||||||
h.Cursor.SelectTo(h.Cursor.Loc)
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
||||||
|
if h.Cursor.Num == 0 && !h.ScrollReachedEnd() {
|
||||||
|
h.ScrollDown(scrollAmount)
|
||||||
|
h.ScrollAdjust()
|
||||||
|
}
|
||||||
h.Relocate()
|
h.Relocate()
|
||||||
return true
|
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 {
|
func (h *BufPane) CursorPageUp() bool {
|
||||||
h.Cursor.Deselect(true)
|
h.Cursor.Deselect(true)
|
||||||
|
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
||||||
if h.Cursor.HasSelection() {
|
scrollAmount := h.BufView().Height - pageOverlap
|
||||||
h.Cursor.Loc = h.Cursor.CurSelection[0]
|
h.MoveCursorUp(scrollAmount)
|
||||||
h.Cursor.ResetSelection()
|
if h.Cursor.Num == 0 {
|
||||||
h.Cursor.StoreVisualX()
|
h.ScrollUp(scrollAmount)
|
||||||
}
|
}
|
||||||
h.MoveCursorUp(h.BufView().Height)
|
|
||||||
h.Relocate()
|
h.Relocate()
|
||||||
return true
|
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 {
|
func (h *BufPane) CursorPageDown() bool {
|
||||||
|
selectionEndNewline := h.Cursor.HasSelection() && h.Cursor.CurSelection[1].X == 0
|
||||||
h.Cursor.Deselect(false)
|
h.Cursor.Deselect(false)
|
||||||
|
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
||||||
if h.Cursor.HasSelection() {
|
scrollAmount := h.BufView().Height - pageOverlap
|
||||||
h.Cursor.Loc = h.Cursor.CurSelection[1]
|
if selectionEndNewline {
|
||||||
h.Cursor.ResetSelection()
|
scrollAmount--
|
||||||
h.Cursor.StoreVisualX()
|
}
|
||||||
|
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()
|
h.Relocate()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -1612,12 +1794,12 @@ func (h *BufPane) HalfPageDown() bool {
|
|||||||
|
|
||||||
// ToggleDiffGutter turns the diff gutter off and on
|
// ToggleDiffGutter turns the diff gutter off and on
|
||||||
func (h *BufPane) ToggleDiffGutter() bool {
|
func (h *BufPane) ToggleDiffGutter() bool {
|
||||||
if !h.Buf.Settings["diffgutter"].(bool) {
|
diffgutter := !h.Buf.Settings["diffgutter"].(bool)
|
||||||
h.Buf.Settings["diffgutter"] = true
|
h.Buf.SetOptionNative("diffgutter", diffgutter)
|
||||||
|
if diffgutter {
|
||||||
h.Buf.UpdateDiff()
|
h.Buf.UpdateDiff()
|
||||||
InfoBar.Message("Enabled diff gutter")
|
InfoBar.Message("Enabled diff gutter")
|
||||||
} else {
|
} else {
|
||||||
h.Buf.Settings["diffgutter"] = false
|
|
||||||
InfoBar.Message("Disabled diff gutter")
|
InfoBar.Message("Disabled diff gutter")
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -1625,11 +1807,11 @@ func (h *BufPane) ToggleDiffGutter() bool {
|
|||||||
|
|
||||||
// ToggleRuler turns line numbers off and on
|
// ToggleRuler turns line numbers off and on
|
||||||
func (h *BufPane) ToggleRuler() bool {
|
func (h *BufPane) ToggleRuler() bool {
|
||||||
if !h.Buf.Settings["ruler"].(bool) {
|
ruler := !h.Buf.Settings["ruler"].(bool)
|
||||||
h.Buf.Settings["ruler"] = true
|
h.Buf.SetOptionNative("ruler", ruler)
|
||||||
|
if ruler {
|
||||||
InfoBar.Message("Enabled ruler")
|
InfoBar.Message("Enabled ruler")
|
||||||
} else {
|
} else {
|
||||||
h.Buf.Settings["ruler"] = false
|
|
||||||
InfoBar.Message("Disabled ruler")
|
InfoBar.Message("Disabled ruler")
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -1645,7 +1827,8 @@ func (h *BufPane) ToggleHelp() bool {
|
|||||||
if h.Buf.Type == buffer.BTHelp {
|
if h.Buf.Type == buffer.BTHelp {
|
||||||
h.Quit()
|
h.Quit()
|
||||||
} else {
|
} else {
|
||||||
h.openHelp("help")
|
hsplit := config.GlobalSettings["helpsplit"] == "hsplit"
|
||||||
|
h.openHelp("help", hsplit, false)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -1681,7 +1864,7 @@ func (h *BufPane) CommandMode() bool {
|
|||||||
|
|
||||||
// ToggleOverwriteMode lets the user toggle the text overwrite mode
|
// ToggleOverwriteMode lets the user toggle the text overwrite mode
|
||||||
func (h *BufPane) ToggleOverwriteMode() bool {
|
func (h *BufPane) ToggleOverwriteMode() bool {
|
||||||
h.isOverwriteMode = !h.isOverwriteMode
|
h.Buf.OverwriteMode = !h.Buf.OverwriteMode
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1708,11 +1891,11 @@ func (h *BufPane) ClearInfo() bool {
|
|||||||
return true
|
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)
|
// (no prompt)
|
||||||
func (h *BufPane) ForceQuit() bool {
|
func (h *BufPane) ForceQuit() bool {
|
||||||
h.Buf.Close()
|
h.Buf.Close()
|
||||||
if len(MainTab().Panes) > 1 {
|
if len(h.tab.Panes) > 1 {
|
||||||
h.Unsplit()
|
h.Unsplit()
|
||||||
} else if len(Tabs.List) > 1 {
|
} else if len(Tabs.List) > 1 {
|
||||||
Tabs.RemoveTab(h.splitID)
|
Tabs.RemoveTab(h.splitID)
|
||||||
@ -1724,23 +1907,29 @@ func (h *BufPane) ForceQuit() bool {
|
|||||||
return true
|
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
|
// Quit this will close the current tab or view that is open
|
||||||
func (h *BufPane) Quit() bool {
|
func (h *BufPane) Quit() bool {
|
||||||
if h.Buf.Modified() {
|
if h.Buf.Modified() && !h.Buf.Shared() {
|
||||||
if config.GlobalSettings["autosave"].(float64) > 0 {
|
if config.GlobalSettings["autosave"].(float64) > 0 && h.Buf.Path != "" {
|
||||||
// autosave on means we automatically save when quitting
|
// autosave on means we automatically save when quitting
|
||||||
h.SaveCB("Quit", func() {
|
h.SaveCB("Quit", func() {
|
||||||
h.ForceQuit()
|
h.ForceQuit()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
|
h.closePrompt("Quit", func() {
|
||||||
if !canceled && !yes {
|
|
||||||
h.ForceQuit()
|
h.ForceQuit()
|
||||||
} else if !canceled && yes {
|
|
||||||
h.SaveCB("Quit", func() {
|
|
||||||
h.ForceQuit()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1793,27 +1982,38 @@ func (h *BufPane) AddTab() bool {
|
|||||||
|
|
||||||
// PreviousTab switches to the previous tab in the tab list
|
// PreviousTab switches to the previous tab in the tab list
|
||||||
func (h *BufPane) PreviousTab() bool {
|
func (h *BufPane) PreviousTab() bool {
|
||||||
tabsLen := len(Tabs.List)
|
if Tabs.Active() == 0 {
|
||||||
if tabsLen == 1 {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
Tabs.SetActive(Tabs.Active() - 1)
|
||||||
a := Tabs.Active() + tabsLen
|
|
||||||
Tabs.SetActive((a - 1) % tabsLen)
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextTab switches to the next tab in the tab list
|
// NextTab switches to the next tab in the tab list
|
||||||
func (h *BufPane) NextTab() bool {
|
func (h *BufPane) NextTab() bool {
|
||||||
tabsLen := len(Tabs.List)
|
if Tabs.Active() == len(Tabs.List)-1 {
|
||||||
if tabsLen == 1 {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
Tabs.SetActive(Tabs.Active() + 1)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
a := Tabs.Active()
|
// FirstTab switches to the first tab in the tab list
|
||||||
Tabs.SetActive((a + 1) % tabsLen)
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1848,36 +2048,38 @@ func (h *BufPane) Unsplit() bool {
|
|||||||
|
|
||||||
// NextSplit changes the view to the next split
|
// NextSplit changes the view to the next split
|
||||||
func (h *BufPane) NextSplit() bool {
|
func (h *BufPane) NextSplit() bool {
|
||||||
if len(h.tab.Panes) == 1 {
|
if h.tab.active == len(h.tab.Panes)-1 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
h.tab.SetActive(h.tab.active + 1)
|
||||||
a := h.tab.active
|
|
||||||
if a < len(h.tab.Panes)-1 {
|
|
||||||
a++
|
|
||||||
} else {
|
|
||||||
a = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
h.tab.SetActive(a)
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// PreviousSplit changes the view to the previous split
|
// PreviousSplit changes the view to the previous split
|
||||||
func (h *BufPane) PreviousSplit() bool {
|
func (h *BufPane) PreviousSplit() bool {
|
||||||
if len(h.tab.Panes) == 1 {
|
if h.tab.active == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
h.tab.SetActive(h.tab.active - 1)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
a := h.tab.active
|
// FirstSplit changes the view to the first split
|
||||||
if a > 0 {
|
func (h *BufPane) FirstSplit() bool {
|
||||||
a--
|
if h.tab.active == 0 {
|
||||||
} else {
|
return false
|
||||||
a = len(h.tab.Panes) - 1
|
|
||||||
}
|
}
|
||||||
h.tab.SetActive(a)
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1955,11 +2157,17 @@ func (h *BufPane) SpawnMultiCursor() bool {
|
|||||||
return true
|
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
|
// SpawnMultiCursorUpN is not an action
|
||||||
func (h *BufPane) SpawnMultiCursorUpN(n int) bool {
|
func (h *BufPane) SpawnMultiCursorUpN(n int) bool {
|
||||||
lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
|
lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
|
||||||
var c *buffer.Cursor
|
|
||||||
if !h.Buf.Settings["softwrap"].(bool) {
|
|
||||||
if n > 0 && lastC.Y == 0 {
|
if n > 0 && lastC.Y == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -1969,24 +2177,11 @@ func (h *BufPane) SpawnMultiCursorUpN(n int) bool {
|
|||||||
|
|
||||||
h.Buf.DeselectCursors()
|
h.Buf.DeselectCursors()
|
||||||
|
|
||||||
c = buffer.NewCursor(h.Buf, buffer.Loc{lastC.X, lastC.Y - n})
|
c := buffer.NewCursor(h.Buf, buffer.Loc{lastC.X, lastC.Y - n})
|
||||||
c.LastVisualX = lastC.LastVisualX
|
c.LastVisualX = lastC.LastVisualX
|
||||||
|
c.LastWrappedVisualX = lastC.LastWrappedVisualX
|
||||||
c.X = c.GetCharPosInLine(h.Buf.LineBytes(c.Y), c.LastVisualX)
|
c.X = c.GetCharPosInLine(h.Buf.LineBytes(c.Y), c.LastVisualX)
|
||||||
c.Relocate()
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Buf.AddCursor(c)
|
h.Buf.AddCursor(c)
|
||||||
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
|
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
|
||||||
@ -2068,14 +2263,16 @@ func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// SkipMultiCursor moves the current multiple cursor to the next available position
|
func (h *BufPane) skipMultiCursor(forward bool) bool {
|
||||||
func (h *BufPane) SkipMultiCursor() bool {
|
|
||||||
lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
|
lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
|
||||||
if !lastC.HasSelection() {
|
if !lastC.HasSelection() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
sel := lastC.GetSelection()
|
sel := lastC.GetSelection()
|
||||||
searchStart := lastC.CurSelection[1]
|
searchStart := lastC.CurSelection[1]
|
||||||
|
if !forward {
|
||||||
|
searchStart = lastC.CurSelection[0]
|
||||||
|
}
|
||||||
|
|
||||||
search := string(sel)
|
search := string(sel)
|
||||||
search = regexp.QuoteMeta(search)
|
search = regexp.QuoteMeta(search)
|
||||||
@ -2083,7 +2280,7 @@ func (h *BufPane) SkipMultiCursor() bool {
|
|||||||
search = "\\b" + search + "\\b"
|
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 {
|
if err != nil {
|
||||||
InfoBar.Error(err)
|
InfoBar.Error(err)
|
||||||
}
|
}
|
||||||
@ -2103,6 +2300,16 @@ func (h *BufPane) SkipMultiCursor() bool {
|
|||||||
return true
|
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
|
// RemoveMultiCursor removes the latest multiple cursor
|
||||||
func (h *BufPane) RemoveMultiCursor() bool {
|
func (h *BufPane) RemoveMultiCursor() bool {
|
||||||
if h.Buf.NumCursors() > 1 {
|
if h.Buf.NumCursors() > 1 {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// +build plan9 nacl windows
|
//go:build plan9 || nacl || windows
|
||||||
|
|
||||||
package action
|
package action
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// +build linux darwin dragonfly solaris openbsd netbsd freebsd
|
//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd
|
||||||
|
|
||||||
package action
|
package action
|
||||||
|
|
||||||
|
@ -4,17 +4,18 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/zyedidia/json5"
|
"github.com/micro-editor/json5"
|
||||||
|
"github.com/micro-editor/tcell/v2"
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/tcell/v2"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Binder = map[string]func(e Event, action string){
|
var Binder = map[string]func(e Event, action string){
|
||||||
@ -23,9 +24,13 @@ var Binder = map[string]func(e Event, action string){
|
|||||||
"terminal": TermMapEvent,
|
"terminal": TermMapEvent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeFile(name string, txt []byte) error {
|
||||||
|
return util.SafeWrite(name, txt, false)
|
||||||
|
}
|
||||||
|
|
||||||
func createBindingsIfNotExist(fname string) {
|
func createBindingsIfNotExist(fname string) {
|
||||||
if _, e := os.Stat(fname); os.IsNotExist(e) {
|
if _, e := os.Stat(fname); errors.Is(e, fs.ErrNotExist) {
|
||||||
ioutil.WriteFile(fname, []byte("{}"), 0644)
|
writeFile(fname, []byte("{}"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +42,7 @@ func InitBindings() {
|
|||||||
createBindingsIfNotExist(filename)
|
createBindingsIfNotExist(filename)
|
||||||
|
|
||||||
if _, e := os.Stat(filename); e == nil {
|
if _, e := os.Stat(filename); e == nil {
|
||||||
input, err := ioutil.ReadFile(filename)
|
input, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
screen.TermMessage("Error reading bindings.json file: " + err.Error())
|
screen.TermMessage("Error reading bindings.json file: " + err.Error())
|
||||||
return
|
return
|
||||||
@ -89,7 +94,7 @@ func BindKey(k, v string, bind func(e Event, a string)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(k, "\x1b") {
|
if strings.HasPrefix(k, "\x1b") {
|
||||||
screen.Screen.RegisterRawSeq(k)
|
screen.RegisterRawSeq(k)
|
||||||
}
|
}
|
||||||
|
|
||||||
bind(event, v)
|
bind(event, v)
|
||||||
@ -265,7 +270,7 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
|
|||||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||||
createBindingsIfNotExist(filename)
|
createBindingsIfNotExist(filename)
|
||||||
if _, e = os.Stat(filename); e == nil {
|
if _, e = os.Stat(filename); e == nil {
|
||||||
input, err := ioutil.ReadFile(filename)
|
input, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.New("Error reading bindings.json file: " + err.Error())
|
return false, errors.New("Error reading bindings.json file: " + err.Error())
|
||||||
}
|
}
|
||||||
@ -304,7 +309,8 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
|
|||||||
BindKey(k, v, Binder["buffer"])
|
BindKey(k, v, Binder["buffer"])
|
||||||
|
|
||||||
txt, _ := json.MarshalIndent(parsed, "", " ")
|
txt, _ := json.MarshalIndent(parsed, "", " ")
|
||||||
return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
txt = append(txt, '\n')
|
||||||
|
return true, writeFile(filename, txt)
|
||||||
}
|
}
|
||||||
return false, e
|
return false, e
|
||||||
}
|
}
|
||||||
@ -317,7 +323,7 @@ func UnbindKey(k string) error {
|
|||||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||||
createBindingsIfNotExist(filename)
|
createBindingsIfNotExist(filename)
|
||||||
if _, e = os.Stat(filename); e == nil {
|
if _, e = os.Stat(filename); e == nil {
|
||||||
input, err := ioutil.ReadFile(filename)
|
input, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("Error reading bindings.json file: " + err.Error())
|
return errors.New("Error reading bindings.json file: " + err.Error())
|
||||||
}
|
}
|
||||||
@ -342,7 +348,7 @@ func UnbindKey(k string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(k, "\x1b") {
|
if strings.HasPrefix(k, "\x1b") {
|
||||||
screen.Screen.UnregisterRawSeq(k)
|
screen.UnregisterRawSeq(k)
|
||||||
}
|
}
|
||||||
|
|
||||||
defaults := DefaultBindings("buffer")
|
defaults := DefaultBindings("buffer")
|
||||||
@ -354,7 +360,8 @@ func UnbindKey(k string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
txt, _ := json.MarshalIndent(parsed, "", " ")
|
txt, _ := json.MarshalIndent(parsed, "", " ")
|
||||||
return ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
txt = append(txt, '\n')
|
||||||
|
return writeFile(filename, txt)
|
||||||
}
|
}
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
luar "layeh.com/gopher-luar"
|
luar "layeh.com/gopher-luar"
|
||||||
|
|
||||||
|
"github.com/micro-editor/tcell/v2"
|
||||||
lua "github.com/yuin/gopher-lua"
|
lua "github.com/yuin/gopher-lua"
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
@ -13,7 +14,6 @@ import (
|
|||||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type BufAction interface{}
|
type BufAction interface{}
|
||||||
@ -100,9 +100,7 @@ func BufMapEvent(k Event, action string) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: fix problem when complex bindings have these
|
idx := util.IndexAnyUnquoted(action, "&|,")
|
||||||
// characters (escape them?)
|
|
||||||
idx := strings.IndexAny(action, "&|,")
|
|
||||||
a := action
|
a := action
|
||||||
if idx >= 0 {
|
if idx >= 0 {
|
||||||
a = action[:idx]
|
a = action[:idx]
|
||||||
@ -226,26 +224,21 @@ type BufPane struct {
|
|||||||
// (possibly multiple) buttons were pressed previously.
|
// (possibly multiple) buttons were pressed previously.
|
||||||
mousePressed map[MouseEvent]bool
|
mousePressed map[MouseEvent]bool
|
||||||
|
|
||||||
// We need to keep track of insert key press toggle
|
|
||||||
isOverwriteMode bool
|
|
||||||
// This stores when the last click was
|
// This stores when the last click was
|
||||||
// This is useful for detecting double and triple clicks
|
// This is useful for detecting double and triple clicks
|
||||||
lastClickTime time.Time
|
lastClickTime time.Time
|
||||||
lastLoc buffer.Loc
|
lastLoc buffer.Loc
|
||||||
|
|
||||||
// lastCutTime stores when the last ctrl+k was issued.
|
// freshClip returns true if one or more lines have been cut to the clipboard
|
||||||
// It is used for clearing the clipboard to replace it with fresh cut lines.
|
// and have never been pasted yet.
|
||||||
lastCutTime time.Time
|
|
||||||
|
|
||||||
// freshClip returns true if the clipboard has never been pasted.
|
|
||||||
freshClip bool
|
freshClip bool
|
||||||
|
|
||||||
// Was the last mouse event actually a double click?
|
// Was the last mouse event actually a double click?
|
||||||
// Useful for detecting triple clicks -- if a double click is detected
|
// 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
|
// 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
|
// 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
|
// Should the current multiple cursor selection search based on word or
|
||||||
// based on selection (false for selection, true for word)
|
// based on selection (false for selection, true for word)
|
||||||
@ -363,9 +356,6 @@ func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
|
|||||||
// Set mouseReleased to true because we assume the mouse is not being
|
// Set mouseReleased to true because we assume the mouse is not being
|
||||||
// pressed when the editor is opened
|
// pressed when the editor is opened
|
||||||
h.resetMouse()
|
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{}
|
h.lastClickTime = time.Time{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -644,7 +634,7 @@ func (h *BufPane) DoRuneInsert(r rune) {
|
|||||||
c.ResetSelection()
|
c.ResetSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.isOverwriteMode {
|
if h.Buf.OverwriteMode {
|
||||||
next := c.Loc
|
next := c.Loc
|
||||||
next.X++
|
next.X++
|
||||||
h.Buf.Replace(c.Loc, next, string(r))
|
h.Buf.Replace(c.Loc, next, string(r))
|
||||||
@ -662,20 +652,28 @@ func (h *BufPane) DoRuneInsert(r rune) {
|
|||||||
// VSplitIndex opens the given buffer in a vertical split on the given side.
|
// VSplitIndex opens the given buffer in a vertical split on the given side.
|
||||||
func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
|
func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
|
||||||
e := NewBufPaneFromBuf(buf, h.tab)
|
e := NewBufPaneFromBuf(buf, h.tab)
|
||||||
e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
|
e.splitID = h.tab.GetNode(h.splitID).VSplit(right)
|
||||||
MainTab().Panes = append(MainTab().Panes, e)
|
currentPaneIdx := h.tab.GetPane(h.splitID)
|
||||||
MainTab().Resize()
|
if right {
|
||||||
MainTab().SetActive(len(MainTab().Panes) - 1)
|
currentPaneIdx++
|
||||||
|
}
|
||||||
|
h.tab.AddPane(e, currentPaneIdx)
|
||||||
|
h.tab.Resize()
|
||||||
|
h.tab.SetActive(currentPaneIdx)
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
// HSplitIndex opens the given buffer in a horizontal split on the given side.
|
// HSplitIndex opens the given buffer in a horizontal split on the given side.
|
||||||
func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
|
func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
|
||||||
e := NewBufPaneFromBuf(buf, h.tab)
|
e := NewBufPaneFromBuf(buf, h.tab)
|
||||||
e.splitID = MainTab().GetNode(h.splitID).HSplit(bottom)
|
e.splitID = h.tab.GetNode(h.splitID).HSplit(bottom)
|
||||||
MainTab().Panes = append(MainTab().Panes, e)
|
currentPaneIdx := h.tab.GetPane(h.splitID)
|
||||||
MainTab().Resize()
|
if bottom {
|
||||||
MainTab().SetActive(len(MainTab().Panes) - 1)
|
currentPaneIdx++
|
||||||
|
}
|
||||||
|
h.tab.AddPane(e, currentPaneIdx)
|
||||||
|
h.tab.Resize()
|
||||||
|
h.tab.SetActive(currentPaneIdx)
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -733,6 +731,9 @@ var BufKeyActions = map[string]BufKeyAction{
|
|||||||
"CursorRight": (*BufPane).CursorRight,
|
"CursorRight": (*BufPane).CursorRight,
|
||||||
"CursorStart": (*BufPane).CursorStart,
|
"CursorStart": (*BufPane).CursorStart,
|
||||||
"CursorEnd": (*BufPane).CursorEnd,
|
"CursorEnd": (*BufPane).CursorEnd,
|
||||||
|
"CursorToViewTop": (*BufPane).CursorToViewTop,
|
||||||
|
"CursorToViewCenter": (*BufPane).CursorToViewCenter,
|
||||||
|
"CursorToViewBottom": (*BufPane).CursorToViewBottom,
|
||||||
"SelectToStart": (*BufPane).SelectToStart,
|
"SelectToStart": (*BufPane).SelectToStart,
|
||||||
"SelectToEnd": (*BufPane).SelectToEnd,
|
"SelectToEnd": (*BufPane).SelectToEnd,
|
||||||
"SelectUp": (*BufPane).SelectUp,
|
"SelectUp": (*BufPane).SelectUp,
|
||||||
@ -780,6 +781,7 @@ var BufKeyActions = map[string]BufKeyAction{
|
|||||||
"CopyLine": (*BufPane).CopyLine,
|
"CopyLine": (*BufPane).CopyLine,
|
||||||
"Cut": (*BufPane).Cut,
|
"Cut": (*BufPane).Cut,
|
||||||
"CutLine": (*BufPane).CutLine,
|
"CutLine": (*BufPane).CutLine,
|
||||||
|
"Duplicate": (*BufPane).Duplicate,
|
||||||
"DuplicateLine": (*BufPane).DuplicateLine,
|
"DuplicateLine": (*BufPane).DuplicateLine,
|
||||||
"DeleteLine": (*BufPane).DeleteLine,
|
"DeleteLine": (*BufPane).DeleteLine,
|
||||||
"MoveLinesUp": (*BufPane).MoveLinesUp,
|
"MoveLinesUp": (*BufPane).MoveLinesUp,
|
||||||
@ -824,8 +826,12 @@ var BufKeyActions = map[string]BufKeyAction{
|
|||||||
"AddTab": (*BufPane).AddTab,
|
"AddTab": (*BufPane).AddTab,
|
||||||
"PreviousTab": (*BufPane).PreviousTab,
|
"PreviousTab": (*BufPane).PreviousTab,
|
||||||
"NextTab": (*BufPane).NextTab,
|
"NextTab": (*BufPane).NextTab,
|
||||||
|
"FirstTab": (*BufPane).FirstTab,
|
||||||
|
"LastTab": (*BufPane).LastTab,
|
||||||
"NextSplit": (*BufPane).NextSplit,
|
"NextSplit": (*BufPane).NextSplit,
|
||||||
"PreviousSplit": (*BufPane).PreviousSplit,
|
"PreviousSplit": (*BufPane).PreviousSplit,
|
||||||
|
"FirstSplit": (*BufPane).FirstSplit,
|
||||||
|
"LastSplit": (*BufPane).LastSplit,
|
||||||
"Unsplit": (*BufPane).Unsplit,
|
"Unsplit": (*BufPane).Unsplit,
|
||||||
"VSplit": (*BufPane).VSplitAction,
|
"VSplit": (*BufPane).VSplitAction,
|
||||||
"HSplit": (*BufPane).HSplitAction,
|
"HSplit": (*BufPane).HSplitAction,
|
||||||
@ -841,6 +847,7 @@ var BufKeyActions = map[string]BufKeyAction{
|
|||||||
"RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
|
"RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
|
||||||
"RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
|
"RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
|
||||||
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
|
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
|
||||||
|
"SkipMultiCursorBack": (*BufPane).SkipMultiCursorBack,
|
||||||
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
|
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
|
||||||
"JumpLine": (*BufPane).JumpLine,
|
"JumpLine": (*BufPane).JumpLine,
|
||||||
"Deselect": (*BufPane).Deselect,
|
"Deselect": (*BufPane).Deselect,
|
||||||
@ -907,6 +914,7 @@ var MultiActions = map[string]bool{
|
|||||||
"Copy": true,
|
"Copy": true,
|
||||||
"Cut": true,
|
"Cut": true,
|
||||||
"CutLine": true,
|
"CutLine": true,
|
||||||
|
"Duplicate": true,
|
||||||
"DuplicateLine": true,
|
"DuplicateLine": true,
|
||||||
"DeleteLine": true,
|
"DeleteLine": true,
|
||||||
"MoveLinesUp": true,
|
"MoveLinesUp": true,
|
||||||
|
@ -139,10 +139,11 @@ func (h *BufPane) TextFilterCmd(args []string) {
|
|||||||
InfoBar.Error("usage: textfilter arguments")
|
InfoBar.Error("usage: textfilter arguments")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sel := h.Cursor.GetSelection()
|
for _, c := range h.Buf.GetCursors() {
|
||||||
|
sel := c.GetSelection()
|
||||||
if len(sel) == 0 {
|
if len(sel) == 0 {
|
||||||
h.Cursor.SelectWord()
|
c.SelectWord()
|
||||||
sel = h.Cursor.GetSelection()
|
sel = c.GetSelection()
|
||||||
}
|
}
|
||||||
var bout, berr bytes.Buffer
|
var bout, berr bytes.Buffer
|
||||||
cmd := exec.Command(args[0], args[1:]...)
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
@ -154,8 +155,9 @@ func (h *BufPane) TextFilterCmd(args []string) {
|
|||||||
InfoBar.Error(err.Error() + " " + berr.String())
|
InfoBar.Error(err.Error() + " " + berr.String())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h.Cursor.DeleteSelection()
|
c.DeleteSelection()
|
||||||
h.Buf.Insert(h.Cursor.Loc, bout.String())
|
h.Buf.Insert(c.Loc, bout.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TabMoveCmd moves the current tab to a given index (starts at 1). The
|
// TabMoveCmd moves the current tab to a given index (starts at 1). The
|
||||||
@ -203,6 +205,7 @@ func (h *BufPane) TabMoveCmd(args []string) {
|
|||||||
Tabs.List = append(Tabs.List, nil)
|
Tabs.List = append(Tabs.List, nil)
|
||||||
copy(Tabs.List[idxTo+1:], Tabs.List[idxTo:])
|
copy(Tabs.List[idxTo+1:], Tabs.List[idxTo:])
|
||||||
Tabs.List[idxTo] = activeTab
|
Tabs.List[idxTo] = activeTab
|
||||||
|
Tabs.Resize()
|
||||||
Tabs.UpdateNames()
|
Tabs.UpdateNames()
|
||||||
Tabs.SetActive(idxTo)
|
Tabs.SetActive(idxTo)
|
||||||
// InfoBar.Message(fmt.Sprintf("Moved tab from slot %d to %d", idxFrom+1, idxTo+1))
|
// InfoBar.Message(fmt.Sprintf("Moved tab from slot %d to %d", idxFrom+1, idxTo+1))
|
||||||
@ -305,15 +308,8 @@ func (h *BufPane) OpenCmd(args []string) {
|
|||||||
}
|
}
|
||||||
h.OpenBuffer(b)
|
h.OpenBuffer(b)
|
||||||
}
|
}
|
||||||
if h.Buf.Modified() {
|
if h.Buf.Modified() && !h.Buf.Shared() {
|
||||||
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
|
h.closePrompt("Save", open)
|
||||||
if !canceled && !yes {
|
|
||||||
open()
|
|
||||||
} else if !canceled && yes {
|
|
||||||
h.Save()
|
|
||||||
open()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
open()
|
open()
|
||||||
}
|
}
|
||||||
@ -428,7 +424,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 {
|
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))
|
return errors.New(fmt.Sprintf("Unable to load help text for %s: %v", page, err))
|
||||||
} else {
|
} else {
|
||||||
@ -437,33 +433,74 @@ func (h *BufPane) openHelp(page string) error {
|
|||||||
helpBuffer.SetOptionNative("hltaberrors", false)
|
helpBuffer.SetOptionNative("hltaberrors", false)
|
||||||
helpBuffer.SetOptionNative("hltrailingws", false)
|
helpBuffer.SetOptionNative("hltrailingws", false)
|
||||||
|
|
||||||
if h.Buf.Type == buffer.BTHelp {
|
if h.Buf.Type == buffer.BTHelp && !forceSplit {
|
||||||
h.OpenBuffer(helpBuffer)
|
h.OpenBuffer(helpBuffer)
|
||||||
} else {
|
} else if hsplit {
|
||||||
h.HSplitBuf(helpBuffer)
|
h.HSplitBuf(helpBuffer)
|
||||||
|
} else {
|
||||||
|
h.VSplitBuf(helpBuffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
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 overriden 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) {
|
func (h *BufPane) HelpCmd(args []string) {
|
||||||
|
hsplit := config.GlobalSettings["helpsplit"] == "hsplit"
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
// Open the default help if the user just typed "> help"
|
// Open the default help if the user just typed "> help"
|
||||||
h.openHelp("help")
|
h.openHelp("help", hsplit, false)
|
||||||
} else {
|
} else {
|
||||||
if config.FindRuntimeFile(config.RTHelp, args[0]) != nil {
|
var topics []string
|
||||||
err := h.openHelp(args[0])
|
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 {
|
if err != nil {
|
||||||
InfoBar.Error(err)
|
InfoBar.Error(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
InfoBar.Error("Sorry, no help for ", args[0])
|
InfoBar.Error("Sorry, no help for ", topic)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
// If no file is given, it opens an empty buffer in a new split
|
||||||
func (h *BufPane) VSplitCmd(args []string) {
|
func (h *BufPane) VSplitCmd(args []string) {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
@ -472,16 +509,18 @@ func (h *BufPane) VSplitCmd(args []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
|
for _, a := range args {
|
||||||
|
buf, err := buffer.NewBufferFromFile(a, buffer.BTDefault)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
InfoBar.Error(err)
|
InfoBar.Error(err)
|
||||||
return
|
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
|
// If no file is given, it opens an empty buffer in a new split
|
||||||
func (h *BufPane) HSplitCmd(args []string) {
|
func (h *BufPane) HSplitCmd(args []string) {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
@ -490,13 +529,15 @@ func (h *BufPane) HSplitCmd(args []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
|
for _, a := range args {
|
||||||
|
buf, err := buffer.NewBufferFromFile(a, buffer.BTDefault)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
InfoBar.Error(err)
|
InfoBar.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.HSplitBuf(buf)
|
h.HSplitBuf(buf)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EvalCmd evaluates a lua expression
|
// EvalCmd evaluates a lua expression
|
||||||
@ -504,7 +545,8 @@ func (h *BufPane) EvalCmd(args []string) {
|
|||||||
InfoBar.Error("Eval unsupported")
|
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) {
|
func (h *BufPane) NewTabCmd(args []string) {
|
||||||
width, height := screen.Screen.Size()
|
width, height := screen.Screen.Size()
|
||||||
iOffset := config.GetInfoBarOffset()
|
iOffset := config.GetInfoBarOffset()
|
||||||
@ -609,7 +651,16 @@ func SetGlobalOptionNative(option string, nativeValue interface{}) error {
|
|||||||
delete(b.LocalSettings, option)
|
delete(b.LocalSettings, option)
|
||||||
}
|
}
|
||||||
|
|
||||||
return config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
|
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) error {
|
||||||
@ -734,8 +785,12 @@ func (h *BufPane) BindCmd(args []string) {
|
|||||||
|
|
||||||
_, err := TryBindKey(parseKeyArg(args[0]), args[1], true)
|
_, err := TryBindKey(parseKeyArg(args[0]), args[1], true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrOverwrite) {
|
||||||
|
screen.TermMessage(err)
|
||||||
|
} else {
|
||||||
InfoBar.Error(err)
|
InfoBar.Error(err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnbindCmd binds a key to its default action
|
// UnbindCmd binds a key to its default action
|
||||||
@ -747,8 +802,12 @@ func (h *BufPane) UnbindCmd(args []string) {
|
|||||||
|
|
||||||
err := UnbindKey(parseKeyArg(args[0]))
|
err := UnbindKey(parseKeyArg(args[0]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrOverwrite) {
|
||||||
|
screen.TermMessage(err)
|
||||||
|
} else {
|
||||||
InfoBar.Error(err)
|
InfoBar.Error(err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunCmd runs a shell command in the background
|
// RunCmd runs a shell command in the background
|
||||||
@ -841,7 +900,7 @@ func (h *BufPane) SaveCmd(args []string) {
|
|||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
h.Save()
|
h.Save()
|
||||||
} else {
|
} else {
|
||||||
h.Buf.SaveAs(args[0])
|
h.saveBufToFile(args[0], "SaveAs", nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -902,10 +961,12 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
|||||||
nreplaced := 0
|
nreplaced := 0
|
||||||
start := h.Buf.Start()
|
start := h.Buf.Start()
|
||||||
end := h.Buf.End()
|
end := h.Buf.End()
|
||||||
|
searchLoc := h.Cursor.Loc
|
||||||
selection := h.Cursor.HasSelection()
|
selection := h.Cursor.HasSelection()
|
||||||
if selection {
|
if selection {
|
||||||
start = h.Cursor.CurSelection[0]
|
start = h.Cursor.CurSelection[0]
|
||||||
end = h.Cursor.CurSelection[1]
|
end = h.Cursor.CurSelection[1]
|
||||||
|
searchLoc = start // otherwise me might start at the end
|
||||||
}
|
}
|
||||||
if all {
|
if all {
|
||||||
nreplaced, _ = h.Buf.ReplaceRegex(start, end, regex, replace, !noRegex)
|
nreplaced, _ = h.Buf.ReplaceRegex(start, end, regex, replace, !noRegex)
|
||||||
@ -914,7 +975,7 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
|||||||
return l.GreaterEqual(start) && l.LessEqual(end)
|
return l.GreaterEqual(start) && l.LessEqual(end)
|
||||||
}
|
}
|
||||||
|
|
||||||
searchLoc := h.Cursor.Loc
|
lastMatchEnd := buffer.Loc{-1, -1}
|
||||||
var doReplacement func()
|
var doReplacement func()
|
||||||
doReplacement = func() {
|
doReplacement = func() {
|
||||||
locs, found, err := h.Buf.FindNext(search, start, end, searchLoc, true, true)
|
locs, found, err := h.Buf.FindNext(search, start, end, searchLoc, true, true)
|
||||||
@ -929,6 +990,18 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
|||||||
return
|
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.SetSelectionStart(locs[0])
|
||||||
h.Cursor.SetSelectionEnd(locs[1])
|
h.Cursor.SetSelectionEnd(locs[1])
|
||||||
h.GotoLoc(locs[0])
|
h.GotoLoc(locs[0])
|
||||||
@ -954,6 +1027,7 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
|||||||
h.Buf.RelocateCursors()
|
h.Buf.RelocateCursors()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
lastMatchEnd = searchLoc
|
||||||
doReplacement()
|
doReplacement()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -985,10 +1059,42 @@ func (h *BufPane) ReplaceAllCmd(args []string) {
|
|||||||
h.ReplaceCmd(append(args, "-a"))
|
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
|
// TermCmd opens a terminal in the current view
|
||||||
func (h *BufPane) TermCmd(args []string) {
|
func (h *BufPane) TermCmd(args []string) {
|
||||||
ps := h.tab.Panes
|
|
||||||
|
|
||||||
if !TermEmuSupported {
|
if !TermEmuSupported {
|
||||||
InfoBar.Error("Terminal emulator not supported on this system")
|
InfoBar.Error("Terminal emulator not supported on this system")
|
||||||
return
|
return
|
||||||
@ -1003,56 +1109,19 @@ func (h *BufPane) TermCmd(args []string) {
|
|||||||
args = []string{sh}
|
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
|
// 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
|
newtab := len(MainTab().Panes) == 1 && len(Tabs.List) == 1
|
||||||
|
|
||||||
if newtab {
|
if newtab {
|
||||||
term(0, true)
|
h.openTerm(args, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, p := range ps {
|
if h.Buf.Modified() && !h.Buf.Shared() {
|
||||||
if p.ID() == h.ID() {
|
h.closePrompt("Save", func() {
|
||||||
if h.Buf.Modified() {
|
h.openTerm(args, false)
|
||||||
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 {
|
} else {
|
||||||
term(i, false)
|
h.openTerm(args, false)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ package action
|
|||||||
var termdefaults = map[string]string{
|
var termdefaults = map[string]string{
|
||||||
"<Ctrl-q><Ctrl-q>": "Exit",
|
"<Ctrl-q><Ctrl-q>": "Exit",
|
||||||
"<Ctrl-e><Ctrl-e>": "CommandMode",
|
"<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
|
// DefaultBindings returns a map containing micro's default keybindings
|
||||||
|
@ -45,23 +45,23 @@ var bufdefaults = map[string]string{
|
|||||||
"Alt-]": "DiffNext|CursorEnd",
|
"Alt-]": "DiffNext|CursorEnd",
|
||||||
"Ctrl-z": "Undo",
|
"Ctrl-z": "Undo",
|
||||||
"Ctrl-y": "Redo",
|
"Ctrl-y": "Redo",
|
||||||
"Ctrl-c": "CopyLine|Copy",
|
"Ctrl-c": "Copy|CopyLine",
|
||||||
"Ctrl-x": "Cut",
|
"Ctrl-x": "Cut|CutLine",
|
||||||
"Ctrl-k": "CutLine",
|
"Ctrl-k": "CutLine",
|
||||||
"Ctrl-d": "DuplicateLine",
|
"Ctrl-d": "Duplicate|DuplicateLine",
|
||||||
"Ctrl-v": "Paste",
|
"Ctrl-v": "Paste",
|
||||||
"Ctrl-a": "SelectAll",
|
"Ctrl-a": "SelectAll",
|
||||||
"Ctrl-t": "AddTab",
|
"Ctrl-t": "AddTab",
|
||||||
"Alt-,": "PreviousTab",
|
"Alt-,": "PreviousTab|LastTab",
|
||||||
"Alt-.": "NextTab",
|
"Alt-.": "NextTab|FirstTab",
|
||||||
"Home": "StartOfTextToggle",
|
"Home": "StartOfTextToggle",
|
||||||
"End": "EndOfLine",
|
"End": "EndOfLine",
|
||||||
"CtrlHome": "CursorStart",
|
"CtrlHome": "CursorStart",
|
||||||
"CtrlEnd": "CursorEnd",
|
"CtrlEnd": "CursorEnd",
|
||||||
"PageUp": "CursorPageUp",
|
"PageUp": "CursorPageUp",
|
||||||
"PageDown": "CursorPageDown",
|
"PageDown": "CursorPageDown",
|
||||||
"CtrlPageUp": "PreviousTab",
|
"CtrlPageUp": "PreviousTab|LastTab",
|
||||||
"CtrlPageDown": "NextTab",
|
"CtrlPageDown": "NextTab|FirstTab",
|
||||||
"ShiftPageUp": "SelectPageUp",
|
"ShiftPageUp": "SelectPageUp",
|
||||||
"ShiftPageDown": "SelectPageDown",
|
"ShiftPageDown": "SelectPageDown",
|
||||||
"Ctrl-g": "ToggleHelp",
|
"Ctrl-g": "ToggleHelp",
|
||||||
@ -72,7 +72,7 @@ var bufdefaults = map[string]string{
|
|||||||
"Ctrl-b": "ShellMode",
|
"Ctrl-b": "ShellMode",
|
||||||
"Ctrl-q": "Quit",
|
"Ctrl-q": "Quit",
|
||||||
"Ctrl-e": "CommandMode",
|
"Ctrl-e": "CommandMode",
|
||||||
"Ctrl-w": "NextSplit",
|
"Ctrl-w": "NextSplit|FirstSplit",
|
||||||
"Ctrl-u": "ToggleMacro",
|
"Ctrl-u": "ToggleMacro",
|
||||||
"Ctrl-j": "PlayMacro",
|
"Ctrl-j": "PlayMacro",
|
||||||
"Insert": "ToggleOverwriteMode",
|
"Insert": "ToggleOverwriteMode",
|
||||||
@ -146,8 +146,8 @@ var infodefaults = map[string]string{
|
|||||||
"Backtab": "CycleAutocompleteBack",
|
"Backtab": "CycleAutocompleteBack",
|
||||||
"Ctrl-z": "Undo",
|
"Ctrl-z": "Undo",
|
||||||
"Ctrl-y": "Redo",
|
"Ctrl-y": "Redo",
|
||||||
"Ctrl-c": "CopyLine|Copy",
|
"Ctrl-c": "Copy|CopyLine",
|
||||||
"Ctrl-x": "Cut",
|
"Ctrl-x": "Cut|CutLine",
|
||||||
"Ctrl-k": "CutLine",
|
"Ctrl-k": "CutLine",
|
||||||
"Ctrl-v": "Paste",
|
"Ctrl-v": "Paste",
|
||||||
"Home": "StartOfTextToggle",
|
"Home": "StartOfTextToggle",
|
||||||
|
@ -48,23 +48,23 @@ var bufdefaults = map[string]string{
|
|||||||
"Alt-]": "DiffNext|CursorEnd",
|
"Alt-]": "DiffNext|CursorEnd",
|
||||||
"Ctrl-z": "Undo",
|
"Ctrl-z": "Undo",
|
||||||
"Ctrl-y": "Redo",
|
"Ctrl-y": "Redo",
|
||||||
"Ctrl-c": "CopyLine|Copy",
|
"Ctrl-c": "Copy|CopyLine",
|
||||||
"Ctrl-x": "Cut",
|
"Ctrl-x": "Cut|CutLine",
|
||||||
"Ctrl-k": "CutLine",
|
"Ctrl-k": "CutLine",
|
||||||
"Ctrl-d": "DuplicateLine",
|
"Ctrl-d": "Duplicate|DuplicateLine",
|
||||||
"Ctrl-v": "Paste",
|
"Ctrl-v": "Paste",
|
||||||
"Ctrl-a": "SelectAll",
|
"Ctrl-a": "SelectAll",
|
||||||
"Ctrl-t": "AddTab",
|
"Ctrl-t": "AddTab",
|
||||||
"Alt-,": "PreviousTab",
|
"Alt-,": "PreviousTab|LastTab",
|
||||||
"Alt-.": "NextTab",
|
"Alt-.": "NextTab|FirstTab",
|
||||||
"Home": "StartOfTextToggle",
|
"Home": "StartOfTextToggle",
|
||||||
"End": "EndOfLine",
|
"End": "EndOfLine",
|
||||||
"CtrlHome": "CursorStart",
|
"CtrlHome": "CursorStart",
|
||||||
"CtrlEnd": "CursorEnd",
|
"CtrlEnd": "CursorEnd",
|
||||||
"PageUp": "CursorPageUp",
|
"PageUp": "CursorPageUp",
|
||||||
"PageDown": "CursorPageDown",
|
"PageDown": "CursorPageDown",
|
||||||
"CtrlPageUp": "PreviousTab",
|
"CtrlPageUp": "PreviousTab|LastTab",
|
||||||
"CtrlPageDown": "NextTab",
|
"CtrlPageDown": "NextTab|FirstTab",
|
||||||
"ShiftPageUp": "SelectPageUp",
|
"ShiftPageUp": "SelectPageUp",
|
||||||
"ShiftPageDown": "SelectPageDown",
|
"ShiftPageDown": "SelectPageDown",
|
||||||
"Ctrl-g": "ToggleHelp",
|
"Ctrl-g": "ToggleHelp",
|
||||||
@ -75,7 +75,7 @@ var bufdefaults = map[string]string{
|
|||||||
"Ctrl-b": "ShellMode",
|
"Ctrl-b": "ShellMode",
|
||||||
"Ctrl-q": "Quit",
|
"Ctrl-q": "Quit",
|
||||||
"Ctrl-e": "CommandMode",
|
"Ctrl-e": "CommandMode",
|
||||||
"Ctrl-w": "NextSplit",
|
"Ctrl-w": "NextSplit|FirstSplit",
|
||||||
"Ctrl-u": "ToggleMacro",
|
"Ctrl-u": "ToggleMacro",
|
||||||
"Ctrl-j": "PlayMacro",
|
"Ctrl-j": "PlayMacro",
|
||||||
"Insert": "ToggleOverwriteMode",
|
"Insert": "ToggleOverwriteMode",
|
||||||
@ -149,8 +149,8 @@ var infodefaults = map[string]string{
|
|||||||
"Backtab": "CycleAutocompleteBack",
|
"Backtab": "CycleAutocompleteBack",
|
||||||
"Ctrl-z": "Undo",
|
"Ctrl-z": "Undo",
|
||||||
"Ctrl-y": "Redo",
|
"Ctrl-y": "Redo",
|
||||||
"Ctrl-c": "CopyLine|Copy",
|
"Ctrl-c": "Copy|CopyLine",
|
||||||
"Ctrl-x": "Cut",
|
"Ctrl-x": "Cut|CutLine",
|
||||||
"Ctrl-k": "CutLine",
|
"Ctrl-k": "CutLine",
|
||||||
"Ctrl-v": "Paste",
|
"Ctrl-v": "Paste",
|
||||||
"Home": "StartOfTextToggle",
|
"Home": "StartOfTextToggle",
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/zyedidia/tcell/v2"
|
"github.com/micro-editor/tcell/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Event interface {
|
type Event interface {
|
||||||
|
@ -135,15 +135,6 @@ headerLoop:
|
|||||||
return chosen, suggestions
|
return chosen, suggestions
|
||||||
}
|
}
|
||||||
|
|
||||||
func contains(s []string, e string) bool {
|
|
||||||
for _, a := range s {
|
|
||||||
if a == e {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptionComplete autocompletes options
|
// OptionComplete autocompletes options
|
||||||
func OptionComplete(b *buffer.Buffer) ([]string, []string) {
|
func OptionComplete(b *buffer.Buffer) ([]string, []string) {
|
||||||
c := b.GetActiveCursor()
|
c := b.GetActiveCursor()
|
||||||
|
@ -3,12 +3,12 @@ package action
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/micro-editor/tcell/v2"
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/display"
|
"github.com/zyedidia/micro/v2/internal/display"
|
||||||
"github.com/zyedidia/micro/v2/internal/info"
|
"github.com/zyedidia/micro/v2/internal/info"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type InfoKeyAction func(*InfoPane)
|
type InfoKeyAction func(*InfoPane)
|
||||||
|
@ -3,7 +3,7 @@ package action
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
||||||
"github.com/zyedidia/tcell/v2"
|
"github.com/micro-editor/tcell/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PaneKeyAction func(Pane) bool
|
type PaneKeyAction func(Pane) bool
|
||||||
|
@ -4,9 +4,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/micro-editor/tcell/v2"
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
"github.com/zyedidia/micro/v2/internal/display"
|
"github.com/zyedidia/micro/v2/internal/display"
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RawPane struct {
|
type RawPane struct {
|
||||||
|
@ -3,13 +3,13 @@ package action
|
|||||||
import (
|
import (
|
||||||
luar "layeh.com/gopher-luar"
|
luar "layeh.com/gopher-luar"
|
||||||
|
|
||||||
|
"github.com/micro-editor/tcell/v2"
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/display"
|
"github.com/zyedidia/micro/v2/internal/display"
|
||||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/micro/v2/internal/views"
|
"github.com/zyedidia/micro/v2/internal/views"
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// The TabList is a list of tabs and a window to display the tab bar
|
// The TabList is a list of tabs and a window to display the tab bar
|
||||||
@ -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
|
// GetPane returns the pane with the given split index
|
||||||
func (t *Tab) GetPane(splitid uint64) int {
|
func (t *Tab) GetPane(splitid uint64) int {
|
||||||
for i, p := range t.Panes {
|
for i, p := range t.Panes {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// +build linux darwin dragonfly openbsd_amd64 freebsd
|
//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd
|
||||||
|
|
||||||
package action
|
package action
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd_amd64
|
//go:build plan9 || nacl || windows
|
||||||
|
|
||||||
package action
|
package action
|
||||||
|
|
||||||
|
@ -4,13 +4,13 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/micro-editor/tcell/v2"
|
||||||
|
"github.com/micro-editor/terminal"
|
||||||
"github.com/zyedidia/micro/v2/internal/clipboard"
|
"github.com/zyedidia/micro/v2/internal/clipboard"
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/display"
|
"github.com/zyedidia/micro/v2/internal/display"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/micro/v2/internal/shell"
|
"github.com/zyedidia/micro/v2/internal/shell"
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
"github.com/zyedidia/terminal"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TermKeyAction func(*TermPane)
|
type TermKeyAction func(*TermPane)
|
||||||
|
@ -2,7 +2,7 @@ package buffer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@ -109,15 +109,15 @@ func FileComplete(b *Buffer) ([]string, []string) {
|
|||||||
sep := string(os.PathSeparator)
|
sep := string(os.PathSeparator)
|
||||||
dirs := strings.Split(input, sep)
|
dirs := strings.Split(input, sep)
|
||||||
|
|
||||||
var files []os.FileInfo
|
var files []fs.DirEntry
|
||||||
var err error
|
var err error
|
||||||
if len(dirs) > 1 {
|
if len(dirs) > 1 {
|
||||||
directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
|
directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
|
||||||
|
|
||||||
directories, _ = util.ReplaceHome(directories)
|
directories, _ = util.ReplaceHome(directories)
|
||||||
files, err = ioutil.ReadDir(directories)
|
files, err = os.ReadDir(directories)
|
||||||
} else {
|
} else {
|
||||||
files, err = ioutil.ReadDir(".")
|
files, err = os.ReadDir(".")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
package buffer
|
package buffer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
"golang.org/x/text/encoding"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const backupMsg = `A backup was detected for this file. This likely means that micro
|
const BackupMsg = `A backup was detected for:
|
||||||
crashed while editing this file, or another instance of micro is currently
|
|
||||||
editing this file.
|
|
||||||
|
|
||||||
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
|
%s
|
||||||
|
|
||||||
@ -30,90 +32,80 @@ The backup was created on %s, and the file is
|
|||||||
|
|
||||||
Options: [r]ecover, [i]gnore, [a]bort: `
|
Options: [r]ecover, [i]gnore, [a]bort: `
|
||||||
|
|
||||||
var backupRequestChan chan *Buffer
|
const backupSeconds = 8
|
||||||
|
|
||||||
func backupThread() {
|
var BackupCompleteChan chan *Buffer
|
||||||
for {
|
|
||||||
time.Sleep(time.Second * 8)
|
|
||||||
|
|
||||||
for len(backupRequestChan) > 0 {
|
|
||||||
b := <-backupRequestChan
|
|
||||||
bfini := atomic.LoadInt32(&(b.fini)) != 0
|
|
||||||
if !bfini {
|
|
||||||
b.Backup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
backupRequestChan = make(chan *Buffer, 10)
|
BackupCompleteChan = make(chan *Buffer, 10)
|
||||||
|
|
||||||
go backupThread()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) RequestBackup() {
|
func (b *Buffer) RequestBackup() {
|
||||||
if !b.requestedBackup {
|
if !b.RequestedBackup {
|
||||||
select {
|
select {
|
||||||
case backupRequestChan <- b:
|
case backupRequestChan <- b:
|
||||||
default:
|
default:
|
||||||
// channel is full
|
// channel is full
|
||||||
}
|
}
|
||||||
b.requestedBackup = true
|
b.RequestedBackup = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backup saves the current buffer to ConfigDir/backups
|
func (b *Buffer) backupDir() string {
|
||||||
|
backupdir, err := util.ReplaceHome(b.Settings["backupdir"].(string))
|
||||||
|
if backupdir == "" || err != nil {
|
||||||
|
backupdir = filepath.Join(config.ConfigDir, "backups")
|
||||||
|
}
|
||||||
|
return backupdir
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) keepBackup() bool {
|
||||||
|
return b.forceKeepBackup || b.Settings["permbackup"].(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup saves the current buffer to the backups directory
|
||||||
func (b *Buffer) Backup() error {
|
func (b *Buffer) Backup() error {
|
||||||
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
|
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
backupdir, err := util.ReplaceHome(b.Settings["backupdir"].(string))
|
backupdir := b.backupDir()
|
||||||
if backupdir == "" || err != nil {
|
if _, err := os.Stat(backupdir); errors.Is(err, fs.ErrNotExist) {
|
||||||
backupdir = filepath.Join(config.ConfigDir, "backups")
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(backupdir); os.IsNotExist(err) {
|
|
||||||
os.Mkdir(backupdir, os.ModePerm)
|
os.Mkdir(backupdir, os.ModePerm)
|
||||||
}
|
}
|
||||||
|
|
||||||
name := filepath.Join(backupdir, util.EscapePath(b.AbsPath))
|
name := util.DetermineEscapePath(backupdir, b.AbsPath)
|
||||||
|
if _, err := os.Stat(name); errors.Is(err, fs.ErrNotExist) {
|
||||||
err = overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
|
_, err = b.overwriteFile(name)
|
||||||
if len(b.lines) == 0 {
|
if err == nil {
|
||||||
return
|
BackupCompleteChan <- b
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// end of line
|
tmp := util.AppendBackupSuffix(name)
|
||||||
eol := []byte{'\n'}
|
_, err := b.overwriteFile(tmp)
|
||||||
|
if err != nil {
|
||||||
// write lines
|
os.Remove(tmp)
|
||||||
if _, e = file.Write(b.lines[0].data); e != nil {
|
return err
|
||||||
return
|
}
|
||||||
|
err = os.Rename(tmp, name)
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(tmp)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, l := range b.lines[1:] {
|
BackupCompleteChan <- b
|
||||||
if _, e = file.Write(eol); e != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, e = file.Write(l.data); e != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}, false)
|
|
||||||
|
|
||||||
b.requestedBackup = false
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveBackup removes any backup file associated with this buffer
|
// RemoveBackup removes any backup file associated with this buffer
|
||||||
func (b *Buffer) RemoveBackup() {
|
func (b *Buffer) RemoveBackup() {
|
||||||
if !b.Settings["backup"].(bool) || b.Settings["permbackup"].(bool) || b.Path == "" || b.Type != BTDefault {
|
if !b.Settings["backup"].(bool) || b.keepBackup() || b.Path == "" || b.Type != BTDefault {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
|
f := util.DetermineEscapePath(b.backupDir(), b.AbsPath)
|
||||||
os.Remove(f)
|
os.Remove(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,13 +113,13 @@ func (b *Buffer) RemoveBackup() {
|
|||||||
// Returns true if a backup was applied
|
// Returns true if a backup was applied
|
||||||
func (b *Buffer) ApplyBackup(fsize int64) (bool, bool) {
|
func (b *Buffer) ApplyBackup(fsize int64) (bool, bool) {
|
||||||
if b.Settings["backup"].(bool) && !b.Settings["permbackup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
|
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 := util.DetermineEscapePath(b.backupDir(), b.AbsPath)
|
||||||
if info, err := os.Stat(backupfile); err == nil {
|
if info, err := os.Stat(backupfile); err == nil {
|
||||||
backup, err := os.Open(backupfile)
|
backup, err := os.Open(backupfile)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
defer backup.Close()
|
defer backup.Close()
|
||||||
t := info.ModTime()
|
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)
|
choice := screen.TermPrompt(msg, []string{"r", "i", "a", "recover", "ignore", "abort"}, true)
|
||||||
|
|
||||||
if choice%3 == 0 {
|
if choice%3 == 0 {
|
||||||
|
@ -7,9 +7,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -25,13 +24,12 @@ import (
|
|||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
"github.com/zyedidia/micro/v2/pkg/highlight"
|
"github.com/zyedidia/micro/v2/pkg/highlight"
|
||||||
|
"golang.org/x/text/encoding"
|
||||||
"golang.org/x/text/encoding/htmlindex"
|
"golang.org/x/text/encoding/htmlindex"
|
||||||
"golang.org/x/text/encoding/unicode"
|
"golang.org/x/text/encoding/unicode"
|
||||||
"golang.org/x/text/transform"
|
"golang.org/x/text/transform"
|
||||||
)
|
)
|
||||||
|
|
||||||
const backupTime = 8000
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// OpenBuffers is a list of the currently open buffers
|
// OpenBuffers is a list of the currently open buffers
|
||||||
OpenBuffers []*Buffer
|
OpenBuffers []*Buffer
|
||||||
@ -89,6 +87,8 @@ type SharedBuffer struct {
|
|||||||
// LocalSettings customized by the user for this buffer only
|
// LocalSettings customized by the user for this buffer only
|
||||||
LocalSettings map[string]bool
|
LocalSettings map[string]bool
|
||||||
|
|
||||||
|
encoding encoding.Encoding
|
||||||
|
|
||||||
Suggestions []string
|
Suggestions []string
|
||||||
Completions []string
|
Completions []string
|
||||||
CurSuggestion int
|
CurSuggestion int
|
||||||
@ -101,7 +101,8 @@ type SharedBuffer struct {
|
|||||||
diffLock sync.RWMutex
|
diffLock sync.RWMutex
|
||||||
diff map[int]DiffStatus
|
diff map[int]DiffStatus
|
||||||
|
|
||||||
requestedBackup bool
|
RequestedBackup bool
|
||||||
|
forceKeepBackup bool
|
||||||
|
|
||||||
// ReloadDisabled allows the user to disable reloads if they
|
// ReloadDisabled allows the user to disable reloads if they
|
||||||
// are viewing a file that is constantly changing
|
// are viewing a file that is constantly changing
|
||||||
@ -209,6 +210,11 @@ type Buffer struct {
|
|||||||
LastSearchRegex bool
|
LastSearchRegex bool
|
||||||
// HighlightSearch enables highlighting all instances of the last successful search
|
// HighlightSearch enables highlighting all instances of the last successful search
|
||||||
HighlightSearch bool
|
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
|
// NewBufferFromFileAtLoc opens a new buffer with a given cursor location
|
||||||
@ -232,17 +238,20 @@ func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.OpenFile(filename, os.O_WRONLY, 0)
|
|
||||||
readonly := os.IsPermission(err)
|
|
||||||
f.Close()
|
|
||||||
|
|
||||||
fileInfo, serr := os.Stat(filename)
|
fileInfo, serr := os.Stat(filename)
|
||||||
if serr != nil && !os.IsNotExist(serr) {
|
if serr != nil && !errors.Is(serr, fs.ErrNotExist) {
|
||||||
return nil, serr
|
return nil, serr
|
||||||
}
|
}
|
||||||
if serr == nil && fileInfo.IsDir() {
|
if serr == nil && fileInfo.IsDir() {
|
||||||
return nil, errors.New("Error: " + filename + " is a directory and cannot be opened")
|
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)
|
file, err := os.Open(filename)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -250,7 +259,7 @@ func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer,
|
|||||||
}
|
}
|
||||||
|
|
||||||
var buf *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
|
// File does not exist -- create an empty buffer with that name
|
||||||
buf = NewBufferFromString("", filename, btype)
|
buf = NewBufferFromString("", filename, btype)
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
@ -320,30 +329,19 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
|||||||
b.AbsPath = absPath
|
b.AbsPath = absPath
|
||||||
b.Path = path
|
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.Settings = config.DefaultCommonSettings()
|
||||||
b.LocalSettings = make(map[string]bool)
|
b.LocalSettings = make(map[string]bool)
|
||||||
for k, v := range config.GlobalSettings {
|
for k, v := range config.GlobalSettings {
|
||||||
if _, ok := config.DefaultGlobalOnlySettings[k]; !ok {
|
if _, ok := config.DefaultGlobalOnlySettings[k]; !ok {
|
||||||
// make sure setting is not global-only
|
// make sure setting is not global-only
|
||||||
settings[k] = v
|
|
||||||
b.Settings[k] = v
|
b.Settings[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config.InitLocalSettings(settings, absPath)
|
config.UpdatePathGlobLocals(b.Settings, absPath)
|
||||||
b.Settings["readonly"] = settings["readonly"]
|
|
||||||
b.Settings["filetype"] = settings["filetype"]
|
|
||||||
b.Settings["syntax"] = settings["syntax"]
|
|
||||||
|
|
||||||
enc, err := htmlindex.Get(settings["encoding"].(string))
|
b.encoding, err = htmlindex.Get(b.Settings["encoding"].(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
enc = unicode.UTF8
|
b.encoding = unicode.UTF8
|
||||||
b.Settings["encoding"] = "utf-8"
|
b.Settings["encoding"] = "utf-8"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,14 +352,14 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
|||||||
return NewBufferFromString("", "", btype)
|
return NewBufferFromString("", "", btype)
|
||||||
}
|
}
|
||||||
if !hasBackup {
|
if !hasBackup {
|
||||||
reader := bufio.NewReader(transform.NewReader(r, enc.NewDecoder()))
|
reader := bufio.NewReader(transform.NewReader(r, b.encoding.NewDecoder()))
|
||||||
|
|
||||||
var ff FileFormat = FFAuto
|
var ff FileFormat = FFAuto
|
||||||
|
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
// for empty files, use the fileformat setting instead of
|
// for empty files, use the fileformat setting instead of
|
||||||
// autodetection
|
// autodetection
|
||||||
switch settings["fileformat"] {
|
switch b.Settings["fileformat"] {
|
||||||
case "unix":
|
case "unix":
|
||||||
ff = FFUnix
|
ff = FFUnix
|
||||||
case "dos":
|
case "dos":
|
||||||
@ -392,10 +390,10 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.UpdateRules()
|
b.UpdateRules()
|
||||||
// init local settings again now that we know the filetype
|
// we know the filetype now, so update per-filetype settings
|
||||||
config.InitLocalSettings(b.Settings, b.Path)
|
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)
|
os.Mkdir(filepath.Join(config.ConfigDir, "buffers"), os.ModePerm)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -480,7 +478,7 @@ func (b *Buffer) GetName() string {
|
|||||||
name = b.Path
|
name = b.Path
|
||||||
}
|
}
|
||||||
if b.Settings["basename"].(bool) {
|
if b.Settings["basename"].(bool) {
|
||||||
return path.Base(name)
|
return filepath.Base(name)
|
||||||
}
|
}
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
@ -546,7 +544,7 @@ func (b *Buffer) ReOpen() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reader := bufio.NewReader(transform.NewReader(file, enc.NewDecoder()))
|
reader := bufio.NewReader(transform.NewReader(file, enc.NewDecoder()))
|
||||||
data, err := ioutil.ReadAll(reader)
|
data, err := io.ReadAll(reader)
|
||||||
txt := string(data)
|
txt := string(data)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -621,6 +619,16 @@ func (b *Buffer) WordAt(loc Loc) []byte {
|
|||||||
return b.Substr(start, end)
|
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
|
// Modified returns if this buffer has been modified since
|
||||||
// being opened
|
// being opened
|
||||||
func (b *Buffer) Modified() bool {
|
func (b *Buffer) Modified() bool {
|
||||||
|
@ -20,8 +20,14 @@ type Cursor struct {
|
|||||||
buf *Buffer
|
buf *Buffer
|
||||||
Loc
|
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
|
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)
|
// The current selection as a range of character numbers (inclusive)
|
||||||
CurSelection [2]Loc
|
CurSelection [2]Loc
|
||||||
@ -61,8 +67,9 @@ func (c *Cursor) Buf() *Buffer {
|
|||||||
// Goto puts the cursor at the given cursor's location and gives
|
// Goto puts the cursor at the given cursor's location and gives
|
||||||
// the current cursor its selection too
|
// the current cursor its selection too
|
||||||
func (c *Cursor) Goto(b Cursor) {
|
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.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection
|
||||||
|
c.StoreVisualX()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GotoLoc puts the cursor at the given cursor's location and gives
|
// 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
|
// GetVisualX returns the x value of the cursor in visual spaces
|
||||||
func (c *Cursor) GetVisualX() int {
|
func (c *Cursor) GetVisualX(wrap bool) int {
|
||||||
if c.buf.GetVisualX != nil {
|
if wrap && c.buf.GetVisualX != nil {
|
||||||
return c.buf.GetVisualX(c.Loc)
|
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
|
// Start moves the cursor to the start of the line it is on
|
||||||
func (c *Cursor) Start() {
|
func (c *Cursor) Start() {
|
||||||
c.X = 0
|
c.X = 0
|
||||||
c.LastVisualX = c.GetVisualX()
|
c.StoreVisualX()
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartOfText moves the cursor to the first non-whitespace rune of
|
// 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
|
// End moves the cursor to the end of the line it is on
|
||||||
func (c *Cursor) End() {
|
func (c *Cursor) End() {
|
||||||
c.X = util.CharacterCount(c.buf.LineBytes(c.Y))
|
c.X = util.CharacterCount(c.buf.LineBytes(c.Y))
|
||||||
c.LastVisualX = c.GetVisualX()
|
c.StoreVisualX()
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopySelection copies the user's selection to either "primary"
|
// CopySelection copies the user's selection to either "primary"
|
||||||
@ -186,7 +193,7 @@ func (c *Cursor) Deselect(start bool) {
|
|||||||
if start {
|
if start {
|
||||||
c.Loc = c.CurSelection[0]
|
c.Loc = c.CurSelection[0]
|
||||||
} else {
|
} else {
|
||||||
c.Loc = c.CurSelection[1].Move(-1, c.buf)
|
c.Loc = c.CurSelection[1]
|
||||||
}
|
}
|
||||||
c.ResetSelection()
|
c.ResetSelection()
|
||||||
c.StoreVisualX()
|
c.StoreVisualX()
|
||||||
@ -615,5 +622,6 @@ func (c *Cursor) RuneUnder(x int) rune {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cursor) StoreVisualX() {
|
func (c *Cursor) StoreVisualX() {
|
||||||
c.LastVisualX = c.GetVisualX()
|
c.LastVisualX = c.GetVisualX(false)
|
||||||
|
c.LastWrappedVisualX = c.GetVisualX(true)
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ func (eh *EventHandler) DoTextEvent(t *TextEvent, useUndo bool) {
|
|||||||
c.OrigSelection[0] = move(c.OrigSelection[0])
|
c.OrigSelection[0] = move(c.OrigSelection[0])
|
||||||
c.OrigSelection[1] = move(c.OrigSelection[1])
|
c.OrigSelection[1] = move(c.OrigSelection[1])
|
||||||
c.Relocate()
|
c.Relocate()
|
||||||
c.LastVisualX = c.GetVisualX()
|
c.StoreVisualX()
|
||||||
}
|
}
|
||||||
|
|
||||||
if useUndo {
|
if useUndo {
|
||||||
|
@ -285,11 +285,6 @@ func (la *LineArray) deleteLines(y1, y2 int) {
|
|||||||
la.lines = la.lines[:y1+copy(la.lines[y1:], la.lines[y2+1:])]
|
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
|
// Substr returns the string representation between two locations
|
||||||
func (la *LineArray) Substr(start, end Loc) []byte {
|
func (la *LineArray) Substr(start, end Loc) []byte {
|
||||||
startX := runeToByteIndex(start.X, la.lines[start.Y].data)
|
startX := runeToByteIndex(start.X, la.lines[start.Y].data)
|
||||||
|
@ -47,6 +47,16 @@ func (l Loc) LessEqual(b Loc) bool {
|
|||||||
return l == b
|
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
|
// The following functions require a buffer to know where newlines are
|
||||||
|
|
||||||
// Diff returns the distance between two locations
|
// Diff returns the distance between two locations
|
||||||
@ -139,10 +149,5 @@ func ByteOffset(pos Loc, buf *Buffer) int {
|
|||||||
|
|
||||||
// clamps a loc within a buffer
|
// clamps a loc within a buffer
|
||||||
func clamp(pos Loc, la *LineArray) Loc {
|
func clamp(pos Loc, la *LineArray) Loc {
|
||||||
if pos.GreaterEqual(la.End()) {
|
return pos.Clamp(la.Start(), la.End())
|
||||||
return la.End()
|
|
||||||
} else if pos.LessThan(la.Start()) {
|
|
||||||
return la.Start()
|
|
||||||
}
|
|
||||||
return pos
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package buffer
|
package buffer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/micro-editor/tcell/v2"
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type MsgType int
|
type MsgType int
|
||||||
|
@ -5,18 +5,19 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
"golang.org/x/text/encoding"
|
|
||||||
"golang.org/x/text/encoding/htmlindex"
|
|
||||||
"golang.org/x/text/transform"
|
"golang.org/x/text/transform"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,68 +25,178 @@ import (
|
|||||||
// because hashing is too slow
|
// because hashing is too slow
|
||||||
const LargeFileThreshold = 50000
|
const LargeFileThreshold = 50000
|
||||||
|
|
||||||
// overwriteFile opens the given file for writing, truncating if one exists, and then calls
|
type wrappedFile struct {
|
||||||
// the supplied function with the file as io.Writer object, also making sure the file is
|
writeCloser io.WriteCloser
|
||||||
// closed afterwards.
|
withSudo bool
|
||||||
func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error, withSudo bool) (err error) {
|
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 *Buffer
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
saveRequestChan = make(chan saveRequest, 10)
|
||||||
|
backupRequestChan = make(chan *Buffer, 10)
|
||||||
|
|
||||||
|
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 <-backupTicker.C:
|
||||||
|
for len(backupRequestChan) > 0 {
|
||||||
|
b := <-backupRequestChan
|
||||||
|
bfini := atomic.LoadInt32(&(b.fini)) != 0
|
||||||
|
if !bfini {
|
||||||
|
b.Backup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func openFile(name string, withSudo bool) (wrappedFile, error) {
|
||||||
|
var err error
|
||||||
var writeCloser io.WriteCloser
|
var writeCloser io.WriteCloser
|
||||||
var screenb bool
|
var screenb bool
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
|
var sigChan chan os.Signal
|
||||||
|
|
||||||
if withSudo {
|
if withSudo {
|
||||||
cmd = exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name)
|
cmd = exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name)
|
||||||
|
writeCloser, err = cmd.StdinPipe()
|
||||||
if writeCloser, err = cmd.StdinPipe(); err != nil {
|
if err != nil {
|
||||||
return
|
return wrappedFile{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
c := make(chan os.Signal, 1)
|
sigChan = make(chan os.Signal, 1)
|
||||||
signal.Notify(c, os.Interrupt)
|
signal.Reset(os.Interrupt)
|
||||||
go func() {
|
signal.Notify(sigChan, os.Interrupt)
|
||||||
<-c
|
|
||||||
cmd.Process.Kill()
|
|
||||||
}()
|
|
||||||
|
|
||||||
screenb = screen.TempFini()
|
screenb = screen.TempFini()
|
||||||
// need to start the process now, otherwise when we flush the file
|
// 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
|
// 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
|
// 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)
|
screen.TempStart(screenb)
|
||||||
return err
|
|
||||||
|
signal.Notify(util.Sigterm, os.Interrupt)
|
||||||
|
signal.Stop(sigChan)
|
||||||
|
|
||||||
|
return wrappedFile{}, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, util.FileMode)
|
||||||
|
if err != nil {
|
||||||
|
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()))
|
return wrappedFile{writeCloser, withSudo, screenb, cmd, sigChan}, nil
|
||||||
err = fn(w)
|
}
|
||||||
|
|
||||||
if err2 := w.Flush(); err2 != nil && err == nil {
|
func (wf wrappedFile) Write(b *Buffer) (int, error) {
|
||||||
err = err2
|
file := bufio.NewWriter(transform.NewWriter(wf.writeCloser, b.encoding.NewEncoder()))
|
||||||
|
|
||||||
|
b.Lock()
|
||||||
|
defer b.Unlock()
|
||||||
|
|
||||||
|
if len(b.lines) == 0 {
|
||||||
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// end of line
|
||||||
|
var eol []byte
|
||||||
|
if b.Endings == FFDos {
|
||||||
|
eol = []byte{'\r', '\n'}
|
||||||
|
} else {
|
||||||
|
eol = []byte{'\n'}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !wf.withSudo {
|
||||||
|
f := wf.writeCloser.(*os.File)
|
||||||
|
err := f.Truncate(0)
|
||||||
|
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.
|
// 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.
|
f := wf.writeCloser.(*os.File)
|
||||||
if !withSudo {
|
err = f.Sync()
|
||||||
f := writeCloser.(*os.File)
|
|
||||||
if err2 := f.Sync(); err2 != nil && err == nil {
|
|
||||||
err = err2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err2 := writeCloser.Close(); err2 != nil && err == nil {
|
|
||||||
err = err2
|
|
||||||
}
|
}
|
||||||
|
return size, err
|
||||||
|
}
|
||||||
|
|
||||||
if withSudo {
|
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
|
// wait for dd to finish and restart the screen if we used sudo
|
||||||
err := cmd.Wait()
|
err := wf.cmd.Wait()
|
||||||
screen.TempStart(screenb)
|
screen.TempStart(wf.screenb)
|
||||||
|
|
||||||
|
signal.Notify(util.Sigterm, os.Interrupt)
|
||||||
|
signal.Stop(wf.sigChan)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return
|
func (b *Buffer) 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
|
// Save saves the buffer to its default path
|
||||||
@ -146,19 +257,35 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the last time this file was updated after saving
|
filename, err = util.ReplaceHome(filename)
|
||||||
defer func() {
|
if err != nil {
|
||||||
b.ModTime, _ = util.GetModTime(filename)
|
return err
|
||||||
err = b.Serialize()
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
// Removes any tilde and replaces with the absolute path to home
|
newFile := false
|
||||||
absFilename, _ := util.ReplaceHome(filename)
|
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
|
// Get the leading path to the file | "." is returned if there's no leading path provided
|
||||||
if dirname := filepath.Dir(absFilename); dirname != "." {
|
if dirname := filepath.Dir(absFilename); dirname != "." {
|
||||||
// Check if the parent dirs don't exist
|
// 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
|
// Prompt to make sure they want to create the dirs that are missing
|
||||||
if b.Settings["mkparents"].(bool) {
|
if b.Settings["mkparents"].(bool) {
|
||||||
// Create all leading dir(s) since they don't exist
|
// Create all leading dir(s) since they don't exist
|
||||||
@ -172,49 +299,22 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileSize int
|
saveResponseChan := make(chan saveResponse)
|
||||||
|
saveRequestChan <- saveRequest{b, absFilename, withSudo, newFile, saveResponseChan}
|
||||||
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
|
result := <-saveResponseChan
|
||||||
|
err = result.err
|
||||||
if err != nil {
|
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) {
|
b.UpdateModTime()
|
||||||
if len(b.lines) == 0 {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !b.Settings["fastdirty"].(bool) {
|
if !b.Settings["fastdirty"].(bool) {
|
||||||
if fileSize > LargeFileThreshold {
|
if result.size > LargeFileThreshold {
|
||||||
// For large files 'fastdirty' needs to be on
|
// For large files 'fastdirty' needs to be on
|
||||||
b.Settings["fastdirty"] = true
|
b.Settings["fastdirty"] = true
|
||||||
} else {
|
} else {
|
||||||
@ -222,10 +322,70 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newPath := b.Path != filename
|
||||||
b.Path = filename
|
b.Path = filename
|
||||||
absPath, _ := filepath.Abs(filename)
|
b.AbsPath = absFilename
|
||||||
b.AbsPath = absPath
|
|
||||||
b.isModified = false
|
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
|
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 *Buffer) 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)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
backupDir := b.backupDir()
|
||||||
|
if _, err := os.Stat(backupDir); err != nil {
|
||||||
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if err = os.Mkdir(backupDir, os.ModePerm); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
backupName := util.DetermineEscapePath(backupDir, path)
|
||||||
|
_, err = b.overwriteFile(backupName)
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(backupName)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.forceKeepBackup = true
|
||||||
|
size, err := file.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
err = util.OverwriteError{err, backupName}
|
||||||
|
return size, err
|
||||||
|
}
|
||||||
|
b.forceKeepBackup = false
|
||||||
|
|
||||||
|
if !b.keepBackup() {
|
||||||
|
os.Remove(backupName)
|
||||||
|
}
|
||||||
|
|
||||||
|
err2 := file.Close()
|
||||||
|
if err2 != nil && err == nil {
|
||||||
|
err = err2
|
||||||
|
}
|
||||||
|
|
||||||
|
return size, err
|
||||||
|
}
|
||||||
|
@ -2,10 +2,56 @@ package buffer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/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 == padStart {
|
||||||
|
r = regexp.MustCompile(".(?:" + r.String() + ")")
|
||||||
|
} else if padMode == padEnd {
|
||||||
|
r = regexp.MustCompile("(?:" + r.String() + ").")
|
||||||
|
} else if 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) {
|
func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||||
lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
|
lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
|
||||||
if start.Y > b.LinesNum()-1 {
|
if start.Y > b.LinesNum()-1 {
|
||||||
@ -22,30 +68,19 @@ func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := start.Y; i <= end.Y; i++ {
|
for i := start.Y; i <= end.Y; i++ {
|
||||||
l := b.LineBytes(i)
|
l, charpos, padMode, rPadded := findLineParams(b, start, end, i, r)
|
||||||
charpos := 0
|
|
||||||
|
|
||||||
if i == start.Y && start.Y == end.Y {
|
match := rPadded.FindIndex(l)
|
||||||
nchars := util.CharacterCount(l)
|
|
||||||
start.X = util.Clamp(start.X, 0, nchars)
|
|
||||||
end.X = util.Clamp(end.X, 0, nchars)
|
|
||||||
l = util.SliceStart(l, end.X)
|
|
||||||
l = util.SliceEnd(l, start.X)
|
|
||||||
charpos = start.X
|
|
||||||
} else if i == start.Y {
|
|
||||||
nchars := 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)
|
|
||||||
|
|
||||||
if match != nil {
|
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}
|
start := Loc{charpos + util.RunePos(l, match[0]), i}
|
||||||
end := Loc{charpos + util.RunePos(l, match[1]), i}
|
end := Loc{charpos + util.RunePos(l, match[1]), i}
|
||||||
return [2]Loc{start, end}, true
|
return [2]Loc{start, end}, true
|
||||||
@ -70,39 +105,39 @@ func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := end.Y; i >= start.Y; i-- {
|
for i := end.Y; i >= start.Y; i-- {
|
||||||
l := b.LineBytes(i)
|
charCount := util.CharacterCount(b.LineBytes(i))
|
||||||
charpos := 0
|
from := Loc{0, i}.Clamp(start, end)
|
||||||
|
to := Loc{charCount, i}.Clamp(start, end)
|
||||||
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)
|
|
||||||
|
|
||||||
|
allMatches := b.findAll(r, from, to)
|
||||||
if allMatches != nil {
|
if allMatches != nil {
|
||||||
match := allMatches[len(allMatches)-1]
|
match := allMatches[len(allMatches)-1]
|
||||||
start := Loc{charpos + util.RunePos(l, match[0]), i}
|
return [2]Loc{match[0], match[1]}, true
|
||||||
end := Loc{charpos + util.RunePos(l, match[1]), i}
|
|
||||||
return [2]Loc{start, end}, true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return [2]Loc{}, false
|
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
|
// 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
|
// It returns the start and end location of the match (if found) and
|
||||||
// a boolean indicating if it was found
|
// a boolean indicating if it was found
|
||||||
@ -146,53 +181,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
|
// 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
|
// 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) {
|
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte, captureGroups bool) (int, int) {
|
||||||
if start.GreaterThan(end) {
|
if start.GreaterThan(end) {
|
||||||
start, end = end, start
|
start, end = end, start
|
||||||
}
|
}
|
||||||
|
|
||||||
netrunes := 0
|
charsEnd := util.CharacterCount(b.LineBytes(end.Y))
|
||||||
|
|
||||||
found := 0
|
found := 0
|
||||||
var deltas []Delta
|
var deltas []Delta
|
||||||
for i := start.Y; i <= end.Y; i++ {
|
|
||||||
l := b.lines[i].data
|
|
||||||
charpos := 0
|
|
||||||
|
|
||||||
if start.Y == end.Y && i == start.Y {
|
for i := start.Y; i <= end.Y; i++ {
|
||||||
l = util.SliceStart(l, end.X)
|
l := b.LineBytes(i)
|
||||||
l = util.SliceEnd(l, start.X)
|
charCount := util.CharacterCount(l)
|
||||||
charpos = start.X
|
if (i == start.Y && start.X > 0) || (i == end.Y && end.X < charCount) {
|
||||||
} else if i == start.Y {
|
// This replacement code works in general, but it creates a separate
|
||||||
l = util.SliceEnd(l, start.X)
|
// modification for each match. We only use it for the first and last
|
||||||
charpos = start.X
|
// lines, which may use padded regexps
|
||||||
} else if i == end.Y {
|
|
||||||
l = util.SliceStart(l, end.X)
|
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
|
||||||
}
|
}
|
||||||
newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
|
deltas = append(deltas, Delta{newText, match[0], match[1]})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newLine := search.ReplaceAllFunc(l, func(in []byte) []byte {
|
||||||
|
found++
|
||||||
var result []byte
|
var result []byte
|
||||||
if captureGroups {
|
if captureGroups {
|
||||||
for _, submatches := range search.FindAllSubmatchIndex(in, -1) {
|
match := search.FindSubmatchIndex(in)
|
||||||
result = search.Expand(result, replace, in, submatches)
|
result = search.Expand(result, replace, in, match)
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
result = replace
|
result = replace
|
||||||
}
|
}
|
||||||
found++
|
|
||||||
if i == end.Y {
|
|
||||||
netrunes += util.CharacterCount(result) - util.CharacterCount(in)
|
|
||||||
}
|
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
|
deltas = append(deltas, Delta{newLine, Loc{0, i}, Loc{charCount, i}})
|
||||||
from := Loc{charpos, i}
|
|
||||||
to := Loc{charpos + util.CharacterCount(l), i}
|
|
||||||
|
|
||||||
deltas = append(deltas, Delta{newText, from, to})
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
b.MultipleReplace(deltas)
|
b.MultipleReplace(deltas)
|
||||||
|
|
||||||
return found, netrunes
|
return found, util.CharacterCount(b.LineBytes(end.Y)) - charsEnd
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
package buffer
|
package buffer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/text/encoding"
|
|
||||||
|
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
)
|
)
|
||||||
@ -31,16 +29,18 @@ func (b *Buffer) Serialize() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
name := filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath))
|
var buf bytes.Buffer
|
||||||
|
err := gob.NewEncoder(&buf).Encode(SerializedBuffer{
|
||||||
return overwriteFile(name, encoding.Nop, func(file io.Writer) error {
|
|
||||||
err := gob.NewEncoder(file).Encode(SerializedBuffer{
|
|
||||||
b.EventHandler,
|
b.EventHandler,
|
||||||
b.GetActiveCursor().Loc,
|
b.GetActiveCursor().Loc,
|
||||||
b.ModTime,
|
b.ModTime,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}, false)
|
}
|
||||||
|
|
||||||
|
name := util.DetermineEscapePath(filepath.Join(config.ConfigDir, "buffers"), b.AbsPath)
|
||||||
|
return util.SafeWrite(name, buf.Bytes(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unserialize loads the buffer info from config.ConfigDir/buffers
|
// Unserialize loads the buffer info from config.ConfigDir/buffers
|
||||||
@ -50,7 +50,7 @@ func (b *Buffer) Unserialize() error {
|
|||||||
if b.Path == "" {
|
if b.Path == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath)))
|
file, err := os.Open(util.DetermineEscapePath(filepath.Join(config.ConfigDir, "buffers"), b.AbsPath))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
var buffer SerializedBuffer
|
var buffer SerializedBuffer
|
||||||
|
@ -5,13 +5,22 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"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/screen"
|
||||||
|
"golang.org/x/text/encoding/htmlindex"
|
||||||
|
"golang.org/x/text/encoding/unicode"
|
||||||
|
luar "layeh.com/gopher-luar"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *Buffer) ReloadSettings(reloadFiletype bool) {
|
func (b *Buffer) ReloadSettings(reloadFiletype bool) {
|
||||||
settings := config.ParsedSettings()
|
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
|
// need to update filetype before updating other settings based on it
|
||||||
b.Settings["filetype"] = "unknown"
|
b.Settings["filetype"] = "unknown"
|
||||||
if v, ok := settings["filetype"]; ok {
|
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
|
// update syntax rules, which will also update filetype if needed
|
||||||
b.UpdateRules()
|
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() {
|
for k, v := range config.DefaultCommonSettings() {
|
||||||
if k == "filetype" {
|
if k == "filetype" {
|
||||||
// prevent recursion
|
// prevent recursion
|
||||||
@ -46,7 +60,8 @@ func (b *Buffer) ReloadSettings(reloadFiletype bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
|
func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
|
||||||
if reflect.DeepEqual(b.Settings[option], nativeValue) {
|
oldValue := b.Settings[option]
|
||||||
|
if reflect.DeepEqual(oldValue, nativeValue) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,6 +99,12 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
|
|||||||
b.UpdateRules()
|
b.UpdateRules()
|
||||||
}
|
}
|
||||||
} else if option == "encoding" {
|
} else if option == "encoding" {
|
||||||
|
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
|
||||||
|
if err != nil {
|
||||||
|
enc = unicode.UTF8
|
||||||
|
b.Settings["encoding"] = "utf-8"
|
||||||
|
}
|
||||||
|
b.encoding = enc
|
||||||
b.isModified = true
|
b.isModified = true
|
||||||
} else if option == "readonly" && b.Type.Kind == BTDefault.Kind {
|
} else if option == "readonly" && b.Type.Kind == BTDefault.Kind {
|
||||||
b.Type.Readonly = nativeValue.(bool)
|
b.Type.Readonly = nativeValue.(bool)
|
||||||
@ -114,9 +135,7 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.OptionCallback != nil {
|
b.doCallbacks(option, oldValue, nativeValue)
|
||||||
b.OptionCallback(option, nativeValue)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
|
func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
|
||||||
@ -143,3 +162,15 @@ func (b *Buffer) SetOption(option, value string) error {
|
|||||||
|
|
||||||
return b.SetOptionNative(option, nativeValue)
|
return b.SetOptionNative(option, nativeValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) doCallbacks(option string, oldValue interface{}, newValue interface{}) {
|
||||||
|
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"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/micro-editor/tcell/v2"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type terminalClipboard struct{}
|
type terminalClipboard struct{}
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/zyedidia/tcell/v2"
|
"github.com/micro-editor/tcell/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefStyle is Micro's default style
|
// DefStyle is Micro's default style
|
||||||
|
@ -3,8 +3,8 @@ package config
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/micro-editor/tcell/v2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSimpleStringToStyle(t *testing.T) {
|
func TestSimpleStringToStyle(t *testing.T) {
|
||||||
|
@ -143,15 +143,3 @@ func FindPlugin(name string) *Plugin {
|
|||||||
}
|
}
|
||||||
return pl
|
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"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -14,8 +13,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/blang/semver"
|
"github.com/blang/semver"
|
||||||
|
"github.com/micro-editor/json5"
|
||||||
lua "github.com/yuin/gopher-lua"
|
lua "github.com/yuin/gopher-lua"
|
||||||
"github.com/zyedidia/json5"
|
|
||||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
)
|
)
|
||||||
@ -396,7 +395,7 @@ func (pv *PluginVersion) DownloadAndInstall(out io.Writer) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
data, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/blang/semver"
|
"github.com/blang/semver"
|
||||||
|
|
||||||
"github.com/zyedidia/json5"
|
"github.com/micro-editor/json5"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDependencyResolving(t *testing.T) {
|
func TestDependencyResolving(t *testing.T) {
|
||||||
|
@ -2,10 +2,8 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@ -62,12 +60,6 @@ type realFile string
|
|||||||
// some asset file
|
// some asset file
|
||||||
type assetFile string
|
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
|
// a file with the data stored in memory
|
||||||
type memoryFile struct {
|
type memoryFile struct {
|
||||||
name string
|
name string
|
||||||
@ -87,22 +79,18 @@ func (rf realFile) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rf realFile) Data() ([]byte, error) {
|
func (rf realFile) Data() ([]byte, error) {
|
||||||
return ioutil.ReadFile(string(rf))
|
return os.ReadFile(string(rf))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (af assetFile) Name() string {
|
func (af assetFile) Name() string {
|
||||||
fn := path.Base(string(af))
|
fn := filepath.Base(string(af))
|
||||||
return fn[:len(fn)-len(path.Ext(fn))]
|
return fn[:len(fn)-len(filepath.Ext(fn))]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (af assetFile) Data() ([]byte, error) {
|
func (af assetFile) Data() ([]byte, error) {
|
||||||
return rt.Asset(string(af))
|
return rt.Asset(string(af))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nf namedFile) Name() string {
|
|
||||||
return nf.name
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRuntimeFile registers a file for the given filetype
|
// AddRuntimeFile registers a file for the given filetype
|
||||||
func AddRuntimeFile(fileType RTFiletype, file RuntimeFile) {
|
func AddRuntimeFile(fileType RTFiletype, file RuntimeFile) {
|
||||||
allFiles[fileType] = append(allFiles[fileType], file)
|
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
|
// AddRuntimeFilesFromDirectory registers each file from the given directory for
|
||||||
// the filetype which matches the file-pattern
|
// the filetype which matches the file-pattern
|
||||||
func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string) {
|
func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string) {
|
||||||
files, _ := ioutil.ReadDir(directory)
|
files, _ := os.ReadDir(directory)
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
|
if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
|
||||||
fullPath := filepath.Join(directory, f.Name())
|
fullPath := filepath.Join(directory, f.Name())
|
||||||
@ -136,8 +124,8 @@ func AddRuntimeFilesFromAssets(fileType RTFiletype, directory, pattern string) {
|
|||||||
|
|
||||||
assetLoop:
|
assetLoop:
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
if ok, _ := path.Match(pattern, f); ok {
|
if ok, _ := filepath.Match(pattern, f); ok {
|
||||||
af := assetFile(path.Join(directory, f))
|
af := assetFile(filepath.Join(directory, f))
|
||||||
for _, rf := range realFiles[fileType] {
|
for _, rf := range realFiles[fileType] {
|
||||||
if af.Name() == rf.Name() {
|
if af.Name() == rf.Name() {
|
||||||
continue assetLoop
|
continue assetLoop
|
||||||
@ -178,7 +166,7 @@ func InitRuntimeFiles(user bool) {
|
|||||||
if user {
|
if user {
|
||||||
AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
|
AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
|
||||||
}
|
}
|
||||||
AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
|
AddRuntimeFilesFromAssets(fileType, filepath.Join("runtime", dir), pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
initRuntimeVars()
|
initRuntimeVars()
|
||||||
@ -204,14 +192,14 @@ func InitPlugins() {
|
|||||||
|
|
||||||
// Search ConfigDir for plugin-scripts
|
// Search ConfigDir for plugin-scripts
|
||||||
plugdir := filepath.Join(ConfigDir, "plug")
|
plugdir := filepath.Join(ConfigDir, "plug")
|
||||||
files, _ := ioutil.ReadDir(plugdir)
|
files, _ := os.ReadDir(plugdir)
|
||||||
|
|
||||||
isID := regexp.MustCompile(`^[_A-Za-z0-9]+$`).MatchString
|
isID := regexp.MustCompile(`^[_A-Za-z0-9]+$`).MatchString
|
||||||
|
|
||||||
for _, d := range files {
|
for _, d := range files {
|
||||||
plugpath := filepath.Join(plugdir, d.Name())
|
plugpath := filepath.Join(plugdir, d.Name())
|
||||||
if stat, err := os.Stat(plugpath); err == nil && stat.IsDir() {
|
if stat, err := os.Stat(plugpath); err == nil && stat.IsDir() {
|
||||||
srcs, _ := ioutil.ReadDir(plugpath)
|
srcs, _ := os.ReadDir(plugpath)
|
||||||
p := new(Plugin)
|
p := new(Plugin)
|
||||||
p.Name = d.Name()
|
p.Name = d.Name()
|
||||||
p.DirName = d.Name()
|
p.DirName = d.Name()
|
||||||
@ -219,7 +207,7 @@ func InitPlugins() {
|
|||||||
if strings.HasSuffix(f.Name(), ".lua") {
|
if strings.HasSuffix(f.Name(), ".lua") {
|
||||||
p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
|
p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
|
||||||
} else if strings.HasSuffix(f.Name(), ".json") {
|
} 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 {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -311,7 +299,7 @@ func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) e
|
|||||||
if _, err := os.Stat(fullpath); err == nil {
|
if _, err := os.Stat(fullpath); err == nil {
|
||||||
AddRealRuntimeFile(filetype, realFile(fullpath))
|
AddRealRuntimeFile(filetype, realFile(fullpath))
|
||||||
} else {
|
} else {
|
||||||
fullpath = path.Join("runtime", "plugins", pldir, filePath)
|
fullpath = filepath.Join("runtime", "plugins", pldir, filePath)
|
||||||
AddRuntimeFile(filetype, assetFile(fullpath))
|
AddRuntimeFile(filetype, assetFile(fullpath))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -328,7 +316,7 @@ func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, dire
|
|||||||
if _, err := os.Stat(fullpath); err == nil {
|
if _, err := os.Stat(fullpath); err == nil {
|
||||||
AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
|
AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
|
||||||
} else {
|
} else {
|
||||||
fullpath = path.Join("runtime", "plugins", pldir, directory)
|
fullpath = filepath.Join("runtime", "plugins", pldir, directory)
|
||||||
AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
|
AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -12,8 +11,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/micro-editor/json5"
|
||||||
"github.com/zyedidia/glob"
|
"github.com/zyedidia/glob"
|
||||||
"github.com/zyedidia/json5"
|
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
"golang.org/x/text/encoding/htmlindex"
|
"golang.org/x/text/encoding/htmlindex"
|
||||||
)
|
)
|
||||||
@ -29,21 +28,26 @@ var optionValidators = map[string]optionValidator{
|
|||||||
"detectlimit": validateNonNegativeValue,
|
"detectlimit": validateNonNegativeValue,
|
||||||
"encoding": validateEncoding,
|
"encoding": validateEncoding,
|
||||||
"fileformat": validateChoice,
|
"fileformat": validateChoice,
|
||||||
|
"helpsplit": validateChoice,
|
||||||
"matchbracestyle": validateChoice,
|
"matchbracestyle": validateChoice,
|
||||||
"multiopen": validateChoice,
|
"multiopen": validateChoice,
|
||||||
|
"pageoverlap": validateNonNegativeValue,
|
||||||
"reload": validateChoice,
|
"reload": validateChoice,
|
||||||
"scrollmargin": validateNonNegativeValue,
|
"scrollmargin": validateNonNegativeValue,
|
||||||
"scrollspeed": validateNonNegativeValue,
|
"scrollspeed": validateNonNegativeValue,
|
||||||
"tabsize": validatePositiveValue,
|
"tabsize": validatePositiveValue,
|
||||||
|
"truecolor": validateChoice,
|
||||||
}
|
}
|
||||||
|
|
||||||
// a list of settings with pre-defined choices
|
// a list of settings with pre-defined choices
|
||||||
var OptionChoices = map[string][]string{
|
var OptionChoices = map[string][]string{
|
||||||
"clipboard": {"internal", "external", "terminal"},
|
"clipboard": {"internal", "external", "terminal"},
|
||||||
"fileformat": {"unix", "dos"},
|
"fileformat": {"unix", "dos"},
|
||||||
|
"helpsplit": {"hsplit", "vsplit"},
|
||||||
"matchbracestyle": {"underline", "highlight"},
|
"matchbracestyle": {"underline", "highlight"},
|
||||||
"multiopen": {"tab", "hsplit", "vsplit"},
|
"multiopen": {"tab", "hsplit", "vsplit"},
|
||||||
"reload": {"prompt", "auto", "disabled"},
|
"reload": {"prompt", "auto", "disabled"},
|
||||||
|
"truecolor": {"auto", "on", "off"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// a list of settings that can be globally and locally modified and their
|
// a list of settings that can be globally and locally modified and their
|
||||||
@ -66,20 +70,21 @@ var defaultCommonSettings = map[string]interface{}{
|
|||||||
"hlsearch": false,
|
"hlsearch": false,
|
||||||
"hltaberrors": false,
|
"hltaberrors": false,
|
||||||
"hltrailingws": false,
|
"hltrailingws": false,
|
||||||
"incsearch": true,
|
|
||||||
"ignorecase": true,
|
"ignorecase": true,
|
||||||
|
"incsearch": true,
|
||||||
"indentchar": " ",
|
"indentchar": " ",
|
||||||
"keepautoindent": false,
|
"keepautoindent": false,
|
||||||
"matchbrace": true,
|
"matchbrace": true,
|
||||||
"matchbraceleft": true,
|
"matchbraceleft": true,
|
||||||
"matchbracestyle": "underline",
|
"matchbracestyle": "underline",
|
||||||
"mkparents": false,
|
"mkparents": false,
|
||||||
|
"pageoverlap": float64(2),
|
||||||
"permbackup": false,
|
"permbackup": false,
|
||||||
"readonly": false,
|
"readonly": false,
|
||||||
|
"relativeruler": false,
|
||||||
"reload": "prompt",
|
"reload": "prompt",
|
||||||
"rmtrailingws": false,
|
"rmtrailingws": false,
|
||||||
"ruler": true,
|
"ruler": true,
|
||||||
"relativeruler": false,
|
|
||||||
"savecursor": false,
|
"savecursor": false,
|
||||||
"saveundo": false,
|
"saveundo": false,
|
||||||
"scrollbar": false,
|
"scrollbar": false,
|
||||||
@ -89,13 +94,14 @@ var defaultCommonSettings = map[string]interface{}{
|
|||||||
"softwrap": false,
|
"softwrap": false,
|
||||||
"splitbottom": true,
|
"splitbottom": true,
|
||||||
"splitright": 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",
|
"statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
|
||||||
"statusline": true,
|
"statusline": true,
|
||||||
"syntax": true,
|
"syntax": true,
|
||||||
"tabmovement": false,
|
"tabmovement": false,
|
||||||
"tabsize": float64(4),
|
"tabsize": float64(4),
|
||||||
"tabstospaces": false,
|
"tabstospaces": false,
|
||||||
|
"truecolor": "auto",
|
||||||
"useprimary": true,
|
"useprimary": true,
|
||||||
"wordwrap": false,
|
"wordwrap": false,
|
||||||
}
|
}
|
||||||
@ -109,6 +115,7 @@ var DefaultGlobalOnlySettings = map[string]interface{}{
|
|||||||
"divchars": "|-",
|
"divchars": "|-",
|
||||||
"divreverse": true,
|
"divreverse": true,
|
||||||
"fakecursor": false,
|
"fakecursor": false,
|
||||||
|
"helpsplit": "hsplit",
|
||||||
"infobar": true,
|
"infobar": true,
|
||||||
"keymenu": false,
|
"keymenu": false,
|
||||||
"mouse": true,
|
"mouse": true,
|
||||||
@ -151,6 +158,10 @@ var (
|
|||||||
VolatileSettings map[string]bool
|
VolatileSettings map[string]bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func writeFile(name string, txt []byte) error {
|
||||||
|
return util.SafeWrite(name, txt, false)
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ModifiedSettings = make(map[string]bool)
|
ModifiedSettings = make(map[string]bool)
|
||||||
VolatileSettings = make(map[string]bool)
|
VolatileSettings = make(map[string]bool)
|
||||||
@ -217,7 +228,7 @@ func ReadSettings() error {
|
|||||||
parsedSettings = make(map[string]interface{})
|
parsedSettings = make(map[string]interface{})
|
||||||
filename := filepath.Join(ConfigDir, "settings.json")
|
filename := filepath.Join(ConfigDir, "settings.json")
|
||||||
if _, e := os.Stat(filename); e == nil {
|
if _, e := os.Stat(filename); e == nil {
|
||||||
input, err := ioutil.ReadFile(filename)
|
input, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
settingsParseError = true
|
settingsParseError = true
|
||||||
return errors.New("Error reading settings.json file: " + err.Error())
|
return errors.New("Error reading settings.json file: " + err.Error())
|
||||||
@ -283,22 +294,31 @@ func InitGlobalSettings() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitLocalSettings scans the json in settings.json and sets the options locally based
|
// UpdatePathGlobLocals scans the already parsed settings and sets the options locally
|
||||||
// on whether the filetype or path matches ft or glob local settings
|
// based on whether the path matches a glob
|
||||||
// Must be called after ReadSettings
|
// Must be called after ReadSettings
|
||||||
func InitLocalSettings(settings map[string]interface{}, path string) {
|
func UpdatePathGlobLocals(settings map[string]interface{}, path string) {
|
||||||
for k, v := range parsedSettings {
|
for k, v := range parsedSettings {
|
||||||
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") && !strings.HasPrefix(k, "ft:") {
|
||||||
if strings.HasPrefix(k, "ft:") {
|
g, _ := glob.Compile(k)
|
||||||
if settings["filetype"].(string) == k[3:] {
|
if g.MatchString(path) {
|
||||||
for k1, v1 := range v.(map[string]interface{}) {
|
for k1, v1 := range v.(map[string]interface{}) {
|
||||||
settings[k1] = v1
|
settings[k1] = v1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
g, _ := glob.Compile(k)
|
}
|
||||||
if g.MatchString(path) {
|
}
|
||||||
|
|
||||||
|
// 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]interface{}, 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]interface{}) {
|
for k1, v1 := range v.(map[string]interface{}) {
|
||||||
|
if k1 != "filetype" {
|
||||||
settings[k1] = v1
|
settings[k1] = v1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -342,7 +362,8 @@ func WriteSettings(filename string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
txt, _ := json.MarshalIndent(parsedSettings, "", " ")
|
txt, _ := json.MarshalIndent(parsedSettings, "", " ")
|
||||||
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
txt = append(txt, '\n')
|
||||||
|
err = writeFile(filename, txt)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -363,8 +384,9 @@ func OverwriteSettings(filename string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
txt, _ := json.MarshalIndent(settings, "", " ")
|
txt, _ := json.MarshalIndent(parsedSettings, "", " ")
|
||||||
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
txt = append(txt, '\n')
|
||||||
|
err = writeFile(filename, txt)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,11 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
runewidth "github.com/mattn/go-runewidth"
|
runewidth "github.com/mattn/go-runewidth"
|
||||||
|
"github.com/micro-editor/tcell/v2"
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// The BufWindow provides a way of displaying a certain section of a buffer.
|
// The BufWindow provides a way of displaying a certain section of a buffer.
|
||||||
@ -58,9 +58,15 @@ func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
|
|||||||
if option == "softwrap" || option == "wordwrap" {
|
if option == "softwrap" || option == "wordwrap" {
|
||||||
w.Relocate()
|
w.Relocate()
|
||||||
for _, c := range w.Buf.GetCursors() {
|
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 {
|
b.GetVisualX = func(loc buffer.Loc) int {
|
||||||
return w.VLocFromLoc(loc).VisualX
|
return w.VLocFromLoc(loc).VisualX
|
||||||
@ -160,7 +166,7 @@ func (w *BufWindow) updateDisplayInfo() {
|
|||||||
|
|
||||||
if w.bufWidth != prevBufWidth && w.Buf.Settings["softwrap"].(bool) {
|
if w.bufWidth != prevBufWidth && w.Buf.Settings["softwrap"].(bool) {
|
||||||
for _, c := range w.Buf.GetCursors() {
|
for _, c := range w.Buf.GetCursors() {
|
||||||
c.LastVisualX = c.GetVisualX()
|
c.LastWrappedVisualX = c.GetVisualX(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -238,7 +244,7 @@ func (w *BufWindow) Relocate() bool {
|
|||||||
|
|
||||||
// horizontal relocation (scrolling)
|
// horizontal relocation (scrolling)
|
||||||
if !b.Settings["softwrap"].(bool) {
|
if !b.Settings["softwrap"].(bool) {
|
||||||
cx := activeC.GetVisualX()
|
cx := activeC.GetVisualX(false)
|
||||||
rw := runewidth.RuneWidth(activeC.RuneUnder(activeC.X))
|
rw := runewidth.RuneWidth(activeC.RuneUnder(activeC.X))
|
||||||
if rw == 0 {
|
if rw == 0 {
|
||||||
rw = 1 // tab or newline
|
rw = 1 // tab or newline
|
||||||
@ -248,8 +254,8 @@ func (w *BufWindow) Relocate() bool {
|
|||||||
w.StartCol = cx
|
w.StartCol = cx
|
||||||
ret = true
|
ret = true
|
||||||
}
|
}
|
||||||
if cx+w.gutterOffset+rw > w.StartCol+w.Width {
|
if cx+rw > w.StartCol+w.bufWidth {
|
||||||
w.StartCol = cx - w.Width + w.gutterOffset + rw
|
w.StartCol = cx - w.bufWidth + rw
|
||||||
ret = true
|
ret = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -449,7 +455,7 @@ func (w *BufWindow) displayBuffer() {
|
|||||||
|
|
||||||
currentLine := false
|
currentLine := false
|
||||||
for _, c := range cursors {
|
for _, c := range cursors {
|
||||||
if bloc.Y == c.Y && w.active {
|
if !c.HasSelection() && bloc.Y == c.Y && w.active {
|
||||||
currentLine = true
|
currentLine = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -619,6 +625,8 @@ func (w *BufWindow) displayBuffer() {
|
|||||||
|
|
||||||
wrap := func() {
|
wrap := func() {
|
||||||
vloc.X = 0
|
vloc.X = 0
|
||||||
|
|
||||||
|
if vloc.Y >= 0 {
|
||||||
if w.hasMessage {
|
if w.hasMessage {
|
||||||
w.drawGutter(&vloc, &bloc)
|
w.drawGutter(&vloc, &bloc)
|
||||||
}
|
}
|
||||||
@ -630,6 +638,9 @@ func (w *BufWindow) displayBuffer() {
|
|||||||
if b.Settings["ruler"].(bool) {
|
if b.Settings["ruler"].(bool) {
|
||||||
w.drawLineNum(lineNumStyle, true, &vloc, &bloc)
|
w.drawLineNum(lineNumStyle, true, &vloc, &bloc)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
vloc.X = w.gutterOffset
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type glyph struct {
|
type glyph struct {
|
||||||
|
@ -2,12 +2,12 @@ package display
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
runewidth "github.com/mattn/go-runewidth"
|
runewidth "github.com/mattn/go-runewidth"
|
||||||
|
"github.com/micro-editor/tcell/v2"
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/info"
|
"github.com/zyedidia/micro/v2/internal/info"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type InfoWindow struct {
|
type InfoWindow struct {
|
||||||
|
@ -291,13 +291,7 @@ func (w *BufWindow) diff(s1, s2 SLoc) int {
|
|||||||
// within the buffer boundaries.
|
// within the buffer boundaries.
|
||||||
func (w *BufWindow) Scroll(s SLoc, n int) SLoc {
|
func (w *BufWindow) Scroll(s SLoc, n int) SLoc {
|
||||||
if !w.Buf.Settings["softwrap"].(bool) {
|
if !w.Buf.Settings["softwrap"].(bool) {
|
||||||
s.Line += n
|
s.Line = util.Clamp(s.Line+n, 0, w.Buf.LinesNum()-1)
|
||||||
if s.Line < 0 {
|
|
||||||
s.Line = 0
|
|
||||||
}
|
|
||||||
if s.Line > w.Buf.LinesNum()-1 {
|
|
||||||
s.Line = w.Buf.LinesNum() - 1
|
|
||||||
}
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
return w.scroll(s, n)
|
return w.scroll(s, n)
|
||||||
|
@ -47,6 +47,12 @@ var statusInfo = map[string]func(*buffer.Buffer) string{
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
},
|
},
|
||||||
|
"overwrite": func(b *buffer.Buffer) string {
|
||||||
|
if b.OverwriteMode && !b.Type.Readonly {
|
||||||
|
return "[ovwr] "
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
},
|
||||||
"lines": func(b *buffer.Buffer) string {
|
"lines": func(b *buffer.Buffer) string {
|
||||||
return strconv.Itoa(b.LinesNum())
|
return strconv.Itoa(b.LinesNum())
|
||||||
},
|
},
|
||||||
|
@ -2,7 +2,7 @@ package display
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
runewidth "github.com/mattn/go-runewidth"
|
runewidth "github.com/mattn/go-runewidth"
|
||||||
"github.com/zyedidia/tcell/v2"
|
"github.com/micro-editor/tcell/v2"
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package display
|
package display
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/micro-editor/tcell/v2"
|
||||||
|
"github.com/micro-editor/terminal"
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/micro/v2/internal/shell"
|
"github.com/zyedidia/micro/v2/internal/shell"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
"github.com/zyedidia/terminal"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TermWindow struct {
|
type TermWindow struct {
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
package info
|
package info
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"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/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,24 +21,23 @@ func (i *InfoBuf) LoadHistory() {
|
|||||||
if config.GetGlobalOption("savehistory").(bool) {
|
if config.GetGlobalOption("savehistory").(bool) {
|
||||||
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", "history"))
|
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", "history"))
|
||||||
var decodedMap map[string][]string
|
var decodedMap map[string][]string
|
||||||
if err == nil {
|
|
||||||
defer file.Close()
|
|
||||||
decoder := gob.NewDecoder(file)
|
|
||||||
err = decoder.Decode(&decodedMap)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
i.Error("Error loading history:", err)
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
i.Error("Error loading history: ", err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
err = gob.NewDecoder(file).Decode(&decodedMap)
|
||||||
|
if err != nil {
|
||||||
|
i.Error("Error decoding history: ", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if decodedMap != nil {
|
if decodedMap != nil {
|
||||||
i.History = decodedMap
|
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"))
|
var buf bytes.Buffer
|
||||||
if err == nil {
|
err := gob.NewEncoder(&buf).Encode(i.History)
|
||||||
defer file.Close()
|
|
||||||
encoder := gob.NewEncoder(file)
|
|
||||||
|
|
||||||
err = encoder.Encode(i.History)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
i.Error("Error saving history:", err)
|
screen.TermMessage("Error encoding history: ", err)
|
||||||
return
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,6 +125,7 @@ func importIo() *lua.LTable {
|
|||||||
L.SetField(pkg, "MultiWriter", luar.New(L, io.MultiWriter))
|
L.SetField(pkg, "MultiWriter", luar.New(L, io.MultiWriter))
|
||||||
L.SetField(pkg, "NewSectionReader", luar.New(L, io.NewSectionReader))
|
L.SetField(pkg, "NewSectionReader", luar.New(L, io.NewSectionReader))
|
||||||
L.SetField(pkg, "Pipe", luar.New(L, io.Pipe))
|
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, "ReadAtLeast", luar.New(L, io.ReadAtLeast))
|
||||||
L.SetField(pkg, "ReadFull", luar.New(L, io.ReadFull))
|
L.SetField(pkg, "ReadFull", luar.New(L, io.ReadFull))
|
||||||
L.SetField(pkg, "TeeReader", luar.New(L, io.TeeReader))
|
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, "PathListSeparator", luar.New(L, os.PathListSeparator))
|
||||||
L.SetField(pkg, "PathSeparator", luar.New(L, os.PathSeparator))
|
L.SetField(pkg, "PathSeparator", luar.New(L, os.PathSeparator))
|
||||||
L.SetField(pkg, "Pipe", luar.New(L, os.Pipe))
|
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, "Readlink", luar.New(L, os.Readlink))
|
||||||
L.SetField(pkg, "Remove", luar.New(L, os.Remove))
|
L.SetField(pkg, "Remove", luar.New(L, os.Remove))
|
||||||
L.SetField(pkg, "RemoveAll", luar.New(L, os.RemoveAll))
|
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, "TempDir", luar.New(L, os.TempDir))
|
||||||
L.SetField(pkg, "Truncate", luar.New(L, os.Truncate))
|
L.SetField(pkg, "Truncate", luar.New(L, os.Truncate))
|
||||||
L.SetField(pkg, "UserHomeDir", luar.New(L, os.UserHomeDir))
|
L.SetField(pkg, "UserHomeDir", luar.New(L, os.UserHomeDir))
|
||||||
|
L.SetField(pkg, "WriteFile", luar.New(L, os.WriteFile))
|
||||||
|
|
||||||
return pkg
|
return pkg
|
||||||
}
|
}
|
||||||
@ -423,7 +427,6 @@ func importPath() *lua.LTable {
|
|||||||
func importFilePath() *lua.LTable {
|
func importFilePath() *lua.LTable {
|
||||||
pkg := L.NewTable()
|
pkg := L.NewTable()
|
||||||
|
|
||||||
L.SetField(pkg, "Join", luar.New(L, filepath.Join))
|
|
||||||
L.SetField(pkg, "Abs", luar.New(L, filepath.Abs))
|
L.SetField(pkg, "Abs", luar.New(L, filepath.Abs))
|
||||||
L.SetField(pkg, "Base", luar.New(L, filepath.Base))
|
L.SetField(pkg, "Base", luar.New(L, filepath.Base))
|
||||||
L.SetField(pkg, "Clean", luar.New(L, filepath.Clean))
|
L.SetField(pkg, "Clean", luar.New(L, filepath.Clean))
|
||||||
|
@ -6,9 +6,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/micro-editor/tcell/v2"
|
||||||
"github.com/zyedidia/micro/v2/internal/config"
|
"github.com/zyedidia/micro/v2/internal/config"
|
||||||
"github.com/zyedidia/micro/v2/internal/util"
|
"github.com/zyedidia/micro/v2/internal/util"
|
||||||
"github.com/zyedidia/tcell/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Screen is the tcell screen we use to draw to the terminal
|
// Screen is the tcell screen we use to draw to the terminal
|
||||||
@ -33,6 +33,12 @@ var lock sync.Mutex
|
|||||||
// written to even if no event user event has occurred
|
// written to even if no event user event has occurred
|
||||||
var drawChan chan bool
|
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
|
// Lock locks the screen lock
|
||||||
func Lock() {
|
func Lock() {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
@ -121,6 +127,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
|
// TempFini shuts the screen down temporarily
|
||||||
func TempFini() bool {
|
func TempFini() bool {
|
||||||
screenWasNil := Screen == nil
|
screenWasNil := Screen == nil
|
||||||
@ -150,10 +184,13 @@ func Init() error {
|
|||||||
drawChan = make(chan bool, 8)
|
drawChan = make(chan bool, 8)
|
||||||
|
|
||||||
// Should we enable true color?
|
// Should we enable true color?
|
||||||
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
|
truecolor := config.GetGlobalOption("truecolor").(string)
|
||||||
|
if truecolor == "on" || (truecolor == "auto" && os.Getenv("MICRO_TRUECOLOR") == "1") {
|
||||||
if !truecolor {
|
os.Setenv("TCELL_TRUECOLOR", "enable")
|
||||||
|
} else if truecolor == "off" {
|
||||||
os.Setenv("TCELL_TRUECOLOR", "disable")
|
os.Setenv("TCELL_TRUECOLOR", "disable")
|
||||||
|
} else {
|
||||||
|
// For "auto", tcell already autodetects truecolor by default
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldTerm string
|
var oldTerm string
|
||||||
@ -195,6 +232,10 @@ func Init() error {
|
|||||||
Screen.EnableMouse()
|
Screen.EnableMouse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, r := range rawSeq {
|
||||||
|
Screen.RegisterRawSeq(r)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,8 +78,10 @@ func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(
|
|||||||
go func() {
|
go func() {
|
||||||
// Run the process in the background and create the onExit callback
|
// Run the process in the background and create the onExit callback
|
||||||
proc.Run()
|
proc.Run()
|
||||||
|
if onExit != nil {
|
||||||
jobFunc := JobFunction{onExit, outbuf.String(), userargs}
|
jobFunc := JobFunction{onExit, outbuf.String(), userargs}
|
||||||
Jobs <- jobFunc
|
Jobs <- jobFunc
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return &Job{proc, stdin}
|
return &Job{proc, stdin}
|
||||||
|
@ -5,9 +5,9 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/micro-editor/terminal"
|
||||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||||
"github.com/zyedidia/micro/v2/internal/screen"
|
"github.com/zyedidia/micro/v2/internal/screen"
|
||||||
"github.com/zyedidia/terminal"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TermType int
|
type TermType int
|
||||||
|
@ -6,7 +6,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -43,8 +45,44 @@ var (
|
|||||||
Stdout *bytes.Buffer
|
Stdout *bytes.Buffer
|
||||||
// Sigterm is a channel where micro exits when written
|
// Sigterm is a channel where micro exits when written
|
||||||
Sigterm chan os.Signal
|
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 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() {
|
func init() {
|
||||||
var err error
|
var err error
|
||||||
SemVersion, err = semver.Make(Version)
|
SemVersion, err = semver.Make(Version)
|
||||||
@ -233,18 +271,6 @@ func IsNonWordChar(r rune) bool {
|
|||||||
return !IsWordChar(r)
|
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'
|
// 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.
|
// 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 '_'.
|
// For now the only sub-word delimiter character is '_'.
|
||||||
@ -332,6 +358,28 @@ func RunePos(b []byte, i int) int {
|
|||||||
return CharacterCount(b[:i])
|
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
|
// MakeRelative will attempt to make a relative path between path and base
|
||||||
func MakeRelative(path, base string) (string, error) {
|
func MakeRelative(path, base string) (string, error) {
|
||||||
if len(path) > 0 {
|
if len(path) > 0 {
|
||||||
@ -398,8 +446,17 @@ func GetModTime(path string) (time.Time, error) {
|
|||||||
return info.ModTime(), nil
|
return info.ModTime(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EscapePath replaces every path separator in a given path with a %
|
func AppendBackupSuffix(path string) string {
|
||||||
func EscapePath(path string) string {
|
return path + ".micro-backup"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
path = filepath.ToSlash(path)
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
// ':' is not valid in a path name on Windows but is ok on Unix
|
// ':' is not valid in a path name on Windows but is ok on Unix
|
||||||
@ -408,6 +465,24 @@ func EscapePath(path string) string {
|
|||||||
return strings.ReplaceAll(path, "/", "%")
|
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).
|
||||||
|
func DetermineEscapePath(dir string, path 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
|
||||||
|
}
|
||||||
|
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
// GetLeadingWhitespace returns the leading whitespace of the given byte array
|
// GetLeadingWhitespace returns the leading whitespace of the given byte array
|
||||||
func GetLeadingWhitespace(b []byte) []byte {
|
func GetLeadingWhitespace(b []byte) []byte {
|
||||||
ws := []byte{}
|
ws := []byte{}
|
||||||
@ -510,11 +585,6 @@ func IsAutocomplete(c rune) bool {
|
|||||||
return c == '.' || IsWordChar(c)
|
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)
|
// String converts a byte array to a string (for lua plugins)
|
||||||
func String(s []byte) string {
|
func String(s []byte) string {
|
||||||
return string(s)
|
return string(s)
|
||||||
@ -585,3 +655,77 @@ func HttpRequest(method string, url string, headers []string) (resp *http.Respon
|
|||||||
}
|
}
|
||||||
return client.Do(req)
|
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 := AppendBackupSuffix(path)
|
||||||
|
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
|
// 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:])
|
copy(n.children[i:], n.children[i+1:])
|
||||||
n.children[len(n.children)-1] = nil
|
n.children[len(n.children)-1] = nil
|
||||||
n.children = n.children[:len(n.children)-1]
|
n.children = n.children[:len(n.children)-1]
|
||||||
|
|
||||||
|
h := n.Kind == STVert
|
||||||
nonrs, numr := n.getResizeInfo(h)
|
nonrs, numr := n.getResizeInfo(h)
|
||||||
if numr == 0 {
|
if numr == 0 {
|
||||||
// This means that this was the last child
|
// This means that this was the last child
|
||||||
@ -470,18 +471,62 @@ func (n *Node) Unsplit() bool {
|
|||||||
ind = i
|
ind = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if n.parent.Kind == STVert {
|
n.parent.unsplit(ind)
|
||||||
n.parent.unsplit(ind, true)
|
|
||||||
} else {
|
|
||||||
n.parent.unsplit(ind, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.parent.IsLeaf() {
|
if n.parent.IsLeaf() {
|
||||||
return n.parent.Unsplit()
|
return n.parent.Unsplit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
n.parent.flatten()
|
||||||
return true
|
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)
|
// String returns the string form of the node and all children (used for debugging)
|
||||||
func (n *Node) String() string {
|
func (n *Node) String() string {
|
||||||
var strf func(n *Node, ident int) string
|
var strf func(n *Node, ident int) string
|
||||||
|
@ -51,19 +51,6 @@ func runePos(p int, str []byte) int {
|
|||||||
return CharacterCount(str[:p])
|
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
|
// A State represents the region at the end of a line
|
||||||
type State *region
|
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 {
|
if curRegion.group == curRegion.limitGroup || p.group == curRegion.limitGroup {
|
||||||
matches := findAllIndex(p.regex, line)
|
matches := findAllIndex(p.regex, line)
|
||||||
for _, m := range matches {
|
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++ {
|
for i := m[0]; i < m[1]; i++ {
|
||||||
fullHighlights[i] = p.group
|
fullHighlights[i] = p.group
|
||||||
}
|
}
|
||||||
|
@ -258,7 +258,7 @@ func ParseDef(f *File, header *Header) (s *Def, err error) {
|
|||||||
|
|
||||||
if s.rules == nil {
|
if s.rules == nil {
|
||||||
// allow empty rules
|
// allow empty rules
|
||||||
s.rules = new(rules)
|
s.rules = &rules{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, err
|
return s, err
|
||||||
@ -476,11 +476,18 @@ func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegio
|
|||||||
r.limitGroup = r.group
|
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.([]interface{}), r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.rules == nil {
|
||||||
|
// allow empty rules
|
||||||
|
r.rules = &rules{}
|
||||||
|
}
|
||||||
|
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,6 @@ color-link gutter-warning "88"
|
|||||||
color-link cursor-line "229"
|
color-link cursor-line "229"
|
||||||
#color-link color-column "196"
|
#color-link color-column "196"
|
||||||
color-link current-line-number "246"
|
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 tab-error "210"
|
||||||
color-link trailingws "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
|
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
|
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
|
colors while using the terminal emulator will be slightly off). Not all
|
||||||
terminals support true color but at this point most do. True color
|
terminals support true color but at this point most do (see below).
|
||||||
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`).
|
|
||||||
True-color colorschemes in micro typically end with `-tc`, such as
|
True-color colorschemes in micro typically end with `-tc`, such as
|
||||||
`solarized-tc`, `atom-dark`, `material-tc`, etc... If true color is not
|
`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
|
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
|
||||||
|
|
||||||
True color requires your terminal to support it. This means that the
|
Micro enables true color support by default as long as it detects that the
|
||||||
environment variable `COLORTERM` should have the value `truecolor`, `24bit`,
|
terminal supports it (which is usually indicated by the environment variable
|
||||||
or `24-bit`. In addition, to enable true color in micro, the environment
|
`COLORTERM` being set to `truecolor`, `24bit` or `24-bit`). You can also force
|
||||||
variable `MICRO_TRUECOLOR` must be set to 1. Note that you have to create
|
enabling it unconditionally by setting the option `truecolor` to `on` (or
|
||||||
and set this variable yourself.
|
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.
|
* `solarized-tc`: this is the solarized colorscheme for true color.
|
||||||
* `atom-dark`: this colorscheme is based off of Atom's "dark" colorscheme.
|
* `atom-dark`: this colorscheme is based off of Atom's "dark" colorscheme.
|
||||||
@ -177,10 +175,14 @@ Here is a list of the colorscheme groups that you can use:
|
|||||||
* todo
|
* todo
|
||||||
* selection (Color of the text selection)
|
* selection (Color of the text selection)
|
||||||
* statusline (Color of the statusline)
|
* 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 (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
|
* indent-char (Color of the character which indicates tabs if the option is
|
||||||
enabled)
|
enabled)
|
||||||
* line-number
|
* line-number
|
||||||
|
* gutter-info
|
||||||
* gutter-error
|
* gutter-error
|
||||||
* gutter-warning
|
* gutter-warning
|
||||||
* diff-added
|
* diff-added
|
||||||
@ -371,7 +373,6 @@ highlighted. For example:
|
|||||||
start: "\""
|
start: "\""
|
||||||
end: "\""
|
end: "\""
|
||||||
skip: "\\."
|
skip: "\\."
|
||||||
rules: []
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Includes
|
#### 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
|
This command will modify `bindings.json` and overwrite any bindings to
|
||||||
`key` that already exist.
|
`key` that already exist.
|
||||||
|
|
||||||
* `help ['topic']`: opens the corresponding help topic. If no topic is provided
|
* `help ['topic'] ['flags']`: opens the corresponding help topics.
|
||||||
opens the default help screen. Help topics are stored as `.md` files in the
|
If no topic is provided opens the default help screen. If multiple topics are
|
||||||
`runtime/help` directory of the source tree, which is embedded in the final
|
provided (separated via ` `) they are opened all as splits.
|
||||||
binary.
|
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
|
* `save ['filename']`: saves the current buffer. If the file is provided it
|
||||||
will 'save as' the filename.
|
will 'save as' the filename.
|
||||||
@ -72,12 +78,15 @@ quotes here but these are not necessary when entering the command in micro.
|
|||||||
command's output will be displayed in one line when it finishes running.
|
command's output will be displayed in one line when it finishes running.
|
||||||
|
|
||||||
* `vsplit ['filename']`: opens a vertical split with `filename`. If no filename
|
* `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
|
* `hsplit ['filename']`: same as `vsplit` but opens a horizontal split instead
|
||||||
of a vertical split.
|
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.
|
* `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
|
If `n` is prefixed with `-` or `+`, then it represents a relative position
|
||||||
|
@ -66,7 +66,9 @@ bindings, tab is bound as
|
|||||||
|
|
||||||
This means that if the `Autocomplete` action is successful, the chain will
|
This means that if the `Autocomplete` action is successful, the chain will
|
||||||
abort. Otherwise, it will try `IndentSelection`, and if that fails too, it
|
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.
|
||||||
|
|
||||||
## Binding commands
|
## Binding commands
|
||||||
|
|
||||||
@ -168,14 +170,15 @@ CursorLeft
|
|||||||
CursorRight
|
CursorRight
|
||||||
CursorStart
|
CursorStart
|
||||||
CursorEnd
|
CursorEnd
|
||||||
|
CursorToViewTop
|
||||||
|
CursorToViewCenter
|
||||||
|
CursorToViewBottom
|
||||||
SelectToStart
|
SelectToStart
|
||||||
SelectToEnd
|
SelectToEnd
|
||||||
SelectUp
|
SelectUp
|
||||||
SelectDown
|
SelectDown
|
||||||
SelectLeft
|
SelectLeft
|
||||||
SelectRight
|
SelectRight
|
||||||
SelectToStartOfText
|
|
||||||
SelectToStartOfTextToggle
|
|
||||||
WordRight
|
WordRight
|
||||||
WordLeft
|
WordLeft
|
||||||
SubWordRight
|
SubWordRight
|
||||||
@ -184,20 +187,22 @@ SelectWordRight
|
|||||||
SelectWordLeft
|
SelectWordLeft
|
||||||
SelectSubWordRight
|
SelectSubWordRight
|
||||||
SelectSubWordLeft
|
SelectSubWordLeft
|
||||||
MoveLinesUp
|
|
||||||
MoveLinesDown
|
|
||||||
DeleteWordRight
|
DeleteWordRight
|
||||||
DeleteWordLeft
|
DeleteWordLeft
|
||||||
DeleteSubWordRight
|
DeleteSubWordRight
|
||||||
DeleteSubWordLeft
|
DeleteSubWordLeft
|
||||||
SelectLine
|
SelectLine
|
||||||
SelectToStartOfLine
|
SelectToStartOfLine
|
||||||
|
SelectToStartOfText
|
||||||
|
SelectToStartOfTextToggle
|
||||||
SelectToEndOfLine
|
SelectToEndOfLine
|
||||||
|
ParagraphPrevious
|
||||||
|
ParagraphNext
|
||||||
|
SelectToParagraphPrevious
|
||||||
|
SelectToParagraphNext
|
||||||
InsertNewline
|
InsertNewline
|
||||||
InsertSpace
|
|
||||||
Backspace
|
Backspace
|
||||||
Delete
|
Delete
|
||||||
Center
|
|
||||||
InsertTab
|
InsertTab
|
||||||
Save
|
Save
|
||||||
SaveAll
|
SaveAll
|
||||||
@ -206,21 +211,28 @@ Find
|
|||||||
FindLiteral
|
FindLiteral
|
||||||
FindNext
|
FindNext
|
||||||
FindPrevious
|
FindPrevious
|
||||||
DiffPrevious
|
|
||||||
DiffNext
|
DiffNext
|
||||||
|
DiffPrevious
|
||||||
|
Center
|
||||||
Undo
|
Undo
|
||||||
Redo
|
Redo
|
||||||
Copy
|
Copy
|
||||||
CopyLine
|
CopyLine
|
||||||
Cut
|
Cut
|
||||||
CutLine
|
CutLine
|
||||||
|
Duplicate
|
||||||
DuplicateLine
|
DuplicateLine
|
||||||
DeleteLine
|
DeleteLine
|
||||||
|
MoveLinesUp
|
||||||
|
MoveLinesDown
|
||||||
IndentSelection
|
IndentSelection
|
||||||
OutdentSelection
|
OutdentSelection
|
||||||
|
Autocomplete
|
||||||
|
CycleAutocompleteBack
|
||||||
OutdentLine
|
OutdentLine
|
||||||
IndentLine
|
IndentLine
|
||||||
Paste
|
Paste
|
||||||
|
PastePrimary
|
||||||
SelectAll
|
SelectAll
|
||||||
OpenFile
|
OpenFile
|
||||||
Start
|
Start
|
||||||
@ -231,33 +243,37 @@ SelectPageUp
|
|||||||
SelectPageDown
|
SelectPageDown
|
||||||
HalfPageUp
|
HalfPageUp
|
||||||
HalfPageDown
|
HalfPageDown
|
||||||
StartOfLine
|
|
||||||
EndOfLine
|
|
||||||
StartOfText
|
StartOfText
|
||||||
StartOfTextToggle
|
StartOfTextToggle
|
||||||
ParagraphPrevious
|
StartOfLine
|
||||||
ParagraphNext
|
EndOfLine
|
||||||
SelectToParagraphPrevious
|
|
||||||
SelectToParagraphNext
|
|
||||||
ToggleHelp
|
ToggleHelp
|
||||||
|
ToggleKeyMenu
|
||||||
ToggleDiffGutter
|
ToggleDiffGutter
|
||||||
ToggleRuler
|
ToggleRuler
|
||||||
JumpLine
|
ToggleHighlightSearch
|
||||||
|
UnhighlightSearch
|
||||||
ResetSearch
|
ResetSearch
|
||||||
ClearInfo
|
|
||||||
ClearStatus
|
ClearStatus
|
||||||
ShellMode
|
ShellMode
|
||||||
CommandMode
|
CommandMode
|
||||||
|
ToggleOverwriteMode
|
||||||
|
Escape
|
||||||
Quit
|
Quit
|
||||||
QuitAll
|
QuitAll
|
||||||
|
ForceQuit
|
||||||
AddTab
|
AddTab
|
||||||
PreviousTab
|
PreviousTab
|
||||||
NextTab
|
NextTab
|
||||||
|
FirstTab
|
||||||
|
LastTab
|
||||||
NextSplit
|
NextSplit
|
||||||
|
PreviousSplit
|
||||||
|
FirstSplit
|
||||||
|
LastSplit
|
||||||
Unsplit
|
Unsplit
|
||||||
VSplit
|
VSplit
|
||||||
HSplit
|
HSplit
|
||||||
PreviousSplit
|
|
||||||
ToggleMacro
|
ToggleMacro
|
||||||
PlayMacro
|
PlayMacro
|
||||||
Suspend (Unix only)
|
Suspend (Unix only)
|
||||||
@ -270,14 +286,25 @@ SpawnMultiCursorSelect
|
|||||||
RemoveMultiCursor
|
RemoveMultiCursor
|
||||||
RemoveAllMultiCursors
|
RemoveAllMultiCursors
|
||||||
SkipMultiCursor
|
SkipMultiCursor
|
||||||
None
|
SkipMultiCursorBack
|
||||||
JumpToMatchingBrace
|
JumpToMatchingBrace
|
||||||
Autocomplete
|
JumpLine
|
||||||
|
Deselect
|
||||||
|
ClearInfo
|
||||||
|
None
|
||||||
```
|
```
|
||||||
|
|
||||||
The `StartOfTextToggle` and `SelectToStartOfTextToggle` actions toggle between
|
The `StartOfTextToggle` and `SelectToStartOfTextToggle` actions toggle between
|
||||||
jumping to the start of the text (first) and start of the line.
|
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)
|
You can also bind some mouse actions (these must be bound to mouse buttons)
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -495,23 +522,23 @@ conventions for text editing defaults.
|
|||||||
"Alt-]": "DiffNext|CursorEnd",
|
"Alt-]": "DiffNext|CursorEnd",
|
||||||
"Ctrl-z": "Undo",
|
"Ctrl-z": "Undo",
|
||||||
"Ctrl-y": "Redo",
|
"Ctrl-y": "Redo",
|
||||||
"Ctrl-c": "CopyLine|Copy",
|
"Ctrl-c": "Copy|CopyLine",
|
||||||
"Ctrl-x": "Cut",
|
"Ctrl-x": "Cut|CutLine",
|
||||||
"Ctrl-k": "CutLine",
|
"Ctrl-k": "CutLine",
|
||||||
"Ctrl-d": "DuplicateLine",
|
"Ctrl-d": "Duplicate|DuplicateLine",
|
||||||
"Ctrl-v": "Paste",
|
"Ctrl-v": "Paste",
|
||||||
"Ctrl-a": "SelectAll",
|
"Ctrl-a": "SelectAll",
|
||||||
"Ctrl-t": "AddTab",
|
"Ctrl-t": "AddTab",
|
||||||
"Alt-,": "PreviousTab",
|
"Alt-,": "PreviousTab|LastTab",
|
||||||
"Alt-.": "NextTab",
|
"Alt-.": "NextTab|FirstTab",
|
||||||
"Home": "StartOfText",
|
"Home": "StartOfText",
|
||||||
"End": "EndOfLine",
|
"End": "EndOfLine",
|
||||||
"CtrlHome": "CursorStart",
|
"CtrlHome": "CursorStart",
|
||||||
"CtrlEnd": "CursorEnd",
|
"CtrlEnd": "CursorEnd",
|
||||||
"PageUp": "CursorPageUp",
|
"PageUp": "CursorPageUp",
|
||||||
"PageDown": "CursorPageDown",
|
"PageDown": "CursorPageDown",
|
||||||
"CtrlPageUp": "PreviousTab",
|
"CtrlPageUp": "PreviousTab|LastTab",
|
||||||
"CtrlPageDown": "NextTab",
|
"CtrlPageDown": "NextTab|FirstTab",
|
||||||
"ShiftPageUp": "SelectPageUp",
|
"ShiftPageUp": "SelectPageUp",
|
||||||
"ShiftPageDown": "SelectPageDown",
|
"ShiftPageDown": "SelectPageDown",
|
||||||
"Ctrl-g": "ToggleHelp",
|
"Ctrl-g": "ToggleHelp",
|
||||||
@ -522,7 +549,7 @@ conventions for text editing defaults.
|
|||||||
"Ctrl-b": "ShellMode",
|
"Ctrl-b": "ShellMode",
|
||||||
"Ctrl-q": "Quit",
|
"Ctrl-q": "Quit",
|
||||||
"Ctrl-e": "CommandMode",
|
"Ctrl-e": "CommandMode",
|
||||||
"Ctrl-w": "NextSplit",
|
"Ctrl-w": "NextSplit|FirstSplit",
|
||||||
"Ctrl-u": "ToggleMacro",
|
"Ctrl-u": "ToggleMacro",
|
||||||
"Ctrl-j": "PlayMacro",
|
"Ctrl-j": "PlayMacro",
|
||||||
"Insert": "ToggleOverwriteMode",
|
"Insert": "ToggleOverwriteMode",
|
||||||
@ -621,8 +648,8 @@ are given below:
|
|||||||
"Backtab": "CycleAutocompleteBack",
|
"Backtab": "CycleAutocompleteBack",
|
||||||
"Ctrl-z": "Undo",
|
"Ctrl-z": "Undo",
|
||||||
"Ctrl-y": "Redo",
|
"Ctrl-y": "Redo",
|
||||||
"Ctrl-c": "CopyLine|Copy",
|
"Ctrl-c": "Copy|CopyLine",
|
||||||
"Ctrl-x": "Cut",
|
"Ctrl-x": "Cut|CutLine",
|
||||||
"Ctrl-k": "CutLine",
|
"Ctrl-k": "CutLine",
|
||||||
"Ctrl-v": "Paste",
|
"Ctrl-v": "Paste",
|
||||||
"Home": "StartOfTextToggle",
|
"Home": "StartOfTextToggle",
|
||||||
|
@ -79,22 +79,16 @@ Here are the available options:
|
|||||||
|
|
||||||
default value: `0`
|
default value: `0`
|
||||||
|
|
||||||
* `colorscheme`: loads the colorscheme stored in
|
* `colorscheme`: use the given colorscheme. This setting is `global only`.
|
||||||
$(configDir)/colorschemes/`option`.micro, 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`
|
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
|
* `cursorline`: highlight the line that the cursor is on in a different color
|
||||||
(the color is defined by the colorscheme you are using).
|
(the color is defined by the colorscheme you are using).
|
||||||
|
|
||||||
@ -172,6 +166,13 @@ Here are the available options:
|
|||||||
default value: `unknown`. This will be automatically overridden depending
|
default value: `unknown`. This will be automatically overridden depending
|
||||||
on the file you open.
|
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
|
* `hlsearch`: highlight all instances of the searched text after a successful
|
||||||
search. This highlighting can be temporarily turned off via the
|
search. This highlighting can be temporarily turned off via the
|
||||||
`UnhighlightSearch` action (triggered by the Esc key by default) or toggled
|
`UnhighlightSearch` action (triggered by the Esc key by default) or toggled
|
||||||
@ -194,11 +195,11 @@ Here are the available options:
|
|||||||
|
|
||||||
default value: `false`
|
default value: `false`
|
||||||
|
|
||||||
* `incsearch`: enable incremental search in "Find" prompt (matching as you type).
|
* `ignorecase`: perform case-insensitive searches.
|
||||||
|
|
||||||
default value: `true`
|
default value: `true`
|
||||||
|
|
||||||
* `ignorecase`: perform case-insensitive searches.
|
* `incsearch`: enable incremental search in "Find" prompt (matching as you type).
|
||||||
|
|
||||||
default value: `true`
|
default value: `true`
|
||||||
|
|
||||||
@ -278,6 +279,23 @@ Here are the available options:
|
|||||||
|
|
||||||
default value: `tab`
|
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
|
* `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
|
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
|
the terminal keybinding (not `Ctrl-v`, which is micro's default paste
|
||||||
@ -287,16 +305,6 @@ Here are the available options:
|
|||||||
|
|
||||||
default value: `false`
|
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
|
* `permbackup`: this option causes backups (see `backup` option) to be
|
||||||
permanently saved. With permanent backups, micro will not remove backups when
|
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
|
files are closed and will never apply them to existing files. Use this option
|
||||||
@ -321,6 +329,12 @@ Here are the available options:
|
|||||||
|
|
||||||
default value: `false`
|
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
|
* `reload`: controls the reload behavior of the current buffer in case the file
|
||||||
has changed. The available options are `prompt`, `auto` & `disabled`.
|
has changed. The available options are `prompt`, `auto` & `disabled`.
|
||||||
|
|
||||||
@ -338,12 +352,6 @@ Here are the available options:
|
|||||||
|
|
||||||
default value: `true`
|
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
|
* `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
|
put it there when you open the file again. Information is saved to
|
||||||
`~/.config/micro/buffers/`
|
`~/.config/micro/buffers/`
|
||||||
@ -401,11 +409,11 @@ Here are the available options:
|
|||||||
* `statusformatl`: format string definition for the left-justified part of the
|
* `statusformatl`: format string definition for the left-justified part of the
|
||||||
statusline. Special directives should be placed inside `$()`. Special
|
statusline. Special directives should be placed inside `$()`. Special
|
||||||
directives include: `filename`, `modified`, `line`, `col`, `lines`,
|
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
|
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.
|
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)`
|
ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)`
|
||||||
|
|
||||||
* `statusformatr`: format string definition for the right-justified part of the
|
* `statusformatr`: format string definition for the right-justified part of the
|
||||||
@ -427,20 +435,20 @@ Here are the available options:
|
|||||||
|
|
||||||
default value: `true`
|
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
|
* `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
|
(e.g. move over 4 spaces at once). This option only does anything if
|
||||||
`tabstospaces` is on.
|
`tabstospaces` is on.
|
||||||
|
|
||||||
default value: `false`
|
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.
|
* `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.
|
* `tabsize`: the size in spaces that a tab character should be displayed with.
|
||||||
|
|
||||||
@ -454,6 +462,19 @@ Here are the available options:
|
|||||||
|
|
||||||
default value: `false`
|
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
|
* `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
|
primary clipboard to copy selections in the background. This does not affect
|
||||||
the normal clipboard using `Ctrl-c` and `Ctrl-v`.
|
the normal clipboard using `Ctrl-c` and `Ctrl-v`.
|
||||||
@ -493,13 +514,13 @@ or disable them:
|
|||||||
recent Git commit rather than the diff since opening the file.
|
recent Git commit rather than the diff since opening the file.
|
||||||
|
|
||||||
Any option you set in the editor will be saved to 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
|
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
|
## 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
|
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,
|
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.
|
so that you can see what the formatting should look like.
|
||||||
@ -518,18 +539,24 @@ so that you can see what the formatting should look like.
|
|||||||
"colorscheme": "default",
|
"colorscheme": "default",
|
||||||
"comment": true,
|
"comment": true,
|
||||||
"cursorline": true,
|
"cursorline": true,
|
||||||
|
"detectlimit": 100,
|
||||||
"diff": true,
|
"diff": true,
|
||||||
"diffgutter": false,
|
"diffgutter": false,
|
||||||
"divchars": "|-",
|
"divchars": "|-",
|
||||||
"divreverse": true,
|
"divreverse": true,
|
||||||
"encoding": "utf-8",
|
"encoding": "utf-8",
|
||||||
"eofnewline": true,
|
"eofnewline": true,
|
||||||
|
"fakecursor": false,
|
||||||
"fastdirty": false,
|
"fastdirty": false,
|
||||||
"fileformat": "unix",
|
"fileformat": "unix",
|
||||||
"filetype": "unknown",
|
"filetype": "unknown",
|
||||||
"incsearch": true,
|
|
||||||
"ftoptions": true,
|
"ftoptions": true,
|
||||||
|
"helpsplit": "hsplit",
|
||||||
|
"hlsearch": false,
|
||||||
|
"hltaberrors": false,
|
||||||
|
"hltrailingws": false,
|
||||||
"ignorecase": true,
|
"ignorecase": true,
|
||||||
|
"incsearch": true,
|
||||||
"indentchar": " ",
|
"indentchar": " ",
|
||||||
"infobar": true,
|
"infobar": true,
|
||||||
"initlua": true,
|
"initlua": true,
|
||||||
@ -542,6 +569,8 @@ so that you can see what the formatting should look like.
|
|||||||
"matchbracestyle": "underline",
|
"matchbracestyle": "underline",
|
||||||
"mkparents": false,
|
"mkparents": false,
|
||||||
"mouse": true,
|
"mouse": true,
|
||||||
|
"multiopen": "tab",
|
||||||
|
"pageoverlap": 2,
|
||||||
"parsecursor": false,
|
"parsecursor": false,
|
||||||
"paste": false,
|
"paste": false,
|
||||||
"permbackup": false,
|
"permbackup": false,
|
||||||
@ -551,12 +580,14 @@ so that you can see what the formatting should look like.
|
|||||||
"pluginrepos": [],
|
"pluginrepos": [],
|
||||||
"readonly": false,
|
"readonly": false,
|
||||||
"relativeruler": false,
|
"relativeruler": false,
|
||||||
|
"reload": "prompt",
|
||||||
"rmtrailingws": false,
|
"rmtrailingws": false,
|
||||||
"ruler": true,
|
"ruler": true,
|
||||||
"savecursor": false,
|
"savecursor": false,
|
||||||
"savehistory": true,
|
"savehistory": true,
|
||||||
"saveundo": false,
|
"saveundo": false,
|
||||||
"scrollbar": false,
|
"scrollbar": false,
|
||||||
|
"scrollbarchar": "|",
|
||||||
"scrollmargin": 3,
|
"scrollmargin": 3,
|
||||||
"scrollspeed": 2,
|
"scrollspeed": 2,
|
||||||
"smartpaste": true,
|
"smartpaste": true,
|
||||||
@ -564,17 +595,18 @@ so that you can see what the formatting should look like.
|
|||||||
"splitbottom": true,
|
"splitbottom": true,
|
||||||
"splitright": true,
|
"splitright": true,
|
||||||
"status": 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",
|
"statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
|
||||||
"statusline": true,
|
"statusline": true,
|
||||||
"sucmd": "sudo",
|
"sucmd": "sudo",
|
||||||
"syntax": true,
|
"syntax": true,
|
||||||
"tabmovement": false,
|
|
||||||
"tabhighlight": true,
|
"tabhighlight": true,
|
||||||
|
"tabmovement": false,
|
||||||
"tabreverse": false,
|
"tabreverse": false,
|
||||||
"tabsize": 4,
|
"tabsize": 4,
|
||||||
"tabstospaces": false,
|
"tabstospaces": false,
|
||||||
"useprimary": true,
|
"useprimary": true,
|
||||||
|
"wordwrap": false,
|
||||||
"xterm": false
|
"xterm": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -57,11 +57,13 @@ that micro defines:
|
|||||||
|
|
||||||
* `deinit()`: cleanup function called when your plugin is unloaded or reloaded.
|
* `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
|
* `onBufferOpen(buf)`: runs when a buffer is opened. The input contains
|
||||||
the buffer object.
|
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
|
* `onBufPaneOpen(bufpane)`: runs when a bufpane is opened. The input
|
||||||
contains the bufpane object.
|
contains the bufpane object.
|
||||||
|
|
||||||
@ -350,7 +352,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
|
- `IsWordChar(s string) bool`: returns true if the first rune in a
|
||||||
string is a word character.
|
string is a word character.
|
||||||
- `String(b []byte) string`: converts a byte array to a string.
|
- `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.
|
- `Unzip(src, dest string) error`: unzips a file to given folder.
|
||||||
- `Version`: micro's version number or commit hash
|
- `Version`: micro's version number or commit hash
|
||||||
- `SemVersion`: micro's semantic version
|
- `SemVersion`: micro's semantic version
|
||||||
|
@ -107,7 +107,7 @@ function commentLine(bp, lineN, indentLen)
|
|||||||
bp.Cursor.Y = curpos.Y
|
bp.Cursor.Y = curpos.Y
|
||||||
end
|
end
|
||||||
bp.Cursor:Relocate()
|
bp.Cursor:Relocate()
|
||||||
bp.Cursor.LastVisualX = bp.Cursor:GetVisualX()
|
bp.Cursor:StoreVisualX()
|
||||||
end
|
end
|
||||||
|
|
||||||
function uncommentLine(bp, lineN, commentRegex)
|
function uncommentLine(bp, lineN, commentRegex)
|
||||||
@ -135,7 +135,7 @@ function uncommentLine(bp, lineN, commentRegex)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
bp.Cursor:Relocate()
|
bp.Cursor:Relocate()
|
||||||
bp.Cursor.LastVisualX = bp.Cursor:GetVisualX()
|
bp.Cursor:StoreVisualX()
|
||||||
end
|
end
|
||||||
|
|
||||||
function toggleCommentLine(bp, lineN, commentRegex)
|
function toggleCommentLine(bp, lineN, commentRegex)
|
||||||
|
@ -9,6 +9,7 @@ following filetypes and linters:
|
|||||||
* **c++**: g++
|
* **c++**: g++
|
||||||
* **d**: dmd
|
* **d**: dmd
|
||||||
* **go**: go build
|
* **go**: go build
|
||||||
|
* **go**: go vet
|
||||||
* **haskell**: hlint
|
* **haskell**: hlint
|
||||||
* **java**: javac
|
* **java**: javac
|
||||||
* **javascript**: jshint
|
* **javascript**: jshint
|
||||||
@ -16,11 +17,16 @@ following filetypes and linters:
|
|||||||
* **literate**: lit
|
* **literate**: lit
|
||||||
* **lua**: luacheck
|
* **lua**: luacheck
|
||||||
* **nim**: nim
|
* **nim**: nim
|
||||||
|
* **nix**: nix-linter
|
||||||
* **objective-c**: clang
|
* **objective-c**: clang
|
||||||
* **python**: pyflakes
|
* **python**: flake8
|
||||||
* **python**: mypy
|
* **python**: mypy
|
||||||
|
* **python**: pyflakes
|
||||||
* **python**: pylint
|
* **python**: pylint
|
||||||
|
* **python**: ruff
|
||||||
|
* **rust**: cargo clippy
|
||||||
* **shell**: shfmt
|
* **shell**: shfmt
|
||||||
|
* **shell**: shellcheck
|
||||||
* **swift**: swiftc (MacOS and Linux only)
|
* **swift**: swiftc (MacOS and Linux only)
|
||||||
* **yaml**: yamllint
|
* **yaml**: yamllint
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ function preinit()
|
|||||||
end
|
end
|
||||||
|
|
||||||
makeLinter("gcc", "c", "gcc", {"-fsyntax-only", "-Wall", "-Wextra", "%f"}, "%f:%l:%c:.+: %m")
|
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("dmd", "d", "dmd", {"-color=off", "-o-", "-w", "-wi", "-c", "%f"}, "%f%(%l%):.+: %m")
|
||||||
makeLinter("eslint", "javascript", "eslint", {"-f","compact","%f"}, "%f: line %l, col %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("gobuild", "go", "go", {"build", "-o", devnull, "%d"}, "%f:%l:%c:? %m")
|
||||||
@ -82,6 +82,7 @@ function preinit()
|
|||||||
makeLinter("pyflakes", "python", "pyflakes", {"%f"}, "%f:%l:.-:? %m")
|
makeLinter("pyflakes", "python", "pyflakes", {"%f"}, "%f:%l:.-:? %m")
|
||||||
makeLinter("mypy", "python", "mypy", {"%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("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("flake8", "python", "flake8", {"%f"}, "%f:%l:%c: %m")
|
||||||
makeLinter("shfmt", "shell", "shfmt", {"%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")
|
makeLinter("shellcheck", "shell", "shellcheck", {"-f", "gcc", "%f"}, "%f:%l:%c:.+: %m")
|
||||||
@ -107,12 +108,7 @@ function contains(list, element)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
function runLinter(buf)
|
function checkFtMatch(ft, v)
|
||||||
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
|
local ftmatch = ft == v.filetype
|
||||||
if v.domatch then
|
if v.domatch then
|
||||||
ftmatch = string.match(ft, v.filetype)
|
ftmatch = string.match(ft, v.filetype)
|
||||||
@ -125,8 +121,16 @@ function runLinter(buf)
|
|||||||
if hasOS and not v.whitelist then
|
if hasOS and not v.whitelist then
|
||||||
ftmatch = false
|
ftmatch = false
|
||||||
end
|
end
|
||||||
|
return ftmatch
|
||||||
|
end
|
||||||
|
|
||||||
if ftmatch then
|
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
|
||||||
|
if checkFtMatch(ft, v) then
|
||||||
local args = {}
|
local args = {}
|
||||||
for k, arg in pairs(v.args) do
|
for k, arg in pairs(v.args) do
|
||||||
args[k] = arg:gsub("%%f", file):gsub("%%d", dir)
|
args[k] = arg:gsub("%%f", file):gsub("%%d", dir)
|
||||||
@ -141,6 +145,19 @@ function onSave(bp)
|
|||||||
return true
|
return true
|
||||||
end
|
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)
|
function lint(buf, linter, cmd, args, errorformat, loff, coff, callback)
|
||||||
buf:ClearMessages(linter)
|
buf:ClearMessages(linter)
|
||||||
|
|
||||||
|
@ -47,7 +47,6 @@ function onBufferOpen(buf)
|
|||||||
syntaxFile = syntaxFile .. " - special:\n"
|
syntaxFile = syntaxFile .. " - special:\n"
|
||||||
syntaxFile = syntaxFile .. " start: \"@\\\\{\"\n"
|
syntaxFile = syntaxFile .. " start: \"@\\\\{\"\n"
|
||||||
syntaxFile = syntaxFile .. " end: \"\\\\}\"\n"
|
syntaxFile = syntaxFile .. " end: \"\\\\}\"\n"
|
||||||
syntaxFile = syntaxFile .. " rules: []\n"
|
|
||||||
syntaxFile = syntaxFile .. " - include: " .. codetype .. "\n"
|
syntaxFile = syntaxFile .. " - include: " .. codetype .. "\n"
|
||||||
|
|
||||||
config.AddRuntimeFileFromMemory(config.RTSyntax, "literate.yaml", syntaxFile)
|
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:
|
This plugin provides functions that can be used in the status line format:
|
||||||
|
|
||||||
* `status.branch`: returns the name of the current git branch.
|
* `status.branch`: returns the name of the current git branch in the repository
|
||||||
* `status.hash`: returns the hash of the current git commit.
|
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"
|
* `status.paste`: returns "" if the paste option is disabled and "PASTE"
|
||||||
if it is enabled.
|
if it is enabled.
|
||||||
* `status.lines`: returns the number of lines in the buffer.
|
* `status.lines`: returns the number of lines in the buffer.
|
||||||
|
@ -3,7 +3,10 @@ VERSION = "1.0.0"
|
|||||||
local micro = import("micro")
|
local micro = import("micro")
|
||||||
local buffer = import("micro/buffer")
|
local buffer = import("micro/buffer")
|
||||||
local config = import("micro/config")
|
local config = import("micro/config")
|
||||||
|
local shell = import("micro/shell")
|
||||||
|
local filepath = import("filepath")
|
||||||
local humanize = import("humanize")
|
local humanize = import("humanize")
|
||||||
|
local strings = import("strings")
|
||||||
|
|
||||||
function init()
|
function init()
|
||||||
micro.SetStatusInfoFn("status.branch")
|
micro.SetStatusInfoFn("status.branch")
|
||||||
@ -21,7 +24,7 @@ function lines(b)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function vcol(b)
|
function vcol(b)
|
||||||
return tostring(b:GetActiveCursor():GetVisualX())
|
return tostring(b:GetActiveCursor():GetVisualX(false))
|
||||||
end
|
end
|
||||||
|
|
||||||
function bytes(b)
|
function bytes(b)
|
||||||
@ -32,30 +35,23 @@ function size(b)
|
|||||||
return humanize.Bytes(b:Size())
|
return humanize.Bytes(b:Size())
|
||||||
end
|
end
|
||||||
|
|
||||||
function branch(b)
|
local function parseRevision(b, opt)
|
||||||
if b.Type.Kind ~= buffer.BTInfo then
|
if b.Type.Kind ~= buffer.BTInfo then
|
||||||
local shell = import("micro/shell")
|
local dir = filepath.Dir(b.Path)
|
||||||
local strings = import("strings")
|
local str, err = shell.ExecCommand("git", "-C", dir, "rev-parse", opt, "HEAD")
|
||||||
|
|
||||||
local branch, err = shell.ExecCommand("git", "rev-parse", "--abbrev-ref", "HEAD")
|
|
||||||
if err == nil then
|
if err == nil then
|
||||||
return strings.TrimSpace(branch)
|
return strings.TrimSpace(str)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
return ""
|
return ""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function branch(b)
|
||||||
|
return parseRevision(b, "--abbrev-ref")
|
||||||
end
|
end
|
||||||
|
|
||||||
function hash(b)
|
function hash(b)
|
||||||
if b.Type.Kind ~= 5 then
|
return parseRevision(b, "--short")
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function paste(b)
|
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.
|
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.
|
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.
|
You can read more about how to write syntax files (and colorschemes) in the [colors](../help/colors.md) documentation.
|
||||||
|
|
||||||
# Legacy '.micro' filetype
|
# Legacy '.micro' filetype
|
||||||
@ -38,6 +33,19 @@ Micro syntax files are almost identical to Nano's, except for some key differenc
|
|||||||
* Micro does not use `icolor`. Instead, for a case insensitive match, use the case insensitive flag (`i`) in the regular expression
|
* Micro does not use `icolor`. Instead, for a case insensitive match, use the case insensitive flag (`i`) in the regular expression
|
||||||
* For example, `icolor green ".*"` would become `color green "(?i).*"`
|
* For example, `icolor green ".*"` would become `color green "(?i).*"`
|
||||||
|
|
||||||
|
# Incompatibilities with older versions of micro
|
||||||
|
|
||||||
|
With PR [#3458](https://github.com/zyedidia/micro/pull/3458) resp. commit
|
||||||
|
[a9b513a](https://github.com/zyedidia/micro/commit/a9b513a28adaaa7782505dc1e284e1a0132cb66f)
|
||||||
|
empty `rules: []` definitions are removed from all syntax files, since
|
||||||
|
`rules` are no longer mandatory.
|
||||||
|
Unfortunately they are mandatory for `micro` versions up to and including `v2.0.14`.
|
||||||
|
|
||||||
|
To use newer syntax definitions from this repository with older `micro` versions
|
||||||
|
you have to add these `rules: []` to all regions not including `rules` already.
|
||||||
|
Otherwise you need to use syntax definitions before the above mentioned PR
|
||||||
|
for example from version [v2.0.14](https://github.com/zyedidia/micro/tree/v2.0.14).
|
||||||
|
|
||||||
# Using with colorschemes
|
# Using with colorschemes
|
||||||
|
|
||||||
Not all of these files have been converted to use micro's colorscheme feature. Most of them just hardcode the colors, which can be problematic depending on the colorscheme you use.
|
Not all of these files have been converted to use micro's colorscheme feature. Most of them just hardcode the colors, which can be problematic depending on the colorscheme you use.
|
||||||
|
@ -4,7 +4,7 @@ detect:
|
|||||||
filename: "\\.(S|s|asm)$"
|
filename: "\\.(S|s|asm)$"
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
# This file is made for NASM assembly
|
# This file is made mainly for NASM assembly
|
||||||
|
|
||||||
## Instructions
|
## Instructions
|
||||||
# x86
|
# x86
|
||||||
@ -108,3 +108,16 @@ rules:
|
|||||||
rules:
|
rules:
|
||||||
- todo: "(TODO|XXX|FIXME):?"
|
- todo: "(TODO|XXX|FIXME):?"
|
||||||
|
|
||||||
|
## C-like comments (supported by some assemblers)
|
||||||
|
|
||||||
|
- comment:
|
||||||
|
start: "//"
|
||||||
|
end: "$"
|
||||||
|
rules:
|
||||||
|
- todo: "(TODO|XXX|FIXME):?"
|
||||||
|
|
||||||
|
- comment:
|
||||||
|
start: "/\\*"
|
||||||
|
end: "\\*/"
|
||||||
|
rules:
|
||||||
|
- todo: "(TODO|XXX|FIXME):?"
|
||||||
|
@ -19,5 +19,4 @@ rules:
|
|||||||
- comment:
|
- comment:
|
||||||
start: "#"
|
start: "#"
|
||||||
end: "$"
|
end: "$"
|
||||||
rules: []
|
|
||||||
|
|
||||||
|
@ -30,7 +30,6 @@ rules:
|
|||||||
- preproc:
|
- preproc:
|
||||||
start: "\\$(\\{|ENV\\{)"
|
start: "\\$(\\{|ENV\\{)"
|
||||||
end: "\\}"
|
end: "\\}"
|
||||||
rules: []
|
|
||||||
|
|
||||||
- identifier.macro: "\\b(APPLE|UNIX|WIN32|CYGWIN|BORLAND|MINGW|MSVC(_IDE|60|71|80|90)?)\\b"
|
- identifier.macro: "\\b(APPLE|UNIX|WIN32|CYGWIN|BORLAND|MINGW|MSVC(_IDE|60|71|80|90)?)\\b"
|
||||||
|
|
||||||
|
@ -27,12 +27,10 @@ rules:
|
|||||||
- constant.string:
|
- constant.string:
|
||||||
start: "`"
|
start: "`"
|
||||||
end: "`"
|
end: "`"
|
||||||
rules: []
|
|
||||||
|
|
||||||
- constant.string:
|
- constant.string:
|
||||||
start: "%x\\{"
|
start: "%x\\{"
|
||||||
end: "\\}"
|
end: "\\}"
|
||||||
rules: []
|
|
||||||
|
|
||||||
- constant.string:
|
- constant.string:
|
||||||
start: "\""
|
start: "\""
|
||||||
@ -68,5 +66,4 @@ rules:
|
|||||||
- constant:
|
- constant:
|
||||||
start: "<<-?'?EOT'?"
|
start: "<<-?'?EOT'?"
|
||||||
end: "^EOT"
|
end: "^EOT"
|
||||||
rules: []
|
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
@ -109,13 +109,10 @@ rules:
|
|||||||
- comment:
|
- comment:
|
||||||
start: "//"
|
start: "//"
|
||||||
end: "$"
|
end: "$"
|
||||||
rules: []
|
|
||||||
- comment:
|
- comment:
|
||||||
start: "/\\*"
|
start: "/\\*"
|
||||||
end: "\\*/"
|
end: "\\*/"
|
||||||
rules: []
|
|
||||||
- comment:
|
- comment:
|
||||||
start: "/\\+"
|
start: "/\\+"
|
||||||
end: "\\+/"
|
end: "\\+/"
|
||||||
rules: []
|
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ rules:
|
|||||||
- default:
|
- default:
|
||||||
start: "<%"
|
start: "<%"
|
||||||
end: "%>"
|
end: "%>"
|
||||||
rules: []
|
|
||||||
|
|
||||||
- preproc: "<%|%>"
|
- preproc: "<%|%>"
|
||||||
- red: "&[^;[[:space:]]]*;"
|
- red: "&[^;[[:space:]]]*;"
|
||||||
@ -37,6 +36,5 @@ rules:
|
|||||||
- identifier.macro:
|
- identifier.macro:
|
||||||
start: "<<-?'?EOT'?"
|
start: "<<-?'?EOT'?"
|
||||||
end: "^EOT"
|
end: "^EOT"
|
||||||
rules: []
|
|
||||||
|
|
||||||
- todo: "(XXX|TODO|FIXME|\\?\\?\\?)"
|
- todo: "(XXX|TODO|FIXME|\\?\\?\\?)"
|
||||||
|
@ -22,7 +22,6 @@ rules:
|
|||||||
start: "'"
|
start: "'"
|
||||||
end: "'"
|
end: "'"
|
||||||
skip: "\\\\."
|
skip: "\\\\."
|
||||||
rules: []
|
|
||||||
# - constant.specialChar: "%."
|
# - constant.specialChar: "%."
|
||||||
# - constant.specialChar: "\\\\[abfnrtv'\\\"\\\\]"
|
# - constant.specialChar: "\\\\[abfnrtv'\\\"\\\\]"
|
||||||
# - constant.specialChar: "\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})"
|
# - constant.specialChar: "\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})"
|
||||||
@ -42,4 +41,3 @@ rules:
|
|||||||
- comment:
|
- comment:
|
||||||
start: "%"
|
start: "%"
|
||||||
end: "$"
|
end: "$"
|
||||||
rules: []
|
|
||||||
|
@ -38,7 +38,6 @@ rules:
|
|||||||
start: "'"
|
start: "'"
|
||||||
end: "'"
|
end: "'"
|
||||||
skip: "\\\\."
|
skip: "\\\\."
|
||||||
rules: []
|
|
||||||
|
|
||||||
- comment:
|
- comment:
|
||||||
start: "#"
|
start: "#"
|
||||||
|
@ -19,7 +19,6 @@ rules:
|
|||||||
- constant.string:
|
- constant.string:
|
||||||
start: "\\b([Ss.]\" )"
|
start: "\\b([Ss.]\" )"
|
||||||
end: "\""
|
end: "\""
|
||||||
rules: []
|
|
||||||
|
|
||||||
- comment:
|
- comment:
|
||||||
start: "\\("
|
start: "\\("
|
||||||
|
@ -45,4 +45,3 @@ rules:
|
|||||||
- comment:
|
- comment:
|
||||||
start: "\\(\\*"
|
start: "\\(\\*"
|
||||||
end: "\\*\\)"
|
end: "\\*\\)"
|
||||||
rules: []
|
|
||||||
|
@ -10,7 +10,6 @@ rules:
|
|||||||
- special:
|
- special:
|
||||||
start: "^```"
|
start: "^```"
|
||||||
end: "^```"
|
end: "^```"
|
||||||
rules: []
|
|
||||||
# heading lines
|
# heading lines
|
||||||
- special: "^#{1,3}.*"
|
- special: "^#{1,3}.*"
|
||||||
# unordered list items
|
# unordered list items
|
||||||
|
@ -20,4 +20,3 @@ rules:
|
|||||||
- comment:
|
- comment:
|
||||||
start: "#"
|
start: "#"
|
||||||
end: "$"
|
end: "$"
|
||||||
rules: []
|
|
||||||
|
@ -22,7 +22,6 @@ rules:
|
|||||||
- comment.line:
|
- comment.line:
|
||||||
start: "^#"
|
start: "^#"
|
||||||
end: "$"
|
end: "$"
|
||||||
rules: []
|
|
||||||
|
|
||||||
# Diffs (i.e. git commit --verbose)
|
# Diffs (i.e. git commit --verbose)
|
||||||
- default:
|
- default:
|
||||||
|
@ -11,4 +11,3 @@ rules:
|
|||||||
- comment:
|
- comment:
|
||||||
start: "#"
|
start: "#"
|
||||||
end: "$"
|
end: "$"
|
||||||
rules: []
|
|
||||||
|
@ -16,4 +16,3 @@ rules:
|
|||||||
- comment.line:
|
- comment.line:
|
||||||
start: "^#"
|
start: "^#"
|
||||||
end: "$"
|
end: "$"
|
||||||
rules: []
|
|
||||||
|
@ -47,7 +47,6 @@ rules:
|
|||||||
- constant.string:
|
- constant.string:
|
||||||
start: "`"
|
start: "`"
|
||||||
end: "`"
|
end: "`"
|
||||||
rules: []
|
|
||||||
|
|
||||||
- comment:
|
- comment:
|
||||||
start: "//"
|
start: "//"
|
||||||
|
@ -44,4 +44,3 @@ rules:
|
|||||||
- comment:
|
- comment:
|
||||||
start: "#"
|
start: "#"
|
||||||
end: "$"
|
end: "$"
|
||||||
rules: []
|
|
||||||
|
@ -11,7 +11,6 @@ rules:
|
|||||||
- constant:
|
- constant:
|
||||||
start: "(\\\\|\\\\\\\\)n\\["
|
start: "(\\\\|\\\\\\\\)n\\["
|
||||||
end: "]"
|
end: "]"
|
||||||
rules: []
|
|
||||||
|
|
||||||
- type: "^\\.[[:space:]]*[^[[:space:]]]*"
|
- type: "^\\.[[:space:]]*[^[[:space:]]]*"
|
||||||
- comment: "^\\.\\\\\".*$"
|
- comment: "^\\.\\\\\".*$"
|
||||||
@ -19,12 +18,10 @@ rules:
|
|||||||
- constant.string:
|
- constant.string:
|
||||||
start: "(\\\\|\\\\\\\\)\\*\\["
|
start: "(\\\\|\\\\\\\\)\\*\\["
|
||||||
end: "]"
|
end: "]"
|
||||||
rules: []
|
|
||||||
|
|
||||||
- constant.specialChar: "\\\\\\(.."
|
- constant.specialChar: "\\\\\\(.."
|
||||||
- constant.specialChar:
|
- constant.specialChar:
|
||||||
start: "\\\\\\["
|
start: "\\\\\\["
|
||||||
end: "]"
|
end: "]"
|
||||||
rules: []
|
|
||||||
|
|
||||||
- identifier.macro: "\\\\\\\\\\$[1-9]"
|
- identifier.macro: "\\\\\\\\\\$[1-9]"
|
||||||
|
@ -75,7 +75,6 @@ rules:
|
|||||||
- identifier:
|
- identifier:
|
||||||
start: "[$][{]"
|
start: "[$][{]"
|
||||||
end: "[}]"
|
end: "[}]"
|
||||||
rules: []
|
|
||||||
|
|
||||||
# Triple-single-quoted strings
|
# Triple-single-quoted strings
|
||||||
- constant.string:
|
- constant.string:
|
||||||
@ -90,7 +89,6 @@ rules:
|
|||||||
- constant.string:
|
- constant.string:
|
||||||
start: "[$]/"
|
start: "[$]/"
|
||||||
end: "/[$]"
|
end: "/[$]"
|
||||||
rules: []
|
|
||||||
|
|
||||||
# Single-line comments
|
# Single-line comments
|
||||||
- comment:
|
- comment:
|
||||||
@ -110,4 +108,3 @@ rules:
|
|||||||
- comment:
|
- comment:
|
||||||
start: "/[*][*]@?"
|
start: "/[*][*]@?"
|
||||||
end: "[*]/"
|
end: "[*]/"
|
||||||
rules: []
|
|
||||||
|
@ -4,25 +4,28 @@ detect:
|
|||||||
filename: "\\.hs$"
|
filename: "\\.hs$"
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
|
- symbol.operator: "[!#$%&:*+/<=>?@.\\\\^\\|~\\p{Sm}\\-]+"
|
||||||
|
|
||||||
|
# Identifiers (with or without a module name)
|
||||||
|
- type: "\\b([A-Z][A-Za-z0-9_]*\\.)*[A-Z]+[A-Za-z0-9_']*\\b"
|
||||||
|
- default: "\\b([A-Z][A-Za-z0-9_]*\\.)*[a-z][A-Za-z0-9_']*\\b"
|
||||||
|
|
||||||
|
- statement: ";"
|
||||||
|
- symbol.bracket: "[\\(\\)\\[\\]\\{\\}]"
|
||||||
|
- special: "`[A-Za-z0-9']+`"
|
||||||
|
|
||||||
# Keywords
|
# Keywords
|
||||||
- statement: "\\b(as|case|of|class|data|default|deriving|do|forall|foreign|hiding|if|then|else|import|infix|infixl|infixr|instance|let|in|mdo|module|newtype|qualified|type|where)\\b"
|
- statement: "\\b(case|of|class|data|default|deriving|do|forall|foreign|hiding|if|then|else|import|infix|infixl|infixr|instance|let|in|mdo|module|newtype|qualified|type|where)\\b"
|
||||||
|
|
||||||
# Various symbols
|
|
||||||
- symbol: "(\\||@|!|:|_|~|=|\\\\|;|\\(\\)|,|\\[|\\]|\\{|\\})"
|
|
||||||
|
|
||||||
# Operators
|
|
||||||
- symbol.operator: "(==|/=|&&|\\|\\||<|>|<=|>=)"
|
|
||||||
|
|
||||||
# Various symbols
|
|
||||||
- special: "(->|<-)"
|
|
||||||
- symbol: "\\.|\\$"
|
|
||||||
|
|
||||||
# Data constructors
|
# Data constructors
|
||||||
- constant.bool: "\\b(True|False)\\b"
|
- constant.bool: "\\b(True|False)\\b"
|
||||||
- constant: "\\b(Nothing|Just|Left|Right|LT|EQ|GT)\\b"
|
- constant: "\\b(Nothing|Just|Left|Right|LT|EQ|GT)\\b"
|
||||||
|
|
||||||
|
- constant: "\\(\\)" # Unit
|
||||||
|
- constant.number: "\\b(0[xX][0-9A-Fa-f]+|0[oO][0-7]+|0[bB][01]+|[-]?[0-9]+([.][0-9]+)?([eE][+-]?[0-9]+)?)\\b"
|
||||||
|
|
||||||
# Data classes
|
# Data classes
|
||||||
- identifier.class: "\\b(Read|Show|Enum|Eq|Ord|Data|Bounded|Typeable|Num|Real|Fractional|Integral|RealFrac|Floating|RealFloat|Monad|MonadPlus|Functor|Foldable|Additive|Zip)[ ]"
|
- identifier.class: "\\b(Additive|Applicative|Bounded|Data|Enum|Eq|Floating|Foldable|Fractional|Functor|Integral|Monad|MonadPlus|Monoid|Num|Ord|Read|Real|RealFloat|RealFrac|Semigroup|Show|Traversable|Typeable|Zip)[ ]"
|
||||||
|
|
||||||
# Strings
|
# Strings
|
||||||
- constant.string:
|
- constant.string:
|
||||||
@ -30,7 +33,8 @@ rules:
|
|||||||
end: "\""
|
end: "\""
|
||||||
skip: "\\\\."
|
skip: "\\\\."
|
||||||
rules:
|
rules:
|
||||||
- constant.specialChar: "\\\\."
|
- special: "\\\\&"
|
||||||
|
- constant.specialChar: "\\\\([abfnrtv\"'\\\\]|[0-9]+|x[0-9a-fA-F]+|o[0-7]+|NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE|DC[1-4]|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS|US|SP|DEL)"
|
||||||
|
|
||||||
# Comments
|
# Comments
|
||||||
- comment:
|
- comment:
|
||||||
@ -45,4 +49,4 @@ rules:
|
|||||||
rules:
|
rules:
|
||||||
- todo: "(TODO|XXX|FIXME):?"
|
- todo: "(TODO|XXX|FIXME):?"
|
||||||
|
|
||||||
- identifier.micro: "undefined"
|
- identifier.macro: "undefined"
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user