mirror of
https://github.com/zyedidia/micro.git
synced 2026-04-01 15:47:12 +09:00
Compare commits
1 Commits
tcell2.0.7
...
vfsgen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
319a5cba74 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -18,3 +18,4 @@ tools/info-plist
|
||||
tools/bindata
|
||||
tools/vscode-tests/
|
||||
*.hdr
|
||||
assets_vfsdata.go
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "tools/go-bindata"]
|
||||
path = tools/go-bindata
|
||||
url = https://github.com/zyedidia/go-bindata
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
language: go
|
||||
go:
|
||||
- "1.13.x"
|
||||
- "1.11.x"
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
- windows
|
||||
script:
|
||||
- go build ./cmd/micro
|
||||
- go test ./internal/...
|
||||
- go test ./cmd/...
|
||||
- env GO111MODULE=on make build
|
||||
- env GO111MODULE=on make test
|
||||
|
||||
20
Makefile
20
Makefile
@@ -6,8 +6,7 @@ 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 "$(VERSION)")
|
||||
GOARCH=$(shell go env GOHOSTARCH))
|
||||
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
|
||||
@@ -15,20 +14,20 @@ VSCODE_TESTS_BASE_URL = 'https://raw.githubusercontent.com/microsoft/vscode/e6a4
|
||||
|
||||
# Builds micro after checking dependencies but without updating the runtime
|
||||
build:
|
||||
go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
go build -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
|
||||
go build -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS) $(DEBUGVAR)" ./cmd/micro
|
||||
|
||||
build-tags: fetch-tags
|
||||
go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
|
||||
# Builds micro after building the runtime and checking dependencies
|
||||
build-all: runtime build
|
||||
|
||||
# Builds micro without checking for dependencies
|
||||
build-quick:
|
||||
go build -trimpath -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
|
||||
|
||||
# Same as 'build' but installs to $GOBIN afterward
|
||||
install:
|
||||
@@ -46,13 +45,7 @@ fetch-tags:
|
||||
|
||||
# Builds the runtime
|
||||
runtime:
|
||||
git submodule update --init
|
||||
rm -f runtime/syntax/*.hdr
|
||||
go run runtime/syntax/make_headers.go runtime/syntax
|
||||
go build -o tools/bindata ./tools/go-bindata
|
||||
tools/bindata -pkg config -nomemcopy -nometadata -o runtime.go runtime/...
|
||||
mv runtime.go internal/config
|
||||
gofmt -w internal/config/runtime.go
|
||||
go generate ./internal/config
|
||||
|
||||
testgen:
|
||||
mkdir -p tools/vscode-tests
|
||||
@@ -65,7 +58,6 @@ testgen:
|
||||
|
||||
test:
|
||||
go test ./internal/...
|
||||
go test ./cmd/...
|
||||
|
||||
bench:
|
||||
for i in 1 2 3; do \
|
||||
|
||||
17
README.md
17
README.md
@@ -5,7 +5,7 @@
|
||||
[](https://github.com/zyedidia/micro/releases)
|
||||
[](https://github.com/zyedidia/micro/blob/master/LICENSE)
|
||||
[](https://gitter.im/zyedidia/micro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://snapcraft.io/micro)
|
||||
[](https://build.snapcraft.io/user/zyedidia/micro)
|
||||
|
||||
**micro** is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the capabilities
|
||||
of modern terminals. It comes as a single, batteries-included, static binary with no dependencies; you can download and use it right now!
|
||||
@@ -17,7 +17,7 @@ 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).
|
||||
To see more screenshots of micro, showcasing some of the default color schemes, see [here](http://zbyedidia.webfactional.com/micro/screenshots.html).
|
||||
|
||||
You can also check out the website for Micro at https://micro-editor.github.io.
|
||||
|
||||
@@ -32,7 +32,7 @@ You can also check out the website for Micro at https://micro-editor.github.io.
|
||||
- [macOS terminal](#macos-terminal)
|
||||
- [Linux clipboard support](#linux-clipboard-support)
|
||||
- [Colors and syntax highlighting](#colors-and-syntax-highlighting)
|
||||
- [Cygwin, Mingw, Plan9](#cygwin-mingw-plan9)
|
||||
- [Plan9, Cygwin, Mingw](#plan9-cygwin-mingw)
|
||||
- [Usage](#usage)
|
||||
- [Documentation and Help](#documentation-and-help)
|
||||
- [Contributing](#contributing)
|
||||
@@ -79,8 +79,6 @@ If you want more information about ways to install micro, see this [wiki page](h
|
||||
Use `micro -version` to get the version information after installing. It is only guaranteed that you are installing the most recent
|
||||
stable version if you install from the prebuilt binaries, Homebrew, or Snap.
|
||||
|
||||
A desktop entry file and man page can be found in the [assets/packaging](https://github.com/zyedidia/micro/tree/master/assets/packaging) directory.
|
||||
|
||||
### Prebuilt binaries
|
||||
|
||||
All you need to install micro is one file, the binary itself. It's as simple as that!
|
||||
@@ -97,9 +95,7 @@ You can easily install micro by running
|
||||
curl https://getmic.ro | bash
|
||||
```
|
||||
|
||||
The script will place the micro binary in the current directory. From there, you can move it to a directory on your path of your choosing (e.g. `sudo mv micro /usr/bin`). See its [GitHub repository](https://github.com/benweissmann/getmic.ro) for more information.
|
||||
|
||||
To uninstall micro, simply remove the binary, and the configuration directory at `~/.config/micro`.
|
||||
The script will place the micro binary in the current directory. See its [GitHub repository](https://github.com/benweissmann/getmic.ro) for more information.
|
||||
|
||||
### Package managers
|
||||
|
||||
@@ -127,10 +123,9 @@ Micro is also available through other package managers on Linux such as apt, dnf
|
||||
for other operating systems. These packages are not guaranteed to be up-to-date.
|
||||
|
||||
* Linux: Available in distro-specific package managers.
|
||||
* `apt install micro` (Ubuntu 20.04 `focal`, and Debian `unstable | testing | buster-backports`). At the moment, this package (2.0.1-1) is outdated and has a known bug where debug mode is enabled.
|
||||
* `apt install micro` (Ubuntu 20.04 `focal`, and Debian `unstable | testing | buster-backports`).
|
||||
* `dnf install micro` (Fedora).
|
||||
* `pacman -S micro` (Arch Linux).
|
||||
* `eopkg install micro` (Solus).
|
||||
* `yay -S micro` (Arch Linux).
|
||||
* See [wiki](https://github.com/zyedidia/micro/wiki/Installing-Micro) for details about CRUX, Termux.
|
||||
* Windows: [Chocolatey](https://chocolatey.org) and [Scoop](https://github.com/lukesampson/scoop).
|
||||
* `choco install micro`.
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 103.2 103.2"
|
||||
enable-background="new 0 0 960 560"
|
||||
xml:space="preserve"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="micro-logo-notext.svg"
|
||||
width="103.2"
|
||||
height="103.2"><metadata
|
||||
id="metadata9"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs7" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="733"
|
||||
inkscape:window-height="480"
|
||||
id="namedview5"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="0.28541667"
|
||||
inkscape:cx="302"
|
||||
inkscape:cy="-4"
|
||||
inkscape:window-x="1699"
|
||||
inkscape:window-y="277"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="Layer_1" /><path
|
||||
d="M 51.6,0 C 23.1,0 0,23.1 0,51.6 c 0,28.5 23.1,51.6 51.6,51.6 28.5,0 51.6,-23.1 51.6,-51.6 C 103.2,23.1 80.1,0 51.6,0 Z m 24.5,58.6 c -0.5,2 -1.3,3.6 -2.4,4.9 -1,1.3 -2,2.1 -3.1,2.5 -1.1,0.4 -2.2,0.6 -3.4,0.6 -1.2,0 -2.2,-0.2 -3,-0.7 C 63.4,65.5 62.8,64.8 62.3,64 61.8,63.2 61.5,62.2 61.3,61.1 61.1,60 61,58.8 61,57.5 c 0,-0.5 0,-1 0.1,-1.7 0.1,-0.7 0.2,-1.6 0.3,-1.6 l -0.2,0 c -1.6,4 -3.8,6.9 -6.6,9.2 -2.8,2.3 -5.9,3.4 -9.3,3.4 -2.3,0 -4.2,-0.9 -5.5,-2.6 -1.4,-1.7 -2.1,-4.3 -2.1,-7.7 0,-0.5 0,-1 0.1,-1.6 0.1,-0.5 0.1,-0.7 0.2,-1.7 l -0.7,0 c -0.9,2 -1.7,4.8 -2.3,7.3 -0.6,2.5 -1.1,4.8 -1.4,6.9 -0.4,2.1 -0.6,4 -0.8,5.6 -0.2,1.6 -0.3,2.7 -0.4,3.3 0.1,0.5 0.2,1 0.3,1.6 0.2,0.6 0.3,1.2 0.5,1.7 0.2,0.5 0.3,1.1 0.4,1.6 0.1,0.5 0.2,0.9 0.2,1.2 0,1.4 -0.3,2.5 -0.9,3.2 -0.6,0.7 -1.3,1.1 -2,1.1 -0.9,0 -1.7,-0.3 -2.3,-0.8 -0.7,-0.6 -1,-1.5 -1,-2.7 0,-1.7 0.3,-3.9 0.9,-6.5 0.6,-2.6 1.5,-5.9 2.6,-9.8 0.6,-1.8 1.1,-3.6 1.7,-5.4 0.6,-1.8 1.1,-3.5 1.6,-5 0.5,-1.5 0.9,-2.9 1.3,-4.1 0.4,-1.2 0.6,-2.1 0.7,-2.8 0.1,-0.3 0.2,-1 0.3,-2 0.1,-1 0.2,-2.1 0.4,-3.4 0.2,-1.3 0.3,-2.7 0.5,-4.1 0.2,-1.5 0.4,-2.8 0.5,-4 0.2,-0.9 0.3,-1.9 0.5,-3 0.2,-1.1 0.5,-2.2 0.9,-3.1 0.4,-1 1,-1.8 1.7,-2.5 0.7,-0.7 1.6,-1 2.7,-1 1.2,0 2,0.4 2.4,1.1 0.4,0.7 0.6,1.6 0.5,2.6 -0.1,1 -0.2,2.1 -0.5,3.2 -0.3,1.1 -0.6,2.1 -0.9,2.9 -0.8,2.5 -1.6,4.8 -2.5,6.7 -0.9,1.9 -1.7,4 -2.4,6.2 -0.6,1.5 -0.8,2.9 -0.8,4.1 0,2.2 0.7,3.8 2,5 1.4,1.2 3,1.7 4.9,1.7 1.5,0 3,-0.5 4.4,-1.6 1.4,-1.1 2.7,-2.4 3.9,-3.9 1.2,-1.5 2.2,-3.1 3,-4.9 0.8,-1.7 1.4,-3.3 1.8,-4.6 0.1,-0.2 0.2,-0.6 0.3,-1.4 0.2,-0.8 0.3,-1.7 0.5,-2.7 0.2,-1 0.4,-2 0.6,-3.1 0.2,-1.1 0.4,-2 0.5,-2.7 0.2,-0.8 0.3,-1.6 0.5,-2.6 0.2,-1 0.5,-1.9 0.9,-2.8 0.4,-0.9 1,-1.6 1.6,-2.2 0.7,-0.6 1.5,-0.9 2.6,-0.9 1.3,0 2.1,0.4 2.6,1.1 0.4,0.7 0.6,1.6 0.6,2.6 -0.1,1 -0.2,2 -0.5,3 -0.3,1 -0.5,1.8 -0.7,2.4 -0.8,2.5 -1.6,4.7 -2.4,6.7 -0.8,2 -1.5,3.8 -2.2,5.2 -0.6,1.5 -1.1,2.6 -1.5,3.5 -0.4,0.9 -0.6,1.5 -0.6,1.8 0,2.6 0.6,4.5 1.7,5.6 1.1,1.1 2.3,1.7 3.6,1.7 2.2,0 3.9,-0.7 5.2,-2 1.3,-1.4 2.3,-3.9 2.9,-6.9 l 0.8,0 c 0.2,2.9 -0.1,5.3 -0.6,7.3 z"
|
||||
id="path3"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#2e3192" /></svg>
|
||||
|
Before Width: | Height: | Size: 3.8 KiB |
@@ -14,8 +14,6 @@ of modern terminals. It comes as one single, batteries-included, static binary w
|
||||
As the name indicates, micro aims to be somewhat of a successor to the nano editor by being easy to install and use in a pinch, but micro also aims to be
|
||||
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
|
||||
|
||||
Use Ctrl-q to quit, Ctrl-s to save, and Ctrl-g to open the in-editor help menu.
|
||||
|
||||
.SH OPTIONS
|
||||
.PP
|
||||
\-clean
|
||||
@@ -53,7 +51,7 @@ Enable debug mode (enables logging to ./log.txt)
|
||||
Show the version number and information
|
||||
.RE
|
||||
|
||||
Micro's plugins can be managed at the command line with the following commands.
|
||||
Micro's plugin's can be managed at the command line with the following commands.
|
||||
.RS 4
|
||||
|
||||
.PP
|
||||
@@ -121,5 +119,5 @@ and to report any newly encountered bugs you may find. We strive to correct
|
||||
bugs as swiftly as possible.
|
||||
|
||||
.SH COPYRIGHT
|
||||
Copyright \(co 2020 Zachary Yedidia, et al. MIT license.
|
||||
See \fBhttps://github.com/zyedidia/micro\fP for details.
|
||||
Copyright \(co 2020 Zachary Yedidia, et al.
|
||||
See /usr/share/doc/micro/LICENSE and /usr/share/doc/micro/AUTHORS for more information.
|
||||
|
||||
@@ -6,10 +6,10 @@ Comment=Edit text files in a terminal
|
||||
|
||||
Icon=micro
|
||||
Type=Application
|
||||
Categories=Utility;TextEditor;Development;
|
||||
Categories=terminal;TextEditor;
|
||||
Keywords=text;editor;syntax;terminal;
|
||||
|
||||
Exec=micro %F
|
||||
Exec=micro %U
|
||||
StartupNotify=false
|
||||
Terminal=true
|
||||
MimeType=text/plain;text/x-chdr;text/x-csrc;text/x-c++hdr;text/x-c++src;text/x-java;text/x-dsrc;text/x-pascal;text/x-perl;text/x-python;application/x-php;application/x-httpd-php3;application/x-httpd-php4;application/x-httpd-php5;application/xml;text/html;text/css;text/x-sql;text/x-diff;
|
||||
|
||||
@@ -41,9 +41,6 @@ func CleanConfig() {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Cleaning default settings")
|
||||
config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
|
||||
|
||||
// detect unused options
|
||||
var unusedOptions []string
|
||||
defaultSettings := config.DefaultAllSettings()
|
||||
@@ -97,13 +94,14 @@ func CleanConfig() {
|
||||
file, e := os.Open(fname)
|
||||
|
||||
if e == nil {
|
||||
defer file.Close()
|
||||
|
||||
decoder := gob.NewDecoder(file)
|
||||
err = decoder.Decode(&buffer)
|
||||
|
||||
if err != nil && f.Name() != "history" {
|
||||
badFiles = append(badFiles, fname)
|
||||
}
|
||||
file.Close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,21 +111,15 @@ func CleanConfig() {
|
||||
fmt.Printf("Removing badly formatted files in %s\n", filepath.Join(config.ConfigDir, "buffers"))
|
||||
|
||||
if shouldContinue() {
|
||||
removed := 0
|
||||
for _, f := range badFiles {
|
||||
err := os.Remove(f)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
removed++
|
||||
}
|
||||
|
||||
if removed == 0 {
|
||||
fmt.Println("Failed to remove files")
|
||||
} else {
|
||||
fmt.Printf("Removed %d badly formatted files\n", removed)
|
||||
}
|
||||
fmt.Println("Removed badly formatted files")
|
||||
fmt.Print("\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,11 +50,12 @@ func luaImportMicro() *lua.LTable {
|
||||
ulua.L.SetField(pkg, "CurPane", luar.New(ulua.L, func() action.Pane {
|
||||
return action.MainTab().CurPane()
|
||||
}))
|
||||
ulua.L.SetField(pkg, "CurTab", luar.New(ulua.L, action.MainTab))
|
||||
ulua.L.SetField(pkg, "CurTab", luar.New(ulua.L, func() *action.Tab {
|
||||
return action.MainTab()
|
||||
}))
|
||||
ulua.L.SetField(pkg, "Tabs", luar.New(ulua.L, func() *action.TabList {
|
||||
return action.Tabs
|
||||
}))
|
||||
ulua.L.SetField(pkg, "Lock", luar.New(ulua.L, ulua.Lock))
|
||||
|
||||
return pkg
|
||||
}
|
||||
@@ -118,9 +119,6 @@ func luaImportMicroBuffer() *lua.LTable {
|
||||
ulua.L.SetField(pkg, "Loc", luar.New(ulua.L, func(x, y int) buffer.Loc {
|
||||
return buffer.Loc{x, y}
|
||||
}))
|
||||
ulua.L.SetField(pkg, "SLoc", luar.New(ulua.L, func(line, row int) display.SLoc {
|
||||
return display.SLoc{line, row}
|
||||
}))
|
||||
ulua.L.SetField(pkg, "BTDefault", luar.New(ulua.L, buffer.BTDefault.Kind))
|
||||
ulua.L.SetField(pkg, "BTHelp", luar.New(ulua.L, buffer.BTHelp.Kind))
|
||||
ulua.L.SetField(pkg, "BTLog", luar.New(ulua.L, buffer.BTLog.Kind))
|
||||
@@ -147,9 +145,6 @@ func luaImportMicroUtil() *lua.LTable {
|
||||
ulua.L.SetField(pkg, "GetLeadingWhitespace", luar.New(ulua.L, util.LuaGetLeadingWhitespace))
|
||||
ulua.L.SetField(pkg, "IsWordChar", luar.New(ulua.L, util.LuaIsWordChar))
|
||||
ulua.L.SetField(pkg, "String", luar.New(ulua.L, util.String))
|
||||
ulua.L.SetField(pkg, "Unzip", luar.New(ulua.L, util.Unzip))
|
||||
ulua.L.SetField(pkg, "Version", luar.New(ulua.L, util.Version))
|
||||
ulua.L.SetField(pkg, "SemVersion", luar.New(ulua.L, util.SemVersion))
|
||||
ulua.L.SetField(pkg, "CharacterCountInString", luar.New(ulua.L, util.CharacterCountInString))
|
||||
ulua.L.SetField(pkg, "RuneStr", luar.New(ulua.L, func(r rune) string {
|
||||
return string(r)
|
||||
|
||||
@@ -4,14 +4,11 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
@@ -19,17 +16,16 @@ import (
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/v2/internal/action"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/clipboard"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
var (
|
||||
// Event channel
|
||||
events chan tcell.Event
|
||||
autosave chan bool
|
||||
|
||||
// Command line flags
|
||||
@@ -40,9 +36,6 @@ var (
|
||||
flagPlugin = flag.String("plugin", "", "Plugin command")
|
||||
flagClean = flag.Bool("clean", false, "Clean configuration directory")
|
||||
optionFlags map[string]*string
|
||||
|
||||
sigterm chan os.Signal
|
||||
sighup chan os.Signal
|
||||
)
|
||||
|
||||
func InitFlags() {
|
||||
@@ -139,7 +132,7 @@ func DoPluginFlags() {
|
||||
|
||||
// LoadInput determines which files should be loaded into buffers
|
||||
// based on the input stored in flag.Args()
|
||||
func LoadInput(args []string) []*buffer.Buffer {
|
||||
func LoadInput() []*buffer.Buffer {
|
||||
// There are a number of ways micro should start given its input
|
||||
|
||||
// 1. If it is given a files in flag.Args(), it should open those
|
||||
@@ -154,6 +147,7 @@ func LoadInput(args []string) []*buffer.Buffer {
|
||||
var filename string
|
||||
var input []byte
|
||||
var err error
|
||||
args := flag.Args()
|
||||
buffers := make([]*buffer.Buffer, 0, len(args))
|
||||
|
||||
btype := buffer.BTDefault
|
||||
@@ -268,35 +262,18 @@ func main() {
|
||||
|
||||
DoPluginFlags()
|
||||
|
||||
err = screen.Init()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Fatal: Micro could not initialize a Screen.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
sigterm = make(chan os.Signal, 1)
|
||||
sighup = make(chan os.Signal, 1)
|
||||
signal.Notify(sigterm, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT)
|
||||
signal.Notify(sighup, syscall.SIGHUP)
|
||||
|
||||
m := clipboard.SetMethod(config.GetGlobalOption("clipboard").(string))
|
||||
clipErr := clipboard.Initialize(m)
|
||||
screen.Init()
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if screen.Screen != nil {
|
||||
screen.Screen.Fini()
|
||||
}
|
||||
if e, ok := err.(*lua.ApiError); ok {
|
||||
fmt.Println("Lua API error:", e)
|
||||
} else {
|
||||
fmt.Println("Micro encountered an error:", errors.Wrap(err, 2).ErrorStack(), "\nIf you can reproduce this error, please report it at https://github.com/zyedidia/micro/issues")
|
||||
}
|
||||
screen.Screen.Fini()
|
||||
fmt.Println("Micro encountered an error:", err)
|
||||
// backup all open buffers
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
b.Backup()
|
||||
b.Backup(false)
|
||||
}
|
||||
// Print the stack trace too
|
||||
fmt.Print(errors.Wrap(err, 2).ErrorStack())
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
@@ -314,13 +291,7 @@ func main() {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
err = config.RunPluginFn("preinit")
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
args := flag.Args()
|
||||
b := LoadInput(args)
|
||||
b := LoadInput()
|
||||
|
||||
if len(b) == 0 {
|
||||
// No buffers to open
|
||||
@@ -336,21 +307,7 @@ func main() {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
err = config.RunPluginFn("postinit")
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
if clipErr != nil {
|
||||
log.Println(clipErr, " or change 'clipboard' option")
|
||||
}
|
||||
|
||||
if a := config.GetGlobalOption("autosave").(float64); a > 0 {
|
||||
config.SetAutoTime(int(a))
|
||||
config.StartAutoSave()
|
||||
}
|
||||
|
||||
screen.Events = make(chan tcell.Event)
|
||||
events = make(chan tcell.Event)
|
||||
|
||||
// Here is the event loop which runs in a separate thread
|
||||
go func() {
|
||||
@@ -359,7 +316,7 @@ func main() {
|
||||
e := screen.Screen.PollEvent()
|
||||
screen.Unlock()
|
||||
if e != nil {
|
||||
screen.Events <- e
|
||||
events <- e
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -372,12 +329,15 @@ func main() {
|
||||
|
||||
// wait for initial resize event
|
||||
select {
|
||||
case event := <-screen.Events:
|
||||
case event := <-events:
|
||||
action.Tabs.HandleEvent(event)
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
// time out after 10ms
|
||||
}
|
||||
|
||||
// Since this loop is very slow (waits for user input every time) it's
|
||||
// okay to be inefficient and run it via a function every time
|
||||
// We do this so we can recover from panics without crashing the editor
|
||||
for {
|
||||
DoEvent()
|
||||
}
|
||||
@@ -387,6 +347,16 @@ func main() {
|
||||
func DoEvent() {
|
||||
var event tcell.Event
|
||||
|
||||
// recover from errors without crashing the editor
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if e, ok := err.(*lua.ApiError); ok {
|
||||
screen.TermMessage("Lua API error:", e)
|
||||
} else {
|
||||
screen.TermMessage("Micro encountered an error:", errors.Wrap(err, 2).ErrorStack(), "\nIf you can reproduce this error, please report it at https://github.com/zyedidia/micro/issues")
|
||||
}
|
||||
}
|
||||
}()
|
||||
// Display everything
|
||||
screen.Screen.Fill(' ', config.DefStyle)
|
||||
screen.Screen.HideCursor()
|
||||
@@ -402,48 +372,22 @@ func DoEvent() {
|
||||
select {
|
||||
case f := <-shell.Jobs:
|
||||
// If a new job has finished while running in the background we should execute the callback
|
||||
ulua.Lock.Lock()
|
||||
f.Function(f.Output, f.Args)
|
||||
ulua.Lock.Unlock()
|
||||
case <-config.Autosave:
|
||||
ulua.Lock.Lock()
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
b.Save()
|
||||
}
|
||||
ulua.Lock.Unlock()
|
||||
case <-shell.CloseTerms:
|
||||
case event = <-screen.Events:
|
||||
case event = <-events:
|
||||
case <-screen.DrawChan():
|
||||
for len(screen.DrawChan()) > 0 {
|
||||
<-screen.DrawChan()
|
||||
}
|
||||
case <-sighup:
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
if !b.Modified() {
|
||||
b.Fini()
|
||||
}
|
||||
}
|
||||
os.Exit(0)
|
||||
case <-sigterm:
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
if !b.Modified() {
|
||||
b.Fini()
|
||||
}
|
||||
}
|
||||
|
||||
if screen.Screen != nil {
|
||||
screen.Screen.Fini()
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
ulua.Lock.Lock()
|
||||
// if event != nil {
|
||||
if action.InfoBar.HasPrompt {
|
||||
action.InfoBar.HandleEvent(event)
|
||||
} else {
|
||||
action.Tabs.HandleEvent(event)
|
||||
}
|
||||
// }
|
||||
ulua.Lock.Unlock()
|
||||
}
|
||||
|
||||
@@ -1,341 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zyedidia/micro/v2/internal/action"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
var tempDir string
|
||||
var sim tcell.SimulationScreen
|
||||
|
||||
func init() {
|
||||
screen.Events = make(chan tcell.Event, 8)
|
||||
}
|
||||
|
||||
func startup(args []string) (tcell.SimulationScreen, error) {
|
||||
var err error
|
||||
|
||||
tempDir, err = ioutil.TempDir("", "micro_test")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = config.InitConfigDir(tempDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.InitRuntimeFiles()
|
||||
err = config.ReadSettings()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = config.InitGlobalSettings()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s, err := screen.InitSimScreen()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
screen.Screen.Fini()
|
||||
fmt.Println("Micro encountered an error:", err)
|
||||
// backup all open buffers
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
b.Backup()
|
||||
}
|
||||
// Print the stack trace too
|
||||
log.Fatalf(errors.Wrap(err, 2).ErrorStack())
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
err = config.LoadAllPlugins()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
action.InitBindings()
|
||||
action.InitCommands()
|
||||
|
||||
err = config.InitColorscheme()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b := LoadInput(args)
|
||||
|
||||
if len(b) == 0 {
|
||||
return nil, errors.New("No buffers opened")
|
||||
}
|
||||
|
||||
action.InitTabs(b)
|
||||
action.InitGlobals()
|
||||
|
||||
err = config.RunPluginFn("init")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.InjectResize()
|
||||
handleEvent()
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func cleanup() {
|
||||
os.RemoveAll(tempDir)
|
||||
}
|
||||
|
||||
func handleEvent() {
|
||||
screen.Lock()
|
||||
e := screen.Screen.PollEvent()
|
||||
screen.Unlock()
|
||||
if e != nil {
|
||||
screen.Events <- e
|
||||
}
|
||||
DoEvent()
|
||||
}
|
||||
|
||||
func injectKey(key tcell.Key, r rune, mod tcell.ModMask) {
|
||||
sim.InjectKey(key, r, mod)
|
||||
handleEvent()
|
||||
}
|
||||
|
||||
func injectMouse(x, y int, buttons tcell.ButtonMask, mod tcell.ModMask) {
|
||||
sim.InjectMouse(x, y, buttons, mod)
|
||||
handleEvent()
|
||||
}
|
||||
|
||||
func injectString(str string) {
|
||||
// the tcell simulation screen event channel can only handle
|
||||
// 10 events at once, so we need to divide up the key events
|
||||
// into chunks of 10 and handle the 10 events before sending
|
||||
// another chunk of events
|
||||
iters := len(str) / 10
|
||||
extra := len(str) % 10
|
||||
|
||||
for i := 0; i < iters; i++ {
|
||||
s := i * 10
|
||||
e := i*10 + 10
|
||||
sim.InjectKeyBytes([]byte(str[s:e]))
|
||||
for i := 0; i < 10; i++ {
|
||||
handleEvent()
|
||||
}
|
||||
}
|
||||
|
||||
sim.InjectKeyBytes([]byte(str[len(str)-extra:]))
|
||||
for i := 0; i < extra; i++ {
|
||||
handleEvent()
|
||||
}
|
||||
}
|
||||
|
||||
func openFile(file string) {
|
||||
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
|
||||
injectString(fmt.Sprintf("open %s", file))
|
||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
||||
}
|
||||
|
||||
func createTestFile(name string, content string) (string, error) {
|
||||
testf, err := ioutil.TempFile("", name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := testf.Write([]byte(content)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := testf.Close(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return testf.Name(), nil
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
var err error
|
||||
sim, err = startup([]string{})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
retval := m.Run()
|
||||
cleanup()
|
||||
|
||||
os.Exit(retval)
|
||||
}
|
||||
|
||||
func TestSimpleEdit(t *testing.T) {
|
||||
file, err := createTestFile("micro_simple_edit_test", "base content")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(file)
|
||||
|
||||
openFile(file)
|
||||
|
||||
var buf *buffer.Buffer
|
||||
for _, b := range buffer.OpenBuffers {
|
||||
if b.Path == file {
|
||||
buf = b
|
||||
}
|
||||
}
|
||||
|
||||
if buf == nil {
|
||||
t.Errorf("Could not find buffer %s", file)
|
||||
return
|
||||
}
|
||||
|
||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
||||
injectKey(tcell.KeyUp, 0, tcell.ModNone)
|
||||
injectString("first line")
|
||||
|
||||
// test both kinds of backspace
|
||||
for i := 0; i < len("ne"); i++ {
|
||||
injectKey(tcell.KeyBackspace, rune(tcell.KeyBackspace), tcell.ModNone)
|
||||
}
|
||||
for i := 0; i < len(" li"); i++ {
|
||||
injectKey(tcell.KeyBackspace2, rune(tcell.KeyBackspace2), tcell.ModNone)
|
||||
}
|
||||
injectString("foobar")
|
||||
|
||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, "firstfoobar\nbase content\n", string(data))
|
||||
}
|
||||
|
||||
func TestMouse(t *testing.T) {
|
||||
file, err := createTestFile("micro_mouse_test", "base content")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(file)
|
||||
|
||||
openFile(file)
|
||||
|
||||
// buffer:
|
||||
// base content
|
||||
// the selections need to happen at different locations to avoid a double click
|
||||
injectMouse(3, 0, tcell.Button1, tcell.ModNone)
|
||||
injectKey(tcell.KeyLeft, 0, tcell.ModNone)
|
||||
injectMouse(0, 0, tcell.ButtonNone, tcell.ModNone)
|
||||
injectString("secondline")
|
||||
// buffer:
|
||||
// secondlinebase content
|
||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
||||
// buffer:
|
||||
// secondline
|
||||
// base content
|
||||
injectMouse(2, 0, tcell.Button1, tcell.ModNone)
|
||||
injectMouse(0, 0, tcell.ButtonNone, tcell.ModNone)
|
||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
||||
// buffer:
|
||||
//
|
||||
// secondline
|
||||
// base content
|
||||
injectKey(tcell.KeyUp, 0, tcell.ModNone)
|
||||
injectString("firstline")
|
||||
// buffer:
|
||||
// firstline
|
||||
// secondline
|
||||
// base content
|
||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, "firstline\nsecondline\nbase content\n", string(data))
|
||||
}
|
||||
|
||||
var srTestStart = `foo
|
||||
foo
|
||||
foofoofoo
|
||||
Ernleȝe foo æðelen
|
||||
`
|
||||
var srTest2 = `test_string
|
||||
test_string
|
||||
test_stringtest_stringtest_string
|
||||
Ernleȝe test_string æðelen
|
||||
`
|
||||
var srTest3 = `test_foo
|
||||
test_string
|
||||
test_footest_stringtest_foo
|
||||
Ernleȝe test_string æðelen
|
||||
`
|
||||
|
||||
func TestSearchAndReplace(t *testing.T) {
|
||||
file, err := createTestFile("micro_search_replace_test", srTestStart)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(file)
|
||||
|
||||
openFile(file)
|
||||
|
||||
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
|
||||
injectString(fmt.Sprintf("replaceall %s %s", "foo", "test_string"))
|
||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
||||
|
||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, srTest2, string(data))
|
||||
|
||||
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
|
||||
injectString(fmt.Sprintf("replace %s %s", "string", "foo"))
|
||||
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
|
||||
injectString("ynyny")
|
||||
injectKey(tcell.KeyEscape, 0, tcell.ModNone)
|
||||
|
||||
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
|
||||
|
||||
data, err = ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, srTest3, string(data))
|
||||
}
|
||||
|
||||
func TestMultiCursor(t *testing.T) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func TestSettingsPersistence(t *testing.T) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
// more tests (rendering, tabs, plugins)?
|
||||
9
go.mod
9
go.mod
@@ -10,16 +10,19 @@ require (
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff
|
||||
github.com/sergi/go-diff v1.1.0
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
|
||||
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd // indirect
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
|
||||
github.com/zyedidia/clipboard v1.0.3
|
||||
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834
|
||||
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3
|
||||
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5
|
||||
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d
|
||||
github.com/zyedidia/pty v2.0.0+incompatible // indirect
|
||||
github.com/zyedidia/tcell/v2 v2.0.7
|
||||
github.com/zyedidia/tcell v1.4.6
|
||||
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415
|
||||
golang.org/x/text v0.3.2
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
|
||||
gopkg.in/sourcemap.v1 v1.0.5 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.7
|
||||
layeh.com/gopher-luar v1.0.7
|
||||
@@ -27,6 +30,6 @@ require (
|
||||
|
||||
replace github.com/kballard/go-shellquote => github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655
|
||||
|
||||
replace github.com/mattn/go-runewidth => github.com/zyedidia/go-runewidth v0.0.12
|
||||
replace github.com/mattn/go-runewidth => github.com/p-e-w/go-runewidth v0.0.10-0.20200613030200-3e1705c5c059
|
||||
|
||||
go 1.11
|
||||
|
||||
21
go.sum
21
go.sum
@@ -27,27 +27,25 @@ github.com/p-e-w/go-runewidth v0.0.10-0.20200613030200-3e1705c5c059 h1:/+h2b6i15
|
||||
github.com/p-e-w/go-runewidth v0.0.10-0.20200613030200-3e1705c5c059/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
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/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff h1:+6NUiITWwE5q1KO6SAfUX918c+Tab0+tGAM/mtdlUyA=
|
||||
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk=
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0=
|
||||
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
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/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
|
||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||
github.com/zyedidia/clipboard v1.0.3 h1:F/nCDVYMdbDWTmY8s8cJl0tnwX32q96IF09JHM14bUI=
|
||||
github.com/zyedidia/clipboard v1.0.3/go.mod h1:zykFnZUXX0ErxqvYLUFEq7QDJKId8rmh2FgD0/Y8cjA=
|
||||
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834 h1:0nOfq3JwYRiY3+nwfWVQYEaXDmGCQgj3RKoqTifLzP4=
|
||||
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834/go.mod h1:zykFnZUXX0ErxqvYLUFEq7QDJKId8rmh2FgD0/Y8cjA=
|
||||
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3 h1:oMHjjTLfGXVuyOQBYj5/td9WC0mw4g1xDBPovIqmHew=
|
||||
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3/go.mod h1:YKbIYP//Eln8eDgAJGI3IDvR3s4Tv9Z9TGIOumiyQ5c=
|
||||
github.com/zyedidia/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/highlight v0.0.0-20170330143449-201131ce5cf5 h1:Zs6mpwXvlqpF9zHl5XaN0p5V4J9XvP+WBuiuXyIgqvc=
|
||||
@@ -58,10 +56,8 @@ github.com/zyedidia/poller v1.0.1 h1:Tt9S3AxAjXwWGNiC2TUdRJkQDZSzCBNVQ4xXiQ7440s
|
||||
github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
|
||||
github.com/zyedidia/pty v2.0.0+incompatible h1:Ou5vXL6tvjst+RV8sUFISbuKDnUJPhnpygApMFGweqw=
|
||||
github.com/zyedidia/pty v2.0.0+incompatible/go.mod h1:4y9l9yJZNxRa7GB/fB+mmDmGkG3CqmzLf4vUxGGotEA=
|
||||
github.com/zyedidia/tcell/v2 v2.0.6 h1:v0GoNpPYJ+Wbd1RiSL09SUFzoq4eVKTuT5awbW6aqGs=
|
||||
github.com/zyedidia/tcell/v2 v2.0.6/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
|
||||
github.com/zyedidia/tcell/v2 v2.0.7 h1:kFzCRq9jgx5lOXBT8fVZidbTgVuX0ws++aMCj/MTCYY=
|
||||
github.com/zyedidia/tcell/v2 v2.0.7/go.mod h1:i4NNlquIQXFeNecrOgxDQQJdu+7LmTi3g62asvmwUws=
|
||||
github.com/zyedidia/tcell v1.4.6 h1:8OYvZpUyqYQ3nigenBwOtnY3fXWEHekbm6QYchBeOxs=
|
||||
github.com/zyedidia/tcell v1.4.6/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
|
||||
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415 h1:752dTQ5OatJ9M5ULK2+9lor+nzyZz+LYDo3WGngg3Rc=
|
||||
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415/go.mod h1:8leT8G0Cm8NoJHdrrKHyR9MirWoF4YW7pZh06B6H+1E=
|
||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -71,6 +67,7 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
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=
|
||||
|
||||
@@ -7,39 +7,33 @@ import (
|
||||
"time"
|
||||
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"github.com/zyedidia/clipboard"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/clipboard"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
// ScrollUp is not an action
|
||||
func (h *BufPane) ScrollUp(n int) {
|
||||
v := h.GetView()
|
||||
v.StartLine = h.Scroll(v.StartLine, -n)
|
||||
h.SetView(v)
|
||||
if v.StartLine >= n {
|
||||
v.StartLine -= n
|
||||
h.SetView(v)
|
||||
} else {
|
||||
v.StartLine = 0
|
||||
}
|
||||
}
|
||||
|
||||
// ScrollDown is not an action
|
||||
func (h *BufPane) ScrollDown(n int) {
|
||||
v := h.GetView()
|
||||
v.StartLine = h.Scroll(v.StartLine, n)
|
||||
h.SetView(v)
|
||||
}
|
||||
|
||||
// If the user has scrolled past the last line, ScrollAdjust can be used
|
||||
// to shift the view so that the last line is at the bottom
|
||||
func (h *BufPane) ScrollAdjust() {
|
||||
v := h.GetView()
|
||||
end := h.SLocFromLoc(h.Buf.End())
|
||||
if h.Diff(v.StartLine, end) < h.BufView().Height-1 {
|
||||
v.StartLine = h.Scroll(end, -h.BufView().Height+1)
|
||||
if v.StartLine <= h.Buf.LinesNum()-1-n {
|
||||
v.StartLine += n
|
||||
h.SetView(v)
|
||||
}
|
||||
h.SetView(v)
|
||||
}
|
||||
|
||||
// MousePress is the event that should happen when a normal click happens
|
||||
@@ -65,7 +59,7 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
|
||||
h.doubleClick = false
|
||||
|
||||
h.Cursor.SelectLine()
|
||||
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
||||
h.Cursor.CopySelection("primary")
|
||||
} else {
|
||||
// Double click
|
||||
h.lastClickTime = time.Now()
|
||||
@@ -74,7 +68,7 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
|
||||
h.tripleClick = false
|
||||
|
||||
h.Cursor.SelectWord()
|
||||
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
||||
h.Cursor.CopySelection("primary")
|
||||
}
|
||||
} else {
|
||||
h.doubleClick = false
|
||||
@@ -98,7 +92,6 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
|
||||
|
||||
h.Cursor.StoreVisualX()
|
||||
h.lastLoc = mouseLoc
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -117,55 +110,22 @@ func (h *BufPane) ScrollDownAction() bool {
|
||||
// Center centers the view on the cursor
|
||||
func (h *BufPane) Center() bool {
|
||||
v := h.GetView()
|
||||
v.StartLine = h.Scroll(h.SLocFromLoc(h.Cursor.Loc), -h.BufView().Height/2)
|
||||
v.StartLine = h.Cursor.Y - v.Height/2
|
||||
if v.StartLine+v.Height > h.Buf.LinesNum() {
|
||||
v.StartLine = h.Buf.LinesNum() - v.Height
|
||||
}
|
||||
if v.StartLine < 0 {
|
||||
v.StartLine = 0
|
||||
}
|
||||
h.SetView(v)
|
||||
h.ScrollAdjust()
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// MoveCursorUp is not an action
|
||||
func (h *BufPane) MoveCursorUp(n int) {
|
||||
if !h.Buf.Settings["softwrap"].(bool) {
|
||||
h.Cursor.UpN(n)
|
||||
} else {
|
||||
vloc := h.VLocFromLoc(h.Cursor.Loc)
|
||||
sloc := h.Scroll(vloc.SLoc, -n)
|
||||
if sloc == vloc.SLoc {
|
||||
// we are at the beginning of buffer
|
||||
h.Cursor.Loc = h.Buf.Start()
|
||||
h.Cursor.LastVisualX = 0
|
||||
} else {
|
||||
vloc.SLoc = sloc
|
||||
vloc.VisualX = h.Cursor.LastVisualX
|
||||
h.Cursor.Loc = h.LocFromVLoc(vloc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MoveCursorDown is not an action
|
||||
func (h *BufPane) MoveCursorDown(n int) {
|
||||
if !h.Buf.Settings["softwrap"].(bool) {
|
||||
h.Cursor.DownN(n)
|
||||
} else {
|
||||
vloc := h.VLocFromLoc(h.Cursor.Loc)
|
||||
sloc := h.Scroll(vloc.SLoc, n)
|
||||
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
|
||||
} else {
|
||||
vloc.SLoc = sloc
|
||||
vloc.VisualX = h.Cursor.LastVisualX
|
||||
h.Cursor.Loc = h.LocFromVLoc(vloc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CursorUp moves the cursor up
|
||||
func (h *BufPane) CursorUp() bool {
|
||||
h.Cursor.Deselect(true)
|
||||
h.MoveCursorUp(1)
|
||||
h.Cursor.Up()
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
@@ -173,7 +133,7 @@ func (h *BufPane) CursorUp() bool {
|
||||
// CursorDown moves the cursor down
|
||||
func (h *BufPane) CursorDown() bool {
|
||||
h.Cursor.Deselect(true)
|
||||
h.MoveCursorDown(1)
|
||||
h.Cursor.Down()
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
@@ -251,7 +211,7 @@ func (h *BufPane) SelectUp() bool {
|
||||
if !h.Cursor.HasSelection() {
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
||||
}
|
||||
h.MoveCursorUp(1)
|
||||
h.Cursor.Up()
|
||||
h.Cursor.SelectTo(h.Cursor.Loc)
|
||||
h.Relocate()
|
||||
return true
|
||||
@@ -262,7 +222,7 @@ func (h *BufPane) SelectDown() bool {
|
||||
if !h.Cursor.HasSelection() {
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
||||
}
|
||||
h.MoveCursorDown(1)
|
||||
h.Cursor.Down()
|
||||
h.Cursor.SelectTo(h.Cursor.Loc)
|
||||
h.Relocate()
|
||||
return true
|
||||
@@ -856,54 +816,27 @@ func (h *BufPane) FindLiteral() bool {
|
||||
return h.find(false)
|
||||
}
|
||||
|
||||
// Search searches for a given string/regex in the buffer and selects the next
|
||||
// match if a match is found
|
||||
// This function affects lastSearch and lastSearchRegex (saved searches) for
|
||||
// use with FindNext and FindPrevious
|
||||
func (h *BufPane) Search(str string, useRegex bool, searchDown bool) error {
|
||||
match, found, err := h.Buf.FindNext(str, h.Buf.Start(), h.Buf.End(), h.Cursor.Loc, searchDown, useRegex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if found {
|
||||
h.Cursor.SetSelectionStart(match[0])
|
||||
h.Cursor.SetSelectionEnd(match[1])
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
|
||||
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
|
||||
h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
|
||||
h.lastSearch = str
|
||||
h.lastSearchRegex = useRegex
|
||||
h.Relocate()
|
||||
} else {
|
||||
h.Cursor.ResetSelection()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *BufPane) find(useRegex bool) bool {
|
||||
h.searchOrig = h.Cursor.Loc
|
||||
prompt := "Find: "
|
||||
if useRegex {
|
||||
prompt = "Find (regex): "
|
||||
}
|
||||
var eventCallback func(resp string)
|
||||
if h.Buf.Settings["incsearch"].(bool) {
|
||||
eventCallback = func(resp string) {
|
||||
match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
|
||||
if found {
|
||||
h.Cursor.SetSelectionStart(match[0])
|
||||
h.Cursor.SetSelectionEnd(match[1])
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
|
||||
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
|
||||
h.Cursor.GotoLoc(match[1])
|
||||
} else {
|
||||
h.Cursor.GotoLoc(h.searchOrig)
|
||||
h.Cursor.ResetSelection()
|
||||
}
|
||||
h.Relocate()
|
||||
InfoBar.Prompt(prompt, "", "Find", func(resp string) {
|
||||
// Event callback
|
||||
match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
|
||||
if found {
|
||||
h.Cursor.SetSelectionStart(match[0])
|
||||
h.Cursor.SetSelectionEnd(match[1])
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
|
||||
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
|
||||
h.Cursor.GotoLoc(match[1])
|
||||
} else {
|
||||
h.Cursor.GotoLoc(h.searchOrig)
|
||||
h.Cursor.ResetSelection()
|
||||
}
|
||||
}
|
||||
InfoBar.Prompt(prompt, "", "Find", eventCallback, func(resp string, canceled bool) {
|
||||
h.Relocate()
|
||||
}, func(resp string, canceled bool) {
|
||||
// Finished callback
|
||||
if !canceled {
|
||||
match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
|
||||
@@ -1004,9 +937,13 @@ func (h *BufPane) Redo() bool {
|
||||
// Copy the selection to the system clipboard
|
||||
func (h *BufPane) Copy() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||
h.Cursor.CopySelection("clipboard")
|
||||
h.freshClip = true
|
||||
InfoBar.Message("Copied selection")
|
||||
if clipboard.Unsupported {
|
||||
InfoBar.Message("Copied selection (install xclip for external clipboard)")
|
||||
} else {
|
||||
InfoBar.Message("Copied selection")
|
||||
}
|
||||
}
|
||||
h.Relocate()
|
||||
return true
|
||||
@@ -1018,9 +955,13 @@ func (h *BufPane) CopyLine() bool {
|
||||
return false
|
||||
} else {
|
||||
h.Cursor.SelectLine()
|
||||
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||
h.Cursor.CopySelection("clipboard")
|
||||
h.freshClip = true
|
||||
InfoBar.Message("Copied line")
|
||||
if clipboard.Unsupported {
|
||||
InfoBar.Message("Copied line (install xclip for external clipboard)")
|
||||
} else {
|
||||
InfoBar.Message("Copied line")
|
||||
}
|
||||
}
|
||||
h.Cursor.Deselect(true)
|
||||
h.Relocate()
|
||||
@@ -1033,15 +974,15 @@ func (h *BufPane) CutLine() bool {
|
||||
if !h.Cursor.HasSelection() {
|
||||
return false
|
||||
}
|
||||
if h.freshClip {
|
||||
if h.freshClip == true {
|
||||
if h.Cursor.HasSelection() {
|
||||
if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
|
||||
InfoBar.Error(err)
|
||||
if clip, err := clipboard.ReadAll("clipboard"); err != nil {
|
||||
// messenger.Error(err)
|
||||
} else {
|
||||
clipboard.WriteMulti(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
|
||||
clipboard.WriteAll(clip+string(h.Cursor.GetSelection()), "clipboard")
|
||||
}
|
||||
}
|
||||
} else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || !h.freshClip {
|
||||
} else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || h.freshClip == false {
|
||||
h.Copy()
|
||||
}
|
||||
h.freshClip = true
|
||||
@@ -1056,7 +997,7 @@ func (h *BufPane) CutLine() bool {
|
||||
// Cut the selection to the system clipboard
|
||||
func (h *BufPane) Cut() bool {
|
||||
if h.Cursor.HasSelection() {
|
||||
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
||||
h.Cursor.CopySelection("clipboard")
|
||||
h.Cursor.DeleteSelection()
|
||||
h.Cursor.ResetSelection()
|
||||
h.freshClip = true
|
||||
@@ -1182,24 +1123,16 @@ func (h *BufPane) MoveLinesDown() bool {
|
||||
// Paste whatever is in the system clipboard into the buffer
|
||||
// Delete and paste if the user has a selection
|
||||
func (h *BufPane) Paste() bool {
|
||||
clip, err := clipboard.ReadMulti(clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
} else {
|
||||
h.paste(clip)
|
||||
}
|
||||
clip, _ := clipboard.ReadAll("clipboard")
|
||||
h.paste(clip)
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// PastePrimary pastes from the primary clipboard (only use on linux)
|
||||
func (h *BufPane) PastePrimary() bool {
|
||||
clip, err := clipboard.ReadMulti(clipboard.PrimaryReg, h.Cursor.Num, h.Buf.NumCursors())
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
} else {
|
||||
h.paste(clip)
|
||||
}
|
||||
clip, _ := clipboard.ReadAll("primary")
|
||||
h.paste(clip)
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
@@ -1208,7 +1141,7 @@ func (h *BufPane) paste(clip string) {
|
||||
if h.Buf.Settings["smartpaste"].(bool) {
|
||||
if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 {
|
||||
leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
|
||||
clip = strings.ReplaceAll(clip, "\n", "\n"+string(leadingWS))
|
||||
clip = strings.Replace(clip, "\n", "\n"+string(leadingWS), -1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1220,7 +1153,11 @@ func (h *BufPane) paste(clip string) {
|
||||
h.Buf.Insert(h.Cursor.Loc, clip)
|
||||
// h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
|
||||
h.freshClip = false
|
||||
InfoBar.Message("Pasted clipboard")
|
||||
if clipboard.Unsupported {
|
||||
InfoBar.Message("Pasted clipboard (install xclip for external clipboard)")
|
||||
} else {
|
||||
InfoBar.Message("Pasted clipboard")
|
||||
}
|
||||
}
|
||||
|
||||
// JumpToMatchingBrace moves the cursor to the matching brace if it is
|
||||
@@ -1237,7 +1174,6 @@ func (h *BufPane) JumpToMatchingBrace() bool {
|
||||
} else {
|
||||
h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
|
||||
}
|
||||
break
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@@ -1282,29 +1218,45 @@ func (h *BufPane) JumpLine() bool {
|
||||
// Start moves the viewport to the start of the buffer
|
||||
func (h *BufPane) Start() bool {
|
||||
v := h.GetView()
|
||||
v.StartLine = display.SLoc{0, 0}
|
||||
v.StartLine = 0
|
||||
h.SetView(v)
|
||||
return true
|
||||
}
|
||||
|
||||
// End moves the viewport to the end of the buffer
|
||||
func (h *BufPane) End() bool {
|
||||
// TODO: softwrap problems?
|
||||
v := h.GetView()
|
||||
v.StartLine = h.Scroll(h.SLocFromLoc(h.Buf.End()), -h.BufView().Height+1)
|
||||
h.SetView(v)
|
||||
if v.Height > h.Buf.LinesNum() {
|
||||
v.StartLine = 0
|
||||
h.SetView(v)
|
||||
} else {
|
||||
v.StartLine = h.Buf.LinesNum() - v.Height
|
||||
h.SetView(v)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// PageUp scrolls the view up a page
|
||||
func (h *BufPane) PageUp() bool {
|
||||
h.ScrollUp(h.BufView().Height)
|
||||
v := h.GetView()
|
||||
if v.StartLine > v.Height {
|
||||
h.ScrollUp(v.Height)
|
||||
} else {
|
||||
v.StartLine = 0
|
||||
}
|
||||
h.SetView(v)
|
||||
return true
|
||||
}
|
||||
|
||||
// PageDown scrolls the view down a page
|
||||
func (h *BufPane) PageDown() bool {
|
||||
h.ScrollDown(h.BufView().Height)
|
||||
h.ScrollAdjust()
|
||||
v := h.GetView()
|
||||
if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height {
|
||||
h.ScrollDown(v.Height)
|
||||
} else if h.Buf.LinesNum() >= v.Height {
|
||||
v.StartLine = h.Buf.LinesNum() - v.Height
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1313,7 +1265,7 @@ func (h *BufPane) SelectPageUp() bool {
|
||||
if !h.Cursor.HasSelection() {
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
||||
}
|
||||
h.MoveCursorUp(h.BufView().Height)
|
||||
h.Cursor.UpN(h.GetView().Height)
|
||||
h.Cursor.SelectTo(h.Cursor.Loc)
|
||||
h.Relocate()
|
||||
return true
|
||||
@@ -1324,7 +1276,7 @@ func (h *BufPane) SelectPageDown() bool {
|
||||
if !h.Cursor.HasSelection() {
|
||||
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
||||
}
|
||||
h.MoveCursorDown(h.BufView().Height)
|
||||
h.Cursor.DownN(h.GetView().Height)
|
||||
h.Cursor.SelectTo(h.Cursor.Loc)
|
||||
h.Relocate()
|
||||
return true
|
||||
@@ -1339,7 +1291,7 @@ func (h *BufPane) CursorPageUp() bool {
|
||||
h.Cursor.ResetSelection()
|
||||
h.Cursor.StoreVisualX()
|
||||
}
|
||||
h.MoveCursorUp(h.BufView().Height)
|
||||
h.Cursor.UpN(h.GetView().Height)
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
@@ -1353,21 +1305,34 @@ func (h *BufPane) CursorPageDown() bool {
|
||||
h.Cursor.ResetSelection()
|
||||
h.Cursor.StoreVisualX()
|
||||
}
|
||||
h.MoveCursorDown(h.BufView().Height)
|
||||
h.Cursor.DownN(h.GetView().Height)
|
||||
h.Relocate()
|
||||
return true
|
||||
}
|
||||
|
||||
// HalfPageUp scrolls the view up half a page
|
||||
func (h *BufPane) HalfPageUp() bool {
|
||||
h.ScrollUp(h.BufView().Height / 2)
|
||||
v := h.GetView()
|
||||
if v.StartLine > v.Height/2 {
|
||||
h.ScrollUp(v.Height / 2)
|
||||
} else {
|
||||
v.StartLine = 0
|
||||
}
|
||||
h.SetView(v)
|
||||
return true
|
||||
}
|
||||
|
||||
// HalfPageDown scrolls the view down half a page
|
||||
func (h *BufPane) HalfPageDown() bool {
|
||||
h.ScrollDown(h.BufView().Height / 2)
|
||||
h.ScrollAdjust()
|
||||
v := h.GetView()
|
||||
if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height/2 {
|
||||
h.ScrollDown(v.Height / 2)
|
||||
} else {
|
||||
if h.Buf.LinesNum() >= v.Height {
|
||||
v.StartLine = h.Buf.LinesNum() - v.Height
|
||||
}
|
||||
}
|
||||
h.SetView(v)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1454,55 +1419,39 @@ func (h *BufPane) Escape() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Deselect deselects on the current cursor
|
||||
func (h *BufPane) Deselect() bool {
|
||||
h.Cursor.Deselect(true)
|
||||
return true
|
||||
}
|
||||
|
||||
// ClearInfo clears the infobar
|
||||
func (h *BufPane) ClearInfo() bool {
|
||||
InfoBar.Message("")
|
||||
return true
|
||||
}
|
||||
|
||||
// ForceQuit closes the current tab or view even if there are unsaved changes
|
||||
// (no prompt)
|
||||
func (h *BufPane) ForceQuit() bool {
|
||||
h.Buf.Close()
|
||||
if len(MainTab().Panes) > 1 {
|
||||
h.Unsplit()
|
||||
} else if len(Tabs.List) > 1 {
|
||||
Tabs.RemoveTab(h.splitID)
|
||||
} else {
|
||||
screen.Screen.Fini()
|
||||
InfoBar.Close()
|
||||
runtime.Goexit()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Quit this will close the current tab or view that is open
|
||||
func (h *BufPane) Quit() bool {
|
||||
quit := func() {
|
||||
h.Buf.Close()
|
||||
if len(MainTab().Panes) > 1 {
|
||||
h.Unsplit()
|
||||
} else if len(Tabs.List) > 1 {
|
||||
Tabs.RemoveTab(h.splitID)
|
||||
} else {
|
||||
screen.Screen.Fini()
|
||||
InfoBar.Close()
|
||||
runtime.Goexit()
|
||||
}
|
||||
}
|
||||
if h.Buf.Modified() {
|
||||
if config.GlobalSettings["autosave"].(float64) > 0 {
|
||||
// autosave on means we automatically save when quitting
|
||||
h.SaveCB("Quit", func() {
|
||||
h.ForceQuit()
|
||||
quit()
|
||||
})
|
||||
} else {
|
||||
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
|
||||
if !canceled && !yes {
|
||||
h.ForceQuit()
|
||||
quit()
|
||||
} else if !canceled && yes {
|
||||
h.SaveCB("Quit", func() {
|
||||
h.ForceQuit()
|
||||
quit()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
h.ForceQuit()
|
||||
quit()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -3,26 +3,18 @@ package action
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/zyedidia/json5"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
var Binder = map[string]func(e Event, action string){
|
||||
"command": InfoMapEvent,
|
||||
"buffer": BufMapEvent,
|
||||
"terminal": TermMapEvent,
|
||||
}
|
||||
|
||||
func createBindingsIfNotExist(fname string) {
|
||||
if _, e := os.Stat(fname); os.IsNotExist(e) {
|
||||
ioutil.WriteFile(fname, []byte("{}"), 0644)
|
||||
@@ -31,7 +23,10 @@ func createBindingsIfNotExist(fname string) {
|
||||
|
||||
// InitBindings intializes the bindings map by reading from bindings.json
|
||||
func InitBindings() {
|
||||
var parsed map[string]interface{}
|
||||
config.Bindings = DefaultBindings()
|
||||
|
||||
var parsed map[string]string
|
||||
defaults := DefaultBindings()
|
||||
|
||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||
createBindingsIfNotExist(filename)
|
||||
@@ -49,89 +44,34 @@ func InitBindings() {
|
||||
}
|
||||
}
|
||||
|
||||
for p, bind := range Binder {
|
||||
defaults := DefaultBindings(p)
|
||||
|
||||
for k, v := range defaults {
|
||||
BindKey(k, v, bind)
|
||||
}
|
||||
for k, v := range defaults {
|
||||
BindKey(k, v)
|
||||
}
|
||||
|
||||
for k, v := range parsed {
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
BindKey(k, val, Binder["buffer"])
|
||||
case map[string]interface{}:
|
||||
bind, ok := Binder[k]
|
||||
if !ok || bind == nil {
|
||||
screen.TermMessage(fmt.Sprintf("%s is not a valid pane type", k))
|
||||
continue
|
||||
}
|
||||
for e, a := range val {
|
||||
s, ok := a.(string)
|
||||
if !ok {
|
||||
screen.TermMessage("Error reading bindings.json: non-string and non-map entry", k)
|
||||
} else {
|
||||
BindKey(e, s, bind)
|
||||
}
|
||||
}
|
||||
default:
|
||||
screen.TermMessage("Error reading bindings.json: non-string and non-map entry", k)
|
||||
}
|
||||
BindKey(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func BindKey(k, v string, bind func(e Event, a string)) {
|
||||
event, err := findEvent(k)
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
return
|
||||
func BindKey(k, v string) {
|
||||
event, ok := findEvent(k)
|
||||
if !ok {
|
||||
screen.TermMessage(k, "is not a bindable event")
|
||||
}
|
||||
|
||||
bind(event, v)
|
||||
|
||||
// switch e := event.(type) {
|
||||
// case KeyEvent:
|
||||
// InfoMapKey(e, v)
|
||||
// case KeySequenceEvent:
|
||||
// InfoMapKey(e, v)
|
||||
// case MouseEvent:
|
||||
// InfoMapMouse(e, v)
|
||||
// case RawEvent:
|
||||
// InfoMapKey(e, v)
|
||||
// }
|
||||
}
|
||||
|
||||
var r = regexp.MustCompile("<(.+?)>")
|
||||
|
||||
func findEvents(k string) (b KeySequenceEvent, ok bool, err error) {
|
||||
var events []Event = nil
|
||||
for len(k) > 0 {
|
||||
groups := r.FindStringSubmatchIndex(k)
|
||||
|
||||
if len(groups) > 3 {
|
||||
if events == nil {
|
||||
events = make([]Event, 0, 3)
|
||||
}
|
||||
|
||||
e, ok := findSingleEvent(k[groups[2]:groups[3]])
|
||||
if !ok {
|
||||
return KeySequenceEvent{}, false, errors.New("Invalid event " + k[groups[2]:groups[3]])
|
||||
}
|
||||
|
||||
events = append(events, e)
|
||||
|
||||
k = k[groups[3]+1:]
|
||||
} else {
|
||||
return KeySequenceEvent{}, false, nil
|
||||
}
|
||||
switch e := event.(type) {
|
||||
case KeyEvent:
|
||||
BufMapKey(e, v)
|
||||
case MouseEvent:
|
||||
BufMapMouse(e, v)
|
||||
case RawEvent:
|
||||
BufMapKey(e, v)
|
||||
}
|
||||
|
||||
return KeySequenceEvent{events}, true, nil
|
||||
config.Bindings[k] = v
|
||||
}
|
||||
|
||||
// findSingleEvent will find binding Key 'b' using string 'k'
|
||||
func findSingleEvent(k string) (b Event, ok bool) {
|
||||
// findEvent will find binding Key 'b' using string 'k'
|
||||
func findEvent(k string) (b Event, ok bool) {
|
||||
modifiers := tcell.ModNone
|
||||
|
||||
// First, we'll strip off all the modifiers in the name and add them to the
|
||||
@@ -162,7 +102,7 @@ modSearch:
|
||||
}
|
||||
}
|
||||
|
||||
if k == "" {
|
||||
if len(k) == 0 {
|
||||
return KeyEvent{}, false
|
||||
}
|
||||
|
||||
@@ -222,28 +162,11 @@ modSearch:
|
||||
return KeyEvent{}, false
|
||||
}
|
||||
|
||||
func findEvent(k string) (Event, error) {
|
||||
var event Event
|
||||
event, ok, err := findEvents(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
event, ok = findSingleEvent(k)
|
||||
if !ok {
|
||||
return nil, errors.New(k + " is not a bindable event")
|
||||
}
|
||||
}
|
||||
|
||||
return event, nil
|
||||
}
|
||||
|
||||
// TryBindKey tries to bind a key by writing to config.ConfigDir/bindings.json
|
||||
// Returns true if the keybinding already existed and a possible error
|
||||
func TryBindKey(k, v string, overwrite bool) (bool, error) {
|
||||
var e error
|
||||
var parsed map[string]interface{}
|
||||
var parsed map[string]string
|
||||
|
||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||
createBindingsIfNotExist(filename)
|
||||
@@ -258,14 +181,14 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
|
||||
return false, errors.New("Error reading bindings.json: " + err.Error())
|
||||
}
|
||||
|
||||
key, err := findEvent(k)
|
||||
if err != nil {
|
||||
return false, err
|
||||
key, ok := findEvent(k)
|
||||
if !ok {
|
||||
return false, errors.New("Invalid event " + k)
|
||||
}
|
||||
|
||||
found := false
|
||||
for ev := range parsed {
|
||||
if e, err := findEvent(ev); err == nil {
|
||||
if e, ok := findEvent(ev); ok {
|
||||
if e == key {
|
||||
if overwrite {
|
||||
parsed[ev] = v
|
||||
@@ -282,7 +205,7 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
|
||||
parsed[k] = v
|
||||
}
|
||||
|
||||
BindKey(k, v, Binder["buffer"])
|
||||
BindKey(k, v)
|
||||
|
||||
txt, _ := json.MarshalIndent(parsed, "", " ")
|
||||
return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
||||
@@ -293,7 +216,7 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
|
||||
// UnbindKey removes the binding for a key from the bindings.json file
|
||||
func UnbindKey(k string) error {
|
||||
var e error
|
||||
var parsed map[string]interface{}
|
||||
var parsed map[string]string
|
||||
|
||||
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
||||
createBindingsIfNotExist(filename)
|
||||
@@ -308,13 +231,13 @@ func UnbindKey(k string) error {
|
||||
return errors.New("Error reading bindings.json: " + err.Error())
|
||||
}
|
||||
|
||||
key, err := findEvent(k)
|
||||
if err != nil {
|
||||
return err
|
||||
key, ok := findEvent(k)
|
||||
if !ok {
|
||||
return errors.New("Invalid event " + k)
|
||||
}
|
||||
|
||||
for ev := range parsed {
|
||||
if e, err := findEvent(ev); err == nil {
|
||||
if e, ok := findEvent(ev); ok {
|
||||
if e == key {
|
||||
delete(parsed, ev)
|
||||
break
|
||||
@@ -322,12 +245,12 @@ func UnbindKey(k string) error {
|
||||
}
|
||||
}
|
||||
|
||||
defaults := DefaultBindings("buffer")
|
||||
defaults := DefaultBindings()
|
||||
if a, ok := defaults[k]; ok {
|
||||
BindKey(k, a, Binder["buffer"])
|
||||
} else if _, ok := config.Bindings["buffer"][k]; ok {
|
||||
BindKey(k, a)
|
||||
} else if _, ok := config.Bindings[k]; ok {
|
||||
BufUnmap(key)
|
||||
delete(config.Bindings["buffer"], k)
|
||||
delete(config.Bindings, k)
|
||||
}
|
||||
|
||||
txt, _ := json.MarshalIndent(parsed, "", " ")
|
||||
@@ -337,9 +260,9 @@ func UnbindKey(k string) error {
|
||||
}
|
||||
|
||||
var mouseEvents = map[string]tcell.ButtonMask{
|
||||
"MouseLeft": tcell.ButtonPrimary,
|
||||
"MouseMiddle": tcell.ButtonMiddle,
|
||||
"MouseRight": tcell.ButtonSecondary,
|
||||
"MouseLeft": tcell.Button1,
|
||||
"MouseMiddle": tcell.Button2,
|
||||
"MouseRight": tcell.Button3,
|
||||
"MouseWheelUp": tcell.WheelUp,
|
||||
"MouseWheelDown": tcell.WheelDown,
|
||||
"MouseWheelLeft": tcell.WheelLeft,
|
||||
|
||||
@@ -8,33 +8,24 @@ import (
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/clipboard"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
type BufKeyAction func(*BufPane) bool
|
||||
type BufMouseAction func(*BufPane, *tcell.EventMouse) bool
|
||||
|
||||
var BufBindings *KeyTree
|
||||
|
||||
func BufKeyActionGeneral(a BufKeyAction) PaneKeyAction {
|
||||
return func(p Pane) bool {
|
||||
return a(p.(*BufPane))
|
||||
}
|
||||
}
|
||||
|
||||
func BufMouseActionGeneral(a BufMouseAction) PaneMouseAction {
|
||||
return func(p Pane, me *tcell.EventMouse) bool {
|
||||
return a(p.(*BufPane), me)
|
||||
}
|
||||
}
|
||||
var BufKeyBindings map[Event]BufKeyAction
|
||||
var BufKeyStrings map[Event]string
|
||||
var BufMouseBindings map[MouseEvent]BufMouseAction
|
||||
|
||||
func init() {
|
||||
BufBindings = NewKeyTree()
|
||||
BufKeyBindings = make(map[Event]BufKeyAction)
|
||||
BufKeyStrings = make(map[Event]string)
|
||||
BufMouseBindings = make(map[MouseEvent]BufMouseAction)
|
||||
}
|
||||
|
||||
func LuaAction(fn string) func(*BufPane) bool {
|
||||
@@ -60,19 +51,9 @@ func LuaAction(fn string) func(*BufPane) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// BufMapKey maps an event to an action
|
||||
func BufMapEvent(k Event, action string) {
|
||||
config.Bindings["buffer"][k.Name()] = action
|
||||
|
||||
switch e := k.(type) {
|
||||
case KeyEvent, KeySequenceEvent, RawEvent:
|
||||
bufMapKey(e, action)
|
||||
case MouseEvent:
|
||||
bufMapMouse(e, action)
|
||||
}
|
||||
}
|
||||
|
||||
func bufMapKey(k Event, action string) {
|
||||
// BufMapKey maps a key event to an action
|
||||
func BufMapKey(k Event, action string) {
|
||||
BufKeyStrings[k] = action
|
||||
var actionfns []func(*BufPane) bool
|
||||
var names []string
|
||||
var types []byte
|
||||
@@ -127,11 +108,10 @@ func bufMapKey(k Event, action string) {
|
||||
}
|
||||
actionfns = append(actionfns, afn)
|
||||
}
|
||||
bufAction := func(h *BufPane) bool {
|
||||
BufKeyBindings[k] = func(h *BufPane) bool {
|
||||
cursors := h.Buf.GetCursors()
|
||||
success := true
|
||||
for i, a := range actionfns {
|
||||
innerSuccess := true
|
||||
for j, c := range cursors {
|
||||
if c == nil {
|
||||
continue
|
||||
@@ -139,41 +119,37 @@ func bufMapKey(k Event, action string) {
|
||||
h.Buf.SetCurCursor(c.Num)
|
||||
h.Cursor = c
|
||||
if i == 0 || (success && types[i-1] == '&') || (!success && types[i-1] == '|') || (types[i-1] == ',') {
|
||||
innerSuccess = innerSuccess && h.execAction(a, names[i], j)
|
||||
success = h.execAction(a, names[i], j)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// if the action changed the current pane, update the reference
|
||||
h = MainTab().CurPane()
|
||||
success = innerSuccess
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
BufBindings.RegisterKeyBinding(k, BufKeyActionGeneral(bufAction))
|
||||
}
|
||||
|
||||
// BufMapMouse maps a mouse event to an action
|
||||
func bufMapMouse(k MouseEvent, action string) {
|
||||
func BufMapMouse(k MouseEvent, action string) {
|
||||
if f, ok := BufMouseActions[action]; ok {
|
||||
BufBindings.RegisterMouseBinding(k, BufMouseActionGeneral(f))
|
||||
BufMouseBindings[k] = f
|
||||
} else {
|
||||
// TODO
|
||||
// delete(BufMouseBindings, k)
|
||||
bufMapKey(k, action)
|
||||
delete(BufMouseBindings, k)
|
||||
BufMapKey(k, action)
|
||||
}
|
||||
}
|
||||
|
||||
// BufUnmap unmaps a key or mouse event from any action
|
||||
func BufUnmap(k Event) {
|
||||
// TODO
|
||||
// delete(BufKeyBindings, k)
|
||||
//
|
||||
// switch e := k.(type) {
|
||||
// case MouseEvent:
|
||||
// delete(BufMouseBindings, e)
|
||||
// }
|
||||
delete(BufKeyBindings, k)
|
||||
delete(BufKeyStrings, k)
|
||||
|
||||
switch e := k.(type) {
|
||||
case MouseEvent:
|
||||
delete(BufMouseBindings, e)
|
||||
}
|
||||
}
|
||||
|
||||
// The BufPane connects the buffer and the window
|
||||
@@ -184,13 +160,9 @@ func BufUnmap(k Event) {
|
||||
type BufPane struct {
|
||||
display.BWindow
|
||||
|
||||
// Buf is the buffer this BufPane views
|
||||
Buf *buffer.Buffer
|
||||
// Bindings stores the association of key events and actions
|
||||
bindings *KeyTree
|
||||
|
||||
// Cursor is the currently active buffer cursor
|
||||
Cursor *buffer.Cursor
|
||||
Cursor *buffer.Cursor // the active cursor
|
||||
|
||||
// Since tcell doesn't differentiate between a mouse release event
|
||||
// and a mouse move event with no keys pressed, we need to keep
|
||||
@@ -347,7 +319,7 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||
case *tcell.EventKey:
|
||||
ke := KeyEvent{
|
||||
code: e.Key(),
|
||||
mod: metaToAlt(e.Modifiers()),
|
||||
mod: e.Modifiers(),
|
||||
r: e.Rune(),
|
||||
}
|
||||
|
||||
@@ -388,7 +360,7 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||
// h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
||||
// }
|
||||
if h.Cursor.HasSelection() {
|
||||
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
||||
h.Cursor.CopySelection("primary")
|
||||
}
|
||||
h.mouseReleased = true
|
||||
}
|
||||
@@ -397,7 +369,7 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||
if !cancel {
|
||||
me := MouseEvent{
|
||||
btn: e.Buttons(),
|
||||
mod: metaToAlt(e.Modifiers()),
|
||||
mod: e.Modifiers(),
|
||||
}
|
||||
h.DoMouseEvent(me, e)
|
||||
}
|
||||
@@ -421,26 +393,13 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BufPane) Bindings() *KeyTree {
|
||||
if h.bindings != nil {
|
||||
return h.bindings
|
||||
}
|
||||
return BufBindings
|
||||
}
|
||||
|
||||
// DoKeyEvent executes a key event by finding the action it is bound
|
||||
// to and executing it (possibly multiple times for multiple cursors)
|
||||
func (h *BufPane) DoKeyEvent(e Event) bool {
|
||||
binds := h.Bindings()
|
||||
action, more := binds.NextEvent(e, nil)
|
||||
if action != nil && !more {
|
||||
action(h)
|
||||
binds.ResetEvents()
|
||||
return true
|
||||
} else if action == nil && !more {
|
||||
binds.ResetEvents()
|
||||
if action, ok := BufKeyBindings[e]; ok {
|
||||
return action(h)
|
||||
}
|
||||
return more
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int) bool {
|
||||
@@ -474,34 +433,22 @@ func (h *BufPane) completeAction(action string) {
|
||||
}
|
||||
|
||||
func (h *BufPane) HasKeyEvent(e Event) bool {
|
||||
// TODO
|
||||
return true
|
||||
// _, ok := BufKeyBindings[e]
|
||||
// return ok
|
||||
_, ok := BufKeyBindings[e]
|
||||
return ok
|
||||
}
|
||||
|
||||
// DoMouseEvent executes a mouse event by finding the action it is bound
|
||||
// to and executing it
|
||||
func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
|
||||
binds := h.Bindings()
|
||||
action, _ := binds.NextEvent(e, te)
|
||||
if action != nil {
|
||||
action(h)
|
||||
binds.ResetEvents()
|
||||
if action, ok := BufMouseBindings[e]; ok {
|
||||
if action(h, te) {
|
||||
h.Relocate()
|
||||
}
|
||||
return true
|
||||
} else if h.HasKeyEvent(e) {
|
||||
return h.DoKeyEvent(e)
|
||||
}
|
||||
// TODO
|
||||
return false
|
||||
|
||||
// if action, ok := BufMouseBindings[e]; ok {
|
||||
// if action(h, te) {
|
||||
// h.Relocate()
|
||||
// }
|
||||
// return true
|
||||
// } else if h.HasKeyEvent(e) {
|
||||
// return h.DoKeyEvent(e)
|
||||
// }
|
||||
// return false
|
||||
}
|
||||
|
||||
// DoRuneInsert inserts a given rune into the current buffer
|
||||
@@ -666,7 +613,6 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||
"Escape": (*BufPane).Escape,
|
||||
"Quit": (*BufPane).Quit,
|
||||
"QuitAll": (*BufPane).QuitAll,
|
||||
"ForceQuit": (*BufPane).ForceQuit,
|
||||
"AddTab": (*BufPane).AddTab,
|
||||
"PreviousTab": (*BufPane).PreviousTab,
|
||||
"NextTab": (*BufPane).NextTab,
|
||||
@@ -689,8 +635,6 @@ var BufKeyActions = map[string]BufKeyAction{
|
||||
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
|
||||
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
|
||||
"JumpLine": (*BufPane).JumpLine,
|
||||
"Deselect": (*BufPane).Deselect,
|
||||
"ClearInfo": (*BufPane).ClearInfo,
|
||||
"None": (*BufPane).None,
|
||||
|
||||
// This was changed to InsertNewline but I don't want to break backwards compatibility
|
||||
@@ -742,7 +686,6 @@ var MultiActions = map[string]bool{
|
||||
"FindNext": true,
|
||||
"FindPrevious": true,
|
||||
"CopyLine": true,
|
||||
"Copy": true,
|
||||
"Cut": true,
|
||||
"CutLine": true,
|
||||
"DuplicateLine": true,
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/clipboard"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/shell"
|
||||
@@ -506,12 +505,6 @@ func SetGlobalOptionNative(option string, nativeValue interface{}) error {
|
||||
}
|
||||
} else if option == "paste" {
|
||||
screen.Screen.SetPaste(nativeValue.(bool))
|
||||
} else if option == "clipboard" {
|
||||
m := clipboard.SetMethod(nativeValue.(string))
|
||||
err := clipboard.Initialize(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
for _, pl := range config.Plugins {
|
||||
if option == pl.Name {
|
||||
@@ -641,12 +634,7 @@ func (h *BufPane) ShowKeyCmd(args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
event, err := findEvent(args[0])
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
}
|
||||
if action, ok := config.Bindings["buffer"][event.Name()]; ok {
|
||||
if action, ok := config.Bindings[args[0]]; ok {
|
||||
InfoBar.Message(action)
|
||||
} else {
|
||||
InfoBar.Message(args[0], " has no binding")
|
||||
@@ -817,10 +805,10 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
||||
return l.GreaterEqual(start) && l.LessEqual(end)
|
||||
}
|
||||
|
||||
searchLoc := h.Cursor.Loc
|
||||
searchLoc := start
|
||||
var doReplacement func()
|
||||
doReplacement = func() {
|
||||
locs, found, err := h.Buf.FindNext(search, start, end, searchLoc, true, true)
|
||||
locs, found, err := h.Buf.FindNext(search, start, end, searchLoc, true, !noRegex)
|
||||
if err != nil {
|
||||
InfoBar.Error(err)
|
||||
return
|
||||
@@ -828,7 +816,6 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
||||
if !found || !inRange(locs[0]) || !inRange(locs[1]) {
|
||||
h.Cursor.ResetSelection()
|
||||
h.Buf.RelocateCursors()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -844,9 +831,7 @@ func (h *BufPane) ReplaceCmd(args []string) {
|
||||
|
||||
searchLoc = locs[0]
|
||||
searchLoc.X += nrunes + locs[0].Diff(locs[1], h.Buf)
|
||||
if end.Y == locs[1].Y {
|
||||
end = end.Move(nrunes, h.Buf)
|
||||
}
|
||||
end.Move(nrunes, h.Buf)
|
||||
h.Cursor.Loc = searchLoc
|
||||
nreplaced++
|
||||
} else if !canceled && !yes {
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package action
|
||||
|
||||
var termdefaults = map[string]string{
|
||||
"<Ctrl-q><Ctrl-q>": "Exit",
|
||||
"<Ctrl-e><Ctrl-e>": "CommandMode",
|
||||
"<Ctrl-w><Ctrl-w>": "NextSplit",
|
||||
}
|
||||
|
||||
// DefaultBindings returns a map containing micro's default keybindings
|
||||
func DefaultBindings(pane string) map[string]string {
|
||||
switch pane {
|
||||
case "command":
|
||||
return infodefaults
|
||||
case "buffer":
|
||||
return bufdefaults
|
||||
case "terminal":
|
||||
return termdefaults
|
||||
default:
|
||||
return map[string]string{}
|
||||
}
|
||||
}
|
||||
@@ -1,179 +1,107 @@
|
||||
package action
|
||||
|
||||
var bufdefaults = map[string]string{
|
||||
"Up": "CursorUp",
|
||||
"Down": "CursorDown",
|
||||
"Right": "CursorRight",
|
||||
"Left": "CursorLeft",
|
||||
"ShiftUp": "SelectUp",
|
||||
"ShiftDown": "SelectDown",
|
||||
"ShiftLeft": "SelectLeft",
|
||||
"ShiftRight": "SelectRight",
|
||||
"AltLeft": "WordLeft",
|
||||
"AltRight": "WordRight",
|
||||
"AltUp": "MoveLinesUp",
|
||||
"AltDown": "MoveLinesDown",
|
||||
"AltShiftRight": "SelectWordRight",
|
||||
"AltShiftLeft": "SelectWordLeft",
|
||||
"CtrlLeft": "StartOfTextToggle",
|
||||
"CtrlRight": "EndOfLine",
|
||||
"CtrlShiftLeft": "SelectToStartOfTextToggle",
|
||||
"ShiftHome": "SelectToStartOfTextToggle",
|
||||
"CtrlShiftRight": "SelectToEndOfLine",
|
||||
"ShiftEnd": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Alt-{": "ParagraphPrevious",
|
||||
"Alt-}": "ParagraphNext",
|
||||
"Enter": "InsertNewline",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
"OldBackspace": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Tab": "Autocomplete|IndentSelection|InsertTab",
|
||||
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
|
||||
"Ctrl-o": "OpenFile",
|
||||
"Ctrl-s": "Save",
|
||||
"Ctrl-f": "Find",
|
||||
"Ctrl-n": "FindNext",
|
||||
"Ctrl-p": "FindPrevious",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-d": "DuplicateLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Ctrl-a": "SelectAll",
|
||||
"Ctrl-t": "AddTab",
|
||||
"Alt-,": "PreviousTab",
|
||||
"Alt-.": "NextTab",
|
||||
"Home": "StartOfTextToggle",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"Ctrl-g": "ToggleHelp",
|
||||
"Alt-g": "ToggleKeyMenu",
|
||||
"Ctrl-r": "ToggleRuler",
|
||||
"Ctrl-l": "command-edit:goto ",
|
||||
"Delete": "Delete",
|
||||
"Ctrl-b": "ShellMode",
|
||||
"Ctrl-q": "Quit",
|
||||
"Ctrl-e": "CommandMode",
|
||||
"Ctrl-w": "NextSplit",
|
||||
"Ctrl-u": "ToggleMacro",
|
||||
"Ctrl-j": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
// DefaultBindings returns a map containing micro's default keybindings
|
||||
func DefaultBindings() map[string]string {
|
||||
return map[string]string{
|
||||
"Up": "CursorUp",
|
||||
"Down": "CursorDown",
|
||||
"Right": "CursorRight",
|
||||
"Left": "CursorLeft",
|
||||
"ShiftUp": "SelectUp",
|
||||
"ShiftDown": "SelectDown",
|
||||
"ShiftLeft": "SelectLeft",
|
||||
"ShiftRight": "SelectRight",
|
||||
"AltLeft": "WordLeft",
|
||||
"AltRight": "WordRight",
|
||||
"AltUp": "MoveLinesUp",
|
||||
"AltDown": "MoveLinesDown",
|
||||
"AltShiftRight": "SelectWordRight",
|
||||
"AltShiftLeft": "SelectWordLeft",
|
||||
"CtrlLeft": "StartOfTextToggle",
|
||||
"CtrlRight": "EndOfLine",
|
||||
"CtrlShiftLeft": "SelectToStartOfTextToggle",
|
||||
"ShiftHome": "SelectToStartOfTextToggle",
|
||||
"CtrlShiftRight": "SelectToEndOfLine",
|
||||
"ShiftEnd": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Alt-{": "ParagraphPrevious",
|
||||
"Alt-}": "ParagraphNext",
|
||||
"Enter": "InsertNewline",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Tab": "Autocomplete|IndentSelection|InsertTab",
|
||||
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
|
||||
"CtrlO": "OpenFile",
|
||||
"CtrlS": "Save",
|
||||
"CtrlF": "Find",
|
||||
"CtrlN": "FindNext",
|
||||
"CtrlP": "FindPrevious",
|
||||
"CtrlZ": "Undo",
|
||||
"CtrlY": "Redo",
|
||||
"CtrlC": "CopyLine|Copy",
|
||||
"CtrlX": "Cut",
|
||||
"CtrlK": "CutLine",
|
||||
"CtrlD": "DuplicateLine",
|
||||
"CtrlV": "Paste",
|
||||
"CtrlA": "SelectAll",
|
||||
"CtrlT": "AddTab",
|
||||
"Alt,": "PreviousTab",
|
||||
"Alt.": "NextTab",
|
||||
"Home": "StartOfTextToggle",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"CtrlG": "ToggleHelp",
|
||||
"Alt-g": "ToggleKeyMenu",
|
||||
"CtrlR": "ToggleRuler",
|
||||
"CtrlL": "command-edit:goto ",
|
||||
"Delete": "Delete",
|
||||
"CtrlB": "ShellMode",
|
||||
"CtrlQ": "Quit",
|
||||
"CtrlE": "CommandMode",
|
||||
"CtrlW": "NextSplit",
|
||||
"CtrlU": "ToggleMacro",
|
||||
"CtrlJ": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
"Alt-b": "WordLeft",
|
||||
"Alt-a": "StartOfText",
|
||||
"Alt-e": "EndOfLine",
|
||||
// "Alt-p": "CursorUp",
|
||||
// "Alt-n": "CursorDown",
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
"Alt-b": "WordLeft",
|
||||
"Alt-a": "StartOfText",
|
||||
"Alt-e": "EndOfLine",
|
||||
// "Alt-p": "CursorUp",
|
||||
// "Alt-n": "CursorDown",
|
||||
|
||||
// Integration with file managers
|
||||
"F2": "Save",
|
||||
"F3": "Find",
|
||||
"F4": "Quit",
|
||||
"F7": "Find",
|
||||
"F10": "Quit",
|
||||
"Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors",
|
||||
// Integration with file managers
|
||||
"F2": "Save",
|
||||
"F3": "Find",
|
||||
"F4": "Quit",
|
||||
"F7": "Find",
|
||||
"F10": "Quit",
|
||||
"Esc": "Escape",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "ScrollUp",
|
||||
"MouseWheelDown": "ScrollDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "ScrollUp",
|
||||
"MouseWheelDown": "ScrollDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"AltShiftUp": "SpawnMultiCursorUp",
|
||||
"AltShiftDown": "SpawnMultiCursorDown",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
}
|
||||
|
||||
var infodefaults = map[string]string{
|
||||
"Up": "HistoryUp",
|
||||
"Down": "HistoryDown",
|
||||
"Right": "CursorRight",
|
||||
"Left": "CursorLeft",
|
||||
"ShiftUp": "SelectUp",
|
||||
"ShiftDown": "SelectDown",
|
||||
"ShiftLeft": "SelectLeft",
|
||||
"ShiftRight": "SelectRight",
|
||||
"AltLeft": "WordLeft",
|
||||
"AltRight": "WordRight",
|
||||
"AltUp": "CursorStart",
|
||||
"AltDown": "CursorEnd",
|
||||
"AltShiftRight": "SelectWordRight",
|
||||
"AltShiftLeft": "SelectWordLeft",
|
||||
"CtrlLeft": "StartOfTextToggle",
|
||||
"CtrlRight": "EndOfLine",
|
||||
"CtrlShiftLeft": "SelectToStartOfTextToggle",
|
||||
"ShiftHome": "SelectToStartOfTextToggle",
|
||||
"CtrlShiftRight": "SelectToEndOfLine",
|
||||
"ShiftEnd": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Enter": "ExecuteCommand",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
"OldBackspace": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Tab": "CommandComplete",
|
||||
"Backtab": "CycleAutocompleteBack",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Home": "StartOfTextToggle",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"Delete": "Delete",
|
||||
"Ctrl-q": "AbortCommand",
|
||||
"Ctrl-e": "EndOfLine",
|
||||
"Ctrl-a": "StartOfLine",
|
||||
"Ctrl-w": "DeleteWordLeft",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
"Ctrl-b": "WordLeft",
|
||||
"Ctrl-f": "WordRight",
|
||||
"Ctrl-d": "DeleteWordLeft",
|
||||
"Ctrl-m": "ExecuteCommand",
|
||||
"Ctrl-n": "HistoryDown",
|
||||
"Ctrl-p": "HistoryUp",
|
||||
"Ctrl-u": "SelectToStart",
|
||||
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
"Alt-b": "WordLeft",
|
||||
"Alt-a": "StartOfText",
|
||||
"Alt-e": "EndOfLine",
|
||||
|
||||
// Integration with file managers
|
||||
"F10": "AbortCommand",
|
||||
"Esc": "AbortCommand",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "HistoryUp",
|
||||
"MouseWheelDown": "HistoryDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"AltShiftUp": "SpawnMultiCursorUp",
|
||||
"AltShiftDown": "SpawnMultiCursorDown",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,180 +2,108 @@
|
||||
|
||||
package action
|
||||
|
||||
var bufdefaults = map[string]string{
|
||||
"Up": "CursorUp",
|
||||
"Down": "CursorDown",
|
||||
"Right": "CursorRight",
|
||||
"Left": "CursorLeft",
|
||||
"ShiftUp": "SelectUp",
|
||||
"ShiftDown": "SelectDown",
|
||||
"ShiftLeft": "SelectLeft",
|
||||
"ShiftRight": "SelectRight",
|
||||
"CtrlLeft": "WordLeft",
|
||||
"CtrlRight": "WordRight",
|
||||
"AltUp": "MoveLinesUp",
|
||||
"AltDown": "MoveLinesDown",
|
||||
"CtrlShiftRight": "SelectWordRight",
|
||||
"CtrlShiftLeft": "SelectWordLeft",
|
||||
"AltLeft": "StartOfTextToggle",
|
||||
"AltRight": "EndOfLine",
|
||||
"AltShiftLeft": "SelectToStartOfTextToggle",
|
||||
"ShiftHome": "SelectToStartOfTextToggle",
|
||||
"AltShiftRight": "SelectToEndOfLine",
|
||||
"ShiftEnd": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Alt-{": "ParagraphPrevious",
|
||||
"Alt-}": "ParagraphNext",
|
||||
"Enter": "InsertNewline",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
"OldBackspace": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Tab": "Autocomplete|IndentSelection|InsertTab",
|
||||
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
|
||||
"Ctrl-o": "OpenFile",
|
||||
"Ctrl-s": "Save",
|
||||
"Ctrl-f": "Find",
|
||||
"Ctrl-n": "FindNext",
|
||||
"Ctrl-p": "FindPrevious",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-d": "DuplicateLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Ctrl-a": "SelectAll",
|
||||
"Ctrl-t": "AddTab",
|
||||
"Alt-,": "PreviousTab",
|
||||
"Alt-.": "NextTab",
|
||||
"Home": "StartOfTextToggle",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"Ctrl-g": "ToggleHelp",
|
||||
"Alt-g": "ToggleKeyMenu",
|
||||
"Ctrl-r": "ToggleRuler",
|
||||
"Ctrl-l": "command-edit:goto ",
|
||||
"Delete": "Delete",
|
||||
"Ctrl-b": "ShellMode",
|
||||
"Ctrl-q": "Quit",
|
||||
"Ctrl-e": "CommandMode",
|
||||
"Ctrl-w": "NextSplit",
|
||||
"Ctrl-u": "ToggleMacro",
|
||||
"Ctrl-j": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
// DefaultBindings returns a map containing micro's default keybindings
|
||||
func DefaultBindings() map[string]string {
|
||||
return map[string]string{
|
||||
"Up": "CursorUp",
|
||||
"Down": "CursorDown",
|
||||
"Right": "CursorRight",
|
||||
"Left": "CursorLeft",
|
||||
"ShiftUp": "SelectUp",
|
||||
"ShiftDown": "SelectDown",
|
||||
"ShiftLeft": "SelectLeft",
|
||||
"ShiftRight": "SelectRight",
|
||||
"CtrlLeft": "WordLeft",
|
||||
"CtrlRight": "WordRight",
|
||||
"AltUp": "MoveLinesUp",
|
||||
"AltDown": "MoveLinesDown",
|
||||
"CtrlShiftRight": "SelectWordRight",
|
||||
"CtrlShiftLeft": "SelectWordLeft",
|
||||
"AltLeft": "StartOfTextToggle",
|
||||
"AltRight": "EndOfLine",
|
||||
"AltShiftLeft": "SelectToStartOfTextToggle",
|
||||
"ShiftHome": "SelectToStartOfTextToggle",
|
||||
"AltShiftRight": "SelectToEndOfLine",
|
||||
"ShiftEnd": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Alt-{": "ParagraphPrevious",
|
||||
"Alt-}": "ParagraphNext",
|
||||
"Enter": "InsertNewline",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Tab": "Autocomplete|IndentSelection|InsertTab",
|
||||
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
|
||||
"CtrlO": "OpenFile",
|
||||
"CtrlS": "Save",
|
||||
"CtrlF": "Find",
|
||||
"CtrlN": "FindNext",
|
||||
"CtrlP": "FindPrevious",
|
||||
"CtrlZ": "Undo",
|
||||
"CtrlY": "Redo",
|
||||
"CtrlC": "CopyLine|Copy",
|
||||
"CtrlX": "Cut",
|
||||
"CtrlK": "CutLine",
|
||||
"CtrlD": "DuplicateLine",
|
||||
"CtrlV": "Paste",
|
||||
"CtrlA": "SelectAll",
|
||||
"CtrlT": "AddTab",
|
||||
"Alt,": "PreviousTab",
|
||||
"Alt.": "NextTab",
|
||||
"Home": "StartOfTextToggle",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"PageUp": "CursorPageUp",
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"CtrlG": "ToggleHelp",
|
||||
"Alt-g": "ToggleKeyMenu",
|
||||
"CtrlR": "ToggleRuler",
|
||||
"CtrlL": "command-edit:goto ",
|
||||
"Delete": "Delete",
|
||||
"CtrlB": "ShellMode",
|
||||
"CtrlQ": "Quit",
|
||||
"CtrlE": "CommandMode",
|
||||
"CtrlW": "NextSplit",
|
||||
"CtrlU": "ToggleMacro",
|
||||
"CtrlJ": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
"Alt-b": "WordLeft",
|
||||
"Alt-a": "StartOfText",
|
||||
"Alt-e": "EndOfLine",
|
||||
// "Alt-p": "CursorUp",
|
||||
// "Alt-n": "CursorDown",
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
"Alt-b": "WordLeft",
|
||||
"Alt-a": "StartOfText",
|
||||
"Alt-e": "EndOfLine",
|
||||
// "Alt-p": "CursorUp",
|
||||
// "Alt-n": "CursorDown",
|
||||
|
||||
// Integration with file managers
|
||||
"F2": "Save",
|
||||
"F3": "Find",
|
||||
"F4": "Quit",
|
||||
"F7": "Find",
|
||||
"F10": "Quit",
|
||||
"Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors",
|
||||
// Integration with file managers
|
||||
"F2": "Save",
|
||||
"F3": "Find",
|
||||
"F4": "Quit",
|
||||
"F7": "Find",
|
||||
"F10": "Quit",
|
||||
"Esc": "Escape",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "ScrollUp",
|
||||
"MouseWheelDown": "ScrollDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "ScrollUp",
|
||||
"MouseWheelDown": "ScrollDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Ctrl-MouseLeft": "MouseMultiCursor",
|
||||
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"AltShiftUp": "SpawnMultiCursorUp",
|
||||
"AltShiftDown": "SpawnMultiCursorDown",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
}
|
||||
|
||||
var infodefaults = map[string]string{
|
||||
"Up": "HistoryUp",
|
||||
"Down": "HistoryDown",
|
||||
"Right": "CursorRight",
|
||||
"Left": "CursorLeft",
|
||||
"ShiftUp": "SelectUp",
|
||||
"ShiftDown": "SelectDown",
|
||||
"ShiftLeft": "SelectLeft",
|
||||
"ShiftRight": "SelectRight",
|
||||
"AltLeft": "StartOfTextToggle",
|
||||
"AltRight": "EndOfLine",
|
||||
"AltUp": "CursorStart",
|
||||
"AltDown": "CursorEnd",
|
||||
"AltShiftRight": "SelectWordRight",
|
||||
"AltShiftLeft": "SelectWordLeft",
|
||||
"CtrlLeft": "WordLeft",
|
||||
"CtrlRight": "WordRight",
|
||||
"CtrlShiftLeft": "SelectToStartOfTextToggle",
|
||||
"ShiftHome": "SelectToStartOfTextToggle",
|
||||
"CtrlShiftRight": "SelectToEndOfLine",
|
||||
"ShiftEnd": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Enter": "ExecuteCommand",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
"OldBackspace": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Tab": "CommandComplete",
|
||||
"Backtab": "CycleAutocompleteBack",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Home": "StartOfTextToggle",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"Delete": "Delete",
|
||||
"Ctrl-q": "AbortCommand",
|
||||
"Ctrl-e": "EndOfLine",
|
||||
"Ctrl-a": "StartOfLine",
|
||||
"Ctrl-w": "DeleteWordLeft",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
"Ctrl-b": "WordLeft",
|
||||
"Ctrl-f": "WordRight",
|
||||
"Ctrl-d": "DeleteWordLeft",
|
||||
"Ctrl-m": "ExecuteCommand",
|
||||
"Ctrl-n": "HistoryDown",
|
||||
"Ctrl-p": "HistoryUp",
|
||||
"Ctrl-u": "SelectToStart",
|
||||
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
"Alt-b": "WordLeft",
|
||||
"Alt-a": "StartOfText",
|
||||
"Alt-e": "EndOfLine",
|
||||
|
||||
// Integration with file managers
|
||||
"F10": "AbortCommand",
|
||||
"Esc": "AbortCommand",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "HistoryUp",
|
||||
"MouseWheelDown": "HistoryDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseMiddle": "PastePrimary",
|
||||
"Alt-n": "SpawnMultiCursor",
|
||||
"Alt-m": "SpawnMultiCursorSelect",
|
||||
"AltShiftUp": "SpawnMultiCursorUp",
|
||||
"AltShiftDown": "SpawnMultiCursorDown",
|
||||
"Alt-p": "RemoveMultiCursor",
|
||||
"Alt-c": "RemoveAllMultiCursors",
|
||||
"Alt-x": "SkipMultiCursor",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
type Event interface {
|
||||
Name() string
|
||||
}
|
||||
type Event interface{}
|
||||
|
||||
// RawEvent is simply an escape code
|
||||
// We allow users to directly bind escape codes
|
||||
@@ -20,10 +13,6 @@ type RawEvent struct {
|
||||
esc string
|
||||
}
|
||||
|
||||
func (r RawEvent) Name() string {
|
||||
return r.esc
|
||||
}
|
||||
|
||||
// KeyEvent is a key event containing a key code,
|
||||
// some possible modifiers (alt, ctrl, etc...) and
|
||||
// a rune if it was simply a character press
|
||||
@@ -33,71 +22,6 @@ type KeyEvent struct {
|
||||
code tcell.Key
|
||||
mod tcell.ModMask
|
||||
r rune
|
||||
any bool
|
||||
}
|
||||
|
||||
func metaToAlt(mod tcell.ModMask) tcell.ModMask {
|
||||
if mod&tcell.ModMeta != 0 {
|
||||
mod &= ^tcell.ModMeta
|
||||
mod |= tcell.ModAlt
|
||||
}
|
||||
return mod
|
||||
}
|
||||
|
||||
func (k KeyEvent) Name() string {
|
||||
if k.any {
|
||||
return "<any>"
|
||||
}
|
||||
s := ""
|
||||
m := []string{}
|
||||
if k.mod&tcell.ModShift != 0 {
|
||||
m = append(m, "Shift")
|
||||
}
|
||||
if k.mod&tcell.ModAlt != 0 {
|
||||
m = append(m, "Alt")
|
||||
}
|
||||
if k.mod&tcell.ModMeta != 0 {
|
||||
m = append(m, "Meta")
|
||||
}
|
||||
if k.mod&tcell.ModCtrl != 0 {
|
||||
m = append(m, "Ctrl")
|
||||
}
|
||||
|
||||
ok := false
|
||||
if s, ok = tcell.KeyNames[k.code]; !ok {
|
||||
if k.code == tcell.KeyRune {
|
||||
s = string(k.r)
|
||||
} else {
|
||||
s = fmt.Sprintf("Key[%d,%d]", k.code, int(k.r))
|
||||
}
|
||||
}
|
||||
if len(m) != 0 {
|
||||
if k.mod&tcell.ModCtrl != 0 && strings.HasPrefix(s, "Ctrl-") {
|
||||
s = s[5:]
|
||||
if len(s) == 1 {
|
||||
s = strings.ToLower(s)
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s-%s", strings.Join(m, "-"), s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// A KeySequence defines a list of consecutive
|
||||
// events. All events in the sequence must be KeyEvents
|
||||
// or MouseEvents.
|
||||
type KeySequenceEvent struct {
|
||||
keys []Event
|
||||
}
|
||||
|
||||
func (k KeySequenceEvent) Name() string {
|
||||
buf := bytes.Buffer{}
|
||||
for _, e := range k.keys {
|
||||
buf.WriteByte('<')
|
||||
buf.WriteString(e.Name())
|
||||
buf.WriteByte('>')
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// MouseEvent is a mouse event with a mouse button and
|
||||
@@ -107,54 +31,8 @@ type MouseEvent struct {
|
||||
mod tcell.ModMask
|
||||
}
|
||||
|
||||
func (m MouseEvent) Name() string {
|
||||
mod := ""
|
||||
if m.mod&tcell.ModShift != 0 {
|
||||
mod = "Shift-"
|
||||
}
|
||||
if m.mod&tcell.ModAlt != 0 {
|
||||
mod = "Alt-"
|
||||
}
|
||||
if m.mod&tcell.ModMeta != 0 {
|
||||
mod = "Meta-"
|
||||
}
|
||||
if m.mod&tcell.ModCtrl != 0 {
|
||||
mod = "Ctrl-"
|
||||
}
|
||||
|
||||
for k, v := range mouseEvents {
|
||||
if v == m.btn {
|
||||
return fmt.Sprintf("%s%s", mod, k)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ConstructEvent takes a tcell event and returns a micro
|
||||
// event. Note that tcell events can't express certain
|
||||
// micro events such as key sequences. This function is
|
||||
// mostly used for debugging/raw panes or constructing
|
||||
// intermediate micro events while parsing a sequence.
|
||||
func ConstructEvent(event tcell.Event) (Event, error) {
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
return KeyEvent{
|
||||
code: e.Key(),
|
||||
mod: metaToAlt(e.Modifiers()),
|
||||
r: e.Rune(),
|
||||
}, nil
|
||||
case *tcell.EventRaw:
|
||||
return RawEvent{
|
||||
esc: e.EscSeq(),
|
||||
}, nil
|
||||
case *tcell.EventMouse:
|
||||
return MouseEvent{
|
||||
btn: e.Buttons(),
|
||||
mod: metaToAlt(e.Modifiers()),
|
||||
}, nil
|
||||
}
|
||||
return nil, errors.New("No micro event equivalent")
|
||||
}
|
||||
type KeyAction func(Handler) bool
|
||||
type MouseAction func(Handler, tcell.EventMouse) bool
|
||||
|
||||
// A Handler will take a tcell event and execute it
|
||||
// appropriately
|
||||
|
||||
@@ -21,6 +21,13 @@ func WriteLog(s string) {
|
||||
buffer.WriteLog(s)
|
||||
if LogBufPane != nil {
|
||||
LogBufPane.CursorEnd()
|
||||
v := LogBufPane.GetView()
|
||||
endY := buffer.LogBuf.End().Y
|
||||
|
||||
if endY > v.StartLine+v.Height {
|
||||
v.StartLine = buffer.LogBuf.End().Y - v.Height + 2
|
||||
LogBufPane.SetView(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,4 +37,12 @@ func WriteLog(s string) {
|
||||
func (h *BufPane) OpenLogBuf() {
|
||||
LogBufPane = h.HSplitBuf(buffer.LogBuf)
|
||||
LogBufPane.CursorEnd()
|
||||
|
||||
v := LogBufPane.GetView()
|
||||
endY := buffer.LogBuf.End().Y
|
||||
|
||||
if endY > v.StartLine+v.Height {
|
||||
v.StartLine = buffer.LogBuf.End().Y - v.Height + 2
|
||||
LogBufPane.SetView(v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,16 +186,6 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
|
||||
if strings.HasPrefix("doas", input) {
|
||||
suggestions = append(suggestions, "doas")
|
||||
}
|
||||
case "clipboard":
|
||||
if strings.HasPrefix("external", input) {
|
||||
suggestions = append(suggestions, "external")
|
||||
}
|
||||
if strings.HasPrefix("internal", input) {
|
||||
suggestions = append(suggestions, "internal")
|
||||
}
|
||||
if strings.HasPrefix("terminal", input) {
|
||||
suggestions = append(suggestions, "terminal")
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Strings(suggestions)
|
||||
|
||||
@@ -2,60 +2,17 @@ package action
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
"github.com/zyedidia/micro/v2/internal/info"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
type InfoKeyAction func(*InfoPane)
|
||||
|
||||
var InfoBindings *KeyTree
|
||||
var InfoBufBindings *KeyTree
|
||||
|
||||
func init() {
|
||||
InfoBindings = NewKeyTree()
|
||||
InfoBufBindings = NewKeyTree()
|
||||
}
|
||||
|
||||
func InfoMapEvent(k Event, action string) {
|
||||
config.Bindings["command"][k.Name()] = action
|
||||
|
||||
switch e := k.(type) {
|
||||
case KeyEvent, KeySequenceEvent, RawEvent:
|
||||
infoMapKey(e, action)
|
||||
case MouseEvent:
|
||||
infoMapMouse(e, action)
|
||||
}
|
||||
}
|
||||
|
||||
func infoMapKey(k Event, action string) {
|
||||
if f, ok := InfoKeyActions[action]; ok {
|
||||
InfoBindings.RegisterKeyBinding(k, InfoKeyActionGeneral(f))
|
||||
} else if f, ok := BufKeyActions[action]; ok {
|
||||
InfoBufBindings.RegisterKeyBinding(k, BufKeyActionGeneral(f))
|
||||
}
|
||||
}
|
||||
|
||||
func infoMapMouse(k MouseEvent, action string) {
|
||||
// TODO: map mouse
|
||||
if f, ok := BufMouseActions[action]; ok {
|
||||
InfoBufBindings.RegisterMouseBinding(k, BufMouseActionGeneral(f))
|
||||
} else {
|
||||
infoMapKey(k, action)
|
||||
}
|
||||
}
|
||||
|
||||
func InfoKeyActionGeneral(a InfoKeyAction) PaneKeyAction {
|
||||
return func(p Pane) bool {
|
||||
a(p.(*InfoPane))
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
type InfoPane struct {
|
||||
*BufPane
|
||||
*info.InfoBuf
|
||||
@@ -65,7 +22,6 @@ func NewInfoPane(ib *info.InfoBuf, w display.BWindow, tab *Tab) *InfoPane {
|
||||
ip := new(InfoPane)
|
||||
ip.InfoBuf = ib
|
||||
ip.BufPane = NewBufPane(ib.Buffer, w, tab)
|
||||
ip.BufPane.bindings = InfoBufBindings
|
||||
|
||||
return ip
|
||||
}
|
||||
@@ -86,7 +42,7 @@ func (h *InfoPane) HandleEvent(event tcell.Event) {
|
||||
case *tcell.EventKey:
|
||||
ke := KeyEvent{
|
||||
code: e.Key(),
|
||||
mod: metaToAlt(e.Modifiers()),
|
||||
mod: e.Modifiers(),
|
||||
r: e.Rune(),
|
||||
}
|
||||
|
||||
@@ -120,43 +76,104 @@ func (h *InfoPane) HandleEvent(event tcell.Event) {
|
||||
|
||||
// DoKeyEvent executes a key event for the command bar, doing any overridden actions
|
||||
func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
|
||||
action, more := InfoBindings.NextEvent(e, nil)
|
||||
if action != nil && !more {
|
||||
action(h)
|
||||
InfoBindings.ResetEvents()
|
||||
|
||||
return true
|
||||
} else if action == nil && !more {
|
||||
InfoBindings.ResetEvents()
|
||||
// return false //TODO:?
|
||||
}
|
||||
|
||||
if !more {
|
||||
action, more = InfoBufBindings.NextEvent(e, nil)
|
||||
if action != nil && !more {
|
||||
done := action(h.BufPane)
|
||||
InfoBufBindings.ResetEvents()
|
||||
return done
|
||||
} else if action == nil && !more {
|
||||
InfoBufBindings.ResetEvents()
|
||||
done := false
|
||||
if action, ok := BufKeyBindings[e]; ok {
|
||||
estr := BufKeyStrings[e]
|
||||
for _, s := range InfoNones {
|
||||
if s == estr {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for s, a := range InfoOverrides {
|
||||
// TODO this is a hack and really we should have support
|
||||
// for having binding overrides for different buffers
|
||||
if strings.HasPrefix(estr, s) {
|
||||
done = true
|
||||
a(h)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !done {
|
||||
done = action(h.BufPane)
|
||||
}
|
||||
}
|
||||
|
||||
return more
|
||||
return done
|
||||
}
|
||||
|
||||
// HistoryUp cycles history up
|
||||
func (h *InfoPane) HistoryUp() {
|
||||
// InfoNones is a list of actions that should have no effect when executed
|
||||
// by an infohandler
|
||||
var InfoNones = []string{
|
||||
"Save",
|
||||
"SaveAll",
|
||||
"SaveAs",
|
||||
"Find",
|
||||
"FindNext",
|
||||
"FindPrevious",
|
||||
"Center",
|
||||
"DuplicateLine",
|
||||
"MoveLinesUp",
|
||||
"MoveLinesDown",
|
||||
"OpenFile",
|
||||
"Start",
|
||||
"End",
|
||||
"PageUp",
|
||||
"PageDown",
|
||||
"SelectPageUp",
|
||||
"SelectPageDown",
|
||||
"HalfPageUp",
|
||||
"HalfPageDown",
|
||||
"ToggleHelp",
|
||||
"ToggleKeyMenu",
|
||||
"ToggleDiffGutter",
|
||||
"ToggleRuler",
|
||||
"JumpLine",
|
||||
"ClearStatus",
|
||||
"ShellMode",
|
||||
"CommandMode",
|
||||
"AddTab",
|
||||
"PreviousTab",
|
||||
"NextTab",
|
||||
"NextSplit",
|
||||
"PreviousSplit",
|
||||
"Unsplit",
|
||||
"VSplit",
|
||||
"HSplit",
|
||||
"ToggleMacro",
|
||||
"PlayMacro",
|
||||
"Suspend",
|
||||
"ScrollUp",
|
||||
"ScrollDown",
|
||||
"SpawnMultiCursor",
|
||||
"SpawnMultiCursorSelect",
|
||||
"RemoveMultiCursor",
|
||||
"RemoveAllMultiCursors",
|
||||
"SkipMultiCursor",
|
||||
}
|
||||
|
||||
// InfoOverrides is the list of actions which have been overridden
|
||||
// by the infohandler
|
||||
var InfoOverrides = map[string]InfoKeyAction{
|
||||
"CursorUp": (*InfoPane).CursorUp,
|
||||
"CursorDown": (*InfoPane).CursorDown,
|
||||
"InsertNewline": (*InfoPane).InsertNewline,
|
||||
"Autocomplete": (*InfoPane).Autocomplete,
|
||||
"Escape": (*InfoPane).Escape,
|
||||
"Quit": (*InfoPane).Quit,
|
||||
"QuitAll": (*InfoPane).QuitAll,
|
||||
}
|
||||
|
||||
// CursorUp cycles history up
|
||||
func (h *InfoPane) CursorUp() {
|
||||
h.UpHistory(h.History[h.PromptType])
|
||||
}
|
||||
|
||||
// HistoryDown cycles history down
|
||||
func (h *InfoPane) HistoryDown() {
|
||||
// CursorDown cycles history down
|
||||
func (h *InfoPane) CursorDown() {
|
||||
h.DownHistory(h.History[h.PromptType])
|
||||
}
|
||||
|
||||
// Autocomplete begins autocompletion
|
||||
func (h *InfoPane) CommandComplete() {
|
||||
func (h *InfoPane) Autocomplete() {
|
||||
b := h.Buf
|
||||
if b.HasSuggestions {
|
||||
b.CycleAutocomplete(true)
|
||||
@@ -184,23 +201,24 @@ func (h *InfoPane) CommandComplete() {
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuteCommand completes the prompt
|
||||
func (h *InfoPane) ExecuteCommand() {
|
||||
// InsertNewline completes the prompt
|
||||
func (h *InfoPane) InsertNewline() {
|
||||
if !h.HasYN {
|
||||
h.DonePrompt(false)
|
||||
}
|
||||
}
|
||||
|
||||
// AbortCommand cancels the prompt
|
||||
func (h *InfoPane) AbortCommand() {
|
||||
// Quit cancels the prompt
|
||||
func (h *InfoPane) Quit() {
|
||||
h.DonePrompt(true)
|
||||
}
|
||||
|
||||
// InfoKeyActions contains the list of all possible key actions the infopane could execute
|
||||
var InfoKeyActions = map[string]InfoKeyAction{
|
||||
"HistoryUp": (*InfoPane).HistoryUp,
|
||||
"HistoryDown": (*InfoPane).HistoryDown,
|
||||
"CommandComplete": (*InfoPane).CommandComplete,
|
||||
"ExecuteCommand": (*InfoPane).ExecuteCommand,
|
||||
"AbortCommand": (*InfoPane).AbortCommand,
|
||||
// QuitAll cancels the prompt
|
||||
func (h *InfoPane) QuitAll() {
|
||||
h.DonePrompt(true)
|
||||
}
|
||||
|
||||
// Escape cancels the prompt
|
||||
func (h *InfoPane) Escape() {
|
||||
h.DonePrompt(true)
|
||||
}
|
||||
|
||||
@@ -1,261 +0,0 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
type PaneKeyAction func(Pane) bool
|
||||
type PaneMouseAction func(Pane, *tcell.EventMouse) bool
|
||||
type PaneKeyAnyAction func(Pane, []KeyEvent) bool
|
||||
|
||||
// A KeyTreeNode stores a single node in the KeyTree (trie). The
|
||||
// children are stored as a map, and any node may store a list of
|
||||
// actions (the list will be nil if no actions correspond to a certain
|
||||
// node)
|
||||
type KeyTreeNode struct {
|
||||
children map[Event]*KeyTreeNode
|
||||
|
||||
// Only one of these actions may be active in the current
|
||||
// mode, and only one will be returned. If multiple actions
|
||||
// are active, it is undefined which one will be the one
|
||||
// returned.
|
||||
actions []TreeAction
|
||||
}
|
||||
|
||||
func NewKeyTreeNode() *KeyTreeNode {
|
||||
n := new(KeyTreeNode)
|
||||
n.children = make(map[Event]*KeyTreeNode)
|
||||
n.actions = []TreeAction{}
|
||||
return n
|
||||
}
|
||||
|
||||
// A TreeAction stores an action, and a set of mode constraints for
|
||||
// the action to be active.
|
||||
type TreeAction struct {
|
||||
// only one of these can be non-nil
|
||||
action PaneKeyAction
|
||||
any PaneKeyAnyAction
|
||||
mouse PaneMouseAction
|
||||
|
||||
modes []ModeConstraint
|
||||
}
|
||||
|
||||
// A KeyTree is a data structure for storing keybindings. It maps
|
||||
// key events to actions, and maintains a set of currently enabled
|
||||
// modes, which affects the action that is returned for a key event.
|
||||
// The tree acts like a Trie for Events to handle sequence events.
|
||||
type KeyTree struct {
|
||||
root *KeyTreeNode
|
||||
modes map[string]bool
|
||||
|
||||
cursor KeyTreeCursor
|
||||
}
|
||||
|
||||
// A KeyTreeCursor keeps track of the current location within the
|
||||
// tree, and stores any information from previous events that may
|
||||
// be needed to execute the action (values of wildcard events or
|
||||
// mouse events)
|
||||
type KeyTreeCursor struct {
|
||||
node *KeyTreeNode
|
||||
|
||||
recordedEvents []Event
|
||||
wildcards []KeyEvent
|
||||
mouseInfo *tcell.EventMouse
|
||||
}
|
||||
|
||||
// MakeClosure uses the information stored in a key tree cursor to construct
|
||||
// a PaneKeyAction from a TreeAction (which may have a PaneKeyAction, PaneMouseAction,
|
||||
// or AnyAction)
|
||||
func (k *KeyTreeCursor) MakeClosure(a TreeAction) PaneKeyAction {
|
||||
if a.action != nil {
|
||||
return a.action
|
||||
} else if a.any != nil {
|
||||
return func(p Pane) bool {
|
||||
return a.any(p, k.wildcards)
|
||||
}
|
||||
} else if a.mouse != nil {
|
||||
return func(p Pane) bool {
|
||||
return a.mouse(p, k.mouseInfo)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewKeyTree allocates and returns an empty key tree
|
||||
func NewKeyTree() *KeyTree {
|
||||
root := NewKeyTreeNode()
|
||||
tree := new(KeyTree)
|
||||
|
||||
tree.root = root
|
||||
tree.modes = make(map[string]bool)
|
||||
tree.cursor = KeyTreeCursor{
|
||||
node: root,
|
||||
wildcards: []KeyEvent{},
|
||||
mouseInfo: nil,
|
||||
}
|
||||
|
||||
return tree
|
||||
}
|
||||
|
||||
// A ModeConstraint specifies that an action can only be executed
|
||||
// while a certain mode is enabled or disabled.
|
||||
type ModeConstraint struct {
|
||||
mode string
|
||||
disabled bool
|
||||
}
|
||||
|
||||
// RegisterKeyBinding registers a PaneKeyAction with an Event.
|
||||
func (k *KeyTree) RegisterKeyBinding(e Event, a PaneKeyAction) {
|
||||
k.registerBinding(e, TreeAction{
|
||||
action: a,
|
||||
any: nil,
|
||||
mouse: nil,
|
||||
modes: nil,
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterKeyAnyBinding registers a PaneKeyAnyAction with an Event.
|
||||
// The event should contain an "any" event.
|
||||
func (k *KeyTree) RegisterKeyAnyBinding(e Event, a PaneKeyAnyAction) {
|
||||
k.registerBinding(e, TreeAction{
|
||||
action: nil,
|
||||
any: a,
|
||||
mouse: nil,
|
||||
modes: nil,
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterMouseBinding registers a PaneMouseAction with an Event.
|
||||
// The event should contain a mouse event.
|
||||
func (k *KeyTree) RegisterMouseBinding(e Event, a PaneMouseAction) {
|
||||
k.registerBinding(e, TreeAction{
|
||||
action: nil,
|
||||
any: nil,
|
||||
mouse: a,
|
||||
modes: nil,
|
||||
})
|
||||
}
|
||||
|
||||
func (k *KeyTree) registerBinding(e Event, a TreeAction) {
|
||||
switch ev := e.(type) {
|
||||
case KeyEvent, MouseEvent, RawEvent:
|
||||
newNode, ok := k.root.children[e]
|
||||
if !ok {
|
||||
newNode = NewKeyTreeNode()
|
||||
k.root.children[e] = newNode
|
||||
}
|
||||
// newNode.actions = append(newNode.actions, a)
|
||||
newNode.actions = []TreeAction{a}
|
||||
case KeySequenceEvent:
|
||||
n := k.root
|
||||
for _, key := range ev.keys {
|
||||
newNode, ok := n.children[key]
|
||||
if !ok {
|
||||
newNode = NewKeyTreeNode()
|
||||
n.children[key] = newNode
|
||||
}
|
||||
|
||||
n = newNode
|
||||
}
|
||||
// n.actions = append(n.actions, a)
|
||||
n.actions = []TreeAction{a}
|
||||
}
|
||||
}
|
||||
|
||||
// NextEvent returns the action for the current sequence where e is the next
|
||||
// event. Even if the action was registered as a PaneKeyAnyAction or PaneMouseAction,
|
||||
// it will be returned as a PaneKeyAction closure where the appropriate arguments
|
||||
// have been provided.
|
||||
// If no action is associated with the given Event, or mode constraints are not
|
||||
// met for that action, nil is returned.
|
||||
// A boolean is returned to indicate if there is a conflict with this action. A
|
||||
// conflict occurs when there is an active action for this event but there are
|
||||
// bindings associated with further sequences starting with this event. The
|
||||
// calling function can decide what to do about the conflict (e.g. use a
|
||||
// timeout).
|
||||
func (k *KeyTree) NextEvent(e Event, mouse *tcell.EventMouse) (PaneKeyAction, bool) {
|
||||
n := k.cursor.node
|
||||
c, ok := n.children[e]
|
||||
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
more := len(c.children) > 0
|
||||
|
||||
k.cursor.node = c
|
||||
|
||||
k.cursor.recordedEvents = append(k.cursor.recordedEvents, e)
|
||||
|
||||
switch ev := e.(type) {
|
||||
case KeyEvent:
|
||||
if ev.any {
|
||||
k.cursor.wildcards = append(k.cursor.wildcards, ev)
|
||||
}
|
||||
case MouseEvent:
|
||||
k.cursor.mouseInfo = mouse
|
||||
}
|
||||
|
||||
if len(c.actions) > 0 {
|
||||
// check if actions are active
|
||||
for _, a := range c.actions {
|
||||
active := true
|
||||
for _, mc := range a.modes {
|
||||
// if any mode constraint is not met, the action is not active
|
||||
hasMode := k.modes[mc.mode]
|
||||
if hasMode != mc.disabled {
|
||||
active = false
|
||||
}
|
||||
}
|
||||
|
||||
if active {
|
||||
// the first active action to be found is returned
|
||||
return k.cursor.MakeClosure(a), more
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, more
|
||||
}
|
||||
|
||||
// ResetEvents sets the current sequence back to the initial value.
|
||||
func (k *KeyTree) ResetEvents() {
|
||||
k.cursor.node = k.root
|
||||
k.cursor.wildcards = []KeyEvent{}
|
||||
k.cursor.recordedEvents = []Event{}
|
||||
k.cursor.mouseInfo = nil
|
||||
}
|
||||
|
||||
// CurrentEventsStr returns the list of recorded events as a string
|
||||
func (k *KeyTree) RecordedEventsStr() string {
|
||||
buf := &bytes.Buffer{}
|
||||
for _, e := range k.cursor.recordedEvents {
|
||||
buf.WriteString(e.Name())
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// DeleteBinding removes any currently active actions associated with the
|
||||
// given event.
|
||||
func (k *KeyTree) DeleteBinding(e Event) {
|
||||
|
||||
}
|
||||
|
||||
// DeleteAllBindings removes all actions associated with the given event,
|
||||
// regardless of whether they are active or not.
|
||||
func (k *KeyTree) DeleteAllBindings(e Event) {
|
||||
|
||||
}
|
||||
|
||||
// SetMode enables or disabled a given mode
|
||||
func (k *KeyTree) SetMode(mode string, en bool) {
|
||||
k.modes[mode] = en
|
||||
}
|
||||
|
||||
// HasMode returns if the given mode is currently active
|
||||
func (k *KeyTree) HasMode(mode string) bool {
|
||||
return k.modes[mode]
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
type RawPane struct {
|
||||
@@ -36,8 +36,8 @@ func (h *RawPane) HandleEvent(event tcell.Event) {
|
||||
|
||||
h.Buf.Insert(h.Cursor.Loc, reflect.TypeOf(event).String()[7:])
|
||||
|
||||
e, err := ConstructEvent(event)
|
||||
if err == nil {
|
||||
switch e := event.(type) {
|
||||
case *tcell.EventKey:
|
||||
h.Buf.Insert(h.Cursor.Loc, fmt.Sprintf(": %s", e.Name()))
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/zyedidia/micro/v2/internal/display"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/views"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
// The TabList is a list of tabs and a window to display the tab bar
|
||||
@@ -175,7 +175,6 @@ func NewTabFromBuffer(x, y, width, height int, b *buffer.Buffer) *Tab {
|
||||
t := new(Tab)
|
||||
t.Node = views.NewRoot(x, y, width, height)
|
||||
t.UIWindow = display.NewUIWindow(t.Node)
|
||||
t.release = true
|
||||
|
||||
e := NewBufPaneFromBuf(b, t)
|
||||
e.SetID(t.ID())
|
||||
@@ -188,7 +187,6 @@ func NewTabFromPane(x, y, width, height int, pane Pane) *Tab {
|
||||
t := new(Tab)
|
||||
t.Node = views.NewRoot(x, y, width, height)
|
||||
t.UIWindow = display.NewUIWindow(t.Node)
|
||||
t.release = true
|
||||
pane.SetTab(t)
|
||||
pane.SetID(t.ID())
|
||||
|
||||
@@ -222,8 +220,9 @@ func (t *Tab) HandleEvent(event tcell.Event) {
|
||||
}
|
||||
|
||||
if wasReleased {
|
||||
t.resizing = t.GetMouseSplitNode(buffer.Loc{mx, my})
|
||||
if t.resizing != nil {
|
||||
resizeID := t.GetMouseSplitID(buffer.Loc{mx, my})
|
||||
if resizeID != 0 {
|
||||
t.resizing = t.GetNode(uint64(resizeID))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -24,10 +24,7 @@ func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callba
|
||||
}
|
||||
|
||||
t := new(shell.Terminal)
|
||||
err = t.Start(args, getOutput, wait, callback, userargs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Start(args, getOutput, wait, callback, userargs)
|
||||
|
||||
h.AddTab()
|
||||
id := MainTab().Panes[0].ID()
|
||||
|
||||
@@ -4,52 +4,14 @@ import (
|
||||
"errors"
|
||||
"runtime"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/clipboard"
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/clipboard"
|
||||
"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/tcell"
|
||||
"github.com/zyedidia/terminal"
|
||||
)
|
||||
|
||||
type TermKeyAction func(*TermPane)
|
||||
|
||||
var TermBindings *KeyTree
|
||||
|
||||
func init() {
|
||||
TermBindings = NewKeyTree()
|
||||
}
|
||||
|
||||
func TermKeyActionGeneral(a TermKeyAction) PaneKeyAction {
|
||||
return func(p Pane) bool {
|
||||
a(p.(*TermPane))
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func TermMapEvent(k Event, action string) {
|
||||
config.Bindings["terminal"][k.Name()] = action
|
||||
|
||||
switch e := k.(type) {
|
||||
case KeyEvent, KeySequenceEvent, RawEvent:
|
||||
termMapKey(e, action)
|
||||
case MouseEvent:
|
||||
termMapMouse(e, action)
|
||||
}
|
||||
}
|
||||
|
||||
func termMapKey(k Event, action string) {
|
||||
if f, ok := TermKeyActions[action]; ok {
|
||||
TermBindings.RegisterKeyBinding(k, TermKeyActionGeneral(f))
|
||||
}
|
||||
}
|
||||
|
||||
func termMapMouse(k MouseEvent, action string) {
|
||||
// TODO: map mouse
|
||||
termMapKey(k, action)
|
||||
}
|
||||
|
||||
type TermPane struct {
|
||||
*shell.Terminal
|
||||
display.Window
|
||||
@@ -91,7 +53,6 @@ func (t *TermPane) Tab() *Tab {
|
||||
|
||||
func (t *TermPane) Close() {}
|
||||
|
||||
// Quit closes this termpane
|
||||
func (t *TermPane) Quit() {
|
||||
t.Close()
|
||||
if len(MainTab().Panes) > 1 {
|
||||
@@ -105,7 +66,6 @@ func (t *TermPane) Quit() {
|
||||
}
|
||||
}
|
||||
|
||||
// Unsplit removes this split
|
||||
func (t *TermPane) Unsplit() {
|
||||
n := MainTab().GetNode(t.id)
|
||||
n.Unsplit()
|
||||
@@ -121,26 +81,6 @@ func (t *TermPane) Unsplit() {
|
||||
// copy-paste
|
||||
func (t *TermPane) HandleEvent(event tcell.Event) {
|
||||
if e, ok := event.(*tcell.EventKey); ok {
|
||||
ke := KeyEvent{
|
||||
code: e.Key(),
|
||||
mod: metaToAlt(e.Modifiers()),
|
||||
r: e.Rune(),
|
||||
}
|
||||
action, more := TermBindings.NextEvent(ke, nil)
|
||||
|
||||
if !more {
|
||||
if action != nil {
|
||||
action(t)
|
||||
TermBindings.ResetEvents()
|
||||
return
|
||||
}
|
||||
TermBindings.ResetEvents()
|
||||
}
|
||||
|
||||
if more {
|
||||
return
|
||||
}
|
||||
|
||||
if t.Status == shell.TTDone {
|
||||
switch e.Key() {
|
||||
case tcell.KeyEscape, tcell.KeyCtrlQ, tcell.KeyEnter:
|
||||
@@ -150,7 +90,7 @@ func (t *TermPane) HandleEvent(event tcell.Event) {
|
||||
}
|
||||
}
|
||||
if e.Key() == tcell.KeyCtrlC && t.HasSelection() {
|
||||
clipboard.Write(t.GetSelection(t.GetView().Width), clipboard.ClipboardReg)
|
||||
clipboard.WriteAll(t.GetSelection(t.GetView().Width), "clipboard")
|
||||
InfoBar.Message("Copied selection to clipboard")
|
||||
} else if t.Status != shell.TTDone {
|
||||
t.WriteString(event.EscSeq())
|
||||
@@ -194,41 +134,6 @@ func (t *TermPane) HandleEvent(event tcell.Event) {
|
||||
}
|
||||
}
|
||||
|
||||
// Exit closes the termpane
|
||||
func (t *TermPane) Exit() {
|
||||
t.Terminal.Close()
|
||||
t.Quit()
|
||||
}
|
||||
|
||||
// CommandMode opens the termpane's command mode
|
||||
func (t *TermPane) CommandMode() {
|
||||
InfoBar.Prompt("> ", "", "TerminalCommand", nil, func(resp string, canceled bool) {
|
||||
if !canceled {
|
||||
t.HandleCommand(resp)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// NextSplit moves to the next split
|
||||
func (t *TermPane) NextSplit() {
|
||||
a := t.tab.active
|
||||
if a < len(t.tab.Panes)-1 {
|
||||
a++
|
||||
} else {
|
||||
a = 0
|
||||
}
|
||||
|
||||
t.tab.SetActive(a)
|
||||
}
|
||||
|
||||
// HandleCommand handles a command for the term pane
|
||||
func (t *TermPane) HandleCommand(input string) {
|
||||
InfoBar.Error("Commands are unsupported in term for now")
|
||||
}
|
||||
|
||||
// TermKeyActions contains the list of all possible key actions the termpane could execute
|
||||
var TermKeyActions = map[string]TermKeyAction{
|
||||
"Exit": (*TermPane).Exit,
|
||||
"CommandMode": (*TermPane).CommandMode,
|
||||
"NextSplit": (*TermPane).NextSplit,
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@ func (b *Buffer) CycleAutocomplete(forward bool) {
|
||||
end := c.Loc
|
||||
if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 {
|
||||
start = end.Move(-util.CharacterCountInString(b.Completions[prevSuggestion]), b)
|
||||
} else {
|
||||
// end = start.Move(1, b)
|
||||
}
|
||||
|
||||
b.Replace(start, end, b.Completions[b.CurSuggestion])
|
||||
@@ -69,11 +71,11 @@ func GetWord(b *Buffer) ([]byte, int) {
|
||||
l := b.LineBytes(c.Y)
|
||||
l = util.SliceStart(l, c.X)
|
||||
|
||||
if c.X == 0 || util.IsWhitespace(b.RuneAt(c.Loc.Move(-1, b))) {
|
||||
if c.X == 0 || util.IsWhitespace(b.RuneAt(c.Loc)) {
|
||||
return []byte{}, -1
|
||||
}
|
||||
|
||||
if util.IsNonAlphaNumeric(b.RuneAt(c.Loc.Move(-1, b))) {
|
||||
if util.IsNonAlphaNumeric(b.RuneAt(c.Loc)) {
|
||||
return []byte{}, c.X
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
@@ -29,56 +28,29 @@ The backup was created on %s, and the file is
|
||||
|
||||
Options: [r]ecover, [i]gnore: `
|
||||
|
||||
var backupRequestChan chan *Buffer
|
||||
|
||||
func backupThread() {
|
||||
for {
|
||||
time.Sleep(time.Second * 8)
|
||||
|
||||
for len(backupRequestChan) > 0 {
|
||||
b := <-backupRequestChan
|
||||
bfini := atomic.LoadInt32(&(b.fini)) != 0
|
||||
if !bfini {
|
||||
b.Backup()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
backupRequestChan = make(chan *Buffer, 10)
|
||||
|
||||
go backupThread()
|
||||
}
|
||||
|
||||
func (b *Buffer) RequestBackup() {
|
||||
if !b.requestedBackup {
|
||||
select {
|
||||
case backupRequestChan <- b:
|
||||
default:
|
||||
// channel is full
|
||||
}
|
||||
b.requestedBackup = true
|
||||
}
|
||||
}
|
||||
|
||||
// Backup saves the current buffer to ConfigDir/backups
|
||||
func (b *Buffer) Backup() error {
|
||||
func (b *Buffer) Backup(checkTime bool) 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 checkTime {
|
||||
sub := time.Now().Sub(b.lastbackup)
|
||||
if sub < time.Duration(backupTime)*time.Millisecond {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
b.lastbackup = time.Now()
|
||||
|
||||
backupdir := filepath.Join(config.ConfigDir, "backups")
|
||||
if _, err := os.Stat(backupdir); os.IsNotExist(err) {
|
||||
os.Mkdir(backupdir, os.ModePerm)
|
||||
}
|
||||
|
||||
name := filepath.Join(backupdir, util.EscapePath(b.AbsPath))
|
||||
|
||||
err = overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
|
||||
err := overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
|
||||
if len(b.lines) == 0 {
|
||||
return
|
||||
}
|
||||
@@ -102,14 +74,12 @@ func (b *Buffer) Backup() error {
|
||||
return
|
||||
}, false)
|
||||
|
||||
b.requestedBackup = false
|
||||
|
||||
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.Path == "" || b.Type != BTDefault {
|
||||
return
|
||||
}
|
||||
f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
|
||||
@@ -119,7 +89,7 @@ func (b *Buffer) RemoveBackup() {
|
||||
// ApplyBackup applies the corresponding backup file to this buffer (if one exists)
|
||||
// Returns true if a backup was applied
|
||||
func (b *Buffer) ApplyBackup(fsize int64) bool {
|
||||
if b.Settings["backup"].(bool) && !b.Settings["permbackup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
|
||||
if b.Settings["backup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
|
||||
backupfile := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
|
||||
if info, err := os.Stat(backupfile); err == nil {
|
||||
backup, err := os.Open(backupfile)
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
luar "layeh.com/gopher-luar"
|
||||
@@ -103,7 +102,9 @@ type SharedBuffer struct {
|
||||
diffLock sync.RWMutex
|
||||
diff map[int]DiffStatus
|
||||
|
||||
requestedBackup bool
|
||||
// counts the number of edits
|
||||
// resets every backupTime edits
|
||||
lastbackup time.Time
|
||||
|
||||
// ReloadDisabled allows the user to disable reloads if they
|
||||
// are viewing a file that is constantly changing
|
||||
@@ -185,23 +186,9 @@ type Buffer struct {
|
||||
*EventHandler
|
||||
*SharedBuffer
|
||||
|
||||
fini int32
|
||||
cursors []*Cursor
|
||||
curCursor int
|
||||
StartCursor Loc
|
||||
|
||||
// OptionCallback is called after a buffer option value is changed.
|
||||
// The display module registers its OptionCallback to ensure the buffer window
|
||||
// is properly updated when needed. This is a workaround for the fact that
|
||||
// the buffer module cannot directly call the display's API (it would mean
|
||||
// a circular dependency between packages).
|
||||
OptionCallback func(option string, nativeValue interface{})
|
||||
|
||||
// The display module registers its own GetVisualX function for getting
|
||||
// the correct visual x location of a cursor when softwrap is used.
|
||||
// This is hacky. Maybe it would be better to move all the visual x logic
|
||||
// from buffer to display, but it would require rewriting a lot of code.
|
||||
GetVisualX func(loc Loc) int
|
||||
}
|
||||
|
||||
// NewBufferFromFileAtLoc opens a new buffer with a given cursor location
|
||||
@@ -225,37 +212,23 @@ 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()
|
||||
file, err := os.Open(filename)
|
||||
fileInfo, _ := os.Stat(filename)
|
||||
|
||||
fileInfo, serr := os.Stat(filename)
|
||||
if serr != nil && !os.IsNotExist(serr) {
|
||||
return nil, serr
|
||||
}
|
||||
if serr == nil && fileInfo.IsDir() {
|
||||
if err == nil && fileInfo.IsDir() {
|
||||
return nil, errors.New("Error: " + filename + " is a directory and cannot be opened")
|
||||
}
|
||||
|
||||
file, err := os.Open(filename)
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var buf *Buffer
|
||||
if os.IsNotExist(err) {
|
||||
if err != nil {
|
||||
// File does not exist -- create an empty buffer with that name
|
||||
buf = NewBufferFromString("", filename, btype)
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
buf = NewBuffer(file, util.FSize(file), filename, cursorLoc, btype)
|
||||
}
|
||||
|
||||
if readonly {
|
||||
buf.SetOptionNative("readonly", true)
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
@@ -298,7 +271,6 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
}
|
||||
}
|
||||
|
||||
hasBackup := false
|
||||
if !found {
|
||||
b.SharedBuffer = new(SharedBuffer)
|
||||
b.Type = btype
|
||||
@@ -306,33 +278,22 @@ 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()
|
||||
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, path)
|
||||
b.Settings["readonly"] = settings["readonly"]
|
||||
b.Settings["filetype"] = settings["filetype"]
|
||||
b.Settings["syntax"] = settings["syntax"]
|
||||
config.InitLocalSettings(b.Settings, path)
|
||||
|
||||
enc, err := htmlindex.Get(settings["encoding"].(string))
|
||||
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
|
||||
if err != nil {
|
||||
enc = unicode.UTF8
|
||||
b.Settings["encoding"] = "utf-8"
|
||||
}
|
||||
|
||||
hasBackup = b.ApplyBackup(size)
|
||||
hasBackup := b.ApplyBackup(size)
|
||||
|
||||
if !hasBackup {
|
||||
reader := bufio.NewReader(transform.NewReader(r, enc.NewDecoder()))
|
||||
@@ -342,7 +303,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
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":
|
||||
@@ -379,10 +340,12 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
|
||||
if startcursor.X != -1 && startcursor.Y != -1 {
|
||||
b.StartCursor = startcursor
|
||||
} else if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
|
||||
err := b.Unserialize()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
} else {
|
||||
if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
|
||||
err := b.Unserialize()
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,9 +356,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
|
||||
if size > LargeFileThreshold {
|
||||
// If the file is larger than LargeFileThreshold fastdirty needs to be on
|
||||
b.Settings["fastdirty"] = true
|
||||
} else if !hasBackup {
|
||||
// since applying a backup does not save the applied backup to disk, we should
|
||||
// not calculate the original hash based on the backup data
|
||||
} else {
|
||||
calcHash(b, &b.origHash)
|
||||
}
|
||||
}
|
||||
@@ -434,8 +395,6 @@ func (b *Buffer) Fini() {
|
||||
if b.Type == BTStdout {
|
||||
fmt.Fprint(util.Stdout, string(b.Bytes()))
|
||||
}
|
||||
|
||||
atomic.StoreInt32(&(b.fini), int32(1))
|
||||
}
|
||||
|
||||
// GetName returns the name that should be displayed in the statusline
|
||||
@@ -466,7 +425,7 @@ func (b *Buffer) Insert(start Loc, text string) {
|
||||
b.EventHandler.active = b.curCursor
|
||||
b.EventHandler.Insert(start, text)
|
||||
|
||||
b.RequestBackup()
|
||||
go b.Backup(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,7 +436,7 @@ func (b *Buffer) Remove(start, end Loc) {
|
||||
b.EventHandler.active = b.curCursor
|
||||
b.EventHandler.Remove(start, end)
|
||||
|
||||
b.RequestBackup()
|
||||
go b.Backup(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,38 +506,16 @@ func (b *Buffer) RuneAt(loc Loc) rune {
|
||||
for len(line) > 0 {
|
||||
r, _, size := util.DecodeCharacter(line)
|
||||
line = line[size:]
|
||||
i++
|
||||
|
||||
if i == loc.X {
|
||||
return r
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
}
|
||||
return '\n'
|
||||
}
|
||||
|
||||
// WordAt returns the word around a given location in the buffer
|
||||
func (b *Buffer) WordAt(loc Loc) []byte {
|
||||
if len(b.LineBytes(loc.Y)) == 0 || !util.IsWordChar(b.RuneAt(loc)) {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
start := loc
|
||||
end := loc.Move(1, b)
|
||||
|
||||
for start.X > 0 && util.IsWordChar(b.RuneAt(start.Move(-1, b))) {
|
||||
start.X--
|
||||
}
|
||||
|
||||
lineLen := util.CharacterCount(b.LineBytes(loc.Y))
|
||||
for end.X < lineLen && util.IsWordChar(b.RuneAt(end)) {
|
||||
end.X++
|
||||
}
|
||||
|
||||
return b.Substr(start, end)
|
||||
}
|
||||
|
||||
// Modified returns if this buffer has been modified since
|
||||
// being opened
|
||||
func (b *Buffer) Modified() bool {
|
||||
@@ -596,22 +533,6 @@ func (b *Buffer) Modified() bool {
|
||||
return buff != b.origHash
|
||||
}
|
||||
|
||||
// Size returns the number of bytes in the current buffer
|
||||
func (b *Buffer) Size() int {
|
||||
nb := 0
|
||||
for i := 0; i < b.LinesNum(); i++ {
|
||||
nb += len(b.LineBytes(i))
|
||||
|
||||
if i != b.LinesNum()-1 {
|
||||
if b.Endings == FFDos {
|
||||
nb++ // carriage return
|
||||
}
|
||||
nb++ // newline
|
||||
}
|
||||
}
|
||||
return nb
|
||||
}
|
||||
|
||||
// calcHash calculates md5 hash of all lines in the buffer
|
||||
func calcHash(b *Buffer, out *[md5.Size]byte) error {
|
||||
h := md5.New()
|
||||
@@ -668,9 +589,6 @@ func (b *Buffer) UpdateRules() {
|
||||
}
|
||||
|
||||
header, err = highlight.MakeHeaderYaml(data)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing header for syntax file " + f.Name() + ": " + err.Error())
|
||||
}
|
||||
file, err := highlight.ParseFile(data)
|
||||
if err != nil {
|
||||
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
|
||||
@@ -1046,9 +964,9 @@ func (b *Buffer) Retab() {
|
||||
ws := util.GetLeadingWhitespace(l)
|
||||
if len(ws) != 0 {
|
||||
if toSpaces {
|
||||
ws = bytes.ReplaceAll(ws, []byte{'\t'}, bytes.Repeat([]byte{' '}, tabsize))
|
||||
ws = bytes.Replace(ws, []byte{'\t'}, bytes.Repeat([]byte{' '}, tabsize), -1)
|
||||
} else {
|
||||
ws = bytes.ReplaceAll(ws, bytes.Repeat([]byte{' '}, tabsize), []byte{'\t'})
|
||||
ws = bytes.Replace(ws, bytes.Repeat([]byte{' '}, tabsize), []byte{'\t'}, -1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/v2/internal/clipboard"
|
||||
"github.com/zyedidia/clipboard"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
@@ -67,10 +67,6 @@ 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 {
|
||||
return c.buf.GetVisualX(c.Loc)
|
||||
}
|
||||
|
||||
if c.X <= 0 {
|
||||
c.X = 0
|
||||
return 0
|
||||
@@ -129,10 +125,10 @@ func (c *Cursor) End() {
|
||||
|
||||
// CopySelection copies the user's selection to either "primary"
|
||||
// or "clipboard"
|
||||
func (c *Cursor) CopySelection(target clipboard.Register) {
|
||||
func (c *Cursor) CopySelection(target string) {
|
||||
if c.HasSelection() {
|
||||
if target != clipboard.PrimaryReg || c.buf.Settings["useprimary"].(bool) {
|
||||
clipboard.WriteMulti(string(c.GetSelection()), target, c.Num, c.buf.NumCursors())
|
||||
if target != "primary" || c.buf.Settings["useprimary"].(bool) {
|
||||
clipboard.WriteAll(string(c.GetSelection()), target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
la.lines = Append(la.lines, Line{
|
||||
data: data,
|
||||
data: data[:],
|
||||
state: nil,
|
||||
match: nil,
|
||||
rehighlight: false,
|
||||
@@ -191,15 +191,10 @@ func (la *LineArray) newlineBelow(y int) {
|
||||
func (la *LineArray) insert(pos Loc, value []byte) {
|
||||
x, y := runeToByteIndex(pos.X, la.lines[pos.Y].data), pos.Y
|
||||
for i := 0; i < len(value); i++ {
|
||||
if value[i] == '\n' || (value[i] == '\r' && i < len(value)-1 && value[i+1] == '\n') {
|
||||
if value[i] == '\n' {
|
||||
la.split(Loc{x, y})
|
||||
x = 0
|
||||
y++
|
||||
|
||||
if value[i] == '\r' {
|
||||
i++
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
la.insertByte(Loc{x, y}, value[i])
|
||||
|
||||
@@ -2,7 +2,7 @@ package buffer
|
||||
|
||||
import (
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
type MsgType int
|
||||
|
||||
@@ -109,7 +109,7 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
|
||||
|
||||
if b.Settings["eofnewline"].(bool) {
|
||||
end := b.End()
|
||||
if b.RuneAt(Loc{end.X - 1, end.Y}) != '\n' {
|
||||
if b.RuneAt(Loc{end.X, end.Y}) != '\n' {
|
||||
b.insert(end, []byte{'\n'})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,6 @@ import (
|
||||
)
|
||||
|
||||
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 {
|
||||
start.X = lastcn - 1
|
||||
}
|
||||
if end.Y > b.LinesNum()-1 {
|
||||
end.X = lastcn
|
||||
}
|
||||
start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
|
||||
end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)
|
||||
|
||||
@@ -55,13 +48,6 @@ func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||
}
|
||||
|
||||
func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
|
||||
lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
|
||||
if start.Y > b.LinesNum()-1 {
|
||||
start.X = lastcn - 1
|
||||
}
|
||||
if end.Y > b.LinesNum()-1 {
|
||||
end.X = lastcn
|
||||
}
|
||||
start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
|
||||
end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)
|
||||
|
||||
@@ -146,7 +132,7 @@ 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
|
||||
// added or removed on the last line of the range
|
||||
// added or removed
|
||||
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte) (int, int) {
|
||||
if start.GreaterThan(end) {
|
||||
start, end = end, start
|
||||
@@ -176,9 +162,7 @@ func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []b
|
||||
result = search.Expand(result, replace, in, submatches)
|
||||
}
|
||||
found++
|
||||
if i == end.Y {
|
||||
netrunes += util.CharacterCount(result) - util.CharacterCount(in)
|
||||
}
|
||||
netrunes += util.CharacterCount(in) - util.CharacterCount(result)
|
||||
return result
|
||||
})
|
||||
|
||||
|
||||
@@ -51,8 +51,8 @@ func (b *Buffer) Unserialize() error {
|
||||
return nil
|
||||
}
|
||||
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath)))
|
||||
defer file.Close()
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
var buffer SerializedBuffer
|
||||
decoder := gob.NewDecoder(file)
|
||||
err = decoder.Decode(&buffer)
|
||||
|
||||
@@ -10,11 +10,9 @@ func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
|
||||
|
||||
if option == "fastdirty" {
|
||||
if !nativeValue.(bool) {
|
||||
if !b.Modified() {
|
||||
e := calcHash(b, &b.origHash)
|
||||
if e == ErrFileTooLarge {
|
||||
b.Settings["fastdirty"] = false
|
||||
}
|
||||
e := calcHash(b, &b.origHash)
|
||||
if e == ErrFileTooLarge {
|
||||
b.Settings["fastdirty"] = false
|
||||
}
|
||||
}
|
||||
} else if option == "statusline" {
|
||||
@@ -41,10 +39,6 @@ func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
|
||||
b.Type.Readonly = nativeValue.(bool)
|
||||
}
|
||||
|
||||
if b.OptionCallback != nil {
|
||||
b.OptionCallback(option, nativeValue)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
package clipboard
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/zyedidia/clipboard"
|
||||
)
|
||||
|
||||
type Method int
|
||||
|
||||
const (
|
||||
// External relies on external tools for accessing the clipboard
|
||||
// These include xclip, xsel, wl-clipboard for linux, pbcopy/pbpaste on Mac,
|
||||
// and Syscalls on Windows.
|
||||
External Method = iota
|
||||
// Terminal uses the terminal to manage the clipboard via OSC 52. Many
|
||||
// terminals do not support OSC 52, in which case this method won't work.
|
||||
Terminal
|
||||
// Internal just manages the clipboard with an internal buffer and doesn't
|
||||
// attempt to interface with the system clipboard
|
||||
Internal
|
||||
)
|
||||
|
||||
// CurrentMethod is the method used to store clipboard information
|
||||
var CurrentMethod Method = Internal
|
||||
|
||||
// A Register is a buffer used to store text. The system clipboard has the 'clipboard'
|
||||
// and 'primary' (linux-only) registers, but other registers may be used internal to micro.
|
||||
type Register int
|
||||
|
||||
const (
|
||||
// ClipboardReg is the main system clipboard
|
||||
ClipboardReg Register = -1
|
||||
// PrimaryReg is the system primary clipboard (linux only)
|
||||
PrimaryReg = -2
|
||||
)
|
||||
|
||||
// Initialize attempts to initialize the clipboard using the given method
|
||||
func Initialize(m Method) error {
|
||||
var err error
|
||||
switch m {
|
||||
case External:
|
||||
err = clipboard.Initialize()
|
||||
}
|
||||
if err != nil {
|
||||
CurrentMethod = Internal
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SetMethod changes the clipboard access method
|
||||
func SetMethod(m string) Method {
|
||||
switch m {
|
||||
case "internal":
|
||||
CurrentMethod = Internal
|
||||
case "external":
|
||||
CurrentMethod = External
|
||||
case "terminal":
|
||||
CurrentMethod = Terminal
|
||||
}
|
||||
return CurrentMethod
|
||||
}
|
||||
|
||||
// Read reads from a clipboard register
|
||||
func Read(r Register) (string, error) {
|
||||
return read(r, CurrentMethod)
|
||||
}
|
||||
|
||||
// Write writes text to a clipboard register
|
||||
func Write(text string, r Register) error {
|
||||
return write(text, r, CurrentMethod)
|
||||
}
|
||||
|
||||
// ReadMulti reads text from a clipboard register for a certain multi-cursor
|
||||
func ReadMulti(r Register, num, ncursors int) (string, error) {
|
||||
clip, err := Read(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if ValidMulti(r, clip, ncursors) {
|
||||
return multi.getText(r, num), nil
|
||||
}
|
||||
return clip, nil
|
||||
}
|
||||
|
||||
// WriteMulti writes text to a clipboard register for a certain multi-cursor
|
||||
func WriteMulti(text string, r Register, num int, ncursors int) error {
|
||||
return writeMulti(text, r, num, ncursors, CurrentMethod)
|
||||
}
|
||||
|
||||
// ValidMulti checks if the internal multi-clipboard is valid and up-to-date
|
||||
// with the system clipboard
|
||||
func ValidMulti(r Register, clip string, ncursors int) bool {
|
||||
return multi.isValid(r, clip, ncursors)
|
||||
}
|
||||
|
||||
func writeMulti(text string, r Register, num int, ncursors int, m Method) error {
|
||||
multi.writeText(text, r, num, ncursors)
|
||||
return write(multi.getAllText(r), r, m)
|
||||
}
|
||||
|
||||
func read(r Register, m Method) (string, error) {
|
||||
switch m {
|
||||
case External:
|
||||
switch r {
|
||||
case ClipboardReg:
|
||||
return clipboard.ReadAll("clipboard")
|
||||
case PrimaryReg:
|
||||
return clipboard.ReadAll("primary")
|
||||
default:
|
||||
return internal.read(r), nil
|
||||
}
|
||||
case Internal:
|
||||
return internal.read(r), nil
|
||||
case Terminal:
|
||||
switch r {
|
||||
case ClipboardReg:
|
||||
// terminal paste works by sending an esc sequence to the
|
||||
// terminal to trigger a paste event
|
||||
return terminal.read("clipboard")
|
||||
case PrimaryReg:
|
||||
return terminal.read("primary")
|
||||
default:
|
||||
return internal.read(r), nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("Invalid clipboard method")
|
||||
}
|
||||
|
||||
func write(text string, r Register, m Method) error {
|
||||
switch m {
|
||||
case External:
|
||||
switch r {
|
||||
case ClipboardReg:
|
||||
return clipboard.WriteAll(text, "clipboard")
|
||||
case PrimaryReg:
|
||||
return clipboard.WriteAll(text, "primary")
|
||||
default:
|
||||
internal.write(text, r)
|
||||
}
|
||||
case Internal:
|
||||
internal.write(text, r)
|
||||
case Terminal:
|
||||
switch r {
|
||||
case ClipboardReg:
|
||||
return terminal.write(text, "c")
|
||||
case PrimaryReg:
|
||||
return terminal.write(text, "p")
|
||||
default:
|
||||
internal.write(text, r)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package clipboard
|
||||
|
||||
type internalClipboard map[Register]string
|
||||
|
||||
var internal internalClipboard
|
||||
|
||||
func init() {
|
||||
internal = make(internalClipboard)
|
||||
}
|
||||
|
||||
func (c internalClipboard) read(r Register) string {
|
||||
return c[r]
|
||||
}
|
||||
|
||||
func (c internalClipboard) write(text string, r Register) {
|
||||
c[r] = text
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package clipboard
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// For storing multi cursor clipboard contents
|
||||
type multiClipboard map[Register][]string
|
||||
|
||||
var multi multiClipboard
|
||||
|
||||
func (c multiClipboard) getAllText(r Register) string {
|
||||
content := c[r]
|
||||
if content == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
for _, s := range content {
|
||||
buf.WriteString(s)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (c multiClipboard) getText(r Register, num int) string {
|
||||
content := c[r]
|
||||
if content == nil || len(content) <= num {
|
||||
return ""
|
||||
}
|
||||
|
||||
return content[num]
|
||||
}
|
||||
|
||||
// isValid checks if the text stored in this multi-clipboard is the same as the
|
||||
// text stored in the system clipboard (provided as an argument), and therefore
|
||||
// if it is safe to use the multi-clipboard for pasting instead of the system
|
||||
// clipboard.
|
||||
func (c multiClipboard) isValid(r Register, clipboard string, ncursors int) bool {
|
||||
content := c[r]
|
||||
if content == nil || len(content) != ncursors {
|
||||
return false
|
||||
}
|
||||
|
||||
return clipboard == c.getAllText(r)
|
||||
}
|
||||
|
||||
func (c multiClipboard) writeText(text string, r Register, num int, ncursors int) {
|
||||
content := c[r]
|
||||
if content == nil || len(content) != ncursors {
|
||||
content = make([]string, ncursors, ncursors)
|
||||
c[r] = content
|
||||
}
|
||||
|
||||
if num >= ncursors {
|
||||
return
|
||||
}
|
||||
|
||||
content[num] = text
|
||||
}
|
||||
|
||||
func init() {
|
||||
multi = make(multiClipboard)
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package clipboard
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
)
|
||||
|
||||
type terminalClipboard struct{}
|
||||
|
||||
var terminal terminalClipboard
|
||||
|
||||
func (t terminalClipboard) read(reg string) (string, error) {
|
||||
screen.Screen.GetClipboard(reg)
|
||||
// wait at most 200ms for response
|
||||
for {
|
||||
select {
|
||||
case event := <-screen.Events:
|
||||
e, ok := event.(*tcell.EventPaste)
|
||||
if ok {
|
||||
return e.Text(), nil
|
||||
}
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
return "", errors.New("No clipboard received from terminal")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t terminalClipboard) write(text, reg string) error {
|
||||
return screen.Screen.SetClipboard(text, reg)
|
||||
}
|
||||
@@ -31,13 +31,14 @@ func GetAutoTime() int {
|
||||
func StartAutoSave() {
|
||||
go func() {
|
||||
for {
|
||||
autolock.Lock()
|
||||
a := autotime
|
||||
autolock.Unlock()
|
||||
if a < 1 {
|
||||
if autotime < 1 {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Duration(autotime) * time.Second)
|
||||
// it's possible autotime was changed while sleeping
|
||||
if autotime < 1 {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Duration(a) * time.Second)
|
||||
Autosave <- true
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
// Micro's default style
|
||||
@@ -117,11 +117,16 @@ func ParseColorscheme(text string) (map[string]tcell.Style, error) {
|
||||
|
||||
// StringToStyle returns a style from a string
|
||||
// The strings must be in the format "extra foregroundcolor,backgroundcolor"
|
||||
// The 'extra' can be bold, reverse, italic or underline
|
||||
// The 'extra' can be bold, reverse, or underline
|
||||
func StringToStyle(str string) tcell.Style {
|
||||
var fg, bg string
|
||||
spaceSplit := strings.Split(str, " ")
|
||||
split := strings.Split(spaceSplit[len(spaceSplit)-1], ",")
|
||||
var split []string
|
||||
if len(spaceSplit) > 1 {
|
||||
split = strings.Split(spaceSplit[1], ",")
|
||||
} else {
|
||||
split = strings.Split(str, ",")
|
||||
}
|
||||
if len(split) > 1 {
|
||||
fg, bg = split[0], split[1]
|
||||
} else {
|
||||
@@ -208,8 +213,47 @@ func StringToColor(str string) tcell.Color {
|
||||
|
||||
// GetColor256 returns the tcell color for a number between 0 and 255
|
||||
func GetColor256(color int) tcell.Color {
|
||||
if color == 0 {
|
||||
return tcell.ColorDefault
|
||||
colors := []tcell.Color{tcell.ColorBlack, tcell.ColorMaroon, tcell.ColorGreen,
|
||||
tcell.ColorOlive, tcell.ColorNavy, tcell.ColorPurple,
|
||||
tcell.ColorTeal, tcell.ColorSilver, tcell.ColorGray,
|
||||
tcell.ColorRed, tcell.ColorLime, tcell.ColorYellow,
|
||||
tcell.ColorBlue, tcell.ColorFuchsia, tcell.ColorAqua,
|
||||
tcell.ColorWhite, tcell.Color16, tcell.Color17, tcell.Color18, tcell.Color19, tcell.Color20,
|
||||
tcell.Color21, tcell.Color22, tcell.Color23, tcell.Color24, tcell.Color25, tcell.Color26, tcell.Color27, tcell.Color28,
|
||||
tcell.Color29, tcell.Color30, tcell.Color31, tcell.Color32, tcell.Color33, tcell.Color34, tcell.Color35, tcell.Color36,
|
||||
tcell.Color37, tcell.Color38, tcell.Color39, tcell.Color40, tcell.Color41, tcell.Color42, tcell.Color43, tcell.Color44,
|
||||
tcell.Color45, tcell.Color46, tcell.Color47, tcell.Color48, tcell.Color49, tcell.Color50, tcell.Color51, tcell.Color52,
|
||||
tcell.Color53, tcell.Color54, tcell.Color55, tcell.Color56, tcell.Color57, tcell.Color58, tcell.Color59, tcell.Color60,
|
||||
tcell.Color61, tcell.Color62, tcell.Color63, tcell.Color64, tcell.Color65, tcell.Color66, tcell.Color67, tcell.Color68,
|
||||
tcell.Color69, tcell.Color70, tcell.Color71, tcell.Color72, tcell.Color73, tcell.Color74, tcell.Color75, tcell.Color76,
|
||||
tcell.Color77, tcell.Color78, tcell.Color79, tcell.Color80, tcell.Color81, tcell.Color82, tcell.Color83, tcell.Color84,
|
||||
tcell.Color85, tcell.Color86, tcell.Color87, tcell.Color88, tcell.Color89, tcell.Color90, tcell.Color91, tcell.Color92,
|
||||
tcell.Color93, tcell.Color94, tcell.Color95, tcell.Color96, tcell.Color97, tcell.Color98, tcell.Color99, tcell.Color100,
|
||||
tcell.Color101, tcell.Color102, tcell.Color103, tcell.Color104, tcell.Color105, tcell.Color106, tcell.Color107, tcell.Color108,
|
||||
tcell.Color109, tcell.Color110, tcell.Color111, tcell.Color112, tcell.Color113, tcell.Color114, tcell.Color115, tcell.Color116,
|
||||
tcell.Color117, tcell.Color118, tcell.Color119, tcell.Color120, tcell.Color121, tcell.Color122, tcell.Color123, tcell.Color124,
|
||||
tcell.Color125, tcell.Color126, tcell.Color127, tcell.Color128, tcell.Color129, tcell.Color130, tcell.Color131, tcell.Color132,
|
||||
tcell.Color133, tcell.Color134, tcell.Color135, tcell.Color136, tcell.Color137, tcell.Color138, tcell.Color139, tcell.Color140,
|
||||
tcell.Color141, tcell.Color142, tcell.Color143, tcell.Color144, tcell.Color145, tcell.Color146, tcell.Color147, tcell.Color148,
|
||||
tcell.Color149, tcell.Color150, tcell.Color151, tcell.Color152, tcell.Color153, tcell.Color154, tcell.Color155, tcell.Color156,
|
||||
tcell.Color157, tcell.Color158, tcell.Color159, tcell.Color160, tcell.Color161, tcell.Color162, tcell.Color163, tcell.Color164,
|
||||
tcell.Color165, tcell.Color166, tcell.Color167, tcell.Color168, tcell.Color169, tcell.Color170, tcell.Color171, tcell.Color172,
|
||||
tcell.Color173, tcell.Color174, tcell.Color175, tcell.Color176, tcell.Color177, tcell.Color178, tcell.Color179, tcell.Color180,
|
||||
tcell.Color181, tcell.Color182, tcell.Color183, tcell.Color184, tcell.Color185, tcell.Color186, tcell.Color187, tcell.Color188,
|
||||
tcell.Color189, tcell.Color190, tcell.Color191, tcell.Color192, tcell.Color193, tcell.Color194, tcell.Color195, tcell.Color196,
|
||||
tcell.Color197, tcell.Color198, tcell.Color199, tcell.Color200, tcell.Color201, tcell.Color202, tcell.Color203, tcell.Color204,
|
||||
tcell.Color205, tcell.Color206, tcell.Color207, tcell.Color208, tcell.Color209, tcell.Color210, tcell.Color211, tcell.Color212,
|
||||
tcell.Color213, tcell.Color214, tcell.Color215, tcell.Color216, tcell.Color217, tcell.Color218, tcell.Color219, tcell.Color220,
|
||||
tcell.Color221, tcell.Color222, tcell.Color223, tcell.Color224, tcell.Color225, tcell.Color226, tcell.Color227, tcell.Color228,
|
||||
tcell.Color229, tcell.Color230, tcell.Color231, tcell.Color232, tcell.Color233, tcell.Color234, tcell.Color235, tcell.Color236,
|
||||
tcell.Color237, tcell.Color238, tcell.Color239, tcell.Color240, tcell.Color241, tcell.Color242, tcell.Color243, tcell.Color244,
|
||||
tcell.Color245, tcell.Color246, tcell.Color247, tcell.Color248, tcell.Color249, tcell.Color250, tcell.Color251, tcell.Color252,
|
||||
tcell.Color253, tcell.Color254, tcell.Color255,
|
||||
}
|
||||
return tcell.PaletteColor(color)
|
||||
|
||||
if color >= 0 && color < len(colors) {
|
||||
return colors[color]
|
||||
}
|
||||
|
||||
return tcell.ColorDefault
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
func TestSimpleStringToStyle(t *testing.T) {
|
||||
@@ -26,18 +26,6 @@ func TestAttributeStringToStyle(t *testing.T) {
|
||||
assert.NotEqual(t, 0, attr&tcell.AttrBold)
|
||||
}
|
||||
|
||||
func TestMultiAttributesStringToStyle(t *testing.T) {
|
||||
s := StringToStyle("bold italic underline cyan,brightcyan")
|
||||
|
||||
fg, bg, attr := s.Decompose()
|
||||
|
||||
assert.Equal(t, tcell.ColorTeal, fg)
|
||||
assert.Equal(t, tcell.ColorAqua, bg)
|
||||
assert.NotEqual(t, 0, attr&tcell.AttrBold)
|
||||
assert.NotEqual(t, 0, attr&tcell.AttrItalic)
|
||||
assert.NotEqual(t, 0, attr&tcell.AttrUnderline)
|
||||
}
|
||||
|
||||
func TestColor256StringToStyle(t *testing.T) {
|
||||
s := StringToStyle("128,60")
|
||||
|
||||
|
||||
@@ -4,12 +4,4 @@ const (
|
||||
DoubleClickThreshold = 400 // How many milliseconds to wait before a second click is not a double click
|
||||
)
|
||||
|
||||
var Bindings map[string]map[string]string
|
||||
|
||||
func init() {
|
||||
Bindings = map[string]map[string]string{
|
||||
"command": make(map[string]string),
|
||||
"buffer": make(map[string]string),
|
||||
"terminal": make(map[string]string),
|
||||
}
|
||||
}
|
||||
var Bindings map[string]string
|
||||
|
||||
@@ -479,7 +479,9 @@ func (pl PluginPackages) GetAllVersions(name string) PluginVersions {
|
||||
result := make(PluginVersions, 0)
|
||||
p := pl.Get(name)
|
||||
if p != nil {
|
||||
result = append(result, p.Versions...)
|
||||
for _, v := range p.Versions {
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -605,7 +607,7 @@ func UpdatePlugins(out io.Writer, plugins []string) {
|
||||
// if no plugins are specified, update all installed plugins.
|
||||
if len(plugins) == 0 {
|
||||
for _, p := range Plugins {
|
||||
if !p.IsEnabled() || p.Default {
|
||||
if !p.IsEnabled() {
|
||||
continue
|
||||
}
|
||||
plugins = append(plugins, p.Name)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:generate go run runtime_generate.go ../../runtime/syntax ../../runtime
|
||||
package config
|
||||
|
||||
import (
|
||||
@@ -9,6 +10,8 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/vfsutil"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -90,7 +93,7 @@ func (af assetFile) Name() string {
|
||||
}
|
||||
|
||||
func (af assetFile) Data() ([]byte, error) {
|
||||
return Asset(string(af))
|
||||
return vfsutil.ReadFile(Assets, string(af))
|
||||
}
|
||||
|
||||
func (nf namedFile) Name() string {
|
||||
@@ -111,7 +114,10 @@ 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, err := ioutil.ReadDir(directory)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, f := range files {
|
||||
if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
|
||||
fullPath := filepath.Join(directory, f.Name())
|
||||
@@ -123,13 +129,13 @@ func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string
|
||||
// AddRuntimeFilesFromAssets registers each file from the given asset-directory for
|
||||
// the filetype which matches the file-pattern
|
||||
func AddRuntimeFilesFromAssets(fileType RTFiletype, directory, pattern string) {
|
||||
files, err := AssetDir(directory)
|
||||
files, err := vfsutil.ReadDir(Assets, directory)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, f := range files {
|
||||
if ok, _ := path.Match(pattern, f); ok {
|
||||
AddRuntimeFile(fileType, assetFile(path.Join(directory, f)))
|
||||
if ok, _ := path.Match(pattern, f.Name()); ok {
|
||||
AddRuntimeFile(fileType, assetFile(path.Join(directory, f.Name())))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,11 +162,60 @@ func ListRealRuntimeFiles(fileType RTFiletype) []RuntimeFile {
|
||||
return realFiles[fileType]
|
||||
}
|
||||
|
||||
func addPlugin(dirname string, plugdir string, isID func(string) bool, vfs bool) {
|
||||
var srcs []os.FileInfo
|
||||
var err error
|
||||
if vfs {
|
||||
srcs, err = vfsutil.ReadDir(Assets, filepath.Join(plugdir, dirname))
|
||||
} else {
|
||||
srcs, err = ioutil.ReadDir(filepath.Join(plugdir, dirname))
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p := new(Plugin)
|
||||
p.Name = dirname
|
||||
p.DirName = dirname
|
||||
p.Default = vfs
|
||||
for _, f := range srcs {
|
||||
if strings.HasSuffix(f.Name(), ".lua") {
|
||||
if vfs {
|
||||
p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, dirname, f.Name())))
|
||||
} else {
|
||||
p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, dirname, f.Name())))
|
||||
}
|
||||
} else if strings.HasSuffix(f.Name(), ".json") {
|
||||
var data []byte
|
||||
var err error
|
||||
|
||||
if vfs {
|
||||
data, err = vfsutil.ReadFile(Assets, filepath.Join(plugdir, dirname, f.Name()))
|
||||
} else {
|
||||
data, err = ioutil.ReadFile(filepath.Join(plugdir, dirname, f.Name()))
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p.Info, err = NewPluginInfo(data)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p.Name = p.Info.Name
|
||||
}
|
||||
}
|
||||
|
||||
if !isID(p.Name) || len(p.Srcs) <= 0 {
|
||||
log.Println(p.Name, "is not a plugin")
|
||||
return
|
||||
}
|
||||
Plugins = append(Plugins, p)
|
||||
}
|
||||
|
||||
// InitRuntimeFiles initializes all assets file and the config directory
|
||||
func InitRuntimeFiles() {
|
||||
add := func(fileType RTFiletype, dir, pattern string) {
|
||||
AddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)
|
||||
AddRuntimeFilesFromAssets(fileType, path.Join("runtime", dir), pattern)
|
||||
AddRuntimeFilesFromAssets(fileType, dir, pattern)
|
||||
}
|
||||
|
||||
add(RTColorscheme, "colorschemes", "*.micro")
|
||||
@@ -179,68 +234,24 @@ func InitRuntimeFiles() {
|
||||
|
||||
// Search ConfigDir for plugin-scripts
|
||||
plugdir := filepath.Join(ConfigDir, "plug")
|
||||
files, _ := ioutil.ReadDir(plugdir)
|
||||
files, err := ioutil.ReadDir(plugdir)
|
||||
|
||||
isID := regexp.MustCompile(`^[_A-Za-z0-9]+$`).MatchString
|
||||
|
||||
for _, d := range files {
|
||||
if d.IsDir() {
|
||||
srcs, _ := ioutil.ReadDir(filepath.Join(plugdir, d.Name()))
|
||||
p := new(Plugin)
|
||||
p.Name = d.Name()
|
||||
p.DirName = d.Name()
|
||||
for _, f := range srcs {
|
||||
if strings.HasSuffix(f.Name(), ".lua") {
|
||||
p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
|
||||
} else if strings.HasSuffix(f.Name(), ".json") {
|
||||
data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p.Info, err = NewPluginInfo(data)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p.Name = p.Info.Name
|
||||
}
|
||||
if err == nil {
|
||||
for _, d := range files {
|
||||
if d.IsDir() {
|
||||
addPlugin(d.Name(), plugdir, isID, false)
|
||||
}
|
||||
|
||||
if !isID(p.Name) || len(p.Srcs) <= 0 {
|
||||
log.Println(p.Name, "is not a plugin")
|
||||
continue
|
||||
}
|
||||
Plugins = append(Plugins, p)
|
||||
}
|
||||
}
|
||||
|
||||
plugdir = filepath.Join("runtime", "plugins")
|
||||
if files, err := AssetDir(plugdir); err == nil {
|
||||
plugdir = "plugins"
|
||||
files, err = vfsutil.ReadDir(Assets, plugdir)
|
||||
if err == nil {
|
||||
for _, d := range files {
|
||||
if srcs, err := AssetDir(filepath.Join(plugdir, d)); err == nil {
|
||||
p := new(Plugin)
|
||||
p.Name = d
|
||||
p.DirName = d
|
||||
p.Default = true
|
||||
for _, f := range srcs {
|
||||
if strings.HasSuffix(f, ".lua") {
|
||||
p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))
|
||||
} else if strings.HasSuffix(f, ".json") {
|
||||
data, err := Asset(filepath.Join(plugdir, d, f))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p.Info, err = NewPluginInfo(data)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
p.Name = p.Info.Name
|
||||
}
|
||||
}
|
||||
if !isID(p.Name) || len(p.Srcs) <= 0 {
|
||||
log.Println(p.Name, "is not a plugin")
|
||||
continue
|
||||
}
|
||||
Plugins = append(Plugins, p)
|
||||
if d.IsDir() {
|
||||
addPlugin(d.Name(), plugdir, isID, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,7 +288,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 = path.Join("plugins", pldir, filePath)
|
||||
AddRuntimeFile(filetype, assetFile(fullpath))
|
||||
}
|
||||
return nil
|
||||
@@ -294,7 +305,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 = path.Join("plugins", pldir, directory)
|
||||
AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
|
||||
}
|
||||
return nil
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,14 +1,22 @@
|
||||
// +build ignore
|
||||
|
||||
// This script generates the embedded runtime filesystem, and also creates
|
||||
// syntax header metadata which makes loading syntax files at runtime faster
|
||||
// Invoke as go run runtime_generate.go syntaxDir runtimeDir
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/shurcooL/vfsgen"
|
||||
)
|
||||
|
||||
type HeaderYaml struct {
|
||||
@@ -25,19 +33,6 @@ type Header struct {
|
||||
HeaderRgx string
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 {
|
||||
os.Chdir(os.Args[1])
|
||||
}
|
||||
files, _ := ioutil.ReadDir(".")
|
||||
for _, f := range files {
|
||||
fname := f.Name()
|
||||
if strings.HasSuffix(fname, ".yaml") {
|
||||
convert(fname[:len(fname)-5])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func convert(name string) {
|
||||
filename := name + ".yaml"
|
||||
var hdr HeaderYaml
|
||||
@@ -61,14 +56,57 @@ func encode(name string, c HeaderYaml) {
|
||||
}
|
||||
|
||||
func decode(name string) Header {
|
||||
start := time.Now()
|
||||
data, _ := ioutil.ReadFile(name + ".hdr")
|
||||
strs := bytes.Split(data, []byte{'\n'})
|
||||
var hdr Header
|
||||
hdr.FileType = string(strs[0])
|
||||
hdr.FNameRgx = string(strs[1])
|
||||
hdr.HeaderRgx = string(strs[2])
|
||||
fmt.Printf("took %v\n", time.Since(start))
|
||||
|
||||
return hdr
|
||||
}
|
||||
|
||||
func main() {
|
||||
orig, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatalln("Couldn't get cwd")
|
||||
return
|
||||
}
|
||||
if len(os.Args) < 2 {
|
||||
log.Fatalln("Not enough arguments")
|
||||
}
|
||||
|
||||
syntaxDir := os.Args[1]
|
||||
assetDir := os.Args[2]
|
||||
|
||||
os.Chdir(syntaxDir)
|
||||
files, _ := ioutil.ReadDir(".")
|
||||
|
||||
// first remove all existing header files (clean the directory)
|
||||
for _, f := range files {
|
||||
fname := f.Name()
|
||||
if strings.HasSuffix(fname, ".hdr") {
|
||||
os.Remove(fname)
|
||||
}
|
||||
}
|
||||
|
||||
// now create a header file for each yaml
|
||||
for _, f := range files {
|
||||
fname := f.Name()
|
||||
if strings.HasSuffix(fname, ".yaml") {
|
||||
convert(fname[:len(fname)-5])
|
||||
}
|
||||
}
|
||||
|
||||
// create the assets_vfsdata.go file for embedding in the binary
|
||||
os.Chdir(orig)
|
||||
|
||||
var assets http.FileSystem = http.Dir(assetDir)
|
||||
err = vfsgen.Generate(assets, vfsgen.Options{
|
||||
PackageName: "config",
|
||||
VariableName: "Assets",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
@@ -27,8 +27,7 @@ var (
|
||||
GlobalSettings map[string]interface{}
|
||||
|
||||
// This is the raw parsed json
|
||||
parsedSettings map[string]interface{}
|
||||
settingsParseError bool
|
||||
parsedSettings map[string]interface{}
|
||||
|
||||
// ModifiedSettings is a map of settings which should be written to disk
|
||||
// because they have been modified by the user in this session
|
||||
@@ -43,7 +42,6 @@ func init() {
|
||||
// Options with validators
|
||||
var optionValidators = map[string]optionValidator{
|
||||
"autosave": validateNonNegativeValue,
|
||||
"clipboard": validateClipboard,
|
||||
"tabsize": validatePositiveValue,
|
||||
"scrollmargin": validateNonNegativeValue,
|
||||
"scrollspeed": validateNonNegativeValue,
|
||||
@@ -58,14 +56,12 @@ func ReadSettings() error {
|
||||
if _, e := os.Stat(filename); e == nil {
|
||||
input, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
settingsParseError = true
|
||||
return errors.New("Error reading settings.json file: " + err.Error())
|
||||
}
|
||||
if !strings.HasPrefix(string(input), "null") {
|
||||
// Unmarshal the input into the parsed map
|
||||
err = json5.Unmarshal(input, &parsedSettings)
|
||||
if err != nil {
|
||||
settingsParseError = true
|
||||
return errors.New("Error reading settings.json: " + err.Error())
|
||||
}
|
||||
|
||||
@@ -104,7 +100,7 @@ func InitGlobalSettings() error {
|
||||
for k, v := range parsedSettings {
|
||||
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
||||
if _, ok := GlobalSettings[k]; ok && !verifySetting(k, reflect.TypeOf(v), reflect.TypeOf(GlobalSettings[k])) {
|
||||
err = fmt.Errorf("Global Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v), GlobalSettings[k], reflect.TypeOf(GlobalSettings[k]))
|
||||
err = errors.New(fmt.Sprintf("Global Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v), GlobalSettings[k], reflect.TypeOf(GlobalSettings[k])))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -125,7 +121,7 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
|
||||
if settings["filetype"].(string) == k[3:] {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
|
||||
parseError = fmt.Errorf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1]))
|
||||
parseError = errors.New(fmt.Sprintf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1])))
|
||||
continue
|
||||
}
|
||||
settings[k1] = v1
|
||||
@@ -141,7 +137,7 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
|
||||
if g.MatchString(path) {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
|
||||
parseError = fmt.Errorf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1]))
|
||||
parseError = errors.New(fmt.Sprintf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1])))
|
||||
continue
|
||||
}
|
||||
settings[k1] = v1
|
||||
@@ -155,14 +151,6 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
|
||||
|
||||
// WriteSettings writes the settings to the specified filename as JSON
|
||||
func WriteSettings(filename string) error {
|
||||
if settingsParseError {
|
||||
// Don't write settings if there was a parse error
|
||||
// because this will delete the settings.json if it
|
||||
// is invalid. Instead we should allow the user to fix
|
||||
// it manually.
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
if _, e := os.Stat(ConfigDir); e == nil {
|
||||
defaults := DefaultGlobalSettings()
|
||||
@@ -217,7 +205,7 @@ func OverwriteSettings(filename string) error {
|
||||
// RegisterCommonOptionPlug creates a new option (called pl.name). This is meant to be called by plugins to add options.
|
||||
func RegisterCommonOptionPlug(pl string, name string, defaultvalue interface{}) error {
|
||||
name = pl + "." + name
|
||||
if _, ok := GlobalSettings[name]; !ok {
|
||||
if v, ok := GlobalSettings[name]; !ok {
|
||||
defaultCommonSettings[name] = defaultvalue
|
||||
GlobalSettings[name] = defaultvalue
|
||||
err := WriteSettings(filepath.Join(ConfigDir, "settings.json"))
|
||||
@@ -225,7 +213,7 @@ func RegisterCommonOptionPlug(pl string, name string, defaultvalue interface{})
|
||||
return errors.New("Error writing settings.json file: " + err.Error())
|
||||
}
|
||||
} else {
|
||||
defaultCommonSettings[name] = defaultvalue
|
||||
defaultCommonSettings[name] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -259,7 +247,6 @@ var defaultCommonSettings = map[string]interface{}{
|
||||
"autoindent": true,
|
||||
"autosu": false,
|
||||
"backup": true,
|
||||
"backupdir": "",
|
||||
"basename": false,
|
||||
"colorcolumn": float64(0),
|
||||
"cursorline": true,
|
||||
@@ -269,13 +256,11 @@ var defaultCommonSettings = map[string]interface{}{
|
||||
"fastdirty": false,
|
||||
"fileformat": "unix",
|
||||
"filetype": "unknown",
|
||||
"incsearch": true,
|
||||
"ignorecase": true,
|
||||
"ignorecase": false,
|
||||
"indentchar": " ",
|
||||
"keepautoindent": false,
|
||||
"matchbrace": true,
|
||||
"mkparents": false,
|
||||
"permbackup": false,
|
||||
"readonly": false,
|
||||
"rmtrailingws": false,
|
||||
"ruler": true,
|
||||
@@ -297,7 +282,6 @@ var defaultCommonSettings = map[string]interface{}{
|
||||
"tabsize": float64(4),
|
||||
"tabstospaces": false,
|
||||
"useprimary": true,
|
||||
"wordwrap": false,
|
||||
}
|
||||
|
||||
func GetInfoBarOffset() int {
|
||||
@@ -325,7 +309,6 @@ func DefaultCommonSettings() map[string]interface{} {
|
||||
// default values
|
||||
var DefaultGlobalOnlySettings = map[string]interface{}{
|
||||
"autosave": float64(0),
|
||||
"clipboard": "external",
|
||||
"colorscheme": "default",
|
||||
"divchars": "|-",
|
||||
"divreverse": true,
|
||||
@@ -454,22 +437,6 @@ func validateColorscheme(option string, value interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateClipboard(option string, value interface{}) error {
|
||||
val, ok := value.(string)
|
||||
|
||||
if !ok {
|
||||
return errors.New("Expected string type for clipboard")
|
||||
}
|
||||
|
||||
switch val {
|
||||
case "internal", "external", "terminal":
|
||||
default:
|
||||
return errors.New(option + " must be 'internal', 'external', or 'terminal'")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateLineEnding(option string, value interface{}) error {
|
||||
endingType, ok := value.(string)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
// The BufWindow provides a way of displaying a certain section
|
||||
@@ -23,20 +23,15 @@ type BufWindow struct {
|
||||
|
||||
sline *StatusLine
|
||||
|
||||
bufWidth int
|
||||
bufHeight int
|
||||
gutterOffset int
|
||||
hasMessage bool
|
||||
maxLineNumLength int
|
||||
drawDivider bool
|
||||
gutterOffset int
|
||||
drawStatus bool
|
||||
}
|
||||
|
||||
// NewBufWindow creates a new window at a location in the screen with a width and height
|
||||
func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow {
|
||||
w := new(BufWindow)
|
||||
w.View = new(View)
|
||||
w.X, w.Y, w.Width, w.Height = x, y, width, height
|
||||
w.SetBuffer(buf)
|
||||
w.X, w.Y, w.Width, w.Height, w.Buf = x, y, width, height, buf
|
||||
w.active = true
|
||||
|
||||
w.sline = NewStatusLine(w)
|
||||
@@ -46,23 +41,6 @@ func NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow {
|
||||
|
||||
func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
|
||||
w.Buf = b
|
||||
b.OptionCallback = func(option string, nativeValue interface{}) {
|
||||
if option == "softwrap" {
|
||||
if nativeValue.(bool) {
|
||||
w.StartCol = 0
|
||||
} else {
|
||||
w.StartLine.Row = 0
|
||||
}
|
||||
w.Relocate()
|
||||
|
||||
for _, c := range w.Buf.GetCursors() {
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
}
|
||||
}
|
||||
b.GetVisualX = func(loc buffer.Loc) int {
|
||||
return w.VLocFromLoc(loc).VisualX
|
||||
}
|
||||
}
|
||||
|
||||
func (w *BufWindow) GetView() *View {
|
||||
@@ -75,15 +53,7 @@ func (w *BufWindow) SetView(view *View) {
|
||||
|
||||
func (w *BufWindow) Resize(width, height int) {
|
||||
w.Width, w.Height = width, height
|
||||
w.updateDisplayInfo()
|
||||
|
||||
w.Relocate()
|
||||
|
||||
if w.Buf.Settings["softwrap"].(bool) {
|
||||
for _, c := range w.Buf.GetCursors() {
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *BufWindow) SetActive(b bool) {
|
||||
@@ -94,63 +64,6 @@ func (w *BufWindow) IsActive() bool {
|
||||
return w.active
|
||||
}
|
||||
|
||||
// BufView returns the width, height and x,y location of the actual buffer.
|
||||
// It is not exactly the same as the whole window which also contains gutter,
|
||||
// ruler, scrollbar and statusline.
|
||||
func (w *BufWindow) BufView() View {
|
||||
return View{
|
||||
X: w.X + w.gutterOffset,
|
||||
Y: w.Y,
|
||||
Width: w.bufWidth,
|
||||
Height: w.bufHeight,
|
||||
StartLine: w.StartLine,
|
||||
StartCol: w.StartCol,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *BufWindow) updateDisplayInfo() {
|
||||
b := w.Buf
|
||||
|
||||
w.drawDivider = false
|
||||
if !b.Settings["statusline"].(bool) {
|
||||
_, h := screen.Screen.Size()
|
||||
infoY := h
|
||||
if config.GetGlobalOption("infobar").(bool) {
|
||||
infoY--
|
||||
}
|
||||
if w.Y+w.Height != infoY {
|
||||
w.drawDivider = true
|
||||
}
|
||||
}
|
||||
|
||||
w.bufHeight = w.Height
|
||||
if b.Settings["statusline"].(bool) || w.drawDivider {
|
||||
w.bufHeight--
|
||||
}
|
||||
|
||||
w.hasMessage = len(b.Messages) > 0
|
||||
|
||||
// We need to know the string length of the largest line number
|
||||
// so we can pad appropriately when displaying line numbers
|
||||
w.maxLineNumLength = len(strconv.Itoa(b.LinesNum()))
|
||||
|
||||
w.gutterOffset = 0
|
||||
if w.hasMessage {
|
||||
w.gutterOffset += 2
|
||||
}
|
||||
if b.Settings["diffgutter"].(bool) {
|
||||
w.gutterOffset++
|
||||
}
|
||||
if b.Settings["ruler"].(bool) {
|
||||
w.gutterOffset += w.maxLineNumLength + 1
|
||||
}
|
||||
|
||||
w.bufWidth = w.Width - w.gutterOffset
|
||||
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
|
||||
w.bufWidth--
|
||||
}
|
||||
}
|
||||
|
||||
func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style) {
|
||||
tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
|
||||
width := 0
|
||||
@@ -193,49 +106,63 @@ func (w *BufWindow) Clear() {
|
||||
}
|
||||
}
|
||||
|
||||
// Bottomline returns the line number of the lowest line in the view
|
||||
// You might think that this is obviously just v.StartLine + v.Height
|
||||
// but if softwrap is enabled things get complicated since one buffer
|
||||
// line can take up multiple lines in the view
|
||||
func (w *BufWindow) Bottomline() int {
|
||||
if !w.Buf.Settings["softwrap"].(bool) {
|
||||
h := w.StartLine + w.Height - 1
|
||||
if w.drawStatus {
|
||||
h--
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
l := w.LocFromVisual(buffer.Loc{0, w.Y + w.Height})
|
||||
|
||||
return l.Y
|
||||
}
|
||||
|
||||
// Relocate moves the view window so that the cursor is in view
|
||||
// This is useful if the user has scrolled far away, and then starts typing
|
||||
// Returns true if the window location is moved
|
||||
func (w *BufWindow) Relocate() bool {
|
||||
b := w.Buf
|
||||
height := w.bufHeight
|
||||
// how many buffer lines are in the view
|
||||
height := w.Bottomline() + 1 - w.StartLine
|
||||
h := w.Height
|
||||
if w.drawStatus {
|
||||
h--
|
||||
}
|
||||
ret := false
|
||||
activeC := w.Buf.GetActiveCursor()
|
||||
cy := activeC.Y
|
||||
scrollmargin := int(b.Settings["scrollmargin"].(float64))
|
||||
|
||||
c := w.SLocFromLoc(activeC.Loc)
|
||||
bStart := SLoc{0, 0}
|
||||
bEnd := w.SLocFromLoc(b.End())
|
||||
|
||||
if c.LessThan(w.Scroll(w.StartLine, scrollmargin)) && c.GreaterThan(w.Scroll(bStart, scrollmargin-1)) {
|
||||
w.StartLine = w.Scroll(c, -scrollmargin)
|
||||
if cy < w.StartLine+scrollmargin && cy > scrollmargin-1 {
|
||||
w.StartLine = cy - scrollmargin
|
||||
ret = true
|
||||
} else if c.LessThan(w.StartLine) {
|
||||
w.StartLine = c
|
||||
} else if cy < w.StartLine {
|
||||
w.StartLine = cy
|
||||
ret = true
|
||||
}
|
||||
if c.GreaterThan(w.Scroll(w.StartLine, height-1-scrollmargin)) && c.LessThan(w.Scroll(bEnd, -scrollmargin+1)) {
|
||||
w.StartLine = w.Scroll(c, -height+1+scrollmargin)
|
||||
if cy > w.StartLine+height-1-scrollmargin && cy < b.LinesNum()-scrollmargin {
|
||||
w.StartLine = cy - height + 1 + scrollmargin
|
||||
ret = true
|
||||
} else if c.GreaterThan(w.Scroll(bEnd, -scrollmargin)) && c.GreaterThan(w.Scroll(w.StartLine, height-1)) {
|
||||
w.StartLine = w.Scroll(bEnd, -height+1)
|
||||
} else if cy >= b.LinesNum()-scrollmargin && cy >= height {
|
||||
w.StartLine = b.LinesNum() - height
|
||||
ret = true
|
||||
}
|
||||
|
||||
// horizontal relocation (scrolling)
|
||||
if !b.Settings["softwrap"].(bool) {
|
||||
cx := activeC.GetVisualX()
|
||||
rw := runewidth.RuneWidth(activeC.RuneUnder(activeC.X))
|
||||
if rw == 0 {
|
||||
rw = 1 // tab or newline
|
||||
}
|
||||
|
||||
if cx < w.StartCol {
|
||||
w.StartCol = cx
|
||||
ret = true
|
||||
}
|
||||
if cx+w.gutterOffset+rw > w.StartCol+w.Width {
|
||||
w.StartCol = cx - w.Width + w.gutterOffset + rw
|
||||
if cx+w.gutterOffset+1 > w.StartCol+w.Width {
|
||||
w.StartCol = cx - w.Width + w.gutterOffset + 1
|
||||
ret = true
|
||||
}
|
||||
}
|
||||
@@ -244,18 +171,123 @@ func (w *BufWindow) Relocate() bool {
|
||||
|
||||
// LocFromVisual takes a visual location (x and y position) and returns the
|
||||
// position in the buffer corresponding to the visual location
|
||||
// Computing the buffer location requires essentially drawing the entire screen
|
||||
// to account for complications like softwrap, wide characters, and horizontal scrolling
|
||||
// If the requested position does not correspond to a buffer location it returns
|
||||
// the nearest position
|
||||
func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
|
||||
vx := svloc.X - w.X - w.gutterOffset
|
||||
if vx < 0 {
|
||||
vx = 0
|
||||
b := w.Buf
|
||||
|
||||
hasMessage := len(b.Messages) > 0
|
||||
bufHeight := w.Height
|
||||
if w.drawStatus {
|
||||
bufHeight--
|
||||
}
|
||||
vloc := VLoc{
|
||||
SLoc: w.Scroll(w.StartLine, svloc.Y-w.Y),
|
||||
VisualX: vx + w.StartCol,
|
||||
|
||||
bufWidth := w.Width
|
||||
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
|
||||
bufWidth--
|
||||
}
|
||||
return w.LocFromVLoc(vloc)
|
||||
|
||||
// We need to know the string length of the largest line number
|
||||
// so we can pad appropriately when displaying line numbers
|
||||
maxLineNumLength := len(strconv.Itoa(b.LinesNum()))
|
||||
|
||||
tabsize := int(b.Settings["tabsize"].(float64))
|
||||
softwrap := b.Settings["softwrap"].(bool)
|
||||
|
||||
// this represents the current draw position
|
||||
// within the current window
|
||||
vloc := buffer.Loc{X: 0, Y: 0}
|
||||
|
||||
// this represents the current draw position in the buffer (char positions)
|
||||
bloc := buffer.Loc{X: -1, Y: w.StartLine}
|
||||
|
||||
for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
|
||||
vloc.X = 0
|
||||
if hasMessage {
|
||||
vloc.X += 2
|
||||
}
|
||||
if b.Settings["diffgutter"].(bool) {
|
||||
vloc.X++
|
||||
}
|
||||
if b.Settings["ruler"].(bool) {
|
||||
vloc.X += maxLineNumLength + 1
|
||||
}
|
||||
|
||||
line := b.LineBytes(bloc.Y)
|
||||
line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, w.StartCol, tabsize)
|
||||
bloc.X = bslice
|
||||
|
||||
draw := func() {
|
||||
if nColsBeforeStart <= 0 {
|
||||
vloc.X++
|
||||
}
|
||||
nColsBeforeStart--
|
||||
}
|
||||
|
||||
totalwidth := w.StartCol - nColsBeforeStart
|
||||
|
||||
if svloc.X <= vloc.X+w.X && vloc.Y+w.Y == svloc.Y {
|
||||
return bloc
|
||||
}
|
||||
for len(line) > 0 {
|
||||
if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y {
|
||||
return bloc
|
||||
}
|
||||
|
||||
r, _, size := util.DecodeCharacter(line)
|
||||
draw()
|
||||
width := 0
|
||||
|
||||
switch r {
|
||||
case '\t':
|
||||
ts := tabsize - (totalwidth % tabsize)
|
||||
width = ts
|
||||
default:
|
||||
width = runewidth.RuneWidth(r)
|
||||
}
|
||||
|
||||
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
|
||||
if width > 1 {
|
||||
for i := 1; i < width; i++ {
|
||||
if vloc.X+w.X == svloc.X && vloc.Y+w.Y == svloc.Y {
|
||||
return bloc
|
||||
}
|
||||
draw()
|
||||
}
|
||||
}
|
||||
bloc.X++
|
||||
line = line[size:]
|
||||
|
||||
totalwidth += width
|
||||
|
||||
// If we reach the end of the window then we either stop or we wrap for softwrap
|
||||
if vloc.X >= bufWidth {
|
||||
if !softwrap {
|
||||
break
|
||||
} else {
|
||||
vloc.Y++
|
||||
if vloc.Y >= bufHeight {
|
||||
break
|
||||
}
|
||||
vloc.X = w.gutterOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
if vloc.Y+w.Y == svloc.Y {
|
||||
return bloc
|
||||
}
|
||||
|
||||
if bloc.Y+1 >= b.LinesNum() || vloc.Y+1 >= bufHeight {
|
||||
return bloc
|
||||
}
|
||||
|
||||
bloc.X = w.StartCol
|
||||
bloc.Y++
|
||||
}
|
||||
|
||||
return buffer.Loc{}
|
||||
}
|
||||
|
||||
func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) {
|
||||
@@ -302,7 +334,7 @@ func (w *BufWindow) drawDiffGutter(backgroundStyle tcell.Style, softwrapped bool
|
||||
vloc.X++
|
||||
}
|
||||
|
||||
func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, vloc *buffer.Loc, bloc *buffer.Loc) {
|
||||
func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *buffer.Loc, bloc *buffer.Loc) {
|
||||
cursorLine := w.Buf.GetActiveCursor().Loc.Y
|
||||
var lineInt int
|
||||
if w.Buf.Settings["relativeruler"] == false || cursorLine == bloc.Y {
|
||||
@@ -313,7 +345,7 @@ func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, vloc
|
||||
lineNum := strconv.Itoa(util.Abs(lineInt))
|
||||
|
||||
// Write the spaces before the line number if necessary
|
||||
for i := 0; i < w.maxLineNumLength-len(lineNum); i++ {
|
||||
for i := 0; i < maxLineNumLength-len(lineNum); i++ {
|
||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
|
||||
vloc.X++
|
||||
}
|
||||
@@ -360,7 +392,16 @@ func (w *BufWindow) displayBuffer() {
|
||||
return
|
||||
}
|
||||
|
||||
maxWidth := w.gutterOffset + w.bufWidth
|
||||
hasMessage := len(b.Messages) > 0
|
||||
bufHeight := w.Height
|
||||
if w.drawStatus {
|
||||
bufHeight--
|
||||
}
|
||||
|
||||
bufWidth := w.Width
|
||||
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
|
||||
bufWidth--
|
||||
}
|
||||
|
||||
if b.ModifiedThisFrame {
|
||||
if b.Settings["diffgutter"].(bool) {
|
||||
@@ -421,27 +462,25 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
}
|
||||
|
||||
softwrap := b.Settings["softwrap"].(bool)
|
||||
wordwrap := softwrap && b.Settings["wordwrap"].(bool)
|
||||
// We need to know the string length of the largest line number
|
||||
// so we can pad appropriately when displaying line numbers
|
||||
maxLineNumLength := len(strconv.Itoa(b.LinesNum()))
|
||||
|
||||
softwrap := b.Settings["softwrap"].(bool)
|
||||
tabsize := util.IntOpt(b.Settings["tabsize"])
|
||||
colorcolumn := util.IntOpt(b.Settings["colorcolumn"])
|
||||
|
||||
// this represents the current draw position
|
||||
// within the current window
|
||||
vloc := buffer.Loc{X: 0, Y: 0}
|
||||
if softwrap {
|
||||
// the start line may be partially out of the current window
|
||||
vloc.Y = -w.StartLine.Row
|
||||
}
|
||||
|
||||
// this represents the current draw position in the buffer (char positions)
|
||||
bloc := buffer.Loc{X: -1, Y: w.StartLine.Line}
|
||||
bloc := buffer.Loc{X: -1, Y: w.StartLine}
|
||||
|
||||
cursors := b.GetCursors()
|
||||
|
||||
curStyle := config.DefStyle
|
||||
for ; vloc.Y < w.bufHeight; vloc.Y++ {
|
||||
for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
|
||||
vloc.X = 0
|
||||
|
||||
currentLine := false
|
||||
@@ -457,92 +496,88 @@ func (w *BufWindow) displayBuffer() {
|
||||
s = curNumStyle
|
||||
}
|
||||
|
||||
if vloc.Y >= 0 {
|
||||
if w.hasMessage {
|
||||
w.drawGutter(&vloc, &bloc)
|
||||
}
|
||||
|
||||
if b.Settings["diffgutter"].(bool) {
|
||||
w.drawDiffGutter(s, false, &vloc, &bloc)
|
||||
}
|
||||
|
||||
if b.Settings["ruler"].(bool) {
|
||||
w.drawLineNum(s, false, &vloc, &bloc)
|
||||
}
|
||||
} else {
|
||||
vloc.X = w.gutterOffset
|
||||
if hasMessage {
|
||||
w.drawGutter(&vloc, &bloc)
|
||||
}
|
||||
|
||||
if b.Settings["diffgutter"].(bool) {
|
||||
w.drawDiffGutter(s, false, &vloc, &bloc)
|
||||
}
|
||||
|
||||
if b.Settings["ruler"].(bool) {
|
||||
w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc)
|
||||
}
|
||||
|
||||
w.gutterOffset = vloc.X
|
||||
|
||||
line, nColsBeforeStart, bslice, startStyle := w.getStartInfo(w.StartCol, bloc.Y)
|
||||
if startStyle != nil {
|
||||
curStyle = *startStyle
|
||||
}
|
||||
bloc.X = bslice
|
||||
|
||||
draw := func(r rune, combc []rune, style tcell.Style, highlight bool, showcursor bool) {
|
||||
if nColsBeforeStart <= 0 && vloc.Y >= 0 {
|
||||
if highlight {
|
||||
_, origBg, _ := style.Decompose()
|
||||
_, defBg, _ := config.DefStyle.Decompose()
|
||||
draw := func(r rune, combc []rune, style tcell.Style, showcursor bool) {
|
||||
if nColsBeforeStart <= 0 {
|
||||
_, origBg, _ := style.Decompose()
|
||||
_, defBg, _ := config.DefStyle.Decompose()
|
||||
|
||||
// syntax highlighting with non-default background takes precedence
|
||||
// over cursor-line and color-column
|
||||
dontOverrideBackground := origBg != defBg
|
||||
// syntax highlighting with non-default background takes precedence
|
||||
// over cursor-line and color-column
|
||||
dontOverrideBackground := origBg != defBg
|
||||
|
||||
for _, c := range cursors {
|
||||
if c.HasSelection() &&
|
||||
(bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
|
||||
bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {
|
||||
// The current character is selected
|
||||
style = config.DefStyle.Reverse(true)
|
||||
for _, c := range cursors {
|
||||
if c.HasSelection() &&
|
||||
(bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
|
||||
bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {
|
||||
// The current character is selected
|
||||
style = config.DefStyle.Reverse(true)
|
||||
|
||||
if s, ok := config.Colorscheme["selection"]; ok {
|
||||
style = s
|
||||
}
|
||||
}
|
||||
|
||||
if b.Settings["cursorline"].(bool) && w.active && !dontOverrideBackground &&
|
||||
!c.HasSelection() && c.Y == bloc.Y {
|
||||
if s, ok := config.Colorscheme["cursor-line"]; ok {
|
||||
fg, _, _ := s.Decompose()
|
||||
style = style.Background(fg)
|
||||
}
|
||||
if s, ok := config.Colorscheme["selection"]; ok {
|
||||
style = s
|
||||
}
|
||||
}
|
||||
|
||||
for _, m := range b.Messages {
|
||||
if bloc.GreaterEqual(m.Start) && bloc.LessThan(m.End) ||
|
||||
bloc.LessThan(m.End) && bloc.GreaterEqual(m.Start) {
|
||||
style = style.Underline(true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if r == '\t' {
|
||||
indentrunes := []rune(b.Settings["indentchar"].(string))
|
||||
// if empty indentchar settings, use space
|
||||
if len(indentrunes) == 0 {
|
||||
indentrunes = []rune{' '}
|
||||
}
|
||||
|
||||
r = indentrunes[0]
|
||||
if s, ok := config.Colorscheme["indent-char"]; ok && r != ' ' {
|
||||
fg, _, _ := s.Decompose()
|
||||
style = style.Foreground(fg)
|
||||
}
|
||||
}
|
||||
|
||||
if s, ok := config.Colorscheme["color-column"]; ok {
|
||||
if colorcolumn != 0 && vloc.X-w.gutterOffset+w.StartCol == colorcolumn && !dontOverrideBackground {
|
||||
if b.Settings["cursorline"].(bool) && w.active && !dontOverrideBackground &&
|
||||
!c.HasSelection() && c.Y == bloc.Y {
|
||||
if s, ok := config.Colorscheme["cursor-line"]; ok {
|
||||
fg, _, _ := s.Decompose()
|
||||
style = style.Background(fg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, mb := range matchingBraces {
|
||||
if mb.X == bloc.X && mb.Y == bloc.Y {
|
||||
style = style.Underline(true)
|
||||
}
|
||||
for _, m := range b.Messages {
|
||||
if bloc.GreaterEqual(m.Start) && bloc.LessThan(m.End) ||
|
||||
bloc.LessThan(m.End) && bloc.GreaterEqual(m.Start) {
|
||||
style = style.Underline(true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if r == '\t' {
|
||||
indentrunes := []rune(b.Settings["indentchar"].(string))
|
||||
// if empty indentchar settings, use space
|
||||
if indentrunes == nil || len(indentrunes) == 0 {
|
||||
indentrunes = []rune{' '}
|
||||
}
|
||||
|
||||
r = indentrunes[0]
|
||||
if s, ok := config.Colorscheme["indent-char"]; ok && r != ' ' {
|
||||
fg, _, _ := s.Decompose()
|
||||
style = style.Foreground(fg)
|
||||
}
|
||||
}
|
||||
|
||||
if s, ok := config.Colorscheme["color-column"]; ok {
|
||||
if colorcolumn != 0 && vloc.X-w.gutterOffset+w.StartCol == colorcolumn && !dontOverrideBackground {
|
||||
fg, _, _ := s.Decompose()
|
||||
style = style.Background(fg)
|
||||
}
|
||||
}
|
||||
|
||||
for _, mb := range matchingBraces {
|
||||
if mb.X == bloc.X && mb.Y == bloc.Y {
|
||||
style = style.Underline(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,122 +590,63 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if nColsBeforeStart <= 0 {
|
||||
vloc.X++
|
||||
}
|
||||
nColsBeforeStart--
|
||||
}
|
||||
|
||||
wrap := func() {
|
||||
vloc.X = 0
|
||||
if w.hasMessage {
|
||||
w.drawGutter(&vloc, &bloc)
|
||||
}
|
||||
if b.Settings["diffgutter"].(bool) {
|
||||
w.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)
|
||||
}
|
||||
|
||||
// This will draw an empty line number because the current line is wrapped
|
||||
if b.Settings["ruler"].(bool) {
|
||||
w.drawLineNum(lineNumStyle, true, &vloc, &bloc)
|
||||
}
|
||||
}
|
||||
|
||||
type glyph struct {
|
||||
r rune
|
||||
combc []rune
|
||||
style tcell.Style
|
||||
width int
|
||||
}
|
||||
|
||||
var word []glyph
|
||||
if wordwrap {
|
||||
word = make([]glyph, 0, w.bufWidth)
|
||||
} else {
|
||||
word = make([]glyph, 0, 1)
|
||||
}
|
||||
wordwidth := 0
|
||||
|
||||
totalwidth := w.StartCol - nColsBeforeStart
|
||||
for len(line) > 0 {
|
||||
r, combc, size := util.DecodeCharacter(line)
|
||||
line = line[size:]
|
||||
|
||||
loc := buffer.Loc{X: bloc.X + len(word), Y: bloc.Y}
|
||||
curStyle, _ = w.getStyle(curStyle, loc)
|
||||
curStyle, _ = w.getStyle(curStyle, bloc)
|
||||
|
||||
draw(r, combc, curStyle, true)
|
||||
|
||||
width := 0
|
||||
|
||||
char := ' '
|
||||
switch r {
|
||||
case '\t':
|
||||
ts := tabsize - (totalwidth % tabsize)
|
||||
width = util.Min(ts, maxWidth-vloc.X)
|
||||
totalwidth += ts
|
||||
width = ts
|
||||
default:
|
||||
width = runewidth.RuneWidth(r)
|
||||
totalwidth += width
|
||||
char = '@'
|
||||
}
|
||||
|
||||
word = append(word, glyph{r, combc, curStyle, width})
|
||||
wordwidth += width
|
||||
|
||||
// Collect a complete word to know its width.
|
||||
// If wordwrap is off, every single character is a complete "word".
|
||||
if wordwrap {
|
||||
if !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {
|
||||
continue
|
||||
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
|
||||
if width > 1 {
|
||||
for i := 1; i < width; i++ {
|
||||
draw(char, nil, curStyle, false)
|
||||
}
|
||||
}
|
||||
bloc.X++
|
||||
line = line[size:]
|
||||
|
||||
// If a word (or just a wide rune) does not fit in the window
|
||||
if vloc.X+wordwidth > maxWidth && vloc.X > w.gutterOffset {
|
||||
for vloc.X < maxWidth {
|
||||
draw(' ', nil, config.DefStyle, false, false)
|
||||
}
|
||||
|
||||
// We either stop or we wrap to draw the word in the next line
|
||||
if !softwrap {
|
||||
break
|
||||
} else {
|
||||
vloc.Y++
|
||||
if vloc.Y >= w.bufHeight {
|
||||
break
|
||||
}
|
||||
wrap()
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range word {
|
||||
draw(r.r, r.combc, r.style, true, true)
|
||||
|
||||
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
|
||||
if r.width > 1 {
|
||||
char := ' '
|
||||
if r.r != '\t' {
|
||||
char = '@'
|
||||
}
|
||||
|
||||
for i := 1; i < r.width; i++ {
|
||||
draw(char, nil, r.style, true, false)
|
||||
}
|
||||
}
|
||||
bloc.X++
|
||||
}
|
||||
|
||||
word = word[:0]
|
||||
wordwidth = 0
|
||||
totalwidth += width
|
||||
|
||||
// If we reach the end of the window then we either stop or we wrap for softwrap
|
||||
if vloc.X >= maxWidth {
|
||||
if vloc.X >= bufWidth {
|
||||
if !softwrap {
|
||||
break
|
||||
} else {
|
||||
vloc.Y++
|
||||
if vloc.Y >= w.bufHeight {
|
||||
if vloc.Y >= bufHeight {
|
||||
break
|
||||
}
|
||||
wrap()
|
||||
vloc.X = 0
|
||||
if hasMessage {
|
||||
w.drawGutter(&vloc, &bloc)
|
||||
}
|
||||
if b.Settings["diffgutter"].(bool) {
|
||||
w.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)
|
||||
}
|
||||
|
||||
// This will draw an empty line number because the current line is wrapped
|
||||
if b.Settings["ruler"].(bool) {
|
||||
w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -685,7 +661,7 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := vloc.X; i < maxWidth; i++ {
|
||||
for i := vloc.X; i < bufWidth; i++ {
|
||||
curStyle := style
|
||||
if s, ok := config.Colorscheme["color-column"]; ok {
|
||||
if colorcolumn != 0 && i-w.gutterOffset+w.StartCol == colorcolumn {
|
||||
@@ -696,9 +672,9 @@ func (w *BufWindow) displayBuffer() {
|
||||
screen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, curStyle)
|
||||
}
|
||||
|
||||
if vloc.X != maxWidth {
|
||||
if vloc.X != bufWidth {
|
||||
// Display newline within a selection
|
||||
draw(' ', nil, config.DefStyle, true, true)
|
||||
draw(' ', nil, config.DefStyle, true)
|
||||
}
|
||||
|
||||
bloc.X = w.StartCol
|
||||
@@ -710,9 +686,18 @@ func (w *BufWindow) displayBuffer() {
|
||||
}
|
||||
|
||||
func (w *BufWindow) displayStatusLine() {
|
||||
_, h := screen.Screen.Size()
|
||||
infoY := h
|
||||
if config.GetGlobalOption("infobar").(bool) {
|
||||
infoY--
|
||||
}
|
||||
|
||||
if w.Buf.Settings["statusline"].(bool) {
|
||||
w.drawStatus = true
|
||||
w.sline.Display()
|
||||
} else if w.drawDivider {
|
||||
} else if w.Y+w.Height != infoY {
|
||||
w.drawStatus = true
|
||||
|
||||
divchars := config.GetGlobalOption("divchars").(string)
|
||||
if util.CharacterCountInString(divchars) != 2 {
|
||||
divchars = "|-"
|
||||
@@ -734,33 +719,31 @@ func (w *BufWindow) displayStatusLine() {
|
||||
for x := w.X; x < w.X+w.Width; x++ {
|
||||
screen.SetContent(x, w.Y+w.Height-1, divchar, combc, dividerStyle)
|
||||
}
|
||||
} else {
|
||||
w.drawStatus = false
|
||||
}
|
||||
}
|
||||
|
||||
func (w *BufWindow) displayScrollBar() {
|
||||
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
|
||||
scrollX := w.X + w.Width - 1
|
||||
bufHeight := w.Height
|
||||
if w.drawStatus {
|
||||
bufHeight--
|
||||
}
|
||||
barsize := int(float64(w.Height) / float64(w.Buf.LinesNum()) * float64(w.Height))
|
||||
if barsize < 1 {
|
||||
barsize = 1
|
||||
}
|
||||
barstart := w.Y + int(float64(w.StartLine.Line)/float64(w.Buf.LinesNum())*float64(w.Height))
|
||||
|
||||
scrollBarStyle := config.DefStyle.Reverse(true)
|
||||
if style, ok := config.Colorscheme["scrollbar"]; ok {
|
||||
scrollBarStyle = style
|
||||
}
|
||||
|
||||
for y := barstart; y < util.Min(barstart+barsize, w.Y+w.bufHeight); y++ {
|
||||
screen.SetContent(scrollX, y, '|', nil, scrollBarStyle)
|
||||
barstart := w.Y + int(float64(w.StartLine)/float64(w.Buf.LinesNum())*float64(w.Height))
|
||||
for y := barstart; y < util.Min(barstart+barsize, w.Y+bufHeight); y++ {
|
||||
screen.SetContent(scrollX, y, '|', nil, config.DefStyle.Reverse(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Display displays the buffer and the statusline
|
||||
func (w *BufWindow) Display() {
|
||||
w.updateDisplayInfo()
|
||||
|
||||
w.displayStatusLine()
|
||||
w.displayScrollBar()
|
||||
w.displayBuffer()
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/zyedidia/micro/v2/internal/info"
|
||||
"github.com/zyedidia/micro/v2/internal/screen"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
type InfoWindow struct {
|
||||
@@ -72,23 +72,6 @@ func (i *InfoWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {
|
||||
return buffer.Loc{c.GetCharPosInLine(l, vloc.X-n), 0}
|
||||
}
|
||||
|
||||
func (i *InfoWindow) BufView() View {
|
||||
return View{
|
||||
X: 0,
|
||||
Y: i.Y,
|
||||
Width: i.Width,
|
||||
Height: 1,
|
||||
StartLine: SLoc{0, 0},
|
||||
StartCol: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *InfoWindow) Scroll(s SLoc, n int) SLoc { return s }
|
||||
func (i *InfoWindow) Diff(s1, s2 SLoc) int { return 0 }
|
||||
func (i *InfoWindow) SLocFromLoc(loc buffer.Loc) SLoc { return SLoc{0, 0} }
|
||||
func (i *InfoWindow) VLocFromLoc(loc buffer.Loc) VLoc { return VLoc{SLoc{0, 0}, loc.X} }
|
||||
func (i *InfoWindow) LocFromVLoc(vloc VLoc) buffer.Loc { return buffer.Loc{vloc.VisualX, 0} }
|
||||
|
||||
func (i *InfoWindow) Clear() {
|
||||
for x := 0; x < i.Width; x++ {
|
||||
screen.SetContent(x, i.Y, ' ', nil, i.defStyle())
|
||||
|
||||
@@ -1,326 +0,0 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/zyedidia/micro/v2/internal/buffer"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
)
|
||||
|
||||
// SLoc represents a vertical scrolling location, i.e. a location of a visual line
|
||||
// in the buffer. When softwrap is enabled, a buffer line may be displayed as
|
||||
// multiple visual lines (rows). So SLoc stores a number of a line in the buffer
|
||||
// and a number of a row within this line.
|
||||
type SLoc struct {
|
||||
Line, Row int
|
||||
}
|
||||
|
||||
// LessThan returns true if s is less b
|
||||
func (s SLoc) LessThan(b SLoc) bool {
|
||||
if s.Line < b.Line {
|
||||
return true
|
||||
}
|
||||
return s.Line == b.Line && s.Row < b.Row
|
||||
}
|
||||
|
||||
// GreaterThan returns true if s is bigger than b
|
||||
func (s SLoc) GreaterThan(b SLoc) bool {
|
||||
if s.Line > b.Line {
|
||||
return true
|
||||
}
|
||||
return s.Line == b.Line && s.Row > b.Row
|
||||
}
|
||||
|
||||
// VLoc represents a location in the buffer as a visual location in the
|
||||
// linewrapped buffer.
|
||||
type VLoc struct {
|
||||
SLoc
|
||||
VisualX int
|
||||
}
|
||||
|
||||
type SoftWrap interface {
|
||||
Scroll(s SLoc, n int) SLoc
|
||||
Diff(s1, s2 SLoc) int
|
||||
SLocFromLoc(loc buffer.Loc) SLoc
|
||||
VLocFromLoc(loc buffer.Loc) VLoc
|
||||
LocFromVLoc(vloc VLoc) buffer.Loc
|
||||
}
|
||||
|
||||
func (w *BufWindow) getVLocFromLoc(loc buffer.Loc) VLoc {
|
||||
vloc := VLoc{SLoc: SLoc{loc.Y, 0}, VisualX: 0}
|
||||
|
||||
if loc.X <= 0 {
|
||||
return vloc
|
||||
}
|
||||
|
||||
if w.bufWidth <= 0 {
|
||||
return vloc
|
||||
}
|
||||
|
||||
wordwrap := w.Buf.Settings["wordwrap"].(bool)
|
||||
tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
|
||||
|
||||
line := w.Buf.LineBytes(loc.Y)
|
||||
x := 0
|
||||
totalwidth := 0
|
||||
|
||||
wordwidth := 0
|
||||
wordoffset := 0
|
||||
|
||||
for len(line) > 0 {
|
||||
r, _, size := util.DecodeCharacter(line)
|
||||
line = line[size:]
|
||||
|
||||
width := 0
|
||||
switch r {
|
||||
case '\t':
|
||||
ts := tabsize - (totalwidth % tabsize)
|
||||
width = util.Min(ts, w.bufWidth-vloc.VisualX)
|
||||
totalwidth += ts
|
||||
default:
|
||||
width = runewidth.RuneWidth(r)
|
||||
totalwidth += width
|
||||
}
|
||||
|
||||
wordwidth += width
|
||||
|
||||
// Collect a complete word to know its width.
|
||||
// If wordwrap is off, every single character is a complete "word".
|
||||
if wordwrap {
|
||||
if !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {
|
||||
if x < loc.X {
|
||||
wordoffset += width
|
||||
x++
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// If a word (or just a wide rune) does not fit in the window
|
||||
if vloc.VisualX+wordwidth > w.bufWidth && vloc.VisualX > 0 {
|
||||
vloc.Row++
|
||||
vloc.VisualX = 0
|
||||
}
|
||||
|
||||
if x == loc.X {
|
||||
vloc.VisualX += wordoffset
|
||||
return vloc
|
||||
}
|
||||
x++
|
||||
|
||||
vloc.VisualX += wordwidth
|
||||
|
||||
wordwidth = 0
|
||||
wordoffset = 0
|
||||
|
||||
if vloc.VisualX >= w.bufWidth {
|
||||
vloc.Row++
|
||||
vloc.VisualX = 0
|
||||
}
|
||||
}
|
||||
return vloc
|
||||
}
|
||||
|
||||
func (w *BufWindow) getLocFromVLoc(svloc VLoc) buffer.Loc {
|
||||
loc := buffer.Loc{X: 0, Y: svloc.Line}
|
||||
|
||||
if w.bufWidth <= 0 {
|
||||
return loc
|
||||
}
|
||||
|
||||
wordwrap := w.Buf.Settings["wordwrap"].(bool)
|
||||
tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
|
||||
|
||||
line := w.Buf.LineBytes(svloc.Line)
|
||||
vloc := VLoc{SLoc: SLoc{svloc.Line, 0}, VisualX: 0}
|
||||
|
||||
totalwidth := 0
|
||||
|
||||
var widths []int
|
||||
if wordwrap {
|
||||
widths = make([]int, 0, w.bufWidth)
|
||||
} else {
|
||||
widths = make([]int, 0, 1)
|
||||
}
|
||||
wordwidth := 0
|
||||
|
||||
for len(line) > 0 {
|
||||
r, _, size := util.DecodeCharacter(line)
|
||||
line = line[size:]
|
||||
|
||||
width := 0
|
||||
switch r {
|
||||
case '\t':
|
||||
ts := tabsize - (totalwidth % tabsize)
|
||||
width = util.Min(ts, w.bufWidth-vloc.VisualX)
|
||||
totalwidth += ts
|
||||
default:
|
||||
width = runewidth.RuneWidth(r)
|
||||
totalwidth += width
|
||||
}
|
||||
|
||||
widths = append(widths, width)
|
||||
wordwidth += width
|
||||
|
||||
// Collect a complete word to know its width.
|
||||
// If wordwrap is off, every single character is a complete "word".
|
||||
if wordwrap {
|
||||
if !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// If a word (or just a wide rune) does not fit in the window
|
||||
if vloc.VisualX+wordwidth > w.bufWidth && vloc.VisualX > 0 {
|
||||
if vloc.Row == svloc.Row {
|
||||
if wordwrap {
|
||||
// it's a word, not a wide rune
|
||||
loc.X--
|
||||
}
|
||||
return loc
|
||||
}
|
||||
vloc.Row++
|
||||
vloc.VisualX = 0
|
||||
}
|
||||
|
||||
for i := range widths {
|
||||
vloc.VisualX += widths[i]
|
||||
if vloc.Row == svloc.Row && vloc.VisualX > svloc.VisualX {
|
||||
return loc
|
||||
}
|
||||
loc.X++
|
||||
}
|
||||
|
||||
widths = widths[:0]
|
||||
wordwidth = 0
|
||||
|
||||
if vloc.VisualX >= w.bufWidth {
|
||||
vloc.Row++
|
||||
vloc.VisualX = 0
|
||||
}
|
||||
}
|
||||
return loc
|
||||
}
|
||||
|
||||
func (w *BufWindow) getRowCount(line int) int {
|
||||
eol := buffer.Loc{X: util.CharacterCount(w.Buf.LineBytes(line)), Y: line}
|
||||
return w.getVLocFromLoc(eol).Row + 1
|
||||
}
|
||||
|
||||
func (w *BufWindow) scrollUp(s SLoc, n int) SLoc {
|
||||
for n > 0 {
|
||||
if n <= s.Row {
|
||||
s.Row -= n
|
||||
n = 0
|
||||
} else if s.Line > 0 {
|
||||
s.Line--
|
||||
n -= s.Row + 1
|
||||
s.Row = w.getRowCount(s.Line) - 1
|
||||
} else {
|
||||
s.Row = 0
|
||||
break
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (w *BufWindow) scrollDown(s SLoc, n int) SLoc {
|
||||
for n > 0 {
|
||||
rc := w.getRowCount(s.Line)
|
||||
if n < rc-s.Row {
|
||||
s.Row += n
|
||||
n = 0
|
||||
} else if s.Line < w.Buf.LinesNum()-1 {
|
||||
s.Line++
|
||||
n -= rc - s.Row
|
||||
s.Row = 0
|
||||
} else {
|
||||
s.Row = rc - 1
|
||||
break
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (w *BufWindow) scroll(s SLoc, n int) SLoc {
|
||||
if n < 0 {
|
||||
return w.scrollUp(s, -n)
|
||||
}
|
||||
return w.scrollDown(s, n)
|
||||
}
|
||||
|
||||
func (w *BufWindow) diff(s1, s2 SLoc) int {
|
||||
n := 0
|
||||
for s1.LessThan(s2) {
|
||||
if s1.Line < s2.Line {
|
||||
n += w.getRowCount(s1.Line) - s1.Row
|
||||
s1.Line++
|
||||
s1.Row = 0
|
||||
} else {
|
||||
n += s2.Row - s1.Row
|
||||
s1.Row = s2.Row
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Scroll returns the location which is n visual lines below the location s
|
||||
// i.e. the result of scrolling n lines down. n can be negative,
|
||||
// which means scrolling up. The returned location is guaranteed to be
|
||||
// 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
|
||||
}
|
||||
return s
|
||||
}
|
||||
return w.scroll(s, n)
|
||||
}
|
||||
|
||||
// Diff returns the difference (the vertical distance) between two SLocs.
|
||||
func (w *BufWindow) Diff(s1, s2 SLoc) int {
|
||||
if !w.Buf.Settings["softwrap"].(bool) {
|
||||
return s2.Line - s1.Line
|
||||
}
|
||||
if s1.GreaterThan(s2) {
|
||||
return -w.diff(s2, s1)
|
||||
}
|
||||
return w.diff(s1, s2)
|
||||
}
|
||||
|
||||
// SLocFromLoc takes a position in the buffer and returns the location
|
||||
// of the visual line containing this position.
|
||||
func (w *BufWindow) SLocFromLoc(loc buffer.Loc) SLoc {
|
||||
if !w.Buf.Settings["softwrap"].(bool) {
|
||||
return SLoc{loc.Y, 0}
|
||||
}
|
||||
return w.getVLocFromLoc(loc).SLoc
|
||||
}
|
||||
|
||||
// VLocFromLoc takes a position in the buffer and returns the corresponding
|
||||
// visual location in the linewrapped buffer.
|
||||
func (w *BufWindow) VLocFromLoc(loc buffer.Loc) VLoc {
|
||||
if !w.Buf.Settings["softwrap"].(bool) {
|
||||
tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
|
||||
|
||||
visualx := util.StringWidth(w.Buf.LineBytes(loc.Y), loc.X, tabsize)
|
||||
return VLoc{SLoc{loc.Y, 0}, visualx}
|
||||
}
|
||||
return w.getVLocFromLoc(loc)
|
||||
}
|
||||
|
||||
// LocFromVLoc takes a visual location in the linewrapped buffer and returns
|
||||
// the position in the buffer corresponding to this visual location.
|
||||
func (w *BufWindow) LocFromVLoc(vloc VLoc) buffer.Loc {
|
||||
if !w.Buf.Settings["softwrap"].(bool) {
|
||||
tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
|
||||
|
||||
x := util.GetCharPosInLine(w.Buf.LineBytes(vloc.Line), vloc.VisualX, tabsize)
|
||||
return buffer.Loc{x, vloc.Line}
|
||||
}
|
||||
return w.getLocFromVLoc(vloc)
|
||||
}
|
||||
@@ -98,8 +98,6 @@ func (s *StatusLine) Display() {
|
||||
// We'll draw the line at the lowest line in the window
|
||||
y := s.win.Height + s.win.Y - 1
|
||||
|
||||
winX := s.win.X
|
||||
|
||||
b := s.win.Buf
|
||||
// autocomplete suggestions (for the buffer, not for the infowindow)
|
||||
if b.HasSuggestions && len(b.Suggestions) > 1 {
|
||||
@@ -107,6 +105,10 @@ func (s *StatusLine) Display() {
|
||||
if style, ok := config.Colorscheme["statusline"]; ok {
|
||||
statusLineStyle = style
|
||||
}
|
||||
keymenuOffset := 0
|
||||
if config.GetGlobalOption("keymenu").(bool) {
|
||||
keymenuOffset = len(keydisplay)
|
||||
}
|
||||
x := 0
|
||||
for j, sug := range b.Suggestions {
|
||||
style := statusLineStyle
|
||||
@@ -114,13 +116,13 @@ func (s *StatusLine) Display() {
|
||||
style = style.Reverse(true)
|
||||
}
|
||||
for _, r := range sug {
|
||||
screen.SetContent(winX+x, y, r, nil, style)
|
||||
screen.SetContent(x, y-keymenuOffset, r, nil, style)
|
||||
x++
|
||||
if x >= s.win.Width {
|
||||
return
|
||||
}
|
||||
}
|
||||
screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
|
||||
screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
|
||||
x++
|
||||
if x >= s.win.Width {
|
||||
return
|
||||
@@ -128,7 +130,7 @@ func (s *StatusLine) Display() {
|
||||
}
|
||||
|
||||
for x < s.win.Width {
|
||||
screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
|
||||
screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
|
||||
x++
|
||||
}
|
||||
return
|
||||
@@ -141,7 +143,7 @@ func (s *StatusLine) Display() {
|
||||
return []byte(fmt.Sprint(s.FindOpt(string(option))))
|
||||
} else if bytes.HasPrefix(name, []byte("bind")) {
|
||||
binding := string(name[5:])
|
||||
for k, v := range config.Bindings["buffer"] {
|
||||
for k, v := range config.Bindings {
|
||||
if v == binding {
|
||||
return []byte(k)
|
||||
}
|
||||
@@ -168,6 +170,7 @@ func (s *StatusLine) Display() {
|
||||
leftLen := util.StringWidth(leftText, util.CharacterCount(leftText), 1)
|
||||
rightLen := util.StringWidth(rightText, util.CharacterCount(rightText), 1)
|
||||
|
||||
winX := s.win.X
|
||||
for x := 0; x < s.win.Width; x++ {
|
||||
if x < leftLen {
|
||||
r, combc, size := util.DecodeCharacter(leftText)
|
||||
|
||||
@@ -98,16 +98,8 @@ func (w *TabWindow) Display() {
|
||||
if style, ok := config.Colorscheme["tabbar"]; ok {
|
||||
tabBarStyle = style
|
||||
}
|
||||
tabBarActiveStyle := tabBarStyle
|
||||
if style, ok := config.Colorscheme["tabbar.active"]; ok {
|
||||
tabBarActiveStyle = style
|
||||
}
|
||||
|
||||
draw := func(r rune, n int, active bool) {
|
||||
style := tabBarStyle
|
||||
if active {
|
||||
style = tabBarActiveStyle
|
||||
}
|
||||
draw := func(r rune, n int) {
|
||||
for i := 0; i < n; i++ {
|
||||
rw := runewidth.RuneWidth(r)
|
||||
for j := 0; j < rw; j++ {
|
||||
@@ -122,7 +114,7 @@ func (w *TabWindow) Display() {
|
||||
} else if x == 0 && w.hscroll > 0 {
|
||||
screen.SetContent(0, w.Y, '<', nil, tabBarStyle)
|
||||
} else if x >= 0 && x < w.Width {
|
||||
screen.SetContent(x, w.Y, c, nil, style)
|
||||
screen.SetContent(x, w.Y, c, nil, tabBarStyle)
|
||||
}
|
||||
x++
|
||||
}
|
||||
@@ -131,21 +123,21 @@ func (w *TabWindow) Display() {
|
||||
|
||||
for i, n := range w.Names {
|
||||
if i == w.active {
|
||||
draw('[', 1, true)
|
||||
draw('[', 1)
|
||||
} else {
|
||||
draw(' ', 1, false)
|
||||
draw(' ', 1)
|
||||
}
|
||||
for _, c := range n {
|
||||
draw(c, 1, i == w.active)
|
||||
draw(c, 1)
|
||||
}
|
||||
if i == len(w.Names)-1 {
|
||||
done = true
|
||||
}
|
||||
if i == w.active {
|
||||
draw(']', 1, true)
|
||||
draw(' ', 2, true)
|
||||
draw(']', 1)
|
||||
draw(' ', 2)
|
||||
} else {
|
||||
draw(' ', 3, false)
|
||||
draw(' ', 3)
|
||||
}
|
||||
if x >= w.Width {
|
||||
break
|
||||
@@ -153,6 +145,6 @@ func (w *TabWindow) Display() {
|
||||
}
|
||||
|
||||
if x < w.Width {
|
||||
draw(' ', w.Width-x, false)
|
||||
draw(' ', w.Width-x)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ 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"
|
||||
"github.com/zyedidia/tcell"
|
||||
"github.com/zyedidia/terminal"
|
||||
)
|
||||
|
||||
@@ -81,7 +81,7 @@ func (w *TermWindow) Display() {
|
||||
if b == terminal.DefaultBG {
|
||||
bg = int(tcell.ColorDefault)
|
||||
}
|
||||
st := tcell.StyleDefault.Foreground(config.GetColor256(fg)).Background(config.GetColor256(bg))
|
||||
st := tcell.StyleDefault.Foreground(config.GetColor256(int(fg))).Background(config.GetColor256(int(bg)))
|
||||
|
||||
if l.LessThan(w.Selection[1]) && l.GreaterEqual(w.Selection[0]) || l.LessThan(w.Selection[0]) && l.GreaterEqual(w.Selection[1]) {
|
||||
st = st.Reverse(true)
|
||||
|
||||
@@ -38,14 +38,15 @@ func (w *UIWindow) drawNode(n *views.Node) {
|
||||
}
|
||||
|
||||
for i, c := range cs {
|
||||
if c.Kind == views.STVert {
|
||||
if c.IsLeaf() && c.Kind == views.STVert {
|
||||
if i != len(cs)-1 {
|
||||
for h := 0; h < c.H; h++ {
|
||||
screen.SetContent(c.X+c.W, c.Y+h, divchar, combc, dividerStyle)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
w.drawNode(c)
|
||||
}
|
||||
w.drawNode(c)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,32 +54,32 @@ func (w *UIWindow) Display() {
|
||||
w.drawNode(w.root)
|
||||
}
|
||||
|
||||
func (w *UIWindow) GetMouseSplitNode(vloc buffer.Loc) *views.Node {
|
||||
var mouseLoc func(*views.Node) *views.Node
|
||||
mouseLoc = func(n *views.Node) *views.Node {
|
||||
func (w *UIWindow) GetMouseSplitID(vloc buffer.Loc) uint64 {
|
||||
var mouseLoc func(*views.Node) uint64
|
||||
mouseLoc = func(n *views.Node) uint64 {
|
||||
cs := n.Children()
|
||||
for i, c := range cs {
|
||||
if c.Kind == views.STVert {
|
||||
if i != len(cs)-1 {
|
||||
if vloc.X == c.X+c.W && vloc.Y >= c.Y && vloc.Y < c.Y+c.H {
|
||||
return c
|
||||
return c.ID()
|
||||
}
|
||||
}
|
||||
} else if c.Kind == views.STHoriz {
|
||||
if i != len(cs)-1 {
|
||||
if vloc.Y == c.Y+c.H-1 && vloc.X >= c.X && vloc.X < c.X+c.W {
|
||||
return c
|
||||
return c.ID()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, c := range cs {
|
||||
m := mouseLoc(c)
|
||||
if m != nil {
|
||||
if m != 0 {
|
||||
return m
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return 0
|
||||
}
|
||||
return mouseLoc(w.root)
|
||||
}
|
||||
|
||||
@@ -8,13 +8,10 @@ type View struct {
|
||||
X, Y int // X,Y location of the view
|
||||
Width, Height int // Width and height of the view
|
||||
|
||||
// Start line of the view (for vertical scroll)
|
||||
StartLine SLoc
|
||||
|
||||
// Start column of the view (for horizontal scroll)
|
||||
// Start line and start column of the view (vertical/horizontal scroll)
|
||||
// note that since the starting column of every line is different if the view
|
||||
// is scrolled, StartCol is a visual index (will be the same for every line)
|
||||
StartCol int
|
||||
StartLine, StartCol int
|
||||
}
|
||||
|
||||
type Window interface {
|
||||
@@ -31,7 +28,5 @@ type Window interface {
|
||||
|
||||
type BWindow interface {
|
||||
Window
|
||||
SoftWrap
|
||||
SetBuffer(b *buffer.Buffer)
|
||||
BufView() View
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ import (
|
||||
func (i *InfoBuf) LoadHistory() {
|
||||
if config.GetGlobalOption("savehistory").(bool) {
|
||||
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", "history"))
|
||||
defer file.Close()
|
||||
var decodedMap map[string][]string
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
decoder := gob.NewDecoder(file)
|
||||
err = decoder.Decode(&decodedMap)
|
||||
|
||||
@@ -48,8 +48,8 @@ func (i *InfoBuf) SaveHistory() {
|
||||
}
|
||||
|
||||
file, err := os.Create(filepath.Join(config.ConfigDir, "buffers", "history"))
|
||||
defer file.Close()
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
encoder := gob.NewEncoder(file)
|
||||
|
||||
err = encoder.Encode(i.History)
|
||||
@@ -61,30 +61,6 @@ func (i *InfoBuf) SaveHistory() {
|
||||
}
|
||||
}
|
||||
|
||||
// AddToHistory adds a new item to the history for the prompt type `ptype`.
|
||||
// This function is not used by micro itself. It is useful for plugins
|
||||
// which add their own items to the history, bypassing the infobar command line.
|
||||
func (i *InfoBuf) AddToHistory(ptype string, item string) {
|
||||
if i.HasPrompt && i.PromptType == ptype {
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := i.History[ptype]; !ok {
|
||||
i.History[ptype] = []string{item}
|
||||
} else {
|
||||
i.History[ptype] = append(i.History[ptype], item)
|
||||
|
||||
// avoid duplicates
|
||||
h := i.History[ptype]
|
||||
for j := len(h) - 2; j >= 0; j-- {
|
||||
if h[j] == h[len(h)-1] {
|
||||
i.History[ptype] = append(h[:j], h[j+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpHistory fetches the previous item in the history
|
||||
func (i *InfoBuf) UpHistory(history []string) {
|
||||
if i.HistoryNum > 0 && i.HasPrompt && !i.HasYN {
|
||||
|
||||
@@ -54,7 +54,7 @@ func (i *InfoBuf) Close() {
|
||||
func (i *InfoBuf) Message(msg ...interface{}) {
|
||||
// only display a new message if there isn't an active prompt
|
||||
// this is to prevent overwriting an existing prompt to the user
|
||||
if !i.HasPrompt {
|
||||
if i.HasPrompt == false {
|
||||
displayMessage := fmt.Sprint(msg...)
|
||||
// if there is no active prompt then style and display the message as normal
|
||||
i.Msg = displayMessage
|
||||
@@ -78,7 +78,7 @@ func (i *InfoBuf) ClearGutter() {
|
||||
func (i *InfoBuf) Error(msg ...interface{}) {
|
||||
// only display a new message if there isn't an active prompt
|
||||
// this is to prevent overwriting an existing prompt to the user
|
||||
if !i.HasPrompt {
|
||||
if i.HasPrompt == false {
|
||||
// if there is no active prompt then style and display the message as normal
|
||||
i.Msg = fmt.Sprint(msg...)
|
||||
i.HasMessage, i.HasError = false, true
|
||||
@@ -137,27 +137,18 @@ func (i *InfoBuf) DonePrompt(canceled bool) {
|
||||
if !hadYN {
|
||||
if i.PromptCallback != nil {
|
||||
if canceled {
|
||||
i.Replace(i.Start(), i.End(), "")
|
||||
i.PromptCallback("", true)
|
||||
h := i.History[i.PromptType]
|
||||
i.History[i.PromptType] = h[:len(h)-1]
|
||||
} else {
|
||||
resp := string(i.LineBytes(0))
|
||||
i.Replace(i.Start(), i.End(), "")
|
||||
i.PromptCallback(resp, false)
|
||||
h := i.History[i.PromptType]
|
||||
h[len(h)-1] = resp
|
||||
|
||||
// avoid duplicates
|
||||
for j := len(h) - 2; j >= 0; j-- {
|
||||
if h[j] == h[len(h)-1] {
|
||||
i.History[i.PromptType] = append(h[:j], h[j+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// i.PromptCallback = nil
|
||||
}
|
||||
i.Replace(i.Start(), i.End(), "")
|
||||
}
|
||||
if i.YNCallback != nil && hadYN {
|
||||
i.YNCallback(i.YNResp, canceled)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package lua
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -10,24 +9,20 @@ import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
luar "layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
var L *lua.LState
|
||||
var Lock sync.Mutex
|
||||
|
||||
// LoadFile loads a lua file
|
||||
func LoadFile(module string, file string, data []byte) error {
|
||||
@@ -74,12 +69,6 @@ func Import(pkg string) *lua.LTable {
|
||||
return importTime()
|
||||
case "unicode/utf8", "utf8":
|
||||
return importUtf8()
|
||||
case "humanize":
|
||||
return importHumanize()
|
||||
case "net/http", "http":
|
||||
return importHTTP()
|
||||
case "archive/zip":
|
||||
return importArchiveZip()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@@ -389,7 +378,6 @@ func importOs() *lua.LTable {
|
||||
L.SetField(pkg, "Symlink", luar.New(L, os.Symlink))
|
||||
L.SetField(pkg, "TempDir", luar.New(L, os.TempDir))
|
||||
L.SetField(pkg, "Truncate", luar.New(L, os.Truncate))
|
||||
L.SetField(pkg, "UserHomeDir", luar.New(L, os.UserHomeDir))
|
||||
|
||||
return pkg
|
||||
}
|
||||
@@ -568,31 +556,3 @@ func importUtf8() *lua.LTable {
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importHumanize() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Bytes", luar.New(L, humanize.Bytes))
|
||||
L.SetField(pkg, "Ordinal", luar.New(L, humanize.Ordinal))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importHTTP() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "Get", luar.New(L, http.Get))
|
||||
L.SetField(pkg, "Post", luar.New(L, http.Post))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
func importArchiveZip() *lua.LTable {
|
||||
pkg := L.NewTable()
|
||||
|
||||
L.SetField(pkg, "OpenReader", luar.New(L, zip.OpenReader))
|
||||
L.SetField(pkg, "NewReader", luar.New(L, zip.NewReader))
|
||||
L.SetField(pkg, "NewWriter", luar.New(L, zip.NewWriter))
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package screen
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"unicode"
|
||||
|
||||
"github.com/zyedidia/micro/v2/internal/config"
|
||||
"github.com/zyedidia/micro/v2/internal/util"
|
||||
"github.com/zyedidia/tcell/v2"
|
||||
"github.com/zyedidia/tcell"
|
||||
)
|
||||
|
||||
// Screen is the tcell screen we use to draw to the terminal
|
||||
@@ -19,9 +19,6 @@ import (
|
||||
// same time too.
|
||||
var Screen tcell.Screen
|
||||
|
||||
// Events is the channel of tcell events
|
||||
var Events chan (tcell.Event)
|
||||
|
||||
// The lock is necessary since the screen is polled on a separate thread
|
||||
var lock sync.Mutex
|
||||
|
||||
@@ -101,7 +98,7 @@ func ShowCursor(x, y int) {
|
||||
// SetContent sets a cell at a point on the screen and makes sure that it is
|
||||
// synced with the last cursor location
|
||||
func SetContent(x, y int, mainc rune, combc []rune, style tcell.Style) {
|
||||
if !Screen.CanDisplay(mainc, true) {
|
||||
if !unicode.IsPrint(mainc) {
|
||||
mainc = '<27>'
|
||||
}
|
||||
|
||||
@@ -134,7 +131,7 @@ func TempStart(screenWasNil bool) {
|
||||
}
|
||||
|
||||
// Init creates and initializes the tcell screen
|
||||
func Init() error {
|
||||
func Init() {
|
||||
drawChan = make(chan bool, 8)
|
||||
|
||||
// Should we enable true color?
|
||||
@@ -145,67 +142,30 @@ func Init() error {
|
||||
}
|
||||
|
||||
var oldTerm string
|
||||
modifiedTerm := false
|
||||
setXterm := func() {
|
||||
if config.GetGlobalOption("xterm").(bool) {
|
||||
oldTerm = os.Getenv("TERM")
|
||||
os.Setenv("TERM", "xterm-256color")
|
||||
modifiedTerm = true
|
||||
}
|
||||
|
||||
if config.GetGlobalOption("xterm").(bool) {
|
||||
setXterm()
|
||||
}
|
||||
|
||||
// Initilize tcell
|
||||
var err error
|
||||
Screen, err = tcell.NewScreen()
|
||||
if err != nil {
|
||||
log.Println("Warning: during screen initialization:", err)
|
||||
log.Println("Falling back to TERM=xterm-256color")
|
||||
setXterm()
|
||||
Screen, err = tcell.NewScreen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(err)
|
||||
fmt.Println("Fatal: Micro could not initialize a Screen.")
|
||||
os.Exit(1)
|
||||
}
|
||||
if err = Screen.Init(); err != nil {
|
||||
return err
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
Screen.SetPaste(config.GetGlobalOption("paste").(bool))
|
||||
|
||||
// restore TERM
|
||||
if modifiedTerm {
|
||||
if config.GetGlobalOption("xterm").(bool) {
|
||||
os.Setenv("TERM", oldTerm)
|
||||
}
|
||||
|
||||
if config.GetGlobalOption("mouse").(bool) {
|
||||
Screen.EnableMouse()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitSimScreen initializes a simulation screen for testing purposes
|
||||
func InitSimScreen() (tcell.SimulationScreen, error) {
|
||||
drawChan = make(chan bool, 8)
|
||||
|
||||
// Initilize tcell
|
||||
var err error
|
||||
s := tcell.NewSimulationScreen("")
|
||||
if s == nil {
|
||||
return nil, errors.New("Failed to get a simulation screen")
|
||||
}
|
||||
if err = s.Init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.SetSize(80, 24)
|
||||
Screen = s
|
||||
|
||||
if config.GetGlobalOption("mouse").(bool) {
|
||||
Screen.EnableMouse()
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@@ -37,12 +37,6 @@ type CallbackFile struct {
|
||||
args []interface{}
|
||||
}
|
||||
|
||||
// Job stores the executing command for the job, and the stdin pipe
|
||||
type Job struct {
|
||||
*exec.Cmd
|
||||
Stdin io.WriteCloser
|
||||
}
|
||||
|
||||
func (f *CallbackFile) Write(data []byte) (int, error) {
|
||||
// This is either stderr or stdout
|
||||
// In either case we create a new job function callback and put it in the jobs channel
|
||||
@@ -53,13 +47,13 @@ func (f *CallbackFile) Write(data []byte) (int, error) {
|
||||
|
||||
// JobStart starts a shell command in the background with the given callbacks
|
||||
// It returns an *exec.Cmd as the job id
|
||||
func JobStart(cmd string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *Job {
|
||||
func JobStart(cmd string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *exec.Cmd {
|
||||
return JobSpawn("sh", []string{"-c", cmd}, onStdout, onStderr, onExit, userargs...)
|
||||
}
|
||||
|
||||
// JobSpawn starts a process with args in the background with the given callbacks
|
||||
// It returns an *exec.Cmd as the job id
|
||||
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *Job {
|
||||
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *exec.Cmd {
|
||||
// Set up everything correctly if the functions have been provided
|
||||
proc := exec.Command(cmdName, cmdArgs...)
|
||||
var outbuf bytes.Buffer
|
||||
@@ -73,24 +67,28 @@ func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(
|
||||
} else {
|
||||
proc.Stderr = &outbuf
|
||||
}
|
||||
stdin, _ := proc.StdinPipe()
|
||||
|
||||
go func() {
|
||||
// Run the process in the background and create the onExit callback
|
||||
proc.Run()
|
||||
jobFunc := JobFunction{onExit, outbuf.String(), userargs}
|
||||
jobFunc := JobFunction{onExit, string(outbuf.Bytes()), userargs}
|
||||
Jobs <- jobFunc
|
||||
}()
|
||||
|
||||
return &Job{proc, stdin}
|
||||
return proc
|
||||
}
|
||||
|
||||
// JobStop kills a job
|
||||
func JobStop(j *Job) {
|
||||
j.Process.Kill()
|
||||
func JobStop(cmd *exec.Cmd) {
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
|
||||
// JobSend sends the given data into the job's stdin stream
|
||||
func JobSend(j *Job, data string) {
|
||||
j.Stdin.Write([]byte(data))
|
||||
func JobSend(cmd *exec.Cmd, data string) {
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
stdin.Write([]byte(data))
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
@@ -48,8 +46,7 @@ func init() {
|
||||
fmt.Println("Invalid version: ", Version, err)
|
||||
}
|
||||
|
||||
_, wt := os.LookupEnv("WT_SESSION")
|
||||
if runtime.GOOS == "windows" && !wt {
|
||||
if runtime.GOOS == "windows" {
|
||||
FakeCursor = true
|
||||
}
|
||||
Stdout = new(bytes.Buffer)
|
||||
@@ -339,11 +336,7 @@ func GetModTime(path string) (time.Time, error) {
|
||||
// EscapePath replaces every path separator in a given path with a %
|
||||
func EscapePath(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
|
||||
path = strings.ReplaceAll(path, ":", "%")
|
||||
}
|
||||
return strings.ReplaceAll(path, "/", "%")
|
||||
return strings.Replace(path, "/", "%", -1)
|
||||
}
|
||||
|
||||
// GetLeadingWhitespace returns the leading whitespace of the given byte array
|
||||
@@ -430,63 +423,10 @@ func IsAutocomplete(c rune) bool {
|
||||
}
|
||||
|
||||
func ParseSpecial(s string) string {
|
||||
return strings.ReplaceAll(s, "\\t", "\t")
|
||||
return strings.Replace(s, "\\t", "\t", -1)
|
||||
}
|
||||
|
||||
// String converts a byte array to a string (for lua plugins)
|
||||
func String(s []byte) string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
// Unzip unzips a file to given folder
|
||||
func Unzip(src, dest string) error {
|
||||
r, err := zip.OpenReader(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
os.MkdirAll(dest, 0755)
|
||||
|
||||
// Closure to address file descriptors issue with all the deferred .Close() methods
|
||||
extractAndWriteFile := func(f *zip.File) error {
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
path := filepath.Join(dest, f.Name)
|
||||
|
||||
// Check for ZipSlip (Directory traversal)
|
||||
if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {
|
||||
return fmt.Errorf("illegal file path: %s", path)
|
||||
}
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
os.MkdirAll(path, f.Mode())
|
||||
} else {
|
||||
os.MkdirAll(filepath.Dir(path), f.Mode())
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = io.Copy(f, rc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, f := range r.File {
|
||||
err := extractAndWriteFile(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
21
internal/vfsutil/file.go
Normal file
21
internal/vfsutil/file.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package vfsutil
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// File implements http.FileSystem using the native file system restricted to a
|
||||
// specific file served at root.
|
||||
//
|
||||
// While the FileSystem.Open method takes '/'-separated paths, a File's string
|
||||
// value is a filename on the native file system, not a URL, so it is separated
|
||||
// by filepath.Separator, which isn't necessarily '/'.
|
||||
type File string
|
||||
|
||||
func (f File) Open(name string) (http.File, error) {
|
||||
if name != "/" {
|
||||
return nil, &os.PathError{Op: "open", Path: name, Err: os.ErrNotExist}
|
||||
}
|
||||
return os.Open(string(f))
|
||||
}
|
||||
39
internal/vfsutil/vfsutil.go
Normal file
39
internal/vfsutil/vfsutil.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Package vfsutil implements some I/O utility functions for http.FileSystem.
|
||||
package vfsutil
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ReadDir reads the contents of the directory associated with file and
|
||||
// returns a slice of FileInfo values in directory order.
|
||||
func ReadDir(fs http.FileSystem, name string) ([]os.FileInfo, error) {
|
||||
f, err := fs.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return f.Readdir(0)
|
||||
}
|
||||
|
||||
// Stat returns the FileInfo structure describing file.
|
||||
func Stat(fs http.FileSystem, name string) (os.FileInfo, error) {
|
||||
f, err := fs.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return f.Stat()
|
||||
}
|
||||
|
||||
// ReadFile reads the file named by path from fs and returns the contents.
|
||||
func ReadFile(fs http.FileSystem, path string) ([]byte, error) {
|
||||
rc, err := fs.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rc.Close()
|
||||
return ioutil.ReadAll(rc)
|
||||
}
|
||||
146
internal/vfsutil/walk.go
Normal file
146
internal/vfsutil/walk.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package vfsutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
pathpkg "path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Walk walks the filesystem rooted at root, calling walkFn for each file or
|
||||
// directory in the filesystem, including root. All errors that arise visiting files
|
||||
// and directories are filtered by walkFn. The files are walked in lexical
|
||||
// order.
|
||||
func Walk(fs http.FileSystem, root string, walkFn filepath.WalkFunc) error {
|
||||
info, err := Stat(fs, root)
|
||||
if err != nil {
|
||||
return walkFn(root, nil, err)
|
||||
}
|
||||
return walk(fs, root, info, walkFn)
|
||||
}
|
||||
|
||||
// readDirNames reads the directory named by dirname and returns
|
||||
// a sorted list of directory entries.
|
||||
func readDirNames(fs http.FileSystem, dirname string) ([]string, error) {
|
||||
fis, err := ReadDir(fs, dirname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
names := make([]string, len(fis))
|
||||
for i := range fis {
|
||||
names[i] = fis[i].Name()
|
||||
}
|
||||
sort.Strings(names)
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// walk recursively descends path, calling walkFn.
|
||||
func walk(fs http.FileSystem, path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
|
||||
err := walkFn(path, info, nil)
|
||||
if err != nil {
|
||||
if info.IsDir() && err == filepath.SkipDir {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
names, err := readDirNames(fs, path)
|
||||
if err != nil {
|
||||
return walkFn(path, info, err)
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
filename := pathpkg.Join(path, name)
|
||||
fileInfo, err := Stat(fs, filename)
|
||||
if err != nil {
|
||||
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = walk(fs, filename, fileInfo, walkFn)
|
||||
if err != nil {
|
||||
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WalkFilesFunc is the type of the function called for each file or directory visited by WalkFiles.
|
||||
// It's like filepath.WalkFunc, except it provides an additional ReadSeeker parameter for file being visited.
|
||||
type WalkFilesFunc func(path string, info os.FileInfo, rs io.ReadSeeker, err error) error
|
||||
|
||||
// WalkFiles walks the filesystem rooted at root, calling walkFn for each file or
|
||||
// directory in the filesystem, including root. In addition to FileInfo, it passes an
|
||||
// ReadSeeker to walkFn for each file it visits.
|
||||
func WalkFiles(fs http.FileSystem, root string, walkFn WalkFilesFunc) error {
|
||||
file, info, err := openStat(fs, root)
|
||||
if err != nil {
|
||||
return walkFn(root, nil, nil, err)
|
||||
}
|
||||
return walkFiles(fs, root, info, file, walkFn)
|
||||
}
|
||||
|
||||
// walkFiles recursively descends path, calling walkFn.
|
||||
// It closes the input file after it's done with it, so the caller shouldn't.
|
||||
func walkFiles(fs http.FileSystem, path string, info os.FileInfo, file http.File, walkFn WalkFilesFunc) error {
|
||||
err := walkFn(path, info, file, nil)
|
||||
file.Close()
|
||||
if err != nil {
|
||||
if info.IsDir() && err == filepath.SkipDir {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
names, err := readDirNames(fs, path)
|
||||
if err != nil {
|
||||
return walkFn(path, info, nil, err)
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
filename := pathpkg.Join(path, name)
|
||||
file, fileInfo, err := openStat(fs, filename)
|
||||
if err != nil {
|
||||
if err := walkFn(filename, nil, nil, err); err != nil && err != filepath.SkipDir {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = walkFiles(fs, filename, fileInfo, file, walkFn)
|
||||
// file is closed by walkFiles, so we don't need to close it here.
|
||||
if err != nil {
|
||||
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// openStat performs Open and Stat and returns results, or first error encountered.
|
||||
// The caller is responsible for closing the returned file when done.
|
||||
func openStat(fs http.FileSystem, name string) (http.File, os.FileInfo, error) {
|
||||
f, err := fs.Open(name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
return f, fi, nil
|
||||
}
|
||||
93
internal/vfsutil/walk_test.go
Normal file
93
internal/vfsutil/walk_test.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package vfsutil_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/shurcooL/httpfs/vfsutil"
|
||||
"golang.org/x/tools/godoc/vfs/httpfs"
|
||||
"golang.org/x/tools/godoc/vfs/mapfs"
|
||||
)
|
||||
|
||||
func ExampleWalk() {
|
||||
var fs http.FileSystem = httpfs.New(mapfs.New(map[string]string{
|
||||
"zzz-last-file.txt": "It should be visited last.",
|
||||
"a-file.txt": "It has stuff.",
|
||||
"another-file.txt": "Also stuff.",
|
||||
"folderA/entry-A.txt": "Alpha.",
|
||||
"folderA/entry-B.txt": "Beta.",
|
||||
}))
|
||||
|
||||
walkFn := func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
log.Printf("can't stat file %s: %v\n", path, err)
|
||||
return nil
|
||||
}
|
||||
fmt.Println(path)
|
||||
return nil
|
||||
}
|
||||
|
||||
err := vfsutil.Walk(fs, "/", walkFn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// /
|
||||
// /a-file.txt
|
||||
// /another-file.txt
|
||||
// /folderA
|
||||
// /folderA/entry-A.txt
|
||||
// /folderA/entry-B.txt
|
||||
// /zzz-last-file.txt
|
||||
}
|
||||
|
||||
func ExampleWalkFiles() {
|
||||
var fs http.FileSystem = httpfs.New(mapfs.New(map[string]string{
|
||||
"zzz-last-file.txt": "It should be visited last.",
|
||||
"a-file.txt": "It has stuff.",
|
||||
"another-file.txt": "Also stuff.",
|
||||
"folderA/entry-A.txt": "Alpha.",
|
||||
"folderA/entry-B.txt": "Beta.",
|
||||
}))
|
||||
|
||||
walkFn := func(path string, fi os.FileInfo, r io.ReadSeeker, err error) error {
|
||||
if err != nil {
|
||||
log.Printf("can't stat file %s: %v\n", path, err)
|
||||
return nil
|
||||
}
|
||||
fmt.Println(path)
|
||||
if !fi.IsDir() {
|
||||
b, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
log.Printf("can't read file %s: %v\n", path, err)
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("%q\n", b)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err := vfsutil.WalkFiles(fs, "/", walkFn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// /
|
||||
// /a-file.txt
|
||||
// "It has stuff."
|
||||
// /another-file.txt
|
||||
// "Also stuff."
|
||||
// /folderA
|
||||
// /folderA/entry-A.txt
|
||||
// "Alpha."
|
||||
// /folderA/entry-B.txt
|
||||
// "Beta."
|
||||
// /zzz-last-file.txt
|
||||
// "It should be visited last."
|
||||
}
|
||||
@@ -5,7 +5,7 @@ color-link constant "#AE81FF,#282828"
|
||||
color-link constant.string "#E6DB74,#282828"
|
||||
color-link constant.string.char "#BDE6AD,#282828"
|
||||
color-link statement "#F92672,#282828"
|
||||
color-link symbol.operator "#F92671,#282828"
|
||||
color-link symbol "#F92672,#282828"
|
||||
color-link preproc "#CB4B16,#282828"
|
||||
color-link type "#66D9EF,#282828"
|
||||
color-link special "#A6E22E,#282828"
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
color-link default "#F8F8F2,#282A36"
|
||||
color-link comment "#6272A4"
|
||||
|
||||
color-link identifier "#50FA7B"
|
||||
color-link identifier.class "#8BE9FD"
|
||||
color-link identifier.var "#F8F8F2"
|
||||
|
||||
color-link constant "#BD93F9"
|
||||
color-link constant.number "#F8F8F2"
|
||||
color-link constant.string "#F1FA8C"
|
||||
|
||||
color-link symbol "#FF79C6"
|
||||
color-link symbol.brackets "#F8F8F2"
|
||||
color-link symbol.tag "#AE81FF"
|
||||
|
||||
color-link type "italic #8BE9FD"
|
||||
color-link type.keyword "#FF79C6"
|
||||
|
||||
color-link special "#FF79C6"
|
||||
color-link statement "#FF79C6"
|
||||
color-link preproc "#FF79C6"
|
||||
|
||||
color-link underlined "#FF79C6"
|
||||
color-link error "bold #FF5555"
|
||||
color-link todo "bold #FF79C6"
|
||||
|
||||
color-link diff-added "#50FA7B"
|
||||
color-link diff-modified "#FFB86C"
|
||||
color-link diff-deleted "#FF5555"
|
||||
|
||||
color-link gutter-error "#FF5555"
|
||||
color-link gutter-warning "#E6DB74"
|
||||
|
||||
color-link statusline "#282A36,#F8F8F2"
|
||||
color-link tabbar "#282A36,#F8F8F2"
|
||||
color-link indent-char "#6272A4"
|
||||
color-link line-number "#6272A4"
|
||||
color-link current-line-number "#F8F8F2"
|
||||
|
||||
color-link cursor-line "#44475A,#F8F8F2"
|
||||
color-link color-column "#44475A"
|
||||
color-link type.extended "default"
|
||||
|
||||
@@ -5,7 +5,7 @@ color-link constant "#AE81FF,#282828"
|
||||
color-link constant.string "#E6DB74,#282828"
|
||||
color-link constant.string.char "#BDE6AD,#282828"
|
||||
color-link statement "#F92672,#282828"
|
||||
color-link symbol.operator "#F92672,#282828"
|
||||
color-link symbol "#F92672,#282828"
|
||||
color-link preproc "#CB4B16,#282828"
|
||||
color-link type "#66D9EF,#282828"
|
||||
color-link special "#A6E22E,#282828"
|
||||
|
||||
@@ -3,7 +3,6 @@ color-link comment "#bc9458,#2b2b2b"
|
||||
color-link statement "#cc7833,#2b2b2b"
|
||||
color-link constant "#a5c261,#2b2b2b"
|
||||
color-link constant.bool "#6d9cbe,#2b2b2b"
|
||||
color-link constant.specialChar "#459231,#2b2b2b"
|
||||
color-link type "#6d9cbe,#2b2b2b"
|
||||
color-link preproc "#cc7833,#2b2b2b"
|
||||
color-link special "#cc7833,#2b2b2b"
|
||||
@@ -19,8 +18,6 @@ color-link diff-modified "#FFAF00"
|
||||
color-link diff-deleted "#D70000"
|
||||
color-link gutter-warning "#a5c261,#11151C"
|
||||
color-link symbol "#edb753,#2b2b2b"
|
||||
color-link symbol.operator "#cc7833,#2b2b2b"
|
||||
color-link symbol.brackets "#cc7833,#2b2b2b"
|
||||
color-link identifier "#edb753,#2b2b2b"
|
||||
color-link statusline "#b1b1b1,#232323"
|
||||
color-link tabbar "bold #b1b1b1,#232323"
|
||||
|
||||
@@ -8,7 +8,7 @@ This help page aims to cover two aspects of micro's syntax highlighting engine:
|
||||
|
||||
## Colorschemes
|
||||
|
||||
To change your colorscheme, press Ctrl-e in micro to bring up the command
|
||||
To change your colorscheme, press CtrlE in micro to bring up the command
|
||||
prompt, and type:
|
||||
|
||||
```
|
||||
@@ -87,8 +87,7 @@ These may vary widely based on the 16 colors selected for your terminal.
|
||||
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.
|
||||
variable `MICRO_TRUECOLOR` must be set to 1.
|
||||
|
||||
* `solarized-tc`: this is the solarized colorscheme for true color.
|
||||
* `atom-dark-tc`: this colorscheme is based off of Atom's "dark" colorscheme.
|
||||
@@ -176,7 +175,6 @@ Here is a list of the colorscheme groups that you can use:
|
||||
* underlined
|
||||
* error
|
||||
* todo
|
||||
* selection (Color of the text selection)
|
||||
* statusline (Color of the statusline)
|
||||
* tabbar (Color of the tabbar that lists open files)
|
||||
* indent-char (Color of the character which indicates tabs if the option is
|
||||
@@ -184,17 +182,11 @@ Here is a list of the colorscheme groups that you can use:
|
||||
* line-number
|
||||
* gutter-error
|
||||
* gutter-warning
|
||||
* diff-added
|
||||
* diff-modified
|
||||
* diff-deleted
|
||||
* cursor-line
|
||||
* current-line-number
|
||||
* color-column
|
||||
* ignore
|
||||
* scrollbar
|
||||
* divider (Color of the divider between vertical splits)
|
||||
* message (Color of messages in the bottom line of the screen)
|
||||
* error-message (Color of error messages in the bottom line of the screen)
|
||||
|
||||
Colorschemes must be placed in the `~/.config/micro/colorschemes` directory to
|
||||
be used.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Command bar
|
||||
|
||||
The command bar is opened by pressing Ctrl-e. It is a single-line buffer,
|
||||
The command bar is opened by pressing CtrlE. It is a single-line buffer,
|
||||
meaning that all keybindings from a normal buffer are supported (as well
|
||||
as mouse and selection).
|
||||
|
||||
@@ -13,7 +13,7 @@ does not look up environment variables.
|
||||
# Commands
|
||||
|
||||
Micro provides the following commands that can be executed at the command-bar
|
||||
by pressing `Ctrl-e` and entering the command. Arguments are placed in single
|
||||
by pressing `CtrlE` and entering the command. Arguments are placed in single
|
||||
quotes here but these are not necessary when entering the command in micro.
|
||||
|
||||
* `bind 'key' 'action'`: creates a keybinding from key to action. See the
|
||||
@@ -109,7 +109,7 @@ quotes here but these are not necessary when entering the command in micro.
|
||||
is most useful for debugging keybindings.
|
||||
|
||||
* `showkey`: Show the action(s) bound to a given key. For example
|
||||
running `> showkey Ctrl-c` will display `Copy`.
|
||||
running `> showkey CtrlC` will display `Copy`.
|
||||
|
||||
* `term exec?`: Open a terminal emulator running the given executable. If no
|
||||
executable is given, this will open the default shell in the terminal
|
||||
|
||||
@@ -4,52 +4,8 @@ because there are multiple methods. This help document will explain
|
||||
the various methods for copying and pasting, how they work,
|
||||
and the best methods for doing so over SSH.
|
||||
|
||||
# OSC 52 (terminal clipboard)
|
||||
|
||||
If possible, setting the `clipboard` option to `terminal` will give
|
||||
best results because it will work over SSH and locally. However, there
|
||||
is limited support among terminal emulators for the terminal clipboard
|
||||
(which uses the OSC 52 protocol to communicate clipboard contents).
|
||||
Here is a list of terminal emulators and their status:
|
||||
|
||||
* Kitty: supported, but only writing is enabled by default. To enable
|
||||
reading, add `read-primary` and `read-clipboard` to the
|
||||
`clipboard_control` option.
|
||||
|
||||
* iTerm2: only copying (writing to clipboard) is supported. Must be enabled in
|
||||
`Preferences->General-> Selection->Applications in terminal may access clipboard`.
|
||||
You can use Command-v to paste.
|
||||
|
||||
* `st`: supported.
|
||||
|
||||
* `rxvt-unicode`: not natively supported, but there is a Perl extension
|
||||
[here](http://anti.teamidiot.de/static/nei/*/Code/urxvt/).
|
||||
|
||||
* `xterm`: supported, but disabled by default. It can be enabled by putting
|
||||
the following in `.Xresources` or `.Xdefaults`:
|
||||
`XTerm*disallowedWindowOps: 20,21,SetXprop`.
|
||||
|
||||
* `gnome-terminal`: does not support OSC 52.
|
||||
|
||||
**Summary:** If you want copy and paste to work over SSH, then you
|
||||
should set `clipboard` to `terminal`, and make sure your terminal
|
||||
supports OSC 52.
|
||||
|
||||
# Pasting
|
||||
|
||||
## Recommendations (TL;DR)
|
||||
|
||||
The recommended method of pasting is the following:
|
||||
|
||||
* If you are not working over SSH, use the micro keybinding (Ctrl-v
|
||||
by default) to perform pastes. If on Linux, install `xclip` or
|
||||
`xsel` beforehand.
|
||||
|
||||
* If you are working over SSH, use the terminal keybinding
|
||||
(Ctrl-Shift-v or Command-v) to perform pastes. If your terminal
|
||||
does not support bracketed paste, when performing a paste first
|
||||
enable the `paste` option, and when finished disable the option.
|
||||
|
||||
## Micro paste events
|
||||
|
||||
Micro is an application that runs within the terminal. This means
|
||||
@@ -100,23 +56,20 @@ machine's clipboard. On the other hand, the terminal keybinding
|
||||
for paste will access your local clipboard and send the text over
|
||||
the network as a paste event, which is what you want.
|
||||
|
||||
# Copying
|
||||
## Recommendations
|
||||
|
||||
# Recommendations (TL;DR)
|
||||
The recommended method of pasting is the following:
|
||||
|
||||
The recommended method of copying is the following:
|
||||
|
||||
* If you are not working over SSH, use the micro keybinding (Ctrl-c by
|
||||
default) to perform copies. If on Linux, install `xclip` or `xsel`
|
||||
beforehand.
|
||||
* If you are not working over SSH, use the micro keybinding (Ctrl-v
|
||||
by default) to perform pastes. If on Linux, install `xclip` or
|
||||
`xsel` beforehand.
|
||||
|
||||
* If you are working over SSH, use the terminal keybinding
|
||||
(Ctrl-Shift-c or Command-c) to perform copies. You must first disable
|
||||
the `mouse` option to perform a terminal selection, and you may wish
|
||||
to disable line numbers and diff indicators (`ruler` and `diffgutter`
|
||||
options) and close other splits. This method will only be able to copy
|
||||
characters that are displayed on the screen (you will not be able to
|
||||
copy more than one page's worth of characters).
|
||||
(Ctrl-Shift-v or Command-v) to perform pastes. If your terminal
|
||||
does not support bracketed paste, when performing a paste first
|
||||
enable the `paste` option, and when finished disable the option.
|
||||
|
||||
# Copying
|
||||
|
||||
Copying follows a similar discussion to the one above about pasting.
|
||||
The primary difference is before performing a copy, the application
|
||||
@@ -139,3 +92,19 @@ means that for copying multiple lines using the terminal selection, you
|
||||
should first disable line numbers and diff indicators (turn off the `ruler`
|
||||
and `diffgutter` options), otherwise they might be part of your selection
|
||||
and copied.
|
||||
|
||||
## Recommendations
|
||||
|
||||
The recommended method of copying is the following:
|
||||
|
||||
* If you are not working over SSH, use the micro keybinding (Ctrl-c by
|
||||
default) to perform copies. If on Linux, install `xclip` or `xsel`
|
||||
beforehand.
|
||||
|
||||
* If you are working over SSH, use the terminal keybinding
|
||||
(Ctrl-Shift-c or Command-c) to perform copies. You must first disable
|
||||
the `mouse` option to perform a terminal selection, and you may wish
|
||||
to disable line numbers and diff indicators (`ruler` and `diffgutter`
|
||||
options) and close other splits. This method will only be able to copy
|
||||
characters that are displayed on the screen (you will not be able to
|
||||
copy more than one page's worth of characters).
|
||||
|
||||
@@ -80,7 +80,7 @@ can change it!
|
||||
| Ctrl-z | Undo |
|
||||
| Ctrl-y | Redo |
|
||||
| Alt-UpArrow | Move current line or selected lines up |
|
||||
| Alt-DownArrow | Move current line or selected lines down |
|
||||
| Alt-DownArrow | Move current line of selected lines down |
|
||||
| Alt-Backspace or Alt-Ctrl-h | Delete word left |
|
||||
| Ctrl-a | Select all |
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ Micro is a terminal-based text editor that aims to be easy to use and
|
||||
intuitive, while also taking advantage of the full capabilities of modern
|
||||
terminals.
|
||||
|
||||
To open the command bar, press Ctrl-e. This enables a `>` prompt for typing
|
||||
To open the command bar, press CtrlE. This enables a `>` prompt for typing
|
||||
commands. From now on when the documentation says to run a command such as `>
|
||||
help`, this means press Ctrl-e and type `help` (and press enter to execute the
|
||||
help`, this means press CtrlE and type `help` (and press enter to execute the
|
||||
command).
|
||||
|
||||
For a list of the default keybindings run `> help defaultkeys`.
|
||||
@@ -14,7 +14,7 @@ For more information on keybindings see `> help keybindings`.
|
||||
|
||||
## Quick-start
|
||||
|
||||
Press Ctrl-q to quit, and Ctrl-s to save. Press Ctrl-e to start typing commands
|
||||
Press Ctrl-q to quit, and Ctrl-s to save. Press CtrlE to start typing commands
|
||||
and you can see which commands are available by pressing tab, or by viewing the
|
||||
help topic `> help commands`.
|
||||
|
||||
@@ -34,7 +34,7 @@ Press Ctrl-w to move between splits, and type `> vsplit filename` or
|
||||
|
||||
Micro has a built-in help system which can be accessed with the `help` command.
|
||||
|
||||
To use it, press Ctrl-e to access command mode and type in `help` followed by a
|
||||
To use it, press CtrlE to access command mode and type in `help` followed by a
|
||||
topic. Typing `help` followed by nothing will open this page.
|
||||
|
||||
Here are the possible help topics that you can read:
|
||||
|
||||
@@ -4,7 +4,7 @@ Micro has a plethora of hotkeys that make it easy and powerful to use and all
|
||||
hotkeys are fully customizable to your liking.
|
||||
|
||||
Custom keybindings are stored internally in micro if changed with the `> bind`
|
||||
command or can also be added in the file `~/.config/micro/bindings.json` as
|
||||
command or you can also be added in the file `~/.config/micro/bindings.json` as
|
||||
discussed below. For a list of the default keybindings in the json format used
|
||||
by micro, please see the end of this file. For a more user-friendly list with
|
||||
explanations of what the default hotkeys are and what they do, please see
|
||||
@@ -30,17 +30,11 @@ following in the `bindings.json` file.
|
||||
|
||||
```json
|
||||
{
|
||||
"Ctrl-y": "Undo",
|
||||
"Ctrl-z": "Redo"
|
||||
"CtrlY": "Undo",
|
||||
"CtrlZ": "Redo"
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** The syntax `<Modifier><key>` is equivalent to `<Modifier>-<key>`. In
|
||||
addition, Ctrl-Shift bindings are not supported by terminals, and are the same
|
||||
as simply Ctrl bindings. This means that `CtrlG`, `Ctrl-G`, and `Ctrl-g` all
|
||||
mean the same thing. However, for Alt this is not the case: `AltG` and `Alt-G`
|
||||
mean `Alt-Shift-g`, while `Alt-g` does not require the Shift modifier.
|
||||
|
||||
In addition to editing your `~/.config/micro/bindings.json`, you can run
|
||||
`>bind <keycombo> <action>` For a list of bindable actions, see below.
|
||||
|
||||
@@ -91,15 +85,15 @@ your working directory in the infobar.
|
||||
You can also bind an "editable" command with `command-edit:`. This means that
|
||||
micro won't immediately execute the command when you press the binding, but
|
||||
instead just place the string in the infobar in command mode. For example,
|
||||
you could rebind `Ctrl-g` to `> help`:
|
||||
you could rebind `CtrlG` to `> help`:
|
||||
|
||||
```json
|
||||
{
|
||||
"Ctrl-g": "command-edit:help "
|
||||
"CtrlG": "command-edit:help "
|
||||
}
|
||||
```
|
||||
|
||||
Now when you press `Ctrl-g`, `help` will appear in the command bar and your
|
||||
Now when you press `CtrlG`, `help` will appear in the command bar and your
|
||||
cursor will be placed after it (note the space in the json that controls the
|
||||
cursor placement).
|
||||
|
||||
@@ -363,32 +357,32 @@ F62
|
||||
F63
|
||||
F64
|
||||
CtrlSpace
|
||||
Ctrl-a
|
||||
Ctrl-b
|
||||
Ctrl-c
|
||||
Ctrl-d
|
||||
Ctrl-e
|
||||
Ctrl-f
|
||||
Ctrl-g
|
||||
Ctrl-h
|
||||
Ctrl-i
|
||||
Ctrl-j
|
||||
Ctrl-k
|
||||
Ctrl-l
|
||||
Ctrl-m
|
||||
Ctrl-n
|
||||
Ctrl-o
|
||||
Ctrl-p
|
||||
Ctrl-q
|
||||
Ctrl-r
|
||||
Ctrl-s
|
||||
Ctrl-t
|
||||
Ctrl-u
|
||||
Ctrl-v
|
||||
Ctrl-w
|
||||
Ctrl-x
|
||||
Ctrl-y
|
||||
Ctrl-z
|
||||
CtrlA
|
||||
CtrlB
|
||||
CtrlC
|
||||
CtrlD
|
||||
CtrlE
|
||||
CtrlF
|
||||
CtrlG
|
||||
CtrlH
|
||||
CtrlI
|
||||
CtrlJ
|
||||
CtrlK
|
||||
CtrlL
|
||||
CtrlM
|
||||
CtrlN
|
||||
CtrlO
|
||||
CtrlP
|
||||
CtrlQ
|
||||
CtrlR
|
||||
CtrlS
|
||||
CtrlT
|
||||
CtrlU
|
||||
CtrlV
|
||||
CtrlW
|
||||
CtrlX
|
||||
CtrlY
|
||||
CtrlZ
|
||||
CtrlLeftSq
|
||||
CtrlBackslash
|
||||
CtrlRightSq
|
||||
@@ -415,11 +409,6 @@ MouseWheelLeft
|
||||
MouseWheelRight
|
||||
```
|
||||
|
||||
## Key sequences
|
||||
|
||||
Key sequences can be bound by specifying valid keys one after another in brackets, such
|
||||
as `<Ctrl-x><Ctrl-c>`.
|
||||
|
||||
# Default keybinding configuration.
|
||||
|
||||
A select few keybindings are different on MacOS compared to other
|
||||
@@ -461,28 +450,28 @@ conventions for text editing defaults.
|
||||
"Alt-{": "ParagraphPrevious",
|
||||
"Alt-}": "ParagraphNext",
|
||||
"Enter": "InsertNewline",
|
||||
"Ctrl-h": "Backspace",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Tab": "Autocomplete|IndentSelection|InsertTab",
|
||||
"Backtab": "OutdentSelection|OutdentLine",
|
||||
"Ctrl-o": "OpenFile",
|
||||
"Ctrl-s": "Save",
|
||||
"Ctrl-f": "Find",
|
||||
"Ctrl-n": "FindNext",
|
||||
"Ctrl-p": "FindPrevious",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-d": "DuplicateLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Ctrl-a": "SelectAll",
|
||||
"Ctrl-t": "AddTab",
|
||||
"Alt-,": "PreviousTab",
|
||||
"Alt-.": "NextTab",
|
||||
"CtrlO": "OpenFile",
|
||||
"CtrlS": "Save",
|
||||
"CtrlF": "Find",
|
||||
"CtrlN": "FindNext",
|
||||
"CtrlP": "FindPrevious",
|
||||
"CtrlZ": "Undo",
|
||||
"CtrlY": "Redo",
|
||||
"CtrlC": "CopyLine|Copy",
|
||||
"CtrlX": "Cut",
|
||||
"CtrlK": "CutLine",
|
||||
"CtrlD": "DuplicateLine",
|
||||
"CtrlV": "Paste",
|
||||
"CtrlA": "SelectAll",
|
||||
"CtrlT": "AddTab",
|
||||
"Alt,": "PreviousTab",
|
||||
"Alt.": "NextTab",
|
||||
"Home": "StartOfText",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
@@ -491,17 +480,17 @@ conventions for text editing defaults.
|
||||
"PageDown": "CursorPageDown",
|
||||
"CtrlPageUp": "PreviousTab",
|
||||
"CtrlPageDown": "NextTab",
|
||||
"Ctrl-g": "ToggleHelp",
|
||||
"CtrlG": "ToggleHelp",
|
||||
"Alt-g": "ToggleKeyMenu",
|
||||
"Ctrl-r": "ToggleRuler",
|
||||
"Ctrl-l": "command-edit:goto ",
|
||||
"CtrlR": "ToggleRuler",
|
||||
"CtrlL": "command-edit:goto ",
|
||||
"Delete": "Delete",
|
||||
"Ctrl-b": "ShellMode",
|
||||
"Ctrl-q": "Quit",
|
||||
"Ctrl-e": "CommandMode",
|
||||
"Ctrl-w": "NextSplit",
|
||||
"Ctrl-u": "ToggleMacro",
|
||||
"Ctrl-j": "PlayMacro",
|
||||
"CtrlB": "ShellMode",
|
||||
"CtrlQ": "Quit",
|
||||
"CtrlE": "CommandMode",
|
||||
"CtrlW": "NextSplit",
|
||||
"CtrlU": "ToggleMacro",
|
||||
"CtrlJ": "PlayMacro",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
|
||||
// Emacs-style keybindings
|
||||
@@ -535,110 +524,9 @@ conventions for text editing defaults.
|
||||
}
|
||||
```
|
||||
|
||||
## Pane type bindings
|
||||
|
||||
Keybindings can be specified for different pane types as well. For example, to
|
||||
make a binding that only affects the command bar, use the `command` subgroup:
|
||||
|
||||
```
|
||||
{
|
||||
"command": {
|
||||
"Ctrl-w": "WordLeft"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The possible pane types are `buffer` (normal buffer), `command` (command bar),
|
||||
and `terminal` (terminal pane). The defaults for the command and terminal panes
|
||||
are given below:
|
||||
|
||||
```
|
||||
{
|
||||
"terminal": {
|
||||
"<Ctrl-q><Ctrl-q>": "Exit",
|
||||
"<Ctrl-e><Ctrl-e>": "CommandMode",
|
||||
"<Ctrl-w><Ctrl-w>": "NextSplit"
|
||||
},
|
||||
|
||||
"command": {
|
||||
"Up": "HistoryUp",
|
||||
"Down": "HistoryDown",
|
||||
"Right": "CursorRight",
|
||||
"Left": "CursorLeft",
|
||||
"ShiftUp": "SelectUp",
|
||||
"ShiftDown": "SelectDown",
|
||||
"ShiftLeft": "SelectLeft",
|
||||
"ShiftRight": "SelectRight",
|
||||
"AltLeft": "StartOfTextToggle",
|
||||
"AltRight": "EndOfLine",
|
||||
"AltUp": "CursorStart",
|
||||
"AltDown": "CursorEnd",
|
||||
"AltShiftRight": "SelectWordRight",
|
||||
"AltShiftLeft": "SelectWordLeft",
|
||||
"CtrlLeft": "WordLeft",
|
||||
"CtrlRight": "WordRight",
|
||||
"CtrlShiftLeft": "SelectToStartOfTextToggle",
|
||||
"ShiftHome": "SelectToStartOfTextToggle",
|
||||
"CtrlShiftRight": "SelectToEndOfLine",
|
||||
"ShiftEnd": "SelectToEndOfLine",
|
||||
"CtrlUp": "CursorStart",
|
||||
"CtrlDown": "CursorEnd",
|
||||
"CtrlShiftUp": "SelectToStart",
|
||||
"CtrlShiftDown": "SelectToEnd",
|
||||
"Enter": "ExecuteCommand",
|
||||
"CtrlH": "Backspace",
|
||||
"Backspace": "Backspace",
|
||||
"OldBackspace": "Backspace",
|
||||
"Alt-CtrlH": "DeleteWordLeft",
|
||||
"Alt-Backspace": "DeleteWordLeft",
|
||||
"Tab": "CommandComplete",
|
||||
"Backtab": "CycleAutocompleteBack",
|
||||
"Ctrl-z": "Undo",
|
||||
"Ctrl-y": "Redo",
|
||||
"Ctrl-c": "CopyLine|Copy",
|
||||
"Ctrl-x": "Cut",
|
||||
"Ctrl-k": "CutLine",
|
||||
"Ctrl-v": "Paste",
|
||||
"Home": "StartOfTextToggle",
|
||||
"End": "EndOfLine",
|
||||
"CtrlHome": "CursorStart",
|
||||
"CtrlEnd": "CursorEnd",
|
||||
"Delete": "Delete",
|
||||
"Ctrl-q": "AbortCommand",
|
||||
"Ctrl-e": "EndOfLine",
|
||||
"Ctrl-a": "StartOfLine",
|
||||
"Ctrl-w": "DeleteWordLeft",
|
||||
"Insert": "ToggleOverwriteMode",
|
||||
"Ctrl-b": "WordLeft",
|
||||
"Ctrl-f": "WordRight",
|
||||
"Ctrl-d": "DeleteWordLeft",
|
||||
"Ctrl-m": "ExecuteCommand",
|
||||
"Ctrl-n": "HistoryDown",
|
||||
"Ctrl-p": "HistoryUp",
|
||||
"Ctrl-u": "SelectToStart",
|
||||
|
||||
// Emacs-style keybindings
|
||||
"Alt-f": "WordRight",
|
||||
"Alt-b": "WordLeft",
|
||||
"Alt-a": "StartOfText",
|
||||
"Alt-e": "EndOfLine",
|
||||
|
||||
// Integration with file managers
|
||||
"F10": "AbortCommand",
|
||||
"Esc": "AbortCommand",
|
||||
|
||||
// Mouse bindings
|
||||
"MouseWheelUp": "HistoryUp",
|
||||
"MouseWheelDown": "HistoryDown",
|
||||
"MouseLeft": "MousePress",
|
||||
"MouseMiddle": "PastePrimary"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Final notes
|
||||
|
||||
Note: On some old terminal emulators and on Windows machines, `Ctrl-h` should be
|
||||
Note: On some old terminal emulators and on Windows machines, `CtrlH` should be
|
||||
used for backspace.
|
||||
|
||||
Additionally, alt keys can be bound by using `Alt-key`. For example `Alt-a` or
|
||||
|
||||
@@ -37,45 +37,21 @@ Here are the available options:
|
||||
closed cleanly. In the case of a system crash or a micro crash, the contents
|
||||
of the buffer can be recovered automatically by opening the file that was
|
||||
being edited before the crash, or manually by searching for the backup in
|
||||
the backup directory. Backups are made in the background for newly modified
|
||||
buffers every 8 seconds, or when micro detects a crash.
|
||||
the backup directory. Backups are made in the background when a buffer is
|
||||
modified and the latest backup is more than 8 seconds old, or when micro
|
||||
detects a crash. It is highly recommended that you leave this feature
|
||||
enabled.
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `backupdir`: the directory micro should place backups in. For the default
|
||||
value of `""` (empty string), the backup directory will be
|
||||
`ConfigDir/backups`, which is `~/.config/micro/backups` by default. The
|
||||
directory specified for backups will be created if it does not exist.
|
||||
|
||||
default value: `""` (empty string)
|
||||
|
||||
* `basename`: in the infobar and tabbar, show only the basename of the file
|
||||
being edited rather than the full path.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `clipboard`: specifies how micro should access the system clipboard.
|
||||
Possible values are:
|
||||
* `external`: accesses clipboard via an external tool, such as xclip/xsel
|
||||
or wl-clipboard on Linux, pbcopy/pbpaste on MacOS, and system calls on
|
||||
Windows. On Linux, if you do not have one of the tools installed, or if
|
||||
they are not working, micro will throw an error and use an internal
|
||||
clipboard.
|
||||
* `terminal`: accesses the clipboard via your terminal emulator. Note that
|
||||
there is limited support among terminal emulators for this feature
|
||||
(called OSC 52). Terminals that are known to work are Kitty (enable
|
||||
reading with `clipboard_control` setting), iTerm2 (only copying),
|
||||
st, rxvt-unicode and xterm if enabled (see `> help copypaste` for
|
||||
details). Note that Gnome-terminal does not support this feature. With
|
||||
this setting, copy-paste **will** work over ssh. See `> help copypaste`
|
||||
for details.
|
||||
* `internal`: micro will use an internal clipboard.
|
||||
|
||||
default value: `external`
|
||||
|
||||
* `colorcolumn`: if this is not set to 0, it will display a column at the
|
||||
specified column. This is useful if you want column 80 to be highlighted
|
||||
special for example.
|
||||
specified column. This is useful if you want column 80 to be highlighted
|
||||
special for example.
|
||||
|
||||
default value: `0`
|
||||
|
||||
@@ -159,13 +135,9 @@ Here are the available options:
|
||||
default value: `unknown`. This will be automatically overridden depending
|
||||
on the file you open.
|
||||
|
||||
* `incsearch`: enable incremental search in "Find" prompt (matching as you type).
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `ignorecase`: perform case-insensitive searches.
|
||||
|
||||
default value: `true`
|
||||
default value: `false`
|
||||
|
||||
* `indentchar`: sets the indentation character.
|
||||
|
||||
@@ -228,20 +200,14 @@ Here are the available 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
|
||||
if you are interested in manually managing your backup files.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `pluginchannels`: list of URLs pointing to plugin channels for downloading and
|
||||
installing plugins. A plugin channel consists of a json file with links to
|
||||
plugin repos, which store information about plugin versions and download URLs.
|
||||
By default, this option points to the official plugin channel hosted on GitHub
|
||||
at https://github.com/micro-editor/plugin-channel.
|
||||
|
||||
default value: `https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json`
|
||||
default value: `https://raw.githubusercontent.com/micro-editor/plugin-channel
|
||||
/master/channel.json`
|
||||
|
||||
* `pluginrepos`: a list of links to plugin repositories.
|
||||
|
||||
@@ -365,11 +331,6 @@ Here are the available options:
|
||||
|
||||
default value: `true`
|
||||
|
||||
* `wordwrap`: wrap long lines by words, i.e. break at spaces. This option
|
||||
only does anything if `softwrap` is on.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `xterm`: micro will assume that the terminal it is running in conforms to
|
||||
`xterm-256color` regardless of what the `$TERM` variable actually contains.
|
||||
Enabling this option may cause unwanted effects if your terminal in fact
|
||||
@@ -402,84 +363,6 @@ Any option you set in the editor will be saved to the file
|
||||
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.
|
||||
|
||||
## Settings.json file
|
||||
|
||||
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.
|
||||
|
||||
```json
|
||||
{
|
||||
"autoclose": true,
|
||||
"autoindent": true,
|
||||
"autosave": 0,
|
||||
"autosu": false,
|
||||
"backup": true,
|
||||
"backupdir": "",
|
||||
"basename": false,
|
||||
"clipboard": "external",
|
||||
"colorcolumn": 0,
|
||||
"colorscheme": "default",
|
||||
"comment": true,
|
||||
"cursorline": true,
|
||||
"diff": true,
|
||||
"diffgutter": false,
|
||||
"divchars": "|-",
|
||||
"divreverse": true,
|
||||
"encoding": "utf-8",
|
||||
"eofnewline": true,
|
||||
"fastdirty": false,
|
||||
"fileformat": "unix",
|
||||
"filetype": "unknown",
|
||||
"incsearch": true,
|
||||
"ftoptions": true,
|
||||
"ignorecase": false,
|
||||
"indentchar": " ",
|
||||
"infobar": true,
|
||||
"initlua": true,
|
||||
"keepautoindent": false,
|
||||
"keymenu": false,
|
||||
"linter": true,
|
||||
"literate": true,
|
||||
"matchbrace": true,
|
||||
"mkparents": false,
|
||||
"mouse": true,
|
||||
"parsecursor": false,
|
||||
"paste": false,
|
||||
"permbackup": false,
|
||||
"pluginchannels": [
|
||||
"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"
|
||||
],
|
||||
"pluginrepos": [],
|
||||
"readonly": false,
|
||||
"relativeruler": false,
|
||||
"rmtrailingws": false,
|
||||
"ruler": true,
|
||||
"savecursor": false,
|
||||
"savehistory": true,
|
||||
"saveundo": false,
|
||||
"scrollbar": false,
|
||||
"scrollmargin": 3,
|
||||
"scrollspeed": 2,
|
||||
"smartpaste": true,
|
||||
"softwrap": false,
|
||||
"splitbottom": true,
|
||||
"splitright": true,
|
||||
"status": true,
|
||||
"statusformatl": "$(filename) $(modified)($(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,
|
||||
"tabsize": 4,
|
||||
"tabstospaces": false,
|
||||
"useprimary": true,
|
||||
"xterm": false
|
||||
}
|
||||
```
|
||||
|
||||
## Global and local settings
|
||||
|
||||
You can set these settings either globally or locally. Locally means that the
|
||||
|
||||
@@ -43,12 +43,6 @@ are called when certain events happen. Here is the list of callbacks
|
||||
which micro defines:
|
||||
|
||||
* `init()`: this function should be used for your plugin initialization.
|
||||
This function is called after buffers have been initialized.
|
||||
|
||||
* `preinit()`: initialization function called before buffers have been
|
||||
initialized.
|
||||
|
||||
* `postinit()`: initialization function called after `init()`.
|
||||
|
||||
* `onBufferOpen(buf)`: runs when a buffer is opened. The input contains
|
||||
the buffer object.
|
||||
@@ -259,7 +253,6 @@ The packages and functions are listed below (in Go type signatures):
|
||||
- `MTError` error message.
|
||||
|
||||
- `Loc(x, y int) Loc`: creates a new location struct.
|
||||
- `SLoc(line, row int) display.SLoc`: creates a new scrolling location struct.
|
||||
|
||||
- `BTDefault`: default buffer type.
|
||||
- `BTLog`: log buffer type.
|
||||
@@ -286,7 +279,6 @@ The packages and functions are listed below (in Go type signatures):
|
||||
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.
|
||||
|
||||
This may seem like a small list of available functions but some of the objects
|
||||
returned by the functions have many methods. The Lua plugin may access any
|
||||
@@ -360,8 +352,6 @@ strings
|
||||
regexp
|
||||
errors
|
||||
time
|
||||
archive/zip
|
||||
net/http
|
||||
```
|
||||
|
||||
For documentation for each of these functions, see the Go standard
|
||||
@@ -369,12 +359,6 @@ library documentation at https://golang.org/pkg/ (for the packages
|
||||
exposed to micro plugins). The Lua standard library is also available
|
||||
to plugins though it is rather small.
|
||||
|
||||
The following functions are also available from the go-humanize package:
|
||||
|
||||
The `humanize` package exposes:
|
||||
* `Bytes`
|
||||
* `Ordinal`
|
||||
|
||||
## Adding help files, syntax files, or colorschemes in your plugin
|
||||
|
||||
You can use the `AddRuntimeFile(name string, type config.RTFiletype,
|
||||
@@ -422,7 +406,7 @@ your own plugins.
|
||||
Micro also has a built in plugin manager which you can invoke with the
|
||||
`> plugin ...` command, or in the shell with `micro -plugin ...`.
|
||||
|
||||
For the valid commands you can use, see the `commands` help topic.
|
||||
For the valid commands you can use, see the `command` help topic.
|
||||
|
||||
The manager fetches plugins from the channels (which is simply a list of plugin
|
||||
metadata) which it knows about. By default, micro only knows about the official
|
||||
|
||||
@@ -16,8 +16,8 @@ the settings and their values. To change an option, you can either change the
|
||||
value in the `settings.json` file, or you can type it in directly while using
|
||||
micro.
|
||||
|
||||
Press Ctrl-e to go to command mode, and type `set option value` (in the
|
||||
future, I will use `> set option value` to indicate pressing Ctrl-e). The change
|
||||
Press CtrlE to go to command mode, and type `set option value` (in the
|
||||
future, I will use `> set option value` to indicate pressing CtrlE). The change
|
||||
will take effect immediately and will also be saved to the `settings.json` file
|
||||
so that the setting will stick even after you close micro.
|
||||
|
||||
@@ -48,12 +48,12 @@ If you would like to know more about all the available options, see the
|
||||
Keybindings work in much the same way as options. You configure them using the
|
||||
`~/.config/micro/bindings.json` file.
|
||||
|
||||
For example if you would like to bind `Ctrl-r` to redo you could put the
|
||||
For example if you would like to bind `CtrlR` to redo you could put the
|
||||
following in `bindings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"Ctrl-r": "redo"
|
||||
"CtrlR": "redo"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -74,7 +74,7 @@ micro starts and is essentially a one-file plugin. The plugin name is
|
||||
`initlua`.
|
||||
|
||||
This example will show you how to use the `init.lua` file by creating a binding
|
||||
to `Ctrl-r` which will execute the bash command `go run` on the current file,
|
||||
to `CtrlR` which will execute the bash command `go run` on the current file,
|
||||
given that the current file is a Go file.
|
||||
|
||||
You can do that by putting the following in `init.lua`:
|
||||
@@ -84,9 +84,9 @@ local config = import("micro/config")
|
||||
local shell = import("micro/shell")
|
||||
|
||||
function init()
|
||||
-- true means overwrite any existing binding to Ctrl-r
|
||||
-- true means overwrite any existing binding to CtrlR
|
||||
-- this will modify the bindings.json file
|
||||
config.TryBindKey("Ctrl-r", "lua:initlua.gorun", true)
|
||||
config.TryBindKey("CtrlR", "lua:initlua.gorun", true)
|
||||
end
|
||||
|
||||
function gorun(bp)
|
||||
@@ -104,7 +104,7 @@ the `bindings.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"Ctrl-r": "lua:initlua.gorun"
|
||||
"CtrlR": "lua:initlua.gorun"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ ft["markdown"] = "<!-- %s -->"
|
||||
ft["nginx"] = "# %s"
|
||||
ft["nim"] = "# %s"
|
||||
ft["objc"] = "// %s"
|
||||
ft["ocaml"] = "(* %s *)"
|
||||
ft["pascal"] = "{ %s }"
|
||||
ft["perl"] = "# %s"
|
||||
ft["php"] = "// %s"
|
||||
@@ -70,38 +69,10 @@ function onBufferOpen(buf)
|
||||
end
|
||||
end
|
||||
|
||||
function isCommented(bp, lineN, commentRegex)
|
||||
local line = bp.Buf:Line(lineN)
|
||||
if string.match(line, commentRegex) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function commentLine(bp, lineN)
|
||||
local line = bp.Buf:Line(lineN)
|
||||
local commentType = bp.Buf.Settings["commenttype"]
|
||||
local sel = -bp.Cursor.CurSelection
|
||||
local curpos = -bp.Cursor.Loc
|
||||
local index = string.find(commentType, "%%s") - 1
|
||||
local commentedLine = commentType:gsub("%%s", trim(line))
|
||||
bp.Buf:Replace(buffer.Loc(0, lineN), buffer.Loc(#line, lineN), util.GetLeadingWhitespace(line) .. commentedLine)
|
||||
if bp.Cursor:HasSelection() then
|
||||
bp.Cursor.CurSelection[1].Y = sel[1].Y
|
||||
bp.Cursor.CurSelection[2].Y = sel[2].Y
|
||||
bp.Cursor.CurSelection[1].X = sel[1].X
|
||||
bp.Cursor.CurSelection[2].X = sel[2].X
|
||||
else
|
||||
bp.Cursor.X = curpos.X + index
|
||||
bp.Cursor.Y = curpos.Y
|
||||
end
|
||||
bp.Cursor:Relocate()
|
||||
bp.Cursor.LastVisualX = bp.Cursor:GetVisualX()
|
||||
end
|
||||
|
||||
function uncommentLine(bp, lineN, commentRegex)
|
||||
local line = bp.Buf:Line(lineN)
|
||||
local commentType = bp.Buf.Settings["commenttype"]
|
||||
local commentRegex = "^%s*" .. commentType:gsub("%%","%%%%"):gsub("%$","%$"):gsub("%)","%)"):gsub("%(","%("):gsub("%?","%?"):gsub("%*", "%*"):gsub("%-", "%-"):gsub("%.", "%."):gsub("%+", "%+"):gsub("%]", "%]"):gsub("%[", "%["):gsub("%%%%s", "(.*)")
|
||||
local sel = -bp.Cursor.CurSelection
|
||||
local curpos = -bp.Cursor.Loc
|
||||
local index = string.find(commentType, "%%s") - 1
|
||||
@@ -117,57 +88,46 @@ function uncommentLine(bp, lineN, commentRegex)
|
||||
bp.Cursor.X = curpos.X - index
|
||||
bp.Cursor.Y = curpos.Y
|
||||
end
|
||||
else
|
||||
local commentedLine = commentType:gsub("%%s", trim(line))
|
||||
bp.Buf:Replace(buffer.Loc(0, lineN), buffer.Loc(#line, lineN), util.GetLeadingWhitespace(line) .. commentedLine)
|
||||
if bp.Cursor:HasSelection() then
|
||||
bp.Cursor.CurSelection[1].Y = sel[1].Y
|
||||
bp.Cursor.CurSelection[2].Y = sel[2].Y
|
||||
bp.Cursor.CurSelection[1].X = sel[1].X
|
||||
bp.Cursor.CurSelection[2].X = sel[2].X
|
||||
else
|
||||
bp.Cursor.X = curpos.X + index
|
||||
bp.Cursor.Y = curpos.Y
|
||||
end
|
||||
end
|
||||
bp.Cursor:Relocate()
|
||||
bp.Cursor.LastVisualX = bp.Cursor:GetVisualX()
|
||||
end
|
||||
|
||||
function toggleCommentLine(bp, lineN, commentRegex)
|
||||
if isCommented(bp, lineN, commentRegex) then
|
||||
uncommentLine(bp, lineN, commentRegex)
|
||||
else
|
||||
commentLine(bp, lineN)
|
||||
end
|
||||
end
|
||||
|
||||
function toggleCommentSelection(bp, startLine, endLine, commentRegex)
|
||||
local allComments = true
|
||||
function commentSelection(bp, startLine, endLine)
|
||||
for line = startLine, endLine do
|
||||
if not isCommented(bp, line, commentRegex) then
|
||||
allComments = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
for line = startLine, endLine do
|
||||
if allComments then
|
||||
uncommentLine(bp, line, commentRegex)
|
||||
else
|
||||
commentLine(bp, line)
|
||||
end
|
||||
commentLine(bp, line)
|
||||
end
|
||||
end
|
||||
|
||||
function comment(bp, args)
|
||||
local commentType = bp.Buf.Settings["commenttype"]
|
||||
local commentRegex = "^%s*" .. commentType:gsub("%%","%%%%"):gsub("%$","%$"):gsub("%)","%)"):gsub("%(","%("):gsub("%?","%?"):gsub("%*", "%*"):gsub("%-", "%-"):gsub("%.", "%."):gsub("%+", "%+"):gsub("%]", "%]"):gsub("%[", "%["):gsub("%%%%s", "(.*)"):gsub("%s+", "%s*")
|
||||
|
||||
if bp.Cursor:HasSelection() then
|
||||
if bp.Cursor.CurSelection[1]:GreaterThan(-bp.Cursor.CurSelection[2]) then
|
||||
local endLine = bp.Cursor.CurSelection[1].Y
|
||||
if bp.Cursor.CurSelection[1].X == 0 then
|
||||
endLine = endLine - 1
|
||||
end
|
||||
toggleCommentSelection(bp, bp.Cursor.CurSelection[2].Y, endLine, commentRegex)
|
||||
commentSelection(bp, bp.Cursor.CurSelection[2].Y, endLine)
|
||||
else
|
||||
local endLine = bp.Cursor.CurSelection[2].Y
|
||||
if bp.Cursor.CurSelection[2].X == 0 then
|
||||
endLine = endLine - 1
|
||||
end
|
||||
toggleCommentSelection(bp, bp.Cursor.CurSelection[1].Y, endLine, commentRegex)
|
||||
commentSelection(bp, bp.Cursor.CurSelection[1].Y, endLine)
|
||||
end
|
||||
else
|
||||
toggleCommentLine(bp, bp.Cursor.Y, commentRegex)
|
||||
commentLine(bp, bp.Cursor.Y)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -183,6 +143,5 @@ end
|
||||
function init()
|
||||
config.MakeCommand("comment", comment, config.NoComplete)
|
||||
config.TryBindKey("Alt-/", "lua:comment.comment", false)
|
||||
config.TryBindKey("CtrlUnderscore", "lua:comment.comment", false)
|
||||
config.AddRuntimeFile("comment", config.RTHelp, "help/comment.md")
|
||||
end
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
# Comment Plugin
|
||||
|
||||
The comment plugin provides auto commenting/uncommenting.
|
||||
The default binding to comment/uncomment a line is `Alt-/`
|
||||
and `CtrlUnderscore`, which is equivalent in most terminals
|
||||
to `Ctrl-/`. You can easily modify that in your `bindings.json`
|
||||
file:
|
||||
The default binding to comment/uncomment a line is `Alt-/`,
|
||||
but you can easily modify that in your `bindings.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -9,10 +9,8 @@ following filetypes and linters:
|
||||
* c++: g++
|
||||
* d: dmd
|
||||
* go: go build
|
||||
* haskell: hlint
|
||||
* java: javac
|
||||
* javascript: jshint
|
||||
* javascript: eslint
|
||||
* literate: lit
|
||||
* lua: luacheck
|
||||
* nim: nim
|
||||
@@ -21,7 +19,7 @@ following filetypes and linters:
|
||||
* python: mypy
|
||||
* python: pylint
|
||||
* shell: shfmt
|
||||
* swift: swiftc (MacOS and Linux only)
|
||||
* swift: swiftc
|
||||
* yaml: yamllint
|
||||
|
||||
If the linter plugin is enabled and the file corresponds to one of
|
||||
@@ -66,17 +64,17 @@ Below is an example for including a linter for any filetype using
|
||||
the `misspell` linter which checks for misspelled words in a file.
|
||||
|
||||
```lua
|
||||
local config = import("micro/config")
|
||||
|
||||
config.RegisterCommonOption("misspell", true)
|
||||
|
||||
function init()
|
||||
-- uses the default linter plugin
|
||||
-- matches any filetype
|
||||
linter.makeLinter("misspell", "", "misspell", {"%f"}, "%f:%l:%c: %m", {}, false, true)
|
||||
end
|
||||
```
|
||||
|
||||
Here is an example for a more typical use-case, where the linter will only match one filetype (C in this case):
|
||||
|
||||
```lua
|
||||
function init()
|
||||
linter.makeLinter("gcc", "c", "gcc", {"-fsyntax-only", "-Wall", "-Wextra", "%f"}, "%f:%l:%c:.+: %m")
|
||||
linter.makeLinter("misspell", "", "misspell", {"%f"}, "%f:%l:%c: %m", {}, false, true, 0, 0, hasMisspell)
|
||||
end
|
||||
|
||||
function hasMisspell(buf)
|
||||
return buf.Settings["misspell"]
|
||||
end
|
||||
```
|
||||
|
||||
@@ -59,7 +59,7 @@ function removeLinter(name)
|
||||
linters[name] = nil
|
||||
end
|
||||
|
||||
function preinit()
|
||||
function init()
|
||||
local devnull = "/dev/null"
|
||||
if runtime.GOOS == "windows" then
|
||||
devnull = "NUL"
|
||||
@@ -68,10 +68,9 @@ function preinit()
|
||||
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("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")
|
||||
-- makeLinter("golint", "go", "golint", {"%f"}, "%f:%l:%c: %m")
|
||||
makeLinter("hlint", "haskell", "hlint", {"%f"}, "%f:%(?%l[,:]%c%)?.-: %m")
|
||||
makeLinter("hlint", "haskell", "hlint", {"%f"}, "%f:%l:%c.-: %m")
|
||||
makeLinter("javac", "java", "javac", {"-d", "%d", "%f"}, "%f:%l: error: %m")
|
||||
makeLinter("jshint", "javascript", "jshint", {"%f"}, "%f: line %l,.+, %m")
|
||||
makeLinter("literate", "literate", "lit", {"-c", "%f"}, "%f:%l:%m", {}, false, true)
|
||||
@@ -81,10 +80,9 @@ 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("flake8", "python", "flake8", {"%f"}, "%f:%l:%c: %m")
|
||||
makeLinter("shfmt", "shell", "shfmt", {"%f"}, "%f:%l:%c: %m")
|
||||
makeLinter("swiftc", "swift", "xcrun", {"swiftc", "%f"}, "%f:%l:%c:.+: %m", {"darwin"}, true)
|
||||
makeLinter("swiftc", "swift", "swiftc", {"%f"}, "%f:%l:%c:.+: %m", {"linux"}, true)
|
||||
makeLinter("swiftc", "swiftc", {"%f"}, "%f:%l:%c:.+: %m", {"linux"}, true)
|
||||
makeLinter("yaml", "yaml", "yamllint", {"--format", "parsable", "%f"}, "%f:%l:%c:.+ %m")
|
||||
|
||||
config.MakeCommand("lint", function(bp, args)
|
||||
@@ -168,6 +166,7 @@ function onExit(output, args)
|
||||
elseif col == nil then
|
||||
hascol = false
|
||||
end
|
||||
micro.Log(basename(buf.Path), basename(file))
|
||||
if basename(buf.Path) == basename(file) then
|
||||
local bmsg = nil
|
||||
if hascol then
|
||||
|
||||
@@ -6,14 +6,10 @@ Using the `statusformatl` and `statusformatr` options, the exact contents
|
||||
of the status line can be modified. Please see the documentation for
|
||||
those options (`> help options`) for more information.
|
||||
|
||||
This plugin provides functions that can be used in the status line format:
|
||||
This plugin provides the three 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.paste`: returns "" if the paste option is disabled and "PASTE"
|
||||
if it is enabled.
|
||||
* `status.lines`: returns the number of lines in the buffer.
|
||||
* `status.vcol`: returns the visual column number of the cursor.
|
||||
* `status.bytes`: returns the number of bytes in the current buffer.
|
||||
* `status.size`: returns the size of the current buffer in a human-readable
|
||||
format.
|
||||
|
||||
@@ -3,35 +3,14 @@ VERSION = "1.0.0"
|
||||
local micro = import("micro")
|
||||
local buffer = import("micro/buffer")
|
||||
local config = import("micro/config")
|
||||
local humanize = import("humanize")
|
||||
|
||||
function init()
|
||||
micro.SetStatusInfoFn("status.branch")
|
||||
micro.SetStatusInfoFn("status.hash")
|
||||
micro.SetStatusInfoFn("status.paste")
|
||||
micro.SetStatusInfoFn("status.vcol")
|
||||
micro.SetStatusInfoFn("status.lines")
|
||||
micro.SetStatusInfoFn("status.bytes")
|
||||
micro.SetStatusInfoFn("status.size")
|
||||
config.AddRuntimeFile("status", config.RTHelp, "help/status.md")
|
||||
end
|
||||
|
||||
function lines(b)
|
||||
return tostring(b:LinesNum())
|
||||
end
|
||||
|
||||
function vcol(b)
|
||||
return tostring(b:GetActiveCursor():GetVisualX())
|
||||
end
|
||||
|
||||
function bytes(b)
|
||||
return tostring(b:Size())
|
||||
end
|
||||
|
||||
function size(b)
|
||||
return humanize.Bytes(b:Size())
|
||||
end
|
||||
|
||||
function branch(b)
|
||||
if b.Type.Kind ~= buffer.BTInfo then
|
||||
local shell = import("micro/shell")
|
||||
|
||||
@@ -5,25 +5,23 @@ detect:
|
||||
|
||||
rules:
|
||||
- identifier: "\\b[A-Z_][0-9A-Z_]+\\b"
|
||||
- type: "\\b(auto|float|double|char|int|short|long|sizeof|enum|void|static|const|struct|union|typedef|extern|(un)?signed|inline)\\b"
|
||||
- type: "\\b(float|double|char|int|short|long|sizeof|enum|void|static|const|struct|union|typedef|extern|(un)?signed|inline)\\b"
|
||||
- type: "\\b((s?size)|((u_?)?int(8|16|32|64|ptr)))_t\\b"
|
||||
- type.extended: "\\b(bool)\\b"
|
||||
- statement: "\\b(volatile|register)\\b"
|
||||
- statement: "\\b(for|if|while|do|else|case|default|switch)\\b"
|
||||
- statement: "\\b(goto|continue|break|return)\\b"
|
||||
- preproc: "^[[:space:]]*#[[:space:]]*(define|pragma|include|(un|ifn?)def|endif|el(if|se)|if|warning|error)"
|
||||
- constant: "'([^'\\\\]|(\\\\[\"'abfnrtv\\\\]))'"
|
||||
- constant: "'\\\\(([0-3]?[0-7]{1,2}))'"
|
||||
- constant: "'\\\\x[0-9A-Fa-f]{1,2}'"
|
||||
# GCC builtins
|
||||
- statement: "__attribute__[[:space:]]*\\(\\([^)]*\\)\\)"
|
||||
- statement: "__(aligned|asm|builtin|hidden|inline|packed|restrict|section|typeof|weak)__"
|
||||
# Operator Color
|
||||
- symbol.operator: "([.:;,+*|=!\\%]|<|>|/|-|&)"
|
||||
- symbol.brackets: "[(){}]|\\[|\\]"
|
||||
# Integer Constants
|
||||
- constant.number: "(\\b([1-9][0-9]*|0[0-7]*|0[Xx][0-9A-Fa-f]+|0[Bb][01]+)([Uu]?[Ll][Ll]?|[Ll][Ll]?[Uu]?)?\\b)"
|
||||
# Decimal Floating Constants
|
||||
- constant.number: "(\\b(([0-9]*[.][0-9]+|[0-9]+[.][0-9]*)([Ee][+-]?[0-9]+)?|[0-9]+[Ee][+-]?[0-9]+)[FfLl]?\\b)"
|
||||
# Hexadecimal Floating Constants
|
||||
- constant.number: "(\\b0[Xx]([0-9A-Za-z]*[.][0-9A-Za-z]+|[0-9A-Za-z]+[.][0-9A-Za-z]*)[Pp][+-]?[0-9]+[FfLl]?\\b)"
|
||||
- constant.number: "(\\b[0-9]+\\b|\\b0x[0-9A-Fa-f]+\\b)"
|
||||
- constant.number: "NULL"
|
||||
|
||||
- constant.string:
|
||||
@@ -31,15 +29,15 @@ rules:
|
||||
end: "\""
|
||||
skip: "\\\\."
|
||||
rules:
|
||||
- constant.specialChar: "\\\\([\"'abfnrtv\\\\]|[0-3]?[0-7]{1,2}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})"
|
||||
- constant.specialChar: "\\\\."
|
||||
|
||||
- constant.string:
|
||||
start: "'"
|
||||
end: "'"
|
||||
skip: "\\\\."
|
||||
rules:
|
||||
- error: "..+"
|
||||
- constant.specialChar: "\\\\([\"'abfnrtv\\\\]|[0-3]?[0-7]{1,2}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})"
|
||||
- preproc: "..+"
|
||||
- constant.specialChar: "\\\\."
|
||||
|
||||
- comment:
|
||||
start: "//"
|
||||
|
||||
@@ -4,32 +4,19 @@ detect:
|
||||
filename: "\\.coffee$"
|
||||
|
||||
rules:
|
||||
- symbol.operator: "([-+/*=<>!~%?:&|]|[.]{3})|\\b(and|or|is|isnt|not)\\b"
|
||||
- symbol.operator: "[!&|=/*+-<>]|\\b(and|or|is|isnt|not)\\b"
|
||||
- identifier.class: "([A-Za-z_][A-Za-z0-9_]*:[[:space:]]*(->|\\()|->)"
|
||||
- symbol.brackets: "[()]"
|
||||
|
||||
- statement: "\\b(await|when|catch|continue|debugger|default|by|until)\\b"
|
||||
- statement: "\\b(delete|do|else|export|finally|for|class|extends|while|then)\\b"
|
||||
- statement: "\\b(get|if|import|from|in|instanceof|new|reject|resolve|return)\\b"
|
||||
- statement: "\\b(set|super|switch|this|throw|try|typeof|with|yield|unless)\\b"
|
||||
|
||||
- statement: "\\b(for|of|continue|break|isnt|null|unless|this|else|if|return)\\b"
|
||||
- statement: "\\b(try|catch|finally|throw|new|delete|typeof|in|instanceof)\\b"
|
||||
- statement: "\\b(debugger|switch|while|do|class|extends|super)\\b"
|
||||
- statement: "\\b(undefined|then|unless|until|loop|of|by|when)\\b"
|
||||
- constant.bool: "\\b(true|false|yes|no|on|off)\\b"
|
||||
- constant.bool.false: "\\b(false|no|off)\\b"
|
||||
- constant.bool.true: "\\b(true|yes|on)\\b"
|
||||
|
||||
- constant.number: "\\b[-+]?([1-9][0-9]*|0[0-7]*|0x[0-9a-fA-F]+)([uU][lL]?|[lL][uU]?)?\\b"
|
||||
- constant.number: "\\b[-+]?([0-9]+\\.[0-9]*|[0-9]*\\.[0-9]+)([EePp][+-]?[0-9]+)?[fFlL]?"
|
||||
- constant.number: "\\b[-+]?([0-9]+[EePp][+-]?[0-9]+)[fFlL]?"
|
||||
- identifier: "@[A-Za-z0-9_]*"
|
||||
|
||||
- error: "\\b(enum|implements|interface|package|private|protected|public)"
|
||||
- constant: "\\b(globalThis|Infinity|null|undefined|NaN)\\b"
|
||||
- constant: "\\b(null|undefined|NaN)\\b"
|
||||
- constant: "\\b(true|false|yes|no|on|off)\\b"
|
||||
- type: "\\b(Array|Boolean|Date|Enumerator|Error|Function|Generator|Map|Math)\\b"
|
||||
- type: "\\b(Number|Object|Promise|Proxy|Reflect|RegExp|Set|String|Symbol|WeakMap|WeakSet)\\b"
|
||||
- type: "\\b(BigInt64Array|BigUint64Array|Float32Array|Float64Array|Int16Array)\\b"
|
||||
|
||||
- constant.string:
|
||||
start: "\""
|
||||
end: "\""
|
||||
@@ -43,14 +30,16 @@ rules:
|
||||
skip: "\\\\."
|
||||
rules:
|
||||
- constant.specialChar: "\\\\."
|
||||
|
||||
- comment:
|
||||
start: "###"
|
||||
end: "###"
|
||||
rules: []
|
||||
|
||||
|
||||
- comment:
|
||||
start: "#"
|
||||
end: "$"
|
||||
rules:
|
||||
- todo: "(TODO|XXX|FIXME):?"
|
||||
- comment:
|
||||
start: "###"
|
||||
end: "###"
|
||||
rules:
|
||||
- todo: "(TODO|XXX|FIXME)"
|
||||
|
||||
|
||||
17
runtime/syntax/conf.yaml
Normal file
17
runtime/syntax/conf.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
filetype: conf
|
||||
|
||||
detect:
|
||||
filename: "\\.c[o]?nf$"
|
||||
|
||||
rules:
|
||||
- constant.string:
|
||||
start: "\""
|
||||
end: "\""
|
||||
skip: "\\\\."
|
||||
rules: []
|
||||
|
||||
- comment:
|
||||
start: "#"
|
||||
end: "$"
|
||||
rules: []
|
||||
|
||||
@@ -4,51 +4,42 @@ detect:
|
||||
filename: "(\\.c(c|pp|xx)$|\\.h(h|pp|xx)$|\\.ii?$|\\.(def)$)"
|
||||
|
||||
rules:
|
||||
- identifier: "\\b[A-Z_][0-9A-Z_]*\\b"
|
||||
- type: "\\b(float|double|bool|char|int|short|long|enum|void|struct|union|typedef|(un)?signed|inline)\\b"
|
||||
- type: "\\b(((s?size)|((u_?)?int(8|16|32|64|ptr))|char(8|16|32))_t|wchar_t)\\b"
|
||||
- type: "\\b(final|override)\\b"
|
||||
- type.keyword: "\\b(auto|volatile|const(expr|eval|init)?|mutable|register|thread_local|static|extern|decltype|explicit|virtual)\\b"
|
||||
- statement: "\\b(class|namespace|template|typename|this|friend|using|public|protected|private|noexcept)\\b"
|
||||
- statement: "\\b(concept|requires)\\b"
|
||||
- statement: "\\b(import|export|module)\\b"
|
||||
- statement: "\\b(for|if|while|do|else|case|default|switch)\\b"
|
||||
- statement: "\\b(try|throw|catch|operator|new|delete|static_assert)\\b"
|
||||
- statement: "\\b(goto|continue|break|return)\\b"
|
||||
- preproc: "^[[:space:]]*#[[:space:]]*(define|pragma|include|(un|ifn?)def|endif|el(if|se)|if|warning|error)|_Pragma"
|
||||
|
||||
# Conditionally-supported/extension keywords
|
||||
- statement: "\\b(asm|fortran)\\b"
|
||||
- identifier: "\\b[A-Z_][0-9A-Z_]+\\b"
|
||||
- type: "\\b(auto|float|double|bool|char|int|short|long|sizeof|enum|void|static|const|constexpr|struct|union|typedef|extern|(un)?signed|inline)\\b"
|
||||
- type: "\\b((s?size)|((u_?)?int(8|16|32|64|ptr)))_t\\b"
|
||||
- statement: "\\b(class|namespace|template|public|protected|private|typename|this|friend|virtual|using|mutable|volatile|register|explicit)\\b"
|
||||
- statement: "\\b(for|if|while|do|else|case|default|switch)\\b"
|
||||
- statement: "\\b(try|throw|catch|operator|new|delete)\\b"
|
||||
- statement: "\\b(goto|continue|break|return)\\b"
|
||||
- preproc: "^[[:space:]]*#[[:space:]]*(define|pragma|include|(un|ifn?)def|endif|el(if|se)|if|warning|error)"
|
||||
- constant: "('([^'\\\\]|(\\\\[\"'abfnrtv\\\\]))'|'\\\\(([0-3]?[0-7]{1,2}))'|'\\\\x[0-9A-Fa-f]{1,2}')"
|
||||
|
||||
# GCC builtins
|
||||
- statement: "(__attribute__[[:space:]]*\\(\\([^)]*\\)\\)|__(aligned|asm|builtin|hidden|inline|packed|restrict|section|typeof|weak)__)"
|
||||
|
||||
# Operator Color
|
||||
- symbol.operator: "[-+*/%=<>.:;,~&|^!?]|\\b(sizeof|alignof|typeid|(and|or|xor|not)(_eq)?|bitor|compl|bitand|(const|dynamic|reinterpret|static)_cast)\\b"
|
||||
- symbol.operator: "([.:;,+*|=!\\%]|<|>|/|-|&)"
|
||||
# Parenthetical Color
|
||||
- symbol.brackets: "[(){}]|\\[|\\]"
|
||||
# Integer Literals
|
||||
- constant.number: "(\\b([1-9][0-9']*|0[0-7']*|0[Xx][0-9a-fA-F']+|0[Bb][01]+)([Uu]?[Ll][Ll]?|[Ll][Ll]?[Uu]?)?\\b)"
|
||||
# Decimal Floating-point Literals
|
||||
- constant.number: "(\\b(([0-9']*[.][0-9']+|[0-9']+[.][0-9']*)([Ee][+-]?[0-9']+)?|[0-9']+[Ee][+-]?[0-9']+)[FfLl]?\\b)"
|
||||
# Hexadecimal Floating-point Literals
|
||||
- constant.number: "(\\b0[Xx]([0-9a-zA-Z']*[.][0-9a-zA-Z']+|[0-9a-zA-Z']+[.][0-9a-zA-Z']*)[Pp][+-]?[0-9']+[FfLl]?\\b)"
|
||||
- constant.bool: "(\\b(true|false|NULL|nullptr)\\b)"
|
||||
|
||||
- constant.number: "(\\b[0-9]+\\b|\\b0x[0-9A-Fa-f]+\\b)"
|
||||
- constant.bool: "(\\b(true|false)\\b|NULL)"
|
||||
|
||||
- constant.string:
|
||||
start: "\""
|
||||
end: "\""
|
||||
skip: "\\\\."
|
||||
rules:
|
||||
- constant.specialChar: "\\\\([\"'abfnrtv\\\\]|[0-3]?[0-7]{1,2}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})"
|
||||
- constant.specialChar: "\\\\."
|
||||
|
||||
- constant.string:
|
||||
start: "'"
|
||||
end: "'"
|
||||
skip: "\\\\."
|
||||
rules:
|
||||
- error: "..+"
|
||||
- constant.specialChar: "\\\\([\"'abfnrtv\\\\]|[0-3]?[0-7]{1,2}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})"
|
||||
- preproc: "..+"
|
||||
- constant.specialChar: "\\\\."
|
||||
|
||||
- comment:
|
||||
start: "//"
|
||||
|
||||
@@ -14,16 +14,16 @@ rules:
|
||||
# month 0-12 (or names, see below)
|
||||
# day of week 0-7 (0 or 7 is Sun, or use names)
|
||||
|
||||
- statement: "^([\\*0-9,\\-\\/]+)\\s+([\\*0-9,\\-\\/]+)\\s+([\\*0-9,\\-\\/]+)\\s+(([\\*0-9,\\-\\/]+)|(\\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\\b))\\s+(([\\*0-9,\\-\\/]+)|(\\b(sun|mon|tue|wed|thu|fri|sat)\\b))\\s+(.*)$\\n?"
|
||||
- constant: "^([\\*0-9,\\-\\/]+)\\s+([\\*0-9,\\-\\/]+)\\s+([\\*0-9,\\-\\/]+)\\s+(([\\*0-9,\\-\\/]+)|(\\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\\b))\\s+(([\\*0-9,\\-\\/]+)|(\\b(sun|mon|tue|wed|thu|fri|sat)\\b))"
|
||||
- statement: "^([\\*0-9,\\-\\/]+)\\s+([\\*0-9,\\-\\/]+)\\s+([\\*0-9,\\-\\/]+)\\s+([\\*0-9,\\-\\/]+)\\s+([\\*0-9,\\-\\/]+)\\s+(.*)$\\n?"
|
||||
- constant: "^([\\*0-9,\\-\\/]+)\\s+([\\*0-9,\\-\\/]+)\\s+([\\*0-9,\\-\\/]+)\\s+([\\*0-9,\\-\\/]+)\\s+([\\*0-9,\\-\\/]+)"
|
||||
|
||||
# Shell Values
|
||||
- type: "^[A-Z]+\\="
|
||||
|
||||
# Months and weekday keywords
|
||||
- constant: "\\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\\b"
|
||||
- constant: "\\b(sun|mon|tue|wed|thu|fri|sat)\\b"
|
||||
- type: "\\@(reboot|yearly|annually|monthly|weekly|daily|midnight|hourly)\\b"
|
||||
- type: "jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec"
|
||||
- constant: "sun|mon|tue|wed|thu|fri|sat"
|
||||
- type: "\\@(reboot|yearly|annually|monthly|weekly|daily|midnight|hourly)"
|
||||
|
||||
# Conditionals
|
||||
- special: "(\\{|\\}|\\(|\\)|\\;|\\]|\\[|`|\\\\|\\$|<|>|^|!|=|&|\\|)"
|
||||
|
||||
@@ -5,14 +5,10 @@ detect:
|
||||
|
||||
rules:
|
||||
# Asciibetical list of reserved words
|
||||
- statement: "\\b(abstract|alias|as|asm|begin|break|case|class|def|do|else|elsif|end|ensure|enum|extend|for|fun|if|in|include|instance_sizeof|lib|loop|macro|module|next|of|out|pointerof|private|protected|raise|require|rescue|return|select|self|sizeof|spawn|struct|super|then|type|typeof|uninitialized|union|unless|until|verbatim|when|while|with|yield)\\b"
|
||||
- statement: "\\b(BEGIN|END|abstract|alias|and|begin|break|case|class|def|defined\\?|do|else|elsif|end|ensure|enum|false|for|fun|if|in|include|lib|loop|macro|module|next|nil|not|of|or|pointerof|private|protected|raise|redo|require|rescue|retry|return|self|sizeof|spawn|struct|super|then|true|type|undef|union|uninitialized|unless|until|when|while|yield)\\b"
|
||||
# Constants
|
||||
- constant: "\\b(true|false|nil)\\b"
|
||||
- constant: "(\\$|@|@@)?\\b[A-Z]+[0-9A-Z_a-z]*"
|
||||
- constant.number: "\\b[0-9]+\\b"
|
||||
# Ones that can't be in the same regex because they include non-words.
|
||||
# The nil? one has to be after the constants.
|
||||
- statement: "\\b(nil\\?|as(\\?|\\b)|is_a\\?|responds_to\\?)"
|
||||
- type: "(\\$|@|@@)?\\b[A-Z]+[0-9A-Z_a-z]*"
|
||||
# Crystal "symbols"
|
||||
- constant: "([ ]|^):[0-9A-Z_]+\\b"
|
||||
# Some unique things we want to stand out
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
filetype: gemini
|
||||
|
||||
detect:
|
||||
filename: "\\.(gmi|gemini)$"
|
||||
|
||||
rules:
|
||||
# link lines
|
||||
- constant: "^=>[[:space:]].*"
|
||||
# preformatted text lines
|
||||
- special:
|
||||
start: "^```"
|
||||
end: "^```"
|
||||
rules: []
|
||||
# heading lines
|
||||
- special: "^#{1,3}.*"
|
||||
# unordered list items
|
||||
- identifier: "^\\*[[:space:]]"
|
||||
# quote lines
|
||||
- statement: "^>.*"
|
||||
@@ -1,113 +0,0 @@
|
||||
filetype: groovy
|
||||
|
||||
detect:
|
||||
filename: "\\.(groovy|gy|gvy|gsh|gradle)$"
|
||||
header: "^#!.*/(env +)?groovy *$"
|
||||
|
||||
rules:
|
||||
# And the style guide for constants is CONSTANT_CASE
|
||||
- identifier: "\\b[A-Z_$]+\\b"
|
||||
# The style guide for JVM languages is PascalCase for classes and interfaces
|
||||
- identifier.class: "\\b[A-Z][a-zA-Z0-9$]+\\b"
|
||||
|
||||
# Primitive types
|
||||
- type: "\\b(byte|short|int|long|float|double|char|boolean|void)\\b"
|
||||
|
||||
# Type-related keywords
|
||||
- type.keyword: "\\b(private|public|protected|static|final|var|def)\\b"
|
||||
|
||||
# Keywords
|
||||
- statement: "\\b(for|while|do|if|else|switch|case|default|try|catch|finally)\\b"
|
||||
- statement: "\\b(break|continue|return|throw|assert)\\b"
|
||||
- statement: "\\b(package|import|class|interface|trait|enum|extends|implements|throws)\\b"
|
||||
- statement: "\\b(this|super)\\b"
|
||||
# Unsused, but reserved keywords
|
||||
- statement: "\\b(goto|const)\\b"
|
||||
|
||||
# Operators and punctuation
|
||||
- symbol.operator: "[-+*/%=<>^~&|!?:;,.@]|\\b(in|is|as|instanceof|new)\\b"
|
||||
- symbol.brackets: "[(){}]|\\[|\\]"
|
||||
|
||||
# Decimal integer literal
|
||||
- constant.number: "(?i)\\b[1-9]([_0-9]*[0-9])?[GLIDF]?\\b"
|
||||
# Binary integer literal
|
||||
- constant.number: "(?i)\\b0b[01]([01_]*[01])?[GLIDF]?\\b"
|
||||
# Octal integer literal
|
||||
- constant.number: "(?i)\\b0[0-7]([0-7_]*[0-7])?[GLIDF]?\\b"
|
||||
# Hexadecimal integer literal
|
||||
- constant.number: "(?i)\\b0x[0-9a-f]([0-9a-f_]*[0-9a-f])?[GLIDF]?\\b"
|
||||
# Floating-point literal
|
||||
- constant.number: "(?i)\\b[0-9]([0-9_]*[0-9])?([.][0-9]([0-9_]*[0-9])?)?(e[+-]?[0-9]([0-9_]*[0-9])?)?[DF]?\\b"
|
||||
- constant.bool: "\\b(true|false|null)\\b"
|
||||
|
||||
# Annotations
|
||||
- identifier: "@[A-Za-z_$][A-Za-z0-9_$]*\\b"
|
||||
|
||||
# Single-quoted strings
|
||||
- constant.string:
|
||||
start: "'"
|
||||
end: "'"
|
||||
skip: "\\\\."
|
||||
rules:
|
||||
- constant.specialChar: "\\\\([\"'bfnrst\\x24\\\\]|u[a-fA-F0-9]{4})"
|
||||
|
||||
# This also matches the Triple-double-quoted strings region, but I can't really find a way to mitigate it, all the while still matching "" as a string correctly
|
||||
# Also, nesting ${} are never going to be matched correctly with just regex either, so highlighting will break if one is to nest interpolation
|
||||
# These two problems combined mean slight mistakes in highlighing that the user is just going to have to deal with
|
||||
# Double-quoted strings
|
||||
- constant.string:
|
||||
start: "\""
|
||||
end: "\""
|
||||
skip: "\\\\."
|
||||
rules:
|
||||
- constant.specialChar: "\\\\([\"'bfnrst\\x24\\\\]|u[a-fA-F0-9]{4})"
|
||||
- identifier.var: "\\x24[\\w\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\uFFFE]+([.][a-zA-Z0-9_\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\uFFFE]+)*"
|
||||
- identifier: "\\x24[{].*[}]"
|
||||
|
||||
# Triple-double-quoted strings
|
||||
- constant.string:
|
||||
start: "\"\"\""
|
||||
end: "\"\"\""
|
||||
skip: "\\\\."
|
||||
rules:
|
||||
- constant.specialChar: "\\\\([\"'bfnrst\\x24\\\\]|u[a-fA-F0-9]{4})"
|
||||
- identifier.var: "\\x24[\\w\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\uFFFE]+([.][a-zA-Z0-9_\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\uFFFE]+)*"
|
||||
- identifier:
|
||||
start: "[$][{]"
|
||||
end: "[}]"
|
||||
rules: []
|
||||
|
||||
# Triple-single-quoted strings
|
||||
- constant.string:
|
||||
start: "'''"
|
||||
end: "'''"
|
||||
skip: "\\\\."
|
||||
rules:
|
||||
- constant.specialChar: "\\\\([\"'bfnrst\\x24\\\\]|u[a-fA-F0-9]{4})"
|
||||
|
||||
# Slashy strings are left out, because they match in unwanted places pretty much all the time
|
||||
# Dollar-slashy strings
|
||||
- constant.string:
|
||||
start: "[$]/"
|
||||
end: "/[$]"
|
||||
rules: []
|
||||
|
||||
# Single-line comments
|
||||
- comment:
|
||||
start: "//"
|
||||
end: "$"
|
||||
rules:
|
||||
- todo: "(TODO|XXX|FIXME):?"
|
||||
|
||||
# Multiline comments
|
||||
- comment:
|
||||
start: "/[*]"
|
||||
end: "[*]/"
|
||||
rules:
|
||||
- todo: "(TODO|XXX|FIXME):?"
|
||||
|
||||
# Groovydoc comments
|
||||
- comment:
|
||||
start: "/[*][*]@?"
|
||||
end: "[*]/"
|
||||
rules: []
|
||||
@@ -5,7 +5,9 @@ detect:
|
||||
|
||||
rules:
|
||||
# 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: "[ ](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)[ ]"
|
||||
- statement: "(^data|^foreign|^import|^infix|^infixl|^infixr|^instance|^module|^newtype|^type)[ ]"
|
||||
- statement: "[ ](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$)"
|
||||
|
||||
# Various symbols
|
||||
- symbol: "(\\||@|!|:|_|~|=|\\\\|;|\\(\\)|,|\\[|\\]|\\{|\\})"
|
||||
|
||||
@@ -20,7 +20,7 @@ rules:
|
||||
- statement: "\\b(async|await|break|case|catch|const|continue|debugger|default)\\b"
|
||||
- statement: "\\b(delete|do|else|export|finally|for|function\\*?|class|extends)\\b"
|
||||
- statement: "\\b(get|if|import|from|in|of|instanceof|let|new|reject|resolve|return)\\b"
|
||||
- statement: "\\b(set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\\b"
|
||||
- statement: "\\b(set|super|switch|this|throw|try|typeof|var|void|while|with|yield)\\b"
|
||||
# reserved but unassigned
|
||||
- error: "\\b(enum|implements|interface|package|private|protected|public)"
|
||||
- constant: "\\b(globalThis|Infinity|null|undefined|NaN)\\b"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user