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:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.19.x]
|
||||
go-version: [1.23.x]
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
|
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@ -8,7 +8,7 @@ jobs:
|
||||
release:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.19.x]
|
||||
go-version: [1.23.x]
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
|
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
@ -4,7 +4,7 @@ jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.19.x]
|
||||
go-version: [1.19.x, 1.23.x]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
|
@ -430,7 +430,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
github.com/gdamore/tcell/LICENSE
|
||||
================
|
||||
github.com/zyedidia/tcell/LICENSE (fork)
|
||||
github.com/micro-editor/tcell/LICENSE (fork)
|
||||
================
|
||||
|
||||
|
||||
@ -1048,7 +1048,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
|
||||
github.com/flynn/json5/LICENSE
|
||||
================
|
||||
github.com/zyedidia/json5/LICENSE (fork)
|
||||
github.com/micro-editor/json5/LICENSE (fork)
|
||||
================
|
||||
|
||||
Decoder code based on package encoding/json from the Go language.
|
||||
@ -1108,7 +1108,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
github.com/james4k/terminal/LICENSE
|
||||
================
|
||||
github.com/zyedidia/terminal/LICENSE (fork)
|
||||
github.com/micro-editor/terminal/LICENSE (fork)
|
||||
================
|
||||
|
||||
Copyright (C) 2013 James Gray
|
||||
|
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)
|
||||
DATE = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
|
||||
go run tools/build-date.go)
|
||||
ADDITIONAL_GO_LINKER_FLAGS = $(shell GOOS=$(shell go env GOHOSTOS) \
|
||||
GOARCH=$(shell go env GOHOSTARCH) \
|
||||
go run tools/info-plist.go "$(shell go env GOOS)" "$(VERSION)")
|
||||
GOBIN ?= $(shell go env GOPATH)/bin
|
||||
GOVARS = -X github.com/zyedidia/micro/v2/internal/util.Version=$(VERSION) -X github.com/zyedidia/micro/v2/internal/util.CommitHash=$(HASH) -X 'github.com/zyedidia/micro/v2/internal/util.CompileDate=$(DATE)'
|
||||
DEBUGVAR = -X github.com/zyedidia/micro/v2/internal/util.Debug=ON
|
||||
VSCODE_TESTS_BASE_URL = 'https://raw.githubusercontent.com/microsoft/vscode/e6a45f4242ebddb7aa9a229f85555e8a3bd987e2/src/vs/editor/test/common/model/'
|
||||
CGO_ENABLED := $(if $(CGO_ENABLED),$(CGO_ENABLED),0)
|
||||
|
||||
ADDITIONAL_GO_LINKER_FLAGS := ""
|
||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||
ifeq ($(GOHOSTOS), darwin)
|
||||
# Native darwin resp. macOS builds need external and dynamic linking
|
||||
ADDITIONAL_GO_LINKER_FLAGS += $(shell GOOS=$(GOHOSTOS) \
|
||||
GOARCH=$(shell go env GOHOSTARCH) \
|
||||
go run tools/info-plist.go "$(shell go env GOOS)" "$(VERSION)")
|
||||
CGO_ENABLED = 1
|
||||
endif
|
||||
|
||||
build: generate build-quick
|
||||
|
||||
build-quick:
|
||||
go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
CGO_ENABLED=$(CGO_ENABLED) go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
|
||||
build-dbg:
|
||||
go build -trimpath -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS) $(DEBUGVAR)" ./cmd/micro
|
||||
CGO_ENABLED=$(CGO_ENABLED) go build -trimpath -ldflags "$(ADDITIONAL_GO_LINKER_FLAGS) $(DEBUGVAR)" ./cmd/micro
|
||||
|
||||
build-tags: fetch-tags generate
|
||||
go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
build-tags: fetch-tags build
|
||||
|
||||
build-all: build
|
||||
|
||||
@ -32,7 +39,7 @@ install: generate
|
||||
install-all: install
|
||||
|
||||
fetch-tags:
|
||||
git fetch --tags
|
||||
git fetch --tags --force
|
||||
|
||||
generate:
|
||||
GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) go generate ./runtime
|
||||
|
38
README.md
38
README.md
@ -18,25 +18,9 @@ Here is a picture of micro editing its source code.
|
||||

|
||||
|
||||
To see more screenshots of micro, showcasing some of the default color schemes, see [here](https://micro-editor.github.io).
|
||||
|
||||
|
||||
You can also check out the website for Micro at https://micro-editor.github.io.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Features](#features)
|
||||
- [Installation](#installation)
|
||||
- [Prebuilt binaries](#pre-built-binaries)
|
||||
- [Package Managers](#package-managers)
|
||||
- [Building from source](#building-from-source)
|
||||
- [Fully static binary](#fully-static-binary)
|
||||
- [macOS terminal](#macos-terminal)
|
||||
- [Linux clipboard support](#linux-clipboard-support)
|
||||
- [Colors and syntax highlighting](#colors-and-syntax-highlighting)
|
||||
- [Cygwin, Mingw, Plan9](#cygwin-mingw-plan9)
|
||||
- [Usage](#usage)
|
||||
- [Documentation and Help](#documentation-and-help)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
- - -
|
||||
|
||||
## Features
|
||||
@ -63,7 +47,7 @@ You can also check out the website for Micro at https://micro-editor.github.io.
|
||||
- Syntax highlighting for over [130 languages](runtime/syntax).
|
||||
- Color scheme support.
|
||||
- By default, micro comes with 16, 256, and true color themes.
|
||||
- True color support (set the `MICRO_TRUECOLOR` environment variable to 1 to enable it).
|
||||
- True color support.
|
||||
- Copy and paste with the system clipboard.
|
||||
- Small and simple.
|
||||
- Easily configurable.
|
||||
@ -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`.
|
||||
|
||||
#### Quick-install script
|
||||
#### Third-party quick-install script
|
||||
|
||||
```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.
|
||||
|
||||
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
|
||||
@ -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),
|
||||
and doesn't disable debug mode.
|
||||
|
||||
### Fully static binary
|
||||
### Fully static or dynamically linked binary
|
||||
|
||||
By default, the micro binary will dynamically link with core system libraries (this is generally
|
||||
recommended for security and portability). However, there is a fully static prebuilt binary that
|
||||
is provided for amd64 as `linux-static.tar.gz`, and to build a fully static binary from source, run
|
||||
By default, the micro binary is linked statically to increase the portability of the prebuilt binaries.
|
||||
This behavior can simply be overriden by providing `CGO_ENABLED=1` to the build target.
|
||||
|
||||
```
|
||||
CGO_ENABLED=0 make build
|
||||
CGO_ENABLED=1 make build
|
||||
```
|
||||
|
||||
Afterwards the micro binary will dynamically link with the present core system libraries.
|
||||
|
||||
**Note for Mac:**
|
||||
Native macOS builds are done with `CGO_ENABLED=1` forced set to support adding the "Information Property List" in the linker step.
|
||||
|
||||
### macOS terminal
|
||||
|
||||
If you are using macOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default terminal (Terminal.app). The iTerm2 terminal has much better mouse support as well as better handling of key events. For best keybinding behavior, choose `xterm defaults` under `Preferences->Profiles->Keys->Presets...`, and select `Esc+` for `Left Option Key` in the same menu. The newest versions also support true color.
|
||||
|
@ -3,8 +3,8 @@ package main
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
func shouldContinue() bool {
|
||||
@ -39,7 +40,16 @@ func CleanConfig() {
|
||||
}
|
||||
|
||||
fmt.Println("Cleaning default settings")
|
||||
config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
|
||||
|
||||
settingsFile := filepath.Join(config.ConfigDir, "settings.json")
|
||||
err := config.WriteSettings(settingsFile)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrOverwrite) {
|
||||
fmt.Println(err.Error())
|
||||
} else {
|
||||
fmt.Println("Error writing settings.json file: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// detect unused options
|
||||
var unusedOptions []string
|
||||
@ -67,16 +77,20 @@ func CleanConfig() {
|
||||
fmt.Printf("%s (value: %v)\n", s, config.GlobalSettings[s])
|
||||
}
|
||||
|
||||
fmt.Printf("These options will be removed from %s\n", filepath.Join(config.ConfigDir, "settings.json"))
|
||||
fmt.Printf("These options will be removed from %s\n", settingsFile)
|
||||
|
||||
if shouldContinue() {
|
||||
for _, s := range unusedOptions {
|
||||
delete(config.GlobalSettings, s)
|
||||
}
|
||||
|
||||
err := config.OverwriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
|
||||
err := config.OverwriteSettings(settingsFile)
|
||||
if err != nil {
|
||||
fmt.Println("Error writing settings.json file: " + err.Error())
|
||||
if errors.Is(err, util.ErrOverwrite) {
|
||||
fmt.Println(err.Error())
|
||||
} else {
|
||||
fmt.Println("Error overwriting settings.json file: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Removed unused options")
|
||||
@ -85,12 +99,13 @@ func CleanConfig() {
|
||||
}
|
||||
|
||||
// detect incorrectly formatted buffer/ files
|
||||
files, err := ioutil.ReadDir(filepath.Join(config.ConfigDir, "buffers"))
|
||||
buffersPath := filepath.Join(config.ConfigDir, "buffers")
|
||||
files, err := os.ReadDir(buffersPath)
|
||||
if err == nil {
|
||||
var badFiles []string
|
||||
var buffer buffer.SerializedBuffer
|
||||
for _, f := range files {
|
||||
fname := filepath.Join(config.ConfigDir, "buffers", f.Name())
|
||||
fname := filepath.Join(buffersPath, f.Name())
|
||||
file, e := os.Open(fname)
|
||||
|
||||
if e == nil {
|
||||
@ -105,9 +120,9 @@ func CleanConfig() {
|
||||
}
|
||||
|
||||
if len(badFiles) > 0 {
|
||||
fmt.Printf("Detected %d files with an invalid format in %s\n", len(badFiles), filepath.Join(config.ConfigDir, "buffers"))
|
||||
fmt.Printf("Detected %d files with an invalid format in %s\n", len(badFiles), buffersPath)
|
||||
fmt.Println("These files store cursor and undo history.")
|
||||
fmt.Printf("Removing badly formatted files in %s\n", filepath.Join(config.ConfigDir, "buffers"))
|
||||
fmt.Printf("Removing badly formatted files in %s\n", buffersPath)
|
||||
|
||||
if shouldContinue() {
|
||||
removed := 0
|
||||
|
@ -18,7 +18,7 @@ func (NullWriter) Write(data []byte) (n int, err error) {
|
||||
// InitLog sets up the debug log system for micro if it has been enabled by compile-time variables
|
||||
func InitLog() {
|
||||
if util.Debug == "ON" {
|
||||
f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, util.FileMode)
|
||||
if err != nil {
|
||||
log.Fatalf("error opening file: %v", err)
|
||||
}
|
||||
|
@ -4,10 +4,10 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
@ -18,6 +18,7 @@ import (
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
isatty "github.com/mattn/go-isatty"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/v2/internal/action"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
@ -26,7 +27,6 @@ import (
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -99,7 +99,7 @@ func InitFlags() {
|
||||
fmt.Println("Version:", util.Version)
|
||||
fmt.Println("Commit hash:", util.CommitHash)
|
||||
fmt.Println("Compiled on", util.CompileDate)
|
||||
os.Exit(0)
|
||||
exit(0)
|
||||
}
|
||||
|
||||
if *flagOptions {
|
||||
@ -115,7 +115,7 @@ func InitFlags() {
|
||||
fmt.Printf("-%s value\n", k)
|
||||
fmt.Printf(" \tDefault value: '%v'\n", v)
|
||||
}
|
||||
os.Exit(0)
|
||||
exit(0)
|
||||
}
|
||||
|
||||
if util.Debug == "OFF" && *flagDebug {
|
||||
@ -136,7 +136,7 @@ func DoPluginFlags() {
|
||||
CleanConfig()
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,7 +209,7 @@ func LoadInput(args []string) []*buffer.Buffer {
|
||||
// Option 2
|
||||
// The input is not a terminal, so something is being piped in
|
||||
// and we should read from stdin
|
||||
input, err = ioutil.ReadAll(os.Stdin)
|
||||
input, err = io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error reading from stdin: ", err)
|
||||
input = []byte{}
|
||||
@ -223,12 +223,55 @@ func LoadInput(args []string) []*buffer.Buffer {
|
||||
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() {
|
||||
defer func() {
|
||||
if util.Stdout.Len() > 0 {
|
||||
fmt.Fprint(os.Stdout, util.Stdout.String())
|
||||
}
|
||||
os.Exit(0)
|
||||
exit(0)
|
||||
}()
|
||||
|
||||
var err error
|
||||
@ -256,6 +299,12 @@ func main() {
|
||||
config.InitRuntimeFiles(true)
|
||||
config.InitPlugins()
|
||||
|
||||
err = checkBackup("settings.json")
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
err = config.ReadSettings()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
@ -288,7 +337,7 @@ func main() {
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Fatal: Micro could not initialize a Screen.")
|
||||
os.Exit(1)
|
||||
exit(1)
|
||||
}
|
||||
m := clipboard.SetMethod(config.GetGlobalOption("clipboard").(string))
|
||||
clipErr := clipboard.Initialize(m)
|
||||
@ -307,7 +356,7 @@ func main() {
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
b.Backup()
|
||||
}
|
||||
os.Exit(1)
|
||||
exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
@ -316,6 +365,12 @@ func main() {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
err = checkBackup("bindings.json")
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
action.InitBindings()
|
||||
action.InitCommands()
|
||||
|
||||
@ -434,24 +489,12 @@ func DoEvent() {
|
||||
}
|
||||
case f := <-timerChan:
|
||||
f()
|
||||
case b := <-buffer.BackupCompleteChan:
|
||||
b.RequestedBackup = false
|
||||
case <-sighup:
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
if !b.Modified() {
|
||||
b.Fini()
|
||||
}
|
||||
}
|
||||
os.Exit(0)
|
||||
exit(0)
|
||||
case <-util.Sigterm:
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
if !b.Modified() {
|
||||
b.Fini()
|
||||
}
|
||||
}
|
||||
|
||||
if screen.Screen != nil {
|
||||
screen.Screen.Fini()
|
||||
}
|
||||
os.Exit(0)
|
||||
exit(0)
|
||||
}
|
||||
|
||||
if e, ok := event.(*tcell.EventError); ok {
|
||||
@ -459,16 +502,7 @@ func DoEvent() {
|
||||
|
||||
if e.Err() == io.EOF {
|
||||
// shutdown due to terminal closing/becoming inaccessible
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
if !b.Modified() {
|
||||
b.Fini()
|
||||
}
|
||||
}
|
||||
|
||||
if screen.Screen != nil {
|
||||
screen.Screen.Fini()
|
||||
}
|
||||
os.Exit(0)
|
||||
exit(0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -2,18 +2,17 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zyedidia/micro/v2/internal/action"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
var tempDir string
|
||||
@ -26,7 +25,7 @@ func init() {
|
||||
func startup(args []string) (tcell.SimulationScreen, error) {
|
||||
var err error
|
||||
|
||||
tempDir, err = ioutil.TempDir("", "micro_test")
|
||||
tempDir, err = os.MkdirTemp("", "micro_test")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -164,20 +163,22 @@ func findBuffer(file string) *buffer.Buffer {
|
||||
return buf
|
||||
}
|
||||
|
||||
func createTestFile(name string, content string) (string, error) {
|
||||
testf, err := ioutil.TempFile("", name)
|
||||
func createTestFile(t *testing.T, content string) string {
|
||||
f, err := os.CreateTemp(t.TempDir(), "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := f.WriteString(content); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := testf.Write([]byte(content)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := testf.Close(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return testf.Name(), nil
|
||||
return f.Name()
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@ -194,18 +195,12 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
func TestSimpleEdit(t *testing.T) {
|
||||
file, err := createTestFile("micro_simple_edit_test", "base content")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(file)
|
||||
file := createTestFile(t, "base content")
|
||||
|
||||
openFile(file)
|
||||
|
||||
if findBuffer(file) == nil {
|
||||
t.Errorf("Could not find buffer %s", file)
|
||||
return
|
||||
t.Fatalf("Could not find buffer %s", file)
|
||||
}
|
||||
|
||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
||||
@ -223,28 +218,21 @@ func TestSimpleEdit(t *testing.T) {
|
||||
|
||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||
|
||||
data, err := ioutil.ReadFile(file)
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, "firstfoobar\nbase content\n", string(data))
|
||||
}
|
||||
|
||||
func TestMouse(t *testing.T) {
|
||||
file, err := createTestFile("micro_mouse_test", "base content")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(file)
|
||||
file := createTestFile(t, "base content")
|
||||
|
||||
openFile(file)
|
||||
|
||||
if findBuffer(file) == nil {
|
||||
t.Errorf("Could not find buffer %s", file)
|
||||
return
|
||||
t.Fatalf("Could not find buffer %s", file)
|
||||
}
|
||||
|
||||
// buffer:
|
||||
@ -275,10 +263,9 @@ func TestMouse(t *testing.T) {
|
||||
// base content
|
||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||
|
||||
data, err := ioutil.ReadFile(file)
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, "firstline\nsecondline\nbase content\n", string(data))
|
||||
@ -301,18 +288,12 @@ Ernleȝe test_string æðelen
|
||||
`
|
||||
|
||||
func TestSearchAndReplace(t *testing.T) {
|
||||
file, err := createTestFile("micro_search_replace_test", srTestStart)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(file)
|
||||
file := createTestFile(t, srTestStart)
|
||||
|
||||
openFile(file)
|
||||
|
||||
if findBuffer(file) == nil {
|
||||
t.Errorf("Could not find buffer %s", file)
|
||||
return
|
||||
t.Fatalf("Could not find buffer %s", file)
|
||||
}
|
||||
|
||||
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
|
||||
@ -321,10 +302,9 @@ func TestSearchAndReplace(t *testing.T) {
|
||||
|
||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||
|
||||
data, err := ioutil.ReadFile(file)
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, srTest2, string(data))
|
||||
@ -337,10 +317,9 @@ func TestSearchAndReplace(t *testing.T) {
|
||||
|
||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||
|
||||
data, err = ioutil.ReadFile(file)
|
||||
data, err = os.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, srTest3, string(data))
|
||||
|
@ -25,6 +25,7 @@
|
||||
<category>TextEditor</category>
|
||||
</categories>
|
||||
<releases>
|
||||
<release version="2.0.14" date="2024-08-27"/>
|
||||
<release version="2.0.13" date="2023-10-22"/>
|
||||
<release version="2.0.12" date="2023-09-06"/>
|
||||
<release version="2.0.11" date="2022-08-01"/>
|
||||
|
33
go.mod
33
go.mod
@ -5,26 +5,35 @@ require (
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/go-errors/errors v1.0.1
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/mattn/go-isatty v0.0.11
|
||||
github.com/mattn/go-runewidth v0.0.7
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mattn/go-runewidth v0.0.16
|
||||
github.com/micro-editor/json5 v1.0.1-micro
|
||||
github.com/micro-editor/tcell/v2 v2.0.11
|
||||
github.com/micro-editor/terminal v0.0.0-20250324214352-e587e959c6b5
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/sergi/go-diff v1.1.0
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
|
||||
github.com/yuin/gopher-lua v1.1.1
|
||||
github.com/zyedidia/clipper v0.1.1
|
||||
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3
|
||||
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d
|
||||
github.com/zyedidia/tcell/v2 v2.0.10 // indirect
|
||||
github.com/zyedidia/terminal v0.0.0-20230315200948-4b3bcf6dddef
|
||||
golang.org/x/text v0.3.8
|
||||
golang.org/x/text v0.4.0
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
layeh.com/gopher-luar v1.0.7
|
||||
layeh.com/gopher-luar v1.0.11
|
||||
)
|
||||
|
||||
replace github.com/kballard/go-shellquote => github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655
|
||||
require (
|
||||
github.com/creack/pty v1.1.18 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/zyedidia/poller v1.0.1 // indirect
|
||||
golang.org/x/sys v0.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/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/layeh/gopher-luar v1.0.7 h1:wnfZhYiJM748y1A4qYBfcFeMY9HWbdERny+ZL0f/jWc=
|
||||
github.com/layeh/gopher-luar v1.0.7/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/layeh/gopher-luar v1.0.11 h1:ss6t9OtykOiETBScJylSMPhuYAtOmpH5rSX10/wCcis=
|
||||
github.com/layeh/gopher-luar v1.0.11/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.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/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGGF0=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/xo/terminfo v0.0.0-20200218205459-454e5b68f9e8 h1:woqigIZtZUZxws1zZA99nAvuz2mQrxtWsuZSR9c8I/A=
|
||||
github.com/xo/terminfo v0.0.0-20200218205459-454e5b68f9e8/go.mod h1:6Yhx5ZJl5942QrNRWLwITArVT9okUXc5c3brgWJMoDc=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
|
||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
github.com/zyedidia/clipper v0.1.1 h1:HBgguFNDq/QmSQKBnhy4sMKzILINr139VEgAhftOUTw=
|
||||
github.com/zyedidia/clipper v0.1.1/go.mod h1:7YApPNiiTZTXdKKZG92G50qj6mnWEX975Sdu65J7YpQ=
|
||||
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3 h1:oMHjjTLfGXVuyOQBYj5/td9WC0mw4g1xDBPovIqmHew=
|
||||
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3/go.mod h1:YKbIYP//Eln8eDgAJGI3IDvR3s4Tv9Z9TGIOumiyQ5c=
|
||||
github.com/zyedidia/go-runewidth v0.0.12 h1:aHWj8qL3aH7caRzoPBJXe1pEaZBXHpKtfTuiBo5p74Q=
|
||||
github.com/zyedidia/go-runewidth v0.0.12/go.mod h1:vF8djYdLmG8BJaUZ4CznFYCJ3pFR8m4B4VinTvTTarU=
|
||||
github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655 h1:Z3RhH6hvcSx7eX6Q/pP6YVsgea/1eMDG99vtWwi3nK4=
|
||||
github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655/go.mod h1:1sTqqO+kcYzZp43M5VsJe1tns9IzlSeC9jB6c2+o/5Y=
|
||||
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d h1:zmDMkh22zXOB7gz8jFaI4GpI7llsPgzm38/jG0UgxjE=
|
||||
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d/go.mod h1:NDJSTTYWivnza6zkRapeX2/LwhKPEMQ7bJxqgDVT78I=
|
||||
github.com/zyedidia/poller v1.0.1 h1:Tt9S3AxAjXwWGNiC2TUdRJkQDZSzCBNVQ4xXiQ7440s=
|
||||
github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
|
||||
github.com/zyedidia/tcell/v2 v2.0.9 h1:FxXRkE62N0GPHES7EMLtp2rteYqC9r1kVid8vJN1kOE=
|
||||
github.com/zyedidia/tcell/v2 v2.0.9/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
|
||||
github.com/zyedidia/tcell/v2 v2.0.10-0.20221007181625-f562052bccb8 h1:53ULv4mmLyQDnqbjVxanckP57WSreWHwTmlLJrJEutY=
|
||||
github.com/zyedidia/tcell/v2 v2.0.10-0.20221007181625-f562052bccb8/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
|
||||
github.com/zyedidia/tcell/v2 v2.0.10-0.20230320201625-54f6acdada4a h1:W4TWa++Wk6uRGxZoxr2nPX1TpIEl+Wxv0mTtocG4TYc=
|
||||
github.com/zyedidia/tcell/v2 v2.0.10-0.20230320201625-54f6acdada4a/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
|
||||
github.com/zyedidia/tcell/v2 v2.0.10-0.20230831153116-061c5b2c7260 h1:SCAmAacT5BxZsmOFdFy5zwwi6nj1MjA60gydjKdTgXo=
|
||||
github.com/zyedidia/tcell/v2 v2.0.10-0.20230831153116-061c5b2c7260/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
|
||||
github.com/zyedidia/tcell/v2 v2.0.10 h1:6fbbYAx/DYc9A//4jU1OeBrxtc9qJxYCZXCtGQbtTWU=
|
||||
github.com/zyedidia/tcell/v2 v2.0.10/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
|
||||
github.com/zyedidia/terminal v0.0.0-20230315200948-4b3bcf6dddef h1:LeB4Qs0Tss4r/Qh8pfsTTqagDYHysfKJLYzAH3MVfu0=
|
||||
github.com/zyedidia/terminal v0.0.0-20230315200948-4b3bcf6dddef/go.mod h1:zeb8MJdcCObFKVvur3n2B4BANIPuo2Q8r4iiNs9Enx0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
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/clipboard"
|
||||
"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/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
// ScrollUp is not an action
|
||||
@ -46,6 +46,14 @@ func (h *BufPane) ScrollAdjust() {
|
||||
h.SetView(v)
|
||||
}
|
||||
|
||||
// ScrollReachedEnd returns true if the view is at the end of the buffer,
|
||||
// i.e. the last line of the buffer is in the view.
|
||||
func (h *BufPane) ScrollReachedEnd() bool {
|
||||
v := h.GetView()
|
||||
end := h.SLocFromLoc(h.Buf.End())
|
||||
return h.Diff(v.StartLine, end) < h.BufView().Height
|
||||
}
|
||||
|
||||
// MousePress is the event that should happen when a normal click happens
|
||||
// This is almost always bound to left click
|
||||
func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
|
||||
@ -65,12 +73,12 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
|
||||
h.Cursor.Loc = mouseLoc
|
||||
}
|
||||
if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
|
||||
if h.doubleClick {
|
||||
if h.DoubleClick {
|
||||
// Triple click
|
||||
h.lastClickTime = time.Now()
|
||||
|
||||
h.tripleClick = true
|
||||
h.doubleClick = false
|
||||
h.TripleClick = true
|
||||
h.DoubleClick = false
|
||||
|
||||
h.Cursor.SelectLine()
|
||||
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
||||
@ -78,15 +86,15 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
|
||||
// Double click
|
||||
h.lastClickTime = time.Now()
|
||||
|
||||
h.doubleClick = true
|
||||
h.tripleClick = false
|
||||
h.DoubleClick = true
|
||||
h.TripleClick = false
|
||||
|
||||
h.Cursor.SelectWord()
|
||||
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
||||
}
|
||||
} else {
|
||||
h.doubleClick = false
|
||||
h.tripleClick = false
|
||||
h.DoubleClick = false
|
||||
h.TripleClick = false
|
||||
h.lastClickTime = time.Now()
|
||||
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
||||
@ -108,9 +116,9 @@ func (h *BufPane) MouseDrag(e *tcell.EventMouse) bool {
|
||||
}
|
||||
h.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my})
|
||||
|
||||
if h.tripleClick {
|
||||
if h.TripleClick {
|
||||
h.Cursor.AddLineToSelection()
|
||||
} else if h.doubleClick {
|
||||
} else if h.DoubleClick {
|
||||
h.Cursor.AddWordToSelection()
|
||||
} else {
|
||||
h.Cursor.SelectTo(h.Cursor.Loc)
|
||||
@ -127,7 +135,7 @@ func (h *BufPane) MouseRelease(e *tcell.EventMouse) bool {
|
||||
// that doesn't support mouse motion events. But when the mouse click is
|
||||
// within the scroll margin, that would cause a scroll and selection
|
||||
// even for a simple mouse click, which is not good.
|
||||
// if !h.doubleClick && !h.tripleClick {
|
||||
// if !h.DoubleClick && !h.TripleClick {
|
||||
// mx, my := e.Position()
|
||||
// h.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my})
|
||||
// h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
||||
@ -145,7 +153,7 @@ func (h *BufPane) ScrollUpAction() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ScrollDownAction scrolls the view up
|
||||
// ScrollDownAction scrolls the view down
|
||||
func (h *BufPane) ScrollDownAction() bool {
|
||||
h.ScrollDown(util.IntOpt(h.Buf.Settings["scrollspeed"]))
|
||||
return true
|
||||
@ -160,6 +168,52 @@ func (h *BufPane) Center() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// CursorToViewTop moves the cursor to the top of the view,
|
||||
// offset by scrollmargin unless at the beginning or end of the file
|
||||
func (h *BufPane) CursorToViewTop() bool {
|
||||
v := h.GetView()
|
||||
h.Buf.ClearCursors()
|
||||
scrollmargin := int(h.Buf.Settings["scrollmargin"].(float64))
|
||||
bStart := display.SLoc{0, 0}
|
||||
if v.StartLine == bStart {
|
||||
scrollmargin = 0
|
||||
}
|
||||
h.Cursor.GotoLoc(h.LocFromVLoc(display.VLoc{
|
||||
SLoc: h.Scroll(v.StartLine, scrollmargin),
|
||||
VisualX: 0,
|
||||
}))
|
||||
return true
|
||||
}
|
||||
|
||||
// CursorToViewCenter moves the cursor to the center of the view
|
||||
func (h *BufPane) CursorToViewCenter() bool {
|
||||
v := h.GetView()
|
||||
h.Buf.ClearCursors()
|
||||
h.Cursor.GotoLoc(h.LocFromVLoc(display.VLoc{
|
||||
SLoc: h.Scroll(v.StartLine, h.BufView().Height/2),
|
||||
VisualX: 0,
|
||||
}))
|
||||
return true
|
||||
}
|
||||
|
||||
// CursorToViewBottom moves the cursor to the bottom of the view,
|
||||
// offset by scrollmargin unless at the beginning or end of the file
|
||||
func (h *BufPane) CursorToViewBottom() bool {
|
||||
v := h.GetView()
|
||||
h.Buf.ClearCursors()
|
||||
scrollmargin := int(h.Buf.Settings["scrollmargin"].(float64))
|
||||
bEnd := h.SLocFromLoc(h.Buf.End())
|
||||
lastLine := h.Scroll(v.StartLine, h.BufView().Height-1)
|
||||
if lastLine == bEnd {
|
||||
scrollmargin = 0
|
||||
}
|
||||
h.Cursor.GotoLoc(h.LocFromVLoc(display.VLoc{
|
||||
SLoc: h.Scroll(lastLine, -scrollmargin),
|
||||
VisualX: 0,
|
||||
}))
|
||||
return true
|
||||
}
|
||||
|
||||
// MoveCursorUp is not an action
|
||||
func (h *BufPane) MoveCursorUp(n int) {
|
||||
if !h.Buf.Settings["softwrap"].(bool) {
|
||||
@ -170,10 +224,10 @@ func (h *BufPane) MoveCursorUp(n int) {
|
||||
if sloc == vloc.SLoc {
|
||||
// we are at the beginning of buffer
|
||||
h.Cursor.Loc = h.Buf.Start()
|
||||
h.Cursor.LastVisualX = 0
|
||||
h.Cursor.StoreVisualX()
|
||||
} else {
|
||||
vloc.SLoc = sloc
|
||||
vloc.VisualX = h.Cursor.LastVisualX
|
||||
vloc.VisualX = h.Cursor.LastWrappedVisualX
|
||||
h.Cursor.Loc = h.LocFromVLoc(vloc)
|
||||
}
|
||||
}
|
||||
@ -189,11 +243,10 @@ func (h *BufPane) MoveCursorDown(n int) {
|
||||
if sloc == vloc.SLoc {
|
||||
// we are at the end of buffer
|
||||
h.Cursor.Loc = h.Buf.End()
|
||||
vloc = h.VLocFromLoc(h.Cursor.Loc)
|
||||
h.Cursor.LastVisualX = vloc.VisualX
|
||||
h.Cursor.StoreVisualX()
|
||||
} else {
|
||||
vloc.SLoc = sloc
|
||||
vloc.VisualX = h.Cursor.LastVisualX
|
||||
vloc.VisualX = h.Cursor.LastWrappedVisualX
|
||||
h.Cursor.Loc = h.LocFromVLoc(vloc)
|
||||
}
|
||||
}
|
||||
@ -209,8 +262,13 @@ func (h *BufPane) CursorUp() bool {
|
||||
|
||||
// CursorDown moves the cursor down
|
||||
func (h *BufPane) CursorDown() bool {
|
||||
selectionEndNewline := h.Cursor.HasSelection() && h.Cursor.CurSelection[1].X == 0
|
||||
h.Cursor.Deselect(false)
|
||||
h.MoveCursorDown(1)
|
||||
if selectionEndNewline {
|
||||
h.Cursor.Start()
|
||||
} else {
|
||||
h.MoveCursorDown(1)
|
||||
}
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
@ -244,7 +302,6 @@ func (h *BufPane) CursorLeft() bool {
|
||||
func (h *BufPane) CursorRight() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
h.Cursor.Deselect(false)
|
||||
h.Cursor.Right()
|
||||
} else {
|
||||
tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
|
||||
tabmovement := h.Buf.Settings["tabmovement"].(bool)
|
||||
@ -657,7 +714,7 @@ func (h *BufPane) InsertNewline() bool {
|
||||
h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: util.CharacterCount(line), Y: h.Cursor.Y - 1})
|
||||
}
|
||||
}
|
||||
h.Cursor.LastVisualX = h.Cursor.GetVisualX()
|
||||
h.Cursor.StoreVisualX()
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
@ -687,7 +744,7 @@ func (h *BufPane) Backspace() bool {
|
||||
h.Buf.Remove(loc.Move(-1, h.Buf), loc)
|
||||
}
|
||||
}
|
||||
h.Cursor.LastVisualX = h.Cursor.GetVisualX()
|
||||
h.Cursor.StoreVisualX()
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
@ -854,6 +911,11 @@ func (h *BufPane) Autocomplete() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if b.HasSuggestions {
|
||||
b.CycleAutocomplete(true)
|
||||
return true
|
||||
}
|
||||
|
||||
if h.Cursor.X == 0 {
|
||||
return false
|
||||
}
|
||||
@ -864,10 +926,6 @@ func (h *BufPane) Autocomplete() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if b.HasSuggestions {
|
||||
b.CycleAutocomplete(true)
|
||||
return true
|
||||
}
|
||||
return b.Autocomplete(buffer.BufferComplete)
|
||||
}
|
||||
|
||||
@ -889,7 +947,7 @@ func (h *BufPane) InsertTab() bool {
|
||||
b := h.Buf
|
||||
indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
|
||||
tabBytes := len(indent)
|
||||
bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
|
||||
bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX(false) % tabBytes)
|
||||
b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
|
||||
h.Relocate()
|
||||
return true
|
||||
@ -946,6 +1004,9 @@ func (h *BufPane) SaveAsCB(action string, callback func()) bool {
|
||||
h.completeAction(action)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
InfoBar.YNPrompt(
|
||||
@ -982,8 +1043,6 @@ func (h *BufPane) saveBufToFile(filename string, action string, callback func())
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
} else {
|
||||
h.Buf.Path = filename
|
||||
h.Buf.SetName(filename)
|
||||
InfoBar.Message("Saved " + filename)
|
||||
if callback != nil {
|
||||
callback()
|
||||
@ -1008,8 +1067,6 @@ func (h *BufPane) saveBufToFile(filename string, action string, callback func())
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
} else {
|
||||
h.Buf.Path = filename
|
||||
h.Buf.SetName(filename)
|
||||
InfoBar.Message("Saved " + filename)
|
||||
if callback != nil {
|
||||
callback()
|
||||
@ -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)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
} else if found && searchLoc == match[0] && match[0] == match[1] {
|
||||
// skip empty match at present cursor location
|
||||
if searchLoc == h.Buf.End() {
|
||||
searchLoc = h.Buf.Start()
|
||||
} else {
|
||||
searchLoc = searchLoc.Move(1, h.Buf)
|
||||
}
|
||||
match, found, _ = h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.Buf.LastSearchRegex)
|
||||
}
|
||||
if found {
|
||||
h.Cursor.SetSelectionStart(match[0])
|
||||
@ -1183,6 +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)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
} else if found && searchLoc == match[0] && match[0] == match[1] {
|
||||
// skip empty match at present cursor location
|
||||
if searchLoc == h.Buf.Start() {
|
||||
searchLoc = h.Buf.End()
|
||||
} else {
|
||||
searchLoc = searchLoc.Move(-1, h.Buf)
|
||||
}
|
||||
match, found, _ = h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.Buf.LastSearchRegex)
|
||||
}
|
||||
if found {
|
||||
h.Cursor.SetSelectionStart(match[0])
|
||||
@ -1238,101 +1311,189 @@ func (h *BufPane) Redo() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *BufPane) selectLines() int {
|
||||
if h.Cursor.HasSelection() {
|
||||
start := h.Cursor.CurSelection[0]
|
||||
end := h.Cursor.CurSelection[1]
|
||||
if start.GreaterThan(end) {
|
||||
start, end = end, start
|
||||
}
|
||||
if end.X == 0 {
|
||||
end = end.Move(-1, h.Buf)
|
||||
}
|
||||
|
||||
h.Cursor.Deselect(true)
|
||||
h.Cursor.SetSelectionStart(buffer.Loc{0, start.Y})
|
||||
h.Cursor.SetSelectionEnd(buffer.Loc{0, end.Y + 1})
|
||||
} else {
|
||||
h.Cursor.SelectLine()
|
||||
}
|
||||
|
||||
nlines := h.Cursor.CurSelection[1].Y - h.Cursor.CurSelection[0].Y
|
||||
if nlines == 0 && h.Cursor.HasSelection() {
|
||||
// selected last line and it is not empty
|
||||
nlines++
|
||||
}
|
||||
return nlines
|
||||
}
|
||||
|
||||
// Copy the selection to the system clipboard
|
||||
func (h *BufPane) Copy() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||
h.freshClip = true
|
||||
InfoBar.Message("Copied selection")
|
||||
}
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// CopyLine copies the current line to the clipboard
|
||||
func (h *BufPane) CopyLine() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
return false
|
||||
}
|
||||
origLoc := h.Cursor.Loc
|
||||
h.Cursor.SelectLine()
|
||||
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||
h.freshClip = true
|
||||
InfoBar.Message("Copied line")
|
||||
|
||||
h.Cursor.Deselect(true)
|
||||
h.Cursor.Loc = origLoc
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// CutLine cuts the current line to the clipboard
|
||||
func (h *BufPane) CutLine() bool {
|
||||
h.Cursor.SelectLine()
|
||||
if !h.Cursor.HasSelection() {
|
||||
return false
|
||||
}
|
||||
if h.freshClip {
|
||||
if h.Cursor.HasSelection() {
|
||||
if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
|
||||
InfoBar.Error(err)
|
||||
} else {
|
||||
clipboard.WriteMulti(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
|
||||
}
|
||||
}
|
||||
} else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || !h.freshClip {
|
||||
h.Copy()
|
||||
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||
h.freshClip = false
|
||||
InfoBar.Message("Copied selection")
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// CopyLine copies the current line to the clipboard. If there is a selection,
|
||||
// CopyLine copies all the lines that are (fully or partially) in the selection.
|
||||
func (h *BufPane) CopyLine() bool {
|
||||
origLoc := h.Cursor.Loc
|
||||
origLastVisualX := h.Cursor.LastVisualX
|
||||
origLastWrappedVisualX := h.Cursor.LastWrappedVisualX
|
||||
origSelection := h.Cursor.CurSelection
|
||||
|
||||
nlines := h.selectLines()
|
||||
if nlines == 0 {
|
||||
return false
|
||||
}
|
||||
h.freshClip = true
|
||||
h.lastCutTime = time.Now()
|
||||
h.Cursor.DeleteSelection()
|
||||
h.Cursor.ResetSelection()
|
||||
InfoBar.Message("Cut line")
|
||||
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||
h.freshClip = false
|
||||
if nlines > 1 {
|
||||
InfoBar.Message(fmt.Sprintf("Copied %d lines", nlines))
|
||||
} else {
|
||||
InfoBar.Message("Copied line")
|
||||
}
|
||||
|
||||
h.Cursor.Loc = origLoc
|
||||
h.Cursor.LastVisualX = origLastVisualX
|
||||
h.Cursor.LastWrappedVisualX = origLastWrappedVisualX
|
||||
h.Cursor.CurSelection = origSelection
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// Cut the selection to the system clipboard
|
||||
func (h *BufPane) Cut() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||
h.Cursor.DeleteSelection()
|
||||
h.Cursor.ResetSelection()
|
||||
h.freshClip = true
|
||||
InfoBar.Message("Cut selection")
|
||||
|
||||
h.Relocate()
|
||||
return true
|
||||
if !h.Cursor.HasSelection() {
|
||||
return false
|
||||
}
|
||||
return h.CutLine()
|
||||
}
|
||||
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||
h.Cursor.DeleteSelection()
|
||||
h.Cursor.ResetSelection()
|
||||
h.freshClip = false
|
||||
InfoBar.Message("Cut selection")
|
||||
|
||||
// DuplicateLine duplicates the current line or selection
|
||||
func (h *BufPane) DuplicateLine() bool {
|
||||
var infoMessage = "Duplicated line"
|
||||
if h.Cursor.HasSelection() {
|
||||
infoMessage = "Duplicated selection"
|
||||
h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
|
||||
} else {
|
||||
h.Cursor.End()
|
||||
h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
|
||||
// h.Cursor.Right()
|
||||
}
|
||||
|
||||
InfoBar.Message(infoMessage)
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// DeleteLine deletes the current line
|
||||
func (h *BufPane) DeleteLine() bool {
|
||||
h.Cursor.SelectLine()
|
||||
// CutLine cuts the current line to the clipboard. If there is a selection,
|
||||
// CutLine cuts all the lines that are (fully or partially) in the selection.
|
||||
func (h *BufPane) CutLine() bool {
|
||||
nlines := h.selectLines()
|
||||
if nlines == 0 {
|
||||
return false
|
||||
}
|
||||
totalLines := nlines
|
||||
if h.freshClip {
|
||||
if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
|
||||
InfoBar.Error(err)
|
||||
return false
|
||||
} else {
|
||||
clipboard.WriteMulti(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
|
||||
totalLines = strings.Count(clip, "\n") + nlines
|
||||
}
|
||||
} else {
|
||||
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||
}
|
||||
h.freshClip = true
|
||||
h.Cursor.DeleteSelection()
|
||||
h.Cursor.ResetSelection()
|
||||
h.Cursor.StoreVisualX()
|
||||
if totalLines > 1 {
|
||||
InfoBar.Message(fmt.Sprintf("Cut %d lines", totalLines))
|
||||
} else {
|
||||
InfoBar.Message("Cut line")
|
||||
}
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// Duplicate the selection
|
||||
func (h *BufPane) Duplicate() bool {
|
||||
if !h.Cursor.HasSelection() {
|
||||
return false
|
||||
}
|
||||
h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
|
||||
InfoBar.Message("Duplicated selection")
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// DuplicateLine duplicates the current line. If there is a selection, DuplicateLine
|
||||
// duplicates all the lines that are (fully or partially) in the selection.
|
||||
func (h *BufPane) DuplicateLine() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
origLoc := h.Cursor.Loc
|
||||
origLastVisualX := h.Cursor.LastVisualX
|
||||
origLastWrappedVisualX := h.Cursor.LastWrappedVisualX
|
||||
origSelection := h.Cursor.CurSelection
|
||||
|
||||
start := h.Cursor.CurSelection[0]
|
||||
end := h.Cursor.CurSelection[1]
|
||||
if start.GreaterThan(end) {
|
||||
start, end = end, start
|
||||
}
|
||||
if end.X == 0 {
|
||||
end = end.Move(-1, h.Buf)
|
||||
}
|
||||
|
||||
h.Cursor.Deselect(true)
|
||||
h.Cursor.Loc = end
|
||||
h.Cursor.End()
|
||||
for y := start.Y; y <= end.Y; y++ {
|
||||
h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(y)))
|
||||
}
|
||||
|
||||
h.Cursor.Loc = origLoc
|
||||
h.Cursor.LastVisualX = origLastVisualX
|
||||
h.Cursor.LastWrappedVisualX = origLastWrappedVisualX
|
||||
h.Cursor.CurSelection = origSelection
|
||||
|
||||
if start.Y < end.Y {
|
||||
InfoBar.Message(fmt.Sprintf("Duplicated %d lines", end.Y-start.Y+1))
|
||||
} else {
|
||||
InfoBar.Message("Duplicated line")
|
||||
}
|
||||
} else {
|
||||
h.Cursor.End()
|
||||
h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
|
||||
InfoBar.Message("Duplicated line")
|
||||
}
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// DeleteLine deletes the current line. If there is a selection, DeleteLine
|
||||
// deletes all the lines that are (fully or partially) in the selection.
|
||||
func (h *BufPane) DeleteLine() bool {
|
||||
nlines := h.selectLines()
|
||||
if nlines == 0 {
|
||||
return false
|
||||
}
|
||||
h.Cursor.DeleteSelection()
|
||||
h.Cursor.ResetSelection()
|
||||
InfoBar.Message("Deleted line")
|
||||
h.Cursor.StoreVisualX()
|
||||
if nlines > 1 {
|
||||
InfoBar.Message(fmt.Sprintf("Deleted %d lines", nlines))
|
||||
} else {
|
||||
InfoBar.Message("Deleted line")
|
||||
}
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
@ -1536,63 +1697,84 @@ func (h *BufPane) End() bool {
|
||||
|
||||
// PageUp scrolls the view up a page
|
||||
func (h *BufPane) PageUp() bool {
|
||||
h.ScrollUp(h.BufView().Height)
|
||||
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
||||
h.ScrollUp(h.BufView().Height - pageOverlap)
|
||||
return true
|
||||
}
|
||||
|
||||
// PageDown scrolls the view down a page
|
||||
func (h *BufPane) PageDown() bool {
|
||||
h.ScrollDown(h.BufView().Height)
|
||||
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
||||
h.ScrollDown(h.BufView().Height - pageOverlap)
|
||||
h.ScrollAdjust()
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectPageUp selects up one page
|
||||
func (h *BufPane) SelectPageUp() bool {
|
||||
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
||||
scrollAmount := h.BufView().Height - pageOverlap
|
||||
if !h.Cursor.HasSelection() {
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
||||
}
|
||||
h.MoveCursorUp(h.BufView().Height)
|
||||
h.MoveCursorUp(scrollAmount)
|
||||
h.Cursor.SelectTo(h.Cursor.Loc)
|
||||
if h.Cursor.Num == 0 {
|
||||
h.ScrollUp(scrollAmount)
|
||||
}
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectPageDown selects down one page
|
||||
func (h *BufPane) SelectPageDown() bool {
|
||||
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
||||
scrollAmount := h.BufView().Height - pageOverlap
|
||||
if !h.Cursor.HasSelection() {
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
||||
}
|
||||
h.MoveCursorDown(h.BufView().Height)
|
||||
h.MoveCursorDown(scrollAmount)
|
||||
h.Cursor.SelectTo(h.Cursor.Loc)
|
||||
if h.Cursor.Num == 0 && !h.ScrollReachedEnd() {
|
||||
h.ScrollDown(scrollAmount)
|
||||
h.ScrollAdjust()
|
||||
}
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// CursorPageUp places the cursor a page up
|
||||
// CursorPageUp places the cursor a page up,
|
||||
// moving the view to keep cursor at the same relative position in the view
|
||||
func (h *BufPane) CursorPageUp() bool {
|
||||
h.Cursor.Deselect(true)
|
||||
|
||||
if h.Cursor.HasSelection() {
|
||||
h.Cursor.Loc = h.Cursor.CurSelection[0]
|
||||
h.Cursor.ResetSelection()
|
||||
h.Cursor.StoreVisualX()
|
||||
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
||||
scrollAmount := h.BufView().Height - pageOverlap
|
||||
h.MoveCursorUp(scrollAmount)
|
||||
if h.Cursor.Num == 0 {
|
||||
h.ScrollUp(scrollAmount)
|
||||
}
|
||||
h.MoveCursorUp(h.BufView().Height)
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// CursorPageDown places the cursor a page up
|
||||
// CursorPageDown places the cursor a page down,
|
||||
// moving the view to keep cursor at the same relative position in the view
|
||||
func (h *BufPane) CursorPageDown() bool {
|
||||
selectionEndNewline := h.Cursor.HasSelection() && h.Cursor.CurSelection[1].X == 0
|
||||
h.Cursor.Deselect(false)
|
||||
|
||||
if h.Cursor.HasSelection() {
|
||||
h.Cursor.Loc = h.Cursor.CurSelection[1]
|
||||
h.Cursor.ResetSelection()
|
||||
h.Cursor.StoreVisualX()
|
||||
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
||||
scrollAmount := h.BufView().Height - pageOverlap
|
||||
if selectionEndNewline {
|
||||
scrollAmount--
|
||||
}
|
||||
h.MoveCursorDown(scrollAmount)
|
||||
if h.Cursor.Num == 0 && !h.ScrollReachedEnd() {
|
||||
h.ScrollDown(scrollAmount)
|
||||
h.ScrollAdjust()
|
||||
}
|
||||
if selectionEndNewline {
|
||||
h.Cursor.Start()
|
||||
}
|
||||
h.MoveCursorDown(h.BufView().Height)
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
@ -1612,12 +1794,12 @@ func (h *BufPane) HalfPageDown() bool {
|
||||
|
||||
// ToggleDiffGutter turns the diff gutter off and on
|
||||
func (h *BufPane) ToggleDiffGutter() bool {
|
||||
if !h.Buf.Settings["diffgutter"].(bool) {
|
||||
h.Buf.Settings["diffgutter"] = true
|
||||
diffgutter := !h.Buf.Settings["diffgutter"].(bool)
|
||||
h.Buf.SetOptionNative("diffgutter", diffgutter)
|
||||
if diffgutter {
|
||||
h.Buf.UpdateDiff()
|
||||
InfoBar.Message("Enabled diff gutter")
|
||||
} else {
|
||||
h.Buf.Settings["diffgutter"] = false
|
||||
InfoBar.Message("Disabled diff gutter")
|
||||
}
|
||||
return true
|
||||
@ -1625,11 +1807,11 @@ func (h *BufPane) ToggleDiffGutter() bool {
|
||||
|
||||
// ToggleRuler turns line numbers off and on
|
||||
func (h *BufPane) ToggleRuler() bool {
|
||||
if !h.Buf.Settings["ruler"].(bool) {
|
||||
h.Buf.Settings["ruler"] = true
|
||||
ruler := !h.Buf.Settings["ruler"].(bool)
|
||||
h.Buf.SetOptionNative("ruler", ruler)
|
||||
if ruler {
|
||||
InfoBar.Message("Enabled ruler")
|
||||
} else {
|
||||
h.Buf.Settings["ruler"] = false
|
||||
InfoBar.Message("Disabled ruler")
|
||||
}
|
||||
return true
|
||||
@ -1645,7 +1827,8 @@ func (h *BufPane) ToggleHelp() bool {
|
||||
if h.Buf.Type == buffer.BTHelp {
|
||||
h.Quit()
|
||||
} else {
|
||||
h.openHelp("help")
|
||||
hsplit := config.GlobalSettings["helpsplit"] == "hsplit"
|
||||
h.openHelp("help", hsplit, false)
|
||||
}
|
||||
return true
|
||||
}
|
||||
@ -1681,7 +1864,7 @@ func (h *BufPane) CommandMode() bool {
|
||||
|
||||
// ToggleOverwriteMode lets the user toggle the text overwrite mode
|
||||
func (h *BufPane) ToggleOverwriteMode() bool {
|
||||
h.isOverwriteMode = !h.isOverwriteMode
|
||||
h.Buf.OverwriteMode = !h.Buf.OverwriteMode
|
||||
return true
|
||||
}
|
||||
|
||||
@ -1708,11 +1891,11 @@ func (h *BufPane) ClearInfo() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ForceQuit closes the current tab or view even if there are unsaved changes
|
||||
// ForceQuit closes the tab or view even if there are unsaved changes
|
||||
// (no prompt)
|
||||
func (h *BufPane) ForceQuit() bool {
|
||||
h.Buf.Close()
|
||||
if len(MainTab().Panes) > 1 {
|
||||
if len(h.tab.Panes) > 1 {
|
||||
h.Unsplit()
|
||||
} else if len(Tabs.List) > 1 {
|
||||
Tabs.RemoveTab(h.splitID)
|
||||
@ -1724,23 +1907,29 @@ func (h *BufPane) ForceQuit() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// closePrompt displays a prompt to save the buffer before closing it to proceed
|
||||
// with a different action or command
|
||||
func (h *BufPane) closePrompt(action string, callback func()) {
|
||||
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
|
||||
if !canceled && !yes {
|
||||
callback()
|
||||
} else if !canceled && yes {
|
||||
h.SaveCB(action, callback)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Quit this will close the current tab or view that is open
|
||||
func (h *BufPane) Quit() bool {
|
||||
if h.Buf.Modified() {
|
||||
if config.GlobalSettings["autosave"].(float64) > 0 {
|
||||
if h.Buf.Modified() && !h.Buf.Shared() {
|
||||
if config.GlobalSettings["autosave"].(float64) > 0 && h.Buf.Path != "" {
|
||||
// autosave on means we automatically save when quitting
|
||||
h.SaveCB("Quit", func() {
|
||||
h.ForceQuit()
|
||||
})
|
||||
} else {
|
||||
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
|
||||
if !canceled && !yes {
|
||||
h.ForceQuit()
|
||||
} else if !canceled && yes {
|
||||
h.SaveCB("Quit", func() {
|
||||
h.ForceQuit()
|
||||
})
|
||||
}
|
||||
h.closePrompt("Quit", func() {
|
||||
h.ForceQuit()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@ -1793,27 +1982,38 @@ func (h *BufPane) AddTab() bool {
|
||||
|
||||
// PreviousTab switches to the previous tab in the tab list
|
||||
func (h *BufPane) PreviousTab() bool {
|
||||
tabsLen := len(Tabs.List)
|
||||
if tabsLen == 1 {
|
||||
if Tabs.Active() == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
a := Tabs.Active() + tabsLen
|
||||
Tabs.SetActive((a - 1) % tabsLen)
|
||||
|
||||
Tabs.SetActive(Tabs.Active() - 1)
|
||||
return true
|
||||
}
|
||||
|
||||
// NextTab switches to the next tab in the tab list
|
||||
func (h *BufPane) NextTab() bool {
|
||||
tabsLen := len(Tabs.List)
|
||||
if tabsLen == 1 {
|
||||
if Tabs.Active() == len(Tabs.List)-1 {
|
||||
return false
|
||||
}
|
||||
Tabs.SetActive(Tabs.Active() + 1)
|
||||
return true
|
||||
}
|
||||
|
||||
a := Tabs.Active()
|
||||
Tabs.SetActive((a + 1) % tabsLen)
|
||||
// FirstTab switches to the first tab in the tab list
|
||||
func (h *BufPane) FirstTab() bool {
|
||||
if Tabs.Active() == 0 {
|
||||
return false
|
||||
}
|
||||
Tabs.SetActive(0)
|
||||
return true
|
||||
}
|
||||
|
||||
// LastTab switches to the last tab in the tab list
|
||||
func (h *BufPane) LastTab() bool {
|
||||
lastTabIndex := len(Tabs.List) - 1
|
||||
if Tabs.Active() == lastTabIndex {
|
||||
return false
|
||||
}
|
||||
Tabs.SetActive(lastTabIndex)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -1848,36 +2048,38 @@ func (h *BufPane) Unsplit() bool {
|
||||
|
||||
// NextSplit changes the view to the next split
|
||||
func (h *BufPane) NextSplit() bool {
|
||||
if len(h.tab.Panes) == 1 {
|
||||
if h.tab.active == len(h.tab.Panes)-1 {
|
||||
return false
|
||||
}
|
||||
|
||||
a := h.tab.active
|
||||
if a < len(h.tab.Panes)-1 {
|
||||
a++
|
||||
} else {
|
||||
a = 0
|
||||
}
|
||||
|
||||
h.tab.SetActive(a)
|
||||
|
||||
h.tab.SetActive(h.tab.active + 1)
|
||||
return true
|
||||
}
|
||||
|
||||
// PreviousSplit changes the view to the previous split
|
||||
func (h *BufPane) PreviousSplit() bool {
|
||||
if len(h.tab.Panes) == 1 {
|
||||
if h.tab.active == 0 {
|
||||
return false
|
||||
}
|
||||
h.tab.SetActive(h.tab.active - 1)
|
||||
return true
|
||||
}
|
||||
|
||||
a := h.tab.active
|
||||
if a > 0 {
|
||||
a--
|
||||
} else {
|
||||
a = len(h.tab.Panes) - 1
|
||||
// FirstSplit changes the view to the first split
|
||||
func (h *BufPane) FirstSplit() bool {
|
||||
if h.tab.active == 0 {
|
||||
return false
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@ -1955,38 +2157,31 @@ func (h *BufPane) SpawnMultiCursor() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SpawnCursorAtLoc spawns a new cursor at a location and merges the cursors
|
||||
func (h *BufPane) SpawnCursorAtLoc(loc buffer.Loc) *buffer.Cursor {
|
||||
c := buffer.NewCursor(h.Buf, loc)
|
||||
h.Buf.AddCursor(c)
|
||||
h.Buf.MergeCursors()
|
||||
return c
|
||||
}
|
||||
|
||||
// SpawnMultiCursorUpN is not an action
|
||||
func (h *BufPane) SpawnMultiCursorUpN(n int) bool {
|
||||
lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
|
||||
var c *buffer.Cursor
|
||||
if !h.Buf.Settings["softwrap"].(bool) {
|
||||
if n > 0 && lastC.Y == 0 {
|
||||
return false
|
||||
}
|
||||
if n < 0 && lastC.Y+1 == h.Buf.LinesNum() {
|
||||
return false
|
||||
}
|
||||
|
||||
h.Buf.DeselectCursors()
|
||||
|
||||
c = buffer.NewCursor(h.Buf, buffer.Loc{lastC.X, lastC.Y - n})
|
||||
c.LastVisualX = lastC.LastVisualX
|
||||
c.X = c.GetCharPosInLine(h.Buf.LineBytes(c.Y), c.LastVisualX)
|
||||
c.Relocate()
|
||||
} else {
|
||||
vloc := h.VLocFromLoc(lastC.Loc)
|
||||
sloc := h.Scroll(vloc.SLoc, -n)
|
||||
if sloc == vloc.SLoc {
|
||||
return false
|
||||
}
|
||||
|
||||
h.Buf.DeselectCursors()
|
||||
|
||||
vloc.SLoc = sloc
|
||||
vloc.VisualX = lastC.LastVisualX
|
||||
c = buffer.NewCursor(h.Buf, h.LocFromVLoc(vloc))
|
||||
c.LastVisualX = lastC.LastVisualX
|
||||
if n > 0 && lastC.Y == 0 {
|
||||
return false
|
||||
}
|
||||
if n < 0 && lastC.Y+1 == h.Buf.LinesNum() {
|
||||
return false
|
||||
}
|
||||
|
||||
h.Buf.DeselectCursors()
|
||||
|
||||
c := buffer.NewCursor(h.Buf, buffer.Loc{lastC.X, lastC.Y - n})
|
||||
c.LastVisualX = lastC.LastVisualX
|
||||
c.LastWrappedVisualX = lastC.LastWrappedVisualX
|
||||
c.X = c.GetCharPosInLine(h.Buf.LineBytes(c.Y), c.LastVisualX)
|
||||
c.Relocate()
|
||||
|
||||
h.Buf.AddCursor(c)
|
||||
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
|
||||
@ -2068,14 +2263,16 @@ func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SkipMultiCursor moves the current multiple cursor to the next available position
|
||||
func (h *BufPane) SkipMultiCursor() bool {
|
||||
func (h *BufPane) skipMultiCursor(forward bool) bool {
|
||||
lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
|
||||
if !lastC.HasSelection() {
|
||||
return false
|
||||
}
|
||||
sel := lastC.GetSelection()
|
||||
searchStart := lastC.CurSelection[1]
|
||||
if !forward {
|
||||
searchStart = lastC.CurSelection[0]
|
||||
}
|
||||
|
||||
search := string(sel)
|
||||
search = regexp.QuoteMeta(search)
|
||||
@ -2083,7 +2280,7 @@ func (h *BufPane) SkipMultiCursor() bool {
|
||||
search = "\\b" + search + "\\b"
|
||||
}
|
||||
|
||||
match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
|
||||
match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, forward, true)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
@ -2103,6 +2300,16 @@ func (h *BufPane) SkipMultiCursor() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SkipMultiCursor moves the current multiple cursor to the next available position
|
||||
func (h *BufPane) SkipMultiCursor() bool {
|
||||
return h.skipMultiCursor(true)
|
||||
}
|
||||
|
||||
// SkipMultiCursorBack moves the current multiple cursor to the previous available position
|
||||
func (h *BufPane) SkipMultiCursorBack() bool {
|
||||
return h.skipMultiCursor(false)
|
||||
}
|
||||
|
||||
// RemoveMultiCursor removes the latest multiple cursor
|
||||
func (h *BufPane) RemoveMultiCursor() bool {
|
||||
if h.Buf.NumCursors() > 1 {
|
||||
|
@ -1,4 +1,4 @@
|
||||
// +build plan9 nacl windows
|
||||
//go:build plan9 || nacl || windows
|
||||
|
||||
package action
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// +build linux darwin dragonfly solaris openbsd netbsd freebsd
|
||||
//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd
|
||||
|
||||
package action
|
||||
|
||||
|
@ -4,17 +4,18 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/zyedidia/json5"
|
||||
"github.com/micro-editor/json5"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"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){
|
||||
@ -23,9 +24,13 @@ var Binder = map[string]func(e Event, action string){
|
||||
"terminal": TermMapEvent,
|
||||
}
|
||||
|
||||
func writeFile(name string, txt []byte) error {
|
||||
return util.SafeWrite(name, txt, false)
|
||||
}
|
||||
|
||||
func createBindingsIfNotExist(fname string) {
|
||||
if _, e := os.Stat(fname); os.IsNotExist(e) {
|
||||
ioutil.WriteFile(fname, []byte("{}"), 0644)
|
||||
if _, e := os.Stat(fname); errors.Is(e, fs.ErrNotExist) {
|
||||
writeFile(fname, []byte("{}"))
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,7 +42,7 @@ func InitBindings() {
|
||||
createBindingsIfNotExist(filename)
|
||||
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
input, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error reading bindings.json file: " + err.Error())
|
||||
return
|
||||
@ -89,7 +94,7 @@ func BindKey(k, v string, bind func(e Event, a string)) {
|
||||
}
|
||||
|
||||
if strings.HasPrefix(k, "\x1b") {
|
||||
screen.Screen.RegisterRawSeq(k)
|
||||
screen.RegisterRawSeq(k)
|
||||
}
|
||||
|
||||
bind(event, v)
|
||||
@ -265,7 +270,7 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
|
||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||
createBindingsIfNotExist(filename)
|
||||
if _, e = os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
input, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return false, errors.New("Error reading bindings.json file: " + err.Error())
|
||||
}
|
||||
@ -304,7 +309,8 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
|
||||
BindKey(k, v, Binder["buffer"])
|
||||
|
||||
txt, _ := json.MarshalIndent(parsed, "", " ")
|
||||
return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
||||
txt = append(txt, '\n')
|
||||
return true, writeFile(filename, txt)
|
||||
}
|
||||
return false, e
|
||||
}
|
||||
@ -317,7 +323,7 @@ func UnbindKey(k string) error {
|
||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||
createBindingsIfNotExist(filename)
|
||||
if _, e = os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
input, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return errors.New("Error reading bindings.json file: " + err.Error())
|
||||
}
|
||||
@ -342,7 +348,7 @@ func UnbindKey(k string) error {
|
||||
}
|
||||
|
||||
if strings.HasPrefix(k, "\x1b") {
|
||||
screen.Screen.UnregisterRawSeq(k)
|
||||
screen.UnregisterRawSeq(k)
|
||||
}
|
||||
|
||||
defaults := DefaultBindings("buffer")
|
||||
@ -354,7 +360,8 @@ func UnbindKey(k string) error {
|
||||
}
|
||||
|
||||
txt, _ := json.MarshalIndent(parsed, "", " ")
|
||||
return ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
||||
txt = append(txt, '\n')
|
||||
return writeFile(filename, txt)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
@ -13,7 +14,6 @@ import (
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
type BufAction interface{}
|
||||
@ -100,9 +100,7 @@ func BufMapEvent(k Event, action string) {
|
||||
break
|
||||
}
|
||||
|
||||
// TODO: fix problem when complex bindings have these
|
||||
// characters (escape them?)
|
||||
idx := strings.IndexAny(action, "&|,")
|
||||
idx := util.IndexAnyUnquoted(action, "&|,")
|
||||
a := action
|
||||
if idx >= 0 {
|
||||
a = action[:idx]
|
||||
@ -226,26 +224,21 @@ type BufPane struct {
|
||||
// (possibly multiple) buttons were pressed previously.
|
||||
mousePressed map[MouseEvent]bool
|
||||
|
||||
// We need to keep track of insert key press toggle
|
||||
isOverwriteMode bool
|
||||
// This stores when the last click was
|
||||
// This is useful for detecting double and triple clicks
|
||||
lastClickTime time.Time
|
||||
lastLoc buffer.Loc
|
||||
|
||||
// lastCutTime stores when the last ctrl+k was issued.
|
||||
// It is used for clearing the clipboard to replace it with fresh cut lines.
|
||||
lastCutTime time.Time
|
||||
|
||||
// freshClip returns true if the clipboard has never been pasted.
|
||||
// freshClip returns true if one or more lines have been cut to the clipboard
|
||||
// and have never been pasted yet.
|
||||
freshClip bool
|
||||
|
||||
// Was the last mouse event actually a double click?
|
||||
// Useful for detecting triple clicks -- if a double click is detected
|
||||
// but the last mouse event was actually a double click, it's a triple click
|
||||
doubleClick bool
|
||||
DoubleClick bool
|
||||
// Same here, just to keep track for mouse move events
|
||||
tripleClick bool
|
||||
TripleClick bool
|
||||
|
||||
// Should the current multiple cursor selection search based on word or
|
||||
// based on selection (false for selection, true for word)
|
||||
@ -363,9 +356,6 @@ func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
|
||||
// Set mouseReleased to true because we assume the mouse is not being
|
||||
// pressed when the editor is opened
|
||||
h.resetMouse()
|
||||
// Set isOverwriteMode to false, because we assume we are in the default
|
||||
// mode when editor is opened
|
||||
h.isOverwriteMode = false
|
||||
h.lastClickTime = time.Time{}
|
||||
}
|
||||
|
||||
@ -644,7 +634,7 @@ func (h *BufPane) DoRuneInsert(r rune) {
|
||||
c.ResetSelection()
|
||||
}
|
||||
|
||||
if h.isOverwriteMode {
|
||||
if h.Buf.OverwriteMode {
|
||||
next := c.Loc
|
||||
next.X++
|
||||
h.Buf.Replace(c.Loc, next, string(r))
|
||||
@ -662,20 +652,28 @@ func (h *BufPane) DoRuneInsert(r rune) {
|
||||
// VSplitIndex opens the given buffer in a vertical split on the given side.
|
||||
func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
|
||||
e := NewBufPaneFromBuf(buf, h.tab)
|
||||
e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
|
||||
MainTab().Panes = append(MainTab().Panes, e)
|
||||
MainTab().Resize()
|
||||
MainTab().SetActive(len(MainTab().Panes) - 1)
|
||||
e.splitID = h.tab.GetNode(h.splitID).VSplit(right)
|
||||
currentPaneIdx := h.tab.GetPane(h.splitID)
|
||||
if right {
|
||||
currentPaneIdx++
|
||||
}
|
||||
h.tab.AddPane(e, currentPaneIdx)
|
||||
h.tab.Resize()
|
||||
h.tab.SetActive(currentPaneIdx)
|
||||
return e
|
||||
}
|
||||
|
||||
// HSplitIndex opens the given buffer in a horizontal split on the given side.
|
||||
func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
|
||||
e := NewBufPaneFromBuf(buf, h.tab)
|
||||
e.splitID = MainTab().GetNode(h.splitID).HSplit(bottom)
|
||||
MainTab().Panes = append(MainTab().Panes, e)
|
||||
MainTab().Resize()
|
||||
MainTab().SetActive(len(MainTab().Panes) - 1)
|
||||
e.splitID = h.tab.GetNode(h.splitID).HSplit(bottom)
|
||||
currentPaneIdx := h.tab.GetPane(h.splitID)
|
||||
if bottom {
|
||||
currentPaneIdx++
|
||||
}
|
||||
h.tab.AddPane(e, currentPaneIdx)
|
||||
h.tab.Resize()
|
||||
h.tab.SetActive(currentPaneIdx)
|
||||
return e
|
||||
}
|
||||
|
||||
@ -733,6 +731,9 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||
"CursorRight": (*BufPane).CursorRight,
|
||||
"CursorStart": (*BufPane).CursorStart,
|
||||
"CursorEnd": (*BufPane).CursorEnd,
|
||||
"CursorToViewTop": (*BufPane).CursorToViewTop,
|
||||
"CursorToViewCenter": (*BufPane).CursorToViewCenter,
|
||||
"CursorToViewBottom": (*BufPane).CursorToViewBottom,
|
||||
"SelectToStart": (*BufPane).SelectToStart,
|
||||
"SelectToEnd": (*BufPane).SelectToEnd,
|
||||
"SelectUp": (*BufPane).SelectUp,
|
||||
@ -780,6 +781,7 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||
"CopyLine": (*BufPane).CopyLine,
|
||||
"Cut": (*BufPane).Cut,
|
||||
"CutLine": (*BufPane).CutLine,
|
||||
"Duplicate": (*BufPane).Duplicate,
|
||||
"DuplicateLine": (*BufPane).DuplicateLine,
|
||||
"DeleteLine": (*BufPane).DeleteLine,
|
||||
"MoveLinesUp": (*BufPane).MoveLinesUp,
|
||||
@ -824,8 +826,12 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||
"AddTab": (*BufPane).AddTab,
|
||||
"PreviousTab": (*BufPane).PreviousTab,
|
||||
"NextTab": (*BufPane).NextTab,
|
||||
"FirstTab": (*BufPane).FirstTab,
|
||||
"LastTab": (*BufPane).LastTab,
|
||||
"NextSplit": (*BufPane).NextSplit,
|
||||
"PreviousSplit": (*BufPane).PreviousSplit,
|
||||
"FirstSplit": (*BufPane).FirstSplit,
|
||||
"LastSplit": (*BufPane).LastSplit,
|
||||
"Unsplit": (*BufPane).Unsplit,
|
||||
"VSplit": (*BufPane).VSplitAction,
|
||||
"HSplit": (*BufPane).HSplitAction,
|
||||
@ -841,6 +847,7 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||
"RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
|
||||
"RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
|
||||
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
|
||||
"SkipMultiCursorBack": (*BufPane).SkipMultiCursorBack,
|
||||
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
|
||||
"JumpLine": (*BufPane).JumpLine,
|
||||
"Deselect": (*BufPane).Deselect,
|
||||
@ -907,6 +914,7 @@ var MultiActions = map[string]bool{
|
||||
"Copy": true,
|
||||
"Cut": true,
|
||||
"CutLine": true,
|
||||
"Duplicate": true,
|
||||
"DuplicateLine": true,
|
||||
"DeleteLine": true,
|
||||
"MoveLinesUp": true,
|
||||
|
@ -139,23 +139,25 @@ func (h *BufPane) TextFilterCmd(args []string) {
|
||||
InfoBar.Error("usage: textfilter arguments")
|
||||
return
|
||||
}
|
||||
sel := h.Cursor.GetSelection()
|
||||
if len(sel) == 0 {
|
||||
h.Cursor.SelectWord()
|
||||
sel = h.Cursor.GetSelection()
|
||||
for _, c := range h.Buf.GetCursors() {
|
||||
sel := c.GetSelection()
|
||||
if len(sel) == 0 {
|
||||
c.SelectWord()
|
||||
sel = c.GetSelection()
|
||||
}
|
||||
var bout, berr bytes.Buffer
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stdin = strings.NewReader(string(sel))
|
||||
cmd.Stderr = &berr
|
||||
cmd.Stdout = &bout
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
InfoBar.Error(err.Error() + " " + berr.String())
|
||||
return
|
||||
}
|
||||
c.DeleteSelection()
|
||||
h.Buf.Insert(c.Loc, bout.String())
|
||||
}
|
||||
var bout, berr bytes.Buffer
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stdin = strings.NewReader(string(sel))
|
||||
cmd.Stderr = &berr
|
||||
cmd.Stdout = &bout
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
InfoBar.Error(err.Error() + " " + berr.String())
|
||||
return
|
||||
}
|
||||
h.Cursor.DeleteSelection()
|
||||
h.Buf.Insert(h.Cursor.Loc, bout.String())
|
||||
}
|
||||
|
||||
// TabMoveCmd moves the current tab to a given index (starts at 1). The
|
||||
@ -203,6 +205,7 @@ func (h *BufPane) TabMoveCmd(args []string) {
|
||||
Tabs.List = append(Tabs.List, nil)
|
||||
copy(Tabs.List[idxTo+1:], Tabs.List[idxTo:])
|
||||
Tabs.List[idxTo] = activeTab
|
||||
Tabs.Resize()
|
||||
Tabs.UpdateNames()
|
||||
Tabs.SetActive(idxTo)
|
||||
// InfoBar.Message(fmt.Sprintf("Moved tab from slot %d to %d", idxFrom+1, idxTo+1))
|
||||
@ -305,15 +308,8 @@ func (h *BufPane) OpenCmd(args []string) {
|
||||
}
|
||||
h.OpenBuffer(b)
|
||||
}
|
||||
if h.Buf.Modified() {
|
||||
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
|
||||
if !canceled && !yes {
|
||||
open()
|
||||
} else if !canceled && yes {
|
||||
h.Save()
|
||||
open()
|
||||
}
|
||||
})
|
||||
if h.Buf.Modified() && !h.Buf.Shared() {
|
||||
h.closePrompt("Save", open)
|
||||
} else {
|
||||
open()
|
||||
}
|
||||
@ -428,7 +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 {
|
||||
return errors.New(fmt.Sprintf("Unable to load help text for %s: %v", page, err))
|
||||
} else {
|
||||
@ -437,33 +433,74 @@ func (h *BufPane) openHelp(page string) error {
|
||||
helpBuffer.SetOptionNative("hltaberrors", false)
|
||||
helpBuffer.SetOptionNative("hltrailingws", false)
|
||||
|
||||
if h.Buf.Type == buffer.BTHelp {
|
||||
if h.Buf.Type == buffer.BTHelp && !forceSplit {
|
||||
h.OpenBuffer(helpBuffer)
|
||||
} else {
|
||||
} else if hsplit {
|
||||
h.HSplitBuf(helpBuffer)
|
||||
} else {
|
||||
h.VSplitBuf(helpBuffer)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HelpCmd tries to open the given help page in a horizontal split
|
||||
// HelpCmd tries to open the given help page according to the split type
|
||||
// configured with the "helpsplit" option. It can be 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) {
|
||||
hsplit := config.GlobalSettings["helpsplit"] == "hsplit"
|
||||
if len(args) < 1 {
|
||||
// Open the default help if the user just typed "> help"
|
||||
h.openHelp("help")
|
||||
h.openHelp("help", hsplit, false)
|
||||
} else {
|
||||
if config.FindRuntimeFile(config.RTHelp, args[0]) != nil {
|
||||
err := h.openHelp(args[0])
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
var topics []string
|
||||
forceSplit := false
|
||||
const errSplit = "hsplit and vsplit are not allowed at the same time"
|
||||
for _, arg := range args {
|
||||
switch arg {
|
||||
case "-vsplit":
|
||||
if forceSplit {
|
||||
InfoBar.Error(errSplit)
|
||||
return
|
||||
}
|
||||
hsplit = false
|
||||
forceSplit = true
|
||||
case "-hsplit":
|
||||
if forceSplit {
|
||||
InfoBar.Error(errSplit)
|
||||
return
|
||||
}
|
||||
hsplit = true
|
||||
forceSplit = true
|
||||
default:
|
||||
topics = append(topics, arg)
|
||||
}
|
||||
}
|
||||
|
||||
if len(topics) < 1 {
|
||||
// Do the same as without arg
|
||||
h.openHelp("help", hsplit, forceSplit)
|
||||
return
|
||||
}
|
||||
if len(topics) > 1 {
|
||||
forceSplit = true
|
||||
}
|
||||
|
||||
for _, topic := range topics {
|
||||
if config.FindRuntimeFile(config.RTHelp, topic) != nil {
|
||||
err := h.openHelp(topic, hsplit, forceSplit)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
} else {
|
||||
InfoBar.Error("Sorry, no help for ", topic)
|
||||
}
|
||||
} else {
|
||||
InfoBar.Error("Sorry, no help for ", args[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// VSplitCmd opens a vertical split with file given in the first argument
|
||||
// VSplitCmd opens one or more vertical splits with the files given as arguments
|
||||
// If no file is given, it opens an empty buffer in a new split
|
||||
func (h *BufPane) VSplitCmd(args []string) {
|
||||
if len(args) == 0 {
|
||||
@ -472,16 +509,18 @@ func (h *BufPane) VSplitCmd(args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
for _, a := range args {
|
||||
buf, err := buffer.NewBufferFromFile(a, buffer.BTDefault)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
h.VSplitBuf(buf)
|
||||
h.VSplitBuf(buf)
|
||||
}
|
||||
}
|
||||
|
||||
// HSplitCmd opens a horizontal split with file given in the first argument
|
||||
// HSplitCmd opens one or more horizontal splits with the files given as arguments
|
||||
// If no file is given, it opens an empty buffer in a new split
|
||||
func (h *BufPane) HSplitCmd(args []string) {
|
||||
if len(args) == 0 {
|
||||
@ -490,13 +529,15 @@ func (h *BufPane) HSplitCmd(args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
for _, a := range args {
|
||||
buf, err := buffer.NewBufferFromFile(a, buffer.BTDefault)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
h.HSplitBuf(buf)
|
||||
h.HSplitBuf(buf)
|
||||
}
|
||||
}
|
||||
|
||||
// EvalCmd evaluates a lua expression
|
||||
@ -504,7 +545,8 @@ func (h *BufPane) EvalCmd(args []string) {
|
||||
InfoBar.Error("Eval unsupported")
|
||||
}
|
||||
|
||||
// NewTabCmd opens the given file in a new tab
|
||||
// NewTabCmd opens one or more tabs with the files given as arguments
|
||||
// If no file is given, it opens an empty buffer in a new tab
|
||||
func (h *BufPane) NewTabCmd(args []string) {
|
||||
width, height := screen.Screen.Size()
|
||||
iOffset := config.GetInfoBarOffset()
|
||||
@ -609,7 +651,16 @@ func SetGlobalOptionNative(option string, nativeValue interface{}) error {
|
||||
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 {
|
||||
@ -734,7 +785,11 @@ func (h *BufPane) BindCmd(args []string) {
|
||||
|
||||
_, err := TryBindKey(parseKeyArg(args[0]), args[1], true)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
if errors.Is(err, util.ErrOverwrite) {
|
||||
screen.TermMessage(err)
|
||||
} else {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -747,7 +802,11 @@ func (h *BufPane) UnbindCmd(args []string) {
|
||||
|
||||
err := UnbindKey(parseKeyArg(args[0]))
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
if errors.Is(err, util.ErrOverwrite) {
|
||||
screen.TermMessage(err)
|
||||
} else {
|
||||
InfoBar.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -841,7 +900,7 @@ func (h *BufPane) SaveCmd(args []string) {
|
||||
if len(args) == 0 {
|
||||
h.Save()
|
||||
} else {
|
||||
h.Buf.SaveAs(args[0])
|
||||
h.saveBufToFile(args[0], "SaveAs", nil)
|
||||
}
|
||||
}
|
||||
|
||||
@ -902,10 +961,12 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
||||
nreplaced := 0
|
||||
start := h.Buf.Start()
|
||||
end := h.Buf.End()
|
||||
searchLoc := h.Cursor.Loc
|
||||
selection := h.Cursor.HasSelection()
|
||||
if selection {
|
||||
start = h.Cursor.CurSelection[0]
|
||||
end = h.Cursor.CurSelection[1]
|
||||
searchLoc = start // otherwise me might start at the end
|
||||
}
|
||||
if all {
|
||||
nreplaced, _ = h.Buf.ReplaceRegex(start, end, regex, replace, !noRegex)
|
||||
@ -914,7 +975,7 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
||||
return l.GreaterEqual(start) && l.LessEqual(end)
|
||||
}
|
||||
|
||||
searchLoc := h.Cursor.Loc
|
||||
lastMatchEnd := buffer.Loc{-1, -1}
|
||||
var doReplacement func()
|
||||
doReplacement = func() {
|
||||
locs, found, err := h.Buf.FindNext(search, start, end, searchLoc, true, true)
|
||||
@ -929,6 +990,18 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
if lastMatchEnd == locs[1] {
|
||||
// skip empty match right after previous match
|
||||
if searchLoc == end {
|
||||
searchLoc = start
|
||||
lastMatchEnd = buffer.Loc{-1, -1}
|
||||
} else {
|
||||
searchLoc = searchLoc.Move(1, h.Buf)
|
||||
}
|
||||
doReplacement()
|
||||
return
|
||||
}
|
||||
|
||||
h.Cursor.SetSelectionStart(locs[0])
|
||||
h.Cursor.SetSelectionEnd(locs[1])
|
||||
h.GotoLoc(locs[0])
|
||||
@ -954,6 +1027,7 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
||||
h.Buf.RelocateCursors()
|
||||
return
|
||||
}
|
||||
lastMatchEnd = searchLoc
|
||||
doReplacement()
|
||||
})
|
||||
}
|
||||
@ -985,10 +1059,42 @@ func (h *BufPane) ReplaceAllCmd(args []string) {
|
||||
h.ReplaceCmd(append(args, "-a"))
|
||||
}
|
||||
|
||||
func (h *BufPane) openTerm(args []string, newtab bool) {
|
||||
t := new(shell.Terminal)
|
||||
err := t.Start(args, false, true, nil, nil)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
pane := 0
|
||||
id := h.ID()
|
||||
if newtab {
|
||||
h.AddTab()
|
||||
id = MainTab().Panes[pane].ID()
|
||||
} else {
|
||||
for i, p := range MainTab().Panes {
|
||||
if p.IsActive() {
|
||||
pane = i
|
||||
id = p.ID()
|
||||
p.Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v := h.GetView()
|
||||
tp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
MainTab().Panes[pane] = tp
|
||||
MainTab().SetActive(pane)
|
||||
}
|
||||
|
||||
// TermCmd opens a terminal in the current view
|
||||
func (h *BufPane) TermCmd(args []string) {
|
||||
ps := h.tab.Panes
|
||||
|
||||
if !TermEmuSupported {
|
||||
InfoBar.Error("Terminal emulator not supported on this system")
|
||||
return
|
||||
@ -1003,56 +1109,19 @@ func (h *BufPane) TermCmd(args []string) {
|
||||
args = []string{sh}
|
||||
}
|
||||
|
||||
term := func(i int, newtab bool) {
|
||||
t := new(shell.Terminal)
|
||||
err := t.Start(args, false, true, nil, nil)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
id := h.ID()
|
||||
if newtab {
|
||||
h.AddTab()
|
||||
i = 0
|
||||
id = MainTab().Panes[0].ID()
|
||||
} else {
|
||||
MainTab().Panes[i].Close()
|
||||
}
|
||||
|
||||
v := h.GetView()
|
||||
tp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
MainTab().Panes[i] = tp
|
||||
MainTab().SetActive(i)
|
||||
}
|
||||
|
||||
// If there is only one open file we make a new tab instead of overwriting it
|
||||
newtab := len(MainTab().Panes) == 1 && len(Tabs.List) == 1
|
||||
|
||||
if newtab {
|
||||
term(0, true)
|
||||
h.openTerm(args, true)
|
||||
return
|
||||
}
|
||||
|
||||
for i, p := range ps {
|
||||
if p.ID() == h.ID() {
|
||||
if h.Buf.Modified() {
|
||||
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
|
||||
if !canceled && !yes {
|
||||
term(i, false)
|
||||
} else if !canceled && yes {
|
||||
h.Save()
|
||||
term(i, false)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
term(i, false)
|
||||
}
|
||||
}
|
||||
if h.Buf.Modified() && !h.Buf.Shared() {
|
||||
h.closePrompt("Save", func() {
|
||||
h.openTerm(args, false)
|
||||
})
|
||||
} else {
|
||||
h.openTerm(args, false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ package action
|
||||
var termdefaults = map[string]string{
|
||||
"<Ctrl-q><Ctrl-q>": "Exit",
|
||||
"<Ctrl-e><Ctrl-e>": "CommandMode",
|
||||
"<Ctrl-w><Ctrl-w>": "NextSplit",
|
||||
"<Ctrl-w><Ctrl-w>": "NextSplit|FirstSplit",
|
||||
}
|
||||
|
||||
// DefaultBindings returns a map containing micro's default keybindings
|
||||
|
@ -45,23 +45,23 @@ var bufdefaults = map[string]string{
|
||||
"Alt-]": "DiffNext|CursorEnd",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-c": "Copy|CopyLine",
|
||||
"Ctrl-x": "Cut|CutLine",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-d": "DuplicateLine",
|
||||
"Ctrl-d": "Duplicate|DuplicateLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Ctrl-a": "SelectAll",
|
||||
"Ctrl-t": "AddTab",
|
||||
"Alt-,": "PreviousTab",
|
||||
"Alt-.": "NextTab",
|
||||
"Alt-,": "PreviousTab|LastTab",
|
||||
"Alt-.": "NextTab|FirstTab",
|
||||
"Home": "StartOfTextToggle",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"CtrlPageUp": "PreviousTab|LastTab",
|
||||
"CtrlPageDown": "NextTab|FirstTab",
|
||||
"ShiftPageUp": "SelectPageUp",
|
||||
"ShiftPageDown": "SelectPageDown",
|
||||
"Ctrl-g": "ToggleHelp",
|
||||
@ -72,7 +72,7 @@ var bufdefaults = map[string]string{
|
||||
"Ctrl-b": "ShellMode",
|
||||
"Ctrl-q": "Quit",
|
||||
"Ctrl-e": "CommandMode",
|
||||
"Ctrl-w": "NextSplit",
|
||||
"Ctrl-w": "NextSplit|FirstSplit",
|
||||
"Ctrl-u": "ToggleMacro",
|
||||
"Ctrl-j": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
@ -146,8 +146,8 @@ var infodefaults = map[string]string{
|
||||
"Backtab": "CycleAutocompleteBack",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-c": "Copy|CopyLine",
|
||||
"Ctrl-x": "Cut|CutLine",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Home": "StartOfTextToggle",
|
||||
|
@ -48,23 +48,23 @@ var bufdefaults = map[string]string{
|
||||
"Alt-]": "DiffNext|CursorEnd",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-c": "Copy|CopyLine",
|
||||
"Ctrl-x": "Cut|CutLine",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-d": "DuplicateLine",
|
||||
"Ctrl-d": "Duplicate|DuplicateLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Ctrl-a": "SelectAll",
|
||||
"Ctrl-t": "AddTab",
|
||||
"Alt-,": "PreviousTab",
|
||||
"Alt-.": "NextTab",
|
||||
"Alt-,": "PreviousTab|LastTab",
|
||||
"Alt-.": "NextTab|FirstTab",
|
||||
"Home": "StartOfTextToggle",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"CtrlPageUp": "PreviousTab|LastTab",
|
||||
"CtrlPageDown": "NextTab|FirstTab",
|
||||
"ShiftPageUp": "SelectPageUp",
|
||||
"ShiftPageDown": "SelectPageDown",
|
||||
"Ctrl-g": "ToggleHelp",
|
||||
@ -75,7 +75,7 @@ var bufdefaults = map[string]string{
|
||||
"Ctrl-b": "ShellMode",
|
||||
"Ctrl-q": "Quit",
|
||||
"Ctrl-e": "CommandMode",
|
||||
"Ctrl-w": "NextSplit",
|
||||
"Ctrl-w": "NextSplit|FirstSplit",
|
||||
"Ctrl-u": "ToggleMacro",
|
||||
"Ctrl-j": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
@ -149,8 +149,8 @@ var infodefaults = map[string]string{
|
||||
"Backtab": "CycleAutocompleteBack",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-c": "Copy|CopyLine",
|
||||
"Ctrl-x": "Cut|CutLine",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Home": "StartOfTextToggle",
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
)
|
||||
|
||||
type Event interface {
|
||||
|
@ -135,15 +135,6 @@ headerLoop:
|
||||
return chosen, suggestions
|
||||
}
|
||||
|
||||
func contains(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// OptionComplete autocompletes options
|
||||
func OptionComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
c := b.GetActiveCursor()
|
||||
|
@ -3,12 +3,12 @@ package action
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
"github.com/zyedidia/micro/v2/internal/info"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
type InfoKeyAction func(*InfoPane)
|
||||
|
@ -3,7 +3,7 @@ package action
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
)
|
||||
|
||||
type PaneKeyAction func(Pane) bool
|
||||
|
@ -4,9 +4,9 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
type RawPane struct {
|
||||
|
@ -3,13 +3,13 @@ package action
|
||||
import (
|
||||
luar "layeh.com/gopher-luar"
|
||||
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/views"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
// The TabList is a list of tabs and a window to display the tab bar
|
||||
@ -211,7 +211,7 @@ func InitTabs(bufs []*buffer.Buffer) {
|
||||
for _, b := range bufs[1:] {
|
||||
if multiopen == "vsplit" {
|
||||
MainTab().CurPane().VSplitBuf(b)
|
||||
} else { // default hsplit
|
||||
} else { // default hsplit
|
||||
MainTab().CurPane().HSplitBuf(b)
|
||||
}
|
||||
}
|
||||
@ -349,6 +349,16 @@ func (t *Tab) SetActive(i int) {
|
||||
}
|
||||
}
|
||||
|
||||
// AddPane adds a pane at a given index
|
||||
func (t *Tab) AddPane(pane Pane, i int) {
|
||||
if len(t.Panes) == i {
|
||||
t.Panes = append(t.Panes, pane)
|
||||
return
|
||||
}
|
||||
t.Panes = append(t.Panes[:i+1], t.Panes[i:]...)
|
||||
t.Panes[i] = pane
|
||||
}
|
||||
|
||||
// GetPane returns the pane with the given split index
|
||||
func (t *Tab) GetPane(splitid uint64) int {
|
||||
for i, p := range t.Panes {
|
||||
|
@ -1,4 +1,4 @@
|
||||
// +build linux darwin dragonfly openbsd_amd64 freebsd
|
||||
//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd
|
||||
|
||||
package action
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd_amd64
|
||||
//go:build plan9 || nacl || windows
|
||||
|
||||
package action
|
||||
|
||||
|
@ -4,13 +4,13 @@ import (
|
||||
"errors"
|
||||
"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/config"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/zyedidia/terminal"
|
||||
)
|
||||
|
||||
type TermKeyAction func(*TermPane)
|
||||
|
@ -2,7 +2,7 @@ package buffer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"io/fs"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
@ -109,15 +109,15 @@ func FileComplete(b *Buffer) ([]string, []string) {
|
||||
sep := string(os.PathSeparator)
|
||||
dirs := strings.Split(input, sep)
|
||||
|
||||
var files []os.FileInfo
|
||||
var files []fs.DirEntry
|
||||
var err error
|
||||
if len(dirs) > 1 {
|
||||
directories := strings.Join(dirs[:len(dirs)-1], sep) + sep
|
||||
|
||||
directories, _ = util.ReplaceHome(directories)
|
||||
files, err = ioutil.ReadDir(directories)
|
||||
files, err = os.ReadDir(directories)
|
||||
} else {
|
||||
files, err = ioutil.ReadDir(".")
|
||||
files, err = os.ReadDir(".")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -1,24 +1,26 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"golang.org/x/text/encoding"
|
||||
)
|
||||
|
||||
const backupMsg = `A backup was detected for this file. This likely means that micro
|
||||
crashed while editing this file, or another instance of micro is currently
|
||||
editing this file.
|
||||
const BackupMsg = `A backup was detected for:
|
||||
|
||||
The backup was created on %s, and the file is
|
||||
%s
|
||||
|
||||
This likely means that micro crashed while editing this file,
|
||||
or another instance of micro is currently editing this file,
|
||||
or an error occurred while saving this file so it may be corrupted.
|
||||
|
||||
The backup was created on %s and its path is:
|
||||
|
||||
%s
|
||||
|
||||
@ -30,90 +32,80 @@ The backup was created on %s, and the file is
|
||||
|
||||
Options: [r]ecover, [i]gnore, [a]bort: `
|
||||
|
||||
var backupRequestChan chan *Buffer
|
||||
const backupSeconds = 8
|
||||
|
||||
func backupThread() {
|
||||
for {
|
||||
time.Sleep(time.Second * 8)
|
||||
|
||||
for len(backupRequestChan) > 0 {
|
||||
b := <-backupRequestChan
|
||||
bfini := atomic.LoadInt32(&(b.fini)) != 0
|
||||
if !bfini {
|
||||
b.Backup()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var BackupCompleteChan chan *Buffer
|
||||
|
||||
func init() {
|
||||
backupRequestChan = make(chan *Buffer, 10)
|
||||
|
||||
go backupThread()
|
||||
BackupCompleteChan = make(chan *Buffer, 10)
|
||||
}
|
||||
|
||||
func (b *Buffer) RequestBackup() {
|
||||
if !b.requestedBackup {
|
||||
if !b.RequestedBackup {
|
||||
select {
|
||||
case backupRequestChan <- b:
|
||||
default:
|
||||
// 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 {
|
||||
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
|
||||
return nil
|
||||
}
|
||||
|
||||
backupdir, err := util.ReplaceHome(b.Settings["backupdir"].(string))
|
||||
if backupdir == "" || err != nil {
|
||||
backupdir = filepath.Join(config.ConfigDir, "backups")
|
||||
}
|
||||
if _, err := os.Stat(backupdir); os.IsNotExist(err) {
|
||||
backupdir := b.backupDir()
|
||||
if _, err := os.Stat(backupdir); errors.Is(err, fs.ErrNotExist) {
|
||||
os.Mkdir(backupdir, os.ModePerm)
|
||||
}
|
||||
|
||||
name := filepath.Join(backupdir, util.EscapePath(b.AbsPath))
|
||||
|
||||
err = overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
|
||||
if len(b.lines) == 0 {
|
||||
return
|
||||
name := util.DetermineEscapePath(backupdir, b.AbsPath)
|
||||
if _, err := os.Stat(name); errors.Is(err, fs.ErrNotExist) {
|
||||
_, err = b.overwriteFile(name)
|
||||
if err == nil {
|
||||
BackupCompleteChan <- b
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// end of line
|
||||
eol := []byte{'\n'}
|
||||
tmp := util.AppendBackupSuffix(name)
|
||||
_, err := b.overwriteFile(tmp)
|
||||
if err != nil {
|
||||
os.Remove(tmp)
|
||||
return err
|
||||
}
|
||||
err = os.Rename(tmp, name)
|
||||
if err != nil {
|
||||
os.Remove(tmp)
|
||||
return err
|
||||
}
|
||||
|
||||
// write lines
|
||||
if _, 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
|
||||
}
|
||||
}
|
||||
return
|
||||
}, false)
|
||||
|
||||
b.requestedBackup = false
|
||||
BackupCompleteChan <- b
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveBackup removes any backup file associated with this buffer
|
||||
func (b *Buffer) RemoveBackup() {
|
||||
if !b.Settings["backup"].(bool) || b.Settings["permbackup"].(bool) || b.Path == "" || b.Type != BTDefault {
|
||||
if !b.Settings["backup"].(bool) || b.keepBackup() || b.Path == "" || b.Type != BTDefault {
|
||||
return
|
||||
}
|
||||
f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
|
||||
f := util.DetermineEscapePath(b.backupDir(), b.AbsPath)
|
||||
os.Remove(f)
|
||||
}
|
||||
|
||||
@ -121,13 +113,13 @@ func (b *Buffer) RemoveBackup() {
|
||||
// Returns true if a backup was applied
|
||||
func (b *Buffer) ApplyBackup(fsize int64) (bool, bool) {
|
||||
if b.Settings["backup"].(bool) && !b.Settings["permbackup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
|
||||
backupfile := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
|
||||
backupfile := util.DetermineEscapePath(b.backupDir(), b.AbsPath)
|
||||
if info, err := os.Stat(backupfile); err == nil {
|
||||
backup, err := os.Open(backupfile)
|
||||
if err == nil {
|
||||
defer backup.Close()
|
||||
t := info.ModTime()
|
||||
msg := fmt.Sprintf(backupMsg, t.Format("Mon Jan _2 at 15:04, 2006"), util.EscapePath(b.AbsPath))
|
||||
msg := fmt.Sprintf(BackupMsg, b.Path, t.Format("Mon Jan _2 at 15:04, 2006"), backupfile)
|
||||
choice := screen.TermPrompt(msg, []string{"r", "i", "a", "recover", "ignore", "abort"}, true)
|
||||
|
||||
if choice%3 == 0 {
|
||||
|
@ -7,9 +7,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -25,13 +24,12 @@ import (
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/micro/v2/pkg/highlight"
|
||||
"golang.org/x/text/encoding"
|
||||
"golang.org/x/text/encoding/htmlindex"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
const backupTime = 8000
|
||||
|
||||
var (
|
||||
// OpenBuffers is a list of the currently open buffers
|
||||
OpenBuffers []*Buffer
|
||||
@ -89,6 +87,8 @@ type SharedBuffer struct {
|
||||
// LocalSettings customized by the user for this buffer only
|
||||
LocalSettings map[string]bool
|
||||
|
||||
encoding encoding.Encoding
|
||||
|
||||
Suggestions []string
|
||||
Completions []string
|
||||
CurSuggestion int
|
||||
@ -101,7 +101,8 @@ type SharedBuffer struct {
|
||||
diffLock sync.RWMutex
|
||||
diff map[int]DiffStatus
|
||||
|
||||
requestedBackup bool
|
||||
RequestedBackup bool
|
||||
forceKeepBackup bool
|
||||
|
||||
// ReloadDisabled allows the user to disable reloads if they
|
||||
// are viewing a file that is constantly changing
|
||||
@ -209,6 +210,11 @@ type Buffer struct {
|
||||
LastSearchRegex bool
|
||||
// HighlightSearch enables highlighting all instances of the last successful search
|
||||
HighlightSearch bool
|
||||
|
||||
// OverwriteMode indicates that we are in overwrite mode (toggled by
|
||||
// Insert key by default) i.e. that typing a character shall replace the
|
||||
// character under the cursor instead of inserting a character before it.
|
||||
OverwriteMode bool
|
||||
}
|
||||
|
||||
// NewBufferFromFileAtLoc opens a new buffer with a given cursor location
|
||||
@ -232,17 +238,20 @@ func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(filename, os.O_WRONLY, 0)
|
||||
readonly := os.IsPermission(err)
|
||||
f.Close()
|
||||
|
||||
fileInfo, serr := os.Stat(filename)
|
||||
if serr != nil && !os.IsNotExist(serr) {
|
||||
if serr != nil && !errors.Is(serr, fs.ErrNotExist) {
|
||||
return nil, serr
|
||||
}
|
||||
if serr == nil && fileInfo.IsDir() {
|
||||
return nil, errors.New("Error: " + filename + " is a directory and cannot be opened")
|
||||
}
|
||||
if serr == nil && !fileInfo.Mode().IsRegular() {
|
||||
return nil, errors.New("Error: " + filename + " is not a regular file and cannot be opened")
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(filename, os.O_WRONLY, 0)
|
||||
readonly := errors.Is(err, fs.ErrPermission)
|
||||
f.Close()
|
||||
|
||||
file, err := os.Open(filename)
|
||||
if err == nil {
|
||||
@ -250,7 +259,7 @@ func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer,
|
||||
}
|
||||
|
||||
var buf *Buffer
|
||||
if os.IsNotExist(err) {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
// File does not exist -- create an empty buffer with that name
|
||||
buf = NewBufferFromString("", filename, btype)
|
||||
} else if err != nil {
|
||||
@ -320,30 +329,19 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
b.AbsPath = absPath
|
||||
b.Path = path
|
||||
|
||||
// this is a little messy since we need to know some settings to read
|
||||
// the file properly, but some settings depend on the filetype, which
|
||||
// we don't know until reading the file. We first read the settings
|
||||
// into a local variable and then use that to determine the encoding,
|
||||
// readonly, and fileformat necessary for reading the file and
|
||||
// assigning the filetype.
|
||||
settings := config.DefaultCommonSettings()
|
||||
b.Settings = config.DefaultCommonSettings()
|
||||
b.LocalSettings = make(map[string]bool)
|
||||
for k, v := range config.GlobalSettings {
|
||||
if _, ok := config.DefaultGlobalOnlySettings[k]; !ok {
|
||||
// make sure setting is not global-only
|
||||
settings[k] = v
|
||||
b.Settings[k] = v
|
||||
}
|
||||
}
|
||||
config.InitLocalSettings(settings, absPath)
|
||||
b.Settings["readonly"] = settings["readonly"]
|
||||
b.Settings["filetype"] = settings["filetype"]
|
||||
b.Settings["syntax"] = settings["syntax"]
|
||||
config.UpdatePathGlobLocals(b.Settings, absPath)
|
||||
|
||||
enc, err := htmlindex.Get(settings["encoding"].(string))
|
||||
b.encoding, err = htmlindex.Get(b.Settings["encoding"].(string))
|
||||
if err != nil {
|
||||
enc = unicode.UTF8
|
||||
b.encoding = unicode.UTF8
|
||||
b.Settings["encoding"] = "utf-8"
|
||||
}
|
||||
|
||||
@ -354,14 +352,14 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
return NewBufferFromString("", "", btype)
|
||||
}
|
||||
if !hasBackup {
|
||||
reader := bufio.NewReader(transform.NewReader(r, enc.NewDecoder()))
|
||||
reader := bufio.NewReader(transform.NewReader(r, b.encoding.NewDecoder()))
|
||||
|
||||
var ff FileFormat = FFAuto
|
||||
|
||||
if size == 0 {
|
||||
// for empty files, use the fileformat setting instead of
|
||||
// autodetection
|
||||
switch settings["fileformat"] {
|
||||
switch b.Settings["fileformat"] {
|
||||
case "unix":
|
||||
ff = FFUnix
|
||||
case "dos":
|
||||
@ -392,10 +390,10 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
}
|
||||
|
||||
b.UpdateRules()
|
||||
// init local settings again now that we know the filetype
|
||||
config.InitLocalSettings(b.Settings, b.Path)
|
||||
// we know the filetype now, so update per-filetype settings
|
||||
config.UpdateFileTypeLocals(b.Settings, b.Settings["filetype"].(string))
|
||||
|
||||
if _, err := os.Stat(filepath.Join(config.ConfigDir, "buffers")); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(filepath.Join(config.ConfigDir, "buffers")); errors.Is(err, fs.ErrNotExist) {
|
||||
os.Mkdir(filepath.Join(config.ConfigDir, "buffers"), os.ModePerm)
|
||||
}
|
||||
|
||||
@ -480,7 +478,7 @@ func (b *Buffer) GetName() string {
|
||||
name = b.Path
|
||||
}
|
||||
if b.Settings["basename"].(bool) {
|
||||
return path.Base(name)
|
||||
return filepath.Base(name)
|
||||
}
|
||||
return name
|
||||
}
|
||||
@ -546,7 +544,7 @@ func (b *Buffer) ReOpen() error {
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(transform.NewReader(file, enc.NewDecoder()))
|
||||
data, err := ioutil.ReadAll(reader)
|
||||
data, err := io.ReadAll(reader)
|
||||
txt := string(data)
|
||||
|
||||
if err != nil {
|
||||
@ -621,6 +619,16 @@ func (b *Buffer) WordAt(loc Loc) []byte {
|
||||
return b.Substr(start, end)
|
||||
}
|
||||
|
||||
// Shared returns if there are other buffers with the same file as this buffer
|
||||
func (b *Buffer) Shared() bool {
|
||||
for _, buf := range OpenBuffers {
|
||||
if buf != b && buf.SharedBuffer == b.SharedBuffer {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Modified returns if this buffer has been modified since
|
||||
// being opened
|
||||
func (b *Buffer) Modified() bool {
|
||||
|
@ -20,8 +20,14 @@ type Cursor struct {
|
||||
buf *Buffer
|
||||
Loc
|
||||
|
||||
// Last cursor x position
|
||||
// Last visual x position of the cursor. Used in cursor up/down movements
|
||||
// for remembering the original x position when moving to a line that is
|
||||
// shorter than current x position.
|
||||
LastVisualX int
|
||||
// Similar to LastVisualX but takes softwrapping into account, i.e. last
|
||||
// visual x position in a visual (wrapped) line on the screen, which may be
|
||||
// different from the line in the buffer.
|
||||
LastWrappedVisualX int
|
||||
|
||||
// The current selection as a range of character numbers (inclusive)
|
||||
CurSelection [2]Loc
|
||||
@ -61,8 +67,9 @@ func (c *Cursor) Buf() *Buffer {
|
||||
// Goto puts the cursor at the given cursor's location and gives
|
||||
// the current cursor its selection too
|
||||
func (c *Cursor) Goto(b Cursor) {
|
||||
c.X, c.Y, c.LastVisualX = b.X, b.Y, b.LastVisualX
|
||||
c.X, c.Y = b.X, b.Y
|
||||
c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection
|
||||
c.StoreVisualX()
|
||||
}
|
||||
|
||||
// GotoLoc puts the cursor at the given cursor's location and gives
|
||||
@ -73,8 +80,8 @@ func (c *Cursor) GotoLoc(l Loc) {
|
||||
}
|
||||
|
||||
// GetVisualX returns the x value of the cursor in visual spaces
|
||||
func (c *Cursor) GetVisualX() int {
|
||||
if c.buf.GetVisualX != nil {
|
||||
func (c *Cursor) GetVisualX(wrap bool) int {
|
||||
if wrap && c.buf.GetVisualX != nil {
|
||||
return c.buf.GetVisualX(c.Loc)
|
||||
}
|
||||
|
||||
@ -100,7 +107,7 @@ func (c *Cursor) GetCharPosInLine(b []byte, visualPos int) int {
|
||||
// Start moves the cursor to the start of the line it is on
|
||||
func (c *Cursor) Start() {
|
||||
c.X = 0
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
c.StoreVisualX()
|
||||
}
|
||||
|
||||
// StartOfText moves the cursor to the first non-whitespace rune of
|
||||
@ -131,7 +138,7 @@ func (c *Cursor) IsStartOfText() bool {
|
||||
// End moves the cursor to the end of the line it is on
|
||||
func (c *Cursor) End() {
|
||||
c.X = util.CharacterCount(c.buf.LineBytes(c.Y))
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
c.StoreVisualX()
|
||||
}
|
||||
|
||||
// CopySelection copies the user's selection to either "primary"
|
||||
@ -186,7 +193,7 @@ func (c *Cursor) Deselect(start bool) {
|
||||
if start {
|
||||
c.Loc = c.CurSelection[0]
|
||||
} else {
|
||||
c.Loc = c.CurSelection[1].Move(-1, c.buf)
|
||||
c.Loc = c.CurSelection[1]
|
||||
}
|
||||
c.ResetSelection()
|
||||
c.StoreVisualX()
|
||||
@ -615,5 +622,6 @@ func (c *Cursor) RuneUnder(x int) rune {
|
||||
}
|
||||
|
||||
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[1] = move(c.OrigSelection[1])
|
||||
c.Relocate()
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
c.StoreVisualX()
|
||||
}
|
||||
|
||||
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:])]
|
||||
}
|
||||
|
||||
// DeleteByte deletes the byte at a position
|
||||
func (la *LineArray) deleteByte(pos Loc) {
|
||||
la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X+copy(la.lines[pos.Y].data[pos.X:], la.lines[pos.Y].data[pos.X+1:])]
|
||||
}
|
||||
|
||||
// Substr returns the string representation between two locations
|
||||
func (la *LineArray) Substr(start, end Loc) []byte {
|
||||
startX := runeToByteIndex(start.X, la.lines[start.Y].data)
|
||||
|
@ -47,6 +47,16 @@ func (l Loc) LessEqual(b Loc) bool {
|
||||
return l == b
|
||||
}
|
||||
|
||||
// Clamp clamps a loc between start and end
|
||||
func (l Loc) Clamp(start, end Loc) Loc {
|
||||
if l.GreaterEqual(end) {
|
||||
return end
|
||||
} else if l.LessThan(start) {
|
||||
return start
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// The following functions require a buffer to know where newlines are
|
||||
|
||||
// Diff returns the distance between two locations
|
||||
@ -139,10 +149,5 @@ func ByteOffset(pos Loc, buf *Buffer) int {
|
||||
|
||||
// clamps a loc within a buffer
|
||||
func clamp(pos Loc, la *LineArray) Loc {
|
||||
if pos.GreaterEqual(la.End()) {
|
||||
return la.End()
|
||||
} else if pos.LessThan(la.Start()) {
|
||||
return la.Start()
|
||||
}
|
||||
return pos
|
||||
return pos.Clamp(la.Start(), la.End())
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
type MsgType int
|
||||
|
@ -5,18 +5,19 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"golang.org/x/text/encoding"
|
||||
"golang.org/x/text/encoding/htmlindex"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
@ -24,68 +25,178 @@ import (
|
||||
// because hashing is too slow
|
||||
const LargeFileThreshold = 50000
|
||||
|
||||
// overwriteFile opens the given file for writing, truncating if one exists, and then calls
|
||||
// the supplied function with the file as io.Writer object, also making sure the file is
|
||||
// closed afterwards.
|
||||
func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error, withSudo bool) (err error) {
|
||||
type wrappedFile struct {
|
||||
writeCloser io.WriteCloser
|
||||
withSudo bool
|
||||
screenb bool
|
||||
cmd *exec.Cmd
|
||||
sigChan chan os.Signal
|
||||
}
|
||||
|
||||
type saveResponse struct {
|
||||
size int
|
||||
err error
|
||||
}
|
||||
|
||||
type saveRequest struct {
|
||||
buf *Buffer
|
||||
path string
|
||||
withSudo bool
|
||||
newFile bool
|
||||
saveResponseChan chan saveResponse
|
||||
}
|
||||
|
||||
var saveRequestChan chan saveRequest
|
||||
var backupRequestChan chan *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 screenb bool
|
||||
var cmd *exec.Cmd
|
||||
var sigChan chan os.Signal
|
||||
|
||||
if withSudo {
|
||||
cmd = exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name)
|
||||
|
||||
if writeCloser, err = cmd.StdinPipe(); err != nil {
|
||||
return
|
||||
writeCloser, err = cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return wrappedFile{}, err
|
||||
}
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
<-c
|
||||
cmd.Process.Kill()
|
||||
}()
|
||||
sigChan = make(chan os.Signal, 1)
|
||||
signal.Reset(os.Interrupt)
|
||||
signal.Notify(sigChan, os.Interrupt)
|
||||
|
||||
screenb = screen.TempFini()
|
||||
// need to start the process now, otherwise when we flush the file
|
||||
// contents to its stdin it might hang because the kernel's pipe size
|
||||
// is too small to handle the full file contents all at once
|
||||
if e := cmd.Start(); e != nil && err == nil {
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
screen.TempStart(screenb)
|
||||
return err
|
||||
|
||||
signal.Notify(util.Sigterm, os.Interrupt)
|
||||
signal.Stop(sigChan)
|
||||
|
||||
return wrappedFile{}, err
|
||||
}
|
||||
} else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
w := bufio.NewWriter(transform.NewWriter(writeCloser, enc.NewEncoder()))
|
||||
err = fn(w)
|
||||
|
||||
if err2 := w.Flush(); err2 != nil && err == nil {
|
||||
err = err2
|
||||
}
|
||||
// Call Sync() on the file to make sure the content is safely on disk.
|
||||
// Does not work with sudo as we don't have direct access to the file.
|
||||
if !withSudo {
|
||||
f := writeCloser.(*os.File)
|
||||
if err2 := f.Sync(); err2 != nil && err == nil {
|
||||
err = err2
|
||||
} else {
|
||||
writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, util.FileMode)
|
||||
if err != nil {
|
||||
return wrappedFile{}, err
|
||||
}
|
||||
}
|
||||
if err2 := writeCloser.Close(); err2 != nil && err == nil {
|
||||
err = err2
|
||||
|
||||
return wrappedFile{writeCloser, withSudo, screenb, cmd, sigChan}, nil
|
||||
}
|
||||
|
||||
func (wf wrappedFile) Write(b *Buffer) (int, error) {
|
||||
file := bufio.NewWriter(transform.NewWriter(wf.writeCloser, b.encoding.NewEncoder()))
|
||||
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
if len(b.lines) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if withSudo {
|
||||
// end of line
|
||||
var eol []byte
|
||||
if b.Endings == FFDos {
|
||||
eol = []byte{'\r', '\n'}
|
||||
} else {
|
||||
eol = []byte{'\n'}
|
||||
}
|
||||
|
||||
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.
|
||||
f := wf.writeCloser.(*os.File)
|
||||
err = f.Sync()
|
||||
}
|
||||
return size, err
|
||||
}
|
||||
|
||||
func (wf wrappedFile) Close() error {
|
||||
err := wf.writeCloser.Close()
|
||||
if wf.withSudo {
|
||||
// wait for dd to finish and restart the screen if we used sudo
|
||||
err := cmd.Wait()
|
||||
screen.TempStart(screenb)
|
||||
err := wf.cmd.Wait()
|
||||
screen.TempStart(wf.screenb)
|
||||
|
||||
signal.Notify(util.Sigterm, os.Interrupt)
|
||||
signal.Stop(wf.sigChan)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
func (b *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
|
||||
@ -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
|
||||
defer func() {
|
||||
b.ModTime, _ = util.GetModTime(filename)
|
||||
err = b.Serialize()
|
||||
}()
|
||||
filename, err = util.ReplaceHome(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Removes any tilde and replaces with the absolute path to home
|
||||
absFilename, _ := util.ReplaceHome(filename)
|
||||
newFile := false
|
||||
fileInfo, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
newFile = true
|
||||
}
|
||||
if err == nil && fileInfo.IsDir() {
|
||||
return errors.New("Error: " + filename + " is a directory and cannot be saved")
|
||||
}
|
||||
if err == nil && !fileInfo.Mode().IsRegular() {
|
||||
return errors.New("Error: " + filename + " is not a regular file and cannot be saved")
|
||||
}
|
||||
|
||||
absFilename, err := filepath.Abs(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the leading path to the file | "." is returned if there's no leading path provided
|
||||
if dirname := filepath.Dir(absFilename); dirname != "." {
|
||||
// Check if the parent dirs don't exist
|
||||
if _, statErr := os.Stat(dirname); os.IsNotExist(statErr) {
|
||||
if _, statErr := os.Stat(dirname); errors.Is(statErr, fs.ErrNotExist) {
|
||||
// Prompt to make sure they want to create the dirs that are missing
|
||||
if b.Settings["mkparents"].(bool) {
|
||||
// Create all leading dir(s) since they don't exist
|
||||
@ -172,49 +299,22 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
|
||||
}
|
||||
}
|
||||
|
||||
var fileSize int
|
||||
|
||||
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
|
||||
saveResponseChan := make(chan saveResponse)
|
||||
saveRequestChan <- saveRequest{b, absFilename, withSudo, newFile, saveResponseChan}
|
||||
result := <-saveResponseChan
|
||||
err = result.err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if errors.Is(err, util.ErrOverwrite) {
|
||||
screen.TermMessage(err)
|
||||
err = errors.Unwrap(err)
|
||||
|
||||
fwriter := func(file io.Writer) (e error) {
|
||||
if len(b.lines) == 0 {
|
||||
return
|
||||
b.UpdateModTime()
|
||||
}
|
||||
|
||||
// end of line
|
||||
var eol []byte
|
||||
if b.Endings == FFDos {
|
||||
eol = []byte{'\r', '\n'}
|
||||
} else {
|
||||
eol = []byte{'\n'}
|
||||
}
|
||||
|
||||
// write lines
|
||||
if fileSize, e = file.Write(b.lines[0].data); e != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, l := range b.lines[1:] {
|
||||
if _, e = file.Write(eol); e != nil {
|
||||
return
|
||||
}
|
||||
if _, e = file.Write(l.data); e != nil {
|
||||
return
|
||||
}
|
||||
fileSize += len(eol) + len(l.data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err = overwriteFile(absFilename, enc, fwriter, withSudo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !b.Settings["fastdirty"].(bool) {
|
||||
if fileSize > LargeFileThreshold {
|
||||
if result.size > LargeFileThreshold {
|
||||
// For large files 'fastdirty' needs to be on
|
||||
b.Settings["fastdirty"] = true
|
||||
} else {
|
||||
@ -222,10 +322,70 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
|
||||
}
|
||||
}
|
||||
|
||||
newPath := b.Path != filename
|
||||
b.Path = filename
|
||||
absPath, _ := filepath.Abs(filename)
|
||||
b.AbsPath = absPath
|
||||
b.AbsPath = absFilename
|
||||
b.isModified = false
|
||||
b.UpdateRules()
|
||||
b.UpdateModTime()
|
||||
|
||||
if newPath {
|
||||
// need to update glob-based and filetype-based settings
|
||||
b.ReloadSettings(true)
|
||||
}
|
||||
|
||||
err = b.Serialize()
|
||||
return err
|
||||
}
|
||||
|
||||
// safeWrite writes the buffer to a file in a "safe" way, preventing loss of the
|
||||
// contents of the file if it fails to write the new contents.
|
||||
// This means that the file is not overwritten directly but by writing to the
|
||||
// backup file first.
|
||||
func (b *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 (
|
||||
"regexp"
|
||||
"unicode/utf8"
|
||||
|
||||
"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) {
|
||||
lastcn := util.CharacterCount(b.LineBytes(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++ {
|
||||
l := b.LineBytes(i)
|
||||
charpos := 0
|
||||
l, charpos, padMode, rPadded := findLineParams(b, start, end, i, r)
|
||||
|
||||
if i == start.Y && start.Y == end.Y {
|
||||
nchars := util.CharacterCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars)
|
||||
end.X = util.Clamp(end.X, 0, nchars)
|
||||
l = util.SliceStart(l, end.X)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == start.Y {
|
||||
nchars := util.CharacterCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == end.Y {
|
||||
nchars := util.CharacterCount(l)
|
||||
end.X = util.Clamp(end.X, 0, nchars)
|
||||
l = util.SliceStart(l, end.X)
|
||||
}
|
||||
|
||||
match := r.FindIndex(l)
|
||||
match := rPadded.FindIndex(l)
|
||||
|
||||
if match != nil {
|
||||
if padMode&padStart != 0 {
|
||||
_, size := utf8.DecodeRune(l[match[0]:])
|
||||
match[0] += size
|
||||
}
|
||||
if padMode&padEnd != 0 {
|
||||
_, size := utf8.DecodeLastRune(l[:match[1]])
|
||||
match[1] -= size
|
||||
}
|
||||
start := Loc{charpos + util.RunePos(l, match[0]), i}
|
||||
end := Loc{charpos + util.RunePos(l, match[1]), i}
|
||||
return [2]Loc{start, end}, true
|
||||
@ -70,39 +105,39 @@ func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||
}
|
||||
|
||||
for i := end.Y; i >= start.Y; i-- {
|
||||
l := b.LineBytes(i)
|
||||
charpos := 0
|
||||
|
||||
if i == start.Y && start.Y == end.Y {
|
||||
nchars := util.CharacterCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars)
|
||||
end.X = util.Clamp(end.X, 0, nchars)
|
||||
l = util.SliceStart(l, end.X)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == start.Y {
|
||||
nchars := util.CharacterCount(l)
|
||||
start.X = util.Clamp(start.X, 0, nchars)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == end.Y {
|
||||
nchars := util.CharacterCount(l)
|
||||
end.X = util.Clamp(end.X, 0, nchars)
|
||||
l = util.SliceStart(l, end.X)
|
||||
}
|
||||
|
||||
allMatches := r.FindAllIndex(l, -1)
|
||||
charCount := util.CharacterCount(b.LineBytes(i))
|
||||
from := Loc{0, i}.Clamp(start, end)
|
||||
to := Loc{charCount, i}.Clamp(start, end)
|
||||
|
||||
allMatches := b.findAll(r, from, to)
|
||||
if allMatches != nil {
|
||||
match := allMatches[len(allMatches)-1]
|
||||
start := Loc{charpos + util.RunePos(l, match[0]), i}
|
||||
end := Loc{charpos + util.RunePos(l, match[1]), i}
|
||||
return [2]Loc{start, end}, true
|
||||
return [2]Loc{match[0], match[1]}, true
|
||||
}
|
||||
}
|
||||
return [2]Loc{}, false
|
||||
}
|
||||
|
||||
func (b *Buffer) findAll(r *regexp.Regexp, start, end Loc) [][2]Loc {
|
||||
var matches [][2]Loc
|
||||
loc := start
|
||||
for {
|
||||
match, found := b.findDown(r, loc, end)
|
||||
if !found {
|
||||
break
|
||||
}
|
||||
matches = append(matches, match)
|
||||
if match[0] != match[1] {
|
||||
loc = match[1]
|
||||
} else if match[1] != end {
|
||||
loc = match[1].Move(1, b)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
// FindNext finds the next occurrence of a given string in the buffer
|
||||
// It returns the start and end location of the match (if found) and
|
||||
// a boolean indicating if it was found
|
||||
@ -146,53 +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
|
||||
// and returns the number of replacements made and the number of runes
|
||||
// and returns the number of replacements made and the number of characters
|
||||
// added or removed on the last line of the range
|
||||
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte, captureGroups bool) (int, int) {
|
||||
if start.GreaterThan(end) {
|
||||
start, end = end, start
|
||||
}
|
||||
|
||||
netrunes := 0
|
||||
|
||||
charsEnd := util.CharacterCount(b.LineBytes(end.Y))
|
||||
found := 0
|
||||
var deltas []Delta
|
||||
|
||||
for i := start.Y; i <= end.Y; i++ {
|
||||
l := b.lines[i].data
|
||||
charpos := 0
|
||||
l := b.LineBytes(i)
|
||||
charCount := util.CharacterCount(l)
|
||||
if (i == start.Y && start.X > 0) || (i == end.Y && end.X < charCount) {
|
||||
// This replacement code works in general, but it creates a separate
|
||||
// modification for each match. We only use it for the first and last
|
||||
// lines, which may use padded regexps
|
||||
|
||||
if start.Y == end.Y && i == start.Y {
|
||||
l = util.SliceStart(l, end.X)
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == start.Y {
|
||||
l = util.SliceEnd(l, start.X)
|
||||
charpos = start.X
|
||||
} else if i == end.Y {
|
||||
l = util.SliceStart(l, end.X)
|
||||
}
|
||||
newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
|
||||
var result []byte
|
||||
if captureGroups {
|
||||
for _, submatches := range search.FindAllSubmatchIndex(in, -1) {
|
||||
result = search.Expand(result, replace, in, submatches)
|
||||
from := Loc{0, i}.Clamp(start, end)
|
||||
to := Loc{charCount, i}.Clamp(start, end)
|
||||
matches := b.findAll(search, from, to)
|
||||
found += len(matches)
|
||||
|
||||
for j := len(matches) - 1; j >= 0; j-- {
|
||||
// if we counted upwards, the different deltas would interfere
|
||||
match := matches[j]
|
||||
var newText []byte
|
||||
if captureGroups {
|
||||
newText = search.ReplaceAll(b.Substr(match[0], match[1]), replace)
|
||||
} else {
|
||||
newText = replace
|
||||
}
|
||||
} else {
|
||||
result = replace
|
||||
deltas = append(deltas, Delta{newText, match[0], match[1]})
|
||||
}
|
||||
found++
|
||||
if i == end.Y {
|
||||
netrunes += util.CharacterCount(result) - util.CharacterCount(in)
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
from := Loc{charpos, i}
|
||||
to := Loc{charpos + util.CharacterCount(l), i}
|
||||
|
||||
deltas = append(deltas, Delta{newText, from, to})
|
||||
} else {
|
||||
newLine := search.ReplaceAllFunc(l, func(in []byte) []byte {
|
||||
found++
|
||||
var result []byte
|
||||
if captureGroups {
|
||||
match := search.FindSubmatchIndex(in)
|
||||
result = search.Expand(result, replace, in, match)
|
||||
} else {
|
||||
result = replace
|
||||
}
|
||||
return result
|
||||
})
|
||||
deltas = append(deltas, Delta{newLine, Loc{0, i}, Loc{charCount, i}})
|
||||
}
|
||||
}
|
||||
|
||||
b.MultipleReplace(deltas)
|
||||
|
||||
return found, netrunes
|
||||
return found, util.CharacterCount(b.LineBytes(end.Y)) - charsEnd
|
||||
}
|
||||
|
@ -1,15 +1,13 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/encoding"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
@ -31,16 +29,18 @@ func (b *Buffer) Serialize() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath))
|
||||
|
||||
return overwriteFile(name, encoding.Nop, func(file io.Writer) error {
|
||||
err := gob.NewEncoder(file).Encode(SerializedBuffer{
|
||||
b.EventHandler,
|
||||
b.GetActiveCursor().Loc,
|
||||
b.ModTime,
|
||||
})
|
||||
var buf bytes.Buffer
|
||||
err := gob.NewEncoder(&buf).Encode(SerializedBuffer{
|
||||
b.EventHandler,
|
||||
b.GetActiveCursor().Loc,
|
||||
b.ModTime,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}, false)
|
||||
}
|
||||
|
||||
name := 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
|
||||
@ -50,7 +50,7 @@ func (b *Buffer) Unserialize() error {
|
||||
if b.Path == "" {
|
||||
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 {
|
||||
defer file.Close()
|
||||
var buffer SerializedBuffer
|
||||
|
@ -5,13 +5,22 @@ import (
|
||||
"reflect"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"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) {
|
||||
settings := config.ParsedSettings()
|
||||
config.UpdatePathGlobLocals(settings, b.AbsPath)
|
||||
|
||||
if _, ok := b.LocalSettings["filetype"]; !ok && reloadFiletype {
|
||||
oldFiletype := b.Settings["filetype"].(string)
|
||||
|
||||
_, local := b.LocalSettings["filetype"]
|
||||
_, volatile := config.VolatileSettings["filetype"]
|
||||
if reloadFiletype && !local && !volatile {
|
||||
// need to update filetype before updating other settings based on it
|
||||
b.Settings["filetype"] = "unknown"
|
||||
if v, ok := settings["filetype"]; ok {
|
||||
@ -21,9 +30,14 @@ func (b *Buffer) ReloadSettings(reloadFiletype bool) {
|
||||
|
||||
// update syntax rules, which will also update filetype if needed
|
||||
b.UpdateRules()
|
||||
settings["filetype"] = b.Settings["filetype"]
|
||||
|
||||
config.InitLocalSettings(settings, b.Path)
|
||||
curFiletype := b.Settings["filetype"].(string)
|
||||
if oldFiletype != curFiletype {
|
||||
b.doCallbacks("filetype", oldFiletype, curFiletype)
|
||||
}
|
||||
|
||||
config.UpdateFileTypeLocals(settings, curFiletype)
|
||||
|
||||
for k, v := range config.DefaultCommonSettings() {
|
||||
if k == "filetype" {
|
||||
// prevent recursion
|
||||
@ -46,7 +60,8 @@ func (b *Buffer) ReloadSettings(reloadFiletype bool) {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -84,6 +99,12 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
|
||||
b.UpdateRules()
|
||||
}
|
||||
} 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
|
||||
} else if option == "readonly" && b.Type.Kind == BTDefault.Kind {
|
||||
b.Type.Readonly = nativeValue.(bool)
|
||||
@ -114,9 +135,7 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
if b.OptionCallback != nil {
|
||||
b.OptionCallback(option, nativeValue)
|
||||
}
|
||||
b.doCallbacks(option, oldValue, nativeValue)
|
||||
}
|
||||
|
||||
func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
|
||||
@ -143,3 +162,15 @@ func (b *Buffer) SetOption(option, value string) error {
|
||||
|
||||
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"
|
||||
"time"
|
||||
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
type terminalClipboard struct{}
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
)
|
||||
|
||||
// DefStyle is Micro's default style
|
||||
|
@ -3,8 +3,8 @@ package config
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
func TestSimpleStringToStyle(t *testing.T) {
|
||||
|
@ -71,7 +71,7 @@ type Plugin struct {
|
||||
Info *PluginInfo // json file containing info
|
||||
Srcs []RuntimeFile // lua files
|
||||
Loaded bool
|
||||
Default bool // pre-installed plugin
|
||||
Default bool // pre-installed plugin
|
||||
}
|
||||
|
||||
// IsLoaded returns if a plugin is enabled
|
||||
@ -143,15 +143,3 @@ func FindPlugin(name string) *Plugin {
|
||||
}
|
||||
return pl
|
||||
}
|
||||
|
||||
// FindAnyPlugin does not require the plugin to be enabled
|
||||
func FindAnyPlugin(name string) *Plugin {
|
||||
var pl *Plugin
|
||||
for _, p := range Plugins {
|
||||
if p.Name == name {
|
||||
pl = p
|
||||
break
|
||||
}
|
||||
}
|
||||
return pl
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -14,8 +13,8 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/micro-editor/json5"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/json5"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
@ -396,7 +395,7 @@ func (pv *PluginVersion) DownloadAndInstall(out io.Writer) error {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/blang/semver"
|
||||
|
||||
"github.com/zyedidia/json5"
|
||||
"github.com/micro-editor/json5"
|
||||
)
|
||||
|
||||
func TestDependencyResolving(t *testing.T) {
|
||||
|
@ -2,10 +2,8 @@ package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
@ -62,12 +60,6 @@ type realFile string
|
||||
// some asset file
|
||||
type assetFile string
|
||||
|
||||
// some file on filesystem but with a different name
|
||||
type namedFile struct {
|
||||
realFile
|
||||
name string
|
||||
}
|
||||
|
||||
// a file with the data stored in memory
|
||||
type memoryFile struct {
|
||||
name string
|
||||
@ -87,22 +79,18 @@ func (rf realFile) Name() string {
|
||||
}
|
||||
|
||||
func (rf realFile) Data() ([]byte, error) {
|
||||
return ioutil.ReadFile(string(rf))
|
||||
return os.ReadFile(string(rf))
|
||||
}
|
||||
|
||||
func (af assetFile) Name() string {
|
||||
fn := path.Base(string(af))
|
||||
return fn[:len(fn)-len(path.Ext(fn))]
|
||||
fn := filepath.Base(string(af))
|
||||
return fn[:len(fn)-len(filepath.Ext(fn))]
|
||||
}
|
||||
|
||||
func (af assetFile) Data() ([]byte, error) {
|
||||
return rt.Asset(string(af))
|
||||
}
|
||||
|
||||
func (nf namedFile) Name() string {
|
||||
return nf.name
|
||||
}
|
||||
|
||||
// AddRuntimeFile registers a file for the given filetype
|
||||
func AddRuntimeFile(fileType RTFiletype, file RuntimeFile) {
|
||||
allFiles[fileType] = append(allFiles[fileType], file)
|
||||
@ -117,7 +105,7 @@ func AddRealRuntimeFile(fileType RTFiletype, file RuntimeFile) {
|
||||
// AddRuntimeFilesFromDirectory registers each file from the given directory for
|
||||
// the filetype which matches the file-pattern
|
||||
func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string) {
|
||||
files, _ := ioutil.ReadDir(directory)
|
||||
files, _ := os.ReadDir(directory)
|
||||
for _, f := range files {
|
||||
if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
|
||||
fullPath := filepath.Join(directory, f.Name())
|
||||
@ -136,8 +124,8 @@ func AddRuntimeFilesFromAssets(fileType RTFiletype, directory, pattern string) {
|
||||
|
||||
assetLoop:
|
||||
for _, f := range files {
|
||||
if ok, _ := path.Match(pattern, f); ok {
|
||||
af := assetFile(path.Join(directory, f))
|
||||
if ok, _ := filepath.Match(pattern, f); ok {
|
||||
af := assetFile(filepath.Join(directory, f))
|
||||
for _, rf := range realFiles[fileType] {
|
||||
if af.Name() == rf.Name() {
|
||||
continue assetLoop
|
||||
@ -178,7 +166,7 @@ func InitRuntimeFiles(user bool) {
|
||||
if user {
|
||||
AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
|
||||
}
|
||||
AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
|
||||
AddRuntimeFilesFromAssets(fileType, filepath.Join("runtime", dir), pattern)
|
||||
}
|
||||
|
||||
initRuntimeVars()
|
||||
@ -204,14 +192,14 @@ func InitPlugins() {
|
||||
|
||||
// Search ConfigDir for plugin-scripts
|
||||
plugdir := filepath.Join(ConfigDir, "plug")
|
||||
files, _ := ioutil.ReadDir(plugdir)
|
||||
files, _ := os.ReadDir(plugdir)
|
||||
|
||||
isID := regexp.MustCompile(`^[_A-Za-z0-9]+$`).MatchString
|
||||
|
||||
for _, d := range files {
|
||||
plugpath := filepath.Join(plugdir, d.Name())
|
||||
if stat, err := os.Stat(plugpath); err == nil && stat.IsDir() {
|
||||
srcs, _ := ioutil.ReadDir(plugpath)
|
||||
srcs, _ := os.ReadDir(plugpath)
|
||||
p := new(Plugin)
|
||||
p.Name = d.Name()
|
||||
p.DirName = d.Name()
|
||||
@ -219,7 +207,7 @@ func InitPlugins() {
|
||||
if strings.HasSuffix(f.Name(), ".lua") {
|
||||
p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
|
||||
} else if strings.HasSuffix(f.Name(), ".json") {
|
||||
data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
|
||||
data, err := os.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@ -311,7 +299,7 @@ func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) e
|
||||
if _, err := os.Stat(fullpath); err == nil {
|
||||
AddRealRuntimeFile(filetype, realFile(fullpath))
|
||||
} else {
|
||||
fullpath = path.Join("runtime", "plugins", pldir, filePath)
|
||||
fullpath = filepath.Join("runtime", "plugins", pldir, filePath)
|
||||
AddRuntimeFile(filetype, assetFile(fullpath))
|
||||
}
|
||||
return nil
|
||||
@ -328,7 +316,7 @@ func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, dire
|
||||
if _, err := os.Stat(fullpath); err == nil {
|
||||
AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
|
||||
} else {
|
||||
fullpath = path.Join("runtime", "plugins", pldir, directory)
|
||||
fullpath = filepath.Join("runtime", "plugins", pldir, directory)
|
||||
AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
|
||||
}
|
||||
return nil
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
@ -12,8 +11,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/micro-editor/json5"
|
||||
"github.com/zyedidia/glob"
|
||||
"github.com/zyedidia/json5"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"golang.org/x/text/encoding/htmlindex"
|
||||
)
|
||||
@ -29,21 +28,26 @@ var optionValidators = map[string]optionValidator{
|
||||
"detectlimit": validateNonNegativeValue,
|
||||
"encoding": validateEncoding,
|
||||
"fileformat": validateChoice,
|
||||
"helpsplit": validateChoice,
|
||||
"matchbracestyle": validateChoice,
|
||||
"multiopen": validateChoice,
|
||||
"pageoverlap": validateNonNegativeValue,
|
||||
"reload": validateChoice,
|
||||
"scrollmargin": validateNonNegativeValue,
|
||||
"scrollspeed": validateNonNegativeValue,
|
||||
"tabsize": validatePositiveValue,
|
||||
"truecolor": validateChoice,
|
||||
}
|
||||
|
||||
// a list of settings with pre-defined choices
|
||||
var OptionChoices = map[string][]string{
|
||||
"clipboard": {"internal", "external", "terminal"},
|
||||
"fileformat": {"unix", "dos"},
|
||||
"helpsplit": {"hsplit", "vsplit"},
|
||||
"matchbracestyle": {"underline", "highlight"},
|
||||
"multiopen": {"tab", "hsplit", "vsplit"},
|
||||
"reload": {"prompt", "auto", "disabled"},
|
||||
"truecolor": {"auto", "on", "off"},
|
||||
}
|
||||
|
||||
// a list of settings that can be globally and locally modified and their
|
||||
@ -66,20 +70,21 @@ var defaultCommonSettings = map[string]interface{}{
|
||||
"hlsearch": false,
|
||||
"hltaberrors": false,
|
||||
"hltrailingws": false,
|
||||
"incsearch": true,
|
||||
"ignorecase": true,
|
||||
"incsearch": true,
|
||||
"indentchar": " ",
|
||||
"keepautoindent": false,
|
||||
"matchbrace": true,
|
||||
"matchbraceleft": true,
|
||||
"matchbracestyle": "underline",
|
||||
"mkparents": false,
|
||||
"pageoverlap": float64(2),
|
||||
"permbackup": false,
|
||||
"readonly": false,
|
||||
"relativeruler": false,
|
||||
"reload": "prompt",
|
||||
"rmtrailingws": false,
|
||||
"ruler": true,
|
||||
"relativeruler": false,
|
||||
"savecursor": false,
|
||||
"saveundo": false,
|
||||
"scrollbar": false,
|
||||
@ -89,13 +94,14 @@ var defaultCommonSettings = map[string]interface{}{
|
||||
"softwrap": false,
|
||||
"splitbottom": true,
|
||||
"splitright": true,
|
||||
"statusformatl": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
|
||||
"statusformatl": "$(filename) $(modified)$(overwrite)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
|
||||
"statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
|
||||
"statusline": true,
|
||||
"syntax": true,
|
||||
"tabmovement": false,
|
||||
"tabsize": float64(4),
|
||||
"tabstospaces": false,
|
||||
"truecolor": "auto",
|
||||
"useprimary": true,
|
||||
"wordwrap": false,
|
||||
}
|
||||
@ -109,6 +115,7 @@ var DefaultGlobalOnlySettings = map[string]interface{}{
|
||||
"divchars": "|-",
|
||||
"divreverse": true,
|
||||
"fakecursor": false,
|
||||
"helpsplit": "hsplit",
|
||||
"infobar": true,
|
||||
"keymenu": false,
|
||||
"mouse": true,
|
||||
@ -151,6 +158,10 @@ var (
|
||||
VolatileSettings map[string]bool
|
||||
)
|
||||
|
||||
func writeFile(name string, txt []byte) error {
|
||||
return util.SafeWrite(name, txt, false)
|
||||
}
|
||||
|
||||
func init() {
|
||||
ModifiedSettings = make(map[string]bool)
|
||||
VolatileSettings = make(map[string]bool)
|
||||
@ -217,7 +228,7 @@ func ReadSettings() error {
|
||||
parsedSettings = make(map[string]interface{})
|
||||
filename := filepath.Join(ConfigDir, "settings.json")
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
input, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
settingsParseError = true
|
||||
return errors.New("Error reading settings.json file: " + err.Error())
|
||||
@ -283,22 +294,31 @@ func InitGlobalSettings() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// InitLocalSettings scans the json in settings.json and sets the options locally based
|
||||
// on whether the filetype or path matches ft or glob local settings
|
||||
// UpdatePathGlobLocals scans the already parsed settings and sets the options locally
|
||||
// based on whether the path matches a glob
|
||||
// Must be called after ReadSettings
|
||||
func InitLocalSettings(settings map[string]interface{}, path string) {
|
||||
func UpdatePathGlobLocals(settings map[string]interface{}, path string) {
|
||||
for k, v := range parsedSettings {
|
||||
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
||||
if strings.HasPrefix(k, "ft:") {
|
||||
if settings["filetype"].(string) == k[3:] {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
settings[k1] = v1
|
||||
}
|
||||
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") && !strings.HasPrefix(k, "ft:") {
|
||||
g, _ := glob.Compile(k)
|
||||
if g.MatchString(path) {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
settings[k1] = v1
|
||||
}
|
||||
} else {
|
||||
g, _ := glob.Compile(k)
|
||||
if g.MatchString(path) {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateFileTypeLocals scans the already parsed settings and sets the options locally
|
||||
// based on whether the filetype matches to "ft:"
|
||||
// Must be called after ReadSettings
|
||||
func UpdateFileTypeLocals(settings map[string]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{}) {
|
||||
if k1 != "filetype" {
|
||||
settings[k1] = v1
|
||||
}
|
||||
}
|
||||
@ -342,7 +362,8 @@ func WriteSettings(filename string) error {
|
||||
}
|
||||
|
||||
txt, _ := json.MarshalIndent(parsedSettings, "", " ")
|
||||
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
||||
txt = append(txt, '\n')
|
||||
err = writeFile(filename, txt)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -363,8 +384,9 @@ func OverwriteSettings(filename string) error {
|
||||
}
|
||||
}
|
||||
|
||||
txt, _ := json.MarshalIndent(settings, "", " ")
|
||||
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
||||
txt, _ := json.MarshalIndent(parsedSettings, "", " ")
|
||||
txt = append(txt, '\n')
|
||||
err = writeFile(filename, txt)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -4,11 +4,11 @@ import (
|
||||
"strconv"
|
||||
|
||||
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/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"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.
|
||||
@ -58,9 +58,15 @@ func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
|
||||
if option == "softwrap" || option == "wordwrap" {
|
||||
w.Relocate()
|
||||
for _, c := range w.Buf.GetCursors() {
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
c.LastWrappedVisualX = c.GetVisualX(true)
|
||||
}
|
||||
}
|
||||
|
||||
if option == "diffgutter" || option == "ruler" || option == "scrollbar" ||
|
||||
option == "statusline" {
|
||||
w.updateDisplayInfo()
|
||||
w.Relocate()
|
||||
}
|
||||
}
|
||||
b.GetVisualX = func(loc buffer.Loc) int {
|
||||
return w.VLocFromLoc(loc).VisualX
|
||||
@ -160,7 +166,7 @@ func (w *BufWindow) updateDisplayInfo() {
|
||||
|
||||
if w.bufWidth != prevBufWidth && w.Buf.Settings["softwrap"].(bool) {
|
||||
for _, c := range w.Buf.GetCursors() {
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
c.LastWrappedVisualX = c.GetVisualX(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -238,7 +244,7 @@ func (w *BufWindow) Relocate() bool {
|
||||
|
||||
// horizontal relocation (scrolling)
|
||||
if !b.Settings["softwrap"].(bool) {
|
||||
cx := activeC.GetVisualX()
|
||||
cx := activeC.GetVisualX(false)
|
||||
rw := runewidth.RuneWidth(activeC.RuneUnder(activeC.X))
|
||||
if rw == 0 {
|
||||
rw = 1 // tab or newline
|
||||
@ -248,8 +254,8 @@ func (w *BufWindow) Relocate() bool {
|
||||
w.StartCol = cx
|
||||
ret = true
|
||||
}
|
||||
if cx+w.gutterOffset+rw > w.StartCol+w.Width {
|
||||
w.StartCol = cx - w.Width + w.gutterOffset + rw
|
||||
if cx+rw > w.StartCol+w.bufWidth {
|
||||
w.StartCol = cx - w.bufWidth + rw
|
||||
ret = true
|
||||
}
|
||||
}
|
||||
@ -449,7 +455,7 @@ func (w *BufWindow) displayBuffer() {
|
||||
|
||||
currentLine := false
|
||||
for _, c := range cursors {
|
||||
if bloc.Y == c.Y && w.active {
|
||||
if !c.HasSelection() && bloc.Y == c.Y && w.active {
|
||||
currentLine = true
|
||||
break
|
||||
}
|
||||
@ -619,16 +625,21 @@ func (w *BufWindow) displayBuffer() {
|
||||
|
||||
wrap := func() {
|
||||
vloc.X = 0
|
||||
if w.hasMessage {
|
||||
w.drawGutter(&vloc, &bloc)
|
||||
}
|
||||
if b.Settings["diffgutter"].(bool) {
|
||||
w.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)
|
||||
}
|
||||
|
||||
// This will draw an empty line number because the current line is wrapped
|
||||
if b.Settings["ruler"].(bool) {
|
||||
w.drawLineNum(lineNumStyle, true, &vloc, &bloc)
|
||||
if vloc.Y >= 0 {
|
||||
if w.hasMessage {
|
||||
w.drawGutter(&vloc, &bloc)
|
||||
}
|
||||
if b.Settings["diffgutter"].(bool) {
|
||||
w.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)
|
||||
}
|
||||
|
||||
// This will draw an empty line number because the current line is wrapped
|
||||
if b.Settings["ruler"].(bool) {
|
||||
w.drawLineNum(lineNumStyle, true, &vloc, &bloc)
|
||||
}
|
||||
} else {
|
||||
vloc.X = w.gutterOffset
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,12 +2,12 @@ package display
|
||||
|
||||
import (
|
||||
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/config"
|
||||
"github.com/zyedidia/micro/v2/internal/info"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
type InfoWindow struct {
|
||||
|
@ -291,13 +291,7 @@ func (w *BufWindow) diff(s1, s2 SLoc) int {
|
||||
// within the buffer boundaries.
|
||||
func (w *BufWindow) Scroll(s SLoc, n int) SLoc {
|
||||
if !w.Buf.Settings["softwrap"].(bool) {
|
||||
s.Line += n
|
||||
if s.Line < 0 {
|
||||
s.Line = 0
|
||||
}
|
||||
if s.Line > w.Buf.LinesNum()-1 {
|
||||
s.Line = w.Buf.LinesNum() - 1
|
||||
}
|
||||
s.Line = util.Clamp(s.Line+n, 0, w.Buf.LinesNum()-1)
|
||||
return s
|
||||
}
|
||||
return w.scroll(s, n)
|
||||
|
@ -47,6 +47,12 @@ var statusInfo = map[string]func(*buffer.Buffer) string{
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"overwrite": func(b *buffer.Buffer) string {
|
||||
if b.OverwriteMode && !b.Type.Readonly {
|
||||
return "[ovwr] "
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"lines": func(b *buffer.Buffer) string {
|
||||
return strconv.Itoa(b.LinesNum())
|
||||
},
|
||||
|
@ -2,7 +2,7 @@ package display
|
||||
|
||||
import (
|
||||
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/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
@ -112,10 +112,10 @@ func (w *TabWindow) Display() {
|
||||
}
|
||||
return tabBarStyle, tabBarActiveStyle
|
||||
}
|
||||
|
||||
|
||||
draw := func(r rune, n int, active bool, reversed bool) {
|
||||
tabBarStyle, tabBarActiveStyle := reverseStyles(reversed)
|
||||
|
||||
|
||||
style := tabBarStyle
|
||||
if active {
|
||||
style = tabBarActiveStyle
|
||||
@ -147,15 +147,15 @@ func (w *TabWindow) Display() {
|
||||
} else {
|
||||
draw(' ', 1, false, tabCharHighlight)
|
||||
}
|
||||
|
||||
|
||||
for _, c := range n {
|
||||
draw(c, 1, i == w.active, tabCharHighlight)
|
||||
}
|
||||
|
||||
|
||||
if i == len(w.Names)-1 {
|
||||
done = true
|
||||
}
|
||||
|
||||
|
||||
if i == w.active {
|
||||
draw(']', 1, true, tabCharHighlight)
|
||||
draw(' ', 2, true, globalTabReverse)
|
||||
@ -163,7 +163,7 @@ func (w *TabWindow) Display() {
|
||||
draw(' ', 1, false, tabCharHighlight)
|
||||
draw(' ', 2, false, globalTabReverse)
|
||||
}
|
||||
|
||||
|
||||
if x >= w.Width {
|
||||
break
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
"github.com/micro-editor/terminal"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/zyedidia/terminal"
|
||||
)
|
||||
|
||||
type TermWindow struct {
|
||||
|
@ -1,12 +1,16 @@
|
||||
package info
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
@ -17,24 +21,23 @@ func (i *InfoBuf) LoadHistory() {
|
||||
if config.GetGlobalOption("savehistory").(bool) {
|
||||
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", "history"))
|
||||
var decodedMap map[string][]string
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
decoder := gob.NewDecoder(file)
|
||||
err = decoder.Decode(&decodedMap)
|
||||
|
||||
if err != nil {
|
||||
i.Error("Error loading history:", err)
|
||||
return
|
||||
if err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
i.Error("Error loading history: ", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
err = gob.NewDecoder(file).Decode(&decodedMap)
|
||||
if err != nil {
|
||||
i.Error("Error decoding history: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
if decodedMap != nil {
|
||||
i.History = decodedMap
|
||||
} else {
|
||||
i.History = make(map[string][]string)
|
||||
}
|
||||
} else {
|
||||
i.History = make(map[string][]string)
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,16 +52,18 @@ func (i *InfoBuf) SaveHistory() {
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Create(filepath.Join(config.ConfigDir, "buffers", "history"))
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
encoder := gob.NewEncoder(file)
|
||||
var buf bytes.Buffer
|
||||
err := gob.NewEncoder(&buf).Encode(i.History)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error encoding history: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = encoder.Encode(i.History)
|
||||
if err != nil {
|
||||
i.Error("Error saving history:", err)
|
||||
return
|
||||
}
|
||||
filename := filepath.Join(config.ConfigDir, "buffers", "history")
|
||||
err = util.SafeWrite(filename, buf.Bytes(), true)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error saving history: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -125,6 +125,7 @@ func importIo() *lua.LTable {
|
||||
L.SetField(pkg, "MultiWriter", luar.New(L, io.MultiWriter))
|
||||
L.SetField(pkg, "NewSectionReader", luar.New(L, io.NewSectionReader))
|
||||
L.SetField(pkg, "Pipe", luar.New(L, io.Pipe))
|
||||
L.SetField(pkg, "ReadAll", luar.New(L, io.ReadAll))
|
||||
L.SetField(pkg, "ReadAtLeast", luar.New(L, io.ReadAtLeast))
|
||||
L.SetField(pkg, "ReadFull", luar.New(L, io.ReadFull))
|
||||
L.SetField(pkg, "TeeReader", luar.New(L, io.TeeReader))
|
||||
@ -370,6 +371,8 @@ func importOs() *lua.LTable {
|
||||
L.SetField(pkg, "PathListSeparator", luar.New(L, os.PathListSeparator))
|
||||
L.SetField(pkg, "PathSeparator", luar.New(L, os.PathSeparator))
|
||||
L.SetField(pkg, "Pipe", luar.New(L, os.Pipe))
|
||||
L.SetField(pkg, "ReadDir", luar.New(L, os.ReadDir))
|
||||
L.SetField(pkg, "ReadFile", luar.New(L, os.ReadFile))
|
||||
L.SetField(pkg, "Readlink", luar.New(L, os.Readlink))
|
||||
L.SetField(pkg, "Remove", luar.New(L, os.Remove))
|
||||
L.SetField(pkg, "RemoveAll", luar.New(L, os.RemoveAll))
|
||||
@ -388,6 +391,7 @@ func importOs() *lua.LTable {
|
||||
L.SetField(pkg, "TempDir", luar.New(L, os.TempDir))
|
||||
L.SetField(pkg, "Truncate", luar.New(L, os.Truncate))
|
||||
L.SetField(pkg, "UserHomeDir", luar.New(L, os.UserHomeDir))
|
||||
L.SetField(pkg, "WriteFile", luar.New(L, os.WriteFile))
|
||||
|
||||
return pkg
|
||||
}
|
||||
@ -423,7 +427,6 @@ func importPath() *lua.LTable {
|
||||
func importFilePath() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Join", luar.New(L, filepath.Join))
|
||||
L.SetField(pkg, "Abs", luar.New(L, filepath.Abs))
|
||||
L.SetField(pkg, "Base", luar.New(L, filepath.Base))
|
||||
L.SetField(pkg, "Clean", luar.New(L, filepath.Clean))
|
||||
|
@ -6,9 +6,9 @@ import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/micro-editor/tcell/v2"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
// 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
|
||||
var drawChan chan bool
|
||||
|
||||
// rawSeq is the list of raw escape sequences that are bound to some actions
|
||||
// via keybindings and thus should be parsed by tcell. We need to register
|
||||
// them in tcell every time we reinitialize the screen, so we need to remember
|
||||
// them in a list
|
||||
var rawSeq = make([]string, 0)
|
||||
|
||||
// Lock locks the screen lock
|
||||
func Lock() {
|
||||
lock.Lock()
|
||||
@ -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
|
||||
func TempFini() bool {
|
||||
screenWasNil := Screen == nil
|
||||
@ -150,10 +184,13 @@ func Init() error {
|
||||
drawChan = make(chan bool, 8)
|
||||
|
||||
// Should we enable true color?
|
||||
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
|
||||
|
||||
if !truecolor {
|
||||
truecolor := config.GetGlobalOption("truecolor").(string)
|
||||
if truecolor == "on" || (truecolor == "auto" && os.Getenv("MICRO_TRUECOLOR") == "1") {
|
||||
os.Setenv("TCELL_TRUECOLOR", "enable")
|
||||
} else if truecolor == "off" {
|
||||
os.Setenv("TCELL_TRUECOLOR", "disable")
|
||||
} else {
|
||||
// For "auto", tcell already autodetects truecolor by default
|
||||
}
|
||||
|
||||
var oldTerm string
|
||||
@ -195,6 +232,10 @@ func Init() error {
|
||||
Screen.EnableMouse()
|
||||
}
|
||||
|
||||
for _, r := range rawSeq {
|
||||
Screen.RegisterRawSeq(r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -78,8 +78,10 @@ func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(
|
||||
go func() {
|
||||
// Run the process in the background and create the onExit callback
|
||||
proc.Run()
|
||||
jobFunc := JobFunction{onExit, outbuf.String(), userargs}
|
||||
Jobs <- jobFunc
|
||||
if onExit != nil {
|
||||
jobFunc := JobFunction{onExit, outbuf.String(), userargs}
|
||||
Jobs <- jobFunc
|
||||
}
|
||||
}()
|
||||
|
||||
return &Job{proc, stdin}
|
||||
|
@ -5,9 +5,9 @@ import (
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
||||
"github.com/micro-editor/terminal"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/terminal"
|
||||
)
|
||||
|
||||
type TermType int
|
||||
|
@ -6,7 +6,9 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
@ -43,8 +45,44 @@ var (
|
||||
Stdout *bytes.Buffer
|
||||
// Sigterm is a channel where micro exits when written
|
||||
Sigterm chan os.Signal
|
||||
|
||||
// To be used for fails on (over-)write with safe writes
|
||||
ErrOverwrite = OverwriteError{}
|
||||
)
|
||||
|
||||
// To be used for file writes before umask is applied
|
||||
const FileMode os.FileMode = 0666
|
||||
|
||||
const OverwriteFailMsg = `An error occurred while writing to the file:
|
||||
|
||||
%s
|
||||
|
||||
The file may be corrupted now. The good news is that it has been
|
||||
successfully backed up. Next time you open this file with Micro,
|
||||
Micro will ask if you want to recover it from the backup.
|
||||
|
||||
The backup path is:
|
||||
|
||||
%s`
|
||||
|
||||
// OverwriteError is a custom error to add additional information
|
||||
type OverwriteError struct {
|
||||
What error
|
||||
BackupName string
|
||||
}
|
||||
|
||||
func (e OverwriteError) Error() string {
|
||||
return fmt.Sprintf(OverwriteFailMsg, e.What, e.BackupName)
|
||||
}
|
||||
|
||||
func (e OverwriteError) Is(target error) bool {
|
||||
return target == ErrOverwrite
|
||||
}
|
||||
|
||||
func (e OverwriteError) Unwrap() error {
|
||||
return e.What
|
||||
}
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
SemVersion, err = semver.Make(Version)
|
||||
@ -233,18 +271,6 @@ func IsNonWordChar(r rune) bool {
|
||||
return !IsWordChar(r)
|
||||
}
|
||||
|
||||
// IsUpperWordChar returns whether or not a rune is an 'upper word character'
|
||||
// Upper word characters are defined as numbers, upper-case letters or sub-word delimiters
|
||||
func IsUpperWordChar(r rune) bool {
|
||||
return IsUpperAlphanumeric(r) || IsSubwordDelimiter(r)
|
||||
}
|
||||
|
||||
// IsLowerWordChar returns whether or not a rune is a 'lower word character'
|
||||
// Lower word characters are defined as numbers, lower-case letters or sub-word delimiters
|
||||
func IsLowerWordChar(r rune) bool {
|
||||
return IsLowerAlphanumeric(r) || IsSubwordDelimiter(r)
|
||||
}
|
||||
|
||||
// IsSubwordDelimiter returns whether or not a rune is a 'sub-word delimiter character'
|
||||
// i.e. is considered a part of the word and is used as a delimiter between sub-words of the word.
|
||||
// For now the only sub-word delimiter character is '_'.
|
||||
@ -332,6 +358,28 @@ func RunePos(b []byte, i int) int {
|
||||
return CharacterCount(b[:i])
|
||||
}
|
||||
|
||||
// IndexAnyUnquoted returns the first position in s of a character from chars.
|
||||
// Escaped (with backslash) and quoted (with single or double quotes) characters
|
||||
// are ignored. Returns -1 if not successful
|
||||
func IndexAnyUnquoted(s, chars string) int {
|
||||
var e bool
|
||||
var q rune
|
||||
for i, r := range s {
|
||||
if e {
|
||||
e = false
|
||||
} else if (q == 0 || q == '"') && r == '\\' {
|
||||
e = true
|
||||
} else if r == q {
|
||||
q = 0
|
||||
} else if q == 0 && (r == '\'' || r == '"') {
|
||||
q = r
|
||||
} else if q == 0 && strings.IndexRune(chars, r) >= 0 {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// MakeRelative will attempt to make a relative path between path and base
|
||||
func MakeRelative(path, base string) (string, error) {
|
||||
if len(path) > 0 {
|
||||
@ -398,8 +446,17 @@ func GetModTime(path string) (time.Time, error) {
|
||||
return info.ModTime(), nil
|
||||
}
|
||||
|
||||
// EscapePath replaces every path separator in a given path with a %
|
||||
func EscapePath(path string) string {
|
||||
func AppendBackupSuffix(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)
|
||||
if runtime.GOOS == "windows" {
|
||||
// ':' 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, "/", "%")
|
||||
}
|
||||
|
||||
// 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
|
||||
func GetLeadingWhitespace(b []byte) []byte {
|
||||
ws := []byte{}
|
||||
@ -510,11 +585,6 @@ func IsAutocomplete(c rune) bool {
|
||||
return c == '.' || IsWordChar(c)
|
||||
}
|
||||
|
||||
// ParseSpecial replaces escaped ts with '\t'.
|
||||
func ParseSpecial(s string) string {
|
||||
return strings.ReplaceAll(s, "\\t", "\t")
|
||||
}
|
||||
|
||||
// String converts a byte array to a string (for lua plugins)
|
||||
func String(s []byte) string {
|
||||
return string(s)
|
||||
@ -585,3 +655,77 @@ func HttpRequest(method string, url string, headers []string) (resp *http.Respon
|
||||
}
|
||||
return client.Do(req)
|
||||
}
|
||||
|
||||
// SafeWrite writes bytes to a file in a "safe" way, preventing loss of the
|
||||
// contents of the file if it fails to write the new contents.
|
||||
// This means that the file is not overwritten directly but by writing to a
|
||||
// temporary file first.
|
||||
//
|
||||
// If rename is true, write is performed atomically, by renaming the temporary
|
||||
// file to the target file after the data is successfully written to the
|
||||
// temporary file. This guarantees that the file will not remain in a corrupted
|
||||
// state, but it also has limitations, e.g. the file should not be a symlink
|
||||
// (otherwise SafeWrite silently replaces this symlink with a regular file),
|
||||
// the file creation date in Linux is not preserved (since the file inode
|
||||
// changes) etc. Use SafeWrite with rename=true for files that are only created
|
||||
// and used by micro for its own needs and are not supposed to be used directly
|
||||
// by the user.
|
||||
//
|
||||
// If rename is false, write is performed by overwriting the target file after
|
||||
// the data is successfully written to the temporary file.
|
||||
// This means that the target file may remain corrupted if overwrite fails,
|
||||
// but in such case the temporary file is preserved as a backup so the file
|
||||
// can be recovered later. So it is less convenient than atomic write but more
|
||||
// universal. Use SafeWrite with rename=false for files that may be managed
|
||||
// directly by the user, like settings.json and bindings.json.
|
||||
func SafeWrite(path string, bytes []byte, rename bool) error {
|
||||
var err error
|
||||
if _, err = os.Stat(path); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
// Force rename for new files!
|
||||
rename = true
|
||||
}
|
||||
|
||||
var file *os.File
|
||||
if !rename {
|
||||
file, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE, FileMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
}
|
||||
|
||||
tmp := 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
|
||||
func (n *Node) unsplit(i int, h bool) {
|
||||
func (n *Node) unsplit(i int) {
|
||||
copy(n.children[i:], n.children[i+1:])
|
||||
n.children[len(n.children)-1] = nil
|
||||
n.children = n.children[:len(n.children)-1]
|
||||
|
||||
h := n.Kind == STVert
|
||||
nonrs, numr := n.getResizeInfo(h)
|
||||
if numr == 0 {
|
||||
// This means that this was the last child
|
||||
@ -470,18 +471,62 @@ func (n *Node) Unsplit() bool {
|
||||
ind = i
|
||||
}
|
||||
}
|
||||
if n.parent.Kind == STVert {
|
||||
n.parent.unsplit(ind, true)
|
||||
} else {
|
||||
n.parent.unsplit(ind, false)
|
||||
}
|
||||
|
||||
n.parent.unsplit(ind)
|
||||
if n.parent.IsLeaf() {
|
||||
return n.parent.Unsplit()
|
||||
}
|
||||
|
||||
n.parent.flatten()
|
||||
return true
|
||||
}
|
||||
|
||||
// flattens the tree by removing unnecessary intermediate parents that have only one child
|
||||
// and handles the side effect of it
|
||||
func (n *Node) flatten() {
|
||||
if n.parent == nil || len(n.children) != 1 {
|
||||
return
|
||||
}
|
||||
|
||||
ind := 0
|
||||
for i, c := range n.parent.children {
|
||||
if c.id == n.id {
|
||||
ind = i
|
||||
}
|
||||
}
|
||||
|
||||
// Replace current node with its child node to remove chained parent
|
||||
successor := n.children[0]
|
||||
n.parent.children[ind] = successor
|
||||
successor.parent = n.parent
|
||||
|
||||
// Maintain the tree in a consistent state: any child node's kind (horiz vs vert)
|
||||
// should be the opposite of its parent's kind.
|
||||
if successor.IsLeaf() {
|
||||
successor.Kind = n.Kind
|
||||
} else {
|
||||
// If the successor node has children, that means it is a chained parent as well.
|
||||
// Therefore it can be replaced by its own children.
|
||||
origsize := len(n.parent.children)
|
||||
|
||||
// Let's say we have 5 children and want to replace [2] with its children [a] [b] [c]
|
||||
// [0] [1] [2] [3] [4] --> [0] [1] [a] [b] [c] [3] [4]
|
||||
// insertcount will be `3 - 1 = 2` in this case
|
||||
insertcount := len(successor.children) - 1
|
||||
|
||||
n.parent.children = append(n.parent.children, make([]*Node, insertcount)...)
|
||||
copy(n.parent.children[ind+insertcount+1:], n.parent.children[ind+1:origsize])
|
||||
copy(n.parent.children[ind:], successor.children)
|
||||
|
||||
for i := 0; i < len(successor.children); i++ {
|
||||
n.parent.children[ind+i].parent = n.parent
|
||||
}
|
||||
}
|
||||
|
||||
// Update propW and propH since the parent of the children has been updated,
|
||||
// so the children have new siblings
|
||||
n.parent.markSizes()
|
||||
}
|
||||
|
||||
// String returns the string form of the node and all children (used for debugging)
|
||||
func (n *Node) String() string {
|
||||
var strf func(n *Node, ident int) string
|
||||
|
@ -51,19 +51,6 @@ func runePos(p int, str []byte) int {
|
||||
return CharacterCount(str[:p])
|
||||
}
|
||||
|
||||
func combineLineMatch(src, dst LineMatch) LineMatch {
|
||||
for k, v := range src {
|
||||
if g, ok := dst[k]; ok {
|
||||
if g == 0 {
|
||||
dst[k] = v
|
||||
}
|
||||
} else {
|
||||
dst[k] = v
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// A State represents the region at the end of a line
|
||||
type State *region
|
||||
|
||||
@ -175,7 +162,7 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
|
||||
if curRegion.group == curRegion.limitGroup || p.group == curRegion.limitGroup {
|
||||
matches := findAllIndex(p.regex, line)
|
||||
for _, m := range matches {
|
||||
if ((endLoc == nil) || (m[0] < endLoc[0])) {
|
||||
if (endLoc == nil) || (m[0] < endLoc[0]) {
|
||||
for i := m[0]; i < m[1]; i++ {
|
||||
fullHighlights[i] = p.group
|
||||
}
|
||||
|
@ -258,7 +258,7 @@ func ParseDef(f *File, header *Header) (s *Def, err error) {
|
||||
|
||||
if s.rules == nil {
|
||||
// allow empty rules
|
||||
s.rules = new(rules)
|
||||
s.rules = &rules{}
|
||||
}
|
||||
|
||||
return s, err
|
||||
@ -476,10 +476,17 @@ func parseRegion(group string, regionInfo map[interface{}]interface{}, prevRegio
|
||||
r.limitGroup = r.group
|
||||
}
|
||||
|
||||
r.rules, err = parseRules(regionInfo["rules"].([]interface{}), r)
|
||||
// rules are optional
|
||||
if rules, ok := regionInfo["rules"]; ok {
|
||||
r.rules, err = parseRules(rules.([]interface{}), r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if r.rules == nil {
|
||||
// allow empty rules
|
||||
r.rules = &rules{}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
|
@ -24,6 +24,6 @@ color-link gutter-warning "88"
|
||||
color-link cursor-line "229"
|
||||
#color-link color-column "196"
|
||||
color-link current-line-number "246"
|
||||
color-line match-brace "230,22"
|
||||
color-link match-brace "230,22"
|
||||
color-link tab-error "210"
|
||||
color-link trailingws "210"
|
||||
|
@ -47,10 +47,7 @@ color support comes in three flavors.
|
||||
displaying any colorscheme, but it should be noted that the user-configured
|
||||
16-color palette is ignored when using true-color mode (this means the
|
||||
colors while using the terminal emulator will be slightly off). Not all
|
||||
terminals support true color but at this point most do. True color
|
||||
support in micro is off by default but can be enabled by setting the
|
||||
environment variable `MICRO_TRUECOLOR` to 1. In addition your terminal
|
||||
must support it (usually indicated by setting `$COLORTERM` to `truecolor`).
|
||||
terminals support true color but at this point most do (see below).
|
||||
True-color colorschemes in micro typically end with `-tc`, such as
|
||||
`solarized-tc`, `atom-dark`, `material-tc`, etc... If true color is not
|
||||
enabled but a true color colorscheme is used, micro will do its best to
|
||||
@ -84,11 +81,12 @@ These may vary widely based on the 16 colors selected for your terminal.
|
||||
|
||||
### True color
|
||||
|
||||
True color requires your terminal to support it. This means that the
|
||||
environment variable `COLORTERM` should have the value `truecolor`, `24bit`,
|
||||
or `24-bit`. In addition, to enable true color in micro, the environment
|
||||
variable `MICRO_TRUECOLOR` must be set to 1. Note that you have to create
|
||||
and set this variable yourself.
|
||||
Micro enables true color support by default as long as it detects that the
|
||||
terminal supports it (which is usually indicated by the environment variable
|
||||
`COLORTERM` being set to `truecolor`, `24bit` or `24-bit`). You can also force
|
||||
enabling it unconditionally by setting the option `truecolor` to `on` (or
|
||||
alternatively by setting the environment variable `MICRO_TRUECOLOR` to 1, which
|
||||
is supported for backward compatibility).
|
||||
|
||||
* `solarized-tc`: this is the solarized colorscheme for true color.
|
||||
* `atom-dark`: this colorscheme is based off of Atom's "dark" colorscheme.
|
||||
@ -177,10 +175,14 @@ Here is a list of the colorscheme groups that you can use:
|
||||
* todo
|
||||
* selection (Color of the text selection)
|
||||
* statusline (Color of the statusline)
|
||||
* statusline.inactive (Color of the statusline of inactive split panes)
|
||||
* statusline.suggestions (Color of the autocomplete suggestions menu)
|
||||
* tabbar (Color of the tabbar that lists open files)
|
||||
* tabbar.active (Color of the active tab in the tabbar)
|
||||
* indent-char (Color of the character which indicates tabs if the option is
|
||||
enabled)
|
||||
* line-number
|
||||
* gutter-info
|
||||
* gutter-error
|
||||
* gutter-warning
|
||||
* diff-added
|
||||
@ -371,7 +373,6 @@ highlighted. For example:
|
||||
start: "\""
|
||||
end: "\""
|
||||
skip: "\\."
|
||||
rules: []
|
||||
```
|
||||
|
||||
#### Includes
|
||||
|
@ -21,10 +21,16 @@ quotes here but these are not necessary when entering the command in micro.
|
||||
This command will modify `bindings.json` and overwrite any bindings to
|
||||
`key` that already exist.
|
||||
|
||||
* `help ['topic']`: opens the corresponding help topic. If no topic is provided
|
||||
opens the default help screen. Help topics are stored as `.md` files in the
|
||||
`runtime/help` directory of the source tree, which is embedded in the final
|
||||
binary.
|
||||
* `help ['topic'] ['flags']`: opens the corresponding help topics.
|
||||
If no topic is provided opens the default help screen. If multiple topics are
|
||||
provided (separated via ` `) they are opened all as splits.
|
||||
Help topics are stored as `.md` files in the `runtime/help` directory of
|
||||
the source tree, which is embedded in the final binary.
|
||||
The `flags` are optional.
|
||||
* `-hsplit`: Opens the help topic in a horizontal split
|
||||
* `-vsplit`: Opens the help topic in a vertical split
|
||||
|
||||
The default split type is defined by the global `helpsplit` option.
|
||||
|
||||
* `save ['filename']`: saves the current buffer. If the file is provided it
|
||||
will 'save as' the filename.
|
||||
@ -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.
|
||||
|
||||
* `vsplit ['filename']`: opens a vertical split with `filename`. If no filename
|
||||
is provided, a vertical split is opened with an empty buffer.
|
||||
is provided, a vertical split is opened with an empty buffer. If multiple
|
||||
files are provided (separated via ` `) they are opened all as splits.
|
||||
|
||||
* `hsplit ['filename']`: same as `vsplit` but opens a horizontal split instead
|
||||
of a vertical split.
|
||||
|
||||
* `tab ['filename']`: opens the given file in a new tab.
|
||||
* `tab ['filename']`: opens the given file in a new tab. If no filename
|
||||
is provided, a tab is opened with an empty buffer. If multiple files are
|
||||
provided (separated via ` `) they are opened all as tabs.
|
||||
|
||||
* `tabmove '[-+]n'`: Moves the active tab to another slot. `n` is an integer.
|
||||
If `n` is prefixed with `-` or `+`, then it represents a relative position
|
||||
|
@ -66,7 +66,9 @@ bindings, tab is bound as
|
||||
|
||||
This means that if the `Autocomplete` action is successful, the chain will
|
||||
abort. Otherwise, it will try `IndentSelection`, and if that fails too, it
|
||||
will execute `InsertTab`.
|
||||
will execute `InsertTab`. To use `,`, `|` or `&` in an action (as an argument
|
||||
to a command, for example), escape it with `\` or wrap it in single or double
|
||||
quotes.
|
||||
|
||||
## Binding commands
|
||||
|
||||
@ -168,14 +170,15 @@ CursorLeft
|
||||
CursorRight
|
||||
CursorStart
|
||||
CursorEnd
|
||||
CursorToViewTop
|
||||
CursorToViewCenter
|
||||
CursorToViewBottom
|
||||
SelectToStart
|
||||
SelectToEnd
|
||||
SelectUp
|
||||
SelectDown
|
||||
SelectLeft
|
||||
SelectRight
|
||||
SelectToStartOfText
|
||||
SelectToStartOfTextToggle
|
||||
WordRight
|
||||
WordLeft
|
||||
SubWordRight
|
||||
@ -184,20 +187,22 @@ SelectWordRight
|
||||
SelectWordLeft
|
||||
SelectSubWordRight
|
||||
SelectSubWordLeft
|
||||
MoveLinesUp
|
||||
MoveLinesDown
|
||||
DeleteWordRight
|
||||
DeleteWordLeft
|
||||
DeleteSubWordRight
|
||||
DeleteSubWordLeft
|
||||
SelectLine
|
||||
SelectToStartOfLine
|
||||
SelectToStartOfText
|
||||
SelectToStartOfTextToggle
|
||||
SelectToEndOfLine
|
||||
ParagraphPrevious
|
||||
ParagraphNext
|
||||
SelectToParagraphPrevious
|
||||
SelectToParagraphNext
|
||||
InsertNewline
|
||||
InsertSpace
|
||||
Backspace
|
||||
Delete
|
||||
Center
|
||||
InsertTab
|
||||
Save
|
||||
SaveAll
|
||||
@ -206,21 +211,28 @@ Find
|
||||
FindLiteral
|
||||
FindNext
|
||||
FindPrevious
|
||||
DiffPrevious
|
||||
DiffNext
|
||||
DiffPrevious
|
||||
Center
|
||||
Undo
|
||||
Redo
|
||||
Copy
|
||||
CopyLine
|
||||
Cut
|
||||
CutLine
|
||||
Duplicate
|
||||
DuplicateLine
|
||||
DeleteLine
|
||||
MoveLinesUp
|
||||
MoveLinesDown
|
||||
IndentSelection
|
||||
OutdentSelection
|
||||
Autocomplete
|
||||
CycleAutocompleteBack
|
||||
OutdentLine
|
||||
IndentLine
|
||||
Paste
|
||||
PastePrimary
|
||||
SelectAll
|
||||
OpenFile
|
||||
Start
|
||||
@ -231,33 +243,37 @@ SelectPageUp
|
||||
SelectPageDown
|
||||
HalfPageUp
|
||||
HalfPageDown
|
||||
StartOfLine
|
||||
EndOfLine
|
||||
StartOfText
|
||||
StartOfTextToggle
|
||||
ParagraphPrevious
|
||||
ParagraphNext
|
||||
SelectToParagraphPrevious
|
||||
SelectToParagraphNext
|
||||
StartOfLine
|
||||
EndOfLine
|
||||
ToggleHelp
|
||||
ToggleKeyMenu
|
||||
ToggleDiffGutter
|
||||
ToggleRuler
|
||||
JumpLine
|
||||
ToggleHighlightSearch
|
||||
UnhighlightSearch
|
||||
ResetSearch
|
||||
ClearInfo
|
||||
ClearStatus
|
||||
ShellMode
|
||||
CommandMode
|
||||
ToggleOverwriteMode
|
||||
Escape
|
||||
Quit
|
||||
QuitAll
|
||||
ForceQuit
|
||||
AddTab
|
||||
PreviousTab
|
||||
NextTab
|
||||
FirstTab
|
||||
LastTab
|
||||
NextSplit
|
||||
PreviousSplit
|
||||
FirstSplit
|
||||
LastSplit
|
||||
Unsplit
|
||||
VSplit
|
||||
HSplit
|
||||
PreviousSplit
|
||||
ToggleMacro
|
||||
PlayMacro
|
||||
Suspend (Unix only)
|
||||
@ -270,14 +286,25 @@ SpawnMultiCursorSelect
|
||||
RemoveMultiCursor
|
||||
RemoveAllMultiCursors
|
||||
SkipMultiCursor
|
||||
None
|
||||
SkipMultiCursorBack
|
||||
JumpToMatchingBrace
|
||||
Autocomplete
|
||||
JumpLine
|
||||
Deselect
|
||||
ClearInfo
|
||||
None
|
||||
```
|
||||
|
||||
The `StartOfTextToggle` and `SelectToStartOfTextToggle` actions toggle between
|
||||
jumping to the start of the text (first) and start of the line.
|
||||
|
||||
The `CutLine` action cuts the current line and adds it to the previously cut
|
||||
lines in the clipboard since the last paste (rather than just replaces the
|
||||
clipboard contents with this line). So you can cut multiple, not necessarily
|
||||
consecutive lines to the clipboard just by pressing `Ctrl-k` multiple times,
|
||||
without selecting them. If you want the more traditional behavior i.e. just
|
||||
rewrite the clipboard every time, you can use `CopyLine,DeleteLine` action
|
||||
instead of `CutLine`.
|
||||
|
||||
You can also bind some mouse actions (these must be bound to mouse buttons)
|
||||
|
||||
```
|
||||
@ -495,23 +522,23 @@ conventions for text editing defaults.
|
||||
"Alt-]": "DiffNext|CursorEnd",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-c": "Copy|CopyLine",
|
||||
"Ctrl-x": "Cut|CutLine",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-d": "DuplicateLine",
|
||||
"Ctrl-d": "Duplicate|DuplicateLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Ctrl-a": "SelectAll",
|
||||
"Ctrl-t": "AddTab",
|
||||
"Alt-,": "PreviousTab",
|
||||
"Alt-.": "NextTab",
|
||||
"Alt-,": "PreviousTab|LastTab",
|
||||
"Alt-.": "NextTab|FirstTab",
|
||||
"Home": "StartOfText",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"CtrlPageUp": "PreviousTab|LastTab",
|
||||
"CtrlPageDown": "NextTab|FirstTab",
|
||||
"ShiftPageUp": "SelectPageUp",
|
||||
"ShiftPageDown": "SelectPageDown",
|
||||
"Ctrl-g": "ToggleHelp",
|
||||
@ -522,7 +549,7 @@ conventions for text editing defaults.
|
||||
"Ctrl-b": "ShellMode",
|
||||
"Ctrl-q": "Quit",
|
||||
"Ctrl-e": "CommandMode",
|
||||
"Ctrl-w": "NextSplit",
|
||||
"Ctrl-w": "NextSplit|FirstSplit",
|
||||
"Ctrl-u": "ToggleMacro",
|
||||
"Ctrl-j": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
@ -621,8 +648,8 @@ are given below:
|
||||
"Backtab": "CycleAutocompleteBack",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-c": "Copy|CopyLine",
|
||||
"Ctrl-x": "Cut|CutLine",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Home": "StartOfTextToggle",
|
||||
|
@ -79,22 +79,16 @@ Here are the available options:
|
||||
|
||||
default value: `0`
|
||||
|
||||
* `colorscheme`: loads the colorscheme stored in
|
||||
$(configDir)/colorschemes/`option`.micro, This setting is `global only`.
|
||||
* `colorscheme`: use the given colorscheme. This setting is `global only`.
|
||||
The colorscheme can be either one of the colorschemes that micro comes with
|
||||
by default (such as `default`, `solarized` or `solarized-tc`) which are
|
||||
embedded in the micro binary, or a custom colorscheme stored in
|
||||
`~/.config/micro/colorschemes/$(option).micro` where `$(option)` is the
|
||||
option value. You can read more about micro's colorschemes and see the list
|
||||
of default colorschemes in `> help colors`.
|
||||
|
||||
default value: `default`
|
||||
|
||||
Note that the default colorschemes (default, solarized, and solarized-tc)
|
||||
are not located in configDir, because they are embedded in the micro
|
||||
binary.
|
||||
|
||||
The colorscheme can be selected from all the files in the
|
||||
~/.config/micro/colorschemes/ directory. Micro comes by default with
|
||||
three colorschemes:
|
||||
|
||||
You can read more about micro's colorschemes in the `colors` help topic
|
||||
(`help colors`).
|
||||
|
||||
* `cursorline`: highlight the line that the cursor is on in a different color
|
||||
(the color is defined by the colorscheme you are using).
|
||||
|
||||
@ -172,6 +166,13 @@ Here are the available options:
|
||||
default value: `unknown`. This will be automatically overridden depending
|
||||
on the file you open.
|
||||
|
||||
* `helpsplit`: sets the split type to be used by the `help` command.
|
||||
Possible values:
|
||||
* `vsplit`: open help in a vertical split pane
|
||||
* `hsplit`: open help in a horizontal split pane
|
||||
|
||||
default value: `hsplit`
|
||||
|
||||
* `hlsearch`: highlight all instances of the searched text after a successful
|
||||
search. This highlighting can be temporarily turned off via the
|
||||
`UnhighlightSearch` action (triggered by the Esc key by default) or toggled
|
||||
@ -194,11 +195,11 @@ Here are the available options:
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `incsearch`: enable incremental search in "Find" prompt (matching as you type).
|
||||
* `ignorecase`: perform case-insensitive searches.
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `ignorecase`: perform case-insensitive searches.
|
||||
* `incsearch`: enable incremental search in "Find" prompt (matching as you type).
|
||||
|
||||
default value: `true`
|
||||
|
||||
@ -278,6 +279,23 @@ Here are the available options:
|
||||
|
||||
default value: `tab`
|
||||
|
||||
* `pageoverlap`: the number of lines from the current view to keep in view
|
||||
when paging up or down. If this is set to 2, for instance, and you page
|
||||
down, the last two lines of the previous page will be the first two lines
|
||||
of the next page.
|
||||
|
||||
default value: `2`
|
||||
|
||||
* `parsecursor`: if enabled, this will cause micro to parse filenames such as
|
||||
`file.txt:10:5` as requesting to open `file.txt` with the cursor at line 10
|
||||
and column 5. The column number can also be dropped to open the file at a
|
||||
given line and column 0. Note that with this option enabled it is not possible
|
||||
to open a file such as `file.txt:10:5`, where `:10:5` is part of the filename.
|
||||
It is also possible to open a file with a certain cursor location by using the
|
||||
`+LINE:COL` flag syntax. See `micro -help` for the command line options.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `paste`: treat characters sent from the terminal in a single chunk as a paste
|
||||
event rather than a series of manual key presses. If you are pasting using
|
||||
the terminal keybinding (not `Ctrl-v`, which is micro's default paste
|
||||
@ -287,16 +305,6 @@ Here are the available options:
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `parsecursor`: if enabled, this will cause micro to parse filenames such as
|
||||
file.txt:10:5 as requesting to open `file.txt` with the cursor at line 10
|
||||
and column 5. The column number can also be dropped to open the file at a
|
||||
given line and column 0. Note that with this option enabled it is not possible
|
||||
to open a file such as `file.txt:10:5`, where `:10:5` is part of the filename.
|
||||
It is also possible to open a file with a certain cursor location by using the
|
||||
`+LINE:COL` flag syntax. See `micro -help` for the command line options.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `permbackup`: this option causes backups (see `backup` option) to be
|
||||
permanently saved. With permanent backups, micro will not remove backups when
|
||||
files are closed and will never apply them to existing files. Use this option
|
||||
@ -321,6 +329,12 @@ Here are the available options:
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `relativeruler`: make line numbers display relatively. If set to true, all
|
||||
lines except for the line that the cursor is located will display the distance
|
||||
from the cursor's line.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `reload`: controls the reload behavior of the current buffer in case the file
|
||||
has changed. The available options are `prompt`, `auto` & `disabled`.
|
||||
|
||||
@ -338,12 +352,6 @@ Here are the available options:
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `relativeruler`: make line numbers display relatively. If set to true, all
|
||||
lines except for the line that the cursor is located will display the distance
|
||||
from the cursor's line.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `savecursor`: remember where the cursor was last time the file was opened and
|
||||
put it there when you open the file again. Information is saved to
|
||||
`~/.config/micro/buffers/`
|
||||
@ -401,11 +409,11 @@ Here are the available options:
|
||||
* `statusformatl`: format string definition for the left-justified part of the
|
||||
statusline. Special directives should be placed inside `$()`. Special
|
||||
directives include: `filename`, `modified`, `line`, `col`, `lines`,
|
||||
`percentage`, `opt`, `bind`.
|
||||
`percentage`, `opt`, `overwrite`, `bind`.
|
||||
The `opt` and `bind` directives take either an option or an action afterward
|
||||
and fill in the value of the option or the key bound to the action.
|
||||
|
||||
default value: `$(filename) $(modified)($(line),$(col)) $(status.paste)|
|
||||
default value: `$(filename) $(modified)$(overwrite)($(line),$(col)) $(status.paste)|
|
||||
ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)`
|
||||
|
||||
* `statusformatr`: format string definition for the right-justified part of the
|
||||
@ -427,20 +435,20 @@ Here are the available options:
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `tabhighlight`: inverts the tab characters' (filename, save indicator, etc)
|
||||
colors with respect to the tab bar.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `tabmovement`: navigate spaces at the beginning of lines as if they are tabs
|
||||
(e.g. move over 4 spaces at once). This option only does anything if
|
||||
`tabstospaces` is on.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `tabhighlight`: inverts the tab characters' (filename, save indicator, etc)
|
||||
colors with respect to the tab bar.
|
||||
|
||||
default value: false
|
||||
|
||||
* `tabreverse`: reverses the tab bar colors when active.
|
||||
|
||||
default value: true
|
||||
default value: `true`
|
||||
|
||||
* `tabsize`: the size in spaces that a tab character should be displayed with.
|
||||
|
||||
@ -454,6 +462,19 @@ Here are the available options:
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `truecolor`: controls whether micro will use true colors (24-bit colors) when
|
||||
using a colorscheme with true colors, such as `solarized-tc` or `atom-dark`.
|
||||
* `auto`: enable usage of true color if micro detects that it is supported by
|
||||
the terminal, otherwise disable it.
|
||||
* `on`: force usage of true color even if micro does not detect its support
|
||||
by the terminal (of course this is not guaranteed to work well unless the
|
||||
terminal actually supports true color).
|
||||
* `off`: disable true color usage.
|
||||
|
||||
Note: The change will take effect after the next start of `micro`.
|
||||
|
||||
default value: `auto`
|
||||
|
||||
* `useprimary` (only useful on unix): defines whether or not micro will use the
|
||||
primary clipboard to copy selections in the background. This does not affect
|
||||
the normal clipboard using `Ctrl-c` and `Ctrl-v`.
|
||||
@ -493,13 +514,13 @@ or disable them:
|
||||
recent Git commit rather than the diff since opening the file.
|
||||
|
||||
Any option you set in the editor will be saved to the file
|
||||
~/.config/micro/settings.json so, in effect, your configuration file will be
|
||||
`~/.config/micro/settings.json` so, in effect, your configuration file will be
|
||||
created for you. If you'd like to take your configuration with you to another
|
||||
machine, simply copy the settings.json to the other machine.
|
||||
machine, simply copy the `settings.json` to the other machine.
|
||||
|
||||
## Settings.json file
|
||||
|
||||
The settings.json file should go in your configuration directory (by default
|
||||
The `settings.json` file should go in your configuration directory (by default
|
||||
at `~/.config/micro`), and should contain only options which have been modified
|
||||
from their default setting. Here is the full list of options in json format,
|
||||
so that you can see what the formatting should look like.
|
||||
@ -518,18 +539,24 @@ so that you can see what the formatting should look like.
|
||||
"colorscheme": "default",
|
||||
"comment": true,
|
||||
"cursorline": true,
|
||||
"detectlimit": 100,
|
||||
"diff": true,
|
||||
"diffgutter": false,
|
||||
"divchars": "|-",
|
||||
"divreverse": true,
|
||||
"encoding": "utf-8",
|
||||
"eofnewline": true,
|
||||
"fakecursor": false,
|
||||
"fastdirty": false,
|
||||
"fileformat": "unix",
|
||||
"filetype": "unknown",
|
||||
"incsearch": true,
|
||||
"ftoptions": true,
|
||||
"helpsplit": "hsplit",
|
||||
"hlsearch": false,
|
||||
"hltaberrors": false,
|
||||
"hltrailingws": false,
|
||||
"ignorecase": true,
|
||||
"incsearch": true,
|
||||
"indentchar": " ",
|
||||
"infobar": true,
|
||||
"initlua": true,
|
||||
@ -542,6 +569,8 @@ so that you can see what the formatting should look like.
|
||||
"matchbracestyle": "underline",
|
||||
"mkparents": false,
|
||||
"mouse": true,
|
||||
"multiopen": "tab",
|
||||
"pageoverlap": 2,
|
||||
"parsecursor": false,
|
||||
"paste": false,
|
||||
"permbackup": false,
|
||||
@ -551,12 +580,14 @@ so that you can see what the formatting should look like.
|
||||
"pluginrepos": [],
|
||||
"readonly": false,
|
||||
"relativeruler": false,
|
||||
"reload": "prompt",
|
||||
"rmtrailingws": false,
|
||||
"ruler": true,
|
||||
"savecursor": false,
|
||||
"savehistory": true,
|
||||
"saveundo": false,
|
||||
"scrollbar": false,
|
||||
"scrollbarchar": "|",
|
||||
"scrollmargin": 3,
|
||||
"scrollspeed": 2,
|
||||
"smartpaste": true,
|
||||
@ -564,17 +595,18 @@ so that you can see what the formatting should look like.
|
||||
"splitbottom": true,
|
||||
"splitright": true,
|
||||
"status": true,
|
||||
"statusformatl": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
|
||||
"statusformatl": "$(filename) $(modified)$(overwrite)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
|
||||
"statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
|
||||
"statusline": true,
|
||||
"sucmd": "sudo",
|
||||
"syntax": true,
|
||||
"tabmovement": false,
|
||||
"tabhighlight": true,
|
||||
"tabmovement": false,
|
||||
"tabreverse": false,
|
||||
"tabsize": 4,
|
||||
"tabstospaces": false,
|
||||
"useprimary": true,
|
||||
"wordwrap": false,
|
||||
"xterm": false
|
||||
}
|
||||
```
|
||||
|
@ -57,11 +57,13 @@ that micro defines:
|
||||
|
||||
* `deinit()`: cleanup function called when your plugin is unloaded or reloaded.
|
||||
|
||||
* `onSetActive(bufpane)`: runs when changing the currently active panel.
|
||||
|
||||
* `onBufferOpen(buf)`: runs when a buffer is opened. The input contains
|
||||
the buffer object.
|
||||
|
||||
* `onBufferOptionChanged(buf, option, old, new)`: runs when an option of the
|
||||
buffer has changed. The input contains the buffer object, the option name,
|
||||
the old and the new value.
|
||||
|
||||
* `onBufPaneOpen(bufpane)`: runs when a bufpane is opened. The input
|
||||
contains the bufpane object.
|
||||
|
||||
@ -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
|
||||
string is a word character.
|
||||
- `String(b []byte) string`: converts a byte array to a string.
|
||||
- `RuneStr(r rune) string`: converts a rune to a string.
|
||||
- `Unzip(src, dest string) error`: unzips a file to given folder.
|
||||
- `Version`: micro's version number or commit hash
|
||||
- `SemVersion`: micro's semantic version
|
||||
|
@ -107,7 +107,7 @@ function commentLine(bp, lineN, indentLen)
|
||||
bp.Cursor.Y = curpos.Y
|
||||
end
|
||||
bp.Cursor:Relocate()
|
||||
bp.Cursor.LastVisualX = bp.Cursor:GetVisualX()
|
||||
bp.Cursor:StoreVisualX()
|
||||
end
|
||||
|
||||
function uncommentLine(bp, lineN, commentRegex)
|
||||
@ -135,7 +135,7 @@ function uncommentLine(bp, lineN, commentRegex)
|
||||
end
|
||||
end
|
||||
bp.Cursor:Relocate()
|
||||
bp.Cursor.LastVisualX = bp.Cursor:GetVisualX()
|
||||
bp.Cursor:StoreVisualX()
|
||||
end
|
||||
|
||||
function toggleCommentLine(bp, lineN, commentRegex)
|
||||
|
@ -9,6 +9,7 @@ following filetypes and linters:
|
||||
* **c++**: g++
|
||||
* **d**: dmd
|
||||
* **go**: go build
|
||||
* **go**: go vet
|
||||
* **haskell**: hlint
|
||||
* **java**: javac
|
||||
* **javascript**: jshint
|
||||
@ -16,11 +17,16 @@ following filetypes and linters:
|
||||
* **literate**: lit
|
||||
* **lua**: luacheck
|
||||
* **nim**: nim
|
||||
* **nix**: nix-linter
|
||||
* **objective-c**: clang
|
||||
* **python**: pyflakes
|
||||
* **python**: flake8
|
||||
* **python**: mypy
|
||||
* **python**: pyflakes
|
||||
* **python**: pylint
|
||||
* **python**: ruff
|
||||
* **rust**: cargo clippy
|
||||
* **shell**: shfmt
|
||||
* **shell**: shellcheck
|
||||
* **swift**: swiftc (MacOS and Linux only)
|
||||
* **yaml**: yamllint
|
||||
|
||||
|
@ -66,7 +66,7 @@ function preinit()
|
||||
end
|
||||
|
||||
makeLinter("gcc", "c", "gcc", {"-fsyntax-only", "-Wall", "-Wextra", "%f"}, "%f:%l:%c:.+: %m")
|
||||
makeLinter("g++", "c++", "gcc", {"-fsyntax-only","-std=c++14", "-Wall", "-Wextra", "%f"}, "%f:%l:%c:.+: %m")
|
||||
makeLinter("g++", "c++", "g++", {"-fsyntax-only","-Wall", "-Wextra", "%f"}, "%f:%l:%c:.+: %m")
|
||||
makeLinter("dmd", "d", "dmd", {"-color=off", "-o-", "-w", "-wi", "-c", "%f"}, "%f%(%l%):.+: %m")
|
||||
makeLinter("eslint", "javascript", "eslint", {"-f","compact","%f"}, "%f: line %l, col %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("mypy", "python", "mypy", {"%f"}, "%f:%l: %m")
|
||||
makeLinter("pylint", "python", "pylint", {"--output-format=parseable", "--reports=no", "%f"}, "%f:%l: %m")
|
||||
makeLinter("ruff", "python", "ruff", {"check", "--output-format=concise", "%f"}, "%f:%l:%c: %m")
|
||||
makeLinter("flake8", "python", "flake8", {"%f"}, "%f:%l:%c: %m")
|
||||
makeLinter("shfmt", "shell", "shfmt", {"%f"}, "%f:%l:%c: %m")
|
||||
makeLinter("shellcheck", "shell", "shellcheck", {"-f", "gcc", "%f"}, "%f:%l:%c:.+: %m")
|
||||
@ -107,26 +108,29 @@ function contains(list, element)
|
||||
return false
|
||||
end
|
||||
|
||||
function checkFtMatch(ft, v)
|
||||
local ftmatch = ft == v.filetype
|
||||
if v.domatch then
|
||||
ftmatch = string.match(ft, v.filetype)
|
||||
end
|
||||
|
||||
local hasOS = contains(v.os, runtime.GOOS)
|
||||
if not hasOS and v.whitelist then
|
||||
ftmatch = false
|
||||
end
|
||||
if hasOS and not v.whitelist then
|
||||
ftmatch = false
|
||||
end
|
||||
return ftmatch
|
||||
end
|
||||
|
||||
function runLinter(buf)
|
||||
local ft = buf:FileType()
|
||||
local file = buf.Path
|
||||
local dir = "." .. util.RuneStr(os.PathSeparator) .. filepath.Dir(file)
|
||||
|
||||
for k, v in pairs(linters) do
|
||||
local ftmatch = ft == v.filetype
|
||||
if v.domatch then
|
||||
ftmatch = string.match(ft, v.filetype)
|
||||
end
|
||||
|
||||
local hasOS = contains(v.os, runtime.GOOS)
|
||||
if not hasOS and v.whitelist then
|
||||
ftmatch = false
|
||||
end
|
||||
if hasOS and not v.whitelist then
|
||||
ftmatch = false
|
||||
end
|
||||
|
||||
if ftmatch then
|
||||
if checkFtMatch(ft, v) then
|
||||
local args = {}
|
||||
for k, arg in pairs(v.args) do
|
||||
args[k] = arg:gsub("%%f", file):gsub("%%d", dir)
|
||||
@ -141,6 +145,19 @@ function onSave(bp)
|
||||
return true
|
||||
end
|
||||
|
||||
function onBufferOptionChanged(buf, option, old, new)
|
||||
if option == "filetype" then
|
||||
if old ~= new then
|
||||
for k, v in pairs(linters) do
|
||||
if checkFtMatch(old, v) then
|
||||
buf:ClearMessages(k)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function lint(buf, linter, cmd, args, errorformat, loff, coff, callback)
|
||||
buf:ClearMessages(linter)
|
||||
|
||||
|
@ -47,7 +47,6 @@ function onBufferOpen(buf)
|
||||
syntaxFile = syntaxFile .. " - special:\n"
|
||||
syntaxFile = syntaxFile .. " start: \"@\\\\{\"\n"
|
||||
syntaxFile = syntaxFile .. " end: \"\\\\}\"\n"
|
||||
syntaxFile = syntaxFile .. " rules: []\n"
|
||||
syntaxFile = syntaxFile .. " - include: " .. codetype .. "\n"
|
||||
|
||||
config.AddRuntimeFileFromMemory(config.RTSyntax, "literate.yaml", syntaxFile)
|
||||
|
@ -8,8 +8,10 @@ those options (`> help options`) for more information.
|
||||
|
||||
This plugin provides functions that can be used in the status line format:
|
||||
|
||||
* `status.branch`: returns the name of the current git branch.
|
||||
* `status.hash`: returns the hash of the current git commit.
|
||||
* `status.branch`: returns the name of the current git branch in the repository
|
||||
where the file is located.
|
||||
* `status.hash`: returns the hash of the current git commit in the repository
|
||||
where the file is located.
|
||||
* `status.paste`: returns "" if the paste option is disabled and "PASTE"
|
||||
if it is enabled.
|
||||
* `status.lines`: returns the number of lines in the buffer.
|
||||
|
@ -3,7 +3,10 @@ VERSION = "1.0.0"
|
||||
local micro = import("micro")
|
||||
local buffer = import("micro/buffer")
|
||||
local config = import("micro/config")
|
||||
local shell = import("micro/shell")
|
||||
local filepath = import("filepath")
|
||||
local humanize = import("humanize")
|
||||
local strings = import("strings")
|
||||
|
||||
function init()
|
||||
micro.SetStatusInfoFn("status.branch")
|
||||
@ -21,7 +24,7 @@ function lines(b)
|
||||
end
|
||||
|
||||
function vcol(b)
|
||||
return tostring(b:GetActiveCursor():GetVisualX())
|
||||
return tostring(b:GetActiveCursor():GetVisualX(false))
|
||||
end
|
||||
|
||||
function bytes(b)
|
||||
@ -32,30 +35,23 @@ function size(b)
|
||||
return humanize.Bytes(b:Size())
|
||||
end
|
||||
|
||||
function branch(b)
|
||||
local function parseRevision(b, opt)
|
||||
if b.Type.Kind ~= buffer.BTInfo then
|
||||
local shell = import("micro/shell")
|
||||
local strings = import("strings")
|
||||
|
||||
local branch, err = shell.ExecCommand("git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
local dir = filepath.Dir(b.Path)
|
||||
local str, err = shell.ExecCommand("git", "-C", dir, "rev-parse", opt, "HEAD")
|
||||
if err == nil then
|
||||
return strings.TrimSpace(branch)
|
||||
return strings.TrimSpace(str)
|
||||
end
|
||||
return ""
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
function branch(b)
|
||||
return parseRevision(b, "--abbrev-ref")
|
||||
end
|
||||
|
||||
function hash(b)
|
||||
if b.Type.Kind ~= 5 then
|
||||
local shell = import("micro/shell")
|
||||
local strings = import("strings")
|
||||
|
||||
local hash, err = shell.ExecCommand("git", "rev-parse", "--short", "HEAD")
|
||||
if err == nil then
|
||||
return strings.TrimSpace(hash)
|
||||
end
|
||||
return ""
|
||||
end
|
||||
return parseRevision(b, "--short")
|
||||
end
|
||||
|
||||
function paste(b)
|
||||
|
@ -6,11 +6,6 @@ Each yaml file specifies how to detect the filetype based on file extension or h
|
||||
In addition, a signature can be provided to help resolving ambiguities when multiple matching filetypes are detected.
|
||||
Then there are patterns and regions linked to highlight groups which tell micro how to highlight that filetype.
|
||||
|
||||
Making your own syntax files is very simple. I recommend you check the file after you are finished with the
|
||||
[`syntax_checker.go`](./syntax_checker.go) program (located in this directory). Just place your yaml syntax
|
||||
file in the current directory and run `go run syntax_checker.go` and it will check every file. If there are no
|
||||
errors it will print `No issues!`.
|
||||
|
||||
You can read more about how to write syntax files (and colorschemes) in the [colors](../help/colors.md) documentation.
|
||||
|
||||
# Legacy '.micro' filetype
|
||||
@ -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
|
||||
* 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
|
||||
|
||||
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)$"
|
||||
|
||||
rules:
|
||||
# This file is made for NASM assembly
|
||||
# This file is made mainly for NASM assembly
|
||||
|
||||
## Instructions
|
||||
# x86
|
||||
@ -108,3 +108,16 @@ rules:
|
||||
rules:
|
||||
- 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:
|
||||
start: "#"
|
||||
end: "$"
|
||||
rules: []
|
||||
|
||||
|
@ -30,7 +30,6 @@ rules:
|
||||
- preproc:
|
||||
start: "\\$(\\{|ENV\\{)"
|
||||
end: "\\}"
|
||||
rules: []
|
||||
|
||||
- identifier.macro: "\\b(APPLE|UNIX|WIN32|CYGWIN|BORLAND|MINGW|MSVC(_IDE|60|71|80|90)?)\\b"
|
||||
|
||||
|
@ -27,12 +27,10 @@ rules:
|
||||
- constant.string:
|
||||
start: "`"
|
||||
end: "`"
|
||||
rules: []
|
||||
|
||||
- constant.string:
|
||||
start: "%x\\{"
|
||||
end: "\\}"
|
||||
rules: []
|
||||
|
||||
- constant.string:
|
||||
start: "\""
|
||||
@ -68,5 +66,4 @@ rules:
|
||||
- constant:
|
||||
start: "<<-?'?EOT'?"
|
||||
end: "^EOT"
|
||||
rules: []
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -109,13 +109,10 @@ rules:
|
||||
- comment:
|
||||
start: "//"
|
||||
end: "$"
|
||||
rules: []
|
||||
- comment:
|
||||
start: "/\\*"
|
||||
end: "\\*/"
|
||||
rules: []
|
||||
- comment:
|
||||
start: "/\\+"
|
||||
end: "\\+/"
|
||||
rules: []
|
||||
|
||||
|
@ -19,7 +19,6 @@ rules:
|
||||
- default:
|
||||
start: "<%"
|
||||
end: "%>"
|
||||
rules: []
|
||||
|
||||
- preproc: "<%|%>"
|
||||
- red: "&[^;[[:space:]]]*;"
|
||||
@ -37,6 +36,5 @@ rules:
|
||||
- identifier.macro:
|
||||
start: "<<-?'?EOT'?"
|
||||
end: "^EOT"
|
||||
rules: []
|
||||
|
||||
- todo: "(XXX|TODO|FIXME|\\?\\?\\?)"
|
||||
|
@ -22,7 +22,6 @@ rules:
|
||||
start: "'"
|
||||
end: "'"
|
||||
skip: "\\\\."
|
||||
rules: []
|
||||
# - constant.specialChar: "%."
|
||||
# - constant.specialChar: "\\\\[abfnrtv'\\\"\\\\]"
|
||||
# - constant.specialChar: "\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})"
|
||||
@ -42,4 +41,3 @@ rules:
|
||||
- comment:
|
||||
start: "%"
|
||||
end: "$"
|
||||
rules: []
|
||||
|
@ -38,7 +38,6 @@ rules:
|
||||
start: "'"
|
||||
end: "'"
|
||||
skip: "\\\\."
|
||||
rules: []
|
||||
|
||||
- comment:
|
||||
start: "#"
|
||||
|
@ -19,7 +19,6 @@ rules:
|
||||
- constant.string:
|
||||
start: "\\b([Ss.]\" )"
|
||||
end: "\""
|
||||
rules: []
|
||||
|
||||
- comment:
|
||||
start: "\\("
|
||||
|
@ -45,4 +45,3 @@ rules:
|
||||
- comment:
|
||||
start: "\\(\\*"
|
||||
end: "\\*\\)"
|
||||
rules: []
|
||||
|
@ -10,7 +10,6 @@ rules:
|
||||
- special:
|
||||
start: "^```"
|
||||
end: "^```"
|
||||
rules: []
|
||||
# heading lines
|
||||
- special: "^#{1,3}.*"
|
||||
# unordered list items
|
||||
|
@ -20,4 +20,3 @@ rules:
|
||||
- comment:
|
||||
start: "#"
|
||||
end: "$"
|
||||
rules: []
|
||||
|
@ -22,7 +22,6 @@ rules:
|
||||
- comment.line:
|
||||
start: "^#"
|
||||
end: "$"
|
||||
rules: []
|
||||
|
||||
# Diffs (i.e. git commit --verbose)
|
||||
- default:
|
||||
|
@ -11,4 +11,3 @@ rules:
|
||||
- comment:
|
||||
start: "#"
|
||||
end: "$"
|
||||
rules: []
|
||||
|
@ -16,4 +16,3 @@ rules:
|
||||
- comment.line:
|
||||
start: "^#"
|
||||
end: "$"
|
||||
rules: []
|
||||
|
@ -47,7 +47,6 @@ rules:
|
||||
- constant.string:
|
||||
start: "`"
|
||||
end: "`"
|
||||
rules: []
|
||||
|
||||
- comment:
|
||||
start: "//"
|
||||
|
@ -44,4 +44,3 @@ rules:
|
||||
- comment:
|
||||
start: "#"
|
||||
end: "$"
|
||||
rules: []
|
||||
|
@ -11,7 +11,6 @@ rules:
|
||||
- constant:
|
||||
start: "(\\\\|\\\\\\\\)n\\["
|
||||
end: "]"
|
||||
rules: []
|
||||
|
||||
- type: "^\\.[[:space:]]*[^[[:space:]]]*"
|
||||
- comment: "^\\.\\\\\".*$"
|
||||
@ -19,12 +18,10 @@ rules:
|
||||
- constant.string:
|
||||
start: "(\\\\|\\\\\\\\)\\*\\["
|
||||
end: "]"
|
||||
rules: []
|
||||
|
||||
- constant.specialChar: "\\\\\\(.."
|
||||
- constant.specialChar:
|
||||
start: "\\\\\\["
|
||||
end: "]"
|
||||
rules: []
|
||||
|
||||
- identifier.macro: "\\\\\\\\\\$[1-9]"
|
||||
|
@ -75,7 +75,6 @@ rules:
|
||||
- identifier:
|
||||
start: "[$][{]"
|
||||
end: "[}]"
|
||||
rules: []
|
||||
|
||||
# Triple-single-quoted strings
|
||||
- constant.string:
|
||||
@ -90,7 +89,6 @@ rules:
|
||||
- constant.string:
|
||||
start: "[$]/"
|
||||
end: "/[$]"
|
||||
rules: []
|
||||
|
||||
# Single-line comments
|
||||
- comment:
|
||||
@ -110,4 +108,3 @@ rules:
|
||||
- comment:
|
||||
start: "/[*][*]@?"
|
||||
end: "[*]/"
|
||||
rules: []
|
||||
|
@ -4,35 +4,39 @@ detect:
|
||||
filename: "\\.hs$"
|
||||
|
||||
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
|
||||
- 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: "\\b(Nothing|Just|Left|Right|LT|EQ|GT)\\b"
|
||||
|
||||
# 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)[ ]"
|
||||
- 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"
|
||||
|
||||
# Strings
|
||||
# Data classes
|
||||
- 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
|
||||
- constant.string:
|
||||
start: "\""
|
||||
end: "\""
|
||||
skip: "\\\\."
|
||||
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:
|
||||
start: "--"
|
||||
end: "$"
|
||||
@ -45,4 +49,4 @@ rules:
|
||||
rules:
|
||||
- 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